@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
package/bin/mailx.js
CHANGED
|
@@ -1,25 +1,28 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* mailx — email client
|
|
3
|
+
* mailx — email client
|
|
4
4
|
*
|
|
5
5
|
* Usage:
|
|
6
|
-
* mailx
|
|
7
|
-
* mailx --server Start
|
|
8
|
-
* mailx --no-browser
|
|
9
|
-
* mailx --external Bind to all interfaces (
|
|
6
|
+
* mailx Launch native app (IPC, no server)
|
|
7
|
+
* mailx --server Start Express server + open browser
|
|
8
|
+
* mailx --no-browser Server mode, don't open browser
|
|
9
|
+
* mailx --external Bind to all interfaces (server mode only)
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
+
import fs from "node:fs";
|
|
13
|
+
import path from "node:path";
|
|
12
14
|
import net from "node:net";
|
|
13
15
|
|
|
14
16
|
const PORT = 9333;
|
|
15
17
|
const args = process.argv.slice(2);
|
|
18
|
+
const serverMode = args.includes("--server");
|
|
19
|
+
const noBrowser = args.includes("--no-browser");
|
|
16
20
|
|
|
17
21
|
function isPortInUse(port) {
|
|
18
22
|
return new Promise((resolve) => {
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
server.listen(port, "127.0.0.1");
|
|
23
|
+
const socket = net.createConnection({ port, host: "127.0.0.1" });
|
|
24
|
+
socket.once("connect", () => { socket.destroy(); resolve(true); });
|
|
25
|
+
socket.once("error", () => resolve(false));
|
|
23
26
|
});
|
|
24
27
|
}
|
|
25
28
|
|
|
@@ -32,33 +35,48 @@ function openBrowser(url) {
|
|
|
32
35
|
}
|
|
33
36
|
|
|
34
37
|
async function main() {
|
|
35
|
-
|
|
36
|
-
|
|
38
|
+
if (serverMode) {
|
|
39
|
+
// Server mode — Express + WebSocket, open browser
|
|
40
|
+
const inUse = await isPortInUse(PORT);
|
|
41
|
+
if (inUse) {
|
|
42
|
+
console.log("mailx server already running");
|
|
43
|
+
if (!noBrowser) openBrowser(`http://localhost:${PORT}`);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
console.log("Starting mailx server...");
|
|
48
|
+
if (args.includes("--external")) process.argv.push("--external");
|
|
49
|
+
await import("../packages/mailx-server/index.js");
|
|
37
50
|
|
|
38
|
-
const inUse = await isPortInUse(PORT);
|
|
39
|
-
if (inUse) {
|
|
40
|
-
console.log("mailx is already running");
|
|
41
51
|
if (!noBrowser) {
|
|
52
|
+
for (let i = 0; i < 30; i++) {
|
|
53
|
+
await new Promise(r => setTimeout(r, 200));
|
|
54
|
+
if (await isPortInUse(PORT)) break;
|
|
55
|
+
}
|
|
42
56
|
openBrowser(`http://localhost:${PORT}`);
|
|
43
|
-
console.log("mailx opened");
|
|
57
|
+
console.log("mailx opened (browser)");
|
|
44
58
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
59
|
+
} else {
|
|
60
|
+
// Default: launch native WebView app with IPC
|
|
61
|
+
const launcherPaths = [
|
|
62
|
+
path.join(import.meta.dirname, "..", "launcher", "bin", "mailx-app.exe"),
|
|
63
|
+
path.join(import.meta.dirname, "..", "launcher", "target", "debug", "mailx-app.exe"),
|
|
64
|
+
];
|
|
51
65
|
|
|
52
|
-
|
|
53
|
-
await import("../packages/mailx-server/index.js");
|
|
66
|
+
let launcherPath = launcherPaths.find(p => fs.existsSync(p));
|
|
54
67
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
68
|
+
if (launcherPath) {
|
|
69
|
+
console.log("Starting mailx...");
|
|
70
|
+
const { spawn } = await import("node:child_process");
|
|
71
|
+
const child = spawn(launcherPath, args, { detached: true, stdio: "ignore" });
|
|
72
|
+
child.unref();
|
|
73
|
+
console.log("mailx launched");
|
|
74
|
+
} else {
|
|
75
|
+
// No native launcher — fall back to server mode + browser
|
|
76
|
+
console.log("Native launcher not found, falling back to server mode");
|
|
77
|
+
process.argv.push("--server");
|
|
78
|
+
await main(); // recurse with --server
|
|
59
79
|
}
|
|
60
|
-
openBrowser(`http://localhost:${PORT}`);
|
|
61
|
-
console.log("mailx opened");
|
|
62
80
|
}
|
|
63
81
|
}
|
|
64
82
|
|
package/client/app.js
CHANGED
|
@@ -8,16 +8,19 @@ import { showMessage, getCurrentMessage } from "./components/message-viewer.js";
|
|
|
8
8
|
import { connectWebSocket, onWsEvent, triggerSync, getAccounts } from "./lib/api-client.js";
|
|
9
9
|
// ── Wire up components ──
|
|
10
10
|
const folderTree = document.getElementById("folder-tree");
|
|
11
|
+
let currentFolderSpecialUse = "";
|
|
11
12
|
initFolderTree(folderTree, (accountId, folderId, folderName, specialUse) => {
|
|
13
|
+
currentFolderSpecialUse = specialUse;
|
|
12
14
|
loadMessages(accountId, folderId, 1, specialUse);
|
|
13
15
|
document.title = `mailx - ${folderName}`;
|
|
14
16
|
}, () => {
|
|
15
17
|
// Unified inbox handler
|
|
18
|
+
currentFolderSpecialUse = "inbox";
|
|
16
19
|
loadUnifiedInbox();
|
|
17
20
|
document.title = "mailx - All Inboxes";
|
|
18
21
|
});
|
|
19
|
-
initMessageList((accountId, uid) => {
|
|
20
|
-
showMessage(accountId, uid);
|
|
22
|
+
initMessageList((accountId, uid, folderId) => {
|
|
23
|
+
showMessage(accountId, uid, folderId, currentFolderSpecialUse);
|
|
21
24
|
// Enable action buttons when a message is selected
|
|
22
25
|
for (const id of ["btn-reply", "btn-reply-all", "btn-forward", "btn-delete", "btn-flag"]) {
|
|
23
26
|
const btn = document.getElementById(id);
|
|
@@ -144,14 +147,27 @@ async function deleteCurrentMessage() {
|
|
|
144
147
|
if (statusSync?.textContent?.includes("Ctrl+Z"))
|
|
145
148
|
statusSync.textContent = "";
|
|
146
149
|
}, 30000);
|
|
147
|
-
//
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
150
|
+
// Remove the deleted row and select the next message
|
|
151
|
+
const mlBody = document.getElementById("ml-body");
|
|
152
|
+
if (mlBody) {
|
|
153
|
+
const deletedRow = mlBody.querySelector(`.ml-row[data-uid="${message.uid}"]`);
|
|
154
|
+
if (deletedRow) {
|
|
155
|
+
const nextRow = (deletedRow.nextElementSibling || deletedRow.previousElementSibling);
|
|
156
|
+
deletedRow.remove();
|
|
157
|
+
if (nextRow?.classList.contains("ml-row")) {
|
|
158
|
+
nextRow.click();
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
// No more messages — clear preview
|
|
162
|
+
const bodyEl = document.getElementById("mv-body");
|
|
163
|
+
const headerEl = document.getElementById("mv-header");
|
|
164
|
+
if (bodyEl)
|
|
165
|
+
bodyEl.innerHTML = `<div class="mv-empty">Select a message to read</div>`;
|
|
166
|
+
if (headerEl)
|
|
167
|
+
headerEl.hidden = true;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
155
171
|
}
|
|
156
172
|
catch (e) {
|
|
157
173
|
console.error(`Delete failed: ${e.message}`);
|
|
@@ -220,6 +236,52 @@ searchInput?.addEventListener("keydown", (e) => {
|
|
|
220
236
|
reloadCurrentFolder();
|
|
221
237
|
}
|
|
222
238
|
});
|
|
239
|
+
// ── Reload message list after drag-move ──
|
|
240
|
+
document.addEventListener("mailx-message-moved", () => reloadCurrentFolder());
|
|
241
|
+
// ── Folder filter ──
|
|
242
|
+
const ftFilterInput = document.getElementById("ft-filter-input");
|
|
243
|
+
if (ftFilterInput) {
|
|
244
|
+
ftFilterInput.addEventListener("input", () => {
|
|
245
|
+
const query = ftFilterInput.value.toLowerCase();
|
|
246
|
+
const tree = document.getElementById("folder-tree");
|
|
247
|
+
if (!tree)
|
|
248
|
+
return;
|
|
249
|
+
if (!query) {
|
|
250
|
+
// Clear filter — show everything
|
|
251
|
+
tree.querySelectorAll(".ft-filter-hidden").forEach(el => el.classList.remove("ft-filter-hidden"));
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
// Hide all folders first, then show matches + their parent accounts
|
|
255
|
+
const folders = tree.querySelectorAll(".ft-folder");
|
|
256
|
+
const accounts = tree.querySelectorAll(".ft-account");
|
|
257
|
+
for (const acct of accounts)
|
|
258
|
+
acct.classList.add("ft-filter-hidden");
|
|
259
|
+
for (const f of folders)
|
|
260
|
+
f.classList.add("ft-filter-hidden");
|
|
261
|
+
for (const f of folders) {
|
|
262
|
+
const name = f.querySelector(".ft-folder-name")?.textContent?.toLowerCase() || "";
|
|
263
|
+
if (name.includes(query)) {
|
|
264
|
+
f.classList.remove("ft-filter-hidden");
|
|
265
|
+
// Show parent account
|
|
266
|
+
const acct = f.closest(".ft-account");
|
|
267
|
+
if (acct)
|
|
268
|
+
acct.classList.remove("ft-filter-hidden");
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
// Also show unified inbox if it matches
|
|
272
|
+
const unified = tree.querySelector(".ft-unified");
|
|
273
|
+
if (unified) {
|
|
274
|
+
const text = unified.textContent?.toLowerCase() || "";
|
|
275
|
+
unified.classList.toggle("ft-filter-hidden", !text.includes(query));
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
ftFilterInput.addEventListener("keydown", (e) => {
|
|
279
|
+
if (e.key === "Escape") {
|
|
280
|
+
ftFilterInput.value = "";
|
|
281
|
+
ftFilterInput.dispatchEvent(new Event("input"));
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
}
|
|
223
285
|
// ── Open links from email body in system browser ──
|
|
224
286
|
window.addEventListener("message", (e) => {
|
|
225
287
|
if (e.data?.type === "openLink" && e.data.url) {
|
|
@@ -260,16 +322,20 @@ if (splitter) {
|
|
|
260
322
|
connectWebSocket();
|
|
261
323
|
onWsEvent((event) => {
|
|
262
324
|
const statusSync = document.getElementById("status-sync");
|
|
325
|
+
const startupStatus = document.getElementById("startup-status");
|
|
263
326
|
switch (event.type) {
|
|
264
327
|
case "connected":
|
|
265
328
|
if (statusSync)
|
|
266
329
|
statusSync.textContent = "Connected";
|
|
267
|
-
|
|
268
|
-
|
|
330
|
+
if (startupStatus)
|
|
331
|
+
startupStatus.textContent = "Loading accounts...";
|
|
332
|
+
// Don't refresh folder tree on connect — it's already loaded by initFolderTree
|
|
269
333
|
break;
|
|
270
334
|
case "syncProgress":
|
|
271
335
|
if (statusSync)
|
|
272
336
|
statusSync.textContent = `Sync: ${event.phase} ${event.progress}%`;
|
|
337
|
+
if (startupStatus)
|
|
338
|
+
startupStatus.textContent = `Syncing ${event.accountId}: ${event.phase}`;
|
|
273
339
|
break;
|
|
274
340
|
case "folderCountsChanged": {
|
|
275
341
|
refreshFolderTree();
|
|
@@ -402,7 +468,10 @@ fetch("/api/version").then(r => r.json()).then(d => {
|
|
|
402
468
|
el.textContent = `mailx s${d.server}/c${d.client}${isApp ? "" : " [browser]"}`;
|
|
403
469
|
}).catch(async () => {
|
|
404
470
|
// Server not running — try to start it if we're in the app
|
|
471
|
+
const startupStatus = document.getElementById("startup-status");
|
|
405
472
|
if (isApp) {
|
|
473
|
+
if (startupStatus)
|
|
474
|
+
startupStatus.textContent = "Starting server...";
|
|
406
475
|
await mailxapi.ensureServer();
|
|
407
476
|
location.reload();
|
|
408
477
|
}
|
|
@@ -410,6 +479,8 @@ fetch("/api/version").then(r => r.json()).then(d => {
|
|
|
410
479
|
const el = document.getElementById("app-version");
|
|
411
480
|
if (el)
|
|
412
481
|
el.textContent = "mailx [server offline]";
|
|
482
|
+
if (startupStatus)
|
|
483
|
+
startupStatus.textContent = "Server offline — start with: node packages/mailx-server/index.js";
|
|
413
484
|
}
|
|
414
485
|
});
|
|
415
486
|
// ── Sync pending indicator ──
|
|
@@ -427,5 +498,14 @@ setInterval(async () => {
|
|
|
427
498
|
}
|
|
428
499
|
catch { /* offline */ }
|
|
429
500
|
}, 5000);
|
|
430
|
-
console.log("mailx client initialized");
|
|
501
|
+
console.log("mailx client initialized, location:", location.href);
|
|
502
|
+
// Diagnostic: test API connectivity (helps debug WebView2 blank screen)
|
|
503
|
+
fetch("/api/version").then(r => r.json()).then(d => {
|
|
504
|
+
console.log("API reachable:", d);
|
|
505
|
+
}).catch(e => {
|
|
506
|
+
console.error("API unreachable:", e.message);
|
|
507
|
+
const status = document.getElementById("startup-status");
|
|
508
|
+
if (status)
|
|
509
|
+
status.textContent = `Cannot reach server API: ${e.message}`;
|
|
510
|
+
});
|
|
431
511
|
//# sourceMappingURL=app.js.map
|
|
@@ -123,7 +123,16 @@ function renderNode(node, container, depth) {
|
|
|
123
123
|
nameSpan.className = "ft-folder-name";
|
|
124
124
|
nameSpan.textContent = node.name;
|
|
125
125
|
folderEl.appendChild(nameSpan);
|
|
126
|
-
|
|
126
|
+
const isOutbox = node.specialUse === "outbox" || node.path.toLowerCase() === "outbox";
|
|
127
|
+
if (isOutbox && node.totalCount > 0) {
|
|
128
|
+
// Outbox: show total (pending) count with warning style
|
|
129
|
+
const badge = document.createElement("span");
|
|
130
|
+
badge.className = "ft-badge ft-badge-outbox";
|
|
131
|
+
badge.textContent = String(node.totalCount);
|
|
132
|
+
badge.title = `${node.totalCount} pending`;
|
|
133
|
+
folderEl.appendChild(badge);
|
|
134
|
+
}
|
|
135
|
+
else if (node.unreadCount > 0) {
|
|
127
136
|
const badge = document.createElement("span");
|
|
128
137
|
badge.className = "ft-badge";
|
|
129
138
|
badge.textContent = String(node.unreadCount);
|
|
@@ -147,6 +156,58 @@ function renderNode(node, container, depth) {
|
|
|
147
156
|
selectedFolderId = node.id;
|
|
148
157
|
onFolderSelect(node.accountId, node.id, node.name, node.specialUse || node.path.toLowerCase());
|
|
149
158
|
});
|
|
159
|
+
// ── Drop target for message drag-and-drop ──
|
|
160
|
+
if (node.id !== -1) {
|
|
161
|
+
folderEl.addEventListener("dragover", (e) => {
|
|
162
|
+
e.preventDefault();
|
|
163
|
+
e.dataTransfer.dropEffect = e.ctrlKey ? "copy" : "move";
|
|
164
|
+
folderEl.classList.add("drop-target");
|
|
165
|
+
});
|
|
166
|
+
folderEl.addEventListener("dragleave", () => folderEl.classList.remove("drop-target"));
|
|
167
|
+
folderEl.addEventListener("drop", async (e) => {
|
|
168
|
+
e.preventDefault();
|
|
169
|
+
folderEl.classList.remove("drop-target");
|
|
170
|
+
// Multi-message or single-message drop
|
|
171
|
+
const multiData = e.dataTransfer.getData("application/x-mailx-messages");
|
|
172
|
+
const singleData = e.dataTransfer.getData("application/x-mailx-message");
|
|
173
|
+
const messages = multiData ? JSON.parse(multiData) : singleData ? [JSON.parse(singleData)] : [];
|
|
174
|
+
// Filter: not already in target folder
|
|
175
|
+
const toMove = messages.filter(m => m.folderId !== node.id || m.accountId !== node.accountId);
|
|
176
|
+
if (toMove.length === 0)
|
|
177
|
+
return;
|
|
178
|
+
const statusEl = document.getElementById("status-sync");
|
|
179
|
+
const crossAccount = toMove.some(m => m.accountId !== node.accountId);
|
|
180
|
+
try {
|
|
181
|
+
let moved = 0;
|
|
182
|
+
for (const msg of toMove) {
|
|
183
|
+
const body = { targetFolderId: node.id };
|
|
184
|
+
if (msg.accountId !== node.accountId)
|
|
185
|
+
body.targetAccountId = node.accountId;
|
|
186
|
+
const res = await fetch(`/api/message/${msg.accountId}/${msg.uid}/move`, {
|
|
187
|
+
method: "POST",
|
|
188
|
+
headers: { "Content-Type": "application/json" },
|
|
189
|
+
body: JSON.stringify(body),
|
|
190
|
+
});
|
|
191
|
+
if (!res.ok) {
|
|
192
|
+
const err = await res.json().catch(() => ({ error: res.statusText }));
|
|
193
|
+
throw new Error(err.error);
|
|
194
|
+
}
|
|
195
|
+
moved++;
|
|
196
|
+
}
|
|
197
|
+
if (statusEl)
|
|
198
|
+
statusEl.textContent = `Moved ${moved} message${moved > 1 ? "s" : ""} to ${node.name}`;
|
|
199
|
+
const treeContainer = document.getElementById("folder-tree");
|
|
200
|
+
if (treeContainer)
|
|
201
|
+
loadFolderTree(treeContainer);
|
|
202
|
+
document.dispatchEvent(new CustomEvent("mailx-message-moved"));
|
|
203
|
+
}
|
|
204
|
+
catch (err) {
|
|
205
|
+
console.error(`Move failed: ${err.message}`);
|
|
206
|
+
if (statusEl)
|
|
207
|
+
statusEl.textContent = `Move failed: ${err.message}`;
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
}
|
|
150
211
|
container.appendChild(folderEl);
|
|
151
212
|
// Render children if expanded
|
|
152
213
|
if (hasChildren && isExpanded) {
|
|
@@ -161,13 +222,19 @@ export function initFolderTree(container, handler, unifiedHandler) {
|
|
|
161
222
|
loadFolderTree(container);
|
|
162
223
|
}
|
|
163
224
|
async function loadFolderTree(container) {
|
|
164
|
-
|
|
225
|
+
// Show loading state while preserving existing tree (if any) on refresh
|
|
226
|
+
const hadContent = container.children.length > 0 && !container.querySelector(".folder-loading");
|
|
227
|
+
if (!hadContent) {
|
|
228
|
+
container.innerHTML = `<div class="folder-loading">Loading accounts...</div>`;
|
|
229
|
+
}
|
|
165
230
|
try {
|
|
166
231
|
const accounts = await getAccounts();
|
|
167
232
|
if (accounts.length === 0) {
|
|
168
233
|
container.innerHTML = `<div class="folder-loading">No accounts configured.<br>Edit ~/.mailx/settings.jsonc</div>`;
|
|
169
234
|
return;
|
|
170
235
|
}
|
|
236
|
+
// Clear loading state now that we have data
|
|
237
|
+
container.innerHTML = "";
|
|
171
238
|
// Unified Inbox (if multiple accounts)
|
|
172
239
|
if (accounts.length > 1) {
|
|
173
240
|
const unifiedEl = document.createElement("div");
|
|
@@ -192,7 +259,7 @@ async function loadFolderTree(container) {
|
|
|
192
259
|
const accountExpanded = expandState[accountKey] !== false; // accounts default expanded
|
|
193
260
|
const header = document.createElement("div");
|
|
194
261
|
header.className = "ft-account-header";
|
|
195
|
-
header.textContent = `${accountExpanded ? "▾" : "▸"} ${account.name}`;
|
|
262
|
+
header.textContent = `${accountExpanded ? "▾" : "▸"} ${account.label || account.name}`;
|
|
196
263
|
header.addEventListener("click", () => {
|
|
197
264
|
expandState[accountKey] = !accountExpanded;
|
|
198
265
|
saveExpandState();
|
|
@@ -272,9 +339,23 @@ async function loadFolderTree(container) {
|
|
|
272
339
|
if (target)
|
|
273
340
|
target.click();
|
|
274
341
|
}
|
|
342
|
+
// Dismiss startup overlay once tree is loaded
|
|
343
|
+
const overlay = document.getElementById("startup-overlay");
|
|
344
|
+
if (overlay)
|
|
345
|
+
overlay.classList.add("hidden");
|
|
346
|
+
// Remove from DOM after transition
|
|
347
|
+
setTimeout(() => overlay?.remove(), 400);
|
|
275
348
|
}
|
|
276
349
|
catch (e) {
|
|
277
350
|
container.innerHTML = `<div class="folder-loading">Error loading folders: ${e.message}</div>`;
|
|
351
|
+
// Dismiss overlay on error too — show the error, not a spinner
|
|
352
|
+
const overlay = document.getElementById("startup-overlay");
|
|
353
|
+
if (overlay) {
|
|
354
|
+
const status = document.getElementById("startup-status");
|
|
355
|
+
if (status)
|
|
356
|
+
status.textContent = `Error: ${e.message}`;
|
|
357
|
+
setTimeout(() => { overlay.classList.add("hidden"); setTimeout(() => overlay.remove(), 400); }, 2000);
|
|
358
|
+
}
|
|
278
359
|
}
|
|
279
360
|
}
|
|
280
361
|
/** Refresh folder tree (e.g., after sync) */
|
|
@@ -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
|
}
|