Files
devplacepy/devplacepy/static/js/CounterManager.js
T
retoor 741d7aade6 docs: add block/mute user relations, emoji-sync CLI, and uid indexes
- Add `/block`, `/mute` endpoints with block/unblock and mute/unmute functionality in `routers/relations.py`, hiding blocked users' content everywhere except their own profile while muting only suppresses notifications
- Introduce `devplace emoji-sync` CLI command to regenerate `static/js/emoji-shortcodes.js` from the emoji library, documented in `CLAUDE.md` and wired in `cli.py`
- Create `get_blocked_uids()` database helper and apply it in `content.py` `load_detail()` to filter blocked users' posts from detail views
- Implement `_uid_index()` and `_drop_index()` helpers in `database.py` for unique uid indexes across tables, with `user_relations` added to `SOFT_DELETE_TABLES`
- Document new routes in `AGENTS.md` and `README.md`, including emoji shortcodes rendering behavior distinct from the emoji picker
2026-06-19 08:06:09 +00:00

59 lines
1.8 KiB
JavaScript

// retoor <retoor@molodetz.nl>
import { Http } from "./Http.js";
import { Poller } from "./Poller.js";
export class CounterManager {
constructor(pubsub) {
if (!document.querySelector("[data-counter]")) {
return;
}
document.addEventListener("visibilitychange", () => {
if (document.visibilityState === "visible") {
this.poll();
}
});
const uid = document.body.dataset.userUid || "";
if (pubsub && uid) {
pubsub.subscribe(`user.${uid}.counts`, (data) => this.applyCounts(data));
}
this.poller = new Poller(() => this.poll(), 60000, { pauseHidden: true });
}
applyCounts(counts) {
if (!counts) return;
this.apply("notifications", counts.notifications);
this.apply("messages", counts.messages);
}
async poll() {
let counts;
try {
counts = await Http.getJson("/notifications/counts");
} catch (error) {
console.error("counter poll failed", error);
return;
}
this.applyCounts(counts);
}
apply(key, count) {
document.querySelectorAll(`[data-counter="${key}"]`).forEach((target) => {
const badge = target.querySelector("[data-counter-badge]");
if (!badge) {
return;
}
badge.textContent = count;
badge.hidden = count === 0;
const noun = target.dataset.counterNoun;
if (noun) {
const label =
count > 0
? `${count} unread ${noun}`
: `${noun.charAt(0).toUpperCase()}${noun.slice(1)}, no unread`;
target.setAttribute("aria-label", label);
}
});
}
}