Added a contextual help feature and started writing help descriptions

This commit is contained in:
n1474335 2023-03-17 17:46:13 +00:00
parent a24fdf4250
commit d6f8e0a520
7 changed files with 142 additions and 37 deletions

View File

@ -281,7 +281,15 @@ class App {
}
// Add edit button to first category (Favourites)
document.querySelector("#categories a").appendChild(document.getElementById("edit-favourites"));
const favCat = document.querySelector("#categories a");
favCat.appendChild(document.getElementById("edit-favourites"));
favCat.setAttribute("data-help-title", "Favourite operations");
favCat.setAttribute("data-help", `<p>This category displays your favourite operations.</p>
<ul>
<li><b>To add:</b> drag an operation over the Favourites category</li>
<li><b>To reorder:</b> Click on the 'Edit favourites' button and drag operations up and down in the list provided</li>
<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>`);
}
@ -656,6 +664,8 @@ class App {
const notice = document.getElementById("notice");
notice.innerHTML = compileInfo;
notice.setAttribute("title", Utils.stripHtmlTags(window.compileMessage));
notice.setAttribute("data-help-title", "Last build");
notice.setAttribute("data-help", "This live version of CyberChef is updated whenever new commits are added to the master branch of the CyberChef repository. It represents the latest, most up-to-date build of CyberChef.");
}

View File

@ -85,7 +85,7 @@ class HTMLIngredient {
</div>`;
break;
case "toggleString":
html += `<div class="form-group input-group ing-wide">
html += `<div class="form-group input-group ing-wide" data-help-title="Multi-type ingredients" data-help="Selecting a data type from the dropdown will change how the ingredient is interpreted by the operation.">
<div class="toggle-string">
<label for="${this.id}"
${this.hint ? `data-toggle="tooltip" title="${this.hint}"` : ""}
@ -168,7 +168,7 @@ class HTMLIngredient {
break;
case "populateOption":
case "populateMultiOption":
html += `<div class="form-group ing-medium">
html += `<div class="form-group ing-medium" data-help-title="Population dropdowns" data-help="Selecting a value from this dropdown will populate some of the other ingredients for this operation with pre-canned values.">
<label for="${this.id}"
${this.hint ? `data-toggle="tooltip" title="${this.hint}"` : ""}
class="bmd-label-floating">${this.name}</label>
@ -275,7 +275,7 @@ class HTMLIngredient {
</div>`;
break;
case "argSelector":
html += `<div class="form-group inline ing-medium">
html += `<div class="form-group inline ing-medium" data-help-title="Ingredient selector" data-help="Selecting options in this dropdown will configure which operation ingredients are visible.">
<label for="${this.id}"
${this.hint ? `data-toggle="tooltip" title="${this.hint}"` : ""}
class="bmd-label-floating inline">${this.name}</label>

View File

@ -83,8 +83,8 @@ class HTMLOperation {
html += `</div>
<div class="recip-icons">
<i class="material-icons breakpoint" title="Set breakpoint" break="false">pause</i>
<i class="material-icons disable-icon" title="Disable operation" disabled="false">not_interested</i>
<i class="material-icons breakpoint" title="Set breakpoint" break="false" data-help-title="Setting breakpoints" data-help="Setting a breakpoint on an operation will cause execution of the Recipe to pause when it reaches that operation.">pause</i>
<i class="material-icons disable-icon" title="Disable operation" disabled="false" data-help-title="Disabling operations" data-help="Disabling an operation will prevent it from being executed when the Recipe is baked. Execution will skip over the disabled operation and continue with subsequent operations.">not_interested</i>
</div>
<div class="clearfix">&nbsp;</div>`;

View File

@ -146,7 +146,9 @@
<div id="content-wrapper">
<div id="banner" class="row">
<div class="col" style="text-align: left; padding-left: 10px;">
<a href="CyberChef_v<%= htmlWebpackPlugin.options.version %>.zip" download>Download CyberChef <i class="material-icons">file_download</i></a>
<a href="CyberChef_v<%= htmlWebpackPlugin.options.version %>.zip" download data-help-title="Downloading CyberChef" data-help="<p>CyberChef runs entirely within your browser with no server-side component, meaning that none of your input data or Recipe configuration is sent anywhere, whether you use the live, official version of CyberChef or a downloaded, standalone version (assuming it is unmodified).</p><p>If you would like to download your own standalone copy of CyberChef, you can click the 'Download CyberChef' link and get a ZIP file containing the whole web app. This can be run locally or hosted on a web server with no configuration required.</p><p>Be aware that the standalone version will never update itself, meaning it will not receive bug fixes or new features until you re-download newer versions manually.</p><p>As a user, it is also worth noting that downloaded, standalone 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>">
Download CyberChef <i class="material-icons">file_download</i>
</a>
</div>
<div class="col-md-6" id="notice-wrapper">
<span id="notice">
@ -161,19 +163,25 @@
</span>
</div>
<div class="col" style="text-align: right; padding-right: 0;">
<a href="#" id="options">Options <i class="material-icons">settings</i></a>
<a href="#" id="support" data-toggle="modal" data-target="#support-modal">About / Support <i class="material-icons">help</i></a>
<a href="#" id="options" 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.">
Options <i class="material-icons">settings</i>
</a>
<a href="#" id="support" data-toggle="modal" data-target="#support-modal" 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.">
About / Support <i class="material-icons">help</i>
</a>
</div>
</div>
<div id="workspace-wrapper">
<div id="operations" class="split split-horizontal no-select">
<div class="title no-select">Operations</div>
<input id="search" type="search" class="form-control" placeholder="Search..." autocomplete="off" tabindex="2">
<div class="title no-select" 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
</div>
<input id="search" type="search" class="form-control" placeholder="Search..." autocomplete="off" tabindex="2" data-help-title="Searching for operations" data-help="<p>Use the search box to find useful operations.</p><p>Both operation names and descriptions are queried using a fuzzy matching algorithm.</p>">
<ul id="search-results" class="op-list"></ul>
<div id="categories" class="panel-group no-select"></div>
</div>
<div id="recipe" class="split split-horizontal no-select">
<div id="recipe" class="split split-horizontal no-select" data-help-title="Recipe pane" data-help="<p>The Recipe pane is where your chosen Operations are configured. If you are a programmer, think of these as functions. If you are not a programmer, these are like steps in a cake recipe. The Input data will be processed based on the Operations in your Recipe.</p><ul><li>To reorder, simply drag and drop the Operations into the order your require</li><li>To remove an operation, either double click it, or drag it outside of the Recipe pane</li></ul><p>The arguments (or 'Ingredients' in CyberChef terminology) can be configured to change how an Operation processes the data.</p>">
<div class="title no-select">
Recipe
<span class="pane-controls hide-on-maximised-output">
@ -192,17 +200,17 @@
<div id="controls" class="no-select hide-on-maximised-output">
<div id="controls-content">
<button type="button" class="mx-2 btn btn-lg btn-secondary" id="step" data-toggle="tooltip" title="Step through the recipe">
<button type="button" class="mx-2 btn btn-lg btn-secondary" id="step" data-toggle="tooltip" title="Step through the recipe" data-help-title="Stepping through the Recipe" data-help="<p>The Step button allows you to execute one operation at a time, rather than running the whole Recipe from beginning to end.</p><p>Step allows you to inspect the data at each stage of the Recipe and understand what is being passed to the next operation.</p>">
Step
</button>
<button type="button" class="mx-2 btn btn-lg btn-success btn-raised btn-block" id="bake">
<button type="button" class="mx-2 btn btn-lg btn-success btn-raised btn-block" id="bake" data-help-title="Baking" data-help="<p>Baking causes CyberChef to run the Recipe against your data. This involves three steps:</p><ol><li>The data in the Input is encoded into bytes using the character encoding selected in the Input status bar.</li><li>The data is run through each of the operations in the Recipe in turn with the output of one operation being fed into the next operation as its input.</li><li>The outcome of the final operation in the Recipe is decoded into Output text using the character encoding selected in the Output status bar.</li></ol><p>If there are multiple Inputs, the Bake button causes every Input to be baked simultaneously.</p>">
<img aria-hidden="true" src="<%- require('../static/images/cook_male-32x32.png') %>" alt="Chef Icon"/>
<span>Bake!</span>
</button>
<div class="form-group" style="display: contents;">
<div class="mx-1 checkbox">
<div class="mx-1 checkbox" data-help-title="Auto-bake" data-help="<p>When Auto-bake is turned on, CyberChef will bake the Input using the Recipe whenever anything in the Input or Recipe changes.</p>This includes:<ul><li>Adding or removing operations</li><li>Modifying operation arguments</li><li>Editing the Input</li><li>Changing the Input character encoding</li></ul><p>If there are multiple inputs, only the currently active tab will be baked when Auto-bake triggers. You can bake all inputs manually using the Bake button.</p>">
<label id="auto-bake-label">
<input type="checkbox" checked="checked" id="auto-bake">
<br>Auto Bake
@ -214,7 +222,7 @@
</div>
<div class="split split-horizontal" id="IO">
<div id="input" class="split no-select">
<div id="input" class="split no-select" data-help-title="Input pane" data-help="<p>Input data can be entered by typing it in, pasting it in, dragging it in, or using the 'Load file' or 'Load folder' buttons.</p><p>CyberChef does its best to represent data as accurately as possible to ensure you know exactly what you are working with. Non-printable characters are represented using control character pictures, for example a null byte (0x00) is displayed like this: <span title='Control character null' aria-label='Control character null' class='cm-specialChar'>␀</span>.</p>">
<div class="title no-select">
<label for="input-text">Input</label>
<span class="pane-controls">
@ -268,7 +276,7 @@
</div>
</div>
<div id="output" class="split">
<div id="output" class="split" data-help-title="Output pane" data-help="<p>This pane displays the results of the Recipe after it has processed your Input.</p><p>CyberChef does its best to represent data as accurately as possible to ensure you know exactly what you are working with. Non-printable characters are represented using control character pictures, for example a null byte (0x00) is displayed like this: <span title='Control character null' aria-label='Control character null' class='cm-specialChar'>␀</span>.</p><p>When copying these characters from the Output, the original byte value should be copied into your clipboard, rather than the control character picture itself.</p>">
<div class="title no-select">
<label for="output-text">Output</label>
<span class="pane-controls">
@ -325,7 +333,7 @@
<div id="output-text"></div>
<div id="output-loader">
<div id="output-loader-animation">
<object id="bombe" data="<%- require('../static/images/bombe.svg') %>" width="100%" height="100%"></object>
<object id="bombe" data="<%- require('../static/images/bombe.svg') %>" width="100%" height="100%" data-help-title="Loading animation" data-help="This loading animation shows an accurate representation of how rotors moved on The Bombe, an electro-mechanical device built at Bletchley Park in 1939 by Alan Turing with refinements by Gordon Welchman in 1940. The Bombe was used by the Government Code and Cipher School (the precursor to GCHQ) to discover daily settings of Enigma machines used by the German military in World War 2.<br><br>More information can be found on <a href='https://wikipedia.org/wiki/Bombe'>Wikipedia</a>."></object>
</div>
<div class="loading-msg"></div>
</div>
@ -531,9 +539,9 @@
</div>
<div class="modal-body" id="favourites-body">
<ul>
<li><span style="font-weight: bold">To add:</span> drag the operation over the favourites category</li>
<li><span style="font-weight: bold">To add:</span> drag the operation over the favourites category and drop it</li>
<li><span style="font-weight: bold">To reorder:</span> drag up and down in the list below</li>
<li><span style="font-weight: bold">To remove:</span> hit the red cross or drag out of the list below</li>
<li><span style="font-weight: bold">To remove:</span> hit the delete button or drag out of the list below</li>
</ul>
<br>
<ul id="edit-favourites-list" class="op-list"></ul>
@ -590,8 +598,16 @@
</li>
</ul>
<div class="tab-content">
<div role="tabpanel" class="tab-pane active" id="faqs">
<div role="tabpanel" class="tab-pane active" id="faqs" data-help-title="FAQ pane" data-help="The Frequently Asked Questions pane provides answers to some of the most common queries people have about CyberChef.">
<br>
<a class="btn btn-primary" data-toggle="collapse" data-target="#faq-contextual-help">
How does X feature work?
</a>
<div class="collapse" id="faq-contextual-help">
<p>CyberChef has a contextual help feature. Just hover your cursor over a feature that you want to learn more about and press <code>F1</code> on your keyboard to get some information about it. Give it a try by hovering over this text and pressing <code>F1</code> now!</code></p>
</div>
<br>
<a class="btn btn-primary" data-toggle="collapse" data-target="#faq-examples">
What sort of things can I do with CyberChef?
</a>
@ -704,6 +720,25 @@
</div>
</div>
<div class="modal fade" id="help-modal" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">
<i class="material-icons modal-icon">info_outline</i>
<span id="help-title"></span>
</h5>
</div>
<div class="modal-body">
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" id="help-ok" data-dismiss="modal">Ok</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="input-tab-modal" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">

View File

@ -50,6 +50,11 @@ body {
padding-left: 2px;
}
.modal-icon {
position: absolute;
right: 25px;
}
.konami {
transform: rotate(180deg);
}

View File

@ -41,6 +41,8 @@ class StatusBarPanel {
const rhs = document.createElement("div");
dom.className = "cm-status-bar";
dom.setAttribute("data-help-title", `${this.label} status bar`);
dom.setAttribute("data-help", `This status bar provides information about data in the ${this.label}. Help topics are available for each of the components by activating help when hovering over them.`);
lhs.innerHTML = this.constructLHS();
rhs.innerHTML = this.constructRHS();
@ -184,6 +186,17 @@ class StatusBarPanel {
return;
}
// CodeMirror always counts line breaks as one character.
// We want to show an accurate reading of how many bytes there are.
let from = state.selection.main.from,
to = state.selection.main.to;
if (state.lineBreak.length !== 1) {
const fromLine = state.doc.lineAt(from).number;
const toLine = state.doc.lineAt(to).number;
from += (state.lineBreak.length * fromLine) - fromLine - 1;
to += (state.lineBreak.length * toLine) - toLine - 1;
}
if (selLen > 0) { // Range
const start = this.dom.querySelector(".sel-start-value"),
end = this.dom.querySelector(".sel-end-value"),
@ -191,17 +204,15 @@ class StatusBarPanel {
selInfo.style.display = "inline-block";
curOffsetInfo.style.display = "none";
start.textContent = state.selection.main.from;
end.textContent = state.selection.main.to;
length.textContent = state.selection.main.to - state.selection.main.from;
start.textContent = from;
end.textContent = to;
length.textContent = to - from;
} else { // Position
const offset = this.dom.querySelector(".cur-offset-value");
selInfo.style.display = "none";
curOffsetInfo.style.display = "inline-block";
offset.textContent = state.selection.main.from;
offset.textContent = from;
}
}
@ -314,21 +325,21 @@ class StatusBarPanel {
*/
constructLHS() {
return `
<span data-toggle="tooltip" title="${this.label} length">
<span data-toggle="tooltip" title="${this.label} length" data-help-title="${this.label} length" data-help="This number represents the number of characters in the ${this.label}.<br><br>The CRLF end of line separator is counted as two characters which impacts this value.">
<i class="material-icons">abc</i>
<span class="stats-length-value"></span>
</span>
<span data-toggle="tooltip" title="Number of lines">
<span data-toggle="tooltip" title="Number of lines" data-help-title="Number of lines" data-help="This number represents the number of lines in the ${this.label}. Lines are separated by the End of Line Sequence which can be changed using the EOL selector at the far right of this status bar.">
<i class="material-icons">sort</i>
<span class="stats-lines-value"></span>
</span>
<span class="sel-info" data-toggle="tooltip" title="Main selection">
<span class="sel-info" data-toggle="tooltip" title="Main selection" data-help-title="Main selection" data-help="These numbers show which offsets have been selected and how many characters are in the current selection. If multiple selections are made, these numbers refer to the latest one. ">
<i class="material-icons">highlight_alt</i>
<span class="sel-start-value"></span>\u279E<span class="sel-end-value"></span>
(<span class="sel-length-value"></span> selected)
</span>
<span class="cur-offset-info" data-toggle="tooltip" title="Cursor offset">
<span class="cur-offset-info" data-toggle="tooltip" title="Cursor offset" data-help-title="Cursor offset" data-help="This number indicates what the current offset of the cursor is from the beginning of the ${this.label}.<br><br>The CRLF end of line separator is counted as two characters which impacts this value.">
<i class="material-icons">location_on</i>
<span class="cur-offset-value"></span>
</span>`;
@ -345,13 +356,23 @@ class StatusBarPanel {
`<a href="#" draggable="false" data-val="${CHR_ENC_SIMPLE_LOOKUP[name]}">${name}</a>`
).join("");
let chrEncHelpText = "",
eolHelpText = "";
if (this.label === "Input") {
chrEncHelpText = "The input character encoding defines how the input text is encoded into bytes which are then processed by the Recipe.<br><br>The 'Raw bytes' option attempts to treat the input as individual bytes in the range 0-255. If it detects any characters with Unicode values above 255, it will treat the entire input as UTF-8. 'Raw bytes' is usually the best option if you are inputting binary data, such as a file.";
eolHelpText = "The End of Line Sequence defines which bytes are considered EOL terminators. Pressing the return key will enter this value into the input and create a new line.<br><br>Changing the EOL sequence will not modify any existing data in the input but may change how previously entered line breaks are displayed. Lines added while a different EOL terminator was set may not now result in a new line, but may be displayed as control characters instead.";
} else {
chrEncHelpText = "The output character encoding defines how the output bytes are decoded into text which can be displayed to you.<br><br>The 'Raw bytes' option treats the output data as individual bytes in the range 0-255.";
eolHelpText = "The End of Line Sequence defines which bytes are considered EOL terminators.<br><br>Changing this value will not modify the value of the output, but may change how certain bytes are displayed and whether they result in a new line being created.";
}
return `
<span class="baking-time-info" style="display: none" data-toggle="tooltip" data-html="true" title="Baking time">
<span class="baking-time-info" style="display: none" data-toggle="tooltip" data-html="true" title="Baking time" data-help-title="Baking time" data-help="The baking time is the total time between data being read from the input, processed, and then displayed in the output.<br><br>The 'Threading overhead' value accounts for the transfer of data between different processing threads, as well as some garbage collection. It is not included in the overall bake time displayed in the status bar as it is largely influenced by background operating system and browser activity which can fluctuate significantly.">
<i class="material-icons">schedule</i>
<span class="baking-time-value"></span>ms
</span>
<div class="cm-status-bar-select chr-enc-select">
<div class="cm-status-bar-select chr-enc-select" data-help-title="${this.label} character encoding" data-help="${chrEncHelpText}">
<span class="cm-status-bar-select-btn" data-toggle="tooltip" data-html="true" data-placement="left" title="${this.label} character encoding">
<i class="material-icons">text_fields</i> <span class="chr-enc-value">Raw Bytes</span>
</span>
@ -371,7 +392,7 @@ class StatusBarPanel {
</div>
</div>
<div class="cm-status-bar-select eol-select">
<div class="cm-status-bar-select eol-select" data-help-title="${this.label} EOL sequence" data-help="${eolHelpText}">
<span class="cm-status-bar-select-btn" data-toggle="tooltip" data-html="true" data-placement="left" title="End of line sequence">
<i class="material-icons">keyboard_return</i> <span class="eol-value"></span>
</span>

View File

@ -148,6 +148,12 @@ class BindingsWaiter {
}
break;
}
} else {
switch (e.code) {
case "F1":
this.contextualHelp();
break;
}
}
}
@ -164,9 +170,14 @@ class BindingsWaiter {
}
document.getElementById("keybList").innerHTML = `
<tr>
<td><b>Command</b></td>
<td><b>Shortcut (Win/Linux)</b></td>
<td><b>Shortcut (Mac)</b></td>
<th>Command</th>
<th>Shortcut (Win/Linux)</th>
<th>Shortcut (Mac)</th>
</tr>
<tr>
<td>Activate contextual help</td>
<td>F1</td>
<td>F1</td>
</tr>
<tr>
<td>Place cursor in search field</td>
@ -255,6 +266,29 @@ class BindingsWaiter {
`;
}
/**
* Shows contextual help message based on where the mouse pointer is
*/
contextualHelp() {
const hoveredHelpEls = document.querySelectorAll(":hover[data-help]");
if (hoveredHelpEls.length) {
const helpEl = hoveredHelpEls[hoveredHelpEls.length - 1],
helpText = helpEl.getAttribute("data-help");
let helpTitle = helpEl.getAttribute("data-help-title");
if (helpTitle)
helpTitle = "<span class='text-muted'>Help topic:</span> " + helpTitle;
else
helpTitle = "<span class='text-muted'>Help topic</span>";
document.querySelector("#help-modal .modal-body").innerHTML = helpText;
document.querySelector("#help-modal #help-title").innerHTML = helpTitle;
$("#help-modal").modal();
}
}
}
export default BindingsWaiter;