Mercurial Hosting > hghosting
comparison templates/static/mercurial.js @ 0:dfc36e7ed22c
init
author | Vadim Filimonov <fffilimonov@yandex.ru> |
---|---|
date | Thu, 12 May 2022 13:51:59 +0400 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:dfc36e7ed22c |
---|---|
1 // mercurial.js - JavaScript utility functions | |
2 // | |
3 // Rendering of branch DAGs on the client side | |
4 // Display of elapsed time | |
5 // Show or hide diffstat | |
6 // | |
7 // Copyright 2008 Dirkjan Ochtman <dirkjan AT ochtman DOT nl> | |
8 // Copyright 2006 Alexander Schremmer <alex AT alexanderweb DOT de> | |
9 // | |
10 // derived from code written by Scott James Remnant <scott@ubuntu.com> | |
11 // Copyright 2005 Canonical Ltd. | |
12 // | |
13 // This software may be used and distributed according to the terms | |
14 // of the GNU General Public License, incorporated herein by reference. | |
15 | |
16 var colors = [ | |
17 [ 1.0, 0.0, 0.0 ], | |
18 [ 1.0, 1.0, 0.0 ], | |
19 [ 0.0, 1.0, 0.0 ], | |
20 [ 0.0, 1.0, 1.0 ], | |
21 [ 0.0, 0.0, 1.0 ], | |
22 [ 1.0, 0.0, 1.0 ] | |
23 ]; | |
24 | |
25 function Graph() { | |
26 | |
27 this.canvas = document.getElementById('graph'); | |
28 this.ctx = this.canvas.getContext('2d'); | |
29 this.ctx.strokeStyle = 'rgb(0, 0, 0)'; | |
30 this.ctx.fillStyle = 'rgb(0, 0, 0)'; | |
31 this.bg = [0, 4]; | |
32 this.cell = [2, 0]; | |
33 this.columns = 0; | |
34 | |
35 } | |
36 | |
37 Graph.prototype = { | |
38 reset: function() { | |
39 this.bg = [0, 4]; | |
40 this.cell = [2, 0]; | |
41 this.columns = 0; | |
42 }, | |
43 | |
44 scale: function(height) { | |
45 this.bg_height = height; | |
46 this.box_size = Math.floor(this.bg_height / 1.2); | |
47 this.cell_height = this.box_size; | |
48 }, | |
49 | |
50 setColor: function(color, bg, fg) { | |
51 | |
52 // Set the colour. | |
53 // | |
54 // If color is a string, expect an hexadecimal RGB | |
55 // value and apply it unchanged. If color is a number, | |
56 // pick a distinct colour based on an internal wheel; | |
57 // the bg parameter provides the value that should be | |
58 // assigned to the 'zero' colours and the fg parameter | |
59 // provides the multiplier that should be applied to | |
60 // the foreground colours. | |
61 var s; | |
62 if(typeof color === "string") { | |
63 s = "#" + color; | |
64 } else { //typeof color === "number" | |
65 color %= colors.length; | |
66 var red = (colors[color][0] * fg) || bg; | |
67 var green = (colors[color][1] * fg) || bg; | |
68 var blue = (colors[color][2] * fg) || bg; | |
69 red = Math.round(red * 255); | |
70 green = Math.round(green * 255); | |
71 blue = Math.round(blue * 255); | |
72 s = 'rgb(' + red + ', ' + green + ', ' + blue + ')'; | |
73 } | |
74 this.ctx.strokeStyle = s; | |
75 this.ctx.fillStyle = s; | |
76 return s; | |
77 | |
78 }, | |
79 | |
80 edge: function(x0, y0, x1, y1, color, width) { | |
81 | |
82 this.setColor(color, 0.0, 0.65); | |
83 if(width >= 0) | |
84 this.ctx.lineWidth = width; | |
85 this.ctx.beginPath(); | |
86 this.ctx.moveTo(x0, y0); | |
87 this.ctx.lineTo(x1, y1); | |
88 this.ctx.stroke(); | |
89 | |
90 }, | |
91 | |
92 graphNodeCurrent: function(x, y, radius) { | |
93 this.ctx.lineWidth = 2; | |
94 this.ctx.beginPath(); | |
95 this.ctx.arc(x, y, radius * 1.75, 0, Math.PI * 2, true); | |
96 this.ctx.stroke(); | |
97 }, | |
98 | |
99 graphNodeClosing: function(x, y, radius) { | |
100 this.ctx.fillRect(x - radius, y - 1.5, radius * 2, 3); | |
101 }, | |
102 | |
103 graphNodeUnstable: function(x, y, radius) { | |
104 var x30 = radius * Math.cos(Math.PI / 6); | |
105 var y30 = radius * Math.sin(Math.PI / 6); | |
106 this.ctx.lineWidth = 2; | |
107 this.ctx.beginPath(); | |
108 this.ctx.moveTo(x, y - radius); | |
109 this.ctx.lineTo(x, y + radius); | |
110 this.ctx.moveTo(x - x30, y - y30); | |
111 this.ctx.lineTo(x + x30, y + y30); | |
112 this.ctx.moveTo(x - x30, y + y30); | |
113 this.ctx.lineTo(x + x30, y - y30); | |
114 this.ctx.stroke(); | |
115 }, | |
116 | |
117 graphNodeObsolete: function(x, y, radius) { | |
118 var p45 = radius * Math.cos(Math.PI / 4); | |
119 this.ctx.lineWidth = 3; | |
120 this.ctx.beginPath(); | |
121 this.ctx.moveTo(x - p45, y - p45); | |
122 this.ctx.lineTo(x + p45, y + p45); | |
123 this.ctx.moveTo(x - p45, y + p45); | |
124 this.ctx.lineTo(x + p45, y - p45); | |
125 this.ctx.stroke(); | |
126 }, | |
127 | |
128 graphNodeNormal: function(x, y, radius) { | |
129 this.ctx.beginPath(); | |
130 this.ctx.arc(x, y, radius, 0, Math.PI * 2, true); | |
131 this.ctx.fill(); | |
132 }, | |
133 | |
134 vertex: function(x, y, radius, color, parity, cur) { | |
135 this.ctx.save(); | |
136 this.setColor(color, 0.25, 0.75); | |
137 if (cur.graphnode[0] === '@') { | |
138 this.graphNodeCurrent(x, y, radius); | |
139 } | |
140 switch (cur.graphnode.substr(-1)) { | |
141 case '_': | |
142 this.graphNodeClosing(x, y, radius); | |
143 break; | |
144 case '*': | |
145 this.graphNodeUnstable(x, y, radius); | |
146 break; | |
147 case 'x': | |
148 this.graphNodeObsolete(x, y, radius); | |
149 break; | |
150 default: | |
151 this.graphNodeNormal(x, y, radius); | |
152 } | |
153 this.ctx.restore(); | |
154 | |
155 var left = (this.bg_height - this.box_size) + (this.columns + 1) * this.box_size; | |
156 var item = document.querySelector('[data-node="' + cur.node + '"]'); | |
157 if (item) { | |
158 item.style.paddingLeft = left + 'px'; | |
159 } | |
160 }, | |
161 | |
162 render: function(data) { | |
163 | |
164 var i, j, cur, line, start, end, color, x, y, x0, y0, x1, y1, column, radius; | |
165 | |
166 var cols = 0; | |
167 for (i = 0; i < data.length; i++) { | |
168 cur = data[i]; | |
169 for (j = 0; j < cur.edges.length; j++) { | |
170 line = cur.edges[j]; | |
171 cols = Math.max(cols, line[0], line[1]); | |
172 } | |
173 } | |
174 this.canvas.width = (cols + 1) * this.bg_height; | |
175 this.canvas.height = (data.length + 1) * this.bg_height - 27; | |
176 | |
177 for (i = 0; i < data.length; i++) { | |
178 | |
179 var parity = i % 2; | |
180 this.cell[1] += this.bg_height; | |
181 this.bg[1] += this.bg_height; | |
182 | |
183 cur = data[i]; | |
184 var fold = false; | |
185 | |
186 var prevWidth = this.ctx.lineWidth; | |
187 for (j = 0; j < cur.edges.length; j++) { | |
188 | |
189 line = cur.edges[j]; | |
190 start = line[0]; | |
191 end = line[1]; | |
192 color = line[2]; | |
193 var width = line[3]; | |
194 if(width < 0) | |
195 width = prevWidth; | |
196 var branchcolor = line[4]; | |
197 if(branchcolor) | |
198 color = branchcolor; | |
199 | |
200 if (end > this.columns || start > this.columns) { | |
201 this.columns += 1; | |
202 } | |
203 | |
204 if (start === this.columns && start > end) { | |
205 fold = true; | |
206 } | |
207 | |
208 x0 = this.cell[0] + this.box_size * start + this.box_size / 2; | |
209 y0 = this.bg[1] - this.bg_height / 2; | |
210 x1 = this.cell[0] + this.box_size * end + this.box_size / 2; | |
211 y1 = this.bg[1] + this.bg_height / 2; | |
212 | |
213 this.edge(x0, y0, x1, y1, color, width); | |
214 | |
215 } | |
216 this.ctx.lineWidth = prevWidth; | |
217 | |
218 // Draw the revision node in the right column | |
219 | |
220 column = cur.vertex[0]; | |
221 color = cur.vertex[1]; | |
222 | |
223 radius = this.box_size / 8; | |
224 x = this.cell[0] + this.box_size * column + this.box_size / 2; | |
225 y = this.bg[1] - this.bg_height / 2; | |
226 this.vertex(x, y, radius, color, parity, cur); | |
227 | |
228 if (fold) this.columns -= 1; | |
229 | |
230 } | |
231 | |
232 } | |
233 | |
234 }; | |
235 | |
236 | |
237 function process_dates(parentSelector){ | |
238 | |
239 // derived from code from mercurial/templatefilter.py | |
240 | |
241 var scales = { | |
242 'year': 365 * 24 * 60 * 60, | |
243 'month': 30 * 24 * 60 * 60, | |
244 'week': 7 * 24 * 60 * 60, | |
245 'day': 24 * 60 * 60, | |
246 'hour': 60 * 60, | |
247 'minute': 60, | |
248 'second': 1 | |
249 }; | |
250 | |
251 function format(count, string){ | |
252 var ret = count + ' ' + string; | |
253 if (count > 1){ | |
254 ret = ret + 's'; | |
255 } | |
256 return ret; | |
257 } | |
258 | |
259 function shortdate(date){ | |
260 var ret = date.getFullYear() + '-'; | |
261 // getMonth() gives a 0-11 result | |
262 var month = date.getMonth() + 1; | |
263 if (month <= 9){ | |
264 ret += '0' + month; | |
265 } else { | |
266 ret += month; | |
267 } | |
268 ret += '-'; | |
269 var day = date.getDate(); | |
270 if (day <= 9){ | |
271 ret += '0' + day; | |
272 } else { | |
273 ret += day; | |
274 } | |
275 return ret; | |
276 } | |
277 | |
278 function age(datestr){ | |
279 var now = new Date(); | |
280 var once = new Date(datestr); | |
281 if (isNaN(once.getTime())){ | |
282 // parsing error | |
283 return datestr; | |
284 } | |
285 | |
286 var delta = Math.floor((now.getTime() - once.getTime()) / 1000); | |
287 | |
288 var future = false; | |
289 if (delta < 0){ | |
290 future = true; | |
291 delta = -delta; | |
292 if (delta > (30 * scales.year)){ | |
293 return "in the distant future"; | |
294 } | |
295 } | |
296 | |
297 if (delta > (2 * scales.year)){ | |
298 return shortdate(once); | |
299 } | |
300 | |
301 for (var unit in scales){ | |
302 if (!scales.hasOwnProperty(unit)) { continue; } | |
303 var s = scales[unit]; | |
304 var n = Math.floor(delta / s); | |
305 if ((n >= 2) || (s === 1)){ | |
306 if (future){ | |
307 return format(n, unit) + ' from now'; | |
308 } else { | |
309 return format(n, unit) + ' ago'; | |
310 } | |
311 } | |
312 } | |
313 } | |
314 | |
315 var nodes = document.querySelectorAll((parentSelector || '') + ' .age'); | |
316 var dateclass = new RegExp('\\bdate\\b'); | |
317 for (var i=0; i<nodes.length; ++i){ | |
318 var node = nodes[i]; | |
319 var classes = node.className; | |
320 var agevalue = age(node.textContent); | |
321 if (dateclass.test(classes)){ | |
322 // We want both: date + (age) | |
323 node.textContent += ' ('+agevalue+')'; | |
324 } else { | |
325 node.title = node.textContent; | |
326 node.textContent = agevalue; | |
327 } | |
328 } | |
329 } | |
330 | |
331 function toggleDiffstat(event) { | |
332 var curdetails = document.getElementById('diffstatdetails').style.display; | |
333 var curexpand = curdetails === 'none' ? 'inline' : 'none'; | |
334 document.getElementById('diffstatdetails').style.display = curexpand; | |
335 document.getElementById('diffstatexpand').style.display = curdetails; | |
336 event.preventDefault(); | |
337 } | |
338 | |
339 function toggleLinewrap(event) { | |
340 function getLinewrap() { | |
341 var nodes = document.getElementsByClassName('sourcelines'); | |
342 // if there are no such nodes, error is thrown here | |
343 return nodes[0].classList.contains('wrap'); | |
344 } | |
345 | |
346 function setLinewrap(enable) { | |
347 var nodes = document.getElementsByClassName('sourcelines'); | |
348 var i; | |
349 for (i = 0; i < nodes.length; i++) { | |
350 if (enable) { | |
351 nodes[i].classList.add('wrap'); | |
352 } else { | |
353 nodes[i].classList.remove('wrap'); | |
354 } | |
355 } | |
356 | |
357 var links = document.getElementsByClassName('linewraplink'); | |
358 for (i = 0; i < links.length; i++) { | |
359 links[i].innerHTML = enable ? 'on' : 'off'; | |
360 } | |
361 } | |
362 | |
363 setLinewrap(!getLinewrap()); | |
364 event.preventDefault(); | |
365 } | |
366 | |
367 function format(str, replacements) { | |
368 return str.replace(/%(\w+)%/g, function(match, p1) { | |
369 return String(replacements[p1]); | |
370 }); | |
371 } | |
372 | |
373 function makeRequest(url, method, onstart, onsuccess, onerror, oncomplete) { | |
374 var xhr = new XMLHttpRequest(); | |
375 xhr.onreadystatechange = function() { | |
376 if (xhr.readyState === 4) { | |
377 try { | |
378 if (xhr.status === 200) { | |
379 onsuccess(xhr.responseText); | |
380 } else { | |
381 throw 'server error'; | |
382 } | |
383 } catch (e) { | |
384 onerror(e); | |
385 } finally { | |
386 oncomplete(); | |
387 } | |
388 } | |
389 }; | |
390 | |
391 xhr.open(method, url); | |
392 xhr.overrideMimeType("text/xhtml; charset=" + document.characterSet.toLowerCase()); | |
393 xhr.send(); | |
394 onstart(); | |
395 return xhr; | |
396 } | |
397 | |
398 function removeByClassName(className) { | |
399 var nodes = document.getElementsByClassName(className); | |
400 while (nodes.length) { | |
401 nodes[0].parentNode.removeChild(nodes[0]); | |
402 } | |
403 } | |
404 | |
405 function docFromHTML(html) { | |
406 var doc = document.implementation.createHTMLDocument(''); | |
407 doc.documentElement.innerHTML = html; | |
408 return doc; | |
409 } | |
410 | |
411 function appendFormatHTML(element, formatStr, replacements) { | |
412 element.insertAdjacentHTML('beforeend', format(formatStr, replacements)); | |
413 } | |
414 | |
415 function adoptChildren(from, to) { | |
416 var nodes = from.children; | |
417 var curClass = 'c' + Date.now(); | |
418 while (nodes.length) { | |
419 var node = nodes[0]; | |
420 node = document.adoptNode(node); | |
421 node.classList.add(curClass); | |
422 to.appendChild(node); | |
423 } | |
424 process_dates('.' + curClass); | |
425 } | |
426 | |
427 function ajaxScrollInit(urlFormat, | |
428 nextPageVar, | |
429 nextPageVarGet, | |
430 containerSelector, | |
431 messageFormat, | |
432 mode) { | |
433 var updateInitiated = false; | |
434 var container = document.querySelector(containerSelector); | |
435 | |
436 function scrollHandler() { | |
437 if (updateInitiated) { | |
438 return; | |
439 } | |
440 | |
441 var scrollHeight = document.documentElement.scrollHeight; | |
442 var clientHeight = document.documentElement.clientHeight; | |
443 var scrollTop = document.body.scrollTop || document.documentElement.scrollTop; | |
444 | |
445 if (scrollHeight - (scrollTop + clientHeight) < 50) { | |
446 updateInitiated = true; | |
447 removeByClassName('scroll-loading-error'); | |
448 container.lastElementChild.classList.add('scroll-separator'); | |
449 | |
450 if (!nextPageVar) { | |
451 var message = { | |
452 'class': 'scroll-loading-info', | |
453 text: 'No more entries' | |
454 }; | |
455 appendFormatHTML(container, messageFormat, message); | |
456 return; | |
457 } | |
458 | |
459 makeRequest( | |
460 format(urlFormat, {next: nextPageVar}), | |
461 'GET', | |
462 function onstart() { | |
463 var message = { | |
464 'class': 'scroll-loading', | |
465 text: 'Loading...' | |
466 }; | |
467 appendFormatHTML(container, messageFormat, message); | |
468 }, | |
469 function onsuccess(htmlText) { | |
470 var doc = docFromHTML(htmlText); | |
471 | |
472 if (mode === 'graph') { | |
473 var graph = window.graph; | |
474 var dataStr = htmlText.match(/^\s*var data = (.*);$/m)[1]; | |
475 var data = JSON.parse(dataStr); | |
476 graph.reset(); | |
477 adoptChildren(doc.querySelector('#graphnodes'), container.querySelector('#graphnodes')); | |
478 graph.render(data); | |
479 } else { | |
480 adoptChildren(doc.querySelector(containerSelector), container); | |
481 } | |
482 | |
483 nextPageVar = nextPageVarGet(htmlText); | |
484 }, | |
485 function onerror(errorText) { | |
486 var message = { | |
487 'class': 'scroll-loading-error', | |
488 text: 'Error: ' + errorText | |
489 }; | |
490 appendFormatHTML(container, messageFormat, message); | |
491 }, | |
492 function oncomplete() { | |
493 removeByClassName('scroll-loading'); | |
494 updateInitiated = false; | |
495 scrollHandler(); | |
496 } | |
497 ); | |
498 } | |
499 } | |
500 | |
501 window.addEventListener('scroll', scrollHandler); | |
502 window.addEventListener('resize', scrollHandler); | |
503 scrollHandler(); | |
504 } | |
505 | |
506 function renderDiffOptsForm() { | |
507 // We use URLSearchParams for query string manipulation. Old browsers don't | |
508 // support this API. | |
509 if (!("URLSearchParams" in window)) { | |
510 return; | |
511 } | |
512 | |
513 var form = document.getElementById("diffopts-form"); | |
514 | |
515 var KEYS = [ | |
516 "ignorews", | |
517 "ignorewsamount", | |
518 "ignorewseol", | |
519 "ignoreblanklines", | |
520 ]; | |
521 | |
522 var urlParams = new window.URLSearchParams(window.location.search); | |
523 | |
524 function updateAndRefresh(e) { | |
525 var checkbox = e.target; | |
526 var name = checkbox.id.substr(0, checkbox.id.indexOf("-")); | |
527 urlParams.set(name, checkbox.checked ? "1" : "0"); | |
528 window.location.search = urlParams.toString(); | |
529 } | |
530 | |
531 var allChecked = form.getAttribute("data-ignorews") === "1"; | |
532 | |
533 for (var i = 0; i < KEYS.length; i++) { | |
534 var key = KEYS[i]; | |
535 | |
536 var checkbox = document.getElementById(key + "-checkbox"); | |
537 if (!checkbox) { | |
538 continue; | |
539 } | |
540 | |
541 var currentValue = form.getAttribute("data-" + key); | |
542 checkbox.checked = currentValue !== "0"; | |
543 | |
544 // ignorews implies ignorewsamount and ignorewseol. | |
545 if (allChecked && (key === "ignorewsamount" || key === "ignorewseol")) { | |
546 checkbox.checked = true; | |
547 checkbox.disabled = true; | |
548 } | |
549 | |
550 checkbox.addEventListener("change", updateAndRefresh, false); | |
551 } | |
552 | |
553 form.style.display = 'block'; | |
554 } | |
555 | |
556 function addDiffStatToggle() { | |
557 var els = document.getElementsByClassName("diffstattoggle"); | |
558 | |
559 for (var i = 0; i < els.length; i++) { | |
560 els[i].addEventListener("click", toggleDiffstat, false); | |
561 } | |
562 } | |
563 | |
564 function addLineWrapToggle() { | |
565 var els = document.getElementsByClassName("linewraptoggle"); | |
566 | |
567 for (var i = 0; i < els.length; i++) { | |
568 var nodes = els[i].getElementsByClassName("linewraplink"); | |
569 | |
570 for (var j = 0; j < nodes.length; j++) { | |
571 nodes[j].addEventListener("click", toggleLinewrap, false); | |
572 } | |
573 } | |
574 } | |
575 | |
576 document.addEventListener('DOMContentLoaded', function() { | |
577 process_dates(); | |
578 addDiffStatToggle(); | |
579 addLineWrapToggle(); | |
580 }, false); |