mirror of
https://github.com/gchq/CyberChef.git
synced 2025-04-23 16:26:16 -04:00
134 lines
3.6 KiB
JavaScript
134 lines
3.6 KiB
JavaScript
/**
|
|
* @author n1474335 [n1474335@gmail.com]
|
|
* @copyright Crown Copyright 2022
|
|
* @license Apache-2.0
|
|
*/
|
|
|
|
import {WidgetType, Decoration, ViewPlugin} from "@codemirror/view";
|
|
import {escapeControlChars} from "./editorUtils.mjs";
|
|
import {htmlCopyOverride} from "./copyOverride.mjs";
|
|
import Utils from "../../core/Utils.mjs";
|
|
|
|
|
|
/**
|
|
* Adds an HTML widget to the Code Mirror editor
|
|
*/
|
|
class HTMLWidget extends WidgetType {
|
|
|
|
/**
|
|
* HTMLWidget consructor
|
|
*/
|
|
constructor(html, view) {
|
|
super();
|
|
this.html = html;
|
|
this.view = view;
|
|
}
|
|
|
|
/**
|
|
* Builds the DOM node
|
|
* @returns {DOMNode}
|
|
*/
|
|
toDOM() {
|
|
const wrap = document.createElement("span");
|
|
wrap.setAttribute("id", "output-html");
|
|
wrap.innerHTML = this.html;
|
|
|
|
// Find text nodes and replace unprintable chars with control codes
|
|
this.walkTextNodes(wrap);
|
|
|
|
// Add a handler for copy events to ensure the control codes are copied correctly
|
|
wrap.addEventListener("copy", htmlCopyOverride);
|
|
return wrap;
|
|
}
|
|
|
|
/**
|
|
* Walks all text nodes in a given element
|
|
* @param {DOMNode} el
|
|
*/
|
|
walkTextNodes(el) {
|
|
for (const node of el.childNodes) {
|
|
switch (node.nodeType) {
|
|
case Node.TEXT_NODE:
|
|
this.replaceControlChars(node);
|
|
break;
|
|
default:
|
|
if (node.nodeName !== "SCRIPT" &&
|
|
node.nodeName !== "STYLE")
|
|
this.walkTextNodes(node);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Renders control characters in text nodes
|
|
* @param {DOMNode} textNode
|
|
*/
|
|
replaceControlChars(textNode) {
|
|
// .nodeValue unencodes HTML encoding such as < to "<"
|
|
// We must remember to escape any potential HTML in TextNodes as we do not
|
|
// want to render it.
|
|
const textValue = Utils.escapeHtml(textNode.nodeValue);
|
|
const val = escapeControlChars(textValue, true, this.view.state.lineBreak);
|
|
if (val.length !== textNode.nodeValue.length) {
|
|
const node = document.createElement("span");
|
|
node.innerHTML = val;
|
|
textNode.parentNode.replaceChild(node, textNode);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Decorator function to provide a set of widgets for the editor DOM
|
|
* @param {EditorView} view
|
|
* @param {string} html
|
|
* @returns {DecorationSet}
|
|
*/
|
|
function decorateHTML(view, html) {
|
|
const widgets = [];
|
|
if (html.length) {
|
|
const deco = Decoration.widget({
|
|
widget: new HTMLWidget(html, view),
|
|
side: 1
|
|
});
|
|
widgets.push(deco.range(0));
|
|
}
|
|
return Decoration.set(widgets);
|
|
}
|
|
|
|
|
|
/**
|
|
* An HTML Plugin builder
|
|
* @param {Object} htmlOutput
|
|
* @returns {ViewPlugin}
|
|
*/
|
|
export function htmlPlugin(htmlOutput) {
|
|
const plugin = ViewPlugin.fromClass(
|
|
class {
|
|
/**
|
|
* Plugin constructor
|
|
* @param {EditorView} view
|
|
*/
|
|
constructor(view) {
|
|
this.htmlOutput = htmlOutput;
|
|
this.decorations = decorateHTML(view, this.htmlOutput.html);
|
|
}
|
|
|
|
/**
|
|
* Editor update listener
|
|
* @param {ViewUpdate} update
|
|
*/
|
|
update(update) {
|
|
if (this.htmlOutput.changed) {
|
|
this.decorations = decorateHTML(update.view, this.htmlOutput.html);
|
|
this.htmlOutput.changed = false;
|
|
}
|
|
}
|
|
}, {
|
|
decorations: v => v.decorations
|
|
}
|
|
);
|
|
|
|
return plugin;
|
|
}
|