"use client"; import { Check, Copy } from "lucide-react"; import { isValidElement, useEffect, useRef, useState, type ReactNode } from "react"; /** The fenced language, from the `language-xxx` class react-markdown puts on . */ export function fencedLanguage(node: ReactNode): string { if (!isValidElement<{ className?: string }>(node)) return ""; return /language-([\w+-]+)/.exec(node.props.className ?? "")?.[1] ?? ""; } /** The raw source inside the fence, for the copy button. */ export function fencedSource(node: ReactNode): string { if (typeof node === "string") return node; if (Array.isArray(node)) return node.map(fencedSource).join(""); if (isValidElement<{ children?: ReactNode }>(node)) return fencedSource(node.props.children); return ""; } export function MarkdownCodeBlock({ children }: { readonly children?: ReactNode }) { const [copied, setCopied] = useState(false); const resetTimer = useRef | null>(null); const language = fencedLanguage(children); const source = fencedSource(children); useEffect(() => () => { if (resetTimer.current) clearTimeout(resetTimer.current); }, []); async function copy() { try { await navigator.clipboard.writeText(source); } catch { return; } setCopied(true); if (resetTimer.current) clearTimeout(resetTimer.current); resetTimer.current = setTimeout(() => setCopied(false), 1600); } return (
{language || "代码"}
{children}
); }