import { useMemo } from 'react'; import { View, Text, RichText } from '@tarojs/components'; // 定义RichText节点类型 interface RichTextNode { name?: string; attrs?: Record; children?: (RichTextNode | { type: 'text'; text: string })[]; type?: 'text'; text?: string; } // 列表项接口(包含内容和可能的子列表) interface ListItem { content: React.ReactNode; children?: ListLevel[]; // 嵌套在该项下的子列表 key: string; // 唯一标识 } // 列表层级信息接口 interface ListLevel { type: 'ul' | 'ol'; items: ListItem[]; depth: number; // 缩进深度,用于判断嵌套关系 } // 生成稳定的key(基于内容哈希) const getStableKey = (content: string, index: number = 0): string => { let hash = 0; const contentWithIndex = content + index.toString(); for (let i = 0; i < contentWithIndex.length; i++) { const char = contentWithIndex.charCodeAt(i); hash = ((hash << 5) - hash) + char; hash = hash & hash; // 转换为32位整数 } return `hash-${Math.abs(hash)}`; }; // 行内元素解析规则 const inlineRules = [ { regex: /`([^`]+)`/, createNode: (content: string) => ({ name: 'code', attrs: {}, children: parseInlineToNodes(content) }) }, { regex: /!\[(.*?)\]\((.*?)\)/, createNode: (alt: string, url: string) => ({ name: 'img', attrs: { src: url, alt }, children: [] }) }, { regex: /\[(.*?)\]\((.*?)\)/, createNode: (text: string, url: string) => ({ name: 'a', attrs: { href: url }, children: parseInlineToNodes(text) }) }, { regex: /\*\*(.*?)\*\*/, createNode: (content: string) => ({ name: 'strong', attrs: {}, children: parseInlineToNodes(content) }) }, { regex: /__(.*?)__/, createNode: (content: string) => ({ name: 'strong', attrs: {}, children: parseInlineToNodes(content) }) }, { regex: /\*(.*?)\*/, createNode: (content: string) => ({ name: 'em', attrs: {}, children: parseInlineToNodes(content) }) }, { regex: /_(.*?)_/, createNode: (content: string) => ({ name: 'em', attrs: {}, children: parseInlineToNodes(content) }) } ]; // 递归解析行内元素 const parseInlineToNodes = (text: string): RichTextNode[] => { const nodes: RichTextNode[] = []; let remaining = text; while (remaining.length > 0) { let matched = false; let bestMatch: { index: number; rule: any; groups: string[] } | null = null; for (const rule of inlineRules) { const matches = remaining.match(rule.regex); if (matches && matches.index !== undefined) { if (!bestMatch || matches.index < bestMatch.index) { bestMatch = { index: matches.index, rule, groups: matches.slice(1) }; } matched = true; } } if (bestMatch) { if (bestMatch.index > 0) { nodes.push({ type: 'text', text: remaining.substring(0, bestMatch.index) }); } const node = bestMatch.rule.createNode(...bestMatch.groups); nodes.push(node); const fullMatch = remaining.match(bestMatch.rule.regex)![0]; remaining = remaining.substring(bestMatch.index + fullMatch.length); } else if (matched) { nodes.push({ type: 'text', text: remaining }); break; } else { nodes.push({ type: 'text', text: remaining }); break; } } return nodes; }; // 计算列表项的缩进深度(4个空格为一级) const getIndentDepth = (line: string): number => { const spaces = line.match(/^\s*/)![0]; return Math.floor(spaces.length / 4); // 每4个空格算一级缩进 }; // 渲染列表项及其子列表 const renderListItems = (items: ListItem[], level: number) => { return items.map((item, index) => { // 渲染子列表 const renderChildLists = () => { if (!item.children || item.children.length === 0) return null; return item.children.map((childLevel, childIndex) => { const childListKey = getStableKey(`child-list-${childLevel.type}-${childIndex}`, index); return ( {renderListItems(childLevel.items, level + 1)} ); }); }; return ( {item.content} {renderChildLists()} ); }); }; interface MarkdownParserProps { content: string; } const MarkdownParser = ({ content }: MarkdownParserProps) => { const parsedElements = useMemo(() => { const lines = content.split('\n'); const elements: React.ReactNode[] = []; let inCodeBlock = false; let currentCodeBlock = ''; let codeLang = ''; // 列表栈:管理嵌套列表层级(栈顶为当前层级) const listStack: ListLevel[] = []; // 刷新当前列表(将栈中所有列表弹出并嵌套组装) const flushLists = () => { if (listStack.length === 0) return; // 从顶层渲染列表 listStack.forEach((level, index) => { const listKey = getStableKey(`list-${level.type}-${level.depth}-${index}`); elements.push( {renderListItems(level.items, index)} ); }); // 清空栈 listStack.length = 0; }; // 处理代码块 const flushCodeBlock = () => { if (inCodeBlock && currentCodeBlock) { const codeKey = getStableKey(currentCodeBlock); elements.push( {codeLang && {codeLang}} {currentCodeBlock} ); inCodeBlock = false; currentCodeBlock = ''; codeLang = ''; } }; lines.forEach((line, lineIndex) => { // 处理空行(保留结构,但不添加多余元素) const trimmedLine = line.trim(); if (!trimmedLine && !inCodeBlock) return; // 处理代码块 if (/^```/.test(trimmedLine)) { flushLists(); // 遇到代码块,先刷新所有列表 if (inCodeBlock) { flushCodeBlock(); } else { inCodeBlock = true; codeLang = trimmedLine.replace(/^```/, '').trim(); } return; } if (inCodeBlock) { currentCodeBlock += line + '\n'; return; } // 处理标题 const headingMatch = line.match(/^\s*#{1,6}\s+/); if (headingMatch) { flushLists(); // 遇到标题,刷新所有列表 const level = headingMatch[0].match(/#/g)?.length || 1; const text = line.replace(/^\s*#{1,6}\s+/, ''); const headingKey = getStableKey(`h${level}-${text}`, lineIndex); elements.push( ); return; } // 处理无序列表和有序列表(统一显示为无序列表) const listMatch = line.match(/^\s*([-*+]|\d+\.)\s+/); if (listMatch) { const depth = getIndentDepth(line); // 当前列表项的缩进深度 // 提取文本内容(移除列表标记) const text = line.replace(/^\s*([-*+]|\d+\.)\s+/, ''); const itemKey = getStableKey(`list-item-${text}`, lineIndex); // 创建新列表项 const newItem: ListItem = { content: , key: itemKey }; // 1. 调整列表栈到当前深度(弹出过深的层级) while (listStack.length > depth) { const poppedLevel = listStack.pop()!; // 如果栈不为空,将弹出的层级作为上一级最后一个项的子列表 if (listStack.length > 0) { const parentLevel = listStack[listStack.length - 1]; if (parentLevel.items.length > 0) { const lastParentItem = parentLevel.items[parentLevel.items.length - 1]; if (!lastParentItem.children) { lastParentItem.children = []; } lastParentItem.children.push(poppedLevel); } } else { // 如果栈为空,直接渲染弹出的层级 const listKey = getStableKey(`unexpected-list-${poppedLevel.type}-${poppedLevel.depth}`); elements.push( {renderListItems(poppedLevel.items, 0)} ); } } // 2. 如果栈为空或当前深度大于栈顶深度,创建新层级(统一使用ul类型) if (listStack.length === 0 || depth > listStack[listStack.length - 1].depth) { listStack.push({ type: 'ul', // 所有列表都显示为无序列表 items: [newItem], depth }); } else { // 3. 否则添加到当前层级 listStack[listStack.length - 1].items.push(newItem); } return; } // 处理引用 const quoteMatch = line.match(/^\s*>\s+/); if (quoteMatch) { flushLists(); const text = line.replace(/^\s*>\s+/, ''); const quoteKey = getStableKey(`quote-${text}`, lineIndex); elements.push( ); return; } // 处理分隔线 if (/^\s*[-*]{3,}\s*$/.test(line)) { flushLists(); const hrKey = getStableKey(`hr-${line}`, lineIndex); elements.push( ); return; } // 处理普通文本行 flushLists(); // 普通文本前刷新所有列表 const paragraphKey = getStableKey(`p-${line}`, lineIndex); elements.push( ); }); // 解析结束后刷新剩余列表和代码块 flushLists(); flushCodeBlock(); return elements; }, [content]); return ( {parsedElements} ); }; export default MarkdownParser;