@bobfrankston/mailx 1.0.9 → 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/bin/postinstall.js +41 -0
- 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 +5 -1
- package/client/styles/components.css +188 -15
- package/client/styles/layout.css +2 -1
- package/killmail.cmd +6 -0
- package/launch.ps1 +48 -3
- 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/launcher/mailx.ico +0 -0
- package/npmg.bat +6 -0
- package/package.json +9 -3
- package/packages/mailx-api/index.js +79 -26
- package/packages/mailx-api/package.json +5 -1
- package/packages/mailx-compose/package.json +5 -1
- 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-imap/package.json +5 -1
- package/packages/mailx-send/package.json +12 -2
- package/packages/mailx-server/index.js +42 -31
- package/packages/mailx-server/package.json +5 -2
- package/packages/mailx-settings/index.d.ts +1 -1
- package/packages/mailx-settings/index.js +21 -12
- package/packages/mailx-settings/package.json +5 -1
- 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-store/package.json +5 -1
- package/packages/mailx-types/index.d.ts +3 -1
- package/packages/mailx-types/package.json +6 -2
- package/.tswalk.json +0 -3366
- 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
|
@@ -3,10 +3,24 @@
|
|
|
3
3
|
* Loads more messages on scroll.
|
|
4
4
|
*/
|
|
5
5
|
import { getMessages, getUnifiedInbox, searchMessages } from "../lib/api-client.js";
|
|
6
|
+
function clearFilter() {
|
|
7
|
+
const f = document.getElementById("ml-filter-input");
|
|
8
|
+
if (f)
|
|
9
|
+
f.value = "";
|
|
10
|
+
}
|
|
11
|
+
/** Clear the message viewer when no message is selected */
|
|
12
|
+
function clearViewer() {
|
|
13
|
+
const bodyEl = document.getElementById("mv-body");
|
|
14
|
+
const headerEl = document.getElementById("mv-header");
|
|
15
|
+
if (bodyEl)
|
|
16
|
+
bodyEl.innerHTML = `<div class="mv-empty">Select a message to read</div>`;
|
|
17
|
+
if (headerEl)
|
|
18
|
+
headerEl.hidden = true;
|
|
19
|
+
}
|
|
6
20
|
let onMessageSelect;
|
|
7
21
|
let currentAccountId;
|
|
8
22
|
let currentFolderId;
|
|
9
|
-
let
|
|
23
|
+
let lastClickedRow = null;
|
|
10
24
|
let currentPage;
|
|
11
25
|
let totalMessages;
|
|
12
26
|
let loading = false;
|
|
@@ -14,11 +28,62 @@ let unifiedMode = false;
|
|
|
14
28
|
let searchMode = false;
|
|
15
29
|
let currentSearchQuery = "";
|
|
16
30
|
let showToInsteadOfFrom = false;
|
|
31
|
+
/** Get all selected message rows */
|
|
32
|
+
export function getSelectedMessages() {
|
|
33
|
+
const body = document.getElementById("ml-body");
|
|
34
|
+
if (!body)
|
|
35
|
+
return [];
|
|
36
|
+
const rows = body.querySelectorAll(".ml-row.selected");
|
|
37
|
+
return Array.from(rows).map(r => ({
|
|
38
|
+
accountId: r.dataset.accountId || "",
|
|
39
|
+
uid: Number(r.dataset.uid),
|
|
40
|
+
folderId: Number(r.dataset.folderId),
|
|
41
|
+
}));
|
|
42
|
+
}
|
|
43
|
+
function clearSelection() {
|
|
44
|
+
const body = document.getElementById("ml-body");
|
|
45
|
+
if (body)
|
|
46
|
+
body.querySelectorAll(".ml-row.selected").forEach(r => r.classList.remove("selected"));
|
|
47
|
+
}
|
|
48
|
+
function selectRange(from, to) {
|
|
49
|
+
const body = document.getElementById("ml-body");
|
|
50
|
+
if (!body)
|
|
51
|
+
return;
|
|
52
|
+
const rows = Array.from(body.querySelectorAll(".ml-row"));
|
|
53
|
+
const fromIdx = rows.indexOf(from);
|
|
54
|
+
const toIdx = rows.indexOf(to);
|
|
55
|
+
if (fromIdx < 0 || toIdx < 0)
|
|
56
|
+
return;
|
|
57
|
+
const lo = Math.min(fromIdx, toIdx);
|
|
58
|
+
const hi = Math.max(fromIdx, toIdx);
|
|
59
|
+
for (let i = lo; i <= hi; i++)
|
|
60
|
+
rows[i].classList.add("selected");
|
|
61
|
+
}
|
|
17
62
|
const timeFmt = { hour: "2-digit", minute: "2-digit", hour12: false };
|
|
18
63
|
const dateFmt = { year: "numeric", month: "short", day: "numeric", hour: "2-digit", minute: "2-digit", hour12: false };
|
|
19
64
|
const dateFmtSameYear = { month: "short", day: "numeric", hour: "2-digit", minute: "2-digit", hour12: false };
|
|
20
65
|
export function initMessageList(handler) {
|
|
21
66
|
onMessageSelect = handler;
|
|
67
|
+
// Client-side filter
|
|
68
|
+
const filterInput = document.getElementById("ml-filter-input");
|
|
69
|
+
if (filterInput) {
|
|
70
|
+
filterInput.addEventListener("input", () => {
|
|
71
|
+
const query = filterInput.value.toLowerCase();
|
|
72
|
+
const body = document.getElementById("ml-body");
|
|
73
|
+
if (!body)
|
|
74
|
+
return;
|
|
75
|
+
for (const row of body.querySelectorAll(".ml-row")) {
|
|
76
|
+
const text = row.textContent?.toLowerCase() || "";
|
|
77
|
+
row.classList.toggle("filter-hidden", query.length > 0 && !text.includes(query));
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
filterInput.addEventListener("keydown", (e) => {
|
|
81
|
+
if (e.key === "Escape") {
|
|
82
|
+
filterInput.value = "";
|
|
83
|
+
filterInput.dispatchEvent(new Event("input"));
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
}
|
|
22
87
|
// Infinite scroll
|
|
23
88
|
const body = document.getElementById("ml-body");
|
|
24
89
|
if (body) {
|
|
@@ -46,6 +111,7 @@ export function reloadCurrentFolder() {
|
|
|
46
111
|
}
|
|
47
112
|
/** Load unified inbox (all accounts) */
|
|
48
113
|
export async function loadUnifiedInbox() {
|
|
114
|
+
clearFilter();
|
|
49
115
|
unifiedMode = true;
|
|
50
116
|
currentPage = 1;
|
|
51
117
|
totalMessages = 0;
|
|
@@ -58,17 +124,24 @@ export async function loadUnifiedInbox() {
|
|
|
58
124
|
totalMessages = result.total;
|
|
59
125
|
if (result.items.length === 0) {
|
|
60
126
|
body.innerHTML = `<div class="ml-empty">No messages</div>`;
|
|
127
|
+
clearViewer();
|
|
61
128
|
return;
|
|
62
129
|
}
|
|
63
130
|
body.innerHTML = "";
|
|
64
131
|
appendMessages(body, "", result.items);
|
|
132
|
+
const firstRow = body.querySelector(".ml-row");
|
|
133
|
+
if (firstRow)
|
|
134
|
+
firstRow.click();
|
|
65
135
|
}
|
|
66
136
|
catch (e) {
|
|
137
|
+
if (e.name === "AbortError")
|
|
138
|
+
return;
|
|
67
139
|
body.innerHTML = `<div class="ml-empty">Error: ${e.message}</div>`;
|
|
68
140
|
}
|
|
69
141
|
}
|
|
70
142
|
/** Load search results */
|
|
71
143
|
export async function loadSearchResults(query) {
|
|
144
|
+
clearFilter();
|
|
72
145
|
searchMode = true;
|
|
73
146
|
unifiedMode = false;
|
|
74
147
|
currentSearchQuery = query;
|
|
@@ -93,6 +166,7 @@ export async function loadSearchResults(query) {
|
|
|
93
166
|
}
|
|
94
167
|
}
|
|
95
168
|
export async function loadMessages(accountId, folderId, page = 1, specialUse = "") {
|
|
169
|
+
clearFilter();
|
|
96
170
|
searchMode = false;
|
|
97
171
|
unifiedMode = false;
|
|
98
172
|
showToInsteadOfFrom = ["sent", "drafts", "outbox"].includes(specialUse) ||
|
|
@@ -114,12 +188,19 @@ export async function loadMessages(accountId, folderId, page = 1, specialUse = "
|
|
|
114
188
|
totalMessages = result.total;
|
|
115
189
|
if (result.items.length === 0) {
|
|
116
190
|
body.innerHTML = `<div class="ml-empty">No messages</div>`;
|
|
191
|
+
clearViewer();
|
|
117
192
|
return;
|
|
118
193
|
}
|
|
119
194
|
body.innerHTML = "";
|
|
120
195
|
appendMessages(body, accountId, result.items);
|
|
196
|
+
// Auto-select first message
|
|
197
|
+
const firstRow = body.querySelector(".ml-row");
|
|
198
|
+
if (firstRow)
|
|
199
|
+
firstRow.click();
|
|
121
200
|
}
|
|
122
201
|
catch (e) {
|
|
202
|
+
if (e.name === "AbortError")
|
|
203
|
+
return; // Superseded by newer request
|
|
123
204
|
body.innerHTML = `<div class="ml-empty">Error: ${e.message}</div>`;
|
|
124
205
|
}
|
|
125
206
|
}
|
|
@@ -146,13 +227,18 @@ async function loadMoreMessages() {
|
|
|
146
227
|
}
|
|
147
228
|
function appendMessages(body, accountId, items) {
|
|
148
229
|
for (const msg of items) {
|
|
230
|
+
// Use per-message accountId for unified inbox, fallback to list-level accountId
|
|
231
|
+
const msgAccountId = msg.accountId || accountId;
|
|
149
232
|
const row = document.createElement("div");
|
|
150
233
|
row.className = "ml-row";
|
|
234
|
+
row.draggable = true;
|
|
151
235
|
if (!msg.flags.includes("\\Seen"))
|
|
152
236
|
row.classList.add("unread");
|
|
153
237
|
if (msg.flags.includes("\\Flagged"))
|
|
154
238
|
row.classList.add("flagged");
|
|
155
239
|
row.dataset.uid = String(msg.uid);
|
|
240
|
+
row.dataset.accountId = msgAccountId;
|
|
241
|
+
row.dataset.folderId = String(msg.folderId);
|
|
156
242
|
const flag = document.createElement("span");
|
|
157
243
|
flag.className = "ml-flag";
|
|
158
244
|
flag.textContent = msg.flags.includes("\\Flagged") ? "\u2605" : "\u2606"; // ★ or ☆
|
|
@@ -185,7 +271,7 @@ function appendMessages(body, accountId, items) {
|
|
|
185
271
|
? currentFlags.filter((f) => f !== "\\Flagged")
|
|
186
272
|
: [...currentFlags, "\\Flagged"];
|
|
187
273
|
try {
|
|
188
|
-
await fetch(`/api/message/${
|
|
274
|
+
await fetch(`/api/message/${msgAccountId}/${msg.uid}/flags`, {
|
|
189
275
|
method: "PATCH",
|
|
190
276
|
headers: { "Content-Type": "application/json" },
|
|
191
277
|
body: JSON.stringify({ flags: newFlags }),
|
|
@@ -200,14 +286,54 @@ function appendMessages(body, accountId, items) {
|
|
|
200
286
|
row.appendChild(from);
|
|
201
287
|
row.appendChild(date);
|
|
202
288
|
row.appendChild(subject);
|
|
203
|
-
row.addEventListener("click", () => {
|
|
204
|
-
if (
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
289
|
+
row.addEventListener("click", (e) => {
|
|
290
|
+
if (e.shiftKey && lastClickedRow) {
|
|
291
|
+
// Shift+click: range select from last clicked to this row
|
|
292
|
+
clearSelection();
|
|
293
|
+
selectRange(lastClickedRow, row);
|
|
294
|
+
}
|
|
295
|
+
else if (e.ctrlKey || e.metaKey) {
|
|
296
|
+
// Ctrl+click: toggle this row
|
|
297
|
+
row.classList.toggle("selected");
|
|
298
|
+
}
|
|
299
|
+
else {
|
|
300
|
+
// Plain click: single select
|
|
301
|
+
clearSelection();
|
|
302
|
+
row.classList.add("selected");
|
|
303
|
+
}
|
|
304
|
+
lastClickedRow = row;
|
|
208
305
|
row.classList.remove("unread");
|
|
209
|
-
onMessageSelect(
|
|
306
|
+
onMessageSelect(msgAccountId, msg.uid, msg.folderId);
|
|
307
|
+
});
|
|
308
|
+
row.addEventListener("dragstart", (e) => {
|
|
309
|
+
// If dragging a non-selected row, select it first
|
|
310
|
+
if (!row.classList.contains("selected")) {
|
|
311
|
+
clearSelection();
|
|
312
|
+
row.classList.add("selected");
|
|
313
|
+
lastClickedRow = row;
|
|
314
|
+
}
|
|
315
|
+
const selected = getSelectedMessages();
|
|
316
|
+
e.dataTransfer.setData("application/x-mailx-messages", JSON.stringify(selected));
|
|
317
|
+
// Legacy single-message format for backwards compat
|
|
318
|
+
e.dataTransfer.setData("application/x-mailx-message", JSON.stringify({
|
|
319
|
+
accountId: msgAccountId,
|
|
320
|
+
uid: msg.uid,
|
|
321
|
+
folderId: msg.folderId,
|
|
322
|
+
subject: msg.subject,
|
|
323
|
+
}));
|
|
324
|
+
e.dataTransfer.effectAllowed = "copyMove";
|
|
325
|
+
row.classList.add("dragging");
|
|
326
|
+
// Show drag count
|
|
327
|
+
if (selected.length > 1) {
|
|
328
|
+
const badge = document.createElement("div");
|
|
329
|
+
badge.textContent = `${selected.length} messages`;
|
|
330
|
+
badge.style.cssText = "position:absolute;top:-1000px;background:#333;color:white;padding:4px 8px;border-radius:4px;font-size:12px";
|
|
331
|
+
document.body.appendChild(badge);
|
|
332
|
+
e.dataTransfer.setDragImage(badge, 0, 0);
|
|
333
|
+
setTimeout(() => badge.remove(), 0);
|
|
334
|
+
}
|
|
210
335
|
});
|
|
336
|
+
row.addEventListener("dragend", () => row.classList.remove("dragging"));
|
|
211
337
|
body.appendChild(row);
|
|
212
338
|
}
|
|
213
339
|
}
|
|
@@ -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),
|