CyberChef/src/web/waiters/HighlighterWaiter.mjs

418 lines
13 KiB
JavaScript
Raw Normal View History

2018-05-15 19:36:45 +02:00
/**
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2016
* @license Apache-2.0
*/
/**
* HighlighterWaiter data type enum for the input.
* @enum
*/
const INPUT = 0;
/**
* HighlighterWaiter data type enum for the output.
* @enum
*/
const OUTPUT = 1;
/**
* Waiter to handle events related to highlighting in CyberChef.
*/
class HighlighterWaiter {
/**
* HighlighterWaiter constructor.
*
* @param {App} app - The main view object for CyberChef.
* @param {Manager} manager - The CyberChef event manager.
*/
constructor(app, manager) {
this.app = app;
this.manager = manager;
this.mouseButtonDown = false;
this.mouseTarget = null;
}
/**
* Determines if the current text selection is running backwards or forwards.
* StackOverflow answer id: 12652116
*
* @private
* @returns {boolean}
*/
_isSelectionBackwards() {
let backwards = false;
const sel = window.getSelection();
if (!sel.isCollapsed) {
const range = document.createRange();
range.setStart(sel.anchorNode, sel.anchorOffset);
range.setEnd(sel.focusNode, sel.focusOffset);
backwards = range.collapsed;
range.detach();
}
return backwards;
}
/**
* Calculates the text offset of a position in an HTML element, ignoring HTML tags.
*
* @private
* @param {element} node - The parent HTML node.
* @param {number} offset - The offset since the last HTML element.
* @returns {number}
*/
_getOutputHtmlOffset(node, offset) {
const sel = window.getSelection();
const range = document.createRange();
range.selectNodeContents(document.getElementById("output-html"));
range.setEnd(node, offset);
sel.removeAllRanges();
sel.addRange(range);
return sel.toString().length;
}
/**
* Gets the current selection offsets in the output HTML, ignoring HTML tags.
*
* @private
* @returns {Object} pos
* @returns {number} pos.start
* @returns {number} pos.end
*/
_getOutputHtmlSelectionOffsets() {
const sel = window.getSelection();
let range,
start = 0,
end = 0,
backwards = false;
if (sel.rangeCount) {
range = sel.getRangeAt(sel.rangeCount - 1);
backwards = this._isSelectionBackwards();
start = this._getOutputHtmlOffset(range.startContainer, range.startOffset);
end = this._getOutputHtmlOffset(range.endContainer, range.endOffset);
sel.removeAllRanges();
sel.addRange(range);
if (backwards) {
// If selecting backwards, reverse the start and end offsets for the selection to
// prevent deselecting as the drag continues.
sel.collapseToEnd();
sel.extend(sel.anchorNode, range.startOffset);
}
}
return {
start: start,
end: end
};
}
/**
* Handler for input scroll events.
* Scrolls the highlighter pane to match the input textarea position.
*
* @param {event} e
*/
inputScroll(e) {
const el = e.target;
document.getElementById("input-highlighter").scrollTop = el.scrollTop;
document.getElementById("input-highlighter").scrollLeft = el.scrollLeft;
2018-05-15 19:36:45 +02:00
}
/**
* Handler for output scroll events.
* Scrolls the highlighter pane to match the output textarea position.
*
* @param {event} e
*/
outputScroll(e) {
const el = e.target;
document.getElementById("output-highlighter").scrollTop = el.scrollTop;
document.getElementById("output-highlighter").scrollLeft = el.scrollLeft;
}
/**
* Handler for input mousedown events.
* Calculates the current selection info, and highlights the corresponding data in the output.
*
* @param {event} e
*/
inputMousedown(e) {
this.mouseButtonDown = true;
this.mouseTarget = INPUT;
this.removeHighlights();
2022-06-29 19:02:49 +02:00
const sel = document.getSelection();
const start = sel.baseOffset;
const end = sel.extentOffset;
2018-05-15 19:36:45 +02:00
if (start !== 0 || end !== 0) {
this.highlightOutput([{start: start, end: end}]);
}
}
/**
* Handler for output mousedown events.
* Calculates the current selection info, and highlights the corresponding data in the input.
*
* @param {event} e
*/
outputMousedown(e) {
this.mouseButtonDown = true;
this.mouseTarget = OUTPUT;
this.removeHighlights();
2022-07-02 20:23:03 +02:00
const sel = document.getSelection();
const start = sel.baseOffset;
const end = sel.extentOffset;
2018-05-15 19:36:45 +02:00
if (start !== 0 || end !== 0) {
this.highlightInput([{start: start, end: end}]);
}
}
/**
* Handler for input mouseup events.
*
* @param {event} e
*/
inputMouseup(e) {
this.mouseButtonDown = false;
}
/**
* Handler for output mouseup events.
*
* @param {event} e
*/
outputMouseup(e) {
this.mouseButtonDown = false;
}
/**
* Handler for input mousemove events.
* Calculates the current selection info, and highlights the corresponding data in the output.
*
* @param {event} e
*/
inputMousemove(e) {
// Check that the left mouse button is pressed
if (!this.mouseButtonDown ||
e.which !== 1 ||
this.mouseTarget !== INPUT)
return;
2022-06-29 19:02:49 +02:00
const sel = document.getSelection();
const start = sel.baseOffset;
const end = sel.extentOffset;
2018-05-15 19:36:45 +02:00
if (start !== 0 || end !== 0) {
this.highlightOutput([{start: start, end: end}]);
}
}
/**
* Handler for output mousemove events.
* Calculates the current selection info, and highlights the corresponding data in the input.
*
* @param {event} e
*/
outputMousemove(e) {
// Check that the left mouse button is pressed
if (!this.mouseButtonDown ||
e.which !== 1 ||
this.mouseTarget !== OUTPUT)
return;
2022-07-02 20:23:03 +02:00
const sel = document.getSelection();
const start = sel.baseOffset;
const end = sel.extentOffset;
2018-05-15 19:36:45 +02:00
if (start !== 0 || end !== 0) {
this.highlightInput([{start: start, end: end}]);
}
}
/**
* Given start and end offsets, writes the HTML for the selection info element with the correct
* padding.
*
* @param {number} start - The start offset.
* @param {number} end - The end offset.
* @returns {string}
*/
selectionInfo(start, end) {
const len = end.toString().length;
const width = len < 2 ? 2 : len;
const startStr = start.toString().padStart(width, " ").replace(/ /g, "&nbsp;");
const endStr = end.toString().padStart(width, " ").replace(/ /g, "&nbsp;");
const lenStr = (end-start).toString().padStart(width, " ").replace(/ /g, "&nbsp;");
return "start: " + startStr + "<br>end: " + endStr + "<br>length: " + lenStr;
}
/**
* Removes highlighting and selection information.
*/
removeHighlights() {
document.getElementById("input-highlighter").innerHTML = "";
2018-05-15 19:36:45 +02:00
document.getElementById("output-highlighter").innerHTML = "";
}
/**
* Highlights the given offsets in the output.
* We will only highlight if:
* - input hasn't changed since last bake
* - last bake was a full bake
* - all operations in the recipe support highlighting
*
* @param {Object} pos - The position object for the highlight.
* @param {number} pos.start - The start offset.
* @param {number} pos.end - The end offset.
*/
highlightOutput(pos) {
if (!this.app.autoBake_ || this.app.baking) return false;
this.manager.worker.highlight(this.app.getRecipeConfig(), "forward", pos);
}
/**
* Highlights the given offsets in the input.
* We will only highlight if:
* - input hasn't changed since last bake
* - last bake was a full bake
* - all operations in the recipe support highlighting
*
* @param {Object} pos - The position object for the highlight.
* @param {number} pos.start - The start offset.
* @param {number} pos.end - The end offset.
*/
highlightInput(pos) {
if (!this.app.autoBake_ || this.app.baking) return false;
this.manager.worker.highlight(this.app.getRecipeConfig(), "reverse", pos);
}
/**
* Displays highlight offsets sent back from the Chef.
*
* @param {Object} pos - The position object for the highlight.
* @param {number} pos.start - The start offset.
* @param {number} pos.end - The end offset.
* @param {string} direction
*/
displayHighlights(pos, direction) {
if (!pos) return;
2019-06-06 17:33:35 +02:00
if (this.manager.tabs.getActiveInputTab() !== this.manager.tabs.getActiveOutputTab()) return;
2018-05-15 19:36:45 +02:00
const io = direction === "forward" ? "output" : "input";
2022-07-02 20:23:03 +02:00
// TODO
// document.getElementById(io + "-selection-info").innerHTML = this.selectionInfo(pos[0].start, pos[0].end);
2018-05-15 19:36:45 +02:00
this.highlight(
document.getElementById(io + "-text"),
document.getElementById(io + "-highlighter"),
pos);
}
/**
* Adds the relevant HTML to the specified highlight element such that highlighting appears
* underneath the correct offset.
*
* @param {element} textarea - The input or output textarea.
* @param {element} highlighter - The input or output highlighter element.
* @param {Object} pos - The position object for the highlight.
* @param {number} pos.start - The start offset.
* @param {number} pos.end - The end offset.
*/
async highlight(textarea, highlighter, pos) {
2022-07-02 20:23:03 +02:00
// if (!this.app.options.showHighlighter) return false;
// if (!this.app.options.attemptHighlight) return false;
// // Check if there is a carriage return in the output dish as this will not
// // be displayed by the HTML textarea and will mess up highlighting offsets.
// if (await this.manager.output.containsCR()) return false;
// const startPlaceholder = "[startHighlight]";
// const startPlaceholderRegex = /\[startHighlight\]/g;
// const endPlaceholder = "[endHighlight]";
// const endPlaceholderRegex = /\[endHighlight\]/g;
// // let text = textarea.value; // TODO
// // Put placeholders in position
// // If there's only one value, select that
// // If there are multiple, ignore the first one and select all others
// if (pos.length === 1) {
// if (pos[0].end < pos[0].start) return;
// text = text.slice(0, pos[0].start) +
// startPlaceholder + text.slice(pos[0].start, pos[0].end) + endPlaceholder +
// text.slice(pos[0].end, text.length);
// } else {
// // O(n^2) - Can anyone improve this without overwriting placeholders?
// let result = "",
// endPlaced = true;
// for (let i = 0; i < text.length; i++) {
// for (let j = 1; j < pos.length; j++) {
// if (pos[j].end < pos[j].start) continue;
// if (pos[j].start === i) {
// result += startPlaceholder;
// endPlaced = false;
// }
// if (pos[j].end === i) {
// result += endPlaceholder;
// endPlaced = true;
// }
// }
// result += text[i];
// }
// if (!endPlaced) result += endPlaceholder;
// text = result;
// }
// const cssClass = "hl1";
// // Remove HTML tags
// text = text
// .replace(/&/g, "&amp;")
// .replace(/</g, "&lt;")
// .replace(/>/g, "&gt;")
// .replace(/\n/g, "&#10;")
// // Convert placeholders to tags
// .replace(startPlaceholderRegex, "<span class=\""+cssClass+"\">")
// .replace(endPlaceholderRegex, "</span>") + "&nbsp;";
// // Adjust width to allow for scrollbars
// highlighter.style.width = textarea.clientWidth + "px";
// highlighter.innerHTML = text;
// highlighter.scrollTop = textarea.scrollTop;
// highlighter.scrollLeft = textarea.scrollLeft;
2018-05-15 19:36:45 +02:00
}
}
export default HighlighterWaiter;