From 3e89d2998588f8e35c524e523ba2ee853f0f6475 Mon Sep 17 00:00:00 2001 From: Maze Winther Date: Mon, 20 Apr 2026 11:31:17 +0200 Subject: refactor: restructure files to their domains, new preview overlay system, and dep graph --- .../components/changelog-notification.tsx | 87 ------------ .../changelog/components/copy-markdown-button.tsx | 98 ------------- apps/web/src/lib/changelog/components/release.tsx | 153 --------------------- 3 files changed, 338 deletions(-) delete mode 100644 apps/web/src/lib/changelog/components/changelog-notification.tsx delete mode 100644 apps/web/src/lib/changelog/components/copy-markdown-button.tsx delete mode 100644 apps/web/src/lib/changelog/components/release.tsx (limited to 'apps/web/src/lib/changelog/components') diff --git a/apps/web/src/lib/changelog/components/changelog-notification.tsx b/apps/web/src/lib/changelog/components/changelog-notification.tsx deleted file mode 100644 index 4433df30..00000000 --- a/apps/web/src/lib/changelog/components/changelog-notification.tsx +++ /dev/null @@ -1,87 +0,0 @@ -"use client"; - -import { useState, useEffect } from "react"; -import Link from "next/link"; -import { Cancel01Icon } from "@hugeicons/core-free-icons"; -import { HugeiconsIcon } from "@hugeicons/react"; -import { Button } from "@/components/ui/button"; -import { getSortedReleases } from "../utils"; -import type { Release } from "../utils"; - -const STORAGE_KEY = "last-seen-version"; - -export function ChangelogNotification() { - const [release, setRelease] = useState(null); - - useEffect(() => { - const releases = getSortedReleases(); - const latest = releases[0]; - if (!latest) return; - - let storedVersion: string | null = null; - try { - storedVersion = localStorage.getItem(STORAGE_KEY); - } catch { - // localStorage unavailable - } - - const isOutdated = - storedVersion === null || - storedVersion.localeCompare(latest.version, undefined, { - numeric: true, - }) < 0; - - // TODO(v0.4): revert to the standard "null = first-time visitor, record silently" - // path. The null case intentionally shows the card for this release so existing - // users who never had the key get the 0.3.0 announcement. - if (!isOutdated) return; - - try { - localStorage.setItem(STORAGE_KEY, latest.version); - } catch { - // ignore - } - - setRelease(latest); - }, []); - - if (!release) return null; - - return ( -
-
-
- - {release.title} - - - v{release.version} - -
- -
- - {release.summary && ( -

- {release.summary} -

- )} - -
- -
-
- ); -} diff --git a/apps/web/src/lib/changelog/components/copy-markdown-button.tsx b/apps/web/src/lib/changelog/components/copy-markdown-button.tsx deleted file mode 100644 index bc7e94bf..00000000 --- a/apps/web/src/lib/changelog/components/copy-markdown-button.tsx +++ /dev/null @@ -1,98 +0,0 @@ -"use client"; - -import { useState } from "react"; -import { CheckIcon, ClipboardIcon } from "lucide-react"; -import { - getSectionTitle, - groupAndOrderChanges, - isSectionCollapsible, -} from "../utils"; -import type { Change } from "../utils"; -import { cn } from "@/utils/ui"; -import { Button } from "@/components/ui/button"; - -function buildMarkdown({ - description, - changes, -}: { - description?: string; - changes: Change[]; -}): string { - const lines: string[] = []; - - if (description) { - lines.push(description, ""); - } - - const { grouped, orderedTypes } = groupAndOrderChanges({ changes }); - - for (const type of orderedTypes) { - if (isSectionCollapsible({ type })) { - lines.push( - buildCollapsibleMarkdownSection({ - title: getSectionTitle({ type }), - changes: grouped[type], - }), - "", - ); - continue; - } - - lines.push(`## ${getSectionTitle({ type })}`); - for (const change of grouped[type]) { - lines.push(`- ${change.text}`); - } - lines.push(""); - } - - return lines.join("\n").trimEnd(); -} - -function buildCollapsibleMarkdownSection({ - title, - changes, -}: { - title: string; - changes: Change[]; -}): string { - const bulletLines = changes.map((change) => `- ${change.text}`).join("\n"); - - return `
\n${title}\n\n${bulletLines}\n\n
`; -} - -export function CopyMarkdownButton({ - description, - changes, -}: { - description?: string; - changes: Change[]; -}) { - const [copied, setCopied] = useState(false); - - const handleCopy = async () => { - const markdown = buildMarkdown({ description, changes }); - await navigator.clipboard.writeText(markdown); - setCopied(true); - setTimeout(() => setCopied(false), 2000); - }; - - return ( - - ); -} diff --git a/apps/web/src/lib/changelog/components/release.tsx b/apps/web/src/lib/changelog/components/release.tsx deleted file mode 100644 index 475b0a6d..00000000 --- a/apps/web/src/lib/changelog/components/release.tsx +++ /dev/null @@ -1,153 +0,0 @@ -import type { ReactNode } from "react"; -import Link from "next/link"; -import { cn } from "@/utils/ui"; -import { ReactMarkdownWrapper } from "@/components/ui/react-markdown-wrapper"; -import { - getSectionTitle, - groupAndOrderChanges, - isSectionCollapsible, - type Change, - type Release, -} from "../utils"; -import { ArrowRightIcon } from "@hugeicons/core-free-icons"; -import { HugeiconsIcon } from "@hugeicons/react"; - -export function ReleaseArticle({ - variant, - isLatest, - children, -}: { - variant: "list" | "detail"; - isLatest?: boolean; - children: ReactNode; -}) { - if (variant === "list") { - return ( -
-
-
-
-
{children}
-
- ); - } - - return
{children}
; -} - -export function ReleaseMeta({ release }: { release: Release }) { - return ( - - {release.version} — {release.date} - - ); -} - -const titleSizes: Record<"h1" | "h2", string> = { - h1: "text-4xl", - h2: "text-2xl", -}; - -export function ReleaseTitle({ - as: As, - href, - children, -}: { - as: "h1" | "h2"; - href?: string; - children: ReactNode; -}) { - return ( - - {href ? ( - - {children} - - ) : ( - children - )} - - ); -} - -export function ReleaseDescription({ children }: { children: ReactNode }) { - return ( -

- {children} -

- ); -} - -export function ReleaseChanges({ release }: { release: Release }) { - const { grouped, orderedTypes } = groupAndOrderChanges({ - changes: release.changes, - }); - - return ( -
- {orderedTypes.map((type) => ( - - ))} -
- ); -} - -function ReleaseChangeSection({ - type, - changes, -}: { - type: string; - changes: Change[]; -}) { - const title = getSectionTitle({ type }); - - if (isSectionCollapsible({ type })) { - return ( -
- - {title}: - - - -
- ); - } - - return ( -
-

{title}:

- -
- ); -} - -function ReleaseChangeList({ - changes, - className, -}: { - changes: Change[]; - className?: string; -}) { - return ( - - ); -} -- cgit v1.3.1