import React from 'react'; /** * Parses inline markdown: * - **bold** or __bold__ * - *italic* or _italic_ * - ~~strikethrough~~ * - `code` * - [label](url) */ function renderInlineTokens(text: string): React.ReactNode[] { if (!text) return []; const regex = /(`[^`]+`|\*\*[^*]+\*\*|__[^_]+__|~~[^~]+~~|\*[^*]+\*|_[^_]+_|\[([^\]]+)\]\(([^)]+)\))/g; const elements: React.ReactNode[] = []; let lastIndex = 0; let match: RegExpExecArray | null; while ((match = regex.exec(text)) !== null) { if (match.index > lastIndex) { elements.push(text.slice(lastIndex, match.index)); } const token = match[0]; const key = `${match.index}-${token}`; if (token.startsWith('`') && token.endsWith('`')) { elements.push( {token.slice(1, -1)} ); } else if ( (token.startsWith('**') && token.endsWith('**')) || (token.startsWith('__') && token.endsWith('__')) ) { elements.push( {token.slice(2, -2)} ); } else if ( (token.startsWith('*') && token.endsWith('*')) || (token.startsWith('_') && token.endsWith('_')) ) { elements.push( {token.slice(1, -1)} ); } else if (token.startsWith('~~') && token.endsWith('~~')) { elements.push( {token.slice(2, -2)} ); } else if (token.startsWith('[') && match[2] && match[3]) { const label = match[2]; const url = match[3]; elements.push( e.stopPropagation()} > {label} ); } else { elements.push(token); } lastIndex = regex.lastIndex; } if (lastIndex < text.length) { elements.push(text.slice(lastIndex)); } return elements; } /** * Parses inline markdown and fenced code blocks: * - ```lang\ncode\n``` * - **bold** or __bold__ * - *italic* or _italic_ * - ~~strikethrough~~ * - `code` * - [label](url) */ export function renderInlineMarkdown(text: string): React.ReactNode[] { if (!text) return []; if (text.includes('```')) { const codeBlockRegex = /(```[a-zA-Z0-9_-]*\r?\n[\s\S]*?```|```[\s\S]*?```)/g; const parts = text.split(codeBlockRegex); const nodes: React.ReactNode[] = []; parts.forEach((part, partIdx) => { if (part.startsWith('```') && part.endsWith('```')) { const lines = part.split(/\r?\n/); const firstLine = lines[0].slice(3).trim(); const codeLines = lines.slice(1, lines.length - 1); const code = codeLines.join('\n'); nodes.push(
{firstLine && (
{firstLine}
)}
              {code}
            
); } else if (part) { nodes.push(...renderInlineTokens(part)); } }); return nodes; } return renderInlineTokens(text); } export function MarkdownViewer({ content, className = '' }: { content: string; className?: string }) { if (!content) return null; const lines = content.split('\n'); const renderedNodes: React.ReactNode[] = []; let inCodeBlock = false; let codeBlockLines: string[] = []; for (let i = 0; i < lines.length; i++) { const line = lines[i]; if (line.trim().startsWith('```')) { if (inCodeBlock) { renderedNodes.push(
            {codeBlockLines.join('\n')}
          
); codeBlockLines = []; inCodeBlock = false; } else { inCodeBlock = true; codeBlockLines = []; } continue; } if (inCodeBlock) { codeBlockLines.push(line); continue; } const trimmed = line.trim(); if (trimmed.startsWith('### ')) { renderedNodes.push(

{renderInlineMarkdown(trimmed.slice(4))}

); } else if (trimmed.startsWith('## ')) { renderedNodes.push(

{renderInlineMarkdown(trimmed.slice(3))}

); } else if (trimmed.startsWith('# ')) { renderedNodes.push(

{renderInlineMarkdown(trimmed.slice(2))}

); } else if (trimmed.startsWith('> ')) { renderedNodes.push(
{renderInlineMarkdown(trimmed.slice(2))}
); } else if (trimmed.startsWith('- ') || trimmed.startsWith('* ')) { renderedNodes.push(
• {renderInlineMarkdown(trimmed.slice(2))}
); } else if (/^\d+\.\s/.test(trimmed)) { const matchNum = trimmed.match(/^(\d+)\.\s(.*)$/); renderedNodes.push(
{matchNum ? matchNum[1] + '.' : '•'} {renderInlineMarkdown(matchNum ? matchNum[2] : trimmed)}
); } else if (trimmed === '') { renderedNodes.push(
); } else { renderedNodes.push(
{renderInlineMarkdown(line)}
); } } if (inCodeBlock && codeBlockLines.length > 0) { renderedNodes.push(
        {codeBlockLines.join('\n')}
      
); } return
{renderedNodes}
; }