@rapidmx/web-client 0.16.0 → 0.17.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 +36 -28
- package/apps/admin/distribution-lists/new/index.tsx +3 -1
- package/apps/admin/domains/[uid].tsx +3 -1
- package/apps/admin/domains/new/index.tsx +3 -1
- package/apps/admin/escrow-scopes/[uid].tsx +3 -1
- package/apps/admin/escrow-scopes/new/index.tsx +3 -1
- package/apps/admin/index.tsx +3 -1
- package/apps/admin/mailboxes/[uid].tsx +3 -1
- package/apps/admin/mailboxes/new/index.tsx +3 -1
- package/apps/admin/transport-rules/new/index.tsx +3 -1
- package/apps/escrow/matters/new/index.tsx +3 -1
- package/apps/shared/components/admin/layout/AdminShell.tsx +4 -1
- package/apps/shared/components/admin/setup/SetupWizard.tsx +3 -1
- package/apps/shared/components/calendar/layout/CalendarShell.tsx +2 -2
- package/apps/shared/components/contacts/layout/ContactsShell.tsx +3 -2
- package/apps/shared/components/layout/AppShell.tsx +24 -43
- package/apps/shared/components/mail/ConversationThreadPane.tsx +1 -0
- package/apps/shared/components/mail/EmptyFolderBar.tsx +41 -0
- package/apps/shared/components/mail/MailSelectionBar.tsx +19 -15
- package/apps/shared/components/mail/MenuButton.tsx +31 -11
- package/apps/shared/components/mail/MessageDetailPane.tsx +202 -4
- package/apps/shared/components/mail/PermanentDeleteDialog.tsx +47 -0
- package/apps/shared/components/mail/layout/MailShell.tsx +37 -34
- package/apps/shared/components/mail/reading/MessageMoreMenu.tsx +258 -0
- package/apps/shared/components/mail/reading/MessageSourceDialog.tsx +79 -0
- package/apps/shared/components/mail/reading/messageExport.ts +59 -0
- package/apps/shared/components/mail/reading/printMessage.ts +99 -0
- package/apps/shared/components/mail/reading/useMessageActions.ts +443 -0
- package/apps/shared/components/settings/layout/SettingsShell.tsx +4 -2
- package/apps/shared/components/tasks/layout/TasksShell.tsx +3 -2
- package/apps/shared/keyboard/GlobalShortcuts.tsx +1 -1
- package/apps/shared/mail/folderOfType.ts +49 -0
- package/apps/shared/mail/permanentDelete.ts +160 -0
- package/apps/shared/mail/reportNotices.ts +66 -0
- package/apps/shared/mail/senderBlocking.ts +141 -0
- package/apps/shared/mail/useMailboxUpdateAccess.ts +60 -0
- package/apps/shared/mail/usePermanentDelete.tsx +160 -0
- package/apps/shared/navigation/frameContext.tsx +1 -1
- package/apps/shared/navigation/index.ts +43 -0
- package/apps/shared/navigation/pageTitle.ts +19 -0
- package/apps/www/_shell.tsx +82 -0
- package/apps/www/calendar/index.tsx +5 -2
- package/apps/www/contacts/[uid].tsx +6 -3
- package/apps/www/contacts/index.tsx +8 -3
- package/apps/www/index.tsx +146 -31
- package/apps/www/messages/[uid].tsx +8 -4
- package/apps/www/settings/appearance/index.tsx +5 -2
- package/apps/www/settings/auto-reply/index.tsx +5 -2
- package/apps/www/settings/blocked-senders/index.tsx +303 -0
- package/apps/www/settings/encryption/index.tsx +5 -2
- package/apps/www/settings/filters/[uid].tsx +5 -2
- package/apps/www/settings/filters/index.tsx +5 -2
- package/apps/www/settings/filters/new/index.tsx +30 -5
- package/apps/www/settings/labels/index.tsx +5 -2
- package/apps/www/settings/privacy/index.tsx +5 -2
- package/apps/www/settings/profile/index.tsx +5 -2
- package/apps/www/settings/read-receipts/index.tsx +5 -2
- package/apps/www/settings/sharing/index.tsx +5 -2
- package/apps/www/settings/signatures/[uid].tsx +5 -2
- package/apps/www/settings/signatures/index.tsx +5 -2
- package/apps/www/settings/signatures/new/index.tsx +6 -3
- package/apps/www/tasks/index.tsx +7 -2
- package/dist/apps/admin/distribution-lists/new/index.js +3 -1
- package/dist/apps/admin/domains/[uid].js +3 -1
- package/dist/apps/admin/domains/new/index.js +3 -1
- package/dist/apps/admin/escrow-scopes/[uid].js +3 -1
- package/dist/apps/admin/escrow-scopes/new/index.js +3 -1
- package/dist/apps/admin/index.js +3 -1
- package/dist/apps/admin/mailboxes/[uid].js +3 -1
- package/dist/apps/admin/mailboxes/new/index.js +3 -1
- package/dist/apps/admin/transport-rules/new/index.js +3 -1
- package/dist/apps/escrow/matters/new/index.js +3 -1
- package/dist/apps/shared/components/admin/layout/AdminShell.js +4 -1
- package/dist/apps/shared/components/admin/setup/SetupWizard.js +3 -1
- package/dist/apps/shared/components/calendar/layout/CalendarShell.js +2 -2
- package/dist/apps/shared/components/contacts/layout/ContactsShell.js +3 -2
- package/dist/apps/shared/components/layout/AppShell.d.ts +8 -11
- package/dist/apps/shared/components/layout/AppShell.js +24 -33
- package/dist/apps/shared/components/mail/ConversationThreadPane.js +1 -1
- package/dist/apps/shared/components/mail/EmptyFolderBar.d.ts +18 -0
- package/dist/apps/shared/components/mail/EmptyFolderBar.js +10 -0
- package/dist/apps/shared/components/mail/MailSelectionBar.d.ts +9 -4
- package/dist/apps/shared/components/mail/MailSelectionBar.js +10 -12
- package/dist/apps/shared/components/mail/MenuButton.d.ts +9 -2
- package/dist/apps/shared/components/mail/MenuButton.js +15 -11
- package/dist/apps/shared/components/mail/MessageDetailPane.d.ts +5 -0
- package/dist/apps/shared/components/mail/MessageDetailPane.js +147 -7
- package/dist/apps/shared/components/mail/PermanentDeleteDialog.d.ts +19 -0
- package/dist/apps/shared/components/mail/PermanentDeleteDialog.js +11 -0
- package/dist/apps/shared/components/mail/layout/MailShell.d.ts +13 -9
- package/dist/apps/shared/components/mail/layout/MailShell.js +27 -33
- package/dist/apps/shared/components/mail/reading/MessageMoreMenu.d.ts +56 -0
- package/dist/apps/shared/components/mail/reading/MessageMoreMenu.js +153 -0
- package/dist/apps/shared/components/mail/reading/MessageSourceDialog.d.ts +27 -0
- package/dist/apps/shared/components/mail/reading/MessageSourceDialog.js +27 -0
- package/dist/apps/shared/components/mail/reading/messageExport.d.ts +19 -0
- package/dist/apps/shared/components/mail/reading/messageExport.js +51 -0
- package/dist/apps/shared/components/mail/reading/printMessage.d.ts +30 -0
- package/dist/apps/shared/components/mail/reading/printMessage.js +70 -0
- package/dist/apps/shared/components/mail/reading/useMessageActions.d.ts +57 -0
- package/dist/apps/shared/components/mail/reading/useMessageActions.js +366 -0
- package/dist/apps/shared/components/settings/layout/SettingsShell.js +4 -2
- package/dist/apps/shared/components/tasks/layout/TasksShell.js +3 -2
- package/dist/apps/shared/keyboard/GlobalShortcuts.js +1 -1
- package/dist/apps/shared/mail/folderOfType.d.ts +13 -0
- package/dist/apps/shared/mail/folderOfType.js +40 -0
- package/dist/apps/shared/mail/permanentDelete.d.ts +62 -0
- package/dist/apps/shared/mail/permanentDelete.js +127 -0
- package/dist/apps/shared/mail/reportNotices.d.ts +21 -0
- package/dist/apps/shared/mail/reportNotices.js +47 -0
- package/dist/apps/shared/mail/senderBlocking.d.ts +26 -0
- package/dist/apps/shared/mail/senderBlocking.js +92 -0
- package/dist/apps/shared/mail/useMailboxUpdateAccess.d.ts +11 -0
- package/dist/apps/shared/mail/useMailboxUpdateAccess.js +52 -0
- package/dist/apps/shared/mail/usePermanentDelete.d.ts +35 -0
- package/dist/apps/shared/mail/usePermanentDelete.js +96 -0
- package/dist/apps/shared/navigation/frameContext.d.ts +1 -1
- package/dist/apps/shared/navigation/index.d.ts +16 -0
- package/dist/apps/shared/navigation/index.js +34 -0
- package/dist/apps/shared/navigation/pageTitle.d.ts +11 -0
- package/dist/apps/shared/navigation/pageTitle.js +12 -0
- package/dist/apps/www/_shell.d.ts +26 -0
- package/dist/apps/www/_shell.js +31 -0
- package/dist/apps/www/calendar/index.d.ts +7 -2
- package/dist/apps/www/calendar/index.js +4 -2
- package/dist/apps/www/contacts/[uid].d.ts +14 -3
- package/dist/apps/www/contacts/[uid].js +5 -3
- package/dist/apps/www/contacts/index.d.ts +7 -2
- package/dist/apps/www/contacts/index.js +6 -4
- package/dist/apps/www/index.d.ts +7 -2
- package/dist/apps/www/index.js +117 -19
- package/dist/apps/www/messages/[uid].d.ts +14 -3
- package/dist/apps/www/messages/[uid].js +7 -5
- package/dist/apps/www/settings/appearance/index.d.ts +8 -2
- package/dist/apps/www/settings/appearance/index.js +4 -2
- package/dist/apps/www/settings/auto-reply/index.d.ts +7 -2
- package/dist/apps/www/settings/auto-reply/index.js +4 -2
- package/dist/apps/www/settings/blocked-senders/index.d.ts +9 -0
- package/dist/apps/www/settings/blocked-senders/index.js +134 -0
- package/dist/apps/www/settings/encryption/index.d.ts +7 -2
- package/dist/apps/www/settings/encryption/index.js +4 -2
- package/dist/apps/www/settings/filters/[uid].d.ts +7 -2
- package/dist/apps/www/settings/filters/[uid].js +4 -2
- package/dist/apps/www/settings/filters/index.d.ts +7 -2
- package/dist/apps/www/settings/filters/index.js +4 -2
- package/dist/apps/www/settings/filters/new/index.d.ts +7 -2
- package/dist/apps/www/settings/filters/new/index.js +28 -5
- package/dist/apps/www/settings/labels/index.d.ts +7 -2
- package/dist/apps/www/settings/labels/index.js +4 -2
- package/dist/apps/www/settings/privacy/index.d.ts +7 -2
- package/dist/apps/www/settings/privacy/index.js +4 -2
- package/dist/apps/www/settings/profile/index.d.ts +7 -2
- package/dist/apps/www/settings/profile/index.js +4 -2
- package/dist/apps/www/settings/read-receipts/index.d.ts +7 -2
- package/dist/apps/www/settings/read-receipts/index.js +4 -2
- package/dist/apps/www/settings/sharing/index.d.ts +7 -2
- package/dist/apps/www/settings/sharing/index.js +4 -2
- package/dist/apps/www/settings/signatures/[uid].d.ts +7 -2
- package/dist/apps/www/settings/signatures/[uid].js +4 -2
- package/dist/apps/www/settings/signatures/index.d.ts +7 -2
- package/dist/apps/www/settings/signatures/index.js +4 -2
- package/dist/apps/www/settings/signatures/new/index.d.ts +7 -2
- package/dist/apps/www/settings/signatures/new/index.js +5 -3
- package/dist/apps/www/tasks/index.d.ts +7 -2
- package/dist/apps/www/tasks/index.js +5 -3
- package/package.json +4 -2
- package/apps/shared/navigation/AppRouter.tsx +0 -300
- package/apps/shared/navigation/idle.ts +0 -45
- package/apps/shared/navigation/routerContext.tsx +0 -83
- package/apps/shared/navigation/routes.ts +0 -72
- package/apps/www/_routedPage.tsx +0 -24
- package/apps/www/_routes.ts +0 -37
- package/dist/apps/shared/navigation/AppRouter.d.ts +0 -52
- package/dist/apps/shared/navigation/AppRouter.js +0 -242
- package/dist/apps/shared/navigation/idle.d.ts +0 -14
- package/dist/apps/shared/navigation/idle.js +0 -43
- package/dist/apps/shared/navigation/routerContext.d.ts +0 -37
- package/dist/apps/shared/navigation/routerContext.js +0 -56
- package/dist/apps/shared/navigation/routes.d.ts +0 -32
- package/dist/apps/shared/navigation/routes.js +0 -37
- package/dist/apps/www/_routedPage.d.ts +0 -12
- package/dist/apps/www/_routedPage.js +0 -19
- package/dist/apps/www/_routes.d.ts +0 -11
- package/dist/apps/www/_routes.js +0 -31
|
@@ -4,6 +4,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
4
4
|
// SPDX-License-Identifier: MPL-2.0
|
|
5
5
|
///////////////////////////////////////////////////////////////////////////////
|
|
6
6
|
import { useState } from "react";
|
|
7
|
+
import { useRouter } from "@rapidrest/react/client";
|
|
7
8
|
import { ApiRequestError } from "@rapidmx/react-shared/util/api.js";
|
|
8
9
|
import { deleteDomain, updateDomain } from "@rapidmx/react-shared/admin/domainsApi.js";
|
|
9
10
|
import AdminShell from "../../shared/components/admin/layout/AdminShell.js";
|
|
@@ -17,6 +18,7 @@ export default function DomainDetailPage(props) {
|
|
|
17
18
|
return (_jsx(AdminShell, { ...props, active: "domains", children: _jsx(DomainDetailContent, { uid: props.params.uid }) }));
|
|
18
19
|
}
|
|
19
20
|
function DomainDetailContent({ uid }) {
|
|
21
|
+
const { navigate } = useRouter();
|
|
20
22
|
const [domain, setDomain] = useState(null);
|
|
21
23
|
const [confirmingDelete, setConfirmingDelete] = useState(false);
|
|
22
24
|
const [deleting, setDeleting] = useState(false);
|
|
@@ -39,7 +41,7 @@ function DomainDetailContent({ uid }) {
|
|
|
39
41
|
setDeleteError(null);
|
|
40
42
|
try {
|
|
41
43
|
await deleteDomain(domain.uid, domain.version);
|
|
42
|
-
|
|
44
|
+
void navigate("/admin/domains");
|
|
43
45
|
}
|
|
44
46
|
catch (err) {
|
|
45
47
|
setDeleteError(err instanceof ApiRequestError ? err.message : "Could not delete this domain.");
|
|
@@ -4,6 +4,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
4
4
|
// SPDX-License-Identifier: MPL-2.0
|
|
5
5
|
///////////////////////////////////////////////////////////////////////////////
|
|
6
6
|
import { useEffect, useState } from "react";
|
|
7
|
+
import { useRouter } from "@rapidrest/react/client";
|
|
7
8
|
import { ApiRequestError } from "@rapidmx/react-shared/util/api.js";
|
|
8
9
|
import { createDomain, listDomains } from "@rapidmx/react-shared/admin/domainsApi.js";
|
|
9
10
|
import AdminShell from "../../../shared/components/admin/layout/AdminShell.js";
|
|
@@ -15,6 +16,7 @@ export default function NewDomainPage(props) {
|
|
|
15
16
|
return (_jsx(AdminShell, { ...props, active: "domains", children: _jsx(NewDomainForm, {}) }));
|
|
16
17
|
}
|
|
17
18
|
function NewDomainForm() {
|
|
19
|
+
const { navigate } = useRouter();
|
|
18
20
|
const [name, setName] = useState("");
|
|
19
21
|
const [aliasOf, setAliasOf] = useState("");
|
|
20
22
|
// Only an existing, non-alias domain can be aliased (no chains) - see `Domain.aliasOf`'s own doc comment
|
|
@@ -37,7 +39,7 @@ function NewDomainForm() {
|
|
|
37
39
|
setSaving(true);
|
|
38
40
|
try {
|
|
39
41
|
const domain = await createDomain({ name: name.trim(), aliasOf: aliasOf || undefined });
|
|
40
|
-
|
|
42
|
+
void navigate(`/admin/domains/${encodeURIComponent(domain.uid)}`);
|
|
41
43
|
}
|
|
42
44
|
catch (err) {
|
|
43
45
|
setError(err instanceof ApiRequestError ? err.message : "Could not create the domain.");
|
|
@@ -4,6 +4,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
4
4
|
// SPDX-License-Identifier: MPL-2.0
|
|
5
5
|
///////////////////////////////////////////////////////////////////////////////
|
|
6
6
|
import { useEffect, useState } from "react";
|
|
7
|
+
import { useRouter } from "@rapidrest/react/client";
|
|
7
8
|
import { ApiRequestError } from "@rapidmx/react-shared/util/api.js";
|
|
8
9
|
import { toDatetimeLocal } from "@rapidmx/react-shared/util/dateInput.js";
|
|
9
10
|
import { deleteEscrowScope, getEscrowScope, updateEscrowScope, } from "@rapidmx/react-shared/admin/escrowScopesApi.js";
|
|
@@ -95,6 +96,7 @@ export default function EscrowScopeDetailPage(props) {
|
|
|
95
96
|
return (_jsx(AdminShell, { ...props, active: "escrowScopes", children: _jsx(EscrowScopeDetailContent, { uid: props.params.uid, adminUid: props.userUid }) }));
|
|
96
97
|
}
|
|
97
98
|
function EscrowScopeDetailContent({ uid, adminUid }) {
|
|
99
|
+
const { navigate } = useRouter();
|
|
98
100
|
const [original, setOriginal] = useState(null);
|
|
99
101
|
const [name, setName] = useState("");
|
|
100
102
|
const [description, setDescription] = useState("");
|
|
@@ -198,7 +200,7 @@ function EscrowScopeDetailContent({ uid, adminUid }) {
|
|
|
198
200
|
setDeleteError(null);
|
|
199
201
|
try {
|
|
200
202
|
await deleteEscrowScope(original.uid, original.version);
|
|
201
|
-
|
|
203
|
+
void navigate("/admin/escrow-scopes");
|
|
202
204
|
}
|
|
203
205
|
catch (err) {
|
|
204
206
|
// Most commonly a 409 ("referenced by an existing Matter") — see `deleteEscrowScope()`'s own
|
|
@@ -4,6 +4,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
4
4
|
// SPDX-License-Identifier: MPL-2.0
|
|
5
5
|
///////////////////////////////////////////////////////////////////////////////
|
|
6
6
|
import { useState } from "react";
|
|
7
|
+
import { useRouter } from "@rapidrest/react/client";
|
|
7
8
|
import { ApiRequestError } from "@rapidmx/react-shared/util/api.js";
|
|
8
9
|
import { createEscrowScope } from "@rapidmx/react-shared/admin/escrowScopesApi.js";
|
|
9
10
|
import AdminShell from "../../../shared/components/admin/layout/AdminShell.js";
|
|
@@ -16,6 +17,7 @@ export default function NewEscrowScopePage(props) {
|
|
|
16
17
|
return (_jsx(AdminShell, { ...props, active: "escrowScopes", children: _jsx(NewEscrowScopeForm, { adminUid: props.userUid }) }));
|
|
17
18
|
}
|
|
18
19
|
function NewEscrowScopeForm({ adminUid }) {
|
|
20
|
+
const { navigate } = useRouter();
|
|
19
21
|
const [name, setName] = useState("");
|
|
20
22
|
const [description, setDescription] = useState("");
|
|
21
23
|
const [keyAndHolders, setKeyAndHolders] = useState(emptyEscrowScopeKeyAndHoldersValue());
|
|
@@ -61,7 +63,7 @@ function NewEscrowScopeForm({ adminUid }) {
|
|
|
61
63
|
requiredHolders: keyAndHolders.requiredHolders,
|
|
62
64
|
notifySubjectOnAccess: keyAndHolders.notifySubjectOnAccess,
|
|
63
65
|
});
|
|
64
|
-
|
|
66
|
+
void navigate(`/admin/escrow-scopes/${encodeURIComponent(created.uid)}`);
|
|
65
67
|
}
|
|
66
68
|
catch (err) {
|
|
67
69
|
setError(err instanceof ApiRequestError ? err.message : "Could not create the escrow scope.");
|
package/dist/apps/admin/index.js
CHANGED
|
@@ -4,6 +4,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
4
4
|
// SPDX-License-Identifier: MPL-2.0
|
|
5
5
|
///////////////////////////////////////////////////////////////////////////////
|
|
6
6
|
import { useEffect, useState } from "react";
|
|
7
|
+
import { useRouter } from "@rapidrest/react/client";
|
|
7
8
|
import { ApiRequestError } from "@rapidmx/react-shared/util/api.js";
|
|
8
9
|
import { listMailboxes } from "@rapidmx/react-shared/mail/mailApi.js";
|
|
9
10
|
import { reopenSetup } from "@rapidmx/react-shared/admin/setupApi.js";
|
|
@@ -18,6 +19,7 @@ export default function MailboxesListPage(props) {
|
|
|
18
19
|
return (_jsx(AdminShell, { ...props, active: "mailboxes", children: _jsx(MailboxesListContent, {}) }));
|
|
19
20
|
}
|
|
20
21
|
function MailboxesListContent() {
|
|
22
|
+
const { navigate } = useRouter();
|
|
21
23
|
const [page, setPage] = useState(0);
|
|
22
24
|
const [mailboxes, setMailboxes] = useState([]);
|
|
23
25
|
const [loading, setLoading] = useState(true);
|
|
@@ -38,7 +40,7 @@ function MailboxesListContent() {
|
|
|
38
40
|
setReopening(true);
|
|
39
41
|
try {
|
|
40
42
|
await reopenSetup();
|
|
41
|
-
|
|
43
|
+
void navigate("/admin/setup");
|
|
42
44
|
}
|
|
43
45
|
catch (err) {
|
|
44
46
|
setError(err instanceof ApiRequestError ? err.message : "Could not reopen setup.");
|
|
@@ -4,6 +4,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
4
4
|
// SPDX-License-Identifier: MPL-2.0
|
|
5
5
|
///////////////////////////////////////////////////////////////////////////////
|
|
6
6
|
import { useEffect, useState } from "react";
|
|
7
|
+
import { useRouter } from "@rapidrest/react/client";
|
|
7
8
|
import { ApiRequestError } from "@rapidmx/react-shared/util/api.js";
|
|
8
9
|
import { deleteMailbox, getMailbox, impersonateUser } from "@rapidmx/react-shared/mail/mailApi.js";
|
|
9
10
|
import AdminShell from "../../shared/components/admin/layout/AdminShell.js";
|
|
@@ -27,6 +28,7 @@ export default function MailboxDetailPage(props) {
|
|
|
27
28
|
}
|
|
28
29
|
function MailboxDetailContent({ uid, impersonationBaseUrl, currentUserUid, }) {
|
|
29
30
|
const [mailbox, setMailbox] = useState(null);
|
|
31
|
+
const { navigate } = useRouter();
|
|
30
32
|
const [loading, setLoading] = useState(true);
|
|
31
33
|
const [error, setError] = useState(null);
|
|
32
34
|
const [confirmingAccess, setConfirmingAccess] = useState(false);
|
|
@@ -76,7 +78,7 @@ function MailboxDetailContent({ uid, impersonationBaseUrl, currentUserUid, }) {
|
|
|
76
78
|
try {
|
|
77
79
|
await deleteMailbox(mailbox.uid, mailbox.version, { erase: eraseData });
|
|
78
80
|
// The Mailboxes page it goes to lists what a delete without `erase` kept, and how far an erasure has got.
|
|
79
|
-
|
|
81
|
+
void navigate("/admin");
|
|
80
82
|
}
|
|
81
83
|
catch (err) {
|
|
82
84
|
// Most commonly a 409 if this mailbox is a custodian on an open legal hold (restapi's own
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useRouter } from "@rapidrest/react/client";
|
|
2
3
|
import AdminShell from "../../../shared/components/admin/layout/AdminShell.js";
|
|
3
4
|
import MailboxCreateForm from "../../../shared/components/admin/settings/MailboxCreateForm.js";
|
|
4
5
|
export default function NewMailboxPage(props) {
|
|
6
|
+
const { navigate } = useRouter();
|
|
5
7
|
return (_jsx(AdminShell, { ...props, active: "mailboxes", children: _jsxs("div", { className: "max-w-xl", children: [_jsx("h1", { className: "text-xl font-bold uppercase tracking-wide mb-1", children: "New mailbox" }), _jsx("p", { className: "text-sm text-text-muted mb-5", children: "Choose \"Shared mailbox\" to create a true ownerless mailbox (the Exchange \"shared mailbox\" concept) \u2014 access is then granted entirely to delegates afterward, not to a single owner. Choose \"Owned by a specific person\" and look them up to create a mailbox for a specific user instead." }), _jsx(MailboxCreateForm, { cancelHref: "/admin", onCreated: (mailbox) => {
|
|
6
|
-
|
|
8
|
+
void navigate(`/admin/mailboxes/${encodeURIComponent(mailbox.uid)}`);
|
|
7
9
|
} })] }) }));
|
|
8
10
|
}
|
|
@@ -4,6 +4,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
4
4
|
// SPDX-License-Identifier: MPL-2.0
|
|
5
5
|
///////////////////////////////////////////////////////////////////////////////
|
|
6
6
|
import { useState } from "react";
|
|
7
|
+
import { useRouter } from "@rapidrest/react/client";
|
|
7
8
|
import { ApiRequestError } from "@rapidmx/react-shared/util/api.js";
|
|
8
9
|
import { createTransportRule, } from "@rapidmx/react-shared/admin/transportRulesApi.js";
|
|
9
10
|
import AdminShell from "../../../shared/components/admin/layout/AdminShell.js";
|
|
@@ -17,6 +18,7 @@ export default function NewTransportRulePage(props) {
|
|
|
17
18
|
return (_jsx(AdminShell, { ...props, active: "transportRules", children: _jsx(NewTransportRuleForm, {}) }));
|
|
18
19
|
}
|
|
19
20
|
function NewTransportRuleForm() {
|
|
21
|
+
const { navigate } = useRouter();
|
|
20
22
|
const [name, setName] = useState("");
|
|
21
23
|
const [rule, setRule] = useState({
|
|
22
24
|
enabled: true,
|
|
@@ -53,7 +55,7 @@ function NewTransportRuleForm() {
|
|
|
53
55
|
setSaving(true);
|
|
54
56
|
try {
|
|
55
57
|
const created = await createTransportRule({ name: name.trim(), ...rule });
|
|
56
|
-
|
|
58
|
+
void navigate(`/admin/transport-rules/${encodeURIComponent(created.uid)}`);
|
|
57
59
|
}
|
|
58
60
|
catch (err) {
|
|
59
61
|
setError(err instanceof ApiRequestError ? err.message : "Could not create the transport rule.");
|
|
@@ -4,6 +4,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
4
4
|
// SPDX-License-Identifier: MPL-2.0
|
|
5
5
|
///////////////////////////////////////////////////////////////////////////////
|
|
6
6
|
import { useState } from "react";
|
|
7
|
+
import { useRouter } from "@rapidrest/react/client";
|
|
7
8
|
import { ApiRequestError } from "@rapidmx/react-shared/util/api.js";
|
|
8
9
|
import { toDatetimeLocal } from "@rapidmx/react-shared/util/dateInput.js";
|
|
9
10
|
import { createMatter } from "@rapidmx/react-shared/admin/mattersApi.js";
|
|
@@ -17,6 +18,7 @@ export default function NewMatterPage(props) {
|
|
|
17
18
|
return (_jsx(EscrowShell, { ...props, active: "matters", children: _jsx(NewMatterForm, {}) }));
|
|
18
19
|
}
|
|
19
20
|
function NewMatterForm() {
|
|
21
|
+
const { navigate } = useRouter();
|
|
20
22
|
const [name, setName] = useState("");
|
|
21
23
|
const [description, setDescription] = useState("");
|
|
22
24
|
// No dropdown here: `EscrowScope` (BaseEscrowScopeRoute) is trusted-admin-only end to end, so this
|
|
@@ -62,7 +64,7 @@ function NewMatterForm() {
|
|
|
62
64
|
dateRangeStart: new Date(dateRangeStart).toISOString(),
|
|
63
65
|
dateRangeEnd: new Date(dateRangeEnd).toISOString(),
|
|
64
66
|
});
|
|
65
|
-
|
|
67
|
+
void navigate(`/escrow/matters/${encodeURIComponent(created.uid)}`);
|
|
66
68
|
}
|
|
67
69
|
catch (err) {
|
|
68
70
|
setError(err instanceof ApiRequestError ? err.message : "Could not create the matter.");
|
|
@@ -5,6 +5,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
5
5
|
///////////////////////////////////////////////////////////////////////////////
|
|
6
6
|
import "../../../styles/app.css";
|
|
7
7
|
import { useEffect, useState } from "react";
|
|
8
|
+
import { useRouter } from "@rapidrest/react/client";
|
|
8
9
|
import { HiOutlineClipboardDocumentList, HiOutlineClock, HiOutlineDocumentArrowDown, HiOutlineDocumentCheck, HiOutlineGlobeAlt, HiOutlineInboxStack, HiOutlineKey, HiOutlineLockClosed, HiOutlinePaintBrush, HiOutlinePuzzlePiece, HiOutlineQueueList, HiOutlineRocketLaunch, HiOutlineShieldCheck, HiOutlineShieldExclamation, HiOutlineUserGroup, HiOutlineWrenchScrewdriver, HiOutlineBars3, } from "react-icons/hi2";
|
|
9
10
|
import { apiFetch, ApiRequestError } from "@rapidmx/react-shared/util/api.js";
|
|
10
11
|
import { getSetupStatus } from "@rapidmx/react-shared/admin/setupApi.js";
|
|
@@ -108,6 +109,7 @@ export function adminNavItems(pluginNav) {
|
|
|
108
109
|
* 403 `api-103` (elevated, but not an administrator), any other 403, and 401 mean "no administrator access".
|
|
109
110
|
*/
|
|
110
111
|
export default function AdminShell({ active, userUid, authServerUrl, pluginNav, children }) {
|
|
112
|
+
const { navigate } = useRouter();
|
|
111
113
|
const [status, setStatus] = useState("checking");
|
|
112
114
|
const [error, setError] = useState(null);
|
|
113
115
|
const [menuOpen, setMenuOpen] = useState(false);
|
|
@@ -129,7 +131,8 @@ export default function AdminShell({ active, userUid, authServerUrl, pluginNav,
|
|
|
129
131
|
if (active !== "setup") {
|
|
130
132
|
try {
|
|
131
133
|
if ((await getSetupStatus()).required) {
|
|
132
|
-
|
|
134
|
+
// Replacing this page in the history, which would otherwise send the back button round again.
|
|
135
|
+
void navigate(SETUP_ITEM.href, { replace: true });
|
|
133
136
|
return;
|
|
134
137
|
}
|
|
135
138
|
}
|
|
@@ -4,6 +4,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
4
4
|
// SPDX-License-Identifier: MPL-2.0
|
|
5
5
|
///////////////////////////////////////////////////////////////////////////////
|
|
6
6
|
import { useEffect, useRef, useState } from "react";
|
|
7
|
+
import { useRouter } from "@rapidrest/react/client";
|
|
7
8
|
import { HiCheck } from "react-icons/hi2";
|
|
8
9
|
import { ApiRequestError } from "@rapidmx/react-shared/util/api.js";
|
|
9
10
|
import { getRetentionPolicy } from "@rapidmx/react-shared/admin/retentionPolicyApi.js";
|
|
@@ -49,6 +50,7 @@ function errorMessage(err, fallback) {
|
|
|
49
50
|
* except the domain can be passed over and configured later from the admin console.
|
|
50
51
|
*/
|
|
51
52
|
export default function SetupWizard({ userUid }) {
|
|
53
|
+
const { navigate } = useRouter();
|
|
52
54
|
const [step, setStep] = useState(null);
|
|
53
55
|
const [status, setStatus] = useState();
|
|
54
56
|
const [domains, setDomains] = useState([]);
|
|
@@ -141,7 +143,7 @@ export default function SetupWizard({ userUid }) {
|
|
|
141
143
|
// Let a step still being recorded land first, so it can't arrive after setup is marked finished.
|
|
142
144
|
await progress.current.running;
|
|
143
145
|
await completeSetup();
|
|
144
|
-
|
|
146
|
+
void navigate("/admin");
|
|
145
147
|
}
|
|
146
148
|
catch (err) {
|
|
147
149
|
setError(errorMessage(err, "Could not finish setup."));
|
|
@@ -10,7 +10,7 @@ import { listFolders, listMailboxes } from "@rapidmx/react-shared/mail/mailApi.j
|
|
|
10
10
|
import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
|
|
11
11
|
import Skeleton, { SkeletonList } from "@rapidmx/react-shared/components/feedback/Skeleton.js";
|
|
12
12
|
import AppShell from "../../layout/AppShell.js";
|
|
13
|
-
import {
|
|
13
|
+
import { useLocation } from "@rapidrest/react/client";
|
|
14
14
|
import MailboxProvisioning from "../../layout/MailboxProvisioning.js";
|
|
15
15
|
import { orderMailboxes, primaryMailboxUid } from "../../../mail/primaryMailbox.js";
|
|
16
16
|
const CalendarShellContext = createContext({
|
|
@@ -40,7 +40,7 @@ export default function CalendarShell({ userUid, authServerUrl, impersonating, i
|
|
|
40
40
|
const [folderRefreshToken, setFolderRefreshToken] = useState(0);
|
|
41
41
|
// Read from the router's location, so a mailbox change (a link, or `navigate()`) takes effect without a page load - and in an
|
|
42
42
|
// effect, not during render, so the server render and the hydrating render agree.
|
|
43
|
-
const search =
|
|
43
|
+
const { search } = useLocation();
|
|
44
44
|
useEffect(() => {
|
|
45
45
|
setRequestedMailboxUid(new URLSearchParams(search).get("mailboxUid"));
|
|
46
46
|
}, [search]);
|
|
@@ -11,7 +11,8 @@ import { listFolders, listMailboxes } from "@rapidmx/react-shared/mail/mailApi.j
|
|
|
11
11
|
import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
|
|
12
12
|
import Skeleton, { SkeletonList } from "@rapidmx/react-shared/components/feedback/Skeleton.js";
|
|
13
13
|
import AppShell from "../../layout/AppShell.js";
|
|
14
|
-
import {
|
|
14
|
+
import { useLocation } from "@rapidrest/react/client";
|
|
15
|
+
import { useNavigate } from "../../../navigation/index.js";
|
|
15
16
|
import MailboxProvisioning from "../../layout/MailboxProvisioning.js";
|
|
16
17
|
import { orderMailboxes, primaryMailboxUid } from "../../../mail/primaryMailbox.js";
|
|
17
18
|
const ContactsShellContext = createContext({ mailboxes: [] });
|
|
@@ -36,7 +37,7 @@ export default function ContactsShell({ userUid, authServerUrl, impersonating, i
|
|
|
36
37
|
const [drawerOpen, setDrawerOpen] = useState(false);
|
|
37
38
|
// Read from the router's location, so a mailbox change (a link, or `navigate()`) takes effect without a page load - and in an
|
|
38
39
|
// effect, not during render, so the server render and the hydrating render agree.
|
|
39
|
-
const search =
|
|
40
|
+
const { search } = useLocation();
|
|
40
41
|
const navigate = useNavigate();
|
|
41
42
|
useEffect(() => {
|
|
42
43
|
setRequestedMailboxUid(new URLSearchParams(search).get("mailboxUid"));
|
|
@@ -49,12 +49,9 @@ export interface AppDef {
|
|
|
49
49
|
export declare const APPS: AppDef[];
|
|
50
50
|
/** `APPS` followed by the plugins' `appRail` items (generic icon), core ids winning - see `mergePluginNavItems`. */
|
|
51
51
|
export declare function appRailItems(pluginNav?: PluginNav): NavItem[];
|
|
52
|
-
/** What only the persistent frame (`
|
|
52
|
+
/** What only the persistent frame (`apps/www/_shell.tsx`) passes to the chrome it keeps mounted. */
|
|
53
53
|
export interface AppChromeProps extends AppShellProps {
|
|
54
|
-
/**
|
|
55
|
-
* sets the document title and announces the page. Absent outside the router, when nothing does. */
|
|
56
|
-
routeKey?: string;
|
|
57
|
-
/** The router is loading the next page's code - the content is `aria-busy`. */
|
|
54
|
+
/** The router is loading the next page - the content is `aria-busy`. */
|
|
58
55
|
busy?: boolean;
|
|
59
56
|
/** A screen that takes over the window (`FrameTakeover`) is showing: the rail, header, banner and footer are hidden - not
|
|
60
57
|
* unmounted, so what is inside them (and the page in the content region) keeps its state. */
|
|
@@ -66,14 +63,14 @@ export interface AppChromeProps extends AppShellProps {
|
|
|
66
63
|
* Each app's own shell (e.g. `MailShell`) renders its own contextual sidebar + content as `children`, inside
|
|
67
64
|
* the area to the right of the icon rail and below the header.
|
|
68
65
|
*
|
|
69
|
-
* Rendered by
|
|
70
|
-
* `AppShell` below for what a page's own shell renders instead.
|
|
66
|
+
* Rendered by the webmail's app shell (`apps/www/_shell.tsx`, the router's persistent client layout), once, for the life of the
|
|
67
|
+
* page, so that moving between apps replaces only `children` - see `AppShell` below for what a page's own shell renders instead.
|
|
71
68
|
*/
|
|
72
|
-
export declare function AppChrome({ active, userUid, authServerUrl, impersonating, impersonationBaseUrl, trusted, trustedRoles, branding: initialBranding, appearance, pluginNav,
|
|
69
|
+
export declare function AppChrome({ active, userUid, authServerUrl, impersonating, impersonationBaseUrl, trusted, trustedRoles, branding: initialBranding, appearance, pluginNav, busy, hideChrome, children, }: PropsWithChildren<AppChromeProps>): React.JSX.Element;
|
|
73
70
|
/**
|
|
74
71
|
* What a page's own shell (`MailShell`, `CalendarShell`, ...) renders around its content. Outside the client-side router it
|
|
75
|
-
* is the whole `AppChrome`, as it always was. Inside it (`
|
|
76
|
-
* mounted as the page is replaced - so this is only its children; everything it would have been given comes from
|
|
77
|
-
*
|
|
72
|
+
* is the whole `AppChrome`, as it always was. Inside it (the router's app shell, `apps/www/_shell.tsx`) the chrome is already mounted above the
|
|
73
|
+
* page - and stays mounted as the page is replaced - so this is only its children; everything it would have been given comes from
|
|
74
|
+
* the shell instead (the props are the same for every page, and `active` is the route's).
|
|
78
75
|
*/
|
|
79
76
|
export default function AppShell(props: PropsWithChildren<AppShellProps>): React.JSX.Element;
|
|
@@ -4,8 +4,9 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
4
4
|
// SPDX-License-Identifier: MPL-2.0
|
|
5
5
|
///////////////////////////////////////////////////////////////////////////////
|
|
6
6
|
import "../../styles/app.css";
|
|
7
|
-
import { useEffect, useRef, useState } from "react";
|
|
7
|
+
import { useEffect, useLayoutEffect, useRef, useState } from "react";
|
|
8
8
|
import { HiOutlineCalendarDays, HiOutlineClipboardDocumentList, HiOutlineEnvelope, HiOutlinePuzzlePiece, HiOutlineUsers, } from "react-icons/hi2";
|
|
9
|
+
import { useRouter } from "@rapidrest/react/client";
|
|
9
10
|
import { useRedirectIfUnauthenticated } from "@rapidmx/react-shared/auth/session.js";
|
|
10
11
|
import { getSetupStatus } from "@rapidmx/react-shared/admin/setupApi.js";
|
|
11
12
|
import { stopImpersonating } from "@rapidmx/react-shared/mail/mailApi.js";
|
|
@@ -27,7 +28,7 @@ import { clearPinnedSignerCache } from "../mail/pinnedSigners.js";
|
|
|
27
28
|
import { mergePluginNavItems } from "../../plugins/pluginNav.js";
|
|
28
29
|
import { useInAppFrame } from "../../navigation/frameContext.js";
|
|
29
30
|
import { APP_HREFS } from "../../navigation/appHrefs.js";
|
|
30
|
-
import { useNavigate } from "../../navigation/
|
|
31
|
+
import { useNavigate } from "../../navigation/index.js";
|
|
31
32
|
import { GlobalShortcuts } from "../../keyboard/GlobalShortcuts.js";
|
|
32
33
|
import { ShortcutProvider } from "../../keyboard/ShortcutProvider.js";
|
|
33
34
|
import ShortcutsDialog from "../../keyboard/ShortcutsDialog.js";
|
|
@@ -84,10 +85,10 @@ export function appRailItems(pluginNav) {
|
|
|
84
85
|
* Each app's own shell (e.g. `MailShell`) renders its own contextual sidebar + content as `children`, inside
|
|
85
86
|
* the area to the right of the icon rail and below the header.
|
|
86
87
|
*
|
|
87
|
-
* Rendered by
|
|
88
|
-
* `AppShell` below for what a page's own shell renders instead.
|
|
88
|
+
* Rendered by the webmail's app shell (`apps/www/_shell.tsx`, the router's persistent client layout), once, for the life of the
|
|
89
|
+
* page, so that moving between apps replaces only `children` - see `AppShell` below for what a page's own shell renders instead.
|
|
89
90
|
*/
|
|
90
|
-
export function AppChrome({ active, userUid, authServerUrl, impersonating, impersonationBaseUrl, trusted, trustedRoles, branding: initialBranding, appearance, pluginNav,
|
|
91
|
+
export function AppChrome({ active, userUid, authServerUrl, impersonating, impersonationBaseUrl, trusted, trustedRoles, branding: initialBranding, appearance, pluginNav, busy, hideChrome, children, }) {
|
|
91
92
|
const [stoppingImpersonation, setStoppingImpersonation] = useState(false);
|
|
92
93
|
// The keyboard shortcuts dialog: opened by `?`/Ctrl+/ (`GlobalShortcuts`) and by the account menu's item.
|
|
93
94
|
const [shortcutsOpen, setShortcutsOpen] = useState(false);
|
|
@@ -111,6 +112,7 @@ export function AppChrome({ active, userUid, authServerUrl, impersonating, imper
|
|
|
111
112
|
// the router (`AppShell` rendered as a page's own chrome: tests, plugin pages) it stays off, and a Mail shell owns its connection itself.
|
|
112
113
|
const inFrame = useInAppFrame();
|
|
113
114
|
const navigate = useNavigate();
|
|
115
|
+
const { pathname } = useRouter();
|
|
114
116
|
const mail = useMailConnection({ userUid, enabled: inFrame, open: navigate });
|
|
115
117
|
// A signing certificate the user asked for is announced when it is issued (or fails), on whichever page they are - see the hook.
|
|
116
118
|
useSigningEnrollmentWatcher({ userUid, mailboxes: mail.mailboxes, enabled: inFrame });
|
|
@@ -210,32 +212,21 @@ export function AppChrome({ active, userUid, authServerUrl, impersonating, imper
|
|
|
210
212
|
window.location.href = "/admin";
|
|
211
213
|
}
|
|
212
214
|
}
|
|
213
|
-
//
|
|
214
|
-
//
|
|
215
|
-
//
|
|
216
|
-
const
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
215
|
+
// The tab's title in the frame is each page's own (its `title` export: rendered by the server, set again by the router on every navigation),
|
|
216
|
+
// but `useBranding()` above sets it to the branding's title once its fetch answers - a title for a page that has none. The page's is put back:
|
|
217
|
+
// the layout effect reads it before that hook's effect runs, this one (declared after it) writes it back.
|
|
218
|
+
const pageTitleRef = useRef("");
|
|
219
|
+
useLayoutEffect(() => {
|
|
220
|
+
pageTitleRef.current = document.title;
|
|
221
|
+
}, [fetchedBranding?.title]);
|
|
220
222
|
useEffect(() => {
|
|
221
|
-
if (
|
|
222
|
-
|
|
223
|
-
document.title = pageLabel ? `${brand}: ${pageLabel}` : brand;
|
|
223
|
+
if (inFrame) {
|
|
224
|
+
document.title = pageTitleRef.current;
|
|
224
225
|
}
|
|
225
|
-
}, [
|
|
226
|
-
//
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
resetKey: `${routeKey}|${pageLabel}|${branding?.title}|${branding?.companyName}`,
|
|
230
|
-
});
|
|
231
|
-
useEffect(() => {
|
|
232
|
-
if (routeKey === undefined || routeKey === firstRouteKeyRef.current) {
|
|
233
|
-
return;
|
|
234
|
-
}
|
|
235
|
-
setAnnouncement(pageLabel ?? "");
|
|
236
|
-
contentRef.current?.focus({ preventScroll: true });
|
|
237
|
-
window.scrollTo(0, 0);
|
|
238
|
-
}, [routeKey]);
|
|
226
|
+
}, [fetchedBranding?.title, inFrame]);
|
|
227
|
+
// The router sets the document's title to the page's own whenever the page changes, which drops the unread count this puts in front
|
|
228
|
+
// of it, so a new pathname is what puts the count back. (A folder change is shallow and keeps the title.)
|
|
229
|
+
useUnreadTitle(inboxUnreadTotal(mail.mailboxFolders, mail.folderCounts.counts), { enabled: inFrame, resetKey: pathname });
|
|
239
230
|
if (!userUid) {
|
|
240
231
|
return _jsx("div", { className: "min-h-screen" });
|
|
241
232
|
}
|
|
@@ -260,13 +251,13 @@ export function AppChrome({ active, userUid, authServerUrl, impersonating, imper
|
|
|
260
251
|
id === active
|
|
261
252
|
? "bg-primary/10 text-primary-dark"
|
|
262
253
|
: "text-text-muted hover:bg-surface-alt hover:text-text",
|
|
263
|
-
].join(" "), children: _jsx(Icon, { size: 20, "aria-hidden": "true" }) }, id)))] })), !hideChrome && _jsx(BottomTabBar, { apps: apps, active: active }), _jsxs("div", { className: "flex-1 flex flex-col min-w-0", children: [!hideChrome && !customHeader && (_jsxs("header", { ref: headerRef, className: "rr-solid sticky top-0 z-30 h-16 shrink-0 bg-surface border-b border-border flex items-center justify-between gap-4 px-6", children: [_jsx("span", { className: "font-display font-bold text-lg uppercase tracking-wide", children: activeLabel }), renderUserMenu("down")] })), _jsx(NotificationCenter, {}), _jsx("div", {
|
|
254
|
+
].join(" "), children: _jsx(Icon, { size: 20, "aria-hidden": "true" }) }, id)))] })), !hideChrome && _jsx(BottomTabBar, { apps: apps, active: active }), _jsxs("div", { className: "flex-1 flex flex-col min-w-0", children: [!hideChrome && !customHeader && (_jsxs("header", { ref: headerRef, className: "rr-solid sticky top-0 z-30 h-16 shrink-0 bg-surface border-b border-border flex items-center justify-between gap-4 px-6", children: [_jsx("span", { className: "font-display font-bold text-lg uppercase tracking-wide", children: activeLabel }), renderUserMenu("down")] })), _jsx(NotificationCenter, {}), _jsx("div", { id: "app-content", tabIndex: -1, "aria-busy": busy || undefined, className: ["flex-1 flex min-h-0 outline-none", hideChrome ? "" : "pb-14 md:pb-0"].join(" "), children: children })] })] }), !hideChrome && _jsx(FrameBrandingFooter, { parsed: footer, userMenu: menuInFooter ? renderUserMenu("up") : undefined, appTitle: activeLabel })] }) })] }), _jsx(NotificationHistoryDialog, { open: historyOpen, onClose: () => setHistoryOpen(false) })] }) }) }));
|
|
264
255
|
}
|
|
265
256
|
/**
|
|
266
257
|
* What a page's own shell (`MailShell`, `CalendarShell`, ...) renders around its content. Outside the client-side router it
|
|
267
|
-
* is the whole `AppChrome`, as it always was. Inside it (`
|
|
268
|
-
* mounted as the page is replaced - so this is only its children; everything it would have been given comes from
|
|
269
|
-
*
|
|
258
|
+
* is the whole `AppChrome`, as it always was. Inside it (the router's app shell, `apps/www/_shell.tsx`) the chrome is already mounted above the
|
|
259
|
+
* page - and stays mounted as the page is replaced - so this is only its children; everything it would have been given comes from
|
|
260
|
+
* the shell instead (the props are the same for every page, and `active` is the route's).
|
|
270
261
|
*/
|
|
271
262
|
export default function AppShell(props) {
|
|
272
263
|
const inFrame = useInAppFrame();
|
|
@@ -448,7 +448,7 @@ export default function ConversationThreadPane({ conversation, mailboxUid, selec
|
|
|
448
448
|
buttonRef: (node) => {
|
|
449
449
|
headerRefs.current[uid] = node;
|
|
450
450
|
},
|
|
451
|
-
}, footer: uid === footerUid, shortcuts: !!shortcuts && uid === keyboardUid, message: message, attachments: attachmentsByUid[uid] ?? [], isSentItems: folderTypeOf(message) === "sent_items", isOutbox: folderTypeOf(message) === "outbox", draftsFolderUid: folders.find((folder) => folder.type === "drafts")?.uid, folders: folders, onMoved: removeMessage, onFolderCreated: onFolderCreated, onRecalled: patchMessage, onReceiptHandled: patchMessage, onScheduledSendCanceled: removeMessage, onArchived: removeMessage, labels: labels, onLabelsChanged: patchMessage, onLabelCreated: onLabelCreated })) : (_jsxs(_Fragment, { children: [_jsx(CollapsedCard, { from: message.from, date: new Date(message.receivedDate).toLocaleString(), preview: message.bodyPreview || (message.encrypted ? _jsx(EncryptedPreview, {}) : ""), unread: messageUnread, senderClassName: senderClass(messageUnread), dateClassName: dateClass(messageUnread), buttonRef: (node) => {
|
|
451
|
+
}, footer: uid === footerUid, shortcuts: !!shortcuts && uid === keyboardUid, message: message, attachments: attachmentsByUid[uid] ?? [], isSentItems: folderTypeOf(message) === "sent_items", isOutbox: folderTypeOf(message) === "outbox", draftsFolderUid: folders.find((folder) => folder.type === "drafts")?.uid, folders: folders, onMoved: removeMessage, onFolderCreated: onFolderCreated, onRecalled: patchMessage, onReceiptHandled: patchMessage, onScheduledSendCanceled: removeMessage, onArchived: removeMessage, onChanged: patchMessage, labels: labels, onLabelsChanged: patchMessage, onLabelCreated: onLabelCreated })) : (_jsxs(_Fragment, { children: [_jsx(CollapsedCard, { from: message.from, date: new Date(message.receivedDate).toLocaleString(), preview: message.bodyPreview || (message.encrypted ? _jsx(EncryptedPreview, {}) : ""), unread: messageUnread, senderClassName: senderClass(messageUnread), dateClassName: dateClass(messageUnread), buttonRef: (node) => {
|
|
452
452
|
headerRefs.current[uid] = node;
|
|
453
453
|
}, buttonProps: {
|
|
454
454
|
onClick: () => toggleExpanded(uid),
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
export interface EmptyFolderBarProps {
|
|
3
|
+
/** The folder the list is showing ("Deleted Items", "Junk Email"). */
|
|
4
|
+
folderName: string;
|
|
5
|
+
/** How many messages it holds, when that is known. */
|
|
6
|
+
count?: number;
|
|
7
|
+
/** Nothing to empty, or not allowed to. */
|
|
8
|
+
disabled: boolean;
|
|
9
|
+
/** Why, shown as the button's tooltip while it is disabled. */
|
|
10
|
+
disabledReason?: string;
|
|
11
|
+
onEmpty: () => void;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Outlook's "Empty folder", at the top of the list of Deleted Items and of Junk Email: the whole folder is permanently deleted, after the
|
|
15
|
+
* page has asked (this only reports the click). A row of its own under the toolbar rather than a toolbar button, so the toolbar keeps the
|
|
16
|
+
* width a phone gives it and the action is never taken for one of the list's arrangement controls.
|
|
17
|
+
*/
|
|
18
|
+
export default function EmptyFolderBar({ folderName, count, disabled, disabledReason, onEmpty }: EmptyFolderBarProps): React.JSX.Element;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { HiOutlineTrash } from "react-icons/hi2";
|
|
3
|
+
/**
|
|
4
|
+
* Outlook's "Empty folder", at the top of the list of Deleted Items and of Junk Email: the whole folder is permanently deleted, after the
|
|
5
|
+
* page has asked (this only reports the click). A row of its own under the toolbar rather than a toolbar button, so the toolbar keeps the
|
|
6
|
+
* width a phone gives it and the action is never taken for one of the list's arrangement controls.
|
|
7
|
+
*/
|
|
8
|
+
export default function EmptyFolderBar({ folderName, count, disabled, disabledReason, onEmpty }) {
|
|
9
|
+
return (_jsxs("div", { className: "flex items-center justify-between gap-2 px-3 py-1 border-b border-border text-xs text-text-muted", children: [_jsx("span", { children: count === undefined ? "" : `${count} ${count === 1 ? "item" : "items"}` }), _jsxs("button", { type: "button", onClick: onEmpty, disabled: disabled, title: disabled ? disabledReason : `Permanently delete everything in ${folderName}`, className: "inline-flex items-center gap-1.5 px-2 py-1 rounded-md text-sm text-danger hover:bg-surface-alt disabled:opacity-50 disabled:hover:bg-transparent", children: [_jsx(HiOutlineTrash, { size: 16, "aria-hidden": "true", className: "shrink-0" }), `Empty ${folderName}`] })] }));
|
|
10
|
+
}
|
|
@@ -40,7 +40,12 @@ export interface MailSelectionBarProps {
|
|
|
40
40
|
* prompt, which would be claiming the move simply didn't happen. */
|
|
41
41
|
onMoveTo: (folderUid: string) => Promise<void>;
|
|
42
42
|
onReportJunk: () => void;
|
|
43
|
+
/** Delete: moves the selection to Deleted Items - or, when `deletesPermanently`, permanently deletes it (the page asks first). */
|
|
43
44
|
onDelete: () => void;
|
|
45
|
+
/** The selection is in Deleted Items, so Delete is the permanent one: the button says "Delete permanently" and is styled as the destructive
|
|
46
|
+
* action it is. The page decides (a search over several mailboxes lists messages from more than one folder, so the folder being viewed is not
|
|
47
|
+
* always the answer). */
|
|
48
|
+
deletesPermanently?: boolean;
|
|
44
49
|
/** A bulk action is in flight - every action is held until it settles, since the next one would send
|
|
45
50
|
* `version`s the first has already superseded. */
|
|
46
51
|
busy: boolean;
|
|
@@ -58,9 +63,9 @@ export interface MailSelectionBarProps {
|
|
|
58
63
|
* select-all/clear, and the bulk actions Outlook offers for a multi-selection - mark read/unread, flag,
|
|
59
64
|
* archive, move, report junk and delete.
|
|
60
65
|
*
|
|
61
|
-
* "Delete" means *move to Deleted Items
|
|
62
|
-
*
|
|
63
|
-
* target folder is the one already being viewed is disabled with a reason rather than hidden, so the set
|
|
66
|
+
* "Delete" means *move to Deleted Items* - except on messages that are already in Deleted Items (`deletesPermanently`),
|
|
67
|
+
* where it is "Delete permanently": the page confirms, then erases them for good, as Outlook's Delete does there. An
|
|
68
|
+
* action whose target folder is the one already being viewed is disabled with a reason rather than hidden, so the set
|
|
64
69
|
* of actions doesn't shift around between folders.
|
|
65
70
|
*/
|
|
66
|
-
export default function MailSelectionBar({ selected, listed, totals, onSelectAll, onClearSelection, onCancel, folders, currentFolderUid, labels, mailboxUid, onLabelCreated, onFolderCreated, onApplyLabels, onSetRead, onSetFlagged, onArchive, onMoveTo, onReportJunk, onDelete, busy, shortcuts, moveDisabledReason, labelsDisabledReason, }: MailSelectionBarProps): React.JSX.Element;
|
|
71
|
+
export default function MailSelectionBar({ selected, listed, totals, onSelectAll, onClearSelection, onCancel, folders, currentFolderUid, labels, mailboxUid, onLabelCreated, onFolderCreated, onApplyLabels, onSetRead, onSetFlagged, onArchive, onMoveTo, onReportJunk, onDelete, deletesPermanently, busy, shortcuts, moveDisabledReason, labelsDisabledReason, }: MailSelectionBarProps): React.JSX.Element;
|
|
@@ -9,25 +9,23 @@ import MoveToFolderDialog, { MOVE_TARGET_TYPES } from "./MoveToFolderDialog.js";
|
|
|
9
9
|
import { ariaKeyShortcuts, withHint } from "../../keyboard/format.js";
|
|
10
10
|
import { SHORTCUTS } from "../../keyboard/keymap.js";
|
|
11
11
|
import { useKeyEnvironment } from "../../keyboard/ShortcutProvider.js";
|
|
12
|
-
function actionClassName() {
|
|
13
|
-
return
|
|
12
|
+
function actionClassName(destructive = false) {
|
|
13
|
+
return `px-2 py-1 rounded-md text-sm ${destructive ? "text-danger font-semibold" : "text-text"} hover:bg-surface-alt disabled:opacity-50 disabled:hover:bg-transparent`;
|
|
14
14
|
}
|
|
15
15
|
/**
|
|
16
16
|
* The header that replaces the list's own toolbar while select mode is on: how many rows are selected,
|
|
17
17
|
* select-all/clear, and the bulk actions Outlook offers for a multi-selection - mark read/unread, flag,
|
|
18
18
|
* archive, move, report junk and delete.
|
|
19
19
|
*
|
|
20
|
-
* "Delete" means *move to Deleted Items
|
|
21
|
-
*
|
|
22
|
-
* target folder is the one already being viewed is disabled with a reason rather than hidden, so the set
|
|
20
|
+
* "Delete" means *move to Deleted Items* - except on messages that are already in Deleted Items (`deletesPermanently`),
|
|
21
|
+
* where it is "Delete permanently": the page confirms, then erases them for good, as Outlook's Delete does there. An
|
|
22
|
+
* action whose target folder is the one already being viewed is disabled with a reason rather than hidden, so the set
|
|
23
23
|
* of actions doesn't shift around between folders.
|
|
24
24
|
*/
|
|
25
|
-
export default function MailSelectionBar({ selected, listed, totals, onSelectAll, onClearSelection, onCancel, folders, currentFolderUid, labels, mailboxUid, onLabelCreated, onFolderCreated, onApplyLabels, onSetRead, onSetFlagged, onArchive, onMoveTo, onReportJunk, onDelete, busy, shortcuts, moveDisabledReason, labelsDisabledReason, }) {
|
|
25
|
+
export default function MailSelectionBar({ selected, listed, totals, onSelectAll, onClearSelection, onCancel, folders, currentFolderUid, labels, mailboxUid, onLabelCreated, onFolderCreated, onApplyLabels, onSetRead, onSetFlagged, onArchive, onMoveTo, onReportJunk, onDelete, deletesPermanently = false, busy, shortcuts, moveDisabledReason, labelsDisabledReason, }) {
|
|
26
26
|
const env = useKeyEnvironment();
|
|
27
|
-
/** `title` and `aria-keyshortcuts` for an action the keyboard also does
|
|
28
|
-
const hint = (label, shortcut,
|
|
29
|
-
? { title: reason ?? withHint(label, shortcut, env), "aria-keyshortcuts": ariaKeyShortcuts(shortcut, env) }
|
|
30
|
-
: { title: reason };
|
|
27
|
+
/** `title` and `aria-keyshortcuts` for an action the keyboard also does. */
|
|
28
|
+
const hint = (label, shortcut) => shortcuts ? { title: withHint(label, shortcut, env), "aria-keyshortcuts": ariaKeyShortcuts(shortcut, env) } : {};
|
|
31
29
|
const selectedCount = totals?.selected ?? selected.length;
|
|
32
30
|
const listedCount = totals?.listed ?? listed.length;
|
|
33
31
|
// Counted on the rows that were ticked, but *emptied* on the messages: a ticked conversation whose
|
|
@@ -44,12 +42,12 @@ export default function MailSelectionBar({ selected, listed, totals, onSelectAll
|
|
|
44
42
|
// them is already being in the folder they would move to.
|
|
45
43
|
const archiveReason = currentType === "archive" ? "These messages are already in Archive" : undefined;
|
|
46
44
|
const junkReason = currentType === "junk" ? "These messages are already in Junk" : undefined;
|
|
47
|
-
const
|
|
45
|
+
const deleteLabel = deletesPermanently ? "Delete permanently" : "Delete";
|
|
48
46
|
// A label every selected message already carries starts ticked; one only some of them carry starts
|
|
49
47
|
// partially applied, and is left exactly as it is unless the reader touches that row.
|
|
50
48
|
const appliedToAll = labels.filter((label) => !none && selected.every((m) => m.labelUids?.includes(label.uid)));
|
|
51
49
|
const appliedToSome = labels.filter((label) => selected.some((m) => m.labelUids?.includes(label.uid)) && !appliedToAll.includes(label));
|
|
52
50
|
return (_jsxs("div", { className: "border-b border-border", children: [_jsx(MoveToFolderDialog, { open: movePrompt, onClose: () => setMovePrompt(false), mailboxUid: mailboxUid, folders: folders, currentFolderUid: currentFolderUid, count: selected.length, onMove: onMoveTo, onFolderCreated: onFolderCreated }), _jsxs("div", { className: "flex items-center gap-2 px-3 py-1.5 bg-surface-alt", children: [_jsxs("span", { "aria-live": "polite", className: "text-sm font-semibold text-text", children: [selectedCount, totals ? ` ${totals.noun}${selectedCount === 1 ? "" : "s"}` : "", " selected"] }), _jsx("button", { type: "button", onClick: onSelectAll, disabled: allSelected || listedCount === 0, className: actionClassName(), children: "Select all" }), _jsx("button", { type: "button", onClick: onClearSelection, disabled: selectedCount === 0, className: actionClassName(), children: "Clear" }), _jsx("button", { type: "button", onClick: onCancel, className: `${actionClassName()} ml-auto`, children: "Cancel" })] }), _jsxs("div", { className: "flex flex-wrap items-center gap-0.5 px-1.5 py-1", children: [_jsx("button", { type: "button", onClick: () => onSetRead(true), disabled: none || busy, className: actionClassName(), ...hint("Mark read", SHORTCUTS.mail.markRead), children: "Mark read" }), _jsx("button", { type: "button", onClick: () => onSetRead(false), disabled: none || busy, className: actionClassName(), ...hint("Mark unread", SHORTCUTS.mail.markUnread), children: "Mark unread" }), _jsx("button", { type: "button", onClick: () => onSetFlagged(true), disabled: none || busy, className: actionClassName(), ...hint("Flag", SHORTCUTS.mail.flag), children: "Flag" }), _jsx("button", { type: "button", onClick: () => onSetFlagged(false), disabled: none || busy, className: actionClassName(), children: "Unflag" }), _jsx("button", { type: "button", onClick: onArchive, disabled: none || busy || !!archiveReason, title: archiveReason, className: actionClassName(), children: "Archive" }), _jsx(LabelMenuButton, { "aria-label": "Apply label", label: "Apply label", className: "text-sm", labels: labels, mailboxUid: mailboxUid, onLabelCreated: onLabelCreated, applied: appliedToAll.map((label) => label.uid), partial: appliedToSome.map((label) => label.uid), onCommit: onApplyLabels, busy: busy, disabled: none || busy || !!labelsDisabledReason, title: labelsDisabledReason, note: selected.length === 1
|
|
53
51
|
? "Ticked labels are applied, unticked ones removed."
|
|
54
|
-
: "Ticked labels are applied to every selected message and unticked ones removed from all of them; a dash means only some have that label, and leaving it alone keeps it that way.", emptyNote: "This mailbox has no labels yet.", commit: { label: "Apply" }, clear: { label: "Remove all labels" } }), _jsx("button", { type: "button", onClick: () => setMovePrompt(true), disabled: none || busy || !!moveDisabledReason, title: moveDisabledReason, className: actionClassName(), children: "Move to" }), _jsx("button", { type: "button", onClick: onReportJunk, disabled: none || busy || !!junkReason, title: junkReason, className: actionClassName(), children: "Report junk" }), _jsx("button", { type: "button", onClick: onDelete, disabled: none || busy
|
|
52
|
+
: "Ticked labels are applied to every selected message and unticked ones removed from all of them; a dash means only some have that label, and leaving it alone keeps it that way.", emptyNote: "This mailbox has no labels yet.", commit: { label: "Apply" }, clear: { label: "Remove all labels" } }), _jsx("button", { type: "button", onClick: () => setMovePrompt(true), disabled: none || busy || !!moveDisabledReason, title: moveDisabledReason, className: actionClassName(), children: "Move to" }), _jsx("button", { type: "button", onClick: onReportJunk, disabled: none || busy || !!junkReason, title: junkReason, className: actionClassName(), children: "Report junk" }), _jsx("button", { type: "button", onClick: onDelete, disabled: none || busy, className: actionClassName(deletesPermanently), ...hint(deleteLabel, SHORTCUTS.mail.delete), children: deleteLabel })] })] }));
|
|
55
53
|
}
|