import React, { Suspense, useEffect, useState } from "react"; import Markdown from "react-markdown"; import remarkGfm from "remark-gfm"; import { cn } from "@/lib/utils"; import { CopyButton } from "@/components/ui/copy-button"; interface MarkdownRendererProps { children: string; } export function MarkdownRenderer({ children }: MarkdownRendererProps) { return (
{children}
); } interface HighlightedPre extends React.HTMLAttributes { children: string; language: string; } const HighlightedPre = React.memo( ({ children, language, ...props }: HighlightedPre) => { const [tokens, setTokens] = useState(null); const [isSupported, setIsSupported] = useState(false); useEffect(() => { let mounted = true; const loadAndHighlight = async () => { try { const { codeToTokens, bundledLanguages } = await import("shiki"); if (!mounted) return; if (!(language in bundledLanguages)) { setIsSupported(false); return; } setIsSupported(true); const { tokens: highlightedTokens } = await codeToTokens(children, { lang: language as keyof typeof bundledLanguages, defaultColor: false, themes: { light: "github-light", dark: "github-dark", }, }); if (mounted) { setTokens(highlightedTokens); } } catch { if (mounted) { setIsSupported(false); } } }; loadAndHighlight(); return () => { mounted = false; }; }, [children, language]); if (!isSupported) { return
{children}
; } if (!tokens) { return
{children}
; } return (
        
          {tokens.map((line, lineIndex) => (
            
              
                {line.map((token, tokenIndex) => {
                  const style =
                    typeof token.htmlStyle === "string"
                      ? undefined
                      : token.htmlStyle;

                  return (
                    
                      {token.content}
                    
                  );
                })}
              
              {lineIndex !== tokens.length - 1 && "\n"}
            
          ))}
        
      
); } ); HighlightedPre.displayName = "HighlightedCode"; interface CodeBlockProps extends React.HTMLAttributes { children: React.ReactNode; className?: string; language: string; } const CodeBlock = ({ children, className, language, ...restProps }: CodeBlockProps) => { const code = typeof children === "string" ? children : childrenTakeAllStringContents(children); const preClass = cn( "overflow-x-scroll rounded-md border bg-background/50 p-4 font-mono text-sm [scrollbar-width:none]", className ); return (
{children} } > {code}
); }; function childrenTakeAllStringContents(element: unknown): string { if (typeof element === "string") { return element; } if (element?.props?.children) { const children = element.props.children; if (Array.isArray(children)) { return children .map(child => childrenTakeAllStringContents(child)) .join(""); } else { return childrenTakeAllStringContents(children); } } return ""; } const COMPONENTS = { h1: withClass("h1", "text-2xl font-semibold"), h2: withClass("h2", "font-semibold text-xl"), h3: withClass("h3", "font-semibold text-lg"), h4: withClass("h4", "font-semibold text-base"), h5: withClass("h5", "font-medium"), strong: withClass("strong", "font-semibold"), a: withClass("a", "text-primary underline underline-offset-2"), blockquote: withClass("blockquote", "border-l-2 border-primary pl-4"), code: ({ children, className, }: { children: React.ReactNode; className?: string; }) => { const match = /language-(\w+)/.exec(className || ""); return match ? ( {children} ) : ( &]:rounded-md [:not(pre)>&]:bg-background/50 [:not(pre)>&]:px-1 [:not(pre)>&]:py-0.5" )} {...rest} > {children} ); }, pre: ({ children }: { children: React.ReactNode }) => children, ol: withClass("ol", "list-decimal space-y-2 pl-6"), ul: withClass("ul", "list-disc space-y-2 pl-6"), li: withClass("li", "my-1.5"), table: withClass( "table", "w-full border-collapse overflow-y-auto rounded-md border border-foreground/20" ), th: withClass( "th", "border border-foreground/20 px-4 py-2 text-left font-bold [&[align=center]]:text-center [&[align=right]]:text-right" ), td: withClass( "td", "border border-foreground/20 px-4 py-2 text-left [&[align=center]]:text-center [&[align=right]]:text-right" ), tr: withClass("tr", "m-0 border-t p-0 even:bg-muted"), p: withClass("p", "whitespace-pre-wrap"), hr: withClass("hr", "border-foreground/20"), }; function withClass(Tag: keyof JSX.IntrinsicElements, classes: string) { const Component = ({ ...props }: Record) => ( ); Component.displayName = Tag; return Component; } export default MarkdownRenderer;