mirror of
https://github.com/gchq/CyberChef.git
synced 2025-05-07 06:57:12 -04:00
search results functionality
This commit is contained in:
parent
4ba31236e9
commit
85fff21068
9 changed files with 105 additions and 115 deletions
|
@ -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, [])
|
||||
});
|
||||
|
|
|
@ -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) + "<b>" +
|
||||
this.name.slice(start, start + length) + "</b>";
|
||||
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) + "<b><u>" +
|
||||
this.description.slice(start, start + length) + "</u></b>";
|
||||
pos = start + length;
|
||||
});
|
||||
desc += this.description.slice(pos, this.description.length);
|
||||
this.description = desc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default HTMLOperation;
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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) + "<b>" +
|
||||
this.name.slice(start, start + length) + "</b>";
|
||||
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) + "<b><u>" +
|
||||
this.description.slice(start, start + length) + "</u></b>";
|
||||
pos = start + length;
|
||||
});
|
||||
desc += this.description.slice(pos, this.description.length);
|
||||
this.description = desc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -204,12 +204,12 @@
|
|||
class="form-control"
|
||||
placeholder="Search..."
|
||||
autocomplete="off"
|
||||
tabindex="2"
|
||||
tabindex="0"
|
||||
data-help-title="Searching for operations"
|
||||
data-help="<p>Use the search box to find useful operations.</p><p>Both operation names and descriptions are queried using a fuzzy matching algorithm.</p>"
|
||||
/>
|
||||
<div id="operations-dropdown">
|
||||
<ul id="search-results" class="op-list hidden"></ul>
|
||||
<div id="operations-dropdown" tabindex="0">
|
||||
<div id="search-results" class="hidden" tabindex="0"></div>
|
||||
<div id="categories" class="panel-group no-select hidden"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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]]);
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue