@elytracms/studio 0.0.93 → 0.0.94
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/convex/analytics-cost.test.ts +817 -0
- package/convex/auth.ts +5 -2
- package/convex/authority.ts +35 -11
- package/convex/contract-parity.test.ts +7 -0
- package/convex/delivery.ts +12 -9
- package/convex/guard.ts +9 -7
- package/convex/http-routes.test.ts +18 -8
- package/convex/http.ts +23 -17
- package/convex/ingest.test.ts +326 -0
- package/convex/ingest.ts +281 -0
- package/convex/invites.ts +13 -5
- package/convex/members.ts +10 -4
- package/convex/record-functions.ts +325 -0
- package/convex/records.test.ts +156 -0
- package/convex/redirect-migration.test.ts +205 -0
- package/convex/redirect-revalidate.test.ts +5 -1
- package/convex/redirects.ts +120 -79
- package/convex/role-policy.test.ts +315 -0
- package/convex/roles.ts +83 -0
- package/convex/schema.ts +30 -0
- package/convex/triggers.ts +21 -1
- package/dist/addon.d.ts +71 -0
- package/dist/addon.js +67 -0
- package/dist/addon.js.map +1 -0
- package/dist/components/account/account-settings.js +2 -2
- package/dist/components/account/account-settings.js.map +1 -1
- package/dist/components/account/credential-panel.js +2 -2
- package/dist/components/account/credential-panel.js.map +1 -1
- package/dist/components/account/self-account-schema.d.ts +6 -15
- package/dist/components/account/self-account-schema.js +52 -34
- package/dist/components/account/self-account-schema.js.map +1 -1
- package/dist/components/account/self-account-view-source.d.ts +1 -1
- package/dist/components/account/self-account-view-source.js +6 -4
- package/dist/components/account/self-account-view-source.js.map +1 -1
- package/dist/components/ai/ai-chat-panel-lazy.js +1 -1
- package/dist/components/ai/ai-chat-panel-lazy.js.map +1 -1
- package/dist/components/ai/ai-chat-panel.js +6 -6
- package/dist/components/ai/ai-chat-panel.js.map +1 -1
- package/dist/components/ai/ai-cms-panel.js +2 -2
- package/dist/components/ai/ai-cms-panel.js.map +1 -1
- package/dist/components/ai/ai-panel-toggle.js +4 -4
- package/dist/components/ai/ai-panel-toggle.js.map +1 -1
- package/dist/components/ai/chat-ui.js +2 -2
- package/dist/components/ai/chat-ui.js.map +1 -1
- package/dist/components/ai/proposal-card.d.ts +2 -2
- package/dist/components/ai/proposal-card.js +2 -2
- package/dist/components/ai/proposal-card.js.map +1 -1
- package/dist/components/ai/proposal-issues.js +2 -2
- package/dist/components/ai/proposal-issues.js.map +1 -1
- package/dist/components/ai/translate-document-action.js +2 -2
- package/dist/components/ai/translate-document-action.js.map +1 -1
- package/dist/components/ai/translate-document-dialog.js +6 -6
- package/dist/components/ai/translate-document-dialog.js.map +1 -1
- package/dist/components/ai/translate-skipped.d.ts +1 -1
- package/dist/components/ai/translate-skipped.js +2 -2
- package/dist/components/ai/translate-skipped.js.map +1 -1
- package/dist/components/app-views/default-modules.d.ts +50 -2
- package/dist/components/app-views/default-modules.js +65 -20
- package/dist/components/app-views/default-modules.js.map +1 -1
- package/dist/components/app-views/module.d.ts +53 -3
- package/dist/components/app-views/module.js +14 -7
- package/dist/components/app-views/module.js.map +1 -1
- package/dist/components/app-views/sidebar.js +1 -1
- package/dist/components/app-views/sidebar.js.map +1 -1
- package/dist/components/app-views/studio-config.d.ts +11 -2
- package/dist/components/app-views/studio-config.js.map +1 -1
- package/dist/components/assets/asset-detail.js +2 -2
- package/dist/components/assets/asset-detail.js.map +1 -1
- package/dist/components/assets/asset-grid.js +2 -2
- package/dist/components/assets/asset-grid.js.map +1 -1
- package/dist/components/assets/asset-thumb.js +3 -3
- package/dist/components/assets/asset-thumb.js.map +1 -1
- package/dist/components/assets/assets-collection-view-source.d.ts +2 -2
- package/dist/components/assets/assets-collection-view-source.js.map +1 -1
- package/dist/components/assets/assets-module.js +1 -1
- package/dist/components/assets/assets-module.js.map +1 -1
- package/dist/components/assets/assets-store.d.ts +2 -2
- package/dist/components/assets/assets-store.js +6 -6
- package/dist/components/assets/assets-store.js.map +1 -1
- package/dist/components/assets/assets-view-preference.js +1 -1
- package/dist/components/assets/assets-view-preference.js.map +1 -1
- package/dist/components/assets/replace-file.js +1 -1
- package/dist/components/assets/replace-file.js.map +1 -1
- package/dist/components/auth-context.d.ts +1 -1
- package/dist/components/auth-context.js +6 -6
- package/dist/components/auth-context.js.map +1 -1
- package/dist/components/builder/blocks-model.d.ts +1 -1
- package/dist/components/builder/blocks-model.js +3 -3
- package/dist/components/builder/blocks-model.js.map +1 -1
- package/dist/components/builder/blocks-panel.d.ts +1 -1
- package/dist/components/builder/blocks-panel.js +4 -4
- package/dist/components/builder/blocks-panel.js.map +1 -1
- package/dist/components/builder/builder-store.js +2 -2
- package/dist/components/builder/builder-store.js.map +1 -1
- package/dist/components/builder/canvas-breakpoints.js +1 -1
- package/dist/components/builder/canvas-breakpoints.js.map +1 -1
- package/dist/components/builder/canvas-dnd.d.ts +2 -2
- package/dist/components/builder/canvas-dnd.js +1 -1
- package/dist/components/builder/canvas-dnd.js.map +1 -1
- package/dist/components/builder/canvas-inline-editing.d.ts +1 -1
- package/dist/components/builder/canvas-inline-editing.js.map +1 -1
- package/dist/components/builder/canvas-overlay-geometry.d.ts +1 -1
- package/dist/components/builder/canvas-overlay-geometry.js.map +1 -1
- package/dist/components/builder/canvas-render-context.d.ts +1 -1
- package/dist/components/builder/canvas-render-context.js +4 -4
- package/dist/components/builder/canvas-render-context.js.map +1 -1
- package/dist/components/builder/canvas-theme-notice.js +2 -2
- package/dist/components/builder/canvas-theme-notice.js.map +1 -1
- package/dist/components/builder/canvas.js +3 -3
- package/dist/components/builder/canvas.js.map +1 -1
- package/dist/components/builder/clipboard-actions.d.ts +2 -2
- package/dist/components/builder/clipboard-actions.js +4 -4
- package/dist/components/builder/clipboard-actions.js.map +1 -1
- package/dist/components/builder/composition-field-editor-lazy.js +1 -1
- package/dist/components/builder/composition-field-editor-lazy.js.map +1 -1
- package/dist/components/builder/composition-field-editor.d.ts +1 -1
- package/dist/components/builder/composition-field-editor.js +3 -3
- package/dist/components/builder/composition-field-editor.js.map +1 -1
- package/dist/components/builder/drag-session.d.ts +1 -1
- package/dist/components/builder/drag-session.js.map +1 -1
- package/dist/components/builder/fixture-thumbnail.js +1 -1
- package/dist/components/builder/fixture-thumbnail.js.map +1 -1
- package/dist/components/builder/insert-context.d.ts +1 -1
- package/dist/components/builder/insert-context.js +3 -3
- package/dist/components/builder/insert-context.js.map +1 -1
- package/dist/components/builder/layout-inspector.js +1 -1
- package/dist/components/builder/layout-inspector.js.map +1 -1
- package/dist/components/builder/node-title.js +1 -1
- package/dist/components/builder/node-title.js.map +1 -1
- package/dist/components/builder/page-tree.d.ts +1 -1
- package/dist/components/builder/page-tree.js +5 -5
- package/dist/components/builder/page-tree.js.map +1 -1
- package/dist/components/builder/placement-refusal.js +3 -3
- package/dist/components/builder/placement-refusal.js.map +1 -1
- package/dist/components/builder/property-inspector.js +4 -4
- package/dist/components/builder/property-inspector.js.map +1 -1
- package/dist/components/builder/reuse-context.js +4 -4
- package/dist/components/builder/reuse-context.js.map +1 -1
- package/dist/components/builder/reuse.js +1 -1
- package/dist/components/builder/reuse.js.map +1 -1
- package/dist/components/builder/rich-text-toolbar.js +1 -1
- package/dist/components/builder/rich-text-toolbar.js.map +1 -1
- package/dist/components/builder/run-inspector.js +1 -1
- package/dist/components/builder/run-inspector.js.map +1 -1
- package/dist/components/builder/selection-overlay.js +3 -3
- package/dist/components/builder/selection-overlay.js.map +1 -1
- package/dist/components/builder/spacing-handles.d.ts +2 -2
- package/dist/components/builder/spacing-handles.js +3 -3
- package/dist/components/builder/spacing-handles.js.map +1 -1
- package/dist/components/cms/asset-field.js +2 -2
- package/dist/components/cms/asset-field.js.map +1 -1
- package/dist/components/cms/asset-picker.js +2 -2
- package/dist/components/cms/asset-picker.js.map +1 -1
- package/dist/components/cms/bulk-actions.d.ts +1 -1
- package/dist/components/cms/bulk-actions.js +2 -2
- package/dist/components/cms/bulk-actions.js.map +1 -1
- package/dist/components/cms/bulk-bar.d.ts +2 -2
- package/dist/components/cms/bulk-bar.js +2 -2
- package/dist/components/cms/bulk-bar.js.map +1 -1
- package/dist/components/cms/cell-edit.d.ts +1 -1
- package/dist/components/cms/cell-edit.js +1 -1
- package/dist/components/cms/cell-edit.js.map +1 -1
- package/dist/components/cms/cell.d.ts +1 -1
- package/dist/components/cms/cell.js +3 -3
- package/dist/components/cms/cell.js.map +1 -1
- package/dist/components/cms/cms-index.js +2 -2
- package/dist/components/cms/cms-index.js.map +1 -1
- package/dist/components/cms/cms-labels.js +2 -2
- package/dist/components/cms/cms-labels.js.map +1 -1
- package/dist/components/cms/cms-store.d.ts +2 -2
- package/dist/components/cms/cms-store.js +10 -10
- package/dist/components/cms/cms-store.js.map +1 -1
- package/dist/components/cms/collection-table.js +7 -7
- package/dist/components/cms/collection-table.js.map +1 -1
- package/dist/components/cms/detail-editor.d.ts +5 -2
- package/dist/components/cms/detail-editor.js +20 -14
- package/dist/components/cms/detail-editor.js.map +1 -1
- package/dist/components/cms/details-pane.js +1 -1
- package/dist/components/cms/details-pane.js.map +1 -1
- package/dist/components/cms/doc-state.js +1 -1
- package/dist/components/cms/doc-state.js.map +1 -1
- package/dist/components/cms/document-diff-view.js +5 -5
- package/dist/components/cms/document-diff-view.js.map +1 -1
- package/dist/components/cms/document-editor-context.js +1 -1
- package/dist/components/cms/document-editor-context.js.map +1 -1
- package/dist/components/cms/document-staging.js +1 -1
- package/dist/components/cms/document-staging.js.map +1 -1
- package/dist/components/cms/document-url-path.js +2 -2
- package/dist/components/cms/document-url-path.js.map +1 -1
- package/dist/components/cms/duplicate-document.d.ts +1 -1
- package/dist/components/cms/duplicate-document.js.map +1 -1
- package/dist/components/cms/editable-cell.js +3 -3
- package/dist/components/cms/editable-cell.js.map +1 -1
- package/dist/components/cms/field-collaboration.d.ts +1 -1
- package/dist/components/cms/field-collaboration.js +1 -1
- package/dist/components/cms/field-collaboration.js.map +1 -1
- package/dist/components/cms/field-connection.d.ts +1 -1
- package/dist/components/cms/field-connection.js +2 -2
- package/dist/components/cms/field-connection.js.map +1 -1
- package/dist/components/cms/field-control-lib.js +1 -1
- package/dist/components/cms/field-control-lib.js.map +1 -1
- package/dist/components/cms/field-control.js +4 -4
- package/dist/components/cms/field-control.js.map +1 -1
- package/dist/components/cms/field-renderer.d.ts +1 -1
- package/dist/components/cms/field-renderer.js +2 -2
- package/dist/components/cms/field-renderer.js.map +1 -1
- package/dist/components/cms/filter-builder.d.ts +1 -1
- package/dist/components/cms/filter-builder.js +3 -3
- package/dist/components/cms/filter-builder.js.map +1 -1
- package/dist/components/cms/gate-validation.js +1 -1
- package/dist/components/cms/gate-validation.js.map +1 -1
- package/dist/components/cms/import-export.js +2 -2
- package/dist/components/cms/import-export.js.map +1 -1
- package/dist/components/cms/issue-copy.js +1 -1
- package/dist/components/cms/issue-copy.js.map +1 -1
- package/dist/components/cms/issues-pane.js +3 -3
- package/dist/components/cms/issues-pane.js.map +1 -1
- package/dist/components/cms/object-array-field.js +2 -2
- package/dist/components/cms/object-array-field.js.map +1 -1
- package/dist/components/cms/prop-fields.js +2 -2
- package/dist/components/cms/prop-fields.js.map +1 -1
- package/dist/components/cms/reference-confirm.d.ts +1 -1
- package/dist/components/cms/reference-confirm.js +2 -2
- package/dist/components/cms/reference-confirm.js.map +1 -1
- package/dist/components/cms/relation-picker.js +2 -2
- package/dist/components/cms/relation-picker.js.map +1 -1
- package/dist/components/cms/rich-text/block-style.js +1 -1
- package/dist/components/cms/rich-text/block-style.js.map +1 -1
- package/dist/components/cms/rich-text/component-embed.js +4 -4
- package/dist/components/cms/rich-text/component-embed.js.map +1 -1
- package/dist/components/cms/rich-text/editor.js +3 -3
- package/dist/components/cms/rich-text/editor.js.map +1 -1
- package/dist/components/cms/rich-text/embed-picker.js +3 -3
- package/dist/components/cms/rich-text/embed-picker.js.map +1 -1
- package/dist/components/cms/rich-text/embed-props.js +3 -3
- package/dist/components/cms/rich-text/embed-props.js.map +1 -1
- package/dist/components/cms/rich-text/extensions.js +1 -1
- package/dist/components/cms/rich-text/extensions.js.map +1 -1
- package/dist/components/cms/rich-text/font-size-control.d.ts +1 -1
- package/dist/components/cms/rich-text/font-size-control.js +2 -2
- package/dist/components/cms/rich-text/font-size-control.js.map +1 -1
- package/dist/components/cms/rich-text/lazy.js +1 -1
- package/dist/components/cms/rich-text/lazy.js.map +1 -1
- package/dist/components/cms/rich-text/link-control.js +2 -2
- package/dist/components/cms/rich-text/link-control.js.map +1 -1
- package/dist/components/cms/rich-text/slash-command.js +3 -3
- package/dist/components/cms/rich-text/slash-command.js.map +1 -1
- package/dist/components/cms/rich-text/toolbar.js +1 -1
- package/dist/components/cms/rich-text/toolbar.js.map +1 -1
- package/dist/components/cms/rich-text/unknown-node.js +2 -2
- package/dist/components/cms/rich-text/unknown-node.js.map +1 -1
- package/dist/components/cms/rich-text-preview.js +2 -2
- package/dist/components/cms/rich-text-preview.js.map +1 -1
- package/dist/components/cms/row-actions.d.ts +1 -1
- package/dist/components/cms/row-actions.js.map +1 -1
- package/dist/components/cms/scalar-value-editor.d.ts +1 -1
- package/dist/components/cms/scalar-value-editor.js +2 -2
- package/dist/components/cms/scalar-value-editor.js.map +1 -1
- package/dist/components/cms/use-discard-controller.d.ts +1 -1
- package/dist/components/cms/use-discard-controller.js +1 -1
- package/dist/components/cms/use-discard-controller.js.map +1 -1
- package/dist/components/cms/use-document-draft.d.ts +7 -1
- package/dist/components/cms/use-document-draft.js +16 -2
- package/dist/components/cms/use-document-draft.js.map +1 -1
- package/dist/components/cms/use-document-versions.js +1 -1
- package/dist/components/cms/use-document-versions.js.map +1 -1
- package/dist/components/cms/use-field-focus.d.ts +1 -1
- package/dist/components/cms/use-field-focus.js +2 -2
- package/dist/components/cms/use-field-focus.js.map +1 -1
- package/dist/components/cms/use-live-url.d.ts +1 -1
- package/dist/components/cms/use-live-url.js +3 -3
- package/dist/components/cms/use-live-url.js.map +1 -1
- package/dist/components/cms/use-publish-controller.d.ts +1 -1
- package/dist/components/cms/use-publish-controller.js +1 -1
- package/dist/components/cms/use-publish-controller.js.map +1 -1
- package/dist/components/cms/use-published-perspective.d.ts +1 -1
- package/dist/components/cms/use-published-perspective.js.map +1 -1
- package/dist/components/cms/use-restore-version.d.ts +1 -1
- package/dist/components/cms/use-restore-version.js +1 -1
- package/dist/components/cms/use-restore-version.js.map +1 -1
- package/dist/components/cms/version-compare-lazy.js +1 -1
- package/dist/components/cms/version-compare-lazy.js.map +1 -1
- package/dist/components/cms/version-compare.js +1 -1
- package/dist/components/cms/version-compare.js.map +1 -1
- package/dist/components/cms/versions-pane.js +3 -3
- package/dist/components/cms/versions-pane.js.map +1 -1
- package/dist/components/cms/view-bar.js +3 -3
- package/dist/components/cms/view-bar.js.map +1 -1
- package/dist/components/collection-view/cms-collection-view-source.d.ts +1 -1
- package/dist/components/collection-view/cms-collection-view-source.js +2 -2
- package/dist/components/collection-view/cms-collection-view-source.js.map +1 -1
- package/dist/components/collection-view/collection-view-context.d.ts +1 -1
- package/dist/components/collection-view/collection-view-context.js +1 -1
- package/dist/components/collection-view/collection-view-context.js.map +1 -1
- package/dist/components/collection-view/collection-view-surface.d.ts +13 -0
- package/dist/components/collection-view/collection-view-surface.js +18 -0
- package/dist/components/collection-view/collection-view-surface.js.map +1 -0
- package/dist/components/components/component-detail.js +10 -10
- package/dist/components/components/component-detail.js.map +1 -1
- package/dist/components/components/component-preview.d.ts +2 -2
- package/dist/components/components/component-preview.js +5 -5
- package/dist/components/components/component-preview.js.map +1 -1
- package/dist/components/components/component-status.d.ts +1 -1
- package/dist/components/components/component-status.js +1 -1
- package/dist/components/components/component-status.js.map +1 -1
- package/dist/components/components/components-index.js +2 -2
- package/dist/components/components/components-index.js.map +1 -1
- package/dist/components/components/field-inspector.js +1 -1
- package/dist/components/components/field-inspector.js.map +1 -1
- package/dist/components/components/registry-browser.js +3 -3
- package/dist/components/components/registry-browser.js.map +1 -1
- package/dist/components/components/taxonomy.d.ts +1 -1
- package/dist/components/components/taxonomy.js.map +1 -1
- package/dist/components/components/transfer-dialog.js +3 -3
- package/dist/components/components/transfer-dialog.js.map +1 -1
- package/dist/components/current-user-menu.js +1 -1
- package/dist/components/current-user-menu.js.map +1 -1
- package/dist/components/delivery-health-banner.js +2 -2
- package/dist/components/delivery-health-banner.js.map +1 -1
- package/dist/components/deployment-setup-gate.js +3 -3
- package/dist/components/deployment-setup-gate.js.map +1 -1
- package/dist/components/dev-login-badge.js +2 -2
- package/dist/components/dev-login-badge.js.map +1 -1
- package/dist/components/guards.js +1 -1
- package/dist/components/guards.js.map +1 -1
- package/dist/components/inspector/document-panes.d.ts +54 -0
- package/dist/components/inspector/document-panes.js +41 -0
- package/dist/components/inspector/document-panes.js.map +1 -0
- package/dist/components/inspector/inspector-sidebar.js +1 -1
- package/dist/components/inspector/inspector-sidebar.js.map +1 -1
- package/dist/components/nav-issues-registry.d.ts +53 -0
- package/dist/components/nav-issues-registry.js +69 -0
- package/dist/components/nav-issues-registry.js.map +1 -0
- package/dist/components/nav-issues.js +4 -4
- package/dist/components/nav-issues.js.map +1 -1
- package/dist/components/operation-error.d.ts +1 -1
- package/dist/components/operation-error.js +4 -4
- package/dist/components/operation-error.js.map +1 -1
- package/dist/components/playground-badge.js +2 -2
- package/dist/components/playground-badge.js.map +1 -1
- package/dist/components/project-list.js +3 -3
- package/dist/components/project-list.js.map +1 -1
- package/dist/components/project-session.d.ts +2 -2
- package/dist/components/project-session.js +5 -5
- package/dist/components/project-session.js.map +1 -1
- package/dist/components/project-switcher.js +2 -2
- package/dist/components/project-switcher.js.map +1 -1
- package/dist/components/rail-store-status.js +1 -1
- package/dist/components/rail-store-status.js.map +1 -1
- package/dist/components/rail-view-source.d.ts +6 -5
- package/dist/components/rail-view-source.js +4 -3
- package/dist/components/rail-view-source.js.map +1 -1
- package/dist/components/records/record-lens.d.ts +51 -0
- package/dist/components/records/record-lens.js +156 -0
- package/dist/components/records/record-lens.js.map +1 -0
- package/dist/components/records/record-rail.d.ts +86 -0
- package/dist/components/records/record-rail.js +241 -0
- package/dist/components/records/record-rail.js.map +1 -0
- package/dist/components/redirects/redirects-schema.js +1 -1
- package/dist/components/redirects/redirects-schema.js.map +1 -1
- package/dist/components/references/references-panel.js +2 -2
- package/dist/components/references/references-panel.js.map +1 -1
- package/dist/components/section-nav.js +27 -11
- package/dist/components/section-nav.js.map +1 -1
- package/dist/components/section-state.js +2 -2
- package/dist/components/section-state.js.map +1 -1
- package/dist/components/section-surface.js +2 -2
- package/dist/components/section-surface.js.map +1 -1
- package/dist/components/session-expired-overlay.js +3 -3
- package/dist/components/session-expired-overlay.js.map +1 -1
- package/dist/components/settings-form.js +1 -1
- package/dist/components/settings-form.js.map +1 -1
- package/dist/components/sign-in-needed-badge.js +1 -1
- package/dist/components/sign-in-needed-badge.js.map +1 -1
- package/dist/components/sign-in-view.d.ts +2 -2
- package/dist/components/sign-in-view.js +5 -5
- package/dist/components/sign-in-view.js.map +1 -1
- package/dist/components/studio-context.js +3 -3
- package/dist/components/studio-context.js.map +1 -1
- package/dist/components/studio-project-screen.js +30 -3
- package/dist/components/studio-project-screen.js.map +1 -1
- package/dist/components/studio-shell.js +6 -5
- package/dist/components/studio-shell.js.map +1 -1
- package/dist/components/theme-context.d.ts +1 -1
- package/dist/components/theme-context.js +2 -2
- package/dist/components/theme-context.js.map +1 -1
- package/dist/components/use-hydration-phase.d.ts +1 -1
- package/dist/components/use-hydration-phase.js +1 -1
- package/dist/components/use-hydration-phase.js.map +1 -1
- package/dist/components/users/invite-user-bar.d.ts +27 -0
- package/dist/components/users/invite-user-bar.js +25 -9
- package/dist/components/users/invite-user-bar.js.map +1 -1
- package/dist/components/users/role-policy-drift-issues.d.ts +38 -0
- package/dist/components/users/role-policy-drift-issues.js +71 -0
- package/dist/components/users/role-policy-drift-issues.js.map +1 -0
- package/dist/components/users/users-collection-view-source.d.ts +1 -1
- package/dist/components/users/users-collection-view-source.js +4 -2
- package/dist/components/users/users-collection-view-source.js.map +1 -1
- package/dist/components/users/users-module.js +1 -1
- package/dist/components/users/users-module.js.map +1 -1
- package/dist/components/users/users-schema.d.ts +10 -1
- package/dist/components/users/users-schema.js +45 -25
- package/dist/components/users/users-schema.js.map +1 -1
- package/dist/components/users/users-store.js +3 -3
- package/dist/components/users/users-store.js.map +1 -1
- package/dist/components/workspace-projects.d.ts +1 -1
- package/dist/components/workspace-projects.js +1 -1
- package/dist/components/workspace-projects.js.map +1 -1
- package/dist/lib/ai/index.d.ts +1 -1
- package/dist/lib/ai/index.js +1 -1
- package/dist/lib/ai/index.js.map +1 -1
- package/dist/lib/ai/node-handler.d.ts +1 -1
- package/dist/lib/ai/node-handler.js.map +1 -1
- package/dist/lib/ai/translate-document.js +1 -1
- package/dist/lib/ai/translate-document.js.map +1 -1
- package/dist/lib/clipboard/paste-issues.js +1 -1
- package/dist/lib/clipboard/paste-issues.js.map +1 -1
- package/dist/lib/clipboard/paste-target.js +2 -2
- package/dist/lib/clipboard/paste-target.js.map +1 -1
- package/dist/lib/cms/published-documents.js +1 -1
- package/dist/lib/cms/published-documents.js.map +1 -1
- package/dist/lib/cms-views/view-model.d.ts +12 -12
- package/dist/lib/cms-views/view-model.js +2 -2
- package/dist/lib/cms-views/view-model.js.map +1 -1
- package/dist/lib/collection-view/collection-view-source.d.ts +7 -2
- package/dist/lib/collection-view/collection-view-source.js.map +1 -1
- package/dist/lib/data/content-session-refresh.d.ts +1 -1
- package/dist/lib/data/content-session-refresh.js.map +1 -1
- package/dist/lib/data/playground-unload-guard.d.ts +1 -1
- package/dist/lib/data/playground-unload-guard.js +1 -1
- package/dist/lib/data/playground-unload-guard.js.map +1 -1
- package/dist/lib/engine-version.d.ts +1 -1
- package/dist/lib/engine-version.js +1 -1
- package/dist/lib/engine-version.js.map +1 -1
- package/dist/lib/i18n/i18n.js +1 -1
- package/dist/lib/i18n/i18n.js.map +1 -1
- package/dist/lib/i18n/messages.de.js +4 -0
- package/dist/lib/i18n/messages.de.js.map +1 -1
- package/dist/lib/i18n/messages.en.d.ts +4 -0
- package/dist/lib/i18n/messages.en.js +8 -0
- package/dist/lib/i18n/messages.en.js.map +1 -1
- package/dist/lib/operations/project-runtime-config.d.ts +9 -0
- package/dist/lib/operations/project-runtime-config.js +13 -0
- package/dist/lib/operations/project-runtime-config.js.map +1 -1
- package/dist/lib/operations/studio-operations.d.ts +9 -1
- package/dist/lib/operations/studio-operations.js +6 -1
- package/dist/lib/operations/studio-operations.js.map +1 -1
- package/dist/lib/persistence/convex-adapter.d.ts +16 -3
- package/dist/lib/persistence/convex-adapter.js +78 -47
- package/dist/lib/persistence/convex-adapter.js.map +1 -1
- package/dist/lib/persistence/index.js +14 -1
- package/dist/lib/persistence/index.js.map +1 -1
- package/dist/lib/persona/labels.js +1 -1
- package/dist/lib/persona/labels.js.map +1 -1
- package/dist/lib/query/operations-query.d.ts +1 -1
- package/dist/lib/query/operations-query.js +1 -1
- package/dist/lib/query/operations-query.js.map +1 -1
- package/dist/lib/references/reference-model.js +1 -1
- package/dist/lib/references/reference-model.js.map +1 -1
- package/dist/lib/references/references-store.js +1 -1
- package/dist/lib/references/references-store.js.map +1 -1
- package/dist/lib/workspace/index.d.ts +6 -0
- package/dist/lib/workspace/index.js +8 -0
- package/dist/lib/workspace/index.js.map +1 -1
- package/dist/mount.d.ts +28 -0
- package/dist/mount.js +27 -0
- package/dist/mount.js.map +1 -0
- package/dist/router/root.js +10 -10
- package/dist/router/root.js.map +1 -1
- package/dist/ui/badge.d.ts +1 -1
- package/dist/ui/button.d.ts +2 -2
- package/package.json +15 -11
- package/dist/components/redirects/redirects-collection-view-source.d.ts +0 -53
- package/dist/components/redirects/redirects-collection-view-source.js +0 -146
- package/dist/components/redirects/redirects-collection-view-source.js.map +0 -1
- package/dist/components/redirects/redirects-module.d.ts +0 -21
- package/dist/components/redirects/redirects-module.js +0 -40
- package/dist/components/redirects/redirects-module.js.map +0 -1
- package/dist/components/redirects/redirects-store.d.ts +0 -68
- package/dist/components/redirects/redirects-store.js +0 -132
- package/dist/components/redirects/redirects-store.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"row-actions.js","sourceRoot":"","sources":["../../../src/components/cms/row-actions.ts"],"names":[],"mappings":"AAsBA;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,aAAa,CAAC,EAAE,YAAY,EAAE,aAAa,EAAE,SAAS,EAAmB;IACvF,MAAM,OAAO,GAAgB,EAAE,CAAA;IAC/B,IAAI,aAAa;QAAE,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;IACvC,IAAI,YAAY,CAAC,SAAS,IAAI,aAAa;QAAE,OAAO,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;IACtE,IAAI,YAAY,CAAC,OAAO;QAAE,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC,CAAA;IAC3E,IAAI,YAAY,CAAC,MAAM;QAAE,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;IAC/C,OAAO,OAAO,CAAA;AAChB,CAAC","sourcesContent":["import type { CollectionViewCapabilities } from '
|
|
1
|
+
{"version":3,"file":"row-actions.js","sourceRoot":"","sources":["../../../src/components/cms/row-actions.ts"],"names":[],"mappings":"AAsBA;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,aAAa,CAAC,EAAE,YAAY,EAAE,aAAa,EAAE,SAAS,EAAmB;IACvF,MAAM,OAAO,GAAgB,EAAE,CAAA;IAC/B,IAAI,aAAa;QAAE,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;IACvC,IAAI,YAAY,CAAC,SAAS,IAAI,aAAa;QAAE,OAAO,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;IACtE,IAAI,YAAY,CAAC,OAAO;QAAE,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC,CAAA;IAC3E,IAAI,YAAY,CAAC,MAAM;QAAE,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;IAC/C,OAAO,OAAO,CAAA;AAChB,CAAC","sourcesContent":["import type { CollectionViewCapabilities } from '../../lib/collection-view/collection-view-source'\n\n/**\n * EC-316 — the right-edge row `⋮` menu's action vocabulary: the SAME bulk-action\n * set the toolbar's selection actions offer (`bulk-actions.ts`'s `BulkAction`),\n * minus `set-field` (a bulk-only composer over a multi-row selection — a single\n * row edits its fields directly in the table), plus `open` (jump to the detail\n * editor — the row's own affordance the primary-column link already offers, but\n * explicit in the menu for a keyboard/menu-driven workflow), plus `duplicate`\n * (EC-442 — copy the draft into a fresh draft document).\n */\nexport type RowAction = 'open' | 'duplicate' | 'publish' | 'unpublish' | 'delete'\n\nexport interface RowActionsInput {\n /** The rail's capability matrix (AD-19) — the SAME gate the list/edit shells use. */\n capabilities: Pick<CollectionViewCapabilities, 'publish' | 'remove' | 'duplicate'>\n /** Whether this row has a navigable detail route (a rail with no detail surface omits it). */\n hasDetailPath: boolean\n /** Whether this document IS published — decides `publish` vs `unpublish`. */\n published: boolean\n}\n\n/**\n * The gated action list for one row (EC-316). Every entry mirrors a capability\n * the rail actually declares — a source without `publish` never offers\n * publish/unpublish, one without `remove` never offers delete, and a rail with\n * no detail surface never offers `open` — so the SAME menu serves the users\n * rail (EC-240), redirects, and any future `CollectionViewSource` without a\n * bespoke per-rail menu. An empty result means the row menu itself renders\n * nothing (never an empty, useless trigger).\n *\n * `duplicate` (EC-442, amended same day) is gated on its OWN capability flag\n * plus a detail surface to land on — never derived from `create`. The owner\n * call (2026-07-17): CMS collections ONLY; every primitive rail declares\n * `duplicate: false` (identities and uploads aren't copyable content;\n * redirects deferred until requested as a product feature).\n */\nexport function rowActionsFor({ capabilities, hasDetailPath, published }: RowActionsInput): RowAction[] {\n const actions: RowAction[] = []\n if (hasDetailPath) actions.push('open')\n if (capabilities.duplicate && hasDetailPath) actions.push('duplicate')\n if (capabilities.publish) actions.push(published ? 'unpublish' : 'publish')\n if (capabilities.remove) actions.push('delete')\n return actions\n}\n"]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { FilterScalar } from '@elytracms/core/content';
|
|
2
|
-
import type { FilterableFieldOption } from '
|
|
2
|
+
import type { FilterableFieldOption } from '../../lib/cms-views/index.js';
|
|
3
3
|
/**
|
|
4
4
|
* The closed single-scalar value control (EC-247), shared by the filter
|
|
5
5
|
* builder's `eq`/comparison clauses and the bulk set-field action so both
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useState } from 'react';
|
|
3
3
|
import { Input, Select } from '@elytracms/studio/ui';
|
|
4
|
-
import { localizedLabel, translate } from '
|
|
5
|
-
import { scalarFromText } from '
|
|
4
|
+
import { localizedLabel, translate } from '../../lib/i18n/index.js';
|
|
5
|
+
import { scalarFromText } from '../../lib/cms-views/index.js';
|
|
6
6
|
import { DateInput } from './date-input.js';
|
|
7
7
|
import { datePickerBounds } from './date-input-core.js';
|
|
8
8
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scalar-value-editor.js","sourceRoot":"","sources":["../../../src/components/cms/scalar-value-editor.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAEhC,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAA;AACpD,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"scalar-value-editor.js","sourceRoot":"","sources":["../../../src/components/cms/scalar-value-editor.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useState } from 'react'\nimport type { FilterScalar } from '@elytracms/core/content'\nimport { Input, Select } from '@elytracms/studio/ui'\nimport { localizedLabel, translate } from '../../lib/i18n'\nimport { scalarFromText } from '../../lib/cms-views'\nimport type { FilterableFieldOption } from '../../lib/cms-views'\nimport { DateInput } from './date-input'\nimport { datePickerBounds } from './date-input-core'\n\n/**\n * The closed single-scalar value control (EC-247), shared by the filter\n * builder's `eq`/comparison clauses and the bulk set-field action so both\n * surfaces enter a value the SAME way. It renders exactly the input the field's\n * schema type warrants — a select for boolean/select, a date input, a\n * number/text input otherwise. The `in` multi-value list and the\n * publication-state pseudo field stay in the filter builder: they are\n * filter-only and never a set-field target.\n *\n * It is deliberately *closed* — no field type invents a new control — keeping\n * value entry consistent and agent-inspectable across the two surfaces.\n */\nexport function ScalarFieldValueEditor({\n option,\n value,\n testId,\n ariaLabel = translate('cms.field.value'),\n onChange,\n}: {\n /** The field whose value is being entered (its `def` drives the control). */\n option: FilterableFieldOption | undefined\n value: FilterScalar\n testId: string\n ariaLabel?: string\n onChange: (value: FilterScalar) => void\n}) {\n switch (option?.def?.type) {\n case 'boolean':\n return (\n <Select\n aria-label={ariaLabel}\n value={value === true ? 'true' : 'false'}\n onChange={(event) => onChange(event.target.value === 'true')}\n className=\"h-7 w-auto py-0 text-xs\"\n data-testid={testId}\n >\n <option value=\"true\">{translate('common.true')}</option>\n <option value=\"false\">{translate('common.false')}</option>\n </Select>\n )\n case 'select':\n return (\n <Select\n aria-label={ariaLabel}\n value={String(value)}\n onChange={(event) => onChange(event.target.value)}\n className=\"h-7 w-auto py-0 text-xs\"\n data-testid={testId}\n >\n <option value=\"\">—</option>\n {option.def.options.map((opt) => (\n <option key={opt.value} value={opt.value}>\n {localizedLabel(opt.label, opt.value)}\n </option>\n ))}\n </Select>\n )\n case 'date':\n // EC-463: the shared date control — a segment keystroke never commits a\n // partial year, and pasting/filling an ISO date works.\n return (\n <DateInput\n ariaLabel={ariaLabel}\n value={value}\n onChange={(stored) => onChange(stored ?? '')}\n className=\"h-7 w-36 text-xs\"\n testId={testId}\n {...datePickerBounds(option.def.validation)}\n />\n )\n case 'number':\n return (\n <DraftValueInput\n type=\"number\"\n initial={value === '' ? '' : String(value)}\n ariaLabel={ariaLabel}\n testId={testId}\n onText={(raw) => onChange(scalarFromText(option, raw))}\n />\n )\n default:\n return (\n <DraftValueInput\n initial={String(value)}\n ariaLabel={ariaLabel}\n testId={testId}\n onText={(raw) => onChange(scalarFromText(option, raw))}\n />\n )\n }\n}\n\n/**\n * Text input with a local draft so typing is never re-normalized mid-keystroke\n * (e.g. a trailing comma in a filter `in` list). Remount (key it) when the\n * field/operator changes so the draft resets to the new initial.\n */\nexport function DraftValueInput({\n initial,\n type = 'text',\n placeholder,\n ariaLabel = translate('cms.field.value'),\n testId,\n onText,\n}: {\n initial: string\n type?: 'text' | 'number'\n placeholder?: string\n ariaLabel?: string\n testId: string\n onText: (raw: string) => void\n}) {\n const [draft, setDraft] = useState(initial)\n return (\n <Input\n aria-label={ariaLabel}\n type={type}\n value={draft}\n placeholder={placeholder ?? translate('cms.field.valuePlaceholder')}\n onChange={(event) => {\n setDraft(event.target.value)\n onText(event.target.value)\n }}\n className=\"h-7 w-36 text-xs\"\n data-testid={testId}\n />\n )\n}\n"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { QueryKey } from '@tanstack/react-query';
|
|
2
2
|
import type { CmsDocument, DocumentRef } from '@elytracms/core/cms-core';
|
|
3
|
-
import type { CollectionViewSource } from '
|
|
3
|
+
import type { CollectionViewSource } from '../../lib/collection-view/collection-view-source.js';
|
|
4
4
|
import type { DiscardDraftStatus } from './discard-draft.js';
|
|
5
5
|
export interface DiscardController {
|
|
6
6
|
status: DiscardDraftStatus;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useCallback, useState } from 'react';
|
|
2
|
-
import { translate } from '
|
|
2
|
+
import { translate } from '../../lib/i18n/index.js';
|
|
3
3
|
import { discardDraftStatus } from './discard-draft.js';
|
|
4
4
|
import { useRestoreVersion } from './use-restore-version.js';
|
|
5
5
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-discard-controller.js","sourceRoot":"","sources":["../../../src/components/cms/use-discard-controller.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAI7C,OAAO,EAAE,SAAS,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"use-discard-controller.js","sourceRoot":"","sources":["../../../src/components/cms/use-discard-controller.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAI7C,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC1C,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AAEpD,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAA;AAezD;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,oBAAoB,CAAC,KAapC;IACC,MAAM,EACJ,UAAU,EACV,SAAS,EACT,YAAY,EACZ,oBAAoB,EACpB,sBAAsB,EACtB,UAAU,EACV,KAAK,EACL,MAAM,EACN,MAAM,EACN,YAAY,EACZ,KAAK,EACL,IAAI,GACL,GAAG,KAAK,CAAA;IAET,MAAM,CAAC,WAAW,EAAE,cAAc,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IACrD,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,iBAAiB,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC,CAAA;IAEtG,MAAM,MAAM,GAAG,kBAAkB,CAAC;QAChC,UAAU;QACV,SAAS;QACT,YAAY;QACZ,iBAAiB,EAAE,oBAAoB;QACvC,KAAK;KACN,CAAC,CAAA;IACF,MAAM,cAAc,GAClB,MAAM,KAAK,gBAAgB;QACzB,CAAC,CAAC,SAAS,CAAC,mCAAmC,CAAC;QAChD,CAAC,CAAC,MAAM,KAAK,YAAY;YACvB,CAAC,CAAC,SAAS,CAAC,qCAAqC,CAAC;YAClD,CAAC,CAAC,MAAM,KAAK,SAAS;gBACpB,CAAC,CAAC,SAAS,CAAC,gBAAgB,CAAC;gBAC7B,CAAC,CAAC,SAAS,CAAA;IAEnB,MAAM,OAAO,GAAG,WAAW,CAAC,KAAK,IAAI,EAAE;QACrC,IAAI,sBAAsB,KAAK,SAAS;YAAE,OAAM;QAChD,IAAI,CAAC;YACH,4EAA4E;YAC5E,+EAA+E;YAC/E,MAAM,OAAO,CAAC,sBAAsB,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAA;QACxD,CAAC;gBAAS,CAAC;YACT,cAAc,CAAC,KAAK,CAAC,CAAA;QACvB,CAAC;IACH,CAAC,EAAE,CAAC,sBAAsB,EAAE,OAAO,CAAC,CAAC,CAAA;IAErC,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,WAAW,EAAE,cAAc,EAAE,IAAI,EAAE,OAAO,EAAE,CAAA;AAC/E,CAAC","sourcesContent":["import { useCallback, useState } from 'react'\nimport type { QueryKey } from '@tanstack/react-query'\nimport type { CmsDocument, DocumentRef } from '@elytracms/core/cms-core'\nimport type { CollectionViewSource } from '../../lib/collection-view/collection-view-source'\nimport { translate } from '../../lib/i18n'\nimport { discardDraftStatus } from './discard-draft'\nimport type { DiscardDraftStatus } from './discard-draft'\nimport { useRestoreVersion } from './use-restore-version'\n\nexport interface DiscardController {\n status: DiscardDraftStatus\n /** The hint shown when the item is disabled (view-read-only / up-to-date / loading). */\n disabledReason: string | undefined\n /** Is the confirmation dialog open? */\n confirmOpen: boolean\n setConfirmOpen: (open: boolean) => void\n /** Is a discard restore round-tripping (gates the dialog buttons)? */\n busy: boolean\n /** Confirm the discard: flush → restore the pinned published version whole. */\n discard: () => Promise<void>\n}\n\n/**\n * EC-316 / EC-325 — the \"Discard changes\" controller. Reverting the draft to the\n * published content IS a whole-document restore of the pinned published version,\n * so it routes through the SAME shared restore sequence\n * ({@link useRestoreVersion}) the Compare view's and Versions pane's restores use\n * — never a parallel write path. That sequence flushes a dirty in-memory buffer\n * FIRST (`documents.restore` snapshots whatever is currently PERSISTED), closing\n * the undo-guarantee gap the header's discard used to have.\n *\n * The enablement is the pure `discardDraftStatus` decision (tested in\n * `discard-draft.test.ts`); its semantic-equality edge is the SAME comparator the\n * Versions pane's publish gate uses, so the two never contradict (EC-298).\n */\nexport function useDiscardController(input: {\n canDiscard: boolean\n published: boolean\n viewReadOnly: boolean\n publishedSnapshotDoc: CmsDocument | undefined\n publishedVersionNumber: number | undefined\n versionKey: QueryKey\n draft: CmsDocument\n source: CollectionViewSource\n docRef: DocumentRef\n replaceDraft: (doc: CmsDocument) => void\n dirty: boolean\n save: () => void\n}): DiscardController {\n const {\n canDiscard,\n published,\n viewReadOnly,\n publishedSnapshotDoc,\n publishedVersionNumber,\n versionKey,\n draft,\n source,\n docRef,\n replaceDraft,\n dirty,\n save,\n } = input\n\n const [confirmOpen, setConfirmOpen] = useState(false)\n const { restore, busy } = useRestoreVersion({ source, docRef, versionKey, dirty, save, replaceDraft })\n\n const status = discardDraftStatus({\n canDiscard,\n published,\n viewReadOnly,\n publishedSnapshot: publishedSnapshotDoc,\n draft,\n })\n const disabledReason =\n status === 'view-read-only'\n ? translate('cms.editor.switchToDraftToDiscard')\n : status === 'up-to-date'\n ? translate('cms.editor.discardDraftUpToDateHint')\n : status === 'loading'\n ? translate('common.loading')\n : undefined\n\n const discard = useCallback(async () => {\n if (publishedVersionNumber === undefined) return\n try {\n // Whole-document restore of the pinned published version (the shared helper\n // flushes any dirty buffer first, mirroring the Versions pane / Compare view).\n await restore(publishedVersionNumber, { kind: 'all' })\n } finally {\n setConfirmOpen(false)\n }\n }, [publishedVersionNumber, restore])\n\n return { status, disabledReason, confirmOpen, setConfirmOpen, busy, discard }\n}\n"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { PresenceEntry } from '@elytracms/core/persistence';
|
|
2
2
|
import type { CmsDocument, CollectionDef, DocumentPatch, FieldDef, Locale, PropFieldLookup, ValuePath } from '@elytracms/core/cms-core';
|
|
3
|
-
import type { CollectionViewSource } from '
|
|
3
|
+
import type { CollectionViewSource } from '../../lib/collection-view/collection-view-source.js';
|
|
4
4
|
import type { Scheduler, UncommittedPosition } from './draft-commit.js';
|
|
5
5
|
/**
|
|
6
6
|
* EC-595 A2 — the detail editor's draft over the revisioned document (and, for the
|
|
@@ -93,4 +93,10 @@ export interface UseDocumentDraftInput {
|
|
|
93
93
|
/** The reader's clock for presence liveness (ISO); `new Date().toISOString()` by default. */
|
|
94
94
|
now?: () => string;
|
|
95
95
|
}
|
|
96
|
+
/**
|
|
97
|
+
* EC-616 — the base a record save is diffed against: the source's value whenever the draft
|
|
98
|
+
* equals it (the record as opened, or as a save came back), else the previous base (the
|
|
99
|
+
* editor has unsaved changes; a change from another writer meanwhile is not theirs). Pure.
|
|
100
|
+
*/
|
|
101
|
+
export declare function recordSaveBase(previous: CmsDocument, draft: CmsDocument, doc: CmsDocument): CmsDocument;
|
|
96
102
|
export declare function useDocumentDraft(input: UseDocumentDraftInput): DocumentDraft;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
2
|
import { PRESENCE_TTL_MS, livePresence, } from '@elytracms/core/persistence';
|
|
3
3
|
import { applyDocumentPatches, formatValuePath, readValueAt, semanticValueEqual, writeField, } from '@elytracms/core/cms-core';
|
|
4
|
-
import { fieldPositionPath } from '
|
|
4
|
+
import { fieldPositionPath } from '../../lib/cms/field-patch.js';
|
|
5
5
|
import { compositionPatches } from '../builder/composition-patches.js';
|
|
6
6
|
import { commitPolicyOf } from './commit-policy.js';
|
|
7
7
|
import { browserScheduler, createDraftCommitter } from './draft-commit.js';
|
|
@@ -71,6 +71,14 @@ export function presenceReaderNow(entries, sessionId, fallback) {
|
|
|
71
71
|
const own = entries.filter((entry) => entry.sessionId === sessionId).map((entry) => entry.at);
|
|
72
72
|
return own.length > 0 ? own.sort().at(-1) : fallback;
|
|
73
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* EC-616 — the base a record save is diffed against: the source's value whenever the draft
|
|
76
|
+
* equals it (the record as opened, or as a save came back), else the previous base (the
|
|
77
|
+
* editor has unsaved changes; a change from another writer meanwhile is not theirs). Pure.
|
|
78
|
+
*/
|
|
79
|
+
export function recordSaveBase(previous, draft, doc) {
|
|
80
|
+
return semanticValueEqual(draft, doc) ? doc : previous;
|
|
81
|
+
}
|
|
74
82
|
export function useDocumentDraft(input) {
|
|
75
83
|
const { source, collection, doc, locale, lookupManifest, canvasField } = input;
|
|
76
84
|
const writes = source.writes;
|
|
@@ -78,6 +86,12 @@ export function useDocumentDraft(input) {
|
|
|
78
86
|
const ref = useMemo(() => ({ collection: doc.collection, id: doc.id }), [doc.collection, doc.id]);
|
|
79
87
|
const [draft, setDraftState] = useState(doc);
|
|
80
88
|
const draftRef = useRef(draft);
|
|
89
|
+
// EC-616: the record as the editor opened it (or as of their last successful save) — the
|
|
90
|
+
// BASE a record rail diffs a save against, so it writes only the editor's changes. It moves
|
|
91
|
+
// only while the draft equals the source's value (at open, and once a save is reflected
|
|
92
|
+
// back): another writer's change arriving mid-edit never becomes the editor's base.
|
|
93
|
+
const baseRef = useRef(doc);
|
|
94
|
+
baseRef.current = recordSaveBase(baseRef.current, draftRef.current, doc);
|
|
81
95
|
const setDraft = useCallback((next) => {
|
|
82
96
|
draftRef.current = next;
|
|
83
97
|
setDraftState(next);
|
|
@@ -185,7 +199,7 @@ export function useDocumentDraft(input) {
|
|
|
185
199
|
if (documentModel)
|
|
186
200
|
committer.commitAll();
|
|
187
201
|
else if (writes.kind === 'record')
|
|
188
|
-
writes.save(draftRef.current);
|
|
202
|
+
writes.save(draftRef.current, baseRef.current);
|
|
189
203
|
}, [documentModel, committer, writes]);
|
|
190
204
|
const flush = useCallback(async () => {
|
|
191
205
|
save();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-document-draft.js","sourceRoot":"","sources":["../../../src/components/cms/use-document-draft.ts"],"names":[],"mappings":"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","sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport {\n PRESENCE_TTL_MS,\n livePresence,\n} from '@elytracms/core/persistence'\nimport type { PresenceEntry } from '@elytracms/core/persistence'\nimport {\n applyDocumentPatches,\n formatValuePath,\n readValueAt,\n semanticValueEqual,\n writeField,\n} from '@elytracms/core/cms-core'\nimport type {\n CmsDocument,\n CollectionDef,\n DocumentPatch,\n DocumentRef,\n FieldDef,\n Locale,\n PropFieldLookup,\n ValuePath,\n} from '@elytracms/core/cms-core'\nimport type { CollectionViewSource } from '@elytracms/studio/lib/collection-view/collection-view-source'\nimport { fieldPositionPath } from '@elytracms/studio/lib/cms/field-patch'\nimport { compositionPatches } from '../builder/composition-patches'\nimport { commitPolicyOf } from './commit-policy'\nimport { browserScheduler, createDraftCommitter } from './draft-commit'\nimport type { DraftCommitter, Scheduler, UncommittedPosition } from './draft-commit'\n\n/**\n * EC-595 A2 — the detail editor's draft over the revisioned document (and, for the\n * explicit-save rails, over a record).\n *\n * **Document write model** (the CMS): the draft changes per keystroke (live validation\n * reads it); a field's edit is COMMITTED per its control kind's policy\n * ({@link commitPolicyOf} / {@link createDraftCommitter}) as the patch of the position it\n * wrote — never a document. The canvas commits each action's node patches at once\n * (`compositionPatches`). The draft REBASES whenever the session's document changes\n * (a write result, a colleague's change by subscription, a resolved conflict): it is the\n * session's document with this editor's uncommitted positions laid over it — local wins\n * until committed, then the per-path check decides (EC-595 point 7).\n *\n * **Forced flushes** (the ruling: before unmount, tab hide, publish, preview, an AI\n * proposal, duplicate, transfer): {@link DocumentDraft.flush} commits every uncommitted\n * position and awaits the persister. Unmount and tab hide run it here; the editor's\n * publish, duplicate and the AI tap call it.\n *\n * **Thin presence** (Stage A): a heartbeat naming the focused field's path on focus\n * change and every {@link PRESENCE_HEARTBEAT_MS} (under the 30 s TTL), cleared on\n * unmount; the other sessions' live heartbeats arrive with the subscription.\n *\n * **Record write model** (users, redirects, assets, the account): the draft is edited\n * in place and written whole on the editor's Save button (EC-240) — unchanged.\n */\n\n/** The presence heartbeat interval — under {@link PRESENCE_TTL_MS} so a present editor never lapses. */\nexport const PRESENCE_HEARTBEAT_MS = 10_000\n\n/** An injectable visibility source (the tab-hide flush; tests drive it). */\nexport interface VisibilitySource {\n /** Call `onHidden` whenever the tab becomes hidden; returns the unsubscribe. */\n onHidden(onHidden: () => void): () => void\n}\n\nexport const documentVisibility: VisibilitySource = {\n onHidden(onHidden) {\n if (typeof document === 'undefined') return () => {}\n const listener = () => {\n if (document.visibilityState === 'hidden') onHidden()\n }\n document.addEventListener('visibilitychange', listener)\n return () => document.removeEventListener('visibilitychange', listener)\n },\n}\n\n/** The patch that writes the value `doc` holds at `path` (unset when absent). */\nexport function positionPatch(doc: CmsDocument, path: ValuePath): DocumentPatch {\n const value = readValueAt(doc, path)\n return value === undefined ? { op: 'unset', path } : { op: 'set', path, value }\n}\n\n/**\n * THE rebase (pure): the session's document with the editor's uncommitted positions laid\n * over it, values from the editor's current draft.\n */\nexport function rebaseDraft(\n sessionDoc: CmsDocument,\n draft: CmsDocument,\n uncommitted: readonly UncommittedPosition[],\n): CmsDocument {\n if (uncommitted.length === 0) return sessionDoc\n const patches = uncommitted.map((position) => positionPatch(draft, position.path))\n return applyDocumentPatches({ document: sessionDoc, patches, since: [] }).document\n}\n\n/**\n * The reader's \"now\" for presence liveness: THIS session's own latest heartbeat `at`\n * (written by the same store clock as everyone else's — the playground store runs a\n * deterministic sequential clock, Convex the writers' wall clocks), falling back to the\n * injected clock before the first heartbeat lands. Comparing others' `at` against a\n * different time base would call every colleague stale (measured live in the\n * playground: the store's clock is months behind the browser's).\n */\nexport function presenceReaderNow(\n entries: readonly PresenceEntry[],\n sessionId: string,\n fallback: string,\n): string {\n const own = entries.filter((entry) => entry.sessionId === sessionId).map((entry) => entry.at)\n return own.length > 0 ? own.sort().at(-1)! : fallback\n}\n\nexport interface DocumentDraft {\n draft: CmsDocument\n /** Uncommitted (document model) / unsaved (record model) edits exist. */\n dirty: boolean\n /** The record model: the editor renders a Save button + navigation guard. */\n explicitSave: boolean\n /** A field's control wrote `value` (the active locale for a localized field). */\n setFieldValue(field: FieldDef, value: unknown): void\n /** A writer that transforms ONE field position (fill / reset from base). */\n editField(field: FieldDef, update: (draft: CmsDocument) => CmsDocument): void\n /** The canvas emitted a new value for its field (node patches, committed at once). */\n setCanvasValue(field: FieldDef, value: unknown): void\n /** The slot alias (a document-level key: a whole-document write at the held revision). */\n setAlias(alias: string | undefined): void\n /** Field boundary events (focus / blur / Enter) — the commit policy's triggers. */\n focused(field: FieldDef): void\n blurred(field: FieldDef): void\n entered(field: FieldDef): void\n /** The forced commit (sync; the persister serialises what follows) / the record model's Save. */\n save(): void\n /** The FORCED FLUSH: commit everything uncommitted and await persistence. */\n flush(): Promise<void>\n /** Adopt a draft from outside (a restored version). */\n replaceDraft(next: CmsDocument): void\n /** Bumped when the canvas must remount from the draft (a restore, a colleague's change). */\n canvasNonce: number\n /** The OTHER sessions present on this document, live by the reader's clock. */\n presence: readonly PresenceEntry[]\n}\n\nexport interface UseDocumentDraftInput {\n source: CollectionViewSource\n collection: CollectionDef\n doc: CmsDocument\n locale: Locale\n lookupManifest: PropFieldLookup\n /** The composition field the canvas edits on this screen, if any. */\n canvasField?: FieldDef\n scheduler?: Scheduler\n visibility?: VisibilitySource\n /** The reader's clock for presence liveness (ISO); `new Date().toISOString()` by default. */\n now?: () => string\n}\n\nexport function useDocumentDraft(input: UseDocumentDraftInput): DocumentDraft {\n const { source, collection, doc, locale, lookupManifest, canvasField } = input\n const writes = source.writes\n const documentModel = writes.kind === 'document' ? writes : undefined\n const ref: DocumentRef = useMemo(() => ({ collection: doc.collection, id: doc.id }), [doc.collection, doc.id])\n const [draft, setDraftState] = useState<CmsDocument>(doc)\n const draftRef = useRef(draft)\n const setDraft = useCallback((next: CmsDocument) => {\n draftRef.current = next\n setDraftState(next)\n }, [])\n const scheduler = input.scheduler ?? browserScheduler\n const now = input.now ?? (() => new Date().toISOString())\n const fieldsByName = useMemo(() => new Map(collection.fields.map((f) => [f.name, f])), [collection])\n\n // --- Committing --------------------------------------------------------------------\n const writesRef = useRef(documentModel)\n writesRef.current = documentModel\n const committer = useMemo<DraftCommitter>(\n () =>\n createDraftCommitter({\n policyOf: (name) => {\n const field = fieldsByName.get(name)\n return field ? commitPolicyOf(field) : 'blur-or-idle'\n },\n commit: (positions) => {\n const target = writesRef.current\n if (!target) return\n void target.commit(\n ref,\n positions.map((position) => positionPatch(draftRef.current, position.path)),\n positions.map((position) => position.base),\n )\n },\n baseOf: () => writesRef.current?.revisionOf(ref),\n scheduler,\n }),\n // One committer per open document (the editor is keyed by it).\n [ref],\n )\n\n // --- The canvas's value and its remount ---------------------------------------------\n const canvasPath = canvasField ? fieldPositionPath(collection, canvasField.name, locale) : undefined\n const canvasPathKey = canvasPath ? formatValuePath(canvasPath) : ''\n const canvasValueRef = useRef<unknown>(canvasPath ? readValueAt(draft, canvasPath) : undefined)\n const [canvasNonce, setCanvasNonce] = useState(0)\n const syncCanvas = useCallback(\n (next: CmsDocument) => {\n if (!canvasPath) return\n const value = readValueAt(next, canvasPath)\n if (semanticValueEqual(value, canvasValueRef.current)) return\n canvasValueRef.current = value\n setCanvasNonce((n) => n + 1)\n },\n // The path is keyed by its string form.\n [canvasPathKey],\n )\n // A locale switch points the canvas at another bucket: remount from it.\n const lastCanvasPathKey = useRef(canvasPathKey)\n useEffect(() => {\n if (lastCanvasPathKey.current === canvasPathKey) return\n lastCanvasPathKey.current = canvasPathKey\n canvasValueRef.current = canvasPath ? readValueAt(draftRef.current, canvasPath) : undefined\n setCanvasNonce((n) => n + 1)\n }, [canvasPathKey])\n\n // --- The rebase (document model) -------------------------------------------------------\n const firstDoc = useRef(true)\n useEffect(() => {\n if (firstDoc.current) {\n firstDoc.current = false\n return\n }\n if (!documentModel) return\n const next = rebaseDraft(doc, draftRef.current, committer.uncommitted())\n setDraft(next)\n syncCanvas(next)\n }, [doc])\n\n // --- Writers ----------------------------------------------------------------------------\n const setFieldValue = useCallback(\n (field: FieldDef, value: unknown) => {\n const next = writeField(collection, draftRef.current, field.name, locale, value, { lookupManifest })\n setDraft(next)\n if (documentModel) committer.edited(field.name, fieldPositionPath(collection, field.name, locale))\n },\n [collection, locale, lookupManifest, setDraft, documentModel, committer],\n )\n const editField = useCallback(\n (field: FieldDef, update: (current: CmsDocument) => CmsDocument) => {\n setDraft(update(draftRef.current))\n if (documentModel) committer.edited(field.name, fieldPositionPath(collection, field.name, locale))\n },\n [collection, locale, setDraft, documentModel, committer],\n )\n const setCanvasValue = useCallback(\n (field: FieldDef, value: unknown) => {\n const path = fieldPositionPath(collection, field.name, locale)\n const before = readValueAt(draftRef.current, path)\n const next = writeField(collection, draftRef.current, field.name, locale, value, { lookupManifest })\n const after = readValueAt(next, path)\n setDraft(next)\n canvasValueRef.current = after\n if (!documentModel) return\n // The canvas writer's node patches, keyed by node id (computed here, in its write\n // path, from the canonical values before and after the action — never a document diff).\n const localized = path.length > 1 ? locale : undefined\n void documentModel.commit(ref, compositionPatches(before, after, field.name, localized))\n },\n [collection, locale, lookupManifest, setDraft, documentModel, ref],\n )\n const setAlias = useCallback(\n (alias: string | undefined) => {\n const next = { ...draftRef.current }\n if (alias === undefined) delete next.alias\n else next.alias = alias\n setDraft(next)\n if (!documentModel) return\n committer.commitAll()\n documentModel.replace(next)\n },\n [setDraft, documentModel, committer],\n )\n\n // --- Forced flush ----------------------------------------------------------------------\n const save = useCallback(() => {\n if (documentModel) committer.commitAll()\n else if (writes.kind === 'record') writes.save(draftRef.current)\n }, [documentModel, committer, writes])\n const flush = useCallback(async () => {\n save()\n if (documentModel) await documentModel.flush(ref)\n }, [save, documentModel, ref])\n\n // Tab hide: commit and flush. Unmount: the same, and leave the document.\n const visibility = input.visibility ?? documentVisibility\n useEffect(() => {\n if (!documentModel) return\n return visibility.onHidden(() => {\n committer.commitAll()\n void documentModel.flush(ref)\n })\n }, [ref])\n useEffect(\n () => () => {\n const target = writesRef.current\n if (!target) return\n committer.commitAll()\n committer.dispose()\n void target.flush(ref)\n target.clearPresence(ref)\n },\n [ref],\n )\n\n // --- Subscription + presence ------------------------------------------------------------\n const [presenceEntries, setPresenceEntries] = useState<readonly PresenceEntry[]>([])\n const [clock, setClock] = useState(now)\n useEffect(() => {\n if (!documentModel) return\n return documentModel.subscribe(ref, setPresenceEntries)\n }, [ref])\n const focusedPathRef = useRef<ValuePath | undefined>(undefined)\n const heartbeat = useCallback(() => {\n writesRef.current?.reportPresence(ref, focusedPathRef.current)\n setClock(now())\n }, [ref])\n useEffect(() => {\n if (!documentModel) return\n heartbeat()\n let handle: unknown\n const tick = () => {\n heartbeat()\n handle = scheduler.setTimeout(tick, PRESENCE_HEARTBEAT_MS)\n }\n handle = scheduler.setTimeout(tick, PRESENCE_HEARTBEAT_MS)\n return () => scheduler.clearTimeout(handle)\n }, [ref])\n const presence = useMemo(\n () =>\n documentModel\n ? livePresence(\n presenceEntries,\n presenceReaderNow(presenceEntries, documentModel.presenceSessionId, clock),\n PRESENCE_TTL_MS,\n ).filter((entry) => entry.sessionId !== documentModel.presenceSessionId)\n : [],\n [presenceEntries, clock, documentModel],\n )\n\n const focused = useCallback(\n (field: FieldDef) => {\n if (!documentModel) return\n committer.focused(field.name)\n focusedPathRef.current = fieldPositionPath(collection, field.name, locale)\n heartbeat()\n },\n [documentModel, committer, collection, locale, heartbeat],\n )\n const blurred = useCallback(\n (field: FieldDef) => {\n if (!documentModel) return\n committer.blurred(field.name)\n focusedPathRef.current = undefined\n heartbeat()\n },\n [documentModel, committer, heartbeat],\n )\n const entered = useCallback(\n (field: FieldDef) => {\n if (documentModel) committer.entered(field.name)\n },\n [documentModel, committer],\n )\n\n const replaceDraft = useCallback(\n (next: CmsDocument) => {\n setDraft(next)\n syncCanvas(next)\n },\n [setDraft, syncCanvas],\n )\n\n const dirty = useMemo(() => !semanticValueEqual(draft, doc), [draft, doc])\n\n return {\n draft,\n dirty,\n explicitSave: writes.kind === 'record',\n setFieldValue,\n editField,\n setCanvasValue,\n setAlias,\n focused,\n blurred,\n entered,\n save,\n flush,\n replaceDraft,\n canvasNonce,\n presence,\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"use-document-draft.js","sourceRoot":"","sources":["../../../src/components/cms/use-document-draft.ts"],"names":[],"mappings":"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","sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport {\n PRESENCE_TTL_MS,\n livePresence,\n} from '@elytracms/core/persistence'\nimport type { PresenceEntry } from '@elytracms/core/persistence'\nimport {\n applyDocumentPatches,\n formatValuePath,\n readValueAt,\n semanticValueEqual,\n writeField,\n} from '@elytracms/core/cms-core'\nimport type {\n CmsDocument,\n CollectionDef,\n DocumentPatch,\n DocumentRef,\n FieldDef,\n Locale,\n PropFieldLookup,\n ValuePath,\n} from '@elytracms/core/cms-core'\nimport type { CollectionViewSource } from '../../lib/collection-view/collection-view-source'\nimport { fieldPositionPath } from '../../lib/cms/field-patch'\nimport { compositionPatches } from '../builder/composition-patches'\nimport { commitPolicyOf } from './commit-policy'\nimport { browserScheduler, createDraftCommitter } from './draft-commit'\nimport type { DraftCommitter, Scheduler, UncommittedPosition } from './draft-commit'\n\n/**\n * EC-595 A2 — the detail editor's draft over the revisioned document (and, for the\n * explicit-save rails, over a record).\n *\n * **Document write model** (the CMS): the draft changes per keystroke (live validation\n * reads it); a field's edit is COMMITTED per its control kind's policy\n * ({@link commitPolicyOf} / {@link createDraftCommitter}) as the patch of the position it\n * wrote — never a document. The canvas commits each action's node patches at once\n * (`compositionPatches`). The draft REBASES whenever the session's document changes\n * (a write result, a colleague's change by subscription, a resolved conflict): it is the\n * session's document with this editor's uncommitted positions laid over it — local wins\n * until committed, then the per-path check decides (EC-595 point 7).\n *\n * **Forced flushes** (the ruling: before unmount, tab hide, publish, preview, an AI\n * proposal, duplicate, transfer): {@link DocumentDraft.flush} commits every uncommitted\n * position and awaits the persister. Unmount and tab hide run it here; the editor's\n * publish, duplicate and the AI tap call it.\n *\n * **Thin presence** (Stage A): a heartbeat naming the focused field's path on focus\n * change and every {@link PRESENCE_HEARTBEAT_MS} (under the 30 s TTL), cleared on\n * unmount; the other sessions' live heartbeats arrive with the subscription.\n *\n * **Record write model** (users, redirects, assets, the account): the draft is edited\n * in place and written whole on the editor's Save button (EC-240) — unchanged.\n */\n\n/** The presence heartbeat interval — under {@link PRESENCE_TTL_MS} so a present editor never lapses. */\nexport const PRESENCE_HEARTBEAT_MS = 10_000\n\n/** An injectable visibility source (the tab-hide flush; tests drive it). */\nexport interface VisibilitySource {\n /** Call `onHidden` whenever the tab becomes hidden; returns the unsubscribe. */\n onHidden(onHidden: () => void): () => void\n}\n\nexport const documentVisibility: VisibilitySource = {\n onHidden(onHidden) {\n if (typeof document === 'undefined') return () => {}\n const listener = () => {\n if (document.visibilityState === 'hidden') onHidden()\n }\n document.addEventListener('visibilitychange', listener)\n return () => document.removeEventListener('visibilitychange', listener)\n },\n}\n\n/** The patch that writes the value `doc` holds at `path` (unset when absent). */\nexport function positionPatch(doc: CmsDocument, path: ValuePath): DocumentPatch {\n const value = readValueAt(doc, path)\n return value === undefined ? { op: 'unset', path } : { op: 'set', path, value }\n}\n\n/**\n * THE rebase (pure): the session's document with the editor's uncommitted positions laid\n * over it, values from the editor's current draft.\n */\nexport function rebaseDraft(\n sessionDoc: CmsDocument,\n draft: CmsDocument,\n uncommitted: readonly UncommittedPosition[],\n): CmsDocument {\n if (uncommitted.length === 0) return sessionDoc\n const patches = uncommitted.map((position) => positionPatch(draft, position.path))\n return applyDocumentPatches({ document: sessionDoc, patches, since: [] }).document\n}\n\n/**\n * The reader's \"now\" for presence liveness: THIS session's own latest heartbeat `at`\n * (written by the same store clock as everyone else's — the playground store runs a\n * deterministic sequential clock, Convex the writers' wall clocks), falling back to the\n * injected clock before the first heartbeat lands. Comparing others' `at` against a\n * different time base would call every colleague stale (measured live in the\n * playground: the store's clock is months behind the browser's).\n */\nexport function presenceReaderNow(\n entries: readonly PresenceEntry[],\n sessionId: string,\n fallback: string,\n): string {\n const own = entries.filter((entry) => entry.sessionId === sessionId).map((entry) => entry.at)\n return own.length > 0 ? own.sort().at(-1)! : fallback\n}\n\nexport interface DocumentDraft {\n draft: CmsDocument\n /** Uncommitted (document model) / unsaved (record model) edits exist. */\n dirty: boolean\n /** The record model: the editor renders a Save button + navigation guard. */\n explicitSave: boolean\n /** A field's control wrote `value` (the active locale for a localized field). */\n setFieldValue(field: FieldDef, value: unknown): void\n /** A writer that transforms ONE field position (fill / reset from base). */\n editField(field: FieldDef, update: (draft: CmsDocument) => CmsDocument): void\n /** The canvas emitted a new value for its field (node patches, committed at once). */\n setCanvasValue(field: FieldDef, value: unknown): void\n /** The slot alias (a document-level key: a whole-document write at the held revision). */\n setAlias(alias: string | undefined): void\n /** Field boundary events (focus / blur / Enter) — the commit policy's triggers. */\n focused(field: FieldDef): void\n blurred(field: FieldDef): void\n entered(field: FieldDef): void\n /** The forced commit (sync; the persister serialises what follows) / the record model's Save. */\n save(): void\n /** The FORCED FLUSH: commit everything uncommitted and await persistence. */\n flush(): Promise<void>\n /** Adopt a draft from outside (a restored version). */\n replaceDraft(next: CmsDocument): void\n /** Bumped when the canvas must remount from the draft (a restore, a colleague's change). */\n canvasNonce: number\n /** The OTHER sessions present on this document, live by the reader's clock. */\n presence: readonly PresenceEntry[]\n}\n\nexport interface UseDocumentDraftInput {\n source: CollectionViewSource\n collection: CollectionDef\n doc: CmsDocument\n locale: Locale\n lookupManifest: PropFieldLookup\n /** The composition field the canvas edits on this screen, if any. */\n canvasField?: FieldDef\n scheduler?: Scheduler\n visibility?: VisibilitySource\n /** The reader's clock for presence liveness (ISO); `new Date().toISOString()` by default. */\n now?: () => string\n}\n\n/**\n * EC-616 — the base a record save is diffed against: the source's value whenever the draft\n * equals it (the record as opened, or as a save came back), else the previous base (the\n * editor has unsaved changes; a change from another writer meanwhile is not theirs). Pure.\n */\nexport function recordSaveBase(previous: CmsDocument, draft: CmsDocument, doc: CmsDocument): CmsDocument {\n return semanticValueEqual(draft, doc) ? doc : previous\n}\n\nexport function useDocumentDraft(input: UseDocumentDraftInput): DocumentDraft {\n const { source, collection, doc, locale, lookupManifest, canvasField } = input\n const writes = source.writes\n const documentModel = writes.kind === 'document' ? writes : undefined\n const ref: DocumentRef = useMemo(() => ({ collection: doc.collection, id: doc.id }), [doc.collection, doc.id])\n const [draft, setDraftState] = useState<CmsDocument>(doc)\n const draftRef = useRef(draft)\n // EC-616: the record as the editor opened it (or as of their last successful save) — the\n // BASE a record rail diffs a save against, so it writes only the editor's changes. It moves\n // only while the draft equals the source's value (at open, and once a save is reflected\n // back): another writer's change arriving mid-edit never becomes the editor's base.\n const baseRef = useRef<CmsDocument>(doc)\n baseRef.current = recordSaveBase(baseRef.current, draftRef.current, doc)\n const setDraft = useCallback((next: CmsDocument) => {\n draftRef.current = next\n setDraftState(next)\n }, [])\n const scheduler = input.scheduler ?? browserScheduler\n const now = input.now ?? (() => new Date().toISOString())\n const fieldsByName = useMemo(() => new Map(collection.fields.map((f) => [f.name, f])), [collection])\n\n // --- Committing --------------------------------------------------------------------\n const writesRef = useRef(documentModel)\n writesRef.current = documentModel\n const committer = useMemo<DraftCommitter>(\n () =>\n createDraftCommitter({\n policyOf: (name) => {\n const field = fieldsByName.get(name)\n return field ? commitPolicyOf(field) : 'blur-or-idle'\n },\n commit: (positions) => {\n const target = writesRef.current\n if (!target) return\n void target.commit(\n ref,\n positions.map((position) => positionPatch(draftRef.current, position.path)),\n positions.map((position) => position.base),\n )\n },\n baseOf: () => writesRef.current?.revisionOf(ref),\n scheduler,\n }),\n // One committer per open document (the editor is keyed by it).\n [ref],\n )\n\n // --- The canvas's value and its remount ---------------------------------------------\n const canvasPath = canvasField ? fieldPositionPath(collection, canvasField.name, locale) : undefined\n const canvasPathKey = canvasPath ? formatValuePath(canvasPath) : ''\n const canvasValueRef = useRef<unknown>(canvasPath ? readValueAt(draft, canvasPath) : undefined)\n const [canvasNonce, setCanvasNonce] = useState(0)\n const syncCanvas = useCallback(\n (next: CmsDocument) => {\n if (!canvasPath) return\n const value = readValueAt(next, canvasPath)\n if (semanticValueEqual(value, canvasValueRef.current)) return\n canvasValueRef.current = value\n setCanvasNonce((n) => n + 1)\n },\n // The path is keyed by its string form.\n [canvasPathKey],\n )\n // A locale switch points the canvas at another bucket: remount from it.\n const lastCanvasPathKey = useRef(canvasPathKey)\n useEffect(() => {\n if (lastCanvasPathKey.current === canvasPathKey) return\n lastCanvasPathKey.current = canvasPathKey\n canvasValueRef.current = canvasPath ? readValueAt(draftRef.current, canvasPath) : undefined\n setCanvasNonce((n) => n + 1)\n }, [canvasPathKey])\n\n // --- The rebase (document model) -------------------------------------------------------\n const firstDoc = useRef(true)\n useEffect(() => {\n if (firstDoc.current) {\n firstDoc.current = false\n return\n }\n if (!documentModel) return\n const next = rebaseDraft(doc, draftRef.current, committer.uncommitted())\n setDraft(next)\n syncCanvas(next)\n }, [doc])\n\n // --- Writers ----------------------------------------------------------------------------\n const setFieldValue = useCallback(\n (field: FieldDef, value: unknown) => {\n const next = writeField(collection, draftRef.current, field.name, locale, value, { lookupManifest })\n setDraft(next)\n if (documentModel) committer.edited(field.name, fieldPositionPath(collection, field.name, locale))\n },\n [collection, locale, lookupManifest, setDraft, documentModel, committer],\n )\n const editField = useCallback(\n (field: FieldDef, update: (current: CmsDocument) => CmsDocument) => {\n setDraft(update(draftRef.current))\n if (documentModel) committer.edited(field.name, fieldPositionPath(collection, field.name, locale))\n },\n [collection, locale, setDraft, documentModel, committer],\n )\n const setCanvasValue = useCallback(\n (field: FieldDef, value: unknown) => {\n const path = fieldPositionPath(collection, field.name, locale)\n const before = readValueAt(draftRef.current, path)\n const next = writeField(collection, draftRef.current, field.name, locale, value, { lookupManifest })\n const after = readValueAt(next, path)\n setDraft(next)\n canvasValueRef.current = after\n if (!documentModel) return\n // The canvas writer's node patches, keyed by node id (computed here, in its write\n // path, from the canonical values before and after the action — never a document diff).\n const localized = path.length > 1 ? locale : undefined\n void documentModel.commit(ref, compositionPatches(before, after, field.name, localized))\n },\n [collection, locale, lookupManifest, setDraft, documentModel, ref],\n )\n const setAlias = useCallback(\n (alias: string | undefined) => {\n const next = { ...draftRef.current }\n if (alias === undefined) delete next.alias\n else next.alias = alias\n setDraft(next)\n if (!documentModel) return\n committer.commitAll()\n documentModel.replace(next)\n },\n [setDraft, documentModel, committer],\n )\n\n // --- Forced flush ----------------------------------------------------------------------\n const save = useCallback(() => {\n if (documentModel) committer.commitAll()\n else if (writes.kind === 'record') writes.save(draftRef.current, baseRef.current)\n }, [documentModel, committer, writes])\n const flush = useCallback(async () => {\n save()\n if (documentModel) await documentModel.flush(ref)\n }, [save, documentModel, ref])\n\n // Tab hide: commit and flush. Unmount: the same, and leave the document.\n const visibility = input.visibility ?? documentVisibility\n useEffect(() => {\n if (!documentModel) return\n return visibility.onHidden(() => {\n committer.commitAll()\n void documentModel.flush(ref)\n })\n }, [ref])\n useEffect(\n () => () => {\n const target = writesRef.current\n if (!target) return\n committer.commitAll()\n committer.dispose()\n void target.flush(ref)\n target.clearPresence(ref)\n },\n [ref],\n )\n\n // --- Subscription + presence ------------------------------------------------------------\n const [presenceEntries, setPresenceEntries] = useState<readonly PresenceEntry[]>([])\n const [clock, setClock] = useState(now)\n useEffect(() => {\n if (!documentModel) return\n return documentModel.subscribe(ref, setPresenceEntries)\n }, [ref])\n const focusedPathRef = useRef<ValuePath | undefined>(undefined)\n const heartbeat = useCallback(() => {\n writesRef.current?.reportPresence(ref, focusedPathRef.current)\n setClock(now())\n }, [ref])\n useEffect(() => {\n if (!documentModel) return\n heartbeat()\n let handle: unknown\n const tick = () => {\n heartbeat()\n handle = scheduler.setTimeout(tick, PRESENCE_HEARTBEAT_MS)\n }\n handle = scheduler.setTimeout(tick, PRESENCE_HEARTBEAT_MS)\n return () => scheduler.clearTimeout(handle)\n }, [ref])\n const presence = useMemo(\n () =>\n documentModel\n ? livePresence(\n presenceEntries,\n presenceReaderNow(presenceEntries, documentModel.presenceSessionId, clock),\n PRESENCE_TTL_MS,\n ).filter((entry) => entry.sessionId !== documentModel.presenceSessionId)\n : [],\n [presenceEntries, clock, documentModel],\n )\n\n const focused = useCallback(\n (field: FieldDef) => {\n if (!documentModel) return\n committer.focused(field.name)\n focusedPathRef.current = fieldPositionPath(collection, field.name, locale)\n heartbeat()\n },\n [documentModel, committer, collection, locale, heartbeat],\n )\n const blurred = useCallback(\n (field: FieldDef) => {\n if (!documentModel) return\n committer.blurred(field.name)\n focusedPathRef.current = undefined\n heartbeat()\n },\n [documentModel, committer, heartbeat],\n )\n const entered = useCallback(\n (field: FieldDef) => {\n if (documentModel) committer.entered(field.name)\n },\n [documentModel, committer],\n )\n\n const replaceDraft = useCallback(\n (next: CmsDocument) => {\n setDraft(next)\n syncCanvas(next)\n },\n [setDraft, syncCanvas],\n )\n\n const dirty = useMemo(() => !semanticValueEqual(draft, doc), [draft, doc])\n\n return {\n draft,\n dirty,\n explicitSave: writes.kind === 'record',\n setFieldValue,\n editField,\n setCanvasValue,\n setAlias,\n focused,\n blurred,\n entered,\n save,\n flush,\n replaceDraft,\n canvasNonce,\n presence,\n }\n}\n"]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useMemo } from 'react';
|
|
2
|
-
import { useOperationQuery } from '
|
|
2
|
+
import { useOperationQuery } from '../../lib/query/index.js';
|
|
3
3
|
import { useCollectionView } from '../collection-view/collection-view-context.js';
|
|
4
4
|
/**
|
|
5
5
|
* Recorded-version freshness (EC-322): re-read on exactly the commands that
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-document-versions.js","sourceRoot":"","sources":["../../../src/components/cms/use-document-versions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAA;AAE/B,OAAO,EAAE,iBAAiB,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"use-document-versions.js","sourceRoot":"","sources":["../../../src/components/cms/use-document-versions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAA;AAE/B,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AACnD,OAAO,EAAE,iBAAiB,EAAE,MAAM,4CAA4C,CAAA;AAG9E;;;;;;;;;;;;;;;GAeG;AACH,MAAM,kBAAkB,GAAG;IACzB,oBAAoB;IACpB,mBAAmB;IACnB,qBAAqB;IACrB,sBAAsB;IACtB,mBAAmB;IACnB,kBAAkB;IAClB,iBAAiB;CACT,CAAA;AAEV;4EAC4E;AAC5E,MAAM,UAAU,mBAAmB,CAAC,SAA6B,EAAE,GAAgB;IACjF,OAAO,CAAC,kBAAkB,EAAE,SAAS,IAAI,GAAG,EAAE,GAAG,CAAC,UAAU,EAAE,GAAG,CAAC,EAAE,CAAU,CAAA;AAChF,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,wBAAwB,CAAC,GAAgB,EAAE,OAA+B;IACxF,MAAM,MAAM,GAAG,iBAAiB,EAAE,CAAA;IAClC,MAAM,UAAU,GAAG,OAAO,CACxB,GAAG,EAAE,CAAC,mBAAmB,CAAC,MAAM,CAAC,SAAS,EAAE,GAAG,CAAC,EAChD,CAAC,MAAM,CAAC,SAAS,EAAE,GAAG,CAAC,UAAU,EAAE,GAAG,CAAC,EAAE,CAAC,CAC3C,CAAA;IACD,MAAM,aAAa,GAAG,iBAAiB,CAAuB;QAC5D,QAAQ,EAAE,UAAU;QACpB,0EAA0E;QAC1E,qEAAqE;QACrE,MAAM,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC,MAAM,MAAM,CAAC,UAAU,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE;QAC5E,YAAY,EAAE,kBAAkB;QAChC,GAAG,CAAC,OAAO,EAAE,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACxE,CAAC,CAAA;IACF,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,CAAA;AACtC,CAAC","sourcesContent":["import { useMemo } from 'react'\nimport type { DocumentRef } from '@elytracms/core/cms-core'\nimport { useOperationQuery } from '../../lib/query'\nimport { useCollectionView } from '../collection-view/collection-view-context'\nimport type { DocumentVersionsView } from './cms-store'\n\n/**\n * Recorded-version freshness (EC-322): re-read on exactly the commands that\n * change a document's version history or its live pin — NOT the whole `documents`\n * domain. The draft the editor commits (`documents.patch`, on quiescence — EC-595 A2)\n * creates no version and moves no pin (versions are appended only on\n * publish/snapshot/restore — see `documentVersions` in `convex/schema.ts`), so\n * listening on the whole domain refetched `listDocumentVersions` on every\n * keystroke while the Versions pane was open. The comparison the pane shows\n * (`liveMatchesDraft`) reads the live snapshot from THIS query and the draft from\n * local editor state, so a draft edit re-renders it without any refetch.\n *\n * Every command that DOES change the view is enumerated (a new version-affecting\n * op must be added here): `snapshot`/`restore` append a version; `publish` appends\n * one and moves the pin; `unpublish`/`pinVersion` move the pin; `delete` cascades\n * the history away.\n */\nconst VERSION_RELOAD_OPS = [\n 'documents.snapshot',\n 'documents.publish',\n 'documents.unpublish',\n 'documents.pinVersion',\n 'documents.restore',\n 'documents.delete',\n 'documents.clear',\n] as const\n\n/** The one cache key for a document's version history — shared so the Versions\n * pane and the EC-298 compare view read through a single deduped query. */\nexport function documentVersionsKey(projectId: string | undefined, ref: DocumentRef) {\n return ['documentVersions', projectId ?? '∅', ref.collection, ref.id] as const\n}\n\n/**\n * A document's recorded version history, read through the one operations-backed\n * query cache (EC-308/AD-17). The flush-before-read ordering lives IN the loader,\n * so every fetch — initial, invalidated, or refetch — lists an authoritative\n * history regardless of which surface triggered the change. Two mounts with the\n * same document share one fetch (TanStack dedup on the key).\n *\n * `enabled` (EC-316, default `true`) lets a caller that only needs this for a\n * gating check — e.g. the header's Discard-draft enablement, which isn't\n * otherwise on the version-browsing path — skip the fetch until it actually\n * applies (an unpublished/non-versioned document never needs its history).\n */\nexport function useDocumentVersionsQuery(ref: DocumentRef, options?: { enabled?: boolean }) {\n const source = useCollectionView()\n const versionKey = useMemo(\n () => documentVersionsKey(source.projectId, ref),\n [source.projectId, ref.collection, ref.id],\n )\n const versionsQuery = useOperationQuery<DocumentVersionsView>({\n queryKey: versionKey,\n // The versioning capability owns the flush-before-read ordering; a source\n // without versions (never mounts this) degrades to an empty history.\n loader: async () => (await source.versioning?.load(ref)) ?? { versions: [] },\n invalidateOn: VERSION_RELOAD_OPS,\n ...(options?.enabled !== undefined ? { enabled: options.enabled } : {}),\n })\n return { versionsQuery, versionKey }\n}\n"]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { CollectionDef, FieldDef } from '@elytracms/core/cms-core';
|
|
2
|
-
import type { CollectionViewSource } from '
|
|
2
|
+
import type { CollectionViewSource } from '../../lib/collection-view/collection-view-source.js';
|
|
3
3
|
import type { FieldFocusRequest } from './field-anchors.js';
|
|
4
4
|
/**
|
|
5
5
|
* EC-283 / EC-285 — where a jump-to-field lands, as a pure decision. A field in
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
2
|
import { SIDEBAR_FIELD_GROUP, tabOrder } from '@elytracms/core/cms-core';
|
|
3
|
-
import { setStudioLocale } from '
|
|
4
|
-
import { parseDocumentFieldPath } from '
|
|
3
|
+
import { setStudioLocale } from '../../lib/locale/index.js';
|
|
4
|
+
import { parseDocumentFieldPath } from '../../lib/references/index.js';
|
|
5
5
|
import { anchorCandidates, deepestExistingAnchor, fieldAnchorSelector, } from './field-anchors.js';
|
|
6
6
|
/**
|
|
7
7
|
* EC-283 / EC-285 — where a jump-to-field lands, as a pure decision. A field in
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-field-focus.js","sourceRoot":"","sources":["../../../src/components/cms/use-field-focus.ts"],"names":[],"mappings":"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","sourcesContent":["import { useCallback, useEffect, useRef, useState } from 'react'\nimport { SIDEBAR_FIELD_GROUP, tabOrder } from '@elytracms/core/cms-core'\nimport type { CollectionDef, FieldDef } from '@elytracms/core/cms-core'\nimport type { CollectionViewSource } from '@elytracms/studio/lib/collection-view/collection-view-source'\nimport { setStudioLocale } from '@elytracms/studio/lib/locale'\nimport { parseDocumentFieldPath } from '@elytracms/studio/lib/references'\nimport {\n anchorCandidates,\n deepestExistingAnchor,\n fieldAnchorSelector,\n} from './field-anchors'\nimport type { FieldFocusRequest } from './field-anchors'\n\n/**\n * EC-283 / EC-285 — where a jump-to-field lands, as a pure decision. A field in\n * the reserved `sidebar` group renders in the Details inspector pane (open that\n * pane, EC-285); any other field lives on a content tab (switch to it). Returns\n * `null` for an unknown field name (no-op). Directly testable, no DOM.\n */\nexport function focusTarget(\n collection: CollectionDef,\n fieldName: string,\n): { field: FieldDef; paneId: 'details' } | { field: FieldDef; tab: string } | null {\n const field = collection.fields.find((f) => f.name === fieldName)\n if (!field) return null\n if (field.form?.group === SIDEBAR_FIELD_GROUP) return { field, paneId: 'details' }\n const fieldTabs = tabOrder(collection.form)\n return { field, tab: field.form?.tab ?? fieldTabs[0] ?? 'main' }\n}\n\n/**\n * EC-443 — nested-anchor resolution retries. Expanding a collapsed array item is\n * React state (context-driven), so each nested array level needs a render cycle\n * before its deeper anchors exist; a short retry ladder lets a multi-level\n * expansion cascade settle before the plan degrades to a shallower anchor.\n */\nconst RESOLVE_ATTEMPT_DELAYS_MS = [60, 200, 450] as const\n/** How long the ring highlight (top-level or nested) stays on. */\nconst HIGHLIGHT_CLEAR_MS = 3200\n\nexport interface FieldFocus {\n /** The top-level field ring-highlighted right now (or null). */\n highlightedField: string | null\n /**\n * The active nested focus request (EC-443): drives collapsed-array expansion\n * and the ring on the exact nested control, via `FieldFocusRequestContext`.\n */\n focusRequest: FieldFocusRequest | null\n /** The inspector sidebar's active pane, controlled so a jump can open Details. */\n inspectorPaneId: string | undefined\n setInspectorPaneId: (id: string | undefined) => void\n /**\n * Focus a field by anchor path (`links` or `links/3/badge/text` — the EC-443\n * convention on `FIELD_PATH_ATTR`): switch to its tab/pane (+ locale), expand\n * collapsed array items en route, scroll to the deepest available anchor, and\n * ring-highlight it. Deepest existing anchor wins; the top-level field is the\n * degradation floor.\n */\n focusField: (fieldPath: string, localeHint?: string) => void\n}\n\n/**\n * EC-172 / EC-283 / EC-443 — the field-focus/highlight controller. Focusing a\n * field switches to its tab (or opens the Details pane for a sidebar field) and\n * the given locale, then lands on the DEEPEST available anchor along the path\n * (EC-443): nested paths publish a {@link FieldFocusRequest} (auto-expanding\n * collapsed array items via context), retry-resolve the deepest stamped anchor,\n * scroll it into view, and ring-highlight it — degrading prefix by prefix to the\n * top-level field control, never a silent no-op. Shared by the reference\n * deep-link (`focusPath`, `?field=`) and the Issues pane's jump-to-field. Owns\n * the highlight + inspector-pane state and every scroll/clear timer (cleaned up\n * on unmount, and cleared between back-to-back jumps).\n */\nexport function useFieldFocus(input: {\n collection: CollectionDef\n source: CollectionViewSource\n focusPath: string | undefined\n setActiveTab: (tab: string) => void\n}): FieldFocus {\n const { collection, source, focusPath, setActiveTab } = input\n const [highlightedField, setHighlightedField] = useState<string | null>(null)\n const [focusRequest, setFocusRequest] = useState<FieldFocusRequest | null>(null)\n const [inspectorPaneId, setInspectorPaneId] = useState<string>()\n const focusTimersRef = useRef<number[]>([])\n const nonceRef = useRef(0)\n\n const focusField = useCallback(\n (fieldPath: string, localeHint?: string) => {\n const tokens = fieldPath.split('/')\n const fieldName = tokens[0] ?? fieldPath\n const rest = tokens.slice(1)\n const target = focusTarget(collection, fieldName)\n if (!target) return\n // A sidebar-group field renders in the Details pane; any other on its tab.\n if ('paneId' in target) setInspectorPaneId(target.paneId)\n else setActiveTab(target.tab)\n if (localeHint !== undefined && source.localeConfig.locales.includes(localeHint)) {\n setStudioLocale(localeHint)\n }\n // Clear any timers from a prior jump so back-to-back jumps don't fight.\n focusTimersRef.current.forEach((t) => window.clearTimeout(t))\n\n const scrollTopLevelControl = () => {\n // Some controls (relation/asset pickers) don't render the input id —\n // the field label (`htmlFor`) is always present.\n const control =\n document.getElementById(`f-${target.field.name}`) ??\n document.querySelector(`label[for=\"${CSS.escape(`f-${target.field.name}`)}\"]`)\n control?.scrollIntoView({ block: 'center', behavior: 'smooth' })\n }\n\n if (rest.length === 0) {\n // Top-level jump: exactly the EC-283 behavior — ring the field row, scroll.\n setFocusRequest(null)\n setHighlightedField(target.field.name)\n const scrollTimer = window.setTimeout(scrollTopLevelControl, 60)\n const clearTimer = window.setTimeout(() => setHighlightedField(null), HIGHLIGHT_CLEAR_MS)\n focusTimersRef.current = [scrollTimer, clearTimer]\n return\n }\n\n // EC-443 nested jump. Publishing the request expands collapsed array items\n // along the path (context-driven, `object-array-field`); each retry then\n // probes for the deepest stamped anchor. Settling: full path found → ring +\n // scroll it (early); last attempt → deepest existing prefix (ring follows\n // the REAL element, not the wished-for one); nothing stamped → the\n // top-level field row is the floor.\n const nonce = ++nonceRef.current\n setHighlightedField(null)\n setFocusRequest({ path: fieldPath, nonce })\n const candidates = anchorCandidates(fieldName, rest)\n const settle = (resolved: string | null) => {\n if (resolved !== null && resolved !== fieldName) {\n setFocusRequest({ path: resolved, nonce })\n document\n .querySelector(fieldAnchorSelector(resolved))\n ?.scrollIntoView({ block: 'center', behavior: 'smooth' })\n } else {\n setFocusRequest(null)\n setHighlightedField(target.field.name)\n scrollTopLevelControl()\n }\n }\n const settled = { done: false }\n const attemptTimers = RESOLVE_ATTEMPT_DELAYS_MS.map((delay, i) =>\n window.setTimeout(() => {\n if (settled.done) return\n const resolved = deepestExistingAnchor(\n candidates,\n (path) => document.querySelector(fieldAnchorSelector(path)) !== null,\n )\n const isLastAttempt = i === RESOLVE_ATTEMPT_DELAYS_MS.length - 1\n if (resolved === fieldPath || isLastAttempt) {\n settled.done = true\n settle(resolved)\n }\n }, delay),\n )\n const clearTimer = window.setTimeout(() => {\n setHighlightedField(null)\n setFocusRequest(null)\n }, HIGHLIGHT_CLEAR_MS)\n focusTimersRef.current = [...attemptTimers, clearTimer]\n },\n [collection, source.localeConfig, setActiveTab],\n )\n useEffect(() => () => focusTimersRef.current.forEach((t) => window.clearTimeout(t)), [])\n\n // Reference deep links (EC-172): `?field=<index path>` pinpoints the exact\n // field — nested tokens ride along (EC-443), so the SAME path-aware focus runs.\n useEffect(() => {\n if (!focusPath) return\n const parsed = parseDocumentFieldPath(focusPath)\n if (!parsed) return\n focusField([parsed.field, ...parsed.rest].join('/'), parsed.locale)\n }, [focusPath, focusField])\n\n return { highlightedField, focusRequest, inspectorPaneId, setInspectorPaneId, focusField }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"use-field-focus.js","sourceRoot":"","sources":["../../../src/components/cms/use-field-focus.ts"],"names":[],"mappings":"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","sourcesContent":["import { useCallback, useEffect, useRef, useState } from 'react'\nimport { SIDEBAR_FIELD_GROUP, tabOrder } from '@elytracms/core/cms-core'\nimport type { CollectionDef, FieldDef } from '@elytracms/core/cms-core'\nimport type { CollectionViewSource } from '../../lib/collection-view/collection-view-source'\nimport { setStudioLocale } from '../../lib/locale'\nimport { parseDocumentFieldPath } from '../../lib/references'\nimport {\n anchorCandidates,\n deepestExistingAnchor,\n fieldAnchorSelector,\n} from './field-anchors'\nimport type { FieldFocusRequest } from './field-anchors'\n\n/**\n * EC-283 / EC-285 — where a jump-to-field lands, as a pure decision. A field in\n * the reserved `sidebar` group renders in the Details inspector pane (open that\n * pane, EC-285); any other field lives on a content tab (switch to it). Returns\n * `null` for an unknown field name (no-op). Directly testable, no DOM.\n */\nexport function focusTarget(\n collection: CollectionDef,\n fieldName: string,\n): { field: FieldDef; paneId: 'details' } | { field: FieldDef; tab: string } | null {\n const field = collection.fields.find((f) => f.name === fieldName)\n if (!field) return null\n if (field.form?.group === SIDEBAR_FIELD_GROUP) return { field, paneId: 'details' }\n const fieldTabs = tabOrder(collection.form)\n return { field, tab: field.form?.tab ?? fieldTabs[0] ?? 'main' }\n}\n\n/**\n * EC-443 — nested-anchor resolution retries. Expanding a collapsed array item is\n * React state (context-driven), so each nested array level needs a render cycle\n * before its deeper anchors exist; a short retry ladder lets a multi-level\n * expansion cascade settle before the plan degrades to a shallower anchor.\n */\nconst RESOLVE_ATTEMPT_DELAYS_MS = [60, 200, 450] as const\n/** How long the ring highlight (top-level or nested) stays on. */\nconst HIGHLIGHT_CLEAR_MS = 3200\n\nexport interface FieldFocus {\n /** The top-level field ring-highlighted right now (or null). */\n highlightedField: string | null\n /**\n * The active nested focus request (EC-443): drives collapsed-array expansion\n * and the ring on the exact nested control, via `FieldFocusRequestContext`.\n */\n focusRequest: FieldFocusRequest | null\n /** The inspector sidebar's active pane, controlled so a jump can open Details. */\n inspectorPaneId: string | undefined\n setInspectorPaneId: (id: string | undefined) => void\n /**\n * Focus a field by anchor path (`links` or `links/3/badge/text` — the EC-443\n * convention on `FIELD_PATH_ATTR`): switch to its tab/pane (+ locale), expand\n * collapsed array items en route, scroll to the deepest available anchor, and\n * ring-highlight it. Deepest existing anchor wins; the top-level field is the\n * degradation floor.\n */\n focusField: (fieldPath: string, localeHint?: string) => void\n}\n\n/**\n * EC-172 / EC-283 / EC-443 — the field-focus/highlight controller. Focusing a\n * field switches to its tab (or opens the Details pane for a sidebar field) and\n * the given locale, then lands on the DEEPEST available anchor along the path\n * (EC-443): nested paths publish a {@link FieldFocusRequest} (auto-expanding\n * collapsed array items via context), retry-resolve the deepest stamped anchor,\n * scroll it into view, and ring-highlight it — degrading prefix by prefix to the\n * top-level field control, never a silent no-op. Shared by the reference\n * deep-link (`focusPath`, `?field=`) and the Issues pane's jump-to-field. Owns\n * the highlight + inspector-pane state and every scroll/clear timer (cleaned up\n * on unmount, and cleared between back-to-back jumps).\n */\nexport function useFieldFocus(input: {\n collection: CollectionDef\n source: CollectionViewSource\n focusPath: string | undefined\n setActiveTab: (tab: string) => void\n}): FieldFocus {\n const { collection, source, focusPath, setActiveTab } = input\n const [highlightedField, setHighlightedField] = useState<string | null>(null)\n const [focusRequest, setFocusRequest] = useState<FieldFocusRequest | null>(null)\n const [inspectorPaneId, setInspectorPaneId] = useState<string>()\n const focusTimersRef = useRef<number[]>([])\n const nonceRef = useRef(0)\n\n const focusField = useCallback(\n (fieldPath: string, localeHint?: string) => {\n const tokens = fieldPath.split('/')\n const fieldName = tokens[0] ?? fieldPath\n const rest = tokens.slice(1)\n const target = focusTarget(collection, fieldName)\n if (!target) return\n // A sidebar-group field renders in the Details pane; any other on its tab.\n if ('paneId' in target) setInspectorPaneId(target.paneId)\n else setActiveTab(target.tab)\n if (localeHint !== undefined && source.localeConfig.locales.includes(localeHint)) {\n setStudioLocale(localeHint)\n }\n // Clear any timers from a prior jump so back-to-back jumps don't fight.\n focusTimersRef.current.forEach((t) => window.clearTimeout(t))\n\n const scrollTopLevelControl = () => {\n // Some controls (relation/asset pickers) don't render the input id —\n // the field label (`htmlFor`) is always present.\n const control =\n document.getElementById(`f-${target.field.name}`) ??\n document.querySelector(`label[for=\"${CSS.escape(`f-${target.field.name}`)}\"]`)\n control?.scrollIntoView({ block: 'center', behavior: 'smooth' })\n }\n\n if (rest.length === 0) {\n // Top-level jump: exactly the EC-283 behavior — ring the field row, scroll.\n setFocusRequest(null)\n setHighlightedField(target.field.name)\n const scrollTimer = window.setTimeout(scrollTopLevelControl, 60)\n const clearTimer = window.setTimeout(() => setHighlightedField(null), HIGHLIGHT_CLEAR_MS)\n focusTimersRef.current = [scrollTimer, clearTimer]\n return\n }\n\n // EC-443 nested jump. Publishing the request expands collapsed array items\n // along the path (context-driven, `object-array-field`); each retry then\n // probes for the deepest stamped anchor. Settling: full path found → ring +\n // scroll it (early); last attempt → deepest existing prefix (ring follows\n // the REAL element, not the wished-for one); nothing stamped → the\n // top-level field row is the floor.\n const nonce = ++nonceRef.current\n setHighlightedField(null)\n setFocusRequest({ path: fieldPath, nonce })\n const candidates = anchorCandidates(fieldName, rest)\n const settle = (resolved: string | null) => {\n if (resolved !== null && resolved !== fieldName) {\n setFocusRequest({ path: resolved, nonce })\n document\n .querySelector(fieldAnchorSelector(resolved))\n ?.scrollIntoView({ block: 'center', behavior: 'smooth' })\n } else {\n setFocusRequest(null)\n setHighlightedField(target.field.name)\n scrollTopLevelControl()\n }\n }\n const settled = { done: false }\n const attemptTimers = RESOLVE_ATTEMPT_DELAYS_MS.map((delay, i) =>\n window.setTimeout(() => {\n if (settled.done) return\n const resolved = deepestExistingAnchor(\n candidates,\n (path) => document.querySelector(fieldAnchorSelector(path)) !== null,\n )\n const isLastAttempt = i === RESOLVE_ATTEMPT_DELAYS_MS.length - 1\n if (resolved === fieldPath || isLastAttempt) {\n settled.done = true\n settle(resolved)\n }\n }, delay),\n )\n const clearTimer = window.setTimeout(() => {\n setHighlightedField(null)\n setFocusRequest(null)\n }, HIGHLIGHT_CLEAR_MS)\n focusTimersRef.current = [...attemptTimers, clearTimer]\n },\n [collection, source.localeConfig, setActiveTab],\n )\n useEffect(() => () => focusTimersRef.current.forEach((t) => window.clearTimeout(t)), [])\n\n // Reference deep links (EC-172): `?field=<index path>` pinpoints the exact\n // field — nested tokens ride along (EC-443), so the SAME path-aware focus runs.\n useEffect(() => {\n if (!focusPath) return\n const parsed = parseDocumentFieldPath(focusPath)\n if (!parsed) return\n focusField([parsed.field, ...parsed.rest].join('/'), parsed.locale)\n }, [focusPath, focusField])\n\n return { highlightedField, focusRequest, inspectorPaneId, setInspectorPaneId, focusField }\n}\n"]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { CmsDocument, CollectionDef, DocumentUrl, Locale, LocaleConfig, RouteRecord } from '@elytracms/core/cms-core';
|
|
2
|
-
import type { CollectionViewSource } from '
|
|
2
|
+
import type { CollectionViewSource } from '../../lib/collection-view/collection-view-source.js';
|
|
3
3
|
import type { IssueSeverity } from '../issue-severity.js';
|
|
4
4
|
/**
|
|
5
5
|
* The header's live-link descriptor: an `href` (open live) + a title, or a
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { useMemo } from 'react';
|
|
2
2
|
import { createRouter } from '@elytracms/core/cms-core';
|
|
3
|
-
import { translate } from '
|
|
4
|
-
import { resolveDeploymentFrontendUrl, resolveDeploymentRoutes } from '
|
|
5
|
-
import { usePublishedDocuments } from '
|
|
3
|
+
import { translate } from '../../lib/i18n/index.js';
|
|
4
|
+
import { resolveDeploymentFrontendUrl, resolveDeploymentRoutes } from '../../lib/workspace/index.js';
|
|
5
|
+
import { usePublishedDocuments } from '../../lib/cms/published-documents.js';
|
|
6
6
|
/**
|
|
7
7
|
* 0.0.42 (round 3) — the header composed-URL row's open-live affordance, as a pure
|
|
8
8
|
* decision. Only a routable collection shows the row; its leaf opens the live page
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-live-url.js","sourceRoot":"","sources":["../../../src/components/cms/use-live-url.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAA;AAC/B,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAUvD,OAAO,EAAE,SAAS,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"use-live-url.js","sourceRoot":"","sources":["../../../src/components/cms/use-live-url.ts"],"names":[],"mappings":"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","sourcesContent":["import { useMemo } from 'react'\nimport { createRouter } from '@elytracms/core/cms-core'\nimport type {\n CmsDocument,\n CollectionDef,\n DocumentUrl,\n Locale,\n LocaleConfig,\n RouteRecord,\n} from '@elytracms/core/cms-core'\nimport type { CollectionViewSource } from '../../lib/collection-view/collection-view-source'\nimport { translate } from '../../lib/i18n'\nimport { resolveDeploymentFrontendUrl, resolveDeploymentRoutes } from '../../lib/workspace'\nimport { usePublishedDocuments } from '../../lib/cms/published-documents'\nimport type { IssueSeverity } from '../issue-severity'\n\n/**\n * The header's live-link descriptor: an `href` (open live) + a title, or a\n * reason-only title while it isn't openable (loading / publish-first / no\n * frontendUrl). `severity` is set when the reason is a FINDING about the URL\n * rather than a lifecycle state — the renderer then shows the shared issue glyph\n * (EC-507) in place of the muted open-live icon, so the row and the Issues pane\n * say the same thing (EC-518).\n */\nexport interface HeaderLive {\n href?: string\n title: string\n severity?: IssueSeverity\n}\n\n/**\n * 0.0.42 (round 3) — the header composed-URL row's open-live affordance, as a pure\n * decision. Only a routable collection shows the row; its leaf opens the live page\n * when the doc is published AND a frontendUrl is set (an absolute `href`),\n * otherwise it is a muted icon whose tooltip explains WHY (still loading, publish\n * first, or set the frontendUrl). The draft-derived path itself updates as you\n * type — only the OPEN-live action is published-gated, preserving draft/live.\n *\n * EC-518 — a URL the engine has computed as NOT `addressable` (EC-492: the host\n * re-derives a different `{locale, path}` pair from it, so no request reaches the\n * document) never becomes an `href`, frontendUrl or not. The engine's verdict is\n * consumed, not re-derived: the row shows the same `warning` the Issues pane shows\n * for it (`document-reachability.ts`), because a link titled \"Open /de\" a few\n * pixels from that warning is the stronger signal, and it is the wrong one.\n */\nexport function resolveHeaderLive(input: {\n routableCollection: boolean\n liveUrl: Pick<DocumentUrl, 'href' | 'path' | 'addressable'> | undefined\n loading: boolean\n published: boolean\n}): HeaderLive | undefined {\n if (!input.routableCollection) return undefined\n if (input.liveUrl && !input.liveUrl.addressable) {\n return { title: translate('cms.issue.unaddressable-url'), severity: 'warning' }\n }\n if (input.liveUrl?.href) {\n return { href: input.liveUrl.href, title: translate('cms.editor.openLiveUrl', { path: input.liveUrl.path }) }\n }\n return {\n title: input.loading\n ? translate('cms.editor.loadingLiveUrl')\n : !input.published\n ? translate('cms.editor.publishToViewLive')\n : translate('cms.editor.setFrontendUrlHint'),\n }\n}\n\n/**\n * EC-590 — derive a published document's live URL through THE router\n * (`createRouter(...).urlFor`), the same reverse direction delivery's links and sitemap\n * read, in the one precedence order — so the studio's live link can no longer advertise a\n * URL the site 404s (IDENTITY M4). The project's routes as declared: the engine derives no\n * home route any more (the former `mergeRouteRecords` + `fallbackHomeRoutes` merge went\n * with it). Pure (no React), so it is unit-tested directly rather than through the hook.\n */\nexport function resolveDocumentLiveUrl(input: {\n routes: readonly RouteRecord[]\n localeConfig: LocaleConfig\n frontendUrl: string | undefined\n collection: CollectionDef\n doc: CmsDocument\n siblings: readonly CmsDocument[]\n locale: Locale\n}): DocumentUrl | undefined {\n return createRouter({\n routes: input.routes,\n collections: [input.collection],\n locales: input.localeConfig,\n }).urlFor(input.doc, {\n siblings: input.siblings,\n locale: input.locale,\n ...(input.frontendUrl !== undefined ? { frontendUrl: input.frontendUrl } : {}),\n })\n}\n\n/**\n * EC-205 / EC-216 — the LIVE URL derives strictly from PUBLISHED data (the pinned\n * published snapshot + its published ancestors), never the draft, so an unpublished\n * slug/parent edit can't leak into the live URL. Resolves the header's live-link\n * descriptor for the composed-URL row; `undefined` for a non-routable collection.\n */\nexport function useLiveUrl(input: {\n source: CollectionViewSource\n collection: CollectionDef\n doc: CmsDocument\n locale: Locale\n published: boolean\n routableCollection: boolean\n}): { headerLive: HeaderLive | undefined } {\n const { source, collection, doc, locale, published, routableCollection } = input\n const publishedDocs = usePublishedDocuments(source.projectId, collection.id)\n const liveUrl = useMemo(() => {\n const publishedDoc = publishedDocs.byId.get(doc.id)\n if (!publishedDoc) return undefined\n return resolveDocumentLiveUrl({\n routes: resolveDeploymentRoutes(),\n localeConfig: source.localeConfig,\n frontendUrl: resolveDeploymentFrontendUrl(),\n collection,\n doc: publishedDoc,\n siblings: [...publishedDocs.byId.values()],\n locale,\n })\n }, [publishedDocs, collection, doc.id, locale, source.localeConfig])\n\n const headerLive = resolveHeaderLive({\n routableCollection,\n liveUrl,\n loading: publishedDocs.status === 'loading',\n published,\n })\n return { headerLive }\n}\n"]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { DocumentRef } from '@elytracms/core/cms-core';
|
|
2
|
-
import type { CollectionViewSource } from '
|
|
2
|
+
import type { CollectionViewSource } from '../../lib/collection-view/collection-view-source.js';
|
|
3
3
|
export type PublishState = 'idle' | 'pending' | 'success';
|
|
4
4
|
/**
|
|
5
5
|
* EC-268 / 0.0.39 — the split Publish button's shared gate, computed once so the
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useCallback, useRef, useState } from 'react';
|
|
2
2
|
import { STUDIO_PROJECT_ROUTE, studioPath, useNavigate } from '@elytracms/studio/router';
|
|
3
3
|
import { toast } from '@elytracms/studio/ui';
|
|
4
|
-
import { translate } from '
|
|
4
|
+
import { translate } from '../../lib/i18n/index.js';
|
|
5
5
|
/**
|
|
6
6
|
* EC-268 / 0.0.39 — the split Publish button's shared gate, computed once so the
|
|
7
7
|
* primary "Publish" and the "Publish & close" item can never diverge. A pure
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-publish-controller.js","sourceRoot":"","sources":["../../../src/components/cms/use-publish-controller.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AACrD,OAAO,EAAE,oBAAoB,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAA;AAGxF,OAAO,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAA;AAC5C,OAAO,EAAE,SAAS,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"use-publish-controller.js","sourceRoot":"","sources":["../../../src/components/cms/use-publish-controller.ts"],"names":[],"mappings":"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","sourcesContent":["import { useCallback, useRef, useState } from 'react'\nimport { STUDIO_PROJECT_ROUTE, studioPath, useNavigate } from '@elytracms/studio/router'\nimport type { DocumentRef } from '@elytracms/core/cms-core'\nimport type { CollectionViewSource } from '../../lib/collection-view/collection-view-source'\nimport { toast } from '@elytracms/studio/ui'\nimport { translate } from '../../lib/i18n'\n\nexport type PublishState = 'idle' | 'pending' | 'success'\n\n/**\n * EC-268 / 0.0.39 — the split Publish button's shared gate, computed once so the\n * primary \"Publish\" and the \"Publish & close\" item can never diverge. A pure\n * decision (directly testable): the Published perspective is read-only (switch to\n * Draft to publish), schema errors block (with a count-aware hint), and an\n * in-flight publish disables re-entry.\n */\nexport function publishGate(input: {\n hasErrors: boolean\n viewReadOnly: boolean\n publishState: PublishState\n errorCount: number\n}): { disabled: boolean; title: string | undefined } {\n const disabled = input.hasErrors || input.viewReadOnly || input.publishState === 'pending'\n const title = input.viewReadOnly\n ? translate('cms.editor.switchToDraftToPublish')\n : input.hasErrors\n ? translate(\n input.errorCount === 1\n ? 'cms.editor.fixIssuesToPublish.one'\n : 'cms.editor.fixIssuesToPublish.other',\n { count: input.errorCount },\n )\n : undefined\n return { disabled, title }\n}\n\nexport interface PublishController {\n publishState: PublishState\n /** Publish the draft. */\n handlePublish: () => void\n /** Publish and, on success, return to the rail's list (0.0.39 \"Publish & close\"). */\n handlePublishAndClose: () => void\n}\n\n/**\n * EC-268 / 0.0.39 — the publish controller. Owns the publish indicator state and the\n * 0.0.39 \"Publish & close\" navigation (a successful publish returns to the rail-owned\n * list).\n *\n * EC-589: the EC-300 confirm-through for unpublished reuse embeds is gone. Integrity is\n * two-state and declared: a STRICT relation to an unpublished target is a blocking\n * validation error (the reuse embed declares `strict: true` on its relation prop), so\n * the publish button is disabled by the same issues that gate everything else — and\n * `documents.publish` refuses it for any client; a weak one publishes with a warning.\n */\nexport function usePublishController(input: {\n source: CollectionViewSource\n docRef: DocumentRef\n projectId: string\n /** EC-595 A2 — the editor's FORCED FLUSH (commit everything uncommitted, await persistence). */\n flushDraft: () => Promise<void>\n}): PublishController {\n const { source, docRef, projectId, flushDraft } = input\n const navigate = useNavigate()\n const [publishState, setPublishState] = useState<PublishState>('idle')\n // The split-button \"Publish & close\" intent, so a successful publish returns to the\n // list. Cleared on failure.\n const closeAfterPublishRef = useRef(false)\n\n const doPublish = useCallback(async () => {\n setPublishState('pending')\n try {\n // EC-595 A2: the FORCED FLUSH — publish validates the COMMITTED draft, so every\n // uncommitted field commits and lands first.\n await flushDraft()\n // EC-585: the hidden-clear (field values, object sub-fields, component props —\n // into the draft AND the snapshot, judged in the project's locale views) runs\n // INSIDE `documents.publish`, for this and every other publish path. A refusal\n // rejects here (`PublishRefusedError`), so the indicator never says \"Published\"\n // for a publish that did not land.\n await source.publishing?.publish(docRef)\n setPublishState('success')\n setTimeout(() => setPublishState('idle'), 1800)\n // \"Publish & close\": return to the list once the publish landed (never on a\n // failure). Rail-owned `listPath`, falling back to the project index (AD-19).\n if (closeAfterPublishRef.current) {\n closeAfterPublishRef.current = false\n void navigate({\n to: STUDIO_PROJECT_ROUTE,\n params: { projectId, _splat: source.listPath ?? studioPath.index() },\n })\n }\n } catch (error) {\n closeAfterPublishRef.current = false\n setPublishState('idle')\n toast.error(\n error instanceof Error\n ? translate('cms.editor.publishFailedReason', { error: error.message })\n : translate('cms.editor.publishFailed'),\n )\n }\n }, [flushDraft, source, docRef, navigate, projectId])\n\n // Schema errors (strict-target errors included, EC-589) disable the button, so they\n // never reach here.\n const handlePublish = useCallback(() => {\n void doPublish()\n }, [doPublish])\n\n const handlePublishAndClose = useCallback(() => {\n closeAfterPublishRef.current = true\n handlePublish()\n }, [handlePublish])\n\n return {\n publishState,\n handlePublish,\n handlePublishAndClose,\n }\n}\n"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { QueryKey } from '@tanstack/react-query';
|
|
2
2
|
import type { CmsDocument, DocumentRef, Perspective } from '@elytracms/core/cms-core';
|
|
3
|
-
import type { CollectionViewSource } from '
|
|
3
|
+
import type { CollectionViewSource } from '../../lib/collection-view/collection-view-source.js';
|
|
4
4
|
import type { DocumentVersionsView } from './cms-store.js';
|
|
5
5
|
import type { PublishedSnapshotState } from './versions-model.js';
|
|
6
6
|
/**
|