mirror of
https://github.com/gchq/CyberChef.git
synced 2026-03-27 01:01:23 -07:00
Update to support added features since last master merge
- Operation counts - Allow toggling on favourite icon - Notify users that a single tap does nothing in the operations list - Fix odd checkbox label position on macos - Fix odd label pinning - History update now happens on drag operation removal - Show compile message rather than "download cyberchef" in banner
This commit is contained in:
parent
9d3567e0e6
commit
d020c53dfb
11 changed files with 157 additions and 89 deletions
|
|
@ -67,6 +67,7 @@ class App {
|
|||
|
||||
this.loadLocalStorage();
|
||||
this.buildCategoryList();
|
||||
this.setCompileMessage();
|
||||
this.buildUI();
|
||||
this.manager.setup();
|
||||
this.manager.output.saveBombe();
|
||||
|
|
@ -385,14 +386,20 @@ class App {
|
|||
this.updateFavourites(this.dfavourites, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the favourites category is expanded
|
||||
*/
|
||||
isFavouritesExpanded() {
|
||||
const catFavourites = document.getElementById("#catFavourites");
|
||||
return catFavourites ? catFavourites.classList.contains("show") : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an operation to the user's favourites.
|
||||
*
|
||||
* @param {string} name - The name of the operation
|
||||
* @param {Boolean} isExpanded - false by default
|
||||
*/
|
||||
addFavourite(name, isExpanded = false) {
|
||||
addFavourite(name) {
|
||||
const favourites = JSON.parse(localStorage.favourites);
|
||||
|
||||
if (favourites.indexOf(name) >= 0) {
|
||||
|
|
@ -401,7 +408,25 @@ class App {
|
|||
}
|
||||
|
||||
favourites.push(name);
|
||||
this.updateFavourites(favourites, isExpanded);
|
||||
this.updateFavourites(favourites, this.isFavouritesExpanded());
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an operation from the user's favourites.
|
||||
*
|
||||
* @param {string} name - The name of the operation
|
||||
*/
|
||||
removeFavourite(name) {
|
||||
const favourites = JSON.parse(localStorage.favourites);
|
||||
|
||||
const index = favourites.indexOf(name);
|
||||
if (index === -1) {
|
||||
this.alert(`'${name}' isn't in your favourites`, 3000);
|
||||
return;
|
||||
}
|
||||
|
||||
favourites.splice(index, 1);
|
||||
this.updateFavourites(favourites, this.isFavouritesExpanded());
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -810,11 +835,20 @@ class App {
|
|||
return isVisible ? elm.classList.remove("hidden") : elm.classList.add("hidden");
|
||||
}
|
||||
|
||||
/**
|
||||
* Set visibility of download link
|
||||
*
|
||||
* @param {boolean} isVisible
|
||||
*/
|
||||
setDownloadLinkVisibility(isVisible) {
|
||||
this.setElementVisibility(document.getElementById("download-wrapper"), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set desktop UI ( on init and on window resize events )
|
||||
*/
|
||||
setDesktopUI() {
|
||||
this.setCompileMessage();
|
||||
this.setDownloadLinkVisibility(true);
|
||||
this.setSplitter();
|
||||
|
||||
this.manager.input.calcMaxTabs();
|
||||
|
|
@ -825,7 +859,9 @@ class App {
|
|||
* Set mobile UI ( on init and on window resize events )
|
||||
*/
|
||||
setMobileUI() {
|
||||
this.setDownloadLinkVisibility(false);
|
||||
this.setSplitter(false);
|
||||
|
||||
this.assignAvailableHeight();
|
||||
$("[data-toggle=tooltip]").tooltip("disable");
|
||||
}
|
||||
|
|
@ -859,6 +895,9 @@ class App {
|
|||
* Build a CCategoryList component and append it to #categories
|
||||
*/
|
||||
buildCategoryList() {
|
||||
// populate total operations count
|
||||
document.querySelector("#operations .title .op-count").innerText = Object.keys(this.operations).length;
|
||||
|
||||
// double-check if the c-category-list already exists,
|
||||
if (document.querySelector("#categories > c-category-list")) {
|
||||
// then destroy it
|
||||
|
|
|
|||
|
|
@ -105,6 +105,10 @@ export class CCategoryLi extends HTMLElement {
|
|||
|
||||
a.innerText = this.label;
|
||||
|
||||
// Create wrapper for right-aligned controls
|
||||
const divRight = document.createElement("div");
|
||||
divRight.setAttribute("class", "category-title-right");
|
||||
|
||||
if (this.label === "Favourites") {
|
||||
const editFavouritesButton = this.buildEditFavouritesButton();
|
||||
|
||||
|
|
@ -119,9 +123,20 @@ export class CCategoryLi extends HTMLElement {
|
|||
<li><b>To remove:</b> Click on the 'Edit favourites' button and hit the delete button next to the operation you want to remove</li>
|
||||
</ul>`);
|
||||
|
||||
a.appendChild(editFavouritesButton);
|
||||
divRight.appendChild(editFavouritesButton);
|
||||
}
|
||||
|
||||
// Show count of operations in category
|
||||
const opCountSpan = document.createElement("span");
|
||||
opCountSpan.classList.add("op-count");
|
||||
if (!this.app.options.showCatCount) {
|
||||
opCountSpan.classList.add("hidden");
|
||||
}
|
||||
opCountSpan.innerText = this.category.ops.length;
|
||||
divRight.appendChild(opCountSpan);
|
||||
|
||||
a.appendChild(divRight);
|
||||
|
||||
return a;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import url from "url";
|
||||
import Utils from "../../core/Utils.mjs";
|
||||
|
||||
/**
|
||||
* c(ustom element)-operation-li ( list item )
|
||||
|
|
@ -37,6 +38,7 @@ export class COperationLi extends HTMLElement {
|
|||
// Use mousedown event instead of click to prevent accidentally firing the handler twice on mobile
|
||||
this.addEventListener("mousedown", this.handleMousedown.bind(this));
|
||||
this.addEventListener("dblclick", this.handleDoubleClick.bind(this));
|
||||
this.addEventListener("touchstart", this.handleTouchStart.bind(this));
|
||||
|
||||
if (this.includeStarIcon) {
|
||||
this.observer = new MutationObserver(this.updateFavourite.bind(this));
|
||||
|
|
@ -50,6 +52,7 @@ export class COperationLi extends HTMLElement {
|
|||
disconnectedCallback() {
|
||||
this.removeEventListener("mousedown", this.handleMousedown.bind(this));
|
||||
this.removeEventListener("dblclick", this.handleDoubleClick.bind(this));
|
||||
this.removeEventListener("touchstart", this.handleTouchStart.bind(this));
|
||||
|
||||
if (this.includeStarIcon) {
|
||||
this.observer.disconnect();
|
||||
|
|
@ -62,6 +65,7 @@ export class COperationLi extends HTMLElement {
|
|||
* Handle double click
|
||||
*/
|
||||
handleDoubleClick() {
|
||||
this.app.manager.ops.clearSingleTapAlerts();
|
||||
this.app.manager.recipe.addOperation(this.operationName);
|
||||
}
|
||||
|
||||
|
|
@ -72,13 +76,30 @@ export class COperationLi extends HTMLElement {
|
|||
*/
|
||||
handleMousedown(e) {
|
||||
if (e.target === this.querySelector("i.star-icon")) {
|
||||
this.app.addFavourite(this.operationName);
|
||||
}
|
||||
// current use case: in the 'Edit favourites' modal, the c-operation-li components have a trashcan icon to the
|
||||
// right
|
||||
if (e.target === this.querySelector("i.remove-icon")) {
|
||||
if (!this.isFavourite) {
|
||||
this.app.addFavourite(this.operationName);
|
||||
this.isFavourite = true;
|
||||
} else {
|
||||
this.app.removeFavourite(this.operationName);
|
||||
this.isFavourite = false;
|
||||
}
|
||||
} else if (e.target === this.querySelector("i.remove-icon")) {
|
||||
// current use case: in the 'Edit favourites' modal, the c-operation-li components have a trashcan icon to the
|
||||
// right
|
||||
this.remove();
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
// if we've handled another event, don't use this to trigger doubleclick
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
/**
|
||||
* If the user taps a single operation, alert them that doubletapping adds operation to recipe.
|
||||
* @param {TouchEvent} e
|
||||
*/
|
||||
handleTouchStart(e) {
|
||||
this.app.manager.ops.sendSingleTapAlert();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -157,9 +178,9 @@ export class COperationLi extends HTMLElement {
|
|||
pageTitle = "";
|
||||
|
||||
switch (urlObj.host) {
|
||||
case "forensicswiki.xyz":
|
||||
case "forensics.wiki":
|
||||
wikiName = "Forensics Wiki";
|
||||
pageTitle = urlObj.query.substr(6).replace(/_/g, " "); // Chop off 'title='
|
||||
pageTitle = Utils.toTitleCase(urlObj.path.replace(/\//g, "").replace(/_/g, " "));
|
||||
break;
|
||||
case "wikipedia.org":
|
||||
wikiName = "Wikipedia";
|
||||
|
|
|
|||
|
|
@ -148,13 +148,13 @@
|
|||
</button>
|
||||
<div id="content-wrapper">
|
||||
<div id="banner">
|
||||
<div>
|
||||
<a href="#" class="banner-link" data-toggle="modal" data-target="#download-modal" data-help-title="Downloading CyberChef" data-help="<p>CyberChef can be downloaded to run locally or hosted within your own network. It has no server-side component so all that is required is that the ZIP file is uncompressed and the files are accessible.</p><p>As a user, it is worth noting that unofficial versions of CyberChef could have been modified to introduce Input and/or Recipe exfiltration. We recommend always using the official, open source, up-to-date version of CyberChef hosted at <a href='https://gchq.github.io/CyberChef'>https://gchq.github.io/CyberChef</a> if accessible.</p><p>The Network tab in your browser's Developer console (F12) can be used to inspect the network requests made by a website. This can confirm that no data is uploaded when a CyberChef recipe is baked.</p>">
|
||||
<div id="download-wrapper">
|
||||
<a href="#" class="banner-link desktop-only" data-toggle="modal" data-target="#download-modal" data-help-title="Downloading CyberChef" data-help="<p>CyberChef can be downloaded to run locally or hosted within your own network. It has no server-side component so all that is required is that the ZIP file is uncompressed and the files are accessible.</p><p>As a user, it is worth noting that unofficial versions of CyberChef could have been modified to introduce Input and/or Recipe exfiltration. We recommend always using the official, open source, up-to-date version of CyberChef hosted at <a href='https://gchq.github.io/CyberChef'>https://gchq.github.io/CyberChef</a> if accessible.</p><p>The Network tab in your browser's Developer console (F12) can be used to inspect the network requests made by a website. This can confirm that no data is uploaded when a CyberChef recipe is baked.</p>">
|
||||
<span>Download CyberChef</span>
|
||||
<i class="material-icons">file_download</i>
|
||||
<i class="material-icons banner-icon">file_download</i>
|
||||
</a>
|
||||
</div>
|
||||
<div id="notice-wrapper" class="desktop-only">
|
||||
<div id="notice-wrapper">
|
||||
<span id="notice">
|
||||
<script type="text/javascript">
|
||||
// Must be text/javascript rather than application/javascript otherwise IE won't recognise it...
|
||||
|
|
@ -173,7 +173,7 @@
|
|||
data-help-title="Options and Settings"
|
||||
data-help="Configurable options to change how CyberChef behaves. These settings are stored in your browser's local storage, meaning they will persist between sessions that use the same browser profile.">
|
||||
<span class="desktop-only">Options</span>
|
||||
<i class="material-icons">settings</i>
|
||||
<i class="material-icons banner-icon">settings</i>
|
||||
</a>
|
||||
<a href="#"
|
||||
id="support"
|
||||
|
|
@ -183,7 +183,7 @@
|
|||
data-help-title="About / Support"
|
||||
data-help="This pane provides information about the CyberChef web app, how to use some of the features, and how to raise bug reports.">
|
||||
<span class="desktop-only">About / Support</span>
|
||||
<i class="material-icons">help</i>
|
||||
<i class="material-icons banner-icon">help</i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -193,15 +193,17 @@
|
|||
data-help-title="Operations list"
|
||||
data-help="<p>The Operations list contains all the operations in CyberChef arranged into categories. Some operations may be present in multiple categories. You can search for operations using the search box.</p><p>To use an operation, either double click it, or drag it into the Recipe pane. You will then be able to configure its arguments (or 'Ingredients' in CyberChef terminology).</p>">
|
||||
Operations
|
||||
<span class="op-count"></span>
|
||||
<span class="pane-controls">
|
||||
<button type="button"
|
||||
class="btn bmd-btn-icon mobile-only hidden"
|
||||
id="close-ops-dropdown-icon"
|
||||
title="Close dropdown">
|
||||
<i class="material-icons">close</i>
|
||||
</button>
|
||||
</span>
|
||||
<div id="ops-title-right">
|
||||
<span class="op-count"></span>
|
||||
<span class="pane-controls">
|
||||
<button type="button"
|
||||
class="btn bmd-btn-icon mobile-only hidden"
|
||||
id="close-ops-dropdown-icon"
|
||||
title="Close dropdown">
|
||||
<i class="material-icons">close</i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div id="operations-wrapper">
|
||||
<input id="search"
|
||||
|
|
|
|||
|
|
@ -47,5 +47,9 @@
|
|||
}
|
||||
}
|
||||
|
||||
.banner-icon {
|
||||
transform: translateY(-1.5px);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -56,6 +56,8 @@
|
|||
font-size: initial;
|
||||
color: var(--primary-font-colour);
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
#auto-bake-label .check,
|
||||
|
|
@ -65,6 +67,7 @@
|
|||
}
|
||||
|
||||
#auto-bake-label .checkbox-decorator {
|
||||
display: inline-flex;
|
||||
position: relative;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
|
|
|||
|
|
@ -111,7 +111,6 @@
|
|||
|
||||
/* Right hand side icons */
|
||||
.rhs {
|
||||
position: fixed;
|
||||
right: 15px;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -59,3 +59,14 @@ c-category-list > ul {
|
|||
padding: 8px 8px 9px 8px;
|
||||
}
|
||||
|
||||
.category-title-right {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.op-count {
|
||||
color: var(--subtext-font-colour);
|
||||
font-weight: normal;
|
||||
font-size: xx-small;
|
||||
opacity: 0.5;
|
||||
padding-left: .5em;
|
||||
}
|
||||
|
|
@ -13,6 +13,11 @@
|
|||
padding: 8px 8px 8px 12px;
|
||||
}
|
||||
|
||||
#operations-title-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#operations-wrapper {
|
||||
position: relative;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,9 @@ class OperationsWaiter {
|
|||
this.manager = manager;
|
||||
|
||||
this.options = {};
|
||||
|
||||
this.lastSingleTapAlert = null;
|
||||
this.singleTapAlertTimeout = null;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -174,64 +177,6 @@ class OperationsWaiter {
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handler for edit favourites click events.
|
||||
* Displays the 'Edit favourites' modal and handles the c-operation-list in the modal
|
||||
*
|
||||
* @param {Event} e
|
||||
*/
|
||||
opListCreate(e) {
|
||||
this.manager.recipe.createSortableSeedList(e.target);
|
||||
|
||||
// Populate ops total
|
||||
document.querySelector("#operations .title .op-count").innerText = Object.keys(this.app.operations).length;
|
||||
|
||||
this.enableOpsListPopovers(e.target);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets up popovers, allowing the popover itself to gain focus which enables scrolling
|
||||
* and other interactions.
|
||||
*
|
||||
* @param {Element} el - The element to start selecting from
|
||||
*/
|
||||
enableOpsListPopovers(el) {
|
||||
$(el).find("[data-toggle=popover]").addBack("[data-toggle=popover]")
|
||||
.popover({trigger: "manual"})
|
||||
.on("mouseenter", function(e) {
|
||||
if (e.buttons > 0) return; // Mouse button held down - likely dragging an operation
|
||||
const _this = this;
|
||||
$(this).popover("show");
|
||||
$(".popover").on("mouseleave", function () {
|
||||
$(_this).popover("hide");
|
||||
});
|
||||
}).on("mouseleave", function () {
|
||||
const _this = this;
|
||||
setTimeout(function() {
|
||||
// Determine if the popover associated with this element is being hovered over
|
||||
if ($(_this).data("bs.popover") &&
|
||||
($(_this).data("bs.popover").tip && !$($(_this).data("bs.popover").tip).is(":hover"))) {
|
||||
$(_this).popover("hide");
|
||||
}
|
||||
}, 50);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handler for operation doubleclick events.
|
||||
* Adds the operation to the recipe and auto bakes.
|
||||
*
|
||||
* @param {event} e
|
||||
*/
|
||||
operationDblclick(e) {
|
||||
const li = e.target;
|
||||
|
||||
this.manager.recipe.addOperation(li.textContent);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handler for edit favourites click events.
|
||||
* Sets up the 'Edit favourites' pane and displays it.
|
||||
|
|
@ -323,18 +268,41 @@ class OperationsWaiter {
|
|||
this.app.resetFavourites();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets whether operation counts are displayed next to a category title
|
||||
*/
|
||||
setCatCount() {
|
||||
if (this.app.options.showCatCount) {
|
||||
document.querySelectorAll(".category-title .op-count").forEach(el => el.classList.remove("hidden"));
|
||||
document.querySelectorAll(".category-title .category-title-right .op-count").forEach(el => el.classList.remove("hidden"));
|
||||
} else {
|
||||
document.querySelectorAll(".category-title .op-count").forEach(el => el.classList.add("hidden"));
|
||||
document.querySelectorAll(".category-title .category-title-right .op-count").forEach(el => el.classList.add("hidden"));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles alerts for single tap events on mobile. Will disallow more than one every
|
||||
* 30 seconds.
|
||||
*/
|
||||
sendSingleTapAlert() {
|
||||
clearTimeout(this.singleTapAlertTimeout);
|
||||
this.singleTapAlertTimeout = setTimeout(() => {
|
||||
const now = new Date();
|
||||
// Only display alert if we've not seen one before or in at least 30 seconds.
|
||||
if (!this.lastSingleTapAlert || now.getTime() - this.lastSingleTapAlert.getTime() > 30_000) {
|
||||
this.lastSingleTapAlert = now;
|
||||
this.app.alert("Double tap an operation to add it to your recipe.", 3000);
|
||||
}
|
||||
}, 500);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear pending single tap alerts.
|
||||
*/
|
||||
clearSingleTapAlerts() {
|
||||
clearTimeout(this.singleTapAlertTimeout);
|
||||
this.singleTapAlertTimeout = null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default OperationsWaiter;
|
||||
|
|
|
|||
|
|
@ -63,6 +63,7 @@ class RecipeWaiter {
|
|||
onEnd: function(e) {
|
||||
if (this.removeIntent) {
|
||||
e.item.remove();
|
||||
document.dispatchEvent(this.manager.statechange);
|
||||
e.target.dispatchEvent(this.manager.operationremove);
|
||||
}
|
||||
}.bind(this),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue