Change index.ts to a mithril template

4a9f3a0954d6e0879474f0429b1bce727309174d

Tucker McKnight <tucker@pangolin.lan> | Mon Jan 12 2026

Change index.ts to a mithril template

Adds needed mithril packages and changes the big string in index.ts
to use mithril. Nothing needs to be changed in main.ts.
js_templates/index.ts:1
Before
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
export default async (eleventyConfig: any) => {
  return (data: any) => {
    return `
      <!doctype html>
      <html lang="en">
        <head>
          <meta charset="utf-8">
          <meta name="viewport" content="width=device-width, initial-scale=1">
          <title>Repositories</title>
          <link rel="stylesheet" href="/css/design-board.css">
          <script src="${data.reposPath}/frontend/top.js"></script>
        </head>
        <body>
          <div class="container">
            <div class="row d-flex justify-content-end">
              <div class="col-auto pt-2">
                <div class="dropdown">
                  <button class="dropdown-toggle btn btn-bg" id="dark-mode-switch" type="button" data-bs-toggle="dropdown" aria-expanded="false">
                    <span>&#xFE0F;</span>
                  </button>
                  <ul class="dropdown-menu">
                    <li><button class="btn shadow-none" data-theme-pref="light" onclick="toggleDarkMode(this)"><span class="me-1">&#x1F31E;</span>Light</button></li>
                    <li><button class="btn shadow-none" data-theme-pref="dark" onclick="toggleDarkMode(this)"><span class="me-1">&#x1F319;</span>Dark</button></li>
                    <li><button class="btn shadow-none" data-theme-pref="auto" onclick="toggleDarkMode(this)"><span class="me-1">&#x1F5A5;&#xFE0F;</span>Match OS</button></li>
                  </ul>
                </div>
              </div>
            </div>
            <div class="row my-3">
              <div class="col">
                <h1>${data.reposConfig.defaultTemplateConfiguration?.allRepositoriesPageTitle || "All Repositories"}</h1>
              </div>
            </div>
            <div class="row">
              <div class="col d-flex flex-wrap">
                ${data.repos.map((repo) => {
                  return `
                    <div class="m-2 card bezel-gray flex-grow-1" style="flex-basis: 20rem;">
                      <div class="card-header">
                        <a class="card-title fs-5" href="${data.reposPath}/${slugify(repo.name)}/branches/${repo.defaultBranch}">${repo.name}</a>
                      </div>
                      <div class="card-body">
                        ${repo.description ? `<p class="card-text">${repo.description}</p>` : ''}
                      </div>
                      <div class="card-footer">
                        <a href="${data.reposPath}/${slugify(repo.name)}/branches/${repo.defaultBranch}" class="mx-1 my-2 btn btn-primary text-white">Go to site</a>
                        <button class="mx-1 my-2 btn btn-outline-secondary shadow-none dropdown-toggle clone-popover-btn" data-copy-text="${repo.cloneUrl}">Clone</button>
                        ${(data.reposConfig.repos[repo.name].defaultTemplateConfiguration?.homepageButtons || []).map((button) => {
                          return `<a class="mx-1 my-2 btn btn-outline-secondary shadow-none" href="${button.url}" ${button.newTab ? 'target="_blank"' : ''}>${button.text}${button.newTab ? ' <span>&#x29C9;</span>' : ''}</a>`
                        }).join('')}
                      </div>
                    </div>
                  `
                }).join('')}
              </div>
            </div>
          </div>
        </div>
        <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js" integrity="sha384-FKyoEForCGlyvwx9Hj09JcYn3nv7wiPVlz7YYwJrWVcXK/BmnVDxM+D2scQbITxI" crossorigin="anonymous"></script>
        <script src="${data.reposPath}/frontend/main-frontend.bundle.js"></script>
      </body>
    </html>
  `
After
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
import m from 'mithril'
import render from 'mithril-node-render'

export default (eleventyConfig: any) => {
  return async (data: any) => {
    return render([
      m.trust('<!doctype html>'),
      m('html', {lang: "en"}, [
        m('head', [
          m('meta', {charset:"utf-8"}),
          m('meta', {name: "viewport", content: "width=device-width, initial-scale=1"}),
          m('title', 'Repositories'),
          m('link', {rel: "stylesheet", href: "/css/design-board.css"}),
          m('script', {src: `${data.reposPath}/frontend/top.js`}),
        ]),
        m('body', [
          m('div', {class: "container"}, [
            m('div', {class: "row d-flex justify-content-end"}, [
              m('div', {class: "col-auto pt-2"}, [
                m('div', {class: "dropdown"}, [
                  m('button', {
                    class: "dropdown-toggle btn btn-bg",
                    id: "dark-mode-switch",
                    type: "button",
                    'data-bs-toggle': "dropdown",
                    'aria-expanded': "false"
                  }, m('span', m.trust('&#xFE0F;'))),
                  m('ul', {class: "dropdown-menu"}, [
                    m('li',
                      m('button', {class: "btn shadow-none", 'data-theme-pref': "light", onclick: "toggleDarkMode(this)"},
                        m('span', {class: "me-1"}, m.trust('&#x1F31E;')),
                        'Light'
                      )
                    ),
                    m('li',
                      m('button', {class: "btn shadow-none", 'data-theme-pref': "dark", onclick: "toggleDarkMode(this)"},
                        m('span', {class: "me-1"}, m.trust('&#x1F319;')),
                        'Dark'
                      )
                    ),
                    m('li',
                      m('button', {class: "btn shadow-none", 'data-theme-pref': "auto", onclick: "toggleDarkMode(this)"},
                        m('span', {class: "me-1"}, m.trust('&#x1F5A5;&#xFE0F;')),
                        'Match OS'
                      )
                    )
                  ])
                ])
              ])
            ]),
            m('div', {class: "row my-3"},
              m('div', {class: "col"},
                m('h1', data.reposConfig.defaultTemplateConfiguration?.allRepositoriesPageTitle || "All Repositories")
              )
            ),
            m('div', {class: "row"},
              m('div', {class: "col d-flex flex-wrap"},
                data.repos.map((repo) => {
                  return (
                    m('div', {class: "m-2 card bezel-gray flex-grow-1", style: "flex-basis: 20rem;"},
                      m('div', {class: "card-header"},
                        m('a', {class: "card-title fs-5", href: `${data.reposPath}/${slugify(repo.name)}/branches/${repo.defaultBranch}`}, repo.name)
                      ),
                      m('div', {class: "card-body"},
                        repo.description ? m('p', {class: "card-text"}, repo.description) : null
                      ),
                      m('div', {class: "card-footer"}, [
                        m('a', {
                          href: `${data.reposPath}/${slugify(repo.name)}/branches/${repo.defaultBranch}`,
                          class: "mx-1 my-2 btn btn-primary text-white"
                        }, 'Go to site'),
                        m('button', {
                          class: "mx-1 my-2 btn btn-outline-secondary shadow-none dropdown-toggle clone-popover-btn",
                          'data-copy-text': repo.cloneUrl
                        }, 'Clone'),
                        (data.reposConfig.repos[repo.name].defaultTemplateConfiguration?.homepageButtons || []).map((button) => {
                          return m('a', {
                            class: "mx-1 my-2 btn btn-outline-secondary shadow-none",
                            href: button.url,
                            target: button.newTab ? '_blank' : '_self'
                          }, [button.text, button.newTab ? [' ', m('span', m.trust('&#x29C9;'))] : null])
                        })
                      ])
                    )
                  )
                })
              )
            )
          ])
        ]),
        m('script', {
          src: "https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js",
          integrity: "sha384-FKyoEForCGlyvwx9Hj09JcYn3nv7wiPVlz7YYwJrWVcXK/BmnVDxM+D2scQbITxI",
          crossorigin: "anonymous"
        }),
        m('script', {src: `${data.reposPath}/frontend/main-frontend.bundle.js`}),
      ])
    ])
package-lock.json:12
Before
12
        "lodash": "^4.17.21"
After
12
        "lodash": "^4.17.21",
        "mithril": "^2.3.8",
        "mithril-node-render": "^3.0.2"
package-lock.json:1864
Before
1864
After
1864
    "node_modules/mithril": {
      "version": "2.3.8",
      "resolved": "https://registry.npmjs.org/mithril/-/mithril-2.3.8.tgz",
      "integrity": "sha512-za/Yo7qXEckjm5syrSfaaI9Utf4tCUT3T1IOIYqH6Lrj7G0OZuYYLAY9SV4ygoaAf0+CNqU92MBt+7pmo53JVQ=="
    },
    "node_modules/mithril-node-render": {
      "version": "3.0.2",
      "resolved": "https://registry.npmjs.org/mithril-node-render/-/mithril-node-render-3.0.2.tgz",
      "integrity": "sha512-8GZfFWW7h1YjO8s9fe+vHpWG3zacGIytQ/FBKxlynHlwknp1KLRHM6MtrtbMy72dVdQOiYw7wyfZXD0IrvpQOQ==",
      "peerDependencies": {
        "mithril": "^2.0.4"
      }
    },
package.json:30
Before
30
    "lodash": "^4.17.21"
After
30
    "lodash": "^4.17.21",
    "mithril": "^2.3.8",
    "mithril-node-render": "^3.0.2"