@remit/web-client 0.0.161 → 0.0.163
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 +9 -0
- package/src/components/compose/InlineCompose.tsx +9 -9
- package/src/components/mail/BriefPane.tsx +11 -2
- package/src/components/mail/ConversationView.tsx +51 -55
- package/src/components/mail/DraftsView.tsx +2 -0
- package/src/components/mail/FlaggedPane.tsx +11 -2
- package/src/components/mail/MailboxPane.tsx +10 -28
- package/src/components/mail/MessageActionMenu.tsx +2 -1
- package/src/components/mail/MessageBody.tsx +2 -2
- package/src/components/mail/MessageCard.tsx +1 -4
- package/src/components/mail/MessageList.tsx +2 -0
- package/src/components/mail/OutboxPane.tsx +7 -1
- package/src/components/mail/SwipeableMessageRow.tsx +2 -0
- package/src/components/mail/conversation-reply-reach.render.test.ts +147 -12
- package/src/components/ui/KeyboardShortcutsModal.tsx +7 -2
- package/src/hooks/search-mirror-detail.render.test.ts +0 -1
- package/src/hooks/useSearchMirror.ts +11 -3
- package/src/lib/intelligence-pref.test.ts +77 -0
- package/src/lib/intelligence-pref.ts +33 -1
- package/src/lib/mail-context.ts +6 -6
- package/src/routes/mail.tsx +74 -30
- package/src/routing/fragment.test.ts +68 -0
- package/src/routing/fragment.ts +79 -16
- package/src/routing/index.ts +8 -2
- package/src/routing/nav-link.tsx +6 -2
package/src/routing/fragment.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { useLocation } from "@tanstack/react-router";
|
|
1
|
+
import { useLocation, useNavigate } from "@tanstack/react-router";
|
|
2
|
+
import { useCallback, useMemo } from "react";
|
|
2
3
|
import { z } from "zod";
|
|
3
4
|
|
|
4
5
|
/**
|
|
@@ -7,29 +8,91 @@ import { z } from "zod";
|
|
|
7
8
|
* independent of in-memory data, so a cold load of the URL reproduces it
|
|
8
9
|
* exactly.
|
|
9
10
|
*/
|
|
10
|
-
export const panelFragments = [
|
|
11
|
-
"intelligence",
|
|
12
|
-
"nav",
|
|
13
|
-
"shortcuts",
|
|
14
|
-
"filters",
|
|
15
|
-
] as const;
|
|
11
|
+
export const panelFragments = ["intelligence", "nav", "shortcuts"] as const;
|
|
16
12
|
|
|
17
13
|
export type PanelFragment = (typeof panelFragments)[number];
|
|
18
14
|
|
|
15
|
+
/**
|
|
16
|
+
* The panels that cover the view rather than sit in it. The rail is a pane in
|
|
17
|
+
* the shell's row and the other two are modal over it, so a sheet opening can
|
|
18
|
+
* never take the rail down with it — the address holds the pane and the overlay
|
|
19
|
+
* at once. Two overlays cannot be up together, because the second one hides the
|
|
20
|
+
* first.
|
|
21
|
+
*/
|
|
22
|
+
export const overlayPanels = ["nav", "shortcuts"] as const;
|
|
23
|
+
|
|
24
|
+
export type OverlayPanel = (typeof overlayPanels)[number];
|
|
25
|
+
|
|
26
|
+
export const isOverlayPanel = (panel: PanelFragment): panel is OverlayPanel =>
|
|
27
|
+
(overlayPanels as readonly PanelFragment[]).includes(panel);
|
|
28
|
+
|
|
19
29
|
const panelFragmentSchema = z.enum(panelFragments);
|
|
20
30
|
|
|
21
31
|
/**
|
|
22
|
-
*
|
|
23
|
-
*
|
|
32
|
+
* The panels the address asks for, in the union's own order so one set of open
|
|
33
|
+
* panels has one spelling. A hand-edited or stale URL is a normal thing to
|
|
34
|
+
* receive, so an unrecognised name is dropped instead of throwing.
|
|
24
35
|
*/
|
|
25
|
-
export function
|
|
26
|
-
const
|
|
27
|
-
|
|
36
|
+
export function parseOpenPanels(hash: string): readonly PanelFragment[] {
|
|
37
|
+
const named = new Set(
|
|
38
|
+
hash
|
|
39
|
+
.split(",")
|
|
40
|
+
.map((token) => panelFragmentSchema.safeParse(token))
|
|
41
|
+
.filter((parsed) => parsed.success)
|
|
42
|
+
.map((parsed) => parsed.data),
|
|
43
|
+
);
|
|
44
|
+
return panelFragments.filter((panel) => named.has(panel));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** The address a set of open panels is written as. */
|
|
48
|
+
export function formatOpenPanels(panels: readonly PanelFragment[]): string {
|
|
49
|
+
return panelFragments.filter((panel) => panels.includes(panel)).join(",");
|
|
28
50
|
}
|
|
29
51
|
|
|
30
|
-
/** The
|
|
31
|
-
export function
|
|
32
|
-
|
|
33
|
-
select: (location) =>
|
|
52
|
+
/** The panels the address currently asks for. */
|
|
53
|
+
export function useOpenPanels(): readonly PanelFragment[] {
|
|
54
|
+
const hash = useLocation({
|
|
55
|
+
select: (location) => formatOpenPanels(parseOpenPanels(location.hash)),
|
|
34
56
|
});
|
|
57
|
+
return useMemo(() => parseOpenPanels(hash), [hash]);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* A destination's `hash`, for a navigation that keeps the reader where they
|
|
62
|
+
* are. The router drops the fragment on every navigation unless the destination
|
|
63
|
+
* asks for it, so this is what a link or a `navigate` call passes to state the
|
|
64
|
+
* rule rather than restate it: a pane is chrome a reader keeps up while they
|
|
65
|
+
* move from one conversation to the next, and an overlay is dismissed by going
|
|
66
|
+
* somewhere, which is what closing it means.
|
|
67
|
+
*
|
|
68
|
+
* The parameter is optional because the router hands a `hash` updater the
|
|
69
|
+
* previous fragment as `string | undefined`.
|
|
70
|
+
*/
|
|
71
|
+
export function retainOpenPanels(hash = ""): string {
|
|
72
|
+
return formatOpenPanels(
|
|
73
|
+
parseOpenPanels(hash).filter((panel) => !isOverlayPanel(panel)),
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Puts a set of panels up, replacing whatever was up before.
|
|
79
|
+
*
|
|
80
|
+
* `replace`, because a panel is chrome over the current view rather than a place
|
|
81
|
+
* to go: Back belongs to the message the reader came from, not to the rail they
|
|
82
|
+
* just collapsed. The fragment names panels and never an element, so it is not
|
|
83
|
+
* an anchor to scroll to either.
|
|
84
|
+
*/
|
|
85
|
+
export function useSetOpenPanels(): (panels: readonly PanelFragment[]) => void {
|
|
86
|
+
const navigate = useNavigate();
|
|
87
|
+
return useCallback(
|
|
88
|
+
(panels: readonly PanelFragment[]) => {
|
|
89
|
+
navigate({
|
|
90
|
+
search: true,
|
|
91
|
+
hash: formatOpenPanels(panels),
|
|
92
|
+
replace: true,
|
|
93
|
+
hashScrollIntoView: false,
|
|
94
|
+
});
|
|
95
|
+
},
|
|
96
|
+
[navigate],
|
|
97
|
+
);
|
|
35
98
|
}
|
package/src/routing/index.ts
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
export {
|
|
2
|
+
formatOpenPanels,
|
|
3
|
+
isOverlayPanel,
|
|
4
|
+
type OverlayPanel,
|
|
5
|
+
overlayPanels,
|
|
2
6
|
type PanelFragment,
|
|
3
7
|
panelFragments,
|
|
4
|
-
|
|
5
|
-
|
|
8
|
+
parseOpenPanels,
|
|
9
|
+
retainOpenPanels,
|
|
10
|
+
useOpenPanels,
|
|
11
|
+
useSetOpenPanels,
|
|
6
12
|
} from "./fragment";
|
|
7
13
|
export { NavLink, type NavLinkProps } from "./nav-link";
|
|
8
14
|
export {
|
package/src/routing/nav-link.tsx
CHANGED
|
@@ -7,7 +7,7 @@ import type {
|
|
|
7
7
|
} from "@tanstack/react-router";
|
|
8
8
|
import { createLink } from "@tanstack/react-router";
|
|
9
9
|
import type { ReactElement } from "react";
|
|
10
|
-
import type
|
|
10
|
+
import { type PanelFragment, retainOpenPanels } from "./fragment";
|
|
11
11
|
|
|
12
12
|
const RouterNavLink = createLink(NavLinkSurface);
|
|
13
13
|
|
|
@@ -42,6 +42,10 @@ export type NavLinkProps<
|
|
|
42
42
|
* The application's only navigation link. `createLink` keeps the router's `to` /
|
|
43
43
|
* `params` / `search` inference, so a route that does not exist or a param set
|
|
44
44
|
* that does not match it fails to compile instead of rendering a blank pane.
|
|
45
|
+
*
|
|
46
|
+
* A link that names no panel carries the open panes across: the rail is up
|
|
47
|
+
* until the reader puts it down, and every row in a list is a link. Overlays
|
|
48
|
+
* are left behind, because going somewhere is what dismisses one.
|
|
45
49
|
*/
|
|
46
50
|
export function NavLink<
|
|
47
51
|
TRouter extends AnyRouter = RegisteredRouter,
|
|
@@ -54,5 +58,5 @@ export function NavLink({
|
|
|
54
58
|
fragment,
|
|
55
59
|
...props
|
|
56
60
|
}: CreateLinkProps & { fragment?: PanelFragment }): ReactElement {
|
|
57
|
-
return <RouterNavLink {...props} hash={fragment} />;
|
|
61
|
+
return <RouterNavLink {...props} hash={fragment ?? retainOpenPanels} />;
|
|
58
62
|
}
|