@antelopejs/dms 0.8.3 → 0.9.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/CHANGELOG.md +20 -0
- 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 +4 -2
- package/dist/implementations/dms/frontend-modules.js +8 -7
- package/dist/implementations/dms/frontend-modules.js.map +1 -1
- package/dist/implementations/dms/manifest.d.ts +6 -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 +11 -5
- package/dist/implementations/dms/page.js +56 -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/realtime.d.ts +5 -1
- package/dist/routes/realtime.js +16 -9
- package/dist/routes/realtime.js.map +1 -1
- 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/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/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/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/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 +6 -28
- 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/page.ts +13 -0
- 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 +2 -2
- package/frontend-vue/pnpm-lock.yaml +5 -5
- 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/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.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
|
@@ -1,7 +1,11 @@
|
|
|
1
|
+
import { router as inertiaRouter } from "@inertiajs/vue3";
|
|
1
2
|
import { layoutUrlWithQuery } from "../../utils/pageQuery";
|
|
3
|
+
import { onBeforeMount, toRaw } from "vue";
|
|
2
4
|
import { withoutTrailingSlash } from "ufo";
|
|
3
5
|
|
|
4
6
|
const STALE_THRESHOLD_MS = 300_000;
|
|
7
|
+
const SITE_LAYOUT_ENDPOINT = "/dms/sitelayout";
|
|
8
|
+
const RENDERED_SITE_LAYOUT_KEY = "dms-rendered-site-layout";
|
|
5
9
|
const IN_FLIGHT_SITE_LAYOUT_KEY = "__dmsInFlightSiteLayout";
|
|
6
10
|
const IN_FLIGHT_PAGE_LAYOUTS_KEY = "__dmsInFlightPageLayouts";
|
|
7
11
|
|
|
@@ -10,28 +14,35 @@ interface PageLayoutRequest {
|
|
|
10
14
|
promise: Promise<PageLayout>;
|
|
11
15
|
}
|
|
12
16
|
|
|
13
|
-
function getCachedLayoutUrls(
|
|
14
|
-
previousSite: SiteLayout | undefined,
|
|
15
|
-
previousCache: Record<string, PageLayout>,
|
|
16
|
-
nextSite: SiteLayout,
|
|
17
|
-
): string[] {
|
|
18
|
-
const urls = Object.entries(previousSite?.pages ?? {}).flatMap(
|
|
19
|
-
([path, page]) => {
|
|
20
|
-
const nextPage = nextSite.pages[path];
|
|
21
|
-
return previousCache[page.layoutUrl] &&
|
|
22
|
-
nextPage?.layoutUrl &&
|
|
23
|
-
nextPage.hasAccess !== false
|
|
24
|
-
? [nextPage.layoutUrl]
|
|
25
|
-
: [];
|
|
26
|
-
},
|
|
27
|
-
);
|
|
28
|
-
return [...new Set(urls)];
|
|
29
|
-
}
|
|
30
|
-
|
|
31
17
|
interface MatchRouteOptions {
|
|
32
18
|
includeCategories: boolean;
|
|
33
19
|
}
|
|
34
20
|
|
|
21
|
+
// The categories a URL resolves against, by slug, per served layout: the
|
|
22
|
+
// layout lists them by full id, and a `urlTransparent` one answers at its
|
|
23
|
+
// parent's slug, so it is never a URL's target.
|
|
24
|
+
const navigableCategoriesByLayout = new WeakMap<
|
|
25
|
+
SiteLayout,
|
|
26
|
+
Map<string, CategoryInfo>
|
|
27
|
+
>();
|
|
28
|
+
|
|
29
|
+
/** The category of the layout a URL resolves to, by its slug. */
|
|
30
|
+
export function findNavigableCategory(
|
|
31
|
+
layout: SiteLayout,
|
|
32
|
+
slug: string,
|
|
33
|
+
): CategoryInfo | undefined {
|
|
34
|
+
let bySlug = navigableCategoriesByLayout.get(layout);
|
|
35
|
+
if (!bySlug) {
|
|
36
|
+
bySlug = new Map(
|
|
37
|
+
Object.values(layout.categories)
|
|
38
|
+
.filter((category) => !category.urlTransparent)
|
|
39
|
+
.map((category) => [category.fullSlug, category]),
|
|
40
|
+
);
|
|
41
|
+
navigableCategoriesByLayout.set(layout, bySlug);
|
|
42
|
+
}
|
|
43
|
+
return bySlug.get(slug);
|
|
44
|
+
}
|
|
45
|
+
|
|
35
46
|
interface MatchRouteResult {
|
|
36
47
|
pattern: string;
|
|
37
48
|
metadata: PageInfo | CategoryInfo;
|
|
@@ -99,24 +110,84 @@ export function extractRouteParams(
|
|
|
99
110
|
return buildParamBag(paramNames, match.slice(1));
|
|
100
111
|
}
|
|
101
112
|
|
|
113
|
+
/**
|
|
114
|
+
* A partial reload of the page on screen: the server sends the site layout
|
|
115
|
+
* and the page fresh, and they replace the browser's copies, the layout
|
|
116
|
+
* Inertia carries from page to page included. A failure rejects and leaves
|
|
117
|
+
* the page as it is: Inertia would otherwise show the error in its place.
|
|
118
|
+
*/
|
|
119
|
+
function reloadSiteLayoutAndPage(): Promise<void> {
|
|
120
|
+
return new Promise((resolve, reject) => {
|
|
121
|
+
let failure: unknown;
|
|
122
|
+
inertiaRouter.reload({
|
|
123
|
+
only: ["shared", "page"],
|
|
124
|
+
onHttpException: (response) => {
|
|
125
|
+
failure = new Error(`Site layout reload failed (${response.status})`);
|
|
126
|
+
return false;
|
|
127
|
+
},
|
|
128
|
+
onNetworkError: (error) => {
|
|
129
|
+
failure = error;
|
|
130
|
+
return false;
|
|
131
|
+
},
|
|
132
|
+
onFinish: () => (failure ? reject(failure) : resolve()),
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
interface SiteLayoutResponse {
|
|
138
|
+
siteLayout: SiteLayout;
|
|
139
|
+
siteLayoutTree: SiteLayoutTree;
|
|
140
|
+
quickActions: QuickActionsPayload;
|
|
141
|
+
modules: Record<string, ModuleWithAccess>;
|
|
142
|
+
isOwner: boolean;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** The shared site layout, as a page's props or `/dms/sitelayout` set it. */
|
|
146
|
+
function useSiteLayoutState() {
|
|
147
|
+
return {
|
|
148
|
+
siteLayout: useDmsState<SiteLayout | undefined>(
|
|
149
|
+
"dms-siteLayout",
|
|
150
|
+
() => undefined,
|
|
151
|
+
),
|
|
152
|
+
siteLayoutTree: useDmsState<SiteLayoutTree | undefined>(
|
|
153
|
+
"dms-pageTree",
|
|
154
|
+
() => undefined,
|
|
155
|
+
),
|
|
156
|
+
quickActions: useDmsState<QuickActionsPayload | undefined>(
|
|
157
|
+
"dms-quickActions",
|
|
158
|
+
() => undefined,
|
|
159
|
+
),
|
|
160
|
+
modules: useDmsState<Record<string, ModuleWithAccess> | undefined>(
|
|
161
|
+
"dms-modules",
|
|
162
|
+
() => undefined,
|
|
163
|
+
),
|
|
164
|
+
isOwner: useDmsState<boolean>("dms-isOwner", () => false),
|
|
165
|
+
lastRefreshTime: useDmsState<number>("dms-lastRefreshTime", () => 0),
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function applySiteLayoutResponse(
|
|
170
|
+
state: ReturnType<typeof useSiteLayoutState>,
|
|
171
|
+
result: SiteLayoutResponse,
|
|
172
|
+
): void {
|
|
173
|
+
state.siteLayout.value = result.siteLayout;
|
|
174
|
+
state.siteLayoutTree.value = result.siteLayoutTree;
|
|
175
|
+
state.quickActions.value = result.quickActions;
|
|
176
|
+
state.modules.value = result.modules;
|
|
177
|
+
state.isOwner.value = result.isOwner;
|
|
178
|
+
state.lastRefreshTime.value = Date.now();
|
|
179
|
+
}
|
|
180
|
+
|
|
102
181
|
export const useSiteLayout = () => {
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
"dms-quickActions",
|
|
113
|
-
() => undefined,
|
|
114
|
-
);
|
|
115
|
-
const modules = useDmsState<Record<string, ModuleWithAccess> | undefined>(
|
|
116
|
-
"dms-modules",
|
|
117
|
-
() => undefined,
|
|
118
|
-
);
|
|
119
|
-
const isOwner = useDmsState<boolean>("dms-isOwner", () => false);
|
|
182
|
+
const state = useSiteLayoutState();
|
|
183
|
+
const {
|
|
184
|
+
siteLayout,
|
|
185
|
+
siteLayoutTree,
|
|
186
|
+
quickActions,
|
|
187
|
+
modules,
|
|
188
|
+
isOwner,
|
|
189
|
+
lastRefreshTime,
|
|
190
|
+
} = state;
|
|
120
191
|
|
|
121
192
|
const isLoading = useDmsState<boolean>("dms-loading", () => false);
|
|
122
193
|
const loadingError = useDmsState<string | null>("dms-error", () => null);
|
|
@@ -125,7 +196,6 @@ export const useSiteLayout = () => {
|
|
|
125
196
|
"dms-pageLayouts",
|
|
126
197
|
() => ({}),
|
|
127
198
|
);
|
|
128
|
-
const lastRefreshTime = useDmsState<number>("dms-lastRefreshTime", () => 0);
|
|
129
199
|
|
|
130
200
|
// Held on the DMS app rather than in a per-call ref: every caller of
|
|
131
201
|
// useSiteLayout writes the same shared layout state, so they have to see the
|
|
@@ -148,79 +218,40 @@ export const useSiteLayout = () => {
|
|
|
148
218
|
},
|
|
149
219
|
};
|
|
150
220
|
|
|
151
|
-
interface SiteLayoutResponse {
|
|
152
|
-
siteLayout: SiteLayout;
|
|
153
|
-
siteLayoutTree: SiteLayoutTree;
|
|
154
|
-
quickActions: QuickActionsPayload;
|
|
155
|
-
modules: Record<string, ModuleWithAccess>;
|
|
156
|
-
isOwner: boolean;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
const SITE_LAYOUT_ENDPOINT = "/dms/sitelayout";
|
|
160
221
|
const SITE_LAYOUT_ERROR_MESSAGE = "Error loading siteLayout";
|
|
161
222
|
const UNKNOWN_SITE_LAYOUT_ERROR = "Unknown error loading siteLayout";
|
|
162
223
|
const HTTP_INTERNAL_ERROR = 500;
|
|
163
224
|
|
|
164
|
-
const applySiteLayoutResponse = (result: SiteLayoutResponse) => {
|
|
165
|
-
siteLayout.value = result.siteLayout;
|
|
166
|
-
siteLayoutTree.value = result.siteLayoutTree;
|
|
167
|
-
quickActions.value = result.quickActions;
|
|
168
|
-
modules.value = result.modules;
|
|
169
|
-
isOwner.value = result.isOwner;
|
|
170
|
-
lastRefreshTime.value = Date.now();
|
|
171
|
-
};
|
|
172
|
-
|
|
173
225
|
const handleSiteLayoutError = (error: unknown) => {
|
|
174
226
|
const errorMessage =
|
|
175
227
|
error instanceof Error ? error.message : UNKNOWN_SITE_LAYOUT_ERROR;
|
|
176
228
|
loadingError.value = errorMessage;
|
|
177
|
-
|
|
229
|
+
showDmsError({
|
|
178
230
|
statusCode: HTTP_INTERNAL_ERROR,
|
|
179
231
|
statusMessage: SITE_LAYOUT_ERROR_MESSAGE,
|
|
180
232
|
message: errorMessage,
|
|
181
233
|
});
|
|
182
234
|
};
|
|
183
235
|
|
|
184
|
-
const requestSiteLayout = async (
|
|
236
|
+
const requestSiteLayout = async (): Promise<boolean> => {
|
|
185
237
|
const initialLayout = siteLayout.value;
|
|
186
238
|
const initialCache = pageLayouts.value;
|
|
239
|
+
// A layout another request committed meanwhile is newer than this one.
|
|
187
240
|
const isCurrent = () =>
|
|
188
241
|
siteLayout.value === initialLayout && pageLayouts.value === initialCache;
|
|
189
242
|
const { $authFetch } = useAuthFetch();
|
|
190
243
|
try {
|
|
191
244
|
const result = await $authFetch<SiteLayoutResponse>(SITE_LAYOUT_ENDPOINT);
|
|
192
|
-
|
|
193
|
-
// Initial page loads mutate this cache in place; identity guards cannot detect those additions.
|
|
194
|
-
while (isCurrent()) {
|
|
195
|
-
const urls = fatal
|
|
196
|
-
? []
|
|
197
|
-
: getCachedLayoutUrls(
|
|
198
|
-
initialLayout,
|
|
199
|
-
initialCache,
|
|
200
|
-
result.siteLayout,
|
|
201
|
-
).filter((url) => !nextCache[url]);
|
|
202
|
-
if (!urls.length) {
|
|
203
|
-
if (!fatal) pageLayouts.value = nextCache;
|
|
204
|
-
applySiteLayoutResponse(result);
|
|
205
|
-
return true;
|
|
206
|
-
}
|
|
207
|
-
const layouts = await Promise.all(
|
|
208
|
-
urls.map(
|
|
209
|
-
async (url) => [url, await $authFetch<PageLayout>(url)] as const,
|
|
210
|
-
),
|
|
211
|
-
);
|
|
212
|
-
Object.assign(nextCache, Object.fromEntries(layouts));
|
|
213
|
-
}
|
|
245
|
+
if (isCurrent()) applySiteLayoutResponse(state, result);
|
|
214
246
|
return true;
|
|
215
247
|
} catch (error) {
|
|
216
|
-
|
|
217
|
-
if (fatal && isCurrent()) handleSiteLayoutError(error);
|
|
248
|
+
if (isCurrent()) handleSiteLayoutError(error);
|
|
218
249
|
throw error;
|
|
219
250
|
}
|
|
220
251
|
};
|
|
221
252
|
|
|
222
|
-
const startSiteLayoutFetch = (
|
|
223
|
-
const started = requestSiteLayout(
|
|
253
|
+
const startSiteLayoutFetch = (): Promise<boolean> => {
|
|
254
|
+
const started = requestSiteLayout().finally(() => {
|
|
224
255
|
isLoading.value = false;
|
|
225
256
|
// A waiting refresh may already have started its replacement request.
|
|
226
257
|
if (loadingPromise.value === started) loadingPromise.value = null;
|
|
@@ -246,12 +277,9 @@ export const useSiteLayout = () => {
|
|
|
246
277
|
pageLayouts.value = {};
|
|
247
278
|
}
|
|
248
279
|
|
|
249
|
-
//
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
const refresh = async (options?: { fatal?: boolean }) => {
|
|
253
|
-
// Background refreshes stage page layouts before committing; identity refreshes clear immediately.
|
|
254
|
-
if (options?.fatal !== false) clearPageLayoutCaches();
|
|
280
|
+
// A background resync (a realtime menu invalidation) goes through `resync`.
|
|
281
|
+
const refresh = async () => {
|
|
282
|
+
clearPageLayoutCaches();
|
|
255
283
|
|
|
256
284
|
// A fetch already in flight was started before whatever prompted this
|
|
257
285
|
// call, so its answer cannot contain the change: joining it would report
|
|
@@ -270,11 +298,26 @@ export const useSiteLayout = () => {
|
|
|
270
298
|
if (!loadingPromise.value) {
|
|
271
299
|
isLoading.value = true;
|
|
272
300
|
loadingError.value = null;
|
|
273
|
-
loadingPromise.value = startSiteLayoutFetch(
|
|
301
|
+
loadingPromise.value = startSiteLayoutFetch();
|
|
274
302
|
}
|
|
275
303
|
await loadingPromise.value;
|
|
276
304
|
};
|
|
277
305
|
|
|
306
|
+
/**
|
|
307
|
+
* Re-reads the menu and the page on screen from the server, for a change
|
|
308
|
+
* the backend announced (a realtime menu invalidation) or a switch of
|
|
309
|
+
* session. The pages prefetched on hover are dropped first: each carries
|
|
310
|
+
* the layout and the access of before. A navigation that starts meanwhile
|
|
311
|
+
* drops the reload's answer, and the page it opens still carries the layout
|
|
312
|
+
* the reload meant to replace: that page is reloaded in turn.
|
|
313
|
+
*/
|
|
314
|
+
const resync = async (): Promise<void> => {
|
|
315
|
+
inertiaRouter.flushAll();
|
|
316
|
+
const path = window.location.pathname;
|
|
317
|
+
await reloadSiteLayoutAndPage();
|
|
318
|
+
if (window.location.pathname !== path) await reloadSiteLayoutAndPage();
|
|
319
|
+
};
|
|
320
|
+
|
|
278
321
|
const refreshIfStale = async () => {
|
|
279
322
|
const elapsed = Date.now() - lastRefreshTime.value;
|
|
280
323
|
if (elapsed < STALE_THRESHOLD_MS) {
|
|
@@ -335,12 +378,11 @@ export const useSiteLayout = () => {
|
|
|
335
378
|
};
|
|
336
379
|
}
|
|
337
380
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
};
|
|
381
|
+
const category = options.includeCategories
|
|
382
|
+
? findNavigableCategory(layout, normalizedPath)
|
|
383
|
+
: undefined;
|
|
384
|
+
if (category) {
|
|
385
|
+
return { pattern: normalizedPath, metadata: category, params: {} };
|
|
344
386
|
}
|
|
345
387
|
|
|
346
388
|
for (const [pattern, metadata] of Object.entries(layout.pages)) {
|
|
@@ -420,7 +462,7 @@ export const useSiteLayout = () => {
|
|
|
420
462
|
return false;
|
|
421
463
|
}
|
|
422
464
|
clearPageLayoutCaches();
|
|
423
|
-
applySiteLayoutResponse(result);
|
|
465
|
+
applySiteLayoutResponse(state, result);
|
|
424
466
|
return true;
|
|
425
467
|
} catch {
|
|
426
468
|
return false;
|
|
@@ -440,9 +482,48 @@ export const useSiteLayout = () => {
|
|
|
440
482
|
loadSiteLayout,
|
|
441
483
|
loadPageLayout,
|
|
442
484
|
refresh,
|
|
485
|
+
resync,
|
|
443
486
|
refreshIfStale,
|
|
444
487
|
findMatchingRoute,
|
|
445
488
|
findMatchingRouteOrCategory,
|
|
446
489
|
probeAndCommitRoute,
|
|
447
490
|
};
|
|
448
491
|
};
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* The site layout for the component rendering it (the sidebar), called in
|
|
495
|
+
* its setup. A page the backend describes brings it with its props; a page
|
|
496
|
+
* only a frontend layer knows (a custom page) does not, and this loads it
|
|
497
|
+
* with the page then: a server render waits for it before rendering the
|
|
498
|
+
* component, and hands it to the browser, which takes it over before the
|
|
499
|
+
* component mounts instead of asking again.
|
|
500
|
+
*/
|
|
501
|
+
export function useRenderedSiteLayout(): void {
|
|
502
|
+
const state = useSiteLayoutState();
|
|
503
|
+
if (state.siteLayout.value) return;
|
|
504
|
+
const layout = useSiteLayout();
|
|
505
|
+
// Through `loadSiteLayout`, which sets the state: a middleware loading it
|
|
506
|
+
// at the same time (the auth guard of a custom page) joins this request.
|
|
507
|
+
const { data } = useApiData<SiteLayoutResponse | null>(
|
|
508
|
+
RENDERED_SITE_LAYOUT_KEY,
|
|
509
|
+
async () => {
|
|
510
|
+
await layout.loadSiteLayout();
|
|
511
|
+
const { siteLayout, siteLayoutTree, quickActions, modules, isOwner } =
|
|
512
|
+
state;
|
|
513
|
+
// What the page payload carries: the plain values, not the state.
|
|
514
|
+
return siteLayout.value
|
|
515
|
+
? {
|
|
516
|
+
siteLayout: toRaw(siteLayout.value),
|
|
517
|
+
siteLayoutTree: toRaw(siteLayoutTree.value!),
|
|
518
|
+
quickActions: toRaw(quickActions.value!),
|
|
519
|
+
modules: toRaw(modules.value!),
|
|
520
|
+
isOwner: isOwner.value,
|
|
521
|
+
}
|
|
522
|
+
: null;
|
|
523
|
+
},
|
|
524
|
+
);
|
|
525
|
+
onBeforeMount(() => {
|
|
526
|
+
if (data.value && !state.siteLayout.value)
|
|
527
|
+
applySiteLayoutResponse(state, data.value);
|
|
528
|
+
});
|
|
529
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
import { pageRoutePath } from "#dms-core/app/utils/pageRoutes";
|
|
2
|
+
const PROFILE_ENDPOINT = pageRoutePath("/settings/user/profile");
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Switches the interface language and stores it on the user's profile, then
|
|
@@ -9,7 +10,7 @@ const PROFILE_ENDPOINT = "/settings/user/profile";
|
|
|
9
10
|
export function useUserLanguage() {
|
|
10
11
|
const dmsApp = useDmsApp();
|
|
11
12
|
const { $authFetch } = useAuthFetch();
|
|
12
|
-
const { user, fetch: refreshUser } =
|
|
13
|
+
const { user, fetch: refreshUser } = useDmsUserSession();
|
|
13
14
|
|
|
14
15
|
async function changeLanguage(language: string): Promise<void> {
|
|
15
16
|
await dmsApp.$i18n.setLocale(language);
|
|
@@ -36,7 +36,6 @@ import {
|
|
|
36
36
|
import DmsSearchInput from "#dms-ui/app/build/components/form/SearchInput.vue";
|
|
37
37
|
|
|
38
38
|
const SKELETON_PLACEHOLDER_COUNT = 6;
|
|
39
|
-
const MODULES_LISTING_DATA_KEY = "modules-page-listing";
|
|
40
39
|
|
|
41
40
|
const { t } = useI18n();
|
|
42
41
|
const { processI18n } = useTranslation();
|
|
@@ -85,37 +84,16 @@ if (!siteLayout.siteLayout.value) {
|
|
|
85
84
|
// 403 page, the same answer as any page they may not open (the entry is not in
|
|
86
85
|
// their menu either).
|
|
87
86
|
if (!isOwner.value) {
|
|
88
|
-
throw
|
|
87
|
+
throw showDmsError({
|
|
89
88
|
statusCode: HTTP_FORBIDDEN,
|
|
90
89
|
statusMessage: "Access denied",
|
|
91
90
|
});
|
|
92
91
|
}
|
|
93
92
|
|
|
94
|
-
//
|
|
95
|
-
//
|
|
96
|
-
//
|
|
97
|
-
|
|
98
|
-
if (isOwner.value) {
|
|
99
|
-
const { data: renderedListing } = await useDmsAsyncData<
|
|
100
|
-
ModuleCatalogEntry[] | null
|
|
101
|
-
>(MODULES_LISTING_DATA_KEY, async () => {
|
|
102
|
-
await modulesListing.loadModulesListing();
|
|
103
|
-
return modulesListing.modules.value ?? null;
|
|
104
|
-
});
|
|
105
|
-
if (modulesListing.modules.value === undefined) {
|
|
106
|
-
if (renderedListing.value) {
|
|
107
|
-
modulesListing.modules.value = renderedListing.value;
|
|
108
|
-
} else {
|
|
109
|
-
await modulesListing.loadModulesListing();
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
onMounted(() => {
|
|
115
|
-
if (isOwner.value && modulesListing.modules.value !== undefined) {
|
|
116
|
-
void modulesListing.revalidate();
|
|
117
|
-
}
|
|
118
|
-
});
|
|
93
|
+
// Read with the page, which the server renders and hands over: the
|
|
94
|
+
// hydration reuses the listing the server rendered (read again, its live
|
|
95
|
+
// readouts, an uptime a minute on, would no longer match the markup).
|
|
96
|
+
await modulesListing.loadModulesListing();
|
|
119
97
|
|
|
120
98
|
const isForbidden = computed(
|
|
121
99
|
() =>
|
|
@@ -204,7 +182,7 @@ const trimmedQuery = computed(() => query.value.trim());
|
|
|
204
182
|
const pageMatch = computed(() => {
|
|
205
183
|
if (visibleModules.value.length > 0 || !trimmedQuery.value) return null;
|
|
206
184
|
const match = findModulePageMatch(
|
|
207
|
-
siteLayout.siteLayout.value
|
|
185
|
+
siteLayout.siteLayout.value,
|
|
208
186
|
trimmedQuery.value,
|
|
209
187
|
new Set(modules.value.map((entry) => entry.id)),
|
|
210
188
|
processI18n,
|
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
+
import type { DmsLogoSources } from "#dms-core/shared/types/app-config";
|
|
2
3
|
import { emailLogoUrl, emailStyles } from "./index";
|
|
3
4
|
|
|
5
|
+
interface EmailLogoConfig {
|
|
6
|
+
default?: Partial<DmsLogoSources>;
|
|
7
|
+
email?: Partial<DmsLogoSources>;
|
|
8
|
+
}
|
|
9
|
+
|
|
4
10
|
interface EmailAppConfig {
|
|
5
11
|
branding?: {
|
|
6
|
-
logo?:
|
|
7
|
-
default?: {
|
|
8
|
-
light?: string;
|
|
9
|
-
};
|
|
10
|
-
};
|
|
12
|
+
logo?: EmailLogoConfig;
|
|
11
13
|
};
|
|
12
14
|
}
|
|
13
15
|
|
|
16
|
+
const DARK_SCHEME_MEDIA = "(prefers-color-scheme: dark)";
|
|
17
|
+
|
|
14
18
|
const props = withDefaults(
|
|
15
19
|
defineProps<{
|
|
16
20
|
/** Shown as the email's heading; no heading when omitted. */
|
|
@@ -33,13 +37,20 @@ const props = withDefaults(
|
|
|
33
37
|
const appConfig = useDmsAppConfig() as EmailAppConfig;
|
|
34
38
|
const runtimeConfig = useDmsRuntimeConfig();
|
|
35
39
|
|
|
36
|
-
const
|
|
40
|
+
const logo = appConfig.branding?.logo;
|
|
37
41
|
const clientBaseUrl = runtimeConfig.public.dms?.clientBaseUrl ?? "";
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
42
|
+
|
|
43
|
+
function logoSource(path: string | undefined): string {
|
|
44
|
+
if (props.publicLogoOnly) return emailLogoUrl(path, clientBaseUrl);
|
|
45
|
+
return path?.startsWith("/") ? `${clientBaseUrl}${path}` : (path ?? "");
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const logoUrl = logoSource(logo?.email?.light || logo?.default?.light);
|
|
49
|
+
// Offered to the mail clients that follow the reader's dark mode; the others
|
|
50
|
+
// skip the picture's source and draw the light image. Taken from the email
|
|
51
|
+
// slot only, so an instance that sets none keeps its single image.
|
|
52
|
+
const darkCandidate = logoSource(logo?.email?.dark);
|
|
53
|
+
const darkLogoUrl = darkCandidate === logoUrl ? "" : darkCandidate;
|
|
43
54
|
</script>
|
|
44
55
|
|
|
45
56
|
<template>
|
|
@@ -52,7 +63,16 @@ const logoUrl = props.publicLogoOnly
|
|
|
52
63
|
<EContainer :style="emailStyles.container">
|
|
53
64
|
<ESection :style="emailStyles.card">
|
|
54
65
|
<ESection v-if="logoUrl" :style="emailStyles.logo">
|
|
66
|
+
<picture v-if="darkLogoUrl">
|
|
67
|
+
<source :media="DARK_SCHEME_MEDIA" :srcset="darkLogoUrl" />
|
|
68
|
+
<EImg
|
|
69
|
+
:src="logoUrl"
|
|
70
|
+
:alt="logoAlt"
|
|
71
|
+
:style="emailStyles.logoImage"
|
|
72
|
+
/>
|
|
73
|
+
</picture>
|
|
55
74
|
<EImg
|
|
75
|
+
v-else
|
|
56
76
|
:src="logoUrl"
|
|
57
77
|
:alt="logoAlt"
|
|
58
78
|
:style="emailStyles.logoImage"
|
|
@@ -4,6 +4,7 @@ import StageCard, {
|
|
|
4
4
|
type StageCardTone,
|
|
5
5
|
} from "./build/components/layout/StageCard.vue";
|
|
6
6
|
import EmptyLayout from "./custom-layouts/EmptyLayout.vue";
|
|
7
|
+
import { publicErrorMessage } from "./build/utils/publicErrorMessage";
|
|
7
8
|
|
|
8
9
|
interface ErrorConfig {
|
|
9
10
|
icon: string;
|
|
@@ -66,10 +67,12 @@ const statusCode = computed(
|
|
|
66
67
|
() => props.error.statusCode || DEFAULT_STATUS_CODE,
|
|
67
68
|
);
|
|
68
69
|
|
|
70
|
+
// Never the backend's address a failed request names (see
|
|
71
|
+
// `publicErrorMessage`).
|
|
69
72
|
const visibleErrorMessage = computed(() =>
|
|
70
73
|
STATUSES_WITHOUT_ERROR_MESSAGE.has(statusCode.value)
|
|
71
74
|
? undefined
|
|
72
|
-
: props.error.message,
|
|
75
|
+
: publicErrorMessage(props.error.message),
|
|
73
76
|
);
|
|
74
77
|
|
|
75
78
|
const errorConfig = computed(
|
|
@@ -89,7 +92,7 @@ const description = computed(() =>
|
|
|
89
92
|
);
|
|
90
93
|
|
|
91
94
|
const handleError = async () => {
|
|
92
|
-
|
|
95
|
+
clearDmsError();
|
|
93
96
|
if (!loggedIn.value) {
|
|
94
97
|
await redirectToAuth(homepage);
|
|
95
98
|
} else {
|
|
@@ -89,7 +89,7 @@ export default defineDmsMiddleware(async (to, from) => {
|
|
|
89
89
|
const moduleEntry = siteLayout.modules.value?.[moduleId];
|
|
90
90
|
|
|
91
91
|
if (!moduleId || !moduleEntry) {
|
|
92
|
-
throw
|
|
92
|
+
throw createDmsError({
|
|
93
93
|
statusCode: MODULE_NOT_FOUND_STATUS,
|
|
94
94
|
statusMessage: MODULE_NOT_FOUND_MESSAGE,
|
|
95
95
|
data: { kind: MODULE_NOT_FOUND_KIND },
|
|
@@ -8,7 +8,7 @@ export default defineDmsMiddleware(async (to, from) => {
|
|
|
8
8
|
// Unsaved changes anywhere (the page's forms, an open drawer or modal, a
|
|
9
9
|
// settings panel): one warning dialog, whatever holds them.
|
|
10
10
|
if (!(await confirmLeave())) {
|
|
11
|
-
return
|
|
11
|
+
return abortDmsNavigation();
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
const { executeGuards } = useLeaveGuard();
|
|
@@ -17,6 +17,6 @@ export default defineDmsMiddleware(async (to, from) => {
|
|
|
17
17
|
const canLeave = await executeGuards(pageKey);
|
|
18
18
|
|
|
19
19
|
if (!canLeave) {
|
|
20
|
-
return
|
|
20
|
+
return abortDmsNavigation();
|
|
21
21
|
}
|
|
22
22
|
});
|
|
@@ -3,6 +3,7 @@ import { withoutTrailingSlash } from "ufo";
|
|
|
3
3
|
import { holdPageHeaderActions } from "../composables/layout/usePageHeaderActions";
|
|
4
4
|
import { useDevReloading } from "../build/composables/dev-reload/useDevReloadHolder";
|
|
5
5
|
import { usePageLiveHost } from "#dms-ui/app/build/composables/live/usePageLiveHost";
|
|
6
|
+
import { usePageDataRefresh } from "#dms-ui/app/build/composables/live/usePageDataRefresh";
|
|
6
7
|
import { pageValidationRedirect } from "../build/utils/pageValidationRedirect";
|
|
7
8
|
|
|
8
9
|
defineOptions({ inheritAttrs: false });
|
|
@@ -42,7 +43,7 @@ async function checkPageMetadata(
|
|
|
42
43
|
if (import.meta.env.DEV && devReloading.value) {
|
|
43
44
|
return;
|
|
44
45
|
}
|
|
45
|
-
throw
|
|
46
|
+
throw showDmsError({
|
|
46
47
|
statusCode: HTTP_NOT_FOUND,
|
|
47
48
|
statusMessage: "Page not found",
|
|
48
49
|
});
|
|
@@ -76,7 +77,7 @@ async function recheckAccessWithFreshSession(): Promise<void> {
|
|
|
76
77
|
if (import.meta.env.DEV && devReloading.value) {
|
|
77
78
|
return;
|
|
78
79
|
}
|
|
79
|
-
throw
|
|
80
|
+
throw showDmsError({
|
|
80
81
|
statusCode: HTTP_NOT_FOUND,
|
|
81
82
|
statusMessage: "Page not found",
|
|
82
83
|
});
|
|
@@ -90,7 +91,7 @@ async function recheckAccessWithFreshSession(): Promise<void> {
|
|
|
90
91
|
return;
|
|
91
92
|
}
|
|
92
93
|
|
|
93
|
-
throw
|
|
94
|
+
throw showDmsError({
|
|
94
95
|
statusCode: HTTP_FORBIDDEN,
|
|
95
96
|
statusMessage: "Access denied",
|
|
96
97
|
});
|
|
@@ -140,7 +141,7 @@ async function enforcePageValidation(): Promise<void> {
|
|
|
140
141
|
routeQuery: query,
|
|
141
142
|
});
|
|
142
143
|
if (!redirect) {
|
|
143
|
-
throw
|
|
144
|
+
throw createDmsError({
|
|
144
145
|
statusCode: HTTP_NOT_FOUND,
|
|
145
146
|
statusMessage: "Page not found",
|
|
146
147
|
});
|
|
@@ -172,7 +173,7 @@ const pagelayoutMetadata = computed(() => routeMatch.value?.metadata);
|
|
|
172
173
|
const routeParams = computed(() => routeMatch.value?.params || {});
|
|
173
174
|
|
|
174
175
|
if (siteLayout.loadingError && siteLayout.loadingError.value) {
|
|
175
|
-
throw
|
|
176
|
+
throw createDmsError({
|
|
176
177
|
statusCode: HTTP_INTERNAL_SERVER_ERROR,
|
|
177
178
|
statusMessage: "Error loading site layout",
|
|
178
179
|
message: siteLayout.loadingError.value || "Unknown error occurred",
|
|
@@ -186,7 +187,7 @@ await checkPageMetadata(pagelayoutMetadata.value);
|
|
|
186
187
|
// flight the page is unregistered for a moment, and the plugin is already
|
|
187
188
|
// waiting for it to come back.
|
|
188
189
|
if (!pagelayoutMetadata.value && !(import.meta.env.DEV && devReloading.value)) {
|
|
189
|
-
throw
|
|
190
|
+
throw createDmsError({
|
|
190
191
|
statusCode: HTTP_NOT_FOUND,
|
|
191
192
|
statusMessage: "Page not found",
|
|
192
193
|
});
|
|
@@ -202,9 +203,9 @@ if (import.meta.env.DEV) {
|
|
|
202
203
|
// the flag back with nobody watching.
|
|
203
204
|
watch(devReloading, (reloading) => {
|
|
204
205
|
if (!reloading && !pagelayoutMetadata.value) {
|
|
205
|
-
// `
|
|
206
|
+
// `showDmsError` commits the error itself; throwing on top of it would only
|
|
206
207
|
// add a second report of the same thing.
|
|
207
|
-
|
|
208
|
+
showDmsError({
|
|
208
209
|
statusCode: HTTP_NOT_FOUND,
|
|
209
210
|
statusMessage: "Page not found",
|
|
210
211
|
});
|
|
@@ -246,6 +247,8 @@ usePageLiveHost(
|
|
|
246
247
|
}),
|
|
247
248
|
{ isStreamConnected: () => !!realtime.sessionId.value },
|
|
248
249
|
);
|
|
250
|
+
// The page's `useApiData` reads follow its refreshes and its realtime events.
|
|
251
|
+
usePageDataRefresh(realtime);
|
|
249
252
|
|
|
250
253
|
function buildPageSetupContext(
|
|
251
254
|
pageInfo: PageInfo,
|
|
@@ -495,7 +498,7 @@ if (import.meta.env.DEV) {
|
|
|
495
498
|
void navigateDms(moved[0]);
|
|
496
499
|
return;
|
|
497
500
|
}
|
|
498
|
-
|
|
501
|
+
showDmsError({
|
|
499
502
|
statusCode: HTTP_NOT_FOUND,
|
|
500
503
|
statusMessage: "Page not found",
|
|
501
504
|
});
|