import changelogRaw from 'virtual:changelog'; import { useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Badge } from '@/components/ui/badge'; import { type ChangelogEntry, parseChangelog } from '@/lib/utils/parseChangelog'; function renderMarkdown(md: string): React.ReactNode[] { const lines = md.split('\n'); const elements: React.ReactNode[] = []; let i = 0; while (i < lines.length) { const line = lines[i]; // Skip empty lines if (line.trim() === '') { i++; continue; } // Tables — collect all lines starting with | if (line.trim().startsWith('|')) { const tableLines: string[] = []; while (i < lines.length && lines[i].trim().startsWith('|')) { tableLines.push(lines[i]); i++; } elements.push(renderTable(tableLines, elements.length)); continue; } // Headings if (line.startsWith('#### ')) { elements.push(
{inlineMarkdown(line)}
, ); i++; } return elements; } function renderTable(tableLines: string[], keyBase: number): React.ReactNode { const parseRow = (line: string) => line .split('|') .slice(1, -1) .map((c) => c.trim()); const headers = parseRow(tableLines[0]); // Skip separator line (index 1) const rows = tableLines.slice(2).map(parseRow); return (| {inlineMarkdown(h)} | ))}
|---|
| {inlineMarkdown(cell)} | ))}
{match[2]}
,
);
} else if (match[3] !== undefined && match[4] !== undefined) {
// Link
parts.push(
{match[3]}
,
);
}
lastIndex = match.index + match[0].length;
match = inlineRe.exec(text);
}
if (lastIndex < text.length) {
parts.push(text.slice(lastIndex));
}
return parts.length === 1 ? parts[0] : parts;
}
function ChangelogEntryCard({ entry }: { entry: ChangelogEntry }) {
const { t } = useTranslation();
const [expanded, setExpanded] = useState(false);
const content = useMemo(() => renderMarkdown(entry.body), [entry.body]);
const isLong = entry.body.split('\n').length > 12;
return (