Add a toggleable rendered markdown page for md files

c96099269241f024408717eaca75db25f26446f4

Tucker McKnight <tucker@pangolin.lan> | Wed Dec 31 2025

Add a toggleable rendered markdown page for md files

Adds a toggle switch for showing rendered markdown content or
the un-rendered original file contents.

Page will obey the toggle switch state on page load.

Move the "view raw file" link to a separate row, since the "show blame"
toggle switch doesn't show when the rendered markdown page is showing.

Also change the permalink for raw files so that it has the original
file's extension.
frontend/main.js:47
Before
46
47
48










49


50
51

const toggleLastTouch = (event) => {
  const isOn = event.target.checked
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
  const annotations = document.getElementById("annotations")
⁣
⁣
  if (isOn) {
    annotations.classList.remove("d-none")
  } else {
After
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63

const toggleLastTouch = (event) => {
  const isOn = event.target.checked
  setShowLastTouch(isOn)
}

const toggleRenderedContent = (event) => {
  const isOn = event.target.checked
  setRenderedContent(isOn)
}

const renderedContent = document.querySelector(".rendered-content")
const codeContent = document.querySelector(".code-content")
const annotations = document.getElementById("annotations")

const setShowLastTouch = (isOn) => {
  if (isOn) {
    annotations.classList.remove("d-none")
  } else {
frontend/main.js:55
Before
54
55
56










57
58

59
  }
}

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

⁣
const copyPull = (event) => {
  const hash = event.target.dataset.hash
After
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
  }
}

const setRenderedContent = (isOn) => {
  if (isOn) {
    renderedContent.classList.remove("d-none")
    codeContent.classList.add("d-none")
  } else {
    renderedContent.classList.add("d-none")
    codeContent.classList.remove("d-none")
  }
}

document.getElementById("showLastTouch")?.addEventListener('click', toggleLastTouch)
document.getElementById("showRenderedContent")?.addEventListener('click', toggleRenderedContent)

const copyPull = (event) => {
  const hash = event.target.dataset.hash
frontend/main.js:129
Before
128
129










})

⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
showBranchesResults(searchBox.value)
After
128
129
130
131
132
133
134
135
136
137
138
139
140
})

showBranchesResults(searchBox.value)

const pageLoadLastTouch = document.getElementById("showLastTouch")
if (pageLoadLastTouch) {
  setShowLastTouch(pageLoadLastTouch.checked)
}

const pageLoadRenderedContent = document.getElementById("showRenderedContent")
if (pageLoadRenderedContent) {
  setRenderedContent(pageLoadRenderedContent.checked)
}
js_templates/file.ts:9
Before
8
9
10
11

12
  const lineNumbers = eleventyConfig.getFilter("lineNumbers")
  const highlightCode = eleventyConfig.getFilter("highlightCode")
  const languageExtension = eleventyConfig.getFilter("languageExtension")

⁣
  return `
    <div class="row my-3">
After
8
9
10
11
12
13
  const lineNumbers = eleventyConfig.getFilter("lineNumbers")
  const highlightCode = eleventyConfig.getFilter("highlightCode")
  const languageExtension = eleventyConfig.getFilter("languageExtension")
  const renderContentIfAvailable = eleventyConfig.getFilter("renderContentIfAvailable")

  return `
    <div class="row my-3">
js_templates/file.ts:30
Before
29
30
31

32
33
    <div class="row">
      <div class="col">
        <p>Files snapshot from <span class="font-monospace">${data.fileInfo.branchName}</span></p>
⁣
      </div>
    </div>
    ${isDirectory(data.fileInfo.file, data.fileInfo.repoName, data.fileInfo.branchName) ?
After
29
30
31
32
33
34
    <div class="row">
      <div class="col">
        <p>Files snapshot from <span class="font-monospace">${data.fileInfo.branchName}</span></p>
        <p><a href="${data.reposPath}/${slugify(data.fileInfo.repoName)}/branches/${slugify(data.fileInfo.branchName)}/raw/${data.fileInfo.file.split('.').map(filePart => slugify(filePart)).join('.')}">View raw file</a></p>
      </div>
    </div>
    ${isDirectory(data.fileInfo.file, data.fileInfo.repoName, data.fileInfo.branchName) ?
js_templates/file.ts:46
Before
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
      </div>
    </div>`
    :
⁣
⁣
⁣
    `<div class="row py-2">
      <div class="col-auto">
        <div class="form-check form-switch">
          <input class="form-check-input" type="checkbox" role="switch" id="showLastTouch">
          <label class="form-check-label" for="showLastTouch">Show last line change</label>
        </div>
      </div>
⁣
      <div class="col">
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
        <a href="${data.reposPath}/${slugify(data.fileInfo.repoName)}/branches/${slugify(data.fileInfo.branchName)}/raw/${slugify(data.fileInfo.file)}">View raw file</a>
      </div>
    </div>
⁣
    <div class="row">
      <div class="col-auto p-0">
        <code style="white-space: pre;"><pre class="language-text">${lineNumbers(await getFileContents(data.fileInfo.repoName, data.fileInfo.branchName, data.fileInfo.file)).map((lineNumber) => {
          return lineNumber
        }).join('\n')}</pre></code>
      </div>
      <div id="annotations" class="col-auto d-none p-0">
        <code style="white-space: pre;"><pre class="language-text">${
          (await getFileLastTouchInfo(
            data.fileInfo.repoName,
            data.fileInfo.branchName,
            data.fileInfo.file
          )).map(
            (annotation) => {
              return `<a href="${data.reposPath}/${slugify(data.fileInfo.repoName)}/branches/${slugify(data.fileInfo.branchName)}/commits/${annotation.sha}">${annotation.sha.substr(0, 6)}</a> ${annotation.author}`
            }
          ).join('\n')
        }</pre></code>
      </div>
      <div class="col overflow-scroll p-0">
        <code>
          ${
            highlightCode(
              await getFileContents(
                data.fileInfo.repoName,
                data.fileInfo.branchName,
                data.fileInfo.file
              ),
              languageExtension(
                data.fileInfo.file,
                data.fileInfo.repoName
              )
            )
          }
        </code>
⁣
⁣
      </div>
    </div>
    `}
After
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
      </div>
    </div>`
    :
    `
    ${data.fileInfo.file.endsWith(".md")
      ? `
        <div class="row">
          <div class="col">
            <div class="form-check form-switch">
              <input class="form-check-input" type="checkbox" role="switch" id="showRenderedContent" checked />
              <label class="form-check-label" for="showRenderedContent">Show rendered markdown</label>
            </div>
          </div>
        </div>
        <div class="row rendered-content py-4">
          <div class="col">
            ${await renderContentIfAvailable(await getFileContents(
              data.fileInfo.repoName,
              data.fileInfo.branchName,
              data.fileInfo.file
            ), data.fileInfo.branchName)}
          </div>
        </div>
        `
      : ''
    }
    <div class="row code-content ${data.fileInfo.file.endsWith('.md') ? 'd-none' : ''}">
      <div class="col">
        <div class="row py-2">
          <div class="col-auto">
            <div class="form-check form-switch">
              <input class="form-check-input" type="checkbox" role="switch" id="showLastTouch">
              <label class="form-check-label" for="showLastTouch">Show last line change</label>
            </div>
          </div>
        </div>
        <div class="row">
          <div class="col-auto p-0">
            <code style="white-space: pre;"><pre class="language-text">${lineNumbers(await getFileContents(data.fileInfo.repoName, data.fileInfo.branchName, data.fileInfo.file)).map((lineNumber) => {
              return lineNumber
            }).join('\n')}</pre></code>
          </div>
          <div id="annotations" class="col-auto d-none p-0">
            <code style="white-space: pre;"><pre class="language-text">${
              (await getFileLastTouchInfo(
                data.fileInfo.repoName,
                data.fileInfo.branchName,
                data.fileInfo.file
              )).map(
                (annotation) => {
                  return `<a href="${data.reposPath}/${slugify(data.fileInfo.repoName)}/branches/${slugify(data.fileInfo.branchName)}/commits/${annotation.sha}">${annotation.sha.substr(0, 6)}</a> ${annotation.author}`
                }
              ).join('\n')
            }</pre></code>
          </div>
          <div class="col overflow-scroll p-0">
            <code>
              ${
                highlightCode(
                  await getFileContents(
                    data.fileInfo.repoName,
                    data.fileInfo.branchName,
                    data.fileInfo.file
                  ),
                  languageExtension(
                    data.fileInfo.file,
                    data.fileInfo.repoName
                  )
                )
              }
            </code>
          </div>
        </div>
      </div>
    </div>
    `}
main.ts:352
Before
351
352
353
354
355
356
      permalink: (data) => {
        const repoName = data.fileInfo.repoName
        const branchName = data.fileInfo.branchName
        return `${reposPath}/${eleventyConfig.getFilter("slugify")(repoName)}/branches/${eleventyConfig.getFilter("slugify")(branchName)}/raw/${eleventyConfig.getFilter("slugify")(data.fileInfo.file)}`
      },
    }
  )
After
351
352
353
354
355
356
      permalink: (data) => {
        const repoName = data.fileInfo.repoName
        const branchName = data.fileInfo.branchName
        return `${reposPath}/${eleventyConfig.getFilter("slugify")(repoName)}/branches/${eleventyConfig.getFilter("slugify")(branchName)}/raw/${data.fileInfo.file.split('.').map(filePart => eleventyConfig.getFilter("slugify")(filePart)).join('.')}`
      },
    }
  )