MarkdownContent.tsx
1 import React, { useState, useRef } from 'react'; 2 import ReactMarkdown from 'react-markdown'; 3 import remarkGfm from 'remark-gfm'; 4 import rehypeHighlight from 'rehype-highlight'; 5 import { CheckOutlined, CopyOutlined } from '@ant-design/icons'; 6 import './MarkdownContent.css'; 7 8 interface MarkdownContentProps { 9 content: string; 10 } 11 12 const CodeBlock = ({ children, className, ...props }: any) => { 13 const [copied, setCopied] = useState(false); 14 const codeRef = useRef<HTMLElement>(null); 15 16 const match = /language-(\w+)/.exec(className || ''); 17 const language = match ? match[1] : ''; 18 19 const handleCopy = () => { 20 if (codeRef.current) { 21 const text = codeRef.current.textContent || ''; 22 navigator.clipboard.writeText(text).catch(() => {}); 23 setCopied(true); 24 setTimeout(() => setCopied(false), 2000); 25 } 26 }; 27 28 return ( 29 <div className="code-block-wrapper"> 30 <div className="code-block-header"> 31 <span className="code-block-lang">{language}</span> 32 <button className="code-block-copy" onClick={handleCopy} title="Copy code"> 33 {copied ? <CheckOutlined /> : <CopyOutlined />} 34 </button> 35 </div> 36 <pre className="code-block-pre"> 37 <code ref={codeRef} className={className} {...props}> 38 {children} 39 </code> 40 </pre> 41 </div> 42 ); 43 }; 44 45 const MarkdownContent: React.FC<MarkdownContentProps> = React.memo(({ content }) => ( 46 <div className="markdown-container" style={{ userSelect: 'text', cursor: 'text' }}> 47 <ReactMarkdown 48 remarkPlugins={[remarkGfm]} 49 rehypePlugins={[rehypeHighlight]} 50 components={{ 51 pre: ({ children }) => <>{children}</>, 52 code: ({ children, className, node, ...props }: any) => { 53 const isBlock = /language-(\w+)/.exec(className || '') || className?.includes('hljs'); 54 if (isBlock) { 55 return ( 56 <CodeBlock className={className} {...props}> 57 {children} 58 </CodeBlock> 59 ); 60 } 61 return ( 62 <code className="inline-code" {...props}> 63 {children} 64 </code> 65 ); 66 }, 67 table: ({ children }) => ( 68 <div className="markdown-table-wrapper"> 69 <table className="markdown-table"> 70 {children} 71 </table> 72 </div> 73 ), 74 th: ({ children }) => <th>{children}</th>, 75 td: ({ children }) => <td>{children}</td>, 76 h1: ({ children }) => <h2 style={{ margin: '12px 0 4px' }}>{children}</h2>, 77 h2: ({ children }) => <h3 style={{ margin: '12px 0 4px' }}>{children}</h3>, 78 h3: ({ children }) => <h4 style={{ margin: '12px 0 4px' }}>{children}</h4>, 79 li: ({ children }) => <li style={{ marginLeft: 16 }}>{children}</li>, 80 p: ({ children }) => <p style={{ margin: '4px 0' }}>{children}</p>, 81 }} 82 > 83 {content} 84 </ReactMarkdown> 85 </div> 86 )); 87 88 export default MarkdownContent;