Tucker McKnight <tucker@pangolin.lan> | Sat Jul 04 2026
Put commit pages as a single permalink, not per-branch or per-tag Commit pages will be a special case where there isn't one on each /branch/branch-name or /tags/tag-name URL. Instead, commit pages are their own thing, detached from branches and tags. So the currentRef for a commit page is just the hash of the commit. There can be a 'commit' refType on the nav helper now. Commit pages will have their nav links just go back to the default branch. Additionally, this change makes pages use the nav helper for determining their permalinks, and uses the nav helper in other places where it wasn't being used before, to DRY things up and avoid broken links. Also, the URL scheme on the default branch for all pages will now omit the /branches/main part of the URL. Instead, default branch pages are just at the repo root path. E.g. /repos/my-repo/files for the default branch, but still /repos/my-repo/branches/development/files for the development branch.
0 1 2 3
import m from 'mithril'
import htmlPage from './common/htmlPage.ts'
export default async (
reposConfig: any,0 1 2 3 4
import m from 'mithril'
import htmlPage from './common/htmlPage.ts'
import { NavHelper } from './helpers/nav.ts'
export default async (
reposConfig: any,9 10 11 12 13
const slugify = eleventyConfig.getFilter("slugify")
const lineNumbers = eleventyConfig.getFilter("lineNumbers")
const languageExtension = eleventyConfig.getFilter("languageExtension")
return await htmlPage(reposConfig, eleventyConfig, data, [
m('div', {class: "row"},9 10 11 12 13 14 15 16 17 18 19 20
const slugify = eleventyConfig.getFilter("slugify")
const lineNumbers = eleventyConfig.getFilter("lineNumbers")
const languageExtension = eleventyConfig.getFilter("languageExtension")
const nav = NavHelper({
reposConfig,
slugify,
currentRepoName: data.currentRepo.name,
currentRefName: data.currentRef.name,
currentRefType: data.currentRefType,
})
return await htmlPage(reposConfig, eleventyConfig, data, [
m('div', {class: "row"},55 56 57 58 59 60
return m('div', {class: 'hunk'}, [
m('span', {class: "font-monospace fw-bold"},
m('a', {
href: `${data.reposPath}/${slugify(data.patchInfo.repoName)}/${data.patchInfo.type}/${slugify(data.patchInfo.refName)}/files/${hunk.fileName.split('/').map((filePart) => { return filePart.split('.').map((subpart) => { return slugify(subpart)}).join('.')}).join('/')}.html`
}, `${hunk.fileName}:${hunk.lineNumber}`)
),
m('div', {class: "diff d-flex"}, [55 56 57 58 59 60
return m('div', {class: 'hunk'}, [
m('span', {class: "font-monospace fw-bold"},
m('a', {
href: nav.file(hunk.fileName)
}, `${hunk.fileName}:${hunk.lineNumber}`)
),
m('div', {class: "diff d-flex"}, [48 49 50 51 52 53
return m('li', {class: "page-item"},
m('a', {
class: `page-link ${pageObj.pageNumber === data.patchPage.pageNumber ? 'active' : ''}`,
href: `${nav.commits()}/page${pageObj.pageNumber}`,
}, pageObj.pageNumber)
)
})48 49 50 51 52 53
return m('li', {class: "page-item"},
m('a', {
class: `page-link ${pageObj.pageNumber === data.patchPage.pageNumber ? 'active' : ''}`,
href: `${nav.commits(pageObj.pageNumber)}`,
}, pageObj.pageNumber)
)
})82 83 84 85 86 87
return m('li', {class: "page-item"},
m('a', {
class: `page-link ${pageObj.pageNumber === data.patchPage.pageNumber ? 'active' : ''}`,
href: `${nav.commits()}/page${pageObj.pageNumber}`,
}, pageObj.pageNumber)
)
})82 83 84 85 86 87
return m('li', {class: "page-item"},
m('a', {
class: `page-link ${pageObj.pageNumber === data.patchPage.pageNumber ? 'active' : ''}`,
href: `${nav.commits(pageObj.pageNumber)}`,
}, pageObj.pageNumber)
)
})0 1 2 3
type Ref = { name: string, href: string, date: string }
type RefType = 'branch' | 'tag'
// These are the menu items that show up in the ref: dropdown menu.
const htmlForRef = (ref: Ref, refType: RefType, currentPageRef: string, currentPageRefType: RefType, defaultBranch: string) => {0 1 2 3
type Ref = { name: string, href: string, date: string }
type RefType = 'branch' | 'tag' | 'commit'
// These are the menu items that show up in the ref: dropdown menu.
const htmlForRef = (ref: Ref, refType: RefType, currentPageRef: string, currentPageRefType: RefType, defaultBranch: string) => {68 69 70 71 72 73
),
m('li', {class: "nav-item me-4 mb-1"}, [
m('span', {class: "nav-link d-inline-block"}, 'ref:'),
relDropDown(ref, data.currentRefType, repo, branchesWithHrefs),
]),
m('ul', {class: "main-nav navbar-nav flex-wrap"}, [
m('li', {class: "nav-item"},68 69 70 71 72 73
),
m('li', {class: "nav-item me-4 mb-1"}, [
m('span', {class: "nav-link d-inline-block"}, 'ref:'),
relDropDown(ref.name, data.currentRefType, repo, branchesWithHrefs),
]),
m('ul', {class: "main-nav navbar-nav flex-wrap"}, [
m('li', {class: "nav-item"},78 79 80 81 82 83
m('a', {class: `nav-link ${data.navTab === 'files' ? 'active' : ''}`, href: nav.files()}, 'Files')
),
m('li', {class: "nav-item"},
m('a', {class: `nav-link ${data.navTab === 'commits' ? 'active' : ''}`, href: nav.commits()}, 'Commits')
),
m('li', {class: "nav-item"},
m('a', {class: `nav-link ${data.navTab === 'branches' ? 'active' : ''}`, href: nav.branches()}, 'Branches')78 79 80 81 82 83
m('a', {class: `nav-link ${data.navTab === 'files' ? 'active' : ''}`, href: nav.files()}, 'Files')
),
m('li', {class: "nav-item"},
m('a', {class: `nav-link ${data.navTab === 'commits' ? 'active' : ''}`, href: nav.commits(1)}, 'Commits')
),
m('li', {class: "nav-item"},
m('a', {class: `nav-link ${data.navTab === 'branches' ? 'active' : ''}`, href: nav.branches()}, 'Branches')1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
import { type Repository } from '../../src/dataTypes.ts'
import branchesListItems from './branchesListItems.ts'
type RefType = Repository['branches'][0]
export default (ref: RefType, refType: "branch" | "tag", repo: Repository, branchesWithHrefs) => {
return m('div', {class: "branch-selector dropdown-center d-inline-block"}, [
m('button', {
class: "branches nav-link d-inline-block btn btn-bg dropdown-toggle",
'data-bs-toggle': "dropdown",
'data-bs-auto-close': "outside",
'aria-expanded': "false"
}, ref.name),
m('div', {class: "dropdown-menu"}, [
m('form', {class: "mx-3 my-1"}, [
m('input', {type: "text", class: "form-control", id: "dropdownBranchSearch", placeholder: "Search branches..."}),
m('div', [1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
import { type Repository } from '../../src/dataTypes.ts'
import branchesListItems from './branchesListItems.ts'
export default (refName: string, refType: "branch" | "tag" | "commit", repo: Repository, branchesWithHrefs) => {
return m('div', {class: "branch-selector dropdown-center d-inline-block"}, [
m('button', {
class: `branches nav-link d-inline-block btn btn-bg dropdown-toggle ${refType === 'commit' ? 'current-ref-commit' : ''}`,
'data-bs-toggle': "dropdown",
'data-bs-auto-close': "outside",
'aria-expanded': "false"
}, refName),
m('div', {class: "dropdown-menu"}, [
(refType === 'commit'
? [
m('div', {class: 'container'}, m.trust(`ℹ️ You are currently on a specific commit. Navigation links from this page will take you back to the default branch.`)),
m('div', {class: 'dropdown-divider'}),
]
: null
),
m('form', {class: "mx-3 my-1"}, [
m('input', {type: "text", class: "form-control", id: "dropdownBranchSearch", placeholder: "Search branches..."}),
m('div', [34 35 36 37 38 39
]),
m('div', {class: "dropdown-divider"}),
m('div', {id: "dropdown-branches-results", class: "dropdown-branches"},
m.trust(branchesListItems(branchesWithHrefs, repo.defaultBranch, ref.name, refType, 'date'))
)
])
])34 35 36 37 38 39
]),
m('div', {class: "dropdown-divider"}),
m('div', {id: "dropdown-branches-results", class: "dropdown-branches"},
m.trust(branchesListItems(branchesWithHrefs, repo.defaultBranch, refName, refType, 'date'))
)
])
])2 3 4 5 6 7
import { type Repository } from '../src/dataTypes.ts'
import getFlatRefs from '../src/flatRefs.ts'
import { dateToRfc3339 } from '@11ty/eleventy-plugin-rss'
import flatPatches from '../src/flatPatches.ts'
export default async (
eleventyConfig: any,2 3 4 5 6 7
import { type Repository } from '../src/dataTypes.ts'
import getFlatRefs from '../src/flatRefs.ts'
import { dateToRfc3339 } from '@11ty/eleventy-plugin-rss'
import { flatPatches } from '../src/flatPatches.ts'
export default async (
eleventyConfig: any,1 2 3 4 5
import htmlPage from './common/htmlPage.ts'
import { type Repository } from '../src/dataTypes.ts'
import { type FlatFileEntry } from '../src/flatFiles.ts'
type Branch = Repository['branches'][0]
export default async (reposConfig: any, eleventyConfig: any, data: any) => {1 2 3 4 5 6
import htmlPage from './common/htmlPage.ts'
import { type Repository } from '../src/dataTypes.ts'
import { type FlatFileEntry } from '../src/flatFiles.ts'
import { NavHelper } from './helpers/nav.ts'
type Branch = Repository['branches'][0]
export default async (reposConfig: any, eleventyConfig: any, data: any) => {13 14 15 16 17 18 19 20 21 22
const highlightCode = eleventyConfig.getFilter("highlightCode")
const languageExtension = eleventyConfig.getFilter("languageExtension")
const renderContentIfAvailable = eleventyConfig.getFilter("renderContentIfAvailable")
const currentRef: Branch = data.currentRef
const currentRepo: Repository = data.currentRepo
const fileInfo: FlatFileEntry = data.fileInfo
const pageContent = [
m('div', {class: "row mt-3 mb-1"},
m('div', {class: "col"},13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
const highlightCode = eleventyConfig.getFilter("highlightCode")
const languageExtension = eleventyConfig.getFilter("languageExtension")
const renderContentIfAvailable = eleventyConfig.getFilter("renderContentIfAvailable")
const currentRef: Branch = data.currentRef
const currentRepo: Repository = data.currentRepo
const fileInfo: FlatFileEntry = data.fileInfo
const nav = NavHelper({
reposConfig,
slugify,
currentRepoName: currentRepo.name,
currentRefName: currentRef.name,
currentRefType: data.currentRefType,
})
const pageContent = [
m('div', {class: "row mt-3 mb-1"},
m('div', {class: "col"},55 56 57 58 59 60 61 62 63 64
return m('li', {class: 'list-group-item'}, [
dir.isDirectory ? m('span', m.trust('📁 ')) : null,
m('a', {
href: `${data.reposPath}/${slugify(fileInfo.repoName)}/${fileInfo.type}/${slugify(fileInfo.refName)}/files/${dir.fullPath.split('/').map((pathPart) => {
return pathPart.split('.').map((subPart) => {
return slugify(subPart)
}).join('.')
}).join('/')}`},
getRelativePath(fileInfo.file, dir.name)
)
])55 56 57 58 59 60
return m('li', {class: 'list-group-item'}, [
dir.isDirectory ? m('span', m.trust('📁 ')) : null,
m('a', {
href: nav.file(dir.fullPath)},
getRelativePath(fileInfo.file, dir.name)
)
])71 72 73 74 75 76
m('div', {class: "row my-3"},
m('div', {class: "col"},
m('span', m('a', {
href: `${data.reposPath}/${slugify(fileInfo.repoName)}/${fileInfo.type}/${slugify(fileInfo.refName)}/raw/${fileInfo.file.split('.').map(filePart => slugify(filePart)).join('.')}`}, 'View raw file'))
)
),
(fileInfo.file.endsWith(".md") ?71 72 73 74 75 76
m('div', {class: "row my-3"},
m('div', {class: "col"},
m('span', m('a', {
href: nav.raw(fileInfo.file)}, 'View raw file'))
)
),
(fileInfo.file.endsWith(".md") ?126 127 128 129 130 131
m('code', {style: "white-space: pre;"}, [
m('pre', {class: "language-text"}, m.trust(
(await currentRepo.files(fileInfo.file, currentRef.sha)).blameLines.map((annotation) => {
return `<a href="${data.reposPath}/${slugify(fileInfo.repoName)}/${fileInfo.type}/${slugify(fileInfo.refName)}/commits/${annotation.sha}">${annotation.sha.substr(0, 6)}</a> ${annotation.author}`
}).join('\n')))
])
),126 127 128 129 130 131
m('code', {style: "white-space: pre;"}, [
m('pre', {class: "language-text"}, m.trust(
(await currentRepo.files(fileInfo.file, currentRef.sha)).blameLines.map((annotation) => {
return `<a href="${nav.commit(annotation.sha)}">${annotation.sha.substr(0, 6)}</a> ${annotation.author}`
}).join('\n')))
])
),35 36 37 38 39 40
return m('li', {class: 'list-group-item'}, [
file.isDirectory ? m.trust('<span>📁 </span>') : null,
m('a', {
href: nav.file(file)
}, file.name)
])
}))35 36 37 38 39 40
return m('li', {class: 'list-group-item'}, [
file.isDirectory ? m.trust('<span>📁 </span>') : null,
m('a', {
href: nav.file(file.fullPath)
}, file.name)
])
}))0 1 2
import { type SortedFileList } from '../../src/dataTypes.ts'
import { type ReposConfiguration } from "../../src/configTypes.ts"
/**0 1
i
mport { type ReposConfiguration } from "../../src/configTypes.ts"
/**13 14 15 16 17 18
slugify: Function,
currentRepoName: string,
currentRefName: string,
currentRefType: 'branch' | 'tag'
}
export const NavHelper = (args: CurrentRefArgs) => {
const reposPath = args.reposConfig.path || ""13 14 15 16 17 18
slugify: Function,
currentRepoName: string,
currentRefName: string,
currentRefType: 'branch' | 'tag' | 'commit'
}
export const NavHelper = (args: CurrentRefArgs) => {
const reposPath = args.reposConfig.path || ""23 24 25 26 27 28
// or 'repos/my-repo-name/branches.'
const repoBasePath = `${reposPath}/${args.slugify(args.currentRepoName)}`
const refPath = (refName, refType) => `${repoBasePath}/${refType}/${args.slugify(refName)}`
type RefArgument = {
refName: string,23 24 25 26 27 28 29 30 31 32 33 34
// or 'repos/my-repo-name/branches.'
const repoBasePath = `${reposPath}/${args.slugify(args.currentRepoName)}`
const refPath = (refName, refType) => {
if (refType === 'commit'
|| (refType === 'branch' && refName === args.reposConfig.repos[args.currentRepoName].defaultBranch)) {
return repoBasePath
}
return `${repoBasePath}/${refType}/${args.slugify(refName)}`
}
type RefArgument = {
refName: string,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
const refName = newRef?.refName || args.currentRefName
const refType = newRef?.refType || args.currentRefType
return (refType === 'branch' && refName === args.reposConfig.repos[args.currentRepoName].defaultBranch)
? repoBasePath
: refPath(refName, refType)
},
file: (file: SortedFileList[0], newRef: RefArgument = null) => {
const refName = newRef?.refName || args.currentRefName
const refType = newRef?.refType || args.currentRefType
return `${refPath(refName, refType)}/files/${file.fullPath.split('/')
.map((pathPart) => {
return pathPart.split('.').map((subPart) => {
return args.slugify(subPart)
}).join('.')
}).join('/')}.html`
},
files: (newRef: RefArgument = null) => {
const refName = newRef?.refName || args.currentRefName
const refType = newRef?.refType || args.currentRefType
return `${refPath(refName, refType)}/files`
},
commit: (hash: string, newRef: RefArgument = null) => {
const refName = newRef?.refName || args.currentRefName
const refType = newRef?.refType || args.currentRefType
return `${refPath(refName, refType)}/commits/${hash}`
},
commits: (newRef: RefArgument = null) => {
const refName = newRef?.refName || args.currentRefName
const refType = newRef?.refType || args.currentRefType
return `${refPath(refName, refType)}/commits/page1`
},
branches: (newRef: RefArgument = null) => {
const refName = newRef?.refName || args.currentRefName46 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 87 88
const refName = newRef?.refName || args.currentRefName
const refType = newRef?.refType || args.currentRefType
return refPath(refName, refType)
},
file: (filePath: string, newRef: RefArgument = null) => {
const refName = newRef?.refName || args.currentRefName
const refType = newRef?.refType || args.currentRefType
return `${refPath(refName, refType)}/files/${filePath.split('/')
.map((pathPart) => {
return pathPart.split('.').map((subPart) => {
return args.slugify(subPart)
}).join('.')
}).join('/')}.html`
},
raw: (filePath: string, newRef: RefArgument = null) => {
const refName = newRef?.refName || args.currentRefName
const refType = newRef?.refType || args.currentRefType
return `${refPath(refName, refType)}/raw/${filePath.split('/')
.map((filePart) => {
return filePart.split('.').map((subPart) => {
return args.slugify(subPart)
}).join('.')
}).join('/')}`
},
files: (newRef: RefArgument = null) => {
const refName = newRef?.refName || args.currentRefName
const refType = newRef?.refType || args.currentRefType
return `${refPath(refName, refType)}/files`
},
commit: (hash: string) => {
return `${repoBasePath}/commits/${hash}`
},
commits: (pageNum: number, newRef: RefArgument = null) => {
const refName = newRef?.refName || args.currentRefName
const refType = newRef?.refType || args.currentRefType
return `${refPath(refName, refType)}/commits/page${pageNum.toString()}`
},
branches: (newRef: RefArgument = null) => {
const refName = newRef?.refName || args.currentRefName3 4 5 6 7 8
import {repos, getBranchesAndTags} from './src/repos.ts'
import getFlatRefs from './src/flatRefs.ts'
import { flatFiles, flatDirectories } from './src/flatFiles.ts'
import flatPatches from './src/flatPatches.ts'
import paginatedPatches, {type PatchPage} from './src/paginatedPatches.ts'
import {getLocation} from './src/helpers.ts'
import {ReposConfiguration} from './src/configTypes.ts'3 4 5 6 7 8
import {repos, getBranchesAndTags} from './src/repos.ts'
import getFlatRefs from './src/flatRefs.ts'
import { flatFiles, flatDirectories } from './src/flatFiles.ts'
import { flatPatches, commitsInRepo } from './src/flatPatches.ts'
import paginatedPatches, {type PatchPage} from './src/paginatedPatches.ts'
import {getLocation} from './src/helpers.ts'
import {ReposConfiguration} from './src/configTypes.ts'21 22 23 24 25
import tagsJsTemplate from './js_templates/tags.ts'
import rawJsTemplate from './js_templates/raw.ts'
import feedJsTemplate from './js_templates/feed.ts'
const ajv = new Ajv()
const exec = util.promisify(childProcess.exec)21 22 23 24 25 26
import tagsJsTemplate from './js_templates/tags.ts'
import rawJsTemplate from './js_templates/raw.ts'
import feedJsTemplate from './js_templates/feed.ts'
import { NavHelper } from './js_templates/helpers/nav.ts'
const ajv = new Ajv()
const exec = util.promisify(childProcess.exec)307 308 309 310 311 312 313 314 315
alias: "flatRef",
},
permalink: (data) => {
const repoName = data.flatRef.repoName
const refName = data.flatRef.refName
const refType = data.flatRef.type
return `${reposPath}/${eleventyConfig.getFilter("slugify")(repoName)}/${refType}/${eleventyConfig.getFilter("slugify")(refName)}/branches/`
},
eleventyComputed: {
nav: {307 308 309 310 311 312 313 314 315 316 317 318 319 320
alias: "flatRef",
},
permalink: (data) => {
const nav = NavHelper({
reposConfig: reposConfiguration,
slugify,
currentRepoName: data.flatRef.repoName,
currentRefName: data.flatRef.refName,
currentRefType: data.flatRef.type,
})
return nav.branches() + '/'
},
eleventyComputed: {
nav: {347 348 349 350 351 352 353 354 355
alias: "flatRef",
},
permalink: (data) => {
const repoName = data.flatRef.repoName
const refName = data.flatRef.refName
const refType = data.flatRef.type
return `${reposPath}/${eleventyConfig.getFilter("slugify")(repoName)}/${refType}/${eleventyConfig.getFilter("slugify")(refName)}/tags/`
},
eleventyComputed: {
nav: {347 348 349 350 351 352 353 354 355 356 357 358 359 360
alias: "flatRef",
},
permalink: (data) => {
const nav = NavHelper({
reposConfig: reposConfiguration,
slugify,
currentRepoName: data.flatRef.repoName,
currentRefName: data.flatRef.refName,
currentRefType: data.flatRef.type,
})
return nav.tags() + '/'
},
eleventyComputed: {
nav: {391 392 393 394 395 396 397 398 399
},
flatFiles: flatFilesData.concat(flatDirectoriesData),
permalink: (data) => {
const repoName = data.fileInfo.repoName
const refName = data.fileInfo.refName
const refType = data.fileInfo.type
return `${reposPath}/${eleventyConfig.getFilter("slugify")(repoName)}/${refType}/${eleventyConfig.getFilter("slugify")(refName)}/files/${data.fileInfo.file.split('/').map((filePart) => filePart.split('.').map((subPart) => eleventyConfig.getFilter("slugify")(subPart)).join('.')).join('/')}.html`
},
eleventyComputed: {
nav: {391 392 393 394 395 396 397 398 399 400 401 402 403 404
},
flatFiles: flatFilesData.concat(flatDirectoriesData),
permalink: (data) => {
const nav = NavHelper({
reposConfig: reposConfiguration,
slugify,
currentRepoName: data.fileInfo.repoName,
currentRefName: data.fileInfo.refName,
currentRefType: data.fileInfo.type,
})
return nav.file(data.fileInfo.file)
},
eleventyComputed: {
nav: {433 434 435 436 437 438 439 440 441
eleventyAllowMissingExtension: true,
flatFiles: flatFilesData,
permalink: (data) => {
const repoName = data.fileInfo.repoName
const refName = data.fileInfo.refName
const refType = data.fileInfo.type
return `${reposPath}/${eleventyConfig.getFilter("slugify")(repoName)}/${refType}/${eleventyConfig.getFilter("slugify")(refName)}/raw/${data.fileInfo.file.split('/').map((filePart) => filePart.split('.').map((subPart) => eleventyConfig.getFilter("slugify")(subPart)).join('.')).join('/')}`
},
eleventyComputed: {
currentRepo: (data) => reposData.find(repo => {433 434 435 436 437 438 439 440 441 442 443 444 445 446
eleventyAllowMissingExtension: true,
flatFiles: flatFilesData,
permalink: (data) => {
const nav = NavHelper({
reposConfig: reposConfiguration,
slugify,
currentRepoName: data.fileInfo.repoName,
currentRefName: data.fileInfo.refName,
currentRefType: data.fileInfo.type,
})
return nav.raw(data.fileInfo.file)
},
eleventyComputed: {
currentRepo: (data) => reposData.find(repo => {467 468 469 470 471 472 473 474 475
alias: "flatRef",
},
permalink: (data) => {
const repoName = data.flatRef.repoName
const refName = data.flatRef.refName
const refType = data.flatRef.type
return `${reposPath}/${eleventyConfig.getFilter("slugify")(repoName)}/${refType}/${eleventyConfig.getFilter("slugify")(refName)}/files/`
},
eleventyComputed: {
nav: {467 468 469 470 471 472 473 474 475 476 477 478 479 480
alias: "flatRef",
},
permalink: (data) => {
const nav = NavHelper({
reposConfig: reposConfiguration,
slugify,
currentRepoName: data.flatRef.repoName,
currentRefName: data.flatRef.refName,
currentRefType: data.flatRef.type,
})
return nav.files() + '/'
},
eleventyComputed: {
nav: {506 507 508 509 510 511 512 513 514 515 516 517 518 519
alias: "flatRef",
},
permalink: (data) => {
const repoName = data.flatRef.repoName
const refName = data.flatRef.refName
const refType = data.flatRef.type
if (refType === "branch" && refName === reposConfiguration.repos[repoName].defaultBranch) {
return `${reposPath}/${eleventyConfig.getFilter("slugify")(repoName)}/`
}
else {
return `${reposPath}/${eleventyConfig.getFilter("slugify")(repoName)}/${refType}/${eleventyConfig.getFilter("slugify")(refName)}/`
}
},
eleventyComputed: {
nav: {506 507 508 509 510 511 512 513 514 515 516 517 518 519
alias: "flatRef",
},
permalink: (data) => {
const nav = NavHelper({
reposConfig: reposConfiguration,
slugify,
currentRepoName: data.flatRef.repoName,
currentRefName: data.flatRef.refName,
currentRefType: data.flatRef.type,
})
return nav.refHome() + '/'
},
eleventyComputed: {
nav: {552 553 554 555 556 557 558 559 560
},
paginatedPatches: paginatedPatchesData,
permalink: (data) => {
const repoName = data.patchPage.repoName
const refName = data.patchPage.refName
const refType = data.patchPage.type
return `${reposPath}/${eleventyConfig.getFilter("slugify")(repoName)}/${refType}/${eleventyConfig.getFilter("slugify")(refName)}/commits/page${data.patchPage.pageNumber}/`
},
eleventyComputed: {
nav: {552 553 554 555 556 557 558 559 560 561 562 563 564 565
},
paginatedPatches: paginatedPatchesData,
permalink: (data) => {
const nav = NavHelper({
reposConfig: reposConfiguration,
slugify,
currentRepoName: data.patchPage.repoName,
currentRefName: data.patchPage.refName,
currentRefType: data.patchPage.type,
})
return nav.commits(data.patchPage.pageNumber) + '/'
},
eleventyComputed: {
nav: {581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620
)
// COMMIT.TS
const flatPatchesData = await flatPatches(reposData)
eleventyConfig.addTemplate(
`repos/commit.11ty.js`,
commonPage(commitJsTemplate, reposConfiguration, eleventyConfig),
{
pagination: {
data: "flatPatches",
size: 1,
alias: "patchInfo",
},
flatPatches: flatPatchesData,
permalink: (data) => {
const repoName = data.patchInfo.repoName
const refName = data.patchInfo.refName
const refType = data.patchInfo.type
return `${reposPath}/${eleventyConfig.getFilter("slugify")(repoName)}/${refType}/${eleventyConfig.getFilter("slugify")(refName)}/commits/${data.patchInfo.commit.hash}/`
},
eleventyComputed: {
nav: {
repoName: (data) => data.patchInfo.repoName,
refName: (data) => data.patchInfo.refName,
path: 'commits/page1',
},
currentRepo: (data) => reposData.find(repo => {
return repo.name === data.patchInfo.repoName
}),
currentRef: (data) => {
const refType = data.patchInfo.type === "branch" ? "branches" : "tags"
return reposData.find(repo => {
return repo.name === data.patchInfo.repoName
})[refType].find(ref => {
return ref.name === data.patchInfo.refName
})
},
currentRefType: (data) => data.patchInfo.type
},
navTab: "commits",
}581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616
)
// COMMIT.TS
const flatPatchesData = flatPatches(reposData)
const commitsInRepos = commitsInRepo(reposData)
eleventyConfig.addTemplate(
`repos/commit.11ty.js`,
commonPage(commitJsTemplate, reposConfiguration, eleventyConfig),
{
pagination: {
data: "commitsInRepos",
size: 1,
alias: "patchInfo",
},
commitsInRepos,
permalink: (data) => {
const repoName = data.patchInfo.repoName
return `${reposPath}/${eleventyConfig.getFilter("slugify")(repoName)}/commits/${data.patchInfo.commit.hash}/`
},
eleventyComputed: {
nav: {
repoName: (data) => data.patchInfo.repoName,
refName: (data) => data.patchInfo.commit.hash,
path: 'commits/page1',
},
currentRepo: (data) => reposData.find(repo => {
return repo.name === data.patchInfo.repoName
}),
currentRef: (data) => {
// TODO: fix this hack (it's becuse other places expect a ref.name to be available)
return {name: data.patchInfo.commit.hash.substring(0, 6)}
},
currentRefType: (data) => "commit",
},
navTab: "commits",
}190 191 192 193 194 195 196 197 198
max-width: 100%;
}
.branch-selector .dropdown-menu {
min-width: 220px;
max-height: 80vh;
overflow-y: scroll;
}
.badge {190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207
max-width: 100%;
}
.branch-selector {
/* Special purple box to draw user's attention to the fact that
* commit pages are a special case. */
.current-ref-commit {
background: $purple;
color: white;
}
.dropdown-menu {
min-width: 220px;
max-height: 80vh;
overflow-y: scroll;
}
}
.badge {0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
import { type Repository } from "./dataTypes.ts"
type FlatPatchRecord = {
commit: ReturnType<Repository['commits']['get']>,
repoName: string,
refName: string,
type: "branch" | "tag",
}
let cachedFlatPatches: Array<FlatPatchRecord> | null = null
export default async (repos: Array<Repository>)
: Promise<Array<FlatPatchRecord>> => {
if (cachedFlatPatches !== null) { return cachedFlatPatches }
const itemsForRef: (0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
import { type Repository } from "./dataTypes.ts"
type Commit = ReturnType<Repository['commits']['get']>
type FlatPatchRecord = {
commit: Commit,
repoName: string,
refName: string,
type: "branch" | "tag",
}
let cachedFlatPatches: Array<FlatPatchRecord> | null = null
let cachedRepoCommits: Array<{
repoName: string,
commit: Commit,
}> | null = null
const flatPatches = (repos: Array<Repository>)
: Array<FlatPatchRecord> => {
if (cachedFlatPatches !== null) { return cachedFlatPatches }
const itemsForRef: (46 47
return cachedFlatPatches
}46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
return cachedFlatPatches
}
const commitsInRepo = (repos: Array<Repository>) : Array<{repoName: string, commit: Commit}> => {
if (cachedRepoCommits !== null) { return cachedRepoCommits }
cachedRepoCommits = repos.flatMap((repo) => {
return Array.from(repo.commits.values()).map((commit) => {
return {
repoName: repo.name,
commit,
}
})
})
return cachedRepoCommits
}
export {
flatPatches,
commitsInRepo,
}0 1 2 3
import { type Repository } from './dataTypes.ts'
import flatPatchesFunc from './flatPatches.js'
export type PatchPage = {
repoName: string,0 1 2 3
import { type Repository } from './dataTypes.ts'
import { flatPatches as flatPatchesFunc } from './flatPatches.js'
export type PatchPage = {
repoName: string,12 13 14 15 16
## Works in Progress
- [ ] Clean up the template views, create mithril components for rows and columns
## Ideas and Todos
12 13 14 15 16 17
## Works in Progress
- [ ] Clean up the template views, create mithril components for rows and columns
- [ ] [Make it only generate one page per commit](./projects/one-page-per-commit.md.html)
## Ideas and Todos
-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
# One Page Per Commit
Currently, it generates one page per commit per branch and tag. Like, each commit
that exists on a branch has its own url of /branch/branch-name/commits/123abc, and
if that same commit is on `other-branch`, then it will also have
/branch/other-branch/commits/123abc, even though the content of the commit is the same
between the two.
The only thing different on these pages is which branch is set as the "current" branch
in the ref dropdown menu at the top of the page.
This will be pretty wasteful for large repositories with a lot of branches and tags.
I think it would be a better idea to only generate one HTML page for the commit.
## What to change
- Get rid of flatPatches, as it is right now
- Should just be flatMap of commits per repo
- Pagination data for the commits page is this new list, URL should not include
the branch or tag name
- The currentRef for the commits pages should be the commit itself. The menu in the
nav should show this -- have a special case for the commits page.
- Clicking any new links should just go to the default branch (kinda sucks, but that's
also what github and codeberg do)
- Links to files on the commits page should instead give you a dropdown menu with a
list of branches/tags that have that file in them.
## Some notes:
- The commit page will not have a currentRef or currentRefType that is set to a
branch or tag
- Is this now the right time to think about having per-commit snapshots? This can
be part of that.