hgtemplates: synchronize templates with 4.2 release (bug 1360007); r?glob draft
authorGregory Szorc <gps@mozilla.com>
Mon, 15 May 2017 18:33:41 -0700
changeset 11089 ad9cfa85b9956be35a606c7cccd980312caca012
parent 11088 b4e9480bf94dcf01bc0d2431078e9161178161c1
child 11090 a68b52fa07406cf795554dd3667ebd7c284fcb8f
push id1683
push userbmo:gps@mozilla.com
push dateWed, 24 May 2017 01:08:55 +0000
reviewersglob
bugs1360007
hgtemplates: synchronize templates with 4.2 release (bug 1360007); r?glob Templates from the Mercurial 4.2 release have been synchronized into the repo. We still need to synchronize changes to gitweb with our local fork. MozReview-Commit-ID: 6Z9xTvLHbyU
hgtemplates/gitweb/changelogentry.tmpl
hgtemplates/gitweb/changeset.tmpl
hgtemplates/gitweb/fileannotate.tmpl
hgtemplates/gitweb/filelog.tmpl
hgtemplates/gitweb/filerevision.tmpl
hgtemplates/gitweb/map
hgtemplates/map-cmdline.default
hgtemplates/map-cmdline.show
hgtemplates/paper/filelog.tmpl
hgtemplates/paper/filelogentry.tmpl
hgtemplates/paper/filerevision.tmpl
hgtemplates/static/followlines.js
hgtemplates/static/style-gitweb.css
hgtemplates/static/style-paper.css
--- a/hgtemplates/gitweb/changelogentry.tmpl
+++ b/hgtemplates/gitweb/changelogentry.tmpl
@@ -2,13 +2,13 @@
 <a class="title" href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}"><span class="age">{date|rfc822date}</span>{desc|strip|firstline|escape|nonempty}<span class="logtags"> {inbranch%inbranchtag}{branches%branchtag}{tags%tagtag}{bookmarks%bookmarktag}</span></a>
 </div>
 <div class="title_text">
 <div class="log_link">
 <a href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">changeset</a><br/>
 </div>
 <i>{author|obfuscate} [{date|rfc822date}] rev {rev}</i><br/>
 </div>
-<div class="log_body">
+<div class="log_body description">
 {desc|strip|escape|websub|addbreaks|nonempty}
 <br/>
 <br/>
 </div>
--- a/hgtemplates/gitweb/changeset.tmpl
+++ b/hgtemplates/gitweb/changeset.tmpl
@@ -38,17 +38,17 @@ changeset |
 <tr>
  <td>changeset {rev}</td>
  <td style="font-family:monospace"><a class="list" href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">{node|short}</a></td>
 </tr>
 {ifeq(count(parent), '2', parent%changesetparentdiff, parent%changesetparent)}
 {child%changesetchild}
 </table></div>
 
-<div class="page_body">
+<div class="page_body description">
 {desc|strip|escape|websub|addbreaks|nonempty}
 </div>
 <div class="list_head"></div>
 <div class="title_text">
 <table cellspacing="0">
 {files}
 </table></div>
 
--- a/hgtemplates/gitweb/fileannotate.tmpl
+++ b/hgtemplates/gitweb/fileannotate.tmpl
@@ -54,17 +54,17 @@ annotate |
 {child%fileannotatechild}
 <tr>
  <td>permissions</td>
  <td style="font-family:monospace">{permissions|permissions}</td>
 </tr>
 </table>
 </div>
 
-<div class="page_path">
+<div class="page_path description">
 {desc|strip|escape|websub|addbreaks|nonempty}
 </div>
 <div class="page_body">
 <table>
 {annotate%annotateline}
 </table>
 </div>
 
--- a/hgtemplates/gitweb/filelog.tmpl
+++ b/hgtemplates/gitweb/filelog.tmpl
@@ -26,19 +26,24 @@ revisions |
 <a href="{url|urlescape}diff/{symrev}/{file|urlescape}{sessionvars%urlparameter}">diff</a> |
 <a href="{url|urlescape}comparison/{symrev}/{file|urlescape}{sessionvars%urlparameter}">comparison</a> |
 <a href="{url|urlescape}rss-log/tip/{file|urlescape}">rss</a> |
 <a href="{url|urlescape}help{sessionvars%urlparameter}">help</a>
 <br/>
 {nav%filenav}
 </div>
 
-<div class="title" >{file|urlescape}</div>
+<div class="title" >
+  {file|urlescape}{if(linerange,
+' (following lines {linerange}{if(descend, ', descending')} <a href="{url|urlescape}log/{symrev}/{file|urlescape}{sessionvars%urlparameter}">back to filelog</a>)')}
+</div>
 
 <table>
 {entries%filelogentry}
 </table>
 
 <div class="page_nav">
+<a href="{url|urlescape}log/{symrev}/{file|urlescape}{lessvars%urlparameter}">less</a>
+<a href="{url|urlescape}log/{symrev}/{file|urlescape}{morevars%urlparameter}">more</a>
 {nav%filenav}
 </div>
 
 {footer}
--- a/hgtemplates/gitweb/filerevision.tmpl
+++ b/hgtemplates/gitweb/filerevision.tmpl
@@ -54,17 +54,19 @@ file |
 {child%filerevchild}
 <tr>
  <td>permissions</td>
  <td style="font-family:monospace">{permissions|permissions}</td>
 </tr>
 </table>
 </div>
 
-<div class="page_path">
+<div class="page_path description">
 {desc|strip|escape|websub|addbreaks|nonempty}
 </div>
 
 <div class="page_body">
-<pre class="sourcelines stripes">{text%fileline}</pre>
+<pre class="sourcelines stripes" data-logurl="{url|urlescape}log/{symrev}/{file|urlescape}" data-ishead="{ishead}">{text%fileline}</pre>
 </div>
 
+<script type="text/javascript" src="{staticurl|urlescape}followlines.js"></script>
+
 {footer}
--- a/hgtemplates/gitweb/map
+++ b/hgtemplates/gitweb/map
@@ -277,32 +277,33 @@ shortlogentry = '
       </a>
     </td>
     <td class="link" nowrap>
       <a href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">changeset</a> |
       <a href="{url|urlescape}file/{node|short}{sessionvars%urlparameter}">files</a>
     </td>
   </tr>'
 filelogentry = '
-  <tr class="parity{parity}">
+  <tr class="parity{if(patch, '1', '{parity}')}">
     <td class="age"><i class="age">{date|rfc822date}</i></td>
     <td><i>{author|person}</i></td>
     <td>
       <a class="list" href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">
         <b>{desc|strip|firstline|escape|nonempty}</b>
         <span class="logtags">{inbranch%inbranchtag}{branches%branchtag}{tags%tagtag}{bookmarks%bookmarktag}</span>
       </a>
     </td>
     <td class="link">
       <a href="{url|urlescape}file/{node|short}/{file|urlescape}{sessionvars%urlparameter}">file</a> |
       <a href="{url|urlescape}diff/{node|short}/{file|urlescape}{sessionvars%urlparameter}">diff</a> |
       <a href="{url|urlescape}annotate/{node|short}/{file|urlescape}{sessionvars%urlparameter}">annotate</a>
       {rename%filelogrename}
     </td>
-  </tr>'
+  </tr>
+  {if(patch, '<tr><td colspan="4">{diff}</td></tr>')}'
 archiveentry = ' | <a href="{url|urlescape}archive/{symrev}{extension}{ifeq(path,'/','',path|urlescape)}">{type|escape}</a> '
 indexentry = '
   <tr class="parity{parity}">
     <td>
       <a class="list" href="{url|urlescape}{sessionvars%urlparameter}">
         <b>{name|escape}</b>
       </a>
     </td>
--- a/hgtemplates/map-cmdline.default
+++ b/hgtemplates/map-cmdline.default
@@ -24,17 +24,19 @@ lfile_dels = '{if(file_dels,
 
 lfile_copies_switch = '{if(file_copies_switch,
                            label("ui.note log.copies",
                                  "copies:     {file_copies_switch
                                                % ' {name} ({source})'}\n"))}'
 
 # General templates
 _trouble_label = 'trouble.{trouble}'
-_cset_labels = 'log.changeset changeset.{phase}{if(troubles, " changeset.troubled {troubles%_trouble_label}")}'
+_troubles_labels = '{if(troubles, "changeset.troubled {troubles%_trouble_label}")}'
+_obsolete_label = '{if(obsolete, "changeset.obsolete")}'
+_cset_labels = '{separate(" ", "log.changeset", "changeset.{phase}", "{_obsolete_label}", "{_troubles_labels}")}'
 cset = '{label("{_cset_labels}",
                "changeset:   {rev}:{node|short}")}\n'
 
 lphase = '{label("log.phase",
                  "phase:       {phase}")}\n'
 
 fullcset = '{label("{_cset_labels}",
                    "changeset:   {rev}:{node}")}\n'
new file mode 100644
--- /dev/null
+++ b/hgtemplates/map-cmdline.show
@@ -0,0 +1,9 @@
+# TODO there are a few deficiencies in this file:
+# * Due to the way the file is loaded, references to other entities in the
+#   template doesn't work. That requires us to inline.
+# * The "namespace" of the labels needs to be worked out. We currently
+#   piggyback on existing values so color works.
+# * Obsolescence isn't considered for node labels. See _cset_labels in
+#   map-cmdline.default.
+showbookmarks = '{if(active, "*", " ")} {pad(bookmark, longestbookmarklen + 4)}{shortest(node, 5)}\n'
+showwork = '{label("log.changeset changeset.{phase}", shortest(node, 5))}{if(branches, " ({label("log.branch", branch)})")}{if(bookmarks, " ({label("log.bookmarks", bookmarks)})")} {label("log.description", desc|firstline)}'
--- a/hgtemplates/paper/filelog.tmpl
+++ b/hgtemplates/paper/filelog.tmpl
@@ -42,16 +42,18 @@
 </div>
 </div>
 
 <div class="main">
 <h2 class="breadcrumb"><a href="/">Mercurial</a> {pathdef%breadcrumb}</h2>
 <h3>
  log {file|escape} @ {rev}:<a href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">{node|short}</a>
  {branch%changelogbranchname}{tags%changelogtag}{bookmarks%changelogtag}
+ {if(linerange,
+' (following lines {linerange}{if(descend, ', descending')} <a href="{url|urlescape}log/{symrev}/{file|urlescape}{sessionvars%urlparameter}">back to filelog</a>)')}
 </h3>
 
 <form class="search" action="{url|urlescape}log">
 {sessionvars%hiddenformentry}
 <p><input name="rev" id="search1" type="text" size="30" /></p>
 <div id="hint">{searchhint}</div>
 </form>
 
--- a/hgtemplates/paper/filelogentry.tmpl
+++ b/hgtemplates/paper/filelogentry.tmpl
@@ -1,8 +1,9 @@
  <tr>
   <td class="age">{date|rfc822date}</td>
   <td class="author">{author|person}</td>
   <td class="description">
    <a href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">{desc|strip|firstline|escape|nonempty}</a>
    {inbranch%changelogbranchname}{branches%changelogbranchhead}{tags%changelogtag}{bookmarks%changelogtag}{rename%filelogrename}
   </td>
  </tr>
+ {if(patch, '<tr><td colspan="3">{diff}</td></tr>')}
--- a/hgtemplates/paper/filerevision.tmpl
+++ b/hgtemplates/paper/filerevision.tmpl
@@ -66,14 +66,17 @@
  <th class="author">children</th>
  <td class="author">{child%filerevchild}</td>
 </tr>
 </table>
 
 <div class="overflow">
 <div class="sourcefirst linewraptoggle">line wrap: <a class="linewraplink" href="javascript:toggleLinewrap()">on</a></div>
 <div class="sourcefirst"> line source</div>
-<pre class="sourcelines stripes4 wrap bottomline">{text%fileline}</pre>
+<pre class="sourcelines stripes4 wrap bottomline" data-logurl="{url|urlescape}log/{symrev}/{file|urlescape}" data-ishead="{ishead}">{text%fileline}</pre>
 </div>
+
+<script type="text/javascript" src="{staticurl|urlescape}followlines.js"></script>
+
 </div>
 </div>
 
 {footer}
new file mode 100644
--- /dev/null
+++ b/hgtemplates/static/followlines.js
@@ -0,0 +1,234 @@
+// followlines.js - JavaScript utilities for followlines UI
+//
+// Copyright 2017 Logilab SA <contact@logilab.fr>
+//
+// 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 */
+document.addEventListener('DOMContentLoaded', function() {
+    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;
+    }
+
+    var isHead = parseInt(sourcelines.dataset.ishead || "0");
+
+    // tooltip to invite on lines selection
+    var tooltip = document.createElement('div');
+    tooltip.id = 'followlines-tooltip';
+    tooltip.classList.add('hidden');
+    var initTooltipText = 'click to start following lines history from here';
+    tooltip.textContent = initTooltipText;
+    sourcelines.appendChild(tooltip);
+
+    //* position "element" on top-right of cursor */
+    function positionTopRight(element, event) {
+        var x = (event.clientX + 10) + 'px',
+            y = (event.clientY - 20) + 'px';
+        element.style.top = y;
+        element.style.left = x;
+    }
+
+    var tooltipTimeoutID;
+    //* move the "tooltip" with cursor (top-right) and show it after 1s */
+    function moveAndShowTooltip(e) {
+        if (typeof tooltipTimeoutID !== 'undefined') {
+            // avoid accumulation of timeout callbacks (blinking)
+            window.clearTimeout(tooltipTimeoutID);
+        }
+        tooltip.classList.add('hidden');
+        positionTopRight(tooltip, e);
+        tooltipTimeoutID = window.setTimeout(function() {
+            tooltip.classList.remove('hidden');
+        }, 1000);
+    }
+
+    // on mousemove, show tooltip close to cursor position
+    sourcelines.addEventListener('mousemove', moveAndShowTooltip);
+
+    // retrieve all direct <span> children of <pre class="sourcelines">
+    var spans = Array.prototype.filter.call(
+        sourcelines.children,
+        function(x) { return x.tagName === 'SPAN' });
+
+    // add a "followlines-select" class to change cursor type in CSS
+    for (var i = 0; i < spans.length; i++) {
+        spans[i].classList.add('followlines-select');
+    }
+
+    var lineSelectedCSSClass = 'followlines-selected';
+
+    //** add CSS class on <span> 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 <span> 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 <span> (maybe <a>): abort, keeping event listener
+            // registered for other click with <span> target
+            return;
+        }
+
+        // update tooltip text
+        tooltip.textContent = 'click again to terminate line block selection here';
+
+        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 <span> (maybe <a>): abort, keeping event listener
+                // registered for other click with <span> target
+                return;
+            }
+
+            // remove this event listener
+            sourcelines.removeEventListener('click', lineSelectEnd);
+
+            // hide tooltip and disable motion tracking
+            tooltip.classList.add('hidden');
+            sourcelines.removeEventListener('mousemove', moveAndShowTooltip);
+            window.clearTimeout(tooltipTimeoutID);
+
+            //* restore initial "tooltip" state */
+            function restoreTooltip() {
+                tooltip.textContent = initTooltipText;
+                sourcelines.addEventListener('mousemove', moveAndShowTooltip);
+            }
+
+            // 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, event listener for selection start, tooltip)
+                removeSelectedCSSClass();
+                sourcelines.addEventListener('click', lineSelectStart);
+                restoreTooltip();
+                return;
+            }
+            var inviteElement = endElement;
+            if (endId < startId) {
+                var tmp = endId;
+                endId = startId;
+                startId = tmp;
+                inviteElement = startElement;
+            }
+
+            addSelectedCSSClass(startId - 1, endId -1);  // CSS
+
+            // append the <div id="followlines"> element to last line of the
+            // selection block
+            var divAndButton = followlinesBox(targetUri, startId, endId, isHead);
+            var div = divAndButton[0],
+                button = divAndButton[1];
+            inviteElement.appendChild(div);
+            // set position close to cursor (top-right)
+            positionTopRight(div, e);
+
+            //** 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();
+                // restore tooltip element
+                restoreTooltip();
+            }
+
+            // bind cancel event to click on <button>
+            button.addEventListener('click', cancel);
+            // as well as on an click on any source line
+            sourcelines.addEventListener('click', cancel);
+        }
+
+        sourcelines.addEventListener('click', lineSelectEnd);
+
+    }
+
+    sourcelines.addEventListener('click', lineSelectStart);
+
+    //** return a <div id="followlines"> and inner cancel <button> elements */
+    function followlinesBox(targetUri, fromline, toline, isHead) {
+        // <div id="followlines">
+        var div = document.createElement('div');
+        div.id = 'followlines';
+
+        //   <div class="followlines-cancel">
+        var buttonDiv = document.createElement('div');
+        buttonDiv.classList.add('followlines-cancel');
+
+        //     <button>x</button>
+        var button = document.createElement('button');
+        button.textContent = 'x';
+        buttonDiv.appendChild(button);
+        div.appendChild(buttonDiv);
+
+        //   <div class="followlines-link">
+        var aDiv = document.createElement('div');
+        aDiv.classList.add('followlines-link');
+        aDiv.textContent = 'follow history of lines ' + fromline + ':' + toline + ':';
+        var linesep = document.createElement('br');
+        aDiv.appendChild(linesep);
+        //     link to "ascending" followlines
+        var aAsc = document.createElement('a');
+        var url = targetUri + '?patch=&linerange=' + fromline + ':' + toline;
+        aAsc.setAttribute('href', url);
+        aAsc.textContent = 'older';
+        aDiv.appendChild(aAsc);
+
+        if (!isHead) {
+            var sep = document.createTextNode(' / ');
+            aDiv.appendChild(sep);
+            //     link to "descending" followlines
+            var aDesc = document.createElement('a');
+            aDesc.setAttribute('href', url + '&descend=');
+            aDesc.textContent = 'newer';
+            aDiv.appendChild(aDesc);
+        }
+
+        div.appendChild(aDiv);
+
+        return [div, button];
+    }
+
+}, false);
--- a/hgtemplates/static/style-gitweb.css
+++ b/hgtemplates/static/style-gitweb.css
@@ -140,16 +140,76 @@ pre.sourcelines > a {
 	height: 1em;
 }
 tr:target td,
 pre.sourcelines > span:target,
 pre.sourcelines.stripes > span:target {
 	background-color: #bfdfff;
 }
 
+.description {
+    font-family: monospace;
+}
+
+/* Followlines */
+div.page_body pre.sourcelines > span.followlines-select:hover {
+  cursor: cell;
+}
+
+pre.sourcelines > span.followlines-selected {
+  background-color: #99C7E9 !important;
+}
+
+div#followlines {
+  background-color: #B7B7B7;
+  border: 1px solid #CCC;
+  border-radius: 5px;
+  padding: 4px;
+  position: fixed;
+}
+
+div.followlines-cancel {
+  text-align: right;
+}
+
+div.followlines-cancel > button {
+  line-height: 80%;
+  padding: 0;
+  border: 0;
+  border-radius: 2px;
+  background-color: inherit;
+  font-weight: bold;
+}
+
+div.followlines-cancel > button:hover {
+  color: #FFFFFF;
+  background-color: #CF1F1F;
+}
+
+div.followlines-link {
+  margin: 2px;
+  margin-top: 4px;
+  font-family: sans-serif;
+}
+
+div#followlines-tooltip {
+  display: none;
+  position: fixed;
+  background-color: #ffc;
+  border: 1px solid #999;
+  padding: 2px;
+}
+
+.sourcelines:hover > div#followlines-tooltip {
+  display: inline;
+}
+
+.sourcelines:hover > div#followlines-tooltip.hidden {
+  display: none;
+}
 /* Graph */
 div#wrapper {
 	position: relative;
 	margin: 0;
 	padding: 0;
 	margin-top: 3px;
 }
 
--- a/hgtemplates/static/style-paper.css
+++ b/hgtemplates/static/style-paper.css
@@ -275,16 +275,72 @@ td.annotate:hover div.annotate-info { di
   content: counters(lineno, ".");
   float: left;
 }
 
 .sourcelines > span:target, tr:target td {
   background-color: #bfdfff;
 }
 
+div.overflow pre.sourcelines > span.followlines-select:hover {
+  cursor: cell;
+}
+
+pre.sourcelines > span.followlines-selected {
+  background-color: #99C7E9;
+}
+
+div#followlines {
+  background-color: #B7B7B7;
+  border: 1px solid #CCC;
+  border-radius: 5px;
+  padding: 4px;
+  position: fixed;
+}
+
+div.followlines-cancel {
+  text-align: right;
+}
+
+div.followlines-cancel > button {
+  line-height: 80%;
+  padding: 0;
+  border: 0;
+  border-radius: 2px;
+  background-color: inherit;
+  font-weight: bold;
+}
+
+div.followlines-cancel > button:hover {
+  color: #FFFFFF;
+  background-color: #CF1F1F;
+}
+
+div.followlines-link {
+  margin: 2px;
+  margin-top: 4px;
+  font-family: sans-serif;
+}
+
+div#followlines-tooltip {
+  display: none;
+  position: fixed;
+  background-color: #ffc;
+  border: 1px solid #999;
+  padding: 2px;
+}
+
+.sourcelines:hover > div#followlines-tooltip {
+  display: inline;
+}
+
+.sourcelines:hover > div#followlines-tooltip.hidden {
+  display: none;
+}
+
 .sourcelines > a {
     display: inline-block;
     position: absolute;
     left: 0px;
     width: 4em;
     height: 1em;
 }