@bobfrankston/rmfmail 1.1.174 → 1.1.177

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,2111 @@ 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
- });
210
+ offset = -1;
211
+ while (++offset < alphabet.length) {
212
+ if (source.indexOf(alphabet[offset]) < 0) {
213
+ value.push(alphabet[offset]);
214
+ }
215
+ }
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];
226
+ } else {
227
+ flags[ruleType] = parts[1];
228
+ }
880
229
  }
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);
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;
906
307
  }
907
- editor2.focus();
908
- editor2.getWin()?.scrollTo(0, 0);
909
- } else {
910
- editor2.selection.select(body, true);
911
- editor2.selection.collapse(false);
912
- editor2.focus();
913
- editor2.selection.scrollIntoView();
914
308
  }
915
- };
916
- try {
917
- place();
918
- editor2.getWin()?.requestAnimationFrame?.(() => {
919
- try {
920
- place();
921
- } catch {
922
- }
923
- });
924
- } catch {
925
309
  }
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"() {
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) {
941
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;
343
+ }
344
+ }
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
+ }
942
359
  }
943
360
  });
944
361
 
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
- };
362
+ // node_modules/nspell/lib/correct.js
363
+ var require_correct = __commonJS({
364
+ "node_modules/nspell/lib/correct.js"(exports, module) {
365
+ "use strict";
366
+ var form = require_form();
367
+ module.exports = correct;
368
+ function correct(value) {
369
+ return Boolean(form(this, value));
370
+ }
951
371
  }
952
372
  });
953
373
 
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) {
374
+ // node_modules/nspell/lib/util/casing.js
375
+ var require_casing = __commonJS({
376
+ "node_modules/nspell/lib/util/casing.js"(exports, module) {
957
377
  "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;
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
+ }
397
+ }
398
+ });
399
+
400
+ // node_modules/nspell/lib/suggest.js
401
+ var require_suggest = __commonJS({
402
+ "node_modules/nspell/lib/suggest.js"(exports, module) {
403
+ "use strict";
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);
453
+ }
454
+ }
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);
969
542
  }
970
- return result;
971
543
  }
972
- return value.split(flags.FLAG === "num" ? "," : "");
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]]);
1053
- }
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]]);
1061
- }
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
- }
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;
1072
605
  }
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);
1102
- }
1103
- } catch (_) {
1104
- entry = null;
1105
- }
1106
- if (entry) {
1107
- rule.entries.push(entry);
1108
- }
606
+ if (nextAfter && upper !== nextUpper) {
607
+ check(before + switchCase(nextAfter));
608
+ check(
609
+ before + switchCase(nextCharacter) + switchCase(character) + nextNextAfter
610
+ );
1109
611
  }
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
- }
612
+ check(before + nextAfter);
613
+ if (nextAfter) {
614
+ check(before + nextCharacter + character + nextNextAfter);
1120
615
  }
1121
616
  offset = -1;
1122
- while (++offset < alphabet.length) {
1123
- if (source.indexOf(alphabet[offset]) < 0) {
1124
- value.push(alphabet[offset]);
1125
- }
1126
- }
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];
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);
623
+ }
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);
630
+ }
631
+ }
1139
632
  }
1140
633
  }
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();
1149
- }
1150
- if (!flags.KEEPCASE) {
1151
- flags.KEEPCASE = false;
1152
- }
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);
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);
646
+ }
647
+ }
648
+ if (state) {
649
+ memory.weighted[value]++;
1165
650
  }
1166
651
  }
1167
- }
1168
- function end(source) {
1169
- return new RegExp(source + "$");
1170
- }
1171
- function start(source) {
1172
- return new RegExp("^" + source);
1173
- }
1174
- }
1175
- });
1176
-
1177
- // node_modules/nspell/lib/util/normalize.js
1178
- var require_normalize = __commonJS({
1179
- "node_modules/nspell/lib/util/normalize.js"(exports, module) {
1180
- "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]);
652
+ function switchCase(fragment) {
653
+ var first = fragment.charAt(0);
654
+ return (first.toLowerCase() === first ? first.toUpperCase() : first.toLowerCase()) + fragment.slice(1);
1186
655
  }
1187
- return value;
1188
656
  }
1189
657
  }
1190
658
  });
1191
659
 
1192
- // node_modules/nspell/lib/util/flag.js
1193
- var require_flag = __commonJS({
1194
- "node_modules/nspell/lib/util/flag.js"(exports, module) {
660
+ // node_modules/nspell/lib/spell.js
661
+ var require_spell = __commonJS({
662
+ "node_modules/nspell/lib/spell.js"(exports, module) {
1195
663
  "use strict";
1196
- module.exports = flag;
1197
- function flag(values, value, flags) {
1198
- return flags && value in values && flags.indexOf(values[value]) > -1;
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
+ };
1199
677
  }
1200
678
  }
1201
679
  });
1202
680
 
1203
- // node_modules/nspell/lib/util/exact.js
1204
- var require_exact = __commonJS({
1205
- "node_modules/nspell/lib/util/exact.js"(exports, module) {
681
+ // node_modules/nspell/lib/util/apply.js
682
+ var require_apply = __commonJS({
683
+ "node_modules/nspell/lib/util/apply.js"(exports, module) {
1206
684
  "use strict";
1207
- var flag = require_flag();
1208
- module.exports = exact;
1209
- function exact(context, value) {
685
+ module.exports = apply;
686
+ function apply(value, rule, rules, words) {
1210
687
  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;
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
+ }
707
+ }
1218
708
  }
1219
709
  }
1220
710
  }
1221
- return false;
711
+ return words;
1222
712
  }
1223
713
  }
1224
714
  });
1225
715
 
1226
- // node_modules/nspell/lib/util/form.js
1227
- var require_form = __commonJS({
1228
- "node_modules/nspell/lib/util/form.js"(exports, module) {
716
+ // node_modules/nspell/lib/util/add.js
717
+ var require_add = __commonJS({
718
+ "node_modules/nspell/lib/util/add.js"(exports, module) {
1229
719
  "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;
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);
1244
731
  }
1245
- return normal;
732
+ } else {
733
+ dict[word] = rules.concat();
1246
734
  }
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
- }
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);
1255
747
  }
1256
- alternative = normal.toLowerCase();
1257
- if (alternative !== normal) {
1258
- if (ignore(context.flags, context.data[alternative], all)) {
1259
- return null;
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);
1260
752
  }
1261
- if (exact(context, alternative)) {
1262
- return alternative;
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
+ }
1263
781
  }
1264
782
  }
1265
- return null;
1266
783
  }
1267
- function ignore(flags, dict, all) {
1268
- return flag(flags, "KEEPCASE", dict) || all || flag(flags, "FORBIDDENWORD", dict);
784
+ }
785
+ });
786
+
787
+ // node_modules/nspell/lib/add.js
788
+ var require_add2 = __commonJS({
789
+ "node_modules/nspell/lib/add.js"(exports, module) {
790
+ "use strict";
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;
1269
798
  }
1270
799
  }
1271
800
  });
1272
801
 
1273
- // node_modules/nspell/lib/correct.js
1274
- var require_correct = __commonJS({
1275
- "node_modules/nspell/lib/correct.js"(exports, module) {
802
+ // node_modules/nspell/lib/remove.js
803
+ var require_remove = __commonJS({
804
+ "node_modules/nspell/lib/remove.js"(exports, module) {
805
+ "use strict";
806
+ module.exports = remove;
807
+ function remove(value) {
808
+ var self = this;
809
+ delete self.data[value];
810
+ return self;
811
+ }
812
+ }
813
+ });
814
+
815
+ // node_modules/nspell/lib/word-characters.js
816
+ var require_word_characters = __commonJS({
817
+ "node_modules/nspell/lib/word-characters.js"(exports, module) {
1276
818
  "use strict";
1277
- var form = require_form();
1278
- module.exports = correct;
1279
- function correct(value) {
1280
- return Boolean(form(this, value));
819
+ module.exports = wordCharacters;
820
+ function wordCharacters() {
821
+ return this.flags.WORDCHARS || null;
1281
822
  }
1282
823
  }
1283
824
  });
1284
825
 
1285
- // node_modules/nspell/lib/util/casing.js
1286
- var require_casing = __commonJS({
1287
- "node_modules/nspell/lib/util/casing.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) {
1288
829
  "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;
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);
841
+ }
842
+ last = index + 1;
843
+ index = value.indexOf("\n", last);
1295
844
  }
1296
- rest = exact(rest);
1297
- if (head === rest) {
1298
- return head;
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);
1299
856
  }
1300
- if (head === "u" && rest === "l") {
1301
- return "s";
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);
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);
1302
875
  }
1303
- return null;
1304
- }
1305
- function exact(value) {
1306
- return value === value.toLowerCase() ? "l" : value === value.toUpperCase() ? "u" : null;
1307
876
  }
1308
877
  }
1309
878
  });
1310
879
 
1311
- // node_modules/nspell/lib/suggest.js
1312
- var require_suggest = __commonJS({
1313
- "node_modules/nspell/lib/suggest.js"(exports, module) {
880
+ // node_modules/nspell/lib/dictionary.js
881
+ var require_dictionary2 = __commonJS({
882
+ "node_modules/nspell/lib/dictionary.js"(exports, module) {
1314
883
  "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) {
884
+ var parse = require_dictionary();
885
+ module.exports = add;
886
+ function add(buf) {
1322
887
  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;
888
+ var index = -1;
889
+ var rule;
890
+ var source;
1336
891
  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 = {};
892
+ var offset;
893
+ parse(buf, self, self.data);
894
+ while (++index < self.compoundRules.length) {
895
+ rule = self.compoundRules[index];
896
+ source = "";
1374
897
  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
- }
898
+ while (++offset < rule.length) {
899
+ character = rule.charAt(offset);
900
+ source += self.compoundRuleCodes[character].length ? "(?:" + self.compoundRuleCodes[character].join("|") + ")" : character;
1395
901
  }
902
+ self.compoundRules[index] = new RegExp(source, "i");
1396
903
  }
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;
904
+ return self;
905
+ }
906
+ }
907
+ });
908
+
909
+ // node_modules/nspell/lib/personal.js
910
+ var require_personal = __commonJS({
911
+ "node_modules/nspell/lib/personal.js"(exports, module) {
912
+ "use strict";
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;
1417
928
  }
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);
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);
1453
938
  }
1454
939
  }
1455
- return values;
1456
- function sort(a, b) {
1457
- return sortWeight(a, b) || sortCasing(a, b) || sortAlpha(a, b);
1458
- }
1459
- function sortWeight(a, b) {
1460
- return weighted[a] === weighted[b] ? 0 : weighted[a] > weighted[b] ? -1 : 1;
1461
- }
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;
1466
- }
1467
- function sortAlpha(a, b) {
1468
- return a.localeCompare(b);
1469
- }
940
+ return self;
1470
941
  }
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 = [];
942
+ }
943
+ });
944
+
945
+ // node_modules/nspell/lib/index.js
946
+ var require_lib = __commonJS({
947
+ "node_modules/nspell/lib/index.js"(exports, module) {
948
+ "use strict";
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) {
1476
962
  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
- }
963
+ var dictionaries;
964
+ if (!(this instanceof NSpell3)) {
965
+ return new NSpell3(aff, dic);
1494
966
  }
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
- }
967
+ if (typeof aff === "string" || buffer(aff)) {
968
+ if (typeof dic === "string" || buffer(dic)) {
969
+ dictionaries = [{ dic }];
970
+ }
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];
1542
978
  }
979
+ aff = aff.aff;
1543
980
  }
1544
981
  }
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);
982
+ if (!aff) {
983
+ throw new Error("Missing `aff` in dictionary");
984
+ }
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);
1557
997
  }
1558
998
  }
1559
- if (state) {
1560
- memory.weighted[value]++;
1561
- }
1562
999
  }
1563
- function switchCase(fragment) {
1564
- var first = fragment.charAt(0);
1565
- return (first.toLowerCase() === first ? first.toUpperCase() : first.toLowerCase()) + fragment.slice(1);
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
+ listContacts: () => listContacts,
1059
+ listQueuedOutgoing: () => listQueuedOutgoing,
1060
+ logClientEvent: () => logClientEvent,
1061
+ markAsSpamMessages: () => markAsSpamMessages,
1062
+ markFolderRead: () => markFolderRead,
1063
+ moveFolderToTrash: () => moveFolderToTrash,
1064
+ moveMessage: () => moveMessage,
1065
+ moveMessages: () => moveMessages,
1066
+ onEvent: () => onEvent,
1067
+ onWsEvent: () => onWsEvent,
1068
+ openAttachment: () => openAttachment,
1069
+ openInTextEditor: () => openInTextEditor,
1070
+ openInWord: () => openInWord,
1071
+ openLocalPath: () => openLocalPath,
1072
+ readConfigHelp: () => readConfigHelp,
1073
+ readJsoncFile: () => readJsoncFile,
1074
+ reauthGoogleScopes: () => reauthGoogleScopes,
1075
+ reauthenticate: () => reauthenticate,
1076
+ recordSpamReport: () => recordSpamReport,
1077
+ removeUserDictWord: () => removeUserDictWord,
1078
+ renameFolder: () => renameFolder,
1079
+ repairAccounts: () => repairAccounts,
1080
+ restartServer: () => restartServer,
1081
+ saveAutocompleteSettings: () => saveAutocompleteSettings,
1082
+ saveDraft: () => saveDraft,
1083
+ saveSettings: () => saveSettings,
1084
+ searchContacts: () => searchContacts,
1085
+ searchMessages: () => searchMessages,
1086
+ sendMessage: () => sendMessage,
1087
+ setPriorityDomain: () => setPriorityDomain,
1088
+ setPrioritySender: () => setPrioritySender,
1089
+ setupAccount: () => setupAccount,
1090
+ showReminderPopup: () => showReminderPopup,
1091
+ subscribeStore: () => subscribeStore,
1092
+ syncAccount: () => syncAccount,
1093
+ triggerSync: () => triggerSync,
1094
+ undeleteMessage: () => undeleteMessage,
1095
+ unsubscribeOneClick: () => unsubscribeOneClick,
1096
+ updateCalendarEvent: () => updateCalendarEvent,
1097
+ updateFlags: () => updateFlags,
1098
+ updateTask: () => updateTask,
1099
+ upsertContact: () => upsertContact,
1100
+ writeJsoncFile: () => writeJsoncFile
1101
+ });
1102
+ function getIpc() {
1103
+ if (typeof mailxapi !== "undefined" && mailxapi?.isApp)
1104
+ return mailxapi;
1105
+ if (window.opener?.mailxapi?.isApp)
1106
+ return window.opener.mailxapi;
1107
+ if (window.parent?.mailxapi?.isApp)
1108
+ return window.parent.mailxapi;
1109
+ return null;
1110
+ }
1111
+ function buildRelayBridge() {
1112
+ const pending = /* @__PURE__ */ new Map();
1113
+ window.addEventListener("message", (ev) => {
1114
+ if (!ev.data || ev.data.type !== "mailx-ipc-result" || !ev.data.id)
1115
+ return;
1116
+ const entry = pending.get(ev.data.id);
1117
+ if (!entry)
1118
+ return;
1119
+ pending.delete(ev.data.id);
1120
+ clearTimeout(entry.timer);
1121
+ if (ev.data.ok)
1122
+ entry.resolve(ev.data.result);
1123
+ else
1124
+ entry.reject(new Error(ev.data.error || "parent-relay ipc error"));
1125
+ });
1126
+ const call = (method, args) => {
1127
+ const id = `ipc-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
1128
+ return new Promise((resolve, reject) => {
1129
+ const timer = setTimeout(() => {
1130
+ pending.delete(id);
1131
+ reject(new Error(`parent-relay timeout: ${method}`));
1132
+ }, 12e4);
1133
+ pending.set(id, { resolve, reject, timer });
1134
+ try {
1135
+ window.parent.postMessage({ type: "mailx-ipc", id, method, args }, "*");
1136
+ } catch (e) {
1137
+ clearTimeout(timer);
1138
+ pending.delete(id);
1139
+ reject(e);
1140
+ }
1141
+ });
1142
+ };
1143
+ return new Proxy({}, {
1144
+ get(_t, prop) {
1145
+ if (prop === "isApp")
1146
+ return true;
1147
+ if (prop === "platform")
1148
+ return window.parent?.mailxapi?.platform || "webview2";
1149
+ if (prop === "onEvent") {
1150
+ return (handler) => window.parent?.mailxapi?.onEvent?.(handler);
1566
1151
  }
1152
+ return (...args) => call(prop, args);
1153
+ }
1154
+ });
1155
+ }
1156
+ function ipc() {
1157
+ const inIframe = window.parent && window.parent !== window;
1158
+ if (inIframe && window.parent?.mailxapi?.isApp) {
1159
+ if (!cachedRelayBridge)
1160
+ cachedRelayBridge = buildRelayBridge();
1161
+ return cachedRelayBridge;
1162
+ }
1163
+ const bridge = getIpc();
1164
+ if (!bridge)
1165
+ throw new Error("IPC bridge not available");
1166
+ return bridge;
1167
+ }
1168
+ function abortMessageListRequests() {
1169
+ if (messageListAbort) {
1170
+ messageListAbort.abort();
1171
+ messageListAbort = null;
1172
+ }
1173
+ }
1174
+ function getAccounts() {
1175
+ return ipc().getAccounts();
1176
+ }
1177
+ function getFolders(accountId) {
1178
+ return ipc().getFolders(accountId);
1179
+ }
1180
+ function getMessages(accountId, folderId, page = 1, pageSize = 50, flaggedOnly = false, sort, sortDir) {
1181
+ abortMessageListRequests();
1182
+ return ipc().getMessages(accountId, folderId, page, pageSize, sort, sortDir, void 0, flaggedOnly);
1183
+ }
1184
+ function getUnifiedInbox(page = 1, pageSize = 50) {
1185
+ abortMessageListRequests();
1186
+ return ipc().getUnifiedInbox(page, pageSize);
1187
+ }
1188
+ function searchMessages(query, page = 1, pageSize = 50, scope = "all", accountId = "", folderId = 0, includeTrashSpam = false) {
1189
+ return ipc().searchMessages(query, page, pageSize, scope, accountId, folderId, includeTrashSpam);
1190
+ }
1191
+ function cancelServerSearch() {
1192
+ return ipc().cancelServerSearch?.();
1193
+ }
1194
+ function getMessage(accountId, uid, allowRemote = false, folderId) {
1195
+ return ipc().getMessage(accountId, uid, allowRemote, folderId);
1196
+ }
1197
+ function updateFlags(accountId, uid, flags) {
1198
+ return ipc().updateFlags(accountId, uid, flags);
1199
+ }
1200
+ function triggerSync() {
1201
+ return ipc().syncAll();
1202
+ }
1203
+ function syncAccount(accountId) {
1204
+ return ipc().syncAccount(accountId);
1205
+ }
1206
+ function reauthenticate(accountId) {
1207
+ return ipc().reauthenticate(accountId);
1208
+ }
1209
+ function reauthGoogleScopes() {
1210
+ return ipc().reauthGoogleScopes();
1211
+ }
1212
+ function getSyncPending() {
1213
+ return ipc().getSyncPending();
1214
+ }
1215
+ function getDiagnostics() {
1216
+ return ipc().getDiagnostics?.() ?? Promise.resolve([]);
1217
+ }
1218
+ function getPrimaryAccount(feature) {
1219
+ return ipc().getPrimaryAccount?.(feature) ?? Promise.resolve(null);
1220
+ }
1221
+ function getCalendarEvents(fromMs, toMs) {
1222
+ return ipc().getCalendarEvents?.(fromMs, toMs) ?? Promise.resolve([]);
1223
+ }
1224
+ function getCalendars() {
1225
+ return ipc().getCalendars?.() ?? Promise.resolve([]);
1226
+ }
1227
+ function createCalendarEvent(ev) {
1228
+ return ipc().createCalendarEvent?.(ev);
1229
+ }
1230
+ function updateCalendarEvent(uuid, patch) {
1231
+ return ipc().updateCalendarEvent?.(uuid, patch);
1232
+ }
1233
+ function deleteCalendarEvent(uuid) {
1234
+ return ipc().deleteCalendarEvent?.(uuid);
1235
+ }
1236
+ function getTasks(includeCompleted = false) {
1237
+ return ipc().getTasks?.(includeCompleted) ?? Promise.resolve([]);
1238
+ }
1239
+ function createTask(t) {
1240
+ return ipc().createTask?.(t);
1241
+ }
1242
+ function updateTask(uuid, patch) {
1243
+ return ipc().updateTask?.(uuid, patch);
1244
+ }
1245
+ function deleteTask(uuid) {
1246
+ return ipc().deleteTask?.(uuid);
1247
+ }
1248
+ function drainStoreSync() {
1249
+ return ipc().drainStoreSync?.();
1250
+ }
1251
+ function recordSpamReport(accountId, uid, folderId) {
1252
+ return ipc().recordSpamReport?.(accountId, uid, folderId);
1253
+ }
1254
+ function getOutboxStatus() {
1255
+ return ipc().getOutboxStatus();
1256
+ }
1257
+ function listQueuedOutgoing() {
1258
+ return ipc().listQueuedOutgoing();
1259
+ }
1260
+ function cancelQueuedOutgoing(p) {
1261
+ return ipc().cancelQueuedOutgoing(p);
1262
+ }
1263
+ function searchContacts(query) {
1264
+ return ipc().searchContacts(query);
1265
+ }
1266
+ function hasCcHistoryTo(email) {
1267
+ return ipc().hasCcHistoryTo(email);
1268
+ }
1269
+ function hasBccHistoryTo(email) {
1270
+ return ipc().hasBccHistoryTo(email);
1271
+ }
1272
+ function listContacts(query, page = 1, pageSize = 100) {
1273
+ return ipc().listContacts(query, page, pageSize);
1274
+ }
1275
+ function upsertContact(name, email) {
1276
+ return ipc().upsertContact(name, email);
1277
+ }
1278
+ function deleteContact(email) {
1279
+ return ipc().deleteContact(email);
1280
+ }
1281
+ function addPreferredContact(entry) {
1282
+ return ipc().addPreferredContact(entry.name, entry.email, entry.source, entry.organization);
1283
+ }
1284
+ function getPriorityLists() {
1285
+ return ipc().getPriorityLists();
1286
+ }
1287
+ function setPrioritySender(email, value, name) {
1288
+ return ipc().setPrioritySender(email, value, name);
1289
+ }
1290
+ function setPriorityDomain(domain, value) {
1291
+ return ipc().setPriorityDomain(domain, value);
1292
+ }
1293
+ function addToDenylist(email) {
1294
+ return ipc().addToDenylist(email);
1295
+ }
1296
+ function openLocalPath(which) {
1297
+ return ipc().openLocalPath(which);
1298
+ }
1299
+ function openInTextEditor(path) {
1300
+ return ipc().openInTextEditor?.(path) ?? Promise.resolve({ ok: false, opener: "none", reason: "no host" });
1301
+ }
1302
+ function allowRemoteContent(type, value) {
1303
+ return ipc().allowRemoteContent(type, value);
1304
+ }
1305
+ function getUserDict() {
1306
+ return ipc().getUserDict?.() ?? Promise.resolve([]);
1307
+ }
1308
+ function addUserDictWord(word) {
1309
+ return ipc().addUserDictWord?.(word) ?? Promise.resolve([]);
1310
+ }
1311
+ function addUserDictWords(words) {
1312
+ return ipc().addUserDictWords?.(words) ?? Promise.resolve([]);
1313
+ }
1314
+ function removeUserDictWord(word) {
1315
+ return ipc().removeUserDictWord?.(word) ?? Promise.resolve([]);
1316
+ }
1317
+ function flagSenderOrDomain(type, value) {
1318
+ return ipc().flagSenderOrDomain?.(type, value) ?? Promise.resolve({ flagged: false });
1319
+ }
1320
+ function deleteMessage(accountId, uid) {
1321
+ return ipc().deleteMessage?.(accountId, uid);
1322
+ }
1323
+ function deleteMessages(accountId, uids) {
1324
+ if (uids.length === 1)
1325
+ return deleteMessage(accountId, uids[0]);
1326
+ return ipc().deleteMessages?.(accountId, uids);
1327
+ }
1328
+ function moveMessages(accountId, uids, targetFolderId, targetAccountId) {
1329
+ if (uids.length === 1)
1330
+ return moveMessage(accountId, uids[0], targetFolderId, targetAccountId);
1331
+ return ipc().moveMessages?.(accountId, uids, targetFolderId, targetAccountId);
1332
+ }
1333
+ function markAsSpamMessages(accountId, uids) {
1334
+ return ipc().markAsSpamMessages?.(accountId, uids);
1335
+ }
1336
+ function undeleteMessage(accountId, uid, folderId) {
1337
+ return ipc().undeleteMessage?.(accountId, uid, folderId);
1338
+ }
1339
+ function moveMessage(accountId, uid, targetFolderId, targetAccountId) {
1340
+ return ipc().moveMessage?.(accountId, uid, targetFolderId, targetAccountId);
1341
+ }
1342
+ function restartServer() {
1343
+ return ipc().restart?.();
1344
+ }
1345
+ function markFolderRead(accountId, folderId) {
1346
+ return ipc().markFolderRead?.(accountId, folderId);
1347
+ }
1348
+ function createFolder(accountId, parentPath, name) {
1349
+ return ipc().createFolder?.(accountId, parentPath, name);
1350
+ }
1351
+ function renameFolder(accountId, folderId, newName) {
1352
+ return ipc().renameFolder?.(accountId, folderId, newName);
1353
+ }
1354
+ function deleteFolder(accountId, folderId) {
1355
+ return ipc().deleteFolder?.(accountId, folderId);
1356
+ }
1357
+ function moveFolderToTrash(accountId, folderId) {
1358
+ return ipc().moveFolderToTrash?.(accountId, folderId);
1359
+ }
1360
+ function emptyFolder(accountId, folderId) {
1361
+ return ipc().emptyFolder?.(accountId, folderId);
1362
+ }
1363
+ function logClientEvent(tag, data) {
1364
+ let delivered = false;
1365
+ try {
1366
+ 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;
1367
+ if (bridge?.logClientEvent) {
1368
+ bridge.logClientEvent(tag, data);
1369
+ delivered = true;
1567
1370
  }
1371
+ } catch {
1568
1372
  }
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
- };
1373
+ try {
1374
+ if (window.parent && window.parent !== window) {
1375
+ window.parent.postMessage({ type: "mailx-trace", tag, data, bridged: delivered }, "*");
1588
1376
  }
1377
+ } catch {
1589
1378
  }
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;
1623
- }
1379
+ }
1380
+ function sendMessage(body) {
1381
+ return ipc().sendMessage?.(body);
1382
+ }
1383
+ function saveDraft(body) {
1384
+ return ipc().saveDraft?.(body);
1385
+ }
1386
+ function onEvent(handler) {
1387
+ eventHandlers.push(handler);
1388
+ return () => {
1389
+ const i = eventHandlers.indexOf(handler);
1390
+ if (i >= 0)
1391
+ eventHandlers.splice(i, 1);
1392
+ };
1393
+ }
1394
+ function subscribeStore(topic, handler) {
1395
+ let set = storeSubs.get(topic);
1396
+ if (!set) {
1397
+ set = /* @__PURE__ */ new Set();
1398
+ storeSubs.set(topic, set);
1624
1399
  }
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
- }
1400
+ set.add(handler);
1401
+ return () => {
1402
+ const s = storeSubs.get(topic);
1403
+ if (s) {
1404
+ s.delete(handler);
1405
+ if (s.size === 0)
1406
+ storeSubs.delete(topic);
1646
1407
  }
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);
1408
+ };
1409
+ }
1410
+ function deliverStore(event) {
1411
+ const exact = storeSubs.get(event.topic);
1412
+ if (exact)
1413
+ for (const h of exact) {
1414
+ try {
1415
+ h(event);
1416
+ } catch (e) {
1417
+ console.error("[store-bus]", e);
1658
1418
  }
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
- }
1419
+ }
1420
+ const wild = storeSubs.get("*");
1421
+ if (wild)
1422
+ for (const h of wild) {
1423
+ try {
1424
+ h(event);
1425
+ } catch (e) {
1426
+ console.error("[store-bus]", e);
1693
1427
  }
1694
1428
  }
1695
- }
1696
- });
1697
-
1698
- // node_modules/nspell/lib/add.js
1699
- var require_add2 = __commonJS({
1700
- "node_modules/nspell/lib/add.js"(exports, module) {
1429
+ }
1430
+ function connectEvents() {
1431
+ ipc().onEvent((event) => {
1432
+ if (event && event._event === "store")
1433
+ deliverStore(event);
1434
+ for (const h of eventHandlers)
1435
+ h(event);
1436
+ });
1437
+ }
1438
+ function autocomplete(body, signal) {
1439
+ return ipc().autocomplete?.(body);
1440
+ }
1441
+ function getAutocompleteSettings() {
1442
+ return ipc().getAutocompleteSettings?.();
1443
+ }
1444
+ function saveAutocompleteSettings(settings) {
1445
+ return ipc().saveAutocompleteSettings?.(settings);
1446
+ }
1447
+ function getVersion() {
1448
+ return ipc().getVersion();
1449
+ }
1450
+ function getSettings() {
1451
+ return ipc().getSettings();
1452
+ }
1453
+ function saveSettings(settings) {
1454
+ return ipc().saveSettingsData?.(settings);
1455
+ }
1456
+ function repairAccounts() {
1457
+ return ipc().repairAccounts?.();
1458
+ }
1459
+ function deleteDraft(accountId, draftUid2, draftId2) {
1460
+ return ipc().deleteDraft?.(accountId, draftUid2, draftId2);
1461
+ }
1462
+ function addContact(name, email) {
1463
+ return ipc().addContact?.(name, email);
1464
+ }
1465
+ function getThreadMessages(accountId, threadId) {
1466
+ return ipc().getThreadMessages?.(accountId, threadId);
1467
+ }
1468
+ function readJsoncFile(name) {
1469
+ return ipc().readJsoncFile?.(name);
1470
+ }
1471
+ function writeJsoncFile(name, content) {
1472
+ return ipc().writeJsoncFile?.(name, content);
1473
+ }
1474
+ function formatJsonc(content) {
1475
+ return ipc().formatJsonc?.(content);
1476
+ }
1477
+ function readConfigHelp(name) {
1478
+ return ipc().readConfigHelp?.(name) ?? Promise.resolve({ content: "" });
1479
+ }
1480
+ function unsubscribeOneClick(url) {
1481
+ return ipc().unsubscribeOneClick?.(url);
1482
+ }
1483
+ function openInWord(editId, html) {
1484
+ return ipc().openInWord?.(editId, html) ?? Promise.resolve({ ok: false, path: "", opener: "none" });
1485
+ }
1486
+ function closeWordEdit(editId) {
1487
+ return ipc().closeWordEdit?.(editId) ?? Promise.resolve();
1488
+ }
1489
+ function showReminderPopup(opts) {
1490
+ return ipc().showReminderPopup?.(opts) ?? Promise.resolve({ button: "", reason: "no host" });
1491
+ }
1492
+ function consumePendingMailto() {
1493
+ return ipc().consumePendingMailto?.() ?? Promise.resolve(null);
1494
+ }
1495
+ function aiTransform(req) {
1496
+ return ipc().aiTransform?.(req) ?? Promise.resolve({ text: "", reason: "AI not available in this host" });
1497
+ }
1498
+ function setupAccount(name, email, password) {
1499
+ return ipc().setupAccount?.(name, email, password);
1500
+ }
1501
+ async function getAttachment(accountId, uid, attachmentId, folderId) {
1502
+ return ipc().getAttachment(accountId, uid, attachmentId, folderId);
1503
+ }
1504
+ async function openAttachment(accountId, uid, attachmentId, folderId) {
1505
+ const fn = ipc().openAttachment;
1506
+ return fn ? fn(accountId, uid, attachmentId, folderId) : void 0;
1507
+ }
1508
+ async function getDeviceAccounts() {
1509
+ return ipc().getDeviceAccounts?.() ?? [];
1510
+ }
1511
+ var cachedRelayBridge, messageListAbort, eventHandlers, storeSubs, connectWebSocket, onWsEvent;
1512
+ var init_api_client = __esm({
1513
+ "client/lib/api-client.js"() {
1701
1514
  "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
- }
1515
+ cachedRelayBridge = null;
1516
+ messageListAbort = null;
1517
+ eventHandlers = [];
1518
+ storeSubs = /* @__PURE__ */ new Map();
1519
+ connectWebSocket = connectEvents;
1520
+ onWsEvent = onEvent;
1710
1521
  }
1711
1522
  });
1712
1523
 
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
- }
1524
+ // client/compose/spellcheck-core.js
1525
+ var spellcheck_core_exports = {};
1526
+ __export(spellcheck_core_exports, {
1527
+ MARKER_ATTR: () => MARKER_ATTR,
1528
+ MIN_WORD_LEN: () => MIN_WORD_LEN,
1529
+ SKIP_TAGS: () => SKIP_TAGS,
1530
+ USER_DICT_KEY: () => USER_DICT_KEY,
1531
+ addToUserDict: () => addToUserDict,
1532
+ buildSuggestionList: () => buildSuggestionList,
1533
+ getSpell: () => getSpell,
1534
+ getWordAtPoint: () => getWordAtPoint,
1535
+ showSuggestionsMenu: () => showSuggestionsMenu
1724
1536
  });
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;
1537
+ async function getSpell() {
1538
+ if (spellPromise)
1539
+ return spellPromise;
1540
+ spellPromise = (async () => {
1541
+ const [affRes, dicRes] = await Promise.all([
1542
+ fetch("../lib/dict/en.aff"),
1543
+ fetch("../lib/dict/en.dic")
1544
+ ]);
1545
+ if (!affRes.ok || !dicRes.ok) {
1546
+ throw new Error(`spellcheck: dict fetch failed (aff=${affRes.status} dic=${dicRes.status})`);
1733
1547
  }
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);
1548
+ const [aff, dic] = await Promise.all([affRes.text(), dicRes.text()]);
1549
+ const sp = new import_nspell.default({ aff, dic });
1550
+ try {
1551
+ const raw = localStorage.getItem(USER_DICT_KEY);
1552
+ if (raw)
1553
+ for (const w of JSON.parse(raw))
1554
+ sp.add(w);
1555
+ } catch {
1757
1556
  }
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);
1557
+ getUserDict().then((cloud) => {
1558
+ const cloudArr = Array.isArray(cloud) ? cloud : [];
1559
+ for (const w of cloudArr)
1560
+ sp.add(w);
1561
+ let local = [];
1562
+ try {
1563
+ const raw = localStorage.getItem(USER_DICT_KEY);
1564
+ local = raw ? JSON.parse(raw) : [];
1565
+ } catch {
1566
+ local = [];
1767
1567
  }
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;
1568
+ const cloudSet = new Set(cloudArr);
1569
+ const localOnly = local.filter((w) => !cloudSet.has(w));
1570
+ if (localOnly.length > 0) {
1571
+ addUserDictWords(localOnly).catch((e) => console.error("[spell] reconcile:", e));
1782
1572
  }
1783
- word = word.trim();
1784
- if (word) {
1785
- add(dict, word, parseCodes(options.flags, codes.trim()), options);
1573
+ try {
1574
+ const merged = [.../* @__PURE__ */ new Set([...local, ...cloudArr])];
1575
+ localStorage.setItem(USER_DICT_KEY, JSON.stringify(merged));
1576
+ } catch {
1786
1577
  }
1578
+ }).catch(() => {
1579
+ });
1580
+ return sp;
1581
+ })();
1582
+ return spellPromise;
1583
+ }
1584
+ function addToUserDict(word, sp) {
1585
+ try {
1586
+ const raw = localStorage.getItem(USER_DICT_KEY);
1587
+ const arr = raw ? JSON.parse(raw) : [];
1588
+ if (!arr.includes(word)) {
1589
+ arr.push(word);
1590
+ localStorage.setItem(USER_DICT_KEY, JSON.stringify(arr));
1787
1591
  }
1592
+ } catch {
1788
1593
  }
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;
1594
+ sp.add(word);
1595
+ addUserDictWord(word).catch((e) => console.error("[spell] addUserDictWord:", e));
1596
+ }
1597
+ function buildSuggestionList(word, sp) {
1598
+ const transposed = [];
1599
+ for (let i = 0; i < word.length - 1; i++) {
1600
+ const swapped = word.slice(0, i) + word[i + 1] + word[i] + word.slice(i + 2);
1601
+ if (swapped !== word && sp.correct(swapped) && !transposed.includes(swapped)) {
1602
+ transposed.push(swapped);
1816
1603
  }
1817
1604
  }
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
- }
1605
+ const nspellSugs = sp.suggest(word);
1606
+ const sugs = [];
1607
+ for (const s of [...transposed, ...nspellSugs]) {
1608
+ if (!sugs.includes(s))
1609
+ sugs.push(s);
1610
+ if (sugs.length >= 7)
1611
+ break;
1612
+ }
1613
+ return sugs;
1614
+ }
1615
+ function showSuggestionsMenu(parentDoc, x, y, items, extraDismissDocs = []) {
1616
+ parentDoc.getElementById("mailx-spell-menu")?.remove();
1617
+ const menu = parentDoc.createElement("div");
1618
+ menu.id = "mailx-spell-menu";
1619
+ menu.style.cssText = `
1620
+ position: fixed;
1621
+ left: ${x}px; top: ${y}px;
1622
+ z-index: 10000;
1623
+ background: var(--color-bg, #fff);
1624
+ color: var(--color-text, #222);
1625
+ border: 1px solid var(--color-border, #ccc);
1626
+ border-radius: 6px;
1627
+ box-shadow: 0 4px 16px rgba(0,0,0,0.18);
1628
+ padding: 4px 0;
1629
+ font: 13px system-ui, sans-serif;
1630
+ min-width: 180px;
1631
+ max-width: 320px;
1632
+ `;
1633
+ for (const it of items) {
1634
+ if (it.separator) {
1635
+ const sep = parentDoc.createElement("div");
1636
+ sep.style.cssText = "border-top:1px solid var(--color-border,#ddd); margin: 4px 0;";
1637
+ menu.appendChild(sep);
1638
+ continue;
1639
+ }
1640
+ const btn = parentDoc.createElement("button");
1641
+ btn.type = "button";
1642
+ btn.textContent = it.label;
1643
+ btn.style.cssText = `
1644
+ display: block; width: 100%; text-align: left;
1645
+ padding: 5px 12px; border: none; background: none;
1646
+ color: inherit; cursor: pointer; font: inherit;
1647
+ ${it.emphasized ? "font-weight: 600;" : ""}
1648
+ `;
1649
+ btn.addEventListener("mouseenter", () => {
1650
+ btn.style.background = "var(--color-bg-hover, #eef)";
1651
+ });
1652
+ btn.addEventListener("mouseleave", () => {
1653
+ btn.style.background = "none";
1654
+ });
1655
+ btn.addEventListener("click", () => {
1656
+ try {
1657
+ it.action();
1658
+ } finally {
1659
+ menu.remove();
1850
1660
  }
1851
- return self;
1661
+ });
1662
+ menu.appendChild(btn);
1663
+ }
1664
+ parentDoc.body.appendChild(menu);
1665
+ const r = menu.getBoundingClientRect();
1666
+ if (r.right > window.innerWidth)
1667
+ menu.style.left = `${Math.max(8, window.innerWidth - r.width - 8)}px`;
1668
+ if (r.bottom > window.innerHeight)
1669
+ menu.style.top = `${Math.max(8, window.innerHeight - r.height - 8)}px`;
1670
+ const docs = [parentDoc, ...extraDismissDocs];
1671
+ const dismiss2 = (e) => {
1672
+ if (e.type === "keydown" && e.key !== "Escape")
1673
+ return;
1674
+ if (e.type === "mousedown" && menu.contains(e.target))
1675
+ return;
1676
+ menu.remove();
1677
+ for (const d of docs) {
1678
+ d.removeEventListener("mousedown", dismiss2, true);
1679
+ d.removeEventListener("keydown", dismiss2, true);
1852
1680
  }
1681
+ };
1682
+ setTimeout(() => {
1683
+ for (const d of docs) {
1684
+ d.addEventListener("mousedown", dismiss2, true);
1685
+ d.addEventListener("keydown", dismiss2, true);
1686
+ }
1687
+ }, 0);
1688
+ }
1689
+ function getWordAtPoint(root, x, y) {
1690
+ const doc = root.ownerDocument || document;
1691
+ let node = null;
1692
+ let offset = 0;
1693
+ const winAny = doc.defaultView;
1694
+ if (typeof doc.caretPositionFromPoint === "function") {
1695
+ const pos = doc.caretPositionFromPoint(x, y);
1696
+ if (pos) {
1697
+ node = pos.offsetNode;
1698
+ offset = pos.offset;
1699
+ }
1700
+ } else if (typeof doc.caretRangeFromPoint === "function") {
1701
+ const range = doc.caretRangeFromPoint(x, y);
1702
+ if (range) {
1703
+ node = range.startContainer;
1704
+ offset = range.startOffset;
1705
+ }
1706
+ }
1707
+ if (!node || node.nodeType !== Node.TEXT_NODE)
1708
+ return null;
1709
+ let walk = node;
1710
+ while (walk && walk !== root)
1711
+ walk = walk.parentNode;
1712
+ if (!walk)
1713
+ return null;
1714
+ let p = node.parentNode;
1715
+ while (p && p !== root) {
1716
+ if (p.nodeType === Node.ELEMENT_NODE && SKIP_TAGS.has(p.tagName))
1717
+ return null;
1718
+ p = p.parentNode;
1719
+ }
1720
+ const text = node.data;
1721
+ if (offset > text.length)
1722
+ offset = text.length;
1723
+ const isWordChar = (c) => /[\p{L}'’\-]/u.test(c);
1724
+ let start = offset;
1725
+ while (start > 0 && isWordChar(text[start - 1]))
1726
+ start--;
1727
+ let end = offset;
1728
+ while (end < text.length && isWordChar(text[end]))
1729
+ end++;
1730
+ if (end - start < MIN_WORD_LEN)
1731
+ return null;
1732
+ const word = text.slice(start, end);
1733
+ if (!/^[\p{L}]/u.test(word))
1734
+ return null;
1735
+ return { word, node, start, end };
1736
+ }
1737
+ var import_nspell, USER_DICT_KEY, MARKER_ATTR, MIN_WORD_LEN, SKIP_TAGS, spellPromise;
1738
+ var init_spellcheck_core = __esm({
1739
+ "client/compose/spellcheck-core.js"() {
1740
+ "use strict";
1741
+ import_nspell = __toESM(require_lib(), 1);
1742
+ init_api_client();
1743
+ USER_DICT_KEY = "mailx-user-dict";
1744
+ MARKER_ATTR = "data-mailx-spellerror";
1745
+ MIN_WORD_LEN = 3;
1746
+ SKIP_TAGS = /* @__PURE__ */ new Set(["BLOCKQUOTE", "CODE", "PRE", "A", "SCRIPT", "STYLE", "KBD", "SAMP", "VAR"]);
1747
+ spellPromise = null;
1853
1748
  }
1854
1749
  });
1855
1750
 
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];
1751
+ // client/lib/rmf-tiny.js
1752
+ var rmf_tiny_exports = {};
1753
+ __export(rmf_tiny_exports, {
1754
+ createTinyMceEditor: () => createTinyMceEditor
1755
+ });
1756
+ async function loadTinymce(opts) {
1757
+ try {
1758
+ const mod = await import(
1759
+ /* @vite-ignore */
1760
+ "tinymce"
1761
+ );
1762
+ const tinymce = mod.default || mod;
1763
+ await Promise.all([
1764
+ import("tinymce/themes/silver").catch(() => {
1765
+ }),
1766
+ import("tinymce/icons/default").catch(() => {
1767
+ }),
1768
+ import("tinymce/models/dom").catch(() => {
1769
+ }),
1770
+ import("tinymce/plugins/paste").catch(() => {
1771
+ }),
1772
+ import("tinymce/plugins/lists").catch(() => {
1773
+ }),
1774
+ import("tinymce/plugins/link").catch(() => {
1775
+ }),
1776
+ import("tinymce/plugins/table").catch(() => {
1777
+ }),
1778
+ import("tinymce/plugins/code").catch(() => {
1779
+ }),
1780
+ import("tinymce/plugins/image").catch(() => {
1781
+ })
1782
+ ]);
1783
+ return tinymce;
1784
+ } catch {
1785
+ }
1786
+ const w = window;
1787
+ if (w.tinymce)
1788
+ return w.tinymce;
1789
+ if (!opts.cdnUrl) {
1790
+ throw new Error("rmf-tiny: tinymce not installed (npm install tinymce) and no cdnUrl supplied. See README for Android setup.");
1791
+ }
1792
+ const url = opts.apiKey && !opts.cdnUrl.includes("api-key") ? `${opts.cdnUrl}${opts.cdnUrl.includes("?") ? "&" : "?"}apiKey=${encodeURIComponent(opts.apiKey)}` : opts.cdnUrl;
1793
+ await new Promise((resolve, reject) => {
1794
+ const s = document.createElement("script");
1795
+ s.src = url;
1796
+ s.referrerPolicy = "origin";
1797
+ s.onload = () => resolve();
1798
+ s.onerror = () => reject(new Error(`rmf-tiny: failed to load TinyMCE from ${url}`));
1799
+ document.head.appendChild(s);
1800
+ });
1801
+ if (!w.tinymce)
1802
+ throw new Error("rmf-tiny: TinyMCE script loaded but window.tinymce is missing");
1803
+ return w.tinymce;
1804
+ }
1805
+ async function createTinyMceEditor(container2, opts = {}) {
1806
+ const tinymce = await loadTinymce(opts);
1807
+ const target = document.createElement("div");
1808
+ target.id = `rmf-tiny-${Math.random().toString(36).slice(2, 10)}`;
1809
+ target.style.cssText = "width:100%;height:100%;";
1810
+ container2.appendChild(target);
1811
+ const editor2 = await new Promise((resolve) => {
1812
+ tinymce.init({
1813
+ target,
1814
+ // Word-paste fidelity is the entire point of using TinyMCE here.
1815
+ // `paste_as_text: false` keeps formatting; powerpaste isn't in
1816
+ // OSS but the standard paste plugin handles Word reasonably well.
1817
+ // All free / OSS plugins bundled in the jsDelivr tinymce@6 script.
1818
+ // No premium plugins listed — listing one without an API key
1819
+ // triggers TinyMCE's upsell dialog on every load.
1820
+ plugins: "paste lists advlist link table code codesample image searchreplace autolink wordcount emoticons charmap insertdatetime quickbars nonbreaking directionality help",
1821
+ toolbar: [
1822
+ "undo redo | bold italic underline strikethrough | forecolor backcolor",
1823
+ "bullist numlist outdent indent | link table image code codesample | emoticons charmap | help"
1824
+ ].join(" | "),
1825
+ // Include "tools" so wordcount and searchreplace are reachable.
1826
+ menubar: "file edit view insert format tools",
1827
+ // View menu — append zoom commands. fullscreen omitted: this editor
1828
+ // lives inside a compose iframe overlay that already fills its host
1829
+ // window; TinyMCE's fullscreen plugin re-positions the container in
1830
+ // ways that produce a blank body, so it's not in the plugin list.
1831
+ menu: {
1832
+ view: { title: "View", items: "code | visualaid visualchars visualblocks | preview | zoomIn zoomOut zoomReset" }
1833
+ },
1834
+ // Disable the "Get all features" upsell badge that TinyMCE 6+
1835
+ // injects automatically when no premium plugins are listed.
1836
+ promotion: false,
1837
+ // Floating toolbar that appears on text selection — keeps the
1838
+ // main toolbar uncluttered while exposing common formatters
1839
+ // where the cursor already is. quickbars_insert_toolbar:false
1840
+ // suppresses the second (insert-on-blank-line) popup which
1841
+ // clutters more than it helps in an email composer.
1842
+ quickbars_selection_toolbar: "bold italic underline | forecolor | quicklink blockquote",
1843
+ quickbars_insert_toolbar: false,
1844
+ quickbars_image_toolbar: "alignleft aligncenter alignright",
1845
+ // Code-sample dropdown languages. TinyMCE's default list omits
1846
+ // "Text" / plain — every option triggers syntax highlighting
1847
+ // which mangles unrelated paste content (Bob 2026-05-24).
1848
+ // Adding Text first so it's the default; rest are the modern
1849
+ // languages we actually paste.
1850
+ codesample_languages: [
1851
+ { text: "Text", value: "text" },
1852
+ { text: "HTML/XML", value: "markup" },
1853
+ { text: "JavaScript", value: "javascript" },
1854
+ { text: "TypeScript", value: "typescript" },
1855
+ { text: "CSS", value: "css" },
1856
+ { text: "JSON", value: "json" },
1857
+ { text: "Python", value: "python" },
1858
+ { text: "Java", value: "java" },
1859
+ { text: "C", value: "c" },
1860
+ { text: "C++", value: "cpp" },
1861
+ { text: "C#", value: "csharp" },
1862
+ { text: "Go", value: "go" },
1863
+ { text: "Rust", value: "rust" },
1864
+ { text: "Ruby", value: "ruby" },
1865
+ { text: "PHP", value: "php" },
1866
+ { text: "Shell", value: "bash" },
1867
+ { text: "SQL", value: "sql" }
1868
+ ],
1869
+ // WebView's native spell-check (red underlines, right-click
1870
+ // "Add to dictionary"). Free; same UX as Quill's spellcheck=true.
1871
+ // Premium tinymcespellchecker plugin would replace this with a
1872
+ // custom backend via spellchecker_rpc_url, but requires a
1873
+ // Tiny Cloud subscription.
1874
+ browser_spellcheck: true,
1875
+ // Right-click context menu DISABLED so WebView2's native menu
1876
+ // fires instead. We need the native menu because that's where
1877
+ // the browser shows spelling suggestions (TinyMCE's contextmenu
1878
+ // intercepts the right-click and replaces the menu — red
1879
+ // underlines appear but suggestions are unreachable). Trade-off:
1880
+ // lose TinyMCE's link / image / table quick actions. Standard
1881
+ // text formatting is still on the toolbar.
1882
+ contextmenu: false,
1883
+ statusbar: false,
1884
+ branding: false,
1885
+ license_key: "gpl",
1886
+ paste_data_images: true,
1887
+ // Permissive valid_elements — preserve as much of the source
1888
+ // formatting as possible. The default is more aggressive about
1889
+ // stripping. Empty string for `valid_elements` means accept
1890
+ // everything that the schema allows.
1891
+ 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]",
1892
+ 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",
1893
+ // Auto-link bare URLs in pasted content. TinyMCE's `autolink`
1894
+ // plugin only fires on TYPED space/enter; URLs that arrive via
1895
+ // clipboard (browser address bar, terminal copy) come in as
1896
+ // plain text and stay un-linked. paste_preprocess runs on the
1897
+ // HTML the paste plugin produced.
1898
+ //
1899
+ // CRITICAL: skip auto-link when the content already contains
1900
+ // anchors. Naive regex over the whole content would wrap
1901
+ // `<a href="X">X</a>` in ANOTHER anchor (nested anchors are
1902
+ // invalid HTML and browsers split them, producing visible
1903
+ // junk). For HTML pastes that already have linked URLs (the
1904
+ // common case from a browser address bar or another mail
1905
+ // client), TinyMCE preserves them — no auto-link pass needed.
1906
+ // For plain-text pastes (no anchors present), wrap any bare
1907
+ // http(s)://… runs. Trailing sentence punctuation is excluded
1908
+ // from the URL.
1909
+ paste_preprocess: (_plugin, args) => {
1910
+ if (/<a[\s>]/i.test(args.content))
1911
+ return;
1912
+ args.content = args.content.replace(/(^|[\s(\[])((?:https?|ftp):\/\/[^\s<>"']+[^\s<>"'.,;:!?)\]])/gi, (_m, lead, url) => `${lead}<a href="${url}">${url}</a>`);
1913
+ },
1914
+ // Body font + quoted-reply styling. Without explicit rules for
1915
+ // <blockquote> and div.reply the editor iframe renders the
1916
+ // quoted block with no visual distinction from the user's own
1917
+ // text — pasted reply quotes vanish into a wall of unformatted
1918
+ // paragraphs (Bob 2026-05-12: "the body losing all formatting").
1919
+ // The styles below match Thunderbird/Outlook conventions: a
1920
+ // muted left-bar + indent on blockquotes, slight color shift on
1921
+ // the "On … wrote:" attribution, untouched typography for
1922
+ // everything else so genuine HTML formatting (bold / italic /
1923
+ // tables / inline color) still comes through verbatim.
1924
+ content_style: [
1925
+ // Dark-blue native caret bar. caret-shape:block produced a
1926
+ // column wider than the gap between letters AND caused the
1927
+ // caret to "scoot back to its old position" after an arrow
1928
+ // press (Chromium block-caret quirk with the arrow-key
1929
+ // selection adjustment, Bob 2026-05-24). Plain bar is
1930
+ // browser-default and behaves correctly with arrow keys;
1931
+ // just darken the color so it stays visible.
1932
+ "body { font-family: system-ui, sans-serif; font-size: 14px; caret-color: #0a2647; }",
1933
+ "blockquote { border-left: 3px solid #c0c8d0; margin: 0 0 0 4px; padding: 2px 0 2px 10px; color: #555; }",
1934
+ "div.reply { margin-top: 0.5em; }",
1935
+ "div.reply > p:first-child { color: #666; font-size: 0.95em; margin: 0 0 4px 0; }",
1936
+ "pre, code { font-family: ui-monospace, Consolas, Menlo, monospace; }"
1937
+ ].join(" "),
1938
+ init_instance_callback: (ed) => resolve(ed),
1939
+ setup: (ed) => {
1940
+ if (opts.initialHtml)
1941
+ ed.on("init", () => ed.setContent(opts.initialHtml));
1942
+ const ZOOM_DEFAULT = 14;
1943
+ const ZOOM_STEP = 2;
1944
+ const ZOOM_MIN = 8;
1945
+ const ZOOM_MAX = 32;
1946
+ const ZOOM_STORAGE_KEY = "rmf-tiny:zoom-px";
1947
+ let zoomPx = ZOOM_DEFAULT;
1948
+ try {
1949
+ const stored = Number(localStorage.getItem(ZOOM_STORAGE_KEY));
1950
+ if (Number.isFinite(stored) && stored >= ZOOM_MIN && stored <= ZOOM_MAX) {
1951
+ zoomPx = stored;
1889
1952
  }
1890
- aff = aff.aff;
1953
+ } catch {
1891
1954
  }
1955
+ const saveZoom = () => {
1956
+ try {
1957
+ localStorage.setItem(ZOOM_STORAGE_KEY, String(zoomPx));
1958
+ } catch {
1959
+ }
1960
+ };
1961
+ const applyZoom = () => {
1962
+ try {
1963
+ const body = ed.getBody();
1964
+ if (body)
1965
+ body.style.fontSize = `${zoomPx}px`;
1966
+ } catch {
1967
+ }
1968
+ };
1969
+ const bumpZoom = (delta) => {
1970
+ zoomPx = Math.max(ZOOM_MIN, Math.min(ZOOM_MAX, zoomPx + delta));
1971
+ applyZoom();
1972
+ saveZoom();
1973
+ };
1974
+ ed.ui.registry.addMenuItem("zoomIn", {
1975
+ text: "Zoom in",
1976
+ shortcut: "Ctrl+=",
1977
+ onAction: () => bumpZoom(ZOOM_STEP)
1978
+ });
1979
+ ed.ui.registry.addMenuItem("zoomOut", {
1980
+ text: "Zoom out",
1981
+ shortcut: "Ctrl+-",
1982
+ onAction: () => bumpZoom(-ZOOM_STEP)
1983
+ });
1984
+ ed.ui.registry.addMenuItem("zoomReset", {
1985
+ text: "Reset zoom",
1986
+ shortcut: "Ctrl+0",
1987
+ onAction: () => {
1988
+ zoomPx = ZOOM_DEFAULT;
1989
+ applyZoom();
1990
+ saveZoom();
1991
+ }
1992
+ });
1993
+ const installLinkEscape = () => {
1994
+ const doc = ed.getDoc();
1995
+ if (!doc || doc.__rmfLinkEscapeInstalled)
1996
+ return;
1997
+ doc.__rmfLinkEscapeInstalled = true;
1998
+ doc.addEventListener("beforeinput", (e) => {
1999
+ const kind = e.inputType || "";
2000
+ const isInsert = kind === "insertText" || kind === "insertCompositionText" || kind === "insertFromPaste" || kind === "insertFromDrop" || kind === "insertFromComposition";
2001
+ if (!isInsert)
2002
+ return;
2003
+ const sel = doc.getSelection();
2004
+ if (!sel || sel.rangeCount === 0)
2005
+ return;
2006
+ const rng = sel.getRangeAt(0);
2007
+ if (!rng.collapsed)
2008
+ return;
2009
+ const node = rng.startContainer;
2010
+ const a = node.nodeType === Node.ELEMENT_NODE ? node : node.parentNode;
2011
+ if (!a)
2012
+ return;
2013
+ const link = a.closest?.("a");
2014
+ if (!link)
2015
+ return;
2016
+ let tail;
2017
+ try {
2018
+ tail = rng.cloneRange();
2019
+ tail.setEndAfter(link);
2020
+ } catch {
2021
+ return;
2022
+ }
2023
+ if (tail.toString().length > 0)
2024
+ return;
2025
+ const after = doc.createRange();
2026
+ after.setStartAfter(link);
2027
+ after.collapse(true);
2028
+ sel.removeAllRanges();
2029
+ sel.addRange(after);
2030
+ }, true);
2031
+ };
2032
+ ed.on("init", installLinkEscape);
2033
+ ed.on("SetContent", installLinkEscape);
2034
+ ed.on("init", () => {
2035
+ try {
2036
+ const body = ed.getBody();
2037
+ if (body) {
2038
+ body.setAttribute("spellcheck", "true");
2039
+ body.setAttribute("lang", "en");
2040
+ }
2041
+ const doc = ed.getDoc();
2042
+ if (doc?.documentElement)
2043
+ doc.documentElement.setAttribute("lang", "en");
2044
+ } catch {
2045
+ }
2046
+ if (zoomPx !== ZOOM_DEFAULT)
2047
+ applyZoom();
2048
+ try {
2049
+ const doc = ed.getDoc();
2050
+ doc.addEventListener("wheel", (e) => {
2051
+ if (!e.ctrlKey)
2052
+ return;
2053
+ e.preventDefault();
2054
+ bumpZoom(e.deltaY < 0 ? ZOOM_STEP : -ZOOM_STEP);
2055
+ }, { passive: false });
2056
+ doc.addEventListener("keydown", (e) => {
2057
+ if (!(e.ctrlKey || e.metaKey))
2058
+ return;
2059
+ if (e.key === "=" || e.key === "+") {
2060
+ e.preventDefault();
2061
+ e.stopPropagation();
2062
+ bumpZoom(ZOOM_STEP);
2063
+ } else if (e.key === "-") {
2064
+ e.preventDefault();
2065
+ e.stopPropagation();
2066
+ bumpZoom(-ZOOM_STEP);
2067
+ } else if (e.key === "0") {
2068
+ e.preventDefault();
2069
+ e.stopPropagation();
2070
+ zoomPx = ZOOM_DEFAULT;
2071
+ applyZoom();
2072
+ }
2073
+ }, true);
2074
+ } catch {
2075
+ }
2076
+ });
1892
2077
  }
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);
2078
+ });
2079
+ });
2080
+ return {
2081
+ setHtml(html) {
2082
+ editor2.setContent(html);
2083
+ },
2084
+ getHtml() {
2085
+ return editor2.getContent();
2086
+ },
2087
+ getText() {
2088
+ return editor2.getContent({ format: "text" });
2089
+ },
2090
+ focus() {
2091
+ editor2.focus();
2092
+ },
2093
+ setCursor(pos) {
2094
+ const place = () => {
2095
+ const body = editor2.getBody();
2096
+ if (pos === 0) {
2097
+ const first = body.firstChild;
2098
+ if (first && first.nodeType === 1) {
2099
+ editor2.selection.setCursorLocation(first, 0);
2100
+ } else {
2101
+ editor2.selection.select(body, true);
2102
+ editor2.selection.collapse(true);
1908
2103
  }
2104
+ editor2.focus();
2105
+ editor2.getWin()?.scrollTo(0, 0);
2106
+ } else {
2107
+ editor2.selection.select(body, true);
2108
+ editor2.selection.collapse(false);
2109
+ editor2.focus();
2110
+ editor2.selection.scrollIntoView();
1909
2111
  }
2112
+ };
2113
+ try {
2114
+ place();
2115
+ editor2.getWin()?.requestAnimationFrame?.(() => {
2116
+ try {
2117
+ place();
2118
+ } catch {
2119
+ }
2120
+ });
2121
+ } catch {
1910
2122
  }
1911
- }
2123
+ },
2124
+ get root() {
2125
+ return editor2.getContainer();
2126
+ },
2127
+ on(event, handler) {
2128
+ editor2.on(event, handler);
2129
+ },
2130
+ off(event, handler) {
2131
+ editor2.off(event, handler);
2132
+ },
2133
+ nativeEditor: editor2
2134
+ };
2135
+ }
2136
+ var init_rmf_tiny = __esm({
2137
+ "client/lib/rmf-tiny.js"() {
2138
+ "use strict";
1912
2139
  }
1913
2140
  });
1914
2141
 
@@ -1917,10 +2144,10 @@ var spellcheck_exports = {};
1917
2144
  __export(spellcheck_exports, {
1918
2145
  wireSpellcheck: () => wireSpellcheck
1919
2146
  });
1920
- async function getSpell() {
1921
- if (spellPromise)
1922
- return spellPromise;
1923
- spellPromise = (async () => {
2147
+ async function getSpell2() {
2148
+ if (spellPromise2)
2149
+ return spellPromise2;
2150
+ spellPromise2 = (async () => {
1924
2151
  const [affRes, dicRes] = await Promise.all([
1925
2152
  fetch("../lib/dict/en.aff"),
1926
2153
  fetch("../lib/dict/en.dic")
@@ -1929,9 +2156,9 @@ async function getSpell() {
1929
2156
  throw new Error(`spellcheck: dict fetch failed (aff=${affRes.status} dic=${dicRes.status})`);
1930
2157
  }
1931
2158
  const [aff, dic] = await Promise.all([affRes.text(), dicRes.text()]);
1932
- const sp = new import_nspell.default({ aff, dic });
2159
+ const sp = new import_nspell2.default({ aff, dic });
1933
2160
  try {
1934
- const raw = localStorage.getItem(USER_DICT_KEY);
2161
+ const raw = localStorage.getItem(USER_DICT_KEY2);
1935
2162
  if (raw)
1936
2163
  for (const w of JSON.parse(raw))
1937
2164
  sp.add(w);
@@ -1943,7 +2170,7 @@ async function getSpell() {
1943
2170
  sp.add(w);
1944
2171
  let local = [];
1945
2172
  try {
1946
- const raw = localStorage.getItem(USER_DICT_KEY);
2173
+ const raw = localStorage.getItem(USER_DICT_KEY2);
1947
2174
  local = raw ? JSON.parse(raw) : [];
1948
2175
  } catch {
1949
2176
  local = [];
@@ -1955,22 +2182,22 @@ async function getSpell() {
1955
2182
  }
1956
2183
  try {
1957
2184
  const merged = [.../* @__PURE__ */ new Set([...local, ...cloudArr])];
1958
- localStorage.setItem(USER_DICT_KEY, JSON.stringify(merged));
2185
+ localStorage.setItem(USER_DICT_KEY2, JSON.stringify(merged));
1959
2186
  } catch {
1960
2187
  }
1961
2188
  }).catch(() => {
1962
2189
  });
1963
2190
  return sp;
1964
2191
  })();
1965
- return spellPromise;
2192
+ return spellPromise2;
1966
2193
  }
1967
- function addToUserDict(word, sp) {
2194
+ function addToUserDict2(word, sp) {
1968
2195
  try {
1969
- const raw = localStorage.getItem(USER_DICT_KEY);
2196
+ const raw = localStorage.getItem(USER_DICT_KEY2);
1970
2197
  const arr = raw ? JSON.parse(raw) : [];
1971
2198
  if (!arr.includes(word)) {
1972
2199
  arr.push(word);
1973
- localStorage.setItem(USER_DICT_KEY, JSON.stringify(arr));
2200
+ localStorage.setItem(USER_DICT_KEY2, JSON.stringify(arr));
1974
2201
  }
1975
2202
  } catch {
1976
2203
  }
@@ -1998,7 +2225,7 @@ function decorate(editor2, sp) {
1998
2225
  const savedBodyScrollTop = body.scrollTop;
1999
2226
  try {
2000
2227
  editor2.undoManager?.ignore?.(() => {
2001
- const old = body.querySelectorAll(`span[${MARKER_ATTR}]`);
2228
+ const old = body.querySelectorAll(`span[${MARKER_ATTR2}]`);
2002
2229
  for (const m of old) {
2003
2230
  const parent2 = m.parentNode;
2004
2231
  if (!parent2)
@@ -2012,7 +2239,7 @@ function decorate(editor2, sp) {
2012
2239
  acceptNode(node) {
2013
2240
  let p = node.parentNode;
2014
2241
  while (p && p !== body) {
2015
- if (p.nodeType === Node.ELEMENT_NODE && SKIP_TAGS.has(p.tagName)) {
2242
+ if (p.nodeType === Node.ELEMENT_NODE && SKIP_TAGS2.has(p.tagName)) {
2016
2243
  return NodeFilter.FILTER_REJECT;
2017
2244
  }
2018
2245
  p = p.parentNode;
@@ -2047,7 +2274,7 @@ function decorate(editor2, sp) {
2047
2274
  WORD_RE.lastIndex = 0;
2048
2275
  while ((m = WORD_RE.exec(text)) !== null) {
2049
2276
  const word = m[0];
2050
- if (word.length < MIN_WORD_LEN)
2277
+ if (word.length < MIN_WORD_LEN2)
2051
2278
  continue;
2052
2279
  const wStart = m.index, wEnd = m.index + word.length;
2053
2280
  if (emailRanges.some(([s, e]) => wStart < e && wEnd > s))
@@ -2067,7 +2294,7 @@ function decorate(editor2, sp) {
2067
2294
  range.setStart(h.node, h.start);
2068
2295
  range.setEnd(h.node, h.end);
2069
2296
  const span = doc.createElement("span");
2070
- span.setAttribute(MARKER_ATTR, "1");
2297
+ span.setAttribute(MARKER_ATTR2, "1");
2071
2298
  try {
2072
2299
  range.surroundContents(span);
2073
2300
  } catch {
@@ -2172,7 +2399,7 @@ function installDecorationStyle(editor2) {
2172
2399
  const style = doc.createElement("style");
2173
2400
  style.id = "mailx-spell-style";
2174
2401
  style.textContent = `
2175
- span[${MARKER_ATTR}] {
2402
+ span[${MARKER_ATTR2}] {
2176
2403
  text-decoration: underline wavy #d33;
2177
2404
  text-decoration-skip-ink: none;
2178
2405
  text-underline-offset: 2px;
@@ -2188,7 +2415,7 @@ function installSerializerFilter(editor2) {
2188
2415
  return;
2189
2416
  editor2.__mailxSpellSerializerWired = true;
2190
2417
  try {
2191
- editor2.serializer.addAttributeFilter(MARKER_ATTR, (nodes) => {
2418
+ editor2.serializer.addAttributeFilter(MARKER_ATTR2, (nodes) => {
2192
2419
  for (const node of nodes) {
2193
2420
  if (typeof node.unwrap === "function")
2194
2421
  node.unwrap();
@@ -2198,7 +2425,7 @@ function installSerializerFilter(editor2) {
2198
2425
  console.warn("[spellcheck] serializer filter setup failed:", e);
2199
2426
  }
2200
2427
  }
2201
- function showSuggestionsMenu(parentDoc, x, y, items) {
2428
+ function showSuggestionsMenu2(parentDoc, x, y, items) {
2202
2429
  parentDoc.getElementById("mailx-spell-menu")?.remove();
2203
2430
  const menu = parentDoc.createElement("div");
2204
2431
  menu.id = "mailx-spell-menu";
@@ -2313,7 +2540,7 @@ function cleanupCorrected(editor2, sp) {
2313
2540
  const activeSel = doc.getSelection();
2314
2541
  if (activeSel && activeSel.rangeCount > 0 && !activeSel.isCollapsed)
2315
2542
  return;
2316
- const markers = body.querySelectorAll(`span[${MARKER_ATTR}]`);
2543
+ const markers = body.querySelectorAll(`span[${MARKER_ATTR2}]`);
2317
2544
  if (markers.length === 0)
2318
2545
  return;
2319
2546
  let caretMarker = null;
@@ -2321,7 +2548,7 @@ function cleanupCorrected(editor2, sp) {
2321
2548
  if (sel && sel.rangeCount > 0) {
2322
2549
  let p = sel.focusNode;
2323
2550
  while (p && p !== body) {
2324
- if (p.nodeType === Node.ELEMENT_NODE && p.hasAttribute?.(MARKER_ATTR)) {
2551
+ if (p.nodeType === Node.ELEMENT_NODE && p.hasAttribute?.(MARKER_ATTR2)) {
2325
2552
  caretMarker = p;
2326
2553
  break;
2327
2554
  }
@@ -2395,7 +2622,7 @@ function wireSpellcheck(editor2) {
2395
2622
  cleanupCorrected(editor2, sp);
2396
2623
  }, CLEANUP_DEBOUNCE_MS);
2397
2624
  };
2398
- getSpell().then((loaded) => {
2625
+ getSpell2().then((loaded) => {
2399
2626
  sp = loaded;
2400
2627
  installDecorationStyle(editor2);
2401
2628
  installSerializerFilter(editor2);
@@ -2411,7 +2638,7 @@ function wireSpellcheck(editor2) {
2411
2638
  const target = e.target;
2412
2639
  if (!target)
2413
2640
  return;
2414
- const marker = target.closest?.(`span[${MARKER_ATTR}]`);
2641
+ const marker = target.closest?.(`span[${MARKER_ATTR2}]`);
2415
2642
  if (!marker)
2416
2643
  return;
2417
2644
  const word = marker.textContent || "";
@@ -2458,7 +2685,7 @@ function wireSpellcheck(editor2) {
2458
2685
  label: `Add "${word}" to dictionary`,
2459
2686
  action: () => {
2460
2687
  if (sp)
2461
- addToUserDict(word, sp);
2688
+ addToUserDict2(word, sp);
2462
2689
  scheduleDecorate();
2463
2690
  }
2464
2691
  });
@@ -2470,22 +2697,22 @@ function wireSpellcheck(editor2) {
2470
2697
  scheduleDecorate();
2471
2698
  }
2472
2699
  });
2473
- showSuggestionsMenu(document, iframeRect.left + e.clientX, iframeRect.top + e.clientY, items);
2700
+ showSuggestionsMenu2(document, iframeRect.left + e.clientX, iframeRect.top + e.clientY, items);
2474
2701
  }, true);
2475
2702
  }
2476
- var import_nspell, USER_DICT_KEY, MARKER_ATTR, DECORATE_DEBOUNCE_MS, CLEANUP_DEBOUNCE_MS, MIN_WORD_LEN, SKIP_TAGS, spellPromise;
2703
+ var import_nspell2, USER_DICT_KEY2, MARKER_ATTR2, DECORATE_DEBOUNCE_MS, CLEANUP_DEBOUNCE_MS, MIN_WORD_LEN2, SKIP_TAGS2, spellPromise2;
2477
2704
  var init_spellcheck = __esm({
2478
2705
  "client/compose/spellcheck.js"() {
2479
2706
  "use strict";
2480
- import_nspell = __toESM(require_lib(), 1);
2707
+ import_nspell2 = __toESM(require_lib(), 1);
2481
2708
  init_api_client();
2482
- USER_DICT_KEY = "mailx-user-dict";
2483
- MARKER_ATTR = "data-mailx-spellerror";
2709
+ USER_DICT_KEY2 = "mailx-user-dict";
2710
+ MARKER_ATTR2 = "data-mailx-spellerror";
2484
2711
  DECORATE_DEBOUNCE_MS = 1200;
2485
2712
  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;
2713
+ MIN_WORD_LEN2 = 3;
2714
+ SKIP_TAGS2 = /* @__PURE__ */ new Set(["BLOCKQUOTE", "CODE", "PRE", "A", "SCRIPT", "STYLE", "KBD", "SAMP", "VAR"]);
2715
+ spellPromise2 = null;
2489
2716
  }
2490
2717
  });
2491
2718
 
@@ -2952,6 +3179,72 @@ function createQuillEditor(container2) {
2952
3179
  toolbar?.addHandler("link", function() {
2953
3180
  openLinkForRange(q, q.getSelection() || { index: q.getLength() - 1, length: 0 });
2954
3181
  });
3182
+ let _spellSp = null;
3183
+ Promise.resolve().then(() => (init_spellcheck_core(), spellcheck_core_exports)).then((m) => m.getSpell()).then((sp) => {
3184
+ _spellSp = sp;
3185
+ }).catch(() => {
3186
+ });
3187
+ q.root.addEventListener("contextmenu", async (e) => {
3188
+ try {
3189
+ if (!_spellSp)
3190
+ return;
3191
+ const sel = q.getSelection();
3192
+ if (sel && sel.length > 0)
3193
+ return;
3194
+ const core = await Promise.resolve().then(() => (init_spellcheck_core(), spellcheck_core_exports));
3195
+ const hit = core.getWordAtPoint(q.root, e.clientX, e.clientY);
3196
+ if (!hit)
3197
+ return;
3198
+ const { word, node, start, end } = hit;
3199
+ if (_spellSp.correct(word))
3200
+ return;
3201
+ e.preventDefault();
3202
+ e.stopPropagation();
3203
+ const sugs = core.buildSuggestionList(word, _spellSp);
3204
+ const items = [];
3205
+ if (sugs.length === 0) {
3206
+ items.push({ label: "(no suggestions)", action: () => {
3207
+ } });
3208
+ } else {
3209
+ for (const s of sugs) {
3210
+ items.push({
3211
+ label: s,
3212
+ emphasized: true,
3213
+ action: () => {
3214
+ try {
3215
+ const range = document.createRange();
3216
+ range.setStart(node, start);
3217
+ range.setEnd(node, end);
3218
+ const docSel = document.getSelection();
3219
+ if (docSel) {
3220
+ docSel.removeAllRanges();
3221
+ docSel.addRange(range);
3222
+ if (!document.execCommand("insertText", false, s)) {
3223
+ range.deleteContents();
3224
+ range.insertNode(document.createTextNode(s));
3225
+ }
3226
+ }
3227
+ } catch {
3228
+ }
3229
+ }
3230
+ });
3231
+ }
3232
+ }
3233
+ items.push({ label: "", action: () => {
3234
+ }, separator: true });
3235
+ items.push({
3236
+ label: `Add "${word}" to dictionary`,
3237
+ action: () => core.addToUserDict(word, _spellSp)
3238
+ });
3239
+ items.push({
3240
+ label: "Ignore (this session)",
3241
+ action: () => _spellSp.add(word)
3242
+ });
3243
+ core.showSuggestionsMenu(document, e.clientX, e.clientY, items);
3244
+ } catch (err) {
3245
+ console.warn("[spellcheck] right-click handler error:", err?.message || err);
3246
+ }
3247
+ });
2955
3248
  q.root.addEventListener("contextmenu", async (e) => {
2956
3249
  try {
2957
3250
  const sel = q.getSelection();