@bobfrankston/rmfmail 1.0.467
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +429 -0
- package/bin/build-icon-ico.js +283 -0
- package/bin/lean-accounts.js +172 -0
- package/bin/mailx.js +1580 -0
- package/bin/mailx.js.map +1 -0
- package/bin/mailx.ts +1549 -0
- package/bin/postinstall.js +39 -0
- package/bin/postinstall.js.map +1 -0
- package/bin/postinstall.ts +41 -0
- package/bin/tsconfig.json +10 -0
- package/client/.gitattributes +10 -0
- package/client/.msger-window.json +1 -0
- package/client/android.html +226 -0
- package/client/app.js +3400 -0
- package/client/app.js.map +1 -0
- package/client/app.ts +3190 -0
- package/client/components/address-book.js +199 -0
- package/client/components/address-book.js.map +1 -0
- package/client/components/address-book.ts +204 -0
- package/client/components/alarms.js +299 -0
- package/client/components/alarms.js.map +1 -0
- package/client/components/alarms.ts +276 -0
- package/client/components/calendar-sidebar.js +508 -0
- package/client/components/calendar-sidebar.js.map +1 -0
- package/client/components/calendar-sidebar.ts +474 -0
- package/client/components/calendar.js +217 -0
- package/client/components/calendar.js.map +1 -0
- package/client/components/calendar.ts +211 -0
- package/client/components/context-menu.js +80 -0
- package/client/components/context-menu.js.map +1 -0
- package/client/components/context-menu.ts +95 -0
- package/client/components/folder-picker.js +119 -0
- package/client/components/folder-picker.js.map +1 -0
- package/client/components/folder-picker.ts +127 -0
- package/client/components/folder-tree.js +1093 -0
- package/client/components/folder-tree.js.map +1 -0
- package/client/components/folder-tree.ts +1069 -0
- package/client/components/message-list.js +1140 -0
- package/client/components/message-list.js.map +1 -0
- package/client/components/message-list.ts +1129 -0
- package/client/components/message-viewer.js +1291 -0
- package/client/components/message-viewer.js.map +1 -0
- package/client/components/message-viewer.ts +1257 -0
- package/client/components/outbox-view.js +104 -0
- package/client/components/outbox-view.js.map +1 -0
- package/client/components/outbox-view.ts +102 -0
- package/client/components/tasks.js +261 -0
- package/client/components/tasks.js.map +1 -0
- package/client/components/tasks.ts +234 -0
- package/client/compose/compose.css +560 -0
- package/client/compose/compose.html +52 -0
- package/client/compose/compose.js +1289 -0
- package/client/compose/compose.js.map +1 -0
- package/client/compose/compose.ts +1231 -0
- package/client/compose/editor.js +616 -0
- package/client/compose/editor.js.map +1 -0
- package/client/compose/editor.ts +599 -0
- package/client/compose/ghost-text.js +122 -0
- package/client/compose/ghost-text.js.map +1 -0
- package/client/compose/ghost-text.ts +140 -0
- package/client/favicon.svg +11 -0
- package/client/icon.ico +0 -0
- package/client/icon.png +0 -0
- package/client/index.html +230 -0
- package/client/lib/android-bootstrap.js +9 -0
- package/client/lib/android-bootstrap.js.map +1 -0
- package/client/lib/android-bootstrap.ts +9 -0
- package/client/lib/api-client.js +378 -0
- package/client/lib/api-client.js.map +1 -0
- package/client/lib/api-client.ts +439 -0
- package/client/lib/local-service.js +603 -0
- package/client/lib/local-service.js.map +1 -0
- package/client/lib/local-service.ts +646 -0
- package/client/lib/local-store.js +214 -0
- package/client/lib/local-store.js.map +1 -0
- package/client/lib/local-store.ts +283 -0
- package/client/lib/mailxapi.js +304 -0
- package/client/lib/message-state.js +132 -0
- package/client/lib/message-state.js.map +1 -0
- package/client/lib/message-state.ts +160 -0
- package/client/package.json +16 -0
- package/client/styles/components.css +2049 -0
- package/client/styles/layout.css +332 -0
- package/client/styles/variables.css +85 -0
- package/client/tsconfig.json +19 -0
- package/package.json +124 -0
- package/packages/mailx-api/.gitattributes +10 -0
- package/packages/mailx-api/index.d.ts +10 -0
- package/packages/mailx-api/index.d.ts.map +1 -0
- package/packages/mailx-api/index.js +322 -0
- package/packages/mailx-api/index.js.map +1 -0
- package/packages/mailx-api/index.ts +283 -0
- package/packages/mailx-api/package.json +27 -0
- package/packages/mailx-api/tsconfig.json +9 -0
- package/packages/mailx-api/tsconfig.tsbuildinfo +1 -0
- package/packages/mailx-compose/.gitattributes +10 -0
- package/packages/mailx-compose/index.d.ts +21 -0
- package/packages/mailx-compose/index.d.ts.map +1 -0
- package/packages/mailx-compose/index.js +61 -0
- package/packages/mailx-compose/index.js.map +1 -0
- package/packages/mailx-compose/index.ts +85 -0
- package/packages/mailx-compose/package.json +18 -0
- package/packages/mailx-compose/tsconfig.json +9 -0
- package/packages/mailx-core/index.d.ts +139 -0
- package/packages/mailx-core/index.d.ts.map +1 -0
- package/packages/mailx-core/index.js +403 -0
- package/packages/mailx-core/index.js.map +1 -0
- package/packages/mailx-core/index.ts +424 -0
- package/packages/mailx-core/ipc.d.ts +13 -0
- package/packages/mailx-core/ipc.d.ts.map +1 -0
- package/packages/mailx-core/ipc.js +56 -0
- package/packages/mailx-core/ipc.js.map +1 -0
- package/packages/mailx-core/ipc.ts +62 -0
- package/packages/mailx-core/package.json +18 -0
- package/packages/mailx-core/tsconfig.json +9 -0
- package/packages/mailx-host/.gitattributes +10 -0
- package/packages/mailx-host/index.d.ts +21 -0
- package/packages/mailx-host/index.d.ts.map +1 -0
- package/packages/mailx-host/index.js +29 -0
- package/packages/mailx-host/index.js.map +1 -0
- package/packages/mailx-host/index.ts +38 -0
- package/packages/mailx-host/package.json +23 -0
- package/packages/mailx-host/tsconfig.json +9 -0
- package/packages/mailx-host/types-shim.d.ts +14 -0
- package/packages/mailx-imap/.gitattributes +10 -0
- package/packages/mailx-imap/index.d.ts +442 -0
- package/packages/mailx-imap/index.d.ts.map +1 -0
- package/packages/mailx-imap/index.js +3684 -0
- package/packages/mailx-imap/index.js.map +1 -0
- package/packages/mailx-imap/index.ts +3652 -0
- package/packages/mailx-imap/package-lock.json +131 -0
- package/packages/mailx-imap/package.json +28 -0
- package/packages/mailx-imap/providers/gmail-api.d.ts +8 -0
- package/packages/mailx-imap/providers/gmail-api.d.ts.map +1 -0
- package/packages/mailx-imap/providers/gmail-api.js +8 -0
- package/packages/mailx-imap/providers/gmail-api.js.map +1 -0
- package/packages/mailx-imap/providers/gmail-api.ts +8 -0
- package/packages/mailx-imap/providers/outlook-api.ts +7 -0
- package/packages/mailx-imap/providers/types.d.ts +9 -0
- package/packages/mailx-imap/providers/types.d.ts.map +1 -0
- package/packages/mailx-imap/providers/types.js +9 -0
- package/packages/mailx-imap/providers/types.js.map +1 -0
- package/packages/mailx-imap/providers/types.ts +9 -0
- package/packages/mailx-imap/tsconfig.json +9 -0
- package/packages/mailx-imap/tsconfig.tsbuildinfo +1 -0
- package/packages/mailx-send/.gitattributes +10 -0
- package/packages/mailx-send/README.md +217 -0
- package/packages/mailx-send/cli-queue.d.ts +11 -0
- package/packages/mailx-send/cli-queue.d.ts.map +1 -0
- package/packages/mailx-send/cli-queue.js +59 -0
- package/packages/mailx-send/cli-queue.js.map +1 -0
- package/packages/mailx-send/cli-queue.ts +62 -0
- package/packages/mailx-send/cli-send.d.ts +17 -0
- package/packages/mailx-send/cli-send.d.ts.map +1 -0
- package/packages/mailx-send/cli-send.js +75 -0
- package/packages/mailx-send/cli-send.js.map +1 -0
- package/packages/mailx-send/cli-send.ts +83 -0
- package/packages/mailx-send/cli.d.ts +22 -0
- package/packages/mailx-send/cli.d.ts.map +1 -0
- package/packages/mailx-send/cli.js +115 -0
- package/packages/mailx-send/cli.js.map +1 -0
- package/packages/mailx-send/cli.ts +126 -0
- package/packages/mailx-send/index.d.ts +77 -0
- package/packages/mailx-send/index.d.ts.map +1 -0
- package/packages/mailx-send/index.js +264 -0
- package/packages/mailx-send/index.js.map +1 -0
- package/packages/mailx-send/index.ts +333 -0
- package/packages/mailx-send/mailsend/README.md +133 -0
- package/packages/mailx-send/mailsend/cli.d.ts +12 -0
- package/packages/mailx-send/mailsend/cli.d.ts.map +1 -0
- package/packages/mailx-send/mailsend/cli.js +70 -0
- package/packages/mailx-send/mailsend/cli.js.map +1 -0
- package/packages/mailx-send/mailsend/cli.ts +81 -0
- package/packages/mailx-send/mailsend/index.d.ts +77 -0
- package/packages/mailx-send/mailsend/index.d.ts.map +1 -0
- package/packages/mailx-send/mailsend/index.js +264 -0
- package/packages/mailx-send/mailsend/index.js.map +1 -0
- package/packages/mailx-send/mailsend/index.ts +333 -0
- package/packages/mailx-send/mailsend/package-lock.json +65 -0
- package/packages/mailx-send/mailsend/package.json +26 -0
- package/packages/mailx-send/mailsend/tsconfig.json +21 -0
- package/packages/mailx-send/package-lock.json +65 -0
- package/packages/mailx-send/package.json +37 -0
- package/packages/mailx-send/tsconfig.json +21 -0
- package/packages/mailx-server/.gitattributes +10 -0
- package/packages/mailx-server/index.d.ts +8 -0
- package/packages/mailx-server/index.d.ts.map +1 -0
- package/packages/mailx-server/index.js +397 -0
- package/packages/mailx-server/index.js.map +1 -0
- package/packages/mailx-server/index.ts +429 -0
- package/packages/mailx-server/package.json +28 -0
- package/packages/mailx-server/tsconfig.json +9 -0
- package/packages/mailx-server/tsconfig.tsbuildinfo +1 -0
- package/packages/mailx-service/google-sync.d.ts +96 -0
- package/packages/mailx-service/google-sync.d.ts.map +1 -0
- package/packages/mailx-service/google-sync.js +153 -0
- package/packages/mailx-service/google-sync.js.map +1 -0
- package/packages/mailx-service/google-sync.ts +238 -0
- package/packages/mailx-service/index.d.ts +366 -0
- package/packages/mailx-service/index.d.ts.map +1 -0
- package/packages/mailx-service/index.js +2589 -0
- package/packages/mailx-service/index.js.map +1 -0
- package/packages/mailx-service/index.ts +2512 -0
- package/packages/mailx-service/jsonrpc.d.ts +25 -0
- package/packages/mailx-service/jsonrpc.d.ts.map +1 -0
- package/packages/mailx-service/jsonrpc.js +238 -0
- package/packages/mailx-service/jsonrpc.js.map +1 -0
- package/packages/mailx-service/jsonrpc.ts +268 -0
- package/packages/mailx-service/package.json +22 -0
- package/packages/mailx-service/tsconfig.json +9 -0
- package/packages/mailx-settings/.gitattributes +10 -0
- package/packages/mailx-settings/cloud.d.ts +47 -0
- package/packages/mailx-settings/cloud.d.ts.map +1 -0
- package/packages/mailx-settings/cloud.js +408 -0
- package/packages/mailx-settings/cloud.js.map +1 -0
- package/packages/mailx-settings/cloud.ts +393 -0
- package/packages/mailx-settings/index.d.ts +140 -0
- package/packages/mailx-settings/index.d.ts.map +1 -0
- package/packages/mailx-settings/index.js +872 -0
- package/packages/mailx-settings/index.js.map +1 -0
- package/packages/mailx-settings/index.ts +908 -0
- package/packages/mailx-settings/package.json +20 -0
- package/packages/mailx-settings/tsconfig.json +9 -0
- package/packages/mailx-settings/tsconfig.tsbuildinfo +1 -0
- package/packages/mailx-store/.gitattributes +10 -0
- package/packages/mailx-store/db.d.ts +346 -0
- package/packages/mailx-store/db.d.ts.map +1 -0
- package/packages/mailx-store/db.js +1796 -0
- package/packages/mailx-store/db.js.map +1 -0
- package/packages/mailx-store/db.ts +2007 -0
- package/packages/mailx-store/file-store.d.ts +36 -0
- package/packages/mailx-store/file-store.d.ts.map +1 -0
- package/packages/mailx-store/file-store.js +80 -0
- package/packages/mailx-store/file-store.js.map +1 -0
- package/packages/mailx-store/file-store.ts +82 -0
- package/packages/mailx-store/index.d.ts +7 -0
- package/packages/mailx-store/index.d.ts.map +1 -0
- package/packages/mailx-store/index.js +7 -0
- package/packages/mailx-store/index.js.map +1 -0
- package/packages/mailx-store/index.ts +7 -0
- package/packages/mailx-store/package.json +19 -0
- package/packages/mailx-store/tsconfig.json +9 -0
- package/packages/mailx-store/tsconfig.tsbuildinfo +1 -0
- package/packages/mailx-store-web/android-bootstrap.d.ts +16 -0
- package/packages/mailx-store-web/android-bootstrap.d.ts.map +1 -0
- package/packages/mailx-store-web/android-bootstrap.js +1247 -0
- package/packages/mailx-store-web/android-bootstrap.js.map +1 -0
- package/packages/mailx-store-web/android-bootstrap.ts +1262 -0
- package/packages/mailx-store-web/db.d.ts +132 -0
- package/packages/mailx-store-web/db.d.ts.map +1 -0
- package/packages/mailx-store-web/db.js +642 -0
- package/packages/mailx-store-web/db.js.map +1 -0
- package/packages/mailx-store-web/db.ts +756 -0
- package/packages/mailx-store-web/gmail-api-web.d.ts +11 -0
- package/packages/mailx-store-web/gmail-api-web.d.ts.map +1 -0
- package/packages/mailx-store-web/gmail-api-web.js +11 -0
- package/packages/mailx-store-web/gmail-api-web.js.map +1 -0
- package/packages/mailx-store-web/gmail-api-web.ts +11 -0
- package/packages/mailx-store-web/imap-web-provider.d.ts +33 -0
- package/packages/mailx-store-web/imap-web-provider.d.ts.map +1 -0
- package/packages/mailx-store-web/imap-web-provider.js +140 -0
- package/packages/mailx-store-web/imap-web-provider.js.map +1 -0
- package/packages/mailx-store-web/imap-web-provider.ts +156 -0
- package/packages/mailx-store-web/index.d.ts +10 -0
- package/packages/mailx-store-web/index.d.ts.map +1 -0
- package/packages/mailx-store-web/index.js +10 -0
- package/packages/mailx-store-web/index.js.map +1 -0
- package/packages/mailx-store-web/index.ts +10 -0
- package/packages/mailx-store-web/main-thread-host.d.ts +15 -0
- package/packages/mailx-store-web/main-thread-host.d.ts.map +1 -0
- package/packages/mailx-store-web/main-thread-host.js +292 -0
- package/packages/mailx-store-web/main-thread-host.js.map +1 -0
- package/packages/mailx-store-web/main-thread-host.ts +322 -0
- package/packages/mailx-store-web/package.json +23 -0
- package/packages/mailx-store-web/provider-types.d.ts +7 -0
- package/packages/mailx-store-web/provider-types.d.ts.map +1 -0
- package/packages/mailx-store-web/provider-types.js +7 -0
- package/packages/mailx-store-web/provider-types.js.map +1 -0
- package/packages/mailx-store-web/provider-types.ts +7 -0
- package/packages/mailx-store-web/sql-wasm-esm.js +10 -0
- package/packages/mailx-store-web/sql.js.d.ts +29 -0
- package/packages/mailx-store-web/sync-manager.d.ts +68 -0
- package/packages/mailx-store-web/sync-manager.d.ts.map +1 -0
- package/packages/mailx-store-web/sync-manager.js +506 -0
- package/packages/mailx-store-web/sync-manager.js.map +1 -0
- package/packages/mailx-store-web/sync-manager.ts +508 -0
- package/packages/mailx-store-web/tsconfig.json +10 -0
- package/packages/mailx-store-web/web-jsonrpc.d.ts +20 -0
- package/packages/mailx-store-web/web-jsonrpc.d.ts.map +1 -0
- package/packages/mailx-store-web/web-jsonrpc.js +102 -0
- package/packages/mailx-store-web/web-jsonrpc.js.map +1 -0
- package/packages/mailx-store-web/web-jsonrpc.ts +116 -0
- package/packages/mailx-store-web/web-message-store.d.ts +16 -0
- package/packages/mailx-store-web/web-message-store.d.ts.map +1 -0
- package/packages/mailx-store-web/web-message-store.js +89 -0
- package/packages/mailx-store-web/web-message-store.js.map +1 -0
- package/packages/mailx-store-web/web-message-store.ts +97 -0
- package/packages/mailx-store-web/web-service.d.ts +111 -0
- package/packages/mailx-store-web/web-service.d.ts.map +1 -0
- package/packages/mailx-store-web/web-service.js +542 -0
- package/packages/mailx-store-web/web-service.js.map +1 -0
- package/packages/mailx-store-web/web-service.ts +616 -0
- package/packages/mailx-store-web/web-settings.d.ts +83 -0
- package/packages/mailx-store-web/web-settings.d.ts.map +1 -0
- package/packages/mailx-store-web/web-settings.js +491 -0
- package/packages/mailx-store-web/web-settings.js.map +1 -0
- package/packages/mailx-store-web/web-settings.ts +522 -0
- package/packages/mailx-store-web/worker-bundle.js +6838 -0
- package/packages/mailx-store-web/worker-entry.d.ts +8 -0
- package/packages/mailx-store-web/worker-entry.d.ts.map +1 -0
- package/packages/mailx-store-web/worker-entry.js +187 -0
- package/packages/mailx-store-web/worker-entry.js.map +1 -0
- package/packages/mailx-store-web/worker-entry.ts +215 -0
- package/packages/mailx-store-web/worker-tcp-transport.d.ts +28 -0
- package/packages/mailx-store-web/worker-tcp-transport.d.ts.map +1 -0
- package/packages/mailx-store-web/worker-tcp-transport.js +98 -0
- package/packages/mailx-store-web/worker-tcp-transport.js.map +1 -0
- package/packages/mailx-store-web/worker-tcp-transport.ts +101 -0
- package/packages/mailx-types/.gitattributes +10 -0
- package/packages/mailx-types/index.d.ts +319 -0
- package/packages/mailx-types/index.d.ts.map +1 -0
- package/packages/mailx-types/index.js +243 -0
- package/packages/mailx-types/index.js.map +1 -0
- package/packages/mailx-types/index.ts +498 -0
- package/packages/mailx-types/package.json +16 -0
- package/packages/mailx-types/tsconfig.json +9 -0
- package/packages/mailx-types/tsconfig.tsbuildinfo +1 -0
- package/todo.json +13 -0
- package/tsconfig.base.json +31 -0
- package/tsconfig.json +9 -0
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* mailxapi — IPC bridge injected into WebView by the Rust launcher.
|
|
3
|
+
* Follows the msgapi pattern: callNode → Promise → _msgapiServiceResolve/Reject.
|
|
4
|
+
*
|
|
5
|
+
* When running in a browser (no IPC), this file is not loaded.
|
|
6
|
+
* api-client.ts auto-detects and falls back to HTTP fetch.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
(function() {
|
|
10
|
+
"use strict";
|
|
11
|
+
|
|
12
|
+
var _callbacks = {};
|
|
13
|
+
var _callbackId = 0;
|
|
14
|
+
var _eventHandlers = [];
|
|
15
|
+
function callNode(action, params) {
|
|
16
|
+
var id = String(++_callbackId);
|
|
17
|
+
return new Promise(function(resolve, reject) {
|
|
18
|
+
var timer = setTimeout(function() {
|
|
19
|
+
delete _callbacks[id];
|
|
20
|
+
reject(new Error("mailxapi timeout: " + action));
|
|
21
|
+
}, 120000);
|
|
22
|
+
_callbacks[id] = { resolve: resolve, reject: reject, timer: timer };
|
|
23
|
+
var msg = Object.assign({ _action: action, _cbid: id }, params || {});
|
|
24
|
+
if (window.ipc && window.ipc.postMessage) {
|
|
25
|
+
window.ipc.postMessage(JSON.stringify(msg));
|
|
26
|
+
} else {
|
|
27
|
+
clearTimeout(timer);
|
|
28
|
+
delete _callbacks[id];
|
|
29
|
+
reject(new Error("No IPC channel available"));
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Called by host (msger Rust / msgview preload) to resolve service-channel promises
|
|
35
|
+
window._msgapiServiceResolve = function(id, value) {
|
|
36
|
+
var cb = _callbacks[id];
|
|
37
|
+
if (!cb) return;
|
|
38
|
+
delete _callbacks[id];
|
|
39
|
+
clearTimeout(cb.timer);
|
|
40
|
+
cb.resolve(value);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// Called by host to reject service-channel promises
|
|
44
|
+
window._msgapiServiceReject = function(id, error) {
|
|
45
|
+
var cb = _callbacks[id];
|
|
46
|
+
if (!cb) return;
|
|
47
|
+
delete _callbacks[id];
|
|
48
|
+
clearTimeout(cb.timer);
|
|
49
|
+
cb.reject(new Error(error));
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// Called by host to push events (new mail, sync progress, etc.)
|
|
53
|
+
window._msgapiServiceEvent = function(event) {
|
|
54
|
+
for (var i = 0; i < _eventHandlers.length; i++) {
|
|
55
|
+
try { _eventHandlers[i](event); } catch(e) { /* ignore */ }
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
window.mailxapi = {
|
|
60
|
+
isApp: true,
|
|
61
|
+
platform: "webview2",
|
|
62
|
+
|
|
63
|
+
// Accounts
|
|
64
|
+
getAccounts: function() { return callNode("getAccounts"); },
|
|
65
|
+
getFolders: function(accountId) { return callNode("getFolders", { accountId: accountId }); },
|
|
66
|
+
|
|
67
|
+
// Messages
|
|
68
|
+
getMessages: function(accountId, folderId, page, pageSize, sort, sortDir, search, flaggedOnly) {
|
|
69
|
+
return callNode("getMessages", { accountId: accountId, folderId: folderId, page: page, pageSize: pageSize, sort: sort, sortDir: sortDir, search: search, flaggedOnly: flaggedOnly });
|
|
70
|
+
},
|
|
71
|
+
getUnifiedInbox: function(page, pageSize) {
|
|
72
|
+
return callNode("getUnifiedInbox", { page: page, pageSize: pageSize });
|
|
73
|
+
},
|
|
74
|
+
getMessage: function(accountId, uid, allowRemote, folderId) {
|
|
75
|
+
return callNode("getMessage", { accountId: accountId, uid: uid, allowRemote: allowRemote, folderId: folderId });
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
// Actions
|
|
79
|
+
updateFlags: function(accountId, uid, flags) {
|
|
80
|
+
return callNode("updateFlags", { accountId: accountId, uid: uid, flags: flags });
|
|
81
|
+
},
|
|
82
|
+
deleteMessage: function(accountId, uid) {
|
|
83
|
+
return callNode("deleteMessage", { accountId: accountId, uid: uid });
|
|
84
|
+
},
|
|
85
|
+
undeleteMessage: function(accountId, uid, folderId) {
|
|
86
|
+
return callNode("undeleteMessage", { accountId: accountId, uid: uid, folderId: folderId });
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
// Diagnostic tracing — callers (compose iframe, app, components) can
|
|
90
|
+
// ship arbitrary named events to the Node log. Surfaces as
|
|
91
|
+
// `[client] <tag> <data>` so a failing pipeline can be traced end
|
|
92
|
+
// to end in a single log file.
|
|
93
|
+
logClientEvent: function(tag, data) { return callNode("logClientEvent", { tag: tag, data: data }); },
|
|
94
|
+
|
|
95
|
+
// Compose
|
|
96
|
+
sendMessage: function(msg) { return callNode("sendMessage", msg); },
|
|
97
|
+
saveDraft: function(params) { return callNode("saveDraft", params); },
|
|
98
|
+
deleteDraft: function(accountId, draftUid, draftId) {
|
|
99
|
+
return callNode("deleteDraft", { accountId: accountId, draftUid: draftUid, draftId: draftId });
|
|
100
|
+
},
|
|
101
|
+
|
|
102
|
+
// Search
|
|
103
|
+
searchMessages: function(query, page, pageSize) {
|
|
104
|
+
return callNode("searchMessages", { query: query, page: page, pageSize: pageSize });
|
|
105
|
+
},
|
|
106
|
+
addContact: function(name, email) {
|
|
107
|
+
return callNode("addContact", { name: name, email: email });
|
|
108
|
+
},
|
|
109
|
+
getThreadMessages: function(accountId, threadId) {
|
|
110
|
+
return callNode("getThreadMessages", { accountId: accountId, threadId: threadId });
|
|
111
|
+
},
|
|
112
|
+
// Calendar / Tasks two-way cache. Reads return local DB immediately;
|
|
113
|
+
// writes commit locally and queue a push-to-Google action.
|
|
114
|
+
getCalendarEvents: function(fromMs, toMs) {
|
|
115
|
+
return callNode("getCalendarEvents", { fromMs: fromMs, toMs: toMs });
|
|
116
|
+
},
|
|
117
|
+
createCalendarEvent: function(ev) { return callNode("createCalendarEvent", ev); },
|
|
118
|
+
updateCalendarEvent: function(uuid, patch) { return callNode("updateCalendarEvent", { uuid: uuid, patch: patch }); },
|
|
119
|
+
deleteCalendarEvent: function(uuid) { return callNode("deleteCalendarEvent", { uuid: uuid }); },
|
|
120
|
+
getTasks: function(includeCompleted) { return callNode("getTasks", { includeCompleted: !!includeCompleted }); },
|
|
121
|
+
createTask: function(t) { return callNode("createTask", t); },
|
|
122
|
+
updateTask: function(uuid, patch) { return callNode("updateTask", { uuid: uuid, patch: patch }); },
|
|
123
|
+
deleteTask: function(uuid) { return callNode("deleteTask", { uuid: uuid }); },
|
|
124
|
+
drainStoreSync: function() { return callNode("drainStoreSync"); },
|
|
125
|
+
recordSpamReport: function(accountId, uid, folderId) {
|
|
126
|
+
return callNode("recordSpamReport", { accountId: accountId, uid: uid, folderId: folderId });
|
|
127
|
+
},
|
|
128
|
+
readJsoncFile: function(name) {
|
|
129
|
+
return callNode("readJsoncFile", { name: name });
|
|
130
|
+
},
|
|
131
|
+
writeJsoncFile: function(name, content) {
|
|
132
|
+
return callNode("writeJsoncFile", { name: name, content: content });
|
|
133
|
+
},
|
|
134
|
+
formatJsonc: function(content) {
|
|
135
|
+
return callNode("formatJsonc", { content: content });
|
|
136
|
+
},
|
|
137
|
+
readConfigHelp: function(name) {
|
|
138
|
+
return callNode("readConfigHelp", { name: name });
|
|
139
|
+
},
|
|
140
|
+
unsubscribeOneClick: function(url) {
|
|
141
|
+
return callNode("unsubscribeOneClick", { url: url });
|
|
142
|
+
},
|
|
143
|
+
openInWord: function(editId, html) {
|
|
144
|
+
return callNode("openInWord", { editId: editId, html: html });
|
|
145
|
+
},
|
|
146
|
+
closeWordEdit: function(editId) {
|
|
147
|
+
return callNode("closeWordEdit", { editId: editId });
|
|
148
|
+
},
|
|
149
|
+
aiTransform: function(req) {
|
|
150
|
+
return callNode("aiTransform", req);
|
|
151
|
+
},
|
|
152
|
+
// Restart the background service so edits to accounts.jsonc /
|
|
153
|
+
// allowlist.jsonc / other live config take effect without the user
|
|
154
|
+
// having to type `mailx -kill`. The service spawns a fresh detached
|
|
155
|
+
// daemon and then exits; the new daemon takes over.
|
|
156
|
+
restartDaemon: function() {
|
|
157
|
+
return new Promise(function(resolve) {
|
|
158
|
+
var id = String(++_callbackId);
|
|
159
|
+
_callbacks[id] = { resolve: resolve, reject: resolve, timer: setTimeout(function() {
|
|
160
|
+
delete _callbacks[id];
|
|
161
|
+
resolve({ ok: true, status: "service-gone" });
|
|
162
|
+
}, 5000) };
|
|
163
|
+
try { window.ipc.postMessage(JSON.stringify({ _action: "restartDaemon", _cbid: id })); } catch(e) { resolve({ ok: false }); }
|
|
164
|
+
});
|
|
165
|
+
},
|
|
166
|
+
searchContacts: function(query) {
|
|
167
|
+
return callNode("searchContacts", { query: query });
|
|
168
|
+
},
|
|
169
|
+
listContacts: function(query, page, pageSize) {
|
|
170
|
+
return callNode("listContacts", { query: query || "", page: page || 1, pageSize: pageSize || 100 });
|
|
171
|
+
},
|
|
172
|
+
upsertContact: function(name, email) {
|
|
173
|
+
return callNode("upsertContact", { name: name, email: email });
|
|
174
|
+
},
|
|
175
|
+
deleteContact: function(email) {
|
|
176
|
+
return callNode("deleteContact", { email: email });
|
|
177
|
+
},
|
|
178
|
+
addPreferredContact: function(name, email, source, organization) {
|
|
179
|
+
return callNode("addPreferredContact", {
|
|
180
|
+
name: name || "", email: email,
|
|
181
|
+
source: source || "", organization: organization || "",
|
|
182
|
+
});
|
|
183
|
+
},
|
|
184
|
+
addToDenylist: function(email) {
|
|
185
|
+
return callNode("addToDenylist", { email: email });
|
|
186
|
+
},
|
|
187
|
+
openLocalPath: function(which) {
|
|
188
|
+
return callNode("openLocalPath", { which: which });
|
|
189
|
+
},
|
|
190
|
+
|
|
191
|
+
// Item 17: platform-aware external openers. On Android (MAUI shell)
|
|
192
|
+
// these fire native Intents so Contacts/Calendar links jump to the
|
|
193
|
+
// OS apps instead of the browser. On desktop the MAUI shell isn't
|
|
194
|
+
// in play; we fall through to a URL open via the navigation path
|
|
195
|
+
// that msger / external launcher handles the same as any http link.
|
|
196
|
+
openContact: function(email) {
|
|
197
|
+
try {
|
|
198
|
+
// MAUI intercepts this scheme in OnNavigating and fires a
|
|
199
|
+
// native ACTION_VIEW intent on the mailto: URI — system
|
|
200
|
+
// Contacts registers for it on stock Android.
|
|
201
|
+
if (/Android/i.test(navigator.userAgent)) {
|
|
202
|
+
window.location.href = "mailxapi-intent://contact/" + encodeURIComponent(email);
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
} catch (e) { /* navigation blocked — fall through */ }
|
|
206
|
+
// Desktop / browser fallback: open mailto: — msger forwards
|
|
207
|
+
// mailto URLs to the system default handler, which on a mailx
|
|
208
|
+
// install is mailx itself (our own compose window takes over).
|
|
209
|
+
window.location.href = "mailto:" + email;
|
|
210
|
+
},
|
|
211
|
+
openCalendarEvent: function(htmlLink) {
|
|
212
|
+
if (!htmlLink) return;
|
|
213
|
+
try {
|
|
214
|
+
if (/Android/i.test(navigator.userAgent)) {
|
|
215
|
+
// MAUI dispatches this to the native Calendar app via
|
|
216
|
+
// ACTION_VIEW on the Google Calendar event URL.
|
|
217
|
+
window.location.href = "mailxapi-intent://calendar/" + encodeURIComponent(htmlLink);
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
} catch (e) { /* */ }
|
|
221
|
+
// Desktop: existing openExternal path (browser, new tab).
|
|
222
|
+
window.open(htmlLink, "_blank");
|
|
223
|
+
},
|
|
224
|
+
|
|
225
|
+
// Sync
|
|
226
|
+
syncAll: function() { return callNode("syncAll"); },
|
|
227
|
+
syncAccount: function(accountId) { return callNode("syncAccount", { accountId: accountId }); },
|
|
228
|
+
getSyncPending: function() { return callNode("getSyncPending"); },
|
|
229
|
+
getOutboxStatus: function() { return callNode("getOutboxStatus"); },
|
|
230
|
+
getDiagnostics: function() { return callNode("getDiagnostics"); },
|
|
231
|
+
getPrimaryAccount: function(feature) { return callNode("getPrimaryAccount", { feature: feature }); },
|
|
232
|
+
listQueuedOutgoing: function() { return callNode("listQueuedOutgoing"); },
|
|
233
|
+
cancelQueuedOutgoing: function(p) { return callNode("cancelQueuedOutgoing", { path: p }); },
|
|
234
|
+
reauthenticate: function(accountId) { return callNode("reauthenticate", { accountId: accountId }); },
|
|
235
|
+
reauthGoogleScopes: function() { return callNode("reauthGoogleScopes"); },
|
|
236
|
+
hasCcHistoryTo: function(email) { return callNode("hasCcHistoryTo", { email: email }); },
|
|
237
|
+
|
|
238
|
+
// Bulk operations
|
|
239
|
+
deleteMessages: function(accountId, uids) {
|
|
240
|
+
return callNode("deleteMessages", { accountId: accountId, uids: uids });
|
|
241
|
+
},
|
|
242
|
+
moveMessage: function(accountId, uid, targetFolderId, targetAccountId) {
|
|
243
|
+
return callNode("moveMessage", { accountId: accountId, uid: uid, targetFolderId: targetFolderId, targetAccountId: targetAccountId });
|
|
244
|
+
},
|
|
245
|
+
moveMessages: function(accountId, uids, targetFolderId) {
|
|
246
|
+
return callNode("moveMessages", { accountId: accountId, uids: uids, targetFolderId: targetFolderId });
|
|
247
|
+
},
|
|
248
|
+
markAsSpamMessages: function(accountId, uids) {
|
|
249
|
+
return callNode("markAsSpamMessages", { accountId: accountId, uids: uids });
|
|
250
|
+
},
|
|
251
|
+
performUpdate: function() {
|
|
252
|
+
return callNode("performUpdate", {});
|
|
253
|
+
},
|
|
254
|
+
markFolderRead: function(accountId, folderId) {
|
|
255
|
+
return callNode("markFolderRead", { folderId: folderId });
|
|
256
|
+
},
|
|
257
|
+
createFolder: function(accountId, parentPath, name) {
|
|
258
|
+
return callNode("createFolder", { accountId: accountId, parentPath: parentPath, name: name });
|
|
259
|
+
},
|
|
260
|
+
renameFolder: function(accountId, folderId, newName) {
|
|
261
|
+
return callNode("renameFolder", { accountId: accountId, folderId: folderId, newName: newName });
|
|
262
|
+
},
|
|
263
|
+
deleteFolder: function(accountId, folderId) {
|
|
264
|
+
return callNode("deleteFolder", { accountId: accountId, folderId: folderId });
|
|
265
|
+
},
|
|
266
|
+
emptyFolder: function(accountId, folderId) {
|
|
267
|
+
return callNode("emptyFolder", { accountId: accountId, folderId: folderId });
|
|
268
|
+
},
|
|
269
|
+
|
|
270
|
+
// Settings
|
|
271
|
+
allowRemoteContent: function(type, value) {
|
|
272
|
+
return callNode("allowRemoteContent", { type: type, value: value });
|
|
273
|
+
},
|
|
274
|
+
flagSenderOrDomain: function(type, value) {
|
|
275
|
+
return callNode("flagSenderOrDomain", { type: type, value: value });
|
|
276
|
+
},
|
|
277
|
+
getSettings: function() { return callNode("getSettings"); },
|
|
278
|
+
saveSettingsData: function(data) { return callNode("saveSettingsData", data); },
|
|
279
|
+
getVersion: function() { return callNode("getVersion"); },
|
|
280
|
+
getAutocompleteSettings: function() { return callNode("getAutocompleteSettings"); },
|
|
281
|
+
saveAutocompleteSettings: function(settings) { return callNode("saveAutocompleteSettings", settings); },
|
|
282
|
+
|
|
283
|
+
// Attachments
|
|
284
|
+
getAttachment: function(accountId, uid, attachmentId, folderId) {
|
|
285
|
+
return callNode("getAttachment", { accountId: accountId, uid: uid, attachmentId: attachmentId, folderId: folderId });
|
|
286
|
+
},
|
|
287
|
+
|
|
288
|
+
// Setup & Repair
|
|
289
|
+
setupAccount: function(name, email, password) {
|
|
290
|
+
return callNode("setupAccount", { name: name, email: email, password: password });
|
|
291
|
+
},
|
|
292
|
+
repairAccounts: function() { return callNode("repairAccounts"); },
|
|
293
|
+
|
|
294
|
+
// Window geometry
|
|
295
|
+
saveWindowGeometry: function(geom) {
|
|
296
|
+
return callNode("saveWindowGeometry", geom);
|
|
297
|
+
},
|
|
298
|
+
|
|
299
|
+
// Events
|
|
300
|
+
onEvent: function(handler) { _eventHandlers.push(handler); },
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
window.dispatchEvent(new CustomEvent("mailxapiready"));
|
|
304
|
+
})();
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared message state — single source of truth for the message list and viewer.
|
|
3
|
+
*
|
|
4
|
+
* The message list and viewer both reference message objects from this store.
|
|
5
|
+
* Operations (move, delete, sync) mutate the store; UI components subscribe
|
|
6
|
+
* and update themselves. No manual DOM cleanup, no custom events.
|
|
7
|
+
*/
|
|
8
|
+
/** The currently loaded messages (what the list shows) */
|
|
9
|
+
let messages = [];
|
|
10
|
+
/** The currently selected/viewed message (same ref as an entry in messages[]) */
|
|
11
|
+
let selected = null;
|
|
12
|
+
const listeners = [];
|
|
13
|
+
/** Subscribe to state changes. Returns unsubscribe function. */
|
|
14
|
+
export function subscribe(fn) {
|
|
15
|
+
listeners.push(fn);
|
|
16
|
+
return () => {
|
|
17
|
+
const i = listeners.indexOf(fn);
|
|
18
|
+
if (i >= 0)
|
|
19
|
+
listeners.splice(i, 1);
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
function notify(change) {
|
|
23
|
+
for (const fn of listeners) {
|
|
24
|
+
try {
|
|
25
|
+
fn(change);
|
|
26
|
+
}
|
|
27
|
+
catch { /* don't let one subscriber break others */ }
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/** Replace the entire message list (folder load, search, unified inbox).
|
|
31
|
+
* Selection stays even when the previously-selected message isn't in
|
|
32
|
+
* the new list — that case happens whenever a periodic sync re-fetches
|
|
33
|
+
* page 1 of the unified inbox and a row the user opened earlier has
|
|
34
|
+
* scrolled off, or when the same row's accountId/uid pair changes
|
|
35
|
+
* shape after dedup. Yanking the viewer mid-read was a user-reported
|
|
36
|
+
* surprise (2026-04-30: "I was reading a message but now it just
|
|
37
|
+
* jumped to Select a message to read"). The viewer keeps the message
|
|
38
|
+
* it has; it'll only clear when the user explicitly clicks elsewhere
|
|
39
|
+
* or the message is removed via removeMessages (real delete/move). */
|
|
40
|
+
export function setMessages(msgs) {
|
|
41
|
+
messages = msgs;
|
|
42
|
+
notify("messages");
|
|
43
|
+
}
|
|
44
|
+
/** Get current messages */
|
|
45
|
+
export function getMessages() {
|
|
46
|
+
return messages;
|
|
47
|
+
}
|
|
48
|
+
/** Select a message (by reference or null to deselect) */
|
|
49
|
+
export function select(msg) {
|
|
50
|
+
selected = msg;
|
|
51
|
+
notify("selected");
|
|
52
|
+
}
|
|
53
|
+
/** Get the selected message */
|
|
54
|
+
export function getSelected() {
|
|
55
|
+
return selected;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Remove messages from the list (after move or delete).
|
|
59
|
+
* If the selected message is removed, auto-selects the next message or null.
|
|
60
|
+
* Decrements dupeCount on any remaining messages that shared a Message-ID
|
|
61
|
+
* with one of the removed rows — so the unified-inbox ⇆ marker disappears
|
|
62
|
+
* when one half of a duplicate pair is deleted, instead of pointing at a
|
|
63
|
+
* sibling that no longer exists.
|
|
64
|
+
*/
|
|
65
|
+
export function removeMessages(uids) {
|
|
66
|
+
const removeSet = new Set(uids.map(u => `${u.accountId}:${u.uid}`));
|
|
67
|
+
const wasSelectedRemoved = selected && removeSet.has(`${selected.accountId}:${selected.uid}`);
|
|
68
|
+
// Identity-based pre-capture of the row that should become selected
|
|
69
|
+
// after the batch. Walk forward from the last removed row to the first
|
|
70
|
+
// surviving row — that's the message the user would have navigated to
|
|
71
|
+
// next. Capturing by reference (not index) so the post-filter shift
|
|
72
|
+
// doesn't matter; if every deleted row had a survivor below it, the
|
|
73
|
+
// chosen survivor is the same regardless of how many got deleted.
|
|
74
|
+
// Falls back to walking backward if the deletion is at the bottom.
|
|
75
|
+
let nextAfterRemoval = null;
|
|
76
|
+
if (wasSelectedRemoved) {
|
|
77
|
+
let lastRemovedIdx = -1;
|
|
78
|
+
for (let i = 0; i < messages.length; i++) {
|
|
79
|
+
if (removeSet.has(`${messages[i].accountId}:${messages[i].uid}`))
|
|
80
|
+
lastRemovedIdx = i;
|
|
81
|
+
}
|
|
82
|
+
for (let i = lastRemovedIdx + 1; i < messages.length; i++) {
|
|
83
|
+
if (!removeSet.has(`${messages[i].accountId}:${messages[i].uid}`)) {
|
|
84
|
+
nextAfterRemoval = messages[i];
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if (!nextAfterRemoval) {
|
|
89
|
+
// No survivor below — fall back to the closest survivor above.
|
|
90
|
+
for (let i = lastRemovedIdx - 1; i >= 0; i--) {
|
|
91
|
+
if (!removeSet.has(`${messages[i].accountId}:${messages[i].uid}`)) {
|
|
92
|
+
nextAfterRemoval = messages[i];
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
// Capture Message-IDs of the rows about to leave, so we can fix up the
|
|
99
|
+
// dupeCount on any remaining siblings. Only IDs that were in the list
|
|
100
|
+
// and had a non-empty messageId count — empty IDs would match every
|
|
101
|
+
// headerless row and falsely "merge" them.
|
|
102
|
+
const removedIds = new Set();
|
|
103
|
+
for (const m of messages) {
|
|
104
|
+
if (removeSet.has(`${m.accountId}:${m.uid}`) && m.messageId) {
|
|
105
|
+
removedIds.add(m.messageId);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
messages = messages.filter(m => !removeSet.has(`${m.accountId}:${m.uid}`));
|
|
109
|
+
// Sibling dupeCount adjustment. dupeCount is server-recomputed on the
|
|
110
|
+
// next getUnifiedInbox fetch, but we don't want to wait for that —
|
|
111
|
+
// user just deleted, the marker should drop now. Decrement once per
|
|
112
|
+
// remaining message whose messageId matches a removed one.
|
|
113
|
+
if (removedIds.size > 0) {
|
|
114
|
+
for (const m of messages) {
|
|
115
|
+
if (m.messageId && removedIds.has(m.messageId) && typeof m.dupeCount === "number") {
|
|
116
|
+
m.dupeCount = Math.max(0, m.dupeCount - 1);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
if (wasSelectedRemoved) {
|
|
121
|
+
selected = nextAfterRemoval;
|
|
122
|
+
}
|
|
123
|
+
notify("removed");
|
|
124
|
+
}
|
|
125
|
+
/** Update flags on a message in the list */
|
|
126
|
+
export function updateMessageFlags(accountId, uid, flags) {
|
|
127
|
+
const msg = messages.find(m => m.uid === uid && m.accountId === accountId);
|
|
128
|
+
if (msg)
|
|
129
|
+
msg.flags = flags;
|
|
130
|
+
// No notify — flag changes are cosmetic, handled inline by the list
|
|
131
|
+
}
|
|
132
|
+
//# sourceMappingURL=message-state.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"message-state.js","sourceRoot":"","sources":["message-state.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared message state — single source of truth for the message list and viewer.
|
|
3
|
+
*
|
|
4
|
+
* The message list and viewer both reference message objects from this store.
|
|
5
|
+
* Operations (move, delete, sync) mutate the store; UI components subscribe
|
|
6
|
+
* and update themselves. No manual DOM cleanup, no custom events.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export interface ListMessage {
|
|
10
|
+
accountId: string;
|
|
11
|
+
uid: number;
|
|
12
|
+
folderId: number;
|
|
13
|
+
subject: string;
|
|
14
|
+
from: { name: string; address: string };
|
|
15
|
+
to: { name: string; address: string }[];
|
|
16
|
+
cc: { name: string; address: string }[];
|
|
17
|
+
date: number;
|
|
18
|
+
flags: string[];
|
|
19
|
+
size: number;
|
|
20
|
+
preview: string;
|
|
21
|
+
hasAttachments: boolean;
|
|
22
|
+
[key: string]: any;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
type ChangeType = "messages" | "selected" | "removed";
|
|
26
|
+
type Listener = (change: ChangeType) => void;
|
|
27
|
+
|
|
28
|
+
/** The currently loaded messages (what the list shows) */
|
|
29
|
+
let messages: ListMessage[] = [];
|
|
30
|
+
|
|
31
|
+
/** The currently selected/viewed message (same ref as an entry in messages[]) */
|
|
32
|
+
let selected: ListMessage | null = null;
|
|
33
|
+
|
|
34
|
+
const listeners: Listener[] = [];
|
|
35
|
+
|
|
36
|
+
/** Subscribe to state changes. Returns unsubscribe function. */
|
|
37
|
+
export function subscribe(fn: Listener): () => void {
|
|
38
|
+
listeners.push(fn);
|
|
39
|
+
return () => {
|
|
40
|
+
const i = listeners.indexOf(fn);
|
|
41
|
+
if (i >= 0) listeners.splice(i, 1);
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function notify(change: ChangeType): void {
|
|
46
|
+
for (const fn of listeners) {
|
|
47
|
+
try { fn(change); } catch { /* don't let one subscriber break others */ }
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Replace the entire message list (folder load, search, unified inbox).
|
|
52
|
+
* Selection stays even when the previously-selected message isn't in
|
|
53
|
+
* the new list — that case happens whenever a periodic sync re-fetches
|
|
54
|
+
* page 1 of the unified inbox and a row the user opened earlier has
|
|
55
|
+
* scrolled off, or when the same row's accountId/uid pair changes
|
|
56
|
+
* shape after dedup. Yanking the viewer mid-read was a user-reported
|
|
57
|
+
* surprise (2026-04-30: "I was reading a message but now it just
|
|
58
|
+
* jumped to Select a message to read"). The viewer keeps the message
|
|
59
|
+
* it has; it'll only clear when the user explicitly clicks elsewhere
|
|
60
|
+
* or the message is removed via removeMessages (real delete/move). */
|
|
61
|
+
export function setMessages(msgs: ListMessage[]): void {
|
|
62
|
+
messages = msgs;
|
|
63
|
+
notify("messages");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Get current messages */
|
|
67
|
+
export function getMessages(): ListMessage[] {
|
|
68
|
+
return messages;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Select a message (by reference or null to deselect) */
|
|
72
|
+
export function select(msg: ListMessage | null): void {
|
|
73
|
+
selected = msg;
|
|
74
|
+
notify("selected");
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Get the selected message */
|
|
78
|
+
export function getSelected(): ListMessage | null {
|
|
79
|
+
return selected;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Remove messages from the list (after move or delete).
|
|
84
|
+
* If the selected message is removed, auto-selects the next message or null.
|
|
85
|
+
* Decrements dupeCount on any remaining messages that shared a Message-ID
|
|
86
|
+
* with one of the removed rows — so the unified-inbox ⇆ marker disappears
|
|
87
|
+
* when one half of a duplicate pair is deleted, instead of pointing at a
|
|
88
|
+
* sibling that no longer exists.
|
|
89
|
+
*/
|
|
90
|
+
export function removeMessages(uids: { accountId: string; uid: number }[]): void {
|
|
91
|
+
const removeSet = new Set(uids.map(u => `${u.accountId}:${u.uid}`));
|
|
92
|
+
const wasSelectedRemoved = selected && removeSet.has(`${selected.accountId}:${selected.uid}`);
|
|
93
|
+
|
|
94
|
+
// Identity-based pre-capture of the row that should become selected
|
|
95
|
+
// after the batch. Walk forward from the last removed row to the first
|
|
96
|
+
// surviving row — that's the message the user would have navigated to
|
|
97
|
+
// next. Capturing by reference (not index) so the post-filter shift
|
|
98
|
+
// doesn't matter; if every deleted row had a survivor below it, the
|
|
99
|
+
// chosen survivor is the same regardless of how many got deleted.
|
|
100
|
+
// Falls back to walking backward if the deletion is at the bottom.
|
|
101
|
+
let nextAfterRemoval: ListMessage | null = null;
|
|
102
|
+
if (wasSelectedRemoved) {
|
|
103
|
+
let lastRemovedIdx = -1;
|
|
104
|
+
for (let i = 0; i < messages.length; i++) {
|
|
105
|
+
if (removeSet.has(`${messages[i].accountId}:${messages[i].uid}`)) lastRemovedIdx = i;
|
|
106
|
+
}
|
|
107
|
+
for (let i = lastRemovedIdx + 1; i < messages.length; i++) {
|
|
108
|
+
if (!removeSet.has(`${messages[i].accountId}:${messages[i].uid}`)) {
|
|
109
|
+
nextAfterRemoval = messages[i];
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (!nextAfterRemoval) {
|
|
114
|
+
// No survivor below — fall back to the closest survivor above.
|
|
115
|
+
for (let i = lastRemovedIdx - 1; i >= 0; i--) {
|
|
116
|
+
if (!removeSet.has(`${messages[i].accountId}:${messages[i].uid}`)) {
|
|
117
|
+
nextAfterRemoval = messages[i];
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Capture Message-IDs of the rows about to leave, so we can fix up the
|
|
125
|
+
// dupeCount on any remaining siblings. Only IDs that were in the list
|
|
126
|
+
// and had a non-empty messageId count — empty IDs would match every
|
|
127
|
+
// headerless row and falsely "merge" them.
|
|
128
|
+
const removedIds = new Set<string>();
|
|
129
|
+
for (const m of messages) {
|
|
130
|
+
if (removeSet.has(`${m.accountId}:${m.uid}`) && m.messageId) {
|
|
131
|
+
removedIds.add(m.messageId);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
messages = messages.filter(m => !removeSet.has(`${m.accountId}:${m.uid}`));
|
|
136
|
+
|
|
137
|
+
// Sibling dupeCount adjustment. dupeCount is server-recomputed on the
|
|
138
|
+
// next getUnifiedInbox fetch, but we don't want to wait for that —
|
|
139
|
+
// user just deleted, the marker should drop now. Decrement once per
|
|
140
|
+
// remaining message whose messageId matches a removed one.
|
|
141
|
+
if (removedIds.size > 0) {
|
|
142
|
+
for (const m of messages) {
|
|
143
|
+
if (m.messageId && removedIds.has(m.messageId) && typeof (m as any).dupeCount === "number") {
|
|
144
|
+
(m as any).dupeCount = Math.max(0, (m as any).dupeCount - 1);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (wasSelectedRemoved) {
|
|
150
|
+
selected = nextAfterRemoval;
|
|
151
|
+
}
|
|
152
|
+
notify("removed");
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** Update flags on a message in the list */
|
|
156
|
+
export function updateMessageFlags(accountId: string, uid: number, flags: string[]): void {
|
|
157
|
+
const msg = messages.find(m => m.uid === uid && m.accountId === accountId);
|
|
158
|
+
if (msg) msg.flags = flags;
|
|
159
|
+
// No notify — flag changes are cosmetic, handled inline by the list
|
|
160
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@bobfrankston/mailx-client",
|
|
3
|
+
"version": "1.0.16",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"build": "tsc"
|
|
8
|
+
},
|
|
9
|
+
"dependencies": {
|
|
10
|
+
"quill": "^2.0.0"
|
|
11
|
+
},
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "https://github.com/BobFrankston/mailx-client.git"
|
|
15
|
+
}
|
|
16
|
+
}
|