@elytracms/studio 0.0.92 → 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/asset-alt-migration.test.ts +127 -0
- package/convex/asset-replace.test.ts +168 -0
- package/convex/asset-revalidate.test.ts +6 -6
- package/convex/assets.ts +41 -5
- package/convex/auth.ts +5 -2
- package/convex/authority.ts +35 -11
- package/convex/cms.ts +25 -11
- package/convex/contract-parity.test.ts +25 -3
- package/convex/delivery.ts +12 -9
- package/convex/document-revalidate.test.ts +10 -12
- 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/orphan-hydration.test.ts +116 -0
- package/convex/rail-write-parity.test.ts +11 -8
- 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 +36 -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 +4 -3
- 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 +10 -8
- package/dist/components/ai/translate-document-dialog.js.map +1 -1
- package/dist/components/ai/translate-skipped.d.ts +19 -0
- package/dist/components/ai/translate-skipped.js +46 -0
- package/dist/components/ai/translate-skipped.js.map +1 -0
- 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 +20 -10
- 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-metadata.d.ts +15 -4
- package/dist/components/assets/asset-metadata.js +15 -2
- package/dist/components/assets/asset-metadata.js.map +1 -1
- package/dist/components/assets/asset-search.js +4 -1
- package/dist/components/assets/asset-search.js.map +1 -1
- package/dist/components/assets/asset-selection.d.ts +0 -8
- package/dist/components/assets/asset-selection.js +4 -23
- package/dist/components/assets/asset-selection.js.map +1 -1
- package/dist/components/assets/asset-thumb.js +6 -5
- package/dist/components/assets/asset-thumb.js.map +1 -1
- package/dist/components/assets/assets-collection-view-source.d.ts +5 -5
- package/dist/components/assets/assets-collection-view-source.js +9 -5
- 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 +16 -2
- package/dist/components/assets/assets-store.js +28 -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.d.ts +22 -0
- package/dist/components/assets/replace-file.js +59 -0
- package/dist/components/assets/replace-file.js.map +1 -0
- 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 +15 -27
- package/dist/components/builder/blocks-model.js +38 -44
- package/dist/components/builder/blocks-model.js.map +1 -1
- package/dist/components/builder/blocks-panel.d.ts +8 -4
- package/dist/components/builder/blocks-panel.js +10 -9
- package/dist/components/builder/blocks-panel.js.map +1 -1
- package/dist/components/builder/builder-store.d.ts +104 -15
- package/dist/components/builder/builder-store.js +157 -32
- package/dist/components/builder/builder-store.js.map +1 -1
- package/dist/components/builder/canvas-body.d.ts +30 -0
- package/dist/components/builder/canvas-body.js +38 -0
- package/dist/components/builder/canvas-body.js.map +1 -0
- package/dist/components/builder/canvas-breakpoints.js +1 -1
- package/dist/components/builder/canvas-breakpoints.js.map +1 -1
- package/dist/components/builder/canvas-chrome-css.js +7 -0
- package/dist/components/builder/canvas-chrome-css.js.map +1 -1
- package/dist/components/builder/canvas-dnd.d.ts +9 -9
- package/dist/components/builder/canvas-dnd.js +6 -6
- 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 +28 -20
- package/dist/components/builder/canvas-render-context.js +46 -47
- package/dist/components/builder/canvas-render-context.js.map +1 -1
- package/dist/components/builder/canvas-runs.d.ts +45 -0
- package/dist/components/builder/canvas-runs.js +51 -0
- package/dist/components/builder/canvas-runs.js.map +1 -0
- 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 +35 -8
- package/dist/components/builder/canvas.js.map +1 -1
- package/dist/components/builder/clipboard-actions.d.ts +16 -1
- package/dist/components/builder/clipboard-actions.js +48 -11
- 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 +4 -16
- package/dist/components/builder/composition-field-editor.js +32 -43
- package/dist/components/builder/composition-field-editor.js.map +1 -1
- package/dist/components/builder/delete-key.d.ts +5 -0
- package/dist/components/builder/delete-key.js +4 -0
- package/dist/components/builder/delete-key.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 +18 -26
- package/dist/components/builder/insert-context.js +47 -94
- 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 +4 -4
- package/dist/components/builder/page-tree.js +8 -8
- package/dist/components/builder/page-tree.js.map +1 -1
- package/dist/components/builder/placement-refusal.d.ts +18 -0
- package/dist/components/builder/placement-refusal.js +46 -0
- package/dist/components/builder/placement-refusal.js.map +1 -0
- package/dist/components/builder/property-inspector.js +8 -4
- package/dist/components/builder/property-inspector.js.map +1 -1
- package/dist/components/builder/reuse-context.js +68 -30
- package/dist/components/builder/reuse-context.js.map +1 -1
- package/dist/components/builder/reuse.d.ts +79 -1
- package/dist/components/builder/reuse.js +70 -3
- 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.d.ts +11 -0
- package/dist/components/builder/run-inspector.js +33 -0
- package/dist/components/builder/run-inspector.js.map +1 -0
- 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.d.ts +38 -1
- package/dist/components/cms/asset-field.js +184 -60
- package/dist/components/cms/asset-field.js.map +1 -1
- package/dist/components/cms/asset-picker-core.d.ts +16 -0
- package/dist/components/cms/asset-picker-core.js +20 -0
- package/dist/components/cms/asset-picker-core.js.map +1 -1
- package/dist/components/cms/asset-picker.js +14 -20
- package/dist/components/cms/asset-picker.js.map +1 -1
- package/dist/components/cms/bulk-actions.d.ts +4 -20
- package/dist/components/cms/bulk-actions.js +5 -26
- 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 +7 -6
- package/dist/components/cms/cms-store.js +23 -43
- 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 -15
- 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.d.ts +3 -1
- package/dist/components/cms/document-diff-view.js +17 -12
- 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-anchors.d.ts +7 -4
- package/dist/components/cms/field-anchors.js +9 -7
- package/dist/components/cms/field-anchors.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.d.ts +15 -1
- package/dist/components/cms/field-control-lib.js +23 -4
- 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.d.ts +21 -30
- package/dist/components/cms/gate-validation.js +11 -32
- 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 +6 -6
- package/dist/components/cms/object-array-field.js.map +1 -1
- package/dist/components/cms/prop-fields.d.ts +7 -2
- package/dist/components/cms/prop-fields.js +14 -7
- 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.d.ts +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/insert.d.ts +20 -9
- package/dist/components/cms/rich-text/insert.js +25 -17
- package/dist/components/cms/rich-text/insert.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 +2 -3
- package/dist/components/cms/use-publish-controller.js +8 -20
- 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 +11 -4
- 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 +5 -3
- 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 +4 -3
- package/dist/components/components/transfer-dialog.js.map +1 -1
- package/dist/components/components/transfer.d.ts +10 -10
- package/dist/components/components/transfer.js +13 -12
- package/dist/components/components/transfer.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/apply-content.js +56 -116
- package/dist/lib/ai/apply-content.js.map +1 -1
- package/dist/lib/ai/chat-context.d.ts +8 -0
- package/dist/lib/ai/chat-context.js +37 -6
- package/dist/lib/ai/chat-context.js.map +1 -1
- package/dist/lib/ai/chat-state.d.ts +6 -0
- package/dist/lib/ai/chat-state.js.map +1 -1
- package/dist/lib/ai/constraints.d.ts +20 -5
- package/dist/lib/ai/constraints.js +125 -33
- package/dist/lib/ai/constraints.js.map +1 -1
- package/dist/lib/ai/fake-provider.d.ts +16 -3
- package/dist/lib/ai/fake-provider.js +22 -6
- package/dist/lib/ai/fake-provider.js.map +1 -1
- package/dist/lib/ai/index.d.ts +3 -1
- package/dist/lib/ai/index.js +3 -1
- package/dist/lib/ai/index.js.map +1 -1
- package/dist/lib/ai/leash.d.ts +34 -0
- package/dist/lib/ai/leash.js +28 -0
- package/dist/lib/ai/leash.js.map +1 -0
- package/dist/lib/ai/node-handler.d.ts +1 -1
- package/dist/lib/ai/node-handler.js.map +1 -1
- package/dist/lib/ai/rich-text-input.d.ts +8 -8
- package/dist/lib/ai/rich-text-input.js +2 -1
- package/dist/lib/ai/rich-text-input.js.map +1 -1
- package/dist/lib/ai/rich-text-translate.d.ts +27 -4
- package/dist/lib/ai/rich-text-translate.js +28 -10
- package/dist/lib/ai/rich-text-translate.js.map +1 -1
- package/dist/lib/ai/shape.d.ts +13 -13
- package/dist/lib/ai/shape.js +69 -93
- package/dist/lib/ai/shape.js.map +1 -1
- package/dist/lib/ai/tools.d.ts +36 -41
- package/dist/lib/ai/tools.js +83 -87
- package/dist/lib/ai/tools.js.map +1 -1
- package/dist/lib/ai/translate-document.d.ts +32 -4
- package/dist/lib/ai/translate-document.js +145 -47
- package/dist/lib/ai/translate-document.js.map +1 -1
- package/dist/lib/ai/types.d.ts +34 -23
- package/dist/lib/ai/types.js.map +1 -1
- package/dist/lib/ai/update-fields.d.ts +15 -12
- package/dist/lib/ai/update-fields.js +112 -188
- package/dist/lib/ai/update-fields.js.map +1 -1
- package/dist/lib/ai/writer-form.d.ts +116 -0
- package/dist/lib/ai/writer-form.js +429 -0
- package/dist/lib/ai/writer-form.js.map +1 -0
- package/dist/lib/assets/upload.d.ts +31 -2
- package/dist/lib/assets/upload.js +61 -6
- package/dist/lib/assets/upload.js.map +1 -1
- package/dist/lib/clipboard/os-clipboard.d.ts +10 -2
- package/dist/lib/clipboard/os-clipboard.js +8 -4
- package/dist/lib/clipboard/os-clipboard.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.d.ts +18 -9
- package/dist/lib/clipboard/paste-target.js +10 -9
- package/dist/lib/clipboard/paste-target.js.map +1 -1
- package/dist/lib/cms/field-patch.d.ts +0 -10
- package/dist/lib/cms/field-patch.js +4 -30
- package/dist/lib/cms/field-patch.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 +9 -9
- 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 +14 -5
- package/dist/lib/collection-view/collection-view-source.js.map +1 -1
- package/dist/lib/data/collection-content.js +3 -2
- package/dist/lib/data/collection-content.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/dnd/drop-rules.d.ts +25 -31
- package/dist/lib/dnd/drop-rules.js +53 -59
- package/dist/lib/dnd/drop-rules.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 +44 -3
- package/dist/lib/i18n/messages.de.js.map +1 -1
- package/dist/lib/i18n/messages.en.d.ts +38 -2
- package/dist/lib/i18n/messages.en.js +51 -3
- package/dist/lib/i18n/messages.en.js.map +1 -1
- package/dist/lib/operations/project-runtime-config.d.ts +34 -6
- package/dist/lib/operations/project-runtime-config.js +50 -10
- package/dist/lib/operations/project-runtime-config.js.map +1 -1
- package/dist/lib/operations/publish-with-revision.d.ts +33 -1
- package/dist/lib/operations/publish-with-revision.js +32 -0
- package/dist/lib/operations/publish-with-revision.js.map +1 -1
- package/dist/lib/operations/runtime-validator.d.ts +19 -68
- package/dist/lib/operations/runtime-validator.js +12 -138
- package/dist/lib/operations/runtime-validator.js.map +1 -1
- package/dist/lib/operations/studio-operations.d.ts +17 -2
- package/dist/lib/operations/studio-operations.js +18 -11
- 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 +83 -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 +4 -2
- 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 +14 -11
- package/dist/lib/workspace/index.js +20 -24
- 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/cms/cms-composition-validators.d.ts +0 -16
- package/dist/components/cms/cms-composition-validators.js +0 -133
- package/dist/components/cms/cms-composition-validators.js.map +0 -1
- package/dist/components/cms/publish-clean.d.ts +0 -28
- package/dist/components/cms/publish-clean.js +0 -38
- package/dist/components/cms/publish-clean.js.map +0 -1
- 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
- package/dist/lib/assets/asset-placements.d.ts +0 -28
- package/dist/lib/assets/asset-placements.js +0 -83
- package/dist/lib/assets/asset-placements.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"link-control.js","sourceRoot":"","sources":["../../../../src/components/cms/rich-text/link-control.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useMemo, useState } from 'react'\nimport { FileText, Link2, Trash2 } from 'lucide-react'\nimport type { Editor } from '@tiptap/react'\nimport { referenceNode } from '@elytracms/core/cms-core'\nimport type { DocumentRef } from '@elytracms/core/cms-core'\nimport {\n Button,\n Input,\n Popover,\n PopoverContent,\n PopoverTrigger,\n Row,\n Separator,\n Stack,\n Text,\n} from '@elytracms/studio/ui'\nimport { cn } from '@elytracms/studio/lib/utils'\nimport { translate } from '@elytracms/studio/lib/i18n'\n\n/** A pickable internal-link destination: a document plus its display labels. */\nexport interface LinkTarget {\n ref: DocumentRef\n label: string\n collectionLabel: string\n}\n\ninterface LinkControlProps {\n editor: Editor\n /** Documents linkable across the workspace (empty ⇒ external-only). */\n targets: LinkTarget[]\n}\n\nconst MAX_RESULTS = 40\n\n/**\n * The rich-text link control (EC-314): set/clear a link on the selection, either as\n * an external URL (a literal `href`) or an internal document — which writes a\n * structured `attrs.ref` (`referenceNode`, EC-290) so the link stays indexed like a\n * relation and resolves to a live path in delivery. Applying an empty URL clears the\n * link, so the one control both sets and removes.\n */\nexport function LinkControl({ editor, targets }: LinkControlProps) {\n const [open, setOpen] = useState(false)\n const [href, setHref] = useState('')\n const [query, setQuery] = useState('')\n // Null-safe like the toolbar's state selector: a destroyed editor (concurrent\n // teardown / lazy-boundary unmount) still renders this control for a frame.\n const isLink = !editor.isDestroyed && editor.isActive('link')\n\n const results = useMemo(() => {\n const q = query.trim().toLowerCase()\n const matched = q\n ? targets.filter((t) => t.label.toLowerCase().includes(q) || t.collectionLabel.toLowerCase().includes(q))\n : targets\n return matched.slice(0, MAX_RESULTS)\n }, [targets, query])\n\n function onOpenChange(next: boolean) {\n if (next) {\n const current = editor.getAttributes('link').href\n setHref(typeof current === 'string' ? current : '')\n setQuery('')\n }\n setOpen(next)\n }\n\n function applyExternal() {\n const url = href.trim()\n const chain = editor.chain().focus().extendMarkRange('link')\n if (url) chain.setLink({ href: url }).run()\n else chain.unsetLink().run()\n setOpen(false)\n }\n\n function applyDocument(target: LinkTarget) {\n editor\n .chain()\n .focus()\n .extendMarkRange('link')\n .setMark('link', { href: null, ref: referenceNode(target.ref) })\n .run()\n setOpen(false)\n }\n\n function remove() {\n editor.chain().focus().extendMarkRange('link').unsetLink().run()\n setOpen(false)\n }\n\n return (\n <Popover open={open} onOpenChange={onOpenChange}>\n <PopoverTrigger asChild>\n <Button\n type=\"button\"\n variant={isLink ? 'secondary' : 'ghost'}\n size=\"icon-sm\"\n aria-label={translate('richText.link.add')}\n aria-pressed={isLink}\n title={translate('richText.link.add')}\n onMouseDown={(e) => e.preventDefault()}\n >\n <Link2 className=\"size-3.5\" />\n </Button>\n </PopoverTrigger>\n <PopoverContent align=\"start\" className=\"w-80\">\n <Stack gap=\"2\">\n <Text as=\"span\" size=\"xs\" tone=\"muted\" className=\"uppercase tracking-wide\">\n {translate('richText.link.external')}\n </Text>\n <Row gap=\"2\" align=\"center\">\n <Input\n autoFocus\n value={href}\n placeholder={translate('richText.link.externalPlaceholder')}\n onChange={(e) => setHref(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === 'Enter') {\n e.preventDefault()\n applyExternal()\n }\n }}\n />\n <Button type=\"button\" size=\"sm\" onClick={applyExternal}>\n {translate('richText.link.apply')}\n </Button>\n </Row>\n\n {targets.length > 0 ? (\n <>\n <Separator className=\"my-0.5\" />\n <Text as=\"span\" size=\"xs\" tone=\"muted\" className=\"uppercase tracking-wide\">\n {translate('richText.link.document')}\n </Text>\n <Input\n value={query}\n placeholder={translate('richText.link.documentSearch')}\n onChange={(e) => setQuery(e.target.value)}\n />\n <Stack gap=\"none\" className=\"max-h-52 overflow-y-auto\">\n {results.length === 0 ? (\n <Text as=\"span\" size=\"xs\" tone=\"muted\" className=\"px-1 py-2\">\n {translate('richText.link.documentEmpty')}\n </Text>\n ) : (\n results.map((target) => (\n <button\n key={`${target.ref.collection}:${target.ref.id}`}\n type=\"button\"\n onMouseDown={(e) => e.preventDefault()}\n onClick={() => applyDocument(target)}\n className={cn(\n 'flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left text-sm',\n 'hover:bg-muted focus-visible:bg-muted outline-none',\n )}\n >\n <FileText className=\"size-3.5 shrink-0 text-muted-foreground\" />\n <span className=\"min-w-0 flex-1 truncate\">{target.label}</span>\n <span className=\"shrink-0 text-xs text-muted-foreground\">{target.collectionLabel}</span>\n </button>\n ))\n )}\n </Stack>\n </>\n ) : null}\n\n {isLink ? (\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" className=\"self-start\" onClick={remove}>\n <Trash2 className=\"size-3.5\" /> {translate('richText.link.remove')}\n </Button>\n ) : null}\n </Stack>\n </PopoverContent>\n </Popover>\n )\n}\n"]}
|
|
1
|
+
{"version":3,"file":"link-control.js","sourceRoot":"","sources":["../../../../src/components/cms/rich-text/link-control.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useMemo, useState } from 'react'\nimport { FileText, Link2, Trash2 } from 'lucide-react'\nimport type { Editor } from '@tiptap/react'\nimport { referenceNode } from '@elytracms/core/cms-core'\nimport type { DocumentRef } from '@elytracms/core/cms-core'\nimport {\n Button,\n Input,\n Popover,\n PopoverContent,\n PopoverTrigger,\n Row,\n Separator,\n Stack,\n Text,\n} from '@elytracms/studio/ui'\nimport { cn } from '../../../lib/utils'\nimport { translate } from '../../../lib/i18n'\n\n/** A pickable internal-link destination: a document plus its display labels. */\nexport interface LinkTarget {\n ref: DocumentRef\n label: string\n collectionLabel: string\n}\n\ninterface LinkControlProps {\n editor: Editor\n /** Documents linkable across the workspace (empty ⇒ external-only). */\n targets: LinkTarget[]\n}\n\nconst MAX_RESULTS = 40\n\n/**\n * The rich-text link control (EC-314): set/clear a link on the selection, either as\n * an external URL (a literal `href`) or an internal document — which writes a\n * structured `attrs.ref` (`referenceNode`, EC-290) so the link stays indexed like a\n * relation and resolves to a live path in delivery. Applying an empty URL clears the\n * link, so the one control both sets and removes.\n */\nexport function LinkControl({ editor, targets }: LinkControlProps) {\n const [open, setOpen] = useState(false)\n const [href, setHref] = useState('')\n const [query, setQuery] = useState('')\n // Null-safe like the toolbar's state selector: a destroyed editor (concurrent\n // teardown / lazy-boundary unmount) still renders this control for a frame.\n const isLink = !editor.isDestroyed && editor.isActive('link')\n\n const results = useMemo(() => {\n const q = query.trim().toLowerCase()\n const matched = q\n ? targets.filter((t) => t.label.toLowerCase().includes(q) || t.collectionLabel.toLowerCase().includes(q))\n : targets\n return matched.slice(0, MAX_RESULTS)\n }, [targets, query])\n\n function onOpenChange(next: boolean) {\n if (next) {\n const current = editor.getAttributes('link').href\n setHref(typeof current === 'string' ? current : '')\n setQuery('')\n }\n setOpen(next)\n }\n\n function applyExternal() {\n const url = href.trim()\n const chain = editor.chain().focus().extendMarkRange('link')\n if (url) chain.setLink({ href: url }).run()\n else chain.unsetLink().run()\n setOpen(false)\n }\n\n function applyDocument(target: LinkTarget) {\n editor\n .chain()\n .focus()\n .extendMarkRange('link')\n .setMark('link', { href: null, ref: referenceNode(target.ref) })\n .run()\n setOpen(false)\n }\n\n function remove() {\n editor.chain().focus().extendMarkRange('link').unsetLink().run()\n setOpen(false)\n }\n\n return (\n <Popover open={open} onOpenChange={onOpenChange}>\n <PopoverTrigger asChild>\n <Button\n type=\"button\"\n variant={isLink ? 'secondary' : 'ghost'}\n size=\"icon-sm\"\n aria-label={translate('richText.link.add')}\n aria-pressed={isLink}\n title={translate('richText.link.add')}\n onMouseDown={(e) => e.preventDefault()}\n >\n <Link2 className=\"size-3.5\" />\n </Button>\n </PopoverTrigger>\n <PopoverContent align=\"start\" className=\"w-80\">\n <Stack gap=\"2\">\n <Text as=\"span\" size=\"xs\" tone=\"muted\" className=\"uppercase tracking-wide\">\n {translate('richText.link.external')}\n </Text>\n <Row gap=\"2\" align=\"center\">\n <Input\n autoFocus\n value={href}\n placeholder={translate('richText.link.externalPlaceholder')}\n onChange={(e) => setHref(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === 'Enter') {\n e.preventDefault()\n applyExternal()\n }\n }}\n />\n <Button type=\"button\" size=\"sm\" onClick={applyExternal}>\n {translate('richText.link.apply')}\n </Button>\n </Row>\n\n {targets.length > 0 ? (\n <>\n <Separator className=\"my-0.5\" />\n <Text as=\"span\" size=\"xs\" tone=\"muted\" className=\"uppercase tracking-wide\">\n {translate('richText.link.document')}\n </Text>\n <Input\n value={query}\n placeholder={translate('richText.link.documentSearch')}\n onChange={(e) => setQuery(e.target.value)}\n />\n <Stack gap=\"none\" className=\"max-h-52 overflow-y-auto\">\n {results.length === 0 ? (\n <Text as=\"span\" size=\"xs\" tone=\"muted\" className=\"px-1 py-2\">\n {translate('richText.link.documentEmpty')}\n </Text>\n ) : (\n results.map((target) => (\n <button\n key={`${target.ref.collection}:${target.ref.id}`}\n type=\"button\"\n onMouseDown={(e) => e.preventDefault()}\n onClick={() => applyDocument(target)}\n className={cn(\n 'flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left text-sm',\n 'hover:bg-muted focus-visible:bg-muted outline-none',\n )}\n >\n <FileText className=\"size-3.5 shrink-0 text-muted-foreground\" />\n <span className=\"min-w-0 flex-1 truncate\">{target.label}</span>\n <span className=\"shrink-0 text-xs text-muted-foreground\">{target.collectionLabel}</span>\n </button>\n ))\n )}\n </Stack>\n </>\n ) : null}\n\n {isLink ? (\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" className=\"self-start\" onClick={remove}>\n <Trash2 className=\"size-3.5\" /> {translate('richText.link.remove')}\n </Button>\n ) : null}\n </Stack>\n </PopoverContent>\n </Popover>\n )\n}\n"]}
|
|
@@ -3,9 +3,9 @@ import { forwardRef, useImperativeHandle, useState } from 'react';
|
|
|
3
3
|
import { Extension, ReactRenderer } from '@tiptap/react';
|
|
4
4
|
import Suggestion from '@tiptap/suggestion';
|
|
5
5
|
import { Blocks, Heading1, Heading2, Heading3, ImagePlus, Pilcrow } from 'lucide-react';
|
|
6
|
-
import { translate } from '
|
|
7
|
-
import { componentDisplayName } from '
|
|
8
|
-
import { cn } from '
|
|
6
|
+
import { translate } from '../../../lib/i18n/index.js';
|
|
7
|
+
import { componentDisplayName } from '../../../lib/persona/labels.js';
|
|
8
|
+
import { cn } from '../../../lib/utils.js';
|
|
9
9
|
import { BLOCK_COMMANDS } from './block-commands.js';
|
|
10
10
|
/** The command list, gated by which media actions + components the editor provided. */
|
|
11
11
|
export function buildSlashItems(options) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"slash-command.js","sourceRoot":"","sources":["../../../../src/components/cms/rich-text/slash-command.tsx"],"names":[],"mappings":"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","sourcesContent":["import { forwardRef, useImperativeHandle, useState } from 'react'\nimport type { ComponentType } from 'react'\nimport { Extension, ReactRenderer } from '@tiptap/react'\nimport type { Editor, Range } from '@tiptap/react'\nimport Suggestion from '@tiptap/suggestion'\nimport type { SuggestionKeyDownProps, SuggestionOptions, SuggestionProps } from '@tiptap/suggestion'\nimport { Blocks, Heading1, Heading2, Heading3, ImagePlus, Pilcrow } from 'lucide-react'\nimport type { LucideIcon } from 'lucide-react'\nimport type { ComponentManifest } from '@elytracms/core/component-registry'\nimport { translate } from '@elytracms/studio/lib/i18n'\nimport { componentDisplayName } from '@elytracms/studio/lib/persona/labels'\nimport { cn } from '@elytracms/studio/lib/utils'\nimport { BLOCK_COMMANDS } from './block-commands'\n\n/**\n * Notion-style slash menu (EC-314): typing `/` at the caret opens a filterable\n * command palette that inserts block-level content — headings, lists, quote, code,\n * divider — and, when available, an image or a registry component.\n *\n * 0.0.41: the embed path shows the permitted components **directly** as their own\n * items under a \"Components\" group (allow-gated exactly like the picker), rather than\n * a single \"Embed component\" item that opened a separate picker dialog — the\n * product-owner feedback that the extra step was unintuitive. Selecting a component\n * inserts it with manifest defaults and selects it, so the sidebar inspector opens.\n * Built on TipTap's `Suggestion` utility; the menu is a React overlay at the caret.\n */\n\n/** How the slash menu inserts a chosen component (owned by the editor). */\nexport interface SlashEmbedOptions {\n /** The permitted component manifests (already `allow`-gated by the field). */\n components: ComponentManifest[]\n /** Insert `manifest` at `range` (deletes the `/query`, inserts + selects). */\n insert: (editor: Editor, range: Range, manifest: ComponentManifest) => void\n}\n\nexport interface SlashCommandOptions {\n /** Open the asset picker (omitted ⇒ no `/image` item). */\n onImage?: () => void\n /** The embeddable components + inserter (omitted/empty ⇒ no component items). */\n embed?: SlashEmbedOptions\n}\n\nexport interface SlashItem {\n key: string\n title: string\n keywords: string\n icon: LucideIcon\n /** Optional group label — consecutive same-group items share a header. */\n group?: string\n run: (editor: Editor, range: Range) => void\n}\n\n/** The command list, gated by which media actions + components the editor provided. */\nexport function buildSlashItems(options: SlashCommandOptions): SlashItem[] {\n const block = (run: (chain: ReturnType<Editor['chain']>) => ReturnType<Editor['chain']>) =>\n (editor: Editor, range: Range) => run(editor.chain().focus().deleteRange(range)).run()\n\n const items: SlashItem[] = [\n { key: 'text', title: translate('richText.slash.text'), keywords: 'paragraph text plain', icon: Pilcrow, run: block((c) => c.setParagraph()) },\n { key: 'h1', title: translate('richText.heading', { level: 1 }), keywords: 'heading title h1', icon: Heading1, run: block((c) => c.toggleHeading({ level: 1 })) },\n { key: 'h2', title: translate('richText.heading', { level: 2 }), keywords: 'heading subtitle h2', icon: Heading2, run: block((c) => c.toggleHeading({ level: 2 })) },\n { key: 'h3', title: translate('richText.heading', { level: 3 }), keywords: 'heading h3', icon: Heading3, run: block((c) => c.toggleHeading({ level: 3 })) },\n // The shared table (EC-328) — bulletList/orderedList/blockquote/codeBlock/divider,\n // the SAME descriptors `RichTextToolbar` derives its buttons from.\n ...BLOCK_COMMANDS.map((cmd) => ({\n key: cmd.key,\n title: translate(cmd.labelKey),\n keywords: cmd.keywords,\n icon: cmd.icon,\n run: block(cmd.chain),\n })),\n ]\n if (options.onImage) {\n items.push({\n key: 'image',\n title: translate('richText.image.add'),\n keywords: 'image picture photo media',\n icon: ImagePlus,\n run: (editor, range) => {\n editor.chain().focus().deleteRange(range).run()\n options.onImage?.()\n },\n })\n }\n const embed = options.embed\n if (embed) {\n const group = translate('richText.slash.componentsGroup')\n for (const manifest of embed.components) {\n const title = componentDisplayName(manifest.id, manifest)\n items.push({\n key: `embed:${manifest.id}`,\n title,\n keywords: `${title} ${manifest.id} component embed block`.toLowerCase(),\n icon: Blocks,\n group,\n run: (editor, range) => embed.insert(editor, range, manifest),\n })\n }\n }\n return items\n}\n\n/** Filter items by a typed query (title or keyword substring) — empty query passes all. */\nexport function filterSlashItems(items: SlashItem[], query: string): SlashItem[] {\n const q = query.trim().toLowerCase()\n if (!q) return items\n return items.filter((item) => item.title.toLowerCase().includes(q) || item.keywords.includes(q))\n}\n\n/** Imperative handle the suggestion render loop calls for keyboard navigation. */\ninterface SlashMenuHandle {\n onKeyDown: (props: SuggestionKeyDownProps) => boolean\n}\n\nconst SlashMenu = forwardRef<SlashMenuHandle, SuggestionProps<SlashItem, SlashItem>>((props, ref) => {\n const [selected, setSelected] = useState(0)\n // Reset the highlight DURING RENDER when `items` changes, not in a post-render\n // Effect (EC-328 fix): `@tiptap/suggestion`'s `createSuggestionView.update()`\n // dispatches a NEW `items` array on nearly every keystroke, so an\n // effect-based reset landed one render late — a frame where `selected` was\n // briefly stale/out-of-range against the freshly filtered list. Comparing\n // against the previous reference here (React's documented \"adjust state\n // during render\" idiom) resets it in the SAME render that receives the new\n // `items`, before anything paints.\n const [prevItems, setPrevItems] = useState(props.items)\n if (props.items !== prevItems) {\n setPrevItems(props.items)\n setSelected(0)\n }\n\n useImperativeHandle(ref, () => ({\n onKeyDown: ({ event }) => {\n const count = props.items.length\n if (count === 0) return false\n if (event.key === 'ArrowUp') {\n setSelected((s) => (s + count - 1) % count)\n return true\n }\n if (event.key === 'ArrowDown') {\n setSelected((s) => (s + 1) % count)\n return true\n }\n if (event.key === 'Enter') {\n const item = props.items[selected]\n if (item) props.command(item)\n return true\n }\n return false\n },\n }))\n\n if (props.items.length === 0) {\n return (\n <div className=\"ec-rte-slash\">\n <span className=\"px-2 py-1.5 text-xs text-muted-foreground\">\n {translate('richText.slash.empty')}\n </span>\n </div>\n )\n }\n\n return (\n <div className=\"ec-rte-slash\" role=\"menu\">\n {props.items.map((item, i) => {\n const Icon = item.icon\n // A group header precedes the first item whose group differs from its\n // predecessor (the block-formatting items are ungrouped; components share one).\n const showHeader = item.group != null && item.group !== props.items[i - 1]?.group\n return (\n <div key={item.key}>\n {showHeader ? (\n <span className=\"block px-2 pt-2 pb-1 text-[0.6875rem] font-medium uppercase tracking-wide text-muted-foreground\">\n {item.group}\n </span>\n ) : null}\n <button\n type=\"button\"\n role=\"menuitem\"\n onMouseEnter={() => setSelected(i)}\n onMouseDown={(e) => {\n e.preventDefault()\n props.command(item)\n }}\n className={cn(\n 'flex w-full items-center gap-2.5 rounded-md px-2 py-1.5 text-left text-sm outline-none',\n i === selected ? 'bg-muted' : 'hover:bg-muted/60',\n )}\n >\n <Icon className=\"size-4 shrink-0 text-muted-foreground\" />\n <span className=\"truncate\">{item.title}</span>\n </button>\n </div>\n )\n })}\n </div>\n )\n})\nSlashMenu.displayName = 'SlashMenu'\n\n/** Position a floating element under the caret, flipping above near the viewport edge. */\nfunction place(el: HTMLElement, rect: DOMRect | null | undefined): void {\n if (!rect) return\n const margin = 8\n const height = el.offsetHeight || 280\n const below = rect.bottom + 4\n const flip = below + height > window.innerHeight - margin && rect.top - height > margin\n el.style.position = 'fixed'\n el.style.left = `${Math.min(rect.left, window.innerWidth - 260)}px`\n el.style.top = flip ? `${rect.top - height - 4}px` : `${below}px`\n el.style.zIndex = '60'\n}\n\n/** The suggestion config (everything but the `editor`, which the extension injects). */\nfunction slashSuggestion(options: SlashCommandOptions): Omit<SuggestionOptions<SlashItem, SlashItem>, 'editor'> {\n return {\n char: '/',\n startOfLine: false,\n items: ({ query }) => filterSlashItems(buildSlashItems(options), query),\n command: ({ editor, range, props }) => props.run(editor, range),\n render: () => {\n let renderer: ReactRenderer<SlashMenuHandle, SuggestionProps<SlashItem, SlashItem>> | null = null\n let wrapper: HTMLDivElement | null = null\n\n return {\n onStart: (props) => {\n renderer = new ReactRenderer(SlashMenu as ComponentType<SuggestionProps<SlashItem, SlashItem>>, {\n props,\n editor: props.editor,\n })\n wrapper = document.createElement('div')\n wrapper.appendChild(renderer.element)\n document.body.appendChild(wrapper)\n place(wrapper, props.clientRect?.())\n },\n onUpdate: (props) => {\n renderer?.updateProps(props)\n if (wrapper) place(wrapper, props.clientRect?.())\n },\n onKeyDown: (props) => {\n if (props.event.key === 'Escape') {\n wrapper?.remove()\n wrapper = null\n return true\n }\n return renderer?.ref?.onKeyDown(props) ?? false\n },\n onExit: () => {\n wrapper?.remove()\n wrapper = null\n renderer?.destroy()\n renderer = null\n },\n }\n },\n }\n}\n\n/** Build the slash-command extension bound to the editor's shared media pickers. */\nexport function createSlashCommand(options: SlashCommandOptions) {\n return Extension.create<SlashCommandOptions>({\n name: 'slashCommand',\n addOptions: () => options,\n addProseMirrorPlugins() {\n return [Suggestion({ editor: this.editor, ...slashSuggestion(this.options) })]\n },\n })\n}\n"]}
|
|
1
|
+
{"version":3,"file":"slash-command.js","sourceRoot":"","sources":["../../../../src/components/cms/rich-text/slash-command.tsx"],"names":[],"mappings":"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","sourcesContent":["import { forwardRef, useImperativeHandle, useState } from 'react'\nimport type { ComponentType } from 'react'\nimport { Extension, ReactRenderer } from '@tiptap/react'\nimport type { Editor, Range } from '@tiptap/react'\nimport Suggestion from '@tiptap/suggestion'\nimport type { SuggestionKeyDownProps, SuggestionOptions, SuggestionProps } from '@tiptap/suggestion'\nimport { Blocks, Heading1, Heading2, Heading3, ImagePlus, Pilcrow } from 'lucide-react'\nimport type { LucideIcon } from 'lucide-react'\nimport type { ComponentManifest } from '@elytracms/core/component-registry'\nimport { translate } from '../../../lib/i18n'\nimport { componentDisplayName } from '../../../lib/persona/labels'\nimport { cn } from '../../../lib/utils'\nimport { BLOCK_COMMANDS } from './block-commands'\n\n/**\n * Notion-style slash menu (EC-314): typing `/` at the caret opens a filterable\n * command palette that inserts block-level content — headings, lists, quote, code,\n * divider — and, when available, an image or a registry component.\n *\n * 0.0.41: the embed path shows the permitted components **directly** as their own\n * items under a \"Components\" group (allow-gated exactly like the picker), rather than\n * a single \"Embed component\" item that opened a separate picker dialog — the\n * product-owner feedback that the extra step was unintuitive. Selecting a component\n * inserts it with manifest defaults and selects it, so the sidebar inspector opens.\n * Built on TipTap's `Suggestion` utility; the menu is a React overlay at the caret.\n */\n\n/** How the slash menu inserts a chosen component (owned by the editor). */\nexport interface SlashEmbedOptions {\n /** The permitted component manifests (already `allow`-gated by the field). */\n components: ComponentManifest[]\n /** Insert `manifest` at `range` (deletes the `/query`, inserts + selects). */\n insert: (editor: Editor, range: Range, manifest: ComponentManifest) => void\n}\n\nexport interface SlashCommandOptions {\n /** Open the asset picker (omitted ⇒ no `/image` item). */\n onImage?: () => void\n /** The embeddable components + inserter (omitted/empty ⇒ no component items). */\n embed?: SlashEmbedOptions\n}\n\nexport interface SlashItem {\n key: string\n title: string\n keywords: string\n icon: LucideIcon\n /** Optional group label — consecutive same-group items share a header. */\n group?: string\n run: (editor: Editor, range: Range) => void\n}\n\n/** The command list, gated by which media actions + components the editor provided. */\nexport function buildSlashItems(options: SlashCommandOptions): SlashItem[] {\n const block = (run: (chain: ReturnType<Editor['chain']>) => ReturnType<Editor['chain']>) =>\n (editor: Editor, range: Range) => run(editor.chain().focus().deleteRange(range)).run()\n\n const items: SlashItem[] = [\n { key: 'text', title: translate('richText.slash.text'), keywords: 'paragraph text plain', icon: Pilcrow, run: block((c) => c.setParagraph()) },\n { key: 'h1', title: translate('richText.heading', { level: 1 }), keywords: 'heading title h1', icon: Heading1, run: block((c) => c.toggleHeading({ level: 1 })) },\n { key: 'h2', title: translate('richText.heading', { level: 2 }), keywords: 'heading subtitle h2', icon: Heading2, run: block((c) => c.toggleHeading({ level: 2 })) },\n { key: 'h3', title: translate('richText.heading', { level: 3 }), keywords: 'heading h3', icon: Heading3, run: block((c) => c.toggleHeading({ level: 3 })) },\n // The shared table (EC-328) — bulletList/orderedList/blockquote/codeBlock/divider,\n // the SAME descriptors `RichTextToolbar` derives its buttons from.\n ...BLOCK_COMMANDS.map((cmd) => ({\n key: cmd.key,\n title: translate(cmd.labelKey),\n keywords: cmd.keywords,\n icon: cmd.icon,\n run: block(cmd.chain),\n })),\n ]\n if (options.onImage) {\n items.push({\n key: 'image',\n title: translate('richText.image.add'),\n keywords: 'image picture photo media',\n icon: ImagePlus,\n run: (editor, range) => {\n editor.chain().focus().deleteRange(range).run()\n options.onImage?.()\n },\n })\n }\n const embed = options.embed\n if (embed) {\n const group = translate('richText.slash.componentsGroup')\n for (const manifest of embed.components) {\n const title = componentDisplayName(manifest.id, manifest)\n items.push({\n key: `embed:${manifest.id}`,\n title,\n keywords: `${title} ${manifest.id} component embed block`.toLowerCase(),\n icon: Blocks,\n group,\n run: (editor, range) => embed.insert(editor, range, manifest),\n })\n }\n }\n return items\n}\n\n/** Filter items by a typed query (title or keyword substring) — empty query passes all. */\nexport function filterSlashItems(items: SlashItem[], query: string): SlashItem[] {\n const q = query.trim().toLowerCase()\n if (!q) return items\n return items.filter((item) => item.title.toLowerCase().includes(q) || item.keywords.includes(q))\n}\n\n/** Imperative handle the suggestion render loop calls for keyboard navigation. */\ninterface SlashMenuHandle {\n onKeyDown: (props: SuggestionKeyDownProps) => boolean\n}\n\nconst SlashMenu = forwardRef<SlashMenuHandle, SuggestionProps<SlashItem, SlashItem>>((props, ref) => {\n const [selected, setSelected] = useState(0)\n // Reset the highlight DURING RENDER when `items` changes, not in a post-render\n // Effect (EC-328 fix): `@tiptap/suggestion`'s `createSuggestionView.update()`\n // dispatches a NEW `items` array on nearly every keystroke, so an\n // effect-based reset landed one render late — a frame where `selected` was\n // briefly stale/out-of-range against the freshly filtered list. Comparing\n // against the previous reference here (React's documented \"adjust state\n // during render\" idiom) resets it in the SAME render that receives the new\n // `items`, before anything paints.\n const [prevItems, setPrevItems] = useState(props.items)\n if (props.items !== prevItems) {\n setPrevItems(props.items)\n setSelected(0)\n }\n\n useImperativeHandle(ref, () => ({\n onKeyDown: ({ event }) => {\n const count = props.items.length\n if (count === 0) return false\n if (event.key === 'ArrowUp') {\n setSelected((s) => (s + count - 1) % count)\n return true\n }\n if (event.key === 'ArrowDown') {\n setSelected((s) => (s + 1) % count)\n return true\n }\n if (event.key === 'Enter') {\n const item = props.items[selected]\n if (item) props.command(item)\n return true\n }\n return false\n },\n }))\n\n if (props.items.length === 0) {\n return (\n <div className=\"ec-rte-slash\">\n <span className=\"px-2 py-1.5 text-xs text-muted-foreground\">\n {translate('richText.slash.empty')}\n </span>\n </div>\n )\n }\n\n return (\n <div className=\"ec-rte-slash\" role=\"menu\">\n {props.items.map((item, i) => {\n const Icon = item.icon\n // A group header precedes the first item whose group differs from its\n // predecessor (the block-formatting items are ungrouped; components share one).\n const showHeader = item.group != null && item.group !== props.items[i - 1]?.group\n return (\n <div key={item.key}>\n {showHeader ? (\n <span className=\"block px-2 pt-2 pb-1 text-[0.6875rem] font-medium uppercase tracking-wide text-muted-foreground\">\n {item.group}\n </span>\n ) : null}\n <button\n type=\"button\"\n role=\"menuitem\"\n onMouseEnter={() => setSelected(i)}\n onMouseDown={(e) => {\n e.preventDefault()\n props.command(item)\n }}\n className={cn(\n 'flex w-full items-center gap-2.5 rounded-md px-2 py-1.5 text-left text-sm outline-none',\n i === selected ? 'bg-muted' : 'hover:bg-muted/60',\n )}\n >\n <Icon className=\"size-4 shrink-0 text-muted-foreground\" />\n <span className=\"truncate\">{item.title}</span>\n </button>\n </div>\n )\n })}\n </div>\n )\n})\nSlashMenu.displayName = 'SlashMenu'\n\n/** Position a floating element under the caret, flipping above near the viewport edge. */\nfunction place(el: HTMLElement, rect: DOMRect | null | undefined): void {\n if (!rect) return\n const margin = 8\n const height = el.offsetHeight || 280\n const below = rect.bottom + 4\n const flip = below + height > window.innerHeight - margin && rect.top - height > margin\n el.style.position = 'fixed'\n el.style.left = `${Math.min(rect.left, window.innerWidth - 260)}px`\n el.style.top = flip ? `${rect.top - height - 4}px` : `${below}px`\n el.style.zIndex = '60'\n}\n\n/** The suggestion config (everything but the `editor`, which the extension injects). */\nfunction slashSuggestion(options: SlashCommandOptions): Omit<SuggestionOptions<SlashItem, SlashItem>, 'editor'> {\n return {\n char: '/',\n startOfLine: false,\n items: ({ query }) => filterSlashItems(buildSlashItems(options), query),\n command: ({ editor, range, props }) => props.run(editor, range),\n render: () => {\n let renderer: ReactRenderer<SlashMenuHandle, SuggestionProps<SlashItem, SlashItem>> | null = null\n let wrapper: HTMLDivElement | null = null\n\n return {\n onStart: (props) => {\n renderer = new ReactRenderer(SlashMenu as ComponentType<SuggestionProps<SlashItem, SlashItem>>, {\n props,\n editor: props.editor,\n })\n wrapper = document.createElement('div')\n wrapper.appendChild(renderer.element)\n document.body.appendChild(wrapper)\n place(wrapper, props.clientRect?.())\n },\n onUpdate: (props) => {\n renderer?.updateProps(props)\n if (wrapper) place(wrapper, props.clientRect?.())\n },\n onKeyDown: (props) => {\n if (props.event.key === 'Escape') {\n wrapper?.remove()\n wrapper = null\n return true\n }\n return renderer?.ref?.onKeyDown(props) ?? false\n },\n onExit: () => {\n wrapper?.remove()\n wrapper = null\n renderer?.destroy()\n renderer = null\n },\n }\n },\n }\n}\n\n/** Build the slash-command extension bound to the editor's shared media pickers. */\nexport function createSlashCommand(options: SlashCommandOptions) {\n return Extension.create<SlashCommandOptions>({\n name: 'slashCommand',\n addOptions: () => options,\n addProseMirrorPlugins() {\n return [Suggestion({ editor: this.editor, ...slashSuggestion(this.options) })]\n },\n })\n}\n"]}
|
|
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
2
2
|
import { useEditorState } from '@tiptap/react';
|
|
3
3
|
import { Blocks, Bold, ChevronDown, Code, ImagePlus, Italic, Maximize2, Minimize2, Redo2, Strikethrough, Underline, Undo2, } from 'lucide-react';
|
|
4
4
|
import { Button, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, Toolbar, ToolbarGroup, ToolbarSeparator, } from '@elytracms/studio/ui';
|
|
5
|
-
import { translate } from '
|
|
5
|
+
import { translate } from '../../../lib/i18n/index.js';
|
|
6
6
|
import { ToolbarButton } from './toolbar-button.js';
|
|
7
7
|
import { LinkControl } from './link-control.js';
|
|
8
8
|
import { toolbarStateFromEditor } from './toolbar-state.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"toolbar.js","sourceRoot":"","sources":["../../../../src/components/cms/rich-text/toolbar.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useEditorState } from '@tiptap/react'\nimport type { Editor } from '@tiptap/react'\nimport {\n Blocks,\n Bold,\n ChevronDown,\n Code,\n ImagePlus,\n Italic,\n Maximize2,\n Minimize2,\n Redo2,\n Strikethrough,\n Underline,\n Undo2,\n} from 'lucide-react'\nimport {\n Button,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n Toolbar,\n ToolbarGroup,\n ToolbarSeparator,\n} from '@elytracms/studio/ui'\nimport { translate } from '@elytracms/studio/lib/i18n'\nimport { ToolbarButton } from './toolbar-button'\nimport { LinkControl } from './link-control'\nimport type { LinkTarget } from './link-control'\nimport { toolbarStateFromEditor } from './toolbar-state'\nimport { BLOCK_COMMANDS, isBlockCommandActive } from './block-commands'\nimport { BLOCK_STYLES, BlockStyleGlyph, blockStyleLabel } from './block-style'\nimport { FontSizeControl } from './font-size-control'\n\n/**\n * The toolbar container classes (EC-391). The load-bearing part is the height\n * contract: `h-auto min-h-10` (NOT a fixed `h-10`/`h-11`) so that when the many\n * controls wrap at narrow inspector widths the strip GROWS and reserves its real\n * layout height — the editable content always starts below it. A fixed height plus\n * `flex-wrap` was the bug: wrapped rows overflowed the locked box and painted over\n * the prose, so the buttons were un-hittable. `py-1` only bites once wrapped (a\n * single row is governed by `min-h-10` + `items-center`), padding the stacked rows.\n * Pinned by `toolbar-layout.test.ts`.\n */\nexport const RICH_TEXT_TOOLBAR_CLASS =\n 'h-auto min-h-10 flex-wrap gap-0.5 rounded-none border-x-0 border-t-0 bg-muted/30 px-1.5 py-1'\n\n/**\n * The block-style dropdown trigger's class (EC-418). The load-bearing part is the\n * FIXED width (`w-12`): the trigger shows the active style's icon glyph (not its\n * variable-width localized label — \"Absatz\" ≪ \"Überschrift 2\"), so caret movement\n * across Absatz/H1/H2/H3 can never change the trigger's width and reflow the wrapped\n * strip beneath the cursor (the reported German-locale defect). `justify-between`\n * seats the glyph and the ChevronDown in that fixed box. Every other control on the\n * strip is already a fixed `size-7` square (`ToolbarButton`, `LinkControl`) whose\n * active state only recolors (a `secondary` border sits inside the box-border square,\n * never resizing it) — so this trigger was the sole selection-variable width.\n * Pinned by `toolbar-layout.test.ts`.\n */\nexport const RICH_TEXT_MENU_TRIGGER_CLASS = 'h-7 w-12 justify-between gap-1 px-2'\n\ninterface RichTextToolbarProps {\n editor: Editor\n /** Internal-link destinations across the workspace (empty ⇒ external-only). */\n targets: LinkTarget[]\n /** Open the shared image picker (omitted ⇒ no image button). */\n onOpenImage?: () => void\n /** Open the shared component-embed picker (omitted ⇒ no embed button). */\n onOpenEmbed?: () => void\n /** EC-388: whether the editor is currently in its expanded (sheet) surface. */\n expanded?: boolean\n /** EC-388: toggle the expanded editing surface (omitted ⇒ no expand affordance). */\n onToggleExpand?: () => void\n}\n\n/**\n * The CMS rich-text toolbar (EC-314) — a compact, Notion-grade control strip that\n * reads its active/enabled state reactively from the editor (`useEditorState`) so a\n * pressed control always mirrors the caret. Every button prevents the default\n * mousedown so clicking it never steals the editor selection.\n */\nexport function RichTextToolbar({\n editor,\n targets,\n onOpenImage,\n onOpenEmbed,\n expanded = false,\n onToggleExpand,\n}: RichTextToolbarProps) {\n // The selector delegates to a pure, NULL-SAFE projection: `useEditorState` runs\n // it through an external store that React can fire against a destroyed/pre-init\n // editor (concurrent teardown, the EC-322 lazy boundary), where `editor.can()`\n // would throw on TipTap v3's nulled `commandManager`. `toolbarStateFromEditor`\n // returns the disabled shape in that window instead of crashing the toolbar.\n const state = useEditorState({\n editor,\n selector: ({ editor }) => toolbarStateFromEditor(editor),\n })\n\n return (\n <Toolbar className={RICH_TEXT_TOOLBAR_CLASS}>\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className={RICH_TEXT_MENU_TRIGGER_CLASS}\n // The visible label moved into the opened menu (EC-418) — the trigger keeps\n // an accessible name for the ACTIVE style from the same catalog keys.\n aria-label={blockStyleLabel(state.headingLevel)}\n onMouseDown={(e) => e.preventDefault()}\n >\n <BlockStyleGlyph headingLevel={state.headingLevel} />\n <ChevronDown className=\"size-3 opacity-60\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\" className=\"min-w-40\">\n {BLOCK_STYLES.map((style) => (\n <DropdownMenuItem\n key={style.headingLevel ?? 'paragraph'}\n onSelect={() => style.chain(editor.chain().focus()).run()}\n >\n <BlockStyleGlyph headingLevel={style.headingLevel} />{' '}\n {blockStyleLabel(style.headingLevel)}\n </DropdownMenuItem>\n ))}\n </DropdownMenuContent>\n </DropdownMenu>\n\n <FontSizeControl editor={editor} activeSize={state.fontSize} />\n\n <ToolbarSeparator />\n\n <ToolbarGroup>\n <ToolbarButton\n active={state.bold}\n label={translate('richText.bold')}\n onClick={() => editor.chain().focus().toggleBold().run()}\n >\n <Bold className=\"size-3.5\" />\n </ToolbarButton>\n <ToolbarButton\n active={state.italic}\n label={translate('richText.italic')}\n onClick={() => editor.chain().focus().toggleItalic().run()}\n >\n <Italic className=\"size-3.5\" />\n </ToolbarButton>\n <ToolbarButton\n active={state.underline}\n label={translate('richText.underline')}\n onClick={() => editor.chain().focus().toggleUnderline().run()}\n >\n <Underline className=\"size-3.5\" />\n </ToolbarButton>\n <ToolbarButton\n active={state.strike}\n label={translate('richText.strike')}\n onClick={() => editor.chain().focus().toggleStrike().run()}\n >\n <Strikethrough className=\"size-3.5\" />\n </ToolbarButton>\n <ToolbarButton\n active={state.code}\n label={translate('richText.code')}\n onClick={() => editor.chain().focus().toggleCode().run()}\n >\n <Code className=\"size-3.5\" />\n </ToolbarButton>\n </ToolbarGroup>\n\n <ToolbarSeparator />\n\n <ToolbarGroup>\n {/* The shared table (EC-328) — the SAME descriptors `buildSlashItems`\n derives its bulletList/orderedList/blockquote/codeBlock/divider items from. */}\n {BLOCK_COMMANDS.map((cmd) => (\n <ToolbarButton\n key={cmd.key}\n active={isBlockCommandActive(cmd, state)}\n label={translate(cmd.labelKey)}\n onClick={() => cmd.chain(editor.chain().focus()).run()}\n >\n <cmd.icon className=\"size-3.5\" />\n </ToolbarButton>\n ))}\n </ToolbarGroup>\n\n <ToolbarSeparator />\n\n <ToolbarGroup>\n <LinkControl editor={editor} targets={targets} />\n {onOpenImage ? (\n <ToolbarButton label={translate('richText.image.add')} onClick={onOpenImage}>\n <ImagePlus className=\"size-3.5\" />\n </ToolbarButton>\n ) : null}\n {onOpenEmbed ? (\n <ToolbarButton label={translate('richText.embed.add')} onClick={onOpenEmbed}>\n <Blocks className=\"size-3.5\" />\n </ToolbarButton>\n ) : null}\n </ToolbarGroup>\n\n <div className=\"flex-1\" />\n\n <ToolbarGroup>\n <ToolbarButton\n active={false}\n disabled={!state.canUndo}\n label={translate('richText.undo')}\n onClick={() => editor.chain().focus().undo().run()}\n >\n <Undo2 className=\"size-3.5\" />\n </ToolbarButton>\n <ToolbarButton\n active={false}\n disabled={!state.canRedo}\n label={translate('richText.redo')}\n onClick={() => editor.chain().focus().redo().run()}\n >\n <Redo2 className=\"size-3.5\" />\n </ToolbarButton>\n </ToolbarGroup>\n\n {/* EC-388: the expand/collapse affordance — moves with the toolbar into the\n expanded sheet (where it reads as \"collapse\") and back to the inline\n editor (where it reads as \"expand\"). Same editor instance, same value\n flow; no separate save/cancel. */}\n {onToggleExpand ? (\n <>\n <ToolbarSeparator />\n <ToolbarGroup>\n <ToolbarButton\n active={expanded}\n label={\n expanded ? translate('richText.expand.collapse') : translate('richText.expand.expand')\n }\n onClick={onToggleExpand}\n >\n {expanded ? <Minimize2 className=\"size-3.5\" /> : <Maximize2 className=\"size-3.5\" />}\n </ToolbarButton>\n </ToolbarGroup>\n </>\n ) : null}\n </Toolbar>\n )\n}\n"]}
|
|
1
|
+
{"version":3,"file":"toolbar.js","sourceRoot":"","sources":["../../../../src/components/cms/rich-text/toolbar.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAE9C,OAAO,EACL,MAAM,EACN,IAAI,EACJ,WAAW,EACX,IAAI,EACJ,SAAS,EACT,MAAM,EACN,SAAS,EACT,SAAS,EACT,KAAK,EACL,aAAa,EACb,SAAS,EACT,KAAK,GACN,MAAM,cAAc,CAAA;AACrB,OAAO,EACL,MAAM,EACN,YAAY,EACZ,mBAAmB,EACnB,gBAAgB,EAChB,mBAAmB,EACnB,OAAO,EACP,YAAY,EACZ,gBAAgB,GACjB,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAA;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAE5C,OAAO,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAA;AACxD,OAAO,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAA;AACvE,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAC9E,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAA;AAErD;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAClC,8FAA8F,CAAA;AAEhG;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,4BAA4B,GAAG,qCAAqC,CAAA;AAgBjF;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAAC,EAC9B,MAAM,EACN,OAAO,EACP,WAAW,EACX,WAAW,EACX,QAAQ,GAAG,KAAK,EAChB,cAAc,GACO;IACrB,gFAAgF;IAChF,gFAAgF;IAChF,+EAA+E;IAC/E,+EAA+E;IAC/E,6EAA6E;IAC7E,MAAM,KAAK,GAAG,cAAc,CAAC;QAC3B,MAAM;QACN,QAAQ,EAAE,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,sBAAsB,CAAC,MAAM,CAAC;KACzD,CAAC,CAAA;IAEF,OAAO,CACL,MAAC,OAAO,IAAC,SAAS,EAAE,uBAAuB,aACzC,MAAC,YAAY,eACX,KAAC,mBAAmB,IAAC,OAAO,kBAC1B,MAAC,MAAM,IACL,IAAI,EAAC,QAAQ,EACb,OAAO,EAAC,OAAO,EACf,IAAI,EAAC,IAAI,EACT,SAAS,EAAE,4BAA4B,gBAG3B,eAAe,CAAC,KAAK,CAAC,YAAY,CAAC,EAC/C,WAAW,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,cAAc,EAAE,aAEtC,KAAC,eAAe,IAAC,YAAY,EAAE,KAAK,CAAC,YAAY,GAAI,EACrD,KAAC,WAAW,IAAC,SAAS,EAAC,mBAAmB,GAAG,IACtC,GACW,EACtB,KAAC,mBAAmB,IAAC,KAAK,EAAC,OAAO,EAAC,SAAS,EAAC,UAAU,YACpD,YAAY,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAC3B,MAAC,gBAAgB,IAEf,QAAQ,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,CAAC,CAAC,GAAG,EAAE,aAEzD,KAAC,eAAe,IAAC,YAAY,EAAE,KAAK,CAAC,YAAY,GAAI,EAAC,GAAG,EACxD,eAAe,CAAC,KAAK,CAAC,YAAY,CAAC,KAJ/B,KAAK,CAAC,YAAY,IAAI,WAAW,CAKrB,CACpB,CAAC,GACkB,IACT,EAEf,KAAC,eAAe,IAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,KAAK,CAAC,QAAQ,GAAI,EAE/D,KAAC,gBAAgB,KAAG,EAEpB,MAAC,YAAY,eACX,KAAC,aAAa,IACZ,MAAM,EAAE,KAAK,CAAC,IAAI,EAClB,KAAK,EAAE,SAAS,CAAC,eAAe,CAAC,EACjC,OAAO,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,CAAC,UAAU,EAAE,CAAC,GAAG,EAAE,YAExD,KAAC,IAAI,IAAC,SAAS,EAAC,UAAU,GAAG,GACf,EAChB,KAAC,aAAa,IACZ,MAAM,EAAE,KAAK,CAAC,MAAM,EACpB,KAAK,EAAE,SAAS,CAAC,iBAAiB,CAAC,EACnC,OAAO,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,CAAC,YAAY,EAAE,CAAC,GAAG,EAAE,YAE1D,KAAC,MAAM,IAAC,SAAS,EAAC,UAAU,GAAG,GACjB,EAChB,KAAC,aAAa,IACZ,MAAM,EAAE,KAAK,CAAC,SAAS,EACvB,KAAK,EAAE,SAAS,CAAC,oBAAoB,CAAC,EACtC,OAAO,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,CAAC,eAAe,EAAE,CAAC,GAAG,EAAE,YAE7D,KAAC,SAAS,IAAC,SAAS,EAAC,UAAU,GAAG,GACpB,EAChB,KAAC,aAAa,IACZ,MAAM,EAAE,KAAK,CAAC,MAAM,EACpB,KAAK,EAAE,SAAS,CAAC,iBAAiB,CAAC,EACnC,OAAO,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,CAAC,YAAY,EAAE,CAAC,GAAG,EAAE,YAE1D,KAAC,aAAa,IAAC,SAAS,EAAC,UAAU,GAAG,GACxB,EAChB,KAAC,aAAa,IACZ,MAAM,EAAE,KAAK,CAAC,IAAI,EAClB,KAAK,EAAE,SAAS,CAAC,eAAe,CAAC,EACjC,OAAO,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,CAAC,UAAU,EAAE,CAAC,GAAG,EAAE,YAExD,KAAC,IAAI,IAAC,SAAS,EAAC,UAAU,GAAG,GACf,IACH,EAEf,KAAC,gBAAgB,KAAG,EAEpB,KAAC,YAAY,cAGV,cAAc,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAC3B,KAAC,aAAa,IAEZ,MAAM,EAAE,oBAAoB,CAAC,GAAG,EAAE,KAAK,CAAC,EACxC,KAAK,EAAE,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,EAC9B,OAAO,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,CAAC,CAAC,GAAG,EAAE,YAEtD,KAAC,GAAG,CAAC,IAAI,IAAC,SAAS,EAAC,UAAU,GAAG,IAL5B,GAAG,CAAC,GAAG,CAME,CACjB,CAAC,GACW,EAEf,KAAC,gBAAgB,KAAG,EAEpB,MAAC,YAAY,eACX,KAAC,WAAW,IAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAI,EAChD,WAAW,CAAC,CAAC,CAAC,CACb,KAAC,aAAa,IAAC,KAAK,EAAE,SAAS,CAAC,oBAAoB,CAAC,EAAE,OAAO,EAAE,WAAW,YACzE,KAAC,SAAS,IAAC,SAAS,EAAC,UAAU,GAAG,GACpB,CACjB,CAAC,CAAC,CAAC,IAAI,EACP,WAAW,CAAC,CAAC,CAAC,CACb,KAAC,aAAa,IAAC,KAAK,EAAE,SAAS,CAAC,oBAAoB,CAAC,EAAE,OAAO,EAAE,WAAW,YACzE,KAAC,MAAM,IAAC,SAAS,EAAC,UAAU,GAAG,GACjB,CACjB,CAAC,CAAC,CAAC,IAAI,IACK,EAEf,cAAK,SAAS,EAAC,QAAQ,GAAG,EAE1B,MAAC,YAAY,eACX,KAAC,aAAa,IACZ,MAAM,EAAE,KAAK,EACb,QAAQ,EAAE,CAAC,KAAK,CAAC,OAAO,EACxB,KAAK,EAAE,SAAS,CAAC,eAAe,CAAC,EACjC,OAAO,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,CAAC,GAAG,EAAE,YAElD,KAAC,KAAK,IAAC,SAAS,EAAC,UAAU,GAAG,GAChB,EAChB,KAAC,aAAa,IACZ,MAAM,EAAE,KAAK,EACb,QAAQ,EAAE,CAAC,KAAK,CAAC,OAAO,EACxB,KAAK,EAAE,SAAS,CAAC,eAAe,CAAC,EACjC,OAAO,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,CAAC,GAAG,EAAE,YAElD,KAAC,KAAK,IAAC,SAAS,EAAC,UAAU,GAAG,GAChB,IACH,EAMd,cAAc,CAAC,CAAC,CAAC,CAChB,8BACE,KAAC,gBAAgB,KAAG,EACpB,KAAC,YAAY,cACX,KAAC,aAAa,IACZ,MAAM,EAAE,QAAQ,EAChB,KAAK,EACH,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,0BAA0B,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,wBAAwB,CAAC,EAExF,OAAO,EAAE,cAAc,YAEtB,QAAQ,CAAC,CAAC,CAAC,KAAC,SAAS,IAAC,SAAS,EAAC,UAAU,GAAG,CAAC,CAAC,CAAC,KAAC,SAAS,IAAC,SAAS,EAAC,UAAU,GAAG,GACrE,GACH,IACd,CACJ,CAAC,CAAC,CAAC,IAAI,IACA,CACX,CAAA;AACH,CAAC","sourcesContent":["import { useEditorState } from '@tiptap/react'\nimport type { Editor } from '@tiptap/react'\nimport {\n Blocks,\n Bold,\n ChevronDown,\n Code,\n ImagePlus,\n Italic,\n Maximize2,\n Minimize2,\n Redo2,\n Strikethrough,\n Underline,\n Undo2,\n} from 'lucide-react'\nimport {\n Button,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n Toolbar,\n ToolbarGroup,\n ToolbarSeparator,\n} from '@elytracms/studio/ui'\nimport { translate } from '../../../lib/i18n'\nimport { ToolbarButton } from './toolbar-button'\nimport { LinkControl } from './link-control'\nimport type { LinkTarget } from './link-control'\nimport { toolbarStateFromEditor } from './toolbar-state'\nimport { BLOCK_COMMANDS, isBlockCommandActive } from './block-commands'\nimport { BLOCK_STYLES, BlockStyleGlyph, blockStyleLabel } from './block-style'\nimport { FontSizeControl } from './font-size-control'\n\n/**\n * The toolbar container classes (EC-391). The load-bearing part is the height\n * contract: `h-auto min-h-10` (NOT a fixed `h-10`/`h-11`) so that when the many\n * controls wrap at narrow inspector widths the strip GROWS and reserves its real\n * layout height — the editable content always starts below it. A fixed height plus\n * `flex-wrap` was the bug: wrapped rows overflowed the locked box and painted over\n * the prose, so the buttons were un-hittable. `py-1` only bites once wrapped (a\n * single row is governed by `min-h-10` + `items-center`), padding the stacked rows.\n * Pinned by `toolbar-layout.test.ts`.\n */\nexport const RICH_TEXT_TOOLBAR_CLASS =\n 'h-auto min-h-10 flex-wrap gap-0.5 rounded-none border-x-0 border-t-0 bg-muted/30 px-1.5 py-1'\n\n/**\n * The block-style dropdown trigger's class (EC-418). The load-bearing part is the\n * FIXED width (`w-12`): the trigger shows the active style's icon glyph (not its\n * variable-width localized label — \"Absatz\" ≪ \"Überschrift 2\"), so caret movement\n * across Absatz/H1/H2/H3 can never change the trigger's width and reflow the wrapped\n * strip beneath the cursor (the reported German-locale defect). `justify-between`\n * seats the glyph and the ChevronDown in that fixed box. Every other control on the\n * strip is already a fixed `size-7` square (`ToolbarButton`, `LinkControl`) whose\n * active state only recolors (a `secondary` border sits inside the box-border square,\n * never resizing it) — so this trigger was the sole selection-variable width.\n * Pinned by `toolbar-layout.test.ts`.\n */\nexport const RICH_TEXT_MENU_TRIGGER_CLASS = 'h-7 w-12 justify-between gap-1 px-2'\n\ninterface RichTextToolbarProps {\n editor: Editor\n /** Internal-link destinations across the workspace (empty ⇒ external-only). */\n targets: LinkTarget[]\n /** Open the shared image picker (omitted ⇒ no image button). */\n onOpenImage?: () => void\n /** Open the shared component-embed picker (omitted ⇒ no embed button). */\n onOpenEmbed?: () => void\n /** EC-388: whether the editor is currently in its expanded (sheet) surface. */\n expanded?: boolean\n /** EC-388: toggle the expanded editing surface (omitted ⇒ no expand affordance). */\n onToggleExpand?: () => void\n}\n\n/**\n * The CMS rich-text toolbar (EC-314) — a compact, Notion-grade control strip that\n * reads its active/enabled state reactively from the editor (`useEditorState`) so a\n * pressed control always mirrors the caret. Every button prevents the default\n * mousedown so clicking it never steals the editor selection.\n */\nexport function RichTextToolbar({\n editor,\n targets,\n onOpenImage,\n onOpenEmbed,\n expanded = false,\n onToggleExpand,\n}: RichTextToolbarProps) {\n // The selector delegates to a pure, NULL-SAFE projection: `useEditorState` runs\n // it through an external store that React can fire against a destroyed/pre-init\n // editor (concurrent teardown, the EC-322 lazy boundary), where `editor.can()`\n // would throw on TipTap v3's nulled `commandManager`. `toolbarStateFromEditor`\n // returns the disabled shape in that window instead of crashing the toolbar.\n const state = useEditorState({\n editor,\n selector: ({ editor }) => toolbarStateFromEditor(editor),\n })\n\n return (\n <Toolbar className={RICH_TEXT_TOOLBAR_CLASS}>\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className={RICH_TEXT_MENU_TRIGGER_CLASS}\n // The visible label moved into the opened menu (EC-418) — the trigger keeps\n // an accessible name for the ACTIVE style from the same catalog keys.\n aria-label={blockStyleLabel(state.headingLevel)}\n onMouseDown={(e) => e.preventDefault()}\n >\n <BlockStyleGlyph headingLevel={state.headingLevel} />\n <ChevronDown className=\"size-3 opacity-60\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\" className=\"min-w-40\">\n {BLOCK_STYLES.map((style) => (\n <DropdownMenuItem\n key={style.headingLevel ?? 'paragraph'}\n onSelect={() => style.chain(editor.chain().focus()).run()}\n >\n <BlockStyleGlyph headingLevel={style.headingLevel} />{' '}\n {blockStyleLabel(style.headingLevel)}\n </DropdownMenuItem>\n ))}\n </DropdownMenuContent>\n </DropdownMenu>\n\n <FontSizeControl editor={editor} activeSize={state.fontSize} />\n\n <ToolbarSeparator />\n\n <ToolbarGroup>\n <ToolbarButton\n active={state.bold}\n label={translate('richText.bold')}\n onClick={() => editor.chain().focus().toggleBold().run()}\n >\n <Bold className=\"size-3.5\" />\n </ToolbarButton>\n <ToolbarButton\n active={state.italic}\n label={translate('richText.italic')}\n onClick={() => editor.chain().focus().toggleItalic().run()}\n >\n <Italic className=\"size-3.5\" />\n </ToolbarButton>\n <ToolbarButton\n active={state.underline}\n label={translate('richText.underline')}\n onClick={() => editor.chain().focus().toggleUnderline().run()}\n >\n <Underline className=\"size-3.5\" />\n </ToolbarButton>\n <ToolbarButton\n active={state.strike}\n label={translate('richText.strike')}\n onClick={() => editor.chain().focus().toggleStrike().run()}\n >\n <Strikethrough className=\"size-3.5\" />\n </ToolbarButton>\n <ToolbarButton\n active={state.code}\n label={translate('richText.code')}\n onClick={() => editor.chain().focus().toggleCode().run()}\n >\n <Code className=\"size-3.5\" />\n </ToolbarButton>\n </ToolbarGroup>\n\n <ToolbarSeparator />\n\n <ToolbarGroup>\n {/* The shared table (EC-328) — the SAME descriptors `buildSlashItems`\n derives its bulletList/orderedList/blockquote/codeBlock/divider items from. */}\n {BLOCK_COMMANDS.map((cmd) => (\n <ToolbarButton\n key={cmd.key}\n active={isBlockCommandActive(cmd, state)}\n label={translate(cmd.labelKey)}\n onClick={() => cmd.chain(editor.chain().focus()).run()}\n >\n <cmd.icon className=\"size-3.5\" />\n </ToolbarButton>\n ))}\n </ToolbarGroup>\n\n <ToolbarSeparator />\n\n <ToolbarGroup>\n <LinkControl editor={editor} targets={targets} />\n {onOpenImage ? (\n <ToolbarButton label={translate('richText.image.add')} onClick={onOpenImage}>\n <ImagePlus className=\"size-3.5\" />\n </ToolbarButton>\n ) : null}\n {onOpenEmbed ? (\n <ToolbarButton label={translate('richText.embed.add')} onClick={onOpenEmbed}>\n <Blocks className=\"size-3.5\" />\n </ToolbarButton>\n ) : null}\n </ToolbarGroup>\n\n <div className=\"flex-1\" />\n\n <ToolbarGroup>\n <ToolbarButton\n active={false}\n disabled={!state.canUndo}\n label={translate('richText.undo')}\n onClick={() => editor.chain().focus().undo().run()}\n >\n <Undo2 className=\"size-3.5\" />\n </ToolbarButton>\n <ToolbarButton\n active={false}\n disabled={!state.canRedo}\n label={translate('richText.redo')}\n onClick={() => editor.chain().focus().redo().run()}\n >\n <Redo2 className=\"size-3.5\" />\n </ToolbarButton>\n </ToolbarGroup>\n\n {/* EC-388: the expand/collapse affordance — moves with the toolbar into the\n expanded sheet (where it reads as \"collapse\") and back to the inline\n editor (where it reads as \"expand\"). Same editor instance, same value\n flow; no separate save/cancel. */}\n {onToggleExpand ? (\n <>\n <ToolbarSeparator />\n <ToolbarGroup>\n <ToolbarButton\n active={expanded}\n label={\n expanded ? translate('richText.expand.collapse') : translate('richText.expand.expand')\n }\n onClick={onToggleExpand}\n >\n {expanded ? <Minimize2 className=\"size-3.5\" /> : <Maximize2 className=\"size-3.5\" />}\n </ToolbarButton>\n </ToolbarGroup>\n </>\n ) : null}\n </Toolbar>\n )\n}\n"]}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Node, NodeViewWrapper, ReactNodeViewRenderer, mergeAttributes } from '@tiptap/react';
|
|
3
3
|
import { FileQuestion } from 'lucide-react';
|
|
4
|
-
import { translate } from '
|
|
5
|
-
import { cn } from '
|
|
4
|
+
import { translate } from '../../../lib/i18n/index.js';
|
|
5
|
+
import { cn } from '../../../lib/utils.js';
|
|
6
6
|
/**
|
|
7
7
|
* The generic unknown-node carrier (EC-547, per EC-500 D3).
|
|
8
8
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"unknown-node.js","sourceRoot":"","sources":["../../../../src/components/cms/rich-text/unknown-node.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,IAAI,EAAE,eAAe,EAAE,qBAAqB,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAE7F,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAC3C,OAAO,EAAE,SAAS,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"unknown-node.js","sourceRoot":"","sources":["../../../../src/components/cms/rich-text/unknown-node.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,IAAI,EAAE,eAAe,EAAE,qBAAqB,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAE7F,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAC3C,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAA;AAC7C,OAAO,EAAE,EAAE,EAAE,MAAM,oBAAoB,CAAA;AAEvC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AAEH,6EAA6E;AAC7E,MAAM,CAAC,MAAM,yBAAyB,GAAG,gBAAgB,CAAA;AAEzD,MAAM,CAAC,MAAM,kBAAkB,GAAG,IAAI,CAAC,MAAM,CAAC;IAC5C,IAAI,EAAE,yBAAyB;IAC/B,KAAK,EAAE,OAAO;IACd,IAAI,EAAE,IAAI;IACV,UAAU,EAAE,IAAI;IAChB,SAAS,EAAE,IAAI;IAEf,aAAa;QACX,2EAA2E;QAC3E,yEAAyE;QACzE,OAAO,EAAE,IAAI,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE,CAAA;IACrD,CAAC;IAED,SAAS;QACP,OAAO,CAAC,EAAE,GAAG,EAAE,sBAAsB,EAAE,CAAC,CAAA;IAC1C,CAAC;IAED,UAAU,CAAC,EAAE,cAAc,EAAE;QAC3B,OAAO,CAAC,KAAK,EAAE,eAAe,CAAC,cAAc,EAAE,EAAE,iBAAiB,EAAE,EAAE,EAAE,CAAC,CAAC,CAAA;IAC5E,CAAC;IAED,WAAW;QACT,OAAO,qBAAqB,CAAC,eAAe,CAAC,CAAA;IAC/C,CAAC;CACF,CAAC,CAAA;AAEF;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,OAAgB;IAC9C,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QAC/E,MAAM,IAAI,GAAI,OAA8B,CAAC,IAAI,CAAA;QACjD,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC;YAAE,OAAO,IAAI,CAAA;IAC9D,CAAC;IACD,OAAO,SAAS,CAAA;AAClB,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,eAAe,CAAC,EAAE,QAAQ,EAAwB;IAChE,OAAO,CACL,kCACmB,QAAQ,EACzB,IAAI,EAAC,MAAM,EACX,eAAe,EAAE,KAAK,EACtB,SAAS,EAAC,+HAA+H,aAEzI,KAAC,YAAY,IAAC,SAAS,EAAC,iBAAiB,GAAG,EAC5C,gBAAM,SAAS,EAAC,yBAAyB,aACtC,SAAS,CAAC,0BAA0B,CAAC,OAAE,eAAM,SAAS,EAAC,WAAW,YAAE,QAAQ,GAAQ,IAChF,IACH,CACP,CAAA;AACH,CAAC;AAED,SAAS,eAAe,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAsB;IAC7D,OAAO,CACL,KAAC,eAAe,8BAEd,SAAS,EAAE,EAAE,CAAC,MAAM,EAAE,QAAQ,IAAI,gCAAgC,CAAC,YAEnE,KAAC,eAAe,IAAC,QAAQ,EAAE,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAI,GAC/C,CACnB,CAAA;AACH,CAAC","sourcesContent":["import { Node, NodeViewWrapper, ReactNodeViewRenderer, mergeAttributes } from '@tiptap/react'\nimport type { ReactNodeViewProps } from '@tiptap/react'\nimport { FileQuestion } from 'lucide-react'\nimport { translate } from '../../../lib/i18n'\nimport { cn } from '../../../lib/utils'\n\n/**\n * The generic unknown-node carrier (EC-547, per EC-500 D3).\n *\n * A ProseMirror schema is CLOSED: any node type it does not declare is silently\n * dropped on load, and the first keystroke persists the loss. That makes the\n * flow editor a data shredder for any stored node it does not natively edit —\n * exactly what the converged block sequence must survive (the anti-Gutenberg\n * round-trip law: a projection may only rewrite nodes it natively edits; every\n * other node survives byte-for-byte).\n *\n * So the storage⇄editor projection ({@link ../value}) wraps every node whose\n * type is outside the editor's native vocabulary into this block atom on the\n * way IN — the ORIGINAL node rides verbatim in the single JSON `node` attr\n * (the `componentEmbed` pattern; in-repo precedent #2 is the dom-editor's\n * `PRESERVE_NODE_ATTR` verbatim-JSON carry) — and unwraps it byte-for-byte on\n * the way OUT. The NodeView renders a visibly-labelled read-only block naming\n * the carried type, matching the server renderer's unknown fallback\n * (`rich-text/render.tsx`: \"Unknown content block: <type>\") so both surfaces\n * speak the same vocabulary. Never a silent drop, never a crash.\n *\n * The carrier exists ONLY in the editor document — it is never written to\n * storage (the projection-out replaces it with its carried payload), so a\n * stored node that happened to reuse this type name would itself be wrapped on\n * the way in and restored untouched on the way out: misinterpretation is\n * structurally impossible, not policed.\n *\n * **Two declared, deliberate limits (EC-550, measured and DECLINED for now):**\n *\n * 1. *Unknown MARKS still shred.* A foreign mark type on a text run inside a\n * natively-edited node is dropped by ProseMirror's closed mark schema on\n * load, and the first keystroke persists the loss (foreign marks inside a\n * CARRIED node survive in its payload). Declined because the failure is\n * bounded — the text itself survives, only the annotation drops — and the\n * mark vocabulary is fixed and global today, so no writer can produce a\n * foreign mark short of hand-written JSON. A mark-preserving projection\n * (marks copied per-run to a carrier attr) is the design if a real producer\n * ever lands (registry-extensible marks, external/AI writers).\n * 2. *Inline-position unknowns relocate.* This carrier is a BLOCK atom; a\n * foreign node inside a paragraph's inline content is wrapped as a block and\n * ProseMirror's fitting moves it out of the inline position — it survives\n * byte-for-byte but changes position on save. Declined for the same reason:\n * there is no inline foreign-node producer today (the engine's own inline\n * vocabulary is closed), and the fix needs a second inline carrier plus a\n * position-aware projection. Revisit alongside (1).\n */\n\n/** The editor-only node type of the opaque carrier. Never a storage type. */\nexport const UNKNOWN_NODE_CARRIER_TYPE = 'unknownCarrier'\n\nexport const UnknownNodeCarrier = Node.create({\n name: UNKNOWN_NODE_CARRIER_TYPE,\n group: 'block',\n atom: true,\n selectable: true,\n draggable: true,\n\n addAttributes() {\n // `node` holds the carried original node JSON verbatim; storage/JSON only,\n // never rendered into markup (same convention as ComponentEmbed's attr).\n return { node: { default: null, rendered: false } }\n },\n\n parseHTML() {\n return [{ tag: 'div[data-ec-carrier]' }]\n },\n\n renderHTML({ HTMLAttributes }) {\n return ['div', mergeAttributes(HTMLAttributes, { 'data-ec-carrier': '' })]\n },\n\n addNodeView() {\n return ReactNodeViewRenderer(UnknownNodeView)\n },\n})\n\n/**\n * The carried node's `type` for the visible label; `'unknown'` when the payload\n * is not even node-shaped (the carrier is total — it can hold any JSON).\n */\nexport function carriedNodeType(carried: unknown): string {\n if (typeof carried === 'object' && carried !== null && !Array.isArray(carried)) {\n const type = (carried as { type?: unknown }).type\n if (typeof type === 'string' && type.length > 0) return type\n }\n return 'unknown'\n}\n\n/**\n * The presentational carrier block — a labelled, read-only card naming the\n * carried node type. Kept free of TipTap context so it renders + asserts\n * standalone (the house `renderToStaticMarkup` smoke pattern). The label rides\n * the studio message catalog (`richText.carrier.unknown`, EC-550) and matches\n * the server renderer's unknown-node fallback vocabulary (`data-unknown-node` /\n * \"Unknown content block: <type>\" — deliberately unlocalized there: delivery\n * has no viewer-locale catalog).\n */\nexport function UnknownNodeCard({ nodeType }: { nodeType: string }) {\n return (\n <div\n data-ec-carrier={nodeType}\n role=\"note\"\n contentEditable={false}\n className=\"flex items-center gap-2.5 rounded-md border border-dashed border-border bg-muted/40 px-3 py-2.5 text-sm text-muted-foreground\"\n >\n <FileQuestion className=\"size-4 shrink-0\" />\n <span className=\"min-w-0 flex-1 truncate\">\n {translate('richText.carrier.unknown')} <span className=\"font-mono\">{nodeType}</span>\n </span>\n </div>\n )\n}\n\nfunction UnknownNodeView({ node, selected }: ReactNodeViewProps) {\n return (\n <NodeViewWrapper\n data-drag-handle\n className={cn('my-2', selected && 'rounded-md ring-2 ring-ring/50')}\n >\n <UnknownNodeCard nodeType={carriedNodeType(node.attrs.node)} />\n </NodeViewWrapper>\n )\n}\n"]}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { parseRichTextValue, renderRichTextValue } from '@elytracms/core/rich-text';
|
|
3
3
|
import { Badge, Text } from '@elytracms/studio/ui';
|
|
4
|
-
import { translate } from '
|
|
5
|
-
import { cn } from '
|
|
4
|
+
import { translate } from '../../lib/i18n/index.js';
|
|
5
|
+
import { cn } from '../../lib/utils.js';
|
|
6
6
|
/**
|
|
7
7
|
* Compact read-only rich-text rendering for review surfaces (EC-379).
|
|
8
8
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rich-text-preview.js","sourceRoot":"","sources":["../../../src/components/cms/rich-text-preview.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAA;AAGnF,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAA;AAClD,OAAO,EAAE,SAAS,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"rich-text-preview.js","sourceRoot":"","sources":["../../../src/components/cms/rich-text-preview.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAA;AAGnF,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAA;AAClD,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC1C,OAAO,EAAE,EAAE,EAAE,MAAM,iBAAiB,CAAA;AAEpC;;;;;;;;;;;;GAYG;AAEH,2EAA2E;AAC3E,MAAM,wBAAwB,GAAG;IAC/B,iDAAiD;IACjD,uCAAuC;IACvC,qCAAqC;IACrC,+DAA+D;IAC/D,qFAAqF;IACrF,iHAAiH;IACjH,0CAA0C;IAC1C,0GAA0G;IAC1G,oCAAoC;IACpC,sBAAsB;CACvB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;AAEX,kFAAkF;AAClF,SAAS,SAAS,CAAC,EAAE,IAAI,EAA2B;IAClD,OAAO,CACL,KAAC,KAAK,IAAC,OAAO,EAAC,SAAS,EAAC,SAAS,EAAC,0BAA0B,YAC1D,IAAI,CAAC,WAAW,GACX,CACT,CAAA;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,EAAE,KAAK,EAAE,SAAS,EAA0C;IAC1F,MAAM,MAAM,GAAG,kBAAkB,CAAC,KAAK,CAAC,CAAA;IACxC,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;QACf,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;YAC9B,OAAO,CACL,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,SAAS,EAAC,iCAAiC,YACxD,KAAK,GACD,CACR,CAAA;QACH,CAAC;QACD,OAAO,CACL,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,YACzB,SAAS,CAAC,yBAAyB,CAAC,GAChC,CACR,CAAA;IACH,CAAC;IACD,OAAO,mBAAmB,CAAC,MAAM,CAAC,KAAK,EAAE;QACvC,SAAS,EAAE,EAAE,CAAC,wBAAwB,EAAE,SAAS,CAAC;QAClD,WAAW,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,KAAC,SAAS,IAAC,IAAI,EAAE,IAAI,GAAI;KACjD,CAAC,CAAA;AACJ,CAAC;AAED,6FAA6F;AAC7F,MAAM,oBAAoB,GAAG,IAAI,GAAG,CAAC;IACnC,WAAW;IACX,SAAS;IACT,MAAM;IACN,WAAW;IACX,YAAY;IACZ,aAAa;IACb,UAAU;IACV,YAAY;CACb,CAAC,CAAA;AAEF;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAAC,KAAc;IACjD,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,EAAE;QAAE,OAAO,IAAI,CAAA;IACtE,MAAM,MAAM,GAAG,kBAAkB,CAAC,KAAK,CAAC,CAAA;IACxC,IAAI,CAAC,MAAM,CAAC,EAAE;QAAE,OAAO,KAAK,CAAA;IAC5B,MAAM,UAAU,GAAG,CAAC,KAA0C,EAAW,EAAE,CACzE,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,IAAI,CAChB,CAAC,IAAI,EAAE,EAAE,CACP,CAAC,OAAO,IAAI,CAAC,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC;QAC9D,CAAC,oBAAoB,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC;QACpC,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,CAC3B,CAAA;IACH,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC,KAAK,CAAC,OAAgB,CAAC,CAAA;AACnD,CAAC","sourcesContent":["import { parseRichTextValue, renderRichTextValue } from '@elytracms/core/rich-text'\nimport type { RichTextNode } from '@elytracms/core/rich-text'\nimport type { ComponentNode } from '@elytracms/core/project-graph'\nimport { Badge, Text } from '@elytracms/studio/ui'\nimport { translate } from '../../lib/i18n'\nimport { cn } from '../../lib/utils'\n\n/**\n * Compact read-only rich-text rendering for review surfaces (EC-379).\n *\n * The AI proposal diff (and any other change-review lane) must show the editor\n * the ACTUAL prose they are approving — never a blind \"Content changed.\" note.\n * This renders a stored rich-text value through the engine's server-safe\n * read-only renderer (`@elytracms/core/rich-text`) with modest, compact styles;\n * component embeds render as an inspectable component-id chip (the diff reviews\n * content, not layout), and unknown nodes keep the renderer's visible fallback.\n *\n * Self-contained by design: EC-382 (proposal-diff layout) restyles the diff\n * chrome around it without touching this component's contract.\n */\n\n/** Compact prose styles applied to the renderer's `div.rich-text` root. */\nconst RICH_TEXT_PREVIEW_STYLES = [\n 'text-sm leading-relaxed break-words space-y-1.5',\n '[&_h1]:text-base [&_h1]:font-semibold',\n '[&_h2]:text-sm [&_h2]:font-semibold',\n '[&_:is(h3,h4,h5,h6)]:text-sm [&_:is(h3,h4,h5,h6)]:font-medium',\n '[&_:is(ul,ol)]:pl-5 [&_:is(ul,ol)]:space-y-0.5 [&_ul]:list-disc [&_ol]:list-decimal',\n '[&_blockquote]:border-l-2 [&_blockquote]:border-border [&_blockquote]:pl-2 [&_blockquote]:text-muted-foreground',\n '[&_a]:underline [&_a]:underline-offset-2',\n '[&_pre]:overflow-x-auto [&_pre]:rounded [&_pre]:bg-muted [&_pre]:p-2 [&_code]:font-mono [&_code]:text-xs',\n '[&_img]:max-w-full [&_img]:rounded',\n '[&_hr]:border-border',\n].join(' ')\n\n/** The inspectable chip a `componentEmbed` renders as inside a review preview. */\nfunction EmbedChip({ node }: { node: ComponentNode }) {\n return (\n <Badge variant=\"outline\" className=\"font-mono text-[0.65rem]\">\n {node.componentId}\n </Badge>\n )\n}\n\n/**\n * Render a stored rich-text value as compact read-only prose. A legacy plain\n * string stays visible as text; any other unparseable value degrades to the\n * generic object note — visible, never silent, never a crash.\n */\nexport function RichTextPreview({ value, className }: { value: unknown; className?: string }) {\n const parsed = parseRichTextValue(value)\n if (!parsed.ok) {\n if (typeof value === 'string') {\n return (\n <Text size=\"sm\" className=\"whitespace-pre-wrap break-words\">\n {value}\n </Text>\n )\n }\n return (\n <Text size=\"sm\" tone=\"muted\">\n {translate('cms.version.objectValue')}\n </Text>\n )\n }\n return renderRichTextValue(parsed.value, {\n className: cn(RICH_TEXT_PREVIEW_STYLES, className),\n renderEmbed: (node) => <EmbedChip node={node} />,\n })\n}\n\n/** Node types that carry no content of their own — an empty scaffold of these is \"empty\". */\nconst PROSE_SCAFFOLD_TYPES = new Set([\n 'paragraph',\n 'heading',\n 'text',\n 'hardBreak',\n 'bulletList',\n 'orderedList',\n 'listItem',\n 'blockquote',\n])\n\n/**\n * Whether a rich-text value is EMPTY for review purposes: unset, an empty doc,\n * or nothing but blank prose scaffolding. A doc carrying any atom (image, embed,\n * custom node) or non-blank text is content — it must be shown, not skipped.\n */\nexport function isRichTextValueEmpty(value: unknown): boolean {\n if (value === undefined || value === null || value === '') return true\n const parsed = parseRichTextValue(value)\n if (!parsed.ok) return false\n const hasContent = (nodes: readonly RichTextNode[] | undefined): boolean =>\n (nodes ?? []).some(\n (node) =>\n (typeof node.text === 'string' && node.text.trim().length > 0) ||\n !PROSE_SCAFFOLD_TYPES.has(node.type) ||\n hasContent(node.content),\n )\n return !hasContent(parsed.value.content as never)\n}\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { CollectionViewCapabilities } from '
|
|
1
|
+
import type { CollectionViewCapabilities } from '../../lib/collection-view/collection-view-source.js';
|
|
2
2
|
/**
|
|
3
3
|
* EC-316 — the right-edge row `⋮` menu's action vocabulary: the SAME bulk-action
|
|
4
4
|
* set the toolbar's selection actions offer (`bulk-actions.ts`'s `BulkAction`),
|
|
@@ -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();
|