# HG changeset patch # User Denis Laxalde # Date 1490819176 -7200 # Node ID 04ec317b81280c189fcea33a05c8cbbac3c186b1 # Parent 473f2fcc762996958185520e47362d3b7243c37e hgweb: expose a followlines UI in filerevision view In filerevision view (/file//) we add some event listeners on mouse clicks of elements in the
 block.
Those listeners will capture a range of lines selected between two mouse
clicks and a box inviting to follow the history of selected lines will then
show up. Selected lines (i.e. the block of lines) get a CSS class which make
them highlighted. Selection can be cancelled (and restarted) by either
clicking on the cancel ("x") button in the invite box or clicking on any other
source line. Also clicking twice on the same line will abort the selection and
reset event listeners to restart the process.

As a first step, this action is only advertised by the "cursor: cell" CSS rule
on source lines elements as any other mechanisms would make the code
significantly more complicated. This might be improved later.

All JavaScript code lives in a new "linerangelog.js" file, sourced in
filerevision template (only in "paper" style for now).

diff -r 473f2fcc7629 -r 04ec317b8128 contrib/wix/templates.wxs
--- a/contrib/wix/templates.wxs	Wed Mar 29 05:31:31 2017 -0700
+++ b/contrib/wix/templates.wxs	Wed Mar 29 22:26:16 2017 +0200
@@ -225,6 +225,7 @@
             
             
             
+            
             
             
             
diff -r 473f2fcc7629 -r 04ec317b8128 mercurial/templates/paper/filerevision.tmpl
--- a/mercurial/templates/paper/filerevision.tmpl	Wed Mar 29 05:31:31 2017 -0700
+++ b/mercurial/templates/paper/filerevision.tmpl	Wed Mar 29 22:26:16 2017 +0200
@@ -71,8 +71,11 @@
 
line wrap: on
line source
-
{text%fileline}
+
{text%fileline}
+ + + diff -r 473f2fcc7629 -r 04ec317b8128 mercurial/templates/static/linerangelog.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/templates/static/linerangelog.js Wed Mar 29 22:26:16 2017 +0200 @@ -0,0 +1,163 @@ +// linerangelog.js - JavaScript utilities for followlines UI +// +// Copyright 2017 Logilab SA +// +// This software may be used and distributed according to the terms of the +// GNU General Public License version 2 or any later version. + +//** Install event listeners for line block selection and followlines action */ +function installLineSelect() { + var sourcelines = document.getElementsByClassName('sourcelines')[0]; + if (typeof sourcelines === 'undefined') { + return; + } + // URL to complement with "linerange" query parameter + var targetUri = sourcelines.dataset.logurl; + if (typeof targetUri === 'undefined') { + return; + } + + // retrieve all direct children of
+    var spans = Array.prototype.filter.call(
+        sourcelines.children,
+        function(x) { return x.tagName === 'SPAN' });
+
+    var lineSelectedCSSClass = 'followlines-selected';
+
+    //** add CSS class on  element in `from`-`to` line range */
+    function addSelectedCSSClass(from, to) {
+        for (var i = from; i <= to; i++) {
+            spans[i].classList.add(lineSelectedCSSClass);
+        }
+    }
+
+    //** remove CSS class from previously selected lines */
+    function removeSelectedCSSClass() {
+        var elements = sourcelines.getElementsByClassName(
+            lineSelectedCSSClass);
+        while (elements.length) {
+            elements[0].classList.remove(lineSelectedCSSClass);
+        }
+    }
+
+    // ** return the  element parent of `element` */
+    function findParentSpan(element) {
+        var parent = element.parentElement;
+        if (parent === null) {
+            return null;
+        }
+        if (element.tagName == 'SPAN' && parent.isSameNode(sourcelines)) {
+            return element;
+        }
+        return findParentSpan(parent);
+    }
+
+    //** event handler for "click" on the first line of a block */
+    function lineSelectStart(e) {
+        var startElement = findParentSpan(e.target);
+        if (startElement === null) {
+            // not a  (maybe ): abort, keeping event listener
+            // registered for other click with  target
+            return;
+        }
+        var startId = parseInt(startElement.id.slice(1));
+        startElement.classList.add(lineSelectedCSSClass); // CSS
+
+        // remove this event listener
+        sourcelines.removeEventListener('click', lineSelectStart);
+
+        //** event handler for "click" on the last line of the block */
+        function lineSelectEnd(e) {
+            var endElement = findParentSpan(e.target);
+            if (endElement === null) {
+                // not a  (maybe ): abort, keeping event listener
+                // registered for other click with  target
+                return;
+            }
+
+            // remove this event listener
+            sourcelines.removeEventListener('click', lineSelectEnd);
+
+            // compute line range (startId, endId)
+            var endId = parseInt(endElement.id.slice(1));
+            if (endId == startId) {
+                // clicked twice the same line, cancel and reset initial state
+                // (CSS and event listener for selection start)
+                removeSelectedCSSClass();
+                sourcelines.addEventListener('click', lineSelectStart);
+                return;
+            }
+            var inviteElement = endElement;
+            if (endId < startId) {
+                var tmp = endId;
+                endId = startId;
+                startId = tmp;
+                inviteElement = startElement;
+            }
+
+            addSelectedCSSClass(startId - 1, endId -1);  // CSS
+
+            // append the 
element to last line of the + // selection block + var divAndButton = followlinesBox(targetUri, startId, endId); + var div = divAndButton[0], + button = divAndButton[1]; + inviteElement.appendChild(div); + + //** event handler for cancelling selection */ + function cancel() { + // remove invite box + div.parentNode.removeChild(div); + // restore initial event listeners + sourcelines.addEventListener('click', lineSelectStart); + sourcelines.removeEventListener('click', cancel); + // remove styles on selected lines + removeSelectedCSSClass(); + } + + // bind cancel event to click on + var button = document.createElement('button'); + button.textContent = 'x'; + buttonDiv.appendChild(button); + div.appendChild(buttonDiv); + + //
@@ -1468,9 +1471,12 @@
line wrap: on
line source
-
+  
   another
+ + + diff -r 473f2fcc7629 -r 04ec317b8128 tests/test-highlight.t --- a/tests/test-highlight.t Wed Mar 29 05:31:31 2017 -0700 +++ b/tests/test-highlight.t Wed Mar 29 22:26:16 2017 +0200 @@ -149,7 +149,7 @@
line wrap: on
line source
-
+  
   #!/usr/bin/env python
   
   """Fun with generators. Corresponding Haskell implementation:
@@ -184,6 +184,9 @@
       print "The first %d primes: %s" % (n, list(islice(p, n)))
   
+ + +