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