diff --git a/src/web/App.mjs b/src/web/App.mjs index 911f51b8..9f43a9d2 100755 --- a/src/web/App.mjs +++ b/src/web/App.mjs @@ -246,8 +246,6 @@ class App { /** * Sets up the adjustable splitter to allow the user to resize areas of the page. - * - * @param {boolean} [minimise=false] - Set this flag if attempting to minimise frames to 0 width */ buildUI() { if (this.isMobileView()) { @@ -259,6 +257,8 @@ class App { /** * Set desktop splitters + * + * @param {boolean} minimise */ setDesktopSplitter(minimise) { if (this.columnSplitter) this.columnSplitter.destroy(); @@ -269,7 +269,7 @@ class App { minSize: minimise ? [0, 0, 0] : [360, 330, 310], gutterSize: 4, expandToMin: true, - onDrag: debounce(function() { + onDrag: debounce(() => { this.adjustComponentSizes(); }, 50, "dragSplitter", this, []) }); diff --git a/src/web/HTMLOperation.mjs b/src/web/HTMLOperation.mjs deleted file mode 100755 index 0b076c34..00000000 --- a/src/web/HTMLOperation.mjs +++ /dev/null @@ -1,83 +0,0 @@ -/** - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - */ - -/** - * Object to handle the creation of operations. - */ -class HTMLOperation { - - /** - * HTMLOperation constructor. - * - * @param {string} name - The name of the operation. - * @param {Object} config - The configuration object for this operation. - * @param {App} app - The main view object for CyberChef. - * @param {Manager} manager - The CyberChef event manager. - */ - constructor(name, config, app, manager) { - this.app = app; - this.manager = manager; - - this.name = name; - this.description = config.description; - this.infoURL = config.infoURL; - this.manualBake = config.manualBake || false; - this.config = config; - } - - - /** - * Highlights searched strings in the name and description of the operation. - * - * @param {[[number]]} nameIdxs - Indexes of the search strings in the operation name [[start, length]] - * @param {[[number]]} descIdxs - Indexes of the search strings in the operation description [[start, length]] - */ - highlightSearchStrings(nameIdxs, descIdxs) { - if (nameIdxs.length && typeof nameIdxs[0][0] === "number") { - let opName = "", - pos = 0; - - nameIdxs.forEach(idxs => { - const [start, length] = idxs; - if (typeof start !== "number") return; - opName += this.name.slice(pos, start) + "" + - this.name.slice(start, start + length) + ""; - pos = start + length; - }); - opName += this.name.slice(pos, this.name.length); - this.name = opName; - } - - if (this.description && descIdxs.length && descIdxs[0][0] >= 0) { - // Find HTML tag offsets - const re = /<[^>]+>/g; - let match; - while ((match = re.exec(this.description))) { - // If the search string occurs within an HTML tag, return without highlighting it. - const inHTMLTag = descIdxs.reduce((acc, idxs) => { - const start = idxs[0]; - return start >= match.index && start <= (match.index + match[0].length); - }, false); - - if (inHTMLTag) return; - } - - let desc = "", - pos = 0; - - descIdxs.forEach(idxs => { - const [start, length] = idxs; - desc += this.description.slice(pos, start) + "" + - this.description.slice(start, start + length) + ""; - pos = start + length; - }); - desc += this.description.slice(pos, this.description.length); - this.description = desc; - } - } -} - -export default HTMLOperation; diff --git a/src/web/TODO.md b/src/web/TODO.md index d5ba0870..f7b669d5 100644 --- a/src/web/TODO.md +++ b/src/web/TODO.md @@ -1,5 +1,8 @@ - ignore dropped item outside of rec-list -- search-results dropdown - can only drag an op to favourites 1 time - stupid popovers on deleting favs for instance ( dont always close nicely ) - UI tests etc. +- esc on selected-op search results will add that op to recipe +- highlight strings +- initial search is kinda slow + diff --git a/src/web/components/c-operation-li.mjs b/src/web/components/c-operation-li.mjs index 365a6a2e..112d4b26 100644 --- a/src/web/components/c-operation-li.mjs +++ b/src/web/components/c-operation-li.mjs @@ -50,7 +50,7 @@ export class COperationLi extends HTMLElement { } /** - * @fires OperationsWaiter#operationDblclick on double click + * Handle double click * @param {Event} e */ handleDoubleClick(e) { @@ -72,7 +72,6 @@ export class COperationLi extends HTMLElement { } } - /** * Disable or enable popover for an element * @@ -252,6 +251,57 @@ export class COperationLi extends HTMLElement { const { app, name, icon, includeStarIcon } = this; return new COperationLi( app, name, icon, includeStarIcon ); } + + + /** + * Highlights searched strings in the name and description of the operation. + * + * @param {[[number]]} nameIdxs - Indexes of the search strings in the operation name [[start, length]] + * @param {[[number]]} descIdxs - Indexes of the search strings in the operation description [[start, length]] + */ + highlightSearchStrings(nameIdxs, descIdxs) { + if (nameIdxs.length && typeof nameIdxs[0][0] === "number") { + let opName = "", + pos = 0; + + nameIdxs.forEach(idxs => { + const [start, length] = idxs; + if (typeof start !== "number") return; + opName += this.name.slice(pos, start) + "" + + this.name.slice(start, start + length) + ""; + pos = start + length; + }); + opName += this.name.slice(pos, this.name.length); + this.name = opName; + } + + if (this.description && descIdxs.length && descIdxs[0][0] >= 0) { + // Find HTML tag offsets + const re = /<[^>]+>/g; + let match; + while ((match = re.exec(this.description))) { + // If the search string occurs within an HTML tag, return without highlighting it. + const inHTMLTag = descIdxs.reduce((acc, idxs) => { + const start = idxs[0]; + return start >= match.index && start <= (match.index + match[0].length); + }, false); + + if (inHTMLTag) return; + } + + let desc = "", + pos = 0; + + descIdxs.forEach(idxs => { + const [start, length] = idxs; + desc += this.description.slice(pos, start) + "" + + this.description.slice(start, start + length) + ""; + pos = start + length; + }); + desc += this.description.slice(pos, this.description.length); + this.description = desc; + } + } } diff --git a/src/web/components/c-operation-list.mjs b/src/web/components/c-operation-list.mjs index 17944b53..d922a6ec 100644 --- a/src/web/components/c-operation-list.mjs +++ b/src/web/components/c-operation-list.mjs @@ -16,8 +16,7 @@ export class COperationList extends HTMLElement { includeStarIcon, isSortable = false, isCloneable = true, - icon, - + icon ) { super(); @@ -118,7 +117,6 @@ export class COperationList extends HTMLElement { .off(".popover") .removeData("bs.popover"); }, - // @todo: popovers dont display anymore after dragging into recipe list and then hovering the op onEnd: ({item}) => { if (item.parentNode.id === targetListId) { this.app.manager.recipe.addOperation(item.name); diff --git a/src/web/html/index.html b/src/web/html/index.html index 77647e2d..ddd47add 100755 --- a/src/web/html/index.html +++ b/src/web/html/index.html @@ -204,12 +204,12 @@ class="form-control" placeholder="Search..." autocomplete="off" - tabindex="2" + tabindex="0" data-help-title="Searching for operations" data-help="

Use the search box to find useful operations.

Both operation names and descriptions are queried using a fuzzy matching algorithm.

" /> -
- +
+
diff --git a/src/web/stylesheets/components/_operation.css b/src/web/stylesheets/components/_operation.css index 56b581d3..05325f87 100755 --- a/src/web/stylesheets/components/_operation.css +++ b/src/web/stylesheets/components/_operation.css @@ -311,13 +311,13 @@ input.toggle-string { .break .form-group * { color: var(--breakpoint-font-colour) !important; } -.selected-op { - color: var(--selected-operation-font-color) !important; +li.operation.selected-op { + /*color: var(--selected-operation-font-color) !important;*/ background-color: var(--selected-operation-bg-colour) !important; - border-color: var(--selected-operation-border-colour) !important; + /*border-color: var(--selected-operation-border-colour) !important;*/ } -.selected-op .form-group * { color: var(--selected-operation-font-color) !important; } +/*.selected-op .form-group * { color: var(--selected-operation-font-color) !important; }*/ .flow-control-op { color: var(--fc-operation-font-colour) !important; diff --git a/src/web/waiters/OperationsWaiter.mjs b/src/web/waiters/OperationsWaiter.mjs index 376e66e0..b2463a3a 100755 --- a/src/web/waiters/OperationsWaiter.mjs +++ b/src/web/waiters/OperationsWaiter.mjs @@ -4,9 +4,9 @@ * @license Apache-2.0 */ -import HTMLOperation from "../HTMLOperation.mjs"; import {fuzzyMatch, calcMatchRanges} from "../../core/lib/FuzzyMatch.mjs"; import {COperationList} from "../components/c-operation-list.mjs"; +import {COperationLi} from "../components/c-operation-li.mjs"; /** * Waiter to handle events related to the operations. @@ -46,9 +46,9 @@ class OperationsWaiter { } } - if (e.type === "search" || e.keyCode === 13) { // Search or Return + if (e.type === "search" || e.key === "Enter") { // Search or Return ( enter ) e.preventDefault(); - ops = document.querySelectorAll("#search-results li"); + ops = document.querySelectorAll("#search-results c-operation-list c-operation-li li"); if (ops.length) { selected = this.getSelectedOp(ops); if (selected > -1) { @@ -59,11 +59,11 @@ class OperationsWaiter { if (e.type === "click" && !e.target.value.length) { this.openOpsDropdown(); - } else if (e.keyCode === 27) { // Escape - this.closeOpsDropdown(); - } else if (e.keyCode === 40) { // Down + } else if (e.key === "Escape") { // Escape + this.closeOpsDropdown() + } else if (e.key === "ArrowDown") { // Down e.preventDefault(); - ops = document.querySelectorAll("#search-results li"); + ops = document.querySelectorAll("#search-results c-operation-list c-operation-li li"); if (ops.length) { selected = this.getSelectedOp(ops); if (selected > -1) { @@ -72,9 +72,9 @@ class OperationsWaiter { if (selected === ops.length-1) selected = -1; ops[selected+1].classList.add("selected-op"); } - } else if (e.keyCode === 38) { // Up + } else if (e.key === "ArrowUp") { // Up e.preventDefault(); - ops = document.querySelectorAll("#search-results li"); + ops = document.querySelectorAll("#search-results c-operation-list c-operation-li li"); if (ops.length) { selected = this.getSelectedOp(ops); if (selected > -1) { @@ -96,13 +96,29 @@ class OperationsWaiter { } $("#categories .show").collapse("hide"); + if (str) { const matchedOps = this.filterOperations(str, true); - const matchedOpsHtml = matchedOps - .map(v => v.toStubHtml()) - .join(""); + let formattedOpNames = []; - searchResultsEl.innerHTML = matchedOpsHtml; + matchedOps.forEach((operation) => { + formattedOpNames.push(operation.name.replace(/(<([^>]+)>)/ig, "")); + }) + + const cOpList = new COperationList( + this.app, + formattedOpNames, + true, + false, + true, + { + class: "check-icon", + innerText: "check" + } + ); + + cOpList.build(); + searchResultsEl.append(cOpList); } this.manager.ops.updateListItemsClasses("#rec-list", "selected"); @@ -136,7 +152,14 @@ class OperationsWaiter { const descPos = op.description.toLowerCase().indexOf(inStr.toLowerCase()); if (nameMatch || descPos >= 0) { - const operation = new HTMLOperation(opName, this.app.operations[opName], this.app, this.manager); + const operation = new COperationLi( + this.app, + opName, + { + class: "check-icon", + innerText: "check" + }, + true ); if (highlight) { operation.highlightSearchStrings(calcMatchRanges(idxs), [[descPos, inStr.length]]); diff --git a/src/web/waiters/RecipeWaiter.mjs b/src/web/waiters/RecipeWaiter.mjs index 78c9106b..6ff2f37a 100755 --- a/src/web/waiters/RecipeWaiter.mjs +++ b/src/web/waiters/RecipeWaiter.mjs @@ -4,7 +4,6 @@ * @license Apache-2.0 */ -import HTMLOperation from "../HTMLOperation.mjs"; import Sortable from "sortablejs"; import Utils from "../../core/Utils.mjs"; import {escapeControlChars} from "../utils/editorUtils.mjs"; @@ -35,7 +34,7 @@ class RecipeWaiter { initDragAndDrop() { const recList = document.getElementById("rec-list"); - let swapThreshold = this.app.isMobileView() ? 0.60 : 0.40; + let swapThreshold = this.app.isMobileView() ? 0.60 : 0.10; let animation = this.app.isMobileView() ? 400 : 200; let delay = this.app.isMobileView() ? 200 : 0;