@rapidmx/web-client 0.10.0 → 0.12.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 +183 -2
- package/apps/admin/_layout.tsx +8 -2
- package/apps/admin/index.tsx +7 -1
- package/apps/admin/ingest-queue/index.tsx +2 -1
- package/apps/admin/mailboxes/[uid].tsx +23 -10
- package/apps/admin/mailboxes/new/index.tsx +3 -3
- package/apps/admin/quarantine/index.tsx +2 -1
- package/apps/admin/signing-certificates/index.tsx +343 -0
- package/apps/escrow/_layout.tsx +8 -2
- package/apps/shared/appearance/AppearanceForm.tsx +392 -0
- package/apps/shared/appearance/AppearanceHead.tsx +49 -0
- package/apps/shared/appearance/AppearanceProvider.tsx +470 -0
- package/apps/shared/appearance/ColorField.tsx +107 -0
- package/apps/shared/appearance/appearanceCache.ts +106 -0
- package/apps/shared/appearance/appearanceContext.ts +77 -0
- package/apps/shared/appearance/bootScript.ts +35 -0
- package/apps/shared/appearance/color.ts +169 -0
- package/apps/shared/appearance/photo.ts +88 -0
- package/apps/shared/appearance/resolvedTheme.ts +67 -0
- package/apps/shared/appearance/theme.ts +355 -0
- package/apps/shared/components/admin/escrowScopes/EscrowScopeKeyAndHoldersFields.tsx +4 -4
- package/apps/shared/components/admin/layout/AdminShell.tsx +22 -10
- package/apps/shared/components/admin/mailboxes/ShareAccessCard.tsx +107 -60
- package/apps/shared/components/admin/settings/BrandingForm.tsx +24 -0
- package/apps/shared/components/admin/settings/DomainDnsSetup.tsx +60 -14
- package/apps/shared/components/admin/settings/MailboxCreateForm.tsx +65 -16
- package/apps/shared/components/admin/settings/PluginsManager.tsx +228 -25
- package/apps/shared/components/admin/setup/SetupWizard.tsx +2 -1
- package/apps/shared/components/calendar/CalendarListSidebar.tsx +4 -5
- package/apps/shared/components/contacts/ContactForm.tsx +11 -5
- package/apps/shared/components/contacts/ContactsSidebar.tsx +5 -3
- package/apps/shared/components/contacts/ContactsToolbar.tsx +25 -52
- package/apps/shared/components/contacts/layout/ContactsShell.tsx +10 -5
- package/apps/shared/components/escrow/layout/EscrowShell.tsx +13 -6
- package/apps/shared/components/layout/AppShell.tsx +98 -32
- package/apps/shared/components/layout/BrandingChrome.tsx +200 -13
- package/apps/shared/components/layout/RailIcon.tsx +100 -0
- package/apps/shared/components/layout/ResponsiveToolbar.tsx +315 -0
- package/apps/shared/components/layout/ThemeSwitch.tsx +84 -0
- package/apps/shared/components/layout/UserMenu.tsx +75 -6
- package/apps/shared/components/mail/ConversationList.tsx +4 -2
- package/apps/shared/components/mail/ConversationThreadPane.tsx +487 -475
- package/apps/shared/components/mail/LazyReadingPane.tsx +14 -16
- package/apps/shared/components/mail/MailSelectionBar.tsx +2 -10
- package/apps/shared/components/mail/MessageDetailPane.tsx +366 -287
- package/apps/shared/components/mail/OutboxBadge.tsx +44 -0
- package/apps/shared/components/mail/OutboxRowStatus.tsx +31 -0
- package/apps/shared/components/mail/compose/ComposeContext.tsx +35 -2
- package/apps/shared/components/mail/compose/ComposeWindow.tsx +239 -437
- package/apps/shared/components/mail/compose/encryptionRequirement.ts +86 -0
- package/apps/shared/components/mail/layout/MailShell.tsx +39 -25
- package/apps/shared/components/mail/reading/EncryptedBody.tsx +65 -0
- package/apps/shared/components/mail/reading/EncryptedPreview.tsx +35 -0
- package/apps/shared/components/mail/reading/MessageBody.tsx +240 -0
- package/apps/shared/components/mail/reading/MessageCard.tsx +174 -0
- package/apps/shared/components/mail/reading/bodyContent.ts +100 -0
- package/apps/shared/components/mail/reading/bodyHtml.ts +298 -0
- package/apps/shared/components/mail/reading/color.ts +245 -0
- package/apps/shared/components/mail/reading/frameControl.ts +142 -0
- package/apps/shared/components/mail/reading/frameDocument.ts +123 -0
- package/apps/shared/components/mail/reading/safeDocument.ts +24 -0
- package/apps/shared/components/mail/reading/themeAdaptation.ts +221 -0
- package/apps/shared/components/mail/reading/themeSurface.ts +90 -0
- package/apps/shared/components/mail/reading/viewOriginal.ts +38 -0
- package/apps/shared/components/settings/SigningCertificateCard.tsx +344 -0
- package/apps/shared/components/settings/layout/SettingsShell.tsx +1 -0
- package/apps/shared/components/sharing/PrincipalListField.tsx +82 -0
- package/apps/shared/components/sharing/PrincipalPicker.tsx +76 -0
- package/apps/shared/components/sharing/PrincipalResolver.tsx +164 -0
- package/apps/shared/components/tasks/TasksSidebar.tsx +5 -3
- package/apps/shared/components/tasks/layout/TasksShell.tsx +9 -5
- package/apps/shared/mail/folderCounts.ts +9 -3
- package/apps/shared/mail/folderTree.ts +143 -0
- package/apps/shared/mail/outbox/UnlockBridge.tsx +23 -0
- package/apps/shared/mail/outbox/composeBridge.ts +69 -0
- package/apps/shared/mail/outbox/outboxState.ts +88 -0
- package/apps/shared/mail/outbox/pendingSends.ts +146 -0
- package/apps/shared/mail/outbox/sendDecision.ts +157 -0
- package/apps/shared/mail/outbox/sendJob.ts +427 -0
- package/apps/shared/mail/outbox/sendOutcomes.ts +146 -0
- package/apps/shared/mail/outbox/sendState.ts +35 -0
- package/apps/shared/mail/outbox/useOutboxStatus.ts +61 -0
- package/apps/shared/mail/useMailConnection.ts +81 -28
- package/apps/shared/mail/useMailLiveUpdates.ts +55 -10
- package/apps/shared/mail/useNewMailNotifications.ts +47 -32
- package/apps/shared/navigation/AppRouter.tsx +2 -2
- package/apps/shared/notifications/NotificationCenter.tsx +215 -0
- package/apps/shared/notifications/NotificationHistoryDialog.tsx +90 -0
- package/apps/shared/notifications/apiErrors.ts +98 -0
- package/apps/shared/notifications/headerOffset.ts +35 -0
- package/apps/shared/notifications/pushStatus.ts +56 -0
- package/apps/shared/notifications/store.ts +549 -0
- package/apps/shared/notifications/systemErrors.ts +63 -0
- package/apps/shared/notifications/useNotifications.ts +41 -0
- package/apps/shared/signing/enrollmentStorage.ts +33 -0
- package/apps/shared/signing/enrollmentTracker.ts +385 -0
- package/apps/shared/signing/enrollmentView.ts +251 -0
- package/apps/shared/signing/signingInfo.ts +58 -0
- package/apps/shared/signing/useNow.ts +19 -0
- package/apps/shared/signing/useSigningEnrollmentWatcher.ts +90 -0
- package/apps/shared/styles/app.css +80 -0
- package/apps/www/_layout.tsx +8 -2
- package/apps/www/_routes.ts +1 -0
- package/apps/www/calendar/index.tsx +9 -14
- package/apps/www/contacts/[uid].tsx +5 -8
- package/apps/www/contacts/index.tsx +587 -584
- package/apps/www/index.tsx +38 -27
- package/apps/www/messages/[uid].tsx +3 -1
- package/apps/www/settings/appearance/index.tsx +21 -0
- package/apps/www/settings/auto-reply/index.tsx +3 -7
- package/apps/www/settings/encryption/index.tsx +151 -116
- package/apps/www/settings/filters/[uid].tsx +3 -1
- package/apps/www/settings/filters/new/index.tsx +3 -1
- package/apps/www/settings/privacy/index.tsx +3 -8
- package/apps/www/settings/read-receipts/index.tsx +3 -7
- package/apps/www/settings/sharing/index.tsx +44 -65
- package/apps/www/settings/signatures/[uid].tsx +3 -1
- package/apps/www/settings/signatures/new/index.tsx +3 -2
- package/apps/www/tasks/index.tsx +11 -16
- package/dist/apps/admin/_layout.d.ts +5 -1
- package/dist/apps/admin/_layout.js +3 -2
- package/dist/apps/admin/index.js +3 -2
- package/dist/apps/admin/ingest-queue/index.js +2 -1
- package/dist/apps/admin/mailboxes/[uid].js +9 -6
- package/dist/apps/admin/mailboxes/new/index.js +1 -1
- package/dist/apps/admin/quarantine/index.js +2 -1
- package/dist/apps/admin/signing-certificates/index.d.ts +3 -0
- package/dist/apps/admin/signing-certificates/index.js +161 -0
- package/dist/apps/escrow/_layout.d.ts +5 -1
- package/dist/apps/escrow/_layout.js +3 -2
- package/dist/apps/shared/appearance/AppearanceForm.d.ts +7 -0
- package/dist/apps/shared/appearance/AppearanceForm.js +131 -0
- package/dist/apps/shared/appearance/AppearanceHead.d.ts +15 -0
- package/dist/apps/shared/appearance/AppearanceHead.js +31 -0
- package/dist/apps/shared/appearance/AppearanceProvider.d.ts +32 -0
- package/dist/apps/shared/appearance/AppearanceProvider.js +390 -0
- package/dist/apps/shared/appearance/ColorField.d.ts +22 -0
- package/dist/apps/shared/appearance/ColorField.js +40 -0
- package/dist/apps/shared/appearance/appearanceCache.d.ts +45 -0
- package/dist/apps/shared/appearance/appearanceCache.js +80 -0
- package/dist/apps/shared/appearance/appearanceContext.d.ts +45 -0
- package/dist/apps/shared/appearance/appearanceContext.js +29 -0
- package/dist/apps/shared/appearance/bootScript.d.ts +16 -0
- package/dist/apps/shared/appearance/bootScript.js +33 -0
- package/dist/apps/shared/appearance/color.d.ts +65 -0
- package/dist/apps/shared/appearance/color.js +132 -0
- package/dist/apps/shared/appearance/photo.d.ts +17 -0
- package/dist/apps/shared/appearance/photo.js +79 -0
- package/dist/apps/shared/appearance/resolvedTheme.d.ts +21 -0
- package/dist/apps/shared/appearance/resolvedTheme.js +55 -0
- package/dist/apps/shared/appearance/theme.d.ts +111 -0
- package/dist/apps/shared/appearance/theme.js +251 -0
- package/dist/apps/shared/components/admin/escrowScopes/EscrowScopeKeyAndHoldersFields.js +3 -2
- package/dist/apps/shared/components/admin/layout/AdminShell.d.ts +1 -1
- package/dist/apps/shared/components/admin/layout/AdminShell.js +16 -6
- package/dist/apps/shared/components/admin/mailboxes/ShareAccessCard.d.ts +10 -5
- package/dist/apps/shared/components/admin/mailboxes/ShareAccessCard.js +49 -34
- package/dist/apps/shared/components/admin/settings/BrandingForm.d.ts +2 -0
- package/dist/apps/shared/components/admin/settings/BrandingForm.js +7 -1
- package/dist/apps/shared/components/admin/settings/DomainDnsSetup.js +25 -5
- package/dist/apps/shared/components/admin/settings/MailboxCreateForm.js +18 -7
- package/dist/apps/shared/components/admin/settings/PluginsManager.js +106 -20
- package/dist/apps/shared/components/admin/setup/SetupWizard.js +2 -1
- package/dist/apps/shared/components/calendar/CalendarListSidebar.js +8 -8
- package/dist/apps/shared/components/contacts/ContactForm.js +1 -1
- package/dist/apps/shared/components/contacts/ContactsSidebar.js +4 -2
- package/dist/apps/shared/components/contacts/ContactsToolbar.d.ts +3 -1
- package/dist/apps/shared/components/contacts/ContactsToolbar.js +24 -9
- package/dist/apps/shared/components/contacts/layout/ContactsShell.js +4 -1
- package/dist/apps/shared/components/escrow/layout/EscrowShell.js +7 -4
- package/dist/apps/shared/components/layout/AppShell.d.ts +6 -1
- package/dist/apps/shared/components/layout/AppShell.js +59 -13
- package/dist/apps/shared/components/layout/BrandingChrome.d.ts +69 -10
- package/dist/apps/shared/components/layout/BrandingChrome.js +140 -12
- package/dist/apps/shared/components/layout/RailIcon.d.ts +21 -0
- package/dist/apps/shared/components/layout/RailIcon.js +85 -0
- package/dist/apps/shared/components/layout/ResponsiveToolbar.d.ts +55 -0
- package/dist/apps/shared/components/layout/ResponsiveToolbar.js +199 -0
- package/dist/apps/shared/components/layout/ThemeSwitch.d.ts +9 -0
- package/dist/apps/shared/components/layout/ThemeSwitch.js +54 -0
- package/dist/apps/shared/components/layout/UserMenu.d.ts +7 -1
- package/dist/apps/shared/components/layout/UserMenu.js +41 -7
- package/dist/apps/shared/components/mail/ConversationList.js +3 -2
- package/dist/apps/shared/components/mail/ConversationThreadPane.d.ts +2 -1
- package/dist/apps/shared/components/mail/ConversationThreadPane.js +42 -20
- package/dist/apps/shared/components/mail/LazyReadingPane.js +9 -8
- package/dist/apps/shared/components/mail/MailSelectionBar.d.ts +3 -4
- package/dist/apps/shared/components/mail/MailSelectionBar.js +2 -3
- package/dist/apps/shared/components/mail/MessageDetailPane.d.ts +18 -7
- package/dist/apps/shared/components/mail/MessageDetailPane.js +86 -41
- package/dist/apps/shared/components/mail/OutboxBadge.d.ts +17 -0
- package/dist/apps/shared/components/mail/OutboxBadge.js +18 -0
- package/dist/apps/shared/components/mail/OutboxRowStatus.d.ts +10 -0
- package/dist/apps/shared/components/mail/OutboxRowStatus.js +19 -0
- package/dist/apps/shared/components/mail/compose/ComposeContext.d.ts +8 -0
- package/dist/apps/shared/components/mail/compose/ComposeContext.js +28 -3
- package/dist/apps/shared/components/mail/compose/ComposeWindow.d.ts +2 -1
- package/dist/apps/shared/components/mail/compose/ComposeWindow.js +229 -328
- package/dist/apps/shared/components/mail/compose/encryptionRequirement.d.ts +49 -0
- package/dist/apps/shared/components/mail/compose/encryptionRequirement.js +35 -0
- package/dist/apps/shared/components/mail/layout/MailShell.d.ts +2 -0
- package/dist/apps/shared/components/mail/layout/MailShell.js +20 -14
- package/dist/apps/shared/components/mail/reading/EncryptedBody.d.ts +21 -0
- package/dist/apps/shared/components/mail/reading/EncryptedBody.js +22 -0
- package/dist/apps/shared/components/mail/reading/EncryptedPreview.d.ts +18 -0
- package/dist/apps/shared/components/mail/reading/EncryptedPreview.js +22 -0
- package/dist/apps/shared/components/mail/reading/MessageBody.d.ts +32 -0
- package/dist/apps/shared/components/mail/reading/MessageBody.js +136 -0
- package/dist/apps/shared/components/mail/reading/MessageCard.d.ts +62 -0
- package/dist/apps/shared/components/mail/reading/MessageCard.js +58 -0
- package/dist/apps/shared/components/mail/reading/bodyContent.d.ts +38 -0
- package/dist/apps/shared/components/mail/reading/bodyContent.js +84 -0
- package/dist/apps/shared/components/mail/reading/bodyHtml.d.ts +47 -0
- package/dist/apps/shared/components/mail/reading/bodyHtml.js +267 -0
- package/dist/apps/shared/components/mail/reading/color.d.ts +63 -0
- package/dist/apps/shared/components/mail/reading/color.js +201 -0
- package/dist/apps/shared/components/mail/reading/frameControl.d.ts +33 -0
- package/dist/apps/shared/components/mail/reading/frameControl.js +120 -0
- package/dist/apps/shared/components/mail/reading/frameDocument.d.ts +52 -0
- package/dist/apps/shared/components/mail/reading/frameDocument.js +98 -0
- package/dist/apps/shared/components/mail/reading/safeDocument.d.ts +3 -0
- package/dist/apps/shared/components/mail/reading/safeDocument.js +12 -0
- package/dist/apps/shared/components/mail/reading/themeAdaptation.d.ts +56 -0
- package/dist/apps/shared/components/mail/reading/themeAdaptation.js +144 -0
- package/dist/apps/shared/components/mail/reading/themeSurface.d.ts +17 -0
- package/dist/apps/shared/components/mail/reading/themeSurface.js +83 -0
- package/dist/apps/shared/components/mail/reading/viewOriginal.d.ts +9 -0
- package/dist/apps/shared/components/mail/reading/viewOriginal.js +32 -0
- package/dist/apps/shared/components/settings/SigningCertificateCard.d.ts +32 -0
- package/dist/apps/shared/components/settings/SigningCertificateCard.js +98 -0
- package/dist/apps/shared/components/settings/layout/SettingsShell.js +1 -0
- package/dist/apps/shared/components/sharing/PrincipalListField.d.ts +24 -0
- package/dist/apps/shared/components/sharing/PrincipalListField.js +23 -0
- package/dist/apps/shared/components/sharing/PrincipalPicker.d.ts +28 -0
- package/dist/apps/shared/components/sharing/PrincipalPicker.js +24 -0
- package/dist/apps/shared/components/sharing/PrincipalResolver.d.ts +44 -0
- package/dist/apps/shared/components/sharing/PrincipalResolver.js +92 -0
- package/dist/apps/shared/components/tasks/TasksSidebar.js +4 -2
- package/dist/apps/shared/components/tasks/layout/TasksShell.js +3 -1
- package/dist/apps/shared/mail/folderCounts.d.ts +1 -1
- package/dist/apps/shared/mail/folderCounts.js +9 -3
- package/dist/apps/shared/mail/folderTree.d.ts +55 -0
- package/dist/apps/shared/mail/folderTree.js +116 -0
- package/dist/apps/shared/mail/outbox/UnlockBridge.d.ts +5 -0
- package/dist/apps/shared/mail/outbox/UnlockBridge.js +18 -0
- package/dist/apps/shared/mail/outbox/composeBridge.d.ts +34 -0
- package/dist/apps/shared/mail/outbox/composeBridge.js +32 -0
- package/dist/apps/shared/mail/outbox/outboxState.d.ts +41 -0
- package/dist/apps/shared/mail/outbox/outboxState.js +42 -0
- package/dist/apps/shared/mail/outbox/pendingSends.d.ts +39 -0
- package/dist/apps/shared/mail/outbox/pendingSends.js +109 -0
- package/dist/apps/shared/mail/outbox/sendDecision.d.ts +80 -0
- package/dist/apps/shared/mail/outbox/sendDecision.js +94 -0
- package/dist/apps/shared/mail/outbox/sendJob.d.ts +68 -0
- package/dist/apps/shared/mail/outbox/sendJob.js +319 -0
- package/dist/apps/shared/mail/outbox/sendOutcomes.d.ts +3 -0
- package/dist/apps/shared/mail/outbox/sendOutcomes.js +141 -0
- package/dist/apps/shared/mail/outbox/sendState.d.ts +25 -0
- package/dist/apps/shared/mail/outbox/sendState.js +8 -0
- package/dist/apps/shared/mail/outbox/useOutboxStatus.d.ts +11 -0
- package/dist/apps/shared/mail/outbox/useOutboxStatus.js +49 -0
- package/dist/apps/shared/mail/useMailConnection.d.ts +9 -8
- package/dist/apps/shared/mail/useMailConnection.js +64 -22
- package/dist/apps/shared/mail/useMailLiveUpdates.d.ts +17 -4
- package/dist/apps/shared/mail/useMailLiveUpdates.js +36 -10
- package/dist/apps/shared/mail/useNewMailNotifications.d.ts +4 -10
- package/dist/apps/shared/mail/useNewMailNotifications.js +43 -23
- package/dist/apps/shared/navigation/AppRouter.js +2 -2
- package/dist/apps/shared/notifications/NotificationCenter.d.ts +19 -0
- package/dist/apps/shared/notifications/NotificationCenter.js +90 -0
- package/dist/apps/shared/notifications/NotificationHistoryDialog.d.ts +10 -0
- package/dist/apps/shared/notifications/NotificationHistoryDialog.js +36 -0
- package/dist/apps/shared/notifications/apiErrors.d.ts +19 -0
- package/dist/apps/shared/notifications/apiErrors.js +86 -0
- package/dist/apps/shared/notifications/headerOffset.d.ts +8 -0
- package/dist/apps/shared/notifications/headerOffset.js +33 -0
- package/dist/apps/shared/notifications/pushStatus.d.ts +9 -0
- package/dist/apps/shared/notifications/pushStatus.js +54 -0
- package/dist/apps/shared/notifications/store.d.ts +136 -0
- package/dist/apps/shared/notifications/store.js +416 -0
- package/dist/apps/shared/notifications/systemErrors.d.ts +9 -0
- package/dist/apps/shared/notifications/systemErrors.js +58 -0
- package/dist/apps/shared/notifications/useNotifications.d.ts +17 -0
- package/dist/apps/shared/notifications/useNotifications.js +18 -0
- package/dist/apps/shared/signing/enrollmentStorage.d.ts +10 -0
- package/dist/apps/shared/signing/enrollmentStorage.js +33 -0
- package/dist/apps/shared/signing/enrollmentTracker.d.ts +73 -0
- package/dist/apps/shared/signing/enrollmentTracker.js +307 -0
- package/dist/apps/shared/signing/enrollmentView.d.ts +57 -0
- package/dist/apps/shared/signing/enrollmentView.js +214 -0
- package/dist/apps/shared/signing/signingInfo.d.ts +11 -0
- package/dist/apps/shared/signing/signingInfo.js +48 -0
- package/dist/apps/shared/signing/useNow.d.ts +2 -0
- package/dist/apps/shared/signing/useNow.js +18 -0
- package/dist/apps/shared/signing/useSigningEnrollmentWatcher.d.ts +16 -0
- package/dist/apps/shared/signing/useSigningEnrollmentWatcher.js +87 -0
- package/dist/apps/shared/styles/app.css +80 -0
- package/dist/apps/www/_layout.d.ts +5 -1
- package/dist/apps/www/_layout.js +3 -2
- package/dist/apps/www/_routes.js +1 -0
- package/dist/apps/www/calendar/index.js +3 -4
- package/dist/apps/www/contacts/[uid].js +5 -7
- package/dist/apps/www/contacts/index.js +14 -21
- package/dist/apps/www/index.js +37 -19
- package/dist/apps/www/messages/[uid].js +3 -1
- package/dist/apps/www/settings/appearance/index.d.ts +4 -0
- package/dist/apps/www/settings/appearance/index.js +13 -0
- package/dist/apps/www/settings/auto-reply/index.js +3 -6
- package/dist/apps/www/settings/encryption/index.js +103 -95
- package/dist/apps/www/settings/filters/[uid].js +3 -1
- package/dist/apps/www/settings/filters/new/index.js +3 -1
- package/dist/apps/www/settings/privacy/index.js +5 -8
- package/dist/apps/www/settings/read-receipts/index.js +3 -6
- package/dist/apps/www/settings/sharing/index.js +19 -33
- package/dist/apps/www/settings/signatures/[uid].js +3 -1
- package/dist/apps/www/settings/signatures/new/index.js +3 -2
- package/dist/apps/www/tasks/index.js +8 -14
- package/package.json +2 -2
- package/apps/shared/components/mail/NewMailToasts.tsx +0 -141
- package/apps/shared/components/mail/compose/SendFailureAlert.tsx +0 -48
- package/dist/apps/shared/components/mail/NewMailToasts.d.ts +0 -19
- package/dist/apps/shared/components/mail/NewMailToasts.js +0 -55
- package/dist/apps/shared/components/mail/compose/SendFailureAlert.d.ts +0 -14
- package/dist/apps/shared/components/mail/compose/SendFailureAlert.js +0 -11
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Mailbox } from "@rapidmx/react-shared/mail/mailApi.js";
|
|
2
|
+
/** Where the pop-up's action leads: the page that shows the certificate. */
|
|
3
|
+
export declare const SIGNING_SETTINGS_HREF = "/settings/encryption";
|
|
4
|
+
/**
|
|
5
|
+
* Tells the user, wherever they are in the app, when a signing certificate they asked for is issued or has failed. Lives in the persistent app
|
|
6
|
+
* frame (`AppChrome`): for each mailbox the user owns it follows the enrollment this browser has a stored id for and - for one started on another
|
|
7
|
+
* device - the one the server names as the mailbox's current enrollment when it is still pending (`GET .../sign-enrollment`; an older server
|
|
8
|
+
* answers 404 and nothing is followed). Reading, backoff and visibility rules are the tracker's (`enrollmentTracker.ts`), shared with the
|
|
9
|
+
* Settings > Encryption card, so an enrollment is asked about once. The pop-up is raised once per enrollment (`dedupeKey`) and only for one that
|
|
10
|
+
* was seen pending - never for a certificate issued long ago.
|
|
11
|
+
*/
|
|
12
|
+
export declare function useSigningEnrollmentWatcher({ userUid, mailboxes, enabled }: {
|
|
13
|
+
userUid?: string;
|
|
14
|
+
mailboxes: Mailbox[];
|
|
15
|
+
enabled: boolean;
|
|
16
|
+
}): void;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
2
|
+
// Copyright (C) 2026 Jean-Philippe Steinmetz
|
|
3
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
5
|
+
import { useEffect, useRef } from "react";
|
|
6
|
+
import { getCurrentSignEnrollment } from "@rapidmx/react-shared/crypto/keyvaultApi.js";
|
|
7
|
+
import { notify } from "../notifications/store.js";
|
|
8
|
+
import { readStoredSignEnrollment, storeSignEnrollment } from "./enrollmentStorage.js";
|
|
9
|
+
import { onEnrollmentEnded, watchCurrentEnrollment, watchEnrollment } from "./enrollmentTracker.js";
|
|
10
|
+
import { formatDate, isInstalling } from "./enrollmentView.js";
|
|
11
|
+
/** Where the pop-up's action leads: the page that shows the certificate. */
|
|
12
|
+
export const SIGNING_SETTINGS_HREF = "/settings/encryption";
|
|
13
|
+
/**
|
|
14
|
+
* Tells the user, wherever they are in the app, when a signing certificate they asked for is issued or has failed. Lives in the persistent app
|
|
15
|
+
* frame (`AppChrome`): for each mailbox the user owns it follows the enrollment this browser has a stored id for and - for one started on another
|
|
16
|
+
* device - the one the server names as the mailbox's current enrollment when it is still pending (`GET .../sign-enrollment`; an older server
|
|
17
|
+
* answers 404 and nothing is followed). Reading, backoff and visibility rules are the tracker's (`enrollmentTracker.ts`), shared with the
|
|
18
|
+
* Settings > Encryption card, so an enrollment is asked about once. The pop-up is raised once per enrollment (`dedupeKey`) and only for one that
|
|
19
|
+
* was seen pending - never for a certificate issued long ago.
|
|
20
|
+
*/
|
|
21
|
+
export function useSigningEnrollmentWatcher({ userUid, mailboxes, enabled }) {
|
|
22
|
+
const mailboxesRef = useRef(mailboxes);
|
|
23
|
+
mailboxesRef.current = mailboxes;
|
|
24
|
+
// Keyed on the uids of the mailboxes the user owns: a refreshed list of the same mailboxes must not start over.
|
|
25
|
+
const ownedKey = mailboxes
|
|
26
|
+
.filter((mailbox) => mailbox.ownerUserUid === userUid)
|
|
27
|
+
.map((mailbox) => mailbox.uid)
|
|
28
|
+
.join("|");
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
if (!enabled || !userUid || !ownedKey) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
let cancelled = false;
|
|
34
|
+
const releases = [];
|
|
35
|
+
for (const mailboxUid of ownedKey.split("|")) {
|
|
36
|
+
const storedId = readStoredSignEnrollment(mailboxUid);
|
|
37
|
+
if (storedId) {
|
|
38
|
+
releases.push(watchEnrollment(mailboxUid, storedId));
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
// Never throws or rejects: a lookup that cannot be made (an older server, offline, anything) is "nothing is pending", and the frame goes on.
|
|
42
|
+
void (async () => {
|
|
43
|
+
try {
|
|
44
|
+
const current = await getCurrentSignEnrollment(mailboxUid);
|
|
45
|
+
if (!cancelled && current?.status === "pending") {
|
|
46
|
+
storeSignEnrollment(mailboxUid, current.enrollmentId);
|
|
47
|
+
releases.push(watchCurrentEnrollment(mailboxUid, current));
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
// Nothing to follow.
|
|
52
|
+
}
|
|
53
|
+
})();
|
|
54
|
+
}
|
|
55
|
+
const stopListening = onEnrollmentEnded((snapshot) => announce(snapshot, mailboxesRef.current));
|
|
56
|
+
return () => {
|
|
57
|
+
cancelled = true;
|
|
58
|
+
stopListening();
|
|
59
|
+
releases.forEach((release) => release());
|
|
60
|
+
};
|
|
61
|
+
}, [enabled, userUid, ownedKey]);
|
|
62
|
+
}
|
|
63
|
+
/** The pop-up for an enrollment that just ended - issued or failed (the tracker announces nothing else, and always with its answer). */
|
|
64
|
+
function announce(snapshot, mailboxes) {
|
|
65
|
+
const address = mailboxes.find((mailbox) => mailbox.uid === snapshot.mailboxUid)?.primarySmtpAddress;
|
|
66
|
+
const result = snapshot.result;
|
|
67
|
+
if (result.status === "issued") {
|
|
68
|
+
notify({
|
|
69
|
+
kind: "success",
|
|
70
|
+
title: "Digital signature certificate issued",
|
|
71
|
+
message: isInstalling(result)
|
|
72
|
+
? `The certificate for ${address ?? "your mailbox"} is issued and is being installed - mail is signed once it is (a few minutes).`
|
|
73
|
+
: `${address ? `Mail from ${address}` : "Your mail"} is now signed${result.notAfter ? ` (valid until ${formatDate(result.notAfter)})` : ""}.`,
|
|
74
|
+
actions: [{ label: "View", href: SIGNING_SETTINGS_HREF }],
|
|
75
|
+
dedupeKey: `sign-enrollment:${snapshot.enrollmentId}`,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
notify({
|
|
80
|
+
kind: "error",
|
|
81
|
+
title: "Digital signature certificate could not be issued",
|
|
82
|
+
message: result.error ?? "The certificate authority did not issue a certificate.",
|
|
83
|
+
actions: [{ label: result.retryable === false ? "Details" : "Try again", href: SIGNING_SETTINGS_HREF }],
|
|
84
|
+
dedupeKey: `sign-enrollment:${snapshot.enrollmentId}`,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -304,3 +304,83 @@ h6 {
|
|
|
304
304
|
transform: none;
|
|
305
305
|
}
|
|
306
306
|
}
|
|
307
|
+
|
|
308
|
+
/* The frame's own background: the page's `surface-alt`, unless the user has chosen a background (`appearance/theme.ts` then sets `--rr-frame-bg`
|
|
309
|
+
* to `transparent` so the fixed background layer - `html::before`/`html::after` - shows through instead). */
|
|
310
|
+
.rr-frame-bg {
|
|
311
|
+
background-color: var(--rr-frame-bg, var(--rr-color-surface-alt));
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/* With a background set, `--color-surface` and `--color-surface-alt` (what `bg-surface` and `bg-surface-alt` read) are translucent panels
|
|
315
|
+
* (`appearance/theme.ts`). Anything that must stay readable on its own - menus, dialogs, drawers, list pop-ups and the pop-ups - takes the
|
|
316
|
+
* opaque colours back for itself and everything inside it, and so does anything that stays put while a page scrolls under it (`.rr-solid`: the
|
|
317
|
+
* sticky title bar, the branding header, the phone's bottom tab bar), or the text beneath would show through it. Without a background these two declarations change nothing. */
|
|
318
|
+
[role="menu"],
|
|
319
|
+
[role="dialog"],
|
|
320
|
+
[role="listbox"],
|
|
321
|
+
.rr-toast-in,
|
|
322
|
+
.rr-solid,
|
|
323
|
+
[data-branding-header],
|
|
324
|
+
nav[aria-label="Mobile navigation"] {
|
|
325
|
+
--color-surface: var(--rr-color-surface);
|
|
326
|
+
--color-surface-alt: var(--rr-color-surface-alt);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/* The slot a branding header or footer's `{USER_MENU}` becomes (`BrandingChrome.tsx`): the account menu's button sits in the line of text
|
|
330
|
+
* the variable was written in. */
|
|
331
|
+
[data-rr-slot="user-menu"] {
|
|
332
|
+
display: inline-flex;
|
|
333
|
+
align-items: center;
|
|
334
|
+
vertical-align: middle;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/* The Outbox pill's "something is on its way" dot (`OutboxBadge`). Still when the user asks for less motion. */
|
|
338
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
339
|
+
.rr-sending-dot {
|
|
340
|
+
animation: rr-sending-dot 1.1s ease-in-out infinite;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
@keyframes rr-sending-dot {
|
|
344
|
+
0%,
|
|
345
|
+
100% {
|
|
346
|
+
opacity: 0.35;
|
|
347
|
+
transform: scale(0.8);
|
|
348
|
+
}
|
|
349
|
+
50% {
|
|
350
|
+
opacity: 1;
|
|
351
|
+
transform: scale(1.15);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/* The signing-certificate card's progress bar (`SigningCertificateCard`): a soft highlight sweeping across the filled part while a step is active, and
|
|
356
|
+
* an endless slide for a bar with no number. Both stand still when the user asks for less motion (the indeterminate bar then shows as a faded full bar). */
|
|
357
|
+
.rr-progress-shimmer {
|
|
358
|
+
position: relative;
|
|
359
|
+
overflow: hidden;
|
|
360
|
+
}
|
|
361
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
362
|
+
.rr-progress-shimmer::after {
|
|
363
|
+
content: "";
|
|
364
|
+
position: absolute;
|
|
365
|
+
inset: 0;
|
|
366
|
+
background: linear-gradient(90deg, transparent, rgb(255 255 255 / 0.45), transparent);
|
|
367
|
+
transform: translateX(-100%);
|
|
368
|
+
animation: rr-progress-shimmer 1.6s ease-in-out infinite;
|
|
369
|
+
}
|
|
370
|
+
.rr-progress-indeterminate {
|
|
371
|
+
animation: rr-progress-slide 1.4s ease-in-out infinite;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
@keyframes rr-progress-shimmer {
|
|
375
|
+
to {
|
|
376
|
+
transform: translateX(100%);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
@keyframes rr-progress-slide {
|
|
380
|
+
0% {
|
|
381
|
+
transform: translateX(-100%);
|
|
382
|
+
}
|
|
383
|
+
100% {
|
|
384
|
+
transform: translateX(300%);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
@@ -10,5 +10,9 @@ export interface LayoutProps {
|
|
|
10
10
|
* which doesn't receive props at all - falling back to the stock defaults there is acceptable.
|
|
11
11
|
*/
|
|
12
12
|
branding?: Branding;
|
|
13
|
+
/** The signed-in user's stored appearance preferences (a page prop from the same `fetchProps()`, present only on the www pages and only when the user has any): rendered as a stylesheet in the `<head>` so the very first paint is themed - see `AppearanceHead`. */
|
|
14
|
+
appearance?: unknown;
|
|
15
|
+
/** The signed-in user (supplied by the framework), so a cached appearance from another account on this browser is never applied. */
|
|
16
|
+
userUid?: string;
|
|
13
17
|
}
|
|
14
|
-
export default function Layout({ children, branding }: PropsWithChildren<LayoutProps>): React.JSX.Element;
|
|
18
|
+
export default function Layout({ children, branding, appearance, userUid }: PropsWithChildren<LayoutProps>): React.JSX.Element;
|
package/dist/apps/www/_layout.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { AppearanceHead, appearanceHtmlAttributes } from "../shared/appearance/AppearanceHead.js";
|
|
2
3
|
import { CUSTOM_STYLESHEET_LINK_ID } from "@rapidmx/react-shared/branding/useBranding.js";
|
|
3
|
-
export default function Layout({ children, branding }) {
|
|
4
|
+
export default function Layout({ children, branding, appearance, userUid }) {
|
|
4
5
|
const title = branding?.title || branding?.companyName ? `${branding?.title || branding?.companyName}: Mail` : "RapidMX: Mail";
|
|
5
6
|
const iconHref = branding?.iconUrl || branding?.logoUrl || "/images/logo.svg";
|
|
6
7
|
const stylesheetHref = branding?.stylesheetUrl;
|
|
7
|
-
return (_jsxs("html", { lang: "en", children: [_jsxs("head", { children: [_jsx("meta", { charSet: "utf-8" }), _jsx("meta", { name: "viewport", content: "width=device-width, initial-scale=1" }), _jsx("title", { children: title }), _jsx("link", { rel: "icon", type: "image/svg+xml", href: iconHref }), _jsx("link", { rel: "alternate icon", href: "/favicon.ico" }), _jsx("link", { rel: "preconnect", href: "https://fonts.googleapis.com" }), _jsx("link", { rel: "preconnect", href: "https://fonts.gstatic.com", crossOrigin: "anonymous" }), _jsx("link", { rel: "stylesheet", href: "https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" }), stylesheetHref && _jsx("link", { rel: "stylesheet", href: stylesheetHref, id: CUSTOM_STYLESHEET_LINK_ID })] }), _jsx("body", { children: children })] }));
|
|
8
|
+
return (_jsxs("html", { lang: "en", ...appearanceHtmlAttributes(appearance, userUid), children: [_jsxs("head", { children: [_jsx("meta", { charSet: "utf-8" }), _jsx("meta", { name: "viewport", content: "width=device-width, initial-scale=1" }), _jsx("title", { children: title }), _jsx("link", { rel: "icon", type: "image/svg+xml", href: iconHref }), _jsx("link", { rel: "alternate icon", href: "/favicon.ico" }), _jsx("link", { rel: "preconnect", href: "https://fonts.googleapis.com" }), _jsx("link", { rel: "preconnect", href: "https://fonts.gstatic.com", crossOrigin: "anonymous" }), _jsx("link", { rel: "stylesheet", href: "https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" }), stylesheetHref && _jsx("link", { rel: "stylesheet", href: stylesheetHref, id: CUSTOM_STYLESHEET_LINK_ID }), _jsx(AppearanceHead, { appearance: appearance })] }), _jsx("body", { children: children })] }));
|
|
8
9
|
}
|
package/dist/apps/www/_routes.js
CHANGED
|
@@ -14,6 +14,7 @@ export const wwwRoutes = [
|
|
|
14
14
|
{ path: "/contacts", active: "contacts", load: () => import("./contacts/index.js"), idlePrefetch: true },
|
|
15
15
|
{ path: "/contacts/:uid", active: "contacts", load: () => import("./contacts/[uid].js") },
|
|
16
16
|
{ path: "/tasks", active: "tasks", load: () => import("./tasks/index.js"), idlePrefetch: true },
|
|
17
|
+
{ path: "/settings/appearance", active: "settings", load: () => import("./settings/appearance/index.js") },
|
|
17
18
|
{ path: "/settings/auto-reply", active: "settings", load: () => import("./settings/auto-reply/index.js") },
|
|
18
19
|
{ path: "/settings/encryption", active: "settings", load: () => import("./settings/encryption/index.js") },
|
|
19
20
|
{ path: "/settings/filters", active: "settings", load: () => import("./settings/filters/index.js") },
|
|
@@ -28,6 +28,7 @@ import Button from "@rapidmx/react-shared/components/buttons/Button.js";
|
|
|
28
28
|
import { SHORTCUTS } from "../../shared/keyboard/keymap.js";
|
|
29
29
|
import { useShortcut } from "../../shared/keyboard/useShortcut.js";
|
|
30
30
|
import { useShortcutProps } from "../../shared/keyboard/useShortcutProps.js";
|
|
31
|
+
import { notifyApiError } from "../../shared/notifications/apiErrors.js";
|
|
31
32
|
function CalendarPage(props) {
|
|
32
33
|
return (_jsx(CalendarShell, { ...props, children: _jsx(CalendarContent, { userUid: props.userUid }) }));
|
|
33
34
|
}
|
|
@@ -66,7 +67,6 @@ function CalendarContent({ userUid }) {
|
|
|
66
67
|
const [events, setEvents] = useState([]);
|
|
67
68
|
const [loading, setLoading] = useState(true);
|
|
68
69
|
const [error, setError] = useState(null);
|
|
69
|
-
const [actionError, setActionError] = useState(null);
|
|
70
70
|
const [modal, setModal] = useState(null);
|
|
71
71
|
// `null` means "not yet customized by the user" — every calendar is treated as checked without
|
|
72
72
|
// needing to be seeded from `calendarFolders` the moment it loads (see `checkedFolderUids` below).
|
|
@@ -245,7 +245,6 @@ function CalendarContent({ userUid }) {
|
|
|
245
245
|
if (!action) {
|
|
246
246
|
return;
|
|
247
247
|
}
|
|
248
|
-
setActionError(null);
|
|
249
248
|
try {
|
|
250
249
|
if (action.type === "move") {
|
|
251
250
|
await moveOccurrence(action.occurrence, action.deltaMs);
|
|
@@ -256,7 +255,7 @@ function CalendarContent({ userUid }) {
|
|
|
256
255
|
reload();
|
|
257
256
|
}
|
|
258
257
|
catch (err) {
|
|
259
|
-
|
|
258
|
+
notifyApiError(err, action.type === "move" ? "Couldn't move the event" : "Couldn't resize the event");
|
|
260
259
|
}
|
|
261
260
|
}
|
|
262
261
|
const title = view === "month"
|
|
@@ -274,6 +273,6 @@ function CalendarContent({ userUid }) {
|
|
|
274
273
|
.filter((mc) => mc.calendarFolders.length > 0 && writableMailboxes.some((mb) => mb.uid === mc.mailbox.uid))
|
|
275
274
|
.map((mc) => ({ mailbox: mc.mailbox, calendars: mc.calendarFolders.map((f) => ({ uid: f.uid, name: f.name })) })), [mailboxCalendars, writableMailboxes]);
|
|
276
275
|
const sidebarContent = (_jsxs(_Fragment, { children: [_jsx(MiniDatePicker, { selected: viewDate, onSelect: (date) => setViewDate(startOfDay(date)) }), _jsx(CalendarListSidebar, { mailboxCalendars: mailboxCalendars, checkedFolderUids: checkedFolderUids, onToggle: toggleCalendar, onAddCalendar: handleAddCalendar, colorFor: colorFor })] }));
|
|
277
|
-
return (_jsxs("div", { className: "flex-1 flex min-h-0", children: [_jsx("div", { className: "hidden
|
|
276
|
+
return (_jsxs("div", { className: "flex-1 min-w-0 flex min-h-0", children: [_jsx("div", { className: "hidden lg:flex w-56 shrink-0 border-r border-border flex-col overflow-y-auto", children: sidebarContent }), _jsx(Drawer, { open: drawerOpen, onClose: () => setDrawerOpen(false), title: "Calendars", children: _jsx("div", { className: "flex flex-col", children: sidebarContent }) }), _jsxs("div", { className: "flex-1 min-w-0 flex flex-col min-h-0", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-x-3 gap-y-2 p-3 border-b border-border shrink-0", children: [_jsx("button", { type: "button", className: "lg:hidden w-9 h-9 flex items-center justify-center rounded-sm text-text-muted hover:bg-surface-alt hover:text-text shrink-0", "aria-label": "Open calendars", onClick: () => setDrawerOpen(true), children: _jsx(HiOutlineBars3, { size: 20, "aria-hidden": "true" }) }), _jsx(Button, { type: "button", onClick: () => openNewEvent(), className: "!w-auto shrink-0", ...newEventHint, children: "+ New event" }), _jsxs("div", { className: "flex items-center gap-1", children: [_jsx("button", { type: "button", onClick: () => shiftView(-1), "aria-label": "Previous", ...previousHint, className: "w-7 h-7 text-sm rounded-sm hover:bg-surface-alt", children: "\u2039" }), _jsx("button", { type: "button", onClick: goToday, ...todayHint, className: "px-2 h-7 text-sm rounded-sm hover:bg-surface-alt", children: "Today" }), _jsx("button", { type: "button", onClick: () => shiftView(1), "aria-label": "Next", ...nextHint, className: "w-7 h-7 text-sm rounded-sm hover:bg-surface-alt", children: "\u203A" })] }), _jsx("h1", { className: "hidden md:block text-lg font-bold tracking-tight", children: title }), _jsx("div", { className: "ml-auto flex flex-wrap gap-1", children: VIEW_TYPES.map((v) => (_jsx(ViewButton, { view: v, current: view, onSelect: setView }, v))) })] }), error && (_jsx("div", { className: "p-3", children: _jsx(Alert, { children: error }) })), loading ? (_jsx("p", { className: "p-4 text-sm text-text-muted", children: "Loading\u2026" })) : (_jsx(DndContext, { sensors: sensors, onDragEnd: handleDragEnd, children: view === "month" ? (_jsx(MonthView, { viewDate: viewDate, occurrences: occurrences, folderColors: folderColors, onSelectDay: handleSelectDay, onSelectEvent: openEvent })) : view === "split" ? (_jsx(SplitDayView, { day: viewDate, columns: splitColumns, occurrences: occurrences, onSelectEvent: openEvent, onSelectSlot: (start, end, targetFolderUid) => openNewEvent(start, end, targetFolderUid) })) : (_jsx(TimeGridView, { days: days, occurrences: occurrences, folderColors: folderColors, onSelectEvent: openEvent, onSelectSlot: openNewEvent })) })), modal && modalMailbox && (_jsx(EventModal, { open: true, onClose: closeModal, mailboxUid: modalMailbox.uid, folderUid: modalFolderUid, calendars: calendarFolders.filter((f) => f.mailboxUid === modalMailbox.uid).map((f) => ({ uid: f.uid, name: f.name })), mailboxOptions: mailboxOptions, organizerAddress: modalMailbox.primarySmtpAddress, organizerAliases: modalMailbox.aliasAddresses, occurrence: modal.occurrence, initialStart: modal.initialStart, initialEnd: modal.initialEnd, onSaved: handleSaved, onDeleted: handleDeleted }))] })] }));
|
|
278
277
|
}
|
|
279
278
|
export default routedPage("/calendar", CalendarPage);
|
|
@@ -13,6 +13,7 @@ import ContactDetailPane from "../../shared/components/contacts/ContactDetailPan
|
|
|
13
13
|
import ContactForm from "../../shared/components/contacts/ContactForm.js";
|
|
14
14
|
import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
|
|
15
15
|
import { clearPinnedSignerCache } from "../../shared/components/mail/pinnedSigners.js";
|
|
16
|
+
import { notifyApiError } from "../../shared/notifications/apiErrors.js";
|
|
16
17
|
/**
|
|
17
18
|
* Only reached on mobile (below the `md` breakpoint) — desktop's `apps/www/contacts/index.tsx` keeps its
|
|
18
19
|
* existing inline detail pane and never navigates here; see that file's `handleSelectRow`. There is no
|
|
@@ -27,9 +28,6 @@ function ContactDetailContent({ uid }) {
|
|
|
27
28
|
const [contact, setContact] = useState(null);
|
|
28
29
|
const [loading, setLoading] = useState(true);
|
|
29
30
|
const [error, setError] = useState(null);
|
|
30
|
-
// Kept apart from the load `error` - a failed delete must leave the contact on screen (with the
|
|
31
|
-
// reason) rather than replacing the whole page with an error as if it had never loaded.
|
|
32
|
-
const [deleteError, setDeleteError] = useState(null);
|
|
33
31
|
const [mode, setMode] = useState("view");
|
|
34
32
|
useEffect(() => {
|
|
35
33
|
setLoading(true);
|
|
@@ -44,7 +42,6 @@ function ContactDetailContent({ uid }) {
|
|
|
44
42
|
// rendered once `contact` is already resolved (see the `error || !contact` guard above it), so this
|
|
45
43
|
// never runs with a stale/absent contact — no redundant null check needed here.
|
|
46
44
|
async function handleDelete(contact) {
|
|
47
|
-
setDeleteError(null);
|
|
48
45
|
try {
|
|
49
46
|
await deleteContact(contact.uid, contact.version);
|
|
50
47
|
// A deleted contact's pinned signing keys must stop vouching for signatures.
|
|
@@ -52,7 +49,8 @@ function ContactDetailContent({ uid }) {
|
|
|
52
49
|
navigate("/contacts");
|
|
53
50
|
}
|
|
54
51
|
catch (err) {
|
|
55
|
-
|
|
52
|
+
// A pop-up: a failed delete leaves the contact on screen rather than replacing the page with an error.
|
|
53
|
+
notifyApiError(err, "Couldn't delete the contact");
|
|
56
54
|
}
|
|
57
55
|
}
|
|
58
56
|
// After a key change was resolved (or found stale): re-read the contact, keeping the one shown if that fails.
|
|
@@ -67,11 +65,11 @@ function ContactDetailContent({ uid }) {
|
|
|
67
65
|
}
|
|
68
66
|
const backHref = "/contacts";
|
|
69
67
|
if (mode === "edit") {
|
|
70
|
-
return (_jsxs("div", {
|
|
68
|
+
return (_jsxs("div", { children: [_jsx("a", { href: backHref, className: "text-sm text-primary-dark hover:underline block px-6 pt-6", children: "\u2190 Back to contacts" }), _jsx(ContactForm, { contact: contact, onSaved: (saved) => {
|
|
71
69
|
setContact(saved);
|
|
72
70
|
setMode("view");
|
|
73
71
|
}, onCancel: () => setMode("view") })] }));
|
|
74
72
|
}
|
|
75
|
-
return (
|
|
73
|
+
return (_jsx("div", { className: "p-6", children: _jsx(ContactDetailPane, { contact: contact, onEdit: () => setMode("edit"), onDelete: () => handleDelete(contact), backHref: backHref, onKeysChanged: handleKeysChanged }) }));
|
|
76
74
|
}
|
|
77
75
|
export default routedPage("/contacts/:uid", ContactDetailPage);
|
|
@@ -26,6 +26,7 @@ import { LIST_PAGE_SIZE, MAX_LIST_PAGES, listAllPages } from "../../shared/mail/
|
|
|
26
26
|
import { clearPinnedSignerCache } from "../../shared/components/mail/pinnedSigners.js";
|
|
27
27
|
import { SHORTCUTS } from "../../shared/keyboard/keymap.js";
|
|
28
28
|
import { useShortcut } from "../../shared/keyboard/useShortcut.js";
|
|
29
|
+
import { notifyApiError } from "../../shared/notifications/apiErrors.js";
|
|
29
30
|
const INPUT_CLASS = "w-full text-sm py-2 px-3 border border-border rounded-sm bg-surface text-text focus:outline-none focus:border-primary";
|
|
30
31
|
function ContactsPage(props) {
|
|
31
32
|
return (_jsx(ContactsShell, { ...props, children: _jsx(ContactsContent, { userUid: props.userUid }) }));
|
|
@@ -75,10 +76,9 @@ function ContactsContent({ userUid }) {
|
|
|
75
76
|
// Set after a contact is created in a different mailbox than the one this list shows.
|
|
76
77
|
const [savedElsewhere, setSavedElsewhere] = useState(null);
|
|
77
78
|
const [confirmingBulkDelete, setConfirmingBulkDelete] = useState(false);
|
|
78
|
-
/** Returns a promise so bulk actions (below) can wait for the refreshed list
|
|
79
|
-
*
|
|
80
|
-
* which
|
|
81
|
-
* ever saw it, since every bulk handler calls this right after its own error-setting loop. */
|
|
79
|
+
/** Returns a promise so bulk actions (below) can wait for the refreshed list. `error` is only this list's own
|
|
80
|
+
* load failure (cleared first, for a fresh fetch attempt); a failed action is a pop-up instead
|
|
81
|
+
* (`notifyApiError()`), which a reload doesn't touch. */
|
|
82
82
|
function reload() {
|
|
83
83
|
if (!folderUid) {
|
|
84
84
|
setContacts([]);
|
|
@@ -255,24 +255,23 @@ function ContactsContent({ userUid }) {
|
|
|
255
255
|
void reload();
|
|
256
256
|
}
|
|
257
257
|
catch (err) {
|
|
258
|
-
|
|
258
|
+
notifyApiError(err, "Couldn't delete the contact");
|
|
259
259
|
}
|
|
260
260
|
}
|
|
261
|
+
// The bulk handlers below try every contact and report each failure: the same failure repeated is one pop-up with a count.
|
|
261
262
|
async function handleBulkDelete() {
|
|
262
263
|
setConfirmingBulkDelete(false);
|
|
263
|
-
let bulkError = null;
|
|
264
264
|
for (const contact of checkedContacts) {
|
|
265
265
|
try {
|
|
266
266
|
await deleteContact(contact.uid, contact.version);
|
|
267
267
|
}
|
|
268
268
|
catch (err) {
|
|
269
|
-
|
|
269
|
+
notifyApiError(err, "Couldn't delete some of the contacts");
|
|
270
270
|
}
|
|
271
271
|
}
|
|
272
272
|
clearPinnedSignerCache();
|
|
273
273
|
setCheckedUids(new Set());
|
|
274
274
|
await reload();
|
|
275
|
-
setError(bulkError);
|
|
276
275
|
}
|
|
277
276
|
// `ContactsToolbar`'s Edit button — the only caller — is itself `disabled` unless exactly one contact
|
|
278
277
|
// is checked, so `checkedContacts[0]` is guaranteed to exist whenever this actually runs; no length
|
|
@@ -290,25 +289,22 @@ function ContactsContent({ userUid }) {
|
|
|
290
289
|
}
|
|
291
290
|
async function handleToggleFavorite() {
|
|
292
291
|
const allFavorited = checkedContacts.every((c) => c.favorite);
|
|
293
|
-
let bulkError = null;
|
|
294
292
|
for (const contact of checkedContacts) {
|
|
295
293
|
try {
|
|
296
294
|
await setContactFavorite(contact, !allFavorited);
|
|
297
295
|
}
|
|
298
296
|
catch (err) {
|
|
299
|
-
|
|
297
|
+
notifyApiError(err, "Couldn't update some of the contacts");
|
|
300
298
|
}
|
|
301
299
|
}
|
|
302
300
|
clearPinnedSignerCache();
|
|
303
301
|
await reload();
|
|
304
|
-
setError(bulkError);
|
|
305
302
|
}
|
|
306
303
|
async function handleAddCategory() {
|
|
307
304
|
const category = window.prompt("Category name");
|
|
308
305
|
if (!category?.trim()) {
|
|
309
306
|
return;
|
|
310
307
|
}
|
|
311
|
-
let bulkError = null;
|
|
312
308
|
for (const contact of checkedContacts) {
|
|
313
309
|
const categories = Array.from(new Set([...(contact.categories ?? []), category.trim()]));
|
|
314
310
|
try {
|
|
@@ -317,12 +313,11 @@ function ContactsContent({ userUid }) {
|
|
|
317
313
|
await updateContact({ uid: contact.uid, version: contact.version, categories });
|
|
318
314
|
}
|
|
319
315
|
catch (err) {
|
|
320
|
-
|
|
316
|
+
notifyApiError(err, "Couldn't update some of the contacts");
|
|
321
317
|
}
|
|
322
318
|
}
|
|
323
319
|
clearPinnedSignerCache();
|
|
324
320
|
await reload();
|
|
325
|
-
setError(bulkError);
|
|
326
321
|
}
|
|
327
322
|
function handleExportVCard() {
|
|
328
323
|
downloadTextFile(checkedContacts.length === 1 ? `${checkedContacts[0].displayName}.vcf` : "contacts.vcf", checkedContacts.length === 1 ? contactToVCard(checkedContacts[0]) : contactsToVCardFile(checkedContacts));
|
|
@@ -333,22 +328,20 @@ function ContactsContent({ userUid }) {
|
|
|
333
328
|
}
|
|
334
329
|
const text = await file.text();
|
|
335
330
|
const parsed = parseVCards(text);
|
|
336
|
-
let bulkError = null;
|
|
337
331
|
for (const input of parsed) {
|
|
338
332
|
try {
|
|
339
333
|
await createContact({ mailboxUid, folderUid, ...input });
|
|
340
334
|
}
|
|
341
335
|
catch (err) {
|
|
342
|
-
|
|
336
|
+
notifyApiError(err, "Couldn't import some of the contacts");
|
|
343
337
|
}
|
|
344
338
|
}
|
|
345
339
|
clearPinnedSignerCache();
|
|
346
340
|
await reload();
|
|
347
|
-
setError(bulkError);
|
|
348
341
|
}
|
|
349
|
-
return (_jsxs("div", { className: "flex-1
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
342
|
+
return (_jsxs("div", { className: "flex-1 min-w-0 min-h-0 flex flex-col lg:flex-row md:flex-none md:h-[calc(100dvh_-_var(--rr-header-h,4rem))] md:overflow-hidden", children: [_jsx(ContactsSidebar, { mailboxUid: mailboxUid, contacts: contacts, active: view, onSelect: handleSelectView, showDeleted: canViewDeleted }), _jsxs("div", { className: "flex-1 min-w-0 min-h-0 flex", children: [_jsxs("div", { className: [
|
|
343
|
+
"w-full md:w-[clamp(16rem,32vw,26rem)] min-w-0 md:shrink-0 md:border-r border-border md:flex flex-col min-h-0",
|
|
344
|
+
mode === "new" ? "hidden md:flex" : "flex",
|
|
345
|
+
].join(" "), children: [_jsx(ContactsToolbar, { selectedCount: checkedContacts.length, allSelectedFavorited: checkedContacts.length > 0 && checkedContacts.every((c) => c.favorite), onNewContact: handleNew, onEdit: handleToolbarEdit, onDelete: () => setConfirmingBulkDelete(true), onEmail: handleEmail, onToggleFavorite: handleToggleFavorite, onAddCategory: handleAddCategory, onExportVCard: handleExportVCard, onImportFile: handleImportFile, shortcuts: true }), _jsx("div", { className: "p-3 border-b border-border", children: _jsx("input", { ref: searchInputRef, type: "search", value: query, onChange: (e) => setQuery(e.target.value), placeholder: "Search contacts", "aria-label": "Search contacts", className: INPUT_CLASS }) }), error && (_jsx("div", { className: "p-3", children: _jsx(Alert, { children: error }) })), (isDeletedView ? deletedTruncated : truncated) && (_jsx("div", { className: "p-3", children: _jsxs(Alert, { children: ["This folder has more contacts than can be shown at once - only the first ", LIST_PAGE_SIZE * MAX_LIST_PAGES, " are listed."] }) })), deletedError && isDeletedView && (_jsx("div", { className: "p-3", children: _jsx(Alert, { children: deletedError }) })), (isDeletedView ? deletedLoading : loading) ? (_jsx("p", { className: "p-4 text-sm text-text-muted", children: "Loading\u2026" })) : sorted.length === 0 ? (_jsx("p", { className: "p-4 text-sm text-text-muted", children: "No contacts found." })) : (_jsx("div", { className: "flex-1 min-h-0 overflow-auto", children: _jsxs("table", { className: "w-full table-fixed text-sm", children: [_jsx("thead", { children: _jsxs("tr", { className: "border-b border-border text-left", children: [!isDeletedView && (_jsx("th", { className: "w-10 px-3 py-2", children: _jsx("input", { type: "checkbox", "aria-label": "Select all contacts", checked: allChecked, onChange: toggleCheckAll }) })), _jsx("th", { className: "w-[58%] px-3 py-2", children: _jsxs("button", { type: "button", onClick: () => handleSort("name"), className: "block max-w-full truncate text-xs font-bold uppercase tracking-wide text-text-muted", children: ["Name", sortColumn === "name" ? (sortDesc ? " ▾" : " ▴") : ""] }) }), _jsx("th", { className: "px-3 py-2", children: _jsxs("button", { type: "button", onClick: () => handleSort("info"), className: "block max-w-full truncate text-xs font-bold uppercase tracking-wide text-text-muted", children: ["Contact info", sortColumn === "info" ? (sortDesc ? " ▾" : " ▴") : ""] }) })] }) }), _jsx("tbody", { children: sorted.map((contact) => (_jsxs("tr", { className: ["border-b border-border", contact.uid === selectedUid ? "bg-primary/10" : "hover:bg-surface-alt"].join(" "), children: [!isDeletedView && (_jsx("td", { className: "px-3 py-2", children: _jsx("input", { type: "checkbox", "aria-label": `Select ${contact.displayName}`, checked: checkedUids.has(contact.uid), onChange: () => toggleChecked(contact.uid) }) })), _jsx("td", { className: "px-3 py-2", children: _jsxs("button", { type: "button", onClick: () => handleSelectRow(contact), className: "flex w-full min-w-0 items-center gap-2 text-left", children: [_jsx(ContactAvatar, { displayName: contact.displayName, size: 28 }), _jsxs("span", { className: "min-w-0 truncate font-medium", children: [contact.displayName, contact.favorite && _jsx("span", { "aria-label": "Favorite", children: " \u2605" })] })] }) }), _jsx("td", { className: "max-w-0 truncate px-3 py-2 text-text-muted", children: primaryInfo(contact) })] }, contact.uid))) })] }) }))] }), _jsxs("div", { className: ["flex-1 min-w-0 min-h-0 flex-col md:flex", mode === "new" ? "flex" : "hidden"].join(" "), children: [savedElsewhere && mode !== "new" && (_jsxs("p", { role: "status", className: "m-6 mb-0 text-sm py-2 px-3 rounded-sm bg-surface-alt text-text", children: [savedElsewhere.displayName, " was added to", " ", mailboxes.find((mb) => mb.uid === savedElsewhere.mailboxUid)?.displayName ?? "another mailbox", ".", " ", _jsx("a", { href: `/contacts?mailboxUid=${encodeURIComponent(savedElsewhere.mailboxUid)}`, className: "font-medium text-primary-dark hover:underline", children: "View that mailbox\u2019s contacts" })] })), mode === "new" && mailboxUid && folderUid ? (_jsx(ContactForm, { mailboxUid: mailboxUid, folderUid: folderUid, mailboxes: writableMailboxes, onSaved: handleSaved, onCancel: handleCancel })) : mode === "edit" && selected ? (_jsx(ContactForm, { contact: selected, onSaved: handleSaved, onCancel: handleCancel })) : selected ? (_jsx("div", { className: "min-h-0 flex-1 overflow-y-auto p-6", children: _jsx(ContactDetailPane, { contact: selected, onEdit: () => setMode("edit"), onDelete: () => handleDelete(selected), onKeysChanged: () => void reload(), canResolveKeys: ownsMailbox || delegateCanUpdate }) })) : (_jsx("div", { className: "flex flex-1 items-center justify-center p-6 text-center", children: _jsx("p", { className: "text-sm text-text-muted", children: "Select a contact, or create a new one." }) }))] })] }), _jsxs(Modal, { open: confirmingBulkDelete, onClose: () => setConfirmingBulkDelete(false), title: "Delete contacts", children: [_jsxs("p", { className: "text-sm mb-5", children: ["Delete ", checkedContacts.length, " selected ", checkedContacts.length === 1 ? "contact" : "contacts", "? They move to Deleted contacts."] }), _jsxs("div", { className: "flex gap-3 justify-end", children: [_jsx(Button, { type: "button", variant: "secondary", className: "!w-auto", onClick: () => setConfirmingBulkDelete(false), children: "Cancel" }), _jsx(Button, { type: "button", className: "!w-auto !bg-none !bg-danger !border-danger hover:!bg-danger", onClick: handleBulkDelete, children: "Delete" })] })] })] }));
|
|
353
346
|
}
|
|
354
347
|
export default routedPage("/contacts", ContactsPage);
|