Turn some NJK templates into typescript files

88b515f196bbaeb8cc0cdcdc1768f0ba15622a50

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.
frontend/main.js:1
Before
0
1
2
import {branchesListItems} from '../dist/js_templates/repo.js'

const setCheckbox = window.setCheckbox
const currentTheme = window.currentTheme
After
0
1
2
import {branchesListItems} from '../dist/js_templates/common/htmlPage.js'

const setCheckbox = window.setCheckbox
const currentTheme = window.currentTheme
frontend/top.js:16
Before
15
16
17
18
19

20
    window['currentTheme'] = mode
  };

  window['currentTheme'] = localStorage.getItem("theme") || "auto"

⁣
  window['setMode'](window['currentTheme'])
After
15
16
17
18
19
20
21
    window['currentTheme'] = mode
  };

  // window['currentTheme'] = localStorage.getItem("theme") || "auto"
  window['currentTheme'] = "light"

  window['setMode'](window['currentTheme'])
js_templates/common/htmlPage.ts:55
Before
54
55
56
57
58
59
                      <a class="nav-link" href="${nav.rootPath()}">&larr; 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>
After
54
55
56
57
58
59
                      <a class="nav-link" href="${nav.rootPath()}">&larr; 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>
js_templates/common/htmlPage.ts:104
Before
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>
After
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>
js_templates/files.ts:1
Before

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>
  `
}
After
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>&#x1F4C1;</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>
⁣
⁣
  `
}
js_templates/repo.ts:1
Before
0
1
2
import { type ReposConfiguration } from '../src/configTypes.ts'
import { type Repository } from '../src/dataTypes.ts'

export default async (eleventyConfig: any, data: any) => {
After
0
1
i⁣
mport { type Repository } from '../src/dataTypes.ts'

export default async (eleventyConfig: any, data: any) => {
main.ts:9
Before
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)
After
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)
main.ts:113
Before
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++) {
After
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++) {
main.ts:149
Before
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;
    }
After
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;
    }
main.ts:286
Before
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",
After
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",
main.ts:344
Before
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",
After
343
344
345
346

347
348
349
350
351
    }
  )

  // FILES.TS
⁣
  eleventyConfig.addTemplate(
    'repos/files.11ty.js',
    htmlPage(reposConfiguration, eleventyConfig, filesJsTemplate),
    {
      pagination: {
        data: "branches",
main.ts:379
Before
After
main.ts:429
Before
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: {
After
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: {
main.ts:446
Before
445
446
447

448
449
          return branch.name === data.branch.branchName
        }),
      },
⁣
    }
  )
After
445
446
447
448
449
450
          return branch.name === data.branch.branchName
        }),
      },
      navTab: "home"
    }
  )
scss/design-board.scss:11
Before
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;
After
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;
scss/design-board.scss:30
Before
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 {
After
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 {
src/dataTypes.ts:27
Before
26
27





    }>
  }>,
⁣
⁣
⁣
⁣
⁣
⁣
}
After
26
27
28
29
30
31
32
33
    }>
  }>,
}

export type SortedFileList = Array<{
  name: string,
  fullPath: string,
  isDirectory: boolean,
}>