mirror of
https://github.com/gchq/CyberChef.git
synced 2024-11-16 17:08:31 +01:00
Merge branch 'artemisbot-features/keybindings'
This commit is contained in:
commit
555fed2d51
4 changed files with 257 additions and 23 deletions
217
src/web/BindingsWaiter.js
Normal file
217
src/web/BindingsWaiter.js
Normal file
|
@ -0,0 +1,217 @@
|
||||||
|
/**
|
||||||
|
* Waiter to handle keybindings to CyberChef functions (i.e. Bake, Step, Save, Load etc.)
|
||||||
|
*
|
||||||
|
* @author Matt C [matt@artemisbot.uk]
|
||||||
|
* @copyright Crown Copyright 2016
|
||||||
|
* @license Apache-2.0
|
||||||
|
*
|
||||||
|
* @constructor
|
||||||
|
* @param {App} app - The main view object for CyberChef.
|
||||||
|
* @param {Manager} manager - The CyberChef event manager.
|
||||||
|
*/
|
||||||
|
const BindingsWaiter = function (app, manager) {
|
||||||
|
this.app = app;
|
||||||
|
this.manager = manager;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler for all keydown events
|
||||||
|
* Checks whether valid keyboard shortcut has been instated
|
||||||
|
*
|
||||||
|
* @fires Manager#statechange
|
||||||
|
* @param {event} e
|
||||||
|
*/
|
||||||
|
BindingsWaiter.prototype.parseInput = function(e) {
|
||||||
|
const modKey = this.app.options.useMetaKey ? e.metaKey : e.altKey;
|
||||||
|
|
||||||
|
if (e.ctrlKey && modKey) {
|
||||||
|
let elem;
|
||||||
|
switch (e.code) {
|
||||||
|
case "KeyF": // Focus search
|
||||||
|
e.preventDefault();
|
||||||
|
document.getElementById("search").focus();
|
||||||
|
break;
|
||||||
|
case "KeyI": // Focus input
|
||||||
|
e.preventDefault();
|
||||||
|
document.getElementById("input-text").focus();
|
||||||
|
break;
|
||||||
|
case "KeyO": // Focus output
|
||||||
|
e.preventDefault();
|
||||||
|
document.getElementById("output-text").focus();
|
||||||
|
break;
|
||||||
|
case "Period": // Focus next operation
|
||||||
|
e.preventDefault();
|
||||||
|
try {
|
||||||
|
elem = document.activeElement.closest(".operation") || document.querySelector("#rec-list .operation");
|
||||||
|
if (elem.parentNode.lastChild === elem) {
|
||||||
|
// If operation is last in recipe, loop around to the top operation's first argument
|
||||||
|
elem.parentNode.firstChild.querySelectorAll(".arg")[0].focus();
|
||||||
|
} else {
|
||||||
|
// Focus first argument of next operation
|
||||||
|
elem.nextSibling.querySelectorAll(".arg")[0].focus();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// do nothing, just don't throw an error
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "KeyB": // Set breakpoint
|
||||||
|
e.preventDefault();
|
||||||
|
try {
|
||||||
|
elem = document.activeElement.closest(".operation").querySelectorAll(".breakpoint")[0];
|
||||||
|
if (elem.getAttribute("break") === "false") {
|
||||||
|
elem.setAttribute("break", "true"); // add break point if not already enabled
|
||||||
|
elem.classList.add("breakpoint-selected");
|
||||||
|
} else {
|
||||||
|
elem.setAttribute("break", "false"); // remove break point if already enabled
|
||||||
|
elem.classList.remove("breakpoint-selected");
|
||||||
|
}
|
||||||
|
window.dispatchEvent(this.manager.statechange);
|
||||||
|
} catch (e) {
|
||||||
|
// do nothing, just don't throw an error
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "KeyD": // Disable operation
|
||||||
|
e.preventDefault();
|
||||||
|
try {
|
||||||
|
elem = document.activeElement.closest(".operation").querySelectorAll(".disable-icon")[0];
|
||||||
|
if (elem.getAttribute("disabled") === "false") {
|
||||||
|
elem.setAttribute("disabled", "true"); // disable operation if enabled
|
||||||
|
elem.classList.add("disable-elem-selected");
|
||||||
|
elem.parentNode.parentNode.classList.add("disabled");
|
||||||
|
} else {
|
||||||
|
elem.setAttribute("disabled", "false"); // enable operation if disabled
|
||||||
|
elem.classList.remove("disable-elem-selected");
|
||||||
|
elem.parentNode.parentNode.classList.remove("disabled");
|
||||||
|
}
|
||||||
|
this.app.progress = 0;
|
||||||
|
window.dispatchEvent(this.manager.statechange);
|
||||||
|
} catch (e) {
|
||||||
|
// do nothing, just don't throw an error
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "Space": // Bake
|
||||||
|
e.preventDefault();
|
||||||
|
this.app.bake();
|
||||||
|
break;
|
||||||
|
case "Quote": // Step through
|
||||||
|
e.preventDefault();
|
||||||
|
this.app.bake(true);
|
||||||
|
break;
|
||||||
|
case "KeyC": // Clear recipe
|
||||||
|
e.preventDefault();
|
||||||
|
this.manager.recipe.clearRecipe();
|
||||||
|
break;
|
||||||
|
case "KeyS": // Save output to file
|
||||||
|
e.preventDefault();
|
||||||
|
this.manager.output.saveClick();
|
||||||
|
break;
|
||||||
|
case "KeyL": // Load recipe
|
||||||
|
e.preventDefault();
|
||||||
|
this.manager.controls.loadClick();
|
||||||
|
break;
|
||||||
|
case "KeyM": // Switch input and output
|
||||||
|
e.preventDefault();
|
||||||
|
this.manager.output.switchClick();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (e.code.match(/Digit[0-9]/g)) { // Select nth operation
|
||||||
|
e.preventDefault();
|
||||||
|
try {
|
||||||
|
// Select the first argument of the operation corresponding to the number pressed
|
||||||
|
document.querySelector(`li:nth-child(${e.code.substr(-1)}) .arg`).focus();
|
||||||
|
} catch (e) {
|
||||||
|
// do nothing, just don't throw an error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates keybinding list when metaKey option is toggled
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
BindingsWaiter.prototype.updateKeybList = function() {
|
||||||
|
let modWinLin = "Alt";
|
||||||
|
let modMac = "Opt";
|
||||||
|
if (this.app.options.useMetaKey) {
|
||||||
|
modWinLin = "Win";
|
||||||
|
modMac = "Cmd";
|
||||||
|
}
|
||||||
|
document.getElementById("keybList").innerHTML = `
|
||||||
|
<tr>
|
||||||
|
<td><b>Command</b></td>
|
||||||
|
<td><b>Shortcut (Win/Linux)</b></td>
|
||||||
|
<td><b>Shortcut (Mac)</b></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Place cursor in search field</td>
|
||||||
|
<td>Ctrl+${modWinLin}+f</td>
|
||||||
|
<td>Ctrl+${modMac}+f</td>
|
||||||
|
<tr>
|
||||||
|
<td>Place cursor in input box</td>
|
||||||
|
<td>Ctrl+${modWinLin}+i</td>
|
||||||
|
<td>Ctrl+${modMac}+i</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Place cursor in output box</td>
|
||||||
|
<td>Ctrl+${modWinLin}+o</td>
|
||||||
|
<td>Ctrl+${modMac}+o</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Place cursor in first argument field of the next operation in the recipe</td>
|
||||||
|
<td>Ctrl+${modWinLin}+.</td>
|
||||||
|
<td>Ctrl+${modMac}+.</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Place cursor in first argument field of the nth operation in the recipe</td>
|
||||||
|
<td>Ctrl+${modWinLin}+[1-9]</td>
|
||||||
|
<td>Ctrl+${modMac}+[1-9]</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Disable current operation</td>
|
||||||
|
<td>Ctrl+${modWinLin}+d</td>
|
||||||
|
<td>Ctrl+${modMac}+d</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Set/clear breakpoint</td>
|
||||||
|
<td>Ctrl+${modWinLin}+b</td>
|
||||||
|
<td>Ctrl+${modMac}+b</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Bake</td>
|
||||||
|
<td>Ctrl+${modWinLin}+Space</td>
|
||||||
|
<td>Ctrl+${modMac}+Space</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Step</td>
|
||||||
|
<td>Ctrl+${modWinLin}+'</td>
|
||||||
|
<td>Ctrl+${modMac}+'</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Clear recipe</td>
|
||||||
|
<td>Ctrl+${modWinLin}+c</td>
|
||||||
|
<td>Ctrl+${modMac}+c</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Save to file</td>
|
||||||
|
<td>Ctrl+${modWinLin}+s</td>
|
||||||
|
<td>Ctrl+${modMac}+s</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Load recipe</td>
|
||||||
|
<td>Ctrl+${modWinLin}+l</td>
|
||||||
|
<td>Ctrl+${modMac}+l</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Move output to input</td>
|
||||||
|
<td>Ctrl+${modWinLin}+m</td>
|
||||||
|
<td>Ctrl+${modMac}+m</td>
|
||||||
|
</tr>
|
||||||
|
`;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BindingsWaiter;
|
|
@ -8,6 +8,7 @@ import OutputWaiter from "./OutputWaiter.js";
|
||||||
import OptionsWaiter from "./OptionsWaiter.js";
|
import OptionsWaiter from "./OptionsWaiter.js";
|
||||||
import HighlighterWaiter from "./HighlighterWaiter.js";
|
import HighlighterWaiter from "./HighlighterWaiter.js";
|
||||||
import SeasonalWaiter from "./SeasonalWaiter.js";
|
import SeasonalWaiter from "./SeasonalWaiter.js";
|
||||||
|
import BindingsWaiter from "./BindingsWaiter.js";
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -60,6 +61,7 @@ const Manager = function(app) {
|
||||||
this.options = new OptionsWaiter(this.app);
|
this.options = new OptionsWaiter(this.app);
|
||||||
this.highlighter = new HighlighterWaiter(this.app, this);
|
this.highlighter = new HighlighterWaiter(this.app, this);
|
||||||
this.seasonal = new SeasonalWaiter(this.app, this);
|
this.seasonal = new SeasonalWaiter(this.app, this);
|
||||||
|
this.bindings = new BindingsWaiter(this.app, this);
|
||||||
|
|
||||||
// Object to store dynamic handlers to fire on elements that may not exist yet
|
// Object to store dynamic handlers to fire on elements that may not exist yet
|
||||||
this.dynamicHandlers = {};
|
this.dynamicHandlers = {};
|
||||||
|
@ -75,6 +77,7 @@ Manager.prototype.setup = function() {
|
||||||
this.worker.registerChefWorker();
|
this.worker.registerChefWorker();
|
||||||
this.recipe.initialiseOperationDragNDrop();
|
this.recipe.initialiseOperationDragNDrop();
|
||||||
this.controls.autoBakeChange();
|
this.controls.autoBakeChange();
|
||||||
|
this.bindings.updateKeybList();
|
||||||
this.seasonal.load();
|
this.seasonal.load();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -160,12 +163,14 @@ Manager.prototype.initialiseEventListeners = function() {
|
||||||
document.getElementById("reset-options").addEventListener("click", this.options.resetOptionsClick.bind(this.options));
|
document.getElementById("reset-options").addEventListener("click", this.options.resetOptionsClick.bind(this.options));
|
||||||
$(document).on("switchChange.bootstrapSwitch", ".option-item input:checkbox", this.options.switchChange.bind(this.options));
|
$(document).on("switchChange.bootstrapSwitch", ".option-item input:checkbox", this.options.switchChange.bind(this.options));
|
||||||
$(document).on("switchChange.bootstrapSwitch", ".option-item input:checkbox", this.options.setWordWrap.bind(this.options));
|
$(document).on("switchChange.bootstrapSwitch", ".option-item input:checkbox", this.options.setWordWrap.bind(this.options));
|
||||||
|
$(document).on("switchChange.bootstrapSwitch", ".option-item input:checkbox#useMetaKey", this.bindings.updateKeybList.bind(this.bindings));
|
||||||
this.addDynamicListener(".option-item input[type=number]", "keyup", this.options.numberChange, this.options);
|
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 input[type=number]", "change", this.options.numberChange, this.options);
|
||||||
this.addDynamicListener(".option-item select", "change", this.options.selectChange, this.options);
|
this.addDynamicListener(".option-item select", "change", this.options.selectChange, this.options);
|
||||||
document.getElementById("theme").addEventListener("change", this.options.themeChange.bind(this.options));
|
document.getElementById("theme").addEventListener("change", this.options.themeChange.bind(this.options));
|
||||||
|
|
||||||
// Misc
|
// Misc
|
||||||
|
window.addEventListener("keydown", this.bindings.parseInput.bind(this.bindings));
|
||||||
document.getElementById("alert-close").addEventListener("click", this.app.alertCloseClick.bind(this.app));
|
document.getElementById("alert-close").addEventListener("click", this.app.alertCloseClick.bind(this.app));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -326,6 +326,10 @@
|
||||||
<input type="checkbox" option="showErrors" id="showErrors" checked />
|
<input type="checkbox" option="showErrors" id="showErrors" checked />
|
||||||
<label for="showErrors"> Operation error reporting (recommended) </label>
|
<label for="showErrors"> Operation error reporting (recommended) </label>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="option-item">
|
||||||
|
<input type="checkbox" option="useMetaKey" id="useMetaKey" />
|
||||||
|
<label for="useMetaKey"> Use meta key for keybindings (Windows ⊞/Command ⌘) </label>
|
||||||
|
</div>
|
||||||
<div class="option-item">
|
<div class="option-item">
|
||||||
<input type="number" option="errorTimeout" id="errorTimeout" />
|
<input type="number" option="errorTimeout" id="errorTimeout" />
|
||||||
<label for="errorTimeout"> Operation error timeout in ms (0 for never) </label>
|
<label for="errorTimeout"> Operation error timeout in ms (0 for never) </label>
|
||||||
|
@ -402,6 +406,10 @@
|
||||||
<img aria-hidden="true" src="<%- require('../static/images/speech-16x16.png') %>" alt="Speech Balloon Icon"/>
|
<img aria-hidden="true" src="<%- require('../static/images/speech-16x16.png') %>" alt="Speech Balloon Icon"/>
|
||||||
About
|
About
|
||||||
</a></li>
|
</a></li>
|
||||||
|
<li role="presentation"><a href="#keybindings" aria-controls="messages" role="tab" data-toggle="tab">
|
||||||
|
<img aria-hidden="true" src="<%- require('../static/images/code-16x16.png') %>" alt="List Icon"/>
|
||||||
|
Keybindings
|
||||||
|
</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
<div class="tab-content">
|
<div class="tab-content">
|
||||||
<div role="tabpanel" class="tab-pane active" id="faqs">
|
<div role="tabpanel" class="tab-pane active" id="faqs">
|
||||||
|
@ -477,6 +485,9 @@
|
||||||
<p>There are around 150 useful operations in CyberChef for anyone working on anything vaguely Internet-related, whether you just want to convert a timestamp to a different format, decompress gzipped data, create a SHA3 hash, or parse an X.509 certificate to find out who issued it.</p>
|
<p>There are around 150 useful operations in CyberChef for anyone working on anything vaguely Internet-related, whether you just want to convert a timestamp to a different format, decompress gzipped data, create a SHA3 hash, or parse an X.509 certificate to find out who issued it.</p>
|
||||||
<p>It’s the Cyber Swiss Army Knife.</p>
|
<p>It’s the Cyber Swiss Army Knife.</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div role="tabpanel" class="tab-pane" id="keybindings" style="padding: 20px;">
|
||||||
|
<table class="table table-condensed table-bordered table-hover" id="keybList"></table>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -46,6 +46,7 @@ function main() {
|
||||||
errorTimeout: 4000,
|
errorTimeout: 4000,
|
||||||
attemptHighlight: true,
|
attemptHighlight: true,
|
||||||
theme: "classic",
|
theme: "classic",
|
||||||
|
useMetaKey: false
|
||||||
};
|
};
|
||||||
|
|
||||||
document.removeEventListener("DOMContentLoaded", main, false);
|
document.removeEventListener("DOMContentLoaded", main, false);
|
||||||
|
|
Loading…
Reference in a new issue