From f4b1a984467714ec2cd843f2c6d4b28a67906f91 Mon Sep 17 00:00:00 2001
From: "Lester L. Martin II" <dev@amlegion.org>
Date: Thu, 5 Jul 2018 11:22:24 -0500
Subject: [PATCH] Merge in changes from fossil repo.

Fossil repo: https://code.amlegion.org/hljs_line_numbers
Adds: line linking, selection links, multi-selection links, scroll to top
  line in link.
---
 LICENSE                         |  33 +++---
 src/highlightjs-line-numbers.js | 177 +++++++++++++++++++++++++++-----
 2 files changed, 168 insertions(+), 42 deletions(-)

diff --git a/LICENSE b/LICENSE
index 4f023b7..c388371 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,22 +1,23 @@
 The MIT License (MIT)
 
 Copyright (c) 2017 Yauheni Pakala
+Copyright (c) 2018 Lester L. Martin II
 
-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:
+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.
+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/src/highlightjs-line-numbers.js b/src/highlightjs-line-numbers.js
index a045a05..c0f9fde 100644
--- a/src/highlightjs-line-numbers.js
+++ b/src/highlightjs-line-numbers.js
@@ -14,6 +14,7 @@
     if (w.hljs) {
         w.hljs.initLineNumbersOnLoad = initLineNumbersOnLoad;
         w.hljs.lineNumbersBlock = lineNumbersBlock;
+        w.hljs.initLineNumScroll = initLineNumScroll;
 
         addStyles();
     } else {
@@ -26,16 +27,37 @@
         css.innerHTML = format(
             '.{0}{border-collapse:collapse}\
             .{0} td{padding:0}\
-            .{1}:before{content:attr({2})}',
-        [
-            TABLE_NAME,
-            NUMBER_LINE_NAME,
-            DATA_ATTR_NAME
-        ]);
+            .{1}:before{content:attr({2})}{3}',
+            [
+                TABLE_NAME,
+                NUMBER_LINE_NAME,
+                DATA_ATTR_NAME,
+            ]);
         d.getElementsByTagName('head')[0].appendChild(css);
     }
 
     function initLineNumbersOnLoad (options) {
+        var css = d.createElement('style');
+
+        css.innerHTML = '\
+        pre>code{white-space:nowrap;}\
+            .hljs-ln-line{white-space:pre;}\
+            .hljs-ln-code .hljs-ln-line{padding-left:10px;}\
+            .hljs-ln-numbers{width:1px;}\
+            .hljs-ln-n{text-align:right;padding-right:10px;\
+                border-right:1px solid;white-space:nowrap;\
+                padding-left:5px;}\
+            .hljs-ln-code:hover{background-color:rgba(0,0,0,.25)}\
+            .hljs-ln-code *::selection{background-color:rgba(0,0,0,.45);}\
+            .hljs-ln-code *::-moz-selection{background-color:rgba(0,0,0,.45);}\
+            .selectedText{background-color:rgba(0,0,0,.25);\
+                border-left:1px solid;border-right:1px solid;}\
+            .selectedTextBoth{background-color:rgba(0,0,0,.25);border:1px solid;}\
+            .selectedTextFirst{background-color:rgba(0,0,0,.25);\
+                border-top:1px solid;border-left:1px solid;border-right:1px solid;}\
+            .selectedTextLast{background-color:rgba(0,0,0,.25);\
+                border-bottom:1px solid;border-left:1px solid;border-right:1px solid;}'
+        d.getElementsByTagName('head')[0].appendChild(css);
         if (d.readyState === 'complete') {
             documentReady(options);
         } else {
@@ -74,12 +96,12 @@
 
             duplicateMultilineNodes(element);
 
-            element.innerHTML = addLineNumbersBlockFor(element.innerHTML, firstLineIndex);
+            element.innerHTML =
+                addLineNumbersBlockFor(element.innerHTML, firstLineIndex);
         });
     }
 
     function addLineNumbersBlockFor (inputHtml, firstLineIndex) {
-
         var lines = getLines(inputHtml);
 
         // if last line contains only carriage return remove it
@@ -90,31 +112,101 @@
         if (lines.length > firstLineIndex) {
             var html = '';
 
+            // setup for linking lines
+            var currLoc = new URL(window.location.href);
+            currLoc.hash = "";
+            var uri = currLoc.href.endsWith('#') ? currLoc.href : currLoc.href + '#';
+
+            // get our line ranges
+            var lnRanges = splitLineParam(getLineParam());
+
+            // our current lnRanges index, and something to tell us
+            // if we should increment it
+            var currIdx = 0;
+
+            // do we highlight this line?
+            var hl = false;
+            var tmp;
+
             for (var i = 0, l = lines.length; i < l; i++) {
+                tmp = doHighlight(i, lnRanges[currIdx]);
+                hl = tmp[0];
+                currIdx += tmp[1] && currIdx + 1 <= lnRanges.length ? 1 : 0;
                 html += format(
-                    '<tr>\
-                        <td class="{0}">\
-                            <div class="{1} {2}" {3}="{5}"></div>\
-                        </td>\
-                        <td class="{4}">\
-                            <div class="{1}">{6}</div>\
-                        </td>\
+                    '<tr class="{8}">\
+                    <td class="{0}" id="#L{5}">\
+                    <a href="{7}L{5}">\
+                    <div class="{1} {2}" {3}="{5}">\
+                    </div>\
+                    </a>\
+                    </td>\
+                    <td class="{4}">\
+                    <div class="{1}">{6}</div>\
+                    </td>\
                     </tr>',
-                [
-                    NUMBERS_BLOCK_NAME,
-                    LINE_NAME,
-                    NUMBER_LINE_NAME,
-                    DATA_ATTR_NAME,
-                    CODE_BLOCK_NAME,
-                    i + 1,
-                    lines[i].length > 0 ? lines[i] : ' '
-                ]);
+                    [
+                        NUMBERS_BLOCK_NAME,
+                        LINE_NAME,
+                        NUMBER_LINE_NAME,
+                        DATA_ATTR_NAME,
+                        CODE_BLOCK_NAME,
+                        i + 1,
+                        lines[i].length > 0 ? lines[i] : ' ',
+                        uri,
+                        hl ? whichSelectedText(tmp) : " "
+                    ]);
+                hl = false;
             }
 
             return format('<table class="{0}">{1}</table>', [ TABLE_NAME, html ]);
         }
+    }
 
-        return inputHtml;
+    function whichSelectedText(check) {
+        if (check[2] && check[3]) {
+            return "selectedTextBoth";
+        } else if (check[2]) {
+            return "selectedTextFirst";
+        } else if (check[3]) {
+            return "selectedTextLast";
+        } else {
+            return "selectedText";
+        }
+    }
+
+    function getLineParam() {
+        var idx1 = window.location.href.indexOf("&ln=") + 3;
+        var idx2 = window.location.href.indexOf("&", idx1);
+        idx2 = idx2 == -1 ? window.location.href.length : idx2 -1;
+        var len = idx2 - idx1;
+        return window.location.href.substr(idx1+1, len) + window.location.hash;
+    }
+
+    function splitLineParam(param) {
+        // split into $NUM-$NUM ranges or $NUM singulars, then split
+        // each into arrays of numbers
+        var ps = param.match(/\d+(-\d+)?/g);
+        return ps !== null ? ps.map(x => (x.match(/\d+/g)).map (x => parseInt(x))) :
+            [];
+    }
+
+    function doHighlight(idx, range) {
+        var nextRange = false;
+        var first = false;
+        var last = false;
+        try {
+            if (range.length == 1) {
+                nextRange = idx+1 == range[0];
+                return [nextRange, nextRange, nextRange, nextRange];
+            } else if (range.length == 2) {
+                nextRange = idx+1 == range[1];
+                first = idx+1 == range[0];
+                last = idx+1 == range[1];
+                return [idx+1 >= range[0] && idx+1 <= range[1], nextRange, first, last];
+            }
+        } catch (e) {
+            return [false, false, false, false];
+        }
     }
 
     /**
@@ -149,8 +241,10 @@
 
         var lines = getLines(element.innerHTML);
 
+        var line_to_fmt = '<span class="{0}">';
         for (var i = 0, result = ''; i < lines.length; i++) {
-            result += format('<span class="{0}">{1}</span>\n', [ className, lines[i] ]);
+            line_to_fmt = '<span class="{0}">' + lines[i] +'</span>\n';
+            result += format(line_to_fmt, [ className ]);
         }
 
         element.innerHTML = result.trim();
@@ -180,4 +274,35 @@
         });
     }
 
+    function rafAsync() {
+        return new Promise(resolve => {
+            requestAnimationFrame(resolve);
+        });
+    }
+
+    function checkEl(id) {
+        if (document.getElementById(id) === null) {
+            return rafAsync().then(() => checkEl(id));
+        } else {
+            return Promise.resolve(true);
+        }
+    }
+
+    function initLineNumScroll (options) {
+        if (d.readyState === 'complete') {
+            documentReady(options);
+        } else {
+            var el = '#L' + splitLineParam(getLineParam())[0][0].toString();
+            w.addEventListener('DOMContentLoaded', function () {
+                checkEl(el).then((element) => {
+                    try {
+                        document.getElementById(el).scrollIntoView();
+                    } catch (e) {
+
+                    }
+                })
+            });
+        }
+    }
+
 }(window, document));