@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.
Files changed (172) hide show
  1. package/bin/mailx.js +52 -28
  2. package/client/app.js +113 -30
  3. package/client/components/folder-tree.js +84 -3
  4. package/client/components/message-list.js +164 -10
  5. package/client/components/message-viewer.js +130 -13
  6. package/client/compose/compose.html +4 -4
  7. package/client/compose/compose.js +53 -34
  8. package/client/index.html +50 -21
  9. package/client/lib/api-client.js +112 -31
  10. package/client/lib/mailxapi.js +123 -0
  11. package/client/package.json +1 -1
  12. package/client/styles/components.css +206 -16
  13. package/client/styles/layout.css +2 -1
  14. package/killmail.cmd +6 -0
  15. package/launch.ps1 +47 -5
  16. package/launcher/bin/mailx-app-linux +0 -0
  17. package/launcher/bin/mailx-app.exe +0 -0
  18. package/launcher/builder/build-config.json +11 -0
  19. package/launcher/builder/postinstall.js +81 -0
  20. package/package.json +2 -4
  21. package/packages/mailx-api/index.js +125 -29
  22. package/packages/mailx-core/index.d.ts +129 -0
  23. package/packages/mailx-core/index.js +323 -0
  24. package/packages/mailx-core/ipc.d.ts +13 -0
  25. package/packages/mailx-core/ipc.js +56 -0
  26. package/packages/mailx-core/package.json +18 -0
  27. package/packages/mailx-imap/index.d.ts +7 -1
  28. package/packages/mailx-imap/index.js +89 -14
  29. package/packages/mailx-server/index.js +42 -31
  30. package/packages/mailx-server/package.json +1 -2
  31. package/packages/mailx-settings/index.d.ts +1 -1
  32. package/packages/mailx-settings/index.js +21 -12
  33. package/packages/mailx-store/db.d.ts +6 -2
  34. package/packages/mailx-store/db.js +78 -16
  35. package/packages/mailx-store/file-store.d.ts +2 -8
  36. package/packages/mailx-store/file-store.js +7 -31
  37. package/packages/mailx-types/index.d.ts +3 -1
  38. package/.tswalk.json +0 -7396
  39. package/launcher/release.cmd +0 -4
  40. package/mailx.json +0 -9
  41. package/packages/mailx-api/node_modules/nodemailer/.ncurc.js +0 -9
  42. package/packages/mailx-api/node_modules/nodemailer/.prettierignore +0 -8
  43. package/packages/mailx-api/node_modules/nodemailer/.prettierrc +0 -12
  44. package/packages/mailx-api/node_modules/nodemailer/.prettierrc.js +0 -10
  45. package/packages/mailx-api/node_modules/nodemailer/.release-please-config.json +0 -9
  46. package/packages/mailx-api/node_modules/nodemailer/LICENSE +0 -16
  47. package/packages/mailx-api/node_modules/nodemailer/README.md +0 -86
  48. package/packages/mailx-api/node_modules/nodemailer/SECURITY.txt +0 -22
  49. package/packages/mailx-api/node_modules/nodemailer/eslint.config.js +0 -88
  50. package/packages/mailx-api/node_modules/nodemailer/lib/addressparser/index.js +0 -383
  51. package/packages/mailx-api/node_modules/nodemailer/lib/base64/index.js +0 -139
  52. package/packages/mailx-api/node_modules/nodemailer/lib/dkim/index.js +0 -253
  53. package/packages/mailx-api/node_modules/nodemailer/lib/dkim/message-parser.js +0 -155
  54. package/packages/mailx-api/node_modules/nodemailer/lib/dkim/relaxed-body.js +0 -154
  55. package/packages/mailx-api/node_modules/nodemailer/lib/dkim/sign.js +0 -117
  56. package/packages/mailx-api/node_modules/nodemailer/lib/fetch/cookies.js +0 -281
  57. package/packages/mailx-api/node_modules/nodemailer/lib/fetch/index.js +0 -280
  58. package/packages/mailx-api/node_modules/nodemailer/lib/json-transport/index.js +0 -82
  59. package/packages/mailx-api/node_modules/nodemailer/lib/mail-composer/index.js +0 -629
  60. package/packages/mailx-api/node_modules/nodemailer/lib/mailer/index.js +0 -441
  61. package/packages/mailx-api/node_modules/nodemailer/lib/mailer/mail-message.js +0 -316
  62. package/packages/mailx-api/node_modules/nodemailer/lib/mime-funcs/index.js +0 -625
  63. package/packages/mailx-api/node_modules/nodemailer/lib/mime-funcs/mime-types.js +0 -2113
  64. package/packages/mailx-api/node_modules/nodemailer/lib/mime-node/index.js +0 -1316
  65. package/packages/mailx-api/node_modules/nodemailer/lib/mime-node/last-newline.js +0 -33
  66. package/packages/mailx-api/node_modules/nodemailer/lib/mime-node/le-unix.js +0 -43
  67. package/packages/mailx-api/node_modules/nodemailer/lib/mime-node/le-windows.js +0 -52
  68. package/packages/mailx-api/node_modules/nodemailer/lib/nodemailer.js +0 -157
  69. package/packages/mailx-api/node_modules/nodemailer/lib/punycode/index.js +0 -460
  70. package/packages/mailx-api/node_modules/nodemailer/lib/qp/index.js +0 -227
  71. package/packages/mailx-api/node_modules/nodemailer/lib/sendmail-transport/index.js +0 -210
  72. package/packages/mailx-api/node_modules/nodemailer/lib/ses-transport/index.js +0 -234
  73. package/packages/mailx-api/node_modules/nodemailer/lib/shared/index.js +0 -754
  74. package/packages/mailx-api/node_modules/nodemailer/lib/smtp-connection/data-stream.js +0 -108
  75. package/packages/mailx-api/node_modules/nodemailer/lib/smtp-connection/http-proxy-client.js +0 -143
  76. package/packages/mailx-api/node_modules/nodemailer/lib/smtp-connection/index.js +0 -1870
  77. package/packages/mailx-api/node_modules/nodemailer/lib/smtp-pool/index.js +0 -652
  78. package/packages/mailx-api/node_modules/nodemailer/lib/smtp-pool/pool-resource.js +0 -259
  79. package/packages/mailx-api/node_modules/nodemailer/lib/smtp-transport/index.js +0 -421
  80. package/packages/mailx-api/node_modules/nodemailer/lib/stream-transport/index.js +0 -135
  81. package/packages/mailx-api/node_modules/nodemailer/lib/well-known/index.js +0 -47
  82. package/packages/mailx-api/node_modules/nodemailer/lib/well-known/services.json +0 -611
  83. package/packages/mailx-api/node_modules/nodemailer/lib/xoauth2/index.js +0 -427
  84. package/packages/mailx-api/node_modules/nodemailer/package.json +0 -47
  85. package/packages/mailx-imap/node_modules/nodemailer/.ncurc.js +0 -9
  86. package/packages/mailx-imap/node_modules/nodemailer/.prettierignore +0 -8
  87. package/packages/mailx-imap/node_modules/nodemailer/.prettierrc +0 -12
  88. package/packages/mailx-imap/node_modules/nodemailer/.prettierrc.js +0 -10
  89. package/packages/mailx-imap/node_modules/nodemailer/.release-please-config.json +0 -9
  90. package/packages/mailx-imap/node_modules/nodemailer/LICENSE +0 -16
  91. package/packages/mailx-imap/node_modules/nodemailer/README.md +0 -86
  92. package/packages/mailx-imap/node_modules/nodemailer/SECURITY.txt +0 -22
  93. package/packages/mailx-imap/node_modules/nodemailer/eslint.config.js +0 -88
  94. package/packages/mailx-imap/node_modules/nodemailer/lib/addressparser/index.js +0 -383
  95. package/packages/mailx-imap/node_modules/nodemailer/lib/base64/index.js +0 -139
  96. package/packages/mailx-imap/node_modules/nodemailer/lib/dkim/index.js +0 -253
  97. package/packages/mailx-imap/node_modules/nodemailer/lib/dkim/message-parser.js +0 -155
  98. package/packages/mailx-imap/node_modules/nodemailer/lib/dkim/relaxed-body.js +0 -154
  99. package/packages/mailx-imap/node_modules/nodemailer/lib/dkim/sign.js +0 -117
  100. package/packages/mailx-imap/node_modules/nodemailer/lib/fetch/cookies.js +0 -281
  101. package/packages/mailx-imap/node_modules/nodemailer/lib/fetch/index.js +0 -280
  102. package/packages/mailx-imap/node_modules/nodemailer/lib/json-transport/index.js +0 -82
  103. package/packages/mailx-imap/node_modules/nodemailer/lib/mail-composer/index.js +0 -629
  104. package/packages/mailx-imap/node_modules/nodemailer/lib/mailer/index.js +0 -441
  105. package/packages/mailx-imap/node_modules/nodemailer/lib/mailer/mail-message.js +0 -316
  106. package/packages/mailx-imap/node_modules/nodemailer/lib/mime-funcs/index.js +0 -625
  107. package/packages/mailx-imap/node_modules/nodemailer/lib/mime-funcs/mime-types.js +0 -2113
  108. package/packages/mailx-imap/node_modules/nodemailer/lib/mime-node/index.js +0 -1316
  109. package/packages/mailx-imap/node_modules/nodemailer/lib/mime-node/last-newline.js +0 -33
  110. package/packages/mailx-imap/node_modules/nodemailer/lib/mime-node/le-unix.js +0 -43
  111. package/packages/mailx-imap/node_modules/nodemailer/lib/mime-node/le-windows.js +0 -52
  112. package/packages/mailx-imap/node_modules/nodemailer/lib/nodemailer.js +0 -157
  113. package/packages/mailx-imap/node_modules/nodemailer/lib/punycode/index.js +0 -460
  114. package/packages/mailx-imap/node_modules/nodemailer/lib/qp/index.js +0 -227
  115. package/packages/mailx-imap/node_modules/nodemailer/lib/sendmail-transport/index.js +0 -210
  116. package/packages/mailx-imap/node_modules/nodemailer/lib/ses-transport/index.js +0 -234
  117. package/packages/mailx-imap/node_modules/nodemailer/lib/shared/index.js +0 -754
  118. package/packages/mailx-imap/node_modules/nodemailer/lib/smtp-connection/data-stream.js +0 -108
  119. package/packages/mailx-imap/node_modules/nodemailer/lib/smtp-connection/http-proxy-client.js +0 -143
  120. package/packages/mailx-imap/node_modules/nodemailer/lib/smtp-connection/index.js +0 -1870
  121. package/packages/mailx-imap/node_modules/nodemailer/lib/smtp-pool/index.js +0 -652
  122. package/packages/mailx-imap/node_modules/nodemailer/lib/smtp-pool/pool-resource.js +0 -259
  123. package/packages/mailx-imap/node_modules/nodemailer/lib/smtp-transport/index.js +0 -421
  124. package/packages/mailx-imap/node_modules/nodemailer/lib/stream-transport/index.js +0 -135
  125. package/packages/mailx-imap/node_modules/nodemailer/lib/well-known/index.js +0 -47
  126. package/packages/mailx-imap/node_modules/nodemailer/lib/well-known/services.json +0 -611
  127. package/packages/mailx-imap/node_modules/nodemailer/lib/xoauth2/index.js +0 -427
  128. package/packages/mailx-imap/node_modules/nodemailer/package.json +0 -47
  129. package/packages/mailx-send/node_modules/nodemailer/.ncurc.js +0 -9
  130. package/packages/mailx-send/node_modules/nodemailer/.prettierignore +0 -8
  131. package/packages/mailx-send/node_modules/nodemailer/.prettierrc +0 -12
  132. package/packages/mailx-send/node_modules/nodemailer/.prettierrc.js +0 -10
  133. package/packages/mailx-send/node_modules/nodemailer/.release-please-config.json +0 -9
  134. package/packages/mailx-send/node_modules/nodemailer/LICENSE +0 -16
  135. package/packages/mailx-send/node_modules/nodemailer/README.md +0 -86
  136. package/packages/mailx-send/node_modules/nodemailer/SECURITY.txt +0 -22
  137. package/packages/mailx-send/node_modules/nodemailer/eslint.config.js +0 -88
  138. package/packages/mailx-send/node_modules/nodemailer/lib/addressparser/index.js +0 -383
  139. package/packages/mailx-send/node_modules/nodemailer/lib/base64/index.js +0 -139
  140. package/packages/mailx-send/node_modules/nodemailer/lib/dkim/index.js +0 -253
  141. package/packages/mailx-send/node_modules/nodemailer/lib/dkim/message-parser.js +0 -155
  142. package/packages/mailx-send/node_modules/nodemailer/lib/dkim/relaxed-body.js +0 -154
  143. package/packages/mailx-send/node_modules/nodemailer/lib/dkim/sign.js +0 -117
  144. package/packages/mailx-send/node_modules/nodemailer/lib/fetch/cookies.js +0 -281
  145. package/packages/mailx-send/node_modules/nodemailer/lib/fetch/index.js +0 -280
  146. package/packages/mailx-send/node_modules/nodemailer/lib/json-transport/index.js +0 -82
  147. package/packages/mailx-send/node_modules/nodemailer/lib/mail-composer/index.js +0 -629
  148. package/packages/mailx-send/node_modules/nodemailer/lib/mailer/index.js +0 -441
  149. package/packages/mailx-send/node_modules/nodemailer/lib/mailer/mail-message.js +0 -316
  150. package/packages/mailx-send/node_modules/nodemailer/lib/mime-funcs/index.js +0 -625
  151. package/packages/mailx-send/node_modules/nodemailer/lib/mime-funcs/mime-types.js +0 -2113
  152. package/packages/mailx-send/node_modules/nodemailer/lib/mime-node/index.js +0 -1316
  153. package/packages/mailx-send/node_modules/nodemailer/lib/mime-node/last-newline.js +0 -33
  154. package/packages/mailx-send/node_modules/nodemailer/lib/mime-node/le-unix.js +0 -43
  155. package/packages/mailx-send/node_modules/nodemailer/lib/mime-node/le-windows.js +0 -52
  156. package/packages/mailx-send/node_modules/nodemailer/lib/nodemailer.js +0 -157
  157. package/packages/mailx-send/node_modules/nodemailer/lib/punycode/index.js +0 -460
  158. package/packages/mailx-send/node_modules/nodemailer/lib/qp/index.js +0 -227
  159. package/packages/mailx-send/node_modules/nodemailer/lib/sendmail-transport/index.js +0 -210
  160. package/packages/mailx-send/node_modules/nodemailer/lib/ses-transport/index.js +0 -234
  161. package/packages/mailx-send/node_modules/nodemailer/lib/shared/index.js +0 -754
  162. package/packages/mailx-send/node_modules/nodemailer/lib/smtp-connection/data-stream.js +0 -108
  163. package/packages/mailx-send/node_modules/nodemailer/lib/smtp-connection/http-proxy-client.js +0 -143
  164. package/packages/mailx-send/node_modules/nodemailer/lib/smtp-connection/index.js +0 -1870
  165. package/packages/mailx-send/node_modules/nodemailer/lib/smtp-pool/index.js +0 -652
  166. package/packages/mailx-send/node_modules/nodemailer/lib/smtp-pool/pool-resource.js +0 -259
  167. package/packages/mailx-send/node_modules/nodemailer/lib/smtp-transport/index.js +0 -421
  168. package/packages/mailx-send/node_modules/nodemailer/lib/stream-transport/index.js +0 -135
  169. package/packages/mailx-send/node_modules/nodemailer/lib/well-known/index.js +0 -47
  170. package/packages/mailx-send/node_modules/nodemailer/lib/well-known/services.json +0 -611
  171. package/packages/mailx-send/node_modules/nodemailer/lib/xoauth2/index.js +0 -427
  172. 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 launcher
3
+ * mailx — email client
4
4
  *
5
5
  * Usage:
6
- * mailx Start unified (in-process server + open browser)
7
- * mailx --server Start as a standalone server only (no browser)
8
- * mailx --no-browser Start server in-process, don't open browser
9
- * mailx --external Bind to all interfaces (default: localhost only)
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 server = net.createServer();
20
- server.once("error", () => resolve(true));
21
- server.once("listening", () => { server.close(); resolve(false); });
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
- const serverOnly = args.includes("--server");
36
- const noBrowser = args.includes("--no-browser") || serverOnly;
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
- return;
46
- }
47
-
48
- console.log("Starting mailx...");
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
- if (args.includes("--external")) process.argv.push("--external");
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
- // Import server directly relative path, in-process
53
- await import("../packages/mailx-server/index.js");
72
+ let launcherPath = launcherPaths.find(p => fs.existsSync(p));
54
73
 
55
- if (!noBrowser) {
56
- for (let i = 0; i < 30; i++) {
57
- await new Promise(r => setTimeout(r, 200));
58
- if (await isPortInUse(PORT)) break;
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
- // Clear the preview pane
148
- const bodyEl = document.getElementById("mv-body");
149
- const headerEl = document.getElementById("mv-header");
150
- if (bodyEl)
151
- bodyEl.innerHTML = `<div class="mv-empty">Select a message to read</div>`;
152
- if (headerEl)
153
- headerEl.hidden = true;
154
- reloadCurrentFolder();
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
- const query = searchInput.value.trim();
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
- // Refresh folder counts on reconnect (non-disruptive)
268
- refreshFolderTree();
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
- if (node.unreadCount > 0) {
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
- container.innerHTML = "";
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) */