Tucker McKnight <tucker@pangolin.lan> | Sun Dec 14 2025
WIP make frontend and backend modules shareable
20 21 22 23 24
window['setMode'](window['currentTheme'])
window['setCheckbox'] = (mode, element) => {
if (mode === 'light') {
element.innerHTML = `<i class="bi bi-brightness-high"></i>`
}20 21 22 23 24 25
window['setMode'](window['currentTheme'])
window['setCheckbox'] = (mode, element) => {
if (element === null) { return }
if (mode === 'light') {
element.innerHTML = `<i class="bi bi-brightness-high"></i>`
}
-1 0 1 2 3 4 5 6
import path from 'path'
export default {
entry: './main.js',
output: {
path: path.resolve(import.meta.dirname, '../dist/frontend'),
filename: 'main-frontend.bundle.js',
},
};1 2 3 4 5
import { type ReposConfiguration } from '../src/configTypes.ts'
import { type Repository } from '../src/dataTypes.ts'
export default async (reposConfig: ReposConfiguration, eleventyConfig: any) => {
return async (data) => {
if (data.currentRepo === '') {1 2 3 4 5 6 7 8 9 10 11 12
import { type ReposConfiguration } from '../src/configTypes.ts'
import { type Repository } from '../src/dataTypes.ts'
export const branchesListItems = (branches: Array<{name: string, href: string}>, defaultBranch: string): string => {
return branches.map((branch) => {
const badge = defaultBranch === branch.name ? '<div class="badge rounded-pill bg-primary ms-2">default</div>' : ''
return `<span class='dropdown-item'><a href='${branch.href}'>${branch.name}</a>${badge}</span>`
}).join('')
}
export default async (reposConfig: ReposConfiguration, eleventyConfig: any) => {
return async (data) => {
if (data.currentRepo === '') {15 16 17 18 19
const nav = NavHelper(reposConfig, slugify, repo.name, branch.name)
return `
<!doctype html>
<html lang="en">15 16 17 18 19 20 21 22 23 24 25 26
const nav = NavHelper(reposConfig, slugify, repo.name, branch.name)
const branchesWithHrefs = repo.branches.map((branch) => {
return {
name: branch.name,
href: nav.repoBranchHome(branch.name),
}
})
return `
<!doctype html>
<html lang="en">25 26 27 28 29 30 31
<link rel="stylesheet" href="/css/design-board.css">
<link href="https://unpkg.com/prismjs@1.20.0/themes/prism.css" rel="stylesheet" />
<script>
window.branches = ${JSON.stringify(repo.branches)};
</script>
</head>
<body>
<div class="container">25 26 27 28 29 30 31 32 33
<link rel="stylesheet" href="/css/design-board.css">
<link href="https://unpkg.com/prismjs@1.20.0/themes/prism.css" rel="stylesheet" />
<script>
window.branchesWithHrefs = ${JSON.stringify(branchesWithHrefs)};
window.defaultBranch = "${repo.defaultBranch}";
</script>
<script src="${nav.rootPath()}frontend/top.js"></script>
</head>
<body>
<div class="container">64 65 66 67 68 69
<div class="row">
<div class="col">
<nav class="navbar navbar-expand">
<ul class="navbar-nav flex-wrap">
<li class="nav-item">
<a class="nav-link active" aria-current="page" href="${nav.repoCurrentBranchHome()}">Home</a>
</li>64 65 66 67 68 69
<div class="row">
<div class="col">
<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>118 119 120 121 122 123
</div>
<div class="row flex-grow-1 align-items-center">
<div class="col-md col-fluid py-3">
<button class="w-100 btn btn-info btn-lg" id="clone-popover-btn">Clone</button>
</div>
<div class="col-md col-fluid py-3">
<button class="w-100 btn btn-outline-info shadow-none btn-lg">Setup Instructions</button>118 119 120 121 122 123
</div>
<div class="row flex-grow-1 align-items-center">
<div class="col-md col-fluid py-3">
<button class="w-100 btn btn-info btn-lg dropdown-toggle" id="clone-popover-btn">Clone</button>
</div>
<div class="col-md col-fluid py-3">
<button class="w-100 btn btn-outline-info shadow-none btn-lg">Setup Instructions</button>164 165 166 167 168
createClonePopover()
</script>
</body>
</html>
`164 165 166 167 168 169
createClonePopover()
</script>
<script src="${nav.rootPath()}frontend/main-frontend.bundle.js"></script>
</body>
</html>
`2 3 4 5 6 7
mkdir -p dist/templates
mkdir -p dist/css
mkdir -p dist/partial_templates
cp -r frontend dist/
cp templates/*.njk dist/templates
cp partial_templates/*.njk dist/partial_templates
cp -r vendor dist/2 3 4 5 6 7
mkdir -p dist/templates
mkdir -p dist/css
mkdir -p dist/partial_templates
cp frontend/top.js dist/frontend/top.js
cp templates/*.njk dist/templates
cp partial_templates/*.njk dist/partial_templates
cp -r vendor dist/18 19 20 21 22 23
"sass": "^1.94.0",
"ts-json-schema-generator": "^2.4.0",
"typedoc": "^0.28.13",
"typescript": "^5.8.3"
}
},
"node_modules/@gerrit0/mini-shiki": {18 19 20 21 22 23 24
"sass": "^1.94.0",
"ts-json-schema-generator": "^2.4.0",
"typedoc": "^0.28.13",
"typescript": "^5.8.3",
"webpack": "^5.103.0"
}
},
"node_modules/@gerrit0/mini-shiki": {512 513 514 515 516
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true
},
"node_modules/bootstrap": {
"version": "5.3.8",
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.8.tgz",512 513 514 515 516 517 518 519 520 521 522 523 524 525
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true
},
"node_modules/baseline-browser-mapping": {
"version": "2.9.7",
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.7.tgz",
"integrity": "sha512-k9xFKplee6KIio3IDbwj+uaCLpqzOwakOgmqzPezM0sFJlFKcg30vk2wOiAJtkTSfx0SSQDSe8q+mWA/fSH5Zg==",
"dev": true,
"bin": {
"baseline-browser-mapping": "dist/cli.js"
}
},
"node_modules/bootstrap": {
"version": "5.3.8",
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.8.tgz",568 569 570 571 572
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",568 569 570 571 572 573 574 575 576 577 578 579 580 581
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/chrome-trace-event": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz",
"integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==",
"dev": true,
"engines": {
"node": ">=6.0"
}
},
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",642 643 644 645 646
"dev": true,
"license": "MIT"
},
"node_modules/emoji-regex": {
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",642 643 644 645 646 647 648 649 650 651 652
"dev": true,
"license": "MIT"
},
"node_modules/electron-to-chromium": {
"version": "1.5.267",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz",
"integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==",
"dev": true
},
"node_modules/emoji-regex": {
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",649 650 651 652 653
"dev": true,
"license": "MIT"
},
"node_modules/entities": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666
"dev": true,
"license": "MIT"
},
"node_modules/enhanced-resolve": {
"version": "5.18.4",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.4.tgz",
"integrity": "sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==",
"dev": true,
"dependencies": {
"graceful-fs": "^4.2.4",
"tapable": "^2.2.0"
},
"engines": {
"node": ">=10.13.0"
}
},
"node_modules/entities": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",837 838 839 840 841
"uc.micro": "^2.0.0"
}
},
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854
"uc.micro": "^2.0.0"
}
},
"node_modules/loader-runner": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz",
"integrity": "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==",
"dev": true,
"engines": {
"node": ">=6.11.5"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/webpack"
}
},
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",882 883 884 885 886
"integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==",
"dev": true
},
"node_modules/micromatch": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",882 883 884 885 886 887 888 889 890 891 892
"integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==",
"dev": true
},
"node_modules/merge-stream": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
"integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
"dev": true
},
"node_modules/micromatch": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",896 897 898 899 900
"node": ">=8.6"
}
},
"node_modules/minimatch": {
"version": "10.0.3",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz",896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921
"node": ">=8.6"
}
},
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"dev": true,
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"dev": true,
"dependencies": {
"mime-db": "1.52.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/minimatch": {
"version": "10.0.3",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz",922 923 924 925 926
"node": ">=16 || 14 >=14.17"
}
},
"node_modules/node-addon-api": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz",922 923 924 925 926 927 928 929 930 931 932
"node": ">=16 || 14 >=14.17"
}
},
"node_modules/neo-async": {
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
"dev": true
},
"node_modules/node-addon-api": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz",929 930 931 932 933
"dev": true,
"optional": true
},
"node_modules/normalize-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",929 930 931 932 933 934 935 936 937 938 939
"dev": true,
"optional": true
},
"node_modules/node-releases": {
"version": "2.0.27",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz",
"integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==",
"dev": true
},
"node_modules/normalize-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",973 974 975 976 977
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/picomatch": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",973 974 975 976 977 978 979 980 981 982 983
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
"dev": true
},
"node_modules/picomatch": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",995 996 997 998 999
"node": ">=6"
}
},
"node_modules/readdirp": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008
"node": ">=6"
}
},
"node_modules/randombytes": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
"integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
"dev": true,
"dependencies": {
"safe-buffer": "^5.1.0"
}
},
"node_modules/readdirp": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",1017 1018 1019 1020 1021
"node": ">=0.10.0"
}
},
"node_modules/safe-stable-stringify": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz",1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041
"node": ">=0.10.0"
}
},
"node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
]
},
"node_modules/safe-stable-stringify": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz",1083 1084 1085 1086 1087
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",1092 1093 1094 1095 1096
"node": ">=0.10.0"
}
},
"node_modules/string-width": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106
"node": ">=0.10.0"
}
},
"node_modules/source-map-support": {
"version": "0.5.21",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
"integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
"dev": true,
"dependencies": {
"buffer-from": "^1.0.0",
"source-map": "^0.6.0"
}
},
"node_modules/string-width": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",3 4 5 6 7 8
"version": "0.0.1-alpha.1",
"main": "dist/main.js",
"scripts": {
"build": "npm run schemas && npm run ts && npm run docs && npm run css",
"ts": "./make.sh && npx tsc",
"schemas": "npx ts-json-schema-generator --path src/configTypes.ts --type ReposConfiguration > schemas/ReposConfiguration.json",
"docs": "npx typedoc src/configTypes.ts --readme none",3 4 5 6 7 8 9
"version": "0.0.1-alpha.1",
"main": "dist/main.js",
"scripts": {
"build": "npm run schemas && npm run ts && npm run frontend && npm run docs && npm run css",
"frontend": "(cd frontend && npx webpack)",
"ts": "./make.sh && npx tsc",
"schemas": "npx ts-json-schema-generator --path src/configTypes.ts --type ReposConfiguration > schemas/ReposConfiguration.json",
"docs": "npx typedoc src/configTypes.ts --readme none",20 21 22 23 24 25
"sass": "^1.94.0",
"ts-json-schema-generator": "^2.4.0",
"typedoc": "^0.28.13",
"typescript": "^5.8.3"
},
"dependencies": {
"ajv": "^8.17.1",20 21 22 23 24 25 26
"sass": "^1.94.0",
"ts-json-schema-generator": "^2.4.0",
"typedoc": "^0.28.13",
"typescript": "^5.8.3",
"webpack": "^5.103.0"
},
"dependencies": {
"ajv": "^8.17.1",23 24 25 26 27
window.location = `{{reposPath}}/${values[0]}/branches/${values[1]}/${values[2]}`
}
</script>
<script src="{{reposPath}}/frontend/main.js"></script>
</body>
</html>23 24 25 26 27
window.location = `{{reposPath}}/${values[0]}/branches/${values[1]}/${values[2]}`
}
</script>
<script src="{{reposPath}}/frontend/main-frontend.bundle.js"></script>
</body>
</html>148 149
.popover {
max-width: 100%;
}148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164
.popover {
max-width: 100%;
}
.branch-selector .dropdown-menu {
min-width: 220px;
}
.badge {
font-family: sans-serif;
}
.sortRadioButtons {
white-space: nowrap;
}
.sortRadioButton {
display: inline-block;
}