v0.2.17
RustBox CI / rustbox (push) Canceled after 0s

This commit is contained in:
Mateusz Gruszczyński committed 2026-11-03 10:35:05 +01:00
1 parent 2ac1566eca
commit 1bb398acd1
32 files changed
+907 -136

No files matched your search

+56 -15
View File
@@ -70,18 +70,43 @@ function updateNotificationPermissionStatus() {
status.textContent = !("Notification" in window) ? "Browser permission: unsupported" : `Browser permission: ${Notification.permission}`;
}
async function loadNotificationPreferences() {
function notificationPreferencesForAccount(accountId) {
if (accountId && state.notificationPrefsByAccount.has(accountId)) return state.notificationPrefsByAccount.get(accountId);
if (accountId === state.accountId) return state.notificationPrefs;
return { enabled: true, sound: false, folders: ["INBOX"], dnd_enabled: false, dnd_start: "22:00", dnd_end: "07:00" };
}
async function loadNotificationPreferencesForAccount(accountId, { silent = true } = {}) {
if (!accountId) return null;
try {
const prefs = await request("/settings/notifications", { method: "GET" });
state.notificationPrefs = { ...state.notificationPrefs, ...prefs };
$("#notify-enabled").checked = Boolean(prefs.enabled);
$("#notify-sound").checked = Boolean(prefs.sound);
$("#notify-dnd-enabled").checked = Boolean(prefs.dnd_enabled);
$("#notify-dnd-start").value = prefs.dnd_start || "22:00";
$("#notify-dnd-end").value = prefs.dnd_end || "07:00";
renderNotificationFolders(prefs.folders || ["INBOX"]);
updateNotificationPermissionStatus();
} catch (error) { toast(error.message, "error"); }
const prefs = await request("/settings/notifications", { method: "GET", accountId });
const normalized = { ...notificationPreferencesForAccount(accountId), ...prefs };
state.notificationPrefsByAccount.set(accountId, normalized);
if (accountId === state.accountId) state.notificationPrefs = normalized;
return normalized;
} catch (error) {
if (!silent) toast(error.message, "error");
return null;
}
}
async function loadCombinedNotificationPreferences() {
if (!multiAccountEnabled()) return;
const accountIds = [...state.notificationAccountIds].filter((accountId) => accountId !== state.accountId && state.accounts.some((account) => account.id === accountId));
await Promise.all(accountIds.map((accountId) => loadNotificationPreferencesForAccount(accountId)));
}
async function loadNotificationPreferences() {
const prefs = await loadNotificationPreferencesForAccount(state.accountId, { silent: false });
if (!prefs) return;
$("#notify-enabled").checked = Boolean(prefs.enabled);
$("#notify-sound").checked = Boolean(prefs.sound);
$("#notify-dnd-enabled").checked = Boolean(prefs.dnd_enabled);
$("#notify-dnd-start").value = prefs.dnd_start || "22:00";
$("#notify-dnd-end").value = prefs.dnd_end || "07:00";
renderNotificationFolders(prefs.folders || ["INBOX"]);
updateNotificationPermissionStatus();
await loadCombinedNotificationPreferences();
}
function renderNotificationFolders(selected) {
@@ -105,6 +130,7 @@ async function saveNotificationPreferences() {
if ($("#notify-enabled").checked && "Notification" in window && Notification.permission === "default") await requestNotificationPermission();
const prefs = await request("/settings/notifications", { method: "PUT", body: JSON.stringify({ enabled: $("#notify-enabled").checked, sound: $("#notify-sound").checked, folders, dnd_enabled: $("#notify-dnd-enabled").checked, dnd_start: $("#notify-dnd-start").value || "22:00", dnd_end: $("#notify-dnd-end").value || "07:00" }) });
state.notificationPrefs = { ...state.notificationPrefs, ...prefs };
if (state.accountId) state.notificationPrefsByAccount.set(state.accountId, state.notificationPrefs);
updateNotificationPermissionStatus();
toast("Notification settings saved");
} catch (error) { toast(error.message, "error"); }
@@ -163,10 +189,14 @@ function playNotificationSound() {
function renderSyncActivityIndicator() {
const indicator = $("#indexing-indicator");
if (!indicator) return;
const busy = Boolean(state.indexingActive || state.imapSyncActive);
const busy = Boolean(state.messageLoadPending > 0 || state.indexingActive || state.imapSyncActive);
indicator.classList.toggle("hidden", !busy);
indicator.classList.toggle("active", busy);
const parts = [];
if (state.messageLoadPending > 0) {
const folder = state.folder ? ` · ${state.folder}` : "";
parts.push(`Loading messages${folder}`);
}
if (state.imapSyncActive || state.imapSyncPending > 0) {
const folder = state.imapSyncFolder ? ` · ${state.imapSyncFolder}` : "";
const pending = state.imapSyncPending > 0 ? ` · ${state.imapSyncPending} headers pending in task` : "";
@@ -190,6 +220,16 @@ function setIndexingIndicator(active, pending = 0) {
renderSyncActivityIndicator();
}
function beginMessageLoadActivity() {
state.messageLoadPending = Math.max(0, Number(state.messageLoadPending) || 0) + 1;
renderSyncActivityIndicator();
}
function endMessageLoadActivity() {
state.messageLoadPending = Math.max(0, (Number(state.messageLoadPending) || 0) - 1);
renderSyncActivityIndicator();
}
function setImapSyncIndicator(active, folder = null, pending = 0) {
state.imapSyncActive = Boolean(active);
state.imapSyncFolder = folder || null;
@@ -318,7 +358,7 @@ async function openRealtimeNotificationTarget(accountId, data) {
}
function notifyNewMail(data, accountId) {
const prefs = state.notificationPrefs || {};
const prefs = notificationPreferencesForAccount(accountId) || {};
if (!prefs.enabled || dndActive(prefs)) return;
const folders = prefs.folders || [];
if (folders.length && !folders.includes(data.folder)) return;
@@ -505,7 +545,8 @@ function connectRealtime() {
clearTimeout(state.wsRetryTimer);
state.wsRetryTimer = null;
const protocol = location.protocol === "https:" ? "wss:" : "ws:";
const socket = new WebSocket(`${protocol}//${location.host}${BASE}/api/ws`);
const accountQuery = state.accountId ? `?account_id=${encodeURIComponent(state.accountId)}` : "";
const socket = new WebSocket(`${protocol}//${location.host}${BASE}/api/ws${accountQuery}`);
state.ws = socket;
socket.onopen = () => {
if (state.ws !== socket) return;
@@ -533,7 +574,7 @@ function connectRealtime() {
if (event.type === "NewMessages") {
const data = event.data || {};
const duplicate = isDuplicateNewMailNotification(data, payload.accountId);
if (!duplicate && !realtimeNewMailNotificationsSuppressed(data, payload.accountId)) {
if (!duplicate && notificationAccountEnabled(payload.accountId) && !realtimeNewMailNotificationsSuppressed(data, payload.accountId)) {
toastNewMail(data, payload.accountId);
notifyNewMail(data, payload.accountId);
}