@rapidmx/web-client 0.9.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +373 -88
- package/apps/admin/_layout.tsx +8 -2
- package/apps/admin/index.tsx +7 -1
- package/apps/admin/ingest-queue/index.tsx +2 -1
- package/apps/admin/mailboxes/[uid].tsx +23 -10
- package/apps/admin/quarantine/index.tsx +2 -1
- package/apps/admin/signing-certificates/index.tsx +343 -0
- package/apps/escrow/_layout.tsx +8 -2
- package/apps/shared/appearance/AppearanceForm.tsx +392 -0
- package/apps/shared/appearance/AppearanceHead.tsx +49 -0
- package/apps/shared/appearance/AppearanceProvider.tsx +470 -0
- package/apps/shared/appearance/ColorField.tsx +107 -0
- package/apps/shared/appearance/appearanceCache.ts +106 -0
- package/apps/shared/appearance/appearanceContext.ts +77 -0
- package/apps/shared/appearance/bootScript.ts +35 -0
- package/apps/shared/appearance/color.ts +169 -0
- package/apps/shared/appearance/photo.ts +88 -0
- package/apps/shared/appearance/resolvedTheme.ts +67 -0
- package/apps/shared/appearance/theme.ts +355 -0
- package/apps/shared/auth/accountUrl.ts +9 -0
- package/apps/shared/auth/adminAccess.ts +99 -0
- package/apps/shared/components/admin/layout/AdminShell.tsx +22 -10
- package/apps/shared/components/admin/mailboxes/ShareAccessCard.tsx +107 -60
- package/apps/shared/components/admin/settings/BrandingForm.tsx +24 -0
- package/apps/shared/components/admin/settings/DomainDnsSetup.tsx +2 -1
- package/apps/shared/components/admin/settings/PluginsManager.tsx +228 -25
- package/apps/shared/components/admin/setup/SetupWizard.tsx +2 -1
- package/apps/shared/components/calendar/CalendarListSidebar.tsx +4 -5
- package/apps/shared/components/calendar/layout/CalendarShell.tsx +8 -2
- package/apps/shared/components/contacts/ContactForm.tsx +11 -5
- package/apps/shared/components/contacts/ContactsSidebar.tsx +5 -3
- package/apps/shared/components/contacts/ContactsToolbar.tsx +98 -115
- package/apps/shared/components/contacts/layout/ContactsShell.tsx +25 -8
- package/apps/shared/components/escrow/layout/EscrowShell.tsx +13 -6
- package/apps/shared/components/layout/AppShell.tsx +477 -288
- package/apps/shared/components/layout/BrandingChrome.tsx +200 -13
- package/apps/shared/components/layout/KeyEnrollmentGate.tsx +398 -389
- package/apps/shared/components/layout/MailboxProvisioning.tsx +168 -164
- package/apps/shared/components/layout/RailIcon.tsx +100 -0
- package/apps/shared/components/layout/ResponsiveToolbar.tsx +315 -0
- package/apps/shared/components/layout/ThemeSwitch.tsx +84 -0
- package/apps/shared/components/layout/UserMenu.tsx +372 -185
- package/apps/shared/components/mail/ConversationList.tsx +271 -262
- package/apps/shared/components/mail/ConversationThreadPane.tsx +147 -94
- package/apps/shared/components/mail/LazyReadingPane.tsx +113 -0
- package/apps/shared/components/mail/MailSelectionBar.tsx +227 -222
- package/apps/shared/components/mail/MessageDetailPane.tsx +1537 -1387
- package/apps/shared/components/mail/OutboxBadge.tsx +44 -0
- package/apps/shared/components/mail/OutboxRowStatus.tsx +31 -0
- package/apps/shared/components/mail/compose/ComposeContext.tsx +312 -160
- package/apps/shared/components/mail/compose/ComposeToolbar.tsx +422 -418
- package/apps/shared/components/mail/compose/ComposeWindow.tsx +1644 -1736
- package/apps/shared/components/mail/compose/ComposeWindowPlaceholder.tsx +111 -0
- package/apps/shared/components/mail/compose/RichTextEditor.tsx +147 -118
- package/apps/shared/components/mail/compose/composePerf.ts +46 -0
- package/apps/shared/components/mail/compose/encryptionRequirement.ts +86 -0
- package/apps/shared/components/mail/compose/quotedBody.ts +161 -100
- package/apps/shared/components/mail/layout/MailShell.tsx +490 -472
- package/apps/shared/components/mail/reading/EncryptedBody.tsx +65 -0
- package/apps/shared/components/mail/reading/EncryptedPreview.tsx +35 -0
- package/apps/shared/components/mail/reading/MessageBody.tsx +240 -0
- package/apps/shared/components/mail/reading/MessageCard.tsx +174 -0
- package/apps/shared/components/mail/reading/bodyContent.ts +100 -0
- package/apps/shared/components/mail/reading/bodyHtml.ts +298 -0
- package/apps/shared/components/mail/reading/color.ts +245 -0
- package/apps/shared/components/mail/reading/frameControl.ts +142 -0
- package/apps/shared/components/mail/reading/frameDocument.ts +123 -0
- package/apps/shared/components/mail/reading/safeDocument.ts +24 -0
- package/apps/shared/components/mail/reading/themeAdaptation.ts +221 -0
- package/apps/shared/components/mail/reading/themeSurface.ts +90 -0
- package/apps/shared/components/mail/reading/viewOriginal.ts +38 -0
- package/apps/shared/components/mail/unreadStyle.tsx +75 -0
- package/apps/shared/components/settings/SigningCertificateCard.tsx +344 -0
- package/apps/shared/components/settings/layout/SettingsShell.tsx +253 -240
- package/apps/shared/components/sharing/PrincipalPicker.tsx +145 -0
- package/apps/shared/components/tasks/TasksSidebar.tsx +5 -3
- package/apps/shared/components/tasks/layout/TasksShell.tsx +24 -8
- package/apps/shared/keyboard/GlobalShortcuts.tsx +51 -0
- package/apps/shared/keyboard/ShortcutProvider.tsx +62 -0
- package/apps/shared/keyboard/ShortcutsDialog.tsx +84 -0
- package/apps/shared/keyboard/dispatch.ts +124 -0
- package/apps/shared/keyboard/format.ts +89 -0
- package/apps/shared/keyboard/keymap.ts +114 -0
- package/apps/shared/keyboard/match.ts +44 -0
- package/apps/shared/keyboard/parse.ts +136 -0
- package/apps/shared/keyboard/platform.ts +34 -0
- package/apps/shared/keyboard/registry.ts +65 -0
- package/apps/shared/keyboard/targets.ts +79 -0
- package/apps/shared/keyboard/useShortcut.ts +50 -0
- package/apps/shared/keyboard/useShortcutProps.ts +17 -0
- package/apps/shared/mail/folderCounts.ts +308 -0
- package/apps/shared/mail/folderTree.ts +143 -0
- package/apps/shared/mail/listSnapshots.ts +87 -0
- package/apps/shared/mail/messageReadState.ts +85 -0
- package/apps/shared/mail/newMailNotifications.ts +183 -0
- package/apps/shared/mail/outbox/UnlockBridge.tsx +23 -0
- package/apps/shared/mail/outbox/composeBridge.ts +69 -0
- package/apps/shared/mail/outbox/outboxState.ts +88 -0
- package/apps/shared/mail/outbox/pendingSends.ts +146 -0
- package/apps/shared/mail/outbox/sendDecision.ts +157 -0
- package/apps/shared/mail/outbox/sendJob.ts +427 -0
- package/apps/shared/mail/outbox/sendOutcomes.ts +146 -0
- package/apps/shared/mail/outbox/sendState.ts +35 -0
- package/apps/shared/mail/outbox/useOutboxStatus.ts +61 -0
- package/apps/shared/mail/useMailConnection.ts +203 -0
- package/apps/shared/mail/useMailLiveUpdates.ts +277 -224
- package/apps/shared/mail/useMarkMessageRead.ts +47 -0
- package/apps/shared/mail/useNewMailNotifications.ts +178 -0
- package/apps/shared/mail/useUnreadTitle.ts +42 -0
- package/apps/shared/navigation/AppRouter.tsx +300 -0
- package/apps/shared/navigation/appHrefs.ts +23 -0
- package/apps/shared/navigation/frameContext.tsx +35 -0
- package/apps/shared/navigation/idle.ts +45 -0
- package/apps/shared/navigation/routerContext.tsx +83 -0
- package/apps/shared/navigation/routes.ts +72 -0
- package/apps/shared/notifications/NotificationCenter.tsx +215 -0
- package/apps/shared/notifications/NotificationHistoryDialog.tsx +90 -0
- package/apps/shared/notifications/apiErrors.ts +98 -0
- package/apps/shared/notifications/headerOffset.ts +35 -0
- package/apps/shared/notifications/pushStatus.ts +56 -0
- package/apps/shared/notifications/store.ts +549 -0
- package/apps/shared/notifications/systemErrors.ts +63 -0
- package/apps/shared/notifications/useNotifications.ts +41 -0
- package/apps/shared/search/LocalIndexLifecycle.tsx +114 -98
- package/apps/shared/signing/enrollmentStorage.ts +33 -0
- package/apps/shared/signing/enrollmentTracker.ts +385 -0
- package/apps/shared/signing/enrollmentView.ts +251 -0
- package/apps/shared/signing/signingInfo.ts +58 -0
- package/apps/shared/signing/useNow.ts +19 -0
- package/apps/shared/signing/useSigningEnrollmentWatcher.ts +90 -0
- package/apps/shared/styles/app.css +108 -10
- package/apps/www/_layout.tsx +8 -2
- package/apps/www/_routedPage.tsx +24 -0
- package/apps/www/_routes.ts +36 -0
- package/apps/www/calendar/index.tsx +64 -33
- package/apps/www/contacts/[uid].tsx +109 -107
- package/apps/www/contacts/index.tsx +62 -42
- package/apps/www/index.tsx +2291 -1917
- package/apps/www/messages/[uid].tsx +108 -101
- package/apps/www/settings/appearance/index.tsx +21 -0
- package/apps/www/settings/auto-reply/index.tsx +7 -8
- package/apps/www/settings/encryption/index.tsx +1287 -1249
- package/apps/www/settings/filters/[uid].tsx +7 -2
- package/apps/www/settings/filters/index.tsx +102 -99
- package/apps/www/settings/filters/new/index.tsx +140 -133
- package/apps/www/settings/labels/index.tsx +204 -201
- package/apps/www/settings/privacy/index.tsx +7 -9
- package/apps/www/settings/read-receipts/index.tsx +7 -8
- package/apps/www/settings/sharing/index.tsx +256 -274
- package/apps/www/settings/signatures/[uid].tsx +172 -167
- package/apps/www/settings/signatures/index.tsx +88 -85
- package/apps/www/settings/signatures/new/index.tsx +135 -129
- package/apps/www/tasks/index.tsx +29 -18
- package/dist/apps/admin/_layout.d.ts +5 -1
- package/dist/apps/admin/_layout.js +3 -2
- package/dist/apps/admin/index.js +3 -2
- package/dist/apps/admin/ingest-queue/index.js +2 -1
- package/dist/apps/admin/mailboxes/[uid].js +9 -6
- package/dist/apps/admin/quarantine/index.js +2 -1
- package/dist/apps/admin/signing-certificates/index.d.ts +3 -0
- package/dist/apps/admin/signing-certificates/index.js +161 -0
- package/dist/apps/escrow/_layout.d.ts +5 -1
- package/dist/apps/escrow/_layout.js +3 -2
- package/dist/apps/shared/appearance/AppearanceForm.d.ts +7 -0
- package/dist/apps/shared/appearance/AppearanceForm.js +131 -0
- package/dist/apps/shared/appearance/AppearanceHead.d.ts +15 -0
- package/dist/apps/shared/appearance/AppearanceHead.js +31 -0
- package/dist/apps/shared/appearance/AppearanceProvider.d.ts +32 -0
- package/dist/apps/shared/appearance/AppearanceProvider.js +390 -0
- package/dist/apps/shared/appearance/ColorField.d.ts +22 -0
- package/dist/apps/shared/appearance/ColorField.js +40 -0
- package/dist/apps/shared/appearance/appearanceCache.d.ts +45 -0
- package/dist/apps/shared/appearance/appearanceCache.js +80 -0
- package/dist/apps/shared/appearance/appearanceContext.d.ts +45 -0
- package/dist/apps/shared/appearance/appearanceContext.js +29 -0
- package/dist/apps/shared/appearance/bootScript.d.ts +16 -0
- package/dist/apps/shared/appearance/bootScript.js +33 -0
- package/dist/apps/shared/appearance/color.d.ts +65 -0
- package/dist/apps/shared/appearance/color.js +132 -0
- package/dist/apps/shared/appearance/photo.d.ts +17 -0
- package/dist/apps/shared/appearance/photo.js +79 -0
- package/dist/apps/shared/appearance/resolvedTheme.d.ts +21 -0
- package/dist/apps/shared/appearance/resolvedTheme.js +55 -0
- package/dist/apps/shared/appearance/theme.d.ts +111 -0
- package/dist/apps/shared/appearance/theme.js +251 -0
- package/dist/apps/shared/auth/accountUrl.d.ts +2 -0
- package/dist/apps/shared/auth/accountUrl.js +8 -0
- package/dist/apps/shared/auth/adminAccess.d.ts +30 -0
- package/dist/apps/shared/auth/adminAccess.js +89 -0
- package/dist/apps/shared/components/admin/layout/AdminShell.d.ts +1 -1
- package/dist/apps/shared/components/admin/layout/AdminShell.js +16 -6
- package/dist/apps/shared/components/admin/mailboxes/ShareAccessCard.d.ts +10 -5
- package/dist/apps/shared/components/admin/mailboxes/ShareAccessCard.js +49 -34
- package/dist/apps/shared/components/admin/settings/BrandingForm.d.ts +2 -0
- package/dist/apps/shared/components/admin/settings/BrandingForm.js +7 -1
- package/dist/apps/shared/components/admin/settings/DomainDnsSetup.js +1 -1
- package/dist/apps/shared/components/admin/settings/PluginsManager.js +106 -20
- package/dist/apps/shared/components/admin/setup/SetupWizard.js +2 -1
- package/dist/apps/shared/components/calendar/CalendarListSidebar.js +8 -8
- package/dist/apps/shared/components/calendar/layout/CalendarShell.d.ts +1 -1
- package/dist/apps/shared/components/calendar/layout/CalendarShell.js +8 -4
- package/dist/apps/shared/components/contacts/ContactForm.js +1 -1
- package/dist/apps/shared/components/contacts/ContactsSidebar.js +4 -2
- package/dist/apps/shared/components/contacts/ContactsToolbar.d.ts +6 -2
- package/dist/apps/shared/components/contacts/ContactsToolbar.js +28 -10
- package/dist/apps/shared/components/contacts/layout/ContactsShell.d.ts +1 -1
- package/dist/apps/shared/components/contacts/layout/ContactsShell.js +19 -6
- package/dist/apps/shared/components/escrow/layout/EscrowShell.js +7 -4
- package/dist/apps/shared/components/layout/AppShell.d.ts +34 -4
- package/dist/apps/shared/components/layout/AppShell.js +122 -17
- package/dist/apps/shared/components/layout/BrandingChrome.d.ts +69 -10
- package/dist/apps/shared/components/layout/BrandingChrome.js +140 -12
- package/dist/apps/shared/components/layout/KeyEnrollmentGate.js +10 -9
- package/dist/apps/shared/components/layout/MailboxProvisioning.js +3 -1
- package/dist/apps/shared/components/layout/RailIcon.d.ts +21 -0
- package/dist/apps/shared/components/layout/RailIcon.js +85 -0
- package/dist/apps/shared/components/layout/ResponsiveToolbar.d.ts +55 -0
- package/dist/apps/shared/components/layout/ResponsiveToolbar.js +199 -0
- package/dist/apps/shared/components/layout/ThemeSwitch.d.ts +9 -0
- package/dist/apps/shared/components/layout/ThemeSwitch.js +54 -0
- package/dist/apps/shared/components/layout/UserMenu.d.ts +25 -3
- package/dist/apps/shared/components/layout/UserMenu.js +88 -7
- package/dist/apps/shared/components/mail/ConversationList.js +5 -11
- package/dist/apps/shared/components/mail/ConversationThreadPane.d.ts +6 -2
- package/dist/apps/shared/components/mail/ConversationThreadPane.js +87 -40
- package/dist/apps/shared/components/mail/LazyReadingPane.d.ts +9 -0
- package/dist/apps/shared/components/mail/LazyReadingPane.js +83 -0
- package/dist/apps/shared/components/mail/MailSelectionBar.d.ts +6 -4
- package/dist/apps/shared/components/mail/MailSelectionBar.js +11 -4
- package/dist/apps/shared/components/mail/MessageDetailPane.d.ts +25 -8
- package/dist/apps/shared/components/mail/MessageDetailPane.js +167 -73
- package/dist/apps/shared/components/mail/OutboxBadge.d.ts +17 -0
- package/dist/apps/shared/components/mail/OutboxBadge.js +18 -0
- package/dist/apps/shared/components/mail/OutboxRowStatus.d.ts +10 -0
- package/dist/apps/shared/components/mail/OutboxRowStatus.js +19 -0
- package/dist/apps/shared/components/mail/compose/ComposeContext.d.ts +38 -0
- package/dist/apps/shared/components/mail/compose/ComposeContext.js +97 -6
- package/dist/apps/shared/components/mail/compose/ComposeToolbar.js +4 -3
- package/dist/apps/shared/components/mail/compose/ComposeWindow.d.ts +3 -2
- package/dist/apps/shared/components/mail/compose/ComposeWindow.js +316 -339
- package/dist/apps/shared/components/mail/compose/ComposeWindowPlaceholder.d.ts +19 -0
- package/dist/apps/shared/components/mail/compose/ComposeWindowPlaceholder.js +33 -0
- package/dist/apps/shared/components/mail/compose/RichTextEditor.d.ts +9 -1
- package/dist/apps/shared/components/mail/compose/RichTextEditor.js +20 -2
- package/dist/apps/shared/components/mail/compose/composePerf.d.ts +16 -0
- package/dist/apps/shared/components/mail/compose/composePerf.js +41 -0
- package/dist/apps/shared/components/mail/compose/encryptionRequirement.d.ts +49 -0
- package/dist/apps/shared/components/mail/compose/encryptionRequirement.js +35 -0
- package/dist/apps/shared/components/mail/compose/quotedBody.d.ts +13 -0
- package/dist/apps/shared/components/mail/compose/quotedBody.js +67 -11
- package/dist/apps/shared/components/mail/layout/MailShell.d.ts +14 -4
- package/dist/apps/shared/components/mail/layout/MailShell.js +90 -104
- package/dist/apps/shared/components/mail/reading/EncryptedBody.d.ts +21 -0
- package/dist/apps/shared/components/mail/reading/EncryptedBody.js +22 -0
- package/dist/apps/shared/components/mail/reading/EncryptedPreview.d.ts +18 -0
- package/dist/apps/shared/components/mail/reading/EncryptedPreview.js +22 -0
- package/dist/apps/shared/components/mail/reading/MessageBody.d.ts +32 -0
- package/dist/apps/shared/components/mail/reading/MessageBody.js +136 -0
- package/dist/apps/shared/components/mail/reading/MessageCard.d.ts +62 -0
- package/dist/apps/shared/components/mail/reading/MessageCard.js +58 -0
- package/dist/apps/shared/components/mail/reading/bodyContent.d.ts +38 -0
- package/dist/apps/shared/components/mail/reading/bodyContent.js +84 -0
- package/dist/apps/shared/components/mail/reading/bodyHtml.d.ts +47 -0
- package/dist/apps/shared/components/mail/reading/bodyHtml.js +267 -0
- package/dist/apps/shared/components/mail/reading/color.d.ts +63 -0
- package/dist/apps/shared/components/mail/reading/color.js +201 -0
- package/dist/apps/shared/components/mail/reading/frameControl.d.ts +33 -0
- package/dist/apps/shared/components/mail/reading/frameControl.js +120 -0
- package/dist/apps/shared/components/mail/reading/frameDocument.d.ts +52 -0
- package/dist/apps/shared/components/mail/reading/frameDocument.js +98 -0
- package/dist/apps/shared/components/mail/reading/safeDocument.d.ts +3 -0
- package/dist/apps/shared/components/mail/reading/safeDocument.js +12 -0
- package/dist/apps/shared/components/mail/reading/themeAdaptation.d.ts +56 -0
- package/dist/apps/shared/components/mail/reading/themeAdaptation.js +144 -0
- package/dist/apps/shared/components/mail/reading/themeSurface.d.ts +17 -0
- package/dist/apps/shared/components/mail/reading/themeSurface.js +83 -0
- package/dist/apps/shared/components/mail/reading/viewOriginal.d.ts +9 -0
- package/dist/apps/shared/components/mail/reading/viewOriginal.js +32 -0
- package/dist/apps/shared/components/mail/unreadStyle.d.ts +45 -0
- package/dist/apps/shared/components/mail/unreadStyle.js +61 -0
- package/dist/apps/shared/components/settings/SigningCertificateCard.d.ts +32 -0
- package/dist/apps/shared/components/settings/SigningCertificateCard.js +98 -0
- package/dist/apps/shared/components/settings/layout/SettingsShell.d.ts +1 -1
- package/dist/apps/shared/components/settings/layout/SettingsShell.js +16 -5
- package/dist/apps/shared/components/sharing/PrincipalPicker.d.ts +26 -0
- package/dist/apps/shared/components/sharing/PrincipalPicker.js +77 -0
- package/dist/apps/shared/components/tasks/TasksSidebar.js +4 -2
- package/dist/apps/shared/components/tasks/layout/TasksShell.d.ts +1 -1
- package/dist/apps/shared/components/tasks/layout/TasksShell.js +18 -6
- package/dist/apps/shared/keyboard/GlobalShortcuts.d.ts +14 -0
- package/dist/apps/shared/keyboard/GlobalShortcuts.js +37 -0
- package/dist/apps/shared/keyboard/ShortcutProvider.d.ts +20 -0
- package/dist/apps/shared/keyboard/ShortcutProvider.js +52 -0
- package/dist/apps/shared/keyboard/ShortcutsDialog.d.ts +14 -0
- package/dist/apps/shared/keyboard/ShortcutsDialog.js +42 -0
- package/dist/apps/shared/keyboard/dispatch.d.ts +16 -0
- package/dist/apps/shared/keyboard/dispatch.js +109 -0
- package/dist/apps/shared/keyboard/format.d.ts +12 -0
- package/dist/apps/shared/keyboard/format.js +74 -0
- package/dist/apps/shared/keyboard/keymap.d.ts +294 -0
- package/dist/apps/shared/keyboard/keymap.js +84 -0
- package/dist/apps/shared/keyboard/match.d.ts +20 -0
- package/dist/apps/shared/keyboard/match.js +31 -0
- package/dist/apps/shared/keyboard/parse.d.ts +31 -0
- package/dist/apps/shared/keyboard/parse.js +107 -0
- package/dist/apps/shared/keyboard/platform.d.ts +15 -0
- package/dist/apps/shared/keyboard/platform.js +22 -0
- package/dist/apps/shared/keyboard/registry.d.ts +39 -0
- package/dist/apps/shared/keyboard/registry.js +33 -0
- package/dist/apps/shared/keyboard/targets.d.ts +14 -0
- package/dist/apps/shared/keyboard/targets.js +67 -0
- package/dist/apps/shared/keyboard/useShortcut.d.ts +19 -0
- package/dist/apps/shared/keyboard/useShortcut.js +35 -0
- package/dist/apps/shared/keyboard/useShortcutProps.d.ts +10 -0
- package/dist/apps/shared/keyboard/useShortcutProps.js +15 -0
- package/dist/apps/shared/mail/folderCounts.d.ts +78 -0
- package/dist/apps/shared/mail/folderCounts.js +218 -0
- package/dist/apps/shared/mail/folderTree.d.ts +55 -0
- package/dist/apps/shared/mail/folderTree.js +116 -0
- package/dist/apps/shared/mail/listSnapshots.d.ts +46 -0
- package/dist/apps/shared/mail/listSnapshots.js +43 -0
- package/dist/apps/shared/mail/messageReadState.d.ts +32 -0
- package/dist/apps/shared/mail/messageReadState.js +63 -0
- package/dist/apps/shared/mail/newMailNotifications.d.ts +62 -0
- package/dist/apps/shared/mail/newMailNotifications.js +138 -0
- package/dist/apps/shared/mail/outbox/UnlockBridge.d.ts +5 -0
- package/dist/apps/shared/mail/outbox/UnlockBridge.js +18 -0
- package/dist/apps/shared/mail/outbox/composeBridge.d.ts +34 -0
- package/dist/apps/shared/mail/outbox/composeBridge.js +32 -0
- package/dist/apps/shared/mail/outbox/outboxState.d.ts +41 -0
- package/dist/apps/shared/mail/outbox/outboxState.js +42 -0
- package/dist/apps/shared/mail/outbox/pendingSends.d.ts +39 -0
- package/dist/apps/shared/mail/outbox/pendingSends.js +109 -0
- package/dist/apps/shared/mail/outbox/sendDecision.d.ts +80 -0
- package/dist/apps/shared/mail/outbox/sendDecision.js +94 -0
- package/dist/apps/shared/mail/outbox/sendJob.d.ts +68 -0
- package/dist/apps/shared/mail/outbox/sendJob.js +319 -0
- package/dist/apps/shared/mail/outbox/sendOutcomes.d.ts +3 -0
- package/dist/apps/shared/mail/outbox/sendOutcomes.js +141 -0
- package/dist/apps/shared/mail/outbox/sendState.d.ts +25 -0
- package/dist/apps/shared/mail/outbox/sendState.js +8 -0
- package/dist/apps/shared/mail/outbox/useOutboxStatus.d.ts +11 -0
- package/dist/apps/shared/mail/outbox/useOutboxStatus.js +49 -0
- package/dist/apps/shared/mail/useMailConnection.d.ts +56 -0
- package/dist/apps/shared/mail/useMailConnection.js +138 -0
- package/dist/apps/shared/mail/useMailLiveUpdates.d.ts +29 -9
- package/dist/apps/shared/mail/useMailLiveUpdates.js +60 -32
- package/dist/apps/shared/mail/useMarkMessageRead.d.ts +12 -0
- package/dist/apps/shared/mail/useMarkMessageRead.js +44 -0
- package/dist/apps/shared/mail/useNewMailNotifications.d.ts +35 -0
- package/dist/apps/shared/mail/useNewMailNotifications.js +130 -0
- package/dist/apps/shared/mail/useUnreadTitle.d.ts +18 -0
- package/dist/apps/shared/mail/useUnreadTitle.js +31 -0
- package/dist/apps/shared/navigation/AppRouter.d.ts +52 -0
- package/dist/apps/shared/navigation/AppRouter.js +242 -0
- package/dist/apps/shared/navigation/appHrefs.d.ts +14 -0
- package/dist/apps/shared/navigation/appHrefs.js +20 -0
- package/dist/apps/shared/navigation/frameContext.d.ts +19 -0
- package/dist/apps/shared/navigation/frameContext.js +24 -0
- package/dist/apps/shared/navigation/idle.d.ts +14 -0
- package/dist/apps/shared/navigation/idle.js +43 -0
- package/dist/apps/shared/navigation/routerContext.d.ts +37 -0
- package/dist/apps/shared/navigation/routerContext.js +56 -0
- package/dist/apps/shared/navigation/routes.d.ts +32 -0
- package/dist/apps/shared/navigation/routes.js +37 -0
- package/dist/apps/shared/notifications/NotificationCenter.d.ts +19 -0
- package/dist/apps/shared/notifications/NotificationCenter.js +90 -0
- package/dist/apps/shared/notifications/NotificationHistoryDialog.d.ts +10 -0
- package/dist/apps/shared/notifications/NotificationHistoryDialog.js +36 -0
- package/dist/apps/shared/notifications/apiErrors.d.ts +19 -0
- package/dist/apps/shared/notifications/apiErrors.js +86 -0
- package/dist/apps/shared/notifications/headerOffset.d.ts +8 -0
- package/dist/apps/shared/notifications/headerOffset.js +33 -0
- package/dist/apps/shared/notifications/pushStatus.d.ts +9 -0
- package/dist/apps/shared/notifications/pushStatus.js +54 -0
- package/dist/apps/shared/notifications/store.d.ts +136 -0
- package/dist/apps/shared/notifications/store.js +416 -0
- package/dist/apps/shared/notifications/systemErrors.d.ts +9 -0
- package/dist/apps/shared/notifications/systemErrors.js +58 -0
- package/dist/apps/shared/notifications/useNotifications.d.ts +17 -0
- package/dist/apps/shared/notifications/useNotifications.js +18 -0
- package/dist/apps/shared/search/LocalIndexLifecycle.js +17 -3
- package/dist/apps/shared/signing/enrollmentStorage.d.ts +10 -0
- package/dist/apps/shared/signing/enrollmentStorage.js +33 -0
- package/dist/apps/shared/signing/enrollmentTracker.d.ts +73 -0
- package/dist/apps/shared/signing/enrollmentTracker.js +307 -0
- package/dist/apps/shared/signing/enrollmentView.d.ts +57 -0
- package/dist/apps/shared/signing/enrollmentView.js +214 -0
- package/dist/apps/shared/signing/signingInfo.d.ts +11 -0
- package/dist/apps/shared/signing/signingInfo.js +48 -0
- package/dist/apps/shared/signing/useNow.d.ts +2 -0
- package/dist/apps/shared/signing/useNow.js +18 -0
- package/dist/apps/shared/signing/useSigningEnrollmentWatcher.d.ts +16 -0
- package/dist/apps/shared/signing/useSigningEnrollmentWatcher.js +87 -0
- package/dist/apps/shared/styles/app.css +108 -10
- package/dist/apps/www/_layout.d.ts +5 -1
- package/dist/apps/www/_layout.js +3 -2
- package/dist/apps/www/_routedPage.d.ts +12 -0
- package/dist/apps/www/_routedPage.js +19 -0
- package/dist/apps/www/_routes.d.ts +11 -0
- package/dist/apps/www/_routes.js +30 -0
- package/dist/apps/www/calendar/index.d.ts +2 -2
- package/dist/apps/www/calendar/index.js +41 -11
- package/dist/apps/www/contacts/[uid].d.ts +3 -9
- package/dist/apps/www/contacts/[uid].js +11 -9
- package/dist/apps/www/contacts/index.d.ts +2 -2
- package/dist/apps/www/contacts/index.js +30 -24
- package/dist/apps/www/index.d.ts +2 -2
- package/dist/apps/www/index.js +332 -48
- package/dist/apps/www/messages/[uid].d.ts +3 -7
- package/dist/apps/www/messages/[uid].js +10 -5
- package/dist/apps/www/settings/appearance/index.d.ts +4 -0
- package/dist/apps/www/settings/appearance/index.js +13 -0
- package/dist/apps/www/settings/auto-reply/index.d.ts +2 -2
- package/dist/apps/www/settings/auto-reply/index.js +6 -7
- package/dist/apps/www/settings/encryption/index.d.ts +2 -2
- package/dist/apps/www/settings/encryption/index.js +106 -96
- package/dist/apps/www/settings/filters/[uid].d.ts +2 -2
- package/dist/apps/www/settings/filters/[uid].js +6 -2
- package/dist/apps/www/settings/filters/index.d.ts +2 -2
- package/dist/apps/www/settings/filters/index.js +3 -1
- package/dist/apps/www/settings/filters/new/index.d.ts +2 -2
- package/dist/apps/www/settings/filters/new/index.js +9 -3
- package/dist/apps/www/settings/labels/index.d.ts +2 -2
- package/dist/apps/www/settings/labels/index.js +3 -1
- package/dist/apps/www/settings/privacy/index.d.ts +2 -2
- package/dist/apps/www/settings/privacy/index.js +8 -9
- package/dist/apps/www/settings/read-receipts/index.d.ts +2 -2
- package/dist/apps/www/settings/read-receipts/index.js +6 -7
- package/dist/apps/www/settings/sharing/index.d.ts +2 -2
- package/dist/apps/www/settings/sharing/index.js +22 -34
- package/dist/apps/www/settings/signatures/[uid].d.ts +2 -2
- package/dist/apps/www/settings/signatures/[uid].js +6 -2
- package/dist/apps/www/settings/signatures/index.d.ts +2 -2
- package/dist/apps/www/settings/signatures/index.js +3 -1
- package/dist/apps/www/settings/signatures/new/index.d.ts +2 -2
- package/dist/apps/www/settings/signatures/new/index.js +9 -4
- package/dist/apps/www/tasks/index.d.ts +2 -2
- package/dist/apps/www/tasks/index.js +22 -16
- package/package.json +2 -2
- package/apps/shared/components/mail/compose/SendFailureAlert.tsx +0 -48
- package/dist/apps/shared/components/mail/compose/SendFailureAlert.d.ts +0 -14
- package/dist/apps/shared/components/mail/compose/SendFailureAlert.js +0 -11
|
@@ -1,15 +1,19 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
///////////////////////////////////////////////////////////////////////////////
|
|
3
3
|
// Copyright (C) 2026 Jean-Philippe Steinmetz
|
|
4
4
|
// SPDX-License-Identifier: MPL-2.0
|
|
5
5
|
///////////////////////////////////////////////////////////////////////////////
|
|
6
6
|
import { useEffect, useLayoutEffect, useRef, useState } from "react";
|
|
7
7
|
import { ApiRequestError } from "@rapidmx/react-shared/util/api.js";
|
|
8
|
-
import { listAttachments
|
|
8
|
+
import { listAttachments } from "@rapidmx/react-shared/mail/mailApi.js";
|
|
9
9
|
import { listConversationMessages } from "@rapidmx/react-shared/mail/conversationsApi.js";
|
|
10
10
|
import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
|
|
11
|
-
import MailAddress from "./MailAddress.js";
|
|
12
11
|
import MessageDetailPane from "./MessageDetailPane.js";
|
|
12
|
+
import { EncryptedPreview, displaySubject } from "./reading/EncryptedPreview.js";
|
|
13
|
+
import { CollapsedCard, SkeletonCards, SubjectCard } from "./reading/MessageCard.js";
|
|
14
|
+
import { useMailShell } from "./layout/MailShell.js";
|
|
15
|
+
import { setReadState } from "../../mail/messageReadState.js";
|
|
16
|
+
import { dateClass, isUnread, senderClass } from "./unreadStyle.js";
|
|
13
17
|
/** One request's worth of the thread. The server's own default for `listConversationMessages()`. */
|
|
14
18
|
export const THREAD_PAGE_SIZE = 100;
|
|
15
19
|
/**
|
|
@@ -73,8 +77,18 @@ async function loadThread(mailboxUid, conversationId) {
|
|
|
73
77
|
messages.reverse();
|
|
74
78
|
return { messages, truncated: more };
|
|
75
79
|
}
|
|
80
|
+
/** Whether the browser is showing a focus ring on `element` - it was focused from the keyboard (or by a key press's handler), not by a click. */
|
|
81
|
+
function hasFocusRing(element) {
|
|
82
|
+
try {
|
|
83
|
+
return element.matches(":focus-visible");
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
76
89
|
/**
|
|
77
|
-
* The reading pane for the conversation list: the
|
|
90
|
+
* The reading pane for the conversation list: the conversation's subject in a card of its own, pinned at the top, and under it the whole thread as
|
|
91
|
+
* a stack of message cards (each exactly as tall as its message; the list scrolls as a whole), **newest at the top**, opened at the message
|
|
78
92
|
* the reader picked. Every message from that one through to the newest is expanded - which in this order is
|
|
79
93
|
* the opened message and the entries above it - and the older ones, below it, are collapsed to a one-line
|
|
80
94
|
* summary (sender, date, preview) that expands on click or Enter. So opening the newest message shows just
|
|
@@ -90,7 +104,8 @@ async function loadThread(mailboxUid, conversationId) {
|
|
|
90
104
|
* single-message pane, and each acts on the message it belongs to. A collapsed message mounts none of
|
|
91
105
|
* that - mounting a body iframe per message up front would be wasteful in a long thread.
|
|
92
106
|
*/
|
|
93
|
-
export default function ConversationThreadPane({ conversation, mailboxUid, selectedUid, folders, labels, onMessagePatched, onMessageRemoved, onLabelCreated, onFolderCreated, }) {
|
|
107
|
+
export default function ConversationThreadPane({ conversation, mailboxUid, selectedUid, folders, labels, onMessagePatched, onMessageRemoved, onLabelCreated, onFolderCreated, shortcuts, }) {
|
|
108
|
+
const { trackMessageChange } = useMailShell();
|
|
94
109
|
const [messages, setMessages] = useState([]);
|
|
95
110
|
const [attachmentsByUid, setAttachmentsByUid] = useState({});
|
|
96
111
|
const [expandedUids, setExpandedUids] = useState(new Set());
|
|
@@ -116,6 +131,9 @@ export default function ConversationThreadPane({ conversation, mailboxUid, selec
|
|
|
116
131
|
/** Where the toggled message's header sat in the viewport before it expanded, so the run below can put
|
|
117
132
|
* it back there - expanding a message above the one being read must not shove that one off-screen. */
|
|
118
133
|
const anchorRef = useRef(null);
|
|
134
|
+
/** The message whose header button had the focus when it was toggled: expanding or collapsing swaps that button for the other state's, so the
|
|
135
|
+
* focus is put back on the new one. */
|
|
136
|
+
const refocusRef = useRef(null);
|
|
119
137
|
const conversationId = conversation?.conversationId;
|
|
120
138
|
useEffect(() => {
|
|
121
139
|
const generation = ++generationRef.current;
|
|
@@ -192,8 +210,15 @@ export default function ConversationThreadPane({ conversation, mailboxUid, selec
|
|
|
192
210
|
scroller.scrollTop += top;
|
|
193
211
|
}
|
|
194
212
|
}
|
|
195
|
-
//
|
|
196
|
-
|
|
213
|
+
// A key press (j, k, the arrows) that opened this thread left the focus on its list row on purpose, so Enter goes on to open the
|
|
214
|
+
// message's page; taking the focus here would make Enter toggle this header instead. A click leaves no focus ring, and hands
|
|
215
|
+
// the focus to the thread as before.
|
|
216
|
+
const active = document.activeElement;
|
|
217
|
+
if (!(active instanceof HTMLElement && active.hasAttribute("data-row-open") && hasFocusRing(active))) {
|
|
218
|
+
// `preventScroll` so focusing doesn't scroll it somewhere else again.
|
|
219
|
+
// (A card's header button is drawn by the message pane, which registers it; a stand-in pane that doesn't has none to focus.)
|
|
220
|
+
headerRefs.current[pendingFocusUid]?.focus({ preventScroll: true });
|
|
221
|
+
}
|
|
197
222
|
setPendingFocusUid(null);
|
|
198
223
|
}, [pendingFocusUid, expandedUids]);
|
|
199
224
|
// Keeps the message whose header was just clicked where it was on screen. Expanding one above the
|
|
@@ -206,6 +231,11 @@ export default function ConversationThreadPane({ conversation, mailboxUid, selec
|
|
|
206
231
|
// The row is still there: the anchor was taken from a rendered row, and toggling never removes one.
|
|
207
232
|
const row = rowRefs.current[anchor.uid];
|
|
208
233
|
scrollingAncestor(row).scrollTop += row.getBoundingClientRect().top - anchor.top;
|
|
234
|
+
// The button the reader had focused is gone (a collapsed card and an expanded one each draw their own): keep them where they were.
|
|
235
|
+
if (refocusRef.current === anchor.uid) {
|
|
236
|
+
headerRefs.current[anchor.uid]?.focus({ preventScroll: true });
|
|
237
|
+
}
|
|
238
|
+
refocusRef.current = null;
|
|
209
239
|
}, [expandedUids]);
|
|
210
240
|
// Attachments and mark-as-read, for expanded messages only - mirrors `mailDetailHooks.ts`'s
|
|
211
241
|
// `useMessageAttachments`/`useMarkMessageRead`, reimplemented here (rather than called in a loop, which
|
|
@@ -230,20 +260,25 @@ export default function ConversationThreadPane({ conversation, mailboxUid, selec
|
|
|
230
260
|
attachmentsRequestedRef.current.delete(uid);
|
|
231
261
|
});
|
|
232
262
|
}
|
|
233
|
-
if (!
|
|
263
|
+
if (!markReadRequestedRef.current.has(uid)) {
|
|
264
|
+
// Asked once per opened conversation and per expanding of the message: a failure is not retried from here, because
|
|
265
|
+
// undoing the optimistic change changes `messages`, which would run this effect again and ask again, for ever. A message
|
|
266
|
+
// that is already read is asked about too (there is nothing to send), so marking it unread while it is open - the
|
|
267
|
+
// keyboard's Ctrl+U - doesn't make this run again and read it straight back.
|
|
234
268
|
markReadRequestedRef.current.add(uid);
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
269
|
+
if (message.flags.read === true) {
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
// `message` is this render's copy, so the request carries its current `version`. The row, the conversation row's
|
|
273
|
+
// unread count and the folder badge all change at once; `previous` is what tells the list's conversation row
|
|
274
|
+
// that its count moved (see `setReadState()`).
|
|
275
|
+
void setReadState(message, true, {
|
|
276
|
+
patch: (updated, previous) => {
|
|
277
|
+
if (generation === generationRef.current) {
|
|
278
|
+
patchMessage(updated, previous);
|
|
279
|
+
}
|
|
280
|
+
},
|
|
281
|
+
track: trackMessageChange,
|
|
247
282
|
});
|
|
248
283
|
}
|
|
249
284
|
}
|
|
@@ -260,8 +295,11 @@ export default function ConversationThreadPane({ conversation, mailboxUid, selec
|
|
|
260
295
|
onMessageRemoved(updated);
|
|
261
296
|
}
|
|
262
297
|
function toggleExpanded(uid) {
|
|
298
|
+
// Opening a message again asks to mark it read again - that is how one whose request failed is retried.
|
|
299
|
+
markReadRequestedRef.current.delete(uid);
|
|
263
300
|
// The row this button lives in has rendered, so its ref is set.
|
|
264
301
|
anchorRef.current = { uid, top: rowRefs.current[uid].getBoundingClientRect().top };
|
|
302
|
+
refocusRef.current = document.activeElement === headerRefs.current[uid] ? uid : null;
|
|
265
303
|
setExpandedUids((prev) => {
|
|
266
304
|
const next = new Set(prev);
|
|
267
305
|
if (next.has(uid)) {
|
|
@@ -273,35 +311,44 @@ export default function ConversationThreadPane({ conversation, mailboxUid, selec
|
|
|
273
311
|
return next;
|
|
274
312
|
});
|
|
275
313
|
}
|
|
314
|
+
// The keyboard acts on the message that was opened - or the newest, when the opened one isn't in this thread - never on all the expanded
|
|
315
|
+
// ones at once (each is a `MessageDetailPane`, and two of them must not both answer Ctrl+R).
|
|
316
|
+
const keyboardUid = messages.some((message) => message.uid === selectedUid) ? selectedUid : messages[0]?.uid;
|
|
276
317
|
function folderTypeOf(message) {
|
|
277
318
|
return folders.find((folder) => folder.uid === message.folderUid)?.type;
|
|
278
319
|
}
|
|
279
320
|
if (!conversation) {
|
|
280
321
|
return _jsx("p", { className: "p-8 text-sm text-text-muted", children: "Select a conversation to read it." });
|
|
281
322
|
}
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
323
|
+
const subject = displaySubject(conversation.subject) || "(no subject)";
|
|
324
|
+
// The newest message that is open carries the Reply / Forward buttons at the foot of its card.
|
|
325
|
+
const footerUid = messages.find((message) => expandedUids.has(message.uid))?.uid;
|
|
326
|
+
return (_jsxs("div", { className: "flex-1 min-w-0 min-h-0 flex flex-col", children: [_jsx(SubjectCard, { subject: subject, meta: loading
|
|
327
|
+
? conversation.messageCount > 1
|
|
328
|
+
? `${conversation.messageCount} messages`
|
|
329
|
+
: undefined
|
|
330
|
+
: error
|
|
331
|
+
? undefined
|
|
332
|
+
: `${messages.length} message${messages.length === 1 ? "" : "s"}`, children: truncated && !loading && !error && (_jsxs("p", { className: "text-xs text-text-muted mt-1", children: ["Only the oldest ", THREAD_MESSAGE_LIMIT, " messages of this conversation are shown here. The rest are still in the message list."] })) }), loading ? (_jsx(SkeletonCards, { messageCount: conversation.messageCount })) : error ? (_jsx("div", { className: "p-2 sm:p-3", children: _jsx(Alert, { children: error }) })) : (_jsx("ul", { className: "flex-1 min-h-0 overflow-y-auto p-2 sm:p-3 flex flex-col gap-3", children: messages.map((message) => {
|
|
289
333
|
const uid = message.uid;
|
|
290
334
|
const expanded = expandedUids.has(uid);
|
|
291
335
|
const bodyId = `thread-message-${uid}`;
|
|
292
|
-
|
|
336
|
+
const messageUnread = isUnread(message);
|
|
337
|
+
return (_jsx("li", { ref: (node) => {
|
|
293
338
|
rowRefs.current[uid] = node;
|
|
294
|
-
},
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
339
|
+
}, "data-unread": messageUnread ? "true" : undefined, children: expanded ? (_jsx(MessageDetailPane, { inThread: true, threadSubject: subject, threadHeader: {
|
|
340
|
+
bodyId,
|
|
341
|
+
unread: messageUnread,
|
|
342
|
+
onToggle: () => toggleExpanded(uid),
|
|
343
|
+
buttonRef: (node) => {
|
|
344
|
+
headerRefs.current[uid] = node;
|
|
345
|
+
},
|
|
346
|
+
}, footer: uid === footerUid, shortcuts: !!shortcuts && uid === keyboardUid, message: message, attachments: attachmentsByUid[uid] ?? [], isSentItems: folderTypeOf(message) === "sent_items", isOutbox: folderTypeOf(message) === "outbox", draftsFolderUid: folders.find((folder) => folder.type === "drafts")?.uid, folders: folders, onMoved: removeMessage, onFolderCreated: onFolderCreated, onRecalled: patchMessage, onReceiptHandled: patchMessage, onScheduledSendCanceled: removeMessage, onArchived: removeMessage, labels: labels, onLabelsChanged: patchMessage, onLabelCreated: onLabelCreated })) : (_jsxs(_Fragment, { children: [_jsx(CollapsedCard, { from: message.from, date: new Date(message.receivedDate).toLocaleString(), preview: message.bodyPreview || (message.encrypted ? _jsx(EncryptedPreview, {}) : ""), unread: messageUnread, senderClassName: senderClass(messageUnread), dateClassName: dateClass(messageUnread), buttonRef: (node) => {
|
|
301
347
|
headerRefs.current[uid] = node;
|
|
302
|
-
},
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
348
|
+
}, buttonProps: {
|
|
349
|
+
onClick: () => toggleExpanded(uid),
|
|
350
|
+
"aria-expanded": false,
|
|
351
|
+
"aria-controls": bodyId,
|
|
352
|
+
} }), _jsx("div", { id: bodyId, hidden: true })] })) }, uid));
|
|
353
|
+
}) }))] }));
|
|
307
354
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type MessageDetailPaneType from "./MessageDetailPane.js";
|
|
3
|
+
import type ConversationThreadPaneType from "./ConversationThreadPane.js";
|
|
4
|
+
/** Starts downloading the reading pane's code, for the page to call when it has nothing better to do. Never rejects. */
|
|
5
|
+
export declare function prefetchReadingPane(): void;
|
|
6
|
+
/** With nothing selected the pane is only its empty-state line, which needs none of the pane's code - so the chunk isn't
|
|
7
|
+
* requested until something is (or the page prefetches it). */
|
|
8
|
+
export declare function LazyMessageDetailPane(props: React.ComponentProps<typeof MessageDetailPaneType>): React.JSX.Element;
|
|
9
|
+
export declare function LazyConversationThreadPane(props: React.ComponentProps<typeof ConversationThreadPaneType>): React.JSX.Element;
|
|
@@ -0,0 +1,83 @@
|
|
|
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, useState } from "react";
|
|
7
|
+
import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
|
|
8
|
+
import Button from "@rapidmx/react-shared/components/buttons/Button.js";
|
|
9
|
+
import { ReadingPaneSkeleton } from "./reading/MessageCard.js";
|
|
10
|
+
/**
|
|
11
|
+
* The reading pane (`MessageDetailPane`, and the thread pane that stacks it) is only rendered once a message is selected, and
|
|
12
|
+
* it is the heaviest piece of the inbox after the compose window - message security, the body sanitizer, the labels and move
|
|
13
|
+
* menus - so it is a chunk of its own, downloaded when the browser is idle after the list has loaded (`prefetchReadingPane()`)
|
|
14
|
+
* and, at the latest, on the first selection. Nothing about the pane changes: these wrap the same components, with a skeleton
|
|
15
|
+
* while the chunk loads and a reload prompt if it can't.
|
|
16
|
+
*
|
|
17
|
+
* Deliberately not `React.lazy()` + `<Suspense>`: a boundary that has shown its fallback holds the content back for up to 300 ms
|
|
18
|
+
* after the code arrives (React's fallback throttle), which would put a fifth of a second between a click and a pane whose
|
|
19
|
+
* code was already fetched. This reads the loaded component synchronously when there is one, so an already-loaded pane renders
|
|
20
|
+
* on the click's own frame, and it can retry a failed download.
|
|
21
|
+
*/
|
|
22
|
+
function lazyComponent(load, skeleton) {
|
|
23
|
+
let loaded;
|
|
24
|
+
let loading;
|
|
25
|
+
/** Starts (once) loading the component's code. Rejects if it can't be downloaded - and then a later call tries again. */
|
|
26
|
+
function start() {
|
|
27
|
+
loading ?? (loading = load().then((module) => (loaded = module.default), (err) => {
|
|
28
|
+
loading = undefined;
|
|
29
|
+
throw err;
|
|
30
|
+
}));
|
|
31
|
+
return loading;
|
|
32
|
+
}
|
|
33
|
+
function Lazy(props) {
|
|
34
|
+
const [, setLoadedNow] = useState(0);
|
|
35
|
+
const [failed, setFailed] = useState(false);
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
if (loaded) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
let cancelled = false;
|
|
41
|
+
start().then(() => !cancelled && setLoadedNow((n) => n + 1), () => !cancelled && setFailed(true));
|
|
42
|
+
return () => {
|
|
43
|
+
cancelled = true;
|
|
44
|
+
};
|
|
45
|
+
}, []);
|
|
46
|
+
if (loaded) {
|
|
47
|
+
const Component = loaded;
|
|
48
|
+
return _jsx(Component, { ...props });
|
|
49
|
+
}
|
|
50
|
+
return failed ? _jsx(ChunkFailed, {}) : skeleton(props);
|
|
51
|
+
}
|
|
52
|
+
return { Lazy, start };
|
|
53
|
+
}
|
|
54
|
+
/** A chunk that can't be downloaded (offline, or replaced by a deploy since this page loaded) is the one failure a reading
|
|
55
|
+
* pane can't recover from by itself: say so, with the fix, rather than leaving a blank pane. */
|
|
56
|
+
function ChunkFailed() {
|
|
57
|
+
return (_jsx("div", { className: "flex-1 p-6", children: _jsxs(Alert, { children: [_jsx("p", { className: "mb-2", children: "This part of the page couldn\u2019t be loaded. Check your connection, then reload." }), _jsx(Button, { type: "button", variant: "secondary", className: "!w-auto", onClick: () => window.location.reload(), children: "Reload" })] }) }));
|
|
58
|
+
}
|
|
59
|
+
// While the code loads the pane already draws its frame - the header card with the subject the list row knows, and a card of placeholder lines - so
|
|
60
|
+
// the click is answered on its own frame and the messages only fill in.
|
|
61
|
+
const messageDetailPane = lazyComponent(() => import("./MessageDetailPane.js"),
|
|
62
|
+
// `LazyMessageDetailPane` renders the empty state, not this, for no message.
|
|
63
|
+
(props) => _jsx(ReadingPaneSkeleton, { subject: props.message.subject }));
|
|
64
|
+
const conversationThreadPane = lazyComponent(() => import("./ConversationThreadPane.js"), (props) => _jsx(ReadingPaneSkeleton, { subject: props.conversation.subject, messageCount: props.conversation.messageCount }));
|
|
65
|
+
/** Starts downloading the reading pane's code, for the page to call when it has nothing better to do. Never rejects. */
|
|
66
|
+
export function prefetchReadingPane() {
|
|
67
|
+
messageDetailPane.start().catch(() => undefined);
|
|
68
|
+
conversationThreadPane.start().catch(() => undefined);
|
|
69
|
+
}
|
|
70
|
+
/** With nothing selected the pane is only its empty-state line, which needs none of the pane's code - so the chunk isn't
|
|
71
|
+
* requested until something is (or the page prefetches it). */
|
|
72
|
+
export function LazyMessageDetailPane(props) {
|
|
73
|
+
if (!props.message) {
|
|
74
|
+
return _jsx("p", { className: "p-8 text-sm text-text-muted", children: "Select a message to read it." });
|
|
75
|
+
}
|
|
76
|
+
return _jsx(messageDetailPane.Lazy, { ...props });
|
|
77
|
+
}
|
|
78
|
+
export function LazyConversationThreadPane(props) {
|
|
79
|
+
if (!props.conversation) {
|
|
80
|
+
return _jsx("p", { className: "p-8 text-sm text-text-muted", children: "Select a conversation to read it." });
|
|
81
|
+
}
|
|
82
|
+
return _jsx(conversationThreadPane.Lazy, { ...props });
|
|
83
|
+
}
|
|
@@ -36,15 +36,17 @@ export interface MailSelectionBarProps {
|
|
|
36
36
|
onArchive: () => void;
|
|
37
37
|
/** Moves the selection into `folderUid`, resolving once the bulk update has settled. It resolves
|
|
38
38
|
* whether or not that update was rejected: a bulk update is applied element by element, so a failure
|
|
39
|
-
* is explained
|
|
40
|
-
* be claiming the move simply didn't happen. */
|
|
39
|
+
* is explained by a pop-up (see `notifyApiError()`), raised by the page along with the reload it triggers, rather than in the
|
|
40
|
+
* prompt, which would be claiming the move simply didn't happen. */
|
|
41
41
|
onMoveTo: (folderUid: string) => Promise<void>;
|
|
42
42
|
onReportJunk: () => void;
|
|
43
43
|
onDelete: () => void;
|
|
44
44
|
/** A bulk action is in flight - every action is held until it settles, since the next one would send
|
|
45
45
|
* `version`s the first has already superseded. */
|
|
46
46
|
busy: boolean;
|
|
47
|
-
|
|
47
|
+
/** The keyboard acts on this selection (Delete, Ctrl+Q, Ctrl+U, Insert - registered by the page): Mark read, Mark unread, Flag and
|
|
48
|
+
* Delete name their shortcut in the tooltip and `aria-keyshortcuts`. */
|
|
49
|
+
shortcuts?: boolean;
|
|
48
50
|
}
|
|
49
51
|
/**
|
|
50
52
|
* The header that replaces the list's own toolbar while select mode is on: how many rows are selected,
|
|
@@ -56,4 +58,4 @@ export interface MailSelectionBarProps {
|
|
|
56
58
|
* target folder is the one already being viewed is disabled with a reason rather than hidden, so the set
|
|
57
59
|
* of actions doesn't shift around between folders.
|
|
58
60
|
*/
|
|
59
|
-
export default function MailSelectionBar({ selected, listed, totals, onSelectAll, onClearSelection, onCancel, folders, currentFolderUid, labels, mailboxUid, onLabelCreated, onFolderCreated, onApplyLabels, onSetRead, onSetFlagged, onArchive, onMoveTo, onReportJunk, onDelete, busy,
|
|
61
|
+
export default function MailSelectionBar({ selected, listed, totals, onSelectAll, onClearSelection, onCancel, folders, currentFolderUid, labels, mailboxUid, onLabelCreated, onFolderCreated, onApplyLabels, onSetRead, onSetFlagged, onArchive, onMoveTo, onReportJunk, onDelete, busy, shortcuts, }: MailSelectionBarProps): React.JSX.Element;
|
|
@@ -4,9 +4,11 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
4
4
|
// SPDX-License-Identifier: MPL-2.0
|
|
5
5
|
///////////////////////////////////////////////////////////////////////////////
|
|
6
6
|
import { useState } from "react";
|
|
7
|
-
import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
|
|
8
7
|
import LabelMenuButton from "./labelMenu.js";
|
|
9
8
|
import MoveToFolderDialog, { MOVE_TARGET_TYPES } from "./MoveToFolderDialog.js";
|
|
9
|
+
import { ariaKeyShortcuts, withHint } from "../../keyboard/format.js";
|
|
10
|
+
import { SHORTCUTS } from "../../keyboard/keymap.js";
|
|
11
|
+
import { useKeyEnvironment } from "../../keyboard/ShortcutProvider.js";
|
|
10
12
|
function actionClassName() {
|
|
11
13
|
return "px-2 py-1 rounded-md text-sm text-text hover:bg-surface-alt disabled:opacity-50 disabled:hover:bg-transparent";
|
|
12
14
|
}
|
|
@@ -20,7 +22,12 @@ function actionClassName() {
|
|
|
20
22
|
* target folder is the one already being viewed is disabled with a reason rather than hidden, so the set
|
|
21
23
|
* of actions doesn't shift around between folders.
|
|
22
24
|
*/
|
|
23
|
-
export default function MailSelectionBar({ selected, listed, totals, onSelectAll, onClearSelection, onCancel, folders, currentFolderUid, labels, mailboxUid, onLabelCreated, onFolderCreated, onApplyLabels, onSetRead, onSetFlagged, onArchive, onMoveTo, onReportJunk, onDelete, busy,
|
|
25
|
+
export default function MailSelectionBar({ selected, listed, totals, onSelectAll, onClearSelection, onCancel, folders, currentFolderUid, labels, mailboxUid, onLabelCreated, onFolderCreated, onApplyLabels, onSetRead, onSetFlagged, onArchive, onMoveTo, onReportJunk, onDelete, busy, shortcuts, }) {
|
|
26
|
+
const env = useKeyEnvironment();
|
|
27
|
+
/** `title` and `aria-keyshortcuts` for an action the keyboard also does; `reason` (why it is disabled) wins the tooltip. */
|
|
28
|
+
const hint = (label, shortcut, reason) => shortcuts
|
|
29
|
+
? { title: reason ?? withHint(label, shortcut, env), "aria-keyshortcuts": ariaKeyShortcuts(shortcut, env) }
|
|
30
|
+
: { title: reason };
|
|
24
31
|
const selectedCount = totals?.selected ?? selected.length;
|
|
25
32
|
const listedCount = totals?.listed ?? listed.length;
|
|
26
33
|
// Counted on the rows that were ticked, but *emptied* on the messages: a ticked conversation whose
|
|
@@ -42,7 +49,7 @@ export default function MailSelectionBar({ selected, listed, totals, onSelectAll
|
|
|
42
49
|
// partially applied, and is left exactly as it is unless the reader touches that row.
|
|
43
50
|
const appliedToAll = labels.filter((label) => !none && selected.every((m) => m.labelUids?.includes(label.uid)));
|
|
44
51
|
const appliedToSome = labels.filter((label) => selected.some((m) => m.labelUids?.includes(label.uid)) && !appliedToAll.includes(label));
|
|
45
|
-
return (_jsxs("div", { className: "border-b border-border", children: [_jsx(MoveToFolderDialog, { open: movePrompt, onClose: () => setMovePrompt(false), mailboxUid: mailboxUid, folders: folders, currentFolderUid: currentFolderUid, count: selected.length, onMove: onMoveTo, onFolderCreated: onFolderCreated }), _jsxs("div", { className: "flex items-center gap-2 px-3 py-1.5 bg-surface-alt", children: [_jsxs("span", { "aria-live": "polite", className: "text-sm font-semibold text-text", children: [selectedCount, totals ? ` ${totals.noun}${selectedCount === 1 ? "" : "s"}` : "", " selected"] }), _jsx("button", { type: "button", onClick: onSelectAll, disabled: allSelected || listedCount === 0, className: actionClassName(), children: "Select all" }), _jsx("button", { type: "button", onClick: onClearSelection, disabled: selectedCount === 0, className: actionClassName(), children: "Clear" }), _jsx("button", { type: "button", onClick: onCancel, className: `${actionClassName()} ml-auto`, children: "Cancel" })] }), _jsxs("div", { className: "flex flex-wrap items-center gap-0.5 px-1.5 py-1", children: [_jsx("button", { type: "button", onClick: () => onSetRead(true), disabled: none || busy, className: actionClassName(), children: "Mark read" }), _jsx("button", { type: "button", onClick: () => onSetRead(false), disabled: none || busy, className: actionClassName(), children: "Mark unread" }), _jsx("button", { type: "button", onClick: () => onSetFlagged(true), disabled: none || busy, className: actionClassName(), children: "Flag" }), _jsx("button", { type: "button", onClick: () => onSetFlagged(false), disabled: none || busy, className: actionClassName(), children: "Unflag" }), _jsx("button", { type: "button", onClick: onArchive, disabled: none || busy || !!archiveReason, title: archiveReason, className: actionClassName(), children: "Archive" }), _jsx(LabelMenuButton, { "aria-label": "Apply label", label: "Apply label", className: "text-sm", labels: labels, mailboxUid: mailboxUid, onLabelCreated: onLabelCreated, applied: appliedToAll.map((label) => label.uid), partial: appliedToSome.map((label) => label.uid), onCommit: onApplyLabels, busy: busy, disabled: none || busy, note: selected.length === 1
|
|
52
|
+
return (_jsxs("div", { className: "border-b border-border", children: [_jsx(MoveToFolderDialog, { open: movePrompt, onClose: () => setMovePrompt(false), mailboxUid: mailboxUid, folders: folders, currentFolderUid: currentFolderUid, count: selected.length, onMove: onMoveTo, onFolderCreated: onFolderCreated }), _jsxs("div", { className: "flex items-center gap-2 px-3 py-1.5 bg-surface-alt", children: [_jsxs("span", { "aria-live": "polite", className: "text-sm font-semibold text-text", children: [selectedCount, totals ? ` ${totals.noun}${selectedCount === 1 ? "" : "s"}` : "", " selected"] }), _jsx("button", { type: "button", onClick: onSelectAll, disabled: allSelected || listedCount === 0, className: actionClassName(), children: "Select all" }), _jsx("button", { type: "button", onClick: onClearSelection, disabled: selectedCount === 0, className: actionClassName(), children: "Clear" }), _jsx("button", { type: "button", onClick: onCancel, className: `${actionClassName()} ml-auto`, children: "Cancel" })] }), _jsxs("div", { className: "flex flex-wrap items-center gap-0.5 px-1.5 py-1", children: [_jsx("button", { type: "button", onClick: () => onSetRead(true), disabled: none || busy, className: actionClassName(), ...hint("Mark read", SHORTCUTS.mail.markRead), children: "Mark read" }), _jsx("button", { type: "button", onClick: () => onSetRead(false), disabled: none || busy, className: actionClassName(), ...hint("Mark unread", SHORTCUTS.mail.markUnread), children: "Mark unread" }), _jsx("button", { type: "button", onClick: () => onSetFlagged(true), disabled: none || busy, className: actionClassName(), ...hint("Flag", SHORTCUTS.mail.flag), children: "Flag" }), _jsx("button", { type: "button", onClick: () => onSetFlagged(false), disabled: none || busy, className: actionClassName(), children: "Unflag" }), _jsx("button", { type: "button", onClick: onArchive, disabled: none || busy || !!archiveReason, title: archiveReason, className: actionClassName(), children: "Archive" }), _jsx(LabelMenuButton, { "aria-label": "Apply label", label: "Apply label", className: "text-sm", labels: labels, mailboxUid: mailboxUid, onLabelCreated: onLabelCreated, applied: appliedToAll.map((label) => label.uid), partial: appliedToSome.map((label) => label.uid), onCommit: onApplyLabels, busy: busy, disabled: none || busy, note: selected.length === 1
|
|
46
53
|
? "Ticked labels are applied, unticked ones removed."
|
|
47
|
-
: "Ticked labels are applied to every selected message and unticked ones removed from all of them; a dash means only some have that label, and leaving it alone keeps it that way.", emptyNote: "This mailbox has no labels yet.", commit: { label: "Apply" }, clear: { label: "Remove all labels" } }), _jsx("button", { type: "button", onClick: () => setMovePrompt(true), disabled: none || busy, className: actionClassName(), children: "Move to" }), _jsx("button", { type: "button", onClick: onReportJunk, disabled: none || busy || !!junkReason, title: junkReason, className: actionClassName(), children: "Report junk" }), _jsx("button", { type: "button", onClick: onDelete, disabled: none || busy || !!deleteReason,
|
|
54
|
+
: "Ticked labels are applied to every selected message and unticked ones removed from all of them; a dash means only some have that label, and leaving it alone keeps it that way.", emptyNote: "This mailbox has no labels yet.", commit: { label: "Apply" }, clear: { label: "Remove all labels" } }), _jsx("button", { type: "button", onClick: () => setMovePrompt(true), disabled: none || busy, className: actionClassName(), children: "Move to" }), _jsx("button", { type: "button", onClick: onReportJunk, disabled: none || busy || !!junkReason, title: junkReason, className: actionClassName(), children: "Report junk" }), _jsx("button", { type: "button", onClick: onDelete, disabled: none || busy || !!deleteReason, className: actionClassName(), ...hint("Delete", SHORTCUTS.mail.delete, deleteReason), children: "Delete" })] })] }));
|
|
48
55
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { Attachment, Folder, Message } from "@rapidmx/react-shared/mail/mailApi.js";
|
|
3
3
|
import { Label } from "@rapidmx/react-shared/mail/labelsApi.js";
|
|
4
|
-
import { MessageSecurityResult } from "@rapidmx/react-shared/crypto/messageSecurity.js";
|
|
4
|
+
import type { MessageSecurityResult } from "@rapidmx/react-shared/crypto/messageSecurity.js";
|
|
5
5
|
/** The detail line under a `verified_at_first_open` badge. */
|
|
6
6
|
export declare function verifiedAtFirstOpenMessage(result: MessageSecurityResult): string;
|
|
7
7
|
export interface SenderNameCheck {
|
|
@@ -24,9 +24,6 @@ export declare function trustSignerErrorMessage(err: unknown): string;
|
|
|
24
24
|
/** Groups a fingerprint into 4-character blocks (`AB12 CD34 ...`), ignoring any separators it came with, so it can be
|
|
25
25
|
* read out and compared with the sender over another channel. */
|
|
26
26
|
export declare function formatFingerprint(fingerprint: string): string;
|
|
27
|
-
/** Sanitizes a client-rendered body for `srcDoc` (DOMPurify plus every remote resource reference stripped - see
|
|
28
|
-
* react-shared's `messageBodySanitizer.ts`), with the CSP meta as its very first element. */
|
|
29
|
-
export declare function buildSecureSrcDoc(html: string): string;
|
|
30
27
|
export interface MessageDetailPaneProps {
|
|
31
28
|
message: Message | null;
|
|
32
29
|
attachments: Attachment[];
|
|
@@ -86,13 +83,33 @@ export interface MessageDetailPaneProps {
|
|
|
86
83
|
/** Handed a label created from the Labels menu, for the caller to add to `labels`. Without it the menu
|
|
87
84
|
* offers no "New label" row, only the link to where labels are managed. */
|
|
88
85
|
onLabelCreated?: (label: Label) => void;
|
|
89
|
-
/** Rendered as one message of a thread (`ConversationThreadPane`) rather than as the pane itself: the
|
|
90
|
-
* subject
|
|
91
|
-
*
|
|
86
|
+
/** Rendered as one message of a thread (`ConversationThreadPane`) rather than as the pane itself: only its card is drawn - the thread
|
|
87
|
+
* owns the subject card, the scrolling and the `h1` - and the message's own subject appears (as an `h3`) only when it differs from the
|
|
88
|
+
* thread's (`threadSubject`). Everything else - the badges, the actions, the body - is identical. */
|
|
92
89
|
inThread?: boolean;
|
|
90
|
+
/** The thread's subject, for a message of a thread to tell whether its own subject is worth showing. */
|
|
91
|
+
threadSubject?: string;
|
|
92
|
+
/** In a thread, what makes the sender line of this expanded card the button that collapses it: the id of the body it controls, whether the
|
|
93
|
+
* message is unread (which draws the accent bar and tint), what it does, and where to put its ref. */
|
|
94
|
+
threadHeader?: {
|
|
95
|
+
bodyId: string;
|
|
96
|
+
unread: boolean;
|
|
97
|
+
onToggle: () => void;
|
|
98
|
+
buttonRef: (node: HTMLButtonElement | null) => void;
|
|
99
|
+
};
|
|
100
|
+
/** Draw the Reply and Forward buttons at the foot of the card - by default for the single-message pane, and in a thread only where the
|
|
101
|
+
* caller asks (the newest expanded message). */
|
|
102
|
+
footer?: boolean;
|
|
103
|
+
/**
|
|
104
|
+
* Registers this pane's keyboard shortcuts - Reply, Reply all, Forward, Archive, Move to - so they act on this message. Only the message
|
|
105
|
+
* the keyboard is acting on should set it: the one pane beside the list, or the opened message of a thread (each expanded message of a
|
|
106
|
+
* thread is a pane, and two of them must not both claim Ctrl+R). Also puts the shortcut in those buttons' tooltips.
|
|
107
|
+
*/
|
|
108
|
+
shortcuts?: boolean;
|
|
93
109
|
}
|
|
94
110
|
/**
|
|
95
|
-
* A message's reading pane
|
|
111
|
+
* A message's reading pane - a subject card, and under it the message as a card: header (sender, recipients, time, actions), notices, attachments,
|
|
112
|
+
* the body (`reading/MessageBody`: an isolated frame exactly as tall as its content, or text) and a footer. Rendered with `inThread`, only the card.
|
|
96
113
|
* Shared by the desktop inline pane (`apps/www/index.tsx`, always visible alongside the message list),
|
|
97
114
|
* and the mobile detail route (`apps/www/messages/[uid].tsx`, a full page on its own reached by tapping
|
|
98
115
|
* a message row) — see each call site for how `message`/`attachments`/`isSentItems` are sourced. The
|