diff --git a/src/web/App.mjs b/src/web/App.mjs
index 9f43a9d2..f66dd9b6 100755
--- a/src/web/App.mjs
+++ b/src/web/App.mjs
@@ -265,7 +265,7 @@ class App {
if (this.ioSplitter) this.ioSplitter.destroy();
this.columnSplitter = Split(["#operations", "#recipe", "#IO"], {
- sizes: [20, 30, 50],
+ sizes: [20, 40, 40],
minSize: minimise ? [0, 0, 0] : [360, 330, 310],
gutterSize: 4,
expandToMin: true,
@@ -510,7 +510,7 @@ class App {
// Search for nearest match and add it
const matchedOps = this.manager.ops.filterOperations(this.uriParams.op, false);
if (matchedOps.length) {
- this.manager.recipe.addOperation(matchedOps[0].name);
+ this.manager.recipe.addOperation(matchedOps[0][0]);
}
// Populate search with the string
diff --git a/src/web/Manager.mjs b/src/web/Manager.mjs
index 59590e0c..64884156 100755
--- a/src/web/Manager.mjs
+++ b/src/web/Manager.mjs
@@ -144,7 +144,7 @@ class Manager {
document.getElementById("maximise-output").addEventListener("click", this.controls.onMaximiseButtonClick.bind(this.controls));
// Operations
- this.addMultiEventListener("#search", "keyup paste search click", this.ops.searchOperations, this.ops);
+ this.addMultiEventListener("#search", "keyup paste click", this.ops.searchOperations, this.ops);
document.getElementById("close-ops-dropdown-icon").addEventListener("click", this.ops.closeOpsDropdown.bind(this.ops));
document.getElementById("save-favourites").addEventListener("click", this.ops.saveFavouritesClick.bind(this.ops));
document.getElementById("reset-favourites").addEventListener("click", this.ops.resetFavouritesClick.bind(this.ops));
diff --git a/src/web/TODO.md b/src/web/TODO.md
index f7b669d5..e69de29b 100644
--- a/src/web/TODO.md
+++ b/src/web/TODO.md
@@ -1,8 +0,0 @@
-- ignore dropped item outside of rec-list
-- 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-category-li.mjs b/src/web/components/c-category-li.mjs
index 8a49050b..baa1cf50 100644
--- a/src/web/components/c-category-li.mjs
+++ b/src/web/components/c-category-li.mjs
@@ -64,7 +64,7 @@ export class CCategoryLi extends HTMLElement {
const opList = new COperationList(
this.app,
- this.category.ops,
+ this.category.ops.map( op => [op]),
this.includeOpLiStarIcon,
false,
true
@@ -81,7 +81,6 @@ export class CCategoryLi extends HTMLElement {
buildListItem() {
const li = document.createElement("li");
- li.classList.add("panel");
li.classList.add("category");
return li;
diff --git a/src/web/components/c-operation-li.mjs b/src/web/components/c-operation-li.mjs
index 112d4b26..f0acceb5 100644
--- a/src/web/components/c-operation-li.mjs
+++ b/src/web/components/c-operation-li.mjs
@@ -1,5 +1,4 @@
import url from "url";
-import HTMLIngredient from "../HTMLIngredient.mjs";
/**
* c(ustom element)-operation-li ( list item )
@@ -8,6 +7,7 @@ import HTMLIngredient from "../HTMLIngredient.mjs";
* @param {string} name - The name of the operation
* @param {Object} icon - { class: string, innerText: string } - The optional and customizable icon displayed on the right side of the operation
* @param {Boolean} includeStarIcon - Include the left side 'star' icon to favourite an operation easily
+ * @param {[number[]]} charIndicesToHighlight - optional array of indices that indicate characters to highlight (bold) in operation name
*/
export class COperationLi extends HTMLElement {
constructor(
@@ -15,6 +15,7 @@ export class COperationLi extends HTMLElement {
name,
icon,
includeStarIcon,
+ charIndicesToHighlight = []
) {
super();
@@ -22,6 +23,7 @@ export class COperationLi extends HTMLElement {
this.name = name;
this.icon = icon;
this.includeStarIcon = includeStarIcon;
+ this.charIndicesToHighlight = charIndicesToHighlight;
this.config = this.app.operations[name];
@@ -54,7 +56,8 @@ export class COperationLi extends HTMLElement {
* @param {Event} e
*/
handleDoubleClick(e) {
- if (e.target === this.querySelector("li")) {
+ // Span contains operation title (highlighted or not)
+ if (e.target === this.querySelector("li") || e.target === this.querySelector("span")) {
this.app.manager.recipe.addOperation(this.name);
}
}
@@ -169,6 +172,8 @@ export class COperationLi extends HTMLElement {
buildListItem() {
const li = document.createElement("li");
+ li.appendChild( this.buildOperationName() );
+
li.setAttribute("data-name", this.name);
li.classList.add("operation");
@@ -176,7 +181,6 @@ export class COperationLi extends HTMLElement {
li.classList.add("favourite");
}
- li.textContent = this.name;
if (this.config.description){
let dataContent = this.config.description;
@@ -193,7 +197,6 @@ export class COperationLi extends HTMLElement {
li.setAttribute("data-boundary", "viewport");
li.setAttribute("data-content", dataContent);
}
-
return li;
}
@@ -248,59 +251,62 @@ export class COperationLi extends HTMLElement {
* with constructor arguments for sortable and cloneable lists
*/
cloneNode() {
- const { app, name, icon, includeStarIcon } = this;
- return new COperationLi( app, name, icon, includeStarIcon );
+ const { app, name, icon, includeStarIcon, charIndicesToHighlight } = this;
+ return new COperationLi( app, name, icon, includeStarIcon, charIndicesToHighlight );
}
/**
* 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") {
+ buildOperationName() {
+ const span = document.createElement('span');
+
+ if (this.charIndicesToHighlight.length) {
let opName = "",
pos = 0;
- nameIdxs.forEach(idxs => {
+ this.charIndicesToHighlight.forEach(idxs => {
const [start, length] = idxs;
if (typeof start !== "number") return;
- opName += this.name.slice(pos, start) + "" +
- this.name.slice(start, start + length) + "";
+ 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;
+ span.innerHTML = opName;
+ } else {
+ span.innerText = this.name;
}
- 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);
+ return span;
- 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;
- }
+ // 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 d922a6ec..9bfb586a 100644
--- a/src/web/components/c-operation-list.mjs
+++ b/src/web/components/c-operation-list.mjs
@@ -5,14 +5,14 @@ import Sortable from "sortablejs";
* c(ustom element)-operation-list
*
* @param {App} app - The main view object for CyberChef
- * @param {string[]} opNames - A list of operation names
+ * @param {[string, number[]]} operations - A list of operation names and indexes of characters to highlight
* @param {boolean} includeStarIcon - optionally add the 'star' icon to the left of the operation
* @param {Object} icon ( { class: string, innerText: string } ). check-icon by default
*/
export class COperationList extends HTMLElement {
constructor(
app,
- opNames,
+ operations,
includeStarIcon,
isSortable = false,
isCloneable = true,
@@ -21,7 +21,7 @@ export class COperationList extends HTMLElement {
super();
this.app = app;
- this.opNames = opNames;
+ this.operations = operations;
this.includeStarIcon = includeStarIcon;
this.isSortable = isSortable;
this.isCloneable = isCloneable;
@@ -35,7 +35,7 @@ export class COperationList extends HTMLElement {
const ul = document.createElement("ul");
ul.classList.add("op-list");
- this.opNames.forEach((opName => {
+ this.operations.forEach((([opName, charIndicesToHighlight]) => {
const cOpLi = new COperationLi(
this.app,
opName,
@@ -43,7 +43,8 @@ export class COperationList extends HTMLElement {
class: this.icon ? this.icon.class : "check-icon",
innerText: this.icon ? this.icon.innerText : "check"
},
- this.includeStarIcon
+ this.includeStarIcon,
+ charIndicesToHighlight
);
ul.appendChild(cOpLi);
diff --git a/src/web/stylesheets/components/_operation.css b/src/web/stylesheets/components/_operation.css
index 05325f87..7eb02521 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; }
-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;*/
+c-operation-li li.operation.focused-op {
+ color: var(--focused-operation-font-color) !important;
+ background-color: var(--focused-operation-bg-colour) !important;
+ border-color: var(--focused-operation-border-colour) !important;
}
-/*.selected-op .form-group * { color: var(--selected-operation-font-color) !important; }*/
+/*.focused-op .form-group * { color: var(--focused-operation-font-color) !important; }*/
.flow-control-op {
color: var(--fc-operation-font-colour) !important;
diff --git a/src/web/stylesheets/components/_recipe.css b/src/web/stylesheets/components/_recipe.css
index 5ee46615..3b8aba68 100644
--- a/src/web/stylesheets/components/_recipe.css
+++ b/src/web/stylesheets/components/_recipe.css
@@ -27,3 +27,4 @@
#rec-list li.sortable-chosen{
filter: brightness(0.8);
}
+
diff --git a/src/web/stylesheets/themes/_classic.css b/src/web/stylesheets/themes/_classic.css
index 4d5c2b43..e3fee882 100755
--- a/src/web/stylesheets/themes/_classic.css
+++ b/src/web/stylesheets/themes/_classic.css
@@ -54,11 +54,12 @@
--rec-list-operation-bg-colour: #dff0d8;
--rec-list-operation-border-colour: #d3e8c0;
- --selected-operation-font-color: #c09853;
- --selected-operation-bg-colour: #d5ebf5;
- --selected-operation-border-colour: #fbeed5;
+ --focused-operation-font-color: #c09853;
+ --focused-operation-bg-colour: #fcf8e3;
+ --focused-operation-border-colour: #fbeed5;
+
+ --selected-operation-bg-colour: #d5ebf5;
- /*mobile UI: selected operation checkmark*/
--checkmark-color: var(--op-list-operation-font-colour);
--breakpoint-font-colour: #b94a48;
diff --git a/src/web/stylesheets/themes/_dark.css b/src/web/stylesheets/themes/_dark.css
index 37fa6381..d914e7e7 100755
--- a/src/web/stylesheets/themes/_dark.css
+++ b/src/web/stylesheets/themes/_dark.css
@@ -50,9 +50,11 @@
--rec-list-operation-bg-colour: #252525;
--rec-list-operation-border-colour: #444;
- --selected-operation-font-color: #c5c5c5;
+ --focused-operation-font-color: #c5c5c5;
+ --focused-operation-bg-colour: #475663;
+ --focused-operation-border-colour: #444;
+
--selected-operation-bg-colour: #3f3f3f;
- --selected-operation-border-colour: #444;
--checkmark-color: #47d047;
diff --git a/src/web/stylesheets/themes/_geocities.css b/src/web/stylesheets/themes/_geocities.css
index fcf3cdb5..2e96a836 100755
--- a/src/web/stylesheets/themes/_geocities.css
+++ b/src/web/stylesheets/themes/_geocities.css
@@ -50,9 +50,9 @@
--rec-list-operation-bg-colour: purple;
--rec-list-operation-border-colour: green;
- --selected-operation-font-color: white;
- --selected-operation-bg-colour: pink;
- --selected-operation-border-colour: blue;
+ --focused-operation-font-color: white;
+ --focused-operation-bg-colour: pink;
+ --focused-operation-border-colour: blue;
--breakpoint-font-colour: white;
--breakpoint-bg-colour: red;
diff --git a/src/web/stylesheets/themes/_solarizedDark.css b/src/web/stylesheets/themes/_solarizedDark.css
index 5bb18d2e..724e61b9 100755
--- a/src/web/stylesheets/themes/_solarizedDark.css
+++ b/src/web/stylesheets/themes/_solarizedDark.css
@@ -69,9 +69,9 @@
--rec-list-operation-bg-colour: var(--base02);
--rec-list-operation-border-colour: var(--base01);
- --selected-operation-font-color: var(--base1);
- --selected-operation-bg-colour: var(--base02);
- --selected-operation-border-colour: var(--base01);
+ --focused-operation-font-color: var(--base1);
+ --focused-operation-bg-colour: var(--base02);
+ --focused-operation-border-colour: var(--base01);
--breakpoint-font-colour: var(--sol-red);
--breakpoint-bg-colour: var(--base02);
diff --git a/src/web/stylesheets/themes/_solarizedLight.css b/src/web/stylesheets/themes/_solarizedLight.css
index f884c3e8..11b8778c 100755
--- a/src/web/stylesheets/themes/_solarizedLight.css
+++ b/src/web/stylesheets/themes/_solarizedLight.css
@@ -69,9 +69,9 @@
--rec-list-operation-bg-colour: var(--base2);
--rec-list-operation-border-colour: var(--base1);
- --selected-operation-font-color: var(--base01);
- --selected-operation-bg-colour: var(--base2);
- --selected-operation-border-colour: var(--base1);
+ --focused-operation-font-color: var(--base01);
+ --focused-operation-bg-colour: var(--base2);
+ --focused-operation-border-colour: var(--base1);
--breakpoint-font-colour: var(--sol-red);
--breakpoint-bg-colour: var(--base2);
diff --git a/src/web/waiters/OperationsWaiter.mjs b/src/web/waiters/OperationsWaiter.mjs
index b2463a3a..9d5cb70e 100755
--- a/src/web/waiters/OperationsWaiter.mjs
+++ b/src/web/waiters/OperationsWaiter.mjs
@@ -34,8 +34,7 @@ class OperationsWaiter {
* @param {Event} e
*/
searchOperations(e) {
- let ops, selected;
-
+ let ops, focused;
if (e.type === "keyup") {
const searchResults = document.getElementById("search-results");
@@ -46,13 +45,13 @@ class OperationsWaiter {
}
}
- if (e.type === "search" || e.key === "Enter") { // Search or Return ( enter )
+ if (e.key === "Enter") { // Search or Return ( enter )
e.preventDefault();
ops = document.querySelectorAll("#search-results c-operation-list c-operation-li li");
if (ops.length) {
- selected = this.getSelectedOp(ops);
- if (selected > -1) {
- this.manager.recipe.addOperation(ops[selected].getAttribute("data-name"));
+ focused = this.getFocusedOp(ops);
+ if (focused > -1) {
+ this.manager.recipe.addOperation(ops[focused].getAttribute("data-name"));
}
}
}
@@ -60,28 +59,28 @@ class OperationsWaiter {
if (e.type === "click" && !e.target.value.length) {
this.openOpsDropdown();
} else if (e.key === "Escape") { // Escape
- this.closeOpsDropdown()
+ this.closeOpsDropdown();
} else if (e.key === "ArrowDown") { // Down
e.preventDefault();
ops = document.querySelectorAll("#search-results c-operation-list c-operation-li li");
if (ops.length) {
- selected = this.getSelectedOp(ops);
- if (selected > -1) {
- ops[selected].classList.remove("selected-op");
+ focused = this.getFocusedOp(ops);
+ if (focused > -1) {
+ ops[focused].classList.remove("focused-op");
}
- if (selected === ops.length-1) selected = -1;
- ops[selected+1].classList.add("selected-op");
+ if (focused === ops.length-1) focused = -1;
+ ops[focused+1].classList.add("focused-op");
}
} else if (e.key === "ArrowUp") { // Up
e.preventDefault();
ops = document.querySelectorAll("#search-results c-operation-list c-operation-li li");
if (ops.length) {
- selected = this.getSelectedOp(ops);
- if (selected > -1) {
- ops[selected].classList.remove("selected-op");
+ focused = this.getFocusedOp(ops);
+ if (focused > -1) {
+ ops[focused].classList.remove("focused-op");
}
- if (selected === 0) selected = ops.length;
- ops[selected-1].classList.add("selected-op");
+ if (focused === 0) focused = ops.length;
+ ops[focused-1].classList.add("focused-op");
}
} else {
const searchResultsEl = document.getElementById("search-results");
@@ -99,15 +98,10 @@ class OperationsWaiter {
if (str) {
const matchedOps = this.filterOperations(str, true);
- let formattedOpNames = [];
-
- matchedOps.forEach((operation) => {
- formattedOpNames.push(operation.name.replace(/(<([^>]+)>)/ig, ""));
- })
const cOpList = new COperationList(
this.app,
- formattedOpNames,
+ matchedOps,
true,
false,
true,
@@ -132,15 +126,15 @@ class OperationsWaiter {
* @param {string} searchStr
* @param {boolean} highlight - Whether to highlight the matching string in the operation
* name and description
- * @returns {string[]}
+ * @returns {[[string, number[]]]}
*/
- filterOperations(inStr, highlight) {
+ filterOperations(searchStr, highlight) {
const matchedOps = [];
const matchedDescs = [];
// Create version with no whitespace for the fuzzy match
// Helps avoid missing matches e.g. query "TCP " would not find "Parse TCP"
- const inStrNWS = inStr.replace(/\s/g, "");
+ const inStrNWS = searchStr.replace(/\s/g, "");
for (const opName in this.app.operations) {
const op = this.app.operations[opName];
@@ -149,26 +143,13 @@ class OperationsWaiter {
const [nameMatch, score, idxs] = fuzzyMatch(inStrNWS, opName);
// Match description based on exact match
- const descPos = op.description.toLowerCase().indexOf(inStr.toLowerCase());
+ const descPos = op.description.toLowerCase().indexOf(searchStr.toLowerCase());
if (nameMatch || descPos >= 0) {
- const operation = new COperationLi(
- this.app,
- opName,
- {
- class: "check-icon",
- innerText: "check"
- },
- true );
-
- if (highlight) {
- operation.highlightSearchStrings(calcMatchRanges(idxs), [[descPos, inStr.length]]);
- }
-
if (nameMatch) {
- matchedOps.push([operation, score]);
+ matchedOps.push([[opName, calcMatchRanges(idxs)], score]);
} else {
- matchedDescs.push(operation);
+ matchedDescs.push([opName]);
}
}
}
@@ -181,15 +162,15 @@ class OperationsWaiter {
/**
- * Finds the operation which has been selected using keyboard shortcuts. This will have the class
- * 'selected-op' set. Returns the index of the operation within the given list.
+ * Finds the operation which has been focused on using keyboard shortcuts. This will have the class
+ * 'focused-op' set. Returns the index of the operation within the given list.
*
* @param {element[]} ops
* @returns {number}
*/
- getSelectedOp(ops) {
+ getFocusedOp(ops) {
for (let i = 0; i < ops.length; i++) {
- if (ops[i].classList.contains("selected-op")) {
+ if (ops[i].classList.contains("focused-op")) {
return i;
}
}
@@ -216,7 +197,7 @@ class OperationsWaiter {
if(favCatConfig !== undefined) {
const opList = new COperationList(
this.app,
- favCatConfig.ops,
+ favCatConfig.ops.map( op => [op]),
false,
true,
false,