Merge branch 'feature/hide-repeated-tags' into feature/seo-redesign

This commit is contained in:
Vinta Chen
2026-09-27 02:05:21 +08:00
4 changed files with 32 additions and 3 deletions
+19
View File
@@ -288,6 +288,7 @@ function sortRows() {
const sortHeaders = document.querySelectorAll("th[data-sort]");
function updateSortIndicators() {
if (table) table.classList.toggle("sorted", activeSort.col !== "editorial");
sortHeaders.forEach(function (th) {
th.classList.remove("sort-asc", "sort-desc");
if (th.dataset.sort === activeSort.col) {
@@ -364,6 +365,24 @@ sortHeaders.forEach(function (th) {
});
});
// Group headings are hidden while sorted flat or filtered out by search, so a link to one must restore them first
document.addEventListener("click", function (e) {
const link = e.target.closest('a[href*="#"]');
if (!link || link.pathname !== location.pathname) return;
const heading = document.getElementById(decodeURIComponent(link.hash.slice(1)));
const groupRow = heading ? heading.closest(".group-row") : null;
if (!groupRow) return;
if (activeSort.col !== "editorial") {
activeSort = defaultSort;
sortRows();
updateSortIndicators();
}
if (groupRow.hidden && searchInput) {
searchInput.value = "";
applyFilters();
}
});
if (searchInput) {
let searchTimer;
searchInput.addEventListener("input", function () {
+5
View File
@@ -868,6 +868,11 @@ kbd {
text-underline-offset: 0.2em;
}
/* main.js adds .sorted once a column sort flattens the groups */
.table:not(.sorted) .repeats-heading {
display: none;
}
.group-count {
font-family: var(--font-body);
font-size: var(--text-sm);
+6 -3
View File
@@ -76,6 +76,9 @@
{% endblock %}
{% block content %}
{% macro entry_rows(entry, index) %}
{# On section and subcategory pages the group heading already names the row's use case #}
{% set grouped_page = entry_groups and not group_categories %}
{% set section_path = "/categories/" ~ parent_category.slug ~ "/" if parent_category else current_path %}
<tr
class="row"
tabindex="0"
@@ -87,7 +90,7 @@
<a href="{{ entry.url }}" target="_blank" rel="noopener"
>{{ entry.name }}</a
>
<span class="mobile-cat"
<span class="mobile-cat{% if grouped_page and (not entry.subcategories or category_urls[entry.subcategories[0].value.split(' > ')[0]] == section_path) %} repeats-heading{% endif %}"
>{% if entry.subcategories %}{{ entry.subcategories[0].name }}{%
else %}{{ category.name }}{% endif %}</span
>
@@ -123,13 +126,13 @@
</td>
<td class="col-cat">
{% for subcat in entry.subcategories %}
<a class="tag{% if subcat.url == current_path %} active{% endif %}" href="{{ category_urls[subcat.value.split(' > ')[0]] }}#{{ subcat.slug }}">
<a class="tag{% if subcat.url == current_path %} active{% endif %}{% if grouped_page and category_urls[subcat.value.split(' > ')[0]] == section_path %} repeats-heading{% endif %}" href="{{ category_urls[subcat.value.split(' > ')[0]] }}#{{ subcat.slug }}">
{{ subcat.name }}
</a>
{% endfor %}
{% for cat in entry.categories %}
<a
class="tag{% if category_urls[cat] == current_path %} active{% endif %}"
class="tag{% if category_urls[cat] == current_path %} active{% endif %}{% if grouped_page and category_urls[cat] == current_path %} repeats-heading{% endif %}"
href="{{ category_urls[cat] }}"
>{{ cat }}</a
>
+2
View File
@@ -991,6 +991,7 @@ class TestBuild:
assert positions == sorted(positions)
assert '<a class="jump-link" href="#small">Small</a>' in html
assert '<a href="/categories/widgets/small/">Small</a>' in html
assert '<a class="tag repeats-heading" href="/categories/widgets/#small">' in html
assert '<tr class="desc-row">' in html
subcategory_html = (site / "widgets" / "small" / "index.html").read_text(encoding="utf-8")
@@ -1033,6 +1034,7 @@ class TestBuild:
dl_heading = html.index('<a href="/categories/deep-learning/">Deep Learning</a>')
assert ml_heading < html.index(">ml1</a") < dl_heading < html.index(">dl1</a")
assert html.count(">ml1</a") == 1
assert "repeats-heading" not in html
assert 'class="jump-links"' not in html
def test_build_rejects_redirect_to_missing_page(self, tmp_path):