mirror of
https://github.com/OpenCut-app/OpenCut.git
synced 2026-07-13 21:52:53 +02:00
feat: add published status to changelog entries
This commit is contained in:
@@ -9,6 +9,7 @@ const changelog = defineCollection({
|
|||||||
content: z.string(),
|
content: z.string(),
|
||||||
version: z.string(),
|
version: z.string(),
|
||||||
date: z.string(),
|
date: z.string(),
|
||||||
|
published: z.boolean().default(true),
|
||||||
title: z.string(),
|
title: z.string(),
|
||||||
description: z.string().optional(),
|
description: z.string().optional(),
|
||||||
changes: z.array(
|
changes: z.array(
|
||||||
@@ -20,10 +21,11 @@ const changelog = defineCollection({
|
|||||||
}),
|
}),
|
||||||
transform: async (doc, { collection }) => {
|
transform: async (doc, { collection }) => {
|
||||||
const allDocs = await collection.documents();
|
const allDocs = await collection.documents();
|
||||||
const sorted = [...allDocs].sort((a, b) =>
|
const publishedDocs = allDocs.filter((entry) => entry.published !== false);
|
||||||
|
const sorted = [...publishedDocs].sort((a, b) =>
|
||||||
b.version.localeCompare(a.version, undefined, { numeric: true }),
|
b.version.localeCompare(a.version, undefined, { numeric: true }),
|
||||||
);
|
);
|
||||||
const isLatest = sorted[0]?.version === doc.version;
|
const isLatest = doc.published !== false && sorted[0]?.version === doc.version;
|
||||||
return { ...doc, isLatest };
|
return { ...doc, isLatest };
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
version: "0.1.0"
|
version: "0.1.0"
|
||||||
date: "2026-02-23"
|
date: "2026-02-23"
|
||||||
|
published: true
|
||||||
title: "Editor foundation"
|
title: "Editor foundation"
|
||||||
description: "This first release focuses on making editing faster, clearer, and more reliable for day-to-day use."
|
description: "This first release focuses on making editing faster, clearer, and more reliable for day-to-day use."
|
||||||
changes:
|
changes:
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
version: "0.2.0"
|
version: "0.2.0"
|
||||||
date: "2026-03-01"
|
date: "2026-03-01"
|
||||||
|
published: true
|
||||||
title: "Motion & effects"
|
title: "Motion & effects"
|
||||||
description: "This release adds the foundation for motion and effects, alongside many improvements & fixes."
|
description: "This release adds the foundation for motion and effects, alongside many improvements & fixes."
|
||||||
changes:
|
changes:
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
version: "0.3.0"
|
version: "0.3.0"
|
||||||
date: "2026-03-04"
|
date: "2026-03-04"
|
||||||
|
published: false
|
||||||
title: "Brand & polish"
|
title: "Brand & polish"
|
||||||
description: "This release adds a brand page with downloadable assets, alongside a few fixes and improvements."
|
description: "This release adds a brand page with downloadable assets, alongside a few fixes and improvements."
|
||||||
changes:
|
changes:
|
||||||
|
|||||||
@@ -2,9 +2,8 @@ import type { Metadata } from "next";
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { notFound } from "next/navigation";
|
import { notFound } from "next/navigation";
|
||||||
import { BasePage } from "@/app/base-page";
|
import { BasePage } from "@/app/base-page";
|
||||||
import { allChangelogs } from "content-collections";
|
|
||||||
import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react";
|
import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react";
|
||||||
import { getSortedReleases } from "../utils";
|
import { getReleaseByVersion, getSortedReleases } from "../utils";
|
||||||
import {
|
import {
|
||||||
ReleaseArticle,
|
ReleaseArticle,
|
||||||
ReleaseMeta,
|
ReleaseMeta,
|
||||||
@@ -17,12 +16,12 @@ import { CopyMarkdownButton } from "../components/copy-markdown-button";
|
|||||||
type Props = { params: Promise<{ version: string }> };
|
type Props = { params: Promise<{ version: string }> };
|
||||||
|
|
||||||
export async function generateStaticParams() {
|
export async function generateStaticParams() {
|
||||||
return allChangelogs.map((r) => ({ version: r.version }));
|
return getSortedReleases().map((release) => ({ version: release.version }));
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function generateMetadata({ params }: Props): Promise<Metadata> {
|
export async function generateMetadata({ params }: Props): Promise<Metadata> {
|
||||||
const { version } = await params;
|
const { version } = await params;
|
||||||
const release = allChangelogs.find((r) => r.version === version);
|
const release = getReleaseByVersion({ version });
|
||||||
if (!release) return {};
|
if (!release) return {};
|
||||||
return {
|
return {
|
||||||
title: `${release.title} (${release.version}) - OpenCut Changelog`,
|
title: `${release.title} (${release.version}) - OpenCut Changelog`,
|
||||||
@@ -33,10 +32,8 @@ export async function generateMetadata({ params }: Props): Promise<Metadata> {
|
|||||||
export default async function ReleaseDetailPage({ params }: Props) {
|
export default async function ReleaseDetailPage({ params }: Props) {
|
||||||
const { version } = await params;
|
const { version } = await params;
|
||||||
const releases = getSortedReleases();
|
const releases = getSortedReleases();
|
||||||
const index = releases.findIndex((r) => r.version === version);
|
const index = releases.findIndex((entry) => entry.version === version);
|
||||||
|
|
||||||
if (index === -1) notFound();
|
if (index === -1) notFound();
|
||||||
|
|
||||||
const release = releases[index];
|
const release = releases[index];
|
||||||
const newer = index > 0 ? releases[index - 1] : null;
|
const newer = index > 0 ? releases[index - 1] : null;
|
||||||
const older = index < releases.length - 1 ? releases[index + 1] : null;
|
const older = index < releases.length - 1 ? releases[index + 1] : null;
|
||||||
@@ -52,20 +49,20 @@ export default async function ReleaseDetailPage({ params }: Props) {
|
|||||||
All releases
|
All releases
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<ReleaseArticle variant="detail">
|
<ReleaseArticle variant="detail">
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<ReleaseMeta release={release} />
|
<ReleaseMeta release={release} />
|
||||||
<CopyMarkdownButton
|
<CopyMarkdownButton
|
||||||
description={release.description}
|
description={release.description}
|
||||||
changes={release.changes}
|
changes={release.changes}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
<ReleaseTitle as="h1">{release.title}</ReleaseTitle>
|
||||||
|
{release.description && (
|
||||||
|
<ReleaseDescription>{release.description}</ReleaseDescription>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<ReleaseTitle as="h1">{release.title}</ReleaseTitle>
|
|
||||||
{release.description && (
|
|
||||||
<ReleaseDescription>{release.description}</ReleaseDescription>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<ReleaseChanges release={release} />
|
<ReleaseChanges release={release} />
|
||||||
</ReleaseArticle>
|
</ReleaseArticle>
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ function buildMarkdown({
|
|||||||
const { grouped, orderedTypes } = groupAndOrderChanges({ changes });
|
const { grouped, orderedTypes } = groupAndOrderChanges({ changes });
|
||||||
|
|
||||||
for (const type of orderedTypes) {
|
for (const type of orderedTypes) {
|
||||||
lines.push(`## ${getSectionTitle(type)}`);
|
lines.push(`## ${getSectionTitle({ type })}`);
|
||||||
for (const change of grouped[type]) {
|
for (const change of grouped[type]) {
|
||||||
lines.push(`- ${change.text}`);
|
lines.push(`- ${change.text}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ export function ReleaseChanges({ release }: { release: Release }) {
|
|||||||
{orderedTypes.map((type) => (
|
{orderedTypes.map((type) => (
|
||||||
<div key={type} className="flex flex-col gap-1.5">
|
<div key={type} className="flex flex-col gap-1.5">
|
||||||
<h3 className="text-base font-semibold text-foreground">
|
<h3 className="text-base font-semibold text-foreground">
|
||||||
{getSectionTitle(type)}:
|
{getSectionTitle({ type })}:
|
||||||
</h3>
|
</h3>
|
||||||
<ul className="list-disc pl-5 space-y-1.5">
|
<ul className="list-disc pl-5 space-y-1.5">
|
||||||
{grouped[type].map((change) => (
|
{grouped[type].map((change) => (
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ const knownSectionTitles: Record<string, string> = {
|
|||||||
breaking: "Breaking Changes",
|
breaking: "Breaking Changes",
|
||||||
};
|
};
|
||||||
|
|
||||||
export function getSectionTitle(type: string): string {
|
export function getSectionTitle({ type }: { type: string }): string {
|
||||||
return (
|
return (
|
||||||
knownSectionTitles[type] ?? type.charAt(0).toUpperCase() + type.slice(1)
|
knownSectionTitles[type] ?? type.charAt(0).toUpperCase() + type.slice(1)
|
||||||
);
|
);
|
||||||
@@ -36,8 +36,18 @@ export function groupAndOrderChanges({ changes }: { changes: Change[] }) {
|
|||||||
return { grouped, orderedTypes };
|
return { grouped, orderedTypes };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isPublishedRelease({ published }: Release) {
|
||||||
|
return published !== false;
|
||||||
|
}
|
||||||
|
|
||||||
export function getSortedReleases() {
|
export function getSortedReleases() {
|
||||||
return [...allChangelogs].sort((a, b) =>
|
return allChangelogs
|
||||||
b.version.localeCompare(a.version, undefined, { numeric: true }),
|
.filter(isPublishedRelease)
|
||||||
);
|
.sort((a, b) =>
|
||||||
|
b.version.localeCompare(a.version, undefined, { numeric: true }),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getReleaseByVersion({ version }: { version: string }) {
|
||||||
|
return getSortedReleases().find((release) => release.version === version);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user