@rapidmx/web-client 0.30.0 → 0.31.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/apps/admin/audit-log/index.tsx +3 -3
- package/apps/admin/branding/index.tsx +3 -3
- package/apps/admin/data-requests/index.tsx +18 -7
- package/apps/admin/distribution-lists/[uid].tsx +3 -3
- package/apps/admin/distribution-lists/index.tsx +3 -3
- package/apps/admin/distribution-lists/new/index.tsx +3 -3
- package/apps/admin/domains/[uid].tsx +6 -5
- package/apps/admin/domains/index.tsx +3 -3
- package/apps/admin/domains/new/index.tsx +3 -3
- package/apps/admin/escrow-scopes/[uid].tsx +11 -5
- package/apps/admin/escrow-scopes/index.tsx +3 -3
- package/apps/admin/escrow-scopes/new/index.tsx +9 -3
- package/apps/admin/index.tsx +4 -4
- package/apps/admin/ingest-queue/index.tsx +3 -3
- package/apps/admin/mailboxes/[uid].tsx +15 -7
- package/apps/admin/quarantine/index.tsx +4 -4
- package/apps/admin/retention-policy/index.tsx +3 -3
- package/apps/admin/signing-certificates/index.tsx +3 -3
- package/apps/admin/transport-rules/[uid].tsx +4 -4
- package/apps/admin/transport-rules/index.tsx +3 -3
- package/apps/admin/transport-rules/new/index.tsx +3 -3
- package/apps/escrow/matters/[uid].tsx +26 -2
- package/apps/escrow/matters/new/index.tsx +4 -0
- package/apps/shared/calendar/calendarLiveUpdates.ts +11 -10
- package/apps/shared/calendar/useCalendarReminders.ts +2 -0
- package/apps/shared/components/admin/ActionAlert.tsx +32 -0
- package/apps/shared/components/admin/diagnostics/InformationPanel.tsx +1 -1
- package/apps/shared/components/admin/diagnostics/InstalledPlugins.tsx +1 -1
- package/apps/shared/components/admin/diagnostics/LogsPanel.tsx +1 -1
- package/apps/shared/components/admin/diagnostics/RuntimePanel.tsx +1 -1
- package/apps/shared/components/admin/diagnostics/SystemPanel.tsx +1 -1
- package/apps/shared/components/admin/distributionLists/MemberListCard.tsx +4 -4
- package/apps/shared/components/admin/elevation.ts +19 -0
- package/apps/shared/components/admin/escrowScopes/EscrowScopeKeyAndHoldersFields.tsx +58 -3
- package/apps/shared/components/admin/layout/AdminShell.tsx +6 -1
- package/apps/shared/components/admin/mailboxes/EraseLeftoverDataDialog.tsx +3 -3
- package/apps/shared/components/admin/mailboxes/EscrowScopeCard.tsx +4 -4
- package/apps/shared/components/admin/mailboxes/LeftoverMailboxesSection.tsx +4 -4
- package/apps/shared/components/admin/mailboxes/ResourceSettingsCard.tsx +3 -3
- package/apps/shared/components/admin/mailboxes/ShareAccessCard.tsx +6 -6
- package/apps/shared/components/admin/settings/BrandingForm.tsx +22 -9
- package/apps/shared/components/admin/settings/DomainDnsSetup.tsx +4 -4
- package/apps/shared/components/admin/settings/EncryptionPolicyForm.tsx +33 -4
- package/apps/shared/components/admin/settings/LoadedSettingsForm.tsx +3 -3
- package/apps/shared/components/admin/settings/MailboxCreateForm.tsx +8 -3
- package/apps/shared/components/admin/settings/MailboxPolicyForm.tsx +3 -3
- package/apps/shared/components/admin/settings/PluginsManager.tsx +39 -17
- package/apps/shared/components/admin/settings/RetentionPolicyForm.tsx +25 -12
- package/apps/shared/components/admin/setup/EscrowSetupStep.tsx +40 -7
- package/apps/shared/components/admin/setup/SetupWizard.tsx +8 -6
- package/apps/shared/components/admin/signOut.ts +3 -0
- package/apps/shared/components/admin/usePagedList.tsx +2 -2
- package/apps/shared/components/calendar/DescriptionEditor.tsx +2 -1
- package/apps/shared/components/calendar/EventDetails.tsx +34 -8
- package/apps/shared/components/calendar/EventEditor.tsx +90 -40
- package/apps/shared/components/calendar/EventFormParts.tsx +5 -2
- package/apps/shared/components/calendar/EventMatchNav.tsx +14 -2
- package/apps/shared/components/calendar/EventModal.tsx +25 -1
- package/apps/shared/components/calendar/FindATime.tsx +10 -2
- package/apps/shared/components/calendar/ListView.tsx +99 -25
- package/apps/shared/components/calendar/RecurrenceEditor.tsx +19 -8
- package/apps/shared/components/calendar/SplitDayView.tsx +14 -18
- package/apps/shared/components/calendar/TaskQuickForm.tsx +3 -2
- package/apps/shared/components/calendar/TimeGridView.tsx +35 -19
- package/apps/shared/components/calendar/gridLayout.ts +94 -0
- package/apps/shared/components/calendar/layout/CalendarShell.tsx +1 -1
- package/apps/shared/components/calendar/timePicker.ts +5 -2
- package/apps/shared/components/contacts/ContactCard.tsx +15 -4
- package/apps/shared/components/contacts/ContactDetailPane.tsx +54 -15
- package/apps/shared/components/contacts/ContactForm.tsx +61 -17
- package/apps/shared/components/contacts/ContactPhotoEditor.tsx +88 -0
- package/apps/shared/components/contacts/ContactPhotoField.tsx +36 -39
- package/apps/shared/components/contacts/layout/ContactsShell.tsx +1 -1
- package/apps/shared/components/contacts/participantDetails.ts +18 -1
- package/apps/shared/components/layout/AppShell.tsx +9 -0
- package/apps/shared/components/layout/KeyEnrollmentGate.tsx +9 -4
- package/apps/shared/components/layout/MailboxProvisioning.tsx +31 -3
- package/apps/shared/components/layout/RecoveryCodeUnlock.tsx +4 -2
- package/apps/shared/components/mail/ConversationList.tsx +49 -6
- package/apps/shared/components/mail/ConversationThreadPane.tsx +1 -1
- package/apps/shared/components/mail/MenuButton.tsx +3 -400
- package/apps/shared/components/mail/MessageDetailPane.tsx +16 -13
- package/apps/shared/components/mail/compose/ComposeToolbar.tsx +11 -1
- package/apps/shared/components/mail/compose/ComposeWindow.tsx +57 -11
- package/apps/shared/components/mail/compose/GifPicker.tsx +10 -4
- package/apps/shared/components/mail/compose/RecipientInput.tsx +9 -7
- package/apps/shared/components/mail/compose/composeFlushRegistry.ts +14 -1
- package/apps/shared/components/mail/compose/encryptionRequirement.ts +5 -0
- package/apps/shared/components/mail/compose/quotedBody.ts +31 -6
- package/apps/shared/components/mail/layout/MailShell.tsx +1 -1
- package/apps/shared/components/mail/reading/MessageBody.tsx +13 -7
- package/apps/shared/components/mail/reading/bodyContent.ts +2 -0
- package/apps/shared/components/mail/reading/bodyHtml.ts +25 -2
- package/apps/shared/components/mail/writableMailboxes.ts +2 -0
- package/apps/shared/components/rules/RuleBuilder.tsx +11 -2
- package/apps/shared/components/settings/GravatarSetting.tsx +27 -0
- package/apps/shared/components/settings/layout/SettingsShell.tsx +1 -1
- package/apps/shared/components/tasks/dueDate.ts +29 -0
- package/apps/shared/components/tasks/layout/TasksShell.tsx +1 -1
- package/apps/shared/mail/decryptedMessages.ts +51 -2
- package/apps/shared/mail/listSnapshots.ts +2 -0
- package/apps/shared/mail/outbox/sendDecision.ts +14 -3
- package/apps/shared/mail/outbox/sendJob.ts +5 -0
- package/apps/shared/mail/useMailLiveUpdates.ts +7 -1
- package/apps/shared/mail/usePermanentDelete.tsx +3 -1
- package/apps/shared/notifications/apiErrors.ts +2 -0
- package/apps/shared/notifications/preferences.ts +1 -1
- package/apps/shared/notifications/store.ts +3 -1
- package/apps/shared/plugins/pluginNav.ts +4 -2
- package/apps/www/calendar/index.tsx +24 -12
- package/apps/www/contacts/[uid].tsx +6 -1
- package/apps/www/contacts/index.tsx +66 -12
- package/apps/www/index.tsx +91 -28
- package/apps/www/messages/[uid].tsx +8 -3
- package/apps/www/settings/auto-reply/index.tsx +13 -0
- package/apps/www/settings/blocked-senders/index.tsx +4 -0
- package/apps/www/settings/encryption/index.tsx +66 -4
- package/apps/www/settings/privacy/index.tsx +4 -1
- package/apps/www/settings/profile/index.tsx +12 -1
- package/apps/www/settings/sharing/index.tsx +11 -1
- package/apps/www/settings/signatures/[uid].tsx +6 -4
- package/apps/www/tasks/index.tsx +97 -29
- package/dist/apps/admin/audit-log/index.js +3 -3
- package/dist/apps/admin/branding/index.js +3 -3
- package/dist/apps/admin/data-requests/index.js +17 -7
- package/dist/apps/admin/distribution-lists/[uid].js +3 -3
- package/dist/apps/admin/distribution-lists/index.js +3 -3
- package/dist/apps/admin/distribution-lists/new/index.js +3 -3
- package/dist/apps/admin/domains/[uid].js +6 -5
- package/dist/apps/admin/domains/index.js +3 -3
- package/dist/apps/admin/domains/new/index.js +3 -3
- package/dist/apps/admin/escrow-scopes/[uid].js +11 -6
- package/dist/apps/admin/escrow-scopes/index.js +3 -3
- package/dist/apps/admin/escrow-scopes/new/index.js +9 -4
- package/dist/apps/admin/index.js +4 -4
- package/dist/apps/admin/ingest-queue/index.js +3 -3
- package/dist/apps/admin/mailboxes/[uid].js +15 -7
- package/dist/apps/admin/quarantine/index.js +4 -4
- package/dist/apps/admin/retention-policy/index.js +3 -3
- package/dist/apps/admin/signing-certificates/index.js +3 -3
- package/dist/apps/admin/transport-rules/[uid].js +4 -4
- package/dist/apps/admin/transport-rules/index.js +3 -3
- package/dist/apps/admin/transport-rules/new/index.js +3 -3
- package/dist/apps/escrow/matters/[uid].js +8 -3
- package/dist/apps/escrow/matters/new/index.js +4 -0
- package/dist/apps/shared/calendar/calendarLiveUpdates.d.ts +11 -7
- package/dist/apps/shared/calendar/calendarLiveUpdates.js +10 -9
- package/dist/apps/shared/calendar/useCalendarReminders.js +2 -0
- package/dist/apps/shared/components/admin/ActionAlert.d.ts +13 -0
- package/dist/apps/shared/components/admin/ActionAlert.js +24 -0
- package/dist/apps/shared/components/admin/diagnostics/InformationPanel.js +1 -1
- package/dist/apps/shared/components/admin/diagnostics/InstalledPlugins.js +1 -1
- package/dist/apps/shared/components/admin/diagnostics/LogsPanel.js +1 -1
- package/dist/apps/shared/components/admin/diagnostics/RuntimePanel.js +1 -1
- package/dist/apps/shared/components/admin/diagnostics/SystemPanel.js +1 -1
- package/dist/apps/shared/components/admin/distributionLists/MemberListCard.js +4 -4
- package/dist/apps/shared/components/admin/elevation.d.ts +9 -0
- package/dist/apps/shared/components/admin/elevation.js +16 -0
- package/dist/apps/shared/components/admin/escrowScopes/EscrowScopeKeyAndHoldersFields.d.ts +2 -0
- package/dist/apps/shared/components/admin/escrowScopes/EscrowScopeKeyAndHoldersFields.js +41 -1
- package/dist/apps/shared/components/admin/layout/AdminShell.js +2 -1
- package/dist/apps/shared/components/admin/mailboxes/EraseLeftoverDataDialog.js +3 -3
- package/dist/apps/shared/components/admin/mailboxes/EscrowScopeCard.js +4 -4
- package/dist/apps/shared/components/admin/mailboxes/LeftoverMailboxesSection.js +4 -4
- package/dist/apps/shared/components/admin/mailboxes/ResourceSettingsCard.js +3 -3
- package/dist/apps/shared/components/admin/mailboxes/ShareAccessCard.js +6 -6
- package/dist/apps/shared/components/admin/settings/BrandingForm.js +17 -7
- package/dist/apps/shared/components/admin/settings/DomainDnsSetup.js +4 -4
- package/dist/apps/shared/components/admin/settings/EncryptionPolicyForm.js +17 -5
- package/dist/apps/shared/components/admin/settings/LoadedSettingsForm.js +3 -3
- package/dist/apps/shared/components/admin/settings/MailboxCreateForm.js +8 -3
- package/dist/apps/shared/components/admin/settings/MailboxPolicyForm.js +3 -3
- package/dist/apps/shared/components/admin/settings/PluginsManager.js +26 -12
- package/dist/apps/shared/components/admin/settings/RetentionPolicyForm.js +17 -10
- package/dist/apps/shared/components/admin/setup/EscrowSetupStep.js +18 -8
- package/dist/apps/shared/components/admin/setup/SetupWizard.js +8 -6
- package/dist/apps/shared/components/admin/signOut.js +3 -0
- package/dist/apps/shared/components/admin/usePagedList.js +2 -2
- package/dist/apps/shared/components/calendar/DescriptionEditor.js +2 -1
- package/dist/apps/shared/components/calendar/EventDetails.d.ts +3 -1
- package/dist/apps/shared/components/calendar/EventDetails.js +7 -4
- package/dist/apps/shared/components/calendar/EventEditor.js +81 -33
- package/dist/apps/shared/components/calendar/EventFormParts.d.ts +3 -1
- package/dist/apps/shared/components/calendar/EventFormParts.js +3 -3
- package/dist/apps/shared/components/calendar/EventMatchNav.d.ts +1 -1
- package/dist/apps/shared/components/calendar/EventMatchNav.js +12 -2
- package/dist/apps/shared/components/calendar/EventModal.js +10 -2
- package/dist/apps/shared/components/calendar/FindATime.js +4 -3
- package/dist/apps/shared/components/calendar/ListView.d.ts +10 -4
- package/dist/apps/shared/components/calendar/ListView.js +92 -27
- package/dist/apps/shared/components/calendar/RecurrenceEditor.d.ts +4 -1
- package/dist/apps/shared/components/calendar/RecurrenceEditor.js +15 -10
- package/dist/apps/shared/components/calendar/SplitDayView.js +11 -17
- package/dist/apps/shared/components/calendar/TaskQuickForm.js +3 -2
- package/dist/apps/shared/components/calendar/TimeGridView.js +23 -21
- package/dist/apps/shared/components/calendar/gridLayout.d.ts +41 -0
- package/dist/apps/shared/components/calendar/gridLayout.js +83 -0
- package/dist/apps/shared/components/calendar/layout/CalendarShell.js +1 -1
- package/dist/apps/shared/components/calendar/timePicker.js +5 -2
- package/dist/apps/shared/components/contacts/ContactCard.js +9 -3
- package/dist/apps/shared/components/contacts/ContactDetailPane.d.ts +5 -3
- package/dist/apps/shared/components/contacts/ContactDetailPane.js +11 -5
- package/dist/apps/shared/components/contacts/ContactForm.js +54 -16
- package/dist/apps/shared/components/contacts/ContactPhotoEditor.d.ts +21 -0
- package/dist/apps/shared/components/contacts/ContactPhotoEditor.js +56 -0
- package/dist/apps/shared/components/contacts/ContactPhotoField.d.ts +7 -4
- package/dist/apps/shared/components/contacts/ContactPhotoField.js +19 -21
- package/dist/apps/shared/components/contacts/layout/ContactsShell.js +1 -1
- package/dist/apps/shared/components/contacts/participantDetails.d.ts +4 -1
- package/dist/apps/shared/components/contacts/participantDetails.js +17 -1
- package/dist/apps/shared/components/layout/AppShell.js +9 -0
- package/dist/apps/shared/components/layout/KeyEnrollmentGate.js +2 -3
- package/dist/apps/shared/components/layout/MailboxProvisioning.d.ts +4 -1
- package/dist/apps/shared/components/layout/MailboxProvisioning.js +24 -2
- package/dist/apps/shared/components/layout/RecoveryCodeUnlock.d.ts +4 -2
- package/dist/apps/shared/components/layout/RecoveryCodeUnlock.js +4 -2
- package/dist/apps/shared/components/mail/ConversationList.js +49 -6
- package/dist/apps/shared/components/mail/ConversationThreadPane.js +1 -1
- package/dist/apps/shared/components/mail/MenuButton.d.ts +2 -78
- package/dist/apps/shared/components/mail/MenuButton.js +2 -211
- package/dist/apps/shared/components/mail/MessageDetailPane.js +10 -6
- package/dist/apps/shared/components/mail/compose/ComposeToolbar.d.ts +3 -0
- package/dist/apps/shared/components/mail/compose/ComposeToolbar.js +10 -1
- package/dist/apps/shared/components/mail/compose/ComposeWindow.js +55 -10
- package/dist/apps/shared/components/mail/compose/GifPicker.js +10 -4
- package/dist/apps/shared/components/mail/compose/RecipientInput.js +9 -7
- package/dist/apps/shared/components/mail/compose/composeFlushRegistry.d.ts +7 -1
- package/dist/apps/shared/components/mail/compose/composeFlushRegistry.js +13 -1
- package/dist/apps/shared/components/mail/compose/encryptionRequirement.d.ts +5 -0
- package/dist/apps/shared/components/mail/compose/quotedBody.js +28 -5
- package/dist/apps/shared/components/mail/layout/MailShell.js +1 -1
- package/dist/apps/shared/components/mail/reading/MessageBody.js +11 -6
- package/dist/apps/shared/components/mail/reading/bodyContent.js +2 -0
- package/dist/apps/shared/components/mail/reading/bodyHtml.d.ts +5 -1
- package/dist/apps/shared/components/mail/reading/bodyHtml.js +23 -2
- package/dist/apps/shared/components/mail/writableMailboxes.js +2 -0
- package/dist/apps/shared/components/rules/RuleBuilder.js +9 -1
- package/dist/apps/shared/components/settings/GravatarSetting.d.ts +6 -0
- package/dist/apps/shared/components/settings/GravatarSetting.js +15 -0
- package/dist/apps/shared/components/settings/layout/SettingsShell.js +1 -1
- package/dist/apps/shared/components/tasks/dueDate.d.ts +12 -0
- package/dist/apps/shared/components/tasks/dueDate.js +26 -0
- package/dist/apps/shared/components/tasks/layout/TasksShell.js +1 -1
- package/dist/apps/shared/mail/decryptedMessages.d.ts +8 -0
- package/dist/apps/shared/mail/decryptedMessages.js +46 -2
- package/dist/apps/shared/mail/listSnapshots.js +2 -0
- package/dist/apps/shared/mail/outbox/sendDecision.d.ts +6 -3
- package/dist/apps/shared/mail/outbox/sendDecision.js +13 -3
- package/dist/apps/shared/mail/outbox/sendJob.d.ts +1 -0
- package/dist/apps/shared/mail/outbox/sendJob.js +5 -1
- package/dist/apps/shared/mail/useMailLiveUpdates.d.ts +3 -0
- package/dist/apps/shared/mail/useMailLiveUpdates.js +6 -1
- package/dist/apps/shared/mail/usePermanentDelete.d.ts +2 -0
- package/dist/apps/shared/mail/usePermanentDelete.js +1 -1
- package/dist/apps/shared/notifications/apiErrors.js +2 -0
- package/dist/apps/shared/notifications/preferences.d.ts +1 -1
- package/dist/apps/shared/notifications/preferences.js +1 -1
- package/dist/apps/shared/notifications/store.js +3 -1
- package/dist/apps/shared/plugins/pluginNav.d.ts +2 -1
- package/dist/apps/shared/plugins/pluginNav.js +4 -2
- package/dist/apps/www/calendar/index.js +24 -12
- package/dist/apps/www/contacts/[uid].js +6 -2
- package/dist/apps/www/contacts/index.js +62 -11
- package/dist/apps/www/index.js +85 -30
- package/dist/apps/www/messages/[uid].js +8 -3
- package/dist/apps/www/settings/auto-reply/index.js +13 -1
- package/dist/apps/www/settings/blocked-senders/index.js +4 -0
- package/dist/apps/www/settings/encryption/index.js +29 -5
- package/dist/apps/www/settings/privacy/index.js +3 -2
- package/dist/apps/www/settings/profile/index.js +4 -1
- package/dist/apps/www/settings/sharing/index.js +10 -1
- package/dist/apps/www/settings/signatures/[uid].js +6 -4
- package/dist/apps/www/tasks/index.js +94 -32
- package/dist/lib/admin/auditLogApi.js +2 -2
- package/dist/lib/admin/distributionListsApi.js +2 -2
- package/dist/lib/admin/domainsApi.js +2 -2
- package/dist/lib/admin/escrowAuditLogApi.js +2 -2
- package/dist/lib/admin/escrowScopesApi.js +2 -2
- package/dist/lib/admin/pluginsApi.d.ts +13 -2
- package/dist/lib/admin/pluginsApi.js +8 -0
- package/dist/lib/admin/retentionPolicyApi.d.ts +3 -0
- package/dist/lib/admin/retentionPolicyApi.js +3 -0
- package/dist/lib/admin/transportRulesApi.js +2 -2
- package/dist/lib/appearance/preferencesApi.d.ts +1 -1
- package/dist/lib/appearance/preferencesApi.js +3 -14
- package/dist/lib/auth/session.d.ts +51 -12
- package/dist/lib/auth/session.js +214 -38
- package/dist/lib/branding/brandingApi.d.ts +3 -3
- package/dist/lib/branding/brandingApi.js +9 -21
- package/dist/lib/calendar/calendarApi.js +3 -1
- package/dist/lib/calendar/eventSearch.js +43 -6
- package/dist/lib/calendar/freeBusyApi.d.ts +2 -1
- package/dist/lib/calendar/freeBusyApi.js +13 -5
- package/dist/lib/calendar/recurrence.js +51 -3
- package/dist/lib/calendar/useDayKey.d.ts +7 -0
- package/dist/lib/calendar/useDayKey.js +43 -0
- package/dist/lib/components/avatar/ContactAvatar.d.ts +6 -2
- package/dist/lib/components/avatar/ContactAvatar.js +37 -8
- package/dist/lib/components/avatar/ContactPhotoAvatar.d.ts +11 -0
- package/dist/lib/components/avatar/ContactPhotoAvatar.js +18 -0
- package/dist/lib/components/forms/CameraCaptureModal.d.ts +17 -0
- package/dist/lib/components/forms/CameraCaptureModal.js +88 -0
- package/dist/lib/components/forms/ImageEditBadge.d.ts +28 -0
- package/dist/lib/components/forms/ImageEditBadge.js +62 -0
- package/dist/lib/components/menus/MenuButton.d.ts +78 -0
- package/dist/lib/components/menus/MenuButton.js +219 -0
- package/dist/lib/components/navigation/BottomTabBar.d.ts +3 -1
- package/dist/lib/components/navigation/BottomTabBar.js +22 -3
- package/dist/lib/components/overlays/PopoverPortal.d.ts +2 -1
- package/dist/lib/components/overlays/PopoverPortal.js +3 -2
- package/dist/lib/components/pickers/TimeZonePicker.js +13 -1
- package/dist/lib/contacts/contactsApi.d.ts +5 -2
- package/dist/lib/contacts/contactsApi.js +16 -19
- package/dist/lib/contacts/gravatar.d.ts +14 -3
- package/dist/lib/contacts/gravatar.js +42 -5
- package/dist/lib/contacts/preparePhoto.d.ts +24 -0
- package/dist/lib/contacts/preparePhoto.js +250 -0
- package/dist/lib/contacts/useContactPhotoSrc.d.ts +19 -0
- package/dist/lib/contacts/useContactPhotoSrc.js +153 -0
- package/dist/lib/contacts/vcard.d.ts +3 -0
- package/dist/lib/contacts/vcard.js +59 -7
- package/dist/lib/crypto/escrowKeys.d.ts +8 -0
- package/dist/lib/crypto/escrowKeys.js +20 -1
- package/dist/lib/crypto/messageSecurity.d.ts +7 -4
- package/dist/lib/crypto/messageSecurity.js +23 -5
- package/dist/lib/crypto/passwordUnlock.d.ts +6 -1
- package/dist/lib/crypto/passwordUnlock.js +13 -2
- package/dist/lib/crypto/smime.js +12 -2
- package/dist/lib/mail/compose/composeQuoting.js +3 -2
- package/dist/lib/mail/compose/quotedImages.d.ts +6 -3
- package/dist/lib/mail/compose/quotedImages.js +35 -9
- package/dist/lib/mail/labelsApi.js +2 -2
- package/dist/lib/mail/mailApi.d.ts +8 -5
- package/dist/lib/mail/mailApi.js +73 -39
- package/dist/lib/mail/mailDetailHooks.js +11 -3
- package/dist/lib/mail/mailFilterRulesApi.js +2 -2
- package/dist/lib/mail/mailSignaturesApi.js +2 -2
- package/dist/lib/mail/mailboxImportApi.d.ts +1 -1
- package/dist/lib/mail/mailboxImportApi.js +3 -15
- package/dist/lib/mail/messageBodySanitizer.d.ts +6 -0
- package/dist/lib/mail/messageBodySanitizer.js +44 -1
- package/dist/lib/search/queryGrammar.js +12 -4
- package/dist/lib/search/searchTier3.d.ts +6 -1
- package/dist/lib/search/searchTier3.js +54 -25
- package/dist/lib/tasks/tasksApi.js +3 -3
- package/dist/lib/util/api.d.ts +26 -4
- package/dist/lib/util/api.js +73 -16
- package/dist/lib/util/apiQuery.d.ts +11 -0
- package/dist/lib/util/apiQuery.js +11 -0
- package/dist/lib/util/settleWithLimit.d.ts +5 -0
- package/dist/lib/util/settleWithLimit.js +25 -0
- package/lib/admin/auditLogApi.ts +2 -2
- package/lib/admin/distributionListsApi.ts +2 -2
- package/lib/admin/domainsApi.ts +2 -2
- package/lib/admin/escrowAuditLogApi.ts +2 -2
- package/lib/admin/escrowScopesApi.ts +2 -2
- package/lib/admin/pluginsApi.ts +21 -2
- package/lib/admin/retentionPolicyApi.ts +4 -0
- package/lib/admin/transportRulesApi.ts +2 -2
- package/lib/appearance/preferencesApi.ts +3 -14
- package/lib/auth/session.ts +239 -39
- package/lib/branding/brandingApi.ts +9 -21
- package/lib/calendar/calendarApi.ts +3 -1
- package/lib/calendar/eventSearch.ts +46 -6
- package/lib/calendar/freeBusyApi.ts +14 -6
- package/lib/calendar/recurrence.ts +59 -8
- package/lib/calendar/useDayKey.ts +45 -0
- package/lib/components/avatar/ContactAvatar.tsx +46 -8
- package/lib/components/avatar/ContactPhotoAvatar.tsx +26 -0
- package/lib/components/forms/CameraCaptureModal.tsx +133 -0
- package/lib/components/forms/ImageEditBadge.tsx +121 -0
- package/lib/components/menus/MenuButton.tsx +408 -0
- package/lib/components/navigation/BottomTabBar.tsx +30 -8
- package/lib/components/overlays/PopoverPortal.tsx +3 -2
- package/lib/components/pickers/TimeZonePicker.tsx +17 -1
- package/lib/contacts/contactsApi.ts +18 -19
- package/lib/contacts/gravatar.ts +47 -5
- package/lib/contacts/preparePhoto.ts +273 -0
- package/lib/contacts/useContactPhotoSrc.ts +177 -0
- package/lib/contacts/vcard.ts +60 -7
- package/lib/crypto/escrowKeys.ts +21 -1
- package/lib/crypto/messageSecurity.ts +33 -9
- package/lib/crypto/passwordUnlock.ts +14 -2
- package/lib/crypto/smime.ts +13 -2
- package/lib/mail/compose/composeQuoting.ts +3 -2
- package/lib/mail/compose/quotedImages.ts +39 -9
- package/lib/mail/labelsApi.ts +2 -2
- package/lib/mail/mailApi.ts +96 -39
- package/lib/mail/mailDetailHooks.ts +11 -3
- package/lib/mail/mailFilterRulesApi.ts +2 -2
- package/lib/mail/mailSignaturesApi.ts +2 -2
- package/lib/mail/mailboxImportApi.ts +9 -15
- package/lib/mail/messageBodySanitizer.ts +47 -1
- package/lib/search/queryGrammar.ts +13 -4
- package/lib/search/searchTier3.ts +65 -32
- package/lib/tasks/tasksApi.ts +3 -3
- package/lib/util/api.ts +99 -16
- package/lib/util/apiQuery.ts +13 -0
- package/lib/util/settleWithLimit.ts +25 -0
- package/package.json +1 -1
|
@@ -3,17 +3,18 @@ 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 { useEffect,
|
|
6
|
+
import { useEffect, useState } from "react";
|
|
7
7
|
import ContactAvatar from "../../../../lib/components/avatar/ContactAvatar.js";
|
|
8
|
-
import
|
|
9
|
-
import
|
|
8
|
+
import { ContactPhotoError, prepareContactPhoto } from "../../../../lib/contacts/preparePhoto.js";
|
|
9
|
+
import ImageEditBadge from "../../../../lib/components/forms/ImageEditBadge.js";
|
|
10
10
|
/**
|
|
11
|
-
* The picture of a contact in the edit form: the avatar as it is (their picture, else Gravatar, else initials) with
|
|
12
|
-
* there is a picture of their own, "Remove photo".
|
|
11
|
+
* The picture of a contact in the edit form: the avatar as it is (their picture, else Gravatar, else initials) with a camera badge at its lower right that opens a
|
|
12
|
+
* menu (`ImageEditBadge`) - "Upload file", "Take photo" and, when there is a picture of their own, "Remove photo". Any picture is taken (a phone's camera photo of several megabytes,
|
|
13
|
+
* in any orientation, included): it is made small enough here by `prepareContactPhoto()` and only uploaded when the form is saved.
|
|
13
14
|
*/
|
|
14
|
-
export default function ContactPhotoField({ displayName, email, currentUrl, picked, removed, onPick, onRemove, onReject }) {
|
|
15
|
-
const input = useRef(null);
|
|
15
|
+
export default function ContactPhotoField({ displayName, email, currentUrl, picked, removed, onPick, onRemove, onReject, onBusyChange }) {
|
|
16
16
|
const [preview, setPreview] = useState();
|
|
17
|
+
const [preparing, setPreparing] = useState(false);
|
|
17
18
|
useEffect(() => {
|
|
18
19
|
if (!picked) {
|
|
19
20
|
setPreview(undefined);
|
|
@@ -23,24 +24,21 @@ export default function ContactPhotoField({ displayName, email, currentUrl, pick
|
|
|
23
24
|
setPreview(url);
|
|
24
25
|
return () => URL.revokeObjectURL(url);
|
|
25
26
|
}, [picked]);
|
|
26
|
-
function handleChange(file) {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
onReject("Choose a JPEG, PNG, GIF or WebP image.");
|
|
27
|
+
async function handleChange(file) {
|
|
28
|
+
setPreparing(true);
|
|
29
|
+
onBusyChange(true);
|
|
30
|
+
try {
|
|
31
|
+
onPick(await prepareContactPhoto(file));
|
|
32
32
|
}
|
|
33
|
-
|
|
34
|
-
onReject("
|
|
33
|
+
catch (err) {
|
|
34
|
+
onReject(err instanceof ContactPhotoError ? err.message : "This picture could not be read - choose another one.");
|
|
35
35
|
}
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
finally {
|
|
37
|
+
setPreparing(false);
|
|
38
|
+
onBusyChange(false);
|
|
38
39
|
}
|
|
39
40
|
}
|
|
40
41
|
const shown = picked ? preview : removed ? undefined : currentUrl;
|
|
41
42
|
const hasOwn = !!picked || (!!currentUrl && !removed);
|
|
42
|
-
return (_jsxs("div", { className: "flex items-center gap-4 mb-3", children: [_jsx(ContactAvatar, { displayName: displayName || "?", size: 72, photoUrl: shown, email: picked || (currentUrl && !removed) ? undefined : email }),
|
|
43
|
-
handleChange(e.target.files?.[0]);
|
|
44
|
-
e.target.value = "";
|
|
45
|
-
} }), _jsx(Button, { type: "button", variant: "secondary", className: "!w-auto !py-1 !px-3 text-xs", onClick: () => input.current?.click(), children: hasOwn ? "Change photo" : "Add photo" }), hasOwn && (_jsx(Button, { type: "button", variant: "secondary", className: "!w-auto !py-1 !px-3 text-xs", onClick: onRemove, children: "Remove photo" }))] })] }));
|
|
43
|
+
return (_jsxs("div", { className: "flex items-center gap-4 mb-3", children: [_jsxs("div", { className: "relative inline-block shrink-0", children: [_jsx(ContactAvatar, { displayName: displayName || "?", size: 72, photoUrl: shown, email: picked || (currentUrl && !removed) ? undefined : email }), _jsx(ImageEditBadge, { position: "bottom-right", label: "Change contact photo", fileInputLabel: "Contact photo file", hasImage: hasOwn, busy: preparing, onFile: (file) => void handleChange(file), onRemove: onRemove })] }), preparing && (_jsx("span", { role: "status", className: "text-xs text-text-muted", children: "Preparing picture\u2026" }))] }));
|
|
46
44
|
}
|
|
@@ -79,7 +79,7 @@ export default function ContactsShell({ userUid, authServerUrl, impersonating, i
|
|
|
79
79
|
// A full-screen takeover, not nested inside the rest of the app's chrome — there's nothing else
|
|
80
80
|
// for a mailbox-less caller to do here yet, so the icon rail/header don't render at all.
|
|
81
81
|
if (userUid && status === "ready" && !mailboxUid) {
|
|
82
|
-
return _jsx(MailboxProvisioning, {});
|
|
82
|
+
return _jsx(MailboxProvisioning, { impersonating: impersonating, impersonationBaseUrl: impersonationBaseUrl });
|
|
83
83
|
}
|
|
84
84
|
let inner = null;
|
|
85
85
|
if (userUid && status === "checking") {
|
|
@@ -52,7 +52,10 @@ export declare function contactInputFromCard(card: ParsedVCardContact, scope: {
|
|
|
52
52
|
mailboxUid: string;
|
|
53
53
|
folderUid: string;
|
|
54
54
|
}): KnownPerson;
|
|
55
|
-
/**
|
|
55
|
+
/**
|
|
56
|
+
* The cards that are not yet in the address book, and how many were: a card is a duplicate when one of its emails is a stored contact's or an earlier card's,
|
|
57
|
+
* and a card with no email at all when its name and first phone number are a stored contact's or an earlier card's (so importing the same file twice adds nothing).
|
|
58
|
+
*/
|
|
56
59
|
export declare function withoutDuplicates(cards: readonly ParsedVCardContact[], existing: readonly Contact[]): {
|
|
57
60
|
fresh: ParsedVCardContact[];
|
|
58
61
|
skipped: number;
|
|
@@ -92,12 +92,28 @@ export function contactInputFromCard(card, scope) {
|
|
|
92
92
|
const names = card.givenName || card.surname ? { givenName: card.givenName, surname: card.surname } : splitPersonName(card.displayName);
|
|
93
93
|
return { ...card, ...scope, ...names, emails: card.emails ?? [], phones: card.phones ?? [], addresses: card.addresses ?? [] };
|
|
94
94
|
}
|
|
95
|
-
/**
|
|
95
|
+
/** What a card with no email is told apart by: its name and its first phone number (digits only). */
|
|
96
|
+
function identityKey(person) {
|
|
97
|
+
return `${person.displayName.trim().toLowerCase()}|${(person.phones?.[0]?.phoneNumber ?? "").replace(/\D/g, "")}`;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* The cards that are not yet in the address book, and how many were: a card is a duplicate when one of its emails is a stored contact's or an earlier card's,
|
|
101
|
+
* and a card with no email at all when its name and first phone number are a stored contact's or an earlier card's (so importing the same file twice adds nothing).
|
|
102
|
+
*/
|
|
96
103
|
export function withoutDuplicates(cards, existing) {
|
|
97
104
|
const known = new Set(existing.flatMap((contact) => contact.emails.map((email) => normalizeAddress(email.address))));
|
|
105
|
+
const knownIdentities = new Set(existing.map(identityKey));
|
|
98
106
|
const fresh = [];
|
|
99
107
|
for (const card of cards) {
|
|
100
108
|
const addresses = (card.emails ?? []).map((email) => normalizeAddress(email.address));
|
|
109
|
+
if (addresses.length === 0) {
|
|
110
|
+
const identity = identityKey(card);
|
|
111
|
+
if (!knownIdentities.has(identity)) {
|
|
112
|
+
knownIdentities.add(identity);
|
|
113
|
+
fresh.push(card);
|
|
114
|
+
}
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
101
117
|
if (addresses.some((address) => known.has(address))) {
|
|
102
118
|
continue;
|
|
103
119
|
}
|
|
@@ -28,6 +28,8 @@ import { SIGN_OUT_CHANNEL, destroyAllLocalIndexes } from "../../search/localInde
|
|
|
28
28
|
import { authApiFetch, setApiUnauthorizedObserver } from "../../../../lib/util/api.js";
|
|
29
29
|
import { destroyUnlockedKeys } from "../../../../lib/crypto/keySession.js";
|
|
30
30
|
import { clearPinnedSignerCache } from "../mail/pinnedSigners.js";
|
|
31
|
+
import { resetNotifications } from "../../notifications/store.js";
|
|
32
|
+
import { clearContactPhotoCache } from "../../../../lib/contacts/useContactPhotoSrc.js";
|
|
31
33
|
import { pluginIcon } from "../../plugins/pluginIcons.js";
|
|
32
34
|
import { mergePluginNavItems } from "../../plugins/pluginNav.js";
|
|
33
35
|
import { useResolvedRailItems } from "../../plugins/useResolvedRailItems.js";
|
|
@@ -181,6 +183,9 @@ export function AppChrome({ active, userUid, authServerUrl, impersonating, imper
|
|
|
181
183
|
destroyUnlockedKeys();
|
|
182
184
|
clearPinnedSignerCache();
|
|
183
185
|
clearAppearanceCache();
|
|
186
|
+
clearContactPhotoCache();
|
|
187
|
+
// The notification history (meeting titles, error details) is this session's, and the tab's storage outlives it.
|
|
188
|
+
resetNotifications();
|
|
184
189
|
// Bounded by its own timeout and never rejects - awaited so navigating doesn't kill the Worker mid-delete.
|
|
185
190
|
void destroyAllLocalIndexes().then(() => {
|
|
186
191
|
window.location.href = authServerUrl ?? "/";
|
|
@@ -198,6 +203,8 @@ export function AppChrome({ active, userUid, authServerUrl, impersonating, imper
|
|
|
198
203
|
// Trusted signer pins read from contacts don't outlive the session either.
|
|
199
204
|
clearPinnedSignerCache();
|
|
200
205
|
clearAppearanceCache();
|
|
206
|
+
clearContactPhotoCache();
|
|
207
|
+
resetNotifications();
|
|
201
208
|
// Open compose windows save edits still waiting on their autosave debounce while the session is still
|
|
202
209
|
// valid - logout invalidates it. Bounded the same way as logout itself, and never rejects.
|
|
203
210
|
await flushComposeDrafts(LOGOUT_TIMEOUT_MS);
|
|
@@ -221,6 +228,8 @@ export function AppChrome({ active, userUid, authServerUrl, impersonating, imper
|
|
|
221
228
|
// handling, which surfaces via a full reload rather than an inline retry affordance.
|
|
222
229
|
}
|
|
223
230
|
finally {
|
|
231
|
+
// What the impersonated user's session raised (their meetings, their errors) must not be left for the administrator to read.
|
|
232
|
+
resetNotifications();
|
|
224
233
|
window.location.href = "/admin";
|
|
225
234
|
}
|
|
226
235
|
}
|
|
@@ -11,12 +11,11 @@ import FormField from "../../../../lib/components/forms/FormField.js";
|
|
|
11
11
|
import { enrollKey, getKeyVault, VaultAlreadyInitializedError } from "../../../../lib/crypto/keyvaultApi.js";
|
|
12
12
|
import { UnopenableKeysNotice, unlockErrorMessage } from "./UnlockPromptProvider.js";
|
|
13
13
|
import { FrameTakeover } from "../../navigation/frameContext.js";
|
|
14
|
-
import { RecoveryFollowUpModal, UnlockModeToggle, startRecoveryUnlock } from "./RecoveryCodeUnlock.js";
|
|
14
|
+
import { MIN_PASSWORD_LENGTH, RecoveryFollowUpModal, UnlockModeToggle, startRecoveryUnlock, } from "./RecoveryCodeUnlock.js";
|
|
15
15
|
import { ENCRYPTION_PRIVATE_KEY_AAD_PURPOSE, getUnlockedKeys, unlockWithPassword, } from "../../../../lib/crypto/keySession.js";
|
|
16
16
|
import { buildAad, generateMasterKey, sealWithKey } from "../../../../lib/crypto/masterKey.js";
|
|
17
17
|
import { buildPasswordWrap, buildRecoveryWraps } from "../../../../lib/crypto/masterKeyWraps.js";
|
|
18
18
|
import { exportPrivateKeyPkcs8, generateKeyPairWithCsr } from "../../../../lib/crypto/keys.js";
|
|
19
|
-
const MIN_PASSWORD_LENGTH = 8;
|
|
20
19
|
async function provisionEncryptionKey(mailboxUid, mailboxAddress, password) {
|
|
21
20
|
const mk = generateMasterKey();
|
|
22
21
|
const passwordWrap = await buildPasswordWrap(mailboxUid, mk, password);
|
|
@@ -180,7 +179,7 @@ export default function KeyEnrollmentGate({ mailboxUid, mailboxAddress, mailboxK
|
|
|
180
179
|
}
|
|
181
180
|
if (status === "setup_password" || status === "enrolling") {
|
|
182
181
|
const enrolling = status === "enrolling";
|
|
183
|
-
return (_jsx(FrameTakeover, { children: _jsx("div", { className: "min-h-screen flex items-center justify-center p-8 bg-surface-alt", children: _jsxs("div", { className: "w-full max-w-md bg-surface border border-border rounded-md p-8", children: [_jsx("h1", { className: "text-lg font-bold mb-2", children: "Protect your mailbox" }),
|
|
182
|
+
return (_jsx(FrameTakeover, { children: _jsx("div", { className: "min-h-screen flex items-center justify-center p-8 bg-surface-alt", children: _jsxs("div", { className: "w-full max-w-md bg-surface border border-border rounded-md p-8", children: [_jsx("h1", { className: "text-lg font-bold mb-2", children: "Protect your mailbox" }), _jsxs("p", { className: "text-sm text-text-muted mb-5", children: ["Choose a password to protect your encryption keys. This is separate from your sign-in password and is never sent to the server. Make it ", MIN_PASSWORD_LENGTH, " or more characters: a passphrase of several unrelated words is long enough and easy to remember."] }), error && _jsx(Alert, { children: error }), _jsxs("form", { onSubmit: handleSetPassword, children: [_jsx(FormField, { label: "Encryption password", htmlFor: "key-enrollment-password", children: _jsx("input", { id: "key-enrollment-password", type: "password", className: "w-full text-sm border border-border rounded-sm py-1.5 px-2 bg-surface", value: password, onChange: (e) => setPassword(e.target.value), disabled: enrolling, autoComplete: "new-password" }) }), _jsx(FormField, { label: "Confirm password", htmlFor: "key-enrollment-password-confirm", children: _jsx("input", { id: "key-enrollment-password-confirm", type: "password", className: "w-full text-sm border border-border rounded-sm py-1.5 px-2 bg-surface", value: confirmPassword, onChange: (e) => setConfirmPassword(e.target.value), disabled: enrolling, autoComplete: "new-password" }) }), _jsx(Button, { type: "submit", loading: enrolling, disabled: enrolling, children: "Continue" })] })] }) }) }));
|
|
184
183
|
}
|
|
185
184
|
if (status === "already_set_up") {
|
|
186
185
|
return (_jsx(FrameTakeover, { children: _jsx("div", { className: "min-h-screen flex items-center justify-center p-8 bg-surface-alt", children: _jsxs("div", { className: "w-full max-w-md bg-surface border border-border rounded-md p-8", children: [_jsx("h1", { className: "text-lg font-bold mb-2", children: "Encryption is already set up" }), _jsx("p", { className: "text-sm text-text-muted mb-5", children: "This mailbox\u2019s encryption was set up in another tab or on another device while this page was open, so nothing was changed here. Reload the page and unlock with the password chosen there." }), _jsx(Button, { type: "button", onClick: () => window.location.reload(), children: "Reload" })] }) }) }));
|
|
@@ -13,4 +13,7 @@ import React from "react";
|
|
|
13
13
|
* refused deliberately (a 4xx, e.g. "Automatic mailbox provisioning is not enabled.") so the caller and the
|
|
14
14
|
* administrator they ask can tell why - see `unavailableReason()`.
|
|
15
15
|
*/
|
|
16
|
-
export default function MailboxProvisioning(
|
|
16
|
+
export default function MailboxProvisioning({ impersonating, impersonationBaseUrl }: {
|
|
17
|
+
impersonating?: boolean;
|
|
18
|
+
impersonationBaseUrl?: string;
|
|
19
|
+
}): React.JSX.Element;
|
|
@@ -5,7 +5,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
5
5
|
///////////////////////////////////////////////////////////////////////////////
|
|
6
6
|
import { useEffect, useState } from "react";
|
|
7
7
|
import { ApiRequestError } from "../../../../lib/util/api.js";
|
|
8
|
-
import { autoProvisionMailbox } from "../../../../lib/mail/mailApi.js";
|
|
8
|
+
import { autoProvisionMailbox, stopImpersonating } from "../../../../lib/mail/mailApi.js";
|
|
9
9
|
import { useApiClient } from "../../../../lib/util/apiClientContext.js";
|
|
10
10
|
import Alert from "../../../../lib/components/feedback/Alert.js";
|
|
11
11
|
import Button from "../../../../lib/components/buttons/Button.js";
|
|
@@ -43,7 +43,7 @@ function unavailableReason(err) {
|
|
|
43
43
|
* refused deliberately (a 4xx, e.g. "Automatic mailbox provisioning is not enabled.") so the caller and the
|
|
44
44
|
* administrator they ask can tell why - see `unavailableReason()`.
|
|
45
45
|
*/
|
|
46
|
-
export default function MailboxProvisioning() {
|
|
46
|
+
export default function MailboxProvisioning({ impersonating, impersonationBaseUrl }) {
|
|
47
47
|
const client = useApiClient();
|
|
48
48
|
const [status, setStatus] = useState("checking");
|
|
49
49
|
const [options, setOptions] = useState([]);
|
|
@@ -55,6 +55,12 @@ export default function MailboxProvisioning() {
|
|
|
55
55
|
// Bumped by "Retry" to re-run the check below.
|
|
56
56
|
const [attempt, setAttempt] = useState(0);
|
|
57
57
|
useEffect(() => {
|
|
58
|
+
if (impersonating) {
|
|
59
|
+
// An administrator viewing as a user must never create a mailbox for them just by looking: nothing is provisioned, and the way back is shown
|
|
60
|
+
// (this takeover hides the frame's own "Return to admin" bar).
|
|
61
|
+
setStatus("impersonating");
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
58
64
|
setStatus("checking");
|
|
59
65
|
autoProvisionMailbox(undefined, client)
|
|
60
66
|
.then((result) => {
|
|
@@ -87,6 +93,19 @@ export default function MailboxProvisioning() {
|
|
|
87
93
|
}
|
|
88
94
|
});
|
|
89
95
|
}, [attempt]);
|
|
96
|
+
const [returning, setReturning] = useState(false);
|
|
97
|
+
async function handleReturnToAdmin() {
|
|
98
|
+
setReturning(true);
|
|
99
|
+
try {
|
|
100
|
+
await stopImpersonating(impersonationBaseUrl ?? "");
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
// Navigate either way, as the frame's own banner does: a failed call leaves the session as it was.
|
|
104
|
+
}
|
|
105
|
+
finally {
|
|
106
|
+
window.location.href = "/admin";
|
|
107
|
+
}
|
|
108
|
+
}
|
|
90
109
|
async function handleConfirm() {
|
|
91
110
|
// `selected` only ever takes a value from `options` itself (the initial default, or the
|
|
92
111
|
// <select>'s own onChange, which can only report one of its own rendered <option> values), so
|
|
@@ -111,6 +130,9 @@ export default function MailboxProvisioning() {
|
|
|
111
130
|
const creating = status === "creating";
|
|
112
131
|
content = (_jsxs("div", { className: "w-full flex flex-col gap-3", children: [_jsx("p", { className: "text-sm text-text-muted text-center", children: "Choose your mailbox address to get started:" }), error && _jsx(Alert, { children: error }), _jsx("select", { "aria-label": "Mailbox address", className: "text-sm border border-border rounded-sm py-1.5 px-2 bg-surface", value: selected, onChange: (e) => setSelected(e.target.value), disabled: creating, children: options.map((option) => (_jsx("option", { value: option.primarySmtpAddress, children: option.primarySmtpAddress }, option.primarySmtpAddress))) }), _jsx(Button, { type: "button", onClick: handleConfirm, loading: creating, disabled: creating, className: "!w-auto self-center", children: "Continue" })] }));
|
|
113
132
|
}
|
|
133
|
+
else if (status === "impersonating") {
|
|
134
|
+
content = (_jsxs(_Fragment, { children: [_jsx("h1", { className: "text-lg font-bold uppercase tracking-wide", children: "No mailbox" }), _jsx("p", { className: "text-sm text-text-muted", children: "The user you are viewing as has no mailbox, and none is created for them while you are viewing as them." }), _jsx(Button, { type: "button", onClick: () => void handleReturnToAdmin(), loading: returning, disabled: returning, className: "!w-auto self-center", children: "Return to admin" })] }));
|
|
135
|
+
}
|
|
114
136
|
else if (status === "retryable") {
|
|
115
137
|
content = (_jsxs(_Fragment, { children: [_jsx("h1", { className: "text-lg font-bold uppercase tracking-wide", children: "Couldn\u2019t check right now" }), _jsx("p", { className: "text-sm text-text-muted", children: retryText }), _jsx(Button, { type: "button", onClick: () => setAttempt((n) => n + 1), className: "!w-auto self-center", children: "Retry" })] }));
|
|
116
138
|
}
|
|
@@ -9,8 +9,10 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import React from "react";
|
|
11
11
|
import { type PublicKey } from "../../../../lib/crypto/keyvaultApi.js";
|
|
12
|
-
/** The same minimum every other "set an encryption password" form in this app uses.
|
|
13
|
-
|
|
12
|
+
/** The same minimum every other "set an encryption password" form in this app uses. The password is the only thing
|
|
13
|
+
* between an offline guesser holding the server's wrapped master key and the mailbox's private keys, so it is
|
|
14
|
+
* long: a passphrase of a few words clears it easily. */
|
|
15
|
+
export declare const MIN_PASSWORD_LENGTH = 12;
|
|
14
16
|
/** Where recovery codes are regenerated. */
|
|
15
17
|
export declare const ENCRYPTION_SETTINGS_HREF = "/settings/encryption";
|
|
16
18
|
/** Deliberately generic: a wrong code and a vault with no recovery codes fail the same way. */
|
|
@@ -22,8 +22,10 @@ import Modal from "../../../../lib/components/overlays/Modal.js";
|
|
|
22
22
|
import Alert from "../../../../lib/components/feedback/Alert.js";
|
|
23
23
|
import Button from "../../../../lib/components/buttons/Button.js";
|
|
24
24
|
import FormField from "../../../../lib/components/forms/FormField.js";
|
|
25
|
-
/** The same minimum every other "set an encryption password" form in this app uses.
|
|
26
|
-
|
|
25
|
+
/** The same minimum every other "set an encryption password" form in this app uses. The password is the only thing
|
|
26
|
+
* between an offline guesser holding the server's wrapped master key and the mailbox's private keys, so it is
|
|
27
|
+
* long: a passphrase of a few words clears it easily. */
|
|
28
|
+
export const MIN_PASSWORD_LENGTH = 12;
|
|
27
29
|
/** Where recovery codes are regenerated. */
|
|
28
30
|
export const ENCRYPTION_SETTINGS_HREF = "/settings/encryption";
|
|
29
31
|
/** Deliberately generic: a wrong code and a vault with no recovery codes fail the same way. */
|
|
@@ -3,13 +3,13 @@ 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 { useEffect, useState } from "react";
|
|
6
|
+
import { useEffect, useRef, useState } from "react";
|
|
7
7
|
import { HiChevronDown, HiChevronRight, HiOutlineFlag, HiOutlinePaperClip } from "react-icons/hi2";
|
|
8
8
|
import { ApiRequestError } from "../../../../lib/util/api.js";
|
|
9
9
|
import { formatMailAddress } from "../../../../lib/mail/mailAddress.js";
|
|
10
10
|
import MailAddress from "./MailAddress.js";
|
|
11
11
|
import { EncryptedPreview, conversationLooksEncrypted, displaySubject } from "./reading/EncryptedPreview.js";
|
|
12
|
-
import { decryptForDisplay, useDecryptedMessages, useUnlockEpoch } from "../../mail/decryptedMessages.js";
|
|
12
|
+
import { decryptForDisplay, mapWithConcurrency, useDecryptedMessages, useUnlockEpoch } from "../../mail/decryptedMessages.js";
|
|
13
13
|
import { listConversationMessages } from "../../../../lib/mail/conversationsApi.js";
|
|
14
14
|
import { useApiClient } from "../../../../lib/util/apiClientContext.js";
|
|
15
15
|
import { ROW_FOCUS_CLASS, UnreadBar, UnreadLabel, dateClass, isUnread, rowClass, senderClass, subjectClass } from "./unreadStyle.js";
|
|
@@ -44,22 +44,64 @@ export default function ConversationList({ conversations, mailboxUid, selectedUi
|
|
|
44
44
|
const unlockEpoch = useUnlockEpoch();
|
|
45
45
|
// An encrypted conversation's row stands for its latest message, whose subject only decrypting can tell. Rows are decrypted as they are listed
|
|
46
46
|
// whenever the mailbox is unlocked (and again once it is), the same way the message list does.
|
|
47
|
+
// A few at a time (the list can hold hundreds), and none more once the list has been replaced by a newer one.
|
|
47
48
|
useEffect(() => {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
let stale = false;
|
|
50
|
+
void mapWithConcurrency(conversations.filter(conversationLooksEncrypted), 4, async (conversation) => {
|
|
51
|
+
if (!stale) {
|
|
52
|
+
await decryptForDisplay(conversation.latestMessageUid, conversation.mailboxUid ?? mailboxUid);
|
|
51
53
|
}
|
|
52
|
-
}
|
|
54
|
+
});
|
|
55
|
+
return () => {
|
|
56
|
+
stale = true;
|
|
57
|
+
};
|
|
53
58
|
}, [conversations, mailboxUid, unlockEpoch]);
|
|
54
59
|
const [expanded, setExpanded] = useState(new Set());
|
|
55
60
|
const [messagesById, setMessagesById] = useState({});
|
|
56
61
|
const [loadingIds, setLoadingIds] = useState(new Set());
|
|
57
62
|
const [errorsById, setErrorsById] = useState({});
|
|
63
|
+
// The messages each cached thread was fetched for (its `messageUids`): a list that now says otherwise has the thread fetched again.
|
|
64
|
+
const fetchedForRef = useRef({});
|
|
58
65
|
// The answers given from a row's RSVP chip, by message uid: a row draws them at once, whichever row (the conversation's, or one of its messages) they were given from.
|
|
59
66
|
const [answers, setAnswers] = useState({});
|
|
60
67
|
function answered(updated) {
|
|
61
68
|
setAnswers((prev) => ({ ...prev, [updated.uid]: updated.meetingResponse }));
|
|
62
69
|
}
|
|
70
|
+
// New mail, a delete or a move changes a conversation's messages: what was fetched of it is out of date. An expanded thread is fetched again
|
|
71
|
+
// in place (the rows it shows stay until the new ones arrive); a collapsed one is forgotten, to be fetched when next opened. A conversation
|
|
72
|
+
// that left the list is forgotten too.
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
const listed = new Set();
|
|
75
|
+
const stale = [];
|
|
76
|
+
for (const conversation of conversations) {
|
|
77
|
+
const id = conversationRowKey(conversation);
|
|
78
|
+
listed.add(id);
|
|
79
|
+
const fetchedFor = fetchedForRef.current[id];
|
|
80
|
+
if (fetchedFor === undefined || fetchedFor === conversation.messageUids.join(",")) {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
if (expanded.has(id)) {
|
|
84
|
+
fetchedForRef.current[id] = conversation.messageUids.join(",");
|
|
85
|
+
listConversationMessages(conversation.mailboxUid ?? mailboxUid, conversation.conversationId, {}, client)
|
|
86
|
+
.then((loaded) => setMessagesById((prev) => ({ ...prev, [id]: loaded })))
|
|
87
|
+
.catch(() => undefined);
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
stale.push(id);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
for (const id of Object.keys(fetchedForRef.current)) {
|
|
94
|
+
if (!listed.has(id)) {
|
|
95
|
+
stale.push(id);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (stale.length > 0) {
|
|
99
|
+
for (const id of stale) {
|
|
100
|
+
delete fetchedForRef.current[id];
|
|
101
|
+
}
|
|
102
|
+
setMessagesById((prev) => Object.fromEntries(Object.entries(prev).filter(([id]) => !stale.includes(id))));
|
|
103
|
+
}
|
|
104
|
+
}, [conversations]);
|
|
63
105
|
function toggle(conversation) {
|
|
64
106
|
const id = conversationRowKey(conversation);
|
|
65
107
|
setExpanded((prev) => {
|
|
@@ -81,6 +123,7 @@ export default function ConversationList({ conversations, mailboxUid, selectedUi
|
|
|
81
123
|
delete next[id];
|
|
82
124
|
return next;
|
|
83
125
|
});
|
|
126
|
+
fetchedForRef.current[id] = conversation.messageUids.join(",");
|
|
84
127
|
listConversationMessages(conversation.mailboxUid ?? mailboxUid, conversation.conversationId, {}, client)
|
|
85
128
|
.then((loaded) => setMessagesById((prev) => ({ ...prev, [id]: loaded })))
|
|
86
129
|
.catch((err) => setErrorsById((prev) => ({
|
|
@@ -1,78 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
* draws the checkmark column; without it the row is a plain command. */
|
|
4
|
-
interface MenuItemBase {
|
|
5
|
-
key: string;
|
|
6
|
-
label: string;
|
|
7
|
-
/** A one-line explanation under the label, for an item whose effect isn't obvious from its name. */
|
|
8
|
-
description?: string;
|
|
9
|
-
role?: "menuitem" | "menuitemradio" | "menuitemcheckbox";
|
|
10
|
-
/** `"mixed"` is ARIA's third checkbox state - some of the things this row acts on have it and some
|
|
11
|
-
* don't, which is what a label applied to only part of a multi-message selection looks like. */
|
|
12
|
-
checked?: boolean | "mixed";
|
|
13
|
-
/** A colour swatch before the label - a label's own colour. */
|
|
14
|
-
swatchColor?: string;
|
|
15
|
-
/** An icon before the label, for a menu whose rows are commands (the reading pane's "More actions"). */
|
|
16
|
-
icon?: ReactNode;
|
|
17
|
-
/** A tooltip for the row - the full text of a label the row truncates. */
|
|
18
|
-
title?: string;
|
|
19
|
-
disabled?: boolean;
|
|
20
|
-
/** Leaves the menu open after choosing this row, for a multi-select list where several rows are ticked
|
|
21
|
-
* before one command commits them all. */
|
|
22
|
-
keepOpen?: boolean;
|
|
23
|
-
}
|
|
24
|
-
/** A row that does something when chosen. */
|
|
25
|
-
export interface MenuCommandSpec extends MenuItemBase {
|
|
26
|
-
onSelect: () => void;
|
|
27
|
-
submenu?: never;
|
|
28
|
-
}
|
|
29
|
-
/** A row that opens a submenu instead: choosing it replaces the menu's contents with these sections, under
|
|
30
|
-
* a Back row. One level deep, which is all any menu here needs - so it has nothing of its own to do. */
|
|
31
|
-
export interface MenuSubmenuSpec extends MenuItemBase {
|
|
32
|
-
submenu: MenuSectionSpec[];
|
|
33
|
-
onSelect?: never;
|
|
34
|
-
}
|
|
35
|
-
export type MenuItemSpec = MenuCommandSpec | MenuSubmenuSpec;
|
|
36
|
-
/** A labelled group of items. Groups after the first are drawn with a separator above them. */
|
|
37
|
-
export interface MenuSectionSpec {
|
|
38
|
-
key: string;
|
|
39
|
-
label?: string;
|
|
40
|
-
/** A short note under the group's items - e.g. why some of them are unavailable right now. */
|
|
41
|
-
note?: string;
|
|
42
|
-
items: MenuItemSpec[];
|
|
43
|
-
}
|
|
44
|
-
/** The height `PopoverPortal` is asked for - exact for a short menu, capped (the list scrolls) for a long one. */
|
|
45
|
-
export declare function menuHeight(sections: MenuSectionSpec[], width?: number): number;
|
|
46
|
-
export interface MenuButtonProps {
|
|
47
|
-
/** The trigger's visible label. Not drawn (nor is the chevron) with `iconOnly`, where `aria-label` is all the trigger says. */
|
|
48
|
-
label: ReactNode;
|
|
49
|
-
/** The trigger is just its `icon` - a round icon button like the reading pane's own command row, whose `className` it takes over
|
|
50
|
-
* completely. `aria-label` (and `title`) name it. */
|
|
51
|
-
iconOnly?: boolean;
|
|
52
|
-
/** The trigger's accessible name, which also names the menu itself - includes the current selection
|
|
53
|
-
* where there is one ("Filter: Unread"), so the button says what it's set to, not just what it does. */
|
|
54
|
-
"aria-label": string;
|
|
55
|
-
icon?: ReactNode;
|
|
56
|
-
disabled?: boolean;
|
|
57
|
-
title?: string;
|
|
58
|
-
sections: MenuSectionSpec[];
|
|
59
|
-
width?: number;
|
|
60
|
-
/** Extra classes for the trigger, on top of the shared toolbar-button styling. */
|
|
61
|
-
className?: string;
|
|
62
|
-
/** Told whenever the menu opens or closes - how a caller resets a draft it keeps for the menu's own
|
|
63
|
-
* multi-select rows (see `useLabelDraft()`). */
|
|
64
|
-
onOpenChange?: (open: boolean) => void;
|
|
65
|
-
}
|
|
66
|
-
/**
|
|
67
|
-
* A toolbar button that opens an ARIA menu: `role="menu"` rows with checkmarks for the current choice,
|
|
68
|
-
* full keyboard support (Enter/Space or Arrow Down to open, arrows/Home/End to move with wrap, Enter/Space
|
|
69
|
-
* to choose, Escape or Tab to close with focus returning to the trigger) and a click outside to dismiss.
|
|
70
|
-
*
|
|
71
|
-
* Rendered through `PopoverPortal` rather than as an absolutely-positioned child: the mail list toolbar
|
|
72
|
-
* lives inside the list's own `overflow-y-auto` scroll container, which clips an absolute popup however
|
|
73
|
-
* high its `z-index` - the same reason Compose's own pickers portal out (see `PopoverPortal`'s own doc
|
|
74
|
-
* comment). The portal supplies the positioning, the outside-click dismissal and the box; the menu
|
|
75
|
-
* semantics, roving focus and keyboard handling are this component's own.
|
|
76
|
-
*/
|
|
77
|
-
export default function MenuButton({ label, iconOnly, icon, disabled, title, sections, width, className, "aria-label": ariaLabel, onOpenChange, }: MenuButtonProps): React.JSX.Element;
|
|
78
|
-
export {};
|
|
1
|
+
export { default, menuHeight } from "../../../../lib/components/menus/MenuButton.js";
|
|
2
|
+
export type { MenuCommandSpec, MenuSubmenuSpec, MenuItemSpec, MenuSectionSpec, MenuButtonProps } from "../../../../lib/components/menus/MenuButton.js";
|