@bobfrankston/rmfmail 1.1.174 → 1.1.180

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.
@@ -31,1884 +31,2196 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
31
31
  mod
32
32
  ));
33
33
 
34
- // client/lib/api-client.js
35
- var api_client_exports = {};
36
- __export(api_client_exports, {
37
- abortMessageListRequests: () => abortMessageListRequests,
38
- addContact: () => addContact,
39
- addPreferredContact: () => addPreferredContact,
40
- addToDenylist: () => addToDenylist,
41
- addUserDictWord: () => addUserDictWord,
42
- addUserDictWords: () => addUserDictWords,
43
- aiTransform: () => aiTransform,
44
- allowRemoteContent: () => allowRemoteContent,
45
- autocomplete: () => autocomplete,
46
- cancelQueuedOutgoing: () => cancelQueuedOutgoing,
47
- cancelServerSearch: () => cancelServerSearch,
48
- closeWordEdit: () => closeWordEdit,
49
- connectEvents: () => connectEvents,
50
- connectWebSocket: () => connectWebSocket,
51
- consumePendingMailto: () => consumePendingMailto,
52
- createCalendarEvent: () => createCalendarEvent,
53
- createFolder: () => createFolder,
54
- createTask: () => createTask,
55
- deleteCalendarEvent: () => deleteCalendarEvent,
56
- deleteContact: () => deleteContact,
57
- deleteDraft: () => deleteDraft,
58
- deleteFolder: () => deleteFolder,
59
- deleteMessage: () => deleteMessage,
60
- deleteMessages: () => deleteMessages,
61
- deleteTask: () => deleteTask,
62
- drainStoreSync: () => drainStoreSync,
63
- emptyFolder: () => emptyFolder,
64
- flagSenderOrDomain: () => flagSenderOrDomain,
65
- formatJsonc: () => formatJsonc,
66
- getAccounts: () => getAccounts,
67
- getAttachment: () => getAttachment,
68
- getAutocompleteSettings: () => getAutocompleteSettings,
69
- getCalendarEvents: () => getCalendarEvents,
70
- getCalendars: () => getCalendars,
71
- getDeviceAccounts: () => getDeviceAccounts,
72
- getDiagnostics: () => getDiagnostics,
73
- getFolders: () => getFolders,
74
- getMessage: () => getMessage,
75
- getMessages: () => getMessages,
76
- getOutboxStatus: () => getOutboxStatus,
77
- getPrimaryAccount: () => getPrimaryAccount,
78
- getPriorityLists: () => getPriorityLists,
79
- getSettings: () => getSettings,
80
- getSyncPending: () => getSyncPending,
81
- getTasks: () => getTasks,
82
- getThreadMessages: () => getThreadMessages,
83
- getUnifiedInbox: () => getUnifiedInbox,
84
- getUserDict: () => getUserDict,
85
- getVersion: () => getVersion,
86
- hasBccHistoryTo: () => hasBccHistoryTo,
87
- hasCcHistoryTo: () => hasCcHistoryTo,
88
- listContacts: () => listContacts,
89
- listQueuedOutgoing: () => listQueuedOutgoing,
90
- logClientEvent: () => logClientEvent,
91
- markAsSpamMessages: () => markAsSpamMessages,
92
- markFolderRead: () => markFolderRead,
93
- moveFolderToTrash: () => moveFolderToTrash,
94
- moveMessage: () => moveMessage,
95
- moveMessages: () => moveMessages,
96
- onEvent: () => onEvent,
97
- onWsEvent: () => onWsEvent,
98
- openAttachment: () => openAttachment,
99
- openInTextEditor: () => openInTextEditor,
100
- openInWord: () => openInWord,
101
- openLocalPath: () => openLocalPath,
102
- readConfigHelp: () => readConfigHelp,
103
- readJsoncFile: () => readJsoncFile,
104
- reauthGoogleScopes: () => reauthGoogleScopes,
105
- reauthenticate: () => reauthenticate,
106
- recordSpamReport: () => recordSpamReport,
107
- removeUserDictWord: () => removeUserDictWord,
108
- renameFolder: () => renameFolder,
109
- repairAccounts: () => repairAccounts,
110
- restartServer: () => restartServer,
111
- saveAutocompleteSettings: () => saveAutocompleteSettings,
112
- saveDraft: () => saveDraft,
113
- saveSettings: () => saveSettings,
114
- searchContacts: () => searchContacts,
115
- searchMessages: () => searchMessages,
116
- sendMessage: () => sendMessage,
117
- setPriorityDomain: () => setPriorityDomain,
118
- setPrioritySender: () => setPrioritySender,
119
- setupAccount: () => setupAccount,
120
- showReminderPopup: () => showReminderPopup,
121
- subscribeStore: () => subscribeStore,
122
- syncAccount: () => syncAccount,
123
- triggerSync: () => triggerSync,
124
- undeleteMessage: () => undeleteMessage,
125
- unsubscribeOneClick: () => unsubscribeOneClick,
126
- updateCalendarEvent: () => updateCalendarEvent,
127
- updateFlags: () => updateFlags,
128
- updateTask: () => updateTask,
129
- upsertContact: () => upsertContact,
130
- writeJsoncFile: () => writeJsoncFile
34
+ // node_modules/is-buffer/index.js
35
+ var require_is_buffer = __commonJS({
36
+ "node_modules/is-buffer/index.js"(exports, module) {
37
+ module.exports = function isBuffer(obj) {
38
+ return obj != null && obj.constructor != null && typeof obj.constructor.isBuffer === "function" && obj.constructor.isBuffer(obj);
39
+ };
40
+ }
131
41
  });
132
- function getIpc() {
133
- if (typeof mailxapi !== "undefined" && mailxapi?.isApp)
134
- return mailxapi;
135
- if (window.opener?.mailxapi?.isApp)
136
- return window.opener.mailxapi;
137
- if (window.parent?.mailxapi?.isApp)
138
- return window.parent.mailxapi;
139
- return null;
140
- }
141
- function buildRelayBridge() {
142
- const pending = /* @__PURE__ */ new Map();
143
- window.addEventListener("message", (ev) => {
144
- if (!ev.data || ev.data.type !== "mailx-ipc-result" || !ev.data.id)
145
- return;
146
- const entry = pending.get(ev.data.id);
147
- if (!entry)
148
- return;
149
- pending.delete(ev.data.id);
150
- clearTimeout(entry.timer);
151
- if (ev.data.ok)
152
- entry.resolve(ev.data.result);
153
- else
154
- entry.reject(new Error(ev.data.error || "parent-relay ipc error"));
155
- });
156
- const call = (method, args) => {
157
- const id = `ipc-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
158
- return new Promise((resolve, reject) => {
159
- const timer = setTimeout(() => {
160
- pending.delete(id);
161
- reject(new Error(`parent-relay timeout: ${method}`));
162
- }, 12e4);
163
- pending.set(id, { resolve, reject, timer });
164
- try {
165
- window.parent.postMessage({ type: "mailx-ipc", id, method, args }, "*");
166
- } catch (e) {
167
- clearTimeout(timer);
168
- pending.delete(id);
169
- reject(e);
170
- }
171
- });
172
- };
173
- return new Proxy({}, {
174
- get(_t, prop) {
175
- if (prop === "isApp")
176
- return true;
177
- if (prop === "platform")
178
- return window.parent?.mailxapi?.platform || "webview2";
179
- if (prop === "onEvent") {
180
- return (handler) => window.parent?.mailxapi?.onEvent?.(handler);
42
+
43
+ // node_modules/nspell/lib/util/rule-codes.js
44
+ var require_rule_codes = __commonJS({
45
+ "node_modules/nspell/lib/util/rule-codes.js"(exports, module) {
46
+ "use strict";
47
+ module.exports = ruleCodes;
48
+ var NO_CODES = [];
49
+ function ruleCodes(flags, value) {
50
+ var index = 0;
51
+ var result;
52
+ if (!value) return NO_CODES;
53
+ if (flags.FLAG === "long") {
54
+ result = new Array(Math.ceil(value.length / 2));
55
+ while (index < value.length) {
56
+ result[index / 2] = value.slice(index, index + 2);
57
+ index += 2;
58
+ }
59
+ return result;
181
60
  }
182
- return (...args) => call(prop, args);
61
+ return value.split(flags.FLAG === "num" ? "," : "");
183
62
  }
184
- });
185
- }
186
- function ipc() {
187
- const inIframe = window.parent && window.parent !== window;
188
- if (inIframe && window.parent?.mailxapi?.isApp) {
189
- if (!cachedRelayBridge)
190
- cachedRelayBridge = buildRelayBridge();
191
- return cachedRelayBridge;
192
- }
193
- const bridge = getIpc();
194
- if (!bridge)
195
- throw new Error("IPC bridge not available");
196
- return bridge;
197
- }
198
- function abortMessageListRequests() {
199
- if (messageListAbort) {
200
- messageListAbort.abort();
201
- messageListAbort = null;
202
63
  }
203
- }
204
- function getAccounts() {
205
- return ipc().getAccounts();
206
- }
207
- function getFolders(accountId) {
208
- return ipc().getFolders(accountId);
209
- }
210
- function getMessages(accountId, folderId, page = 1, pageSize = 50, flaggedOnly = false, sort, sortDir) {
211
- abortMessageListRequests();
212
- return ipc().getMessages(accountId, folderId, page, pageSize, sort, sortDir, void 0, flaggedOnly);
213
- }
214
- function getUnifiedInbox(page = 1, pageSize = 50) {
215
- abortMessageListRequests();
216
- return ipc().getUnifiedInbox(page, pageSize);
217
- }
218
- function searchMessages(query, page = 1, pageSize = 50, scope = "all", accountId = "", folderId = 0, includeTrashSpam = false) {
219
- return ipc().searchMessages(query, page, pageSize, scope, accountId, folderId, includeTrashSpam);
220
- }
221
- function cancelServerSearch() {
222
- return ipc().cancelServerSearch?.();
223
- }
224
- function getMessage(accountId, uid, allowRemote = false, folderId) {
225
- return ipc().getMessage(accountId, uid, allowRemote, folderId);
226
- }
227
- function updateFlags(accountId, uid, flags) {
228
- return ipc().updateFlags(accountId, uid, flags);
229
- }
230
- function triggerSync() {
231
- return ipc().syncAll();
232
- }
233
- function syncAccount(accountId) {
234
- return ipc().syncAccount(accountId);
235
- }
236
- function reauthenticate(accountId) {
237
- return ipc().reauthenticate(accountId);
238
- }
239
- function reauthGoogleScopes() {
240
- return ipc().reauthGoogleScopes();
241
- }
242
- function getSyncPending() {
243
- return ipc().getSyncPending();
244
- }
245
- function getDiagnostics() {
246
- return ipc().getDiagnostics?.() ?? Promise.resolve([]);
247
- }
248
- function getPrimaryAccount(feature) {
249
- return ipc().getPrimaryAccount?.(feature) ?? Promise.resolve(null);
250
- }
251
- function getCalendarEvents(fromMs, toMs) {
252
- return ipc().getCalendarEvents?.(fromMs, toMs) ?? Promise.resolve([]);
253
- }
254
- function getCalendars() {
255
- return ipc().getCalendars?.() ?? Promise.resolve([]);
256
- }
257
- function createCalendarEvent(ev) {
258
- return ipc().createCalendarEvent?.(ev);
259
- }
260
- function updateCalendarEvent(uuid, patch) {
261
- return ipc().updateCalendarEvent?.(uuid, patch);
262
- }
263
- function deleteCalendarEvent(uuid) {
264
- return ipc().deleteCalendarEvent?.(uuid);
265
- }
266
- function getTasks(includeCompleted = false) {
267
- return ipc().getTasks?.(includeCompleted) ?? Promise.resolve([]);
268
- }
269
- function createTask(t) {
270
- return ipc().createTask?.(t);
271
- }
272
- function updateTask(uuid, patch) {
273
- return ipc().updateTask?.(uuid, patch);
274
- }
275
- function deleteTask(uuid) {
276
- return ipc().deleteTask?.(uuid);
277
- }
278
- function drainStoreSync() {
279
- return ipc().drainStoreSync?.();
280
- }
281
- function recordSpamReport(accountId, uid, folderId) {
282
- return ipc().recordSpamReport?.(accountId, uid, folderId);
283
- }
284
- function getOutboxStatus() {
285
- return ipc().getOutboxStatus();
286
- }
287
- function listQueuedOutgoing() {
288
- return ipc().listQueuedOutgoing();
289
- }
290
- function cancelQueuedOutgoing(p) {
291
- return ipc().cancelQueuedOutgoing(p);
292
- }
293
- function searchContacts(query) {
294
- return ipc().searchContacts(query);
295
- }
296
- function hasCcHistoryTo(email) {
297
- return ipc().hasCcHistoryTo(email);
298
- }
299
- function hasBccHistoryTo(email) {
300
- return ipc().hasBccHistoryTo(email);
301
- }
302
- function listContacts(query, page = 1, pageSize = 100) {
303
- return ipc().listContacts(query, page, pageSize);
304
- }
305
- function upsertContact(name, email) {
306
- return ipc().upsertContact(name, email);
307
- }
308
- function deleteContact(email) {
309
- return ipc().deleteContact(email);
310
- }
311
- function addPreferredContact(entry) {
312
- return ipc().addPreferredContact(entry.name, entry.email, entry.source, entry.organization);
313
- }
314
- function getPriorityLists() {
315
- return ipc().getPriorityLists();
316
- }
317
- function setPrioritySender(email, value, name) {
318
- return ipc().setPrioritySender(email, value, name);
319
- }
320
- function setPriorityDomain(domain, value) {
321
- return ipc().setPriorityDomain(domain, value);
322
- }
323
- function addToDenylist(email) {
324
- return ipc().addToDenylist(email);
325
- }
326
- function openLocalPath(which) {
327
- return ipc().openLocalPath(which);
328
- }
329
- function openInTextEditor(path) {
330
- return ipc().openInTextEditor?.(path) ?? Promise.resolve({ ok: false, opener: "none", reason: "no host" });
331
- }
332
- function allowRemoteContent(type, value) {
333
- return ipc().allowRemoteContent(type, value);
334
- }
335
- function getUserDict() {
336
- return ipc().getUserDict?.() ?? Promise.resolve([]);
337
- }
338
- function addUserDictWord(word) {
339
- return ipc().addUserDictWord?.(word) ?? Promise.resolve([]);
340
- }
341
- function addUserDictWords(words) {
342
- return ipc().addUserDictWords?.(words) ?? Promise.resolve([]);
343
- }
344
- function removeUserDictWord(word) {
345
- return ipc().removeUserDictWord?.(word) ?? Promise.resolve([]);
346
- }
347
- function flagSenderOrDomain(type, value) {
348
- return ipc().flagSenderOrDomain?.(type, value) ?? Promise.resolve({ flagged: false });
349
- }
350
- function deleteMessage(accountId, uid) {
351
- return ipc().deleteMessage?.(accountId, uid);
352
- }
353
- function deleteMessages(accountId, uids) {
354
- if (uids.length === 1)
355
- return deleteMessage(accountId, uids[0]);
356
- return ipc().deleteMessages?.(accountId, uids);
357
- }
358
- function moveMessages(accountId, uids, targetFolderId, targetAccountId) {
359
- if (uids.length === 1)
360
- return moveMessage(accountId, uids[0], targetFolderId, targetAccountId);
361
- return ipc().moveMessages?.(accountId, uids, targetFolderId, targetAccountId);
362
- }
363
- function markAsSpamMessages(accountId, uids) {
364
- return ipc().markAsSpamMessages?.(accountId, uids);
365
- }
366
- function undeleteMessage(accountId, uid, folderId) {
367
- return ipc().undeleteMessage?.(accountId, uid, folderId);
368
- }
369
- function moveMessage(accountId, uid, targetFolderId, targetAccountId) {
370
- return ipc().moveMessage?.(accountId, uid, targetFolderId, targetAccountId);
371
- }
372
- function restartServer() {
373
- return ipc().restart?.();
374
- }
375
- function markFolderRead(accountId, folderId) {
376
- return ipc().markFolderRead?.(accountId, folderId);
377
- }
378
- function createFolder(accountId, parentPath, name) {
379
- return ipc().createFolder?.(accountId, parentPath, name);
380
- }
381
- function renameFolder(accountId, folderId, newName) {
382
- return ipc().renameFolder?.(accountId, folderId, newName);
383
- }
384
- function deleteFolder(accountId, folderId) {
385
- return ipc().deleteFolder?.(accountId, folderId);
386
- }
387
- function moveFolderToTrash(accountId, folderId) {
388
- return ipc().moveFolderToTrash?.(accountId, folderId);
389
- }
390
- function emptyFolder(accountId, folderId) {
391
- return ipc().emptyFolder?.(accountId, folderId);
392
- }
393
- function logClientEvent(tag, data) {
394
- let delivered = false;
395
- try {
396
- const bridge = typeof globalThis.mailxapi !== "undefined" && globalThis.mailxapi?.isApp ? globalThis.mailxapi : window.opener?.mailxapi?.isApp ? window.opener.mailxapi : window.parent?.mailxapi?.isApp ? window.parent.mailxapi : null;
397
- if (bridge?.logClientEvent) {
398
- bridge.logClientEvent(tag, data);
399
- delivered = true;
400
- }
401
- } catch {
402
- }
403
- try {
404
- if (window.parent && window.parent !== window) {
405
- window.parent.postMessage({ type: "mailx-trace", tag, data, bridged: delivered }, "*");
406
- }
407
- } catch {
408
- }
409
- }
410
- function sendMessage(body) {
411
- return ipc().sendMessage?.(body);
412
- }
413
- function saveDraft(body) {
414
- return ipc().saveDraft?.(body);
415
- }
416
- function onEvent(handler) {
417
- eventHandlers.push(handler);
418
- return () => {
419
- const i = eventHandlers.indexOf(handler);
420
- if (i >= 0)
421
- eventHandlers.splice(i, 1);
422
- };
423
- }
424
- function subscribeStore(topic, handler) {
425
- let set = storeSubs.get(topic);
426
- if (!set) {
427
- set = /* @__PURE__ */ new Set();
428
- storeSubs.set(topic, set);
429
- }
430
- set.add(handler);
431
- return () => {
432
- const s = storeSubs.get(topic);
433
- if (s) {
434
- s.delete(handler);
435
- if (s.size === 0)
436
- storeSubs.delete(topic);
437
- }
438
- };
439
- }
440
- function deliverStore(event) {
441
- const exact = storeSubs.get(event.topic);
442
- if (exact)
443
- for (const h of exact) {
444
- try {
445
- h(event);
446
- } catch (e) {
447
- console.error("[store-bus]", e);
448
- }
449
- }
450
- const wild = storeSubs.get("*");
451
- if (wild)
452
- for (const h of wild) {
453
- try {
454
- h(event);
455
- } catch (e) {
456
- console.error("[store-bus]", e);
457
- }
458
- }
459
- }
460
- function connectEvents() {
461
- ipc().onEvent((event) => {
462
- if (event && event._event === "store")
463
- deliverStore(event);
464
- for (const h of eventHandlers)
465
- h(event);
466
- });
467
- }
468
- function autocomplete(body, signal) {
469
- return ipc().autocomplete?.(body);
470
- }
471
- function getAutocompleteSettings() {
472
- return ipc().getAutocompleteSettings?.();
473
- }
474
- function saveAutocompleteSettings(settings) {
475
- return ipc().saveAutocompleteSettings?.(settings);
476
- }
477
- function getVersion() {
478
- return ipc().getVersion();
479
- }
480
- function getSettings() {
481
- return ipc().getSettings();
482
- }
483
- function saveSettings(settings) {
484
- return ipc().saveSettingsData?.(settings);
485
- }
486
- function repairAccounts() {
487
- return ipc().repairAccounts?.();
488
- }
489
- function deleteDraft(accountId, draftUid2, draftId2) {
490
- return ipc().deleteDraft?.(accountId, draftUid2, draftId2);
491
- }
492
- function addContact(name, email) {
493
- return ipc().addContact?.(name, email);
494
- }
495
- function getThreadMessages(accountId, threadId) {
496
- return ipc().getThreadMessages?.(accountId, threadId);
497
- }
498
- function readJsoncFile(name) {
499
- return ipc().readJsoncFile?.(name);
500
- }
501
- function writeJsoncFile(name, content) {
502
- return ipc().writeJsoncFile?.(name, content);
503
- }
504
- function formatJsonc(content) {
505
- return ipc().formatJsonc?.(content);
506
- }
507
- function readConfigHelp(name) {
508
- return ipc().readConfigHelp?.(name) ?? Promise.resolve({ content: "" });
509
- }
510
- function unsubscribeOneClick(url) {
511
- return ipc().unsubscribeOneClick?.(url);
512
- }
513
- function openInWord(editId, html) {
514
- return ipc().openInWord?.(editId, html) ?? Promise.resolve({ ok: false, path: "", opener: "none" });
515
- }
516
- function closeWordEdit(editId) {
517
- return ipc().closeWordEdit?.(editId) ?? Promise.resolve();
518
- }
519
- function showReminderPopup(opts) {
520
- return ipc().showReminderPopup?.(opts) ?? Promise.resolve({ button: "", reason: "no host" });
521
- }
522
- function consumePendingMailto() {
523
- return ipc().consumePendingMailto?.() ?? Promise.resolve(null);
524
- }
525
- function aiTransform(req) {
526
- return ipc().aiTransform?.(req) ?? Promise.resolve({ text: "", reason: "AI not available in this host" });
527
- }
528
- function setupAccount(name, email, password) {
529
- return ipc().setupAccount?.(name, email, password);
530
- }
531
- async function getAttachment(accountId, uid, attachmentId, folderId) {
532
- return ipc().getAttachment(accountId, uid, attachmentId, folderId);
533
- }
534
- async function openAttachment(accountId, uid, attachmentId, folderId) {
535
- const fn = ipc().openAttachment;
536
- return fn ? fn(accountId, uid, attachmentId, folderId) : void 0;
537
- }
538
- async function getDeviceAccounts() {
539
- return ipc().getDeviceAccounts?.() ?? [];
540
- }
541
- var cachedRelayBridge, messageListAbort, eventHandlers, storeSubs, connectWebSocket, onWsEvent;
542
- var init_api_client = __esm({
543
- "client/lib/api-client.js"() {
544
- "use strict";
545
- cachedRelayBridge = null;
546
- messageListAbort = null;
547
- eventHandlers = [];
548
- storeSubs = /* @__PURE__ */ new Map();
549
- connectWebSocket = connectEvents;
550
- onWsEvent = onEvent;
551
- }
552
- });
553
-
554
- // client/lib/rmf-tiny.js
555
- var rmf_tiny_exports = {};
556
- __export(rmf_tiny_exports, {
557
- createTinyMceEditor: () => createTinyMceEditor
558
- });
559
- async function loadTinymce(opts) {
560
- try {
561
- const mod = await import(
562
- /* @vite-ignore */
563
- "tinymce"
564
- );
565
- const tinymce = mod.default || mod;
566
- await Promise.all([
567
- import("tinymce/themes/silver").catch(() => {
568
- }),
569
- import("tinymce/icons/default").catch(() => {
570
- }),
571
- import("tinymce/models/dom").catch(() => {
572
- }),
573
- import("tinymce/plugins/paste").catch(() => {
574
- }),
575
- import("tinymce/plugins/lists").catch(() => {
576
- }),
577
- import("tinymce/plugins/link").catch(() => {
578
- }),
579
- import("tinymce/plugins/table").catch(() => {
580
- }),
581
- import("tinymce/plugins/code").catch(() => {
582
- }),
583
- import("tinymce/plugins/image").catch(() => {
584
- })
585
- ]);
586
- return tinymce;
587
- } catch {
588
- }
589
- const w = window;
590
- if (w.tinymce)
591
- return w.tinymce;
592
- if (!opts.cdnUrl) {
593
- throw new Error("rmf-tiny: tinymce not installed (npm install tinymce) and no cdnUrl supplied. See README for Android setup.");
594
- }
595
- const url = opts.apiKey && !opts.cdnUrl.includes("api-key") ? `${opts.cdnUrl}${opts.cdnUrl.includes("?") ? "&" : "?"}apiKey=${encodeURIComponent(opts.apiKey)}` : opts.cdnUrl;
596
- await new Promise((resolve, reject) => {
597
- const s = document.createElement("script");
598
- s.src = url;
599
- s.referrerPolicy = "origin";
600
- s.onload = () => resolve();
601
- s.onerror = () => reject(new Error(`rmf-tiny: failed to load TinyMCE from ${url}`));
602
- document.head.appendChild(s);
603
- });
604
- if (!w.tinymce)
605
- throw new Error("rmf-tiny: TinyMCE script loaded but window.tinymce is missing");
606
- return w.tinymce;
607
- }
608
- async function createTinyMceEditor(container2, opts = {}) {
609
- const tinymce = await loadTinymce(opts);
610
- const target = document.createElement("div");
611
- target.id = `rmf-tiny-${Math.random().toString(36).slice(2, 10)}`;
612
- target.style.cssText = "width:100%;height:100%;";
613
- container2.appendChild(target);
614
- const editor2 = await new Promise((resolve) => {
615
- tinymce.init({
616
- target,
617
- // Word-paste fidelity is the entire point of using TinyMCE here.
618
- // `paste_as_text: false` keeps formatting; powerpaste isn't in
619
- // OSS but the standard paste plugin handles Word reasonably well.
620
- // All free / OSS plugins bundled in the jsDelivr tinymce@6 script.
621
- // No premium plugins listed — listing one without an API key
622
- // triggers TinyMCE's upsell dialog on every load.
623
- plugins: "paste lists advlist link table code codesample image searchreplace autolink wordcount emoticons charmap insertdatetime quickbars nonbreaking directionality help",
624
- toolbar: [
625
- "undo redo | bold italic underline strikethrough | forecolor backcolor",
626
- "bullist numlist outdent indent | link table image code codesample | emoticons charmap | help"
627
- ].join(" | "),
628
- // Include "tools" so wordcount and searchreplace are reachable.
629
- menubar: "file edit view insert format tools",
630
- // View menu — append zoom commands. fullscreen omitted: this editor
631
- // lives inside a compose iframe overlay that already fills its host
632
- // window; TinyMCE's fullscreen plugin re-positions the container in
633
- // ways that produce a blank body, so it's not in the plugin list.
634
- menu: {
635
- view: { title: "View", items: "code | visualaid visualchars visualblocks | preview | zoomIn zoomOut zoomReset" }
636
- },
637
- // Disable the "Get all features" upsell badge that TinyMCE 6+
638
- // injects automatically when no premium plugins are listed.
639
- promotion: false,
640
- // Floating toolbar that appears on text selection — keeps the
641
- // main toolbar uncluttered while exposing common formatters
642
- // where the cursor already is. quickbars_insert_toolbar:false
643
- // suppresses the second (insert-on-blank-line) popup which
644
- // clutters more than it helps in an email composer.
645
- quickbars_selection_toolbar: "bold italic underline | forecolor | quicklink blockquote",
646
- quickbars_insert_toolbar: false,
647
- quickbars_image_toolbar: "alignleft aligncenter alignright",
648
- // Code-sample dropdown languages. TinyMCE's default list omits
649
- // "Text" / plain — every option triggers syntax highlighting
650
- // which mangles unrelated paste content (Bob 2026-05-24).
651
- // Adding Text first so it's the default; rest are the modern
652
- // languages we actually paste.
653
- codesample_languages: [
654
- { text: "Text", value: "text" },
655
- { text: "HTML/XML", value: "markup" },
656
- { text: "JavaScript", value: "javascript" },
657
- { text: "TypeScript", value: "typescript" },
658
- { text: "CSS", value: "css" },
659
- { text: "JSON", value: "json" },
660
- { text: "Python", value: "python" },
661
- { text: "Java", value: "java" },
662
- { text: "C", value: "c" },
663
- { text: "C++", value: "cpp" },
664
- { text: "C#", value: "csharp" },
665
- { text: "Go", value: "go" },
666
- { text: "Rust", value: "rust" },
667
- { text: "Ruby", value: "ruby" },
668
- { text: "PHP", value: "php" },
669
- { text: "Shell", value: "bash" },
670
- { text: "SQL", value: "sql" }
671
- ],
672
- // WebView's native spell-check (red underlines, right-click
673
- // "Add to dictionary"). Free; same UX as Quill's spellcheck=true.
674
- // Premium tinymcespellchecker plugin would replace this with a
675
- // custom backend via spellchecker_rpc_url, but requires a
676
- // Tiny Cloud subscription.
677
- browser_spellcheck: true,
678
- // Right-click context menu DISABLED so WebView2's native menu
679
- // fires instead. We need the native menu because that's where
680
- // the browser shows spelling suggestions (TinyMCE's contextmenu
681
- // intercepts the right-click and replaces the menu — red
682
- // underlines appear but suggestions are unreachable). Trade-off:
683
- // lose TinyMCE's link / image / table quick actions. Standard
684
- // text formatting is still on the toolbar.
685
- contextmenu: false,
686
- statusbar: false,
687
- branding: false,
688
- license_key: "gpl",
689
- paste_data_images: true,
690
- // Permissive valid_elements — preserve as much of the source
691
- // formatting as possible. The default is more aggressive about
692
- // stripping. Empty string for `valid_elements` means accept
693
- // everything that the schema allows.
694
- 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]",
695
- 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",
696
- // Auto-link bare URLs in pasted content. TinyMCE's `autolink`
697
- // plugin only fires on TYPED space/enter; URLs that arrive via
698
- // clipboard (browser address bar, terminal copy) come in as
699
- // plain text and stay un-linked. paste_preprocess runs on the
700
- // HTML the paste plugin produced.
701
- //
702
- // CRITICAL: skip auto-link when the content already contains
703
- // anchors. Naive regex over the whole content would wrap
704
- // `<a href="X">X</a>` in ANOTHER anchor (nested anchors are
705
- // invalid HTML and browsers split them, producing visible
706
- // junk). For HTML pastes that already have linked URLs (the
707
- // common case from a browser address bar or another mail
708
- // client), TinyMCE preserves them — no auto-link pass needed.
709
- // For plain-text pastes (no anchors present), wrap any bare
710
- // http(s)://… runs. Trailing sentence punctuation is excluded
711
- // from the URL.
712
- paste_preprocess: (_plugin, args) => {
713
- if (/<a[\s>]/i.test(args.content))
714
- return;
715
- args.content = args.content.replace(/(^|[\s(\[])((?:https?|ftp):\/\/[^\s<>"']+[^\s<>"'.,;:!?)\]])/gi, (_m, lead, url) => `${lead}<a href="${url}">${url}</a>`);
716
- },
717
- // Body font + quoted-reply styling. Without explicit rules for
718
- // <blockquote> and div.reply the editor iframe renders the
719
- // quoted block with no visual distinction from the user's own
720
- // text — pasted reply quotes vanish into a wall of unformatted
721
- // paragraphs (Bob 2026-05-12: "the body losing all formatting").
722
- // The styles below match Thunderbird/Outlook conventions: a
723
- // muted left-bar + indent on blockquotes, slight color shift on
724
- // the "On … wrote:" attribution, untouched typography for
725
- // everything else so genuine HTML formatting (bold / italic /
726
- // tables / inline color) still comes through verbatim.
727
- content_style: [
728
- // Dark-blue native caret bar. caret-shape:block produced a
729
- // column wider than the gap between letters AND caused the
730
- // caret to "scoot back to its old position" after an arrow
731
- // press (Chromium block-caret quirk with the arrow-key
732
- // selection adjustment, Bob 2026-05-24). Plain bar is
733
- // browser-default and behaves correctly with arrow keys;
734
- // just darken the color so it stays visible.
735
- "body { font-family: system-ui, sans-serif; font-size: 14px; caret-color: #0a2647; }",
736
- "blockquote { border-left: 3px solid #c0c8d0; margin: 0 0 0 4px; padding: 2px 0 2px 10px; color: #555; }",
737
- "div.reply { margin-top: 0.5em; }",
738
- "div.reply > p:first-child { color: #666; font-size: 0.95em; margin: 0 0 4px 0; }",
739
- "pre, code { font-family: ui-monospace, Consolas, Menlo, monospace; }"
740
- ].join(" "),
741
- init_instance_callback: (ed) => resolve(ed),
742
- setup: (ed) => {
743
- if (opts.initialHtml)
744
- ed.on("init", () => ed.setContent(opts.initialHtml));
745
- const ZOOM_DEFAULT = 14;
746
- const ZOOM_STEP = 2;
747
- const ZOOM_MIN = 8;
748
- const ZOOM_MAX = 32;
749
- const ZOOM_STORAGE_KEY = "rmf-tiny:zoom-px";
750
- let zoomPx = ZOOM_DEFAULT;
751
- try {
752
- const stored = Number(localStorage.getItem(ZOOM_STORAGE_KEY));
753
- if (Number.isFinite(stored) && stored >= ZOOM_MIN && stored <= ZOOM_MAX) {
754
- zoomPx = stored;
755
- }
756
- } catch {
757
- }
758
- const saveZoom = () => {
759
- try {
760
- localStorage.setItem(ZOOM_STORAGE_KEY, String(zoomPx));
761
- } catch {
762
- }
763
- };
764
- const applyZoom = () => {
765
- try {
766
- const body = ed.getBody();
767
- if (body)
768
- body.style.fontSize = `${zoomPx}px`;
769
- } catch {
64
+ });
65
+
66
+ // node_modules/nspell/lib/util/affix.js
67
+ var require_affix = __commonJS({
68
+ "node_modules/nspell/lib/util/affix.js"(exports, module) {
69
+ "use strict";
70
+ var parse = require_rule_codes();
71
+ module.exports = affix;
72
+ var push = [].push;
73
+ var alphabet = "etaoinshrdlcumwfgypbvkjxqz".split("");
74
+ var whiteSpaceExpression = /\s+/;
75
+ var defaultKeyboardLayout = [
76
+ "qwertzuop",
77
+ "yxcvbnm",
78
+ "qaw",
79
+ "say",
80
+ "wse",
81
+ "dsx",
82
+ "sy",
83
+ "edr",
84
+ "fdc",
85
+ "dx",
86
+ "rft",
87
+ "gfv",
88
+ "fc",
89
+ "tgz",
90
+ "hgb",
91
+ "gv",
92
+ "zhu",
93
+ "jhn",
94
+ "hb",
95
+ "uji",
96
+ "kjm",
97
+ "jn",
98
+ "iko",
99
+ "lkm"
100
+ ];
101
+ function affix(doc) {
102
+ var rules = /* @__PURE__ */ Object.create(null);
103
+ var compoundRuleCodes = /* @__PURE__ */ Object.create(null);
104
+ var flags = /* @__PURE__ */ Object.create(null);
105
+ var replacementTable = [];
106
+ var conversion = { in: [], out: [] };
107
+ var compoundRules = [];
108
+ var aff = doc.toString("utf8");
109
+ var lines = [];
110
+ var last = 0;
111
+ var index = aff.indexOf("\n");
112
+ var parts;
113
+ var line;
114
+ var ruleType;
115
+ var count;
116
+ var remove;
117
+ var add;
118
+ var source;
119
+ var entry;
120
+ var position;
121
+ var rule;
122
+ var value;
123
+ var offset;
124
+ var character;
125
+ flags.KEY = [];
126
+ while (index > -1) {
127
+ pushLine(aff.slice(last, index));
128
+ last = index + 1;
129
+ index = aff.indexOf("\n", last);
130
+ }
131
+ pushLine(aff.slice(last));
132
+ index = -1;
133
+ while (++index < lines.length) {
134
+ line = lines[index];
135
+ parts = line.split(whiteSpaceExpression);
136
+ ruleType = parts[0];
137
+ if (ruleType === "REP") {
138
+ count = index + parseInt(parts[1], 10);
139
+ while (++index <= count) {
140
+ parts = lines[index].split(whiteSpaceExpression);
141
+ replacementTable.push([parts[1], parts[2]]);
770
142
  }
771
- };
772
- const bumpZoom = (delta) => {
773
- zoomPx = Math.max(ZOOM_MIN, Math.min(ZOOM_MAX, zoomPx + delta));
774
- applyZoom();
775
- saveZoom();
776
- };
777
- ed.ui.registry.addMenuItem("zoomIn", {
778
- text: "Zoom in",
779
- shortcut: "Ctrl+=",
780
- onAction: () => bumpZoom(ZOOM_STEP)
781
- });
782
- ed.ui.registry.addMenuItem("zoomOut", {
783
- text: "Zoom out",
784
- shortcut: "Ctrl+-",
785
- onAction: () => bumpZoom(-ZOOM_STEP)
786
- });
787
- ed.ui.registry.addMenuItem("zoomReset", {
788
- text: "Reset zoom",
789
- shortcut: "Ctrl+0",
790
- onAction: () => {
791
- zoomPx = ZOOM_DEFAULT;
792
- applyZoom();
793
- saveZoom();
143
+ index--;
144
+ } else if (ruleType === "ICONV" || ruleType === "OCONV") {
145
+ count = index + parseInt(parts[1], 10);
146
+ entry = conversion[ruleType === "ICONV" ? "in" : "out"];
147
+ while (++index <= count) {
148
+ parts = lines[index].split(whiteSpaceExpression);
149
+ entry.push([new RegExp(parts[1], "g"), parts[2]]);
794
150
  }
795
- });
796
- const installLinkEscape = () => {
797
- const doc = ed.getDoc();
798
- if (!doc || doc.__rmfLinkEscapeInstalled)
799
- return;
800
- doc.__rmfLinkEscapeInstalled = true;
801
- doc.addEventListener("beforeinput", (e) => {
802
- const kind = e.inputType || "";
803
- const isInsert = kind === "insertText" || kind === "insertCompositionText" || kind === "insertFromPaste" || kind === "insertFromDrop" || kind === "insertFromComposition";
804
- if (!isInsert)
805
- return;
806
- const sel = doc.getSelection();
807
- if (!sel || sel.rangeCount === 0)
808
- return;
809
- const rng = sel.getRangeAt(0);
810
- if (!rng.collapsed)
811
- return;
812
- const node = rng.startContainer;
813
- const a = node.nodeType === Node.ELEMENT_NODE ? node : node.parentNode;
814
- if (!a)
815
- return;
816
- const link = a.closest?.("a");
817
- if (!link)
818
- return;
819
- let tail;
151
+ index--;
152
+ } else if (ruleType === "COMPOUNDRULE") {
153
+ count = index + parseInt(parts[1], 10);
154
+ while (++index <= count) {
155
+ rule = lines[index].split(whiteSpaceExpression)[1];
156
+ position = -1;
157
+ compoundRules.push(rule);
158
+ while (++position < rule.length) {
159
+ compoundRuleCodes[rule.charAt(position)] = [];
160
+ }
161
+ }
162
+ index--;
163
+ } else if (ruleType === "PFX" || ruleType === "SFX") {
164
+ count = index + parseInt(parts[3], 10);
165
+ rule = {
166
+ type: ruleType,
167
+ combineable: parts[2] === "Y",
168
+ entries: []
169
+ };
170
+ rules[parts[1]] = rule;
171
+ while (++index <= count) {
172
+ parts = lines[index].split(whiteSpaceExpression);
173
+ remove = parts[2];
174
+ add = parts[3].split("/");
175
+ source = parts[4];
176
+ entry = {
177
+ add: "",
178
+ remove: "",
179
+ match: "",
180
+ continuation: parse(flags, add[1])
181
+ };
182
+ if (add && add[0] !== "0") {
183
+ entry.add = add[0];
184
+ }
820
185
  try {
821
- tail = rng.cloneRange();
822
- tail.setEndAfter(link);
823
- } catch {
824
- return;
186
+ if (remove !== "0") {
187
+ entry.remove = ruleType === "SFX" ? end(remove) : remove;
188
+ }
189
+ if (source && source !== ".") {
190
+ entry.match = ruleType === "SFX" ? end(source) : start(source);
191
+ }
192
+ } catch (_) {
193
+ entry = null;
825
194
  }
826
- if (tail.toString().length > 0)
827
- return;
828
- const after = doc.createRange();
829
- after.setStartAfter(link);
830
- after.collapse(true);
831
- sel.removeAllRanges();
832
- sel.addRange(after);
833
- }, true);
834
- };
835
- ed.on("init", installLinkEscape);
836
- ed.on("SetContent", installLinkEscape);
837
- ed.on("init", () => {
838
- try {
839
- const body = ed.getBody();
840
- if (body) {
841
- body.setAttribute("spellcheck", "true");
842
- body.setAttribute("lang", "en");
195
+ if (entry) {
196
+ rule.entries.push(entry);
843
197
  }
844
- const doc = ed.getDoc();
845
- if (doc?.documentElement)
846
- doc.documentElement.setAttribute("lang", "en");
847
- } catch {
848
198
  }
849
- if (zoomPx !== ZOOM_DEFAULT)
850
- applyZoom();
851
- try {
852
- const doc = ed.getDoc();
853
- doc.addEventListener("wheel", (e) => {
854
- if (!e.ctrlKey)
855
- return;
856
- e.preventDefault();
857
- bumpZoom(e.deltaY < 0 ? ZOOM_STEP : -ZOOM_STEP);
858
- }, { passive: false });
859
- doc.addEventListener("keydown", (e) => {
860
- if (!(e.ctrlKey || e.metaKey))
861
- return;
862
- if (e.key === "=" || e.key === "+") {
863
- e.preventDefault();
864
- e.stopPropagation();
865
- bumpZoom(ZOOM_STEP);
866
- } else if (e.key === "-") {
867
- e.preventDefault();
868
- e.stopPropagation();
869
- bumpZoom(-ZOOM_STEP);
870
- } else if (e.key === "0") {
871
- e.preventDefault();
872
- e.stopPropagation();
873
- zoomPx = ZOOM_DEFAULT;
874
- applyZoom();
875
- }
876
- }, true);
877
- } catch {
199
+ index--;
200
+ } else if (ruleType === "TRY") {
201
+ source = parts[1];
202
+ offset = -1;
203
+ value = [];
204
+ while (++offset < source.length) {
205
+ character = source.charAt(offset);
206
+ if (character.toLowerCase() === character) {
207
+ value.push(character);
208
+ }
878
209
  }
879
- });
880
- }
881
- });
882
- });
883
- return {
884
- setHtml(html) {
885
- editor2.setContent(html);
886
- },
887
- getHtml() {
888
- return editor2.getContent();
889
- },
890
- getText() {
891
- return editor2.getContent({ format: "text" });
892
- },
893
- focus() {
894
- editor2.focus();
895
- },
896
- setCursor(pos) {
897
- const place = () => {
898
- const body = editor2.getBody();
899
- if (pos === 0) {
900
- const first = body.firstChild;
901
- if (first && first.nodeType === 1) {
902
- editor2.selection.setCursorLocation(first, 0);
903
- } else {
904
- editor2.selection.select(body, true);
905
- editor2.selection.collapse(true);
210
+ offset = -1;
211
+ while (++offset < alphabet.length) {
212
+ if (source.indexOf(alphabet[offset]) < 0) {
213
+ value.push(alphabet[offset]);
214
+ }
906
215
  }
907
- editor2.focus();
908
- editor2.getWin()?.scrollTo(0, 0);
216
+ flags[ruleType] = value;
217
+ } else if (ruleType === "KEY") {
218
+ push.apply(flags[ruleType], parts[1].split("|"));
219
+ } else if (ruleType === "COMPOUNDMIN") {
220
+ flags[ruleType] = Number(parts[1]);
221
+ } else if (ruleType === "ONLYINCOMPOUND") {
222
+ flags[ruleType] = parts[1];
223
+ compoundRuleCodes[parts[1]] = [];
224
+ } else if (ruleType === "FLAG" || ruleType === "KEEPCASE" || ruleType === "NOSUGGEST" || ruleType === "WORDCHARS") {
225
+ flags[ruleType] = parts[1];
909
226
  } else {
910
- editor2.selection.select(body, true);
911
- editor2.selection.collapse(false);
912
- editor2.focus();
913
- editor2.selection.scrollIntoView();
227
+ flags[ruleType] = parts[1];
228
+ }
229
+ }
230
+ if (isNaN(flags.COMPOUNDMIN)) {
231
+ flags.COMPOUNDMIN = 3;
232
+ }
233
+ if (!flags.KEY.length) {
234
+ flags.KEY = defaultKeyboardLayout;
235
+ }
236
+ if (!flags.TRY) {
237
+ flags.TRY = alphabet.concat();
238
+ }
239
+ if (!flags.KEEPCASE) {
240
+ flags.KEEPCASE = false;
241
+ }
242
+ return {
243
+ compoundRuleCodes,
244
+ replacementTable,
245
+ conversion,
246
+ compoundRules,
247
+ rules,
248
+ flags
249
+ };
250
+ function pushLine(line2) {
251
+ line2 = line2.trim();
252
+ if (line2 && line2.charCodeAt(0) !== 35) {
253
+ lines.push(line2);
254
+ }
255
+ }
256
+ }
257
+ function end(source) {
258
+ return new RegExp(source + "$");
259
+ }
260
+ function start(source) {
261
+ return new RegExp("^" + source);
262
+ }
263
+ }
264
+ });
265
+
266
+ // node_modules/nspell/lib/util/normalize.js
267
+ var require_normalize = __commonJS({
268
+ "node_modules/nspell/lib/util/normalize.js"(exports, module) {
269
+ "use strict";
270
+ module.exports = normalize;
271
+ function normalize(value, patterns) {
272
+ var index = -1;
273
+ while (++index < patterns.length) {
274
+ value = value.replace(patterns[index][0], patterns[index][1]);
275
+ }
276
+ return value;
277
+ }
278
+ }
279
+ });
280
+
281
+ // node_modules/nspell/lib/util/flag.js
282
+ var require_flag = __commonJS({
283
+ "node_modules/nspell/lib/util/flag.js"(exports, module) {
284
+ "use strict";
285
+ module.exports = flag;
286
+ function flag(values, value, flags) {
287
+ return flags && value in values && flags.indexOf(values[value]) > -1;
288
+ }
289
+ }
290
+ });
291
+
292
+ // node_modules/nspell/lib/util/exact.js
293
+ var require_exact = __commonJS({
294
+ "node_modules/nspell/lib/util/exact.js"(exports, module) {
295
+ "use strict";
296
+ var flag = require_flag();
297
+ module.exports = exact;
298
+ function exact(context, value) {
299
+ var index = -1;
300
+ if (context.data[value]) {
301
+ return !flag(context.flags, "ONLYINCOMPOUND", context.data[value]);
302
+ }
303
+ if (value.length >= context.flags.COMPOUNDMIN) {
304
+ while (++index < context.compoundRules.length) {
305
+ if (context.compoundRules[index].test(value)) {
306
+ return true;
307
+ }
308
+ }
309
+ }
310
+ return false;
311
+ }
312
+ }
313
+ });
314
+
315
+ // node_modules/nspell/lib/util/form.js
316
+ var require_form = __commonJS({
317
+ "node_modules/nspell/lib/util/form.js"(exports, module) {
318
+ "use strict";
319
+ var normalize = require_normalize();
320
+ var exact = require_exact();
321
+ var flag = require_flag();
322
+ module.exports = form;
323
+ function form(context, value, all) {
324
+ var normal = value.trim();
325
+ var alternative;
326
+ if (!normal) {
327
+ return null;
328
+ }
329
+ normal = normalize(normal, context.conversion.in);
330
+ if (exact(context, normal)) {
331
+ if (!all && flag(context.flags, "FORBIDDENWORD", context.data[normal])) {
332
+ return null;
333
+ }
334
+ return normal;
335
+ }
336
+ if (normal.toUpperCase() === normal) {
337
+ alternative = normal.charAt(0) + normal.slice(1).toLowerCase();
338
+ if (ignore(context.flags, context.data[alternative], all)) {
339
+ return null;
340
+ }
341
+ if (exact(context, alternative)) {
342
+ return alternative;
914
343
  }
915
- };
916
- try {
917
- place();
918
- editor2.getWin()?.requestAnimationFrame?.(() => {
919
- try {
920
- place();
921
- } catch {
922
- }
923
- });
924
- } catch {
925
344
  }
926
- },
927
- get root() {
928
- return editor2.getContainer();
929
- },
930
- on(event, handler) {
931
- editor2.on(event, handler);
932
- },
933
- off(event, handler) {
934
- editor2.off(event, handler);
935
- },
936
- nativeEditor: editor2
937
- };
938
- }
939
- var init_rmf_tiny = __esm({
940
- "client/lib/rmf-tiny.js"() {
345
+ alternative = normal.toLowerCase();
346
+ if (alternative !== normal) {
347
+ if (ignore(context.flags, context.data[alternative], all)) {
348
+ return null;
349
+ }
350
+ if (exact(context, alternative)) {
351
+ return alternative;
352
+ }
353
+ }
354
+ return null;
355
+ }
356
+ function ignore(flags, dict, all) {
357
+ return flag(flags, "KEEPCASE", dict) || all || flag(flags, "FORBIDDENWORD", dict);
358
+ }
359
+ }
360
+ });
361
+
362
+ // node_modules/nspell/lib/correct.js
363
+ var require_correct = __commonJS({
364
+ "node_modules/nspell/lib/correct.js"(exports, module) {
941
365
  "use strict";
366
+ var form = require_form();
367
+ module.exports = correct;
368
+ function correct(value) {
369
+ return Boolean(form(this, value));
370
+ }
942
371
  }
943
372
  });
944
373
 
945
- // node_modules/is-buffer/index.js
946
- var require_is_buffer = __commonJS({
947
- "node_modules/is-buffer/index.js"(exports, module) {
948
- module.exports = function isBuffer(obj) {
949
- return obj != null && obj.constructor != null && typeof obj.constructor.isBuffer === "function" && obj.constructor.isBuffer(obj);
950
- };
374
+ // node_modules/nspell/lib/util/casing.js
375
+ var require_casing = __commonJS({
376
+ "node_modules/nspell/lib/util/casing.js"(exports, module) {
377
+ "use strict";
378
+ module.exports = casing;
379
+ function casing(value) {
380
+ var head = exact(value.charAt(0));
381
+ var rest = value.slice(1);
382
+ if (!rest) {
383
+ return head;
384
+ }
385
+ rest = exact(rest);
386
+ if (head === rest) {
387
+ return head;
388
+ }
389
+ if (head === "u" && rest === "l") {
390
+ return "s";
391
+ }
392
+ return null;
393
+ }
394
+ function exact(value) {
395
+ return value === value.toLowerCase() ? "l" : value === value.toUpperCase() ? "u" : null;
396
+ }
951
397
  }
952
398
  });
953
399
 
954
- // node_modules/nspell/lib/util/rule-codes.js
955
- var require_rule_codes = __commonJS({
956
- "node_modules/nspell/lib/util/rule-codes.js"(exports, module) {
400
+ // node_modules/nspell/lib/suggest.js
401
+ var require_suggest = __commonJS({
402
+ "node_modules/nspell/lib/suggest.js"(exports, module) {
957
403
  "use strict";
958
- module.exports = ruleCodes;
959
- var NO_CODES = [];
960
- function ruleCodes(flags, value) {
961
- var index = 0;
962
- var result;
963
- if (!value) return NO_CODES;
964
- if (flags.FLAG === "long") {
965
- result = new Array(Math.ceil(value.length / 2));
966
- while (index < value.length) {
967
- result[index / 2] = value.slice(index, index + 2);
968
- index += 2;
404
+ var casing = require_casing();
405
+ var normalize = require_normalize();
406
+ var flag = require_flag();
407
+ var form = require_form();
408
+ module.exports = suggest;
409
+ var push = [].push;
410
+ function suggest(value) {
411
+ var self = this;
412
+ var charAdded = {};
413
+ var suggestions = [];
414
+ var weighted = {};
415
+ var memory;
416
+ var replacement;
417
+ var edits = [];
418
+ var values;
419
+ var index;
420
+ var offset;
421
+ var position;
422
+ var count;
423
+ var otherOffset;
424
+ var otherCharacter;
425
+ var character;
426
+ var group;
427
+ var before;
428
+ var after;
429
+ var upper;
430
+ var insensitive;
431
+ var firstLevel;
432
+ var previous;
433
+ var next;
434
+ var nextCharacter;
435
+ var max;
436
+ var distance;
437
+ var size;
438
+ var normalized;
439
+ var suggestion;
440
+ var currentCase;
441
+ value = normalize(value.trim(), self.conversion.in);
442
+ if (!value || self.correct(value)) {
443
+ return [];
444
+ }
445
+ currentCase = casing(value);
446
+ index = -1;
447
+ while (++index < self.replacementTable.length) {
448
+ replacement = self.replacementTable[index];
449
+ offset = value.indexOf(replacement[0]);
450
+ while (offset > -1) {
451
+ edits.push(value.replace(replacement[0], replacement[1]));
452
+ offset = value.indexOf(replacement[0], offset + 1);
969
453
  }
970
- return result;
971
454
  }
972
- return value.split(flags.FLAG === "num" ? "," : "");
455
+ index = -1;
456
+ while (++index < value.length) {
457
+ character = value.charAt(index);
458
+ before = value.slice(0, index);
459
+ after = value.slice(index + 1);
460
+ insensitive = character.toLowerCase();
461
+ upper = insensitive !== character;
462
+ charAdded = {};
463
+ offset = -1;
464
+ while (++offset < self.flags.KEY.length) {
465
+ group = self.flags.KEY[offset];
466
+ position = group.indexOf(insensitive);
467
+ if (position < 0) {
468
+ continue;
469
+ }
470
+ otherOffset = -1;
471
+ while (++otherOffset < group.length) {
472
+ if (otherOffset !== position) {
473
+ otherCharacter = group.charAt(otherOffset);
474
+ if (charAdded[otherCharacter]) {
475
+ continue;
476
+ }
477
+ charAdded[otherCharacter] = true;
478
+ if (upper) {
479
+ otherCharacter = otherCharacter.toUpperCase();
480
+ }
481
+ edits.push(before + otherCharacter + after);
482
+ }
483
+ }
484
+ }
485
+ }
486
+ index = -1;
487
+ nextCharacter = value.charAt(0);
488
+ values = [""];
489
+ max = 1;
490
+ distance = 0;
491
+ while (++index < value.length) {
492
+ character = nextCharacter;
493
+ nextCharacter = value.charAt(index + 1);
494
+ before = value.slice(0, index);
495
+ replacement = character === nextCharacter ? "" : character + character;
496
+ offset = -1;
497
+ count = values.length;
498
+ while (++offset < count) {
499
+ if (offset <= max) {
500
+ values.push(values[offset] + replacement);
501
+ }
502
+ values[offset] += character;
503
+ }
504
+ if (++distance < 3) {
505
+ max = values.length;
506
+ }
507
+ }
508
+ push.apply(edits, values);
509
+ values = [value];
510
+ replacement = value.toLowerCase();
511
+ if (value === replacement || currentCase === null) {
512
+ values.push(value.charAt(0).toUpperCase() + replacement.slice(1));
513
+ }
514
+ replacement = value.toUpperCase();
515
+ if (value !== replacement) {
516
+ values.push(replacement);
517
+ }
518
+ memory = {
519
+ state: {},
520
+ weighted,
521
+ suggestions
522
+ };
523
+ firstLevel = generate(self, memory, values, edits);
524
+ previous = 0;
525
+ max = Math.min(firstLevel.length, Math.pow(Math.max(15 - value.length, 3), 3));
526
+ size = Math.max(Math.pow(10 - value.length, 3), 1);
527
+ while (!suggestions.length && previous < max) {
528
+ next = previous + size;
529
+ generate(self, memory, firstLevel.slice(previous, next));
530
+ previous = next;
531
+ }
532
+ suggestions.sort(sort);
533
+ values = [];
534
+ normalized = [];
535
+ index = -1;
536
+ while (++index < suggestions.length) {
537
+ suggestion = normalize(suggestions[index], self.conversion.out);
538
+ replacement = suggestion.toLowerCase();
539
+ if (normalized.indexOf(replacement) < 0) {
540
+ values.push(suggestion);
541
+ normalized.push(replacement);
542
+ }
543
+ }
544
+ return values;
545
+ function sort(a, b) {
546
+ return sortWeight(a, b) || sortCasing(a, b) || sortAlpha(a, b);
547
+ }
548
+ function sortWeight(a, b) {
549
+ return weighted[a] === weighted[b] ? 0 : weighted[a] > weighted[b] ? -1 : 1;
550
+ }
551
+ function sortCasing(a, b) {
552
+ var leftCasing = casing(a);
553
+ var rightCasing = casing(b);
554
+ return leftCasing === rightCasing ? 0 : leftCasing === currentCase ? -1 : rightCasing === currentCase ? 1 : void 0;
555
+ }
556
+ function sortAlpha(a, b) {
557
+ return a.localeCompare(b);
558
+ }
973
559
  }
974
- }
975
- });
976
-
977
- // node_modules/nspell/lib/util/affix.js
978
- var require_affix = __commonJS({
979
- "node_modules/nspell/lib/util/affix.js"(exports, module) {
980
- "use strict";
981
- var parse = require_rule_codes();
982
- module.exports = affix;
983
- var push = [].push;
984
- var alphabet = "etaoinshrdlcumwfgypbvkjxqz".split("");
985
- var whiteSpaceExpression = /\s+/;
986
- var defaultKeyboardLayout = [
987
- "qwertzuop",
988
- "yxcvbnm",
989
- "qaw",
990
- "say",
991
- "wse",
992
- "dsx",
993
- "sy",
994
- "edr",
995
- "fdc",
996
- "dx",
997
- "rft",
998
- "gfv",
999
- "fc",
1000
- "tgz",
1001
- "hgb",
1002
- "gv",
1003
- "zhu",
1004
- "jhn",
1005
- "hb",
1006
- "uji",
1007
- "kjm",
1008
- "jn",
1009
- "iko",
1010
- "lkm"
1011
- ];
1012
- function affix(doc) {
1013
- var rules = /* @__PURE__ */ Object.create(null);
1014
- var compoundRuleCodes = /* @__PURE__ */ Object.create(null);
1015
- var flags = /* @__PURE__ */ Object.create(null);
1016
- var replacementTable = [];
1017
- var conversion = { in: [], out: [] };
1018
- var compoundRules = [];
1019
- var aff = doc.toString("utf8");
1020
- var lines = [];
1021
- var last = 0;
1022
- var index = aff.indexOf("\n");
1023
- var parts;
1024
- var line;
1025
- var ruleType;
1026
- var count;
1027
- var remove;
1028
- var add;
1029
- var source;
1030
- var entry;
560
+ function generate(context, memory, words, edits) {
561
+ var characters = context.flags.TRY;
562
+ var data = context.data;
563
+ var flags = context.flags;
564
+ var result = [];
565
+ var index = -1;
566
+ var word;
567
+ var before;
568
+ var character;
569
+ var nextCharacter;
570
+ var nextAfter;
571
+ var nextNextAfter;
572
+ var nextUpper;
573
+ var currentCase;
1031
574
  var position;
1032
- var rule;
1033
- var value;
575
+ var after;
576
+ var upper;
577
+ var inject;
1034
578
  var offset;
1035
- var character;
1036
- flags.KEY = [];
1037
- while (index > -1) {
1038
- pushLine(aff.slice(last, index));
1039
- last = index + 1;
1040
- index = aff.indexOf("\n", last);
579
+ if (edits) {
580
+ while (++index < edits.length) {
581
+ check(edits[index], true);
582
+ }
1041
583
  }
1042
- pushLine(aff.slice(last));
1043
584
  index = -1;
1044
- while (++index < lines.length) {
1045
- line = lines[index];
1046
- parts = line.split(whiteSpaceExpression);
1047
- ruleType = parts[0];
1048
- if (ruleType === "REP") {
1049
- count = index + parseInt(parts[1], 10);
1050
- while (++index <= count) {
1051
- parts = lines[index].split(whiteSpaceExpression);
1052
- replacementTable.push([parts[1], parts[2]]);
585
+ while (++index < words.length) {
586
+ word = words[index];
587
+ before = "";
588
+ character = "";
589
+ nextCharacter = word.charAt(0);
590
+ nextAfter = word;
591
+ nextNextAfter = word.slice(1);
592
+ nextUpper = nextCharacter.toLowerCase() !== nextCharacter;
593
+ currentCase = casing(word);
594
+ position = -1;
595
+ while (++position <= word.length) {
596
+ before += character;
597
+ after = nextAfter;
598
+ nextAfter = nextNextAfter;
599
+ nextNextAfter = nextAfter.slice(1);
600
+ character = nextCharacter;
601
+ nextCharacter = word.charAt(position + 1);
602
+ upper = nextUpper;
603
+ if (nextCharacter) {
604
+ nextUpper = nextCharacter.toLowerCase() !== nextCharacter;
1053
605
  }
1054
- index--;
1055
- } else if (ruleType === "ICONV" || ruleType === "OCONV") {
1056
- count = index + parseInt(parts[1], 10);
1057
- entry = conversion[ruleType === "ICONV" ? "in" : "out"];
1058
- while (++index <= count) {
1059
- parts = lines[index].split(whiteSpaceExpression);
1060
- entry.push([new RegExp(parts[1], "g"), parts[2]]);
606
+ if (nextAfter && upper !== nextUpper) {
607
+ check(before + switchCase(nextAfter));
608
+ check(
609
+ before + switchCase(nextCharacter) + switchCase(character) + nextNextAfter
610
+ );
1061
611
  }
1062
- index--;
1063
- } else if (ruleType === "COMPOUNDRULE") {
1064
- count = index + parseInt(parts[1], 10);
1065
- while (++index <= count) {
1066
- rule = lines[index].split(whiteSpaceExpression)[1];
1067
- position = -1;
1068
- compoundRules.push(rule);
1069
- while (++position < rule.length) {
1070
- compoundRuleCodes[rule.charAt(position)] = [];
1071
- }
612
+ check(before + nextAfter);
613
+ if (nextAfter) {
614
+ check(before + nextCharacter + character + nextNextAfter);
1072
615
  }
1073
- index--;
1074
- } else if (ruleType === "PFX" || ruleType === "SFX") {
1075
- count = index + parseInt(parts[3], 10);
1076
- rule = {
1077
- type: ruleType,
1078
- combineable: parts[2] === "Y",
1079
- entries: []
1080
- };
1081
- rules[parts[1]] = rule;
1082
- while (++index <= count) {
1083
- parts = lines[index].split(whiteSpaceExpression);
1084
- remove = parts[2];
1085
- add = parts[3].split("/");
1086
- source = parts[4];
1087
- entry = {
1088
- add: "",
1089
- remove: "",
1090
- match: "",
1091
- continuation: parse(flags, add[1])
1092
- };
1093
- if (add && add[0] !== "0") {
1094
- entry.add = add[0];
1095
- }
1096
- try {
1097
- if (remove !== "0") {
1098
- entry.remove = ruleType === "SFX" ? end(remove) : remove;
1099
- }
1100
- if (source && source !== ".") {
1101
- entry.match = ruleType === "SFX" ? end(source) : start(source);
616
+ offset = -1;
617
+ while (++offset < characters.length) {
618
+ inject = characters[offset];
619
+ if (upper && inject !== inject.toUpperCase()) {
620
+ if (currentCase !== "s") {
621
+ check(before + inject + after);
622
+ check(before + inject + nextAfter);
1102
623
  }
1103
- } catch (_) {
1104
- entry = null;
1105
- }
1106
- if (entry) {
1107
- rule.entries.push(entry);
624
+ inject = inject.toUpperCase();
625
+ check(before + inject + after);
626
+ check(before + inject + nextAfter);
627
+ } else {
628
+ check(before + inject + after);
629
+ check(before + inject + nextAfter);
1108
630
  }
1109
631
  }
1110
- index--;
1111
- } else if (ruleType === "TRY") {
1112
- source = parts[1];
1113
- offset = -1;
1114
- value = [];
1115
- while (++offset < source.length) {
1116
- character = source.charAt(offset);
1117
- if (character.toLowerCase() === character) {
1118
- value.push(character);
1119
- }
632
+ }
633
+ }
634
+ return result;
635
+ function check(value, double) {
636
+ var state = memory.state[value];
637
+ var corrected;
638
+ if (state !== Boolean(state)) {
639
+ result.push(value);
640
+ corrected = form(context, value);
641
+ state = corrected && !flag(flags, "NOSUGGEST", data[corrected]);
642
+ memory.state[value] = state;
643
+ if (state) {
644
+ memory.weighted[value] = double ? 10 : 0;
645
+ memory.suggestions.push(value);
1120
646
  }
1121
- offset = -1;
1122
- while (++offset < alphabet.length) {
1123
- if (source.indexOf(alphabet[offset]) < 0) {
1124
- value.push(alphabet[offset]);
647
+ }
648
+ if (state) {
649
+ memory.weighted[value]++;
650
+ }
651
+ }
652
+ function switchCase(fragment) {
653
+ var first = fragment.charAt(0);
654
+ return (first.toLowerCase() === first ? first.toUpperCase() : first.toLowerCase()) + fragment.slice(1);
655
+ }
656
+ }
657
+ }
658
+ });
659
+
660
+ // node_modules/nspell/lib/spell.js
661
+ var require_spell = __commonJS({
662
+ "node_modules/nspell/lib/spell.js"(exports, module) {
663
+ "use strict";
664
+ var form = require_form();
665
+ var flag = require_flag();
666
+ module.exports = spell;
667
+ function spell(word) {
668
+ var self = this;
669
+ var value = form(self, word, true);
670
+ return {
671
+ correct: self.correct(word),
672
+ forbidden: Boolean(
673
+ value && flag(self.flags, "FORBIDDENWORD", self.data[value])
674
+ ),
675
+ warn: Boolean(value && flag(self.flags, "WARN", self.data[value]))
676
+ };
677
+ }
678
+ }
679
+ });
680
+
681
+ // node_modules/nspell/lib/util/apply.js
682
+ var require_apply = __commonJS({
683
+ "node_modules/nspell/lib/util/apply.js"(exports, module) {
684
+ "use strict";
685
+ module.exports = apply;
686
+ function apply(value, rule, rules, words) {
687
+ var index = -1;
688
+ var entry;
689
+ var next;
690
+ var continuationRule;
691
+ var continuation;
692
+ var position;
693
+ while (++index < rule.entries.length) {
694
+ entry = rule.entries[index];
695
+ continuation = entry.continuation;
696
+ position = -1;
697
+ if (!entry.match || entry.match.test(value)) {
698
+ next = entry.remove ? value.replace(entry.remove, "") : value;
699
+ next = rule.type === "SFX" ? next + entry.add : entry.add + next;
700
+ words.push(next);
701
+ if (continuation && continuation.length) {
702
+ while (++position < continuation.length) {
703
+ continuationRule = rules[continuation[position]];
704
+ if (continuationRule) {
705
+ apply(next, continuationRule, rules, words);
706
+ }
1125
707
  }
1126
708
  }
1127
- flags[ruleType] = value;
1128
- } else if (ruleType === "KEY") {
1129
- push.apply(flags[ruleType], parts[1].split("|"));
1130
- } else if (ruleType === "COMPOUNDMIN") {
1131
- flags[ruleType] = Number(parts[1]);
1132
- } else if (ruleType === "ONLYINCOMPOUND") {
1133
- flags[ruleType] = parts[1];
1134
- compoundRuleCodes[parts[1]] = [];
1135
- } else if (ruleType === "FLAG" || ruleType === "KEEPCASE" || ruleType === "NOSUGGEST" || ruleType === "WORDCHARS") {
1136
- flags[ruleType] = parts[1];
1137
- } else {
1138
- flags[ruleType] = parts[1];
1139
709
  }
1140
710
  }
1141
- if (isNaN(flags.COMPOUNDMIN)) {
1142
- flags.COMPOUNDMIN = 3;
1143
- }
1144
- if (!flags.KEY.length) {
1145
- flags.KEY = defaultKeyboardLayout;
1146
- }
1147
- if (!flags.TRY) {
1148
- flags.TRY = alphabet.concat();
711
+ return words;
712
+ }
713
+ }
714
+ });
715
+
716
+ // node_modules/nspell/lib/util/add.js
717
+ var require_add = __commonJS({
718
+ "node_modules/nspell/lib/util/add.js"(exports, module) {
719
+ "use strict";
720
+ var apply = require_apply();
721
+ module.exports = add;
722
+ var push = [].push;
723
+ var NO_RULES = [];
724
+ function addRules(dict, word, rules) {
725
+ var curr = dict[word];
726
+ if (word in dict) {
727
+ if (curr === NO_RULES) {
728
+ dict[word] = rules.concat();
729
+ } else {
730
+ push.apply(curr, rules);
731
+ }
732
+ } else {
733
+ dict[word] = rules.concat();
1149
734
  }
1150
- if (!flags.KEEPCASE) {
1151
- flags.KEEPCASE = false;
735
+ }
736
+ function add(dict, word, codes, options) {
737
+ var position = -1;
738
+ var rule;
739
+ var offset;
740
+ var subposition;
741
+ var suboffset;
742
+ var combined;
743
+ var newWords;
744
+ var otherNewWords;
745
+ if (!("NEEDAFFIX" in options.flags) || codes.indexOf(options.flags.NEEDAFFIX) < 0) {
746
+ addRules(dict, word, codes);
1152
747
  }
1153
- return {
1154
- compoundRuleCodes,
1155
- replacementTable,
1156
- conversion,
1157
- compoundRules,
1158
- rules,
1159
- flags
1160
- };
1161
- function pushLine(line2) {
1162
- line2 = line2.trim();
1163
- if (line2 && line2.charCodeAt(0) !== 35) {
1164
- lines.push(line2);
748
+ while (++position < codes.length) {
749
+ rule = options.rules[codes[position]];
750
+ if (codes[position] in options.compoundRuleCodes) {
751
+ options.compoundRuleCodes[codes[position]].push(word);
752
+ }
753
+ if (rule) {
754
+ newWords = apply(word, rule, options.rules, []);
755
+ offset = -1;
756
+ while (++offset < newWords.length) {
757
+ if (!(newWords[offset] in dict)) {
758
+ dict[newWords[offset]] = NO_RULES;
759
+ }
760
+ if (rule.combineable) {
761
+ subposition = position;
762
+ while (++subposition < codes.length) {
763
+ combined = options.rules[codes[subposition]];
764
+ if (combined && combined.combineable && rule.type !== combined.type) {
765
+ otherNewWords = apply(
766
+ newWords[offset],
767
+ combined,
768
+ options.rules,
769
+ []
770
+ );
771
+ suboffset = -1;
772
+ while (++suboffset < otherNewWords.length) {
773
+ if (!(otherNewWords[suboffset] in dict)) {
774
+ dict[otherNewWords[suboffset]] = NO_RULES;
775
+ }
776
+ }
777
+ }
778
+ }
779
+ }
780
+ }
1165
781
  }
1166
782
  }
1167
783
  }
1168
- function end(source) {
1169
- return new RegExp(source + "$");
1170
- }
1171
- function start(source) {
1172
- return new RegExp("^" + source);
1173
- }
1174
784
  }
1175
785
  });
1176
786
 
1177
- // node_modules/nspell/lib/util/normalize.js
1178
- var require_normalize = __commonJS({
1179
- "node_modules/nspell/lib/util/normalize.js"(exports, module) {
787
+ // node_modules/nspell/lib/add.js
788
+ var require_add2 = __commonJS({
789
+ "node_modules/nspell/lib/add.js"(exports, module) {
1180
790
  "use strict";
1181
- module.exports = normalize;
1182
- function normalize(value, patterns) {
1183
- var index = -1;
1184
- while (++index < patterns.length) {
1185
- value = value.replace(patterns[index][0], patterns[index][1]);
1186
- }
1187
- return value;
791
+ var push = require_add();
792
+ module.exports = add;
793
+ var NO_CODES = [];
794
+ function add(value, model) {
795
+ var self = this;
796
+ push(self.data, value, self.data[model] || NO_CODES, self);
797
+ return self;
1188
798
  }
1189
799
  }
1190
800
  });
1191
801
 
1192
- // node_modules/nspell/lib/util/flag.js
1193
- var require_flag = __commonJS({
1194
- "node_modules/nspell/lib/util/flag.js"(exports, module) {
802
+ // node_modules/nspell/lib/remove.js
803
+ var require_remove = __commonJS({
804
+ "node_modules/nspell/lib/remove.js"(exports, module) {
1195
805
  "use strict";
1196
- module.exports = flag;
1197
- function flag(values, value, flags) {
1198
- return flags && value in values && flags.indexOf(values[value]) > -1;
806
+ module.exports = remove;
807
+ function remove(value) {
808
+ var self = this;
809
+ delete self.data[value];
810
+ return self;
1199
811
  }
1200
812
  }
1201
813
  });
1202
814
 
1203
- // node_modules/nspell/lib/util/exact.js
1204
- var require_exact = __commonJS({
1205
- "node_modules/nspell/lib/util/exact.js"(exports, module) {
815
+ // node_modules/nspell/lib/word-characters.js
816
+ var require_word_characters = __commonJS({
817
+ "node_modules/nspell/lib/word-characters.js"(exports, module) {
1206
818
  "use strict";
1207
- var flag = require_flag();
1208
- module.exports = exact;
1209
- function exact(context, value) {
1210
- var index = -1;
1211
- if (context.data[value]) {
1212
- return !flag(context.flags, "ONLYINCOMPOUND", context.data[value]);
1213
- }
1214
- if (value.length >= context.flags.COMPOUNDMIN) {
1215
- while (++index < context.compoundRules.length) {
1216
- if (context.compoundRules[index].test(value)) {
1217
- return true;
1218
- }
1219
- }
1220
- }
1221
- return false;
819
+ module.exports = wordCharacters;
820
+ function wordCharacters() {
821
+ return this.flags.WORDCHARS || null;
1222
822
  }
1223
823
  }
1224
824
  });
1225
825
 
1226
- // node_modules/nspell/lib/util/form.js
1227
- var require_form = __commonJS({
1228
- "node_modules/nspell/lib/util/form.js"(exports, module) {
826
+ // node_modules/nspell/lib/util/dictionary.js
827
+ var require_dictionary = __commonJS({
828
+ "node_modules/nspell/lib/util/dictionary.js"(exports, module) {
1229
829
  "use strict";
1230
- var normalize = require_normalize();
1231
- var exact = require_exact();
1232
- var flag = require_flag();
1233
- module.exports = form;
1234
- function form(context, value, all) {
1235
- var normal = value.trim();
1236
- var alternative;
1237
- if (!normal) {
1238
- return null;
1239
- }
1240
- normal = normalize(normal, context.conversion.in);
1241
- if (exact(context, normal)) {
1242
- if (!all && flag(context.flags, "FORBIDDENWORD", context.data[normal])) {
1243
- return null;
830
+ var parseCodes = require_rule_codes();
831
+ var add = require_add();
832
+ module.exports = parse;
833
+ var whiteSpaceExpression = /\s/g;
834
+ function parse(buf, options, dict) {
835
+ var value = buf.toString("utf8");
836
+ var last = value.indexOf("\n") + 1;
837
+ var index = value.indexOf("\n", last);
838
+ while (index > -1) {
839
+ if (value.charCodeAt(last) !== 9) {
840
+ parseLine(value.slice(last, index), options, dict);
1244
841
  }
1245
- return normal;
842
+ last = index + 1;
843
+ index = value.indexOf("\n", last);
1246
844
  }
1247
- if (normal.toUpperCase() === normal) {
1248
- alternative = normal.charAt(0) + normal.slice(1).toLowerCase();
1249
- if (ignore(context.flags, context.data[alternative], all)) {
1250
- return null;
1251
- }
1252
- if (exact(context, alternative)) {
1253
- return alternative;
1254
- }
845
+ parseLine(value.slice(last), options, dict);
846
+ }
847
+ function parseLine(line, options, dict) {
848
+ var slashOffset = line.indexOf("/");
849
+ var hashOffset = line.indexOf("#");
850
+ var codes = "";
851
+ var word;
852
+ var result;
853
+ while (slashOffset > -1 && line.charCodeAt(slashOffset - 1) === 92) {
854
+ line = line.slice(0, slashOffset - 1) + line.slice(slashOffset);
855
+ slashOffset = line.indexOf("/", slashOffset);
1255
856
  }
1256
- alternative = normal.toLowerCase();
1257
- if (alternative !== normal) {
1258
- if (ignore(context.flags, context.data[alternative], all)) {
1259
- return null;
1260
- }
1261
- if (exact(context, alternative)) {
1262
- return alternative;
857
+ if (hashOffset > -1) {
858
+ if (slashOffset > -1 && slashOffset < hashOffset) {
859
+ word = line.slice(0, slashOffset);
860
+ whiteSpaceExpression.lastIndex = slashOffset + 1;
861
+ result = whiteSpaceExpression.exec(line);
862
+ codes = line.slice(slashOffset + 1, result ? result.index : void 0);
863
+ } else {
864
+ word = line.slice(0, hashOffset);
1263
865
  }
866
+ } else if (slashOffset > -1) {
867
+ word = line.slice(0, slashOffset);
868
+ codes = line.slice(slashOffset + 1);
869
+ } else {
870
+ word = line;
871
+ }
872
+ word = word.trim();
873
+ if (word) {
874
+ add(dict, word, parseCodes(options.flags, codes.trim()), options);
1264
875
  }
1265
- return null;
1266
- }
1267
- function ignore(flags, dict, all) {
1268
- return flag(flags, "KEEPCASE", dict) || all || flag(flags, "FORBIDDENWORD", dict);
1269
876
  }
1270
877
  }
1271
878
  });
1272
879
 
1273
- // node_modules/nspell/lib/correct.js
1274
- var require_correct = __commonJS({
1275
- "node_modules/nspell/lib/correct.js"(exports, module) {
880
+ // node_modules/nspell/lib/dictionary.js
881
+ var require_dictionary2 = __commonJS({
882
+ "node_modules/nspell/lib/dictionary.js"(exports, module) {
1276
883
  "use strict";
1277
- var form = require_form();
1278
- module.exports = correct;
1279
- function correct(value) {
1280
- return Boolean(form(this, value));
884
+ var parse = require_dictionary();
885
+ module.exports = add;
886
+ function add(buf) {
887
+ var self = this;
888
+ var index = -1;
889
+ var rule;
890
+ var source;
891
+ var character;
892
+ var offset;
893
+ parse(buf, self, self.data);
894
+ while (++index < self.compoundRules.length) {
895
+ rule = self.compoundRules[index];
896
+ source = "";
897
+ offset = -1;
898
+ while (++offset < rule.length) {
899
+ character = rule.charAt(offset);
900
+ source += self.compoundRuleCodes[character].length ? "(?:" + self.compoundRuleCodes[character].join("|") + ")" : character;
901
+ }
902
+ self.compoundRules[index] = new RegExp(source, "i");
903
+ }
904
+ return self;
1281
905
  }
1282
906
  }
1283
907
  });
1284
908
 
1285
- // node_modules/nspell/lib/util/casing.js
1286
- var require_casing = __commonJS({
1287
- "node_modules/nspell/lib/util/casing.js"(exports, module) {
909
+ // node_modules/nspell/lib/personal.js
910
+ var require_personal = __commonJS({
911
+ "node_modules/nspell/lib/personal.js"(exports, module) {
1288
912
  "use strict";
1289
- module.exports = casing;
1290
- function casing(value) {
1291
- var head = exact(value.charAt(0));
1292
- var rest = value.slice(1);
1293
- if (!rest) {
1294
- return head;
1295
- }
1296
- rest = exact(rest);
1297
- if (head === rest) {
1298
- return head;
1299
- }
1300
- if (head === "u" && rest === "l") {
1301
- return "s";
913
+ module.exports = add;
914
+ function add(buf) {
915
+ var self = this;
916
+ var lines = buf.toString("utf8").split("\n");
917
+ var index = -1;
918
+ var line;
919
+ var forbidden;
920
+ var word;
921
+ var flag;
922
+ if (self.flags.FORBIDDENWORD === void 0) self.flags.FORBIDDENWORD = false;
923
+ flag = self.flags.FORBIDDENWORD;
924
+ while (++index < lines.length) {
925
+ line = lines[index].trim();
926
+ if (!line) {
927
+ continue;
928
+ }
929
+ line = line.split("/");
930
+ word = line[0];
931
+ forbidden = word.charAt(0) === "*";
932
+ if (forbidden) {
933
+ word = word.slice(1);
934
+ }
935
+ self.add(word, line[1]);
936
+ if (forbidden) {
937
+ self.data[word].push(flag);
938
+ }
1302
939
  }
1303
- return null;
1304
- }
1305
- function exact(value) {
1306
- return value === value.toLowerCase() ? "l" : value === value.toUpperCase() ? "u" : null;
940
+ return self;
1307
941
  }
1308
942
  }
1309
943
  });
1310
944
 
1311
- // node_modules/nspell/lib/suggest.js
1312
- var require_suggest = __commonJS({
1313
- "node_modules/nspell/lib/suggest.js"(exports, module) {
945
+ // node_modules/nspell/lib/index.js
946
+ var require_lib = __commonJS({
947
+ "node_modules/nspell/lib/index.js"(exports, module) {
1314
948
  "use strict";
1315
- var casing = require_casing();
1316
- var normalize = require_normalize();
1317
- var flag = require_flag();
1318
- var form = require_form();
1319
- module.exports = suggest;
1320
- var push = [].push;
1321
- function suggest(value) {
1322
- var self = this;
1323
- var charAdded = {};
1324
- var suggestions = [];
1325
- var weighted = {};
1326
- var memory;
1327
- var replacement;
1328
- var edits = [];
1329
- var values;
1330
- var index;
1331
- var offset;
1332
- var position;
1333
- var count;
1334
- var otherOffset;
1335
- var otherCharacter;
1336
- var character;
1337
- var group;
1338
- var before;
1339
- var after;
1340
- var upper;
1341
- var insensitive;
1342
- var firstLevel;
1343
- var previous;
1344
- var next;
1345
- var nextCharacter;
1346
- var max;
1347
- var distance;
1348
- var size;
1349
- var normalized;
1350
- var suggestion;
1351
- var currentCase;
1352
- value = normalize(value.trim(), self.conversion.in);
1353
- if (!value || self.correct(value)) {
1354
- return [];
1355
- }
1356
- currentCase = casing(value);
1357
- index = -1;
1358
- while (++index < self.replacementTable.length) {
1359
- replacement = self.replacementTable[index];
1360
- offset = value.indexOf(replacement[0]);
1361
- while (offset > -1) {
1362
- edits.push(value.replace(replacement[0], replacement[1]));
1363
- offset = value.indexOf(replacement[0], offset + 1);
1364
- }
1365
- }
1366
- index = -1;
1367
- while (++index < value.length) {
1368
- character = value.charAt(index);
1369
- before = value.slice(0, index);
1370
- after = value.slice(index + 1);
1371
- insensitive = character.toLowerCase();
1372
- upper = insensitive !== character;
1373
- charAdded = {};
1374
- offset = -1;
1375
- while (++offset < self.flags.KEY.length) {
1376
- group = self.flags.KEY[offset];
1377
- position = group.indexOf(insensitive);
1378
- if (position < 0) {
1379
- continue;
1380
- }
1381
- otherOffset = -1;
1382
- while (++otherOffset < group.length) {
1383
- if (otherOffset !== position) {
1384
- otherCharacter = group.charAt(otherOffset);
1385
- if (charAdded[otherCharacter]) {
1386
- continue;
1387
- }
1388
- charAdded[otherCharacter] = true;
1389
- if (upper) {
1390
- otherCharacter = otherCharacter.toUpperCase();
1391
- }
1392
- edits.push(before + otherCharacter + after);
1393
- }
1394
- }
1395
- }
949
+ var buffer = require_is_buffer();
950
+ var affix = require_affix();
951
+ module.exports = NSpell3;
952
+ var proto = NSpell3.prototype;
953
+ proto.correct = require_correct();
954
+ proto.suggest = require_suggest();
955
+ proto.spell = require_spell();
956
+ proto.add = require_add2();
957
+ proto.remove = require_remove();
958
+ proto.wordCharacters = require_word_characters();
959
+ proto.dictionary = require_dictionary2();
960
+ proto.personal = require_personal();
961
+ function NSpell3(aff, dic) {
962
+ var index = -1;
963
+ var dictionaries;
964
+ if (!(this instanceof NSpell3)) {
965
+ return new NSpell3(aff, dic);
1396
966
  }
1397
- index = -1;
1398
- nextCharacter = value.charAt(0);
1399
- values = [""];
1400
- max = 1;
1401
- distance = 0;
1402
- while (++index < value.length) {
1403
- character = nextCharacter;
1404
- nextCharacter = value.charAt(index + 1);
1405
- before = value.slice(0, index);
1406
- replacement = character === nextCharacter ? "" : character + character;
1407
- offset = -1;
1408
- count = values.length;
1409
- while (++offset < count) {
1410
- if (offset <= max) {
1411
- values.push(values[offset] + replacement);
1412
- }
1413
- values[offset] += character;
1414
- }
1415
- if (++distance < 3) {
1416
- max = values.length;
967
+ if (typeof aff === "string" || buffer(aff)) {
968
+ if (typeof dic === "string" || buffer(dic)) {
969
+ dictionaries = [{ dic }];
1417
970
  }
1418
- }
1419
- push.apply(edits, values);
1420
- values = [value];
1421
- replacement = value.toLowerCase();
1422
- if (value === replacement || currentCase === null) {
1423
- values.push(value.charAt(0).toUpperCase() + replacement.slice(1));
1424
- }
1425
- replacement = value.toUpperCase();
1426
- if (value !== replacement) {
1427
- values.push(replacement);
1428
- }
1429
- memory = {
1430
- state: {},
1431
- weighted,
1432
- suggestions
1433
- };
1434
- firstLevel = generate(self, memory, values, edits);
1435
- previous = 0;
1436
- max = Math.min(firstLevel.length, Math.pow(Math.max(15 - value.length, 3), 3));
1437
- size = Math.max(Math.pow(10 - value.length, 3), 1);
1438
- while (!suggestions.length && previous < max) {
1439
- next = previous + size;
1440
- generate(self, memory, firstLevel.slice(previous, next));
1441
- previous = next;
1442
- }
1443
- suggestions.sort(sort);
1444
- values = [];
1445
- normalized = [];
1446
- index = -1;
1447
- while (++index < suggestions.length) {
1448
- suggestion = normalize(suggestions[index], self.conversion.out);
1449
- replacement = suggestion.toLowerCase();
1450
- if (normalized.indexOf(replacement) < 0) {
1451
- values.push(suggestion);
1452
- normalized.push(replacement);
971
+ } else if (aff) {
972
+ if ("length" in aff) {
973
+ dictionaries = aff;
974
+ aff = aff[0] && aff[0].aff;
975
+ } else {
976
+ if (aff.dic) {
977
+ dictionaries = [aff];
978
+ }
979
+ aff = aff.aff;
1453
980
  }
1454
981
  }
1455
- return values;
1456
- function sort(a, b) {
1457
- return sortWeight(a, b) || sortCasing(a, b) || sortAlpha(a, b);
982
+ if (!aff) {
983
+ throw new Error("Missing `aff` in dictionary");
1458
984
  }
1459
- function sortWeight(a, b) {
1460
- return weighted[a] === weighted[b] ? 0 : weighted[a] > weighted[b] ? -1 : 1;
985
+ aff = affix(aff);
986
+ this.data = /* @__PURE__ */ Object.create(null);
987
+ this.compoundRuleCodes = aff.compoundRuleCodes;
988
+ this.replacementTable = aff.replacementTable;
989
+ this.conversion = aff.conversion;
990
+ this.compoundRules = aff.compoundRules;
991
+ this.rules = aff.rules;
992
+ this.flags = aff.flags;
993
+ if (dictionaries) {
994
+ while (++index < dictionaries.length) {
995
+ if (dictionaries[index].dic) {
996
+ this.dictionary(dictionaries[index].dic);
997
+ }
998
+ }
1461
999
  }
1462
- function sortCasing(a, b) {
1463
- var leftCasing = casing(a);
1464
- var rightCasing = casing(b);
1465
- return leftCasing === rightCasing ? 0 : leftCasing === currentCase ? -1 : rightCasing === currentCase ? 1 : void 0;
1000
+ }
1001
+ }
1002
+ });
1003
+
1004
+ // client/lib/api-client.js
1005
+ var api_client_exports = {};
1006
+ __export(api_client_exports, {
1007
+ abortMessageListRequests: () => abortMessageListRequests,
1008
+ addContact: () => addContact,
1009
+ addPreferredContact: () => addPreferredContact,
1010
+ addToDenylist: () => addToDenylist,
1011
+ addUserDictWord: () => addUserDictWord,
1012
+ addUserDictWords: () => addUserDictWords,
1013
+ aiTransform: () => aiTransform,
1014
+ allowRemoteContent: () => allowRemoteContent,
1015
+ autocomplete: () => autocomplete,
1016
+ cancelQueuedOutgoing: () => cancelQueuedOutgoing,
1017
+ cancelServerSearch: () => cancelServerSearch,
1018
+ closeWordEdit: () => closeWordEdit,
1019
+ connectEvents: () => connectEvents,
1020
+ connectWebSocket: () => connectWebSocket,
1021
+ consumePendingMailto: () => consumePendingMailto,
1022
+ createCalendarEvent: () => createCalendarEvent,
1023
+ createFolder: () => createFolder,
1024
+ createTask: () => createTask,
1025
+ deleteCalendarEvent: () => deleteCalendarEvent,
1026
+ deleteContact: () => deleteContact,
1027
+ deleteDraft: () => deleteDraft,
1028
+ deleteFolder: () => deleteFolder,
1029
+ deleteMessage: () => deleteMessage,
1030
+ deleteMessages: () => deleteMessages,
1031
+ deleteTask: () => deleteTask,
1032
+ drainStoreSync: () => drainStoreSync,
1033
+ emptyFolder: () => emptyFolder,
1034
+ flagSenderOrDomain: () => flagSenderOrDomain,
1035
+ formatJsonc: () => formatJsonc,
1036
+ getAccounts: () => getAccounts,
1037
+ getAttachment: () => getAttachment,
1038
+ getAutocompleteSettings: () => getAutocompleteSettings,
1039
+ getCalendarEvents: () => getCalendarEvents,
1040
+ getCalendars: () => getCalendars,
1041
+ getDeviceAccounts: () => getDeviceAccounts,
1042
+ getDiagnostics: () => getDiagnostics,
1043
+ getFolders: () => getFolders,
1044
+ getMessage: () => getMessage,
1045
+ getMessages: () => getMessages,
1046
+ getOutboxStatus: () => getOutboxStatus,
1047
+ getPrimaryAccount: () => getPrimaryAccount,
1048
+ getPriorityLists: () => getPriorityLists,
1049
+ getSettings: () => getSettings,
1050
+ getSyncPending: () => getSyncPending,
1051
+ getTasks: () => getTasks,
1052
+ getThreadMessages: () => getThreadMessages,
1053
+ getUnifiedInbox: () => getUnifiedInbox,
1054
+ getUserDict: () => getUserDict,
1055
+ getVersion: () => getVersion,
1056
+ hasBccHistoryTo: () => hasBccHistoryTo,
1057
+ hasCcHistoryTo: () => hasCcHistoryTo,
1058
+ installConsoleCapture: () => installConsoleCapture,
1059
+ listContacts: () => listContacts,
1060
+ listQueuedOutgoing: () => listQueuedOutgoing,
1061
+ logClientEvent: () => logClientEvent,
1062
+ markAsSpamMessages: () => markAsSpamMessages,
1063
+ markFolderRead: () => markFolderRead,
1064
+ moveFolderToTrash: () => moveFolderToTrash,
1065
+ moveMessage: () => moveMessage,
1066
+ moveMessages: () => moveMessages,
1067
+ onEvent: () => onEvent,
1068
+ onWsEvent: () => onWsEvent,
1069
+ openAttachment: () => openAttachment,
1070
+ openInTextEditor: () => openInTextEditor,
1071
+ openInWord: () => openInWord,
1072
+ openLocalPath: () => openLocalPath,
1073
+ readConfigHelp: () => readConfigHelp,
1074
+ readJsoncFile: () => readJsoncFile,
1075
+ reauthGoogleScopes: () => reauthGoogleScopes,
1076
+ reauthenticate: () => reauthenticate,
1077
+ recordSpamReport: () => recordSpamReport,
1078
+ removeUserDictWord: () => removeUserDictWord,
1079
+ renameFolder: () => renameFolder,
1080
+ repairAccounts: () => repairAccounts,
1081
+ restartServer: () => restartServer,
1082
+ saveAutocompleteSettings: () => saveAutocompleteSettings,
1083
+ saveDraft: () => saveDraft,
1084
+ saveSettings: () => saveSettings,
1085
+ searchContacts: () => searchContacts,
1086
+ searchMessages: () => searchMessages,
1087
+ sendMessage: () => sendMessage,
1088
+ setPriorityDomain: () => setPriorityDomain,
1089
+ setPrioritySender: () => setPrioritySender,
1090
+ setupAccount: () => setupAccount,
1091
+ showReminderPopup: () => showReminderPopup,
1092
+ subscribeStore: () => subscribeStore,
1093
+ syncAccount: () => syncAccount,
1094
+ triggerSync: () => triggerSync,
1095
+ undeleteMessage: () => undeleteMessage,
1096
+ unsubscribeOneClick: () => unsubscribeOneClick,
1097
+ updateCalendarEvent: () => updateCalendarEvent,
1098
+ updateFlags: () => updateFlags,
1099
+ updateTask: () => updateTask,
1100
+ upsertContact: () => upsertContact,
1101
+ writeJsoncFile: () => writeJsoncFile
1102
+ });
1103
+ function getIpc() {
1104
+ if (typeof mailxapi !== "undefined" && mailxapi?.isApp)
1105
+ return mailxapi;
1106
+ if (window.opener?.mailxapi?.isApp)
1107
+ return window.opener.mailxapi;
1108
+ if (window.parent?.mailxapi?.isApp)
1109
+ return window.parent.mailxapi;
1110
+ return null;
1111
+ }
1112
+ function buildRelayBridge() {
1113
+ const pending = /* @__PURE__ */ new Map();
1114
+ window.addEventListener("message", (ev) => {
1115
+ if (!ev.data || ev.data.type !== "mailx-ipc-result" || !ev.data.id)
1116
+ return;
1117
+ const entry = pending.get(ev.data.id);
1118
+ if (!entry)
1119
+ return;
1120
+ pending.delete(ev.data.id);
1121
+ clearTimeout(entry.timer);
1122
+ if (ev.data.ok)
1123
+ entry.resolve(ev.data.result);
1124
+ else
1125
+ entry.reject(new Error(ev.data.error || "parent-relay ipc error"));
1126
+ });
1127
+ const call = (method, args) => {
1128
+ const id = `ipc-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
1129
+ return new Promise((resolve, reject) => {
1130
+ const timer = setTimeout(() => {
1131
+ pending.delete(id);
1132
+ reject(new Error(`parent-relay timeout: ${method}`));
1133
+ }, 12e4);
1134
+ pending.set(id, { resolve, reject, timer });
1135
+ try {
1136
+ window.parent.postMessage({ type: "mailx-ipc", id, method, args }, "*");
1137
+ } catch (e) {
1138
+ clearTimeout(timer);
1139
+ pending.delete(id);
1140
+ reject(e);
1466
1141
  }
1467
- function sortAlpha(a, b) {
1468
- return a.localeCompare(b);
1142
+ });
1143
+ };
1144
+ return new Proxy({}, {
1145
+ get(_t, prop) {
1146
+ if (prop === "isApp")
1147
+ return true;
1148
+ if (prop === "platform")
1149
+ return window.parent?.mailxapi?.platform || "webview2";
1150
+ if (prop === "onEvent") {
1151
+ return (handler) => window.parent?.mailxapi?.onEvent?.(handler);
1152
+ }
1153
+ return (...args) => call(prop, args);
1154
+ }
1155
+ });
1156
+ }
1157
+ function ipc() {
1158
+ const inIframe = window.parent && window.parent !== window;
1159
+ if (inIframe && window.parent?.mailxapi?.isApp) {
1160
+ if (!cachedRelayBridge)
1161
+ cachedRelayBridge = buildRelayBridge();
1162
+ return cachedRelayBridge;
1163
+ }
1164
+ const bridge = getIpc();
1165
+ if (!bridge)
1166
+ throw new Error("IPC bridge not available");
1167
+ return bridge;
1168
+ }
1169
+ function abortMessageListRequests() {
1170
+ if (messageListAbort) {
1171
+ messageListAbort.abort();
1172
+ messageListAbort = null;
1173
+ }
1174
+ }
1175
+ function getAccounts() {
1176
+ return ipc().getAccounts();
1177
+ }
1178
+ function getFolders(accountId) {
1179
+ return ipc().getFolders(accountId);
1180
+ }
1181
+ function getMessages(accountId, folderId, page = 1, pageSize = 50, flaggedOnly = false, sort, sortDir) {
1182
+ abortMessageListRequests();
1183
+ return ipc().getMessages(accountId, folderId, page, pageSize, sort, sortDir, void 0, flaggedOnly);
1184
+ }
1185
+ function getUnifiedInbox(page = 1, pageSize = 50) {
1186
+ abortMessageListRequests();
1187
+ return ipc().getUnifiedInbox(page, pageSize);
1188
+ }
1189
+ function searchMessages(query, page = 1, pageSize = 50, scope = "all", accountId = "", folderId = 0, includeTrashSpam = false) {
1190
+ return ipc().searchMessages(query, page, pageSize, scope, accountId, folderId, includeTrashSpam);
1191
+ }
1192
+ function cancelServerSearch() {
1193
+ return ipc().cancelServerSearch?.();
1194
+ }
1195
+ function getMessage(accountId, uid, allowRemote = false, folderId) {
1196
+ return ipc().getMessage(accountId, uid, allowRemote, folderId);
1197
+ }
1198
+ function updateFlags(accountId, uid, flags) {
1199
+ return ipc().updateFlags(accountId, uid, flags);
1200
+ }
1201
+ function triggerSync() {
1202
+ return ipc().syncAll();
1203
+ }
1204
+ function syncAccount(accountId) {
1205
+ return ipc().syncAccount(accountId);
1206
+ }
1207
+ function reauthenticate(accountId) {
1208
+ return ipc().reauthenticate(accountId);
1209
+ }
1210
+ function reauthGoogleScopes() {
1211
+ return ipc().reauthGoogleScopes();
1212
+ }
1213
+ function getSyncPending() {
1214
+ return ipc().getSyncPending();
1215
+ }
1216
+ function getDiagnostics() {
1217
+ return ipc().getDiagnostics?.() ?? Promise.resolve([]);
1218
+ }
1219
+ function getPrimaryAccount(feature) {
1220
+ return ipc().getPrimaryAccount?.(feature) ?? Promise.resolve(null);
1221
+ }
1222
+ function getCalendarEvents(fromMs, toMs) {
1223
+ return ipc().getCalendarEvents?.(fromMs, toMs) ?? Promise.resolve([]);
1224
+ }
1225
+ function getCalendars() {
1226
+ return ipc().getCalendars?.() ?? Promise.resolve([]);
1227
+ }
1228
+ function createCalendarEvent(ev) {
1229
+ return ipc().createCalendarEvent?.(ev);
1230
+ }
1231
+ function updateCalendarEvent(uuid, patch) {
1232
+ return ipc().updateCalendarEvent?.(uuid, patch);
1233
+ }
1234
+ function deleteCalendarEvent(uuid) {
1235
+ return ipc().deleteCalendarEvent?.(uuid);
1236
+ }
1237
+ function getTasks(includeCompleted = false) {
1238
+ return ipc().getTasks?.(includeCompleted) ?? Promise.resolve([]);
1239
+ }
1240
+ function createTask(t) {
1241
+ return ipc().createTask?.(t);
1242
+ }
1243
+ function updateTask(uuid, patch) {
1244
+ return ipc().updateTask?.(uuid, patch);
1245
+ }
1246
+ function deleteTask(uuid) {
1247
+ return ipc().deleteTask?.(uuid);
1248
+ }
1249
+ function drainStoreSync() {
1250
+ return ipc().drainStoreSync?.();
1251
+ }
1252
+ function recordSpamReport(accountId, uid, folderId) {
1253
+ return ipc().recordSpamReport?.(accountId, uid, folderId);
1254
+ }
1255
+ function getOutboxStatus() {
1256
+ return ipc().getOutboxStatus();
1257
+ }
1258
+ function listQueuedOutgoing() {
1259
+ return ipc().listQueuedOutgoing();
1260
+ }
1261
+ function cancelQueuedOutgoing(p) {
1262
+ return ipc().cancelQueuedOutgoing(p);
1263
+ }
1264
+ function searchContacts(query) {
1265
+ return ipc().searchContacts(query);
1266
+ }
1267
+ function hasCcHistoryTo(email) {
1268
+ return ipc().hasCcHistoryTo(email);
1269
+ }
1270
+ function hasBccHistoryTo(email) {
1271
+ return ipc().hasBccHistoryTo(email);
1272
+ }
1273
+ function listContacts(query, page = 1, pageSize = 100) {
1274
+ return ipc().listContacts(query, page, pageSize);
1275
+ }
1276
+ function upsertContact(name, email) {
1277
+ return ipc().upsertContact(name, email);
1278
+ }
1279
+ function deleteContact(email) {
1280
+ return ipc().deleteContact(email);
1281
+ }
1282
+ function addPreferredContact(entry) {
1283
+ return ipc().addPreferredContact(entry.name, entry.email, entry.source, entry.organization);
1284
+ }
1285
+ function getPriorityLists() {
1286
+ return ipc().getPriorityLists();
1287
+ }
1288
+ function setPrioritySender(email, value, name) {
1289
+ return ipc().setPrioritySender(email, value, name);
1290
+ }
1291
+ function setPriorityDomain(domain, value) {
1292
+ return ipc().setPriorityDomain(domain, value);
1293
+ }
1294
+ function addToDenylist(email) {
1295
+ return ipc().addToDenylist(email);
1296
+ }
1297
+ function openLocalPath(which) {
1298
+ return ipc().openLocalPath(which);
1299
+ }
1300
+ function openInTextEditor(path) {
1301
+ return ipc().openInTextEditor?.(path) ?? Promise.resolve({ ok: false, opener: "none", reason: "no host" });
1302
+ }
1303
+ function allowRemoteContent(type, value) {
1304
+ return ipc().allowRemoteContent(type, value);
1305
+ }
1306
+ function getUserDict() {
1307
+ return ipc().getUserDict?.() ?? Promise.resolve([]);
1308
+ }
1309
+ function addUserDictWord(word) {
1310
+ return ipc().addUserDictWord?.(word) ?? Promise.resolve([]);
1311
+ }
1312
+ function addUserDictWords(words) {
1313
+ return ipc().addUserDictWords?.(words) ?? Promise.resolve([]);
1314
+ }
1315
+ function removeUserDictWord(word) {
1316
+ return ipc().removeUserDictWord?.(word) ?? Promise.resolve([]);
1317
+ }
1318
+ function flagSenderOrDomain(type, value) {
1319
+ return ipc().flagSenderOrDomain?.(type, value) ?? Promise.resolve({ flagged: false });
1320
+ }
1321
+ function deleteMessage(accountId, uid) {
1322
+ return ipc().deleteMessage?.(accountId, uid);
1323
+ }
1324
+ function deleteMessages(accountId, uids) {
1325
+ if (uids.length === 1)
1326
+ return deleteMessage(accountId, uids[0]);
1327
+ return ipc().deleteMessages?.(accountId, uids);
1328
+ }
1329
+ function moveMessages(accountId, uids, targetFolderId, targetAccountId) {
1330
+ if (uids.length === 1)
1331
+ return moveMessage(accountId, uids[0], targetFolderId, targetAccountId);
1332
+ return ipc().moveMessages?.(accountId, uids, targetFolderId, targetAccountId);
1333
+ }
1334
+ function markAsSpamMessages(accountId, uids) {
1335
+ return ipc().markAsSpamMessages?.(accountId, uids);
1336
+ }
1337
+ function undeleteMessage(accountId, uid, folderId) {
1338
+ return ipc().undeleteMessage?.(accountId, uid, folderId);
1339
+ }
1340
+ function moveMessage(accountId, uid, targetFolderId, targetAccountId) {
1341
+ return ipc().moveMessage?.(accountId, uid, targetFolderId, targetAccountId);
1342
+ }
1343
+ function restartServer() {
1344
+ return ipc().restart?.();
1345
+ }
1346
+ function markFolderRead(accountId, folderId) {
1347
+ return ipc().markFolderRead?.(accountId, folderId);
1348
+ }
1349
+ function createFolder(accountId, parentPath, name) {
1350
+ return ipc().createFolder?.(accountId, parentPath, name);
1351
+ }
1352
+ function renameFolder(accountId, folderId, newName) {
1353
+ return ipc().renameFolder?.(accountId, folderId, newName);
1354
+ }
1355
+ function deleteFolder(accountId, folderId) {
1356
+ return ipc().deleteFolder?.(accountId, folderId);
1357
+ }
1358
+ function moveFolderToTrash(accountId, folderId) {
1359
+ return ipc().moveFolderToTrash?.(accountId, folderId);
1360
+ }
1361
+ function emptyFolder(accountId, folderId) {
1362
+ return ipc().emptyFolder?.(accountId, folderId);
1363
+ }
1364
+ function installConsoleCapture() {
1365
+ const g = globalThis;
1366
+ if (g.__mailxConsoleCaptureInstalled)
1367
+ return;
1368
+ g.__mailxConsoleCaptureInstalled = true;
1369
+ const orig = {
1370
+ log: console.log.bind(console),
1371
+ info: console.info.bind(console),
1372
+ warn: console.warn.bind(console),
1373
+ error: console.error.bind(console),
1374
+ debug: console.debug.bind(console)
1375
+ };
1376
+ g._origConsole = orig;
1377
+ const serialize = (v) => {
1378
+ if (v == null)
1379
+ return v;
1380
+ const t = typeof v;
1381
+ if (t === "string" || t === "number" || t === "boolean")
1382
+ return v;
1383
+ if (v instanceof Error)
1384
+ return { __err: true, message: v.message, stack: v.stack };
1385
+ try {
1386
+ const s = JSON.stringify(v);
1387
+ return s.length > 4096 ? s.slice(0, 4096) + "\u2026[truncated]" : JSON.parse(s);
1388
+ } catch {
1389
+ try {
1390
+ return String(v);
1391
+ } catch {
1392
+ return "[unserializable]";
1469
1393
  }
1470
1394
  }
1471
- function generate(context, memory, words, edits) {
1472
- var characters = context.flags.TRY;
1473
- var data = context.data;
1474
- var flags = context.flags;
1475
- var result = [];
1476
- var index = -1;
1477
- var word;
1478
- var before;
1479
- var character;
1480
- var nextCharacter;
1481
- var nextAfter;
1482
- var nextNextAfter;
1483
- var nextUpper;
1484
- var currentCase;
1485
- var position;
1486
- var after;
1487
- var upper;
1488
- var inject;
1489
- var offset;
1490
- if (edits) {
1491
- while (++index < edits.length) {
1492
- check(edits[index], true);
1493
- }
1494
- }
1495
- index = -1;
1496
- while (++index < words.length) {
1497
- word = words[index];
1498
- before = "";
1499
- character = "";
1500
- nextCharacter = word.charAt(0);
1501
- nextAfter = word;
1502
- nextNextAfter = word.slice(1);
1503
- nextUpper = nextCharacter.toLowerCase() !== nextCharacter;
1504
- currentCase = casing(word);
1505
- position = -1;
1506
- while (++position <= word.length) {
1507
- before += character;
1508
- after = nextAfter;
1509
- nextAfter = nextNextAfter;
1510
- nextNextAfter = nextAfter.slice(1);
1511
- character = nextCharacter;
1512
- nextCharacter = word.charAt(position + 1);
1513
- upper = nextUpper;
1514
- if (nextCharacter) {
1515
- nextUpper = nextCharacter.toLowerCase() !== nextCharacter;
1516
- }
1517
- if (nextAfter && upper !== nextUpper) {
1518
- check(before + switchCase(nextAfter));
1519
- check(
1520
- before + switchCase(nextCharacter) + switchCase(character) + nextNextAfter
1521
- );
1522
- }
1523
- check(before + nextAfter);
1524
- if (nextAfter) {
1525
- check(before + nextCharacter + character + nextNextAfter);
1526
- }
1527
- offset = -1;
1528
- while (++offset < characters.length) {
1529
- inject = characters[offset];
1530
- if (upper && inject !== inject.toUpperCase()) {
1531
- if (currentCase !== "s") {
1532
- check(before + inject + after);
1533
- check(before + inject + nextAfter);
1534
- }
1535
- inject = inject.toUpperCase();
1536
- check(before + inject + after);
1537
- check(before + inject + nextAfter);
1538
- } else {
1539
- check(before + inject + after);
1540
- check(before + inject + nextAfter);
1541
- }
1542
- }
1543
- }
1544
- }
1545
- return result;
1546
- function check(value, double) {
1547
- var state = memory.state[value];
1548
- var corrected;
1549
- if (state !== Boolean(state)) {
1550
- result.push(value);
1551
- corrected = form(context, value);
1552
- state = corrected && !flag(flags, "NOSUGGEST", data[corrected]);
1553
- memory.state[value] = state;
1554
- if (state) {
1555
- memory.weighted[value] = double ? 10 : 0;
1556
- memory.suggestions.push(value);
1557
- }
1558
- }
1559
- if (state) {
1560
- memory.weighted[value]++;
1561
- }
1395
+ };
1396
+ const forward = (level, args) => {
1397
+ try {
1398
+ logClientEvent(`console.${level}`, { args: args.map(serialize) });
1399
+ } catch {
1400
+ }
1401
+ };
1402
+ console.log = (...args) => {
1403
+ forward("log", args);
1404
+ orig.log(...args);
1405
+ };
1406
+ console.info = (...args) => {
1407
+ forward("info", args);
1408
+ orig.info(...args);
1409
+ };
1410
+ console.warn = (...args) => {
1411
+ forward("warn", args);
1412
+ orig.warn(...args);
1413
+ };
1414
+ console.error = (...args) => {
1415
+ forward("error", args);
1416
+ orig.error(...args);
1417
+ };
1418
+ console.debug = (...args) => {
1419
+ forward("debug", args);
1420
+ orig.debug(...args);
1421
+ };
1422
+ try {
1423
+ window.addEventListener("error", (e) => {
1424
+ try {
1425
+ logClientEvent("window.error", {
1426
+ message: e.message,
1427
+ filename: e.filename,
1428
+ lineno: e.lineno,
1429
+ colno: e.colno,
1430
+ stack: e.error?.stack || null
1431
+ });
1432
+ } catch {
1562
1433
  }
1563
- function switchCase(fragment) {
1564
- var first = fragment.charAt(0);
1565
- return (first.toLowerCase() === first ? first.toUpperCase() : first.toLowerCase()) + fragment.slice(1);
1434
+ });
1435
+ window.addEventListener("unhandledrejection", (e) => {
1436
+ try {
1437
+ const r = e.reason;
1438
+ logClientEvent("window.unhandledrejection", {
1439
+ message: r?.message || String(r),
1440
+ stack: r?.stack || null
1441
+ });
1442
+ } catch {
1566
1443
  }
1567
- }
1444
+ });
1445
+ } catch {
1568
1446
  }
1569
- });
1570
-
1571
- // node_modules/nspell/lib/spell.js
1572
- var require_spell = __commonJS({
1573
- "node_modules/nspell/lib/spell.js"(exports, module) {
1574
- "use strict";
1575
- var form = require_form();
1576
- var flag = require_flag();
1577
- module.exports = spell;
1578
- function spell(word) {
1579
- var self = this;
1580
- var value = form(self, word, true);
1581
- return {
1582
- correct: self.correct(word),
1583
- forbidden: Boolean(
1584
- value && flag(self.flags, "FORBIDDENWORD", self.data[value])
1585
- ),
1586
- warn: Boolean(value && flag(self.flags, "WARN", self.data[value]))
1587
- };
1447
+ }
1448
+ function logClientEvent(tag, data) {
1449
+ let delivered = false;
1450
+ try {
1451
+ const bridge = typeof globalThis.mailxapi !== "undefined" && globalThis.mailxapi?.isApp ? globalThis.mailxapi : window.opener?.mailxapi?.isApp ? window.opener.mailxapi : window.parent?.mailxapi?.isApp ? window.parent.mailxapi : null;
1452
+ if (bridge?.logClientEvent) {
1453
+ bridge.logClientEvent(tag, data);
1454
+ delivered = true;
1588
1455
  }
1456
+ } catch {
1589
1457
  }
1590
- });
1591
-
1592
- // node_modules/nspell/lib/util/apply.js
1593
- var require_apply = __commonJS({
1594
- "node_modules/nspell/lib/util/apply.js"(exports, module) {
1595
- "use strict";
1596
- module.exports = apply;
1597
- function apply(value, rule, rules, words) {
1598
- var index = -1;
1599
- var entry;
1600
- var next;
1601
- var continuationRule;
1602
- var continuation;
1603
- var position;
1604
- while (++index < rule.entries.length) {
1605
- entry = rule.entries[index];
1606
- continuation = entry.continuation;
1607
- position = -1;
1608
- if (!entry.match || entry.match.test(value)) {
1609
- next = entry.remove ? value.replace(entry.remove, "") : value;
1610
- next = rule.type === "SFX" ? next + entry.add : entry.add + next;
1611
- words.push(next);
1612
- if (continuation && continuation.length) {
1613
- while (++position < continuation.length) {
1614
- continuationRule = rules[continuation[position]];
1615
- if (continuationRule) {
1616
- apply(next, continuationRule, rules, words);
1617
- }
1618
- }
1619
- }
1620
- }
1621
- }
1622
- return words;
1458
+ try {
1459
+ if (window.parent && window.parent !== window) {
1460
+ window.parent.postMessage({ type: "mailx-trace", tag, data, bridged: delivered }, "*");
1623
1461
  }
1462
+ } catch {
1624
1463
  }
1625
- });
1626
-
1627
- // node_modules/nspell/lib/util/add.js
1628
- var require_add = __commonJS({
1629
- "node_modules/nspell/lib/util/add.js"(exports, module) {
1630
- "use strict";
1631
- var apply = require_apply();
1632
- module.exports = add;
1633
- var push = [].push;
1634
- var NO_RULES = [];
1635
- function addRules(dict, word, rules) {
1636
- var curr = dict[word];
1637
- if (word in dict) {
1638
- if (curr === NO_RULES) {
1639
- dict[word] = rules.concat();
1640
- } else {
1641
- push.apply(curr, rules);
1642
- }
1643
- } else {
1644
- dict[word] = rules.concat();
1645
- }
1464
+ }
1465
+ function sendMessage(body) {
1466
+ return ipc().sendMessage?.(body);
1467
+ }
1468
+ function saveDraft(body) {
1469
+ return ipc().saveDraft?.(body);
1470
+ }
1471
+ function onEvent(handler) {
1472
+ eventHandlers.push(handler);
1473
+ return () => {
1474
+ const i = eventHandlers.indexOf(handler);
1475
+ if (i >= 0)
1476
+ eventHandlers.splice(i, 1);
1477
+ };
1478
+ }
1479
+ function subscribeStore(topic, handler) {
1480
+ let set = storeSubs.get(topic);
1481
+ if (!set) {
1482
+ set = /* @__PURE__ */ new Set();
1483
+ storeSubs.set(topic, set);
1484
+ }
1485
+ set.add(handler);
1486
+ return () => {
1487
+ const s = storeSubs.get(topic);
1488
+ if (s) {
1489
+ s.delete(handler);
1490
+ if (s.size === 0)
1491
+ storeSubs.delete(topic);
1646
1492
  }
1647
- function add(dict, word, codes, options) {
1648
- var position = -1;
1649
- var rule;
1650
- var offset;
1651
- var subposition;
1652
- var suboffset;
1653
- var combined;
1654
- var newWords;
1655
- var otherNewWords;
1656
- if (!("NEEDAFFIX" in options.flags) || codes.indexOf(options.flags.NEEDAFFIX) < 0) {
1657
- addRules(dict, word, codes);
1493
+ };
1494
+ }
1495
+ function deliverStore(event) {
1496
+ const exact = storeSubs.get(event.topic);
1497
+ if (exact)
1498
+ for (const h of exact) {
1499
+ try {
1500
+ h(event);
1501
+ } catch (e) {
1502
+ console.error("[store-bus]", e);
1658
1503
  }
1659
- while (++position < codes.length) {
1660
- rule = options.rules[codes[position]];
1661
- if (codes[position] in options.compoundRuleCodes) {
1662
- options.compoundRuleCodes[codes[position]].push(word);
1663
- }
1664
- if (rule) {
1665
- newWords = apply(word, rule, options.rules, []);
1666
- offset = -1;
1667
- while (++offset < newWords.length) {
1668
- if (!(newWords[offset] in dict)) {
1669
- dict[newWords[offset]] = NO_RULES;
1670
- }
1671
- if (rule.combineable) {
1672
- subposition = position;
1673
- while (++subposition < codes.length) {
1674
- combined = options.rules[codes[subposition]];
1675
- if (combined && combined.combineable && rule.type !== combined.type) {
1676
- otherNewWords = apply(
1677
- newWords[offset],
1678
- combined,
1679
- options.rules,
1680
- []
1681
- );
1682
- suboffset = -1;
1683
- while (++suboffset < otherNewWords.length) {
1684
- if (!(otherNewWords[suboffset] in dict)) {
1685
- dict[otherNewWords[suboffset]] = NO_RULES;
1686
- }
1687
- }
1688
- }
1689
- }
1690
- }
1691
- }
1692
- }
1504
+ }
1505
+ const wild = storeSubs.get("*");
1506
+ if (wild)
1507
+ for (const h of wild) {
1508
+ try {
1509
+ h(event);
1510
+ } catch (e) {
1511
+ console.error("[store-bus]", e);
1693
1512
  }
1694
1513
  }
1695
- }
1696
- });
1697
-
1698
- // node_modules/nspell/lib/add.js
1699
- var require_add2 = __commonJS({
1700
- "node_modules/nspell/lib/add.js"(exports, module) {
1514
+ }
1515
+ function connectEvents() {
1516
+ ipc().onEvent((event) => {
1517
+ if (event && event._event === "store")
1518
+ deliverStore(event);
1519
+ for (const h of eventHandlers)
1520
+ h(event);
1521
+ });
1522
+ }
1523
+ function autocomplete(body, signal) {
1524
+ return ipc().autocomplete?.(body);
1525
+ }
1526
+ function getAutocompleteSettings() {
1527
+ return ipc().getAutocompleteSettings?.();
1528
+ }
1529
+ function saveAutocompleteSettings(settings) {
1530
+ return ipc().saveAutocompleteSettings?.(settings);
1531
+ }
1532
+ function getVersion() {
1533
+ return ipc().getVersion();
1534
+ }
1535
+ function getSettings() {
1536
+ return ipc().getSettings();
1537
+ }
1538
+ function saveSettings(settings) {
1539
+ return ipc().saveSettingsData?.(settings);
1540
+ }
1541
+ function repairAccounts() {
1542
+ return ipc().repairAccounts?.();
1543
+ }
1544
+ function deleteDraft(accountId, draftUid2, draftId2) {
1545
+ return ipc().deleteDraft?.(accountId, draftUid2, draftId2);
1546
+ }
1547
+ function addContact(name, email) {
1548
+ return ipc().addContact?.(name, email);
1549
+ }
1550
+ function getThreadMessages(accountId, threadId) {
1551
+ return ipc().getThreadMessages?.(accountId, threadId);
1552
+ }
1553
+ function readJsoncFile(name) {
1554
+ return ipc().readJsoncFile?.(name);
1555
+ }
1556
+ function writeJsoncFile(name, content) {
1557
+ return ipc().writeJsoncFile?.(name, content);
1558
+ }
1559
+ function formatJsonc(content) {
1560
+ return ipc().formatJsonc?.(content);
1561
+ }
1562
+ function readConfigHelp(name) {
1563
+ return ipc().readConfigHelp?.(name) ?? Promise.resolve({ content: "" });
1564
+ }
1565
+ function unsubscribeOneClick(url) {
1566
+ return ipc().unsubscribeOneClick?.(url);
1567
+ }
1568
+ function openInWord(editId, html) {
1569
+ return ipc().openInWord?.(editId, html) ?? Promise.resolve({ ok: false, path: "", opener: "none" });
1570
+ }
1571
+ function closeWordEdit(editId) {
1572
+ return ipc().closeWordEdit?.(editId) ?? Promise.resolve();
1573
+ }
1574
+ function showReminderPopup(opts) {
1575
+ return ipc().showReminderPopup?.(opts) ?? Promise.resolve({ button: "", reason: "no host" });
1576
+ }
1577
+ function consumePendingMailto() {
1578
+ return ipc().consumePendingMailto?.() ?? Promise.resolve(null);
1579
+ }
1580
+ function aiTransform(req) {
1581
+ return ipc().aiTransform?.(req) ?? Promise.resolve({ text: "", reason: "AI not available in this host" });
1582
+ }
1583
+ function setupAccount(name, email, password) {
1584
+ return ipc().setupAccount?.(name, email, password);
1585
+ }
1586
+ async function getAttachment(accountId, uid, attachmentId, folderId) {
1587
+ return ipc().getAttachment(accountId, uid, attachmentId, folderId);
1588
+ }
1589
+ async function openAttachment(accountId, uid, attachmentId, folderId) {
1590
+ const fn = ipc().openAttachment;
1591
+ return fn ? fn(accountId, uid, attachmentId, folderId) : void 0;
1592
+ }
1593
+ async function getDeviceAccounts() {
1594
+ return ipc().getDeviceAccounts?.() ?? [];
1595
+ }
1596
+ var cachedRelayBridge, messageListAbort, eventHandlers, storeSubs, connectWebSocket, onWsEvent;
1597
+ var init_api_client = __esm({
1598
+ "client/lib/api-client.js"() {
1701
1599
  "use strict";
1702
- var push = require_add();
1703
- module.exports = add;
1704
- var NO_CODES = [];
1705
- function add(value, model) {
1706
- var self = this;
1707
- push(self.data, value, self.data[model] || NO_CODES, self);
1708
- return self;
1709
- }
1600
+ cachedRelayBridge = null;
1601
+ messageListAbort = null;
1602
+ eventHandlers = [];
1603
+ storeSubs = /* @__PURE__ */ new Map();
1604
+ connectWebSocket = connectEvents;
1605
+ onWsEvent = onEvent;
1710
1606
  }
1711
1607
  });
1712
1608
 
1713
- // node_modules/nspell/lib/remove.js
1714
- var require_remove = __commonJS({
1715
- "node_modules/nspell/lib/remove.js"(exports, module) {
1716
- "use strict";
1717
- module.exports = remove;
1718
- function remove(value) {
1719
- var self = this;
1720
- delete self.data[value];
1721
- return self;
1722
- }
1723
- }
1609
+ // client/compose/spellcheck-core.js
1610
+ var spellcheck_core_exports = {};
1611
+ __export(spellcheck_core_exports, {
1612
+ MARKER_ATTR: () => MARKER_ATTR,
1613
+ MIN_WORD_LEN: () => MIN_WORD_LEN,
1614
+ SKIP_TAGS: () => SKIP_TAGS,
1615
+ USER_DICT_KEY: () => USER_DICT_KEY,
1616
+ addToUserDict: () => addToUserDict,
1617
+ buildSuggestionList: () => buildSuggestionList,
1618
+ getSpell: () => getSpell,
1619
+ getWordAtPoint: () => getWordAtPoint,
1620
+ showSuggestionsMenu: () => showSuggestionsMenu
1724
1621
  });
1725
-
1726
- // node_modules/nspell/lib/word-characters.js
1727
- var require_word_characters = __commonJS({
1728
- "node_modules/nspell/lib/word-characters.js"(exports, module) {
1729
- "use strict";
1730
- module.exports = wordCharacters;
1731
- function wordCharacters() {
1732
- return this.flags.WORDCHARS || null;
1622
+ async function getSpell() {
1623
+ if (spellPromise)
1624
+ return spellPromise;
1625
+ spellPromise = (async () => {
1626
+ const [affRes, dicRes] = await Promise.all([
1627
+ fetch("../lib/dict/en.aff"),
1628
+ fetch("../lib/dict/en.dic")
1629
+ ]);
1630
+ if (!affRes.ok || !dicRes.ok) {
1631
+ throw new Error(`spellcheck: dict fetch failed (aff=${affRes.status} dic=${dicRes.status})`);
1733
1632
  }
1734
- }
1735
- });
1736
-
1737
- // node_modules/nspell/lib/util/dictionary.js
1738
- var require_dictionary = __commonJS({
1739
- "node_modules/nspell/lib/util/dictionary.js"(exports, module) {
1740
- "use strict";
1741
- var parseCodes = require_rule_codes();
1742
- var add = require_add();
1743
- module.exports = parse;
1744
- var whiteSpaceExpression = /\s/g;
1745
- function parse(buf, options, dict) {
1746
- var value = buf.toString("utf8");
1747
- var last = value.indexOf("\n") + 1;
1748
- var index = value.indexOf("\n", last);
1749
- while (index > -1) {
1750
- if (value.charCodeAt(last) !== 9) {
1751
- parseLine(value.slice(last, index), options, dict);
1752
- }
1753
- last = index + 1;
1754
- index = value.indexOf("\n", last);
1755
- }
1756
- parseLine(value.slice(last), options, dict);
1633
+ const [aff, dic] = await Promise.all([affRes.text(), dicRes.text()]);
1634
+ const sp = new import_nspell.default({ aff, dic });
1635
+ try {
1636
+ const raw = localStorage.getItem(USER_DICT_KEY);
1637
+ if (raw)
1638
+ for (const w of JSON.parse(raw))
1639
+ sp.add(w);
1640
+ } catch {
1757
1641
  }
1758
- function parseLine(line, options, dict) {
1759
- var slashOffset = line.indexOf("/");
1760
- var hashOffset = line.indexOf("#");
1761
- var codes = "";
1762
- var word;
1763
- var result;
1764
- while (slashOffset > -1 && line.charCodeAt(slashOffset - 1) === 92) {
1765
- line = line.slice(0, slashOffset - 1) + line.slice(slashOffset);
1766
- slashOffset = line.indexOf("/", slashOffset);
1642
+ getUserDict().then((cloud) => {
1643
+ const cloudArr = Array.isArray(cloud) ? cloud : [];
1644
+ for (const w of cloudArr)
1645
+ sp.add(w);
1646
+ let local = [];
1647
+ try {
1648
+ const raw = localStorage.getItem(USER_DICT_KEY);
1649
+ local = raw ? JSON.parse(raw) : [];
1650
+ } catch {
1651
+ local = [];
1767
1652
  }
1768
- if (hashOffset > -1) {
1769
- if (slashOffset > -1 && slashOffset < hashOffset) {
1770
- word = line.slice(0, slashOffset);
1771
- whiteSpaceExpression.lastIndex = slashOffset + 1;
1772
- result = whiteSpaceExpression.exec(line);
1773
- codes = line.slice(slashOffset + 1, result ? result.index : void 0);
1774
- } else {
1775
- word = line.slice(0, hashOffset);
1776
- }
1777
- } else if (slashOffset > -1) {
1778
- word = line.slice(0, slashOffset);
1779
- codes = line.slice(slashOffset + 1);
1780
- } else {
1781
- word = line;
1653
+ const cloudSet = new Set(cloudArr);
1654
+ const localOnly = local.filter((w) => !cloudSet.has(w));
1655
+ if (localOnly.length > 0) {
1656
+ addUserDictWords(localOnly).catch((e) => console.error("[spell] reconcile:", e));
1782
1657
  }
1783
- word = word.trim();
1784
- if (word) {
1785
- add(dict, word, parseCodes(options.flags, codes.trim()), options);
1658
+ try {
1659
+ const merged = [.../* @__PURE__ */ new Set([...local, ...cloudArr])];
1660
+ localStorage.setItem(USER_DICT_KEY, JSON.stringify(merged));
1661
+ } catch {
1786
1662
  }
1663
+ }).catch(() => {
1664
+ });
1665
+ return sp;
1666
+ })();
1667
+ return spellPromise;
1668
+ }
1669
+ function addToUserDict(word, sp) {
1670
+ try {
1671
+ const raw = localStorage.getItem(USER_DICT_KEY);
1672
+ const arr = raw ? JSON.parse(raw) : [];
1673
+ if (!arr.includes(word)) {
1674
+ arr.push(word);
1675
+ localStorage.setItem(USER_DICT_KEY, JSON.stringify(arr));
1787
1676
  }
1677
+ } catch {
1788
1678
  }
1789
- });
1790
-
1791
- // node_modules/nspell/lib/dictionary.js
1792
- var require_dictionary2 = __commonJS({
1793
- "node_modules/nspell/lib/dictionary.js"(exports, module) {
1794
- "use strict";
1795
- var parse = require_dictionary();
1796
- module.exports = add;
1797
- function add(buf) {
1798
- var self = this;
1799
- var index = -1;
1800
- var rule;
1801
- var source;
1802
- var character;
1803
- var offset;
1804
- parse(buf, self, self.data);
1805
- while (++index < self.compoundRules.length) {
1806
- rule = self.compoundRules[index];
1807
- source = "";
1808
- offset = -1;
1809
- while (++offset < rule.length) {
1810
- character = rule.charAt(offset);
1811
- source += self.compoundRuleCodes[character].length ? "(?:" + self.compoundRuleCodes[character].join("|") + ")" : character;
1812
- }
1813
- self.compoundRules[index] = new RegExp(source, "i");
1814
- }
1815
- return self;
1679
+ sp.add(word);
1680
+ addUserDictWord(word).catch((e) => console.error("[spell] addUserDictWord:", e));
1681
+ }
1682
+ function buildSuggestionList(word, sp) {
1683
+ const transposed = [];
1684
+ for (let i = 0; i < word.length - 1; i++) {
1685
+ const swapped = word.slice(0, i) + word[i + 1] + word[i] + word.slice(i + 2);
1686
+ if (swapped !== word && sp.correct(swapped) && !transposed.includes(swapped)) {
1687
+ transposed.push(swapped);
1816
1688
  }
1817
1689
  }
1818
- });
1819
-
1820
- // node_modules/nspell/lib/personal.js
1821
- var require_personal = __commonJS({
1822
- "node_modules/nspell/lib/personal.js"(exports, module) {
1823
- "use strict";
1824
- module.exports = add;
1825
- function add(buf) {
1826
- var self = this;
1827
- var lines = buf.toString("utf8").split("\n");
1828
- var index = -1;
1829
- var line;
1830
- var forbidden;
1831
- var word;
1832
- var flag;
1833
- if (self.flags.FORBIDDENWORD === void 0) self.flags.FORBIDDENWORD = false;
1834
- flag = self.flags.FORBIDDENWORD;
1835
- while (++index < lines.length) {
1836
- line = lines[index].trim();
1837
- if (!line) {
1838
- continue;
1839
- }
1840
- line = line.split("/");
1841
- word = line[0];
1842
- forbidden = word.charAt(0) === "*";
1843
- if (forbidden) {
1844
- word = word.slice(1);
1845
- }
1846
- self.add(word, line[1]);
1847
- if (forbidden) {
1848
- self.data[word].push(flag);
1849
- }
1690
+ const nspellSugs = sp.suggest(word);
1691
+ const sugs = [];
1692
+ for (const s of [...transposed, ...nspellSugs]) {
1693
+ if (!sugs.includes(s))
1694
+ sugs.push(s);
1695
+ if (sugs.length >= 7)
1696
+ break;
1697
+ }
1698
+ return sugs;
1699
+ }
1700
+ function showSuggestionsMenu(parentDoc, x, y, items, extraDismissDocs = []) {
1701
+ parentDoc.getElementById("mailx-spell-menu")?.remove();
1702
+ const menu = parentDoc.createElement("div");
1703
+ menu.id = "mailx-spell-menu";
1704
+ menu.style.cssText = `
1705
+ position: fixed;
1706
+ left: ${x}px; top: ${y}px;
1707
+ z-index: 10000;
1708
+ background: var(--color-bg, #fff);
1709
+ color: var(--color-text, #222);
1710
+ border: 1px solid var(--color-border, #ccc);
1711
+ border-radius: 6px;
1712
+ box-shadow: 0 4px 16px rgba(0,0,0,0.18);
1713
+ padding: 4px 0;
1714
+ font: 13px system-ui, sans-serif;
1715
+ min-width: 180px;
1716
+ max-width: 320px;
1717
+ `;
1718
+ for (const it of items) {
1719
+ if (it.separator) {
1720
+ const sep = parentDoc.createElement("div");
1721
+ sep.style.cssText = "border-top:1px solid var(--color-border,#ddd); margin: 4px 0;";
1722
+ menu.appendChild(sep);
1723
+ continue;
1724
+ }
1725
+ const btn = parentDoc.createElement("button");
1726
+ btn.type = "button";
1727
+ btn.textContent = it.label;
1728
+ btn.style.cssText = `
1729
+ display: block; width: 100%; text-align: left;
1730
+ padding: 5px 12px; border: none; background: none;
1731
+ color: inherit; cursor: pointer; font: inherit;
1732
+ ${it.emphasized ? "font-weight: 600;" : ""}
1733
+ `;
1734
+ btn.addEventListener("mouseenter", () => {
1735
+ btn.style.background = "var(--color-bg-hover, #eef)";
1736
+ });
1737
+ btn.addEventListener("mouseleave", () => {
1738
+ btn.style.background = "none";
1739
+ });
1740
+ btn.addEventListener("click", () => {
1741
+ try {
1742
+ it.action();
1743
+ } finally {
1744
+ menu.remove();
1850
1745
  }
1851
- return self;
1746
+ });
1747
+ menu.appendChild(btn);
1748
+ }
1749
+ parentDoc.body.appendChild(menu);
1750
+ const r = menu.getBoundingClientRect();
1751
+ if (r.right > window.innerWidth)
1752
+ menu.style.left = `${Math.max(8, window.innerWidth - r.width - 8)}px`;
1753
+ if (r.bottom > window.innerHeight)
1754
+ menu.style.top = `${Math.max(8, window.innerHeight - r.height - 8)}px`;
1755
+ const docs = [parentDoc, ...extraDismissDocs];
1756
+ const dismiss2 = (e) => {
1757
+ if (e.type === "keydown" && e.key !== "Escape")
1758
+ return;
1759
+ if (e.type === "mousedown" && menu.contains(e.target))
1760
+ return;
1761
+ menu.remove();
1762
+ for (const d of docs) {
1763
+ d.removeEventListener("mousedown", dismiss2, true);
1764
+ d.removeEventListener("keydown", dismiss2, true);
1852
1765
  }
1766
+ };
1767
+ setTimeout(() => {
1768
+ for (const d of docs) {
1769
+ d.addEventListener("mousedown", dismiss2, true);
1770
+ d.addEventListener("keydown", dismiss2, true);
1771
+ }
1772
+ }, 0);
1773
+ }
1774
+ function getWordAtPoint(root, x, y) {
1775
+ const doc = root.ownerDocument || document;
1776
+ let node = null;
1777
+ let offset = 0;
1778
+ const winAny = doc.defaultView;
1779
+ if (typeof doc.caretPositionFromPoint === "function") {
1780
+ const pos = doc.caretPositionFromPoint(x, y);
1781
+ if (pos) {
1782
+ node = pos.offsetNode;
1783
+ offset = pos.offset;
1784
+ }
1785
+ } else if (typeof doc.caretRangeFromPoint === "function") {
1786
+ const range = doc.caretRangeFromPoint(x, y);
1787
+ if (range) {
1788
+ node = range.startContainer;
1789
+ offset = range.startOffset;
1790
+ }
1791
+ }
1792
+ if (!node || node.nodeType !== Node.TEXT_NODE)
1793
+ return null;
1794
+ let walk = node;
1795
+ while (walk && walk !== root)
1796
+ walk = walk.parentNode;
1797
+ if (!walk)
1798
+ return null;
1799
+ let p = node.parentNode;
1800
+ while (p && p !== root) {
1801
+ if (p.nodeType === Node.ELEMENT_NODE && SKIP_TAGS.has(p.tagName))
1802
+ return null;
1803
+ p = p.parentNode;
1804
+ }
1805
+ const text = node.data;
1806
+ if (offset > text.length)
1807
+ offset = text.length;
1808
+ const isWordChar = (c) => /[\p{L}'’\-]/u.test(c);
1809
+ let start = offset;
1810
+ while (start > 0 && isWordChar(text[start - 1]))
1811
+ start--;
1812
+ let end = offset;
1813
+ while (end < text.length && isWordChar(text[end]))
1814
+ end++;
1815
+ if (end - start < MIN_WORD_LEN)
1816
+ return null;
1817
+ const word = text.slice(start, end);
1818
+ if (!/^[\p{L}]/u.test(word))
1819
+ return null;
1820
+ return { word, node, start, end };
1821
+ }
1822
+ var import_nspell, USER_DICT_KEY, MARKER_ATTR, MIN_WORD_LEN, SKIP_TAGS, spellPromise;
1823
+ var init_spellcheck_core = __esm({
1824
+ "client/compose/spellcheck-core.js"() {
1825
+ "use strict";
1826
+ import_nspell = __toESM(require_lib(), 1);
1827
+ init_api_client();
1828
+ USER_DICT_KEY = "mailx-user-dict";
1829
+ MARKER_ATTR = "data-mailx-spellerror";
1830
+ MIN_WORD_LEN = 3;
1831
+ SKIP_TAGS = /* @__PURE__ */ new Set(["BLOCKQUOTE", "CODE", "PRE", "A", "SCRIPT", "STYLE", "KBD", "SAMP", "VAR"]);
1832
+ spellPromise = null;
1853
1833
  }
1854
1834
  });
1855
1835
 
1856
- // node_modules/nspell/lib/index.js
1857
- var require_lib = __commonJS({
1858
- "node_modules/nspell/lib/index.js"(exports, module) {
1859
- "use strict";
1860
- var buffer = require_is_buffer();
1861
- var affix = require_affix();
1862
- module.exports = NSpell2;
1863
- var proto = NSpell2.prototype;
1864
- proto.correct = require_correct();
1865
- proto.suggest = require_suggest();
1866
- proto.spell = require_spell();
1867
- proto.add = require_add2();
1868
- proto.remove = require_remove();
1869
- proto.wordCharacters = require_word_characters();
1870
- proto.dictionary = require_dictionary2();
1871
- proto.personal = require_personal();
1872
- function NSpell2(aff, dic) {
1873
- var index = -1;
1874
- var dictionaries;
1875
- if (!(this instanceof NSpell2)) {
1876
- return new NSpell2(aff, dic);
1877
- }
1878
- if (typeof aff === "string" || buffer(aff)) {
1879
- if (typeof dic === "string" || buffer(dic)) {
1880
- dictionaries = [{ dic }];
1881
- }
1882
- } else if (aff) {
1883
- if ("length" in aff) {
1884
- dictionaries = aff;
1885
- aff = aff[0] && aff[0].aff;
1886
- } else {
1887
- if (aff.dic) {
1888
- dictionaries = [aff];
1836
+ // client/lib/rmf-tiny.js
1837
+ var rmf_tiny_exports = {};
1838
+ __export(rmf_tiny_exports, {
1839
+ createTinyMceEditor: () => createTinyMceEditor
1840
+ });
1841
+ async function loadTinymce(opts) {
1842
+ try {
1843
+ const mod = await import(
1844
+ /* @vite-ignore */
1845
+ "tinymce"
1846
+ );
1847
+ const tinymce = mod.default || mod;
1848
+ await Promise.all([
1849
+ import("tinymce/themes/silver").catch(() => {
1850
+ }),
1851
+ import("tinymce/icons/default").catch(() => {
1852
+ }),
1853
+ import("tinymce/models/dom").catch(() => {
1854
+ }),
1855
+ import("tinymce/plugins/paste").catch(() => {
1856
+ }),
1857
+ import("tinymce/plugins/lists").catch(() => {
1858
+ }),
1859
+ import("tinymce/plugins/link").catch(() => {
1860
+ }),
1861
+ import("tinymce/plugins/table").catch(() => {
1862
+ }),
1863
+ import("tinymce/plugins/code").catch(() => {
1864
+ }),
1865
+ import("tinymce/plugins/image").catch(() => {
1866
+ })
1867
+ ]);
1868
+ return tinymce;
1869
+ } catch {
1870
+ }
1871
+ const w = window;
1872
+ if (w.tinymce)
1873
+ return w.tinymce;
1874
+ if (!opts.cdnUrl) {
1875
+ throw new Error("rmf-tiny: tinymce not installed (npm install tinymce) and no cdnUrl supplied. See README for Android setup.");
1876
+ }
1877
+ const url = opts.apiKey && !opts.cdnUrl.includes("api-key") ? `${opts.cdnUrl}${opts.cdnUrl.includes("?") ? "&" : "?"}apiKey=${encodeURIComponent(opts.apiKey)}` : opts.cdnUrl;
1878
+ await new Promise((resolve, reject) => {
1879
+ const s = document.createElement("script");
1880
+ s.src = url;
1881
+ s.referrerPolicy = "origin";
1882
+ s.onload = () => resolve();
1883
+ s.onerror = () => reject(new Error(`rmf-tiny: failed to load TinyMCE from ${url}`));
1884
+ document.head.appendChild(s);
1885
+ });
1886
+ if (!w.tinymce)
1887
+ throw new Error("rmf-tiny: TinyMCE script loaded but window.tinymce is missing");
1888
+ return w.tinymce;
1889
+ }
1890
+ async function createTinyMceEditor(container2, opts = {}) {
1891
+ const tinymce = await loadTinymce(opts);
1892
+ const target = document.createElement("div");
1893
+ target.id = `rmf-tiny-${Math.random().toString(36).slice(2, 10)}`;
1894
+ target.style.cssText = "width:100%;height:100%;";
1895
+ container2.appendChild(target);
1896
+ const editor2 = await new Promise((resolve) => {
1897
+ tinymce.init({
1898
+ target,
1899
+ // Word-paste fidelity is the entire point of using TinyMCE here.
1900
+ // `paste_as_text: false` keeps formatting; powerpaste isn't in
1901
+ // OSS but the standard paste plugin handles Word reasonably well.
1902
+ // All free / OSS plugins bundled in the jsDelivr tinymce@6 script.
1903
+ // No premium plugins listed — listing one without an API key
1904
+ // triggers TinyMCE's upsell dialog on every load.
1905
+ plugins: "paste lists advlist link table code codesample image searchreplace autolink wordcount emoticons charmap insertdatetime quickbars nonbreaking directionality help",
1906
+ toolbar: [
1907
+ "undo redo | bold italic underline strikethrough | forecolor backcolor",
1908
+ "bullist numlist outdent indent | link table image code codesample | emoticons charmap | help"
1909
+ ].join(" | "),
1910
+ // Include "tools" so wordcount and searchreplace are reachable.
1911
+ menubar: "file edit view insert format tools",
1912
+ // View menu — append zoom commands. fullscreen omitted: this editor
1913
+ // lives inside a compose iframe overlay that already fills its host
1914
+ // window; TinyMCE's fullscreen plugin re-positions the container in
1915
+ // ways that produce a blank body, so it's not in the plugin list.
1916
+ menu: {
1917
+ view: { title: "View", items: "code | visualaid visualchars visualblocks | preview | zoomIn zoomOut zoomReset" }
1918
+ },
1919
+ // Disable the "Get all features" upsell badge that TinyMCE 6+
1920
+ // injects automatically when no premium plugins are listed.
1921
+ promotion: false,
1922
+ // Floating toolbar that appears on text selection — keeps the
1923
+ // main toolbar uncluttered while exposing common formatters
1924
+ // where the cursor already is. quickbars_insert_toolbar:false
1925
+ // suppresses the second (insert-on-blank-line) popup which
1926
+ // clutters more than it helps in an email composer.
1927
+ quickbars_selection_toolbar: "bold italic underline | forecolor | quicklink blockquote",
1928
+ quickbars_insert_toolbar: false,
1929
+ quickbars_image_toolbar: "alignleft aligncenter alignright",
1930
+ // Code-sample dropdown languages. TinyMCE's default list omits
1931
+ // "Text" / plain — every option triggers syntax highlighting
1932
+ // which mangles unrelated paste content (Bob 2026-05-24).
1933
+ // Adding Text first so it's the default; rest are the modern
1934
+ // languages we actually paste.
1935
+ codesample_languages: [
1936
+ { text: "Text", value: "text" },
1937
+ { text: "HTML/XML", value: "markup" },
1938
+ { text: "JavaScript", value: "javascript" },
1939
+ { text: "TypeScript", value: "typescript" },
1940
+ { text: "CSS", value: "css" },
1941
+ { text: "JSON", value: "json" },
1942
+ { text: "Python", value: "python" },
1943
+ { text: "Java", value: "java" },
1944
+ { text: "C", value: "c" },
1945
+ { text: "C++", value: "cpp" },
1946
+ { text: "C#", value: "csharp" },
1947
+ { text: "Go", value: "go" },
1948
+ { text: "Rust", value: "rust" },
1949
+ { text: "Ruby", value: "ruby" },
1950
+ { text: "PHP", value: "php" },
1951
+ { text: "Shell", value: "bash" },
1952
+ { text: "SQL", value: "sql" }
1953
+ ],
1954
+ // WebView's native spell-check (red underlines, right-click
1955
+ // "Add to dictionary"). Free; same UX as Quill's spellcheck=true.
1956
+ // Premium tinymcespellchecker plugin would replace this with a
1957
+ // custom backend via spellchecker_rpc_url, but requires a
1958
+ // Tiny Cloud subscription.
1959
+ browser_spellcheck: true,
1960
+ // Right-click context menu DISABLED so WebView2's native menu
1961
+ // fires instead. We need the native menu because that's where
1962
+ // the browser shows spelling suggestions (TinyMCE's contextmenu
1963
+ // intercepts the right-click and replaces the menu — red
1964
+ // underlines appear but suggestions are unreachable). Trade-off:
1965
+ // lose TinyMCE's link / image / table quick actions. Standard
1966
+ // text formatting is still on the toolbar.
1967
+ contextmenu: false,
1968
+ statusbar: false,
1969
+ branding: false,
1970
+ license_key: "gpl",
1971
+ paste_data_images: true,
1972
+ // Permissive valid_elements — preserve as much of the source
1973
+ // formatting as possible. The default is more aggressive about
1974
+ // stripping. Empty string for `valid_elements` means accept
1975
+ // everything that the schema allows.
1976
+ 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]",
1977
+ 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",
1978
+ // Auto-link bare URLs in pasted content. TinyMCE's `autolink`
1979
+ // plugin only fires on TYPED space/enter; URLs that arrive via
1980
+ // clipboard (browser address bar, terminal copy) come in as
1981
+ // plain text and stay un-linked. paste_preprocess runs on the
1982
+ // HTML the paste plugin produced.
1983
+ //
1984
+ // CRITICAL: skip auto-link when the content already contains
1985
+ // anchors. Naive regex over the whole content would wrap
1986
+ // `<a href="X">X</a>` in ANOTHER anchor (nested anchors are
1987
+ // invalid HTML and browsers split them, producing visible
1988
+ // junk). For HTML pastes that already have linked URLs (the
1989
+ // common case from a browser address bar or another mail
1990
+ // client), TinyMCE preserves them — no auto-link pass needed.
1991
+ // For plain-text pastes (no anchors present), wrap any bare
1992
+ // http(s)://… runs. Trailing sentence punctuation is excluded
1993
+ // from the URL.
1994
+ paste_preprocess: (_plugin, args) => {
1995
+ if (/<a[\s>]/i.test(args.content))
1996
+ return;
1997
+ args.content = args.content.replace(/(^|[\s(\[])((?:https?|ftp):\/\/[^\s<>"']+[^\s<>"'.,;:!?)\]])/gi, (_m, lead, url) => `${lead}<a href="${url}">${url}</a>`);
1998
+ },
1999
+ // Body font + quoted-reply styling. Without explicit rules for
2000
+ // <blockquote> and div.reply the editor iframe renders the
2001
+ // quoted block with no visual distinction from the user's own
2002
+ // text — pasted reply quotes vanish into a wall of unformatted
2003
+ // paragraphs (Bob 2026-05-12: "the body losing all formatting").
2004
+ // The styles below match Thunderbird/Outlook conventions: a
2005
+ // muted left-bar + indent on blockquotes, slight color shift on
2006
+ // the "On … wrote:" attribution, untouched typography for
2007
+ // everything else so genuine HTML formatting (bold / italic /
2008
+ // tables / inline color) still comes through verbatim.
2009
+ content_style: [
2010
+ // Dark-blue native caret bar. caret-shape:block produced a
2011
+ // column wider than the gap between letters AND caused the
2012
+ // caret to "scoot back to its old position" after an arrow
2013
+ // press (Chromium block-caret quirk with the arrow-key
2014
+ // selection adjustment, Bob 2026-05-24). Plain bar is
2015
+ // browser-default and behaves correctly with arrow keys;
2016
+ // just darken the color so it stays visible.
2017
+ "body { font-family: system-ui, sans-serif; font-size: 14px; caret-color: #0a2647; }",
2018
+ "blockquote { border-left: 3px solid #c0c8d0; margin: 0 0 0 4px; padding: 2px 0 2px 10px; color: #555; }",
2019
+ "div.reply { margin-top: 0.5em; }",
2020
+ "div.reply > p:first-child { color: #666; font-size: 0.95em; margin: 0 0 4px 0; }",
2021
+ "pre, code { font-family: ui-monospace, Consolas, Menlo, monospace; }"
2022
+ ].join(" "),
2023
+ init_instance_callback: (ed) => resolve(ed),
2024
+ setup: (ed) => {
2025
+ if (opts.initialHtml)
2026
+ ed.on("init", () => ed.setContent(opts.initialHtml));
2027
+ const ZOOM_DEFAULT = 14;
2028
+ const ZOOM_STEP = 2;
2029
+ const ZOOM_MIN = 8;
2030
+ const ZOOM_MAX = 32;
2031
+ const ZOOM_STORAGE_KEY = "rmf-tiny:zoom-px";
2032
+ let zoomPx = ZOOM_DEFAULT;
2033
+ try {
2034
+ const stored = Number(localStorage.getItem(ZOOM_STORAGE_KEY));
2035
+ if (Number.isFinite(stored) && stored >= ZOOM_MIN && stored <= ZOOM_MAX) {
2036
+ zoomPx = stored;
1889
2037
  }
1890
- aff = aff.aff;
2038
+ } catch {
1891
2039
  }
2040
+ const saveZoom = () => {
2041
+ try {
2042
+ localStorage.setItem(ZOOM_STORAGE_KEY, String(zoomPx));
2043
+ } catch {
2044
+ }
2045
+ };
2046
+ const applyZoom = () => {
2047
+ try {
2048
+ const body = ed.getBody();
2049
+ if (body)
2050
+ body.style.fontSize = `${zoomPx}px`;
2051
+ } catch {
2052
+ }
2053
+ };
2054
+ const bumpZoom = (delta) => {
2055
+ zoomPx = Math.max(ZOOM_MIN, Math.min(ZOOM_MAX, zoomPx + delta));
2056
+ applyZoom();
2057
+ saveZoom();
2058
+ };
2059
+ ed.ui.registry.addMenuItem("zoomIn", {
2060
+ text: "Zoom in",
2061
+ shortcut: "Ctrl+=",
2062
+ onAction: () => bumpZoom(ZOOM_STEP)
2063
+ });
2064
+ ed.ui.registry.addMenuItem("zoomOut", {
2065
+ text: "Zoom out",
2066
+ shortcut: "Ctrl+-",
2067
+ onAction: () => bumpZoom(-ZOOM_STEP)
2068
+ });
2069
+ ed.ui.registry.addMenuItem("zoomReset", {
2070
+ text: "Reset zoom",
2071
+ shortcut: "Ctrl+0",
2072
+ onAction: () => {
2073
+ zoomPx = ZOOM_DEFAULT;
2074
+ applyZoom();
2075
+ saveZoom();
2076
+ }
2077
+ });
2078
+ const installLinkEscape = () => {
2079
+ const doc = ed.getDoc();
2080
+ if (!doc || doc.__rmfLinkEscapeInstalled)
2081
+ return;
2082
+ doc.__rmfLinkEscapeInstalled = true;
2083
+ doc.addEventListener("beforeinput", (e) => {
2084
+ const kind = e.inputType || "";
2085
+ const isInsert = kind === "insertText" || kind === "insertCompositionText" || kind === "insertFromPaste" || kind === "insertFromDrop" || kind === "insertFromComposition";
2086
+ if (!isInsert)
2087
+ return;
2088
+ const sel = doc.getSelection();
2089
+ if (!sel || sel.rangeCount === 0)
2090
+ return;
2091
+ const rng = sel.getRangeAt(0);
2092
+ if (!rng.collapsed)
2093
+ return;
2094
+ const node = rng.startContainer;
2095
+ const a = node.nodeType === Node.ELEMENT_NODE ? node : node.parentNode;
2096
+ if (!a)
2097
+ return;
2098
+ const link = a.closest?.("a");
2099
+ if (!link)
2100
+ return;
2101
+ let tail;
2102
+ try {
2103
+ tail = rng.cloneRange();
2104
+ tail.setEndAfter(link);
2105
+ } catch {
2106
+ return;
2107
+ }
2108
+ if (tail.toString().length > 0)
2109
+ return;
2110
+ const after = doc.createRange();
2111
+ after.setStartAfter(link);
2112
+ after.collapse(true);
2113
+ sel.removeAllRanges();
2114
+ sel.addRange(after);
2115
+ }, true);
2116
+ };
2117
+ ed.on("init", installLinkEscape);
2118
+ ed.on("SetContent", installLinkEscape);
2119
+ ed.on("init", () => {
2120
+ try {
2121
+ const body = ed.getBody();
2122
+ if (body) {
2123
+ body.setAttribute("spellcheck", "true");
2124
+ body.setAttribute("lang", "en");
2125
+ }
2126
+ const doc = ed.getDoc();
2127
+ if (doc?.documentElement)
2128
+ doc.documentElement.setAttribute("lang", "en");
2129
+ } catch {
2130
+ }
2131
+ if (zoomPx !== ZOOM_DEFAULT)
2132
+ applyZoom();
2133
+ try {
2134
+ const doc = ed.getDoc();
2135
+ doc.addEventListener("wheel", (e) => {
2136
+ if (!e.ctrlKey)
2137
+ return;
2138
+ e.preventDefault();
2139
+ bumpZoom(e.deltaY < 0 ? ZOOM_STEP : -ZOOM_STEP);
2140
+ }, { passive: false });
2141
+ doc.addEventListener("keydown", (e) => {
2142
+ if (!(e.ctrlKey || e.metaKey))
2143
+ return;
2144
+ if (e.key === "=" || e.key === "+") {
2145
+ e.preventDefault();
2146
+ e.stopPropagation();
2147
+ bumpZoom(ZOOM_STEP);
2148
+ } else if (e.key === "-") {
2149
+ e.preventDefault();
2150
+ e.stopPropagation();
2151
+ bumpZoom(-ZOOM_STEP);
2152
+ } else if (e.key === "0") {
2153
+ e.preventDefault();
2154
+ e.stopPropagation();
2155
+ zoomPx = ZOOM_DEFAULT;
2156
+ applyZoom();
2157
+ }
2158
+ }, true);
2159
+ } catch {
2160
+ }
2161
+ });
1892
2162
  }
1893
- if (!aff) {
1894
- throw new Error("Missing `aff` in dictionary");
1895
- }
1896
- aff = affix(aff);
1897
- this.data = /* @__PURE__ */ Object.create(null);
1898
- this.compoundRuleCodes = aff.compoundRuleCodes;
1899
- this.replacementTable = aff.replacementTable;
1900
- this.conversion = aff.conversion;
1901
- this.compoundRules = aff.compoundRules;
1902
- this.rules = aff.rules;
1903
- this.flags = aff.flags;
1904
- if (dictionaries) {
1905
- while (++index < dictionaries.length) {
1906
- if (dictionaries[index].dic) {
1907
- this.dictionary(dictionaries[index].dic);
2163
+ });
2164
+ });
2165
+ return {
2166
+ setHtml(html) {
2167
+ editor2.setContent(html);
2168
+ },
2169
+ getHtml() {
2170
+ return editor2.getContent();
2171
+ },
2172
+ getText() {
2173
+ return editor2.getContent({ format: "text" });
2174
+ },
2175
+ focus() {
2176
+ editor2.focus();
2177
+ },
2178
+ setCursor(pos) {
2179
+ const place = () => {
2180
+ const body = editor2.getBody();
2181
+ if (pos === 0) {
2182
+ const first = body.firstChild;
2183
+ if (first && first.nodeType === 1) {
2184
+ editor2.selection.setCursorLocation(first, 0);
2185
+ } else {
2186
+ editor2.selection.select(body, true);
2187
+ editor2.selection.collapse(true);
1908
2188
  }
2189
+ editor2.focus();
2190
+ editor2.getWin()?.scrollTo(0, 0);
2191
+ } else {
2192
+ editor2.selection.select(body, true);
2193
+ editor2.selection.collapse(false);
2194
+ editor2.focus();
2195
+ editor2.selection.scrollIntoView();
1909
2196
  }
2197
+ };
2198
+ try {
2199
+ place();
2200
+ editor2.getWin()?.requestAnimationFrame?.(() => {
2201
+ try {
2202
+ place();
2203
+ } catch {
2204
+ }
2205
+ });
2206
+ } catch {
1910
2207
  }
1911
- }
2208
+ },
2209
+ get root() {
2210
+ return editor2.getContainer();
2211
+ },
2212
+ on(event, handler) {
2213
+ editor2.on(event, handler);
2214
+ },
2215
+ off(event, handler) {
2216
+ editor2.off(event, handler);
2217
+ },
2218
+ nativeEditor: editor2
2219
+ };
2220
+ }
2221
+ var init_rmf_tiny = __esm({
2222
+ "client/lib/rmf-tiny.js"() {
2223
+ "use strict";
1912
2224
  }
1913
2225
  });
1914
2226
 
@@ -1917,10 +2229,10 @@ var spellcheck_exports = {};
1917
2229
  __export(spellcheck_exports, {
1918
2230
  wireSpellcheck: () => wireSpellcheck
1919
2231
  });
1920
- async function getSpell() {
1921
- if (spellPromise)
1922
- return spellPromise;
1923
- spellPromise = (async () => {
2232
+ async function getSpell2() {
2233
+ if (spellPromise2)
2234
+ return spellPromise2;
2235
+ spellPromise2 = (async () => {
1924
2236
  const [affRes, dicRes] = await Promise.all([
1925
2237
  fetch("../lib/dict/en.aff"),
1926
2238
  fetch("../lib/dict/en.dic")
@@ -1929,9 +2241,9 @@ async function getSpell() {
1929
2241
  throw new Error(`spellcheck: dict fetch failed (aff=${affRes.status} dic=${dicRes.status})`);
1930
2242
  }
1931
2243
  const [aff, dic] = await Promise.all([affRes.text(), dicRes.text()]);
1932
- const sp = new import_nspell.default({ aff, dic });
2244
+ const sp = new import_nspell2.default({ aff, dic });
1933
2245
  try {
1934
- const raw = localStorage.getItem(USER_DICT_KEY);
2246
+ const raw = localStorage.getItem(USER_DICT_KEY2);
1935
2247
  if (raw)
1936
2248
  for (const w of JSON.parse(raw))
1937
2249
  sp.add(w);
@@ -1943,7 +2255,7 @@ async function getSpell() {
1943
2255
  sp.add(w);
1944
2256
  let local = [];
1945
2257
  try {
1946
- const raw = localStorage.getItem(USER_DICT_KEY);
2258
+ const raw = localStorage.getItem(USER_DICT_KEY2);
1947
2259
  local = raw ? JSON.parse(raw) : [];
1948
2260
  } catch {
1949
2261
  local = [];
@@ -1955,22 +2267,22 @@ async function getSpell() {
1955
2267
  }
1956
2268
  try {
1957
2269
  const merged = [.../* @__PURE__ */ new Set([...local, ...cloudArr])];
1958
- localStorage.setItem(USER_DICT_KEY, JSON.stringify(merged));
2270
+ localStorage.setItem(USER_DICT_KEY2, JSON.stringify(merged));
1959
2271
  } catch {
1960
2272
  }
1961
2273
  }).catch(() => {
1962
2274
  });
1963
2275
  return sp;
1964
2276
  })();
1965
- return spellPromise;
2277
+ return spellPromise2;
1966
2278
  }
1967
- function addToUserDict(word, sp) {
2279
+ function addToUserDict2(word, sp) {
1968
2280
  try {
1969
- const raw = localStorage.getItem(USER_DICT_KEY);
2281
+ const raw = localStorage.getItem(USER_DICT_KEY2);
1970
2282
  const arr = raw ? JSON.parse(raw) : [];
1971
2283
  if (!arr.includes(word)) {
1972
2284
  arr.push(word);
1973
- localStorage.setItem(USER_DICT_KEY, JSON.stringify(arr));
2285
+ localStorage.setItem(USER_DICT_KEY2, JSON.stringify(arr));
1974
2286
  }
1975
2287
  } catch {
1976
2288
  }
@@ -1998,7 +2310,7 @@ function decorate(editor2, sp) {
1998
2310
  const savedBodyScrollTop = body.scrollTop;
1999
2311
  try {
2000
2312
  editor2.undoManager?.ignore?.(() => {
2001
- const old = body.querySelectorAll(`span[${MARKER_ATTR}]`);
2313
+ const old = body.querySelectorAll(`span[${MARKER_ATTR2}]`);
2002
2314
  for (const m of old) {
2003
2315
  const parent2 = m.parentNode;
2004
2316
  if (!parent2)
@@ -2012,7 +2324,7 @@ function decorate(editor2, sp) {
2012
2324
  acceptNode(node) {
2013
2325
  let p = node.parentNode;
2014
2326
  while (p && p !== body) {
2015
- if (p.nodeType === Node.ELEMENT_NODE && SKIP_TAGS.has(p.tagName)) {
2327
+ if (p.nodeType === Node.ELEMENT_NODE && SKIP_TAGS2.has(p.tagName)) {
2016
2328
  return NodeFilter.FILTER_REJECT;
2017
2329
  }
2018
2330
  p = p.parentNode;
@@ -2047,7 +2359,7 @@ function decorate(editor2, sp) {
2047
2359
  WORD_RE.lastIndex = 0;
2048
2360
  while ((m = WORD_RE.exec(text)) !== null) {
2049
2361
  const word = m[0];
2050
- if (word.length < MIN_WORD_LEN)
2362
+ if (word.length < MIN_WORD_LEN2)
2051
2363
  continue;
2052
2364
  const wStart = m.index, wEnd = m.index + word.length;
2053
2365
  if (emailRanges.some(([s, e]) => wStart < e && wEnd > s))
@@ -2067,7 +2379,7 @@ function decorate(editor2, sp) {
2067
2379
  range.setStart(h.node, h.start);
2068
2380
  range.setEnd(h.node, h.end);
2069
2381
  const span = doc.createElement("span");
2070
- span.setAttribute(MARKER_ATTR, "1");
2382
+ span.setAttribute(MARKER_ATTR2, "1");
2071
2383
  try {
2072
2384
  range.surroundContents(span);
2073
2385
  } catch {
@@ -2172,7 +2484,7 @@ function installDecorationStyle(editor2) {
2172
2484
  const style = doc.createElement("style");
2173
2485
  style.id = "mailx-spell-style";
2174
2486
  style.textContent = `
2175
- span[${MARKER_ATTR}] {
2487
+ span[${MARKER_ATTR2}] {
2176
2488
  text-decoration: underline wavy #d33;
2177
2489
  text-decoration-skip-ink: none;
2178
2490
  text-underline-offset: 2px;
@@ -2188,7 +2500,7 @@ function installSerializerFilter(editor2) {
2188
2500
  return;
2189
2501
  editor2.__mailxSpellSerializerWired = true;
2190
2502
  try {
2191
- editor2.serializer.addAttributeFilter(MARKER_ATTR, (nodes) => {
2503
+ editor2.serializer.addAttributeFilter(MARKER_ATTR2, (nodes) => {
2192
2504
  for (const node of nodes) {
2193
2505
  if (typeof node.unwrap === "function")
2194
2506
  node.unwrap();
@@ -2198,7 +2510,7 @@ function installSerializerFilter(editor2) {
2198
2510
  console.warn("[spellcheck] serializer filter setup failed:", e);
2199
2511
  }
2200
2512
  }
2201
- function showSuggestionsMenu(parentDoc, x, y, items) {
2513
+ function showSuggestionsMenu2(parentDoc, x, y, items) {
2202
2514
  parentDoc.getElementById("mailx-spell-menu")?.remove();
2203
2515
  const menu = parentDoc.createElement("div");
2204
2516
  menu.id = "mailx-spell-menu";
@@ -2313,7 +2625,7 @@ function cleanupCorrected(editor2, sp) {
2313
2625
  const activeSel = doc.getSelection();
2314
2626
  if (activeSel && activeSel.rangeCount > 0 && !activeSel.isCollapsed)
2315
2627
  return;
2316
- const markers = body.querySelectorAll(`span[${MARKER_ATTR}]`);
2628
+ const markers = body.querySelectorAll(`span[${MARKER_ATTR2}]`);
2317
2629
  if (markers.length === 0)
2318
2630
  return;
2319
2631
  let caretMarker = null;
@@ -2321,7 +2633,7 @@ function cleanupCorrected(editor2, sp) {
2321
2633
  if (sel && sel.rangeCount > 0) {
2322
2634
  let p = sel.focusNode;
2323
2635
  while (p && p !== body) {
2324
- if (p.nodeType === Node.ELEMENT_NODE && p.hasAttribute?.(MARKER_ATTR)) {
2636
+ if (p.nodeType === Node.ELEMENT_NODE && p.hasAttribute?.(MARKER_ATTR2)) {
2325
2637
  caretMarker = p;
2326
2638
  break;
2327
2639
  }
@@ -2395,7 +2707,7 @@ function wireSpellcheck(editor2) {
2395
2707
  cleanupCorrected(editor2, sp);
2396
2708
  }, CLEANUP_DEBOUNCE_MS);
2397
2709
  };
2398
- getSpell().then((loaded) => {
2710
+ getSpell2().then((loaded) => {
2399
2711
  sp = loaded;
2400
2712
  installDecorationStyle(editor2);
2401
2713
  installSerializerFilter(editor2);
@@ -2411,7 +2723,7 @@ function wireSpellcheck(editor2) {
2411
2723
  const target = e.target;
2412
2724
  if (!target)
2413
2725
  return;
2414
- const marker = target.closest?.(`span[${MARKER_ATTR}]`);
2726
+ const marker = target.closest?.(`span[${MARKER_ATTR2}]`);
2415
2727
  if (!marker)
2416
2728
  return;
2417
2729
  const word = marker.textContent || "";
@@ -2458,7 +2770,7 @@ function wireSpellcheck(editor2) {
2458
2770
  label: `Add "${word}" to dictionary`,
2459
2771
  action: () => {
2460
2772
  if (sp)
2461
- addToUserDict(word, sp);
2773
+ addToUserDict2(word, sp);
2462
2774
  scheduleDecorate();
2463
2775
  }
2464
2776
  });
@@ -2470,22 +2782,22 @@ function wireSpellcheck(editor2) {
2470
2782
  scheduleDecorate();
2471
2783
  }
2472
2784
  });
2473
- showSuggestionsMenu(document, iframeRect.left + e.clientX, iframeRect.top + e.clientY, items);
2785
+ showSuggestionsMenu2(document, iframeRect.left + e.clientX, iframeRect.top + e.clientY, items);
2474
2786
  }, true);
2475
2787
  }
2476
- var import_nspell, USER_DICT_KEY, MARKER_ATTR, DECORATE_DEBOUNCE_MS, CLEANUP_DEBOUNCE_MS, MIN_WORD_LEN, SKIP_TAGS, spellPromise;
2788
+ var import_nspell2, USER_DICT_KEY2, MARKER_ATTR2, DECORATE_DEBOUNCE_MS, CLEANUP_DEBOUNCE_MS, MIN_WORD_LEN2, SKIP_TAGS2, spellPromise2;
2477
2789
  var init_spellcheck = __esm({
2478
2790
  "client/compose/spellcheck.js"() {
2479
2791
  "use strict";
2480
- import_nspell = __toESM(require_lib(), 1);
2792
+ import_nspell2 = __toESM(require_lib(), 1);
2481
2793
  init_api_client();
2482
- USER_DICT_KEY = "mailx-user-dict";
2483
- MARKER_ATTR = "data-mailx-spellerror";
2794
+ USER_DICT_KEY2 = "mailx-user-dict";
2795
+ MARKER_ATTR2 = "data-mailx-spellerror";
2484
2796
  DECORATE_DEBOUNCE_MS = 1200;
2485
2797
  CLEANUP_DEBOUNCE_MS = 300;
2486
- MIN_WORD_LEN = 3;
2487
- SKIP_TAGS = /* @__PURE__ */ new Set(["BLOCKQUOTE", "CODE", "PRE", "A", "SCRIPT", "STYLE", "KBD", "SAMP", "VAR"]);
2488
- spellPromise = null;
2798
+ MIN_WORD_LEN2 = 3;
2799
+ SKIP_TAGS2 = /* @__PURE__ */ new Set(["BLOCKQUOTE", "CODE", "PRE", "A", "SCRIPT", "STYLE", "KBD", "SAMP", "VAR"]);
2800
+ spellPromise2 = null;
2489
2801
  }
2490
2802
  });
2491
2803
 
@@ -2952,6 +3264,72 @@ function createQuillEditor(container2) {
2952
3264
  toolbar?.addHandler("link", function() {
2953
3265
  openLinkForRange(q, q.getSelection() || { index: q.getLength() - 1, length: 0 });
2954
3266
  });
3267
+ let _spellSp = null;
3268
+ Promise.resolve().then(() => (init_spellcheck_core(), spellcheck_core_exports)).then((m) => m.getSpell()).then((sp) => {
3269
+ _spellSp = sp;
3270
+ }).catch(() => {
3271
+ });
3272
+ q.root.addEventListener("contextmenu", async (e) => {
3273
+ try {
3274
+ if (!_spellSp)
3275
+ return;
3276
+ const sel = q.getSelection();
3277
+ if (sel && sel.length > 0)
3278
+ return;
3279
+ const core = await Promise.resolve().then(() => (init_spellcheck_core(), spellcheck_core_exports));
3280
+ const hit = core.getWordAtPoint(q.root, e.clientX, e.clientY);
3281
+ if (!hit)
3282
+ return;
3283
+ const { word, node, start, end } = hit;
3284
+ if (_spellSp.correct(word))
3285
+ return;
3286
+ e.preventDefault();
3287
+ e.stopPropagation();
3288
+ const sugs = core.buildSuggestionList(word, _spellSp);
3289
+ const items = [];
3290
+ if (sugs.length === 0) {
3291
+ items.push({ label: "(no suggestions)", action: () => {
3292
+ } });
3293
+ } else {
3294
+ for (const s of sugs) {
3295
+ items.push({
3296
+ label: s,
3297
+ emphasized: true,
3298
+ action: () => {
3299
+ try {
3300
+ const range = document.createRange();
3301
+ range.setStart(node, start);
3302
+ range.setEnd(node, end);
3303
+ const docSel = document.getSelection();
3304
+ if (docSel) {
3305
+ docSel.removeAllRanges();
3306
+ docSel.addRange(range);
3307
+ if (!document.execCommand("insertText", false, s)) {
3308
+ range.deleteContents();
3309
+ range.insertNode(document.createTextNode(s));
3310
+ }
3311
+ }
3312
+ } catch {
3313
+ }
3314
+ }
3315
+ });
3316
+ }
3317
+ }
3318
+ items.push({ label: "", action: () => {
3319
+ }, separator: true });
3320
+ items.push({
3321
+ label: `Add "${word}" to dictionary`,
3322
+ action: () => core.addToUserDict(word, _spellSp)
3323
+ });
3324
+ items.push({
3325
+ label: "Ignore (this session)",
3326
+ action: () => _spellSp.add(word)
3327
+ });
3328
+ core.showSuggestionsMenu(document, e.clientX, e.clientY, items);
3329
+ } catch (err) {
3330
+ console.warn("[spellcheck] right-click handler error:", err?.message || err);
3331
+ }
3332
+ });
2955
3333
  q.root.addEventListener("contextmenu", async (e) => {
2956
3334
  try {
2957
3335
  const sel = q.getSelection();
@@ -3409,6 +3787,7 @@ window.addEventListener("message", (e) => {
3409
3787
  });
3410
3788
 
3411
3789
  // client/compose/compose.ts
3790
+ installConsoleCapture();
3412
3791
  logClientEvent("compose-module-loaded", { href: location.href, version: window.mailxVersion || "?" });
3413
3792
  var _composeT0 = performance.now();
3414
3793
  function _ctick(label) {