feat(website): group changelog patches by minor release
Keep each patch dated and directly linkable while reducing top-level changelog noise.
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import { createFileRoute } from "@tanstack/react-router";
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
import ReactMarkdown from "react-markdown";
|
import ReactMarkdown, { type Components } from "react-markdown";
|
||||||
import changelogMarkdown from "../../../../CHANGELOG.md?raw";
|
import changelogMarkdown from "../../../../CHANGELOG.md?raw";
|
||||||
import { SiteShell } from "~/components/site-shell";
|
import { SiteShell } from "~/components/site-shell";
|
||||||
import { pageMeta } from "~/meta";
|
import { pageMeta } from "~/meta";
|
||||||
@@ -10,7 +10,13 @@ interface ChangelogRelease {
|
|||||||
markdown: string;
|
markdown: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ChangelogReleaseGroup {
|
||||||
|
version: string;
|
||||||
|
releases: ChangelogRelease[];
|
||||||
|
}
|
||||||
|
|
||||||
const releaseHeadingPattern = /^## (.+?) - (\d{4}-\d{2}-\d{2})$/;
|
const releaseHeadingPattern = /^## (.+?) - (\d{4}-\d{2}-\d{2})$/;
|
||||||
|
const patchMarkdownComponents: Components = { h3: "h4" };
|
||||||
|
|
||||||
export const Route = createFileRoute("/changelog")({
|
export const Route = createFileRoute("/changelog")({
|
||||||
head: () =>
|
head: () =>
|
||||||
@@ -64,28 +70,76 @@ function parseChangelog(markdown: string): ChangelogRelease[] {
|
|||||||
|
|
||||||
const changelogReleases = parseChangelog(changelogMarkdown);
|
const changelogReleases = parseChangelog(changelogMarkdown);
|
||||||
|
|
||||||
function Release({ release }: { release: ChangelogRelease }) {
|
function minorVersion(version: string): string {
|
||||||
|
return version.split(".").slice(0, 2).join(".");
|
||||||
|
}
|
||||||
|
|
||||||
|
function groupChangelogReleases(releases: ChangelogRelease[]): ChangelogReleaseGroup[] {
|
||||||
|
const groups: ChangelogReleaseGroup[] = [];
|
||||||
|
|
||||||
|
for (const release of releases) {
|
||||||
|
const version = minorVersion(release.version);
|
||||||
|
const currentGroup = groups.at(-1);
|
||||||
|
if (currentGroup?.version === version) {
|
||||||
|
currentGroup.releases.push(release);
|
||||||
|
} else {
|
||||||
|
groups.push({ version, releases: [release] });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return groups;
|
||||||
|
}
|
||||||
|
|
||||||
|
const changelogReleaseGroups = groupChangelogReleases(changelogReleases);
|
||||||
|
|
||||||
|
function HeadingAnchor({ version }: { version: string }) {
|
||||||
|
const anchor = `release-${version}`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<a
|
||||||
|
href={`#${anchor}`}
|
||||||
|
className="changelog-heading-anchor"
|
||||||
|
aria-label={`Link to Paseo ${version}`}
|
||||||
|
>
|
||||||
|
<span aria-hidden="true">#</span>
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function PatchRelease({ release }: { release: ChangelogRelease }) {
|
||||||
const anchor = `release-${release.version}`;
|
const anchor = `release-${release.version}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<article className="changelog-release">
|
<section className="changelog-patch">
|
||||||
<time dateTime={release.date} className="changelog-release-date">
|
<div id={anchor} className="changelog-patch-heading">
|
||||||
{formatDate(release.date)}
|
<HeadingAnchor version={release.version} />
|
||||||
</time>
|
<h3 className="changelog-patch-title">{release.version}</h3>
|
||||||
<div id={anchor} className="changelog-release-heading">
|
<time dateTime={release.date} className="changelog-release-date">
|
||||||
<a
|
{formatDate(release.date)}
|
||||||
href={`#${anchor}`}
|
</time>
|
||||||
className="changelog-heading-anchor"
|
|
||||||
aria-label={`Link to Paseo ${release.version}`}
|
|
||||||
>
|
|
||||||
<span aria-hidden="true">#</span>
|
|
||||||
</a>
|
|
||||||
<h2 className="changelog-release-title">
|
|
||||||
Paseo <span>{release.version}</span>
|
|
||||||
</h2>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="changelog-release-notes">
|
<div className="changelog-release-notes">
|
||||||
<ReactMarkdown>{release.markdown}</ReactMarkdown>
|
<ReactMarkdown components={patchMarkdownComponents}>{release.markdown}</ReactMarkdown>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Release({ group }: { group: ChangelogReleaseGroup }) {
|
||||||
|
const anchor = `release-${group.version}`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<article className="changelog-release">
|
||||||
|
<div id={anchor} className="changelog-release-heading">
|
||||||
|
<HeadingAnchor version={group.version} />
|
||||||
|
<h2 className="changelog-release-title">
|
||||||
|
Paseo <span>{group.version}</span>
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<div className="changelog-patches">
|
||||||
|
{group.releases.map((release) => (
|
||||||
|
<PatchRelease key={release.version} release={release} />
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
);
|
);
|
||||||
@@ -96,8 +150,8 @@ function Changelog() {
|
|||||||
<SiteShell width="default">
|
<SiteShell width="default">
|
||||||
<div className="max-w-2xl">
|
<div className="max-w-2xl">
|
||||||
<h1 className="mb-12 text-3xl font-medium tracking-tight">Changelog</h1>
|
<h1 className="mb-12 text-3xl font-medium tracking-tight">Changelog</h1>
|
||||||
{changelogReleases.map((release) => (
|
{changelogReleaseGroups.map((group) => (
|
||||||
<Release key={release.version} release={release} />
|
<Release key={group.version} group={group} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</SiteShell>
|
</SiteShell>
|
||||||
|
|||||||
@@ -176,6 +176,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.changelog-release-heading:hover .changelog-heading-anchor,
|
.changelog-release-heading:hover .changelog-heading-anchor,
|
||||||
|
.changelog-patch-heading:hover .changelog-heading-anchor,
|
||||||
.changelog-heading-anchor:focus {
|
.changelog-heading-anchor:focus {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
@@ -184,13 +185,44 @@
|
|||||||
color: var(--color-foreground);
|
color: var(--color-foreground);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.changelog-patches {
|
||||||
|
display: grid;
|
||||||
|
gap: 2.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.changelog-patch + .changelog-patch {
|
||||||
|
border-top: 1px solid var(--color-border);
|
||||||
|
padding-top: 2.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.changelog-patch-heading {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
gap: 0.625rem;
|
||||||
|
scroll-margin-top: 1.5rem;
|
||||||
|
margin-bottom: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.changelog-patch-title {
|
||||||
|
color: var(--color-foreground);
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.changelog-patch-heading .changelog-release-date {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.changelog-release-notes {
|
.changelog-release-notes {
|
||||||
color: color-mix(in srgb, var(--color-foreground) 76%, transparent);
|
color: color-mix(in srgb, var(--color-foreground) 76%, transparent);
|
||||||
font-size: 0.9375rem;
|
font-size: 0.9375rem;
|
||||||
line-height: 1.7;
|
line-height: 1.7;
|
||||||
}
|
}
|
||||||
|
|
||||||
.changelog-release-notes h3 {
|
.changelog-release-notes h3,
|
||||||
|
.changelog-release-notes h4 {
|
||||||
margin-top: 1.75rem;
|
margin-top: 1.75rem;
|
||||||
margin-bottom: 0.625rem;
|
margin-bottom: 0.625rem;
|
||||||
color: var(--color-muted-foreground);
|
color: var(--color-muted-foreground);
|
||||||
@@ -199,7 +231,8 @@
|
|||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
}
|
}
|
||||||
|
|
||||||
.changelog-release-notes h3:first-child {
|
.changelog-release-notes h3:first-child,
|
||||||
|
.changelog-release-notes h4:first-child {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user