@rapidmx/web-client 0.30.0 → 0.32.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 +3 -3
- 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/diagnostics/diagnosticsApi.ts +2 -2
- 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 +195 -46
- 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 +2 -2
- 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/diagnostics/diagnosticsApi.d.ts +2 -2
- 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 +103 -18
- 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 +31 -2
- package/dist/lib/admin/pluginsApi.js +23 -1
- 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 +45 -3
- 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
|
@@ -34,11 +34,13 @@ function DetailRow({ icon, children }) {
|
|
|
34
34
|
* An event this reader may only see as a busy block (`occurrence.redacted`: a private or confidential event on a shared calendar) is drawn as just that -
|
|
35
35
|
* its time and that it is busy - with nothing to change, delete or answer.
|
|
36
36
|
*/
|
|
37
|
-
export default function EventDetails({ occurrence, isInvited, myResponse, isOwnAddress, calendarName, calendarColor, onClose, onModify, onSaved, onDeleted, }) {
|
|
37
|
+
export default function EventDetails({ occurrence, isInvited, myResponse, isOwnAddress, calendarName, calendarColor, canWrite = true, onClose, onModify, onSaved, onDeleted, }) {
|
|
38
38
|
const client = useApiClient();
|
|
39
39
|
const [error, setError] = useState(null);
|
|
40
40
|
const [busy, setBusy] = useState(false);
|
|
41
41
|
const [confirmingDelete, setConfirmingDelete] = useState(false);
|
|
42
|
+
// An answer to one occurrence of a series waits here for the reader to confirm: the server records it for the entire series.
|
|
43
|
+
const [pendingResponse, setPendingResponse] = useState(null);
|
|
42
44
|
const [requesting, setRequesting] = useState(false);
|
|
43
45
|
// The organizer's own join link for the event's video meeting: `undefined` while it is fetched, `null` once known to be none.
|
|
44
46
|
const [organizerJoinUrl, setOrganizerJoinUrl] = useState(undefined);
|
|
@@ -84,6 +86,7 @@ export default function EventDetails({ occurrence, isInvited, myResponse, isOwnA
|
|
|
84
86
|
// `@rapidmx/restapi`'s `respond()` route has no occurrence-vs-series scope of its own (see `calendarApi.ts`'s `respondToEvent` doc
|
|
85
87
|
// comment) - it always acts on `occurrence.uid` as a single event document.
|
|
86
88
|
async function handleRespond(responseStatus) {
|
|
89
|
+
setPendingResponse(null);
|
|
87
90
|
setError(null);
|
|
88
91
|
setBusy(true);
|
|
89
92
|
try {
|
|
@@ -133,8 +136,8 @@ export default function EventDetails({ occurrence, isInvited, myResponse, isOwnA
|
|
|
133
136
|
description: occurrence.description,
|
|
134
137
|
descriptionHtml: occurrence.descriptionHtml,
|
|
135
138
|
guestAddresses: occurrence.attendees.map((a) => a.address),
|
|
136
|
-
}, canChange: canChange, canInvite: canInvite, onSent: () => setRequesting(false), onCancel: () => setRequesting(false) }) })), myResponse !== undefined && (_jsxs("div", { className: "flex flex-col gap-2 mx-5 mb-3 p-3 rounded-md bg-surface-alt", children: [_jsxs("div", { className: "flex items-center justify-between gap-3 flex-wrap", children: [_jsx("span", { className: "text-sm font-medium", children: "Your response" }), _jsx("div", { className: "flex gap-2", children: answers.map(({ response, label }) => {
|
|
139
|
+
}, canChange: canChange, canInvite: canInvite, onSent: () => setRequesting(false), onCancel: () => setRequesting(false) }) })), myResponse !== undefined && canWrite && (_jsxs("div", { className: "flex flex-col gap-2 mx-5 mb-3 p-3 rounded-md bg-surface-alt", children: [_jsxs("div", { className: "flex items-center justify-between gap-3 flex-wrap", children: [_jsx("span", { className: "text-sm font-medium", children: "Your response" }), _jsx("div", { className: "flex gap-2", children: answers.map(({ response, label }) => {
|
|
137
140
|
const current = myResponse === response;
|
|
138
|
-
return (_jsxs(Button, { type: "button", variant: "secondary", className: ["!w-auto", response === "declined" ? "text-danger" : "", current ? "!bg-primary/10 !border-primary" : ""].join(" "), "aria-pressed": current, disabled: busy, onClick: () => void handleRespond(response), children: [current && _jsx(HiOutlineCheck, { size: 14, "aria-hidden": "true" }), label] }, response));
|
|
139
|
-
}) })] }), _jsx("p", { className: "text-xs text-text-muted", children: "Other attendees\u2019 responses may take a few minutes to update." })] })), _jsxs("div", { className: "flex flex-wrap items-center gap-3 px-5 pb-4", children: [!isInvited && (_jsx(Button, { type: "button", className: "!w-auto", disabled: busy, onClick: onModify, children: "Modify" })), (canChange || canInvite) && !requesting && (_jsx(Button, { type: "button", variant: "secondary", className: "!w-auto", disabled: busy, onClick: () => setRequesting(true), children: canChange ? "Request a change" : "Add guests" })), !
|
|
141
|
+
return (_jsxs(Button, { type: "button", variant: "secondary", className: ["!w-auto", response === "declined" ? "text-danger" : "", current ? "!bg-primary/10 !border-primary" : ""].join(" "), "aria-pressed": current, disabled: busy, onClick: () => (occurrence.isRecurringOccurrence ? setPendingResponse(response) : void handleRespond(response)), children: [current && _jsx(HiOutlineCheck, { size: 14, "aria-hidden": "true" }), label] }, response));
|
|
142
|
+
}) })] }), pendingResponse && (_jsxs("div", { role: "alert", className: "flex flex-wrap items-center gap-2 text-sm", children: [_jsxs("span", { className: "flex-1 min-w-0", children: [pendingResponse === "declined" ? "Declining" : "Your answer", " applies to every event in this series, not just this one", pendingResponse === "declined" ? ", and removes the series from your calendar" : "", "."] }), _jsx(Button, { type: "button", variant: "secondary", className: "!w-auto", disabled: busy, onClick: () => setPendingResponse(null), children: "Cancel" }), _jsxs(Button, { type: "button", className: "!w-auto", disabled: busy, onClick: () => void handleRespond(pendingResponse), children: [answers.find((a) => a.response === pendingResponse).label, " the series"] })] })), _jsx("p", { className: "text-xs text-text-muted", children: "Other attendees\u2019 responses may take a few minutes to update." })] })), _jsxs("div", { className: "flex flex-wrap items-center gap-3 px-5 pb-4", children: [!isInvited && canWrite && (_jsx(Button, { type: "button", className: "!w-auto", disabled: busy, onClick: onModify, children: "Modify" })), (canChange || canInvite) && !requesting && (_jsx(Button, { type: "button", variant: "secondary", className: "!w-auto", disabled: busy, onClick: () => setRequesting(true), children: canChange ? "Request a change" : "Add guests" })), !canWrite ? null : !confirmingDelete ? (_jsx(Button, { type: "button", variant: "secondary", className: "!w-auto text-danger", disabled: busy, onClick: () => setConfirmingDelete(true), children: "Delete" })) : !occurrence.isRecurringOccurrence ? (_jsxs("div", { role: "alert", className: "flex items-center gap-2", children: [_jsx("span", { className: "text-sm", children: "Delete this event?" }), _jsx(Button, { type: "button", variant: "secondary", className: "!w-auto text-danger", disabled: busy, onClick: () => void handleDelete("series"), children: "Yes, delete" }), _jsx(Button, { type: "button", variant: "secondary", className: "!w-auto", disabled: busy, onClick: () => setConfirmingDelete(false), children: "Keep" })] })) : (_jsxs("div", { className: "flex gap-2", children: [_jsx(Button, { type: "button", variant: "secondary", className: "!w-auto text-danger", disabled: busy, onClick: () => void handleDelete("occurrence"), children: "Delete this event" }), _jsx(Button, { type: "button", variant: "secondary", className: "!w-auto text-danger", disabled: busy, onClick: () => void handleDelete("series"), children: "Delete series" })] })), _jsx(Button, { type: "button", variant: "secondary", className: "!w-auto ml-auto", onClick: onClose, children: "Close" })] })] }));
|
|
140
143
|
}
|
|
@@ -3,7 +3,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
3
3
|
// Copyright (C) 2026 Jean-Philippe Steinmetz
|
|
4
4
|
// SPDX-License-Identifier: MPL-2.0
|
|
5
5
|
///////////////////////////////////////////////////////////////////////////////
|
|
6
|
-
import { useEffect, useMemo, useState } from "react";
|
|
6
|
+
import { useEffect, useMemo, useRef, useState } from "react";
|
|
7
7
|
import { format } from "date-fns";
|
|
8
8
|
import { ApiRequestError } from "../../../../lib/util/api.js";
|
|
9
9
|
import { useApiClient } from "../../../../lib/util/apiClientContext.js";
|
|
@@ -46,19 +46,25 @@ function wallTimeOfDayMs(instantMs, timezone) {
|
|
|
46
46
|
const wall = toEventWallClock(instantMs, timezone, false);
|
|
47
47
|
return ((wall % MS_PER_DAY) + MS_PER_DAY) % MS_PER_DAY;
|
|
48
48
|
}
|
|
49
|
+
/** The calendar date (`YYYY-MM-DD`) an instant falls on in the frame a series expands in: the UTC date for an all-day event, `timezone`'s wall clock otherwise. */
|
|
50
|
+
function seriesDateKey(iso, allDay, timezone) {
|
|
51
|
+
return allDay ? allDayDateKey(iso) : new Date(toEventWallClock(new Date(iso).getTime(), timezone, false)).toISOString().slice(0, 10);
|
|
52
|
+
}
|
|
53
|
+
const WEEKDAY_ORDER = ["SU", "MO", "TU", "WE", "TH", "FR", "SA"];
|
|
49
54
|
/**
|
|
50
55
|
* Rewrites an edited occurrence's `startDate`/`endDate` for a save to the entire series. The modal only
|
|
51
56
|
* ever shows (and edits) *this occurrence's* dates, but the series is stored as its master record starting
|
|
52
57
|
* at the first occurrence - sending the occurrence's dates as-is would move the whole series' start to
|
|
53
|
-
* this occurrence. So
|
|
54
|
-
* start
|
|
58
|
+
* this occurrence. So the change made to the occurrence (its date and its time of day) is applied to the
|
|
59
|
+
* master's own start, and the new duration is kept: moving a Tuesday occurrence to Thursday moves the series
|
|
60
|
+
* by two days, never silently dropping the change. Unchanged times aren't sent at all.
|
|
55
61
|
*
|
|
56
|
-
*
|
|
57
|
-
* browser's
|
|
58
|
-
*
|
|
59
|
-
*
|
|
62
|
+
* The change is measured on the event's own timezone wall clock (the series expands there), not the
|
|
63
|
+
* browser's. The master's new start is converted back from that wall clock, so a master on the other side of
|
|
64
|
+
* a DST change keeps the chosen local time. A weekly rule that the edit left untouched is rotated by the
|
|
65
|
+
* same number of days, so its weekdays move with the start (a rule the form already changed is left as it is).
|
|
60
66
|
*/
|
|
61
|
-
async function toSeriesFields(occurrence, fields, client) {
|
|
67
|
+
async function toSeriesFields(occurrence, fields, formZone, client) {
|
|
62
68
|
const { startDate, endDate, ...rest } = fields;
|
|
63
69
|
if (sameMinute(startDate, occurrence.startDate) && sameMinute(endDate, occurrence.endDate)) {
|
|
64
70
|
// `saveEventSeries()` treats *any* `timezone`/`allDay` in the update as a possible reinterpretation of
|
|
@@ -71,25 +77,32 @@ async function toSeriesFields(occurrence, fields, client) {
|
|
|
71
77
|
const timezone = fields.timezone;
|
|
72
78
|
const newStart = new Date(startDate).getTime();
|
|
73
79
|
const durationMs = new Date(endDate).getTime() - newStart;
|
|
80
|
+
// The days the date moved: on the event's own wall clock when both are timed, otherwise on the clock the form showed the timed side on.
|
|
81
|
+
const frame = fields.allDay || occurrence.allDay ? formZone : timezone;
|
|
82
|
+
const dayShift = Math.round((Date.parse(seriesDateKey(startDate, !!fields.allDay, frame)) - Date.parse(seriesDateKey(occurrence.startDate, occurrence.allDay, frame))) / MS_PER_DAY);
|
|
74
83
|
let masterStart;
|
|
75
84
|
if (fields.allDay) {
|
|
76
|
-
//
|
|
85
|
+
// The master's own date (on its wall clock when it was timed), moved by the days the occurrence moved.
|
|
77
86
|
const masterDateKey = master.allDay
|
|
78
87
|
? allDayDateKey(master.startDate)
|
|
79
88
|
: new Date(toEventWallClock(new Date(master.startDate).getTime(), timezone, false)).toISOString().slice(0, 10);
|
|
80
|
-
masterStart = new Date(allDayInstant(masterDateKey)).getTime();
|
|
89
|
+
masterStart = new Date(allDayInstant(addDaysToKey(masterDateKey, dayShift))).getTime();
|
|
81
90
|
}
|
|
82
91
|
else if (master.allDay) {
|
|
83
|
-
// All-day -> timed: the master's own date at the newly chosen wall-clock time.
|
|
84
|
-
const masterDateWall = new Date(allDayInstant(allDayDateKey(master.startDate))).getTime();
|
|
92
|
+
// All-day -> timed: the master's own date (moved by the days the occurrence moved) at the newly chosen wall-clock time.
|
|
93
|
+
const masterDateWall = new Date(allDayInstant(addDaysToKey(allDayDateKey(master.startDate), dayShift))).getTime();
|
|
85
94
|
masterStart = fromEventWallClock(masterDateWall + wallTimeOfDayMs(newStart, timezone), timezone, false);
|
|
86
95
|
}
|
|
87
96
|
else {
|
|
88
|
-
const
|
|
89
|
-
const delta = ((((rawDelta + MS_PER_DAY / 2) % MS_PER_DAY) + MS_PER_DAY) % MS_PER_DAY) - MS_PER_DAY / 2;
|
|
97
|
+
const delta = toEventWallClock(newStart, timezone, false) - toEventWallClock(new Date(occurrence.startDate).getTime(), timezone, false);
|
|
90
98
|
const masterWall = toEventWallClock(new Date(master.startDate).getTime(), timezone, false);
|
|
91
99
|
masterStart = fromEventWallClock(masterWall + delta, timezone, false);
|
|
92
100
|
}
|
|
101
|
+
const rule = rest.recurrenceRule;
|
|
102
|
+
const original = occurrence.recurrenceRule?.byDay;
|
|
103
|
+
if (rule?.freq === "weekly" && rule.byDay && original && dayShift % 7 !== 0 && rule.byDay.join() === original.join()) {
|
|
104
|
+
rest.recurrenceRule = { ...rule, byDay: rule.byDay.map((day) => WEEKDAY_ORDER[(WEEKDAY_ORDER.indexOf(day) + (dayShift % 7) + 7) % 7]) };
|
|
105
|
+
}
|
|
93
106
|
return {
|
|
94
107
|
...rest,
|
|
95
108
|
startDate: new Date(masterStart).toISOString(),
|
|
@@ -150,6 +163,8 @@ export default function EventEditor({ layout, onExpand, onCancel, onSaved, onSyn
|
|
|
150
163
|
targetFolderUid: folderUid,
|
|
151
164
|
};
|
|
152
165
|
});
|
|
166
|
+
// What an existing event's form held when it opened, to tell whether anything was changed since.
|
|
167
|
+
const [pristine] = useState(() => JSON.stringify(values));
|
|
153
168
|
const { attendees, location, videoEnabled } = values;
|
|
154
169
|
function update(patch) {
|
|
155
170
|
setValues((prev) => ({ ...prev, ...patch }));
|
|
@@ -172,6 +187,12 @@ export default function EventEditor({ layout, onExpand, onCancel, onSaved, onSyn
|
|
|
172
187
|
// finish the video-conferencing step after it: the modal then stays open on that error, and the retry
|
|
173
188
|
// must update *that* record (its uid and its new version), not re-create it or reuse the stale prop.
|
|
174
189
|
const [savedEvent, setSavedEvent] = useState(null);
|
|
190
|
+
// Whether that stored record is a series saved with `toSeriesFields()`: its master's dates are not the form's (the occurrence's), so a retry
|
|
191
|
+
// must not send them - the first save already applied any change of time.
|
|
192
|
+
const savedAsSeries = useRef(false);
|
|
193
|
+
// A meeting minted for this event whose link onto the event could not be saved. A retry reuses it rather than minting a second one,
|
|
194
|
+
// which would send the invitees a second set of join links and leave this one orphaned.
|
|
195
|
+
const mintedMeeting = useRef(null);
|
|
175
196
|
const [editScope, setEditScope] = useState("occurrence");
|
|
176
197
|
const [error, setError] = useState(null);
|
|
177
198
|
const [saving, setSaving] = useState(false);
|
|
@@ -201,7 +222,9 @@ export default function EventEditor({ layout, onExpand, onCancel, onSaved, onSyn
|
|
|
201
222
|
}, [occurrence?.videoMeetingUid, client]);
|
|
202
223
|
// "This event only" detaches a standalone, non-repeating copy - the series' rule doesn't apply to it.
|
|
203
224
|
const editingSingleOccurrence = !!occurrence?.isRecurringOccurrence && editScope === "occurrence";
|
|
204
|
-
dirtyRef.current =
|
|
225
|
+
dirtyRef.current = occurrence
|
|
226
|
+
? JSON.stringify(values) !== pristine
|
|
227
|
+
: !!(values.title.trim() || values.location.trim() || values.attendees.length > 0 || values.guestDraft.trim() || values.guestInvalid.length > 0 || hasDescriptionText(values.descriptionHtml));
|
|
205
228
|
function updateAttendee(index, patch) {
|
|
206
229
|
setValues((prev) => ({ ...prev, attendees: prev.attendees.map((a, i) => (i === index ? { ...a, ...patch } : a)) }));
|
|
207
230
|
}
|
|
@@ -217,6 +240,11 @@ export default function EventEditor({ layout, onExpand, onCancel, onSaved, onSyn
|
|
|
217
240
|
],
|
|
218
241
|
}));
|
|
219
242
|
}
|
|
243
|
+
// The weekday the form's start falls on, in the frame the series expands in (`undefined` while the start is not a valid date).
|
|
244
|
+
function startWeekdayOf(v) {
|
|
245
|
+
const ms = v.allDay ? Number.NaN : wallStringToMs(v.start, v.formZone, deviceZone);
|
|
246
|
+
return startWeekdayCode(v.allDay ? v.start : Number.isNaN(ms) ? "" : new Date(ms).toISOString(), v.allDay, v.timezone);
|
|
247
|
+
}
|
|
220
248
|
// Moving the start moves the end with it, so the event keeps its length (a Google Calendar habit, and the way to
|
|
221
249
|
// reschedule without touching two fields).
|
|
222
250
|
function setStart(next) {
|
|
@@ -231,7 +259,13 @@ export default function EventEditor({ layout, onExpand, onCancel, onSaved, onSyn
|
|
|
231
259
|
? `${new Date(endMs + shift).toISOString().slice(0, 10)}T00:00`
|
|
232
260
|
: msToWallString(endMs + shift, prev.formZone, deviceZone);
|
|
233
261
|
}
|
|
234
|
-
|
|
262
|
+
// A weekly rule on just the start's weekday ("Weekly on Wednesday") follows the start to its new weekday - left alone it
|
|
263
|
+
// would not include the event's own first day, and the series would begin on the next of the old weekday instead.
|
|
264
|
+
const oldDay = startWeekdayOf(prev);
|
|
265
|
+
const nextDay = startWeekdayOf({ ...prev, start: next });
|
|
266
|
+
const rule = prev.recurrenceRule;
|
|
267
|
+
const followsStart = oldDay && nextDay && rule?.freq === "weekly" && rule.byDay?.length === 1 && rule.byDay[0] === oldDay;
|
|
268
|
+
return { ...prev, start: next, end: nextEnd, recurrenceRule: followsStart ? { ...rule, byDay: [nextDay] } : rule };
|
|
235
269
|
});
|
|
236
270
|
}
|
|
237
271
|
// The recurrence end date is stored in the frame the series expands in (UTC for all-day, local otherwise),
|
|
@@ -331,23 +365,28 @@ export default function EventEditor({ layout, onExpand, onCancel, onSaved, onSyn
|
|
|
331
365
|
setVideoError("Add at least one attendee other than yourself to add video conferencing.");
|
|
332
366
|
return { event: saved, failed: true };
|
|
333
367
|
}
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
368
|
+
if (!mintedMeeting.current) {
|
|
369
|
+
const result = await createVideoMeeting({
|
|
370
|
+
mailboxUid: saved.mailboxUid,
|
|
371
|
+
title: saved.title.slice(0, MAX_MEETING_TITLE_LENGTH),
|
|
372
|
+
visibility: "private",
|
|
373
|
+
calendarEventUid: saved.uid,
|
|
374
|
+
startTime: saved.startDate,
|
|
375
|
+
endTime: saved.endDate,
|
|
376
|
+
invitees,
|
|
377
|
+
}, client);
|
|
378
|
+
mintedMeeting.current = { uid: result.meeting.uid, organizerJoinUrl: result.organizerJoinUrl ?? null };
|
|
379
|
+
}
|
|
380
|
+
const meeting = mintedMeeting.current;
|
|
343
381
|
const patched = await updateCalendarEvent({
|
|
344
382
|
uid: saved.uid,
|
|
345
383
|
version: saved.version,
|
|
346
384
|
location: VIDEO_LOCATION_PLACEHOLDER,
|
|
347
|
-
videoMeetingUid:
|
|
385
|
+
videoMeetingUid: meeting.uid,
|
|
348
386
|
}, client);
|
|
349
|
-
|
|
350
|
-
|
|
387
|
+
mintedMeeting.current = null;
|
|
388
|
+
setVideoMeetingUid(meeting.uid);
|
|
389
|
+
setOrganizerJoinUrl(meeting.organizerJoinUrl);
|
|
351
390
|
update({ location: VIDEO_LOCATION_PLACEHOLDER });
|
|
352
391
|
return { event: patched, failed: false };
|
|
353
392
|
}
|
|
@@ -455,8 +494,17 @@ export default function EventEditor({ layout, onExpand, onCancel, onSaved, onSyn
|
|
|
455
494
|
// A retry after the event was stored but its video meeting couldn't be (see `savedEvent`):
|
|
456
495
|
// update exactly the record the first attempt left behind, whichever branch below created it.
|
|
457
496
|
// A series' own exception/detached-occurrence re-pointing already ran on that attempt and has
|
|
458
|
-
// nothing left to shift
|
|
459
|
-
|
|
497
|
+
// nothing left to shift. For a series the form's dates are those of the edited occurrence, not
|
|
498
|
+
// the master's: sending them would move the whole series to that occurrence, so they (and the
|
|
499
|
+
// zone and all-day flag they go with) stay as the first attempt left them.
|
|
500
|
+
const retryFields = { ...fields };
|
|
501
|
+
if (savedAsSeries.current) {
|
|
502
|
+
delete retryFields.startDate;
|
|
503
|
+
delete retryFields.endDate;
|
|
504
|
+
delete retryFields.timezone;
|
|
505
|
+
delete retryFields.allDay;
|
|
506
|
+
}
|
|
507
|
+
saved = await updateCalendarEvent({ uid: savedEvent.uid, version: savedEvent.version, ...retryFields }, client);
|
|
460
508
|
}
|
|
461
509
|
else if (!occurrence) {
|
|
462
510
|
// The organizer is only ever set on create - an edit keeps the event's own organizer.
|
|
@@ -471,8 +519,9 @@ export default function EventEditor({ layout, onExpand, onCancel, onSaved, onSyn
|
|
|
471
519
|
saved = await detachOccurrence(occurrence, fields, client);
|
|
472
520
|
}
|
|
473
521
|
else if (occurrence.isRecurringOccurrence) {
|
|
474
|
-
const series = await saveEventSeries(occurrence, (await toSeriesFields(occurrence, fields, client)), client);
|
|
522
|
+
const series = await saveEventSeries(occurrence, (await toSeriesFields(occurrence, fields, values.formZone, client)), client);
|
|
475
523
|
saved = series;
|
|
524
|
+
savedAsSeries.current = true;
|
|
476
525
|
detachedSyncFailed = !!series.detachedOccurrenceSyncFailed;
|
|
477
526
|
}
|
|
478
527
|
else {
|
|
@@ -507,7 +556,6 @@ export default function EventEditor({ layout, onExpand, onCancel, onSaved, onSyn
|
|
|
507
556
|
// stays disabled either way, and the two are told apart below purely from `organizerJoinUrl` itself.
|
|
508
557
|
const joinUrl = joinMeetingUrl(organizerJoinUrl);
|
|
509
558
|
const timeZones = useMemo(() => timeZoneOptions(values.formZone, values.timezone, deviceZone), [values.formZone, values.timezone, deviceZone]);
|
|
510
|
-
const startMs = values.allDay ? Number.NaN : wallStringToMs(values.start, values.formZone, deviceZone);
|
|
511
559
|
const calendarFolderUid = occurrence ? occurrence.folderUid : values.targetFolderUid;
|
|
512
560
|
const controller = {
|
|
513
561
|
occurrence,
|
|
@@ -520,7 +568,7 @@ export default function EventEditor({ layout, onExpand, onCancel, onSaved, onSyn
|
|
|
520
568
|
timeZones,
|
|
521
569
|
organizerAddress: effectiveOrganizerAddress,
|
|
522
570
|
deviceZone,
|
|
523
|
-
startWeekday:
|
|
571
|
+
startWeekday: startWeekdayOf(values),
|
|
524
572
|
editingSingleOccurrence,
|
|
525
573
|
updateAttendee,
|
|
526
574
|
removeAttendee,
|
|
@@ -38,11 +38,13 @@ export declare function ReminderRow({ value, onChange }: {
|
|
|
38
38
|
* "Does not repeat" and the plain repeats as a menu, with "Custom…" opening `RecurrenceEditor`'s full set (an interval, weekdays, an end).
|
|
39
39
|
* Choosing one of the plain repeats keeps the rule's skipped dates; the menu shows "Custom…" for any rule it has no plain entry for.
|
|
40
40
|
*/
|
|
41
|
-
export declare function RecurrenceSelect({ value, onChange, allDay, startWeekday, }: {
|
|
41
|
+
export declare function RecurrenceSelect({ value, onChange, allDay, startWeekday, startDateKey, }: {
|
|
42
42
|
value: RecurrenceRule | null;
|
|
43
43
|
onChange: (rule: RecurrenceRule | null) => void;
|
|
44
44
|
allDay: boolean;
|
|
45
45
|
startWeekday: WeekdayCode | undefined;
|
|
46
|
+
/** The event's start date (`yyyy-MM-dd`), for a custom rule's default end date. */
|
|
47
|
+
startDateKey?: string;
|
|
46
48
|
}): React.JSX.Element;
|
|
47
49
|
/** [start date] [start time] to [end time] [end date], then the "Time zone" link (and its menu), "All day" and how the event repeats. */
|
|
48
50
|
export declare function DateTimeControls({ c }: {
|
|
@@ -124,7 +124,7 @@ function presetOf(rule, startWeekday) {
|
|
|
124
124
|
* "Does not repeat" and the plain repeats as a menu, with "Custom…" opening `RecurrenceEditor`'s full set (an interval, weekdays, an end).
|
|
125
125
|
* Choosing one of the plain repeats keeps the rule's skipped dates; the menu shows "Custom…" for any rule it has no plain entry for.
|
|
126
126
|
*/
|
|
127
|
-
export function RecurrenceSelect({ value, onChange, allDay, startWeekday, }) {
|
|
127
|
+
export function RecurrenceSelect({ value, onChange, allDay, startWeekday, startDateKey, }) {
|
|
128
128
|
// Picking "Custom…" for a rule that is also a plain repeat still opens the editor.
|
|
129
129
|
const [customRequested, setCustomRequested] = useState(false);
|
|
130
130
|
const selected = customRequested && value ? "custom" : presetOf(value, startWeekday);
|
|
@@ -140,7 +140,7 @@ export function RecurrenceSelect({ value, onChange, allDay, startWeekday, }) {
|
|
|
140
140
|
onChange({ ...presetRule(key, startWeekday), exceptions: value?.exceptions ?? [] });
|
|
141
141
|
}
|
|
142
142
|
}
|
|
143
|
-
return (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsxs("select", { id: "event-recurrence", "aria-label": "Recurrence", className: SELECT_CLASS, value: selected, onChange: (e) => choose(e.target.value), children: [_jsx("option", { value: "none", children: "Does not repeat" }), _jsx("option", { value: "daily", children: "Daily" }), _jsx("option", { value: "weekly", children: startWeekday ? `Weekly on ${WEEKDAY_NAME[startWeekday]}` : "Weekly" }), _jsx("option", { value: "monthly", children: "Monthly" }), _jsx("option", { value: "yearly", children: "Annually" }), _jsx("option", { value: "weekdays", children: "Every weekday (Monday to Friday)" }), _jsx("option", { value: "custom", children: "Custom\u2026" })] }), selected === "custom" && _jsx(RecurrenceEditor, { value: value, onChange: onChange, allDay: allDay, startWeekday: startWeekday, hideToggle: true })] }));
|
|
143
|
+
return (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsxs("select", { id: "event-recurrence", "aria-label": "Recurrence", className: SELECT_CLASS, value: selected, onChange: (e) => choose(e.target.value), children: [_jsx("option", { value: "none", children: "Does not repeat" }), _jsx("option", { value: "daily", children: "Daily" }), _jsx("option", { value: "weekly", children: startWeekday ? `Weekly on ${WEEKDAY_NAME[startWeekday]}` : "Weekly" }), _jsx("option", { value: "monthly", children: "Monthly" }), _jsx("option", { value: "yearly", children: "Annually" }), _jsx("option", { value: "weekdays", children: "Every weekday (Monday to Friday)" }), _jsx("option", { value: "custom", children: "Custom\u2026" })] }), selected === "custom" && _jsx(RecurrenceEditor, { value: value, onChange: onChange, allDay: allDay, startWeekday: startWeekday, startDateKey: startDateKey, hideToggle: true })] }));
|
|
144
144
|
}
|
|
145
145
|
const START_TIME_OPTIONS = dayTimes().map((time) => ({ value: time, label: formatTimeOfDay(time) }));
|
|
146
146
|
/** [start date] [start time] to [end time] [end date], then the "Time zone" link (and its menu), "All day" and how the event repeats. */
|
|
@@ -149,7 +149,7 @@ export function DateTimeControls({ c }) {
|
|
|
149
149
|
const [zoneOpen, setZoneOpen] = useState(false);
|
|
150
150
|
return (_jsxs("div", { className: "flex flex-col gap-3", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2 text-sm", children: [_jsx("input", { type: "date", "aria-label": "Event start date", className: `${SELECT_CLASS} min-w-0`, value: values.start.slice(0, 10), onChange: (e) => c.setStart(`${e.target.value}${values.start.slice(10)}`) }), !values.allDay && (_jsx(TimeComboBox, { "aria-label": "Event start time", className: `${SELECT_CLASS} w-28`, display: formatTimeOfDay(values.start.slice(11, 16)), current: values.start.slice(11, 16), options: START_TIME_OPTIONS,
|
|
151
151
|
// Moving the start moves the end with it, so the event keeps its length: whatever the start is set to.
|
|
152
|
-
onSelect: (time) => c.setStart(`${values.start.slice(0, 11)}${time}`), onType: (time) => c.setStart(`${values.start.slice(0, 11)}${time}`) })), _jsx("span", { className: "text-text-muted", children: "to" }), !values.allDay && (_jsx(TimeComboBox, { "aria-label": "Event end time", className: `${SELECT_CLASS} w-28`, display: formatTimeOfDay(values.end.slice(11, 16)), current: values.end, options: endTimeOptions(values.start).map((option) => ({ value: option.value, label: option.label, hint: option.hint })), onSelect: c.setEnd, onType: (time) => c.setEnd(`${values.end.slice(0, 11)}${time}`) })), _jsx("input", { type: "date", "aria-label": "Event end date", className: `${SELECT_CLASS} min-w-0`, value: values.end.slice(0, 10), onChange: (e) => c.setEnd(`${e.target.value}${values.end.slice(10)}`) })] }), _jsx("button", { type: "button", "aria-expanded": zoneOpen, className: "self-start text-sm text-primary-dark hover:underline", onClick: () => setZoneOpen((open) => !open), children: "Time zone" }), zoneOpen && (_jsx(TimeZonePicker, { "aria-label": "Event time zone", className: `${SELECT_CLASS} max-w-full`, value: values.formZone, zones: c.timeZones, at: zoneDate(values.start), onChange: c.onTimeZoneChange })), _jsxs("label", { className: "flex items-center gap-2 text-sm", children: [_jsx("input", { type: "checkbox", checked: values.allDay, onChange: (e) => c.onAllDayChange(e.target.checked) }), "All day"] }), !c.editingSingleOccurrence && (_jsx(RecurrenceSelect, { value: values.recurrenceRule, onChange: (rule) => c.update({ recurrenceRule: rule }), allDay: values.allDay, startWeekday: c.startWeekday }))] }));
|
|
152
|
+
onSelect: (time) => c.setStart(`${values.start.slice(0, 11)}${time}`), onType: (time) => c.setStart(`${values.start.slice(0, 11)}${time}`) })), _jsx("span", { className: "text-text-muted", children: "to" }), !values.allDay && (_jsx(TimeComboBox, { "aria-label": "Event end time", className: `${SELECT_CLASS} w-28`, display: formatTimeOfDay(values.end.slice(11, 16)), current: values.end, options: endTimeOptions(values.start).map((option) => ({ value: option.value, label: option.label, hint: option.hint })), onSelect: c.setEnd, onType: (time) => c.setEnd(`${values.end.slice(0, 11)}${time}`) })), _jsx("input", { type: "date", "aria-label": "Event end date", className: `${SELECT_CLASS} min-w-0`, value: values.end.slice(0, 10), onChange: (e) => c.setEnd(`${e.target.value}${values.end.slice(10)}`) })] }), _jsx("button", { type: "button", "aria-expanded": zoneOpen, className: "self-start text-sm text-primary-dark hover:underline", onClick: () => setZoneOpen((open) => !open), children: "Time zone" }), zoneOpen && (_jsx(TimeZonePicker, { "aria-label": "Event time zone", className: `${SELECT_CLASS} max-w-full`, value: values.formZone, zones: c.timeZones, at: zoneDate(values.start), onChange: c.onTimeZoneChange })), _jsxs("label", { className: "flex items-center gap-2 text-sm", children: [_jsx("input", { type: "checkbox", checked: values.allDay, onChange: (e) => c.onAllDayChange(e.target.checked) }), "All day"] }), !c.editingSingleOccurrence && (_jsx(RecurrenceSelect, { value: values.recurrenceRule, onChange: (rule) => c.update({ recurrenceRule: rule }), allDay: values.allDay, startWeekday: c.startWeekday, startDateKey: values.start.slice(0, 10) }))] }));
|
|
153
153
|
}
|
|
154
154
|
/** The calendar this event is in: its colour and name, a choice between calendars (and mailboxes) when an event is being created. */
|
|
155
155
|
export function CalendarField({ c }) {
|
|
@@ -12,6 +12,6 @@ export interface EventMatchNavProps {
|
|
|
12
12
|
* The Previous / Next match buttons beside an event's card while a search is running: the card moves on to the neighbouring result. They sit
|
|
13
13
|
* inside the dialog (so Tab reaches them and the dialog's focus trap holds) but are positioned against the window - either side of the
|
|
14
14
|
* centered card on a desktop, the bottom corners on a phone, where the card fills the width. The left and right arrow keys do the same unless
|
|
15
|
-
* a field is taking them.
|
|
15
|
+
* a field or a widget (a select, a menu, a slider, ...) is taking them.
|
|
16
16
|
*/
|
|
17
17
|
export default function EventMatchNav({ onPrevious, onNext, canPrevious, canNext, noun }: EventMatchNavProps): React.JSX.Element;
|
|
@@ -6,16 +6,26 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
|
|
|
6
6
|
import { useEffect } from "react";
|
|
7
7
|
import { HiOutlineChevronLeft, HiOutlineChevronRight } from "react-icons/hi2";
|
|
8
8
|
import { isTextEntry } from "../../keyboard/targets.js";
|
|
9
|
+
/** Widgets that take the arrow keys themselves (a menu's items, a radio group, a slider, a list's options, a tab list, ...) and an open popup's trigger. */
|
|
10
|
+
const ARROW_WIDGET_SELECTOR = [
|
|
11
|
+
...["menu", "menubar", "menuitem", "menuitemcheckbox", "menuitemradio", "radiogroup", "radio", "listbox", "option"].map((role) => `[role="${role}"]`),
|
|
12
|
+
...["slider", "spinbutton", "tablist", "tab", "tree", "grid", "combobox"].map((role) => `[role="${role}"]`),
|
|
13
|
+
'[aria-haspopup][aria-expanded="true"]',
|
|
14
|
+
].join(",");
|
|
15
|
+
/** Whether the arrow keys pressed at `target` belong to what is there: a field or select, or a widget with arrows of its own. */
|
|
16
|
+
function takesArrowKeys(target) {
|
|
17
|
+
return isTextEntry(target) || (target instanceof Element && target.closest(ARROW_WIDGET_SELECTOR) !== null);
|
|
18
|
+
}
|
|
9
19
|
/**
|
|
10
20
|
* The Previous / Next match buttons beside an event's card while a search is running: the card moves on to the neighbouring result. They sit
|
|
11
21
|
* inside the dialog (so Tab reaches them and the dialog's focus trap holds) but are positioned against the window - either side of the
|
|
12
22
|
* centered card on a desktop, the bottom corners on a phone, where the card fills the width. The left and right arrow keys do the same unless
|
|
13
|
-
* a field is taking them.
|
|
23
|
+
* a field or a widget (a select, a menu, a slider, ...) is taking them.
|
|
14
24
|
*/
|
|
15
25
|
export default function EventMatchNav({ onPrevious, onNext, canPrevious = true, canNext = true, noun = "match" }) {
|
|
16
26
|
useEffect(() => {
|
|
17
27
|
function handleKeyDown(event) {
|
|
18
|
-
if ((event.key !== "ArrowLeft" && event.key !== "ArrowRight") || event.defaultPrevented ||
|
|
28
|
+
if ((event.key !== "ArrowLeft" && event.key !== "ArrowRight") || event.defaultPrevented || takesArrowKeys(event.target)) {
|
|
19
29
|
return;
|
|
20
30
|
}
|
|
21
31
|
if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) {
|
|
@@ -7,6 +7,7 @@ import { useRef, useState } from "react";
|
|
|
7
7
|
import Modal from "../../../../lib/components/overlays/Modal.js";
|
|
8
8
|
import Button from "../../../../lib/components/buttons/Button.js";
|
|
9
9
|
import useIsMobile from "../../../../lib/util/useIsMobile.js";
|
|
10
|
+
import { useMailboxUpdateAccess } from "../../mail/useMailboxUpdateAccess.js";
|
|
10
11
|
import EventDetails from "./EventDetails.js";
|
|
11
12
|
import EventEditor from "./EventEditor.js";
|
|
12
13
|
import EventMatchNav from "./EventMatchNav.js";
|
|
@@ -43,6 +44,10 @@ export default function EventModal({ open, onClose, mailboxUid, folderUid, calen
|
|
|
43
44
|
const [syncWarning, setSyncWarning] = useState(false);
|
|
44
45
|
// Whether the form holds anything a click on the backdrop would throw away (kept up to date by the editor).
|
|
45
46
|
const dirtyRef = useRef(false);
|
|
47
|
+
// Escape or the close button on a form that holds something asks before throwing it away.
|
|
48
|
+
const [confirmingDiscard, setConfirmingDiscard] = useState(false);
|
|
49
|
+
// An event on a calendar shared with the reader view-only cannot be changed, deleted or answered.
|
|
50
|
+
const canWrite = useMailboxUpdateAccess(mailboxOptions?.find((option) => option.mailbox.uid === occurrence?.mailboxUid)?.mailbox);
|
|
46
51
|
if (!open) {
|
|
47
52
|
return null;
|
|
48
53
|
}
|
|
@@ -65,7 +70,7 @@ export default function EventModal({ open, onClose, mailboxUid, folderUid, calen
|
|
|
65
70
|
const width = !isForm ? DETAILS_WIDTH : layout === "expanded" ? (tab === "event" || occurrence ? FORM_WIDTH : TASK_FORM_WIDTH) : QUICK_WIDTH;
|
|
66
71
|
// Leaving the form: an existing event goes back to its details, a new one is done.
|
|
67
72
|
const leaveForm = occurrence ? () => setEditing(false) : onClose;
|
|
68
|
-
return (_jsxs(EventShell, { variant: variant, label: !occurrence ? "New event" : editing ? "Edit event" : "Event details", focusKey: isForm ? layout : "details", anchor: anchor, width: width, onClose: isForm ? leaveForm : onClose, onBackdropPress: isForm ? () => !dirtyRef.current && leaveForm() : undefined, children: [isForm ? (_jsx(EventEditor, { layout: layout, onExpand: () => setExpanded(true), onCancel: leaveForm, onSaved: onSaved, onSyncWarning: () => setSyncWarning(true), dirtyRef: dirtyRef, mailboxUid: mailboxUid, folderUid: folderUid, calendars: calendars, mailboxOptions: mailboxOptions, folderColors: folderColors, organizerAddress: organizerAddress, occurrence: occurrence, initialStart: initialStart, initialEnd: initialEnd, initialAllDay: initialAllDay, quickCreate: occurrence
|
|
73
|
+
return (_jsxs(EventShell, { variant: variant, label: !occurrence ? "New event" : editing ? "Edit event" : "Event details", focusKey: isForm ? layout : "details", anchor: anchor, width: width, onClose: isForm ? () => (dirtyRef.current ? setConfirmingDiscard(true) : leaveForm()) : onClose, onBackdropPress: isForm ? () => !dirtyRef.current && leaveForm() : undefined, children: [isForm ? (_jsx(EventEditor, { layout: layout, onExpand: () => setExpanded(true), onCancel: leaveForm, onSaved: onSaved, onSyncWarning: () => setSyncWarning(true), dirtyRef: dirtyRef, mailboxUid: mailboxUid, folderUid: folderUid, calendars: calendars, mailboxOptions: mailboxOptions, folderColors: folderColors, organizerAddress: organizerAddress, occurrence: occurrence, initialStart: initialStart, initialEnd: initialEnd, initialAllDay: initialAllDay, quickCreate: occurrence
|
|
69
74
|
? undefined
|
|
70
75
|
: {
|
|
71
76
|
tab,
|
|
@@ -74,5 +79,8 @@ export default function EventModal({ open, onClose, mailboxUid, folderUid, calen
|
|
|
74
79
|
setExpanded(false);
|
|
75
80
|
},
|
|
76
81
|
bookingHref,
|
|
77
|
-
} })) : (_jsx(SlideIn, { from: matchNav?.slide?.occurrenceKey === occurrence.occurrenceKey ? matchNav.slide.from : undefined, children: _jsx(EventDetails, { occurrence: occurrence, isInvited: isInvited, myResponse: canRespond ? myAttendee.responseStatus : undefined, isOwnAddress: isOwnAddress, calendarName: calendars?.find((cal) => cal.uid === occurrence.folderUid)?.name, calendarColor: folderColors?.[occurrence.folderUid], onClose: onClose, onModify: () => setEditing(true), onSaved: onSaved, onDeleted: onDeleted }) }, occurrence.occurrenceKey)), !isForm && matchNav && _jsx(EventMatchNav, { onPrevious: matchNav.onPrevious, onNext: matchNav.onNext, canPrevious: matchNav.canPrevious, canNext: matchNav.canNext, noun: matchNav.noun })
|
|
82
|
+
} })) : (_jsx(SlideIn, { from: matchNav?.slide?.occurrenceKey === occurrence.occurrenceKey ? matchNav.slide.from : undefined, children: _jsx(EventDetails, { occurrence: occurrence, isInvited: isInvited, myResponse: canRespond ? myAttendee.responseStatus : undefined, isOwnAddress: isOwnAddress, calendarName: calendars?.find((cal) => cal.uid === occurrence.folderUid)?.name, calendarColor: folderColors?.[occurrence.folderUid], canWrite: canWrite, onClose: onClose, onModify: () => setEditing(true), onSaved: onSaved, onDeleted: onDeleted }) }, occurrence.occurrenceKey)), !isForm && matchNav && _jsx(EventMatchNav, { onPrevious: matchNav.onPrevious, onNext: matchNav.onNext, canPrevious: matchNav.canPrevious, canNext: matchNav.canNext, noun: matchNav.noun }), _jsxs(Modal, { open: confirmingDiscard, onClose: () => setConfirmingDiscard(false), title: "Discard changes?", children: [_jsx("p", { className: "text-sm mb-5", children: "What you entered in this event has not been saved." }), _jsxs("div", { className: "flex gap-3 justify-end", children: [_jsx(Button, { type: "button", variant: "secondary", className: "!w-auto", onClick: () => setConfirmingDiscard(false), children: "Keep editing" }), _jsx(Button, { type: "button", className: "!w-auto !bg-none !bg-danger !border-danger hover:!bg-danger", onClick: () => {
|
|
83
|
+
setConfirmingDiscard(false);
|
|
84
|
+
leaveForm();
|
|
85
|
+
}, children: "Discard" })] })] })] }));
|
|
78
86
|
}
|
|
@@ -69,7 +69,7 @@ export default function FindATime({ c }) {
|
|
|
69
69
|
}
|
|
70
70
|
}, [startDay]);
|
|
71
71
|
// You, then each guest once (case-insensitively), at most as many as one look-up may ask about.
|
|
72
|
-
const rows = useMemo(() => {
|
|
72
|
+
const { rows, skipped } = useMemo(() => {
|
|
73
73
|
const seen = new Set([organizerAddress.toLowerCase()]);
|
|
74
74
|
const guests = [];
|
|
75
75
|
for (const attendee of values.attendees) {
|
|
@@ -79,7 +79,8 @@ export default function FindATime({ c }) {
|
|
|
79
79
|
guests.push({ address: attendee.address, name: attendee.displayName || attendee.address, you: false });
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
|
-
|
|
82
|
+
const everyone = [{ address: organizerAddress, name: "You", you: true }, ...guests];
|
|
83
|
+
return { rows: everyone.slice(0, FREE_BUSY_MAX_ADDRESSES), skipped: Math.max(0, everyone.length - FREE_BUSY_MAX_ADDRESSES) };
|
|
83
84
|
}, [values.attendees, organizerAddress]);
|
|
84
85
|
const guestCount = rows.length - 1;
|
|
85
86
|
const addresses = rows.map((row) => row.address);
|
|
@@ -168,7 +169,7 @@ export default function FindATime({ c }) {
|
|
|
168
169
|
.map((block) => `${block.tentative ? "Tentatively busy" : "Busy"} ${clockLabel(block.startMs, formZone, deviceZone)} to ${clockLabel(block.endMs, formZone, deviceZone)}`)
|
|
169
170
|
.join(", ");
|
|
170
171
|
}
|
|
171
|
-
return (_jsxs("div", { className: "flex flex-col gap-3 min-w-0", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx("button", { type: "button", "aria-label": "Previous day", onClick: () => setDayKey((key) => addDaysToKey(key, -1)), className: "w-8 h-8 flex items-center justify-center rounded-full text-text-muted hover:bg-surface-alt hover:text-text", children: "\u2039" }), _jsx("span", { className: "text-sm font-medium min-w-[9rem] text-center", children: dayLabel }), _jsx("button", { type: "button", "aria-label": "Next day", onClick: () => setDayKey((key) => addDaysToKey(key, 1)), className: "w-8 h-8 flex items-center justify-center rounded-full text-text-muted hover:bg-surface-alt hover:text-text", children: "\u203A" }), _jsx("span", { className: "text-xs text-text-muted", children: describeTimeZone(formZone, zoneDate(dayKey)) })] }), lookUp.status === "error" && (_jsxs("div", { role: "alert", className: "flex flex-wrap items-center gap-3 text-sm text-danger", children: [_jsx("span", { children: lookUp.message }), _jsx(Button, { type: "button", variant: "secondary", className: "!w-auto", onClick: () => setRetry((count) => count + 1), children: "Try again" })] })), _jsx("div", { ref: scroller, role: "group", "aria-label": "Guests' availability", "aria-busy": lookUp.status === "loading", className: "overflow-x-auto border border-border rounded-md", children: _jsxs("div", { className: "relative", style: { width: NAME_WIDTH + TIMELINE_WIDTH }, children: [_jsxs("div", { className: "flex bg-surface border-b border-border", style: { height: HEADER_HEIGHT }, children: [_jsx("div", { className: "sticky left-0 z-20 shrink-0 bg-surface border-r border-border", style: { width: NAME_WIDTH } }), HOURS.map((hour) => (_jsx("button", { type: "button", "aria-label": `Start at ${hourLabel(hour)}`, title: `Start at ${hourLabel(hour)}`, onClick: () => startAt(hour * 60), className: "shrink-0 text-[10px] text-text-muted hover:bg-surface-alt hover:text-text text-left pl-1", style: { width: HOUR_WIDTH }, children: hourLabel(hour) }, hour)))] }), rows.map((row) => {
|
|
172
|
+
return (_jsxs("div", { className: "flex flex-col gap-3 min-w-0", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx("button", { type: "button", "aria-label": "Previous day", onClick: () => setDayKey((key) => addDaysToKey(key, -1)), className: "w-8 h-8 flex items-center justify-center rounded-full text-text-muted hover:bg-surface-alt hover:text-text", children: "\u2039" }), _jsx("span", { className: "text-sm font-medium min-w-[9rem] text-center", children: dayLabel }), _jsx("button", { type: "button", "aria-label": "Next day", onClick: () => setDayKey((key) => addDaysToKey(key, 1)), className: "w-8 h-8 flex items-center justify-center rounded-full text-text-muted hover:bg-surface-alt hover:text-text", children: "\u203A" }), _jsx("span", { className: "text-xs text-text-muted", children: describeTimeZone(formZone, zoneDate(dayKey)) })] }), skipped > 0 && (_jsxs("p", { className: "text-xs text-text-muted", children: [skipped, " ", skipped === 1 ? "guest" : "guests", " beyond the first ", FREE_BUSY_MAX_ADDRESSES - 1, " ", skipped === 1 ? "isn’t" : "aren’t", " checked, and ", skipped === 1 ? "is" : "are", " not counted in who is free."] })), lookUp.status === "error" && (_jsxs("div", { role: "alert", className: "flex flex-wrap items-center gap-3 text-sm text-danger", children: [_jsx("span", { children: lookUp.message }), _jsx(Button, { type: "button", variant: "secondary", className: "!w-auto", onClick: () => setRetry((count) => count + 1), children: "Try again" })] })), _jsx("div", { ref: scroller, role: "group", "aria-label": "Guests' availability", "aria-busy": lookUp.status === "loading", className: "overflow-x-auto border border-border rounded-md", children: _jsxs("div", { className: "relative", style: { width: NAME_WIDTH + TIMELINE_WIDTH }, children: [_jsxs("div", { className: "flex bg-surface border-b border-border", style: { height: HEADER_HEIGHT }, children: [_jsx("div", { className: "sticky left-0 z-20 shrink-0 bg-surface border-r border-border", style: { width: NAME_WIDTH } }), HOURS.map((hour) => (_jsx("button", { type: "button", "aria-label": `Start at ${hourLabel(hour)}`, title: `Start at ${hourLabel(hour)}`, onClick: () => startAt(hour * 60), className: "shrink-0 text-[10px] text-text-muted hover:bg-surface-alt hover:text-text text-left pl-1", style: { width: HOUR_WIDTH }, children: hourLabel(hour) }, hour)))] }), rows.map((row) => {
|
|
172
173
|
const person = byAddress.get(row.address.toLowerCase());
|
|
173
174
|
const state = person && timeIsValid && covered ? availabilityDuring(person, eventStart, eventEnd) : undefined;
|
|
174
175
|
const hidden = person && person.status !== "available" ? person : undefined;
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { CalendarOccurrence } from "../../../../lib/calendar/recurrence.js";
|
|
3
|
-
/** The most days one event is listed on, so an event that runs for years does not become thousands of rows. */
|
|
3
|
+
/** The most days one event is listed on (counted from today), so an event that runs for years does not become thousands of rows. */
|
|
4
4
|
export declare const LIST_MAX_DAYS_PER_EVENT = 366;
|
|
5
|
+
/** The days (that have events) rendered at first, and how many more each time the end of the list is reached. */
|
|
6
|
+
export declare const LIST_INITIAL_DAYS = 60;
|
|
7
|
+
export declare const LIST_DAYS_PER_PAGE = 60;
|
|
5
8
|
/** A step through the list's months: `nonce` makes a second press in the same direction a new step. */
|
|
6
9
|
export interface ListStep {
|
|
7
10
|
direction: 1 | -1;
|
|
@@ -24,7 +27,10 @@ export interface ListViewProps {
|
|
|
24
27
|
* nothing at all says so). A multi-day event is listed on every day it covers, as the month grid shows it. Each row is a full-width button that
|
|
25
28
|
* opens the event's card, with the time, title, place (or organizer) and its calendar's colour.
|
|
26
29
|
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
30
|
+
* The days are grouped once per change of `occurrences` (or of the day), not per render, but only the first `LIST_INITIAL_DAYS` of them are rendered:
|
|
31
|
+
* more follow as the end of the list is scrolled to (a daily series alone is thousands of rows). A jump, a step or a search match that lies beyond
|
|
32
|
+
* the rendered days renders up to it first. The list scrolls itself to `focusDate` and, on a `step`, to the neighbouring month.
|
|
29
33
|
*/
|
|
30
|
-
|
|
34
|
+
declare function ListView({ occurrences, folderColors, onSelectEvent, focusDate, jumpNonce, step }: ListViewProps): React.JSX.Element;
|
|
35
|
+
declare const _default: React.MemoExoticComponent<typeof ListView>;
|
|
36
|
+
export default _default;
|