feat(cli): add page icon frontmatter support

This commit is contained in:
Prad Nukala committed 2026-09-25 13:14:22 -04:00
1 parent 314f2bff10
commit e189bb876e
1 file changed
+18 -3
+18 -3
View File
@@ -5,8 +5,8 @@
* - New problems get a full page (frontmatter, badge, warning, examples,
* constraints, solution) in the gold-standard format.
* - Already-ported pages only get their script-owned parts regenerated — the
* `<Badge>` line and the `## Solution` section — so hand-curated prose is
* never touched.
* frontmatter `icon:` (`PAGE_ICON`), the `<Badge>` line and the
* `## Solution` section — so hand-curated prose is never touched.
* - When a problem is solved in both JavaScript and Python the solution
* renders as a <CodeGroup> (Python first); single-language solutions
* render as a plain fence.
@@ -417,6 +417,9 @@ function warningText(h: Header): string {
);
}
/** Sidebar/page icon for every problem page (Lucide, resolved by Blume). */
const PAGE_ICON = "line-dot-left-horizontal";
function badgeLine(sol: Solution): string {
return `<Badge variant="accent">${sol.category}</Badge>`;
}
@@ -431,6 +434,7 @@ function renderPage(sol: Solution): string {
"---",
`title: '${h.num}. ${h.title.replaceAll("'", "''")}'`,
/[:#]/.test(description) ? `description: '${description.replaceAll("'", "''")}'` : `description: ${description}`,
`icon: ${PAGE_ICON}`,
"sidebar:",
` label: '${h.title.replaceAll("'", "''")}'`,
` badge: '${h.difficulty}'`,
@@ -508,6 +512,17 @@ function ensureLabel(page: string, h: Header): string {
return page.replace(/^sidebar:$/m, `sidebar:\n${label}`);
}
/** Set the frontmatter's top-level `icon:` to `PAGE_ICON`, inserting it before `sidebar:`. */
function ensureIcon(page: string): string {
const fm = page.match(/^---\n[\s\S]*?\n---/);
if (!fm) return page;
const line = `icon: ${PAGE_ICON}`;
const next = /^icon: .*$/m.test(fm[0])
? fm[0].replace(/^icon: .*$/m, line)
: fm[0].replace(/^sidebar:$/m, `${line}\nsidebar:`);
return next + page.slice(fm[0].length);
}
/**
* Offset just past the script-owned `## Solution` section: the next top-level
* heading that is not inside a code fence, or end of file. Sections a human
@@ -564,7 +579,7 @@ for (const sol of ordered) {
}
const tail = page.slice(solutionEnd(page, at));
const body = page.slice(0, at) + solutionSection(sol) + (tail ? `\n${tail}` : "");
const next = ensureLabel(body, sol.header).replace(/^<Badge variant="accent">.*<\/Badge>$/m, badgeLine(sol));
const next = ensureIcon(ensureLabel(body, sol.header)).replace(/^<Badge variant="accent">.*<\/Badge>$/m, badgeLine(sol));
const actions: string[] = [];
if (existing !== name) {
await rename(join(DOCS, existing), join(DOCS, name));