@antelopejs/dms 0.8.3 → 0.9.1
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/CHANGELOG.md +36 -0
- package/data/module-store/catalog.json +71 -0
- package/dist/config.d.ts +7 -0
- package/dist/config.js +8 -0
- package/dist/config.js.map +1 -1
- package/dist/implementations/dms/frontend-backend-proxies.d.ts +3 -0
- package/dist/implementations/dms/frontend-backend-proxies.js +39 -0
- package/dist/implementations/dms/frontend-backend-proxies.js.map +1 -0
- package/dist/implementations/dms/frontend-bootstrap.d.ts +1 -0
- package/dist/implementations/dms/frontend-bootstrap.js +13 -0
- package/dist/implementations/dms/frontend-bootstrap.js.map +1 -1
- package/dist/implementations/dms/frontend-modules.d.ts +6 -2
- package/dist/implementations/dms/frontend-modules.js +37 -8
- package/dist/implementations/dms/frontend-modules.js.map +1 -1
- package/dist/implementations/dms/frontend-ready.d.ts +8 -0
- package/dist/implementations/dms/frontend-ready.js +34 -0
- package/dist/implementations/dms/frontend-ready.js.map +1 -0
- package/dist/implementations/dms/manifest.d.ts +7 -0
- package/dist/implementations/dms/manifest.js +13 -0
- package/dist/implementations/dms/manifest.js.map +1 -1
- package/dist/implementations/dms/menu-categories.d.ts +16 -0
- package/dist/implementations/dms/menu-categories.js +30 -0
- package/dist/implementations/dms/menu-categories.js.map +1 -0
- package/dist/implementations/dms/nav-badges.d.ts +0 -8
- package/dist/implementations/dms/nav-badges.js +0 -16
- package/dist/implementations/dms/nav-badges.js.map +1 -1
- package/dist/implementations/dms/page.d.ts +13 -5
- package/dist/implementations/dms/page.js +72 -41
- package/dist/implementations/dms/page.js.map +1 -1
- package/dist/implementations/dms-html-render/index.js +5 -2
- package/dist/implementations/dms-html-render/index.js.map +1 -1
- package/dist/implementations/dms-notifications/index.d.ts +4 -3
- package/dist/implementations/dms-notifications/index.js +14 -6
- package/dist/implementations/dms-notifications/index.js.map +1 -1
- package/dist/pages/settings/settings-index.js +3 -2
- package/dist/pages/settings/settings-index.js.map +1 -1
- package/dist/pages/settings/users/invites.js +2 -2
- package/dist/pages/settings/users/invites.js.map +1 -1
- package/dist/pages/settings/users/member-change-notifications.js +17 -6
- package/dist/pages/settings/users/member-change-notifications.js.map +1 -1
- package/dist/pages/settings/users/member-invite-form.d.ts +3 -2
- package/dist/pages/settings/users/member-invite-form.js +7 -4
- package/dist/pages/settings/users/member-invite-form.js.map +1 -1
- package/dist/pages/settings/users/member-roles-field.js +2 -1
- package/dist/pages/settings/users/member-roles-field.js.map +1 -1
- package/dist/pages/settings/users/members.js +4 -1
- package/dist/pages/settings/users/members.js.map +1 -1
- package/dist/pages/settings/users/notifications.d.ts +6 -0
- package/dist/pages/settings/users/notifications.js +23 -15
- package/dist/pages/settings/users/notifications.js.map +1 -1
- package/dist/pages/settings/users/profile.js +1 -1
- package/dist/pages/settings/users/profile.js.map +1 -1
- package/dist/pages/settings/users/region-form.d.ts +1 -1
- package/dist/pages/settings/users/region-form.js +2 -1
- package/dist/pages/settings/users/region-form.js.map +1 -1
- package/dist/pages/settings/users/role-editor-store.d.ts +3 -1
- package/dist/pages/settings/users/role-editor-store.js +17 -4
- package/dist/pages/settings/users/role-editor-store.js.map +1 -1
- package/dist/pages/settings/users/roles.js +1 -0
- package/dist/pages/settings/users/roles.js.map +1 -1
- package/dist/realtime/registry.d.ts +1 -0
- package/dist/realtime/registry.js +5 -0
- package/dist/realtime/registry.js.map +1 -1
- package/dist/routes/index.d.ts +1 -0
- package/dist/routes/index.js +1 -0
- package/dist/routes/index.js.map +1 -1
- package/dist/routes/realtime.d.ts +5 -1
- package/dist/routes/realtime.js +16 -9
- package/dist/routes/realtime.js.map +1 -1
- package/dist/routes/store.d.ts +21 -0
- package/dist/routes/store.js +130 -0
- package/dist/routes/store.js.map +1 -0
- package/dist/store/activity.d.ts +2 -0
- package/dist/store/activity.js +12 -0
- package/dist/store/activity.js.map +1 -0
- package/dist/store/cache.d.ts +8 -0
- package/dist/store/cache.js +29 -0
- package/dist/store/cache.js.map +1 -0
- package/dist/store/catalog.d.ts +4 -0
- package/dist/store/catalog.js +69 -0
- package/dist/store/catalog.js.map +1 -0
- package/dist/store/compatibility.d.ts +3 -0
- package/dist/store/compatibility.js +45 -0
- package/dist/store/compatibility.js.map +1 -0
- package/dist/store/declarations.d.ts +18 -0
- package/dist/store/declarations.js +116 -0
- package/dist/store/declarations.js.map +1 -0
- package/dist/store/frontend-compatibility.d.ts +18 -0
- package/dist/store/frontend-compatibility.js +93 -0
- package/dist/store/frontend-compatibility.js.map +1 -0
- package/dist/store/interfaces.d.ts +6 -0
- package/dist/store/interfaces.js +126 -0
- package/dist/store/interfaces.js.map +1 -0
- package/dist/store/listing.d.ts +2 -0
- package/dist/store/listing.js +180 -0
- package/dist/store/listing.js.map +1 -0
- package/dist/store/operations.d.ts +8 -0
- package/dist/store/operations.js +254 -0
- package/dist/store/operations.js.map +1 -0
- package/dist/store/registry.d.ts +4 -0
- package/dist/store/registry.js +61 -0
- package/dist/store/registry.js.map +1 -0
- package/dist/store/requirements.d.ts +14 -0
- package/dist/store/requirements.js +81 -0
- package/dist/store/requirements.js.map +1 -0
- package/dist/store/runtime.d.ts +11 -0
- package/dist/store/runtime.js +78 -0
- package/dist/store/runtime.js.map +1 -0
- package/dist/store/tarball.d.ts +2 -0
- package/dist/store/tarball.js +80 -0
- package/dist/store/tarball.js.map +1 -0
- package/dist/store/types.d.ts +98 -0
- package/dist/store/types.js +3 -0
- package/dist/store/types.js.map +1 -0
- package/dist/test/integration/account-deletion.test.js +2 -2
- package/dist/test/integration/account-deletion.test.js.map +1 -1
- package/dist/test/integration/auth/auth-key-rotation.test.js +2 -1
- package/dist/test/integration/auth/auth-key-rotation.test.js.map +1 -1
- package/dist/test/integration/auth/current-password.test.js +2 -1
- package/dist/test/integration/auth/current-password.test.js.map +1 -1
- package/dist/test/integration/auth/session-revocation.test.js +2 -1
- package/dist/test/integration/auth/session-revocation.test.js.map +1 -1
- package/dist/test/integration/auth/sign-in-country.test.js +2 -1
- package/dist/test/integration/auth/sign-in-country.test.js.map +1 -1
- package/dist/test/integration/auth/two-factor-codes.test.js +2 -1
- package/dist/test/integration/auth/two-factor-codes.test.js.map +1 -1
- package/dist/test/integration/auth/two-factor-password.test.js +3 -2
- package/dist/test/integration/auth/two-factor-password.test.js.map +1 -1
- package/dist/test/integration/email-change.test.js +2 -1
- package/dist/test/integration/email-change.test.js.map +1 -1
- package/dist/test/integration/files/component-files.test.js +2 -2
- package/dist/test/integration/files/component-files.test.js.map +1 -1
- package/dist/test/integration/member-settings-access.test.js +8 -7
- package/dist/test/integration/member-settings-access.test.js.map +1 -1
- package/dist/test/integration/menu-access-events.test.d.ts +1 -0
- package/dist/test/integration/menu-access-events.test.js +148 -0
- package/dist/test/integration/menu-access-events.test.js.map +1 -0
- package/dist/test/integration/notification-inbox.test.js +4 -1
- package/dist/test/integration/notification-inbox.test.js.map +1 -1
- package/dist/test/integration/notification-locked-subjects.test.js +3 -2
- package/dist/test/integration/notification-locked-subjects.test.js.map +1 -1
- package/dist/test/integration/notification-unread-badge.test.js +51 -5
- package/dist/test/integration/notification-unread-badge.test.js.map +1 -1
- package/dist/test/integration/page-layout-route-guard.test.d.ts +1 -0
- package/dist/test/integration/page-layout-route-guard.test.js +56 -0
- package/dist/test/integration/page-layout-route-guard.test.js.map +1 -0
- package/dist/test/integration/permission-warnings.test.js +3 -2
- package/dist/test/integration/permission-warnings.test.js.map +1 -1
- package/dist/test/integration/removed-member-notifications.test.js +3 -2
- package/dist/test/integration/removed-member-notifications.test.js.map +1 -1
- package/dist/test/integration/role-delete-reassign.test.js +2 -1
- package/dist/test/integration/role-delete-reassign.test.js.map +1 -1
- package/dist/test/integration/role-permission-ancestors.test.js +2 -1
- package/dist/test/integration/role-permission-ancestors.test.js.map +1 -1
- package/dist/test/unit/frontend-bootstrap.test.js +13 -0
- package/dist/test/unit/frontend-bootstrap.test.js.map +1 -1
- package/dist/test/unit/implementations/dms/category-slug-registry.test.js +14 -5
- package/dist/test/unit/implementations/dms/category-slug-registry.test.js.map +1 -1
- package/dist/test/unit/implementations/dms/frontend-backend-proxies.test.d.ts +1 -0
- package/dist/test/unit/implementations/dms/frontend-backend-proxies.test.js +66 -0
- package/dist/test/unit/implementations/dms/frontend-backend-proxies.test.js.map +1 -0
- package/dist/test/unit/implementations/dms/manifest-strip.test.js +26 -0
- package/dist/test/unit/implementations/dms/manifest-strip.test.js.map +1 -1
- package/dist/test/unit/implementations/dms/nav-badges.test.js +23 -9
- package/dist/test/unit/implementations/dms/nav-badges.test.js.map +1 -1
- package/dist/test/unit/implementations/dms-html-render/generate-html.test.js +22 -4
- package/dist/test/unit/implementations/dms-html-render/generate-html.test.js.map +1 -1
- package/dist/test/unit/interfaces/dms/page-layout-query.test.js +12 -0
- package/dist/test/unit/interfaces/dms/page-layout-query.test.js.map +1 -1
- package/dist/test/unit/interfaces/dms/page-teardown.test.js +1 -1
- package/dist/test/unit/interfaces/dms/page-teardown.test.js.map +1 -1
- package/dist/test/unit/pages/settings/member-roles-field.test.js +1 -1
- package/dist/test/unit/pages/settings/member-roles-field.test.js.map +1 -1
- package/dist/test/unit/pages/settings/role-delete-confirm.test.js.map +1 -1
- package/dist/test/unit/realtime/menu-topics.test.d.ts +1 -0
- package/dist/test/unit/realtime/menu-topics.test.js +26 -0
- package/dist/test/unit/realtime/menu-topics.test.js.map +1 -0
- package/dist/test/unit/store/store.test.d.ts +1 -0
- package/dist/test/unit/store/store.test.js +127 -0
- package/dist/test/unit/store/store.test.js.map +1 -0
- package/dist/validation/store.schema.d.ts +39 -0
- package/dist/validation/store.schema.js +27 -0
- package/dist/validation/store.schema.js.map +1 -0
- package/frontend-vue/deferred-plugins.ts +3 -4
- package/frontend-vue/dms.email.ts +5 -1
- package/frontend-vue/dms.frontend.ts +15 -5
- package/frontend-vue/dms.server.ts +74 -0
- package/frontend-vue/layers/dms-auth/app/composables/useLogout.ts +1 -1
- package/frontend-vue/layers/dms-auth/app/composables/usePostLoginRedirect.ts +16 -3
- package/frontend-vue/layers/dms-auth/app/custom-pages/auth/2fa.vue +3 -1
- package/frontend-vue/layers/dms-auth/app/custom-pages/auth/backup.vue +3 -1
- package/frontend-vue/layers/dms-auth/app/custom-pages/auth/forgot-validation.vue +1 -1
- package/frontend-vue/layers/dms-auth/app/custom-pages/auth/oauth-complete.vue +26 -3
- package/frontend-vue/layers/dms-auth/app/custom-pages/auth/recover.vue +1 -1
- package/frontend-vue/layers/dms-auth/app/custom-pages/auth/signup.vue +22 -25
- package/frontend-vue/layers/dms-auth/app/custom-pages/auth/validate.vue +1 -1
- package/frontend-vue/layers/dms-auth/app/middleware/auth.ts +5 -7
- package/frontend-vue/layers/dms-auth/app/plugins/permissions-preload.ts +17 -0
- package/frontend-vue/layers/dms-auth/server/accounts.ts +135 -0
- package/frontend-vue/layers/dms-auth/server/actions.ts +251 -0
- package/frontend-vue/layers/dms-auth/server/oauth.ts +260 -0
- package/frontend-vue/layers/dms-auth/server/session.ts +158 -0
- package/frontend-vue/layers/dms-core/app/build/utils/asyncDataScope.ts +47 -0
- package/frontend-vue/layers/dms-core/app/build/utils/permission-preview.ts +6 -2
- package/frontend-vue/layers/dms-core/app/composables/auth/useApiData.ts +148 -0
- package/frontend-vue/layers/dms-core/app/composables/auth/useAuthFetch.ts +7 -20
- package/frontend-vue/layers/dms-core/app/composables/auth/useCurrentUser.ts +2 -2
- package/frontend-vue/layers/dms-core/app/composables/auth/useMultiAccount.ts +18 -5
- package/frontend-vue/layers/dms-core/app/composables/auth/usePermissions.ts +35 -6
- package/frontend-vue/layers/dms-core/app/composables/auth/useSessionRecovery.ts +1 -1
- package/frontend-vue/layers/dms-core/app/composables/period/compareResolvers.ts +20 -14
- package/frontend-vue/layers/dms-core/app/composables/period/periodClock.ts +121 -0
- package/frontend-vue/layers/dms-core/app/composables/period/periodRenderNow.ts +42 -0
- package/frontend-vue/layers/dms-core/app/composables/period/periodTimeZone.ts +31 -0
- package/frontend-vue/layers/dms-core/app/composables/period/presetResolvers.ts +48 -19
- package/frontend-vue/layers/dms-core/app/composables/period/usePeriod.ts +33 -4
- package/frontend-vue/layers/dms-core/app/composables/period/usePeriodScope.ts +78 -25
- package/frontend-vue/layers/dms-core/app/composables/realtime/usePageRealtime.ts +3 -0
- package/frontend-vue/layers/dms-core/app/composables/realtime/useUserRealtime.ts +36 -6
- package/frontend-vue/layers/dms-core/app/composables/user/useUserRegionalPreferences.ts +1 -1
- package/frontend-vue/layers/dms-core/app/plugins/async-data-scope.ts +26 -0
- package/frontend-vue/layers/dms-core/app/utils/pageRoutes.ts +22 -0
- package/frontend-vue/layers/dms-core/app/utils/regional.ts +36 -3
- package/frontend-vue/layers/dms-core/app/utils/trailingDeduper.ts +10 -1
- package/frontend-vue/layers/dms-core/shared/types/app-config.ts +48 -4
- package/frontend-vue/layers/dms-layout/app/app.config.ts +42 -23
- package/frontend-vue/layers/dms-layout/app/assets/css/main.css +72 -20
- package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardFrame.vue +9 -0
- package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardSidebar.vue +0 -3
- package/frontend-vue/layers/dms-layout/app/build/components/notification/NotificationPopover.vue +9 -3
- package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreDependencyPicker.vue +136 -0
- package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreRequirements.vue +98 -0
- package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreTile.vue +247 -29
- package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleTile.vue +19 -2
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/members/MemberRolePicker.vue +13 -12
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/notification/NotificationInboxDisplay.vue +17 -7
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/notification/NotificationPreferencesForm.vue +0 -1
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/profile/ProfilePersonalInfo.vue +36 -33
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/profile/ProfileSecuritySummary.vue +1 -2
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/role-types.ts +5 -4
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/security/SecuritySessions.vue +35 -28
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/security/SecurityStatus.vue +0 -1
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/security/SecurityTwoFactor.vue +1 -1
- package/frontend-vue/layers/dms-layout/app/build/composables/dev-reload/useDevReloadHolder.ts +7 -6
- package/frontend-vue/layers/dms-layout/app/build/composables/layout/sidePanelState.ts +1 -1
- package/frontend-vue/layers/dms-layout/app/build/composables/navigation/useVisitNavBadges.ts +26 -0
- package/frontend-vue/layers/dms-layout/app/build/composables/notification/useNotificationCatalog.ts +25 -23
- package/frontend-vue/layers/dms-layout/app/build/composables/notification/useNotificationPreferences.ts +44 -23
- package/frontend-vue/layers/dms-layout/app/build/composables/security/useSessionHandoff.ts +1 -1
- package/frontend-vue/layers/dms-layout/app/build/composables/settings/profile/preferencesSummary.ts +1 -1
- package/frontend-vue/layers/dms-layout/app/build/composables/settings/profile/useAccountData.ts +3 -2
- package/frontend-vue/layers/dms-layout/app/build/composables/settings/profile/useProfileAvatar.ts +1 -18
- package/frontend-vue/layers/dms-layout/app/build/composables/settings/profile/useProfilePreferences.ts +19 -23
- package/frontend-vue/layers/dms-layout/app/build/composables/settings/security/useSecurityFormat.ts +6 -3
- package/frontend-vue/layers/dms-layout/app/build/composables/settings/security/useSecurityOverview.ts +13 -18
- package/frontend-vue/layers/dms-layout/app/build/utils/module-store-catalog.ts +186 -120
- package/frontend-vue/layers/dms-layout/app/build/utils/module-store-install.ts +146 -0
- package/frontend-vue/layers/dms-layout/app/build/utils/modules-catalog.ts +8 -6
- package/frontend-vue/layers/dms-layout/app/build/utils/publicErrorMessage.ts +19 -0
- package/frontend-vue/layers/dms-layout/app/components/navigation/CollapsedMenuItem.vue +0 -11
- package/frontend-vue/layers/dms-layout/app/components/navigation/NavigationMenu.vue +3 -13
- package/frontend-vue/layers/dms-layout/app/composables/general/useSystemState.ts +7 -11
- package/frontend-vue/layers/dms-layout/app/composables/notification/useNotifications.ts +103 -22
- package/frontend-vue/layers/dms-layout/app/composables/page/useModuleStore.ts +109 -0
- package/frontend-vue/layers/dms-layout/app/composables/page/useModulesListing.ts +25 -102
- package/frontend-vue/layers/dms-layout/app/composables/page/usePageComponentPreload.ts +0 -10
- package/frontend-vue/layers/dms-layout/app/composables/page/useSiteLayout.ts +177 -96
- package/frontend-vue/layers/dms-layout/app/composables/profile/useUserLanguage.ts +3 -2
- package/frontend-vue/layers/dms-layout/app/custom-pages/modules.vue +341 -62
- package/frontend-vue/layers/dms-layout/app/emails/components/EmailLayout.vue +31 -11
- package/frontend-vue/layers/dms-layout/app/error.vue +5 -2
- package/frontend-vue/layers/dms-layout/app/middleware/module-routing.global.ts +1 -1
- package/frontend-vue/layers/dms-layout/app/middleware/page-leave-guard.global.ts +2 -2
- package/frontend-vue/layers/dms-layout/app/pages/[...slug].vue +12 -9
- package/frontend-vue/layers/dms-layout/app/plugins/dms-menu-sync.client.ts +31 -3
- package/frontend-vue/layers/dms-layout/app/plugins/favicon.ts +58 -0
- package/frontend-vue/layers/dms-layout/app/plugins/notification-stream.client.ts +15 -7
- package/frontend-vue/layers/dms-layout/app/plugins/profile-sync.client.ts +5 -1
- package/frontend-vue/layers/dms-layout/app/plugins/regional-preferences.ts +10 -4
- package/frontend-vue/layers/dms-layout/app/plugins/time-zone-cookie.client.ts +12 -0
- package/frontend-vue/layers/dms-layout/app/types/module-store.ts +134 -0
- package/frontend-vue/layers/dms-layout/app/types/page.ts +13 -0
- package/frontend-vue/layers/dms-layout/i18n/locales/layout-en-GB.json +107 -9
- package/frontend-vue/layers/dms-layout/i18n/locales/layout-fr-FR.json +107 -9
- package/frontend-vue/layers/dms-layout/server/html-render.ts +58 -0
- package/frontend-vue/layers/dms-layout/server/render-token.ts +95 -0
- package/frontend-vue/layers/dms-onboarding/app/composables/onboarding/useOnboarding.ts +25 -21
- package/frontend-vue/layers/dms-onboarding/app/middleware/onboarding.global.ts +1 -1
- package/frontend-vue/layers/dms-onboarding/app/plugins/onboarding-preload.ts +9 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/data-types/registerDefaults.ts +4 -1
- package/frontend-vue/layers/dms-ui/app/build/composables/files/useFileMetadata.ts +162 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/form/formRecordLoad.ts +23 -4
- package/frontend-vue/layers/dms-ui/app/build/composables/live/usePageDataRefresh.ts +101 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/navigation/useNavBadges.ts +33 -6
- package/frontend-vue/layers/dms-ui/app/build/composables/period/periodDisplay.ts +19 -16
- package/frontend-vue/layers/dms-ui/app/build/composables/period/periodQuery.ts +40 -20
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableFooter.ts +6 -8
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableListData.ts +42 -14
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableRows.ts +19 -8
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableViews.ts +1 -1
- package/frontend-vue/layers/dms-ui/app/components/chart/ChartCard.vue +5 -2
- package/frontend-vue/layers/dms-ui/app/components/flow-canvas/FlowCanvas.vue +9 -9
- package/frontend-vue/layers/dms-ui/app/components/form/Form.vue +20 -18
- package/frontend-vue/layers/dms-ui/app/components/form/components/Cascader.vue +2 -1
- package/frontend-vue/layers/dms-ui/app/components/form/components/File.vue +26 -132
- package/frontend-vue/layers/dms-ui/app/components/form/components/Image.vue +40 -105
- package/frontend-vue/layers/dms-ui/app/components/form/components/InputAddress.vue +2 -0
- package/frontend-vue/layers/dms-ui/app/components/form/components/Relation.vue +4 -4
- package/frontend-vue/layers/dms-ui/app/components/period/PeriodSelector.vue +9 -13
- package/frontend-vue/layers/dms-ui/app/components/table-view/CascaderPath.vue +2 -1
- package/frontend-vue/layers/dms-ui/app/components/table-view/FilePreview.vue +12 -24
- package/frontend-vue/layers/dms-ui/app/components/table-view/ImagePreview.vue +17 -29
- package/frontend-vue/layers/dms-ui/app/components/table-view/KanbanBoard.vue +7 -3
- package/frontend-vue/layers/dms-ui/app/components/table-view/TableView.vue +20 -8
- package/frontend-vue/layers/dms-ui/app/components/top-list/TopListCard.vue +8 -11
- package/frontend-vue/layers/dms-ui/app/composables/chart/useChartFetch.ts +146 -182
- package/frontend-vue/layers/dms-ui/app/composables/live/usePageLiveRefresh.ts +8 -2
- package/frontend-vue/layers/dms-ui/app/composables/useFileReadUrls.ts +11 -99
- package/frontend-vue/layers/dms-ui/app/utils/blockRefresh.ts +32 -3
- package/frontend-vue/package.json +3 -3
- package/frontend-vue/pnpm-lock.yaml +16 -22
- package/frontend-vue/server/private-options.ts +27 -0
- package/frontend-vue/tests/async-data-scope.test.ts +121 -0
- package/frontend-vue/tests/auth-middleware-error.test.ts +3 -3
- package/frontend-vue/tests/banner-block-fetch.test.ts +3 -0
- package/frontend-vue/tests/block-fetch-options-forwarding.test.ts +3 -0
- package/frontend-vue/tests/block-items-period-realtime.test.ts +3 -0
- package/frontend-vue/tests/block-not-found.test.ts +16 -2
- package/frontend-vue/tests/block-page-refresh.test.ts +3 -0
- package/frontend-vue/tests/block-route-params.test.ts +3 -0
- package/frontend-vue/tests/branding-defaults.test.ts +34 -0
- package/frontend-vue/tests/builtin-emails.test.ts +23 -2
- package/frontend-vue/tests/chart-live-pause.test.ts +4 -1
- package/frontend-vue/tests/chart-value-propagation.test.ts +4 -1
- package/frontend-vue/tests/composed-text-blocks.test.ts +3 -0
- package/frontend-vue/tests/email-layout.test.ts +66 -4
- package/frontend-vue/tests/error-page-message.test.ts +25 -2
- package/frontend-vue/tests/favicon.test.ts +131 -0
- package/frontend-vue/tests/file-fields-ssr.test.ts +145 -0
- package/frontend-vue/tests/file-read-urls.test.ts +145 -34
- package/frontend-vue/tests/file-unavailable.test.ts +5 -1
- package/frontend-vue/tests/fixtures/dataEngine.ts +44 -0
- package/frontend-vue/tests/form-load-error.test.ts +55 -1
- package/frontend-vue/tests/helpers/api-data-runtime.ts +77 -0
- package/frontend-vue/tests/image-hydration-ids.test.ts +21 -16
- package/frontend-vue/tests/image-reference-preservation.test.ts +59 -53
- package/frontend-vue/tests/input-number-adjust.test.ts +14 -0
- package/frontend-vue/tests/key-value-list-copy.test.ts +3 -0
- package/frontend-vue/tests/member-role-picker.test.ts +4 -2
- package/frontend-vue/tests/menu-sync-reconnect.test.ts +47 -0
- package/frontend-vue/tests/metadata-retry.test.ts +5 -1
- package/frontend-vue/tests/module-routing.test.ts +2 -5
- package/frontend-vue/tests/module-store-catalog.test.ts +270 -51
- package/frontend-vue/tests/modules-catalog.test.ts +17 -4
- package/frontend-vue/tests/modules-listing-transfer.test.ts +95 -0
- package/frontend-vue/tests/multi-account-validate.test.ts +23 -1
- package/frontend-vue/tests/nav-badge-tones.test.ts +66 -3
- package/frontend-vue/tests/notification-counts-from-writes.test.ts +223 -0
- package/frontend-vue/tests/notification-counts-transfer.test.ts +88 -0
- package/frontend-vue/tests/notification-inbox-display.test.ts +57 -5
- package/frontend-vue/tests/notification-popover-errors.test.ts +21 -1
- package/frontend-vue/tests/oauth-complete-handoff.test.ts +209 -0
- package/frontend-vue/tests/onboarding-middleware.test.ts +1 -1
- package/frontend-vue/tests/onboarding-transfer.test.ts +106 -0
- package/frontend-vue/tests/page-data-refresh.test.ts +141 -0
- package/frontend-vue/tests/page-fill-height.test.ts +16 -3
- package/frontend-vue/tests/page-no-visible-blocks.test.ts +12 -2
- package/frontend-vue/tests/page-realtime-remount.test.ts +1 -1
- package/frontend-vue/tests/page-record-label.test.ts +0 -7
- package/frontend-vue/tests/page-validation-redirect.test.ts +12 -2
- package/frontend-vue/tests/period-hydration-now.test.ts +185 -0
- package/frontend-vue/tests/period-live-pause.test.ts +3 -0
- package/frontend-vue/tests/period-reader-zone.test.ts +137 -0
- package/frontend-vue/tests/period-scope-first-fetch.test.ts +17 -1
- package/frontend-vue/tests/period-ssr.test.ts +195 -0
- package/frontend-vue/tests/permission-preview-partial.test.ts +0 -2
- package/frontend-vue/tests/permissions-transfer.test.ts +194 -0
- package/frontend-vue/tests/post-login-redirect.test.ts +10 -5
- package/frontend-vue/tests/profile-personal-info-ssr.test.ts +150 -0
- package/frontend-vue/tests/profile-preferences-summary.test.ts +15 -11
- package/frontend-vue/tests/realtime-reconnect.test.ts +22 -3
- package/frontend-vue/tests/roles-name-errors.test.ts +1 -1
- package/frontend-vue/tests/row-side-panel.test.ts +1 -1
- package/frontend-vue/tests/security-sessions-load-error.test.ts +3 -1
- package/frontend-vue/tests/server-routes-vs-pages.test.ts +26 -0
- package/frontend-vue/tests/server-routes.test.ts +582 -0
- package/frontend-vue/tests/session-handoff.test.ts +1 -1
- package/frontend-vue/tests/settings-reads-ssr.test.ts +232 -0
- package/frontend-vue/tests/side-panels.test.ts +8 -1
- package/frontend-vue/tests/sign-in-exit-history.test.ts +219 -0
- package/frontend-vue/tests/signup-invitation-link.test.ts +56 -8
- package/frontend-vue/tests/site-layout-categories.test.ts +42 -0
- package/frontend-vue/tests/site-layout-hydration.test.ts +3 -3
- package/frontend-vue/tests/site-layout-rendered.test.ts +103 -0
- package/frontend-vue/tests/site-layout-resync.test.ts +87 -0
- package/frontend-vue/tests/ssr-guard-preload.test.ts +188 -0
- package/frontend-vue/tests/table-cell-tones.test.ts +36 -1
- package/frontend-vue/tests/table-data-ssr.test.ts +195 -0
- package/frontend-vue/tests/table-list-data-requests.test.ts +154 -0
- package/frontend-vue/tests/table-retry-loading.test.ts +37 -0
- package/frontend-vue/tests/table-source-navigation.test.ts +0 -1
- package/frontend-vue/tests/table-source-period-scope.test.ts +0 -1
- package/frontend-vue/tests/table-view-views.test.ts +1 -1
- package/frontend-vue/tests/top-list-card-actions.test.ts +3 -0
- package/frontend-vue/tests/top-list-card-period-badge-ssr.test.ts +212 -0
- package/frontend-vue/tests/top-list-card-period-badge.test.ts +79 -0
- package/frontend-vue/tests/top-list-card-value-suffix.test.ts +3 -0
- package/frontend-vue/tests/trailing-deduper.test.ts +22 -0
- package/frontend-vue/tests/use-api-data-update.test.ts +119 -0
- package/frontend-vue/tests/use-api-data.test.ts +410 -0
- package/frontend-vue/tests/user-language.test.ts +8 -5
- package/frontend-vue/tests/widget-label-i18n.test.ts +4 -1
- package/frontend-vue/vitest.config.ts +6 -0
- package/package.json +2 -2
- package/skills/dms/SKILL.md +2 -1
- package/skills/dms-module/SKILL.md +1 -1
- package/skills/dms-pages/SKILL.md +4 -1
- package/frontend-vue/layers/dms-core/app/build/composables/page/usePageRecordLabel.ts +0 -9
- package/frontend-vue/layers/dms-layout/app/composables/page/usePrefetch.ts +0 -21
- package/frontend-vue/layers/dms-ui/app/build/composables/form/useControlError.ts +0 -10
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useServerRenderedAsyncData.ts +0 -23
- package/frontend-vue/tests/control-error-path.test.ts +0 -11
- package/frontend-vue/tests/modules-listing-per-request.test.ts +0 -104
- package/frontend-vue/tests/page-layout-background-refresh.test.ts +0 -321
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/** The part of the session the data scope follows. */
|
|
2
|
+
export interface ScopedSession {
|
|
3
|
+
accountId?: string;
|
|
4
|
+
activeTenantId?: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
const HASH_SEED_LOW = 0xdeadbeef;
|
|
8
|
+
const HASH_SEED_HIGH = 0x41c6ce57;
|
|
9
|
+
const HASH_MULTIPLIER_LOW = 2_654_435_761;
|
|
10
|
+
const HASH_MULTIPLIER_HIGH = 1_597_334_677;
|
|
11
|
+
const HASH_MIX_LOW = 2_246_822_507;
|
|
12
|
+
const HASH_MIX_HIGH = 3_266_489_909;
|
|
13
|
+
const HIGH_WORD = 2 ** 32;
|
|
14
|
+
const HIGH_BITS_MASK = 2_097_151;
|
|
15
|
+
const RADIX = 36;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* A 53-bit digest of `text` (cyrb53): the same in a server render and in the
|
|
19
|
+
* browser, and synchronous, so the scope is set before the page renders.
|
|
20
|
+
* Not a secret: it only keeps the identifiers out of the page.
|
|
21
|
+
*/
|
|
22
|
+
function digest(text: string): string {
|
|
23
|
+
let low = HASH_SEED_LOW;
|
|
24
|
+
let high = HASH_SEED_HIGH;
|
|
25
|
+
for (let index = 0; index < text.length; index++) {
|
|
26
|
+
const code = text.charCodeAt(index);
|
|
27
|
+
low = Math.imul(low ^ code, HASH_MULTIPLIER_LOW);
|
|
28
|
+
high = Math.imul(high ^ code, HASH_MULTIPLIER_HIGH);
|
|
29
|
+
}
|
|
30
|
+
low = Math.imul(low ^ (low >>> 16), HASH_MIX_LOW);
|
|
31
|
+
low ^= Math.imul(high ^ (high >>> 13), HASH_MIX_HIGH);
|
|
32
|
+
high = Math.imul(high ^ (high >>> 16), HASH_MIX_LOW);
|
|
33
|
+
high ^= Math.imul(low ^ (low >>> 13), HASH_MIX_HIGH);
|
|
34
|
+
return (HIGH_WORD * (HIGH_BITS_MASK & high) + (low >>> 0)).toString(RADIX);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* The scope the async data of a session is kept under: a digest of its
|
|
39
|
+
* account and workspace, or none when signed out. The same key read by
|
|
40
|
+
* another account, or in another workspace, is another entry.
|
|
41
|
+
*/
|
|
42
|
+
export function asyncDataScopeOf(
|
|
43
|
+
session: ScopedSession | null | undefined,
|
|
44
|
+
): string | undefined {
|
|
45
|
+
if (!session?.accountId) return undefined;
|
|
46
|
+
return digest(`${session.accountId}\n${session.activeTenantId ?? ""}`);
|
|
47
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { pageRoutePath } from "#dms-core/app/utils/pageRoutes";
|
|
1
2
|
// "Preview as role" (roles editor): the shapes the preview endpoint answers
|
|
2
3
|
// with, how a preview session travels from the editor tab to the preview tab,
|
|
3
4
|
// and which requests a preview tab may still send. Pure: no Vue, no window.
|
|
@@ -9,7 +10,7 @@ export type PermissionPreviewBlock =
|
|
|
9
10
|
|
|
10
11
|
export type PermissionPreviewState = PermissionPreviewBlock["state"];
|
|
11
12
|
|
|
12
|
-
/** Answer of `POST /settings/workspace/roles/preview`. */
|
|
13
|
+
/** Answer of `POST /dms/pages/settings/workspace/roles/preview`. */
|
|
13
14
|
export interface PermissionPreviewResult {
|
|
14
15
|
page: { fullId: string; displayName: string; hidden: boolean } | null;
|
|
15
16
|
/** Blocks keyed by layout path (component key, then child ids). */
|
|
@@ -44,7 +45,10 @@ export interface PermissionPreviewSession {
|
|
|
44
45
|
}
|
|
45
46
|
|
|
46
47
|
/** Endpoint the preview tab asks; a POST, so the request guard lets it through. */
|
|
47
|
-
export const PERMISSION_PREVIEW_ENDPOINT =
|
|
48
|
+
export const PERMISSION_PREVIEW_ENDPOINT = pageRoutePath(
|
|
49
|
+
"/settings/workspace/roles",
|
|
50
|
+
"/preview",
|
|
51
|
+
);
|
|
48
52
|
/** Query parameter that hands a preview session to a new tab. */
|
|
49
53
|
export const PERMISSION_PREVIEW_QUERY_KEY = "dms-preview";
|
|
50
54
|
/** `localStorage` key prefix of the sessions the editor shares with its preview tabs. */
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
DmsAsyncDataKey,
|
|
3
|
+
DmsAsyncDataResult,
|
|
4
|
+
DmsPagedDataResult,
|
|
5
|
+
UseAsyncDataOptions,
|
|
6
|
+
UseDmsPagedDataOptions,
|
|
7
|
+
UseFetchOptions,
|
|
8
|
+
} from "#dms/frontend-module";
|
|
9
|
+
import type { MaybeRefOrGetter } from "vue";
|
|
10
|
+
|
|
11
|
+
/** The authenticated fetch `useAuthFetch` returns. */
|
|
12
|
+
type AuthFetch = ReturnType<typeof useAuthFetch>["$authFetch"];
|
|
13
|
+
|
|
14
|
+
/** What a handler loading API data receives. */
|
|
15
|
+
export interface ApiDataContext {
|
|
16
|
+
/** `useAuthFetch`'s fetch: the session retry, the language and realtime headers. */
|
|
17
|
+
$authFetch: AuthFetch;
|
|
18
|
+
/** Aborted once the result is no longer wanted: pass it to each request. */
|
|
19
|
+
signal: AbortSignal;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type ApiDataHandler<T> = (context: ApiDataContext) => Promise<T>;
|
|
23
|
+
|
|
24
|
+
/** `useDmsFetch`'s options, the fetch function aside: it is `$authFetch`. */
|
|
25
|
+
export type UseApiDataOptions<T> = Omit<UseFetchOptions<T>, "$fetch">;
|
|
26
|
+
|
|
27
|
+
/** A backend URL to read, or nothing to read while it is null or undefined. */
|
|
28
|
+
export type ApiDataRequest = MaybeRefOrGetter<string | null | undefined>;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* What `useApiData` returns: `data`, `status`, `pending`, `error`, the `key`
|
|
32
|
+
* the data is loaded under, `placeholder` (a reactive request still showing
|
|
33
|
+
* the previous answer), `execute()`, `refresh()` and `clear()`. Usable at
|
|
34
|
+
* once, or awaited until the data is there.
|
|
35
|
+
*/
|
|
36
|
+
export type ApiDataResult<T> = DmsAsyncDataResult<T>;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Reads backend data for a component: the one way a module loads what it
|
|
40
|
+
* renders. A server render waits for it and hands it to the browser with the
|
|
41
|
+
* page, which then does not ask again; the callers of one request share it.
|
|
42
|
+
* Data read before shows at once and is read again in the background on each
|
|
43
|
+
* mount (stale while revalidate).
|
|
44
|
+
*
|
|
45
|
+
* - `useApiData(url, options)` reads one URL, like Nuxt's `useFetch`. The URL,
|
|
46
|
+
* `method`, `query`, `params` and `body` may be refs or getters, read again
|
|
47
|
+
* for each request: the data follows them. A URL that is null or undefined
|
|
48
|
+
* reads nothing until it has a value.
|
|
49
|
+
* - `useApiData(key, handler, options)` loads what the handler returns, like
|
|
50
|
+
* Nuxt's `useAsyncData`: for data made of several requests. The handler
|
|
51
|
+
* receives `$authFetch` and the `signal` to pass to each request.
|
|
52
|
+
*
|
|
53
|
+
* Requests go through `$authFetch` (session retry, language and realtime
|
|
54
|
+
* headers), and the data is kept per account and workspace: a switch of
|
|
55
|
+
* either reads it again. The options are the renderer's:
|
|
56
|
+
*
|
|
57
|
+
* - `lazy` spares a client navigation the wait; `server: false` leaves the
|
|
58
|
+
* data to the browser (for what only the browser knows).
|
|
59
|
+
* - `immediate: false` waits for `execute()`: a dialog or a drawer reads
|
|
60
|
+
* what it shows once it opens.
|
|
61
|
+
* - `refreshInterval: ms` reads again on an interval while the caller shows
|
|
62
|
+
* the data and the page is visible: polling. A ref or a getter changes it,
|
|
63
|
+
* and `0` or `null` stops it; a call from a plugin polls for as long as
|
|
64
|
+
* the application runs.
|
|
65
|
+
* - The page's refreshes (`refreshPageBlocks()`) and the events of its
|
|
66
|
+
* realtime topics read the data again; `refreshOnPageRefresh: false` opts
|
|
67
|
+
* out, for data with refresh triggers of its own.
|
|
68
|
+
* - `data` is a shallow ref: replace the value to change it, or pass
|
|
69
|
+
* `deep: true`.
|
|
70
|
+
*
|
|
71
|
+
* A write stays an `$authFetch` call; once it is done, the page's refresh
|
|
72
|
+
* reads what changed, or `useDmsData(key)` puts its answer in the data in
|
|
73
|
+
* place (the URL form names its key with `key`).
|
|
74
|
+
*/
|
|
75
|
+
export function useApiData<T>(
|
|
76
|
+
url: ApiDataRequest,
|
|
77
|
+
options?: UseApiDataOptions<T>,
|
|
78
|
+
): ApiDataResult<T>;
|
|
79
|
+
export function useApiData<T>(
|
|
80
|
+
key: MaybeRefOrGetter<DmsAsyncDataKey>,
|
|
81
|
+
handler: ApiDataHandler<T>,
|
|
82
|
+
options?: UseAsyncDataOptions<T>,
|
|
83
|
+
): ApiDataResult<T>;
|
|
84
|
+
export function useApiData<T>(
|
|
85
|
+
source: ApiDataRequest,
|
|
86
|
+
handlerOrOptions?: ApiDataHandler<T> | UseApiDataOptions<T>,
|
|
87
|
+
options?: UseAsyncDataOptions<T>,
|
|
88
|
+
): ApiDataResult<T> {
|
|
89
|
+
const { $authFetch } = useAuthFetch();
|
|
90
|
+
if (typeof handlerOrOptions === "function")
|
|
91
|
+
return useDmsAsyncData<T>(
|
|
92
|
+
source,
|
|
93
|
+
({ signal }) => handlerOrOptions({ $authFetch, signal }),
|
|
94
|
+
options,
|
|
95
|
+
);
|
|
96
|
+
return useDmsFetch<T>(source, {
|
|
97
|
+
...handlerOrOptions,
|
|
98
|
+
$fetch: $authFetch as UseFetchOptions<T>["$fetch"],
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** What a handler reading one page of API data receives. */
|
|
103
|
+
export interface ApiPagedDataContext<TCursor> extends ApiDataContext {
|
|
104
|
+
/** The page to read: `initialCursor` (none by default) for the first one. */
|
|
105
|
+
cursor: TCursor | undefined;
|
|
106
|
+
/** Its position, from `0`. */
|
|
107
|
+
index: number;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export type ApiPagedDataHandler<TPage, TCursor> = (
|
|
111
|
+
context: ApiPagedDataContext<TCursor>,
|
|
112
|
+
) => Promise<TPage>;
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* What `useApiPagedData` returns: `useApiData`'s result over the pages
|
|
116
|
+
* loaded, with `pages`, `hasMore`, `loadingMore` and `loadMore()`.
|
|
117
|
+
*/
|
|
118
|
+
export type ApiPagedDataResult<TPage, TCursor> = DmsPagedDataResult<
|
|
119
|
+
TPage,
|
|
120
|
+
TCursor
|
|
121
|
+
>;
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Reads a backend list page by page under one key: a "load more" button, an
|
|
125
|
+
* infinite list. The handler reads the page at `cursor` through `$authFetch`;
|
|
126
|
+
* `nextCursor(page, pages)` gives the next page's cursor, or `null` at the
|
|
127
|
+
* end. A server render shows the first page, `loadMore()` appends the next
|
|
128
|
+
* one, and a revalidation (a mount, the page's refreshes, the polling)
|
|
129
|
+
* reads every page loaded so far again. Put what filters the list in the
|
|
130
|
+
* key (a ref or a getter for it): a new key starts from its first page.
|
|
131
|
+
*
|
|
132
|
+
* @param key The key, or a ref or getter for it
|
|
133
|
+
* @param handler Reads one page
|
|
134
|
+
* @param options `nextCursor`, `initialCursor` and `useApiData`'s options
|
|
135
|
+
*/
|
|
136
|
+
export function useApiPagedData<TPage, TCursor = unknown>(
|
|
137
|
+
key: MaybeRefOrGetter<DmsAsyncDataKey>,
|
|
138
|
+
handler: ApiPagedDataHandler<TPage, TCursor>,
|
|
139
|
+
options: UseDmsPagedDataOptions<TPage, TCursor>,
|
|
140
|
+
): ApiPagedDataResult<TPage, TCursor> {
|
|
141
|
+
const { $authFetch } = useAuthFetch();
|
|
142
|
+
return useDmsPagedData<TPage, TCursor>(
|
|
143
|
+
key,
|
|
144
|
+
({ cursor, index, signal }) =>
|
|
145
|
+
handler({ $authFetch, cursor, index, signal }),
|
|
146
|
+
options,
|
|
147
|
+
);
|
|
148
|
+
}
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import type { UseFetchOptions } from "#dms/frontend-module";
|
|
2
|
-
|
|
3
1
|
export const CONTENT_LANGUAGE_HEADER = "x-content-language";
|
|
4
2
|
|
|
5
3
|
const RETRY_HEADER = "x-auth-retry";
|
|
@@ -38,7 +36,7 @@ function isUnauthorized(error: unknown): boolean {
|
|
|
38
36
|
}
|
|
39
37
|
|
|
40
38
|
export const useAuthFetch = () => {
|
|
41
|
-
const { loggedIn: isLoggedIn } =
|
|
39
|
+
const { loggedIn: isLoggedIn } = useDmsUserSession();
|
|
42
40
|
const { refreshSession, reconcileSession, redirectToAuth } =
|
|
43
41
|
useSessionRecovery();
|
|
44
42
|
const config = useDmsRuntimeConfig();
|
|
@@ -51,10 +49,12 @@ export const useAuthFetch = () => {
|
|
|
51
49
|
|
|
52
50
|
const baseFetch = $fetch.create({
|
|
53
51
|
baseURL: config.public.dms.baseURL,
|
|
54
|
-
headers: {
|
|
55
|
-
[CONTENT_LANGUAGE_HEADER]: dmsApp.$i18n.locale.value,
|
|
56
|
-
},
|
|
57
52
|
onRequest({ options }) {
|
|
53
|
+
// Read as each request leaves: a `$authFetch` kept across a language
|
|
54
|
+
// change asks in the new language. A header the caller sets wins.
|
|
55
|
+
if (!options.headers.has(CONTENT_LANGUAGE_HEADER)) {
|
|
56
|
+
options.headers.set(CONTENT_LANGUAGE_HEADER, dmsApp.$i18n.locale.value);
|
|
57
|
+
}
|
|
58
58
|
if (realtimeSessionId.value) {
|
|
59
59
|
options.headers.set(REALTIME_SESSION_HEADER, realtimeSessionId.value);
|
|
60
60
|
}
|
|
@@ -117,18 +117,5 @@ export const useAuthFetch = () => {
|
|
|
117
117
|
baseFetch.raw(request, options),
|
|
118
118
|
) as AuthFetch["raw"];
|
|
119
119
|
|
|
120
|
-
|
|
121
|
-
url: string | (() => string),
|
|
122
|
-
options?: UseFetchOptions<T>,
|
|
123
|
-
) {
|
|
124
|
-
return useDmsFetch(url, {
|
|
125
|
-
...options,
|
|
126
|
-
$fetch: $authFetch,
|
|
127
|
-
});
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
return {
|
|
131
|
-
$authFetch,
|
|
132
|
-
useFetchAuth,
|
|
133
|
-
};
|
|
120
|
+
return { $authFetch };
|
|
134
121
|
};
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Global reactive access to the signed-in user's identity (name, email, ...).
|
|
3
3
|
*
|
|
4
|
-
* `user` is the shared session state from `
|
|
4
|
+
* `user` is the shared session state from `useDmsUserSession`, so every consumer
|
|
5
5
|
* (header menu, switch-account page, ...) re-renders as soon as it changes.
|
|
6
6
|
* `refresh` re-fetches the session through the frontend server session hook
|
|
7
7
|
* pulls a fresh `/api/auth/me`) and syncs the stored multi-account entry, so a
|
|
8
8
|
* profile edit propagates immediately without re-logging in.
|
|
9
9
|
*/
|
|
10
10
|
export function useCurrentUser() {
|
|
11
|
-
const { user, fetch: refreshSession } =
|
|
11
|
+
const { user, fetch: refreshSession } = useDmsUserSession<User>();
|
|
12
12
|
const { syncActiveAccount } = useMultiAccount();
|
|
13
13
|
|
|
14
14
|
async function refresh(): Promise<void> {
|
|
@@ -116,13 +116,23 @@ async function requestAccountSwitch(account: StoredAccount) {
|
|
|
116
116
|
account.isExpired = false;
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
-
|
|
119
|
+
/**
|
|
120
|
+
* Moves what the browser shows to the session it switched to. The session
|
|
121
|
+
* read again moves the data scope (the previous account's data is dropped)
|
|
122
|
+
* and drops the prefetched pages; the site layout and the page come back
|
|
123
|
+
* through Inertia, whose once prop is keyed on the account and workspace.
|
|
124
|
+
*
|
|
125
|
+
* @internal
|
|
126
|
+
*/
|
|
127
|
+
export async function refreshSessionSurfaces(
|
|
128
|
+
refreshUser: () => Promise<unknown>,
|
|
129
|
+
): Promise<void> {
|
|
120
130
|
await refreshUser();
|
|
121
131
|
|
|
122
|
-
const {
|
|
132
|
+
const { resync: reloadSiteLayout } = useSiteLayout();
|
|
123
133
|
const { clear: clearPermissions } = usePermissions();
|
|
124
134
|
clearPermissions();
|
|
125
|
-
await
|
|
135
|
+
await reloadSiteLayout();
|
|
126
136
|
}
|
|
127
137
|
|
|
128
138
|
export async function validateAccount(account: StoredAccount) {
|
|
@@ -187,8 +197,11 @@ function createValidateAllAccounts(
|
|
|
187
197
|
|
|
188
198
|
export function useMultiAccount() {
|
|
189
199
|
const accounts = useAccountsStorage();
|
|
190
|
-
const {
|
|
191
|
-
|
|
200
|
+
const {
|
|
201
|
+
user,
|
|
202
|
+
session,
|
|
203
|
+
fetch: refreshUser,
|
|
204
|
+
} = useDmsUserSession<User, BrowserSession>();
|
|
192
205
|
|
|
193
206
|
const getActiveAccount = (): StoredAccount | undefined =>
|
|
194
207
|
buildActiveAccount(
|
|
@@ -9,11 +9,29 @@ interface PermissionsState {
|
|
|
9
9
|
fetchGeneration: Ref<number>;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
+
// The set a server render loads (the `auth` middleware preloads it before the
|
|
13
|
+
// page renders) is the one the page renders with: the browser starts from it
|
|
14
|
+
// rather than asking again, and its age keeps counting from the server's load.
|
|
15
|
+
// The generation only guards the runs in flight of one side.
|
|
16
|
+
const TRANSFERRED = { transfer: true };
|
|
17
|
+
|
|
12
18
|
function usePermissionsState(): PermissionsState {
|
|
13
19
|
return {
|
|
14
|
-
permissions: useDmsState<Set<string>>(
|
|
15
|
-
|
|
16
|
-
|
|
20
|
+
permissions: useDmsState<Set<string>>(
|
|
21
|
+
"dms-user-permissions",
|
|
22
|
+
() => new Set(),
|
|
23
|
+
TRANSFERRED,
|
|
24
|
+
),
|
|
25
|
+
isLoaded: useDmsState<boolean>(
|
|
26
|
+
"dms-permissions-loaded",
|
|
27
|
+
() => false,
|
|
28
|
+
TRANSFERRED,
|
|
29
|
+
),
|
|
30
|
+
lastFetchTime: useDmsState<number>(
|
|
31
|
+
"dms-permissions-lastFetch",
|
|
32
|
+
() => 0,
|
|
33
|
+
TRANSFERRED,
|
|
34
|
+
),
|
|
17
35
|
fetchGeneration: useDmsState<number>("dms-permissions-gen", () => 0),
|
|
18
36
|
};
|
|
19
37
|
}
|
|
@@ -30,15 +48,25 @@ function createFetchPermissions(state: PermissionsState) {
|
|
|
30
48
|
};
|
|
31
49
|
|
|
32
50
|
// A refetch asked while one is in flight gets its own pass instead of the
|
|
33
|
-
// older response, so an invalidation is never swallowed. Scoped to the DMS
|
|
51
|
+
// older response, so an invalidation is never swallowed. Scoped to the DMS
|
|
34
52
|
// app, so concurrent server-rendered requests never share a pass.
|
|
35
53
|
const deduper = useTrailingDeduper("permissions");
|
|
36
|
-
|
|
54
|
+
const fetchPermissions = (): Promise<void> => deduper.run(runFetch);
|
|
55
|
+
// A caller that only needs a set has nothing to invalidate: it joins the
|
|
56
|
+
// load in flight instead of queueing a second one.
|
|
57
|
+
const loadPermissions = async (): Promise<void> => {
|
|
58
|
+
if (state.isLoaded.value) return;
|
|
59
|
+
await deduper.join(runFetch);
|
|
60
|
+
// The run it joined was started before a `clear()` (another account):
|
|
61
|
+
// its answer was dropped, and this caller still needs one.
|
|
62
|
+
if (!state.isLoaded.value) await deduper.join(runFetch);
|
|
63
|
+
};
|
|
64
|
+
return { fetchPermissions, loadPermissions };
|
|
37
65
|
}
|
|
38
66
|
|
|
39
67
|
export function usePermissions() {
|
|
40
68
|
const state = usePermissionsState();
|
|
41
|
-
const fetchPermissions = createFetchPermissions(state);
|
|
69
|
+
const { fetchPermissions, loadPermissions } = createFetchPermissions(state);
|
|
42
70
|
|
|
43
71
|
async function refreshIfStale() {
|
|
44
72
|
const elapsed = Date.now() - state.lastFetchTime.value;
|
|
@@ -66,6 +94,7 @@ export function usePermissions() {
|
|
|
66
94
|
permissions: state.permissions,
|
|
67
95
|
isLoaded: state.isLoaded,
|
|
68
96
|
fetchPermissions,
|
|
97
|
+
loadPermissions,
|
|
69
98
|
refreshIfStale,
|
|
70
99
|
hasPermission,
|
|
71
100
|
clear,
|
|
@@ -5,7 +5,7 @@ const IN_FLIGHT_REFRESH_KEY = "__dmsAuthInFlightRefresh";
|
|
|
5
5
|
|
|
6
6
|
export const useSessionRecovery = () => {
|
|
7
7
|
const dmsApp = useDmsApp();
|
|
8
|
-
const { loggedIn, fetch: fetchSession } =
|
|
8
|
+
const { loggedIn, fetch: fetchSession } = useDmsUserSession();
|
|
9
9
|
// useDmsRoute() must not be called here: this composable runs inside route
|
|
10
10
|
// middleware (via useAuthFetch), where the DMS runtime warns that useDmsRoute is
|
|
11
11
|
// misleading. Resolve the route lazily from the router instead.
|
|
@@ -1,16 +1,25 @@
|
|
|
1
|
+
import { readerClock } from "./periodClock";
|
|
1
2
|
import type { PeriodComparison, PeriodRange } from "./types";
|
|
2
3
|
|
|
3
4
|
const NON_OVERLAP_GAP_MS = 1;
|
|
4
5
|
|
|
5
6
|
const MS_PER_DAY = 24 * 60 * 60 * 1000;
|
|
6
7
|
|
|
8
|
+
// In the reader's calendar (`readerClock`), as the range was resolved.
|
|
7
9
|
function isLocalMidnight(date: Date): boolean {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
const { hours, minutes, seconds, milliseconds } = readerClock().wall(date);
|
|
11
|
+
return hours === 0 && minutes === 0 && seconds === 0 && milliseconds === 0;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** `date` moved by whole calendar days and years, its time of day kept. */
|
|
15
|
+
function shiftCalendar(date: Date, days: number, years: number): Date {
|
|
16
|
+
const clock = readerClock();
|
|
17
|
+
const wall = clock.wall(date);
|
|
18
|
+
return clock.instant({
|
|
19
|
+
...wall,
|
|
20
|
+
year: wall.year + years,
|
|
21
|
+
day: wall.day + days,
|
|
22
|
+
});
|
|
14
23
|
}
|
|
15
24
|
|
|
16
25
|
/** A range of whole local days: midnight to the last millisecond of a day. */
|
|
@@ -27,10 +36,8 @@ function shiftPreviousPeriod(range: PeriodRange): PeriodRange {
|
|
|
27
36
|
if (isWholeDayRange(range)) {
|
|
28
37
|
const end = range.to.getTime() + NON_OVERLAP_GAP_MS;
|
|
29
38
|
const days = Math.round((end - range.from.getTime()) / MS_PER_DAY);
|
|
30
|
-
const from = new Date(range.from);
|
|
31
|
-
from.setDate(from.getDate() - days);
|
|
32
39
|
return {
|
|
33
|
-
from,
|
|
40
|
+
from: shiftCalendar(range.from, -days, 0),
|
|
34
41
|
to: new Date(range.from.getTime() - NON_OVERLAP_GAP_MS),
|
|
35
42
|
};
|
|
36
43
|
}
|
|
@@ -44,11 +51,10 @@ function shiftPreviousPeriod(range: PeriodRange): PeriodRange {
|
|
|
44
51
|
}
|
|
45
52
|
|
|
46
53
|
function shiftPreviousYear(range: PeriodRange): PeriodRange {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
return { from: previousFrom, to: previousTo };
|
|
54
|
+
return {
|
|
55
|
+
from: shiftCalendar(range.from, 0, -1),
|
|
56
|
+
to: shiftCalendar(range.to, 0, -1),
|
|
57
|
+
};
|
|
52
58
|
}
|
|
53
59
|
|
|
54
60
|
const COMPARISON_RESOLVERS: Record<
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { readerTimeZone } from "../../utils/regional";
|
|
2
|
+
|
|
3
|
+
/** The fields of a wall-clock time, months counted from 0 as `Date` does. */
|
|
4
|
+
export interface WallTime {
|
|
5
|
+
year: number;
|
|
6
|
+
month: number;
|
|
7
|
+
day: number;
|
|
8
|
+
hours: number;
|
|
9
|
+
minutes: number;
|
|
10
|
+
seconds: number;
|
|
11
|
+
milliseconds: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* A calendar in one time zone: the wall-clock time of an instant, and the
|
|
16
|
+
* instant of a wall-clock time. Fields out of range roll over as `Date`'s do
|
|
17
|
+
* (day 0 is the last day of the month before).
|
|
18
|
+
*/
|
|
19
|
+
export interface PeriodClock {
|
|
20
|
+
wall(date: Date): WallTime;
|
|
21
|
+
instant(wall: WallTime): Date;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** The calendar of the zone the code runs in, through `Date`'s own methods. */
|
|
25
|
+
export const localClock: PeriodClock = {
|
|
26
|
+
wall: (date) => ({
|
|
27
|
+
year: date.getFullYear(),
|
|
28
|
+
month: date.getMonth(),
|
|
29
|
+
day: date.getDate(),
|
|
30
|
+
hours: date.getHours(),
|
|
31
|
+
minutes: date.getMinutes(),
|
|
32
|
+
seconds: date.getSeconds(),
|
|
33
|
+
milliseconds: date.getMilliseconds(),
|
|
34
|
+
}),
|
|
35
|
+
instant: (wall) =>
|
|
36
|
+
new Date(
|
|
37
|
+
wall.year,
|
|
38
|
+
wall.month,
|
|
39
|
+
wall.day,
|
|
40
|
+
wall.hours,
|
|
41
|
+
wall.minutes,
|
|
42
|
+
wall.seconds,
|
|
43
|
+
wall.milliseconds,
|
|
44
|
+
),
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const WALL_PARTS = ["year", "month", "day", "hour", "minute", "second"];
|
|
48
|
+
const zonedFormatters = new Map<string, Intl.DateTimeFormat>();
|
|
49
|
+
|
|
50
|
+
function zonedFormatter(timeZone: string): Intl.DateTimeFormat {
|
|
51
|
+
let formatter = zonedFormatters.get(timeZone);
|
|
52
|
+
if (!formatter) {
|
|
53
|
+
formatter = new Intl.DateTimeFormat("en-US", {
|
|
54
|
+
timeZone,
|
|
55
|
+
hourCycle: "h23",
|
|
56
|
+
year: "numeric",
|
|
57
|
+
month: "numeric",
|
|
58
|
+
day: "numeric",
|
|
59
|
+
hour: "numeric",
|
|
60
|
+
minute: "numeric",
|
|
61
|
+
second: "numeric",
|
|
62
|
+
});
|
|
63
|
+
zonedFormatters.set(timeZone, formatter);
|
|
64
|
+
}
|
|
65
|
+
return formatter;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function utcOf(wall: WallTime): number {
|
|
69
|
+
return Date.UTC(
|
|
70
|
+
wall.year,
|
|
71
|
+
wall.month,
|
|
72
|
+
wall.day,
|
|
73
|
+
wall.hours,
|
|
74
|
+
wall.minutes,
|
|
75
|
+
wall.seconds,
|
|
76
|
+
wall.milliseconds,
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** The calendar of `timeZone`, through `Intl` whatever zone the code runs in. */
|
|
81
|
+
export function zonedClock(timeZone: string): PeriodClock {
|
|
82
|
+
const wall = (date: Date): WallTime => {
|
|
83
|
+
const parts = Object.fromEntries(
|
|
84
|
+
zonedFormatter(timeZone)
|
|
85
|
+
.formatToParts(date)
|
|
86
|
+
.filter((part) => WALL_PARTS.includes(part.type))
|
|
87
|
+
.map((part) => [part.type, Number(part.value)]),
|
|
88
|
+
);
|
|
89
|
+
return {
|
|
90
|
+
year: parts.year!,
|
|
91
|
+
month: parts.month! - 1,
|
|
92
|
+
day: parts.day!,
|
|
93
|
+
hours: parts.hour!,
|
|
94
|
+
minutes: parts.minute!,
|
|
95
|
+
seconds: parts.second!,
|
|
96
|
+
milliseconds: date.getUTCMilliseconds(),
|
|
97
|
+
};
|
|
98
|
+
};
|
|
99
|
+
// The zone's offset at an instant: how far its wall clock is from UTC.
|
|
100
|
+
const offsetAt = (time: number) => utcOf(wall(new Date(time))) - time;
|
|
101
|
+
return {
|
|
102
|
+
wall,
|
|
103
|
+
instant: (target) => {
|
|
104
|
+
const asUtc = utcOf(target);
|
|
105
|
+
// Twice: the offset at the first guess is the right one unless the
|
|
106
|
+
// guess and the answer fall on either side of a daylight saving change.
|
|
107
|
+
const guess = asUtc - offsetAt(asUtc);
|
|
108
|
+
return new Date(asUtc - offsetAt(guess));
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* The calendar a period is resolved in: the reader's browser's zone. That is
|
|
115
|
+
* the code's own in the browser; on a server, the zone the browser's cookie
|
|
116
|
+
* names (see `readerTimeZone`), or the server's own without one.
|
|
117
|
+
*/
|
|
118
|
+
export function readerClock(): PeriodClock {
|
|
119
|
+
const zone = readerTimeZone();
|
|
120
|
+
return zone ? zonedClock(zone) : localClock;
|
|
121
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { getCurrentInstance } from "vue";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The instant a server render resolves its periods from, handed to the
|
|
5
|
+
* browser with the page.
|
|
6
|
+
*/
|
|
7
|
+
const RENDER_NOW_STATE_KEY = "dms-period-render-now";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Whether the component setting up is hydrating the server's HTML: Vue binds
|
|
11
|
+
* a hydrating component's vnode to its server-rendered node before its setup
|
|
12
|
+
* runs, where a fresh mount has no node yet.
|
|
13
|
+
*/
|
|
14
|
+
function isHydratingSetup(): boolean {
|
|
15
|
+
return !!getCurrentInstance()?.vnode.el;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The instant a period set up now resolves from, or `null` for the clock's.
|
|
20
|
+
*
|
|
21
|
+
* A server render resolves every period of the request from one instant,
|
|
22
|
+
* which it transfers; the browser hydrating that render resolves from the
|
|
23
|
+
* same one, so a relative preset (`last-24h`) or a calendar one read across
|
|
24
|
+
* midnight keeps the bounds, key and labels the server rendered, and nothing
|
|
25
|
+
* reads again on hydration. Anywhere else (a client navigation, a call
|
|
26
|
+
* outside a component) it is `null`: the period follows the clock.
|
|
27
|
+
*/
|
|
28
|
+
export function periodRenderNow(): Date | null {
|
|
29
|
+
if (!getCurrentInstance()) return null;
|
|
30
|
+
if (typeof window === "undefined") {
|
|
31
|
+
const now = useDmsState<number>(RENDER_NOW_STATE_KEY, () => Date.now(), {
|
|
32
|
+
transfer: true,
|
|
33
|
+
});
|
|
34
|
+
return new Date(now.value);
|
|
35
|
+
}
|
|
36
|
+
if (!isHydratingSetup()) return null;
|
|
37
|
+
const transferred = useDmsState<number | null>(
|
|
38
|
+
RENDER_NOW_STATE_KEY,
|
|
39
|
+
() => null,
|
|
40
|
+
).value;
|
|
41
|
+
return typeof transferred === "number" ? new Date(transferred) : null;
|
|
42
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Ref } from "vue";
|
|
2
|
+
import { isKnownTimeZone } from "../../utils/regional";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The browser's time zone, kept in a cookie for a server render: a period is
|
|
6
|
+
* made of calendar days in the browser's zone, which the server has no other
|
|
7
|
+
* way to know.
|
|
8
|
+
*/
|
|
9
|
+
export const TIME_ZONE_COOKIE = "dms-time-zone";
|
|
10
|
+
const TIME_ZONE_COOKIE_MAX_AGE = 60 * 60 * 24 * 365;
|
|
11
|
+
|
|
12
|
+
/** The browser's time zone as its cookie holds it; `null` before it is written. */
|
|
13
|
+
export function useTimeZoneCookie(): Ref<string | null> {
|
|
14
|
+
return useDmsCookie<string | null>(TIME_ZONE_COOKIE, {
|
|
15
|
+
default: () => null,
|
|
16
|
+
maxAge: TIME_ZONE_COOKIE_MAX_AGE,
|
|
17
|
+
path: "/",
|
|
18
|
+
sameSite: "lax",
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Whether this render resolves a period as the browser does. The browser
|
|
24
|
+
* always does; a server once the browser's cookie names its time zone, in
|
|
25
|
+
* which the presets then resolve (`readerClock`).
|
|
26
|
+
*/
|
|
27
|
+
export function resolvesPeriodsAsBrowser(): boolean {
|
|
28
|
+
if (typeof window !== "undefined") return true;
|
|
29
|
+
const zone = useTimeZoneCookie().value;
|
|
30
|
+
return !!zone && isKnownTimeZone(zone);
|
|
31
|
+
}
|