From 62a4e7c4e5a50c4583bfa853dbab00a15dd67b10 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 27 Sep 2026 00:20:56 +0800 Subject: [PATCH] fix: make tags link to category pages instead of filtering in place Clicking a category or group tag filtered the homepage table in place while rewriting the address bar to the category URL, so one URL rendered two different pages and readers never reached category pages with their intros and guides; category pages also showed a redundant self-referential filter bar. Co-Authored-By: Claude --- website/build.py | 8 --- website/static/main.js | 91 +++------------------------------ website/static/style.css | 49 +----------------- website/templates/category.html | 16 +----- website/templates/index.html | 16 +----- website/tests/test_build.py | 85 ++++-------------------------- 6 files changed, 18 insertions(+), 247 deletions(-) diff --git a/website/build.py b/website/build.py index 9164ca1a..70bbdc14 100644 --- a/website/build.py +++ b/website/build.py @@ -678,12 +678,7 @@ def build(repo_root: Path) -> None: filter_urls: dict[str, str] = dict(category_urls) for group in parsed_groups: filter_urls[group["name"]] = group_path(group["slug"]) - for entry in entries: - for sub in entry.get("subcategories", []): - filter_urls[sub["value"]] = sub["url"] builtin_entries = [e for e in entries if e.get("source_type") == BUILTIN_FILTER] - if builtin_entries: - filter_urls[BUILTIN_FILTER] = BUILTIN_PATH env = Environment( loader=FileSystemLoader(website / "templates"), @@ -696,7 +691,6 @@ def build(repo_root: Path) -> None: shutil.rmtree(site_dir) site_dir.mkdir(parents=True) - filter_urls_json = json.dumps(filter_urls, sort_keys=True, ensure_ascii=False).replace(" None: sponsors=sponsors, category_urls=category_urls, filter_urls=filter_urls, - filter_urls_json=filter_urls_json, homepage_json_ld=homepage_json_ld, ), encoding="utf-8", @@ -762,7 +755,6 @@ def build(repo_root: Path) -> None: category_urls=category_urls, current_path=current_path, filter_urls=filter_urls, - filter_urls_json=filter_urls_json, parent_category=parent_category, group_categories=group_categories, category_json_ld=category_json_ld, diff --git a/website/static/main.js b/website/static/main.js index 307e3f92..919aefd4 100644 --- a/website/static/main.js +++ b/website/static/main.js @@ -10,15 +10,10 @@ const defaultSort = table && table.dataset.defaultSort === "editorial" ? { col: "editorial", order: "asc" } : { col: "downloads", order: "desc" }; -let activeFilter = null; let activeSort = defaultSort; const searchInput = document.querySelector(".search"); -const filterBar = document.querySelector(".filter-bar"); -const filterValue = document.querySelector(".filter-value"); -const filterClear = document.querySelector(".filter-clear"); const noResults = document.querySelector(".no-results"); const rows = document.querySelectorAll(".table tbody tr.row"); -const tags = document.querySelectorAll(".tag"); const tbody = document.querySelector(".table tbody"); const groupRows = document.querySelectorAll(".table tbody tr.group-row"); @@ -145,7 +140,7 @@ function collapseAll() { function applyFilters() { const query = searchInput ? searchInput.value.toLowerCase().trim() : ""; - const descRowsVisible = !isIndexDocument || activeFilter !== null; + const descRowsVisible = !isIndexDocument; let visibleCount = 0; collapseAll(); @@ -153,12 +148,7 @@ function applyFilters() { rows.forEach(function (row) { let show = true; - if (activeFilter) { - const rowTags = row.dataset.tags; - show = rowTags ? rowTags.split("||").includes(activeFilter) : false; - } - - if (show && query) { + if (query) { if (!row._searchText) { let text = row.textContent.toLowerCase(); if (row._descRow) { @@ -200,35 +190,12 @@ function applyFilters() { if (noResults) noResults.hidden = visibleCount > 0; - tags.forEach(function (tag) { - tag.classList.toggle("active", activeFilter === tag.dataset.value); - }); - - if (filterBar) { - if (activeFilter) { - filterBar.classList.add("visible"); - if (filterValue) filterValue.textContent = activeFilter; - } else { - filterBar.classList.remove("visible"); - } - } - updateURL(); } -const filterUrlsScript = document.getElementById("filter-urls"); -const filterToUrl = filterUrlsScript - ? JSON.parse(filterUrlsScript.textContent) - : {}; - const isIndexDocument = location.pathname === "/" || location.pathname === "/index.html"; -const urlToFilter = {}; -Object.keys(filterToUrl).forEach(function (k) { - urlToFilter[filterToUrl[k]] = k; -}); - function buildQueryString() { const params = new URLSearchParams(); const query = searchInput ? searchInput.value.trim() : ""; @@ -243,9 +210,7 @@ function buildQueryString() { function updateURL() { if (!isIndexDocument) return; - const path = - activeFilter && filterToUrl[activeFilter] ? filterToUrl[activeFilter] : "/"; - history.replaceState(null, "", path + buildQueryString()); + history.replaceState(null, "", "/" + buildQueryString()); } function getSortValue(row, col) { @@ -340,8 +305,8 @@ function updateSortIndicators() { // Expand/collapse: event delegation on tbody if (tbody) { tbody.addEventListener("click", function (e) { - // Don't toggle if clicking a link or tag button - if (e.target.closest("a") || e.target.closest(".tag")) return; + // Don't toggle if clicking a link + if (e.target.closest("a")) return; let row = e.target.closest("tr.row"); if (!row) { @@ -370,36 +335,6 @@ if (tbody) { }); } -tags.forEach(function (tag) { - tag.addEventListener("click", function (e) { - e.preventDefault(); - const value = tag.dataset.value; - const url = tag.dataset.url; - if (isIndexDocument) { - 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 + "#library-index"; - } - }); -}); - -if (filterClear) { - filterClear.addEventListener("click", function () { - if (!isIndexDocument) { - window.location.href = "/#library-index"; - return; - } - activeFilter = null; - applyFilters(); - }); -} - const noResultsClear = document.querySelector(".no-results-clear"); if (noResultsClear) { noResultsClear.addEventListener("click", function () { @@ -408,7 +343,6 @@ if (noResultsClear) { return; } if (searchInput) searchInput.value = ""; - activeFilter = null; applyFilters(); }); } @@ -451,7 +385,6 @@ if (searchInput) { } if (e.key === "Escape" && document.activeElement === searchInput) { searchInput.value = ""; - activeFilter = null; applyFilters(); searchInput.blur(); } @@ -512,20 +445,8 @@ if (backToTop) { ) { activeSort = { col: sort, order: order }; } - const matched = urlToFilter[location.pathname]; - if (matched) activeFilter = matched; - if (q || activeFilter || sort) { + if (q || sort) { sortRows(); } - if (activeFilter) { - applyFilters(); - } updateSortIndicators(); })(); - -window.addEventListener("popstate", function () { - if (!isIndexDocument) return; - const matched = urlToFilter[location.pathname]; - activeFilter = matched || null; - applyFilters(); -}); diff --git a/website/static/style.css b/website/static/style.css index f7bddd67..4e067c06 100644 --- a/website/static/style.css +++ b/website/static/style.css @@ -265,8 +265,7 @@ kbd { .hero-topbar-link:active, .hero-action:active, -.tag:active, -.filter-clear:active { +.tag:active { transform: translateY(1px); } @@ -436,7 +435,6 @@ kbd { .hero-topbar-link:focus-visible, .hero-category-link:focus-visible, .search:focus-visible, -.filter-clear:focus-visible, .tag:focus-visible, .jump-link:focus-visible, .back-to-top:focus-visible, @@ -771,51 +769,6 @@ kbd { 0 1.6rem 3rem -2rem var(--search-focus-shadow); } -.filter-bar { - display: flex; - align-items: center; - gap: 0.75rem; - min-height: 2.3rem; - font-size: var(--text-sm); - color: var(--ink-soft); - opacity: 0; - transform: translateY(-0.4rem); - pointer-events: none; - transition: - opacity 180ms ease, - transform 180ms cubic-bezier(0.22, 1, 0.36, 1); -} - -.filter-bar.visible { - opacity: 1; - transform: translateY(0); - pointer-events: auto; -} - -.filter-bar strong { - color: var(--ink); -} - -.filter-clear { - border: 1px solid var(--line); - border-radius: 999px; - background: var(--bg-paper); - color: var(--ink-soft); - padding: 0.42rem 0.82rem; - cursor: pointer; - transition: - border-color 180ms ease, - color 180ms ease, - background-color 180ms ease, - transform 180ms ease; -} - -.filter-clear:hover { - color: var(--ink); - background: var(--accent-soft); - border-color: oklch(68% 0.08 58 / 0.5); -} - .table-wrap { width: 100%; border-top: 1px solid var(--line); diff --git a/website/templates/category.html b/website/templates/category.html index a9618aef..7528da00 100644 --- a/website/templates/category.html +++ b/website/templates/category.html @@ -78,7 +78,6 @@ {% macro entry_rows(entry, index) %} {% for subcat in entry.subcategories %} - + {{ subcat.name }} {% endfor %} @@ -132,8 +131,6 @@ {{ cat }} {% endfor %} @@ -142,8 +139,6 @@ {{ entry.groups[0] }} @@ -152,8 +147,6 @@ Stdlib @@ -211,7 +204,6 @@ {% endmacro %} -
{% if entry_groups %} @@ -255,12 +247,6 @@ aria-label="Search projects" />
-
- Filtering for - -

Results

diff --git a/website/templates/index.html b/website/templates/index.html index b5540624..e4d95d10 100644 --- a/website/templates/index.html +++ b/website/templates/index.html @@ -96,7 +96,6 @@
{% endif %} -
@@ -132,12 +131,6 @@ aria-label="Search projects" />
-
- Filtering for - -

Results

@@ -175,7 +168,6 @@ {% for entry in entries %} {% for subcat in entry.subcategories %} - + {{ subcat.name }} {% endfor %} {% for cat in entry.categories %} {{ cat }} {% endfor %} {{ entry.groups[0] }} @@ -245,8 +233,6 @@ Stdlib diff --git a/website/tests/test_build.py b/website/tests/test_build.py index cfebc616..f2448e90 100644 --- a/website/tests/test_build.py +++ b/website/tests/test_build.py @@ -280,7 +280,6 @@ class TestBuild: parser.feed(category_html) assert 'href="/categories/widgets/"' in index_html - assert 'data-value="Widgets"' in index_html assert parser.title.strip() == "Python Widgets Libraries - Awesome Python" assert parser.meta_by_name["description"] == "Widget libraries. Also see awesome-widgets. Explore 2 curated Python projects in Widgets." assert parser.links_by_rel["canonical"] == "https://awesome-python.com/categories/widgets/" @@ -816,75 +815,6 @@ class TestBuild: {"@type": "ListItem", "position": 2, "name": "Sponsorship", "item": "https://awesome-python.com/sponsorship/"}, ] - def test_index_embeds_filter_urls_json(self, tmp_path): - readme = textwrap.dedent("""\ - # T - - ## Projects - - **AI & ML** - - ## Deep Learning - - - [dl1](https://example.com/dl1) - DL. - - ## Machine Learning - - - Classical - - - [ml1](https://example.com/ml1) - ML. - - # Contributing - - Done. - """) - self._copy_real_templates(tmp_path) - (tmp_path / "README.md").write_text(readme, encoding="utf-8") - build(tmp_path) - - site = tmp_path / "website" / "output" - index_html = (site / "index.html").read_text(encoding="utf-8") - - marker = '", start) - data = json.loads(index_html[start:end]) - - assert data["Deep Learning"] == "/categories/deep-learning/" - assert data["Machine Learning"] == "/categories/machine-learning/" - assert data["AI & ML"] == "/categories/ai-ml/" - assert data["Machine Learning > Classical"] == "/categories/machine-learning/classical/" - - def test_filter_urls_json_escapes_closing_script_tag(self, tmp_path): - readme = textwrap.dedent("""\ - # T - - ## Projects - - ## Sneaky - - - [a](https://example.com) - A. - - # Contributing - - Done. - """) - self._copy_real_templates(tmp_path) - (tmp_path / "README.md").write_text(readme, encoding="utf-8") - build(tmp_path) - - site = tmp_path / "website" / "output" - index_html = (site / "index.html").read_text(encoding="utf-8") - - marker = '", start) - block = index_html[start:end] - assert "" not in block - data = json.loads(block) - assert any("Sneaky" in key for key in data) - def test_build_creates_group_pages(self, tmp_path): readme = textwrap.dedent("""\ # T @@ -925,7 +855,7 @@ class TestBuild: assert "wf1" in web_dev assert "dl1" not in web_dev - def test_tag_buttons_have_data_url(self, tmp_path): + def test_tags_link_to_pages_and_subcategory_anchors(self, tmp_path): readme = textwrap.dedent("""\ # T @@ -950,11 +880,14 @@ class TestBuild: site = tmp_path / "website" / "output" index_html = (site / "index.html").read_text(encoding="utf-8") - assert 'data-value="Deep Learning"' in index_html - assert 'data-url="/categories/deep-learning/"' in index_html - assert 'data-value="AI & ML"' in index_html or 'data-value="AI & ML"' in index_html - assert 'data-url="/categories/ai-ml/"' in index_html - assert 'data-url="/categories/deep-learning/vision/"' in index_html + category_html = (site / "categories" / "deep-learning" / "index.html").read_text(encoding="utf-8") + + for html in (index_html, category_html): + assert 'href="/categories/deep-learning/#vision"' in html + assert 'href="/categories/ai-ml/"' in html + assert "data-url=" not in html + assert 'href="/categories/deep-learning/"' in index_html + assert 'id="vision"' in category_html _REDIRECT_README = textwrap.dedent("""\ # Awesome Python