Tucker McKnight <tucker@pangolin.lan> | Sun Dec 21 2025
Turn some NJK templates into typescript files Added an htmlPage wrapper for the common header/nav elements. Repo, files, and file pages done so far.
0 1 2
import {branchesListItems} from '../dist/js_templates/repo.js'
const setCheckbox = window.setCheckbox
const currentTheme = window.currentTheme0 1 2
import {branchesListItems} from '../dist/js_templates/common/htmlPage.js'
const setCheckbox = window.setCheckbox
const currentTheme = window.currentTheme15 16 17 18 19 20
window['currentTheme'] = mode
};
window['currentTheme'] = localStorage.getItem("theme") || "auto"
window['setMode'](window['currentTheme'])
15 16 17 18 19 20 21
window['currentTheme'] = mode
};
// window['currentTheme'] = localStorage.getItem("theme") || "auto"
window['currentTheme'] = "light"
window['setMode'](window['currentTheme'])
54 55 56 57 58 59
<a class="nav-link" href="${nav.rootPath()}">← All repositories</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">${repo.name}</a>
</li>
<li class="nav-item">
<span class="nav-link d-inline-block">Branch:</span>54 55 56 57 58 59
<a class="nav-link" href="${nav.rootPath()}">← All repositories</a>
</li>
<li class="nav-item">
<a class="nav-link" href="${nav.repoCurrentBranchHome()}">${repo.name}</a>
</li>
<li class="nav-item">
<span class="nav-link d-inline-block">Branch:</span>103 104 105 106 107 108 109 110 111
<nav class="navbar navbar-expand">
<ul class="main-nav navbar-nav flex-wrap">
<li class="nav-item">
<a class="nav-link active" aria-current="page" href="${nav.repoCurrentBranchHome()}">Home</a>
</li>
<li class="nav-item">
<a class="nav-link" href="${nav.repoCurrentBranchFiles()}">Files</a>
</li>
<li class="nav-item">
<a class="nav-link" href="${nav.repoCurrentBranchCommits()}">Commits</a>103 104 105 106 107 108 109 110 111
<nav class="navbar navbar-expand">
<ul class="main-nav navbar-nav flex-wrap">
<li class="nav-item">
<a class="nav-link ${data.navTab === 'home' ? 'active' : ''}" aria-current="page" href="${nav.repoCurrentBranchHome()}">Home</a>
</li>
<li class="nav-item">
<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>0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
export default async (eleventyConfig: any, data: any) => {
const branch: Repository['branches'][0] = data.currentBranch
const topLevelFilesOnly = eleventyConfig.getFilter("topLevelFilesOnly")
const slugify = eleventyConfig.getFilter("slugify")
const files = topLevelFilesOnly('', branch.fileList)
return `
<div class="row">
<div class="col">
<ul class="list-group">
${files.map(files) => {
return `
<li class="list-group-item">
${file.isDirectory ? '<i class="bi bi-folder-fill"></i>' : '<i class="bi bi-file-earmark"></i>'
<a href="${data.reposPath}/${slugify(data.branchInfo.repoName)}/branches/${slugify(branchInfo.branchName)}/files/${slugify(file.fullPath)}.html">${file.name}</a>
</li>
`
}}
</ul>
</div>
</div>
`
}0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
import { type SortedFileList, type Repository } from "../src/dataTypes.ts"
export default async (eleventyConfig: any, data: any) => {
const branch: Repository['branches'][0] = data.currentBranch
const topLevelFilesOnly = eleventyConfig.getFilter("topLevelFilesOnly")
const slugify = eleventyConfig.getFilter("slugify")
const files: SortedFileList = topLevelFilesOnly(branch.fileList, '')
return `
<h3>./</h3>
<p>Files snapshot from <span class="font-monospace">${data.branchInfo.branchName}</span></p>
<ul class="list-group">
${files.map((file) => {
return `
<li class="list-group-item">
${file.isDirectory ? '<span>📁</span>' : ''}
<a href="${data.reposPath}/${slugify(data.branchInfo.repoName)}/branches/${slugify(data.branchInfo.branchName)}/files/${slugify(file.fullPath)}.html">${file.name}</a>
</li>
`
}).join('')}
</ul>
`
}0 1 2
import { type ReposConfiguration } from '../src/configTypes.ts'
import { type Repository } from '../src/dataTypes.ts'
export default async (eleventyConfig: any, data: any) => {0 1
i
mport { type Repository } from '../src/dataTypes.ts'
export default async (eleventyConfig: any, data: any) => {8 9 10 11 12 13 14 15 16
import {getLocation} from './src/helpers.ts'
import * as operations from './src/vcses/git/operations.ts'
import {ReposConfiguration} from './src/configTypes.ts'
import {Ajv} from 'ajv'
import ConfigSchema from './schemas/ReposConfiguration.json' with { type: 'json' }
import htmlPage from './js_templates/common/htmlPage.ts'
import repoJsTemplate from './js_templates/repo.ts'
const ajv = new Ajv()
const exec = util.promisify(childProcess.exec)8 9 10 11 12 13 14 15 16 17 18 19
import {getLocation} from './src/helpers.ts'
import * as operations from './src/vcses/git/operations.ts'
import {ReposConfiguration} from './src/configTypes.ts'
import { type SortedFileList } from './src/dataTypes.ts'
import {Ajv} from 'ajv'
import ConfigSchema from './schemas/ReposConfiguration.json' with { type: 'json' }
import htmlPage from './js_templates/common/htmlPage.ts'
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)112 113 114 115 116
})
eleventyConfig.addFilter("lineNumbers", (code: string) => {
const numLines = code.split('\n').length
const lineNumbers = []
for (let i = 1; i <= numLines; i++) {112 113 114 115 116 117 118 119 120 121 122
})
eleventyConfig.addFilter("lineNumbers", (code: string) => {
try {
code.split('')
}
catch (error) {
console.log(code)
}
const numLines = code.split('\n').length
const lineNumbers = []
for (let i = 1; i <= numLines; i++) {148 149 150 151 152 153
return extensionsConfig && extensionsConfig[extension] ? extensionsConfig[extension] : extension
})
eleventyConfig.addFilter("topLevelFilesOnly", (files: Array<string>, currentLevel: string) => {
const onlyUnique = (value, index, array) => {
return array.findIndex(test => test.name === value.name) === index;
}148 149 150 151 152 153
return extensionsConfig && extensionsConfig[extension] ? extensionsConfig[extension] : extension
})
eleventyConfig.addFilter("topLevelFilesOnly", (files: Array<string>, currentLevel: string): SortedFileList => {
const onlyUnique = (value, index, array) => {
return array.findIndex(test => test.name === value.name) === index;
}285 286 287 288 289 290 291 292 293 294 295
}
)
// FILE.NJK
const fileTemplate = fsImport.readFileSync(`${import.meta.dirname}/templates/file.njk`).toString()
const flatFilesData = flatFiles(reposData)
eleventyConfig.addTemplate(
'repos/file.njk',
topLayoutPartial + fileTemplate + bottomLayoutPartial,
{
pagination: {
data: "flatFiles",285 286 287 288 289 290 291 292 293 294
}
)
// FILE.TS
const flatFilesData = flatFiles(reposData)
eleventyConfig.addTemplate(
'repos/file.11ty.js',
htmlPage(reposConfiguration, eleventyConfig, fileJsTemplate),
{
pagination: {
data: "flatFiles",343 344 345 346 347 348 349 350 351 352
}
)
// FILES.NJK
const filesTemplate = fsImport.readFileSync(`${import.meta.dirname}/templates/files.njk`).toString()
eleventyConfig.addTemplate(
'repos/files.njk',
topLayoutPartial + filesTemplate + bottomLayoutPartial,
{
pagination: {
data: "branches",343 344 345 346 347 348 349 350 351
}
)
// FILES.TS
eleventyConfig.addTemplate(
'repos/files.11ty.js',
htmlPage(reposConfiguration, eleventyConfig, filesJsTemplate),
{
pagination: {
data: "branches",428 429 430 431 432 433
permalink: (data) => {
const repoName = data.branch.repoName
const branchName = data.branch.branchName
return `${reposPath}/${eleventyConfig.getFilter("slugify")(repoName)}/new/${eleventyConfig.getFilter("slugify")(branchName)}/`
},
eleventyComputed: {
nav: {428 429 430 431 432 433
permalink: (data) => {
const repoName = data.branch.repoName
const branchName = data.branch.branchName
return `${reposPath}/${eleventyConfig.getFilter("slugify")(repoName)}/branches/${eleventyConfig.getFilter("slugify")(branchName)}/`
},
eleventyComputed: {
nav: {445 446 447 448 449
return branch.name === data.branch.branchName
}),
},
}
)
445 446 447 448 449 450
return branch.name === data.branch.branchName
}),
},
navTab: "home"
}
)
10 11 12 13 14 15 16
$enable-transitions: false;
$gray: #EFF2E4; /* this is... not gray? */
$blue: #7388FA;
$lightblue: #73B2FA;
$purple: #852cdf;
$lilac: #E273FA;
$magenta: #FF549B;10 11 12 13 14 15 16
$enable-transitions: false;
$gray: #EFF2E4; /* this is... not gray? */
$blue: #556DF0;
$lightblue: #7388FA;
$purple: #852cdf;
$lilac: #E273FA;
$magenta: #FF549B;29 30 31 32 33 34 35 36 37 38 39 40
@import "../node_modules/bootstrap/scss/bootstrap";
.bezel {
background-color: $blue;
border-top: 6px solid color.adjust($blue, $lightness: 15%);
border-left: 6px solid color.adjust($blue, $lightness: 13%);
border-bottom: 6px solid color.adjust($blue, $lightness: -10%);
border-right: 6px solid color.adjust($blue, $lightness: -13%);
}
.bezel-purple {29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
@import "../node_modules/bootstrap/scss/bootstrap";
.list-group {
--bs-list-group-border-width: 1px;
--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%);
border-bottom: 6px solid color.adjust($lightblue, $lightness: -10%);
border-right: 6px solid color.adjust($lightblue, $lightness: -13%);
}
.bezel-purple {26 27
}>
}>,
}26 27 28 29 30 31 32 33
}>
}>,
}
export type SortedFileList = Array<{
name: string,
fullPath: string,
isDirectory: boolean,
}>