[WIP] starting work on a tags page

eafb15f67e96d4be72e6e52aaa3146a55e85cebd

Tucker McKnight <tmcknight@instructure.com> | Sun Mar 29 2026

[WIP] starting work on a tags page

Currently it's mostly a copy-paste of the branches page, with
parts removed that tags don't have.

Still need to do:
- Changes all "branches" links to have either "branch" or "tag" in the
  URL, since the site is now being generated for all branches and all tags.
js_templates/branches.ts:18
Before
17
18
19
20
21
22
      return branch.repoName === data.flatRel.repoName ?
        m('li', [
          m('a', {
            href: `${data.reposPath}/${slugify(branch.repoName)}/branches/${slugify(branch.relName)}/branches`
          }, branch.relName),
          branch.relName === data.flatRel.relName
            ? m('div', {class: "badge rounded-pill bg-secondary mx-1"}, 'current') : null,
After
17
18
19
20
21
22
      return branch.repoName === data.flatRel.repoName ?
        m('li', [
          m('a', {
            href: `${data.reposPath}/${slugify(branch.repoName)}/branch/${slugify(branch.relName)}/branches`
          }, branch.relName),
          branch.relName === data.flatRel.relName
            ? m('div', {class: "badge rounded-pill bg-secondary mx-1"}, 'current') : null,
js_templates/commit.ts:56
Before
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)}/branches/${slugify(data.patchInfo.branchName)}/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"}, [
After
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)}/branch/${slugify(data.patchInfo.branchName)}/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"}, [
js_templates/common/htmlPage.ts:22
Before
21
22
23
24
25
26
27
28
29
30
31
32
33
34
      href: nav.repoBranchHome(branch.name) + '/' + data.nav.path,
      date: repo.commits.get(branch.sha).date.toISOString(),
    }
    }).concat(repo.tags.map((tag) => {
      return {
        name: tag.name,
        href: "tbd",
        date: repo.commits.get(tag.sha).date.toISOString(),
      }
    }))

    console.log(branchesWithHrefs)

  return {
    rootPath: nav.rootPath(),
After
21
22
23
24
25
26
27
28
29
30
31


32
      href: nav.repoBranchHome(branch.name) + '/' + data.nav.path,
      date: repo.commits.get(branch.sha).date.toISOString(),
    }
  }).concat(repo.tags.map((tag) => {
    return {
      name: tag.name,
      href: nav.repoTagHome(tag.name) + '/' + data.nav.path,
      date: repo.commits.get(tag.sha).date.toISOString(),
    }
  }))

⁣
⁣
  return {
    rootPath: nav.rootPath(),
js_templates/common/htmlPage.ts:118
Before
117
118
119
120



121
              ),
              m('li', {class: "nav-item"},
                m('a', {class: `nav-link ${data.navTab === 'branches' ? 'active' : ''}`, href: nav.repoCurrentBranchBranches()}, 'Branches')
              )
⁣
⁣
⁣
            ])
          )
After
117
118
119
120
121
122
123
124
              ),
              m('li', {class: "nav-item"},
                m('a', {class: `nav-link ${data.navTab === 'branches' ? 'active' : ''}`, href: nav.repoCurrentBranchBranches()}, 'Branches')
              ),
              m('li', {class: "nav-item"},
                m('a', {class: `nav-link ${data.navTab === 'tags' ? 'active' : ''}`, href: nav.repoCurrentBranchTags()}, 'Tags')
              )
            ])
          )
js_templates/common/htmlPage.ts.orig:0
Before







































































































































































































































































⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
After
-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
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
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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
import m from 'mithril'
import { type ReposConfiguration } from '../../src/configTypes.ts'
import { type Repository } from '../../src/dataTypes.ts'
import { NavHelper } from '../helpers/nav.ts'
import branchesListItems from './branchesListItems.ts'

export default async (reposConfig: ReposConfiguration, eleventyConfig: any, data: any, pageContent: any) => {
  // this still necessary?
  if (data.currentRepo === '') {
    return
  }

  const repo: Repository = data.currentRepo
  const branch: Repository['branches'][0] = data.currentBranch

  const slugify = eleventyConfig.getFilter("slugify")
  const nav = NavHelper(reposConfig, slugify, repo.name, branch.name)

  const branchesWithHrefs = repo.branches.map((branch) => {
    return {
      name: branch.name,
      href: nav.repoBranchHome(branch.name) + '/' + data.nav.path,
      date: repo.commits.get(branch.head).date.toISOString(),
    }
  })

<<<<<<< HEAD
    const repo: Repository = data.currentRepo
    const branch: Repository['branches'][0] = data.currentBranch

    const slugify = eleventyConfig.getFilter("slugify")
    const nav = NavHelper(reposConfig, slugify, repo.name, branch.name)

    const branchesWithHrefs = repo.branches.map((branch) => {
      return {
        name: branch.name,
        href: nav.repoBranchHome(branch.name) + '/' + data.nav.path,
        date: repo.commits.get(branch.head).date.toISOString(),
      }
    }).concat(repo.tags.map((tag) => {
      return {
        name: tag.name,
        href: "tbd",
        date: repo.commits.get(tag.sha).date.toISOString(),
      }
    }))

    console.log(branchesWithHrefs)

    return `
      <!doctype html>
      <html lang="en">
        <head>
          <meta charset="utf-8">
          <meta name="viewport" content="width=device-width, initial-scale=1">
          <title>${repo.name}</title>
          <link rel="stylesheet" href="/css/design-board.css">
          <script>
            window.branchesWithHrefs = ${JSON.stringify(branchesWithHrefs)};
            window.defaultBranch = "${repo.defaultBranch}";
            window.currentBranch = "${branch.name}";
            window.cloneUrl = "${repo.cloneUrl}";
          </script>
          <script src="${nav.rootPath()}frontend/top.js"></script>
          <link rel="stylesheet" id="prism-theme" type="text/css" href="${data.reposPath}/vendor/prism.css" />
        </head>
        <body>
          <div class="container">
            <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" 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>
                      <div class="branch-selector dropdown-center d-inline-block">
                        <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">
                          ${branch.name}
                        </button>
                        <div class="dropdown-menu">
                          <form class="mx-3 my-1">
                            <input type="text" class="form-control" id="dropdownBranchSearch" placeholder="Search branches...">
                            <div>
                              <div class="row mt-3">
                                <div class="col">
                                  <label class="form-label">Sort by:</label>
                                </div>
                              </div>
                              <div class="sortRadioButtons">
                                <div class="sortRadioButton pe-1">
                                  <input class="form-check-input sort-filter" type="radio" name="branchSort" value='date' id="branchSortByDate" checked>
                                  <label class="form-check-label" for="branchSortByDate">
                                    Last commit
                                  </label>
                                </div>
                                <div class="sortRadioButton ps-1">
                                  <input class="form-check-input sort-filter" type="radio" name="branchSort" value='name' id="branchSortByName">
                                  <label class="form-check-label" for="branchSortByName">
                                    Name
                                  </label>
                                </div>
                              </div>
                            </div>
                          </form>
                          <div class="dropdown-divider"></div>
                          <div id="dropdown-branches-results" class="dropdown-branches">
                            ${branchesListItems(branchesWithHrefs, repo.defaultBranch, branch.name, 'date')}
                          </div>
                        </div>
                      </div>
                    </li>
                  </ul>
                </nav>
              </div>
              <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">
              <div class="col">
                <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 ${data.navTab === 'commits' ? 'active' : ''}" href="${nav.repoCurrentBranchCommits()}">Commits</a>
                    </li>
                    <li class="nav-item">
                      <a class="nav-link ${data.navTab === 'branches' ? 'active' : ''}" href="${nav.repoCurrentBranchBranches()}">Branches</a>
                    </li>
                  </ul>
                </nav>
              </div>
            </div>

            <div class="row">
              <div class="col-12">
                ${await pageContent(eleventyConfig, data, nav)}
              </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="${nav.rootPath()}frontend/main-frontend.bundle.js"></script>
        </body>
      </html>
      `
=======
  return {
    rootPath: nav.rootPath(),
    pageTitle: repo.name,
    additionalHeadContent: [
      m('script', m.trust(`
        window.branchesWithHrefs = ${JSON.stringify(branchesWithHrefs)};
        window.defaultBranch = "${repo.defaultBranch}";
        window.currentBranch = "${branch.name}";
        window.cloneUrl = "${repo.cloneUrl}";
      `)),
      m('link', {
        rel: "stylesheet",
        id: "prism-theme",
        type: "text/css",
        href: `${data.reposPath}/vendor/prism.css`
      }),
    ],
    navbarContent: m('nav', {class: "navbar navbar-expand"}, [
      m('ul', {class: "navbar-nav flex-wrap"}, [
        m('li', {class: "nav-item"},
          m('a', {
            class: "nav-link",
            href: nav.rootPath()
          }, m.trust('&#10094; Repos'))
        ),
        m('li', {class: "nav-item"},
          m('a', {
            class: "nav-link",
            href: nav.repoCurrentBranchHome()
          }, repo.name)
        ),
        m('li', {class: "nav-item"}, [
          m('span', {class: "nav-link d-inline-block"}, 'Branch:'),
          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"
            }, branch.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', [
                  m('div', {class: "row mt-3"},
                    m('div', {class: "col"},
                      m('label', {class: "form-label"}, 'Sort by:')
                    )
                  ),
                  m('div', {class: "sortRadioButtons"}, [
                    m('div', {class: "sortRadioButton pe-1"}, [
                      m('input', {class: "form-check-input sort-filter", type: "radio", name: "branchSort", value: 'date', id: "branchSortByDate", checked: true}),
                      m('label', {class: "form-check-label", for: "branchSortByDate"}, 'Last commit')
                    ]),
                    m('div', {class: "sortRadioButton ps-1"}, [
                      m('input', {class: "form-check-input sort-filter", type: "radio", name: "branchSort", value: 'name', id: "branchSortByName"}),
                      m('label', {class: "form-check-label", for: "branchSortByName"}, 'Name')
                    ])
                  ])
                ])
              ]),
              m('div', {class: "dropdown-divider"}),
              m('div', {id: "dropdown-branches-results", class: "dropdown-branches"},
                branchesListItems(branchesWithHrefs, repo.defaultBranch, branch.name, 'date')
              )
            ])
          ])
        ])
      ])
    ]),
    pageContent: [
      m('div', {class: "row"},
        m('div', {class: "col"},
          m('nav', {class: "navbar navbar-expand"},
            m('ul', {class: "main-nav navbar-nav flex-wrap"}, [
              m('li', {class: "nav-item"},
                m('a', {class: `nav-link ${data.navTab === 'home' ? 'active' : ''}`, 'aria-current': "page", href: nav.repoCurrentBranchHome()}, 'Home')
              ),
              m('li', {class: "nav-item"},
                m('a', {class: `nav-link ${data.navTab === 'files' ? 'active' : ''}`, href: nav.repoCurrentBranchFiles()}, 'Files')
              ),
              m('li', {class: "nav-item"},
                m('a', {class: `nav-link ${data.navTab === 'commits' ? 'active' : ''}`, href: nav.repoCurrentBranchCommits()}, 'Commits')
              ),
              m('li', {class: "nav-item"},
                m('a', {class: `nav-link ${data.navTab === 'branches' ? 'active' : ''}`, href: nav.repoCurrentBranchBranches()}, 'Branches')
              )
            ])
          )
        )
      ),
      m('div', {class: "row"},
        m('div', {class: "col-12"},
          await pageContent
        )
      ),
    ]
>>>>>>> main
  }
}
js_templates/file.ts:31
Before
30
31
32
33
34
35
      m('div', {class: "col"},
        m('h3', [
          m('span', {class: "bezel-gray p-1 my-1 d-inline-block"},
            m('a', {href: `${data.reposPath}/${fileInfo.repoName}/branches/${fileInfo.branchName}/files`}, './')
          ),
          fileInfo.file.split('/').map((dir, index, arr) => {
            if (index === arr.length - 1) {
After
30
31
32
33
34
35
      m('div', {class: "col"},
        m('h3', [
          m('span', {class: "bezel-gray p-1 my-1 d-inline-block"},
            m('a', {href: `${data.reposPath}/${fileInfo.repoName}/branch/${fileInfo.branchName}/files`}, './')
          ),
          fileInfo.file.split('/').map((dir, index, arr) => {
            if (index === arr.length - 1) {
js_templates/file.ts:40
Before
39
40
41
42
43
44
            else {
              return m('span', {class: "bezel-gray p-1 my-1 d-inline-block"}, [
                m('a', {
                  href: `${data.reposPath}/${fileInfo.repoName}/branches/${fileInfo.branchName}/files/${arr.slice(0, index + 1).map((part) => slugify(part)).join('/')}.html`}, `${dir}/`)
              ])
            }
          })
After
39
40
41
42
43
44
            else {
              return m('span', {class: "bezel-gray p-1 my-1 d-inline-block"}, [
                m('a', {
                  href: `${data.reposPath}/${fileInfo.repoName}/branch/${fileInfo.branchName}/files/${arr.slice(0, index + 1).map((part) => slugify(part)).join('/')}.html`}, `${dir}/`)
              ])
            }
          })
js_templates/file.ts:55
Before
54
55
56
57
58
59
              return m('li', {class: 'list-group-item'}, [
                dir.isDirectory ? m('span', m.trust('&#x1F4C1;&nbsp;')) : null,
                m('a', {
                  href: `${data.reposPath}/${slugify(fileInfo.repoName)}/branches/${slugify(fileInfo.branchName)}/files/${dir.fullPath.split('/').map((pathPart) => {
                    return pathPart.split('.').map((subPart) => {
                      return slugify(subPart)
                    }).join('.')
After
54
55
56
57
58
59
              return m('li', {class: 'list-group-item'}, [
                dir.isDirectory ? m('span', m.trust('&#x1F4C1;&nbsp;')) : null,
                m('a', {
                  href: `${data.reposPath}/${slugify(fileInfo.repoName)}/branch/${slugify(fileInfo.branchName)}/files/${dir.fullPath.split('/').map((pathPart) => {
                    return pathPart.split('.').map((subPart) => {
                      return slugify(subPart)
                    }).join('.')
js_templates/file.ts:71
Before
70
71
72
73
74
75
        m('div', {class: "row my-3"},
          m('div', {class: "col"},
            m('span', m('a', {
              href: `${data.reposPath}/${slugify(fileInfo.repoName)}/branches/${slugify(fileInfo.branchName)}/raw/${fileInfo.file.split('.').map(filePart => slugify(filePart)).join('.')}`}, 'View raw file'))
          )
        ),
        (fileInfo.file.endsWith(".md") ?
After
70
71
72
73
74
75
        m('div', {class: "row my-3"},
          m('div', {class: "col"},
            m('span', m('a', {
              href: `${data.reposPath}/${slugify(fileInfo.repoName)}/branch/${slugify(fileInfo.branchName)}/raw/${fileInfo.file.split('.').map(filePart => slugify(filePart)).join('.')}`}, 'View raw file'))
          )
        ),
        (fileInfo.file.endsWith(".md") ?
js_templates/file.ts:126
Before
125
126
127
128
129
130
                m('code', {style: "white-space: pre;"}, [
                  m('pre', {class: "language-text"}, m.trust(
                    currentRel.fileList.get(fileInfo.file).fileInfo.blameLines.map((annotation) => {
                      return `<a href="${data.reposPath}/${slugify(fileInfo.repoName)}/branches/${slugify(fileInfo.branchName)}/commits/${annotation.sha}">${annotation.sha.substr(0, 6)}</a> ${annotation.author}`
                    }).join('\n')))
                ])
              ),
After
125
126
127
128
129
130
                m('code', {style: "white-space: pre;"}, [
                  m('pre', {class: "language-text"}, m.trust(
                    currentRel.fileList.get(fileInfo.file).fileInfo.blameLines.map((annotation) => {
                      return `<a href="${data.reposPath}/${slugify(fileInfo.repoName)}/branch/${slugify(fileInfo.branchName)}/commits/${annotation.sha}">${annotation.sha.substr(0, 6)}</a> ${annotation.author}`
                    }).join('\n')))
                ])
              ),
js_templates/file.ts.orig:0
Before















































































































































































⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
After
-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
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
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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
import m from 'mithril'
<<<<<<< HEAD
import render from 'mithril-node-render'
import { type Repository } from '../src/dataTypes.ts'
import { type FlatFileEntry } from '../src/flatFiles.ts'
type Branch = Repository['branches'][0]
=======
import htmlPage from './common/htmlPage.ts'
>>>>>>> main

export default async (reposConfig: any, eleventyConfig: any, data: any) => {
  const isDirectory = eleventyConfig.getFilter("isDirectory")
  const topLevelFilesOnly = eleventyConfig.getFilter("topLevelFilesOnly")
  const getDirectoryContents = eleventyConfig.getFilter("getDirectoryContents")
  const getRelativePath = eleventyConfig.getFilter("getRelativePath")
  const slugify = eleventyConfig.getFilter("slugify")
  const lineNumbers = eleventyConfig.getFilter("lineNumbers")
  const highlightCode = eleventyConfig.getFilter("highlightCode")
  const languageExtension = eleventyConfig.getFilter("languageExtension")
  const renderContentIfAvailable = eleventyConfig.getFilter("renderContentIfAvailable")

<<<<<<< HEAD
  const currentBranch: Branch = data.currentBranch
  const fileInfo: FlatFileEntry = data.fileInfo

  return render([
=======
  const pageContent = [
>>>>>>> main
    m('div', {class: "row mt-3 mb-1"},
      m('div', {class: "col"},
        m('p', [
          'Files snapshot from ',
          m('span', {class: "font-monospace"}, fileInfo.branchName)
        ])
      )
    ),
    m('div', {class: "directory-buttons row"},
      m('div', {class: "col"},
        m('h3', [
<<<<<<< HEAD
          m('span', {class: "bezel-gray p-1"},
            m('a', {href: `${data.reposPath}/${fileInfo.repoName}/branches/${fileInfo.branchName}/files`}, './')
=======
          m('span', {class: "bezel-gray p-1 my-1 d-inline-block"},
            m('a', {href: `${data.reposPath}/${data.fileInfo.repoName}/branches/${data.fileInfo.branchName}/files`}, './')
>>>>>>> main
          ),
          fileInfo.file.split('/').map((dir, index, arr) => {
            if (index === arr.length - 1) {
              return m('span', {class: "px-2 my-1 d-inline-block"}, dir)
            }
            else {
              return m('span', {class: "bezel-gray p-1 my-1 d-inline-block"}, [
                m('a', {
                  href: `${data.reposPath}/${fileInfo.repoName}/branches/${fileInfo.branchName}/files/${arr.slice(0, index + 1).map((part) => slugify(part)).join('/')}.html`}, `${dir}/`)
              ])
            }
          })
        ])
      )
    ),
    (isDirectory(fileInfo.file, fileInfo.repoName, fileInfo.branchName) ?
      m('div', {class: "row"},
        m('div', {class: "col"},
          m('ul', {class: "list-group"},
            topLevelFilesOnly(getDirectoryContents(fileInfo.repoName, fileInfo.branchName, fileInfo.file), fileInfo.file + '/').map((dir) => {
              return m('li', {class: 'list-group-item'}, [
                dir.isDirectory ? m('span', m.trust('&#x1F4C1;&nbsp;')) : null,
                m('a', {
                  href: `${data.reposPath}/${slugify(fileInfo.repoName)}/branches/${slugify(fileInfo.branchName)}/files/${dir.fullPath.split('/').map((pathPart) => {
                    return pathPart.split('.').map((subPart) => {
                      return slugify(subPart)
                    }).join('.')
                  }).join('/')}.html`},
                  getRelativePath(fileInfo.file, dir.name)
                )
              ])
            })
          )
        )
      )
    : [
        m('div', {class: "row my-3"},
          m('div', {class: "col"},
            m('span', m('a', {
              href: `${data.reposPath}/${slugify(fileInfo.repoName)}/branches/${slugify(fileInfo.branchName)}/raw/${fileInfo.file.split('.').map(filePart => slugify(filePart)).join('.')}`}, 'View raw file'))
          )
        ),
        (fileInfo.file.endsWith(".md") ?
          [
            m('div', {class: "row my-2"},
              m('div', {class: "col"},
                m('div', {class: "form-check form-switch"}, [
                  m('input', {
                    class: "form-check-input",
                    type: "checkbox",
                    role: "switch",
                    id: "showRenderedContent",
                    checked: true
                  }),
                  m('label', {
                    class: "form-check-label",
                    for: "showRenderedContent"
                  }, 'Show rendered markdown')
                ])
              )
            ),
            m('div', {class: "row rendered-content"},
              m('div', {class: "col"},
                m.trust(await renderContentIfAvailable(
                  currentBranch.fileList.get(fileInfo.file).fileInfo.contents
                ))
              )
            )
          ]
        : null),
        m('div', {class: `row code-content ${fileInfo.file.endsWith('.md') ? 'd-none' : ''}`},
          m('div', {class: "col"}, [
            m('div', {class: "row"},
              m('div', {class: "col-auto"},
                m('div', {class: "form-check form-switch"}, [
                  m('input', {class: "form-check-input", type: "checkbox", role: "switch", id: "showLastTouch"}),
                  m('label', {class: "form-check-label", for: "showLastTouch"}, 'Show last line change'),
                ])
              )
            ),
            m('div', {class: "row"}, [
              m('div', {class: "col-auto p-0"},
                m('code', {style: "white-space: pre;"},
                  m('pre', {class: "language-text"},
                    lineNumbers(currentBranch.fileList.get(fileInfo.file).fileInfo.contents).map((lineNumber) => {
                      return lineNumber
                    }).join('\n')
                  )
                )
              ),
              m('div', {id: "annotations", class: "col-auto d-none p-0"},
                m('code', {style: "white-space: pre;"}, [
                  m('pre', {class: "language-text"}, m.trust(
                    currentBranch.fileList.get(fileInfo.file).fileInfo.blameLines.map((annotation) => {
                      return `<a href="${data.reposPath}/${slugify(fileInfo.repoName)}/branches/${slugify(fileInfo.branchName)}/commits/${annotation.sha}">${annotation.sha.substr(0, 6)}</a> ${annotation.author}`
                    }).join('\n')))
                ])
              ),
              m('div', {class: "col overflow-scroll p-0"},
                m('code', m.trust(highlightCode(
                  currentBranch.fileList.get(fileInfo.file).fileInfo.contents,
                  languageExtension(
                    fileInfo.file,
                    fileInfo.repoName
                  )
                )))
              )
            ])
          ])
        ),
        m('script', {type: "text/javascript"},
          `
          const toggleLastTouch = (event) => {
            const isOn = event.target.checked
            const annotations = document.getElementById("annotations")
            if (isOn) {
              annotations.classList.remove("d-none")
            } else {
              annotations.classList.add("d-none")
            }
          }

          document.getElementById("showLastTouch")?.addEventListener('click', toggleLastTouch)
          `
        )
      ]
    )
  ]

  return await htmlPage(reposConfig, eleventyConfig, data, pageContent)
}
js_templates/files.ts:27
Before
26
27
28
29
30
31
      return m('li', {class: 'list-group-item'}, [
        file.isDirectory ? m.trust('<span>&#x1F4C1;&nbsp;</span>') : null,
        m('a', {
          href: `${data.reposPath}/${slugify(data.flatRel.repoName)}/branches/${slugify(data.flatRel.relName)}/files/${
            file.fullPath.split('/')
            .map((pathPart) => {
              return pathPart.split('.').map((subPart) => {
After
26
27
28
29
30
31
      return m('li', {class: 'list-group-item'}, [
        file.isDirectory ? m.trust('<span>&#x1F4C1;&nbsp;</span>') : null,
        m('a', {
          href: `${data.reposPath}/${slugify(data.flatRel.repoName)}/branch/${slugify(data.flatRel.relName)}/files/${
            file.fullPath.split('/')
            .map((pathPart) => {
              return pathPart.split('.').map((subPart) => {
js_templates/helpers/nav.ts:7
Before
6
7
8
9
10

11
  // every page URL. E.g. all of them start with 'repos/my-repo-name'
  // or 'repos/my-repo-name/branches.'
  const repoBasePath = `${reposPath}/${slugify(repoName)}`
  const rootBasePathBranches = `${repoBasePath}/branches`

⁣
  const currentBranchPath = `${rootBasePathBranches}/${slugify(branchName)}`
  const repoBranchCommitsBase = `${currentBranchPath}/commits/`
After
6
7
8
9
10
11
12
  // every page URL. E.g. all of them start with 'repos/my-repo-name'
  // or 'repos/my-repo-name/branches.'
  const repoBasePath = `${reposPath}/${slugify(repoName)}`
  const rootBasePathBranches = `${repoBasePath}/branch`
  const rootBasePathTags = `${repoBasePath}/tag`

  const currentBranchPath = `${rootBasePathBranches}/${slugify(branchName)}`
  const repoBranchCommitsBase = `${currentBranchPath}/commits/`
js_templates/helpers/nav.ts:30
Before
29
30
31



32
33
34
35
36
37


38
39
    repoBranchHome: (branchName: string) => {
      return `${rootBasePathBranches}/${slugify(branchName)}`
    },
⁣
⁣
⁣
    repoCurrentBranchFiles: () => {
      return `${currentBranchPath}/files`
    },
    repoBranchCommitsBase: () => repoBranchCommitsBase,
    repoCurrentBranchCommits: () => `${repoBranchCommitsBase}page1`,
    repoCurrentBranchBranches: () => `${currentBranchPath}/branches`,
⁣
⁣
    repoCurrentBranchRssFeed: () => `${currentBranchPath}/commits.xml`,
    homepageButtons: reposConfig.repos[repoName].defaultTemplateConfiguration?.homepageButtons || []
  }
After
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
    repoBranchHome: (branchName: string) => {
      return `${rootBasePathBranches}/${slugify(branchName)}`
    },
    repoTagHome: (tagName: string) => {
      return `${rootBasePathTags}/${slugify(tagName)}`
    },
    repoCurrentBranchFiles: () => {
      return `${currentBranchPath}/files`
    },
    repoBranchCommitsBase: () => repoBranchCommitsBase,
    repoCurrentBranchCommits: () => `${repoBranchCommitsBase}page1`,
    repoCurrentBranchBranches: () => `${currentBranchPath}/branches`,
    // TODO: change these "currentBranch"es to "currentRel"s
    repoCurrentBranchTags: () => `${currentBranchPath}/tags`,
    repoCurrentBranchRssFeed: () => `${currentBranchPath}/commits.xml`,
    homepageButtons: reposConfig.repos[repoName].defaultTemplateConfiguration?.homepageButtons || []
  }
js_templates/index.ts:16
Before
15
16
17
18
19
20
21
22
23
24
25
26
27
          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: "ms-0 me-2 my-2 btn btn-primary text-white"
                }, 'Go to site'),
                m('button', {
After
15
16
17
18
19
20
21
22
23
24
25
26
27
          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)}/branch/${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)}/branch/${repo.defaultBranch}`,
                  class: "ms-0 me-2 my-2 btn btn-primary text-white"
                }, 'Go to site'),
                m('button', {
js_templates/repo.ts.orig:0
Before































































































































⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
After
-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
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
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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
import m from 'mithril'
import { type Repository } from '../src/dataTypes.ts'
import { NavHelper } from './helpers/nav.ts'
import htmlPage from './common/htmlPage.ts'

export default async (reposConfig: any, eleventyConfig: any, data: any) => {
  const repo: Repository = data.currentRepo
  const branch: Repository['branches'][0] = data.currentBranch
  const renderContentIfAvailable = eleventyConfig.getFilter("renderContentIfAvailable")
  const slugify = eleventyConfig.getFilter("slugify")
  const getReadMe = eleventyConfig.getFilter("getReadMe")
  const latestCommit = repo.commits.get(branch.head)
  const latestCommitMessage = latestCommit.message.length > 72
    ? latestCommit.message.split('\n')[0].substr(0, 72) + '...'
    : latestCommit.message

<<<<<<< HEAD
  const languageCounts = Array.from(branch.fileList.keys()).reduce((counts, currentFile) => {
=======
  const nav = NavHelper(reposConfig, slugify, repo.name, branch.name)

  const languageCounts = branch.fileList.reduce((counts, currentFile) => {
>>>>>>> main
    const fileParts = currentFile.split(".")
    const fileExtension = fileParts[fileParts.length - 1]
// todo: add more ignoreable extensions or specific files
// (like package-lock.json). Allow glob patterns?
    if (fileExtension === 'gitignore') {
      return counts
    }

    counts.set(fileExtension, (counts.get(fileExtension) + 1) || 1)
    return counts
  }, new Map<string, number>())

  // todo: this is probably broken for repos that use fewer than 6 languages
  let languagePercentages: Array<[string, number]> = []
  const total = Array.from(branch.fileList.keys()).length

  for (const entry of languageCounts) {
    languagePercentages.push([entry[0], entry[1] / total])
  }
  languagePercentages.sort((a, b) => {
    return b[1] - a[1]
  })
  const topLanguagePercentages = languagePercentages.slice(0, 5)
  const otherLanguagePercent = languagePercentages.slice(6, languagePercentages.length - 6).reduce((sum, current) => {
    return sum + current[1]
  }, 0)

  const largestPercent = Math.max(...topLanguagePercentages.map(tuple => tuple[1]), otherLanguagePercent)

  const readmeContent = await renderContentIfAvailable(await getReadMe(repo.name, branch.name), branch.name)

  const pageContent = [
    m('div', {class: "row"}, [
      m('div', {class: "col"}, [
        m('div', {class: "px-4 pt-3 bezel-header"}, [
          m('div', {class: "row"}, [
            m('div', {class: "col-12 col-lg-6"}, [
              m('h1', {class: "display-3 text-white"},
                m('em', repo.name)
              ),
              repo.description
                ? m('p', {class: "text-white fs-4 fw-light"}, repo.description)
                : null
            ]),
            m('div', {class: "col-12 col-lg-6 d-flex flex-column justify-content-around"}, [
              m('div', {class: "row flex-grow-1 align-items-stretch language-percent-row"}, [
                m('div', {class: "col-12 d-flex align-items-stretch"}, [
                  m('div', {class: "language-cols d-flex flex-grow-1 flex-nowrap"}, topLanguagePercentages.map((percentTuple) => {
                    return m('div', {class: 'language-col flex-grow-1 overflow-hidden'}, [
                      m('div', {class: "language-name text-light font-monospace"}, percentTuple[0]),
                      m('div', {class: "language-percent", style: `flex-grow: ${percentTuple[1] / largestPercent};`})
                    ])
                  }).concat([m('div', {class: 'language-col flex-grow-1 overflow-hidden'},
                    m('div', {class: "language-name text-light font-monospace"}, 'other'),
                      m('div', {class: "language-percent", style: `flex-grow: ${otherLanguagePercent / largestPercent};`})
                  )])),
                ])
              ]),
              m('div', {class: "row align-items-center"}, [
                m('div', {class: "col-12"}, [
                  m('div', {class: "header-button-container"}, [
                    m('button', {class: "btn btn-info btn-lg dropdown-toggle clone-popover-btn"}, 'Clone'),
                    nav.homepageButtons.map((buttonConfig) => {
                      return m('a', {
                        class: "btn btn-outline-info btn-lg shadow-none",
                        href: buttonConfig.url,
                        target: buttonConfig.newTab ? "_blank" : "_self"
                      }, m.trust(buttonConfig.text + `${buttonConfig.newTab ? ' <span>&#x29C9;</span>' : ''}`))
                    })
                  ])
                ])
              ])
            ])
          ]),
          m('noscript',
            m('div', {class: "row mt-2"},
              m('div', {class: "col"},
                m('p', {class: "font-monospace text-white"},
                  `Clone URL: ${repo.cloneUrl}`
                )
              )
            )
          ),
          m('div', {class: "row mt-2"}, [
            m('div', {class: "col"}, [
              m('p', {class: "font-monospace text-white mb-2 d-inline-block"}, [
                m('a', {class: "btn btn-outline-info badge shadow-none me-2", href: nav.repoCurrentBranchRssFeed()}, [
                  m('img', {src: `${nav.rootPath()}frontend/img/rss-icon.svg`, style: "width: 11px; margin-right: 5px;"}),
                  'RSS'
                ]),
                `Latest commit: ${latestCommit.date.toDateString()} `,
                m('a', {class: "fw-bold link-info", href: `${nav.repoBranchCommitsBase()}${latestCommit.hash}`}, latestCommit.hash.substr(0, 6)),
                ' ',
                latestCommitMessage,
              ])
            ])
          ])
        ])
      ])
    ]),
    m('div', {class: "row my-4 mx-1"},
      m('div', {class: "col readme"}, m.trust(readmeContent))
    )
  ]

  return await htmlPage(reposConfig, eleventyConfig, data, pageContent)
}
js_templates/tags.ts:0
Before



































⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
After
-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
31
32
33
34
35
import m from 'mithril'
import {NavHelper} from './helpers/nav.ts'
import htmlPage from './common/htmlPage.ts'

export default async (
  reposConfig: any,
  eleventyConfig: any,
  data: any,
) => {
  const slugify = eleventyConfig.getFilter("slugify")
  const nav = NavHelper(reposConfig, slugify, data.flatRel.repoName, data.flatRel.relName)

  const tags = data.flatRels.filter(rel => rel.type === "tag")

  const branchCards = (tags) => {
    return m('ul', tags.map((tag) => {
      return tag.repoName === data.flatRel.repoName ?
        m('li', [
          m('a', {
            href: `${data.reposPath}/${slugify(tag.repoName)}/tag/${slugify(tag.relName)}/tags`
          }, tag.relName),
          tag.relName === data.flatRel.relName
            ? m('div', {class: "badge rounded-pill bg-secondary mx-1"}, 'current') : null,
          m('ul', [
            tag.description ? m('li', tag.description) : null
          ]),
        ])
      : null
    }))
  }

  const pageContent = [
    m('h1', {class: 'fs-2'}, 'Tags'),
    branchCards(tags),
  ]

  return await htmlPage(reposConfig, eleventyConfig, data, pageContent)
}
main.ts:19
Before
18
19
20

21
22
import commitsJsTemplate from './js_templates/commits.ts'
import indexJsTemplate from './js_templates/index.ts'
import branchesJsTemplate from './js_templates/branches.ts'
⁣
import rawJsTemplate from './js_templates/raw.ts'
import feedJsTemplate from './js_templates/feed.ts'
After
18
19
20
21
22
23
import commitsJsTemplate from './js_templates/commits.ts'
import indexJsTemplate from './js_templates/index.ts'
import branchesJsTemplate from './js_templates/branches.ts'
import tagsJsTemplate from './js_templates/tags.ts'
import rawJsTemplate from './js_templates/raw.ts'
import feedJsTemplate from './js_templates/feed.ts'
main.ts:328
Before
327
328
329







































330
331
    }
  )

⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
  // FILE.TS
  const flatFilesData = flatFiles(reposData)
  eleventyConfig.addTemplate(
After
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
    }
  )

  // TAGS.TS
  eleventyConfig.addTemplate(
    'repos/tags.11ty.js',
    commonPage(tagsJsTemplate, reposConfiguration, eleventyConfig),
    {
      pagination: {
        data: "flatRels",
        size: 1,
        alias: "flatRel",
      },
      permalink: (data) => {
        const repoName = data.flatRel.repoName
        const relName = data.flatRel.relName
        const relType = data.flatRel.type
        return `${reposPath}/${eleventyConfig.getFilter("slugify")(repoName)}/${relType}/${eleventyConfig.getFilter("slugify")(relName)}/tags/`
      },
      eleventyComputed: {
        nav: {
          repoName: (data) => data.flatRel.repoName,
          relName: (data) => data.flatRel.relName,
          path: "tags"
        },
        currentRepo: (data) => reposData.find(repo => {
          return repo.name === data.flatRel.repoName
        }),
        currentRel: (data) => {
          const relType = data.flatRel.type === "branch" ? "branches" : "tags"
          const currentRepo = reposData.find(repo => {
            return repo.name === data.flatRel.repoName
          })
          return currentRepo[relType].find(rel => {
            return rel.name === data.flatRel.relName
          })
        }
      },
      navTab: "tags",
    }
  )

  // FILE.TS
  const flatFilesData = flatFiles(reposData)
  eleventyConfig.addTemplate(