Tucker McKnight <tucker@pangolin.lan> | Sat Dec 27 2025
Change patch.njk to commit.ts, use new template Adds a "Copy" button for copying the hash of the commit. Made the "Commits" nav item get underlined when it's the active navTab. Some style tweaks, added a looping template for bezel-color classes. (The bezel-header class is a special case because it changes colors between light and dark mode -- not sure what to do about that one yet.)
6 7 8 9 10 11
const copyCommand = (event) => {
const elem = event.target
const cloneText = elem.dataset.cloneUrl
const originalInnerText = elem.innerText
navigator.clipboard.writeText(cloneText).then(() => {
elem.innerText = "Copied"6 7 8 9 10 11
const copyCommand = (event) => {
const elem = event.target
const cloneText = elem.dataset.copyText
const originalInnerText = elem.innerText
navigator.clipboard.writeText(cloneText).then(() => {
elem.innerText = "Copied"24 25 26 27 28 29 30 31
<label class='form-label'>HTTPS URL</label>
<div class='input-group d-flex flex-nowrap'>
<span class='clone overflow-hidden input-group-text'>${window.cloneUrl}</span>
<button data-clone-url='${window.cloneUrl}' class='btn btn-primary shadow-none text-white' id='clone-button'>Copy</button>
</div>`
const popoverBtn = document.getElementById("clone-popover-btn")
const bsPopover = new bootstrap.Popover(popoverBtn, {
sanitize: false,24 25 26 27 28 29 30 31 32 33
<label class='form-label'>HTTPS URL</label>
<div class='input-group d-flex flex-nowrap'>
<span class='clone overflow-hidden input-group-text'>${window.cloneUrl}</span>
<button data-copy-text='${window.cloneUrl}' class='btn btn-primary shadow-none text-white' id='copy-button'>Copy</button>
</div>`
div.querySelector("#copy-button").addEventListener('click', copyCommand)
const popoverBtn = document.getElementById("clone-popover-btn")
const bsPopover = new bootstrap.Popover(popoverBtn, {
sanitize: false,37 38 39 40 41 42 43
container: 'body',
})
div.querySelector("#clone-button").addEventListener('click', copyCommand)
document.body.addEventListener('click', (event) => {
const target = event.target
// If they didn't click the #clone-popover-btn or if we're not inside of37 38 39 40 41
container: 'body',
})
document.body.addEventListener('click', (event) => {
const target = event.target
// If they didn't click the #clone-popover-btn or if we're not inside of102 103
if (document.getElementById('clone-popover-btn')) {
createClonePopover()
}102 103 104 105
if (document.getElementById('clone-popover-btn')) {
createClonePopover()
}
document.querySelector("#copy-button").addEventListener('click', copyCommand)
-1 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
import {NavHelper} from './helpers/nav.ts'
export default async (eleventyConfig: any, data: any, nav: ReturnType<typeof NavHelper>) => {
const date = eleventyConfig.getFilter("date")
const slugify = eleventyConfig.getFilter("slugify")
const lineNumbers = eleventyConfig.getFilter("lineNumbers")
const languageExtension = eleventyConfig.getFilter("languageExtension")
return `
<div class="row">
<div class="col-auto">
<div class="bezel-secondary px-3 py-2">
<h1>${data.patchInfo.commit.message.split('\n')[0]}</h2>
<div class="input-group mb-2">
<span class="font-monospace input-group-text border-info text-white text-bg-dark">${data.patchInfo.commit.hash}</span>
<button data-copy-text='${data.patchInfo.commit.hash}' class="btn btn-info shadow-none" id="copy-button">Copy</button>
</div>
<p>${date(data.patchInfo.commit.date)}</p>
<p>${data.patchInfo.commit.author}</p>
<pre>${data.patchInfo.commit.message}</pre>
</div>
</div>
</div>
<div class="row my-4">
<div class="col-auto">
<div class="form-check">
<input class="form-check-input" type="radio" name="unified" id="splitModeSwitch" value="split" onchange="toggleUnifiedMode(this)" checked>
<label class="form-check-label" for="splitModeSwitch">
Side-by-side
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="unified" id="unifiedModeSwitch" value="unified" onchange="toggleUnifiedMode(this)">
<label class="form-check-label" for="unifiedModeSwitch">
Stacked
</label>
</div>
</div>
</div>
<div class="row" id="diffs">
${data.patchInfo.commit.diffs.map((hunk) => {
return `
<div class=hunk>
<span class="font-monospace fw-bold"><a href="${data.reposPath}/${slugify(data.patchInfo.repoName)}/branches/${slugify(data.patchInfo.branchName)}/files/${slugify(hunk.fileName)}.html">${hunk.fileName}:${hunk.lineNumber}</a></span>
<div class="diff d-flex">
<div class="flex-grow-1 diff-left pe-2">
<span class='font-monospace text-secondary'>Before</span>
<div class="row">
<div class="col-auto border-end"><pre>
${lineNumbers(hunk.beforeText).map((lineNumber) => {
return (lineNumber + hunk.lineNumber - 1).toString()
}).join('\n')}</pre></div>
<div class="col overflow-scroll">
<pre data-start="${hunk.lineNumber}"><code data-type="before" class="line-numbers language-${languageExtension(hunk.fileName, data.patchInfo.repoName)}">${hunk.beforeText}</code></pre>
</div>
</div>
</div>
<div class="diff-right flex-grow-1">
<span class='font-monospace text-secondary'>After</span>
<div class="row">
<div class="col-auto border-end"><pre>
${lineNumbers(hunk.beforeText).map((lineNumber) => {
return (lineNumber + hunk.lineNumber - 1).toString()
}).join('\n')}</pre></div>
<div class="col overflow-scroll">
<pre data-start="${hunk.lineNumber}"><code data-type="after" class="line-numbers language-${languageExtension(hunk.fileName, data.patchInfo.repoName)}">${hunk.afterText}</code></pre>
</div>
</div>
</div>
</div>
</div>
`
}).join('')}
</div>
<script>
const toggleUnifiedMode = (e) => {
const diffs = document.getElementById('diffs')
const afterDiffs = document.querySelectorAll('.diff-right')
if (e.value == "unified") {
diffs.classList.add("unified")
}
else {
diffs.classList.remove("unified")
}
}
</script>
`
}117 118 119 120 121 122
<a class="nav-link ${data.navTab === 'files' ? 'active' : ''}" href="${nav.repoCurrentBranchFiles()}">Files</a>
</li>
<li class="nav-item">
<a class="nav-link" href="${nav.repoCurrentBranchCommits()}">Commits</a>
</li>
<li class="nav-item">
<a class="nav-link" href="${nav.repoCurrentBranchBranches()}">Branches</a>117 118 119 120 121 122
<a class="nav-link ${data.navTab === 'files' ? 'active' : ''}" href="${nav.repoCurrentBranchFiles()}">Files</a>
</li>
<li class="nav-item">
<a class="nav-link ${data.navTab === 'commits' ? 'active' : ''}" href="${nav.repoCurrentBranchCommits()}">Commits</a>
</li>
<li class="nav-item">
<a class="nav-link" href="${nav.repoCurrentBranchBranches()}">Branches</a>38 39 40 41 42 43
return `
<div class="row">
<div class="col">
<div class="px-4 pt-3 bezel">
<div class="row">
<div class="col-12 col-xl-6">
<h1 class="display-2 text-white"><em>${repo.name}</em></h1>38 39 40 41 42 43
return `
<div class="row">
<div class="col">
<div class="px-4 pt-3 bezel-header">
<div class="row">
<div class="col-12 col-xl-6">
<h1 class="display-2 text-white"><em>${repo.name}</em></h1>15 16 17 18 19
import repoJsTemplate from './js_templates/repo.ts'
import filesJsTemplate from './js_templates/files.ts'
import fileJsTemplate from './js_templates/file.ts'
const ajv = new Ajv()
const exec = util.promisify(childProcess.exec)15 16 17 18 19 20
import repoJsTemplate from './js_templates/repo.ts'
import filesJsTemplate from './js_templates/files.ts'
import fileJsTemplate from './js_templates/file.ts'
import commitJsTemplate from './js_templates/commit.ts'
const ajv = new Ajv()
const exec = util.promisify(childProcess.exec)419 420 421 422 423 424 425
}
)
// PATCHES.NJK
const patchesTemplate = fsImport.readFileSync(`${import.meta.dirname}/templates/patches.njk`).toString()
const paginatedPatchesData = await paginatedPatches(reposData)419 420 421 422 423
}
)
// PATCHES.NJK
const patchesTemplate = fsImport.readFileSync(`${import.meta.dirname}/templates/patches.njk`).toString()
const paginatedPatchesData = await paginatedPatches(reposData)458 459 460 461 462 463 464 465 466 467 468
}
)
// PATCH.NJK
const patchTemplate = fsImport.readFileSync(`${import.meta.dirname}/templates/patch.njk`).toString()
const flatPatchesData = await flatPatches(reposData)
eleventyConfig.addTemplate(
`repos/patch.njk`,
topLayoutPartial + patchTemplate + bottomLayoutPartial,
{
pagination: {
data: "flatPatches",458 459 460 461 462 463 464 465 466 467
}
)
// COMMIT.TS
const flatPatchesData = await flatPatches(reposData)
eleventyConfig.addTemplate(
`repos/commit.11ty.js`,
htmlPage(reposConfiguration, eleventyConfig, commitJsTemplate),
{
pagination: {
data: "flatPatches",491 492 493 494 495 496 497
return branch.name === data.patchInfo.branchName
}),
},
width: "full",
navTab: "patches",
}
)
491 492 493 494 495 496
return branch.name === data.patchInfo.branchName
}),
},
navTab: "commits",
}
)
35 36 37 38 39 40
--bs-list-group-border-color: #BAC1CA;
}
.bezel {
background-color: $lightblue;
border-top: 6px solid color.adjust($lightblue, $lightness: 15%);
border-left: 6px solid color.adjust($lightblue, $lightness: 13%);35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
--bs-list-group-border-color: #BAC1CA;
}
$bezel-colors: ("primary": $primary, "secondary": $secondary, "info": $info, "warning": $warning);
@each $color, $value in $bezel-colors {
.bezel-#{$color} {
background-color: $value;
border-top: 6px solid color.adjust($value, $lightness: 15%);
border-left: 6px solid color.adjust($value, $lightness: 13%);
border-bottom: 6px solid color.adjust($value, $lightness: -30%);
border-right: 6px solid color.adjust($value, $lightness: -40%);
}
}
@each $color, $value in map-remove($bezel-colors, "info") {
.bezel-#{$color} {
color: $body-color-dark;
}
}
.bezel-header {
background-color: $lightblue;
border-top: 6px solid color.adjust($lightblue, $lightness: 15%);
border-left: 6px solid color.adjust($lightblue, $lightness: 13%);46 47 48 49 50 51
@include color-mode(dark) {
$blue: #354BC6;
.bezel {
background-color: $blue;
border-top: 6px solid color.adjust($blue, $lightness: 10%);
border-left: 6px solid color.adjust($blue, $lightness: 7%);46 47 48 49 50 51
@include color-mode(dark) {
$blue: #354BC6;
.bezel-header {
background-color: $blue;
border-top: 6px solid color.adjust($blue, $lightness: 10%);
border-left: 6px solid color.adjust($blue, $lightness: 7%);237 238
margin: 10px 10px;
}
}237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264
margin: 10px 10px;
}
}
/* Commit page */
.hunk {
box-sizing: border-box;
margin-bottom: 25px;
}
.unified .diff {
flex-wrap: wrap;
}
.diff-left, .diff-right {
flex: 1;
overflow: hidden;
}
.unified .diff-left, .unified .diff-right {
flex-basis: 100%;
}
[data-type=before] mark {
background-color: rgba(252, 78, 78, .15);
}
[data-type=after] mark, .token.inserted {
background-color: rgba(170, 227, 203, .35);
}
[data-bs-theme=dark] [data-type=after] mark, [data-bs-theme=dark] .token.inserted {
background-color: rgba(51, 247, 160, .1); /* different green for dark mode; looks better */
}0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
<div class="container-lg">
<div class="row">
<div class="col-auto">
<h1>commit first line goes here</h2>
<p>{{patchInfo.commit.date | date }}</p>
<p>{{patchInfo.commit.author }}</p>
<pre>{{patchInfo.commit.message}}</pre>
</div>
</div>
<div class="row">
<div class="col-auto">
<p class="font-monospace fw-bold text-secondary">{{patchInfo.commit.hash}}</p>
</div>
</div>
</div>
<div class="container-fluid border-top">
<div class="row my-2">
<div class="col-auto d-flex align-items-center fs-4">
<i class="bi bi-layout-split"></i><span class="fs-6 mx-1">Side-by-side</span>
<div class="form-check form-switch mb-1 px-2">
<input class="form-check-input mx-0" type="checkbox" id="unifiedModeSwitch" value="unified" onchange="toggleUnifiedMode(this)" aria-label="Toggle switch for stacked or side by side diff view">
</div>
<i class="bi bi-hr"></i><span class="fs-6 mx-1">Stacked</span>
</div>
</div>
<div class="row" id="diffs">
{% set patchHunks = patchInfo.commit.diffs %}
{% for hunk in patchHunks %}
<div class=hunk>
<span class="font-monospace fw-bold"><a href="{{reposPath}}/{{patchInfo.repoName | slugify}}/branches/{{patchInfo.branchName | slugify}}/files/{{ hunk.fileName | slugify}}.html">{{ hunk.fileName }}:{{ hunk.lineNumber }}</a></span>
<div class="diff d-flex">
<div class="flex-grow-1 diff-left pe-2">
<span class='font-monospace text-secondary'>Before</span>
<div class="row">
<div class="col-auto border-end">
<code>
{%- for lineNumber in hunk.beforeText | lineNumbers -%}
{{ lineNumber + hunk.lineNumber - 1}}
{% endfor -%}
</code>
</div>
<div class="col overflow-scroll">
<pre data-start="{{hunk.lineNumber}}"><code data-type="before" class="line-numbers language-{{hunk.fileName | languageExtension(patchInfo.repoName)}}">{{hunk.beforeText | safe}}</code></pre>
</div>
</div>
</div>
<div class="diff-right flex-grow-1 ps-2 border-start">
<span class='font-monospace text-secondary'>After</span>
<div class="row">
<div class="col-auto border-end">
<code>
{%- for lineNumber in hunk.afterText | lineNumbers -%}
{{ lineNumber + hunk.lineNumber - 1}}
{% endfor -%}
</code>
</div>
<div class="col overflow-scroll">
<pre data-start="{{hunk.lineNumber}}"><code data-type="after" class="line-numbers language-{{hunk.fileName | languageExtension(patchInfo.repoName)}}">{{ hunk.afterText | safe}}</code></pre>
</div>
</div>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
<script>
const copyCommand = () => {
const text = document.getElementById("clone-command").innerText
const button = document.getElementById("clone-button")
navigator.clipboard.writeText(text).then(() => {
button.innerText = "Copied"
})
}
</script>
1 2 3 4 5 6
<ul class="pagination">
{% for pageObj in (paginatedPatches | pagesJustForBranch(patchPage.repoName, patchPage.branchName)) %}
<li class="page-item">
<a class="page-link {% if pageObj.pageNumber == patchPage.pageNumber %}active{% endif %}" href="{{reposPath}}/{{patchPage.repoName | slugify}}/branches/{{patchPage.branchName}}/patches/page{{pageObj.pageNumber}}">{{ pageObj.pageNumber }}</a>
</li>
{% endfor %}
</ul>1 2 3 4 5 6
<ul class="pagination">
{% for pageObj in (paginatedPatches | pagesJustForBranch(patchPage.repoName, patchPage.branchName)) %}
<li class="page-item">
<a class="page-link {% if pageObj.pageNumber == patchPage.pageNumber %}active{% endif %}" href="{{reposPath}}/{{patchPage.repoName | slugify}}/branches/{{patchPage.branchName}}/commits/page{{pageObj.pageNumber}}">{{ pageObj.pageNumber }}</a>
</li>
{% endfor %}
</ul>11 12 13 14 15 16
{% for commit in patchPage.commits %}
<li class="patch">
<div>
<span class="patch-name"><a href="{{reposPath}}/{{patchPage.repoName | slugify}}/branches/{{patchPage.branchName | slugify}}/patches/{{commit.hash}}">{{commit.message}}</a></span>
<br />
<span>{{commit.date | date}}</span>
<br />11 12 13 14 15 16
{% for commit in patchPage.commits %}
<li class="patch">
<div>
<span class="patch-name"><a href="{{reposPath}}/{{patchPage.repoName | slugify}}/branches/{{patchPage.branchName | slugify}}/commits/{{commit.hash}}">{{commit.message}}</a></span>
<br />
<span>{{commit.date | date}}</span>
<br />