SEANK.H.LIAO

goldmark code blocks

syntax highlighting, line numbers, copy button

goldmark code blocks

I use github.com/yuin/goldmark as the markdown renderer for my blog. Until recently, I hadn't bothered to do syntax highlighting, but the day has come! Anyway, there's the github.com/yuin/goldmark-highlighting/v2 extension for syntax highlighting, which uses github.com/alecthomas/chroma/v2.

linkable line numbers

Theres the WithLinkableLineNumbers(b bool, prefix string) option, but I was a bit confused about how to generate unique links for each block of code. That was until I realized I could create a new instance of the highlighting extension and markdown renderer for each page and close over an incrememting counter.

 1var block int
 2hl := highlighting.NewHighlighting(
 3        highlighting.WithFormatOptions(
 4                chromahtml.WithLineNumbers(true),
 5        ),
 6        highlighting.WithCodeBlockOptions(func(c highlighting.CodeBlockContext) []chromahtml.Option {
 7                block++
 8                return []chromahtml.Option{
 9                        chromahtml.WithLinkableLineNumbers(true, fmt.Sprintf("block%d-", block)),
10                }
11        }),
12)

copy button

Now I wanted a copy button for the text. In the generated html, each line looked like:

1</span></span>
2<span class="line"><span class="ln" id="block1-2"><a class="lnlinks" href="#block1-2"> 2</a></span>
3<span class="cl">  <span class="nt">&#34;after&#34;</span><span class="p">:</span> <span class="s2">&#34;cf2d3c9bb11e17eca797d8ab0d80aaef68f19b99&#34;</span><span class="p">,</span>

Some fiddling with js later, I had some js to progressively enhance the blocks and copy out the text by just joining the cl (code line?) elements.

 1document.querySelectorAll(".chroma").forEach((block) => {
 2  if (!navigator.clipboard) {
 3    return;
 4  }
 5
 6  let button = document.createElement("button");
 7  button.innerText = "Copy";
 8  block.appendChild(button);
 9
10  button.addEventListener("click", async () => {
11    let codeText = [...block.querySelectorAll(".cl")]
12      .map((n) => n.innerText)
13      .join("");
14    await navigator.clipboard.writeText(codeText);
15
16    button.innerText = "Copied";
17
18    setTimeout(() => {
19      button.innerText = "Copy";
20    }, 2000);
21  });
22});