mirror of
https://github.com/vinta/awesome-python.git
synced 2026-05-09 22:53:49 +08:00
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:
+71
-95
@@ -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)
|
category_url=group_public_url(group["slug"]),
|
||||||
synthetic = {
|
entries=[e for e in entries if group["name"] in e["groups"]],
|
||||||
"name": group["name"],
|
current_path=group_path(group["slug"]),
|
||||||
"slug": group["slug"],
|
page_dir=categories_dir / group["slug"],
|
||||||
"description": "",
|
group_categories=group["categories"],
|
||||||
"description_html": "",
|
|
||||||
}
|
|
||||||
(page_dir / "index.html").write_text(
|
|
||||||
tpl_category.render(
|
|
||||||
category=synthetic,
|
|
||||||
category_url=group_public_url(group["slug"]),
|
|
||||||
entries=group_entries,
|
|
||||||
total_categories=len(categories),
|
|
||||||
page_kind="group",
|
|
||||||
category_urls=category_urls,
|
|
||||||
current_path=group_path(group["slug"]),
|
|
||||||
filter_urls_json=filter_urls_json,
|
|
||||||
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 = {
|
category_url=BUILTIN_PUBLIC_URL,
|
||||||
"name": BUILTIN_FILTER,
|
entries=builtin_entries,
|
||||||
"slug": BUILTIN_SLUG,
|
current_path=BUILTIN_PATH,
|
||||||
"description": "",
|
page_dir=categories_dir / BUILTIN_SLUG,
|
||||||
"description_html": "",
|
|
||||||
}
|
|
||||||
(page_dir / "index.html").write_text(
|
|
||||||
tpl_category.render(
|
|
||||||
category=synthetic,
|
|
||||||
category_url=BUILTIN_PUBLIC_URL,
|
|
||||||
entries=builtin_entries,
|
|
||||||
total_categories=len(categories),
|
|
||||||
page_kind="built-in",
|
|
||||||
category_urls=category_urls,
|
|
||||||
current_path=BUILTIN_PATH,
|
|
||||||
filter_urls_json=filter_urls_json,
|
|
||||||
),
|
|
||||||
encoding="utf-8",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
sponsorship_dir = site_dir / "sponsorship"
|
sponsorship_dir = site_dir / "sponsorship"
|
||||||
@@ -446,51 +440,33 @@ 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}
|
||||||
for entry in entries:
|
cat_by_slug = {c["slug"]: c for c in categories}
|
||||||
for sub in entry.get("subcategories", []):
|
for entry in entries:
|
||||||
if not sub["url"].startswith(cat_url_prefix):
|
for sub in entry.get("subcategories", []):
|
||||||
continue
|
value = sub["value"]
|
||||||
key = (category["slug"], sub["slug"])
|
subcat_to_entries.setdefault(value, []).append(entry)
|
||||||
if key in seen_subcats:
|
if value not in subcat_meta:
|
||||||
continue
|
for prefix, cat_slug in cat_slug_by_url_prefix.items():
|
||||||
seen_subcats.add(key)
|
if sub["url"].startswith(prefix):
|
||||||
sub_entries = [
|
subcat_meta[value] = (cat_slug, sub["slug"], sub["name"])
|
||||||
e for e in entries
|
break
|
||||||
if any(s["value"] == sub["value"] for s in e.get("subcategories", []))
|
|
||||||
]
|
for value, (cat_slug, sub_slug, sub_name) in subcat_meta.items():
|
||||||
page_dir = categories_dir / category["slug"] / sub["slug"]
|
render_category(
|
||||||
page_dir.mkdir(parents=True, exist_ok=True)
|
synthetic_category(sub_name, sub_slug),
|
||||||
synthetic = {
|
category_url=subcategory_public_url(cat_slug, sub_slug),
|
||||||
"name": sub["name"],
|
entries=subcat_to_entries[value],
|
||||||
"slug": sub["slug"],
|
current_path=subcategory_path(cat_slug, sub_slug),
|
||||||
"description": "",
|
page_dir=categories_dir / cat_slug / sub_slug,
|
||||||
"description_html": "",
|
parent_category=cat_by_slug[cat_slug],
|
||||||
}
|
)
|
||||||
(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"
|
||||||
static_dst = site_dir / "static"
|
static_dst = site_dir / "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
@@ -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();
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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] }}"
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user