refactor(website): extract render_category, replace slugify filter with filter_urls map

- Extract render_category() helper to deduplicate the three category/group/builtin
  rendering blocks in build.py
- Replace synthetic dict literals with synthetic_category() helper
- Rewrite subcategory rendering to avoid O(n²) loop using precomputed dicts
- Pass filter_urls (not just JSON) to templates so Jinja can look up group URLs
  directly instead of applying the slugify filter at render time
- Remove slugify from env.filters (no longer used in templates)
- Replace isIndexPage() wrapper with isIndexDocument constant in main.js
- Fix: call applyFilters() on page load when activeFilter is set
- Remove dead else branch in tag click handler (category pages with no URL)
- Switch .hero-category-links from CSS columns to CSS grid for more even layout
- Remove max-width cap on .category-subtitle

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Vinta Chen
2026-05-03 11:38:22 +08:00
parent 8a32d27ef5
commit ee01a0bade
5 changed files with 91 additions and 122 deletions
+62 -86
View File
@@ -5,6 +5,7 @@ import json
import re import re
import shutil import shutil
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
from collections import Counter
from collections.abc import Sequence from collections.abc import Sequence
from datetime import UTC, datetime from datetime import UTC, datetime
from pathlib import Path from pathlib import Path
@@ -116,6 +117,10 @@ def subcategory_public_url(category_slug: str, subcategory_slug: str) -> str:
return f"{SITE_URL}categories/{category_slug}/{subcategory_slug}/" return f"{SITE_URL}categories/{category_slug}/{subcategory_slug}/"
def synthetic_category(name: str, slug: str) -> dict[str, str]:
return {"name": name, "slug": slug, "description": "", "description_html": ""}
def write_sitemap_xml(path: Path, urls: Sequence[tuple[str, str]]) -> None: def write_sitemap_xml(path: Path, urls: Sequence[tuple[str, str]]) -> None:
ET.register_namespace("", SITEMAP_NS) ET.register_namespace("", SITEMAP_NS)
urlset = ET.Element(f"{{{SITEMAP_NS}}}urlset") urlset = ET.Element(f"{{{SITEMAP_NS}}}urlset")
@@ -296,7 +301,7 @@ def build(repo_root: Path) -> None:
cat_slugs = [cat["slug"] for cat in categories] cat_slugs = [cat["slug"] for cat in categories]
group_slugs = [g["slug"] for g in parsed_groups] group_slugs = [g["slug"] for g in parsed_groups]
all_top_level_slugs = cat_slugs + group_slugs + [BUILTIN_SLUG] all_top_level_slugs = cat_slugs + group_slugs + [BUILTIN_SLUG]
duplicates = {s for s in all_top_level_slugs if all_top_level_slugs.count(s) > 1} duplicates = {s for s, n in Counter(all_top_level_slugs).items() if n > 1}
if duplicates: if duplicates:
raise ValueError( raise ValueError(
f"slug collision in /categories/ namespace: {sorted(duplicates)}. " f"slug collision in /categories/ namespace: {sorted(duplicates)}. "
@@ -343,8 +348,6 @@ def build(repo_root: Path) -> None:
trim_blocks=True, trim_blocks=True,
lstrip_blocks=True, lstrip_blocks=True,
) )
env.filters["slugify"] = slugify
site_dir = website / "output" site_dir = website / "output"
if site_dir.exists(): if site_dir.exists():
shutil.rmtree(site_dir) shutil.rmtree(site_dir)
@@ -364,6 +367,7 @@ def build(repo_root: Path) -> None:
build_date=build_date.strftime("%B %d, %Y"), build_date=build_date.strftime("%B %d, %Y"),
sponsors=sponsors, sponsors=sponsors,
category_urls=category_urls, category_urls=category_urls,
filter_urls=filter_urls,
filter_urls_json=filter_urls_json, filter_urls_json=filter_urls_json,
), ),
encoding="utf-8", encoding="utf-8",
@@ -371,70 +375,60 @@ def build(repo_root: Path) -> None:
tpl_category = env.get_template("category.html") tpl_category = env.get_template("category.html")
categories_dir = site_dir / "categories" categories_dir = site_dir / "categories"
for category in categories:
category_entries = [entry for entry in entries if category["name"] in entry["categories"]] def render_category(
page_dir = categories_dir / category["slug"] category: dict,
*,
category_url: str,
entries: list[dict],
current_path: str,
page_dir: Path,
parent_category: dict | None = None,
group_categories: list | None = None,
) -> None:
page_dir.mkdir(parents=True, exist_ok=True) page_dir.mkdir(parents=True, exist_ok=True)
(page_dir / "index.html").write_text( (page_dir / "index.html").write_text(
tpl_category.render( tpl_category.render(
category=category, category=category,
category_url=category_public_url(category), category_url=category_url,
entries=category_entries, entries=entries,
total_categories=len(categories), total_categories=len(categories),
page_kind="category",
category_urls=category_urls, category_urls=category_urls,
current_path=category_path(category), current_path=current_path,
filter_urls=filter_urls,
filter_urls_json=filter_urls_json, filter_urls_json=filter_urls_json,
parent_category=parent_category,
group_categories=group_categories,
), ),
encoding="utf-8", encoding="utf-8",
) )
for category in categories:
render_category(
category,
category_url=category_public_url(category),
entries=[e for e in entries if category["name"] in e["categories"]],
current_path=category_path(category),
page_dir=categories_dir / category["slug"],
)
for group in parsed_groups: for group in parsed_groups:
group_entries = [entry for entry in entries if group["name"] in entry["groups"]] render_category(
page_dir = categories_dir / group["slug"] synthetic_category(group["name"], group["slug"]),
page_dir.mkdir(parents=True, exist_ok=True)
synthetic = {
"name": group["name"],
"slug": group["slug"],
"description": "",
"description_html": "",
}
(page_dir / "index.html").write_text(
tpl_category.render(
category=synthetic,
category_url=group_public_url(group["slug"]), category_url=group_public_url(group["slug"]),
entries=group_entries, entries=[e for e in entries if group["name"] in e["groups"]],
total_categories=len(categories),
page_kind="group",
category_urls=category_urls,
current_path=group_path(group["slug"]), current_path=group_path(group["slug"]),
filter_urls_json=filter_urls_json, page_dir=categories_dir / group["slug"],
group_categories=group["categories"], group_categories=group["categories"],
),
encoding="utf-8",
) )
if builtin_entries: if builtin_entries:
page_dir = categories_dir / BUILTIN_SLUG render_category(
page_dir.mkdir(parents=True, exist_ok=True) synthetic_category(BUILTIN_FILTER, BUILTIN_SLUG),
synthetic = {
"name": BUILTIN_FILTER,
"slug": BUILTIN_SLUG,
"description": "",
"description_html": "",
}
(page_dir / "index.html").write_text(
tpl_category.render(
category=synthetic,
category_url=BUILTIN_PUBLIC_URL, category_url=BUILTIN_PUBLIC_URL,
entries=builtin_entries, entries=builtin_entries,
total_categories=len(categories),
page_kind="built-in",
category_urls=category_urls,
current_path=BUILTIN_PATH, current_path=BUILTIN_PATH,
filter_urls_json=filter_urls_json, page_dir=categories_dir / BUILTIN_SLUG,
),
encoding="utf-8",
) )
sponsorship_dir = site_dir / "sponsorship" sponsorship_dir = site_dir / "sponsorship"
@@ -446,50 +440,32 @@ def build(repo_root: Path) -> None:
hero_stats.append(f"{total_entries}+ curated projects") hero_stats.append(f"{total_entries}+ curated projects")
hero_stats.append(f"Updated {build_date.strftime('%B %d, %Y')}") hero_stats.append(f"Updated {build_date.strftime('%B %d, %Y')}")
(sponsorship_dir / "index.html").write_text( (sponsorship_dir / "index.html").write_text(
tpl_sponsorship.render( tpl_sponsorship.render(hero_stats=hero_stats),
total_entries=total_entries,
total_categories=len(categories),
hero_stats=hero_stats,
),
encoding="utf-8", encoding="utf-8",
) )
seen_subcats: set[tuple[str, str]] = set() subcat_to_entries: dict[str, list[dict]] = {}
for category in categories: subcat_meta: dict[str, tuple[str, str, str]] = {} # value -> (cat_slug, sub_slug, sub_name)
cat_url_prefix = f"/categories/{category['slug']}/" cat_slug_by_url_prefix = {f"/categories/{c['slug']}/": c["slug"] for c in categories}
cat_by_slug = {c["slug"]: c for c in categories}
for entry in entries: for entry in entries:
for sub in entry.get("subcategories", []): for sub in entry.get("subcategories", []):
if not sub["url"].startswith(cat_url_prefix): value = sub["value"]
continue subcat_to_entries.setdefault(value, []).append(entry)
key = (category["slug"], sub["slug"]) if value not in subcat_meta:
if key in seen_subcats: for prefix, cat_slug in cat_slug_by_url_prefix.items():
continue if sub["url"].startswith(prefix):
seen_subcats.add(key) subcat_meta[value] = (cat_slug, sub["slug"], sub["name"])
sub_entries = [ break
e for e in entries
if any(s["value"] == sub["value"] for s in e.get("subcategories", [])) for value, (cat_slug, sub_slug, sub_name) in subcat_meta.items():
] render_category(
page_dir = categories_dir / category["slug"] / sub["slug"] synthetic_category(sub_name, sub_slug),
page_dir.mkdir(parents=True, exist_ok=True) category_url=subcategory_public_url(cat_slug, sub_slug),
synthetic = { entries=subcat_to_entries[value],
"name": sub["name"], current_path=subcategory_path(cat_slug, sub_slug),
"slug": sub["slug"], page_dir=categories_dir / cat_slug / sub_slug,
"description": "", parent_category=cat_by_slug[cat_slug],
"description_html": "",
}
(page_dir / "index.html").write_text(
tpl_category.render(
category=synthetic,
category_url=subcategory_public_url(category["slug"], sub["slug"]),
entries=sub_entries,
total_categories=len(categories),
page_kind="subcategory",
parent_category=category,
category_urls=category_urls,
current_path=subcategory_path(category["slug"], sub["slug"]),
filter_urls_json=filter_urls_json,
),
encoding="utf-8",
) )
static_src = website / "static" static_src = website / "static"
@@ -509,7 +485,7 @@ def build(repo_root: Path) -> None:
sitemap_urls.extend((group_public_url(g["slug"]), sitemap_date) for g in parsed_groups) sitemap_urls.extend((group_public_url(g["slug"]), sitemap_date) for g in parsed_groups)
if builtin_entries: if builtin_entries:
sitemap_urls.append((BUILTIN_PUBLIC_URL, sitemap_date)) sitemap_urls.append((BUILTIN_PUBLIC_URL, sitemap_date))
for cat_slug, sub_slug in sorted(seen_subcats): for cat_slug, sub_slug, _ in sorted(subcat_meta.values()):
sitemap_urls.append((subcategory_public_url(cat_slug, sub_slug), sitemap_date)) sitemap_urls.append((subcategory_public_url(cat_slug, sub_slug), sitemap_date))
sitemap_urls.append((SPONSORSHIP_PUBLIC_URL, sitemap_date)) sitemap_urls.append((SPONSORSHIP_PUBLIC_URL, sitemap_date))
write_sitemap_xml(site_dir / "sitemap.xml", sitemap_urls) write_sitemap_xml(site_dir / "sitemap.xml", sitemap_urls)
+12 -15
View File
@@ -184,17 +184,14 @@ const filterUrlsScript = document.getElementById("filter-urls");
const filterToUrl = filterUrlsScript const filterToUrl = filterUrlsScript
? JSON.parse(filterUrlsScript.textContent) ? JSON.parse(filterUrlsScript.textContent)
: {}; : {};
const urlToFilter = {};
Object.keys(filterToUrl).forEach(function (k) {
urlToFilter[filterToUrl[k]] = k;
});
const isIndexDocument = const isIndexDocument =
location.pathname === "/" || location.pathname === "/index.html"; location.pathname === "/" || location.pathname === "/index.html";
function isIndexPage() { const urlToFilter = {};
return isIndexDocument; Object.keys(filterToUrl).forEach(function (k) {
} urlToFilter[filterToUrl[k]] = k;
});
function buildQueryString() { function buildQueryString() {
const params = new URLSearchParams(); const params = new URLSearchParams();
@@ -209,7 +206,7 @@ function buildQueryString() {
} }
function updateURL() { function updateURL() {
if (!isIndexPage()) return; if (!isIndexDocument) return;
const path = const path =
activeFilter && filterToUrl[activeFilter] ? filterToUrl[activeFilter] : "/"; activeFilter && filterToUrl[activeFilter] ? filterToUrl[activeFilter] : "/";
history.replaceState(null, "", path + buildQueryString()); history.replaceState(null, "", path + buildQueryString());
@@ -322,7 +319,7 @@ tags.forEach(function (tag) {
e.preventDefault(); e.preventDefault();
const value = tag.dataset.value; const value = tag.dataset.value;
const url = tag.dataset.url; const url = tag.dataset.url;
if (isIndexPage()) { if (isIndexDocument) {
activeFilter = activeFilter === value ? null : value; activeFilter = activeFilter === value ? null : value;
if (activeFilter && url) { if (activeFilter && url) {
history.pushState(null, "", url + buildQueryString()); history.pushState(null, "", url + buildQueryString());
@@ -332,16 +329,13 @@ tags.forEach(function (tag) {
applyFilters(); applyFilters();
} else if (url) { } else if (url) {
window.location.href = url; window.location.href = url;
} else {
activeFilter = activeFilter === value ? null : value;
applyFilters();
} }
}); });
}); });
if (filterClear) { if (filterClear) {
filterClear.addEventListener("click", function () { filterClear.addEventListener("click", function () {
if (!isIndexPage()) { if (!isIndexDocument) {
window.location.href = "/#library-index"; window.location.href = "/#library-index";
return; return;
} }
@@ -353,7 +347,7 @@ if (filterClear) {
const noResultsClear = document.querySelector(".no-results-clear"); const noResultsClear = document.querySelector(".no-results-clear");
if (noResultsClear) { if (noResultsClear) {
noResultsClear.addEventListener("click", function () { noResultsClear.addEventListener("click", function () {
if (!isIndexPage()) { if (!isIndexDocument) {
window.location.href = "/"; window.location.href = "/";
return; return;
} }
@@ -464,11 +458,14 @@ if (backToTop) {
if (q || activeFilter || sort) { if (q || activeFilter || sort) {
sortRows(); sortRows();
} }
if (activeFilter) {
applyFilters();
}
updateSortIndicators(); updateSortIndicators();
})(); })();
window.addEventListener("popstate", function () { window.addEventListener("popstate", function () {
if (!isIndexPage()) return; if (!isIndexDocument) return;
const matched = urlToFilter[location.pathname]; const matched = urlToFilter[location.pathname];
activeFilter = matched || null; activeFilter = matched || null;
applyFilters(); applyFilters();
+5 -9
View File
@@ -354,13 +354,10 @@ kbd {
.hero-category-links { .hero-category-links {
list-style: none; list-style: none;
columns: 5 9.5rem; display: grid;
grid-template-columns: repeat(auto-fill, minmax(13rem, 1fr));
column-gap: clamp(1.25rem, 3vw, 2.5rem); column-gap: clamp(1.25rem, 3vw, 2.5rem);
} row-gap: 0.28rem;
.hero-category-links li {
break-inside: avoid;
margin-bottom: 0.28rem;
} }
.hero-category-link { .hero-category-link {
@@ -480,7 +477,6 @@ kbd {
} }
.category-subtitle { .category-subtitle {
max-width: 68ch;
margin-top: 1.1rem; margin-top: 1.1rem;
color: var(--hero-muted); color: var(--hero-muted);
font-size: clamp(1rem, 1.8vw, 1.18rem); font-size: clamp(1rem, 1.8vw, 1.18rem);
@@ -1544,7 +1540,7 @@ th[data-sort].sort-asc::after {
} }
.hero-category-links { .hero-category-links {
columns: 3 8rem; grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr));
} }
.results-note { .results-note {
@@ -1612,7 +1608,7 @@ th[data-sort].sort-asc::after {
} }
.hero-category-links { .hero-category-links {
columns: 3 6.3rem; grid-template-columns: repeat(auto-fill, minmax(8rem, 1fr));
column-gap: 0.75rem; column-gap: 0.75rem;
} }
+2 -2
View File
@@ -24,7 +24,7 @@
</nav> </nav>
<div class="category-hero-copy"> <div class="category-hero-copy">
{% if page_kind == "subcategory" and parent_category %} {% if parent_category %}
<p class="category-breadcrumb"> <p class="category-breadcrumb">
<a href="/categories/{{ parent_category.slug }}/">{{ parent_category.name }}</a> <a href="/categories/{{ parent_category.slug }}/">{{ parent_category.name }}</a>
</p> </p>
@@ -183,7 +183,7 @@
> >
{% endfor %} {% endfor %}
{% if entry.groups %} {% if entry.groups %}
{% set group_url = "/categories/" ~ (entry.groups[0] | slugify) ~ "/" %} {% set group_url = filter_urls[entry.groups[0]] %}
<button <button
class="tag tag-group{% if group_url == current_path %} active{% endif %}" class="tag tag-group{% if group_url == current_path %} active{% endif %}"
data-value="{{ entry.groups[0] }}" data-value="{{ entry.groups[0] }}"
+1 -1
View File
@@ -235,7 +235,7 @@
<button <button
class="tag tag-group" class="tag tag-group"
data-value="{{ entry.groups[0] }}" data-value="{{ entry.groups[0] }}"
data-url="/categories/{{ entry.groups[0] | slugify }}/" data-url="{{ filter_urls[entry.groups[0]] }}"
> >
{{ entry.groups[0] }} {{ entry.groups[0] }}
</button> </button>