@antelopejs/dms 0.8.2 → 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 +36 -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/interfaces/dms-base/table-and-card-catalog.test.js +25 -0
- package/dist/test/unit/interfaces/dms-base/table-and-card-catalog.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/validation/regional-preferences.schema.d.ts +2 -2
- 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
package/frontend-vue/layers/dms-layout/app/build/composables/dev-reload/useDevReloadHolder.ts
CHANGED
|
@@ -256,7 +256,8 @@ async function awaitRoute(
|
|
|
256
256
|
}
|
|
257
257
|
|
|
258
258
|
// Deadline spent waiting on someone else's pass.
|
|
259
|
-
if (state.refreshing || deps.now() >= deadline)
|
|
259
|
+
if (state.refreshing || deps.now() >= deadline)
|
|
260
|
+
return deps.isRouteKnown(path);
|
|
260
261
|
|
|
261
262
|
const committed = await runRefresh(state, path, deadline);
|
|
262
263
|
// A trailing re-run booked over ours may be the one that brought the route
|
|
@@ -293,14 +294,14 @@ export function createDevReloadCoordinator(
|
|
|
293
294
|
// has been (re)committed for the current route. Kept separate from the layout
|
|
294
295
|
// commit so the commit can stay atomic with its route check (see
|
|
295
296
|
// probeAndCommitRoute) — the 404-prone part was committing the layout, not this.
|
|
296
|
-
|
|
297
|
+
// The page layouts reload with the layout commit, which empties their cache:
|
|
298
|
+
// no async data key holds them.
|
|
299
|
+
async function postLayoutRefresh(): Promise<void> {
|
|
297
300
|
const permissions = usePermissions();
|
|
298
301
|
// fetchPermissions() refetches and swaps the set atomically, so there is no
|
|
299
302
|
// need to clear() first — clearing would briefly empty permissions and flash
|
|
300
303
|
// permission-gated UI.
|
|
301
304
|
await permissions.fetchPermissions();
|
|
302
|
-
await refreshDmsData(currentRoutePath);
|
|
303
|
-
await refreshDmsData(`layout-${currentRoutePath}`);
|
|
304
305
|
}
|
|
305
306
|
|
|
306
307
|
interface DevReloadHolder {
|
|
@@ -326,7 +327,7 @@ export function useDevReloadHolder(): DevReloadHolder {
|
|
|
326
327
|
const reloading = import.meta.env.DEV ? useDevReloading() : ref(false);
|
|
327
328
|
const siteLayout = useSiteLayout();
|
|
328
329
|
const router = useDmsRouter();
|
|
329
|
-
const currentError =
|
|
330
|
+
const currentError = useDmsError();
|
|
330
331
|
|
|
331
332
|
const coordinator = createDevReloadCoordinator({
|
|
332
333
|
isDev: import.meta.env.DEV,
|
|
@@ -343,7 +344,7 @@ export function useDevReloadHolder(): DevReloadHolder {
|
|
|
343
344
|
if (router.currentRoute.value.path !== recoveredPath) return;
|
|
344
345
|
const err = currentError.value;
|
|
345
346
|
if (!err || err.statusCode !== HTTP_NOT_FOUND) return;
|
|
346
|
-
void
|
|
347
|
+
void clearDmsError();
|
|
347
348
|
},
|
|
348
349
|
setReloading: (value) => {
|
|
349
350
|
reloading.value = value;
|
|
@@ -172,7 +172,7 @@ export function useActiveSidePanel(): ActiveSidePanel {
|
|
|
172
172
|
const { panels } = useAppSidePanels();
|
|
173
173
|
const preferences = useSidePanelPreferences();
|
|
174
174
|
const dragWidth = useSidePanelDragWidth();
|
|
175
|
-
const { loggedIn } =
|
|
175
|
+
const { loggedIn } = useDmsUserSession();
|
|
176
176
|
const panel = computed(() => {
|
|
177
177
|
if (!loggedIn.value) return null;
|
|
178
178
|
const { openId } = readSidePanelPreferences(preferences.value);
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { watch } from "vue";
|
|
2
|
+
import { useDmsInertiaPage } from "#dms/frontend-module";
|
|
3
|
+
import {
|
|
4
|
+
type CountedNavBadge,
|
|
5
|
+
useNavBadges,
|
|
6
|
+
} from "#dms-ui/app/build/composables/navigation/useNavBadges";
|
|
7
|
+
|
|
8
|
+
interface CountedPage {
|
|
9
|
+
navBadges?: Record<string, CountedNavBadge>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Draws the navigation badges each page visit carries (`navBadges`): they
|
|
14
|
+
* are counted for the visit and travel with the page, not with the site
|
|
15
|
+
* layout a browser keeps for minutes. The dashboard frame calls it, so the
|
|
16
|
+
* server render draws them as the browser does.
|
|
17
|
+
*/
|
|
18
|
+
export function useVisitNavBadges(): void {
|
|
19
|
+
const inertiaPage = useDmsInertiaPage();
|
|
20
|
+
const { applyCountedNavBadges } = useNavBadges();
|
|
21
|
+
watch(
|
|
22
|
+
() => (inertiaPage.props.page as CountedPage | undefined)?.navBadges,
|
|
23
|
+
applyCountedNavBadges,
|
|
24
|
+
{ immediate: true },
|
|
25
|
+
);
|
|
26
|
+
}
|
package/frontend-vue/layers/dms-layout/app/build/composables/notification/useNotificationCatalog.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { pageRoutePath } from "#dms-core/app/utils/pageRoutes";
|
|
1
2
|
export type NotificationTogglePermission = "allowed" | "forbidden" | "default";
|
|
2
3
|
|
|
3
4
|
export interface NotificationCategory {
|
|
@@ -32,34 +33,34 @@ export const buildPreferenceKey = (categoryId: string, subjectId: string) =>
|
|
|
32
33
|
export const subjectPreferenceKey = (subject: NotificationSubject) =>
|
|
33
34
|
buildPreferenceKey(subject.category.id, subject.id);
|
|
34
35
|
|
|
36
|
+
const CATALOG_URL = pageRoutePath(
|
|
37
|
+
"/settings/user/notifications",
|
|
38
|
+
"/categories",
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
/** Whether a read has answered with a failure, and no retry is running. */
|
|
42
|
+
export const hasFailed = (read: ApiDataResult<unknown>): boolean =>
|
|
43
|
+
!!read.error.value && read.status.value !== "pending";
|
|
44
|
+
|
|
35
45
|
/**
|
|
36
|
-
* The registered notification categories and subjects,
|
|
37
|
-
* shared by the preferences matrix and the inbox (which tags each
|
|
46
|
+
* The registered notification categories and subjects, read once with the
|
|
47
|
+
* page and shared by the preferences matrix and the inbox (which tags each
|
|
38
48
|
* notification with its source).
|
|
49
|
+
*
|
|
50
|
+
* @param enabled Whether the page shows them: nothing is read otherwise
|
|
39
51
|
*/
|
|
40
|
-
export const useNotificationCatalog = () => {
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
"notification-catalog-categories",
|
|
44
|
-
() => [],
|
|
45
|
-
);
|
|
46
|
-
const subjects = useDmsState<NotificationSubject[]>(
|
|
47
|
-
"notification-catalog-subjects",
|
|
48
|
-
() => [],
|
|
49
|
-
);
|
|
50
|
-
const isLoaded = useDmsState<boolean>(
|
|
51
|
-
"notification-catalog-loaded",
|
|
52
|
-
() => false,
|
|
52
|
+
export const useNotificationCatalog = (enabled: () => boolean = () => true) => {
|
|
53
|
+
const catalog = useApiData<NotificationCatalogResponse>(() =>
|
|
54
|
+
enabled() ? CATALOG_URL : null,
|
|
53
55
|
);
|
|
56
|
+
const categories = computed(() => catalog.data.value?.categories ?? []);
|
|
57
|
+
const subjects = computed(() => catalog.data.value?.subjects ?? []);
|
|
58
|
+
const isLoaded = computed(() => !!catalog.data.value);
|
|
59
|
+
const loadFailed = computed(() => !isLoaded.value && hasFailed(catalog));
|
|
54
60
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
);
|
|
59
|
-
categories.value = response.categories;
|
|
60
|
-
subjects.value = response.subjects;
|
|
61
|
-
isLoaded.value = true;
|
|
62
|
-
};
|
|
61
|
+
/** Reads the catalog again (a retry). */
|
|
62
|
+
const loadCatalog = (): Promise<void> =>
|
|
63
|
+
catalog.refresh({ dedupe: "defer" });
|
|
63
64
|
|
|
64
65
|
const subjectsOf = (categoryId: string) =>
|
|
65
66
|
subjects.value.filter((subject) => subject.category.id === categoryId);
|
|
@@ -77,6 +78,7 @@ export const useNotificationCatalog = () => {
|
|
|
77
78
|
categories,
|
|
78
79
|
subjects,
|
|
79
80
|
isLoaded,
|
|
81
|
+
loadFailed,
|
|
80
82
|
loadCatalog,
|
|
81
83
|
subjectsOf,
|
|
82
84
|
findCategory,
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { pageRoutePath } from "#dms-core/app/utils/pageRoutes";
|
|
1
2
|
import type { SaveStatusState } from "#dms-ui/app/components/save-bar/SaveStatus.vue";
|
|
2
3
|
import { useInstantSave } from "#dms-ui/app/build/composables/instant-save/useInstantSave";
|
|
3
4
|
import {
|
|
5
|
+
hasFailed,
|
|
4
6
|
type NotificationCategory,
|
|
5
7
|
type NotificationSubject,
|
|
6
8
|
subjectPreferenceKey,
|
|
@@ -9,19 +11,45 @@ import {
|
|
|
9
11
|
|
|
10
12
|
type PreferenceMap = Record<string, boolean>;
|
|
11
13
|
|
|
12
|
-
const PREFERENCES_URL =
|
|
14
|
+
const PREFERENCES_URL = pageRoutePath(
|
|
15
|
+
"/settings/user/notifications",
|
|
16
|
+
"/preferences",
|
|
17
|
+
);
|
|
13
18
|
|
|
14
19
|
/**
|
|
15
|
-
* State of the notification preferences matrix
|
|
16
|
-
*
|
|
17
|
-
*
|
|
20
|
+
* State of the notification preferences matrix, read with the page (the
|
|
21
|
+
* stored preferences and the catalog). Each switch saves on its own through
|
|
22
|
+
* the shared instant save (a PATCH of the keys it changes); a failed save
|
|
23
|
+
* puts the switches back and keeps the change for a retry.
|
|
24
|
+
*
|
|
25
|
+
* @param enabled Whether the page shows them: nothing is read otherwise
|
|
18
26
|
*/
|
|
19
|
-
export const useNotificationPreferences = (
|
|
27
|
+
export const useNotificationPreferences = (
|
|
28
|
+
enabled: () => boolean = () => true,
|
|
29
|
+
) => {
|
|
20
30
|
const { $authFetch } = useAuthFetch();
|
|
21
|
-
const catalog = useNotificationCatalog();
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
31
|
+
const catalog = useNotificationCatalog(enabled);
|
|
32
|
+
const stored = useApiData<PreferenceMap>(() =>
|
|
33
|
+
enabled() ? PREFERENCES_URL : null,
|
|
34
|
+
);
|
|
35
|
+
// The switches edit the read's data in place.
|
|
36
|
+
const preferences = computed<PreferenceMap>({
|
|
37
|
+
get: () => stored.data.value ?? {},
|
|
38
|
+
set: (value) => {
|
|
39
|
+
stored.data.value = value;
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
// A user who never changed a switch has no stored preferences (null).
|
|
43
|
+
const isStoredRead = computed(
|
|
44
|
+
() => stored.status.value === "success" || stored.data.value != null,
|
|
45
|
+
);
|
|
46
|
+
const loadFailed = computed(
|
|
47
|
+
() => (!isStoredRead.value && hasFailed(stored)) || catalog.loadFailed.value,
|
|
48
|
+
);
|
|
49
|
+
const isLoading = computed(
|
|
50
|
+
() =>
|
|
51
|
+
!(isStoredRead.value && catalog.isLoaded.value) && !loadFailed.value,
|
|
52
|
+
);
|
|
25
53
|
|
|
26
54
|
const instant = useInstantSave<PreferenceMap>({
|
|
27
55
|
read: (key) => preferences.value[key] ?? true,
|
|
@@ -54,21 +82,14 @@ export const useNotificationPreferences = () => {
|
|
|
54
82
|
),
|
|
55
83
|
);
|
|
56
84
|
|
|
85
|
+
/** Reads the preferences and the catalog again (a retry). */
|
|
57
86
|
const load = async () => {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
]);
|
|
65
|
-
preferences.value = stored ?? {};
|
|
66
|
-
instant.confirm(preferences.value);
|
|
67
|
-
} catch {
|
|
68
|
-
loadFailed.value = true;
|
|
69
|
-
} finally {
|
|
70
|
-
isLoading.value = false;
|
|
71
|
-
}
|
|
87
|
+
await Promise.all([
|
|
88
|
+
stored.refresh({ dedupe: "defer" }),
|
|
89
|
+
catalog.loadCatalog(),
|
|
90
|
+
]);
|
|
91
|
+
// A failed save then puts back what the server holds now.
|
|
92
|
+
if (stored.status.value === "success") instant.confirm(preferences.value);
|
|
72
93
|
};
|
|
73
94
|
|
|
74
95
|
return {
|
|
@@ -28,7 +28,7 @@ interface BrowserSession {
|
|
|
28
28
|
* account entry, and the old entry — holding a dead refresh token — is dropped.
|
|
29
29
|
*/
|
|
30
30
|
export function useSessionHandoff() {
|
|
31
|
-
const { session, fetch: refreshUser } =
|
|
31
|
+
const { session, fetch: refreshUser } = useDmsUserSession<
|
|
32
32
|
unknown,
|
|
33
33
|
BrowserSession
|
|
34
34
|
>();
|
package/frontend-vue/layers/dms-layout/app/build/composables/settings/profile/preferencesSummary.ts
CHANGED
|
@@ -59,7 +59,7 @@ export function resolvePreferenceRows(
|
|
|
59
59
|
};
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
/** What `GET /settings/user/profile/access` answers. */
|
|
62
|
+
/** What `GET /dms/pages/settings/user/profile/access` answers. */
|
|
63
63
|
export interface ProfileAccess {
|
|
64
64
|
roles: string[];
|
|
65
65
|
workspaceOwner: boolean;
|
package/frontend-vue/layers/dms-layout/app/build/composables/settings/profile/useAccountData.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { pageRoutePath } from "#dms-core/app/utils/pageRoutes";
|
|
1
2
|
import UFormField from "@nuxt/ui/components/FormField.vue";
|
|
2
3
|
import UIcon from "@nuxt/ui/runtime/vue/components/Icon.vue";
|
|
3
4
|
import UInput from "@nuxt/ui/components/Input.vue";
|
|
@@ -12,7 +13,7 @@ import { resolveFieldErrors } from "#dms-core/app/composables/useFieldErrors";
|
|
|
12
13
|
import { REQUIRED_MESSAGE } from "#dms-core/app/composables/useFormValidation";
|
|
13
14
|
import { useSecurityFormat } from "../security/useSecurityFormat";
|
|
14
15
|
|
|
15
|
-
const PROFILE_ENDPOINT = "/settings/user/profile";
|
|
16
|
+
const PROFILE_ENDPOINT = pageRoutePath("/settings/user/profile");
|
|
16
17
|
const EXPORT_ENDPOINT = `${PROFILE_ENDPOINT}/account-export`;
|
|
17
18
|
const DELETION_ENDPOINT = `${PROFILE_ENDPOINT}/account-deletion`;
|
|
18
19
|
const EXPORT_FILE_PREFIX = "dms-account-export";
|
|
@@ -64,7 +65,7 @@ export function accountExportFilename(at: Date = new Date()): string {
|
|
|
64
65
|
export function useAccountData() {
|
|
65
66
|
const { $authFetch } = useAuthFetch();
|
|
66
67
|
const { confirm } = useConfirm();
|
|
67
|
-
const { user, clear } =
|
|
68
|
+
const { user, clear } = useDmsUserSession<User>();
|
|
68
69
|
const { removeAccount } = useMultiAccount();
|
|
69
70
|
const { processI18n, processApiMessage } = useTranslation();
|
|
70
71
|
const { errorMessage, errorCode } = useSecurityFormat();
|
package/frontend-vue/layers/dms-layout/app/build/composables/settings/profile/useProfileAvatar.ts
CHANGED
|
@@ -12,7 +12,6 @@ import {
|
|
|
12
12
|
} from "#dms-ui/app/utils/imageResize";
|
|
13
13
|
|
|
14
14
|
const PRESIGN_URL = "/api/files/presign";
|
|
15
|
-
const METADATA_URL = "/api/files/metadata";
|
|
16
15
|
|
|
17
16
|
/** Upload options the backend serialized for the avatar image field. */
|
|
18
17
|
export interface AvatarFieldOptions {
|
|
@@ -25,10 +24,6 @@ export interface AvatarFieldOptions {
|
|
|
25
24
|
/** Why a picked file was refused before any upload. */
|
|
26
25
|
export type AvatarRejection = "mimetype" | "size";
|
|
27
26
|
|
|
28
|
-
interface FileMetadataResponse {
|
|
29
|
-
url: string;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
27
|
/** Outcome of an avatar upload: a staged key, or why it was refused. */
|
|
33
28
|
export interface AvatarUploadResult {
|
|
34
29
|
key?: string;
|
|
@@ -79,17 +74,5 @@ export function useProfileAvatar(options: () => AvatarFieldOptions) {
|
|
|
79
74
|
return { key: presign.resourceKey };
|
|
80
75
|
}
|
|
81
76
|
|
|
82
|
-
|
|
83
|
-
async function resolveUrl(key: string): Promise<string | null> {
|
|
84
|
-
try {
|
|
85
|
-
const metadata = await $authFetch<FileMetadataResponse>(METADATA_URL, {
|
|
86
|
-
query: { resourceKey: key, storage: options().storage },
|
|
87
|
-
});
|
|
88
|
-
return metadata.url;
|
|
89
|
-
} catch {
|
|
90
|
-
return null;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
return { progress, upload, resolveUrl };
|
|
77
|
+
return { progress, upload };
|
|
95
78
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { pageRoutePath } from "#dms-core/app/utils/pageRoutes";
|
|
1
2
|
import { usePreferredDark } from "@vueuse/core";
|
|
2
3
|
import { computed, onMounted, ref } from "vue";
|
|
3
4
|
import { useUserRegionalPreferences } from "#dms-core/app/composables/user/useUserRegionalPreferences";
|
|
@@ -24,7 +25,10 @@ import {
|
|
|
24
25
|
tallySubjects,
|
|
25
26
|
} from "./preferencesSummary";
|
|
26
27
|
|
|
27
|
-
export const PROFILE_ACCESS_ENDPOINT =
|
|
28
|
+
export const PROFILE_ACCESS_ENDPOINT = pageRoutePath(
|
|
29
|
+
"/settings/user/profile",
|
|
30
|
+
"/access",
|
|
31
|
+
);
|
|
28
32
|
// Sample values the skeletons are shaped from.
|
|
29
33
|
const SAMPLE_ACCESS: ProfileAccess = {
|
|
30
34
|
roles: ["Admin", "Finance"],
|
|
@@ -50,16 +54,15 @@ const MONDAY = 1;
|
|
|
50
54
|
* and each one's sentence, `undefined` while it is not known yet (the row
|
|
51
55
|
* shows a skeleton then, never a made-up "0 unread").
|
|
52
56
|
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
* own; the regional and appearance sentences, which depend on
|
|
56
|
-
* (time zone, today's date, the system theme), are written after
|
|
57
|
-
* the server and the hydrating browser render the same skeleton.
|
|
57
|
+
* The roles and the notification preferences are read with the page (a
|
|
58
|
+
* server render shows them and hands them over); the unread count is the
|
|
59
|
+
* header bell's own; the regional and appearance sentences, which depend on
|
|
60
|
+
* the browser (time zone, today's date, the system theme), are written after
|
|
61
|
+
* mount so the server and the hydrating browser render the same skeleton.
|
|
58
62
|
*/
|
|
59
63
|
export function useProfilePreferences() {
|
|
60
64
|
const { t: translate, locale } = useI18n();
|
|
61
65
|
const t = translate as SummaryTranslate;
|
|
62
|
-
const { $authFetch } = useAuthFetch();
|
|
63
66
|
const { groups } = useSettingsNavigation();
|
|
64
67
|
const isOwner = useIsOwner();
|
|
65
68
|
const isMounted = ref(false);
|
|
@@ -76,23 +79,14 @@ export function useProfilePreferences() {
|
|
|
76
79
|
),
|
|
77
80
|
);
|
|
78
81
|
|
|
79
|
-
const access =
|
|
80
|
-
const accessFailed = ref(false);
|
|
82
|
+
const access = useApiData<ProfileAccess>(PROFILE_ACCESS_ENDPOINT);
|
|
81
83
|
const accessSummary = computed(() => {
|
|
82
|
-
if (access.value) return buildAccessSummary(t, access.value);
|
|
83
|
-
return
|
|
84
|
+
if (access.data.value) return buildAccessSummary(t, access.data.value);
|
|
85
|
+
return access.error.value
|
|
84
86
|
? t("page.settings.profile.preferences.access_unavailable")
|
|
85
87
|
: undefined;
|
|
86
88
|
});
|
|
87
89
|
|
|
88
|
-
async function loadAccess(): Promise<void> {
|
|
89
|
-
try {
|
|
90
|
-
access.value = await $authFetch<ProfileAccess>(PROFILE_ACCESS_ENDPOINT);
|
|
91
|
-
} catch {
|
|
92
|
-
accessFailed.value = true;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
90
|
const { uniqueLocales } = useUniqueLocales();
|
|
97
91
|
const regional = useUserRegionalPreferences();
|
|
98
92
|
// Frozen at mount: the row shows formats, not a ticking clock.
|
|
@@ -117,8 +111,12 @@ export function useProfilePreferences() {
|
|
|
117
111
|
});
|
|
118
112
|
});
|
|
119
113
|
|
|
120
|
-
const notificationPreferences = useNotificationPreferences(
|
|
121
|
-
|
|
114
|
+
const notificationPreferences = useNotificationPreferences(
|
|
115
|
+
() => rows.value.notifications.visible,
|
|
116
|
+
);
|
|
117
|
+
const { subjects } = useNotificationCatalog(
|
|
118
|
+
() => rows.value.notifications.visible,
|
|
119
|
+
);
|
|
122
120
|
const { unreadCount, unreadTone, areCountsLoaded } = useNotifications();
|
|
123
121
|
const isNotificationsLoading = computed(
|
|
124
122
|
() => notificationPreferences.isLoading.value || !areCountsLoaded.value,
|
|
@@ -188,8 +186,6 @@ export function useProfilePreferences() {
|
|
|
188
186
|
onMounted(() => {
|
|
189
187
|
today = new Date();
|
|
190
188
|
isMounted.value = true;
|
|
191
|
-
void loadAccess();
|
|
192
|
-
if (rows.value.notifications.visible) void notificationPreferences.load();
|
|
193
189
|
});
|
|
194
190
|
|
|
195
191
|
return {
|
package/frontend-vue/layers/dms-layout/app/build/composables/settings/security/useSecurityFormat.ts
CHANGED
|
@@ -53,9 +53,12 @@ export function useSecurityFormat() {
|
|
|
53
53
|
Math.floor((Date.now() - new Date(value).getTime()) / MS_PER_DAY),
|
|
54
54
|
);
|
|
55
55
|
|
|
56
|
-
/**
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
/**
|
|
57
|
+
* "3 days ago", "in an hour"… or "now" under a minute, as of `now` (a
|
|
58
|
+
* list's read time, which the server and the hydrating browser share).
|
|
59
|
+
*/
|
|
60
|
+
const formatRelative = (value: DateInput, now = Date.now()): string => {
|
|
61
|
+
const elapsed = new Date(value).getTime() - now;
|
|
59
62
|
const formatter = new Intl.RelativeTimeFormat(locale.value, {
|
|
60
63
|
numeric: "auto",
|
|
61
64
|
});
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import { pageRoutePath } from "#dms-core/app/utils/pageRoutes";
|
|
1
2
|
import { useNavBadges } from "#dms-ui/app/build/composables/navigation/useNavBadges";
|
|
2
3
|
import type { Tone } from "#dms-ui/app/types/tone";
|
|
3
4
|
|
|
4
5
|
/** Base URL of the Security page API. */
|
|
5
|
-
export const SECURITY_ENDPOINT = "/settings/user/security";
|
|
6
|
+
export const SECURITY_ENDPOINT = pageRoutePath("/settings/user/security");
|
|
6
7
|
/** Route of the Security page. */
|
|
7
8
|
export const SECURITY_PAGE_PATH = "/settings/user/security";
|
|
8
9
|
const SECURITY_PAGE_ID = "settings.user.security";
|
|
@@ -68,21 +69,19 @@ const ATTENTION_TONE_ORDER: Tone[] = [
|
|
|
68
69
|
|
|
69
70
|
/**
|
|
70
71
|
* Shared Security summary: the status strip, every Security block and the
|
|
71
|
-
* profile pointer card read the same
|
|
72
|
-
*
|
|
72
|
+
* profile pointer card read the same request, with the page (a server
|
|
73
|
+
* render shows it and hands it over), and any change refreshes it for all
|
|
74
|
+
* of them. It also keeps the navigation badge of the Security page, the
|
|
73
75
|
* count of what needs attention in the tone the server gives it, in sync
|
|
74
76
|
* after a change.
|
|
75
77
|
*/
|
|
76
78
|
export function useSecurityOverview() {
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
79
|
+
const summary = useApiData<SecurityOverview>(SECURITY_ENDPOINT);
|
|
80
|
+
const overview = computed(() => summary.data.value ?? null);
|
|
81
|
+
// A retry in progress is not a failure to show.
|
|
82
|
+
const isUnavailable = computed(
|
|
83
|
+
() => !!summary.error.value && summary.status.value !== "pending",
|
|
80
84
|
);
|
|
81
|
-
const isUnavailable = useDmsState<boolean>(
|
|
82
|
-
"dms-security-overview-unavailable",
|
|
83
|
-
() => false,
|
|
84
|
-
);
|
|
85
|
-
const { $authFetch } = useAuthFetch();
|
|
86
85
|
const { setNavBadge } = useNavBadges();
|
|
87
86
|
|
|
88
87
|
const attention = computed<SecurityAttention[]>(
|
|
@@ -105,14 +104,10 @@ export function useSecurityOverview() {
|
|
|
105
104
|
return ATTENTION_TONE_ORDER.find((tone) => present.includes(tone));
|
|
106
105
|
}
|
|
107
106
|
|
|
107
|
+
/** Reads the summary again after a change: every block follows. */
|
|
108
108
|
async function refresh(): Promise<void> {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
isUnavailable.value = false;
|
|
112
|
-
} catch {
|
|
113
|
-
isUnavailable.value = true;
|
|
114
|
-
return;
|
|
115
|
-
}
|
|
109
|
+
await summary.refresh({ dedupe: "defer" });
|
|
110
|
+
if (summary.error.value) return;
|
|
116
111
|
const count = attention.value.length;
|
|
117
112
|
setNavBadge(
|
|
118
113
|
SECURITY_PAGE_ID,
|
|
@@ -2,6 +2,7 @@ import type {
|
|
|
2
2
|
ModuleCatalogEntry,
|
|
3
3
|
ModuleStatus,
|
|
4
4
|
PageInfo,
|
|
5
|
+
SiteLayout,
|
|
5
6
|
} from "../../types/page";
|
|
6
7
|
|
|
7
8
|
/** Category of the modules that declare none. */
|
|
@@ -148,11 +149,10 @@ export function sortModules(
|
|
|
148
149
|
|
|
149
150
|
/**
|
|
150
151
|
* A page as `/dms/sitelayout` serves it: the payload also carries the
|
|
151
|
-
* owning module
|
|
152
|
+
* owning module, which the shared type leaves out.
|
|
152
153
|
*/
|
|
153
154
|
type ServedPage = PageInfo & {
|
|
154
155
|
module?: string;
|
|
155
|
-
category?: { displayName?: string; isModuleRoot?: boolean };
|
|
156
156
|
};
|
|
157
157
|
|
|
158
158
|
function isOpenablePage(page: ServedPage): boolean {
|
|
@@ -169,19 +169,21 @@ function isOpenablePage(page: ServedPage): boolean {
|
|
|
169
169
|
* the empty state can point to where the thing actually lives.
|
|
170
170
|
*/
|
|
171
171
|
export function findModulePageMatch(
|
|
172
|
-
|
|
172
|
+
siteLayout: Pick<SiteLayout, "pages" | "categories"> | undefined,
|
|
173
173
|
query: string,
|
|
174
174
|
installedModuleIds: ReadonlySet<string>,
|
|
175
175
|
resolve: DisplayResolver,
|
|
176
176
|
): ModulePageMatch | null {
|
|
177
177
|
const needle = normalizeQuery(query);
|
|
178
|
-
if (!needle || !
|
|
179
|
-
for (const page of Object.values(pages) as ServedPage[]) {
|
|
178
|
+
if (!needle || !siteLayout) return null;
|
|
179
|
+
for (const page of Object.values(siteLayout.pages) as ServedPage[]) {
|
|
180
180
|
if (!page.module || !installedModuleIds.has(page.module)) continue;
|
|
181
181
|
if (!isOpenablePage(page)) continue;
|
|
182
182
|
const pageTitle = resolve(page.displayName);
|
|
183
183
|
if (!pageTitle.toLocaleLowerCase().includes(needle)) continue;
|
|
184
|
-
const parent = page.
|
|
184
|
+
const parent = page.categoryId
|
|
185
|
+
? siteLayout.categories[page.categoryId]
|
|
186
|
+
: undefined;
|
|
185
187
|
const sectionTitle =
|
|
186
188
|
parent?.displayName && !parent.isModuleRoot
|
|
187
189
|
? resolve(parent.displayName)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// A fetch failure names the request it failed: `[GET] "http://10.0.0.4:5010/
|
|
2
|
+
// dms/page?path=/x": 502 Bad Gateway`. On a server, that is the backend's
|
|
3
|
+
// internal address, which an error page must never show.
|
|
4
|
+
const FETCH_FAILURE = /^\[[A-Z]+\]\s+"[^"]*":\s*(.*)$/s;
|
|
5
|
+
const ABSOLUTE_URL = /\b[a-z][a-z0-9+.-]*:\/\/[^\s"'<>]+/gi;
|
|
6
|
+
const SPACES = /\s{2,}/g;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The part of an error's message a page may show: a fetch failure's status
|
|
10
|
+
* text alone, and no absolute URL anywhere; nothing when nothing is left.
|
|
11
|
+
*/
|
|
12
|
+
export function publicErrorMessage(
|
|
13
|
+
message: string | undefined,
|
|
14
|
+
): string | undefined {
|
|
15
|
+
if (!message) return undefined;
|
|
16
|
+
const statusText = FETCH_FAILURE.exec(message)?.[1] ?? message;
|
|
17
|
+
const shown = statusText.replace(ABSOLUTE_URL, "").replace(SPACES, " ");
|
|
18
|
+
return shown.trim() || undefined;
|
|
19
|
+
}
|
|
@@ -102,15 +102,6 @@ const paddingLeft = computed(() => {
|
|
|
102
102
|
const chevronClass = (isOpen: boolean) => {
|
|
103
103
|
return theme({ isOpen }).chevron();
|
|
104
104
|
};
|
|
105
|
-
|
|
106
|
-
const { prefetchPageLayout } = usePrefetch();
|
|
107
|
-
|
|
108
|
-
function onLinkHover(): void {
|
|
109
|
-
const { to } = props.item;
|
|
110
|
-
if (typeof to === "string") {
|
|
111
|
-
prefetchPageLayout(stripQueryAndHash(to));
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
105
|
</script>
|
|
115
106
|
|
|
116
107
|
<template>
|
|
@@ -128,7 +119,6 @@ function onLinkHover(): void {
|
|
|
128
119
|
:is="item.to ? DmsLink : 'div'"
|
|
129
120
|
:to="item.to"
|
|
130
121
|
:class="ui.topLevelButton()"
|
|
131
|
-
@mouseenter="onLinkHover"
|
|
132
122
|
>
|
|
133
123
|
<UIcon v-if="item.icon" :name="item.icon" :class="ui.icon()" />
|
|
134
124
|
<UIcon
|
|
@@ -150,7 +140,6 @@ function onLinkHover(): void {
|
|
|
150
140
|
:class="ui.link()"
|
|
151
141
|
:style="{ paddingLeft }"
|
|
152
142
|
:title="item.title"
|
|
153
|
-
@mouseenter="onLinkHover"
|
|
154
143
|
>
|
|
155
144
|
<UIcon v-if="item.icon" :name="item.icon" :class="ui.icon()" />
|
|
156
145
|
<span>{{ item.label }}</span>
|
|
@@ -206,6 +206,9 @@ function decorateMenuItem(item: DmsMenuItem): DmsMenuItem {
|
|
|
206
206
|
|
|
207
207
|
return {
|
|
208
208
|
...item,
|
|
209
|
+
// Inertia prefetches the page on hover, the way a `DmsLink` does: the
|
|
210
|
+
// visit then shows it without a request, and its components load early.
|
|
211
|
+
...(item.to ? { prefetch: true } : {}),
|
|
209
212
|
...(variant.link ? { class: [item.class, variant.link] } : {}),
|
|
210
213
|
...(statusClass ? { trailingIcon: STATUS_DOT_ICON } : {}),
|
|
211
214
|
badge,
|
|
@@ -232,18 +235,6 @@ const groupedItems = computed((): NavigationMenuItem[][] => {
|
|
|
232
235
|
);
|
|
233
236
|
});
|
|
234
237
|
|
|
235
|
-
const { prefetchPageLayout } = usePrefetch();
|
|
236
|
-
|
|
237
|
-
function onMenuMouseover(event: MouseEvent): void {
|
|
238
|
-
const target = (event.target as HTMLElement).closest("a[href]");
|
|
239
|
-
if (!target) return;
|
|
240
|
-
|
|
241
|
-
const href = target.getAttribute("href");
|
|
242
|
-
if (href && href.startsWith("/")) {
|
|
243
|
-
prefetchPageLayout(stripQueryAndHash(href));
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
|
|
247
238
|
function isDesiredOpen(id: string): boolean {
|
|
248
239
|
return props.modelValue?.includes(id) ?? false;
|
|
249
240
|
}
|
|
@@ -271,7 +262,6 @@ function onStateChange(id: string, open: boolean): void {
|
|
|
271
262
|
:orientation="orientation"
|
|
272
263
|
:highlight="orientation === 'vertical'"
|
|
273
264
|
value-key="id"
|
|
274
|
-
@mouseover="onMenuMouseover"
|
|
275
265
|
>
|
|
276
266
|
<template #item-leading="{ item, active, ui }">
|
|
277
267
|
<DmsAccordionItemSync
|
|
@@ -16,17 +16,13 @@ export const useSystemState = () => {
|
|
|
16
16
|
data: systemState,
|
|
17
17
|
error,
|
|
18
18
|
refresh,
|
|
19
|
-
} =
|
|
20
|
-
SYSTEM_STATE_CACHE_KEY,
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
default: () => ({ ...DEFAULT_SYSTEM_STATE }),
|
|
27
|
-
server: true,
|
|
28
|
-
},
|
|
29
|
-
);
|
|
19
|
+
} = useApiData<SystemState>(SYSTEM_STATE_ENDPOINT, {
|
|
20
|
+
key: SYSTEM_STATE_CACHE_KEY,
|
|
21
|
+
lazy: true,
|
|
22
|
+
default: () => ({ ...DEFAULT_SYSTEM_STATE }),
|
|
23
|
+
// The deployment's state, not the page's data.
|
|
24
|
+
refreshOnPageRefresh: false,
|
|
25
|
+
});
|
|
30
26
|
|
|
31
27
|
const metaTitle = computed(() => systemState.value?.meta?.title ?? "");
|
|
32
28
|
const metaDescription = computed(
|