@elytracms/studio 0.0.91 → 0.0.93
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/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/cms.ts +537 -177
- package/convex/contract-parity.test.ts +173 -19
- package/convex/delivery.ts +26 -13
- package/convex/document-revalidate.test.ts +17 -16
- package/convex/orphan-hydration.test.ts +116 -0
- package/convex/presence-cost.test.ts +89 -0
- package/convex/rail-write-parity.test.ts +17 -14
- package/convex/referenceMaintenance.ts +15 -15
- package/convex/schema.ts +70 -5
- package/convex/triggers.ts +21 -8
- package/convex/write-limits.ts +1 -1
- package/dist/components/ai/ai-chat-panel.js +33 -22
- package/dist/components/ai/ai-chat-panel.js.map +1 -1
- package/dist/components/ai/proposal-card.js +2 -1
- package/dist/components/ai/proposal-card.js.map +1 -1
- package/dist/components/ai/translate-document-action.d.ts +8 -2
- package/dist/components/ai/translate-document-action.js.map +1 -1
- package/dist/components/ai/translate-document-dialog.d.ts +1 -1
- package/dist/components/ai/translate-document-dialog.js +14 -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 +23 -6
- package/dist/components/app-views/default-modules.js +33 -14
- package/dist/components/app-views/default-modules.js.map +1 -1
- package/dist/components/assets/asset-detail.js +18 -8
- package/dist/components/assets/asset-detail.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 +3 -2
- package/dist/components/assets/asset-thumb.js.map +1 -1
- package/dist/components/assets/assets-collection-view-source.d.ts +3 -3
- 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-store.d.ts +14 -0
- package/dist/components/assets/assets-store.js +23 -1
- package/dist/components/assets/assets-store.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/builder/blocks-model.d.ts +14 -26
- package/dist/components/builder/blocks-model.js +35 -41
- package/dist/components/builder/blocks-model.js.map +1 -1
- package/dist/components/builder/blocks-panel.d.ts +7 -3
- package/dist/components/builder/blocks-panel.js +6 -5
- package/dist/components/builder/blocks-panel.js.map +1 -1
- package/dist/components/builder/builder-store.d.ts +126 -30
- package/dist/components/builder/builder-store.js +169 -67
- 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-chrome-css.js +7 -0
- package/dist/components/builder/canvas-chrome-css.js.map +1 -1
- package/dist/components/builder/canvas-dnd.d.ts +7 -7
- package/dist/components/builder/canvas-dnd.js +5 -5
- package/dist/components/builder/canvas-dnd.js.map +1 -1
- package/dist/components/builder/canvas-render-context.d.ts +30 -22
- package/dist/components/builder/canvas-render-context.js +44 -44
- 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.d.ts +5 -4
- package/dist/components/builder/canvas.js +34 -6
- package/dist/components/builder/canvas.js.map +1 -1
- package/dist/components/builder/clipboard-actions.d.ts +15 -0
- package/dist/components/builder/clipboard-actions.js +50 -13
- package/dist/components/builder/clipboard-actions.js.map +1 -1
- package/dist/components/builder/composition-field-editor.d.ts +10 -16
- package/dist/components/builder/composition-field-editor.js +30 -40
- package/dist/components/builder/composition-field-editor.js.map +1 -1
- package/dist/components/builder/composition-patches.d.ts +7 -0
- package/dist/components/builder/composition-patches.js +256 -0
- package/dist/components/builder/composition-patches.js.map +1 -0
- 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/insert-context.d.ts +17 -25
- package/dist/components/builder/insert-context.js +45 -92
- package/dist/components/builder/insert-context.js.map +1 -1
- package/dist/components/builder/page-tree.d.ts +3 -3
- package/dist/components/builder/page-tree.js +3 -3
- 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 +4 -0
- package/dist/components/builder/property-inspector.js.map +1 -1
- package/dist/components/builder/reuse-context.js +65 -26
- package/dist/components/builder/reuse-context.js.map +1 -1
- package/dist/components/builder/reuse.d.ts +79 -8
- package/dist/components/builder/reuse.js +71 -23
- package/dist/components/builder/reuse.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/cms/asset-field.d.ts +38 -1
- package/dist/components/cms/asset-field.js +182 -58
- 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 +12 -18
- 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 +3 -24
- package/dist/components/cms/bulk-actions.js.map +1 -1
- package/dist/components/cms/cell-edit.d.ts +5 -5
- package/dist/components/cms/cell-edit.js.map +1 -1
- package/dist/components/cms/cell.js.map +1 -1
- package/dist/components/cms/cms-store.d.ts +87 -45
- package/dist/components/cms/cms-store.js +347 -203
- package/dist/components/cms/cms-store.js.map +1 -1
- package/dist/components/cms/collection-table.js +7 -2
- package/dist/components/cms/collection-table.js.map +1 -1
- package/dist/components/cms/commit-policy.d.ts +24 -0
- package/dist/components/cms/commit-policy.js +37 -0
- package/dist/components/cms/commit-policy.js.map +1 -0
- package/dist/components/cms/detail-editor.js +83 -137
- package/dist/components/cms/detail-editor.js.map +1 -1
- package/dist/components/cms/document-diff-view.d.ts +3 -1
- package/dist/components/cms/document-diff-view.js +12 -7
- package/dist/components/cms/document-diff-view.js.map +1 -1
- package/dist/components/cms/draft-commit.d.ts +70 -0
- package/dist/components/cms/draft-commit.js +82 -0
- package/dist/components/cms/draft-commit.js.map +1 -0
- package/dist/components/cms/duplicate-document.d.ts +11 -13
- package/dist/components/cms/duplicate-document.js +26 -34
- package/dist/components/cms/duplicate-document.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 +47 -0
- package/dist/components/cms/field-collaboration.js +87 -0
- package/dist/components/cms/field-collaboration.js.map +1 -0
- package/dist/components/cms/field-control-lib.d.ts +22 -1
- package/dist/components/cms/field-control-lib.js +33 -4
- package/dist/components/cms/field-control-lib.js.map +1 -1
- package/dist/components/cms/field-control.js +17 -14
- package/dist/components/cms/field-control.js.map +1 -1
- package/dist/components/cms/gate-validation.d.ts +21 -30
- package/dist/components/cms/gate-validation.js +11 -22
- package/dist/components/cms/gate-validation.js.map +1 -1
- package/dist/components/cms/object-array-field.js +4 -4
- 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 +13 -6
- package/dist/components/cms/prop-fields.js.map +1 -1
- package/dist/components/cms/reference-confirm.js +10 -4
- package/dist/components/cms/reference-confirm.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.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/use-document-draft.d.ts +96 -0
- package/dist/components/cms/use-document-draft.js +283 -0
- package/dist/components/cms/use-document-draft.js.map +1 -0
- package/dist/components/cms/use-document-versions.js +2 -2
- package/dist/components/cms/use-document-versions.js.map +1 -1
- package/dist/components/cms/use-live-url.d.ts +6 -12
- package/dist/components/cms/use-live-url.js +16 -15
- package/dist/components/cms/use-live-url.js.map +1 -1
- package/dist/components/cms/use-publish-controller.d.ts +13 -18
- package/dist/components/cms/use-publish-controller.js +24 -41
- package/dist/components/cms/use-publish-controller.js.map +1 -1
- package/dist/components/cms/use-published-perspective.d.ts +2 -3
- package/dist/components/cms/use-published-perspective.js +4 -3
- package/dist/components/cms/use-published-perspective.js.map +1 -1
- package/dist/components/cms/versions-pane.js +8 -1
- package/dist/components/cms/versions-pane.js.map +1 -1
- package/dist/components/collection-view/cms-collection-view-source.js +24 -7
- package/dist/components/collection-view/cms-collection-view-source.js.map +1 -1
- package/dist/components/components/transfer-dialog.js +28 -23
- package/dist/components/components/transfer-dialog.js.map +1 -1
- package/dist/components/components/transfer.d.ts +31 -21
- package/dist/components/components/transfer.js +49 -35
- package/dist/components/components/transfer.js.map +1 -1
- package/dist/components/project-list.js.map +1 -1
- package/dist/components/project-session.d.ts +6 -3
- package/dist/components/project-session.js +14 -8
- package/dist/components/project-session.js.map +1 -1
- package/dist/components/project-switcher.js.map +1 -1
- package/dist/components/rail-view-source.js +2 -1
- package/dist/components/rail-view-source.js.map +1 -1
- package/dist/components/section-nav.js +1 -1
- package/dist/components/section-nav.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 +14 -1
- 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 +2 -0
- package/dist/lib/ai/index.js +2 -0
- 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/provenance.d.ts +1 -1
- package/dist/lib/ai/provenance.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 +85 -87
- package/dist/lib/ai/tools.js.map +1 -1
- package/dist/lib/ai/translate-document.d.ts +39 -5
- package/dist/lib/ai/translate-document.js +160 -51
- 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 +21 -12
- package/dist/lib/ai/update-fields.js +118 -177
- 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/ai-current-document.d.ts +11 -11
- package/dist/lib/ai-current-document.js +4 -4
- package/dist/lib/ai-current-document.js.map +1 -1
- 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-target.d.ts +18 -9
- package/dist/lib/clipboard/paste-target.js +8 -7
- package/dist/lib/clipboard/paste-target.js.map +1 -1
- package/dist/lib/cms/field-patch.d.ts +15 -0
- package/dist/lib/cms/field-patch.js +25 -0
- package/dist/lib/cms/field-patch.js.map +1 -0
- package/dist/lib/cms/published-documents.d.ts +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 +3 -3
- package/dist/lib/collection-view/__fixtures__/document-writes.d.ts +6 -0
- package/dist/lib/collection-view/__fixtures__/document-writes.js +23 -0
- package/dist/lib/collection-view/__fixtures__/document-writes.js.map +1 -0
- package/dist/lib/collection-view/collection-view-source.d.ts +69 -6
- package/dist/lib/collection-view/collection-view-source.js.map +1 -1
- package/dist/lib/data/collection-content.d.ts +4 -13
- package/dist/lib/data/collection-content.js +11 -5
- package/dist/lib/data/collection-content.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/messages.de.js +72 -15
- package/dist/lib/i18n/messages.de.js.map +1 -1
- package/dist/lib/i18n/messages.en.d.ts +61 -12
- package/dist/lib/i18n/messages.en.js +76 -16
- package/dist/lib/i18n/messages.en.js.map +1 -1
- package/dist/lib/operations/__fixtures__/write-document.d.ts +30 -0
- package/dist/lib/operations/__fixtures__/write-document.js +16 -0
- package/dist/lib/operations/__fixtures__/write-document.js.map +1 -0
- package/dist/lib/operations/cms-persistence.d.ts +124 -31
- package/dist/lib/operations/cms-persistence.js +447 -68
- package/dist/lib/operations/cms-persistence.js.map +1 -1
- package/dist/lib/operations/config-manifest-lookup.d.ts +35 -0
- package/dist/lib/operations/config-manifest-lookup.js +56 -0
- package/dist/lib/operations/config-manifest-lookup.js.map +1 -0
- package/dist/lib/operations/project-runtime-config.d.ts +39 -0
- package/dist/lib/operations/project-runtime-config.js +54 -0
- package/dist/lib/operations/project-runtime-config.js.map +1 -0
- package/dist/lib/operations/publish-with-revision.d.ts +64 -0
- package/dist/lib/operations/publish-with-revision.js +66 -0
- package/dist/lib/operations/publish-with-revision.js.map +1 -0
- package/dist/lib/operations/runtime-validator.d.ts +32 -0
- package/dist/lib/operations/runtime-validator.js +18 -0
- package/dist/lib/operations/runtime-validator.js.map +1 -0
- package/dist/lib/operations/session-hydration.d.ts +31 -14
- package/dist/lib/operations/session-hydration.js +12 -16
- package/dist/lib/operations/session-hydration.js.map +1 -1
- package/dist/lib/operations/studio-operations.d.ts +59 -2
- package/dist/lib/operations/studio-operations.js +76 -14
- package/dist/lib/operations/studio-operations.js.map +1 -1
- package/dist/lib/persistence/convex-adapter.d.ts +13 -5
- package/dist/lib/persistence/convex-adapter.js +189 -74
- package/dist/lib/persistence/convex-adapter.js.map +1 -1
- package/dist/lib/persistence/convex-client.d.ts +65 -5
- package/dist/lib/persistence/convex-client.js +106 -3
- package/dist/lib/persistence/convex-client.js.map +1 -1
- package/dist/lib/persistence/index.js +17 -18
- package/dist/lib/persistence/index.js.map +1 -1
- package/dist/lib/persona/labels.js +4 -1
- package/dist/lib/persona/labels.js.map +1 -1
- package/dist/lib/query/operations-query.d.ts +1 -1
- package/dist/lib/query/operations-query.js +1 -1
- package/dist/lib/query/operations-query.js.map +1 -1
- package/dist/lib/references/impact.d.ts +21 -15
- package/dist/lib/references/impact.js +13 -26
- package/dist/lib/references/impact.js.map +1 -1
- package/dist/lib/references/references-store.d.ts +9 -0
- package/dist/lib/references/references-store.js +24 -4
- package/dist/lib/references/references-store.js.map +1 -1
- package/dist/lib/routes/route-issues.d.ts +6 -6
- package/dist/lib/routes/route-issues.js +26 -80
- package/dist/lib/routes/route-issues.js.map +1 -1
- package/dist/lib/workspace/config-issues.js +2 -1
- package/dist/lib/workspace/config-issues.js.map +1 -1
- package/dist/lib/workspace/index.d.ts +31 -1
- package/dist/lib/workspace/index.js +64 -17
- package/dist/lib/workspace/index.js.map +1 -1
- package/dist/lib/workspace/switcher-source.d.ts +3 -3
- package/dist/lib/workspace/switcher-source.js.map +1 -1
- package/dist/lib/workspace/workspace-source.d.ts +2 -2
- package/dist/lib/workspace/workspace-source.js +2 -1
- package/dist/lib/workspace/workspace-source.js.map +1 -1
- package/package.json +3 -3
- 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/cms/use-save-mode.d.ts +0 -35
- package/dist/components/cms/use-save-mode.js +0 -54
- package/dist/components/cms/use-save-mode.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
- package/dist/lib/cms/document-gate.d.ts +0 -47
- package/dist/lib/cms/document-gate.js +0 -61
- package/dist/lib/cms/document-gate.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reuse-context.js","sourceRoot":"","sources":["../../../src/components/builder/reuse-context.tsx"],"names":[],"mappings":"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","sourcesContent":["import { createContext, useCallback, useContext, useMemo, useState } from 'react'\nimport type { FormEvent, ReactNode } from 'react'\nimport { blockRootsOf, getNode, isStaticProp } from '@elytracms/core/project-graph'\nimport { readDocumentRef } from '@elytracms/core/cms-core'\nimport {\n Button,\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n Field,\n Input,\n Stack,\n toast,\n} from '@elytracms/studio/ui'\nimport { getStudioRegistry } from '@elytracms/studio/lib/registry'\nimport { resolveDeploymentReuse } from '@elytracms/studio/lib/workspace'\nimport { localizedLabel, translate } from '@elytracms/studio/lib/i18n'\nimport { useCms } from '../cms/cms-store'\nimport { useBuilder } from './builder-store'\nimport { buildReusableValues, makeEmbedNode, resolveReuseCapability } from './reuse'\nimport type { ReuseCapability } from './reuse'\n\n/**\n * In-canvas reuse gestures (EC-282): \"Make reusable\" promotes a selected block to\n * a new reusable document and replaces it in place with a transcluding embed;\n * \"Detach to local copy\" inlines an embed's referenced blocks so a page can\n * diverge. Both round-trip through the existing persistence + in-Convex reference\n * index (EC-277), so where-used is correct immediately.\n *\n * The provider needs BOTH the builder store (the edited composition) and the CMS\n * store (to create/read the reusable document), so it mounts inside the\n * composition-field editor's `BuilderProvider`, itself inside the CMS document\n * editor. Where no provider is mounted (or the capability is absent), the menu\n * reads `null` and simply omits the gestures.\n */\nexport interface ReuseGestures {\n capability: ReuseCapability | null\n canMakeReusable: (nodeId: string) => boolean\n canDetach: (nodeId: string) => boolean\n /** Open the name dialog for promoting `nodeId`. */\n startMakeReusable: (nodeId: string) => void\n /** Replace a `Reusable` embed with an editable inline copy of its target's blocks. */\n detach: (nodeId: string) => void\n}\n\nconst ReuseContext = createContext<ReuseGestures | null>(null)\n\nexport function useReuse(): ReuseGestures | null {\n return useContext(ReuseContext)\n}\n\nexport function ReuseProvider({ children }: { children: ReactNode }) {\n const builder = useBuilder()\n const cms = useCms()\n const [pending, setPending] = useState<{ nodeId: string; defaultName: string } | null>(null)\n\n const manifests = useMemo(() => getStudioRegistry().registry.list(), [])\n // EC-467: the optional `reuse` declaration wins; absent, the heuristic runs\n // exactly as before. The config holder is static per studio boot, so it is read\n // inside the memo (like `useStudioNav` reads the sidebar holder). Resolution\n // ISSUES are surfaced once, by the shell rail — this provider is mounted per\n // composition field, so reporting here would repeat them per field.\n const capability = useMemo(\n () => resolveReuseCapability(manifests, cms.collections, resolveDeploymentReuse()),\n [manifests, cms.collections],\n )\n\n const canMakeReusable = useCallback(\n (nodeId: string): boolean => {\n if (!capability) return false\n const node = getNode(builder.present, nodeId)\n // Anything but the synthetic composition root can be promoted; nesting a\n // reusable inside a reusable is allowed (the renderer + bake are cycle-\n // guarded, EC-281), so an embed itself is still promotable.\n return node !== undefined && node.id !== builder.present.id\n },\n [capability, builder],\n )\n\n const canDetach = useCallback(\n (nodeId: string): boolean => {\n if (!capability) return false\n return getNode(builder.present, nodeId)?.componentId === capability.embedComponentId\n },\n [capability, builder],\n )\n\n const startMakeReusable = useCallback(\n (nodeId: string) => {\n if (!canMakeReusable(nodeId)) return\n const node = getNode(builder.present, nodeId)\n if (!node) return\n const manifestTitle = localizedLabel(manifests.find((m) => m.id === node.componentId)?.title) || undefined\n const collection = capability ? cms.getCollection(capability.collectionId) : undefined\n const defaultName =\n manifestTitle ?? (localizedLabel(collection?.form?.labelSingular) || localizedLabel(collection?.form?.label) || translate('builder.reuse.defaultElementName'))\n setPending({ nodeId, defaultName })\n },\n [canMakeReusable, builder, manifests, capability, cms],\n )\n\n const confirmMakeReusable = useCallback(\n (name: string, publish: boolean) => {\n const cap = capability\n if (!cap || !pending) return\n const node = getNode(builder.present, pending.nodeId)\n const collection = cms.getCollection(cap.collectionId)\n if (!node || !collection) {\n setPending(null)\n return\n }\n // Mint the reusable document, then write its title + content (createDocument\n // only seeds defaults). The new id is referenced by the embed below.\n const ref = cms.createDocument(cap.collectionId)\n const content = buildReusableValues(\n collection,\n cap,\n name,\n node,\n cms.documentsOf(cap.collectionId),\n cms.localeConfig,\n )\n cms.saveDocument({ collection: cap.collectionId, id: ref.id, ...content })\n // Replace the selection in place with a transcluding embed (the action\n // clones to a fresh id; the source `node` stays untouched in the new doc).\n builder.dispatch({ type: 'replace', id: pending.nodeId, nodes: [makeEmbedNode(cap, ref.id)] })\n setPending(null)\n // EC-300: the new reusable is a separate document with its own lifecycle. A\n // draft-only target resolves to nothing in the published perspective (delivery\n // + the published canvas view), so the editor chooses at creation: PUBLISH keeps\n // the section visible everywhere immediately (the continuity-preserving default);\n // SAVE DRAFT keeps it editable, visible in the draft canvas, surfaced as a\n // pending reference until published. No silent auto-publish (AD-16).\n if (publish) {\n void cms.publishDocument(ref).then(\n () => toast.success(translate('builder.reuse.madeReusablePublished', { name })),\n () => toast.error(translate('builder.reuse.madeReusablePublishFailed', { name })),\n )\n } else {\n toast.success(translate('builder.reuse.madeReusableDraft', { name }))\n }\n },\n [capability, pending, builder, cms],\n )\n\n const detach = useCallback(\n (nodeId: string) => {\n const cap = capability\n if (!cap) return\n const node = getNode(builder.present, nodeId)\n if (!node || node.componentId !== cap.embedComponentId) return\n const prop = node.props?.[cap.relationProp]\n const ref = isStaticProp(prop) ? readDocumentRef(prop.value) : undefined\n if (!ref) {\n toast.error(translate('builder.reuse.noTargetToDetach'))\n return\n }\n const target = cms.getDocument(ref)\n const blocks = blockRootsOf(target?.values?.[cap.contentField])\n if (blocks.length === 0) {\n toast.error(translate('builder.reuse.noContentToDetach'))\n return\n }\n builder.dispatch({ type: 'replace', id: nodeId, nodes: blocks })\n toast.success(translate('builder.reuse.detachedToCopy'))\n },\n [capability, builder, cms],\n )\n\n const value = useMemo<ReuseGestures>(\n () => ({ capability, canMakeReusable, canDetach, startMakeReusable, detach }),\n [capability, canMakeReusable, canDetach, startMakeReusable, detach],\n )\n\n return (\n <ReuseContext.Provider value={value}>\n {children}\n <MakeReusableDialog\n open={pending !== null}\n defaultName={pending?.defaultName ?? ''}\n onConfirm={confirmMakeReusable}\n onCancel={() => setPending(null)}\n />\n </ReuseContext.Provider>\n )\n}\n\n/** Name-and-create dialog for the \"Make reusable\" gesture. */\nfunction MakeReusableDialog({\n open,\n defaultName,\n onConfirm,\n onCancel,\n}: {\n open: boolean\n defaultName: string\n /** `publish` chooses the new reusable's initial state (EC-300/AD-16). */\n onConfirm: (name: string, publish: boolean) => void\n onCancel: () => void\n}) {\n const [name, setName] = useState(defaultName)\n // Re-seed the field each time the dialog opens for a new selection.\n const [seededFor, setSeededFor] = useState<string | null>(null)\n if (open && seededFor !== defaultName) {\n setName(defaultName)\n setSeededFor(defaultName)\n }\n\n const empty = name.trim() === ''\n function commit(publish: boolean) {\n if (empty) return\n onConfirm(name.trim(), publish)\n setSeededFor(null)\n }\n\n return (\n <Dialog\n open={open}\n onOpenChange={(next) => {\n if (!next) {\n onCancel()\n setSeededFor(null)\n }\n }}\n >\n <DialogContent>\n <DialogHeader>\n <DialogTitle>{translate('builder.reuse.makeReusable')}</DialogTitle>\n <DialogDescription>\n {translate('builder.reuse.makeReusableDescription')}\n </DialogDescription>\n </DialogHeader>\n {/* Enter submits the primary action (Publish) — the continuity-preserving default. */}\n <form\n onSubmit={(event: FormEvent) => {\n event.preventDefault()\n commit(true)\n }}\n >\n <Stack gap=\"3\">\n <Field label={translate('builder.reuse.nameLabel')} htmlFor=\"make-reusable-name\" required>\n <Input\n id=\"make-reusable-name\"\n value={name}\n onChange={(event) => setName(event.target.value)}\n placeholder={translate('builder.reuse.namePlaceholder')}\n autoFocus\n required\n />\n </Field>\n </Stack>\n <DialogFooter className=\"mt-4\">\n {/* Cancel pinned left; the two commit actions group right. */}\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" className=\"mr-auto\" onClick={onCancel}>\n {translate('common.cancel')}\n </Button>\n <Button\n type=\"button\"\n variant=\"secondary\"\n size=\"sm\"\n disabled={empty}\n onClick={() => commit(false)}\n data-testid=\"make-reusable-draft\"\n >\n {translate('builder.reuse.saveDraft')}\n </Button>\n <Button\n type=\"submit\"\n size=\"sm\"\n disabled={empty}\n data-testid=\"make-reusable-publish\"\n >\n {translate('builder.reuse.publish')}\n </Button>\n </DialogFooter>\n </form>\n </DialogContent>\n </Dialog>\n )\n}\n"]}
|
|
1
|
+
{"version":3,"file":"reuse-context.js","sourceRoot":"","sources":["../../../src/components/builder/reuse-context.tsx"],"names":[],"mappings":"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","sourcesContent":["import { createContext, useCallback, useContext, useMemo, useState } from 'react'\nimport type { FormEvent, ReactNode } from 'react'\nimport { blockRootsOf, getNode, isStaticProp } from '@elytracms/core/project-graph'\nimport { readDocumentRef } from '@elytracms/core/cms-core'\nimport {\n Button,\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n Field,\n Input,\n Stack,\n toast,\n} from '@elytracms/studio/ui'\nimport { getStudioRegistry } from '@elytracms/studio/lib/registry'\nimport { getStudioOperations } from '@elytracms/studio/lib/operations'\nimport { resolveDeploymentReuse } from '@elytracms/studio/lib/workspace'\nimport { localizedLabel, translate } from '@elytracms/studio/lib/i18n'\nimport { useCms } from '../cms/cms-store'\nimport { useProjectSession } from '../project-session'\nimport { useBuilder } from './builder-store'\nimport { detachRefusal, resolveReuseCapability, runPromote } from './reuse'\nimport type { ReuseCapability, ReuseRefusal } from './reuse'\nimport { placementRefusalMessage } from './placement-refusal'\n\n/**\n * In-canvas reuse gestures (EC-282): \"Make reusable\" promotes a selected block to\n * a new reusable document and replaces it in place with a transcluding embed;\n * \"Detach to local copy\" inlines an embed's referenced blocks so a page can\n * diverge. Both round-trip through the existing persistence + in-Convex reference\n * index (EC-277), so where-used is correct immediately.\n *\n * The provider needs BOTH the builder store (the edited composition) and the CMS\n * store (to create/read the reusable document), so it mounts inside the\n * composition-field editor's `BuilderProvider`, itself inside the CMS document\n * editor. Where no provider is mounted (or the capability is absent), the menu\n * reads `null` and simply omits the gestures.\n */\nexport interface ReuseGestures {\n capability: ReuseCapability | null\n canMakeReusable: (nodeId: string) => boolean\n canDetach: (nodeId: string) => boolean\n /** Open the name dialog for promoting `nodeId`. */\n startMakeReusable: (nodeId: string) => void\n /** Replace a `Reusable` embed with an editable inline copy of its target's blocks. */\n detach: (nodeId: string) => void\n}\n\nconst ReuseContext = createContext<ReuseGestures | null>(null)\n\nexport function useReuse(): ReuseGestures | null {\n return useContext(ReuseContext)\n}\n\n/** A refused reuse gesture's visible reason (EC-584) — the judge's verdict, worded once. */\nfunction toastRefusal(refusal: ReuseRefusal): void {\n toast.error(placementRefusalMessage(refusal.verdict, refusal.fieldLabel))\n}\n\nexport function ReuseProvider({ children }: { children: ReactNode }) {\n const builder = useBuilder()\n const cms = useCms()\n const { projectId } = useProjectSession()\n const [pending, setPending] = useState<{ nodeId: string; defaultName: string } | null>(null)\n\n const manifests = useMemo(() => getStudioRegistry().registry.list(), [])\n // EC-467: the optional `reuse` declaration wins; absent, the heuristic runs\n // exactly as before. The config holder is static per studio boot, so it is read\n // inside the memo (like `useStudioNav` reads the sidebar holder). Resolution\n // ISSUES are surfaced once, by the shell rail — this provider is mounted per\n // composition field, so reporting here would repeat them per field.\n const capability = useMemo(\n () => resolveReuseCapability(manifests, cms.collections, resolveDeploymentReuse()),\n [manifests, cms.collections],\n )\n\n const canMakeReusable = useCallback(\n (nodeId: string): boolean => {\n if (!capability) return false\n const node = getNode(builder.present, nodeId)\n // Anything but the synthetic composition root can be promoted; nesting a\n // reusable inside a reusable is allowed (the renderer + bake are cycle-\n // guarded, EC-281), so an embed itself is still promotable.\n return node !== undefined && node.id !== builder.present.id\n },\n [capability, builder],\n )\n\n const canDetach = useCallback(\n (nodeId: string): boolean => {\n if (!capability) return false\n return getNode(builder.present, nodeId)?.componentId === capability.embedComponentId\n },\n [capability, builder],\n )\n\n const startMakeReusable = useCallback(\n (nodeId: string) => {\n if (!canMakeReusable(nodeId)) return\n const node = getNode(builder.present, nodeId)\n if (!node) return\n const manifestTitle = localizedLabel(manifests.find((m) => m.id === node.componentId)?.title) || undefined\n const collection = capability ? cms.getCollection(capability.collectionId) : undefined\n const defaultName =\n manifestTitle ?? (localizedLabel(collection?.form?.labelSingular) || localizedLabel(collection?.form?.label) || translate('builder.reuse.defaultElementName'))\n setPending({ nodeId, defaultName })\n },\n [canMakeReusable, builder, manifests, capability, cms],\n )\n\n const confirmMakeReusable = useCallback(\n (name: string, publish: boolean) => {\n const cap = capability\n if (!cap || !pending) return\n const node = getNode(builder.present, pending.nodeId)\n const collection = cms.getCollection(cap.collectionId)\n setPending(null)\n if (!node || !collection) return\n // EC-584 / EC-560: the placement preflight runs FIRST — a refused promote creates,\n // publishes and replaces nothing. Then (EC-595 A2 / EC-578) ONE insert-if-absent\n // create with a factory id, the in-place swap for the transcluding embed (the\n // action clones to a fresh id; the source `node` stays untouched in the new doc),\n // and the chosen publish. EC-300: PUBLISH keeps the section visible everywhere\n // immediately (the continuity-preserving default); SAVE DRAFT keeps it editable,\n // surfaced as a pending reference until published. No silent auto-publish (AD-16).\n void runPromote({\n root: builder.present,\n nodeId: pending.nodeId,\n node,\n name,\n publish,\n capability: cap,\n collection,\n existing: cms.documentsOf(cap.collectionId),\n locales: cms.localeConfig,\n placement: builder.placement,\n canvasFieldLabel: builder.field.label,\n effects: {\n newDocumentId: () => cms.newDocumentId(),\n insertDocument: (doc) => cms.insertDocument(doc),\n replaceInCanvas: (nodeId, embed) => builder.dispatch({ type: 'replace', id: nodeId, nodes: [embed] }),\n // The store's publish reports a refusal through its error state, not the\n // promise — so success is the AUTHORITATIVE pointer after it settles (the\n // same read the store reconciles its badge from), never the resolution.\n publish: async (ref) => {\n await cms.publishDocument(ref)\n if (projectId === undefined) return false\n const { operations } = await getStudioOperations()\n const record = await operations.documents.get({ projectId, ref })\n return record.ok && record.value.publishedVersion !== undefined\n },\n refused: toastRefusal,\n madeDraft: (label) => toast.success(translate('builder.reuse.madeReusableDraft', { name: label })),\n madePublished: (label) => toast.success(translate('builder.reuse.madeReusablePublished', { name: label })),\n publishFailed: (label) => toast.error(translate('builder.reuse.madeReusablePublishFailed', { name: label })),\n },\n })\n },\n [capability, pending, builder, cms, projectId],\n )\n\n const detach = useCallback(\n (nodeId: string) => {\n const cap = capability\n if (!cap) return\n const node = getNode(builder.present, nodeId)\n if (!node || node.componentId !== cap.embedComponentId) return\n const prop = node.props?.[cap.relationProp]\n const ref = isStaticProp(prop) ? readDocumentRef(prop.value) : undefined\n if (!ref) {\n toast.error(translate('builder.reuse.noTargetToDetach'))\n return\n }\n const target = cms.getDocument(ref)\n const blocks = blockRootsOf(target?.values?.[cap.contentField])\n if (blocks.length === 0) {\n toast.error(translate('builder.reuse.noContentToDetach'))\n return\n }\n // EC-584 / EC-560: the inlined blocks land where the embed is — judged first, so\n // a refusal keeps the reference in place, says why, and dispatches nothing.\n const refusal = detachRefusal({\n root: builder.present,\n nodeId,\n blocks,\n placement: builder.placement,\n canvasFieldLabel: builder.field.label,\n })\n if (refusal) {\n toastRefusal(refusal)\n return\n }\n builder.dispatch({ type: 'replace', id: nodeId, nodes: blocks })\n toast.success(translate('builder.reuse.detachedToCopy'))\n },\n [capability, builder, cms],\n )\n\n const value = useMemo<ReuseGestures>(\n () => ({ capability, canMakeReusable, canDetach, startMakeReusable, detach }),\n [capability, canMakeReusable, canDetach, startMakeReusable, detach],\n )\n\n return (\n <ReuseContext.Provider value={value}>\n {children}\n <MakeReusableDialog\n open={pending !== null}\n defaultName={pending?.defaultName ?? ''}\n onConfirm={confirmMakeReusable}\n onCancel={() => setPending(null)}\n />\n </ReuseContext.Provider>\n )\n}\n\n/** Name-and-create dialog for the \"Make reusable\" gesture. */\nfunction MakeReusableDialog({\n open,\n defaultName,\n onConfirm,\n onCancel,\n}: {\n open: boolean\n defaultName: string\n /** `publish` chooses the new reusable's initial state (EC-300/AD-16). */\n onConfirm: (name: string, publish: boolean) => void\n onCancel: () => void\n}) {\n const [name, setName] = useState(defaultName)\n // Re-seed the field each time the dialog opens for a new selection.\n const [seededFor, setSeededFor] = useState<string | null>(null)\n if (open && seededFor !== defaultName) {\n setName(defaultName)\n setSeededFor(defaultName)\n }\n\n const empty = name.trim() === ''\n function commit(publish: boolean) {\n if (empty) return\n onConfirm(name.trim(), publish)\n setSeededFor(null)\n }\n\n return (\n <Dialog\n open={open}\n onOpenChange={(next) => {\n if (!next) {\n onCancel()\n setSeededFor(null)\n }\n }}\n >\n <DialogContent>\n <DialogHeader>\n <DialogTitle>{translate('builder.reuse.makeReusable')}</DialogTitle>\n <DialogDescription>\n {translate('builder.reuse.makeReusableDescription')}\n </DialogDescription>\n </DialogHeader>\n {/* Enter submits the primary action (Publish) — the continuity-preserving default. */}\n <form\n onSubmit={(event: FormEvent) => {\n event.preventDefault()\n commit(true)\n }}\n >\n <Stack gap=\"3\">\n <Field label={translate('builder.reuse.nameLabel')} htmlFor=\"make-reusable-name\" required>\n <Input\n id=\"make-reusable-name\"\n value={name}\n onChange={(event) => setName(event.target.value)}\n placeholder={translate('builder.reuse.namePlaceholder')}\n autoFocus\n required\n />\n </Field>\n </Stack>\n <DialogFooter className=\"mt-4\">\n {/* Cancel pinned left; the two commit actions group right. */}\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" className=\"mr-auto\" onClick={onCancel}>\n {translate('common.cancel')}\n </Button>\n <Button\n type=\"button\"\n variant=\"secondary\"\n size=\"sm\"\n disabled={empty}\n onClick={() => commit(false)}\n data-testid=\"make-reusable-draft\"\n >\n {translate('builder.reuse.saveDraft')}\n </Button>\n <Button\n type=\"submit\"\n size=\"sm\"\n disabled={empty}\n data-testid=\"make-reusable-publish\"\n >\n {translate('builder.reuse.publish')}\n </Button>\n </DialogFooter>\n </form>\n </DialogContent>\n </Dialog>\n )\n}\n"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ComponentManifest } from '@elytracms/core/component-registry';
|
|
2
2
|
import type { CmsDocument, CollectionDef, DocumentRef, LocaleConfig } from '@elytracms/core/cms-core';
|
|
3
|
-
import type { ComponentNode } from '@elytracms/core/project-graph';
|
|
3
|
+
import type { ComponentNode, PlacementScope, PlacementVerdict } from '@elytracms/core/project-graph';
|
|
4
4
|
import type { CustomizationIssue } from '@elytracms/core/studio-core';
|
|
5
5
|
/**
|
|
6
6
|
* The reuse capability (EC-282): the engine-generic description of how a project
|
|
@@ -155,12 +155,83 @@ export declare function buildReusableValues(collection: CollectionDef, capabilit
|
|
|
155
155
|
* whose target doesn't resolve (→ placeholder) without reaching into prop internals.
|
|
156
156
|
*/
|
|
157
157
|
export declare function reuseEmbedRef(node: ComponentNode, capability: ReuseCapability): DocumentRef | null;
|
|
158
|
-
/**
|
|
159
|
-
* Every reuse-embed target ref in a composition `blocks` value, at any depth
|
|
160
|
-
* (EC-300) — the hard references a publish-integrity check walks to find
|
|
161
|
-
* unpublished targets. Recurses slots; order is document order. Duplicates are
|
|
162
|
-
* kept (the caller dedupes by key if needed).
|
|
163
|
-
*/
|
|
164
|
-
export declare function collectEmbedRefs(blocks: readonly ComponentNode[], capability: ReuseCapability): DocumentRef[];
|
|
165
158
|
/** The `Reusable` embed node referencing the new document (id minted by the action). */
|
|
166
159
|
export declare function makeEmbedNode(capability: ReuseCapability, documentId: string): ComponentNode;
|
|
160
|
+
/**
|
|
161
|
+
* Why a reuse gesture is refused before it does anything: the one core placement
|
|
162
|
+
* judge's verdict, and the label of the FIELD it names when the refusal is at a
|
|
163
|
+
* field's top level (the canvas field, or the reusable collection's content field).
|
|
164
|
+
*/
|
|
165
|
+
export interface ReuseRefusal {
|
|
166
|
+
verdict: PlacementVerdict;
|
|
167
|
+
fieldLabel: string;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* EC-584 / EC-560 — "Make reusable" preflight, run BEFORE any side effect (the
|
|
171
|
+
* reusable document is created — and maybe published — before the canvas swaps the
|
|
172
|
+
* block for the embed). Two placements, both judged by the one core judge the
|
|
173
|
+
* builder reducer enforces:
|
|
174
|
+
*
|
|
175
|
+
* 1. the EMBED lands where the promoted block was (the canvas field's `allow` at the
|
|
176
|
+
* root, or the slot's `allow` when the block sits in a slot);
|
|
177
|
+
* 2. the promoted BLOCK lands at the top level of the reusable collection's content
|
|
178
|
+
* field (that field's effective vocabulary, `fieldAllowList`).
|
|
179
|
+
*
|
|
180
|
+
* `null` when both are admitted. The reducer still enforces (1) on its own.
|
|
181
|
+
*/
|
|
182
|
+
export declare function promoteRefusal(args: {
|
|
183
|
+
root: ComponentNode;
|
|
184
|
+
nodeId: string;
|
|
185
|
+
node: ComponentNode;
|
|
186
|
+
capability: ReuseCapability;
|
|
187
|
+
collection: CollectionDef;
|
|
188
|
+
placement: PlacementScope;
|
|
189
|
+
canvasFieldLabel: string;
|
|
190
|
+
}): ReuseRefusal | null;
|
|
191
|
+
/**
|
|
192
|
+
* EC-584 / EC-560 — "Detach to local copy" preflight: the reusable's content blocks
|
|
193
|
+
* land where the embed was. Refusing the WHOLE gesture when one inlined block is
|
|
194
|
+
* outside the vocabulary keeps the reference in place (EC-560's frame: refuse
|
|
195
|
+
* visibly, dispatch nothing) — the editor sees why and decides.
|
|
196
|
+
*/
|
|
197
|
+
export declare function detachRefusal(args: {
|
|
198
|
+
root: ComponentNode;
|
|
199
|
+
nodeId: string;
|
|
200
|
+
blocks: readonly ComponentNode[];
|
|
201
|
+
placement: PlacementScope;
|
|
202
|
+
canvasFieldLabel: string;
|
|
203
|
+
}): ReuseRefusal | null;
|
|
204
|
+
/** The effects "Make reusable" performs — injected, so the order and the refusal are testable. */
|
|
205
|
+
export interface PromoteEffects {
|
|
206
|
+
newDocumentId(): string;
|
|
207
|
+
insertDocument(doc: CmsDocument): void;
|
|
208
|
+
replaceInCanvas(nodeId: string, embed: ComponentNode): void;
|
|
209
|
+
/** Publish the new reusable; resolves `true` only when the document IS published afterwards. */
|
|
210
|
+
publish(ref: DocumentRef): Promise<boolean>;
|
|
211
|
+
refused(refusal: ReuseRefusal): void;
|
|
212
|
+
madeDraft(name: string): void;
|
|
213
|
+
madePublished(name: string): void;
|
|
214
|
+
publishFailed(name: string): void;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Run "Make reusable" (EC-282) — the preflight first (EC-584: a refusal creates,
|
|
218
|
+
* publishes and replaces NOTHING), then ONE insert-if-absent create of the reusable
|
|
219
|
+
* document (EC-595 A2), the in-place swap for the embed, and the chosen publish,
|
|
220
|
+
* whose success notice is shown only when the document is actually published.
|
|
221
|
+
*/
|
|
222
|
+
export declare function runPromote(args: {
|
|
223
|
+
root: ComponentNode;
|
|
224
|
+
nodeId: string;
|
|
225
|
+
node: ComponentNode;
|
|
226
|
+
name: string;
|
|
227
|
+
publish: boolean;
|
|
228
|
+
capability: ReuseCapability;
|
|
229
|
+
collection: CollectionDef;
|
|
230
|
+
existing: readonly {
|
|
231
|
+
values?: Record<string, unknown>;
|
|
232
|
+
}[];
|
|
233
|
+
locales: LocaleConfig;
|
|
234
|
+
placement: PlacementScope;
|
|
235
|
+
canvasFieldLabel: string;
|
|
236
|
+
effects: PromoteEffects;
|
|
237
|
+
}): Promise<'refused' | 'draft' | 'published' | 'publish-failed'>;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { manifestPropFields, manifestSlots } from '@elytracms/core/component-registry';
|
|
2
|
-
import { itemLimit, readDocumentRef, seedDefaults, titleFieldOf, writeField } from '@elytracms/core/cms-core';
|
|
3
|
-
import { editBlockRoots, isStaticProp } from '@elytracms/core/project-graph';
|
|
2
|
+
import { fieldAllowList, itemLimit, readDocumentRef, referenceNode, seedDefaults, titleFieldOf, writeField, } from '@elytracms/core/cms-core';
|
|
3
|
+
import { editBlockRoots, isStaticProp, replacePlacementRefusal, valuePlacementRefusal } from '@elytracms/core/project-graph';
|
|
4
4
|
import { slugify } from '@elytracms/studio/lib/utils';
|
|
5
|
+
import { fieldLabel } from '../cms/cms-labels.js';
|
|
5
6
|
/** The customization point name stamped onto every reuse issue (EC-236 locator). */
|
|
6
7
|
const REUSE_POINT = 'reuse';
|
|
7
8
|
/** Every `relation` prop of a manifest, in declaration order. */
|
|
@@ -284,26 +285,6 @@ export function reuseEmbedRef(node, capability) {
|
|
|
284
285
|
const ref = isStaticProp(prop) ? readDocumentRef(prop.value) : undefined;
|
|
285
286
|
return ref ?? null;
|
|
286
287
|
}
|
|
287
|
-
/**
|
|
288
|
-
* Every reuse-embed target ref in a composition `blocks` value, at any depth
|
|
289
|
-
* (EC-300) — the hard references a publish-integrity check walks to find
|
|
290
|
-
* unpublished targets. Recurses slots; order is document order. Duplicates are
|
|
291
|
-
* kept (the caller dedupes by key if needed).
|
|
292
|
-
*/
|
|
293
|
-
export function collectEmbedRefs(blocks, capability) {
|
|
294
|
-
const refs = [];
|
|
295
|
-
const visit = (node) => {
|
|
296
|
-
const ref = reuseEmbedRef(node, capability);
|
|
297
|
-
if (ref)
|
|
298
|
-
refs.push(ref);
|
|
299
|
-
for (const children of Object.values(node.slots ?? {}))
|
|
300
|
-
for (const child of children)
|
|
301
|
-
visit(child);
|
|
302
|
-
};
|
|
303
|
-
for (const node of blocks)
|
|
304
|
-
visit(node);
|
|
305
|
-
return refs;
|
|
306
|
-
}
|
|
307
288
|
/** The `Reusable` embed node referencing the new document (id minted by the action). */
|
|
308
289
|
export function makeEmbedNode(capability, documentId) {
|
|
309
290
|
return {
|
|
@@ -312,9 +293,76 @@ export function makeEmbedNode(capability, documentId) {
|
|
|
312
293
|
props: {
|
|
313
294
|
[capability.relationProp]: {
|
|
314
295
|
kind: 'static',
|
|
315
|
-
|
|
296
|
+
// EC-589 / EC-586: a reference is a typed envelope from the first write.
|
|
297
|
+
value: referenceNode({ collection: capability.collectionId, id: documentId }),
|
|
316
298
|
},
|
|
317
299
|
},
|
|
318
300
|
};
|
|
319
301
|
}
|
|
302
|
+
/**
|
|
303
|
+
* EC-584 / EC-560 — "Make reusable" preflight, run BEFORE any side effect (the
|
|
304
|
+
* reusable document is created — and maybe published — before the canvas swaps the
|
|
305
|
+
* block for the embed). Two placements, both judged by the one core judge the
|
|
306
|
+
* builder reducer enforces:
|
|
307
|
+
*
|
|
308
|
+
* 1. the EMBED lands where the promoted block was (the canvas field's `allow` at the
|
|
309
|
+
* root, or the slot's `allow` when the block sits in a slot);
|
|
310
|
+
* 2. the promoted BLOCK lands at the top level of the reusable collection's content
|
|
311
|
+
* field (that field's effective vocabulary, `fieldAllowList`).
|
|
312
|
+
*
|
|
313
|
+
* `null` when both are admitted. The reducer still enforces (1) on its own.
|
|
314
|
+
*/
|
|
315
|
+
export function promoteRefusal(args) {
|
|
316
|
+
const { root, nodeId, node, capability, collection, placement, canvasFieldLabel } = args;
|
|
317
|
+
const embed = replacePlacementRefusal(root, nodeId, [makeEmbedNode(capability, 'preflight')], placement);
|
|
318
|
+
if (embed)
|
|
319
|
+
return { verdict: embed, fieldLabel: canvasFieldLabel };
|
|
320
|
+
const contentField = collection.fields.find((field) => field.name === capability.contentField);
|
|
321
|
+
const contentAllow = contentField ? fieldAllowList(contentField) : undefined;
|
|
322
|
+
if (contentField && contentAllow) {
|
|
323
|
+
const into = valuePlacementRefusal([node], { components: placement.components, fieldAllow: contentAllow });
|
|
324
|
+
if (into)
|
|
325
|
+
return { verdict: into, fieldLabel: fieldLabel(contentField) };
|
|
326
|
+
}
|
|
327
|
+
return null;
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* EC-584 / EC-560 — "Detach to local copy" preflight: the reusable's content blocks
|
|
331
|
+
* land where the embed was. Refusing the WHOLE gesture when one inlined block is
|
|
332
|
+
* outside the vocabulary keeps the reference in place (EC-560's frame: refuse
|
|
333
|
+
* visibly, dispatch nothing) — the editor sees why and decides.
|
|
334
|
+
*/
|
|
335
|
+
export function detachRefusal(args) {
|
|
336
|
+
const verdict = replacePlacementRefusal(args.root, args.nodeId, args.blocks, args.placement);
|
|
337
|
+
return verdict ? { verdict, fieldLabel: args.canvasFieldLabel } : null;
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Run "Make reusable" (EC-282) — the preflight first (EC-584: a refusal creates,
|
|
341
|
+
* publishes and replaces NOTHING), then ONE insert-if-absent create of the reusable
|
|
342
|
+
* document (EC-595 A2), the in-place swap for the embed, and the chosen publish,
|
|
343
|
+
* whose success notice is shown only when the document is actually published.
|
|
344
|
+
*/
|
|
345
|
+
export async function runPromote(args) {
|
|
346
|
+
const { root, nodeId, node, name, capability, collection, placement, canvasFieldLabel, effects } = args;
|
|
347
|
+
const refusal = promoteRefusal({ root, nodeId, node, capability, collection, placement, canvasFieldLabel });
|
|
348
|
+
if (refusal) {
|
|
349
|
+
effects.refused(refusal);
|
|
350
|
+
return 'refused';
|
|
351
|
+
}
|
|
352
|
+
const ref = { collection: capability.collectionId, id: effects.newDocumentId() };
|
|
353
|
+
const content = buildReusableValues(collection, capability, name, node, args.existing, args.locales);
|
|
354
|
+
effects.insertDocument({ ...ref, ...content });
|
|
355
|
+
effects.replaceInCanvas(nodeId, makeEmbedNode(capability, ref.id));
|
|
356
|
+
if (!args.publish) {
|
|
357
|
+
effects.madeDraft(name);
|
|
358
|
+
return 'draft';
|
|
359
|
+
}
|
|
360
|
+
const published = await effects.publish(ref).catch(() => false);
|
|
361
|
+
if (published) {
|
|
362
|
+
effects.madePublished(name);
|
|
363
|
+
return 'published';
|
|
364
|
+
}
|
|
365
|
+
effects.publishFailed(name);
|
|
366
|
+
return 'publish-failed';
|
|
367
|
+
}
|
|
320
368
|
//# sourceMappingURL=reuse.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reuse.js","sourceRoot":"","sources":["../../../src/components/builder/reuse.ts"],"names":[],"mappings":"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","sourcesContent":["import { manifestPropFields, manifestSlots } from '@elytracms/core/component-registry'\nimport type { ComponentManifest } from '@elytracms/core/component-registry'\nimport { itemLimit, readDocumentRef, seedDefaults, titleFieldOf, writeField } from '@elytracms/core/cms-core'\nimport type { CmsDocument, CollectionDef, DocumentRef, LocaleConfig } from '@elytracms/core/cms-core'\nimport { editBlockRoots, isStaticProp } from '@elytracms/core/project-graph'\nimport type { ComponentNode } from '@elytracms/core/project-graph'\nimport type { CustomizationIssue, CustomizationMessageRef } from '@elytracms/core/studio-core'\nimport { slugify } from '@elytracms/studio/lib/utils'\n\n/**\n * The reuse capability (EC-282): the engine-generic description of how a project\n * models reusable elements. **AD-a (EC-467): reusable elements are SINGULAR per\n * project** — exactly one embed component and exactly one reusable collection.\n * Multiple reuse collections are deliberately not supported, which is why this is\n * one value and never a list.\n *\n * The capability is normally DERIVED from config (manifests + collections) — no\n * declaration needed, so a consumer that already has a transcluding embed gets the\n * in-canvas gestures for free (the EC-296 \"works automatically\" pattern). The\n * derivation predicate for a \"reusable embed\" component: it contributes no slots of\n * its own and has exactly ONE `relation` prop (cardinality `one`) whose target\n * collection holds a `blocks` field — i.e. it embeds a single document and renders\n * that document's composition by transclusion (EC-254/EC-281). For Konditorei that\n * is uniquely `project.Reusable` → `element` → `reusable.content`.\n *\n * EC-462 — the predicate additionally DISQUALIFIES a candidate whose own manifest\n * explicitly declares its relation prop to be something other than an embed\n * pointer. The shape test alone proved unsound the moment a relation prop's\n * target collection gains a `blocks` field of its own (a category/page with a\n * canvas — a completely natural content shape): the EC-276 sprint's ProductGrid\n * and the demo's OptionBox were both claimed as \"the embed\", so their ordinary\n * relation picks rendered the reusable-element placeholder, grew Detach/Make-\n * reusable gestures, and fed embed-worded publish-integrity issues. Two\n * explicit-declaration disqualifiers close the observed class:\n *\n * - a manifest with a `listing` declaration is a QUERY host (EC-255) — its\n * relation prop feeds the query (a `{ prop }` operand of `listing.where`),\n * never a transclusion pointer;\n * - a relation prop with `form.hidden` (predicate or `true`) is conditional/\n * auxiliary — EC-394 strips a hidden relation's ref at publish, so it cannot\n * coherently be what the component transcludes.\n *\n * Both read only the CANDIDATE manifest's own declarations (never the target's\n * contents) and can only EXCLUDE, so a genuine pure embed (which declares\n * neither) is always kept. EC-462's residual — a bare unconditional\n * single-relation display component (e.g. a page teaser) is at rest\n * indistinguishable from a genuine embed — is closed by EC-467's optional\n * {@link StudioReuseConfig} declaration: a project that hits ambiguity declares\n * its way out, and the ambiguity is LOUD instead of silently disabling the\n * gestures. See {@link resolveReuse}.\n */\nexport interface ReuseCapability {\n /** The embed component, e.g. `project.Reusable`. */\n embedComponentId: string\n /** The embed's relation prop that points at the reusable document, e.g. `element`. */\n relationProp: string\n /** The collection reusable documents live in, e.g. `reusable`. */\n collectionId: string\n /** The collection's composition field the embed transcludes, e.g. `content`. */\n contentField: string\n /** Whether that field holds a single block or a list. */\n contentCardinality: 'one' | 'many'\n}\n\n/**\n * EC-467 (AD-b) — the OPTIONAL studio-config declaration of the reuse embed:\n * `defineStudio({ reuse: { component: 'project.Reusable' } })`. See\n * {@link import('../app-views/studio-config').StudioConfig.reuse} for the\n * consumer-facing contract; this is the shape {@link resolveReuse} consumes.\n *\n * One identifier is enough because the rest DERIVES from it (its single-document\n * relation prop → that prop's target collection → that collection's `blocks`\n * field), and because reusable elements are singular per project (AD-a).\n */\nexport interface StudioReuseConfig {\n /** The embed component's registry id, e.g. `'project.Reusable'`. */\n component: string\n /**\n * Which relation prop points at the reusable document. Needed ONLY when the\n * declared component legitimately has more than one single-document relation\n * prop — otherwise the sole one is derived.\n */\n relationProp?: string\n}\n\n/**\n * EC-467 — the resolution of the project's reuse capability: the capability (or\n * `null`), plus the config issues a developer must SEE.\n *\n * The issues ride the EC-236 {@link CustomizationIssue} surface (`point: 'reuse'`),\n * exactly like the sidebar's and the field renderers' — so they render at the\n * studio rail foot on every surface, which is the studio's home for\n * project-CONFIGURATION problems (the document-scoped Issues pane is the wrong\n * home: this is not a content problem in one document).\n *\n * EC-478: each issue carries a `messageRef` (catalog key + identifier values)\n * alongside its prebuilt English, so the rail foot renders it in the active UI\n * locale. This module stays pure — it names a key, it never calls `translate`;\n * `lib/persona/labels.ts` resolves it at the render site. That is what makes the\n * whole list localize together, which is the mixed-language outcome EC-467\n * declined to create by localizing only its own half.\n */\nexport interface ReuseResolution {\n capability: ReuseCapability | null\n /** Empty whenever the capability is unambiguous — including \"this project has none\". */\n issues: CustomizationIssue[]\n}\n\n/** The customization point name stamped onto every reuse issue (EC-236 locator). */\nconst REUSE_POINT = 'reuse'\n\n/** A `relation` prop of a manifest, normalized for both resolution paths. */\ninterface RelationProp {\n name: string\n /** The target collection id; `undefined` when the manifest declares none. */\n target: string | undefined\n multiple: boolean\n /** EC-462: `form.hidden` present and not literally `false` (predicate or `true`). */\n conditional: boolean\n}\n\n/** Every `relation` prop of a manifest, in declaration order. */\nfunction relationPropsOf(manifest: ComponentManifest): RelationProp[] {\n const props: RelationProp[] = []\n for (const field of manifestPropFields(manifest)) {\n if (field.type !== 'relation') continue\n const hidden = field.form?.hidden\n props.push({\n name: field.name,\n target: field.target,\n multiple: field.multiple === true,\n conditional: hidden !== undefined && hidden !== false,\n })\n }\n return props\n}\n\n/**\n * The relation props a DECLARED embed could plausibly transclude through: targeted\n * and cardinality-one. A cardinality-`many` (or targetless) relation is excluded\n * structurally, not heuristically — an embed transcludes exactly one document and\n * {@link makeEmbedNode} writes exactly one `{collection,id}`, which a list-valued\n * or target-less prop cannot hold.\n */\nfunction embeddableRelationProps(manifest: ComponentManifest): TargetedRelationProp[] {\n return relationPropsOf(manifest).filter(\n (r): r is TargetedRelationProp => r.target !== undefined && !r.multiple,\n )\n}\ntype TargetedRelationProp = RelationProp & { target: string }\n\n/** The collection's first composition field — what an embed of it transcludes. */\nfunction blocksFieldOf(collection: CollectionDef) {\n const field = collection.fields.find((f) => f.type === 'blocks')\n return field?.type === 'blocks' ? field : undefined\n}\n\n/**\n * The catalog keys for the reuse config issues (EC-478), in the `builder.reuse.config.*`\n * namespace. Named here so the producer and the catalog-parity test agree on one list.\n */\nconst REUSE_KEYS = {\n unknownComponent: 'builder.reuse.config.unknownComponent',\n noRelationProp: 'builder.reuse.config.noRelationProp',\n unknownRelationProp: 'builder.reuse.config.unknownRelationProp',\n ambiguousRelationProp: 'builder.reuse.config.ambiguousRelationProp',\n unknownCollection: 'builder.reuse.config.unknownCollection',\n noBlocksField: 'builder.reuse.config.noBlocksField',\n ambiguousInference: 'builder.reuse.config.ambiguousInference',\n} as const\n\nfunction reuseIssue(\n code: CustomizationIssue['code'],\n severity: CustomizationIssue['severity'],\n message: string,\n messageRef: CustomizationMessageRef,\n id?: string,\n): CustomizationIssue {\n return {\n code,\n severity,\n message,\n messageRef,\n point: REUSE_POINT,\n ...(id === undefined ? {} : { id }),\n }\n}\n\n/** `\"a\", \"b\"` — a readable list of prop/component ids for an issue message. */\nfunction quotedList(names: readonly string[]): string {\n return names.map((name) => `\"${name}\"`).join(', ')\n}\n\n/**\n * The components whose SHAPE makes them plausible reuse embeds (the zero-config\n * heuristic + EC-462's disqualifiers), in manifest order. Exported for the tests\n * and for issue copy; the resolution rule lives in {@link resolveReuse}.\n */\nexport function inferredEmbedCandidates(\n manifests: readonly ComponentManifest[],\n collections: readonly CollectionDef[],\n): ReuseCapability[] {\n const matches: ReuseCapability[] = []\n for (const manifest of manifests) {\n // EC-591 (COMPOSITION #15): the DERIVED slots — a `blocks` prop is a child\n // region (`manifestSlots`), so a manifest with `slots: []` and a blocks prop is\n // NOT slot-less; reading raw `manifest.slots` inferred it as a reuse embed.\n if (manifestSlots(manifest).length > 0) continue\n // EC-462: a `listing` manifest is a query host by its own declaration — its\n // relation prop is the filter source, never an embed pointer.\n if (manifest.listing) continue\n // The heuristic requires the relation to be UNAMBIGUOUS within the manifest:\n // exactly ONE relation prop of any kind, which must then be targeted,\n // cardinality-one and unconditional. (Counting all relation props — not just\n // the embeddable ones — is deliberate: a component with a second, list-valued\n // relation is not the single-purpose shape the heuristic is allowed to claim.)\n const relations = relationPropsOf(manifest)\n const relation = relations.length === 1 ? relations[0]! : undefined\n if (!relation || relation.target === undefined || relation.multiple || relation.conditional)\n continue\n const collection = collections.find((c) => c.id === relation.target)\n if (!collection) continue\n const blocksField = blocksFieldOf(collection)\n if (!blocksField) continue\n matches.push({\n embedComponentId: manifest.id,\n relationProp: relation.name,\n collectionId: collection.id,\n contentField: blocksField.name,\n contentCardinality: itemLimit(blocksField) === 1 ? 'one' : 'many',\n })\n }\n return matches\n}\n\n/**\n * Resolve the DECLARED embed (EC-467, AD-b). An explicit declaration is the\n * developer's authority, so the inference disqualifiers are NOT applied here: a\n * declared component may have slots, may declare `listing`, and its relation prop\n * may be `form.hidden` — those exist to stop the engine GUESSING, not to overrule\n * a stated intent. Only structural impossibilities are issues (see AD-c row 2):\n * an unknown component id, no usable relation prop (or an ambiguous set with no\n * `relationProp`), an undefined target collection, or a target with no `blocks`\n * field — each of which would leave nothing coherent to transclude.\n */\nfunction resolveDeclaredReuse(\n declaration: StudioReuseConfig,\n manifests: readonly ComponentManifest[],\n collections: readonly CollectionDef[],\n): ReuseResolution {\n const { component, relationProp } = declaration\n const fail = (message: string, messageRef: CustomizationMessageRef): ReuseResolution => ({\n capability: null,\n issues: [reuseIssue('invalid-override', 'error', message, messageRef, component)],\n })\n\n const manifest = manifests.find((m) => m.id === component)\n if (!manifest) {\n return {\n capability: null,\n issues: [\n reuseIssue(\n 'unknown-override-target',\n 'error',\n `Reusable elements: studio config declares reuse.component \"${component}\", which is not a registered component. Fix the id, or remove the reuse block to let the studio infer the embed.`,\n { key: REUSE_KEYS.unknownComponent, vars: { component } },\n component,\n ),\n ],\n }\n }\n\n const relations = embeddableRelationProps(manifest)\n if (relations.length === 0) {\n return fail(\n `Reusable elements: the declared embed \"${component}\" has no single-document relation prop, so it cannot point at a reusable document. An embed needs one relation prop with cardinality one.`,\n { key: REUSE_KEYS.noRelationProp, vars: { component } },\n )\n }\n const names = relations.map((r) => r.name)\n let relation: TargetedRelationProp\n if (relationProp !== undefined) {\n const named = relations.find((r) => r.name === relationProp)\n if (!named) {\n return fail(\n `Reusable elements: the declared embed \"${component}\" has no single-document relation prop named \"${relationProp}\". Its relation props are: ${quotedList(names)}.`,\n {\n key: REUSE_KEYS.unknownRelationProp,\n vars: { component, relationProp, props: quotedList(names) },\n },\n )\n }\n relation = named\n } else if (relations.length === 1) {\n relation = relations[0]!\n } else {\n return fail(\n `Reusable elements: the declared embed \"${component}\" has ${relations.length} single-document relation props (${quotedList(names)}) — add reuse.relationProp to say which one points at the reusable document.`,\n {\n key: REUSE_KEYS.ambiguousRelationProp,\n vars: { component, count: relations.length, props: quotedList(names) },\n },\n )\n }\n\n const collection = collections.find((c) => c.id === relation.target)\n if (!collection) {\n return fail(\n `Reusable elements: \"${component}.${relation.name}\" targets the collection \"${relation.target}\", which this project does not define.`,\n {\n key: REUSE_KEYS.unknownCollection,\n vars: { component, relationProp: relation.name, collection: relation.target },\n },\n )\n }\n const blocksField = blocksFieldOf(collection)\n if (!blocksField) {\n return fail(\n `Reusable elements: the collection \"${collection.id}\" behind \"${component}.${relation.name}\" has no blocks (composition) field, so there is nothing for the embed to transclude.`,\n {\n key: REUSE_KEYS.noBlocksField,\n vars: { component, relationProp: relation.name, collection: collection.id },\n },\n )\n }\n\n return {\n capability: {\n embedComponentId: manifest.id,\n relationProp: relation.name,\n collectionId: collection.id,\n contentField: blocksField.name,\n contentCardinality: itemLimit(blocksField) === 1 ? 'one' : 'many',\n },\n issues: [],\n }\n}\n\n/**\n * Resolve the project's ONE reuse capability (AD-a) plus any config issues —\n * EC-467's full matrix (AD-c):\n *\n * | Declaration | Candidates | Result |\n * | --- | --- | --- |\n * | present, resolves | any | the declared capability, no issue |\n * | present, does not resolve | any | `null` + an ERROR issue (misconfiguration) |\n * | absent | exactly 1 | that candidate, no issue (the zero-config default) |\n * | absent | 2 or more | `null` + a WARNING issue naming every candidate |\n * | absent | 0 | `null`, **no issue** — a project need not use reusable elements |\n *\n * Severity is the honest split: a declaration that cannot resolve is broken\n * config the developer asked for (error); inference ambiguity is nothing broken —\n * the engine declines to guess and asks for one line (warning). Both disable the\n * gestures, and both render identically in the rail's issue list.\n *\n * The zero-candidate row is deliberately silent: crying wolf at every project\n * that has no reusable elements would train developers to ignore the surface.\n */\nexport function resolveReuse(\n manifests: readonly ComponentManifest[],\n collections: readonly CollectionDef[],\n declaration?: StudioReuseConfig,\n): ReuseResolution {\n if (declaration) return resolveDeclaredReuse(declaration, manifests, collections)\n\n const candidates = inferredEmbedCandidates(manifests, collections)\n if (candidates.length === 1) return { capability: candidates[0]!, issues: [] }\n if (candidates.length === 0) return { capability: null, issues: [] }\n return {\n capability: null,\n issues: [\n reuseIssue(\n 'ambiguous-inference',\n 'warning',\n `Reusable elements: ${candidates.length} components have the reusable-embed shape (${quotedList(\n candidates.map((c) => c.embedComponentId),\n )}). A project has exactly one reusable-element embed, so the studio will not guess — declare it in studio.config as reuse: { component: '<id>' }. Until then the reuse gestures are off.`,\n {\n key: REUSE_KEYS.ambiguousInference,\n vars: {\n count: candidates.length,\n components: quotedList(candidates.map((c) => c.embedComponentId)),\n },\n },\n ),\n ],\n }\n}\n\n/**\n * The project's reuse capability alone, or `null` when it is absent, ambiguous, or\n * misdeclared — for callers that act on the capability and do not surface config\n * issues themselves (the publish-integrity walk). The issues are surfaced ONCE, by\n * the shell rail; see {@link resolveReuse}.\n */\nexport function resolveReuseCapability(\n manifests: readonly ComponentManifest[],\n collections: readonly CollectionDef[],\n declaration?: StudioReuseConfig,\n): ReuseCapability | null {\n return resolveReuse(manifests, collections, declaration).capability\n}\n\n/** A slug for `name`, suffixed `-2`/`-3`/… until it is free among `taken`. */\nexport function uniqueSlug(name: string, taken: ReadonlySet<string>): string {\n const base = slugify(name) || 'element'\n let id = base\n for (let n = 2; taken.has(id); n += 1) id = `${base}-${n}`\n return id\n}\n\n/**\n * The values for a new reusable document promoted from `node` (EC-282): seeded\n * defaults, the given `name` on the title field, the node on the content field,\n * and a collision-safe slug on any UNIQUE text field (e.g. Konditorei's\n * `internalId`) so the created document is publish-valid without hardcoding that\n * field's name. Other required fields, if any, are completed by the editor when\n * they open the new reusable.\n *\n * EC-580: every write goes through core's `writeField` in the project's DEFAULT\n * locale — the new document is created in its default bucket, so a localized title\n * or content field lands under `localized[default]`, never on the shared bag.\n */\nexport function buildReusableValues(\n collection: CollectionDef,\n capability: ReuseCapability,\n name: string,\n node: ComponentNode,\n existing: readonly { values?: Record<string, unknown> }[],\n locales: LocaleConfig,\n): Pick<CmsDocument, 'values' | 'localized'> {\n const at = locales.default\n let doc: CmsDocument = { collection: collection.id, id: '', ...seedDefaults(collection, locales) }\n const titleField = titleFieldOf(collection)?.name\n if (titleField) doc = writeField(collection, doc, titleField, at, name)\n for (const field of collection.fields) {\n if (\n field.type === 'text' &&\n field.validation?.unique === true &&\n field.name !== titleField &&\n field.name !== capability.contentField\n ) {\n const taken = new Set(\n existing\n .map((d) => d.values?.[field.name])\n .filter((v): v is string => typeof v === 'string'),\n )\n doc = writeField(collection, doc, field.name, at, uniqueSlug(name, taken))\n }\n }\n // EC-576: the converged v2 sequence holding the tagged node, through the core\n // write path — never the pre-0.0.84 bare node/array. Cardinality is a\n // constraint over that one shape, not a second shape (EC-500 D2).\n doc = writeField(collection, doc, capability.contentField, at, editBlockRoots(undefined, () => [node]))\n return { values: doc.values, ...(doc.localized !== undefined ? { localized: doc.localized } : {}) }\n}\n\n/**\n * The document a reuse-embed `node` targets (EC-300), or null when `node` is not the\n * embed component or has no static pick yet. Lets the canvas detect a HARD reference\n * whose target doesn't resolve (→ placeholder) without reaching into prop internals.\n */\nexport function reuseEmbedRef(node: ComponentNode, capability: ReuseCapability): DocumentRef | null {\n if (node.componentId !== capability.embedComponentId) return null\n const prop = node.props?.[capability.relationProp]\n const ref = isStaticProp(prop) ? readDocumentRef(prop.value) : undefined\n return ref ?? null\n}\n\n/**\n * Every reuse-embed target ref in a composition `blocks` value, at any depth\n * (EC-300) — the hard references a publish-integrity check walks to find\n * unpublished targets. Recurses slots; order is document order. Duplicates are\n * kept (the caller dedupes by key if needed).\n */\nexport function collectEmbedRefs(\n blocks: readonly ComponentNode[],\n capability: ReuseCapability,\n): DocumentRef[] {\n const refs: DocumentRef[] = []\n const visit = (node: ComponentNode): void => {\n const ref = reuseEmbedRef(node, capability)\n if (ref) refs.push(ref)\n for (const children of Object.values(node.slots ?? {})) for (const child of children) visit(child)\n }\n for (const node of blocks) visit(node)\n return refs\n}\n\n/** The `Reusable` embed node referencing the new document (id minted by the action). */\nexport function makeEmbedNode(capability: ReuseCapability, documentId: string): ComponentNode {\n return {\n id: 'reuse-embed',\n componentId: capability.embedComponentId,\n props: {\n [capability.relationProp]: {\n kind: 'static',\n value: { collection: capability.collectionId, id: documentId },\n },\n },\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"reuse.js","sourceRoot":"","sources":["../../../src/components/builder/reuse.ts"],"names":[],"mappings":"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","sourcesContent":["import { manifestPropFields, manifestSlots } from '@elytracms/core/component-registry'\nimport type { ComponentManifest } from '@elytracms/core/component-registry'\nimport {\n fieldAllowList,\n itemLimit,\n readDocumentRef,\n referenceNode,\n seedDefaults,\n titleFieldOf,\n writeField,\n} from '@elytracms/core/cms-core'\nimport type { CmsDocument, CollectionDef, DocumentRef, LocaleConfig } from '@elytracms/core/cms-core'\nimport { editBlockRoots, isStaticProp, replacePlacementRefusal, valuePlacementRefusal } from '@elytracms/core/project-graph'\nimport type { ComponentNode, PlacementScope, PlacementVerdict } from '@elytracms/core/project-graph'\nimport type { CustomizationIssue, CustomizationMessageRef } from '@elytracms/core/studio-core'\nimport { slugify } from '@elytracms/studio/lib/utils'\nimport { fieldLabel } from '../cms/cms-labels'\n\n/**\n * The reuse capability (EC-282): the engine-generic description of how a project\n * models reusable elements. **AD-a (EC-467): reusable elements are SINGULAR per\n * project** — exactly one embed component and exactly one reusable collection.\n * Multiple reuse collections are deliberately not supported, which is why this is\n * one value and never a list.\n *\n * The capability is normally DERIVED from config (manifests + collections) — no\n * declaration needed, so a consumer that already has a transcluding embed gets the\n * in-canvas gestures for free (the EC-296 \"works automatically\" pattern). The\n * derivation predicate for a \"reusable embed\" component: it contributes no slots of\n * its own and has exactly ONE `relation` prop (cardinality `one`) whose target\n * collection holds a `blocks` field — i.e. it embeds a single document and renders\n * that document's composition by transclusion (EC-254/EC-281). For Konditorei that\n * is uniquely `project.Reusable` → `element` → `reusable.content`.\n *\n * EC-462 — the predicate additionally DISQUALIFIES a candidate whose own manifest\n * explicitly declares its relation prop to be something other than an embed\n * pointer. The shape test alone proved unsound the moment a relation prop's\n * target collection gains a `blocks` field of its own (a category/page with a\n * canvas — a completely natural content shape): the EC-276 sprint's ProductGrid\n * and the demo's OptionBox were both claimed as \"the embed\", so their ordinary\n * relation picks rendered the reusable-element placeholder, grew Detach/Make-\n * reusable gestures, and fed embed-worded publish-integrity issues. Two\n * explicit-declaration disqualifiers close the observed class:\n *\n * - a manifest with a `listing` declaration is a QUERY host (EC-255) — its\n * relation prop feeds the query (a `{ prop }` operand of `listing.where`),\n * never a transclusion pointer;\n * - a relation prop with `form.hidden` (predicate or `true`) is conditional/\n * auxiliary — EC-394 strips a hidden relation's ref at publish, so it cannot\n * coherently be what the component transcludes.\n *\n * Both read only the CANDIDATE manifest's own declarations (never the target's\n * contents) and can only EXCLUDE, so a genuine pure embed (which declares\n * neither) is always kept. EC-462's residual — a bare unconditional\n * single-relation display component (e.g. a page teaser) is at rest\n * indistinguishable from a genuine embed — is closed by EC-467's optional\n * {@link StudioReuseConfig} declaration: a project that hits ambiguity declares\n * its way out, and the ambiguity is LOUD instead of silently disabling the\n * gestures. See {@link resolveReuse}.\n */\nexport interface ReuseCapability {\n /** The embed component, e.g. `project.Reusable`. */\n embedComponentId: string\n /** The embed's relation prop that points at the reusable document, e.g. `element`. */\n relationProp: string\n /** The collection reusable documents live in, e.g. `reusable`. */\n collectionId: string\n /** The collection's composition field the embed transcludes, e.g. `content`. */\n contentField: string\n /** Whether that field holds a single block or a list. */\n contentCardinality: 'one' | 'many'\n}\n\n/**\n * EC-467 (AD-b) — the OPTIONAL studio-config declaration of the reuse embed:\n * `defineStudio({ reuse: { component: 'project.Reusable' } })`. See\n * {@link import('../app-views/studio-config').StudioConfig.reuse} for the\n * consumer-facing contract; this is the shape {@link resolveReuse} consumes.\n *\n * One identifier is enough because the rest DERIVES from it (its single-document\n * relation prop → that prop's target collection → that collection's `blocks`\n * field), and because reusable elements are singular per project (AD-a).\n */\nexport interface StudioReuseConfig {\n /** The embed component's registry id, e.g. `'project.Reusable'`. */\n component: string\n /**\n * Which relation prop points at the reusable document. Needed ONLY when the\n * declared component legitimately has more than one single-document relation\n * prop — otherwise the sole one is derived.\n */\n relationProp?: string\n}\n\n/**\n * EC-467 — the resolution of the project's reuse capability: the capability (or\n * `null`), plus the config issues a developer must SEE.\n *\n * The issues ride the EC-236 {@link CustomizationIssue} surface (`point: 'reuse'`),\n * exactly like the sidebar's and the field renderers' — so they render at the\n * studio rail foot on every surface, which is the studio's home for\n * project-CONFIGURATION problems (the document-scoped Issues pane is the wrong\n * home: this is not a content problem in one document).\n *\n * EC-478: each issue carries a `messageRef` (catalog key + identifier values)\n * alongside its prebuilt English, so the rail foot renders it in the active UI\n * locale. This module stays pure — it names a key, it never calls `translate`;\n * `lib/persona/labels.ts` resolves it at the render site. That is what makes the\n * whole list localize together, which is the mixed-language outcome EC-467\n * declined to create by localizing only its own half.\n */\nexport interface ReuseResolution {\n capability: ReuseCapability | null\n /** Empty whenever the capability is unambiguous — including \"this project has none\". */\n issues: CustomizationIssue[]\n}\n\n/** The customization point name stamped onto every reuse issue (EC-236 locator). */\nconst REUSE_POINT = 'reuse'\n\n/** A `relation` prop of a manifest, normalized for both resolution paths. */\ninterface RelationProp {\n name: string\n /** The target collection id; `undefined` when the manifest declares none. */\n target: string | undefined\n multiple: boolean\n /** EC-462: `form.hidden` present and not literally `false` (predicate or `true`). */\n conditional: boolean\n}\n\n/** Every `relation` prop of a manifest, in declaration order. */\nfunction relationPropsOf(manifest: ComponentManifest): RelationProp[] {\n const props: RelationProp[] = []\n for (const field of manifestPropFields(manifest)) {\n if (field.type !== 'relation') continue\n const hidden = field.form?.hidden\n props.push({\n name: field.name,\n target: field.target,\n multiple: field.multiple === true,\n conditional: hidden !== undefined && hidden !== false,\n })\n }\n return props\n}\n\n/**\n * The relation props a DECLARED embed could plausibly transclude through: targeted\n * and cardinality-one. A cardinality-`many` (or targetless) relation is excluded\n * structurally, not heuristically — an embed transcludes exactly one document and\n * {@link makeEmbedNode} writes exactly one `{collection,id}`, which a list-valued\n * or target-less prop cannot hold.\n */\nfunction embeddableRelationProps(manifest: ComponentManifest): TargetedRelationProp[] {\n return relationPropsOf(manifest).filter(\n (r): r is TargetedRelationProp => r.target !== undefined && !r.multiple,\n )\n}\ntype TargetedRelationProp = RelationProp & { target: string }\n\n/** The collection's first composition field — what an embed of it transcludes. */\nfunction blocksFieldOf(collection: CollectionDef) {\n const field = collection.fields.find((f) => f.type === 'blocks')\n return field?.type === 'blocks' ? field : undefined\n}\n\n/**\n * The catalog keys for the reuse config issues (EC-478), in the `builder.reuse.config.*`\n * namespace. Named here so the producer and the catalog-parity test agree on one list.\n */\nconst REUSE_KEYS = {\n unknownComponent: 'builder.reuse.config.unknownComponent',\n noRelationProp: 'builder.reuse.config.noRelationProp',\n unknownRelationProp: 'builder.reuse.config.unknownRelationProp',\n ambiguousRelationProp: 'builder.reuse.config.ambiguousRelationProp',\n unknownCollection: 'builder.reuse.config.unknownCollection',\n noBlocksField: 'builder.reuse.config.noBlocksField',\n ambiguousInference: 'builder.reuse.config.ambiguousInference',\n} as const\n\nfunction reuseIssue(\n code: CustomizationIssue['code'],\n severity: CustomizationIssue['severity'],\n message: string,\n messageRef: CustomizationMessageRef,\n id?: string,\n): CustomizationIssue {\n return {\n code,\n severity,\n message,\n messageRef,\n point: REUSE_POINT,\n ...(id === undefined ? {} : { id }),\n }\n}\n\n/** `\"a\", \"b\"` — a readable list of prop/component ids for an issue message. */\nfunction quotedList(names: readonly string[]): string {\n return names.map((name) => `\"${name}\"`).join(', ')\n}\n\n/**\n * The components whose SHAPE makes them plausible reuse embeds (the zero-config\n * heuristic + EC-462's disqualifiers), in manifest order. Exported for the tests\n * and for issue copy; the resolution rule lives in {@link resolveReuse}.\n */\nexport function inferredEmbedCandidates(\n manifests: readonly ComponentManifest[],\n collections: readonly CollectionDef[],\n): ReuseCapability[] {\n const matches: ReuseCapability[] = []\n for (const manifest of manifests) {\n // EC-591 (COMPOSITION #15): the DERIVED slots — a `blocks` prop is a child\n // region (`manifestSlots`), so a manifest with `slots: []` and a blocks prop is\n // NOT slot-less; reading raw `manifest.slots` inferred it as a reuse embed.\n if (manifestSlots(manifest).length > 0) continue\n // EC-462: a `listing` manifest is a query host by its own declaration — its\n // relation prop is the filter source, never an embed pointer.\n if (manifest.listing) continue\n // The heuristic requires the relation to be UNAMBIGUOUS within the manifest:\n // exactly ONE relation prop of any kind, which must then be targeted,\n // cardinality-one and unconditional. (Counting all relation props — not just\n // the embeddable ones — is deliberate: a component with a second, list-valued\n // relation is not the single-purpose shape the heuristic is allowed to claim.)\n const relations = relationPropsOf(manifest)\n const relation = relations.length === 1 ? relations[0]! : undefined\n if (!relation || relation.target === undefined || relation.multiple || relation.conditional)\n continue\n const collection = collections.find((c) => c.id === relation.target)\n if (!collection) continue\n const blocksField = blocksFieldOf(collection)\n if (!blocksField) continue\n matches.push({\n embedComponentId: manifest.id,\n relationProp: relation.name,\n collectionId: collection.id,\n contentField: blocksField.name,\n contentCardinality: itemLimit(blocksField) === 1 ? 'one' : 'many',\n })\n }\n return matches\n}\n\n/**\n * Resolve the DECLARED embed (EC-467, AD-b). An explicit declaration is the\n * developer's authority, so the inference disqualifiers are NOT applied here: a\n * declared component may have slots, may declare `listing`, and its relation prop\n * may be `form.hidden` — those exist to stop the engine GUESSING, not to overrule\n * a stated intent. Only structural impossibilities are issues (see AD-c row 2):\n * an unknown component id, no usable relation prop (or an ambiguous set with no\n * `relationProp`), an undefined target collection, or a target with no `blocks`\n * field — each of which would leave nothing coherent to transclude.\n */\nfunction resolveDeclaredReuse(\n declaration: StudioReuseConfig,\n manifests: readonly ComponentManifest[],\n collections: readonly CollectionDef[],\n): ReuseResolution {\n const { component, relationProp } = declaration\n const fail = (message: string, messageRef: CustomizationMessageRef): ReuseResolution => ({\n capability: null,\n issues: [reuseIssue('invalid-override', 'error', message, messageRef, component)],\n })\n\n const manifest = manifests.find((m) => m.id === component)\n if (!manifest) {\n return {\n capability: null,\n issues: [\n reuseIssue(\n 'unknown-override-target',\n 'error',\n `Reusable elements: studio config declares reuse.component \"${component}\", which is not a registered component. Fix the id, or remove the reuse block to let the studio infer the embed.`,\n { key: REUSE_KEYS.unknownComponent, vars: { component } },\n component,\n ),\n ],\n }\n }\n\n const relations = embeddableRelationProps(manifest)\n if (relations.length === 0) {\n return fail(\n `Reusable elements: the declared embed \"${component}\" has no single-document relation prop, so it cannot point at a reusable document. An embed needs one relation prop with cardinality one.`,\n { key: REUSE_KEYS.noRelationProp, vars: { component } },\n )\n }\n const names = relations.map((r) => r.name)\n let relation: TargetedRelationProp\n if (relationProp !== undefined) {\n const named = relations.find((r) => r.name === relationProp)\n if (!named) {\n return fail(\n `Reusable elements: the declared embed \"${component}\" has no single-document relation prop named \"${relationProp}\". Its relation props are: ${quotedList(names)}.`,\n {\n key: REUSE_KEYS.unknownRelationProp,\n vars: { component, relationProp, props: quotedList(names) },\n },\n )\n }\n relation = named\n } else if (relations.length === 1) {\n relation = relations[0]!\n } else {\n return fail(\n `Reusable elements: the declared embed \"${component}\" has ${relations.length} single-document relation props (${quotedList(names)}) — add reuse.relationProp to say which one points at the reusable document.`,\n {\n key: REUSE_KEYS.ambiguousRelationProp,\n vars: { component, count: relations.length, props: quotedList(names) },\n },\n )\n }\n\n const collection = collections.find((c) => c.id === relation.target)\n if (!collection) {\n return fail(\n `Reusable elements: \"${component}.${relation.name}\" targets the collection \"${relation.target}\", which this project does not define.`,\n {\n key: REUSE_KEYS.unknownCollection,\n vars: { component, relationProp: relation.name, collection: relation.target },\n },\n )\n }\n const blocksField = blocksFieldOf(collection)\n if (!blocksField) {\n return fail(\n `Reusable elements: the collection \"${collection.id}\" behind \"${component}.${relation.name}\" has no blocks (composition) field, so there is nothing for the embed to transclude.`,\n {\n key: REUSE_KEYS.noBlocksField,\n vars: { component, relationProp: relation.name, collection: collection.id },\n },\n )\n }\n\n return {\n capability: {\n embedComponentId: manifest.id,\n relationProp: relation.name,\n collectionId: collection.id,\n contentField: blocksField.name,\n contentCardinality: itemLimit(blocksField) === 1 ? 'one' : 'many',\n },\n issues: [],\n }\n}\n\n/**\n * Resolve the project's ONE reuse capability (AD-a) plus any config issues —\n * EC-467's full matrix (AD-c):\n *\n * | Declaration | Candidates | Result |\n * | --- | --- | --- |\n * | present, resolves | any | the declared capability, no issue |\n * | present, does not resolve | any | `null` + an ERROR issue (misconfiguration) |\n * | absent | exactly 1 | that candidate, no issue (the zero-config default) |\n * | absent | 2 or more | `null` + a WARNING issue naming every candidate |\n * | absent | 0 | `null`, **no issue** — a project need not use reusable elements |\n *\n * Severity is the honest split: a declaration that cannot resolve is broken\n * config the developer asked for (error); inference ambiguity is nothing broken —\n * the engine declines to guess and asks for one line (warning). Both disable the\n * gestures, and both render identically in the rail's issue list.\n *\n * The zero-candidate row is deliberately silent: crying wolf at every project\n * that has no reusable elements would train developers to ignore the surface.\n */\nexport function resolveReuse(\n manifests: readonly ComponentManifest[],\n collections: readonly CollectionDef[],\n declaration?: StudioReuseConfig,\n): ReuseResolution {\n if (declaration) return resolveDeclaredReuse(declaration, manifests, collections)\n\n const candidates = inferredEmbedCandidates(manifests, collections)\n if (candidates.length === 1) return { capability: candidates[0]!, issues: [] }\n if (candidates.length === 0) return { capability: null, issues: [] }\n return {\n capability: null,\n issues: [\n reuseIssue(\n 'ambiguous-inference',\n 'warning',\n `Reusable elements: ${candidates.length} components have the reusable-embed shape (${quotedList(\n candidates.map((c) => c.embedComponentId),\n )}). A project has exactly one reusable-element embed, so the studio will not guess — declare it in studio.config as reuse: { component: '<id>' }. Until then the reuse gestures are off.`,\n {\n key: REUSE_KEYS.ambiguousInference,\n vars: {\n count: candidates.length,\n components: quotedList(candidates.map((c) => c.embedComponentId)),\n },\n },\n ),\n ],\n }\n}\n\n/**\n * The project's reuse capability alone, or `null` when it is absent, ambiguous, or\n * misdeclared — for callers that act on the capability and do not surface config\n * issues themselves (the publish-integrity walk). The issues are surfaced ONCE, by\n * the shell rail; see {@link resolveReuse}.\n */\nexport function resolveReuseCapability(\n manifests: readonly ComponentManifest[],\n collections: readonly CollectionDef[],\n declaration?: StudioReuseConfig,\n): ReuseCapability | null {\n return resolveReuse(manifests, collections, declaration).capability\n}\n\n/** A slug for `name`, suffixed `-2`/`-3`/… until it is free among `taken`. */\nexport function uniqueSlug(name: string, taken: ReadonlySet<string>): string {\n const base = slugify(name) || 'element'\n let id = base\n for (let n = 2; taken.has(id); n += 1) id = `${base}-${n}`\n return id\n}\n\n/**\n * The values for a new reusable document promoted from `node` (EC-282): seeded\n * defaults, the given `name` on the title field, the node on the content field,\n * and a collision-safe slug on any UNIQUE text field (e.g. Konditorei's\n * `internalId`) so the created document is publish-valid without hardcoding that\n * field's name. Other required fields, if any, are completed by the editor when\n * they open the new reusable.\n *\n * EC-580: every write goes through core's `writeField` in the project's DEFAULT\n * locale — the new document is created in its default bucket, so a localized title\n * or content field lands under `localized[default]`, never on the shared bag.\n */\nexport function buildReusableValues(\n collection: CollectionDef,\n capability: ReuseCapability,\n name: string,\n node: ComponentNode,\n existing: readonly { values?: Record<string, unknown> }[],\n locales: LocaleConfig,\n): Pick<CmsDocument, 'values' | 'localized'> {\n const at = locales.default\n let doc: CmsDocument = { collection: collection.id, id: '', ...seedDefaults(collection, locales) }\n const titleField = titleFieldOf(collection)?.name\n if (titleField) doc = writeField(collection, doc, titleField, at, name)\n for (const field of collection.fields) {\n if (\n field.type === 'text' &&\n field.validation?.unique === true &&\n field.name !== titleField &&\n field.name !== capability.contentField\n ) {\n const taken = new Set(\n existing\n .map((d) => d.values?.[field.name])\n .filter((v): v is string => typeof v === 'string'),\n )\n doc = writeField(collection, doc, field.name, at, uniqueSlug(name, taken))\n }\n }\n // EC-576: the converged v2 sequence holding the tagged node, through the core\n // write path — never the pre-0.0.84 bare node/array. Cardinality is a\n // constraint over that one shape, not a second shape (EC-500 D2).\n doc = writeField(collection, doc, capability.contentField, at, editBlockRoots(undefined, () => [node]))\n return { values: doc.values, ...(doc.localized !== undefined ? { localized: doc.localized } : {}) }\n}\n\n/**\n * The document a reuse-embed `node` targets (EC-300), or null when `node` is not the\n * embed component or has no static pick yet. Lets the canvas detect a HARD reference\n * whose target doesn't resolve (→ placeholder) without reaching into prop internals.\n */\nexport function reuseEmbedRef(node: ComponentNode, capability: ReuseCapability): DocumentRef | null {\n if (node.componentId !== capability.embedComponentId) return null\n const prop = node.props?.[capability.relationProp]\n const ref = isStaticProp(prop) ? readDocumentRef(prop.value) : undefined\n return ref ?? null\n}\n\n/** The `Reusable` embed node referencing the new document (id minted by the action). */\nexport function makeEmbedNode(capability: ReuseCapability, documentId: string): ComponentNode {\n return {\n id: 'reuse-embed',\n componentId: capability.embedComponentId,\n props: {\n [capability.relationProp]: {\n kind: 'static',\n // EC-589 / EC-586: a reference is a typed envelope from the first write.\n value: referenceNode({ collection: capability.collectionId, id: documentId }),\n },\n },\n }\n}\n\n// --- The placement preflight (EC-584, closes EC-560) --------------------------------\n\n/**\n * Why a reuse gesture is refused before it does anything: the one core placement\n * judge's verdict, and the label of the FIELD it names when the refusal is at a\n * field's top level (the canvas field, or the reusable collection's content field).\n */\nexport interface ReuseRefusal {\n verdict: PlacementVerdict\n fieldLabel: string\n}\n\n/**\n * EC-584 / EC-560 — \"Make reusable\" preflight, run BEFORE any side effect (the\n * reusable document is created — and maybe published — before the canvas swaps the\n * block for the embed). Two placements, both judged by the one core judge the\n * builder reducer enforces:\n *\n * 1. the EMBED lands where the promoted block was (the canvas field's `allow` at the\n * root, or the slot's `allow` when the block sits in a slot);\n * 2. the promoted BLOCK lands at the top level of the reusable collection's content\n * field (that field's effective vocabulary, `fieldAllowList`).\n *\n * `null` when both are admitted. The reducer still enforces (1) on its own.\n */\nexport function promoteRefusal(args: {\n root: ComponentNode\n nodeId: string\n node: ComponentNode\n capability: ReuseCapability\n collection: CollectionDef\n placement: PlacementScope\n canvasFieldLabel: string\n}): ReuseRefusal | null {\n const { root, nodeId, node, capability, collection, placement, canvasFieldLabel } = args\n const embed = replacePlacementRefusal(root, nodeId, [makeEmbedNode(capability, 'preflight')], placement)\n if (embed) return { verdict: embed, fieldLabel: canvasFieldLabel }\n const contentField = collection.fields.find((field) => field.name === capability.contentField)\n const contentAllow = contentField ? fieldAllowList(contentField) : undefined\n if (contentField && contentAllow) {\n const into = valuePlacementRefusal([node], { components: placement.components, fieldAllow: contentAllow })\n if (into) return { verdict: into, fieldLabel: fieldLabel(contentField) }\n }\n return null\n}\n\n/**\n * EC-584 / EC-560 — \"Detach to local copy\" preflight: the reusable's content blocks\n * land where the embed was. Refusing the WHOLE gesture when one inlined block is\n * outside the vocabulary keeps the reference in place (EC-560's frame: refuse\n * visibly, dispatch nothing) — the editor sees why and decides.\n */\nexport function detachRefusal(args: {\n root: ComponentNode\n nodeId: string\n blocks: readonly ComponentNode[]\n placement: PlacementScope\n canvasFieldLabel: string\n}): ReuseRefusal | null {\n const verdict = replacePlacementRefusal(args.root, args.nodeId, args.blocks, args.placement)\n return verdict ? { verdict, fieldLabel: args.canvasFieldLabel } : null\n}\n\n/** The effects \"Make reusable\" performs — injected, so the order and the refusal are testable. */\nexport interface PromoteEffects {\n newDocumentId(): string\n insertDocument(doc: CmsDocument): void\n replaceInCanvas(nodeId: string, embed: ComponentNode): void\n /** Publish the new reusable; resolves `true` only when the document IS published afterwards. */\n publish(ref: DocumentRef): Promise<boolean>\n refused(refusal: ReuseRefusal): void\n madeDraft(name: string): void\n madePublished(name: string): void\n publishFailed(name: string): void\n}\n\n/**\n * Run \"Make reusable\" (EC-282) — the preflight first (EC-584: a refusal creates,\n * publishes and replaces NOTHING), then ONE insert-if-absent create of the reusable\n * document (EC-595 A2), the in-place swap for the embed, and the chosen publish,\n * whose success notice is shown only when the document is actually published.\n */\nexport async function runPromote(args: {\n root: ComponentNode\n nodeId: string\n node: ComponentNode\n name: string\n publish: boolean\n capability: ReuseCapability\n collection: CollectionDef\n existing: readonly { values?: Record<string, unknown> }[]\n locales: LocaleConfig\n placement: PlacementScope\n canvasFieldLabel: string\n effects: PromoteEffects\n}): Promise<'refused' | 'draft' | 'published' | 'publish-failed'> {\n const { root, nodeId, node, name, capability, collection, placement, canvasFieldLabel, effects } = args\n const refusal = promoteRefusal({ root, nodeId, node, capability, collection, placement, canvasFieldLabel })\n if (refusal) {\n effects.refused(refusal)\n return 'refused'\n }\n const ref = { collection: capability.collectionId, id: effects.newDocumentId() }\n const content = buildReusableValues(collection, capability, name, node, args.existing, args.locales)\n effects.insertDocument({ ...ref, ...content })\n effects.replaceInCanvas(nodeId, makeEmbedNode(capability, ref.id))\n if (!args.publish) {\n effects.madeDraft(name)\n return 'draft'\n }\n const published = await effects.publish(ref).catch(() => false)\n if (published) {\n effects.madePublished(name)\n return 'published'\n }\n effects.publishFailed(name)\n return 'publish-failed'\n}\n"]}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The inspector for a selected typography RUN (EC-549, owner-ratified 4d) — loose prose
|
|
3
|
+
* a `blocks` value legitimately carries (a surface switch, a transfer, an AI write).
|
|
4
|
+
* Its text is edited HERE, in the rich-text editor (the v1 inline-editing deferral);
|
|
5
|
+
* the canvas never creates loose prose and reordering it is not a v1 gesture. Every
|
|
6
|
+
* edit and the delete are store actions on the preserved list (`setRun` /
|
|
7
|
+
* `removeRun`), so undo restores them exactly.
|
|
8
|
+
*/
|
|
9
|
+
export declare function RunInspector({ run: key }: {
|
|
10
|
+
run: number;
|
|
11
|
+
}): import("react").JSX.Element | null;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo } from 'react';
|
|
3
|
+
import { Pilcrow, Trash2 } from 'lucide-react';
|
|
4
|
+
import { DEFAULT_SLOT, allowListIds } from '@elytracms/core/project-graph';
|
|
5
|
+
import { Button, Code, PanelHeader, Spacer, Stack, Text } from '@elytracms/studio/ui';
|
|
6
|
+
import { translate } from '@elytracms/studio/lib/i18n';
|
|
7
|
+
import { RichTextEditor } from '../cms/rich-text/index.js';
|
|
8
|
+
import { useBuilder } from './builder-store.js';
|
|
9
|
+
import { runByKey, runValue } from './canvas-runs.js';
|
|
10
|
+
/**
|
|
11
|
+
* The inspector for a selected typography RUN (EC-549, owner-ratified 4d) — loose prose
|
|
12
|
+
* a `blocks` value legitimately carries (a surface switch, a transfer, an AI write).
|
|
13
|
+
* Its text is edited HERE, in the rich-text editor (the v1 inline-editing deferral);
|
|
14
|
+
* the canvas never creates loose prose and reordering it is not a v1 gesture. Every
|
|
15
|
+
* edit and the delete are store actions on the preserved list (`setRun` /
|
|
16
|
+
* `removeRun`), so undo restores them exactly.
|
|
17
|
+
*/
|
|
18
|
+
export function RunInspector({ run: key }) {
|
|
19
|
+
const builder = useBuilder();
|
|
20
|
+
const rootCount = builder.present.slots?.[DEFAULT_SLOT]?.length ?? 0;
|
|
21
|
+
const run = runByKey(rootCount, builder.preserved, key);
|
|
22
|
+
const value = useMemo(() => (run ? runValue(builder.preserved, run) : undefined), [builder.preserved, run]);
|
|
23
|
+
if (!run || !value)
|
|
24
|
+
return null;
|
|
25
|
+
const dispatch = builder.dispatch;
|
|
26
|
+
return (_jsxs("div", { className: "flex h-full min-h-0 flex-col", "data-testid": "run-inspector", children: [_jsxs(PanelHeader, { children: [_jsx(Pilcrow, { "aria-hidden": true, className: "size-3.5 text-muted-foreground" }), _jsx(Text, { size: "sm", weight: "semibold", children: translate('builder.run.title') }), _jsx(Spacer, {}), _jsx(Code, { tone: "muted", children: translate('builder.run.items', { count: run.members.length }) })] }), _jsx("div", { className: "min-h-0 flex-1 overflow-auto p-3", children: _jsxs(Stack, { gap: "3", children: [_jsx(Text, { size: "xs", tone: "muted", children: translate('builder.run.hint') }), _jsx(RichTextEditor, { value: value, label: translate('builder.run.title'), allow: allowListIds(builder.field.allow), onChange: (next) => {
|
|
27
|
+
const content = next && typeof next === 'object' && Array.isArray(next.content)
|
|
28
|
+
? (next.content)
|
|
29
|
+
: [];
|
|
30
|
+
dispatch({ type: 'setRun', run: key, content, coalesceKey: `run:${key}` });
|
|
31
|
+
} }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => dispatch({ type: 'removeRun', run: key }), "data-testid": "run-delete", children: [_jsx(Trash2, {}), " ", translate('builder.run.delete')] })] }) })] }));
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=run-inspector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-inspector.js","sourceRoot":"","sources":["../../../src/components/builder/run-inspector.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useMemo } from 'react'\nimport { Pilcrow, Trash2 } from 'lucide-react'\nimport { DEFAULT_SLOT, allowListIds } from '@elytracms/core/project-graph'\nimport { Button, Code, PanelHeader, Spacer, Stack, Text } from '@elytracms/studio/ui'\nimport { translate } from '@elytracms/studio/lib/i18n'\nimport { RichTextEditor } from '../cms/rich-text'\nimport { useBuilder } from './builder-store'\nimport { runByKey, runValue } from './canvas-runs'\n\n/**\n * The inspector for a selected typography RUN (EC-549, owner-ratified 4d) — loose prose\n * a `blocks` value legitimately carries (a surface switch, a transfer, an AI write).\n * Its text is edited HERE, in the rich-text editor (the v1 inline-editing deferral);\n * the canvas never creates loose prose and reordering it is not a v1 gesture. Every\n * edit and the delete are store actions on the preserved list (`setRun` /\n * `removeRun`), so undo restores them exactly.\n */\nexport function RunInspector({ run: key }: { run: number }) {\n const builder = useBuilder()\n const rootCount = builder.present.slots?.[DEFAULT_SLOT]?.length ?? 0\n const run = runByKey(rootCount, builder.preserved, key)\n const value = useMemo(() => (run ? runValue(builder.preserved, run) : undefined), [builder.preserved, run])\n if (!run || !value) return null\n const dispatch = builder.dispatch\n return (\n <div className=\"flex h-full min-h-0 flex-col\" data-testid=\"run-inspector\">\n <PanelHeader>\n <Pilcrow aria-hidden className=\"size-3.5 text-muted-foreground\" />\n <Text size=\"sm\" weight=\"semibold\">\n {translate('builder.run.title')}\n </Text>\n <Spacer />\n <Code tone=\"muted\">{translate('builder.run.items', { count: run.members.length })}</Code>\n </PanelHeader>\n <div className=\"min-h-0 flex-1 overflow-auto p-3\">\n <Stack gap=\"3\">\n <Text size=\"xs\" tone=\"muted\">\n {translate('builder.run.hint')}\n </Text>\n <RichTextEditor\n value={value}\n label={translate('builder.run.title')}\n allow={allowListIds(builder.field.allow) as string[] | undefined}\n onChange={(next) => {\n const content =\n next && typeof next === 'object' && Array.isArray((next as { content?: unknown }).content)\n ? ((next as { content: unknown[] }).content)\n : []\n dispatch({ type: 'setRun', run: key, content, coalesceKey: `run:${key}` })\n }}\n />\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => dispatch({ type: 'removeRun', run: key })}\n data-testid=\"run-delete\"\n >\n <Trash2 /> {translate('builder.run.delete')}\n </Button>\n </Stack>\n </div>\n </div>\n )\n}\n"]}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import type { AssetFieldDef, FieldDef } from '@elytracms/core/cms-core';
|
|
3
|
+
import type { AssetRecord } from '@elytracms/core/persistence';
|
|
2
4
|
/**
|
|
3
5
|
* The asset-field family (EC-152 + EC-248 + EC-301) — split from
|
|
4
6
|
* `field-control.tsx` (EC-330): the {@link AssetField} control plus its
|
|
@@ -21,3 +23,38 @@ export declare function AssetField({ field, value: stored, onChange, readOnly, }
|
|
|
21
23
|
onChange: (value: unknown) => void;
|
|
22
24
|
readOnly: boolean;
|
|
23
25
|
}): import("react").JSX.Element;
|
|
26
|
+
/** A placement's own form (EC-588 2i): the asset field's per-use sub-fields and their value. */
|
|
27
|
+
export interface PlacementForm {
|
|
28
|
+
fields: readonly FieldDef[];
|
|
29
|
+
value: Record<string, unknown>;
|
|
30
|
+
onChange: (value: Record<string, unknown>) => void;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* The two sections of the image options dialog (EC-588 2i), stateless — exported so a
|
|
34
|
+
* node smoke render can pin the split (the dialog itself portals, `null` in node).
|
|
35
|
+
*/
|
|
36
|
+
export declare function AssetOptionsSections({ record, readOnly, metaFields, meta, onMetaField, focal, onFocalCommit, onFocalPreview, placement, whereUsed, replaceAction, replaced, }: {
|
|
37
|
+
record: AssetRecord | undefined;
|
|
38
|
+
readOnly: boolean;
|
|
39
|
+
metaFields: readonly FieldDef[];
|
|
40
|
+
meta: Record<string, unknown>;
|
|
41
|
+
onMetaField: (name: string, value: unknown) => void;
|
|
42
|
+
focal: {
|
|
43
|
+
x: number;
|
|
44
|
+
y: number;
|
|
45
|
+
} | undefined;
|
|
46
|
+
onFocalCommit: (point: {
|
|
47
|
+
x: number;
|
|
48
|
+
y: number;
|
|
49
|
+
} | null) => void;
|
|
50
|
+
onFocalPreview: (point: {
|
|
51
|
+
x: number;
|
|
52
|
+
y: number;
|
|
53
|
+
} | undefined) => void;
|
|
54
|
+
placement?: PlacementForm;
|
|
55
|
+
whereUsed?: ReactNode;
|
|
56
|
+
/** EC-568: the "Replace file" action of the asset section. */
|
|
57
|
+
replaceAction?: ReactNode;
|
|
58
|
+
/** EC-568: the file was just replaced — the kept focal point needs a look. */
|
|
59
|
+
replaced?: boolean;
|
|
60
|
+
}): import("react").JSX.Element;
|