@bobfrankston/mailx 1.0.12 → 1.0.14
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 +52 -28
- package/client/app.js +113 -30
- package/client/components/folder-tree.js +84 -3
- package/client/components/message-list.js +164 -10
- 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 +50 -21
- package/client/lib/api-client.js +112 -31
- package/client/lib/mailxapi.js +123 -0
- package/client/package.json +1 -1
- package/client/styles/components.css +206 -16
- package/client/styles/layout.css +2 -1
- package/killmail.cmd +6 -0
- package/launch.ps1 +47 -5
- package/launcher/bin/mailx-app-linux +0 -0
- package/launcher/bin/mailx-app.exe +0 -0
- package/launcher/builder/build-config.json +11 -0
- package/launcher/builder/postinstall.js +81 -0
- package/package.json +2 -4
- package/packages/mailx-api/index.js +125 -29
- 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 +7 -1
- package/packages/mailx-imap/index.js +89 -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 +6 -2
- package/packages/mailx-store/db.js +78 -16
- 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,54 @@ 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
|
-
|
|
59
|
+
} else {
|
|
60
|
+
// Default: launch native WebView app with IPC
|
|
61
|
+
// Platform-specific binary naming (matches msger pattern)
|
|
62
|
+
let binaryName;
|
|
63
|
+
if (process.platform === "win32") binaryName = "mailx-app.exe";
|
|
64
|
+
else if (process.platform === "darwin") binaryName = process.arch === "arm64" ? "mailx-app-arm64" : "mailx-app";
|
|
65
|
+
else binaryName = process.arch === "arm64" ? "mailx-app-linux-aarch64" : "mailx-app-linux";
|
|
49
66
|
|
|
50
|
-
|
|
67
|
+
const launcherPaths = [
|
|
68
|
+
path.join(import.meta.dirname, "..", "launcher", "bin", binaryName),
|
|
69
|
+
path.join(import.meta.dirname, "..", "launcher", "target", "release", binaryName),
|
|
70
|
+
];
|
|
51
71
|
|
|
52
|
-
|
|
53
|
-
await import("../packages/mailx-server/index.js");
|
|
72
|
+
let launcherPath = launcherPaths.find(p => fs.existsSync(p));
|
|
54
73
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
74
|
+
if (launcherPath) {
|
|
75
|
+
console.log("Starting mailx...");
|
|
76
|
+
const { spawn } = await import("node:child_process");
|
|
77
|
+
const child = spawn(launcherPath, args, { detached: true, stdio: "ignore" });
|
|
78
|
+
child.unref();
|
|
79
|
+
console.log("mailx launched");
|
|
80
|
+
} else {
|
|
81
|
+
// No native launcher — fall back to server mode + browser
|
|
82
|
+
console.log("Native launcher not found, falling back to server mode");
|
|
83
|
+
process.argv.push("--server");
|
|
84
|
+
await main(); // recurse with --server
|
|
59
85
|
}
|
|
60
|
-
openBrowser(`http://localhost:${PORT}`);
|
|
61
|
-
console.log("mailx opened");
|
|
62
86
|
}
|
|
63
87
|
}
|
|
64
88
|
|
package/client/app.js
CHANGED
|
@@ -8,16 +8,21 @@ 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;
|
|
14
|
+
currentAccountId = accountId;
|
|
15
|
+
currentFolderId = folderId;
|
|
12
16
|
loadMessages(accountId, folderId, 1, specialUse);
|
|
13
17
|
document.title = `mailx - ${folderName}`;
|
|
14
18
|
}, () => {
|
|
15
19
|
// Unified inbox handler
|
|
20
|
+
currentFolderSpecialUse = "inbox";
|
|
16
21
|
loadUnifiedInbox();
|
|
17
22
|
document.title = "mailx - All Inboxes";
|
|
18
23
|
});
|
|
19
|
-
initMessageList((accountId, uid) => {
|
|
20
|
-
showMessage(accountId, uid);
|
|
24
|
+
initMessageList((accountId, uid, folderId) => {
|
|
25
|
+
showMessage(accountId, uid, folderId, currentFolderSpecialUse);
|
|
21
26
|
// Enable action buttons when a message is selected
|
|
22
27
|
for (const id of ["btn-reply", "btn-reply-all", "btn-forward", "btn-delete", "btn-flag"]) {
|
|
23
28
|
const btn = document.getElementById(id);
|
|
@@ -144,14 +149,27 @@ async function deleteCurrentMessage() {
|
|
|
144
149
|
if (statusSync?.textContent?.includes("Ctrl+Z"))
|
|
145
150
|
statusSync.textContent = "";
|
|
146
151
|
}, 30000);
|
|
147
|
-
//
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
152
|
+
// Remove the deleted row and select the next message
|
|
153
|
+
const mlBody = document.getElementById("ml-body");
|
|
154
|
+
if (mlBody) {
|
|
155
|
+
const deletedRow = mlBody.querySelector(`.ml-row[data-uid="${message.uid}"]`);
|
|
156
|
+
if (deletedRow) {
|
|
157
|
+
const nextRow = (deletedRow.nextElementSibling || deletedRow.previousElementSibling);
|
|
158
|
+
deletedRow.remove();
|
|
159
|
+
if (nextRow?.classList.contains("ml-row")) {
|
|
160
|
+
nextRow.click();
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
// No more messages — clear preview
|
|
164
|
+
const bodyEl = document.getElementById("mv-body");
|
|
165
|
+
const headerEl = document.getElementById("mv-header");
|
|
166
|
+
if (bodyEl)
|
|
167
|
+
bodyEl.innerHTML = `<div class="mv-empty">Select a message to read</div>`;
|
|
168
|
+
if (headerEl)
|
|
169
|
+
headerEl.hidden = true;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
155
173
|
}
|
|
156
174
|
catch (e) {
|
|
157
175
|
console.error(`Delete failed: ${e.message}`);
|
|
@@ -191,35 +209,82 @@ document.getElementById("btn-forward")?.addEventListener("click", () => openComp
|
|
|
191
209
|
// ── Search ──
|
|
192
210
|
let searchTimeout;
|
|
193
211
|
const searchInput = document.getElementById("search-input");
|
|
212
|
+
const searchScope = document.getElementById("search-scope");
|
|
213
|
+
function doSearch() {
|
|
214
|
+
const query = searchInput.value.trim();
|
|
215
|
+
if (query.length < 2) {
|
|
216
|
+
if (query.length === 0)
|
|
217
|
+
reloadCurrentFolder();
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
const scope = searchScope?.value || "all";
|
|
221
|
+
// For "current" and "server" scopes, pass the active folder context
|
|
222
|
+
loadSearchResults(query, scope, currentAccountId, currentFolderId);
|
|
223
|
+
document.title = `mailx - Search: ${query}`;
|
|
224
|
+
}
|
|
225
|
+
// Track current folder for scoped search
|
|
226
|
+
let currentAccountId = "";
|
|
227
|
+
let currentFolderId = 0;
|
|
194
228
|
searchInput?.addEventListener("input", () => {
|
|
195
229
|
clearTimeout(searchTimeout);
|
|
196
|
-
searchTimeout = setTimeout(
|
|
197
|
-
const query = searchInput.value.trim();
|
|
198
|
-
if (query.length >= 2) {
|
|
199
|
-
loadSearchResults(query);
|
|
200
|
-
document.title = `mailx - Search: ${query}`;
|
|
201
|
-
}
|
|
202
|
-
else if (query.length === 0) {
|
|
203
|
-
// Clear search — reload current folder
|
|
204
|
-
reloadCurrentFolder();
|
|
205
|
-
}
|
|
206
|
-
}, 400);
|
|
230
|
+
searchTimeout = setTimeout(doSearch, 400);
|
|
207
231
|
});
|
|
208
|
-
// Enter triggers immediate search
|
|
209
232
|
searchInput?.addEventListener("keydown", (e) => {
|
|
210
233
|
if (e.key === "Enter") {
|
|
211
234
|
clearTimeout(searchTimeout);
|
|
212
|
-
|
|
213
|
-
if (query) {
|
|
214
|
-
loadSearchResults(query);
|
|
215
|
-
document.title = `mailx - Search: ${query}`;
|
|
216
|
-
}
|
|
235
|
+
doSearch();
|
|
217
236
|
}
|
|
218
237
|
if (e.key === "Escape") {
|
|
219
238
|
searchInput.value = "";
|
|
220
239
|
reloadCurrentFolder();
|
|
221
240
|
}
|
|
222
241
|
});
|
|
242
|
+
// ── Reload message list after drag-move ──
|
|
243
|
+
document.addEventListener("mailx-message-moved", () => reloadCurrentFolder());
|
|
244
|
+
// ── Folder filter ──
|
|
245
|
+
const ftFilterInput = document.getElementById("ft-filter-input");
|
|
246
|
+
if (ftFilterInput) {
|
|
247
|
+
ftFilterInput.addEventListener("input", () => {
|
|
248
|
+
const query = ftFilterInput.value.toLowerCase();
|
|
249
|
+
const tree = document.getElementById("folder-tree");
|
|
250
|
+
if (!tree)
|
|
251
|
+
return;
|
|
252
|
+
if (!query) {
|
|
253
|
+
// Clear filter — show everything
|
|
254
|
+
tree.querySelectorAll(".ft-filter-hidden").forEach(el => el.classList.remove("ft-filter-hidden"));
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
// Hide all folders first, then show matches + their parent accounts
|
|
258
|
+
const folders = tree.querySelectorAll(".ft-folder");
|
|
259
|
+
const accounts = tree.querySelectorAll(".ft-account");
|
|
260
|
+
for (const acct of accounts)
|
|
261
|
+
acct.classList.add("ft-filter-hidden");
|
|
262
|
+
for (const f of folders)
|
|
263
|
+
f.classList.add("ft-filter-hidden");
|
|
264
|
+
for (const f of folders) {
|
|
265
|
+
const name = f.querySelector(".ft-folder-name")?.textContent?.toLowerCase() || "";
|
|
266
|
+
if (name.includes(query)) {
|
|
267
|
+
f.classList.remove("ft-filter-hidden");
|
|
268
|
+
// Show parent account
|
|
269
|
+
const acct = f.closest(".ft-account");
|
|
270
|
+
if (acct)
|
|
271
|
+
acct.classList.remove("ft-filter-hidden");
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
// Also show unified inbox if it matches
|
|
275
|
+
const unified = tree.querySelector(".ft-unified");
|
|
276
|
+
if (unified) {
|
|
277
|
+
const text = unified.textContent?.toLowerCase() || "";
|
|
278
|
+
unified.classList.toggle("ft-filter-hidden", !text.includes(query));
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
ftFilterInput.addEventListener("keydown", (e) => {
|
|
282
|
+
if (e.key === "Escape") {
|
|
283
|
+
ftFilterInput.value = "";
|
|
284
|
+
ftFilterInput.dispatchEvent(new Event("input"));
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
}
|
|
223
288
|
// ── Open links from email body in system browser ──
|
|
224
289
|
window.addEventListener("message", (e) => {
|
|
225
290
|
if (e.data?.type === "openLink" && e.data.url) {
|
|
@@ -260,16 +325,20 @@ if (splitter) {
|
|
|
260
325
|
connectWebSocket();
|
|
261
326
|
onWsEvent((event) => {
|
|
262
327
|
const statusSync = document.getElementById("status-sync");
|
|
328
|
+
const startupStatus = document.getElementById("startup-status");
|
|
263
329
|
switch (event.type) {
|
|
264
330
|
case "connected":
|
|
265
331
|
if (statusSync)
|
|
266
332
|
statusSync.textContent = "Connected";
|
|
267
|
-
|
|
268
|
-
|
|
333
|
+
if (startupStatus)
|
|
334
|
+
startupStatus.textContent = "Loading accounts...";
|
|
335
|
+
// Don't refresh folder tree on connect — it's already loaded by initFolderTree
|
|
269
336
|
break;
|
|
270
337
|
case "syncProgress":
|
|
271
338
|
if (statusSync)
|
|
272
339
|
statusSync.textContent = `Sync: ${event.phase} ${event.progress}%`;
|
|
340
|
+
if (startupStatus)
|
|
341
|
+
startupStatus.textContent = `Syncing ${event.accountId}: ${event.phase}`;
|
|
273
342
|
break;
|
|
274
343
|
case "folderCountsChanged": {
|
|
275
344
|
refreshFolderTree();
|
|
@@ -402,7 +471,10 @@ fetch("/api/version").then(r => r.json()).then(d => {
|
|
|
402
471
|
el.textContent = `mailx s${d.server}/c${d.client}${isApp ? "" : " [browser]"}`;
|
|
403
472
|
}).catch(async () => {
|
|
404
473
|
// Server not running — try to start it if we're in the app
|
|
474
|
+
const startupStatus = document.getElementById("startup-status");
|
|
405
475
|
if (isApp) {
|
|
476
|
+
if (startupStatus)
|
|
477
|
+
startupStatus.textContent = "Starting server...";
|
|
406
478
|
await mailxapi.ensureServer();
|
|
407
479
|
location.reload();
|
|
408
480
|
}
|
|
@@ -410,6 +482,8 @@ fetch("/api/version").then(r => r.json()).then(d => {
|
|
|
410
482
|
const el = document.getElementById("app-version");
|
|
411
483
|
if (el)
|
|
412
484
|
el.textContent = "mailx [server offline]";
|
|
485
|
+
if (startupStatus)
|
|
486
|
+
startupStatus.textContent = "Server offline — start with: node packages/mailx-server/index.js";
|
|
413
487
|
}
|
|
414
488
|
});
|
|
415
489
|
// ── Sync pending indicator ──
|
|
@@ -427,5 +501,14 @@ setInterval(async () => {
|
|
|
427
501
|
}
|
|
428
502
|
catch { /* offline */ }
|
|
429
503
|
}, 5000);
|
|
430
|
-
console.log("mailx client initialized");
|
|
504
|
+
console.log("mailx client initialized, location:", location.href);
|
|
505
|
+
// Diagnostic: test API connectivity (helps debug WebView2 blank screen)
|
|
506
|
+
fetch("/api/version").then(r => r.json()).then(d => {
|
|
507
|
+
console.log("API reachable:", d);
|
|
508
|
+
}).catch(e => {
|
|
509
|
+
console.error("API unreachable:", e.message);
|
|
510
|
+
const status = document.getElementById("startup-status");
|
|
511
|
+
if (status)
|
|
512
|
+
status.textContent = `Cannot reach server API: ${e.message}`;
|
|
513
|
+
});
|
|
431
514
|
//# 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) */
|