@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
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import { useParams } from "@tanstack/react-router";
|
|
2
|
-
import { useMemo } from "react";
|
|
1
|
+
import { useNavigate, useParams } from "@tanstack/react-router";
|
|
2
|
+
import { useCallback, useMemo } from "react";
|
|
3
|
+
import { useBrowsedList, useNavigateToBrowsedList } from "./browsed-list";
|
|
4
|
+
import { useRetainOpenPanels } from "./fragment";
|
|
3
5
|
|
|
4
6
|
/** The conversation a list has open, as the address states it. */
|
|
5
7
|
export interface OpenThreadPath {
|
|
@@ -68,3 +70,88 @@ export function useOpenThreadPath(): OpenThreadPath | undefined {
|
|
|
68
70
|
[threadId, messageId],
|
|
69
71
|
);
|
|
70
72
|
}
|
|
73
|
+
|
|
74
|
+
/** How an open differs from the plain one, where it does. */
|
|
75
|
+
export interface OpenThreadOptions {
|
|
76
|
+
/** Replace the entry rather than push one, so Back skips the row. */
|
|
77
|
+
replace?: boolean;
|
|
78
|
+
/**
|
|
79
|
+
* Commit this query with the open. The debounced q-mirror walks back up to
|
|
80
|
+
* the list when a query goes active, so a row tapped before the debounce
|
|
81
|
+
* settles is closed again a moment later unless the address already says the
|
|
82
|
+
* query the conversation was opened under. Omitted, the open carries whatever
|
|
83
|
+
* query the address already had.
|
|
84
|
+
*/
|
|
85
|
+
query?: string;
|
|
86
|
+
/**
|
|
87
|
+
* Open under this mailbox rather than the one being browsed. A search hit
|
|
88
|
+
* carries its own, so a row can never open under a mailbox it does not belong
|
|
89
|
+
* to.
|
|
90
|
+
*/
|
|
91
|
+
mailboxId?: string;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Open a conversation on the list the reader is browsing.
|
|
96
|
+
*
|
|
97
|
+
* The thread is a child route of its list, so the list decides the address and
|
|
98
|
+
* the caller only names the conversation. The brief is the fallback, and legal
|
|
99
|
+
* for any thread: a list with no thread route under it — the outbox — and a
|
|
100
|
+
* mailbox route in the frame before its id resolves both still open the
|
|
101
|
+
* conversation rather than doing nothing, which is the dead row this route shape
|
|
102
|
+
* exists to delete.
|
|
103
|
+
*/
|
|
104
|
+
export function useOpenThread(): (
|
|
105
|
+
target: OpenThreadTarget,
|
|
106
|
+
options?: OpenThreadOptions,
|
|
107
|
+
) => void {
|
|
108
|
+
const navigate = useNavigate();
|
|
109
|
+
const retainPanels = useRetainOpenPanels();
|
|
110
|
+
const { list, mailboxId: browsedMailboxId } = useBrowsedList();
|
|
111
|
+
|
|
112
|
+
return useCallback(
|
|
113
|
+
(target: OpenThreadTarget, options?: OpenThreadOptions) => {
|
|
114
|
+
const query = options?.query;
|
|
115
|
+
const search = (prev: Record<string, unknown>) =>
|
|
116
|
+
query === undefined ? prev : { ...prev, q: query || undefined };
|
|
117
|
+
const rest = {
|
|
118
|
+
search,
|
|
119
|
+
hash: retainPanels,
|
|
120
|
+
replace: options?.replace ?? false,
|
|
121
|
+
};
|
|
122
|
+
if (list === "flagged") {
|
|
123
|
+
navigate({
|
|
124
|
+
to: "/mail/flagged/$threadId/$messageId",
|
|
125
|
+
params: target,
|
|
126
|
+
...rest,
|
|
127
|
+
});
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
const mailboxId = options?.mailboxId ?? browsedMailboxId;
|
|
131
|
+
if (list === "mailbox" && mailboxId) {
|
|
132
|
+
navigate({
|
|
133
|
+
to: "/mail/$mailboxId/$threadId/$messageId",
|
|
134
|
+
params: { mailboxId, ...target },
|
|
135
|
+
...rest,
|
|
136
|
+
});
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
navigate({
|
|
140
|
+
to: "/mail/brief/$threadId/$messageId",
|
|
141
|
+
params: target,
|
|
142
|
+
...rest,
|
|
143
|
+
});
|
|
144
|
+
},
|
|
145
|
+
[navigate, retainPanels, list, browsedMailboxId],
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Close the conversation, landing back on the list it was opened from.
|
|
151
|
+
*
|
|
152
|
+
* A navigation up rather than a flag, so nothing is left mounted below the list
|
|
153
|
+
* and Esc unwinds exactly one step.
|
|
154
|
+
*/
|
|
155
|
+
export function useCloseThread(): () => void {
|
|
156
|
+
return useNavigateToBrowsedList();
|
|
157
|
+
}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import { useParams } from "@tanstack/react-router";
|
|
1
|
+
import { useNavigate, useParams } from "@tanstack/react-router";
|
|
2
|
+
import { useCallback } from "react";
|
|
3
|
+
import { useNavigateToBrowsedList } from "./browsed-list";
|
|
4
|
+
import { useRetainOpenPanels } from "./fragment";
|
|
2
5
|
|
|
3
6
|
/**
|
|
4
7
|
* The outbox message the reader has open, read off the path.
|
|
@@ -15,3 +18,26 @@ export function useOutboxDraftId(): string | undefined {
|
|
|
15
18
|
});
|
|
16
19
|
return draft?.outboxMessageId;
|
|
17
20
|
}
|
|
21
|
+
|
|
22
|
+
/** Open an outbox message in the reading pane. */
|
|
23
|
+
export function useOpenOutboxDraft(): (outboxMessageId: string) => void {
|
|
24
|
+
const navigate = useNavigate();
|
|
25
|
+
const retainPanels = useRetainOpenPanels();
|
|
26
|
+
|
|
27
|
+
return useCallback(
|
|
28
|
+
(outboxMessageId: string) => {
|
|
29
|
+
navigate({
|
|
30
|
+
to: "/mail/outbox/draft/$outboxMessageId",
|
|
31
|
+
params: { outboxMessageId },
|
|
32
|
+
search: (prev: Record<string, unknown>) => prev,
|
|
33
|
+
hash: retainPanels,
|
|
34
|
+
});
|
|
35
|
+
},
|
|
36
|
+
[navigate, retainPanels],
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Close it again, landing back on the outbox. */
|
|
41
|
+
export function useCloseOutboxDraft(): () => void {
|
|
42
|
+
return useNavigateToBrowsedList();
|
|
43
|
+
}
|
|
@@ -34,7 +34,7 @@ import {
|
|
|
34
34
|
} from "@tanstack/react-router";
|
|
35
35
|
import { createElement, useState } from "react";
|
|
36
36
|
import { createDomHarness, type DomHarness } from "../test-support/dom";
|
|
37
|
-
import { useSearchField } from "./
|
|
37
|
+
import { useSearchField } from "./search-field";
|
|
38
38
|
|
|
39
39
|
let harness: DomHarness | undefined;
|
|
40
40
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { useRouter, useRouterState } from "@tanstack/react-router";
|
|
2
2
|
import { useCallback, useState } from "react";
|
|
3
|
+
import { useDebouncedValue } from "@/hooks/useDebouncedValue";
|
|
3
4
|
import { addressQuery, mailViewKey } from "@/lib/mail-route";
|
|
4
5
|
import { committedSearchQuery, searchInputForView } from "@/lib/search-view";
|
|
5
|
-
import { useDebouncedValue } from "./useDebouncedValue";
|
|
6
6
|
|
|
7
7
|
export interface SearchField {
|
|
8
8
|
/** The live field text, which every search surface binds. */
|
|
@@ -34,7 +34,7 @@ import { createElement, useState } from "react";
|
|
|
34
34
|
import { MailContext, type MailContextValue } from "@/lib/mail-context";
|
|
35
35
|
import { EMPTY_RESULT_FOLDER_INDEX } from "@/lib/result-folder";
|
|
36
36
|
import { createDomHarness, type DomHarness } from "../test-support/dom";
|
|
37
|
-
import { useSearchMirror } from "./
|
|
37
|
+
import { useSearchMirror } from "./search-mirror";
|
|
38
38
|
|
|
39
39
|
let harness: DomHarness | undefined;
|
|
40
40
|
|
|
@@ -32,7 +32,7 @@ import { MailContext, type MailContextValue } from "@/lib/mail-context";
|
|
|
32
32
|
import { mailViewKey } from "@/lib/mail-route";
|
|
33
33
|
import { EMPTY_RESULT_FOLDER_INDEX } from "@/lib/result-folder";
|
|
34
34
|
import { createDomHarness, type DomHarness } from "../test-support/dom";
|
|
35
|
-
import { useSearchMirror } from "./
|
|
35
|
+
import { useSearchMirror } from "./search-mirror";
|
|
36
36
|
|
|
37
37
|
let harness: DomHarness | undefined;
|
|
38
38
|
|
|
@@ -3,7 +3,8 @@ import { useEffect } from "react";
|
|
|
3
3
|
import { useMailContext } from "@/lib/mail-context";
|
|
4
4
|
import { addressQuery } from "@/lib/mail-route";
|
|
5
5
|
import { shouldMirrorQuery } from "@/lib/search-view";
|
|
6
|
-
import { useIsComposing
|
|
6
|
+
import { useIsComposing } from "./compose";
|
|
7
|
+
import { useIsReplying } from "./reply";
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* The list the mirror writes to. Each list calls the hook with its own route,
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { useNavigate } from "@tanstack/react-router";
|
|
2
|
+
import { useCallback } from "react";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Search everything, carrying the query.
|
|
6
|
+
*
|
|
7
|
+
* "Everything" is the daily brief — the cross-account view whose scope is
|
|
8
|
+
* nothing — so dropping the scope chip and running a saved search are the same
|
|
9
|
+
* movement, and both are a navigation rather than an edit of the query text:
|
|
10
|
+
* the chip mirrors the route (see `lib/search-scope.ts`), so editing the text
|
|
11
|
+
* would leave the chip up and the list still narrowed.
|
|
12
|
+
*/
|
|
13
|
+
export function useSearchEverything(): (query: string) => void {
|
|
14
|
+
const navigate = useNavigate();
|
|
15
|
+
return useCallback(
|
|
16
|
+
(query: string) => {
|
|
17
|
+
navigate({ to: "/mail/brief", search: { q: query || undefined } });
|
|
18
|
+
},
|
|
19
|
+
[navigate],
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Narrow the search to one mailbox, carrying the query.
|
|
25
|
+
*
|
|
26
|
+
* The route is the scope, so the offer to look in Spam for what a global search
|
|
27
|
+
* held back goes to Spam rather than rewriting the query with an `in:` term.
|
|
28
|
+
*/
|
|
29
|
+
export function useScopeSearchToMailbox(): (
|
|
30
|
+
mailboxId: string,
|
|
31
|
+
query: string,
|
|
32
|
+
) => void {
|
|
33
|
+
const navigate = useNavigate();
|
|
34
|
+
return useCallback(
|
|
35
|
+
(mailboxId: string, query: string) => {
|
|
36
|
+
navigate({
|
|
37
|
+
to: "/mail/$mailboxId",
|
|
38
|
+
params: { mailboxId },
|
|
39
|
+
search: { q: query || undefined },
|
|
40
|
+
});
|
|
41
|
+
},
|
|
42
|
+
[navigate],
|
|
43
|
+
);
|
|
44
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { useNavigate } from "@tanstack/react-router";
|
|
2
|
+
import { useCallback } from "react";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A top-level destination, as the sidebar and the keyboard name it. These are
|
|
6
|
+
* the moves that leave whatever the reader is on rather than open something
|
|
7
|
+
* under it, so they carry no panels and no query.
|
|
8
|
+
*/
|
|
9
|
+
export type AppSection =
|
|
10
|
+
| "brief"
|
|
11
|
+
| "flagged"
|
|
12
|
+
| "outbox"
|
|
13
|
+
| "settings"
|
|
14
|
+
| "accounts";
|
|
15
|
+
|
|
16
|
+
const sectionRoute = {
|
|
17
|
+
brief: "/mail/brief",
|
|
18
|
+
flagged: "/mail/flagged",
|
|
19
|
+
outbox: "/mail/outbox",
|
|
20
|
+
settings: "/settings",
|
|
21
|
+
accounts: "/settings/accounts",
|
|
22
|
+
} as const satisfies Record<AppSection, string>;
|
|
23
|
+
|
|
24
|
+
/** Go to a top-level section by name, so no caller writes a path. */
|
|
25
|
+
export function useGoToSection(): (section: AppSection) => void {
|
|
26
|
+
const navigate = useNavigate();
|
|
27
|
+
return useCallback(
|
|
28
|
+
(section: AppSection) => {
|
|
29
|
+
navigate({ to: sectionRoute[section] });
|
|
30
|
+
},
|
|
31
|
+
[navigate],
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Open one account's settings on its outgoing-mail fields.
|
|
37
|
+
*
|
|
38
|
+
* The account and the field to land on ride the address, so the banner that
|
|
39
|
+
* sends the reader there is not also responsible for telling the settings pane
|
|
40
|
+
* what it was about.
|
|
41
|
+
*/
|
|
42
|
+
export function useConfigureAccountSmtp(): (accountId: string) => void {
|
|
43
|
+
const navigate = useNavigate();
|
|
44
|
+
return useCallback(
|
|
45
|
+
(accountId: string) => {
|
|
46
|
+
navigate({
|
|
47
|
+
to: "/settings/accounts",
|
|
48
|
+
search: { editAccountId: accountId, focusSmtp: true },
|
|
49
|
+
});
|
|
50
|
+
},
|
|
51
|
+
[navigate],
|
|
52
|
+
);
|
|
53
|
+
}
|
|
File without changes
|
|
File without changes
|