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.
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)
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">"after"</span><span class="p">:</span> <span class="s2">"cf2d3c9bb11e17eca797d8ab0d80aaef68f19b99"</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});