@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,19 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { ComposeSession } from "./ComposeContext.js";
|
|
3
|
+
export interface ComposeWindowPlaceholderProps {
|
|
4
|
+
session: ComposeSession;
|
|
5
|
+
/** The compose window's code couldn't be downloaded. */
|
|
6
|
+
failed: boolean;
|
|
7
|
+
onRetry: () => void;
|
|
8
|
+
onClose: () => void;
|
|
9
|
+
onToggleMinimize: () => void;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* What stands in for the compose window in the moment between clicking Compose/Reply/Forward and the window's code being
|
|
13
|
+
* loaded (already there when it was prefetched, so usually not seen at all): the window's own frame at its own size and
|
|
14
|
+
* place, its title and the recipients and subject the window will open with, all of which are known without the network -
|
|
15
|
+
* so the click is answered on the same frame. It is deliberately tiny and lives in the page's own chunk.
|
|
16
|
+
*
|
|
17
|
+
* Nothing here can be typed into yet, and there is no draft to save or discard, so Close just closes.
|
|
18
|
+
*/
|
|
19
|
+
export default function ComposeWindowPlaceholder({ session, failed, onRetry, onClose, onToggleMinimize }: ComposeWindowPlaceholderProps): React.JSX.Element;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
3
|
+
// Copyright (C) 2026 Jean-Philippe Steinmetz
|
|
4
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
5
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
6
|
+
import { useEffect } from "react";
|
|
7
|
+
import { HiOutlineMinus, HiOutlineXMark } from "react-icons/hi2";
|
|
8
|
+
import Skeleton from "@rapidmx/react-shared/components/feedback/Skeleton.js";
|
|
9
|
+
import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
|
|
10
|
+
import Button from "@rapidmx/react-shared/components/buttons/Button.js";
|
|
11
|
+
import useIsMobile from "@rapidmx/react-shared/util/useIsMobile.js";
|
|
12
|
+
import { markComposePhase } from "./composePerf.js";
|
|
13
|
+
/**
|
|
14
|
+
* What stands in for the compose window in the moment between clicking Compose/Reply/Forward and the window's code being
|
|
15
|
+
* loaded (already there when it was prefetched, so usually not seen at all): the window's own frame at its own size and
|
|
16
|
+
* place, its title and the recipients and subject the window will open with, all of which are known without the network -
|
|
17
|
+
* so the click is answered on the same frame. It is deliberately tiny and lives in the page's own chunk.
|
|
18
|
+
*
|
|
19
|
+
* Nothing here can be typed into yet, and there is no draft to save or discard, so Close just closes.
|
|
20
|
+
*/
|
|
21
|
+
export default function ComposeWindowPlaceholder({ session, failed, onRetry, onClose, onToggleMinimize }) {
|
|
22
|
+
const isMobile = useIsMobile();
|
|
23
|
+
const title = session.initialSubject?.trim() || "New Message";
|
|
24
|
+
const titleId = `compose-title-${session.id}`;
|
|
25
|
+
useEffect(() => markComposePhase(session.id, "shell"), [session.id]);
|
|
26
|
+
if (session.minimized) {
|
|
27
|
+
return (_jsx("div", { role: "dialog", "aria-label": title, className: "w-64 shrink-0 bg-surface border border-border border-b-0 rounded-t-md shadow-modal", children: _jsx("div", { className: "h-10 flex items-center justify-between gap-2 px-3 rounded-t-md bg-primary-darker text-white cursor-pointer", onClick: onToggleMinimize, children: _jsx("span", { className: "text-sm font-medium truncate", children: title }) }) }));
|
|
28
|
+
}
|
|
29
|
+
return (_jsxs("div", { role: "dialog", "aria-labelledby": titleId, "aria-busy": !failed, className: [
|
|
30
|
+
"relative shrink-0 flex flex-col bg-surface border border-border shadow-modal overflow-hidden",
|
|
31
|
+
isMobile ? "fixed inset-0 w-full h-full rounded-none border-0" : "border-b-0 rounded-t-md w-[480px] h-[520px]",
|
|
32
|
+
].join(" "), children: [_jsxs("div", { className: "h-10 shrink-0 flex items-center justify-between gap-2 px-3 bg-primary-darker text-white cursor-pointer", onClick: onToggleMinimize, children: [_jsx("span", { id: titleId, className: "text-sm font-medium truncate", children: title }), _jsxs("div", { className: "flex items-center gap-0.5", onClick: (e) => e.stopPropagation(), children: [_jsx("button", { type: "button", "aria-label": "Minimize", title: "Minimize", onClick: onToggleMinimize, className: "w-6 h-6 flex items-center justify-center rounded-sm text-white/80 hover:bg-white/15 hover:text-white", children: _jsx(HiOutlineMinus, { size: 14 }) }), _jsx("button", { type: "button", "aria-label": "Close", title: "Close", onClick: onClose, className: "w-6 h-6 flex items-center justify-center rounded-sm text-white/80 hover:bg-white/15 hover:text-white", children: _jsx(HiOutlineXMark, { size: 14 }) })] })] }), failed ? (_jsx("div", { className: "px-3 pt-2", children: _jsxs(Alert, { children: [_jsx("p", { className: "mb-2", children: "The compose window couldn\u2019t be loaded. Check your connection and try again." }), _jsx(Button, { type: "button", variant: "secondary", className: "!w-auto", onClick: onRetry, children: "Retry" })] }) })) : (_jsxs("div", { role: "status", className: "flex-1 min-h-0 flex flex-col", children: [_jsx("span", { className: "sr-only", children: "Opening the compose window" }), _jsxs("div", { className: "flex items-center gap-2 px-3 py-1.5 border-b border-border text-sm", children: [_jsx("span", { className: "text-text-muted", children: "To" }), _jsx("span", { className: "truncate", children: session.initialTo })] }), _jsx("div", { className: "flex items-center gap-2 px-3 py-1.5 border-b border-border text-sm", children: _jsx("span", { className: "truncate", children: session.initialSubject || _jsx("span", { className: "text-text-muted", children: "Subject" }) }) }), _jsxs("div", { className: "flex-1 p-3 flex flex-col gap-2", "aria-hidden": "true", children: [_jsx(Skeleton, { height: "h-4" }), _jsx(Skeleton, { height: "h-4" }), _jsx(Skeleton, { height: "h-4" })] })] }))] }));
|
|
33
|
+
}
|
|
@@ -18,6 +18,14 @@ export interface RichTextEditorProps {
|
|
|
18
18
|
* quote). That normalization happens on the editor's first transaction - even one that only moves the caret -
|
|
19
19
|
* and isn't reported through `onChange`, so a caller can compare later edits against this instead of `value`. */
|
|
20
20
|
onInitialized?: (value: string) => void;
|
|
21
|
+
/**
|
|
22
|
+
* HTML to add at the end of the document, once, as soon as there is some (it may be undefined when the editor is created and
|
|
23
|
+
* defined later). A reply opens its editor before the quoted original has been fetched and hands the quote in through this when
|
|
24
|
+
* it arrives - so whatever has been typed above stays, and the quote lands where it always did, under the signature.
|
|
25
|
+
*/
|
|
26
|
+
appendHtml?: string;
|
|
27
|
+
/** Called with the document as the editor serializes it, right after `appendHtml` was added. */
|
|
28
|
+
onAppended?: (value: string) => void;
|
|
21
29
|
}
|
|
22
30
|
/**
|
|
23
31
|
* A WYSIWYG rich-text editor for the Compose window's message body — replaces `MonacoHtmlEditor` (an HTML
|
|
@@ -40,4 +48,4 @@ export interface RichTextEditorProps {
|
|
|
40
48
|
* sufficient here since TipTap (unlike Monaco) never touches the DOM at module-evaluation time, only when
|
|
41
49
|
* an editor view actually mounts.
|
|
42
50
|
*/
|
|
43
|
-
export default function RichTextEditor({ value, onChange, height, fill, onUploadImage, autoFocusStart, onInitialized, }: RichTextEditorProps): React.JSX.Element;
|
|
51
|
+
export default function RichTextEditor({ value, onChange, height, fill, onUploadImage, autoFocusStart, onInitialized, appendHtml, onAppended, }: RichTextEditorProps): React.JSX.Element;
|
|
@@ -3,7 +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 { useRef, useState } from "react";
|
|
6
|
+
import { useEffect, useRef, useState } from "react";
|
|
7
7
|
import { EditorContent, useEditor } from "@tiptap/react";
|
|
8
8
|
import StarterKit from "@tiptap/starter-kit";
|
|
9
9
|
import { TextStyleKit } from "@tiptap/extension-text-style";
|
|
@@ -34,11 +34,14 @@ import ComposeToolbar from "./ComposeToolbar.js";
|
|
|
34
34
|
* sufficient here since TipTap (unlike Monaco) never touches the DOM at module-evaluation time, only when
|
|
35
35
|
* an editor view actually mounts.
|
|
36
36
|
*/
|
|
37
|
-
export default function RichTextEditor({ value, onChange, height = "360px", fill = false, onUploadImage, autoFocusStart = false, onInitialized, }) {
|
|
37
|
+
export default function RichTextEditor({ value, onChange, height = "360px", fill = false, onUploadImage, autoFocusStart = false, onInitialized, appendHtml, onAppended, }) {
|
|
38
38
|
const onChangeRef = useRef(onChange);
|
|
39
39
|
onChangeRef.current = onChange;
|
|
40
40
|
const onInitializedRef = useRef(onInitialized);
|
|
41
41
|
onInitializedRef.current = onInitialized;
|
|
42
|
+
const onAppendedRef = useRef(onAppended);
|
|
43
|
+
onAppendedRef.current = onAppended;
|
|
44
|
+
const appendedRef = useRef(undefined);
|
|
42
45
|
// Transactions before `create` (the autofocus, and the normalizing one below) aren't edits.
|
|
43
46
|
const initializedRef = useRef(false);
|
|
44
47
|
// Held for this editor's lifetime: TipTap focuses a tick after creating the editor, and a re-render passing a
|
|
@@ -57,6 +60,12 @@ export default function RichTextEditor({ value, onChange, height = "360px", fill
|
|
|
57
60
|
content: value,
|
|
58
61
|
autofocus,
|
|
59
62
|
immediatelyRender: false,
|
|
63
|
+
editorProps: {
|
|
64
|
+
// Ctrl/Cmd+Enter is the compose window's Send. TipTap binds it to a hard break, and a key it handles never reaches the
|
|
65
|
+
// window's shortcut (which skips what a widget already took), so it inserted a line break and sent nothing. Returning true here
|
|
66
|
+
// stops ProseMirror handling it without preventing the event, which goes on to the shortcut. Shift+Enter is still the hard break.
|
|
67
|
+
handleDOMEvents: { keydown: (_view, event) => event.key === "Enter" && (event.ctrlKey || event.metaKey) },
|
|
68
|
+
},
|
|
60
69
|
onCreate: ({ editor: created }) => {
|
|
61
70
|
// An empty transaction runs the schema's append-transaction normalization now, before any edit.
|
|
62
71
|
created.view.dispatch(created.state.tr);
|
|
@@ -69,6 +78,15 @@ export default function RichTextEditor({ value, onChange, height = "360px", fill
|
|
|
69
78
|
}
|
|
70
79
|
},
|
|
71
80
|
});
|
|
81
|
+
// Added once, at the end, whatever the reader has typed meanwhile (the caret stays where it is).
|
|
82
|
+
useEffect(() => {
|
|
83
|
+
if (!editor || appendHtml === undefined || appendedRef.current === appendHtml) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
appendedRef.current = appendHtml;
|
|
87
|
+
editor.commands.insertContentAt(editor.state.doc.content.size, appendHtml);
|
|
88
|
+
onAppendedRef.current?.(editor.getHTML());
|
|
89
|
+
}, [editor, appendHtml]);
|
|
72
90
|
return (_jsxs("div", { className: ["border border-border rounded-sm overflow-hidden", fill ? "h-full flex flex-col" : ""].filter(Boolean).join(" "), children: [_jsx(ComposeToolbar, { editor: editor, onUploadImage: onUploadImage }), _jsx(EditorContent, { editor: editor, style: fill ? undefined : { height }, className: [
|
|
73
91
|
"overflow-y-auto px-3 py-2 text-sm [&_.tiptap]:outline-none [&_.tiptap]:h-full",
|
|
74
92
|
fill ? "flex-1 min-h-0" : "",
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `performance.mark()`/`measure()` around the phases between clicking Compose/Reply/Forward and the window being usable,
|
|
3
|
+
* for the browser's Performance panel (or `performance.getEntriesByType("measure")`). Marks are named
|
|
4
|
+
* `compose:<window id>:<phase>`; the measures are named `compose:<from>-><to>` and carry the window id in `detail`.
|
|
5
|
+
*
|
|
6
|
+
* Phases, in order: `click` (the handler ran), `shell` (the window's frame is on screen - the placeholder, or the real
|
|
7
|
+
* window once its chunk has loaded), `chunk` (the window's code has loaded and rendered), `body` (the seeded body -
|
|
8
|
+
* signature and quoted original - is ready), `editor` (the editor has mounted and can be typed into), `draft` (the server
|
|
9
|
+
* draft exists).
|
|
10
|
+
*
|
|
11
|
+
* On in development builds, and in a production build when `window.__RAPIDMX_PERF__` is set to `true` (from the console:
|
|
12
|
+
* `window.__RAPIDMX_PERF__ = true`). Off otherwise, when each call is a no-op.
|
|
13
|
+
*/
|
|
14
|
+
export type ComposePhase = "click" | "shell" | "chunk" | "body" | "editor" | "draft";
|
|
15
|
+
/** Marks `phase` of window `id` once (later calls for the same phase are ignored), and measures it from `click`. */
|
|
16
|
+
export declare function markComposePhase(id: string, phase: ComposePhase): void;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
2
|
+
// Copyright (C) 2026 Jean-Philippe Steinmetz
|
|
3
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
5
|
+
/**
|
|
6
|
+
* `performance.mark()`/`measure()` around the phases between clicking Compose/Reply/Forward and the window being usable,
|
|
7
|
+
* for the browser's Performance panel (or `performance.getEntriesByType("measure")`). Marks are named
|
|
8
|
+
* `compose:<window id>:<phase>`; the measures are named `compose:<from>-><to>` and carry the window id in `detail`.
|
|
9
|
+
*
|
|
10
|
+
* Phases, in order: `click` (the handler ran), `shell` (the window's frame is on screen - the placeholder, or the real
|
|
11
|
+
* window once its chunk has loaded), `chunk` (the window's code has loaded and rendered), `body` (the seeded body -
|
|
12
|
+
* signature and quoted original - is ready), `editor` (the editor has mounted and can be typed into), `draft` (the server
|
|
13
|
+
* draft exists).
|
|
14
|
+
*
|
|
15
|
+
* On in development builds, and in a production build when `window.__RAPIDMX_PERF__` is set to `true` (from the console:
|
|
16
|
+
* `window.__RAPIDMX_PERF__ = true`). Off otherwise, when each call is a no-op.
|
|
17
|
+
*/
|
|
18
|
+
function enabled() {
|
|
19
|
+
if (typeof performance === "undefined" || typeof performance.mark !== "function") {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
return import.meta.env?.DEV === true || globalThis.__RAPIDMX_PERF__ === true;
|
|
23
|
+
}
|
|
24
|
+
/** The compose sessions a `click` mark was made for, so the first mark of each phase is the only one that counts. */
|
|
25
|
+
const marked = new Set();
|
|
26
|
+
/** Marks `phase` of window `id` once (later calls for the same phase are ignored), and measures it from `click`. */
|
|
27
|
+
export function markComposePhase(id, phase) {
|
|
28
|
+
if (!enabled()) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const key = `compose:${id}:${phase}`;
|
|
32
|
+
if (marked.has(key)) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
marked.add(key);
|
|
36
|
+
performance.mark(key);
|
|
37
|
+
const start = `compose:${id}:click`;
|
|
38
|
+
if (phase !== "click" && marked.has(start)) {
|
|
39
|
+
performance.measure(`compose:click->${phase}`, { start, end: key, detail: { id } });
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { RecipientEncryptionStatus } from "@rapidmx/react-shared/crypto/composeSecurity.js";
|
|
2
|
+
import type { EncryptionPolicy } from "@rapidmx/react-shared/crypto/keyvaultApi.js";
|
|
3
|
+
/**
|
|
4
|
+
* Whether a message must be treated as encrypted - the one place that decides it, as a pure function of what is *known*.
|
|
5
|
+
*
|
|
6
|
+
* **The rule is fail open.** A message is plain, its draft is saved, and nothing about encryption is ever shown, unless encryption is
|
|
7
|
+
* actually in play. Encryption is in play only when
|
|
8
|
+
*
|
|
9
|
+
* 1. the user asked for it ("Encrypt this message", or a reply to encrypted mail, whose quote may carry decrypted text) - that is local
|
|
10
|
+
* knowledge and needs no request to have succeeded; or
|
|
11
|
+
* 2. the system's encryption policy **was loaded successfully**, this sender has an encryption key (enrolled on their mailbox, or unlocked
|
|
12
|
+
* in this tab), the message has recipients, **every** recipient's key lookup succeeded, and the policy auto-applies encryption to all
|
|
13
|
+
* of them (`decideMessageEncryption().autoEncrypt`: policy `automatic` for their tier, both sides preferring "mutual", a key found).
|
|
14
|
+
*
|
|
15
|
+
* Anything that failed to load, is still loading or is slow (the policy, the mailbox, a recipient's lookup) is **unknown**, and unknown
|
|
16
|
+
* counts as "not encrypted": that is what stops a gateway that flaps, a slow request, or a user who has never set encryption up from
|
|
17
|
+
* ever blocking a draft, blocking Close, blocking Send or showing an "encryption can't be checked" message. What was unknown is
|
|
18
|
+
* reported in `unknown`, so a caller that is about to send can give the loads a moment - and only a caller that finds out later that the
|
|
19
|
+
* message *does* need encryption acts on it (see `ComposeWindow`'s transition rules).
|
|
20
|
+
*
|
|
21
|
+
* Why this is safe enough: the requirement only ever protects *this client's* plaintext drafts and sends from being unencrypted when the
|
|
22
|
+
* server's policy says they should be; it is not the enforcement point (the keys are the user's, the server never sees them). A missing
|
|
23
|
+
* policy therefore does not mean "encrypt or refuse" - it means the client cannot tell, and the honest default is the one that works.
|
|
24
|
+
*/
|
|
25
|
+
export interface EncryptionFacts {
|
|
26
|
+
/** The user turned encryption on, or this compose is a reply to / forward of an encrypted message. */
|
|
27
|
+
encryptRequested: boolean;
|
|
28
|
+
/** The policy, once it has loaded; `undefined` while loading, failed or slow. */
|
|
29
|
+
policy: EncryptionPolicy | undefined;
|
|
30
|
+
/** The sender's mailbox has loaded (its enrolled keys are known). */
|
|
31
|
+
mailboxLoaded: boolean;
|
|
32
|
+
/** The sender has an encryption key to encrypt with: enrolled on the mailbox, unlocked in this tab, or offered earlier this session. */
|
|
33
|
+
hasEncryptionKey: boolean;
|
|
34
|
+
/** One entry per current recipient (To, Cc and Bcc): the resolved status, or `undefined` while that recipient's lookup is pending or failed. */
|
|
35
|
+
recipients: (RecipientEncryptionStatus | undefined)[];
|
|
36
|
+
}
|
|
37
|
+
export type EncryptionReason = "requested" | "policy";
|
|
38
|
+
/** What was not known, and could still change the answer. */
|
|
39
|
+
export type UnknownFact = "mailbox" | "policy" | "recipients";
|
|
40
|
+
export interface EncryptionRequirement {
|
|
41
|
+
/** The message must be encrypted: it is never stored as a plaintext draft and never sent in the clear. */
|
|
42
|
+
required: boolean;
|
|
43
|
+
reason: EncryptionReason | null;
|
|
44
|
+
/** What was unknown (and so assumed "no encryption"). Empty when the answer cannot change with more loading. Never shown to the user. */
|
|
45
|
+
unknown: UnknownFact[];
|
|
46
|
+
}
|
|
47
|
+
/** Any tier of `policy` auto-encrypts - only then can what is still unknown about the recipients turn a message into an encrypted one. */
|
|
48
|
+
export declare function policyCanAutoEncrypt(policy: EncryptionPolicy): boolean;
|
|
49
|
+
export declare function evaluateEncryptionRequirement(facts: EncryptionFacts): EncryptionRequirement;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
2
|
+
// Copyright (C) 2026 Jean-Philippe Steinmetz
|
|
3
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
5
|
+
import { decideMessageEncryption } from "@rapidmx/react-shared/crypto/composeSecurity.js";
|
|
6
|
+
/** Any tier of `policy` auto-encrypts - only then can what is still unknown about the recipients turn a message into an encrypted one. */
|
|
7
|
+
export function policyCanAutoEncrypt(policy) {
|
|
8
|
+
return [policy.encryptSameOrg, policy.encryptFederated, policy.encryptExternal].includes("automatic");
|
|
9
|
+
}
|
|
10
|
+
export function evaluateEncryptionRequirement(facts) {
|
|
11
|
+
if (facts.encryptRequested) {
|
|
12
|
+
return { required: true, reason: "requested", unknown: [] };
|
|
13
|
+
}
|
|
14
|
+
const unknown = [];
|
|
15
|
+
if (!facts.hasEncryptionKey) {
|
|
16
|
+
// Without a key nothing can be encrypted: a user who never set encryption up sees nothing at all. Whether the mailbox has one is
|
|
17
|
+
// only *unknown* while it has not loaded.
|
|
18
|
+
return { required: false, reason: null, unknown: facts.mailboxLoaded ? [] : ["mailbox"] };
|
|
19
|
+
}
|
|
20
|
+
if (!facts.policy) {
|
|
21
|
+
return { required: false, reason: null, unknown: ["policy"] };
|
|
22
|
+
}
|
|
23
|
+
if (!policyCanAutoEncrypt(facts.policy)) {
|
|
24
|
+
return { required: false, reason: null, unknown };
|
|
25
|
+
}
|
|
26
|
+
if (facts.recipients.length === 0) {
|
|
27
|
+
// Nobody to decide by yet: plain until a recipient is added.
|
|
28
|
+
return { required: false, reason: null, unknown };
|
|
29
|
+
}
|
|
30
|
+
const statuses = facts.recipients.filter((status) => !!status);
|
|
31
|
+
if (statuses.length < facts.recipients.length) {
|
|
32
|
+
return { required: false, reason: null, unknown: ["recipients"] };
|
|
33
|
+
}
|
|
34
|
+
return decideMessageEncryption(statuses).autoEncrypt ? { required: true, reason: "policy", unknown } : { required: false, reason: null, unknown };
|
|
35
|
+
}
|
|
@@ -9,6 +9,19 @@ export interface OriginalMessage {
|
|
|
9
9
|
* be. Only asked for by Reply All (see `loadOriginalMessage()`). */
|
|
10
10
|
recipients?: Recipient[];
|
|
11
11
|
}
|
|
12
|
+
/** How long a reply waits for the original's body before it goes ahead with the preview - a slow or hung request must not
|
|
13
|
+
* leave the window waiting on it (or, before it opened at once, keep it from opening) indefinitely. */
|
|
14
|
+
export declare const QUOTE_FETCH_TIMEOUT_MS = 10000;
|
|
15
|
+
/** How long a fetched (or prefetched) body is remembered, so hovering Reply and then clicking it costs one request. */
|
|
16
|
+
export declare const QUOTE_CACHE_MS = 30000;
|
|
17
|
+
/**
|
|
18
|
+
* Starts fetching what a Reply/Forward of `message` will quote, without waiting for the click - called when the pointer or
|
|
19
|
+
* keyboard reaches a Reply/Reply All/Forward button - so the request is usually already answered when it is clicked.
|
|
20
|
+
* Nothing to fetch for an encrypted message (`loadOriginalMessage()` never asks the server for one's body).
|
|
21
|
+
*/
|
|
22
|
+
export declare function prefetchOriginalMessage(message: Message): void;
|
|
23
|
+
/** Forgets every remembered body (the sign-out and test seam). */
|
|
24
|
+
export declare function clearOriginalMessageCache(): void;
|
|
12
25
|
/**
|
|
13
26
|
* Loads what a Reply/Reply All/Forward needs from `message`: the full body to quote - the same content
|
|
14
27
|
* `MessageDetailPane` shows, never the truncated `bodyPreview` unless nothing else can be had - and, with
|
|
@@ -6,6 +6,66 @@ import { apiUrl } from "@rapidmx/react-shared/util/api.js";
|
|
|
6
6
|
import { getMessageRawContent } from "@rapidmx/react-shared/mail/mailApi.js";
|
|
7
7
|
import { decodeHeaderText, extractDisplayBody, parseMimeEntity } from "@rapidmx/react-shared/crypto/mime.js";
|
|
8
8
|
import { parseRecipientList } from "./recipients.js";
|
|
9
|
+
/** How long a reply waits for the original's body before it goes ahead with the preview - a slow or hung request must not
|
|
10
|
+
* leave the window waiting on it (or, before it opened at once, keep it from opening) indefinitely. */
|
|
11
|
+
export const QUOTE_FETCH_TIMEOUT_MS = 10000;
|
|
12
|
+
/** How long a fetched (or prefetched) body is remembered, so hovering Reply and then clicking it costs one request. */
|
|
13
|
+
export const QUOTE_CACHE_MS = 30000;
|
|
14
|
+
const contentCache = new Map();
|
|
15
|
+
/**
|
|
16
|
+
* The server's sanitized HTML body of a message (`GET /mail/messages/:id/content`), or `undefined` when it has none (a
|
|
17
|
+
* `text/plain` message is answered with its preview instead) or the request failed or timed out. Never rejects. Remembered
|
|
18
|
+
* for `QUOTE_CACHE_MS` - except a failure, which is asked again the next time.
|
|
19
|
+
*/
|
|
20
|
+
function fetchContentHtml(uid) {
|
|
21
|
+
const cached = contentCache.get(uid);
|
|
22
|
+
if (cached && Date.now() - cached.at < QUOTE_CACHE_MS) {
|
|
23
|
+
return cached.promise;
|
|
24
|
+
}
|
|
25
|
+
const controller = new AbortController();
|
|
26
|
+
const timer = setTimeout(() => controller.abort(), QUOTE_FETCH_TIMEOUT_MS);
|
|
27
|
+
const promise = (async () => {
|
|
28
|
+
try {
|
|
29
|
+
const res = await fetch(apiUrl(`/mail/messages/${encodeURIComponent(uid)}/content`), { credentials: "include", signal: controller.signal });
|
|
30
|
+
return res.ok && (res.headers.get("content-type") ?? "").includes("text/html") ? await res.text() : undefined;
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
finally {
|
|
36
|
+
clearTimeout(timer);
|
|
37
|
+
}
|
|
38
|
+
})();
|
|
39
|
+
contentCache.set(uid, { at: Date.now(), promise });
|
|
40
|
+
void promise.then((html) => {
|
|
41
|
+
if (html === undefined && contentCache.get(uid)?.promise === promise) {
|
|
42
|
+
contentCache.delete(uid);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
return promise;
|
|
46
|
+
}
|
|
47
|
+
/** Rejects when `promise` hasn't settled within `ms` (`getMessageRawContent()` takes no `AbortSignal`, so it is raced rather than aborted). */
|
|
48
|
+
function withTimeout(promise, ms) {
|
|
49
|
+
let timer;
|
|
50
|
+
const timeout = new Promise((_resolve, reject) => {
|
|
51
|
+
timer = setTimeout(() => reject(new Error("Timed out.")), ms);
|
|
52
|
+
});
|
|
53
|
+
return Promise.race([promise, timeout]).finally(() => clearTimeout(timer));
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Starts fetching what a Reply/Forward of `message` will quote, without waiting for the click - called when the pointer or
|
|
57
|
+
* keyboard reaches a Reply/Reply All/Forward button - so the request is usually already answered when it is clicked.
|
|
58
|
+
* Nothing to fetch for an encrypted message (`loadOriginalMessage()` never asks the server for one's body).
|
|
59
|
+
*/
|
|
60
|
+
export function prefetchOriginalMessage(message) {
|
|
61
|
+
if (!message.encrypted) {
|
|
62
|
+
void fetchContentHtml(message.uid);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/** Forgets every remembered body (the sign-out and test seam). */
|
|
66
|
+
export function clearOriginalMessageCache() {
|
|
67
|
+
contentCache.clear();
|
|
68
|
+
}
|
|
9
69
|
/** The recipients one address-list header names, RFC 2047-decoded, with their display names. */
|
|
10
70
|
function headerRecipients(value, type) {
|
|
11
71
|
if (!value) {
|
|
@@ -50,23 +110,19 @@ export async function loadOriginalMessage(message, security, options = {}) {
|
|
|
50
110
|
}
|
|
51
111
|
let needsRaw = !!options.recipients && result.recipients === undefined;
|
|
52
112
|
if (!recovered) {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
58
|
-
else {
|
|
59
|
-
needsRaw = true;
|
|
60
|
-
}
|
|
113
|
+
// Never rejects; nothing (a plain-text message, a failure, a timeout) leaves the raw message below as the other way
|
|
114
|
+
// to the body.
|
|
115
|
+
const html = await fetchContentHtml(message.uid);
|
|
116
|
+
if (html !== undefined) {
|
|
117
|
+
result.body = { html };
|
|
61
118
|
}
|
|
62
|
-
|
|
63
|
-
// The raw message below is the other way to the body.
|
|
119
|
+
else {
|
|
64
120
|
needsRaw = true;
|
|
65
121
|
}
|
|
66
122
|
}
|
|
67
123
|
try {
|
|
68
124
|
if (needsRaw) {
|
|
69
|
-
const entity = parseMimeEntity(await getMessageRawContent(message.uid));
|
|
125
|
+
const entity = parseMimeEntity(await withTimeout(getMessageRawContent(message.uid), QUOTE_FETCH_TIMEOUT_MS));
|
|
70
126
|
if (!recovered && result.body.html === undefined) {
|
|
71
127
|
const body = extractDisplayBody(entity);
|
|
72
128
|
result.body = body.html !== undefined ? { html: body.html } : body.text !== undefined ? { text: body.text } : {};
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import React, { PropsWithChildren } from "react";
|
|
2
|
-
import { Folder, Mailbox } from "@rapidmx/react-shared/mail/mailApi.js";
|
|
2
|
+
import { Folder, Mailbox, Message } from "@rapidmx/react-shared/mail/mailApi.js";
|
|
3
3
|
import { AppShellProps } from "../../layout/AppShell.js";
|
|
4
4
|
import { LiveUpdates } from "../../../mail/useMailLiveUpdates.js";
|
|
5
|
+
import { MAILBOX_LIST_LIMIT } from "../../../mail/useMailConnection.js";
|
|
6
|
+
import { CountTracker } from "../../../mail/folderCounts.js";
|
|
5
7
|
export type MailShellProps = Omit<AppShellProps, "active">;
|
|
6
8
|
/** One mailbox's own mail folders (already filtered to `MAIL_FOLDER_TYPES` and unsorted) - one entry per
|
|
7
9
|
* mailbox in `mailboxes`, fetched in parallel so every accessible mailbox's folder tree can render
|
|
@@ -42,17 +44,25 @@ export interface MailShellContextValue {
|
|
|
42
44
|
* A no-op on the default context value, which is only ever read outside a real shell.
|
|
43
45
|
*/
|
|
44
46
|
onFolderCreated: (folder: Folder) => void;
|
|
47
|
+
/** Says which folders the messages on screen live in: one the sidebar does not know is looked for (see `MailConnection.noteFolderUids`). A no-op on the default context value. */
|
|
48
|
+
noteFolderUids: (folderUids: Iterable<string>) => void;
|
|
45
49
|
/**
|
|
46
50
|
* Bumped whenever new mail (or another change to a message) may have arrived - a push event, a reconnect or the safety-net
|
|
47
51
|
* poll - so the list on screen can quietly refetch its first page. See `useMailLiveUpdates()`. Never changes on the default
|
|
48
52
|
* context value, which is only ever read outside a real shell.
|
|
49
53
|
*/
|
|
50
54
|
live: LiveUpdates;
|
|
55
|
+
/**
|
|
56
|
+
* Applies a change to a message (`previous` to `next`; `next` absent when it was deleted) to the folder badges at once, and
|
|
57
|
+
* hands back what to call when the server has answered: `settle()` keeps it, `revert()` takes it back. Every place that
|
|
58
|
+
* reads, moves or deletes a message goes through this (see `setReadState()`), so the badges follow. A no-op on the default
|
|
59
|
+
* context value, which is only ever read outside a real shell.
|
|
60
|
+
*/
|
|
61
|
+
trackMessageChange: (previous: Message, next: Message | null) => CountTracker;
|
|
51
62
|
}
|
|
52
63
|
/** Reads the mailbox/folder a page is currently showing, as resolved by the enclosing `MailShell`. */
|
|
53
64
|
export declare function useMailShell(): MailShellContextValue;
|
|
54
|
-
|
|
55
|
-
export declare const MAILBOX_LIST_LIMIT = 100;
|
|
65
|
+
export { MAILBOX_LIST_LIMIT };
|
|
56
66
|
/**
|
|
57
67
|
* Mail's own contextual sidebar (every accessible mailbox's own folder tree, plus a merged "All Mailboxes"
|
|
58
68
|
* aggregate section) + content area, rendered inside the shared `AppShell` chrome (icon rail, header,
|
|
@@ -69,4 +79,4 @@ export declare const MAILBOX_LIST_LIMIT = 100;
|
|
|
69
79
|
* aggregate section) simultaneously, matching the shared-mailbox feature's own "display as a separate set
|
|
70
80
|
* of folders" requirement.
|
|
71
81
|
*/
|
|
72
|
-
export default function MailShell({ userUid, authServerUrl, impersonating, impersonationBaseUrl, trusted, pluginNav, children, }: PropsWithChildren<MailShellProps>): React.JSX.Element;
|
|
82
|
+
export default function MailShell({ userUid, authServerUrl, impersonating, impersonationBaseUrl, trusted, trustedRoles, pluginNav, children, }: PropsWithChildren<MailShellProps>): React.JSX.Element;
|