/**
* Markdown Rendering Utility
* Converts markdown text to styled React components
*/
import React from 'react';
/**
* Parse inline markdown syntax (code, bold, italic, links)
* Converts inline markdown elements within a text string to React components
*/
function parseInlineMarkdown(text: string): React.ReactNode {
const parts: React.ReactNode[] = [];
let remaining = text;
let keyIndex = 0;
while (remaining.length > 0) {
// Inline code
const codeMatch = remaining.match(/^`([^`]+)`/);
if (codeMatch) {
parts.push(
{codeMatch[1]}
);
remaining = remaining.slice(codeMatch[0].length);
continue;
}
// Bold
const boldMatch = remaining.match(/^\*\*([^*]+)\*\*/);
if (boldMatch) {
parts.push({boldMatch[1]});
remaining = remaining.slice(boldMatch[0].length);
continue;
}
// Italic
const italicMatch = remaining.match(/^\*([^*]+)\*/);
if (italicMatch) {
parts.push({italicMatch[1]});
remaining = remaining.slice(italicMatch[0].length);
continue;
}
// Links
const linkMatch = remaining.match(/^\[([^\]]+)\]\(([^)]+)\)/);
if (linkMatch) {
parts.push(
{linkMatch[1]}
);
remaining = remaining.slice(linkMatch[0].length);
continue;
}
// Regular character
const nextSpecial = remaining.search(/[`*\[]/);
if (nextSpecial === -1) {
parts.push(remaining);
break;
} else if (nextSpecial === 0) {
parts.push(remaining[0]);
remaining = remaining.slice(1);
} else {
parts.push(remaining.slice(0, nextSpecial));
remaining = remaining.slice(nextSpecial);
}
}
return parts.length === 1 ? parts[0] : <>{parts}>;
}
/**
* Render markdown content to React components
* Supports headers, lists, code blocks, tables, blockquotes, and inline formatting
*/
export function renderMarkdown(content: string): React.ReactNode {
const lines = content.split('\n');
const elements: React.ReactNode[] = [];
let i = 0;
let listItems: { type: 'ul' | 'ol'; items: React.ReactNode[] } | null = null;
let codeBlock: { language: string; lines: string[] } | null = null;
let tableRows: string[][] | null = null;
const flushList = () => {
if (listItems) {
if (listItems.type === 'ul') {
elements.push(
| {cell.trim()} | ))}
|---|
| {cell.trim()} | ))}
{codeBlock.lines.join('\n')}
);
codeBlock = null;
} else {
// Start code block
flushList();
flushTable();
const language = line.slice(3).trim();
codeBlock = { language, lines: [] };
}
i++;
continue;
}
// Inside code block
if (codeBlock) {
codeBlock.lines.push(line);
i++;
continue;
}
// Table row
if (line.startsWith('|') && line.endsWith('|')) {
flushList();
const cells = line.slice(1, -1).split('|');
if (!tableRows) {
tableRows = [];
}
tableRows.push(cells);
i++;
continue;
} else if (tableRows) {
flushTable();
}
// Headers
if (line.startsWith('#### ')) {
flushList();
elements.push(
{parseInlineMarkdown(line.slice(2))}); i++; continue; } // Empty line if (!line.trim()) { flushList(); i++; continue; } // Regular paragraph flushList(); elements.push(
{parseInlineMarkdown(line)}
); i++; } // Flush any remaining items flushList(); flushTable(); return