@rapidmx/web-client 0.9.0 → 0.11.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 +373 -88
- 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/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/auth/accountUrl.ts +9 -0
- package/apps/shared/auth/adminAccess.ts +99 -0
- 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 +2 -1
- 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/calendar/layout/CalendarShell.tsx +8 -2
- 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 +98 -115
- package/apps/shared/components/contacts/layout/ContactsShell.tsx +25 -8
- package/apps/shared/components/escrow/layout/EscrowShell.tsx +13 -6
- package/apps/shared/components/layout/AppShell.tsx +477 -288
- package/apps/shared/components/layout/BrandingChrome.tsx +200 -13
- package/apps/shared/components/layout/KeyEnrollmentGate.tsx +398 -389
- package/apps/shared/components/layout/MailboxProvisioning.tsx +168 -164
- 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 +372 -185
- package/apps/shared/components/mail/ConversationList.tsx +271 -262
- package/apps/shared/components/mail/ConversationThreadPane.tsx +147 -94
- package/apps/shared/components/mail/LazyReadingPane.tsx +113 -0
- package/apps/shared/components/mail/MailSelectionBar.tsx +227 -222
- package/apps/shared/components/mail/MessageDetailPane.tsx +1537 -1387
- 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 +312 -160
- package/apps/shared/components/mail/compose/ComposeToolbar.tsx +422 -418
- package/apps/shared/components/mail/compose/ComposeWindow.tsx +1644 -1736
- package/apps/shared/components/mail/compose/ComposeWindowPlaceholder.tsx +111 -0
- package/apps/shared/components/mail/compose/RichTextEditor.tsx +147 -118
- package/apps/shared/components/mail/compose/composePerf.ts +46 -0
- package/apps/shared/components/mail/compose/encryptionRequirement.ts +86 -0
- package/apps/shared/components/mail/compose/quotedBody.ts +161 -100
- package/apps/shared/components/mail/layout/MailShell.tsx +490 -472
- 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/mail/unreadStyle.tsx +75 -0
- package/apps/shared/components/settings/SigningCertificateCard.tsx +344 -0
- package/apps/shared/components/settings/layout/SettingsShell.tsx +253 -240
- package/apps/shared/components/sharing/PrincipalPicker.tsx +145 -0
- package/apps/shared/components/tasks/TasksSidebar.tsx +5 -3
- package/apps/shared/components/tasks/layout/TasksShell.tsx +24 -8
- package/apps/shared/keyboard/GlobalShortcuts.tsx +51 -0
- package/apps/shared/keyboard/ShortcutProvider.tsx +62 -0
- package/apps/shared/keyboard/ShortcutsDialog.tsx +84 -0
- package/apps/shared/keyboard/dispatch.ts +124 -0
- package/apps/shared/keyboard/format.ts +89 -0
- package/apps/shared/keyboard/keymap.ts +114 -0
- package/apps/shared/keyboard/match.ts +44 -0
- package/apps/shared/keyboard/parse.ts +136 -0
- package/apps/shared/keyboard/platform.ts +34 -0
- package/apps/shared/keyboard/registry.ts +65 -0
- package/apps/shared/keyboard/targets.ts +79 -0
- package/apps/shared/keyboard/useShortcut.ts +50 -0
- package/apps/shared/keyboard/useShortcutProps.ts +17 -0
- package/apps/shared/mail/folderCounts.ts +308 -0
- package/apps/shared/mail/folderTree.ts +143 -0
- package/apps/shared/mail/listSnapshots.ts +87 -0
- package/apps/shared/mail/messageReadState.ts +85 -0
- package/apps/shared/mail/newMailNotifications.ts +183 -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 +203 -0
- package/apps/shared/mail/useMailLiveUpdates.ts +277 -224
- package/apps/shared/mail/useMarkMessageRead.ts +47 -0
- package/apps/shared/mail/useNewMailNotifications.ts +178 -0
- package/apps/shared/mail/useUnreadTitle.ts +42 -0
- package/apps/shared/navigation/AppRouter.tsx +300 -0
- package/apps/shared/navigation/appHrefs.ts +23 -0
- package/apps/shared/navigation/frameContext.tsx +35 -0
- package/apps/shared/navigation/idle.ts +45 -0
- package/apps/shared/navigation/routerContext.tsx +83 -0
- package/apps/shared/navigation/routes.ts +72 -0
- package/apps/shared/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/search/LocalIndexLifecycle.tsx +114 -98
- 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 +108 -10
- package/apps/www/_layout.tsx +8 -2
- package/apps/www/_routedPage.tsx +24 -0
- package/apps/www/_routes.ts +36 -0
- package/apps/www/calendar/index.tsx +64 -33
- package/apps/www/contacts/[uid].tsx +109 -107
- package/apps/www/contacts/index.tsx +62 -42
- package/apps/www/index.tsx +2291 -1917
- package/apps/www/messages/[uid].tsx +108 -101
- package/apps/www/settings/appearance/index.tsx +21 -0
- package/apps/www/settings/auto-reply/index.tsx +7 -8
- package/apps/www/settings/encryption/index.tsx +1287 -1249
- package/apps/www/settings/filters/[uid].tsx +7 -2
- package/apps/www/settings/filters/index.tsx +102 -99
- package/apps/www/settings/filters/new/index.tsx +140 -133
- package/apps/www/settings/labels/index.tsx +204 -201
- package/apps/www/settings/privacy/index.tsx +7 -9
- package/apps/www/settings/read-receipts/index.tsx +7 -8
- package/apps/www/settings/sharing/index.tsx +256 -274
- package/apps/www/settings/signatures/[uid].tsx +172 -167
- package/apps/www/settings/signatures/index.tsx +88 -85
- package/apps/www/settings/signatures/new/index.tsx +135 -129
- package/apps/www/tasks/index.tsx +29 -18
- 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/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/auth/accountUrl.d.ts +2 -0
- package/dist/apps/shared/auth/accountUrl.js +8 -0
- package/dist/apps/shared/auth/adminAccess.d.ts +30 -0
- package/dist/apps/shared/auth/adminAccess.js +89 -0
- package/dist/apps/shared/components/admin/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 +1 -1
- 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/calendar/layout/CalendarShell.d.ts +1 -1
- package/dist/apps/shared/components/calendar/layout/CalendarShell.js +8 -4
- 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 +6 -2
- package/dist/apps/shared/components/contacts/ContactsToolbar.js +28 -10
- package/dist/apps/shared/components/contacts/layout/ContactsShell.d.ts +1 -1
- package/dist/apps/shared/components/contacts/layout/ContactsShell.js +19 -6
- package/dist/apps/shared/components/escrow/layout/EscrowShell.js +7 -4
- package/dist/apps/shared/components/layout/AppShell.d.ts +34 -4
- package/dist/apps/shared/components/layout/AppShell.js +122 -17
- 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/KeyEnrollmentGate.js +10 -9
- package/dist/apps/shared/components/layout/MailboxProvisioning.js +3 -1
- 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 +25 -3
- package/dist/apps/shared/components/layout/UserMenu.js +88 -7
- package/dist/apps/shared/components/mail/ConversationList.js +5 -11
- package/dist/apps/shared/components/mail/ConversationThreadPane.d.ts +6 -2
- package/dist/apps/shared/components/mail/ConversationThreadPane.js +87 -40
- package/dist/apps/shared/components/mail/LazyReadingPane.d.ts +9 -0
- package/dist/apps/shared/components/mail/LazyReadingPane.js +83 -0
- package/dist/apps/shared/components/mail/MailSelectionBar.d.ts +6 -4
- package/dist/apps/shared/components/mail/MailSelectionBar.js +11 -4
- package/dist/apps/shared/components/mail/MessageDetailPane.d.ts +25 -8
- package/dist/apps/shared/components/mail/MessageDetailPane.js +167 -73
- 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 +38 -0
- package/dist/apps/shared/components/mail/compose/ComposeContext.js +97 -6
- package/dist/apps/shared/components/mail/compose/ComposeToolbar.js +4 -3
- package/dist/apps/shared/components/mail/compose/ComposeWindow.d.ts +3 -2
- package/dist/apps/shared/components/mail/compose/ComposeWindow.js +316 -339
- package/dist/apps/shared/components/mail/compose/ComposeWindowPlaceholder.d.ts +19 -0
- package/dist/apps/shared/components/mail/compose/ComposeWindowPlaceholder.js +33 -0
- package/dist/apps/shared/components/mail/compose/RichTextEditor.d.ts +9 -1
- package/dist/apps/shared/components/mail/compose/RichTextEditor.js +20 -2
- package/dist/apps/shared/components/mail/compose/composePerf.d.ts +16 -0
- package/dist/apps/shared/components/mail/compose/composePerf.js +41 -0
- 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/compose/quotedBody.d.ts +13 -0
- package/dist/apps/shared/components/mail/compose/quotedBody.js +67 -11
- package/dist/apps/shared/components/mail/layout/MailShell.d.ts +14 -4
- package/dist/apps/shared/components/mail/layout/MailShell.js +90 -104
- 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/mail/unreadStyle.d.ts +45 -0
- package/dist/apps/shared/components/mail/unreadStyle.js +61 -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.d.ts +1 -1
- package/dist/apps/shared/components/settings/layout/SettingsShell.js +16 -5
- package/dist/apps/shared/components/sharing/PrincipalPicker.d.ts +26 -0
- package/dist/apps/shared/components/sharing/PrincipalPicker.js +77 -0
- package/dist/apps/shared/components/tasks/TasksSidebar.js +4 -2
- package/dist/apps/shared/components/tasks/layout/TasksShell.d.ts +1 -1
- package/dist/apps/shared/components/tasks/layout/TasksShell.js +18 -6
- package/dist/apps/shared/keyboard/GlobalShortcuts.d.ts +14 -0
- package/dist/apps/shared/keyboard/GlobalShortcuts.js +37 -0
- package/dist/apps/shared/keyboard/ShortcutProvider.d.ts +20 -0
- package/dist/apps/shared/keyboard/ShortcutProvider.js +52 -0
- package/dist/apps/shared/keyboard/ShortcutsDialog.d.ts +14 -0
- package/dist/apps/shared/keyboard/ShortcutsDialog.js +42 -0
- package/dist/apps/shared/keyboard/dispatch.d.ts +16 -0
- package/dist/apps/shared/keyboard/dispatch.js +109 -0
- package/dist/apps/shared/keyboard/format.d.ts +12 -0
- package/dist/apps/shared/keyboard/format.js +74 -0
- package/dist/apps/shared/keyboard/keymap.d.ts +294 -0
- package/dist/apps/shared/keyboard/keymap.js +84 -0
- package/dist/apps/shared/keyboard/match.d.ts +20 -0
- package/dist/apps/shared/keyboard/match.js +31 -0
- package/dist/apps/shared/keyboard/parse.d.ts +31 -0
- package/dist/apps/shared/keyboard/parse.js +107 -0
- package/dist/apps/shared/keyboard/platform.d.ts +15 -0
- package/dist/apps/shared/keyboard/platform.js +22 -0
- package/dist/apps/shared/keyboard/registry.d.ts +39 -0
- package/dist/apps/shared/keyboard/registry.js +33 -0
- package/dist/apps/shared/keyboard/targets.d.ts +14 -0
- package/dist/apps/shared/keyboard/targets.js +67 -0
- package/dist/apps/shared/keyboard/useShortcut.d.ts +19 -0
- package/dist/apps/shared/keyboard/useShortcut.js +35 -0
- package/dist/apps/shared/keyboard/useShortcutProps.d.ts +10 -0
- package/dist/apps/shared/keyboard/useShortcutProps.js +15 -0
- package/dist/apps/shared/mail/folderCounts.d.ts +78 -0
- package/dist/apps/shared/mail/folderCounts.js +218 -0
- package/dist/apps/shared/mail/folderTree.d.ts +55 -0
- package/dist/apps/shared/mail/folderTree.js +116 -0
- package/dist/apps/shared/mail/listSnapshots.d.ts +46 -0
- package/dist/apps/shared/mail/listSnapshots.js +43 -0
- package/dist/apps/shared/mail/messageReadState.d.ts +32 -0
- package/dist/apps/shared/mail/messageReadState.js +63 -0
- package/dist/apps/shared/mail/newMailNotifications.d.ts +62 -0
- package/dist/apps/shared/mail/newMailNotifications.js +138 -0
- package/dist/apps/shared/mail/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 +56 -0
- package/dist/apps/shared/mail/useMailConnection.js +138 -0
- package/dist/apps/shared/mail/useMailLiveUpdates.d.ts +29 -9
- package/dist/apps/shared/mail/useMailLiveUpdates.js +60 -32
- package/dist/apps/shared/mail/useMarkMessageRead.d.ts +12 -0
- package/dist/apps/shared/mail/useMarkMessageRead.js +44 -0
- package/dist/apps/shared/mail/useNewMailNotifications.d.ts +35 -0
- package/dist/apps/shared/mail/useNewMailNotifications.js +130 -0
- package/dist/apps/shared/mail/useUnreadTitle.d.ts +18 -0
- package/dist/apps/shared/mail/useUnreadTitle.js +31 -0
- package/dist/apps/shared/navigation/AppRouter.d.ts +52 -0
- package/dist/apps/shared/navigation/AppRouter.js +242 -0
- package/dist/apps/shared/navigation/appHrefs.d.ts +14 -0
- package/dist/apps/shared/navigation/appHrefs.js +20 -0
- package/dist/apps/shared/navigation/frameContext.d.ts +19 -0
- package/dist/apps/shared/navigation/frameContext.js +24 -0
- package/dist/apps/shared/navigation/idle.d.ts +14 -0
- package/dist/apps/shared/navigation/idle.js +43 -0
- package/dist/apps/shared/navigation/routerContext.d.ts +37 -0
- package/dist/apps/shared/navigation/routerContext.js +56 -0
- package/dist/apps/shared/navigation/routes.d.ts +32 -0
- package/dist/apps/shared/navigation/routes.js +37 -0
- package/dist/apps/shared/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/search/LocalIndexLifecycle.js +17 -3
- 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 +108 -10
- package/dist/apps/www/_layout.d.ts +5 -1
- package/dist/apps/www/_layout.js +3 -2
- package/dist/apps/www/_routedPage.d.ts +12 -0
- package/dist/apps/www/_routedPage.js +19 -0
- package/dist/apps/www/_routes.d.ts +11 -0
- package/dist/apps/www/_routes.js +30 -0
- package/dist/apps/www/calendar/index.d.ts +2 -2
- package/dist/apps/www/calendar/index.js +41 -11
- package/dist/apps/www/contacts/[uid].d.ts +3 -9
- package/dist/apps/www/contacts/[uid].js +11 -9
- package/dist/apps/www/contacts/index.d.ts +2 -2
- package/dist/apps/www/contacts/index.js +30 -24
- package/dist/apps/www/index.d.ts +2 -2
- package/dist/apps/www/index.js +332 -48
- package/dist/apps/www/messages/[uid].d.ts +3 -7
- package/dist/apps/www/messages/[uid].js +10 -5
- 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.d.ts +2 -2
- package/dist/apps/www/settings/auto-reply/index.js +6 -7
- package/dist/apps/www/settings/encryption/index.d.ts +2 -2
- package/dist/apps/www/settings/encryption/index.js +106 -96
- package/dist/apps/www/settings/filters/[uid].d.ts +2 -2
- package/dist/apps/www/settings/filters/[uid].js +6 -2
- package/dist/apps/www/settings/filters/index.d.ts +2 -2
- package/dist/apps/www/settings/filters/index.js +3 -1
- package/dist/apps/www/settings/filters/new/index.d.ts +2 -2
- package/dist/apps/www/settings/filters/new/index.js +9 -3
- package/dist/apps/www/settings/labels/index.d.ts +2 -2
- package/dist/apps/www/settings/labels/index.js +3 -1
- package/dist/apps/www/settings/privacy/index.d.ts +2 -2
- package/dist/apps/www/settings/privacy/index.js +8 -9
- package/dist/apps/www/settings/read-receipts/index.d.ts +2 -2
- package/dist/apps/www/settings/read-receipts/index.js +6 -7
- package/dist/apps/www/settings/sharing/index.d.ts +2 -2
- package/dist/apps/www/settings/sharing/index.js +22 -34
- package/dist/apps/www/settings/signatures/[uid].d.ts +2 -2
- package/dist/apps/www/settings/signatures/[uid].js +6 -2
- package/dist/apps/www/settings/signatures/index.d.ts +2 -2
- package/dist/apps/www/settings/signatures/index.js +3 -1
- package/dist/apps/www/settings/signatures/new/index.d.ts +2 -2
- package/dist/apps/www/settings/signatures/new/index.js +9 -4
- package/dist/apps/www/tasks/index.d.ts +2 -2
- package/dist/apps/www/tasks/index.js +22 -16
- package/package.json +2 -2
- package/apps/shared/components/mail/compose/SendFailureAlert.tsx +0 -48
- package/dist/apps/shared/components/mail/compose/SendFailureAlert.d.ts +0 -14
- package/dist/apps/shared/components/mail/compose/SendFailureAlert.js +0 -11
|
@@ -1,11 +1,7 @@
|
|
|
1
|
-
import React from "react";
|
|
2
1
|
import { MailShellProps } from "../../shared/components/mail/layout/MailShell.js";
|
|
3
|
-
|
|
4
|
-
* Only reached on mobile (below the `md` breakpoint) — desktop's `apps/www/index.tsx` keeps its existing
|
|
5
|
-
* inline reading pane and never navigates here; see that file's `handleSelect`.
|
|
6
|
-
*/
|
|
7
|
-
export default function MessageDetailPage(props: MailShellProps & {
|
|
2
|
+
declare const _default: import("../../shared/navigation/AppRouter.js").RoutedPageComponent<MailShellProps & {
|
|
8
3
|
params: {
|
|
9
4
|
uid: string;
|
|
10
5
|
};
|
|
11
|
-
}
|
|
6
|
+
}>;
|
|
7
|
+
export default _default;
|
|
@@ -3,19 +3,22 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
3
3
|
// Copyright (C) 2026 Jean-Philippe Steinmetz
|
|
4
4
|
// SPDX-License-Identifier: MPL-2.0
|
|
5
5
|
///////////////////////////////////////////////////////////////////////////////
|
|
6
|
+
import { routedPage } from "../_routedPage.js";
|
|
6
7
|
import { useEffect, useState } from "react";
|
|
7
8
|
import { ApiRequestError } from "@rapidmx/react-shared/util/api.js";
|
|
8
9
|
import { getMessage } from "@rapidmx/react-shared/mail/mailApi.js";
|
|
9
10
|
import { listLabels } from "@rapidmx/react-shared/mail/labelsApi.js";
|
|
10
|
-
import {
|
|
11
|
+
import { useMessageAttachments } from "@rapidmx/react-shared/mail/mailDetailHooks.js";
|
|
12
|
+
import { useMarkMessageRead } from "../../shared/mail/useMarkMessageRead.js";
|
|
11
13
|
import MailShell, { useMailShell } from "../../shared/components/mail/layout/MailShell.js";
|
|
12
|
-
import
|
|
14
|
+
import { LazyMessageDetailPane } from "../../shared/components/mail/LazyReadingPane.js";
|
|
15
|
+
import { ReadingPaneSkeleton } from "../../shared/components/mail/reading/MessageCard.js";
|
|
13
16
|
import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
|
|
14
17
|
/**
|
|
15
18
|
* Only reached on mobile (below the `md` breakpoint) — desktop's `apps/www/index.tsx` keeps its existing
|
|
16
19
|
* inline reading pane and never navigates here; see that file's `handleSelect`.
|
|
17
20
|
*/
|
|
18
|
-
|
|
21
|
+
function MessageDetailPage(props) {
|
|
19
22
|
return (_jsx(MailShell, { ...props, children: _jsx(MessageDetailContent, { uid: props.params.uid }) }));
|
|
20
23
|
}
|
|
21
24
|
function MessageDetailContent({ uid }) {
|
|
@@ -55,7 +58,8 @@ function MessageDetailContent({ uid }) {
|
|
|
55
58
|
const attachments = useMessageAttachments(message);
|
|
56
59
|
useMarkMessageRead(message, setMessage);
|
|
57
60
|
if (loading) {
|
|
58
|
-
|
|
61
|
+
// The pane's own frame - a header card and a message card - rather than a line of text: what is coming is known, only not yet its contents.
|
|
62
|
+
return _jsx(ReadingPaneSkeleton, {});
|
|
59
63
|
}
|
|
60
64
|
if (error || !message) {
|
|
61
65
|
return _jsx(Alert, { children: error ?? "Message not found." });
|
|
@@ -68,5 +72,6 @@ function MessageDetailContent({ uid }) {
|
|
|
68
72
|
const isSentItems = folders.find((f) => f.uid === message.folderUid)?.type === "sent_items";
|
|
69
73
|
const isOutbox = folders.find((f) => f.uid === message.folderUid)?.type === "outbox";
|
|
70
74
|
const draftsFolderUid = folders.find((f) => f.type === "drafts")?.uid;
|
|
71
|
-
return (_jsx(
|
|
75
|
+
return (_jsx(LazyMessageDetailPane, { shortcuts: true, message: message, attachments: attachments, backHref: backHref, isSentItems: isSentItems, onRecalled: setMessage, isOutbox: isOutbox, onReceiptHandled: setMessage, draftsFolderUid: draftsFolderUid, folders: folders, onMoved: setMessage, onFolderCreated: onFolderCreated, onScheduledSendCanceled: setMessage, onArchived: setMessage, labels: labels, onLabelsChanged: setMessage, onLabelCreated: (label) => setLabels((prev) => [...prev, label]) }));
|
|
72
76
|
}
|
|
77
|
+
export default routedPage("/messages/:uid", MessageDetailPage);
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { SettingsShellProps } from "../../../shared/components/settings/layout/SettingsShell.js";
|
|
2
|
+
export type SettingsAppearancePageProps = Omit<SettingsShellProps, "active">;
|
|
3
|
+
declare const _default: import("../../../shared/navigation/AppRouter.js").RoutedPageComponent<SettingsAppearancePageProps>;
|
|
4
|
+
export default _default;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
3
|
+
// Copyright (C) 2026 Jean-Philippe Steinmetz
|
|
4
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
5
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
6
|
+
import { routedPage } from "../../_routedPage.js";
|
|
7
|
+
import SettingsShell from "../../../shared/components/settings/layout/SettingsShell.js";
|
|
8
|
+
import AppearanceForm from "../../../shared/appearance/AppearanceForm.js";
|
|
9
|
+
/** Settings > Appearance: the colour scheme, theme colours and background. Not mailbox-scoped - it is the signed-in user's own. */
|
|
10
|
+
function SettingsAppearancePage(props) {
|
|
11
|
+
return (_jsx(SettingsShell, { ...props, active: "appearance", children: _jsx(AppearanceForm, {}) }));
|
|
12
|
+
}
|
|
13
|
+
export default routedPage("/settings/appearance", SettingsAppearancePage);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React from "react";
|
|
2
1
|
import { SettingsShellProps } from "../../../shared/components/settings/layout/SettingsShell.js";
|
|
3
2
|
export type SettingsAutoReplyPageProps = Omit<SettingsShellProps, "active">;
|
|
4
|
-
|
|
3
|
+
declare const _default: import("../../../shared/navigation/AppRouter.js").RoutedPageComponent<SettingsAutoReplyPageProps>;
|
|
4
|
+
export default _default;
|
|
@@ -3,15 +3,15 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
3
3
|
// Copyright (C) 2026 Jean-Philippe Steinmetz
|
|
4
4
|
// SPDX-License-Identifier: MPL-2.0
|
|
5
5
|
///////////////////////////////////////////////////////////////////////////////
|
|
6
|
+
import { routedPage } from "../../_routedPage.js";
|
|
6
7
|
import { useState } from "react";
|
|
7
|
-
import { ApiRequestError } from "@rapidmx/react-shared/util/api.js";
|
|
8
8
|
import { toDatetimeLocal } from "@rapidmx/react-shared/util/dateInput.js";
|
|
9
9
|
import { updateMailbox } from "@rapidmx/react-shared/mail/mailApi.js";
|
|
10
10
|
import SettingsShell, { useSettingsShell } from "../../../shared/components/settings/layout/SettingsShell.js";
|
|
11
|
-
import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
|
|
12
11
|
import Button from "@rapidmx/react-shared/components/buttons/Button.js";
|
|
12
|
+
import { notifyApiError } from "../../../shared/notifications/apiErrors.js";
|
|
13
13
|
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";
|
|
14
|
-
|
|
14
|
+
function SettingsAutoReplyPage(props) {
|
|
15
15
|
return (_jsx(SettingsShell, { ...props, active: "auto-reply", children: _jsx(AutoReplyContent, {}) }));
|
|
16
16
|
}
|
|
17
17
|
function AutoReplyContent() {
|
|
@@ -27,12 +27,10 @@ function AutoReplyContent() {
|
|
|
27
27
|
// `SettingsShell`'s `mailboxes` is fetched once - every save bumps the server's version, so a second
|
|
28
28
|
// save without a reload must send the version the previous save returned, not the stale original.
|
|
29
29
|
const [version, setVersion] = useState(mailbox.version);
|
|
30
|
-
const [error, setError] = useState(null);
|
|
31
30
|
const [saving, setSaving] = useState(false);
|
|
32
31
|
const [saved, setSaved] = useState(false);
|
|
33
32
|
async function handleSubmit(e) {
|
|
34
33
|
e.preventDefault();
|
|
35
|
-
setError(null);
|
|
36
34
|
setSaved(false);
|
|
37
35
|
setSaving(true);
|
|
38
36
|
try {
|
|
@@ -49,11 +47,12 @@ function AutoReplyContent() {
|
|
|
49
47
|
setSaved(true);
|
|
50
48
|
}
|
|
51
49
|
catch (err) {
|
|
52
|
-
|
|
50
|
+
notifyApiError(err, "Couldn't save the automatic reply settings");
|
|
53
51
|
}
|
|
54
52
|
finally {
|
|
55
53
|
setSaving(false);
|
|
56
54
|
}
|
|
57
55
|
}
|
|
58
|
-
return (_jsx("div", { className: "flex-1 min-w-0 overflow-y-auto p-6", children: _jsxs("div", { className: "max-w-xl", children: [_jsx("h1", { className: "text-lg font-bold tracking-tight mb-1", children: "Automatic Replies" }), _jsxs("p", { className: "text-sm text-text-muted mb-4", children: ["Send an automatic reply to anyone who emails ", mailbox.displayName, " while you\u2019re away. A calendar event with its own automatic reply enabled takes over for its own start/end window."] }),
|
|
56
|
+
return (_jsx("div", { className: "flex-1 min-w-0 overflow-y-auto p-6", children: _jsxs("div", { className: "max-w-xl", children: [_jsx("h1", { className: "text-lg font-bold tracking-tight mb-1", children: "Automatic Replies" }), _jsxs("p", { className: "text-sm text-text-muted mb-4", children: ["Send an automatic reply to anyone who emails ", mailbox.displayName, " while you\u2019re away. A calendar event with its own automatic reply enabled takes over for its own start/end window."] }), saved && _jsx("div", { className: "mb-4 text-sm text-success font-medium", children: "Saved." }), _jsxs("form", { onSubmit: handleSubmit, className: "flex flex-col gap-4", children: [_jsxs("label", { className: "flex items-center gap-2 text-sm font-medium", children: [_jsx("input", { type: "checkbox", checked: oofEnabled, onChange: (e) => setOofEnabled(e.target.checked) }), "Automatic replies are on"] }), oofEnabled && (_jsxs(_Fragment, { children: [_jsxs("label", { className: "flex flex-col gap-1.5 text-sm", children: [_jsx("span", { className: "font-semibold", children: "Message" }), _jsx("textarea", { "aria-label": "Automatic reply message", className: INPUT_CLASS, rows: 5, value: oofMessage, onChange: (e) => setOofMessage(e.target.value) })] }), _jsxs("div", { className: "grid grid-cols-2 gap-3", children: [_jsxs("label", { className: "flex flex-col gap-1.5 text-sm", children: [_jsx("span", { className: "font-semibold", children: "Start (optional)" }), _jsx("input", { type: "datetime-local", "aria-label": "Automatic reply start", className: INPUT_CLASS, value: oofStartTime, onChange: (e) => setOofStartTime(e.target.value) })] }), _jsxs("label", { className: "flex flex-col gap-1.5 text-sm", children: [_jsx("span", { className: "font-semibold", children: "End (optional)" }), _jsx("input", { type: "datetime-local", "aria-label": "Automatic reply end", className: INPUT_CLASS, value: oofEndTime, onChange: (e) => setOofEndTime(e.target.value) })] })] }), _jsx("p", { className: "text-xs text-text-muted -mt-2", children: "Leave both blank to stay on indefinitely until turned off." })] })), _jsx("div", { children: _jsx(Button, { type: "submit", loading: saving, disabled: saving, className: "!w-auto", children: "Save" }) })] })] }) }));
|
|
59
57
|
}
|
|
58
|
+
export default routedPage("/settings/auto-reply", SettingsAutoReplyPage);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import React from "react";
|
|
2
1
|
import { SettingsShellProps } from "../../../shared/components/settings/layout/SettingsShell.js";
|
|
3
2
|
/** Mirrors restapi's `BaseKeyVaultRoute` `MAX_MASTER_KEY_WRAPS` - `addMasterKeyWrap()` refuses a vault
|
|
4
3
|
* already holding this many wraps (escrow included). */
|
|
5
4
|
export declare const MAX_MASTER_KEY_WRAPS = 20;
|
|
6
5
|
export type SettingsEncryptionPageProps = Omit<SettingsShellProps, "active">;
|
|
7
|
-
|
|
6
|
+
declare const _default: import("../../../shared/navigation/AppRouter.js").RoutedPageComponent<SettingsEncryptionPageProps>;
|
|
7
|
+
export default _default;
|
|
@@ -3,9 +3,10 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
3
3
|
// Copyright (C) 2026 Jean-Philippe Steinmetz
|
|
4
4
|
// SPDX-License-Identifier: MPL-2.0
|
|
5
5
|
///////////////////////////////////////////////////////////////////////////////
|
|
6
|
+
import { routedPage } from "../../_routedPage.js";
|
|
6
7
|
import { useEffect, useState } from "react";
|
|
7
8
|
import { ApiRequestError } from "@rapidmx/react-shared/util/api.js";
|
|
8
|
-
import { addMasterKeyWrap, cancelSignEnrollment,
|
|
9
|
+
import { addMasterKeyWrap, cancelSignEnrollment, findActivePublicKey, getCurrentSignEnrollment, getEscrowInfo, getKeyVault, rekey, removeMasterKeyWrap, startSignEnrollment, } from "@rapidmx/react-shared/crypto/keyvaultApi.js";
|
|
9
10
|
import { ENCRYPTION_PRIVATE_KEY_AAD_PURPOSE, SIGNING_PRIVATE_KEY_AAD_PURPOSE, destroyUnlockedKeys, getUnlockedKeys, unlockWithPassword, } from "@rapidmx/react-shared/crypto/keySession.js";
|
|
10
11
|
import { IDLE_TIMEOUT_OPTIONS_MINUTES, getIdleTimeoutMinutes, setIdleTimeoutMinutes } from "@rapidmx/react-shared/crypto/idleTimeout.js";
|
|
11
12
|
import { LOCAL_INDEX_SIZE_OPTIONS, getDefaultLocalIndexByteBudget, getLocalIndexByteBudget, setLocalIndexByteBudget, } from "../../../shared/search/localIndexSizePreference.js";
|
|
@@ -18,13 +19,22 @@ import SettingsShell, { useSettingsShell } from "../../../shared/components/sett
|
|
|
18
19
|
import KeyEnrollmentGate from "../../../shared/components/layout/KeyEnrollmentGate.js";
|
|
19
20
|
import { useUnlockPrompt } from "../../../shared/components/layout/UnlockPromptProvider.js";
|
|
20
21
|
import { destroyLocalIndex } from "../../../shared/search/localIndexRpcClient.js";
|
|
22
|
+
import { notifyApiError } from "../../../shared/notifications/apiErrors.js";
|
|
21
23
|
import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
|
|
22
24
|
import Button from "@rapidmx/react-shared/components/buttons/Button.js";
|
|
23
25
|
import Modal from "@rapidmx/react-shared/components/overlays/Modal.js";
|
|
26
|
+
import SigningCertificateCard from "../../../shared/components/settings/SigningCertificateCard.js";
|
|
27
|
+
import { readStoredSignEnrollment, storeSignEnrollment } from "../../../shared/signing/enrollmentStorage.js";
|
|
28
|
+
import { checkEnrollmentNow, forgetEnrollment, recordEnrollmentResult, useEnrollmentSnapshot, watchCurrentEnrollment, watchEnrollment, } from "../../../shared/signing/enrollmentTracker.js";
|
|
29
|
+
import { STALE_REQUEST_TEXT, beforeRequestText, healthWarning, isInstalling, timeOf } from "../../../shared/signing/enrollmentView.js";
|
|
30
|
+
import { useSigningEnrollmentInfo } from "../../../shared/signing/signingInfo.js";
|
|
24
31
|
const MIN_PASSWORD_LENGTH = 8;
|
|
25
32
|
/** Mirrors restapi's `BaseKeyVaultRoute` `MAX_MASTER_KEY_WRAPS` - `addMasterKeyWrap()` refuses a vault
|
|
26
33
|
* already holding this many wraps (escrow included). */
|
|
27
34
|
export const MAX_MASTER_KEY_WRAPS = 20;
|
|
35
|
+
/** How often, and how many times, the page asks for the mailbox's keys while a certificate is issued but not installed yet. */
|
|
36
|
+
const INSTALL_WATCH_INTERVAL_MS = 15000;
|
|
37
|
+
const INSTALL_WATCH_ATTEMPTS = 40;
|
|
28
38
|
const KEYS_LOCKED_MESSAGE = "Your encryption keys were locked before this could finish. Unlock them and try again.";
|
|
29
39
|
/** Thrown inside `handleRotateKeys()` when the escrow wrap of the new master key can't be built - the rotation is
|
|
30
40
|
* then abandoned before `rekey()`. */
|
|
@@ -122,38 +132,6 @@ async function rewrapVaultPrivateKeys(mailboxUid, currentMasterKey, wrappedKeys)
|
|
|
122
132
|
}
|
|
123
133
|
}
|
|
124
134
|
}
|
|
125
|
-
// RFC 8823 ACME issuance is a real email round-trip with a public CA - "likely minutes," not seconds -
|
|
126
|
-
// so this polls infrequently rather than hammering the endpoint.
|
|
127
|
-
const SIGNING_ENROLLMENT_POLL_INTERVAL_MS = 15000;
|
|
128
|
-
/**
|
|
129
|
-
* A started signing enrollment's id, per mailbox. Kept only so a reload can ask the server
|
|
130
|
-
* (`checkSignEnrollmentStatus()`) whether that enrollment is still pending: a rotation while it is pending
|
|
131
|
-
* would strand the enrollment's already-submitted private key under a master key that no longer exists. The
|
|
132
|
-
* stored id is never trusted on its own - the server's answer decides the state, and restapi's own `rekey()`
|
|
133
|
-
* refuses (409) a rotation during an enrollment this browser never saw (e.g. one started on another device).
|
|
134
|
-
*/
|
|
135
|
-
const SIGN_ENROLLMENT_STORAGE_PREFIX = "rapidmx.signEnrollment.";
|
|
136
|
-
function readStoredSignEnrollment(mailboxUid) {
|
|
137
|
-
try {
|
|
138
|
-
return localStorage.getItem(SIGN_ENROLLMENT_STORAGE_PREFIX + mailboxUid);
|
|
139
|
-
}
|
|
140
|
-
catch {
|
|
141
|
-
return null;
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
function storeSignEnrollment(mailboxUid, enrollmentId) {
|
|
145
|
-
try {
|
|
146
|
-
if (enrollmentId) {
|
|
147
|
-
localStorage.setItem(SIGN_ENROLLMENT_STORAGE_PREFIX + mailboxUid, enrollmentId);
|
|
148
|
-
}
|
|
149
|
-
else {
|
|
150
|
-
localStorage.removeItem(SIGN_ENROLLMENT_STORAGE_PREFIX + mailboxUid);
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
catch {
|
|
154
|
-
// Storage blocked - restapi's own 409 on rekey still guards a rotation.
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
135
|
const ROTATION_CONFLICT_MESSAGE = "Your keys were not rotated because the server reported a conflicting change. If a signing certificate enrollment is still in progress for this mailbox (it may have been started on another device), wait for it to finish, then try again. If this mailbox is under escrow, its escrow protection may have changed meanwhile - reload this page and try again. Nothing was changed.";
|
|
158
136
|
const METHOD_LABELS = {
|
|
159
137
|
password: "Password",
|
|
@@ -170,7 +148,7 @@ function idleTimeoutLabel(minutes) {
|
|
|
170
148
|
}
|
|
171
149
|
return `${minutes} minutes`;
|
|
172
150
|
}
|
|
173
|
-
|
|
151
|
+
function SettingsEncryptionPage(props) {
|
|
174
152
|
return (_jsx(SettingsShell, { ...props, active: "encryption", children: _jsx(EncryptionGate, { userUid: props.userUid, impersonating: props.impersonating }) }));
|
|
175
153
|
}
|
|
176
154
|
/**
|
|
@@ -235,6 +213,12 @@ function EncryptionContent({ canManageKeys }) {
|
|
|
235
213
|
const [signingEnrollmentId, setSigningEnrollmentId] = useState(null);
|
|
236
214
|
const [signingError, setSigningError] = useState(null);
|
|
237
215
|
const rotationBlockedBySigning = signingStatus !== "idle";
|
|
216
|
+
// What is known of the mailbox's signing-certificate enrollment: kept current by the shared tracker (`signing/enrollmentTracker.ts`), which reads
|
|
217
|
+
// the server with backoff while this page is visible, stops when the enrollment ends, and goes on in the app frame when this page is left.
|
|
218
|
+
const enrollment = useEnrollmentSnapshot(mailboxUid);
|
|
219
|
+
const enrollmentResult = enrollment?.result ?? null;
|
|
220
|
+
// How this deployment issues certificates (a public CA by itself, or an administrator by hand): what the card and the request say follows it. Only the owner asks.
|
|
221
|
+
const signingInfo = useSigningEnrollmentInfo(canManageKeys);
|
|
238
222
|
const [cancelingEnrollment, setCancelingEnrollment] = useState(false);
|
|
239
223
|
const [cancelEnrollmentError, setCancelEnrollmentError] = useState(null);
|
|
240
224
|
// restapi's rekey() drops every old escrow wrap and requires a fresh one for the new master key (see
|
|
@@ -302,79 +286,80 @@ function EncryptionContent({ canManageKeys }) {
|
|
|
302
286
|
setWrappingEscrow(false);
|
|
303
287
|
}
|
|
304
288
|
}
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
if (result.status === "pending") {
|
|
308
|
-
setSigningEnrollmentId(enrollmentId);
|
|
309
|
-
setSigningStatus("pending");
|
|
310
|
-
return;
|
|
311
|
-
}
|
|
312
|
-
storeSignEnrollment(mailboxUid, null);
|
|
313
|
-
setSigningStatus("idle");
|
|
314
|
-
setSigningEnrollmentId(null);
|
|
315
|
-
if (result.status === "failed") {
|
|
316
|
-
setSigningError(result.error ?? "Signing certificate enrollment failed.");
|
|
317
|
-
return;
|
|
318
|
-
}
|
|
319
|
-
try {
|
|
320
|
-
const refreshed = await getMailbox(mailboxUid);
|
|
321
|
-
if (!isCancelled()) {
|
|
322
|
-
setRefreshedKeys(refreshed.keys ?? []);
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
catch {
|
|
326
|
-
// The new key shows on the next load - the enrollment itself is finished either way.
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
// Confirms an enrollment started before a reload with the server before rotation is offered again.
|
|
289
|
+
// Follows the enrollment this browser started (its stored id) - or, for the owner, the one the server names as the mailbox's current one (started
|
|
290
|
+
// on another device, or long ago), so a pending one blocks rotation and shows its progress here too.
|
|
330
291
|
useEffect(() => {
|
|
331
292
|
const storedId = readStoredSignEnrollment(mailboxUid);
|
|
332
|
-
if (!storedId) {
|
|
333
|
-
return;
|
|
334
|
-
}
|
|
335
293
|
let cancelled = false;
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
}
|
|
294
|
+
let release;
|
|
295
|
+
if (storedId) {
|
|
296
|
+
setSigningStatus("checking");
|
|
297
|
+
release = watchEnrollment(mailboxUid, storedId);
|
|
298
|
+
}
|
|
299
|
+
else if (canManageKeys) {
|
|
300
|
+
getCurrentSignEnrollment(mailboxUid)
|
|
301
|
+
.then((current) => {
|
|
302
|
+
if (!cancelled && current) {
|
|
303
|
+
if (current.status === "pending") {
|
|
304
|
+
storeSignEnrollment(mailboxUid, current.enrollmentId);
|
|
305
|
+
}
|
|
306
|
+
release = watchCurrentEnrollment(mailboxUid, current, current.status === "pending");
|
|
307
|
+
}
|
|
308
|
+
})
|
|
309
|
+
.catch(() => undefined);
|
|
310
|
+
}
|
|
353
311
|
return () => {
|
|
354
312
|
cancelled = true;
|
|
313
|
+
release?.();
|
|
355
314
|
};
|
|
356
|
-
}, [mailboxUid]);
|
|
315
|
+
}, [mailboxUid, canManageKeys]);
|
|
316
|
+
// What the tracker knows becomes this page's state: pending (or not heard from yet - assume it is still going, and let the polling keep asking) blocks
|
|
317
|
+
// rotation; an enrollment that ended, or that the server no longer knows, does not.
|
|
357
318
|
useEffect(() => {
|
|
358
|
-
if (
|
|
319
|
+
if (!enrollment) {
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
if (enrollmentResult?.status === "pending" || (!enrollmentResult && enrollment.offline)) {
|
|
323
|
+
setSigningEnrollmentId(enrollment.enrollmentId);
|
|
324
|
+
setSigningStatus("pending");
|
|
325
|
+
}
|
|
326
|
+
else if (enrollmentResult || enrollment.gone) {
|
|
327
|
+
setSigningEnrollmentId(null);
|
|
328
|
+
setSigningStatus("idle");
|
|
329
|
+
}
|
|
330
|
+
}, [enrollment?.enrollmentId, enrollmentResult?.status, enrollment?.gone, enrollment?.offline]);
|
|
331
|
+
// Once a certificate is issued the server has installed a new signing key that `mailbox.keys` (fetched once by the shell) never saw. The server installs it
|
|
332
|
+
// with a job a few minutes after the CA issues it (`installedAt`): until then the key is not there, so ask again every 15 s (for ten minutes at most).
|
|
333
|
+
const issuedUntil = timeOf(enrollmentResult?.notAfter);
|
|
334
|
+
const keyIsCurrent = !!activeSigningKey && (issuedUntil === undefined || issuedUntil <= activeSigningKey.notAfter + 60000);
|
|
335
|
+
useEffect(() => {
|
|
336
|
+
if (enrollmentResult?.status !== "issued" || keyIsCurrent) {
|
|
359
337
|
return;
|
|
360
338
|
}
|
|
361
339
|
let cancelled = false;
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
340
|
+
let timer;
|
|
341
|
+
let attempts = 0;
|
|
342
|
+
const refresh = () => {
|
|
343
|
+
getMailbox(mailboxUid)
|
|
344
|
+
.then((refreshed) => {
|
|
345
|
+
if (cancelled) {
|
|
346
|
+
return;
|
|
367
347
|
}
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
//
|
|
371
|
-
|
|
372
|
-
|
|
348
|
+
const keys = refreshed.keys ?? [];
|
|
349
|
+
setRefreshedKeys(keys);
|
|
350
|
+
// Not installed yet, and the server said it is on its way: look again.
|
|
351
|
+
if (isInstalling(enrollmentResult) && ++attempts < INSTALL_WATCH_ATTEMPTS && !findActivePublicKey(keys, "sign")) {
|
|
352
|
+
timer = setTimeout(refresh, INSTALL_WATCH_INTERVAL_MS);
|
|
353
|
+
}
|
|
354
|
+
})
|
|
355
|
+
.catch(() => undefined);
|
|
356
|
+
};
|
|
357
|
+
refresh();
|
|
373
358
|
return () => {
|
|
374
359
|
cancelled = true;
|
|
375
|
-
|
|
360
|
+
clearTimeout(timer);
|
|
376
361
|
};
|
|
377
|
-
}, [
|
|
362
|
+
}, [enrollmentResult?.status, enrollmentResult?.installedAt, enrollment?.enrollmentId, keyIsCurrent, mailboxUid]);
|
|
378
363
|
/** Cancels the pending enrollment (restapi's owner-only DELETE), which is what lets rotation run again. */
|
|
379
364
|
async function handleCancelEnrollment(enrollmentId) {
|
|
380
365
|
setCancelEnrollmentError(null);
|
|
@@ -386,18 +371,20 @@ function EncryptionContent({ canManageKeys }) {
|
|
|
386
371
|
}
|
|
387
372
|
else if (result.status === "failed") {
|
|
388
373
|
// Cancelled - the "failure" is the cancellation itself, not something to report.
|
|
374
|
+
forgetEnrollment(mailboxUid);
|
|
389
375
|
storeSignEnrollment(mailboxUid, null);
|
|
390
376
|
setSigningStatus("idle");
|
|
391
377
|
setSigningEnrollmentId(null);
|
|
392
378
|
}
|
|
393
379
|
else {
|
|
394
380
|
// Issued before the cancel landed: it finishes like any other issued enrollment.
|
|
395
|
-
|
|
381
|
+
recordEnrollmentResult(mailboxUid, enrollmentId, result);
|
|
396
382
|
}
|
|
397
383
|
}
|
|
398
384
|
catch (err) {
|
|
399
385
|
if (err instanceof ApiRequestError && err.status === 404) {
|
|
400
386
|
// The server no longer knows this enrollment, so nothing is pending.
|
|
387
|
+
forgetEnrollment(mailboxUid);
|
|
401
388
|
storeSignEnrollment(mailboxUid, null);
|
|
402
389
|
setSigningStatus("idle");
|
|
403
390
|
setSigningEnrollmentId(null);
|
|
@@ -423,6 +410,8 @@ function EncryptionContent({ canManageKeys }) {
|
|
|
423
410
|
wrappedKey: { ciphertext: wrappedKeySealed.ciphertext, nonce: wrappedKeySealed.nonce, algorithm: "AES-256-GCM" },
|
|
424
411
|
});
|
|
425
412
|
storeSignEnrollment(mailboxUid, enrollmentId);
|
|
413
|
+
// Followed from now on (and after this page is left): the server has just accepted it, so it is pending - no need to ask first.
|
|
414
|
+
watchEnrollment(mailboxUid, enrollmentId, { initial: { status: "pending" } });
|
|
426
415
|
setSigningEnrollmentId(enrollmentId);
|
|
427
416
|
setSigningStatus("pending");
|
|
428
417
|
}
|
|
@@ -471,7 +460,8 @@ function EncryptionContent({ canManageKeys }) {
|
|
|
471
460
|
await loadVault();
|
|
472
461
|
}
|
|
473
462
|
catch (err) {
|
|
474
|
-
|
|
463
|
+
// A pop-up (the confirmation is already closed); `actionError` keeps the guidance tied to the forms below.
|
|
464
|
+
notifyApiError(err, "Couldn't remove this unlock method");
|
|
475
465
|
}
|
|
476
466
|
finally {
|
|
477
467
|
setRemovingMethod(null);
|
|
@@ -763,8 +753,27 @@ function EncryptionContent({ canManageKeys }) {
|
|
|
763
753
|
return (_jsx("div", { className: "flex-1 min-w-0 overflow-y-auto p-6", children: _jsx("div", { className: "max-w-xl", children: _jsx(Alert, { children: rotationRelockReason ??
|
|
764
754
|
"Your encryption keys have been removed from this session. Reload the page (or open Mail again) to unlock them when you need to read or send encrypted mail." }) }) }));
|
|
765
755
|
}
|
|
756
|
+
// The "Digital signatures" section: a pending enrollment first (even while an older certificate is still active - it is a renewal); else the
|
|
757
|
+
// active certificate; else how the last enrollment ended (a failure, or a certificate that has since expired); else nothing has been requested.
|
|
758
|
+
const expiredSigningKey = displayedKeys
|
|
759
|
+
.filter((key) => key.useType === "sign" && !key.revokedAt && key.notAfter <= Date.now())
|
|
760
|
+
.sort((a, b) => b.notAfter - a.notAfter)[0];
|
|
761
|
+
// A certificate the CA has issued but the server has not installed yet counts as issued too (there is no key to show yet: the card says it is installing).
|
|
762
|
+
const installing = canManageKeys && isInstalling(enrollmentResult);
|
|
763
|
+
const certificateMode = signingStatus === "pending"
|
|
764
|
+
? "pending"
|
|
765
|
+
: activeSigningKey || installing
|
|
766
|
+
? canManageKeys
|
|
767
|
+
? "issued"
|
|
768
|
+
: null
|
|
769
|
+
: canManageKeys && enrollmentResult?.status === "failed"
|
|
770
|
+
? "failed"
|
|
771
|
+
: canManageKeys && expiredSigningKey
|
|
772
|
+
? "expired"
|
|
773
|
+
: null;
|
|
774
|
+
const certificateEnrollment = certificateMode === "issued" && enrollmentResult?.status !== "issued" ? null : certificateMode === "expired" ? null : enrollmentResult;
|
|
766
775
|
return (_jsx("div", { className: "flex-1 min-w-0 overflow-y-auto p-6", children: _jsxs("div", { className: "max-w-xl flex flex-col gap-6", children: [_jsxs("div", { children: [_jsx("h1", { className: "text-lg font-bold tracking-tight mb-1", children: "Encryption" }), _jsxs("p", { className: "text-sm text-text-muted", children: ["Manage how ", mailbox.displayName, " unlocks its encryption keys on this and other devices."] })] }), loadError && _jsx(Alert, { children: loadError }), actionError && _jsx(Alert, { children: actionError }), !canManageKeys && (_jsx("p", { className: "text-sm text-text-muted", children: "Only this mailbox\u2019s owner, signed in as themselves, can change its encryption keys and unlock methods." })), _jsxs("div", { children: [_jsx("h2", { className: "text-sm font-semibold mb-2", children: "Encryption keys" }), displayedKeys.length > 0 ? (_jsx("ul", { className: "flex flex-col gap-1 text-sm", children: displayedKeys.map((key) => (_jsxs("li", { className: "font-mono text-xs", children: [key.useType === "sign" ? "Signing" : "Encryption", " key: ", key.fingerprint, key.revokedAt &&
|
|
767
|
-
(key.revocationReason === "superseded" ? (_jsx("span", { className: "text-text-muted", children: " (superseded)" })) : (_jsx("span", { className: "text-danger", children: " (revoked)" })))] }, key.fingerprint))) })) : (_jsx("p", { className: "text-sm text-text-muted", children: "No keys enrolled yet." }))] }), _jsxs("div", { children: [_jsx("h2", { className: "text-sm font-semibold mb-2", children: "Digital signatures" }), activeSigningKey
|
|
776
|
+
(key.revocationReason === "superseded" ? (_jsx("span", { className: "text-text-muted", children: " (superseded)" })) : (_jsx("span", { className: "text-danger", children: " (revoked)" })))] }, key.fingerprint))) })) : (_jsx("p", { className: "text-sm text-text-muted", children: "No keys enrolled yet." }))] }), _jsxs("div", { children: [_jsx("h2", { className: "text-sm font-semibold mb-2", children: "Digital signatures" }), activeSigningKey && signingStatus !== "pending" && (_jsx("p", { className: ["text-sm text-text-muted", certificateMode ? "mb-3" : ""].join(" "), children: "Enabled \u2014 outgoing mail from this mailbox is signed with a publicly-trusted certificate." })), certificateMode && (_jsxs("div", { className: "flex flex-col gap-2", children: [certificateMode === "failed" && signingError && _jsx(Alert, { children: signingError }), _jsx(SigningCertificateCard, { mode: certificateMode, enrollment: certificateEnrollment, snapshot: enrollment, info: signingInfo, address: mailbox.primarySmtpAddress, keyNotAfter: certificateMode === "expired" ? expiredSigningKey?.notAfter : activeSigningKey?.notAfter, canRequest: canManageKeys, requesting: signingStatus === "enrolling", onCheck: canManageKeys ? () => void checkEnrollmentNow(mailboxUid) : undefined, onRequest: handleEnrollSigning })] })), !activeSigningKey && !certificateMode && !canManageKeys ? (_jsx("p", { className: "text-sm text-text-muted", children: "Not enabled." })) : activeSigningKey || certificateMode ? null : (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx("p", { className: "text-xs text-text-muted", children: "Lets recipients verify that mail from this mailbox is genuinely from you. Optional \u2014 encryption already works without it." }), beforeRequestText(signingInfo, mailbox.primarySmtpAddress) && (_jsx("p", { className: "text-xs text-text-muted", children: beforeRequestText(signingInfo, mailbox.primarySmtpAddress) })), healthWarning(signingInfo) && _jsx(Alert, { children: healthWarning(signingInfo) }), enrollment?.gone && _jsx(Alert, { children: STALE_REQUEST_TEXT }), signingError && _jsx(Alert, { children: signingError }), signingInfo?.backend !== "none" && (_jsx(Button, { type: "button", variant: "secondary", className: "!w-auto", loading: signingStatus === "enrolling", disabled: signingStatus !== "idle", onClick: handleEnrollSigning, children: "Enable digital signatures" }))] }))] }), mailbox.escrowScopeId && (_jsxs("div", { children: [_jsx("h2", { className: "text-sm font-semibold mb-2", children: "Escrow" }), escrowError && _jsx(Alert, { children: escrowError }), hasEscrowWrap ? (_jsx("p", { className: "text-sm text-text-muted", children: "This mailbox is under legal/compliance escrow \u2014 an authorized holder in your organization can recover its encrypted mail if needed. This does not weaken protection against anyone else." })) : (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsxs("p", { className: "text-xs text-text-muted", children: ["Your organization has assigned this mailbox to an escrow scope, but nothing has been protected yet \u2014 an authorized holder cannot recover this mailbox\u2019s encrypted mail until you complete this step.", " ", "This does not weaken protection against anyone else."] }), canManageKeys && (_jsx(Button, { type: "button", variant: "secondary", className: "!w-auto", loading: wrappingEscrow, disabled: wrappingEscrow, onClick: handleWrapEscrow, children: "Add escrow protection" }))] }))] })), _jsxs("div", { children: [_jsx("h2", { className: "text-sm font-semibold mb-2", children: "Unlock methods" }), _jsxs("p", { className: "text-xs text-text-muted mb-3", children: ["Removing a method here stops it from being usable to unlock this mailbox going forward, but it is ", _jsx("strong", { children: "not" }), " full revocation \u2014 anyone who already captured a wrapped copy and knows its secret could still use it. For real revocation (e.g. after a lost device), rotate your keys entirely below instead."] }), vault && vault.masterKeyWraps.length > 0 ? (_jsx("ul", { className: "flex flex-col gap-2", children: [...passwordWraps, ...recoveryWraps, ...otherWraps].map((wrap) => {
|
|
768
777
|
const key = `${wrap.method}:${wrap.methodId ?? ""}`;
|
|
769
778
|
return (_jsxs("li", { className: "flex items-center justify-between gap-3 text-sm py-1.5 px-3 bg-surface-alt rounded-sm", children: [_jsx("span", { children: METHOD_LABELS[wrap.method] ?? wrap.method }), wrap.method !== "escrow" &&
|
|
770
779
|
canManageKeys &&
|
|
@@ -773,3 +782,4 @@ function EncryptionContent({ canManageKeys }) {
|
|
|
773
782
|
? "Checking whether a signing certificate enrollment is still in progress..."
|
|
774
783
|
: "Rotation is unavailable while a signing certificate enrollment is in progress - rotating now would lose the key being enrolled. Try again once it finishes, or cancel the enrollment." })), signingStatus === "pending" && signingEnrollmentId && (_jsxs("div", { className: "flex flex-col gap-2", children: [cancelEnrollmentError && _jsx(Alert, { children: cancelEnrollmentError }), _jsx("div", { children: _jsx(Button, { type: "button", variant: "secondary", className: "!w-auto", loading: cancelingEnrollment, disabled: cancelingEnrollment, onClick: () => handleCancelEnrollment(signingEnrollmentId), children: "Cancel enrollment" }) })] })), _jsx("input", { type: "password", "aria-label": "New password for rotated keys", placeholder: "New password", className: "w-full text-sm border border-border rounded-sm py-1.5 px-2 bg-surface", value: rotationPassword, onChange: (e) => setRotationPassword(e.target.value), disabled: rotating || rotationBlockedBySigning, autoComplete: "new-password" }), _jsx("input", { type: "password", "aria-label": "Confirm new password for rotated keys", placeholder: "Confirm new password", className: "w-full text-sm border border-border rounded-sm py-1.5 px-2 bg-surface", value: rotationConfirmPassword, onChange: (e) => setRotationConfirmPassword(e.target.value), disabled: rotating || rotationBlockedBySigning, autoComplete: "new-password" }), _jsx("div", { children: _jsx(Button, { type: "submit", variant: "secondary", className: "!w-auto text-danger", loading: rotating, disabled: rotating || rotationBlockedBySigning, children: "Rotate keys now" }) })] })] })), _jsxs("div", { children: [_jsx("h2", { className: "text-sm font-semibold mb-2", children: "Session timeout" }), _jsx("p", { className: "text-xs text-text-muted mb-3", children: "Automatically destroys your unlocked keys on this device after this much time with no activity anywhere in the app - not just Mail or Settings. Applies the next time you open or reload the app." }), _jsx("select", { "aria-label": "Session timeout", className: "text-sm border border-border rounded-sm py-1.5 px-2 bg-surface", value: idleTimeoutMinutes, onChange: handleIdleTimeoutChange, children: IDLE_TIMEOUT_OPTIONS_MINUTES.map((minutes) => (_jsx("option", { value: minutes, children: idleTimeoutLabel(minutes) }, minutes))) })] }), _jsxs("div", { children: [_jsx("h2", { className: "text-sm font-semibold mb-2", children: "Local search index size" }), _jsxs("p", { className: "text-xs text-text-muted mb-3", children: ["How much decrypted mail this device keeps in a local encrypted search index, so recent search results and inbox previews work instantly without contacting the server every time. Defaults to ", localIndexDefaultByteBudgetLabel, " on this device. Applies the next time this mailbox\u2019s index rebuilds (e.g. the next time you unlock it) \u2014 lowering it doesn\u2019t delete anything already indexed elsewhere, it just narrows what this device keeps a local copy of."] }), _jsx("select", { "aria-label": "Local search index size", className: "text-sm border border-border rounded-sm py-1.5 px-2 bg-surface", value: localIndexByteBudget, onChange: handleLocalIndexByteBudgetChange, children: LOCAL_INDEX_SIZE_OPTIONS.map((option) => (_jsx("option", { value: option.bytes, children: option.label }, option.bytes))) })] }), _jsxs("div", { children: [_jsx("h2", { className: "text-sm font-semibold mb-2", children: "This session" }), _jsx("p", { className: "text-xs text-text-muted mb-3", children: "Removes your unlocked keys from this browser tab\u2019s memory right now, without affecting any other device. You\u2019ll be asked to unlock again the next time you read or send encrypted mail here." }), _jsx(Button, { type: "button", variant: "secondary", className: "!w-auto text-danger", onClick: handleDestroyKeysNow, children: "Destroy keys on this device now" })] })] }) }));
|
|
775
784
|
}
|
|
785
|
+
export default routedPage("/settings/encryption", SettingsEncryptionPage);
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import React from "react";
|
|
2
1
|
import { SettingsShellProps } from "../../../shared/components/settings/layout/SettingsShell.js";
|
|
3
2
|
export type MailFilterDetailPageProps = Omit<SettingsShellProps, "active"> & {
|
|
4
3
|
params: {
|
|
5
4
|
uid: string;
|
|
6
5
|
};
|
|
7
6
|
};
|
|
8
|
-
|
|
7
|
+
declare const _default: import("../../../shared/navigation/AppRouter.js").RoutedPageComponent<MailFilterDetailPageProps>;
|
|
8
|
+
export default _default;
|
|
@@ -3,6 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
// Copyright (C) 2026 Jean-Philippe Steinmetz
|
|
4
4
|
// SPDX-License-Identifier: MPL-2.0
|
|
5
5
|
///////////////////////////////////////////////////////////////////////////////
|
|
6
|
+
import { routedPage } from "../../_routedPage.js";
|
|
6
7
|
import { useEffect, useState } from "react";
|
|
7
8
|
import { ApiRequestError } from "@rapidmx/react-shared/util/api.js";
|
|
8
9
|
import { listFolders } from "@rapidmx/react-shared/mail/mailApi.js";
|
|
@@ -13,8 +14,9 @@ import { MAIL_FILTER_CONDITION_FIELDS, buildMailFilterActionTypes } from "./_mai
|
|
|
13
14
|
import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
|
|
14
15
|
import Button from "@rapidmx/react-shared/components/buttons/Button.js";
|
|
15
16
|
import FormField from "@rapidmx/react-shared/components/forms/FormField.js";
|
|
17
|
+
import { notifyApiError } from "../../../shared/notifications/apiErrors.js";
|
|
16
18
|
const INPUT_CLASS = "w-full text-sm py-2.5 px-3 border border-border rounded-sm bg-surface text-text focus:outline-none focus:border-primary";
|
|
17
|
-
|
|
19
|
+
function MailFilterDetailPage(props) {
|
|
18
20
|
return (_jsx(SettingsShell, { ...props, active: "filters", children: _jsx(MailFilterDetailContent, { uid: props.params.uid }) }));
|
|
19
21
|
}
|
|
20
22
|
function MailFilterDetailContent({ uid }) {
|
|
@@ -86,7 +88,8 @@ function MailFilterDetailContent({ uid }) {
|
|
|
86
88
|
setSaved(true);
|
|
87
89
|
}
|
|
88
90
|
catch (err) {
|
|
89
|
-
|
|
91
|
+
// A pop-up: `error` is only this form's own validation (and the load failure above).
|
|
92
|
+
notifyApiError(err, "Couldn't save the mail filter");
|
|
90
93
|
}
|
|
91
94
|
finally {
|
|
92
95
|
setSaving(false);
|
|
@@ -107,3 +110,4 @@ function MailFilterDetailContent({ uid }) {
|
|
|
107
110
|
}
|
|
108
111
|
return (_jsx("div", { className: "flex-1 min-w-0 overflow-y-auto p-6", children: _jsxs("div", { className: "max-w-3xl", children: [_jsx("a", { href: `/settings/filters?mailboxUid=${encodeURIComponent(mailboxUid)}`, className: "text-sm text-primary-dark hover:underline", children: "\u2190 All mail filters" }), _jsx("h1", { className: "text-lg font-bold tracking-tight mt-1 mb-5", children: original.name }), error && _jsx(Alert, { children: error }), saved && !error && _jsx("div", { className: "mb-4 text-sm text-success font-medium", children: "Saved." }), _jsxs("form", { onSubmit: handleSubmit, className: "flex flex-col gap-5", children: [_jsx("div", { className: "bg-surface border border-border rounded-md p-6", children: _jsx(FormField, { label: "Name", htmlFor: "name", children: _jsx("input", { id: "name", type: "text", className: INPUT_CLASS, value: name, onChange: (e) => setName(e.target.value) }) }) }), _jsx(RuleBuilder, { value: rule, onChange: setRule, conditionFields: MAIL_FILTER_CONDITION_FIELDS, actionTypes: buildMailFilterActionTypes(folders), showValidation: showValidation }), _jsx("div", { children: _jsx(Button, { type: "submit", loading: saving, disabled: saving, className: "!w-auto", children: "Save changes" }) })] })] }) }));
|
|
109
112
|
}
|
|
113
|
+
export default routedPage("/settings/filters/:uid", MailFilterDetailPage);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React from "react";
|
|
2
1
|
import { SettingsShellProps } from "../../../shared/components/settings/layout/SettingsShell.js";
|
|
3
2
|
export type SettingsFiltersPageProps = Omit<SettingsShellProps, "active">;
|
|
4
|
-
|
|
3
|
+
declare const _default: import("../../../shared/navigation/AppRouter.js").RoutedPageComponent<SettingsFiltersPageProps>;
|
|
4
|
+
export default _default;
|
|
@@ -3,13 +3,14 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
// Copyright (C) 2026 Jean-Philippe Steinmetz
|
|
4
4
|
// SPDX-License-Identifier: MPL-2.0
|
|
5
5
|
///////////////////////////////////////////////////////////////////////////////
|
|
6
|
+
import { routedPage } from "../../_routedPage.js";
|
|
6
7
|
import { useEffect, useState } from "react";
|
|
7
8
|
import { ApiRequestError } from "@rapidmx/react-shared/util/api.js";
|
|
8
9
|
import { listMailFilterRules } from "@rapidmx/react-shared/mail/mailFilterRulesApi.js";
|
|
9
10
|
import SettingsShell, { useSettingsShell } from "../../../shared/components/settings/layout/SettingsShell.js";
|
|
10
11
|
import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
|
|
11
12
|
import Button from "@rapidmx/react-shared/components/buttons/Button.js";
|
|
12
|
-
|
|
13
|
+
function SettingsFiltersPage(props) {
|
|
13
14
|
return (_jsx(SettingsShell, { ...props, active: "filters", children: _jsx(FiltersContent, {}) }));
|
|
14
15
|
}
|
|
15
16
|
function FiltersContent() {
|
|
@@ -28,3 +29,4 @@ function FiltersContent() {
|
|
|
28
29
|
const sorted = [...rules].sort((a, b) => a.sequence - b.sequence);
|
|
29
30
|
return (_jsx("div", { className: "flex-1 min-w-0 overflow-y-auto p-6", children: _jsxs("div", { className: "max-w-3xl", children: [_jsxs("div", { className: "flex items-center justify-between mb-5", children: [_jsx("h1", { className: "text-lg font-bold tracking-tight", children: "Mail Filters" }), _jsx("a", { href: `/settings/filters/new?mailboxUid=${encodeURIComponent(mailboxUid)}`, children: _jsx(Button, { type: "button", className: "!w-auto", children: "+ New filter" }) })] }), _jsx("p", { className: "text-sm text-text-muted mb-4", children: "Applied in order once a message is verdicted safe to deliver, before it reaches your Inbox." }), error && _jsx(Alert, { children: error }), loading ? (_jsx("p", { className: "text-sm text-text-muted", children: "Loading\u2026" })) : sorted.length === 0 ? (_jsx("p", { className: "text-sm text-text-muted", children: "No mail filters yet." })) : (_jsx("div", { className: "overflow-x-auto", children: _jsxs("table", { className: "w-full text-sm border-collapse", children: [_jsx("thead", { children: _jsx("tr", { children: ["Sequence", "Name", "Enabled", "Actions", ""].map((h) => (_jsx("th", { className: "text-left text-xs uppercase tracking-wide text-text-muted py-2 px-2.5 border-b border-border", children: h }, h))) }) }), _jsx("tbody", { children: sorted.map((rule) => (_jsxs("tr", { children: [_jsx("td", { className: "py-2.5 px-2.5 border-b border-border", children: rule.sequence }), _jsx("td", { className: "py-2.5 px-2.5 border-b border-border", children: rule.name }), _jsx("td", { className: "py-2.5 px-2.5 border-b border-border", children: rule.enabled ? "Yes" : "No" }), _jsx("td", { className: "py-2.5 px-2.5 border-b border-border", children: rule.actions.length }), _jsx("td", { className: "py-2.5 px-2.5 border-b border-border text-right", children: _jsx("a", { href: `/settings/filters/${encodeURIComponent(rule.uid)}?mailboxUid=${encodeURIComponent(mailboxUid)}`, className: "text-primary-dark hover:underline font-medium", children: "View" }) })] }, rule.uid))) })] }) }))] }) }));
|
|
30
31
|
}
|
|
32
|
+
export default routedPage("/settings/filters", SettingsFiltersPage);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React from "react";
|
|
2
1
|
import { SettingsShellProps } from "../../../../shared/components/settings/layout/SettingsShell.js";
|
|
3
2
|
export type NewMailFilterPageProps = Omit<SettingsShellProps, "active">;
|
|
4
|
-
|
|
3
|
+
declare const _default: import("../../../../shared/navigation/AppRouter.js").RoutedPageComponent<NewMailFilterPageProps>;
|
|
4
|
+
export default _default;
|