import React, { useState, useCallback, useMemo } from 'react'; import ReactMarkdown from 'react-markdown'; import Marked, { ReactRenderer } from 'marked-react'; import SyntaxHighlighter from 'react-syntax-highlighter'; import { oneDark } from 'react-syntax-highlighter/dist/esm/styles/prism'; import { HoverCard, HoverCardContent, HoverCardTrigger } from "@/components/ui/hover-card"; import { Button } from '@/components/ui/button'; import { Check, Copy } from 'lucide-react'; import Image from 'next/image'; import Link from 'next/link'; import { fetchMetadata } from '@/app/actions'; interface MarkdownRendererProps { content: string; } interface CitationLink { text: string; link: string; } interface LinkMetadata { title: string; description: string; } const isValidUrl = (str: string) => { try { new URL(str); return true; } catch { return false; } }; const MarkdownRenderer: React.FC = ({ content }) => { const [metadataCache, setMetadataCache] = useState>({}); const citationLinks = useMemo(() => { return Array.from(content.matchAll(/\[([^\]]+)\]\(([^)]+)\)/g)).map(([_, text, link]) => ({ text, link, })); }, [content]); const fetchMetadataWithCache = useCallback(async (url: string) => { if (metadataCache[url]) { return metadataCache[url]; } const metadata = await fetchMetadata(url); if (metadata) { setMetadataCache(prev => ({ ...prev, [url]: metadata })); } return metadata; }, [metadataCache]); const CodeBlock = ({ language, children }: { language: string | undefined; children: string }) => { const [isCopied, setIsCopied] = useState(false); const handleCopy = async () => { await navigator.clipboard.writeText(children); setIsCopied(true); setTimeout(() => setIsCopied(false), 2000); }; return (
{children}
); }; const LinkPreview = ({ href }: { href: string }) => { const [metadata, setMetadata] = useState(null); const [isLoading, setIsLoading] = useState(false); React.useEffect(() => { setIsLoading(true); fetchMetadataWithCache(href).then((data) => { setMetadata(data); setIsLoading(false); }); }, [href]); if (isLoading) { return (
); } const domain = new URL(href).hostname; return (
Favicon {domain}

{metadata?.title || "Untitled"}

{metadata?.description && (

{metadata.description}

)}
); }; const renderHoverCard = (href: string, text: React.ReactNode, isCitation: boolean = false) => { return ( {text} ); }; const renderer: Partial = { paragraph(children) { return

{children}

; }, code(children, language) { return {String(children)}; }, link(href, text) { const citationIndex = citationLinks.findIndex(link => link.link === href); if (citationIndex !== -1) { return ( {renderHoverCard(href, citationIndex + 1, true)} ); } return isValidUrl(href) ? renderHoverCard(href, text) : {text}; }, heading(children, level) { const HeadingTag = `h${level}` as keyof JSX.IntrinsicElements; const className = `text-${4 - level}xl font-bold my-4 text-neutral-800 dark:text-neutral-100`; return {children}; }, list(children, ordered) { const ListTag = ordered ? 'ol' : 'ul'; return {children}; }, listItem(children) { return
  • {children}
  • ; }, blockquote(children) { return
    {children}
    ; }, }; return (
    {content}
    ); }; export default MarkdownRenderer;