feat(frontend): watches, webhooks and alerts panels read humanly, with chat picker, url filter, test send and delivery log

This commit is contained in:
hh committed 2026-10-19 02:57:27 +00:00
1 parent 6a4fd6ad47
commit ea3a7c0a61
19 files changed
+3699 -458

No files matched your search

+120
View File
@@ -0,0 +1,120 @@
import {
type AlertItem,
type LiveAlertEvent,
unseenAlerts,
} from "#lib/api/watches.js";
import { alertLine, alertTitle } from "#lib/format/watches.js";
import { events } from "#lib/stores/events.svelte.js";
import { browser } from "$app/env";
import { goto } from "$app/navigation";
const NOTIFY_KEY = "bg.alerts.notify";
export const ALERTS_PARAM = "alerts";
type Listener = (alert: AlertItem) => void;
function notifySupported(): boolean {
return browser && "Notification" in window;
}
function createAlerts() {
const listeners = new Set<Listener>();
let unseen = $state(0);
let notify = $state(
notifySupported() &&
localStorage.getItem(NOTIFY_KEY) === "1" &&
Notification.permission === "granted"
);
let account: number | null = null;
async function refresh() {
if (account === null) {
return;
}
const current = account;
const count = await unseenAlerts().catch(() => null);
if (count !== null && current === account) {
unseen = count;
}
}
function popUp(alert: AlertItem) {
if (!(notify && document.hidden)) {
return;
}
const popup = new Notification(alertTitle(alert), {
body: alertLine(alert),
tag: `bg-alert-${alert.id}`,
});
popup.onclick = () => {
window.focus();
goto(alert.path);
popup.close();
};
}
function apply(event: LiveAlertEvent) {
({ unseen } = event);
if (event.alert === null) {
return;
}
for (const listener of listeners) {
listener(event.alert);
}
popUp(event.alert);
}
events.subscribe((event) => {
if (event.type === "alert") {
apply(event);
}
});
events.onReconnect(() => {
refresh();
});
return {
get notify(): boolean {
return notify;
},
get notifySupported(): boolean {
return notifySupported();
},
onAlert(listener: Listener): () => void {
listeners.add(listener);
return () => listeners.delete(listener);
},
read(count = 1) {
unseen = Math.max(0, unseen - count);
},
readAll() {
unseen = 0;
},
async setNotify(on: boolean): Promise<boolean> {
if (!notifySupported()) {
return false;
}
const allowed =
on &&
(Notification.permission === "granted" ||
(await Notification.requestPermission()) === "granted");
notify = allowed;
localStorage.setItem(NOTIFY_KEY, allowed ? "1" : "0");
return allowed === on;
},
track(accountId: number | null) {
if (accountId === account) {
return;
}
account = accountId;
unseen = 0;
refresh();
},
get unseen(): number {
return unseen;
},
};
}
export const alerts = createAlerts();
+1
View File
@@ -13,6 +13,7 @@ export type RightPanel =
| "stories-all"
| "policy"
| "watches"
| "webhooks"
| "alerts"
| "shares"
| "bots"