[epiphany] highlight.js: add new plugin highlightjs-line-numbers v2.8.0



commit 6b8b957033f1c88e7b8fb1181baea454a759433b
Author: Jim Mason <jmason ibinx com>
Date:   Mon May 18 18:39:04 2020 +0100

    highlight.js: add new plugin highlightjs-line-numbers v2.8.0

 embed/ephy-view-source-handler.c                   |   5 +-
 .../highlightjs/LICENSE.highlightjs-line-numbers   |  22 ++
 third-party/highlightjs/README.epiphany            |   9 +
 third-party/highlightjs/epiphany.css               |  36 ++
 .../highlightjs/highlightjs-line-numbers.js        | 366 +++++++++++++++++++++
 third-party/highlightjs/highlightjs.gresource.xml  |   2 +
 6 files changed, 439 insertions(+), 1 deletion(-)
---
diff --git a/embed/ephy-view-source-handler.c b/embed/ephy-view-source-handler.c
index 55878e105..52e29519c 100644
--- a/embed/ephy-view-source-handler.c
+++ b/embed/ephy-view-source-handler.c
@@ -126,11 +126,14 @@ web_resource_data_cb (WebKitWebResource     *resource,
   html = g_strdup_printf ("<head>"
                           "  <link rel='stylesheet' 
href='ephy-resource:///org/gnome/epiphany/highlightjs/nnfx.css' media='(prefers-color-scheme: no-preference), 
(prefers-color-scheme: light)'>"
                           "  <link rel='stylesheet' 
href='ephy-resource:///org/gnome/epiphany/highlightjs/nnfx-dark.css' media='(prefers-color-scheme: dark)'>"
+                          "  <link rel='stylesheet' 
href='ephy-resource:///org/gnome/epiphany/highlightjs/epiphany.css'>"
                           "  <title>%s</title>"
                           "</head>"
                           "<body class='hljs'>"
                           "  <script 
src='ephy-resource:///org/gnome/epiphany/highlightjs/highlight.js'></script>"
-                          "  <script>hljs.initHighlightingOnLoad();</script>"
+                          "  <script 
src='ephy-resource:///org/gnome/epiphany/highlightjs/highlightjs-line-numbers.js'></script>"
+                          "  <script>hljs.initHighlightingOnLoad();"
+                          "          hljs.initLineNumbersOnLoad();</script>"
                           "  <pre><code class='html'>%s</code></pre>"
                           "</body>",
                           webkit_web_resource_get_uri (resource),
diff --git a/third-party/highlightjs/LICENSE.highlightjs-line-numbers 
b/third-party/highlightjs/LICENSE.highlightjs-line-numbers
new file mode 100644
index 000000000..4f023b7ee
--- /dev/null
+++ b/third-party/highlightjs/LICENSE.highlightjs-line-numbers
@@ -0,0 +1,22 @@
+The MIT License (MIT)
+
+Copyright (c) 2017 Yauheni Pakala
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
diff --git a/third-party/highlightjs/README.epiphany b/third-party/highlightjs/README.epiphany
index c26df05ec..a86599554 100644
--- a/third-party/highlightjs/README.epiphany
+++ b/third-party/highlightjs/README.epiphany
@@ -14,4 +14,13 @@ Copy build/highlight.js to <epiphany-source>/third-party/highlightjs/
 Copy src/styles/nnfx.css to <epiphany-source>third-party/highlightjs/
 Copy src/styles/nnfx-dark.css to <epiphany-source>third-party/highlightjs/
 
+# Highlight.js line numbers plugin:
+
+git clone https://github.com/wcoder/highlightjs-line-numbers.js.git
+cd highlightjs-line-numbers.js
+git checkout <version number>
+
+Copy src/highlightjs-line-numbers.js to <epiphany-source>/third-party/highlightjs/
+Copy LICENSE to <epiphany-source>/third-party/highlightjs/LICENSE.highlightjs-line-numbers
+
 # Documentation created by Jan-Michael Brummer <jan brummer tabos org>
diff --git a/third-party/highlightjs/epiphany.css b/third-party/highlightjs/epiphany.css
new file mode 100644
index 000000000..3ef17a9eb
--- /dev/null
+++ b/third-party/highlightjs/epiphany.css
@@ -0,0 +1,36 @@
+/**
+  * Customizations for GNOME Web
+  *
+  * This file provides epiphany-specific customizations to the upstream css.
+  */
+
+.hljs {
+  /*
+   * fix overflow handling
+   *
+   * let the browser window display overflow scrollbars, if any,
+   * instead of placing scrollbars inside the content
+   */
+  overflow-x: visible;
+}
+
+/**
+  * styling for the highlightjs-line-numbers plugin
+  */
+.hljs-ln {
+  white-space: pre;
+}
+
+.hljs-ln .hljs-ln-numbers {
+  -webkit-user-select: none;
+
+  text-align: right;
+  color: #ccc;
+  vertical-align: top;
+  border-right: 1px solid;
+  padding-right: 5px;
+}
+
+.hljs-ln .hljs-ln-code {
+  padding-left: 10px;
+}
diff --git a/third-party/highlightjs/highlightjs-line-numbers.js 
b/third-party/highlightjs/highlightjs-line-numbers.js
new file mode 100644
index 000000000..a6c10e814
--- /dev/null
+++ b/third-party/highlightjs/highlightjs-line-numbers.js
@@ -0,0 +1,366 @@
+// jshint multistr:true
+
+(function (w, d) {
+    'use strict';
+
+    var TABLE_NAME = 'hljs-ln',
+        LINE_NAME = 'hljs-ln-line',
+        CODE_BLOCK_NAME = 'hljs-ln-code',
+        NUMBERS_BLOCK_NAME = 'hljs-ln-numbers',
+        NUMBER_LINE_NAME = 'hljs-ln-n',
+        DATA_ATTR_NAME = 'data-line-number',
+        BREAK_LINE_REGEXP = /\r\n|\r|\n/g;
+
+    if (w.hljs) {
+        w.hljs.initLineNumbersOnLoad = initLineNumbersOnLoad;
+        w.hljs.lineNumbersBlock = lineNumbersBlock;
+        w.hljs.lineNumbersValue = lineNumbersValue;
+
+        addStyles();
+    } else {
+        w.console.error('highlight.js not detected!');
+    }
+
+    function isHljsLnCodeDescendant(domElt) {
+        var curElt = domElt;
+        while (curElt) {
+            if (curElt.className && curElt.className.indexOf('hljs-ln-code') !== -1) {
+                return true;
+            }
+            curElt = curElt.parentNode;
+        }
+        return false;
+    }
+
+    function getHljsLnTable(hljsLnDomElt) {
+        var curElt = hljsLnDomElt;
+        while (curElt.nodeName !== 'TABLE') {
+            curElt = curElt.parentNode;
+        }
+        return curElt;
+    }
+
+    // Function to workaround a copy issue with Microsoft Edge.
+    // Due to hljs-ln wrapping the lines of code inside a <table> element,
+    // itself wrapped inside a <pre> element, window.getSelection().toString()
+    // does not contain any line breaks. So we need to get them back using the
+    // rendered code in the DOM as reference.
+    function edgeGetSelectedCodeLines(selection) {
+        // current selected text without line breaks
+        var selectionText = selection.toString();
+
+        // get the <td> element wrapping the first line of selected code
+        var tdAnchor = selection.anchorNode;
+        while (tdAnchor.nodeName !== 'TD') {
+            tdAnchor = tdAnchor.parentNode;
+        }
+
+        // get the <td> element wrapping the last line of selected code
+        var tdFocus = selection.focusNode;
+        while (tdFocus.nodeName !== 'TD') {
+            tdFocus = tdFocus.parentNode;
+        }
+
+        // extract line numbers
+        var firstLineNumber = parseInt(tdAnchor.dataset.lineNumber);
+        var lastLineNumber = parseInt(tdFocus.dataset.lineNumber);
+
+        // multi-lines copied case
+        if (firstLineNumber != lastLineNumber) {
+
+            var firstLineText = tdAnchor.textContent;
+            var lastLineText = tdFocus.textContent;
+
+            // if the selection was made backward, swap values
+            if (firstLineNumber > lastLineNumber) {
+                var tmp = firstLineNumber;
+                firstLineNumber = lastLineNumber;
+                lastLineNumber = tmp;
+                tmp = firstLineText;
+                firstLineText = lastLineText;
+                lastLineText = tmp;
+            }
+
+            // discard not copied characters in first line
+            while (selectionText.indexOf(firstLineText) !== 0) {
+                firstLineText = firstLineText.slice(1);
+            }
+
+            // discard not copied characters in last line
+            while (selectionText.lastIndexOf(lastLineText) === -1) {
+                lastLineText = lastLineText.slice(0, -1);
+            }
+
+            // reconstruct and return the real copied text
+            var selectedText = firstLineText;
+            var hljsLnTable = getHljsLnTable(tdAnchor);
+            for (var i = firstLineNumber + 1 ; i < lastLineNumber ; ++i) {
+                var codeLineSel = format('.{0}[{1}="{2}"]', [CODE_BLOCK_NAME, DATA_ATTR_NAME, i]);
+                var codeLineElt = hljsLnTable.querySelector(codeLineSel);
+                selectedText += '\n' + codeLineElt.textContent;
+            }
+            selectedText += '\n' + lastLineText;
+            return selectedText;
+        // single copied line case
+        } else {
+            return selectionText;
+        }
+    }
+
+    // ensure consistent code copy/paste behavior across all browsers
+    // (see https://github.com/wcoder/highlightjs-line-numbers.js/issues/51)
+    document.addEventListener('copy', function(e) {
+        // get current selection
+        var selection = window.getSelection();
+        // override behavior when one wants to copy line of codes
+        if (isHljsLnCodeDescendant(selection.anchorNode)) {
+            var selectionText;
+            // workaround an issue with Microsoft Edge as copied line breaks
+            // are removed otherwise from the selection string
+            if (window.navigator.userAgent.indexOf('Edge') !== -1) {
+                selectionText = edgeGetSelectedCodeLines(selection);
+            } else {
+                // other browsers can directly use the selection string
+                selectionText = selection.toString();
+            }
+            e.clipboardData.setData('text/plain', selectionText);
+            e.preventDefault();
+        }
+    });
+
+    function addStyles () {
+        var css = d.createElement('style');
+        css.type = 'text/css';
+        css.innerHTML = format(
+            '.{0}{border-collapse:collapse}' +
+            '.{0} td{padding:0}' +
+            '.{1}:before{content:attr({2})}',
+        [
+            TABLE_NAME,
+            NUMBER_LINE_NAME,
+            DATA_ATTR_NAME
+        ]);
+        d.getElementsByTagName('head')[0].appendChild(css);
+    }
+
+    function initLineNumbersOnLoad (options) {
+        if (d.readyState === 'interactive' || d.readyState === 'complete') {
+            documentReady(options);
+        } else {
+            w.addEventListener('DOMContentLoaded', function () {
+                documentReady(options);
+            });
+        }
+    }
+
+    function documentReady (options) {
+        try {
+            var blocks = d.querySelectorAll('code.hljs,code.nohighlight');
+
+            for (var i in blocks) {
+                if (blocks.hasOwnProperty(i)) {
+                    if (!isPluginDisabledForBlock(blocks[i])) {
+                        lineNumbersBlock(blocks[i], options);
+                    }
+                }
+            }
+        } catch (e) {
+            w.console.error('LineNumbers error: ', e);
+        }
+    }
+
+    function isPluginDisabledForBlock(element) {
+        return element.classList.contains('nohljsln');
+    }
+
+    function lineNumbersBlock (element, options) {
+        if (typeof element !== 'object') return;
+
+        async(function () {
+            element.innerHTML = lineNumbersInternal(element, options);
+        });
+    }
+
+    function lineNumbersValue (value, options) {
+        if (typeof value !== 'string') return;
+
+        var element = document.createElement('code')
+        element.innerHTML = value
+
+        return lineNumbersInternal(element, options);
+    }
+
+    function lineNumbersInternal (element, options) {
+
+        var internalOptions = mapOptions(element, options);
+
+        duplicateMultilineNodes(element);
+
+        return addLineNumbersBlockFor(element.innerHTML, internalOptions);
+    }
+
+    function addLineNumbersBlockFor (inputHtml, options) {
+        var lines = getLines(inputHtml);
+
+        // if last line contains only carriage return remove it
+        if (lines[lines.length-1].trim() === '') {
+            lines.pop();
+        }
+
+        if (lines.length > 1 || options.singleLine) {
+            var html = '';
+
+            for (var i = 0, l = lines.length; i < l; i++) {
+                html += format(
+                    '<tr>' +
+                        '<td class="{0} {1}" {3}="{5}">' +
+                            '<div class="{2}" {3}="{5}"></div>' +
+                        '</td>' +
+                        '<td class="{0} {4}" {3}="{5}">' +
+                            '{6}' +
+                        '</td>' +
+                    '</tr>',
+                [
+                    LINE_NAME,
+                    NUMBERS_BLOCK_NAME,
+                    NUMBER_LINE_NAME,
+                    DATA_ATTR_NAME,
+                    CODE_BLOCK_NAME,
+                    i + options.startFrom,
+                    lines[i].length > 0 ? lines[i] : ' '
+                ]);
+            }
+
+            return format('<table class="{0}">{1}</table>', [ TABLE_NAME, html ]);
+        }
+
+        return inputHtml;
+    }
+
+    /**
+     * @param {HTMLElement} element Code block.
+     * @param {Object} options External API options.
+     * @returns {Object} Internal API options.
+     */
+    function mapOptions (element, options) {
+        options = options || {};
+        return {
+            singleLine: getSingleLineOption(options),
+            startFrom: getStartFromOption(element, options)
+        };
+    }
+
+    function getSingleLineOption (options) {
+        var defaultValue = false;
+        if (!!options.singleLine) {
+            return options.singleLine;
+        }
+        return defaultValue;
+    }
+
+    function getStartFromOption (element, options) {
+        var defaultValue = 1;
+        var startFrom = defaultValue;
+
+        if (isFinite(options.startFrom)) {
+            startFrom = options.startFrom;
+        }
+
+        // can be overridden because local option is priority
+        var value = getAttribute(element, 'data-ln-start-from');
+        if (value !== null) {
+            startFrom = toNumber(value, defaultValue);
+        }
+
+        return startFrom;
+    }
+
+    /**
+     * Recursive method for fix multi-line elements implementation in highlight.js
+     * Doing deep passage on child nodes.
+     * @param {HTMLElement} element
+     */
+    function duplicateMultilineNodes (element) {
+        var nodes = element.childNodes;
+        for (var node in nodes) {
+            if (nodes.hasOwnProperty(node)) {
+                var child = nodes[node];
+                if (getLinesCount(child.textContent) > 0) {
+                    if (child.childNodes.length > 0) {
+                        duplicateMultilineNodes(child);
+                    } else {
+                        duplicateMultilineNode(child.parentNode);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Method for fix multi-line elements implementation in highlight.js
+     * @param {HTMLElement} element
+     */
+    function duplicateMultilineNode (element) {
+        var className = element.className;
+
+        if ( ! /hljs-/.test(className)) return;
+
+        var lines = getLines(element.innerHTML);
+
+        for (var i = 0, result = ''; i < lines.length; i++) {
+            var lineText = lines[i].length > 0 ? lines[i] : ' ';
+            result += format('<span class="{0}">{1}</span>\n', [ className,  lineText ]);
+        }
+
+        element.innerHTML = result.trim();
+    }
+
+    function getLines (text) {
+        if (text.length === 0) return [];
+        return text.split(BREAK_LINE_REGEXP);
+    }
+
+    function getLinesCount (text) {
+        return (text.trim().match(BREAK_LINE_REGEXP) || []).length;
+    }
+
+    ///
+    /// HELPERS
+    ///
+
+    function async (func) {
+        w.setTimeout(func, 0);
+    }
+
+    /**
+     * {@link https://wcoder.github.io/notes/string-format-for-string-formating-in-javascript}
+     * @param {string} format
+     * @param {array} args
+     */
+    function format (format, args) {
+        return format.replace(/\{(\d+)\}/g, function(m, n){
+            return args[n] !== undefined ? args[n] : m;
+        });
+    }
+
+    /**
+     * @param {HTMLElement} element Code block.
+     * @param {String} attrName Attribute name.
+     * @returns {String} Attribute value or empty.
+     */
+    function getAttribute (element, attrName) {
+        return element.hasAttribute(attrName) ? element.getAttribute(attrName) : null;
+    }
+
+    /**
+     * @param {String} str Source string.
+     * @param {Number} fallback Fallback value.
+     * @returns Parsed number or fallback value.
+     */
+    function toNumber (str, fallback) {
+        if (!str) return fallback;
+        var number = Number(str);
+        return isFinite(number) ? number : fallback;
+    }
+
+}(window, document));
diff --git a/third-party/highlightjs/highlightjs.gresource.xml 
b/third-party/highlightjs/highlightjs.gresource.xml
index 3d1ea96ee..db1415675 100644
--- a/third-party/highlightjs/highlightjs.gresource.xml
+++ b/third-party/highlightjs/highlightjs.gresource.xml
@@ -2,6 +2,8 @@
 <gresources>
   <gresource prefix="/org/gnome/epiphany/highlightjs">
     <file compressed="true">highlight.js</file>
+    <file compressed="true">highlightjs-line-numbers.js</file>
+    <file compressed="true">epiphany.css</file>
     <file compressed="true">nnfx.css</file>
     <file compressed="true">nnfx-dark.css</file>
   </gresource>


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]