6dd48b5c25
Entries are plain strings with occasional **bold**/`code` spans, never full markdown — added a tiny inline parser rather than pulling in react-markdown just for this. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
72 lines
2.8 KiB
TypeScript
72 lines
2.8 KiB
TypeScript
import { Fragment } from "react";
|
|
import { Badge } from "@/components/ui/badge";
|
|
import { Separator } from "@/components/ui/separator";
|
|
import { CHANGELOG } from "@/lib/changelog";
|
|
|
|
// Changelog entries are plain strings with occasional **bold** and `code`
|
|
// spans — never full markdown (lists, links, headings) — so a tiny inline
|
|
// parser is enough and keeps this off the react-markdown dependency used for
|
|
// full AI chat output elsewhere.
|
|
function InlineMarkdown({ text }: { text: string }) {
|
|
const parts = text.split(/(\*\*[^*]+\*\*|`[^`]+`)/g).filter(Boolean);
|
|
return (
|
|
<>
|
|
{parts.map((part, i) => {
|
|
if (part.startsWith("**") && part.endsWith("**")) {
|
|
return <strong key={i}>{part.slice(2, -2)}</strong>;
|
|
}
|
|
if (part.startsWith("`") && part.endsWith("`")) {
|
|
return <code key={i} className="rounded bg-muted px-1 py-0.5 text-xs">{part.slice(1, -1)}</code>;
|
|
}
|
|
return <Fragment key={i}>{part}</Fragment>;
|
|
})}
|
|
</>
|
|
);
|
|
}
|
|
|
|
export function ChangelogList() {
|
|
return (
|
|
<div className="space-y-8 max-w-2xl">
|
|
{CHANGELOG.map((entry, i) => (
|
|
<div key={entry.version} className="space-y-3">
|
|
<div className="flex items-center gap-2">
|
|
<Badge variant={i === 0 ? "default" : "secondary"}>v{entry.version}</Badge>
|
|
<span className="text-sm text-muted-foreground">{entry.date}</span>
|
|
</div>
|
|
|
|
{entry.notes && <p className="text-sm text-muted-foreground">{entry.notes}</p>}
|
|
|
|
{entry.added && entry.added.length > 0 && (
|
|
<div className="space-y-1.5">
|
|
<p className="text-xs font-medium uppercase tracking-wide text-muted-foreground">Added</p>
|
|
<ul className="space-y-1 text-sm list-disc pl-5">
|
|
{entry.added.map((line, j) => <li key={j}><InlineMarkdown text={line} /></li>)}
|
|
</ul>
|
|
</div>
|
|
)}
|
|
|
|
{entry.fixed && entry.fixed.length > 0 && (
|
|
<div className="space-y-1.5">
|
|
<p className="text-xs font-medium uppercase tracking-wide text-muted-foreground">Fixed</p>
|
|
<ul className="space-y-1 text-sm list-disc pl-5">
|
|
{entry.fixed.map((line, j) => <li key={j}><InlineMarkdown text={line} /></li>)}
|
|
</ul>
|
|
</div>
|
|
)}
|
|
|
|
{entry.security && entry.security.length > 0 && (
|
|
<div className="space-y-1.5">
|
|
<p className="text-xs font-medium uppercase tracking-wide text-muted-foreground">Security</p>
|
|
<ul className="space-y-1 text-sm list-disc pl-5">
|
|
{entry.security.map((line, j) => <li key={j}><InlineMarkdown text={line} /></li>)}
|
|
</ul>
|
|
</div>
|
|
)}
|
|
|
|
{i < CHANGELOG.length - 1 && <Separator className="mt-6" />}
|
|
</div>
|
|
))}
|
|
</div>
|
|
);
|
|
}
|