import { memo, isValidElement, type ReactNode } from 'react' import ReactMarkdown, { type Components } from 'react-markdown' import remarkGfm from 'remark-gfm' import { cn } from '@/lib/utils' import { CopyButton } from './copy-button' // react-markdown hands
its rendered element, not the raw source,
// so pull the text back out of the React tree for the copy button.
function nodeText(node: ReactNode): string {
if (node == null || typeof node === 'boolean') return ''
if (typeof node === 'string' || typeof node === 'number') return String(node)
if (Array.isArray(node)) return node.map(nodeText).join('')
if (isValidElement(node)) return nodeText((node.props as { children?: ReactNode }).children)
return ''
}
function Pre({ children }: { children?: ReactNode }) {
return (
{children}
)
}
const components: Components = {
p: ({ children }) => (
{children}
),
a: ({ children, href }) => (
{children}
),
h1: ({ children }) => (
{children}
),
h2: ({ children }) => (
{children}
),
h3: ({ children }) => (
{children}
),
h4: ({ children }) => (
{children}
),
ul: ({ children }) => (
{children}
),
ol: ({ children }) => (
{children}
),
li: ({ children }) => {children} ,
blockquote: ({ children }) => (
{children}
),
hr: () =>
,
strong: ({ children }) => {children},
em: ({ children }) => {children},
del: ({ children }) => {children},
table: ({ children }) => (
{children}
),
thead: ({ children }) => {children},
th: ({ children, style }) => (
{children}
),
td: ({ children, style }) => (
{children}
),
code: ({ className, children, ...props }) => {
const isBlock = /language-/.test(className ?? '')
if (isBlock) {
return (
{children}
)
}
return (
{children}
)
},
pre: ({ children }) => {children},
}
interface MarkdownProps {
children: string
className?: string
}
function MarkdownInner({ children, className }: MarkdownProps) {
return (
{children}
)
}
export const Markdown = memo(MarkdownInner)