Files
RustBox/src/web/js/95-bindings-boot.js
T
Mateusz Gruszczyński fff8521a17
RustBox CI / rustbox (push) Waiting to run
v0.0.2
2026-10-29 19:28:47 +01:00

455 lines
34 KiB
JavaScript

/* RustBox UI module: 95-bindings-boot.js */
function bindEditor() {
$$(".editor-toolbar [data-command]").forEach((button) => button.addEventListener("click", () => {
$("#compose-editor").focus();
document.execCommand(button.dataset.command, false, button.dataset.commandValue || null);
}));
$("#editor-link").addEventListener("click", async () => {
const url = await appPrompt("Insert link", { value: "https://", type: "url", confirmLabel: "Insert link", required: true });
if (!url) return;
$("#compose-editor").focus();
document.execCommand("createLink", false, url);
});
$("#editor-font").addEventListener("change", (event) => {
if (!event.target.value) return;
$("#compose-editor").focus();
document.execCommand("fontName", false, event.target.value);
scheduleDraftSave();
});
$("#editor-size").addEventListener("change", (event) => {
if (!event.target.value) return;
$("#compose-editor").focus();
document.execCommand("fontSize", false, event.target.value);
scheduleDraftSave();
});
$("#editor-color").addEventListener("input", (event) => {
$("#compose-editor").focus();
document.execCommand("foreColor", false, event.target.value);
scheduleDraftSave();
});
}
function bindPaneResizer(selector, preferenceKey, minPx, maxPx) {
const handle = $(selector);
if (!handle) return;
handle.addEventListener("pointerdown", (event) => {
if (innerWidth <= 820 || event.button !== 0) return;
event.preventDefault();
const scaleFactor = Number(getComputedStyle(root).getPropertyValue("--ui-scale-factor")) || 1;
const startX = event.clientX;
const startRendered = Number(state.prefs[preferenceKey] || minPx) * scaleFactor;
handle.setPointerCapture?.(event.pointerId);
document.body.classList.add("resizing-pane");
const move = (moveEvent) => {
const rendered = Math.max(minPx * scaleFactor, Math.min(maxPx * scaleFactor, startRendered + (moveEvent.clientX - startX)));
const base = Math.round(rendered / scaleFactor);
state.prefs[preferenceKey] = base;
const cssName = preferenceKey === "folderPaneWidth" ? "--folder-pane-width" : "--message-pane-width";
root.style.setProperty(cssName, `${Math.round(rendered)}px`);
handle.setAttribute("aria-valuenow", String(base));
};
const finish = () => {
handle.removeEventListener("pointermove", move);
handle.removeEventListener("pointerup", finish);
handle.removeEventListener("pointercancel", finish);
document.body.classList.remove("resizing-pane");
savePreference(preferenceKey, state.prefs[preferenceKey]);
};
handle.addEventListener("pointermove", move);
handle.addEventListener("pointerup", finish);
handle.addEventListener("pointercancel", finish);
});
}
function bindEvents() {
bindPaneResizer("#folder-resizer", "folderPaneWidth", 170, 420);
bindPaneResizer("#message-resizer", "messagePaneWidth", 300, 760);
bindRecipientAutocomplete("#compose-to");
bindRecipientAutocomplete("#compose-cc");
bindRecipientAutocomplete("#compose-bcc");
$("#login-form").addEventListener("submit", login);
$("#logout-btn").addEventListener("click", logout);
$("#compose-btn").addEventListener("click", () => openCompose("new"));
$("#compose-fullscreen").addEventListener("click", toggleComposeFullscreen);
$("#compose-form").addEventListener("submit", sendMessage);
$("#compose-attach").addEventListener("click", () => $("#compose-files").click());
$("#compose-files").addEventListener("change", async (event) => { await uploadComposeFiles(event.target.files); event.target.value = ""; });
$("#editor-image").addEventListener("click", () => { state.composeInsertRange = captureEditorRange(); $("#compose-images").click(); });
$("#editor-image-edit").addEventListener("click", () => openInlineImageEditor());
$("#compose-images").addEventListener("change", async (event) => { await uploadInlineImage(event.target.files, state.composeInsertRange); state.composeInsertRange = null; event.target.value = ""; });
$("#image-edit-form").addEventListener("submit", applyInlineImageEditor);
$("#image-edit-reset").addEventListener("click", resetImageCropControls);
["#image-crop-top", "#image-crop-right", "#image-crop-bottom", "#image-crop-left"].forEach((selector) => $(selector).addEventListener("input", drawInlineImageCropPreview));
$("#image-edit-width").addEventListener("input", updateImageEditOutputs);
$("#image-edit-dialog").addEventListener("close", () => { state.imageEditorBitmap?.close?.(); state.imageEditorBitmap = null; state.imageEditorBlob = null; });
$("#compose-recover").addEventListener("click", recoverDraft);
$("#compose-mode").addEventListener("change", async (event) => {
const editor = $("#compose-editor");
const previous = editor.dataset.mode || "html";
const next = event.target.value;
const richContent = previous === "html" && next !== "html" && Boolean(editor.querySelector("img,a,strong,b,em,i,u,s,ul,ol,li,blockquote,[style]"));
if (richContent && !await appConfirm("Switching away from HTML can hide or discard formatting when the message is sent. Continue?", { title: "Change compose format", confirmLabel: "Switch format" })) {
event.target.value = previous;
return;
}
setComposeMode(next, true);
insertIdentitySignature(editor);
scheduleDraftSave();
});
$("#compose-from").addEventListener("change", () => { insertIdentitySignature($("#compose-editor")); updateComposePgpUi(true); });
["#compose-to", "#compose-cc", "#compose-bcc"].forEach((selector) => $(selector).addEventListener("input", () => {
state.pgpRecipientStatus = [];
updateComposePgpUi();
scheduleDraftSave();
}));
$("#compose-subject").addEventListener("input", scheduleDraftSave);
["#compose-priority", "#compose-mdn", "#compose-pgp-mode", "#compose-pgp-inline", "#compose-pgp-attach-key", "#compose-pgp-attach-recipient-keys"].forEach((selector) => $(selector).addEventListener("change", async () => {
if (selector === "#compose-pgp-attach-recipient-keys" && $(selector).checked) await checkComposePgpRecipients(false, composePgpVisibleRecipients());
updateComposePgpUi();
scheduleDraftSave();
}));
$("#compose-editor").addEventListener("input", () => { if (state.composeSelectedImage && !state.composeSelectedImage.isConnected) setSelectedInlineImage(null); scheduleDraftSave(); scheduleUnusedInlineAttachmentCleanup(); });
$("#compose-editor").addEventListener("click", (event) => {
const image = event.target.closest?.("img[data-rustbox-cid]");
setSelectedInlineImage(image || null);
});
$("#compose-editor").addEventListener("dblclick", (event) => {
const image = event.target.closest?.("img[data-rustbox-cid]");
if (image) { event.preventDefault(); openInlineImageEditor(image); }
});
$("#compose-editor").addEventListener("paste", async (event) => {
const images = [...(event.clipboardData?.items || [])]
.filter((item) => item.kind === "file" && /^image\/(png|jpeg|gif|webp)$/i.test(item.type))
.map((item) => item.getAsFile())
.filter(Boolean);
if (!images.length) return;
event.preventDefault();
if ($("#compose-mode").value !== "html") return toast("Switch to HTML mode to paste inline images", "warning");
const range = captureEditorRange();
await uploadInlineImage(images, range);
});
$("#compose-editor").addEventListener("dragover", (event) => { if ([...event.dataTransfer.types].includes("Files")) event.preventDefault(); });
$("#compose-editor").addEventListener("drop", async (event) => {
const files = [...event.dataTransfer.files];
if (!files.length) return;
event.preventDefault();
const allImages = files.every((file) => /^image\/(png|jpeg|gif|webp)$/i.test(file.type));
if ($("#compose-mode").value === "html" && allImages) await uploadInlineImage(files, editorRangeFromPoint(event.clientX, event.clientY));
else await uploadComposeFiles(files);
});
$("#reply-btn").addEventListener("click", () => openCompose("reply"));
$("#reply-all-btn").addEventListener("click", () => openCompose("reply-all"));
$("#forward-btn").addEventListener("click", () => openCompose("forward"));
$("#delete-btn").addEventListener("click", deleteCurrent);
$("#unread-btn").addEventListener("click", toggleUnread);
$("#flag-btn").addEventListener("click", toggleFlagged);
$("#archive-btn").addEventListener("click", archiveCurrent);
$("#spam-btn").addEventListener("click", junkCurrent);
$("#move-select").addEventListener("change", async (event) => { const target = event.target.value; event.target.value = ""; if (target) await moveCurrentTo(target); });
$("#copy-select").addEventListener("change", async (event) => { const target = event.target.value; event.target.value = ""; if (target) await copyCurrentTo(target); });
$("#bulk-select-all").addEventListener("change", (event) => { const source = state.searchAllResults || state.messages; for (const message of source) { const key = messageKey(message); event.target.checked ? state.selectedMessages.add(key) : state.selectedMessages.delete(key); } renderMessages(); });
$("#bulk-read").addEventListener("click", () => performBulk("read"));
$("#bulk-unread").addEventListener("click", () => performBulk("unread"));
$("#bulk-flag").addEventListener("click", () => performBulk("flag"));
$("#bulk-unflag").addEventListener("click", () => performBulk("unflag"));
$("#bulk-archive").addEventListener("click", () => performBulk("archive"));
$("#bulk-select-search").addEventListener("click", selectAllSearchResults);
$("#bulk-junk").addEventListener("click", () => performBulk("junk"));
$("#bulk-delete").addEventListener("click", () => performBulk("delete"));
$("#bulk-move-select").addEventListener("change", async (event) => { const target = event.target.value; event.target.value = ""; if (target) await performBulk("move", target); });
$("#bulk-copy-select").addEventListener("change", async (event) => { const target = event.target.value; event.target.value = ""; if (target) await performBulk("copy", target); });
$("#bulk-tag-select").addEventListener("change", async (event) => { const tagId = event.target.value; event.target.value = ""; if (tagId) await bulkAddTag(tagId); });
$("#select-mode-btn").addEventListener("click", () => setSelectionMode(!state.selectionMode));
$("#selection-done").addEventListener("click", () => setSelectionMode(false));
$("#refresh-btn").addEventListener("click", () => loadFolders(false));
$("#message-sort").addEventListener("change", async (event) => {
const value = event.target.value;
state.mailPrefs.default_sort = value;
$("#mail-pref-sort").value = value;
try {
state.mailPrefs = await request("/settings/mailbox", { method: "PUT", body: JSON.stringify({ default_sort: value }) });
state.page = 0;
await loadMessages(true);
toast("Message sort updated", "success");
} catch (error) { toast(error.message, "error"); }
});
$("#load-more").addEventListener("click", async () => { state.page += 1; await loadMessages(false); });
$("#message-list").addEventListener("scroll", scheduleMessageListStateSave, { passive: true });
window.addEventListener("offline", () => setConnectivityState(false));
window.addEventListener("online", async () => {
setConnectivityState(true);
disconnectRealtime();
connectRealtime();
if (state.accounts.length) {
await refreshFolderListOnly();
if (state.searchContext) await runSearch(new URLSearchParams(state.searchContext.params), state.searchContext.label, { restoreScrollTop: state.searchContext.scrollTop });
else await loadMessages(true);
}
});
$("#search-form").addEventListener("submit", searchMail);
$("#advanced-search-btn").addEventListener("click", openAdvancedSearch);
$("#advanced-search-form").addEventListener("submit", submitAdvancedSearch);
$("#search-reset-btn").addEventListener("click", resetAdvancedSearch);
$("#search-save-btn").addEventListener("click", saveAdvancedSearch);
$("#contacts-btn").addEventListener("click", openContacts);
$("#contact-group-filter").addEventListener("change", async () => { renderContactGroupFilter(); await loadContacts($("#contact-search").value.trim()); });
$("#contact-group-new").addEventListener("click", createContactGroup);
$("#contact-group-rename").addEventListener("click", renameContactGroup);
$("#contact-group-delete").addEventListener("click", deleteContactGroup);
$("#tag-add-btn").addEventListener("click", () => openTagDialog());
$("#tag-form").addEventListener("submit", saveTag);
$("#tag-delete").addEventListener("click", deleteTag);
$("#calendar-btn").addEventListener("click", openCalendar);
$("#settings-btn").addEventListener("click", () => openSettings("appearance"));
$("#shortcuts-open").addEventListener("click", () => $("#shortcuts-dialog").showModal());
$("#account-add").addEventListener("click", () => { $("#add-account-error").classList.add("hidden"); $("#add-account-dialog").showModal(); setTimeout(() => $("#add-account-email").focus(), 20); });
$("#account-revoke-others").addEventListener("click", revokeOtherSessions);
$("#add-account-form").addEventListener("submit", addBrowserAccount);
$("#mobile-folders").addEventListener("click", toggleFolderDrawer);
$("#folder-drawer-close").addEventListener("click", () => setFolderDrawerOpen(false));
$("#folder-drawer-scrim").addEventListener("click", () => setFolderDrawerOpen(false));
$("#reader-back").addEventListener("click", closeReader);
$("#reader-prev").addEventListener("click", () => navigateMessage(-1));
$("#reader-next").addEventListener("click", () => navigateMessage(1));
$("#thread-toggle").addEventListener("click", () => {
state.threadExpanded = !state.threadExpanded;
localStorage.setItem("rustbox.threadExpanded", String(state.threadExpanded));
if (state.currentDetail) renderThread(state.currentDetail);
});
$("#folder-manage-btn").addEventListener("click", openFolderDialog);
$("#create-folder-btn").addEventListener("click", createFolder);
$("#rename-folder-btn").addEventListener("click", renameFolder);
$("#delete-folder-btn").addEventListener("click", deleteFolder);
$("#subscribe-folder-btn").addEventListener("click", toggleCurrentFolderSubscription);
$("#headers-btn").addEventListener("click", showHeaders);
$("#source-btn").addEventListener("click", showMessageSource);
$("#quick-filter-btn").addEventListener("click", () => createFilterFromCurrentMessage("sender"));
$("#reader-from").addEventListener("click", (event) => { const rect = event.currentTarget.getBoundingClientRect(); openCurrentSenderMenu(rect.left, rect.bottom + 4); });
$("#reader-from").addEventListener("keydown", (event) => { if (["Enter", " "].includes(event.key)) { event.preventDefault(); const rect = event.currentTarget.getBoundingClientRect(); openCurrentSenderMenu(rect.left, rect.bottom + 4); } });
$("#print-btn").addEventListener("click", printCurrentMessage);
$("#pgp-reader-process").addEventListener("click", processCurrentPgpMessage);
$("#pgp-reader-discover").addEventListener("click", discoverCurrentPgpSigner);
$("#copy-headers-btn").addEventListener("click", copyHeaders);
$$(".close-dialog").forEach((button) => button.addEventListener("click", async () => {
const dialog = button.closest("dialog");
if (dialog?.id === "compose-dialog") await closeComposer();
else dialog?.close();
}));
$("#compose-dialog").addEventListener("cancel", async (event) => { event.preventDefault(); await closeComposer(); });
window.addEventListener("beforeunload", () => { if (state.composeDirty && draftHasContent()) storeLocalDraftSnapshot(); });
$("#compose-pgp-check").addEventListener("click", () => checkComposePgpRecipients(true));
$("#compose-pgp-discover").addEventListener("click", discoverComposePgpRecipients);
["#compose-to", "#compose-cc", "#compose-bcc"].forEach((selector) => $(selector).addEventListener("change", () => { state.pgpRecipientStatus = []; updateComposePgpUi(); }));
$("#pgp-refresh").addEventListener("click", loadOpenPgp);
$("#pgp-pref-save").addEventListener("click", savePgpPreferences);
$("#pgp-generate").addEventListener("click", generatePgpKey);
$("#pgp-import").addEventListener("click", () => importPgpText());
$("#pgp-import-file").addEventListener("click", () => $("#pgp-import-file-input").click());
$("#pgp-import-file-input").addEventListener("change", async (event) => { const file = event.target.files?.[0]; if (file) await importPgpFile(file); event.target.value = ""; });
$("#pgp-discover").addEventListener("click", () => discoverPgpEmail());
$$(".settings-tab").forEach((button) => button.addEventListener("click", () => { selectSettingsTab(button.dataset.tab); if (button.dataset.tab === "sieve") loadSieve(); if (button.dataset.tab === "identities") { loadIdentities(); loadOpenPgp(); } if (button.dataset.tab === "openpgp") loadOpenPgp(); if (button.dataset.tab === "notifications") loadNotificationPreferences(); if (button.dataset.tab === "mailbox") loadMailPreferences(); if (button.dataset.tab === "composing") loadResponseTemplates(); if (button.dataset.tab === "diagnostics") loadDiagnostics(); if (button.dataset.tab === "account") { renderAccountSettings(); loadActiveSessions(); } if (button.dataset.tab === "folders") { loadSpecialFolders(); loadQuota(); } }));
$("#pref-theme").addEventListener("change", (event) => savePreference("theme", event.target.value));
$("#pref-interface-skin").addEventListener("change", (event) => savePreference("interfaceSkin", event.target.value));
$("#pref-density").addEventListener("change", (event) => savePreference("density", event.target.value));
$("#pref-preview").addEventListener("change", (event) => savePreference("preview", event.target.value));
$("#pref-scale").addEventListener("change", (event) => savePreference("scale", event.target.value));
$("#pref-language").addEventListener("change", (event) => savePreference("language", event.target.value));
$("#pref-color-preset").addEventListener("change", (event) => savePreference("colorPreset", event.target.value));
$("#pref-accent").addEventListener("input", (event) => { $("#pref-accent-value").textContent = event.target.value; savePreference("accentColor", event.target.value); });
[["#pref-background", "#pref-background-value", "backgroundColor"], ["#pref-panel", "#pref-panel-value", "panelColor"], ["#pref-text-color", "#pref-text-color-value", "textColor"]].forEach(([input, output, key]) => $(input).addEventListener("input", (event) => { $(output).textContent = event.target.value; savePreference(key, event.target.value); }));
$("#pref-font-family").addEventListener("change", (event) => savePreference("fontFamily", event.target.value));
[["#pref-font-size", "fontSize"], ["#pref-row-height", "rowHeight"], ["#pref-folder-width", "folderPaneWidth"], ["#pref-message-width", "messagePaneWidth"], ["#pref-radius", "borderRadius"]].forEach(([selector, key]) => $(selector).addEventListener("input", (event) => savePreference(key, Number(event.target.value))));
$("#pref-contrast").addEventListener("change", (event) => savePreference("contrast", event.target.value));
$("#pref-date-format").addEventListener("change", (event) => { savePreference("dateFormat", event.target.value); renderMessages(); if (state.currentDetail) renderMessageDetail(state.currentDetail); });
$("#pref-time-format").addEventListener("change", (event) => { savePreference("timeFormat", event.target.value); renderMessages(); if (state.currentDetail) renderMessageDetail(state.currentDetail); });
$("#pref-avatars").addEventListener("change", (event) => savePreference("showAvatars", event.target.checked));
$("#pref-attachment-icons").addEventListener("change", (event) => savePreference("showAttachmentIcons", event.target.checked));
$("#pref-flags").addEventListener("change", (event) => savePreference("showFlags", event.target.checked));
$("#pref-reduce-motion").addEventListener("change", (event) => savePreference("reduceMotion", event.target.checked));
[["#pref-icon-size", "iconSize"], ["#pref-sidebar-height", "sidebarItemHeight"], ["#pref-composer-width", "composerWidth"], ["#pref-unread-weight", "unreadFontWeight"]].forEach(([selector, key]) => $(selector).addEventListener("input", (event) => savePreference(key, Number(event.target.value))));
$("#pref-reader-width").addEventListener("change", (event) => savePreference("readerMaxWidth", Number(event.target.value)));
$("#pref-toolbar-density").addEventListener("change", (event) => savePreference("toolbarDensity", event.target.value));
$("#pref-selection-color").addEventListener("input", (event) => { $("#pref-selection-color-value").textContent = event.target.value; savePreference("selectionColor", event.target.value); });
$("#pref-unread-badge").addEventListener("change", (event) => savePreference("unreadBadgeStyle", event.target.value));
$("#pref-scrollbar-density").addEventListener("change", (event) => savePreference("scrollbarDensity", event.target.value));
$("#pref-compact-headers").addEventListener("change", (event) => savePreference("compactHeaders", event.target.checked));
$("#pref-show-borders").addEventListener("change", (event) => savePreference("showBorders", event.target.checked));
$("#pref-monochrome").addEventListener("change", (event) => savePreference("monochrome", event.target.checked));
[["folder", "folderBackground", "#101310"], ["message", "messageBackground", "#181c19"], ["reader", "readerBackground", "#181c19"]].forEach(([name, key, fallback]) => {
const enabled = $(`#pref-${name}-bg-enabled`); const color = $(`#pref-${name}-bg`);
enabled.addEventListener("change", () => savePreference(key, enabled.checked ? color.value : ""));
color.addEventListener("input", () => { if (enabled.checked) savePreference(key, color.value); });
});
$("#response-template-new").addEventListener("click", () => editResponseTemplate());
$("#compose-response-template").addEventListener("change", (event) => { $("#compose-response-insert").disabled = !event.target.value; });
$("#compose-response-insert").addEventListener("click", insertSavedResponse);
$("#compose-pref-format").addEventListener("change", (event) => savePreference("composeFormat", event.target.value));
$("#compose-pref-autosave").addEventListener("change", (event) => savePreference("composeAutosaveMs", Number(event.target.value)));
$("#compose-pref-priority").addEventListener("change", (event) => savePreference("defaultPriority", event.target.value));
$("#compose-pref-mdn").addEventListener("change", (event) => savePreference("requestMdnDefault", event.target.checked));
$("#compose-pref-attachment-reminder").addEventListener("change", (event) => savePreference("attachmentReminder", event.target.checked));
$("#compose-pref-font").addEventListener("change", (event) => savePreference("composeFontFamily", event.target.value));
$("#compose-pref-reply-position").addEventListener("change", (event) => savePreference("replyPosition", event.target.value));
$("#compose-pref-quote-style").addEventListener("change", (event) => savePreference("quoteStyle", event.target.value));
$("#compose-pref-forward-mode").addEventListener("change", (event) => savePreference("forwardMode", event.target.value));
$("#compose-pref-spellcheck").addEventListener("change", (event) => savePreference("spellcheckEnabled", event.target.checked));
$("#compose-markdown-preview").addEventListener("click", previewMarkdown);
$("#pref-reset").addEventListener("click", resetAppearance);
$("#special-folders-save").addEventListener("click", saveSpecialFolders);
$("#special-folders-auto").addEventListener("click", autoDetectSpecialFolders);
$("#identity-form").addEventListener("submit", saveIdentity);
$("#identity-new").addEventListener("click", () => editIdentity(null));
$("#identity-delete").addEventListener("click", deleteIdentity);
$$("[data-signature-command]").forEach((button) => button.addEventListener("click", () => {
const editor = $("#identity-signature");
editor.focus();
document.execCommand(button.dataset.signatureCommand, false, null);
}));
$("#identity-signature-link").addEventListener("click", async () => {
const editor = $("#identity-signature");
const range = captureEditorRange(editor);
const url = await appPrompt("Insert signature link", { placeholder: "https://example.com", type: "url", confirmLabel: "Insert" });
if (!url) return;
editor.focus();
restoreEditorRange(range, editor);
document.execCommand("createLink", false, url);
});
$("#identity-signature-image-btn").addEventListener("click", () => { state.identitySignatureRange = captureEditorRange($("#identity-signature")); $("#identity-signature-image").click(); });
$("#identity-signature-image").addEventListener("change", async (event) => { const file = event.target.files?.[0]; await insertIdentitySignatureImage(file); event.target.value = ""; });
$("#identity-signature").addEventListener("paste", async (event) => {
const file = [...(event.clipboardData?.items || [])].find((item) => item.kind === "file" && /^image\/(png|jpeg|gif|webp)$/i.test(item.type))?.getAsFile();
if (!file) return;
event.preventDefault();
await insertIdentitySignatureImage(file, captureEditorRange($("#identity-signature")));
});
$("#contact-form").addEventListener("submit", saveContact);
$("#contact-new").addEventListener("click", () => editContact(null));
$("#contact-import").addEventListener("click", () => $("#contact-import-file").click());
$("#contact-import-file").addEventListener("change", (event) => importContactVcard(event.target.files));
$("#contact-export").addEventListener("click", downloadContactExport);
$("#contact-duplicates").addEventListener("click", showContactDuplicates);
$("#contact-photo-upload").addEventListener("click", () => $("#contact-photo-file").click());
$("#contact-photo-file").addEventListener("change", (event) => uploadContactPhoto(event.target.files));
$("#contact-photo-delete").addEventListener("click", deleteContactPhoto);
$("#contact-delete").addEventListener("click", deleteContact);
$("#contact-compose").addEventListener("click", composeToContact);
$("#contact-search").addEventListener("input", (event) => { clearTimeout(event.target._timer); event.target._timer = setTimeout(() => loadContacts(event.target.value.trim()), 180); });
$("#calendar-form").addEventListener("submit", saveCalendarEvent);
$("#calendar-import").addEventListener("click", () => $("#calendar-import-file").click());
$("#calendar-import-file").addEventListener("change", (event) => importCalendarIcs(event.target.files));
$("#calendar-export").addEventListener("click", exportCalendarIcs);
$("#calendar-recurrence").addEventListener("change", updateCalendarRecurrenceUi);
$("#calendar-new").addEventListener("click", () => editCalendarEvent(null));
$("#calendar-cancel").addEventListener("click", () => $("#calendar-form").classList.add("hidden"));
$("#calendar-delete").addEventListener("click", deleteCalendarEvent);
$("#calendar-view").addEventListener("change", async (event) => { state.calendarView = event.target.value; localStorage.setItem("rustbox.calendarView", state.calendarView); await loadCalendar(); });
$("#calendar-prev").addEventListener("click", async () => { shiftCalendarCursor(-1); await loadCalendar(); });
$("#calendar-next").addEventListener("click", async () => { shiftCalendarCursor(1); await loadCalendar(); });
$("#calendar-today").addEventListener("click", async () => { state.calendarCursor = new Date(); await loadCalendar(); });
$("#notify-save").addEventListener("click", saveNotificationPreferences);
$("#notify-permission").addEventListener("click", requestNotificationPermission);
$("#notify-test").addEventListener("click", testNotification);
$("#notify-enabled").addEventListener("change", (event) => { if (event.target.checked && "Notification" in window && Notification.permission === "default") requestNotificationPermission(); });
$("#mail-pref-save").addEventListener("click", saveMailPreferences);
$("#mail-pref-reconcile-labels")?.addEventListener("click", reconcileImapLabels);
$("#diagnostics-refresh").addEventListener("click", loadDiagnostics);
$("#diagnostic-imap-reconnect").addEventListener("click", reconnectImap);
$("#diagnostic-search-rebuild").addEventListener("click", rebuildSearchIndex);
$("#remote-image-load").addEventListener("click", () => { if (state.currentDetail) renderMessageBody(state.currentDetail, true); });
$("#remote-image-trust").addEventListener("click", trustCurrentSender);
$("#reader-add-sender-contact").addEventListener("click", (event) => { event.stopPropagation(); addSenderToContacts(); });
const sieveBuilderFields = [
"#sieve-builder-name", "#sieve-builder-enabled", "#sieve-builder-stop",
"#sieve-builder-condition", "#sieve-builder-match", "#sieve-builder-header", "#sieve-builder-value", "#sieve-builder-negate",
"#sieve-builder-condition-2", "#sieve-builder-match-2", "#sieve-builder-header-2", "#sieve-builder-value-2", "#sieve-builder-negate-2", "#sieve-builder-logic",
"#sieve-builder-condition-3", "#sieve-builder-match-3", "#sieve-builder-header-3", "#sieve-builder-value-3", "#sieve-builder-negate-3", "#sieve-builder-logic-3",
"#sieve-builder-action", "#sieve-builder-target", "#sieve-builder-action-2", "#sieve-builder-target-2",
];
sieveBuilderFields.forEach((selector) => {
const node = $(selector);
if (!node) return;
node.addEventListener("input", updateSieveBuilder);
node.addEventListener("change", updateSieveBuilder);
});
$("#sieve-rule-new").addEventListener("click", openNewSieveRuleDialog);
$("#sieve-builder-add-condition").addEventListener("click", () => { $("#sieve-builder-extra-conditions").append(createSieveDynamicCondition()); updateSieveBuilder(); });
$("#sieve-builder-add-action").addEventListener("click", () => { $("#sieve-builder-extra-actions").append(createSieveDynamicAction()); updateSieveBuilder(); });
$("#sieve-vacation-start").addEventListener("change", updateSieveBuilder);
$("#sieve-vacation-end").addEventListener("change", updateSieveBuilder);
$("#sieve-builder-append").addEventListener("click", () => applySieveVisualRule(false));
$("#sieve-builder-replace").addEventListener("click", () => applySieveVisualRule(true));
$("#sieve-builder-clear").addEventListener("click", clearSieveRuleBuilder);
$("#sieve-editor").addEventListener("input", () => {
state.sieveEditingRuleId = null;
renderManagedSieveRules();
updateSieveBuilder();
});
$("#sieve-vacation-append").addEventListener("click", appendSieveVacation);
$("#sieve-backup").addEventListener("click", backupSieveScripts);
$("#sieve-restore").addEventListener("click", () => $("#sieve-restore-file").click());
$("#sieve-restore-file").addEventListener("change", (event) => restoreSieveBackup(event.target.files?.[0]));
$("#sieve-refresh").addEventListener("click", loadSieve);
$("#sieve-new").addEventListener("click", newSieveScript);
$("#sieve-check").addEventListener("click", validateSieve);
$("#sieve-save").addEventListener("click", saveSieve);
$("#sieve-activate").addEventListener("click", activateSieve);
$("#sieve-deactivate").addEventListener("click", deactivateSieve);
$("#sieve-delete").addEventListener("click", deleteSieve);
bindEditor();
document.addEventListener("click", (event) => { if (!event.target.closest("#context-menu")) hideContextMenu(); });
window.addEventListener("blur", hideContextMenu);
document.addEventListener("scroll", hideContextMenu, true);
document.addEventListener("keydown", (event) => {
const tag = document.activeElement?.tagName || "";
if (event.key === "/" && !["INPUT", "TEXTAREA"].includes(tag) && !document.activeElement?.isContentEditable) {
event.preventDefault(); $("#search-input").focus();
}
if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === "n") { event.preventDefault(); openCompose("new"); }
if (event.key === "?" && !["INPUT", "TEXTAREA", "SELECT"].includes(tag) && !document.activeElement?.isContentEditable) {
event.preventDefault();
if (!document.querySelector("dialog[open]")) $("#shortcuts-dialog").showModal();
}
const dialogOpen = Boolean(document.querySelector("dialog[open]"));
const keyboardIdle = !dialogOpen && !["INPUT", "TEXTAREA", "SELECT"].includes(tag) && !document.activeElement?.isContentEditable;
if (keyboardIdle) {
const globalKey = event.key.toLowerCase();
if (globalKey === "c") { event.preventDefault(); openCompose("new"); return; }
if (globalKey === "v") { event.preventDefault(); setSelectionMode(!state.selectionMode); return; }
}
if (keyboardIdle && state.currentDetail) {
const key = event.key.toLowerCase();
if (key === "x") {
const current = state.messages.find((message) => message.uid === state.currentDetail.uid && (message.folder || state.folder) === state.currentDetail.folder);
if (current) { event.preventDefault(); setMessageSelected(current, !state.selectedMessages.has(messageKey(current))); }
}
else if (key === "j") { event.preventDefault(); navigateMessage(1); }
else if (key === "k") { event.preventDefault(); navigateMessage(-1); }
else if (key === "r") { event.preventDefault(); openCompose("reply"); }
else if (key === "a") { event.preventDefault(); openCompose("reply-all"); }
else if (key === "f") { event.preventDefault(); openCompose("forward"); }
else if (key === "e") { event.preventDefault(); archiveCurrent(); }
else if (key === "m") { event.preventDefault(); toggleUnread(); }
else if (key === "s") { event.preventDefault(); toggleFlagged(); }
else if (event.key === "!") { event.preventDefault(); junkCurrent(); }
else if (event.key === "Delete") { event.preventDefault(); deleteCurrent(); }
}
if (event.key === "Escape" && innerWidth <= 820) {
if ($("#folder-pane")?.classList.contains("open")) setFolderDrawerOpen(false);
else closeReader();
}
});
matchMedia("(prefers-color-scheme: light)").addEventListener("change", () => { if (state.prefs.theme === "system") applyPreferences(); });
window.addEventListener("resize", () => { if (innerWidth > 820) setFolderDrawerOpen(false); });
}
async function boot() {
applyPreferences();
bindEvents();
await loadAuthPolicy();
const authenticated = await loadAccounts();
if (authenticated) { await loadPreferences(); await loadMailPreferences(); await loadOpenPgp(); await loadIdentities(); await loadResponseTemplates(); await loadFolders(true); await loadSpecialFolders(); await loadTags(); await loadNotificationPreferences(); await loadTrustedSenders(); startCalendarReminderWatcher(); connectRealtime(); } else { await loadTranslations(state.prefs.language); }
}
boot();
}) ();