Files
aragrp/lib/changelog-display.ts
T
ar.badieiandCursor 6cb9e2aac5 feat: auto-detect blockquote and table colors from content
Replace manual hashtags and color cycling with content-tone heuristics for blockquotes and tables, and refine blockquote layout with a centered icon, larger sizing, and clearer contrast over background decorations.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-12 01:55:50 +03:30

81 lines
2.4 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}.`;
}
function toLatinDigits(value: string): string {
return value.replace(/[\u0660-\u0669\u06f0-\u06f9]/g, (digit) =>
String(
digit.charCodeAt(0) -
(digit <= "\u0669" ? 0x0660 : 0x06f0)
)
);
}
export function formatReleaseDate(date: string): string {
const parsed = new Date(`${date}T00:00:00`);
if (Number.isNaN(parsed.getTime())) return date;
const formatted = new Intl.DateTimeFormat("en-US", {
month: "long",
day: "numeric",
year: "numeric",
numberingSystem: "latn",
}).format(parsed);
return toLatinDigits(formatted);
}