@bobfrankston/rmfmail 1.1.125 → 1.1.127

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../lib/api-client.ts", "../lib/rmf-tiny.js", "../../node_modules/is-buffer/index.js", "../../node_modules/nspell/lib/util/rule-codes.js", "../../node_modules/nspell/lib/util/affix.js", "../../node_modules/nspell/lib/util/normalize.js", "../../node_modules/nspell/lib/util/flag.js", "../../node_modules/nspell/lib/util/exact.js", "../../node_modules/nspell/lib/util/form.js", "../../node_modules/nspell/lib/correct.js", "../../node_modules/nspell/lib/util/casing.js", "../../node_modules/nspell/lib/suggest.js", "../../node_modules/nspell/lib/spell.js", "../../node_modules/nspell/lib/util/apply.js", "../../node_modules/nspell/lib/util/add.js", "../../node_modules/nspell/lib/add.js", "../../node_modules/nspell/lib/remove.js", "../../node_modules/nspell/lib/word-characters.js", "../../node_modules/nspell/lib/util/dictionary.js", "../../node_modules/nspell/lib/dictionary.js", "../../node_modules/nspell/lib/personal.js", "../../node_modules/nspell/lib/index.js", "spellcheck.ts", "ghost-text.ts", "editor-help.ts", "editor.ts", "compose.ts", "../components/context-menu.ts"],
4
- "sourcesContent": ["/**\n * API client \u2014 all operations go through the IPC bridge (mailxapi).\n * mailxapi is injected by the launcher (msger on desktop, MAUI on Android).\n */\n\ndeclare const mailxapi: any;\n\nfunction getIpc(): any {\n if (typeof mailxapi !== \"undefined\" && mailxapi?.isApp) return mailxapi;\n if ((window as any).opener?.mailxapi?.isApp) return (window as any).opener.mailxapi;\n // Compose iframe \u2014 check parent\n if ((window as any).parent?.mailxapi?.isApp) return (window as any).parent.mailxapi;\n return null;\n}\n\n/** Build a proxy bridge that forwards every method call to the parent window\n * via postMessage. Used when the compose iframe's own attempt to reach\n * msger's IPC silently drops messages \u2014 empirically, `sendMessage` and\n * `saveDraft` both hit this (user-visible: \"Sending\u2026\" spinner forever;\n * \"Draft save failed: mailxapi timeout\"). The main window's bridge is\n * provably fine, so the iframe routes through it. */\nfunction buildRelayBridge(): any {\n const pending = new Map<string, { resolve: (v: any) => void; reject: (e: any) => void; timer: any }>();\n window.addEventListener(\"message\", (ev: MessageEvent) => {\n if (!ev.data || ev.data.type !== \"mailx-ipc-result\" || !ev.data.id) return;\n const entry = pending.get(ev.data.id);\n if (!entry) return;\n pending.delete(ev.data.id);\n clearTimeout(entry.timer);\n if (ev.data.ok) entry.resolve(ev.data.result);\n else entry.reject(new Error(ev.data.error || \"parent-relay ipc error\"));\n });\n const call = (method: string, args: any[]): Promise<any> => {\n const id = `ipc-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;\n return new Promise((resolve, reject) => {\n const timer = setTimeout(() => {\n pending.delete(id);\n reject(new Error(`parent-relay timeout: ${method}`));\n }, 120000);\n pending.set(id, { resolve, reject, timer });\n try {\n (window.parent as any).postMessage({ type: \"mailx-ipc\", id, method, args }, \"*\");\n } catch (e) {\n clearTimeout(timer);\n pending.delete(id);\n reject(e);\n }\n });\n };\n // Proxy: any property access returns a function that forwards to parent.\n // `isApp` / `platform` / other non-function reads return sensible defaults\n // so existing getIpc-style checks still work.\n return new Proxy({}, {\n get(_t, prop: string) {\n if (prop === \"isApp\") return true;\n if (prop === \"platform\") return (window.parent as any)?.mailxapi?.platform || \"webview2\";\n if (prop === \"onEvent\") {\n // Event subscription can't be relayed simply \u2014 iframes that need\n // events are rare. Fall back to direct parent bridge for onEvent\n // since the subscription path doesn't hit the broken send path.\n return (handler: any) => (window.parent as any)?.mailxapi?.onEvent?.(handler);\n }\n return (...args: any[]) => call(prop, args);\n }\n });\n}\n\nlet cachedRelayBridge: any = null;\nfunction ipc(): any {\n // Direct bridge is fine for the top window (main mailx app). The iframe\n // (compose) can't trust its own bridge resolution because msger-routed\n // sendMessage / saveDraft IPCs disappear without trace. So when we're in\n // a child frame with a parent bridge, go through the parent.\n const inIframe = window.parent && window.parent !== window;\n if (inIframe && (window.parent as any)?.mailxapi?.isApp) {\n if (!cachedRelayBridge) cachedRelayBridge = buildRelayBridge();\n return cachedRelayBridge;\n }\n const bridge = getIpc();\n if (!bridge) throw new Error(\"IPC bridge not available\");\n return bridge;\n}\n\n// \u2500\u2500 Abort controller for message-list requests \u2500\u2500\n\nlet messageListAbort: AbortController | null = null;\n\nexport function abortMessageListRequests(): void {\n if (messageListAbort) {\n messageListAbort.abort();\n messageListAbort = null;\n }\n}\n\n// \u2500\u2500 API Methods \u2500\u2500\n\nexport function getAccounts() {\n return ipc().getAccounts();\n}\n\nexport function getFolders(accountId: string) {\n return ipc().getFolders(accountId);\n}\n\nexport function getMessages(accountId: string, folderId: number, page = 1, pageSize = 50, flaggedOnly = false, sort?: string, sortDir?: string) {\n abortMessageListRequests();\n return ipc().getMessages(accountId, folderId, page, pageSize, sort, sortDir, undefined, flaggedOnly);\n}\n\nexport function getUnifiedInbox(page = 1, pageSize = 50) {\n abortMessageListRequests();\n return ipc().getUnifiedInbox(page, pageSize);\n}\n\nexport function searchMessages(query: string, page = 1, pageSize = 50, scope = \"all\", accountId = \"\", folderId = 0, includeTrashSpam = false) {\n return ipc().searchMessages(query, page, pageSize, scope, accountId, folderId, includeTrashSpam);\n}\n\n/** Abort any in-flight server (IMAP) search. Fire-and-forget \u2014 the daemon\n * bumps a generation counter its per-folder loop checks between batches. */\nexport function cancelServerSearch() {\n return ipc().cancelServerSearch?.();\n}\n\nexport function getMessage(accountId: string, uid: number, allowRemote = false, folderId?: number) {\n return ipc().getMessage(accountId, uid, allowRemote, folderId);\n}\n\nexport function updateFlags(accountId: string, uid: number, flags: string[]) {\n return ipc().updateFlags(accountId, uid, flags);\n}\n\nexport function triggerSync() {\n return ipc().syncAll();\n}\n\nexport function syncAccount(accountId: string) {\n return ipc().syncAccount(accountId);\n}\n\nexport function reauthenticate(accountId: string) {\n return ipc().reauthenticate(accountId);\n}\n\nexport function reauthGoogleScopes(): Promise<{ cleared: number }> {\n return (ipc() as any).reauthGoogleScopes();\n}\n\nexport function getSyncPending() {\n return ipc().getSyncPending();\n}\n\nexport function getDiagnostics(): Promise<any> {\n return ipc().getDiagnostics?.() ?? Promise.resolve([]);\n}\n\n/** Account that supplies `feature` data (calendar / tasks / contacts).\n * Resolution: per-feature primary flag \u2192 catch-all `primary` \u2192 first account.\n * Pass e.g. \"calendar\" to honor `primaryCalendar:true` overrides; omit for\n * back-compat single-flag behavior. */\nexport function getPrimaryAccount(feature?: string): Promise<any> {\n return ipc().getPrimaryAccount?.(feature) ?? Promise.resolve(null);\n}\n\n// Calendar / Tasks: two-way cache. Reads return local-cached rows; writes\n// commit locally and queue a push to Google. Service layer handles drain.\nexport function getCalendarEvents(fromMs: number, toMs: number): Promise<any[]> {\n return ipc().getCalendarEvents?.(fromMs, toMs) ?? Promise.resolve([]);\n}\n/** List the user's selected Google calendars (id, name, color, primary) so\n * the sidebar can render a checkbox + icon per calendar. */\nexport function getCalendars(): Promise<Array<{ id: string; name: string; color: string; primary: boolean }>> {\n return ipc().getCalendars?.() ?? Promise.resolve([]);\n}\nexport function createCalendarEvent(ev: {\n title: string; startMs: number; endMs: number; allDay?: boolean;\n location?: string; notes?: string;\n}): Promise<{ uuid: string }> {\n return ipc().createCalendarEvent?.(ev);\n}\nexport function updateCalendarEvent(uuid: string, patch: any): Promise<{ ok: boolean }> {\n return ipc().updateCalendarEvent?.(uuid, patch);\n}\nexport function deleteCalendarEvent(uuid: string): Promise<{ ok: boolean }> {\n return ipc().deleteCalendarEvent?.(uuid);\n}\nexport function getTasks(includeCompleted = false): Promise<any[]> {\n return ipc().getTasks?.(includeCompleted) ?? Promise.resolve([]);\n}\nexport function createTask(t: { title: string; notes?: string; dueMs?: number }): Promise<{ uuid: string }> {\n return ipc().createTask?.(t);\n}\nexport function updateTask(uuid: string, patch: any): Promise<{ ok: boolean }> {\n return ipc().updateTask?.(uuid, patch);\n}\nexport function deleteTask(uuid: string): Promise<{ ok: boolean }> {\n return ipc().deleteTask?.(uuid);\n}\nexport function drainStoreSync(): Promise<{ ok: boolean }> {\n return ipc().drainStoreSync?.();\n}\n\n/** Report the currently-viewed message as spam \u2192 appends a row to\n * `~/.mailx/spam.csv`. Placeholder: no folder move, no flag change, no\n * auto-delete. Training data for a smarter pass later. */\nexport function recordSpamReport(accountId: string, uid: number, folderId: number): Promise<{ ok: boolean }> {\n return ipc().recordSpamReport?.(accountId, uid, folderId);\n}\n\nexport function getOutboxStatus() {\n return (ipc() as any).getOutboxStatus();\n}\n\nexport function listQueuedOutgoing() {\n return (ipc() as any).listQueuedOutgoing();\n}\n\nexport function cancelQueuedOutgoing(p: string) {\n return (ipc() as any).cancelQueuedOutgoing(p);\n}\n\nexport function searchContacts(query: string) {\n return ipc().searchContacts(query);\n}\n\nexport function hasCcHistoryTo(email: string): Promise<{ hasCc: boolean }> {\n return (ipc() as any).hasCcHistoryTo(email);\n}\n\nexport function hasBccHistoryTo(email: string): Promise<{ hasBcc: boolean }> {\n return (ipc() as any).hasBccHistoryTo(email);\n}\n\nexport function listContacts(query: string, page = 1, pageSize = 100) {\n return (ipc() as any).listContacts(query, page, pageSize);\n}\n\nexport function upsertContact(name: string, email: string) {\n return (ipc() as any).upsertContact(name, email);\n}\n\nexport function deleteContact(email: string) {\n return (ipc() as any).deleteContact(email);\n}\n\nexport function addPreferredContact(entry: { name: string; email: string; source?: string; organization?: string }) {\n return (ipc() as any).addPreferredContact(entry.name, entry.email, entry.source, entry.organization);\n}\n\nexport function getPriorityLists(): Promise<{ senders: string[]; domains: string[] }> {\n return (ipc() as any).getPriorityLists();\n}\n\nexport function setPrioritySender(email: string, value: boolean, name?: string): Promise<{ ok: boolean }> {\n return (ipc() as any).setPrioritySender(email, value, name);\n}\n\nexport function setPriorityDomain(domain: string, value: boolean): Promise<{ ok: boolean }> {\n return (ipc() as any).setPriorityDomain(domain, value);\n}\n\nexport function addToDenylist(email: string) {\n return (ipc() as any).addToDenylist(email);\n}\n\nexport function openLocalPath(which: \"config\" | \"log\") {\n return (ipc() as any).openLocalPath(which);\n}\n/** Open an absolute file path (under ~/.rmfmail) in the OS default\n * *text* editor \u2014 Notepad on Windows, TextEdit on Mac, $EDITOR or\n * xdg-open(.txt) on Linux. Distinct from the file's default app,\n * which for .eml is usually Outlook / a mail client. */\nexport function openInTextEditor(path: string): Promise<{ ok: boolean; opener: string; reason?: string }> {\n return (ipc() as any).openInTextEditor?.(path) ?? Promise.resolve({ ok: false, opener: \"none\", reason: \"no host\" });\n}\n\nexport function allowRemoteContent(type: string, value: string) {\n return ipc().allowRemoteContent(type, value);\n}\nexport function getUserDict(): Promise<string[]> {\n return (ipc() as any).getUserDict?.() ?? Promise.resolve([]);\n}\nexport function addUserDictWord(word: string): Promise<string[]> {\n return (ipc() as any).addUserDictWord?.(word) ?? Promise.resolve([]);\n}\nexport function addUserDictWords(words: string[]): Promise<string[]> {\n return (ipc() as any).addUserDictWords?.(words) ?? Promise.resolve([]);\n}\nexport function removeUserDictWord(word: string): Promise<string[]> {\n return (ipc() as any).removeUserDictWord?.(word) ?? Promise.resolve([]);\n}\nexport function flagSenderOrDomain(type: \"sender\" | \"domain\", value: string): Promise<{ flagged: boolean }> {\n return (ipc() as any).flagSenderOrDomain?.(type, value) ?? Promise.resolve({ flagged: false });\n}\n\nexport function deleteMessage(accountId: string, uid: number) {\n return ipc().deleteMessage?.(accountId, uid);\n}\n\nexport function deleteMessages(accountId: string, uids: number[]) {\n if (uids.length === 1) return deleteMessage(accountId, uids[0]);\n return ipc().deleteMessages?.(accountId, uids);\n}\n\nexport function moveMessages(accountId: string, uids: number[], targetFolderId: number, targetAccountId?: string) {\n if (uids.length === 1) return moveMessage(accountId, uids[0], targetFolderId, targetAccountId);\n return ipc().moveMessages?.(accountId, uids, targetFolderId, targetAccountId);\n}\n\nexport function markAsSpamMessages(accountId: string, uids: number[]): Promise<{ targetFolderId: number; moved: number }> {\n return ipc().markAsSpamMessages?.(accountId, uids);\n}\n\nexport function undeleteMessage(accountId: string, uid: number, folderId: number) {\n return ipc().undeleteMessage?.(accountId, uid, folderId);\n}\n\nexport function moveMessage(accountId: string, uid: number, targetFolderId: number, targetAccountId?: string) {\n return ipc().moveMessage?.(accountId, uid, targetFolderId, targetAccountId);\n}\n\nexport function restartServer() {\n return ipc().restart?.();\n}\n\nexport function markFolderRead(accountId: string, folderId: number) {\n return ipc().markFolderRead?.(accountId, folderId);\n}\n\nexport function createFolder(accountId: string, parentPath: string, name: string) {\n return ipc().createFolder?.(accountId, parentPath, name);\n}\n\nexport function renameFolder(accountId: string, folderId: number, newName: string) {\n return ipc().renameFolder?.(accountId, folderId, newName);\n}\n\nexport function deleteFolder(accountId: string, folderId: number) {\n return ipc().deleteFolder?.(accountId, folderId);\n}\n\nexport function moveFolderToTrash(accountId: string, folderId: number) {\n return ipc().moveFolderToTrash?.(accountId, folderId);\n}\n\nexport function emptyFolder(accountId: string, folderId: number) {\n return ipc().emptyFolder?.(accountId, folderId);\n}\n\n/** Ship a named event to the Node log as `[client] <tag> <data>`. Fire and\n * forget \u2014 never awaits, never throws, never blocks the caller. Tries two\n * paths so a broken primary channel can't swallow the trace:\n * 1. Direct bridge call (self / opener / parent mailxapi).\n * 2. parent.postMessage fallback \u2014 the main window listens and relays.\n * The fallback matters because the whole point of tracing is to diagnose\n * a broken iframe bridge; a single-path tracer that goes through that same\n * bridge is useless in exactly the case we need it. */\nexport function logClientEvent(tag: string, data?: any): void {\n let delivered = false;\n try {\n const bridge = typeof (globalThis as any).mailxapi !== \"undefined\" && (globalThis as any).mailxapi?.isApp ? (globalThis as any).mailxapi\n : (window as any).opener?.mailxapi?.isApp ? (window as any).opener.mailxapi\n : (window as any).parent?.mailxapi?.isApp ? (window as any).parent.mailxapi\n : null;\n if (bridge?.logClientEvent) {\n bridge.logClientEvent(tag, data);\n delivered = true;\n }\n } catch { /* never throw from tracing */ }\n try {\n if (window.parent && window.parent !== window) {\n (window.parent as any).postMessage({ type: \"mailx-trace\", tag, data, bridged: delivered }, \"*\");\n }\n } catch { /* */ }\n}\n\nexport function sendMessage(body: any) {\n return ipc().sendMessage?.(body);\n}\n\nexport function saveDraft(body: any) {\n return ipc().saveDraft?.(body);\n}\n\n// \u2500\u2500 Events \u2500\u2500\n\ntype EventHandler = (event: any) => void;\nconst eventHandlers: EventHandler[] = [];\n\nexport function onEvent(handler: EventHandler): () => void {\n eventHandlers.push(handler);\n return () => {\n const i = eventHandlers.indexOf(handler);\n if (i >= 0) eventHandlers.splice(i, 1);\n };\n}\n\n// \u2500\u2500 Store-bus subscriptions (mirrors packages/mailx-store/bus.ts) \u2500\u2500\n//\n// The service forwards every storeBus event over IPC as `{ _event: \"store\",\n// topic, kind, ... }`. Mirror the topic/wildcard semantics here so consumers\n// subscribe by topic instead of filtering inside a single onEvent callback.\n// Same shape as the server-side bus \u2014 that's the load-bearing property.\n\ntype StoreEvent = { topic: string; kind: string; [k: string]: any };\ntype StoreHandler = (event: StoreEvent) => void;\nconst storeSubs = new Map<string, Set<StoreHandler>>();\n\nexport function subscribeStore(topic: string, handler: StoreHandler): () => void {\n let set = storeSubs.get(topic);\n if (!set) { set = new Set(); storeSubs.set(topic, set); }\n set.add(handler);\n return () => {\n const s = storeSubs.get(topic);\n if (s) { s.delete(handler); if (s.size === 0) storeSubs.delete(topic); }\n };\n}\n\nfunction deliverStore(event: StoreEvent): void {\n const exact = storeSubs.get(event.topic);\n if (exact) for (const h of exact) { try { h(event); } catch (e) { console.error(\"[store-bus]\", e); } }\n const wild = storeSubs.get(\"*\");\n if (wild) for (const h of wild) { try { h(event); } catch (e) { console.error(\"[store-bus]\", e); } }\n}\n\nexport function connectEvents(): void {\n ipc().onEvent((event: any) => {\n if (event && event._event === \"store\") deliverStore(event as StoreEvent);\n for (const h of eventHandlers) h(event);\n });\n}\n\n// \u2500\u2500 Autocomplete \u2500\u2500\n\nexport function autocomplete(body: { subject: string; to: string; bodyText: string; cursorOffset: number }, signal?: AbortSignal) {\n return ipc().autocomplete?.(body);\n}\n\nexport function getAutocompleteSettings() {\n return ipc().getAutocompleteSettings?.();\n}\n\nexport function saveAutocompleteSettings(settings: any) {\n return ipc().saveAutocompleteSettings?.(settings);\n}\n\nexport function getVersion() {\n return ipc().getVersion();\n}\n\nexport function getSettings() {\n return ipc().getSettings();\n}\n\nexport function saveSettings(settings: any) {\n return ipc().saveSettingsData?.(settings);\n}\n\nexport function repairAccounts() {\n return ipc().repairAccounts?.();\n}\n\nexport function deleteDraft(accountId: string, draftUid: number, draftId?: string) {\n return ipc().deleteDraft?.(accountId, draftUid, draftId);\n}\n\nexport function addContact(name: string, email: string) {\n return ipc().addContact?.(name, email);\n}\n\nexport function getThreadMessages(accountId: string, threadId: string) {\n return ipc().getThreadMessages?.(accountId, threadId);\n}\n\nexport function readJsoncFile(name: string): Promise<{ content: string | null }> {\n return ipc().readJsoncFile?.(name);\n}\nexport function writeJsoncFile(name: string, content: string): Promise<{ ok: boolean }> {\n return ipc().writeJsoncFile?.(name, content);\n}\nexport function formatJsonc(content: string): Promise<{ content: string }> {\n return (ipc() as any).formatJsonc?.(content);\n}\nexport function readConfigHelp(name: string): Promise<{ content: string }> {\n return ipc().readConfigHelp?.(name) ?? Promise.resolve({ content: \"\" });\n}\nexport function unsubscribeOneClick(url: string): Promise<{ ok: boolean; status: number; statusText: string }> {\n return ipc().unsubscribeOneClick?.(url);\n}\nexport function openInWord(editId: string, html: string): Promise<{ ok: boolean; path: string; opener: string }> {\n return (ipc() as any).openInWord?.(editId, html) ?? Promise.resolve({ ok: false, path: \"\", opener: \"none\" });\n}\nexport function closeWordEdit(editId: string): Promise<void> {\n return (ipc() as any).closeWordEdit?.(editId) ?? Promise.resolve();\n}\n\n/** Show an OS-level always-on-top reminder popup via msger. Returns the\n * label of the button the user clicked. Empty string when the host\n * isn't available (browser fallback) \u2014 caller should fall back to an\n * in-WebView popup in that case. */\nexport function showReminderPopup(opts: {\n title: string;\n html: string;\n buttons: string[];\n size?: { width: number; height: number };\n pos?: { x: number; y: number };\n}): Promise<{ button: string; form?: any; reason?: string }> {\n return (ipc() as any).showReminderPopup?.(opts) ?? Promise.resolve({ button: \"\", reason: \"no host\" });\n}\n\n/** Read + delete a pending mailto: drop file, if any (P115). Used at app\n * startup so a `mailx --mailto <url>` that just spawned us doesn't lose\n * its compose payload to the daemon-fires-before-app-registers race\n * window. Subsequent live clicks arrive via the `openMailto` event. */\nexport function consumePendingMailto(): Promise<{\n to: string[]; cc: string[]; bcc: string[];\n subject: string; body: string; inReplyTo: string;\n} | null> {\n return ipc().consumePendingMailto?.() ?? Promise.resolve(null);\n}\n\n/** Run an AI text transform (translate / proofread / summarize). Returns\n * empty `text` with a `reason` when the feature is disabled or the provider\n * errors \u2014 caller should surface `reason` in a status bar, not throw. */\nexport function aiTransform(req: {\n action: \"translate\" | \"proofread\" | \"summarize\" | \"extractEvent\";\n text: string;\n targetLang?: string;\n nowISO?: string;\n}): Promise<{ text: string; reason?: string; event?: any }> {\n return ipc().aiTransform?.(req) ?? Promise.resolve({ text: \"\", reason: \"AI not available in this host\" });\n}\n\nexport function setupAccount(name: string, email: string, password: string) {\n return ipc().setupAccount?.(name, email, password);\n}\n\nexport async function getAttachment(accountId: string, uid: number, attachmentId: number, folderId?: number): Promise<{ content: string; contentType: string; filename: string }> {\n return ipc().getAttachment(accountId, uid, attachmentId, folderId);\n}\n\n/** Desktop: have the Node service save the attachment and open it with the\n * OS default app. Returns undefined when the host has no such method (real\n * browser / --server mode) so the caller can fall back to a blob download. */\nexport async function openAttachment(accountId: string, uid: number, attachmentId: number, folderId?: number): Promise<{ ok: boolean; path: string } | undefined> {\n const fn = (ipc() as any).openAttachment;\n return fn ? fn(accountId, uid, attachmentId, folderId) : undefined;\n}\n\nexport async function getDeviceAccounts(): Promise<{ email: string; name: string }[]> {\n return ipc().getDeviceAccounts?.() ?? [];\n}\n\n// Legacy exports for backward compatibility\nexport const connectWebSocket = connectEvents;\nexport const onWsEvent = onEvent;\n", "/**\n * @bobfrankston/rmf-tiny \u2014 TinyMCE adapter for rmfmail.\n *\n * MIT-licensed adapter glue. Imports TinyMCE at runtime; ships no\n * TinyMCE bytes. The user is responsible for making TinyMCE reachable\n * to their environment:\n *\n * - Desktop / Node-side install: `npm install tinymce`\n * (rmfmail's createEditor(\"tinymce\") branch dynamically imports\n * this adapter, which dynamically imports tinymce from\n * node_modules.)\n *\n * - Android / browser-only WebView: pass `cdnUrl` in the options to\n * have the adapter inject a <script src=\u2026> tag. The bytes are\n * fetched directly from Tiny's CDN (or whatever URL the user\n * configured); the host APK / page never carries them.\n *\n * The adapter implements the same `MailxEditor` shape as the Quill /\n * tiptap factories in rmfmail's editor.ts, so the host code path is\n * identical regardless of which editor is in use.\n */\n/** Load tinymce. Tries native module import first (desktop / npm-installed);\n * falls back to script-tag injection from cdnUrl. Resolves to whatever's\n * on `window.tinymce`. */\nasync function loadTinymce(opts) {\n // 1. Try the npm-installed path. Wrapped in try because in browser-only\n // environments the module specifier won't resolve and we want to\n // fall through to CDN injection.\n try {\n const mod = await import(/* @vite-ignore */ \"tinymce\");\n const tinymce = mod.default || mod;\n // Pull in the plugins TinyMCE's paste-from-Word handler needs. These\n // are side-effect imports \u2014 they register themselves on the global.\n await Promise.all([\n import(\"tinymce/themes/silver\").catch(() => { }),\n import(\"tinymce/icons/default\").catch(() => { }),\n import(\"tinymce/models/dom\").catch(() => { }),\n import(\"tinymce/plugins/paste\").catch(() => { }),\n import(\"tinymce/plugins/lists\").catch(() => { }),\n import(\"tinymce/plugins/link\").catch(() => { }),\n import(\"tinymce/plugins/table\").catch(() => { }),\n import(\"tinymce/plugins/code\").catch(() => { }),\n import(\"tinymce/plugins/image\").catch(() => { }),\n ]);\n return tinymce;\n }\n catch {\n // Fall through.\n }\n // 2. Already loaded by a prior call.\n const w = window;\n if (w.tinymce)\n return w.tinymce;\n // 3. Inject from CDN if provided.\n if (!opts.cdnUrl) {\n throw new Error(\"rmf-tiny: tinymce not installed (npm install tinymce) and no cdnUrl supplied. See README for Android setup.\");\n }\n const url = opts.apiKey && !opts.cdnUrl.includes(\"api-key\")\n ? `${opts.cdnUrl}${opts.cdnUrl.includes(\"?\") ? \"&\" : \"?\"}apiKey=${encodeURIComponent(opts.apiKey)}`\n : opts.cdnUrl;\n await new Promise((resolve, reject) => {\n const s = document.createElement(\"script\");\n s.src = url;\n s.referrerPolicy = \"origin\";\n s.onload = () => resolve();\n s.onerror = () => reject(new Error(`rmf-tiny: failed to load TinyMCE from ${url}`));\n document.head.appendChild(s);\n });\n if (!w.tinymce)\n throw new Error(\"rmf-tiny: TinyMCE script loaded but window.tinymce is missing\");\n return w.tinymce;\n}\n/** Create a TinyMCE-backed MailxEditor in `container`. */\nexport async function createTinyMceEditor(container, opts = {}) {\n const tinymce = await loadTinymce(opts);\n // TinyMCE attaches to a target element by id selector; create one\n // inside the host container so multiple editors on a page don't\n // collide and so we don't pollute the caller's element with TinyMCE\n // attributes directly.\n const target = document.createElement(\"div\");\n target.id = `rmf-tiny-${Math.random().toString(36).slice(2, 10)}`;\n target.style.cssText = \"width:100%;height:100%;\";\n container.appendChild(target);\n const editor = await new Promise((resolve) => {\n tinymce.init({\n target,\n // Word-paste fidelity is the entire point of using TinyMCE here.\n // `paste_as_text: false` keeps formatting; powerpaste isn't in\n // OSS but the standard paste plugin handles Word reasonably well.\n // All free / OSS plugins bundled in the jsDelivr tinymce@6 script.\n // No premium plugins listed \u2014 listing one without an API key\n // triggers TinyMCE's upsell dialog on every load.\n plugins: \"paste lists advlist link table code codesample image searchreplace autolink wordcount emoticons charmap insertdatetime quickbars nonbreaking directionality help\",\n toolbar: [\n \"undo redo | bold italic underline strikethrough | forecolor backcolor\",\n \"bullist numlist outdent indent | link table image code codesample | emoticons charmap | help\",\n ].join(\" | \"),\n // Include \"tools\" so wordcount and searchreplace are reachable.\n menubar: \"file edit view insert format tools\",\n // View menu \u2014 append zoom commands. fullscreen omitted: this editor\n // lives inside a compose iframe overlay that already fills its host\n // window; TinyMCE's fullscreen plugin re-positions the container in\n // ways that produce a blank body, so it's not in the plugin list.\n menu: {\n view: { title: \"View\", items: \"code | visualaid visualchars visualblocks | preview | zoomIn zoomOut zoomReset\" },\n },\n // Disable the \"Get all features\" upsell badge that TinyMCE 6+\n // injects automatically when no premium plugins are listed.\n promotion: false,\n // Floating toolbar that appears on text selection \u2014 keeps the\n // main toolbar uncluttered while exposing common formatters\n // where the cursor already is. quickbars_insert_toolbar:false\n // suppresses the second (insert-on-blank-line) popup which\n // clutters more than it helps in an email composer.\n quickbars_selection_toolbar: \"bold italic underline | forecolor | quicklink blockquote\",\n quickbars_insert_toolbar: false,\n quickbars_image_toolbar: \"alignleft aligncenter alignright\",\n // WebView's native spell-check (red underlines, right-click\n // \"Add to dictionary\"). Free; same UX as Quill's spellcheck=true.\n // Premium tinymcespellchecker plugin would replace this with a\n // custom backend via spellchecker_rpc_url, but requires a\n // Tiny Cloud subscription.\n browser_spellcheck: true,\n // Right-click context menu DISABLED so WebView2's native menu\n // fires instead. We need the native menu because that's where\n // the browser shows spelling suggestions (TinyMCE's contextmenu\n // intercepts the right-click and replaces the menu \u2014 red\n // underlines appear but suggestions are unreachable). Trade-off:\n // lose TinyMCE's link / image / table quick actions. Standard\n // text formatting is still on the toolbar.\n contextmenu: false,\n statusbar: false,\n branding: false,\n license_key: \"gpl\",\n paste_data_images: true,\n // Permissive valid_elements \u2014 preserve as much of the source\n // formatting as possible. The default is more aggressive about\n // stripping. Empty string for `valid_elements` means accept\n // everything that the schema allows.\n paste_word_valid_elements: \"@[style|class],-strong/b,-em/i,-u,-s,-sub,-sup,-strike,-p,-ol,-ul,-li,-h1,-h2,-h3,-h4,-h5,-h6,-blockquote,-table[border|cellpadding|cellspacing|width|height|class|style],-tr,-td[colspan|rowspan|width|height|class|style|valign|align|background|bgcolor],-th,-thead,-tbody,-tfoot,-pre,-br,-a[href|target|title],-img[src|alt|width|height|style|class]\",\n paste_retain_style_properties: \"color background background-color font-family font-size font-weight font-style text-decoration text-align padding padding-top padding-bottom padding-left padding-right margin margin-top margin-bottom margin-left margin-right border border-top border-bottom border-left border-right\",\n // Auto-link bare URLs in pasted content. TinyMCE's `autolink`\n // plugin only fires on TYPED space/enter; URLs that arrive via\n // clipboard (browser address bar, terminal copy) come in as\n // plain text and stay un-linked. paste_preprocess runs on the\n // HTML the paste plugin produced.\n //\n // CRITICAL: skip auto-link when the content already contains\n // anchors. Naive regex over the whole content would wrap\n // `<a href=\"X\">X</a>` in ANOTHER anchor (nested anchors are\n // invalid HTML and browsers split them, producing visible\n // junk). For HTML pastes that already have linked URLs (the\n // common case from a browser address bar or another mail\n // client), TinyMCE preserves them \u2014 no auto-link pass needed.\n // For plain-text pastes (no anchors present), wrap any bare\n // http(s)://\u2026 runs. Trailing sentence punctuation is excluded\n // from the URL.\n paste_preprocess: (_plugin, args) => {\n if (/<a[\\s>]/i.test(args.content))\n return;\n args.content = args.content.replace(/(^|[\\s(\\[])((?:https?|ftp):\\/\\/[^\\s<>\"']+[^\\s<>\"'.,;:!?)\\]])/gi, (_m, lead, url) => `${lead}<a href=\"${url}\">${url}</a>`);\n },\n // Body font + quoted-reply styling. Without explicit rules for\n // <blockquote> and div.reply the editor iframe renders the\n // quoted block with no visual distinction from the user's own\n // text \u2014 pasted reply quotes vanish into a wall of unformatted\n // paragraphs (Bob 2026-05-12: \"the body losing all formatting\").\n // The styles below match Thunderbird/Outlook conventions: a\n // muted left-bar + indent on blockquotes, slight color shift on\n // the \"On \u2026 wrote:\" attribution, untouched typography for\n // everything else so genuine HTML formatting (bold / italic /\n // tables / inline color) still comes through verbatim.\n content_style: [\n \"body { font-family: system-ui, sans-serif; font-size: 14px; }\",\n \"blockquote { border-left: 3px solid #c0c8d0; margin: 0 0 0 4px; padding: 2px 0 2px 10px; color: #555; }\",\n \"div.reply { margin-top: 0.5em; }\",\n \"div.reply > p:first-child { color: #666; font-size: 0.95em; margin: 0 0 4px 0; }\",\n \"pre, code { font-family: ui-monospace, Consolas, Menlo, monospace; }\",\n ].join(\" \"),\n init_instance_callback: (ed) => resolve(ed),\n setup: (ed) => {\n if (opts.initialHtml)\n ed.on(\"init\", () => ed.setContent(opts.initialHtml));\n // Zoom \u2014 content font-size scales between ZOOM_MIN and ZOOM_MAX.\n // Reachable via View \u2192 Zoom in/out/reset, Ctrl+wheel inside the\n // editor iframe, and Ctrl+= / Ctrl+- / Ctrl+0 shortcuts.\n const ZOOM_DEFAULT = 14;\n const ZOOM_STEP = 2;\n const ZOOM_MIN = 8;\n const ZOOM_MAX = 32;\n let zoomPx = ZOOM_DEFAULT;\n const applyZoom = () => {\n try {\n const body = ed.getBody();\n if (body)\n body.style.fontSize = `${zoomPx}px`;\n }\n catch { /* */ }\n };\n const bumpZoom = (delta) => {\n zoomPx = Math.max(ZOOM_MIN, Math.min(ZOOM_MAX, zoomPx + delta));\n applyZoom();\n };\n ed.ui.registry.addMenuItem(\"zoomIn\", {\n text: \"Zoom in\", shortcut: \"Ctrl+=\",\n onAction: () => bumpZoom(ZOOM_STEP),\n });\n ed.ui.registry.addMenuItem(\"zoomOut\", {\n text: \"Zoom out\", shortcut: \"Ctrl+-\",\n onAction: () => bumpZoom(-ZOOM_STEP),\n });\n ed.ui.registry.addMenuItem(\"zoomReset\", {\n text: \"Reset zoom\", shortcut: \"Ctrl+0\",\n onAction: () => { zoomPx = ZOOM_DEFAULT; applyZoom(); },\n });\n // Keyboard shortcuts go on the iframe doc directly. TinyMCE's\n // `addShortcut` is too late \u2014 WebView2 intercepts Ctrl+= and\n // Ctrl+- for its built-in page zoom, so we have to call\n // preventDefault in the capture phase to win the race.\n // Bound below in the \"init\" handler once the iframe doc exists.\n // Typing right after a link must not extend the link. When a\n // printable character is about to be inserted with the caret\n // collapsed at the very end of an <a>, hop the caret to just\n // after the <a> first \u2014 otherwise contenteditable keeps\n // appending into the link and the whole sentence turns into\n // link text (Bob 2026-05-18: \"I typed a URL but it then\n // included everything else I typed in the URL text\"). Covers\n // autolink, pasted URLs, and hand-made links alike.\n ed.on(\"keypress\", (e) => {\n if (e.ctrlKey || e.altKey || e.metaKey)\n return;\n const sel = ed.selection;\n const rng = sel.getRng();\n if (!rng || !rng.collapsed)\n return;\n const a = ed.dom.getParent(sel.getNode(), \"a\");\n if (!a)\n return;\n // Range from the caret to just after the <a>: no text in\n // it \u21D2 the caret sits at the link's trailing edge.\n let tail;\n try {\n tail = rng.cloneRange();\n tail.setEndAfter(a);\n }\n catch {\n return;\n }\n if (tail.toString().length > 0)\n return;\n const after = ed.getDoc().createRange();\n after.setStartAfter(a);\n after.collapse(true);\n sel.setRng(after);\n });\n ed.on(\"init\", () => {\n // Engage WebView2's native spellcheck. TinyMCE's own\n // `browser_spellcheck: true` is a no-op \u2014 its code path\n // only DISABLES spellcheck on false; on true it leaves\n // the iframe body without an explicit attribute, and\n // WebView2 doesn't engage red underlines on a fresh\n // contenteditable iframe without `spellcheck=\"true\"` set.\n try {\n const body = ed.getBody();\n if (body) {\n body.setAttribute(\"spellcheck\", \"true\");\n body.setAttribute(\"lang\", \"en\");\n }\n const doc = ed.getDoc();\n if (doc?.documentElement)\n doc.documentElement.setAttribute(\"lang\", \"en\");\n }\n catch { /* */ }\n // Ctrl+wheel zoom inside the editor iframe.\n try {\n const doc = ed.getDoc();\n doc.addEventListener(\"wheel\", (e) => {\n if (!e.ctrlKey)\n return;\n e.preventDefault();\n bumpZoom(e.deltaY < 0 ? ZOOM_STEP : -ZOOM_STEP);\n }, { passive: false });\n // Capture-phase keydown so we beat WebView2's built-in\n // page-zoom binding. `e.key` is \"=\" (or \"+\" with shift),\n // \"-\", and \"0\". MetaKey covers Mac Cmd; ctrlKey covers\n // Windows/Linux Ctrl. Numpad +/- arrive as \"+\" / \"-\"\n // already so a single check covers both.\n doc.addEventListener(\"keydown\", (e) => {\n if (!(e.ctrlKey || e.metaKey))\n return;\n if (e.key === \"=\" || e.key === \"+\") {\n e.preventDefault();\n e.stopPropagation();\n bumpZoom(ZOOM_STEP);\n }\n else if (e.key === \"-\") {\n e.preventDefault();\n e.stopPropagation();\n bumpZoom(-ZOOM_STEP);\n }\n else if (e.key === \"0\") {\n e.preventDefault();\n e.stopPropagation();\n zoomPx = ZOOM_DEFAULT;\n applyZoom();\n }\n }, true);\n }\n catch { /* */ }\n });\n },\n });\n });\n return {\n setHtml(html) { editor.setContent(html); },\n getHtml() { return editor.getContent(); },\n getText() { return editor.getContent({ format: \"text\" }); },\n focus() { editor.focus(); },\n setCursor(pos) {\n // TinyMCE works in ranges, not character indices. Map the\n // common rmfmail usages: pos === 0 \u2192 cursor at TOP of body\n // (reply / forward \u2014 user types above the quoted block);\n // anything else \u2192 cursor at END (legacy \"put cursor at end\"\n // semantics).\n const place = () => {\n const body = editor.getBody();\n if (pos === 0) {\n // Put the caret INSIDE the first block element. Collapsing\n // to raw body-start lands it outside any block (before /\n // between bare nodes) where contentEditable insertion is\n // unpredictable \u2014 Bob 2026-05-21: \"typing goes in the\n // wrong place until you try again\". rmfmail's reply body\n // now leads with a real <p>; drop the caret into it.\n const first = body.firstChild;\n if (first && first.nodeType === 1 /* element */) {\n editor.selection.setCursorLocation(first, 0);\n }\n else {\n editor.selection.select(body, true);\n editor.selection.collapse(true);\n }\n editor.focus();\n // Viewport to the top so the user looks at the empty\n // reply line, not scrolled down into the quote.\n editor.getWin()?.scrollTo(0, 0);\n }\n else {\n editor.selection.select(body, true);\n editor.selection.collapse(false);\n editor.focus();\n editor.selection.scrollIntoView();\n }\n };\n try {\n place();\n // Re-apply on the next frame. Cross-iframe focus (compose is\n // an iframe; TinyMCE's edit area is a nested iframe) lets the\n // first selection set get clobbered by a late layout / focus\n // event \u2014 the \"have to click in and try again\" symptom. A\n // second pass after the frame settles makes it stick.\n editor.getWin()?.requestAnimationFrame?.(() => {\n try {\n place();\n }\n catch { /* */ }\n });\n }\n catch { /* */ }\n },\n get root() { return editor.getContainer(); },\n on(event, handler) { editor.on(event, handler); },\n off(event, handler) { editor.off(event, handler); },\n nativeEditor: editor,\n };\n}\n//# sourceMappingURL=adapter.js.map", "/*!\n * Determine if an object is a Buffer\n *\n * @author Feross Aboukhadijeh <https://feross.org>\n * @license MIT\n */\n\nmodule.exports = function isBuffer (obj) {\n return obj != null && obj.constructor != null &&\n typeof obj.constructor.isBuffer === 'function' && obj.constructor.isBuffer(obj)\n}\n", "'use strict'\n\nmodule.exports = ruleCodes\n\nvar NO_CODES = []\n\n// Parse rule codes.\nfunction ruleCodes(flags, value) {\n var index = 0\n var result\n\n if (!value) return NO_CODES\n\n if (flags.FLAG === 'long') {\n // Creating an array of the right length immediately\n // avoiding resizes and using memory more efficiently\n result = new Array(Math.ceil(value.length / 2))\n\n while (index < value.length) {\n result[index / 2] = value.slice(index, index + 2)\n index += 2\n }\n\n return result\n }\n\n return value.split(flags.FLAG === 'num' ? ',' : '')\n}\n", "'use strict'\n\nvar parse = require('./rule-codes.js')\n\nmodule.exports = affix\n\nvar push = [].push\n\n// Relative frequencies of letters in the English language.\nvar alphabet = 'etaoinshrdlcumwfgypbvkjxqz'.split('')\n\n// Expressions.\nvar whiteSpaceExpression = /\\s+/\n\n// Defaults.\nvar defaultKeyboardLayout = [\n 'qwertzuop',\n 'yxcvbnm',\n 'qaw',\n 'say',\n 'wse',\n 'dsx',\n 'sy',\n 'edr',\n 'fdc',\n 'dx',\n 'rft',\n 'gfv',\n 'fc',\n 'tgz',\n 'hgb',\n 'gv',\n 'zhu',\n 'jhn',\n 'hb',\n 'uji',\n 'kjm',\n 'jn',\n 'iko',\n 'lkm'\n]\n\n// Parse an affix file.\n// eslint-disable-next-line complexity\nfunction affix(doc) {\n var rules = Object.create(null)\n var compoundRuleCodes = Object.create(null)\n var flags = Object.create(null)\n var replacementTable = []\n var conversion = {in: [], out: []}\n var compoundRules = []\n var aff = doc.toString('utf8')\n var lines = []\n var last = 0\n var index = aff.indexOf('\\n')\n var parts\n var line\n var ruleType\n var count\n var remove\n var add\n var source\n var entry\n var position\n var rule\n var value\n var offset\n var character\n\n flags.KEY = []\n\n // Process the affix buffer into a list of applicable lines.\n while (index > -1) {\n pushLine(aff.slice(last, index))\n last = index + 1\n index = aff.indexOf('\\n', last)\n }\n\n pushLine(aff.slice(last))\n\n // Process each line.\n index = -1\n\n while (++index < lines.length) {\n line = lines[index]\n parts = line.split(whiteSpaceExpression)\n ruleType = parts[0]\n\n if (ruleType === 'REP') {\n count = index + parseInt(parts[1], 10)\n\n while (++index <= count) {\n parts = lines[index].split(whiteSpaceExpression)\n replacementTable.push([parts[1], parts[2]])\n }\n\n index--\n } else if (ruleType === 'ICONV' || ruleType === 'OCONV') {\n count = index + parseInt(parts[1], 10)\n entry = conversion[ruleType === 'ICONV' ? 'in' : 'out']\n\n while (++index <= count) {\n parts = lines[index].split(whiteSpaceExpression)\n entry.push([new RegExp(parts[1], 'g'), parts[2]])\n }\n\n index--\n } else if (ruleType === 'COMPOUNDRULE') {\n count = index + parseInt(parts[1], 10)\n\n while (++index <= count) {\n rule = lines[index].split(whiteSpaceExpression)[1]\n position = -1\n\n compoundRules.push(rule)\n\n while (++position < rule.length) {\n compoundRuleCodes[rule.charAt(position)] = []\n }\n }\n\n index--\n } else if (ruleType === 'PFX' || ruleType === 'SFX') {\n count = index + parseInt(parts[3], 10)\n\n rule = {\n type: ruleType,\n combineable: parts[2] === 'Y',\n entries: []\n }\n\n rules[parts[1]] = rule\n\n while (++index <= count) {\n parts = lines[index].split(whiteSpaceExpression)\n remove = parts[2]\n add = parts[3].split('/')\n source = parts[4]\n\n entry = {\n add: '',\n remove: '',\n match: '',\n continuation: parse(flags, add[1])\n }\n\n if (add && add[0] !== '0') {\n entry.add = add[0]\n }\n\n try {\n if (remove !== '0') {\n entry.remove = ruleType === 'SFX' ? end(remove) : remove\n }\n\n if (source && source !== '.') {\n entry.match = ruleType === 'SFX' ? end(source) : start(source)\n }\n } catch (_) {\n // Ignore invalid regex patterns.\n entry = null\n }\n\n if (entry) {\n rule.entries.push(entry)\n }\n }\n\n index--\n } else if (ruleType === 'TRY') {\n source = parts[1]\n offset = -1\n value = []\n\n while (++offset < source.length) {\n character = source.charAt(offset)\n\n if (character.toLowerCase() === character) {\n value.push(character)\n }\n }\n\n // Some dictionaries may forget a character.\n // Notably `en` forgets `j`, `x`, and `y`.\n offset = -1\n\n while (++offset < alphabet.length) {\n if (source.indexOf(alphabet[offset]) < 0) {\n value.push(alphabet[offset])\n }\n }\n\n flags[ruleType] = value\n } else if (ruleType === 'KEY') {\n push.apply(flags[ruleType], parts[1].split('|'))\n } else if (ruleType === 'COMPOUNDMIN') {\n flags[ruleType] = Number(parts[1])\n } else if (ruleType === 'ONLYINCOMPOUND') {\n // If we add this ONLYINCOMPOUND flag to `compoundRuleCodes`, then\n // `parseDic` will do the work of saving the list of words that are\n // compound-only.\n flags[ruleType] = parts[1]\n compoundRuleCodes[parts[1]] = []\n } else if (\n ruleType === 'FLAG' ||\n ruleType === 'KEEPCASE' ||\n ruleType === 'NOSUGGEST' ||\n ruleType === 'WORDCHARS'\n ) {\n flags[ruleType] = parts[1]\n } else {\n // Default handling: set them for now.\n flags[ruleType] = parts[1]\n }\n }\n\n // Default for `COMPOUNDMIN` is `3`.\n // See `man 4 hunspell`.\n if (isNaN(flags.COMPOUNDMIN)) {\n flags.COMPOUNDMIN = 3\n }\n\n if (!flags.KEY.length) {\n flags.KEY = defaultKeyboardLayout\n }\n\n /* istanbul ignore if - Dictionaries seem to always have this. */\n if (!flags.TRY) {\n flags.TRY = alphabet.concat()\n }\n\n if (!flags.KEEPCASE) {\n flags.KEEPCASE = false\n }\n\n return {\n compoundRuleCodes: compoundRuleCodes,\n replacementTable: replacementTable,\n conversion: conversion,\n compoundRules: compoundRules,\n rules: rules,\n flags: flags\n }\n\n function pushLine(line) {\n line = line.trim()\n\n // Hash can be a valid flag, so we only discard line that starts with it.\n if (line && line.charCodeAt(0) !== 35 /* `#` */) {\n lines.push(line)\n }\n }\n}\n\n// Wrap the `source` of an expression-like string so that it matches only at\n// the end of a value.\nfunction end(source) {\n return new RegExp(source + '$')\n}\n\n// Wrap the `source` of an expression-like string so that it matches only at\n// the start of a value.\nfunction start(source) {\n return new RegExp('^' + source)\n}\n", "'use strict'\n\nmodule.exports = normalize\n\n// Normalize `value` with patterns.\nfunction normalize(value, patterns) {\n var index = -1\n\n while (++index < patterns.length) {\n value = value.replace(patterns[index][0], patterns[index][1])\n }\n\n return value\n}\n", "'use strict'\n\nmodule.exports = flag\n\n// Check whether a word has a flag.\nfunction flag(values, value, flags) {\n return flags && value in values && flags.indexOf(values[value]) > -1\n}\n", "'use strict'\n\nvar flag = require('./flag.js')\n\nmodule.exports = exact\n\n// Check spelling of `value`, exactly.\nfunction exact(context, value) {\n var index = -1\n\n if (context.data[value]) {\n return !flag(context.flags, 'ONLYINCOMPOUND', context.data[value])\n }\n\n // Check if this might be a compound word.\n if (value.length >= context.flags.COMPOUNDMIN) {\n while (++index < context.compoundRules.length) {\n if (context.compoundRules[index].test(value)) {\n return true\n }\n }\n }\n\n return false\n}\n", "'use strict'\n\nvar normalize = require('./normalize.js')\nvar exact = require('./exact.js')\nvar flag = require('./flag.js')\n\nmodule.exports = form\n\n// Find a known form of `value`.\nfunction form(context, value, all) {\n var normal = value.trim()\n var alternative\n\n if (!normal) {\n return null\n }\n\n normal = normalize(normal, context.conversion.in)\n\n if (exact(context, normal)) {\n if (!all && flag(context.flags, 'FORBIDDENWORD', context.data[normal])) {\n return null\n }\n\n return normal\n }\n\n // Try sentence case if the value is uppercase.\n if (normal.toUpperCase() === normal) {\n alternative = normal.charAt(0) + normal.slice(1).toLowerCase()\n\n if (ignore(context.flags, context.data[alternative], all)) {\n return null\n }\n\n if (exact(context, alternative)) {\n return alternative\n }\n }\n\n // Try lowercase.\n alternative = normal.toLowerCase()\n\n if (alternative !== normal) {\n if (ignore(context.flags, context.data[alternative], all)) {\n return null\n }\n\n if (exact(context, alternative)) {\n return alternative\n }\n }\n\n return null\n}\n\nfunction ignore(flags, dict, all) {\n return (\n flag(flags, 'KEEPCASE', dict) || all || flag(flags, 'FORBIDDENWORD', dict)\n )\n}\n", "'use strict'\n\nvar form = require('./util/form.js')\n\nmodule.exports = correct\n\n// Check spelling of `value`.\nfunction correct(value) {\n return Boolean(form(this, value))\n}\n", "'use strict'\n\nmodule.exports = casing\n\n// Get the casing of `value`.\nfunction casing(value) {\n var head = exact(value.charAt(0))\n var rest = value.slice(1)\n\n if (!rest) {\n return head\n }\n\n rest = exact(rest)\n\n if (head === rest) {\n return head\n }\n\n if (head === 'u' && rest === 'l') {\n return 's'\n }\n\n return null\n}\n\nfunction exact(value) {\n return value === value.toLowerCase()\n ? 'l'\n : value === value.toUpperCase()\n ? 'u'\n : null\n}\n", "'use strict'\n\nvar casing = require('./util/casing.js')\nvar normalize = require('./util/normalize.js')\nvar flag = require('./util/flag.js')\nvar form = require('./util/form.js')\n\nmodule.exports = suggest\n\nvar push = [].push\n\n// Suggest spelling for `value`.\n// eslint-disable-next-line complexity\nfunction suggest(value) {\n var self = this\n var charAdded = {}\n var suggestions = []\n var weighted = {}\n var memory\n var replacement\n var edits = []\n var values\n var index\n var offset\n var position\n var count\n var otherOffset\n var otherCharacter\n var character\n var group\n var before\n var after\n var upper\n var insensitive\n var firstLevel\n var previous\n var next\n var nextCharacter\n var max\n var distance\n var size\n var normalized\n var suggestion\n var currentCase\n\n value = normalize(value.trim(), self.conversion.in)\n\n if (!value || self.correct(value)) {\n return []\n }\n\n currentCase = casing(value)\n\n // Check the replacement table.\n index = -1\n\n while (++index < self.replacementTable.length) {\n replacement = self.replacementTable[index]\n offset = value.indexOf(replacement[0])\n\n while (offset > -1) {\n edits.push(value.replace(replacement[0], replacement[1]))\n offset = value.indexOf(replacement[0], offset + 1)\n }\n }\n\n // Check the keyboard.\n index = -1\n\n while (++index < value.length) {\n character = value.charAt(index)\n before = value.slice(0, index)\n after = value.slice(index + 1)\n insensitive = character.toLowerCase()\n upper = insensitive !== character\n charAdded = {}\n\n offset = -1\n\n while (++offset < self.flags.KEY.length) {\n group = self.flags.KEY[offset]\n position = group.indexOf(insensitive)\n\n if (position < 0) {\n continue\n }\n\n otherOffset = -1\n\n while (++otherOffset < group.length) {\n if (otherOffset !== position) {\n otherCharacter = group.charAt(otherOffset)\n\n if (charAdded[otherCharacter]) {\n continue\n }\n\n charAdded[otherCharacter] = true\n\n if (upper) {\n otherCharacter = otherCharacter.toUpperCase()\n }\n\n edits.push(before + otherCharacter + after)\n }\n }\n }\n }\n\n // Check cases where one of a double character was forgotten, or one too many\n // were added, up to three \u201Cdistances\u201D. This increases the success-rate by 2%\n // and speeds the process up by 13%.\n index = -1\n nextCharacter = value.charAt(0)\n values = ['']\n max = 1\n distance = 0\n\n while (++index < value.length) {\n character = nextCharacter\n nextCharacter = value.charAt(index + 1)\n before = value.slice(0, index)\n\n replacement = character === nextCharacter ? '' : character + character\n offset = -1\n count = values.length\n\n while (++offset < count) {\n if (offset <= max) {\n values.push(values[offset] + replacement)\n }\n\n values[offset] += character\n }\n\n if (++distance < 3) {\n max = values.length\n }\n }\n\n push.apply(edits, values)\n\n // Ensure the capitalised and uppercase values are included.\n values = [value]\n replacement = value.toLowerCase()\n\n if (value === replacement || currentCase === null) {\n values.push(value.charAt(0).toUpperCase() + replacement.slice(1))\n }\n\n replacement = value.toUpperCase()\n\n if (value !== replacement) {\n values.push(replacement)\n }\n\n // Construct a memory object for `generate`.\n memory = {\n state: {},\n weighted: weighted,\n suggestions: suggestions\n }\n\n firstLevel = generate(self, memory, values, edits)\n\n // While there are no suggestions based on generated values with an\n // edit-distance of `1`, check the generated values, `SIZE` at a time.\n // Basically, we\u2019re generating values with an edit-distance of `2`, but were\n // doing it in small batches because it\u2019s such an expensive operation.\n previous = 0\n max = Math.min(firstLevel.length, Math.pow(Math.max(15 - value.length, 3), 3))\n size = Math.max(Math.pow(10 - value.length, 3), 1)\n\n while (!suggestions.length && previous < max) {\n next = previous + size\n generate(self, memory, firstLevel.slice(previous, next))\n previous = next\n }\n\n // Sort the suggestions based on their weight.\n suggestions.sort(sort)\n\n // Normalize the output.\n values = []\n normalized = []\n index = -1\n\n while (++index < suggestions.length) {\n suggestion = normalize(suggestions[index], self.conversion.out)\n replacement = suggestion.toLowerCase()\n\n if (normalized.indexOf(replacement) < 0) {\n values.push(suggestion)\n normalized.push(replacement)\n }\n }\n\n // BOOM! All done!\n return values\n\n function sort(a, b) {\n return sortWeight(a, b) || sortCasing(a, b) || sortAlpha(a, b)\n }\n\n function sortWeight(a, b) {\n return weighted[a] === weighted[b] ? 0 : weighted[a] > weighted[b] ? -1 : 1\n }\n\n function sortCasing(a, b) {\n var leftCasing = casing(a)\n var rightCasing = casing(b)\n\n return leftCasing === rightCasing\n ? 0\n : leftCasing === currentCase\n ? -1\n : rightCasing === currentCase\n ? 1\n : undefined\n }\n\n function sortAlpha(a, b) {\n return a.localeCompare(b)\n }\n}\n\n// Get a list of values close in edit distance to `words`.\nfunction generate(context, memory, words, edits) {\n var characters = context.flags.TRY\n var data = context.data\n var flags = context.flags\n var result = []\n var index = -1\n var word\n var before\n var character\n var nextCharacter\n var nextAfter\n var nextNextAfter\n var nextUpper\n var currentCase\n var position\n var after\n var upper\n var inject\n var offset\n\n // Check the pre-generated edits.\n if (edits) {\n while (++index < edits.length) {\n check(edits[index], true)\n }\n }\n\n // Iterate over given word.\n index = -1\n\n while (++index < words.length) {\n word = words[index]\n before = ''\n character = ''\n nextCharacter = word.charAt(0)\n nextAfter = word\n nextNextAfter = word.slice(1)\n nextUpper = nextCharacter.toLowerCase() !== nextCharacter\n currentCase = casing(word)\n position = -1\n\n // Iterate over every character (including the end).\n while (++position <= word.length) {\n before += character\n after = nextAfter\n nextAfter = nextNextAfter\n nextNextAfter = nextAfter.slice(1)\n character = nextCharacter\n nextCharacter = word.charAt(position + 1)\n upper = nextUpper\n\n if (nextCharacter) {\n nextUpper = nextCharacter.toLowerCase() !== nextCharacter\n }\n\n if (nextAfter && upper !== nextUpper) {\n // Remove.\n check(before + switchCase(nextAfter))\n\n // Switch.\n check(\n before +\n switchCase(nextCharacter) +\n switchCase(character) +\n nextNextAfter\n )\n }\n\n // Remove.\n check(before + nextAfter)\n\n // Switch.\n if (nextAfter) {\n check(before + nextCharacter + character + nextNextAfter)\n }\n\n // Iterate over all possible letters.\n offset = -1\n\n while (++offset < characters.length) {\n inject = characters[offset]\n\n // Try uppercase if the original character was uppercased.\n if (upper && inject !== inject.toUpperCase()) {\n if (currentCase !== 's') {\n check(before + inject + after)\n check(before + inject + nextAfter)\n }\n\n inject = inject.toUpperCase()\n\n check(before + inject + after)\n check(before + inject + nextAfter)\n } else {\n // Add and replace.\n check(before + inject + after)\n check(before + inject + nextAfter)\n }\n }\n }\n }\n\n // Return the list of generated words.\n return result\n\n // Check and handle a generated value.\n function check(value, double) {\n var state = memory.state[value]\n var corrected\n\n if (state !== Boolean(state)) {\n result.push(value)\n\n corrected = form(context, value)\n state = corrected && !flag(flags, 'NOSUGGEST', data[corrected])\n\n memory.state[value] = state\n\n if (state) {\n memory.weighted[value] = double ? 10 : 0\n memory.suggestions.push(value)\n }\n }\n\n if (state) {\n memory.weighted[value]++\n }\n }\n\n function switchCase(fragment) {\n var first = fragment.charAt(0)\n\n return (\n (first.toLowerCase() === first\n ? first.toUpperCase()\n : first.toLowerCase()) + fragment.slice(1)\n )\n }\n}\n", "'use strict'\n\nvar form = require('./util/form.js')\nvar flag = require('./util/flag.js')\n\nmodule.exports = spell\n\n// Check spelling of `word`.\nfunction spell(word) {\n var self = this\n var value = form(self, word, true)\n\n // Hunspell also provides `root` (root word of the input word), and `compound`\n // (whether `word` was compound).\n return {\n correct: self.correct(word),\n forbidden: Boolean(\n value && flag(self.flags, 'FORBIDDENWORD', self.data[value])\n ),\n warn: Boolean(value && flag(self.flags, 'WARN', self.data[value]))\n }\n}\n", "'use strict'\n\nmodule.exports = apply\n\n// Apply a rule.\nfunction apply(value, rule, rules, words) {\n var index = -1\n var entry\n var next\n var continuationRule\n var continuation\n var position\n\n while (++index < rule.entries.length) {\n entry = rule.entries[index]\n continuation = entry.continuation\n position = -1\n\n if (!entry.match || entry.match.test(value)) {\n next = entry.remove ? value.replace(entry.remove, '') : value\n next = rule.type === 'SFX' ? next + entry.add : entry.add + next\n words.push(next)\n\n if (continuation && continuation.length) {\n while (++position < continuation.length) {\n continuationRule = rules[continuation[position]]\n\n if (continuationRule) {\n apply(next, continuationRule, rules, words)\n }\n }\n }\n }\n }\n\n return words\n}\n", "'use strict'\n\nvar apply = require('./apply.js')\n\nmodule.exports = add\n\nvar push = [].push\n\nvar NO_RULES = []\n\n// Add `rules` for `word` to the table.\nfunction addRules(dict, word, rules) {\n var curr = dict[word]\n\n // Some dictionaries will list the same word multiple times with different\n // rule sets.\n if (word in dict) {\n if (curr === NO_RULES) {\n dict[word] = rules.concat()\n } else {\n push.apply(curr, rules)\n }\n } else {\n dict[word] = rules.concat()\n }\n}\n\nfunction add(dict, word, codes, options) {\n var position = -1\n var rule\n var offset\n var subposition\n var suboffset\n var combined\n var newWords\n var otherNewWords\n\n // Compound words.\n if (\n !('NEEDAFFIX' in options.flags) ||\n codes.indexOf(options.flags.NEEDAFFIX) < 0\n ) {\n addRules(dict, word, codes)\n }\n\n while (++position < codes.length) {\n rule = options.rules[codes[position]]\n\n if (codes[position] in options.compoundRuleCodes) {\n options.compoundRuleCodes[codes[position]].push(word)\n }\n\n if (rule) {\n newWords = apply(word, rule, options.rules, [])\n offset = -1\n\n while (++offset < newWords.length) {\n if (!(newWords[offset] in dict)) {\n dict[newWords[offset]] = NO_RULES\n }\n\n if (rule.combineable) {\n subposition = position\n\n while (++subposition < codes.length) {\n combined = options.rules[codes[subposition]]\n\n if (\n combined &&\n combined.combineable &&\n rule.type !== combined.type\n ) {\n otherNewWords = apply(\n newWords[offset],\n combined,\n options.rules,\n []\n )\n suboffset = -1\n\n while (++suboffset < otherNewWords.length) {\n if (!(otherNewWords[suboffset] in dict)) {\n dict[otherNewWords[suboffset]] = NO_RULES\n }\n }\n }\n }\n }\n }\n }\n }\n}\n", "'use strict'\n\nvar push = require('./util/add.js')\n\nmodule.exports = add\n\nvar NO_CODES = []\n\n// Add `value` to the checker.\nfunction add(value, model) {\n var self = this\n\n push(self.data, value, self.data[model] || NO_CODES, self)\n\n return self\n}\n", "'use strict'\n\nmodule.exports = remove\n\n// Remove `value` from the checker.\nfunction remove(value) {\n var self = this\n\n delete self.data[value]\n\n return self\n}\n", "'use strict'\n\nmodule.exports = wordCharacters\n\n// Get the word characters defined in affix.\nfunction wordCharacters() {\n return this.flags.WORDCHARS || null\n}\n", "'use strict'\n\nvar parseCodes = require('./rule-codes.js')\nvar add = require('./add.js')\n\nmodule.exports = parse\n\n// Expressions.\nvar whiteSpaceExpression = /\\s/g\n\n// Parse a dictionary.\nfunction parse(buf, options, dict) {\n // Parse as lines (ignoring the first line).\n var value = buf.toString('utf8')\n var last = value.indexOf('\\n') + 1\n var index = value.indexOf('\\n', last)\n\n while (index > -1) {\n // Some dictionaries use tabs as comments.\n if (value.charCodeAt(last) !== 9 /* `\\t` */) {\n parseLine(value.slice(last, index), options, dict)\n }\n\n last = index + 1\n index = value.indexOf('\\n', last)\n }\n\n parseLine(value.slice(last), options, dict)\n}\n\n// Parse a line in dictionary.\nfunction parseLine(line, options, dict) {\n var slashOffset = line.indexOf('/')\n var hashOffset = line.indexOf('#')\n var codes = ''\n var word\n var result\n\n // Find offsets.\n while (\n slashOffset > -1 &&\n line.charCodeAt(slashOffset - 1) === 92 /* `\\` */\n ) {\n line = line.slice(0, slashOffset - 1) + line.slice(slashOffset)\n slashOffset = line.indexOf('/', slashOffset)\n }\n\n // Handle hash and slash offsets.\n // Note that hash can be a valid flag, so we should not just discard\n // everything after it.\n if (hashOffset > -1) {\n if (slashOffset > -1 && slashOffset < hashOffset) {\n word = line.slice(0, slashOffset)\n whiteSpaceExpression.lastIndex = slashOffset + 1\n result = whiteSpaceExpression.exec(line)\n codes = line.slice(slashOffset + 1, result ? result.index : undefined)\n } else {\n word = line.slice(0, hashOffset)\n }\n } else if (slashOffset > -1) {\n word = line.slice(0, slashOffset)\n codes = line.slice(slashOffset + 1)\n } else {\n word = line\n }\n\n word = word.trim()\n\n if (word) {\n add(dict, word, parseCodes(options.flags, codes.trim()), options)\n }\n}\n", "'use strict'\n\nvar parse = require('./util/dictionary.js')\n\nmodule.exports = add\n\n// Add a dictionary file.\nfunction add(buf) {\n var self = this\n var index = -1\n var rule\n var source\n var character\n var offset\n\n parse(buf, self, self.data)\n\n // Regenerate compound expressions.\n while (++index < self.compoundRules.length) {\n rule = self.compoundRules[index]\n source = ''\n offset = -1\n\n while (++offset < rule.length) {\n character = rule.charAt(offset)\n source += self.compoundRuleCodes[character].length\n ? '(?:' + self.compoundRuleCodes[character].join('|') + ')'\n : character\n }\n\n self.compoundRules[index] = new RegExp(source, 'i')\n }\n\n return self\n}\n", "'use strict'\n\nmodule.exports = add\n\n// Add a dictionary.\nfunction add(buf) {\n var self = this\n var lines = buf.toString('utf8').split('\\n')\n var index = -1\n var line\n var forbidden\n var word\n var flag\n\n // Ensure there\u2019s a key for `FORBIDDENWORD`: `false` cannot be set through an\n // affix file so its safe to use as a magic constant.\n if (self.flags.FORBIDDENWORD === undefined) self.flags.FORBIDDENWORD = false\n flag = self.flags.FORBIDDENWORD\n\n while (++index < lines.length) {\n line = lines[index].trim()\n\n if (!line) {\n continue\n }\n\n line = line.split('/')\n word = line[0]\n forbidden = word.charAt(0) === '*'\n\n if (forbidden) {\n word = word.slice(1)\n }\n\n self.add(word, line[1])\n\n if (forbidden) {\n self.data[word].push(flag)\n }\n }\n\n return self\n}\n", "'use strict'\n\nvar buffer = require('is-buffer')\nvar affix = require('./util/affix.js')\n\nmodule.exports = NSpell\n\nvar proto = NSpell.prototype\n\nproto.correct = require('./correct.js')\nproto.suggest = require('./suggest.js')\nproto.spell = require('./spell.js')\nproto.add = require('./add.js')\nproto.remove = require('./remove.js')\nproto.wordCharacters = require('./word-characters.js')\nproto.dictionary = require('./dictionary.js')\nproto.personal = require('./personal.js')\n\n// Construct a new spelling context.\nfunction NSpell(aff, dic) {\n var index = -1\n var dictionaries\n\n if (!(this instanceof NSpell)) {\n return new NSpell(aff, dic)\n }\n\n if (typeof aff === 'string' || buffer(aff)) {\n if (typeof dic === 'string' || buffer(dic)) {\n dictionaries = [{dic: dic}]\n }\n } else if (aff) {\n if ('length' in aff) {\n dictionaries = aff\n aff = aff[0] && aff[0].aff\n } else {\n if (aff.dic) {\n dictionaries = [aff]\n }\n\n aff = aff.aff\n }\n }\n\n if (!aff) {\n throw new Error('Missing `aff` in dictionary')\n }\n\n aff = affix(aff)\n\n this.data = Object.create(null)\n this.compoundRuleCodes = aff.compoundRuleCodes\n this.replacementTable = aff.replacementTable\n this.conversion = aff.conversion\n this.compoundRules = aff.compoundRules\n this.rules = aff.rules\n this.flags = aff.flags\n\n if (dictionaries) {\n while (++index < dictionaries.length) {\n if (dictionaries[index].dic) {\n this.dictionary(dictionaries[index].dic)\n }\n }\n }\n}\n", "/**\n * Live spell-check for the TinyMCE compose editor.\n *\n * Why this exists: WebView2's built-in spell checker isn't reliably\n * enabled on our msger-hosted compose iframe (see msger main.rs around\n * AreDefaultContextMenusEnabled \u2014 the comment claims it leaves\n * defaults for spell-suggest menus, but `IsSpellcheckEnabled` is never\n * explicitly set and the default varies by WebView2 runtime). Rather\n * than depend on the host, we ship a JS spellchecker (`nspell`) + the\n * standard `dictionary-en` Hunspell files.\n *\n * Two layers:\n * 1. Live decoration \u2014 words that nspell flags get wrapped in a\n * `<span data-mailx-spellerror=\"1\">` while editing. CSS gives them\n * a wavy red underline (text-decoration: underline wavy #d33). The\n * markers are stripped at serialization time so they never reach\n * the sent message or the saved draft.\n * 2. Right-click \u2014 clicking on a marker shows our own popup with\n * suggestions plus \"Add to dictionary\" / \"Ignore (this session)\".\n *\n * Decoration runs:\n * - Once after the editor inits + dict loads (initial scan of pre-\n * populated quote / signature, though we skip blockquote content).\n * - Debounced after every input (~500 ms idle).\n * - After setContent (paste, reply-quote insertion, \u2026).\n *\n * The decoration walker:\n * - Skips `<blockquote>` (the quoted reply \u2014 not the user's words),\n * `<code>`, `<pre>`, `<a>` (URLs aren't natural-language words),\n * and content inside any existing marker (so re-scans don't double-\n * wrap).\n * - Uses TinyMCE's `undoManager.ignore` + selection bookmarks so the\n * decoration mutations don't pollute undo and don't move the caret.\n *\n * Dictionary persistence:\n * - User additions: `localStorage[\"mailx-user-dict\"]` (a JSON array).\n * - \"Ignore this session\": in-memory only via `nspell.add()`.\n */\n// @ts-expect-error \u2014 nspell ships no type defs. Treated as `any`; the\n// surface we use (`new NSpell({aff, dic})`, `.correct`, `.suggest`,\n// `.add`) is small and stable.\nimport NSpell from \"nspell\";\nimport { getUserDict, addUserDictWord, addUserDictWords } from \"../lib/api-client.js\";\ntype NSpell = any;\n\n// Cloud-mirrored dictionary. `userdict.csv` on GDrive (a plain one-word-\n// per-line list) is the source of truth; the localStorage entry is a\n// write-through cache so the popup of suggestions can resolve synchronously\n// while the service round-trips the add.\nconst USER_DICT_KEY = \"mailx-user-dict\";\nconst MARKER_ATTR = \"data-mailx-spellerror\";\n// Long enough that a mid-word pause doesn't fire decoration. Bob 2026-05-12:\n// \"popping up the redlines too quickly and then very slow about removing\n// them and you keep extending them. At least wait till I finish typing a\n// word.\" 500 ms was too eager. 1200 ms feels reactive after pause but\n// stays out of the way while typing.\nconst DECORATE_DEBOUNCE_MS = 1200;\n// Removal-only cleanup runs on a much shorter debounce than the full\n// decorate pass. Removing a corrected word's red underline can't thrash\n// the way *adding* underlines mid-typing can, so it doesn't need the calm\n// 1200 ms \u2014 a corrected word's underline clearing in ~0.3 s instead of\n// 1.2 s is the difference Bob asked about (2026-05-17).\nconst CLEANUP_DEBOUNCE_MS = 300;\nconst MIN_WORD_LEN = 3;\nconst SKIP_TAGS = new Set([\"BLOCKQUOTE\", \"CODE\", \"PRE\", \"A\", \"SCRIPT\", \"STYLE\", \"KBD\", \"SAMP\", \"VAR\"]);\n\nlet spellPromise: Promise<NSpell> | null = null;\nasync function getSpell(): Promise<NSpell> {\n if (spellPromise) return spellPromise;\n spellPromise = (async () => {\n const [affRes, dicRes] = await Promise.all([\n fetch(\"../lib/dict/en.aff\"),\n fetch(\"../lib/dict/en.dic\"),\n ]);\n if (!affRes.ok || !dicRes.ok) {\n throw new Error(`spellcheck: dict fetch failed (aff=${affRes.status} dic=${dicRes.status})`);\n }\n const [aff, dic] = await Promise.all([affRes.text(), dicRes.text()]);\n const sp = new NSpell({ aff, dic });\n // 1) Seed from local cache so the editor never has to wait on\n // network for known-correct words to disappear from the\n // redline pass.\n try {\n const raw = localStorage.getItem(USER_DICT_KEY);\n if (raw) for (const w of JSON.parse(raw) as string[]) sp.add(w);\n } catch { /* corrupt cache \u2014 start clean */ }\n // 2) Pull the cloud copy, union it in, and reconcile. Fire-and-forget\n // \u2014 if it fails the cache still works.\n getUserDict().then(cloud => {\n const cloudArr = Array.isArray(cloud) ? cloud : [];\n for (const w of cloudArr) sp.add(w);\n // Read this machine's local cache.\n let local: string[] = [];\n try {\n const raw = localStorage.getItem(USER_DICT_KEY);\n local = raw ? (JSON.parse(raw) as string[]) : [];\n } catch { local = []; }\n // Reconcile up: words that exist only in localStorage (e.g. added\n // on a build where the cloud round-trip was a silent no-op) get\n // pushed to the server so they land in userdict.csv.\n const cloudSet = new Set(cloudArr);\n const localOnly = local.filter(w => !cloudSet.has(w));\n if (localOnly.length > 0) {\n addUserDictWords(localOnly).catch(e => console.error(\"[spell] reconcile:\", e));\n }\n // Refresh the cache with the union so the next boot starts whole.\n try {\n const merged = [...new Set([...local, ...cloudArr])];\n localStorage.setItem(USER_DICT_KEY, JSON.stringify(merged));\n } catch { /* */ }\n }).catch(() => { /* offline / no cloud \u2014 that's fine */ });\n return sp;\n })();\n return spellPromise;\n}\n\nfunction addToUserDict(word: string, sp: NSpell): void {\n // Local cache: synchronous, so suggestions disappear immediately.\n try {\n const raw = localStorage.getItem(USER_DICT_KEY);\n const arr = raw ? (JSON.parse(raw) as string[]) : [];\n if (!arr.includes(word)) {\n arr.push(word);\n localStorage.setItem(USER_DICT_KEY, JSON.stringify(arr));\n }\n } catch { /* */ }\n sp.add(word);\n // Cloud: fire-and-forget so the right-click \"Add\" doesn't block the\n // editor. The service merges with the existing cloud copy so concurrent\n // adds from a second machine don't lose entries.\n addUserDictWord(word).catch(e => console.error(\"[spell] addUserDictWord:\", e));\n}\n\n// \u2500\u2500 Live decoration \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/** Walk the editor body, wrap newly-misspelled words, unwrap markers\n * that are now correct. Mutations are wrapped in undoManager.ignore so\n * they don't pollute undo history; selection is preserved via a\n * TinyMCE bookmark. */\nfunction decorate(editor: any, sp: NSpell): void {\n const body: HTMLElement | null = editor.getBody?.();\n const doc: Document | null = editor.getDoc?.();\n if (!body || !doc) return;\n\n // Don't fight active typing \u2014 if the caret is INSIDE a current\n // misspelling marker, the user is mid-correction. Let them finish.\n // We'll re-scan after the debounce on their next pause.\n const sel = doc.getSelection();\n if (sel && sel.rangeCount > 0) {\n const focus = sel.focusNode as Node | null;\n let p: Node | null = focus;\n while (p && p !== body) {\n if (p.nodeType === Node.ELEMENT_NODE && (p as Element).hasAttribute?.(MARKER_ATTR)) {\n // Cursor inside a marker \u2014 skip this pass; re-decorate\n // when they next stop typing.\n return;\n }\n p = p.parentNode;\n }\n }\n\n // Caret preservation: TinyMCE's getBookmark(2) + moveToBookmark\n // claimed to be mutation-safe, but in practice it landed the caret\n // at the START of the nearest wrapped span (Bob 2026-05-12: \"you\n // violently yank the cursor and plop it down at the beginning of\n // the twiddle\"). Replace with an absolute-character-offset save\n // before mutations and a walk-to-restore after \u2014 robust against\n // any reshape that preserves the text content (which our wrap/\n // unwrap operations do by construction).\n const savedAbs = caretAbsOffsetFromBody(body);\n // Scroll preservation: unwrap \u2192 normalize \u2192 wrap reshapes the DOM\n // around the caret. Browsers (incl. WebView2) often scroll the new\n // selection into view, which yanks the editor viewport to the top\n // of the document when the restored caret lands above the previous\n // viewport. Save scrollTop on both the editor doc's scrolling element\n // AND the body (TinyMCE sometimes uses one or the other depending\n // on theme/skin) and restore them in the finally block. Bob 2026-05-12:\n // \"the message that shows is different. It jumped to the top.\"\n const scroller = doc.scrollingElement || doc.documentElement;\n const savedScrollTop = scroller?.scrollTop ?? 0;\n const savedBodyScrollTop = body.scrollTop;\n try {\n editor.undoManager?.ignore?.(() => {\n // 1. Unwrap any existing markers \u2014 we'll re-wrap fresh based\n // on the current content. Cheaper than incremental update\n // and avoids stale markers if the user fixed a word\n // without going through our menu (just retyped it).\n const old = body.querySelectorAll(`span[${MARKER_ATTR}]`);\n for (const m of old) {\n const parent = m.parentNode;\n if (!parent) continue;\n while (m.firstChild) parent.insertBefore(m.firstChild, m);\n parent.removeChild(m);\n }\n // Merge text nodes that were split by the now-removed spans.\n body.normalize();\n\n // 2. Walk text nodes and collect words to wrap.\n const walker = doc.createTreeWalker(body, NodeFilter.SHOW_TEXT, {\n acceptNode(node) {\n let p: Node | null = node.parentNode;\n while (p && p !== body) {\n if (p.nodeType === Node.ELEMENT_NODE && SKIP_TAGS.has((p as Element).tagName)) {\n return NodeFilter.FILTER_REJECT;\n }\n p = p.parentNode;\n }\n return NodeFilter.FILTER_ACCEPT;\n },\n });\n // Capture the caret position so we can skip flagging the\n // word currently being typed. Without this, every keystroke\n // mid-word produces a red underline that grows as the user\n // types \u2014 exactly the \"twiddling\" Bob called out. Stripping\n // existing markers (step 1 above) plus this skip means a\n // freshly-typed word stays clean; once the caret leaves it\n // (space, punctuation, arrow keys) the next pass will mark\n // it if still misspelled.\n //\n // After body.normalize() in step 1, the live selection's\n // focusNode may have been collapsed away \u2014 re-read it now.\n let caretNode: Text | null = null;\n let caretOffset = 0;\n const liveSel = doc.getSelection();\n if (liveSel && liveSel.rangeCount > 0) {\n const f = liveSel.focusNode;\n if (f && f.nodeType === Node.TEXT_NODE) {\n caretNode = f as Text;\n caretOffset = liveSel.focusOffset;\n }\n }\n type Hit = { node: Text; start: number; end: number };\n const hits: Hit[] = [];\n let n: Node | null = walker.nextNode();\n // Letter / digit / apostrophe / hyphen \u2014 tokenize words via\n // Unicode-aware regex so we don't false-flag accented words.\n const WORD_RE = /[\\p{L}][\\p{L}'\u2019\\-]*/gu;\n // Email addresses aren't natural-language words. Without this,\n // `bob@example.com` tokenizes to `bob` / `example` / `com` and\n // each gets spell-checked (and usually redlined). Find email\n // spans per text node up front and skip any word hit inside one.\n const EMAIL_RE = /[^\\s@<>()]+@[^\\s@<>()]+\\.[^\\s@<>()]+/g;\n while (n) {\n const tn = n as Text;\n const text = tn.data;\n const emailRanges: Array<[number, number]> = [];\n EMAIL_RE.lastIndex = 0;\n let em: RegExpExecArray | null;\n while ((em = EMAIL_RE.exec(text)) !== null) {\n emailRanges.push([em.index, em.index + em[0].length]);\n }\n let m: RegExpExecArray | null;\n WORD_RE.lastIndex = 0;\n while ((m = WORD_RE.exec(text)) !== null) {\n const word = m[0];\n if (word.length < MIN_WORD_LEN) continue;\n // Inside an email address \u2014 not a word to check.\n const wStart = m.index, wEnd = m.index + word.length;\n if (emailRanges.some(([s, e]) => wStart < e && wEnd > s)) continue;\n // Skip the word the caret is sitting inside (or\n // immediately adjacent to \u2014 inclusive on both ends).\n if (caretNode === tn\n && caretOffset >= m.index\n && caretOffset <= m.index + word.length) {\n continue;\n }\n if (sp.correct(word)) continue;\n hits.push({ node: tn, start: m.index, end: m.index + word.length });\n }\n n = walker.nextNode();\n }\n\n // 3. Wrap hits in reverse order \u2014 wrapping a span splits the\n // text node, which would invalidate earlier offsets. Going\n // right-to-left keeps the not-yet-touched offsets valid.\n hits.reverse();\n for (const h of hits) {\n const range = doc.createRange();\n range.setStart(h.node, h.start);\n range.setEnd(h.node, h.end);\n const span = doc.createElement(\"span\");\n span.setAttribute(MARKER_ATTR, \"1\");\n try { range.surroundContents(span); }\n catch { /* range spans a node boundary \u2014 rare; skip */ }\n }\n });\n } finally {\n if (savedAbs != null) restoreCaretFromAbsOffset(body, savedAbs);\n // Restore scroll positions AFTER the caret restore \u2014 setting the\n // caret may itself trigger scrollIntoView; setting scrollTop last\n // wins. Both targets get restored because the active scroller\n // differs across TinyMCE skins.\n if (scroller && scroller.scrollTop !== savedScrollTop) scroller.scrollTop = savedScrollTop;\n if (body.scrollTop !== savedBodyScrollTop) body.scrollTop = savedBodyScrollTop;\n }\n}\n\n/** Live focus position \u2192 absolute character offset from `body`. Walks all\n * text nodes, accumulates lengths until reaching focusNode, then adds\n * focusOffset. Returns null when no selection or selection isn't in a\n * text node we can find. */\nfunction caretAbsOffsetFromBody(body: HTMLElement): number | null {\n const doc = body.ownerDocument!;\n const sel = doc.getSelection();\n if (!sel || sel.rangeCount === 0) return null;\n const focusNode = sel.focusNode;\n const focusOffset = sel.focusOffset;\n if (!focusNode) return null;\n // Caret on element node (rare for typing-time decoration) \u2014 fall back\n // to \"offset\" being a child index; treat as zero contribution.\n if (focusNode.nodeType !== Node.TEXT_NODE) {\n // Walk only text BEFORE the focus point.\n let abs = 0;\n const walker = doc.createTreeWalker(body, NodeFilter.SHOW_TEXT);\n let n: Node | null = walker.nextNode();\n while (n) {\n if (focusNode.contains(n)) break;\n // n strictly precedes focusNode in tree order?\n const cmp = focusNode.compareDocumentPosition(n);\n if (cmp & Node.DOCUMENT_POSITION_PRECEDING) abs += (n as Text).data.length;\n n = walker.nextNode();\n }\n return abs;\n }\n let abs = 0;\n const walker = doc.createTreeWalker(body, NodeFilter.SHOW_TEXT);\n let n: Node | null = walker.nextNode();\n while (n) {\n if (n === focusNode) return abs + focusOffset;\n abs += (n as Text).data.length;\n n = walker.nextNode();\n }\n return null;\n}\n\n/** Restore caret to the absolute character offset from `body`. Walks\n * text nodes until the cumulative length crosses `abs`, then collapses\n * the selection there. No-op if abs is out of range. */\nfunction restoreCaretFromAbsOffset(body: HTMLElement, abs: number): void {\n const doc = body.ownerDocument!;\n const sel = doc.getSelection();\n if (!sel) return;\n const walker = doc.createTreeWalker(body, NodeFilter.SHOW_TEXT);\n let acc = 0;\n let n: Node | null = walker.nextNode();\n while (n) {\n const len = (n as Text).data.length;\n if (acc + len >= abs) {\n const range = doc.createRange();\n range.setStart(n, Math.max(0, abs - acc));\n range.collapse(true);\n sel.removeAllRanges();\n sel.addRange(range);\n return;\n }\n acc += len;\n n = walker.nextNode();\n }\n // abs past end \u2014 drop caret at end of last text node if any.\n const last = walker.previousNode() as Text | null;\n if (last) {\n const range = doc.createRange();\n range.setStart(last, last.data.length);\n range.collapse(true);\n sel.removeAllRanges();\n sel.addRange(range);\n }\n}\n\n/** Inject the wavy-red CSS into the editor iframe. */\nfunction installDecorationStyle(editor: any): void {\n const doc: Document | null = editor.getDoc?.();\n if (!doc) return;\n if (doc.getElementById(\"mailx-spell-style\")) return;\n const style = doc.createElement(\"style\");\n style.id = \"mailx-spell-style\";\n style.textContent = `\n span[${MARKER_ATTR}] {\n text-decoration: underline wavy #d33;\n text-decoration-skip-ink: none;\n text-underline-offset: 2px;\n /* No background \u2014 keeps the styling subtle, like a native\n * spell underline, not a Find-highlight. */\n background: transparent;\n }\n `;\n doc.head.appendChild(style);\n}\n\n/** Strip decoration markers from serialized output. TinyMCE fires\n * attribute filters during getContent / draft-save; this filter\n * unwraps the span so the saved/sent HTML carries only the text. */\nfunction installSerializerFilter(editor: any): void {\n if ((editor as any).__mailxSpellSerializerWired) return;\n (editor as any).__mailxSpellSerializerWired = true;\n try {\n editor.serializer.addAttributeFilter(MARKER_ATTR, (nodes: any[]) => {\n for (const node of nodes) {\n // TinyMCE's html-node API: `unwrap()` replaces the node\n // with its children. Exactly what we want \u2014 keep the\n // text, drop the span.\n if (typeof node.unwrap === \"function\") node.unwrap();\n }\n });\n } catch (e) {\n console.warn(\"[spellcheck] serializer filter setup failed:\", e);\n }\n}\n\n// \u2500\u2500 Context menu \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction showSuggestionsMenu(\n parentDoc: Document, x: number, y: number,\n items: Array<{ label: string; action: () => void; emphasized?: boolean; separator?: boolean }>,\n): void {\n parentDoc.getElementById(\"mailx-spell-menu\")?.remove();\n const menu = parentDoc.createElement(\"div\");\n menu.id = \"mailx-spell-menu\";\n menu.style.cssText = `\n position: fixed;\n left: ${x}px; top: ${y}px;\n z-index: 10000;\n background: var(--color-bg, #fff);\n color: var(--color-text, #222);\n border: 1px solid var(--color-border, #ccc);\n border-radius: 6px;\n box-shadow: 0 4px 16px rgba(0,0,0,0.18);\n padding: 4px 0;\n font: 13px system-ui, sans-serif;\n min-width: 180px;\n max-width: 320px;\n `;\n for (const it of items) {\n if (it.separator) {\n const sep = parentDoc.createElement(\"div\");\n sep.style.cssText = \"border-top:1px solid var(--color-border,#ddd); margin: 4px 0;\";\n menu.appendChild(sep);\n continue;\n }\n const btn = parentDoc.createElement(\"button\");\n btn.type = \"button\";\n btn.textContent = it.label;\n btn.style.cssText = `\n display: block; width: 100%; text-align: left;\n padding: 5px 12px; border: none; background: none;\n color: inherit; cursor: pointer; font: inherit;\n ${it.emphasized ? \"font-weight: 600;\" : \"\"}\n `;\n btn.addEventListener(\"mouseenter\", () => { btn.style.background = \"var(--color-bg-hover, #eef)\"; });\n btn.addEventListener(\"mouseleave\", () => { btn.style.background = \"none\"; });\n btn.addEventListener(\"click\", () => {\n try { it.action(); } finally { menu.remove(); }\n });\n menu.appendChild(btn);\n }\n parentDoc.body.appendChild(menu);\n const r = menu.getBoundingClientRect();\n if (r.right > window.innerWidth) menu.style.left = `${Math.max(8, window.innerWidth - r.width - 8)}px`;\n if (r.bottom > window.innerHeight) menu.style.top = `${Math.max(8, window.innerHeight - r.height - 8)}px`;\n // Dismiss listeners on EVERY document the user could plausibly click\n // into: the compose document (parentDoc, where the menu is rendered),\n // the TinyMCE editor iframe doc (where the right-click originated and\n // where the user's caret usually lives), and the top mailx window\n // (outside the compose overlay entirely). Without the editor-iframe\n // and top-doc listeners, clicking back into the editor or onto the\n // folder list left the menu pinned (Bob 2026-05-12: \"when I click\n // outside this menu why isn't it going away?\").\n const docs: Document[] = [parentDoc];\n try {\n const composeWin = parentDoc.defaultView as Window | null;\n if (composeWin?.frameElement && composeWin.parent?.document\n && composeWin.parent.document !== parentDoc) {\n docs.push(composeWin.parent.document);\n }\n } catch { /* cross-origin \u2014 ignore */ }\n // Editor iframe sits inside parentDoc; locate it via TinyMCE convention\n // (.tox-edit-area iframe) or fall back to first iframe in the body.\n try {\n const editorIframe = parentDoc.querySelector(\"iframe.tox-edit-area__iframe\")\n || parentDoc.querySelector(\"iframe\");\n const editorDoc = (editorIframe as HTMLIFrameElement | null)?.contentDocument;\n if (editorDoc && editorDoc !== parentDoc) docs.push(editorDoc);\n } catch { /* */ }\n const dismiss = (e: Event) => {\n if (e.type === \"keydown\" && (e as KeyboardEvent).key !== \"Escape\") return;\n if (e.type === \"mousedown\" && menu.contains(e.target as Node)) return;\n menu.remove();\n for (const d of docs) {\n d.removeEventListener(\"mousedown\", dismiss, true);\n d.removeEventListener(\"keydown\", dismiss, true);\n }\n };\n setTimeout(() => {\n for (const d of docs) {\n d.addEventListener(\"mousedown\", dismiss, true);\n d.addEventListener(\"keydown\", dismiss, true);\n }\n }, 0);\n}\n\n/** Replace a misspelling marker span with the correction. Done via\n * range-based selection + insertText so TinyMCE's undo stack and\n * dirty-tracking pick it up properly. */\nfunction replaceMarker(editor: any, marker: HTMLElement, replacement: string): void {\n const doc: Document = editor.getDoc();\n const range = doc.createRange();\n range.selectNode(marker);\n const sel = doc.getSelection();\n if (!sel) return;\n sel.removeAllRanges();\n sel.addRange(range);\n try {\n if (!doc.execCommand(\"insertText\", false, replacement)) {\n range.deleteContents();\n range.insertNode(doc.createTextNode(replacement));\n }\n } catch {\n range.deleteContents();\n range.insertNode(doc.createTextNode(replacement));\n }\n}\n\n/** Fast removal-only pass: unwrap any misspelling marker whose word is now\n * correct (or no longer a single word). Runs on the short CLEANUP debounce.\n * Safe at a short interval because it only ever *removes* underlines \u2014\n * unlike decorate(), which adds them and so waits the calmer 1200 ms.\n * This is what makes a corrected word's red line vanish promptly. */\nfunction cleanupCorrected(editor: any, sp: NSpell): void {\n const body: HTMLElement | null = editor.getBody?.();\n const doc: Document | null = editor.getDoc?.();\n if (!body || !doc) return;\n const markers = body.querySelectorAll(`span[${MARKER_ATTR}]`);\n if (markers.length === 0) return;\n // The marker the caret is inside is being actively edited \u2014 leave it.\n let caretMarker: Node | null = null;\n const sel = doc.getSelection();\n if (sel && sel.rangeCount > 0) {\n let p: Node | null = sel.focusNode;\n while (p && p !== body) {\n if (p.nodeType === Node.ELEMENT_NODE && (p as Element).hasAttribute?.(MARKER_ATTR)) { caretMarker = p; break; }\n p = p.parentNode;\n }\n }\n const stale: Element[] = [];\n for (const m of markers) {\n if (m === caretMarker) continue;\n const word = m.textContent || \"\";\n // Now correct, emptied, or split by editing into multiple tokens.\n if (!word || /\\s/.test(word) || sp.correct(word)) stale.push(m);\n }\n if (stale.length === 0) return;\n // Unwrap only \u2014 no re-walk, no body.normalize() \u2014 so the live caret\n // Range stays valid (we never touch the caret's own marker, and a plain\n // unwrap leaves text-node offsets intact). No caret save/restore needed.\n editor.undoManager?.ignore?.(() => {\n for (const m of stale) {\n const parent = m.parentNode;\n if (!parent) continue;\n while (m.firstChild) parent.insertBefore(m.firstChild, m);\n parent.removeChild(m);\n }\n });\n}\n\n// \u2500\u2500 Public entry point \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/** Wire the spell-check into a TinyMCE editor instance. Idempotent. */\nexport function wireSpellcheck(editor: any): void {\n if ((editor as any).__mailxSpellWired) return;\n (editor as any).__mailxSpellWired = true;\n\n // Kill the native (Chromium/WebView2) spellchecker on this editor. mailx\n // runs its own nspell checker; leaving the native one on too double-\n // underlines every word AND pops the native suggestion menu instead of\n // ours (Bob 2026-05-18: \"why so much red twiddle\" / \"spell fixing is\n // broken?\"). Force spellcheck=\"false\" on the body and keep it off \u2014 some\n // WebView2 builds re-enable it, hence the observer.\n const killNativeSpellcheck = (): void => {\n try {\n const body: HTMLElement | null = editor.getBody?.();\n if (body && body.getAttribute(\"spellcheck\") !== \"false\") {\n body.setAttribute(\"spellcheck\", \"false\");\n }\n } catch { /* editor not ready \u2014 retried by the observer / events */ }\n };\n killNativeSpellcheck();\n try {\n const body: HTMLElement | null = editor.getBody?.();\n if (body) new MutationObserver(killNativeSpellcheck)\n .observe(body, { attributes: true, attributeFilter: [\"spellcheck\"] });\n } catch { /* */ }\n\n let sp: NSpell | null = null;\n let decorateTimer: ReturnType<typeof setTimeout> | null = null;\n const scheduleDecorate = (): void => {\n if (!sp) return;\n if (decorateTimer) clearTimeout(decorateTimer);\n decorateTimer = setTimeout(() => {\n decorateTimer = null;\n if (sp) decorate(editor, sp);\n }, DECORATE_DEBOUNCE_MS);\n };\n // Short-debounce removal-only pass so a corrected word's red line clears\n // promptly without waiting on the full (add+remove) decorate debounce.\n let cleanupTimer: ReturnType<typeof setTimeout> | null = null;\n const scheduleCleanup = (): void => {\n if (!sp) return;\n if (cleanupTimer) clearTimeout(cleanupTimer);\n cleanupTimer = setTimeout(() => {\n cleanupTimer = null;\n if (sp) cleanupCorrected(editor, sp);\n }, CLEANUP_DEBOUNCE_MS);\n };\n\n // Kick off the dictionary load. First decoration runs as soon as\n // it resolves; subsequent runs are triggered by editor events.\n getSpell().then((loaded) => {\n sp = loaded;\n installDecorationStyle(editor);\n installSerializerFilter(editor);\n decorate(editor, loaded);\n }).catch((err) => {\n console.error(\"[spellcheck] dict load failed:\", err);\n });\n\n // Re-decorate on edits / paste / content swap. `nodechange` covers\n // most of these; `input` catches typing in finer-grained events on\n // some TinyMCE versions. The short cleanup pass runs on the same\n // events so a just-corrected word loses its underline quickly.\n editor.on(\"input nodechange setcontent paste keyup\", scheduleDecorate);\n editor.on(\"input nodechange setcontent paste keyup\", scheduleCleanup);\n\n // Right-click handler. If the click landed on a marker span, show\n // suggestions; otherwise let the default menu (WebView2's) fire.\n const iframeDoc: Document = editor.getDoc();\n iframeDoc.addEventListener(\"contextmenu\", (ev: Event) => {\n const e = ev as MouseEvent;\n const target = e.target as HTMLElement | null;\n if (!target) return;\n const marker = target.closest?.(`span[${MARKER_ATTR}]`) as HTMLElement | null;\n if (!marker) return; // not on a misspelled word \u2014 default menu fires\n const word = marker.textContent || \"\";\n if (!word || !sp) return;\n e.preventDefault();\n e.stopPropagation();\n // nspell's suggest() leans on Hunspell's TRY/REP heuristics and\n // doesn't reliably surface adjacent-letter transpositions \u2014 the\n // single most common English typo (Bob 2026-05-12: \"what kind of\n // a spell is this if it can't see hte is the\"). Prepend our own\n // transposition pass: for each adjacent pair in the word, swap\n // them and keep results that exist in the dictionary. Then merge\n // with nspell's list, transpositions first, dedup, cap at 7.\n const transposed: string[] = [];\n for (let i = 0; i < word.length - 1; i++) {\n const swapped = word.slice(0, i) + word[i + 1] + word[i] + word.slice(i + 2);\n if (swapped !== word && sp.correct(swapped) && !transposed.includes(swapped)) {\n transposed.push(swapped);\n }\n }\n const nspellSugs: string[] = sp.suggest(word) as string[];\n const sugs: string[] = [];\n for (const s of [...transposed, ...nspellSugs]) {\n if (!sugs.includes(s)) sugs.push(s);\n if (sugs.length >= 7) break;\n }\n const iframeEl = editor.iframeElement as HTMLIFrameElement | undefined;\n const iframeRect = iframeEl ? iframeEl.getBoundingClientRect() : { left: 0, top: 0 };\n const items: Array<{ label: string; action: () => void; emphasized?: boolean; separator?: boolean }> = [];\n if (sugs.length === 0) {\n items.push({ label: \"(no suggestions)\", action: () => { /* */ } });\n } else {\n for (const s of sugs) {\n items.push({\n label: s,\n emphasized: true,\n action: () => {\n replaceMarker(editor, marker, s);\n // Re-decorate so the replacement is checked too.\n scheduleDecorate();\n },\n });\n }\n }\n items.push({ label: \"\", action: () => { /* */ }, separator: true });\n items.push({\n label: `Add \"${word}\" to dictionary`,\n action: () => { if (sp) addToUserDict(word, sp); scheduleDecorate(); },\n });\n items.push({\n label: \"Ignore (this session)\",\n action: () => { if (sp) sp.add(word); scheduleDecorate(); },\n });\n showSuggestionsMenu(document, iframeRect.left + e.clientX, iframeRect.top + e.clientY, items);\n }, true);\n}\n", "/**\n * Ghost text autocomplete \u2014 shows AI suggestions as translucent overlay at cursor.\n * Tab accepts, Escape dismisses, any other key dismisses and re-triggers after debounce.\n * Editor-agnostic: works with both Quill and tiptap via MailxEditor interface.\n */\n\nimport type { MailxEditor } from \"./editor.js\";\nimport { autocomplete } from \"../lib/api-client.js\";\n\ninterface GhostTextContext {\n getSubject: () => string;\n getTo: () => string;\n}\n\nlet ghostEl: HTMLElement | null = null;\nlet currentSuggestion: string | null = null;\nlet debounceTimer: ReturnType<typeof setTimeout> | null = null;\nlet abortController: AbortController | null = null;\nlet activeEditor: MailxEditor | null = null;\nlet debounceMs = 600;\n\nfunction dismiss(): void {\n currentSuggestion = null;\n if (ghostEl) {\n ghostEl.remove();\n ghostEl = null;\n }\n}\n\nfunction positionGhost(editor: MailxEditor, text: string): void {\n dismiss();\n\n const sel = window.getSelection();\n if (!sel || sel.rangeCount === 0 || !sel.isCollapsed) return;\n\n const range = sel.getRangeAt(0);\n let rect = range.getBoundingClientRect();\n\n // Collapsed range may return zero-width rect \u2014 use a temp span to measure\n if (rect.width === 0 && rect.height === 0) {\n const span = document.createElement(\"span\");\n span.textContent = \"\\u200B\"; // zero-width space\n range.insertNode(span);\n rect = span.getBoundingClientRect();\n span.remove();\n // Restore selection\n sel.removeAllRanges();\n sel.addRange(range);\n }\n\n const container = editor.getScrollContainer();\n const containerRect = container.getBoundingClientRect();\n\n ghostEl = document.createElement(\"span\");\n ghostEl.className = \"ghost-text\";\n ghostEl.textContent = text;\n ghostEl.style.top = `${rect.top - containerRect.top + container.scrollTop}px`;\n ghostEl.style.left = `${rect.left - containerRect.left + container.scrollLeft}px`;\n container.appendChild(ghostEl);\n\n currentSuggestion = text;\n}\n\nfunction requestSuggestion(editor: MailxEditor, context: GhostTextContext): void {\n // Cancel any in-flight request\n if (abortController) {\n abortController.abort();\n abortController = null;\n }\n\n const bodyText = editor.getText();\n if (!bodyText || bodyText.trim().length < 3) return; // need at least a few characters\n\n abortController = new AbortController();\n const signal = abortController.signal;\n\n autocomplete({\n subject: context.getSubject(),\n to: context.getTo(),\n bodyText,\n cursorOffset: bodyText.length,\n }, signal).then((result: any) => {\n if (signal.aborted) return;\n if (result.suggestion) {\n positionGhost(editor, result.suggestion);\n }\n }).catch((e: any) => {\n if (e.name === \"AbortError\") return;\n // Silently ignore autocomplete errors\n });\n}\n\nexport function initGhostText(editor: MailxEditor, context: GhostTextContext, options?: { debounceMs?: number }): void {\n activeEditor = editor;\n if (options?.debounceMs) debounceMs = options.debounceMs;\n\n // Debounced content change \u2192 request suggestion\n editor.onContentChange(() => {\n dismiss();\n if (debounceTimer) clearTimeout(debounceTimer);\n debounceTimer = setTimeout(() => {\n requestSuggestion(editor, context);\n }, debounceMs);\n });\n\n // Key handler: Tab accepts, Escape dismisses\n editor.onKeyDown((e: KeyboardEvent) => {\n if (!currentSuggestion) return;\n\n if (e.key === \"Tab\") {\n e.preventDefault();\n e.stopPropagation();\n const text = currentSuggestion;\n dismiss();\n editor.insertTextAtCursor(text);\n return;\n }\n\n if (e.key === \"Escape\") {\n e.preventDefault();\n e.stopPropagation();\n dismiss();\n return;\n }\n\n // Any other key: dismiss (new suggestion will come after debounce)\n dismiss();\n });\n\n // Dismiss on blur/scroll\n editor.root.addEventListener(\"blur\", dismiss);\n editor.getScrollContainer().addEventListener(\"scroll\", dismiss);\n}\n\nexport function destroyGhostText(): void {\n dismiss();\n if (debounceTimer) clearTimeout(debounceTimer);\n if (abortController) abortController.abort();\n activeEditor = null;\n}\n", "/**\n * Editor help text \u2014 embedded copy of `app/docs/editor.md`.\n *\n * Source of truth is the .md file (it's what ships in `app/docs/` for any\n * external reader). This TS const is the runtime copy the compose iframe\n * loads \u2014 embedding here avoids an asset fetch from the WebView (which\n * has different paths on desktop/Android and would fail silently when the\n * docs aren't bundled).\n *\n * Keep this in sync with `app/docs/editor.md`. When updating either, copy\n * the .md contents into the EDITOR_HELP_MD literal below. (A small sync\n * script under `app/docs/` could automate this \u2014 TODO once the rules /\n * docs sync pattern stabilizes.)\n */\nexport const EDITOR_HELP_MD = `# Compose editor \u2014 formatting and shortcuts\n\nmailx ships with two rich-text editors: **Quill** (default) and **tiptap**.\nMost things work the same in both. Differences are called out below.\n\nSwitch editors via **Settings \u2192 Editor \u2192 Quill | tiptap**.\n\n## Universal \u2014 works in both editors\n\n| Action | Shortcut | Where |\n|---|---|---|\n| **Send** | Ctrl+Enter | toolbar Send button |\n| Bold / Italic / Underline | Ctrl+B / Ctrl+I / Ctrl+U | toolbar B / I / U |\n| Strikethrough | Ctrl+Shift+X | toolbar S |\n| Bulleted list | Ctrl+Shift+8 | toolbar \u2022 |\n| Ordered list | Ctrl+Shift+7 | toolbar 1. |\n| Insert / edit link | Ctrl+K | toolbar \uD83D\uDD17 |\n| Remove link | Ctrl+Shift+K | (no toolbar button \u2014 use Ctrl+Shift+K) |\n| Blockquote | (Quill: Ctrl+Shift+Q; tiptap: toolbar) | toolbar \\\" |\n| Clear formatting | Ctrl+\\\\\\\\ | toolbar \u2716 |\n| Heading (H1 / H2 / H3) | (Quill: format dropdown; tiptap: select dropdown) | \"Normal / Heading 1 / 2 / 3\" |\n| Undo / Redo | Ctrl+Z / Ctrl+Y | (no toolbar button) |\n| Spell-check | (browser native \u2014 red underlines) | right-click word |\n| Paste plain text | Ctrl+Shift+V | (browser native) |\n\n## Quill-only\n\n| Action | Shortcut |\n|---|---|\n| Indent / outdent | Ctrl+] / Ctrl+[ |\n| Color text | Ctrl+Shift+C |\n| Format dropdown | toolbar (left side) |\n| Inline code | toolbar \\`<>\\` |\n| Code block | toolbar |\n| Image inserter | (no built-in; use drag-and-drop or paste) |\n\nQuill has a more elaborate toolbar with format-specific dropdowns (font, size,\ncolor). Internally Quill uses its own *Delta* document model \u2014 copy/paste\nfrom Word/Outlook sometimes leaves extra empty paragraphs that you'll see\nin the sent message body.\n\n## tiptap-only\n\n| Action | Where |\n|---|---|\n| Heading select | left of toolbar |\n| Toggle bold / italic / underline / strike | toolbar B / I / U / S |\n| Blockquote | toolbar \\\" |\n| Image (drag-and-drop) | drop a file into the body |\n\ntiptap is built on ProseMirror. Output HTML is cleaner than Quill on\nWord/Outlook paste roundtrips. Bundle is smaller. Some Quill toolbar\nfeatures (inline code, indent shortcuts, color picker) aren't wired \u2014\nuse the heading select / format menu instead.\n\n## Common features (across both)\n\n- **Drag-and-drop attachments** \u2014 drop files anywhere on the compose\n window to attach. Overlay highlights the drop target while dragging.\n- **Edit in Word / LibreOffice** \u2014 toolbar **Edit in Word** button opens\n the body in your default external editor. Save in the external editor\n and the body reloads here. mailx writes a temporary \\`.docx\\` file (see\n \\`~/.rmfmail/external-edit/\\`) and watches it for changes.\n- **Auto-save drafts** \u2014 every 5 seconds (and on input debounce / on\n blur). Drafts land in the Drafts folder via IMAP append.\n- **Address auto-completion** \u2014 type a partial name in To/Cc/Bcc; matches\n rank by recency \u00D7 use-count. Group names from \\`contacts.jsonc \u2192 groups\\`\n also surface here.\n- **Address-field expansion** \u2014 recipient fields are auto-growing\n textareas; long lists wrap to multiple lines.\n- **Group expansion on send** \u2014 type a group name (e.g. \\`family\\`) in\n To/Cc/Bcc and it expands to the address list at send time.\n- **Ghost-text autocomplete** (off by default) \u2014 Settings \u2192\n AI autocomplete \u2192 on. Predicts the next words while you type.\n\n## When the toolbar doesn't appear\n\nThe editor loads from a CDN (jsdelivr). If your network can't reach it, the\ntoolbar disappears and a plain \\`contenteditable\\` fallback takes over. Status\nbar shows the failure. mailx tries the *other* editor before falling all the\nway back; if both fail you get a plain textarea with no shortcuts and no\ntoolbar \u2014 sending still works.\n\n## See also\n\n- \\`accounts.md\\`, \\`contacts.md\\`, \\`allowlist.md\\`, \\`clients.md\\`, \\`config.md\\`,\n \\`preferences.md\\` \u2014 config-file references (these live in your GDrive\n \\`.rmfmail/\\` folder).\n- This document is **app-internal** \u2014 it ships with each release and\n documents the editor as it currently exists in the version you're\n running. It is not deployed to your user folder.\n`;\n", "/**\n * Editor abstraction \u2014 wraps Quill / tiptap / TinyMCE behind a common\n * interface. The compose window loads this module and calls\n * createEditor() based on the user's setting.\n *\n * rmf-tiny is dynamic-imported (not static) so a missing/unreachable\n * adapter fails ONLY the TinyMCE branch \u2014 Quill and tiptap stay\n * working. A static top-of-file import would crash the whole module\n * if the browser couldn't resolve \"@bobfrankston/rmf-tiny\" via the\n * import map, and compose.ts (which imports createEditor from here)\n * would silently fail to attach handlers.\n */\n\nexport interface MailxEditor {\n setHtml(html: string): void;\n getHtml(): string;\n getText(): string;\n focus(): void;\n setCursor(pos: number): void;\n /** The editor's root editable element (for checking content) */\n root: HTMLElement;\n /** The scrollable container for positioning ghost text */\n getScrollContainer(): HTMLElement;\n /** Register a handler for content changes */\n onContentChange(handler: () => void): void;\n /** Register a keydown handler on the editor */\n onKeyDown(handler: (e: KeyboardEvent) => void): void;\n /** Insert plain text at the current cursor position */\n insertTextAtCursor(text: string): void;\n}\n\n// \u2500\u2500 Quill \u2500\u2500\n\ndeclare const Quill: any;\n\n/** URL-ish test: accepts http(s)://, mailto:, tel:, and bare domains with a dot. */\nfunction looksLikeUrl(s: string): boolean {\n const t = s.trim();\n if (!t) return false;\n if (/^(https?|mailto|tel):/i.test(t)) return true;\n // bare domain (e.g. \"example.com/path\") \u2014 require a dot and no internal whitespace\n return /^[\\w-]+(\\.[\\w-]+)+(\\/\\S*)?$/.test(t);\n}\nfunction normalizeUrl(s: string): string {\n const t = s.trim();\n if (!t) return t;\n if (/^(https?|mailto|tel):/i.test(t)) return t;\n if (/^[\\w.+-]+@[\\w-]+(\\.[\\w-]+)+$/.test(t)) return `mailto:${t}`;\n return `https://${t}`;\n}\n\n/** Floating modal that edits both link text and URL. Returns null on Cancel,\n * { text, url } on OK, or { text: \"\", url: \"\" } on \"Remove link\". */\nfunction openLinkDialog(initialText: string, initialUrl: string): Promise<{ text: string; url: string; remove?: boolean } | null> {\n return new Promise(resolve => {\n const backdrop = document.createElement(\"div\");\n backdrop.className = \"mailx-modal-backdrop\";\n const panel = document.createElement(\"div\");\n panel.className = \"mailx-modal\";\n panel.innerHTML = `\n <div class=\"mailx-modal-title\">Edit link</div>\n <label class=\"mailx-modal-label\">Text\n <input type=\"text\" class=\"mailx-modal-input\" id=\"mailx-link-text\">\n </label>\n <label class=\"mailx-modal-label\">URL\n <input type=\"text\" class=\"mailx-modal-input\" id=\"mailx-link-url\" spellcheck=\"false\" autocomplete=\"off\">\n </label>\n <div class=\"mailx-modal-buttons\">\n <button type=\"button\" class=\"mailx-modal-btn\" data-action=\"remove\">Remove link</button>\n <span class=\"mailx-modal-spacer\"></span>\n <button type=\"button\" class=\"mailx-modal-btn\" data-action=\"cancel\">Cancel</button>\n <button type=\"button\" class=\"mailx-modal-btn mailx-modal-btn-primary\" data-action=\"ok\">OK</button>\n </div>`;\n backdrop.appendChild(panel);\n document.body.appendChild(backdrop);\n\n const textInput = panel.querySelector<HTMLInputElement>(\"#mailx-link-text\")!;\n const urlInput = panel.querySelector<HTMLInputElement>(\"#mailx-link-url\")!;\n textInput.value = initialText;\n urlInput.value = initialUrl;\n\n const close = (result: { text: string; url: string; remove?: boolean } | null) => {\n backdrop.remove();\n document.removeEventListener(\"keydown\", onKey, true);\n resolve(result);\n };\n const commit = () => close({ text: textInput.value, url: normalizeUrl(urlInput.value) });\n const onKey = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") { e.stopPropagation(); e.preventDefault(); close(null); }\n else if (e.key === \"Enter\") { e.stopPropagation(); e.preventDefault(); commit(); }\n };\n document.addEventListener(\"keydown\", onKey, true);\n\n panel.querySelectorAll<HTMLButtonElement>(\".mailx-modal-btn\").forEach(btn => {\n btn.addEventListener(\"click\", () => {\n const action = btn.dataset.action;\n if (action === \"cancel\") close(null);\n else if (action === \"remove\") close({ text: textInput.value, url: \"\", remove: true });\n else commit();\n });\n });\n backdrop.addEventListener(\"mousedown\", (e) => { if (e.target === backdrop) close(null); });\n // Focus URL if we have text, else text first\n (initialText ? urlInput : textInput).focus();\n (initialText ? urlInput : textInput).select();\n });\n}\n\nfunction createQuillEditor(container: HTMLElement): MailxEditor {\n /** Open the link dialog for the current selection or cursor. If range has\n * a selection, prefill text from the selected text; if cursor is inside\n * a link, prefill both from the link's range. */\n const openLinkForRange = async (quill: any, range: any) => {\n if (!range) return;\n // Expand a bare cursor inside a link to the full link range\n let linkRange = range;\n const format = quill.getFormat(range);\n if (range.length === 0 && format.link) {\n // Walk left and right to find the link extent\n const [leaf, offset] = quill.getLeaf(range.index);\n if (leaf) {\n // Build a range that spans the whole link\n const text = quill.getText();\n let start = range.index, end = range.index;\n while (start > 0 && quill.getFormat(start - 1, 1).link === format.link) start--;\n while (end < text.length - 1 && quill.getFormat(end, 1).link === format.link) end++;\n linkRange = { index: start, length: end - start };\n }\n }\n const currentText = linkRange.length ? quill.getText(linkRange.index, linkRange.length).replace(/\\n$/, \"\") : \"\";\n const currentUrl = format.link || \"\";\n const result = await openLinkDialog(currentText, currentUrl);\n if (!result) return;\n if (result.remove) {\n if (linkRange.length) quill.formatText(linkRange.index, linkRange.length, \"link\", false);\n return;\n }\n if (!result.url) return;\n const newText = result.text || result.url;\n if (linkRange.length) {\n // Replace the existing text+link with new text+link\n quill.deleteText(linkRange.index, linkRange.length);\n quill.insertText(linkRange.index, newText, { link: result.url });\n quill.setSelection(linkRange.index + newText.length, 0);\n } else {\n quill.insertText(linkRange.index, newText, { link: result.url });\n quill.setSelection(linkRange.index + newText.length, 0);\n }\n };\n\n // Extra keybindings for formatting that Quill doesn't wire up by default.\n // Ctrl+K (insert/edit link) is the one most users expect; we also add shortcuts\n // for strikethrough, lists, indent, color, and clear-formatting.\n const extraBindings: any = {\n insertLink: {\n key: \"K\", shortKey: true,\n handler: function (this: any, range: any) {\n openLinkForRange(this.quill, range);\n },\n },\n removeLink: {\n key: \"K\", shortKey: true, shiftKey: true,\n handler: function (this: any) { this.quill.format(\"link\", false); },\n },\n strike: {\n key: \"X\", shortKey: true, shiftKey: true,\n handler: function (this: any, range: any) {\n if (!range) return true;\n const cur = this.quill.getFormat(range).strike;\n this.quill.format(\"strike\", !cur);\n },\n },\n orderedList: {\n key: \"7\", shortKey: true, shiftKey: true,\n handler: function (this: any) { this.quill.format(\"list\", \"ordered\"); },\n },\n bulletList: {\n key: \"8\", shortKey: true, shiftKey: true,\n handler: function (this: any) { this.quill.format(\"list\", \"bullet\"); },\n },\n indent: {\n key: \"]\", shortKey: true,\n handler: function (this: any, range: any, context: any) {\n this.quill.format(\"indent\", (context.format.indent || 0) + 1);\n },\n },\n outdent: {\n key: \"[\", shortKey: true,\n handler: function (this: any, range: any, context: any) {\n this.quill.format(\"indent\", Math.max(0, (context.format.indent || 0) - 1));\n },\n },\n color: {\n key: \"C\", shortKey: true, shiftKey: true,\n handler: function (this: any, range: any) {\n if (!range) return true;\n const current = this.quill.getFormat(range).color || \"\";\n const color = prompt(\"Text color (name or #hex, blank to clear):\", current);\n if (color === null) return;\n this.quill.format(\"color\", color || false);\n },\n },\n clearFormat: {\n key: \"\\\\\", shortKey: true,\n handler: function (this: any, range: any) {\n if (!range) return true;\n this.quill.removeFormat(range.index, range.length || 0);\n },\n },\n };\n\n const q = new Quill(container, {\n theme: \"snow\",\n placeholder: \"Write your message...\",\n modules: {\n toolbar: [\n [{ font: [] }, { size: [\"small\", false, \"large\", \"huge\"] }],\n [{ header: [1, 2, 3, false] }],\n [\"bold\", \"italic\", \"underline\", \"strike\"],\n [{ color: [] }, { background: [] }],\n [{ list: \"ordered\" }, { list: \"bullet\" }],\n [{ align: [] }],\n [\"blockquote\", \"link\", \"image\"],\n [\"clean\"]\n ],\n keyboard: { bindings: extraBindings },\n }\n });\n\n // Make toolbar buttons non-tabbable so Tab goes straight to editor body\n document.querySelectorAll(\".ql-toolbar button, .ql-toolbar select, .ql-toolbar .ql-picker-label\").forEach(\n el => (el as HTMLElement).setAttribute(\"tabindex\", \"-1\")\n );\n\n // Native spell-check: WebView2 / Chromium underlines misspellings and the\n // right-click menu offers \"Add to dictionary\". Quill clears spellcheck on\n // its root by default AND may re-clear it asynchronously as part of its\n // setup (user-reported \"spell-check broken again\" with the one-shot set).\n // Set once now, again after the frame flushes so post-init clears can't\n // win, and install a MutationObserver to re-assert if anything later\n // strips the attribute. Cheap \u2014 fires at most on each clear.\n const applySpellcheck = () => {\n if (q.root.getAttribute(\"spellcheck\") !== \"true\") q.root.setAttribute(\"spellcheck\", \"true\");\n if (q.root.getAttribute(\"autocorrect\") !== \"on\") q.root.setAttribute(\"autocorrect\", \"on\");\n if (q.root.getAttribute(\"autocapitalize\") !== \"on\") q.root.setAttribute(\"autocapitalize\", \"on\");\n };\n applySpellcheck();\n requestAnimationFrame(applySpellcheck);\n setTimeout(applySpellcheck, 100);\n const spellObs = new MutationObserver(() => applySpellcheck());\n spellObs.observe(q.root, { attributes: true, attributeFilter: [\"spellcheck\", \"autocorrect\", \"autocapitalize\"] });\n\n // Toolbar link button: open our modal instead of Quill's built-in URL prompt.\n const toolbar = q.getModule(\"toolbar\");\n toolbar?.addHandler(\"link\", function (this: any) {\n openLinkForRange(q, q.getSelection() || { index: q.getLength() - 1, length: 0 });\n });\n\n // Paste handling:\n // - text/html clipboard with exactly one anchor (the common \"copy a link\n // with anchor text from a webpage\" case): take it over from Quill \u2014\n // Quill's clipboard module was producing duplicates (\"click here\" as\n // text PLUS a separate \"https://example.com\" as a link tail). Insert\n // the anchor's text content as a single linked run.\n // - text/html with richer content: defer to Quill (preserves formatting).\n // - text/plain that's a URL: insert as a link, optionally wrapping any\n // currently-selected text.\n // - Anything else: default Quill behavior (verbatim plain or HTML).\n // C36: AI proofread on right-click \u2192 \"Proofread selection\" item.\n // Uses the existing aiTransform IPC. Gated by autocomplete.proofreadEnabled.\n q.root.addEventListener(\"contextmenu\", async (e: MouseEvent) => {\n try {\n const sel = q.getSelection();\n if (!sel || sel.length === 0) return; // no selection \u2014 let native menu handle\n const settingsRaw = localStorage.getItem(\"mailx-ai-proofread-enabled\");\n if (settingsRaw !== \"true\") return; // feature off\n const text = q.getText(sel.index, sel.length);\n if (!text.trim()) return;\n e.preventDefault();\n // Inline action menu next to the cursor\n const menu = document.createElement(\"div\");\n menu.style.cssText = `position:fixed;z-index:2500;background:var(--color-bg);color:var(--color-text);border:1px solid var(--color-border);border-radius:6px;box-shadow:0 4px 16px rgba(0,0,0,0.2);padding:4px 0;font-size:13px;min-width:160px;`;\n menu.style.left = `${e.clientX}px`;\n menu.style.top = `${e.clientY}px`;\n const item = document.createElement(\"div\");\n item.textContent = \"Proofread selection\";\n item.style.cssText = `padding:6px 12px;cursor:pointer;`;\n item.addEventListener(\"mouseenter\", () => item.style.background = \"var(--color-bg-hover)\");\n item.addEventListener(\"mouseleave\", () => item.style.background = \"\");\n item.addEventListener(\"click\", async () => {\n menu.remove();\n try {\n const { aiTransform } = await import(\"../lib/api-client.js\");\n const r = await aiTransform({ action: \"proofread\", text });\n if (r?.text && r.text !== text) {\n q.deleteText(sel.index, sel.length);\n q.insertText(sel.index, r.text);\n q.setSelection(sel.index, r.text.length);\n } else if (r?.reason) {\n alert(`Proofread: ${r.reason}`);\n }\n } catch (err: any) {\n alert(`Proofread failed: ${err?.message || err}`);\n }\n });\n menu.appendChild(item);\n document.body.appendChild(menu);\n const dismiss = () => { menu.remove(); document.removeEventListener(\"mousedown\", dismiss); };\n setTimeout(() => document.addEventListener(\"mousedown\", dismiss), 0);\n } catch { /* fall through to native menu */ }\n });\n\n // IMPORTANT: register on the capture phase AND use stopImmediatePropagation\n // when we handle the paste ourselves. Quill 2.x's clipboard module attaches\n // its own listener on the same root element; preventDefault only stops the\n // browser's default contenteditable insertion, NOT Quill's parallel listener.\n // Without stopImmediatePropagation the two handlers fire independently and\n // both insert \u2014 user sees the URL twice. Capture phase guarantees we run\n // before Quill so stopImmediatePropagation actually blocks it.\n q.root.addEventListener(\"paste\", (e: ClipboardEvent) => {\n const cb = e.clipboardData;\n if (!cb) return;\n\n // Helper: call when we've handled the paste ourselves. Stops Quill's\n // own listener from also processing the same event.\n const consume = () => {\n e.preventDefault();\n e.stopImmediatePropagation();\n };\n\n // Q3: image-on-clipboard \u2192 inline as data: URL.\n for (const item of Array.from(cb.items)) {\n if (item.kind === \"file\" && item.type.startsWith(\"image/\")) {\n const file = item.getAsFile();\n if (!file) continue;\n consume();\n const reader = new FileReader();\n reader.onload = () => {\n const dataUrl = String(reader.result || \"\");\n const range = q.getSelection(true) || { index: q.getLength(), length: 0 };\n q.insertEmbed(range.index, \"image\", dataUrl);\n q.setSelection(range.index + 1, 0);\n };\n reader.readAsDataURL(file);\n return;\n }\n }\n\n const html = cb.getData(\"text/html\");\n const plain = cb.getData(\"text/plain\");\n\n if (html) {\n // Detect \"single anchor\" clipboard \u2014 copy from a browser usually\n // produces something like:\n // <meta charset='utf-8'><a href=\"https://example.com\">click here</a>\n // or wrapped in <html><body>. Parse and check.\n try {\n const tmp = document.createElement(\"div\");\n tmp.innerHTML = html;\n // Strip script/style, then unwrap <html>/<body> noise.\n const root = tmp.querySelector(\"body\") || tmp;\n // Walk for the only meaningful element\n const meaningful = Array.from(root.childNodes).filter(n => {\n if (n.nodeType === Node.TEXT_NODE) return (n.textContent || \"\").trim().length > 0;\n if (n.nodeType === Node.ELEMENT_NODE) {\n const tag = (n as Element).tagName.toLowerCase();\n return tag !== \"meta\" && tag !== \"style\" && tag !== \"script\";\n }\n return false;\n });\n if (meaningful.length === 1 && (meaningful[0] as HTMLElement).tagName?.toLowerCase() === \"a\") {\n const a = meaningful[0] as HTMLAnchorElement;\n const href = a.getAttribute(\"href\") || \"\";\n const text = (a.textContent || \"\").trim();\n if (href && text) {\n consume();\n const range = q.getSelection(true);\n if (!range) return;\n if (range.length > 0) {\n // Selected text exists \u2014 replace with the linked anchor text\n q.deleteText(range.index, range.length);\n }\n q.insertText(range.index, text, { link: href });\n q.setSelection(range.index + text.length, 0);\n return;\n }\n }\n // Single text-node wrapping the URL \u2014 common when copying from\n // browser address bar (Chrome ships text/html as\n // `<meta><span>URL</span>` alongside text/plain). Fall through\n // to the plain-URL path below instead of letting Quill insert\n // the bare URL text AND our handler insert it linked \u2014 which\n // is exactly the double-paste the user reported.\n const textOnly = (root.textContent || \"\").trim();\n if (textOnly && looksLikeUrl(textOnly) && !root.querySelector(\"a\")) {\n consume();\n const range = q.getSelection(true);\n if (!range) return;\n const url = normalizeUrl(textOnly);\n if (range.length > 0) {\n q.formatText(range.index, range.length, \"link\", url);\n q.setSelection(range.index + range.length, 0);\n } else {\n q.insertText(range.index, textOnly, { link: url });\n q.setSelection(range.index + textOnly.length, 0);\n }\n return;\n }\n } catch { /* fall through to Quill default */ }\n return; // Quill handles richer HTML clipboard (no consume \u2192 Quill runs)\n }\n\n if (plain && looksLikeUrl(plain)) {\n consume();\n const range = q.getSelection(true);\n if (!range) return;\n const url = normalizeUrl(plain);\n if (range.length > 0) {\n // Preserve existing selection text, just format it as a link\n q.formatText(range.index, range.length, \"link\", url);\n q.setSelection(range.index + range.length, 0);\n } else {\n q.insertText(range.index, plain.trim(), { link: url });\n q.setSelection(range.index + plain.trim().length, 0);\n }\n }\n }, true); // capture=true \u2014 run before Quill's own paste listener\n\n // Hover preview: show the target URL in a floating tooltip when the\n // pointer is over a link. Built on top of native mouseover/mouseout\n // rather than Quill's ql-tooltip (which is keyboard-triggered).\n let hoverTip: HTMLElement | null = null;\n q.root.addEventListener(\"mouseover\", (e: MouseEvent) => {\n const a = (e.target as HTMLElement).closest(\"a[href]\") as HTMLAnchorElement | null;\n if (!a) return;\n if (hoverTip) hoverTip.remove();\n hoverTip = document.createElement(\"div\");\n hoverTip.className = \"mailx-link-hover\";\n hoverTip.textContent = a.getAttribute(\"href\") || \"\";\n document.body.appendChild(hoverTip);\n const rect = a.getBoundingClientRect();\n hoverTip.style.left = `${Math.max(8, rect.left)}px`;\n hoverTip.style.top = `${rect.bottom + 4}px`;\n });\n q.root.addEventListener(\"mouseout\", (e: MouseEvent) => {\n const to = e.relatedTarget as HTMLElement | null;\n if (to && to.closest(\"a[href]\")) return;\n if (hoverTip) { hoverTip.remove(); hoverTip = null; }\n });\n\n return {\n setHtml(html: string): void {\n q.clipboard.dangerouslyPasteHTML(html);\n },\n getHtml(): string {\n return q.root.innerHTML;\n },\n getText(): string {\n return q.getText();\n },\n focus(): void {\n q.focus();\n },\n setCursor(pos: number): void {\n q.setSelection(pos, 0);\n },\n root: q.root,\n getScrollContainer(): HTMLElement {\n return q.root;\n },\n onContentChange(handler: () => void): void {\n q.on(\"text-change\", handler);\n },\n onKeyDown(handler: (e: KeyboardEvent) => void): void {\n q.root.addEventListener(\"keydown\", handler);\n },\n insertTextAtCursor(text: string): void {\n const sel = q.getSelection();\n if (sel) q.insertText(sel.index, text);\n }\n };\n}\n\n// \u2500\u2500 tiptap \u2500\u2500\n\nasync function createTiptapEditor(container: HTMLElement): Promise<MailxEditor> {\n // tiptap loaded via CDN \u2014 use global UMD bundles\n const { Editor } = (window as any).tiptapCore;\n const { StarterKit } = (window as any).tiptapStarterKit;\n const { Link } = (window as any).tiptapExtensionLink;\n const { Image } = (window as any).tiptapExtensionImage;\n const { Underline } = (window as any).tiptapExtensionUnderline;\n const { Placeholder } = (window as any).tiptapExtensionPlaceholder;\n\n // Build toolbar\n const toolbar = document.createElement(\"div\");\n toolbar.className = \"tt-toolbar\";\n toolbar.innerHTML = `\n <select class=\"tt-heading\" tabindex=\"-1\">\n <option value=\"p\">Normal</option>\n <option value=\"1\">Heading 1</option>\n <option value=\"2\">Heading 2</option>\n <option value=\"3\">Heading 3</option>\n </select>\n <button class=\"tt-btn\" data-cmd=\"bold\" title=\"Bold\" tabindex=\"-1\"><b>B</b></button>\n <button class=\"tt-btn\" data-cmd=\"italic\" title=\"Italic\" tabindex=\"-1\"><i>I</i></button>\n <button class=\"tt-btn\" data-cmd=\"underline\" title=\"Underline\" tabindex=\"-1\"><u>U</u></button>\n <button class=\"tt-btn\" data-cmd=\"strike\" title=\"Strikethrough\" tabindex=\"-1\"><s>S</s></button>\n <button class=\"tt-btn\" data-cmd=\"bulletList\" title=\"Bullet list\" tabindex=\"-1\">&#8226;</button>\n <button class=\"tt-btn\" data-cmd=\"orderedList\" title=\"Ordered list\" tabindex=\"-1\">1.</button>\n <button class=\"tt-btn\" data-cmd=\"blockquote\" title=\"Blockquote\" tabindex=\"-1\">&ldquo;</button>\n <button class=\"tt-btn\" data-cmd=\"link\" title=\"Link\" tabindex=\"-1\">&#128279;</button>\n <button class=\"tt-btn\" data-cmd=\"clearFormat\" title=\"Clear formatting\" tabindex=\"-1\">&#8999;</button>\n `;\n\n // Content area\n const content = document.createElement(\"div\");\n content.className = \"tt-content\";\n\n container.appendChild(toolbar);\n container.appendChild(content);\n\n const ed = new Editor({\n element: content,\n extensions: [\n StarterKit,\n Link.configure({ openOnClick: false }),\n Image,\n Underline,\n Placeholder.configure({ placeholder: \"Write your message...\" }),\n ],\n content: \"\",\n });\n\n // Wire toolbar buttons\n toolbar.querySelectorAll(\".tt-btn\").forEach((btn: Element) => {\n btn.addEventListener(\"mousedown\", (e) => {\n e.preventDefault();\n const cmd = (btn as HTMLElement).dataset.cmd;\n switch (cmd) {\n case \"bold\": ed.chain().focus().toggleBold().run(); break;\n case \"italic\": ed.chain().focus().toggleItalic().run(); break;\n case \"underline\": ed.chain().focus().toggleUnderline().run(); break;\n case \"strike\": ed.chain().focus().toggleStrike().run(); break;\n case \"bulletList\": ed.chain().focus().toggleBulletList().run(); break;\n case \"orderedList\": ed.chain().focus().toggleOrderedList().run(); break;\n case \"blockquote\": ed.chain().focus().toggleBlockquote().run(); break;\n case \"link\": {\n const url = prompt(\"URL:\");\n if (url) ed.chain().focus().setLink({ href: url }).run();\n break;\n }\n case \"clearFormat\": ed.chain().focus().clearNodes().unsetAllMarks().run(); break;\n }\n });\n });\n\n // Wire heading select\n const headingSelect = toolbar.querySelector(\".tt-heading\") as HTMLSelectElement;\n headingSelect?.addEventListener(\"change\", () => {\n const val = headingSelect.value;\n if (val === \"p\") ed.chain().focus().setParagraph().run();\n else ed.chain().focus().toggleHeading({ level: parseInt(val) as 1 | 2 | 3 }).run();\n });\n\n // Keyboard shortcuts \u2014 Quill wires these via Modules; tiptap needs them\n // hooked manually. Match the Quill bindings (Ctrl+Shift+7/8 for lists,\n // Ctrl+K for link, Ctrl+Shift+K to unlink, Ctrl+Shift+X strike, Ctrl+\\\n // clear). Listen on the editor's content element so we don't intercept\n // typing in the To/Cc/Bcc fields.\n content.addEventListener(\"keydown\", (e: KeyboardEvent) => {\n const mod = e.ctrlKey || e.metaKey;\n if (!mod) return;\n const k = e.key.toLowerCase();\n if (e.shiftKey && k === \"7\") { e.preventDefault(); ed.chain().focus().toggleOrderedList().run(); return; }\n if (e.shiftKey && k === \"8\") { e.preventDefault(); ed.chain().focus().toggleBulletList().run(); return; }\n if (e.shiftKey && k === \"x\") { e.preventDefault(); ed.chain().focus().toggleStrike().run(); return; }\n if (e.shiftKey && k === \"k\") { e.preventDefault(); ed.chain().focus().unsetLink().run(); return; }\n if (!e.shiftKey && k === \"k\") {\n e.preventDefault();\n const url = prompt(\"URL:\");\n if (url) ed.chain().focus().setLink({ href: url }).run();\n return;\n }\n if (k === \"\\\\\") { e.preventDefault(); ed.chain().focus().clearNodes().unsetAllMarks().run(); return; }\n });\n\n const editorEl = content.querySelector(\".tiptap\") as HTMLElement || content;\n\n return {\n setHtml(html: string): void {\n ed.commands.setContent(html);\n },\n getHtml(): string {\n return ed.getHTML();\n },\n getText(): string {\n return ed.getText();\n },\n focus(): void {\n ed.commands.focus(\"start\");\n },\n setCursor(pos: number): void {\n ed.commands.focus(\"start\");\n },\n root: editorEl,\n getScrollContainer(): HTMLElement {\n return editorEl;\n },\n onContentChange(handler: () => void): void {\n ed.on(\"update\", handler);\n },\n onKeyDown(handler: (e: KeyboardEvent) => void): void {\n editorEl.addEventListener(\"keydown\", handler);\n },\n insertTextAtCursor(text: string): void {\n ed.commands.insertContent(text);\n }\n };\n}\n\n// \u2500\u2500 Factory \u2500\u2500\n\nexport type EditorType = \"quill\" | \"tiptap\" | \"tinymce\";\n\nexport async function createEditor(container: HTMLElement, type: EditorType): Promise<MailxEditor> {\n if (type === \"tiptap\") {\n return createTiptapEditor(container);\n }\n if (type === \"tinymce\") {\n return createTinyMceEditor(container);\n }\n return createQuillEditor(container);\n}\n\n/** Default jsDelivr URL \u2014 works without an API key. Users can override\n * in Settings if they want Tiny Cloud or a self-hosted bundle. */\n// Local bundled TinyMCE \u2014 copied from node_modules/tinymce/ into\n// client/lib/tinymce/ by build-tinymce.js. Loaded with no internet\n// dependency at runtime. Relative to compose.html (`client/compose/`)\n// so it works under both msger custom protocol and Android file:// .\n// Users wanting Tiny Cloud premium override this via the\n// `mailx-tinymce-cdn` localStorage entry surfaced in Settings.\nconst DEFAULT_TINYMCE_CDN = \"../lib/tinymce/tinymce.min.js\";\n\n/** TinyMCE branch \u2014 dynamic-imports the rmf-tiny adapter so a missing\n * module only affects this branch. Throws on failure so the outer\n * compose.ts fallback can take over (and properly load Quill's\n * assets first). Don't fall back to Quill inline here \u2014 Quill's\n * global isn't loaded when type === \"tinymce\" was requested, so\n * `new Quill()` throws \"Quill is not defined\" and masks the real\n * rmf-tiny error in the log. */\nasync function createTinyMceEditor(container: HTMLElement): Promise<MailxEditor> {\n let cdnUrl = DEFAULT_TINYMCE_CDN;\n let apiKey: string | undefined;\n try {\n cdnUrl = localStorage.getItem(\"mailx-tinymce-cdn\") || DEFAULT_TINYMCE_CDN;\n apiKey = localStorage.getItem(\"mailx-tinymce-apikey\") || undefined;\n } catch { /* private mode \u2014 use defaults */ }\n // Build step copies node_modules/@bobfrankston/rmf-tiny/src/adapter.js\n // \u2192 client/lib/rmf-tiny.js. Bare-name `@bobfrankston/rmf-tiny` resolution\n // would target `node_modules/`, which msger's custom protocol can't reach\n // (outside contentDir=client/). The relative path stays inside the\n // served root.\n const m = (await import(\"../lib/rmf-tiny.js\" as any)) as {\n createTinyMceEditor(container: HTMLElement, opts: { cdnUrl?: string; apiKey?: string }): Promise<MailxEditor>;\n };\n const ed = await m.createTinyMceEditor(container, { cdnUrl, apiKey });\n return ed as unknown as MailxEditor;\n}\n", "/**\n * Compose window entry point.\n * Opened as a popup from the main mailx window.\n * Receives init data via window.opener.postMessage or URL params.\n */\n\nimport { createEditor, type MailxEditor } from \"./editor.js\";\nimport { getVersion, getSettings, getAccounts, searchContacts, sendMessage, saveDraft as apiSaveDraft, deleteDraft, logClientEvent, addPreferredContact, addToDenylist, openInWord, closeWordEdit, onEvent } from \"../lib/api-client.js\";\nimport { showContextMenu } from \"../components/context-menu.js\";\n\n// Very first line the iframe runs \u2014 if this doesn't reach Node, the iframe\n// itself isn't loading or the bridge is completely broken.\nlogClientEvent(\"compose-module-loaded\", { href: location.href, version: (window as any).mailxVersion || \"?\" });\n\n// Per-step timing \u2014 Ctrl+N \u2192 compose-visible has been a perceived-latency\n// problem; the `[compose-tick]` log lines isolate which stage actually\n// costs the time. `t0` is iframe-module-execution-start; each tick adds\n// the delta plus a stage label.\nconst _composeT0 = performance.now();\nfunction _ctick(label: string): void {\n const ms = (performance.now() - _composeT0).toFixed(0).padStart(5);\n try { logClientEvent(`compose-tick ${ms}ms ${label}`); } catch { /* */ }\n}\n_ctick(\"module body executing\");\n\n/** Close compose window */\nfunction closeCompose(): void {\n logClientEvent(\"compose-close\");\n // S61: Android WebView's window.close() override is unreliable inside\n // iframes \u2014 compose overlay sometimes stays visible after Send. Primary\n // path is a parent postMessage; window.close() is a fallback that also\n // works on desktop/msger where the override DOES fire reliably.\n try { parent.postMessage({ type: \"mailx-compose-close\" }, \"*\"); } catch { /* */ }\n try { window.close(); } catch { /* */ }\n}\n\ninterface ComposeInit {\n mode: string;\n accountId: string;\n to: { name: string; address: string }[];\n cc: { name: string; address: string }[];\n subject: string;\n bodyHtml: string;\n inReplyTo: string;\n references: string[];\n accounts: { id: string; name: string; email: string }[];\n fromAddress?: string;\n draftUid?: number;\n draftFolderId?: number;\n}\n\n// \u2500\u2500 Load editor scripts dynamically \u2500\u2500\n\nfunction loadScript(src: string): Promise<void> {\n return new Promise((resolve, reject) => {\n const s = document.createElement(\"script\");\n s.src = src;\n s.onload = () => resolve();\n s.onerror = () => reject(new Error(`Failed to load ${src}`));\n document.head.appendChild(s);\n });\n}\n\nfunction loadCSS(href: string): void {\n const link = document.createElement(\"link\");\n link.rel = \"stylesheet\";\n link.href = href;\n document.head.appendChild(link);\n}\n\nasync function loadEditorAssets(type: \"quill\" | \"tiptap\"): Promise<void> {\n if (type === \"tiptap\") {\n // tiptap UMD bundles from CDN\n const cdn = \"https://cdn.jsdelivr.net/npm\";\n await loadScript(`${cdn}/@tiptap/core@2/dist/index.umd.js`);\n await Promise.all([\n loadScript(`${cdn}/@tiptap/starter-kit@2/dist/index.umd.js`),\n loadScript(`${cdn}/@tiptap/extension-link@2/dist/index.umd.js`),\n loadScript(`${cdn}/@tiptap/extension-image@2/dist/index.umd.js`),\n loadScript(`${cdn}/@tiptap/extension-underline@2/dist/index.umd.js`),\n loadScript(`${cdn}/@tiptap/extension-placeholder@2/dist/index.umd.js`),\n ]);\n } else {\n // Quill \u2014 bundled locally by bin/build-quill.js to client/lib/quill/.\n // Earlier versions loaded from jsdelivr CDN, which added 100-500 ms\n // (network-dependent) to every compose open. Relative paths resolve\n // against `client/compose/`, so `../lib/quill/...` reaches the copy.\n loadCSS(\"../lib/quill/quill.snow.css\");\n await loadScript(\"../lib/quill/quill.js\");\n }\n}\n\n// \u2500\u2500 Determine editor type from settings \u2500\u2500\n//\n// Compose must open fast. The previous flow awaited getVersion() then\n// getSettings() sequentially before the editor was even loaded \u2014 any\n// service-side stall (busy sync, slow IMAP, hung OAuth refresh) turned\n// \"click Reply\" into a multi-second / multi-minute wait with a blank\n// compose window. Local-first: read the editor-type preference from a\n// tiny localStorage cache that we update whenever getSettings succeeds\n// in the background. Default to quill on first run / cache miss.\ntype EditorTypeChoice = \"quill\" | \"tiptap\" | \"tinymce\";\nlet editorType: EditorTypeChoice = \"quill\";\nlet appSettings: any = null;\ntry {\n const cached = localStorage.getItem(\"mailx-editor-type\");\n if (cached === \"tiptap\" || cached === \"quill\" || cached === \"tinymce\") editorType = cached;\n} catch { /* private-mode / SecurityError \u2014 default quill */ }\n// Refresh the cache asynchronously \u2014 doesn't block compose open.\n(async () => {\n try {\n appSettings = await getSettings();\n const settingValue = appSettings?.ui?.editor;\n const next: EditorTypeChoice =\n settingValue === \"tiptap\" ? \"tiptap\"\n : settingValue === \"tinymce\" ? \"tinymce\"\n : \"quill\";\n try { localStorage.setItem(\"mailx-editor-type\", next); } catch { /* */ }\n // Note: we don't hot-swap the editor if the preference changed while\n // compose was opening \u2014 the old type is already instantiated. Next\n // compose open will pick up the new preference.\n } catch { /* non-fatal */ }\n})();\n\n// Editor init \u2014 try the preferred type first; if its CDN/setup fails,\n// fall back to the OTHER editor before giving up on the toolbar entirely.\n// Both Quill and tiptap fetch from jsdelivr; transient CDN issues used to\n// drop the user into a plain-textarea fallback with no formatting controls.\n// Now we keep trying until either an editor with a toolbar comes up, or\n// both fail and we render the minimal contenteditable as last resort.\nlet editor: MailxEditor;\nconst container = document.getElementById(\"compose-editor\")!;\n\n/** Update the small badge in the compose toolbar showing which editor\n * is actually active (may differ from the user's setting if the\n * preferred one failed to load). Helpful for diagnosing paste / format\n * differences across Quill / tiptap / TinyMCE without diving into logs. */\nfunction setActiveEditorBadge(type: EditorTypeChoice | \"fallback\"): void {\n const el = document.getElementById(\"compose-editor-badge\");\n if (!el) return;\n const labels: Record<string, string> = {\n quill: \"Quill\",\n tiptap: \"tiptap\",\n tinymce: \"TinyMCE\",\n fallback: \"plain (fallback)\",\n };\n const docs: Record<string, string> = {\n quill: \"https://quilljs.com/docs/quickstart\",\n tiptap: \"https://tiptap.dev/docs/editor/introduction\",\n tinymce: \"https://www.tiny.cloud/docs/tinymce/6/\",\n fallback: \"https://github.com/BobFrankston/mailx/blob/master/app/docs/editor.md\",\n };\n el.textContent = labels[type] || type;\n el.dataset.editor = type;\n const url = docs[type];\n if (url) {\n el.style.cursor = \"pointer\";\n el.title = `Open ${labels[type]} documentation`;\n el.onclick = () => {\n const api = (window as any).mailxapi;\n if (api?.openExternal) api.openExternal(url);\n else window.open(url, \"_blank\", \"noopener,noreferrer\");\n };\n } else {\n el.style.cursor = \"\";\n el.title = \"\";\n el.onclick = null;\n }\n}\n\nasync function tryEditor(type: EditorTypeChoice): Promise<MailxEditor | null> {\n try {\n // tinymce loads itself via the rmf-tiny adapter (no jsdelivr asset).\n if (type !== \"tinymce\") await loadEditorAssets(type);\n } catch (e: any) {\n logClientEvent(\"compose-editor-assets-failed\", { type, error: String(e?.message || e) });\n return null;\n }\n container.classList.remove(\"editor-tiptap\", \"editor-quill\", \"editor-tinymce\");\n container.classList.add(`editor-${type}`);\n try {\n const ed = await createEditor(container, type);\n // TinyMCE: wire our JS spellchecker into the editor iframe so\n // right-click on a misspelled word offers suggestions + \"Add to\n // dictionary.\" WebView2's built-in spellcheck doesn't reliably\n // engage on msger-hosted iframes (see msger main.rs around\n // IsSpellcheckEnabled). nspell + dictionary-en gives us the\n // suggestions UX without depending on the host. Done at this\n // layer (not inside rmf-tiny) so the spellcheck stays a mailx\n // concern; other apps embedding rmf-tiny stay clean.\n if (type === \"tinymce\" && ed && (ed as any).nativeEditor) {\n const native = (ed as any).nativeEditor;\n const attach = () => {\n import(\"./spellcheck.js\").then(m => m.wireSpellcheck(native))\n .catch(e => console.error(\"[compose] spellcheck wire failed:\", e));\n };\n // TinyMCE's iframe exists immediately, but `getDoc()` returns\n // null until the editor's `init` event fires. Hook either path.\n if (native.initialized) attach();\n else native.on(\"init\", attach);\n }\n return ed;\n } catch (e: any) {\n logClientEvent(\"compose-editor-create-failed\", { type, error: String(e?.message || e) });\n return null;\n }\n}\n\nlet activeEditorType: EditorTypeChoice | \"fallback\" = editorType;\n_ctick(`editor load start (${editorType})`);\neditor = (await tryEditor(editorType))!;\n_ctick(`editor load end (${editorType}, ok=${!!editor})`);\nif (!editor) {\n // Preferred editor failed \u2014 try the other one before giving up.\n // For tinymce: fall back to quill since the user opted into tinymce\n // explicitly; if it isn't installed, give them the working default.\n const fallbackType: EditorTypeChoice = editorType === \"quill\" ? \"tiptap\" : \"quill\";\n logClientEvent(\"compose-editor-fallback-other\", { from: editorType, to: fallbackType });\n container.innerHTML = \"\"; // clear any partial render from the failed try\n editor = (await tryEditor(fallbackType))!;\n if (editor) {\n activeEditorType = fallbackType;\n setTimeout(() => showDraftStatus(`${editorType} editor unavailable \u2014 using ${fallbackType} instead.`, false), 0);\n }\n}\nif (!editor) {\n // Both editors failed \u2014 render a minimal contenteditable as last resort.\n // No toolbar, no rich-text shortcuts; user can still type. Status bar\n // surfaces the failure so the missing toolbar isn't a silent mystery.\n logClientEvent(\"compose-editor-create-failed-both\", {});\n container.innerHTML = `<div class=\"compose-fallback-editor\" contenteditable=\"true\" style=\"border:1px solid #c00;padding:8px;min-height:200px;background:#fff\" data-fallback=\"true\"></div>`;\n const fallback = container.querySelector<HTMLElement>(\".compose-fallback-editor\")!;\n editor = {\n root: fallback,\n setHtml: (html: string) => { fallback.innerHTML = html; },\n getHtml: () => fallback.innerHTML,\n getText: () => fallback.innerText,\n focus: () => fallback.focus(),\n setCursor: () => { /* no-op */ },\n getScrollContainer: () => fallback,\n onContentChange: (handler: () => void) => { fallback.addEventListener(\"input\", handler); },\n onKeyDown: (handler: (e: KeyboardEvent) => void) => { fallback.addEventListener(\"keydown\", handler); },\n insertTextAtCursor: (text: string) => {\n const sel = window.getSelection();\n if (sel && sel.rangeCount > 0) {\n const range = sel.getRangeAt(0);\n range.deleteContents();\n range.insertNode(document.createTextNode(text));\n } else {\n fallback.append(document.createTextNode(text));\n }\n },\n };\n activeEditorType = \"fallback\";\n setTimeout(() => showDraftStatus(`Both editors failed to load. Plain-text fallback in use. Check the log; CDN may be unreachable.`, true), 0);\n}\nsetActiveEditorBadge(activeEditorType);\n\n// Ctrl+scroll / Ctrl+= / Ctrl+- / Ctrl+0 zoom for the compose editor body.\n// Persists per-session in localStorage so zoom survives window pop/close cycles.\n(() => {\n const STORAGE_KEY = \"mailx.compose.zoom\";\n const MIN = 0.5, MAX = 3, STEP = 0.1;\n // Default 1.15 \u2014 matches the viewer's bumped default so reading and\n // composing have the same size baseline. Persisted value overrides.\n let zoom = parseFloat(localStorage.getItem(STORAGE_KEY) || \"1.15\") || 1.15;\n const applyZoom = () => {\n container.style.fontSize = `${zoom}em`;\n localStorage.setItem(STORAGE_KEY, String(zoom));\n };\n applyZoom();\n container.addEventListener(\"wheel\", (e: WheelEvent) => {\n if (!e.ctrlKey) return;\n e.preventDefault();\n const delta = e.deltaY < 0 ? STEP : -STEP;\n zoom = Math.min(MAX, Math.max(MIN, Math.round((zoom + delta) * 10) / 10));\n applyZoom();\n }, { passive: false });\n document.addEventListener(\"keydown\", (e: KeyboardEvent) => {\n if (!(e.ctrlKey || e.metaKey)) return;\n if (e.key === \"=\" || e.key === \"+\") { zoom = Math.min(MAX, zoom + STEP); applyZoom(); e.preventDefault(); }\n else if (e.key === \"-\") { zoom = Math.max(MIN, zoom - STEP); applyZoom(); e.preventDefault(); }\n else if (e.key === \"0\") { zoom = 1; applyZoom(); e.preventDefault(); }\n });\n})();\n\n// \u2500\u2500 Populate from init data \u2500\u2500\n\n// From field is a free-text input with a <datalist> of known accounts. The\n// user can pick a preset or type an arbitrary \"Name <addr@domain>\" \u2014 no\n// separate \"Other...\" escape hatch, no hidden custom input toggle.\nconst fromInput = document.getElementById(\"compose-from-input\") as HTMLInputElement;\nconst fromOptions = document.getElementById(\"compose-from-options\") as HTMLDataListElement;\n// Address fields are textareas (not inputs) so they wrap to multiple lines\n// when the recipient list gets long \u2014 single-line inputs scrolled off-screen\n// horizontally. JS auto-grows their height on input below.\nconst toInput = document.getElementById(\"compose-to\") as HTMLTextAreaElement;\nconst ccInput = document.getElementById(\"compose-cc\") as HTMLTextAreaElement;\nconst bccInput = document.getElementById(\"compose-bcc\") as HTMLTextAreaElement;\nconst subjectInput = document.getElementById(\"compose-subject\") as HTMLInputElement;\n\n/** Resize an address textarea to fit its content. Called on input + after\n * programmatic value sets (applyInit). Caps at 8 lines \u2014 beyond that the\n * field scrolls to keep the overall compose window manageable. */\nfunction autoGrowAddrInput(el: HTMLTextAreaElement | null): void {\n if (!el) return;\n el.style.height = \"auto\";\n const max = 8 * 22; // ~8 lines \u00D7 line-height\n el.style.height = Math.min(el.scrollHeight, max) + \"px\";\n if (el.scrollHeight > max) el.style.overflowY = \"auto\";\n else el.style.overflowY = \"hidden\";\n}\nfor (const el of [toInput, ccInput, bccInput]) {\n el?.addEventListener(\"input\", () => autoGrowAddrInput(el));\n // Treat Enter as a recipient separator (split on comma OR newline at\n // send time), but don't insert a literal newline \u2014 that'd break the\n // implicit \"comma-separated list\" assumption downstream. Replace with\n // a comma + space.\n el?.addEventListener(\"keydown\", (e) => {\n if (e.key === \"Enter\" && !e.shiftKey && !e.ctrlKey && !e.metaKey) {\n e.preventDefault();\n const start = el.selectionStart || 0;\n const v = el.value;\n el.value = v.slice(0, start).replace(/[,\\s]+$/, \"\") + \", \" + v.slice(start).replace(/^[,\\s]+/, \"\");\n el.selectionStart = el.selectionEnd = start + 2;\n autoGrowAddrInput(el);\n }\n });\n}\n\n/** Registered accounts \u2014 populated once at init time, used to map the From\n * input value back to an account id on send. */\ninterface ComposeAccount { id: string; name: string; label?: string; email: string; defaultSend?: boolean; }\nlet knownAccounts: ComposeAccount[] = [];\n\n// \u2500\u2500 AI ghost text autocomplete \u2500\u2500\nif (appSettings?.autocomplete?.enabled && appSettings.autocomplete.provider !== \"off\") {\n import(\"./ghost-text.js\").then(({ initGhostText }) => {\n initGhostText(editor, {\n getSubject: () => subjectInput.value,\n getTo: () => toInput.value,\n }, { debounceMs: appSettings.autocomplete.debounceMs || 600 });\n }).catch(() => { /* autocomplete unavailable */ });\n}\n\n/** Format an account for the From field: \"Name <email>\". */\nfunction formatAccountFrom(acct: ComposeAccount): string {\n return `${acct.name} <${acct.email}>`;\n}\n\nconst FROM_HISTORY_KEY = \"mailx-from-history\"; // up to 20 recent manual From entries\nconst FROM_HISTORY_MAX = 20;\n\nfunction loadFromHistory(): string[] {\n try { return JSON.parse(localStorage.getItem(FROM_HISTORY_KEY) || \"[]\"); } catch { return []; }\n}\nfunction recordFromHistory(value: string): void {\n const v = (value || \"\").trim();\n if (!v) return;\n try {\n const list = loadFromHistory().filter(x => x !== v);\n list.unshift(v);\n localStorage.setItem(FROM_HISTORY_KEY, JSON.stringify(list.slice(0, FROM_HISTORY_MAX)));\n } catch { /* private mode */ }\n}\n\n/** Populate the From <datalist> with one entry per known account plus any\n * manually-typed addresses from localStorage history. Account entries rank\n * first; history entries get an \"(used before)\" label so the user can tell\n * which ones are real accounts vs free-form aliases. */\nfunction populateFromOptions(accounts: ComposeAccount[], selectedId?: string): void {\n knownAccounts = accounts;\n fromOptions.innerHTML = \"\";\n const seenValues = new Set<string>();\n for (const acct of accounts) {\n const opt = document.createElement(\"option\");\n opt.value = formatAccountFrom(acct);\n const tag = acct.label || acct.name;\n opt.label = tag;\n fromOptions.appendChild(opt);\n seenValues.add(opt.value);\n }\n // Custom From history \u2014 addresses the user has typed before that don't\n // match any known account (aliases, +tag addresses, one-off identities).\n // Stored in localStorage because they're inherently per-device preferences;\n // moving them to an account profile would be a different feature.\n for (const value of loadFromHistory()) {\n if (seenValues.has(value)) continue;\n const opt = document.createElement(\"option\");\n opt.value = value;\n opt.label = \"(used before)\";\n fromOptions.appendChild(opt);\n }\n if (!fromInput.value) {\n const selected = (selectedId && accounts.find(a => a.id === selectedId)) ||\n accounts.find(a => a.defaultSend) ||\n accounts[0];\n if (selected) fromInput.value = formatAccountFrom(selected);\n }\n}\n\n/** Parse the current From input into { name, address } for header building. */\nfunction parseFromInput(): { name: string; address: string } {\n const raw = fromInput.value.trim();\n const match = raw.match(/^(.+?)\\s*<(.+?)>$/);\n if (match) return { name: match[1].trim(), address: match[2].trim() };\n return { name: \"\", address: raw };\n}\n\n/** Match the From input's address against the known accounts table and\n * return that account's id. Used by send() / saveDraft() to decide which\n * account to send through. Falls back to defaultSend, then first account. */\nfunction getFromAccountId(): string {\n const { address } = parseFromInput();\n const lower = address.toLowerCase();\n // Exact match wins\n const exact = knownAccounts.find(a => a.email.toLowerCase() === lower);\n if (exact) return exact.id;\n // Same-domain match \u2014 handles +tag aliases and identity addresses\n const domain = lower.split(\"@\")[1] || \"\";\n if (domain) {\n const sameDomain = knownAccounts.find(a => a.email.toLowerCase().endsWith(\"@\" + domain));\n if (sameDomain) return sameDomain.id;\n }\n // Give up \u2014 use default send account or the first account\n const def = knownAccounts.find(a => a.defaultSend) || knownAccounts[0];\n return def?.id || \"\";\n}\n\n/** Get the raw From header string (\"Name <addr>\"). */\nfunction getFromAddress(): string {\n return fromInput.value.trim();\n}\n\n/** Smart tab \u2014 skip to next empty field, ending at body */\nfunction smartTab(current: HTMLInputElement): void {\n const fields = [toInput, ccInput, bccInput, subjectInput];\n const currentIdx = fields.indexOf(current);\n // Look for next empty field after current\n for (let i = currentIdx + 1; i < fields.length; i++) {\n if (!fields[i].value.trim()) {\n fields[i].focus();\n return;\n }\n }\n // All fields filled or past the end \u2014 go to editor body\n editor.focus();\n}\n\n// \u2500\u2500 Autocomplete \u2500\u2500\n\n/** Right-click on an autocomplete row \u2192 contextual actions. Two paths:\n * - Add to preferred (small modal: name / email / source-tag / org \u2192 write to\n * contacts.jsonc#preferred[])\n * - Never suggest this address (write to contacts.jsonc#denylist[]; the\n * service-side handler purges any matching discovered rows on apply) */\nfunction showAutocompleteContextMenu(\n e: MouseEvent,\n row: { name: string; email: string; source: string },\n): void {\n showContextMenu(e.clientX, e.clientY, [\n {\n label: \"Add to preferred\u2026\",\n action: () => openAddToPreferredModal(row),\n },\n {\n label: \"Never suggest this address\",\n action: async () => {\n try {\n await addToDenylist(row.email);\n } catch (err: any) {\n alert(`Failed to add to denylist: ${err?.message || err}`);\n }\n },\n },\n {\n // Punt real Google-contact editing to Google's own UI, which\n // already handles add-to-new / add-to-existing / merge \u2014 no\n // in-app picker or People API write scope needed.\n label: \"Open in Google Contacts\u2026\",\n tooltip: \"Add as a new contact, attach to an existing one, or merge \u2014 in Google's own UI.\",\n action: () => {\n window.open(`https://contacts.google.com/search/${encodeURIComponent(row.name || row.email)}`, \"_blank\");\n },\n },\n ]);\n}\n\nfunction openAddToPreferredModal(prefill: { name: string; email: string; source: string }): void {\n const overlay = document.createElement(\"div\");\n overlay.className = \"modal-overlay\";\n overlay.innerHTML = `\n <div class=\"modal\" role=\"dialog\" aria-label=\"Add to preferred contacts\">\n <h3>Add to preferred</h3>\n <p class=\"muted\">Saved to <code>contacts.jsonc</code> on your shared drive.</p>\n <label>Name <input type=\"text\" id=\"pf-name\" /></label>\n <label>Email <input type=\"email\" id=\"pf-email\" /></label>\n <label>Source tag <input type=\"text\" id=\"pf-source\" placeholder=\"(optional \u2014 e.g. work, family)\" /></label>\n <label>Organization <input type=\"text\" id=\"pf-org\" placeholder=\"(optional)\" /></label>\n <div class=\"modal-actions\">\n <button id=\"pf-cancel\">Cancel</button>\n <button id=\"pf-save\" class=\"primary\">Save</button>\n </div>\n </div>\n `;\n document.body.appendChild(overlay);\n (overlay.querySelector(\"#pf-name\") as HTMLInputElement).value = prefill.name || \"\";\n (overlay.querySelector(\"#pf-email\") as HTMLInputElement).value = prefill.email || \"\";\n // Pre-fill source from existing tag if it's already a custom one (not a system source).\n const sysSources = new Set([\"google\", \"discovered\", \"preferred\", \"\"]);\n const initSource = sysSources.has(prefill.source || \"\") ? \"\" : prefill.source;\n (overlay.querySelector(\"#pf-source\") as HTMLInputElement).value = initSource;\n const close = () => overlay.remove();\n overlay.querySelector(\"#pf-cancel\")!.addEventListener(\"click\", close);\n overlay.addEventListener(\"click\", (ev) => { if (ev.target === overlay) close(); });\n overlay.querySelector(\"#pf-save\")!.addEventListener(\"click\", async () => {\n const name = (overlay.querySelector(\"#pf-name\") as HTMLInputElement).value.trim();\n const email = (overlay.querySelector(\"#pf-email\") as HTMLInputElement).value.trim();\n const source = (overlay.querySelector(\"#pf-source\") as HTMLInputElement).value.trim();\n const org = (overlay.querySelector(\"#pf-org\") as HTMLInputElement).value.trim();\n if (!email) { alert(\"Email is required.\"); return; }\n try {\n await addPreferredContact({ name, email, source, organization: org });\n close();\n } catch (err: any) {\n alert(`Failed to save: ${err?.message || err}`);\n }\n });\n (overlay.querySelector(\"#pf-name\") as HTMLInputElement).focus();\n}\n\nfunction setupAutocomplete(input: HTMLInputElement | HTMLTextAreaElement): void {\n let dropdown: HTMLDivElement | null = null;\n let activeIndex = -1;\n let debounce: ReturnType<typeof setTimeout>;\n\n function closeDropdown(): void {\n if (dropdown) { dropdown.remove(); dropdown = null; }\n activeIndex = -1;\n }\n\n function getCaretToken(): string {\n const val = input.value;\n const caret = input.selectionStart ?? val.length;\n const { start, end } = tokenSpanAtCaret(val, caret);\n return val.substring(start, end).trim();\n }\n\n function replaceCaretToken(replacement: string): void {\n const val = input.value;\n const caret = input.selectionStart ?? val.length;\n const { start, end } = tokenSpanAtCaret(val, caret);\n const before = val.substring(0, start);\n const after = val.substring(end);\n // A space after a preceding comma; a trailing \", \" only when this is\n // the final token, so the user can carry on with the next recipient\n // without disturbing recipients that already follow.\n const lead = before.length && !before.endsWith(\" \") ? \" \" : \"\";\n const isLast = after.trim() === \"\";\n const insert = lead + replacement + (isLast ? \", \" : \"\");\n input.value = before + insert + after;\n const pos = before.length + insert.length;\n closeDropdown();\n input.focus();\n input.setSelectionRange(pos, pos);\n }\n\n input.addEventListener(\"input\", () => {\n clearTimeout(debounce);\n const token = getCaretToken();\n if (token.length < 1) { closeDropdown(); return; }\n\n debounce = setTimeout(() => {\n // rAF yield before hitting the DB \u2014 S60 mitigation, same reason\n // as the draft-save path. The 200 ms timer already deferred past\n // the input burst; this extra frame lets the last keystroke paint.\n requestAnimationFrame(async () => {\n try {\n const results = await searchContacts(token) as { name: string; email: string; source: string; sources?: string[]; useCount: number }[];\n if (results.length === 0) { closeDropdown(); return; }\n\n closeDropdown();\n dropdown = document.createElement(\"div\");\n dropdown.className = \"ac-dropdown\";\n activeIndex = 0; // first item highlighted by default\n\n for (let i = 0; i < results.length; i++) {\n const r = results[i];\n const item = document.createElement(\"div\");\n item.className = `ac-item${i === 0 ? \" ac-active\" : \"\"}`;\n\n // Text column (name / email / source badge).\n const main = document.createElement(\"div\");\n main.className = \"ac-item-main\";\n\n const nameEl = document.createElement(\"span\");\n nameEl.className = \"ac-item-name\";\n nameEl.textContent = r.name || r.email;\n\n const emailEl = document.createElement(\"span\");\n emailEl.className = \"ac-item-email\";\n emailEl.textContent = r.email;\n\n // Source badge(s) \u2014 shows where this row came from. When\n // the same address is in multiple sources (e.g. google AND\n // discovered after Google Contacts sync), the merged list\n // is displayed comma-separated. Custom user tags from\n // contacts.jsonc preferred entries (`source: \"work\"`,\n // `source: \"family\"`) flow through verbatim alongside the\n // system sources google / discovered / preferred.\n const sourceEl = document.createElement(\"span\");\n sourceEl.className = \"ac-item-source\";\n const sourceList = (r.sources && r.sources.length)\n ? r.sources\n : (r.source ? [r.source] : []);\n sourceEl.textContent = sourceList.join(\", \");\n\n main.appendChild(nameEl);\n if (r.name) main.appendChild(emailEl);\n if (sourceList.length) main.appendChild(sourceEl);\n\n // Visible per-row prefer / ignore buttons (shown on\n // hover). A plain click \u2014 no dependence on right-click /\n // the context menu, which is undiscoverable and flaky.\n // \u2605 \u2192 contacts.jsonc#preferred[]\n // \u2298 \u2192 contacts.jsonc#denylist[] (purges discovered)\n const actions = document.createElement(\"div\");\n actions.className = \"ac-item-actions\";\n const mkBtn = (glyph: string, title: string, run: () => Promise<unknown>): HTMLButtonElement => {\n const b = document.createElement(\"button\");\n b.type = \"button\";\n b.className = \"ac-item-btn\";\n b.textContent = glyph;\n b.title = title;\n // Block the row's mousedown-to-select so the button\n // acts on its own without inserting the address.\n b.addEventListener(\"mousedown\", (e) => { e.preventDefault(); e.stopPropagation(); });\n b.addEventListener(\"click\", async (e) => {\n e.preventDefault();\n e.stopPropagation();\n try { await run(); } catch (err: any) { alert(`Failed: ${err?.message || err}`); }\n closeDropdown();\n });\n return b;\n };\n actions.appendChild(mkBtn(\"\u2605\", \"Prefer this contact\", () => addPreferredContact({ name: r.name, email: r.email, source: \"\", organization: \"\" })));\n actions.appendChild(mkBtn(\"\u2298\", \"Never suggest this address\", () => addToDenylist(r.email)));\n // First-pass alias handling: instead of an in-app\n // add-to-existing-vs-new picker, just open Google Contacts\n // searched for this contact so the user can merge an alias\n // / create / edit there with Google's own UI.\n actions.appendChild(mkBtn(\"\u2197\", \"Open in Google Contacts (add, edit, merge aliases)\", async () => {\n window.open(`https://contacts.google.com/search/${encodeURIComponent(r.name || r.email)}`, \"_blank\");\n }));\n\n item.appendChild(main);\n item.appendChild(actions);\n\n item.addEventListener(\"mousedown\", (e) => {\n // preventDefault on EVERY button \u2014 this is what keeps the\n // To input focused. Without it a right-click blurs the\n // input, blur schedules closeDropdown() 150ms later, and\n // a deliberate right-click then fires `contextmenu` after\n // the dropdown is already gone \u2014 landing on the editor\n // underneath (Bob 2026-05-16). Only the LEFT button then\n // proceeds to select-and-close; right-click falls through\n // to the row's contextmenu handler with the row intact.\n e.preventDefault();\n if (e.button !== 0) return;\n const display = formatRecipient(r.name, r.email);\n replaceCaretToken(display);\n });\n\n // Right-click still offers the same actions (plus the\n // full \"Add to preferred\u2026\" modal) as a bonus when it works.\n item.addEventListener(\"contextmenu\", (e) => {\n e.preventDefault();\n e.stopPropagation();\n showAutocompleteContextMenu(e as MouseEvent, r);\n });\n\n dropdown.appendChild(item);\n }\n\n input.parentElement!.appendChild(dropdown);\n\n // Flip the dropdown above the input if there's no room below.\n // Compose can be a small popup window (or the viewport can be\n // short on phones / split screens) \u2014 when To is near the\n // bottom, the default `top: 100%` drop spills off-screen and\n // most matches are unreachable. Measure on insertion: if the\n // dropdown's bottom would clear the viewport AND there's more\n // room above the input than below, anchor it to the top of\n // the field instead.\n requestAnimationFrame(() => {\n if (!dropdown) return;\n const rect = dropdown.getBoundingClientRect();\n const inputRect = input.getBoundingClientRect();\n const spaceBelow = window.innerHeight - inputRect.bottom;\n const spaceAbove = inputRect.top;\n if (rect.bottom > window.innerHeight && spaceAbove > spaceBelow) {\n dropdown.style.top = \"auto\";\n dropdown.style.bottom = \"100%\";\n }\n });\n } catch { /* ignore */ }\n });\n }, 200);\n });\n\n input.addEventListener(\"keydown\", (e: Event) => {\n if (!dropdown) return;\n const items = dropdown.querySelectorAll(\".ac-item\");\n const ke = e as KeyboardEvent;\n if (ke.key === \"ArrowDown\") {\n e.preventDefault();\n activeIndex = Math.min(activeIndex + 1, items.length - 1);\n items.forEach((el, i) => el.classList.toggle(\"ac-active\", i === activeIndex));\n } else if (ke.key === \"ArrowUp\") {\n e.preventDefault();\n activeIndex = Math.max(activeIndex - 1, 0);\n items.forEach((el, i) => el.classList.toggle(\"ac-active\", i === activeIndex));\n } else if (ke.key === \"Tab\" || ke.key === \"Enter\") {\n if (items.length > 0) {\n e.preventDefault();\n const idx = activeIndex >= 0 ? activeIndex : 0;\n (items[idx] as HTMLElement).dispatchEvent(new MouseEvent(\"mousedown\"));\n // Stay in field \u2014 user may want to add more addresses\n return;\n }\n } else if (ke.key === \"Escape\") {\n closeDropdown();\n }\n });\n\n input.addEventListener(\"blur\", () => {\n setTimeout(closeDropdown, 150);\n });\n}\n\nsetupAutocomplete(toInput);\nsetupAutocomplete(ccInput);\nsetupAutocomplete(bccInput);\n\n/** Split a recipient string on TOP-LEVEL commas only \u2014 a comma inside a\n * quoted display name (`\"Frankston, Bob\" <bob@x.com>`) is part of the name,\n * not a separator. The naive `s.split(\",\")` parsed such a name as two\n * recipients (Bob 2026-05-17). */\nfunction splitRecipients(s: string): string[] {\n const out: string[] = [];\n let cur = \"\", inQuote = false;\n for (const c of s) {\n if (c === \"\\\"\") { inQuote = !inQuote; cur += c; }\n else if (c === \",\" && !inQuote) { out.push(cur); cur = \"\"; }\n else cur += c;\n }\n out.push(cur);\n return out;\n}\n\n/** [start, end) span of the recipient token containing `caret`. Tokens are\n * separated by unquoted commas (commas inside \"quoted names\" don't split).\n * Autocomplete must match the token the cursor is in \u2014 not just the final\n * one \u2014 otherwise typing a name before an existing comma matches the wrong\n * recipient. */\nfunction tokenSpanAtCaret(s: string, caret: number): { start: number; end: number } {\n let inQuote = false;\n let start = 0;\n let end = s.length;\n for (let i = 0; i < s.length; i++) {\n if (s[i] === \"\\\"\") inQuote = !inQuote;\n else if (s[i] === \",\" && !inQuote) {\n if (i < caret) start = i + 1;\n else { end = i; break; }\n }\n }\n return { start, end };\n}\n\n/** `Name <email>` \u2014 display name quoted when it contains a comma or quote,\n * so the recipient survives the comma-separated round-trip in the field. */\nfunction formatRecipient(name: string, address: string): string {\n if (!name) return address;\n const quoted = /[,\"]/.test(name) ? `\"${name.replace(/\"/g, \"'\")}\"` : name;\n return `${quoted} <${address}>`;\n}\n\nfunction formatAddrs(addrs: { name: string; address: string }[]): string {\n return addrs.map(a => formatRecipient(a.name, a.address)).join(\", \");\n}\n\nfunction parseAddrs(s: string): { name: string; address: string }[] {\n if (!s.trim()) return [];\n // Split on TOP-LEVEL commas only (see splitRecipients) so a quoted\n // comma-bearing name stays one recipient. Trailing commas / stray\n // whitespace are dropped \u2014 no phantom addresses that fail validation.\n return splitRecipients(s)\n .map(p => p.trim())\n .filter(p => p.length > 0)\n .map(part => {\n const match = part.match(/^(.+?)\\s*<(.+?)>$/);\n if (match) {\n let name = match[1].trim();\n if (name.length >= 2 && name.startsWith(\"\\\"\") && name.endsWith(\"\\\"\")) {\n name = name.slice(1, -1); // unwrap a quoted display name\n }\n return { name, address: match[2].trim() };\n }\n return { name: \"\", address: part };\n });\n}\n\n/** Expand any group names in a recipient string against the user's\n * contacts.jsonc \u2192 groups map. Recipients that match a group name (case\n * insensitive) get replaced by their member list, recursively. Unresolved\n * tokens are left in place so the user sees them and can fix the typo.\n * Loaded ASYNCHRONOUSLY at compose-init time \u2014 Send must NEVER await\n * this. Earlier `await loadGroupsMap()` inside the click handler hung\n * Send for 61 seconds (Bob 2026-05-09 00:16:57): the GDrive read of\n * contacts.jsonc was on a slow connection. Bob then clicked Send a\n * second time at 00:23, producing two real outgoing messages with\n * different Message-IDs because each Send call assigned its own. Send\n * must be instantaneous; group expansion must not gate it.\n *\n * localStorage is the synchronous tier \u2014 populated by the background\n * refresh, read instantly by `expandGroups`. If localStorage has\n * nothing yet (first ever launch on this device), expandGroups is a\n * no-op and the user's literal recipient string flows through. The\n * background refresh seeds localStorage on first success. */\nconst GROUPS_LS_KEY = \"mailx-groups-cache-v1\";\nlet _groupsCache: Record<string, string[]> = readGroupsFromLocalStorage();\n\nfunction readGroupsFromLocalStorage(): Record<string, string[]> {\n try {\n const raw = localStorage.getItem(GROUPS_LS_KEY);\n if (raw) return JSON.parse(raw) || {};\n } catch { /* */ }\n return {};\n}\n\nfunction writeGroupsToLocalStorage(groups: Record<string, string[]>): void {\n try { localStorage.setItem(GROUPS_LS_KEY, JSON.stringify(groups)); }\n catch { /* localStorage full or private mode */ }\n}\n\n/** Background refresh of the groups map. Fires once at compose init,\n * results land in `_groupsCache` and localStorage for next time. */\nfunction refreshGroupsMapInBackground(): void {\n (async () => {\n try {\n const { readJsoncFile } = await import(\"../lib/api-client.js\");\n const r = await readJsoncFile(\"contacts.jsonc\");\n if (!r?.content) return;\n const stripped = r.content.replace(/^\\s*\\/\\/.*$/gm, \"\").replace(/,(\\s*[}\\]])/g, \"$1\");\n const cfg = JSON.parse(stripped);\n const groups = (cfg && typeof cfg.groups === \"object\" && cfg.groups) ? cfg.groups : {};\n _groupsCache = groups;\n writeGroupsToLocalStorage(groups);\n } catch { /* leave existing cache in place */ }\n })();\n}\n// Kick off the background refresh once when this module loads.\nrefreshGroupsMapInBackground();\n\n/** Expand the raw recipient string against the cached groups map.\n * SYNCHRONOUS by design \u2014 Send must not wait on cloud I/O for group\n * expansion. If no groups are cached, the input passes through. */\nfunction expandGroups(raw: string): string {\n if (!raw.trim()) return raw;\n if (Object.keys(_groupsCache).length === 0) return raw;\n // Lazy-load the expansion utility synchronously isn't possible\n // since it's a separate package \u2014 fall back to inline expansion.\n // Same algorithm as `expandRecipients`: split on comma, look up\n // each token (case-insensitive), keep the original on miss.\n const groupsLc: Record<string, string[]> = {};\n for (const k of Object.keys(_groupsCache)) groupsLc[k.toLowerCase()] = _groupsCache[k];\n const tokens = raw.split(\",\").map(s => s.trim()).filter(Boolean);\n const out: string[] = [];\n for (const tok of tokens) {\n const members = groupsLc[tok.toLowerCase()];\n if (members && Array.isArray(members)) out.push(...members);\n else out.push(tok);\n }\n return out.join(\", \");\n}\n\nfunction applyInit(init: ComposeInit): void {\n // Populate the From datalist with known accounts\n populateFromOptions(init.accounts, init.accountId);\n\n // If the reply has a specific identity address (alias / +tag), set it\n // as the From value directly \u2014 overrides the account default.\n if (init.fromAddress) {\n const account = init.accounts.find(a => a.id === init.accountId);\n const displayName = account?.name || \"\";\n fromInput.value = displayName ? `${displayName} <${init.fromAddress}>` : init.fromAddress;\n }\n\n toInput.value = formatAddrs(init.to);\n ccInput.value = formatAddrs(init.cc);\n subjectInput.value = init.subject;\n // Resize the textareas to match the freshly-loaded recipient lists \u2014\n // without this the textareas stay 1-row even when the To: contains a\n // 200-character reply-all address list.\n autoGrowAddrInput(toInput);\n autoGrowAddrInput(ccInput);\n autoGrowAddrInput(bccInput);\n\n // Auto-expand Cc row if the init already has Cc content (reply-all, draft-with-cc)\n if (ccInput.value.trim()) {\n const ccRowEl = document.getElementById(\"compose-cc-row\");\n const ccBtn = document.getElementById(\"btn-toggle-cc\");\n if (ccRowEl) ccRowEl.hidden = false;\n if (ccBtn) ccBtn.classList.add(\"active\");\n } else if (init.to && init.to.length === 1) {\n // Q49: heuristic auto-expand \u2014 when replying/composing to a single\n // recipient, check sent-history. If the user has previously Cc'd or\n // Bcc'd anyone on a message to this recipient, expand the matching\n // row (empty, just visible) so they're prompted to fill it.\n // Fire-and-forget; if the service call fails or the user starts\n // typing manually before it resolves, the answer doesn't matter.\n const firstEmail = init.to[0]?.address || \"\";\n if (firstEmail) {\n import(\"../lib/api-client.js\").then(({ hasCcHistoryTo, hasBccHistoryTo }) => {\n hasCcHistoryTo(firstEmail)\n .then(res => {\n if (!res?.hasCc) return;\n const ccRowEl = document.getElementById(\"compose-cc-row\");\n const ccBtn = document.getElementById(\"btn-toggle-cc\");\n if (ccRowEl?.hidden && !ccInput.value) {\n ccRowEl.hidden = false;\n ccBtn?.classList.add(\"active\");\n }\n })\n .catch(() => { /* non-fatal hint */ });\n hasBccHistoryTo(firstEmail)\n .then(res => {\n if (!res?.hasBcc) return;\n const bccRowEl = document.getElementById(\"compose-bcc-row\");\n const bccBtn = document.getElementById(\"btn-toggle-bcc\");\n if (bccRowEl?.hidden && !bccInput.value) {\n bccRowEl.hidden = false;\n bccBtn?.classList.add(\"active\");\n }\n })\n .catch(() => { /* non-fatal hint */ });\n });\n }\n }\n\n // C42: append the account's signature (if configured) BEFORE rendering\n // the body. Two sources, in priority order:\n // 1. New `sig: { text, html? }` object \u2014 applied to NEW messages only.\n // `text` is HTML-escaped (newlines \u2192 <br>) unless `html: true` is set\n // (reserved for future use; currently `html` is ignored and text is\n // always escaped).\n // 2. Legacy `signature: string` \u2014 HTML, applied to new + reply + forward.\n //\n // Drafts are skipped \u2014 the signature is already baked into the saved body.\n // Editing an existing draft also skipped.\n let bodyToRender = init.bodyHtml || \"\";\n\n // Preserve hard line breaks inside <pre> blocks. Thunderbird-authored\n // drafts and reply quotes wrap quoted text in `<pre wrap=\"\" class=\"moz-quote-pre\">`\n // with literal `\\n` line breaks. Browsers preserve those breaks because\n // `<pre>` is whitespace-significant, but the rich-text editor (Quill /\n // TinyMCE) normalises `<pre>` to a flow block on import and silently\n // collapses internal newlines into spaces \u2014 so a draft that wrapped\n // correctly on disk shows up as one long unbroken paragraph in compose.\n // Bob 2026-05-13: \"lost the line wrapping on a draft I'm editing.\" Fix:\n // before handing the body to the editor, convert `\\n` inside any `<pre>`\n // block to `<br>` so the visual breaks survive whatever the editor's\n // import normaliser does. The `<pre>` element stays; we only mutate its\n // contents.\n bodyToRender = bodyToRender.replace(\n /<pre\\b([^>]*)>([\\s\\S]*?)<\\/pre>/gi,\n (_match, attrs, inner) =>\n `<pre${attrs}>${inner.replace(/\\r\\n|\\r|\\n/g, \"<br>\")}</pre>`,\n );\n\n const acct: any = init.accounts.find(a => a.id === init.accountId);\n const isReplyForward = init.mode === \"reply\" || init.mode === \"replyAll\"\n || init.mode === \"forward\";\n\n // Signature \u2014 applies to new mail AND replies/forwards (drafts already\n // have it baked into the saved body). New format `acct.sig` {text,html}\n // takes precedence; legacy `acct.signature` (raw HTML) is the fallback.\n // Earlier the new-format branch was gated on \"new mail only\", so anyone\n // using the new sig format got NO signature on a reply (Bob 2026-05-18).\n if (init.mode !== \"draft\" && !init.draftUid) {\n let sigHtml = \"\";\n if (acct?.sig?.text) {\n sigHtml = acct.sig.html\n ? acct.sig.text // trusted raw HTML\n : escapeHtml(acct.sig.text).replace(/\\n/g, \"<br>\");\n } else if (acct?.signature) {\n sigHtml = acct.signature;\n }\n if (sigHtml) {\n const sigBlock = `<br><br>-- <br>${sigHtml}`;\n bodyToRender = isReplyForward\n ? `<br>${sigBlock}<br>${bodyToRender}` // sig above the quoted block\n : `${bodyToRender}${sigBlock}`; // sig at the end for new mail\n }\n }\n if (bodyToRender) {\n editor.setHtml(bodyToRender);\n editor.setCursor(0);\n }\n\n // If resuming a draft, track its UID for deletion after send\n if (init.draftUid) {\n draftUid = init.draftUid;\n }\n // Capture reply/forward linkage. init.inReplyTo is the parent's Message-ID,\n // init.references is the full ancestry chain (parent's existing References\n // header plus the parent's own Message-ID). Both set by app.ts:openCompose\n // for mode === \"reply\" | \"replyAll\" | \"forward\".\n replyInReplyTo = init.inReplyTo || \"\";\n replyReferences = init.references || [];\n\n setComposeTitle(init.subject || \"\");\n\n // Focus first empty field: To \u2192 Subject \u2192 body\n if (!toInput.value) toInput.focus();\n else if (!subjectInput.value) subjectInput.focus();\n else editor.focus();\n\n // Record the post-init content so autosave / hasContent only react to\n // genuine user edits. Without this baseline, a reply opens with quoted\n // body + signature already populated and autosave fires within seconds\n // even if the user typed nothing \u2014 producing the \"draft droppings\"\n // that pile up in the Drafts folder.\n recordContentBaseline();\n}\n\n// Q68: dirty marker (\u2022) in the window title until the next successful save.\nlet composeDirty = false;\nfunction setComposeTitle(subject: string): void {\n const base = subject ? `${subject} - Compose` : \"Compose - mailx\";\n document.title = composeDirty ? `\u2022 ${base}` : base;\n}\nfunction markComposeDirty(): void {\n if (composeDirty) return;\n composeDirty = true;\n setComposeTitle(subjectInput?.value || \"\");\n}\nfunction markComposeClean(): void {\n if (!composeDirty) return;\n composeDirty = false;\n setComposeTitle(subjectInput?.value || \"\");\n}\n\n// \u2500\u2500 Compose state (declared before init so the async IIFE can reference them) \u2500\u2500\n\nconst DRAFT_INPUT_DEBOUNCE_MS = 800; // save ~0.8s after the last keystroke\nconst DRAFT_INTERVAL_MS = 2000; // safety-net interval save\nlet draftUid: number | null = null;\nlet draftId: string | null = null; // stable ID for dedup when APPENDUID unavailable\n// Reply / Reply-All / Forward \u2014 captured at init time, forwarded to the daemon\n// so the outgoing MIME gets `In-Reply-To` + `References` headers. Without these\n// the reply lands as a top-level message: no threading anywhere, no \u21A9 indicator\n// on the original (since the linkage signal is the header itself). Mirror of\n// draftUid/draftId at module scope so the btn-send handler can read them.\nlet replyInReplyTo: string = \"\";\nlet replyReferences: string[] = [];\nlet draftTimer: ReturnType<typeof setInterval> | null = null;\nlet draftDebounceTimer: ReturnType<typeof setTimeout> | null = null;\nlet lastDraftContent = \"\";\n\n// Snapshot of compose content right after init populates it (signature, quoted\n// body, pre-filled recipients). composeHasUserChanges() compares against this\n// so autosave and the Save/Discard prompt only react to genuine user edits.\nlet baselineSnapshot = \"\";\nfunction getContentSnapshot(): string {\n return JSON.stringify({\n body: editor?.getHtml() || \"\",\n to: toInput?.value || \"\",\n cc: ccInput?.value || \"\",\n bcc: bccInput?.value || \"\",\n subject: subjectInput?.value || \"\",\n });\n}\nfunction recordContentBaseline(): void {\n baselineSnapshot = getContentSnapshot();\n // Seed lastDraftContent so the first autosave-tick after init doesn't\n // fire just because the snapshot differs from the empty string.\n lastDraftContent = baselineSnapshot;\n}\nfunction composeHasUserChanges(): boolean {\n return getContentSnapshot() !== baselineSnapshot;\n}\nlet draftSaving = false; // prevent concurrent saves\nlet draftSaveFailed = false; // surfaced in the compose status tag\n\ninterface PendingAttachment {\n filename: string;\n mimeType: string;\n size: number;\n dataBase64: string;\n}\nconst attachments: PendingAttachment[] = [];\n\nfunction showDraftStatus(text: string, isError: boolean): void {\n const status = document.getElementById(\"compose-status\");\n if (!status) return;\n status.textContent = text;\n status.classList.toggle(\"compose-status-error\", isError);\n}\n\nasync function saveDraft(): Promise<void> {\n if (draftSaving) return; // previous save still in flight\n // Skip autosave when nothing has changed from the post-init baseline\n // (signature, quoted body, pre-filled To). Editing an existing draft\n // bypasses the check \u2014 we still need to round-trip user edits to the\n // server-side draft. Without this, replies/forwards saved a draft the\n // moment they opened, even with no user typing.\n if (!draftUid && !draftId && !composeHasUserChanges()) return;\n // Don't checkpoint a meaningless draft. A fresh compose with no subject,\n // no body, and no REAL recipient (a bare word like \"sherrik\" typed into\n // To: when the user thought focus was in the search box) is junk \u2014 it\n // littered Bob's mailbox with \"To: sherrik <>\" drafts 2026-05-20. The\n // `\\S+@\\S+` test is a cheap \"is there an actual address\" proxy. Editing\n // an existing draft (draftUid/draftId set) always saves \u2014 we must\n // round-trip the user's edits even if they cleared all the fields.\n if (!draftUid && !draftId) {\n const hasSubject = subjectInput.value.trim().length > 0;\n const hasBody = editor.getText().trim().length > 0;\n const hasRealRecipient = /\\S+@\\S+/.test(`${toInput.value} ${ccInput.value} ${bccInput.value}`);\n if (!hasSubject && !hasBody && !hasRealRecipient) return;\n }\n const content = getContentSnapshot();\n if (content === lastDraftContent) return; // no changes since last save\n // Expose to window for blur-handler.\n (window as any).__mailxSaveDraft = saveDraft;\n lastDraftContent = content;\n draftSaving = true;\n\n try {\n const data = await apiSaveDraft({\n accountId: getFromAccountId(),\n subject: subjectInput.value,\n bodyHtml: editor.getHtml(),\n bodyText: editor.getText(),\n to: toInput.value,\n cc: ccInput.value,\n previousDraftUid: draftUid,\n draftId: draftId,\n });\n if (data?.draftUid) draftUid = data.draftUid;\n if (data?.draftId) draftId = data.draftId;\n if (draftSaveFailed) { draftSaveFailed = false; showDraftStatus(\"Draft saved\", false); }\n else showDraftStatus(`Draft saved ${new Date().toLocaleTimeString()}`, false);\n markComposeClean();\n } catch (e: any) {\n // Surface the error \u2014 silent failures are how drafts get lost on IMAP hiccups.\n // The local editing/ checkpoint already exists server-side regardless.\n console.error(\"[draft] save failed:\", e);\n draftSaveFailed = true;\n showDraftStatus(`Draft save failed: ${e?.message || e}`, true);\n // Clear lastDraftContent so the next tick retries the same content\n lastDraftContent = \"\";\n }\n finally { draftSaving = false; }\n}\n\n/** Schedule a debounced save on user input \u2014 fires ~1.5s after the last\n * keystroke, then yields one animation frame before actually writing so\n * the browser can paint any keystroke in the mean time. S60 mitigation:\n * wa-sqlite writes are synchronous on Android; this keeps the typing\n * experience responsive by never running the write in the same task as\n * an input event. */\nfunction scheduleDraftSave(): void {\n markComposeDirty();\n if (draftDebounceTimer) clearTimeout(draftDebounceTimer);\n draftDebounceTimer = setTimeout(() => {\n draftDebounceTimer = null;\n // rAF yield \u2014 lets any pending keystroke render before we block on\n // the DB write. A no-op when the tab is hidden (rAF is throttled),\n // which is fine because the user isn't typing then either.\n requestAnimationFrame(() => { saveDraft(); });\n }, DRAFT_INPUT_DEBOUNCE_MS);\n}\n\n// \u2500\u2500 Initialize: local-first population.\n//\n// Reply / Reply-All / Forward callers pre-populate `init.accounts` with the\n// full account list (app.ts:openCompose). In that common case we do NOT need\n// to call getAccounts() \u2014 everything required to fill the compose form is\n// already in sessionStorage and reads synchronously. That turns \"click Reply\"\n// into an instant-open instead of \"wait for getAccounts IPC to respond,\n// which can take >120s when the service is busy syncing / hung on IMAP\".\n//\n// getAccounts is still called (non-blocking) to refresh the dropdown with\n// the freshest data \u2014 and it IS awaited only in the fallback path where\n// init doesn't have an account list (message-viewer's Edit Draft passes\n// init.accounts=[]).\n\n// Parent (app.ts:openCompose) opens this iframe in parallel with its\n// `getAccounts()` IPC. If sessionStorage isn't populated yet by the time\n// we get here, wait briefly for the parent's `compose-init-ready`\n// postMessage. Listener registered as early as possible so a fast parent\n// post that beats this IIFE still gets captured.\nlet _parentInitReady = !!sessionStorage.getItem(\"composeInit\");\nconst _parentInitListeners: Array<() => void> = [];\nwindow.addEventListener(\"message\", (e: MessageEvent) => {\n if (e.data?.type !== \"compose-init-ready\") return;\n _parentInitReady = true;\n for (const fn of _parentInitListeners.splice(0)) fn();\n});\nfunction waitForParentInit(maxMs: number): Promise<void> {\n if (_parentInitReady) return Promise.resolve();\n return new Promise<void>(resolve => {\n const timer = setTimeout(() => { _parentInitReady = true; resolve(); }, maxMs);\n _parentInitListeners.push(() => { clearTimeout(timer); resolve(); });\n });\n}\n\n(async () => {\n _ctick(\"init IIFE start\");\n if (!sessionStorage.getItem(\"composeInit\")) {\n _ctick(\"waiting for parent init\");\n await waitForParentInit(1500);\n _ctick(\"parent init received\");\n }\n const stored = sessionStorage.getItem(\"composeInit\");\n if (stored) {\n sessionStorage.removeItem(\"composeInit\");\n const init = JSON.parse(stored) as ComposeInit;\n _ctick(`init parsed (mode=${init.mode}, bodyHtml=${init.bodyHtml?.length || 0} bytes)`);\n if (init.accounts && init.accounts.length > 0) {\n // Happy path \u2014 init is complete. Apply immediately. Kick\n // getAccounts in the background to refresh the dropdown if the\n // user keeps compose open long enough for the result.\n applyInit(init);\n _ctick(\"applyInit done \u2014 compose visible\");\n getAccounts().then((fresh: any[]) => {\n if (Array.isArray(fresh) && fresh.length > 0) {\n init.accounts = fresh;\n // Re-populate the From dropdown only \u2014 don't clobber\n // anything the user may have already typed.\n try { populateFromOptions(fresh); } catch { /* */ }\n }\n }).catch(() => { /* non-fatal */ });\n } else {\n // Edit Draft / other callers that didn't pre-fill accounts.\n // Have to wait on getAccounts here \u2014 the From dropdown needs it.\n let fresh: any[] = [];\n try { fresh = await getAccounts(); } catch (e: any) { console.error(\"Failed to load accounts:\", e); }\n init.accounts = fresh;\n applyInit(init);\n }\n } else {\n let accounts: any[] = [];\n try { accounts = await getAccounts(); } catch (e: any) { console.error(\"Failed to load accounts:\", e); }\n populateFromOptions(accounts);\n toInput.focus();\n }\n\n // Wire debounced saves to input events \u2014 checkpoint ~1.5s after the last\n // keystroke instead of waiting up to 5s for the interval tick.\n toInput.addEventListener(\"input\", scheduleDraftSave);\n ccInput.addEventListener(\"input\", scheduleDraftSave);\n bccInput.addEventListener(\"input\", scheduleDraftSave);\n subjectInput.addEventListener(\"input\", scheduleDraftSave);\n editor.onContentChange(scheduleDraftSave);\n\n // Safety-net interval: even with no user input, catch any edge cases.\n draftTimer = setInterval(saveDraft, DRAFT_INTERVAL_MS);\n\n // 2026-05-13 safety net: poll editor state directly every 4s and force\n // a save when the body has changed since the last seen snapshot. Bypasses\n // the editor's change-event wiring (TinyMCE's `update` event silently\n // missed Bob's 2h 15m of typing today \u2014 log shows zero saveDraft IPC).\n // Compares the canonicalized editor HTML, not the JSON snapshot, so\n // TinyMCE renormalisation doesn't trip the check on every poll. Until\n // the root TinyMCE-event bug is fixed, this guarantees disk recovery.\n //\n // Backoff: when saveDraft fails (e.g., the IPC times out because the\n // daemon is hung on a slow IMAP fetch), we don't want to refire every\n // 4 s and re-paint the error banner. The polling loop reads\n // `draftSaveFailed` and skips a tick when the previous attempt failed,\n // doubling the silent window each time up to ~30 s. Resets on the\n // first success.\n let lastPolledHtml = editor.getHtml();\n let _pollBackoffSkips = 0;\n let _pollSkipsRemaining = 0;\n setInterval(() => {\n try {\n if (_pollSkipsRemaining > 0) { _pollSkipsRemaining--; return; }\n const current = editor.getHtml();\n if (current === lastPolledHtml) return;\n lastPolledHtml = current;\n markComposeDirty();\n lastDraftContent = \"\";\n const wasFailing = draftSaveFailed;\n saveDraft().then(() => {\n // Successful save resets the backoff window.\n if (!draftSaveFailed) _pollBackoffSkips = 0;\n }).catch(() => { /* saveDraft handles its own logging */ });\n if (wasFailing || draftSaveFailed) {\n // Last attempt is still in-flight or failed \u2014 wait longer\n // before the next force-flush. 4 s * (2,4,6,8) \u2192 ~32 s ceiling.\n _pollBackoffSkips = Math.min(_pollBackoffSkips + 1, 7);\n _pollSkipsRemaining = _pollBackoffSkips;\n }\n } catch { /* poll loop must never throw */ }\n }, 4000);\n\n // Flush the draft on window close so the last-typed content lands in\n // editing/ even if the interval tick hasn't fired yet. navigator.sendBeacon\n // is synchronous enough to survive unload; callNode IPC would be dropped.\n window.addEventListener(\"beforeunload\", () => {\n if (draftDebounceTimer) { clearTimeout(draftDebounceTimer); draftDebounceTimer = null; }\n // fire-and-forget \u2014 can't await during unload\n saveDraft();\n });\n})();\n\n// \u2500\u2500 Send \u2500\u2500\n\n// Q55: Ctrl+Enter (or Cmd+Enter on macOS) anywhere in compose triggers send.\ndocument.addEventListener(\"keydown\", (e: KeyboardEvent) => {\n if ((e.ctrlKey || e.metaKey) && e.key === \"Enter\") {\n e.preventDefault();\n document.getElementById(\"btn-send\")?.click();\n }\n});\n// Q59: autosave when the window loses focus (in addition to debounce + interval).\nwindow.addEventListener(\"blur\", () => {\n // Use the same saveDraft path as the 5s interval.\n try { (window as any).__mailxSaveDraft?.(); } catch { /* */ }\n});\n\ndocument.getElementById(\"btn-send\")?.addEventListener(\"click\", async () => {\n // Loud tracing through the whole send pipeline. Every step ships a\n // `[client] compose-send-*` event to the Node log so a \"vanished message\"\n // report can be traced end-to-end without devtools. If the log stops\n // at any step, that's where the pipeline broke.\n logClientEvent(\"compose-send-click\");\n // Group expansion \u2014 replace any group names (family, neighbors, etc.)\n // with their address lists from contacts.jsonc \u2192 groups before parsing\n // into the validated {name, address}[] shape the service expects.\n // Synchronous group expansion \u2014 reads in-memory + localStorage cache\n // ONLY. NEVER awaits cloud I/O. This used to be `await expandGroups`\n // which fired a GDrive read of contacts.jsonc and hung Send for 61 s\n // on a slow network (Bob 2026-05-09 00:16:57); the user clicked\n // Send again, producing a real duplicate message.\n const toExpanded = expandGroups(toInput.value);\n const ccExpanded = expandGroups(ccInput.value);\n const bccExpanded = expandGroups(bccInput.value);\n const body = {\n from: getFromAccountId(),\n fromAddress: getFromAddress(),\n to: parseAddrs(toExpanded),\n cc: parseAddrs(ccExpanded),\n bcc: parseAddrs(bccExpanded),\n subject: subjectInput.value,\n bodyHtml: editor.getHtml(),\n bodyText: editor.getText(),\n attachments: attachments.map(a => ({ filename: a.filename, mimeType: a.mimeType, dataBase64: a.dataBase64 })),\n // Threading headers \u2014 daemon writes In-Reply-To and References into\n // the outgoing MIME from these. Without them every reply lands as a\n // top-level message and the \u21A9 indicator never appears on the original\n // (linkage is by header, not by anything client-only).\n inReplyTo: replyInReplyTo,\n references: replyReferences,\n // Hand draft identifiers to the daemon so it owns post-send cleanup.\n // Previously the client fired deleteDraft() as a separate IPC after\n // send, fire-and-forget with a silent catch \u2014 when the IMAP path\n // hiccuped the draft survived (\"draft droppings\"). Daemon-side\n // cleanup queues a sync_action on failure for reliable retry.\n draftUid: draftUid ?? undefined,\n draftId: draftId ?? undefined,\n };\n logClientEvent(\"compose-send-body-built\", { from: body.from, toCount: body.to.length, subjectLen: (body.subject || \"\").length, bodyHtmlLen: (body.bodyHtml || \"\").length, atts: body.attachments.length });\n // Local validity (one missing-To check) \u2014 must run before close so the\n // user gets an inline error instead of silent loss. Anything else (real\n // address validation, MIME assembly, disk write) happens server-side.\n if (!body.to.length) {\n logClientEvent(\"compose-send-rejected-no-to\");\n alert(\"Please add at least one To recipient.\");\n return;\n }\n // Local-first send: validate fast in the client, then fire-and-forget\n // the IPC and close compose IMMEDIATELY. The body is already snapshotted\n // into `body` above; nothing the user types after this point can affect\n // what gets sent.\n //\n // Earlier \"wait for IPC ack before closing\" version produced a real,\n // user-reported bug: clicking Send \u2192 typing for a few hundred ms while\n // the IPC was in flight \u2192 IPC returns \u2192 compose closes mid-keystroke\n // and the user's last edits go nowhere. The fix is structural: send\n // is committed by the client validation + the snapshot, NOT by the\n // server's reply. The server-side write is synchronous-on-disk inside\n // service.send() (queueOutgoingLocal), and any failure surfaces as a\n // top-level banner via the parent's `mailx-send-error` postMessage.\n //\n // Client-side regex validation up front so a typo'd address bounces\n // back inline (compose stays open) instead of disappearing into a\n // toast after compose has closed. Same pattern Outlook/Thunderbird use.\n const emailRe = /^[^\\s<>@]+@[^\\s<>@]+\\.[^\\s<>@]+$/;\n const badAddress = (list: { address?: string }[] | undefined): string | null => {\n if (!list) return null;\n for (const a of list) {\n const addr = (a?.address || \"\").trim();\n if (!addr) continue; // empty fragments are dropped\n if (!emailRe.test(addr)) return addr;\n }\n return null;\n };\n const bad = badAddress(body.to) || badAddress(body.cc) || badAddress(body.bcc);\n if (bad) {\n logClientEvent(\"compose-send-rejected-bad-addr\", { addr: bad });\n const statusEl = document.getElementById(\"compose-status\");\n if (statusEl) statusEl.textContent = `Invalid address: \"${bad}\"`;\n else alert(`Invalid address: \"${bad}\"`);\n return;\n }\n console.log(`[compose] Send clicked: from=${body.from} to=${JSON.stringify(body.to)} subject=\"${body.subject}\" attachments=${body.attachments.length}`);\n\n // Stop autosave NOW \u2014 the body we're sending is the body we're sending,\n // and we don't want a stray autosave to write a stale \"still typing\"\n // copy back to the Drafts folder while SMTP is in flight.\n if (draftTimer) { clearInterval(draftTimer); draftTimer = null; }\n // Record From-address history before close. Only manual values worth\n // keeping \u2014 skip anything that exactly matches a known account.\n try {\n const raw = fromInput.value.trim();\n const known = knownAccounts.some(a => formatAccountFrom(a) === raw);\n if (raw && !known && /@.+\\./.test(raw)) recordFromHistory(raw);\n } catch { /* */ }\n // Draft cleanup is now part of the send IPC payload (body.draftUid /\n // body.draftId) \u2014 daemon handles deletion with retry semantics. The\n // earlier client-side deleteDraft() call here was fire-and-forget and\n // could silently fail, leaving stale drafts in the IMAP folder.\n\n const sendStart = Date.now();\n logClientEvent(\"compose-send-pre-ipc\");\n // Fire the parent-relay IPC and don't await it. Parent will postMessage\n // a `mailx-send-error` to the parent window on failure; the app handles\n // that with a top-level banner. On success the outbox-status pill picks\n // up the queued message via the daemon's outboxStatus event.\n try {\n const reqId = `send-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;\n // Result listener: log only. Compose is already closed by the time\n // any of these fire.\n const onMsg = (ev: MessageEvent) => {\n if (!ev.data || ev.data.type !== \"mailx-compose-send-result\" || ev.data.id !== reqId) return;\n window.removeEventListener(\"message\", onMsg);\n if (ev.data.ok) {\n logClientEvent(\"compose-send-ipc-resolved\", { ms: Date.now() - sendStart });\n } else {\n const msg = ev.data.error || \"unknown\";\n logClientEvent(\"compose-send-ipc-rejected\", { error: msg, ms: Date.now() - sendStart });\n // Bubble the error up so the parent can show a banner. The\n // dropped-into-outbox path (queueOutgoingLocal) catches most\n // failures locally, so this branch fires only when the\n // *queue write itself* failed \u2014 rare, but the user must see\n // it because the message would otherwise be lost silently.\n try { parent.postMessage({ type: \"mailx-send-error\", message: msg, accountId: body.from }, \"*\"); } catch { /* */ }\n }\n };\n window.addEventListener(\"message\", onMsg);\n // Safety: if parent never replies (msger pipe broken), prune the\n // listener after 120s so we don't leak it. No retry here \u2014 the\n // daemon's outbox worker is the retry path.\n setTimeout(() => window.removeEventListener(\"message\", onMsg), 120000);\n parent.postMessage({ type: \"mailx-compose-send\", id: reqId, body }, \"*\");\n logClientEvent(\"compose-send-ipc-invoked\", { via: \"parent-relay\", reqId });\n } catch (e: any) {\n // postMessage itself threw \u2014 bridge totally dead. This is the only\n // path where we keep compose open, since we couldn't even hand the\n // body off.\n const msg: string = e?.message || String(e);\n logClientEvent(\"compose-send-ipc-throw\", { error: msg });\n const sendBtn = document.getElementById(\"btn-send\") as HTMLButtonElement | null;\n if (sendBtn) { sendBtn.disabled = false; sendBtn.textContent = \"Send\"; }\n const statusEl = document.getElementById(\"compose-status\");\n if (statusEl) statusEl.textContent = `Bridge error: ${msg}`;\n return;\n }\n\n closeCompose();\n});\n\n// \u2500\u2500 Close handling \u2500\u2500\n\n/** True if the compose has user-driven changes worth prompting about. Compares\n * against the post-init baseline rather than \"any non-empty content\" \u2014 a\n * reply has quoted body + signature pre-populated, so the old \"any content\"\n * check produced unwanted Save/Discard prompts on closes with no user input. */\nfunction composeHasContent(): boolean {\n return composeHasUserChanges();\n}\n\n/** Ask Save/Discard/Cancel. Returns \"save\" | \"discard\" | \"cancel\".\n * Uses an in-page modal so all three choices are presented at once \u2014 the\n * native confirm() flow forced the user through two sequential dialogs and\n * hid Discard behind a Cancel click, which was confusing. */\nfunction promptSaveOrDiscard(): Promise<\"save\" | \"discard\" | \"cancel\"> {\n return new Promise(resolve => {\n const overlay = document.createElement(\"div\");\n overlay.className = \"compose-modal-overlay\";\n const box = document.createElement(\"div\");\n box.className = \"compose-modal\";\n const msg = document.createElement(\"div\");\n msg.className = \"compose-modal-msg\";\n msg.textContent = \"Save this message as a draft?\";\n const btnRow = document.createElement(\"div\");\n btnRow.className = \"compose-modal-buttons\";\n\n const mkBtn = (label: string, choice: \"save\" | \"discard\" | \"cancel\", primary: boolean): HTMLButtonElement => {\n const b = document.createElement(\"button\");\n b.type = \"button\";\n b.textContent = label;\n b.className = primary ? \"compose-modal-btn primary\" : \"compose-modal-btn\";\n b.addEventListener(\"click\", () => { cleanup(); resolve(choice); });\n return b;\n };\n\n const cleanup = (): void => {\n document.removeEventListener(\"keydown\", onKey);\n overlay.remove();\n };\n const onKey = (e: KeyboardEvent): void => {\n if (e.key === \"Escape\") { e.preventDefault(); cleanup(); resolve(\"cancel\"); }\n else if (e.key === \"Enter\") { e.preventDefault(); cleanup(); resolve(\"save\"); }\n };\n document.addEventListener(\"keydown\", onKey);\n\n btnRow.appendChild(mkBtn(\"Save draft\", \"save\", true));\n btnRow.appendChild(mkBtn(\"Discard\", \"discard\", false));\n btnRow.appendChild(mkBtn(\"Cancel\", \"cancel\", false));\n box.appendChild(msg);\n box.appendChild(btnRow);\n overlay.appendChild(box);\n document.body.appendChild(overlay);\n (btnRow.firstChild as HTMLButtonElement).focus();\n });\n}\n\n/** Handle any \"close the compose\" action (Discard button, Escape, X, window close). */\nasync function handleCloseRequest(): Promise<boolean> {\n if (!composeHasContent()) { closeCompose(); return true; }\n const choice = await promptSaveOrDiscard();\n if (choice === \"cancel\") return false;\n // Stop auto-save so it can't race with our explicit save/discard.\n if (draftDebounceTimer) { clearTimeout(draftDebounceTimer); draftDebounceTimer = null; }\n if (draftTimer) { clearInterval(draftTimer); draftTimer = null; }\n if (choice === \"save\") {\n try { await saveDraft(); } catch { /* already logged */ }\n } else {\n // Discard: delete the tracked draft so the orphan doesn't stick\n // around \u2014 but fire-and-forget. The IMAP draft delete is a server\n // round-trip; awaiting it held the window open for seconds (Bob:\n // \"I press X but it doesn't go away ... eventually it did\"). The\n // close is the user's action and must complete instantly.\n if (draftUid || draftId) {\n deleteDraft(getFromAccountId(), draftUid || 0, draftId || \"\").catch(() => { /* ignore */ });\n }\n }\n closeCompose();\n return true;\n}\n\ndocument.getElementById(\"btn-discard\")?.addEventListener(\"click\", async () => {\n // Explicit Discard \u2014 the user already declared intent. Don't re-prompt\n // them with Save/Discard/Cancel (handleCloseRequest is for the X / Esc\n // path, which is ambiguous and needs the prompt). Just delete the\n // tracked draft (if any) and close.\n // Skip the confirm dialog when there's nothing to lose. Same rule the\n // X/Esc path uses (`composeHasContent` covers body + to/cc/bcc/subject)\n // \u2014 empty compose just closes.\n if (composeHasContent() && !confirm(\"Discard this draft? Any unsaved content will be lost.\")) return;\n if (draftDebounceTimer) { clearTimeout(draftDebounceTimer); draftDebounceTimer = null; }\n if (draftTimer) { clearInterval(draftTimer); draftTimer = null; }\n // Fire-and-forget \u2014 the IMAP draft delete must not hold the window open.\n if (draftUid || draftId) {\n deleteDraft(getFromAccountId(), draftUid || 0, draftId || \"\").catch(() => { /* ignore */ });\n }\n closeCompose();\n});\n\n// \u2500\u2500 Cc / Bcc toggle \u2500\u2500\nconst ccRow = document.getElementById(\"compose-cc-row\") as HTMLElement;\nconst bccRow = document.getElementById(\"compose-bcc-row\") as HTMLElement;\nconst toggleCcBtn = document.getElementById(\"btn-toggle-cc\") as HTMLButtonElement;\nconst toggleBccBtn = document.getElementById(\"btn-toggle-bcc\") as HTMLButtonElement;\n\nfunction setCcVisible(visible: boolean): void {\n ccRow.hidden = !visible;\n toggleCcBtn.classList.toggle(\"active\", visible);\n // Visibility \u2260 existence. Hiding the row keeps the value intact so\n // toggling it back shows what was there. Send still picks up the\n // value from a hidden row \u2014 the field exists in the DOM, just hidden.\n if (visible) ccInput.focus();\n}\nfunction setBccVisible(visible: boolean): void {\n bccRow.hidden = !visible;\n toggleBccBtn.classList.toggle(\"active\", visible);\n if (visible) bccInput.focus();\n}\ntoggleCcBtn?.addEventListener(\"click\", () => setCcVisible(ccRow.hidden));\ntoggleBccBtn?.addEventListener(\"click\", () => setBccVisible(bccRow.hidden));\n// Q49 deferred: should be derived from the address book / sent-history DB,\n// not a parallel localStorage store. Pending: extend contacts schema or\n// query messages table on To-input change (debounced); auto-expand Cc/Bcc\n// when this recipient's history shows \u2265N past uses.\n\n// \u2500\u2500 Attachments \u2500\u2500\nconst fileInput = document.getElementById(\"compose-file\") as HTMLInputElement;\nconst attEl = document.getElementById(\"compose-attachments\") as HTMLElement;\n\nfunction renderAttachmentChips(): void {\n attEl.innerHTML = \"\";\n if (attachments.length === 0) { attEl.hidden = true; return; }\n attEl.hidden = false;\n for (let i = 0; i < attachments.length; i++) {\n const a = attachments[i];\n const chip = document.createElement(\"span\");\n chip.className = \"compose-att-chip\";\n chip.innerHTML = `\\uD83D\\uDCCE ${escapeHtml(a.filename)} (${formatSize(a.size)}) `;\n const rm = document.createElement(\"button\");\n rm.type = \"button\";\n rm.title = \"Remove attachment\";\n rm.textContent = \"\\u2715\";\n rm.addEventListener(\"click\", () => {\n attachments.splice(i, 1);\n renderAttachmentChips();\n });\n chip.appendChild(rm);\n attEl.appendChild(chip);\n }\n}\nfunction escapeHtml(s: string): string {\n return s.replace(/[&<>\"']/g, c => ({ \"&\": \"&amp;\", \"<\": \"&lt;\", \">\": \"&gt;\", '\"': \"&quot;\", \"'\": \"&#39;\" }[c]!));\n}\nfunction formatSize(n: number): string {\n if (n < 1024) return `${n} B`;\n if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`;\n return `${(n / (1024 * 1024)).toFixed(1)} MB`;\n}\n\n/** Set when the user clicks Attach \u2014 the native file picker eats the Esc\n * press, but on Windows WebView2 the keydown can still spill to the page\n * and trip the document-level Esc-closes-compose handler. While this flag\n * is set, that handler short-circuits. Cleared shortly after click. */\nlet attachJustClicked = 0;\ndocument.getElementById(\"btn-attach\")?.addEventListener(\"click\", () => {\n attachJustClicked = Date.now();\n fileInput?.click();\n});\n\n// \u2500\u2500 Edit in Word (external editor handoff) \u2500\u2500\n//\n// Click writes the current body to a temp file, opens it in Word (or the\n// platform fallback), and watches the file. When Word saves, the service\n// emits `wordEditUpdated` and we replace the editor's HTML with the new\n// content. The editId is per-compose-window \u2014 closeWordEdit cleans up the\n// temp file when the window closes or the message is sent.\nlet wordEditId: string | null = null;\n// Edit-in-Word is desktop-only \u2014 hide the button on Android where the file\n// system / external-editor path can't work. Detection: the MAUI WebView\n// exposes `mailxapi.platform === \"android\"` once the bridge is up.\n{\n const isAndroid = (window as any).mailxapi?.platform === \"android\"\n || (window.parent as any)?.mailxapi?.platform === \"android\";\n if (isAndroid) {\n const btn = document.getElementById(\"btn-edit-in-word\") as HTMLElement | null;\n if (btn) btn.hidden = true;\n }\n}\n\ndocument.getElementById(\"btn-edit-in-word\")?.addEventListener(\"click\", async () => {\n if (!wordEditId) wordEditId = `compose-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;\n showDraftStatus(\"Opening in Word\u2026\", false);\n try {\n const result = await openInWord(wordEditId, editor.getHtml());\n if (!result.ok || result.opener === \"none\") {\n showDraftStatus(\"Couldn't launch an editor. Install Word, LibreOffice, or set a default for .html.\", true);\n return;\n }\n const label =\n result.opener === \"word\" ? \"Word\" :\n result.opener === \"libreoffice\" ? \"LibreOffice\" :\n \"your default editor\";\n showDraftStatus(`Editing in ${label} \u2014 saves there will reload here.`, false);\n showExternalEditHint(label);\n } catch (e: any) {\n showDraftStatus(`Edit-in-Word failed: ${e?.message || e}`, true);\n }\n});\n\n// Editor help button \u2014 opens a reference modal with shortcuts and the\n// Quill / tiptap differences. Source content lives in app/docs/editor.md\n// (and is mirrored in editor-help.ts for runtime embed).\ndocument.getElementById(\"btn-compose-help\")?.addEventListener(\"click\", async () => {\n try {\n const { EDITOR_HELP_MD } = await import(\"./editor-help.js\");\n showEditorHelpModal(EDITOR_HELP_MD);\n } catch (e: any) {\n showDraftStatus(`Couldn't open help: ${e?.message || e}`, true);\n }\n});\n\n/** Render a markdown string in a centered modal. Click outside / Esc /\n * Close button dismiss. Lightweight markdown rendering \u2014 handles headings,\n * paragraphs, lists, tables, inline code, bold/italic. Intentionally\n * doesn't pull a full markdown library since this is one document. */\nfunction showEditorHelpModal(md: string): void {\n if (document.getElementById(\"mailx-editor-help-modal\")) return;\n const backdrop = document.createElement(\"div\");\n backdrop.id = \"mailx-editor-help-modal\";\n backdrop.style.cssText = \"position:fixed;inset:0;background:rgba(0,0,0,0.5);z-index:10001;display:flex;align-items:center;justify-content:center;padding:24px;\";\n const panel = document.createElement(\"div\");\n panel.style.cssText = \"background:var(--color-bg, #fff);color:var(--color-text, #000);border-radius:8px;max-width:780px;max-height:88vh;width:100%;display:flex;flex-direction:column;box-shadow:0 8px 32px rgba(0,0,0,0.4);\";\n const header = document.createElement(\"div\");\n header.style.cssText = \"display:flex;justify-content:space-between;align-items:center;padding:12px 18px;border-bottom:1px solid var(--color-border, #ddd);\";\n header.innerHTML = `<span style=\"font-weight:600;\">Editor help</span><button id=\"mailx-editor-help-close\" style=\"background:none;border:0;font-size:18px;cursor:pointer;color:var(--color-text);padding:2px 8px;\" aria-label=\"Close\">&times;</button>`;\n const body = document.createElement(\"div\");\n body.style.cssText = \"flex:1;overflow:auto;padding:18px 22px;font:14px/1.55 system-ui;\";\n body.innerHTML = renderMarkdownLite(md);\n panel.appendChild(header);\n panel.appendChild(body);\n backdrop.appendChild(panel);\n document.body.appendChild(backdrop);\n const close = (): void => {\n backdrop.remove();\n document.removeEventListener(\"keydown\", onKey, true);\n };\n const onKey = (e: KeyboardEvent): void => {\n if (e.key === \"Escape\") { e.stopPropagation(); e.preventDefault(); close(); }\n };\n document.addEventListener(\"keydown\", onKey, true);\n header.querySelector<HTMLButtonElement>(\"#mailx-editor-help-close\")?.addEventListener(\"click\", close);\n backdrop.addEventListener(\"mousedown\", (e) => { if (e.target === backdrop) close(); });\n}\n\n/** Compact markdown renderer. Handles headings, paragraphs, code blocks,\n * inline code, bold, italic, links, lists, and pipe-tables. Built for the\n * editor help doc shape \u2014 not a general-purpose markdown engine. */\nfunction renderMarkdownLite(md: string): string {\n const esc = (s: string) => s.replace(/[&<>\"']/g, c => ({ \"&\": \"&amp;\", \"<\": \"&lt;\", \">\": \"&gt;\", '\"': \"&quot;\", \"'\": \"&#39;\" })[c]!);\n const inline = (s: string) => esc(s)\n .replace(/`([^`]+)`/g, '<code style=\"background:var(--color-bg-surface,#f3f3f3);padding:1px 4px;border-radius:3px;\">$1</code>')\n .replace(/\\*\\*([^*]+)\\*\\*/g, \"<strong>$1</strong>\")\n .replace(/(?<![*\\w])\\*([^*\\n]+)\\*(?!\\w)/g, \"<em>$1</em>\")\n .replace(/\\[([^\\]]+)\\]\\(([^)]+)\\)/g, '<a href=\"$2\" target=\"_blank\" rel=\"noopener\">$1</a>');\n const lines = md.split(/\\r?\\n/);\n const out: string[] = [];\n let i = 0;\n while (i < lines.length) {\n const line = lines[i];\n const h = /^(#{1,6})\\s+(.*)$/.exec(line);\n if (h) { out.push(`<h${h[1].length} style=\"margin:18px 0 8px 0;\">${inline(h[2])}</h${h[1].length}>`); i++; continue; }\n if (/^\\s*$/.test(line)) { i++; continue; }\n // Pipe-table\n if (line.includes(\"|\") && /^\\s*\\|/.test(line)) {\n const rows: string[][] = [];\n while (i < lines.length && /^\\s*\\|/.test(lines[i])) {\n rows.push(lines[i].trim().split(\"|\").slice(1, -1).map(s => s.trim()));\n i++;\n }\n if (rows.length >= 2 && /^[\\s\\-:]+$/.test(rows[1].join(\"\"))) {\n const head = rows[0];\n const data = rows.slice(2);\n out.push(`<table style=\"border-collapse:collapse;margin:8px 0;\">`);\n out.push(`<thead><tr>${head.map(h => `<th style=\"border-bottom:2px solid var(--color-border,#ccc);padding:4px 10px;text-align:left;\">${inline(h)}</th>`).join(\"\")}</tr></thead>`);\n out.push(`<tbody>${data.map(r => `<tr>${r.map(c => `<td style=\"border-bottom:1px solid var(--color-border,#eee);padding:4px 10px;\">${inline(c)}</td>`).join(\"\")}</tr>`).join(\"\")}</tbody>`);\n out.push(`</table>`);\n continue;\n }\n }\n // Bullet / numbered list\n if (/^\\s*[-*]\\s+/.test(line)) {\n const items: string[] = [];\n while (i < lines.length && /^\\s*[-*]\\s+/.test(lines[i])) {\n items.push(lines[i].replace(/^\\s*[-*]\\s+/, \"\"));\n i++;\n }\n out.push(`<ul style=\"margin:6px 0 6px 22px;\">${items.map(it => `<li style=\"margin:3px 0;\">${inline(it)}</li>`).join(\"\")}</ul>`);\n continue;\n }\n // Default: paragraph\n out.push(`<p style=\"margin:6px 0;\">${inline(line)}</p>`);\n i++;\n }\n return out.join(\"\\n\");\n}\n\n/** Modal hint shown when Edit-in-Word launches. The user is about to lose\n * focus to Word and may not know what to do next, so spell it out: save in\n * Word \u2192 switch back here \u2192 click Send. Dismissed by Esc, the close button,\n * or any click outside the panel. */\nfunction showExternalEditHint(editorLabel: string): void {\n if (document.getElementById(\"mailx-extedit-hint\")) return; // don't stack\n const backdrop = document.createElement(\"div\");\n backdrop.id = \"mailx-extedit-hint\";\n backdrop.style.cssText = \"position:fixed;inset:0;background:rgba(0,0,0,0.45);z-index:10001;display:flex;align-items:center;justify-content:center;\";\n const panel = document.createElement(\"div\");\n panel.style.cssText = \"background:var(--color-bg, #fff);color:var(--color-text, #000);border:1px solid var(--color-border, #ccc);border-radius:8px;padding:18px 22px;max-width:480px;box-shadow:0 8px 32px rgba(0,0,0,0.4);font:14px/1.5 system-ui;\";\n panel.innerHTML = `\n <div style=\"font-weight:600;font-size:16px;margin-bottom:10px;\">Editing in ${escapeHtml(editorLabel)}</div>\n <ol style=\"margin:0 0 12px 18px;padding:0;\">\n <li>Edit your message in <b>${escapeHtml(editorLabel)}</b>.</li>\n <li>Save (<b>Ctrl+S</b>). Today, choose <b>\"Web Page, Filtered (.htm)\"</b> if Word asks for a format \u2014 keep the same filename.</li>\n <li>Switch back to this window (<b>Alt+Tab</b>). The body will reload here.</li>\n <li>Click <b>Send</b> in this window when ready.</li>\n </ol>\n <div style=\"text-align:right;margin-top:8px;\">\n <button type=\"button\" id=\"mailx-extedit-hint-ok\" style=\"padding:6px 16px;border:1px solid var(--color-border, #ccc);background:var(--color-bg-surface, #f6f6f6);border-radius:4px;cursor:pointer;font:inherit;\">Got it</button>\n </div>\n `;\n backdrop.appendChild(panel);\n document.body.appendChild(backdrop);\n const close = (): void => {\n backdrop.remove();\n document.removeEventListener(\"keydown\", onKey, true);\n };\n const onKey = (e: KeyboardEvent): void => {\n if (e.key === \"Escape\") { e.stopPropagation(); e.preventDefault(); close(); }\n };\n document.addEventListener(\"keydown\", onKey, true);\n panel.querySelector<HTMLButtonElement>(\"#mailx-extedit-hint-ok\")?.addEventListener(\"click\", close);\n // Click outside the panel (i.e. on the backdrop) dismisses.\n backdrop.addEventListener(\"mousedown\", (e) => { if (e.target === backdrop) close(); });\n}\n\n// Listen for external-editor saves. Only react to events for this compose's\n// editId \u2014 multiple compose windows can be open and should not stomp each\n// other's bodies.\nonEvent((ev: any) => {\n if (ev?.type !== \"wordEditUpdated\") return;\n if (!wordEditId || ev.editId !== wordEditId) return;\n try {\n editor.setHtml(ev.html || \"\");\n showDraftStatus(\"Reloaded edits from external editor.\", false);\n scheduleDraftSave();\n } catch (e: any) {\n showDraftStatus(`Reload failed: ${e?.message || e}`, true);\n }\n});\nwindow.addEventListener(\"beforeunload\", () => {\n if (wordEditId) closeWordEdit(wordEditId).catch(() => { /* */ });\n});\n\nasync function ingestFiles(files: FileList | File[]): Promise<void> {\n for (const file of Array.from(files)) {\n const buf = await file.arrayBuffer();\n // base64 the whole thing \u2014 mailx-service builds the multipart/mixed\n let binary = \"\";\n const bytes = new Uint8Array(buf);\n for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]);\n const dataBase64 = btoa(binary);\n attachments.push({\n filename: file.name,\n mimeType: file.type || \"application/octet-stream\",\n size: file.size,\n dataBase64,\n });\n }\n renderAttachmentChips();\n scheduleDraftSave();\n}\n\nfileInput?.addEventListener(\"change\", async () => {\n if (!fileInput.files) return;\n await ingestFiles(fileInput.files);\n fileInput.value = \"\";\n});\n\n// Drag-and-drop: dropping files anywhere on the compose window attaches them.\n// Highlights a subtle overlay while dragging so the target is obvious. The\n// editor iframe swallows drag events internally so we attach to the compose\n// document root; Quill's own paste/drop handling doesn't fight us because\n// files-with-no-HTML-or-text dragover never hits Quill's clipboard module.\n(() => {\n let dragDepth = 0;\n const root = document.body;\n const overlay = document.createElement(\"div\");\n overlay.id = \"compose-drop-overlay\";\n // Toggle `display` directly \u2014 can't use the `hidden` attribute here\n // because the inline `display` property in cssText outranks it, which is\n // why the overlay showed permanently when I used `overlay.hidden = true`\n // (user-reported 2026-04-24 with screenshot \u2014 blue tint + dashed border\n // were visible before any drag started).\n const baseStyle = \"position:fixed;inset:0;background:oklch(0.6 0.18 250 / 0.15);border:3px dashed oklch(0.55 0.2 250);z-index:9999;pointer-events:none;align-items:center;justify-content:center;font-size:1.5rem;font-weight:500;color:oklch(0.35 0.2 250)\";\n overlay.style.cssText = baseStyle + \";display:none\";\n overlay.textContent = \"Drop files to attach\";\n root.appendChild(overlay);\n const show = () => { overlay.style.display = \"flex\"; };\n const hide = () => { overlay.style.display = \"none\"; };\n\n const hasFiles = (e: DragEvent) =>\n Array.from(e.dataTransfer?.types || []).includes(\"Files\");\n\n root.addEventListener(\"dragenter\", (e) => {\n if (!hasFiles(e)) return;\n dragDepth++;\n show();\n });\n root.addEventListener(\"dragleave\", (e) => {\n if (!hasFiles(e)) return;\n dragDepth = Math.max(0, dragDepth - 1);\n if (dragDepth === 0) hide();\n });\n root.addEventListener(\"dragover\", (e) => {\n if (!hasFiles(e)) return;\n e.preventDefault(); // required so drop fires\n if (e.dataTransfer) e.dataTransfer.dropEffect = \"copy\";\n });\n root.addEventListener(\"drop\", async (e) => {\n if (!hasFiles(e)) return;\n e.preventDefault();\n dragDepth = 0;\n hide();\n const files = e.dataTransfer?.files;\n if (files && files.length > 0) await ingestFiles(files);\n });\n})();\n\n// \u2500\u2500 Save and close (X button from parent) \u2500\u2500\nwindow.addEventListener(\"compose-save-and-close\", () => {\n handleCloseRequest();\n});\n\n// \u2500\u2500 Discard (trash icon from parent title bar) \u2500\u2500\n// Routes through the existing btn-discard handler so the confirm dialog and\n// draft-deletion logic stay in one place.\nwindow.addEventListener(\"compose-discard\", () => {\n document.getElementById(\"btn-discard\")?.click();\n});\n\n// \u2500\u2500 Keyboard shortcuts \u2500\u2500\n\ndocument.addEventListener(\"keydown\", (e) => {\n if (e.ctrlKey && e.key === \"Enter\") {\n document.getElementById(\"btn-send\")?.click();\n }\n if (e.key === \"Escape\") {\n // If the user just clicked Attach, the native file picker is up.\n // The picker swallows the Esc that dismissed it, but the keydown can\n // still bubble here on WebView2 \u2014 closing the whole compose. Suppress\n // for a short window after the attach click.\n if (Date.now() - attachJustClicked < 1500) return;\n // A modal/dialog open over compose (the link editor, TinyMCE's\n // dialogs, the add-to-preferred modal, \u2026) owns Escape \u2014 dismissing\n // the dialog must NOT also bubble here and offer to close the whole\n // compose window. `e.target.closest` still resolves even when the\n // dialog removed itself in an earlier event phase: the detached\n // subtree stays intact, so `.closest()` still walks it.\n const t = e.target as Element | null;\n if (t && typeof t.closest === \"function\"\n && t.closest('.mailx-modal-backdrop, .tox-dialog, [role=\"dialog\"]')) {\n return;\n }\n e.preventDefault();\n handleCloseRequest();\n }\n // Ctrl+K in an address field = trigger address completion.\n // NOTE: Ctrl+K is ALSO the editor's \"insert link\" shortcut. Scope this handler\n // strictly to the to/cc/bcc inputs so it doesn't shadow the editor binding when\n // focus is in the body.\n if (e.ctrlKey && (e.key === \"k\" || e.key === \"K\")) {\n const active = document.activeElement as HTMLElement;\n const addressFields: HTMLElement[] = [toInput, ccInput, bccInput];\n if (addressFields.includes(active)) {\n e.preventDefault();\n (active as HTMLInputElement).dispatchEvent(new Event(\"input\"));\n }\n }\n});\n", "/**\n * Simple context menu component.\n * Shows a menu at a given position with clickable items.\n */\n\nlet activeMenu: HTMLElement | null = null;\nlet dismissListener: ((e: Event) => void) | null = null;\nlet escapeListener: ((e: KeyboardEvent) => void) | null = null;\n\nexport interface MenuItem {\n label: string;\n action: () => void;\n disabled?: boolean;\n separator?: boolean;\n /** Native browser tooltip shown on hover (title attribute). Use it\n * to explain non-obvious side effects of an action \u2014 e.g., \"skips\n * Trash, no undo\" so the user can tell two near-identical entries\n * apart without trial-and-error. */\n tooltip?: string;\n}\n\n/** Close any open context menu and remove dismiss listeners */\nexport function closeContextMenu(): void {\n if (activeMenu) {\n activeMenu.remove();\n activeMenu = null;\n }\n if (dismissListener) {\n document.removeEventListener(\"pointerdown\", dismissListener, true);\n dismissListener = null;\n }\n if (escapeListener) {\n document.removeEventListener(\"keydown\", escapeListener, true);\n escapeListener = null;\n }\n}\n\n/** Show a context menu at the given position */\nexport function showContextMenu(x: number, y: number, items: MenuItem[]): void {\n closeContextMenu();\n\n const menu = document.createElement(\"div\");\n menu.className = \"ctx-menu\";\n\n for (const item of items) {\n if (item.separator) {\n const sep = document.createElement(\"div\");\n sep.className = \"ctx-sep\";\n menu.appendChild(sep);\n continue;\n }\n const el = document.createElement(\"div\");\n el.className = \"ctx-item\" + (item.disabled ? \" ctx-disabled\" : \"\");\n el.textContent = item.label;\n if (item.tooltip) el.title = item.tooltip;\n if (!item.disabled) {\n el.addEventListener(\"click\", () => {\n closeContextMenu();\n item.action();\n });\n }\n menu.appendChild(el);\n }\n\n menu.style.left = `${x}px`;\n menu.style.top = `${y}px`;\n document.body.appendChild(menu);\n\n // Adjust if menu goes off-screen\n const rect = menu.getBoundingClientRect();\n if (rect.right > window.innerWidth) menu.style.left = `${x - rect.width}px`;\n if (rect.bottom > window.innerHeight) menu.style.top = `${y - rect.height}px`;\n\n activeMenu = menu;\n\n // Dismiss on click/tap outside the menu. Uses pointerdown in capture phase\n // so it fires before any child handler and catches both left- and right-clicks.\n // Deferred by one frame so the opening pointerdown doesn't immediately close it.\n requestAnimationFrame(() => {\n dismissListener = (e: Event) => {\n if (activeMenu && !activeMenu.contains(e.target as Node)) {\n closeContextMenu();\n }\n };\n document.addEventListener(\"pointerdown\", dismissListener, true);\n\n escapeListener = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") {\n e.preventDefault();\n e.stopPropagation();\n closeContextMenu();\n }\n };\n document.addEventListener(\"keydown\", escapeListener, true);\n });\n}\n\n// Scroll anywhere closes the menu (capture phase so nested scrollers trigger it)\ndocument.addEventListener(\"scroll\", closeContextMenu, true);\n// A new right-click that opens a different menu goes through showContextMenu\u2192closeContextMenu\ndocument.addEventListener(\"contextmenu\", () => { /* handled by showContextMenu */ });\n// Iframe pointerdown forwarded from preview/compose iframes \u2014 needed because\n// iframe events don't bubble to the parent document, so the dismissListener\n// (which hooks document.pointerdown) doesn't see clicks INSIDE iframes. The\n// message-viewer's inline iframe script posts {type:\"iframePointerDown\"} on\n// every non-right-click pointerdown.\nwindow.addEventListener(\"message\", (e: MessageEvent) => {\n if (e.data && (e.data as any).type === \"iframePointerDown\") closeContextMenu();\n});\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * API client \u2014 all operations go through the IPC bridge (mailxapi).\n * mailxapi is injected by the launcher (msger on desktop, MAUI on Android).\n */\n\ndeclare const mailxapi: any;\n\nfunction getIpc(): any {\n if (typeof mailxapi !== \"undefined\" && mailxapi?.isApp) return mailxapi;\n if ((window as any).opener?.mailxapi?.isApp) return (window as any).opener.mailxapi;\n // Compose iframe \u2014 check parent\n if ((window as any).parent?.mailxapi?.isApp) return (window as any).parent.mailxapi;\n return null;\n}\n\n/** Build a proxy bridge that forwards every method call to the parent window\n * via postMessage. Used when the compose iframe's own attempt to reach\n * msger's IPC silently drops messages \u2014 empirically, `sendMessage` and\n * `saveDraft` both hit this (user-visible: \"Sending\u2026\" spinner forever;\n * \"Draft save failed: mailxapi timeout\"). The main window's bridge is\n * provably fine, so the iframe routes through it. */\nfunction buildRelayBridge(): any {\n const pending = new Map<string, { resolve: (v: any) => void; reject: (e: any) => void; timer: any }>();\n window.addEventListener(\"message\", (ev: MessageEvent) => {\n if (!ev.data || ev.data.type !== \"mailx-ipc-result\" || !ev.data.id) return;\n const entry = pending.get(ev.data.id);\n if (!entry) return;\n pending.delete(ev.data.id);\n clearTimeout(entry.timer);\n if (ev.data.ok) entry.resolve(ev.data.result);\n else entry.reject(new Error(ev.data.error || \"parent-relay ipc error\"));\n });\n const call = (method: string, args: any[]): Promise<any> => {\n const id = `ipc-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;\n return new Promise((resolve, reject) => {\n const timer = setTimeout(() => {\n pending.delete(id);\n reject(new Error(`parent-relay timeout: ${method}`));\n }, 120000);\n pending.set(id, { resolve, reject, timer });\n try {\n (window.parent as any).postMessage({ type: \"mailx-ipc\", id, method, args }, \"*\");\n } catch (e) {\n clearTimeout(timer);\n pending.delete(id);\n reject(e);\n }\n });\n };\n // Proxy: any property access returns a function that forwards to parent.\n // `isApp` / `platform` / other non-function reads return sensible defaults\n // so existing getIpc-style checks still work.\n return new Proxy({}, {\n get(_t, prop: string) {\n if (prop === \"isApp\") return true;\n if (prop === \"platform\") return (window.parent as any)?.mailxapi?.platform || \"webview2\";\n if (prop === \"onEvent\") {\n // Event subscription can't be relayed simply \u2014 iframes that need\n // events are rare. Fall back to direct parent bridge for onEvent\n // since the subscription path doesn't hit the broken send path.\n return (handler: any) => (window.parent as any)?.mailxapi?.onEvent?.(handler);\n }\n return (...args: any[]) => call(prop, args);\n }\n });\n}\n\nlet cachedRelayBridge: any = null;\nfunction ipc(): any {\n // Direct bridge is fine for the top window (main mailx app). The iframe\n // (compose) can't trust its own bridge resolution because msger-routed\n // sendMessage / saveDraft IPCs disappear without trace. So when we're in\n // a child frame with a parent bridge, go through the parent.\n const inIframe = window.parent && window.parent !== window;\n if (inIframe && (window.parent as any)?.mailxapi?.isApp) {\n if (!cachedRelayBridge) cachedRelayBridge = buildRelayBridge();\n return cachedRelayBridge;\n }\n const bridge = getIpc();\n if (!bridge) throw new Error(\"IPC bridge not available\");\n return bridge;\n}\n\n// \u2500\u2500 Abort controller for message-list requests \u2500\u2500\n\nlet messageListAbort: AbortController | null = null;\n\nexport function abortMessageListRequests(): void {\n if (messageListAbort) {\n messageListAbort.abort();\n messageListAbort = null;\n }\n}\n\n// \u2500\u2500 API Methods \u2500\u2500\n\nexport function getAccounts() {\n return ipc().getAccounts();\n}\n\nexport function getFolders(accountId: string) {\n return ipc().getFolders(accountId);\n}\n\nexport function getMessages(accountId: string, folderId: number, page = 1, pageSize = 50, flaggedOnly = false, sort?: string, sortDir?: string) {\n abortMessageListRequests();\n return ipc().getMessages(accountId, folderId, page, pageSize, sort, sortDir, undefined, flaggedOnly);\n}\n\nexport function getUnifiedInbox(page = 1, pageSize = 50) {\n abortMessageListRequests();\n return ipc().getUnifiedInbox(page, pageSize);\n}\n\nexport function searchMessages(query: string, page = 1, pageSize = 50, scope = \"all\", accountId = \"\", folderId = 0, includeTrashSpam = false) {\n return ipc().searchMessages(query, page, pageSize, scope, accountId, folderId, includeTrashSpam);\n}\n\n/** Abort any in-flight server (IMAP) search. Fire-and-forget \u2014 the daemon\n * bumps a generation counter its per-folder loop checks between batches. */\nexport function cancelServerSearch() {\n return ipc().cancelServerSearch?.();\n}\n\nexport function getMessage(accountId: string, uid: number, allowRemote = false, folderId?: number) {\n return ipc().getMessage(accountId, uid, allowRemote, folderId);\n}\n\nexport function updateFlags(accountId: string, uid: number, flags: string[]) {\n return ipc().updateFlags(accountId, uid, flags);\n}\n\nexport function triggerSync() {\n return ipc().syncAll();\n}\n\nexport function syncAccount(accountId: string) {\n return ipc().syncAccount(accountId);\n}\n\nexport function reauthenticate(accountId: string) {\n return ipc().reauthenticate(accountId);\n}\n\nexport function reauthGoogleScopes(): Promise<{ cleared: number }> {\n return (ipc() as any).reauthGoogleScopes();\n}\n\nexport function getSyncPending() {\n return ipc().getSyncPending();\n}\n\nexport function getDiagnostics(): Promise<any> {\n return ipc().getDiagnostics?.() ?? Promise.resolve([]);\n}\n\n/** Account that supplies `feature` data (calendar / tasks / contacts).\n * Resolution: per-feature primary flag \u2192 catch-all `primary` \u2192 first account.\n * Pass e.g. \"calendar\" to honor `primaryCalendar:true` overrides; omit for\n * back-compat single-flag behavior. */\nexport function getPrimaryAccount(feature?: string): Promise<any> {\n return ipc().getPrimaryAccount?.(feature) ?? Promise.resolve(null);\n}\n\n// Calendar / Tasks: two-way cache. Reads return local-cached rows; writes\n// commit locally and queue a push to Google. Service layer handles drain.\nexport function getCalendarEvents(fromMs: number, toMs: number): Promise<any[]> {\n return ipc().getCalendarEvents?.(fromMs, toMs) ?? Promise.resolve([]);\n}\n/** List the user's selected Google calendars (id, name, color, primary) so\n * the sidebar can render a checkbox + icon per calendar. */\nexport function getCalendars(): Promise<Array<{ id: string; name: string; color: string; primary: boolean }>> {\n return ipc().getCalendars?.() ?? Promise.resolve([]);\n}\nexport function createCalendarEvent(ev: {\n title: string; startMs: number; endMs: number; allDay?: boolean;\n location?: string; notes?: string;\n}): Promise<{ uuid: string }> {\n return ipc().createCalendarEvent?.(ev);\n}\nexport function updateCalendarEvent(uuid: string, patch: any): Promise<{ ok: boolean }> {\n return ipc().updateCalendarEvent?.(uuid, patch);\n}\nexport function deleteCalendarEvent(uuid: string): Promise<{ ok: boolean }> {\n return ipc().deleteCalendarEvent?.(uuid);\n}\nexport function getTasks(includeCompleted = false): Promise<any[]> {\n return ipc().getTasks?.(includeCompleted) ?? Promise.resolve([]);\n}\nexport function createTask(t: { title: string; notes?: string; dueMs?: number }): Promise<{ uuid: string }> {\n return ipc().createTask?.(t);\n}\nexport function updateTask(uuid: string, patch: any): Promise<{ ok: boolean }> {\n return ipc().updateTask?.(uuid, patch);\n}\nexport function deleteTask(uuid: string): Promise<{ ok: boolean }> {\n return ipc().deleteTask?.(uuid);\n}\nexport function drainStoreSync(): Promise<{ ok: boolean }> {\n return ipc().drainStoreSync?.();\n}\n\n/** Report the currently-viewed message as spam \u2192 appends a row to\n * `~/.mailx/spam.csv`. Placeholder: no folder move, no flag change, no\n * auto-delete. Training data for a smarter pass later. */\nexport function recordSpamReport(accountId: string, uid: number, folderId: number): Promise<{ ok: boolean }> {\n return ipc().recordSpamReport?.(accountId, uid, folderId);\n}\n\nexport function getOutboxStatus() {\n return (ipc() as any).getOutboxStatus();\n}\n\nexport function listQueuedOutgoing() {\n return (ipc() as any).listQueuedOutgoing();\n}\n\nexport function cancelQueuedOutgoing(p: string) {\n return (ipc() as any).cancelQueuedOutgoing(p);\n}\n\nexport function searchContacts(query: string) {\n return ipc().searchContacts(query);\n}\n\nexport function hasCcHistoryTo(email: string): Promise<{ hasCc: boolean }> {\n return (ipc() as any).hasCcHistoryTo(email);\n}\n\nexport function hasBccHistoryTo(email: string): Promise<{ hasBcc: boolean }> {\n return (ipc() as any).hasBccHistoryTo(email);\n}\n\nexport function listContacts(query: string, page = 1, pageSize = 100) {\n return (ipc() as any).listContacts(query, page, pageSize);\n}\n\nexport function upsertContact(name: string, email: string) {\n return (ipc() as any).upsertContact(name, email);\n}\n\nexport function deleteContact(email: string) {\n return (ipc() as any).deleteContact(email);\n}\n\nexport function addPreferredContact(entry: { name: string; email: string; source?: string; organization?: string }) {\n return (ipc() as any).addPreferredContact(entry.name, entry.email, entry.source, entry.organization);\n}\n\nexport function getPriorityLists(): Promise<{ senders: string[]; domains: string[] }> {\n return (ipc() as any).getPriorityLists();\n}\n\nexport function setPrioritySender(email: string, value: boolean, name?: string): Promise<{ ok: boolean }> {\n return (ipc() as any).setPrioritySender(email, value, name);\n}\n\nexport function setPriorityDomain(domain: string, value: boolean): Promise<{ ok: boolean }> {\n return (ipc() as any).setPriorityDomain(domain, value);\n}\n\nexport function addToDenylist(email: string) {\n return (ipc() as any).addToDenylist(email);\n}\n\nexport function openLocalPath(which: \"config\" | \"log\") {\n return (ipc() as any).openLocalPath(which);\n}\n/** Open an absolute file path (under ~/.rmfmail) in the OS default\n * *text* editor \u2014 Notepad on Windows, TextEdit on Mac, $EDITOR or\n * xdg-open(.txt) on Linux. Distinct from the file's default app,\n * which for .eml is usually Outlook / a mail client. */\nexport function openInTextEditor(path: string): Promise<{ ok: boolean; opener: string; reason?: string }> {\n return (ipc() as any).openInTextEditor?.(path) ?? Promise.resolve({ ok: false, opener: \"none\", reason: \"no host\" });\n}\n\nexport function allowRemoteContent(type: string, value: string) {\n return ipc().allowRemoteContent(type, value);\n}\nexport function getUserDict(): Promise<string[]> {\n return (ipc() as any).getUserDict?.() ?? Promise.resolve([]);\n}\nexport function addUserDictWord(word: string): Promise<string[]> {\n return (ipc() as any).addUserDictWord?.(word) ?? Promise.resolve([]);\n}\nexport function addUserDictWords(words: string[]): Promise<string[]> {\n return (ipc() as any).addUserDictWords?.(words) ?? Promise.resolve([]);\n}\nexport function removeUserDictWord(word: string): Promise<string[]> {\n return (ipc() as any).removeUserDictWord?.(word) ?? Promise.resolve([]);\n}\nexport function flagSenderOrDomain(type: \"sender\" | \"domain\", value: string): Promise<{ flagged: boolean }> {\n return (ipc() as any).flagSenderOrDomain?.(type, value) ?? Promise.resolve({ flagged: false });\n}\n\nexport function deleteMessage(accountId: string, uid: number) {\n return ipc().deleteMessage?.(accountId, uid);\n}\n\nexport function deleteMessages(accountId: string, uids: number[]) {\n if (uids.length === 1) return deleteMessage(accountId, uids[0]);\n return ipc().deleteMessages?.(accountId, uids);\n}\n\nexport function moveMessages(accountId: string, uids: number[], targetFolderId: number, targetAccountId?: string) {\n if (uids.length === 1) return moveMessage(accountId, uids[0], targetFolderId, targetAccountId);\n return ipc().moveMessages?.(accountId, uids, targetFolderId, targetAccountId);\n}\n\nexport function markAsSpamMessages(accountId: string, uids: number[]): Promise<{ targetFolderId: number; moved: number }> {\n return ipc().markAsSpamMessages?.(accountId, uids);\n}\n\nexport function undeleteMessage(accountId: string, uid: number, folderId: number) {\n return ipc().undeleteMessage?.(accountId, uid, folderId);\n}\n\nexport function moveMessage(accountId: string, uid: number, targetFolderId: number, targetAccountId?: string) {\n return ipc().moveMessage?.(accountId, uid, targetFolderId, targetAccountId);\n}\n\nexport function restartServer() {\n return ipc().restart?.();\n}\n\nexport function markFolderRead(accountId: string, folderId: number) {\n return ipc().markFolderRead?.(accountId, folderId);\n}\n\nexport function createFolder(accountId: string, parentPath: string, name: string) {\n return ipc().createFolder?.(accountId, parentPath, name);\n}\n\nexport function renameFolder(accountId: string, folderId: number, newName: string) {\n return ipc().renameFolder?.(accountId, folderId, newName);\n}\n\nexport function deleteFolder(accountId: string, folderId: number) {\n return ipc().deleteFolder?.(accountId, folderId);\n}\n\nexport function moveFolderToTrash(accountId: string, folderId: number) {\n return ipc().moveFolderToTrash?.(accountId, folderId);\n}\n\nexport function emptyFolder(accountId: string, folderId: number) {\n return ipc().emptyFolder?.(accountId, folderId);\n}\n\n/** Ship a named event to the Node log as `[client] <tag> <data>`. Fire and\n * forget \u2014 never awaits, never throws, never blocks the caller. Tries two\n * paths so a broken primary channel can't swallow the trace:\n * 1. Direct bridge call (self / opener / parent mailxapi).\n * 2. parent.postMessage fallback \u2014 the main window listens and relays.\n * The fallback matters because the whole point of tracing is to diagnose\n * a broken iframe bridge; a single-path tracer that goes through that same\n * bridge is useless in exactly the case we need it. */\nexport function logClientEvent(tag: string, data?: any): void {\n let delivered = false;\n try {\n const bridge = typeof (globalThis as any).mailxapi !== \"undefined\" && (globalThis as any).mailxapi?.isApp ? (globalThis as any).mailxapi\n : (window as any).opener?.mailxapi?.isApp ? (window as any).opener.mailxapi\n : (window as any).parent?.mailxapi?.isApp ? (window as any).parent.mailxapi\n : null;\n if (bridge?.logClientEvent) {\n bridge.logClientEvent(tag, data);\n delivered = true;\n }\n } catch { /* never throw from tracing */ }\n try {\n if (window.parent && window.parent !== window) {\n (window.parent as any).postMessage({ type: \"mailx-trace\", tag, data, bridged: delivered }, \"*\");\n }\n } catch { /* */ }\n}\n\nexport function sendMessage(body: any) {\n return ipc().sendMessage?.(body);\n}\n\nexport function saveDraft(body: any) {\n return ipc().saveDraft?.(body);\n}\n\n// \u2500\u2500 Events \u2500\u2500\n\ntype EventHandler = (event: any) => void;\nconst eventHandlers: EventHandler[] = [];\n\nexport function onEvent(handler: EventHandler): () => void {\n eventHandlers.push(handler);\n return () => {\n const i = eventHandlers.indexOf(handler);\n if (i >= 0) eventHandlers.splice(i, 1);\n };\n}\n\n// \u2500\u2500 Store-bus subscriptions (mirrors packages/mailx-store/bus.ts) \u2500\u2500\n//\n// The service forwards every storeBus event over IPC as `{ _event: \"store\",\n// topic, kind, ... }`. Mirror the topic/wildcard semantics here so consumers\n// subscribe by topic instead of filtering inside a single onEvent callback.\n// Same shape as the server-side bus \u2014 that's the load-bearing property.\n\ntype StoreEvent = { topic: string; kind: string; [k: string]: any };\ntype StoreHandler = (event: StoreEvent) => void;\nconst storeSubs = new Map<string, Set<StoreHandler>>();\n\nexport function subscribeStore(topic: string, handler: StoreHandler): () => void {\n let set = storeSubs.get(topic);\n if (!set) { set = new Set(); storeSubs.set(topic, set); }\n set.add(handler);\n return () => {\n const s = storeSubs.get(topic);\n if (s) { s.delete(handler); if (s.size === 0) storeSubs.delete(topic); }\n };\n}\n\nfunction deliverStore(event: StoreEvent): void {\n const exact = storeSubs.get(event.topic);\n if (exact) for (const h of exact) { try { h(event); } catch (e) { console.error(\"[store-bus]\", e); } }\n const wild = storeSubs.get(\"*\");\n if (wild) for (const h of wild) { try { h(event); } catch (e) { console.error(\"[store-bus]\", e); } }\n}\n\nexport function connectEvents(): void {\n ipc().onEvent((event: any) => {\n if (event && event._event === \"store\") deliverStore(event as StoreEvent);\n for (const h of eventHandlers) h(event);\n });\n}\n\n// \u2500\u2500 Autocomplete \u2500\u2500\n\nexport function autocomplete(body: { subject: string; to: string; bodyText: string; cursorOffset: number }, signal?: AbortSignal) {\n return ipc().autocomplete?.(body);\n}\n\nexport function getAutocompleteSettings() {\n return ipc().getAutocompleteSettings?.();\n}\n\nexport function saveAutocompleteSettings(settings: any) {\n return ipc().saveAutocompleteSettings?.(settings);\n}\n\nexport function getVersion() {\n return ipc().getVersion();\n}\n\nexport function getSettings() {\n return ipc().getSettings();\n}\n\nexport function saveSettings(settings: any) {\n return ipc().saveSettingsData?.(settings);\n}\n\nexport function repairAccounts() {\n return ipc().repairAccounts?.();\n}\n\nexport function deleteDraft(accountId: string, draftUid: number, draftId?: string) {\n return ipc().deleteDraft?.(accountId, draftUid, draftId);\n}\n\nexport function addContact(name: string, email: string) {\n return ipc().addContact?.(name, email);\n}\n\nexport function getThreadMessages(accountId: string, threadId: string) {\n return ipc().getThreadMessages?.(accountId, threadId);\n}\n\nexport function readJsoncFile(name: string): Promise<{ content: string | null }> {\n return ipc().readJsoncFile?.(name);\n}\nexport function writeJsoncFile(name: string, content: string): Promise<{ ok: boolean }> {\n return ipc().writeJsoncFile?.(name, content);\n}\nexport function formatJsonc(content: string): Promise<{ content: string }> {\n return (ipc() as any).formatJsonc?.(content);\n}\nexport function readConfigHelp(name: string): Promise<{ content: string }> {\n return ipc().readConfigHelp?.(name) ?? Promise.resolve({ content: \"\" });\n}\nexport function unsubscribeOneClick(url: string): Promise<{ ok: boolean; status: number; statusText: string }> {\n return ipc().unsubscribeOneClick?.(url);\n}\nexport function openInWord(editId: string, html: string): Promise<{ ok: boolean; path: string; opener: string }> {\n return (ipc() as any).openInWord?.(editId, html) ?? Promise.resolve({ ok: false, path: \"\", opener: \"none\" });\n}\nexport function closeWordEdit(editId: string): Promise<void> {\n return (ipc() as any).closeWordEdit?.(editId) ?? Promise.resolve();\n}\n\n/** Show an OS-level always-on-top reminder popup via msger. Returns the\n * label of the button the user clicked. Empty string when the host\n * isn't available (browser fallback) \u2014 caller should fall back to an\n * in-WebView popup in that case. */\nexport function showReminderPopup(opts: {\n title: string;\n html: string;\n buttons: string[];\n size?: { width: number; height: number };\n pos?: { x: number; y: number };\n}): Promise<{ button: string; form?: any; reason?: string }> {\n return (ipc() as any).showReminderPopup?.(opts) ?? Promise.resolve({ button: \"\", reason: \"no host\" });\n}\n\n/** Read + delete a pending mailto: drop file, if any (P115). Used at app\n * startup so a `mailx --mailto <url>` that just spawned us doesn't lose\n * its compose payload to the daemon-fires-before-app-registers race\n * window. Subsequent live clicks arrive via the `openMailto` event. */\nexport function consumePendingMailto(): Promise<{\n to: string[]; cc: string[]; bcc: string[];\n subject: string; body: string; inReplyTo: string;\n} | null> {\n return ipc().consumePendingMailto?.() ?? Promise.resolve(null);\n}\n\n/** Run an AI text transform (translate / proofread / summarize). Returns\n * empty `text` with a `reason` when the feature is disabled or the provider\n * errors \u2014 caller should surface `reason` in a status bar, not throw. */\nexport function aiTransform(req: {\n action: \"translate\" | \"proofread\" | \"summarize\" | \"extractEvent\";\n text: string;\n targetLang?: string;\n nowISO?: string;\n}): Promise<{ text: string; reason?: string; event?: any }> {\n return ipc().aiTransform?.(req) ?? Promise.resolve({ text: \"\", reason: \"AI not available in this host\" });\n}\n\nexport function setupAccount(name: string, email: string, password: string) {\n return ipc().setupAccount?.(name, email, password);\n}\n\nexport async function getAttachment(accountId: string, uid: number, attachmentId: number, folderId?: number): Promise<{ content: string; contentType: string; filename: string }> {\n return ipc().getAttachment(accountId, uid, attachmentId, folderId);\n}\n\n/** Desktop: have the Node service save the attachment and open it with the\n * OS default app. Returns undefined when the host has no such method (real\n * browser / --server mode) so the caller can fall back to a blob download. */\nexport async function openAttachment(accountId: string, uid: number, attachmentId: number, folderId?: number): Promise<{ ok: boolean; path: string } | undefined> {\n const fn = (ipc() as any).openAttachment;\n return fn ? fn(accountId, uid, attachmentId, folderId) : undefined;\n}\n\nexport async function getDeviceAccounts(): Promise<{ email: string; name: string }[]> {\n return ipc().getDeviceAccounts?.() ?? [];\n}\n\n// Legacy exports for backward compatibility\nexport const connectWebSocket = connectEvents;\nexport const onWsEvent = onEvent;\n", "/**\n * @bobfrankston/rmf-tiny \u2014 TinyMCE adapter for rmfmail.\n *\n * MIT-licensed adapter glue. Imports TinyMCE at runtime; ships no\n * TinyMCE bytes. The user is responsible for making TinyMCE reachable\n * to their environment:\n *\n * - Desktop / Node-side install: `npm install tinymce`\n * (rmfmail's createEditor(\"tinymce\") branch dynamically imports\n * this adapter, which dynamically imports tinymce from\n * node_modules.)\n *\n * - Android / browser-only WebView: pass `cdnUrl` in the options to\n * have the adapter inject a <script src=\u2026> tag. The bytes are\n * fetched directly from Tiny's CDN (or whatever URL the user\n * configured); the host APK / page never carries them.\n *\n * The adapter implements the same `MailxEditor` shape as the Quill /\n * tiptap factories in rmfmail's editor.ts, so the host code path is\n * identical regardless of which editor is in use.\n */\n/** Load tinymce. Tries native module import first (desktop / npm-installed);\n * falls back to script-tag injection from cdnUrl. Resolves to whatever's\n * on `window.tinymce`. */\nasync function loadTinymce(opts) {\n // 1. Try the npm-installed path. Wrapped in try because in browser-only\n // environments the module specifier won't resolve and we want to\n // fall through to CDN injection.\n try {\n const mod = await import(/* @vite-ignore */ \"tinymce\");\n const tinymce = mod.default || mod;\n // Pull in the plugins TinyMCE's paste-from-Word handler needs. These\n // are side-effect imports \u2014 they register themselves on the global.\n await Promise.all([\n import(\"tinymce/themes/silver\").catch(() => { }),\n import(\"tinymce/icons/default\").catch(() => { }),\n import(\"tinymce/models/dom\").catch(() => { }),\n import(\"tinymce/plugins/paste\").catch(() => { }),\n import(\"tinymce/plugins/lists\").catch(() => { }),\n import(\"tinymce/plugins/link\").catch(() => { }),\n import(\"tinymce/plugins/table\").catch(() => { }),\n import(\"tinymce/plugins/code\").catch(() => { }),\n import(\"tinymce/plugins/image\").catch(() => { }),\n ]);\n return tinymce;\n }\n catch {\n // Fall through.\n }\n // 2. Already loaded by a prior call.\n const w = window;\n if (w.tinymce)\n return w.tinymce;\n // 3. Inject from CDN if provided.\n if (!opts.cdnUrl) {\n throw new Error(\"rmf-tiny: tinymce not installed (npm install tinymce) and no cdnUrl supplied. See README for Android setup.\");\n }\n const url = opts.apiKey && !opts.cdnUrl.includes(\"api-key\")\n ? `${opts.cdnUrl}${opts.cdnUrl.includes(\"?\") ? \"&\" : \"?\"}apiKey=${encodeURIComponent(opts.apiKey)}`\n : opts.cdnUrl;\n await new Promise((resolve, reject) => {\n const s = document.createElement(\"script\");\n s.src = url;\n s.referrerPolicy = \"origin\";\n s.onload = () => resolve();\n s.onerror = () => reject(new Error(`rmf-tiny: failed to load TinyMCE from ${url}`));\n document.head.appendChild(s);\n });\n if (!w.tinymce)\n throw new Error(\"rmf-tiny: TinyMCE script loaded but window.tinymce is missing\");\n return w.tinymce;\n}\n/** Create a TinyMCE-backed MailxEditor in `container`. */\nexport async function createTinyMceEditor(container, opts = {}) {\n const tinymce = await loadTinymce(opts);\n // TinyMCE attaches to a target element by id selector; create one\n // inside the host container so multiple editors on a page don't\n // collide and so we don't pollute the caller's element with TinyMCE\n // attributes directly.\n const target = document.createElement(\"div\");\n target.id = `rmf-tiny-${Math.random().toString(36).slice(2, 10)}`;\n target.style.cssText = \"width:100%;height:100%;\";\n container.appendChild(target);\n const editor = await new Promise((resolve) => {\n tinymce.init({\n target,\n // Word-paste fidelity is the entire point of using TinyMCE here.\n // `paste_as_text: false` keeps formatting; powerpaste isn't in\n // OSS but the standard paste plugin handles Word reasonably well.\n // All free / OSS plugins bundled in the jsDelivr tinymce@6 script.\n // No premium plugins listed \u2014 listing one without an API key\n // triggers TinyMCE's upsell dialog on every load.\n plugins: \"paste lists advlist link table code codesample image searchreplace autolink wordcount emoticons charmap insertdatetime quickbars nonbreaking directionality help\",\n toolbar: [\n \"undo redo | bold italic underline strikethrough | forecolor backcolor\",\n \"bullist numlist outdent indent | link table image code codesample | emoticons charmap | help\",\n ].join(\" | \"),\n // Include \"tools\" so wordcount and searchreplace are reachable.\n menubar: \"file edit view insert format tools\",\n // View menu \u2014 append zoom commands. fullscreen omitted: this editor\n // lives inside a compose iframe overlay that already fills its host\n // window; TinyMCE's fullscreen plugin re-positions the container in\n // ways that produce a blank body, so it's not in the plugin list.\n menu: {\n view: { title: \"View\", items: \"code | visualaid visualchars visualblocks | preview | zoomIn zoomOut zoomReset\" },\n },\n // Disable the \"Get all features\" upsell badge that TinyMCE 6+\n // injects automatically when no premium plugins are listed.\n promotion: false,\n // Floating toolbar that appears on text selection \u2014 keeps the\n // main toolbar uncluttered while exposing common formatters\n // where the cursor already is. quickbars_insert_toolbar:false\n // suppresses the second (insert-on-blank-line) popup which\n // clutters more than it helps in an email composer.\n quickbars_selection_toolbar: \"bold italic underline | forecolor | quicklink blockquote\",\n quickbars_insert_toolbar: false,\n quickbars_image_toolbar: \"alignleft aligncenter alignright\",\n // WebView's native spell-check (red underlines, right-click\n // \"Add to dictionary\"). Free; same UX as Quill's spellcheck=true.\n // Premium tinymcespellchecker plugin would replace this with a\n // custom backend via spellchecker_rpc_url, but requires a\n // Tiny Cloud subscription.\n browser_spellcheck: true,\n // Right-click context menu DISABLED so WebView2's native menu\n // fires instead. We need the native menu because that's where\n // the browser shows spelling suggestions (TinyMCE's contextmenu\n // intercepts the right-click and replaces the menu \u2014 red\n // underlines appear but suggestions are unreachable). Trade-off:\n // lose TinyMCE's link / image / table quick actions. Standard\n // text formatting is still on the toolbar.\n contextmenu: false,\n statusbar: false,\n branding: false,\n license_key: \"gpl\",\n paste_data_images: true,\n // Permissive valid_elements \u2014 preserve as much of the source\n // formatting as possible. The default is more aggressive about\n // stripping. Empty string for `valid_elements` means accept\n // everything that the schema allows.\n paste_word_valid_elements: \"@[style|class],-strong/b,-em/i,-u,-s,-sub,-sup,-strike,-p,-ol,-ul,-li,-h1,-h2,-h3,-h4,-h5,-h6,-blockquote,-table[border|cellpadding|cellspacing|width|height|class|style],-tr,-td[colspan|rowspan|width|height|class|style|valign|align|background|bgcolor],-th,-thead,-tbody,-tfoot,-pre,-br,-a[href|target|title],-img[src|alt|width|height|style|class]\",\n paste_retain_style_properties: \"color background background-color font-family font-size font-weight font-style text-decoration text-align padding padding-top padding-bottom padding-left padding-right margin margin-top margin-bottom margin-left margin-right border border-top border-bottom border-left border-right\",\n // Auto-link bare URLs in pasted content. TinyMCE's `autolink`\n // plugin only fires on TYPED space/enter; URLs that arrive via\n // clipboard (browser address bar, terminal copy) come in as\n // plain text and stay un-linked. paste_preprocess runs on the\n // HTML the paste plugin produced.\n //\n // CRITICAL: skip auto-link when the content already contains\n // anchors. Naive regex over the whole content would wrap\n // `<a href=\"X\">X</a>` in ANOTHER anchor (nested anchors are\n // invalid HTML and browsers split them, producing visible\n // junk). For HTML pastes that already have linked URLs (the\n // common case from a browser address bar or another mail\n // client), TinyMCE preserves them \u2014 no auto-link pass needed.\n // For plain-text pastes (no anchors present), wrap any bare\n // http(s)://\u2026 runs. Trailing sentence punctuation is excluded\n // from the URL.\n paste_preprocess: (_plugin, args) => {\n if (/<a[\\s>]/i.test(args.content))\n return;\n args.content = args.content.replace(/(^|[\\s(\\[])((?:https?|ftp):\\/\\/[^\\s<>\"']+[^\\s<>\"'.,;:!?)\\]])/gi, (_m, lead, url) => `${lead}<a href=\"${url}\">${url}</a>`);\n },\n // Body font + quoted-reply styling. Without explicit rules for\n // <blockquote> and div.reply the editor iframe renders the\n // quoted block with no visual distinction from the user's own\n // text \u2014 pasted reply quotes vanish into a wall of unformatted\n // paragraphs (Bob 2026-05-12: \"the body losing all formatting\").\n // The styles below match Thunderbird/Outlook conventions: a\n // muted left-bar + indent on blockquotes, slight color shift on\n // the \"On \u2026 wrote:\" attribution, untouched typography for\n // everything else so genuine HTML formatting (bold / italic /\n // tables / inline color) still comes through verbatim.\n content_style: [\n \"body { font-family: system-ui, sans-serif; font-size: 14px; }\",\n \"blockquote { border-left: 3px solid #c0c8d0; margin: 0 0 0 4px; padding: 2px 0 2px 10px; color: #555; }\",\n \"div.reply { margin-top: 0.5em; }\",\n \"div.reply > p:first-child { color: #666; font-size: 0.95em; margin: 0 0 4px 0; }\",\n \"pre, code { font-family: ui-monospace, Consolas, Menlo, monospace; }\",\n ].join(\" \"),\n init_instance_callback: (ed) => resolve(ed),\n setup: (ed) => {\n if (opts.initialHtml)\n ed.on(\"init\", () => ed.setContent(opts.initialHtml));\n // Zoom \u2014 content font-size scales between ZOOM_MIN and ZOOM_MAX.\n // Reachable via View \u2192 Zoom in/out/reset, Ctrl+wheel inside the\n // editor iframe, and Ctrl+= / Ctrl+- / Ctrl+0 shortcuts.\n const ZOOM_DEFAULT = 14;\n const ZOOM_STEP = 2;\n const ZOOM_MIN = 8;\n const ZOOM_MAX = 32;\n let zoomPx = ZOOM_DEFAULT;\n const applyZoom = () => {\n try {\n const body = ed.getBody();\n if (body)\n body.style.fontSize = `${zoomPx}px`;\n }\n catch { /* */ }\n };\n const bumpZoom = (delta) => {\n zoomPx = Math.max(ZOOM_MIN, Math.min(ZOOM_MAX, zoomPx + delta));\n applyZoom();\n };\n ed.ui.registry.addMenuItem(\"zoomIn\", {\n text: \"Zoom in\", shortcut: \"Ctrl+=\",\n onAction: () => bumpZoom(ZOOM_STEP),\n });\n ed.ui.registry.addMenuItem(\"zoomOut\", {\n text: \"Zoom out\", shortcut: \"Ctrl+-\",\n onAction: () => bumpZoom(-ZOOM_STEP),\n });\n ed.ui.registry.addMenuItem(\"zoomReset\", {\n text: \"Reset zoom\", shortcut: \"Ctrl+0\",\n onAction: () => { zoomPx = ZOOM_DEFAULT; applyZoom(); },\n });\n // Keyboard shortcuts go on the iframe doc directly. TinyMCE's\n // `addShortcut` is too late \u2014 WebView2 intercepts Ctrl+= and\n // Ctrl+- for its built-in page zoom, so we have to call\n // preventDefault in the capture phase to win the race.\n // Bound below in the \"init\" handler once the iframe doc exists.\n // Typing right after a link must not extend the link. When a\n // printable character is about to be inserted with the caret\n // collapsed at the very end of an <a>, hop the caret to just\n // after the <a> first \u2014 otherwise contenteditable keeps\n // appending into the link and the whole sentence turns into\n // link text (Bob 2026-05-18: \"I typed a URL but it then\n // included everything else I typed in the URL text\"). Covers\n // autolink, pasted URLs, and hand-made links alike.\n ed.on(\"keypress\", (e) => {\n if (e.ctrlKey || e.altKey || e.metaKey)\n return;\n const sel = ed.selection;\n const rng = sel.getRng();\n if (!rng || !rng.collapsed)\n return;\n const a = ed.dom.getParent(sel.getNode(), \"a\");\n if (!a)\n return;\n // Range from the caret to just after the <a>: no text in\n // it \u21D2 the caret sits at the link's trailing edge.\n let tail;\n try {\n tail = rng.cloneRange();\n tail.setEndAfter(a);\n }\n catch {\n return;\n }\n if (tail.toString().length > 0)\n return;\n const after = ed.getDoc().createRange();\n after.setStartAfter(a);\n after.collapse(true);\n sel.setRng(after);\n });\n ed.on(\"init\", () => {\n // Engage WebView2's native spellcheck. TinyMCE's own\n // `browser_spellcheck: true` is a no-op \u2014 its code path\n // only DISABLES spellcheck on false; on true it leaves\n // the iframe body without an explicit attribute, and\n // WebView2 doesn't engage red underlines on a fresh\n // contenteditable iframe without `spellcheck=\"true\"` set.\n try {\n const body = ed.getBody();\n if (body) {\n body.setAttribute(\"spellcheck\", \"true\");\n body.setAttribute(\"lang\", \"en\");\n }\n const doc = ed.getDoc();\n if (doc?.documentElement)\n doc.documentElement.setAttribute(\"lang\", \"en\");\n }\n catch { /* */ }\n // Ctrl+wheel zoom inside the editor iframe.\n try {\n const doc = ed.getDoc();\n doc.addEventListener(\"wheel\", (e) => {\n if (!e.ctrlKey)\n return;\n e.preventDefault();\n bumpZoom(e.deltaY < 0 ? ZOOM_STEP : -ZOOM_STEP);\n }, { passive: false });\n // Capture-phase keydown so we beat WebView2's built-in\n // page-zoom binding. `e.key` is \"=\" (or \"+\" with shift),\n // \"-\", and \"0\". MetaKey covers Mac Cmd; ctrlKey covers\n // Windows/Linux Ctrl. Numpad +/- arrive as \"+\" / \"-\"\n // already so a single check covers both.\n doc.addEventListener(\"keydown\", (e) => {\n if (!(e.ctrlKey || e.metaKey))\n return;\n if (e.key === \"=\" || e.key === \"+\") {\n e.preventDefault();\n e.stopPropagation();\n bumpZoom(ZOOM_STEP);\n }\n else if (e.key === \"-\") {\n e.preventDefault();\n e.stopPropagation();\n bumpZoom(-ZOOM_STEP);\n }\n else if (e.key === \"0\") {\n e.preventDefault();\n e.stopPropagation();\n zoomPx = ZOOM_DEFAULT;\n applyZoom();\n }\n }, true);\n }\n catch { /* */ }\n });\n },\n });\n });\n return {\n setHtml(html) { editor.setContent(html); },\n getHtml() { return editor.getContent(); },\n getText() { return editor.getContent({ format: \"text\" }); },\n focus() { editor.focus(); },\n setCursor(pos) {\n // TinyMCE works in ranges, not character indices. Map the\n // common rmfmail usages: pos === 0 \u2192 cursor at TOP of body\n // (reply / forward \u2014 user types above the quoted block);\n // anything else \u2192 cursor at END (legacy \"put cursor at end\"\n // semantics).\n const place = () => {\n const body = editor.getBody();\n if (pos === 0) {\n // Put the caret INSIDE the first block element. Collapsing\n // to raw body-start lands it outside any block (before /\n // between bare nodes) where contentEditable insertion is\n // unpredictable \u2014 Bob 2026-05-21: \"typing goes in the\n // wrong place until you try again\". rmfmail's reply body\n // now leads with a real <p>; drop the caret into it.\n const first = body.firstChild;\n if (first && first.nodeType === 1 /* element */) {\n editor.selection.setCursorLocation(first, 0);\n }\n else {\n editor.selection.select(body, true);\n editor.selection.collapse(true);\n }\n editor.focus();\n // Viewport to the top so the user looks at the empty\n // reply line, not scrolled down into the quote.\n editor.getWin()?.scrollTo(0, 0);\n }\n else {\n editor.selection.select(body, true);\n editor.selection.collapse(false);\n editor.focus();\n editor.selection.scrollIntoView();\n }\n };\n try {\n place();\n // Re-apply on the next frame. Cross-iframe focus (compose is\n // an iframe; TinyMCE's edit area is a nested iframe) lets the\n // first selection set get clobbered by a late layout / focus\n // event \u2014 the \"have to click in and try again\" symptom. A\n // second pass after the frame settles makes it stick.\n editor.getWin()?.requestAnimationFrame?.(() => {\n try {\n place();\n }\n catch { /* */ }\n });\n }\n catch { /* */ }\n },\n get root() { return editor.getContainer(); },\n on(event, handler) { editor.on(event, handler); },\n off(event, handler) { editor.off(event, handler); },\n nativeEditor: editor,\n };\n}\n//# sourceMappingURL=adapter.js.map", "/*!\n * Determine if an object is a Buffer\n *\n * @author Feross Aboukhadijeh <https://feross.org>\n * @license MIT\n */\n\nmodule.exports = function isBuffer (obj) {\n return obj != null && obj.constructor != null &&\n typeof obj.constructor.isBuffer === 'function' && obj.constructor.isBuffer(obj)\n}\n", "'use strict'\n\nmodule.exports = ruleCodes\n\nvar NO_CODES = []\n\n// Parse rule codes.\nfunction ruleCodes(flags, value) {\n var index = 0\n var result\n\n if (!value) return NO_CODES\n\n if (flags.FLAG === 'long') {\n // Creating an array of the right length immediately\n // avoiding resizes and using memory more efficiently\n result = new Array(Math.ceil(value.length / 2))\n\n while (index < value.length) {\n result[index / 2] = value.slice(index, index + 2)\n index += 2\n }\n\n return result\n }\n\n return value.split(flags.FLAG === 'num' ? ',' : '')\n}\n", "'use strict'\n\nvar parse = require('./rule-codes.js')\n\nmodule.exports = affix\n\nvar push = [].push\n\n// Relative frequencies of letters in the English language.\nvar alphabet = 'etaoinshrdlcumwfgypbvkjxqz'.split('')\n\n// Expressions.\nvar whiteSpaceExpression = /\\s+/\n\n// Defaults.\nvar defaultKeyboardLayout = [\n 'qwertzuop',\n 'yxcvbnm',\n 'qaw',\n 'say',\n 'wse',\n 'dsx',\n 'sy',\n 'edr',\n 'fdc',\n 'dx',\n 'rft',\n 'gfv',\n 'fc',\n 'tgz',\n 'hgb',\n 'gv',\n 'zhu',\n 'jhn',\n 'hb',\n 'uji',\n 'kjm',\n 'jn',\n 'iko',\n 'lkm'\n]\n\n// Parse an affix file.\n// eslint-disable-next-line complexity\nfunction affix(doc) {\n var rules = Object.create(null)\n var compoundRuleCodes = Object.create(null)\n var flags = Object.create(null)\n var replacementTable = []\n var conversion = {in: [], out: []}\n var compoundRules = []\n var aff = doc.toString('utf8')\n var lines = []\n var last = 0\n var index = aff.indexOf('\\n')\n var parts\n var line\n var ruleType\n var count\n var remove\n var add\n var source\n var entry\n var position\n var rule\n var value\n var offset\n var character\n\n flags.KEY = []\n\n // Process the affix buffer into a list of applicable lines.\n while (index > -1) {\n pushLine(aff.slice(last, index))\n last = index + 1\n index = aff.indexOf('\\n', last)\n }\n\n pushLine(aff.slice(last))\n\n // Process each line.\n index = -1\n\n while (++index < lines.length) {\n line = lines[index]\n parts = line.split(whiteSpaceExpression)\n ruleType = parts[0]\n\n if (ruleType === 'REP') {\n count = index + parseInt(parts[1], 10)\n\n while (++index <= count) {\n parts = lines[index].split(whiteSpaceExpression)\n replacementTable.push([parts[1], parts[2]])\n }\n\n index--\n } else if (ruleType === 'ICONV' || ruleType === 'OCONV') {\n count = index + parseInt(parts[1], 10)\n entry = conversion[ruleType === 'ICONV' ? 'in' : 'out']\n\n while (++index <= count) {\n parts = lines[index].split(whiteSpaceExpression)\n entry.push([new RegExp(parts[1], 'g'), parts[2]])\n }\n\n index--\n } else if (ruleType === 'COMPOUNDRULE') {\n count = index + parseInt(parts[1], 10)\n\n while (++index <= count) {\n rule = lines[index].split(whiteSpaceExpression)[1]\n position = -1\n\n compoundRules.push(rule)\n\n while (++position < rule.length) {\n compoundRuleCodes[rule.charAt(position)] = []\n }\n }\n\n index--\n } else if (ruleType === 'PFX' || ruleType === 'SFX') {\n count = index + parseInt(parts[3], 10)\n\n rule = {\n type: ruleType,\n combineable: parts[2] === 'Y',\n entries: []\n }\n\n rules[parts[1]] = rule\n\n while (++index <= count) {\n parts = lines[index].split(whiteSpaceExpression)\n remove = parts[2]\n add = parts[3].split('/')\n source = parts[4]\n\n entry = {\n add: '',\n remove: '',\n match: '',\n continuation: parse(flags, add[1])\n }\n\n if (add && add[0] !== '0') {\n entry.add = add[0]\n }\n\n try {\n if (remove !== '0') {\n entry.remove = ruleType === 'SFX' ? end(remove) : remove\n }\n\n if (source && source !== '.') {\n entry.match = ruleType === 'SFX' ? end(source) : start(source)\n }\n } catch (_) {\n // Ignore invalid regex patterns.\n entry = null\n }\n\n if (entry) {\n rule.entries.push(entry)\n }\n }\n\n index--\n } else if (ruleType === 'TRY') {\n source = parts[1]\n offset = -1\n value = []\n\n while (++offset < source.length) {\n character = source.charAt(offset)\n\n if (character.toLowerCase() === character) {\n value.push(character)\n }\n }\n\n // Some dictionaries may forget a character.\n // Notably `en` forgets `j`, `x`, and `y`.\n offset = -1\n\n while (++offset < alphabet.length) {\n if (source.indexOf(alphabet[offset]) < 0) {\n value.push(alphabet[offset])\n }\n }\n\n flags[ruleType] = value\n } else if (ruleType === 'KEY') {\n push.apply(flags[ruleType], parts[1].split('|'))\n } else if (ruleType === 'COMPOUNDMIN') {\n flags[ruleType] = Number(parts[1])\n } else if (ruleType === 'ONLYINCOMPOUND') {\n // If we add this ONLYINCOMPOUND flag to `compoundRuleCodes`, then\n // `parseDic` will do the work of saving the list of words that are\n // compound-only.\n flags[ruleType] = parts[1]\n compoundRuleCodes[parts[1]] = []\n } else if (\n ruleType === 'FLAG' ||\n ruleType === 'KEEPCASE' ||\n ruleType === 'NOSUGGEST' ||\n ruleType === 'WORDCHARS'\n ) {\n flags[ruleType] = parts[1]\n } else {\n // Default handling: set them for now.\n flags[ruleType] = parts[1]\n }\n }\n\n // Default for `COMPOUNDMIN` is `3`.\n // See `man 4 hunspell`.\n if (isNaN(flags.COMPOUNDMIN)) {\n flags.COMPOUNDMIN = 3\n }\n\n if (!flags.KEY.length) {\n flags.KEY = defaultKeyboardLayout\n }\n\n /* istanbul ignore if - Dictionaries seem to always have this. */\n if (!flags.TRY) {\n flags.TRY = alphabet.concat()\n }\n\n if (!flags.KEEPCASE) {\n flags.KEEPCASE = false\n }\n\n return {\n compoundRuleCodes: compoundRuleCodes,\n replacementTable: replacementTable,\n conversion: conversion,\n compoundRules: compoundRules,\n rules: rules,\n flags: flags\n }\n\n function pushLine(line) {\n line = line.trim()\n\n // Hash can be a valid flag, so we only discard line that starts with it.\n if (line && line.charCodeAt(0) !== 35 /* `#` */) {\n lines.push(line)\n }\n }\n}\n\n// Wrap the `source` of an expression-like string so that it matches only at\n// the end of a value.\nfunction end(source) {\n return new RegExp(source + '$')\n}\n\n// Wrap the `source` of an expression-like string so that it matches only at\n// the start of a value.\nfunction start(source) {\n return new RegExp('^' + source)\n}\n", "'use strict'\n\nmodule.exports = normalize\n\n// Normalize `value` with patterns.\nfunction normalize(value, patterns) {\n var index = -1\n\n while (++index < patterns.length) {\n value = value.replace(patterns[index][0], patterns[index][1])\n }\n\n return value\n}\n", "'use strict'\n\nmodule.exports = flag\n\n// Check whether a word has a flag.\nfunction flag(values, value, flags) {\n return flags && value in values && flags.indexOf(values[value]) > -1\n}\n", "'use strict'\n\nvar flag = require('./flag.js')\n\nmodule.exports = exact\n\n// Check spelling of `value`, exactly.\nfunction exact(context, value) {\n var index = -1\n\n if (context.data[value]) {\n return !flag(context.flags, 'ONLYINCOMPOUND', context.data[value])\n }\n\n // Check if this might be a compound word.\n if (value.length >= context.flags.COMPOUNDMIN) {\n while (++index < context.compoundRules.length) {\n if (context.compoundRules[index].test(value)) {\n return true\n }\n }\n }\n\n return false\n}\n", "'use strict'\n\nvar normalize = require('./normalize.js')\nvar exact = require('./exact.js')\nvar flag = require('./flag.js')\n\nmodule.exports = form\n\n// Find a known form of `value`.\nfunction form(context, value, all) {\n var normal = value.trim()\n var alternative\n\n if (!normal) {\n return null\n }\n\n normal = normalize(normal, context.conversion.in)\n\n if (exact(context, normal)) {\n if (!all && flag(context.flags, 'FORBIDDENWORD', context.data[normal])) {\n return null\n }\n\n return normal\n }\n\n // Try sentence case if the value is uppercase.\n if (normal.toUpperCase() === normal) {\n alternative = normal.charAt(0) + normal.slice(1).toLowerCase()\n\n if (ignore(context.flags, context.data[alternative], all)) {\n return null\n }\n\n if (exact(context, alternative)) {\n return alternative\n }\n }\n\n // Try lowercase.\n alternative = normal.toLowerCase()\n\n if (alternative !== normal) {\n if (ignore(context.flags, context.data[alternative], all)) {\n return null\n }\n\n if (exact(context, alternative)) {\n return alternative\n }\n }\n\n return null\n}\n\nfunction ignore(flags, dict, all) {\n return (\n flag(flags, 'KEEPCASE', dict) || all || flag(flags, 'FORBIDDENWORD', dict)\n )\n}\n", "'use strict'\n\nvar form = require('./util/form.js')\n\nmodule.exports = correct\n\n// Check spelling of `value`.\nfunction correct(value) {\n return Boolean(form(this, value))\n}\n", "'use strict'\n\nmodule.exports = casing\n\n// Get the casing of `value`.\nfunction casing(value) {\n var head = exact(value.charAt(0))\n var rest = value.slice(1)\n\n if (!rest) {\n return head\n }\n\n rest = exact(rest)\n\n if (head === rest) {\n return head\n }\n\n if (head === 'u' && rest === 'l') {\n return 's'\n }\n\n return null\n}\n\nfunction exact(value) {\n return value === value.toLowerCase()\n ? 'l'\n : value === value.toUpperCase()\n ? 'u'\n : null\n}\n", "'use strict'\n\nvar casing = require('./util/casing.js')\nvar normalize = require('./util/normalize.js')\nvar flag = require('./util/flag.js')\nvar form = require('./util/form.js')\n\nmodule.exports = suggest\n\nvar push = [].push\n\n// Suggest spelling for `value`.\n// eslint-disable-next-line complexity\nfunction suggest(value) {\n var self = this\n var charAdded = {}\n var suggestions = []\n var weighted = {}\n var memory\n var replacement\n var edits = []\n var values\n var index\n var offset\n var position\n var count\n var otherOffset\n var otherCharacter\n var character\n var group\n var before\n var after\n var upper\n var insensitive\n var firstLevel\n var previous\n var next\n var nextCharacter\n var max\n var distance\n var size\n var normalized\n var suggestion\n var currentCase\n\n value = normalize(value.trim(), self.conversion.in)\n\n if (!value || self.correct(value)) {\n return []\n }\n\n currentCase = casing(value)\n\n // Check the replacement table.\n index = -1\n\n while (++index < self.replacementTable.length) {\n replacement = self.replacementTable[index]\n offset = value.indexOf(replacement[0])\n\n while (offset > -1) {\n edits.push(value.replace(replacement[0], replacement[1]))\n offset = value.indexOf(replacement[0], offset + 1)\n }\n }\n\n // Check the keyboard.\n index = -1\n\n while (++index < value.length) {\n character = value.charAt(index)\n before = value.slice(0, index)\n after = value.slice(index + 1)\n insensitive = character.toLowerCase()\n upper = insensitive !== character\n charAdded = {}\n\n offset = -1\n\n while (++offset < self.flags.KEY.length) {\n group = self.flags.KEY[offset]\n position = group.indexOf(insensitive)\n\n if (position < 0) {\n continue\n }\n\n otherOffset = -1\n\n while (++otherOffset < group.length) {\n if (otherOffset !== position) {\n otherCharacter = group.charAt(otherOffset)\n\n if (charAdded[otherCharacter]) {\n continue\n }\n\n charAdded[otherCharacter] = true\n\n if (upper) {\n otherCharacter = otherCharacter.toUpperCase()\n }\n\n edits.push(before + otherCharacter + after)\n }\n }\n }\n }\n\n // Check cases where one of a double character was forgotten, or one too many\n // were added, up to three \u201Cdistances\u201D. This increases the success-rate by 2%\n // and speeds the process up by 13%.\n index = -1\n nextCharacter = value.charAt(0)\n values = ['']\n max = 1\n distance = 0\n\n while (++index < value.length) {\n character = nextCharacter\n nextCharacter = value.charAt(index + 1)\n before = value.slice(0, index)\n\n replacement = character === nextCharacter ? '' : character + character\n offset = -1\n count = values.length\n\n while (++offset < count) {\n if (offset <= max) {\n values.push(values[offset] + replacement)\n }\n\n values[offset] += character\n }\n\n if (++distance < 3) {\n max = values.length\n }\n }\n\n push.apply(edits, values)\n\n // Ensure the capitalised and uppercase values are included.\n values = [value]\n replacement = value.toLowerCase()\n\n if (value === replacement || currentCase === null) {\n values.push(value.charAt(0).toUpperCase() + replacement.slice(1))\n }\n\n replacement = value.toUpperCase()\n\n if (value !== replacement) {\n values.push(replacement)\n }\n\n // Construct a memory object for `generate`.\n memory = {\n state: {},\n weighted: weighted,\n suggestions: suggestions\n }\n\n firstLevel = generate(self, memory, values, edits)\n\n // While there are no suggestions based on generated values with an\n // edit-distance of `1`, check the generated values, `SIZE` at a time.\n // Basically, we\u2019re generating values with an edit-distance of `2`, but were\n // doing it in small batches because it\u2019s such an expensive operation.\n previous = 0\n max = Math.min(firstLevel.length, Math.pow(Math.max(15 - value.length, 3), 3))\n size = Math.max(Math.pow(10 - value.length, 3), 1)\n\n while (!suggestions.length && previous < max) {\n next = previous + size\n generate(self, memory, firstLevel.slice(previous, next))\n previous = next\n }\n\n // Sort the suggestions based on their weight.\n suggestions.sort(sort)\n\n // Normalize the output.\n values = []\n normalized = []\n index = -1\n\n while (++index < suggestions.length) {\n suggestion = normalize(suggestions[index], self.conversion.out)\n replacement = suggestion.toLowerCase()\n\n if (normalized.indexOf(replacement) < 0) {\n values.push(suggestion)\n normalized.push(replacement)\n }\n }\n\n // BOOM! All done!\n return values\n\n function sort(a, b) {\n return sortWeight(a, b) || sortCasing(a, b) || sortAlpha(a, b)\n }\n\n function sortWeight(a, b) {\n return weighted[a] === weighted[b] ? 0 : weighted[a] > weighted[b] ? -1 : 1\n }\n\n function sortCasing(a, b) {\n var leftCasing = casing(a)\n var rightCasing = casing(b)\n\n return leftCasing === rightCasing\n ? 0\n : leftCasing === currentCase\n ? -1\n : rightCasing === currentCase\n ? 1\n : undefined\n }\n\n function sortAlpha(a, b) {\n return a.localeCompare(b)\n }\n}\n\n// Get a list of values close in edit distance to `words`.\nfunction generate(context, memory, words, edits) {\n var characters = context.flags.TRY\n var data = context.data\n var flags = context.flags\n var result = []\n var index = -1\n var word\n var before\n var character\n var nextCharacter\n var nextAfter\n var nextNextAfter\n var nextUpper\n var currentCase\n var position\n var after\n var upper\n var inject\n var offset\n\n // Check the pre-generated edits.\n if (edits) {\n while (++index < edits.length) {\n check(edits[index], true)\n }\n }\n\n // Iterate over given word.\n index = -1\n\n while (++index < words.length) {\n word = words[index]\n before = ''\n character = ''\n nextCharacter = word.charAt(0)\n nextAfter = word\n nextNextAfter = word.slice(1)\n nextUpper = nextCharacter.toLowerCase() !== nextCharacter\n currentCase = casing(word)\n position = -1\n\n // Iterate over every character (including the end).\n while (++position <= word.length) {\n before += character\n after = nextAfter\n nextAfter = nextNextAfter\n nextNextAfter = nextAfter.slice(1)\n character = nextCharacter\n nextCharacter = word.charAt(position + 1)\n upper = nextUpper\n\n if (nextCharacter) {\n nextUpper = nextCharacter.toLowerCase() !== nextCharacter\n }\n\n if (nextAfter && upper !== nextUpper) {\n // Remove.\n check(before + switchCase(nextAfter))\n\n // Switch.\n check(\n before +\n switchCase(nextCharacter) +\n switchCase(character) +\n nextNextAfter\n )\n }\n\n // Remove.\n check(before + nextAfter)\n\n // Switch.\n if (nextAfter) {\n check(before + nextCharacter + character + nextNextAfter)\n }\n\n // Iterate over all possible letters.\n offset = -1\n\n while (++offset < characters.length) {\n inject = characters[offset]\n\n // Try uppercase if the original character was uppercased.\n if (upper && inject !== inject.toUpperCase()) {\n if (currentCase !== 's') {\n check(before + inject + after)\n check(before + inject + nextAfter)\n }\n\n inject = inject.toUpperCase()\n\n check(before + inject + after)\n check(before + inject + nextAfter)\n } else {\n // Add and replace.\n check(before + inject + after)\n check(before + inject + nextAfter)\n }\n }\n }\n }\n\n // Return the list of generated words.\n return result\n\n // Check and handle a generated value.\n function check(value, double) {\n var state = memory.state[value]\n var corrected\n\n if (state !== Boolean(state)) {\n result.push(value)\n\n corrected = form(context, value)\n state = corrected && !flag(flags, 'NOSUGGEST', data[corrected])\n\n memory.state[value] = state\n\n if (state) {\n memory.weighted[value] = double ? 10 : 0\n memory.suggestions.push(value)\n }\n }\n\n if (state) {\n memory.weighted[value]++\n }\n }\n\n function switchCase(fragment) {\n var first = fragment.charAt(0)\n\n return (\n (first.toLowerCase() === first\n ? first.toUpperCase()\n : first.toLowerCase()) + fragment.slice(1)\n )\n }\n}\n", "'use strict'\n\nvar form = require('./util/form.js')\nvar flag = require('./util/flag.js')\n\nmodule.exports = spell\n\n// Check spelling of `word`.\nfunction spell(word) {\n var self = this\n var value = form(self, word, true)\n\n // Hunspell also provides `root` (root word of the input word), and `compound`\n // (whether `word` was compound).\n return {\n correct: self.correct(word),\n forbidden: Boolean(\n value && flag(self.flags, 'FORBIDDENWORD', self.data[value])\n ),\n warn: Boolean(value && flag(self.flags, 'WARN', self.data[value]))\n }\n}\n", "'use strict'\n\nmodule.exports = apply\n\n// Apply a rule.\nfunction apply(value, rule, rules, words) {\n var index = -1\n var entry\n var next\n var continuationRule\n var continuation\n var position\n\n while (++index < rule.entries.length) {\n entry = rule.entries[index]\n continuation = entry.continuation\n position = -1\n\n if (!entry.match || entry.match.test(value)) {\n next = entry.remove ? value.replace(entry.remove, '') : value\n next = rule.type === 'SFX' ? next + entry.add : entry.add + next\n words.push(next)\n\n if (continuation && continuation.length) {\n while (++position < continuation.length) {\n continuationRule = rules[continuation[position]]\n\n if (continuationRule) {\n apply(next, continuationRule, rules, words)\n }\n }\n }\n }\n }\n\n return words\n}\n", "'use strict'\n\nvar apply = require('./apply.js')\n\nmodule.exports = add\n\nvar push = [].push\n\nvar NO_RULES = []\n\n// Add `rules` for `word` to the table.\nfunction addRules(dict, word, rules) {\n var curr = dict[word]\n\n // Some dictionaries will list the same word multiple times with different\n // rule sets.\n if (word in dict) {\n if (curr === NO_RULES) {\n dict[word] = rules.concat()\n } else {\n push.apply(curr, rules)\n }\n } else {\n dict[word] = rules.concat()\n }\n}\n\nfunction add(dict, word, codes, options) {\n var position = -1\n var rule\n var offset\n var subposition\n var suboffset\n var combined\n var newWords\n var otherNewWords\n\n // Compound words.\n if (\n !('NEEDAFFIX' in options.flags) ||\n codes.indexOf(options.flags.NEEDAFFIX) < 0\n ) {\n addRules(dict, word, codes)\n }\n\n while (++position < codes.length) {\n rule = options.rules[codes[position]]\n\n if (codes[position] in options.compoundRuleCodes) {\n options.compoundRuleCodes[codes[position]].push(word)\n }\n\n if (rule) {\n newWords = apply(word, rule, options.rules, [])\n offset = -1\n\n while (++offset < newWords.length) {\n if (!(newWords[offset] in dict)) {\n dict[newWords[offset]] = NO_RULES\n }\n\n if (rule.combineable) {\n subposition = position\n\n while (++subposition < codes.length) {\n combined = options.rules[codes[subposition]]\n\n if (\n combined &&\n combined.combineable &&\n rule.type !== combined.type\n ) {\n otherNewWords = apply(\n newWords[offset],\n combined,\n options.rules,\n []\n )\n suboffset = -1\n\n while (++suboffset < otherNewWords.length) {\n if (!(otherNewWords[suboffset] in dict)) {\n dict[otherNewWords[suboffset]] = NO_RULES\n }\n }\n }\n }\n }\n }\n }\n }\n}\n", "'use strict'\n\nvar push = require('./util/add.js')\n\nmodule.exports = add\n\nvar NO_CODES = []\n\n// Add `value` to the checker.\nfunction add(value, model) {\n var self = this\n\n push(self.data, value, self.data[model] || NO_CODES, self)\n\n return self\n}\n", "'use strict'\n\nmodule.exports = remove\n\n// Remove `value` from the checker.\nfunction remove(value) {\n var self = this\n\n delete self.data[value]\n\n return self\n}\n", "'use strict'\n\nmodule.exports = wordCharacters\n\n// Get the word characters defined in affix.\nfunction wordCharacters() {\n return this.flags.WORDCHARS || null\n}\n", "'use strict'\n\nvar parseCodes = require('./rule-codes.js')\nvar add = require('./add.js')\n\nmodule.exports = parse\n\n// Expressions.\nvar whiteSpaceExpression = /\\s/g\n\n// Parse a dictionary.\nfunction parse(buf, options, dict) {\n // Parse as lines (ignoring the first line).\n var value = buf.toString('utf8')\n var last = value.indexOf('\\n') + 1\n var index = value.indexOf('\\n', last)\n\n while (index > -1) {\n // Some dictionaries use tabs as comments.\n if (value.charCodeAt(last) !== 9 /* `\\t` */) {\n parseLine(value.slice(last, index), options, dict)\n }\n\n last = index + 1\n index = value.indexOf('\\n', last)\n }\n\n parseLine(value.slice(last), options, dict)\n}\n\n// Parse a line in dictionary.\nfunction parseLine(line, options, dict) {\n var slashOffset = line.indexOf('/')\n var hashOffset = line.indexOf('#')\n var codes = ''\n var word\n var result\n\n // Find offsets.\n while (\n slashOffset > -1 &&\n line.charCodeAt(slashOffset - 1) === 92 /* `\\` */\n ) {\n line = line.slice(0, slashOffset - 1) + line.slice(slashOffset)\n slashOffset = line.indexOf('/', slashOffset)\n }\n\n // Handle hash and slash offsets.\n // Note that hash can be a valid flag, so we should not just discard\n // everything after it.\n if (hashOffset > -1) {\n if (slashOffset > -1 && slashOffset < hashOffset) {\n word = line.slice(0, slashOffset)\n whiteSpaceExpression.lastIndex = slashOffset + 1\n result = whiteSpaceExpression.exec(line)\n codes = line.slice(slashOffset + 1, result ? result.index : undefined)\n } else {\n word = line.slice(0, hashOffset)\n }\n } else if (slashOffset > -1) {\n word = line.slice(0, slashOffset)\n codes = line.slice(slashOffset + 1)\n } else {\n word = line\n }\n\n word = word.trim()\n\n if (word) {\n add(dict, word, parseCodes(options.flags, codes.trim()), options)\n }\n}\n", "'use strict'\n\nvar parse = require('./util/dictionary.js')\n\nmodule.exports = add\n\n// Add a dictionary file.\nfunction add(buf) {\n var self = this\n var index = -1\n var rule\n var source\n var character\n var offset\n\n parse(buf, self, self.data)\n\n // Regenerate compound expressions.\n while (++index < self.compoundRules.length) {\n rule = self.compoundRules[index]\n source = ''\n offset = -1\n\n while (++offset < rule.length) {\n character = rule.charAt(offset)\n source += self.compoundRuleCodes[character].length\n ? '(?:' + self.compoundRuleCodes[character].join('|') + ')'\n : character\n }\n\n self.compoundRules[index] = new RegExp(source, 'i')\n }\n\n return self\n}\n", "'use strict'\n\nmodule.exports = add\n\n// Add a dictionary.\nfunction add(buf) {\n var self = this\n var lines = buf.toString('utf8').split('\\n')\n var index = -1\n var line\n var forbidden\n var word\n var flag\n\n // Ensure there\u2019s a key for `FORBIDDENWORD`: `false` cannot be set through an\n // affix file so its safe to use as a magic constant.\n if (self.flags.FORBIDDENWORD === undefined) self.flags.FORBIDDENWORD = false\n flag = self.flags.FORBIDDENWORD\n\n while (++index < lines.length) {\n line = lines[index].trim()\n\n if (!line) {\n continue\n }\n\n line = line.split('/')\n word = line[0]\n forbidden = word.charAt(0) === '*'\n\n if (forbidden) {\n word = word.slice(1)\n }\n\n self.add(word, line[1])\n\n if (forbidden) {\n self.data[word].push(flag)\n }\n }\n\n return self\n}\n", "'use strict'\n\nvar buffer = require('is-buffer')\nvar affix = require('./util/affix.js')\n\nmodule.exports = NSpell\n\nvar proto = NSpell.prototype\n\nproto.correct = require('./correct.js')\nproto.suggest = require('./suggest.js')\nproto.spell = require('./spell.js')\nproto.add = require('./add.js')\nproto.remove = require('./remove.js')\nproto.wordCharacters = require('./word-characters.js')\nproto.dictionary = require('./dictionary.js')\nproto.personal = require('./personal.js')\n\n// Construct a new spelling context.\nfunction NSpell(aff, dic) {\n var index = -1\n var dictionaries\n\n if (!(this instanceof NSpell)) {\n return new NSpell(aff, dic)\n }\n\n if (typeof aff === 'string' || buffer(aff)) {\n if (typeof dic === 'string' || buffer(dic)) {\n dictionaries = [{dic: dic}]\n }\n } else if (aff) {\n if ('length' in aff) {\n dictionaries = aff\n aff = aff[0] && aff[0].aff\n } else {\n if (aff.dic) {\n dictionaries = [aff]\n }\n\n aff = aff.aff\n }\n }\n\n if (!aff) {\n throw new Error('Missing `aff` in dictionary')\n }\n\n aff = affix(aff)\n\n this.data = Object.create(null)\n this.compoundRuleCodes = aff.compoundRuleCodes\n this.replacementTable = aff.replacementTable\n this.conversion = aff.conversion\n this.compoundRules = aff.compoundRules\n this.rules = aff.rules\n this.flags = aff.flags\n\n if (dictionaries) {\n while (++index < dictionaries.length) {\n if (dictionaries[index].dic) {\n this.dictionary(dictionaries[index].dic)\n }\n }\n }\n}\n", "/**\n * Live spell-check for the TinyMCE compose editor.\n *\n * Why this exists: WebView2's built-in spell checker isn't reliably\n * enabled on our msger-hosted compose iframe (see msger main.rs around\n * AreDefaultContextMenusEnabled \u2014 the comment claims it leaves\n * defaults for spell-suggest menus, but `IsSpellcheckEnabled` is never\n * explicitly set and the default varies by WebView2 runtime). Rather\n * than depend on the host, we ship a JS spellchecker (`nspell`) + the\n * standard `dictionary-en` Hunspell files.\n *\n * Two layers:\n * 1. Live decoration \u2014 words that nspell flags get wrapped in a\n * `<span data-mailx-spellerror=\"1\">` while editing. CSS gives them\n * a wavy red underline (text-decoration: underline wavy #d33). The\n * markers are stripped at serialization time so they never reach\n * the sent message or the saved draft.\n * 2. Right-click \u2014 clicking on a marker shows our own popup with\n * suggestions plus \"Add to dictionary\" / \"Ignore (this session)\".\n *\n * Decoration runs:\n * - Once after the editor inits + dict loads (initial scan of pre-\n * populated quote / signature, though we skip blockquote content).\n * - Debounced after every input (~500 ms idle).\n * - After setContent (paste, reply-quote insertion, \u2026).\n *\n * The decoration walker:\n * - Skips `<blockquote>` (the quoted reply \u2014 not the user's words),\n * `<code>`, `<pre>`, `<a>` (URLs aren't natural-language words),\n * and content inside any existing marker (so re-scans don't double-\n * wrap).\n * - Uses TinyMCE's `undoManager.ignore` + selection bookmarks so the\n * decoration mutations don't pollute undo and don't move the caret.\n *\n * Dictionary persistence:\n * - User additions: `localStorage[\"mailx-user-dict\"]` (a JSON array).\n * - \"Ignore this session\": in-memory only via `nspell.add()`.\n */\n// @ts-expect-error \u2014 nspell ships no type defs. Treated as `any`; the\n// surface we use (`new NSpell({aff, dic})`, `.correct`, `.suggest`,\n// `.add`) is small and stable.\nimport NSpell from \"nspell\";\nimport { getUserDict, addUserDictWord, addUserDictWords } from \"../lib/api-client.js\";\ntype NSpell = any;\n\n// Cloud-mirrored dictionary. `userdict.csv` on GDrive (a plain one-word-\n// per-line list) is the source of truth; the localStorage entry is a\n// write-through cache so the popup of suggestions can resolve synchronously\n// while the service round-trips the add.\nconst USER_DICT_KEY = \"mailx-user-dict\";\nconst MARKER_ATTR = \"data-mailx-spellerror\";\n// Long enough that a mid-word pause doesn't fire decoration. Bob 2026-05-12:\n// \"popping up the redlines too quickly and then very slow about removing\n// them and you keep extending them. At least wait till I finish typing a\n// word.\" 500 ms was too eager. 1200 ms feels reactive after pause but\n// stays out of the way while typing.\nconst DECORATE_DEBOUNCE_MS = 1200;\n// Removal-only cleanup runs on a much shorter debounce than the full\n// decorate pass. Removing a corrected word's red underline can't thrash\n// the way *adding* underlines mid-typing can, so it doesn't need the calm\n// 1200 ms \u2014 a corrected word's underline clearing in ~0.3 s instead of\n// 1.2 s is the difference Bob asked about (2026-05-17).\nconst CLEANUP_DEBOUNCE_MS = 300;\nconst MIN_WORD_LEN = 3;\nconst SKIP_TAGS = new Set([\"BLOCKQUOTE\", \"CODE\", \"PRE\", \"A\", \"SCRIPT\", \"STYLE\", \"KBD\", \"SAMP\", \"VAR\"]);\n\nlet spellPromise: Promise<NSpell> | null = null;\nasync function getSpell(): Promise<NSpell> {\n if (spellPromise) return spellPromise;\n spellPromise = (async () => {\n const [affRes, dicRes] = await Promise.all([\n fetch(\"../lib/dict/en.aff\"),\n fetch(\"../lib/dict/en.dic\"),\n ]);\n if (!affRes.ok || !dicRes.ok) {\n throw new Error(`spellcheck: dict fetch failed (aff=${affRes.status} dic=${dicRes.status})`);\n }\n const [aff, dic] = await Promise.all([affRes.text(), dicRes.text()]);\n const sp = new NSpell({ aff, dic });\n // 1) Seed from local cache so the editor never has to wait on\n // network for known-correct words to disappear from the\n // redline pass.\n try {\n const raw = localStorage.getItem(USER_DICT_KEY);\n if (raw) for (const w of JSON.parse(raw) as string[]) sp.add(w);\n } catch { /* corrupt cache \u2014 start clean */ }\n // 2) Pull the cloud copy, union it in, and reconcile. Fire-and-forget\n // \u2014 if it fails the cache still works.\n getUserDict().then(cloud => {\n const cloudArr = Array.isArray(cloud) ? cloud : [];\n for (const w of cloudArr) sp.add(w);\n // Read this machine's local cache.\n let local: string[] = [];\n try {\n const raw = localStorage.getItem(USER_DICT_KEY);\n local = raw ? (JSON.parse(raw) as string[]) : [];\n } catch { local = []; }\n // Reconcile up: words that exist only in localStorage (e.g. added\n // on a build where the cloud round-trip was a silent no-op) get\n // pushed to the server so they land in userdict.csv.\n const cloudSet = new Set(cloudArr);\n const localOnly = local.filter(w => !cloudSet.has(w));\n if (localOnly.length > 0) {\n addUserDictWords(localOnly).catch(e => console.error(\"[spell] reconcile:\", e));\n }\n // Refresh the cache with the union so the next boot starts whole.\n try {\n const merged = [...new Set([...local, ...cloudArr])];\n localStorage.setItem(USER_DICT_KEY, JSON.stringify(merged));\n } catch { /* */ }\n }).catch(() => { /* offline / no cloud \u2014 that's fine */ });\n return sp;\n })();\n return spellPromise;\n}\n\nfunction addToUserDict(word: string, sp: NSpell): void {\n // Local cache: synchronous, so suggestions disappear immediately.\n try {\n const raw = localStorage.getItem(USER_DICT_KEY);\n const arr = raw ? (JSON.parse(raw) as string[]) : [];\n if (!arr.includes(word)) {\n arr.push(word);\n localStorage.setItem(USER_DICT_KEY, JSON.stringify(arr));\n }\n } catch { /* */ }\n sp.add(word);\n // Cloud: fire-and-forget so the right-click \"Add\" doesn't block the\n // editor. The service merges with the existing cloud copy so concurrent\n // adds from a second machine don't lose entries.\n addUserDictWord(word).catch(e => console.error(\"[spell] addUserDictWord:\", e));\n}\n\n// \u2500\u2500 Live decoration \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/** Walk the editor body, wrap newly-misspelled words, unwrap markers\n * that are now correct. Mutations are wrapped in undoManager.ignore so\n * they don't pollute undo history; selection is preserved via a\n * TinyMCE bookmark. */\nfunction decorate(editor: any, sp: NSpell): void {\n const body: HTMLElement | null = editor.getBody?.();\n const doc: Document | null = editor.getDoc?.();\n if (!body || !doc) return;\n\n // \"Don't fight active typing\" is enforced WORD-LEVEL by the walker's\n // caret-in-word skip below \u2014 NOT by a function-level early-return.\n // The earlier function-level bail (skip the whole pass if the caret\n // sits in any marker) had a worse failure mode: once a marker existed\n // and the caret stayed inside it, browsers extended the marker as\n // adjacent text was typed, and decorate never ran to unwrap-and-\n // re-mark, so the underline grew to span entire sentences and\n // persisted (Bob 2026-05-22 \"the persistent spellcheck twiddle is\n // not going away\"). The walker already skips the specific word the\n // caret is on; everything else is fair game.\n\n // Caret preservation: TinyMCE's getBookmark(2) + moveToBookmark\n // claimed to be mutation-safe, but in practice it landed the caret\n // at the START of the nearest wrapped span (Bob 2026-05-12: \"you\n // violently yank the cursor and plop it down at the beginning of\n // the twiddle\"). Replace with an absolute-character-offset save\n // before mutations and a walk-to-restore after \u2014 robust against\n // any reshape that preserves the text content (which our wrap/\n // unwrap operations do by construction).\n const savedAbs = caretAbsOffsetFromBody(body);\n // Scroll preservation: unwrap \u2192 normalize \u2192 wrap reshapes the DOM\n // around the caret. Browsers (incl. WebView2) often scroll the new\n // selection into view, which yanks the editor viewport to the top\n // of the document when the restored caret lands above the previous\n // viewport. Save scrollTop on both the editor doc's scrolling element\n // AND the body (TinyMCE sometimes uses one or the other depending\n // on theme/skin) and restore them in the finally block. Bob 2026-05-12:\n // \"the message that shows is different. It jumped to the top.\"\n const scroller = doc.scrollingElement || doc.documentElement;\n const savedScrollTop = scroller?.scrollTop ?? 0;\n const savedBodyScrollTop = body.scrollTop;\n try {\n editor.undoManager?.ignore?.(() => {\n // 1. Unwrap any existing markers \u2014 we'll re-wrap fresh based\n // on the current content. Cheaper than incremental update\n // and avoids stale markers if the user fixed a word\n // without going through our menu (just retyped it).\n const old = body.querySelectorAll(`span[${MARKER_ATTR}]`);\n for (const m of old) {\n const parent = m.parentNode;\n if (!parent) continue;\n while (m.firstChild) parent.insertBefore(m.firstChild, m);\n parent.removeChild(m);\n }\n // Merge text nodes that were split by the now-removed spans.\n body.normalize();\n\n // 2. Walk text nodes and collect words to wrap.\n const walker = doc.createTreeWalker(body, NodeFilter.SHOW_TEXT, {\n acceptNode(node) {\n let p: Node | null = node.parentNode;\n while (p && p !== body) {\n if (p.nodeType === Node.ELEMENT_NODE && SKIP_TAGS.has((p as Element).tagName)) {\n return NodeFilter.FILTER_REJECT;\n }\n p = p.parentNode;\n }\n return NodeFilter.FILTER_ACCEPT;\n },\n });\n // Capture the caret position so we can skip flagging the\n // word currently being typed. Without this, every keystroke\n // mid-word produces a red underline that grows as the user\n // types \u2014 exactly the \"twiddling\" Bob called out. Stripping\n // existing markers (step 1 above) plus this skip means a\n // freshly-typed word stays clean; once the caret leaves it\n // (space, punctuation, arrow keys) the next pass will mark\n // it if still misspelled.\n //\n // After body.normalize() in step 1, the live selection's\n // focusNode may have been collapsed away \u2014 re-read it now.\n let caretNode: Text | null = null;\n let caretOffset = 0;\n const liveSel = doc.getSelection();\n if (liveSel && liveSel.rangeCount > 0) {\n const f = liveSel.focusNode;\n if (f && f.nodeType === Node.TEXT_NODE) {\n caretNode = f as Text;\n caretOffset = liveSel.focusOffset;\n }\n }\n type Hit = { node: Text; start: number; end: number };\n const hits: Hit[] = [];\n let n: Node | null = walker.nextNode();\n // Letter / digit / apostrophe / hyphen \u2014 tokenize words via\n // Unicode-aware regex so we don't false-flag accented words.\n const WORD_RE = /[\\p{L}][\\p{L}'\u2019\\-]*/gu;\n // Email addresses aren't natural-language words. Without this,\n // `bob@example.com` tokenizes to `bob` / `example` / `com` and\n // each gets spell-checked (and usually redlined). Find email\n // spans per text node up front and skip any word hit inside one.\n const EMAIL_RE = /[^\\s@<>()]+@[^\\s@<>()]+\\.[^\\s@<>()]+/g;\n while (n) {\n const tn = n as Text;\n const text = tn.data;\n const emailRanges: Array<[number, number]> = [];\n EMAIL_RE.lastIndex = 0;\n let em: RegExpExecArray | null;\n while ((em = EMAIL_RE.exec(text)) !== null) {\n emailRanges.push([em.index, em.index + em[0].length]);\n }\n let m: RegExpExecArray | null;\n WORD_RE.lastIndex = 0;\n while ((m = WORD_RE.exec(text)) !== null) {\n const word = m[0];\n if (word.length < MIN_WORD_LEN) continue;\n // Inside an email address \u2014 not a word to check.\n const wStart = m.index, wEnd = m.index + word.length;\n if (emailRanges.some(([s, e]) => wStart < e && wEnd > s)) continue;\n // Skip the word the caret is sitting inside (or\n // immediately adjacent to \u2014 inclusive on both ends).\n if (caretNode === tn\n && caretOffset >= m.index\n && caretOffset <= m.index + word.length) {\n continue;\n }\n if (sp.correct(word)) continue;\n hits.push({ node: tn, start: m.index, end: m.index + word.length });\n }\n n = walker.nextNode();\n }\n\n // 3. Wrap hits in reverse order \u2014 wrapping a span splits the\n // text node, which would invalidate earlier offsets. Going\n // right-to-left keeps the not-yet-touched offsets valid.\n hits.reverse();\n for (const h of hits) {\n const range = doc.createRange();\n range.setStart(h.node, h.start);\n range.setEnd(h.node, h.end);\n const span = doc.createElement(\"span\");\n span.setAttribute(MARKER_ATTR, \"1\");\n try { range.surroundContents(span); }\n catch { /* range spans a node boundary \u2014 rare; skip */ }\n }\n });\n } finally {\n if (savedAbs != null) restoreCaretFromAbsOffset(body, savedAbs);\n // Restore scroll positions AFTER the caret restore \u2014 setting the\n // caret may itself trigger scrollIntoView; setting scrollTop last\n // wins. Both targets get restored because the active scroller\n // differs across TinyMCE skins.\n if (scroller && scroller.scrollTop !== savedScrollTop) scroller.scrollTop = savedScrollTop;\n if (body.scrollTop !== savedBodyScrollTop) body.scrollTop = savedBodyScrollTop;\n }\n}\n\n/** Live focus position \u2192 absolute character offset from `body`. Walks all\n * text nodes, accumulates lengths until reaching focusNode, then adds\n * focusOffset. Returns null when no selection or selection isn't in a\n * text node we can find. */\nfunction caretAbsOffsetFromBody(body: HTMLElement): number | null {\n const doc = body.ownerDocument!;\n const sel = doc.getSelection();\n if (!sel || sel.rangeCount === 0) return null;\n const focusNode = sel.focusNode;\n const focusOffset = sel.focusOffset;\n if (!focusNode) return null;\n // Caret on element node (rare for typing-time decoration) \u2014 fall back\n // to \"offset\" being a child index; treat as zero contribution.\n if (focusNode.nodeType !== Node.TEXT_NODE) {\n // Walk only text BEFORE the focus point.\n let abs = 0;\n const walker = doc.createTreeWalker(body, NodeFilter.SHOW_TEXT);\n let n: Node | null = walker.nextNode();\n while (n) {\n if (focusNode.contains(n)) break;\n // n strictly precedes focusNode in tree order?\n const cmp = focusNode.compareDocumentPosition(n);\n if (cmp & Node.DOCUMENT_POSITION_PRECEDING) abs += (n as Text).data.length;\n n = walker.nextNode();\n }\n return abs;\n }\n let abs = 0;\n const walker = doc.createTreeWalker(body, NodeFilter.SHOW_TEXT);\n let n: Node | null = walker.nextNode();\n while (n) {\n if (n === focusNode) return abs + focusOffset;\n abs += (n as Text).data.length;\n n = walker.nextNode();\n }\n return null;\n}\n\n/** Restore caret to the absolute character offset from `body`. Walks\n * text nodes until the cumulative length crosses `abs`, then collapses\n * the selection there. No-op if abs is out of range. */\nfunction restoreCaretFromAbsOffset(body: HTMLElement, abs: number): void {\n const doc = body.ownerDocument!;\n const sel = doc.getSelection();\n if (!sel) return;\n const walker = doc.createTreeWalker(body, NodeFilter.SHOW_TEXT);\n let acc = 0;\n let n: Node | null = walker.nextNode();\n while (n) {\n const len = (n as Text).data.length;\n if (acc + len >= abs) {\n const range = doc.createRange();\n range.setStart(n, Math.max(0, abs - acc));\n range.collapse(true);\n sel.removeAllRanges();\n sel.addRange(range);\n return;\n }\n acc += len;\n n = walker.nextNode();\n }\n // abs past end \u2014 drop caret at end of last text node if any.\n const last = walker.previousNode() as Text | null;\n if (last) {\n const range = doc.createRange();\n range.setStart(last, last.data.length);\n range.collapse(true);\n sel.removeAllRanges();\n sel.addRange(range);\n }\n}\n\n/** Inject the wavy-red CSS into the editor iframe. */\nfunction installDecorationStyle(editor: any): void {\n const doc: Document | null = editor.getDoc?.();\n if (!doc) return;\n if (doc.getElementById(\"mailx-spell-style\")) return;\n const style = doc.createElement(\"style\");\n style.id = \"mailx-spell-style\";\n style.textContent = `\n span[${MARKER_ATTR}] {\n text-decoration: underline wavy #d33;\n text-decoration-skip-ink: none;\n text-underline-offset: 2px;\n /* No background \u2014 keeps the styling subtle, like a native\n * spell underline, not a Find-highlight. */\n background: transparent;\n }\n `;\n doc.head.appendChild(style);\n}\n\n/** Strip decoration markers from serialized output. TinyMCE fires\n * attribute filters during getContent / draft-save; this filter\n * unwraps the span so the saved/sent HTML carries only the text. */\nfunction installSerializerFilter(editor: any): void {\n if ((editor as any).__mailxSpellSerializerWired) return;\n (editor as any).__mailxSpellSerializerWired = true;\n try {\n editor.serializer.addAttributeFilter(MARKER_ATTR, (nodes: any[]) => {\n for (const node of nodes) {\n // TinyMCE's html-node API: `unwrap()` replaces the node\n // with its children. Exactly what we want \u2014 keep the\n // text, drop the span.\n if (typeof node.unwrap === \"function\") node.unwrap();\n }\n });\n } catch (e) {\n console.warn(\"[spellcheck] serializer filter setup failed:\", e);\n }\n}\n\n// \u2500\u2500 Context menu \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction showSuggestionsMenu(\n parentDoc: Document, x: number, y: number,\n items: Array<{ label: string; action: () => void; emphasized?: boolean; separator?: boolean }>,\n): void {\n parentDoc.getElementById(\"mailx-spell-menu\")?.remove();\n const menu = parentDoc.createElement(\"div\");\n menu.id = \"mailx-spell-menu\";\n menu.style.cssText = `\n position: fixed;\n left: ${x}px; top: ${y}px;\n z-index: 10000;\n background: var(--color-bg, #fff);\n color: var(--color-text, #222);\n border: 1px solid var(--color-border, #ccc);\n border-radius: 6px;\n box-shadow: 0 4px 16px rgba(0,0,0,0.18);\n padding: 4px 0;\n font: 13px system-ui, sans-serif;\n min-width: 180px;\n max-width: 320px;\n `;\n for (const it of items) {\n if (it.separator) {\n const sep = parentDoc.createElement(\"div\");\n sep.style.cssText = \"border-top:1px solid var(--color-border,#ddd); margin: 4px 0;\";\n menu.appendChild(sep);\n continue;\n }\n const btn = parentDoc.createElement(\"button\");\n btn.type = \"button\";\n btn.textContent = it.label;\n btn.style.cssText = `\n display: block; width: 100%; text-align: left;\n padding: 5px 12px; border: none; background: none;\n color: inherit; cursor: pointer; font: inherit;\n ${it.emphasized ? \"font-weight: 600;\" : \"\"}\n `;\n btn.addEventListener(\"mouseenter\", () => { btn.style.background = \"var(--color-bg-hover, #eef)\"; });\n btn.addEventListener(\"mouseleave\", () => { btn.style.background = \"none\"; });\n btn.addEventListener(\"click\", () => {\n try { it.action(); } finally { menu.remove(); }\n });\n menu.appendChild(btn);\n }\n parentDoc.body.appendChild(menu);\n const r = menu.getBoundingClientRect();\n if (r.right > window.innerWidth) menu.style.left = `${Math.max(8, window.innerWidth - r.width - 8)}px`;\n if (r.bottom > window.innerHeight) menu.style.top = `${Math.max(8, window.innerHeight - r.height - 8)}px`;\n // Dismiss listeners on EVERY document the user could plausibly click\n // into: the compose document (parentDoc, where the menu is rendered),\n // the TinyMCE editor iframe doc (where the right-click originated and\n // where the user's caret usually lives), and the top mailx window\n // (outside the compose overlay entirely). Without the editor-iframe\n // and top-doc listeners, clicking back into the editor or onto the\n // folder list left the menu pinned (Bob 2026-05-12: \"when I click\n // outside this menu why isn't it going away?\").\n const docs: Document[] = [parentDoc];\n try {\n const composeWin = parentDoc.defaultView as Window | null;\n if (composeWin?.frameElement && composeWin.parent?.document\n && composeWin.parent.document !== parentDoc) {\n docs.push(composeWin.parent.document);\n }\n } catch { /* cross-origin \u2014 ignore */ }\n // Editor iframe sits inside parentDoc; locate it via TinyMCE convention\n // (.tox-edit-area iframe) or fall back to first iframe in the body.\n try {\n const editorIframe = parentDoc.querySelector(\"iframe.tox-edit-area__iframe\")\n || parentDoc.querySelector(\"iframe\");\n const editorDoc = (editorIframe as HTMLIFrameElement | null)?.contentDocument;\n if (editorDoc && editorDoc !== parentDoc) docs.push(editorDoc);\n } catch { /* */ }\n const dismiss = (e: Event) => {\n if (e.type === \"keydown\" && (e as KeyboardEvent).key !== \"Escape\") return;\n if (e.type === \"mousedown\" && menu.contains(e.target as Node)) return;\n menu.remove();\n for (const d of docs) {\n d.removeEventListener(\"mousedown\", dismiss, true);\n d.removeEventListener(\"keydown\", dismiss, true);\n }\n };\n setTimeout(() => {\n for (const d of docs) {\n d.addEventListener(\"mousedown\", dismiss, true);\n d.addEventListener(\"keydown\", dismiss, true);\n }\n }, 0);\n}\n\n/** Replace a misspelling marker span with the correction. Done via\n * range-based selection + insertText so TinyMCE's undo stack and\n * dirty-tracking pick it up properly. */\nfunction replaceMarker(editor: any, marker: HTMLElement, replacement: string): void {\n const doc: Document = editor.getDoc();\n const range = doc.createRange();\n range.selectNode(marker);\n const sel = doc.getSelection();\n if (!sel) return;\n sel.removeAllRanges();\n sel.addRange(range);\n try {\n if (!doc.execCommand(\"insertText\", false, replacement)) {\n range.deleteContents();\n range.insertNode(doc.createTextNode(replacement));\n }\n } catch {\n range.deleteContents();\n range.insertNode(doc.createTextNode(replacement));\n }\n}\n\n/** Fast removal-only pass: unwrap any misspelling marker whose word is now\n * correct (or no longer a single word). Runs on the short CLEANUP debounce.\n * Safe at a short interval because it only ever *removes* underlines \u2014\n * unlike decorate(), which adds them and so waits the calmer 1200 ms.\n * This is what makes a corrected word's red line vanish promptly. */\nfunction cleanupCorrected(editor: any, sp: NSpell): void {\n const body: HTMLElement | null = editor.getBody?.();\n const doc: Document | null = editor.getDoc?.();\n if (!body || !doc) return;\n const markers = body.querySelectorAll(`span[${MARKER_ATTR}]`);\n if (markers.length === 0) return;\n // The marker the caret is inside is being actively edited \u2014 leave it.\n let caretMarker: Node | null = null;\n const sel = doc.getSelection();\n if (sel && sel.rangeCount > 0) {\n let p: Node | null = sel.focusNode;\n while (p && p !== body) {\n if (p.nodeType === Node.ELEMENT_NODE && (p as Element).hasAttribute?.(MARKER_ATTR)) { caretMarker = p; break; }\n p = p.parentNode;\n }\n }\n const stale: Element[] = [];\n for (const m of markers) {\n const word = m.textContent || \"\";\n // Now correct, emptied, or split by editing into multiple tokens.\n const isStale = !word || /\\s/.test(word) || sp.correct(word);\n // Caret protection applies ONLY to non-stale markers \u2014 i.e. the\n // user actively mid-correcting a real misspelling. A stale marker\n // (whitespace inside = it's grown past the original misspelled\n // word, or contents are now correct) is unwrapped regardless;\n // the caret survives because unwrap leaves the text node in place\n // (moved to the marker's parent at the same offset). Without\n // this, a marker that swallowed adjacent typing kept its red\n // wavy underline until the user moved the caret out + the 1200 ms\n // decorate debounce \u2014 \"took a very long time\" (Bob 2026-05-22).\n if (!isStale && m === caretMarker) continue;\n if (isStale) stale.push(m);\n }\n if (stale.length === 0) return;\n // Unwrap only \u2014 no re-walk, no body.normalize() \u2014 so the live caret\n // Range stays valid (we never touch the caret's own marker, and a plain\n // unwrap leaves text-node offsets intact). No caret save/restore needed.\n editor.undoManager?.ignore?.(() => {\n for (const m of stale) {\n const parent = m.parentNode;\n if (!parent) continue;\n while (m.firstChild) parent.insertBefore(m.firstChild, m);\n parent.removeChild(m);\n }\n });\n}\n\n// \u2500\u2500 Public entry point \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/** Wire the spell-check into a TinyMCE editor instance. Idempotent. */\nexport function wireSpellcheck(editor: any): void {\n if ((editor as any).__mailxSpellWired) return;\n (editor as any).__mailxSpellWired = true;\n\n // Kill the native (Chromium/WebView2) spellchecker on this editor. mailx\n // runs its own nspell checker; leaving the native one on too double-\n // underlines every word AND pops the native suggestion menu instead of\n // ours (Bob 2026-05-18: \"why so much red twiddle\" / \"spell fixing is\n // broken?\"). Force spellcheck=\"false\" on the body and keep it off \u2014 some\n // WebView2 builds re-enable it, hence the observer.\n const killNativeSpellcheck = (): void => {\n try {\n const body: HTMLElement | null = editor.getBody?.();\n if (body && body.getAttribute(\"spellcheck\") !== \"false\") {\n body.setAttribute(\"spellcheck\", \"false\");\n }\n } catch { /* editor not ready \u2014 retried by the observer / events */ }\n };\n killNativeSpellcheck();\n try {\n const body: HTMLElement | null = editor.getBody?.();\n if (body) new MutationObserver(killNativeSpellcheck)\n .observe(body, { attributes: true, attributeFilter: [\"spellcheck\"] });\n } catch { /* */ }\n\n let sp: NSpell | null = null;\n let decorateTimer: ReturnType<typeof setTimeout> | null = null;\n const scheduleDecorate = (): void => {\n if (!sp) return;\n if (decorateTimer) clearTimeout(decorateTimer);\n decorateTimer = setTimeout(() => {\n decorateTimer = null;\n if (sp) decorate(editor, sp);\n }, DECORATE_DEBOUNCE_MS);\n };\n // Short-debounce removal-only pass so a corrected word's red line clears\n // promptly without waiting on the full (add+remove) decorate debounce.\n let cleanupTimer: ReturnType<typeof setTimeout> | null = null;\n const scheduleCleanup = (): void => {\n if (!sp) return;\n if (cleanupTimer) clearTimeout(cleanupTimer);\n cleanupTimer = setTimeout(() => {\n cleanupTimer = null;\n if (sp) cleanupCorrected(editor, sp);\n }, CLEANUP_DEBOUNCE_MS);\n };\n\n // Kick off the dictionary load. First decoration runs as soon as\n // it resolves; subsequent runs are triggered by editor events.\n getSpell().then((loaded) => {\n sp = loaded;\n installDecorationStyle(editor);\n installSerializerFilter(editor);\n decorate(editor, loaded);\n }).catch((err) => {\n console.error(\"[spellcheck] dict load failed:\", err);\n });\n\n // Re-decorate on edits / paste / content swap. `nodechange` covers\n // most of these; `input` catches typing in finer-grained events on\n // some TinyMCE versions. The short cleanup pass runs on the same\n // events so a just-corrected word loses its underline quickly.\n editor.on(\"input nodechange setcontent paste keyup\", scheduleDecorate);\n editor.on(\"input nodechange setcontent paste keyup\", scheduleCleanup);\n\n // Right-click handler. If the click landed on a marker span, show\n // suggestions; otherwise let the default menu (WebView2's) fire.\n const iframeDoc: Document = editor.getDoc();\n iframeDoc.addEventListener(\"contextmenu\", (ev: Event) => {\n const e = ev as MouseEvent;\n const target = e.target as HTMLElement | null;\n if (!target) return;\n const marker = target.closest?.(`span[${MARKER_ATTR}]`) as HTMLElement | null;\n if (!marker) return; // not on a misspelled word \u2014 default menu fires\n const word = marker.textContent || \"\";\n if (!word || !sp) return;\n e.preventDefault();\n e.stopPropagation();\n // nspell's suggest() leans on Hunspell's TRY/REP heuristics and\n // doesn't reliably surface adjacent-letter transpositions \u2014 the\n // single most common English typo (Bob 2026-05-12: \"what kind of\n // a spell is this if it can't see hte is the\"). Prepend our own\n // transposition pass: for each adjacent pair in the word, swap\n // them and keep results that exist in the dictionary. Then merge\n // with nspell's list, transpositions first, dedup, cap at 7.\n const transposed: string[] = [];\n for (let i = 0; i < word.length - 1; i++) {\n const swapped = word.slice(0, i) + word[i + 1] + word[i] + word.slice(i + 2);\n if (swapped !== word && sp.correct(swapped) && !transposed.includes(swapped)) {\n transposed.push(swapped);\n }\n }\n const nspellSugs: string[] = sp.suggest(word) as string[];\n const sugs: string[] = [];\n for (const s of [...transposed, ...nspellSugs]) {\n if (!sugs.includes(s)) sugs.push(s);\n if (sugs.length >= 7) break;\n }\n const iframeEl = editor.iframeElement as HTMLIFrameElement | undefined;\n const iframeRect = iframeEl ? iframeEl.getBoundingClientRect() : { left: 0, top: 0 };\n const items: Array<{ label: string; action: () => void; emphasized?: boolean; separator?: boolean }> = [];\n if (sugs.length === 0) {\n items.push({ label: \"(no suggestions)\", action: () => { /* */ } });\n } else {\n for (const s of sugs) {\n items.push({\n label: s,\n emphasized: true,\n action: () => {\n replaceMarker(editor, marker, s);\n // Re-decorate so the replacement is checked too.\n scheduleDecorate();\n },\n });\n }\n }\n items.push({ label: \"\", action: () => { /* */ }, separator: true });\n items.push({\n label: `Add \"${word}\" to dictionary`,\n action: () => { if (sp) addToUserDict(word, sp); scheduleDecorate(); },\n });\n items.push({\n label: \"Ignore (this session)\",\n action: () => { if (sp) sp.add(word); scheduleDecorate(); },\n });\n showSuggestionsMenu(document, iframeRect.left + e.clientX, iframeRect.top + e.clientY, items);\n }, true);\n}\n", "/**\n * Ghost text autocomplete \u2014 shows AI suggestions as translucent overlay at cursor.\n * Tab accepts, Escape dismisses, any other key dismisses and re-triggers after debounce.\n * Editor-agnostic: works with both Quill and tiptap via MailxEditor interface.\n */\n\nimport type { MailxEditor } from \"./editor.js\";\nimport { autocomplete } from \"../lib/api-client.js\";\n\ninterface GhostTextContext {\n getSubject: () => string;\n getTo: () => string;\n}\n\nlet ghostEl: HTMLElement | null = null;\nlet currentSuggestion: string | null = null;\nlet debounceTimer: ReturnType<typeof setTimeout> | null = null;\nlet abortController: AbortController | null = null;\nlet activeEditor: MailxEditor | null = null;\nlet debounceMs = 600;\n\nfunction dismiss(): void {\n currentSuggestion = null;\n if (ghostEl) {\n ghostEl.remove();\n ghostEl = null;\n }\n}\n\nfunction positionGhost(editor: MailxEditor, text: string): void {\n dismiss();\n\n const sel = window.getSelection();\n if (!sel || sel.rangeCount === 0 || !sel.isCollapsed) return;\n\n const range = sel.getRangeAt(0);\n let rect = range.getBoundingClientRect();\n\n // Collapsed range may return zero-width rect \u2014 use a temp span to measure\n if (rect.width === 0 && rect.height === 0) {\n const span = document.createElement(\"span\");\n span.textContent = \"\\u200B\"; // zero-width space\n range.insertNode(span);\n rect = span.getBoundingClientRect();\n span.remove();\n // Restore selection\n sel.removeAllRanges();\n sel.addRange(range);\n }\n\n const container = editor.getScrollContainer();\n const containerRect = container.getBoundingClientRect();\n\n ghostEl = document.createElement(\"span\");\n ghostEl.className = \"ghost-text\";\n ghostEl.textContent = text;\n ghostEl.style.top = `${rect.top - containerRect.top + container.scrollTop}px`;\n ghostEl.style.left = `${rect.left - containerRect.left + container.scrollLeft}px`;\n container.appendChild(ghostEl);\n\n currentSuggestion = text;\n}\n\nfunction requestSuggestion(editor: MailxEditor, context: GhostTextContext): void {\n // Cancel any in-flight request\n if (abortController) {\n abortController.abort();\n abortController = null;\n }\n\n const bodyText = editor.getText();\n if (!bodyText || bodyText.trim().length < 3) return; // need at least a few characters\n\n abortController = new AbortController();\n const signal = abortController.signal;\n\n autocomplete({\n subject: context.getSubject(),\n to: context.getTo(),\n bodyText,\n cursorOffset: bodyText.length,\n }, signal).then((result: any) => {\n if (signal.aborted) return;\n if (result.suggestion) {\n positionGhost(editor, result.suggestion);\n }\n }).catch((e: any) => {\n if (e.name === \"AbortError\") return;\n // Silently ignore autocomplete errors\n });\n}\n\nexport function initGhostText(editor: MailxEditor, context: GhostTextContext, options?: { debounceMs?: number }): void {\n activeEditor = editor;\n if (options?.debounceMs) debounceMs = options.debounceMs;\n\n // Debounced content change \u2192 request suggestion\n editor.onContentChange(() => {\n dismiss();\n if (debounceTimer) clearTimeout(debounceTimer);\n debounceTimer = setTimeout(() => {\n requestSuggestion(editor, context);\n }, debounceMs);\n });\n\n // Key handler: Tab accepts, Escape dismisses\n editor.onKeyDown((e: KeyboardEvent) => {\n if (!currentSuggestion) return;\n\n if (e.key === \"Tab\") {\n e.preventDefault();\n e.stopPropagation();\n const text = currentSuggestion;\n dismiss();\n editor.insertTextAtCursor(text);\n return;\n }\n\n if (e.key === \"Escape\") {\n e.preventDefault();\n e.stopPropagation();\n dismiss();\n return;\n }\n\n // Any other key: dismiss (new suggestion will come after debounce)\n dismiss();\n });\n\n // Dismiss on blur/scroll\n editor.root.addEventListener(\"blur\", dismiss);\n editor.getScrollContainer().addEventListener(\"scroll\", dismiss);\n}\n\nexport function destroyGhostText(): void {\n dismiss();\n if (debounceTimer) clearTimeout(debounceTimer);\n if (abortController) abortController.abort();\n activeEditor = null;\n}\n", "/**\n * Editor help text \u2014 embedded copy of `app/docs/editor.md`.\n *\n * Source of truth is the .md file (it's what ships in `app/docs/` for any\n * external reader). This TS const is the runtime copy the compose iframe\n * loads \u2014 embedding here avoids an asset fetch from the WebView (which\n * has different paths on desktop/Android and would fail silently when the\n * docs aren't bundled).\n *\n * Keep this in sync with `app/docs/editor.md`. When updating either, copy\n * the .md contents into the EDITOR_HELP_MD literal below. (A small sync\n * script under `app/docs/` could automate this \u2014 TODO once the rules /\n * docs sync pattern stabilizes.)\n */\nexport const EDITOR_HELP_MD = `# Compose editor \u2014 formatting and shortcuts\n\nmailx ships with two rich-text editors: **Quill** (default) and **tiptap**.\nMost things work the same in both. Differences are called out below.\n\nSwitch editors via **Settings \u2192 Editor \u2192 Quill | tiptap**.\n\n## Universal \u2014 works in both editors\n\n| Action | Shortcut | Where |\n|---|---|---|\n| **Send** | Ctrl+Enter | toolbar Send button |\n| Bold / Italic / Underline | Ctrl+B / Ctrl+I / Ctrl+U | toolbar B / I / U |\n| Strikethrough | Ctrl+Shift+X | toolbar S |\n| Bulleted list | Ctrl+Shift+8 | toolbar \u2022 |\n| Ordered list | Ctrl+Shift+7 | toolbar 1. |\n| Insert / edit link | Ctrl+K | toolbar \uD83D\uDD17 |\n| Remove link | Ctrl+Shift+K | (no toolbar button \u2014 use Ctrl+Shift+K) |\n| Blockquote | (Quill: Ctrl+Shift+Q; tiptap: toolbar) | toolbar \\\" |\n| Clear formatting | Ctrl+\\\\\\\\ | toolbar \u2716 |\n| Heading (H1 / H2 / H3) | (Quill: format dropdown; tiptap: select dropdown) | \"Normal / Heading 1 / 2 / 3\" |\n| Undo / Redo | Ctrl+Z / Ctrl+Y | (no toolbar button) |\n| Spell-check | (browser native \u2014 red underlines) | right-click word |\n| Paste plain text | Ctrl+Shift+V | (browser native) |\n\n## Quill-only\n\n| Action | Shortcut |\n|---|---|\n| Indent / outdent | Ctrl+] / Ctrl+[ |\n| Color text | Ctrl+Shift+C |\n| Format dropdown | toolbar (left side) |\n| Inline code | toolbar \\`<>\\` |\n| Code block | toolbar |\n| Image inserter | (no built-in; use drag-and-drop or paste) |\n\nQuill has a more elaborate toolbar with format-specific dropdowns (font, size,\ncolor). Internally Quill uses its own *Delta* document model \u2014 copy/paste\nfrom Word/Outlook sometimes leaves extra empty paragraphs that you'll see\nin the sent message body.\n\n## tiptap-only\n\n| Action | Where |\n|---|---|\n| Heading select | left of toolbar |\n| Toggle bold / italic / underline / strike | toolbar B / I / U / S |\n| Blockquote | toolbar \\\" |\n| Image (drag-and-drop) | drop a file into the body |\n\ntiptap is built on ProseMirror. Output HTML is cleaner than Quill on\nWord/Outlook paste roundtrips. Bundle is smaller. Some Quill toolbar\nfeatures (inline code, indent shortcuts, color picker) aren't wired \u2014\nuse the heading select / format menu instead.\n\n## Common features (across both)\n\n- **Drag-and-drop attachments** \u2014 drop files anywhere on the compose\n window to attach. Overlay highlights the drop target while dragging.\n- **Edit in Word / LibreOffice** \u2014 toolbar **Edit in Word** button opens\n the body in your default external editor. Save in the external editor\n and the body reloads here. mailx writes a temporary \\`.docx\\` file (see\n \\`~/.rmfmail/external-edit/\\`) and watches it for changes.\n- **Auto-save drafts** \u2014 every 5 seconds (and on input debounce / on\n blur). Drafts land in the Drafts folder via IMAP append.\n- **Address auto-completion** \u2014 type a partial name in To/Cc/Bcc; matches\n rank by recency \u00D7 use-count. Group names from \\`contacts.jsonc \u2192 groups\\`\n also surface here.\n- **Address-field expansion** \u2014 recipient fields are auto-growing\n textareas; long lists wrap to multiple lines.\n- **Group expansion on send** \u2014 type a group name (e.g. \\`family\\`) in\n To/Cc/Bcc and it expands to the address list at send time.\n- **Ghost-text autocomplete** (off by default) \u2014 Settings \u2192\n AI autocomplete \u2192 on. Predicts the next words while you type.\n\n## When the toolbar doesn't appear\n\nThe editor loads from a CDN (jsdelivr). If your network can't reach it, the\ntoolbar disappears and a plain \\`contenteditable\\` fallback takes over. Status\nbar shows the failure. mailx tries the *other* editor before falling all the\nway back; if both fail you get a plain textarea with no shortcuts and no\ntoolbar \u2014 sending still works.\n\n## See also\n\n- \\`accounts.md\\`, \\`contacts.md\\`, \\`allowlist.md\\`, \\`clients.md\\`, \\`config.md\\`,\n \\`preferences.md\\` \u2014 config-file references (these live in your GDrive\n \\`.rmfmail/\\` folder).\n- This document is **app-internal** \u2014 it ships with each release and\n documents the editor as it currently exists in the version you're\n running. It is not deployed to your user folder.\n`;\n", "/**\n * Editor abstraction \u2014 wraps Quill / tiptap / TinyMCE behind a common\n * interface. The compose window loads this module and calls\n * createEditor() based on the user's setting.\n *\n * rmf-tiny is dynamic-imported (not static) so a missing/unreachable\n * adapter fails ONLY the TinyMCE branch \u2014 Quill and tiptap stay\n * working. A static top-of-file import would crash the whole module\n * if the browser couldn't resolve \"@bobfrankston/rmf-tiny\" via the\n * import map, and compose.ts (which imports createEditor from here)\n * would silently fail to attach handlers.\n */\n\nexport interface MailxEditor {\n setHtml(html: string): void;\n getHtml(): string;\n getText(): string;\n focus(): void;\n setCursor(pos: number): void;\n /** The editor's root editable element (for checking content) */\n root: HTMLElement;\n /** The scrollable container for positioning ghost text */\n getScrollContainer(): HTMLElement;\n /** Register a handler for content changes */\n onContentChange(handler: () => void): void;\n /** Register a keydown handler on the editor */\n onKeyDown(handler: (e: KeyboardEvent) => void): void;\n /** Insert plain text at the current cursor position */\n insertTextAtCursor(text: string): void;\n}\n\n// \u2500\u2500 Quill \u2500\u2500\n\ndeclare const Quill: any;\n\n/** URL-ish test: accepts http(s)://, mailto:, tel:, and bare domains with a dot. */\nfunction looksLikeUrl(s: string): boolean {\n const t = s.trim();\n if (!t) return false;\n if (/^(https?|mailto|tel):/i.test(t)) return true;\n // bare domain (e.g. \"example.com/path\") \u2014 require a dot and no internal whitespace\n return /^[\\w-]+(\\.[\\w-]+)+(\\/\\S*)?$/.test(t);\n}\nfunction normalizeUrl(s: string): string {\n const t = s.trim();\n if (!t) return t;\n if (/^(https?|mailto|tel):/i.test(t)) return t;\n if (/^[\\w.+-]+@[\\w-]+(\\.[\\w-]+)+$/.test(t)) return `mailto:${t}`;\n return `https://${t}`;\n}\n\n/** Floating modal that edits both link text and URL. Returns null on Cancel,\n * { text, url } on OK, or { text: \"\", url: \"\" } on \"Remove link\". */\nfunction openLinkDialog(initialText: string, initialUrl: string): Promise<{ text: string; url: string; remove?: boolean } | null> {\n return new Promise(resolve => {\n const backdrop = document.createElement(\"div\");\n backdrop.className = \"mailx-modal-backdrop\";\n const panel = document.createElement(\"div\");\n panel.className = \"mailx-modal\";\n panel.innerHTML = `\n <div class=\"mailx-modal-title\">Edit link</div>\n <label class=\"mailx-modal-label\">Text\n <input type=\"text\" class=\"mailx-modal-input\" id=\"mailx-link-text\">\n </label>\n <label class=\"mailx-modal-label\">URL\n <input type=\"text\" class=\"mailx-modal-input\" id=\"mailx-link-url\" spellcheck=\"false\" autocomplete=\"off\">\n </label>\n <div class=\"mailx-modal-buttons\">\n <button type=\"button\" class=\"mailx-modal-btn\" data-action=\"remove\">Remove link</button>\n <span class=\"mailx-modal-spacer\"></span>\n <button type=\"button\" class=\"mailx-modal-btn\" data-action=\"cancel\">Cancel</button>\n <button type=\"button\" class=\"mailx-modal-btn mailx-modal-btn-primary\" data-action=\"ok\">OK</button>\n </div>`;\n backdrop.appendChild(panel);\n document.body.appendChild(backdrop);\n\n const textInput = panel.querySelector<HTMLInputElement>(\"#mailx-link-text\")!;\n const urlInput = panel.querySelector<HTMLInputElement>(\"#mailx-link-url\")!;\n textInput.value = initialText;\n urlInput.value = initialUrl;\n\n const close = (result: { text: string; url: string; remove?: boolean } | null) => {\n backdrop.remove();\n document.removeEventListener(\"keydown\", onKey, true);\n resolve(result);\n };\n const commit = () => close({ text: textInput.value, url: normalizeUrl(urlInput.value) });\n const onKey = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") { e.stopPropagation(); e.preventDefault(); close(null); }\n else if (e.key === \"Enter\") { e.stopPropagation(); e.preventDefault(); commit(); }\n };\n document.addEventListener(\"keydown\", onKey, true);\n\n panel.querySelectorAll<HTMLButtonElement>(\".mailx-modal-btn\").forEach(btn => {\n btn.addEventListener(\"click\", () => {\n const action = btn.dataset.action;\n if (action === \"cancel\") close(null);\n else if (action === \"remove\") close({ text: textInput.value, url: \"\", remove: true });\n else commit();\n });\n });\n backdrop.addEventListener(\"mousedown\", (e) => { if (e.target === backdrop) close(null); });\n // Focus URL if we have text, else text first\n (initialText ? urlInput : textInput).focus();\n (initialText ? urlInput : textInput).select();\n });\n}\n\nfunction createQuillEditor(container: HTMLElement): MailxEditor {\n /** Open the link dialog for the current selection or cursor. If range has\n * a selection, prefill text from the selected text; if cursor is inside\n * a link, prefill both from the link's range. */\n const openLinkForRange = async (quill: any, range: any) => {\n if (!range) return;\n // Expand a bare cursor inside a link to the full link range\n let linkRange = range;\n const format = quill.getFormat(range);\n if (range.length === 0 && format.link) {\n // Walk left and right to find the link extent\n const [leaf, offset] = quill.getLeaf(range.index);\n if (leaf) {\n // Build a range that spans the whole link\n const text = quill.getText();\n let start = range.index, end = range.index;\n while (start > 0 && quill.getFormat(start - 1, 1).link === format.link) start--;\n while (end < text.length - 1 && quill.getFormat(end, 1).link === format.link) end++;\n linkRange = { index: start, length: end - start };\n }\n }\n const currentText = linkRange.length ? quill.getText(linkRange.index, linkRange.length).replace(/\\n$/, \"\") : \"\";\n const currentUrl = format.link || \"\";\n const result = await openLinkDialog(currentText, currentUrl);\n if (!result) return;\n if (result.remove) {\n if (linkRange.length) quill.formatText(linkRange.index, linkRange.length, \"link\", false);\n return;\n }\n if (!result.url) return;\n const newText = result.text || result.url;\n if (linkRange.length) {\n // Replace the existing text+link with new text+link\n quill.deleteText(linkRange.index, linkRange.length);\n quill.insertText(linkRange.index, newText, { link: result.url });\n quill.setSelection(linkRange.index + newText.length, 0);\n } else {\n quill.insertText(linkRange.index, newText, { link: result.url });\n quill.setSelection(linkRange.index + newText.length, 0);\n }\n };\n\n // Extra keybindings for formatting that Quill doesn't wire up by default.\n // Ctrl+K (insert/edit link) is the one most users expect; we also add shortcuts\n // for strikethrough, lists, indent, color, and clear-formatting.\n const extraBindings: any = {\n insertLink: {\n key: \"K\", shortKey: true,\n handler: function (this: any, range: any) {\n openLinkForRange(this.quill, range);\n },\n },\n removeLink: {\n key: \"K\", shortKey: true, shiftKey: true,\n handler: function (this: any) { this.quill.format(\"link\", false); },\n },\n strike: {\n key: \"X\", shortKey: true, shiftKey: true,\n handler: function (this: any, range: any) {\n if (!range) return true;\n const cur = this.quill.getFormat(range).strike;\n this.quill.format(\"strike\", !cur);\n },\n },\n orderedList: {\n key: \"7\", shortKey: true, shiftKey: true,\n handler: function (this: any) { this.quill.format(\"list\", \"ordered\"); },\n },\n bulletList: {\n key: \"8\", shortKey: true, shiftKey: true,\n handler: function (this: any) { this.quill.format(\"list\", \"bullet\"); },\n },\n indent: {\n key: \"]\", shortKey: true,\n handler: function (this: any, range: any, context: any) {\n this.quill.format(\"indent\", (context.format.indent || 0) + 1);\n },\n },\n outdent: {\n key: \"[\", shortKey: true,\n handler: function (this: any, range: any, context: any) {\n this.quill.format(\"indent\", Math.max(0, (context.format.indent || 0) - 1));\n },\n },\n color: {\n key: \"C\", shortKey: true, shiftKey: true,\n handler: function (this: any, range: any) {\n if (!range) return true;\n const current = this.quill.getFormat(range).color || \"\";\n const color = prompt(\"Text color (name or #hex, blank to clear):\", current);\n if (color === null) return;\n this.quill.format(\"color\", color || false);\n },\n },\n clearFormat: {\n key: \"\\\\\", shortKey: true,\n handler: function (this: any, range: any) {\n if (!range) return true;\n this.quill.removeFormat(range.index, range.length || 0);\n },\n },\n };\n\n const q = new Quill(container, {\n theme: \"snow\",\n placeholder: \"Write your message...\",\n modules: {\n toolbar: [\n [{ font: [] }, { size: [\"small\", false, \"large\", \"huge\"] }],\n [{ header: [1, 2, 3, false] }],\n [\"bold\", \"italic\", \"underline\", \"strike\"],\n [{ color: [] }, { background: [] }],\n [{ list: \"ordered\" }, { list: \"bullet\" }],\n [{ align: [] }],\n [\"blockquote\", \"link\", \"image\"],\n [\"clean\"]\n ],\n keyboard: { bindings: extraBindings },\n }\n });\n\n // Make toolbar buttons non-tabbable so Tab goes straight to editor body\n document.querySelectorAll(\".ql-toolbar button, .ql-toolbar select, .ql-toolbar .ql-picker-label\").forEach(\n el => (el as HTMLElement).setAttribute(\"tabindex\", \"-1\")\n );\n\n // Native spell-check: WebView2 / Chromium underlines misspellings and the\n // right-click menu offers \"Add to dictionary\". Quill clears spellcheck on\n // its root by default AND may re-clear it asynchronously as part of its\n // setup (user-reported \"spell-check broken again\" with the one-shot set).\n // Set once now, again after the frame flushes so post-init clears can't\n // win, and install a MutationObserver to re-assert if anything later\n // strips the attribute. Cheap \u2014 fires at most on each clear.\n const applySpellcheck = () => {\n if (q.root.getAttribute(\"spellcheck\") !== \"true\") q.root.setAttribute(\"spellcheck\", \"true\");\n if (q.root.getAttribute(\"autocorrect\") !== \"on\") q.root.setAttribute(\"autocorrect\", \"on\");\n if (q.root.getAttribute(\"autocapitalize\") !== \"on\") q.root.setAttribute(\"autocapitalize\", \"on\");\n };\n applySpellcheck();\n requestAnimationFrame(applySpellcheck);\n setTimeout(applySpellcheck, 100);\n const spellObs = new MutationObserver(() => applySpellcheck());\n spellObs.observe(q.root, { attributes: true, attributeFilter: [\"spellcheck\", \"autocorrect\", \"autocapitalize\"] });\n\n // Toolbar link button: open our modal instead of Quill's built-in URL prompt.\n const toolbar = q.getModule(\"toolbar\");\n toolbar?.addHandler(\"link\", function (this: any) {\n openLinkForRange(q, q.getSelection() || { index: q.getLength() - 1, length: 0 });\n });\n\n // Paste handling:\n // - text/html clipboard with exactly one anchor (the common \"copy a link\n // with anchor text from a webpage\" case): take it over from Quill \u2014\n // Quill's clipboard module was producing duplicates (\"click here\" as\n // text PLUS a separate \"https://example.com\" as a link tail). Insert\n // the anchor's text content as a single linked run.\n // - text/html with richer content: defer to Quill (preserves formatting).\n // - text/plain that's a URL: insert as a link, optionally wrapping any\n // currently-selected text.\n // - Anything else: default Quill behavior (verbatim plain or HTML).\n // C36: AI proofread on right-click \u2192 \"Proofread selection\" item.\n // Uses the existing aiTransform IPC. Gated by autocomplete.proofreadEnabled.\n q.root.addEventListener(\"contextmenu\", async (e: MouseEvent) => {\n try {\n const sel = q.getSelection();\n if (!sel || sel.length === 0) return; // no selection \u2014 let native menu handle\n const settingsRaw = localStorage.getItem(\"mailx-ai-proofread-enabled\");\n if (settingsRaw !== \"true\") return; // feature off\n const text = q.getText(sel.index, sel.length);\n if (!text.trim()) return;\n e.preventDefault();\n // Inline action menu next to the cursor\n const menu = document.createElement(\"div\");\n menu.style.cssText = `position:fixed;z-index:2500;background:var(--color-bg);color:var(--color-text);border:1px solid var(--color-border);border-radius:6px;box-shadow:0 4px 16px rgba(0,0,0,0.2);padding:4px 0;font-size:13px;min-width:160px;`;\n menu.style.left = `${e.clientX}px`;\n menu.style.top = `${e.clientY}px`;\n const item = document.createElement(\"div\");\n item.textContent = \"Proofread selection\";\n item.style.cssText = `padding:6px 12px;cursor:pointer;`;\n item.addEventListener(\"mouseenter\", () => item.style.background = \"var(--color-bg-hover)\");\n item.addEventListener(\"mouseleave\", () => item.style.background = \"\");\n item.addEventListener(\"click\", async () => {\n menu.remove();\n try {\n const { aiTransform } = await import(\"../lib/api-client.js\");\n const r = await aiTransform({ action: \"proofread\", text });\n if (r?.text && r.text !== text) {\n q.deleteText(sel.index, sel.length);\n q.insertText(sel.index, r.text);\n q.setSelection(sel.index, r.text.length);\n } else if (r?.reason) {\n alert(`Proofread: ${r.reason}`);\n }\n } catch (err: any) {\n alert(`Proofread failed: ${err?.message || err}`);\n }\n });\n menu.appendChild(item);\n document.body.appendChild(menu);\n const dismiss = () => { menu.remove(); document.removeEventListener(\"mousedown\", dismiss); };\n setTimeout(() => document.addEventListener(\"mousedown\", dismiss), 0);\n } catch { /* fall through to native menu */ }\n });\n\n // IMPORTANT: register on the capture phase AND use stopImmediatePropagation\n // when we handle the paste ourselves. Quill 2.x's clipboard module attaches\n // its own listener on the same root element; preventDefault only stops the\n // browser's default contenteditable insertion, NOT Quill's parallel listener.\n // Without stopImmediatePropagation the two handlers fire independently and\n // both insert \u2014 user sees the URL twice. Capture phase guarantees we run\n // before Quill so stopImmediatePropagation actually blocks it.\n q.root.addEventListener(\"paste\", (e: ClipboardEvent) => {\n const cb = e.clipboardData;\n if (!cb) return;\n\n // Helper: call when we've handled the paste ourselves. Stops Quill's\n // own listener from also processing the same event.\n const consume = () => {\n e.preventDefault();\n e.stopImmediatePropagation();\n };\n\n // Q3: image-on-clipboard \u2192 inline as data: URL.\n for (const item of Array.from(cb.items)) {\n if (item.kind === \"file\" && item.type.startsWith(\"image/\")) {\n const file = item.getAsFile();\n if (!file) continue;\n consume();\n const reader = new FileReader();\n reader.onload = () => {\n const dataUrl = String(reader.result || \"\");\n const range = q.getSelection(true) || { index: q.getLength(), length: 0 };\n q.insertEmbed(range.index, \"image\", dataUrl);\n q.setSelection(range.index + 1, 0);\n };\n reader.readAsDataURL(file);\n return;\n }\n }\n\n const html = cb.getData(\"text/html\");\n const plain = cb.getData(\"text/plain\");\n\n if (html) {\n // Detect \"single anchor\" clipboard \u2014 copy from a browser usually\n // produces something like:\n // <meta charset='utf-8'><a href=\"https://example.com\">click here</a>\n // or wrapped in <html><body>. Parse and check.\n try {\n const tmp = document.createElement(\"div\");\n tmp.innerHTML = html;\n // Strip script/style, then unwrap <html>/<body> noise.\n const root = tmp.querySelector(\"body\") || tmp;\n // Walk for the only meaningful element\n const meaningful = Array.from(root.childNodes).filter(n => {\n if (n.nodeType === Node.TEXT_NODE) return (n.textContent || \"\").trim().length > 0;\n if (n.nodeType === Node.ELEMENT_NODE) {\n const tag = (n as Element).tagName.toLowerCase();\n return tag !== \"meta\" && tag !== \"style\" && tag !== \"script\";\n }\n return false;\n });\n if (meaningful.length === 1 && (meaningful[0] as HTMLElement).tagName?.toLowerCase() === \"a\") {\n const a = meaningful[0] as HTMLAnchorElement;\n const href = a.getAttribute(\"href\") || \"\";\n const text = (a.textContent || \"\").trim();\n if (href && text) {\n consume();\n const range = q.getSelection(true);\n if (!range) return;\n if (range.length > 0) {\n // Selected text exists \u2014 replace with the linked anchor text\n q.deleteText(range.index, range.length);\n }\n q.insertText(range.index, text, { link: href });\n q.setSelection(range.index + text.length, 0);\n return;\n }\n }\n // Single text-node wrapping the URL \u2014 common when copying from\n // browser address bar (Chrome ships text/html as\n // `<meta><span>URL</span>` alongside text/plain). Fall through\n // to the plain-URL path below instead of letting Quill insert\n // the bare URL text AND our handler insert it linked \u2014 which\n // is exactly the double-paste the user reported.\n const textOnly = (root.textContent || \"\").trim();\n if (textOnly && looksLikeUrl(textOnly) && !root.querySelector(\"a\")) {\n consume();\n const range = q.getSelection(true);\n if (!range) return;\n const url = normalizeUrl(textOnly);\n if (range.length > 0) {\n q.formatText(range.index, range.length, \"link\", url);\n q.setSelection(range.index + range.length, 0);\n } else {\n q.insertText(range.index, textOnly, { link: url });\n q.setSelection(range.index + textOnly.length, 0);\n }\n return;\n }\n } catch { /* fall through to Quill default */ }\n return; // Quill handles richer HTML clipboard (no consume \u2192 Quill runs)\n }\n\n if (plain && looksLikeUrl(plain)) {\n consume();\n const range = q.getSelection(true);\n if (!range) return;\n const url = normalizeUrl(plain);\n if (range.length > 0) {\n // Preserve existing selection text, just format it as a link\n q.formatText(range.index, range.length, \"link\", url);\n q.setSelection(range.index + range.length, 0);\n } else {\n q.insertText(range.index, plain.trim(), { link: url });\n q.setSelection(range.index + plain.trim().length, 0);\n }\n }\n }, true); // capture=true \u2014 run before Quill's own paste listener\n\n // Hover preview: show the target URL in a floating tooltip when the\n // pointer is over a link. Built on top of native mouseover/mouseout\n // rather than Quill's ql-tooltip (which is keyboard-triggered).\n let hoverTip: HTMLElement | null = null;\n q.root.addEventListener(\"mouseover\", (e: MouseEvent) => {\n const a = (e.target as HTMLElement).closest(\"a[href]\") as HTMLAnchorElement | null;\n if (!a) return;\n if (hoverTip) hoverTip.remove();\n hoverTip = document.createElement(\"div\");\n hoverTip.className = \"mailx-link-hover\";\n hoverTip.textContent = a.getAttribute(\"href\") || \"\";\n document.body.appendChild(hoverTip);\n const rect = a.getBoundingClientRect();\n hoverTip.style.left = `${Math.max(8, rect.left)}px`;\n hoverTip.style.top = `${rect.bottom + 4}px`;\n });\n q.root.addEventListener(\"mouseout\", (e: MouseEvent) => {\n const to = e.relatedTarget as HTMLElement | null;\n if (to && to.closest(\"a[href]\")) return;\n if (hoverTip) { hoverTip.remove(); hoverTip = null; }\n });\n\n return {\n setHtml(html: string): void {\n q.clipboard.dangerouslyPasteHTML(html);\n },\n getHtml(): string {\n return q.root.innerHTML;\n },\n getText(): string {\n return q.getText();\n },\n focus(): void {\n q.focus();\n },\n setCursor(pos: number): void {\n q.setSelection(pos, 0);\n },\n root: q.root,\n getScrollContainer(): HTMLElement {\n return q.root;\n },\n onContentChange(handler: () => void): void {\n q.on(\"text-change\", handler);\n },\n onKeyDown(handler: (e: KeyboardEvent) => void): void {\n q.root.addEventListener(\"keydown\", handler);\n },\n insertTextAtCursor(text: string): void {\n const sel = q.getSelection();\n if (sel) q.insertText(sel.index, text);\n }\n };\n}\n\n// \u2500\u2500 tiptap \u2500\u2500\n\nasync function createTiptapEditor(container: HTMLElement): Promise<MailxEditor> {\n // tiptap loaded via CDN \u2014 use global UMD bundles\n const { Editor } = (window as any).tiptapCore;\n const { StarterKit } = (window as any).tiptapStarterKit;\n const { Link } = (window as any).tiptapExtensionLink;\n const { Image } = (window as any).tiptapExtensionImage;\n const { Underline } = (window as any).tiptapExtensionUnderline;\n const { Placeholder } = (window as any).tiptapExtensionPlaceholder;\n\n // Build toolbar\n const toolbar = document.createElement(\"div\");\n toolbar.className = \"tt-toolbar\";\n toolbar.innerHTML = `\n <select class=\"tt-heading\" tabindex=\"-1\">\n <option value=\"p\">Normal</option>\n <option value=\"1\">Heading 1</option>\n <option value=\"2\">Heading 2</option>\n <option value=\"3\">Heading 3</option>\n </select>\n <button class=\"tt-btn\" data-cmd=\"bold\" title=\"Bold\" tabindex=\"-1\"><b>B</b></button>\n <button class=\"tt-btn\" data-cmd=\"italic\" title=\"Italic\" tabindex=\"-1\"><i>I</i></button>\n <button class=\"tt-btn\" data-cmd=\"underline\" title=\"Underline\" tabindex=\"-1\"><u>U</u></button>\n <button class=\"tt-btn\" data-cmd=\"strike\" title=\"Strikethrough\" tabindex=\"-1\"><s>S</s></button>\n <button class=\"tt-btn\" data-cmd=\"bulletList\" title=\"Bullet list\" tabindex=\"-1\">&#8226;</button>\n <button class=\"tt-btn\" data-cmd=\"orderedList\" title=\"Ordered list\" tabindex=\"-1\">1.</button>\n <button class=\"tt-btn\" data-cmd=\"blockquote\" title=\"Blockquote\" tabindex=\"-1\">&ldquo;</button>\n <button class=\"tt-btn\" data-cmd=\"link\" title=\"Link\" tabindex=\"-1\">&#128279;</button>\n <button class=\"tt-btn\" data-cmd=\"clearFormat\" title=\"Clear formatting\" tabindex=\"-1\">&#8999;</button>\n `;\n\n // Content area\n const content = document.createElement(\"div\");\n content.className = \"tt-content\";\n\n container.appendChild(toolbar);\n container.appendChild(content);\n\n const ed = new Editor({\n element: content,\n extensions: [\n StarterKit,\n Link.configure({ openOnClick: false }),\n Image,\n Underline,\n Placeholder.configure({ placeholder: \"Write your message...\" }),\n ],\n content: \"\",\n });\n\n // Wire toolbar buttons\n toolbar.querySelectorAll(\".tt-btn\").forEach((btn: Element) => {\n btn.addEventListener(\"mousedown\", (e) => {\n e.preventDefault();\n const cmd = (btn as HTMLElement).dataset.cmd;\n switch (cmd) {\n case \"bold\": ed.chain().focus().toggleBold().run(); break;\n case \"italic\": ed.chain().focus().toggleItalic().run(); break;\n case \"underline\": ed.chain().focus().toggleUnderline().run(); break;\n case \"strike\": ed.chain().focus().toggleStrike().run(); break;\n case \"bulletList\": ed.chain().focus().toggleBulletList().run(); break;\n case \"orderedList\": ed.chain().focus().toggleOrderedList().run(); break;\n case \"blockquote\": ed.chain().focus().toggleBlockquote().run(); break;\n case \"link\": {\n const url = prompt(\"URL:\");\n if (url) ed.chain().focus().setLink({ href: url }).run();\n break;\n }\n case \"clearFormat\": ed.chain().focus().clearNodes().unsetAllMarks().run(); break;\n }\n });\n });\n\n // Wire heading select\n const headingSelect = toolbar.querySelector(\".tt-heading\") as HTMLSelectElement;\n headingSelect?.addEventListener(\"change\", () => {\n const val = headingSelect.value;\n if (val === \"p\") ed.chain().focus().setParagraph().run();\n else ed.chain().focus().toggleHeading({ level: parseInt(val) as 1 | 2 | 3 }).run();\n });\n\n // Keyboard shortcuts \u2014 Quill wires these via Modules; tiptap needs them\n // hooked manually. Match the Quill bindings (Ctrl+Shift+7/8 for lists,\n // Ctrl+K for link, Ctrl+Shift+K to unlink, Ctrl+Shift+X strike, Ctrl+\\\n // clear). Listen on the editor's content element so we don't intercept\n // typing in the To/Cc/Bcc fields.\n content.addEventListener(\"keydown\", (e: KeyboardEvent) => {\n const mod = e.ctrlKey || e.metaKey;\n if (!mod) return;\n const k = e.key.toLowerCase();\n if (e.shiftKey && k === \"7\") { e.preventDefault(); ed.chain().focus().toggleOrderedList().run(); return; }\n if (e.shiftKey && k === \"8\") { e.preventDefault(); ed.chain().focus().toggleBulletList().run(); return; }\n if (e.shiftKey && k === \"x\") { e.preventDefault(); ed.chain().focus().toggleStrike().run(); return; }\n if (e.shiftKey && k === \"k\") { e.preventDefault(); ed.chain().focus().unsetLink().run(); return; }\n if (!e.shiftKey && k === \"k\") {\n e.preventDefault();\n const url = prompt(\"URL:\");\n if (url) ed.chain().focus().setLink({ href: url }).run();\n return;\n }\n if (k === \"\\\\\") { e.preventDefault(); ed.chain().focus().clearNodes().unsetAllMarks().run(); return; }\n });\n\n const editorEl = content.querySelector(\".tiptap\") as HTMLElement || content;\n\n return {\n setHtml(html: string): void {\n ed.commands.setContent(html);\n },\n getHtml(): string {\n return ed.getHTML();\n },\n getText(): string {\n return ed.getText();\n },\n focus(): void {\n ed.commands.focus(\"start\");\n },\n setCursor(pos: number): void {\n ed.commands.focus(\"start\");\n },\n root: editorEl,\n getScrollContainer(): HTMLElement {\n return editorEl;\n },\n onContentChange(handler: () => void): void {\n ed.on(\"update\", handler);\n },\n onKeyDown(handler: (e: KeyboardEvent) => void): void {\n editorEl.addEventListener(\"keydown\", handler);\n },\n insertTextAtCursor(text: string): void {\n ed.commands.insertContent(text);\n }\n };\n}\n\n// \u2500\u2500 Factory \u2500\u2500\n\nexport type EditorType = \"quill\" | \"tiptap\" | \"tinymce\";\n\nexport async function createEditor(container: HTMLElement, type: EditorType): Promise<MailxEditor> {\n if (type === \"tiptap\") {\n return createTiptapEditor(container);\n }\n if (type === \"tinymce\") {\n return createTinyMceEditor(container);\n }\n return createQuillEditor(container);\n}\n\n/** Default jsDelivr URL \u2014 works without an API key. Users can override\n * in Settings if they want Tiny Cloud or a self-hosted bundle. */\n// Local bundled TinyMCE \u2014 copied from node_modules/tinymce/ into\n// client/lib/tinymce/ by build-tinymce.js. Loaded with no internet\n// dependency at runtime. Relative to compose.html (`client/compose/`)\n// so it works under both msger custom protocol and Android file:// .\n// Users wanting Tiny Cloud premium override this via the\n// `mailx-tinymce-cdn` localStorage entry surfaced in Settings.\nconst DEFAULT_TINYMCE_CDN = \"../lib/tinymce/tinymce.min.js\";\n\n/** TinyMCE branch \u2014 dynamic-imports the rmf-tiny adapter so a missing\n * module only affects this branch. Throws on failure so the outer\n * compose.ts fallback can take over (and properly load Quill's\n * assets first). Don't fall back to Quill inline here \u2014 Quill's\n * global isn't loaded when type === \"tinymce\" was requested, so\n * `new Quill()` throws \"Quill is not defined\" and masks the real\n * rmf-tiny error in the log. */\nasync function createTinyMceEditor(container: HTMLElement): Promise<MailxEditor> {\n let cdnUrl = DEFAULT_TINYMCE_CDN;\n let apiKey: string | undefined;\n try {\n cdnUrl = localStorage.getItem(\"mailx-tinymce-cdn\") || DEFAULT_TINYMCE_CDN;\n apiKey = localStorage.getItem(\"mailx-tinymce-apikey\") || undefined;\n } catch { /* private mode \u2014 use defaults */ }\n // Build step copies node_modules/@bobfrankston/rmf-tiny/src/adapter.js\n // \u2192 client/lib/rmf-tiny.js. Bare-name `@bobfrankston/rmf-tiny` resolution\n // would target `node_modules/`, which msger's custom protocol can't reach\n // (outside contentDir=client/). The relative path stays inside the\n // served root.\n const m = (await import(\"../lib/rmf-tiny.js\" as any)) as {\n createTinyMceEditor(container: HTMLElement, opts: { cdnUrl?: string; apiKey?: string }): Promise<MailxEditor>;\n };\n const ed = await m.createTinyMceEditor(container, { cdnUrl, apiKey });\n return ed as unknown as MailxEditor;\n}\n", "/**\n * Compose window entry point.\n * Opened as a popup from the main mailx window.\n * Receives init data via window.opener.postMessage or URL params.\n */\n\nimport { createEditor, type MailxEditor } from \"./editor.js\";\nimport { getVersion, getSettings, getAccounts, searchContacts, sendMessage, saveDraft as apiSaveDraft, deleteDraft, logClientEvent, addPreferredContact, addToDenylist, openInWord, closeWordEdit, onEvent } from \"../lib/api-client.js\";\nimport { showContextMenu } from \"../components/context-menu.js\";\n\n// Very first line the iframe runs \u2014 if this doesn't reach Node, the iframe\n// itself isn't loading or the bridge is completely broken.\nlogClientEvent(\"compose-module-loaded\", { href: location.href, version: (window as any).mailxVersion || \"?\" });\n\n// Per-step timing \u2014 Ctrl+N \u2192 compose-visible has been a perceived-latency\n// problem; the `[compose-tick]` log lines isolate which stage actually\n// costs the time. `t0` is iframe-module-execution-start; each tick adds\n// the delta plus a stage label.\nconst _composeT0 = performance.now();\nfunction _ctick(label: string): void {\n const ms = (performance.now() - _composeT0).toFixed(0).padStart(5);\n try { logClientEvent(`compose-tick ${ms}ms ${label}`); } catch { /* */ }\n}\n_ctick(\"module body executing\");\n\n/** Close compose window */\nfunction closeCompose(): void {\n logClientEvent(\"compose-close\");\n // S61: Android WebView's window.close() override is unreliable inside\n // iframes \u2014 compose overlay sometimes stays visible after Send. Primary\n // path is a parent postMessage; window.close() is a fallback that also\n // works on desktop/msger where the override DOES fire reliably.\n try { parent.postMessage({ type: \"mailx-compose-close\" }, \"*\"); } catch { /* */ }\n try { window.close(); } catch { /* */ }\n}\n\ninterface ComposeInit {\n mode: string;\n accountId: string;\n to: { name: string; address: string }[];\n cc: { name: string; address: string }[];\n subject: string;\n bodyHtml: string;\n inReplyTo: string;\n references: string[];\n accounts: { id: string; name: string; email: string }[];\n fromAddress?: string;\n draftUid?: number;\n draftFolderId?: number;\n}\n\n// \u2500\u2500 Load editor scripts dynamically \u2500\u2500\n\nfunction loadScript(src: string): Promise<void> {\n return new Promise((resolve, reject) => {\n const s = document.createElement(\"script\");\n s.src = src;\n s.onload = () => resolve();\n s.onerror = () => reject(new Error(`Failed to load ${src}`));\n document.head.appendChild(s);\n });\n}\n\nfunction loadCSS(href: string): void {\n const link = document.createElement(\"link\");\n link.rel = \"stylesheet\";\n link.href = href;\n document.head.appendChild(link);\n}\n\nasync function loadEditorAssets(type: \"quill\" | \"tiptap\"): Promise<void> {\n if (type === \"tiptap\") {\n // tiptap UMD bundles from CDN\n const cdn = \"https://cdn.jsdelivr.net/npm\";\n await loadScript(`${cdn}/@tiptap/core@2/dist/index.umd.js`);\n await Promise.all([\n loadScript(`${cdn}/@tiptap/starter-kit@2/dist/index.umd.js`),\n loadScript(`${cdn}/@tiptap/extension-link@2/dist/index.umd.js`),\n loadScript(`${cdn}/@tiptap/extension-image@2/dist/index.umd.js`),\n loadScript(`${cdn}/@tiptap/extension-underline@2/dist/index.umd.js`),\n loadScript(`${cdn}/@tiptap/extension-placeholder@2/dist/index.umd.js`),\n ]);\n } else {\n // Quill \u2014 bundled locally by bin/build-quill.js to client/lib/quill/.\n // Earlier versions loaded from jsdelivr CDN, which added 100-500 ms\n // (network-dependent) to every compose open. Relative paths resolve\n // against `client/compose/`, so `../lib/quill/...` reaches the copy.\n loadCSS(\"../lib/quill/quill.snow.css\");\n await loadScript(\"../lib/quill/quill.js\");\n }\n}\n\n// \u2500\u2500 Determine editor type from settings \u2500\u2500\n//\n// Compose must open fast. The previous flow awaited getVersion() then\n// getSettings() sequentially before the editor was even loaded \u2014 any\n// service-side stall (busy sync, slow IMAP, hung OAuth refresh) turned\n// \"click Reply\" into a multi-second / multi-minute wait with a blank\n// compose window. Local-first: read the editor-type preference from a\n// tiny localStorage cache that we update whenever getSettings succeeds\n// in the background. Default to quill on first run / cache miss.\ntype EditorTypeChoice = \"quill\" | \"tiptap\" | \"tinymce\";\nlet editorType: EditorTypeChoice = \"quill\";\nlet appSettings: any = null;\ntry {\n const cached = localStorage.getItem(\"mailx-editor-type\");\n if (cached === \"tiptap\" || cached === \"quill\" || cached === \"tinymce\") editorType = cached;\n} catch { /* private-mode / SecurityError \u2014 default quill */ }\n// Refresh the cache asynchronously \u2014 doesn't block compose open.\n(async () => {\n try {\n appSettings = await getSettings();\n const settingValue = appSettings?.ui?.editor;\n const next: EditorTypeChoice =\n settingValue === \"tiptap\" ? \"tiptap\"\n : settingValue === \"tinymce\" ? \"tinymce\"\n : \"quill\";\n try { localStorage.setItem(\"mailx-editor-type\", next); } catch { /* */ }\n // Note: we don't hot-swap the editor if the preference changed while\n // compose was opening \u2014 the old type is already instantiated. Next\n // compose open will pick up the new preference.\n } catch { /* non-fatal */ }\n})();\n\n// Editor init \u2014 try the preferred type first; if its CDN/setup fails,\n// fall back to the OTHER editor before giving up on the toolbar entirely.\n// Both Quill and tiptap fetch from jsdelivr; transient CDN issues used to\n// drop the user into a plain-textarea fallback with no formatting controls.\n// Now we keep trying until either an editor with a toolbar comes up, or\n// both fail and we render the minimal contenteditable as last resort.\nlet editor: MailxEditor;\nconst container = document.getElementById(\"compose-editor\")!;\n\n/** Update the small badge in the compose toolbar showing which editor\n * is actually active (may differ from the user's setting if the\n * preferred one failed to load). Helpful for diagnosing paste / format\n * differences across Quill / tiptap / TinyMCE without diving into logs. */\nfunction setActiveEditorBadge(type: EditorTypeChoice | \"fallback\"): void {\n const el = document.getElementById(\"compose-editor-badge\");\n if (!el) return;\n const labels: Record<string, string> = {\n quill: \"Quill\",\n tiptap: \"tiptap\",\n tinymce: \"TinyMCE\",\n fallback: \"plain (fallback)\",\n };\n const docs: Record<string, string> = {\n quill: \"https://quilljs.com/docs/quickstart\",\n tiptap: \"https://tiptap.dev/docs/editor/introduction\",\n tinymce: \"https://www.tiny.cloud/docs/tinymce/6/\",\n fallback: \"https://github.com/BobFrankston/mailx/blob/master/app/docs/editor.md\",\n };\n el.textContent = labels[type] || type;\n el.dataset.editor = type;\n const url = docs[type];\n if (url) {\n el.style.cursor = \"pointer\";\n el.title = `Open ${labels[type]} documentation`;\n el.onclick = () => {\n const api = (window as any).mailxapi;\n if (api?.openExternal) api.openExternal(url);\n else window.open(url, \"_blank\", \"noopener,noreferrer\");\n };\n } else {\n el.style.cursor = \"\";\n el.title = \"\";\n el.onclick = null;\n }\n}\n\nasync function tryEditor(type: EditorTypeChoice): Promise<MailxEditor | null> {\n try {\n // tinymce loads itself via the rmf-tiny adapter (no jsdelivr asset).\n if (type !== \"tinymce\") await loadEditorAssets(type);\n } catch (e: any) {\n logClientEvent(\"compose-editor-assets-failed\", { type, error: String(e?.message || e) });\n return null;\n }\n container.classList.remove(\"editor-tiptap\", \"editor-quill\", \"editor-tinymce\");\n container.classList.add(`editor-${type}`);\n try {\n const ed = await createEditor(container, type);\n // TinyMCE: wire our JS spellchecker into the editor iframe so\n // right-click on a misspelled word offers suggestions + \"Add to\n // dictionary.\" WebView2's built-in spellcheck doesn't reliably\n // engage on msger-hosted iframes (see msger main.rs around\n // IsSpellcheckEnabled). nspell + dictionary-en gives us the\n // suggestions UX without depending on the host. Done at this\n // layer (not inside rmf-tiny) so the spellcheck stays a mailx\n // concern; other apps embedding rmf-tiny stay clean.\n if (type === \"tinymce\" && ed && (ed as any).nativeEditor) {\n const native = (ed as any).nativeEditor;\n const attach = () => {\n import(\"./spellcheck.js\").then(m => m.wireSpellcheck(native))\n .catch(e => console.error(\"[compose] spellcheck wire failed:\", e));\n };\n // TinyMCE's iframe exists immediately, but `getDoc()` returns\n // null until the editor's `init` event fires. Hook either path.\n if (native.initialized) attach();\n else native.on(\"init\", attach);\n }\n return ed;\n } catch (e: any) {\n logClientEvent(\"compose-editor-create-failed\", { type, error: String(e?.message || e) });\n return null;\n }\n}\n\nlet activeEditorType: EditorTypeChoice | \"fallback\" = editorType;\n_ctick(`editor load start (${editorType})`);\neditor = (await tryEditor(editorType))!;\n_ctick(`editor load end (${editorType}, ok=${!!editor})`);\nif (!editor) {\n // Preferred editor failed \u2014 try the other one before giving up.\n // For tinymce: fall back to quill since the user opted into tinymce\n // explicitly; if it isn't installed, give them the working default.\n const fallbackType: EditorTypeChoice = editorType === \"quill\" ? \"tiptap\" : \"quill\";\n logClientEvent(\"compose-editor-fallback-other\", { from: editorType, to: fallbackType });\n container.innerHTML = \"\"; // clear any partial render from the failed try\n editor = (await tryEditor(fallbackType))!;\n if (editor) {\n activeEditorType = fallbackType;\n setTimeout(() => showDraftStatus(`${editorType} editor unavailable \u2014 using ${fallbackType} instead.`, false), 0);\n }\n}\nif (!editor) {\n // Both editors failed \u2014 render a minimal contenteditable as last resort.\n // No toolbar, no rich-text shortcuts; user can still type. Status bar\n // surfaces the failure so the missing toolbar isn't a silent mystery.\n logClientEvent(\"compose-editor-create-failed-both\", {});\n container.innerHTML = `<div class=\"compose-fallback-editor\" contenteditable=\"true\" style=\"border:1px solid #c00;padding:8px;min-height:200px;background:#fff\" data-fallback=\"true\"></div>`;\n const fallback = container.querySelector<HTMLElement>(\".compose-fallback-editor\")!;\n editor = {\n root: fallback,\n setHtml: (html: string) => { fallback.innerHTML = html; },\n getHtml: () => fallback.innerHTML,\n getText: () => fallback.innerText,\n focus: () => fallback.focus(),\n setCursor: () => { /* no-op */ },\n getScrollContainer: () => fallback,\n onContentChange: (handler: () => void) => { fallback.addEventListener(\"input\", handler); },\n onKeyDown: (handler: (e: KeyboardEvent) => void) => { fallback.addEventListener(\"keydown\", handler); },\n insertTextAtCursor: (text: string) => {\n const sel = window.getSelection();\n if (sel && sel.rangeCount > 0) {\n const range = sel.getRangeAt(0);\n range.deleteContents();\n range.insertNode(document.createTextNode(text));\n } else {\n fallback.append(document.createTextNode(text));\n }\n },\n };\n activeEditorType = \"fallback\";\n setTimeout(() => showDraftStatus(`Both editors failed to load. Plain-text fallback in use. Check the log; CDN may be unreachable.`, true), 0);\n}\nsetActiveEditorBadge(activeEditorType);\n\n// Ctrl+scroll / Ctrl+= / Ctrl+- / Ctrl+0 zoom for the compose editor body.\n// Persists per-session in localStorage so zoom survives window pop/close cycles.\n(() => {\n const STORAGE_KEY = \"mailx.compose.zoom\";\n const MIN = 0.5, MAX = 3, STEP = 0.1;\n // Default 1.15 \u2014 matches the viewer's bumped default so reading and\n // composing have the same size baseline. Persisted value overrides.\n let zoom = parseFloat(localStorage.getItem(STORAGE_KEY) || \"1.15\") || 1.15;\n const applyZoom = () => {\n container.style.fontSize = `${zoom}em`;\n localStorage.setItem(STORAGE_KEY, String(zoom));\n };\n applyZoom();\n container.addEventListener(\"wheel\", (e: WheelEvent) => {\n if (!e.ctrlKey) return;\n e.preventDefault();\n const delta = e.deltaY < 0 ? STEP : -STEP;\n zoom = Math.min(MAX, Math.max(MIN, Math.round((zoom + delta) * 10) / 10));\n applyZoom();\n }, { passive: false });\n document.addEventListener(\"keydown\", (e: KeyboardEvent) => {\n if (!(e.ctrlKey || e.metaKey)) return;\n if (e.key === \"=\" || e.key === \"+\") { zoom = Math.min(MAX, zoom + STEP); applyZoom(); e.preventDefault(); }\n else if (e.key === \"-\") { zoom = Math.max(MIN, zoom - STEP); applyZoom(); e.preventDefault(); }\n else if (e.key === \"0\") { zoom = 1; applyZoom(); e.preventDefault(); }\n });\n})();\n\n// \u2500\u2500 Populate from init data \u2500\u2500\n\n// From field is a free-text input with a <datalist> of known accounts. The\n// user can pick a preset or type an arbitrary \"Name <addr@domain>\" \u2014 no\n// separate \"Other...\" escape hatch, no hidden custom input toggle.\nconst fromInput = document.getElementById(\"compose-from-input\") as HTMLInputElement;\nconst fromOptions = document.getElementById(\"compose-from-options\") as HTMLDataListElement;\n// Address fields are textareas (not inputs) so they wrap to multiple lines\n// when the recipient list gets long \u2014 single-line inputs scrolled off-screen\n// horizontally. JS auto-grows their height on input below.\nconst toInput = document.getElementById(\"compose-to\") as HTMLTextAreaElement;\nconst ccInput = document.getElementById(\"compose-cc\") as HTMLTextAreaElement;\nconst bccInput = document.getElementById(\"compose-bcc\") as HTMLTextAreaElement;\nconst subjectInput = document.getElementById(\"compose-subject\") as HTMLInputElement;\n\n/** Resize an address textarea to fit its content. Called on input + after\n * programmatic value sets (applyInit). Caps at 8 lines \u2014 beyond that the\n * field scrolls to keep the overall compose window manageable. */\nfunction autoGrowAddrInput(el: HTMLTextAreaElement | null): void {\n if (!el) return;\n el.style.height = \"auto\";\n const max = 8 * 22; // ~8 lines \u00D7 line-height\n el.style.height = Math.min(el.scrollHeight, max) + \"px\";\n if (el.scrollHeight > max) el.style.overflowY = \"auto\";\n else el.style.overflowY = \"hidden\";\n}\nfor (const el of [toInput, ccInput, bccInput]) {\n el?.addEventListener(\"input\", () => autoGrowAddrInput(el));\n // Treat Enter as a recipient separator (split on comma OR newline at\n // send time), but don't insert a literal newline \u2014 that'd break the\n // implicit \"comma-separated list\" assumption downstream. Replace with\n // a comma + space.\n el?.addEventListener(\"keydown\", (e) => {\n if (e.key === \"Enter\" && !e.shiftKey && !e.ctrlKey && !e.metaKey) {\n e.preventDefault();\n const start = el.selectionStart || 0;\n const v = el.value;\n el.value = v.slice(0, start).replace(/[,\\s]+$/, \"\") + \", \" + v.slice(start).replace(/^[,\\s]+/, \"\");\n el.selectionStart = el.selectionEnd = start + 2;\n autoGrowAddrInput(el);\n }\n });\n}\n\n/** Registered accounts \u2014 populated once at init time, used to map the From\n * input value back to an account id on send. */\ninterface ComposeAccount { id: string; name: string; label?: string; email: string; defaultSend?: boolean; }\nlet knownAccounts: ComposeAccount[] = [];\n\n// \u2500\u2500 AI ghost text autocomplete \u2500\u2500\nif (appSettings?.autocomplete?.enabled && appSettings.autocomplete.provider !== \"off\") {\n import(\"./ghost-text.js\").then(({ initGhostText }) => {\n initGhostText(editor, {\n getSubject: () => subjectInput.value,\n getTo: () => toInput.value,\n }, { debounceMs: appSettings.autocomplete.debounceMs || 600 });\n }).catch(() => { /* autocomplete unavailable */ });\n}\n\n/** Format an account for the From field: \"Name <email>\". */\nfunction formatAccountFrom(acct: ComposeAccount): string {\n return `${acct.name} <${acct.email}>`;\n}\n\nconst FROM_HISTORY_KEY = \"mailx-from-history\"; // up to 20 recent manual From entries\nconst FROM_HISTORY_MAX = 20;\n\nfunction loadFromHistory(): string[] {\n try { return JSON.parse(localStorage.getItem(FROM_HISTORY_KEY) || \"[]\"); } catch { return []; }\n}\nfunction recordFromHistory(value: string): void {\n const v = (value || \"\").trim();\n if (!v) return;\n try {\n const list = loadFromHistory().filter(x => x !== v);\n list.unshift(v);\n localStorage.setItem(FROM_HISTORY_KEY, JSON.stringify(list.slice(0, FROM_HISTORY_MAX)));\n } catch { /* private mode */ }\n}\n\n/** Populate the From <datalist> with one entry per known account plus any\n * manually-typed addresses from localStorage history. Account entries rank\n * first; history entries get an \"(used before)\" label so the user can tell\n * which ones are real accounts vs free-form aliases. */\nfunction populateFromOptions(accounts: ComposeAccount[], selectedId?: string): void {\n knownAccounts = accounts;\n fromOptions.innerHTML = \"\";\n const seenValues = new Set<string>();\n for (const acct of accounts) {\n const opt = document.createElement(\"option\");\n opt.value = formatAccountFrom(acct);\n const tag = acct.label || acct.name;\n opt.label = tag;\n fromOptions.appendChild(opt);\n seenValues.add(opt.value);\n }\n // Custom From history \u2014 addresses the user has typed before that don't\n // match any known account (aliases, +tag addresses, one-off identities).\n // Stored in localStorage because they're inherently per-device preferences;\n // moving them to an account profile would be a different feature.\n for (const value of loadFromHistory()) {\n if (seenValues.has(value)) continue;\n const opt = document.createElement(\"option\");\n opt.value = value;\n opt.label = \"(used before)\";\n fromOptions.appendChild(opt);\n }\n if (!fromInput.value) {\n const selected = (selectedId && accounts.find(a => a.id === selectedId)) ||\n accounts.find(a => a.defaultSend) ||\n accounts[0];\n if (selected) fromInput.value = formatAccountFrom(selected);\n }\n}\n\n/** Parse the current From input into { name, address } for header building. */\nfunction parseFromInput(): { name: string; address: string } {\n const raw = fromInput.value.trim();\n const match = raw.match(/^(.+?)\\s*<(.+?)>$/);\n if (match) return { name: match[1].trim(), address: match[2].trim() };\n return { name: \"\", address: raw };\n}\n\n/** Match the From input's address against the known accounts table and\n * return that account's id. Used by send() / saveDraft() to decide which\n * account to send through. Falls back to defaultSend, then first account. */\nfunction getFromAccountId(): string {\n const { address } = parseFromInput();\n const lower = address.toLowerCase();\n // Exact match wins\n const exact = knownAccounts.find(a => a.email.toLowerCase() === lower);\n if (exact) return exact.id;\n // Same-domain match \u2014 handles +tag aliases and identity addresses\n const domain = lower.split(\"@\")[1] || \"\";\n if (domain) {\n const sameDomain = knownAccounts.find(a => a.email.toLowerCase().endsWith(\"@\" + domain));\n if (sameDomain) return sameDomain.id;\n }\n // Give up \u2014 use default send account or the first account\n const def = knownAccounts.find(a => a.defaultSend) || knownAccounts[0];\n return def?.id || \"\";\n}\n\n/** Get the raw From header string (\"Name <addr>\"). */\nfunction getFromAddress(): string {\n return fromInput.value.trim();\n}\n\n/** Smart tab \u2014 skip to next empty field, ending at body */\nfunction smartTab(current: HTMLInputElement): void {\n const fields = [toInput, ccInput, bccInput, subjectInput];\n const currentIdx = fields.indexOf(current);\n // Look for next empty field after current\n for (let i = currentIdx + 1; i < fields.length; i++) {\n if (!fields[i].value.trim()) {\n fields[i].focus();\n return;\n }\n }\n // All fields filled or past the end \u2014 go to editor body\n editor.focus();\n}\n\n// \u2500\u2500 Autocomplete \u2500\u2500\n\n/** Right-click on an autocomplete row \u2192 contextual actions. Two paths:\n * - Add to preferred (small modal: name / email / source-tag / org \u2192 write to\n * contacts.jsonc#preferred[])\n * - Never suggest this address (write to contacts.jsonc#denylist[]; the\n * service-side handler purges any matching discovered rows on apply) */\nfunction showAutocompleteContextMenu(\n e: MouseEvent,\n row: { name: string; email: string; source: string },\n): void {\n showContextMenu(e.clientX, e.clientY, [\n {\n label: \"Add to preferred\u2026\",\n action: () => openAddToPreferredModal(row),\n },\n {\n label: \"Never suggest this address\",\n action: async () => {\n try {\n await addToDenylist(row.email);\n } catch (err: any) {\n alert(`Failed to add to denylist: ${err?.message || err}`);\n }\n },\n },\n {\n // Punt real Google-contact editing to Google's own UI, which\n // already handles add-to-new / add-to-existing / merge \u2014 no\n // in-app picker or People API write scope needed.\n label: \"Open in Google Contacts\u2026\",\n tooltip: \"Add as a new contact, attach to an existing one, or merge \u2014 in Google's own UI.\",\n action: () => {\n window.open(`https://contacts.google.com/search/${encodeURIComponent(row.name || row.email)}`, \"_blank\");\n },\n },\n ]);\n}\n\nfunction openAddToPreferredModal(prefill: { name: string; email: string; source: string }): void {\n const overlay = document.createElement(\"div\");\n overlay.className = \"modal-overlay\";\n overlay.innerHTML = `\n <div class=\"modal\" role=\"dialog\" aria-label=\"Add to preferred contacts\">\n <h3>Add to preferred</h3>\n <p class=\"muted\">Saved to <code>contacts.jsonc</code> on your shared drive.</p>\n <label>Name <input type=\"text\" id=\"pf-name\" /></label>\n <label>Email <input type=\"email\" id=\"pf-email\" /></label>\n <label>Source tag <input type=\"text\" id=\"pf-source\" placeholder=\"(optional \u2014 e.g. work, family)\" /></label>\n <label>Organization <input type=\"text\" id=\"pf-org\" placeholder=\"(optional)\" /></label>\n <div class=\"modal-actions\">\n <button id=\"pf-cancel\">Cancel</button>\n <button id=\"pf-save\" class=\"primary\">Save</button>\n </div>\n </div>\n `;\n document.body.appendChild(overlay);\n (overlay.querySelector(\"#pf-name\") as HTMLInputElement).value = prefill.name || \"\";\n (overlay.querySelector(\"#pf-email\") as HTMLInputElement).value = prefill.email || \"\";\n // Pre-fill source from existing tag if it's already a custom one (not a system source).\n const sysSources = new Set([\"google\", \"discovered\", \"preferred\", \"\"]);\n const initSource = sysSources.has(prefill.source || \"\") ? \"\" : prefill.source;\n (overlay.querySelector(\"#pf-source\") as HTMLInputElement).value = initSource;\n const close = () => overlay.remove();\n overlay.querySelector(\"#pf-cancel\")!.addEventListener(\"click\", close);\n overlay.addEventListener(\"click\", (ev) => { if (ev.target === overlay) close(); });\n overlay.querySelector(\"#pf-save\")!.addEventListener(\"click\", async () => {\n const name = (overlay.querySelector(\"#pf-name\") as HTMLInputElement).value.trim();\n const email = (overlay.querySelector(\"#pf-email\") as HTMLInputElement).value.trim();\n const source = (overlay.querySelector(\"#pf-source\") as HTMLInputElement).value.trim();\n const org = (overlay.querySelector(\"#pf-org\") as HTMLInputElement).value.trim();\n if (!email) { alert(\"Email is required.\"); return; }\n try {\n await addPreferredContact({ name, email, source, organization: org });\n close();\n } catch (err: any) {\n alert(`Failed to save: ${err?.message || err}`);\n }\n });\n (overlay.querySelector(\"#pf-name\") as HTMLInputElement).focus();\n}\n\nfunction setupAutocomplete(input: HTMLInputElement | HTMLTextAreaElement): void {\n let dropdown: HTMLDivElement | null = null;\n let activeIndex = -1;\n let debounce: ReturnType<typeof setTimeout>;\n\n function closeDropdown(): void {\n if (dropdown) { dropdown.remove(); dropdown = null; }\n activeIndex = -1;\n }\n\n function getCaretToken(): string {\n const val = input.value;\n const caret = input.selectionStart ?? val.length;\n const { start, end } = tokenSpanAtCaret(val, caret);\n return val.substring(start, end).trim();\n }\n\n function replaceCaretToken(replacement: string): void {\n const val = input.value;\n const caret = input.selectionStart ?? val.length;\n const { start, end } = tokenSpanAtCaret(val, caret);\n const before = val.substring(0, start);\n const after = val.substring(end);\n // A space after a preceding comma; a trailing \", \" only when this is\n // the final token, so the user can carry on with the next recipient\n // without disturbing recipients that already follow.\n const lead = before.length && !before.endsWith(\" \") ? \" \" : \"\";\n const isLast = after.trim() === \"\";\n const insert = lead + replacement + (isLast ? \", \" : \"\");\n input.value = before + insert + after;\n const pos = before.length + insert.length;\n closeDropdown();\n input.focus();\n input.setSelectionRange(pos, pos);\n }\n\n input.addEventListener(\"input\", () => {\n clearTimeout(debounce);\n const token = getCaretToken();\n if (token.length < 1) { closeDropdown(); return; }\n\n debounce = setTimeout(() => {\n // rAF yield before hitting the DB \u2014 S60 mitigation, same reason\n // as the draft-save path. The 200 ms timer already deferred past\n // the input burst; this extra frame lets the last keystroke paint.\n requestAnimationFrame(async () => {\n try {\n const results = await searchContacts(token) as { name: string; email: string; source: string; sources?: string[]; useCount: number }[];\n if (results.length === 0) { closeDropdown(); return; }\n\n closeDropdown();\n dropdown = document.createElement(\"div\");\n dropdown.className = \"ac-dropdown\";\n activeIndex = 0; // first item highlighted by default\n\n for (let i = 0; i < results.length; i++) {\n const r = results[i];\n const item = document.createElement(\"div\");\n item.className = `ac-item${i === 0 ? \" ac-active\" : \"\"}`;\n\n // Text column (name / email / source badge).\n const main = document.createElement(\"div\");\n main.className = \"ac-item-main\";\n\n const nameEl = document.createElement(\"span\");\n nameEl.className = \"ac-item-name\";\n nameEl.textContent = r.name || r.email;\n\n const emailEl = document.createElement(\"span\");\n emailEl.className = \"ac-item-email\";\n emailEl.textContent = r.email;\n\n // Source badge(s) \u2014 shows where this row came from. When\n // the same address is in multiple sources (e.g. google AND\n // discovered after Google Contacts sync), the merged list\n // is displayed comma-separated. Custom user tags from\n // contacts.jsonc preferred entries (`source: \"work\"`,\n // `source: \"family\"`) flow through verbatim alongside the\n // system sources google / discovered / preferred.\n const sourceEl = document.createElement(\"span\");\n sourceEl.className = \"ac-item-source\";\n const sourceList = (r.sources && r.sources.length)\n ? r.sources\n : (r.source ? [r.source] : []);\n sourceEl.textContent = sourceList.join(\", \");\n\n main.appendChild(nameEl);\n if (r.name) main.appendChild(emailEl);\n if (sourceList.length) main.appendChild(sourceEl);\n\n // Visible per-row prefer / ignore buttons (shown on\n // hover). A plain click \u2014 no dependence on right-click /\n // the context menu, which is undiscoverable and flaky.\n // \u2605 \u2192 contacts.jsonc#preferred[]\n // \u2298 \u2192 contacts.jsonc#denylist[] (purges discovered)\n const actions = document.createElement(\"div\");\n actions.className = \"ac-item-actions\";\n const mkBtn = (glyph: string, title: string, run: () => Promise<unknown>): HTMLButtonElement => {\n const b = document.createElement(\"button\");\n b.type = \"button\";\n b.className = \"ac-item-btn\";\n b.textContent = glyph;\n b.title = title;\n // Block the row's mousedown-to-select so the button\n // acts on its own without inserting the address.\n b.addEventListener(\"mousedown\", (e) => { e.preventDefault(); e.stopPropagation(); });\n b.addEventListener(\"click\", async (e) => {\n e.preventDefault();\n e.stopPropagation();\n try { await run(); } catch (err: any) { alert(`Failed: ${err?.message || err}`); }\n closeDropdown();\n });\n return b;\n };\n actions.appendChild(mkBtn(\"\u2605\", \"Prefer this contact\", () => addPreferredContact({ name: r.name, email: r.email, source: \"\", organization: \"\" })));\n actions.appendChild(mkBtn(\"\u2298\", \"Never suggest this address\", () => addToDenylist(r.email)));\n // First-pass alias handling: instead of an in-app\n // add-to-existing-vs-new picker, just open Google Contacts\n // searched for this contact so the user can merge an alias\n // / create / edit there with Google's own UI.\n actions.appendChild(mkBtn(\"\u2197\", \"Open in Google Contacts (add, edit, merge aliases)\", async () => {\n window.open(`https://contacts.google.com/search/${encodeURIComponent(r.name || r.email)}`, \"_blank\");\n }));\n\n item.appendChild(main);\n item.appendChild(actions);\n\n item.addEventListener(\"mousedown\", (e) => {\n // preventDefault on EVERY button \u2014 this is what keeps the\n // To input focused. Without it a right-click blurs the\n // input, blur schedules closeDropdown() 150ms later, and\n // a deliberate right-click then fires `contextmenu` after\n // the dropdown is already gone \u2014 landing on the editor\n // underneath (Bob 2026-05-16). Only the LEFT button then\n // proceeds to select-and-close; right-click falls through\n // to the row's contextmenu handler with the row intact.\n e.preventDefault();\n if (e.button !== 0) return;\n const display = formatRecipient(r.name, r.email);\n replaceCaretToken(display);\n });\n\n // Right-click still offers the same actions (plus the\n // full \"Add to preferred\u2026\" modal) as a bonus when it works.\n item.addEventListener(\"contextmenu\", (e) => {\n e.preventDefault();\n e.stopPropagation();\n showAutocompleteContextMenu(e as MouseEvent, r);\n });\n\n dropdown.appendChild(item);\n }\n\n input.parentElement!.appendChild(dropdown);\n\n // Flip the dropdown above the input if there's no room below.\n // Compose can be a small popup window (or the viewport can be\n // short on phones / split screens) \u2014 when To is near the\n // bottom, the default `top: 100%` drop spills off-screen and\n // most matches are unreachable. Measure on insertion: if the\n // dropdown's bottom would clear the viewport AND there's more\n // room above the input than below, anchor it to the top of\n // the field instead.\n requestAnimationFrame(() => {\n if (!dropdown) return;\n const rect = dropdown.getBoundingClientRect();\n const inputRect = input.getBoundingClientRect();\n const spaceBelow = window.innerHeight - inputRect.bottom;\n const spaceAbove = inputRect.top;\n if (rect.bottom > window.innerHeight && spaceAbove > spaceBelow) {\n dropdown.style.top = \"auto\";\n dropdown.style.bottom = \"100%\";\n }\n });\n } catch { /* ignore */ }\n });\n }, 200);\n });\n\n input.addEventListener(\"keydown\", (e: Event) => {\n if (!dropdown) return;\n const items = dropdown.querySelectorAll(\".ac-item\");\n const ke = e as KeyboardEvent;\n if (ke.key === \"ArrowDown\") {\n e.preventDefault();\n activeIndex = Math.min(activeIndex + 1, items.length - 1);\n items.forEach((el, i) => el.classList.toggle(\"ac-active\", i === activeIndex));\n } else if (ke.key === \"ArrowUp\") {\n e.preventDefault();\n activeIndex = Math.max(activeIndex - 1, 0);\n items.forEach((el, i) => el.classList.toggle(\"ac-active\", i === activeIndex));\n } else if (ke.key === \"Tab\" || ke.key === \"Enter\") {\n if (items.length > 0) {\n e.preventDefault();\n const idx = activeIndex >= 0 ? activeIndex : 0;\n (items[idx] as HTMLElement).dispatchEvent(new MouseEvent(\"mousedown\"));\n // Stay in field \u2014 user may want to add more addresses\n return;\n }\n } else if (ke.key === \"Escape\") {\n closeDropdown();\n }\n });\n\n input.addEventListener(\"blur\", () => {\n setTimeout(closeDropdown, 150);\n });\n}\n\nsetupAutocomplete(toInput);\nsetupAutocomplete(ccInput);\nsetupAutocomplete(bccInput);\n\n/** Split a recipient string on TOP-LEVEL commas only \u2014 a comma inside a\n * quoted display name (`\"Frankston, Bob\" <bob@x.com>`) is part of the name,\n * not a separator. The naive `s.split(\",\")` parsed such a name as two\n * recipients (Bob 2026-05-17). */\nfunction splitRecipients(s: string): string[] {\n const out: string[] = [];\n let cur = \"\", inQuote = false;\n for (const c of s) {\n if (c === \"\\\"\") { inQuote = !inQuote; cur += c; }\n else if (c === \",\" && !inQuote) { out.push(cur); cur = \"\"; }\n else cur += c;\n }\n out.push(cur);\n return out;\n}\n\n/** [start, end) span of the recipient token containing `caret`. Tokens are\n * separated by unquoted commas (commas inside \"quoted names\" don't split).\n * Autocomplete must match the token the cursor is in \u2014 not just the final\n * one \u2014 otherwise typing a name before an existing comma matches the wrong\n * recipient. */\nfunction tokenSpanAtCaret(s: string, caret: number): { start: number; end: number } {\n let inQuote = false;\n let start = 0;\n let end = s.length;\n for (let i = 0; i < s.length; i++) {\n if (s[i] === \"\\\"\") inQuote = !inQuote;\n else if (s[i] === \",\" && !inQuote) {\n if (i < caret) start = i + 1;\n else { end = i; break; }\n }\n }\n return { start, end };\n}\n\n/** `Name <email>` \u2014 display name quoted when it contains a comma or quote,\n * so the recipient survives the comma-separated round-trip in the field. */\nfunction formatRecipient(name: string, address: string): string {\n if (!name) return address;\n const quoted = /[,\"]/.test(name) ? `\"${name.replace(/\"/g, \"'\")}\"` : name;\n return `${quoted} <${address}>`;\n}\n\nfunction formatAddrs(addrs: { name: string; address: string }[]): string {\n return addrs.map(a => formatRecipient(a.name, a.address)).join(\", \");\n}\n\nfunction parseAddrs(s: string): { name: string; address: string }[] {\n if (!s.trim()) return [];\n // Split on TOP-LEVEL commas only (see splitRecipients) so a quoted\n // comma-bearing name stays one recipient. Trailing commas / stray\n // whitespace are dropped \u2014 no phantom addresses that fail validation.\n return splitRecipients(s)\n .map(p => p.trim())\n .filter(p => p.length > 0)\n .map(part => {\n const match = part.match(/^(.+?)\\s*<(.+?)>$/);\n if (match) {\n let name = match[1].trim();\n if (name.length >= 2 && name.startsWith(\"\\\"\") && name.endsWith(\"\\\"\")) {\n name = name.slice(1, -1); // unwrap a quoted display name\n }\n return { name, address: match[2].trim() };\n }\n return { name: \"\", address: part };\n });\n}\n\n/** Expand any group names in a recipient string against the user's\n * contacts.jsonc \u2192 groups map. Recipients that match a group name (case\n * insensitive) get replaced by their member list, recursively. Unresolved\n * tokens are left in place so the user sees them and can fix the typo.\n * Loaded ASYNCHRONOUSLY at compose-init time \u2014 Send must NEVER await\n * this. Earlier `await loadGroupsMap()` inside the click handler hung\n * Send for 61 seconds (Bob 2026-05-09 00:16:57): the GDrive read of\n * contacts.jsonc was on a slow connection. Bob then clicked Send a\n * second time at 00:23, producing two real outgoing messages with\n * different Message-IDs because each Send call assigned its own. Send\n * must be instantaneous; group expansion must not gate it.\n *\n * localStorage is the synchronous tier \u2014 populated by the background\n * refresh, read instantly by `expandGroups`. If localStorage has\n * nothing yet (first ever launch on this device), expandGroups is a\n * no-op and the user's literal recipient string flows through. The\n * background refresh seeds localStorage on first success. */\nconst GROUPS_LS_KEY = \"mailx-groups-cache-v1\";\nlet _groupsCache: Record<string, string[]> = readGroupsFromLocalStorage();\n\nfunction readGroupsFromLocalStorage(): Record<string, string[]> {\n try {\n const raw = localStorage.getItem(GROUPS_LS_KEY);\n if (raw) return JSON.parse(raw) || {};\n } catch { /* */ }\n return {};\n}\n\nfunction writeGroupsToLocalStorage(groups: Record<string, string[]>): void {\n try { localStorage.setItem(GROUPS_LS_KEY, JSON.stringify(groups)); }\n catch { /* localStorage full or private mode */ }\n}\n\n/** Background refresh of the groups map. Fires once at compose init,\n * results land in `_groupsCache` and localStorage for next time. */\nfunction refreshGroupsMapInBackground(): void {\n (async () => {\n try {\n const { readJsoncFile } = await import(\"../lib/api-client.js\");\n const r = await readJsoncFile(\"contacts.jsonc\");\n if (!r?.content) return;\n const stripped = r.content.replace(/^\\s*\\/\\/.*$/gm, \"\").replace(/,(\\s*[}\\]])/g, \"$1\");\n const cfg = JSON.parse(stripped);\n const groups = (cfg && typeof cfg.groups === \"object\" && cfg.groups) ? cfg.groups : {};\n _groupsCache = groups;\n writeGroupsToLocalStorage(groups);\n } catch { /* leave existing cache in place */ }\n })();\n}\n// Kick off the background refresh once when this module loads.\nrefreshGroupsMapInBackground();\n\n/** Expand the raw recipient string against the cached groups map.\n * SYNCHRONOUS by design \u2014 Send must not wait on cloud I/O for group\n * expansion. If no groups are cached, the input passes through. */\nfunction expandGroups(raw: string): string {\n if (!raw.trim()) return raw;\n if (Object.keys(_groupsCache).length === 0) return raw;\n // Lazy-load the expansion utility synchronously isn't possible\n // since it's a separate package \u2014 fall back to inline expansion.\n // Same algorithm as `expandRecipients`: split on comma, look up\n // each token (case-insensitive), keep the original on miss.\n const groupsLc: Record<string, string[]> = {};\n for (const k of Object.keys(_groupsCache)) groupsLc[k.toLowerCase()] = _groupsCache[k];\n const tokens = raw.split(\",\").map(s => s.trim()).filter(Boolean);\n const out: string[] = [];\n for (const tok of tokens) {\n const members = groupsLc[tok.toLowerCase()];\n if (members && Array.isArray(members)) out.push(...members);\n else out.push(tok);\n }\n return out.join(\", \");\n}\n\nfunction applyInit(init: ComposeInit): void {\n // Populate the From datalist with known accounts\n populateFromOptions(init.accounts, init.accountId);\n\n // If the reply has a specific identity address (alias / +tag), set it\n // as the From value directly \u2014 overrides the account default.\n if (init.fromAddress) {\n const account = init.accounts.find(a => a.id === init.accountId);\n const displayName = account?.name || \"\";\n fromInput.value = displayName ? `${displayName} <${init.fromAddress}>` : init.fromAddress;\n }\n\n toInput.value = formatAddrs(init.to);\n ccInput.value = formatAddrs(init.cc);\n subjectInput.value = init.subject;\n // Resize the textareas to match the freshly-loaded recipient lists \u2014\n // without this the textareas stay 1-row even when the To: contains a\n // 200-character reply-all address list.\n autoGrowAddrInput(toInput);\n autoGrowAddrInput(ccInput);\n autoGrowAddrInput(bccInput);\n\n // Auto-expand Cc row if the init already has Cc content (reply-all, draft-with-cc)\n if (ccInput.value.trim()) {\n const ccRowEl = document.getElementById(\"compose-cc-row\");\n const ccBtn = document.getElementById(\"btn-toggle-cc\");\n if (ccRowEl) ccRowEl.hidden = false;\n if (ccBtn) ccBtn.classList.add(\"active\");\n } else if (init.to && init.to.length === 1) {\n // Q49: heuristic auto-expand \u2014 when replying/composing to a single\n // recipient, check sent-history. If the user has previously Cc'd or\n // Bcc'd anyone on a message to this recipient, expand the matching\n // row (empty, just visible) so they're prompted to fill it.\n // Fire-and-forget; if the service call fails or the user starts\n // typing manually before it resolves, the answer doesn't matter.\n const firstEmail = init.to[0]?.address || \"\";\n if (firstEmail) {\n import(\"../lib/api-client.js\").then(({ hasCcHistoryTo, hasBccHistoryTo }) => {\n hasCcHistoryTo(firstEmail)\n .then(res => {\n if (!res?.hasCc) return;\n const ccRowEl = document.getElementById(\"compose-cc-row\");\n const ccBtn = document.getElementById(\"btn-toggle-cc\");\n if (ccRowEl?.hidden && !ccInput.value) {\n ccRowEl.hidden = false;\n ccBtn?.classList.add(\"active\");\n }\n })\n .catch(() => { /* non-fatal hint */ });\n hasBccHistoryTo(firstEmail)\n .then(res => {\n if (!res?.hasBcc) return;\n const bccRowEl = document.getElementById(\"compose-bcc-row\");\n const bccBtn = document.getElementById(\"btn-toggle-bcc\");\n if (bccRowEl?.hidden && !bccInput.value) {\n bccRowEl.hidden = false;\n bccBtn?.classList.add(\"active\");\n }\n })\n .catch(() => { /* non-fatal hint */ });\n });\n }\n }\n\n // C42: append the account's signature (if configured) BEFORE rendering\n // the body. Two sources, in priority order:\n // 1. New `sig: { text, html? }` object \u2014 applied to NEW messages only.\n // `text` is HTML-escaped (newlines \u2192 <br>) unless `html: true` is set\n // (reserved for future use; currently `html` is ignored and text is\n // always escaped).\n // 2. Legacy `signature: string` \u2014 HTML, applied to new + reply + forward.\n //\n // Drafts are skipped \u2014 the signature is already baked into the saved body.\n // Editing an existing draft also skipped.\n let bodyToRender = init.bodyHtml || \"\";\n\n // Preserve hard line breaks inside <pre> blocks. Thunderbird-authored\n // drafts and reply quotes wrap quoted text in `<pre wrap=\"\" class=\"moz-quote-pre\">`\n // with literal `\\n` line breaks. Browsers preserve those breaks because\n // `<pre>` is whitespace-significant, but the rich-text editor (Quill /\n // TinyMCE) normalises `<pre>` to a flow block on import and silently\n // collapses internal newlines into spaces \u2014 so a draft that wrapped\n // correctly on disk shows up as one long unbroken paragraph in compose.\n // Bob 2026-05-13: \"lost the line wrapping on a draft I'm editing.\" Fix:\n // before handing the body to the editor, convert `\\n` inside any `<pre>`\n // block to `<br>` so the visual breaks survive whatever the editor's\n // import normaliser does. The `<pre>` element stays; we only mutate its\n // contents.\n bodyToRender = bodyToRender.replace(\n /<pre\\b([^>]*)>([\\s\\S]*?)<\\/pre>/gi,\n (_match, attrs, inner) =>\n `<pre${attrs}>${inner.replace(/\\r\\n|\\r|\\n/g, \"<br>\")}</pre>`,\n );\n\n const acct: any = init.accounts.find(a => a.id === init.accountId);\n const isReplyForward = init.mode === \"reply\" || init.mode === \"replyAll\"\n || init.mode === \"forward\";\n\n // Signature \u2014 applies to new mail AND replies/forwards (drafts already\n // have it baked into the saved body). New format `acct.sig` {text,html}\n // takes precedence; legacy `acct.signature` (raw HTML) is the fallback.\n // Earlier the new-format branch was gated on \"new mail only\", so anyone\n // using the new sig format got NO signature on a reply (Bob 2026-05-18).\n if (init.mode !== \"draft\" && !init.draftUid) {\n let sigHtml = \"\";\n if (acct?.sig?.text) {\n sigHtml = acct.sig.html\n ? acct.sig.text // trusted raw HTML\n : escapeHtml(acct.sig.text).replace(/\\n/g, \"<br>\");\n } else if (acct?.signature) {\n sigHtml = acct.signature;\n }\n if (sigHtml) {\n const sigBlock = `<br><br>-- <br>${sigHtml}`;\n bodyToRender = isReplyForward\n ? `<br>${sigBlock}<br>${bodyToRender}` // sig above the quoted block\n : `${bodyToRender}${sigBlock}`; // sig at the end for new mail\n }\n }\n if (bodyToRender) {\n editor.setHtml(bodyToRender);\n editor.setCursor(0);\n }\n\n // If resuming a draft, track its UID for deletion after send\n if (init.draftUid) {\n draftUid = init.draftUid;\n }\n // Capture reply/forward linkage. init.inReplyTo is the parent's Message-ID,\n // init.references is the full ancestry chain (parent's existing References\n // header plus the parent's own Message-ID). Both set by app.ts:openCompose\n // for mode === \"reply\" | \"replyAll\" | \"forward\".\n replyInReplyTo = init.inReplyTo || \"\";\n replyReferences = init.references || [];\n\n setComposeTitle(init.subject || \"\");\n\n // Focus first empty field: To \u2192 Subject \u2192 body\n if (!toInput.value) toInput.focus();\n else if (!subjectInput.value) subjectInput.focus();\n else editor.focus();\n\n // Record the post-init content so autosave / hasContent only react to\n // genuine user edits. Without this baseline, a reply opens with quoted\n // body + signature already populated and autosave fires within seconds\n // even if the user typed nothing \u2014 producing the \"draft droppings\"\n // that pile up in the Drafts folder.\n recordContentBaseline();\n}\n\n// Q68: dirty marker (\u2022) in the window title until the next successful save.\nlet composeDirty = false;\nfunction setComposeTitle(subject: string): void {\n const base = subject ? `${subject} - Compose` : \"Compose - mailx\";\n document.title = composeDirty ? `\u2022 ${base}` : base;\n}\nfunction markComposeDirty(): void {\n if (composeDirty) return;\n composeDirty = true;\n setComposeTitle(subjectInput?.value || \"\");\n}\nfunction markComposeClean(): void {\n if (!composeDirty) return;\n composeDirty = false;\n setComposeTitle(subjectInput?.value || \"\");\n}\n\n// \u2500\u2500 Compose state (declared before init so the async IIFE can reference them) \u2500\u2500\n\nconst DRAFT_INPUT_DEBOUNCE_MS = 800; // save ~0.8s after the last keystroke\nconst DRAFT_INTERVAL_MS = 2000; // safety-net interval save\nlet draftUid: number | null = null;\nlet draftId: string | null = null; // stable ID for dedup when APPENDUID unavailable\n// Reply / Reply-All / Forward \u2014 captured at init time, forwarded to the daemon\n// so the outgoing MIME gets `In-Reply-To` + `References` headers. Without these\n// the reply lands as a top-level message: no threading anywhere, no \u21A9 indicator\n// on the original (since the linkage signal is the header itself). Mirror of\n// draftUid/draftId at module scope so the btn-send handler can read them.\nlet replyInReplyTo: string = \"\";\nlet replyReferences: string[] = [];\nlet draftTimer: ReturnType<typeof setInterval> | null = null;\nlet draftDebounceTimer: ReturnType<typeof setTimeout> | null = null;\nlet lastDraftContent = \"\";\n\n// Snapshot of compose content right after init populates it (signature, quoted\n// body, pre-filled recipients). composeHasUserChanges() compares against this\n// so autosave and the Save/Discard prompt only react to genuine user edits.\nlet baselineSnapshot = \"\";\nfunction getContentSnapshot(): string {\n return JSON.stringify({\n body: editor?.getHtml() || \"\",\n to: toInput?.value || \"\",\n cc: ccInput?.value || \"\",\n bcc: bccInput?.value || \"\",\n subject: subjectInput?.value || \"\",\n });\n}\nfunction recordContentBaseline(): void {\n baselineSnapshot = getContentSnapshot();\n // Seed lastDraftContent so the first autosave-tick after init doesn't\n // fire just because the snapshot differs from the empty string.\n lastDraftContent = baselineSnapshot;\n}\nfunction composeHasUserChanges(): boolean {\n return getContentSnapshot() !== baselineSnapshot;\n}\nlet draftSaving = false; // prevent concurrent saves\nlet draftSaveFailed = false; // surfaced in the compose status tag\n\ninterface PendingAttachment {\n filename: string;\n mimeType: string;\n size: number;\n dataBase64: string;\n}\nconst attachments: PendingAttachment[] = [];\n\nfunction showDraftStatus(text: string, isError: boolean): void {\n const status = document.getElementById(\"compose-status\");\n if (!status) return;\n status.textContent = text;\n status.classList.toggle(\"compose-status-error\", isError);\n}\n\nasync function saveDraft(): Promise<void> {\n if (draftSaving) return; // previous save still in flight\n // Skip autosave when nothing has changed from the post-init baseline\n // (signature, quoted body, pre-filled To). Editing an existing draft\n // bypasses the check \u2014 we still need to round-trip user edits to the\n // server-side draft. Without this, replies/forwards saved a draft the\n // moment they opened, even with no user typing.\n if (!draftUid && !draftId && !composeHasUserChanges()) return;\n // Don't checkpoint a meaningless draft. A fresh compose with no subject,\n // no body, and no REAL recipient (a bare word like \"sherrik\" typed into\n // To: when the user thought focus was in the search box) is junk \u2014 it\n // littered Bob's mailbox with \"To: sherrik <>\" drafts 2026-05-20. The\n // `\\S+@\\S+` test is a cheap \"is there an actual address\" proxy. Editing\n // an existing draft (draftUid/draftId set) always saves \u2014 we must\n // round-trip the user's edits even if they cleared all the fields.\n if (!draftUid && !draftId) {\n const hasSubject = subjectInput.value.trim().length > 0;\n const hasBody = editor.getText().trim().length > 0;\n const hasRealRecipient = /\\S+@\\S+/.test(`${toInput.value} ${ccInput.value} ${bccInput.value}`);\n if (!hasSubject && !hasBody && !hasRealRecipient) return;\n }\n const content = getContentSnapshot();\n if (content === lastDraftContent) return; // no changes since last save\n // Expose to window for blur-handler.\n (window as any).__mailxSaveDraft = saveDraft;\n lastDraftContent = content;\n draftSaving = true;\n\n try {\n const data = await apiSaveDraft({\n accountId: getFromAccountId(),\n subject: subjectInput.value,\n bodyHtml: editor.getHtml(),\n bodyText: editor.getText(),\n to: toInput.value,\n cc: ccInput.value,\n previousDraftUid: draftUid,\n draftId: draftId,\n });\n if (data?.draftUid) draftUid = data.draftUid;\n if (data?.draftId) draftId = data.draftId;\n if (draftSaveFailed) { draftSaveFailed = false; showDraftStatus(\"Draft saved\", false); }\n else showDraftStatus(`Draft saved ${new Date().toLocaleTimeString()}`, false);\n markComposeClean();\n } catch (e: any) {\n // Surface the error \u2014 silent failures are how drafts get lost on IMAP hiccups.\n // The local editing/ checkpoint already exists server-side regardless.\n console.error(\"[draft] save failed:\", e);\n draftSaveFailed = true;\n showDraftStatus(`Draft save failed: ${e?.message || e}`, true);\n // Clear lastDraftContent so the next tick retries the same content\n lastDraftContent = \"\";\n }\n finally { draftSaving = false; }\n}\n\n/** Schedule a debounced save on user input \u2014 fires ~1.5s after the last\n * keystroke, then yields one animation frame before actually writing so\n * the browser can paint any keystroke in the mean time. S60 mitigation:\n * wa-sqlite writes are synchronous on Android; this keeps the typing\n * experience responsive by never running the write in the same task as\n * an input event. */\nfunction scheduleDraftSave(): void {\n markComposeDirty();\n if (draftDebounceTimer) clearTimeout(draftDebounceTimer);\n draftDebounceTimer = setTimeout(() => {\n draftDebounceTimer = null;\n // rAF yield \u2014 lets any pending keystroke render before we block on\n // the DB write. A no-op when the tab is hidden (rAF is throttled),\n // which is fine because the user isn't typing then either.\n requestAnimationFrame(() => { saveDraft(); });\n }, DRAFT_INPUT_DEBOUNCE_MS);\n}\n\n// \u2500\u2500 Initialize: local-first population.\n//\n// Reply / Reply-All / Forward callers pre-populate `init.accounts` with the\n// full account list (app.ts:openCompose). In that common case we do NOT need\n// to call getAccounts() \u2014 everything required to fill the compose form is\n// already in sessionStorage and reads synchronously. That turns \"click Reply\"\n// into an instant-open instead of \"wait for getAccounts IPC to respond,\n// which can take >120s when the service is busy syncing / hung on IMAP\".\n//\n// getAccounts is still called (non-blocking) to refresh the dropdown with\n// the freshest data \u2014 and it IS awaited only in the fallback path where\n// init doesn't have an account list (message-viewer's Edit Draft passes\n// init.accounts=[]).\n\n// Parent (app.ts:openCompose) opens this iframe in parallel with its\n// `getAccounts()` IPC. If sessionStorage isn't populated yet by the time\n// we get here, wait briefly for the parent's `compose-init-ready`\n// postMessage. Listener registered as early as possible so a fast parent\n// post that beats this IIFE still gets captured.\nlet _parentInitReady = !!sessionStorage.getItem(\"composeInit\");\nconst _parentInitListeners: Array<() => void> = [];\nwindow.addEventListener(\"message\", (e: MessageEvent) => {\n if (e.data?.type !== \"compose-init-ready\") return;\n _parentInitReady = true;\n for (const fn of _parentInitListeners.splice(0)) fn();\n});\nfunction waitForParentInit(maxMs: number): Promise<void> {\n if (_parentInitReady) return Promise.resolve();\n return new Promise<void>(resolve => {\n const timer = setTimeout(() => { _parentInitReady = true; resolve(); }, maxMs);\n _parentInitListeners.push(() => { clearTimeout(timer); resolve(); });\n });\n}\n\n(async () => {\n _ctick(\"init IIFE start\");\n if (!sessionStorage.getItem(\"composeInit\")) {\n _ctick(\"waiting for parent init\");\n await waitForParentInit(1500);\n _ctick(\"parent init received\");\n }\n const stored = sessionStorage.getItem(\"composeInit\");\n if (stored) {\n sessionStorage.removeItem(\"composeInit\");\n const init = JSON.parse(stored) as ComposeInit;\n _ctick(`init parsed (mode=${init.mode}, bodyHtml=${init.bodyHtml?.length || 0} bytes)`);\n if (init.accounts && init.accounts.length > 0) {\n // Happy path \u2014 init is complete. Apply immediately. Kick\n // getAccounts in the background to refresh the dropdown if the\n // user keeps compose open long enough for the result.\n applyInit(init);\n _ctick(\"applyInit done \u2014 compose visible\");\n getAccounts().then((fresh: any[]) => {\n if (Array.isArray(fresh) && fresh.length > 0) {\n init.accounts = fresh;\n // Re-populate the From dropdown only \u2014 don't clobber\n // anything the user may have already typed.\n try { populateFromOptions(fresh); } catch { /* */ }\n }\n }).catch(() => { /* non-fatal */ });\n } else {\n // Edit Draft / other callers that didn't pre-fill accounts.\n // Have to wait on getAccounts here \u2014 the From dropdown needs it.\n let fresh: any[] = [];\n try { fresh = await getAccounts(); } catch (e: any) { console.error(\"Failed to load accounts:\", e); }\n init.accounts = fresh;\n applyInit(init);\n }\n } else {\n let accounts: any[] = [];\n try { accounts = await getAccounts(); } catch (e: any) { console.error(\"Failed to load accounts:\", e); }\n populateFromOptions(accounts);\n toInput.focus();\n }\n\n // Wire debounced saves to input events \u2014 checkpoint ~1.5s after the last\n // keystroke instead of waiting up to 5s for the interval tick.\n toInput.addEventListener(\"input\", scheduleDraftSave);\n ccInput.addEventListener(\"input\", scheduleDraftSave);\n bccInput.addEventListener(\"input\", scheduleDraftSave);\n subjectInput.addEventListener(\"input\", scheduleDraftSave);\n editor.onContentChange(scheduleDraftSave);\n\n // Safety-net interval: even with no user input, catch any edge cases.\n draftTimer = setInterval(saveDraft, DRAFT_INTERVAL_MS);\n\n // 2026-05-13 safety net: poll editor state directly every 4s and force\n // a save when the body has changed since the last seen snapshot. Bypasses\n // the editor's change-event wiring (TinyMCE's `update` event silently\n // missed Bob's 2h 15m of typing today \u2014 log shows zero saveDraft IPC).\n // Compares the canonicalized editor HTML, not the JSON snapshot, so\n // TinyMCE renormalisation doesn't trip the check on every poll. Until\n // the root TinyMCE-event bug is fixed, this guarantees disk recovery.\n //\n // Backoff: when saveDraft fails (e.g., the IPC times out because the\n // daemon is hung on a slow IMAP fetch), we don't want to refire every\n // 4 s and re-paint the error banner. The polling loop reads\n // `draftSaveFailed` and skips a tick when the previous attempt failed,\n // doubling the silent window each time up to ~30 s. Resets on the\n // first success.\n let lastPolledHtml = editor.getHtml();\n let _pollBackoffSkips = 0;\n let _pollSkipsRemaining = 0;\n setInterval(() => {\n try {\n if (_pollSkipsRemaining > 0) { _pollSkipsRemaining--; return; }\n const current = editor.getHtml();\n if (current === lastPolledHtml) return;\n lastPolledHtml = current;\n markComposeDirty();\n lastDraftContent = \"\";\n const wasFailing = draftSaveFailed;\n saveDraft().then(() => {\n // Successful save resets the backoff window.\n if (!draftSaveFailed) _pollBackoffSkips = 0;\n }).catch(() => { /* saveDraft handles its own logging */ });\n if (wasFailing || draftSaveFailed) {\n // Last attempt is still in-flight or failed \u2014 wait longer\n // before the next force-flush. 4 s * (2,4,6,8) \u2192 ~32 s ceiling.\n _pollBackoffSkips = Math.min(_pollBackoffSkips + 1, 7);\n _pollSkipsRemaining = _pollBackoffSkips;\n }\n } catch { /* poll loop must never throw */ }\n }, 4000);\n\n // Flush the draft on window close so the last-typed content lands in\n // editing/ even if the interval tick hasn't fired yet. navigator.sendBeacon\n // is synchronous enough to survive unload; callNode IPC would be dropped.\n window.addEventListener(\"beforeunload\", () => {\n if (draftDebounceTimer) { clearTimeout(draftDebounceTimer); draftDebounceTimer = null; }\n // fire-and-forget \u2014 can't await during unload\n saveDraft();\n });\n})();\n\n// \u2500\u2500 Send \u2500\u2500\n\n// Q55: Ctrl+Enter (or Cmd+Enter on macOS) anywhere in compose triggers send.\ndocument.addEventListener(\"keydown\", (e: KeyboardEvent) => {\n if ((e.ctrlKey || e.metaKey) && e.key === \"Enter\") {\n e.preventDefault();\n document.getElementById(\"btn-send\")?.click();\n }\n});\n// Q59: autosave when the window loses focus (in addition to debounce + interval).\nwindow.addEventListener(\"blur\", () => {\n // Use the same saveDraft path as the 5s interval.\n try { (window as any).__mailxSaveDraft?.(); } catch { /* */ }\n});\n\ndocument.getElementById(\"btn-send\")?.addEventListener(\"click\", async () => {\n // Loud tracing through the whole send pipeline. Every step ships a\n // `[client] compose-send-*` event to the Node log so a \"vanished message\"\n // report can be traced end-to-end without devtools. If the log stops\n // at any step, that's where the pipeline broke.\n logClientEvent(\"compose-send-click\");\n // Group expansion \u2014 replace any group names (family, neighbors, etc.)\n // with their address lists from contacts.jsonc \u2192 groups before parsing\n // into the validated {name, address}[] shape the service expects.\n // Synchronous group expansion \u2014 reads in-memory + localStorage cache\n // ONLY. NEVER awaits cloud I/O. This used to be `await expandGroups`\n // which fired a GDrive read of contacts.jsonc and hung Send for 61 s\n // on a slow network (Bob 2026-05-09 00:16:57); the user clicked\n // Send again, producing a real duplicate message.\n const toExpanded = expandGroups(toInput.value);\n const ccExpanded = expandGroups(ccInput.value);\n const bccExpanded = expandGroups(bccInput.value);\n const body = {\n from: getFromAccountId(),\n fromAddress: getFromAddress(),\n to: parseAddrs(toExpanded),\n cc: parseAddrs(ccExpanded),\n bcc: parseAddrs(bccExpanded),\n subject: subjectInput.value,\n bodyHtml: editor.getHtml(),\n bodyText: editor.getText(),\n attachments: attachments.map(a => ({ filename: a.filename, mimeType: a.mimeType, dataBase64: a.dataBase64 })),\n // Threading headers \u2014 daemon writes In-Reply-To and References into\n // the outgoing MIME from these. Without them every reply lands as a\n // top-level message and the \u21A9 indicator never appears on the original\n // (linkage is by header, not by anything client-only).\n inReplyTo: replyInReplyTo,\n references: replyReferences,\n // Hand draft identifiers to the daemon so it owns post-send cleanup.\n // Previously the client fired deleteDraft() as a separate IPC after\n // send, fire-and-forget with a silent catch \u2014 when the IMAP path\n // hiccuped the draft survived (\"draft droppings\"). Daemon-side\n // cleanup queues a sync_action on failure for reliable retry.\n draftUid: draftUid ?? undefined,\n draftId: draftId ?? undefined,\n };\n logClientEvent(\"compose-send-body-built\", { from: body.from, toCount: body.to.length, subjectLen: (body.subject || \"\").length, bodyHtmlLen: (body.bodyHtml || \"\").length, atts: body.attachments.length });\n // Local validity (one missing-To check) \u2014 must run before close so the\n // user gets an inline error instead of silent loss. Anything else (real\n // address validation, MIME assembly, disk write) happens server-side.\n if (!body.to.length) {\n logClientEvent(\"compose-send-rejected-no-to\");\n alert(\"Please add at least one To recipient.\");\n return;\n }\n // Local-first send: validate fast in the client, then fire-and-forget\n // the IPC and close compose IMMEDIATELY. The body is already snapshotted\n // into `body` above; nothing the user types after this point can affect\n // what gets sent.\n //\n // Earlier \"wait for IPC ack before closing\" version produced a real,\n // user-reported bug: clicking Send \u2192 typing for a few hundred ms while\n // the IPC was in flight \u2192 IPC returns \u2192 compose closes mid-keystroke\n // and the user's last edits go nowhere. The fix is structural: send\n // is committed by the client validation + the snapshot, NOT by the\n // server's reply. The server-side write is synchronous-on-disk inside\n // service.send() (queueOutgoingLocal), and any failure surfaces as a\n // top-level banner via the parent's `mailx-send-error` postMessage.\n //\n // Client-side regex validation up front so a typo'd address bounces\n // back inline (compose stays open) instead of disappearing into a\n // toast after compose has closed. Same pattern Outlook/Thunderbird use.\n const emailRe = /^[^\\s<>@]+@[^\\s<>@]+\\.[^\\s<>@]+$/;\n const badAddress = (list: { address?: string }[] | undefined): string | null => {\n if (!list) return null;\n for (const a of list) {\n const addr = (a?.address || \"\").trim();\n if (!addr) continue; // empty fragments are dropped\n if (!emailRe.test(addr)) return addr;\n }\n return null;\n };\n const bad = badAddress(body.to) || badAddress(body.cc) || badAddress(body.bcc);\n if (bad) {\n logClientEvent(\"compose-send-rejected-bad-addr\", { addr: bad });\n const statusEl = document.getElementById(\"compose-status\");\n if (statusEl) statusEl.textContent = `Invalid address: \"${bad}\"`;\n else alert(`Invalid address: \"${bad}\"`);\n return;\n }\n console.log(`[compose] Send clicked: from=${body.from} to=${JSON.stringify(body.to)} subject=\"${body.subject}\" attachments=${body.attachments.length}`);\n\n // Stop autosave NOW \u2014 the body we're sending is the body we're sending,\n // and we don't want a stray autosave to write a stale \"still typing\"\n // copy back to the Drafts folder while SMTP is in flight.\n if (draftTimer) { clearInterval(draftTimer); draftTimer = null; }\n // Record From-address history before close. Only manual values worth\n // keeping \u2014 skip anything that exactly matches a known account.\n try {\n const raw = fromInput.value.trim();\n const known = knownAccounts.some(a => formatAccountFrom(a) === raw);\n if (raw && !known && /@.+\\./.test(raw)) recordFromHistory(raw);\n } catch { /* */ }\n // Draft cleanup is now part of the send IPC payload (body.draftUid /\n // body.draftId) \u2014 daemon handles deletion with retry semantics. The\n // earlier client-side deleteDraft() call here was fire-and-forget and\n // could silently fail, leaving stale drafts in the IMAP folder.\n\n const sendStart = Date.now();\n logClientEvent(\"compose-send-pre-ipc\");\n // Fire the parent-relay IPC and don't await it. Parent will postMessage\n // a `mailx-send-error` to the parent window on failure; the app handles\n // that with a top-level banner. On success the outbox-status pill picks\n // up the queued message via the daemon's outboxStatus event.\n try {\n const reqId = `send-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;\n // Result listener: log only. Compose is already closed by the time\n // any of these fire.\n const onMsg = (ev: MessageEvent) => {\n if (!ev.data || ev.data.type !== \"mailx-compose-send-result\" || ev.data.id !== reqId) return;\n window.removeEventListener(\"message\", onMsg);\n if (ev.data.ok) {\n logClientEvent(\"compose-send-ipc-resolved\", { ms: Date.now() - sendStart });\n } else {\n const msg = ev.data.error || \"unknown\";\n logClientEvent(\"compose-send-ipc-rejected\", { error: msg, ms: Date.now() - sendStart });\n // Bubble the error up so the parent can show a banner. The\n // dropped-into-outbox path (queueOutgoingLocal) catches most\n // failures locally, so this branch fires only when the\n // *queue write itself* failed \u2014 rare, but the user must see\n // it because the message would otherwise be lost silently.\n try { parent.postMessage({ type: \"mailx-send-error\", message: msg, accountId: body.from }, \"*\"); } catch { /* */ }\n }\n };\n window.addEventListener(\"message\", onMsg);\n // Safety: if parent never replies (msger pipe broken), prune the\n // listener after 120s so we don't leak it. No retry here \u2014 the\n // daemon's outbox worker is the retry path.\n setTimeout(() => window.removeEventListener(\"message\", onMsg), 120000);\n parent.postMessage({ type: \"mailx-compose-send\", id: reqId, body }, \"*\");\n logClientEvent(\"compose-send-ipc-invoked\", { via: \"parent-relay\", reqId });\n } catch (e: any) {\n // postMessage itself threw \u2014 bridge totally dead. This is the only\n // path where we keep compose open, since we couldn't even hand the\n // body off.\n const msg: string = e?.message || String(e);\n logClientEvent(\"compose-send-ipc-throw\", { error: msg });\n const sendBtn = document.getElementById(\"btn-send\") as HTMLButtonElement | null;\n if (sendBtn) { sendBtn.disabled = false; sendBtn.textContent = \"Send\"; }\n const statusEl = document.getElementById(\"compose-status\");\n if (statusEl) statusEl.textContent = `Bridge error: ${msg}`;\n return;\n }\n\n closeCompose();\n});\n\n// \u2500\u2500 Close handling \u2500\u2500\n\n/** True if the compose has user-driven changes worth prompting about. Compares\n * against the post-init baseline rather than \"any non-empty content\" \u2014 a\n * reply has quoted body + signature pre-populated, so the old \"any content\"\n * check produced unwanted Save/Discard prompts on closes with no user input. */\nfunction composeHasContent(): boolean {\n return composeHasUserChanges();\n}\n\n/** Ask Save/Discard/Cancel. Returns \"save\" | \"discard\" | \"cancel\".\n * Uses an in-page modal so all three choices are presented at once \u2014 the\n * native confirm() flow forced the user through two sequential dialogs and\n * hid Discard behind a Cancel click, which was confusing. */\nfunction promptSaveOrDiscard(): Promise<\"save\" | \"discard\" | \"cancel\"> {\n return new Promise(resolve => {\n const overlay = document.createElement(\"div\");\n overlay.className = \"compose-modal-overlay\";\n const box = document.createElement(\"div\");\n box.className = \"compose-modal\";\n const msg = document.createElement(\"div\");\n msg.className = \"compose-modal-msg\";\n msg.textContent = \"Save this message as a draft?\";\n const btnRow = document.createElement(\"div\");\n btnRow.className = \"compose-modal-buttons\";\n\n const mkBtn = (label: string, choice: \"save\" | \"discard\" | \"cancel\", primary: boolean): HTMLButtonElement => {\n const b = document.createElement(\"button\");\n b.type = \"button\";\n b.textContent = label;\n b.className = primary ? \"compose-modal-btn primary\" : \"compose-modal-btn\";\n b.addEventListener(\"click\", () => { cleanup(); resolve(choice); });\n return b;\n };\n\n const cleanup = (): void => {\n document.removeEventListener(\"keydown\", onKey);\n overlay.remove();\n };\n const onKey = (e: KeyboardEvent): void => {\n if (e.key === \"Escape\") { e.preventDefault(); cleanup(); resolve(\"cancel\"); }\n else if (e.key === \"Enter\") { e.preventDefault(); cleanup(); resolve(\"save\"); }\n };\n document.addEventListener(\"keydown\", onKey);\n\n btnRow.appendChild(mkBtn(\"Save draft\", \"save\", true));\n btnRow.appendChild(mkBtn(\"Discard\", \"discard\", false));\n btnRow.appendChild(mkBtn(\"Cancel\", \"cancel\", false));\n box.appendChild(msg);\n box.appendChild(btnRow);\n overlay.appendChild(box);\n document.body.appendChild(overlay);\n (btnRow.firstChild as HTMLButtonElement).focus();\n });\n}\n\n/** Handle any \"close the compose\" action (Discard button, Escape, X, window close). */\nasync function handleCloseRequest(): Promise<boolean> {\n if (!composeHasContent()) { closeCompose(); return true; }\n const choice = await promptSaveOrDiscard();\n if (choice === \"cancel\") return false;\n // Stop auto-save so it can't race with our explicit save/discard.\n if (draftDebounceTimer) { clearTimeout(draftDebounceTimer); draftDebounceTimer = null; }\n if (draftTimer) { clearInterval(draftTimer); draftTimer = null; }\n if (choice === \"save\") {\n try { await saveDraft(); } catch { /* already logged */ }\n } else {\n // Discard: delete the tracked draft so the orphan doesn't stick\n // around \u2014 but fire-and-forget. The IMAP draft delete is a server\n // round-trip; awaiting it held the window open for seconds (Bob:\n // \"I press X but it doesn't go away ... eventually it did\"). The\n // close is the user's action and must complete instantly.\n if (draftUid || draftId) {\n deleteDraft(getFromAccountId(), draftUid || 0, draftId || \"\").catch(() => { /* ignore */ });\n }\n }\n closeCompose();\n return true;\n}\n\ndocument.getElementById(\"btn-discard\")?.addEventListener(\"click\", async () => {\n // Explicit Discard \u2014 the user already declared intent. Don't re-prompt\n // them with Save/Discard/Cancel (handleCloseRequest is for the X / Esc\n // path, which is ambiguous and needs the prompt). Just delete the\n // tracked draft (if any) and close.\n // Skip the confirm dialog when there's nothing to lose. Same rule the\n // X/Esc path uses (`composeHasContent` covers body + to/cc/bcc/subject)\n // \u2014 empty compose just closes.\n if (composeHasContent() && !confirm(\"Discard this draft? Any unsaved content will be lost.\")) return;\n if (draftDebounceTimer) { clearTimeout(draftDebounceTimer); draftDebounceTimer = null; }\n if (draftTimer) { clearInterval(draftTimer); draftTimer = null; }\n // Fire-and-forget \u2014 the IMAP draft delete must not hold the window open.\n if (draftUid || draftId) {\n deleteDraft(getFromAccountId(), draftUid || 0, draftId || \"\").catch(() => { /* ignore */ });\n }\n closeCompose();\n});\n\n// \u2500\u2500 Cc / Bcc toggle \u2500\u2500\nconst ccRow = document.getElementById(\"compose-cc-row\") as HTMLElement;\nconst bccRow = document.getElementById(\"compose-bcc-row\") as HTMLElement;\nconst toggleCcBtn = document.getElementById(\"btn-toggle-cc\") as HTMLButtonElement;\nconst toggleBccBtn = document.getElementById(\"btn-toggle-bcc\") as HTMLButtonElement;\n\nfunction setCcVisible(visible: boolean): void {\n ccRow.hidden = !visible;\n toggleCcBtn.classList.toggle(\"active\", visible);\n // Visibility \u2260 existence. Hiding the row keeps the value intact so\n // toggling it back shows what was there. Send still picks up the\n // value from a hidden row \u2014 the field exists in the DOM, just hidden.\n if (visible) ccInput.focus();\n}\nfunction setBccVisible(visible: boolean): void {\n bccRow.hidden = !visible;\n toggleBccBtn.classList.toggle(\"active\", visible);\n if (visible) bccInput.focus();\n}\ntoggleCcBtn?.addEventListener(\"click\", () => setCcVisible(ccRow.hidden));\ntoggleBccBtn?.addEventListener(\"click\", () => setBccVisible(bccRow.hidden));\n// Q49 deferred: should be derived from the address book / sent-history DB,\n// not a parallel localStorage store. Pending: extend contacts schema or\n// query messages table on To-input change (debounced); auto-expand Cc/Bcc\n// when this recipient's history shows \u2265N past uses.\n\n// \u2500\u2500 Attachments \u2500\u2500\nconst fileInput = document.getElementById(\"compose-file\") as HTMLInputElement;\nconst attEl = document.getElementById(\"compose-attachments\") as HTMLElement;\n\nfunction renderAttachmentChips(): void {\n attEl.innerHTML = \"\";\n if (attachments.length === 0) { attEl.hidden = true; return; }\n attEl.hidden = false;\n for (let i = 0; i < attachments.length; i++) {\n const a = attachments[i];\n const chip = document.createElement(\"span\");\n chip.className = \"compose-att-chip\";\n chip.innerHTML = `\\uD83D\\uDCCE ${escapeHtml(a.filename)} (${formatSize(a.size)}) `;\n const rm = document.createElement(\"button\");\n rm.type = \"button\";\n rm.title = \"Remove attachment\";\n rm.textContent = \"\\u2715\";\n rm.addEventListener(\"click\", () => {\n attachments.splice(i, 1);\n renderAttachmentChips();\n });\n chip.appendChild(rm);\n attEl.appendChild(chip);\n }\n}\nfunction escapeHtml(s: string): string {\n return s.replace(/[&<>\"']/g, c => ({ \"&\": \"&amp;\", \"<\": \"&lt;\", \">\": \"&gt;\", '\"': \"&quot;\", \"'\": \"&#39;\" }[c]!));\n}\nfunction formatSize(n: number): string {\n if (n < 1024) return `${n} B`;\n if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`;\n return `${(n / (1024 * 1024)).toFixed(1)} MB`;\n}\n\n/** Set when the user clicks Attach \u2014 the native file picker eats the Esc\n * press, but on Windows WebView2 the keydown can still spill to the page\n * and trip the document-level Esc-closes-compose handler. While this flag\n * is set, that handler short-circuits. Cleared shortly after click. */\nlet attachJustClicked = 0;\ndocument.getElementById(\"btn-attach\")?.addEventListener(\"click\", () => {\n attachJustClicked = Date.now();\n fileInput?.click();\n});\n\n// \u2500\u2500 Edit in Word (external editor handoff) \u2500\u2500\n//\n// Click writes the current body to a temp file, opens it in Word (or the\n// platform fallback), and watches the file. When Word saves, the service\n// emits `wordEditUpdated` and we replace the editor's HTML with the new\n// content. The editId is per-compose-window \u2014 closeWordEdit cleans up the\n// temp file when the window closes or the message is sent.\nlet wordEditId: string | null = null;\n// Edit-in-Word is desktop-only \u2014 hide the button on Android where the file\n// system / external-editor path can't work. Detection: the MAUI WebView\n// exposes `mailxapi.platform === \"android\"` once the bridge is up.\n{\n const isAndroid = (window as any).mailxapi?.platform === \"android\"\n || (window.parent as any)?.mailxapi?.platform === \"android\";\n if (isAndroid) {\n const btn = document.getElementById(\"btn-edit-in-word\") as HTMLElement | null;\n if (btn) btn.hidden = true;\n }\n}\n\ndocument.getElementById(\"btn-edit-in-word\")?.addEventListener(\"click\", async () => {\n if (!wordEditId) wordEditId = `compose-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;\n showDraftStatus(\"Opening in Word\u2026\", false);\n try {\n const result = await openInWord(wordEditId, editor.getHtml());\n if (!result.ok || result.opener === \"none\") {\n showDraftStatus(\"Couldn't launch an editor. Install Word, LibreOffice, or set a default for .html.\", true);\n return;\n }\n const label =\n result.opener === \"word\" ? \"Word\" :\n result.opener === \"libreoffice\" ? \"LibreOffice\" :\n \"your default editor\";\n showDraftStatus(`Editing in ${label} \u2014 saves there will reload here.`, false);\n showExternalEditHint(label);\n } catch (e: any) {\n showDraftStatus(`Edit-in-Word failed: ${e?.message || e}`, true);\n }\n});\n\n// Editor help button \u2014 opens a reference modal with shortcuts and the\n// Quill / tiptap differences. Source content lives in app/docs/editor.md\n// (and is mirrored in editor-help.ts for runtime embed).\ndocument.getElementById(\"btn-compose-help\")?.addEventListener(\"click\", async () => {\n try {\n const { EDITOR_HELP_MD } = await import(\"./editor-help.js\");\n showEditorHelpModal(EDITOR_HELP_MD);\n } catch (e: any) {\n showDraftStatus(`Couldn't open help: ${e?.message || e}`, true);\n }\n});\n\n/** Render a markdown string in a centered modal. Click outside / Esc /\n * Close button dismiss. Lightweight markdown rendering \u2014 handles headings,\n * paragraphs, lists, tables, inline code, bold/italic. Intentionally\n * doesn't pull a full markdown library since this is one document. */\nfunction showEditorHelpModal(md: string): void {\n if (document.getElementById(\"mailx-editor-help-modal\")) return;\n const backdrop = document.createElement(\"div\");\n backdrop.id = \"mailx-editor-help-modal\";\n backdrop.style.cssText = \"position:fixed;inset:0;background:rgba(0,0,0,0.5);z-index:10001;display:flex;align-items:center;justify-content:center;padding:24px;\";\n const panel = document.createElement(\"div\");\n panel.style.cssText = \"background:var(--color-bg, #fff);color:var(--color-text, #000);border-radius:8px;max-width:780px;max-height:88vh;width:100%;display:flex;flex-direction:column;box-shadow:0 8px 32px rgba(0,0,0,0.4);\";\n const header = document.createElement(\"div\");\n header.style.cssText = \"display:flex;justify-content:space-between;align-items:center;padding:12px 18px;border-bottom:1px solid var(--color-border, #ddd);\";\n header.innerHTML = `<span style=\"font-weight:600;\">Editor help</span><button id=\"mailx-editor-help-close\" style=\"background:none;border:0;font-size:18px;cursor:pointer;color:var(--color-text);padding:2px 8px;\" aria-label=\"Close\">&times;</button>`;\n const body = document.createElement(\"div\");\n body.style.cssText = \"flex:1;overflow:auto;padding:18px 22px;font:14px/1.55 system-ui;\";\n body.innerHTML = renderMarkdownLite(md);\n panel.appendChild(header);\n panel.appendChild(body);\n backdrop.appendChild(panel);\n document.body.appendChild(backdrop);\n const close = (): void => {\n backdrop.remove();\n document.removeEventListener(\"keydown\", onKey, true);\n };\n const onKey = (e: KeyboardEvent): void => {\n if (e.key === \"Escape\") { e.stopPropagation(); e.preventDefault(); close(); }\n };\n document.addEventListener(\"keydown\", onKey, true);\n header.querySelector<HTMLButtonElement>(\"#mailx-editor-help-close\")?.addEventListener(\"click\", close);\n backdrop.addEventListener(\"mousedown\", (e) => { if (e.target === backdrop) close(); });\n}\n\n/** Compact markdown renderer. Handles headings, paragraphs, code blocks,\n * inline code, bold, italic, links, lists, and pipe-tables. Built for the\n * editor help doc shape \u2014 not a general-purpose markdown engine. */\nfunction renderMarkdownLite(md: string): string {\n const esc = (s: string) => s.replace(/[&<>\"']/g, c => ({ \"&\": \"&amp;\", \"<\": \"&lt;\", \">\": \"&gt;\", '\"': \"&quot;\", \"'\": \"&#39;\" })[c]!);\n const inline = (s: string) => esc(s)\n .replace(/`([^`]+)`/g, '<code style=\"background:var(--color-bg-surface,#f3f3f3);padding:1px 4px;border-radius:3px;\">$1</code>')\n .replace(/\\*\\*([^*]+)\\*\\*/g, \"<strong>$1</strong>\")\n .replace(/(?<![*\\w])\\*([^*\\n]+)\\*(?!\\w)/g, \"<em>$1</em>\")\n .replace(/\\[([^\\]]+)\\]\\(([^)]+)\\)/g, '<a href=\"$2\" target=\"_blank\" rel=\"noopener\">$1</a>');\n const lines = md.split(/\\r?\\n/);\n const out: string[] = [];\n let i = 0;\n while (i < lines.length) {\n const line = lines[i];\n const h = /^(#{1,6})\\s+(.*)$/.exec(line);\n if (h) { out.push(`<h${h[1].length} style=\"margin:18px 0 8px 0;\">${inline(h[2])}</h${h[1].length}>`); i++; continue; }\n if (/^\\s*$/.test(line)) { i++; continue; }\n // Pipe-table\n if (line.includes(\"|\") && /^\\s*\\|/.test(line)) {\n const rows: string[][] = [];\n while (i < lines.length && /^\\s*\\|/.test(lines[i])) {\n rows.push(lines[i].trim().split(\"|\").slice(1, -1).map(s => s.trim()));\n i++;\n }\n if (rows.length >= 2 && /^[\\s\\-:]+$/.test(rows[1].join(\"\"))) {\n const head = rows[0];\n const data = rows.slice(2);\n out.push(`<table style=\"border-collapse:collapse;margin:8px 0;\">`);\n out.push(`<thead><tr>${head.map(h => `<th style=\"border-bottom:2px solid var(--color-border,#ccc);padding:4px 10px;text-align:left;\">${inline(h)}</th>`).join(\"\")}</tr></thead>`);\n out.push(`<tbody>${data.map(r => `<tr>${r.map(c => `<td style=\"border-bottom:1px solid var(--color-border,#eee);padding:4px 10px;\">${inline(c)}</td>`).join(\"\")}</tr>`).join(\"\")}</tbody>`);\n out.push(`</table>`);\n continue;\n }\n }\n // Bullet / numbered list\n if (/^\\s*[-*]\\s+/.test(line)) {\n const items: string[] = [];\n while (i < lines.length && /^\\s*[-*]\\s+/.test(lines[i])) {\n items.push(lines[i].replace(/^\\s*[-*]\\s+/, \"\"));\n i++;\n }\n out.push(`<ul style=\"margin:6px 0 6px 22px;\">${items.map(it => `<li style=\"margin:3px 0;\">${inline(it)}</li>`).join(\"\")}</ul>`);\n continue;\n }\n // Default: paragraph\n out.push(`<p style=\"margin:6px 0;\">${inline(line)}</p>`);\n i++;\n }\n return out.join(\"\\n\");\n}\n\n/** Modal hint shown when Edit-in-Word launches. The user is about to lose\n * focus to Word and may not know what to do next, so spell it out: save in\n * Word \u2192 switch back here \u2192 click Send. Dismissed by Esc, the close button,\n * or any click outside the panel. */\nfunction showExternalEditHint(editorLabel: string): void {\n if (document.getElementById(\"mailx-extedit-hint\")) return; // don't stack\n const backdrop = document.createElement(\"div\");\n backdrop.id = \"mailx-extedit-hint\";\n backdrop.style.cssText = \"position:fixed;inset:0;background:rgba(0,0,0,0.45);z-index:10001;display:flex;align-items:center;justify-content:center;\";\n const panel = document.createElement(\"div\");\n panel.style.cssText = \"background:var(--color-bg, #fff);color:var(--color-text, #000);border:1px solid var(--color-border, #ccc);border-radius:8px;padding:18px 22px;max-width:480px;box-shadow:0 8px 32px rgba(0,0,0,0.4);font:14px/1.5 system-ui;\";\n panel.innerHTML = `\n <div style=\"font-weight:600;font-size:16px;margin-bottom:10px;\">Editing in ${escapeHtml(editorLabel)}</div>\n <ol style=\"margin:0 0 12px 18px;padding:0;\">\n <li>Edit your message in <b>${escapeHtml(editorLabel)}</b>.</li>\n <li>Save (<b>Ctrl+S</b>). Today, choose <b>\"Web Page, Filtered (.htm)\"</b> if Word asks for a format \u2014 keep the same filename.</li>\n <li>Switch back to this window (<b>Alt+Tab</b>). The body will reload here.</li>\n <li>Click <b>Send</b> in this window when ready.</li>\n </ol>\n <div style=\"text-align:right;margin-top:8px;\">\n <button type=\"button\" id=\"mailx-extedit-hint-ok\" style=\"padding:6px 16px;border:1px solid var(--color-border, #ccc);background:var(--color-bg-surface, #f6f6f6);border-radius:4px;cursor:pointer;font:inherit;\">Got it</button>\n </div>\n `;\n backdrop.appendChild(panel);\n document.body.appendChild(backdrop);\n const close = (): void => {\n backdrop.remove();\n document.removeEventListener(\"keydown\", onKey, true);\n };\n const onKey = (e: KeyboardEvent): void => {\n if (e.key === \"Escape\") { e.stopPropagation(); e.preventDefault(); close(); }\n };\n document.addEventListener(\"keydown\", onKey, true);\n panel.querySelector<HTMLButtonElement>(\"#mailx-extedit-hint-ok\")?.addEventListener(\"click\", close);\n // Click outside the panel (i.e. on the backdrop) dismisses.\n backdrop.addEventListener(\"mousedown\", (e) => { if (e.target === backdrop) close(); });\n}\n\n// Listen for external-editor saves. Only react to events for this compose's\n// editId \u2014 multiple compose windows can be open and should not stomp each\n// other's bodies.\nonEvent((ev: any) => {\n if (ev?.type !== \"wordEditUpdated\") return;\n if (!wordEditId || ev.editId !== wordEditId) return;\n try {\n editor.setHtml(ev.html || \"\");\n showDraftStatus(\"Reloaded edits from external editor.\", false);\n scheduleDraftSave();\n } catch (e: any) {\n showDraftStatus(`Reload failed: ${e?.message || e}`, true);\n }\n});\nwindow.addEventListener(\"beforeunload\", () => {\n if (wordEditId) closeWordEdit(wordEditId).catch(() => { /* */ });\n});\n\nasync function ingestFiles(files: FileList | File[]): Promise<void> {\n for (const file of Array.from(files)) {\n const buf = await file.arrayBuffer();\n // base64 the whole thing \u2014 mailx-service builds the multipart/mixed\n let binary = \"\";\n const bytes = new Uint8Array(buf);\n for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]);\n const dataBase64 = btoa(binary);\n attachments.push({\n filename: file.name,\n mimeType: file.type || \"application/octet-stream\",\n size: file.size,\n dataBase64,\n });\n }\n renderAttachmentChips();\n scheduleDraftSave();\n}\n\nfileInput?.addEventListener(\"change\", async () => {\n if (!fileInput.files) return;\n await ingestFiles(fileInput.files);\n fileInput.value = \"\";\n});\n\n// Drag-and-drop: dropping files anywhere on the compose window attaches them.\n// Highlights a subtle overlay while dragging so the target is obvious. The\n// editor iframe swallows drag events internally so we attach to the compose\n// document root; Quill's own paste/drop handling doesn't fight us because\n// files-with-no-HTML-or-text dragover never hits Quill's clipboard module.\n(() => {\n let dragDepth = 0;\n const root = document.body;\n const overlay = document.createElement(\"div\");\n overlay.id = \"compose-drop-overlay\";\n // Toggle `display` directly \u2014 can't use the `hidden` attribute here\n // because the inline `display` property in cssText outranks it, which is\n // why the overlay showed permanently when I used `overlay.hidden = true`\n // (user-reported 2026-04-24 with screenshot \u2014 blue tint + dashed border\n // were visible before any drag started).\n const baseStyle = \"position:fixed;inset:0;background:oklch(0.6 0.18 250 / 0.15);border:3px dashed oklch(0.55 0.2 250);z-index:9999;pointer-events:none;align-items:center;justify-content:center;font-size:1.5rem;font-weight:500;color:oklch(0.35 0.2 250)\";\n overlay.style.cssText = baseStyle + \";display:none\";\n overlay.textContent = \"Drop files to attach\";\n root.appendChild(overlay);\n const show = () => { overlay.style.display = \"flex\"; };\n const hide = () => { overlay.style.display = \"none\"; };\n\n const hasFiles = (e: DragEvent) =>\n Array.from(e.dataTransfer?.types || []).includes(\"Files\");\n\n root.addEventListener(\"dragenter\", (e) => {\n if (!hasFiles(e)) return;\n dragDepth++;\n show();\n });\n root.addEventListener(\"dragleave\", (e) => {\n if (!hasFiles(e)) return;\n dragDepth = Math.max(0, dragDepth - 1);\n if (dragDepth === 0) hide();\n });\n root.addEventListener(\"dragover\", (e) => {\n if (!hasFiles(e)) return;\n e.preventDefault(); // required so drop fires\n if (e.dataTransfer) e.dataTransfer.dropEffect = \"copy\";\n });\n root.addEventListener(\"drop\", async (e) => {\n if (!hasFiles(e)) return;\n e.preventDefault();\n dragDepth = 0;\n hide();\n const files = e.dataTransfer?.files;\n if (files && files.length > 0) await ingestFiles(files);\n });\n})();\n\n// \u2500\u2500 Save and close (X button from parent) \u2500\u2500\nwindow.addEventListener(\"compose-save-and-close\", () => {\n handleCloseRequest();\n});\n\n// \u2500\u2500 Discard (trash icon from parent title bar) \u2500\u2500\n// Routes through the existing btn-discard handler so the confirm dialog and\n// draft-deletion logic stay in one place.\nwindow.addEventListener(\"compose-discard\", () => {\n document.getElementById(\"btn-discard\")?.click();\n});\n\n// \u2500\u2500 Keyboard shortcuts \u2500\u2500\n\ndocument.addEventListener(\"keydown\", (e) => {\n if (e.ctrlKey && e.key === \"Enter\") {\n document.getElementById(\"btn-send\")?.click();\n }\n if (e.key === \"Escape\") {\n // If the user just clicked Attach, the native file picker is up.\n // The picker swallows the Esc that dismissed it, but the keydown can\n // still bubble here on WebView2 \u2014 closing the whole compose. Suppress\n // for a short window after the attach click.\n if (Date.now() - attachJustClicked < 1500) return;\n // A modal/dialog open over compose (the link editor, TinyMCE's\n // dialogs, the add-to-preferred modal, \u2026) owns Escape \u2014 dismissing\n // the dialog must NOT also bubble here and offer to close the whole\n // compose window. `e.target.closest` still resolves even when the\n // dialog removed itself in an earlier event phase: the detached\n // subtree stays intact, so `.closest()` still walks it.\n const t = e.target as Element | null;\n if (t && typeof t.closest === \"function\"\n && t.closest('.mailx-modal-backdrop, .tox-dialog, [role=\"dialog\"]')) {\n return;\n }\n e.preventDefault();\n handleCloseRequest();\n }\n // Ctrl+K in an address field = trigger address completion.\n // NOTE: Ctrl+K is ALSO the editor's \"insert link\" shortcut. Scope this handler\n // strictly to the to/cc/bcc inputs so it doesn't shadow the editor binding when\n // focus is in the body.\n if (e.ctrlKey && (e.key === \"k\" || e.key === \"K\")) {\n const active = document.activeElement as HTMLElement;\n const addressFields: HTMLElement[] = [toInput, ccInput, bccInput];\n if (addressFields.includes(active)) {\n e.preventDefault();\n (active as HTMLInputElement).dispatchEvent(new Event(\"input\"));\n }\n }\n});\n", "/**\n * Simple context menu component.\n * Shows a menu at a given position with clickable items.\n */\n\nlet activeMenu: HTMLElement | null = null;\nlet dismissListener: ((e: Event) => void) | null = null;\nlet escapeListener: ((e: KeyboardEvent) => void) | null = null;\n\nexport interface MenuItem {\n label: string;\n action: () => void;\n disabled?: boolean;\n separator?: boolean;\n /** Native browser tooltip shown on hover (title attribute). Use it\n * to explain non-obvious side effects of an action \u2014 e.g., \"skips\n * Trash, no undo\" so the user can tell two near-identical entries\n * apart without trial-and-error. */\n tooltip?: string;\n}\n\n/** Close any open context menu and remove dismiss listeners */\nexport function closeContextMenu(): void {\n if (activeMenu) {\n activeMenu.remove();\n activeMenu = null;\n }\n if (dismissListener) {\n document.removeEventListener(\"pointerdown\", dismissListener, true);\n dismissListener = null;\n }\n if (escapeListener) {\n document.removeEventListener(\"keydown\", escapeListener, true);\n escapeListener = null;\n }\n}\n\n/** Show a context menu at the given position */\nexport function showContextMenu(x: number, y: number, items: MenuItem[]): void {\n closeContextMenu();\n\n const menu = document.createElement(\"div\");\n menu.className = \"ctx-menu\";\n\n for (const item of items) {\n if (item.separator) {\n const sep = document.createElement(\"div\");\n sep.className = \"ctx-sep\";\n menu.appendChild(sep);\n continue;\n }\n const el = document.createElement(\"div\");\n el.className = \"ctx-item\" + (item.disabled ? \" ctx-disabled\" : \"\");\n el.textContent = item.label;\n if (item.tooltip) el.title = item.tooltip;\n if (!item.disabled) {\n el.addEventListener(\"click\", () => {\n closeContextMenu();\n item.action();\n });\n }\n menu.appendChild(el);\n }\n\n menu.style.left = `${x}px`;\n menu.style.top = `${y}px`;\n document.body.appendChild(menu);\n\n // Adjust if menu goes off-screen\n const rect = menu.getBoundingClientRect();\n if (rect.right > window.innerWidth) menu.style.left = `${x - rect.width}px`;\n if (rect.bottom > window.innerHeight) menu.style.top = `${y - rect.height}px`;\n\n activeMenu = menu;\n\n // Dismiss on click/tap outside the menu. Uses pointerdown in capture phase\n // so it fires before any child handler and catches both left- and right-clicks.\n // Deferred by one frame so the opening pointerdown doesn't immediately close it.\n requestAnimationFrame(() => {\n dismissListener = (e: Event) => {\n if (activeMenu && !activeMenu.contains(e.target as Node)) {\n closeContextMenu();\n }\n };\n document.addEventListener(\"pointerdown\", dismissListener, true);\n\n escapeListener = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") {\n e.preventDefault();\n e.stopPropagation();\n closeContextMenu();\n }\n };\n document.addEventListener(\"keydown\", escapeListener, true);\n });\n}\n\n// Scroll anywhere closes the menu (capture phase so nested scrollers trigger it)\ndocument.addEventListener(\"scroll\", closeContextMenu, true);\n// A new right-click that opens a different menu goes through showContextMenu\u2192closeContextMenu\ndocument.addEventListener(\"contextmenu\", () => { /* handled by showContextMenu */ });\n// Iframe pointerdown forwarded from preview/compose iframes \u2014 needed because\n// iframe events don't bubble to the parent document, so the dismissListener\n// (which hooks document.pointerdown) doesn't see clicks INSIDE iframes. The\n// message-viewer's inline iframe script posts {type:\"iframePointerDown\"} on\n// every non-right-click pointerdown.\nwindow.addEventListener(\"message\", (e: MessageEvent) => {\n if (e.data && (e.data as any).type === \"iframePointerDown\") closeContextMenu();\n});\n"],
5
+ "mappings": "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",
6
6
  "names": ["draftUid", "draftId", "container", "editor", "line", "require_add", "require_dictionary", "NSpell", "NSpell", "editor", "parent", "abs", "walker", "n", "dismiss", "editor", "container", "container", "aiTransform", "dismiss", "createTinyMceEditor", "initGhostText", "readJsoncFile", "hasCcHistoryTo", "hasBccHistoryTo", "saveDraft", "EDITOR_HELP_MD", "h"]
7
7
  }