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
+12 -15
View File
@@ -184,17 +184,14 @@ const filterUrlsScript = document.getElementById("filter-urls");
const filterToUrl = filterUrlsScript
? JSON.parse(filterUrlsScript.textContent)
: {};
const urlToFilter = {};
Object.keys(filterToUrl).forEach(function (k) {
urlToFilter[filterToUrl[k]] = k;
});
const isIndexDocument =
location.pathname === "/" || location.pathname === "/index.html";
function isIndexPage() {
return isIndexDocument;
}
const urlToFilter = {};
Object.keys(filterToUrl).forEach(function (k) {
urlToFilter[filterToUrl[k]] = k;
});
function buildQueryString() {
const params = new URLSearchParams();
@@ -209,7 +206,7 @@ function buildQueryString() {
}
function updateURL() {
if (!isIndexPage()) return;
if (!isIndexDocument) return;
const path =
activeFilter && filterToUrl[activeFilter] ? filterToUrl[activeFilter] : "/";
history.replaceState(null, "", path + buildQueryString());
@@ -322,7 +319,7 @@ tags.forEach(function (tag) {
e.preventDefault();
const value = tag.dataset.value;
const url = tag.dataset.url;
if (isIndexPage()) {
if (isIndexDocument) {
activeFilter = activeFilter === value ? null : value;
if (activeFilter && url) {
history.pushState(null, "", url + buildQueryString());
@@ -332,16 +329,13 @@ tags.forEach(function (tag) {
applyFilters();
} else if (url) {
window.location.href = url;
} else {
activeFilter = activeFilter === value ? null : value;
applyFilters();
}
});
});
if (filterClear) {
filterClear.addEventListener("click", function () {
if (!isIndexPage()) {
if (!isIndexDocument) {
window.location.href = "/#library-index";
return;
}
@@ -353,7 +347,7 @@ if (filterClear) {
const noResultsClear = document.querySelector(".no-results-clear");
if (noResultsClear) {
noResultsClear.addEventListener("click", function () {
if (!isIndexPage()) {
if (!isIndexDocument) {
window.location.href = "/";
return;
}
@@ -464,11 +458,14 @@ if (backToTop) {
if (q || activeFilter || sort) {
sortRows();
}
if (activeFilter) {
applyFilters();
}
updateSortIndicators();
})();
window.addEventListener("popstate", function () {
if (!isIndexPage()) return;
if (!isIndexDocument) return;
const matched = urlToFilter[location.pathname];
activeFilter = matched || null;
applyFilters();