@rapidmx/web-client 0.8.0 → 0.10.0
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/README.md +192 -88
- package/apps/shared/auth/accountUrl.ts +9 -0
- package/apps/shared/auth/adminAccess.ts +99 -0
- package/apps/shared/components/admin/elevation.ts +61 -0
- package/apps/shared/components/admin/layout/AdminShell.tsx +73 -7
- package/apps/shared/components/admin/settings/BrandingForm.tsx +2 -1
- package/apps/shared/components/admin/settings/DomainDnsSetup.tsx +87 -24
- package/apps/shared/components/calendar/layout/CalendarShell.tsx +8 -2
- package/apps/shared/components/contacts/ContactsToolbar.tsx +125 -115
- package/apps/shared/components/contacts/layout/ContactsShell.tsx +15 -3
- package/apps/shared/components/layout/AppShell.tsx +411 -288
- package/apps/shared/components/layout/BrandingChrome.tsx +3 -2
- package/apps/shared/components/layout/KeyEnrollmentGate.tsx +398 -389
- package/apps/shared/components/layout/MailboxProvisioning.tsx +168 -126
- package/apps/shared/components/layout/UserMenu.tsx +303 -150
- package/apps/shared/components/mail/ConversationList.tsx +269 -253
- package/apps/shared/components/mail/ConversationThreadPane.tsx +475 -434
- package/apps/shared/components/mail/LazyReadingPane.tsx +115 -0
- package/apps/shared/components/mail/MailAddress.tsx +88 -0
- package/apps/shared/components/mail/MailSelectionBar.tsx +235 -222
- package/apps/shared/components/mail/MessageDetailPane.tsx +1458 -1383
- package/apps/shared/components/mail/NewMailToasts.tsx +141 -0
- package/apps/shared/components/mail/compose/ComposeContext.tsx +279 -160
- package/apps/shared/components/mail/compose/ComposeToolbar.tsx +422 -418
- package/apps/shared/components/mail/compose/ComposeWindow.tsx +1842 -1700
- package/apps/shared/components/mail/compose/ComposeWindowPlaceholder.tsx +111 -0
- package/apps/shared/components/mail/compose/RichTextEditor.tsx +147 -118
- package/apps/shared/components/mail/compose/SendFailureAlert.tsx +48 -0
- package/apps/shared/components/mail/compose/composePerf.ts +46 -0
- package/apps/shared/components/mail/compose/quotedBody.ts +161 -100
- package/apps/shared/components/mail/layout/MailShell.tsx +476 -445
- package/apps/shared/components/mail/unreadStyle.tsx +75 -0
- package/apps/shared/components/settings/layout/SettingsShell.tsx +252 -240
- package/apps/shared/components/tasks/layout/TasksShell.tsx +15 -3
- package/apps/shared/keyboard/GlobalShortcuts.tsx +51 -0
- package/apps/shared/keyboard/ShortcutProvider.tsx +62 -0
- package/apps/shared/keyboard/ShortcutsDialog.tsx +84 -0
- package/apps/shared/keyboard/dispatch.ts +124 -0
- package/apps/shared/keyboard/format.ts +89 -0
- package/apps/shared/keyboard/keymap.ts +114 -0
- package/apps/shared/keyboard/match.ts +44 -0
- package/apps/shared/keyboard/parse.ts +136 -0
- package/apps/shared/keyboard/platform.ts +34 -0
- package/apps/shared/keyboard/registry.ts +65 -0
- package/apps/shared/keyboard/targets.ts +79 -0
- package/apps/shared/keyboard/useShortcut.ts +50 -0
- package/apps/shared/keyboard/useShortcutProps.ts +17 -0
- package/apps/shared/mail/folderCounts.ts +302 -0
- package/apps/shared/mail/listSnapshots.ts +87 -0
- package/apps/shared/mail/mergeFirstPage.ts +47 -0
- package/apps/shared/mail/messageReadState.ts +85 -0
- package/apps/shared/mail/newMailNotifications.ts +183 -0
- package/apps/shared/mail/useMailConnection.ts +150 -0
- package/apps/shared/mail/useMailLiveUpdates.ts +232 -0
- package/apps/shared/mail/useMarkMessageRead.ts +47 -0
- package/apps/shared/mail/useNewMailNotifications.ts +163 -0
- package/apps/shared/mail/useUnreadTitle.ts +42 -0
- package/apps/shared/navigation/AppRouter.tsx +300 -0
- package/apps/shared/navigation/appHrefs.ts +23 -0
- package/apps/shared/navigation/frameContext.tsx +35 -0
- package/apps/shared/navigation/idle.ts +45 -0
- package/apps/shared/navigation/routerContext.tsx +83 -0
- package/apps/shared/navigation/routes.ts +72 -0
- package/apps/shared/search/LocalIndexLifecycle.tsx +114 -98
- package/apps/shared/styles/app.css +28 -10
- package/apps/www/_routedPage.tsx +24 -0
- package/apps/www/_routes.ts +35 -0
- package/apps/www/calendar/index.tsx +55 -19
- package/apps/www/contacts/[uid].tsx +112 -107
- package/apps/www/contacts/index.tsx +584 -567
- package/apps/www/index.tsx +2280 -1854
- package/apps/www/messages/[uid].tsx +106 -101
- package/apps/www/settings/auto-reply/index.tsx +4 -1
- package/apps/www/settings/encryption/index.tsx +1252 -1249
- package/apps/www/settings/filters/[uid].tsx +4 -1
- package/apps/www/settings/filters/index.tsx +102 -99
- package/apps/www/settings/filters/new/index.tsx +138 -133
- package/apps/www/settings/labels/index.tsx +204 -201
- package/apps/www/settings/privacy/index.tsx +4 -1
- package/apps/www/settings/read-receipts/index.tsx +4 -1
- package/apps/www/settings/sharing/index.tsx +277 -274
- package/apps/www/settings/signatures/[uid].tsx +170 -167
- package/apps/www/settings/signatures/index.tsx +88 -85
- package/apps/www/settings/signatures/new/index.tsx +134 -129
- package/apps/www/tasks/index.tsx +18 -2
- package/dist/apps/shared/auth/accountUrl.d.ts +2 -0
- package/dist/apps/shared/auth/accountUrl.js +8 -0
- package/dist/apps/shared/auth/adminAccess.d.ts +30 -0
- package/dist/apps/shared/auth/adminAccess.js +89 -0
- package/dist/apps/shared/components/admin/elevation.d.ts +24 -0
- package/dist/apps/shared/components/admin/elevation.js +56 -0
- package/dist/apps/shared/components/admin/layout/AdminShell.d.ts +12 -4
- package/dist/apps/shared/components/admin/layout/AdminShell.js +49 -6
- package/dist/apps/shared/components/admin/settings/BrandingForm.js +1 -1
- package/dist/apps/shared/components/admin/settings/DomainDnsSetup.js +31 -13
- package/dist/apps/shared/components/calendar/layout/CalendarShell.d.ts +1 -1
- package/dist/apps/shared/components/calendar/layout/CalendarShell.js +8 -4
- package/dist/apps/shared/components/contacts/ContactsToolbar.d.ts +3 -1
- package/dist/apps/shared/components/contacts/ContactsToolbar.js +7 -4
- package/dist/apps/shared/components/contacts/layout/ContactsShell.d.ts +1 -1
- package/dist/apps/shared/components/contacts/layout/ContactsShell.js +15 -5
- package/dist/apps/shared/components/layout/AppShell.d.ts +29 -4
- package/dist/apps/shared/components/layout/AppShell.js +73 -14
- package/dist/apps/shared/components/layout/BrandingChrome.d.ts +3 -2
- package/dist/apps/shared/components/layout/BrandingChrome.js +3 -2
- package/dist/apps/shared/components/layout/KeyEnrollmentGate.js +10 -9
- package/dist/apps/shared/components/layout/MailboxProvisioning.d.ts +5 -2
- package/dist/apps/shared/components/layout/MailboxProvisioning.js +45 -8
- package/dist/apps/shared/components/layout/UserMenu.d.ts +28 -9
- package/dist/apps/shared/components/layout/UserMenu.js +87 -14
- package/dist/apps/shared/components/mail/ConversationList.js +9 -13
- package/dist/apps/shared/components/mail/ConversationThreadPane.d.ts +4 -1
- package/dist/apps/shared/components/mail/ConversationThreadPane.js +53 -28
- package/dist/apps/shared/components/mail/LazyReadingPane.d.ts +9 -0
- package/dist/apps/shared/components/mail/LazyReadingPane.js +82 -0
- package/dist/apps/shared/components/mail/MailAddress.d.ts +27 -0
- package/dist/apps/shared/components/mail/MailAddress.js +39 -0
- package/dist/apps/shared/components/mail/MailSelectionBar.d.ts +4 -1
- package/dist/apps/shared/components/mail/MailSelectionBar.js +11 -3
- package/dist/apps/shared/components/mail/MessageDetailPane.d.ts +7 -1
- package/dist/apps/shared/components/mail/MessageDetailPane.js +87 -36
- package/dist/apps/shared/components/mail/NewMailToasts.d.ts +19 -0
- package/dist/apps/shared/components/mail/NewMailToasts.js +55 -0
- package/dist/apps/shared/components/mail/compose/ComposeContext.d.ts +30 -0
- package/dist/apps/shared/components/mail/compose/ComposeContext.js +71 -5
- package/dist/apps/shared/components/mail/compose/ComposeToolbar.js +4 -3
- package/dist/apps/shared/components/mail/compose/ComposeWindow.d.ts +1 -1
- package/dist/apps/shared/components/mail/compose/ComposeWindow.js +122 -27
- package/dist/apps/shared/components/mail/compose/ComposeWindowPlaceholder.d.ts +19 -0
- package/dist/apps/shared/components/mail/compose/ComposeWindowPlaceholder.js +33 -0
- package/dist/apps/shared/components/mail/compose/RichTextEditor.d.ts +9 -1
- package/dist/apps/shared/components/mail/compose/RichTextEditor.js +20 -2
- package/dist/apps/shared/components/mail/compose/SendFailureAlert.d.ts +14 -0
- package/dist/apps/shared/components/mail/compose/SendFailureAlert.js +11 -0
- package/dist/apps/shared/components/mail/compose/composePerf.d.ts +16 -0
- package/dist/apps/shared/components/mail/compose/composePerf.js +41 -0
- package/dist/apps/shared/components/mail/compose/quotedBody.d.ts +13 -0
- package/dist/apps/shared/components/mail/compose/quotedBody.js +67 -11
- package/dist/apps/shared/components/mail/layout/MailShell.d.ts +19 -4
- package/dist/apps/shared/components/mail/layout/MailShell.js +85 -85
- package/dist/apps/shared/components/mail/unreadStyle.d.ts +45 -0
- package/dist/apps/shared/components/mail/unreadStyle.js +61 -0
- package/dist/apps/shared/components/settings/layout/SettingsShell.d.ts +1 -1
- package/dist/apps/shared/components/settings/layout/SettingsShell.js +15 -5
- package/dist/apps/shared/components/tasks/layout/TasksShell.d.ts +1 -1
- package/dist/apps/shared/components/tasks/layout/TasksShell.js +15 -5
- package/dist/apps/shared/keyboard/GlobalShortcuts.d.ts +14 -0
- package/dist/apps/shared/keyboard/GlobalShortcuts.js +37 -0
- package/dist/apps/shared/keyboard/ShortcutProvider.d.ts +20 -0
- package/dist/apps/shared/keyboard/ShortcutProvider.js +52 -0
- package/dist/apps/shared/keyboard/ShortcutsDialog.d.ts +14 -0
- package/dist/apps/shared/keyboard/ShortcutsDialog.js +42 -0
- package/dist/apps/shared/keyboard/dispatch.d.ts +16 -0
- package/dist/apps/shared/keyboard/dispatch.js +109 -0
- package/dist/apps/shared/keyboard/format.d.ts +12 -0
- package/dist/apps/shared/keyboard/format.js +74 -0
- package/dist/apps/shared/keyboard/keymap.d.ts +294 -0
- package/dist/apps/shared/keyboard/keymap.js +84 -0
- package/dist/apps/shared/keyboard/match.d.ts +20 -0
- package/dist/apps/shared/keyboard/match.js +31 -0
- package/dist/apps/shared/keyboard/parse.d.ts +31 -0
- package/dist/apps/shared/keyboard/parse.js +107 -0
- package/dist/apps/shared/keyboard/platform.d.ts +15 -0
- package/dist/apps/shared/keyboard/platform.js +22 -0
- package/dist/apps/shared/keyboard/registry.d.ts +39 -0
- package/dist/apps/shared/keyboard/registry.js +33 -0
- package/dist/apps/shared/keyboard/targets.d.ts +14 -0
- package/dist/apps/shared/keyboard/targets.js +67 -0
- package/dist/apps/shared/keyboard/useShortcut.d.ts +19 -0
- package/dist/apps/shared/keyboard/useShortcut.js +35 -0
- package/dist/apps/shared/keyboard/useShortcutProps.d.ts +10 -0
- package/dist/apps/shared/keyboard/useShortcutProps.js +15 -0
- package/dist/apps/shared/mail/folderCounts.d.ts +78 -0
- package/dist/apps/shared/mail/folderCounts.js +212 -0
- package/dist/apps/shared/mail/listSnapshots.d.ts +46 -0
- package/dist/apps/shared/mail/listSnapshots.js +43 -0
- package/dist/apps/shared/mail/mergeFirstPage.d.ts +23 -0
- package/dist/apps/shared/mail/mergeFirstPage.js +30 -0
- package/dist/apps/shared/mail/messageReadState.d.ts +32 -0
- package/dist/apps/shared/mail/messageReadState.js +63 -0
- package/dist/apps/shared/mail/newMailNotifications.d.ts +62 -0
- package/dist/apps/shared/mail/newMailNotifications.js +138 -0
- package/dist/apps/shared/mail/useMailConnection.d.ts +55 -0
- package/dist/apps/shared/mail/useMailConnection.js +96 -0
- package/dist/apps/shared/mail/useMailLiveUpdates.d.ts +55 -0
- package/dist/apps/shared/mail/useMailLiveUpdates.js +182 -0
- package/dist/apps/shared/mail/useMarkMessageRead.d.ts +12 -0
- package/dist/apps/shared/mail/useMarkMessageRead.js +44 -0
- package/dist/apps/shared/mail/useNewMailNotifications.d.ts +41 -0
- package/dist/apps/shared/mail/useNewMailNotifications.js +110 -0
- package/dist/apps/shared/mail/useUnreadTitle.d.ts +18 -0
- package/dist/apps/shared/mail/useUnreadTitle.js +31 -0
- package/dist/apps/shared/navigation/AppRouter.d.ts +52 -0
- package/dist/apps/shared/navigation/AppRouter.js +242 -0
- package/dist/apps/shared/navigation/appHrefs.d.ts +14 -0
- package/dist/apps/shared/navigation/appHrefs.js +20 -0
- package/dist/apps/shared/navigation/frameContext.d.ts +19 -0
- package/dist/apps/shared/navigation/frameContext.js +24 -0
- package/dist/apps/shared/navigation/idle.d.ts +14 -0
- package/dist/apps/shared/navigation/idle.js +43 -0
- package/dist/apps/shared/navigation/routerContext.d.ts +37 -0
- package/dist/apps/shared/navigation/routerContext.js +56 -0
- package/dist/apps/shared/navigation/routes.d.ts +32 -0
- package/dist/apps/shared/navigation/routes.js +37 -0
- package/dist/apps/shared/search/LocalIndexLifecycle.js +17 -3
- package/dist/apps/shared/styles/app.css +28 -10
- package/dist/apps/www/_routedPage.d.ts +12 -0
- package/dist/apps/www/_routedPage.js +19 -0
- package/dist/apps/www/_routes.d.ts +11 -0
- package/dist/apps/www/_routes.js +29 -0
- package/dist/apps/www/calendar/index.d.ts +2 -2
- package/dist/apps/www/calendar/index.js +39 -8
- package/dist/apps/www/contacts/[uid].d.ts +3 -9
- package/dist/apps/www/contacts/[uid].js +6 -2
- package/dist/apps/www/contacts/index.d.ts +2 -2
- package/dist/apps/www/contacts/index.js +17 -4
- package/dist/apps/www/index.d.ts +2 -2
- package/dist/apps/www/index.js +361 -34
- package/dist/apps/www/messages/[uid].d.ts +3 -7
- package/dist/apps/www/messages/[uid].js +7 -4
- package/dist/apps/www/settings/auto-reply/index.d.ts +2 -2
- package/dist/apps/www/settings/auto-reply/index.js +3 -1
- package/dist/apps/www/settings/encryption/index.d.ts +2 -2
- package/dist/apps/www/settings/encryption/index.js +3 -1
- package/dist/apps/www/settings/filters/[uid].d.ts +2 -2
- package/dist/apps/www/settings/filters/[uid].js +3 -1
- package/dist/apps/www/settings/filters/index.d.ts +2 -2
- package/dist/apps/www/settings/filters/index.js +3 -1
- package/dist/apps/www/settings/filters/new/index.d.ts +2 -2
- package/dist/apps/www/settings/filters/new/index.js +6 -2
- package/dist/apps/www/settings/labels/index.d.ts +2 -2
- package/dist/apps/www/settings/labels/index.js +3 -1
- package/dist/apps/www/settings/privacy/index.d.ts +2 -2
- package/dist/apps/www/settings/privacy/index.js +3 -1
- package/dist/apps/www/settings/read-receipts/index.d.ts +2 -2
- package/dist/apps/www/settings/read-receipts/index.js +3 -1
- package/dist/apps/www/settings/sharing/index.d.ts +2 -2
- package/dist/apps/www/settings/sharing/index.js +3 -1
- package/dist/apps/www/settings/signatures/[uid].d.ts +2 -2
- package/dist/apps/www/settings/signatures/[uid].js +3 -1
- package/dist/apps/www/settings/signatures/index.d.ts +2 -2
- package/dist/apps/www/settings/signatures/index.js +3 -1
- package/dist/apps/www/settings/signatures/new/index.d.ts +2 -2
- package/dist/apps/www/settings/signatures/new/index.js +6 -2
- package/dist/apps/www/tasks/index.d.ts +2 -2
- package/dist/apps/www/tasks/index.js +15 -3
- package/package.json +2 -2
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
2
|
+
// Copyright (C) 2026 Jean-Philippe Steinmetz
|
|
3
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
5
|
+
import React, { ComponentType, useEffect, useState } from "react";
|
|
6
|
+
import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
|
|
7
|
+
import Button from "@rapidmx/react-shared/components/buttons/Button.js";
|
|
8
|
+
import Skeleton from "@rapidmx/react-shared/components/feedback/Skeleton.js";
|
|
9
|
+
import type MessageDetailPaneType from "./MessageDetailPane.js";
|
|
10
|
+
import type ConversationThreadPaneType from "./ConversationThreadPane.js";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The reading pane (`MessageDetailPane`, and the thread pane that stacks it) is only rendered once a message is selected, and
|
|
14
|
+
* it is the heaviest piece of the inbox after the compose window - message security, the body sanitizer, the labels and move
|
|
15
|
+
* menus - so it is a chunk of its own, downloaded when the browser is idle after the list has loaded (`prefetchReadingPane()`)
|
|
16
|
+
* and, at the latest, on the first selection. Nothing about the pane changes: these wrap the same components, with a skeleton
|
|
17
|
+
* while the chunk loads and a reload prompt if it can't.
|
|
18
|
+
*
|
|
19
|
+
* Deliberately not `React.lazy()` + `<Suspense>`: a boundary that has shown its fallback holds the content back for up to 300 ms
|
|
20
|
+
* after the code arrives (React's fallback throttle), which would put a fifth of a second between a click and a pane whose
|
|
21
|
+
* code was already fetched. This reads the loaded component synchronously when there is one, so an already-loaded pane renders
|
|
22
|
+
* on the click's own frame, and it can retry a failed download.
|
|
23
|
+
*/
|
|
24
|
+
function lazyComponent<P extends object>(load: () => Promise<{ default: ComponentType<P> }>) {
|
|
25
|
+
let loaded: ComponentType<P> | undefined;
|
|
26
|
+
let loading: Promise<ComponentType<P>> | undefined;
|
|
27
|
+
|
|
28
|
+
/** Starts (once) loading the component's code. Rejects if it can't be downloaded - and then a later call tries again. */
|
|
29
|
+
function start(): Promise<ComponentType<P>> {
|
|
30
|
+
loading ??= load().then(
|
|
31
|
+
(module) => (loaded = module.default),
|
|
32
|
+
(err) => {
|
|
33
|
+
loading = undefined;
|
|
34
|
+
throw err;
|
|
35
|
+
},
|
|
36
|
+
);
|
|
37
|
+
return loading;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function Lazy(props: P) {
|
|
41
|
+
const [, setLoadedNow] = useState(0);
|
|
42
|
+
const [failed, setFailed] = useState(false);
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
if (loaded) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
let cancelled = false;
|
|
48
|
+
start().then(
|
|
49
|
+
() => !cancelled && setLoadedNow((n) => n + 1),
|
|
50
|
+
() => !cancelled && setFailed(true),
|
|
51
|
+
);
|
|
52
|
+
return () => {
|
|
53
|
+
cancelled = true;
|
|
54
|
+
};
|
|
55
|
+
}, []);
|
|
56
|
+
if (loaded) {
|
|
57
|
+
const Component = loaded;
|
|
58
|
+
return <Component {...props} />;
|
|
59
|
+
}
|
|
60
|
+
return failed ? <ChunkFailed /> : <PaneSkeleton />;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return { Lazy, start };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function PaneSkeleton() {
|
|
67
|
+
return (
|
|
68
|
+
<div role="status" aria-busy="true" className="flex-1 p-6 flex flex-col gap-3">
|
|
69
|
+
<span className="sr-only">Loading the message</span>
|
|
70
|
+
<Skeleton width="w-2/3" height="h-6" />
|
|
71
|
+
<Skeleton width="w-1/3" />
|
|
72
|
+
<Skeleton height="h-40" />
|
|
73
|
+
</div>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** A chunk that can't be downloaded (offline, or replaced by a deploy since this page loaded) is the one failure a reading
|
|
78
|
+
* pane can't recover from by itself: say so, with the fix, rather than leaving a blank pane. */
|
|
79
|
+
function ChunkFailed() {
|
|
80
|
+
return (
|
|
81
|
+
<div className="flex-1 p-6">
|
|
82
|
+
<Alert>
|
|
83
|
+
<p className="mb-2">This part of the page couldn’t be loaded. Check your connection, then reload.</p>
|
|
84
|
+
<Button type="button" variant="secondary" className="!w-auto" onClick={() => window.location.reload()}>
|
|
85
|
+
Reload
|
|
86
|
+
</Button>
|
|
87
|
+
</Alert>
|
|
88
|
+
</div>
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const messageDetailPane = lazyComponent<React.ComponentProps<typeof MessageDetailPaneType>>(() => import("./MessageDetailPane.js"));
|
|
93
|
+
const conversationThreadPane = lazyComponent<React.ComponentProps<typeof ConversationThreadPaneType>>(() => import("./ConversationThreadPane.js"));
|
|
94
|
+
|
|
95
|
+
/** Starts downloading the reading pane's code, for the page to call when it has nothing better to do. Never rejects. */
|
|
96
|
+
export function prefetchReadingPane(): void {
|
|
97
|
+
messageDetailPane.start().catch(() => undefined);
|
|
98
|
+
conversationThreadPane.start().catch(() => undefined);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** With nothing selected the pane is only its empty-state line, which needs none of the pane's code - so the chunk isn't
|
|
102
|
+
* requested until something is (or the page prefetches it). */
|
|
103
|
+
export function LazyMessageDetailPane(props: React.ComponentProps<typeof MessageDetailPaneType>) {
|
|
104
|
+
if (!props.message) {
|
|
105
|
+
return <p className="p-8 text-sm text-text-muted">Select a message to read it.</p>;
|
|
106
|
+
}
|
|
107
|
+
return <messageDetailPane.Lazy {...props} />;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function LazyConversationThreadPane(props: React.ComponentProps<typeof ConversationThreadPaneType>) {
|
|
111
|
+
if (!props.conversation) {
|
|
112
|
+
return <p className="p-8 text-sm text-text-muted">Select a conversation to read it.</p>;
|
|
113
|
+
}
|
|
114
|
+
return <conversationThreadPane.Lazy {...props} />;
|
|
115
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
2
|
+
// Copyright (C) 2026 Jean-Philippe Steinmetz
|
|
3
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
5
|
+
import React, { useState } from "react";
|
|
6
|
+
import { MailAddressLike, formatMailAddress, splitMailAddress } from "@rapidmx/react-shared/mail/mailAddress.js";
|
|
7
|
+
|
|
8
|
+
export interface MailAddressProps {
|
|
9
|
+
/** A message's `from`, a recipient, a conversation participant. */
|
|
10
|
+
recipient: MailAddressLike;
|
|
11
|
+
className?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* A sender or recipient as one compact line for a dense row: the name, when there is one, with the real address in muted
|
|
16
|
+
* type after it - `Jean-Philippe <jp@example.com>` - or the bare address without a name. The address is never left out,
|
|
17
|
+
* and when the row is too narrow for both the name gives way first (it shrinks a thousand times faster), then the
|
|
18
|
+
* address's own local part - the `@domain`, which is what says who really sent it, keeps its room to the end. The full
|
|
19
|
+
* `Name <address>` is the tooltip and, once, the accessible text (the two visible halves are hidden from assistive
|
|
20
|
+
* technology), so a screen reader hears one address, not fragments.
|
|
21
|
+
*/
|
|
22
|
+
export default function MailAddress({ recipient, className }: MailAddressProps) {
|
|
23
|
+
const { name, address } = splitMailAddress(recipient);
|
|
24
|
+
const full = formatMailAddress(recipient);
|
|
25
|
+
const at = address.lastIndexOf("@");
|
|
26
|
+
const local = at > 0 ? address.slice(0, at) : address;
|
|
27
|
+
const domain = at > 0 ? address.slice(at) : "";
|
|
28
|
+
return (
|
|
29
|
+
<span className={["flex min-w-0 items-baseline gap-1.5", className].filter(Boolean).join(" ")} title={full}>
|
|
30
|
+
<span className="sr-only">{full}</span>
|
|
31
|
+
{name && (
|
|
32
|
+
<span aria-hidden="true" className="truncate min-w-0" style={{ flexShrink: 1000 }}>
|
|
33
|
+
{name}
|
|
34
|
+
</span>
|
|
35
|
+
)}
|
|
36
|
+
<span
|
|
37
|
+
aria-hidden="true"
|
|
38
|
+
className={["flex min-w-0 items-baseline", name ? "text-xs font-normal text-text-muted" : ""].filter(Boolean).join(" ")}
|
|
39
|
+
>
|
|
40
|
+
{name && <span><</span>}
|
|
41
|
+
<span className="truncate min-w-0">{local}</span>
|
|
42
|
+
{domain && <span className="shrink-0">{domain}</span>}
|
|
43
|
+
{name && <span>></span>}
|
|
44
|
+
</span>
|
|
45
|
+
</span>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** How many recipients a `RecipientLine` shows before folding the rest behind "and N more". */
|
|
50
|
+
export const RECIPIENT_LINE_LIMIT = 3;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* A `To`/`Cc` line of a message header: every recipient as `Name <address>` in the text itself (never hover-only), wrapping
|
|
54
|
+
* onto more lines as needed. A long list shows its first `RECIPIENT_LINE_LIMIT` and folds the rest behind a button -
|
|
55
|
+
* "and 12 more" / "Show fewer" - so a message to a hundred people doesn't take the whole header. Nothing when empty.
|
|
56
|
+
*/
|
|
57
|
+
export function RecipientLine({ label, recipients }: { label: string; recipients: MailAddressLike[] }) {
|
|
58
|
+
const [expanded, setExpanded] = useState(false);
|
|
59
|
+
if (recipients.length === 0) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
const folded = recipients.length > RECIPIENT_LINE_LIMIT;
|
|
63
|
+
const shown = folded && !expanded ? recipients.slice(0, RECIPIENT_LINE_LIMIT) : recipients;
|
|
64
|
+
return (
|
|
65
|
+
<p className="text-sm text-text-muted break-words">
|
|
66
|
+
<span>{label}</span>{" "}
|
|
67
|
+
{shown.map((recipient, index) => (
|
|
68
|
+
<React.Fragment key={index}>
|
|
69
|
+
{index > 0 && ", "}
|
|
70
|
+
<span>{formatMailAddress(recipient)}</span>
|
|
71
|
+
</React.Fragment>
|
|
72
|
+
))}
|
|
73
|
+
{folded && (
|
|
74
|
+
<>
|
|
75
|
+
{" "}
|
|
76
|
+
<button
|
|
77
|
+
type="button"
|
|
78
|
+
aria-expanded={expanded}
|
|
79
|
+
onClick={() => setExpanded((open) => !open)}
|
|
80
|
+
className="text-xs font-semibold text-primary-dark underline hover:no-underline"
|
|
81
|
+
>
|
|
82
|
+
{expanded ? "Show fewer" : `and ${recipients.length - RECIPIENT_LINE_LIMIT} more`}
|
|
83
|
+
</button>
|
|
84
|
+
</>
|
|
85
|
+
)}
|
|
86
|
+
</p>
|
|
87
|
+
);
|
|
88
|
+
}
|