Give the search a zero-result state, hide the strip while filtering
The client filter only toggles card.hidden, so a query matching nothing left the list area blank while the unfiltered "Continue reading" strip stayed populated above it — the screen contradicted itself and read as a crash. list.html now carries a hidden no-match block; applyFilter counts visible cards, fills the query in with textContent, and hides .recent for the duration of a filter. The Clear-search button is handled by a delegated click listener so it survives an htmx tab swap. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -4,17 +4,43 @@
|
|||||||
function applyFilter() {
|
function applyFilter() {
|
||||||
var box = document.getElementById("search");
|
var box = document.getElementById("search");
|
||||||
if (!box) return;
|
if (!box) return;
|
||||||
var needle = box.value.trim().toLowerCase();
|
var query = box.value.trim();
|
||||||
document.querySelectorAll(".card").forEach(function (card) {
|
var needle = query.toLowerCase();
|
||||||
|
var cards = document.querySelectorAll(".card");
|
||||||
|
var visible = 0;
|
||||||
|
cards.forEach(function (card) {
|
||||||
var title = (card.dataset.title || "").toLowerCase();
|
var title = (card.dataset.title || "").toLowerCase();
|
||||||
card.hidden = needle !== "" && title.indexOf(needle) === -1;
|
card.hidden = needle !== "" && title.indexOf(needle) === -1;
|
||||||
|
if (!card.hidden) visible++;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// The strip is never filtered, so leaving it up during a search puts
|
||||||
|
// non-matching covers above an empty list and the screen contradicts
|
||||||
|
// itself.
|
||||||
|
var recent = document.querySelector(".recent");
|
||||||
|
if (recent) recent.hidden = needle !== "";
|
||||||
|
|
||||||
|
// An empty bucket already explains itself server-side; this only speaks
|
||||||
|
// when the filter is what emptied the screen.
|
||||||
|
var none = document.getElementById("no-match");
|
||||||
|
if (none) {
|
||||||
|
none.hidden = !(needle !== "" && cards.length > 0 && visible === 0);
|
||||||
|
if (!none.hidden) none.querySelector(".no-match-q").textContent = query;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener("input", function (e) {
|
document.addEventListener("input", function (e) {
|
||||||
if (e.target && e.target.id === "search") applyFilter();
|
if (e.target && e.target.id === "search") applyFilter();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
document.addEventListener("click", function (e) {
|
||||||
|
if (!e.target || !e.target.classList.contains("clear-search")) return;
|
||||||
|
var box = document.getElementById("search");
|
||||||
|
box.value = "";
|
||||||
|
applyFilter();
|
||||||
|
box.focus();
|
||||||
|
});
|
||||||
|
|
||||||
// htmx replaces the list on a tab switch, so re-apply to the new cards.
|
// htmx replaces the list on a tab switch, so re-apply to the new cards.
|
||||||
document.body.addEventListener("htmx:afterSwap", applyFilter);
|
document.body.addEventListener("htmx:afterSwap", applyFilter);
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -591,6 +591,17 @@ button { cursor: pointer; }
|
|||||||
}
|
}
|
||||||
.empty strong { font: 400 20px var(--font-display); color: var(--paper); }
|
.empty strong { font: 400 20px var(--font-display); color: var(--paper); }
|
||||||
.empty.hot strong { color: var(--ember); }
|
.empty.hot strong { color: var(--ember); }
|
||||||
|
.empty .clear-search {
|
||||||
|
align-self: flex-start;
|
||||||
|
margin-top: 4px;
|
||||||
|
height: 44px;
|
||||||
|
padding: 0 16px;
|
||||||
|
border: 1px solid var(--field-line);
|
||||||
|
background: transparent;
|
||||||
|
color: var(--paper-dim);
|
||||||
|
font: 400 16px var(--font-display);
|
||||||
|
}
|
||||||
|
.empty .clear-search:hover { color: var(--paper); border-color: var(--paper); }
|
||||||
.empty p {
|
.empty p {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
max-width: 44ch;
|
max-width: 44ch;
|
||||||
|
|||||||
@@ -1,6 +1,13 @@
|
|||||||
{{define "list"}}
|
{{define "list"}}
|
||||||
{{if .Items}}
|
{{if .Items}}
|
||||||
{{range .Items}}{{template "card" .}}{{end}}
|
{{range .Items}}{{template "card" .}}{{end}}
|
||||||
|
{{/* The client filter only hides cards, so without this the list area goes
|
||||||
|
blank on a query that matches nothing. filter.js fills in the query and
|
||||||
|
unhides it; it lives inside #list so a tab swap re-creates it. */}}
|
||||||
|
<div class="empty" id="no-match" hidden>
|
||||||
|
<strong>No titles match “<span class="no-match-q"></span>”.</strong>
|
||||||
|
<button type="button" class="clear-search">Clear search</button>
|
||||||
|
</div>
|
||||||
{{else if eq .Tab "fav"}}
|
{{else if eq .Tab "fav"}}
|
||||||
<div class="empty"><strong>No favourites yet.</strong><p>Star a series to pin it here.</p></div>
|
<div class="empty"><strong>No favourites yet.</strong><p>Star a series to pin it here.</p></div>
|
||||||
{{else if eq .Tab "new"}}
|
{{else if eq .Tab "new"}}
|
||||||
|
|||||||
Reference in New Issue
Block a user