@remit/web-client 0.0.165 → 0.0.167
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 +77 -25
- package/src/components/compose/ComposeProvider.tsx +11 -122
- package/src/components/compose/FullCompose.tsx +29 -18
- package/src/components/compose/InlineCompose.tsx +21 -11
- package/src/components/compose/MobileComposeSheet.tsx +14 -18
- package/src/components/compose/compose-send-stops-autosave.render.test.ts +7 -9
- package/src/components/compose/compose-starts-a-second-message.render.test.ts +170 -0
- package/src/components/compose/compose-title.ts +10 -0
- package/src/components/compose/mobile-header-stays-expanded.render.test.ts +5 -13
- package/src/components/layout/ComposeFab.tsx +6 -15
- package/src/components/layout/MailShell.tsx +9 -1
- package/src/components/layout/MailTopBar.tsx +3 -6
- package/src/components/mail/BriefPane.tsx +6 -5
- package/src/components/mail/DraftsView.tsx +14 -15
- package/src/components/mail/FlaggedPane.tsx +6 -5
- package/src/components/mail/MailboxPane.tsx +36 -124
- package/src/components/mail/MessageActionMenu.tsx +4 -3
- package/src/components/mail/MessageList.tsx +4 -3
- package/src/components/mail/OutboxPane.tsx +8 -13
- package/src/components/mail/SwipeableMessageRow.tsx +4 -3
- package/src/components/mail/intelligence-drawer.stories.tsx +208 -0
- package/src/hooks/useSaveDraft.ts +11 -0
- package/src/hooks/useSearchMirror.ts +12 -1
- package/src/lib/mail-route.test.ts +35 -1
- package/src/lib/mail-route.ts +2 -1
- package/src/routeTree.gen.ts +92 -0
- package/src/routes/mail/$mailboxId/compose.{-$outboxMessageId}.tsx +14 -0
- package/src/routes/mail/brief/compose.{-$outboxMessageId}.tsx +22 -0
- package/src/routes/mail/flagged/compose.{-$outboxMessageId}.tsx +13 -0
- package/src/routes/mail/outbox/compose.{-$outboxMessageId}.tsx +17 -0
- package/src/routes/mail.tsx +12 -8
- package/src/routing/compose-press-opens.render.test.ts +179 -0
- package/src/routing/compose.ts +220 -0
- package/src/routing/fragment.render.test.ts +98 -0
- package/src/routing/fragment.test.ts +54 -9
- package/src/routing/fragment.ts +37 -9
- package/src/routing/index.ts +10 -1
- package/src/routing/nav-link.tsx +5 -3
- package/src/components/compose/compose-clears-open-thread.render.test.ts +0 -312
- package/src/hooks/useComposeTargetMailbox.ts +0 -75
- package/src/lib/compose-routes.test.ts +0 -46
- package/src/lib/compose-routes.ts +0 -25
|
@@ -16,8 +16,6 @@
|
|
|
16
16
|
* On phone, use `<MailboxPane.Phone />` instead of the slot sub-views.
|
|
17
17
|
*/
|
|
18
18
|
import {
|
|
19
|
-
outboxDetailOperationsDeleteOutboxMessageMutation,
|
|
20
|
-
outboxOperationsListOutboxMessagesQueryKey,
|
|
21
19
|
threadOperationsListThreadsQueryKey,
|
|
22
20
|
threadOperationsSearchThreadsQueryKey,
|
|
23
21
|
} from "@remit/api-http-client/@tanstack/react-query.gen.ts";
|
|
@@ -35,11 +33,7 @@ import {
|
|
|
35
33
|
type SearchResult,
|
|
36
34
|
useAppShellLayout,
|
|
37
35
|
} from "@remit/ui";
|
|
38
|
-
import {
|
|
39
|
-
useInfiniteQuery,
|
|
40
|
-
useMutation,
|
|
41
|
-
useQueryClient,
|
|
42
|
-
} from "@tanstack/react-query";
|
|
36
|
+
import { useInfiniteQuery } from "@tanstack/react-query";
|
|
43
37
|
import { useNavigate } from "@tanstack/react-router";
|
|
44
38
|
import {
|
|
45
39
|
createContext,
|
|
@@ -53,8 +47,6 @@ import {
|
|
|
53
47
|
useState,
|
|
54
48
|
} from "react";
|
|
55
49
|
import type { ComposeMode } from "@/components/compose/ComposeProvider";
|
|
56
|
-
import { useCompose } from "@/components/compose/ComposeProvider";
|
|
57
|
-
import { FullCompose } from "@/components/compose/FullCompose";
|
|
58
50
|
import { Drawer } from "@/components/layout/Drawer";
|
|
59
51
|
import { ConversationView } from "@/components/mail/ConversationView";
|
|
60
52
|
import { DraftsView } from "@/components/mail/DraftsView";
|
|
@@ -66,8 +58,6 @@ import {
|
|
|
66
58
|
import { MessageToolbar } from "@/components/mail/MessageToolbar";
|
|
67
59
|
import { PullToRefresh } from "@/components/mail/PullToRefresh";
|
|
68
60
|
import { SpamRescue } from "@/components/mail/SpamRescue";
|
|
69
|
-
import { useErrorBanners } from "@/components/ui/ErrorBannerProvider";
|
|
70
|
-
import { buildMutationErrorBanner } from "@/components/ui/error-banners";
|
|
71
61
|
import {
|
|
72
62
|
useArchiveMailbox,
|
|
73
63
|
useDraftsMailbox,
|
|
@@ -83,7 +73,6 @@ import {
|
|
|
83
73
|
} from "@/hooks/useDeleteMessages";
|
|
84
74
|
import type { EscalationSearchQuery } from "@/hooks/useEscalatedActions";
|
|
85
75
|
import { useIntelligenceData } from "@/hooks/useIntelligenceData";
|
|
86
|
-
import { useKeyboardNavigation } from "@/hooks/useKeyboardNavigation";
|
|
87
76
|
import { useLayoutTier } from "@/hooks/useLayoutTier";
|
|
88
77
|
import { useMailboxAccount } from "@/hooks/useMailboxAccount";
|
|
89
78
|
import { useToggleReadFor } from "@/hooks/useMarkAsRead";
|
|
@@ -127,7 +116,9 @@ import {
|
|
|
127
116
|
import {
|
|
128
117
|
type OpenThreadPath,
|
|
129
118
|
type OpenThreadTarget,
|
|
130
|
-
|
|
119
|
+
useIsComposing,
|
|
120
|
+
useOpenCompose,
|
|
121
|
+
useRetainOpenPanels,
|
|
131
122
|
} from "@/routing";
|
|
132
123
|
import { MailViewChrome } from "./MailViewChrome";
|
|
133
124
|
|
|
@@ -215,11 +206,7 @@ interface MailboxPaneContextValue {
|
|
|
215
206
|
onClearComposeRequest: () => void;
|
|
216
207
|
onToolbarDelete: () => void;
|
|
217
208
|
onToolbarStar: () => void;
|
|
218
|
-
onToolbarDiscardDraft: () => void;
|
|
219
209
|
onToolbarMove: (destMailboxId: string) => void;
|
|
220
|
-
composeState: ReturnType<typeof useCompose>["state"];
|
|
221
|
-
closeCompose: () => void;
|
|
222
|
-
hasRemitDraftOpen: boolean;
|
|
223
210
|
// Phone actions
|
|
224
211
|
onBack: () => void;
|
|
225
212
|
/** The rows either side of the open one — the phone's swipe gestures. */
|
|
@@ -264,6 +251,7 @@ function MailboxPaneProvider({
|
|
|
264
251
|
children,
|
|
265
252
|
}: MailboxPaneProps) {
|
|
266
253
|
const navigate = useNavigate();
|
|
254
|
+
const retainPanels = useRetainOpenPanels();
|
|
267
255
|
const threadId = thread?.threadId;
|
|
268
256
|
const pointedAtMessageId = thread?.messageId;
|
|
269
257
|
const telemetry = useTelemetry();
|
|
@@ -467,9 +455,9 @@ function MailboxPaneProvider({
|
|
|
467
455
|
to: "/mail/$mailboxId",
|
|
468
456
|
params: { mailboxId },
|
|
469
457
|
search: (prev) => prev,
|
|
470
|
-
hash:
|
|
458
|
+
hash: retainPanels,
|
|
471
459
|
});
|
|
472
|
-
}, [mailboxId, navigate]);
|
|
460
|
+
}, [mailboxId, navigate, retainPanels]);
|
|
473
461
|
|
|
474
462
|
const handleOpenThread = useCallback(
|
|
475
463
|
(target: OpenThreadTarget) => {
|
|
@@ -477,10 +465,10 @@ function MailboxPaneProvider({
|
|
|
477
465
|
to: "/mail/$mailboxId/$threadId/$messageId",
|
|
478
466
|
params: { mailboxId, ...target },
|
|
479
467
|
search: (prev) => prev,
|
|
480
|
-
hash:
|
|
468
|
+
hash: retainPanels,
|
|
481
469
|
});
|
|
482
470
|
},
|
|
483
|
-
[mailboxId, navigate],
|
|
471
|
+
[mailboxId, navigate, retainPanels],
|
|
484
472
|
);
|
|
485
473
|
|
|
486
474
|
const handleDeselectIfRemoved = useCallback(
|
|
@@ -538,9 +526,6 @@ function MailboxPaneProvider({
|
|
|
538
526
|
// so there is no fallback: until the mailbox resolves there is no number.
|
|
539
527
|
const unreadCount = useCurrentMailboxUnseenCount({ accounts }) ?? 0;
|
|
540
528
|
|
|
541
|
-
const queryClient = useQueryClient();
|
|
542
|
-
const { pushError } = useErrorBanners();
|
|
543
|
-
|
|
544
529
|
const toolbarActions = useThreadActions({
|
|
545
530
|
thread: selectedThread,
|
|
546
531
|
mailboxId,
|
|
@@ -568,48 +553,8 @@ function MailboxPaneProvider({
|
|
|
568
553
|
setToolbarComposeRequest("forward");
|
|
569
554
|
}, [setToolbarComposeRequest]);
|
|
570
555
|
|
|
571
|
-
const
|
|
572
|
-
|
|
573
|
-
const handleNewCompose = useCallback(() => {
|
|
574
|
-
openCompose({ mode: "new" });
|
|
575
|
-
}, [openCompose]);
|
|
576
|
-
|
|
577
|
-
// A thread opening closes compose. Only a selection arriving counts, so this
|
|
578
|
-
// cannot close the compose that just cleared one.
|
|
579
|
-
const previousSelectionRef = useRef(selectedMessageId);
|
|
580
|
-
useEffect(() => {
|
|
581
|
-
const previous = previousSelectionRef.current;
|
|
582
|
-
previousSelectionRef.current = selectedMessageId;
|
|
583
|
-
if (!selectedMessageId || selectedMessageId === previous) return;
|
|
584
|
-
closeCompose();
|
|
585
|
-
}, [selectedMessageId, closeCompose]);
|
|
586
|
-
|
|
587
|
-
const deleteOutboxMutation = useMutation({
|
|
588
|
-
...outboxDetailOperationsDeleteOutboxMessageMutation(),
|
|
589
|
-
onError: (mutationError) => {
|
|
590
|
-
pushError(
|
|
591
|
-
buildMutationErrorBanner(
|
|
592
|
-
"Couldn't discard draft",
|
|
593
|
-
"The draft wasn't deleted.",
|
|
594
|
-
mutationError,
|
|
595
|
-
),
|
|
596
|
-
);
|
|
597
|
-
},
|
|
598
|
-
});
|
|
599
|
-
const handleToolbarDiscardDraft = useCallback(() => {
|
|
600
|
-
const outboxMessageId = composeState.outboxMessageId;
|
|
601
|
-
if (!outboxMessageId) return;
|
|
602
|
-
deleteOutboxMutation.mutate({ path: { outboxMessageId } });
|
|
603
|
-
queryClient.invalidateQueries({
|
|
604
|
-
queryKey: outboxOperationsListOutboxMessagesQueryKey(),
|
|
605
|
-
});
|
|
606
|
-
closeCompose();
|
|
607
|
-
}, [
|
|
608
|
-
composeState.outboxMessageId,
|
|
609
|
-
deleteOutboxMutation,
|
|
610
|
-
queryClient,
|
|
611
|
-
closeCompose,
|
|
612
|
-
]);
|
|
556
|
+
const isComposing = useIsComposing();
|
|
557
|
+
const openCompose = useOpenCompose();
|
|
613
558
|
|
|
614
559
|
const messageIdsForFocusedThread = useCallback(
|
|
615
560
|
(thread: typeof focusedThread): string[] => {
|
|
@@ -783,17 +728,13 @@ function MailboxPaneProvider({
|
|
|
783
728
|
}
|
|
784
729
|
}, [normalizedSearchQuery, mailboxType, telemetry]);
|
|
785
730
|
|
|
786
|
-
const hasRemitDraftOpen =
|
|
787
|
-
isDraftsMailbox &&
|
|
788
|
-
composeState.isOpen &&
|
|
789
|
-
!!composeState.outboxMessageId &&
|
|
790
|
-
!selectedThread;
|
|
791
|
-
|
|
792
731
|
const { goBack, nextMessageId, previousMessageId } = useTriageLayer({
|
|
793
732
|
context: triage,
|
|
794
733
|
orderedIds: threads.map((t) => t.messageId),
|
|
795
734
|
selectedMessageId,
|
|
796
|
-
|
|
735
|
+
// The list stays mounted under the compose surface, so the triage keys
|
|
736
|
+
// would otherwise fire at the message behind whatever is being typed.
|
|
737
|
+
enabled: !isComposing,
|
|
797
738
|
onClose: closeThread,
|
|
798
739
|
handlers: {
|
|
799
740
|
reply: triageReply,
|
|
@@ -807,7 +748,7 @@ function MailboxPaneProvider({
|
|
|
807
748
|
vipSender: triageVip,
|
|
808
749
|
markJunk: triageMarkJunk,
|
|
809
750
|
toggleIntelligence: selectedThread ? onToggleIntelligence : undefined,
|
|
810
|
-
compose:
|
|
751
|
+
compose: openCompose,
|
|
811
752
|
goBrief: () => goToRoute("/mail/brief"),
|
|
812
753
|
goInbox: () => goToRoute("/mail/brief"),
|
|
813
754
|
goSent: () => goToRoute("/mail/brief"),
|
|
@@ -816,11 +757,6 @@ function MailboxPaneProvider({
|
|
|
816
757
|
},
|
|
817
758
|
});
|
|
818
759
|
|
|
819
|
-
useKeyboardNavigation({
|
|
820
|
-
enabled: composeState.isOpen,
|
|
821
|
-
bindings: [{ key: "Escape", handler: closeCompose, preventDefault: true }],
|
|
822
|
-
});
|
|
823
|
-
|
|
824
760
|
// The swipe gestures open a whole conversation, so the adjacent row has to
|
|
825
761
|
// name its thread. This folder's own listing is where that is looked up, so a
|
|
826
762
|
// row it does not hold offers no gesture rather than a tap that goes nowhere.
|
|
@@ -883,11 +819,7 @@ function MailboxPaneProvider({
|
|
|
883
819
|
onClearComposeRequest: toolbarActions.clearComposeRequest,
|
|
884
820
|
onToolbarDelete: toolbarActions.deleteThread,
|
|
885
821
|
onToolbarStar: toolbarActions.toggleStar,
|
|
886
|
-
onToolbarDiscardDraft: handleToolbarDiscardDraft,
|
|
887
822
|
onToolbarMove: toolbarActions.moveThread,
|
|
888
|
-
composeState,
|
|
889
|
-
closeCompose,
|
|
890
|
-
hasRemitDraftOpen,
|
|
891
823
|
onBack: goBack,
|
|
892
824
|
nextThread: adjacentThread(nextMessageId),
|
|
893
825
|
previousThread: adjacentThread(previousMessageId),
|
|
@@ -942,6 +874,7 @@ function MailboxList() {
|
|
|
942
874
|
useMailContext();
|
|
943
875
|
const tier = useLayoutTier();
|
|
944
876
|
const navigate = useNavigate();
|
|
877
|
+
const retainPanels = useRetainOpenPanels();
|
|
945
878
|
|
|
946
879
|
const listTitle = mailboxName ?? "Inbox";
|
|
947
880
|
const preset = useMemo(() => inboxFilterConfig(), []);
|
|
@@ -1029,10 +962,10 @@ function MailboxList() {
|
|
|
1029
962
|
// `searchInput`: a row can be tapped before the debounce settles, when
|
|
1030
963
|
// the committed query is still empty.
|
|
1031
964
|
search: (prev) => ({ ...prev, q: searchInput || undefined }),
|
|
1032
|
-
hash:
|
|
965
|
+
hash: retainPanels,
|
|
1033
966
|
});
|
|
1034
967
|
},
|
|
1035
|
-
[mailboxId, navigate, searchInput, threads],
|
|
968
|
+
[mailboxId, navigate, retainPanels, searchInput, threads],
|
|
1036
969
|
);
|
|
1037
970
|
|
|
1038
971
|
// Drafts keep their own dedicated view (and header); they don't carry the
|
|
@@ -1136,7 +1069,6 @@ function MailboxReading() {
|
|
|
1136
1069
|
mailboxAccountId,
|
|
1137
1070
|
selectedThread,
|
|
1138
1071
|
conversation,
|
|
1139
|
-
hasRemitDraftOpen,
|
|
1140
1072
|
intelligenceOpen,
|
|
1141
1073
|
onToggleIntelligence,
|
|
1142
1074
|
toolbarComposeRequest,
|
|
@@ -1146,9 +1078,7 @@ function MailboxReading() {
|
|
|
1146
1078
|
onClearComposeRequest,
|
|
1147
1079
|
onToolbarDelete,
|
|
1148
1080
|
onToolbarStar,
|
|
1149
|
-
onToolbarDiscardDraft,
|
|
1150
1081
|
onToolbarMove,
|
|
1151
|
-
composeState,
|
|
1152
1082
|
handleDeselectIfRemoved,
|
|
1153
1083
|
} = useMailboxPane();
|
|
1154
1084
|
// Which surface intelligence has here: the rail between 1280 and up, the
|
|
@@ -1195,25 +1125,22 @@ function MailboxReading() {
|
|
|
1195
1125
|
const intelligenceShowing =
|
|
1196
1126
|
hasThread && (railFits ? intelligenceOpen : drawerOpen);
|
|
1197
1127
|
|
|
1198
|
-
const detailPane =
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
) : (
|
|
1215
|
-
<ReadingPaneEmpty />
|
|
1216
|
-
);
|
|
1128
|
+
const detailPane = conversation ? (
|
|
1129
|
+
<ConversationView
|
|
1130
|
+
threadId={conversation.threadId}
|
|
1131
|
+
mailboxId={conversation.mailboxId}
|
|
1132
|
+
subject={conversation.subject}
|
|
1133
|
+
selectedMessageId={conversation.messageId}
|
|
1134
|
+
authenticity={conversation.authenticity}
|
|
1135
|
+
onOpenIntelligence={
|
|
1136
|
+
conversation.authenticity?.dkimMismatch ? openIntelligence : undefined
|
|
1137
|
+
}
|
|
1138
|
+
composeRequest={toolbarComposeRequest}
|
|
1139
|
+
onComposeClose={onClearComposeRequest}
|
|
1140
|
+
/>
|
|
1141
|
+
) : (
|
|
1142
|
+
<ReadingPaneEmpty />
|
|
1143
|
+
);
|
|
1217
1144
|
|
|
1218
1145
|
return (
|
|
1219
1146
|
<>
|
|
@@ -1226,14 +1153,8 @@ function MailboxReading() {
|
|
|
1226
1153
|
onReply={hasThread ? onToolbarReply : undefined}
|
|
1227
1154
|
onReplyAll={hasThread ? onToolbarReplyAll : undefined}
|
|
1228
1155
|
onForward={hasThread ? onToolbarForward : undefined}
|
|
1229
|
-
canDelete={hasThread
|
|
1230
|
-
onDelete={
|
|
1231
|
-
hasThread
|
|
1232
|
-
? onToolbarDelete
|
|
1233
|
-
: hasRemitDraftOpen
|
|
1234
|
-
? onToolbarDiscardDraft
|
|
1235
|
-
: undefined
|
|
1236
|
-
}
|
|
1156
|
+
canDelete={hasThread}
|
|
1157
|
+
onDelete={hasThread ? onToolbarDelete : undefined}
|
|
1237
1158
|
onToggleStar={hasThread ? onToolbarStar : undefined}
|
|
1238
1159
|
isStarred={selectedThread?.hasStars}
|
|
1239
1160
|
moveContext={
|
|
@@ -1307,7 +1228,6 @@ function MailboxPhone() {
|
|
|
1307
1228
|
onBack,
|
|
1308
1229
|
nextThread,
|
|
1309
1230
|
previousThread,
|
|
1310
|
-
composeState,
|
|
1311
1231
|
handleDeselectIfRemoved,
|
|
1312
1232
|
} = useMailboxPane();
|
|
1313
1233
|
|
|
@@ -1347,14 +1267,6 @@ function MailboxPhone() {
|
|
|
1347
1267
|
);
|
|
1348
1268
|
}
|
|
1349
1269
|
|
|
1350
|
-
if (composeState.isOpen) {
|
|
1351
|
-
return (
|
|
1352
|
-
<div className="h-full">
|
|
1353
|
-
<FullCompose />
|
|
1354
|
-
</div>
|
|
1355
|
-
);
|
|
1356
|
-
}
|
|
1357
|
-
|
|
1358
1270
|
return <MailboxList />;
|
|
1359
1271
|
}
|
|
1360
1272
|
|
|
@@ -35,7 +35,7 @@ import {
|
|
|
35
35
|
type ThreadListSnapshotEntry,
|
|
36
36
|
threadListCacheKeys,
|
|
37
37
|
} from "@/lib/thread-list-cache";
|
|
38
|
-
import {
|
|
38
|
+
import { useOpenThreadPath, useRetainOpenPanels } from "@/routing";
|
|
39
39
|
import { MoveToTrigger } from "./MoveToTrigger";
|
|
40
40
|
|
|
41
41
|
interface ThreadMessagesData {
|
|
@@ -103,6 +103,7 @@ export const MessageActionMenu = ({
|
|
|
103
103
|
}: MessageActionMenuProps) => {
|
|
104
104
|
const queryClient = useQueryClient();
|
|
105
105
|
const navigate = useNavigate();
|
|
106
|
+
const retainPanels = useRetainOpenPanels();
|
|
106
107
|
const { pushError } = useErrorBanners();
|
|
107
108
|
const selectedMessageId = useOpenThreadPath()?.messageId;
|
|
108
109
|
|
|
@@ -193,10 +194,10 @@ export const MessageActionMenu = ({
|
|
|
193
194
|
to: "/mail/$mailboxId",
|
|
194
195
|
params: { mailboxId },
|
|
195
196
|
search: (prev) => prev,
|
|
196
|
-
hash:
|
|
197
|
+
hash: retainPanels,
|
|
197
198
|
});
|
|
198
199
|
},
|
|
199
|
-
[selectedMessageId, mailboxId, navigate],
|
|
200
|
+
[selectedMessageId, mailboxId, navigate, retainPanels],
|
|
200
201
|
);
|
|
201
202
|
|
|
202
203
|
const { deleteMessages, isPending: isDeleting } = useDeleteMessages({
|
|
@@ -53,7 +53,7 @@ import { useListHeaderChrome } from "@/lib/list-header-chrome";
|
|
|
53
53
|
import { listVerbRequest } from "@/lib/list-verb-request";
|
|
54
54
|
import { shouldExitSelectionOnNavigate } from "@/lib/selection-mode";
|
|
55
55
|
import { useSelectionWizard, useWizardStepValue } from "@/lib/wizard-history";
|
|
56
|
-
import {
|
|
56
|
+
import { useRetainOpenPanels } from "@/routing";
|
|
57
57
|
import { LabelApplyTrigger } from "./LabelApplyTrigger";
|
|
58
58
|
import {
|
|
59
59
|
type EscalatedSelection,
|
|
@@ -234,6 +234,7 @@ export const MessageList = ({
|
|
|
234
234
|
}: MessageListProps) => {
|
|
235
235
|
const parentRef = useRef<HTMLDivElement>(null);
|
|
236
236
|
const navigate = useNavigate();
|
|
237
|
+
const retainPanels = useRetainOpenPanels();
|
|
237
238
|
const isDesktop = useIsDesktop();
|
|
238
239
|
const wizard = useSelectionWizard();
|
|
239
240
|
const { verb: wizardVerb, start: startWizard, startFromSearch } = wizard;
|
|
@@ -635,11 +636,11 @@ export const MessageList = ({
|
|
|
635
636
|
to: "/mail/$mailboxId/$threadId/$messageId",
|
|
636
637
|
params: { mailboxId, threadId, messageId },
|
|
637
638
|
search: (prev) => prev,
|
|
638
|
-
hash:
|
|
639
|
+
hash: retainPanels,
|
|
639
640
|
replace: options?.replace,
|
|
640
641
|
});
|
|
641
642
|
},
|
|
642
|
-
[navigate, mailboxId, threads],
|
|
643
|
+
[navigate, retainPanels, mailboxId, threads],
|
|
643
644
|
);
|
|
644
645
|
|
|
645
646
|
// Enter: open the focused row in the reading pane. This is the focus→open
|
|
@@ -48,7 +48,6 @@ import {
|
|
|
48
48
|
useMemo,
|
|
49
49
|
useRef,
|
|
50
50
|
} from "react";
|
|
51
|
-
import { useCompose } from "@/components/compose/ComposeProvider";
|
|
52
51
|
import { MessageBody } from "@/components/mail/MessageBody";
|
|
53
52
|
import { NavMenuButton } from "@/components/mail/NavMenuButton";
|
|
54
53
|
import { useErrorBanners } from "@/components/ui/ErrorBannerProvider";
|
|
@@ -63,7 +62,7 @@ import {
|
|
|
63
62
|
import { isOutboxListRow, isUnsendableStatus } from "@/lib/outbox-status";
|
|
64
63
|
import { normalizeSearchQuery } from "@/lib/search-query";
|
|
65
64
|
import { parseSearchTokens } from "@/lib/search-tokens";
|
|
66
|
-
import {
|
|
65
|
+
import { useEditDraft, useRetainOpenPanels } from "@/routing";
|
|
67
66
|
|
|
68
67
|
/* ------------------------------------------------------------------ */
|
|
69
68
|
/* Helpers (shared between List, Reading, Phone sub-views) */
|
|
@@ -174,6 +173,7 @@ function OutboxPaneProvider({
|
|
|
174
173
|
children,
|
|
175
174
|
}: OutboxPaneProps) {
|
|
176
175
|
const navigate = useNavigate();
|
|
176
|
+
const retainPanels = useRetainOpenPanels();
|
|
177
177
|
|
|
178
178
|
const { data: outboxResponse, isLoading } = useQuery(
|
|
179
179
|
outboxOperationsListOutboxMessagesOptions(),
|
|
@@ -219,19 +219,19 @@ function OutboxPaneProvider({
|
|
|
219
219
|
to: "/mail/outbox/draft/$outboxMessageId",
|
|
220
220
|
params: { outboxMessageId },
|
|
221
221
|
search: (prev) => prev,
|
|
222
|
-
hash:
|
|
222
|
+
hash: retainPanels,
|
|
223
223
|
});
|
|
224
224
|
},
|
|
225
|
-
[navigate],
|
|
225
|
+
[navigate, retainPanels],
|
|
226
226
|
);
|
|
227
227
|
|
|
228
228
|
const handleCloseMessage = useCallback(() => {
|
|
229
229
|
navigate({
|
|
230
230
|
to: "/mail/outbox",
|
|
231
231
|
search: (prev) => prev,
|
|
232
|
-
hash:
|
|
232
|
+
hash: retainPanels,
|
|
233
233
|
});
|
|
234
|
-
}, [navigate]);
|
|
234
|
+
}, [navigate, retainPanels]);
|
|
235
235
|
|
|
236
236
|
const ctx: OutboxPaneContextValue = {
|
|
237
237
|
messages,
|
|
@@ -265,7 +265,7 @@ function OutboxMessageRow({
|
|
|
265
265
|
onSelect,
|
|
266
266
|
}: OutboxMessageRowProps) {
|
|
267
267
|
const queryClient = useQueryClient();
|
|
268
|
-
const
|
|
268
|
+
const editDraft = useEditDraft();
|
|
269
269
|
const { pushError } = useErrorBanners();
|
|
270
270
|
|
|
271
271
|
const invalidateOutbox = useCallback(() => {
|
|
@@ -330,12 +330,7 @@ function OutboxMessageRow({
|
|
|
330
330
|
: undefined
|
|
331
331
|
}
|
|
332
332
|
retrying={retryMutation.isPending}
|
|
333
|
-
onEdit={() =>
|
|
334
|
-
openCompose({
|
|
335
|
-
mode: "new",
|
|
336
|
-
outboxMessageId: message.outboxMessageId,
|
|
337
|
-
})
|
|
338
|
-
}
|
|
333
|
+
onEdit={() => editDraft(message.outboxMessageId)}
|
|
339
334
|
onDelete={() =>
|
|
340
335
|
deleteMutation.mutate({
|
|
341
336
|
path: { outboxMessageId: message.outboxMessageId },
|
|
@@ -10,7 +10,7 @@ import { useNavigate } from "@tanstack/react-router";
|
|
|
10
10
|
import { useCallback, useState } from "react";
|
|
11
11
|
import { toDisplayCategory } from "@/lib/display-category";
|
|
12
12
|
import { formatEmailDate } from "@/lib/format";
|
|
13
|
-
import {
|
|
13
|
+
import { useRetainOpenPanels } from "@/routing";
|
|
14
14
|
import { MessageListItem } from "./MessageListItem";
|
|
15
15
|
import { useModifierSelect } from "./useModifierSelect";
|
|
16
16
|
|
|
@@ -75,6 +75,7 @@ export const SwipeableMessageRow = ({
|
|
|
75
75
|
}: SwipeableMessageRowProps) => {
|
|
76
76
|
const [peek, setPeek] = useState<SwipePeek>("none");
|
|
77
77
|
const navigate = useNavigate();
|
|
78
|
+
const retainPanels = useRetainOpenPanels();
|
|
78
79
|
|
|
79
80
|
const handleAct = useCallback(
|
|
80
81
|
(side: "leading" | "trailing") => {
|
|
@@ -106,9 +107,9 @@ export const SwipeableMessageRow = ({
|
|
|
106
107
|
messageId: thread.messageId,
|
|
107
108
|
},
|
|
108
109
|
search: (prev) => prev,
|
|
109
|
-
hash:
|
|
110
|
+
hash: retainPanels,
|
|
110
111
|
});
|
|
111
|
-
}, [navigate, mailboxId, thread.threadId, thread.messageId]);
|
|
112
|
+
}, [navigate, retainPanels, mailboxId, thread.threadId, thread.messageId]);
|
|
112
113
|
|
|
113
114
|
const modifierSelect = useModifierSelect(thread.messageId, onRowSelect);
|
|
114
115
|
|