import { createHighlighter } from "shiki";
// Highlighter singleton
let highlighter: Awaited<ReturnType<typeof createHighlighter>> | null = null;
async function getHighlighter() {
if (!highlighter) {
highlighter = await createHighlighter({
themes: ["github-dark", "github-light"],
langs: ["javascript", "typescript", "html", "css", "json", "bash"],
});
}
return highlighter;
}
/**
* Transform Marble HTML content to add syntax highlighting to code blocks
*/
export async function highlightContent(htmlContent: string): Promise<string> {
const hl = await getHighlighter();
// Marble returns code blocks as: <pre><code class="language-js">...</code></pre>
const codeBlockRegex =
/<pre><code(?:\s+class="language-([^"]+)")?[^>]*>([\s\S]*?)<\/code><\/pre>/g;
return htmlContent.replace(codeBlockRegex, (match, language, code) => {
try {
// Decode HTML entities
const decodedCode = code
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/&/g, "&")
.replace(/"/g, '"')
.replace(/'/g, "'");
const lang = language || "text";
const supported = hl.getLoadedLanguages();
const finalLang = supported.includes(lang) ? lang : "text";
return hl.codeToHtml(decodedCode, {
lang: finalLang,
theme: "github-dark",
});
} catch (error) {
console.warn("Failed to highlight code block:", error);
return match;
}
});
}