@bobfrankston/rmfmail 1.1.243 → 1.1.245
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.
- package/client/app.bundle.js +14 -9
- package/client/app.bundle.js.map +2 -2
- package/client/app.js +30 -17
- package/client/app.js.map +1 -1
- package/client/app.ts +31 -15
- package/client/compose/compose.bundle.js +145 -518
- package/client/compose/compose.bundle.js.map +3 -3
- package/client/compose/spellcheck.js +209 -758
- package/client/compose/spellcheck.js.map +1 -1
- package/client/compose/spellcheck.ts +209 -707
- package/package.json +5 -5
- package/packages/mailx-imap/index.d.ts.map +1 -1
- package/packages/mailx-imap/index.js +15 -0
- package/packages/mailx-imap/index.js.map +1 -1
- package/packages/mailx-imap/index.ts +12 -0
- package/packages/mailx-imap/package-lock.json +2 -2
- package/packages/mailx-imap/package.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../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", "../lib/api-client.ts", "spellcheck-core.ts", "../lib/rmf-tiny.js", "spellcheck.ts", "ghost-text.ts", "editor-help.ts", "editor.ts", "compose.ts", "../components/context-menu.ts"],
|
|
4
|
-
"sourcesContent": ["/*!\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 * 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, flaggedOnly = false) {\n abortMessageListRequests();\n return ipc().getUnifiedInbox(page, pageSize, flaggedOnly);\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\n/** Sync one folder on demand (lazy-folder-sync: fired when the user opens a\n * folder so it's fresh without the app sweeping all folders on a tight timer). */\nexport function syncFolderNow(accountId: string, folderId: number) {\n return ipc().syncFolderNow?.(accountId, folderId);\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, folderId?: number) {\n return ipc().deleteMessage?.(accountId, uid, folderId);\n}\n\nexport function deleteMessages(accountId: string, uids: number[], folderIds?: number[]) {\n if (uids.length === 1) return deleteMessage(accountId, uids[0], folderIds?.[0]);\n return ipc().deleteMessages?.(accountId, uids, folderIds);\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. */\n/** Capture every console.log / console.warn / console.error / console.info\n * / console.debug call AND every uncaught error + unhandledrejection, and\n * route them through logClientEvent to the daemon log.\n *\n * Why: debugging \"the list is empty\" or \"preview but no summary\" required\n * asking the user to open devtools and read the console. That's slow and\n * often the user has already navigated away by the time they look. With\n * this hook everything the WebView console would show is also in\n * rmfmail-YYYY-MM-DD.log \u2014 searchable, persistent, no devtools required.\n *\n * Idempotent. Original console methods preserved on _origConsole so any\n * internal logger (including logClientEvent's own bridge fallback) can\n * still write to the real console without recursing through the hook. */\nexport function installConsoleCapture(): void {\n const g: any = globalThis as any;\n if (g.__mailxConsoleCaptureInstalled) return;\n g.__mailxConsoleCaptureInstalled = true;\n\n const orig = {\n log: console.log.bind(console),\n info: console.info.bind(console),\n warn: console.warn.bind(console),\n error: console.error.bind(console),\n debug: console.debug.bind(console),\n };\n g._origConsole = orig;\n\n const serialize = (v: any): any => {\n if (v == null) return v;\n const t = typeof v;\n if (t === \"string\" || t === \"number\" || t === \"boolean\") return v;\n if (v instanceof Error) return { __err: true, message: v.message, stack: v.stack };\n try {\n // Cap large objects so a circular ref or a giant DOM dump\n // doesn't OOM the IPC channel.\n const s = JSON.stringify(v);\n return s.length > 4096 ? s.slice(0, 4096) + \"\u2026[truncated]\" : JSON.parse(s);\n } catch {\n try { return String(v); } catch { return \"[unserializable]\"; }\n }\n };\n\n const forward = (level: \"warn\" | \"error\", args: any[]): void => {\n try {\n // First arg often a format string; rest are values. Pass as array.\n logClientEvent(`console.${level}`, { args: args.map(serialize) });\n } catch { /* never throw from log capture */ }\n };\n\n // CRITICAL: only forward warn + error to the daemon log. The first cut\n // of this (1.1.178) forwarded console.log/info/debug too \u2014 but mailx logs\n // console.log on a hot path (per-message preview timing, sync ticks),\n // turning EVERY one into an IPC round-trip. That ~100x'd IPC traffic,\n // saturated the channel, and the getUnifiedInbox RESPONSE (sent by the\n // daemon in ~180ms) couldn't get processed client-side \u2014 the list hung on\n // \"Loading\u2026\" forever (Bob 2026-05-28, \"gave up\"). warn/error are rare and\n // high-value; log/info/debug stay console-only. Explicit logClientEvent()\n // calls elsewhere are unaffected.\n console.log = (...args: any[]) => { orig.log(...args); };\n console.info = (...args: any[]) => { orig.info(...args); };\n console.debug = (...args: any[]) => { orig.debug(...args); };\n console.warn = (...args: any[]) => { forward(\"warn\", args); orig.warn(...args); };\n console.error = (...args: any[]) => { forward(\"error\", args); orig.error(...args); };\n\n // Uncaught errors \u2014 what stops renderMessages mid-execution and leaves\n // a blank list. Without this we'd see nothing in the daemon log.\n try {\n window.addEventListener(\"error\", (e: ErrorEvent) => {\n try {\n logClientEvent(\"window.error\", {\n message: e.message,\n filename: e.filename,\n lineno: e.lineno,\n colno: e.colno,\n stack: e.error?.stack || null,\n });\n } catch { /* */ }\n });\n window.addEventListener(\"unhandledrejection\", (e: PromiseRejectionEvent) => {\n try {\n const r: any = e.reason;\n const msg = r?.message || String(r);\n // Break the cascade: a `logClientEvent` IPC timeout is itself a\n // rejection; forwarding it spawns ANOTHER logClientEvent which\n // can also time out \u2192 a self-amplifying flood (Bob 2026-05-28\n // saw dozens/sec). Never forward tracing-channel rejections.\n if (/logClientEvent|mailxapi timeout/i.test(msg)) return;\n logClientEvent(\"window.unhandledrejection\", {\n message: msg,\n stack: r?.stack || null,\n });\n } catch { /* */ }\n });\n } catch { /* */ }\n}\n\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 returns a callNode promise that REJECTS on\n // IPC timeout. Swallow it \u2014 an unhandled rejection here cascades\n // (the rejection gets logged \u2192 spawns another logClientEvent \u2192\n // \u2026). Fire-and-forget with a no-op catch.\n const p: any = bridge.logClientEvent(tag, data);\n if (p && typeof p.catch === \"function\") p.catch(() => { /* tracing is best-effort */ });\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, filename?: string): Promise<{ ok: boolean; path: string } | undefined> {\n const fn = (ipc() as any).openAttachment;\n return fn ? fn(accountId, uid, attachmentId, folderId, filename) : 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 * Editor-agnostic spell-check core.\n *\n * The pieces that are NOT tied to a specific editor:\n * - Dictionary load (nspell + hunspell en.aff/en.dic + user-dict from cloud).\n * - addToUserDict (write-through cache + cloud round-trip).\n * - showSuggestionsMenu (renders a floating menu in any document).\n * - getWordAtPoint (finds the word under a click in a contenteditable).\n * - buildSuggestionList (transposition-first list + nspell.suggest, capped).\n *\n * The pieces that ARE editor-specific (separate per editor):\n * - DOM walking for live decoration.\n * - replaceWord \u2014 needs the editor's selection model so undo works.\n * - Serializer filter that strips marker spans (depends on the editor's\n * output API).\n *\n * spellcheck.ts (TinyMCE) and spellcheck-quill.ts (Quill) both consume\n * this module. Adding a new editor means writing a thin adapter for the\n * editor-specific bits and reusing everything else here.\n */\n\n// @ts-expect-error \u2014 nspell ships no type defs.\nimport NSpell from \"nspell\";\nimport { getUserDict, addUserDictWord, addUserDictWords } from \"../lib/api-client.js\";\n\nexport type NSpellInstance = any;\n\nexport const USER_DICT_KEY = \"mailx-user-dict\";\nexport const MARKER_ATTR = \"data-mailx-spellerror\";\nexport const MIN_WORD_LEN = 3;\nexport const SKIP_TAGS = new Set([\"BLOCKQUOTE\", \"CODE\", \"PRE\", \"A\", \"SCRIPT\", \"STYLE\", \"KBD\", \"SAMP\", \"VAR\"]);\n\nlet spellPromise: Promise<NSpellInstance> | null = null;\n\n/** Resolve the shared nspell instance. Loads the en.aff/en.dic Hunspell\n * files once, layers in the user's dictionary from localStorage + GDrive,\n * and reconciles any local-only additions back up to the cloud. */\nexport async function getSpell(): Promise<NSpellInstance> {\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 as any)({ aff, dic });\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 */ }\n getUserDict().then(cloud => {\n const cloudArr = Array.isArray(cloud) ? cloud : [];\n for (const w of cloudArr) sp.add(w);\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 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 try {\n const merged = [...new Set([...local, ...cloudArr])];\n localStorage.setItem(USER_DICT_KEY, JSON.stringify(merged));\n } catch { /* */ }\n }).catch(() => { /* offline */ });\n return sp;\n })();\n return spellPromise;\n}\n\n/** Persist a word to the user dictionary. Synchronous local-cache write +\n * in-memory nspell.add for immediate effect, plus a fire-and-forget cloud\n * round-trip so the userdict.csv on GDrive picks it up too. */\nexport function addToUserDict(word: string, sp: NSpellInstance): void {\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 addUserDictWord(word).catch(e => console.error(\"[spell] addUserDictWord:\", e));\n}\n\n/** Build a suggestion list: transpositions first (the single most common\n * English typo class \u2014 \"hte\"\u2192\"the\"), then nspell.suggest, deduped, capped at 7. */\nexport function buildSuggestionList(word: string, sp: NSpellInstance): string[] {\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 return sugs;\n}\n\nexport type MenuItem = {\n label: string;\n action: () => void;\n emphasized?: boolean;\n separator?: boolean;\n};\n\n/** Render a floating suggestions menu at (x,y) in the given document.\n * Dismisses on Escape, on mousedown outside the menu, or after the user\n * picks an item. Listeners attached to every document the user could\n * plausibly click into so the menu can't get stuck. */\nexport function showSuggestionsMenu(\n parentDoc: Document,\n x: number,\n y: number,\n items: MenuItem[],\n extraDismissDocs: Document[] = [],\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 const docs: Document[] = [parentDoc, ...extraDismissDocs];\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/** Find the word at the given client (x,y) inside `root`. Returns null if\n * there's no word there (whitespace, link, code, etc). Works in any\n * contenteditable in the same document \u2014 used by the Quill adapter for\n * the right-click-on-misspelling path where there's no marker span to\n * hang off of (Quill doesn't decorate).\n *\n * Uses caretPositionFromPoint where available (Firefox), falls back to\n * caretRangeFromPoint (Chromium/WebView2). Both give us the text node +\n * offset under the click; we then expand to word boundaries. */\nexport function getWordAtPoint(\n root: HTMLElement,\n x: number,\n y: number,\n): { word: string; node: Text; start: number; end: number } | null {\n const doc = root.ownerDocument || document;\n let node: Node | null = null;\n let offset = 0;\n const winAny = doc.defaultView as any;\n if (typeof (doc as any).caretPositionFromPoint === \"function\") {\n const pos = (doc as any).caretPositionFromPoint(x, y);\n if (pos) { node = pos.offsetNode; offset = pos.offset; }\n } else if (typeof (doc as any).caretRangeFromPoint === \"function\") {\n const range = (doc as any).caretRangeFromPoint(x, y);\n if (range) { node = range.startContainer; offset = range.startOffset; }\n }\n if (!node || node.nodeType !== Node.TEXT_NODE) return null;\n // Make sure the text node is inside `root` \u2014 caretPositionFromPoint\n // can land outside if the click is in a sibling.\n let walk: Node | null = node;\n while (walk && walk !== root) walk = walk.parentNode;\n if (!walk) return null;\n // Reject if the click is inside a skipped tag (link, code, blockquote).\n let p: Node | null = node.parentNode;\n while (p && p !== root) {\n if (p.nodeType === Node.ELEMENT_NODE && SKIP_TAGS.has((p as Element).tagName)) return null;\n p = p.parentNode;\n }\n const text = (node as Text).data;\n if (offset > text.length) offset = text.length;\n // Word boundary: letters, apostrophes, hyphens. Same regex as the\n // walker in spellcheck.ts so both editors flag the same tokens.\n const isWordChar = (c: string) => /[\\p{L}'\u2019\\-]/u.test(c);\n let start = offset;\n while (start > 0 && isWordChar(text[start - 1])) start--;\n let end = offset;\n while (end < text.length && isWordChar(text[end])) end++;\n if (end - start < MIN_WORD_LEN) return null;\n const word = text.slice(start, end);\n if (!/^[\\p{L}]/u.test(word)) return null; // must start with a letter\n return { word, node: node as Text, start, end };\n}\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/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 // Code-block languages \u2014 shared by the stock codesample dialog\n // (codesample_languages) AND our custom \"Insert code\" dialog below, so a\n // language added here shows up in both. \"Text\" first = default (no\n // highlighting, which would mangle plain pasted snippets \u2014 Bob 2026-05-24).\n // PowerShell added 2026-05-31 (Bob: \".ps1 is another file type\").\n const CODE_LANGS = [\n { text: \"Text\", value: \"text\" },\n { text: \"HTML/XML\", value: \"markup\" },\n { text: \"JavaScript\", value: \"javascript\" },\n { text: \"TypeScript\", value: \"typescript\" },\n { text: \"CSS\", value: \"css\" },\n { text: \"JSON\", value: \"json\" },\n { text: \"Python\", value: \"python\" },\n { text: \"Java\", value: \"java\" },\n { text: \"C\", value: \"c\" },\n { text: \"C++\", value: \"cpp\" },\n { text: \"C#\", value: \"csharp\" },\n { text: \"Go\", value: \"go\" },\n { text: \"Rust\", value: \"rust\" },\n { text: \"Ruby\", value: \"ruby\" },\n { text: \"PHP\", value: \"php\" },\n { text: \"Shell\", value: \"bash\" },\n { text: \"PowerShell\", value: \"powershell\" },\n { text: \"SQL\", value: \"sql\" },\n ];\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 // NOTE: no \"paste\" plugin \u2014 it was REMOVED in TinyMCE 6 (paste,\n // including Word-paste fidelity, is now built into core). We're on\n // v8; listing \"paste\" makes TinyMCE's loader fetch the nonexistent\n // plugins/paste/plugin.min.js over msger's custom protocol, which\n // returns an error body the WebView tries to eval \u2192 \"Uncaught\n // SyntaxError: Unexpected identifier 'Not'\" \u2192 init fails \u2192 compose\n // falls back to a stub editor and Reply/Reply-All break (Bob\n // 2026-06-01). The paste_* OPTIONS below remain valid (core).\n plugins: \"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 rmfcode | 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 // Code-sample dropdown languages. TinyMCE's default list omits\n // \"Text\" / plain \u2014 every option triggers syntax highlighting\n // which mangles unrelated paste content (Bob 2026-05-24).\n // Adding Text first so it's the default; rest are the modern\n // languages we actually paste.\n codesample_languages: CODE_LANGS,\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 // Keep URLs verbatim. TinyMCE defaults to convert_urls:true +\n // relative_urls:true, which rewrites an absolute href\n // (https://github.com/\u2026) to be RELATIVE to the editor's base URL\n // (the msger custom-protocol page). A link that worked in the\n // original message arrives in the reply quote rewritten/dead\n // (Bob 2026-05-31: \"URL works in the original but not in the\n // reply\"). false on both = hrefs travel into the sent message\n // exactly as the sender wrote them.\n convert_urls: false,\n relative_urls: false,\n remove_script_host: false,\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 // Dark-blue native caret bar. caret-shape:block produced a\n // column wider than the gap between letters AND caused the\n // caret to \"scoot back to its old position\" after an arrow\n // press (Chromium block-caret quirk with the arrow-key\n // selection adjustment, Bob 2026-05-24). Plain bar is\n // browser-default and behaves correctly with arrow keys;\n // just darken the color so it stays visible.\n \"body { font-family: system-ui, sans-serif; font-size: 14px; caret-color: #0a2647; }\",\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 // Persisted in localStorage so re-opening compose lands at the\n // size the user picked. Per-host scope (one key, not per-doc)\n // matches how every other rich-text editor's zoom works.\n const ZOOM_DEFAULT = 14;\n const ZOOM_STEP = 2;\n const ZOOM_MIN = 8;\n const ZOOM_MAX = 32;\n const ZOOM_STORAGE_KEY = \"rmf-tiny:zoom-px\";\n let zoomPx = ZOOM_DEFAULT;\n try {\n const stored = Number(localStorage.getItem(ZOOM_STORAGE_KEY));\n if (Number.isFinite(stored) && stored >= ZOOM_MIN && stored <= ZOOM_MAX) {\n zoomPx = stored;\n }\n }\n catch { /* localStorage unavailable \u2014 use default */ }\n const saveZoom = () => {\n try {\n localStorage.setItem(ZOOM_STORAGE_KEY, String(zoomPx));\n }\n catch { /* quota / disabled \u2014 non-fatal */ }\n };\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 saveZoom();\n };\n // Custom \"Insert code\" \u2014 wraps the stock codesample plugin\n // (keeps its Prism highlighting + edit-in-place) but adds a\n // \"Box it in\" border checkbox so a snippet is visually\n // demarcated from surrounding prose. The border is applied as\n // an INLINE style on the <pre>, NOT a content_style class, so\n // it survives into the sent email where the recipient has none\n // of our editor CSS. Bob 2026-05-31.\n const BORDER_STYLES = { \"border\": \"1px solid #888\", \"border-radius\": \"4px\", \"padding\": \"10px\" };\n const openCodeDialog = () => {\n const preEl = ed.dom.getParent(ed.selection.getNode(), \"pre\");\n let curLang = \"text\";\n let curBorder = false;\n let curCode = \"\";\n if (preEl) {\n const m = (preEl.className || \"\").match(/language-([\\w-]+)/);\n if (m)\n curLang = m[1];\n curBorder = !!(preEl.style && preEl.style.border && preEl.style.border !== \"none\");\n // Read existing code straight off the DOM \u2014 don't depend on\n // the codesample plugin's internal getCurrentCode (its API\n // shape isn't stable across TinyMCE versions).\n curCode = preEl.textContent || \"\";\n }\n ed.windowManager.open({\n title: \"Insert code\",\n size: \"large\",\n body: {\n type: \"panel\",\n items: [\n { type: \"listbox\", name: \"language\", label: \"Language\", items: CODE_LANGS },\n { type: \"textarea\", name: \"code\", label: \"Code\" },\n { type: \"checkbox\", name: \"border\", label: \"Box it in (border around the code)\" },\n ],\n },\n initialData: { language: curLang, code: curCode, border: curBorder },\n buttons: [\n { type: \"cancel\", text: \"Cancel\" },\n { type: \"submit\", text: \"Save\", primary: true },\n ],\n onSubmit: (api) => {\n const data = api.getData();\n const lang = data.language || \"text\";\n const esc = (s) => String(s)\n .replace(/&/g, \"&\").replace(/</g, \"<\").replace(/>/g, \">\");\n const borderStyle = data.border\n ? ` style=\"${Object.entries(BORDER_STYLES).map(([k, v]) => `${k}:${v}`).join(\";\")}\"`\n : \"\";\n // Build the block and insert it directly. The previous\n // path called the codesample plugin's internal\n // `insertCodeSample`, which silently inserted NOTHING on\n // the current TinyMCE (Bob 2026-06-06 \"nothing got\n // inserted\"). Direct insertContent / setOuterHTML is\n // version-independent and can't silently no-op.\n const html = `<pre class=\"language-${lang}\"${borderStyle}><code>${esc(data.code || \"\")}</code></pre>`;\n if (preEl && preEl.parentNode) {\n ed.dom.setOuterHTML(preEl, html); // editing an existing block\n }\n else {\n ed.insertContent(html); // fresh insert\n }\n ed.undoManager.add(); // snapshot + fire AddUndo \u2192 draft save\n api.close();\n },\n });\n };\n ed.ui.registry.addButton(\"rmfcode\", {\n icon: \"code-sample\",\n tooltip: \"Insert/edit code block\",\n onAction: openCodeDialog,\n });\n ed.ui.registry.addMenuItem(\"rmfcode\", {\n icon: \"code-sample\",\n text: \"Code block\u2026\",\n onAction: openCodeDialog,\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(); saveZoom(); },\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\n // any text insertion is about to happen 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\"; Bob\n // 2026-05-25 reconfirmed). Covers autolink, pasted URLs,\n // hand-made links, IME composition, and clipboard paste.\n //\n // Uses `beforeinput` (modern, fires for ALL text insertion\n // kinds \u2014 keypress is deprecated and silently skips IME /\n // paste / autocomplete on Chromium). Done as a raw DOM\n // listener so it runs BEFORE TinyMCE's own beforeinput\n // handling, which is what reaches into the <a> and extends\n // it. Re-bound on every `init` (the iframe doc is recreated\n // on setContent, fullscreen, etc.).\n const installLinkEscape = () => {\n const doc = ed.getDoc();\n if (!doc || doc.__rmfLinkEscapeInstalled)\n return;\n doc.__rmfLinkEscapeInstalled = true;\n doc.addEventListener(\"beforeinput\", (e) => {\n // Only intercept text-insertion kinds. Deletes,\n // formatting, history nav don't extend links.\n const kind = e.inputType || \"\";\n const isInsert = kind === \"insertText\"\n || kind === \"insertCompositionText\"\n || kind === \"insertFromPaste\"\n || kind === \"insertFromDrop\"\n || kind === \"insertFromComposition\";\n if (!isInsert)\n return;\n const sel = doc.getSelection();\n if (!sel || sel.rangeCount === 0)\n return;\n const rng = sel.getRangeAt(0);\n if (!rng.collapsed)\n return;\n const node = rng.startContainer;\n const a = (node.nodeType === Node.ELEMENT_NODE ? node : node.parentNode);\n if (!a)\n return;\n const link = a.closest?.(\"a\");\n if (!link)\n return;\n // Range from caret to end-of-link: empty \u21D2 caret is\n // at the link's trailing edge. Anything else means\n // mid-link edit, which we want to leave alone.\n let tail;\n try {\n tail = rng.cloneRange();\n tail.setEndAfter(link);\n }\n catch {\n return;\n }\n if (tail.toString().length > 0)\n return;\n // Move the caret to just after the link. Use TinyMCE's\n // own selection API rather than a raw\n // removeAllRanges/addRange: the raw mutation ran ahead\n // of TinyMCE's beforeinput handling and desynced its\n // UndoManager, leaving Ctrl+Z erratic (Bob 2026-05-31:\n // \"^z is very broken in TinyMCE\"). Going through\n // ed.selection keeps TinyMCE's selection + undo state\n // consistent. Raw range as a fallback if it throws.\n const parent = link.parentNode;\n const idx = parent ? Array.prototype.indexOf.call(parent.childNodes, link) + 1 : -1;\n if (parent && idx >= 0) {\n try {\n ed.selection.setCursorLocation(parent, idx);\n }\n catch {\n const after = doc.createRange();\n after.setStartAfter(link);\n after.collapse(true);\n sel.removeAllRanges();\n sel.addRange(after);\n }\n }\n }, true);\n };\n ed.on(\"init\", installLinkEscape);\n ed.on(\"SetContent\", installLinkEscape);\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 // Apply the restored zoom (if any). Default = 14px which\n // matches the content_style baseline, so no-op when the\n // user hasn't changed zoom; otherwise the editor opens at\n // the size they last set.\n if (zoomPx !== ZOOM_DEFAULT)\n applyZoom();\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 /** Subscribe to content changes. compose.ts calls this to drive draft\n * auto-save; it was MISSING from the facade, so the call threw\n * \"editor.onContentChange is not a function\" during compose init \u2014\n * aborting the rest of init and leaving change-tracking unwired, so\n * edits made via dialogs (Insert code, Source code) were never\n * captured into the draft and looked \"ignored\" (Bob 2026-06-06).\n * `SetContent`/`ExecCommand` are the load-bearing events here: they're\n * what the codesample + code (source) dialogs fire on apply. */\n onContentChange(handler) {\n editor.on(\"input change undo redo keyup SetContent ExecCommand AddUndo\", () => handler());\n },\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 * 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 // Active non-collapsed selection? Bail. The decorate pass saves only\n // focusNode/focusOffset and restores as a collapsed range, so running\n // it over a live selection erases the selection (Bob 2026-05-26: \"I\n // select a sentence and then move to B for bold and the sentence gets\n // unselected\"). Skipping the whole pass is safe \u2014 the next input /\n // nodechange / keyup event reschedules decorate, and the misspellings\n // get marked the moment the user is no longer holding a range.\n const _activeSel = doc.getSelection();\n if (_activeSel && _activeSel.rangeCount > 0 && !_activeSel.isCollapsed) 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 // Save the focusNode REFERENCE too. The abs-offset alone loses the\n // user's element when they sit in an empty `<p>` (typical reply\n // scaffold: empty <p> on top + `<div class=\"reply\">` below). With\n // only abs=0, restore walks to the first TEXT node \u2014 which lives\n // INSIDE the quoted reply block \u2014 and caret yanks INTO the quote\n // every 1.2 s (Bob 2026-05-25: \"I move the cursor to the top, wait,\n // it goes to the beginning of the reply\"). The decorate mutations\n // only wrap/unwrap misspelling spans inside text \u2014 they don't touch\n // the user's `<p>` containers \u2014 so the focusNode reference survives\n // the pass unchanged. Restore by reference when we can; abs-offset\n // is the fallback when the node was inside a re-wrapped span.\n let savedFocusNode: Node | null = null;\n let savedFocusOffset = 0;\n const _preSel = doc.getSelection();\n if (_preSel && _preSel.rangeCount > 0) {\n savedFocusNode = _preSel.focusNode;\n savedFocusOffset = _preSel.focusOffset;\n }\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 // Prefer the node-reference path when it still attaches to the body.\n // Falls back to abs-offset only when the saved node was inside a\n // span that got unwrapped/rewrapped during the pass.\n let restored = false;\n if (savedFocusNode && body.contains(savedFocusNode)) {\n try {\n const range = doc.createRange();\n const maxOffset = savedFocusNode.nodeType === Node.TEXT_NODE\n ? (savedFocusNode as Text).data.length\n : savedFocusNode.childNodes.length;\n range.setStart(savedFocusNode, Math.min(savedFocusOffset, maxOffset));\n range.collapse(true);\n const sel = doc.getSelection();\n if (sel) { sel.removeAllRanges(); sel.addRange(range); restored = true; }\n } catch { /* fall through to abs-offset path */ }\n }\n if (!restored && 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.\n *\n * Uses TinyMCE's own selection + insertContent API, which focuses the editor\n * iframe, replaces the selection, and registers on the undo stack.\n *\n * The previous implementation called `doc.execCommand(\"insertText\")` directly\n * on the iframe document. That silently failed: the suggestions popup lives in\n * the TOP document (showSuggestionsMenu(document, \u2026)), so clicking a suggestion\n * moves focus OUT of the editor iframe \u2014 and Chromium/WebView2 `execCommand` on\n * an UNFOCUSED contenteditable returns `true` while doing nothing. Because it\n * returned truthy, the raw-DOM fallback never ran either, so the correction was\n * simply dropped (Bob 2026-06-11: \"spelling corrections not getting applied\").\n * editor.focus() + editor.selection.select() makes the replacement land. */\nfunction replaceMarker(editor: any, marker: HTMLElement, replacement: string): void {\n try {\n editor.focus();\n editor.selection.select(marker);\n editor.insertContent(editor.dom.encode(replacement));\n } catch {\n // Raw-DOM fallback if the editor API is unavailable for any reason.\n const doc: Document = editor.getDoc();\n const range = doc.createRange();\n range.selectNode(marker);\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 // Same selection-active guard as decorate(). Unwrap reshapes text-node\n // parentage, which can collapse a live range to a point in WebView2;\n // skip if the user is holding a selection.\n const activeSel = doc.getSelection();\n if (activeSel && activeSel.rangeCount > 0 && !activeSel.isCollapsed) 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 // \u2500\u2500 Spell-check right-click handler (Quill) \u2500\u2500\n // Runs FIRST so a right-click on a misspelled word shows our suggestions\n // menu (Add to dictionary / Ignore / replace) instead of falling through\n // to the WebView2 native menu (which on msger-hosted compose iframes was\n // unreliable for \"Add to dictionary\" \u2014 Bob 2026-05-27 \"can't ignore nor\n // add an entry\"). Shares the nspell dictionary + user-dict-mirror with\n // the TinyMCE editor's spellcheck.ts via spellcheck-core.ts \u2014 same\n // dictionary, same persistence, same suggestion ranking.\n //\n // Decoration (red wavy underlines) is NOT wired here yet \u2014 Quill's\n // MutationObserver-based delta tracking interferes with arbitrary\n // span-wrapping. The visible-on-hover suggestion is left to native\n // (WebView2 still shows red squiggles via OS spell-check on\n // spellcheck=\"true\"); we just take over the right-click flow so the\n // user can ACT on a flagged word with persistence that actually works.\n let _spellSp: any = null;\n import(\"./spellcheck-core.js\").then(m => m.getSpell()).then(sp => { _spellSp = sp; }).catch(() => { /* dict unavailable */ });\n q.root.addEventListener(\"contextmenu\", async (e: MouseEvent) => {\n try {\n if (!_spellSp) return; // dict still loading\n const sel = q.getSelection();\n if (sel && sel.length > 0) return; // selection path handled below\n const core = await import(\"./spellcheck-core.js\");\n const hit = core.getWordAtPoint(q.root as HTMLElement, e.clientX, e.clientY);\n if (!hit) return;\n const { word, node, start, end } = hit;\n if (_spellSp.correct(word)) return; // word is fine \u2014 let native menu fire\n e.preventDefault();\n e.stopPropagation();\n const sugs = core.buildSuggestionList(word, _spellSp);\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 // Replace via Range so the browser's text-edit\n // handling kicks in and Quill sees it as a normal\n // edit (undoable, Delta-tracked). Going through\n // q.deleteText/insertText needs character offsets\n // from the Quill index space \u2014 possible but more\n // arithmetic; Range is simpler and works for the\n // text-node case the misspelling always lives in.\n try {\n const range = document.createRange();\n range.setStart(node, start);\n range.setEnd(node, end);\n const docSel = document.getSelection();\n if (docSel) {\n docSel.removeAllRanges();\n docSel.addRange(range);\n if (!document.execCommand(\"insertText\", false, s)) {\n range.deleteContents();\n range.insertNode(document.createTextNode(s));\n }\n }\n } catch { /* */ }\n },\n });\n }\n }\n items.push({ label: \"\", action: () => {}, separator: true });\n items.push({\n label: `Add \"${word}\" to dictionary`,\n action: () => core.addToUserDict(word, _spellSp),\n });\n items.push({\n label: \"Ignore (this session)\",\n action: () => _spellSp.add(word),\n });\n core.showSuggestionsMenu(document, e.clientX, e.clientY, items);\n } catch (err: any) {\n console.warn(\"[spellcheck] right-click handler error:\", err?.message || err);\n }\n });\n\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\">•</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\">“</button>\n <button class=\"tt-btn\" data-cmd=\"link\" title=\"Link\" tabindex=\"-1\">🔗</button>\n <button class=\"tt-btn\" data-cmd=\"clearFormat\" title=\"Clear formatting\" tabindex=\"-1\">⌧</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, installConsoleCapture } from \"../lib/api-client.js\";\nimport { showContextMenu, type MenuItem } from \"../components/context-menu.js\";\n\n// Capture compose-iframe console + window errors to the daemon log. Same\n// reason as app.ts \u2014 debugging \"draft save failed silently\" or \"iframe\n// hung mid-init\" doesn't require devtools.\ninstallConsoleCapture();\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 draftId?: string;\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}\n/** Parse a single recipient segment (\"Name <a@b.c>\" or a bare address) into\n * name/email. Returns null when no address is present. */\nfunction parseRecipientSegment(seg: string): { name: string; email: string } | null {\n const s = (seg || \"\").trim();\n if (!s) return null;\n const angled = s.match(/^\\s*\"?([^\"<]*?)\"?\\s*<([^>]+)>\\s*$/);\n if (angled && /@/.test(angled[2])) return { name: angled[1].trim(), email: angled[2].trim() };\n const bare = s.match(/[^\\s<>,\"]+@[^\\s<>,\"]+\\.[^\\s<>,\"]+/);\n if (bare) return { name: \"\", email: bare[0] };\n return null;\n}\n\n/** Which comma-separated address sits under the caret/right-click position in\n * a recipient textarea. Falls back to the last segment. */\nfunction recipientAtCaret(el: HTMLTextAreaElement): { name: string; email: string } | null {\n const v = el.value;\n const caret = el.selectionStart ?? v.length;\n let start = 0;\n for (const seg of v.split(\",\")) {\n const end = start + seg.length;\n if (caret >= start && caret <= end) return parseRecipientSegment(seg);\n start = end + 1; // account for the comma delimiter\n }\n return parseRecipientSegment(v.split(\",\").pop() || \"\");\n}\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 // Right-click on a recipient \u2192 contact actions for the address under the\n // caret, plus the clipboard essentials (this replaces the native WebView\n // menu, so Cut/Copy/Paste/Select-all are re-supplied). Google-contact\n // add/update/merge is punted to Google's own UI \u2014 same decision as the\n // autocomplete-row menu (no in-app People API write / dedup needed).\n el?.addEventListener(\"contextmenu\", (e) => {\n if (!el) return;\n e.preventDefault();\n const me = e as MouseEvent;\n const items: MenuItem[] = [];\n const addr = recipientAtCaret(el);\n if (addr?.email) {\n items.push({\n label: `Open in Google Contacts: ${addr.email}`,\n tooltip: \"Add as a new contact, attach to an existing one, or merge/update \u2014 in Google's own UI.\",\n action: () => window.open(`https://contacts.google.com/search/${encodeURIComponent(addr.name || addr.email)}`, \"_blank\"),\n });\n items.push({ label: \"Add to preferred\u2026\", action: () => openAddToPreferredModal({ name: addr.name, email: addr.email, source: \"\" }) });\n items.push({ label: `Copy address: ${addr.email}`, action: () => { void navigator.clipboard.writeText(addr.email); } });\n items.push({ label: \"\", action: () => {}, separator: true });\n }\n const selText = (): string => el.value.slice(el.selectionStart ?? 0, el.selectionEnd ?? 0);\n const replaceSelection = (text: string): void => {\n const s = el.selectionStart ?? el.value.length, en = el.selectionEnd ?? el.value.length;\n el.value = el.value.slice(0, s) + text + el.value.slice(en);\n el.selectionStart = el.selectionEnd = s + text.length;\n el.dispatchEvent(new Event(\"input\", { bubbles: true }));\n };\n items.push({ label: \"Cut\", action: async () => { const t = selText(); if (t) { try { await navigator.clipboard.writeText(t); } catch { /* */ } replaceSelection(\"\"); } } });\n items.push({ label: \"Copy\", action: async () => { const t = selText(); if (t) { try { await navigator.clipboard.writeText(t); } catch { /* */ } } } });\n items.push({ label: \"Paste\", action: async () => { try { replaceSelection(await navigator.clipboard.readText()); } catch { /* clipboard blocked */ } } });\n items.push({ label: \"Select all\", action: () => el.select() });\n showContextMenu(me.clientX, me.clientY, items);\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, contact?: { name: string; email: string }): void {\n const val = input.value;\n const caret = input.selectionStart ?? val.length;\n const { start, end } = tokenSpanAtCaret(val, caret);\n let before = val.substring(0, start);\n let after = val.substring(end);\n // Strip a trailing \",\" / \", \" from `before` \u2014 `tokenSpanAtCaret`\n // returns `start` immediately AFTER the prior separator-comma, so\n // `before` always ends with that comma. We re-add a clean \", \"\n // separator below; without stripping here, \", bob\" \u2192 click suggestion\n // produced \"bob, \"Frankston, Bob\" <addr>\" (the original `bob` token\n // was actually preserved by an off-by-one in some edge cases where\n // the dropdown opened on a stale `bob` while the input had advanced\n // past it). Bob 2026-05-24.\n before = before.replace(/[ \\t,]+$/, \"\");\n\n // Drop \"stranded partials\" from before/after: prior unresolved tokens\n // (no `@`) that look like earlier attempts to type THIS contact.\n // Pattern: user types \"hod\" \u2192 suggestion appears \u2192 user keeps typing\n // \", p\" \u2192 new suggestion for \"p\" \u2192 clicks Peter Hoddie. Without this\n // cleanup the input ends up as \"hod, Peter Hoddie <peter@\u2026>, \" \u2014\n // \"hod\" gets stranded and fails validation at send time. The\n // chosen contact's name+email becomes the haystack; any prior bare\n // token that's a substring is treated as the same intent and dropped\n // (Bob 2026-05-24).\n if (contact) {\n const haystack = `${contact.name} ${contact.email}`.toLowerCase();\n const stripStranded = (s: string): string => splitRecipients(s)\n .map(p => p.trim())\n .filter(p => p.length > 0 && (p.includes(\"@\") || !haystack.includes(p.toLowerCase())))\n .join(\", \");\n if (before) before = stripStranded(before);\n const afterCore = after.replace(/^[\\s,]+/, \"\").replace(/[\\s,]+$/, \"\");\n if (afterCore) {\n const cleanedAfter = stripStranded(afterCore);\n after = cleanedAfter ? \", \" + cleanedAfter : \"\";\n }\n }\n\n const lead = before.length ? \", \" : \"\";\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, { name: r.name, email: r.email });\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 // Skip any leading whitespace inside the token span \u2014 without this, a\n // selection of `\"Frankston, Bob\" <bob@x.com>` against an input like\n // `bob` whose token span was `{0, 3}` would be fine, but if the user\n // had partially-completed a prior recipient (e.g. value `prev, bob`\n // with caret at 9 in span {6, 9}) the leading space belonged to the\n // SEPARATOR, not the token. Replacing the span as-is then leaves the\n // space in `before` AND prepends another `, ` ahead of the insert \u2014\n // producing duplicated separators on commit. Trimming leading WS keeps\n // `before` clean and lets the replacement own its own separators.\n while (start < end && (s[start] === \" \" || s[start] === \"\\t\")) start++;\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 AND its\n // stable X-Mailx-Draft-ID so saveDraft can keep replacing the same\n // draft instead of appending a new one every save. Without the ID\n // round-trip the daemon falls back to UID-based dedup only \u2014 fine\n // when APPENDUID returns synchronously, but when it doesn't (Dovecot\n // slow, IMAP push deferred) each save lands a fresh draft alongside\n // the old one.\n if (init.draftUid) {\n draftUid = init.draftUid;\n }\n if (init.draftId) {\n draftId = init.draftId;\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 _postedInit: ComposeInit | null = null;\nlet _parentInitReady = !!sessionStorage.getItem(\"composeInit\");\nconst _parentInitListeners: Array<() => void> = [];\nlet _msgEventCount = 0;\nwindow.addEventListener(\"message\", (e: MessageEvent) => {\n _msgEventCount++;\n // Accept either the legacy ready-signal (paired with sessionStorage) or\n // the newer payload-carrying message \u2014 postMessage is the fallback for\n // when sessionStorage doesn't bridge to the iframe (WebView2 quirk on\n // the custom-protocol host).\n if (e.data?.type === \"compose-init-ready\") {\n _parentInitReady = true;\n for (const fn of _parentInitListeners.splice(0)) fn();\n } else if (e.data?.type === \"compose-init\" && e.data.init) {\n if (!_postedInit) { try { logClientEvent(\"compose-init-received\", { src: \"postMessage\" }); } catch { /* */ } }\n _postedInit = e.data.init as ComposeInit;\n _parentInitReady = true;\n for (const fn of _parentInitListeners.splice(0)) fn();\n }\n});\n\n// Last-resort handoff: pull init from the parent window's keyed stash.\n// sessionStorage AND postMessage have both failed in production\n// (Bob 2026-05-24: replyAll opened with empty fields; no init parsed).\n// Same-origin iframe \u2192 window.parent access is allowed by SOP and is\n// synchronous (no race). Keyed by a token the parent set on iframe.dataset.\nfunction pullInitFromParent(): ComposeInit | null {\n try {\n const myFrame = window.frameElement as HTMLIFrameElement | null;\n const key = myFrame?.dataset?.composeKey;\n if (!key) return null;\n const stash = (window.parent as any)?.__mailxComposeInits;\n const init = stash?.[key];\n if (init) {\n try { logClientEvent(\"compose-init-received\", { src: \"parent-stash\", key }); } catch { /* */ }\n return init as ComposeInit;\n }\n } catch (e: any) {\n try { logClientEvent(\"compose-init-pull-failed\", { err: e?.message || String(e) }); } catch { /* */ }\n }\n return null;\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 // Try parent-stash FIRST \u2014 it's synchronous and most reliable. Only\n // wait/poll if it's empty.\n let parentInit = pullInitFromParent();\n if (!parentInit && !sessionStorage.getItem(\"composeInit\") && !_postedInit) {\n _ctick(\"waiting for parent init\");\n await waitForParentInit(1500);\n _ctick(`parent init received (msgEvents=${_msgEventCount})`);\n // Re-poll the parent stash \u2014 it may have been populated during the wait.\n if (!_postedInit) parentInit = pullInitFromParent();\n }\n // Priority: parent-stash > postMessage > sessionStorage.\n const stored = sessionStorage.getItem(\"composeInit\");\n const initRaw: ComposeInit | null = parentInit\n || _postedInit\n || (stored ? (JSON.parse(stored) as ComposeInit) : null);\n if (initRaw) {\n sessionStorage.removeItem(\"composeInit\");\n const init = initRaw;\n const src = parentInit ? \"parent-stash\" : (_postedInit ? \"postMessage\" : \"sessionStorage\");\n _ctick(`init parsed (mode=${init.mode}, bodyHtml=${init.bodyHtml?.length || 0} bytes, src=${src})`);\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) {\n statusEl.textContent = `Invalid address: \"${bad}\" \u2014 Send blocked`;\n // Toggle the error class so the high-contrast white-on-red\n // styling kicks in; otherwise the message used the muted-grey\n // default color and was missable (Bob 2026-05-24).\n statusEl.classList.add(\"compose-status-error\");\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 => ({ \"&\": \"&\", \"<\": \"<\", \">\": \">\", '\"': \""\", \"'\": \"'\" }[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// Close handle for the \"Editing in Word\" instruction modal, so the\n// wordEditUpdated handler can dismiss it the moment edits reload (i.e. when\n// the user returns from Word). Null when no hint is showing.\nlet extEditHintClose: (() => void) | 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\">×</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 => ({ \"&\": \"&\", \"<\": \"<\", \">\": \">\", '\"': \""\", \"'\": \"'\" })[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 extEditHintClose = null;\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 // Deliberately NO click-outside-to-dismiss: this hint must survive the whole\n // round-trip to Word. The backdrop spans the compose window, so the first\n // click when the user Alt-Tabs back used to land on it and close the hint\n // before they'd read the steps (Bob 2026-06-11). It now stays up until the\n // user returns from Word (wordEditUpdated auto-closes it) or clicks \"Got it\"\n // / presses Escape.\n extEditHintClose = 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 // Returned from Word with a save \u2014 the instruction hint has done its\n // job; take it down so the reloaded body is visible.\n extEditHintClose?.();\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 // Ctrl+S = save draft now. Without this binding the only saves come\n // from the 800ms input debounce and the 2s safety-net interval, so\n // hitting Ctrl+S after typing felt laggy and the preview pane could\n // sit on stale text for seconds. Bypass the debounce timer and fire\n // saveDraft immediately. Also suppress the WebView2 default action\n // (\"save page as\u2026\") that otherwise opens a file dialog over compose.\n if (e.ctrlKey && (e.key === \"s\" || e.key === \"S\") && !e.shiftKey && !e.altKey) {\n e.preventDefault();\n if (draftDebounceTimer) {\n clearTimeout(draftDebounceTimer);\n draftDebounceTimer = null;\n }\n saveDraft().catch(() => { /* errors already surfaced in status bar */ });\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
|
-
"names": ["line", "require_add", "require_dictionary", "NSpell", "draftUid", "draftId", "NSpell", "dismiss", "container", "editor", "parent", "
|
|
4
|
+
"sourcesContent": ["/*!\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 * 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, flaggedOnly = false) {\n abortMessageListRequests();\n return ipc().getUnifiedInbox(page, pageSize, flaggedOnly);\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\n/** Sync one folder on demand (lazy-folder-sync: fired when the user opens a\n * folder so it's fresh without the app sweeping all folders on a tight timer). */\nexport function syncFolderNow(accountId: string, folderId: number) {\n return ipc().syncFolderNow?.(accountId, folderId);\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, folderId?: number) {\n return ipc().deleteMessage?.(accountId, uid, folderId);\n}\n\nexport function deleteMessages(accountId: string, uids: number[], folderIds?: number[]) {\n if (uids.length === 1) return deleteMessage(accountId, uids[0], folderIds?.[0]);\n return ipc().deleteMessages?.(accountId, uids, folderIds);\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. */\n/** Capture every console.log / console.warn / console.error / console.info\n * / console.debug call AND every uncaught error + unhandledrejection, and\n * route them through logClientEvent to the daemon log.\n *\n * Why: debugging \"the list is empty\" or \"preview but no summary\" required\n * asking the user to open devtools and read the console. That's slow and\n * often the user has already navigated away by the time they look. With\n * this hook everything the WebView console would show is also in\n * rmfmail-YYYY-MM-DD.log \u2014 searchable, persistent, no devtools required.\n *\n * Idempotent. Original console methods preserved on _origConsole so any\n * internal logger (including logClientEvent's own bridge fallback) can\n * still write to the real console without recursing through the hook. */\nexport function installConsoleCapture(): void {\n const g: any = globalThis as any;\n if (g.__mailxConsoleCaptureInstalled) return;\n g.__mailxConsoleCaptureInstalled = true;\n\n const orig = {\n log: console.log.bind(console),\n info: console.info.bind(console),\n warn: console.warn.bind(console),\n error: console.error.bind(console),\n debug: console.debug.bind(console),\n };\n g._origConsole = orig;\n\n const serialize = (v: any): any => {\n if (v == null) return v;\n const t = typeof v;\n if (t === \"string\" || t === \"number\" || t === \"boolean\") return v;\n if (v instanceof Error) return { __err: true, message: v.message, stack: v.stack };\n try {\n // Cap large objects so a circular ref or a giant DOM dump\n // doesn't OOM the IPC channel.\n const s = JSON.stringify(v);\n return s.length > 4096 ? s.slice(0, 4096) + \"\u2026[truncated]\" : JSON.parse(s);\n } catch {\n try { return String(v); } catch { return \"[unserializable]\"; }\n }\n };\n\n const forward = (level: \"warn\" | \"error\", args: any[]): void => {\n try {\n // First arg often a format string; rest are values. Pass as array.\n logClientEvent(`console.${level}`, { args: args.map(serialize) });\n } catch { /* never throw from log capture */ }\n };\n\n // CRITICAL: only forward warn + error to the daemon log. The first cut\n // of this (1.1.178) forwarded console.log/info/debug too \u2014 but mailx logs\n // console.log on a hot path (per-message preview timing, sync ticks),\n // turning EVERY one into an IPC round-trip. That ~100x'd IPC traffic,\n // saturated the channel, and the getUnifiedInbox RESPONSE (sent by the\n // daemon in ~180ms) couldn't get processed client-side \u2014 the list hung on\n // \"Loading\u2026\" forever (Bob 2026-05-28, \"gave up\"). warn/error are rare and\n // high-value; log/info/debug stay console-only. Explicit logClientEvent()\n // calls elsewhere are unaffected.\n console.log = (...args: any[]) => { orig.log(...args); };\n console.info = (...args: any[]) => { orig.info(...args); };\n console.debug = (...args: any[]) => { orig.debug(...args); };\n console.warn = (...args: any[]) => { forward(\"warn\", args); orig.warn(...args); };\n console.error = (...args: any[]) => { forward(\"error\", args); orig.error(...args); };\n\n // Uncaught errors \u2014 what stops renderMessages mid-execution and leaves\n // a blank list. Without this we'd see nothing in the daemon log.\n try {\n window.addEventListener(\"error\", (e: ErrorEvent) => {\n try {\n logClientEvent(\"window.error\", {\n message: e.message,\n filename: e.filename,\n lineno: e.lineno,\n colno: e.colno,\n stack: e.error?.stack || null,\n });\n } catch { /* */ }\n });\n window.addEventListener(\"unhandledrejection\", (e: PromiseRejectionEvent) => {\n try {\n const r: any = e.reason;\n const msg = r?.message || String(r);\n // Break the cascade: a `logClientEvent` IPC timeout is itself a\n // rejection; forwarding it spawns ANOTHER logClientEvent which\n // can also time out \u2192 a self-amplifying flood (Bob 2026-05-28\n // saw dozens/sec). Never forward tracing-channel rejections.\n if (/logClientEvent|mailxapi timeout/i.test(msg)) return;\n logClientEvent(\"window.unhandledrejection\", {\n message: msg,\n stack: r?.stack || null,\n });\n } catch { /* */ }\n });\n } catch { /* */ }\n}\n\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 returns a callNode promise that REJECTS on\n // IPC timeout. Swallow it \u2014 an unhandled rejection here cascades\n // (the rejection gets logged \u2192 spawns another logClientEvent \u2192\n // \u2026). Fire-and-forget with a no-op catch.\n const p: any = bridge.logClientEvent(tag, data);\n if (p && typeof p.catch === \"function\") p.catch(() => { /* tracing is best-effort */ });\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, filename?: string): Promise<{ ok: boolean; path: string } | undefined> {\n const fn = (ipc() as any).openAttachment;\n return fn ? fn(accountId, uid, attachmentId, folderId, filename) : 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 * Editor-agnostic spell-check core.\n *\n * The pieces that are NOT tied to a specific editor:\n * - Dictionary load (nspell + hunspell en.aff/en.dic + user-dict from cloud).\n * - addToUserDict (write-through cache + cloud round-trip).\n * - showSuggestionsMenu (renders a floating menu in any document).\n * - getWordAtPoint (finds the word under a click in a contenteditable).\n * - buildSuggestionList (transposition-first list + nspell.suggest, capped).\n *\n * The pieces that ARE editor-specific (separate per editor):\n * - DOM walking for live decoration.\n * - replaceWord \u2014 needs the editor's selection model so undo works.\n * - Serializer filter that strips marker spans (depends on the editor's\n * output API).\n *\n * spellcheck.ts (TinyMCE) and spellcheck-quill.ts (Quill) both consume\n * this module. Adding a new editor means writing a thin adapter for the\n * editor-specific bits and reusing everything else here.\n */\n\n// @ts-expect-error \u2014 nspell ships no type defs.\nimport NSpell from \"nspell\";\nimport { getUserDict, addUserDictWord, addUserDictWords } from \"../lib/api-client.js\";\n\nexport type NSpellInstance = any;\n\nexport const USER_DICT_KEY = \"mailx-user-dict\";\nexport const MARKER_ATTR = \"data-mailx-spellerror\";\nexport const MIN_WORD_LEN = 3;\nexport const SKIP_TAGS = new Set([\"BLOCKQUOTE\", \"CODE\", \"PRE\", \"A\", \"SCRIPT\", \"STYLE\", \"KBD\", \"SAMP\", \"VAR\"]);\n\nlet spellPromise: Promise<NSpellInstance> | null = null;\n\n/** Resolve the shared nspell instance. Loads the en.aff/en.dic Hunspell\n * files once, layers in the user's dictionary from localStorage + GDrive,\n * and reconciles any local-only additions back up to the cloud. */\nexport async function getSpell(): Promise<NSpellInstance> {\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 as any)({ aff, dic });\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 */ }\n getUserDict().then(cloud => {\n const cloudArr = Array.isArray(cloud) ? cloud : [];\n for (const w of cloudArr) sp.add(w);\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 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 try {\n const merged = [...new Set([...local, ...cloudArr])];\n localStorage.setItem(USER_DICT_KEY, JSON.stringify(merged));\n } catch { /* */ }\n }).catch(() => { /* offline */ });\n return sp;\n })();\n return spellPromise;\n}\n\n/** Persist a word to the user dictionary. Synchronous local-cache write +\n * in-memory nspell.add for immediate effect, plus a fire-and-forget cloud\n * round-trip so the userdict.csv on GDrive picks it up too. */\nexport function addToUserDict(word: string, sp: NSpellInstance): void {\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 addUserDictWord(word).catch(e => console.error(\"[spell] addUserDictWord:\", e));\n}\n\n/** Build a suggestion list: transpositions first (the single most common\n * English typo class \u2014 \"hte\"\u2192\"the\"), then nspell.suggest, deduped, capped at 7. */\nexport function buildSuggestionList(word: string, sp: NSpellInstance): string[] {\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 return sugs;\n}\n\nexport type MenuItem = {\n label: string;\n action: () => void;\n emphasized?: boolean;\n separator?: boolean;\n};\n\n/** Render a floating suggestions menu at (x,y) in the given document.\n * Dismisses on Escape, on mousedown outside the menu, or after the user\n * picks an item. Listeners attached to every document the user could\n * plausibly click into so the menu can't get stuck. */\nexport function showSuggestionsMenu(\n parentDoc: Document,\n x: number,\n y: number,\n items: MenuItem[],\n extraDismissDocs: Document[] = [],\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 const docs: Document[] = [parentDoc, ...extraDismissDocs];\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/** Find the word at the given client (x,y) inside `root`. Returns null if\n * there's no word there (whitespace, link, code, etc). Works in any\n * contenteditable in the same document \u2014 used by the Quill adapter for\n * the right-click-on-misspelling path where there's no marker span to\n * hang off of (Quill doesn't decorate).\n *\n * Uses caretPositionFromPoint where available (Firefox), falls back to\n * caretRangeFromPoint (Chromium/WebView2). Both give us the text node +\n * offset under the click; we then expand to word boundaries. */\nexport function getWordAtPoint(\n root: HTMLElement,\n x: number,\n y: number,\n): { word: string; node: Text; start: number; end: number } | null {\n const doc = root.ownerDocument || document;\n let node: Node | null = null;\n let offset = 0;\n const winAny = doc.defaultView as any;\n if (typeof (doc as any).caretPositionFromPoint === \"function\") {\n const pos = (doc as any).caretPositionFromPoint(x, y);\n if (pos) { node = pos.offsetNode; offset = pos.offset; }\n } else if (typeof (doc as any).caretRangeFromPoint === \"function\") {\n const range = (doc as any).caretRangeFromPoint(x, y);\n if (range) { node = range.startContainer; offset = range.startOffset; }\n }\n if (!node || node.nodeType !== Node.TEXT_NODE) return null;\n // Make sure the text node is inside `root` \u2014 caretPositionFromPoint\n // can land outside if the click is in a sibling.\n let walk: Node | null = node;\n while (walk && walk !== root) walk = walk.parentNode;\n if (!walk) return null;\n // Reject if the click is inside a skipped tag (link, code, blockquote).\n let p: Node | null = node.parentNode;\n while (p && p !== root) {\n if (p.nodeType === Node.ELEMENT_NODE && SKIP_TAGS.has((p as Element).tagName)) return null;\n p = p.parentNode;\n }\n const text = (node as Text).data;\n if (offset > text.length) offset = text.length;\n // Word boundary: letters, apostrophes, hyphens. Same regex as the\n // walker in spellcheck.ts so both editors flag the same tokens.\n const isWordChar = (c: string) => /[\\p{L}'\u2019\\-]/u.test(c);\n let start = offset;\n while (start > 0 && isWordChar(text[start - 1])) start--;\n let end = offset;\n while (end < text.length && isWordChar(text[end])) end++;\n if (end - start < MIN_WORD_LEN) return null;\n const word = text.slice(start, end);\n if (!/^[\\p{L}]/u.test(word)) return null; // must start with a letter\n return { word, node: node as Text, start, end };\n}\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/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 // Code-block languages \u2014 shared by the stock codesample dialog\n // (codesample_languages) AND our custom \"Insert code\" dialog below, so a\n // language added here shows up in both. \"Text\" first = default (no\n // highlighting, which would mangle plain pasted snippets \u2014 Bob 2026-05-24).\n // PowerShell added 2026-05-31 (Bob: \".ps1 is another file type\").\n const CODE_LANGS = [\n { text: \"Text\", value: \"text\" },\n { text: \"HTML/XML\", value: \"markup\" },\n { text: \"JavaScript\", value: \"javascript\" },\n { text: \"TypeScript\", value: \"typescript\" },\n { text: \"CSS\", value: \"css\" },\n { text: \"JSON\", value: \"json\" },\n { text: \"Python\", value: \"python\" },\n { text: \"Java\", value: \"java\" },\n { text: \"C\", value: \"c\" },\n { text: \"C++\", value: \"cpp\" },\n { text: \"C#\", value: \"csharp\" },\n { text: \"Go\", value: \"go\" },\n { text: \"Rust\", value: \"rust\" },\n { text: \"Ruby\", value: \"ruby\" },\n { text: \"PHP\", value: \"php\" },\n { text: \"Shell\", value: \"bash\" },\n { text: \"PowerShell\", value: \"powershell\" },\n { text: \"SQL\", value: \"sql\" },\n ];\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 // NOTE: no \"paste\" plugin \u2014 it was REMOVED in TinyMCE 6 (paste,\n // including Word-paste fidelity, is now built into core). We're on\n // v8; listing \"paste\" makes TinyMCE's loader fetch the nonexistent\n // plugins/paste/plugin.min.js over msger's custom protocol, which\n // returns an error body the WebView tries to eval \u2192 \"Uncaught\n // SyntaxError: Unexpected identifier 'Not'\" \u2192 init fails \u2192 compose\n // falls back to a stub editor and Reply/Reply-All break (Bob\n // 2026-06-01). The paste_* OPTIONS below remain valid (core).\n plugins: \"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 rmfcode | 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 // Code-sample dropdown languages. TinyMCE's default list omits\n // \"Text\" / plain \u2014 every option triggers syntax highlighting\n // which mangles unrelated paste content (Bob 2026-05-24).\n // Adding Text first so it's the default; rest are the modern\n // languages we actually paste.\n codesample_languages: CODE_LANGS,\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 // Keep URLs verbatim. TinyMCE defaults to convert_urls:true +\n // relative_urls:true, which rewrites an absolute href\n // (https://github.com/\u2026) to be RELATIVE to the editor's base URL\n // (the msger custom-protocol page). A link that worked in the\n // original message arrives in the reply quote rewritten/dead\n // (Bob 2026-05-31: \"URL works in the original but not in the\n // reply\"). false on both = hrefs travel into the sent message\n // exactly as the sender wrote them.\n convert_urls: false,\n relative_urls: false,\n remove_script_host: false,\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 // Dark-blue native caret bar. caret-shape:block produced a\n // column wider than the gap between letters AND caused the\n // caret to \"scoot back to its old position\" after an arrow\n // press (Chromium block-caret quirk with the arrow-key\n // selection adjustment, Bob 2026-05-24). Plain bar is\n // browser-default and behaves correctly with arrow keys;\n // just darken the color so it stays visible.\n \"body { font-family: system-ui, sans-serif; font-size: 14px; caret-color: #0a2647; }\",\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 // Persisted in localStorage so re-opening compose lands at the\n // size the user picked. Per-host scope (one key, not per-doc)\n // matches how every other rich-text editor's zoom works.\n const ZOOM_DEFAULT = 14;\n const ZOOM_STEP = 2;\n const ZOOM_MIN = 8;\n const ZOOM_MAX = 32;\n const ZOOM_STORAGE_KEY = \"rmf-tiny:zoom-px\";\n let zoomPx = ZOOM_DEFAULT;\n try {\n const stored = Number(localStorage.getItem(ZOOM_STORAGE_KEY));\n if (Number.isFinite(stored) && stored >= ZOOM_MIN && stored <= ZOOM_MAX) {\n zoomPx = stored;\n }\n }\n catch { /* localStorage unavailable \u2014 use default */ }\n const saveZoom = () => {\n try {\n localStorage.setItem(ZOOM_STORAGE_KEY, String(zoomPx));\n }\n catch { /* quota / disabled \u2014 non-fatal */ }\n };\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 saveZoom();\n };\n // Custom \"Insert code\" \u2014 wraps the stock codesample plugin\n // (keeps its Prism highlighting + edit-in-place) but adds a\n // \"Box it in\" border checkbox so a snippet is visually\n // demarcated from surrounding prose. The border is applied as\n // an INLINE style on the <pre>, NOT a content_style class, so\n // it survives into the sent email where the recipient has none\n // of our editor CSS. Bob 2026-05-31.\n const BORDER_STYLES = { \"border\": \"1px solid #888\", \"border-radius\": \"4px\", \"padding\": \"10px\" };\n const openCodeDialog = () => {\n const preEl = ed.dom.getParent(ed.selection.getNode(), \"pre\");\n let curLang = \"text\";\n let curBorder = false;\n let curCode = \"\";\n if (preEl) {\n const m = (preEl.className || \"\").match(/language-([\\w-]+)/);\n if (m)\n curLang = m[1];\n curBorder = !!(preEl.style && preEl.style.border && preEl.style.border !== \"none\");\n // Read existing code straight off the DOM \u2014 don't depend on\n // the codesample plugin's internal getCurrentCode (its API\n // shape isn't stable across TinyMCE versions).\n curCode = preEl.textContent || \"\";\n }\n ed.windowManager.open({\n title: \"Insert code\",\n size: \"large\",\n body: {\n type: \"panel\",\n items: [\n { type: \"listbox\", name: \"language\", label: \"Language\", items: CODE_LANGS },\n { type: \"textarea\", name: \"code\", label: \"Code\" },\n { type: \"checkbox\", name: \"border\", label: \"Box it in (border around the code)\" },\n ],\n },\n initialData: { language: curLang, code: curCode, border: curBorder },\n buttons: [\n { type: \"cancel\", text: \"Cancel\" },\n { type: \"submit\", text: \"Save\", primary: true },\n ],\n onSubmit: (api) => {\n const data = api.getData();\n const lang = data.language || \"text\";\n const esc = (s) => String(s)\n .replace(/&/g, \"&\").replace(/</g, \"<\").replace(/>/g, \">\");\n const borderStyle = data.border\n ? ` style=\"${Object.entries(BORDER_STYLES).map(([k, v]) => `${k}:${v}`).join(\";\")}\"`\n : \"\";\n // Build the block and insert it directly. The previous\n // path called the codesample plugin's internal\n // `insertCodeSample`, which silently inserted NOTHING on\n // the current TinyMCE (Bob 2026-06-06 \"nothing got\n // inserted\"). Direct insertContent / setOuterHTML is\n // version-independent and can't silently no-op.\n const html = `<pre class=\"language-${lang}\"${borderStyle}><code>${esc(data.code || \"\")}</code></pre>`;\n if (preEl && preEl.parentNode) {\n ed.dom.setOuterHTML(preEl, html); // editing an existing block\n }\n else {\n ed.insertContent(html); // fresh insert\n }\n ed.undoManager.add(); // snapshot + fire AddUndo \u2192 draft save\n api.close();\n },\n });\n };\n ed.ui.registry.addButton(\"rmfcode\", {\n icon: \"code-sample\",\n tooltip: \"Insert/edit code block\",\n onAction: openCodeDialog,\n });\n ed.ui.registry.addMenuItem(\"rmfcode\", {\n icon: \"code-sample\",\n text: \"Code block\u2026\",\n onAction: openCodeDialog,\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(); saveZoom(); },\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\n // any text insertion is about to happen 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\"; Bob\n // 2026-05-25 reconfirmed). Covers autolink, pasted URLs,\n // hand-made links, IME composition, and clipboard paste.\n //\n // Uses `beforeinput` (modern, fires for ALL text insertion\n // kinds \u2014 keypress is deprecated and silently skips IME /\n // paste / autocomplete on Chromium). Done as a raw DOM\n // listener so it runs BEFORE TinyMCE's own beforeinput\n // handling, which is what reaches into the <a> and extends\n // it. Re-bound on every `init` (the iframe doc is recreated\n // on setContent, fullscreen, etc.).\n const installLinkEscape = () => {\n const doc = ed.getDoc();\n if (!doc || doc.__rmfLinkEscapeInstalled)\n return;\n doc.__rmfLinkEscapeInstalled = true;\n doc.addEventListener(\"beforeinput\", (e) => {\n // Only intercept text-insertion kinds. Deletes,\n // formatting, history nav don't extend links.\n const kind = e.inputType || \"\";\n const isInsert = kind === \"insertText\"\n || kind === \"insertCompositionText\"\n || kind === \"insertFromPaste\"\n || kind === \"insertFromDrop\"\n || kind === \"insertFromComposition\";\n if (!isInsert)\n return;\n const sel = doc.getSelection();\n if (!sel || sel.rangeCount === 0)\n return;\n const rng = sel.getRangeAt(0);\n if (!rng.collapsed)\n return;\n const node = rng.startContainer;\n const a = (node.nodeType === Node.ELEMENT_NODE ? node : node.parentNode);\n if (!a)\n return;\n const link = a.closest?.(\"a\");\n if (!link)\n return;\n // Range from caret to end-of-link: empty \u21D2 caret is\n // at the link's trailing edge. Anything else means\n // mid-link edit, which we want to leave alone.\n let tail;\n try {\n tail = rng.cloneRange();\n tail.setEndAfter(link);\n }\n catch {\n return;\n }\n if (tail.toString().length > 0)\n return;\n // Move the caret to just after the link. Use TinyMCE's\n // own selection API rather than a raw\n // removeAllRanges/addRange: the raw mutation ran ahead\n // of TinyMCE's beforeinput handling and desynced its\n // UndoManager, leaving Ctrl+Z erratic (Bob 2026-05-31:\n // \"^z is very broken in TinyMCE\"). Going through\n // ed.selection keeps TinyMCE's selection + undo state\n // consistent. Raw range as a fallback if it throws.\n const parent = link.parentNode;\n const idx = parent ? Array.prototype.indexOf.call(parent.childNodes, link) + 1 : -1;\n if (parent && idx >= 0) {\n try {\n ed.selection.setCursorLocation(parent, idx);\n }\n catch {\n const after = doc.createRange();\n after.setStartAfter(link);\n after.collapse(true);\n sel.removeAllRanges();\n sel.addRange(after);\n }\n }\n }, true);\n };\n ed.on(\"init\", installLinkEscape);\n ed.on(\"SetContent\", installLinkEscape);\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 // Apply the restored zoom (if any). Default = 14px which\n // matches the content_style baseline, so no-op when the\n // user hasn't changed zoom; otherwise the editor opens at\n // the size they last set.\n if (zoomPx !== ZOOM_DEFAULT)\n applyZoom();\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 /** Subscribe to content changes. compose.ts calls this to drive draft\n * auto-save; it was MISSING from the facade, so the call threw\n * \"editor.onContentChange is not a function\" during compose init \u2014\n * aborting the rest of init and leaving change-tracking unwired, so\n * edits made via dialogs (Insert code, Source code) were never\n * captured into the draft and looked \"ignored\" (Bob 2026-06-06).\n * `SetContent`/`ExecCommand` are the load-bearing events here: they're\n * what the codesample + code (source) dialogs fire on apply. */\n onContentChange(handler) {\n editor.on(\"input change undo redo keyup SetContent ExecCommand AddUndo\", () => handler());\n },\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 * Spell-check for the TinyMCE compose editor \u2014 NON-MUTATING OVERLAY.\n *\n * Why this rewrite (Bob 2026-06-12): the previous version drew the red\n * squiggles by WRAPPING each misspelled word in a `<span>` inside the live\n * contenteditable, on a debounce, while the user typed \u2014 then tried to put\n * the caret back by re-counting characters. That approach fought the user's\n * typing: it yanked the cursor to a different position, and because it\n * reshaped the DOM between keystrokes it corrupted TinyMCE's undo stack so a\n * single Ctrl+Z reverted a whole reshape instead of the last few characters.\n * It had been patched ~10 times and still regressed. The editor became\n * untrustworthy.\n *\n * The fix is to stop editing the content at all. We:\n * 1. READ the body's text nodes (TreeWalker) and ask nspell which words are\n * misspelled \u2014 no mutation.\n * 2. Measure each misspelled word's on-screen rectangle with a DOM Range's\n * getClientRects(), and draw a wavy red underline at that rectangle in a\n * SEPARATE overlay layer.\n *\n * The overlay is a single `<div>` appended to the body but marked\n * `contenteditable=\"false\"` + `data-mce-bogus=\"all\"` (TinyMCE excludes bogus\n * elements from both serialization and the undo snapshot) and\n * `pointer-events:none` (the caret can never enter it, clicks pass through to\n * the text). Mutating it therefore can't move the cursor, can't pollute undo,\n * and can't leak into the sent message or the saved draft. The squiggles are\n * absolutely positioned in the body's content coordinate space, so they scroll\n * with the text without any per-scroll work.\n *\n * Right-click uses getWordAtPoint() (find the word under the click directly),\n * so there are no marker spans to hang suggestions off of. Applying a\n * correction goes through TinyMCE's own selection + insertContent so it's\n * undo-tracked and focus-safe.\n *\n * Dictionary load, suggestion building, the floating menu, word-at-point, and\n * user-dictionary persistence are all shared with the Quill adapter via\n * spellcheck-core.ts.\n */\nimport {\n getSpell, addToUserDict, buildSuggestionList, showSuggestionsMenu,\n getWordAtPoint, MIN_WORD_LEN, SKIP_TAGS, type NSpellInstance, type MenuItem,\n} from \"./spellcheck-core.js\";\n\n// Re-scan after the user pauses. Long enough not to fire mid-word; short\n// enough to feel responsive. Nothing about this debounce affects typing\n// stability anymore (the scan never touches the content), so it's purely a\n// CPU/perf knob.\nconst SCAN_DEBOUNCE_MS = 600;\n\n// Red wavy underline: a 6x3 SVG wave tiled horizontally under the word.\n// encodeURIComponent keeps the inline SVG valid inside a CSS url().\nconst WAVE = `url(\"data:image/svg+xml,${encodeURIComponent(\n '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"6\" height=\"3\">' +\n '<path d=\"M0 2 Q1.5 0 3 2 T6 2\" stroke=\"#d33\" fill=\"none\" stroke-width=\"1\"/></svg>',\n)}\")`;\n\nconst OVERLAY_ID = \"mailx-spell-overlay\";\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 let sp: NSpellInstance | null = null;\n\n // Kill the native (Chromium/WebView2) spellchecker on this editor so we\n // don't get two sets of underlines / the native suggestion menu instead of\n // ours. Some WebView2 builds re-enable it, hence the observer.\n const killNative = (): 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 */ }\n };\n killNative();\n try {\n const body: HTMLElement | null = editor.getBody?.();\n if (body) new MutationObserver(killNative)\n .observe(body, { attributes: true, attributeFilter: [\"spellcheck\"] });\n } catch { /* */ }\n\n // The overlay layer. Bogus + non-editable + pointer-events:none so it's\n // invisible to serialization, undo, the caret, and the mouse.\n const ensureOverlay = (): HTMLElement | null => {\n const body: HTMLElement | null = editor.getBody?.();\n const doc: Document | null = editor.getDoc?.();\n if (!body || !doc) return null;\n let ov = doc.getElementById(OVERLAY_ID) as HTMLElement | null;\n if (!ov || ov.parentNode !== body) {\n ov?.remove();\n ov = doc.createElement(\"div\");\n ov.id = OVERLAY_ID;\n ov.setAttribute(\"contenteditable\", \"false\");\n ov.setAttribute(\"data-mce-bogus\", \"all\");\n ov.style.cssText = \"position:absolute;top:0;left:0;pointer-events:none;user-select:none;\";\n body.appendChild(ov);\n }\n return ov;\n };\n\n // Read the content, find misspelled words, draw squiggles. Reads the\n // content DOM (TreeWalker + Range measurement) but writes ONLY to the\n // bogus overlay \u2014 never the user's text or selection.\n const scan = (): void => {\n if (!sp) return;\n const body: HTMLElement | null = editor.getBody?.();\n const doc: Document | null = editor.getDoc?.();\n const ov = ensureOverlay();\n if (!body || !doc || !ov) return;\n\n const walker = doc.createTreeWalker(body, NodeFilter.SHOW_TEXT, {\n acceptNode(node: Node) {\n // Skip the overlay's own (none, but defensive) and non-prose\n // containers: quoted reply, code, links, etc.\n let p: Node | null = node.parentNode;\n while (p && p !== body) {\n if (p.nodeType === Node.ELEMENT_NODE) {\n const el = p as Element;\n if (el.id === OVERLAY_ID) return NodeFilter.FILTER_REJECT;\n if (SKIP_TAGS.has(el.tagName)) return NodeFilter.FILTER_REJECT;\n }\n p = p.parentNode;\n }\n return NodeFilter.FILTER_ACCEPT;\n },\n });\n\n const bodyRect = body.getBoundingClientRect();\n const sx = body.scrollLeft;\n const sy = body.scrollTop;\n const frag = doc.createDocumentFragment();\n // A word starts with a letter, then letters / apostrophes / hyphens.\n const wordRe = /[\\p{L}][\\p{L}'\u2019-]*/gu;\n\n for (let n = walker.nextNode() as Text | null; n; n = walker.nextNode() as Text | null) {\n const text = n.data;\n if (!text || text.length < MIN_WORD_LEN) continue;\n wordRe.lastIndex = 0;\n let m: RegExpExecArray | null;\n while ((m = wordRe.exec(text))) {\n const w = m[0];\n if (w.length < MIN_WORD_LEN) continue;\n if (sp.correct(w)) continue;\n const r = doc.createRange();\n r.setStart(n, m.index);\n r.setEnd(n, m.index + w.length);\n const rects = r.getClientRects();\n for (let i = 0; i < rects.length; i++) {\n const rect = rects[i];\n if (rect.width < 1) continue;\n const sq = doc.createElement(\"div\");\n // Content-space coordinates: the overlay is an absolutely\n // positioned child of the (scrolling) body, so a child placed\n // at content-space (x,y) tracks the word through scroll with\n // no per-scroll recompute.\n sq.style.cssText =\n \"position:absolute;\" +\n `left:${(rect.left - bodyRect.left + sx).toFixed(1)}px;` +\n `top:${(rect.bottom - bodyRect.top + sy - 3).toFixed(1)}px;` +\n `width:${rect.width.toFixed(1)}px;height:3px;` +\n `background:${WAVE} repeat-x left bottom;`;\n frag.appendChild(sq);\n }\n }\n }\n ov.textContent = \"\";\n ov.appendChild(frag);\n };\n\n let scanTimer: ReturnType<typeof setTimeout> | null = null;\n const scheduleScan = (): void => {\n if (!sp) return;\n if (scanTimer) clearTimeout(scanTimer);\n scanTimer = setTimeout(() => { scanTimer = null; scan(); }, SCAN_DEBOUNCE_MS);\n };\n\n getSpell().then((loaded: NSpellInstance) => { sp = loaded; scan(); })\n .catch((e: any) => console.error(\"[spellcheck] dict load failed:\", e));\n\n // Re-scan on any content change. NodeChange is deliberately excluded \u2014 it\n // can fire on pure selection moves and we don't want a scan per caret tick;\n // input/keyup cover real edits, and Undo/Redo/SetContent cover the rest.\n editor.on(\"input keyup paste SetContent Undo Redo\", scheduleScan);\n // Reposition after the editor reflows or scrolls.\n editor.on(\"ResizeEditor\", scheduleScan);\n try {\n editor.getDoc()?.addEventListener(\"scroll\", scheduleScan, { passive: true } as AddEventListenerOptions);\n } catch { /* */ }\n\n // Apply a correction: select the word's text-node range and let TinyMCE\n // replace it. editor.focus() + selection + insertContent is undo-tracked\n // and lands even though the suggestion menu (in the top document) stole\n // focus \u2014 the same focus-aware path the old replaceMarker needed.\n const apply = (node: Text, start: number, end: number, replacement: string): void => {\n try {\n const doc = editor.getDoc();\n const range = doc.createRange();\n range.setStart(node, start);\n range.setEnd(node, end);\n editor.focus();\n editor.selection.setRng(range);\n editor.insertContent(editor.dom.encode(replacement));\n } catch {\n // Raw-DOM fallback if the editor API is unavailable.\n try {\n const doc = editor.getDoc();\n const range = doc.createRange();\n range.setStart(node, start);\n range.setEnd(node, end);\n range.deleteContents();\n range.insertNode(doc.createTextNode(replacement));\n } catch { /* */ }\n }\n scheduleScan();\n };\n\n // Right-click: if the click landed on a misspelled word, show suggestions;\n // otherwise let the default (native) menu fire.\n const iframeDoc: Document = editor.getDoc();\n iframeDoc.addEventListener(\"contextmenu\", (ev: Event) => {\n const e = ev as MouseEvent;\n const body: HTMLElement | null = editor.getBody?.();\n if (!body || !sp) return;\n const hit = getWordAtPoint(body, e.clientX, e.clientY);\n if (!hit) return; // not on a word\n if (sp.correct(hit.word)) return; // spelled correctly \u2014 no menu\n e.preventDefault();\n e.stopPropagation();\n\n const sugs = buildSuggestionList(hit.word, sp);\n const items: MenuItem[] = sugs.length === 0\n ? [{ label: \"(no suggestions)\", action: () => { /* */ } }]\n : sugs.map(s => ({\n label: s,\n emphasized: true,\n action: () => apply(hit.node, hit.start, hit.end, s),\n }));\n items.push({ label: \"\", action: () => { /* */ }, separator: true });\n items.push({\n label: `Add \"${hit.word}\" to dictionary`,\n action: () => { if (sp) addToUserDict(hit.word, sp); scheduleScan(); },\n });\n items.push({\n label: \"Ignore (this session)\",\n action: () => { if (sp) sp.add(hit.word); scheduleScan(); },\n });\n\n // getWordAtPoint's (x,y) are iframe-local; the menu lives in the top\n // document, so offset by the iframe's position on the page.\n const iframeEl = editor.iframeElement as HTMLIFrameElement | undefined;\n const rect = iframeEl ? iframeEl.getBoundingClientRect() : ({ left: 0, top: 0 } as DOMRect);\n showSuggestionsMenu(document, rect.left + e.clientX, rect.top + e.clientY, items, [iframeDoc]);\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 // \u2500\u2500 Spell-check right-click handler (Quill) \u2500\u2500\n // Runs FIRST so a right-click on a misspelled word shows our suggestions\n // menu (Add to dictionary / Ignore / replace) instead of falling through\n // to the WebView2 native menu (which on msger-hosted compose iframes was\n // unreliable for \"Add to dictionary\" \u2014 Bob 2026-05-27 \"can't ignore nor\n // add an entry\"). Shares the nspell dictionary + user-dict-mirror with\n // the TinyMCE editor's spellcheck.ts via spellcheck-core.ts \u2014 same\n // dictionary, same persistence, same suggestion ranking.\n //\n // Decoration (red wavy underlines) is NOT wired here yet \u2014 Quill's\n // MutationObserver-based delta tracking interferes with arbitrary\n // span-wrapping. The visible-on-hover suggestion is left to native\n // (WebView2 still shows red squiggles via OS spell-check on\n // spellcheck=\"true\"); we just take over the right-click flow so the\n // user can ACT on a flagged word with persistence that actually works.\n let _spellSp: any = null;\n import(\"./spellcheck-core.js\").then(m => m.getSpell()).then(sp => { _spellSp = sp; }).catch(() => { /* dict unavailable */ });\n q.root.addEventListener(\"contextmenu\", async (e: MouseEvent) => {\n try {\n if (!_spellSp) return; // dict still loading\n const sel = q.getSelection();\n if (sel && sel.length > 0) return; // selection path handled below\n const core = await import(\"./spellcheck-core.js\");\n const hit = core.getWordAtPoint(q.root as HTMLElement, e.clientX, e.clientY);\n if (!hit) return;\n const { word, node, start, end } = hit;\n if (_spellSp.correct(word)) return; // word is fine \u2014 let native menu fire\n e.preventDefault();\n e.stopPropagation();\n const sugs = core.buildSuggestionList(word, _spellSp);\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 // Replace via Range so the browser's text-edit\n // handling kicks in and Quill sees it as a normal\n // edit (undoable, Delta-tracked). Going through\n // q.deleteText/insertText needs character offsets\n // from the Quill index space \u2014 possible but more\n // arithmetic; Range is simpler and works for the\n // text-node case the misspelling always lives in.\n try {\n const range = document.createRange();\n range.setStart(node, start);\n range.setEnd(node, end);\n const docSel = document.getSelection();\n if (docSel) {\n docSel.removeAllRanges();\n docSel.addRange(range);\n if (!document.execCommand(\"insertText\", false, s)) {\n range.deleteContents();\n range.insertNode(document.createTextNode(s));\n }\n }\n } catch { /* */ }\n },\n });\n }\n }\n items.push({ label: \"\", action: () => {}, separator: true });\n items.push({\n label: `Add \"${word}\" to dictionary`,\n action: () => core.addToUserDict(word, _spellSp),\n });\n items.push({\n label: \"Ignore (this session)\",\n action: () => _spellSp.add(word),\n });\n core.showSuggestionsMenu(document, e.clientX, e.clientY, items);\n } catch (err: any) {\n console.warn(\"[spellcheck] right-click handler error:\", err?.message || err);\n }\n });\n\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\">•</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\">“</button>\n <button class=\"tt-btn\" data-cmd=\"link\" title=\"Link\" tabindex=\"-1\">🔗</button>\n <button class=\"tt-btn\" data-cmd=\"clearFormat\" title=\"Clear formatting\" tabindex=\"-1\">⌧</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, installConsoleCapture } from \"../lib/api-client.js\";\nimport { showContextMenu, type MenuItem } from \"../components/context-menu.js\";\n\n// Capture compose-iframe console + window errors to the daemon log. Same\n// reason as app.ts \u2014 debugging \"draft save failed silently\" or \"iframe\n// hung mid-init\" doesn't require devtools.\ninstallConsoleCapture();\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 draftId?: string;\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}\n/** Parse a single recipient segment (\"Name <a@b.c>\" or a bare address) into\n * name/email. Returns null when no address is present. */\nfunction parseRecipientSegment(seg: string): { name: string; email: string } | null {\n const s = (seg || \"\").trim();\n if (!s) return null;\n const angled = s.match(/^\\s*\"?([^\"<]*?)\"?\\s*<([^>]+)>\\s*$/);\n if (angled && /@/.test(angled[2])) return { name: angled[1].trim(), email: angled[2].trim() };\n const bare = s.match(/[^\\s<>,\"]+@[^\\s<>,\"]+\\.[^\\s<>,\"]+/);\n if (bare) return { name: \"\", email: bare[0] };\n return null;\n}\n\n/** Which comma-separated address sits under the caret/right-click position in\n * a recipient textarea. Falls back to the last segment. */\nfunction recipientAtCaret(el: HTMLTextAreaElement): { name: string; email: string } | null {\n const v = el.value;\n const caret = el.selectionStart ?? v.length;\n let start = 0;\n for (const seg of v.split(\",\")) {\n const end = start + seg.length;\n if (caret >= start && caret <= end) return parseRecipientSegment(seg);\n start = end + 1; // account for the comma delimiter\n }\n return parseRecipientSegment(v.split(\",\").pop() || \"\");\n}\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 // Right-click on a recipient \u2192 contact actions for the address under the\n // caret, plus the clipboard essentials (this replaces the native WebView\n // menu, so Cut/Copy/Paste/Select-all are re-supplied). Google-contact\n // add/update/merge is punted to Google's own UI \u2014 same decision as the\n // autocomplete-row menu (no in-app People API write / dedup needed).\n el?.addEventListener(\"contextmenu\", (e) => {\n if (!el) return;\n e.preventDefault();\n const me = e as MouseEvent;\n const items: MenuItem[] = [];\n const addr = recipientAtCaret(el);\n if (addr?.email) {\n items.push({\n label: `Open in Google Contacts: ${addr.email}`,\n tooltip: \"Add as a new contact, attach to an existing one, or merge/update \u2014 in Google's own UI.\",\n action: () => window.open(`https://contacts.google.com/search/${encodeURIComponent(addr.name || addr.email)}`, \"_blank\"),\n });\n items.push({ label: \"Add to preferred\u2026\", action: () => openAddToPreferredModal({ name: addr.name, email: addr.email, source: \"\" }) });\n items.push({ label: `Copy address: ${addr.email}`, action: () => { void navigator.clipboard.writeText(addr.email); } });\n items.push({ label: \"\", action: () => {}, separator: true });\n }\n const selText = (): string => el.value.slice(el.selectionStart ?? 0, el.selectionEnd ?? 0);\n const replaceSelection = (text: string): void => {\n const s = el.selectionStart ?? el.value.length, en = el.selectionEnd ?? el.value.length;\n el.value = el.value.slice(0, s) + text + el.value.slice(en);\n el.selectionStart = el.selectionEnd = s + text.length;\n el.dispatchEvent(new Event(\"input\", { bubbles: true }));\n };\n items.push({ label: \"Cut\", action: async () => { const t = selText(); if (t) { try { await navigator.clipboard.writeText(t); } catch { /* */ } replaceSelection(\"\"); } } });\n items.push({ label: \"Copy\", action: async () => { const t = selText(); if (t) { try { await navigator.clipboard.writeText(t); } catch { /* */ } } } });\n items.push({ label: \"Paste\", action: async () => { try { replaceSelection(await navigator.clipboard.readText()); } catch { /* clipboard blocked */ } } });\n items.push({ label: \"Select all\", action: () => el.select() });\n showContextMenu(me.clientX, me.clientY, items);\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, contact?: { name: string; email: string }): void {\n const val = input.value;\n const caret = input.selectionStart ?? val.length;\n const { start, end } = tokenSpanAtCaret(val, caret);\n let before = val.substring(0, start);\n let after = val.substring(end);\n // Strip a trailing \",\" / \", \" from `before` \u2014 `tokenSpanAtCaret`\n // returns `start` immediately AFTER the prior separator-comma, so\n // `before` always ends with that comma. We re-add a clean \", \"\n // separator below; without stripping here, \", bob\" \u2192 click suggestion\n // produced \"bob, \"Frankston, Bob\" <addr>\" (the original `bob` token\n // was actually preserved by an off-by-one in some edge cases where\n // the dropdown opened on a stale `bob` while the input had advanced\n // past it). Bob 2026-05-24.\n before = before.replace(/[ \\t,]+$/, \"\");\n\n // Drop \"stranded partials\" from before/after: prior unresolved tokens\n // (no `@`) that look like earlier attempts to type THIS contact.\n // Pattern: user types \"hod\" \u2192 suggestion appears \u2192 user keeps typing\n // \", p\" \u2192 new suggestion for \"p\" \u2192 clicks Peter Hoddie. Without this\n // cleanup the input ends up as \"hod, Peter Hoddie <peter@\u2026>, \" \u2014\n // \"hod\" gets stranded and fails validation at send time. The\n // chosen contact's name+email becomes the haystack; any prior bare\n // token that's a substring is treated as the same intent and dropped\n // (Bob 2026-05-24).\n if (contact) {\n const haystack = `${contact.name} ${contact.email}`.toLowerCase();\n const stripStranded = (s: string): string => splitRecipients(s)\n .map(p => p.trim())\n .filter(p => p.length > 0 && (p.includes(\"@\") || !haystack.includes(p.toLowerCase())))\n .join(\", \");\n if (before) before = stripStranded(before);\n const afterCore = after.replace(/^[\\s,]+/, \"\").replace(/[\\s,]+$/, \"\");\n if (afterCore) {\n const cleanedAfter = stripStranded(afterCore);\n after = cleanedAfter ? \", \" + cleanedAfter : \"\";\n }\n }\n\n const lead = before.length ? \", \" : \"\";\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, { name: r.name, email: r.email });\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 // Skip any leading whitespace inside the token span \u2014 without this, a\n // selection of `\"Frankston, Bob\" <bob@x.com>` against an input like\n // `bob` whose token span was `{0, 3}` would be fine, but if the user\n // had partially-completed a prior recipient (e.g. value `prev, bob`\n // with caret at 9 in span {6, 9}) the leading space belonged to the\n // SEPARATOR, not the token. Replacing the span as-is then leaves the\n // space in `before` AND prepends another `, ` ahead of the insert \u2014\n // producing duplicated separators on commit. Trimming leading WS keeps\n // `before` clean and lets the replacement own its own separators.\n while (start < end && (s[start] === \" \" || s[start] === \"\\t\")) start++;\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 AND its\n // stable X-Mailx-Draft-ID so saveDraft can keep replacing the same\n // draft instead of appending a new one every save. Without the ID\n // round-trip the daemon falls back to UID-based dedup only \u2014 fine\n // when APPENDUID returns synchronously, but when it doesn't (Dovecot\n // slow, IMAP push deferred) each save lands a fresh draft alongside\n // the old one.\n if (init.draftUid) {\n draftUid = init.draftUid;\n }\n if (init.draftId) {\n draftId = init.draftId;\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 _postedInit: ComposeInit | null = null;\nlet _parentInitReady = !!sessionStorage.getItem(\"composeInit\");\nconst _parentInitListeners: Array<() => void> = [];\nlet _msgEventCount = 0;\nwindow.addEventListener(\"message\", (e: MessageEvent) => {\n _msgEventCount++;\n // Accept either the legacy ready-signal (paired with sessionStorage) or\n // the newer payload-carrying message \u2014 postMessage is the fallback for\n // when sessionStorage doesn't bridge to the iframe (WebView2 quirk on\n // the custom-protocol host).\n if (e.data?.type === \"compose-init-ready\") {\n _parentInitReady = true;\n for (const fn of _parentInitListeners.splice(0)) fn();\n } else if (e.data?.type === \"compose-init\" && e.data.init) {\n if (!_postedInit) { try { logClientEvent(\"compose-init-received\", { src: \"postMessage\" }); } catch { /* */ } }\n _postedInit = e.data.init as ComposeInit;\n _parentInitReady = true;\n for (const fn of _parentInitListeners.splice(0)) fn();\n }\n});\n\n// Last-resort handoff: pull init from the parent window's keyed stash.\n// sessionStorage AND postMessage have both failed in production\n// (Bob 2026-05-24: replyAll opened with empty fields; no init parsed).\n// Same-origin iframe \u2192 window.parent access is allowed by SOP and is\n// synchronous (no race). Keyed by a token the parent set on iframe.dataset.\nfunction pullInitFromParent(): ComposeInit | null {\n try {\n const myFrame = window.frameElement as HTMLIFrameElement | null;\n const key = myFrame?.dataset?.composeKey;\n if (!key) return null;\n const stash = (window.parent as any)?.__mailxComposeInits;\n const init = stash?.[key];\n if (init) {\n try { logClientEvent(\"compose-init-received\", { src: \"parent-stash\", key }); } catch { /* */ }\n return init as ComposeInit;\n }\n } catch (e: any) {\n try { logClientEvent(\"compose-init-pull-failed\", { err: e?.message || String(e) }); } catch { /* */ }\n }\n return null;\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 // Try parent-stash FIRST \u2014 it's synchronous and most reliable. Only\n // wait/poll if it's empty.\n let parentInit = pullInitFromParent();\n if (!parentInit && !sessionStorage.getItem(\"composeInit\") && !_postedInit) {\n _ctick(\"waiting for parent init\");\n await waitForParentInit(1500);\n _ctick(`parent init received (msgEvents=${_msgEventCount})`);\n // Re-poll the parent stash \u2014 it may have been populated during the wait.\n if (!_postedInit) parentInit = pullInitFromParent();\n }\n // Priority: parent-stash > postMessage > sessionStorage.\n const stored = sessionStorage.getItem(\"composeInit\");\n const initRaw: ComposeInit | null = parentInit\n || _postedInit\n || (stored ? (JSON.parse(stored) as ComposeInit) : null);\n if (initRaw) {\n sessionStorage.removeItem(\"composeInit\");\n const init = initRaw;\n const src = parentInit ? \"parent-stash\" : (_postedInit ? \"postMessage\" : \"sessionStorage\");\n _ctick(`init parsed (mode=${init.mode}, bodyHtml=${init.bodyHtml?.length || 0} bytes, src=${src})`);\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) {\n statusEl.textContent = `Invalid address: \"${bad}\" \u2014 Send blocked`;\n // Toggle the error class so the high-contrast white-on-red\n // styling kicks in; otherwise the message used the muted-grey\n // default color and was missable (Bob 2026-05-24).\n statusEl.classList.add(\"compose-status-error\");\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 => ({ \"&\": \"&\", \"<\": \"<\", \">\": \">\", '\"': \""\", \"'\": \"'\" }[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// Close handle for the \"Editing in Word\" instruction modal, so the\n// wordEditUpdated handler can dismiss it the moment edits reload (i.e. when\n// the user returns from Word). Null when no hint is showing.\nlet extEditHintClose: (() => void) | 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\">×</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 => ({ \"&\": \"&\", \"<\": \"<\", \">\": \">\", '\"': \""\", \"'\": \"'\" })[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 extEditHintClose = null;\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 // Deliberately NO click-outside-to-dismiss: this hint must survive the whole\n // round-trip to Word. The backdrop spans the compose window, so the first\n // click when the user Alt-Tabs back used to land on it and close the hint\n // before they'd read the steps (Bob 2026-06-11). It now stays up until the\n // user returns from Word (wordEditUpdated auto-closes it) or clicks \"Got it\"\n // / presses Escape.\n extEditHintClose = 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 // Returned from Word with a save \u2014 the instruction hint has done its\n // job; take it down so the reloaded body is visible.\n extEditHintClose?.();\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 // Ctrl+S = save draft now. Without this binding the only saves come\n // from the 800ms input debounce and the 2s safety-net interval, so\n // hitting Ctrl+S after typing felt laggy and the preview pane could\n // sit on stale text for seconds. Bypass the debounce timer and fire\n // saveDraft immediately. Also suppress the WebView2 default action\n // (\"save page as\u2026\") that otherwise opens a file dialog over compose.\n if (e.ctrlKey && (e.key === \"s\" || e.key === \"S\") && !e.shiftKey && !e.altKey) {\n e.preventDefault();\n if (draftDebounceTimer) {\n clearTimeout(draftDebounceTimer);\n draftDebounceTimer = null;\n }\n saveDraft().catch(() => { /* errors already surfaced in status bar */ });\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
|
+
"names": ["line", "require_add", "require_dictionary", "NSpell", "draftUid", "draftId", "NSpell", "dismiss", "container", "editor", "parent", "editor", "editor", "container", "container", "aiTransform", "dismiss", "createTinyMceEditor", "initGhostText", "readJsoncFile", "hasCcHistoryTo", "hasBccHistoryTo", "saveDraft", "EDITOR_HELP_MD", "h"]
|
|
7
7
|
}
|