From f15ad10b94df36f1430456024d09c2874bb35671 Mon Sep 17 00:00:00 2001 From: Allan Leary Date: Tue, 14 Jul 2026 10:31:12 +0100 Subject: [PATCH] Add operation category display in popovers - Add 'Show operation category in popover' option (enabled by default) - Display category names (excluding Favourites) in operation popovers - Make category names clickable to open the category in operations list - Clicking a category link clears search and expands the category - Add UI test for category popover functionality - Addresses issue gchq/CyberChef#1654 --- src/web/HTMLOperation.mjs | 36 +++++++++++++- src/web/Manager.mjs | 2 + src/web/html/index.html | 7 +++ src/web/index.js | 1 + src/web/waiters/OperationsWaiter.mjs | 62 +++++++++++++++++++++++ tests/browser/00_nightwatch.js | 73 ++++++++++++++++++++++++++++ 6 files changed, 180 insertions(+), 1 deletion(-) diff --git a/src/web/HTMLOperation.mjs b/src/web/HTMLOperation.mjs index 0ba0ffc8..8dcf1168 100755 --- a/src/web/HTMLOperation.mjs +++ b/src/web/HTMLOperation.mjs @@ -56,7 +56,8 @@ class HTMLOperation { if (this.description) { const infoLink = this.infoURL ? `
${titleFromWikiLink(this.infoURL)}` : ""; - const content = Utils.escapeHtml(this.description + infoLink); + const categoryInfo = this.getCategoryInfo(); + const content = Utils.escapeHtml(this.description + infoLink + categoryInfo); html += ` data-container='body' data-toggle='popover' data-placement='right' data-content="${content}" data-html='true' data-trigger='hover' @@ -75,6 +76,39 @@ class HTMLOperation { } + /** + * Gets the category information for this operation as an HTML string. + * + * @returns {string} + */ + getCategoryInfo() { + if (!this.app.options.showOpCategories) { + return ""; + } + + // Find all categories this operation belongs to, excluding Favourites + const categories = []; + for (let i = 0; i < this.app.categories.length; i++) { + const cat = this.app.categories[i]; + if (cat.name !== "Favourites" && cat.ops.includes(this.name)) { + categories.push(cat.name); + } + } + + if (categories.length === 0) { + return ""; + } + + // Build the category links + const categoryLinks = categories.map(catName => { + const catId = "cat" + catName.replace(/[\s/\-:_]/g, ""); + return `${catName}`; + }).join(", "); + + return `
Category: ${categoryLinks}`; + } + + /** * Renders the operation in HTML as a full operation with ingredients. * diff --git a/src/web/Manager.mjs b/src/web/Manager.mjs index 7cde638d..f2140335 100755 --- a/src/web/Manager.mjs +++ b/src/web/Manager.mjs @@ -232,6 +232,8 @@ class Manager { this.addDynamicListener(".option-item input[type=checkbox]#wordWrap", "change", this.options.setWordWrap, this.options); this.addDynamicListener(".option-item input[type=checkbox]#useMetaKey", "change", this.bindings.updateKeybList, this.bindings); this.addDynamicListener(".option-item input[type=checkbox]#showCatCount", "change", this.ops.setCatCount, this.ops); + this.addDynamicListener(".option-item input[type=checkbox]#showOpCategories", "change", this.ops.toggleOpCategories, this.ops); + this.addDynamicListener(".op-category-link", "click", this.ops.categoryLinkClick, this.ops); this.addDynamicListener(".option-item input[type=number]", "keyup", this.options.numberChange, this.options); this.addDynamicListener(".option-item input[type=number]", "change", this.options.numberChange, this.options); this.addDynamicListener(".option-item select", "change", this.options.selectChange, this.options); diff --git a/src/web/html/index.html b/src/web/html/index.html index 6ce8dd9a..f3670fbb 100755 --- a/src/web/html/index.html +++ b/src/web/html/index.html @@ -529,6 +529,13 @@ Show the number of operations in each category + +
+ +