/**
* Lightweight markdown renderer for chat messages.
* Handles: bold, italic, code, tables, lists, blockquotes, links.
* No external dependency — pure React.
*/
import React from "react";
export function renderMarkdown(text: string): React.ReactNode {
const lines = text.split("\n");
const elements: React.ReactNode[] = [];
let i = 0;
while (i < lines.length) {
const line = lines[i];
// Table detection: line with | separators
if (line.includes("|") && line.trim().startsWith("|")) {
const tableLines: string[] = [];
while (i < lines.length && lines[i].includes("|") && lines[i].trim().startsWith("|")) {
tableLines.push(lines[i]);
i++;
}
if (tableLines.length >= 2) {
elements.push(
{renderInline(line.slice(2))}); i++; continue; } // Numbered list if (/^\d+\.\s/.test(line)) { const listItems: string[] = []; while (i < lines.length && /^\d+\.\s/.test(lines[i])) { listItems.push(lines[i].replace(/^\d+\.\s/, "")); i++; } elements.push(
{renderInline(line)}
); i++; } return <>{elements}>; } function MarkdownTable({ lines }: { lines: string[] }) { const parseRow = (line: string): string[] => line.split("|").slice(1, -1).map((cell) => cell.trim()); const headers = parseRow(lines[0]); // Skip separator row (line with ---) const dataStart = lines[1]?.includes("---") ? 2 : 1; const rows = lines.slice(dataStart).map(parseRow); // Detect alignment from separator row const alignments: ("left" | "center" | "right")[] = []; if (lines[1]?.includes("---")) { const sepCells = parseRow(lines[1]); for (const cell of sepCells) { if (cell.startsWith(":") && cell.endsWith(":")) alignments.push("center"); else if (cell.endsWith(":")) alignments.push("right"); else alignments.push("left"); } } return (| {renderInline(h)} | ))}
|---|
| {renderInline(cell)} | ))}
{codeMatch[2]}
);
remaining = codeMatch[3];
continue;
}
// Link: [text](url)
const linkMatch = remaining.match(/^([\s\S]*?)\[(.+?)\]\((.+?)\)([\s\S]*)/);
if (linkMatch) {
if (linkMatch[1]) parts.push({linkMatch[1]});
parts.push(
{linkMatch[2]}
);
remaining = linkMatch[4];
continue;
}
// Italic: *text*
const italicMatch = remaining.match(/^([\s\S]*?)\*(.+?)\*([\s\S]*)/);
if (italicMatch) {
if (italicMatch[1]) parts.push({italicMatch[1]});
parts.push({italicMatch[2]});
remaining = italicMatch[3];
continue;
}
// No match — push the rest
parts.push({remaining});
break;
}
return <>{parts}>;
}