@remit/web-client 0.0.61 → 0.0.62
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remit/web-client",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.62",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Remit web client, published as composable primitives — the app shell, auth shells, and runtime config. A distributor imports what it composes and bundles it.",
|
|
6
6
|
"exports": {
|
|
@@ -8,7 +8,7 @@ interface AutoMovedIndicatorProps {
|
|
|
8
8
|
threadId: string;
|
|
9
9
|
mailboxId: string;
|
|
10
10
|
autoMoved: RemitImapAutoMovedInfo | undefined;
|
|
11
|
-
/** `md` (reading view) adds the inline Undo
|
|
11
|
+
/** `md` (reading view) adds the inline actions (Undo, and the Manage-filter link for a filter move); `sm` (list row) shows the icon + label. */
|
|
12
12
|
size?: "sm" | "md";
|
|
13
13
|
}
|
|
14
14
|
|
|
@@ -43,6 +43,7 @@ export function AutoMovedIndicator({
|
|
|
43
43
|
size={size}
|
|
44
44
|
onUndo={badge.onUndo}
|
|
45
45
|
undoLabel={badge.isUndoing ? "Undoing…" : "Undo"}
|
|
46
|
+
filtersHref={size === "md" ? badge.filtersHref : undefined}
|
|
46
47
|
/>
|
|
47
48
|
);
|
|
48
49
|
}
|
|
@@ -1,10 +1,13 @@
|
|
|
1
|
+
import { mailboxOperationsListMailboxesOptions } from "@remit/api-http-client/@tanstack/react-query.gen.ts";
|
|
1
2
|
import type { RemitImapAutoMovedInfo } from "@remit/api-http-client/types.gen.ts";
|
|
3
|
+
import { useQuery } from "@tanstack/react-query";
|
|
2
4
|
import { useCallback } from "react";
|
|
3
5
|
import {
|
|
4
6
|
autoMovedLabel,
|
|
5
7
|
isAutoMoveInEffect,
|
|
6
8
|
resolveUndoTargetMailboxId,
|
|
7
9
|
} from "@/lib/auto-moved";
|
|
10
|
+
import { getMailboxDisplayName } from "@/lib/folder-roles";
|
|
8
11
|
import { useInboxMailbox, useJunkMailbox } from "./useArchiveMailbox";
|
|
9
12
|
import { useMoveMessages } from "./useMoveMessages";
|
|
10
13
|
|
|
@@ -24,14 +27,26 @@ export interface AutoMovedBadgeState {
|
|
|
24
27
|
/** Present only when the undo target mailbox resolved. */
|
|
25
28
|
onUndo?: () => void;
|
|
26
29
|
isUndoing: boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Settings › Filters link, present only for a standing-filter move — undo
|
|
32
|
+
* returns the message but never disables the filter, so the badge points to
|
|
33
|
+
* where the filter can be managed.
|
|
34
|
+
*/
|
|
35
|
+
filtersHref?: string;
|
|
27
36
|
}
|
|
28
37
|
|
|
29
38
|
/**
|
|
30
|
-
* Composes the account's
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
39
|
+
* Composes the account's mailboxes with the message's `autoMoved` projection
|
|
40
|
+
* into everything the `AutoMovedBadge` kit component needs: the derived "still
|
|
41
|
+
* in effect" gate, the plain-language label, and a one-click undo bound to the
|
|
42
|
+
* existing `moveMessages` mutation (no new endpoint — moves back through the
|
|
43
|
+
* same bulk move operation, the other direction).
|
|
44
|
+
*
|
|
45
|
+
* Both auto-move shapes are handled. A classifier move resolves its
|
|
46
|
+
* Inbox/Junk role mailboxes; a standing-filter move names an arbitrary source
|
|
47
|
+
* folder, whose display name is resolved from the account's mailbox list, and
|
|
48
|
+
* carries a Settings › Filters link so the filter that keeps moving mail is one
|
|
49
|
+
* tap away — undo does not disable it.
|
|
35
50
|
*
|
|
36
51
|
* `show` re-derives on every render from `mailboxId` — no local dismissed
|
|
37
52
|
* flag. Once `moveMessages` settles, its query invalidation refetches the
|
|
@@ -53,11 +68,20 @@ export const useAutoMovedBadge = ({
|
|
|
53
68
|
accountId,
|
|
54
69
|
});
|
|
55
70
|
|
|
71
|
+
const { data: mailboxes } = useQuery({
|
|
72
|
+
...mailboxOperationsListMailboxesOptions({
|
|
73
|
+
path: { accountId: accountId ?? "" },
|
|
74
|
+
}),
|
|
75
|
+
staleTime: Infinity,
|
|
76
|
+
enabled: !!accountId && autoMoved?.fromMailboxId !== undefined,
|
|
77
|
+
});
|
|
78
|
+
|
|
56
79
|
const roleMailboxes = { inboxMailboxId, junkMailboxId };
|
|
57
80
|
const show = isAutoMoveInEffect(autoMoved, mailboxId, roleMailboxes);
|
|
58
|
-
const undoTargetMailboxId =
|
|
59
|
-
|
|
60
|
-
|
|
81
|
+
const undoTargetMailboxId = resolveUndoTargetMailboxId(
|
|
82
|
+
autoMoved,
|
|
83
|
+
roleMailboxes,
|
|
84
|
+
);
|
|
61
85
|
|
|
62
86
|
const handleUndo = useCallback(() => {
|
|
63
87
|
if (!undoTargetMailboxId) return;
|
|
@@ -68,10 +92,17 @@ export const useAutoMovedBadge = ({
|
|
|
68
92
|
return { show: false, label: "", isUndoing: false };
|
|
69
93
|
}
|
|
70
94
|
|
|
95
|
+
const sourceFolderName = autoMoved.fromMailboxId
|
|
96
|
+
? mailboxes?.items
|
|
97
|
+
.filter((mailbox) => mailbox.mailboxId === autoMoved.fromMailboxId)
|
|
98
|
+
.map((mailbox) => getMailboxDisplayName(mailbox.fullPath))[0]
|
|
99
|
+
: undefined;
|
|
100
|
+
|
|
71
101
|
return {
|
|
72
102
|
show: true,
|
|
73
|
-
label: autoMovedLabel(autoMoved
|
|
103
|
+
label: autoMovedLabel(autoMoved, sourceFolderName),
|
|
74
104
|
onUndo: undoTargetMailboxId ? handleUndo : undefined,
|
|
75
105
|
isUndoing: isPending,
|
|
106
|
+
...(autoMoved.filterId ? { filtersHref: "/settings/filters" } : {}),
|
|
76
107
|
};
|
|
77
108
|
};
|
|
@@ -14,23 +14,65 @@ const ROLE_MAILBOXES: AutoMovedRoleMailboxes = {
|
|
|
14
14
|
junkMailboxId: "mb-junk",
|
|
15
15
|
};
|
|
16
16
|
|
|
17
|
+
const classifierMove = (
|
|
18
|
+
action: RemitImapAutoMovedInfo["action"],
|
|
19
|
+
fromPlacement: string,
|
|
20
|
+
): RemitImapAutoMovedInfo => ({ action, fromPlacement });
|
|
21
|
+
|
|
22
|
+
const filterMove = (
|
|
23
|
+
fromMailboxId: string,
|
|
24
|
+
destinationMailboxId: string,
|
|
25
|
+
): RemitImapAutoMovedInfo => ({
|
|
26
|
+
fromMailboxId,
|
|
27
|
+
destinationMailboxId,
|
|
28
|
+
filterId: "flt-1",
|
|
29
|
+
});
|
|
30
|
+
|
|
17
31
|
describe("autoMovedLabel", () => {
|
|
18
|
-
test("reads 'Moved from Junk by Remit'", () => {
|
|
19
|
-
assert.equal(
|
|
32
|
+
test("classifier move reads 'Moved from Junk by Remit'", () => {
|
|
33
|
+
assert.equal(
|
|
34
|
+
autoMovedLabel(classifierMove(PlacementAction.MoveToInbox, "junk")),
|
|
35
|
+
"Moved from Junk by Remit",
|
|
36
|
+
);
|
|
20
37
|
});
|
|
21
38
|
|
|
22
|
-
test("reads 'Moved from Inbox by Remit'", () => {
|
|
23
|
-
assert.equal(
|
|
39
|
+
test("classifier move reads 'Moved from Inbox by Remit'", () => {
|
|
40
|
+
assert.equal(
|
|
41
|
+
autoMovedLabel(classifierMove(PlacementAction.MoveToJunk, "inbox")),
|
|
42
|
+
"Moved from Inbox by Remit",
|
|
43
|
+
);
|
|
24
44
|
});
|
|
25
45
|
|
|
26
|
-
test("falls back to plain language for an unrecognized placement", () => {
|
|
27
|
-
assert.equal(
|
|
46
|
+
test("classifier move falls back to plain language for an unrecognized placement", () => {
|
|
47
|
+
assert.equal(
|
|
48
|
+
autoMovedLabel(classifierMove(PlacementAction.MoveToInbox, "other")),
|
|
49
|
+
"Moved from another folder by Remit",
|
|
50
|
+
);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test("filter move names the resolved source folder", () => {
|
|
54
|
+
assert.equal(
|
|
55
|
+
autoMovedLabel(filterMove("mb-inbox", "mb-travel"), "Travel"),
|
|
56
|
+
"Moved from Travel by Remit",
|
|
57
|
+
);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("filter move falls back to 'another folder' before the name resolves", () => {
|
|
61
|
+
assert.equal(
|
|
62
|
+
autoMovedLabel(filterMove("mb-inbox", "mb-travel")),
|
|
63
|
+
"Moved from another folder by Remit",
|
|
64
|
+
);
|
|
28
65
|
});
|
|
29
66
|
|
|
30
67
|
test("never leaks verdict jargon", () => {
|
|
31
|
-
|
|
68
|
+
const cases = [
|
|
69
|
+
classifierMove(PlacementAction.MoveToInbox, "junk"),
|
|
70
|
+
classifierMove(PlacementAction.MoveToJunk, "inbox"),
|
|
71
|
+
filterMove("mb-inbox", "mb-travel"),
|
|
72
|
+
];
|
|
73
|
+
for (const autoMoved of cases) {
|
|
32
74
|
assert.doesNotMatch(
|
|
33
|
-
autoMovedLabel(
|
|
75
|
+
autoMovedLabel(autoMoved),
|
|
34
76
|
/confiden|dry.?run|verdict/i,
|
|
35
77
|
);
|
|
36
78
|
}
|
|
@@ -46,94 +88,138 @@ describe("isAutoMoveInEffect", () => {
|
|
|
46
88
|
});
|
|
47
89
|
|
|
48
90
|
test("false when currentMailboxId is absent", () => {
|
|
49
|
-
const autoMoved: RemitImapAutoMovedInfo = {
|
|
50
|
-
action: PlacementAction.MoveToInbox,
|
|
51
|
-
fromPlacement: "junk",
|
|
52
|
-
};
|
|
53
91
|
assert.equal(
|
|
54
|
-
isAutoMoveInEffect(
|
|
92
|
+
isAutoMoveInEffect(
|
|
93
|
+
classifierMove(PlacementAction.MoveToInbox, "junk"),
|
|
94
|
+
undefined,
|
|
95
|
+
ROLE_MAILBOXES,
|
|
96
|
+
),
|
|
55
97
|
false,
|
|
56
98
|
);
|
|
57
99
|
});
|
|
58
100
|
|
|
59
|
-
test("true when a MoveToInbox message
|
|
60
|
-
const autoMoved: RemitImapAutoMovedInfo = {
|
|
61
|
-
action: PlacementAction.MoveToInbox,
|
|
62
|
-
fromPlacement: "junk",
|
|
63
|
-
};
|
|
101
|
+
test("classifier: true when a MoveToInbox message sits in the Inbox mailbox", () => {
|
|
64
102
|
assert.equal(
|
|
65
|
-
isAutoMoveInEffect(
|
|
103
|
+
isAutoMoveInEffect(
|
|
104
|
+
classifierMove(PlacementAction.MoveToInbox, "junk"),
|
|
105
|
+
"mb-inbox",
|
|
106
|
+
ROLE_MAILBOXES,
|
|
107
|
+
),
|
|
66
108
|
true,
|
|
67
109
|
);
|
|
68
110
|
});
|
|
69
111
|
|
|
70
|
-
test("true when a MoveToJunk message
|
|
71
|
-
const autoMoved: RemitImapAutoMovedInfo = {
|
|
72
|
-
action: PlacementAction.MoveToJunk,
|
|
73
|
-
fromPlacement: "inbox",
|
|
74
|
-
};
|
|
112
|
+
test("classifier: true when a MoveToJunk message sits in the Junk mailbox", () => {
|
|
75
113
|
assert.equal(
|
|
76
|
-
isAutoMoveInEffect(
|
|
114
|
+
isAutoMoveInEffect(
|
|
115
|
+
classifierMove(PlacementAction.MoveToJunk, "inbox"),
|
|
116
|
+
"mb-junk",
|
|
117
|
+
ROLE_MAILBOXES,
|
|
118
|
+
),
|
|
77
119
|
true,
|
|
78
120
|
);
|
|
79
121
|
});
|
|
80
122
|
|
|
81
|
-
test("false once the message has moved elsewhere
|
|
82
|
-
const autoMoved
|
|
83
|
-
action: PlacementAction.MoveToInbox,
|
|
84
|
-
fromPlacement: "junk",
|
|
85
|
-
};
|
|
123
|
+
test("classifier: false once the message has moved elsewhere", () => {
|
|
124
|
+
const autoMoved = classifierMove(PlacementAction.MoveToInbox, "junk");
|
|
86
125
|
assert.equal(
|
|
87
126
|
isAutoMoveInEffect(autoMoved, "mb-archive", ROLE_MAILBOXES),
|
|
88
127
|
false,
|
|
89
128
|
);
|
|
90
|
-
// Undone back to Junk — no longer in the implied destination.
|
|
91
129
|
assert.equal(
|
|
92
130
|
isAutoMoveInEffect(autoMoved, "mb-junk", ROLE_MAILBOXES),
|
|
93
131
|
false,
|
|
94
132
|
);
|
|
95
133
|
});
|
|
96
134
|
|
|
97
|
-
test("
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
135
|
+
test("filter: true while the message sits in the filter's destination", () => {
|
|
136
|
+
assert.equal(
|
|
137
|
+
isAutoMoveInEffect(
|
|
138
|
+
filterMove("mb-inbox", "mb-travel"),
|
|
139
|
+
"mb-travel",
|
|
140
|
+
ROLE_MAILBOXES,
|
|
141
|
+
),
|
|
142
|
+
true,
|
|
143
|
+
);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test("filter: false once undone back to the source folder", () => {
|
|
147
|
+
assert.equal(
|
|
148
|
+
isAutoMoveInEffect(
|
|
149
|
+
filterMove("mb-inbox", "mb-travel"),
|
|
150
|
+
"mb-inbox",
|
|
151
|
+
ROLE_MAILBOXES,
|
|
152
|
+
),
|
|
153
|
+
false,
|
|
154
|
+
);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test("filter: in-effect does not depend on the Inbox/Junk role mailboxes", () => {
|
|
102
158
|
assert.equal(
|
|
103
|
-
isAutoMoveInEffect(
|
|
159
|
+
isAutoMoveInEffect(filterMove("mb-inbox", "mb-travel"), "mb-travel", {
|
|
104
160
|
inboxMailboxId: undefined,
|
|
105
|
-
junkMailboxId:
|
|
161
|
+
junkMailboxId: undefined,
|
|
106
162
|
}),
|
|
163
|
+
true,
|
|
164
|
+
);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test("classifier: false when the implied destination mailbox hasn't resolved yet", () => {
|
|
168
|
+
assert.equal(
|
|
169
|
+
isAutoMoveInEffect(
|
|
170
|
+
classifierMove(PlacementAction.MoveToInbox, "junk"),
|
|
171
|
+
"mb-inbox",
|
|
172
|
+
{ inboxMailboxId: undefined, junkMailboxId: "mb-junk" },
|
|
173
|
+
),
|
|
107
174
|
false,
|
|
108
175
|
);
|
|
109
176
|
});
|
|
110
177
|
});
|
|
111
178
|
|
|
112
179
|
describe("resolveUndoTargetMailboxId", () => {
|
|
113
|
-
test("resolves the Junk mailbox for fromPlacement='junk'", () => {
|
|
114
|
-
assert.equal(
|
|
180
|
+
test("classifier: resolves the Junk mailbox for fromPlacement='junk'", () => {
|
|
181
|
+
assert.equal(
|
|
182
|
+
resolveUndoTargetMailboxId(
|
|
183
|
+
classifierMove(PlacementAction.MoveToInbox, "junk"),
|
|
184
|
+
ROLE_MAILBOXES,
|
|
185
|
+
),
|
|
186
|
+
"mb-junk",
|
|
187
|
+
);
|
|
115
188
|
});
|
|
116
189
|
|
|
117
|
-
test("resolves the Inbox mailbox for fromPlacement='inbox'", () => {
|
|
190
|
+
test("classifier: resolves the Inbox mailbox for fromPlacement='inbox'", () => {
|
|
118
191
|
assert.equal(
|
|
119
|
-
resolveUndoTargetMailboxId(
|
|
192
|
+
resolveUndoTargetMailboxId(
|
|
193
|
+
classifierMove(PlacementAction.MoveToJunk, "inbox"),
|
|
194
|
+
ROLE_MAILBOXES,
|
|
195
|
+
),
|
|
120
196
|
"mb-inbox",
|
|
121
197
|
);
|
|
122
198
|
});
|
|
123
199
|
|
|
124
|
-
test("undefined
|
|
200
|
+
test("classifier: undefined when the role mailbox hasn't resolved yet", () => {
|
|
125
201
|
assert.equal(
|
|
126
|
-
resolveUndoTargetMailboxId(
|
|
202
|
+
resolveUndoTargetMailboxId(
|
|
203
|
+
classifierMove(PlacementAction.MoveToInbox, "junk"),
|
|
204
|
+
{ inboxMailboxId: "mb-inbox", junkMailboxId: undefined },
|
|
205
|
+
),
|
|
127
206
|
undefined,
|
|
128
207
|
);
|
|
129
208
|
});
|
|
130
209
|
|
|
131
|
-
test("
|
|
210
|
+
test("filter: undo targets the recorded source mailbox verbatim", () => {
|
|
132
211
|
assert.equal(
|
|
133
|
-
resolveUndoTargetMailboxId(
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
212
|
+
resolveUndoTargetMailboxId(
|
|
213
|
+
filterMove("mb-work", "mb-travel"),
|
|
214
|
+
ROLE_MAILBOXES,
|
|
215
|
+
),
|
|
216
|
+
"mb-work",
|
|
217
|
+
);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
test("undefined when autoMoved is absent", () => {
|
|
221
|
+
assert.equal(
|
|
222
|
+
resolveUndoTargetMailboxId(undefined, ROLE_MAILBOXES),
|
|
137
223
|
undefined,
|
|
138
224
|
);
|
|
139
225
|
});
|
package/src/lib/auto-moved.ts
CHANGED
|
@@ -12,17 +12,35 @@ const FROM_PLACEMENT_LABEL: Record<string, string> = {
|
|
|
12
12
|
junk: "Junk",
|
|
13
13
|
};
|
|
14
14
|
|
|
15
|
+
/** A standing-filter move names an arbitrary destination via `destinationMailboxId`; a classifier move names a direction via `action`. */
|
|
16
|
+
const isFilterMove = (autoMoved: RemitImapAutoMovedInfo): boolean =>
|
|
17
|
+
autoMoved.destinationMailboxId !== undefined;
|
|
18
|
+
|
|
15
19
|
/**
|
|
16
20
|
* Plain-language badge text, e.g. "Moved from Junk by Remit". No jargon
|
|
17
21
|
* (verdict/confidence/dryRun never surface) — mirrors the no-jargon precedent
|
|
18
22
|
* in `rescue-candidates.ts`.
|
|
23
|
+
*
|
|
24
|
+
* A classifier move reads its source from the `fromPlacement` role. A
|
|
25
|
+
* standing-filter move moved from an arbitrary folder, so the caller resolves
|
|
26
|
+
* that folder's display name and passes it as `sourceFolderName`; absent (name
|
|
27
|
+
* not resolved yet) falls back to "another folder".
|
|
19
28
|
*/
|
|
20
|
-
export const autoMovedLabel = (
|
|
21
|
-
|
|
29
|
+
export const autoMovedLabel = (
|
|
30
|
+
autoMoved: RemitImapAutoMovedInfo,
|
|
31
|
+
sourceFolderName?: string,
|
|
32
|
+
): string => {
|
|
33
|
+
if (isFilterMove(autoMoved)) {
|
|
34
|
+
return `Moved from ${sourceFolderName ?? "another folder"} by Remit`;
|
|
35
|
+
}
|
|
36
|
+
const from = autoMoved.fromPlacement ?? "";
|
|
37
|
+
return `Moved from ${FROM_PLACEMENT_LABEL[from] ?? "another folder"} by Remit`;
|
|
38
|
+
};
|
|
22
39
|
|
|
23
40
|
/**
|
|
24
|
-
* The mailbox the verdict's `action` implies as the destination — where
|
|
25
|
-
* message should currently sit for the move to still be "in
|
|
41
|
+
* The mailbox the verdict's `action` implies as the destination — where a
|
|
42
|
+
* classifier-moved message should currently sit for the move to still be "in
|
|
43
|
+
* effect".
|
|
26
44
|
*/
|
|
27
45
|
const impliedDestinationMailboxId = (
|
|
28
46
|
action: string | undefined,
|
|
@@ -34,12 +52,13 @@ const impliedDestinationMailboxId = (
|
|
|
34
52
|
};
|
|
35
53
|
|
|
36
54
|
/**
|
|
37
|
-
* True only while the auto-move is still in effect: the message currently
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
55
|
+
* True only while the auto-move is still in effect: the message currently sits
|
|
56
|
+
* in the mailbox the move targeted. A standing-filter move targets the
|
|
57
|
+
* `destinationMailboxId` it recorded; a classifier move targets the mailbox its
|
|
58
|
+
* `action` implies. Once the message moves elsewhere (an undo, or any other
|
|
59
|
+
* move), this returns false — the badge has no local "dismissed" flag; it
|
|
60
|
+
* re-derives from current placement every render, per the data-flow rule that a
|
|
61
|
+
* projection never forks from its source (doc/rules/data-flow.md).
|
|
43
62
|
*/
|
|
44
63
|
export const isAutoMoveInEffect = (
|
|
45
64
|
autoMoved: RemitImapAutoMovedInfo | undefined,
|
|
@@ -47,22 +66,29 @@ export const isAutoMoveInEffect = (
|
|
|
47
66
|
mailboxes: AutoMovedRoleMailboxes,
|
|
48
67
|
): boolean => {
|
|
49
68
|
if (!autoMoved || !currentMailboxId) return false;
|
|
50
|
-
const destination =
|
|
69
|
+
const destination = isFilterMove(autoMoved)
|
|
70
|
+
? autoMoved.destinationMailboxId
|
|
71
|
+
: impliedDestinationMailboxId(autoMoved.action, mailboxes);
|
|
51
72
|
return destination !== undefined && destination === currentMailboxId;
|
|
52
73
|
};
|
|
53
74
|
|
|
54
75
|
/**
|
|
55
|
-
* Resolve the undo destination: the
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
76
|
+
* Resolve the undo destination: where the message was before the move. A
|
|
77
|
+
* standing-filter move recorded the exact source mailbox (`fromMailboxId`),
|
|
78
|
+
* used verbatim. A classifier move recorded only a role (`fromPlacement`,
|
|
79
|
+
* always `inbox` or `junk` for an actionable verdict — the Tier 0 classifier
|
|
80
|
+
* only fires move-to-inbox from junk and move-to-junk from inbox, see
|
|
81
|
+
* `classifyPlacement.ts`), resolved to the account's mailbox for that role.
|
|
82
|
+
* `undefined` when the account has no mailbox for that role, or the source
|
|
83
|
+
* cannot be resolved.
|
|
60
84
|
*/
|
|
61
85
|
export const resolveUndoTargetMailboxId = (
|
|
62
|
-
|
|
86
|
+
autoMoved: RemitImapAutoMovedInfo | undefined,
|
|
63
87
|
mailboxes: AutoMovedRoleMailboxes,
|
|
64
88
|
): string | undefined => {
|
|
65
|
-
if (
|
|
66
|
-
if (
|
|
89
|
+
if (!autoMoved) return undefined;
|
|
90
|
+
if (isFilterMove(autoMoved)) return autoMoved.fromMailboxId;
|
|
91
|
+
if (autoMoved.fromPlacement === "inbox") return mailboxes.inboxMailboxId;
|
|
92
|
+
if (autoMoved.fromPlacement === "junk") return mailboxes.junkMailboxId;
|
|
67
93
|
return undefined;
|
|
68
94
|
};
|