@remit/web-client 0.0.202 → 0.0.203
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/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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remit/web-client",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.203",
|
|
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": {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ComposeSmtpMissingBanner as Banner } from "@remit/ui";
|
|
2
|
-
import { useNavigate } from "@tanstack/react-router";
|
|
3
2
|
import type { Ref } from "react";
|
|
3
|
+
import { useConfigureAccountSmtp } from "@/routing";
|
|
4
4
|
|
|
5
5
|
interface ComposeSmtpMissingBannerProps {
|
|
6
6
|
accountId: string;
|
|
@@ -12,16 +12,13 @@ export const ComposeSmtpMissingBanner = ({
|
|
|
12
12
|
accountId,
|
|
13
13
|
configureRef,
|
|
14
14
|
}: ComposeSmtpMissingBannerProps) => {
|
|
15
|
-
const
|
|
15
|
+
const configureSmtp = useConfigureAccountSmtp();
|
|
16
16
|
|
|
17
17
|
return (
|
|
18
18
|
<Banner
|
|
19
19
|
configureRef={configureRef}
|
|
20
20
|
onConfigure={() => {
|
|
21
|
-
|
|
22
|
-
to: "/settings/accounts",
|
|
23
|
-
search: { editAccountId: accountId, focusSmtp: true },
|
|
24
|
-
});
|
|
21
|
+
configureSmtp(accountId);
|
|
25
22
|
}}
|
|
26
23
|
/>
|
|
27
24
|
);
|
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
import { useLocation } from "@tanstack/react-router";
|
|
2
1
|
import { Pencil } from "lucide-react";
|
|
3
|
-
import {
|
|
4
|
-
import { useOpenCompose } from "@/routing";
|
|
2
|
+
import { useOpenCompose, useOpensDetail } from "@/routing";
|
|
5
3
|
|
|
6
4
|
/**
|
|
7
5
|
* Floating Action Button for composing a new message. Mobile-only.
|
|
@@ -16,9 +14,9 @@ import { useOpenCompose } from "@/routing";
|
|
|
16
14
|
*/
|
|
17
15
|
export const ComposeFab = () => {
|
|
18
16
|
const compose = useOpenCompose();
|
|
19
|
-
const
|
|
17
|
+
const opensDetail = useOpensDetail();
|
|
20
18
|
|
|
21
|
-
if (
|
|
19
|
+
if (opensDetail) return null;
|
|
22
20
|
|
|
23
21
|
return (
|
|
24
22
|
<button
|
|
@@ -8,7 +8,6 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import type { RemitImapAccountResponse } from "@remit/api-http-client/types.gen.ts";
|
|
10
10
|
import { RefreshButton, ShellTopBar, shortcutHintForAction } from "@remit/ui";
|
|
11
|
-
import { useNavigate } from "@tanstack/react-router";
|
|
12
11
|
import { useMemo } from "react";
|
|
13
12
|
import { AccountMenu } from "@/auth/AccountMenu";
|
|
14
13
|
import { useRefreshControl } from "@/hooks/useRefreshControl";
|
|
@@ -16,7 +15,7 @@ import { useSearchScope } from "@/hooks/useSearchScope";
|
|
|
16
15
|
import { openBugReport } from "@/lib/bug-report";
|
|
17
16
|
import { useMailContext } from "@/lib/mail-context";
|
|
18
17
|
import { useMailFreshness } from "@/lib/mail-freshness";
|
|
19
|
-
import { useOpenCompose } from "@/routing";
|
|
18
|
+
import { useGoToSection, useOpenCompose } from "@/routing";
|
|
20
19
|
|
|
21
20
|
interface MailTopBarProps {
|
|
22
21
|
accounts: RemitImapAccountResponse[];
|
|
@@ -25,7 +24,7 @@ interface MailTopBarProps {
|
|
|
25
24
|
export function MailTopBar({ accounts }: MailTopBarProps) {
|
|
26
25
|
const { searchInput, onSearchChange, onSearchClear, onSearchClearQuery } =
|
|
27
26
|
useMailContext();
|
|
28
|
-
const
|
|
27
|
+
const goToSection = useGoToSection();
|
|
29
28
|
const compose = useOpenCompose();
|
|
30
29
|
const { scope, clearScope } = useSearchScope(accounts);
|
|
31
30
|
const chips =
|
|
@@ -59,7 +58,7 @@ export function MailTopBar({ accounts }: MailTopBarProps) {
|
|
|
59
58
|
}}
|
|
60
59
|
onCompose={compose}
|
|
61
60
|
onReportBug={openBugReport}
|
|
62
|
-
onOpenSettings={() =>
|
|
61
|
+
onOpenSettings={() => goToSection("accounts")}
|
|
63
62
|
composeShortcut={shortcutHintForAction("compose")}
|
|
64
63
|
refreshControl={
|
|
65
64
|
<RefreshButton
|
|
@@ -16,7 +16,6 @@ import { unifiedThreadOperationsListAllThreadsOptions } from "@remit/api-http-cl
|
|
|
16
16
|
import type { RemitImapThreadMessageResponse } from "@remit/api-http-client/types.gen.ts";
|
|
17
17
|
import { ReadingPaneEmpty } from "@remit/ui";
|
|
18
18
|
import { useQuery } from "@tanstack/react-query";
|
|
19
|
-
import { useNavigate } from "@tanstack/react-router";
|
|
20
19
|
import {
|
|
21
20
|
createContext,
|
|
22
21
|
type ReactNode,
|
|
@@ -48,10 +47,12 @@ import {
|
|
|
48
47
|
type OpenThreadTarget,
|
|
49
48
|
type ReplyMode,
|
|
50
49
|
replyToThread,
|
|
50
|
+
useCloseThread,
|
|
51
|
+
useGoToSection,
|
|
51
52
|
useIsComposing,
|
|
52
53
|
useIsReplying,
|
|
53
54
|
useOpenReply,
|
|
54
|
-
|
|
55
|
+
useOpenThread,
|
|
55
56
|
} from "@/routing";
|
|
56
57
|
|
|
57
58
|
/* ------------------------------------------------------------------ */
|
|
@@ -113,8 +114,9 @@ interface BriefPaneProps {
|
|
|
113
114
|
}
|
|
114
115
|
|
|
115
116
|
function BriefPaneProvider({ thread, children }: BriefPaneProps) {
|
|
116
|
-
const
|
|
117
|
-
const
|
|
117
|
+
const openThread = useOpenThread();
|
|
118
|
+
const closeThread = useCloseThread();
|
|
119
|
+
const goToSection = useGoToSection();
|
|
118
120
|
const { searchInput } = useMailContext();
|
|
119
121
|
const threadId = thread?.threadId;
|
|
120
122
|
const pointedAtMessageId = thread?.messageId;
|
|
@@ -155,37 +157,18 @@ function BriefPaneProvider({ thread, children }: BriefPaneProps) {
|
|
|
155
157
|
|
|
156
158
|
const handleOpenThread = useCallback(
|
|
157
159
|
(target: OpenThreadTarget, options?: OpenMessageOptions) => {
|
|
158
|
-
|
|
159
|
-
to: "/mail/brief/$threadId/$messageId",
|
|
160
|
-
params: target,
|
|
161
|
-
replace: options?.replace,
|
|
162
|
-
// Commit the active query with the open so the debounced q-mirror —
|
|
163
|
-
// which walks back up to the list when the query goes active — is
|
|
164
|
-
// already satisfied and leaves the conversation alone. The *live*
|
|
165
|
-
// `searchInput`: a row can be tapped before the debounce settles, when
|
|
166
|
-
// the committed query is still empty.
|
|
167
|
-
search: (prev) => ({ ...prev, q: searchInput || undefined }),
|
|
168
|
-
hash: retainPanels,
|
|
169
|
-
});
|
|
160
|
+
openThread(target, { replace: options?.replace, query: searchInput });
|
|
170
161
|
},
|
|
171
|
-
[
|
|
162
|
+
[openThread, searchInput],
|
|
172
163
|
);
|
|
173
164
|
|
|
174
|
-
const handleCloseThread = useCallback(() => {
|
|
175
|
-
navigate({
|
|
176
|
-
to: "/mail/brief",
|
|
177
|
-
search: (prev) => prev,
|
|
178
|
-
hash: retainPanels,
|
|
179
|
-
});
|
|
180
|
-
}, [navigate, retainPanels]);
|
|
181
|
-
|
|
182
165
|
const handleDeselectIfRemoved = useCallback(
|
|
183
166
|
(removedIds: string[]) => {
|
|
184
167
|
if (!selectedMessageId) return;
|
|
185
168
|
if (!removedIds.includes(selectedMessageId)) return;
|
|
186
|
-
|
|
169
|
+
closeThread();
|
|
187
170
|
},
|
|
188
|
-
[selectedMessageId,
|
|
171
|
+
[selectedMessageId, closeThread],
|
|
189
172
|
);
|
|
190
173
|
|
|
191
174
|
const actions = useThreadActions({
|
|
@@ -247,7 +230,7 @@ function BriefPaneProvider({ thread, children }: BriefPaneProps) {
|
|
|
247
230
|
// would otherwise fire at the message behind whatever is being typed — or
|
|
248
231
|
// answer a row the cursor moved to while a reply was open.
|
|
249
232
|
enabled: !isComposing && !isReplying,
|
|
250
|
-
onClose:
|
|
233
|
+
onClose: closeThread,
|
|
251
234
|
handlers: {
|
|
252
235
|
reply: () => replyToFocusedThread?.("reply"),
|
|
253
236
|
replyAll: () => replyToFocusedThread?.("reply-all"),
|
|
@@ -265,8 +248,8 @@ function BriefPaneProvider({ thread, children }: BriefPaneProps) {
|
|
|
265
248
|
if (!triageTarget) return;
|
|
266
249
|
toggleReadFor([triageTarget.messageId], !triageTarget.isRead);
|
|
267
250
|
},
|
|
268
|
-
goFlagged: () =>
|
|
269
|
-
goSettings: () =>
|
|
251
|
+
goFlagged: () => goToSection("flagged"),
|
|
252
|
+
goSettings: () => goToSection("settings"),
|
|
270
253
|
},
|
|
271
254
|
});
|
|
272
255
|
|
|
@@ -287,7 +270,7 @@ function BriefPaneProvider({ thread, children }: BriefPaneProps) {
|
|
|
287
270
|
selectedThread,
|
|
288
271
|
conversation,
|
|
289
272
|
onOpenThread: handleOpenThread,
|
|
290
|
-
onCloseThread:
|
|
273
|
+
onCloseThread: closeThread,
|
|
291
274
|
nextThread: adjacentThread(nextMessageId),
|
|
292
275
|
previousThread: adjacentThread(previousMessageId),
|
|
293
276
|
actions,
|
|
@@ -63,7 +63,6 @@ import {
|
|
|
63
63
|
toggleBriefFilterInQuery,
|
|
64
64
|
} from "@remit/ui";
|
|
65
65
|
import { useQueries, useQuery } from "@tanstack/react-query";
|
|
66
|
-
import { useNavigate } from "@tanstack/react-router";
|
|
67
66
|
import { AlertCircle, RefreshCw } from "lucide-react";
|
|
68
67
|
import {
|
|
69
68
|
type ReactNode,
|
|
@@ -103,12 +102,14 @@ import { showInlineSearchResults } from "@/lib/search-surface";
|
|
|
103
102
|
import { parseSearchTokens } from "@/lib/search-tokens";
|
|
104
103
|
import { resolveSelectionAccountScope } from "@/lib/selection-account-scope";
|
|
105
104
|
import { spamOfferForResults } from "@/lib/spam-offer";
|
|
105
|
+
import { wizardSelectionFrom } from "@/lib/wizard-selection";
|
|
106
106
|
import {
|
|
107
|
+
type OpenThreadTarget,
|
|
107
108
|
type SelectionWizardControl,
|
|
109
|
+
useGoToSection,
|
|
110
|
+
useScopeSearchToMailbox,
|
|
108
111
|
useSelectionWizard,
|
|
109
|
-
} from "@/
|
|
110
|
-
import { wizardSelectionFrom } from "@/lib/wizard-selection";
|
|
111
|
-
import type { OpenThreadTarget } from "@/routing";
|
|
112
|
+
} from "@/routing";
|
|
112
113
|
import { LabelApplyTrigger } from "./LabelApplyTrigger";
|
|
113
114
|
import { MailListHeader, type MailListHeaderProps } from "./MailListHeader";
|
|
114
115
|
import type { MessageListCommands } from "./MessageList";
|
|
@@ -160,14 +161,14 @@ interface ErrorBannerProps {
|
|
|
160
161
|
}
|
|
161
162
|
|
|
162
163
|
const ErrorBanner = ({ accountEmail }: ErrorBannerProps) => {
|
|
163
|
-
const
|
|
164
|
+
const goToSection = useGoToSection();
|
|
164
165
|
return (
|
|
165
166
|
<div className="flex items-center gap-2 px-row-inset py-2 border-b border-line bg-danger-soft/40 text-xs text-danger">
|
|
166
167
|
<AlertCircle className="size-3.5 shrink-0" />
|
|
167
168
|
<span className="flex-1 truncate">{accountEmail} can't connect</span>
|
|
168
169
|
<button
|
|
169
170
|
type="button"
|
|
170
|
-
onClick={() =>
|
|
171
|
+
onClick={() => goToSection("accounts")}
|
|
171
172
|
className="shrink-0 underline text-danger hover:opacity-80"
|
|
172
173
|
>
|
|
173
174
|
Reconnect
|
|
@@ -398,7 +399,7 @@ export function DailyBrief({
|
|
|
398
399
|
const isDesktop = useIsDesktop();
|
|
399
400
|
const tier = useLayoutTier();
|
|
400
401
|
const wizard = useSelectionWizard();
|
|
401
|
-
const
|
|
402
|
+
const scopeSearchToMailbox = useScopeSearchToMailbox();
|
|
402
403
|
|
|
403
404
|
const nonMuted = useMemo(
|
|
404
405
|
() => sortAccountsByCreatedAt(accounts.filter((a) => !a.muted?.value)),
|
|
@@ -811,11 +812,7 @@ export function DailyBrief({
|
|
|
811
812
|
<SpamResultsOffer
|
|
812
813
|
count={briefSpamOffer.count}
|
|
813
814
|
onScopeToSpam={() =>
|
|
814
|
-
|
|
815
|
-
to: "/mail/$mailboxId",
|
|
816
|
-
params: { mailboxId: briefSpamOffer.mailboxId },
|
|
817
|
-
search: { q: searchQuery || undefined },
|
|
818
|
-
})
|
|
815
|
+
scopeSearchToMailbox(briefSpamOffer.mailboxId, searchQuery)
|
|
819
816
|
}
|
|
820
817
|
/>
|
|
821
818
|
)}
|
|
@@ -36,16 +36,11 @@ import {
|
|
|
36
36
|
type ThreadSection,
|
|
37
37
|
} from "@remit/ui";
|
|
38
38
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
39
|
-
import { useNavigate } from "@tanstack/react-router";
|
|
40
39
|
import { FileText, Inbox, Trash2 } from "lucide-react";
|
|
41
40
|
import { useMemo } from "react";
|
|
42
41
|
|
|
43
42
|
import { groupDraftSections } from "@/lib/drafts";
|
|
44
|
-
import {
|
|
45
|
-
useComposeDraftId,
|
|
46
|
-
useEditDraft,
|
|
47
|
-
useRetainOpenPanels,
|
|
48
|
-
} from "@/routing";
|
|
43
|
+
import { useComposeDraftId, useEditDraft, useOpenThread } from "@/routing";
|
|
49
44
|
import { NavMenuButton } from "./NavMenuButton";
|
|
50
45
|
|
|
51
46
|
// ---------------------------------------------------------------------------
|
|
@@ -136,8 +131,6 @@ const ImapDraftRow = ({ row, isSelected, onOpen }: ImapDraftRowProps) => {
|
|
|
136
131
|
// ---------------------------------------------------------------------------
|
|
137
132
|
|
|
138
133
|
interface DraftsViewProps {
|
|
139
|
-
/** The mailbox id for the \Drafts mailbox being viewed. */
|
|
140
|
-
mailboxId: string;
|
|
141
134
|
/** The accountId that owns this \Drafts mailbox. */
|
|
142
135
|
accountId: string;
|
|
143
136
|
/** The currently selected message id (reading pane). */
|
|
@@ -151,7 +144,6 @@ interface DraftsViewProps {
|
|
|
151
144
|
}
|
|
152
145
|
|
|
153
146
|
export function DraftsView({
|
|
154
|
-
mailboxId,
|
|
155
147
|
accountId,
|
|
156
148
|
selectedMessageId,
|
|
157
149
|
imapThreads,
|
|
@@ -160,8 +152,7 @@ export function DraftsView({
|
|
|
160
152
|
}: DraftsViewProps) {
|
|
161
153
|
const openDraftId = useComposeDraftId();
|
|
162
154
|
const editDraft = useEditDraft();
|
|
163
|
-
const
|
|
164
|
-
const retainPanels = useRetainOpenPanels();
|
|
155
|
+
const openThread = useOpenThread();
|
|
165
156
|
const queryClient = useQueryClient();
|
|
166
157
|
|
|
167
158
|
// Fetch the full outbox list — both sources are already fetched by the
|
|
@@ -196,12 +187,7 @@ export function DraftsView({
|
|
|
196
187
|
(thread) => thread.messageId === messageId,
|
|
197
188
|
)?.threadId;
|
|
198
189
|
if (!threadId) return;
|
|
199
|
-
|
|
200
|
-
to: "/mail/$mailboxId/$threadId/$messageId",
|
|
201
|
-
params: { mailboxId, threadId, messageId },
|
|
202
|
-
search: (prev) => prev,
|
|
203
|
-
hash: retainPanels,
|
|
204
|
-
});
|
|
190
|
+
openThread({ threadId, messageId });
|
|
205
191
|
};
|
|
206
192
|
|
|
207
193
|
const isEmpty = sections.length === 0;
|
|
@@ -37,9 +37,9 @@ import { useMailContext } from "@/lib/mail-context";
|
|
|
37
37
|
import { rowToSearchResult } from "@/lib/search-result";
|
|
38
38
|
import { parseSearchTokens } from "@/lib/search-tokens";
|
|
39
39
|
import { dedupeByThread } from "@/lib/starred-rows";
|
|
40
|
-
import { useSelectionWizard } from "@/lib/wizard-history";
|
|
41
40
|
import { wizardSelectionFrom } from "@/lib/wizard-selection";
|
|
42
41
|
import type { OpenThreadTarget } from "@/routing";
|
|
42
|
+
import { useSelectionWizard } from "@/routing";
|
|
43
43
|
import { MailViewChrome } from "./MailViewChrome";
|
|
44
44
|
import type { MessageListCommands } from "./MessageList";
|
|
45
45
|
import { MessageRow } from "./MessageRow";
|
|
@@ -26,7 +26,6 @@
|
|
|
26
26
|
*/
|
|
27
27
|
import type { RemitImapThreadMessageResponse } from "@remit/api-http-client/types.gen.ts";
|
|
28
28
|
import { ReadingPaneEmpty } from "@remit/ui";
|
|
29
|
-
import { useNavigate } from "@tanstack/react-router";
|
|
30
29
|
import {
|
|
31
30
|
createContext,
|
|
32
31
|
type ReactNode,
|
|
@@ -59,10 +58,12 @@ import {
|
|
|
59
58
|
type OpenThreadTarget,
|
|
60
59
|
type ReplyMode,
|
|
61
60
|
replyToThread,
|
|
61
|
+
useCloseThread,
|
|
62
|
+
useGoToSection,
|
|
62
63
|
useIsComposing,
|
|
63
64
|
useIsReplying,
|
|
64
65
|
useOpenReply,
|
|
65
|
-
|
|
66
|
+
useOpenThread,
|
|
66
67
|
} from "@/routing";
|
|
67
68
|
|
|
68
69
|
/* ------------------------------------------------------------------ */
|
|
@@ -124,8 +125,9 @@ interface FlaggedPaneProps {
|
|
|
124
125
|
}
|
|
125
126
|
|
|
126
127
|
function FlaggedPaneProvider({ thread, children }: FlaggedPaneProps) {
|
|
127
|
-
const
|
|
128
|
-
const
|
|
128
|
+
const openThread = useOpenThread();
|
|
129
|
+
const closeThread = useCloseThread();
|
|
130
|
+
const goToSection = useGoToSection();
|
|
129
131
|
const { searchInput } = useMailContext();
|
|
130
132
|
const threadId = thread?.threadId;
|
|
131
133
|
const pointedAtMessageId = thread?.messageId;
|
|
@@ -161,37 +163,18 @@ function FlaggedPaneProvider({ thread, children }: FlaggedPaneProps) {
|
|
|
161
163
|
|
|
162
164
|
const handleOpenThread = useCallback(
|
|
163
165
|
(target: OpenThreadTarget, options?: OpenMessageOptions) => {
|
|
164
|
-
|
|
165
|
-
to: "/mail/flagged/$threadId/$messageId",
|
|
166
|
-
params: target,
|
|
167
|
-
replace: options?.replace,
|
|
168
|
-
// Commit the active query with the open so the debounced q-mirror —
|
|
169
|
-
// which walks back up to the list when the query goes active — is
|
|
170
|
-
// already satisfied and leaves the conversation alone. The *live*
|
|
171
|
-
// `searchInput`: a row can be tapped before the debounce settles, when
|
|
172
|
-
// the committed query is still empty.
|
|
173
|
-
search: (prev) => ({ ...prev, q: searchInput || undefined }),
|
|
174
|
-
hash: retainPanels,
|
|
175
|
-
});
|
|
166
|
+
openThread(target, { replace: options?.replace, query: searchInput });
|
|
176
167
|
},
|
|
177
|
-
[
|
|
168
|
+
[openThread, searchInput],
|
|
178
169
|
);
|
|
179
170
|
|
|
180
|
-
const handleCloseThread = useCallback(() => {
|
|
181
|
-
navigate({
|
|
182
|
-
to: "/mail/flagged",
|
|
183
|
-
search: (prev) => prev,
|
|
184
|
-
hash: retainPanels,
|
|
185
|
-
});
|
|
186
|
-
}, [navigate, retainPanels]);
|
|
187
|
-
|
|
188
171
|
const handleDeselectIfRemoved = useCallback(
|
|
189
172
|
(removedIds: string[]) => {
|
|
190
173
|
if (!selectedMessageId) return;
|
|
191
174
|
if (!removedIds.includes(selectedMessageId)) return;
|
|
192
|
-
|
|
175
|
+
closeThread();
|
|
193
176
|
},
|
|
194
|
-
[selectedMessageId,
|
|
177
|
+
[selectedMessageId, closeThread],
|
|
195
178
|
);
|
|
196
179
|
|
|
197
180
|
const actions = useThreadActions({
|
|
@@ -253,7 +236,7 @@ function FlaggedPaneProvider({ thread, children }: FlaggedPaneProps) {
|
|
|
253
236
|
// would otherwise fire at the message behind whatever is being typed — or
|
|
254
237
|
// answer a row the cursor moved to while a reply was open.
|
|
255
238
|
enabled: !isComposing && !isReplying,
|
|
256
|
-
onClose:
|
|
239
|
+
onClose: closeThread,
|
|
257
240
|
handlers: {
|
|
258
241
|
reply: () => replyToFocusedThread?.("reply"),
|
|
259
242
|
replyAll: () => replyToFocusedThread?.("reply-all"),
|
|
@@ -271,8 +254,8 @@ function FlaggedPaneProvider({ thread, children }: FlaggedPaneProps) {
|
|
|
271
254
|
if (!triageTarget) return;
|
|
272
255
|
toggleReadFor([triageTarget.messageId], !triageTarget.isRead);
|
|
273
256
|
},
|
|
274
|
-
goBrief: () =>
|
|
275
|
-
goSettings: () =>
|
|
257
|
+
goBrief: () => goToSection("brief"),
|
|
258
|
+
goSettings: () => goToSection("settings"),
|
|
276
259
|
},
|
|
277
260
|
});
|
|
278
261
|
|
|
@@ -293,7 +276,7 @@ function FlaggedPaneProvider({ thread, children }: FlaggedPaneProps) {
|
|
|
293
276
|
selectedThread,
|
|
294
277
|
conversation,
|
|
295
278
|
onOpenThread: handleOpenThread,
|
|
296
|
-
onCloseThread:
|
|
279
|
+
onCloseThread: closeThread,
|
|
297
280
|
nextThread: adjacentThread(nextMessageId),
|
|
298
281
|
previousThread: adjacentThread(previousMessageId),
|
|
299
282
|
actions,
|
|
@@ -58,7 +58,6 @@ import {
|
|
|
58
58
|
useAppShellLayout,
|
|
59
59
|
useSuggestList,
|
|
60
60
|
} from "@remit/ui";
|
|
61
|
-
import { useNavigate, useRouterState } from "@tanstack/react-router";
|
|
62
61
|
import { Menu, Search, X } from "lucide-react";
|
|
63
62
|
import {
|
|
64
63
|
type ReactNode,
|
|
@@ -88,7 +87,11 @@ import {
|
|
|
88
87
|
searchTokenLabel,
|
|
89
88
|
} from "@/lib/search-tokens";
|
|
90
89
|
import { spamOfferForResults } from "@/lib/spam-offer";
|
|
91
|
-
import {
|
|
90
|
+
import {
|
|
91
|
+
useBrowsedList,
|
|
92
|
+
useOpenWizard,
|
|
93
|
+
useScopeSearchToMailbox,
|
|
94
|
+
} from "@/routing";
|
|
92
95
|
|
|
93
96
|
export interface MailListHeaderProps {
|
|
94
97
|
title: string;
|
|
@@ -176,7 +179,7 @@ export function MailListHeader({
|
|
|
176
179
|
searchViewKey,
|
|
177
180
|
} = useMailContext();
|
|
178
181
|
const tokenContext = useSearchTokenContext();
|
|
179
|
-
const
|
|
182
|
+
const scopeSearchToMailbox = useScopeSearchToMailbox();
|
|
180
183
|
const layout = useAppShellLayout();
|
|
181
184
|
const tier = useLayoutTier();
|
|
182
185
|
const [searchOpen, setSearchOpen] = useState(false);
|
|
@@ -301,10 +304,10 @@ export function MailListHeader({
|
|
|
301
304
|
// The mailbox's appointed role is what lets a search scoped to Spam show its
|
|
302
305
|
// rows rather than drop them.
|
|
303
306
|
const { scope } = useSearchScope(accounts);
|
|
304
|
-
const
|
|
305
|
-
const scopedMailboxId = routeMailboxId(
|
|
307
|
+
const browsed = useBrowsedList();
|
|
308
|
+
const scopedMailboxId = routeMailboxId(browsed);
|
|
306
309
|
const routeScope = resultsScopeForRoute(
|
|
307
|
-
|
|
310
|
+
browsed,
|
|
308
311
|
scope,
|
|
309
312
|
scopedMailboxId ? resultFolderIndex.get(scopedMailboxId)?.role : undefined,
|
|
310
313
|
);
|
|
@@ -320,11 +323,7 @@ export function MailListHeader({
|
|
|
320
323
|
? {
|
|
321
324
|
kind: "global",
|
|
322
325
|
onScopeToSpam: () =>
|
|
323
|
-
|
|
324
|
-
to: "/mail/$mailboxId",
|
|
325
|
-
params: { mailboxId: spamOffer.mailboxId },
|
|
326
|
-
search: { q: searchQuery || undefined },
|
|
327
|
-
}),
|
|
326
|
+
scopeSearchToMailbox(spamOffer.mailboxId, searchQuery),
|
|
328
327
|
}
|
|
329
328
|
: routeScope;
|
|
330
329
|
|
|
@@ -10,7 +10,6 @@ import type {
|
|
|
10
10
|
import type { NavAccount, NavLinkComponent, NavMailboxRole } from "@remit/ui";
|
|
11
11
|
import { NavSidebar } from "@remit/ui";
|
|
12
12
|
import { useQueries, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
13
|
-
import { useLocation, useNavigate, useParams } from "@tanstack/react-router";
|
|
14
13
|
import { useCallback, useMemo, useState } from "react";
|
|
15
14
|
import { useTranslation } from "react-i18next";
|
|
16
15
|
import {
|
|
@@ -25,7 +24,7 @@ import {
|
|
|
25
24
|
removeSavedSearch,
|
|
26
25
|
saveSearch,
|
|
27
26
|
} from "@/lib/saved-searches";
|
|
28
|
-
import { NavLink } from "@/routing";
|
|
27
|
+
import { NavLink, useSearchEverything, useSelectedNavId } from "@/routing";
|
|
29
28
|
|
|
30
29
|
interface MailSidebarAdapterProps {
|
|
31
30
|
accounts: RemitImapAccountResponse[];
|
|
@@ -112,26 +111,6 @@ function toNavMailbox(
|
|
|
112
111
|
};
|
|
113
112
|
}
|
|
114
113
|
|
|
115
|
-
/**
|
|
116
|
-
* Resolves the currently selected nav ID from the active route so the kit
|
|
117
|
-
* component can highlight the right item.
|
|
118
|
-
* - /mail/outbox → "outbox"
|
|
119
|
-
* - /mail/flagged → "flagged"
|
|
120
|
-
* - /mail/brief → "brief"
|
|
121
|
-
* - /mail/$mailboxId → mailboxId
|
|
122
|
-
*/
|
|
123
|
-
function useSelectedNavId(): string {
|
|
124
|
-
const location = useLocation();
|
|
125
|
-
const params = useParams({ strict: false }) as { mailboxId?: string };
|
|
126
|
-
|
|
127
|
-
if (location.pathname.startsWith("/settings")) return "settings";
|
|
128
|
-
if (location.pathname.startsWith("/mail/outbox")) return "outbox";
|
|
129
|
-
if (location.pathname.startsWith("/mail/flagged")) return "flagged";
|
|
130
|
-
if (location.pathname.startsWith("/mail/brief")) return "brief";
|
|
131
|
-
if (params.mailboxId) return params.mailboxId;
|
|
132
|
-
return "";
|
|
133
|
-
}
|
|
134
|
-
|
|
135
114
|
/**
|
|
136
115
|
* Pane 1 of the 4-pane shell. Data bridge that fetches mailboxes per account,
|
|
137
116
|
* maps them onto the kit's NavAccount shape, and renders each entry as a real
|
|
@@ -144,7 +123,7 @@ export function MailSidebarAdapter({
|
|
|
144
123
|
variant = "desktop",
|
|
145
124
|
}: MailSidebarAdapterProps) {
|
|
146
125
|
const queryClient = useQueryClient();
|
|
147
|
-
const
|
|
126
|
+
const searchEverything = useSearchEverything();
|
|
148
127
|
const selectedNavId = useSelectedNavId();
|
|
149
128
|
const { searchInput, onSearchChange } = useMailContext();
|
|
150
129
|
const [savedSearches, setSavedSearches] = useState(loadSavedSearches);
|
|
@@ -341,10 +320,10 @@ export function MailSidebarAdapter({
|
|
|
341
320
|
const handleSelectSavedSearch = useCallback(
|
|
342
321
|
(query: string) => {
|
|
343
322
|
onSearchChange(query);
|
|
344
|
-
|
|
323
|
+
searchEverything(query);
|
|
345
324
|
onMailboxSelect?.();
|
|
346
325
|
},
|
|
347
|
-
[onSearchChange,
|
|
326
|
+
[onSearchChange, searchEverything, onMailboxSelect],
|
|
348
327
|
);
|
|
349
328
|
|
|
350
329
|
return (
|