"use client"; import { Button } from "@/components/ui/button"; import { CheckmarkCircle01Icon, CopyIcon, } from "@hugeicons/core-free-icons"; import { HugeiconsIcon } from "@hugeicons/react"; import type { ReactNode } from "react"; import { useCallback, useEffect, useRef, useState } from "react"; import type { BundledLanguage } from "shiki"; import { CodeBlockContent } from "./code-block"; const SUPPORTED = new Set([ "ts", "tsx", "js", "jsx", "json", "md", "markdown", "sh", "bash", "zsh", "shell", "rs", "rust", "py", "python", "css", "html", "yaml", "yml", "toml", "go", "java", "c", "cpp", "ruby", "swift", "kotlin", "sql", "diff", "text", "txt", "plaintext", ]); const ALIASES: Record = { shell: "bash" as BundledLanguage, zsh: "bash" as BundledLanguage, markdown: "md" as BundledLanguage, python: "py" as BundledLanguage, rust: "rs" as BundledLanguage, txt: "text" as BundledLanguage, plaintext: "text" as BundledLanguage, }; function resolveLanguage(raw: string | null): BundledLanguage { if (!raw) return "text" as BundledLanguage; const norm = raw.toLowerCase(); if (!SUPPORTED.has(norm)) return "text" as BundledLanguage; return (ALIASES[norm] ?? norm) as BundledLanguage; } /** * Streamdown `components.code` override: handles BOTH inline and fenced. * Detection: fenced blocks come with a `language-*` className. */ export function MarkdownCode({ className, children, ...rest }: { className?: string; children?: ReactNode; }) { const match = className?.match(/language-(\w+)/); if (!match) { return ( {children} ); } const lang = resolveLanguage(match[1]); const code = String(children ?? "").replace(/\n$/, ""); return ; } export function MarkdownCodeBlock({ code, language, rawLang, }: { code: string; language: BundledLanguage; rawLang: string; }) { return (
{rawLang || "text"}
); } function CopyButton({ text }: { text: string }) { const [copied, setCopied] = useState(false); const tRef = useRef(0); const onCopy = useCallback(async () => { if (!navigator?.clipboard?.writeText) return; try { await navigator.clipboard.writeText(text); setCopied(true); tRef.current = window.setTimeout(() => setCopied(false), 1500); } catch { /* swallow */ } }, [text]); useEffect(() => () => window.clearTimeout(tRef.current), []); return ( ); }