import { Cpu, ArrowLeft } from "lucide-react"; import Link from "next/link"; type Tag = "New" | "Improved" | "Fixed"; interface ChangelogEntry { version: string; date: string; title?: string; description?: string; items: { tag: Tag; text: string }[]; } function parseChangelog(md: string): ChangelogEntry[] { const lines = md.split("\n"); const entries: ChangelogEntry[] = []; let current: ChangelogEntry | null = null; let descLines: string[] = []; const finalize = () => { if (!current) return; const desc = descLines.join(" ").trim(); if (desc) current.description = desc; entries.push(current); current = null; descLines = []; }; for (const raw of lines) { const line = raw.trimEnd(); if (line.startsWith("## ")) { finalize(); const parts = line.slice(3).split(" — "); current = { version: parts[0]?.trim() ?? "", date: parts[1]?.trim() ?? "", title: parts[2]?.trim() || undefined, items: [], }; } else if (line.startsWith("- ") && current) { const text = line.slice(2); const m = text.match(/^\[(New|Improved|Fixed)\]\s*(.+)$/); if (m) { current.items.push({ tag: m[1] as Tag, text: m[2] }); } else { current.items.push({ tag: "Improved", text }); } } else if (current && current.items.length === 0 && line.trim() && !line.startsWith("#")) { descLines.push(line.trim()); } } finalize(); return entries; } function formatDate(iso: string): string { const d = new Date(iso + "T00:00:00Z"); if (Number.isNaN(d.getTime())) return iso; return d.toLocaleDateString("en-US", { year: "numeric", month: "long", day: "numeric", timeZone: "UTC", }); } const TAG_STYLES: Record = { New: "border-emerald-500/40 bg-emerald-500/10 text-emerald-600 dark:text-emerald-400", Improved: "border-blue-500/40 bg-blue-500/10 text-blue-600 dark:text-blue-400", Fixed: "border-orange-500/40 bg-orange-500/10 text-orange-600 dark:text-orange-400", }; function TagPill({ tag }: { tag: Tag }) { return ( {tag} ); } export function ChangelogTimeline({ content }: { content: string }) { const entries = parseChangelog(content); return (
Back to home Periscope

Changelog

What's new in Periscope.

    {entries.map((entry, i) => { const isLast = i === entries.length - 1; return (
  1. {!isLast && ( )}
    {formatDate(entry.date)} {entry.version && ( <> · v{entry.version} )}
    {entry.title && (

    {entry.title}

    )} {entry.description && (

    {entry.description}

    )}
      {entry.items.map((item, j) => (
    • {item.text}
    • ))}
  2. ); })}
); }