Let's hope everything works as before or better. Co-authored-by: Adam Bem <adam.bem@zoho.eu> Reviewed-on: #191 Reviewed-by: Mikolaj Widla <widlam@noreply.example.com>
		
			
				
	
	
		
			251 lines
		
	
	
		
			7.5 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			251 lines
		
	
	
		
			7.5 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
 | 
						|
 | 
						|
const mergeHTMLPlugin = (function () {
 | 
						|
    'use strict';
 | 
						|
 | 
						|
    var originalStream;
 | 
						|
 | 
						|
    /**
 | 
						|
     * @param {string} value
 | 
						|
     * @returns {string}
 | 
						|
     */
 | 
						|
    function escapeHTML(value) {
 | 
						|
        return value
 | 
						|
            .replace(/&/g, '&')
 | 
						|
            .replace(/</g, '<')
 | 
						|
            .replace(/>/g, '>')
 | 
						|
            .replace(/"/g, '"')
 | 
						|
            .replace(/'/g, ''');
 | 
						|
    }
 | 
						|
 | 
						|
 | 
						|
    /* plugin itself */
 | 
						|
 | 
						|
    /** @type {HLJSPlugin} */
 | 
						|
    const mergeHTMLPlugin = {
 | 
						|
        // preserve the original HTML token stream
 | 
						|
        "before:highlightElement": ({ el }) => {
 | 
						|
            originalStream = nodeStream(el);
 | 
						|
        },
 | 
						|
        // merge it afterwards with the highlighted token stream
 | 
						|
        "after:highlightElement": ({ el, result, text }) => {
 | 
						|
            if (!originalStream.length) {
 | 
						|
                return;
 | 
						|
            }
 | 
						|
 | 
						|
            const resultNode = document.createElement('div');
 | 
						|
            resultNode.innerHTML = result.value;
 | 
						|
            result.value = mergeStreams(originalStream, nodeStream(resultNode), text);
 | 
						|
            el.innerHTML = result.value;
 | 
						|
        }
 | 
						|
    };
 | 
						|
 | 
						|
    /**
 | 
						|
     * @param {Node} node
 | 
						|
     */
 | 
						|
    function tag(node) {
 | 
						|
        return node.nodeName.toLowerCase();
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * @param {Node} node
 | 
						|
     */
 | 
						|
    function nodeStream(node) {
 | 
						|
        /** @type Event[] */
 | 
						|
        const result = [];
 | 
						|
        (function _nodeStream(node, offset) {
 | 
						|
            for (let child = node.firstChild; child; child = child.nextSibling) {
 | 
						|
                if (child.nodeType === 3) {
 | 
						|
                    offset += child.nodeValue.length;
 | 
						|
                } else if (child.nodeType === 1) {
 | 
						|
                    result.push({
 | 
						|
                        event: 'start',
 | 
						|
                        offset: offset,
 | 
						|
                        node: child
 | 
						|
                    });
 | 
						|
                    offset = _nodeStream(child, offset);
 | 
						|
 | 
						|
                    if (!tag(child).match(/br|hr|img|input/)) {
 | 
						|
                        result.push({
 | 
						|
                            event: 'stop',
 | 
						|
                            offset: offset,
 | 
						|
                            node: child
 | 
						|
                        });
 | 
						|
                    }
 | 
						|
                }
 | 
						|
            }
 | 
						|
            return offset;
 | 
						|
        })(node, 0);
 | 
						|
        return result;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * @param {any} original - the original stream
 | 
						|
     * @param {any} highlighted - stream of the highlighted source
 | 
						|
     * @param {string} value - the original source itself
 | 
						|
     */
 | 
						|
    function mergeStreams(original, highlighted, value) {
 | 
						|
        let processed = 0;
 | 
						|
        let result = '';
 | 
						|
        const nodeStack = [];
 | 
						|
 | 
						|
        function selectStream() {
 | 
						|
            if (!original.length || !highlighted.length) {
 | 
						|
                return original.length ? original : highlighted;
 | 
						|
            }
 | 
						|
            if (original[0].offset !== highlighted[0].offset) {
 | 
						|
                return (original[0].offset < highlighted[0].offset) ? original : highlighted;
 | 
						|
            }
 | 
						|
 | 
						|
            return highlighted[0].event === 'start' ? original : highlighted;
 | 
						|
        }
 | 
						|
 | 
						|
        /**
 | 
						|
         * @param {Node} node
 | 
						|
         */
 | 
						|
        function open(node) {
 | 
						|
            /** @param {Attr} attr */
 | 
						|
            function attributeString(attr) {
 | 
						|
                return ' ' + attr.nodeName + '="' + escapeHTML(attr.value) + '"';
 | 
						|
            }
 | 
						|
 | 
						|
            // @ts-ignore
 | 
						|
            result += '<' + tag(node) + [].map.call(node.attributes, attributeString).join('')
 | 
						|
                + '>';
 | 
						|
        }
 | 
						|
 | 
						|
        /**
 | 
						|
         * @param {Node} node
 | 
						|
         */
 | 
						|
        function close(node) {
 | 
						|
            result += '</' + tag(node) + '>';
 | 
						|
        }
 | 
						|
 | 
						|
        /**
 | 
						|
         * @param {Event} event
 | 
						|
         */
 | 
						|
        function render(event) {
 | 
						|
            (event.event === 'start' ? open : close)(event.node);
 | 
						|
        }
 | 
						|
 | 
						|
        while (original.length || highlighted.length) {
 | 
						|
            let stream = selectStream();
 | 
						|
            result += escapeHTML(value.substring(processed, stream[0].offset));
 | 
						|
            processed = stream[0].offset;
 | 
						|
            if (stream === original) {
 | 
						|
                /*
 | 
						|
                On any opening or closing tag of the original markup we first close
 | 
						|
                the entire highlighted node stack, then render the original tag along
 | 
						|
                with all the following original tags at the same offset and then
 | 
						|
                reopen all the tags on the highlighted stack.
 | 
						|
                */
 | 
						|
                nodeStack.reverse().forEach(close);
 | 
						|
                do {
 | 
						|
                    render(stream.splice(0, 1)[0]);
 | 
						|
                    stream = selectStream();
 | 
						|
                } while (stream === original && stream.length && stream[0].offset === processed);
 | 
						|
                nodeStack.reverse().forEach(open);
 | 
						|
            } else {
 | 
						|
                if (stream[0].event === 'start') {
 | 
						|
                    nodeStack.push(stream[0].node);
 | 
						|
                } else {
 | 
						|
                    nodeStack.pop();
 | 
						|
                }
 | 
						|
                render(stream.splice(0, 1)[0]);
 | 
						|
            }
 | 
						|
        }
 | 
						|
        return result + escapeHTML(value.substr(processed));
 | 
						|
    }
 | 
						|
 | 
						|
    return mergeHTMLPlugin;
 | 
						|
 | 
						|
}());
 | 
						|
 | 
						|
function formatAndValidateJson(errorElement) {
 | 
						|
    const input = document.querySelector('#jsonBlock');
 | 
						|
    const processInfo = document.getElementById(errorElement);
 | 
						|
 | 
						|
    const address = window.location.protocol + "//" + window.location.hostname + ":" + 8081 + "/json/formatting"
 | 
						|
 | 
						|
    fetch(address, {
 | 
						|
        method: 'POST',
 | 
						|
        body: input.textContent
 | 
						|
    })
 | 
						|
        .then(async (response) => {
 | 
						|
            const promise = response.json();
 | 
						|
            if (!response.ok) {
 | 
						|
                throw Error(await promise);
 | 
						|
            }
 | 
						|
 | 
						|
            return promise;
 | 
						|
        })
 | 
						|
        .then((data) => {
 | 
						|
            input.innerText = data.data;
 | 
						|
            processInfo.innerText = "";
 | 
						|
            hljs.highlightElement(input);
 | 
						|
 | 
						|
            processInfo.innerHTML = "<b style='color: green'>Computed in </b> <span style='color: green'>" + data.time + "ms</span>";
 | 
						|
        })
 | 
						|
        .catch((error) => {
 | 
						|
            processInfo.innerHTML = "<b style='color: red'>" + error.data + "</b>";
 | 
						|
            console.error('Error:', error);
 | 
						|
        });
 | 
						|
}
 | 
						|
 | 
						|
function minimizeJson(errorElement) {
 | 
						|
    const input = document.querySelector('#jsonBlock');
 | 
						|
    const processInfo = document.getElementById(errorElement);
 | 
						|
 | 
						|
    const address = window.location.protocol + "//" + window.location.hostname + ":" + 8081 + "/json/minimize"
 | 
						|
 | 
						|
    fetch(address, {
 | 
						|
        method: 'POST',
 | 
						|
        body: input.textContent
 | 
						|
    })
 | 
						|
        .then(async (response) => {
 | 
						|
            const promise = response.json();
 | 
						|
            if (!response.ok) {
 | 
						|
                throw Error(await promise);
 | 
						|
            }
 | 
						|
 | 
						|
            return promise;
 | 
						|
        })
 | 
						|
        .then((data) => {
 | 
						|
            input.innerText = data.data;
 | 
						|
            processInfo.innerText = "";
 | 
						|
            hljs.highlightElement(input);
 | 
						|
 | 
						|
            processInfo.innerHTML = "<b style='color: green'>Computed in </b> <span style='color: green'>" + data.time + "ms</span>";
 | 
						|
        })
 | 
						|
        .catch((error) => {
 | 
						|
            processInfo.innerHTML = "<b style='color: red'>" + error.data + "</b>";
 | 
						|
            console.error('Error:', error);
 | 
						|
        });
 | 
						|
}
 | 
						|
 | 
						|
function clearJsonData() {
 | 
						|
    const input = document.querySelector('#jsonBlock');
 | 
						|
    input.textContent = "";
 | 
						|
}
 | 
						|
 | 
						|
function insertDefaultJson() {
 | 
						|
    const input = document.querySelector('#jsonBlock');
 | 
						|
    input.textContent = "{\"enter\": \"your\", \"json\": \"here\"}";
 | 
						|
    hljs.highlightElement(input);
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * This function is executed after the page is loaded.
 | 
						|
 * 
 | 
						|
 * @function
 | 
						|
 * @name init
 | 
						|
 * @kind function
 | 
						|
 */
 | 
						|
function init() {
 | 
						|
    // Make sure that only plain text is pasted
 | 
						|
    configurePastingInElement("jsonBlock");
 | 
						|
 | 
						|
    hljs.addPlugin(mergeHTMLPlugin);
 | 
						|
}
 | 
						|
 |