import { memo, useState, type ReactNode } from 'react' import ReactMarkdown from 'react-markdown' import remarkGfm from 'remark-gfm' import remarkMath from 'remark-math' import rehypeKatex from 'rehype-katex' import rehypeHighlight from 'rehype-highlight' import Mermaid from './Mermaid' // Note: no rehype-raw — raw HTML in the markdown is rendered as text. Since // any document can arrive via a shared link, HTML passthrough would let a // link author run script in the viewer's browser. function extractText(node: ReactNode): string { if (typeof node === 'string') return node if (Array.isArray(node)) return node.map(extractText).join('') if (node && typeof node === 'object' && 'props' in node) { return extractText((node.props as { children?: ReactNode }).children) } return '' } function Pre({ children, ...props }: React.HTMLAttributes) { const [copied, setCopied] = useState(false) // ```mermaid fences render as a diagram instead of a code block const child = Array.isArray(children) ? children[0] : children if ( child && typeof child === 'object' && 'props' in child && /\blanguage-mermaid\b/.test( String((child.props as { className?: string }).className ?? ''), ) ) { return } const copy = () => { navigator.clipboard.writeText(extractText(children).replace(/\n$/, '')) setCopied(true) setTimeout(() => setCopied(false), 1500) } return (
{children}
) } export default memo(function Preview({ markdown }: { markdown: string }) { return (
{markdown}
) })