@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
|
@@ -6,6 +6,7 @@ import { pageTitle } from "../../../shared/navigation/pageTitle.js";
|
|
|
6
6
|
import React, { FormEvent, useEffect, useState } from "react";
|
|
7
7
|
import { ApiRequestError } from "../../../../lib/util/api.js";
|
|
8
8
|
import {
|
|
9
|
+
EscrowInfo,
|
|
9
10
|
KeyVault,
|
|
10
11
|
MasterKeyWrap,
|
|
11
12
|
PublicKey,
|
|
@@ -43,6 +44,7 @@ import { getMailbox } from "../../../../lib/mail/mailApi.js";
|
|
|
43
44
|
import { useApiClient } from "../../../../lib/util/apiClientContext.js";
|
|
44
45
|
import SettingsShell, { SettingsShellProps, useSettingsShell } from "../../../shared/components/settings/layout/SettingsShell.js";
|
|
45
46
|
import KeyEnrollmentGate from "../../../shared/components/layout/KeyEnrollmentGate.js";
|
|
47
|
+
import { MIN_PASSWORD_LENGTH } from "../../../shared/components/layout/RecoveryCodeUnlock.js";
|
|
46
48
|
import { useUnlockPrompt } from "../../../shared/components/layout/UnlockPromptProvider.js";
|
|
47
49
|
import { destroyLocalIndex } from "../../../shared/search/localIndexRpcClient.js";
|
|
48
50
|
import { notifyApiError } from "../../../shared/notifications/apiErrors.js";
|
|
@@ -62,8 +64,6 @@ import {
|
|
|
62
64
|
import { STALE_REQUEST_TEXT, beforeRequestText, healthWarning, isInstalling, timeOf } from "../../../shared/signing/enrollmentView.js";
|
|
63
65
|
import { useSigningEnrollmentInfo } from "../../../shared/signing/signingInfo.js";
|
|
64
66
|
|
|
65
|
-
const MIN_PASSWORD_LENGTH = 8;
|
|
66
|
-
|
|
67
67
|
/** Mirrors restapi's `BaseKeyVaultRoute` `MAX_MASTER_KEY_WRAPS` - `addMasterKeyWrap()` refuses a vault
|
|
68
68
|
* already holding this many wraps (escrow included). */
|
|
69
69
|
export const MAX_MASTER_KEY_WRAPS = 20;
|
|
@@ -253,6 +253,7 @@ function EncryptionContent({ canManageKeys }: { canManageKeys: boolean }) {
|
|
|
253
253
|
// Distinguishes the "Save your new recovery codes" screen's copy for the two different actions that
|
|
254
254
|
// land on it - a plain regeneration only invalidates old recovery codes, while a rotation also
|
|
255
255
|
// invalidates every other unlock method, which the copy needs to say plainly.
|
|
256
|
+
const [confirmingRotation, setConfirmingRotation] = useState(false);
|
|
256
257
|
const [recoveryCodesReason, setRecoveryCodesReason] = useState<"regenerate" | "rotate">("regenerate");
|
|
257
258
|
const [codesSaved, setCodesSaved] = useState(false);
|
|
258
259
|
const [codesCopied, setCodesCopied] = useState(false);
|
|
@@ -304,6 +305,7 @@ function EncryptionContent({ canManageKeys }: { canManageKeys: boolean }) {
|
|
|
304
305
|
const hasEscrowWrap = vault?.masterKeyWraps.some((w) => w.method === "escrow") ?? false;
|
|
305
306
|
const [wrappingEscrow, setWrappingEscrow] = useState(false);
|
|
306
307
|
const [escrowError, setEscrowError] = useState<string | null>(null);
|
|
308
|
+
const [pendingEscrow, setPendingEscrow] = useState<EscrowInfo | null>(null);
|
|
307
309
|
|
|
308
310
|
/**
|
|
309
311
|
* This mailbox's unlocked keys *right now* - never a copy read at render time, which a lock (idle
|
|
@@ -351,13 +353,28 @@ function EncryptionContent({ canManageKeys }: { canManageKeys: boolean }) {
|
|
|
351
353
|
return err instanceof ApiRequestError ? err.message : fallback;
|
|
352
354
|
}
|
|
353
355
|
|
|
356
|
+
/** Looks up the certificate the server says this mailbox's escrow scope holds, and asks the owner to confirm it
|
|
357
|
+
* before the master key is wrapped to it: nothing else vouches for that certificate, and whoever holds its
|
|
358
|
+
* private key can read this mailbox's encrypted mail. */
|
|
354
359
|
async function handleWrapEscrow() {
|
|
355
360
|
setEscrowError(null);
|
|
356
361
|
setWrappingEscrow(true);
|
|
357
362
|
try {
|
|
358
363
|
// Only reachable when mailbox.escrowScopeId is set (see the render guard below).
|
|
364
|
+
await verifiedUnlockedKeys();
|
|
365
|
+
setPendingEscrow(await getEscrowInfo(mailboxUid!, client));
|
|
366
|
+
} catch (err) {
|
|
367
|
+
setEscrowError(errorMessage(err, "Could not add escrow protection for this mailbox."));
|
|
368
|
+
} finally {
|
|
369
|
+
setWrappingEscrow(false);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
async function handleConfirmEscrow(escrowInfo: EscrowInfo) {
|
|
374
|
+
setPendingEscrow(null);
|
|
375
|
+
setWrappingEscrow(true);
|
|
376
|
+
try {
|
|
359
377
|
const { current } = await verifiedUnlockedKeys();
|
|
360
|
-
const escrowInfo = await getEscrowInfo(mailboxUid!, client);
|
|
361
378
|
const wrap = await buildEscrowWrap(current.masterKey, escrowInfo.escrowScopeId, fromBase64(escrowInfo.publicKey.publicKey));
|
|
362
379
|
await addMasterKeyWrap(mailboxUid!, wrap, undefined, client);
|
|
363
380
|
await loadVault();
|
|
@@ -729,7 +746,7 @@ function EncryptionContent({ canManageKeys }: { canManageKeys: boolean }) {
|
|
|
729
746
|
* working the instant this succeeds, since `rekey()` replaces `masterKeyWraps` wholesale - the whole
|
|
730
747
|
* point, for a captured-wrap scenario where it's unclear which method was compromised.
|
|
731
748
|
*/
|
|
732
|
-
|
|
749
|
+
function handleRotateKeys(e: FormEvent) {
|
|
733
750
|
e.preventDefault();
|
|
734
751
|
if (rotationBlockedBySigning) {
|
|
735
752
|
// The form's controls are disabled in this state too; this also covers an implicit submit.
|
|
@@ -744,6 +761,13 @@ function EncryptionContent({ canManageKeys }: { canManageKeys: boolean }) {
|
|
|
744
761
|
return;
|
|
745
762
|
}
|
|
746
763
|
setActionError(null);
|
|
764
|
+
// One press must not end every other unlock method and every other device's password: say so, and wait for a yes.
|
|
765
|
+
setConfirmingRotation(true);
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
async function rotateKeys() {
|
|
769
|
+
setConfirmingRotation(false);
|
|
770
|
+
setActionError(null);
|
|
747
771
|
setEscrowError(null);
|
|
748
772
|
setRotating(true);
|
|
749
773
|
let keys: PublicKey[];
|
|
@@ -1038,6 +1062,30 @@ function EncryptionContent({ canManageKeys }: { canManageKeys: boolean }) {
|
|
|
1038
1062
|
)}
|
|
1039
1063
|
</div>
|
|
1040
1064
|
)}
|
|
1065
|
+
<Modal open={pendingEscrow !== null} onClose={() => setPendingEscrow(null)} title="Confirm escrow certificate">
|
|
1066
|
+
<p className="text-sm mb-3">
|
|
1067
|
+
Anyone holding the private key for this certificate will be able to recover this mailbox’s
|
|
1068
|
+
encrypted mail. Confirm it is the escrow certificate your organization expects before continuing.
|
|
1069
|
+
</p>
|
|
1070
|
+
<dl className="text-sm mb-5 flex flex-col gap-1">
|
|
1071
|
+
<div>
|
|
1072
|
+
<dt className="text-xs text-text-muted">Escrow scope</dt>
|
|
1073
|
+
<dd className="break-all">{pendingEscrow?.escrowScopeId}</dd>
|
|
1074
|
+
</div>
|
|
1075
|
+
<div>
|
|
1076
|
+
<dt className="text-xs text-text-muted">Certificate fingerprint</dt>
|
|
1077
|
+
<dd className="font-mono break-all">{pendingEscrow?.publicKey.fingerprint}</dd>
|
|
1078
|
+
</div>
|
|
1079
|
+
</dl>
|
|
1080
|
+
<div className="flex gap-3 justify-end">
|
|
1081
|
+
<Button type="button" variant="secondary" className="!w-auto" onClick={() => setPendingEscrow(null)}>
|
|
1082
|
+
Cancel
|
|
1083
|
+
</Button>
|
|
1084
|
+
<Button type="button" className="!w-auto" onClick={() => void handleConfirmEscrow(pendingEscrow!)}>
|
|
1085
|
+
Trust this certificate
|
|
1086
|
+
</Button>
|
|
1087
|
+
</div>
|
|
1088
|
+
</Modal>
|
|
1041
1089
|
</div>
|
|
1042
1090
|
)}
|
|
1043
1091
|
|
|
@@ -1160,6 +1208,20 @@ function EncryptionContent({ canManageKeys }: { canManageKeys: boolean }) {
|
|
|
1160
1208
|
</Button>
|
|
1161
1209
|
</div>
|
|
1162
1210
|
|
|
1211
|
+
<Modal open={confirmingRotation} onClose={() => setConfirmingRotation(false)} title="Rotate your keys?">
|
|
1212
|
+
<p className="text-sm mb-3">
|
|
1213
|
+
Every other way of unlocking this mailbox - its password, its recovery codes, and every other device’s saved password - stops working
|
|
1214
|
+
the moment this finishes, and cannot be brought back. You will be shown new recovery codes once.
|
|
1215
|
+
</p>
|
|
1216
|
+
<div className="flex gap-3 justify-end">
|
|
1217
|
+
<Button type="button" variant="secondary" className="!w-auto" onClick={() => setConfirmingRotation(false)}>
|
|
1218
|
+
Cancel
|
|
1219
|
+
</Button>
|
|
1220
|
+
<Button type="button" className="!w-auto !bg-none !bg-danger !border-danger hover:!bg-danger" onClick={() => void rotateKeys()}>
|
|
1221
|
+
Yes, rotate my keys
|
|
1222
|
+
</Button>
|
|
1223
|
+
</div>
|
|
1224
|
+
</Modal>
|
|
1163
1225
|
<form onSubmit={handleRotateKeys} className="flex flex-col gap-2 border-t border-border pt-6">
|
|
1164
1226
|
<h2 className="text-sm font-semibold">Rotate keys</h2>
|
|
1165
1227
|
<p className="text-xs text-text-muted mb-1">
|
|
@@ -30,6 +30,7 @@ import Alert from "../../../../lib/components/feedback/Alert.js";
|
|
|
30
30
|
import Button from "../../../../lib/components/buttons/Button.js";
|
|
31
31
|
import Modal from "../../../../lib/components/overlays/Modal.js";
|
|
32
32
|
import { notifyApiError } from "../../../shared/notifications/apiErrors.js";
|
|
33
|
+
import GravatarSetting from "../../../shared/components/settings/GravatarSetting.js";
|
|
33
34
|
|
|
34
35
|
const NON_MAIL_FOLDER_TYPES = new Set(["calendar", "contacts", "tasks", "notes"]);
|
|
35
36
|
|
|
@@ -83,6 +84,8 @@ function PrivacyContent({ userUid }: { userUid?: string }) {
|
|
|
83
84
|
<p className="text-sm text-text-muted">Export or manage the data associated with this mailbox.</p>
|
|
84
85
|
</div>
|
|
85
86
|
|
|
87
|
+
<GravatarSetting />
|
|
88
|
+
|
|
86
89
|
{selectedIsOwn ? (
|
|
87
90
|
<>
|
|
88
91
|
<ExportSection mailboxUid={mailboxUid} mailboxLabel={`${selected.displayName} (${selected.primarySmtpAddress})`} />
|
|
@@ -288,7 +291,7 @@ function ImportSection({ mailboxUid }: { mailboxUid?: string }) {
|
|
|
288
291
|
return;
|
|
289
292
|
}
|
|
290
293
|
setUploading(true);
|
|
291
|
-
uploadMailboxImport(file, { format: importFormatFromFilename(file.name), targetFolderUid })
|
|
294
|
+
uploadMailboxImport(file, { format: importFormatFromFilename(file.name), targetFolderUid, mailboxUid: mailboxUid! }, client)
|
|
292
295
|
.then(() => loadRequests())
|
|
293
296
|
.catch((err) => notifyApiError(err, "Couldn't upload the file"))
|
|
294
297
|
.finally(() => setUploading(false));
|
|
@@ -197,7 +197,18 @@ function ProfileContent() {
|
|
|
197
197
|
</div>
|
|
198
198
|
{zoneWasPreselected && (
|
|
199
199
|
<p className="mt-1 text-xs text-text-muted">
|
|
200
|
-
Your mailbox has no time zone chosen yet; this device’s is preselected
|
|
200
|
+
Your mailbox has no time zone chosen yet (it uses UTC); this device’s is preselected, and Save sets it - whatever else you change.{" "}
|
|
201
|
+
<Button
|
|
202
|
+
type="button"
|
|
203
|
+
variant="text"
|
|
204
|
+
className="!w-auto !inline !p-0 !text-xs"
|
|
205
|
+
onClick={() => {
|
|
206
|
+
setTimezone(DEFAULT_TIME_ZONE);
|
|
207
|
+
setSaved(false);
|
|
208
|
+
}}
|
|
209
|
+
>
|
|
210
|
+
Keep UTC
|
|
211
|
+
</Button>
|
|
201
212
|
</p>
|
|
202
213
|
)}
|
|
203
214
|
<p id="profile-time-zone-help" className="mt-1 text-xs text-text-muted">
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// SPDX-License-Identifier: MPL-2.0
|
|
4
4
|
///////////////////////////////////////////////////////////////////////////////
|
|
5
5
|
import { pageTitle } from "../../../shared/navigation/pageTitle.js";
|
|
6
|
-
import React, { useEffect, useState } from "react";
|
|
6
|
+
import React, { useEffect, useRef, useState } from "react";
|
|
7
7
|
import { ApiRequestError } from "../../../../lib/util/api.js";
|
|
8
8
|
import {
|
|
9
9
|
MailboxAccessMember,
|
|
@@ -60,15 +60,25 @@ function SharingContent() {
|
|
|
60
60
|
// `SettingsShell` only ever renders its children once `mailboxUid` has resolved (a mailbox-less caller
|
|
61
61
|
// gets `<MailboxProvisioning />` instead) - same established precedent as the Labels/Focused Inbox
|
|
62
62
|
// settings pages, so none of the handlers below need their own `!mailboxUid` guard.
|
|
63
|
+
// Only the latest request's answer is shown (a reload after a change can overtake a slower earlier one).
|
|
64
|
+
const latestLoad = useRef(0);
|
|
65
|
+
|
|
63
66
|
function reload() {
|
|
67
|
+
const load = ++latestLoad.current;
|
|
64
68
|
setStatus("loading");
|
|
65
69
|
setError(null);
|
|
66
70
|
listMailboxAccess(mailboxUid!)
|
|
67
71
|
.then((result) => {
|
|
72
|
+
if (load !== latestLoad.current) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
68
75
|
setMembers(result);
|
|
69
76
|
setStatus("ready");
|
|
70
77
|
})
|
|
71
78
|
.catch((err) => {
|
|
79
|
+
if (load !== latestLoad.current) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
72
82
|
// A 403 here is the live, server-verified "can this caller manage this mailbox's sharing"
|
|
73
83
|
// signal - the same permission threshold (ACLAction.UPDATE) this route itself requires, so
|
|
74
84
|
// no separate probe call is needed to decide which state to render.
|
|
@@ -77,10 +77,7 @@ function SignatureDetailContent({ uid }: { uid: string }) {
|
|
|
77
77
|
setSaving(true);
|
|
78
78
|
setSaved(false);
|
|
79
79
|
try {
|
|
80
|
-
|
|
81
|
-
const existing = await listMailSignatures(mailboxUid!, { limit: 100 });
|
|
82
|
-
await clearPreviousDefaults(existing, original!.uid, isDefaultForNewMessages, isDefaultForReplyForward);
|
|
83
|
-
}
|
|
80
|
+
// This signature is saved first: were it refused (a 409, a 400) after the others had been cleared, the mailbox would be left with no default at all.
|
|
84
81
|
const updated = await updateMailSignature({
|
|
85
82
|
uid: original!.uid,
|
|
86
83
|
version: original!.version,
|
|
@@ -90,6 +87,11 @@ function SignatureDetailContent({ uid }: { uid: string }) {
|
|
|
90
87
|
isDefaultForReplyForward,
|
|
91
88
|
});
|
|
92
89
|
setOriginal(updated);
|
|
90
|
+
if (isDefaultForNewMessages || isDefaultForReplyForward) {
|
|
91
|
+
// The signature's own mailbox, not the one the page happens to have selected.
|
|
92
|
+
const existing = await listMailSignatures(original!.mailboxUid, { limit: 100 });
|
|
93
|
+
await clearPreviousDefaults(existing, original!.uid, isDefaultForNewMessages, isDefaultForReplyForward);
|
|
94
|
+
}
|
|
93
95
|
setSaved(true);
|
|
94
96
|
} catch (err) {
|
|
95
97
|
// A pop-up: `error` is only this form's own validation (and the load failure above).
|
package/apps/www/tasks/index.tsx
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
///////////////////////////////////////////////////////////////////////////////
|
|
5
5
|
import { pageTitle } from "../../shared/navigation/pageTitle.js";
|
|
6
6
|
import React, { FormEvent, useEffect, useMemo, useRef, useState } from "react";
|
|
7
|
-
import { endOfWeek, isAfter, isBefore, isToday,
|
|
7
|
+
import { endOfWeek, isAfter, isBefore, isToday, startOfDay } from "date-fns";
|
|
8
8
|
import { ApiRequestError } from "../../../lib/util/api.js";
|
|
9
9
|
import {
|
|
10
10
|
Task,
|
|
@@ -19,6 +19,8 @@ import {
|
|
|
19
19
|
import { listFlaggedMessages } from "../../../lib/mail/flaggedMessages.js";
|
|
20
20
|
import { Message } from "../../../lib/mail/mailApi.js";
|
|
21
21
|
import { useApiClient } from "../../../lib/util/apiClientContext.js";
|
|
22
|
+
import { useDayKey } from "../../../lib/calendar/useDayKey.js";
|
|
23
|
+
import { dueDateInstant, parseDueDate } from "../../shared/components/tasks/dueDate.js";
|
|
22
24
|
import TasksShell, { TasksShellProps, useTasksShell } from "../../shared/components/tasks/layout/TasksShell.js";
|
|
23
25
|
import TasksSidebar, { TasksView } from "../../shared/components/tasks/TasksSidebar.js";
|
|
24
26
|
import TasksToolbar, { TasksViewMode } from "../../shared/components/tasks/TasksToolbar.js";
|
|
@@ -44,15 +46,6 @@ function TasksPage(props: TasksShellProps) {
|
|
|
44
46
|
);
|
|
45
47
|
}
|
|
46
48
|
|
|
47
|
-
/**
|
|
48
|
-
* Parses a task's `dueDate` - `parseISO()` rather than `new Date()`, since `new Date("2026-06-16")` reads a
|
|
49
|
-
* date-only value as UTC midnight, which is the *previous* local day anywhere west of UTC. A full
|
|
50
|
-
* timestamp parses identically either way.
|
|
51
|
-
*/
|
|
52
|
-
function parseDueDate(value: string): Date {
|
|
53
|
-
return parseISO(value);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
49
|
type Bucket = "Overdue" | "Today" | "This Week" | "Later" | "No due date";
|
|
57
50
|
const BUCKET_ORDER: Bucket[] = ["Overdue", "Today", "This Week", "Later", "No due date"];
|
|
58
51
|
|
|
@@ -118,7 +111,10 @@ function TasksContent() {
|
|
|
118
111
|
useShortcut(SHORTCUTS.tasks.create, () => addTaskRef.current!.focus(), { enabled: canAddTask });
|
|
119
112
|
const addTaskHint = useShortcutProps("Add a task", SHORTCUTS.tasks.create, canAddTask);
|
|
120
113
|
|
|
114
|
+
// Which load is the latest: a slower, older one (another folder's, from before the mailbox changed) must not replace the list a newer one set.
|
|
115
|
+
const loadRun = useRef(0);
|
|
121
116
|
function reload(): Promise<void> {
|
|
117
|
+
const run = ++loadRun.current;
|
|
122
118
|
if (!folderUid) {
|
|
123
119
|
setTasks([]);
|
|
124
120
|
setTruncated(false);
|
|
@@ -129,11 +125,21 @@ function TasksContent() {
|
|
|
129
125
|
setError(null);
|
|
130
126
|
return listAllPages((page) => listTasks(folderUid, { limit: LIST_PAGE_SIZE, page }, client))
|
|
131
127
|
.then((result) => {
|
|
132
|
-
|
|
133
|
-
|
|
128
|
+
if (run === loadRun.current) {
|
|
129
|
+
setTasks(result.items);
|
|
130
|
+
setTruncated(result.truncated);
|
|
131
|
+
}
|
|
134
132
|
})
|
|
135
|
-
.catch((err) =>
|
|
136
|
-
|
|
133
|
+
.catch((err) => {
|
|
134
|
+
if (run === loadRun.current) {
|
|
135
|
+
setError(err instanceof ApiRequestError ? err.message : "Could not load tasks.");
|
|
136
|
+
}
|
|
137
|
+
})
|
|
138
|
+
.finally(() => {
|
|
139
|
+
if (run === loadRun.current) {
|
|
140
|
+
setLoading(false);
|
|
141
|
+
}
|
|
142
|
+
});
|
|
137
143
|
}
|
|
138
144
|
|
|
139
145
|
useEffect(() => {
|
|
@@ -149,12 +155,29 @@ function TasksContent() {
|
|
|
149
155
|
if (view.type !== "flagged" || !mailboxUid) {
|
|
150
156
|
return;
|
|
151
157
|
}
|
|
158
|
+
// Set once the view or mailbox this load was for is no longer the one shown, so its answer is dropped.
|
|
159
|
+
let stale = false;
|
|
152
160
|
setFlaggedLoading(true);
|
|
153
161
|
setFlaggedError(null);
|
|
154
162
|
listFlaggedMessages(mailboxUid, client)
|
|
155
|
-
.then(
|
|
156
|
-
|
|
157
|
-
|
|
163
|
+
.then((messages) => {
|
|
164
|
+
if (!stale) {
|
|
165
|
+
setFlaggedMessages(messages);
|
|
166
|
+
}
|
|
167
|
+
})
|
|
168
|
+
.catch((err) => {
|
|
169
|
+
if (!stale) {
|
|
170
|
+
setFlaggedError(err instanceof ApiRequestError ? err.message : "Could not load flagged email.");
|
|
171
|
+
}
|
|
172
|
+
})
|
|
173
|
+
.finally(() => {
|
|
174
|
+
if (!stale) {
|
|
175
|
+
setFlaggedLoading(false);
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
return () => {
|
|
179
|
+
stale = true;
|
|
180
|
+
};
|
|
158
181
|
}, [view, mailboxUid]);
|
|
159
182
|
|
|
160
183
|
async function handleCreate(e: FormEvent) {
|
|
@@ -180,8 +203,8 @@ function TasksContent() {
|
|
|
180
203
|
mailboxUid: target,
|
|
181
204
|
folderUid: targetFolderUid,
|
|
182
205
|
title: title.trim(),
|
|
183
|
-
// The date input's "yyyy-MM-dd" is
|
|
184
|
-
dueDate: dueDate ?
|
|
206
|
+
// The date input's "yyyy-MM-dd" is stored as that day's UTC midnight - see `dueDateInstant()`.
|
|
207
|
+
dueDate: dueDate ? dueDateInstant(dueDate) : undefined,
|
|
185
208
|
priority,
|
|
186
209
|
},
|
|
187
210
|
client,
|
|
@@ -203,7 +226,24 @@ function TasksContent() {
|
|
|
203
226
|
}
|
|
204
227
|
}
|
|
205
228
|
|
|
229
|
+
// The tasks whose completion is being saved. The control is disabled meanwhile, and the ref (current at once, unlike state) turns away a second click that
|
|
230
|
+
// lands before the page has re-rendered: it would send the same stale `version` and be refused, rolling the task back to a state the server has left.
|
|
231
|
+
const togglingRef = useRef(new Set<string>());
|
|
232
|
+
const [toggling, setToggling] = useState<Set<string>>(new Set());
|
|
233
|
+
function setTogglePending(uid: string, pending: boolean) {
|
|
234
|
+
if (pending) {
|
|
235
|
+
togglingRef.current.add(uid);
|
|
236
|
+
} else {
|
|
237
|
+
togglingRef.current.delete(uid);
|
|
238
|
+
}
|
|
239
|
+
setToggling(new Set(togglingRef.current));
|
|
240
|
+
}
|
|
241
|
+
|
|
206
242
|
async function handleToggle(task: Task) {
|
|
243
|
+
if (togglingRef.current.has(task.uid)) {
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
setTogglePending(task.uid, true);
|
|
207
247
|
const nextCompleted = !task.completed;
|
|
208
248
|
setTasks((prev) => prev.map((t) => (t.uid === task.uid ? { ...t, completed: nextCompleted } : t)));
|
|
209
249
|
try {
|
|
@@ -212,6 +252,8 @@ function TasksContent() {
|
|
|
212
252
|
} catch (err) {
|
|
213
253
|
setTasks((prev) => prev.map((t) => (t.uid === task.uid ? task : t)));
|
|
214
254
|
notifyApiError(err, "Couldn't update the task");
|
|
255
|
+
} finally {
|
|
256
|
+
setTogglePending(task.uid, false);
|
|
215
257
|
}
|
|
216
258
|
}
|
|
217
259
|
|
|
@@ -262,8 +304,24 @@ function TasksContent() {
|
|
|
262
304
|
|
|
263
305
|
const checkedTasks = viewFiltered.filter((t) => checkedUids.has(t.uid));
|
|
264
306
|
|
|
307
|
+
// A bulk action sends every checked row's own version: pressed again before it finishes, it would send stale ones and raise a stream of 409s.
|
|
308
|
+
const bulkRunning = useRef(false);
|
|
309
|
+
function singleFlight(run: () => Promise<void>): () => Promise<void> {
|
|
310
|
+
return async () => {
|
|
311
|
+
if (bulkRunning.current) {
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
bulkRunning.current = true;
|
|
315
|
+
try {
|
|
316
|
+
await run();
|
|
317
|
+
} finally {
|
|
318
|
+
bulkRunning.current = false;
|
|
319
|
+
}
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
|
|
265
323
|
// The bulk handlers try every task and report each failure: the same failure repeated is one pop-up with a count.
|
|
266
|
-
|
|
324
|
+
const handleBulkComplete = singleFlight(async () => {
|
|
267
325
|
for (const task of checkedTasks) {
|
|
268
326
|
try {
|
|
269
327
|
await setTaskCompleted(task, true, client);
|
|
@@ -273,9 +331,9 @@ function TasksContent() {
|
|
|
273
331
|
}
|
|
274
332
|
setCheckedUids(new Set());
|
|
275
333
|
await reload();
|
|
276
|
-
}
|
|
334
|
+
});
|
|
277
335
|
|
|
278
|
-
|
|
336
|
+
const handleBulkAddToMyDay = singleFlight(async () => {
|
|
279
337
|
for (const task of checkedTasks) {
|
|
280
338
|
try {
|
|
281
339
|
await setTaskMyDay(task, true, client);
|
|
@@ -285,7 +343,7 @@ function TasksContent() {
|
|
|
285
343
|
}
|
|
286
344
|
setCheckedUids(new Set());
|
|
287
345
|
await reload();
|
|
288
|
-
}
|
|
346
|
+
});
|
|
289
347
|
|
|
290
348
|
async function handleBulkDelete() {
|
|
291
349
|
for (const task of checkedTasks) {
|
|
@@ -313,6 +371,8 @@ function TasksContent() {
|
|
|
313
371
|
}
|
|
314
372
|
}
|
|
315
373
|
|
|
374
|
+
// Re-worked when the day changes, so a page left open past midnight moves Today's tasks to Overdue.
|
|
375
|
+
const dayKey = useDayKey();
|
|
316
376
|
const grouped = useMemo(() => {
|
|
317
377
|
const now = new Date();
|
|
318
378
|
const map = new Map<Bucket, Task[]>(BUCKET_ORDER.map((b) => [b, []]));
|
|
@@ -323,7 +383,7 @@ function TasksContent() {
|
|
|
323
383
|
map.get(bucketFor(task, now))!.push(task);
|
|
324
384
|
}
|
|
325
385
|
return map;
|
|
326
|
-
}, [viewFiltered]);
|
|
386
|
+
}, [viewFiltered, dayKey]);
|
|
327
387
|
|
|
328
388
|
const completedTasks = viewFiltered.filter((t) => t.completed);
|
|
329
389
|
|
|
@@ -428,6 +488,7 @@ function TasksContent() {
|
|
|
428
488
|
checkedUids={checkedUids}
|
|
429
489
|
onToggleChecked={toggleChecked}
|
|
430
490
|
onToggle={handleToggle}
|
|
491
|
+
toggling={toggling}
|
|
431
492
|
onDelete={requestDelete}
|
|
432
493
|
/>
|
|
433
494
|
) : (
|
|
@@ -445,6 +506,7 @@ function TasksContent() {
|
|
|
445
506
|
checkedUids={checkedUids}
|
|
446
507
|
onToggleChecked={toggleChecked}
|
|
447
508
|
onToggle={handleToggle}
|
|
509
|
+
toggling={toggling}
|
|
448
510
|
onDelete={requestDelete}
|
|
449
511
|
/>
|
|
450
512
|
);
|
|
@@ -456,6 +518,7 @@ function TasksContent() {
|
|
|
456
518
|
checkedUids={checkedUids}
|
|
457
519
|
onToggleChecked={toggleChecked}
|
|
458
520
|
onToggle={handleToggle}
|
|
521
|
+
toggling={toggling}
|
|
459
522
|
onDelete={requestDelete}
|
|
460
523
|
/>
|
|
461
524
|
)}
|
|
@@ -509,12 +572,13 @@ function FlaggedEmailList({ loading, error, messages }: { loading: boolean; erro
|
|
|
509
572
|
|
|
510
573
|
/** A circular completion-toggle control matching Outlook's visual language (not a native checkbox), while
|
|
511
574
|
* staying a real accessible toggle. */
|
|
512
|
-
function CompletionToggle({ task, onToggle }: { task: Task; onToggle: (task: Task) => void }) {
|
|
575
|
+
function CompletionToggle({ task, onToggle, pending }: { task: Task; onToggle: (task: Task) => void; pending: boolean }) {
|
|
513
576
|
return (
|
|
514
577
|
<button
|
|
515
578
|
type="button"
|
|
516
579
|
role="checkbox"
|
|
517
580
|
aria-checked={task.completed}
|
|
581
|
+
disabled={pending}
|
|
518
582
|
aria-label={`Mark "${task.title}" as ${task.completed ? "not complete" : "complete"}`}
|
|
519
583
|
onClick={() => onToggle(task)}
|
|
520
584
|
className={[
|
|
@@ -533,10 +597,12 @@ interface TaskGroupProps {
|
|
|
533
597
|
checkedUids: Set<string>;
|
|
534
598
|
onToggleChecked: (uid: string) => void;
|
|
535
599
|
onToggle: (task: Task) => void;
|
|
600
|
+
/** The uids of the tasks whose completion is being saved. */
|
|
601
|
+
toggling: Set<string>;
|
|
536
602
|
onDelete: (task: Task) => void;
|
|
537
603
|
}
|
|
538
604
|
|
|
539
|
-
function TaskGroup({ label, tasks, checkedUids, onToggleChecked, onToggle, onDelete }: TaskGroupProps) {
|
|
605
|
+
function TaskGroup({ label, tasks, checkedUids, onToggleChecked, onToggle, toggling, onDelete }: TaskGroupProps) {
|
|
540
606
|
return (
|
|
541
607
|
<div>
|
|
542
608
|
<h2 className="text-xs font-bold uppercase tracking-wide text-text-muted mb-2">{label}</h2>
|
|
@@ -549,7 +615,7 @@ function TaskGroup({ label, tasks, checkedUids, onToggleChecked, onToggle, onDel
|
|
|
549
615
|
checked={checkedUids.has(task.uid)}
|
|
550
616
|
onChange={() => onToggleChecked(task.uid)}
|
|
551
617
|
/>
|
|
552
|
-
<CompletionToggle task={task} onToggle={onToggle} />
|
|
618
|
+
<CompletionToggle task={task} onToggle={onToggle} pending={toggling.has(task.uid)} />
|
|
553
619
|
<span className={["flex-1 text-sm", task.completed ? "line-through text-text-muted" : ""].join(" ")}>
|
|
554
620
|
{task.title}
|
|
555
621
|
</span>
|
|
@@ -581,10 +647,12 @@ interface TaskTableProps {
|
|
|
581
647
|
checkedUids: Set<string>;
|
|
582
648
|
onToggleChecked: (uid: string) => void;
|
|
583
649
|
onToggle: (task: Task) => void;
|
|
650
|
+
/** The uids of the tasks whose completion is being saved. */
|
|
651
|
+
toggling: Set<string>;
|
|
584
652
|
onDelete: (task: Task) => void;
|
|
585
653
|
}
|
|
586
654
|
|
|
587
|
-
function TaskTable({ tasks, checkedUids, onToggleChecked, onToggle, onDelete }: TaskTableProps) {
|
|
655
|
+
function TaskTable({ tasks, checkedUids, onToggleChecked, onToggle, toggling, onDelete }: TaskTableProps) {
|
|
588
656
|
const allChecked = tasks.length > 0 && tasks.every((t) => checkedUids.has(t.uid));
|
|
589
657
|
|
|
590
658
|
function toggleAll() {
|
|
@@ -630,7 +698,7 @@ function TaskTable({ tasks, checkedUids, onToggleChecked, onToggle, onDelete }:
|
|
|
630
698
|
/>
|
|
631
699
|
</td>
|
|
632
700
|
<td className="px-3 py-2">
|
|
633
|
-
<CompletionToggle task={task} onToggle={onToggle} />
|
|
701
|
+
<CompletionToggle task={task} onToggle={onToggle} pending={toggling.has(task.uid)} />
|
|
634
702
|
</td>
|
|
635
703
|
<td className={["px-3 py-2", task.completed ? "line-through text-text-muted" : ""].join(" ")}>{task.title}</td>
|
|
636
704
|
<td className="px-3 py-2 text-text-muted">{task.dueDate ? parseDueDate(task.dueDate).toLocaleDateString() : ""}</td>
|
|
@@ -4,11 +4,11 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
4
4
|
// SPDX-License-Identifier: MPL-2.0
|
|
5
5
|
///////////////////////////////////////////////////////////////////////////////
|
|
6
6
|
import { useEffect, useRef, useState } from "react";
|
|
7
|
-
import { ApiRequestError } from "../../../lib/util/api.js";
|
|
8
7
|
import { listAuditLog } from "../../../lib/admin/auditLogApi.js";
|
|
9
8
|
import AdminShell from "../../shared/components/admin/layout/AdminShell.js";
|
|
10
|
-
import Alert from "
|
|
9
|
+
import Alert from "../../shared/components/admin/ActionAlert.js";
|
|
11
10
|
import Button from "../../../lib/components/buttons/Button.js";
|
|
11
|
+
import { actionErrorMessage } from "../../shared/components/admin/elevation.js";
|
|
12
12
|
const PAGE_SIZE = 25;
|
|
13
13
|
/** How long typing in a filter box has to pause before the log is re-fetched. */
|
|
14
14
|
export const FILTER_DEBOUNCE_MS = 300;
|
|
@@ -64,7 +64,7 @@ function AuditLogContent() {
|
|
|
64
64
|
})
|
|
65
65
|
.catch((err) => {
|
|
66
66
|
if (!cancelled)
|
|
67
|
-
setError(err
|
|
67
|
+
setError(actionErrorMessage(err, "Could not load the audit log."));
|
|
68
68
|
})
|
|
69
69
|
.finally(() => {
|
|
70
70
|
if (!cancelled)
|
|
@@ -4,11 +4,11 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
4
4
|
// SPDX-License-Identifier: MPL-2.0
|
|
5
5
|
///////////////////////////////////////////////////////////////////////////////
|
|
6
6
|
import { useEffect, useState } from "react";
|
|
7
|
-
import { ApiRequestError } from "../../../lib/util/api.js";
|
|
8
7
|
import { getBranding } from "../../../lib/branding/brandingApi.js";
|
|
9
8
|
import AdminShell from "../../shared/components/admin/layout/AdminShell.js";
|
|
10
9
|
import BrandingForm from "../../shared/components/admin/settings/BrandingForm.js";
|
|
11
|
-
import Alert from "
|
|
10
|
+
import Alert from "../../shared/components/admin/ActionAlert.js";
|
|
11
|
+
import { actionErrorMessage } from "../../shared/components/admin/elevation.js";
|
|
12
12
|
export default function BrandingPage(props) {
|
|
13
13
|
return (_jsx(AdminShell, { ...props, active: "branding", children: _jsx(BrandingContent, {}) }));
|
|
14
14
|
}
|
|
@@ -19,7 +19,7 @@ function BrandingContent() {
|
|
|
19
19
|
useEffect(() => {
|
|
20
20
|
getBranding()
|
|
21
21
|
.then(setBranding)
|
|
22
|
-
.catch((err) => setLoadError(err
|
|
22
|
+
.catch((err) => setLoadError(actionErrorMessage(err, "Could not load branding.")))
|
|
23
23
|
.finally(() => setLoading(false));
|
|
24
24
|
}, []);
|
|
25
25
|
if (loading) {
|