news-digest: live-recompute source + desk counts; hide empty sections
The server-rendered .source-count / .desk-count badges were correct at render time but went stale the moment the user hid anything — "r/HOMELAB (4)" stayed at 4 even after all 4 items were hidden. Worse, the entire source header still rendered with a (0) badge once every item underneath was gone. app.js gains a refreshCounts() pass that walks every .source and .desk, recomputes the visible (non-.is-hidden) child count, updates the badge text, and toggles an .is-empty class. CSS rule for .source.is-empty and .desk.is-empty sets display:none so empty groups collapse out entirely. Hooked into hideItem, restoreItem, and the initial-paint hidden-set application.
This commit is contained in:
@@ -61,6 +61,28 @@
|
|||||||
return r.json();
|
return r.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── live counts ─────────────────────────────────────────────────
|
||||||
|
The server-rendered .source-count and .desk-count badges are
|
||||||
|
accurate at render time, but they fall stale as soon as the user
|
||||||
|
hides anything. Recompute from the DOM whenever the visible set
|
||||||
|
changes. Empty sources / desks get an .is-empty class that hides
|
||||||
|
them entirely (no point showing "r/homelab (0)"). */
|
||||||
|
|
||||||
|
function refreshCounts() {
|
||||||
|
document.querySelectorAll(".source").forEach((src) => {
|
||||||
|
const visible = src.querySelectorAll(".item:not(.is-hidden)").length;
|
||||||
|
const badge = src.querySelector(".source-count");
|
||||||
|
if (badge) badge.textContent = String(visible);
|
||||||
|
src.classList.toggle("is-empty", visible === 0);
|
||||||
|
});
|
||||||
|
document.querySelectorAll(".desk").forEach((desk) => {
|
||||||
|
const visibleItems = desk.querySelectorAll(".item:not(.is-hidden)").length;
|
||||||
|
const badge = desk.querySelector(".desk-count");
|
||||||
|
if (badge) badge.textContent = `${visibleItems} items`;
|
||||||
|
desk.classList.toggle("is-empty", visibleItems === 0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/* ── tray rendering ─────────────────────────────────────────────── */
|
/* ── tray rendering ─────────────────────────────────────────────── */
|
||||||
|
|
||||||
function refreshTrayVisibility() {
|
function refreshTrayVisibility() {
|
||||||
@@ -122,6 +144,7 @@
|
|||||||
// Optimistic — flip class first, talk to server next.
|
// Optimistic — flip class first, talk to server next.
|
||||||
el.classList.add(HIDE_CLASS);
|
el.classList.add(HIDE_CLASS);
|
||||||
addTrayRow(id, titleOf(el));
|
addTrayRow(id, titleOf(el));
|
||||||
|
refreshCounts();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await apiHide(id, true);
|
await apiHide(id, true);
|
||||||
@@ -129,6 +152,7 @@
|
|||||||
console.warn("[digest] hide failed, rolling back:", e);
|
console.warn("[digest] hide failed, rolling back:", e);
|
||||||
el.classList.remove(HIDE_CLASS);
|
el.classList.remove(HIDE_CLASS);
|
||||||
removeTrayRow(id);
|
removeTrayRow(id);
|
||||||
|
refreshCounts();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,12 +168,14 @@
|
|||||||
}
|
}
|
||||||
el.classList.remove(HIDE_CLASS);
|
el.classList.remove(HIDE_CLASS);
|
||||||
removeTrayRow(id);
|
removeTrayRow(id);
|
||||||
|
refreshCounts();
|
||||||
try {
|
try {
|
||||||
await apiHide(id, false);
|
await apiHide(id, false);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn("[digest] restore failed, re-hiding:", e);
|
console.warn("[digest] restore failed, re-hiding:", e);
|
||||||
el.classList.add(HIDE_CLASS);
|
el.classList.add(HIDE_CLASS);
|
||||||
addTrayRow(id, titleOf(el));
|
addTrayRow(id, titleOf(el));
|
||||||
|
refreshCounts();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,5 +213,6 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (trayHadAdditions) refreshTrayVisibility();
|
if (trayHadAdditions) refreshTrayVisibility();
|
||||||
|
refreshCounts();
|
||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -632,6 +632,14 @@ a:hover { color: var(--accent); }
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* When every item in a .source (or every source in a .desk) is hidden,
|
||||||
|
collapse the whole header out — no point rendering "r/homelab (0)".
|
||||||
|
app.js applies .is-empty when the visible-count drops to zero. */
|
||||||
|
.source.is-empty,
|
||||||
|
.desk.is-empty {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
.hidden-tray {
|
.hidden-tray {
|
||||||
margin-top: 24px;
|
margin-top: 24px;
|
||||||
padding: 14px 18px;
|
padding: 14px 18px;
|
||||||
|
|||||||
Reference in New Issue
Block a user