Share code block enhancement between renderers

The copy button and syntax highlighting setup for code blocks moves out
of the Markdown component, so other document renderers can reuse it.
This commit is contained in:
Rūdolfs Ošiņš
2026-10-06 16:27:01 +02:00
parent 41eac34ddc
commit 2a0dd5a747
2 changed files with 37 additions and 35 deletions
+2 -35
View File
@@ -1,7 +1,7 @@
<script lang="ts">
import { toDom } from "hast-util-to-dom";
import { mount, tick, unmount } from "svelte";
import { enhanceCodeBlocks } from "@app/lib/codeBlocks";
import { decodeEmbed, embedPreviewKind, retinaWidth } from "@app/lib/embeds";
import { parseFrontmatter } from "@app/lib/frontmatter";
import { invoke } from "@app/lib/invoke";
@@ -13,7 +13,6 @@
} from "@app/lib/markdown";
import { parseEntityHref } from "@app/lib/mentions";
import { isOid } from "@app/lib/radUri";
import { highlight } from "@app/lib/syntax";
import { scrollIntoView, twemoji } from "@app/lib/utils";
import Icon from "@app/components/Icon.svelte";
@@ -333,39 +332,7 @@
}
}
// Replaces code blocks in the background with highlighted code.
const prefix = "language-";
const nodes = Array.from(container.querySelectorAll("pre code"));
const treeChanges: Promise<void>[] = [];
for (const node of nodes) {
const preElement = node.parentElement as HTMLElement;
const copyButton = document.createElement("radicle-clipboard");
copyButton.setAttribute("text", node.textContent || "");
const preWrapper = document.createElement("div");
preWrapper.classList.add("pre-wrapper");
preElement.parentNode?.insertBefore(preWrapper, preElement);
preWrapper.appendChild(preElement);
preWrapper.appendChild(copyButton);
const className = Array.from(node.classList).find(name =>
name.startsWith(prefix),
);
if (!className) continue;
treeChanges.push(
highlight(node.textContent ?? "", className.slice(prefix.length))
.then(tree => {
if (tree) {
node.replaceChildren(toDom(tree, { fragment: true }));
}
})
.catch(e => console.warn("Not able to highlight code block", e)),
);
}
void Promise.allSettled(treeChanges);
enhanceCodeBlocks(container);
if (!scrolledToHash && window.location.hash) {
scrollIntoView(window.location.hash.substring(1));
+35
View File
@@ -0,0 +1,35 @@
import { toDom } from "hast-util-to-dom";
import { highlight } from "@app/lib/syntax";
const languagePrefix = "language-";
/**
* Adds a copy button to every `pre code` block in `container` and replaces
* the blocks that name a language with highlighted code in the background.
*/
export function enhanceCodeBlocks(container: HTMLElement) {
for (const node of container.querySelectorAll("pre code")) {
const preElement = node.parentElement as HTMLElement;
const copyButton = document.createElement("radicle-clipboard");
copyButton.setAttribute("text", node.textContent || "");
const preWrapper = document.createElement("div");
preWrapper.classList.add("pre-wrapper");
preElement.parentNode?.insertBefore(preWrapper, preElement);
preWrapper.appendChild(preElement);
preWrapper.appendChild(copyButton);
const className = Array.from(node.classList).find(name =>
name.startsWith(languagePrefix),
);
if (!className) continue;
highlight(node.textContent ?? "", className.slice(languagePrefix.length))
.then(tree => {
if (tree) {
node.replaceChildren(toDom(tree, { fragment: true }));
}
})
.catch(e => console.warn("Not able to highlight code block", e));
}
}