@@ -107,9 +107,12 @@ const pageTemplate = `<!doctype html> |
| 107 |
<img class="avatar commit-avatar" src="{{.AvatarURL}}" alt="" loading="lazy" referrerpolicy="no-referrer"> |
107 |
<img class="avatar commit-avatar" src="{{.AvatarURL}}" alt="" loading="lazy" referrerpolicy="no-referrer"> |
| 108 |
<div class="commit-list-body"> |
108 |
<div class="commit-list-body"> |
| 109 |
<a class="commit-list-message" href="{{.URL}}">{{.Message}}</a> |
109 |
<a class="commit-list-message" href="{{.URL}}">{{.Message}}</a> |
| 110 |
<span><strong>{{.Author}}</strong> committed on <time datetime="{{.DateTime}}">{{.Date}}</time></span> |
110 |
<span><strong>{{.Author}}</strong> committed on <time datetime="{{.DateTime}}" data-smart-time>{{.Date}}</time></span> |
|
|
111 |
</div> |
|
|
112 |
<div class="commit-list-actions"> |
|
|
113 |
<a class="commit-sha" href="{{.URL}}">{{.ShortOID}}</a> |
|
|
114 |
<button class="secondary-button commit-list-copy" type="button" data-copy="{{.OID}}" aria-label="Copy commit ID" title="Copy full commit ID"><span data-copy-icon>{{icon "copy"}}</span><span data-copied-icon hidden>{{icon "check"}}</span><span class="sr-only" data-copy-label>Copy commit ID</span></button> |
| 111 |
</div> |
115 |
</div> |
| 112 |
<a class="commit-sha" href="{{.URL}}">{{.ShortOID}}</a> |
|
|
| 113 |
</article>{{end}} |
116 |
</article>{{end}} |
| 114 |
</div> |
117 |
</div> |
| 115 |
</section>{{end}} |
118 |
</section>{{end}} |
@@ -119,7 +122,7 @@ const pageTemplate = `<!doctype html> |
| 119 |
<div class="commit-page-heading"><h1>Commit <code>{{.ViewedCommit.Commit.ShortOID}}</code></h1></div> |
122 |
<div class="commit-page-heading"><h1>Commit <code>{{.ViewedCommit.Commit.ShortOID}}</code></h1></div> |
| 120 |
<div class="commit-author-row"> |
123 |
<div class="commit-author-row"> |
| 121 |
<img class="avatar commit-avatar" src="{{.ViewedCommit.Commit.AvatarURL}}" alt="" referrerpolicy="no-referrer"> |
124 |
<img class="avatar commit-avatar" src="{{.ViewedCommit.Commit.AvatarURL}}" alt="" referrerpolicy="no-referrer"> |
| 122 |
<span><strong>{{.ViewedCommit.Commit.Author}}</strong> committed on <time datetime="{{.ViewedCommit.Commit.DateTime}}">{{.ViewedCommit.Commit.Date}}</time></span> |
125 |
<span><strong>{{.ViewedCommit.Commit.Author}}</strong> committed on <time datetime="{{.ViewedCommit.Commit.DateTime}}" data-smart-time>{{.ViewedCommit.Commit.Date}}</time></span> |
| 123 |
</div> |
126 |
</div> |
| 124 |
<article class="commit-summary-card"> |
127 |
<article class="commit-summary-card"> |
| 125 |
<div class="commit-summary-title"> |
128 |
<div class="commit-summary-title"> |
@@ -142,10 +145,11 @@ const pageTemplate = `<!doctype html> |
| 142 |
<div class="diff-limit-notice"><strong>Diff too large to render</strong><p>This commit's patch exceeds the 8 MB static-page limit. Clone the repository to inspect the complete change.</p></div> |
145 |
<div class="diff-limit-notice"><strong>Diff too large to render</strong><p>This commit's patch exceeds the 8 MB static-page limit. Clone the repository to inspect the complete change.</p></div> |
| 143 |
{{else if .ViewedCommit.Files}} |
146 |
{{else if .ViewedCommit.Files}} |
| 144 |
<div class="diff-layout"> |
147 |
<div class="diff-layout"> |
| 145 |
<aside class="diff-sidebar"> |
148 |
<aside class="file-browser diff-sidebar" aria-label="Changed files"> |
| 146 |
<h2>Files changed</h2> |
149 |
<div class="file-browser-heading">{{icon "folder"}}<strong>Files changed</strong></div> |
| 147 |
<nav class="diff-file-nav" aria-label="Changed files"> |
150 |
<label class="file-tree-search">{{icon "search"}}<input type="search" placeholder="Search changed files" autocomplete="off" data-diff-file-tree-query></label> |
| 148 |
{{range .ViewedCommit.Files}}<a href="#{{.Anchor}}"><span>{{.Status}}</span>{{.DisplayPath}}</a>{{end}} |
151 |
<nav class="file-tree" data-diff-file-tree> |
|
|
152 |
{{range .ViewedCommit.Files}}<a class="file-tree-file" href="#{{.Anchor}}" data-diff-file data-path="{{.TreePath}}">{{icon "file"}}<span>{{.DisplayPath}}</span></a>{{end}} |
| 149 |
</nav> |
153 |
</nav> |
| 150 |
</aside> |
154 |
</aside> |
| 151 |
<div class="diff-files"> |
155 |
<div class="diff-files"> |
@@ -260,6 +264,34 @@ const pageTemplate = `<!doctype html> |
| 260 |
button.addEventListener('click', function () { copyText(button.getAttribute('data-copy'), button); }); |
264 |
button.addEventListener('click', function () { copyText(button.getAttribute('data-copy'), button); }); |
| 261 |
}); |
265 |
}); |
| 262 |
|
266 |
|
|
|
267 |
var relativeFormatter = window.Intl && Intl.RelativeTimeFormat ? new Intl.RelativeTimeFormat(undefined, {numeric: 'always'}) : null; |
|
|
268 |
function updateRelativeTimes() { |
|
|
269 |
if (!relativeFormatter) return; |
|
|
270 |
var now = Date.now(); |
|
|
271 |
all('[data-relative-time], [data-smart-time]').forEach(function (node) { |
|
|
272 |
var date = new Date(node.getAttribute('datetime')); |
|
|
273 |
if (isNaN(date.getTime())) return; |
|
|
274 |
if (!node.dataset.absoluteLabel) node.dataset.absoluteLabel = node.textContent; |
|
|
275 |
var seconds = (date.getTime() - now) / 1000; |
|
|
276 |
var absolute = Math.abs(seconds); |
|
|
277 |
if (node.hasAttribute('data-smart-time') && absolute >= 604800) { |
|
|
278 |
node.textContent = node.dataset.absoluteLabel; |
|
|
279 |
return; |
|
|
280 |
} |
|
|
281 |
var divisor = 1; |
|
|
282 |
var unit = 'second'; |
|
|
283 |
if (absolute >= 31536000) { divisor = 31536000; unit = 'year'; } |
|
|
284 |
else if (absolute >= 2592000) { divisor = 2592000; unit = 'month'; } |
|
|
285 |
else if (absolute >= 86400) { divisor = 86400; unit = 'day'; } |
|
|
286 |
else if (absolute >= 3600) { divisor = 3600; unit = 'hour'; } |
|
|
287 |
else if (absolute >= 60) { divisor = 60; unit = 'minute'; } |
|
|
288 |
node.textContent = relativeFormatter.format(Math.round(seconds / divisor), unit); |
|
|
289 |
if (!node.title) node.title = date.toLocaleString(); |
|
|
290 |
}); |
|
|
291 |
} |
|
|
292 |
updateRelativeTimes(); |
|
|
293 |
if (relativeFormatter) window.setInterval(updateRelativeTimes, 60000); |
|
|
294 |
|
| 263 |
function closeMenu(menu) { |
295 |
function closeMenu(menu) { |
| 264 |
if (!menu) return; |
296 |
if (!menu) return; |
| 265 |
var panel = one('[data-popover]', menu); |
297 |
var panel = one('[data-popover]', menu); |
@@ -399,8 +431,6 @@ const pageTemplate = `<!doctype html> |
| 399 |
if (event.key === 'Enter') { var first = one('.file-result', fileResults); if (first) window.location.href = first.href; } |
431 |
if (event.key === 'Enter') { var first = one('.file-result', fileResults); if (first) window.location.href = first.href; } |
| 400 |
}); |
432 |
}); |
| 401 |
|
433 |
|
| 402 |
var fileTree = one('[data-file-tree]'); |
|
|
| 403 |
var fileTreeQuery = one('[data-file-tree-query]'); |
|
|
| 404 |
function addTreeIcon(target, selector) { |
434 |
function addTreeIcon(target, selector) { |
| 405 |
var source = one(selector); |
435 |
var source = one(selector); |
| 406 |
if (source) target.appendChild(source.content.cloneNode(true)); |
436 |
if (source) target.appendChild(source.content.cloneNode(true)); |
@@ -414,13 +444,13 @@ const pageTemplate = `<!doctype html> |
| 414 |
text.textContent = label; |
444 |
text.textContent = label; |
| 415 |
if (compact) text.title = file.path; |
445 |
if (compact) text.title = file.path; |
| 416 |
link.appendChild(text); |
446 |
link.appendChild(text); |
| 417 |
if (file.path === body.dataset.routePath) { |
447 |
if (file.path === body.dataset.routePath || (file.url.charAt(0) === '#' && file.url === window.location.hash)) { |
| 418 |
link.classList.add('selected'); |
448 |
link.classList.add('selected'); |
| 419 |
link.setAttribute('aria-current', 'page'); |
449 |
link.setAttribute('aria-current', 'page'); |
| 420 |
} |
450 |
} |
| 421 |
return link; |
451 |
return link; |
| 422 |
} |
452 |
} |
| 423 |
function renderTreeNode(node, container, prefix) { |
453 |
function renderTreeNode(node, container, prefix, expandAll) { |
| 424 |
Object.keys(node.folders).sort().forEach(function (name) { |
454 |
Object.keys(node.folders).sort().forEach(function (name) { |
| 425 |
var child = node.folders[name]; |
455 |
var child = node.folders[name]; |
| 426 |
var childPath = prefix ? prefix + '/' + name : name; |
456 |
var childPath = prefix ? prefix + '/' + name : name; |
@@ -439,7 +469,7 @@ const pageTemplate = `<!doctype html> |
| 439 |
toggle.appendChild(label); |
469 |
toggle.appendChild(label); |
| 440 |
var children = document.createElement('div'); |
470 |
var children = document.createElement('div'); |
| 441 |
children.className = 'file-tree-children'; |
471 |
children.className = 'file-tree-children'; |
| 442 |
var open = body.dataset.routePath === childPath || body.dataset.routePath.indexOf(childPath + '/') === 0; |
472 |
var open = expandAll || body.dataset.routePath === childPath || body.dataset.routePath.indexOf(childPath + '/') === 0; |
| 443 |
children.hidden = !open; |
473 |
children.hidden = !open; |
| 444 |
caret.classList.toggle('open', open); |
474 |
caret.classList.toggle('open', open); |
| 445 |
toggle.setAttribute('aria-expanded', open ? 'true' : 'false'); |
475 |
toggle.setAttribute('aria-expanded', open ? 'true' : 'false'); |
@@ -452,24 +482,24 @@ const pageTemplate = `<!doctype html> |
| 452 |
wrapper.appendChild(toggle); |
482 |
wrapper.appendChild(toggle); |
| 453 |
wrapper.appendChild(children); |
483 |
wrapper.appendChild(children); |
| 454 |
container.appendChild(wrapper); |
484 |
container.appendChild(wrapper); |
| 455 |
renderTreeNode(child, children, childPath); |
485 |
renderTreeNode(child, children, childPath, expandAll); |
| 456 |
}); |
486 |
}); |
| 457 |
node.files.sort(function (a, b) { return a.name.localeCompare(b.name); }).forEach(function (entry) { |
487 |
node.files.sort(function (a, b) { return a.name.localeCompare(b.name); }).forEach(function (entry) { |
| 458 |
container.appendChild(fileTreeLink(entry.file, entry.name, false)); |
488 |
container.appendChild(fileTreeLink(entry.file, entry.name, false)); |
| 459 |
}); |
489 |
}); |
| 460 |
} |
490 |
} |
| 461 |
function renderFileTree() { |
491 |
function renderFileTree(target, queryInput, treeFiles, expandAll) { |
| 462 |
if (!fileTree || !files) return; |
492 |
if (!target || !treeFiles) return; |
| 463 |
var query = fileTreeQuery.value.trim().toLowerCase(); |
493 |
var query = queryInput ? queryInput.value.trim().toLowerCase() : ''; |
| 464 |
fileTree.textContent = ''; |
494 |
target.textContent = ''; |
| 465 |
if (query) { |
495 |
if (query) { |
| 466 |
var matches = files.filter(function (file) { return file.path.toLowerCase().indexOf(query) !== -1; }); |
496 |
var matches = treeFiles.filter(function (file) { return file.path.toLowerCase().indexOf(query) !== -1; }); |
| 467 |
matches.slice(0, 200).forEach(function (file) { fileTree.appendChild(fileTreeLink(file, file.path, true)); }); |
497 |
matches.slice(0, 200).forEach(function (file) { target.appendChild(fileTreeLink(file, file.path, true)); }); |
| 468 |
if (!matches.length) fileTree.innerHTML = '<p class="menu-status">No matching files.</p>'; |
498 |
if (!matches.length) target.innerHTML = '<p class="menu-status">No matching files.</p>'; |
| 469 |
return; |
499 |
return; |
| 470 |
} |
500 |
} |
| 471 |
var root = {folders: {}, files: []}; |
501 |
var root = {folders: {}, files: []}; |
| 472 |
files.forEach(function (file) { |
502 |
treeFiles.forEach(function (file) { |
| 473 |
var parts = file.path.split('/'); |
503 |
var parts = file.path.split('/'); |
| 474 |
var node = root; |
504 |
var node = root; |
| 475 |
parts.slice(0, -1).forEach(function (part) { |
505 |
parts.slice(0, -1).forEach(function (part) { |
@@ -478,13 +508,35 @@ const pageTemplate = `<!doctype html> |
| 478 |
}); |
508 |
}); |
| 479 |
node.files.push({name: parts[parts.length - 1], file: file}); |
509 |
node.files.push({name: parts[parts.length - 1], file: file}); |
| 480 |
}); |
510 |
}); |
| 481 |
renderTreeNode(root, fileTree, ''); |
511 |
renderTreeNode(root, target, '', expandAll); |
| 482 |
var selected = one('.file-tree-file.selected', fileTree); |
512 |
var selected = one('.file-tree-file.selected', target); |
| 483 |
if (selected) fileTree.scrollTop = Math.max(0, selected.offsetTop - (fileTree.clientHeight - selected.offsetHeight) / 2); |
513 |
if (selected) target.scrollTop = Math.max(0, selected.offsetTop - (target.clientHeight - selected.offsetHeight) / 2); |
| 484 |
} |
514 |
} |
|
|
515 |
|
|
|
516 |
var fileTree = one('[data-file-tree]'); |
|
|
517 |
var fileTreeQuery = one('[data-file-tree-query]'); |
| 485 |
if (fileTree) { |
518 |
if (fileTree) { |
| 486 |
loadFiles().then(renderFileTree).catch(function () { fileTree.innerHTML = '<p class="menu-status">Could not load files.</p>'; }); |
519 |
loadFiles().then(function (loadedFiles) { renderFileTree(fileTree, fileTreeQuery, loadedFiles, false); }) |
| 487 |
fileTreeQuery.addEventListener('input', renderFileTree); |
520 |
.catch(function () { fileTree.innerHTML = '<p class="menu-status">Could not load files.</p>'; }); |
|
|
521 |
fileTreeQuery.addEventListener('input', function () { renderFileTree(fileTree, fileTreeQuery, files, false); }); |
|
|
522 |
} |
|
|
523 |
|
|
|
524 |
var diffFileTree = one('[data-diff-file-tree]'); |
|
|
525 |
var diffFileTreeQuery = one('[data-diff-file-tree-query]'); |
|
|
526 |
if (diffFileTree) { |
|
|
527 |
var diffFiles = all('[data-diff-file]', diffFileTree).map(function (link) { |
|
|
528 |
return {path: link.getAttribute('data-path'), url: link.getAttribute('href')}; |
|
|
529 |
}); |
|
|
530 |
renderFileTree(diffFileTree, diffFileTreeQuery, diffFiles, true); |
|
|
531 |
diffFileTreeQuery.addEventListener('input', function () { renderFileTree(diffFileTree, diffFileTreeQuery, diffFiles, true); }); |
|
|
532 |
window.addEventListener('hashchange', function () { |
|
|
533 |
all('.file-tree-file', diffFileTree).forEach(function (link) { |
|
|
534 |
var selected = link.getAttribute('href') === window.location.hash; |
|
|
535 |
link.classList.toggle('selected', selected); |
|
|
536 |
if (selected) link.setAttribute('aria-current', 'page'); |
|
|
537 |
else link.removeAttribute('aria-current'); |
|
|
538 |
}); |
|
|
539 |
}); |
| 488 |
} |
540 |
} |
| 489 |
|
541 |
|
| 490 |
document.addEventListener('keydown', function (event) { |
542 |
document.addEventListener('keydown', function (event) { |
@@ -530,7 +582,7 @@ const pageTemplate = `<!doctype html> |
| 530 |
<img class="avatar commit-avatar" src="{{.Commit.AvatarURL}}" alt="" referrerpolicy="no-referrer"> |
582 |
<img class="avatar commit-avatar" src="{{.Commit.AvatarURL}}" alt="" referrerpolicy="no-referrer"> |
| 531 |
<div class="latest-commit"><strong>{{.Commit.Author}}</strong><a class="commit-message" href="{{.Commit.URL}}">{{.Commit.Message}}</a></div> |
583 |
<div class="latest-commit"><strong>{{.Commit.Author}}</strong><a class="commit-message" href="{{.Commit.URL}}">{{.Commit.Message}}</a></div> |
| 532 |
<a class="commit-hash" href="{{.Commit.URL}}">{{.Commit.ShortOID}}</a> |
584 |
<a class="commit-hash" href="{{.Commit.URL}}">{{.Commit.ShortOID}}</a> |
| 533 |
<time datetime="{{.Commit.DateTime}}">{{.Commit.Date}}</time> |
585 |
<time datetime="{{.Commit.DateTime}}" data-smart-time>{{.Commit.Date}}</time> |
| 534 |
<a class="commit-count-link" href="{{.CommitsURL}}">{{icon "history"}}<strong>{{.CommitCount}}</strong> commits</a> |
586 |
<a class="commit-count-link" href="{{.CommitsURL}}">{{icon "history"}}<strong>{{.CommitCount}}</strong> commits</a> |
| 535 |
</div> |
587 |
</div> |
| 536 |
<div class="tree-rows"> |
588 |
<div class="tree-rows"> |
@@ -539,7 +591,7 @@ const pageTemplate = `<!doctype html> |
| 539 |
{{if eq .Type "tree"}}<span class="file-icon folder">{{icon "folder"}}</span>{{else if eq .Type "commit"}}<span class="file-icon">{{icon "repo"}}</span>{{else}}<span class="file-icon">{{icon "file"}}</span>{{end}} |
591 |
{{if eq .Type "tree"}}<span class="file-icon folder">{{icon "folder"}}</span>{{else if eq .Type "commit"}}<span class="file-icon">{{icon "repo"}}</span>{{else}}<span class="file-icon">{{icon "file"}}</span>{{end}} |
| 540 |
<a class="file-name" href="{{.URL}}">{{.Name}}</a> |
592 |
<a class="file-name" href="{{.URL}}">{{.Name}}</a> |
| 541 |
<a class="row-message" href="{{.CommitURL}}" title="{{.CommitMessage}}">{{.CommitMessage}}</a> |
593 |
<a class="row-message" href="{{.CommitURL}}" title="{{.CommitMessage}}">{{.CommitMessage}}</a> |
| 542 |
<span class="file-size">{{humanSize .Size}}</span> |
594 |
<time class="file-date" datetime="{{.CommitDateTime}}" data-relative-time>{{.CommitDate}}</time> |
| 543 |
</div>{{end}} |
595 |
</div>{{end}} |
| 544 |
</div> |
596 |
</div> |
| 545 |
</div> |
597 |
</div> |