export function Markdown({ content }) {
if (!content) return null;
const blocks = [];
const codeBlockRe = /```(\w*)\n?([\s\S]*?)```/g;
let last = 0;
let match;
while ((match = codeBlockRe.exec(content)) !== null) {
if (match.index > last) {
blocks.push({ type: "text", value: content.slice(last, match.index) });
}
blocks.push({ type: "code", lang: match[1], value: match[2] });
last = match.index + match[0].length;
}
if (last < content.length) {
blocks.push({ type: "text", value: content.slice(last) });
}
return (
{blocks.map((block, i) =>
block.type === "code" ? (
{block.value.trimEnd()}
) : (
)
)}
);
}
function TextBlock({ text }) {
const lines = text.split("\n");
const elements = [];
let i = 0;
while (i < lines.length) {
const line = lines[i];
// Heading
const hMatch = line.match(/^(#{1,3})\s+(.+)/);
if (hMatch) {
const level = hMatch[1].length;
const sizes = { 1: "1.2rem", 2: "1.05rem", 3: "0.95rem" };
elements.push(
{inlineMarkdown(hMatch[2])}
);
i++;
continue;
}
// Unordered list
if (/^[-*]\s+/.test(line)) {
const items = [];
while (i < lines.length && /^[-*]\s+/.test(lines[i])) {
items.push({inlineMarkdown(lines[i].replace(/^[-*]\s+/, ""))});
i++;
}
elements.push();
continue;
}
// Ordered list
if (/^\d+\.\s+/.test(line)) {
const items = [];
while (i < lines.length && /^\d+\.\s+/.test(lines[i])) {
items.push({inlineMarkdown(lines[i].replace(/^\d+\.\s+/, ""))});
i++;
}
elements.push({items}
);
continue;
}
// Horizontal rule
if (/^---+$/.test(line.trim())) {
elements.push(
);
i++;
continue;
}
// Empty line → small gap
if (line.trim() === "") {
elements.push();
i++;
continue;
}
// Regular paragraph line
elements.push({inlineMarkdown(line)}
);
i++;
}
return <>{elements}>;
}
function inlineMarkdown(text) {
const parts = [];
const re = /(\*\*(.+?)\*\*|\*(.+?)\*|`([^`]+)`)/g;
let last = 0;
let m;
while ((m = re.exec(text)) !== null) {
if (m.index > last) parts.push(text.slice(last, m.index));
if (m[2] !== undefined) {
parts.push({m[2]});
} else if (m[3] !== undefined) {
parts.push({m[3]});
} else if (m[4] !== undefined) {
parts.push(
{m[4]}
);
}
last = m.index + m[0].length;
}
if (last < text.length) parts.push(text.slice(last));
return parts.length === 1 && typeof parts[0] === "string" ? parts[0] : parts;
}