grim/hgkeeper-theme

Add mercurial.js as we are importing it
draft
2020-03-06, Gary Kramlich
b8bba4307838
Parents 196699022756
Children f4286b6da71d
Add mercurial.js as we are importing it
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/static/mercurial.js Fri Mar 06 09:22:16 2020 -0600
@@ -0,0 +1,580 @@
+// mercurial.js - JavaScript utility functions
+//
+// Rendering of branch DAGs on the client side
+// Display of elapsed time
+// Show or hide diffstat
+//
+// Copyright 2008 Dirkjan Ochtman <dirkjan AT ochtman DOT nl>
+// Copyright 2006 Alexander Schremmer <alex AT alexanderweb DOT de>
+//
+// derived from code written by Scott James Remnant <scott@ubuntu.com>
+// Copyright 2005 Canonical Ltd.
+//
+// This software may be used and distributed according to the terms
+// of the GNU General Public License, incorporated herein by reference.
+
+var colors = [
+ [ 1.0, 0.0, 0.0 ],
+ [ 1.0, 1.0, 0.0 ],
+ [ 0.0, 1.0, 0.0 ],
+ [ 0.0, 1.0, 1.0 ],
+ [ 0.0, 0.0, 1.0 ],
+ [ 1.0, 0.0, 1.0 ]
+];
+
+function Graph() {
+
+ this.canvas = document.getElementById('graph');
+ this.ctx = this.canvas.getContext('2d');
+ this.ctx.strokeStyle = 'rgb(0, 0, 0)';
+ this.ctx.fillStyle = 'rgb(0, 0, 0)';
+ this.bg = [0, 4];
+ this.cell = [2, 0];
+ this.columns = 0;
+
+}
+
+Graph.prototype = {
+ reset: function() {
+ this.bg = [0, 4];
+ this.cell = [2, 0];
+ this.columns = 0;
+ },
+
+ scale: function(height) {
+ this.bg_height = height;
+ this.box_size = Math.floor(this.bg_height / 1.2);
+ this.cell_height = this.box_size;
+ },
+
+ setColor: function(color, bg, fg) {
+
+ // Set the colour.
+ //
+ // If color is a string, expect an hexadecimal RGB
+ // value and apply it unchanged. If color is a number,
+ // pick a distinct colour based on an internal wheel;
+ // the bg parameter provides the value that should be
+ // assigned to the 'zero' colours and the fg parameter
+ // provides the multiplier that should be applied to
+ // the foreground colours.
+ var s;
+ if(typeof color === "string") {
+ s = "#" + color;
+ } else { //typeof color === "number"
+ color %= colors.length;
+ var red = (colors[color][0] * fg) || bg;
+ var green = (colors[color][1] * fg) || bg;
+ var blue = (colors[color][2] * fg) || bg;
+ red = Math.round(red * 255);
+ green = Math.round(green * 255);
+ blue = Math.round(blue * 255);
+ s = 'rgb(' + red + ', ' + green + ', ' + blue + ')';
+ }
+ this.ctx.strokeStyle = s;
+ this.ctx.fillStyle = s;
+ return s;
+
+ },
+
+ edge: function(x0, y0, x1, y1, color, width) {
+
+ this.setColor(color, 0.0, 0.65);
+ if(width >= 0)
+ this.ctx.lineWidth = width;
+ this.ctx.beginPath();
+ this.ctx.moveTo(x0, y0);
+ this.ctx.lineTo(x1, y1);
+ this.ctx.stroke();
+
+ },
+
+ graphNodeCurrent: function(x, y, radius) {
+ this.ctx.lineWidth = 2;
+ this.ctx.beginPath();
+ this.ctx.arc(x, y, radius * 1.75, 0, Math.PI * 2, true);
+ this.ctx.stroke();
+ },
+
+ graphNodeClosing: function(x, y, radius) {
+ this.ctx.fillRect(x - radius, y - 1.5, radius * 2, 3);
+ },
+
+ graphNodeUnstable: function(x, y, radius) {
+ var x30 = radius * Math.cos(Math.PI / 6);
+ var y30 = radius * Math.sin(Math.PI / 6);
+ this.ctx.lineWidth = 2;
+ this.ctx.beginPath();
+ this.ctx.moveTo(x, y - radius);
+ this.ctx.lineTo(x, y + radius);
+ this.ctx.moveTo(x - x30, y - y30);
+ this.ctx.lineTo(x + x30, y + y30);
+ this.ctx.moveTo(x - x30, y + y30);
+ this.ctx.lineTo(x + x30, y - y30);
+ this.ctx.stroke();
+ },
+
+ graphNodeObsolete: function(x, y, radius) {
+ var p45 = radius * Math.cos(Math.PI / 4);
+ this.ctx.lineWidth = 3;
+ this.ctx.beginPath();
+ this.ctx.moveTo(x - p45, y - p45);
+ this.ctx.lineTo(x + p45, y + p45);
+ this.ctx.moveTo(x - p45, y + p45);
+ this.ctx.lineTo(x + p45, y - p45);
+ this.ctx.stroke();
+ },
+
+ graphNodeNormal: function(x, y, radius) {
+ this.ctx.beginPath();
+ this.ctx.arc(x, y, radius, 0, Math.PI * 2, true);
+ this.ctx.fill();
+ },
+
+ vertex: function(x, y, radius, color, parity, cur) {
+ this.ctx.save();
+ this.setColor(color, 0.25, 0.75);
+ if (cur.graphnode[0] === '@') {
+ this.graphNodeCurrent(x, y, radius);
+ }
+ switch (cur.graphnode.substr(-1)) {
+ case '_':
+ this.graphNodeClosing(x, y, radius);
+ break;
+ case '*':
+ this.graphNodeUnstable(x, y, radius);
+ break;
+ case 'x':
+ this.graphNodeObsolete(x, y, radius);
+ break;
+ default:
+ this.graphNodeNormal(x, y, radius);
+ }
+ this.ctx.restore();
+
+ var left = (this.bg_height - this.box_size) + (this.columns + 1) * this.box_size;
+ var item = document.querySelector('[data-node="' + cur.node + '"]');
+ if (item) {
+ item.style.paddingLeft = left + 'px';
+ }
+ },
+
+ render: function(data) {
+
+ var i, j, cur, line, start, end, color, x, y, x0, y0, x1, y1, column, radius;
+
+ var cols = 0;
+ for (i = 0; i < data.length; i++) {
+ cur = data[i];
+ for (j = 0; j < cur.edges.length; j++) {
+ line = cur.edges[j];
+ cols = Math.max(cols, line[0], line[1]);
+ }
+ }
+ this.canvas.width = (cols + 1) * this.bg_height;
+ this.canvas.height = (data.length + 1) * this.bg_height - 27;
+
+ for (i = 0; i < data.length; i++) {
+
+ var parity = i % 2;
+ this.cell[1] += this.bg_height;
+ this.bg[1] += this.bg_height;
+
+ cur = data[i];
+ var fold = false;
+
+ var prevWidth = this.ctx.lineWidth;
+ for (j = 0; j < cur.edges.length; j++) {
+
+ line = cur.edges[j];
+ start = line[0];
+ end = line[1];
+ color = line[2];
+ var width = line[3];
+ if(width < 0)
+ width = prevWidth;
+ var branchcolor = line[4];
+ if(branchcolor)
+ color = branchcolor;
+
+ if (end > this.columns || start > this.columns) {
+ this.columns += 1;
+ }
+
+ if (start === this.columns && start > end) {
+ fold = true;
+ }
+
+ x0 = this.cell[0] + this.box_size * start + this.box_size / 2;
+ y0 = this.bg[1] - this.bg_height / 2;
+ x1 = this.cell[0] + this.box_size * end + this.box_size / 2;
+ y1 = this.bg[1] + this.bg_height / 2;
+
+ this.edge(x0, y0, x1, y1, color, width);
+
+ }
+ this.ctx.lineWidth = prevWidth;
+
+ // Draw the revision node in the right column
+
+ column = cur.vertex[0];
+ color = cur.vertex[1];
+
+ radius = this.box_size / 8;
+ x = this.cell[0] + this.box_size * column + this.box_size / 2;
+ y = this.bg[1] - this.bg_height / 2;
+ this.vertex(x, y, radius, color, parity, cur);
+
+ if (fold) this.columns -= 1;
+
+ }
+
+ }
+
+};
+
+
+function process_dates(parentSelector){
+
+ // derived from code from mercurial/templatefilter.py
+
+ var scales = {
+ 'year': 365 * 24 * 60 * 60,
+ 'month': 30 * 24 * 60 * 60,
+ 'week': 7 * 24 * 60 * 60,
+ 'day': 24 * 60 * 60,
+ 'hour': 60 * 60,
+ 'minute': 60,
+ 'second': 1
+ };
+
+ function format(count, string){
+ var ret = count + ' ' + string;
+ if (count > 1){
+ ret = ret + 's';
+ }
+ return ret;
+ }
+
+ function shortdate(date){
+ var ret = date.getFullYear() + '-';
+ // getMonth() gives a 0-11 result
+ var month = date.getMonth() + 1;
+ if (month <= 9){
+ ret += '0' + month;
+ } else {
+ ret += month;
+ }
+ ret += '-';
+ var day = date.getDate();
+ if (day <= 9){
+ ret += '0' + day;
+ } else {
+ ret += day;
+ }
+ return ret;
+ }
+
+ function age(datestr){
+ var now = new Date();
+ var once = new Date(datestr);
+ if (isNaN(once.getTime())){
+ // parsing error
+ return datestr;
+ }
+
+ var delta = Math.floor((now.getTime() - once.getTime()) / 1000);
+
+ var future = false;
+ if (delta < 0){
+ future = true;
+ delta = -delta;
+ if (delta > (30 * scales.year)){
+ return "in the distant future";
+ }
+ }
+
+ if (delta > (2 * scales.year)){
+ return shortdate(once);
+ }
+
+ for (var unit in scales){
+ if (!scales.hasOwnProperty(unit)) { continue; }
+ var s = scales[unit];
+ var n = Math.floor(delta / s);
+ if ((n >= 2) || (s === 1)){
+ if (future){
+ return format(n, unit) + ' from now';
+ } else {
+ return format(n, unit) + ' ago';
+ }
+ }
+ }
+ }
+
+ var nodes = document.querySelectorAll((parentSelector || '') + ' .age');
+ var dateclass = new RegExp('\\bdate\\b');
+ for (var i=0; i<nodes.length; ++i){
+ var node = nodes[i];
+ var classes = node.className;
+ var agevalue = age(node.textContent);
+ if (dateclass.test(classes)){
+ // We want both: date + (age)
+ node.textContent += ' ('+agevalue+')';
+ } else {
+ node.title = node.textContent;
+ node.textContent = agevalue;
+ }
+ }
+}
+
+function toggleDiffstat(event) {
+ var curdetails = document.getElementById('diffstatdetails').style.display;
+ var curexpand = curdetails === 'none' ? 'inline' : 'none';
+ document.getElementById('diffstatdetails').style.display = curexpand;
+ document.getElementById('diffstatexpand').style.display = curdetails;
+ event.preventDefault();
+}
+
+function toggleLinewrap(event) {
+ function getLinewrap() {
+ var nodes = document.getElementsByClassName('sourcelines');
+ // if there are no such nodes, error is thrown here
+ return nodes[0].classList.contains('wrap');
+ }
+
+ function setLinewrap(enable) {
+ var nodes = document.getElementsByClassName('sourcelines');
+ var i;
+ for (i = 0; i < nodes.length; i++) {
+ if (enable) {
+ nodes[i].classList.add('wrap');
+ } else {
+ nodes[i].classList.remove('wrap');
+ }
+ }
+
+ var links = document.getElementsByClassName('linewraplink');
+ for (i = 0; i < links.length; i++) {
+ links[i].innerHTML = enable ? 'on' : 'off';
+ }
+ }
+
+ setLinewrap(!getLinewrap());
+ event.preventDefault();
+}
+
+function format(str, replacements) {
+ return str.replace(/%(\w+)%/g, function(match, p1) {
+ return String(replacements[p1]);
+ });
+}
+
+function makeRequest(url, method, onstart, onsuccess, onerror, oncomplete) {
+ var xhr = new XMLHttpRequest();
+ xhr.onreadystatechange = function() {
+ if (xhr.readyState === 4) {
+ try {
+ if (xhr.status === 200) {
+ onsuccess(xhr.responseText);
+ } else {
+ throw 'server error';
+ }
+ } catch (e) {
+ onerror(e);
+ } finally {
+ oncomplete();
+ }
+ }
+ };
+
+ xhr.open(method, url);
+ xhr.overrideMimeType("text/xhtml; charset=" + document.characterSet.toLowerCase());
+ xhr.send();
+ onstart();
+ return xhr;
+}
+
+function removeByClassName(className) {
+ var nodes = document.getElementsByClassName(className);
+ while (nodes.length) {
+ nodes[0].parentNode.removeChild(nodes[0]);
+ }
+}
+
+function docFromHTML(html) {
+ var doc = document.implementation.createHTMLDocument('');
+ doc.documentElement.innerHTML = html;
+ return doc;
+}
+
+function appendFormatHTML(element, formatStr, replacements) {
+ element.insertAdjacentHTML('beforeend', format(formatStr, replacements));
+}
+
+function adoptChildren(from, to) {
+ var nodes = from.children;
+ var curClass = 'c' + Date.now();
+ while (nodes.length) {
+ var node = nodes[0];
+ node = document.adoptNode(node);
+ node.classList.add(curClass);
+ to.appendChild(node);
+ }
+ process_dates('.' + curClass);
+}
+
+function ajaxScrollInit(urlFormat,
+ nextPageVar,
+ nextPageVarGet,
+ containerSelector,
+ messageFormat,
+ mode) {
+ var updateInitiated = false;
+ var container = document.querySelector(containerSelector);
+
+ function scrollHandler() {
+ if (updateInitiated) {
+ return;
+ }
+
+ var scrollHeight = document.documentElement.scrollHeight;
+ var clientHeight = document.documentElement.clientHeight;
+ var scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
+
+ if (scrollHeight - (scrollTop + clientHeight) < 50) {
+ updateInitiated = true;
+ removeByClassName('scroll-loading-error');
+ container.lastElementChild.classList.add('scroll-separator');
+
+ if (!nextPageVar) {
+ var message = {
+ 'class': 'scroll-loading-info',
+ text: 'No more entries'
+ };
+ appendFormatHTML(container, messageFormat, message);
+ return;
+ }
+
+ makeRequest(
+ format(urlFormat, {next: nextPageVar}),
+ 'GET',
+ function onstart() {
+ var message = {
+ 'class': 'scroll-loading',
+ text: 'Loading...'
+ };
+ appendFormatHTML(container, messageFormat, message);
+ },
+ function onsuccess(htmlText) {
+ var doc = docFromHTML(htmlText);
+
+ if (mode === 'graph') {
+ var graph = window.graph;
+ var dataStr = htmlText.match(/^\s*var data = (.*);$/m)[1];
+ var data = JSON.parse(dataStr);
+ graph.reset();
+ adoptChildren(doc.querySelector('#graphnodes'), container.querySelector('#graphnodes'));
+ graph.render(data);
+ } else {
+ adoptChildren(doc.querySelector(containerSelector), container);
+ }
+
+ nextPageVar = nextPageVarGet(htmlText);
+ },
+ function onerror(errorText) {
+ var message = {
+ 'class': 'scroll-loading-error',
+ text: 'Error: ' + errorText
+ };
+ appendFormatHTML(container, messageFormat, message);
+ },
+ function oncomplete() {
+ removeByClassName('scroll-loading');
+ updateInitiated = false;
+ scrollHandler();
+ }
+ );
+ }
+ }
+
+ window.addEventListener('scroll', scrollHandler);
+ window.addEventListener('resize', scrollHandler);
+ scrollHandler();
+}
+
+function renderDiffOptsForm() {
+ // We use URLSearchParams for query string manipulation. Old browsers don't
+ // support this API.
+ if (!("URLSearchParams" in window)) {
+ return;
+ }
+
+ var form = document.getElementById("diffopts-form");
+
+ var KEYS = [
+ "ignorews",
+ "ignorewsamount",
+ "ignorewseol",
+ "ignoreblanklines",
+ ];
+
+ var urlParams = new window.URLSearchParams(window.location.search);
+
+ function updateAndRefresh(e) {
+ var checkbox = e.target;
+ var name = checkbox.id.substr(0, checkbox.id.indexOf("-"));
+ urlParams.set(name, checkbox.checked ? "1" : "0");
+ window.location.search = urlParams.toString();
+ }
+
+ var allChecked = form.getAttribute("data-ignorews") === "1";
+
+ for (var i = 0; i < KEYS.length; i++) {
+ var key = KEYS[i];
+
+ var checkbox = document.getElementById(key + "-checkbox");
+ if (!checkbox) {
+ continue;
+ }
+
+ var currentValue = form.getAttribute("data-" + key);
+ checkbox.checked = currentValue !== "0";
+
+ // ignorews implies ignorewsamount and ignorewseol.
+ if (allChecked && (key === "ignorewsamount" || key === "ignorewseol")) {
+ checkbox.checked = true;
+ checkbox.disabled = true;
+ }
+
+ checkbox.addEventListener("change", updateAndRefresh, false);
+ }
+
+ form.style.display = 'block';
+}
+
+function addDiffStatToggle() {
+ var els = document.getElementsByClassName("diffstattoggle");
+
+ for (var i = 0; i < els.length; i++) {
+ els[i].addEventListener("click", toggleDiffstat, false);
+ }
+}
+
+function addLineWrapToggle() {
+ var els = document.getElementsByClassName("linewraptoggle");
+
+ for (var i = 0; i < els.length; i++) {
+ var nodes = els[i].getElementsByClassName("linewraplink");
+
+ for (var j = 0; j < nodes.length; j++) {
+ nodes[j].addEventListener("click", toggleLinewrap, false);
+ }
+ }
+}
+
+document.addEventListener('DOMContentLoaded', function() {
+ process_dates();
+ addDiffStatToggle();
+ addLineWrapToggle();
+}, false);