Initial keybinding functionality + documentation

Todo:
- allow user to specify whether to use alt or meta key (relatively easy to implement)
- keybinding icon for about pane
This commit is contained in:
Matt C 2017-10-17 19:36:51 +01:00
parent bd1790b692
commit 638e03856b
3 changed files with 222 additions and 24 deletions

118
src/web/BindingsWaiter.js Normal file
View File

@ -0,0 +1,118 @@
/**
* 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) {
if (e.ctrlKey && e.altKey) {
let elem;
switch (e.code) {
case "KeyF":
e.preventDefault();
document.getElementById("search").focus(); // Focus search
break;
case "KeyI":
e.preventDefault();
document.getElementById("input-text").focus(); // Focus input
break;
case "KeyO":
e.preventDefault();
document.getElementById("output-text").focus(); // Focus output
break;
case "Period":
try {
elem = document.activeElement.closest(".operation");
if (elem.parentNode.lastChild === elem) {
elem.parentNode.firstChild.querySelectorAll(".arg")[0].focus(); // if operation is last in recipe, loop around to the top operation's first argument
} else {
elem.nextSibling.querySelectorAll(".arg")[0].focus(); //focus first argument of next operation
}
} catch (e) {
// do nothing, just don't throw an error
}
break;
case "KeyB":
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":
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":
this.app.bake(); // bake the recipe
break;
case "Quote":
this.app.bake(true); // step through the recipe
break;
case "KeyC":
this.manager.recipe.clearRecipe(); // clear recipe
break;
case "KeyS":
this.manager.output.saveClick(); // save output to file
break;
case "KeyL":
this.manager.controls.loadClick(); // load a recipe
break;
case "KeyM":
this.manager.controls.switchClick(); // switch input & output
break;
default:
if (e.code.match(/Digit[0-9]/g)) {
e.preventDefault();
try {
document.querySelector(`li:nth-child(${e.code.substr(-1)}) .arg`).focus(); // select the first argument of the operation corresponding to the number pressed
} catch (e) {
// do nothing, just don't throw an error
}
}
break;
}
}
};
export default BindingsWaiter;

View File

@ -8,6 +8,7 @@ import OutputWaiter from "./OutputWaiter.js";
import OptionsWaiter from "./OptionsWaiter.js";
import HighlighterWaiter from "./HighlighterWaiter.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.highlighter = new HighlighterWaiter(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
this.dynamicHandlers = {};
@ -89,7 +91,7 @@ Manager.prototype.initialiseEventListeners = function() {
window.addEventListener("focus", this.window.windowFocus.bind(this.window));
window.addEventListener("statechange", this.app.stateChange.bind(this.app));
window.addEventListener("popstate", this.app.popState.bind(this.app));
window.addEventListener("keydown", this.bindings.parseInput.bind(this.bindings));
// Controls
document.getElementById("bake").addEventListener("click", this.controls.bakeClick.bind(this.controls));
document.getElementById("auto-bake").addEventListener("change", this.controls.autoBakeChange.bind(this.controls));

View File

@ -396,6 +396,11 @@
<img aria-hidden="true" src="<%- require('../static/images/speech-16x16.png') %>" alt="Speech Balloon Icon"/>
About
</a></li>
<li role="presentation"><a href="#keybindings" aria-controls="messages" role="tab" data-toggle="tab">
<!-- Icon is a placeholder -->
<img aria-hidden="true" src="<%- require('../static/images/code-16x16.png') %>" alt="Code Icon"/>
Keybindings
</a></li>
</ul>
<div class="tab-content">
<div role="tabpanel" class="tab-pane active" id="faqs">
@ -470,6 +475,79 @@
<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>Its the Cyber Swiss Army Knife.</p>
</div>
<div role="tabpanel" class="tab-pane" id="keybindings" style="padding: 20px;">
<table class="table table-condensed table-bordered">
<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+Alt+f</td>
<td>Ctrl+Opt+f</td>
<tr>
<td>Place cursor in input box</td>
<td>Ctrl+Alt+i</td>
<td>Ctrl+Opt+i</td>
</tr>
<tr>
<td>Place cursor in output box</td>
<td>Ctrl+Alt+o</td>
<td>Ctrl+Opt+o</td>
</tr>
<tr>
<td>Place cursor in first argument field<br>of the next operation in the recipe</td>
<td>Ctrl+Alt+.</td>
<td>Ctrl+Opt+.</td>
</tr>
<tr>
<td>Place cursor in first argument field<br>of the nth operation in the recipe</td>
<td>Ctrl+Alt+[1-9]</td>
<td>Ctrl+Opt+[1-9]</td>
</tr>
<tr>
<td>Disable current operation</td>
<td>Ctrl+Alt+d</td>
<td>Ctrl+Opt+d</td>
</tr>
<tr>
<td>Set/clear breakpoint</td>
<td>Ctrl+Alt+b</td>
<td>Ctrl+Opt+b</td>
</tr>
<tr>
<td>Bake</td>
<td>Ctrl+Alt+Space</td>
<td>Ctrl+Opt+Space</td>
</tr>
<tr>
<td>Step</td>
<td>Ctrl+Alt+'</td>
<td>Ctrl+Opt+'</td>
</tr>
<tr>
<td>Clear recipe</td>
<td>Ctrl+Alt+c</td>
<td>Ctrl+Opt+c</td>
</tr>
<tr>
<td>Save to file</td>
<td>Ctrl+Alt+s</td>
<td>Ctrl+Opt+s</td>
</tr>
<tr>
<td>Load recipe</td>
<td>Ctrl+Alt+l</td>
<td>Ctrl+Opt+l</td>
</tr>
<tr>
<td>Move output to input</td>
<td>Ctrl+Alt+m</td>
<td>Ctrl+Opt+m</td>
</tr>
</table>
</div>
</div>
</div>
</div>