@antelopejs/dms 0.6.2 → 0.7.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 +22 -0
- package/dist/db/models/notification-feed-filter.d.ts +0 -1
- package/dist/db/models/notification-feed-filter.js +0 -4
- package/dist/db/models/notification-feed-filter.js.map +1 -1
- package/dist/db/models/signInAttempts.model.d.ts +18 -0
- package/dist/db/models/signInAttempts.model.js +83 -12
- package/dist/db/models/signInAttempts.model.js.map +1 -1
- package/dist/db/models/userEmailChanges.model.d.ts +8 -1
- package/dist/db/models/userEmailChanges.model.js +27 -2
- package/dist/db/models/userEmailChanges.model.js.map +1 -1
- package/dist/db/models/userNotificationPreferences.model.d.ts +0 -2
- package/dist/db/models/userNotificationPreferences.model.js +0 -13
- package/dist/db/models/userNotificationPreferences.model.js.map +1 -1
- package/dist/db/models/userNotifications.model.d.ts +5 -2
- package/dist/db/models/userNotifications.model.js +19 -2
- package/dist/db/models/userNotifications.model.js.map +1 -1
- package/dist/db/tables/signInAttempts.table.d.ts +2 -1
- package/dist/db/tables/signInAttempts.table.js +4 -0
- package/dist/db/tables/signInAttempts.table.js.map +1 -1
- package/dist/db/tables/userEmailChanges.table.d.ts +2 -1
- package/dist/db/tables/userEmailChanges.table.js +6 -1
- package/dist/db/tables/userEmailChanges.table.js.map +1 -1
- package/dist/db/tables/userNotificationPreferences.table.d.ts +0 -1
- package/dist/db/tables/userNotificationPreferences.table.js +0 -4
- package/dist/db/tables/userNotificationPreferences.table.js.map +1 -1
- package/dist/implementations/dms/nav-badges.d.ts +6 -4
- package/dist/implementations/dms/nav-badges.js +6 -5
- package/dist/implementations/dms/nav-badges.js.map +1 -1
- package/dist/implementations/dms/page.d.ts +1 -0
- package/dist/implementations/dms/page.js +46 -8
- package/dist/implementations/dms/page.js.map +1 -1
- package/dist/implementations/dms/permission-preview.d.ts +1 -1
- package/dist/implementations/dms/permission-preview.js +9 -4
- package/dist/implementations/dms/permission-preview.js.map +1 -1
- package/dist/implementations/dms-auth/index.d.ts +3 -2
- package/dist/implementations/dms-auth/index.js +39 -35
- package/dist/implementations/dms-auth/index.js.map +1 -1
- package/dist/implementations/dms-auth/token-session.d.ts +3 -0
- package/dist/implementations/dms-auth/token-session.js +22 -0
- package/dist/implementations/dms-auth/token-session.js.map +1 -0
- package/dist/implementations/dms-notifications/index.d.ts +0 -1
- package/dist/implementations/dms-notifications/index.js +0 -7
- 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/account-activity-feed.d.ts +1 -1
- package/dist/pages/settings/users/account-activity-feed.js +1 -1
- package/dist/pages/settings/users/account-activity-feed.js.map +1 -1
- package/dist/pages/settings/users/account-activity.d.ts +1 -1
- package/dist/pages/settings/users/account-activity.js +1 -1
- package/dist/pages/settings/users/account-activity.js.map +1 -1
- package/dist/pages/settings/users/account-credentials.d.ts +3 -0
- package/dist/pages/settings/users/account-credentials.js +2 -0
- package/dist/pages/settings/users/account-credentials.js.map +1 -1
- package/dist/pages/settings/users/current-password.d.ts +1 -1
- package/dist/pages/settings/users/current-password.js +23 -2
- package/dist/pages/settings/users/current-password.js.map +1 -1
- package/dist/pages/settings/users/email-change.d.ts +1 -1
- package/dist/pages/settings/users/email-change.js +33 -26
- package/dist/pages/settings/users/email-change.js.map +1 -1
- package/dist/pages/settings/users/invite-resend.js +0 -1
- package/dist/pages/settings/users/invite-resend.js.map +1 -1
- package/dist/pages/settings/users/invites.d.ts +1 -2
- package/dist/pages/settings/users/invites.js +21 -19
- package/dist/pages/settings/users/invites.js.map +1 -1
- package/dist/pages/settings/users/member-invite-batch.js +0 -1
- package/dist/pages/settings/users/member-invite-batch.js.map +1 -1
- package/dist/pages/settings/users/member-invite-form.js +10 -22
- package/dist/pages/settings/users/member-invite-form.js.map +1 -1
- package/dist/pages/settings/users/member-role-options.d.ts +6 -4
- package/dist/pages/settings/users/member-role-options.js +15 -4
- package/dist/pages/settings/users/member-role-options.js.map +1 -1
- package/dist/pages/settings/users/members.d.ts +2 -3
- package/dist/pages/settings/users/members.js +7 -12
- package/dist/pages/settings/users/members.js.map +1 -1
- package/dist/pages/settings/users/notifications.d.ts +1 -8
- package/dist/pages/settings/users/notifications.js +6 -38
- package/dist/pages/settings/users/notifications.js.map +1 -1
- package/dist/pages/settings/users/permission-tree-nodes.d.ts +1 -1
- package/dist/pages/settings/users/permission-tree-nodes.js +23 -9
- package/dist/pages/settings/users/permission-tree-nodes.js.map +1 -1
- package/dist/pages/settings/users/permission-warnings.d.ts +7 -0
- package/dist/pages/settings/users/permission-warnings.js +42 -0
- package/dist/pages/settings/users/permission-warnings.js.map +1 -0
- package/dist/pages/settings/users/profile-helpers.d.ts +1 -3
- package/dist/pages/settings/users/profile-helpers.js +0 -16
- package/dist/pages/settings/users/profile-helpers.js.map +1 -1
- package/dist/pages/settings/users/role-editor-store.js +6 -3
- package/dist/pages/settings/users/role-editor-store.js.map +1 -1
- package/dist/pages/settings/users/role-editor.d.ts +2 -1
- package/dist/pages/settings/users/role-editor.js +8 -4
- package/dist/pages/settings/users/role-editor.js.map +1 -1
- package/dist/pages/settings/users/role-preview.js +2 -1
- package/dist/pages/settings/users/role-preview.js.map +1 -1
- package/dist/pages/settings/users/roles.js +5 -1
- package/dist/pages/settings/users/roles.js.map +1 -1
- package/dist/pages/settings/users/security-attention.d.ts +4 -0
- package/dist/pages/settings/users/security-attention.js +23 -7
- package/dist/pages/settings/users/security-attention.js.map +1 -1
- package/dist/pages/settings/users/security.d.ts +2 -1
- package/dist/pages/settings/users/security.js +13 -2
- package/dist/pages/settings/users/security.js.map +1 -1
- package/dist/pages/settings/users/two-factor-operations.d.ts +2 -1
- package/dist/pages/settings/users/two-factor-operations.js +28 -29
- package/dist/pages/settings/users/two-factor-operations.js.map +1 -1
- package/dist/routes/auth/forgot-password.js +3 -0
- package/dist/routes/auth/forgot-password.js.map +1 -1
- package/dist/routes/auth/index.d.ts +1 -0
- package/dist/routes/auth/index.js +1 -0
- package/dist/routes/auth/index.js.map +1 -1
- package/dist/routes/auth/invite.js +28 -4
- package/dist/routes/auth/invite.js.map +1 -1
- package/dist/routes/auth/login-throttle.d.ts +2 -0
- package/dist/routes/auth/login-throttle.js +5 -4
- package/dist/routes/auth/login-throttle.js.map +1 -1
- package/dist/routes/auth/request-2fa-email.js +6 -18
- package/dist/routes/auth/request-2fa-email.js.map +1 -1
- package/dist/routes/auth/reset-code.d.ts +3 -0
- package/dist/routes/auth/reset-code.js +37 -0
- package/dist/routes/auth/reset-code.js.map +1 -0
- package/dist/routes/auth/reset-password.d.ts +2 -2
- package/dist/routes/auth/reset-password.js +5 -10
- package/dist/routes/auth/reset-password.js.map +1 -1
- package/dist/routes/auth/signup.js +10 -32
- package/dist/routes/auth/signup.js.map +1 -1
- package/dist/routes/auth/two-factor-throttle.d.ts +5 -0
- package/dist/routes/auth/two-factor-throttle.js +81 -0
- package/dist/routes/auth/two-factor-throttle.js.map +1 -0
- package/dist/routes/auth/validate-forgot-password-token.js +2 -8
- package/dist/routes/auth/validate-forgot-password-token.js.map +1 -1
- package/dist/routes/auth/validate-invite-token.d.ts +1 -0
- package/dist/routes/auth/validate-invite-token.js +11 -0
- package/dist/routes/auth/validate-invite-token.js.map +1 -0
- package/dist/routes/auth/verify-2fa.js +10 -13
- package/dist/routes/auth/verify-2fa.js.map +1 -1
- package/dist/routes/auth.d.ts +1 -0
- package/dist/routes/auth.js +11 -1
- package/dist/routes/auth.js.map +1 -1
- package/dist/test/helpers/auth.d.ts +0 -1
- package/dist/test/helpers/auth.js +0 -1
- package/dist/test/helpers/auth.js.map +1 -1
- package/dist/test/helpers/emails.d.ts +10 -0
- package/dist/test/helpers/emails.js +36 -0
- package/dist/test/helpers/emails.js.map +1 -0
- package/dist/test/helpers/fixtures.d.ts +0 -1
- package/dist/test/helpers/fixtures.js +1 -2
- package/dist/test/helpers/fixtures.js.map +1 -1
- package/dist/test/helpers/page-access.d.ts +1 -0
- package/dist/test/helpers/page-access.js +6 -1
- package/dist/test/helpers/page-access.js.map +1 -1
- package/dist/test/integration/auth/current-password.test.d.ts +1 -0
- package/dist/test/integration/auth/current-password.test.js +65 -0
- package/dist/test/integration/auth/current-password.test.js.map +1 -0
- package/dist/test/integration/auth/password-reset.test.d.ts +1 -0
- package/dist/test/integration/auth/password-reset.test.js +126 -0
- package/dist/test/integration/auth/password-reset.test.js.map +1 -0
- package/dist/test/integration/auth/session-revocation.test.d.ts +1 -0
- package/dist/test/integration/auth/session-revocation.test.js +45 -0
- package/dist/test/integration/auth/session-revocation.test.js.map +1 -0
- package/dist/test/integration/auth/two-factor-codes.test.d.ts +1 -0
- package/dist/test/integration/auth/two-factor-codes.test.js +335 -0
- package/dist/test/integration/auth/two-factor-codes.test.js.map +1 -0
- package/dist/test/integration/auth/two-factor-password.test.js +5 -1
- package/dist/test/integration/auth/two-factor-password.test.js.map +1 -1
- package/dist/test/integration/auth/two-factor-token.test.d.ts +1 -0
- package/dist/test/integration/auth/two-factor-token.test.js +92 -0
- package/dist/test/integration/auth/two-factor-token.test.js.map +1 -0
- package/dist/test/integration/email-change.test.js +36 -0
- package/dist/test/integration/email-change.test.js.map +1 -1
- package/dist/test/integration/invite-extension-edit.test.js +0 -1
- package/dist/test/integration/invite-extension-edit.test.js.map +1 -1
- package/dist/test/integration/invite-link-status.test.d.ts +1 -0
- package/dist/test/integration/invite-link-status.test.js +110 -0
- package/dist/test/integration/invite-link-status.test.js.map +1 -0
- package/dist/test/integration/invite-resolution.test.js +0 -3
- package/dist/test/integration/invite-resolution.test.js.map +1 -1
- package/dist/test/integration/member-settings-access.test.js +17 -0
- package/dist/test/integration/member-settings-access.test.js.map +1 -1
- package/dist/test/integration/notification-unread-badge.test.d.ts +1 -0
- package/dist/test/integration/notification-unread-badge.test.js +92 -0
- package/dist/test/integration/notification-unread-badge.test.js.map +1 -0
- package/dist/test/integration/permission-warnings.test.d.ts +1 -0
- package/dist/test/integration/permission-warnings.test.js +71 -0
- package/dist/test/integration/permission-warnings.test.js.map +1 -0
- package/dist/test/integration/removed-member-notifications.test.d.ts +1 -0
- package/dist/test/integration/removed-member-notifications.test.js +80 -0
- package/dist/test/integration/removed-member-notifications.test.js.map +1 -0
- package/dist/test/integration/role-permission-ancestors.test.d.ts +1 -0
- package/dist/test/integration/role-permission-ancestors.test.js +146 -0
- package/dist/test/integration/role-permission-ancestors.test.js.map +1 -0
- package/dist/test/integration/tenant-lifecycle.test.js +0 -4
- package/dist/test/integration/tenant-lifecycle.test.js.map +1 -1
- package/dist/test/unit/auth-session-revocation.test.d.ts +1 -0
- package/dist/test/unit/auth-session-revocation.test.js +105 -0
- package/dist/test/unit/auth-session-revocation.test.js.map +1 -0
- package/dist/test/unit/db/email-normalization.test.js +0 -1
- package/dist/test/unit/db/email-normalization.test.js.map +1 -1
- package/dist/test/unit/db/notification-feed-filter.test.js +0 -16
- package/dist/test/unit/db/notification-feed-filter.test.js.map +1 -1
- package/dist/test/unit/db/notification-inbox.test.js +63 -13
- package/dist/test/unit/db/notification-inbox.test.js.map +1 -1
- package/dist/test/unit/db/notification-preferences.test.js +0 -17
- package/dist/test/unit/db/notification-preferences.test.js.map +1 -1
- package/dist/test/unit/implementations/dms/empty-menu-group.test.d.ts +1 -0
- package/dist/test/unit/implementations/dms/empty-menu-group.test.js +133 -0
- package/dist/test/unit/implementations/dms/empty-menu-group.test.js.map +1 -0
- package/dist/test/unit/implementations/dms/layout-banners.test.js +2 -1
- package/dist/test/unit/implementations/dms/layout-banners.test.js.map +1 -1
- package/dist/test/unit/implementations/dms/menu-hidden-blocks.test.d.ts +1 -0
- package/dist/test/unit/implementations/dms/menu-hidden-blocks.test.js +149 -0
- package/dist/test/unit/implementations/dms/menu-hidden-blocks.test.js.map +1 -0
- package/dist/test/unit/implementations/dms/nav-badges.test.js +55 -4
- package/dist/test/unit/implementations/dms/nav-badges.test.js.map +1 -1
- package/dist/test/unit/implementations/dms/page-all-components-hidden.test.d.ts +1 -0
- package/dist/test/unit/implementations/dms/page-all-components-hidden.test.js +118 -0
- package/dist/test/unit/implementations/dms/page-all-components-hidden.test.js.map +1 -0
- package/dist/test/unit/implementations/dms/permission-preview-aggregate.test.js +16 -0
- package/dist/test/unit/implementations/dms/permission-preview-aggregate.test.js.map +1 -1
- package/dist/test/unit/implementations/dms/permission-preview-cost.test.js +5 -3
- package/dist/test/unit/implementations/dms/permission-preview-cost.test.js.map +1 -1
- package/dist/test/unit/implementations/dms/permission-preview-menu.test.js +29 -3
- package/dist/test/unit/implementations/dms/permission-preview-menu.test.js.map +1 -1
- package/dist/test/unit/implementations/dms/quick-actions-button.test.js +4 -2
- package/dist/test/unit/implementations/dms/quick-actions-button.test.js.map +1 -1
- package/dist/test/unit/implementations/dms/user-permissions-gate.test.js +2 -1
- package/dist/test/unit/implementations/dms/user-permissions-gate.test.js.map +1 -1
- package/dist/test/unit/implementations/dms-base/table-view-shared-controller.test.js +2 -1
- package/dist/test/unit/implementations/dms-base/table-view-shared-controller.test.js.map +1 -1
- package/dist/test/unit/interfaces/dms/component-children-permissions.test.js +7 -2
- package/dist/test/unit/interfaces/dms/component-children-permissions.test.js.map +1 -1
- package/dist/test/unit/interfaces/dms/form-watch-permissions.test.d.ts +1 -0
- package/dist/test/unit/interfaces/dms/form-watch-permissions.test.js +133 -0
- package/dist/test/unit/interfaces/dms/form-watch-permissions.test.js.map +1 -0
- package/dist/test/unit/interfaces/dms/has-permission-ancestors.test.d.ts +1 -0
- package/dist/test/unit/interfaces/dms/has-permission-ancestors.test.js +149 -0
- package/dist/test/unit/interfaces/dms/has-permission-ancestors.test.js.map +1 -0
- package/dist/test/unit/interfaces/dms/invite-extension.test.js +13 -0
- package/dist/test/unit/interfaces/dms/invite-extension.test.js.map +1 -1
- package/dist/test/unit/interfaces/dms/member-access.test.js +27 -1
- package/dist/test/unit/interfaces/dms/member-access.test.js.map +1 -1
- package/dist/test/unit/interfaces/dms/nav-badge-tones.test.d.ts +1 -0
- package/dist/test/unit/interfaces/dms/nav-badge-tones.test.js +31 -0
- package/dist/test/unit/interfaces/dms/nav-badge-tones.test.js.map +1 -0
- package/dist/test/unit/interfaces/dms/page-extension.test.js +4 -1
- package/dist/test/unit/interfaces/dms/page-extension.test.js.map +1 -1
- package/dist/test/unit/interfaces/dms/permission-requirements.test.d.ts +1 -0
- package/dist/test/unit/interfaces/dms/permission-requirements.test.js +144 -0
- package/dist/test/unit/interfaces/dms/permission-requirements.test.js.map +1 -0
- package/dist/test/unit/interfaces/dms-base/layout-header-actions.test.js +6 -2
- package/dist/test/unit/interfaces/dms-base/layout-header-actions.test.js.map +1 -1
- package/dist/test/unit/interfaces/dms-base/lists-and-settings-catalog.test.js +1 -1
- package/dist/test/unit/interfaces/dms-base/lists-and-settings-catalog.test.js.map +1 -1
- package/dist/test/unit/interfaces/dms-base/table-view-export-permission.test.js +2 -1
- package/dist/test/unit/interfaces/dms-base/table-view-export-permission.test.js.map +1 -1
- package/dist/test/unit/interfaces/dms-base/table-view-reduced-chrome.test.js +2 -1
- package/dist/test/unit/interfaces/dms-base/table-view-reduced-chrome.test.js.map +1 -1
- package/dist/test/unit/interfaces/dms-base/table-view-shared-controller-permissions.test.js +2 -1
- package/dist/test/unit/interfaces/dms-base/table-view-shared-controller-permissions.test.js.map +1 -1
- package/dist/test/unit/pages/settings/account-activity-feed.test.js +2 -2
- package/dist/test/unit/pages/settings/account-activity-feed.test.js.map +1 -1
- package/dist/test/unit/pages/settings/account-activity.test.js +11 -2
- package/dist/test/unit/pages/settings/account-activity.test.js.map +1 -1
- package/dist/test/unit/pages/settings/invite-email-delivery.test.js +0 -1
- package/dist/test/unit/pages/settings/invite-email-delivery.test.js.map +1 -1
- package/dist/test/unit/pages/settings/invite-email-validation-option.test.d.ts +1 -0
- package/dist/test/unit/pages/settings/invite-email-validation-option.test.js +33 -0
- package/dist/test/unit/pages/settings/invite-email-validation-option.test.js.map +1 -0
- package/dist/test/unit/pages/settings/member-lists-page-size.test.d.ts +1 -0
- package/dist/test/unit/pages/settings/member-lists-page-size.test.js +22 -0
- package/dist/test/unit/pages/settings/member-lists-page-size.test.js.map +1 -0
- package/dist/test/unit/pages/settings/member-table-widths.test.js +2 -2
- package/dist/test/unit/pages/settings/member-table-widths.test.js.map +1 -1
- package/dist/test/unit/pages/settings/members-page.test.js +7 -10
- package/dist/test/unit/pages/settings/members-page.test.js.map +1 -1
- package/dist/test/unit/pages/settings/notifications-access-gate.test.js +3 -10
- package/dist/test/unit/pages/settings/notifications-access-gate.test.js.map +1 -1
- package/dist/test/unit/pages/settings/permission-tree-nodes.test.js +57 -0
- package/dist/test/unit/pages/settings/permission-tree-nodes.test.js.map +1 -1
- package/dist/test/unit/pages/settings/permission-warnings.test.d.ts +1 -0
- package/dist/test/unit/pages/settings/permission-warnings.test.js +49 -0
- package/dist/test/unit/pages/settings/permission-warnings.test.js.map +1 -0
- package/dist/test/unit/pages/settings/role-editor.test.js +16 -0
- package/dist/test/unit/pages/settings/role-editor.test.js.map +1 -1
- package/dist/test/unit/pages/settings/role-preview.test.js +7 -0
- package/dist/test/unit/pages/settings/role-preview.test.js.map +1 -1
- package/dist/test/unit/pages/settings/security-attention.test.d.ts +1 -0
- package/dist/test/unit/pages/settings/security-attention.test.js +74 -0
- package/dist/test/unit/pages/settings/security-attention.test.js.map +1 -0
- package/dist/test/unit/sanitize-user.test.d.ts +1 -0
- package/dist/test/unit/sanitize-user.test.js +21 -0
- package/dist/test/unit/sanitize-user.test.js.map +1 -0
- package/dist/test/unit/signup-email-validation.test.d.ts +1 -0
- package/dist/test/unit/signup-email-validation.test.js +79 -0
- package/dist/test/unit/signup-email-validation.test.js.map +1 -0
- package/dist/test/unit/validation/member-invite.schema.test.js +14 -52
- package/dist/test/unit/validation/member-invite.schema.test.js.map +1 -1
- package/dist/utils/account-notifications.d.ts +5 -0
- package/dist/utils/account-notifications.js +8 -0
- package/dist/utils/account-notifications.js.map +1 -1
- package/dist/utils/notification-emitter.d.ts +1 -1
- package/dist/utils/notification-emitter.js +1 -1
- package/dist/utils/notification-emitter.js.map +1 -1
- package/dist/utils/notification-tones.d.ts +2 -1
- package/dist/utils/notification-tones.js +5 -1
- package/dist/utils/notification-tones.js.map +1 -1
- package/dist/utils/one-time-code.d.ts +3 -0
- package/dist/utils/one-time-code.js +21 -0
- package/dist/utils/one-time-code.js.map +1 -0
- package/dist/utils/sign-in-monitor.d.ts +1 -0
- package/dist/utils/sign-in-monitor.js +3 -2
- package/dist/utils/sign-in-monitor.js.map +1 -1
- package/dist/utils/two-factor-codes.d.ts +10 -0
- package/dist/utils/two-factor-codes.js +76 -0
- package/dist/utils/two-factor-codes.js.map +1 -0
- package/dist/validation/auth.schema.d.ts +17 -0
- package/dist/validation/auth.schema.js +17 -6
- package/dist/validation/auth.schema.js.map +1 -1
- package/dist/validation/issue-message.d.ts +1 -0
- package/dist/validation/issue-message.js +9 -0
- package/dist/validation/issue-message.js.map +1 -0
- package/dist/validation/member-invite.schema.d.ts +1 -25
- package/dist/validation/member-invite.schema.js +2 -23
- package/dist/validation/member-invite.schema.js.map +1 -1
- package/frontend-vue/dms.frontend.ts +2 -0
- package/frontend-vue/layers/dms-auth/app/custom-pages/auth/forgot-validation.vue +3 -0
- package/frontend-vue/layers/dms-auth/app/custom-pages/auth/signup.vue +105 -5
- package/frontend-vue/layers/dms-auth/i18n/locales/auth-en-GB.json +20 -4
- package/frontend-vue/layers/dms-auth/i18n/locales/auth-fr-FR.json +20 -4
- package/frontend-vue/layers/dms-core/app/composables/useFieldErrors.ts +3 -2
- package/frontend-vue/layers/dms-core/app/types/menu.ts +15 -0
- package/frontend-vue/layers/dms-core/i18n/locales/core-en-GB.json +4 -1
- package/frontend-vue/layers/dms-core/i18n/locales/core-fr-FR.json +4 -1
- package/frontend-vue/layers/dms-layout/app/assets/css/main.css +20 -0
- package/frontend-vue/layers/dms-layout/app/build/components/layout/CommandPaletteAssistantAnswer.vue +75 -0
- package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardFrame.vue +22 -0
- package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardHeader.vue +24 -10
- package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardSidePanel.vue +139 -0
- package/frontend-vue/layers/dms-layout/app/build/components/layout/Footer.vue +4 -2
- package/frontend-vue/layers/dms-layout/app/build/components/layout/GithubStarPrompt.vue +90 -0
- package/frontend-vue/layers/dms-layout/app/build/components/layout/SidePanelHost.vue +17 -0
- package/frontend-vue/layers/dms-layout/app/build/components/notification/NotificationPopover.vue +25 -43
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/members/MemberRolePicker.vue +24 -2
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/notification/NotificationMatrixCategory.vue +70 -25
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/notification/NotificationPreferencesForm.vue +25 -10
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/notification/notificationDisplay.ts +41 -5
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/profile/ProfilePreferencesSummary.vue +2 -1
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/profile/ProfileSecuritySummary.vue +20 -4
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/RoleEditor.vue +21 -0
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/RolePermissionArea.vue +16 -2
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/RolePermissionBranch.vue +1 -0
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/RolePermissionLeaf.vue +13 -1
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/RolePermissionTree.vue +9 -0
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/RolePermissionWarning.vue +46 -0
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/role-permissions.ts +108 -29
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/role-types.ts +2 -0
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/security/SecurityDialogFrame.vue +6 -6
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/security/SecurityEmail.vue +7 -1
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/security/SecuritySessions.vue +5 -4
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/security/SecurityStatus.vue +4 -3
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/security/SecurityTwoFactor.vue +54 -18
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/shell/SettingsNav.vue +13 -8
- package/frontend-vue/layers/dms-layout/app/build/composables/command-palette/assistantGroups.ts +143 -0
- package/frontend-vue/layers/dms-layout/app/build/composables/command-palette/useCommandPaletteAssistantMode.ts +249 -0
- package/frontend-vue/layers/dms-layout/app/build/composables/general/useGithubStarPrompt.ts +178 -0
- package/frontend-vue/layers/dms-layout/app/build/composables/layout/sidePanelState.ts +191 -0
- package/frontend-vue/layers/dms-layout/app/build/composables/layout/useHeaderActionControls.ts +51 -0
- package/frontend-vue/layers/dms-layout/app/build/composables/layout/useSidePanelWidth.ts +113 -0
- package/frontend-vue/layers/dms-layout/app/build/composables/navigation/useCategoryNavCards.ts +17 -10
- package/frontend-vue/layers/dms-layout/app/build/composables/notification/useNotificationGroupsOpen.ts +45 -0
- package/frontend-vue/layers/dms-layout/app/build/composables/settings/profile/useProfilePreferences.ts +8 -1
- package/frontend-vue/layers/dms-layout/app/build/composables/settings/security/useSecurityOverview.ts +50 -3
- package/frontend-vue/layers/dms-layout/app/build/composables/settings/useSettingsNavigation.ts +3 -0
- package/frontend-vue/layers/dms-layout/app/build/utils/categoryPages.ts +1 -2
- package/frontend-vue/layers/dms-layout/app/build/utils/github-repository.ts +5 -0
- package/frontend-vue/layers/dms-layout/app/components/layout/DashboardSearch.vue +109 -7
- package/frontend-vue/layers/dms-layout/app/components/navigation/NavigationMenu.vue +19 -9
- package/frontend-vue/layers/dms-layout/app/composables/notification/useNotifications.ts +22 -40
- package/frontend-vue/layers/dms-layout/app/composables/useAppSidePanels.ts +96 -0
- package/frontend-vue/layers/dms-layout/app/composables/useCommandPaletteAssistant.ts +129 -0
- package/frontend-vue/layers/dms-layout/app/composables/useHeaderActions.ts +33 -11
- package/frontend-vue/layers/dms-layout/app/composables/useSidePanel.ts +41 -0
- package/frontend-vue/layers/dms-layout/app/pages/[...slug].vue +15 -0
- package/frontend-vue/layers/dms-layout/app/plugins/notification-stream.client.ts +1 -7
- package/frontend-vue/layers/dms-layout/app/plugins/side-panel-host.ts +13 -0
- package/frontend-vue/layers/dms-layout/app/types/page.ts +4 -0
- package/frontend-vue/layers/dms-layout/i18n/locales/layout-en-GB.json +43 -13
- package/frontend-vue/layers/dms-layout/i18n/locales/layout-fr-FR.json +43 -13
- package/frontend-vue/layers/dms-ui/app/build/components/form/FormLoadError.vue +45 -0
- package/frontend-vue/layers/dms-ui/app/build/components/modal/ModalEscHint.vue +28 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/form/floatingSaveBars.ts +38 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/form/formRecordLoad.ts +66 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/navigation/useNavBadges.ts +50 -9
- package/frontend-vue/layers/dms-ui/app/build/composables/table/useTable.ts +7 -1
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/chrome.ts +2 -2
- package/frontend-vue/layers/dms-ui/app/build/types/tree.ts +14 -10
- package/frontend-vue/layers/dms-ui/app/build/utils/navBadge.ts +12 -0
- package/frontend-vue/layers/dms-ui/app/build/utils/tone.ts +22 -0
- package/frontend-vue/layers/dms-ui/app/components/activity-feed/ActivityFeed.vue +117 -82
- package/frontend-vue/layers/dms-ui/app/components/blocks/NavCardGridBlock.vue +12 -1
- package/frontend-vue/layers/dms-ui/app/components/card/NavCard.vue +16 -1
- package/frontend-vue/layers/dms-ui/app/components/confirm/ConfirmModal.vue +7 -8
- package/frontend-vue/layers/dms-ui/app/components/form/Form.vue +28 -6
- package/frontend-vue/layers/dms-ui/app/components/form/components/Display.vue +5 -1
- package/frontend-vue/layers/dms-ui/app/components/save-bar/SaveBar.vue +4 -0
- package/frontend-vue/layers/dms-ui/app/components/status-pill/StatusPill.vue +7 -2
- package/frontend-vue/layers/dms-ui/app/components/table-view/TableView.vue +7 -2
- package/frontend-vue/layers/dms-ui/app/composables/form/useForm.ts +48 -34
- package/frontend-vue/layers/dms-ui/app/composables/table-view/types/config.ts +13 -3
- package/frontend-vue/layers/dms-ui/i18n/locales/ui-en-GB.json +16 -14
- package/frontend-vue/layers/dms-ui/i18n/locales/ui-fr-FR.json +16 -14
- package/frontend-vue/package.json +2 -2
- package/frontend-vue/pnpm-lock.yaml +5 -5
- package/frontend-vue/tests/activity-feed-fill-height.test.ts +107 -0
- package/frontend-vue/tests/category-nav-cards.test.ts +21 -13
- package/frontend-vue/tests/command-palette-assistant.test.ts +344 -0
- package/frontend-vue/tests/fixtures/dashboard-search-stub.ts +91 -0
- package/frontend-vue/tests/forgot-validation-resend.test.ts +234 -0
- package/frontend-vue/tests/form-display-options.test.ts +64 -0
- package/frontend-vue/tests/form-load-error.test.ts +160 -0
- package/frontend-vue/tests/form-saved-before-success.test.ts +110 -0
- package/frontend-vue/tests/github-star-prompt.test.ts +328 -0
- package/frontend-vue/tests/header-actions.test.ts +191 -0
- package/frontend-vue/tests/invites-edit-modal.test.ts +157 -0
- package/frontend-vue/tests/member-role-picker.test.ts +26 -0
- package/frontend-vue/tests/menu-hidden-page.test.ts +93 -0
- package/frontend-vue/tests/nav-badge-tones.test.ts +274 -0
- package/frontend-vue/tests/nav-card-badge.test.ts +69 -0
- package/frontend-vue/tests/nav-card-grid-empty-category.test.ts +163 -0
- package/frontend-vue/tests/notification-popover-errors.test.ts +71 -37
- package/frontend-vue/tests/notification-preferences-groups.test.ts +268 -0
- package/frontend-vue/tests/notification-tone-contract.test.ts +70 -0
- package/frontend-vue/tests/page-fill-height.test.ts +9 -1
- package/frontend-vue/tests/page-no-visible-blocks.test.ts +202 -0
- package/frontend-vue/tests/profile-preferences-summary.test.ts +26 -2
- package/frontend-vue/tests/profile-security-summary.test.ts +121 -0
- package/frontend-vue/tests/role-permission-tree.test.ts +102 -8
- package/frontend-vue/tests/role-permissions.test.ts +178 -56
- package/frontend-vue/tests/side-panels.test.ts +470 -0
- package/frontend-vue/tests/signup-invitation-link.test.ts +71 -2
- package/frontend-vue/tests/table-actions-column-width.test.ts +51 -1
- package/frontend-vue/tests/table-view-chrome.test.ts +2 -2
- package/frontend-vue/tests/vue-inventory.test.ts +1 -1
- package/package.json +1 -1
- package/skills/dms/REFERENCE.md +1 -1
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import type { CommandPaletteGroup } from "@nuxt/ui";
|
|
2
|
+
import { computed, ref, watch, type ComputedRef, type Ref } from "vue";
|
|
3
|
+
import type { I18nTranslate } from "#dms-core/app/composables/translation/useTranslation";
|
|
4
|
+
import {
|
|
5
|
+
appendAssistantAskGroup,
|
|
6
|
+
buildAssistantModeGroups,
|
|
7
|
+
type ResolvedAssistantSuggestion,
|
|
8
|
+
} from "./assistantGroups";
|
|
9
|
+
|
|
10
|
+
/** @internal */
|
|
11
|
+
export type CommandPaletteMode = "search" | "assistant";
|
|
12
|
+
|
|
13
|
+
/** @internal One submitted prompt; `id` changes on every submission. */
|
|
14
|
+
export interface CommandPaletteSubmission {
|
|
15
|
+
id: number;
|
|
16
|
+
prompt: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** @internal The registered assistant's fields, resolved for display. */
|
|
20
|
+
export interface ResolvedCommandPaletteAssistant {
|
|
21
|
+
label: string;
|
|
22
|
+
icon: string;
|
|
23
|
+
placeholder: string;
|
|
24
|
+
answerComponent: string;
|
|
25
|
+
suggestions: ResolvedAssistantSuggestion[];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface PaletteGroupsContext {
|
|
29
|
+
assistant: ResolvedCommandPaletteAssistant | undefined;
|
|
30
|
+
isAssistantMode: boolean;
|
|
31
|
+
hasAnswer: boolean;
|
|
32
|
+
prompt: string;
|
|
33
|
+
translate: I18nTranslate;
|
|
34
|
+
submit: (prompt: string) => void;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const MODE_ANNOUNCEMENT_KEYS: Record<CommandPaletteMode, string> = {
|
|
38
|
+
search: "commandPalette.assistant.modeSearch",
|
|
39
|
+
assistant: "commandPalette.assistant.modeAssistant",
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const OTHER_MODE: Record<CommandPaletteMode, CommandPaletteMode> = {
|
|
43
|
+
search: "assistant",
|
|
44
|
+
assistant: "search",
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Tab without a modifier: Shift+Tab and the browser's own combinations keep
|
|
49
|
+
* moving focus, so the dialog's keyboard navigation stays reachable.
|
|
50
|
+
*
|
|
51
|
+
* @internal
|
|
52
|
+
*/
|
|
53
|
+
export function isModeToggleKey(event: KeyboardEvent): boolean {
|
|
54
|
+
return (
|
|
55
|
+
event.key === "Tab" &&
|
|
56
|
+
!event.shiftKey &&
|
|
57
|
+
!event.altKey &&
|
|
58
|
+
!event.ctrlKey &&
|
|
59
|
+
!event.metaKey &&
|
|
60
|
+
!event.isComposing
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function createModeToggleHandler(
|
|
65
|
+
isEnabled: () => boolean,
|
|
66
|
+
toggle: () => void,
|
|
67
|
+
): (event: KeyboardEvent) => void {
|
|
68
|
+
return (event) => {
|
|
69
|
+
if (!isEnabled() || !isModeToggleKey(event)) return;
|
|
70
|
+
// The dialog's focus trap listens further up and would move focus.
|
|
71
|
+
event.preventDefault();
|
|
72
|
+
event.stopPropagation();
|
|
73
|
+
toggle();
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function useResolvedAssistant(): ComputedRef<
|
|
78
|
+
ResolvedCommandPaletteAssistant | undefined
|
|
79
|
+
> {
|
|
80
|
+
const { assistant } = useCommandPaletteAssistant();
|
|
81
|
+
const { t } = useI18n();
|
|
82
|
+
const { processI18n } = useTranslation();
|
|
83
|
+
|
|
84
|
+
return computed(() => {
|
|
85
|
+
const entry = assistant.value;
|
|
86
|
+
if (!entry) return undefined;
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
label: processI18n(entry.label),
|
|
90
|
+
icon: entry.icon,
|
|
91
|
+
placeholder: entry.placeholder
|
|
92
|
+
? processI18n(entry.placeholder)
|
|
93
|
+
: t("commandPalette.assistant.placeholder"),
|
|
94
|
+
answerComponent: entry.answerComponent,
|
|
95
|
+
suggestions: entry.suggestions().map((suggestion) => {
|
|
96
|
+
const label = processI18n(suggestion.label);
|
|
97
|
+
return {
|
|
98
|
+
label,
|
|
99
|
+
prompt: suggestion.prompt ?? label,
|
|
100
|
+
icon: suggestion.icon ?? entry.icon,
|
|
101
|
+
};
|
|
102
|
+
}),
|
|
103
|
+
};
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function useAssistantSession(
|
|
108
|
+
isOpen: Ref<boolean>,
|
|
109
|
+
searchTerm: Ref<string>,
|
|
110
|
+
hasAssistant: ComputedRef<boolean>,
|
|
111
|
+
) {
|
|
112
|
+
const { t } = useI18n();
|
|
113
|
+
const mode = ref<CommandPaletteMode>("search");
|
|
114
|
+
const submission = ref<CommandPaletteSubmission | null>(null);
|
|
115
|
+
const announcement = ref("");
|
|
116
|
+
let submissionCount = 0;
|
|
117
|
+
|
|
118
|
+
function setMode(next: CommandPaletteMode): void {
|
|
119
|
+
if (!hasAssistant.value) return;
|
|
120
|
+
mode.value = next;
|
|
121
|
+
submission.value = null;
|
|
122
|
+
announcement.value = t(MODE_ANNOUNCEMENT_KEYS[next]);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function submit(prompt: string): void {
|
|
126
|
+
const trimmed = prompt.trim();
|
|
127
|
+
if (!trimmed || !hasAssistant.value) return;
|
|
128
|
+
mode.value = "assistant";
|
|
129
|
+
searchTerm.value = trimmed;
|
|
130
|
+
submissionCount += 1;
|
|
131
|
+
submission.value = { id: submissionCount, prompt: trimmed };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
watch(isOpen, (open) => {
|
|
135
|
+
if (open) return;
|
|
136
|
+
mode.value = "search";
|
|
137
|
+
submission.value = null;
|
|
138
|
+
announcement.value = "";
|
|
139
|
+
});
|
|
140
|
+
const onInputKeydown = createModeToggleHandler(
|
|
141
|
+
() => hasAssistant.value,
|
|
142
|
+
() => setMode(OTHER_MODE[mode.value]),
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
return { mode, submission, announcement, setMode, submit, onInputKeydown };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** The submission whose answer shows: gone as soon as the prompt is edited. */
|
|
149
|
+
function useShownAnswer(
|
|
150
|
+
isAssistantMode: ComputedRef<boolean>,
|
|
151
|
+
submission: Ref<CommandPaletteSubmission | null>,
|
|
152
|
+
searchTerm: Ref<string>,
|
|
153
|
+
): ComputedRef<CommandPaletteSubmission | null> {
|
|
154
|
+
return computed(() =>
|
|
155
|
+
isAssistantMode.value &&
|
|
156
|
+
submission.value?.prompt === searchTerm.value.trim()
|
|
157
|
+
? submission.value
|
|
158
|
+
: null,
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function assistantModeGroups(
|
|
163
|
+
assistant: ResolvedCommandPaletteAssistant,
|
|
164
|
+
context: PaletteGroupsContext,
|
|
165
|
+
): CommandPaletteGroup[] {
|
|
166
|
+
if (context.hasAnswer) return [];
|
|
167
|
+
|
|
168
|
+
return buildAssistantModeGroups(context.prompt, {
|
|
169
|
+
icon: assistant.icon,
|
|
170
|
+
promptLabel: assistant.label,
|
|
171
|
+
suggestionsLabel: context.translate(
|
|
172
|
+
"commandPalette.assistant.suggestions",
|
|
173
|
+
{},
|
|
174
|
+
),
|
|
175
|
+
sendLabel: (prompt) =>
|
|
176
|
+
context.translate("commandPalette.assistant.send", { prompt }),
|
|
177
|
+
suggestions: assistant.suggestions,
|
|
178
|
+
onSubmit: context.submit,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* The groups the palette renders: the sources' groups untouched while no
|
|
184
|
+
* assistant is registered, the same groups followed by the "Ask the
|
|
185
|
+
* assistant" item once a search is typed, and the prompt and suggestions in
|
|
186
|
+
* assistant mode — none once an answer is shown, which the empty slot renders.
|
|
187
|
+
*
|
|
188
|
+
* @internal
|
|
189
|
+
*/
|
|
190
|
+
export function buildPaletteGroups(
|
|
191
|
+
sourceGroups: CommandPaletteGroup[],
|
|
192
|
+
context: PaletteGroupsContext,
|
|
193
|
+
): CommandPaletteGroup[] {
|
|
194
|
+
const { assistant } = context;
|
|
195
|
+
if (!assistant) return sourceGroups;
|
|
196
|
+
if (context.isAssistantMode) return assistantModeGroups(assistant, context);
|
|
197
|
+
|
|
198
|
+
return appendAssistantAskGroup(sourceGroups, context.prompt, {
|
|
199
|
+
label: assistant.label,
|
|
200
|
+
icon: assistant.icon,
|
|
201
|
+
askLabel: (query) =>
|
|
202
|
+
context.translate("commandPalette.assistant.ask", { query }),
|
|
203
|
+
onAsk: context.submit,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* State of the command palette's assistant mode: the mode, the groups shown
|
|
209
|
+
* for it, the submitted prompt, and the Tab toggle bound to the input. Every
|
|
210
|
+
* piece degrades to the plain search palette while no assistant is registered.
|
|
211
|
+
*
|
|
212
|
+
* @internal
|
|
213
|
+
*/
|
|
214
|
+
export function useCommandPaletteAssistantMode(
|
|
215
|
+
searchGroups: Ref<CommandPaletteGroup[]>,
|
|
216
|
+
) {
|
|
217
|
+
const { t } = useI18n();
|
|
218
|
+
const isOpen = ref(false);
|
|
219
|
+
const searchTerm = ref("");
|
|
220
|
+
const assistant = useResolvedAssistant();
|
|
221
|
+
const hasAssistant = computed(() => !!assistant.value);
|
|
222
|
+
const session = useAssistantSession(isOpen, searchTerm, hasAssistant);
|
|
223
|
+
const { mode, submission } = session;
|
|
224
|
+
|
|
225
|
+
const isAssistantMode = computed(
|
|
226
|
+
() => hasAssistant.value && mode.value === "assistant",
|
|
227
|
+
);
|
|
228
|
+
const answer = useShownAnswer(isAssistantMode, submission, searchTerm);
|
|
229
|
+
const groups = computed(() =>
|
|
230
|
+
buildPaletteGroups(searchGroups.value, {
|
|
231
|
+
assistant: assistant.value,
|
|
232
|
+
isAssistantMode: isAssistantMode.value,
|
|
233
|
+
hasAnswer: !!answer.value,
|
|
234
|
+
prompt: searchTerm.value.trim(),
|
|
235
|
+
translate: (key, params) => t(key, params),
|
|
236
|
+
submit: session.submit,
|
|
237
|
+
}),
|
|
238
|
+
);
|
|
239
|
+
|
|
240
|
+
return {
|
|
241
|
+
...session,
|
|
242
|
+
isOpen,
|
|
243
|
+
searchTerm,
|
|
244
|
+
assistant,
|
|
245
|
+
isAssistantMode,
|
|
246
|
+
answer,
|
|
247
|
+
groups,
|
|
248
|
+
};
|
|
249
|
+
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import {
|
|
2
|
+
computed,
|
|
3
|
+
onBeforeUnmount,
|
|
4
|
+
onMounted,
|
|
5
|
+
ref,
|
|
6
|
+
type ComputedRef,
|
|
7
|
+
} from "vue";
|
|
8
|
+
import { GITHUB_REPOSITORY_URL } from "../../utils/github-repository";
|
|
9
|
+
|
|
10
|
+
const STORAGE_PREFIX = "dms-github-star-prompt:";
|
|
11
|
+
|
|
12
|
+
/** `localStorage` keys of the star prompt, per browser. */
|
|
13
|
+
export const GITHUB_STAR_PROMPT_KEYS = {
|
|
14
|
+
/** Visible time spent on signed-in dashboard pages, in milliseconds. */
|
|
15
|
+
activeMs: `${STORAGE_PREFIX}active-ms`,
|
|
16
|
+
/** Epoch milliseconds until which "Later" keeps the prompt hidden. */
|
|
17
|
+
snoozedUntil: `${STORAGE_PREFIX}snoozed-until`,
|
|
18
|
+
/** Set once the prompt is starred or closed: it never shows again. */
|
|
19
|
+
dismissed: `${STORAGE_PREFIX}dismissed`,
|
|
20
|
+
} as const;
|
|
21
|
+
|
|
22
|
+
/** Active dashboard time before the prompt first shows. */
|
|
23
|
+
export const GITHUB_STAR_PROMPT_DELAY_MS = 15 * 60 * 1000;
|
|
24
|
+
/** How long "Later" hides the prompt. */
|
|
25
|
+
export const GITHUB_STAR_PROMPT_SNOOZE_MS = 48 * 60 * 60 * 1000;
|
|
26
|
+
/** How often the visible time is saved and the snooze checked. */
|
|
27
|
+
export const GITHUB_STAR_PROMPT_TICK_MS = 30 * 1000;
|
|
28
|
+
|
|
29
|
+
const DISMISSED = "1";
|
|
30
|
+
const VISIBLE = "visible";
|
|
31
|
+
|
|
32
|
+
function readStorage(key: string): string | null {
|
|
33
|
+
try {
|
|
34
|
+
return localStorage.getItem(key);
|
|
35
|
+
} catch {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function writeStorage(key: string, value: string): void {
|
|
41
|
+
try {
|
|
42
|
+
localStorage.setItem(key, value);
|
|
43
|
+
} catch {
|
|
44
|
+
/* storage unavailable (private mode, quota): the prompt state lasts the page */
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function readCount(key: string): number {
|
|
49
|
+
const value = Number(readStorage(key));
|
|
50
|
+
return Number.isFinite(value) && value > 0 ? value : 0;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function isPageVisible(): boolean {
|
|
54
|
+
return document.visibilityState === VISIBLE;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface GithubStarPrompt {
|
|
58
|
+
/** Whether the prompt is due: enough active time, not snoozed, not dismissed. */
|
|
59
|
+
isDue: ComputedRef<boolean>;
|
|
60
|
+
/** "Later": hides the prompt for {@link GITHUB_STAR_PROMPT_SNOOZE_MS}. */
|
|
61
|
+
snooze: () => void;
|
|
62
|
+
/** Opens the repository in a new tab, then never shows the prompt again. */
|
|
63
|
+
star: () => void;
|
|
64
|
+
/** Never shows the prompt again. */
|
|
65
|
+
dismiss: () => void;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* The GitHub star invitation's timing, kept per browser in `localStorage`.
|
|
70
|
+
* Only the time the tab is visible counts, added up across sessions, so a
|
|
71
|
+
* dashboard left open in a background tab does not bring the prompt sooner.
|
|
72
|
+
* Without `isEnabled` nothing is counted or shown.
|
|
73
|
+
*/
|
|
74
|
+
export const useGithubStarPrompt = (isEnabled: boolean): GithubStarPrompt => {
|
|
75
|
+
const isMounted = ref(false);
|
|
76
|
+
const activeMs = ref(0);
|
|
77
|
+
const snoozedUntil = ref(0);
|
|
78
|
+
const isDismissed = ref(false);
|
|
79
|
+
const now = ref(0);
|
|
80
|
+
let countedSince: number | null = null;
|
|
81
|
+
let ticker: ReturnType<typeof setInterval> | undefined;
|
|
82
|
+
|
|
83
|
+
const isDue = computed(
|
|
84
|
+
() =>
|
|
85
|
+
isMounted.value &&
|
|
86
|
+
!isDismissed.value &&
|
|
87
|
+
activeMs.value >= GITHUB_STAR_PROMPT_DELAY_MS &&
|
|
88
|
+
now.value >= snoozedUntil.value,
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
// Merged with what this page holds: a choice made in another tab is
|
|
92
|
+
// followed here, and one made here outlives a storage that cannot be read.
|
|
93
|
+
function load(): void {
|
|
94
|
+
activeMs.value = storedActiveMs();
|
|
95
|
+
snoozedUntil.value = Math.max(
|
|
96
|
+
snoozedUntil.value,
|
|
97
|
+
readCount(GITHUB_STAR_PROMPT_KEYS.snoozedUntil),
|
|
98
|
+
);
|
|
99
|
+
isDismissed.value ||=
|
|
100
|
+
readStorage(GITHUB_STAR_PROMPT_KEYS.dismissed) === DISMISSED;
|
|
101
|
+
now.value = Date.now();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function storedActiveMs(): number {
|
|
105
|
+
return Math.max(
|
|
106
|
+
activeMs.value,
|
|
107
|
+
readCount(GITHUB_STAR_PROMPT_KEYS.activeMs),
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Added to the stored total rather than written over it, so two tabs add
|
|
112
|
+
// up. One tick at most is counted per call: a longer gap means the device
|
|
113
|
+
// slept, which is not time spent on the dashboard.
|
|
114
|
+
function count(): void {
|
|
115
|
+
if (countedSince === null) return;
|
|
116
|
+
const at = Date.now();
|
|
117
|
+
const elapsed = Math.min(at - countedSince, GITHUB_STAR_PROMPT_TICK_MS);
|
|
118
|
+
countedSince = at;
|
|
119
|
+
if (activeMs.value >= GITHUB_STAR_PROMPT_DELAY_MS) return;
|
|
120
|
+
activeMs.value = storedActiveMs() + elapsed;
|
|
121
|
+
writeStorage(GITHUB_STAR_PROMPT_KEYS.activeMs, String(activeMs.value));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function onVisibilityChange(): void {
|
|
125
|
+
count();
|
|
126
|
+
countedSince = isPageVisible() ? Date.now() : null;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function tick(): void {
|
|
130
|
+
count();
|
|
131
|
+
load();
|
|
132
|
+
if (isDismissed.value) stop();
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function stop(): void {
|
|
136
|
+
clearInterval(ticker);
|
|
137
|
+
ticker = undefined;
|
|
138
|
+
document.removeEventListener("visibilitychange", onVisibilityChange);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function dismiss(): void {
|
|
142
|
+
isDismissed.value = true;
|
|
143
|
+
writeStorage(GITHUB_STAR_PROMPT_KEYS.dismissed, DISMISSED);
|
|
144
|
+
stop();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function snooze(): void {
|
|
148
|
+
now.value = Date.now();
|
|
149
|
+
snoozedUntil.value = now.value + GITHUB_STAR_PROMPT_SNOOZE_MS;
|
|
150
|
+
writeStorage(
|
|
151
|
+
GITHUB_STAR_PROMPT_KEYS.snoozedUntil,
|
|
152
|
+
String(snoozedUntil.value),
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function star(): void {
|
|
157
|
+
window.open(GITHUB_REPOSITORY_URL, "_blank", "noopener,noreferrer");
|
|
158
|
+
dismiss();
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
onMounted(() => {
|
|
162
|
+
if (!isEnabled) return;
|
|
163
|
+
load();
|
|
164
|
+
isMounted.value = true;
|
|
165
|
+
if (isDismissed.value) return;
|
|
166
|
+
countedSince = isPageVisible() ? Date.now() : null;
|
|
167
|
+
document.addEventListener("visibilitychange", onVisibilityChange);
|
|
168
|
+
ticker = setInterval(tick, GITHUB_STAR_PROMPT_TICK_MS);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
onBeforeUnmount(() => {
|
|
172
|
+
if (ticker === undefined) return;
|
|
173
|
+
count();
|
|
174
|
+
stop();
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
return { isDue, snooze, star, dismiss };
|
|
178
|
+
};
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import type { ComputedRef, Ref } from "vue";
|
|
2
|
+
import {
|
|
3
|
+
resolveSidePanelWidthBounds,
|
|
4
|
+
type SidePanel,
|
|
5
|
+
type SidePanelWidthBounds,
|
|
6
|
+
useAppSidePanels,
|
|
7
|
+
} from "../../../composables/useAppSidePanels";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Set to the panel's width in px by the panel itself and by the dashboard
|
|
11
|
+
* frame, which keeps that much room free on its right from `lg`. Both cap it
|
|
12
|
+
* at 60vw.
|
|
13
|
+
*
|
|
14
|
+
* @internal
|
|
15
|
+
*/
|
|
16
|
+
export const SIDE_PANEL_WIDTH_VARIABLE = "--dms-side-panel-width";
|
|
17
|
+
/** @internal */
|
|
18
|
+
export const SIDE_PANEL_COOKIE = "dms-side-panel";
|
|
19
|
+
const SIDE_PANEL_COOKIE_MAX_AGE = 60 * 60 * 24 * 365;
|
|
20
|
+
const SIDE_PANEL_DRAG_WIDTH_STATE_KEY = "dms:side-panel-drag-width";
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* What the DMS remembers of the side panels on this device: which one is open
|
|
24
|
+
* and the width each was last given. A cookie, so the server renders the open
|
|
25
|
+
* panel at its width and a reload does not move the page.
|
|
26
|
+
*
|
|
27
|
+
* @internal
|
|
28
|
+
*/
|
|
29
|
+
export interface SidePanelPreferences {
|
|
30
|
+
openId: string | null;
|
|
31
|
+
widths: Record<string, number>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** @internal */
|
|
35
|
+
export interface ActiveSidePanel {
|
|
36
|
+
panel: ComputedRef<SidePanel | null>;
|
|
37
|
+
bounds: ComputedRef<SidePanelWidthBounds | null>;
|
|
38
|
+
width: ComputedRef<number>;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function emptyPreferences(): SidePanelPreferences {
|
|
42
|
+
return { openId: null, widths: {} };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
46
|
+
return typeof value === "object" && value !== null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function readWidths(value: unknown): Record<string, number> {
|
|
50
|
+
if (!isRecord(value)) return {};
|
|
51
|
+
return Object.fromEntries(
|
|
52
|
+
Object.entries(value).filter(([, width]) => Number.isFinite(width)),
|
|
53
|
+
) as Record<string, number>;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Reads the cookie value back defensively: it comes from the browser, and an
|
|
58
|
+
* older or hand-edited value must not break the dashboard.
|
|
59
|
+
*
|
|
60
|
+
* @internal
|
|
61
|
+
*/
|
|
62
|
+
export function readSidePanelPreferences(value: unknown): SidePanelPreferences {
|
|
63
|
+
if (!isRecord(value)) return emptyPreferences();
|
|
64
|
+
const openId = typeof value.openId === "string" ? value.openId : null;
|
|
65
|
+
return { openId, widths: readWidths(value.widths) };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** @internal */
|
|
69
|
+
export function useSidePanelPreferences(): Ref<SidePanelPreferences> {
|
|
70
|
+
return useDmsCookie<SidePanelPreferences>(SIDE_PANEL_COOKIE, {
|
|
71
|
+
default: emptyPreferences,
|
|
72
|
+
maxAge: SIDE_PANEL_COOKIE_MAX_AGE,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Opens the panel `openId`, or closes every panel with `null`. One panel is
|
|
78
|
+
* open at a time, so opening one closes the other.
|
|
79
|
+
*
|
|
80
|
+
* @internal
|
|
81
|
+
*/
|
|
82
|
+
export function setOpenSidePanel(
|
|
83
|
+
preferences: Ref<SidePanelPreferences>,
|
|
84
|
+
openId: string | null,
|
|
85
|
+
): void {
|
|
86
|
+
const current = readSidePanelPreferences(preferences.value);
|
|
87
|
+
if (current.openId === openId) return;
|
|
88
|
+
preferences.value = { ...current, openId };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** @internal */
|
|
92
|
+
export function isSidePanelOpen(
|
|
93
|
+
preferences: Ref<SidePanelPreferences>,
|
|
94
|
+
id: string,
|
|
95
|
+
): boolean {
|
|
96
|
+
return readSidePanelPreferences(preferences.value).openId === id;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** @internal */
|
|
100
|
+
export function toggleSidePanel(
|
|
101
|
+
preferences: Ref<SidePanelPreferences>,
|
|
102
|
+
id: string,
|
|
103
|
+
): void {
|
|
104
|
+
setOpenSidePanel(preferences, isSidePanelOpen(preferences, id) ? null : id);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** @internal */
|
|
108
|
+
export function storeSidePanelWidth(
|
|
109
|
+
preferences: Ref<SidePanelPreferences>,
|
|
110
|
+
id: string,
|
|
111
|
+
width: number,
|
|
112
|
+
): void {
|
|
113
|
+
const current = readSidePanelPreferences(preferences.value);
|
|
114
|
+
preferences.value = {
|
|
115
|
+
...current,
|
|
116
|
+
widths: { ...current.widths, [id]: width },
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* The width of the panel being dragged, shared with the dashboard frame so the
|
|
122
|
+
* page follows the handle. The cookie is written once the drag ends.
|
|
123
|
+
*
|
|
124
|
+
* @internal
|
|
125
|
+
*/
|
|
126
|
+
export function useSidePanelDragWidth(): Ref<number | null> {
|
|
127
|
+
return useDmsState<number | null>(
|
|
128
|
+
SIDE_PANEL_DRAG_WIDTH_STATE_KEY,
|
|
129
|
+
() => null,
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** @internal */
|
|
134
|
+
export function sidePanelWidthStyle(width: number): Record<string, string> {
|
|
135
|
+
return { [SIDE_PANEL_WIDTH_VARIABLE]: `${width}px` };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** @internal */
|
|
139
|
+
export function clampSidePanelWidth(
|
|
140
|
+
width: number,
|
|
141
|
+
bounds: SidePanelWidthBounds,
|
|
142
|
+
): number {
|
|
143
|
+
return Math.round(
|
|
144
|
+
Math.min(bounds.maxWidth, Math.max(bounds.minWidth, width)),
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* The width a panel was last given, or its default, within its bounds.
|
|
150
|
+
*
|
|
151
|
+
* @internal
|
|
152
|
+
*/
|
|
153
|
+
export function storedSidePanelWidth(
|
|
154
|
+
preferences: SidePanelPreferences,
|
|
155
|
+
id: string,
|
|
156
|
+
bounds: SidePanelWidthBounds,
|
|
157
|
+
): number {
|
|
158
|
+
return clampSidePanelWidth(
|
|
159
|
+
preferences.widths[id] ?? bounds.defaultWidth,
|
|
160
|
+
bounds,
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* The side panel the dashboard shows, and its width: the open panel, if it is
|
|
166
|
+
* registered and a user is signed in. Read by the panel host and by every
|
|
167
|
+
* dashboard frame, which reserves that width next to the page.
|
|
168
|
+
*
|
|
169
|
+
* @internal
|
|
170
|
+
*/
|
|
171
|
+
export function useActiveSidePanel(): ActiveSidePanel {
|
|
172
|
+
const { panels } = useAppSidePanels();
|
|
173
|
+
const preferences = useSidePanelPreferences();
|
|
174
|
+
const dragWidth = useSidePanelDragWidth();
|
|
175
|
+
const { loggedIn } = useUserSession();
|
|
176
|
+
const panel = computed(() => {
|
|
177
|
+
if (!loggedIn.value) return null;
|
|
178
|
+
const { openId } = readSidePanelPreferences(preferences.value);
|
|
179
|
+
return panels.value.find((entry) => entry.id === openId) ?? null;
|
|
180
|
+
});
|
|
181
|
+
const bounds = computed(() =>
|
|
182
|
+
panel.value ? resolveSidePanelWidthBounds(panel.value) : null,
|
|
183
|
+
);
|
|
184
|
+
const width = computed(() => {
|
|
185
|
+
if (!panel.value || !bounds.value) return 0;
|
|
186
|
+
if (dragWidth.value !== null) return dragWidth.value;
|
|
187
|
+
const current = readSidePanelPreferences(preferences.value);
|
|
188
|
+
return storedSidePanelWidth(current, panel.value.id, bounds.value);
|
|
189
|
+
});
|
|
190
|
+
return { panel, bounds, width };
|
|
191
|
+
}
|
package/frontend-vue/layers/dms-layout/app/build/composables/layout/useHeaderActionControls.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { HeaderAction } from "../../../composables/useHeaderActions";
|
|
2
|
+
import {
|
|
3
|
+
isSidePanelOpen,
|
|
4
|
+
toggleSidePanel,
|
|
5
|
+
useSidePanelPreferences,
|
|
6
|
+
} from "./sidePanelState";
|
|
7
|
+
|
|
8
|
+
/** @internal */
|
|
9
|
+
export interface HeaderActionControls {
|
|
10
|
+
/** Whether the action is a toggle, whose button reports a pressed state. */
|
|
11
|
+
isToggle: (action: HeaderAction) => boolean;
|
|
12
|
+
/** Whether the toggle is on: its side panel is open, or `isActive()` says so. */
|
|
13
|
+
isActive: (action: HeaderAction) => boolean;
|
|
14
|
+
/** Toggles the action's side panel, or runs its `onSelect`. */
|
|
15
|
+
select: (action: HeaderAction) => void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* How the header reads and runs any {@link HeaderAction}: an action linked to
|
|
20
|
+
* a side panel gets its state and its click from the DMS, any other one from
|
|
21
|
+
* its own callables.
|
|
22
|
+
*
|
|
23
|
+
* @internal
|
|
24
|
+
*/
|
|
25
|
+
export function useHeaderActionControls(): HeaderActionControls {
|
|
26
|
+
const preferences = useSidePanelPreferences();
|
|
27
|
+
|
|
28
|
+
function isToggle(action: HeaderAction): boolean {
|
|
29
|
+
return (
|
|
30
|
+
typeof action.sidePanelId === "string" ||
|
|
31
|
+
typeof action.isActive === "function"
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function isActive(action: HeaderAction): boolean {
|
|
36
|
+
if (typeof action.sidePanelId === "string") {
|
|
37
|
+
return isSidePanelOpen(preferences, action.sidePanelId);
|
|
38
|
+
}
|
|
39
|
+
return typeof action.isActive === "function" && action.isActive();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function select(action: HeaderAction): void {
|
|
43
|
+
if (typeof action.sidePanelId === "string") {
|
|
44
|
+
toggleSidePanel(preferences, action.sidePanelId);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
action.onSelect?.();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return { isToggle, isActive, select };
|
|
51
|
+
}
|