@bobfrankston/mailx-imap 0.1.32 → 0.1.34
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/index.d.ts +12 -1
- package/index.js +66 -9
- package/package.json +5 -5
package/index.d.ts
CHANGED
|
@@ -398,7 +398,18 @@ export declare class ImapManager extends EventEmitter {
|
|
|
398
398
|
moveMessage(accountId: string, uid: number, fromFolderId: number, toFolderId: number): Promise<void>;
|
|
399
399
|
/** Move message across accounts using iflow's moveMessageToServer */
|
|
400
400
|
moveMessageCrossAccount(fromAccountId: string, uid: number, fromFolderId: number, toAccountId: string, toFolderId: number): Promise<void>;
|
|
401
|
-
/** Undelete — move from Trash back to original folder
|
|
401
|
+
/** Undelete — move from Trash back to original folder. Local-first:
|
|
402
|
+
* the row was moved (not deleted) on trash, so we just move it back
|
|
403
|
+
* in the local DB and reconcile the IMAP queue. Two cases:
|
|
404
|
+
* (a) the to-trash MOVE is still pending — cancel it; the server
|
|
405
|
+
* never saw the delete, so no counter-action is needed.
|
|
406
|
+
* (b) the to-trash MOVE drained — the message is now in Trash on
|
|
407
|
+
* the server with a new uid. Queue a counter-move from
|
|
408
|
+
* trash → original. The IMAP processor's fetchByUid in trash
|
|
409
|
+
* will use the local membership uid (which the reconciler
|
|
410
|
+
* rebound to the server's new trash uid via Message-ID match).
|
|
411
|
+
* If reconcile hasn't run yet (unlikely race), action retries
|
|
412
|
+
* until it does. */
|
|
402
413
|
undeleteMessage(accountId: string, uid: number, originalFolderId: number): Promise<void>;
|
|
403
414
|
/** Update flags — local-first, queues IMAP sync */
|
|
404
415
|
updateFlagsLocal(accountId: string, uid: number, folderId: number, flags: string[]): Promise<void>;
|
package/index.js
CHANGED
|
@@ -943,6 +943,9 @@ export class ImapManager extends EventEmitter {
|
|
|
943
943
|
const parsed = await simpleParser(source);
|
|
944
944
|
// Coerce mailparser AddressObject(s) into the flat `{name, address}[]`
|
|
945
945
|
// shape storeMessages's downstream toEmailAddresses expects.
|
|
946
|
+
// RFC 2047 encoded-word decoding (incl. inside quoted-strings) is
|
|
947
|
+
// handled uniformly in db.upsertMessage, not per-callsite — see
|
|
948
|
+
// `decodeHeaderWords` there.
|
|
946
949
|
const flat = (a) => {
|
|
947
950
|
if (!a)
|
|
948
951
|
return [];
|
|
@@ -2696,12 +2699,24 @@ export class ImapManager extends EventEmitter {
|
|
|
2696
2699
|
if (messages.length === 0)
|
|
2697
2700
|
return;
|
|
2698
2701
|
const trash = this.findFolder(accountId, "trash");
|
|
2699
|
-
// Local first —
|
|
2702
|
+
// Local first — move to trash folder locally so the row stays
|
|
2703
|
+
// visible in Trash and Ctrl+Z can restore it. Body file stays in
|
|
2704
|
+
// its original folder dir; the next sync rebinds path on
|
|
2705
|
+
// membership uid change. Old behavior was `db.deleteMessage` +
|
|
2706
|
+
// `unlinkBodyFile` which made undelete impossible (no row to
|
|
2707
|
+
// restore, no body to read). For folders that ARE the trash
|
|
2708
|
+
// already, fall through to hard delete (the action will EXPUNGE
|
|
2709
|
+
// and reconciliation cleans up).
|
|
2700
2710
|
for (const msg of messages) {
|
|
2701
|
-
|
|
2702
|
-
|
|
2711
|
+
if (trash && trash.id !== msg.folderId) {
|
|
2712
|
+
this.db.moveMessageLocal(accountId, msg.uid, msg.folderId, trash.id);
|
|
2713
|
+
}
|
|
2714
|
+
else {
|
|
2715
|
+
this.unlinkBodyFile(accountId, msg.uid, msg.folderId).catch(() => { });
|
|
2716
|
+
this.db.deleteMessage(accountId, msg.uid, "user-initiated trash (already in trash → expunge)", "mailx-imap trashMessages");
|
|
2717
|
+
}
|
|
2703
2718
|
}
|
|
2704
|
-
console.log(`
|
|
2719
|
+
console.log(` Trashed ${messages.length} messages locally (moved to trash folder, body files retained)`);
|
|
2705
2720
|
// Queue IMAP actions
|
|
2706
2721
|
for (const msg of messages) {
|
|
2707
2722
|
if (trash && trash.id !== msg.folderId) {
|
|
@@ -2750,9 +2765,17 @@ export class ImapManager extends EventEmitter {
|
|
|
2750
2765
|
/** Move a message to Trash (delete) — local-first, queues IMAP sync */
|
|
2751
2766
|
async trashMessage(accountId, folderId, uid) {
|
|
2752
2767
|
const trash = this.findFolder(accountId, "trash");
|
|
2753
|
-
// Local first —
|
|
2754
|
-
|
|
2755
|
-
|
|
2768
|
+
// Local first — move to trash folder so the row stays visible in
|
|
2769
|
+
// Trash and Ctrl+Z can restore. Body file retained for undelete.
|
|
2770
|
+
// If we're already in trash (or no trash configured), fall through
|
|
2771
|
+
// to hard delete + EXPUNGE.
|
|
2772
|
+
if (trash && trash.id !== folderId) {
|
|
2773
|
+
this.db.moveMessageLocal(accountId, uid, folderId, trash.id);
|
|
2774
|
+
}
|
|
2775
|
+
else {
|
|
2776
|
+
this.unlinkBodyFile(accountId, uid, folderId).catch(() => { });
|
|
2777
|
+
this.db.deleteMessage(accountId, uid, "user-initiated trash (already in trash → expunge)", "mailx-imap trashMessage");
|
|
2778
|
+
}
|
|
2756
2779
|
// Queue IMAP action + log the resolution so "I deleted a message and
|
|
2757
2780
|
// now it's in neither trash nor deleted" is diagnosable from the log.
|
|
2758
2781
|
if (trash && trash.id !== folderId) {
|
|
@@ -2764,6 +2787,12 @@ export class ImapManager extends EventEmitter {
|
|
|
2764
2787
|
this.db.queueSyncAction(accountId, "delete", uid, folderId);
|
|
2765
2788
|
console.log(` [trash] ${accountId} UID ${uid}: queued EXPUNGE in folder ${folderId} (already in trash or no trash configured)`);
|
|
2766
2789
|
}
|
|
2790
|
+
// Folder counts moved — refresh both source and trash so the
|
|
2791
|
+
// tree badges update immediately, not at the next sync.
|
|
2792
|
+
this.db.recalcFolderCounts(folderId);
|
|
2793
|
+
if (trash && trash.id !== folderId)
|
|
2794
|
+
this.db.recalcFolderCounts(trash.id);
|
|
2795
|
+
this.emit("folderCountsChanged", accountId, {});
|
|
2767
2796
|
// Debounced sync — batches multiple deletes into one IMAP session
|
|
2768
2797
|
this.debounceSyncActions(accountId);
|
|
2769
2798
|
}
|
|
@@ -2799,12 +2828,40 @@ export class ImapManager extends EventEmitter {
|
|
|
2799
2828
|
});
|
|
2800
2829
|
});
|
|
2801
2830
|
}
|
|
2802
|
-
/** Undelete — move from Trash back to original folder
|
|
2831
|
+
/** Undelete — move from Trash back to original folder. Local-first:
|
|
2832
|
+
* the row was moved (not deleted) on trash, so we just move it back
|
|
2833
|
+
* in the local DB and reconcile the IMAP queue. Two cases:
|
|
2834
|
+
* (a) the to-trash MOVE is still pending — cancel it; the server
|
|
2835
|
+
* never saw the delete, so no counter-action is needed.
|
|
2836
|
+
* (b) the to-trash MOVE drained — the message is now in Trash on
|
|
2837
|
+
* the server with a new uid. Queue a counter-move from
|
|
2838
|
+
* trash → original. The IMAP processor's fetchByUid in trash
|
|
2839
|
+
* will use the local membership uid (which the reconciler
|
|
2840
|
+
* rebound to the server's new trash uid via Message-ID match).
|
|
2841
|
+
* If reconcile hasn't run yet (unlikely race), action retries
|
|
2842
|
+
* until it does. */
|
|
2803
2843
|
async undeleteMessage(accountId, uid, originalFolderId) {
|
|
2804
2844
|
const trash = this.findFolder(accountId, "trash");
|
|
2805
2845
|
if (!trash)
|
|
2806
2846
|
throw new Error("No Trash folder found");
|
|
2807
|
-
|
|
2847
|
+
// Move locally back to the original folder.
|
|
2848
|
+
const moved = this.db.moveMessageLocal(accountId, uid, trash.id, originalFolderId);
|
|
2849
|
+
if (!moved) {
|
|
2850
|
+
console.log(` [undelete] ${accountId} UID ${uid}: no row in trash — nothing to restore locally (sync may have already pruned)`);
|
|
2851
|
+
}
|
|
2852
|
+
// (a) cancel still-pending to-trash action.
|
|
2853
|
+
const pending = this.db.findPendingSyncAction(accountId, "move", uid, originalFolderId, trash.id);
|
|
2854
|
+
if (pending) {
|
|
2855
|
+
this.db.completeSyncAction(pending.id);
|
|
2856
|
+
console.log(` [undelete] ${accountId} UID ${uid}: cancelled pending MOVE to trash (server never saw delete)`);
|
|
2857
|
+
this.emit("folderCountsChanged", accountId, {});
|
|
2858
|
+
return;
|
|
2859
|
+
}
|
|
2860
|
+
// (b) queue counter-move from trash → original.
|
|
2861
|
+
this.db.queueSyncAction(accountId, "move", uid, trash.id, { targetFolderId: originalFolderId });
|
|
2862
|
+
console.log(` [undelete] ${accountId} UID ${uid}: queued counter-MOVE trash → folder ${originalFolderId}`);
|
|
2863
|
+
this.debounceSyncActions(accountId);
|
|
2864
|
+
this.emit("folderCountsChanged", accountId, {});
|
|
2808
2865
|
}
|
|
2809
2866
|
/** Update flags — local-first, queues IMAP sync */
|
|
2810
2867
|
async updateFlagsLocal(accountId, uid, folderId, flags) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bobfrankston/mailx-imap",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.34",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
"license": "ISC",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@bobfrankston/mailx-types": "^0.1.10",
|
|
13
|
-
"@bobfrankston/mailx-settings": "^0.1.
|
|
14
|
-
"@bobfrankston/mailx-store": "^0.1.
|
|
13
|
+
"@bobfrankston/mailx-settings": "^0.1.14",
|
|
14
|
+
"@bobfrankston/mailx-store": "^0.1.16",
|
|
15
15
|
"@bobfrankston/iflow-direct": "^0.1.39",
|
|
16
16
|
"@bobfrankston/tcp-transport": "^0.1.6",
|
|
17
17
|
"@bobfrankston/smtp-direct": "^0.1.8",
|
|
@@ -38,8 +38,8 @@
|
|
|
38
38
|
".transformedSnapshot": {
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"@bobfrankston/mailx-types": "^0.1.10",
|
|
41
|
-
"@bobfrankston/mailx-settings": "^0.1.
|
|
42
|
-
"@bobfrankston/mailx-store": "^0.1.
|
|
41
|
+
"@bobfrankston/mailx-settings": "^0.1.14",
|
|
42
|
+
"@bobfrankston/mailx-store": "^0.1.16",
|
|
43
43
|
"@bobfrankston/iflow-direct": "^0.1.39",
|
|
44
44
|
"@bobfrankston/tcp-transport": "^0.1.6",
|
|
45
45
|
"@bobfrankston/smtp-direct": "^0.1.8",
|