@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
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
2
|
+
// Copyright (C) 2026 Jean-Philippe Steinmetz
|
|
3
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
5
|
+
import { useEffect, useState } from "react";
|
|
6
|
+
import { getSigningEnrollmentInfo } from "@rapidmx/react-shared/crypto/signingProviderApi.js";
|
|
7
|
+
/** How long an answer is reused: the page asks once when it opens, and again after this if it is opened again. */
|
|
8
|
+
export const SIGNING_INFO_CACHE_MS = 5 * 60000;
|
|
9
|
+
let cached;
|
|
10
|
+
/** The deployment's signing-enrollment description, or `null` when it cannot be had (an older server with no such endpoint, offline,
|
|
11
|
+
* anything) - never rejects. One request at a time, reused for five minutes. */
|
|
12
|
+
export function fetchSigningEnrollmentInfo(now = Date.now()) {
|
|
13
|
+
if (cached && now - cached.at < SIGNING_INFO_CACHE_MS) {
|
|
14
|
+
return cached.promise;
|
|
15
|
+
}
|
|
16
|
+
const promise = getSigningEnrollmentInfo().catch(() => null);
|
|
17
|
+
cached = { at: now, promise };
|
|
18
|
+
// An answer that could not be had is not worth remembering: the next visit asks again.
|
|
19
|
+
void promise.then((info) => {
|
|
20
|
+
if (info === null && cached?.promise === promise) {
|
|
21
|
+
cached = undefined;
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
return promise;
|
|
25
|
+
}
|
|
26
|
+
/** Forgets the remembered answer: for tests, which share the module. */
|
|
27
|
+
export function resetSigningInfo() {
|
|
28
|
+
cached = undefined;
|
|
29
|
+
}
|
|
30
|
+
/** The deployment's description, once it is known: `undefined` while it is being asked for, `null` when there is none. Asks only while `enabled`. */
|
|
31
|
+
export function useSigningEnrollmentInfo(enabled) {
|
|
32
|
+
const [info, setInfo] = useState(undefined);
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
if (!enabled) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
let cancelled = false;
|
|
38
|
+
void fetchSigningEnrollmentInfo().then((answer) => {
|
|
39
|
+
if (!cancelled) {
|
|
40
|
+
setInfo(answer);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
return () => {
|
|
44
|
+
cancelled = true;
|
|
45
|
+
};
|
|
46
|
+
}, [enabled]);
|
|
47
|
+
return info;
|
|
48
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
2
|
+
// Copyright (C) 2026 Jean-Philippe Steinmetz
|
|
3
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
5
|
+
import { useEffect, useState } from "react";
|
|
6
|
+
/** The current time in epoch ms, re-read every `intervalMs` while `enabled` - for "3 min ago" texts and countdowns that keep themselves right. */
|
|
7
|
+
export function useNow(intervalMs, enabled = true) {
|
|
8
|
+
const [now, setNow] = useState(() => Date.now());
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
if (!enabled) {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
setNow(Date.now());
|
|
14
|
+
const timer = setInterval(() => setNow(Date.now()), intervalMs);
|
|
15
|
+
return () => clearInterval(timer);
|
|
16
|
+
}, [intervalMs, enabled]);
|
|
17
|
+
return now;
|
|
18
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -276,13 +276,111 @@ h5,
|
|
|
276
276
|
h6 {
|
|
277
277
|
font-family: var(--rr-font-display);
|
|
278
278
|
}
|
|
279
|
-
|
|
280
|
-
/* The compose editor's quoted original (`composeQuoting.ts`'s blockquote). TipTap keeps the element but not the
|
|
281
|
-
* inline style it was seeded with, and Tailwind's preflight resets the browser's own blockquote indent - without
|
|
282
|
-
* this the quote under a reply reads as if it were part of the new message. */
|
|
283
|
-
.tiptap blockquote {
|
|
284
|
-
margin: 0;
|
|
285
|
-
padding-left: 1em;
|
|
286
|
-
border-left: 2px solid var(--rr-color-border);
|
|
287
|
-
color: var(--rr-color-text-muted);
|
|
288
|
-
}
|
|
279
|
+
|
|
280
|
+
/* The compose editor's quoted original (`composeQuoting.ts`'s blockquote). TipTap keeps the element but not the
|
|
281
|
+
* inline style it was seeded with, and Tailwind's preflight resets the browser's own blockquote indent - without
|
|
282
|
+
* this the quote under a reply reads as if it were part of the new message. */
|
|
283
|
+
.tiptap blockquote {
|
|
284
|
+
margin: 0;
|
|
285
|
+
padding-left: 1em;
|
|
286
|
+
border-left: 2px solid var(--rr-color-border);
|
|
287
|
+
color: var(--rr-color-text-muted);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/* A new-mail pop-up (`NewMailToasts.tsx`) eases in from just above its place. Only for readers who have not asked their system
|
|
291
|
+
* for less motion: with `prefers-reduced-motion: reduce` it simply appears. */
|
|
292
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
293
|
+
.rr-toast-in {
|
|
294
|
+
animation: rr-toast-in 0.18s ease-out;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
@keyframes rr-toast-in {
|
|
298
|
+
from {
|
|
299
|
+
opacity: 0;
|
|
300
|
+
transform: translateY(-0.5rem);
|
|
301
|
+
}
|
|
302
|
+
to {
|
|
303
|
+
opacity: 1;
|
|
304
|
+
transform: none;
|
|
305
|
+
}
|
|
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
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { ComponentType } from "react";
|
|
2
|
+
import { RoutedPageComponent } from "../shared/navigation/AppRouter.js";
|
|
3
|
+
/**
|
|
4
|
+
* Makes a `www` page the entry of the client-side router: the page's default export is `routedPage("/its/route", Page)`.
|
|
5
|
+
* `@rapidrest/react` server-renders and hydrates exactly that component, so what it renders - the router, with the persistent
|
|
6
|
+
* app frame (`AppChrome`) and `Page` inside it - is the whole client tree, and later pages replace `Page` without a page load.
|
|
7
|
+
* `path` is the page's route template, the same as its entry in `_routes.ts`.
|
|
8
|
+
*
|
|
9
|
+
* The result carries the plain `Page` as `.page`, which is what the router shows when it loads this module for a navigation
|
|
10
|
+
* (rendering the wrapped default export there would nest a second router).
|
|
11
|
+
*/
|
|
12
|
+
export declare function routedPage<P extends object>(path: string, Page: ComponentType<P>): RoutedPageComponent<P>;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import AppRouter from "../shared/navigation/AppRouter.js";
|
|
3
|
+
import { wwwRoutes } from "./_routes.js";
|
|
4
|
+
/**
|
|
5
|
+
* Makes a `www` page the entry of the client-side router: the page's default export is `routedPage("/its/route", Page)`.
|
|
6
|
+
* `@rapidrest/react` server-renders and hydrates exactly that component, so what it renders - the router, with the persistent
|
|
7
|
+
* app frame (`AppChrome`) and `Page` inside it - is the whole client tree, and later pages replace `Page` without a page load.
|
|
8
|
+
* `path` is the page's route template, the same as its entry in `_routes.ts`.
|
|
9
|
+
*
|
|
10
|
+
* The result carries the plain `Page` as `.page`, which is what the router shows when it loads this module for a navigation
|
|
11
|
+
* (rendering the wrapped default export there would nest a second router).
|
|
12
|
+
*/
|
|
13
|
+
export function routedPage(path, Page) {
|
|
14
|
+
function RoutedPage(props) {
|
|
15
|
+
return _jsx(AppRouter, { routes: wwwRoutes, initialPath: path, initialPage: Page, pageProps: props });
|
|
16
|
+
}
|
|
17
|
+
RoutedPage.page = Page;
|
|
18
|
+
return RoutedPage;
|
|
19
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { RouteDefinition } from "../shared/navigation/routes.js";
|
|
2
|
+
/**
|
|
3
|
+
* Every `apps/www` page the client-side router can show without a page load (see `apps/shared/navigation/AppRouter.tsx`), in
|
|
4
|
+
* the order they are matched: literal routes before the parameterized ones that would also match them. Each page loads with a
|
|
5
|
+
* dynamic `import()`, which is what makes it a chunk of its own - only the page a document was rendered for is in its first
|
|
6
|
+
* JavaScript. Keep it in step with the files under `apps/www`; a page missing from here still works, it is just reached with a
|
|
7
|
+
* page load. (The file starts with `_` so `@rapidrest/react` doesn't treat it as a page.)
|
|
8
|
+
*
|
|
9
|
+
* The four app-rail pages are `idlePrefetch`ed: downloaded when the browser is idle after the first page has loaded.
|
|
10
|
+
*/
|
|
11
|
+
export declare const wwwRoutes: readonly RouteDefinition[];
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every `apps/www` page the client-side router can show without a page load (see `apps/shared/navigation/AppRouter.tsx`), in
|
|
3
|
+
* the order they are matched: literal routes before the parameterized ones that would also match them. Each page loads with a
|
|
4
|
+
* dynamic `import()`, which is what makes it a chunk of its own - only the page a document was rendered for is in its first
|
|
5
|
+
* JavaScript. Keep it in step with the files under `apps/www`; a page missing from here still works, it is just reached with a
|
|
6
|
+
* page load. (The file starts with `_` so `@rapidrest/react` doesn't treat it as a page.)
|
|
7
|
+
*
|
|
8
|
+
* The four app-rail pages are `idlePrefetch`ed: downloaded when the browser is idle after the first page has loaded.
|
|
9
|
+
*/
|
|
10
|
+
export const wwwRoutes = [
|
|
11
|
+
{ path: "/", active: "mail", load: () => import("./index.js"), idlePrefetch: true },
|
|
12
|
+
{ path: "/messages/:uid", active: "mail", load: () => import("./messages/[uid].js") },
|
|
13
|
+
{ path: "/calendar", active: "calendar", load: () => import("./calendar/index.js"), idlePrefetch: true },
|
|
14
|
+
{ path: "/contacts", active: "contacts", load: () => import("./contacts/index.js"), idlePrefetch: true },
|
|
15
|
+
{ path: "/contacts/:uid", active: "contacts", load: () => import("./contacts/[uid].js") },
|
|
16
|
+
{ path: "/tasks", active: "tasks", load: () => import("./tasks/index.js"), idlePrefetch: true },
|
|
17
|
+
{ path: "/settings/appearance", active: "settings", load: () => import("./settings/appearance/index.js") },
|
|
18
|
+
{ path: "/settings/auto-reply", active: "settings", load: () => import("./settings/auto-reply/index.js") },
|
|
19
|
+
{ path: "/settings/encryption", active: "settings", load: () => import("./settings/encryption/index.js") },
|
|
20
|
+
{ path: "/settings/filters", active: "settings", load: () => import("./settings/filters/index.js") },
|
|
21
|
+
{ path: "/settings/filters/new", active: "settings", load: () => import("./settings/filters/new/index.js") },
|
|
22
|
+
{ path: "/settings/filters/:uid", active: "settings", load: () => import("./settings/filters/[uid].js") },
|
|
23
|
+
{ path: "/settings/labels", active: "settings", load: () => import("./settings/labels/index.js") },
|
|
24
|
+
{ path: "/settings/privacy", active: "settings", load: () => import("./settings/privacy/index.js") },
|
|
25
|
+
{ path: "/settings/read-receipts", active: "settings", load: () => import("./settings/read-receipts/index.js") },
|
|
26
|
+
{ path: "/settings/sharing", active: "settings", load: () => import("./settings/sharing/index.js") },
|
|
27
|
+
{ path: "/settings/signatures", active: "settings", load: () => import("./settings/signatures/index.js") },
|
|
28
|
+
{ path: "/settings/signatures/new", active: "settings", load: () => import("./settings/signatures/new/index.js") },
|
|
29
|
+
{ path: "/settings/signatures/:uid", active: "settings", load: () => import("./settings/signatures/[uid].js") },
|
|
30
|
+
];
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import React from "react";
|
|
2
1
|
import { CalendarShellProps } from "../../shared/components/calendar/layout/CalendarShell.js";
|
|
3
|
-
|
|
2
|
+
declare const _default: import("../../shared/navigation/AppRouter.js").RoutedPageComponent<CalendarShellProps>;
|
|
3
|
+
export default _default;
|
|
@@ -3,6 +3,7 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } 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, useMemo, useRef, useState } from "react";
|
|
7
8
|
import { DndContext, MouseSensor, TouchSensor, useSensor, useSensors } from "@dnd-kit/core";
|
|
8
9
|
import { HiOutlineBars3 } from "react-icons/hi2";
|
|
@@ -24,11 +25,34 @@ import SplitDayView from "../../shared/components/calendar/SplitDayView.js";
|
|
|
24
25
|
import TimeGridView from "../../shared/components/calendar/TimeGridView.js";
|
|
25
26
|
import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
|
|
26
27
|
import Button from "@rapidmx/react-shared/components/buttons/Button.js";
|
|
27
|
-
|
|
28
|
+
import { SHORTCUTS } from "../../shared/keyboard/keymap.js";
|
|
29
|
+
import { useShortcut } from "../../shared/keyboard/useShortcut.js";
|
|
30
|
+
import { useShortcutProps } from "../../shared/keyboard/useShortcutProps.js";
|
|
31
|
+
import { notifyApiError } from "../../shared/notifications/apiErrors.js";
|
|
32
|
+
function CalendarPage(props) {
|
|
28
33
|
return (_jsx(CalendarShell, { ...props, children: _jsx(CalendarContent, { userUid: props.userUid }) }));
|
|
29
34
|
}
|
|
30
35
|
const VIEW_TYPES = ["month", "week", "workWeek", "day", "split"];
|
|
31
36
|
const VIEW_LABELS = { month: "Month", week: "Week", workWeek: "Work Week", day: "Day", split: "Split" };
|
|
37
|
+
/** The shortcut that switches to a view (Outlook's Ctrl+Alt+1-4); the split view has none. */
|
|
38
|
+
const VIEW_SHORTCUTS = {
|
|
39
|
+
day: SHORTCUTS.calendar.day,
|
|
40
|
+
workWeek: SHORTCUTS.calendar.workWeek,
|
|
41
|
+
week: SHORTCUTS.calendar.week,
|
|
42
|
+
month: SHORTCUTS.calendar.month,
|
|
43
|
+
};
|
|
44
|
+
/** One of the view switcher's buttons, with its shortcut in the tooltip. */
|
|
45
|
+
function ViewButton({ view, current, onSelect }) {
|
|
46
|
+
const shortcut = VIEW_SHORTCUTS[view];
|
|
47
|
+
const hint = useShortcutProps(VIEW_LABELS[view], shortcut ?? SHORTCUTS.calendar.day, !!shortcut);
|
|
48
|
+
return (_jsx("button", { type: "button", onClick: () => onSelect(view), ...hint, className: [
|
|
49
|
+
"px-3 h-7 text-sm rounded-sm whitespace-nowrap",
|
|
50
|
+
current === view ? "bg-primary text-white" : "hover:bg-surface-alt",
|
|
51
|
+
(view === "workWeek" || view === "split") && "hidden md:inline-block",
|
|
52
|
+
]
|
|
53
|
+
.filter(Boolean)
|
|
54
|
+
.join(" "), children: VIEW_LABELS[view] }));
|
|
55
|
+
}
|
|
32
56
|
function CalendarContent({ userUid }) {
|
|
33
57
|
const { mailboxUid, folderUid, calendarFolders, mailboxCalendars, mailboxes, reloadFolders, colorFor } = useCalendarShell();
|
|
34
58
|
// `PointerSensor` alone activates a drag on the very first touch-move, indistinguishable from a
|
|
@@ -43,7 +67,6 @@ function CalendarContent({ userUid }) {
|
|
|
43
67
|
const [events, setEvents] = useState([]);
|
|
44
68
|
const [loading, setLoading] = useState(true);
|
|
45
69
|
const [error, setError] = useState(null);
|
|
46
|
-
const [actionError, setActionError] = useState(null);
|
|
47
70
|
const [modal, setModal] = useState(null);
|
|
48
71
|
// `null` means "not yet customized by the user" — every calendar is treated as checked without
|
|
49
72
|
// needing to be seeded from `calendarFolders` the moment it loads (see `checkedFolderUids` below).
|
|
@@ -195,6 +218,19 @@ function CalendarContent({ userUid }) {
|
|
|
195
218
|
function closeModal() {
|
|
196
219
|
setModal(null);
|
|
197
220
|
}
|
|
221
|
+
// Keyboard shortcuts - the toolbar's own actions. (A dialog open - the event editor - silences them.)
|
|
222
|
+
useShortcut(SHORTCUTS.calendar.create, () => openNewEvent(), { enabled: !!mailboxUid && !!folderUid });
|
|
223
|
+
useShortcut(SHORTCUTS.calendar.today, goToday);
|
|
224
|
+
useShortcut(SHORTCUTS.calendar.previous, () => shiftView(-1));
|
|
225
|
+
useShortcut(SHORTCUTS.calendar.next, () => shiftView(1));
|
|
226
|
+
useShortcut(SHORTCUTS.calendar.day, () => setView("day"));
|
|
227
|
+
useShortcut(SHORTCUTS.calendar.workWeek, () => setView("workWeek"));
|
|
228
|
+
useShortcut(SHORTCUTS.calendar.week, () => setView("week"));
|
|
229
|
+
useShortcut(SHORTCUTS.calendar.month, () => setView("month"));
|
|
230
|
+
const newEventHint = useShortcutProps("New event", SHORTCUTS.calendar.create, !!mailboxUid && !!folderUid);
|
|
231
|
+
const previousHint = useShortcutProps("Previous", SHORTCUTS.calendar.previous);
|
|
232
|
+
const todayHint = useShortcutProps("Today", SHORTCUTS.calendar.today);
|
|
233
|
+
const nextHint = useShortcutProps("Next", SHORTCUTS.calendar.next);
|
|
198
234
|
function handleSaved() {
|
|
199
235
|
setModal(null);
|
|
200
236
|
reload();
|
|
@@ -209,7 +245,6 @@ function CalendarContent({ userUid }) {
|
|
|
209
245
|
if (!action) {
|
|
210
246
|
return;
|
|
211
247
|
}
|
|
212
|
-
setActionError(null);
|
|
213
248
|
try {
|
|
214
249
|
if (action.type === "move") {
|
|
215
250
|
await moveOccurrence(action.occurrence, action.deltaMs);
|
|
@@ -220,7 +255,7 @@ function CalendarContent({ userUid }) {
|
|
|
220
255
|
reload();
|
|
221
256
|
}
|
|
222
257
|
catch (err) {
|
|
223
|
-
|
|
258
|
+
notifyApiError(err, action.type === "move" ? "Couldn't move the event" : "Couldn't resize the event");
|
|
224
259
|
}
|
|
225
260
|
}
|
|
226
261
|
const title = view === "month"
|
|
@@ -238,11 +273,6 @@ function CalendarContent({ userUid }) {
|
|
|
238
273
|
.filter((mc) => mc.calendarFolders.length > 0 && writableMailboxes.some((mb) => mb.uid === mc.mailbox.uid))
|
|
239
274
|
.map((mc) => ({ mailbox: mc.mailbox, calendars: mc.calendarFolders.map((f) => ({ uid: f.uid, name: f.name })) })), [mailboxCalendars, writableMailboxes]);
|
|
240
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 })] }));
|
|
241
|
-
return (_jsxs("div", { className: "flex-1 flex min-h-0", children: [_jsx("div", { className: "hidden
|
|
242
|
-
"px-3 h-7 text-sm rounded-sm whitespace-nowrap",
|
|
243
|
-
view === v ? "bg-primary text-white" : "hover:bg-surface-alt",
|
|
244
|
-
(v === "workWeek" || v === "split") && "hidden md:inline-block",
|
|
245
|
-
]
|
|
246
|
-
.filter(Boolean)
|
|
247
|
-
.join(" "), children: VIEW_LABELS[v] }, v))) })] }), error && (_jsx("div", { className: "p-3", children: _jsx(Alert, { children: error }) })), actionError && (_jsx("div", { className: "p-3", children: _jsx(Alert, { children: actionError }) })), 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 }))] })] }));
|
|
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 }))] })] }));
|
|
248
277
|
}
|
|
278
|
+
export default routedPage("/calendar", CalendarPage);
|
|
@@ -1,13 +1,7 @@
|
|
|
1
|
-
import React from "react";
|
|
2
1
|
import { ContactsShellProps } from "../../shared/components/contacts/layout/ContactsShell.js";
|
|
3
|
-
|
|
4
|
-
* Only reached on mobile (below the `md` breakpoint) — desktop's `apps/www/contacts/index.tsx` keeps its
|
|
5
|
-
* existing inline detail pane and never navigates here; see that file's `handleSelectRow`. There is no
|
|
6
|
-
* equivalent "new contact" route — an unsaved contact has no uid to route on, so creation stays an
|
|
7
|
-
* in-place mode-switch on every device (see `apps/www/contacts/index.tsx`'s `handleNew`).
|
|
8
|
-
*/
|
|
9
|
-
export default function ContactDetailPage(props: ContactsShellProps & {
|
|
2
|
+
declare const _default: import("../../shared/navigation/AppRouter.js").RoutedPageComponent<ContactsShellProps & {
|
|
10
3
|
params: {
|
|
11
4
|
uid: string;
|
|
12
5
|
};
|
|
13
|
-
}
|
|
6
|
+
}>;
|
|
7
|
+
export default _default;
|
|
@@ -3,6 +3,8 @@ 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";
|
|
7
|
+
import { useNavigate } from "../../shared/navigation/AppRouter.js";
|
|
6
8
|
import { useEffect, useState } from "react";
|
|
7
9
|
import { ApiRequestError } from "@rapidmx/react-shared/util/api.js";
|
|
8
10
|
import { deleteContact, getContact } from "@rapidmx/react-shared/contacts/contactsApi.js";
|
|
@@ -11,22 +13,21 @@ import ContactDetailPane from "../../shared/components/contacts/ContactDetailPan
|
|
|
11
13
|
import ContactForm from "../../shared/components/contacts/ContactForm.js";
|
|
12
14
|
import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
|
|
13
15
|
import { clearPinnedSignerCache } from "../../shared/components/mail/pinnedSigners.js";
|
|
16
|
+
import { notifyApiError } from "../../shared/notifications/apiErrors.js";
|
|
14
17
|
/**
|
|
15
18
|
* Only reached on mobile (below the `md` breakpoint) — desktop's `apps/www/contacts/index.tsx` keeps its
|
|
16
19
|
* existing inline detail pane and never navigates here; see that file's `handleSelectRow`. There is no
|
|
17
20
|
* equivalent "new contact" route — an unsaved contact has no uid to route on, so creation stays an
|
|
18
21
|
* in-place mode-switch on every device (see `apps/www/contacts/index.tsx`'s `handleNew`).
|
|
19
22
|
*/
|
|
20
|
-
|
|
23
|
+
function ContactDetailPage(props) {
|
|
21
24
|
return (_jsx(ContactsShell, { ...props, children: _jsx(ContactDetailContent, { uid: props.params.uid }) }));
|
|
22
25
|
}
|
|
23
26
|
function ContactDetailContent({ uid }) {
|
|
27
|
+
const navigate = useNavigate();
|
|
24
28
|
const [contact, setContact] = useState(null);
|
|
25
29
|
const [loading, setLoading] = useState(true);
|
|
26
30
|
const [error, setError] = useState(null);
|
|
27
|
-
// Kept apart from the load `error` - a failed delete must leave the contact on screen (with the
|
|
28
|
-
// reason) rather than replacing the whole page with an error as if it had never loaded.
|
|
29
|
-
const [deleteError, setDeleteError] = useState(null);
|
|
30
31
|
const [mode, setMode] = useState("view");
|
|
31
32
|
useEffect(() => {
|
|
32
33
|
setLoading(true);
|
|
@@ -41,15 +42,15 @@ function ContactDetailContent({ uid }) {
|
|
|
41
42
|
// rendered once `contact` is already resolved (see the `error || !contact` guard above it), so this
|
|
42
43
|
// never runs with a stale/absent contact — no redundant null check needed here.
|
|
43
44
|
async function handleDelete(contact) {
|
|
44
|
-
setDeleteError(null);
|
|
45
45
|
try {
|
|
46
46
|
await deleteContact(contact.uid, contact.version);
|
|
47
47
|
// A deleted contact's pinned signing keys must stop vouching for signatures.
|
|
48
48
|
clearPinnedSignerCache();
|
|
49
|
-
|
|
49
|
+
navigate("/contacts");
|
|
50
50
|
}
|
|
51
51
|
catch (err) {
|
|
52
|
-
|
|
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");
|
|
53
54
|
}
|
|
54
55
|
}
|
|
55
56
|
// After a key change was resolved (or found stale): re-read the contact, keeping the one shown if that fails.
|
|
@@ -64,10 +65,11 @@ function ContactDetailContent({ uid }) {
|
|
|
64
65
|
}
|
|
65
66
|
const backHref = "/contacts";
|
|
66
67
|
if (mode === "edit") {
|
|
67
|
-
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) => {
|
|
68
69
|
setContact(saved);
|
|
69
70
|
setMode("view");
|
|
70
71
|
}, onCancel: () => setMode("view") })] }));
|
|
71
72
|
}
|
|
72
|
-
return (
|
|
73
|
+
return (_jsx("div", { className: "p-6", children: _jsx(ContactDetailPane, { contact: contact, onEdit: () => setMode("edit"), onDelete: () => handleDelete(contact), backHref: backHref, onKeysChanged: handleKeysChanged }) }));
|
|
73
74
|
}
|
|
75
|
+
export default routedPage("/contacts/:uid", ContactDetailPage);
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import React from "react";
|
|
2
1
|
import { ContactsShellProps } from "../../shared/components/contacts/layout/ContactsShell.js";
|
|
3
|
-
|
|
2
|
+
declare const _default: import("../../shared/navigation/AppRouter.js").RoutedPageComponent<ContactsShellProps>;
|
|
3
|
+
export default _default;
|