From b0136ac2669f2872aa3d83aef9e649b478cef9fa Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 3 May 2026 00:49:41 +0800 Subject: [PATCH] feat(website): switch index filter URLs from querystring to path Tag clicks on / pushState a category/group/subcategory path; on static pages they fully navigate. Search and sort stay in querystring. Built-in source tag has no data-url and stays as an in-page filter. The isIndexDocument flag is captured at load time so toggling on the index keeps working after pushState changes location.pathname. Co-Authored-By: Claude Opus 4.7 (1M context) --- website/static/main.js | 62 +++++++++++++++++++++++++++++++++++------- 1 file changed, 52 insertions(+), 10 deletions(-) diff --git a/website/static/main.js b/website/static/main.js index f875f8b1..b8d35337 100644 --- a/website/static/main.js +++ b/website/static/main.js @@ -167,19 +167,39 @@ function applyFilters() { updateURL(); } -function updateURL() { +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; +} + +function buildQueryString() { const params = new URLSearchParams(); const query = searchInput ? searchInput.value.trim() : ""; if (query) params.set("q", query); - if (activeFilter) { - params.set("filter", activeFilter); - } if (activeSort.col !== "stars" || activeSort.order !== "desc") { params.set("sort", activeSort.col); params.set("order", activeSort.order); } const qs = params.toString(); - history.replaceState(null, "", qs ? "?" + qs : location.pathname); + return qs ? "?" + qs : ""; +} + +function updateURL() { + if (!isIndexPage()) return; + const path = + activeFilter && filterToUrl[activeFilter] ? filterToUrl[activeFilter] : "/"; + history.replaceState(null, "", path + buildQueryString()); } function getSortValue(row, col) { @@ -288,8 +308,21 @@ tags.forEach(function (tag) { tag.addEventListener("click", function (e) { e.preventDefault(); const value = tag.dataset.value; - activeFilter = activeFilter === value ? null : value; - applyFilters(); + const url = tag.dataset.url; + if (isIndexPage()) { + activeFilter = activeFilter === value ? null : value; + if (activeFilter && url) { + history.pushState(null, "", url + buildQueryString()); + } else { + history.pushState(null, "", "/" + buildQueryString()); + } + applyFilters(); + } else if (url) { + window.location.href = url; + } else { + activeFilter = activeFilter === value ? null : value; + applyFilters(); + } }); }); @@ -396,19 +429,28 @@ if (backToTop) { (function () { const params = new URLSearchParams(location.search); const q = params.get("q"); - const filter = params.get("filter"); const sort = params.get("sort"); const order = params.get("order"); if (q && searchInput) searchInput.value = q; - if (filter) activeFilter = filter; if ( (sort === "name" || sort === "stars" || sort === "commit-time") && (order === "desc" || order === "asc") ) { activeSort = { col: sort, order: order }; } - if (q || filter || sort) { + if (isIndexPage()) { + const matched = urlToFilter[location.pathname]; + if (matched) activeFilter = matched; + } + if (q || activeFilter || sort) { sortRows(); } updateSortIndicators(); })(); + +window.addEventListener("popstate", function () { + if (!isIndexPage()) return; + const matched = urlToFilter[location.pathname]; + activeFilter = matched || null; + applyFilters(); +});