@bobfrankston/mailx 1.0.12 → 1.0.13
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/bin/mailx.js +47 -29
- package/client/app.js +93 -13
- package/client/components/folder-tree.js +84 -3
- package/client/components/message-list.js +134 -8
- package/client/components/message-viewer.js +130 -13
- package/client/compose/compose.html +4 -4
- package/client/compose/compose.js +53 -34
- package/client/index.html +33 -9
- package/client/lib/api-client.js +102 -30
- package/client/lib/mailxapi.js +123 -0
- package/client/package.json +1 -1
- package/client/styles/components.css +188 -15
- package/client/styles/layout.css +2 -1
- package/killmail.cmd +6 -0
- package/launch.ps1 +47 -5
- package/launcher/bin/mailx-app.exe +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Breadcrumbs +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Crashpad/metadata +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Crashpad/settings.dat +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Crashpad/throttle_store.dat +1 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/CrashpadMetrics-active.pma +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/BrowsingTopicsSiteData +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Cache/No_Vary_Search/journal.baj +1 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/DIPS +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/DashTrackerDatabase +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/EdgeJourneys/EdgeJourneys.db +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Extension Rules/LOCK +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Extension Rules/LOG +3 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Extension Rules/MANIFEST-000001 +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Extension Scripts/LOCK +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Extension Scripts/LOG +3 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Extension Scripts/MANIFEST-000001 +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Extension State/LOCK +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Extension State/LOG +3 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Extension State/MANIFEST-000001 +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/ExtensionActivityComp +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/ExtensionActivityEdge +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Favicons +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/History +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/History-journal +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/IndexedDB/devtools_devtools_0.indexeddb.leveldb/LOCK +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/IndexedDB/devtools_devtools_0.indexeddb.leveldb/LOG +3 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/IndexedDB/devtools_devtools_0.indexeddb.leveldb/MANIFEST-000001 +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Local Storage/leveldb/LOCK +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Local Storage/leveldb/LOG +3 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Local Storage/leveldb/MANIFEST-000001 +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Login Data +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Login Data For Account +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Network/Cookies +0 -0
- NEL +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Network/Trust Tokens +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Network Action Predictor +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Preferences +1 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Safe Browsing Network/Safe Browsing Cookies +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/ServerCertificate +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Session Storage/LOCK +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Session Storage/LOG +3 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Session Storage/MANIFEST-000001 +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Shared Dictionary/db +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/SharedStorage +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Shortcuts +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Site Characteristics Database/LOCK +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Site Characteristics Database/LOG +3 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Site Characteristics Database/MANIFEST-000001 +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Sync Data/LevelDB/LOCK +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Sync Data/LevelDB/LOG +3 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Sync Data/LevelDB/MANIFEST-000001 +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Top Sites +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Vpn Tokens +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Web Data +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Web Data-journal +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/WebStorage/QuotaManager +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/WebStorage/QuotaManager-journal +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/heavy_ad_intervention_opt_out.db +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/shared_proto_db/LOCK +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/shared_proto_db/LOG +3 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/shared_proto_db/MANIFEST-000001 +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/shared_proto_db/metadata/LOCK +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/shared_proto_db/metadata/LOG +3 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/shared_proto_db/metadata/MANIFEST-000001 +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/DeferredBrowserMetrics/BrowserMetrics-69CAD063-BE24.pma +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Local State +1 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Variations +1 -0
- package/launcher/bin/mailx-app.old.exe +0 -0
- package/package.json +1 -1
- package/packages/mailx-api/index.js +79 -26
- package/packages/mailx-core/index.d.ts +129 -0
- package/packages/mailx-core/index.js +323 -0
- package/packages/mailx-core/ipc.d.ts +13 -0
- package/packages/mailx-core/ipc.js +56 -0
- package/packages/mailx-core/package.json +18 -0
- package/packages/mailx-imap/index.d.ts +5 -1
- package/packages/mailx-imap/index.js +76 -14
- package/packages/mailx-server/index.js +42 -31
- package/packages/mailx-server/package.json +1 -2
- package/packages/mailx-settings/index.d.ts +1 -1
- package/packages/mailx-settings/index.js +21 -12
- package/packages/mailx-store/db.d.ts +5 -1
- package/packages/mailx-store/db.js +64 -12
- package/packages/mailx-store/file-store.d.ts +2 -8
- package/packages/mailx-store/file-store.js +7 -31
- package/packages/mailx-types/index.d.ts +3 -1
- package/.tswalk.json +0 -7396
- package/launcher/release.cmd +0 -4
- package/mailx.json +0 -9
- package/packages/mailx-api/node_modules/nodemailer/.ncurc.js +0 -9
- package/packages/mailx-api/node_modules/nodemailer/.prettierignore +0 -8
- package/packages/mailx-api/node_modules/nodemailer/.prettierrc +0 -12
- package/packages/mailx-api/node_modules/nodemailer/.prettierrc.js +0 -10
- package/packages/mailx-api/node_modules/nodemailer/.release-please-config.json +0 -9
- package/packages/mailx-api/node_modules/nodemailer/LICENSE +0 -16
- package/packages/mailx-api/node_modules/nodemailer/README.md +0 -86
- package/packages/mailx-api/node_modules/nodemailer/SECURITY.txt +0 -22
- package/packages/mailx-api/node_modules/nodemailer/eslint.config.js +0 -88
- package/packages/mailx-api/node_modules/nodemailer/lib/addressparser/index.js +0 -383
- package/packages/mailx-api/node_modules/nodemailer/lib/base64/index.js +0 -139
- package/packages/mailx-api/node_modules/nodemailer/lib/dkim/index.js +0 -253
- package/packages/mailx-api/node_modules/nodemailer/lib/dkim/message-parser.js +0 -155
- package/packages/mailx-api/node_modules/nodemailer/lib/dkim/relaxed-body.js +0 -154
- package/packages/mailx-api/node_modules/nodemailer/lib/dkim/sign.js +0 -117
- package/packages/mailx-api/node_modules/nodemailer/lib/fetch/cookies.js +0 -281
- package/packages/mailx-api/node_modules/nodemailer/lib/fetch/index.js +0 -280
- package/packages/mailx-api/node_modules/nodemailer/lib/json-transport/index.js +0 -82
- package/packages/mailx-api/node_modules/nodemailer/lib/mail-composer/index.js +0 -629
- package/packages/mailx-api/node_modules/nodemailer/lib/mailer/index.js +0 -441
- package/packages/mailx-api/node_modules/nodemailer/lib/mailer/mail-message.js +0 -316
- package/packages/mailx-api/node_modules/nodemailer/lib/mime-funcs/index.js +0 -625
- package/packages/mailx-api/node_modules/nodemailer/lib/mime-funcs/mime-types.js +0 -2113
- package/packages/mailx-api/node_modules/nodemailer/lib/mime-node/index.js +0 -1316
- package/packages/mailx-api/node_modules/nodemailer/lib/mime-node/last-newline.js +0 -33
- package/packages/mailx-api/node_modules/nodemailer/lib/mime-node/le-unix.js +0 -43
- package/packages/mailx-api/node_modules/nodemailer/lib/mime-node/le-windows.js +0 -52
- package/packages/mailx-api/node_modules/nodemailer/lib/nodemailer.js +0 -157
- package/packages/mailx-api/node_modules/nodemailer/lib/punycode/index.js +0 -460
- package/packages/mailx-api/node_modules/nodemailer/lib/qp/index.js +0 -227
- package/packages/mailx-api/node_modules/nodemailer/lib/sendmail-transport/index.js +0 -210
- package/packages/mailx-api/node_modules/nodemailer/lib/ses-transport/index.js +0 -234
- package/packages/mailx-api/node_modules/nodemailer/lib/shared/index.js +0 -754
- package/packages/mailx-api/node_modules/nodemailer/lib/smtp-connection/data-stream.js +0 -108
- package/packages/mailx-api/node_modules/nodemailer/lib/smtp-connection/http-proxy-client.js +0 -143
- package/packages/mailx-api/node_modules/nodemailer/lib/smtp-connection/index.js +0 -1870
- package/packages/mailx-api/node_modules/nodemailer/lib/smtp-pool/index.js +0 -652
- package/packages/mailx-api/node_modules/nodemailer/lib/smtp-pool/pool-resource.js +0 -259
- package/packages/mailx-api/node_modules/nodemailer/lib/smtp-transport/index.js +0 -421
- package/packages/mailx-api/node_modules/nodemailer/lib/stream-transport/index.js +0 -135
- package/packages/mailx-api/node_modules/nodemailer/lib/well-known/index.js +0 -47
- package/packages/mailx-api/node_modules/nodemailer/lib/well-known/services.json +0 -611
- package/packages/mailx-api/node_modules/nodemailer/lib/xoauth2/index.js +0 -427
- package/packages/mailx-api/node_modules/nodemailer/package.json +0 -47
- package/packages/mailx-imap/node_modules/nodemailer/.ncurc.js +0 -9
- package/packages/mailx-imap/node_modules/nodemailer/.prettierignore +0 -8
- package/packages/mailx-imap/node_modules/nodemailer/.prettierrc +0 -12
- package/packages/mailx-imap/node_modules/nodemailer/.prettierrc.js +0 -10
- package/packages/mailx-imap/node_modules/nodemailer/.release-please-config.json +0 -9
- package/packages/mailx-imap/node_modules/nodemailer/LICENSE +0 -16
- package/packages/mailx-imap/node_modules/nodemailer/README.md +0 -86
- package/packages/mailx-imap/node_modules/nodemailer/SECURITY.txt +0 -22
- package/packages/mailx-imap/node_modules/nodemailer/eslint.config.js +0 -88
- package/packages/mailx-imap/node_modules/nodemailer/lib/addressparser/index.js +0 -383
- package/packages/mailx-imap/node_modules/nodemailer/lib/base64/index.js +0 -139
- package/packages/mailx-imap/node_modules/nodemailer/lib/dkim/index.js +0 -253
- package/packages/mailx-imap/node_modules/nodemailer/lib/dkim/message-parser.js +0 -155
- package/packages/mailx-imap/node_modules/nodemailer/lib/dkim/relaxed-body.js +0 -154
- package/packages/mailx-imap/node_modules/nodemailer/lib/dkim/sign.js +0 -117
- package/packages/mailx-imap/node_modules/nodemailer/lib/fetch/cookies.js +0 -281
- package/packages/mailx-imap/node_modules/nodemailer/lib/fetch/index.js +0 -280
- package/packages/mailx-imap/node_modules/nodemailer/lib/json-transport/index.js +0 -82
- package/packages/mailx-imap/node_modules/nodemailer/lib/mail-composer/index.js +0 -629
- package/packages/mailx-imap/node_modules/nodemailer/lib/mailer/index.js +0 -441
- package/packages/mailx-imap/node_modules/nodemailer/lib/mailer/mail-message.js +0 -316
- package/packages/mailx-imap/node_modules/nodemailer/lib/mime-funcs/index.js +0 -625
- package/packages/mailx-imap/node_modules/nodemailer/lib/mime-funcs/mime-types.js +0 -2113
- package/packages/mailx-imap/node_modules/nodemailer/lib/mime-node/index.js +0 -1316
- package/packages/mailx-imap/node_modules/nodemailer/lib/mime-node/last-newline.js +0 -33
- package/packages/mailx-imap/node_modules/nodemailer/lib/mime-node/le-unix.js +0 -43
- package/packages/mailx-imap/node_modules/nodemailer/lib/mime-node/le-windows.js +0 -52
- package/packages/mailx-imap/node_modules/nodemailer/lib/nodemailer.js +0 -157
- package/packages/mailx-imap/node_modules/nodemailer/lib/punycode/index.js +0 -460
- package/packages/mailx-imap/node_modules/nodemailer/lib/qp/index.js +0 -227
- package/packages/mailx-imap/node_modules/nodemailer/lib/sendmail-transport/index.js +0 -210
- package/packages/mailx-imap/node_modules/nodemailer/lib/ses-transport/index.js +0 -234
- package/packages/mailx-imap/node_modules/nodemailer/lib/shared/index.js +0 -754
- package/packages/mailx-imap/node_modules/nodemailer/lib/smtp-connection/data-stream.js +0 -108
- package/packages/mailx-imap/node_modules/nodemailer/lib/smtp-connection/http-proxy-client.js +0 -143
- package/packages/mailx-imap/node_modules/nodemailer/lib/smtp-connection/index.js +0 -1870
- package/packages/mailx-imap/node_modules/nodemailer/lib/smtp-pool/index.js +0 -652
- package/packages/mailx-imap/node_modules/nodemailer/lib/smtp-pool/pool-resource.js +0 -259
- package/packages/mailx-imap/node_modules/nodemailer/lib/smtp-transport/index.js +0 -421
- package/packages/mailx-imap/node_modules/nodemailer/lib/stream-transport/index.js +0 -135
- package/packages/mailx-imap/node_modules/nodemailer/lib/well-known/index.js +0 -47
- package/packages/mailx-imap/node_modules/nodemailer/lib/well-known/services.json +0 -611
- package/packages/mailx-imap/node_modules/nodemailer/lib/xoauth2/index.js +0 -427
- package/packages/mailx-imap/node_modules/nodemailer/package.json +0 -47
- package/packages/mailx-send/node_modules/nodemailer/.ncurc.js +0 -9
- package/packages/mailx-send/node_modules/nodemailer/.prettierignore +0 -8
- package/packages/mailx-send/node_modules/nodemailer/.prettierrc +0 -12
- package/packages/mailx-send/node_modules/nodemailer/.prettierrc.js +0 -10
- package/packages/mailx-send/node_modules/nodemailer/.release-please-config.json +0 -9
- package/packages/mailx-send/node_modules/nodemailer/LICENSE +0 -16
- package/packages/mailx-send/node_modules/nodemailer/README.md +0 -86
- package/packages/mailx-send/node_modules/nodemailer/SECURITY.txt +0 -22
- package/packages/mailx-send/node_modules/nodemailer/eslint.config.js +0 -88
- package/packages/mailx-send/node_modules/nodemailer/lib/addressparser/index.js +0 -383
- package/packages/mailx-send/node_modules/nodemailer/lib/base64/index.js +0 -139
- package/packages/mailx-send/node_modules/nodemailer/lib/dkim/index.js +0 -253
- package/packages/mailx-send/node_modules/nodemailer/lib/dkim/message-parser.js +0 -155
- package/packages/mailx-send/node_modules/nodemailer/lib/dkim/relaxed-body.js +0 -154
- package/packages/mailx-send/node_modules/nodemailer/lib/dkim/sign.js +0 -117
- package/packages/mailx-send/node_modules/nodemailer/lib/fetch/cookies.js +0 -281
- package/packages/mailx-send/node_modules/nodemailer/lib/fetch/index.js +0 -280
- package/packages/mailx-send/node_modules/nodemailer/lib/json-transport/index.js +0 -82
- package/packages/mailx-send/node_modules/nodemailer/lib/mail-composer/index.js +0 -629
- package/packages/mailx-send/node_modules/nodemailer/lib/mailer/index.js +0 -441
- package/packages/mailx-send/node_modules/nodemailer/lib/mailer/mail-message.js +0 -316
- package/packages/mailx-send/node_modules/nodemailer/lib/mime-funcs/index.js +0 -625
- package/packages/mailx-send/node_modules/nodemailer/lib/mime-funcs/mime-types.js +0 -2113
- package/packages/mailx-send/node_modules/nodemailer/lib/mime-node/index.js +0 -1316
- package/packages/mailx-send/node_modules/nodemailer/lib/mime-node/last-newline.js +0 -33
- package/packages/mailx-send/node_modules/nodemailer/lib/mime-node/le-unix.js +0 -43
- package/packages/mailx-send/node_modules/nodemailer/lib/mime-node/le-windows.js +0 -52
- package/packages/mailx-send/node_modules/nodemailer/lib/nodemailer.js +0 -157
- package/packages/mailx-send/node_modules/nodemailer/lib/punycode/index.js +0 -460
- package/packages/mailx-send/node_modules/nodemailer/lib/qp/index.js +0 -227
- package/packages/mailx-send/node_modules/nodemailer/lib/sendmail-transport/index.js +0 -210
- package/packages/mailx-send/node_modules/nodemailer/lib/ses-transport/index.js +0 -234
- package/packages/mailx-send/node_modules/nodemailer/lib/shared/index.js +0 -754
- package/packages/mailx-send/node_modules/nodemailer/lib/smtp-connection/data-stream.js +0 -108
- package/packages/mailx-send/node_modules/nodemailer/lib/smtp-connection/http-proxy-client.js +0 -143
- package/packages/mailx-send/node_modules/nodemailer/lib/smtp-connection/index.js +0 -1870
- package/packages/mailx-send/node_modules/nodemailer/lib/smtp-pool/index.js +0 -652
- package/packages/mailx-send/node_modules/nodemailer/lib/smtp-pool/pool-resource.js +0 -259
- package/packages/mailx-send/node_modules/nodemailer/lib/smtp-transport/index.js +0 -421
- package/packages/mailx-send/node_modules/nodemailer/lib/stream-transport/index.js +0 -135
- package/packages/mailx-send/node_modules/nodemailer/lib/well-known/index.js +0 -47
- package/packages/mailx-send/node_modules/nodemailer/lib/well-known/services.json +0 -611
- package/packages/mailx-send/node_modules/nodemailer/lib/xoauth2/index.js +0 -427
- package/packages/mailx-send/node_modules/nodemailer/package.json +0 -47
|
@@ -5,12 +5,14 @@ import { getMessage, updateFlags } from "../lib/api-client.js";
|
|
|
5
5
|
/** Currently displayed message (for reply/forward) */
|
|
6
6
|
let currentMessage = null;
|
|
7
7
|
let currentAccountId = "";
|
|
8
|
+
let showMessageGeneration = 0; // Cancel stale fetches
|
|
8
9
|
export function getCurrentMessage() {
|
|
9
10
|
if (!currentMessage)
|
|
10
11
|
return null;
|
|
11
12
|
return { accountId: currentAccountId, message: currentMessage };
|
|
12
13
|
}
|
|
13
|
-
export async function showMessage(accountId, uid) {
|
|
14
|
+
export async function showMessage(accountId, uid, folderId, specialUse) {
|
|
15
|
+
const gen = ++showMessageGeneration;
|
|
14
16
|
const headerEl = document.getElementById("mv-header");
|
|
15
17
|
const bodyEl = document.getElementById("mv-body");
|
|
16
18
|
const attEl = document.getElementById("mv-attachments");
|
|
@@ -18,9 +20,10 @@ export async function showMessage(accountId, uid) {
|
|
|
18
20
|
headerEl.hidden = true;
|
|
19
21
|
attEl.hidden = true;
|
|
20
22
|
try {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
const msg = await getMessage(accountId, uid, false, folderId);
|
|
24
|
+
// Stale response — a newer showMessage was called while we were fetching
|
|
25
|
+
if (gen !== showMessageGeneration)
|
|
26
|
+
return;
|
|
24
27
|
currentMessage = msg;
|
|
25
28
|
currentAccountId = accountId;
|
|
26
29
|
// Mark as read
|
|
@@ -37,18 +40,114 @@ export async function showMessage(accountId, uid) {
|
|
|
37
40
|
}
|
|
38
41
|
headerEl.querySelector(".mv-subject").textContent = msg.subject;
|
|
39
42
|
headerEl.querySelector(".mv-date").textContent = new Date(msg.date).toLocaleString(undefined, { year: "numeric", month: "short", day: "numeric", hour: "2-digit", minute: "2-digit", hour12: false });
|
|
40
|
-
//
|
|
43
|
+
// Unsubscribe button (upper right of header)
|
|
44
|
+
const unsubBtn = document.getElementById("mv-unsubscribe");
|
|
45
|
+
const headerUnsub = msg.listUnsubscribe || "";
|
|
46
|
+
const headerUnsubUrl = headerUnsub.match(/<(https?:\/\/[^>]+)>/)?.[1]
|
|
47
|
+
|| headerUnsub.match(/<(mailto:[^>]+)>/)?.[1] || "";
|
|
48
|
+
if (unsubBtn) {
|
|
49
|
+
if (headerUnsubUrl) {
|
|
50
|
+
unsubBtn.hidden = false;
|
|
51
|
+
unsubBtn.href = headerUnsubUrl;
|
|
52
|
+
unsubBtn.target = "_blank";
|
|
53
|
+
unsubBtn.rel = "noopener noreferrer";
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
unsubBtn.hidden = true;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// View Source button — shows .eml file path
|
|
60
|
+
const srcBtn = document.getElementById("mv-view-source");
|
|
61
|
+
if (srcBtn) {
|
|
62
|
+
if (msg.emlPath) {
|
|
63
|
+
srcBtn.hidden = false;
|
|
64
|
+
srcBtn.title = msg.emlPath;
|
|
65
|
+
srcBtn.onclick = () => {
|
|
66
|
+
// Copy path to clipboard and show in status bar
|
|
67
|
+
navigator.clipboard.writeText(msg.emlPath).then(() => {
|
|
68
|
+
const status = document.getElementById("status-sync");
|
|
69
|
+
if (status)
|
|
70
|
+
status.textContent = `Path copied: ${msg.emlPath}`;
|
|
71
|
+
}).catch(() => {
|
|
72
|
+
prompt("EML file path:", msg.emlPath);
|
|
73
|
+
});
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
srcBtn.hidden = true;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
// Edit Draft / Send from Outbox button
|
|
81
|
+
const editBtn = document.getElementById("mv-edit-draft");
|
|
82
|
+
if (editBtn) {
|
|
83
|
+
const isDraft = specialUse === "drafts" || specialUse === "outbox";
|
|
84
|
+
if (isDraft) {
|
|
85
|
+
editBtn.hidden = false;
|
|
86
|
+
editBtn.textContent = specialUse === "outbox" ? "Edit & Send" : "Edit Draft";
|
|
87
|
+
editBtn.onclick = () => {
|
|
88
|
+
// Open compose window pre-filled with this draft
|
|
89
|
+
const init = {
|
|
90
|
+
mode: "draft",
|
|
91
|
+
accountId,
|
|
92
|
+
to: msg.to || [],
|
|
93
|
+
cc: msg.cc || [],
|
|
94
|
+
subject: msg.subject || "",
|
|
95
|
+
bodyHtml: msg.bodyHtml || "",
|
|
96
|
+
inReplyTo: msg.inReplyTo || "",
|
|
97
|
+
references: msg.references || [],
|
|
98
|
+
accounts: [],
|
|
99
|
+
draftUid: msg.uid,
|
|
100
|
+
draftFolderId: msg.folderId,
|
|
101
|
+
};
|
|
102
|
+
sessionStorage.setItem("composeInit", JSON.stringify(init));
|
|
103
|
+
window.open("/compose/compose.html", "_blank", "width=800,height=600,menubar=no,toolbar=no,status=no");
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
editBtn.hidden = true;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
// Remote content banner (collapsible dropdown with sender/recipient details)
|
|
41
111
|
bodyEl.innerHTML = "";
|
|
42
112
|
if (msg.hasRemoteContent) {
|
|
43
113
|
const senderAddr = msg.from?.address || "";
|
|
114
|
+
const senderName = msg.from?.name || "";
|
|
44
115
|
const senderDomain = senderAddr.split("@")[1] || "";
|
|
116
|
+
const deliveredTo = msg.deliveredTo || "";
|
|
117
|
+
const toAddr = msg.to?.[0]?.address || "";
|
|
118
|
+
const returnPath = msg.returnPath || "";
|
|
45
119
|
const banner = document.createElement("div");
|
|
46
120
|
banner.className = "mv-remote-banner";
|
|
47
|
-
banner.innerHTML =
|
|
48
|
-
`<
|
|
49
|
-
|
|
50
|
-
|
|
121
|
+
banner.innerHTML =
|
|
122
|
+
`<div class="mv-rb-summary">` +
|
|
123
|
+
`<span class="mv-rb-toggle">▸</span>` +
|
|
124
|
+
`<span>Remote content blocked</span>` +
|
|
125
|
+
`<span class="mv-rb-buttons">` +
|
|
126
|
+
`<button id="btn-load-remote">Load once</button>` +
|
|
127
|
+
`<button id="btn-allow-sender" title="${escapeText(senderAddr)}">Always: ${escapeText(senderAddr)}</button>` +
|
|
128
|
+
(senderDomain ? `<button id="btn-allow-domain" title="*@${escapeText(senderDomain)}">Always: *@${escapeText(senderDomain)}</button>` : "") +
|
|
129
|
+
`</span>` +
|
|
130
|
+
`</div>` +
|
|
131
|
+
`<div class="mv-rb-details" hidden>` +
|
|
132
|
+
`<div class="mv-rb-info">` +
|
|
133
|
+
`<div><span class="mv-rb-label">From:</span> ${escapeText(senderName ? `${senderName} <${senderAddr}>` : senderAddr)}</div>` +
|
|
134
|
+
(deliveredTo ? `<div><span class="mv-rb-label">Delivered-To:</span> ${escapeText(deliveredTo)}</div>` : "") +
|
|
135
|
+
(toAddr && toAddr !== deliveredTo ? `<div><span class="mv-rb-label">To:</span> ${escapeText(toAddr)}</div>` : "") +
|
|
136
|
+
(returnPath && returnPath !== senderAddr ? `<div><span class="mv-rb-label">Return-Path:</span> ${escapeText(returnPath)}</div>` : "") +
|
|
137
|
+
`</div>` +
|
|
138
|
+
(deliveredTo || toAddr ? `<div class="mv-rb-actions"><button id="btn-allow-to">Always allow to: ${escapeText(deliveredTo || toAddr)}</button></div>` : "") +
|
|
139
|
+
`</div>`;
|
|
51
140
|
bodyEl.appendChild(banner);
|
|
141
|
+
// Toggle dropdown — click arrow or text to expand details
|
|
142
|
+
const summary = banner.querySelector(".mv-rb-summary");
|
|
143
|
+
const details = banner.querySelector(".mv-rb-details");
|
|
144
|
+
const toggle = banner.querySelector(".mv-rb-toggle");
|
|
145
|
+
summary.addEventListener("click", (e) => {
|
|
146
|
+
if (e.target.tagName === "BUTTON")
|
|
147
|
+
return;
|
|
148
|
+
details.hidden = !details.hidden;
|
|
149
|
+
toggle.textContent = details.hidden ? "\u25B8" : "\u25BE";
|
|
150
|
+
});
|
|
52
151
|
const loadRemote = async () => {
|
|
53
152
|
banner.remove();
|
|
54
153
|
const full = await getMessage(accountId, uid, true);
|
|
@@ -78,6 +177,17 @@ export async function showMessage(accountId, uid) {
|
|
|
78
177
|
});
|
|
79
178
|
loadRemote();
|
|
80
179
|
});
|
|
180
|
+
banner.querySelector("#btn-allow-to")?.addEventListener("click", async () => {
|
|
181
|
+
const addr = deliveredTo || toAddr;
|
|
182
|
+
if (!addr)
|
|
183
|
+
return;
|
|
184
|
+
await fetch("/api/settings/allow-remote", {
|
|
185
|
+
method: "POST",
|
|
186
|
+
headers: { "Content-Type": "application/json" },
|
|
187
|
+
body: JSON.stringify({ type: "recipient", value: addr }),
|
|
188
|
+
});
|
|
189
|
+
loadRemote();
|
|
190
|
+
});
|
|
81
191
|
}
|
|
82
192
|
// Body in sandboxed iframe
|
|
83
193
|
if (msg.bodyHtml) {
|
|
@@ -85,7 +195,8 @@ export async function showMessage(accountId, uid) {
|
|
|
85
195
|
iframe.sandbox.add("allow-same-origin");
|
|
86
196
|
iframe.sandbox.add("allow-popups");
|
|
87
197
|
iframe.sandbox.add("allow-popups-to-escape-sandbox");
|
|
88
|
-
iframe.
|
|
198
|
+
iframe.sandbox.add("allow-top-navigation-by-user-activation");
|
|
199
|
+
iframe.srcdoc = wrapHtmlBody(msg.bodyHtml, msg.remoteAllowed);
|
|
89
200
|
bodyEl.appendChild(iframe);
|
|
90
201
|
}
|
|
91
202
|
else if (msg.bodyText) {
|
|
@@ -118,6 +229,11 @@ function formatAddr(addr) {
|
|
|
118
229
|
return `${addr.name} <${addr.address}>`;
|
|
119
230
|
return addr.address;
|
|
120
231
|
}
|
|
232
|
+
function escapeText(s) {
|
|
233
|
+
const div = document.createElement("div");
|
|
234
|
+
div.textContent = s;
|
|
235
|
+
return div.innerHTML;
|
|
236
|
+
}
|
|
121
237
|
function formatSize(bytes) {
|
|
122
238
|
if (bytes < 1024)
|
|
123
239
|
return `${bytes} B`;
|
|
@@ -126,9 +242,10 @@ function formatSize(bytes) {
|
|
|
126
242
|
return `${(bytes / 1048576).toFixed(1)} MB`;
|
|
127
243
|
}
|
|
128
244
|
function wrapHtmlBody(html, allowRemote = false) {
|
|
245
|
+
// CSP blocks remote resource loading (tracking pixels, external CSS) but allows link clicks
|
|
129
246
|
const csp = allowRemote
|
|
130
|
-
? ""
|
|
131
|
-
: `<meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'; img-src data: cid:;">`;
|
|
247
|
+
? ""
|
|
248
|
+
: `<meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'; img-src data: cid:; form-action 'none';">`;
|
|
132
249
|
return `<!DOCTYPE html>
|
|
133
250
|
<html><head>
|
|
134
251
|
<meta charset="UTF-8">
|
|
@@ -136,7 +253,7 @@ ${csp}
|
|
|
136
253
|
<style>
|
|
137
254
|
body {
|
|
138
255
|
font-family: system-ui, sans-serif;
|
|
139
|
-
font-size:
|
|
256
|
+
font-size: 17.5px;
|
|
140
257
|
line-height: 1.5;
|
|
141
258
|
color: #1a1a2e;
|
|
142
259
|
background: #fff;
|
|
@@ -13,10 +13,10 @@
|
|
|
13
13
|
</head>
|
|
14
14
|
<body>
|
|
15
15
|
<div class="compose-header">
|
|
16
|
-
<div class="compose-field">
|
|
17
|
-
<label for="compose-from">From</label>
|
|
18
|
-
<
|
|
19
|
-
<
|
|
16
|
+
<div class="compose-field compose-from-field">
|
|
17
|
+
<label for="compose-from-select">From</label>
|
|
18
|
+
<select id="compose-from-select"></select>
|
|
19
|
+
<input type="text" id="compose-from-custom" placeholder="Custom address..." hidden>
|
|
20
20
|
</div>
|
|
21
21
|
<div class="compose-field">
|
|
22
22
|
<label for="compose-to">To</label>
|
|
@@ -18,27 +18,59 @@ const editor = new Quill("#compose-editor", {
|
|
|
18
18
|
}
|
|
19
19
|
});
|
|
20
20
|
// ── Populate from init data ──
|
|
21
|
-
const
|
|
22
|
-
const
|
|
21
|
+
const fromSelect = document.getElementById("compose-from-select");
|
|
22
|
+
const fromCustom = document.getElementById("compose-from-custom");
|
|
23
23
|
const toInput = document.getElementById("compose-to");
|
|
24
24
|
const ccInput = document.getElementById("compose-cc");
|
|
25
25
|
const bccInput = document.getElementById("compose-bcc");
|
|
26
26
|
const subjectInput = document.getElementById("compose-subject");
|
|
27
|
-
/**
|
|
27
|
+
/** Populate the From dropdown with accounts */
|
|
28
|
+
function populateFromSelect(accounts, selectedId) {
|
|
29
|
+
fromSelect.innerHTML = "";
|
|
30
|
+
for (const acct of accounts) {
|
|
31
|
+
const opt = document.createElement("option");
|
|
32
|
+
opt.value = acct.id;
|
|
33
|
+
const displayLabel = acct.label || acct.name;
|
|
34
|
+
opt.textContent = `${displayLabel}: ${acct.name} <${acct.email}>`;
|
|
35
|
+
opt.dataset.email = acct.email;
|
|
36
|
+
opt.dataset.name = acct.name;
|
|
37
|
+
if (acct.defaultSend)
|
|
38
|
+
opt.dataset.defaultSend = "true";
|
|
39
|
+
if (acct.id === selectedId)
|
|
40
|
+
opt.selected = true;
|
|
41
|
+
fromSelect.appendChild(opt);
|
|
42
|
+
}
|
|
43
|
+
// "Other..." option for custom address
|
|
44
|
+
const other = document.createElement("option");
|
|
45
|
+
other.value = "__custom__";
|
|
46
|
+
other.textContent = "Other...";
|
|
47
|
+
fromSelect.appendChild(other);
|
|
48
|
+
}
|
|
49
|
+
fromSelect.addEventListener("change", () => {
|
|
50
|
+
if (fromSelect.value === "__custom__") {
|
|
51
|
+
fromCustom.hidden = false;
|
|
52
|
+
fromCustom.focus();
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
fromCustom.hidden = true;
|
|
56
|
+
fromCustom.value = "";
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
/** Extract account ID from the From field */
|
|
28
60
|
function getFromAccountId() {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
return opt.dataset.accountId;
|
|
61
|
+
if (fromSelect.value === "__custom__") {
|
|
62
|
+
// Custom address — use default send account, fallback to first
|
|
63
|
+
const defaultOpt = Array.from(fromSelect.options).find(o => o.dataset.defaultSend === "true");
|
|
64
|
+
return defaultOpt?.value || fromSelect.options[0]?.value || "";
|
|
34
65
|
}
|
|
35
|
-
|
|
36
|
-
const first = fromDatalist.options[0];
|
|
37
|
-
return first?.dataset.accountId || val;
|
|
66
|
+
return fromSelect.value;
|
|
38
67
|
}
|
|
39
68
|
/** Get the From address string for the message headers */
|
|
40
69
|
function getFromAddress() {
|
|
41
|
-
|
|
70
|
+
if (fromSelect.value === "__custom__")
|
|
71
|
+
return fromCustom.value;
|
|
72
|
+
const opt = fromSelect.selectedOptions[0];
|
|
73
|
+
return opt ? `${opt.dataset.name} <${opt.dataset.email}>` : "";
|
|
42
74
|
}
|
|
43
75
|
/** Smart tab — skip to next empty field, ending at body */
|
|
44
76
|
function smartTab(current) {
|
|
@@ -145,8 +177,7 @@ function setupAutocomplete(input) {
|
|
|
145
177
|
e.preventDefault();
|
|
146
178
|
const idx = activeIndex >= 0 ? activeIndex : 0;
|
|
147
179
|
items[idx].dispatchEvent(new MouseEvent("mousedown"));
|
|
148
|
-
//
|
|
149
|
-
setTimeout(() => smartTab(input), 50);
|
|
180
|
+
// Stay in field — user may want to add more addresses
|
|
150
181
|
return;
|
|
151
182
|
}
|
|
152
183
|
}
|
|
@@ -176,16 +207,7 @@ function parseAddrs(s) {
|
|
|
176
207
|
}
|
|
177
208
|
function applyInit(init) {
|
|
178
209
|
// Populate From dropdown
|
|
179
|
-
|
|
180
|
-
for (const acct of init.accounts) {
|
|
181
|
-
const opt = document.createElement("option");
|
|
182
|
-
opt.value = `${acct.name} <${acct.email}>`;
|
|
183
|
-
opt.dataset.accountId = acct.id;
|
|
184
|
-
fromDatalist.appendChild(opt);
|
|
185
|
-
}
|
|
186
|
-
const selectedAcct = init.accounts.find((a) => a.id === init.accountId) || init.accounts[0];
|
|
187
|
-
if (selectedAcct)
|
|
188
|
-
fromInput.value = `${selectedAcct.name} <${selectedAcct.email}>`;
|
|
210
|
+
populateFromSelect(init.accounts, init.accountId);
|
|
189
211
|
toInput.value = formatAddrs(init.to);
|
|
190
212
|
ccInput.value = formatAddrs(init.cc);
|
|
191
213
|
subjectInput.value = init.subject;
|
|
@@ -193,6 +215,10 @@ function applyInit(init) {
|
|
|
193
215
|
editor.clipboard.dangerouslyPasteHTML(init.bodyHtml);
|
|
194
216
|
editor.setSelection(0, 0);
|
|
195
217
|
}
|
|
218
|
+
// If resuming a draft, track its UID for deletion after send
|
|
219
|
+
if (init.draftUid) {
|
|
220
|
+
draftUid = init.draftUid;
|
|
221
|
+
}
|
|
196
222
|
document.title = init.subject ? `${init.subject} - Compose` : "Compose - mailx";
|
|
197
223
|
// Focus first empty field: To → Subject → body
|
|
198
224
|
if (!toInput.value)
|
|
@@ -213,19 +239,11 @@ else {
|
|
|
213
239
|
toInput.focus();
|
|
214
240
|
}
|
|
215
241
|
// If From dropdown is empty (new compose without init, or init had no accounts), fetch from API
|
|
216
|
-
if (
|
|
242
|
+
if (fromSelect.options.length === 0) {
|
|
217
243
|
fetch("/api/accounts")
|
|
218
244
|
.then(r => r.json())
|
|
219
245
|
.then((accounts) => {
|
|
220
|
-
|
|
221
|
-
const opt = document.createElement("option");
|
|
222
|
-
opt.value = `${acct.name} <${acct.email}>`;
|
|
223
|
-
opt.dataset.accountId = acct.id;
|
|
224
|
-
fromDatalist.appendChild(opt);
|
|
225
|
-
}
|
|
226
|
-
if (!fromInput.value && accounts.length > 0) {
|
|
227
|
-
fromInput.value = `${accounts[0].name} <${accounts[0].email}>`;
|
|
228
|
-
}
|
|
246
|
+
populateFromSelect(accounts);
|
|
229
247
|
})
|
|
230
248
|
.catch(e => console.error("Failed to load accounts:", e));
|
|
231
249
|
}
|
|
@@ -270,6 +288,7 @@ document.getElementById("btn-send")?.addEventListener("click", async () => {
|
|
|
270
288
|
btn.textContent = "Sending...";
|
|
271
289
|
const body = {
|
|
272
290
|
from: getFromAccountId(),
|
|
291
|
+
fromAddress: getFromAddress(),
|
|
273
292
|
to: parseAddrs(toInput.value),
|
|
274
293
|
cc: parseAddrs(ccInput.value),
|
|
275
294
|
bcc: parseAddrs(bccInput.value),
|
package/client/index.html
CHANGED
|
@@ -48,21 +48,29 @@
|
|
|
48
48
|
<search>
|
|
49
49
|
<input type="search" id="search-input" placeholder="Search..." autocomplete="off">
|
|
50
50
|
</search>
|
|
51
|
-
<button class="tb-btn" id="btn-sync" title="Sync">
|
|
52
|
-
<span class="tb-icon">↻</span>
|
|
51
|
+
<button class="tb-btn" id="btn-sync" title="Sync all folders (F5)">
|
|
52
|
+
<span class="tb-icon">↻</span> Sync
|
|
53
53
|
</button>
|
|
54
|
-
<button class="tb-btn" id="btn-restart" title="Restart server
|
|
55
|
-
<span class="tb-icon">⚡</span>
|
|
54
|
+
<button class="tb-btn" id="btn-restart" title="Restart server and reload page">
|
|
55
|
+
<span class="tb-icon">⚡</span> Restart
|
|
56
56
|
</button>
|
|
57
57
|
</div>
|
|
58
58
|
</header>
|
|
59
59
|
|
|
60
|
-
<
|
|
61
|
-
<div class="
|
|
62
|
-
|
|
60
|
+
<div class="folder-panel">
|
|
61
|
+
<div class="ft-filter">
|
|
62
|
+
<input type="text" id="ft-filter-input" placeholder="Find folder..." autocomplete="off">
|
|
63
|
+
</div>
|
|
64
|
+
<nav class="folder-tree" id="folder-tree">
|
|
65
|
+
<div class="folder-loading">Loading accounts...</div>
|
|
66
|
+
</nav>
|
|
67
|
+
</div>
|
|
63
68
|
|
|
64
69
|
<main class="main-area">
|
|
65
70
|
<section class="message-list" id="message-list">
|
|
71
|
+
<div class="ml-filter">
|
|
72
|
+
<input type="text" id="ml-filter-input" placeholder="Filter..." autocomplete="off">
|
|
73
|
+
</div>
|
|
66
74
|
<div class="ml-header">
|
|
67
75
|
<span class="ml-col ml-col-flag"></span>
|
|
68
76
|
<span class="ml-col ml-col-from" data-sort="from">From</span>
|
|
@@ -78,8 +86,17 @@
|
|
|
78
86
|
|
|
79
87
|
<section class="message-viewer" id="message-viewer">
|
|
80
88
|
<div class="mv-header" id="mv-header" hidden>
|
|
81
|
-
<div class="mv-
|
|
82
|
-
|
|
89
|
+
<div class="mv-header-top">
|
|
90
|
+
<div class="mv-header-info">
|
|
91
|
+
<div class="mv-from"></div>
|
|
92
|
+
<div class="mv-to"></div>
|
|
93
|
+
</div>
|
|
94
|
+
<div class="mv-header-actions">
|
|
95
|
+
<button class="mv-action mv-action-primary" id="mv-edit-draft" hidden>Edit & Send</button>
|
|
96
|
+
<a class="mv-unsubscribe" id="mv-unsubscribe" hidden>Unsubscribe</a>
|
|
97
|
+
<button class="mv-action" id="mv-view-source" title="View source (.eml)" hidden>Source</button>
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
83
100
|
<div class="mv-subject"></div>
|
|
84
101
|
<div class="mv-date"></div>
|
|
85
102
|
</div>
|
|
@@ -96,5 +113,12 @@
|
|
|
96
113
|
<span id="status-pending"></span>
|
|
97
114
|
<span id="status-queue">Queue: empty</span>
|
|
98
115
|
</footer>
|
|
116
|
+
|
|
117
|
+
<div id="startup-overlay" class="startup-overlay">
|
|
118
|
+
<div class="startup-content">
|
|
119
|
+
<div class="startup-spinner"></div>
|
|
120
|
+
<div id="startup-status">Connecting to server...</div>
|
|
121
|
+
</div>
|
|
122
|
+
</div>
|
|
99
123
|
</body>
|
|
100
124
|
</html>
|
package/client/lib/api-client.js
CHANGED
|
@@ -1,8 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* API client — auto-detects IPC (WebView) vs HTTP (browser).
|
|
3
|
+
* When mailxapi is available (injected by launcher), calls go directly via IPC.
|
|
4
|
+
* Otherwise falls back to REST/WebSocket.
|
|
4
5
|
*/
|
|
5
|
-
|
|
6
|
+
const hasIPC = typeof mailxapi !== "undefined" && mailxapi?.isApp;
|
|
7
|
+
// ── HTTP fallback ──
|
|
8
|
+
// Abort controller for message-list requests — cancel stale fetches when folder changes
|
|
9
|
+
let messageListAbort = null;
|
|
10
|
+
export function abortMessageListRequests() {
|
|
11
|
+
if (messageListAbort) {
|
|
12
|
+
messageListAbort.abort();
|
|
13
|
+
messageListAbort = null;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function newMessageListSignal() {
|
|
17
|
+
abortMessageListRequests();
|
|
18
|
+
messageListAbort = new AbortController();
|
|
19
|
+
return messageListAbort.signal;
|
|
20
|
+
}
|
|
6
21
|
async function api(path, options) {
|
|
7
22
|
const res = await fetch(`/api${path}`, {
|
|
8
23
|
headers: { "Content-Type": "application/json" },
|
|
@@ -14,54 +29,111 @@ async function api(path, options) {
|
|
|
14
29
|
}
|
|
15
30
|
return res.json();
|
|
16
31
|
}
|
|
17
|
-
// ──
|
|
32
|
+
// ── API Methods (IPC or HTTP) ──
|
|
18
33
|
export function getAccounts() {
|
|
34
|
+
if (hasIPC)
|
|
35
|
+
return mailxapi.getAccounts();
|
|
19
36
|
return api("/accounts");
|
|
20
37
|
}
|
|
21
38
|
export function getFolders(accountId) {
|
|
39
|
+
if (hasIPC)
|
|
40
|
+
return mailxapi.getFolders(accountId);
|
|
22
41
|
return api(`/folders/${accountId}`);
|
|
23
42
|
}
|
|
24
43
|
export function getMessages(accountId, folderId, page = 1, pageSize = 50) {
|
|
25
|
-
|
|
44
|
+
if (hasIPC)
|
|
45
|
+
return mailxapi.getMessages(accountId, folderId, page, pageSize);
|
|
46
|
+
const signal = newMessageListSignal();
|
|
47
|
+
return api(`/messages/${accountId}/${folderId}?page=${page}&pageSize=${pageSize}`, { signal });
|
|
48
|
+
}
|
|
49
|
+
export function getUnifiedInbox(page = 1, pageSize = 50) {
|
|
50
|
+
if (hasIPC)
|
|
51
|
+
return mailxapi.getUnifiedInbox(page, pageSize);
|
|
52
|
+
const signal = newMessageListSignal();
|
|
53
|
+
return api(`/messages/unified/inbox?page=${page}&pageSize=${pageSize}`, { signal });
|
|
26
54
|
}
|
|
27
55
|
export function searchMessages(query, page = 1, pageSize = 50) {
|
|
56
|
+
if (hasIPC)
|
|
57
|
+
return mailxapi.searchMessages(query, page, pageSize);
|
|
28
58
|
return api(`/search?q=${encodeURIComponent(query)}&page=${page}&pageSize=${pageSize}`);
|
|
29
59
|
}
|
|
30
|
-
export function
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
60
|
+
export function getMessage(accountId, uid, allowRemote = false, folderId) {
|
|
61
|
+
if (hasIPC)
|
|
62
|
+
return mailxapi.getMessage(accountId, uid, allowRemote, folderId);
|
|
63
|
+
const params = new URLSearchParams();
|
|
64
|
+
if (allowRemote)
|
|
65
|
+
params.set("allowRemote", "true");
|
|
66
|
+
if (folderId != null)
|
|
67
|
+
params.set("folderId", String(folderId));
|
|
68
|
+
const q = params.toString() ? `?${params}` : "";
|
|
35
69
|
return api(`/message/${accountId}/${uid}${q}`);
|
|
36
70
|
}
|
|
37
71
|
export function updateFlags(accountId, uid, flags) {
|
|
72
|
+
if (hasIPC)
|
|
73
|
+
return mailxapi.updateFlags(accountId, uid, flags);
|
|
38
74
|
return api(`/message/${accountId}/${uid}/flags`, {
|
|
39
75
|
method: "PATCH",
|
|
40
76
|
body: JSON.stringify({ flags })
|
|
41
77
|
});
|
|
42
78
|
}
|
|
43
79
|
export function triggerSync() {
|
|
80
|
+
if (hasIPC)
|
|
81
|
+
return mailxapi.syncAll();
|
|
44
82
|
return api("/sync", { method: "POST" });
|
|
45
83
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
84
|
+
export function getVersion() {
|
|
85
|
+
if (hasIPC)
|
|
86
|
+
return mailxapi.getVersion();
|
|
87
|
+
return api("/version");
|
|
88
|
+
}
|
|
89
|
+
export function getSyncPending() {
|
|
90
|
+
if (hasIPC)
|
|
91
|
+
return mailxapi.getSyncPending();
|
|
92
|
+
return api("/sync/pending");
|
|
93
|
+
}
|
|
94
|
+
export function searchContacts(query) {
|
|
95
|
+
if (hasIPC)
|
|
96
|
+
return mailxapi.searchContacts(query);
|
|
97
|
+
return api(`/contacts?q=${encodeURIComponent(query)}`);
|
|
98
|
+
}
|
|
99
|
+
export function allowRemoteContent(type, value) {
|
|
100
|
+
if (hasIPC)
|
|
101
|
+
return mailxapi.allowRemoteContent(type, value);
|
|
102
|
+
return api("/settings/allow-remote", {
|
|
103
|
+
method: "POST",
|
|
104
|
+
body: JSON.stringify({ type, value })
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
const eventHandlers = [];
|
|
108
|
+
export function onEvent(handler) {
|
|
109
|
+
eventHandlers.push(handler);
|
|
110
|
+
}
|
|
111
|
+
export function connectEvents() {
|
|
112
|
+
if (hasIPC) {
|
|
113
|
+
// IPC events come via mailxapi.onEvent
|
|
114
|
+
mailxapi.onEvent((event) => {
|
|
115
|
+
for (const h of eventHandlers)
|
|
116
|
+
h(event);
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
// WebSocket for HTTP mode
|
|
121
|
+
const protocol = location.protocol === "https:" ? "wss:" : "ws:";
|
|
122
|
+
const ws = new WebSocket(`${protocol}//${location.host}`);
|
|
123
|
+
ws.onmessage = (ev) => {
|
|
124
|
+
try {
|
|
125
|
+
const event = JSON.parse(ev.data);
|
|
126
|
+
for (const h of eventHandlers)
|
|
127
|
+
h(event);
|
|
128
|
+
}
|
|
129
|
+
catch { /* ignore */ }
|
|
130
|
+
};
|
|
131
|
+
ws.onclose = () => {
|
|
132
|
+
setTimeout(connectEvents, 3000);
|
|
133
|
+
};
|
|
134
|
+
}
|
|
66
135
|
}
|
|
136
|
+
// Legacy exports for backward compatibility
|
|
137
|
+
export const connectWebSocket = connectEvents;
|
|
138
|
+
export const onWsEvent = onEvent;
|
|
67
139
|
//# sourceMappingURL=api-client.js.map
|