@remit/web-client 0.0.167 → 0.0.169
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 +1 -1
- package/src/components/compose/ComposeForm.tsx +14 -4
- package/src/components/compose/ComposeProvider.tsx +7 -1
- package/src/components/compose/ConversationCompose.tsx +57 -0
- package/src/components/mail/BriefPane.tsx +49 -13
- package/src/components/mail/ConversationView.tsx +82 -77
- package/src/components/mail/FlaggedPane.tsx +49 -13
- package/src/components/mail/MailboxPane.tsx +55 -55
- package/src/components/mail/conversation-reply-reach.render.test.ts +81 -28
- package/src/hooks/useSearchMirror.ts +10 -6
- package/src/hooks/useThreadActions.ts +8 -17
- package/src/routeTree.gen.ts +128 -15
- package/src/routes/mail/$mailboxId/$threadId/$messageId/$mode.{-$outboxMessageId}.tsx +14 -0
- package/src/routes/mail/$mailboxId/$threadId/$messageId.tsx +4 -0
- package/src/routes/mail/brief/$threadId/$messageId/$mode.{-$outboxMessageId}.tsx +26 -0
- package/src/routes/mail/brief/$threadId/$messageId.tsx +4 -0
- package/src/routes/mail/flagged/$threadId/$messageId/$mode.{-$outboxMessageId}.tsx +14 -0
- package/src/routes/mail/flagged/$threadId/$messageId.tsx +4 -0
- package/src/routing/browsed-list.ts +22 -0
- package/src/routing/compose.ts +2 -17
- package/src/routing/index.ts +12 -0
- package/src/routing/reply.ts +282 -0
- package/src/components/compose/InlineCompose.tsx +0 -47
|
@@ -46,7 +46,6 @@ import {
|
|
|
46
46
|
useRef,
|
|
47
47
|
useState,
|
|
48
48
|
} from "react";
|
|
49
|
-
import type { ComposeMode } from "@/components/compose/ComposeProvider";
|
|
50
49
|
import { Drawer } from "@/components/layout/Drawer";
|
|
51
50
|
import { ConversationView } from "@/components/mail/ConversationView";
|
|
52
51
|
import { DraftsView } from "@/components/mail/DraftsView";
|
|
@@ -116,8 +115,11 @@ import {
|
|
|
116
115
|
import {
|
|
117
116
|
type OpenThreadPath,
|
|
118
117
|
type OpenThreadTarget,
|
|
118
|
+
type ReplyMode,
|
|
119
119
|
useIsComposing,
|
|
120
|
+
useIsReplying,
|
|
120
121
|
useOpenCompose,
|
|
122
|
+
useOpenReply,
|
|
121
123
|
useRetainOpenPanels,
|
|
122
124
|
} from "@/routing";
|
|
123
125
|
import { MailViewChrome } from "./MailViewChrome";
|
|
@@ -199,11 +201,11 @@ interface MailboxPaneContextValue {
|
|
|
199
201
|
listCommandsRef: RefObject<MessageListCommands | null>;
|
|
200
202
|
onRetry: () => void;
|
|
201
203
|
// Toolbar / reading pane actions
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
204
|
+
/**
|
|
205
|
+
* Answer the open conversation. Absent when none is open, which is what the
|
|
206
|
+
* toolbar turns into its own explanation.
|
|
207
|
+
*/
|
|
208
|
+
onReply: ((mode: ReplyMode) => void) | undefined;
|
|
207
209
|
onToolbarDelete: () => void;
|
|
208
210
|
onToolbarStar: () => void;
|
|
209
211
|
onToolbarMove: (destMailboxId: string) => void;
|
|
@@ -541,19 +543,34 @@ function MailboxPaneProvider({
|
|
|
541
543
|
|
|
542
544
|
const getThreadMessageIds = useThreadMessageIds();
|
|
543
545
|
|
|
544
|
-
const
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
546
|
+
const openReply = useOpenReply();
|
|
547
|
+
|
|
548
|
+
// The toolbar answers the conversation on screen; the keyboard answers the
|
|
549
|
+
// row the cursor is on, which may be one the address has not opened yet. Both
|
|
550
|
+
// are one navigation, because the mode and the message it answers are
|
|
551
|
+
// segments of the same address.
|
|
552
|
+
const replyToOpenThread = useMemo(() => {
|
|
553
|
+
if (!selectedThread || !selectedMessageId) return undefined;
|
|
554
|
+
return (mode: ReplyMode) =>
|
|
555
|
+
openReply({
|
|
556
|
+
threadId: selectedThread.threadId,
|
|
557
|
+
messageId: selectedMessageId,
|
|
558
|
+
mode,
|
|
559
|
+
});
|
|
560
|
+
}, [openReply, selectedThread, selectedMessageId]);
|
|
561
|
+
|
|
562
|
+
const replyToFocusedThread = useMemo(() => {
|
|
563
|
+
if (!focusedThread) return undefined;
|
|
564
|
+
return (mode: ReplyMode) =>
|
|
565
|
+
openReply({
|
|
566
|
+
threadId: focusedThread.threadId,
|
|
567
|
+
messageId: focusedThread.messageId,
|
|
568
|
+
mode,
|
|
569
|
+
});
|
|
570
|
+
}, [openReply, focusedThread]);
|
|
555
571
|
|
|
556
572
|
const isComposing = useIsComposing();
|
|
573
|
+
const isReplying = useIsReplying();
|
|
557
574
|
const openCompose = useOpenCompose();
|
|
558
575
|
|
|
559
576
|
const messageIdsForFocusedThread = useCallback(
|
|
@@ -599,26 +616,18 @@ function MailboxPaneProvider({
|
|
|
599
616
|
senderEmail: focusedThread?.fromEmail ?? undefined,
|
|
600
617
|
});
|
|
601
618
|
|
|
602
|
-
const
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
const
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
const triageReplyAll = useCallback(() => {
|
|
615
|
-
if (ensureFocusedOpen()) return;
|
|
616
|
-
if (selectedThread) setToolbarComposeRequest("reply_all");
|
|
617
|
-
}, [ensureFocusedOpen, selectedThread, setToolbarComposeRequest]);
|
|
618
|
-
const triageForward = useCallback(() => {
|
|
619
|
-
if (ensureFocusedOpen()) return;
|
|
620
|
-
if (selectedThread) setToolbarComposeRequest("forward");
|
|
621
|
-
}, [ensureFocusedOpen, selectedThread, setToolbarComposeRequest]);
|
|
619
|
+
const triageReply = useCallback(
|
|
620
|
+
() => replyToFocusedThread?.("reply"),
|
|
621
|
+
[replyToFocusedThread],
|
|
622
|
+
);
|
|
623
|
+
const triageReplyAll = useCallback(
|
|
624
|
+
() => replyToFocusedThread?.("reply-all"),
|
|
625
|
+
[replyToFocusedThread],
|
|
626
|
+
);
|
|
627
|
+
const triageForward = useCallback(
|
|
628
|
+
() => replyToFocusedThread?.("forward"),
|
|
629
|
+
[replyToFocusedThread],
|
|
630
|
+
);
|
|
622
631
|
|
|
623
632
|
const triageTargetMessageIds = useCallback(
|
|
624
633
|
(): string[] => messageIdsForFocusedThread(focusedThread),
|
|
@@ -732,9 +741,10 @@ function MailboxPaneProvider({
|
|
|
732
741
|
context: triage,
|
|
733
742
|
orderedIds: threads.map((t) => t.messageId),
|
|
734
743
|
selectedMessageId,
|
|
735
|
-
// The list stays mounted under
|
|
736
|
-
// would otherwise fire at the message behind whatever is being typed
|
|
737
|
-
|
|
744
|
+
// The list stays mounted under both writing surfaces, so the triage keys
|
|
745
|
+
// would otherwise fire at the message behind whatever is being typed — or
|
|
746
|
+
// answer a row the cursor moved to while a reply was open.
|
|
747
|
+
enabled: !isComposing && !isReplying,
|
|
738
748
|
onClose: closeThread,
|
|
739
749
|
handlers: {
|
|
740
750
|
reply: triageReply,
|
|
@@ -812,11 +822,7 @@ function MailboxPaneProvider({
|
|
|
812
822
|
onTriageContextChange: handleTriageContextChange,
|
|
813
823
|
listCommandsRef,
|
|
814
824
|
onRetry: () => refetch(),
|
|
815
|
-
|
|
816
|
-
onToolbarReply: handleToolbarReply,
|
|
817
|
-
onToolbarReplyAll: handleToolbarReplyAll,
|
|
818
|
-
onToolbarForward: handleToolbarForward,
|
|
819
|
-
onClearComposeRequest: toolbarActions.clearComposeRequest,
|
|
825
|
+
onReply: replyToOpenThread,
|
|
820
826
|
onToolbarDelete: toolbarActions.deleteThread,
|
|
821
827
|
onToolbarStar: toolbarActions.toggleStar,
|
|
822
828
|
onToolbarMove: toolbarActions.moveThread,
|
|
@@ -1071,11 +1077,7 @@ function MailboxReading() {
|
|
|
1071
1077
|
conversation,
|
|
1072
1078
|
intelligenceOpen,
|
|
1073
1079
|
onToggleIntelligence,
|
|
1074
|
-
|
|
1075
|
-
onToolbarReply,
|
|
1076
|
-
onToolbarReplyAll,
|
|
1077
|
-
onToolbarForward,
|
|
1078
|
-
onClearComposeRequest,
|
|
1080
|
+
onReply,
|
|
1079
1081
|
onToolbarDelete,
|
|
1080
1082
|
onToolbarStar,
|
|
1081
1083
|
onToolbarMove,
|
|
@@ -1135,8 +1137,6 @@ function MailboxReading() {
|
|
|
1135
1137
|
onOpenIntelligence={
|
|
1136
1138
|
conversation.authenticity?.dkimMismatch ? openIntelligence : undefined
|
|
1137
1139
|
}
|
|
1138
|
-
composeRequest={toolbarComposeRequest}
|
|
1139
|
-
onComposeClose={onClearComposeRequest}
|
|
1140
1140
|
/>
|
|
1141
1141
|
) : (
|
|
1142
1142
|
<ReadingPaneEmpty />
|
|
@@ -1150,9 +1150,9 @@ function MailboxReading() {
|
|
|
1150
1150
|
intelligenceOpen={intelligenceShowing}
|
|
1151
1151
|
canToggleIntelligence={hasThread}
|
|
1152
1152
|
onToggleIntelligence={toggleIntelligence}
|
|
1153
|
-
onReply={
|
|
1154
|
-
onReplyAll={
|
|
1155
|
-
onForward={
|
|
1153
|
+
onReply={onReply ? () => onReply("reply") : undefined}
|
|
1154
|
+
onReplyAll={onReply ? () => onReply("reply-all") : undefined}
|
|
1155
|
+
onForward={onReply ? () => onReply("forward") : undefined}
|
|
1156
1156
|
canDelete={hasThread}
|
|
1157
1157
|
onDelete={hasThread ? onToolbarDelete : undefined}
|
|
1158
1158
|
onToggleStar={hasThread ? onToolbarStar : undefined}
|
|
@@ -126,35 +126,55 @@ afterEach(() => {
|
|
|
126
126
|
// `window`.
|
|
127
127
|
(globalThis as { self?: typeof globalThis }).self ??= globalThis;
|
|
128
128
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
129
|
+
const MESSAGE_PATH = `/mail/${MAILBOX_ID}/${THREAD_ID}/${MESSAGE_ID}`;
|
|
130
|
+
const REPLY_PATH = `${MESSAGE_PATH}/reply`;
|
|
131
|
+
|
|
132
|
+
// The conversation is what the message route renders, and the reply is the
|
|
133
|
+
// segment under it — so the tree here is the app's: the folder, the thread, the
|
|
134
|
+
// message, and the mode below it. The address is what opens the reply, so the
|
|
135
|
+
// cases below mount at one or the other.
|
|
136
|
+
const testRouter = (href: string): AnyRouter => {
|
|
133
137
|
// The compose provider sits at the root the way `__root.tsx` mounts it.
|
|
134
138
|
const rootRoute = createRootRoute({
|
|
135
139
|
component: () =>
|
|
136
140
|
createElement(ComposeProvider, null, createElement(Outlet)),
|
|
137
141
|
});
|
|
142
|
+
const mailboxRoute = createRoute({
|
|
143
|
+
getParentRoute: () => rootRoute,
|
|
144
|
+
path: "/mail/$mailboxId",
|
|
145
|
+
validateSearch: (search: Record<string, unknown>) => search,
|
|
146
|
+
});
|
|
147
|
+
const threadRoute = createRoute({
|
|
148
|
+
getParentRoute: () => mailboxRoute,
|
|
149
|
+
path: "$threadId",
|
|
150
|
+
});
|
|
151
|
+
const messageRoute = createRoute({
|
|
152
|
+
getParentRoute: () => threadRoute,
|
|
153
|
+
path: "$messageId",
|
|
154
|
+
component: () =>
|
|
155
|
+
createElement(ConversationView, {
|
|
156
|
+
threadId: THREAD_ID,
|
|
157
|
+
mailboxId: MAILBOX_ID,
|
|
158
|
+
subject: "Lunch Thursday?",
|
|
159
|
+
selectedMessageId: MESSAGE_ID,
|
|
160
|
+
}),
|
|
161
|
+
});
|
|
162
|
+
const replyRoute = createRoute({
|
|
163
|
+
getParentRoute: () => messageRoute,
|
|
164
|
+
path: "$mode/{-$outboxMessageId}",
|
|
165
|
+
});
|
|
138
166
|
const routeTree = rootRoute.addChildren([
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
validateSearch: (search: Record<string, unknown>) => search,
|
|
143
|
-
component: () =>
|
|
144
|
-
createElement(ConversationView, {
|
|
145
|
-
threadId: THREAD_ID,
|
|
146
|
-
mailboxId: MAILBOX_ID,
|
|
147
|
-
subject: "Lunch Thursday?",
|
|
148
|
-
}),
|
|
149
|
-
}),
|
|
167
|
+
mailboxRoute.addChildren([
|
|
168
|
+
threadRoute.addChildren([messageRoute.addChildren([replyRoute])]),
|
|
169
|
+
]),
|
|
150
170
|
]);
|
|
151
171
|
return createRouter({
|
|
152
172
|
routeTree,
|
|
153
|
-
history: createMemoryHistory({ initialEntries: [
|
|
173
|
+
history: createMemoryHistory({ initialEntries: [href] }),
|
|
154
174
|
}) as unknown as AnyRouter;
|
|
155
175
|
};
|
|
156
176
|
|
|
157
|
-
const
|
|
177
|
+
const mountAt = async (href: string): Promise<[DomHarness, AnyRouter]> => {
|
|
158
178
|
http = mockFetch((call) => {
|
|
159
179
|
if (call.path.endsWith("/config")) return { accounts: [account] };
|
|
160
180
|
if (call.path.endsWith(`/threads/${THREAD_ID}/messages`)) {
|
|
@@ -164,17 +184,39 @@ const mount = async (): Promise<DomHarness> => {
|
|
|
164
184
|
return { items: [] };
|
|
165
185
|
});
|
|
166
186
|
|
|
167
|
-
const router = testRouter();
|
|
187
|
+
const router = testRouter(href);
|
|
168
188
|
await router.load();
|
|
169
189
|
harness = createDomHarness();
|
|
170
190
|
harness.renderApp(createElement(RouterProvider, { router }));
|
|
171
191
|
await harness.flush();
|
|
172
192
|
await harness.wait(20);
|
|
173
193
|
await harness.flush();
|
|
174
|
-
return harness;
|
|
194
|
+
return [harness, router];
|
|
175
195
|
};
|
|
176
196
|
|
|
177
|
-
/** The
|
|
197
|
+
/** The conversation, with nothing being written under it. */
|
|
198
|
+
const mount = async (): Promise<DomHarness> => {
|
|
199
|
+
const [mounted] = await mountAt(MESSAGE_PATH);
|
|
200
|
+
return mounted;
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* The same conversation at the address that names a reply — which is the whole
|
|
205
|
+
* of what opens one.
|
|
206
|
+
*
|
|
207
|
+
* A cold load rather than a press: a navigation inside this harness moves the
|
|
208
|
+
* router but never re-renders the tree, so nothing here can say what the pane
|
|
209
|
+
* does as an address changes under it. Every case below is what one address
|
|
210
|
+
* renders, and the transitions between two of them — the reply opening, and its
|
|
211
|
+
* closing again with the conversation left standing — are asserted in
|
|
212
|
+
* `packages/e2e/specs/detail-routes.spec.ts`, against a real browser.
|
|
213
|
+
*/
|
|
214
|
+
const mountReplying = async (): Promise<DomHarness> => {
|
|
215
|
+
const [mounted] = await mountAt(REPLY_PATH);
|
|
216
|
+
return mounted;
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
/** The r shortcut the reading pane binds — how a reader asks for the reply. */
|
|
178
220
|
const pressReply = async (mounted: DomHarness): Promise<void> => {
|
|
179
221
|
mounted.dispatch(
|
|
180
222
|
mounted.window,
|
|
@@ -224,9 +266,7 @@ const verticalScrollers = (root: Element): Element[] =>
|
|
|
224
266
|
|
|
225
267
|
describe("answering the message that is open", () => {
|
|
226
268
|
it("puts the reply in the same scrolling region as the message", async () => {
|
|
227
|
-
const mounted = await
|
|
228
|
-
|
|
229
|
-
await pressReply(mounted);
|
|
269
|
+
const mounted = await mountReplying();
|
|
230
270
|
|
|
231
271
|
const pane = mounted.query("article");
|
|
232
272
|
assert.ok(pane, "the conversation pane is mounted");
|
|
@@ -246,9 +286,7 @@ describe("answering the message that is open", () => {
|
|
|
246
286
|
});
|
|
247
287
|
|
|
248
288
|
it("leads the pane with the reply, above the thread it answers", async () => {
|
|
249
|
-
const mounted = await
|
|
250
|
-
|
|
251
|
-
await pressReply(mounted);
|
|
289
|
+
const mounted = await mountReplying();
|
|
252
290
|
|
|
253
291
|
const compose = mounted.query('[data-testid="compose-body-area"]');
|
|
254
292
|
assert.ok(compose, "the reply opened");
|
|
@@ -313,7 +351,7 @@ describe("answering the message that is open", () => {
|
|
|
313
351
|
);
|
|
314
352
|
});
|
|
315
353
|
|
|
316
|
-
it("gives the pane one scrollbar
|
|
354
|
+
it("gives the pane one scrollbar with nothing being written", async () => {
|
|
317
355
|
const mounted = await mount();
|
|
318
356
|
|
|
319
357
|
const pane = mounted.query("article");
|
|
@@ -323,9 +361,13 @@ describe("answering the message that is open", () => {
|
|
|
323
361
|
1,
|
|
324
362
|
"reading a thread scrolls one thing",
|
|
325
363
|
);
|
|
364
|
+
});
|
|
326
365
|
|
|
327
|
-
|
|
366
|
+
it("gives the pane one scrollbar with the reply open", async () => {
|
|
367
|
+
const mounted = await mountReplying();
|
|
328
368
|
|
|
369
|
+
const pane = mounted.query("article");
|
|
370
|
+
assert.ok(pane, "the conversation pane is mounted");
|
|
329
371
|
assert.equal(
|
|
330
372
|
verticalScrollers(pane).length,
|
|
331
373
|
1,
|
|
@@ -333,6 +375,17 @@ describe("answering the message that is open", () => {
|
|
|
333
375
|
);
|
|
334
376
|
});
|
|
335
377
|
|
|
378
|
+
// The address is the whole of what opens a reply, so what the key does is
|
|
379
|
+
// move it: the mode lands under the message that was open, and the thread
|
|
380
|
+
// that was matched behind it stays matched.
|
|
381
|
+
it("moves the address to the reply under the open message", async () => {
|
|
382
|
+
const [mounted, router] = await mountAt(MESSAGE_PATH);
|
|
383
|
+
|
|
384
|
+
await pressReply(mounted);
|
|
385
|
+
|
|
386
|
+
assert.equal(router.state.location.pathname, REPLY_PATH);
|
|
387
|
+
});
|
|
388
|
+
|
|
336
389
|
it("collapses the message from the chevron beside the sender", async () => {
|
|
337
390
|
const mounted = await mount();
|
|
338
391
|
|
|
@@ -2,7 +2,7 @@ import { useNavigate, useRouterState, useSearch } from "@tanstack/react-router";
|
|
|
2
2
|
import { useEffect, useRef } from "react";
|
|
3
3
|
import { useMailContext } from "@/lib/mail-context";
|
|
4
4
|
import { shouldMirrorQuery } from "@/lib/search-view";
|
|
5
|
-
import { useIsComposing } from "@/routing";
|
|
5
|
+
import { useIsComposing, useIsReplying } from "@/routing";
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* The list the mirror writes to. Each list calls the hook with its own route,
|
|
@@ -44,7 +44,9 @@ export type SearchMirrorTarget =
|
|
|
44
44
|
*
|
|
45
45
|
* A composer is not the pre-search list's leftover either. It is the reader's
|
|
46
46
|
* own unsent message, so a search started while it is up narrows the list
|
|
47
|
-
* behind it and leaves it standing.
|
|
47
|
+
* behind it and leaves it standing. A reply, a reply-all and a forward hold
|
|
48
|
+
* unsent text the same way and are spared the same way. Both are read off the
|
|
49
|
+
* path, so neither has to be kept in step with the surface that is showing.
|
|
48
50
|
*/
|
|
49
51
|
export function useSearchMirror(target: SearchMirrorTarget): void {
|
|
50
52
|
const navigate = useNavigate();
|
|
@@ -52,6 +54,8 @@ export function useSearchMirror(target: SearchMirrorTarget): void {
|
|
|
52
54
|
const { q: urlQuery = "" } = useSearch({ from: "/mail" });
|
|
53
55
|
const pathname = useRouterState({ select: (s) => s.location.pathname });
|
|
54
56
|
const isComposing = useIsComposing();
|
|
57
|
+
const isReplying = useIsReplying();
|
|
58
|
+
const isWriting = isComposing || isReplying;
|
|
55
59
|
|
|
56
60
|
// Read at effect time rather than depended on: the URL is what the mirror
|
|
57
61
|
// compares against, not what re-triggers it.
|
|
@@ -73,10 +77,10 @@ export function useSearchMirror(target: SearchMirrorTarget): void {
|
|
|
73
77
|
if (!mayWrite) return;
|
|
74
78
|
const queryGoesActive =
|
|
75
79
|
Boolean(committedQuery) && urlQueryRef.current !== committedQuery;
|
|
76
|
-
//
|
|
77
|
-
//
|
|
80
|
+
// A message being written is the reader's own, not a leftover of the list
|
|
81
|
+
// they were on, so a query going active narrows the list behind it and
|
|
78
82
|
// leaves it where it is.
|
|
79
|
-
const closesTheOpenSurface = queryGoesActive && !
|
|
83
|
+
const closesTheOpenSurface = queryGoesActive && !isWriting;
|
|
80
84
|
const search = (prev: Record<string, unknown>) => ({
|
|
81
85
|
...prev,
|
|
82
86
|
q: committedQuery || undefined,
|
|
@@ -107,6 +111,6 @@ export function useSearchMirror(target: SearchMirrorTarget): void {
|
|
|
107
111
|
mailboxId,
|
|
108
112
|
listPath,
|
|
109
113
|
pathname,
|
|
110
|
-
|
|
114
|
+
isWriting,
|
|
111
115
|
]);
|
|
112
116
|
}
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* useThreadActions — the reading pane's verbs for one open thread.
|
|
3
3
|
*
|
|
4
|
-
* Delete, move
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* Delete, move and star, over the same mutation hooks the mailbox list uses.
|
|
5
|
+
* The mailbox view keys them by its route; the brief and Flagged are
|
|
6
|
+
* cross-account, so they key by the open thread's own `mailboxId` /
|
|
7
|
+
* `accountId` (#149).
|
|
8
|
+
*
|
|
9
|
+
* Answering a message is not here: reply, reply-all and forward are a segment
|
|
10
|
+
* under the message, so they are a navigation rather than a verb a pane holds.
|
|
8
11
|
*/
|
|
9
12
|
import type { RemitImapThreadMessageResponse } from "@remit/api-http-client/types.gen.ts";
|
|
10
|
-
import { useCallback
|
|
11
|
-
import type { ComposeMode } from "@/components/compose/ComposeProvider";
|
|
13
|
+
import { useCallback } from "react";
|
|
12
14
|
import { useDeleteMessages } from "@/hooks/useDeleteMessages";
|
|
13
15
|
import { useMailboxAccount } from "@/hooks/useMailboxAccount";
|
|
14
16
|
import { useMoveMessages } from "@/hooks/useMoveMessages";
|
|
@@ -31,9 +33,6 @@ export interface ThreadActions {
|
|
|
31
33
|
deleteThread: () => void;
|
|
32
34
|
moveThread: (destinationMailboxId: string) => void;
|
|
33
35
|
toggleStar: () => void;
|
|
34
|
-
composeRequest: ComposeMode | null;
|
|
35
|
-
requestCompose: (mode: ComposeMode) => void;
|
|
36
|
-
clearComposeRequest: () => void;
|
|
37
36
|
}
|
|
38
37
|
|
|
39
38
|
export const useThreadActions = ({
|
|
@@ -91,11 +90,6 @@ export const useThreadActions = ({
|
|
|
91
90
|
toggleStarFor(thread.messageId, thread.hasStars);
|
|
92
91
|
}, [thread, toggleStarFor]);
|
|
93
92
|
|
|
94
|
-
const [composeRequest, setComposeRequest] = useState<ComposeMode | null>(
|
|
95
|
-
null,
|
|
96
|
-
);
|
|
97
|
-
const clearComposeRequest = useCallback(() => setComposeRequest(null), []);
|
|
98
|
-
|
|
99
93
|
return {
|
|
100
94
|
mailboxId: resolvedMailboxId,
|
|
101
95
|
accountId: resolvedAccountId,
|
|
@@ -103,8 +97,5 @@ export const useThreadActions = ({
|
|
|
103
97
|
deleteThread,
|
|
104
98
|
moveThread,
|
|
105
99
|
toggleStar,
|
|
106
|
-
composeRequest,
|
|
107
|
-
requestCompose: setComposeRequest,
|
|
108
|
-
clearComposeRequest,
|
|
109
100
|
};
|
|
110
101
|
};
|