import copy from "copy-to-clipboard"; import hljs from "highlight.js"; import { CopyIcon } from "lucide-react"; import { useEffect, useMemo } from "react"; import toast from "react-hot-toast"; import { cn } from "@/lib/utils"; import MermaidBlock from "./MermaidBlock"; import { BaseProps } from "./types"; // Special languages that are rendered differently. enum SpecialLanguage { HTML = "__html", MERMAID = "mermaid", } interface Props extends BaseProps { language: string; content: string; } const CodeBlock: React.FC = ({ language, content }: Props) => { const formatedLanguage = useMemo(() => (language || "").toLowerCase() || "text", [language]); // Users can set Markdown code blocks as `__html` to render HTML directly. if (formatedLanguage === SpecialLanguage.HTML) { return (
); } else if (formatedLanguage === SpecialLanguage.MERMAID) { return ; } useEffect(() => { const dynamicImportStyle = async () => { const isDark = document.documentElement.classList.contains("dark"); // Remove any existing highlight.js style const existingStyle = document.querySelector("style[data-hljs-theme]"); if (existingStyle) { existingStyle.remove(); } try { // Dynamically import the appropriate CSS. const cssModule = isDark ? await import("highlight.js/styles/atom-one-dark.css?inline") : await import("highlight.js/styles/github.css?inline"); // Create and inject the style const style = document.createElement("style"); style.textContent = cssModule.default; style.setAttribute("data-hljs-theme", isDark ? "dark" : "light"); document.head.appendChild(style); } catch (error) { console.warn("Failed to load highlight.js theme:", error); } }; dynamicImportStyle(); // Watch for changes to the dark class const observer = new MutationObserver(dynamicImportStyle); observer.observe(document.documentElement, { attributes: true, attributeFilter: ["class"], }); return () => observer.disconnect(); }, []); const highlightedCode = useMemo(() => { try { const lang = hljs.getLanguage(formatedLanguage); if (lang) { return hljs.highlight(content, { language: formatedLanguage, }).value; } } catch { // Skip error and use default highlighted code. } // Escape any HTML entities when rendering original content. return Object.assign(document.createElement("span"), { textContent: content, }).innerHTML; }, [formatedLanguage, content]); const copyContent = () => { copy(content); toast.success("Copied to clipboard!"); }; return (
{formatedLanguage}
          
        
); }; export default CodeBlock;