@bobfrankston/mailx 1.0.349 → 1.0.351
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.
|
@@ -236,8 +236,13 @@ export async function openTasks() {
|
|
|
236
236
|
document.removeEventListener("keydown", onKey, true);
|
|
237
237
|
isOpen = false;
|
|
238
238
|
};
|
|
239
|
+
// Esc always closes. Earlier guard blocked Esc when focus was in the
|
|
240
|
+
// quickadd input, which made the panel feel stuck — user hits Esc to
|
|
241
|
+
// dismiss and nothing happens. Blur-on-Esc before close is the right
|
|
242
|
+
// behavior: the close handler is idempotent and the input loses focus
|
|
243
|
+
// when the backdrop goes away anyway.
|
|
239
244
|
const onKey = (e) => {
|
|
240
|
-
if (e.key === "Escape"
|
|
245
|
+
if (e.key === "Escape") {
|
|
241
246
|
e.stopPropagation();
|
|
242
247
|
e.preventDefault();
|
|
243
248
|
close();
|
|
@@ -595,32 +595,66 @@ document.getElementById("btn-send")?.addEventListener("click", () => {
|
|
|
595
595
|
return;
|
|
596
596
|
}
|
|
597
597
|
console.log(`[compose] Send clicked: from=${body.from} to=${JSON.stringify(body.to)} subject="${body.subject}" attachments=${body.attachments.length}`);
|
|
598
|
-
//
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
//
|
|
604
|
-
|
|
598
|
+
// Wait for the IPC round-trip before closing compose. The IPC is supposed
|
|
599
|
+
// to be <100ms (Node validates + disk-writes synchronously inside send()
|
|
600
|
+
// then returns). If it throws OR takes too long, the user keeps their
|
|
601
|
+
// typed message and sees the error inline instead of losing it to a
|
|
602
|
+
// fire-and-forget that never landed. Earlier fire-and-forget version lost
|
|
603
|
+
// messages silently when anything upstream of disk write broke.
|
|
604
|
+
const sendBtn = document.getElementById("btn-send");
|
|
605
|
+
if (sendBtn) {
|
|
606
|
+
sendBtn.disabled = true;
|
|
607
|
+
sendBtn.textContent = "Sending…";
|
|
608
|
+
}
|
|
609
|
+
const statusEl = document.getElementById("compose-status");
|
|
610
|
+
if (statusEl)
|
|
611
|
+
statusEl.textContent = "";
|
|
605
612
|
const sendStart = Date.now();
|
|
606
|
-
|
|
613
|
+
let ipcPromise;
|
|
614
|
+
try {
|
|
615
|
+
ipcPromise = sendMessage(body);
|
|
616
|
+
}
|
|
617
|
+
catch (e) {
|
|
618
|
+
const msg = e?.message || String(e);
|
|
619
|
+
console.error(`[compose] Send threw synchronously: ${msg}`);
|
|
620
|
+
if (sendBtn) {
|
|
621
|
+
sendBtn.disabled = false;
|
|
622
|
+
sendBtn.textContent = "Send";
|
|
623
|
+
}
|
|
624
|
+
if (statusEl)
|
|
625
|
+
statusEl.textContent = `Send failed: ${msg}`;
|
|
626
|
+
else
|
|
627
|
+
alert(`Send failed: ${msg}`);
|
|
628
|
+
return;
|
|
629
|
+
}
|
|
630
|
+
Promise.resolve(ipcPromise)
|
|
607
631
|
.then(() => {
|
|
608
632
|
console.log(`[compose] Send IPC returned OK in ${Date.now() - sendStart}ms`);
|
|
633
|
+
// Stop autosave only after ACK — if send threw we want the draft
|
|
634
|
+
// autosave to keep the message safe.
|
|
635
|
+
if (draftTimer) {
|
|
636
|
+
clearInterval(draftTimer);
|
|
637
|
+
draftTimer = null;
|
|
638
|
+
}
|
|
609
639
|
if (draftUid || draftId) {
|
|
610
640
|
deleteDraft(getFromAccountId(), draftUid || 0, draftId || "").catch(() => { });
|
|
611
641
|
}
|
|
642
|
+
closeCompose();
|
|
612
643
|
})
|
|
613
644
|
.catch((e) => {
|
|
614
645
|
const msg = e?.message || String(e);
|
|
615
646
|
console.error(`[compose] Send IPC failed after ${Date.now() - sendStart}ms: ${msg}`);
|
|
616
|
-
|
|
617
|
-
|
|
647
|
+
if (sendBtn) {
|
|
648
|
+
sendBtn.disabled = false;
|
|
649
|
+
sendBtn.textContent = "Send";
|
|
650
|
+
}
|
|
651
|
+
if (statusEl)
|
|
652
|
+
statusEl.textContent = `Send failed: ${msg}`;
|
|
618
653
|
try {
|
|
619
654
|
parent.postMessage({ type: "mailx-send-error", message: msg, accountId: body.from }, "*");
|
|
620
655
|
}
|
|
621
656
|
catch { /* */ }
|
|
622
657
|
});
|
|
623
|
-
closeCompose();
|
|
624
658
|
});
|
|
625
659
|
// ── Close handling ──
|
|
626
660
|
/** True if the compose has anything worth asking about. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bobfrankston/mailx",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.351",
|
|
4
4
|
"description": "Local-first email client with IMAP sync and standalone native app",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "bin/mailx.js",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"postinstall": "node bin/postinstall.js"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@bobfrankston/iflow-direct": "^0.1.
|
|
23
|
+
"@bobfrankston/iflow-direct": "^0.1.25",
|
|
24
24
|
"@bobfrankston/iflow-node": "^0.1.7",
|
|
25
25
|
"@bobfrankston/miscinfo": "^1.0.9",
|
|
26
26
|
"@bobfrankston/oauthsupport": "^1.0.24",
|
|
@@ -84,7 +84,7 @@
|
|
|
84
84
|
},
|
|
85
85
|
".transformedSnapshot": {
|
|
86
86
|
"dependencies": {
|
|
87
|
-
"@bobfrankston/iflow-direct": "^0.1.
|
|
87
|
+
"@bobfrankston/iflow-direct": "^0.1.25",
|
|
88
88
|
"@bobfrankston/iflow-node": "^0.1.7",
|
|
89
89
|
"@bobfrankston/miscinfo": "^1.0.9",
|
|
90
90
|
"@bobfrankston/oauthsupport": "^1.0.24",
|
|
@@ -2471,6 +2471,12 @@ export class ImapManager extends EventEmitter {
|
|
|
2471
2471
|
* work and risked double-send when both paths fired on the same message. */
|
|
2472
2472
|
queueOutgoingLocal(accountId, rawMessage) {
|
|
2473
2473
|
// Loud logging so a "vanished message" report is diagnosable from the log alone.
|
|
2474
|
+
// ALWAYS leave a backup copy in sending/<acct>/attempted/ first — unconditionally,
|
|
2475
|
+
// before the outbox write. The outbox .ltr may be claimed/consumed by the worker
|
|
2476
|
+
// within milliseconds; this copy survives regardless of SMTP success, IMAP
|
|
2477
|
+
// append, worker crash, or any other downstream failure. User asked for this
|
|
2478
|
+
// as a fallback because "there isn't even the backup copy in sent".
|
|
2479
|
+
this.saveSendingCopy(accountId, rawMessage, "attempted");
|
|
2474
2480
|
const outboxDir = path.join(getConfigDir(), "outbox", accountId);
|
|
2475
2481
|
try {
|
|
2476
2482
|
fs.mkdirSync(outboxDir, { recursive: true });
|