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
--- 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;
}