@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
|
@@ -2,403 +2,6 @@
|
|
|
2
2
|
// Copyright (C) 2026 Jean-Philippe Steinmetz
|
|
3
3
|
// SPDX-License-Identifier: MPL-2.0
|
|
4
4
|
///////////////////////////////////////////////////////////////////////////////
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
/** What every row has. `checked` turns it into a radio/checkbox item (the caller says which via `role`) and
|
|
10
|
-
* draws the checkmark column; without it the row is a plain command. */
|
|
11
|
-
interface MenuItemBase {
|
|
12
|
-
key: string;
|
|
13
|
-
label: string;
|
|
14
|
-
/** A one-line explanation under the label, for an item whose effect isn't obvious from its name. */
|
|
15
|
-
description?: string;
|
|
16
|
-
role?: "menuitem" | "menuitemradio" | "menuitemcheckbox";
|
|
17
|
-
/** `"mixed"` is ARIA's third checkbox state - some of the things this row acts on have it and some
|
|
18
|
-
* don't, which is what a label applied to only part of a multi-message selection looks like. */
|
|
19
|
-
checked?: boolean | "mixed";
|
|
20
|
-
/** A colour swatch before the label - a label's own colour. */
|
|
21
|
-
swatchColor?: string;
|
|
22
|
-
/** An icon before the label, for a menu whose rows are commands (the reading pane's "More actions"). */
|
|
23
|
-
icon?: ReactNode;
|
|
24
|
-
/** A tooltip for the row - the full text of a label the row truncates. */
|
|
25
|
-
title?: string;
|
|
26
|
-
disabled?: boolean;
|
|
27
|
-
/** Leaves the menu open after choosing this row, for a multi-select list where several rows are ticked
|
|
28
|
-
* before one command commits them all. */
|
|
29
|
-
keepOpen?: boolean;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/** A row that does something when chosen. */
|
|
33
|
-
export interface MenuCommandSpec extends MenuItemBase {
|
|
34
|
-
onSelect: () => void;
|
|
35
|
-
submenu?: never;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/** A row that opens a submenu instead: choosing it replaces the menu's contents with these sections, under
|
|
39
|
-
* a Back row. One level deep, which is all any menu here needs - so it has nothing of its own to do. */
|
|
40
|
-
export interface MenuSubmenuSpec extends MenuItemBase {
|
|
41
|
-
submenu: MenuSectionSpec[];
|
|
42
|
-
onSelect?: never;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export type MenuItemSpec = MenuCommandSpec | MenuSubmenuSpec;
|
|
46
|
-
|
|
47
|
-
/** A labelled group of items. Groups after the first are drawn with a separator above them. */
|
|
48
|
-
export interface MenuSectionSpec {
|
|
49
|
-
key: string;
|
|
50
|
-
label?: string;
|
|
51
|
-
/** A short note under the group's items - e.g. why some of them are unavailable right now. */
|
|
52
|
-
note?: string;
|
|
53
|
-
items: MenuItemSpec[];
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// `PopoverPortal` positions a fixed-size box (it has no auto-height mode), so the menu's height is
|
|
57
|
-
// computed from its own contents rather than measured - from whichever level is *shown*, so a submenu
|
|
58
|
-
// isn't left standing in the parent menu's taller box. These are the exact heights the classes below
|
|
59
|
-
// render at, so the box is never short enough to clip its last row: an item is `h-9`, a group label
|
|
60
|
-
// `h-6`, a note as many `leading-4` lines as it wraps to, a separator a 1px rule inside `my-1`, and the
|
|
61
|
-
// list itself `py-1`.
|
|
62
|
-
const ITEM_HEIGHT = 36;
|
|
63
|
-
const ITEM_DESCRIPTION_HEIGHT = 16;
|
|
64
|
-
const GROUP_LABEL_HEIGHT = 24;
|
|
65
|
-
const NOTE_LINE_HEIGHT = 16;
|
|
66
|
-
const NOTE_PADDING = 4;
|
|
67
|
-
/** A note wraps, so its height depends on how much of it fits a line: `text-xs` averages a little over 6px
|
|
68
|
-
* a character, and the note sits inside the list's `px-3`. Rounded so the estimate is never *under* the
|
|
69
|
-
* lines the browser actually draws - a box a few pixels too tall shows blank space, one too short clips. */
|
|
70
|
-
const NOTE_CHAR_WIDTH = 6.4;
|
|
71
|
-
const NOTE_PADDING_X = 24;
|
|
72
|
-
const SEPARATOR_HEIGHT = 9;
|
|
73
|
-
const LIST_PADDING = 8;
|
|
74
|
-
const MENU_MAX_HEIGHT = 460;
|
|
75
|
-
const DEFAULT_MENU_WIDTH = 248;
|
|
76
|
-
|
|
77
|
-
/** How tall `note` renders at `width`, wrapped. */
|
|
78
|
-
function noteHeight(note: string, width: number): number {
|
|
79
|
-
const charsPerLine = Math.max(1, Math.floor((width - NOTE_PADDING_X) / NOTE_CHAR_WIDTH));
|
|
80
|
-
return Math.ceil(note.length / charsPerLine) * NOTE_LINE_HEIGHT + NOTE_PADDING;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/** The height `PopoverPortal` is asked for - exact for a short menu, capped (the list scrolls) for a long one. */
|
|
84
|
-
export function menuHeight(sections: MenuSectionSpec[], width: number = DEFAULT_MENU_WIDTH): number {
|
|
85
|
-
let height = LIST_PADDING;
|
|
86
|
-
sections.forEach((section, index) => {
|
|
87
|
-
if (index > 0) {
|
|
88
|
-
height += SEPARATOR_HEIGHT;
|
|
89
|
-
}
|
|
90
|
-
if (section.label) {
|
|
91
|
-
height += GROUP_LABEL_HEIGHT;
|
|
92
|
-
}
|
|
93
|
-
for (const item of section.items) {
|
|
94
|
-
height += ITEM_HEIGHT + (item.description ? ITEM_DESCRIPTION_HEIGHT : 0);
|
|
95
|
-
}
|
|
96
|
-
if (section.note) {
|
|
97
|
-
height += noteHeight(section.note, width);
|
|
98
|
-
}
|
|
99
|
-
});
|
|
100
|
-
return Math.min(height, MENU_MAX_HEIGHT);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
export interface MenuButtonProps {
|
|
104
|
-
/** The trigger's visible label. Not drawn (nor is the chevron) with `iconOnly`, where `aria-label` is all the trigger says. */
|
|
105
|
-
label: ReactNode;
|
|
106
|
-
/** The trigger is just its `icon` - a round icon button like the reading pane's own command row, whose `className` it takes over
|
|
107
|
-
* completely. `aria-label` (and `title`) name it. */
|
|
108
|
-
iconOnly?: boolean;
|
|
109
|
-
/** The trigger's accessible name, which also names the menu itself - includes the current selection
|
|
110
|
-
* where there is one ("Filter: Unread"), so the button says what it's set to, not just what it does. */
|
|
111
|
-
"aria-label": string;
|
|
112
|
-
icon?: ReactNode;
|
|
113
|
-
disabled?: boolean;
|
|
114
|
-
title?: string;
|
|
115
|
-
sections: MenuSectionSpec[];
|
|
116
|
-
width?: number;
|
|
117
|
-
/** Extra classes for the trigger, on top of the shared toolbar-button styling. */
|
|
118
|
-
className?: string;
|
|
119
|
-
/** Told whenever the menu opens or closes - how a caller resets a draft it keeps for the menu's own
|
|
120
|
-
* multi-select rows (see `useLabelDraft()`). */
|
|
121
|
-
onOpenChange?: (open: boolean) => void;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* A toolbar button that opens an ARIA menu: `role="menu"` rows with checkmarks for the current choice,
|
|
126
|
-
* full keyboard support (Enter/Space or Arrow Down to open, arrows/Home/End to move with wrap, Enter/Space
|
|
127
|
-
* to choose, Escape or Tab to close with focus returning to the trigger) and a click outside to dismiss.
|
|
128
|
-
*
|
|
129
|
-
* Rendered through `PopoverPortal` rather than as an absolutely-positioned child: the mail list toolbar
|
|
130
|
-
* lives inside the list's own `overflow-y-auto` scroll container, which clips an absolute popup however
|
|
131
|
-
* high its `z-index` - the same reason Compose's own pickers portal out (see `PopoverPortal`'s own doc
|
|
132
|
-
* comment). The portal supplies the positioning, the outside-click dismissal and the box; the menu
|
|
133
|
-
* semantics, roving focus and keyboard handling are this component's own.
|
|
134
|
-
*/
|
|
135
|
-
export default function MenuButton({
|
|
136
|
-
label,
|
|
137
|
-
iconOnly = false,
|
|
138
|
-
icon,
|
|
139
|
-
disabled,
|
|
140
|
-
title,
|
|
141
|
-
sections,
|
|
142
|
-
width = DEFAULT_MENU_WIDTH,
|
|
143
|
-
className = "",
|
|
144
|
-
"aria-label": ariaLabel,
|
|
145
|
-
onOpenChange,
|
|
146
|
-
}: MenuButtonProps) {
|
|
147
|
-
const [open, setOpen] = useState(false);
|
|
148
|
-
const [activeIndex, setActiveIndex] = useState(0);
|
|
149
|
-
// The key of the item whose submenu is showing, or `null` at the top level. Held as a key, not as the
|
|
150
|
-
// item itself, because `sections` is rebuilt on every render.
|
|
151
|
-
const [submenuKey, setSubmenuKey] = useState<string | null>(null);
|
|
152
|
-
const triggerRef = useRef<HTMLButtonElement>(null);
|
|
153
|
-
const itemRefs = useRef<(HTMLButtonElement | null)[]>([]);
|
|
154
|
-
// State, not a ref: `PopoverPortal` renders nothing at all until its own positioning effect has run, so
|
|
155
|
-
// the rows don't exist yet when this component's effects first fire. Keying the focus effect on the
|
|
156
|
-
// menu node itself makes it run again once they do.
|
|
157
|
-
const [menuNode, setMenuNode] = useState<HTMLDivElement | null>(null);
|
|
158
|
-
|
|
159
|
-
const openSubmenu = sections
|
|
160
|
-
.flatMap((section) => section.items)
|
|
161
|
-
.find((item): item is MenuSubmenuSpec => item.key === submenuKey && !!item.submenu);
|
|
162
|
-
/** The row that leaves a submenu again - synthesized rather than asked of the caller, so every submenu
|
|
163
|
-
* has the same way back however it was built. */
|
|
164
|
-
const backItem: MenuCommandSpec = {
|
|
165
|
-
key: "__back",
|
|
166
|
-
label: `Back to ${ariaLabel}`,
|
|
167
|
-
keepOpen: true,
|
|
168
|
-
onSelect: () => leaveSubmenu(),
|
|
169
|
-
};
|
|
170
|
-
const shownSections: MenuSectionSpec[] = openSubmenu
|
|
171
|
-
? [{ key: "__back", items: [backItem] }, ...openSubmenu.submenu]
|
|
172
|
-
: sections;
|
|
173
|
-
const items = shownSections.flatMap((section) => section.items);
|
|
174
|
-
const enabledIndexes = items.map((item, index) => (item.disabled ? -1 : index)).filter((index) => index !== -1);
|
|
175
|
-
|
|
176
|
-
/** The row focus starts on: the current choice where there is one, else the first row that can take focus. */
|
|
177
|
-
function initialActiveIndex(within: MenuItemSpec[]): number {
|
|
178
|
-
const checked = within.findIndex((item) => item.checked === true && !item.disabled);
|
|
179
|
-
const enabled = within.map((item, index) => (item.disabled ? -1 : index)).filter((index) => index !== -1);
|
|
180
|
-
return checked === -1 ? (enabled[0] ?? 0) : checked;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
function openMenu() {
|
|
184
|
-
setSubmenuKey(null);
|
|
185
|
-
setActiveIndex(initialActiveIndex(sections.flatMap((section) => section.items)));
|
|
186
|
-
setOpen(true);
|
|
187
|
-
onOpenChange?.(true);
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
function closeMenu(returnFocus: boolean) {
|
|
191
|
-
setOpen(false);
|
|
192
|
-
setSubmenuKey(null);
|
|
193
|
-
onOpenChange?.(false);
|
|
194
|
-
if (returnFocus) {
|
|
195
|
-
triggerRef.current?.focus();
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
function enterSubmenu(item: MenuSubmenuSpec) {
|
|
200
|
-
setSubmenuKey(item.key);
|
|
201
|
-
const rows = item.submenu.flatMap((section) => section.items);
|
|
202
|
-
// Past the Back row, onto the first row of the submenu itself - or, when none of them can take focus (every row is unavailable right now), onto
|
|
203
|
-
// the Back row, so the keyboard still has a place to be.
|
|
204
|
-
setActiveIndex(rows.some((row) => !row.disabled) ? 1 + initialActiveIndex(rows) : 0);
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
function leaveSubmenu() {
|
|
208
|
-
// Back on the row the submenu was opened from.
|
|
209
|
-
const parentIndex = sections.flatMap((section) => section.items).findIndex((item) => item.key === submenuKey);
|
|
210
|
-
setSubmenuKey(null);
|
|
211
|
-
setActiveIndex(parentIndex);
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
/** What a row does when it is chosen: open its submenu, or run it and close unless it asked to stay. */
|
|
215
|
-
function selectItem(item: MenuItemSpec) {
|
|
216
|
-
if (item.submenu) {
|
|
217
|
-
enterSubmenu(item);
|
|
218
|
-
return;
|
|
219
|
-
}
|
|
220
|
-
item.onSelect();
|
|
221
|
-
if (!item.keepOpen) {
|
|
222
|
-
closeMenu(true);
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
// Roving focus: only the active row is tabbable, and it takes DOM focus whenever it changes, so the
|
|
227
|
-
// arrow keys move the screen reader's cursor too rather than only a visual highlight.
|
|
228
|
-
useEffect(() => {
|
|
229
|
-
if (open && menuNode) {
|
|
230
|
-
// A disabled row can't take focus, so a menu with nothing enabled focuses its own container -
|
|
231
|
-
// otherwise the keys it handles would go to whatever had focus before it opened.
|
|
232
|
-
if (enabledIndexes.length === 0) {
|
|
233
|
-
menuNode.focus();
|
|
234
|
-
} else {
|
|
235
|
-
itemRefs.current[activeIndex]?.focus();
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
// `submenuKey` too: drilling in or out can land on the same index in the other level's list, and
|
|
239
|
-
// the row that index *means* is a different button, which the effect must move focus to.
|
|
240
|
-
}, [open, activeIndex, submenuKey, menuNode]);
|
|
241
|
-
|
|
242
|
-
function moveActive(delta: number) {
|
|
243
|
-
if (enabledIndexes.length === 0) {
|
|
244
|
-
return;
|
|
245
|
-
}
|
|
246
|
-
const position = enabledIndexes.indexOf(activeIndex);
|
|
247
|
-
setActiveIndex(enabledIndexes[(position + delta + enabledIndexes.length) % enabledIndexes.length]);
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
function handleMenuKeyDown(e: React.KeyboardEvent) {
|
|
251
|
-
if (e.key === "ArrowDown") {
|
|
252
|
-
e.preventDefault();
|
|
253
|
-
moveActive(1);
|
|
254
|
-
} else if (e.key === "ArrowUp") {
|
|
255
|
-
e.preventDefault();
|
|
256
|
-
moveActive(-1);
|
|
257
|
-
} else if (e.key === "Home") {
|
|
258
|
-
e.preventDefault();
|
|
259
|
-
setActiveIndex(enabledIndexes[0] ?? 0);
|
|
260
|
-
} else if (e.key === "End") {
|
|
261
|
-
e.preventDefault();
|
|
262
|
-
setActiveIndex(enabledIndexes[enabledIndexes.length - 1] ?? 0);
|
|
263
|
-
} else if (e.key === "ArrowRight" && items[activeIndex]?.submenu) {
|
|
264
|
-
e.preventDefault();
|
|
265
|
-
enterSubmenu(items[activeIndex]);
|
|
266
|
-
} else if ((e.key === "ArrowLeft" || e.key === "Escape") && openSubmenu) {
|
|
267
|
-
// ARIA's own submenu behaviour: Escape leaves the submenu for its parent menu rather than
|
|
268
|
-
// dismissing the whole thing.
|
|
269
|
-
e.preventDefault();
|
|
270
|
-
leaveSubmenu();
|
|
271
|
-
} else if (e.key === "Escape" || e.key === "Tab") {
|
|
272
|
-
e.preventDefault();
|
|
273
|
-
closeMenu(true);
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
function handleTriggerKeyDown(e: React.KeyboardEvent) {
|
|
278
|
-
if (!open && (e.key === "ArrowDown" || e.key === "ArrowUp")) {
|
|
279
|
-
e.preventDefault();
|
|
280
|
-
openMenu();
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
let itemIndex = -1;
|
|
285
|
-
|
|
286
|
-
return (
|
|
287
|
-
<>
|
|
288
|
-
<button
|
|
289
|
-
ref={triggerRef}
|
|
290
|
-
type="button"
|
|
291
|
-
aria-haspopup="menu"
|
|
292
|
-
aria-expanded={open}
|
|
293
|
-
aria-label={ariaLabel}
|
|
294
|
-
title={title}
|
|
295
|
-
disabled={disabled}
|
|
296
|
-
onClick={() => (open ? closeMenu(false) : openMenu())}
|
|
297
|
-
onKeyDown={handleTriggerKeyDown}
|
|
298
|
-
className={
|
|
299
|
-
iconOnly
|
|
300
|
-
? className
|
|
301
|
-
: [
|
|
302
|
-
// `min-w-0` so a long label ("Filter: Has attachments") truncates instead of pushing
|
|
303
|
-
// whatever sits beside it in the toolbar off the edge of a 384px list column.
|
|
304
|
-
"inline-flex min-w-0 items-center gap-1 px-2 py-1 rounded-md text-sm text-text hover:bg-surface-alt disabled:opacity-50 disabled:hover:bg-transparent",
|
|
305
|
-
className,
|
|
306
|
-
].join(" ")
|
|
307
|
-
}
|
|
308
|
-
>
|
|
309
|
-
{icon}
|
|
310
|
-
{!iconOnly && <span className="truncate">{label}</span>}
|
|
311
|
-
{!iconOnly && <HiChevronDown size={14} aria-hidden="true" className="shrink-0 text-text-muted" />}
|
|
312
|
-
</button>
|
|
313
|
-
{open && (
|
|
314
|
-
<PopoverPortal
|
|
315
|
-
anchorRef={triggerRef}
|
|
316
|
-
onClose={() => closeMenu(false)}
|
|
317
|
-
width={width}
|
|
318
|
-
height={menuHeight(shownSections, width)}
|
|
319
|
-
aria-label={ariaLabel}
|
|
320
|
-
>
|
|
321
|
-
<div
|
|
322
|
-
ref={setMenuNode}
|
|
323
|
-
role="menu"
|
|
324
|
-
tabIndex={-1}
|
|
325
|
-
aria-label={ariaLabel}
|
|
326
|
-
onKeyDown={handleMenuKeyDown}
|
|
327
|
-
className="flex-1 overflow-y-auto py-1"
|
|
328
|
-
>
|
|
329
|
-
{shownSections.map((section, sectionIndex) => (
|
|
330
|
-
<div key={section.key} role="group" aria-label={section.label} className={sectionIndex > 0 ? "border-t border-border mt-1 pt-1" : ""}>
|
|
331
|
-
{section.label && (
|
|
332
|
-
<div aria-hidden="true" className="h-6 flex items-center px-3 text-xs font-semibold uppercase tracking-wide text-text-muted">
|
|
333
|
-
{section.label}
|
|
334
|
-
</div>
|
|
335
|
-
)}
|
|
336
|
-
{section.items.map((item) => {
|
|
337
|
-
itemIndex += 1;
|
|
338
|
-
const index = itemIndex;
|
|
339
|
-
const role = item.role ?? "menuitem";
|
|
340
|
-
return (
|
|
341
|
-
<button
|
|
342
|
-
key={item.key}
|
|
343
|
-
ref={(node) => {
|
|
344
|
-
itemRefs.current[index] = node;
|
|
345
|
-
}}
|
|
346
|
-
type="button"
|
|
347
|
-
role={role}
|
|
348
|
-
disabled={item.disabled}
|
|
349
|
-
title={item.title}
|
|
350
|
-
tabIndex={index === activeIndex ? 0 : -1}
|
|
351
|
-
{...(role === "menuitem"
|
|
352
|
-
? {}
|
|
353
|
-
: { "aria-checked": item.checked === "mixed" ? ("mixed" as const) : !!item.checked })}
|
|
354
|
-
{...(item.submenu ? { "aria-haspopup": "menu" as const, "aria-expanded": false } : {})}
|
|
355
|
-
onClick={() => selectItem(item)}
|
|
356
|
-
className="w-full flex items-start gap-2 px-3 py-2 text-left text-sm text-text hover:bg-surface-alt disabled:opacity-50 disabled:hover:bg-transparent"
|
|
357
|
-
>
|
|
358
|
-
<span className="w-4 shrink-0 flex justify-center pt-0.5">
|
|
359
|
-
{item.key === "__back" && (
|
|
360
|
-
<HiChevronLeft size={14} aria-hidden="true" className="text-text-muted" />
|
|
361
|
-
)}
|
|
362
|
-
{item.checked === "mixed" ? (
|
|
363
|
-
<HiMinus size={14} aria-hidden="true" className="text-primary-dark" />
|
|
364
|
-
) : (
|
|
365
|
-
item.checked && <HiCheck size={14} aria-hidden="true" className="text-primary-dark" />
|
|
366
|
-
)}
|
|
367
|
-
</span>
|
|
368
|
-
<span className="min-w-0 flex-1">
|
|
369
|
-
<span className="flex h-5 items-center gap-1.5">
|
|
370
|
-
{item.icon && (
|
|
371
|
-
<span aria-hidden="true" className="shrink-0 text-text-muted">
|
|
372
|
-
{item.icon}
|
|
373
|
-
</span>
|
|
374
|
-
)}
|
|
375
|
-
{item.swatchColor && (
|
|
376
|
-
<span
|
|
377
|
-
aria-hidden="true"
|
|
378
|
-
className="w-2.5 h-2.5 rounded-full shrink-0"
|
|
379
|
-
style={{ backgroundColor: item.swatchColor }}
|
|
380
|
-
/>
|
|
381
|
-
)}
|
|
382
|
-
<span className="truncate">{item.label}</span>
|
|
383
|
-
</span>
|
|
384
|
-
{item.description && (
|
|
385
|
-
<span className="block h-4 text-xs leading-4 text-text-muted truncate font-normal">
|
|
386
|
-
{item.description}
|
|
387
|
-
</span>
|
|
388
|
-
)}
|
|
389
|
-
</span>
|
|
390
|
-
{item.submenu && (
|
|
391
|
-
<HiChevronRight size={14} aria-hidden="true" className="shrink-0 mt-0.5 text-text-muted" />
|
|
392
|
-
)}
|
|
393
|
-
</button>
|
|
394
|
-
);
|
|
395
|
-
})}
|
|
396
|
-
{section.note && <p className="px-3 py-1 text-xs leading-4 text-text-muted">{section.note}</p>}
|
|
397
|
-
</div>
|
|
398
|
-
))}
|
|
399
|
-
</div>
|
|
400
|
-
</PopoverPortal>
|
|
401
|
-
)}
|
|
402
|
-
</>
|
|
403
|
-
);
|
|
404
|
-
}
|
|
5
|
+
// `MenuButton` lives in `lib/components/menus` so shared form components (`ImageEditBadge`) can use it; this keeps the mail components' import path working.
|
|
6
|
+
export { default, menuHeight } from "../../../../lib/components/menus/MenuButton.js";
|
|
7
|
+
export type { MenuCommandSpec, MenuSubmenuSpec, MenuItemSpec, MenuSectionSpec, MenuButtonProps } from "../../../../lib/components/menus/MenuButton.js";
|
|
@@ -85,7 +85,7 @@ import LabelMenuButton from "./labelMenu.js";
|
|
|
85
85
|
import Button from "../../../../lib/components/buttons/Button.js";
|
|
86
86
|
import EncryptedBody from "./reading/EncryptedBody.js";
|
|
87
87
|
import { ENCRYPTED_SUBJECT_PLACEHOLDER, displaySubject } from "./reading/EncryptedPreview.js";
|
|
88
|
-
import { rememberDecrypted } from "../../mail/decryptedMessages.js";
|
|
88
|
+
import { mapWithConcurrency, rememberDecrypted } from "../../mail/decryptedMessages.js";
|
|
89
89
|
import MessageBody, { BodySkeleton } from "./reading/MessageBody.js";
|
|
90
90
|
import { BODY_FONT_STYLE, CardShell, SenderAvatar, SubjectCard } from "./reading/MessageCard.js";
|
|
91
91
|
import MessageMoreMenu, { MessageMenuActions } from "./reading/MessageMoreMenu.js";
|
|
@@ -424,6 +424,9 @@ function sameSubject(subject: string, threadSubject: string | undefined): boolea
|
|
|
424
424
|
return subjectCore(subject) === subjectCore(threadSubject);
|
|
425
425
|
}
|
|
426
426
|
|
|
427
|
+
/** How many earlier messages of a conversation are loaded at once when printing it. */
|
|
428
|
+
const PRINT_CONCURRENCY = 4;
|
|
429
|
+
|
|
427
430
|
function formatBytes(bytes: number): string {
|
|
428
431
|
if (bytes >= 1_000_000) return `${(bytes / 1_000_000).toFixed(1)} MB`;
|
|
429
432
|
if (bytes >= 1_000) return `${(bytes / 1_000).toFixed(1)} KB`;
|
|
@@ -1038,10 +1041,11 @@ function MessageDetailContent({
|
|
|
1038
1041
|
(verifiedOrSealed || security.state === "encrypted" || security.state === "encrypted_unverified_signer" || security.state === "signature_failed")
|
|
1039
1042
|
? security.attachments
|
|
1040
1043
|
: undefined;
|
|
1041
|
-
// Any state
|
|
1044
|
+
// Any state with a valid signature shows the address actually signed for (the protected From when the message
|
|
1042
1045
|
// carries one - it equals the outer From's address, or verification would have failed) next to the badge, never a
|
|
1043
|
-
// display name alone: anyone can put "ceo@corp.com" in the name of a message sent from x@corp-pay.com.
|
|
1044
|
-
|
|
1046
|
+
// display name alone: anyone can put "ceo@corp.com" in the name of a message sent from x@corp-pay.com. A failed
|
|
1047
|
+
// signature vouches for nothing, so its message shows the outer address like an unsigned one.
|
|
1048
|
+
const signatureShown = security !== null && (verifiedOrSealed || UNVERIFIED_SIGNER_STATES.has(security.state));
|
|
1045
1049
|
const senderAddress = (signatureShown ? extractAddresses(security.protectedHeaders?.from)[0] : undefined) ?? message.from.address;
|
|
1046
1050
|
const senderName = message.from.displayName;
|
|
1047
1051
|
const senderNameCheck = checkSenderName(senderName, senderAddress);
|
|
@@ -1139,15 +1143,14 @@ function MessageDetailContent({
|
|
|
1139
1143
|
try {
|
|
1140
1144
|
const content = bodyContent ?? (await fetchBodyContent(message.uid, message.version));
|
|
1141
1145
|
const older = await olderConversationMessages(message, client).catch(() => []);
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
),
|
|
1146
|
+
// A few at a time: a long conversation is hundreds of messages, each of them several requests.
|
|
1147
|
+
const earlier = await mapWithConcurrency(older, PRINT_CONCURRENCY, (other) =>
|
|
1148
|
+
printableOlderMessage(other).catch(
|
|
1149
|
+
(): PrintableMessage => ({
|
|
1150
|
+
subject: other.subject || "(no subject)",
|
|
1151
|
+
headers: printHeaders(other, formatMailAddress(other.from)),
|
|
1152
|
+
content: { kind: "text", text: "This message could not be loaded, so it is not printed." },
|
|
1153
|
+
}),
|
|
1151
1154
|
),
|
|
1152
1155
|
);
|
|
1153
1156
|
const printable = buildConversationPrintDocument(shownSubject, [
|
|
@@ -29,6 +29,16 @@ import {
|
|
|
29
29
|
} from "react-icons/bs";
|
|
30
30
|
import GifPicker from "./GifPicker.js";
|
|
31
31
|
|
|
32
|
+
/** What a typed link target is stored as. One without a scheme (`example.com`) would be a relative link, which readers' clients drop as dead text: a bare
|
|
33
|
+
* email address becomes `mailto:`, anything else that is not a path or an anchor `https://`. A target with a scheme is kept as it is. */
|
|
34
|
+
export function linkHref(typed: string): string {
|
|
35
|
+
const target = typed.trim();
|
|
36
|
+
if (/^[a-z][a-z0-9+.-]*:/i.test(target) || /^[/#]/.test(target)) {
|
|
37
|
+
return target;
|
|
38
|
+
}
|
|
39
|
+
return /^[^\s@/]+@[^\s@/]+\.[^\s@/]+$/.test(target) ? `mailto:${target}` : `https://${target}`;
|
|
40
|
+
}
|
|
41
|
+
|
|
32
42
|
/** Loaded when the picker is first opened: the emoji list is a half-megabyte JSON file that nothing else needs. */
|
|
33
43
|
const EmojiPicker = lazy(() => import("./EmojiPicker.js"));
|
|
34
44
|
|
|
@@ -153,7 +163,7 @@ export default function ComposeToolbar({ editor, onUploadImage }: ComposeToolbar
|
|
|
153
163
|
if (linkUrl.trim().length === 0) {
|
|
154
164
|
run((ed) => ed.chain().focus().extendMarkRange("link").unsetLink().run());
|
|
155
165
|
} else {
|
|
156
|
-
run((ed) => ed.chain().focus().extendMarkRange("link").setLink({ href: linkUrl
|
|
166
|
+
run((ed) => ed.chain().focus().extendMarkRange("link").setLink({ href: linkHref(linkUrl) }).run());
|
|
157
167
|
}
|
|
158
168
|
setOpenPopup(null);
|
|
159
169
|
}
|