refactor(components): add appendix tag icons and drop list badge

This commit is contained in:
Prad Nukala committed 2026-10-05 12:34:23 -04:00
1 parent 099123539e
commit 88c0c9b898
2 files changed
+20 -31

No files matched your search

+4 -3
View File
@@ -1,9 +1,10 @@
--- ---
/** /**
* The `PageFooter` slot: an Obsidian-style backlinks list under a topic landing * The `PageFooter` slot: an Obsidian-style backlinks list under a topic landing
* or guide page — every problem page that points at it, as `<num> <name>` * page — every problem page that points at it, as `<num> <name>` rows in
* rows in LeetCode-number order. Pages nothing points at (problem pages, the * LeetCode-number order. Pages nothing points at (problem pages, the
* overview, the changelog) render nothing. * overview) render nothing; a tag no topic teaches lists its problems on its
* Appendix card instead (`pages/appendix.astro`), from the same answer.
* *
* A problem points at a page when either * A problem points at a page when either
* - one of its topic badges links there — the same `tagTarget()` answer * - one of its topic badges links there — the same `tagTarget()` answer
+16 -28
View File
@@ -11,28 +11,27 @@
* *
* - `topics:` (problem pages, written by `apps/cli/sync.ts`) is LeetCode's own * - `topics:` (problem pages, written by `apps/cli/sync.ts`) is LeetCode's own
* topic tags. A tag the campaign teaches (`topic-landings.ts`) links to that * topic tags. A tag the campaign teaches (`topic-landings.ts`) links to that
* topic's landing page and is filled; any other tag links to its guide * topic's landing page and is filled; any other tag links to its card on the
* page in the Appendix, `/appendix/<slug>` (sync creates one per such tag), and is outlined. * Appendix glossary, `/appendix#<slug>` (`appendix.ts`), and is outlined.
* `tagTarget()` picks the route (and between two landings, e.g. DFS); the * `tagTarget()` picks the route (and between two landings, e.g. DFS); the
* `PageFooter` backlinks list reads the same answer. Each tag badge is a * `PageFooter` backlinks list and the Appendix page read the same answer.
* pill carrying the icon its target shows in the sidebar (the topic's * Each tag badge is a pill carrying the icon its target shows (a topic's
* `meta.ts` icon, the guide's `icon:`) and the tag's own colour * `meta.ts` icon in the sidebar, a term's `icon` on its card) and the tag's
* (`lib/tag-colors.ts`), assigned over every tag the site has a page for — * own colour (`lib/tag-colors.ts`), assigned over every tag the site has a
* no two tags share one, and tags that share problem pages are spread * page or card for — no two tags share one, and tags that share problem
* furthest apart. A tag with no page (the LeetCode-list fallback) stays * pages are spread furthest apart. A tag with neither (the LeetCode-list
* gray and iconless. * fallback) stays gray and iconless.
* - `video:` (problem pages, written by `apps/cli/videos.ts`) is the NeetCode * - `video:` (problem pages, written by `apps/cli/videos.ts`) is the NeetCode
* walkthrough's YouTube id, the red "Explanation" badge. * walkthrough's YouTube id, the red "Explanation" badge.
* - `issue:` (problem pages, written by sync) is the problem's GitHub issue * - `issue:` (problem pages, written by sync) is the problem's GitHub issue
* URL, a neutral "Issue #N" badge with the GitHub mark, right after * URL, a neutral "Issue #N" badge with the GitHub mark, right after
* "Explanation" at the end of the row. * "Explanation" at the end of the row.
* - `list:` (guide pages, written by sync on create) is the tag's LeetCode
* problem list, an orange "LeetCode list" badge.
*/ */
import { getEntry } from "astro:content"; import { getEntry } from "astro:content";
import data from "blume:data"; import data from "blume:data";
import Badge from "blume/components/content/Badge.astro"; import Badge from "blume/components/content/Badge.astro";
import { stripBase, withBase } from "blume/components/islands/base-path.ts"; import { stripBase, withBase } from "blume/components/islands/base-path.ts";
import { APPENDIX } from "../appendix.ts";
import { tagColors } from "../lib/tag-colors.ts"; import { tagColors } from "../lib/tag-colors.ts";
import { TOPIC_LANDINGS, tagTarget, type TopicTag } from "../topic-landings.ts"; import { TOPIC_LANDINGS, tagTarget, type TopicTag } from "../topic-landings.ts";
@@ -46,7 +45,6 @@ const at = data.routes.find((r: { path: string }) => r.path === route);
const entry = at ? await getEntry(at.collection, at.entryId) : undefined; const entry = at ? await getEntry(at.collection, at.entryId) : undefined;
const tags: TopicTag[] = entry?.data.topics ?? []; const tags: TopicTag[] = entry?.data.topics ?? [];
const video: string | undefined = entry?.data.video; const video: string | undefined = entry?.data.video;
const list: string | undefined = entry?.data.list;
const issue: string | undefined = entry?.data.issue; const issue: string | undefined = entry?.data.issue;
const served = new Set<string>(data.routes.map((r: { path: string }) => r.path)); const served = new Set<string>(data.routes.map((r: { path: string }) => r.path));
@@ -67,15 +65,12 @@ const walk = (nodes: NavNode[]) => {
}; };
walk(data.navigation.sidebar); walk(data.navigation.sidebar);
// Every tag with a page — the ones a landing teaches, plus every served guide — // Every tag with a landing or an Appendix card, coloured against how problem
// coloured against how problem pages (`<num>. <name>` titles, the shape sync // pages (`<num>. <name>` titles, the shape sync writes) combine them. Only a
// writes) combine them. Only a page with tags needs the answer. // page with tags needs the answer.
const colors = tags.length const colors = tags.length
? tagColors( ? tagColors(
[ [...Object.keys(TOPIC_LANDINGS), ...Object.keys(APPENDIX)],
...Object.keys(TOPIC_LANDINGS),
...[...served].flatMap((path) => path.match(/^\/appendix\/([^/]+)$/u)?.slice(1) ?? []),
],
await Promise.all( await Promise.all(
data.routes data.routes
.filter((r: { title: string }) => /^\d+\.\s/u.test(r.title)) .filter((r: { title: string }) => /^\d+\.\s/u.test(r.title))
@@ -94,7 +89,7 @@ const badges = tags.map((tag) => {
name: tag.name, name: tag.name,
href: external ? target.href : withBase(target.href), href: external ? target.href : withBase(target.href),
external, external,
icon: external ? undefined : icons.get(target.href), icon: target.kind === "appendix" ? APPENDIX[tag.slug]?.icon : external ? undefined : icons.get(target.href),
color: external ? undefined : colors.get(tag.slug), color: external ? undefined : colors.get(tag.slug),
stroke: target.kind !== "landing", stroke: target.kind !== "landing",
}; };
@@ -104,7 +99,7 @@ const linkClass = "no-underline transition-opacity hover:opacity-80";
--- ---
{ {
(badges.length > 0 || video || list || issue) && ( (badges.length > 0 || video || issue) && (
<div class="not-prose mb-6 flex flex-wrap gap-1.5"> <div class="not-prose mb-6 flex flex-wrap gap-1.5">
{badges.map((b) => ( {badges.map((b) => (
<a <a
@@ -132,13 +127,6 @@ const linkClass = "no-underline transition-opacity hover:opacity-80";
</Badge> </Badge>
</a> </a>
)} )}
{list && (
<a href={list} target="_blank" rel="noopener" class={linkClass}>
<Badge variant="warning" icon="list" shape="pill">
LeetCode list
</Badge>
</a>
)}
</div> </div>
) )
} }