Make language percent breakdown work; add optional config for extra buttons

073785ab9fa931c3d89e9631c36b2ff05e1b14ee

Tucker McKnight | Wed Dec 24 2025

Make language percent breakdown work; add optional config for extra buttons
frontend/top.js:23
Before
23
      element.innerHTML = `<span>&#x1F4A1;</span>`
After
23
      element.innerHTML = `<span>&#x1F31E;</span>`
js_templates/common/htmlPage.ts:96
Before
96
97
                  <button class="dropdown-toggle btn shadow-none" id="dark-mode-switch" type="button" data-bs-toggle="dropdown" aria-expanded="false">
                    <li><button class="btn shadow-none" data-theme-pref="light" onclick="toggleDarkMode(this)"><span class="me-1">&#x1F4A1;</span>Light</button></li>
After
96
97
                  <button class="dropdown-toggle btn btn-bg" id="dark-mode-switch" type="button" data-bs-toggle="dropdown" aria-expanded="false">
                    <li><button class="btn shadow-none" data-theme-pref="light" onclick="toggleDarkMode(this)"><span class="me-1">&#x1F31E;</span>Light</button></li>
js_templates/helpers/nav.ts:36
Before
36
After
36
    homepageButtons: reposConfig.repos[repoName].defaultTemplateConfiguration?.homepageButtons
js_templates/repo.ts:7
Before
7
After
7
  const latestCommitMessage = latestCommit.message.length > 72
    ? latestCommit.message.split('\n')[0].substr(0, 72) + '...'
    : latestCommit.message

  const languageCounts = branch.fileList.reduce((counts, currentFile) => {
    const fileParts = currentFile.split(".")
    const fileExtension = fileParts[fileParts.length - 1]

    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 = branch.fileList.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)
js_templates/repo.ts:17
Before
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
            <div class="col-12 col-xl-6 d-flex flex-column">
              <div class="row py-3 flex-grow-1 align-items-center">
                <div class="col-12">
                  <div class="row">
                    <div class=&quot;col-12 language-percentages&quot;>
                      <span style="width: 25%;" class="language-name text-light font-monospace">HTML</span>
                      <span style="width: 15%;" class="language-name text-light font-monospace">CSS</span>
                      <span style=&quot;width: 10%;&quot; class=&quot;language-name text-light font-monospace&quot;>Rust</span>
                      <span style="width: 33%;" class="language-name text-light font-monospace">Typescript</span>
                      <span style="width: 17%;" class="language-name text-light font-monospace">Bash</span>
                    </div>
                  </div>
                  <div class="row">
                    <div class="col-12 language-percentages">
                      <div style="background: #E273FA; width: 25%;" class="language-percent"></div>
                      <div style="background: #852CDF; width: 15%;" class="language-percent"></div>
                      <div style=&quot;background: #E273FA; width: 10%;" class="language-percent"&gt;</div>
                      <div style="background: #852CDF; width: 33%;" class="language-percent"></div>
                      <div style="background: #E273FA; width: 17%;&quot; class="language-percent"&gt;&lt;/div&gt;
                    &lt;/div&gt;
              <div class=&quot;row flex-grow-1 align-items-center&quot;>
                &lt;div class=";col-md col-fluid py-3&quot;>
                  &lt;button class="w-100 btn btn-info btn-lg dropdown-toggle&quot; id=&quot;clone-popover-btn&quot;>Clone&lt;/button>
                &lt;/div&gt;
                <div class="col-md col-fluid py-3">
                  <button class="w-100 btn btn-outline-info shadow-none btn-lg">Setup Instructions</button>
After
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
            <div class="col-12 col-xl-6 d-flex flex-column justify-content-around">
              <div class="row flex-grow-1 align-items-stretch language-percent-row">
                <div class="col-12 d-flex align-items-stretch">
                  <div class="row d-flex flex-grow-1">
                    ${topLanguagePercentages.map((percentTuple) =&gt; `&lt;div class=&#39;col language-col&#39;><div class="language-name text-light font-monospace">${percentTuple[0]}</div><div class="language-percent" style="flex-grow: ${percentTuple[1] / largestPercent}"></div></div&gt;`).join(';&#39;)}
                  <div class=&#39;col language-col&#39;><div class="language-name text-light font-monospace">other</div><div class="language-percent" style="flex-grow: ${otherLanguagePercent / largestPercent}"></div></div>
              <div class="row align-items-center">
                <div class="col-12">
                  <div class="header-button-container">
                    <button class="btn btn-info btn-lg dropdown-toggle" id="clone-popover-btn">Clone</button>
                    ${nav.homepageButtons.map((buttonConfig) => {
                      return `<a class="btn btn-outline-info btn-lg shadow-none" href="${buttonConfig.url}" ${buttonConfig.newTab ? &#39;target="_blank"&#39; : &#39;&#39;}>${buttonConfig.text}${buttonConfig.newTab ? &#39; &lt;span>&amp;#x29C9;&lt;/span>&#39; : &#39;&#39;}</a>`
                    }).join(&#39;&#39;)}
                  </div>
js_templates/repo.ts:61
Before
61
62
              <p class="font-monospace text-white text-truncate">
                Latest commit: ${latestCommit.date.toDateString()} <a class="fw-bold link-info" href="${nav.repoBranchCommitsBase()}${latestCommit.hash}">${latestCommit.hash.substr(0, 6)}</a> ${latestCommit.message.split('\n')[0]}
After
61
62
              <p class="font-monospace text-white">
                Latest commit: ${latestCommit.date.toDateString()} <a class="fw-bold link-info" href="${nav.repoBranchCommitsBase()}${latestCommit.hash}">${latestCommit.hash.substr(0, 6)}</a> ${latestCommitMessage}
schemas/ReposConfiguration.json:57
Before
57
After
57
        "defaultTemplateConfiguration": {
          "additionalProperties": false,
          "properties": {
            "homepageButtons": {
              "items": {
                "additionalProperties": false,
                "properties": {
                  "newTab": {
                    "type": "boolean"
                  },
                  "text": {
                    "type": "string"
                  },
                  "url": {
                    "type": "string"
                  }
                },
                "required": [
                  "url",
                  "text"
                ],
                "type": "object"
              },
              "type": "array"
            }
          },
          "required": [
            "homepageButtons"
          ],
          "type": "object"
        },
schemas/ReposConfiguration.json:121
Before
121
After
121
        },
        "useDefaultTemplate": {
          "type": "boolean"
scss/design-board.scss:40
Before
40
41
42
43
44
45
  border-bottom: 6px solid color.adjust($lightblue, $lightness: -10%);
  border-right: 6px solid color.adjust($lightblue, $lightness: -13%);
}

#dark-mode-switch span {
  text-shadow: 0 0 12px $black;
After
40
41
42
43
44
45
  border-bottom: 6px solid color.adjust($lightblue, $lightness: -30%);
  border-right: 6px solid color.adjust($lightblue, $lightness: -40%);
scss/design-board.scss:78
Before
78
79
80
  #dark-mode-switch span {
    text-shadow: 0 0 12px $white;
  }
After
78
79
80
}
.btn-bg, .btn-bg:hover {
  border: 1px solid $body-color;
scss/design-board.scss:165
Before
165
166

  height: 2rem;
After
165
166
.language-percent-row {
  max-height: 60%;
}
.language-col {
  display: flex;
  flex-direction: column;
  justify-content: end;
  min-height: 4rem;
}
  background-color: $lilac;
  // background-color: #E273FA;
}
.language-col:nth-of-type(2n) .language-percent {
  background-color: $magenta;
  // background-color: #852CDF;
scss/design-board.scss:181
Before
181
182
183
184
.branches, .branches:hover {
  border: 1px solid $body-color;
}
After
181
182
183
184
scss/design-board.scss:210
Before
210
After
210

.badge.bg-primary {
  background: linear-gradient(color.adjust($primary, $lightness: 10%), $primary);
  box-shadow: -1px -3px 4px inset color.adjust($primary, $lightness: -30%);
}

.badge.bg-info {
  background: linear-gradient(color.adjust($info, $lightness: 10%), $info);
  box-shadow: -1px -3px 4px inset color.adjust($info, $lightness: -30%);
}

.header-button-container {
  display: flex;
  flex-wrap: wrap;
  margin: 10px -10px;

  .btn {
    flex: 1;
    flex-basis: 200px;
    margin: 10px 10px;
  }
}
src/configTypes.ts:40
Before
40
After
40
  useDefaultTemplate?: boolean,
src/configTypes.ts:58
Before
58
  }[]
After
58
  }[],
  defaultTemplateConfiguration?: {
    homepageButtons: Array<{
      url: string,
      text: string,
      newTab?: boolean,
    }>
  },