v0.7.0: mark all as read by clicking the unread badge
- Click a source's unread count in the sidebar to mark all of that source's messages read (clears the badge + reduces the All-sources total); click the All-sources total to mark everything read. - POST /feed/mark-read (session, owner-scoped, excludes trashed). Visible feed rows drop their unread state too. Badge is keyboard-activatable (Enter/Space). 156 tests pass (4 new); inline JS re-validated with node --check. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
7b161f1276
commit
89936ebf58
7 changed files with 123 additions and 5 deletions
|
|
@ -37,12 +37,12 @@
|
|||
<nav class="source-list">
|
||||
<a class="source-link all-sources-link {% if not selected_source and not selected_folder %}selected{% endif %}" href="?{% if selected_label %}label={{ selected_label|urlencode }}&{% endif %}{% if selected_kind %}kind={{ selected_kind|urlencode }}&{% endif %}{% if title_query %}q={{ title_query|urlencode }}&{% endif %}view={{ selected_view }}&preview=none">
|
||||
<span class="source-name">All sources</span>
|
||||
{% if total_count %}<span class="source-count">{{ total_count }}</span>{% endif %}
|
||||
{% if total_count %}<span class="source-count" data-mark-read role="button" tabindex="0" title="Mark all as read">{{ total_count }}</span>{% endif %}
|
||||
</a>
|
||||
{% for entry in source_counts %}
|
||||
<a class="source-link {% if entry.source == selected_source and not selected_folder %}selected{% endif %}" href="?source={{ entry.source|urlencode }}{% if selected_label %}&label={{ selected_label|urlencode }}{% endif %}{% if selected_kind %}&kind={{ selected_kind|urlencode }}{% endif %}{% if title_query %}&q={{ title_query|urlencode }}{% endif %}&view={{ selected_view }}&preview=none">
|
||||
<a class="source-link {% if entry.source == selected_source and not selected_folder %}selected{% endif %}" href="?source={{ entry.source|urlencode }}{% if selected_label %}&label={{ selected_label|urlencode }}{% endif %}{% if selected_kind %}&kind={{ selected_kind|urlencode }}{% endif %}{% if title_query %}&q={{ title_query|urlencode }}{% endif %}&view={{ selected_view }}&preview=none" data-source="{{ entry.source }}">
|
||||
<span class="source-name">{{ entry.source }}</span>
|
||||
{% if entry.count %}<span class="source-count">{{ entry.count }}</span>{% endif %}
|
||||
{% if entry.count %}<span class="source-count" data-mark-read role="button" tabindex="0" title="Mark all as read">{{ entry.count }}</span>{% endif %}
|
||||
</a>
|
||||
{% endfor %}
|
||||
</nav>
|
||||
|
|
@ -620,6 +620,59 @@
|
|||
openRowMenu(row, event.clientX, event.clientY);
|
||||
});
|
||||
|
||||
// Click a source's unread badge to mark all of that source's messages read.
|
||||
const markSourceRead = async (badge) => {
|
||||
const link = badge.closest(".source-link");
|
||||
if (!link) return;
|
||||
const isAll = link.classList.contains("all-sources-link");
|
||||
const source = link.dataset.source || "";
|
||||
const body = new URLSearchParams();
|
||||
if (!isAll && source) body.set("source", source);
|
||||
try {
|
||||
const response = await fetch("{% url 'feed-mark-read' %}", {
|
||||
method: "POST",
|
||||
headers: {"X-CSRFToken": csrfToken, "Content-Type": "application/x-www-form-urlencoded"},
|
||||
body: body.toString(),
|
||||
});
|
||||
if (!response.ok) return;
|
||||
} catch (e) { return; }
|
||||
if (isAll) {
|
||||
document.querySelectorAll(".source-list .source-count").forEach((b) => b.remove());
|
||||
artifactList.querySelectorAll(".artifact-row.unread").forEach((r) => r.classList.remove("unread"));
|
||||
} else {
|
||||
const n = parseInt(badge.textContent, 10) || 0;
|
||||
badge.remove();
|
||||
const allBadge = document.querySelector(".all-sources-link .source-count");
|
||||
if (allBadge) {
|
||||
const t = (parseInt(allBadge.textContent, 10) || 0) - n;
|
||||
if (t > 0) allBadge.textContent = String(t);
|
||||
else allBadge.remove();
|
||||
}
|
||||
artifactList
|
||||
.querySelectorAll(`.artifact-row.unread[data-source="${CSS.escape(source)}"]`)
|
||||
.forEach((r) => r.classList.remove("unread"));
|
||||
}
|
||||
};
|
||||
|
||||
const sourceListEl = document.querySelector(".source-list");
|
||||
if (sourceListEl) {
|
||||
const badgeFromEvent = (event) => event.target.closest("[data-mark-read]");
|
||||
sourceListEl.addEventListener("click", (event) => {
|
||||
const badge = badgeFromEvent(event);
|
||||
if (!badge) return;
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
markSourceRead(badge);
|
||||
});
|
||||
sourceListEl.addEventListener("keydown", (event) => {
|
||||
if (event.key !== "Enter" && event.key !== " ") return;
|
||||
const badge = badgeFromEvent(event);
|
||||
if (!badge) return;
|
||||
event.preventDefault();
|
||||
markSourceRead(badge);
|
||||
});
|
||||
}
|
||||
|
||||
const renderPreview = (row, pushState = true) => {
|
||||
navigationMode = "artifacts";
|
||||
markRead(row);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue