import React, { useState, useEffect, useCallback, useMemo } from 'react'; import { markdownService } from '../services/markdownService'; import { MarkdownParseResult, MarkdownNode, OutlineItem, LinkInfo, ValidationResult, MarkdownParserConfig, ValidationSeverity, MarkdownNodeType, } from '../types/markdown'; /** * Markdown解析渲染器属性 */ interface MarkdownParserRendererProps { /** Markdown文本内容 */ content: string; /** 解析器配置 */ config?: MarkdownParserConfig; /** 是否显示大纲 */ showOutline?: boolean; /** 是否显示链接列表 */ showLinks?: boolean; /** 是否显示验证结果 */ showValidation?: boolean; /** 是否显示位置信息 */ showPositionInfo?: boolean; /** 是否启用实时解析 */ enableRealTimeParsing?: boolean; /** 自定义样式类名 */ className?: string; /** 点击节点时的回调 */ onNodeClick?: (node: MarkdownNode) => void; /** 解析完成时的回调 */ onParseComplete?: (result: MarkdownParseResult) => void; } /** * 基于Tree-sitter的Markdown解析渲染器组件 * 支持原文引用、位置信息显示和交互式节点选择 */ export const MarkdownParserRenderer: React.FC = ({ content, config, showOutline = false, showLinks = false, showValidation = false, showPositionInfo = false, enableRealTimeParsing = false, className = '', onNodeClick, onParseComplete, }) => { const [parseResult, setParseResult] = useState(null); const [outline, setOutline] = useState([]); const [links, setLinks] = useState([]); const [validation, setValidation] = useState(null); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); const [selectedNode, setSelectedNode] = useState(null); /** * 解析Markdown内容 */ const parseContent = useCallback(async () => { if (!content.trim()) { setParseResult(null); setOutline([]); setLinks([]); setValidation(null); return; } setIsLoading(true); setError(null); try { // 解析主要内容 const result = await markdownService.parseMarkdown(content, config); setParseResult(result); onParseComplete?.(result); // 提取大纲 if (showOutline) { const outlineItems = await markdownService.extractOutline(content); setOutline(outlineItems); } // 提取链接 if (showLinks) { const linkItems = await markdownService.extractLinks(content); setLinks(linkItems); } // 验证文档 if (showValidation) { const validationResult = await markdownService.validateMarkdown(content); setValidation(validationResult); } } catch (err) { setError(err instanceof Error ? err.message : 'Unknown error occurred'); } finally { setIsLoading(false); } }, [content, config, showOutline, showLinks, showValidation, onParseComplete]); /** * 实时解析效果 */ useEffect(() => { if (enableRealTimeParsing) { const timeoutId = setTimeout(parseContent, 300); // 防抖 return () => clearTimeout(timeoutId); } }, [content, enableRealTimeParsing, parseContent]); /** * 初始解析 */ useEffect(() => { if (!enableRealTimeParsing) { parseContent(); } }, [parseContent, enableRealTimeParsing]); /** * 渲染单个节点 */ const renderNode = useCallback((node: MarkdownNode, depth: number = 0): React.ReactNode => { const handleNodeClick = () => { setSelectedNode(node); onNodeClick?.(node); }; const isSelected = selectedNode === node; const nodeClasses = ` markdown-node markdown-node-${node.node_type.toLowerCase()} ${isSelected ? 'selected' : ''} ${showPositionInfo ? 'with-position' : ''} cursor-pointer hover:bg-gray-100 transition-colors `.trim(); const positionInfo = showPositionInfo ? ( [{markdownService.formatRange(node.range.start, node.range.end)}] ) : null; switch (node.node_type) { case MarkdownNodeType.Heading: const level = parseInt(node.attributes.level || '1'); const HeadingTag = `h${Math.min(level, 6)}` as keyof JSX.IntrinsicElements; return ( {markdownService.extractTextContent(node)} {positionInfo} ); case MarkdownNodeType.Paragraph: return (

{node.children.map((child, index) => renderNode(child, depth + 1))} {positionInfo}

); case MarkdownNodeType.CodeBlock: const language = node.attributes.language || 'text'; return (
            
              {markdownService.extractTextContent(node)}
            
            {positionInfo}
          
); case MarkdownNodeType.Link: const url = node.attributes.url || '#'; const title = node.attributes.title; return ( { e.preventDefault(); handleNodeClick(); }} style={{ marginLeft: `${depth * 20}px` }} > {markdownService.extractTextContent(node)} {positionInfo} ); case MarkdownNodeType.Image: const src = node.attributes.src || ''; const alt = node.attributes.alt || ''; return ( {alt} ); case MarkdownNodeType.List: const ListTag = node.content.trim().startsWith('1.') ? 'ol' : 'ul'; return ( {node.children.map((child, index) => renderNode(child, depth + 1))} {positionInfo} ); case MarkdownNodeType.ListItem: return (
  • {node.children.map((child, index) => renderNode(child, depth + 1))} {positionInfo}
  • ); case MarkdownNodeType.Emphasis: return ( {node.children.map((child, index) => renderNode(child, depth + 1))} {positionInfo} ); case MarkdownNodeType.Strong: return ( {node.children.map((child, index) => renderNode(child, depth + 1))} {positionInfo} ); case MarkdownNodeType.Text: return ( {node.content} {positionInfo} ); default: return (
    {node.children.map((child, index) => renderNode(child, depth + 1))} {positionInfo}
    ); } }, [selectedNode, showPositionInfo, onNodeClick]); /** * 渲染大纲 */ const renderOutline = useMemo(() => { if (!showOutline || outline.length === 0) return null; return (

    目录

      {outline.map((item, index) => (
    • { // 可以添加滚动到对应位置的逻辑 console.log('Navigate to:', item); }} > H{item.level} {item.title} {showPositionInfo && ( [Line {item.range.start.line + 1}] )}
    • ))}
    ); }, [showOutline, outline, showPositionInfo]); /** * 渲染链接列表 */ const renderLinks = useMemo(() => { if (!showLinks || links.length === 0) return null; return (

    链接

      {links.map((link, index) => (
    • {link.link_type === 'Image' ? '🖼️' : '🔗'} {link.text || link.url} {showPositionInfo && ( [Line {link.range.start.line + 1}] )}
    • ))}
    ); }, [showLinks, links, showPositionInfo]); /** * 渲染验证结果 */ const renderValidation = useMemo(() => { if (!showValidation || !validation) return null; const { is_valid, issues } = validation; return (

    验证结果 {is_valid ? '✅' : '❌'}

    {issues.length > 0 && (
      {issues.map((issue, index) => (
    • {issue.severity === ValidationSeverity.Error ? '❌' : issue.severity === ValidationSeverity.Warning ? '⚠️' : 'ℹ️'}
      {issue.message}
      {showPositionInfo && (
      Line {issue.range.start.line + 1}
      )}
    • ))}
    )}
    ); }, [showValidation, validation, showPositionInfo]); if (isLoading) { return (
    解析中...
    ); } if (error) { return (

    解析错误

    {error}

    ); } return (
    {renderOutline} {renderLinks} {renderValidation}
    {parseResult ? (
    {renderNode(parseResult.root)}
    ) : (
    暂无内容
    )}
    {selectedNode && (

    选中节点信息

    类型: {selectedNode.node_type}
    位置: {markdownService.formatRange(selectedNode.range.start, selectedNode.range.end)}
    内容长度: {selectedNode.content.length} 字符
    {Object.keys(selectedNode.attributes).length > 0 && (
    属性: {JSON.stringify(selectedNode.attributes, null, 2)}
    )}
    )} {parseResult && (

    解析统计

    总节点数: {parseResult.statistics.total_nodes}
    错误节点数: {parseResult.statistics.error_nodes}
    解析耗时: {parseResult.statistics.parse_time_ms}ms
    最大深度: {parseResult.statistics.max_depth}
    )}
    ); }; export default MarkdownParserRenderer;