forked from retoor/devplacepy
- 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
59 lines
1.8 KiB
JavaScript
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);
|
|
}
|
|
});
|
|
}
|
|
}
|