diff options
Diffstat (limited to 'apps/web')
| -rw-r--r-- | apps/web/content-collections.ts | 32 | ||||
| -rw-r--r-- | apps/web/content/changelog/0.1.0.md | 35 | ||||
| -rw-r--r-- | apps/web/content/changelog/0.2.0.md | 13 | ||||
| -rw-r--r-- | apps/web/next.config.ts | 3 | ||||
| -rw-r--r-- | apps/web/package.json | 2 | ||||
| -rw-r--r-- | apps/web/src/app/changelog/page.tsx | 142 | ||||
| -rw-r--r-- | apps/web/tsconfig.json | 3 |
7 files changed, 228 insertions, 2 deletions
diff --git a/apps/web/content-collections.ts b/apps/web/content-collections.ts new file mode 100644 index 00000000..b3028846 --- /dev/null +++ b/apps/web/content-collections.ts @@ -0,0 +1,32 @@ +import { defineCollection, defineConfig } from "@content-collections/core"; +import { z } from "zod"; + +const changelog = defineCollection({ + name: "changelog", + directory: "content/changelog", + include: "*.md", + schema: z.object({ + version: z.string(), + date: z.string(), + title: z.string(), + description: z.string().optional(), + changes: z.array( + z.object({ + type: z.string(), + text: z.string(), + }), + ), + }), + transform: async (doc, { collection }) => { + const allDocs = await collection.documents(); + const sorted = [...allDocs].sort((a, b) => + b.version.localeCompare(a.version, undefined, { numeric: true }), + ); + const isLatest = sorted[0]?.version === doc.version; + return { ...doc, isLatest }; + }, +}); + +export default defineConfig({ + content: [changelog], +}); diff --git a/apps/web/content/changelog/0.1.0.md b/apps/web/content/changelog/0.1.0.md new file mode 100644 index 00000000..d1c21bf0 --- /dev/null +++ b/apps/web/content/changelog/0.1.0.md @@ -0,0 +1,35 @@ +--- +version: "0.1.0" +date: "2026-02-23" +title: "Editor Foundation" +description: "This first release focuses on making editing faster, clearer, and more reliable for day-to-day use." +changes: + - type: new + text: "Rebuilt the properties panel from scratch. Number inputs now support math expressions and click-drag scrubbing instead of just typing values in." + - type: new + text: "The properties panel went from a flat list of basic text styles to organized sections: transform, blending, typography, spacing, and background controls. Text elements finally have position, scale, and rotation." + - type: new + text: "New color picker with eyedropper, opacity control, and multiple color formats." + - type: new + text: "Bookmarks now support notes, color labels, and optional duration to plan scenes more clearly." + - type: new + text: "Move, scale, and rotate elements directly in the preview panel." + - type: new + text: "Blend modes (Multiply, Screen, Overlay, etc). Only available on text elements for now." + - type: new + text: "Paste images, videos, or audio from your clipboard straight into the editor." + - type: new + text: "Hold Shift while moving or resizing to temporarily turn off snapping." + - type: improved + text: "Expanded font selection from 7 to over 1,000." + - type: improved + text: "Fonts load much faster." + - type: improved + text: "All asset panel tabs now share a consistent layout." + - type: improved + text: "Text rendering properly supports multiple lines, line height, and letter spacing." + - type: improved + text: "Better contrast in the dark theme." + - type: fixed + text: "Fixed undo/redo for drag-and-drop so dropped items are reliably undoable." +--- diff --git a/apps/web/content/changelog/0.2.0.md b/apps/web/content/changelog/0.2.0.md new file mode 100644 index 00000000..ec3a99e2 --- /dev/null +++ b/apps/web/content/changelog/0.2.0.md @@ -0,0 +1,13 @@ +--- +version: "0.2.0" +date: "2026-02-29" +title: "Motion & Effects" +description: "This release adds the foundation for motion and effects." +changes: + - type: new + text: "Keyframe animation system, starting with transform properties (position, scale, rotation)." + - type: new + text: "Effects system (with our first effect: Blur!)" + - type: fixed + text: "Fixed an issue where click-and-drag selection on one timeline track could accidentally select items from the track below." +--- diff --git a/apps/web/next.config.ts b/apps/web/next.config.ts index c3676f0f..2f509d2a 100644 --- a/apps/web/next.config.ts +++ b/apps/web/next.config.ts @@ -1,5 +1,6 @@ import type { NextConfig } from "next"; import { withBotId } from "botid/next/config"; +import { withContentCollections } from "@content-collections/next"; const nextConfig: NextConfig = { turbopack: { @@ -54,4 +55,4 @@ const nextConfig: NextConfig = { }, }; -export default withBotId(nextConfig); +export default withContentCollections(withBotId(nextConfig)); diff --git a/apps/web/package.json b/apps/web/package.json index 90aa5d3b..8094c548 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -87,6 +87,8 @@ "zustand": "^5.0.2" }, "devDependencies": { + "@content-collections/core": "^0.14.1", + "@content-collections/next": "^0.2.11", "@napi-rs/canvas": "^0.1.92", "@tailwindcss/postcss": "^4.1.11", "@tailwindcss/typography": "^0.5.16", diff --git a/apps/web/src/app/changelog/page.tsx b/apps/web/src/app/changelog/page.tsx new file mode 100644 index 00000000..a9072485 --- /dev/null +++ b/apps/web/src/app/changelog/page.tsx @@ -0,0 +1,142 @@ +import type { Metadata } from "next"; +import { BasePage } from "@/app/base-page"; +import { Separator } from "@/components/ui/separator"; +import { cn } from "@/utils/ui"; +import { allChangelogs } from "content-collections"; + +export const metadata: Metadata = { + title: "Changelog - OpenCut", + description: "Every update, improvement, and fix to OpenCut — documented.", + openGraph: { + title: "Changelog - OpenCut", + description: "Every update, improvement, and fix to OpenCut — documented.", + type: "website", + }, +}; + +const knownSectionOrder = ["new", "improved", "fixed", "breaking"]; + +const knownSectionTitles: Record<string, string> = { + new: "Features", + improved: "Improvements", + fixed: "Fixes", + breaking: "Breaking Changes", +}; + +function getSectionTitle(type: string): string { + return ( + knownSectionTitles[type] ?? type.charAt(0).toUpperCase() + type.slice(1) + ); +} + +export default function ChangelogPage() { + const releases = [...allChangelogs].sort((a, b) => + b.version.localeCompare(a.version, undefined, { numeric: true }), + ); + + return ( + <BasePage title="Changelog" description="See what's new in OpenCut"> + <div className="mx-auto w-full max-w-3xl"> + <div className="relative"> + <div + aria-hidden + className="absolute top-2 bottom-0 left-[5px] w-px bg-border hidden sm:block" + /> + + <div className="flex flex-col"> + {releases.map((release, releaseIndex) => ( + <div key={release.version} className="flex flex-col"> + <ReleaseEntry release={release} /> + {releaseIndex < releases.length - 1 && ( + // ml-1.5 aligns with the center of the 11px timeline dot + <Separator className="my-10 sm:ml-1.5" /> + )} + </div> + ))} + </div> + </div> + </div> + </BasePage> + ); +} + +type Change = { type: string; text: string }; +type Release = (typeof allChangelogs)[number]; + +function groupAndOrderChanges({ changes }: { changes: Change[] }) { + const grouped = changes.reduce<Record<string, Change[]>>((acc, change) => { + if (!acc[change.type]) { + acc[change.type] = []; + } + acc[change.type].push(change); + return acc; + }, {}); + + const customTypes = Object.keys(grouped).filter( + (type) => !knownSectionOrder.includes(type), + ); + const orderedTypes = [ + ...knownSectionOrder.filter((type) => grouped[type]?.length > 0), + ...customTypes, + ]; + + return { grouped, orderedTypes }; +} + +function ReleaseEntry({ release }: { release: Release }) { + const { grouped: groupedChanges, orderedTypes } = groupAndOrderChanges({ + changes: release.changes, + }); + + return ( + <article className="relative sm:pl-10"> + <div aria-hidden className="absolute left-0 top-[3px] hidden sm:block"> + <div + className={cn( + "size-[11px] rounded-full border-[1.5px]", + release.isLatest + ? "border-foreground bg-foreground" + : "border-muted-foreground/30 bg-background", + )} + /> + </div> + + <div className="flex flex-col gap-5"> + <div className="flex items-center gap-2.5 flex-wrap"> + <span className="text-sm font-medium tracking-widest text-muted-foreground"> + {release.version} - {release.date} + </span> + </div> + + <div className="flex flex-col gap-4"> + <h2 className="text-2xl font-bold tracking-tight">{release.title}</h2> + {release.description && ( + <p className="text-base text-foreground leading-relaxed max-w-xl"> + {release.description} + </p> + )} + </div> + + <div className="flex flex-col gap-4"> + {orderedTypes.map((type) => ( + <div key={type} className="flex flex-col gap-1.5"> + <h3 className="text-base font-semibold text-foreground"> + {getSectionTitle(type)}: + </h3> + <ul className="list-disc pl-5 space-y-1.5"> + {groupedChanges[type].map((change) => ( + <li + key={change.text} + className="text-base text-foreground leading-relaxed" + > + {change.text} + </li> + ))} + </ul> + </div> + ))} + </div> + </div> + </article> + ); +} diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json index a8b420f7..3573338a 100644 --- a/apps/web/tsconfig.json +++ b/apps/web/tsconfig.json @@ -19,7 +19,8 @@ } ], "paths": { - "@/*": ["./src/*"] + "@/*": ["./src/*"], + "content-collections": ["./.content-collections/generated"] }, "forceConsistentCasingInFileNames": true }, |
