@remit/web-client 0.0.48 → 0.0.49
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.49",
|
|
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": {
|
|
@@ -33,6 +33,7 @@ import { ConversationView } from "@/components/mail/ConversationView";
|
|
|
33
33
|
import { DailyBrief } from "@/components/mail/DailyBrief";
|
|
34
34
|
import { IntelligencePane } from "@/components/mail/IntelligencePane";
|
|
35
35
|
import { MessageToolbar } from "@/components/mail/MessageToolbar";
|
|
36
|
+
import { type ThreadActions, useThreadActions } from "@/hooks/useThreadActions";
|
|
36
37
|
import {
|
|
37
38
|
buildConversationTarget,
|
|
38
39
|
type ConversationTarget,
|
|
@@ -51,6 +52,11 @@ interface BriefPaneContextValue {
|
|
|
51
52
|
onSelectMessage: (id: string) => void;
|
|
52
53
|
onSelectSearchResult: (result: SearchResult) => void;
|
|
53
54
|
onCloseThread: () => void;
|
|
55
|
+
/**
|
|
56
|
+
* Toolbar verbs for the open thread, keyed by the thread's own mailbox and
|
|
57
|
+
* account — the brief spans accounts, so there is no route mailbox to key by.
|
|
58
|
+
*/
|
|
59
|
+
actions: ThreadActions;
|
|
54
60
|
}
|
|
55
61
|
|
|
56
62
|
const BriefPaneCtx = createContext<BriefPaneContextValue | null>(null);
|
|
@@ -136,6 +142,28 @@ function BriefPaneProvider({ selectedMessageId, children }: BriefPaneProps) {
|
|
|
136
142
|
[navigate, searchInput],
|
|
137
143
|
);
|
|
138
144
|
|
|
145
|
+
const handleDeselectIfRemoved = useCallback(
|
|
146
|
+
(removedIds: string[]) => {
|
|
147
|
+
if (!selectedMessageId) return;
|
|
148
|
+
if (!removedIds.includes(selectedMessageId)) return;
|
|
149
|
+
navigate({
|
|
150
|
+
to: "/mail",
|
|
151
|
+
search: (prev) => ({
|
|
152
|
+
...prev,
|
|
153
|
+
selectedMessageId: undefined,
|
|
154
|
+
selectedThreadId: undefined,
|
|
155
|
+
selectedMailboxId: undefined,
|
|
156
|
+
}),
|
|
157
|
+
});
|
|
158
|
+
},
|
|
159
|
+
[selectedMessageId, navigate],
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
const actions = useThreadActions({
|
|
163
|
+
thread: selectedThread,
|
|
164
|
+
onAfterOptimisticRemove: handleDeselectIfRemoved,
|
|
165
|
+
});
|
|
166
|
+
|
|
139
167
|
const handleCloseThread = useCallback(() => {
|
|
140
168
|
navigate({
|
|
141
169
|
to: "/mail",
|
|
@@ -155,6 +183,7 @@ function BriefPaneProvider({ selectedMessageId, children }: BriefPaneProps) {
|
|
|
155
183
|
onSelectMessage: handleSelectMessage,
|
|
156
184
|
onSelectSearchResult: handleSelectSearchResult,
|
|
157
185
|
onCloseThread: handleCloseThread,
|
|
186
|
+
actions,
|
|
158
187
|
};
|
|
159
188
|
|
|
160
189
|
return <BriefPaneCtx.Provider value={ctx}>{children}</BriefPaneCtx.Provider>;
|
|
@@ -187,7 +216,7 @@ function BriefList() {
|
|
|
187
216
|
* Mount in the `reading` slot of `AppShellSlotted`. Only rendered ≥ 1024px.
|
|
188
217
|
*/
|
|
189
218
|
function BriefReading() {
|
|
190
|
-
const { conversation } = useBriefPane();
|
|
219
|
+
const { conversation, actions } = useBriefPane();
|
|
191
220
|
const { intelligenceOpen, onToggleIntelligence } = useMailContext();
|
|
192
221
|
// The rail's own width gate, not the shell tier: between 1024 and 1280 the
|
|
193
222
|
// reading pane is mounted but the rail is not, so "enabled" would promise an
|
|
@@ -203,6 +232,25 @@ function BriefReading() {
|
|
|
203
232
|
intelligenceOpen={canToggleIntelligence && intelligenceOpen}
|
|
204
233
|
canToggleIntelligence={canToggleIntelligence}
|
|
205
234
|
onToggleIntelligence={onToggleIntelligence}
|
|
235
|
+
onReply={hasThread ? () => actions.requestCompose("reply") : undefined}
|
|
236
|
+
onReplyAll={
|
|
237
|
+
hasThread ? () => actions.requestCompose("reply_all") : undefined
|
|
238
|
+
}
|
|
239
|
+
onForward={
|
|
240
|
+
hasThread ? () => actions.requestCompose("forward") : undefined
|
|
241
|
+
}
|
|
242
|
+
onDelete={hasThread ? actions.deleteThread : undefined}
|
|
243
|
+
onToggleStar={hasThread ? actions.toggleStar : undefined}
|
|
244
|
+
isStarred={actions.isStarred}
|
|
245
|
+
moveContext={
|
|
246
|
+
hasThread && actions.accountId && actions.mailboxId
|
|
247
|
+
? {
|
|
248
|
+
accountId: actions.accountId,
|
|
249
|
+
currentMailboxId: actions.mailboxId,
|
|
250
|
+
onMove: actions.moveThread,
|
|
251
|
+
}
|
|
252
|
+
: undefined
|
|
253
|
+
}
|
|
206
254
|
/>
|
|
207
255
|
<div className="min-h-0 flex-1 overflow-hidden">
|
|
208
256
|
{conversation ? (
|
|
@@ -212,6 +260,8 @@ function BriefReading() {
|
|
|
212
260
|
subject={conversation.subject}
|
|
213
261
|
selectedMessageId={conversation.messageId}
|
|
214
262
|
authenticity={conversation.authenticity}
|
|
263
|
+
composeRequest={actions.composeRequest}
|
|
264
|
+
onComposeClose={actions.clearComposeRequest}
|
|
215
265
|
/>
|
|
216
266
|
) : (
|
|
217
267
|
<ReadingPaneEmpty />
|
|
@@ -36,6 +36,7 @@ import { FlaggedList } from "@/components/mail/FlaggedList";
|
|
|
36
36
|
import { IntelligencePane } from "@/components/mail/IntelligencePane";
|
|
37
37
|
import { MessageToolbar } from "@/components/mail/MessageToolbar";
|
|
38
38
|
import { useStarredThreads } from "@/hooks/useStarredThreads";
|
|
39
|
+
import { type ThreadActions, useThreadActions } from "@/hooks/useThreadActions";
|
|
39
40
|
import { useMailContext } from "@/lib/mail-context";
|
|
40
41
|
|
|
41
42
|
/* ------------------------------------------------------------------ */
|
|
@@ -47,6 +48,11 @@ interface FlaggedPaneContextValue {
|
|
|
47
48
|
selectedThread: RemitImapThreadMessageResponse | undefined;
|
|
48
49
|
onSelectMessage: (id: string) => void;
|
|
49
50
|
onCloseThread: () => void;
|
|
51
|
+
/**
|
|
52
|
+
* Toolbar verbs for the open thread, keyed by the thread's own mailbox and
|
|
53
|
+
* account — Flagged spans accounts, so there is no route mailbox to key by.
|
|
54
|
+
*/
|
|
55
|
+
actions: ThreadActions;
|
|
50
56
|
}
|
|
51
57
|
|
|
52
58
|
const FlaggedPaneCtx = createContext<FlaggedPaneContextValue | null>(null);
|
|
@@ -96,11 +102,26 @@ function FlaggedPaneProvider({
|
|
|
96
102
|
});
|
|
97
103
|
}, [navigate]);
|
|
98
104
|
|
|
105
|
+
const handleDeselectIfRemoved = useCallback(
|
|
106
|
+
(removedIds: string[]) => {
|
|
107
|
+
if (!selectedMessageId) return;
|
|
108
|
+
if (!removedIds.includes(selectedMessageId)) return;
|
|
109
|
+
handleCloseThread();
|
|
110
|
+
},
|
|
111
|
+
[selectedMessageId, handleCloseThread],
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
const actions = useThreadActions({
|
|
115
|
+
thread: selectedThread,
|
|
116
|
+
onAfterOptimisticRemove: handleDeselectIfRemoved,
|
|
117
|
+
});
|
|
118
|
+
|
|
99
119
|
const ctx: FlaggedPaneContextValue = {
|
|
100
120
|
selectedMessageId,
|
|
101
121
|
selectedThread,
|
|
102
122
|
onSelectMessage: handleSelectMessage,
|
|
103
123
|
onCloseThread: handleCloseThread,
|
|
124
|
+
actions,
|
|
104
125
|
};
|
|
105
126
|
|
|
106
127
|
return (
|
|
@@ -128,7 +149,7 @@ function FlaggedListSlot() {
|
|
|
128
149
|
* Mount in the `reading` slot of `AppShellSlotted`. Only rendered ≥ 1024px.
|
|
129
150
|
*/
|
|
130
151
|
function FlaggedReading() {
|
|
131
|
-
const { selectedThread } = useFlaggedPane();
|
|
152
|
+
const { selectedThread, actions } = useFlaggedPane();
|
|
132
153
|
const { intelligenceOpen, onToggleIntelligence } = useMailContext();
|
|
133
154
|
// The rail's own width gate, not the shell tier: between 1024 and 1280 the
|
|
134
155
|
// reading pane is mounted but the rail is not, so "enabled" would promise an
|
|
@@ -144,6 +165,25 @@ function FlaggedReading() {
|
|
|
144
165
|
intelligenceOpen={canToggleIntelligence && intelligenceOpen}
|
|
145
166
|
canToggleIntelligence={canToggleIntelligence}
|
|
146
167
|
onToggleIntelligence={onToggleIntelligence}
|
|
168
|
+
onReply={hasThread ? () => actions.requestCompose("reply") : undefined}
|
|
169
|
+
onReplyAll={
|
|
170
|
+
hasThread ? () => actions.requestCompose("reply_all") : undefined
|
|
171
|
+
}
|
|
172
|
+
onForward={
|
|
173
|
+
hasThread ? () => actions.requestCompose("forward") : undefined
|
|
174
|
+
}
|
|
175
|
+
onDelete={hasThread ? actions.deleteThread : undefined}
|
|
176
|
+
onToggleStar={hasThread ? actions.toggleStar : undefined}
|
|
177
|
+
isStarred={actions.isStarred}
|
|
178
|
+
moveContext={
|
|
179
|
+
hasThread && actions.accountId && actions.mailboxId
|
|
180
|
+
? {
|
|
181
|
+
accountId: actions.accountId,
|
|
182
|
+
currentMailboxId: actions.mailboxId,
|
|
183
|
+
onMove: actions.moveThread,
|
|
184
|
+
}
|
|
185
|
+
: undefined
|
|
186
|
+
}
|
|
147
187
|
/>
|
|
148
188
|
<div className="min-h-0 flex-1 overflow-hidden">
|
|
149
189
|
{selectedThread ? (
|
|
@@ -152,6 +192,8 @@ function FlaggedReading() {
|
|
|
152
192
|
mailboxId={selectedThread.mailboxId}
|
|
153
193
|
subject={selectedThread.subject}
|
|
154
194
|
authenticity={selectedThread.authenticity}
|
|
195
|
+
composeRequest={actions.composeRequest}
|
|
196
|
+
onComposeClose={actions.clearComposeRequest}
|
|
155
197
|
/>
|
|
156
198
|
) : (
|
|
157
199
|
<ReadingPaneEmpty />
|
|
@@ -18,7 +18,6 @@
|
|
|
18
18
|
import {
|
|
19
19
|
outboxDetailOperationsDeleteOutboxMessageMutation,
|
|
20
20
|
outboxOperationsListOutboxMessagesQueryKey,
|
|
21
|
-
threadDetailOperationsListThreadMessagesQueryKey,
|
|
22
21
|
threadOperationsListThreadsQueryKey,
|
|
23
22
|
threadOperationsSearchThreadsQueryKey,
|
|
24
23
|
} from "@remit/api-http-client/@tanstack/react-query.gen.ts";
|
|
@@ -91,6 +90,8 @@ import { useMoveMessages } from "@/hooks/useMoveMessages";
|
|
|
91
90
|
import { useRescueCandidates } from "@/hooks/useRescueCandidates";
|
|
92
91
|
import { useSearchTokenContext } from "@/hooks/useSearchTokenContext";
|
|
93
92
|
import { useSemanticSearch } from "@/hooks/useSemanticSearch";
|
|
93
|
+
import { useThreadActions } from "@/hooks/useThreadActions";
|
|
94
|
+
import { useThreadMessageIds } from "@/hooks/useThreadMessageIds";
|
|
94
95
|
import { useToggleStar } from "@/hooks/useToggleStar";
|
|
95
96
|
import { useTriageKeyboard } from "@/hooks/useTriageKeyboard";
|
|
96
97
|
import { useUpdateAddressFlags } from "@/hooks/useUpdateAddressFlags";
|
|
@@ -503,15 +504,9 @@ function MailboxPaneProvider({
|
|
|
503
504
|
const queryClient = useQueryClient();
|
|
504
505
|
const { pushError } = useErrorBanners();
|
|
505
506
|
|
|
506
|
-
const
|
|
507
|
+
const toolbarActions = useThreadActions({
|
|
508
|
+
thread: selectedThread,
|
|
507
509
|
mailboxId,
|
|
508
|
-
threadId: selectedThread?.threadId,
|
|
509
|
-
onAfterOptimisticRemove: handleDeselectIfRemoved,
|
|
510
|
-
});
|
|
511
|
-
|
|
512
|
-
const { moveMessages: toolbarMove } = useMoveMessages({
|
|
513
|
-
mailboxId,
|
|
514
|
-
threadId: selectedThread?.threadId,
|
|
515
510
|
accountId: mailboxAccountId,
|
|
516
511
|
onAfterOptimisticRemove: handleDeselectIfRemoved,
|
|
517
512
|
});
|
|
@@ -522,63 +517,19 @@ function MailboxPaneProvider({
|
|
|
522
517
|
|
|
523
518
|
const { archiveMailboxId } = useArchiveMailbox(mailboxAccountId);
|
|
524
519
|
|
|
525
|
-
|
|
526
|
-
const getThreadMessageIds = useCallback(
|
|
527
|
-
(thread: RemitImapThreadMessageResponse) => {
|
|
528
|
-
const threadKey = threadDetailOperationsListThreadMessagesQueryKey({
|
|
529
|
-
path: { threadId: thread.threadId },
|
|
530
|
-
});
|
|
531
|
-
const cached = queryClient.getQueriesData<{
|
|
532
|
-
items: { messageId: string }[];
|
|
533
|
-
}>({ queryKey: threadKey });
|
|
534
|
-
const ids = cached.flatMap(
|
|
535
|
-
([, data]) => data?.items.map((m) => m.messageId) ?? [],
|
|
536
|
-
);
|
|
537
|
-
return ids.length > 0 ? ids : [thread.messageId];
|
|
538
|
-
},
|
|
539
|
-
[queryClient],
|
|
540
|
-
);
|
|
541
|
-
|
|
542
|
-
const handleToolbarDelete = useCallback(() => {
|
|
543
|
-
if (!selectedThread) return;
|
|
544
|
-
const messageIds = getThreadMessageIds(selectedThread);
|
|
545
|
-
toolbarDelete(messageIds);
|
|
546
|
-
}, [selectedThread, getThreadMessageIds, toolbarDelete]);
|
|
547
|
-
|
|
548
|
-
const handleToolbarMove = useCallback(
|
|
549
|
-
(destMailboxId: string) => {
|
|
550
|
-
if (!selectedThread) return;
|
|
551
|
-
const messageIds = getThreadMessageIds(selectedThread);
|
|
552
|
-
toolbarMove(messageIds, destMailboxId);
|
|
553
|
-
},
|
|
554
|
-
[selectedThread, getThreadMessageIds, toolbarMove],
|
|
555
|
-
);
|
|
556
|
-
|
|
557
|
-
const { toggleStar: toolbarToggleStar } = useToggleStar({
|
|
558
|
-
threadId: selectedThread?.threadId ?? "",
|
|
559
|
-
mailboxId,
|
|
560
|
-
});
|
|
561
|
-
|
|
562
|
-
const handleToolbarStar = useCallback(() => {
|
|
563
|
-
if (!selectedThread) return;
|
|
564
|
-
toolbarToggleStar(selectedThread.messageId, selectedThread.hasStars);
|
|
565
|
-
}, [selectedThread, toolbarToggleStar]);
|
|
520
|
+
const getThreadMessageIds = useThreadMessageIds();
|
|
566
521
|
|
|
567
|
-
const
|
|
568
|
-
useState<ComposeMode | null>(null);
|
|
522
|
+
const { requestCompose: setToolbarComposeRequest } = toolbarActions;
|
|
569
523
|
|
|
570
524
|
const handleToolbarReply = useCallback(() => {
|
|
571
525
|
setToolbarComposeRequest("reply");
|
|
572
|
-
}, []);
|
|
526
|
+
}, [setToolbarComposeRequest]);
|
|
573
527
|
const handleToolbarReplyAll = useCallback(() => {
|
|
574
528
|
setToolbarComposeRequest("reply_all");
|
|
575
|
-
}, []);
|
|
529
|
+
}, [setToolbarComposeRequest]);
|
|
576
530
|
const handleToolbarForward = useCallback(() => {
|
|
577
531
|
setToolbarComposeRequest("forward");
|
|
578
|
-
}, []);
|
|
579
|
-
const handleClearComposeRequest = useCallback(() => {
|
|
580
|
-
setToolbarComposeRequest(null);
|
|
581
|
-
}, []);
|
|
532
|
+
}, [setToolbarComposeRequest]);
|
|
582
533
|
|
|
583
534
|
const { state: composeState, openCompose, closeCompose } = useCompose();
|
|
584
535
|
|
|
@@ -688,15 +639,15 @@ function MailboxPaneProvider({
|
|
|
688
639
|
const triageReply = useCallback(() => {
|
|
689
640
|
if (ensureFocusedOpen()) return;
|
|
690
641
|
if (selectedThread) setToolbarComposeRequest("reply");
|
|
691
|
-
}, [ensureFocusedOpen, selectedThread]);
|
|
642
|
+
}, [ensureFocusedOpen, selectedThread, setToolbarComposeRequest]);
|
|
692
643
|
const triageReplyAll = useCallback(() => {
|
|
693
644
|
if (ensureFocusedOpen()) return;
|
|
694
645
|
if (selectedThread) setToolbarComposeRequest("reply_all");
|
|
695
|
-
}, [ensureFocusedOpen, selectedThread]);
|
|
646
|
+
}, [ensureFocusedOpen, selectedThread, setToolbarComposeRequest]);
|
|
696
647
|
const triageForward = useCallback(() => {
|
|
697
648
|
if (ensureFocusedOpen()) return;
|
|
698
649
|
if (selectedThread) setToolbarComposeRequest("forward");
|
|
699
|
-
}, [ensureFocusedOpen, selectedThread]);
|
|
650
|
+
}, [ensureFocusedOpen, selectedThread, setToolbarComposeRequest]);
|
|
700
651
|
|
|
701
652
|
const triageTargetMessageIds = useCallback((): string[] => {
|
|
702
653
|
if (triageSelectedIds.length > 0) return triageSelectedIds;
|
|
@@ -889,15 +840,15 @@ function MailboxPaneProvider({
|
|
|
889
840
|
onTriageContextChange: handleTriageContextChange,
|
|
890
841
|
listCommandsRef,
|
|
891
842
|
onRetry: () => refetch(),
|
|
892
|
-
toolbarComposeRequest,
|
|
843
|
+
toolbarComposeRequest: toolbarActions.composeRequest,
|
|
893
844
|
onToolbarReply: handleToolbarReply,
|
|
894
845
|
onToolbarReplyAll: handleToolbarReplyAll,
|
|
895
846
|
onToolbarForward: handleToolbarForward,
|
|
896
|
-
onClearComposeRequest:
|
|
897
|
-
onToolbarDelete:
|
|
898
|
-
onToolbarStar:
|
|
847
|
+
onClearComposeRequest: toolbarActions.clearComposeRequest,
|
|
848
|
+
onToolbarDelete: toolbarActions.deleteThread,
|
|
849
|
+
onToolbarStar: toolbarActions.toggleStar,
|
|
899
850
|
onToolbarDiscardDraft: handleToolbarDiscardDraft,
|
|
900
|
-
onToolbarMove:
|
|
851
|
+
onToolbarMove: toolbarActions.moveThread,
|
|
901
852
|
composeState,
|
|
902
853
|
closeCompose,
|
|
903
854
|
hasRemitDraftOpen,
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useThreadActions — the reading pane's verbs for one open thread.
|
|
3
|
+
*
|
|
4
|
+
* Delete, move, star and the compose requests (reply / reply-all / forward),
|
|
5
|
+
* over the same mutation hooks the mailbox list uses. The mailbox view keys
|
|
6
|
+
* them by its route; the brief and Flagged are cross-account, so they key by
|
|
7
|
+
* the open thread's own `mailboxId` / `accountConfigId` (#149).
|
|
8
|
+
*/
|
|
9
|
+
import type { RemitImapThreadMessageResponse } from "@remit/api-http-client/types.gen.ts";
|
|
10
|
+
import { useCallback, useState } from "react";
|
|
11
|
+
import type { ComposeMode } from "@/components/compose/ComposeProvider";
|
|
12
|
+
import { useDeleteMessages } from "@/hooks/useDeleteMessages";
|
|
13
|
+
import { useMoveMessages } from "@/hooks/useMoveMessages";
|
|
14
|
+
import { useThreadMessageIds } from "@/hooks/useThreadMessageIds";
|
|
15
|
+
import { useToggleStar } from "@/hooks/useToggleStar";
|
|
16
|
+
|
|
17
|
+
interface UseThreadActionsOptions {
|
|
18
|
+
thread: RemitImapThreadMessageResponse | undefined;
|
|
19
|
+
/** Mailbox whose listings the mutations patch. Defaults to the thread's own. */
|
|
20
|
+
mailboxId?: string;
|
|
21
|
+
/** Account the move picker offers folders from. Defaults to the thread's own. */
|
|
22
|
+
accountId?: string;
|
|
23
|
+
onAfterOptimisticRemove?: (messageIds: string[]) => void;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface ThreadActions {
|
|
27
|
+
mailboxId: string | undefined;
|
|
28
|
+
accountId: string | undefined;
|
|
29
|
+
isStarred: boolean | undefined;
|
|
30
|
+
deleteThread: () => void;
|
|
31
|
+
moveThread: (destinationMailboxId: string) => void;
|
|
32
|
+
toggleStar: () => void;
|
|
33
|
+
composeRequest: ComposeMode | null;
|
|
34
|
+
requestCompose: (mode: ComposeMode) => void;
|
|
35
|
+
clearComposeRequest: () => void;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const useThreadActions = ({
|
|
39
|
+
thread,
|
|
40
|
+
mailboxId,
|
|
41
|
+
accountId,
|
|
42
|
+
onAfterOptimisticRemove,
|
|
43
|
+
}: UseThreadActionsOptions): ThreadActions => {
|
|
44
|
+
const resolvedMailboxId = mailboxId ?? thread?.mailboxId;
|
|
45
|
+
const resolvedAccountId = accountId ?? thread?.accountConfigId;
|
|
46
|
+
const threadMessageIds = useThreadMessageIds();
|
|
47
|
+
|
|
48
|
+
const { deleteMessages } = useDeleteMessages({
|
|
49
|
+
mailboxId: resolvedMailboxId ?? "",
|
|
50
|
+
threadId: thread?.threadId,
|
|
51
|
+
accountId: resolvedAccountId,
|
|
52
|
+
onAfterOptimisticRemove,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const { moveMessages } = useMoveMessages({
|
|
56
|
+
mailboxId: resolvedMailboxId ?? "",
|
|
57
|
+
threadId: thread?.threadId,
|
|
58
|
+
accountId: resolvedAccountId,
|
|
59
|
+
onAfterOptimisticRemove,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const { toggleStar: toggleStarFor } = useToggleStar({
|
|
63
|
+
threadId: thread?.threadId ?? "",
|
|
64
|
+
mailboxId: resolvedMailboxId ?? "",
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const deleteThread = useCallback(() => {
|
|
68
|
+
if (!thread) return;
|
|
69
|
+
deleteMessages(threadMessageIds(thread));
|
|
70
|
+
}, [thread, threadMessageIds, deleteMessages]);
|
|
71
|
+
|
|
72
|
+
const moveThread = useCallback(
|
|
73
|
+
(destinationMailboxId: string) => {
|
|
74
|
+
if (!thread) return;
|
|
75
|
+
moveMessages(threadMessageIds(thread), destinationMailboxId);
|
|
76
|
+
},
|
|
77
|
+
[thread, threadMessageIds, moveMessages],
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
const toggleStar = useCallback(() => {
|
|
81
|
+
if (!thread) return;
|
|
82
|
+
toggleStarFor(thread.messageId, thread.hasStars);
|
|
83
|
+
}, [thread, toggleStarFor]);
|
|
84
|
+
|
|
85
|
+
const [composeRequest, setComposeRequest] = useState<ComposeMode | null>(
|
|
86
|
+
null,
|
|
87
|
+
);
|
|
88
|
+
const clearComposeRequest = useCallback(() => setComposeRequest(null), []);
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
mailboxId: resolvedMailboxId,
|
|
92
|
+
accountId: resolvedAccountId,
|
|
93
|
+
isStarred: thread?.hasStars,
|
|
94
|
+
deleteThread,
|
|
95
|
+
moveThread,
|
|
96
|
+
toggleStar,
|
|
97
|
+
composeRequest,
|
|
98
|
+
requestCompose: setComposeRequest,
|
|
99
|
+
clearComposeRequest,
|
|
100
|
+
};
|
|
101
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { threadDetailOperationsListThreadMessagesQueryKey } from "@remit/api-http-client/@tanstack/react-query.gen.ts";
|
|
2
|
+
import type { RemitImapThreadMessageResponse } from "@remit/api-http-client/types.gen.ts";
|
|
3
|
+
import { useQueryClient } from "@tanstack/react-query";
|
|
4
|
+
import { useCallback } from "react";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Every message id in a thread, read from the cached thread-messages listing.
|
|
8
|
+
*
|
|
9
|
+
* A toolbar verb acts on the whole conversation, not the one row that
|
|
10
|
+
* represents it in a list. When the conversation has not been opened yet its
|
|
11
|
+
* messages are not cached, and the representative message id is the best the
|
|
12
|
+
* caller can do.
|
|
13
|
+
*/
|
|
14
|
+
export const useThreadMessageIds = (): ((
|
|
15
|
+
thread: RemitImapThreadMessageResponse,
|
|
16
|
+
) => string[]) => {
|
|
17
|
+
const queryClient = useQueryClient();
|
|
18
|
+
return useCallback(
|
|
19
|
+
(thread: RemitImapThreadMessageResponse) => {
|
|
20
|
+
const threadKey = threadDetailOperationsListThreadMessagesQueryKey({
|
|
21
|
+
path: { threadId: thread.threadId },
|
|
22
|
+
});
|
|
23
|
+
const cached = queryClient.getQueriesData<{
|
|
24
|
+
items: { messageId: string }[];
|
|
25
|
+
}>({ queryKey: threadKey });
|
|
26
|
+
const ids = cached.flatMap(
|
|
27
|
+
([, data]) => data?.items.map((m) => m.messageId) ?? [],
|
|
28
|
+
);
|
|
29
|
+
return ids.length > 0 ? ids : [thread.messageId];
|
|
30
|
+
},
|
|
31
|
+
[queryClient],
|
|
32
|
+
);
|
|
33
|
+
};
|