/* 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(); }) ();