import { cn } from "@/lib/utils" import { marked } from "marked" import { memo, useId, useMemo } from "react" import ReactMarkdown, { Components } from "react-markdown" import remarkBreaks from "remark-breaks" import remarkGfm from "remark-gfm" import { CodeBlock, CodeBlockCode } from "./code-block" export type MarkdownProps = { children: string id?: string className?: string components?: Partial } function parseMarkdownIntoBlocks(markdown: string): string[] { const tokens = marked.lexer(markdown) return tokens.map((token) => token.raw) } function extractLanguage(className?: string): string { if (!className) return "plaintext" const match = className.match(/language-(\w+)/) return match ? match[1] : "plaintext" } const INITIAL_COMPONENTS: Partial = { code: function CodeComponent({ className, children, ...props }) { const isInline = !props.node?.position?.start.line || props.node?.position?.start.line === props.node?.position?.end.line if (isInline) { return ( {children} ) } const language = extractLanguage(className) return ( ) }, pre: function PreComponent({ children }) { return <>{children} }, } const MemoizedMarkdownBlock = memo( function MarkdownBlock({ content, components = INITIAL_COMPONENTS, }: { content: string components?: Partial }) { return ( {content} ) }, function propsAreEqual(prevProps, nextProps) { return prevProps.content === nextProps.content } ) MemoizedMarkdownBlock.displayName = "MemoizedMarkdownBlock" function MarkdownComponent({ children, id, className, components = INITIAL_COMPONENTS, }: MarkdownProps) { const generatedId = useId() const blockId = id ?? generatedId const blocks = useMemo(() => parseMarkdownIntoBlocks(children), [children]) return (
{blocks.map((block, index) => ( ))}
) } const Markdown = memo(MarkdownComponent) Markdown.displayName = "Markdown" export { Markdown }