Introduce a footer-triggered Version History drawer with categorized release notes, hardcoded release data, and semver tracking at v0.3.0. Also ships blog blockquote/table styling, site-wide typography, home page image updates, header refinements, and related bug fixes. Co-authored-by: Cursor <cursoragent@cursor.com>
69 lines
2.1 KiB
TypeScript
69 lines
2.1 KiB
TypeScript
import type { ChangelogSection, ChangelogSectionTitle } from "@/lib/changelog-types";
|
|
|
|
/** Use a short paragraph when a release has very few notes. */
|
|
const PARAGRAPH_ITEM_THRESHOLD = 4;
|
|
|
|
export function stripChangelogMarkdown(text: string): string {
|
|
return text
|
|
.replace(/`([^`]+)`/g, "$1")
|
|
.replace(/\*\*([^*]+)\*\*/g, "$1")
|
|
.replace(/\s+/g, " ")
|
|
.trim();
|
|
}
|
|
|
|
export function countReleaseItems(sections: ChangelogSection[]): number {
|
|
return sections.reduce((total, section) => total + section.items.length, 0);
|
|
}
|
|
|
|
export function shouldUseParagraphSummary(sections: ChangelogSection[]): boolean {
|
|
return countReleaseItems(sections) <= PARAGRAPH_ITEM_THRESHOLD;
|
|
}
|
|
|
|
const SECTION_VERBS: Partial<Record<ChangelogSectionTitle, string>> = {
|
|
Added: "introduces",
|
|
Changed: "improves",
|
|
Fixed: "fixes",
|
|
Removed: "removes",
|
|
Deprecated: "deprecates",
|
|
Security: "patches",
|
|
};
|
|
|
|
function joinNaturalList(items: string[]): string {
|
|
if (items.length === 0) return "";
|
|
if (items.length === 1) return items[0];
|
|
if (items.length === 2) return `${items[0]} and ${items[1]}`;
|
|
return `${items.slice(0, -1).join(", ")}, and ${items[items.length - 1]}`;
|
|
}
|
|
|
|
export function buildReleaseParagraph(sections: ChangelogSection[]): string {
|
|
const clauses = sections
|
|
.map((section) => {
|
|
const items = section.items.map((item) =>
|
|
stripChangelogMarkdown(item).replace(/\.$/, "").toLowerCase()
|
|
);
|
|
const verb = SECTION_VERBS[section.title] ?? "updates";
|
|
return `${verb} ${joinNaturalList(items)}`;
|
|
})
|
|
.filter(Boolean);
|
|
|
|
if (clauses.length === 0) return "";
|
|
if (clauses.length === 1) {
|
|
return `This release ${clauses[0]}.`;
|
|
}
|
|
|
|
const last = clauses[clauses.length - 1];
|
|
const rest = clauses.slice(0, -1);
|
|
return `This release ${rest.join(", ")}, and ${last}.`;
|
|
}
|
|
|
|
export function formatReleaseDate(date: string): string {
|
|
const parsed = new Date(`${date}T00:00:00`);
|
|
if (Number.isNaN(parsed.getTime())) return date;
|
|
|
|
return new Intl.DateTimeFormat("en-US", {
|
|
month: "long",
|
|
day: "numeric",
|
|
year: "numeric",
|
|
}).format(parsed);
|
|
}
|