@remit/web-client 0.0.202 → 0.0.204
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/spellcheck/vite-plugin-licence.test.ts +66 -18
- package/src/components/compose/ComposeSmtpMissingBanner.tsx +3 -6
- package/src/components/layout/ComposeFab.tsx +3 -5
- package/src/components/layout/MailTopBar.tsx +3 -4
- package/src/components/mail/BriefPane.tsx +14 -31
- package/src/components/mail/DailyBrief.tsx +9 -12
- package/src/components/mail/DraftsView.tsx +3 -17
- package/src/components/mail/FlaggedList.tsx +1 -1
- package/src/components/mail/FlaggedPane.tsx +14 -31
- package/src/components/mail/MailListHeader.tsx +10 -11
- package/src/components/mail/MailSidebarAdapter.tsx +4 -25
- package/src/components/mail/MailboxPane.tsx +21 -59
- package/src/components/mail/MessageActionMenu.tsx +4 -11
- package/src/components/mail/MessageList.tsx +9 -13
- package/src/components/mail/OutboxPane.tsx +9 -26
- package/src/components/mail/SelectionWizardHost.tsx +1 -1
- package/src/components/mail/SwipeableMessageRow.tsx +4 -15
- package/src/hooks/useCurrentMailboxName.ts +5 -9
- package/src/hooks/useSearchScope.ts +6 -12
- package/src/hooks/useSearchTokenContext.ts +3 -2
- package/src/hooks/useSemanticSearch.ts +3 -3
- package/src/lib/mail-route.ts +10 -0
- package/src/lib/search-scope.test.ts +45 -32
- package/src/lib/search-scope.ts +14 -19
- package/src/lib/search-view.test.ts +1 -1
- package/src/lib/search-view.ts +2 -2
- package/src/routes/mail/$mailboxId.tsx +1 -2
- package/src/routes/mail/brief.tsx +1 -2
- package/src/routes/mail/flagged.tsx +1 -2
- package/src/routes/mail/outbox.tsx +1 -2
- package/src/routes/mail.tsx +7 -3
- package/src/routing/browsed-list.ts +60 -9
- package/src/routing/compose.ts +2 -27
- package/src/routing/index.ts +40 -1
- package/src/routing/nav-selection.ts +24 -0
- package/src/routing/open-thread.ts +89 -2
- package/src/routing/outbox-draft.ts +27 -1
- package/src/{hooks/useSearchField.render.test.ts → routing/search-field.render.test.ts} +1 -1
- package/src/{hooks/useSearchField.ts → routing/search-field.ts} +1 -1
- package/src/{hooks → routing}/search-mirror-convergence.render.test.ts +1 -1
- package/src/{hooks → routing}/search-mirror-detail.render.test.ts +1 -1
- package/src/{hooks/useSearchMirror.ts → routing/search-mirror.ts} +2 -1
- package/src/routing/search-navigation.ts +44 -0
- package/src/routing/sections.ts +53 -0
- /package/src/{lib → routing}/wizard-history.test.ts +0 -0
- /package/src/{lib → routing}/wizard-history.ts +0 -0
|
@@ -33,7 +33,6 @@ import {
|
|
|
33
33
|
type SearchResult,
|
|
34
34
|
} from "@remit/ui";
|
|
35
35
|
import { useInfiniteQuery } from "@tanstack/react-query";
|
|
36
|
-
import { useNavigate } from "@tanstack/react-router";
|
|
37
36
|
import {
|
|
38
37
|
createContext,
|
|
39
38
|
type ReactNode,
|
|
@@ -127,11 +126,13 @@ import {
|
|
|
127
126
|
type OpenThreadTarget,
|
|
128
127
|
type ReplyMode,
|
|
129
128
|
replyToThread,
|
|
129
|
+
useCloseThread,
|
|
130
|
+
useGoToSection,
|
|
130
131
|
useIsComposing,
|
|
131
132
|
useIsReplying,
|
|
132
133
|
useOpenCompose,
|
|
133
134
|
useOpenReply,
|
|
134
|
-
|
|
135
|
+
useOpenThread,
|
|
135
136
|
} from "@/routing";
|
|
136
137
|
import { MailViewChrome } from "./MailViewChrome";
|
|
137
138
|
|
|
@@ -270,8 +271,9 @@ function MailboxPaneProvider({
|
|
|
270
271
|
thread,
|
|
271
272
|
children,
|
|
272
273
|
}: MailboxPaneProps) {
|
|
273
|
-
const
|
|
274
|
-
const
|
|
274
|
+
const openThread = useOpenThread();
|
|
275
|
+
const closeThread = useCloseThread();
|
|
276
|
+
const goToSection = useGoToSection();
|
|
275
277
|
const threadId = thread?.threadId;
|
|
276
278
|
const pointedAtMessageId = thread?.messageId;
|
|
277
279
|
const telemetry = useTelemetry();
|
|
@@ -470,26 +472,6 @@ function MailboxPaneProvider({
|
|
|
470
472
|
// Esc unwinds one step at a time: an active selection first (handled by the
|
|
471
473
|
// triage layer), then the open thread — which is a navigation up to the list,
|
|
472
474
|
// so nothing is left mounted below it.
|
|
473
|
-
const closeThread = useCallback(() => {
|
|
474
|
-
navigate({
|
|
475
|
-
to: "/mail/$mailboxId",
|
|
476
|
-
params: { mailboxId },
|
|
477
|
-
search: (prev) => prev,
|
|
478
|
-
hash: retainPanels,
|
|
479
|
-
});
|
|
480
|
-
}, [mailboxId, navigate, retainPanels]);
|
|
481
|
-
|
|
482
|
-
const handleOpenThread = useCallback(
|
|
483
|
-
(target: OpenThreadTarget) => {
|
|
484
|
-
navigate({
|
|
485
|
-
to: "/mail/$mailboxId/$threadId/$messageId",
|
|
486
|
-
params: { mailboxId, ...target },
|
|
487
|
-
search: (prev) => prev,
|
|
488
|
-
hash: retainPanels,
|
|
489
|
-
});
|
|
490
|
-
},
|
|
491
|
-
[mailboxId, navigate, retainPanels],
|
|
492
|
-
);
|
|
493
475
|
|
|
494
476
|
const handleDeselectIfRemoved = useCallback(
|
|
495
477
|
(removedIds: string[]) => {
|
|
@@ -716,13 +698,6 @@ function MailboxPaneProvider({
|
|
|
716
698
|
intelligenceRef.current?.open();
|
|
717
699
|
}, []);
|
|
718
700
|
|
|
719
|
-
const goToRoute = useCallback(
|
|
720
|
-
(to: "/mail/brief" | "/mail/flagged" | "/settings") => {
|
|
721
|
-
navigate({ to });
|
|
722
|
-
},
|
|
723
|
-
[navigate],
|
|
724
|
-
);
|
|
725
|
-
|
|
726
701
|
const mailboxType = isDraftsMailbox
|
|
727
702
|
? "drafts"
|
|
728
703
|
: archiveMailboxId === mailboxId
|
|
@@ -764,11 +739,11 @@ function MailboxPaneProvider({
|
|
|
764
739
|
? () => intelligenceRef.current?.toggle()
|
|
765
740
|
: undefined,
|
|
766
741
|
compose: openCompose,
|
|
767
|
-
goBrief: () =>
|
|
768
|
-
goInbox: () =>
|
|
769
|
-
goSent: () =>
|
|
770
|
-
goFlagged: () =>
|
|
771
|
-
goSettings: () =>
|
|
742
|
+
goBrief: () => goToSection("brief"),
|
|
743
|
+
goInbox: () => goToSection("brief"),
|
|
744
|
+
goSent: () => goToSection("brief"),
|
|
745
|
+
goFlagged: () => goToSection("flagged"),
|
|
746
|
+
goSettings: () => goToSection("settings"),
|
|
772
747
|
},
|
|
773
748
|
});
|
|
774
749
|
|
|
@@ -788,7 +763,7 @@ function MailboxPaneProvider({
|
|
|
788
763
|
selectedMessageId,
|
|
789
764
|
selectedThread,
|
|
790
765
|
conversation,
|
|
791
|
-
onOpenThread:
|
|
766
|
+
onOpenThread: openThread,
|
|
792
767
|
threads,
|
|
793
768
|
isLoading,
|
|
794
769
|
isError,
|
|
@@ -885,8 +860,7 @@ function MailboxList() {
|
|
|
885
860
|
const { searchQuery, searchInput, accounts, resultFolderIndex } =
|
|
886
861
|
useMailContext();
|
|
887
862
|
const tier = useLayoutTier();
|
|
888
|
-
const
|
|
889
|
-
const retainPanels = useRetainOpenPanels();
|
|
863
|
+
const openThread = useOpenThread();
|
|
890
864
|
|
|
891
865
|
const listTitle = mailboxName ?? "Inbox";
|
|
892
866
|
const preset = useMemo(() => inboxFilterConfig(), []);
|
|
@@ -970,26 +944,15 @@ function MailboxList() {
|
|
|
970
944
|
result.threadId ??
|
|
971
945
|
threads.find((thread) => thread.messageId === result.id)?.threadId;
|
|
972
946
|
if (!threadId) return;
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
threadId,
|
|
981
|
-
messageId: result.id,
|
|
982
|
-
},
|
|
983
|
-
// Commit the active query with the open so the debounced q-mirror —
|
|
984
|
-
// which walks back up to the list when the query goes active — is
|
|
985
|
-
// already satisfied and leaves the conversation alone. The *live*
|
|
986
|
-
// `searchInput`: a row can be tapped before the debounce settles, when
|
|
987
|
-
// the committed query is still empty.
|
|
988
|
-
search: (prev) => ({ ...prev, q: searchInput || undefined }),
|
|
989
|
-
hash: retainPanels,
|
|
990
|
-
});
|
|
947
|
+
// Both sections are scoped to this mailbox, so a result's own mailbox is
|
|
948
|
+
// normally this one; keep reading it off the result so a row can never
|
|
949
|
+
// open under a mailbox it does not belong to.
|
|
950
|
+
openThread(
|
|
951
|
+
{ threadId, messageId: result.id },
|
|
952
|
+
{ mailboxId: result.mailboxId ?? mailboxId, query: searchInput },
|
|
953
|
+
);
|
|
991
954
|
},
|
|
992
|
-
[mailboxId,
|
|
955
|
+
[mailboxId, openThread, searchInput, threads],
|
|
993
956
|
);
|
|
994
957
|
|
|
995
958
|
// Drafts keep their own dedicated view (and header); they don't carry the
|
|
@@ -997,7 +960,6 @@ function MailboxList() {
|
|
|
997
960
|
if (isDraftsMailbox && mailboxAccountId) {
|
|
998
961
|
return (
|
|
999
962
|
<DraftsView
|
|
1000
|
-
mailboxId={mailboxId}
|
|
1001
963
|
accountId={mailboxAccountId}
|
|
1002
964
|
selectedMessageId={selectedMessageId}
|
|
1003
965
|
imapThreads={threads}
|
|
@@ -3,7 +3,6 @@ import {
|
|
|
3
3
|
threadDetailOperationsListThreadMessagesQueryKey,
|
|
4
4
|
} from "@remit/api-http-client/@tanstack/react-query.gen.ts";
|
|
5
5
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
|
6
|
-
import { useNavigate } from "@tanstack/react-router";
|
|
7
6
|
import {
|
|
8
7
|
BadgeCheck,
|
|
9
8
|
Check,
|
|
@@ -35,7 +34,7 @@ import {
|
|
|
35
34
|
type ThreadMutationContext,
|
|
36
35
|
threadListCacheKeys,
|
|
37
36
|
} from "@/lib/thread-list-cache";
|
|
38
|
-
import {
|
|
37
|
+
import { useCloseThread, useOpenThreadPath } from "@/routing";
|
|
39
38
|
import { MoveToTrigger } from "./MoveToTrigger";
|
|
40
39
|
|
|
41
40
|
interface MessageActionMenuProps {
|
|
@@ -85,8 +84,7 @@ export const MessageActionMenu = ({
|
|
|
85
84
|
onToggleRaw,
|
|
86
85
|
}: MessageActionMenuProps) => {
|
|
87
86
|
const queryClient = useQueryClient();
|
|
88
|
-
const
|
|
89
|
-
const retainPanels = useRetainOpenPanels();
|
|
87
|
+
const closeThread = useCloseThread();
|
|
90
88
|
const { pushError } = useErrorBanners();
|
|
91
89
|
const selectedMessageId = useOpenThreadPath()?.messageId;
|
|
92
90
|
|
|
@@ -173,14 +171,9 @@ export const MessageActionMenu = ({
|
|
|
173
171
|
(removedIds: string[]) => {
|
|
174
172
|
if (!selectedMessageId) return;
|
|
175
173
|
if (!removedIds.includes(selectedMessageId)) return;
|
|
176
|
-
|
|
177
|
-
to: "/mail/$mailboxId",
|
|
178
|
-
params: { mailboxId },
|
|
179
|
-
search: (prev) => prev,
|
|
180
|
-
hash: retainPanels,
|
|
181
|
-
});
|
|
174
|
+
closeThread();
|
|
182
175
|
},
|
|
183
|
-
[selectedMessageId,
|
|
176
|
+
[selectedMessageId, closeThread],
|
|
184
177
|
);
|
|
185
178
|
|
|
186
179
|
const { deleteMessages, isPending: isDeleting } = useDeleteMessages({
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
useSelection,
|
|
14
14
|
type Verb,
|
|
15
15
|
} from "@remit/ui";
|
|
16
|
-
import { useBlocker
|
|
16
|
+
import { useBlocker } from "@tanstack/react-router";
|
|
17
17
|
import { useVirtualizer } from "@tanstack/react-virtual";
|
|
18
18
|
import { Search } from "lucide-react";
|
|
19
19
|
import type { RefObject } from "react";
|
|
@@ -52,9 +52,12 @@ import { useListHeaderChrome } from "@/lib/list-header-chrome";
|
|
|
52
52
|
import { listVerbRequest } from "@/lib/list-verb-request";
|
|
53
53
|
import { resolveSelectionAccountScope } from "@/lib/selection-account-scope";
|
|
54
54
|
import { shouldExitSelectionOnNavigate } from "@/lib/selection-mode";
|
|
55
|
-
import { useSelectionWizard, useWizardStepValue } from "@/lib/wizard-history";
|
|
56
55
|
import type { WizardSelectionMessage } from "@/lib/wizard-selection";
|
|
57
|
-
import {
|
|
56
|
+
import {
|
|
57
|
+
useOpenThread,
|
|
58
|
+
useSelectionWizard,
|
|
59
|
+
useWizardStepValue,
|
|
60
|
+
} from "@/routing";
|
|
58
61
|
import { DeleteConfirmDialog } from "./DeleteConfirmDialog";
|
|
59
62
|
import { LabelApplyTrigger } from "./LabelApplyTrigger";
|
|
60
63
|
import {
|
|
@@ -251,8 +254,7 @@ export const MessageList = ({
|
|
|
251
254
|
hideHeader = false,
|
|
252
255
|
}: MessageListProps) => {
|
|
253
256
|
const parentRef = useRef<HTMLDivElement>(null);
|
|
254
|
-
const
|
|
255
|
-
const retainPanels = useRetainOpenPanels();
|
|
257
|
+
const openThread = useOpenThread();
|
|
256
258
|
const isDesktop = useIsDesktop();
|
|
257
259
|
const wizard = useSelectionWizard();
|
|
258
260
|
const { verb: wizardVerb, start: startWizard, startFromSearch } = wizard;
|
|
@@ -655,15 +657,9 @@ export const MessageList = ({
|
|
|
655
657
|
(thread) => thread.messageId === messageId,
|
|
656
658
|
)?.threadId;
|
|
657
659
|
if (!threadId) return;
|
|
658
|
-
|
|
659
|
-
to: "/mail/$mailboxId/$threadId/$messageId",
|
|
660
|
-
params: { mailboxId, threadId, messageId },
|
|
661
|
-
search: (prev) => prev,
|
|
662
|
-
hash: retainPanels,
|
|
663
|
-
replace: options?.replace,
|
|
664
|
-
});
|
|
660
|
+
openThread({ threadId, messageId }, { replace: options?.replace });
|
|
665
661
|
},
|
|
666
|
-
[
|
|
662
|
+
[openThread, threads],
|
|
667
663
|
);
|
|
668
664
|
|
|
669
665
|
// Enter: open the focused row in the reading pane. This is the focus→open
|
|
@@ -30,7 +30,6 @@ import {
|
|
|
30
30
|
useRovingFocus,
|
|
31
31
|
} from "@remit/ui";
|
|
32
32
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
33
|
-
import { useNavigate } from "@tanstack/react-router";
|
|
34
33
|
import {
|
|
35
34
|
AlertCircle,
|
|
36
35
|
AlertTriangle,
|
|
@@ -62,7 +61,11 @@ import {
|
|
|
62
61
|
import { isOutboxListRow, showsLastError } from "@/lib/outbox-status";
|
|
63
62
|
import { normalizeSearchQuery } from "@/lib/search-query";
|
|
64
63
|
import { parseSearchTokens } from "@/lib/search-tokens";
|
|
65
|
-
import {
|
|
64
|
+
import {
|
|
65
|
+
useCloseOutboxDraft,
|
|
66
|
+
useEditDraft,
|
|
67
|
+
useOpenOutboxDraft,
|
|
68
|
+
} from "@/routing";
|
|
66
69
|
|
|
67
70
|
/* ------------------------------------------------------------------ */
|
|
68
71
|
/* Helpers (shared between List, Reading, Phone sub-views) */
|
|
@@ -177,8 +180,8 @@ function OutboxPaneProvider({
|
|
|
177
180
|
outboxMessageId: selectedMessageId,
|
|
178
181
|
children,
|
|
179
182
|
}: OutboxPaneProps) {
|
|
180
|
-
const
|
|
181
|
-
const
|
|
183
|
+
const openDraft = useOpenOutboxDraft();
|
|
184
|
+
const closeDraft = useCloseOutboxDraft();
|
|
182
185
|
|
|
183
186
|
const { data: outboxResponse, isLoading } = useQuery(
|
|
184
187
|
outboxOperationsListOutboxMessagesOptions(),
|
|
@@ -218,26 +221,6 @@ function OutboxPaneProvider({
|
|
|
218
221
|
[messages, selectedMessageId],
|
|
219
222
|
);
|
|
220
223
|
|
|
221
|
-
const handleOpenMessage = useCallback(
|
|
222
|
-
(outboxMessageId: string) => {
|
|
223
|
-
navigate({
|
|
224
|
-
to: "/mail/outbox/draft/$outboxMessageId",
|
|
225
|
-
params: { outboxMessageId },
|
|
226
|
-
search: (prev) => prev,
|
|
227
|
-
hash: retainPanels,
|
|
228
|
-
});
|
|
229
|
-
},
|
|
230
|
-
[navigate, retainPanels],
|
|
231
|
-
);
|
|
232
|
-
|
|
233
|
-
const handleCloseMessage = useCallback(() => {
|
|
234
|
-
navigate({
|
|
235
|
-
to: "/mail/outbox",
|
|
236
|
-
search: (prev) => prev,
|
|
237
|
-
hash: retainPanels,
|
|
238
|
-
});
|
|
239
|
-
}, [navigate, retainPanels]);
|
|
240
|
-
|
|
241
224
|
const ctx: OutboxPaneContextValue = {
|
|
242
225
|
messages,
|
|
243
226
|
hasQuery,
|
|
@@ -245,8 +228,8 @@ function OutboxPaneProvider({
|
|
|
245
228
|
isLoading,
|
|
246
229
|
selectedMessageId,
|
|
247
230
|
selectedMessage,
|
|
248
|
-
onOpenMessage:
|
|
249
|
-
onCloseMessage:
|
|
231
|
+
onOpenMessage: openDraft,
|
|
232
|
+
onCloseMessage: closeDraft,
|
|
250
233
|
};
|
|
251
234
|
|
|
252
235
|
return (
|
|
@@ -70,8 +70,8 @@ import {
|
|
|
70
70
|
} from "@/lib/organize/rule-model";
|
|
71
71
|
import { searchRuleAccountId } from "@/lib/organize/search-to-rule";
|
|
72
72
|
import type { OrganizeMatchPredicate } from "@/lib/organize/sender-fallback";
|
|
73
|
-
import { useWizardEntryValue, useWizardStep } from "@/lib/wizard-history";
|
|
74
73
|
import type { WizardSelectionMessage } from "@/lib/wizard-selection";
|
|
74
|
+
import { useWizardEntryValue, useWizardStep } from "@/routing";
|
|
75
75
|
import { organizeRunState } from "./organize-run-state";
|
|
76
76
|
import {
|
|
77
77
|
retryIntent,
|
|
@@ -6,11 +6,10 @@ import {
|
|
|
6
6
|
type SwipePeek,
|
|
7
7
|
type ThreadRowData,
|
|
8
8
|
} from "@remit/ui";
|
|
9
|
-
import { useNavigate } from "@tanstack/react-router";
|
|
10
9
|
import { useCallback, useState } from "react";
|
|
11
10
|
import { toDisplayCategory } from "@/lib/display-category";
|
|
12
11
|
import { formatEmailDate } from "@/lib/format";
|
|
13
|
-
import {
|
|
12
|
+
import { useOpenThread } from "@/routing";
|
|
14
13
|
import { MessageListItem } from "./MessageListItem";
|
|
15
14
|
import { useModifierSelect } from "./useModifierSelect";
|
|
16
15
|
|
|
@@ -74,8 +73,7 @@ export const SwipeableMessageRow = ({
|
|
|
74
73
|
density,
|
|
75
74
|
}: SwipeableMessageRowProps) => {
|
|
76
75
|
const [peek, setPeek] = useState<SwipePeek>("none");
|
|
77
|
-
const
|
|
78
|
-
const retainPanels = useRetainOpenPanels();
|
|
76
|
+
const openThread = useOpenThread();
|
|
79
77
|
|
|
80
78
|
const handleAct = useCallback(
|
|
81
79
|
(side: "leading" | "trailing") => {
|
|
@@ -99,17 +97,8 @@ export const SwipeableMessageRow = ({
|
|
|
99
97
|
}, [onToggleCheck, thread.messageId]);
|
|
100
98
|
|
|
101
99
|
const handleOpen = useCallback(() => {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
params: {
|
|
105
|
-
mailboxId,
|
|
106
|
-
threadId: thread.threadId,
|
|
107
|
-
messageId: thread.messageId,
|
|
108
|
-
},
|
|
109
|
-
search: (prev) => prev,
|
|
110
|
-
hash: retainPanels,
|
|
111
|
-
});
|
|
112
|
-
}, [navigate, retainPanels, mailboxId, thread.threadId, thread.messageId]);
|
|
100
|
+
openThread({ threadId: thread.threadId, messageId: thread.messageId });
|
|
101
|
+
}, [openThread, thread.threadId, thread.messageId]);
|
|
113
102
|
|
|
114
103
|
const modifierSelect = useModifierSelect(thread.messageId, onRowSelect);
|
|
115
104
|
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { mailboxOperationsListMailboxesOptions } from "@remit/api-http-client/@tanstack/react-query.gen.ts";
|
|
2
2
|
import type { RemitImapAccountResponse } from "@remit/api-http-client/types.gen.ts";
|
|
3
3
|
import { useQueries } from "@tanstack/react-query";
|
|
4
|
-
import { useLocation, useParams } from "@tanstack/react-router";
|
|
5
4
|
import { useCallback } from "react";
|
|
6
5
|
import { useTranslation } from "react-i18next";
|
|
7
6
|
import { buildMailboxRoleMap, labelForMailbox } from "@/lib/folder-roles";
|
|
7
|
+
import { useBrowsedList } from "@/routing";
|
|
8
8
|
|
|
9
9
|
interface UseCurrentMailboxNameOptions {
|
|
10
10
|
accounts: RemitImapAccountResponse[];
|
|
@@ -27,9 +27,7 @@ interface UseCurrentMailboxNameOptions {
|
|
|
27
27
|
export const useCurrentMailboxName = ({
|
|
28
28
|
accounts,
|
|
29
29
|
}: UseCurrentMailboxNameOptions): string | null => {
|
|
30
|
-
const
|
|
31
|
-
const location = useLocation();
|
|
32
|
-
const mailboxId = (params as { mailboxId?: string }).mailboxId;
|
|
30
|
+
const { list, mailboxId } = useBrowsedList();
|
|
33
31
|
const { t } = useTranslation("mail", { useSuspense: false });
|
|
34
32
|
const translator = useCallback(
|
|
35
33
|
(key: string, fallback: string) => t(key, { defaultValue: fallback }),
|
|
@@ -45,7 +43,7 @@ export const useCurrentMailboxName = ({
|
|
|
45
43
|
})),
|
|
46
44
|
});
|
|
47
45
|
|
|
48
|
-
if (
|
|
46
|
+
if (list === "outbox") return "Outbox";
|
|
49
47
|
if (!mailboxId) return null;
|
|
50
48
|
|
|
51
49
|
for (let i = 0; i < accounts.length; i++) {
|
|
@@ -74,8 +72,7 @@ export const useCurrentMailboxName = ({
|
|
|
74
72
|
export const useCurrentMailboxUnseenCount = ({
|
|
75
73
|
accounts,
|
|
76
74
|
}: UseCurrentMailboxNameOptions): number | null => {
|
|
77
|
-
const
|
|
78
|
-
const mailboxId = (params as { mailboxId?: string }).mailboxId;
|
|
75
|
+
const { mailboxId } = useBrowsedList();
|
|
79
76
|
|
|
80
77
|
const queries = useQueries({
|
|
81
78
|
queries: accounts.map((account) => ({
|
|
@@ -107,8 +104,7 @@ export const useCurrentMailboxUnseenCount = ({
|
|
|
107
104
|
export const useCurrentMailboxMessageCount = ({
|
|
108
105
|
accounts,
|
|
109
106
|
}: UseCurrentMailboxNameOptions): number => {
|
|
110
|
-
const
|
|
111
|
-
const mailboxId = (params as { mailboxId?: string }).mailboxId;
|
|
107
|
+
const { mailboxId } = useBrowsedList();
|
|
112
108
|
|
|
113
109
|
const queries = useQueries({
|
|
114
110
|
queries: accounts.map((account) => ({
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { RemitImapAccountResponse } from "@remit/api-http-client/types.gen.ts";
|
|
2
|
-
import { useNavigate, useRouterState } from "@tanstack/react-router";
|
|
3
2
|
import { useCallback } from "react";
|
|
4
3
|
import { useMailContext } from "@/lib/mail-context";
|
|
5
4
|
import {
|
|
@@ -7,6 +6,7 @@ import {
|
|
|
7
6
|
type SearchScopeState,
|
|
8
7
|
searchScopeForRoute,
|
|
9
8
|
} from "@/lib/search-scope";
|
|
9
|
+
import { useBrowsedList, useSearchEverything } from "@/routing";
|
|
10
10
|
import { useCurrentMailboxName } from "./useCurrentMailboxName";
|
|
11
11
|
|
|
12
12
|
/**
|
|
@@ -22,14 +22,11 @@ export function useSearchScope(accounts: RemitImapAccountResponse[]): {
|
|
|
22
22
|
scope: SearchScopeState;
|
|
23
23
|
clearScope: (chipId: string) => void;
|
|
24
24
|
} {
|
|
25
|
-
const
|
|
25
|
+
const searchEverything = useSearchEverything();
|
|
26
26
|
const { searchInput } = useMailContext();
|
|
27
27
|
const mailboxName = useCurrentMailboxName({ accounts });
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
// previous folder until the next navigation.
|
|
31
|
-
const matches = useRouterState({ select: (s) => s.matches });
|
|
32
|
-
const scope = searchScopeForRoute(matches, mailboxName);
|
|
28
|
+
const browsed = useBrowsedList();
|
|
29
|
+
const scope = searchScopeForRoute(browsed, mailboxName);
|
|
33
30
|
|
|
34
31
|
// Takes the chip id the field removed rather than assuming which chip that
|
|
35
32
|
// was. The bar owns one chip today; keying on the id means a second one
|
|
@@ -37,12 +34,9 @@ export function useSearchScope(accounts: RemitImapAccountResponse[]): {
|
|
|
37
34
|
const clearScope = useCallback(
|
|
38
35
|
(chipId: string) => {
|
|
39
36
|
if (chipId !== SEARCH_SCOPE_CHIP_ID) return;
|
|
40
|
-
|
|
41
|
-
to: "/mail/brief",
|
|
42
|
-
search: { q: searchInput || undefined },
|
|
43
|
-
});
|
|
37
|
+
searchEverything(searchInput);
|
|
44
38
|
},
|
|
45
|
-
[
|
|
39
|
+
[searchEverything, searchInput],
|
|
46
40
|
);
|
|
47
41
|
|
|
48
42
|
return { scope, clearScope };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { useRouterState } from "@tanstack/react-router";
|
|
2
1
|
import { useMailContext } from "@/lib/mail-context";
|
|
3
2
|
import { isScopedRoute } from "@/lib/search-scope";
|
|
4
3
|
import type { SearchTokenContext } from "@/lib/search-tokens";
|
|
4
|
+
import { useBrowsedList } from "@/routing";
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* The one context every search engine parses the query through.
|
|
@@ -20,7 +20,8 @@ import type { SearchTokenContext } from "@/lib/search-tokens";
|
|
|
20
20
|
*/
|
|
21
21
|
export function useSearchTokenContext(): SearchTokenContext {
|
|
22
22
|
const { mailboxNameIndex, accountNameIndex } = useMailContext();
|
|
23
|
-
const
|
|
23
|
+
const browsed = useBrowsedList();
|
|
24
|
+
const scoped = isScopedRoute(browsed);
|
|
24
25
|
return {
|
|
25
26
|
...(scoped ? {} : { mailboxesByName: mailboxNameIndex }),
|
|
26
27
|
accountsByName: accountNameIndex,
|
|
@@ -2,11 +2,11 @@ import { semanticSearchOperationsSemanticSearchOptions } from "@remit/api-http-c
|
|
|
2
2
|
import type { RemitImapSemanticSearchResult } from "@remit/api-http-client/types.gen.ts";
|
|
3
3
|
import { MessageCategory } from "@remit/domain-enums";
|
|
4
4
|
import { useQuery } from "@tanstack/react-query";
|
|
5
|
-
import { useRouterState } from "@tanstack/react-router";
|
|
6
5
|
import { useMailContext } from "@/lib/mail-context";
|
|
7
6
|
import { normalizeSearchQuery } from "@/lib/search-query";
|
|
8
7
|
import { semanticMailboxScope } from "@/lib/search-scope";
|
|
9
8
|
import { parseSearchTokens, type SearchToken } from "@/lib/search-tokens";
|
|
9
|
+
import { useBrowsedList } from "@/routing";
|
|
10
10
|
import { useSearchTokenContext } from "./useSearchTokenContext";
|
|
11
11
|
|
|
12
12
|
/** Cap the "Related" section; the literal "Top matches" is the primary surface. */
|
|
@@ -79,7 +79,7 @@ export function useSemanticSearch({
|
|
|
79
79
|
} {
|
|
80
80
|
const { searchQuery } = useMailContext();
|
|
81
81
|
const tokenContext = useSearchTokenContext();
|
|
82
|
-
const
|
|
82
|
+
const browsed = useBrowsedList();
|
|
83
83
|
const normalizedQuery = normalizeSearchQuery(searchQuery);
|
|
84
84
|
const { freeText, tokens } = parseSearchTokens(normalizedQuery, tokenContext);
|
|
85
85
|
// `freeText`, not the raw query: a query of nothing but tokens
|
|
@@ -105,7 +105,7 @@ export function useSemanticSearch({
|
|
|
105
105
|
const inToken = tokens.find((t) => t.type === "in");
|
|
106
106
|
// The route decides the scope, not the call site — see `semanticMailboxScope`.
|
|
107
107
|
const effectiveMailboxId = semanticMailboxScope({
|
|
108
|
-
|
|
108
|
+
browsed,
|
|
109
109
|
callerMailboxId: mailboxId,
|
|
110
110
|
inTokenMailboxId: inToken?.mailboxId,
|
|
111
111
|
});
|
package/src/lib/mail-route.ts
CHANGED
|
@@ -30,6 +30,16 @@ export type MailListRoute =
|
|
|
30
30
|
| { list: "outbox" }
|
|
31
31
|
| { list: "mailbox"; mailboxId: string | undefined };
|
|
32
32
|
|
|
33
|
+
/**
|
|
34
|
+
* The list a caller is on, flattened to the two values a navigation to it — or
|
|
35
|
+
* a decision about it — needs. Both are primitives, so a caller holding them in
|
|
36
|
+
* a dependency array settles, and `list` is `undefined` outside the mail shell.
|
|
37
|
+
*/
|
|
38
|
+
export interface BrowsedList {
|
|
39
|
+
list: MailListRoute["list"] | undefined;
|
|
40
|
+
mailboxId: string | undefined;
|
|
41
|
+
}
|
|
42
|
+
|
|
33
43
|
/**
|
|
34
44
|
* The list layout among the matched routes, or `undefined` outside the mail
|
|
35
45
|
* shell.
|