@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
|
@@ -5,11 +5,13 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
5
5
|
///////////////////////////////////////////////////////////////////////////////
|
|
6
6
|
import { pageTitle } from "../../shared/navigation/pageTitle.js";
|
|
7
7
|
import { useEffect, useMemo, useRef, useState } from "react";
|
|
8
|
-
import { endOfWeek, isAfter, isBefore, isToday,
|
|
8
|
+
import { endOfWeek, isAfter, isBefore, isToday, startOfDay } from "date-fns";
|
|
9
9
|
import { ApiRequestError } from "../../../lib/util/api.js";
|
|
10
10
|
import { createTask, deleteTask, listTasks, setTaskCompleted, setTaskMyDay, } from "../../../lib/tasks/tasksApi.js";
|
|
11
11
|
import { listFlaggedMessages } from "../../../lib/mail/flaggedMessages.js";
|
|
12
12
|
import { useApiClient } from "../../../lib/util/apiClientContext.js";
|
|
13
|
+
import { useDayKey } from "../../../lib/calendar/useDayKey.js";
|
|
14
|
+
import { dueDateInstant, parseDueDate } from "../../shared/components/tasks/dueDate.js";
|
|
13
15
|
import TasksShell, { useTasksShell } from "../../shared/components/tasks/layout/TasksShell.js";
|
|
14
16
|
import TasksSidebar from "../../shared/components/tasks/TasksSidebar.js";
|
|
15
17
|
import TasksToolbar from "../../shared/components/tasks/TasksToolbar.js";
|
|
@@ -27,14 +29,6 @@ const INPUT_CLASS = "text-sm py-1.5 px-2 border border-border rounded-sm bg-surf
|
|
|
27
29
|
function TasksPage(props) {
|
|
28
30
|
return (_jsx(TasksShell, { ...props, children: _jsx(TasksContent, {}) }));
|
|
29
31
|
}
|
|
30
|
-
/**
|
|
31
|
-
* Parses a task's `dueDate` - `parseISO()` rather than `new Date()`, since `new Date("2026-06-16")` reads a
|
|
32
|
-
* date-only value as UTC midnight, which is the *previous* local day anywhere west of UTC. A full
|
|
33
|
-
* timestamp parses identically either way.
|
|
34
|
-
*/
|
|
35
|
-
function parseDueDate(value) {
|
|
36
|
-
return parseISO(value);
|
|
37
|
-
}
|
|
38
32
|
const BUCKET_ORDER = ["Overdue", "Today", "This Week", "Later", "No due date"];
|
|
39
33
|
/** Buckets a task by its due date relative to `now`, matching the Outlook/To-Do convention this list mirrors. */
|
|
40
34
|
function bucketFor(task, now) {
|
|
@@ -94,7 +88,10 @@ function TasksContent() {
|
|
|
94
88
|
// Registered only while the form is rendered (`canAddTask`), so its field is there.
|
|
95
89
|
useShortcut(SHORTCUTS.tasks.create, () => addTaskRef.current.focus(), { enabled: canAddTask });
|
|
96
90
|
const addTaskHint = useShortcutProps("Add a task", SHORTCUTS.tasks.create, canAddTask);
|
|
91
|
+
// 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.
|
|
92
|
+
const loadRun = useRef(0);
|
|
97
93
|
function reload() {
|
|
94
|
+
const run = ++loadRun.current;
|
|
98
95
|
if (!folderUid) {
|
|
99
96
|
setTasks([]);
|
|
100
97
|
setTruncated(false);
|
|
@@ -105,11 +102,21 @@ function TasksContent() {
|
|
|
105
102
|
setError(null);
|
|
106
103
|
return listAllPages((page) => listTasks(folderUid, { limit: LIST_PAGE_SIZE, page }, client))
|
|
107
104
|
.then((result) => {
|
|
108
|
-
|
|
109
|
-
|
|
105
|
+
if (run === loadRun.current) {
|
|
106
|
+
setTasks(result.items);
|
|
107
|
+
setTruncated(result.truncated);
|
|
108
|
+
}
|
|
109
|
+
})
|
|
110
|
+
.catch((err) => {
|
|
111
|
+
if (run === loadRun.current) {
|
|
112
|
+
setError(err instanceof ApiRequestError ? err.message : "Could not load tasks.");
|
|
113
|
+
}
|
|
110
114
|
})
|
|
111
|
-
.
|
|
112
|
-
|
|
115
|
+
.finally(() => {
|
|
116
|
+
if (run === loadRun.current) {
|
|
117
|
+
setLoading(false);
|
|
118
|
+
}
|
|
119
|
+
});
|
|
113
120
|
}
|
|
114
121
|
useEffect(() => {
|
|
115
122
|
void reload();
|
|
@@ -122,12 +129,29 @@ function TasksContent() {
|
|
|
122
129
|
if (view.type !== "flagged" || !mailboxUid) {
|
|
123
130
|
return;
|
|
124
131
|
}
|
|
132
|
+
// Set once the view or mailbox this load was for is no longer the one shown, so its answer is dropped.
|
|
133
|
+
let stale = false;
|
|
125
134
|
setFlaggedLoading(true);
|
|
126
135
|
setFlaggedError(null);
|
|
127
136
|
listFlaggedMessages(mailboxUid, client)
|
|
128
|
-
.then(
|
|
129
|
-
|
|
130
|
-
|
|
137
|
+
.then((messages) => {
|
|
138
|
+
if (!stale) {
|
|
139
|
+
setFlaggedMessages(messages);
|
|
140
|
+
}
|
|
141
|
+
})
|
|
142
|
+
.catch((err) => {
|
|
143
|
+
if (!stale) {
|
|
144
|
+
setFlaggedError(err instanceof ApiRequestError ? err.message : "Could not load flagged email.");
|
|
145
|
+
}
|
|
146
|
+
})
|
|
147
|
+
.finally(() => {
|
|
148
|
+
if (!stale) {
|
|
149
|
+
setFlaggedLoading(false);
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
return () => {
|
|
153
|
+
stale = true;
|
|
154
|
+
};
|
|
131
155
|
}, [view, mailboxUid]);
|
|
132
156
|
async function handleCreate(e) {
|
|
133
157
|
e.preventDefault();
|
|
@@ -151,8 +175,8 @@ function TasksContent() {
|
|
|
151
175
|
mailboxUid: target,
|
|
152
176
|
folderUid: targetFolderUid,
|
|
153
177
|
title: title.trim(),
|
|
154
|
-
// The date input's "yyyy-MM-dd" is
|
|
155
|
-
dueDate: dueDate ?
|
|
178
|
+
// The date input's "yyyy-MM-dd" is stored as that day's UTC midnight - see `dueDateInstant()`.
|
|
179
|
+
dueDate: dueDate ? dueDateInstant(dueDate) : undefined,
|
|
156
180
|
priority,
|
|
157
181
|
}, client);
|
|
158
182
|
if (target === mailboxUid) {
|
|
@@ -174,7 +198,24 @@ function TasksContent() {
|
|
|
174
198
|
setCreating(false);
|
|
175
199
|
}
|
|
176
200
|
}
|
|
201
|
+
// 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
|
|
202
|
+
// 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.
|
|
203
|
+
const togglingRef = useRef(new Set());
|
|
204
|
+
const [toggling, setToggling] = useState(new Set());
|
|
205
|
+
function setTogglePending(uid, pending) {
|
|
206
|
+
if (pending) {
|
|
207
|
+
togglingRef.current.add(uid);
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
togglingRef.current.delete(uid);
|
|
211
|
+
}
|
|
212
|
+
setToggling(new Set(togglingRef.current));
|
|
213
|
+
}
|
|
177
214
|
async function handleToggle(task) {
|
|
215
|
+
if (togglingRef.current.has(task.uid)) {
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
setTogglePending(task.uid, true);
|
|
178
219
|
const nextCompleted = !task.completed;
|
|
179
220
|
setTasks((prev) => prev.map((t) => (t.uid === task.uid ? { ...t, completed: nextCompleted } : t)));
|
|
180
221
|
try {
|
|
@@ -185,6 +226,9 @@ function TasksContent() {
|
|
|
185
226
|
setTasks((prev) => prev.map((t) => (t.uid === task.uid ? task : t)));
|
|
186
227
|
notifyApiError(err, "Couldn't update the task");
|
|
187
228
|
}
|
|
229
|
+
finally {
|
|
230
|
+
setTogglePending(task.uid, false);
|
|
231
|
+
}
|
|
188
232
|
}
|
|
189
233
|
async function handleDelete(task) {
|
|
190
234
|
try {
|
|
@@ -230,8 +274,24 @@ function TasksContent() {
|
|
|
230
274
|
}
|
|
231
275
|
}, [tasks, view, userUid]);
|
|
232
276
|
const checkedTasks = viewFiltered.filter((t) => checkedUids.has(t.uid));
|
|
277
|
+
// 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.
|
|
278
|
+
const bulkRunning = useRef(false);
|
|
279
|
+
function singleFlight(run) {
|
|
280
|
+
return async () => {
|
|
281
|
+
if (bulkRunning.current) {
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
bulkRunning.current = true;
|
|
285
|
+
try {
|
|
286
|
+
await run();
|
|
287
|
+
}
|
|
288
|
+
finally {
|
|
289
|
+
bulkRunning.current = false;
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
}
|
|
233
293
|
// The bulk handlers try every task and report each failure: the same failure repeated is one pop-up with a count.
|
|
234
|
-
|
|
294
|
+
const handleBulkComplete = singleFlight(async () => {
|
|
235
295
|
for (const task of checkedTasks) {
|
|
236
296
|
try {
|
|
237
297
|
await setTaskCompleted(task, true, client);
|
|
@@ -242,8 +302,8 @@ function TasksContent() {
|
|
|
242
302
|
}
|
|
243
303
|
setCheckedUids(new Set());
|
|
244
304
|
await reload();
|
|
245
|
-
}
|
|
246
|
-
|
|
305
|
+
});
|
|
306
|
+
const handleBulkAddToMyDay = singleFlight(async () => {
|
|
247
307
|
for (const task of checkedTasks) {
|
|
248
308
|
try {
|
|
249
309
|
await setTaskMyDay(task, true, client);
|
|
@@ -254,7 +314,7 @@ function TasksContent() {
|
|
|
254
314
|
}
|
|
255
315
|
setCheckedUids(new Set());
|
|
256
316
|
await reload();
|
|
257
|
-
}
|
|
317
|
+
});
|
|
258
318
|
async function handleBulkDelete() {
|
|
259
319
|
for (const task of checkedTasks) {
|
|
260
320
|
try {
|
|
@@ -280,6 +340,8 @@ function TasksContent() {
|
|
|
280
340
|
void handleBulkDelete();
|
|
281
341
|
}
|
|
282
342
|
}
|
|
343
|
+
// Re-worked when the day changes, so a page left open past midnight moves Today's tasks to Overdue.
|
|
344
|
+
const dayKey = useDayKey();
|
|
283
345
|
const grouped = useMemo(() => {
|
|
284
346
|
const now = new Date();
|
|
285
347
|
const map = new Map(BUCKET_ORDER.map((b) => [b, []]));
|
|
@@ -290,17 +352,17 @@ function TasksContent() {
|
|
|
290
352
|
map.get(bucketFor(task, now)).push(task);
|
|
291
353
|
}
|
|
292
354
|
return map;
|
|
293
|
-
}, [viewFiltered]);
|
|
355
|
+
}, [viewFiltered, dayKey]);
|
|
294
356
|
const completedTasks = viewFiltered.filter((t) => t.completed);
|
|
295
357
|
return (_jsxs("div", { className: "flex-1 min-w-0 min-h-0 flex flex-col lg:flex-row", children: [_jsx(TasksSidebar, { mailboxUid: mailboxUid, tasks: tasks, userUid: userUid, active: view, onSelect: handleSelectView }), _jsxs("div", { className: "flex-1 min-w-0 flex flex-col overflow-y-auto", children: [_jsx(TasksToolbar, { viewMode: viewMode, onViewModeChange: setViewMode, selectedCount: checkedTasks.length, onComplete: handleBulkComplete, onDelete: () => setPendingDelete({ type: "bulk" }), onAddToMyDay: handleBulkAddToMyDay }), _jsxs("div", { role: "region", "aria-label": "Tasks list", className: "max-w-2xl mx-auto w-full flex flex-col gap-6 p-6", children: [_jsx("h1", { className: "text-xl font-bold uppercase tracking-wide", children: "Tasks" }), error && _jsx(Alert, { children: error }), truncated && view.type !== "flagged" && (_jsxs(Alert, { children: ["This folder has more tasks than can be shown at once - only the first ", LIST_PAGE_SIZE * MAX_LIST_PAGES, " are listed."] })), view.type === "flagged" ? (_jsx(FlaggedEmailList, { loading: flaggedLoading, error: flaggedError, messages: flaggedMessages })) : (_jsxs(_Fragment, { children: [_jsxs("form", { onSubmit: handleCreate, className: "flex flex-wrap items-center gap-2 bg-surface border border-border rounded-md p-2", children: [_jsx("input", { ref: addTaskRef, type: "text", "aria-label": "Add a task", ...addTaskHint, className: `${INPUT_CLASS} min-w-40 basis-40 flex-1`, value: title, onChange: (e) => setTitle(e.target.value), placeholder: "Add a task" }), _jsx("input", { type: "date", "aria-label": "Due date", className: INPUT_CLASS, value: dueDate, onChange: (e) => setDueDate(e.target.value) }), _jsxs("select", { "aria-label": "Priority", className: INPUT_CLASS, value: priority, onChange: (e) => setPriority(e.target.value), children: [_jsx("option", { value: "low", children: "Low" }), _jsx("option", { value: "normal", children: "Normal" }), _jsx("option", { value: "high", children: "High" })] }), writableMailboxes.length > 1 && (_jsx("select", { "aria-label": "Task mailbox", className: `${INPUT_CLASS} max-w-40`,
|
|
296
358
|
// `writableMailboxes` only has entries once `mailboxUid` has resolved.
|
|
297
|
-
value: targetMailboxUid ?? mailboxUid, onChange: (e) => setTargetMailboxUid(e.target.value), children: writableMailboxes.map((mb) => (_jsxs("option", { value: mb.uid, children: [mb.displayName, mb.ownerUserUid === userUid ? "" : " (shared)"] }, mb.uid))) })), _jsx(Button, { type: "submit", loading: creating, disabled: creating, className: "!w-auto shrink-0", children: "Add" })] }), createError && _jsx(Alert, { children: createError }), addedElsewhere && (_jsxs("p", { role: "status", className: "text-sm py-2 px-3 rounded-sm bg-surface-alt text-text", children: ["\u201C", addedElsewhere.title, "\u201D was added to", " ", mailboxes.find((mb) => mb.uid === addedElsewhere.mailboxUid)?.displayName ?? "another mailbox", ".", " ", _jsx("a", { href: `/tasks?mailboxUid=${encodeURIComponent(addedElsewhere.mailboxUid)}`, "data-router-shallow": "", "data-router-prefetch": "false", className: "font-medium text-primary-dark hover:underline", children: "View that mailbox\u2019s tasks" })] })), loading ? (_jsx("p", { className: "text-sm text-text-muted", children: "Loading\u2026" })) : viewFiltered.length === 0 ? (_jsx("p", { className: "text-sm text-text-muted", children: "No tasks yet." })) : viewMode === "grid" ? (_jsx(TaskTable, { tasks: viewFiltered, checkedUids: checkedUids, onToggleChecked: toggleChecked, onToggle: handleToggle, onDelete: requestDelete })) : (_jsxs(_Fragment, { children: [BUCKET_ORDER.map((bucket) => {
|
|
359
|
+
value: targetMailboxUid ?? mailboxUid, onChange: (e) => setTargetMailboxUid(e.target.value), children: writableMailboxes.map((mb) => (_jsxs("option", { value: mb.uid, children: [mb.displayName, mb.ownerUserUid === userUid ? "" : " (shared)"] }, mb.uid))) })), _jsx(Button, { type: "submit", loading: creating, disabled: creating, className: "!w-auto shrink-0", children: "Add" })] }), createError && _jsx(Alert, { children: createError }), addedElsewhere && (_jsxs("p", { role: "status", className: "text-sm py-2 px-3 rounded-sm bg-surface-alt text-text", children: ["\u201C", addedElsewhere.title, "\u201D was added to", " ", mailboxes.find((mb) => mb.uid === addedElsewhere.mailboxUid)?.displayName ?? "another mailbox", ".", " ", _jsx("a", { href: `/tasks?mailboxUid=${encodeURIComponent(addedElsewhere.mailboxUid)}`, "data-router-shallow": "", "data-router-prefetch": "false", className: "font-medium text-primary-dark hover:underline", children: "View that mailbox\u2019s tasks" })] })), loading ? (_jsx("p", { className: "text-sm text-text-muted", children: "Loading\u2026" })) : viewFiltered.length === 0 ? (_jsx("p", { className: "text-sm text-text-muted", children: "No tasks yet." })) : viewMode === "grid" ? (_jsx(TaskTable, { tasks: viewFiltered, checkedUids: checkedUids, onToggleChecked: toggleChecked, onToggle: handleToggle, toggling: toggling, onDelete: requestDelete })) : (_jsxs(_Fragment, { children: [BUCKET_ORDER.map((bucket) => {
|
|
298
360
|
const items = grouped.get(bucket);
|
|
299
361
|
if (items.length === 0) {
|
|
300
362
|
return null;
|
|
301
363
|
}
|
|
302
|
-
return (_jsx(TaskGroup, { label: bucket, tasks: items, checkedUids: checkedUids, onToggleChecked: toggleChecked, onToggle: handleToggle, onDelete: requestDelete }, bucket));
|
|
303
|
-
}), completedTasks.length > 0 && (_jsx(TaskGroup, { label: "Completed", tasks: completedTasks, checkedUids: checkedUids, onToggleChecked: toggleChecked, onToggle: handleToggle, onDelete: requestDelete }))] }))] }))] })] }), _jsxs(Modal, { open: pendingDelete !== null, onClose: () => setPendingDelete(null), title: "Delete tasks", children: [_jsx("p", { className: "text-sm mb-5", children: pendingDelete?.type === "single"
|
|
364
|
+
return (_jsx(TaskGroup, { label: bucket, tasks: items, checkedUids: checkedUids, onToggleChecked: toggleChecked, onToggle: handleToggle, toggling: toggling, onDelete: requestDelete }, bucket));
|
|
365
|
+
}), completedTasks.length > 0 && (_jsx(TaskGroup, { label: "Completed", tasks: completedTasks, checkedUids: checkedUids, onToggleChecked: toggleChecked, onToggle: handleToggle, toggling: toggling, onDelete: requestDelete }))] }))] }))] })] }), _jsxs(Modal, { open: pendingDelete !== null, onClose: () => setPendingDelete(null), title: "Delete tasks", children: [_jsx("p", { className: "text-sm mb-5", children: pendingDelete?.type === "single"
|
|
304
366
|
? `Delete "${pendingDelete.task.title}"?`
|
|
305
367
|
: `Delete ${checkedTasks.length} selected ${checkedTasks.length === 1 ? "task" : "tasks"}?` }), _jsxs("div", { className: "flex gap-3 justify-end", children: [_jsx(Button, { type: "button", variant: "secondary", className: "!w-auto", onClick: () => setPendingDelete(null), children: "Cancel" }), _jsx(Button, { type: "button", className: "!w-auto !bg-none !bg-danger !border-danger hover:!bg-danger", onClick: handleConfirmDelete, children: "Delete" })] })] })] }));
|
|
306
368
|
}
|
|
@@ -318,16 +380,16 @@ function FlaggedEmailList({ loading, error, messages }) {
|
|
|
318
380
|
}
|
|
319
381
|
/** A circular completion-toggle control matching Outlook's visual language (not a native checkbox), while
|
|
320
382
|
* staying a real accessible toggle. */
|
|
321
|
-
function CompletionToggle({ task, onToggle }) {
|
|
322
|
-
return (_jsx("button", { type: "button", role: "checkbox", "aria-checked": task.completed, "aria-label": `Mark "${task.title}" as ${task.completed ? "not complete" : "complete"}`, onClick: () => onToggle(task), className: [
|
|
383
|
+
function CompletionToggle({ task, onToggle, pending }) {
|
|
384
|
+
return (_jsx("button", { type: "button", role: "checkbox", "aria-checked": task.completed, disabled: pending, "aria-label": `Mark "${task.title}" as ${task.completed ? "not complete" : "complete"}`, onClick: () => onToggle(task), className: [
|
|
323
385
|
"w-4 h-4 rounded-full border shrink-0 flex items-center justify-center text-[10px]",
|
|
324
386
|
task.completed ? "bg-primary border-primary text-white" : "border-border text-transparent",
|
|
325
387
|
].join(" "), children: "\u2713" }));
|
|
326
388
|
}
|
|
327
|
-
function TaskGroup({ label, tasks, checkedUids, onToggleChecked, onToggle, onDelete }) {
|
|
328
|
-
return (_jsxs("div", { children: [_jsx("h2", { className: "text-xs font-bold uppercase tracking-wide text-text-muted mb-2", children: label }), _jsx("ul", { className: "bg-surface border border-border rounded-md divide-y divide-border", children: tasks.map((task) => (_jsxs("li", { className: "flex items-center gap-3 py-2.5 px-3", children: [_jsx("input", { type: "checkbox", "aria-label": `Select ${task.title}`, checked: checkedUids.has(task.uid), onChange: () => onToggleChecked(task.uid) }), _jsx(CompletionToggle, { task: task, onToggle: onToggle }), _jsx("span", { className: ["flex-1 text-sm", task.completed ? "line-through text-text-muted" : ""].join(" "), children: task.title }), task.priority !== "normal" && (_jsx("span", { className: ["text-xs font-medium", PRIORITY_CLASS[task.priority]].join(" "), children: PRIORITY_LABEL[task.priority] })), task.dueDate && (_jsx("span", { className: "text-xs text-text-muted shrink-0", children: parseDueDate(task.dueDate).toLocaleDateString() })), _jsx("button", { type: "button", onClick: () => onDelete(task), "aria-label": `Delete "${task.title}"`, className: "text-text-muted hover:text-danger text-sm px-1", children: "\u00D7" })] }, task.uid))) })] }));
|
|
389
|
+
function TaskGroup({ label, tasks, checkedUids, onToggleChecked, onToggle, toggling, onDelete }) {
|
|
390
|
+
return (_jsxs("div", { children: [_jsx("h2", { className: "text-xs font-bold uppercase tracking-wide text-text-muted mb-2", children: label }), _jsx("ul", { className: "bg-surface border border-border rounded-md divide-y divide-border", children: tasks.map((task) => (_jsxs("li", { className: "flex items-center gap-3 py-2.5 px-3", children: [_jsx("input", { type: "checkbox", "aria-label": `Select ${task.title}`, checked: checkedUids.has(task.uid), onChange: () => onToggleChecked(task.uid) }), _jsx(CompletionToggle, { task: task, onToggle: onToggle, pending: toggling.has(task.uid) }), _jsx("span", { className: ["flex-1 text-sm", task.completed ? "line-through text-text-muted" : ""].join(" "), children: task.title }), task.priority !== "normal" && (_jsx("span", { className: ["text-xs font-medium", PRIORITY_CLASS[task.priority]].join(" "), children: PRIORITY_LABEL[task.priority] })), task.dueDate && (_jsx("span", { className: "text-xs text-text-muted shrink-0", children: parseDueDate(task.dueDate).toLocaleDateString() })), _jsx("button", { type: "button", onClick: () => onDelete(task), "aria-label": `Delete "${task.title}"`, className: "text-text-muted hover:text-danger text-sm px-1", children: "\u00D7" })] }, task.uid))) })] }));
|
|
329
391
|
}
|
|
330
|
-
function TaskTable({ tasks, checkedUids, onToggleChecked, onToggle, onDelete }) {
|
|
392
|
+
function TaskTable({ tasks, checkedUids, onToggleChecked, onToggle, toggling, onDelete }) {
|
|
331
393
|
const allChecked = tasks.length > 0 && tasks.every((t) => checkedUids.has(t.uid));
|
|
332
394
|
function toggleAll() {
|
|
333
395
|
if (allChecked) {
|
|
@@ -345,7 +407,7 @@ function TaskTable({ tasks, checkedUids, onToggleChecked, onToggle, onDelete })
|
|
|
345
407
|
}
|
|
346
408
|
}
|
|
347
409
|
}
|
|
348
|
-
return (_jsx("div", { className: "overflow-x-auto", children: _jsxs("table", { className: "w-full text-sm bg-surface border border-border rounded-md", children: [_jsx("thead", { children: _jsxs("tr", { className: "border-b border-border text-left", children: [_jsx("th", { className: "w-8 px-3 py-2", children: _jsx("input", { type: "checkbox", "aria-label": "Select all tasks", checked: allChecked, onChange: toggleAll }) }), _jsx("th", { className: "w-8 px-3 py-2" }), _jsx("th", { className: "px-3 py-2 text-xs font-bold uppercase tracking-wide text-text-muted", children: "Title" }), _jsx("th", { className: "px-3 py-2 text-xs font-bold uppercase tracking-wide text-text-muted", children: "Due Date" }), _jsx("th", { className: "px-3 py-2 text-xs font-bold uppercase tracking-wide text-text-muted", children: "Importance" }), _jsx("th", { className: "w-8 px-3 py-2" })] }) }), _jsx("tbody", { children: tasks.map((task) => (_jsxs("tr", { className: "border-b border-border last:border-0", children: [_jsx("td", { className: "px-3 py-2", children: _jsx("input", { type: "checkbox", "aria-label": `Select ${task.title}`, checked: checkedUids.has(task.uid), onChange: () => onToggleChecked(task.uid) }) }), _jsx("td", { className: "px-3 py-2", children: _jsx(CompletionToggle, { task: task, onToggle: onToggle }) }), _jsx("td", { className: ["px-3 py-2", task.completed ? "line-through text-text-muted" : ""].join(" "), children: task.title }), _jsx("td", { className: "px-3 py-2 text-text-muted", children: task.dueDate ? parseDueDate(task.dueDate).toLocaleDateString() : "" }), _jsx("td", { className: ["px-3 py-2", PRIORITY_CLASS[task.priority]].join(" "), children: PRIORITY_LABEL[task.priority] }), _jsx("td", { className: "px-3 py-2", children: _jsx("button", { type: "button", onClick: () => onDelete(task), "aria-label": `Delete "${task.title}"`, className: "text-text-muted hover:text-danger text-sm px-1", children: "\u00D7" }) })] }, task.uid))) })] }) }));
|
|
410
|
+
return (_jsx("div", { className: "overflow-x-auto", children: _jsxs("table", { className: "w-full text-sm bg-surface border border-border rounded-md", children: [_jsx("thead", { children: _jsxs("tr", { className: "border-b border-border text-left", children: [_jsx("th", { className: "w-8 px-3 py-2", children: _jsx("input", { type: "checkbox", "aria-label": "Select all tasks", checked: allChecked, onChange: toggleAll }) }), _jsx("th", { className: "w-8 px-3 py-2" }), _jsx("th", { className: "px-3 py-2 text-xs font-bold uppercase tracking-wide text-text-muted", children: "Title" }), _jsx("th", { className: "px-3 py-2 text-xs font-bold uppercase tracking-wide text-text-muted", children: "Due Date" }), _jsx("th", { className: "px-3 py-2 text-xs font-bold uppercase tracking-wide text-text-muted", children: "Importance" }), _jsx("th", { className: "w-8 px-3 py-2" })] }) }), _jsx("tbody", { children: tasks.map((task) => (_jsxs("tr", { className: "border-b border-border last:border-0", children: [_jsx("td", { className: "px-3 py-2", children: _jsx("input", { type: "checkbox", "aria-label": `Select ${task.title}`, checked: checkedUids.has(task.uid), onChange: () => onToggleChecked(task.uid) }) }), _jsx("td", { className: "px-3 py-2", children: _jsx(CompletionToggle, { task: task, onToggle: onToggle, pending: toggling.has(task.uid) }) }), _jsx("td", { className: ["px-3 py-2", task.completed ? "line-through text-text-muted" : ""].join(" "), children: task.title }), _jsx("td", { className: "px-3 py-2 text-text-muted", children: task.dueDate ? parseDueDate(task.dueDate).toLocaleDateString() : "" }), _jsx("td", { className: ["px-3 py-2", PRIORITY_CLASS[task.priority]].join(" "), children: PRIORITY_LABEL[task.priority] }), _jsx("td", { className: "px-3 py-2", children: _jsx("button", { type: "button", onClick: () => onDelete(task), "aria-label": `Delete "${task.title}"`, className: "text-text-muted hover:text-danger text-sm px-1", children: "\u00D7" }) })] }, task.uid))) })] }) }));
|
|
349
411
|
}
|
|
350
412
|
export default TasksPage;
|
|
351
413
|
/** The tab's title: `Brand: Tasks` (see `pageTitle()`). */
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* own server-internal `recordAuditLog()`.
|
|
10
10
|
*/
|
|
11
11
|
import { withClient } from "../util/api.js";
|
|
12
|
-
import { buildQuery } from "../util/apiQuery.js";
|
|
12
|
+
import { buildQuery, sortQuery } from "../util/apiQuery.js";
|
|
13
13
|
/** `client`, given below, is an explicit `ApiClient` from `createApiClient()` (e.g. one account of a multi-account
|
|
14
14
|
* app) to call instead of the default global `apiFetch()` - see `withClient()`'s own doc comment in `util/api.ts`.
|
|
15
15
|
* Omitted (the default), this behaves exactly as before. */
|
|
@@ -23,5 +23,5 @@ export function listAuditLog(filters = {}, params = {}, client) {
|
|
|
23
23
|
extra.action = filters.action;
|
|
24
24
|
if (filters.targetType)
|
|
25
25
|
extra.targetType = filters.targetType;
|
|
26
|
-
return withClient(client, `/mail/audit-log?${buildQuery(params, extra)}`);
|
|
26
|
+
return withClient(client, `/mail/audit-log?${buildQuery(params, { ...extra, ...sortQuery({ dateCreated: "DESC", uid: "ASC" }) })}`);
|
|
27
27
|
}
|
|
@@ -7,12 +7,12 @@
|
|
|
7
7
|
* trusted-role-only, no self-service creation or per-list delegated ownership (v1).
|
|
8
8
|
*/
|
|
9
9
|
import { withClient } from "../util/api.js";
|
|
10
|
-
import { buildQuery } from "../util/apiQuery.js";
|
|
10
|
+
import { buildQuery, sortQuery } from "../util/apiQuery.js";
|
|
11
11
|
/** `client`, given by every function below, is an explicit `ApiClient` from `createApiClient()` (e.g. one
|
|
12
12
|
* account of a multi-account app) to call instead of the default global `apiFetch()` - see `withClient()`'s
|
|
13
13
|
* own doc comment in `util/api.ts`. Omitted (the default), every function here behaves exactly as before. */
|
|
14
14
|
export function listDistributionLists(params = {}, client) {
|
|
15
|
-
return withClient(client, `/mail/distribution-lists?${buildQuery(params)}`);
|
|
15
|
+
return withClient(client, `/mail/distribution-lists?${buildQuery(params, sortQuery({ uid: "ASC" }))}`);
|
|
16
16
|
}
|
|
17
17
|
export function getDistributionList(uid, client) {
|
|
18
18
|
return withClient(client, `/mail/distribution-lists/${encodeURIComponent(uid)}`);
|
|
@@ -8,12 +8,12 @@
|
|
|
8
8
|
* admin-scoped entities.
|
|
9
9
|
*/
|
|
10
10
|
import { withClient } from "../util/api.js";
|
|
11
|
-
import { buildQuery } from "../util/apiQuery.js";
|
|
11
|
+
import { buildQuery, sortQuery } from "../util/apiQuery.js";
|
|
12
12
|
/** `client`, given by every function below, is an explicit `ApiClient` from `createApiClient()` (e.g. one
|
|
13
13
|
* account of a multi-account app) to call instead of the default global `apiFetch()` - see `withClient()`'s
|
|
14
14
|
* own doc comment in `util/api.ts`. Omitted (the default), every function here behaves exactly as before. */
|
|
15
15
|
export function listDomains(params = {}, client) {
|
|
16
|
-
return withClient(client, `/mail/domains?${buildQuery(params)}`);
|
|
16
|
+
return withClient(client, `/mail/domains?${buildQuery(params, sortQuery({ uid: "ASC" }))}`);
|
|
17
17
|
}
|
|
18
18
|
export function getDomain(uid, client) {
|
|
19
19
|
return withClient(client, `/mail/domains/${encodeURIComponent(uid)}`);
|
|
@@ -12,12 +12,12 @@
|
|
|
12
12
|
* enforces it and a non-trusted caller simply gets a 403 `ApiRequestError` back.
|
|
13
13
|
*/
|
|
14
14
|
import { withClient } from "../util/api.js";
|
|
15
|
-
import { buildQuery } from "../util/apiQuery.js";
|
|
15
|
+
import { buildQuery, sortQuery } from "../util/apiQuery.js";
|
|
16
16
|
/** `client`, given by every function below, is an explicit `ApiClient` from `createApiClient()` (e.g. one
|
|
17
17
|
* account of a multi-account app) to call instead of the default global `apiFetch()` - see `withClient()`'s
|
|
18
18
|
* own doc comment in `util/api.ts`. Omitted (the default), every function here behaves exactly as before. */
|
|
19
19
|
export function listAuditLogEntries(params = {}, client) {
|
|
20
|
-
return withClient(client, `/escrow/audit-log?${buildQuery(params)}`);
|
|
20
|
+
return withClient(client, `/escrow/audit-log?${buildQuery(params, sortQuery({ sequence: "DESC" }))}`);
|
|
21
21
|
}
|
|
22
22
|
export function getAuditLogEntry(uid, client) {
|
|
23
23
|
return withClient(client, `/escrow/audit-log/${encodeURIComponent(uid)}`);
|
|
@@ -11,12 +11,12 @@
|
|
|
11
11
|
* `apps/admin/escrow-scopes/*`, never by the holder-facing `apps/escrow` area.
|
|
12
12
|
*/
|
|
13
13
|
import { withClient } from "../util/api.js";
|
|
14
|
-
import { buildQuery } from "../util/apiQuery.js";
|
|
14
|
+
import { buildQuery, sortQuery } from "../util/apiQuery.js";
|
|
15
15
|
/** `client`, given by every function below, is an explicit `ApiClient` from `createApiClient()` (e.g. one
|
|
16
16
|
* account of a multi-account app) to call instead of the default global `apiFetch()` - see `withClient()`'s
|
|
17
17
|
* own doc comment in `util/api.ts`. Omitted (the default), every function here behaves exactly as before. */
|
|
18
18
|
export function listEscrowScopes(params = {}, client) {
|
|
19
|
-
return withClient(client, `/escrow/scopes?${buildQuery(params)}`);
|
|
19
|
+
return withClient(client, `/escrow/scopes?${buildQuery(params, sortQuery({ uid: "ASC" }))}`);
|
|
20
20
|
}
|
|
21
21
|
/**
|
|
22
22
|
* Who a typed principal - a mailbox address, an auth-server username or e-mail alias, or a user uid - is, for the
|
|
@@ -32,6 +32,17 @@ export interface PluginManifest {
|
|
|
32
32
|
mailboxScopedData?: boolean;
|
|
33
33
|
}
|
|
34
34
|
export type PluginSettingValue = string | number | boolean;
|
|
35
|
+
/** What `@rapidmx/restapi` answers in place of the saved value of a setting whose key names a secret (secret, password, credential, token, api key), so a
|
|
36
|
+
* stored API key or shared secret never reaches the console. Sent back as it was given, it means "left alone": the saved value stays. */
|
|
37
|
+
export interface PluginSecretSetting {
|
|
38
|
+
secret: true;
|
|
39
|
+
}
|
|
40
|
+
/** A saved setting as a response shows it. */
|
|
41
|
+
export type PluginSavedSetting = PluginSettingValue | PluginSecretSetting;
|
|
42
|
+
/** Whether `value` is the placeholder a saved secret setting is shown as. */
|
|
43
|
+
export declare function isSecretSetting(value: unknown): value is PluginSecretSetting;
|
|
44
|
+
/** Whether a setting's key names a secret - mirrors `@rapidmx/restapi`'s `SECRET_SETTING_KEY`, which decides what it withholds. */
|
|
45
|
+
export declare function isSecretSettingKey(key: string): boolean;
|
|
35
46
|
/** Mirrors `@rapidmx/restapi`'s `PluginConfiguredSetting`: what the deployment's own configuration (the command line, the
|
|
36
47
|
* environment, the server's defaults) says about one setting. It applies until a value is saved, which wins over it. */
|
|
37
48
|
export interface PluginConfiguredSetting {
|
|
@@ -48,7 +59,7 @@ export interface Plugin {
|
|
|
48
59
|
packageVersion: string;
|
|
49
60
|
integrity?: string;
|
|
50
61
|
enabled: boolean;
|
|
51
|
-
settings: Record<string,
|
|
62
|
+
settings: Record<string, PluginSavedSetting>;
|
|
52
63
|
/** For each setting the deployment's configuration provides a value for, what it says. Absent from a server that
|
|
53
64
|
* predates it, which reads as nothing being configured. */
|
|
54
65
|
configured?: Record<string, PluginConfiguredSetting>;
|
|
@@ -214,7 +225,7 @@ export interface UpdatePluginInput {
|
|
|
214
225
|
packageVersion?: string;
|
|
215
226
|
enabled?: boolean;
|
|
216
227
|
/** A `null` value clears a setting back to the plugin's own default. */
|
|
217
|
-
settings?: Record<string,
|
|
228
|
+
settings?: Record<string, PluginSavedSetting | null>;
|
|
218
229
|
/** What a previewed version change or enable was confirmed to also install and enable. */
|
|
219
230
|
expectedPlan?: PluginExpectedPlan;
|
|
220
231
|
}
|
|
@@ -9,6 +9,14 @@
|
|
|
9
9
|
* `getPluginStatus()`.
|
|
10
10
|
*/
|
|
11
11
|
import { withClient } from "../util/api.js";
|
|
12
|
+
/** Whether `value` is the placeholder a saved secret setting is shown as. */
|
|
13
|
+
export function isSecretSetting(value) {
|
|
14
|
+
return typeof value === "object" && value !== null && value.secret === true;
|
|
15
|
+
}
|
|
16
|
+
/** Whether a setting's key names a secret - mirrors `@rapidmx/restapi`'s `SECRET_SETTING_KEY`, which decides what it withholds. */
|
|
17
|
+
export function isSecretSettingKey(key) {
|
|
18
|
+
return /secret|password|credential|token|api_?key/i.test(key);
|
|
19
|
+
}
|
|
12
20
|
/** The `expectedPlan` to send for a change previewed as `plan`. */
|
|
13
21
|
export function expectedPlanOf(plan) {
|
|
14
22
|
return {
|
|
@@ -20,6 +20,9 @@ export interface RetentionPolicy {
|
|
|
20
20
|
* minimum) — mirrored here only so client-side validation can give the same message before a round trip;
|
|
21
21
|
* the server enforces this regardless. */
|
|
22
22
|
export declare const MIN_AUDIT_LOG_RETENTION_DAYS = 2190;
|
|
23
|
+
/** The server-enforced floor for `messageRetentionDays` (30 days) - `MIN_MESSAGE_RETENTION_DAYS` in `@rapidmx/restapi`, mirrored here for the same reason as
|
|
24
|
+
* `MIN_AUDIT_LOG_RETENTION_DAYS`. A shorter period is refused with a 400. */
|
|
25
|
+
export declare const MIN_MESSAGE_RETENTION_DAYS = 30;
|
|
23
26
|
/** `client`, given by every function below, is an explicit `ApiClient` from `createApiClient()` (e.g. one
|
|
24
27
|
* account of a multi-account app) to call instead of the default global `apiFetch()` - see `withClient()`'s
|
|
25
28
|
* own doc comment in `util/api.ts`. Omitted (the default), every function here behaves exactly as before. */
|
|
@@ -14,6 +14,9 @@ import { withClient } from "../util/api.js";
|
|
|
14
14
|
* minimum) — mirrored here only so client-side validation can give the same message before a round trip;
|
|
15
15
|
* the server enforces this regardless. */
|
|
16
16
|
export const MIN_AUDIT_LOG_RETENTION_DAYS = 2190;
|
|
17
|
+
/** The server-enforced floor for `messageRetentionDays` (30 days) - `MIN_MESSAGE_RETENTION_DAYS` in `@rapidmx/restapi`, mirrored here for the same reason as
|
|
18
|
+
* `MIN_AUDIT_LOG_RETENTION_DAYS`. A shorter period is refused with a 400. */
|
|
19
|
+
export const MIN_MESSAGE_RETENTION_DAYS = 30;
|
|
17
20
|
/** `client`, given by every function below, is an explicit `ApiClient` from `createApiClient()` (e.g. one
|
|
18
21
|
* account of a multi-account app) to call instead of the default global `apiFetch()` - see `withClient()`'s
|
|
19
22
|
* own doc comment in `util/api.ts`. Omitted (the default), every function here behaves exactly as before. */
|
|
@@ -9,12 +9,12 @@
|
|
|
9
9
|
* natural address of its own.
|
|
10
10
|
*/
|
|
11
11
|
import { withClient } from "../util/api.js";
|
|
12
|
-
import { buildQuery } from "../util/apiQuery.js";
|
|
12
|
+
import { buildQuery, sortQuery } from "../util/apiQuery.js";
|
|
13
13
|
/** `client`, given by every function below, is an explicit `ApiClient` from `createApiClient()` (e.g. one
|
|
14
14
|
* account of a multi-account app) to call instead of the default global `apiFetch()` - see `withClient()`'s
|
|
15
15
|
* own doc comment in `util/api.ts`. Omitted (the default), every function here behaves exactly as before. */
|
|
16
16
|
export function listTransportRules(params = {}, client) {
|
|
17
|
-
return withClient(client, `/mail/transport-rules?${buildQuery(params)}`);
|
|
17
|
+
return withClient(client, `/mail/transport-rules?${buildQuery(params, sortQuery({ sequence: "ASC", uid: "ASC" }))}`);
|
|
18
18
|
}
|
|
19
19
|
export function getTransportRule(uid, client) {
|
|
20
20
|
return withClient(client, `/mail/transport-rules/${encodeURIComponent(uid)}`);
|
|
@@ -112,7 +112,7 @@ export declare function getAppearance(client?: ApiClient): Promise<AppearancePre
|
|
|
112
112
|
export declare function saveAppearance(update: AppearanceUpdate, client?: ApiClient): Promise<AppearancePreferences>;
|
|
113
113
|
/** Stores `file` (already checked with `validateBackgroundFile()`) as the background: its raw bytes, with its own content type - which
|
|
114
114
|
* bypasses `apiFetch()`, as it always sends JSON. Answers with the updated preferences. */
|
|
115
|
-
export declare function uploadAppearanceBackground(file: Blob): Promise<AppearancePreferences>;
|
|
115
|
+
export declare function uploadAppearanceBackground(file: Blob, client?: ApiClient): Promise<AppearancePreferences>;
|
|
116
116
|
/** Removes the stored background image. Answers with the updated preferences when the server sends them, else `undefined`. */
|
|
117
117
|
export declare function deleteAppearanceBackground(client?: ApiClient): Promise<AppearancePreferences | undefined>;
|
|
118
118
|
/** The URL of the stored background image `version` - what CSS `url()` and `<img src>` use. Immutable per version. */
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* Everything that comes from outside - the response, a push event, the page's server-rendered props, `localStorage` - goes through
|
|
20
20
|
* `normalizeAppearance()`, which keeps only well-formed values (so a stale or hostile value can never reach a CSS declaration).
|
|
21
21
|
*/
|
|
22
|
-
import { ApiRequestError, apiUrl, withClient,
|
|
22
|
+
import { ApiRequestError, apiUrl, withClient, withClientRaw } from "../util/api.js";
|
|
23
23
|
export const APPEARANCE_MODES = ["system", "light", "dark"];
|
|
24
24
|
export const BACKGROUND_KINDS = ["none", "color", "image"];
|
|
25
25
|
export const BACKGROUND_FITS = ["cover", "contain", "tile"];
|
|
@@ -212,19 +212,8 @@ export async function saveAppearance(update, client) {
|
|
|
212
212
|
}
|
|
213
213
|
/** Stores `file` (already checked with `validateBackgroundFile()`) as the background: its raw bytes, with its own content type - which
|
|
214
214
|
* bypasses `apiFetch()`, as it always sends JSON. Answers with the updated preferences. */
|
|
215
|
-
export async function uploadAppearanceBackground(file) {
|
|
216
|
-
const
|
|
217
|
-
method: "POST",
|
|
218
|
-
credentials: "include",
|
|
219
|
-
headers: withCsrfHeader({ "Content-Type": file.type || "application/octet-stream" }),
|
|
220
|
-
body: file,
|
|
221
|
-
});
|
|
222
|
-
const contentType = res.headers.get("content-type") ?? "";
|
|
223
|
-
const body = contentType.includes("application/json") ? await res.json().catch(() => undefined) : undefined;
|
|
224
|
-
if (!res.ok) {
|
|
225
|
-
const message = (body && (body.message || body.error)) || res.statusText || "Upload failed.";
|
|
226
|
-
throw new ApiRequestError(message, res.status, body?.code);
|
|
227
|
-
}
|
|
215
|
+
export async function uploadAppearanceBackground(file, client) {
|
|
216
|
+
const body = await withClientRaw(client, `${PATH}/background`, "POST", file, file.type || "application/octet-stream");
|
|
228
217
|
const stored = normalizeAppearance(body);
|
|
229
218
|
if (!stored) {
|
|
230
219
|
throw new ApiRequestError("The server did not return the saved appearance.", 502);
|
|
@@ -4,30 +4,67 @@ export declare const SESSION_REFRESH_AFTER_MS: number;
|
|
|
4
4
|
export declare const SESSION_REFRESH_CHECK_MS: number;
|
|
5
5
|
/** A reload that follows a successful recovery refresh but still finds no session is not repeated within this window. */
|
|
6
6
|
export declare const SESSION_RELOAD_GUARD_MS: number;
|
|
7
|
+
/** A refresh answered with a refusal is asked once more after this long, in case the cookies were rotated by another tab or the server's session save lagged. */
|
|
8
|
+
export declare const SESSION_REFRESH_RETRY_MS = 1500;
|
|
9
|
+
/** A forced refresh (a request was refused, or the page has no session) does nothing when a refresh succeeded within this long: the refusal came from the old cookies. */
|
|
10
|
+
export declare const SESSION_REFRESH_MIN_GAP_MS: number;
|
|
11
|
+
/** How many entries `rapidmx.session.log` keeps. */
|
|
12
|
+
export declare const SESSION_LOG_MAX = 40;
|
|
13
|
+
/**
|
|
14
|
+
* How long a refresh request may go unanswered before it is given up on (not aborted - see `postRefresh()`). A refresh runs inside a cross-tab Web Lock, so one that hangs would hold the lock - and
|
|
15
|
+
* with it every tab's refresh, and every request waiting on a recovery - for good.
|
|
16
|
+
*/
|
|
17
|
+
export declare const SESSION_REFRESH_TIMEOUT_MS: number;
|
|
18
|
+
/** Sets how long a refresh request may go unanswered (for tests); `resetSessionRecovery()` restores the default. */
|
|
19
|
+
export declare function setSessionRefreshTimeout(ms: number): void;
|
|
20
|
+
/** Sets how long a refused refresh waits before its one retry (for tests); `resetSessionRecovery()` restores the default. */
|
|
21
|
+
export declare function setSessionRefreshRetryDelay(ms: number): void;
|
|
22
|
+
/** Whether a refresh succeeded within the last `ms`, in this tab or another (they share `localStorage`). A cleared or future-dated record counts as no. */
|
|
23
|
+
export declare function refreshedRecently(ms: number): boolean;
|
|
24
|
+
/** One entry of the session log (`rapidmx.session.log`). */
|
|
25
|
+
export interface SessionLogEntry {
|
|
26
|
+
/** When it happened, ISO 8601. */
|
|
27
|
+
t: string;
|
|
28
|
+
/** What happened, e.g. `refresh:start`, `refresh:rejected`, `leave`. */
|
|
29
|
+
e: string;
|
|
30
|
+
/** A short detail (a reason, a status, an age). Never a token or cookie value. */
|
|
31
|
+
d?: string;
|
|
32
|
+
}
|
|
33
|
+
/** The session log: the last `SESSION_LOG_MAX` things the silent refresh did, oldest first, kept in `localStorage['rapidmx.session.log']` so the cause of an unexpected sign-in can be read afterward. */
|
|
34
|
+
export declare function getSessionLog(): SessionLogEntry[];
|
|
35
|
+
/** Appends to the session log, dropping the oldest entries past `SESSION_LOG_MAX`. Best effort: blocked storage loses the entry. */
|
|
36
|
+
export declare function logSessionEvent(e: string, d?: string): void;
|
|
37
|
+
/** Why a refresh was made, for the session log. */
|
|
38
|
+
export type SessionRefreshReason = "check" | "recovery" | "nouser";
|
|
7
39
|
/**
|
|
8
40
|
* Refreshes the signed-in session through auth-server: exchanges the `refresh` cookie for a new access token and
|
|
9
41
|
* refresh token, both set as cookies on the response. Rejects with the `ApiRequestError` auth-server answered
|
|
10
42
|
* (`401`/`403` when the refresh token has expired or was revoked).
|
|
11
43
|
*
|
|
12
|
-
* A refresh token is single-use and bound to the session,
|
|
13
|
-
*
|
|
14
|
-
*
|
|
44
|
+
* A refresh token is single-use and bound to the session, and auth-server saves the rotated token a moment after it
|
|
45
|
+
* answers, so refreshing twice in quick succession makes the second look like a dead session. Refreshes are therefore
|
|
46
|
+
* shared by every caller in the tab (one in flight at a time), serialised across tabs (Web Locks, where the browser has
|
|
47
|
+
* them), and each tab records when the last one succeeded, so a caller that gets its turn right after a refresh does
|
|
48
|
+
* nothing. A refresh that is refused is asked once more after `SESSION_REFRESH_RETRY_MS` (the cookies may have been
|
|
49
|
+
* rotated meanwhile) before it counts as a rejection.
|
|
15
50
|
*
|
|
16
|
-
* @param force Refresh even when the last refresh, by this tab or another, was
|
|
51
|
+
* @param force Refresh even when the last refresh, by this tab or another, was `SESSION_REFRESH_AFTER_MS` ago or less. Even forced, a refresh made within `SESSION_REFRESH_MIN_GAP_MS` is not repeated.
|
|
52
|
+
* @param reason Why, for the session log.
|
|
17
53
|
* @returns `true` when a refresh was made, `false` when one was not needed.
|
|
18
54
|
*/
|
|
19
|
-
export declare function refreshSession(authServerUrl: string, force?: boolean): Promise<boolean>;
|
|
55
|
+
export declare function refreshSession(authServerUrl: string, force?: boolean, reason?: SessionRefreshReason): Promise<boolean>;
|
|
20
56
|
/** How long the outcome of a recovery is reused for: requests that fail together share one refresh, and a session that will not come back is not asked again at once. */
|
|
21
57
|
export declare const SESSION_RECOVERY_COOLDOWN_MS: number;
|
|
22
58
|
/**
|
|
23
|
-
* Renews the session because a request was refused for lack of one, whatever this browser's record of the last refresh says
|
|
24
|
-
*
|
|
25
|
-
*
|
|
59
|
+
* Renews the session because a request was refused for lack of one, whatever this browser's record of the last refresh says - except that a refresh that
|
|
60
|
+
* succeeded within `SESSION_REFRESH_MIN_GAP_MS` (by this tab or another) is not repeated: the request was refused with the cookies from before it, so it is
|
|
61
|
+
* simply sent again with the new ones. Every request refused at about the same time shares one refresh, and its outcome is reused for `SESSION_RECOVERY_COOLDOWN_MS`.
|
|
62
|
+
* A refusal also clears the record of the last refresh, so the next regular check asks again (and sends the browser to sign-in if the refresh token is truly gone).
|
|
26
63
|
*
|
|
27
64
|
* @returns Whether the session was renewed.
|
|
28
65
|
*/
|
|
29
66
|
export declare function recoverSession(authServerUrl: string): Promise<boolean>;
|
|
30
|
-
/** Forgets what recoveries have done (for tests). */
|
|
67
|
+
/** Forgets what recoveries and refreshes have done, and restores the retry delay and timeout (for tests). */
|
|
31
68
|
export declare function resetSessionRecovery(): void;
|
|
32
69
|
/**
|
|
33
70
|
* Redirects the browser to auth-server's sign-in page when `userUid` is absent, carrying the current URL as
|
|
@@ -66,9 +103,11 @@ export interface SessionRefreshOptions {
|
|
|
66
103
|
* - `userUid` set: also renews the session when a request is refused for lack of one (`apiFetch()` asks `recoverSession()` and sends the request
|
|
67
104
|
* again), so a token that ran out while timers were stopped costs nothing. Refreshes once the token is `SESSION_REFRESH_AFTER_MS` old (and right away when this browser has no
|
|
68
105
|
* record of a recent refresh, since the page's token age is unknown), checked every `SESSION_REFRESH_CHECK_MS` and
|
|
69
|
-
* whenever the tab becomes visible or the network returns, because a sleeping laptop stops timers
|
|
70
|
-
*
|
|
71
|
-
*
|
|
106
|
+
* whenever the tab becomes visible or focused or the network returns, because a sleeping laptop stops timers (a tab that wakes after the
|
|
107
|
+
* token's hour checks first, and a request refused meanwhile waits for that same refresh rather than starting its own). A failure that is
|
|
108
|
+
* not a rejection is simply retried at the next check. A rejection (`401`/`403` twice running, `SESSION_REFRESH_RETRY_MS` apart: the refresh
|
|
109
|
+
* token expired or was revoked, so the session cannot be kept) redirects to sign-in with `return_to` after `options.beforeRedirect` has run.
|
|
110
|
+
* Everything it does is recorded in the session log (`getSessionLog()`), which is also printed to the console when the browser goes to sign-in.
|
|
72
111
|
*
|
|
73
112
|
* **A no-op under an `ApiClientContext.Provider`** (see `util/apiClientContext.ts`): everything above assumes a
|
|
74
113
|
* `jwt`/`refresh` cookie pair this app can renew through auth-server's own `/auth/refresh` - meaningless for a
|