@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
|
@@ -2,11 +2,9 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
2
2
|
import { Link, STUDIO_PROJECT_ROUTE, studioPath, useNavigate } from '@elytracms/studio/router';
|
|
3
3
|
import { Check, ChevronDown, CircleAlert, CircleCheck, Copy, Eye, EyeOff, GitCompareArrows, History, Loader2, Lock, MoreHorizontal, RotateCcw, Save, Tag, Trash2, X, } from 'lucide-react';
|
|
4
4
|
import { useCallback, useMemo, useState } from 'react';
|
|
5
|
-
import { FIELD_WIDTH_SPANS, SIDEBAR_FIELD_GROUP, documentKey, groupOrder, groupSectionOf, hasAliases, isBlocksField, isFieldHiddenIn, isFieldLocalized, isSingleton, localeViews, resolveDocumentTitle, resolveField,
|
|
6
|
-
import { blockRootsOf } from '@elytracms/core/project-graph';
|
|
5
|
+
import { DRAFT_PERSPECTIVE, DRAFT_VARIANT, FIELD_WIDTH_SPANS, SIDEBAR_FIELD_GROUP, documentKey, groupOrder, groupSectionOf, hasAliases, isBlocksField, isFieldHiddenIn, isFieldLocalized, isSingleton, localeViews, resolveDocumentTitle, resolveField, sidebarFields, tabDescriptorOf, tabOrder, titleFieldOf, staticFieldHidden, valuesInLocale, perspectiveKey, variantOf, } from '@elytracms/core/cms-core';
|
|
7
6
|
import { Badge, Button, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, CollapsibleSection, Field, Heading, Row, Spacer, Stack, Text, toast, } from '@elytracms/studio/ui';
|
|
8
|
-
import {
|
|
9
|
-
import { collectEmbedRefs, resolveReuseCapability } from '../builder/reuse.js';
|
|
7
|
+
import { studioManifestLookup } from '@elytracms/studio/lib/registry';
|
|
10
8
|
import { cn } from '@elytracms/studio/lib/utils';
|
|
11
9
|
import { localizedLabel, translate } from '@elytracms/studio/lib/i18n';
|
|
12
10
|
import { LocaleSwitcher, displayedFieldFallback, fieldConnection, fillFieldFromBase, resetFieldToBase, } from '@elytracms/studio/lib/locale';
|
|
@@ -37,12 +35,15 @@ import { DetailsPane } from './details-pane.js';
|
|
|
37
35
|
import { DocumentUrlPath } from './document-url-path.js';
|
|
38
36
|
import { useSetBreadcrumb } from '../breadcrumb-context.js';
|
|
39
37
|
import { useAiCurrentDocumentTap } from '@elytracms/studio/lib/ai-current-document';
|
|
40
|
-
import {
|
|
38
|
+
import { resolveDeploymentRoutes } from '@elytracms/studio/lib/workspace';
|
|
41
39
|
// EC-325 — the controller hooks EditorBody orchestrates (each independently
|
|
42
40
|
// unit-tested at its pure core); the shell threads shared draft/ref/replaceDraft
|
|
43
41
|
// state across their boundaries.
|
|
44
42
|
import { usePublishedPerspective } from './use-published-perspective.js';
|
|
45
|
-
import {
|
|
43
|
+
import { useDocumentDraft } from './use-document-draft.js';
|
|
44
|
+
import { FieldCommitBoundary, FieldConflictNotice, FieldPresenceNote, conflictActorName, pathInField, presentInField, } from './field-collaboration.js';
|
|
45
|
+
import { fieldPositionPath } from '@elytracms/studio/lib/cms/field-patch';
|
|
46
|
+
import { getStudioActor } from '@elytracms/studio/lib/operations';
|
|
46
47
|
import { useDiscardController } from './use-discard-controller.js';
|
|
47
48
|
import { usePublishController, publishGate } from './use-publish-controller.js';
|
|
48
49
|
import { FieldFocusRequestContext, FieldPathContext } from './field-anchors.js';
|
|
@@ -109,40 +110,44 @@ function EditorBody({ projectId, collection, doc, focusPath, formAppendix, }) {
|
|
|
109
110
|
// to, and its one document is fixed (never deleted). The header drops the
|
|
110
111
|
// back-crumb/id line and the Delete action accordingly.
|
|
111
112
|
const singleton = isSingleton(collection);
|
|
112
|
-
const [draft, setDraft] = useState(() => structuredClone(doc));
|
|
113
113
|
// The studio's ambient active locale (EC-176): the form displays and edits
|
|
114
114
|
// exactly this locale — switched via the shared LocaleSwitcher, never per field.
|
|
115
115
|
const locale = source.activeLocale;
|
|
116
|
-
// Bumped when a version restore replaces the draft out-of-band, to remount the
|
|
117
|
-
// (uncontrolled) canvas editor from the restored value (EC-224).
|
|
118
|
-
const [restoreNonce, setRestoreNonce] = useState(0);
|
|
119
116
|
// EC-173: unpublish/delete go through the reference-aware confirmation.
|
|
120
117
|
const [confirmAction, setConfirmAction] = useState(null);
|
|
121
118
|
// Stable so the document-editor context value (EC-224) doesn't churn each render.
|
|
122
119
|
const ref = useMemo(() => ({ collection: collection.id, id: doc.id }), [collection.id, doc.id]);
|
|
123
120
|
const confirmTargets = useMemo(() => [{ collection: collection.id, id: doc.id }], [collection.id, doc.id]);
|
|
124
|
-
// EC-
|
|
125
|
-
//
|
|
126
|
-
//
|
|
127
|
-
//
|
|
128
|
-
//
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
121
|
+
// EC-595 A2 — the draft over the revisioned document (`useDocumentDraft`): per-keystroke
|
|
122
|
+
// edits, committed per control kind (commit on quiescence) as the patches of the
|
|
123
|
+
// positions they wrote; rebased on the session's document whenever it changes (a write
|
|
124
|
+
// result, a colleague's change by subscription); forced flushes on unmount and tab
|
|
125
|
+
// hide; the thin presence heartbeat. The explicit-save rails keep their Save button.
|
|
126
|
+
// EC-188: the canvas field is found from the schema (its tab), so the draft knows it.
|
|
127
|
+
const lookupManifest = useMemo(() => studioManifestLookup(), []);
|
|
128
|
+
const draftCanvasField = collection.fields.find(isBlocksField);
|
|
129
|
+
const documentDraft = useDocumentDraft({
|
|
130
|
+
source,
|
|
131
|
+
collection,
|
|
132
|
+
doc,
|
|
133
|
+
locale,
|
|
134
|
+
lookupManifest,
|
|
135
|
+
...(draftCanvasField ? { canvasField: draftCanvasField } : {}),
|
|
136
|
+
});
|
|
137
|
+
const { draft, dirty, explicitSave } = documentDraft;
|
|
138
|
+
// `save` — the forced commit (document model) / the Save button (record model);
|
|
139
|
+
// `replaceDraft` adopts a restored snapshot (the canvas remounts when its value moved).
|
|
140
|
+
const save = documentDraft.save;
|
|
141
|
+
const replaceDraft = documentDraft.replaceDraft;
|
|
142
|
+
// EC-378 — the current-document seam: publish WHICH document is open so the AI
|
|
143
|
+
// assistant resolves "this document" without asking, and the FORCED FLUSH it runs
|
|
144
|
+
// before reading or patching it (EC-595 A2 — the assistant's applied changes reach
|
|
145
|
+
// this editor through the session, which the draft rebases on).
|
|
141
146
|
useAiCurrentDocumentTap({
|
|
142
147
|
collection: collection.id,
|
|
143
148
|
id: doc.id,
|
|
144
149
|
title: primaryTitle(draft, collection, locale, source.localeConfig) || doc.id,
|
|
145
|
-
|
|
150
|
+
flush: documentDraft.flush,
|
|
146
151
|
});
|
|
147
152
|
// EC-226 / EC-316 / EC-325 — the Draft/Published VIEW perspective AND the pinned
|
|
148
153
|
// published snapshot, both derived from the ONE shared version-history query
|
|
@@ -150,25 +155,21 @@ function EditorBody({ projectId, collection, doc, focusPath, formAppendix, }) {
|
|
|
150
155
|
// in this component). `canDiscard`/`versionKey`/`publishedVersionNumber`/
|
|
151
156
|
// `publishedSnapshotDoc` feed the Discard controller below off that same query.
|
|
152
157
|
const { perspective, setPerspective, published, canDiscard, versionKey, publishedVersionNumber, publishedSnapshotDoc, publishedSnapshot, viewReadOnly, viewDoc, publishedPending, } = usePublishedPerspective({ ref, source, canPublish, canVersions, draft });
|
|
153
|
-
// EC-586: THE equality verdict (`semanticValueEqual` over canonical forms)
|
|
154
|
-
//
|
|
155
|
-
//
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
//
|
|
159
|
-
//
|
|
160
|
-
//
|
|
161
|
-
//
|
|
162
|
-
|
|
163
|
-
const
|
|
164
|
-
const issues = useMemo(() => [...schemaIssues, ...referenceIssues], [schemaIssues, referenceIssues]);
|
|
165
|
-
const errorCount = schemaIssues.filter((i) => i.severity === 'error').length;
|
|
158
|
+
// EC-586: THE equality verdict (`semanticValueEqual` over canonical forms) is the
|
|
159
|
+
// draft's `dirty` (`useDocumentDraft`): uncommitted positions exist.
|
|
160
|
+
// EC-589: ONE validation path. A strict relation (the reuse embed declares
|
|
161
|
+
// `strict: true` on its relation prop) to an unpublished target is a blocking error
|
|
162
|
+
// from the validator's relation-target pass — at every position the reference walk
|
|
163
|
+
// reaches, localized buckets and rich-text embeds included — so it gates the Publish
|
|
164
|
+
// button like any other error, and `documents.publish` refuses it for every client.
|
|
165
|
+
// (The EC-300 special case that walked top-level `blocks` in `values` only, and let
|
|
166
|
+
// the editor publish through, is gone.)
|
|
167
|
+
const issues = useMemo(() => source.validate(draft), [source, draft]);
|
|
168
|
+
const errorCount = issues.filter((i) => i.severity === 'error').length;
|
|
166
169
|
const hasErrors = errorCount > 0;
|
|
167
|
-
|
|
168
|
-
//
|
|
169
|
-
//
|
|
170
|
-
// saves explicitly and relies on the `UnsavedChangesGuard` below for navigation).
|
|
171
|
-
const { explicitSave } = useSaveMode({ canPublish, dirty, draft, source });
|
|
170
|
+
// EC-595 A2 · EC-240 — the write model decides the discipline (`source.writes`): a
|
|
171
|
+
// document commits on quiescence and flushes on unmount / tab hide; a record saves
|
|
172
|
+
// explicitly and relies on the `UnsavedChangesGuard` below for navigation.
|
|
172
173
|
// EC-316 / EC-325 — the "Discard changes" controller: the pure enablement
|
|
173
174
|
// decision plus the shared restore sequence (flush → restore the pinned published
|
|
174
175
|
// version whole → replaceDraft → invalidate). Destructured to the names the header
|
|
@@ -277,9 +278,22 @@ function EditorBody({ projectId, collection, doc, focusPath, formAppendix, }) {
|
|
|
277
278
|
// reuse the same foundation, so typing, filling, and resetting can never diverge.
|
|
278
279
|
// EC-586: the write canonicalises (`writeField`), and the manifest lookup covers a
|
|
279
280
|
// composition's static props — a canvas or embed edit lands canonical in the draft.
|
|
280
|
-
|
|
281
|
+
// EC-595 A2: each write is also the patch of the position it wrote, committed per the
|
|
282
|
+
// field's control kind (`useDocumentDraft` → `commit-policy.ts`).
|
|
281
283
|
function setValue(field, value) {
|
|
282
|
-
|
|
284
|
+
documentDraft.setFieldValue(field, value);
|
|
285
|
+
}
|
|
286
|
+
// EC-595 A2 — the per-field conflicts and the thin presence on this document.
|
|
287
|
+
const documentWrites = source.writes.kind === 'document' ? source.writes : undefined;
|
|
288
|
+
const conflicts = documentWrites?.conflicts(ref) ?? [];
|
|
289
|
+
const selfActor = getStudioActor()?.id;
|
|
290
|
+
function conflictNotices(field) {
|
|
291
|
+
if (!documentWrites)
|
|
292
|
+
return null;
|
|
293
|
+
const own = conflicts.filter((conflict) => pathInField(conflict.path, field.name, isFieldLocalized(collection, field.name) ? locale : undefined));
|
|
294
|
+
if (own.length === 0)
|
|
295
|
+
return null;
|
|
296
|
+
return (_jsx(Stack, { gap: "1", children: own.map((conflict) => (_jsx(FieldConflictNotice, { conflict: conflict, actorName: conflictActorName(conflict, documentDraft.presence, selfActor), onTakeTheirs: () => documentWrites.resolveConflict(ref, conflict.id, 'theirs'), onKeepMine: () => documentWrites.resolveConflict(ref, conflict.id, 'mine') }, conflict.id))) }));
|
|
283
297
|
}
|
|
284
298
|
// EC-273/EC-580: conditional visibility, judged in the DISPLAYED view of the
|
|
285
299
|
// draft (`valuesInLocale`, with the default-locale fallback) — a localized field
|
|
@@ -322,7 +336,7 @@ function EditorBody({ projectId, collection, doc, focusPath, formAppendix, }) {
|
|
|
322
336
|
const connection = localized
|
|
323
337
|
? fieldConnection(collection, draft, field.name, locale, source.localeConfig)
|
|
324
338
|
: null;
|
|
325
|
-
return (
|
|
339
|
+
return (_jsxs(Field, { className: cn('rounded-md transition-shadow', highlightedField === field.name && 'bg-primary/5 p-2 ring-2 ring-primary/70'), label: _jsxs(Row, { gap: "2", children: [_jsx("span", { children: fieldLabel(field) }), field.validation?.required ? _jsx("span", { className: "text-destructive", children: "*" }) : null, localized ? _jsx(Badge, { variant: "outline", children: locale }) : null, fieldLevelReadOnly ? (_jsxs(Badge, { variant: "outline", children: [_jsx(Lock, { className: "size-3" }), " ", translate('cms.editor.readOnlyBadge')] })) : null, _jsx(Spacer, {}), connection && !fieldReadOnly ? (_jsx(FieldConnectionControl, { connection: connection, fieldName: field.name, locale: locale, onFillFromBase: () => documentDraft.editField(field, (d) => fillFieldFromBase(collection, d, field.name, locale, source.localeConfig)), onResetToBase: () => documentDraft.editField(field, (d) => resetFieldToBase(collection, d, field.name, locale)) })) : null] }), htmlFor: `f-${field.name}`, fieldPath: field.name, hint: localizedLabel(field.form?.description) || undefined, error: fieldReadOnly ? undefined : errorsByField.get(field.name), children: [_jsx(FieldPresenceNote, { entries: presentInField(documentDraft.presence, fieldPositionPath(collection, field.name, locale)) }), fieldReadOnly ? null : conflictNotices(field), _jsx(FieldCommitBoundary, { onFocusField: () => documentDraft.focused(field), onBlurField: () => documentDraft.blurred(field), onEnter: () => documentDraft.entered(field), children: _jsx(FieldPathContext.Provider, { value: field.name, children: _jsx(FieldControl, { id: `f-${field.name}`, field: field, value: getValue(field), onChange: (v) => setValue(field, v), readOnly: fieldReadOnly }) }) })] }, field.name));
|
|
326
340
|
}
|
|
327
341
|
// The frame-owned `Versions` pane (EC-224) — appended after any view-supplied
|
|
328
342
|
// pane (the canvas's `Properties`). One instance feeds both the form view's
|
|
@@ -367,14 +381,7 @@ function EditorBody({ projectId, collection, doc, focusPath, formAppendix, }) {
|
|
|
367
381
|
id: 'details',
|
|
368
382
|
label: translate('cms.editor.detailsPane'),
|
|
369
383
|
icon: _jsx(Tag, { className: "size-3.5" }),
|
|
370
|
-
render: () => (_jsx(DetailsPane, { collection: collection, doc: draft, siblings: source.documents(), readOnly: viewReadOnly, fields: visibleSidebarFields, renderField: renderFieldRow, onAliasChange: (alias) =>
|
|
371
|
-
const next = { ...d };
|
|
372
|
-
if (alias === undefined)
|
|
373
|
-
delete next.alias;
|
|
374
|
-
else
|
|
375
|
-
next.alias = alias;
|
|
376
|
-
return next;
|
|
377
|
-
}) })),
|
|
384
|
+
render: () => (_jsx(DetailsPane, { collection: collection, doc: draft, siblings: source.documents(), readOnly: viewReadOnly, fields: visibleSidebarFields, renderField: renderFieldRow, onAliasChange: (alias) => documentDraft.setAlias(alias) })),
|
|
378
385
|
}
|
|
379
386
|
: null;
|
|
380
387
|
// Versions is capability-gated (AD-19): a non-versioned rail (users) never mounts
|
|
@@ -384,19 +391,13 @@ function EditorBody({ projectId, collection, doc, focusPath, formAppendix, }) {
|
|
|
384
391
|
...(canVersions ? [versionsPane] : []),
|
|
385
392
|
issuesPane,
|
|
386
393
|
];
|
|
387
|
-
// EC-268 /
|
|
388
|
-
//
|
|
389
|
-
|
|
390
|
-
// match the header split button + the confirm-through dialog below.
|
|
391
|
-
const { publishState, publishConfirm, handlePublish, handlePublishAndClose, confirmPublish, cancelPublishConfirm, } = usePublishController({
|
|
394
|
+
// EC-268 / 0.0.39 — the publish controller: the publish indicator state and the
|
|
395
|
+
// "Publish & close" navigation. Handlers are named to match the header split button.
|
|
396
|
+
const { publishState, handlePublish, handlePublishAndClose } = usePublishController({
|
|
392
397
|
source,
|
|
393
398
|
docRef: ref,
|
|
394
|
-
collection,
|
|
395
399
|
projectId,
|
|
396
|
-
|
|
397
|
-
setDraft,
|
|
398
|
-
dirty,
|
|
399
|
-
hasUnpublishedRefs,
|
|
400
|
+
flushDraft: documentDraft.flush,
|
|
400
401
|
});
|
|
401
402
|
// EC-442 (amended): Duplicate in the header `…` menu — gated on the explicit
|
|
402
403
|
// `duplicate` capability (CMS collections only, owner call 2026-07-17) + a
|
|
@@ -404,12 +405,15 @@ function EditorBody({ projectId, collection, doc, focusPath, formAppendix, }) {
|
|
|
404
405
|
// document"; a copy would be EC-274 drift by construction, mirroring the
|
|
405
406
|
// hidden Delete).
|
|
406
407
|
const canDuplicate = !singleton && source.capabilities.duplicate && source.detailPath !== undefined;
|
|
407
|
-
function handleDuplicate() {
|
|
408
|
-
//
|
|
409
|
-
//
|
|
410
|
-
// auto-published (live-is-published-only), created
|
|
411
|
-
//
|
|
412
|
-
|
|
408
|
+
async function handleDuplicate() {
|
|
409
|
+
// EC-595 A2: the FORCED FLUSH first — the uncommitted fields commit and land — then
|
|
410
|
+
// copy the committed draft (what the editor sees). The duplicate is a fresh DRAFT,
|
|
411
|
+
// never auto-published (live-is-published-only), created insert-if-absent with a
|
|
412
|
+
// factory id (`duplicateFromSource`).
|
|
413
|
+
await documentDraft.flush();
|
|
414
|
+
const newRef = duplicateFromSource(source, source.get(ref) ?? draft);
|
|
415
|
+
if (!newRef)
|
|
416
|
+
return;
|
|
413
417
|
toast.success(translate('cms.editor.duplicateCreated'));
|
|
414
418
|
const splat = source.detailPath?.(newRef.id);
|
|
415
419
|
if (splat === undefined)
|
|
@@ -512,20 +516,13 @@ function EditorBody({ projectId, collection, doc, focusPath, formAppendix, }) {
|
|
|
512
516
|
exitCompare,
|
|
513
517
|
}, children: _jsx(FieldFocusRequestContext.Provider, { value: focusRequest, children: _jsxs("div", { className: cn('flex h-full min-h-0 flex-col', viewReadOnly && 'bg-info/[0.04]'), children: [_jsxs("div", { className: "shrink-0 border-b border-border px-4 py-3 sm:px-6", children: [_jsxs(Row, { gap: "3", align: "start", children: [_jsxs(Stack, { gap: "1", className: "min-w-0", children: [_jsxs(Row, { gap: "2", align: "center", className: "min-w-0", children: [_jsx(Heading, { level: 1, size: "h2", className: "truncate", children: singleton ? localizedLabel(collection.form?.label, collection.id) : title || doc.id }), singleton ? null : _jsx(FallbackChip, { fallback: titleFallback, testId: "doc-title-fallback" })] }), routableCollection ? (_jsx(DocumentUrlPath, { projectId: projectId, collection: collection, doc: draft, siblings: source.documents(), routes: resolveDeploymentRoutes(), localeConfig: source.localeConfig, locale: locale, live: headerLive })) : null] }), _jsx(Spacer, {}), _jsxs(Stack, { gap: "none", align: "end", className: "min-w-0", children: [_jsx(Text, { size: "xs", tone: "muted", className: "whitespace-nowrap", "data-testid": "autosave-status", children: source.lastError
|
|
514
518
|
? translate('cms.editor.saveFailed')
|
|
515
|
-
:
|
|
516
|
-
?
|
|
517
|
-
|
|
518
|
-
: translate('cms.editor.saved')
|
|
519
|
-
: dirty
|
|
520
|
-
? translate('cms.editor.saving')
|
|
521
|
-
: translate('cms.editor.saved') }), source.lastError?.message ? (_jsx(Text, { size: "xs", className: "max-w-64 truncate text-destructive", title: source.lastError.message, "data-testid": "save-error-message", children: source.lastError.message })) : null] }), canPublish ? _jsx(DocStateBadge, { published: published }) : null, canPublish ? (_jsxs(Row, { gap: "px", align: "center", children: [_jsx(Button, { size: "sm", onClick: handlePublish, disabled: publishDisabled, title: publishTitle, className: canPublishClose ? 'rounded-r-none' : undefined, "data-testid": "doc-publish", children: publishButtonLabel }), canPublishClose ? (_jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx(Button, { size: "sm", disabled: publishDisabled, "aria-label": translate('cms.editor.publishActions'), "data-testid": "doc-publish-actions", className: "rounded-l-none border-l border-primary-foreground/25 px-1.5", children: _jsx(ChevronDown, { className: "size-3.5" }) }) }), _jsx(DropdownMenuContent, { align: "end", children: _jsxs(DropdownMenuItem, { onClick: handlePublishAndClose, "data-testid": "doc-publish-and-close", children: [_jsx(Save, {}), " ", translate('cms.editor.publishAndClose')] }) })] })) : null] })) : null, explicitSave ? (_jsxs(Button, { size: "sm", onClick: save, disabled: !dirty, "data-testid": "doc-save", children: [_jsx(Check, {}), " ", translate('common.save')] })) : null, !viewReadOnly ? (_jsx(TranslateDocumentAction, { projectId: projectId, collection: collection, docRef: ref, document: draft, localeConfig: source.localeConfig, localesEnabled: canLocales, onApplied: (next) => {
|
|
522
|
-
source.save(next);
|
|
523
|
-
replaceDraft(next);
|
|
524
|
-
} })) : null, published || !singleton ? (_jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx(Button, { variant: "outline", size: "icon-sm", "aria-label": translate('cms.editor.moreActions'), children: _jsx(MoreHorizontal, {}) }) }), _jsxs(DropdownMenuContent, { align: "end", children: [canDuplicate ? (_jsxs(DropdownMenuItem, { "data-testid": "doc-duplicate", onClick: handleDuplicate, children: [_jsx(Copy, {}), " ", translate('common.duplicate')] })) : null, discardStatus !== 'hidden' ? (_jsxs(DropdownMenuItem, { "data-testid": "doc-discard-draft", disabled: discardStatus !== 'ready', title: discardDisabledReason, onClick: () => setDiscardConfirm(true), children: [_jsx(RotateCcw, {}), " ", translate('cms.editor.discardDraft')] })) : null, published ? (_jsxs(DropdownMenuItem, { onClick: () => setConfirmAction('unpublish'), children: [_jsx(EyeOff, {}), " ", translate('cms.editor.unpublish')] })) : null, singleton ? null : (_jsxs(DropdownMenuItem, { "data-testid": "doc-delete", className: "text-destructive", onClick: () => setConfirmAction('delete'), children: [_jsx(Trash2, {}), " ", translate('common.delete')] }))] })] })) : null] }), showLensRow ? (_jsxs(Row, { gap: "3", className: "mt-3", wrap: true, children: [_jsxs(Row, { gap: "1", align: "center", children: [showContentTabs ? tabs.map((tab) => renderContentTab(tab)) : null, showReferences ? (_jsxs(_Fragment, { children: [_jsx("div", { className: "mx-1 h-4 w-px bg-border", "aria-hidden": true }), renderContentTab(REFERENCES_TAB)] })) : null, compare ? (_jsxs("span", { className: cn('inline-flex items-center gap-1 rounded-md px-2.5 py-1 text-xs font-medium transition-colors', activeTab === COMPARE_TAB
|
|
519
|
+
: dirty
|
|
520
|
+
? translate('cms.editor.unsavedChanges')
|
|
521
|
+
: translate('cms.editor.saved') }), source.lastError?.message ? (_jsx(Text, { size: "xs", className: "max-w-64 truncate text-destructive", title: source.lastError.message, "data-testid": "save-error-message", children: source.lastError.message })) : null] }), canPublish ? _jsx(DocStateBadge, { published: published }) : null, canPublish ? (_jsxs(Row, { gap: "px", align: "center", children: [_jsx(Button, { size: "sm", onClick: handlePublish, disabled: publishDisabled, title: publishTitle, className: canPublishClose ? 'rounded-r-none' : undefined, "data-testid": "doc-publish", children: publishButtonLabel }), canPublishClose ? (_jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx(Button, { size: "sm", disabled: publishDisabled, "aria-label": translate('cms.editor.publishActions'), "data-testid": "doc-publish-actions", className: "rounded-l-none border-l border-primary-foreground/25 px-1.5", children: _jsx(ChevronDown, { className: "size-3.5" }) }) }), _jsx(DropdownMenuContent, { align: "end", children: _jsxs(DropdownMenuItem, { onClick: handlePublishAndClose, "data-testid": "doc-publish-and-close", children: [_jsx(Save, {}), " ", translate('cms.editor.publishAndClose')] }) })] })) : null] })) : null, explicitSave ? (_jsxs(Button, { size: "sm", onClick: save, disabled: !dirty, "data-testid": "doc-save", children: [_jsx(Check, {}), " ", translate('common.save')] })) : null, !viewReadOnly ? (_jsx(TranslateDocumentAction, { projectId: projectId, collection: collection, docRef: ref, document: draft, localeConfig: source.localeConfig, localesEnabled: canLocales, flushDraft: documentDraft.flush })) : null, published || !singleton ? (_jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx(Button, { variant: "outline", size: "icon-sm", "aria-label": translate('cms.editor.moreActions'), children: _jsx(MoreHorizontal, {}) }) }), _jsxs(DropdownMenuContent, { align: "end", children: [canDuplicate ? (_jsxs(DropdownMenuItem, { "data-testid": "doc-duplicate", onClick: () => void handleDuplicate(), children: [_jsx(Copy, {}), " ", translate('common.duplicate')] })) : null, discardStatus !== 'hidden' ? (_jsxs(DropdownMenuItem, { "data-testid": "doc-discard-draft", disabled: discardStatus !== 'ready', title: discardDisabledReason, onClick: () => setDiscardConfirm(true), children: [_jsx(RotateCcw, {}), " ", translate('cms.editor.discardDraft')] })) : null, published ? (_jsxs(DropdownMenuItem, { onClick: () => setConfirmAction('unpublish'), children: [_jsx(EyeOff, {}), " ", translate('cms.editor.unpublish')] })) : null, singleton ? null : (_jsxs(DropdownMenuItem, { "data-testid": "doc-delete", className: "text-destructive", onClick: () => setConfirmAction('delete'), children: [_jsx(Trash2, {}), " ", translate('common.delete')] }))] })] })) : null] }), showLensRow ? (_jsxs(Row, { gap: "3", className: "mt-3", wrap: true, children: [_jsxs(Row, { gap: "1", align: "center", children: [showContentTabs ? tabs.map((tab) => renderContentTab(tab)) : null, showReferences ? (_jsxs(_Fragment, { children: [_jsx("div", { className: "mx-1 h-4 w-px bg-border", "aria-hidden": true }), renderContentTab(REFERENCES_TAB)] })) : null, compare ? (_jsxs("span", { className: cn('inline-flex items-center gap-1 rounded-md px-2.5 py-1 text-xs font-medium transition-colors', activeTab === COMPARE_TAB
|
|
525
522
|
? 'bg-accent text-accent-foreground'
|
|
526
|
-
: 'text-muted-foreground hover:bg-muted'), children: [_jsxs("button", { type: "button", onClick: () => setActiveTab(COMPARE_TAB), "aria-pressed": activeTab === COMPARE_TAB, className: "inline-flex items-center gap-1", children: [_jsx(GitCompareArrows, { className: "size-3", "aria-hidden": true }), " ", translate('cms.version.compare')] }), _jsx("button", { type: "button", onClick: exitCompare, "aria-label": translate('cms.version.closeCompare'), className: "ml-0.5 rounded p-0.5 hover:bg-background/40", children: _jsx(X, { className: "size-3" }) })] })) : null] }), _jsx(Spacer, {}), canLocales ? _jsx(LocaleSwitcher, {}) : null, canPublish ? (_jsxs(Row, { gap: "px", className: "rounded-md bg-muted/60 p-0.5", children: [_jsx(PerspectiveButton, { label: translate('cms.editor.draftState'), active: perspective ===
|
|
523
|
+
: 'text-muted-foreground hover:bg-muted'), children: [_jsxs("button", { type: "button", onClick: () => setActiveTab(COMPARE_TAB), "aria-pressed": activeTab === COMPARE_TAB, className: "inline-flex items-center gap-1", children: [_jsx(GitCompareArrows, { className: "size-3", "aria-hidden": true }), " ", translate('cms.version.compare')] }), _jsx("button", { type: "button", onClick: exitCompare, "aria-label": translate('cms.version.closeCompare'), className: "ml-0.5 rounded p-0.5 hover:bg-background/40", children: _jsx(X, { className: "size-3" }) })] })) : null] }), _jsx(Spacer, {}), canLocales ? _jsx(LocaleSwitcher, {}) : null, canPublish ? (_jsxs(Row, { gap: "px", className: "rounded-md bg-muted/60 p-0.5", children: [_jsx(PerspectiveButton, { label: translate('cms.editor.draftState'), active: variantOf(perspective) === DRAFT_VARIANT, onClick: () => setPerspective(DRAFT_PERSPECTIVE) }), _jsx(PerspectiveButton, { label: translate('cms.editor.publishedState'), active: perspective === 'published', accent: "info", disabled: !published, title: published ? undefined : translate('cms.editor.publishToViewPublished'), onClick: () => setPerspective('published') })] })) : null] })) : null] }), viewReadOnly ? (_jsxs(Row, { gap: "2", className: "shrink-0 border-b border-info/30 bg-info/10 px-4 py-1.5 text-info sm:px-6", "data-testid": "published-view-banner", children: [_jsx(Lock, { className: "size-3.5 shrink-0", "aria-hidden": true }), _jsx(Text, { size: "xs", className: "text-info", children: translate('cms.editor.publishedReadOnlyBanner') }), _jsx(Spacer, {}), _jsx("button", { type: "button", onClick: () => setPerspective(DRAFT_PERSPECTIVE), className: "text-xs font-medium underline-offset-2 hover:underline", children: translate('cms.editor.switchToDraft') })] })) : null, publishedPending ? (_jsx("div", { className: "grid min-h-0 flex-1 place-items-center p-8", children: _jsx(Text, { size: "sm", tone: "muted", children: publishedSnapshot.status === 'loading'
|
|
527
524
|
? translate('cms.editor.loadingPublishedVersion')
|
|
528
|
-
: translate('cms.editor.noPublishedVersion') }) })) : activeTab === COMPARE_TAB && compare ? (_jsxs("div", { className: "flex min-h-0 flex-1 overflow-hidden", children: [_jsx("div", { className: "min-h-0 flex-1 overflow-auto", children: _jsx(VersionCompare, {}) }), _jsx(InspectorSidebar, { panes: trailingPanes, activeId: inspectorPaneId, onActiveChange: setInspectorPaneId, className: "w-[320px] shrink-0 border-l border-border" })] })) : canvasField ? (_jsx("div", { className: "min-h-0 flex-1 overflow-hidden", children: _jsx(CompositionFieldEditor, { field: canvasField, value: getValue(canvasField), onChange: (v) =>
|
|
525
|
+
: translate('cms.editor.noPublishedVersion') }) })) : activeTab === COMPARE_TAB && compare ? (_jsxs("div", { className: "flex min-h-0 flex-1 overflow-hidden", children: [_jsx("div", { className: "min-h-0 flex-1 overflow-auto", children: _jsx(VersionCompare, {}) }), _jsx(InspectorSidebar, { panes: trailingPanes, activeId: inspectorPaneId, onActiveChange: setInspectorPaneId, className: "w-[320px] shrink-0 border-l border-border" })] })) : canvasField ? (_jsx("div", { className: "min-h-0 flex-1 overflow-hidden", children: _jsx(CompositionFieldEditor, { field: canvasField, value: getValue(canvasField), onChange: (v) => documentDraft.setCanvasValue(canvasField, v), conflicts: conflictNotices(canvasField), readOnly: viewReadOnly || source.isFieldReadOnly(canvasField), trailingPanes: trailingPanes }, `${canvasField.name}:${documentDraft.canvasNonce}:${perspectiveKey(perspective)}`) })) : activeTab === REFERENCES_TAB ? (_jsx("div", { className: "flex min-h-0 flex-1 overflow-hidden", children: _jsx("div", { className: "min-h-0 flex-1 overflow-auto p-4 sm:p-6", children: _jsx("div", { className: "mx-auto w-full max-w-2xl", children: _jsx(ReferencesPanel, { projectId: projectId, source: { type: 'document', id: documentKey(ref) }, target: { type: 'document', id: documentKey(ref) } }) }) }) })) : (_jsxs("div", { className: "flex min-h-0 flex-1 overflow-hidden", children: [_jsx("div", { className: "min-h-0 flex-1 overflow-auto p-4 sm:p-6", children: _jsx("div", { className: "mx-auto w-full max-w-2xl", children: _jsxs(Stack, { gap: "6", children: [groups.map((group) => {
|
|
529
526
|
// EC-273/EC-240/EC-306: drop a fieldset whose every field is hidden —
|
|
530
527
|
// by its own rule, by its (static or predicate) group section, or for
|
|
531
528
|
// this principal — so the form never shows an empty section header.
|
|
@@ -547,58 +544,7 @@ function EditorBody({ projectId, collection, doc, focusPath, formAppendix, }) {
|
|
|
547
544
|
if (!open)
|
|
548
545
|
setConfirmAction(null);
|
|
549
546
|
}, onConfirm: handleConfirmedAction })) : null, _jsx(Dialog, { open: discardConfirm, onOpenChange: (open) => { if (!open)
|
|
550
|
-
setDiscardConfirm(false); }, children: _jsxs(DialogContent, { children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: translate('cms.editor.discardDraftTitle') }), _jsx(DialogDescription, { children: translate('cms.editor.discardDraftDescription') })] }), _jsxs(DialogFooter, { className: "mt-4", children: [_jsx(Button, { variant: "ghost", size: "sm", onClick: () => setDiscardConfirm(false), disabled: discardBusy, children: translate('common.cancel') }), _jsx(Button, { variant: "destructive", size: "sm", "data-testid": "discard-draft-confirm", disabled: discardBusy, onClick: () => void handleDiscardDraft(), children: discardBusy ? (_jsxs(_Fragment, { children: [_jsx(Loader2, { className: "animate-spin" }), " ", translate('cms.editor.discardingDraft')] })) : (_jsxs(_Fragment, { children: [_jsx(RotateCcw, {}), " ", translate('cms.editor.discardDraftConfirm')] })) })] })] }) })
|
|
551
|
-
cancelPublishConfirm(); }, children: _jsxs(DialogContent, { children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: translate('cms.editor.publishUnpublishedRefsTitle') }), _jsx(DialogDescription, { children: translate('cms.editor.publishUnpublishedRefsDescription') })] }), _jsxs(Stack, { gap: "1", children: [referenceIssues.map((issue, i) => (_jsxs(Text, { size: "xs", tone: "muted", children: ["\u2022 ", cmsValidationIssueMessage(issue)] }, `${issue.meta?.id ?? i}`))), schemaIssues.length > 0 ? (_jsx(Text, { size: "xs", tone: "muted", className: "mt-1", children: translate(schemaIssues.length === 1
|
|
552
|
-
? 'cms.editor.publishAlsoHasIssues.one'
|
|
553
|
-
: 'cms.editor.publishAlsoHasIssues.other', { count: schemaIssues.length }) })) : null] }), _jsxs(DialogFooter, { className: "mt-4", children: [_jsx(Button, { variant: "ghost", size: "sm", onClick: cancelPublishConfirm, children: translate('common.cancel') }), _jsx(Button, { size: "sm", "data-testid": "publish-anyway", onClick: confirmPublish, children: translate('cms.editor.publishAnyway') })] })] }) })] }) }) }));
|
|
554
|
-
}
|
|
555
|
-
/**
|
|
556
|
-
* EC-300/AD-16 — errors for composition embeds (hard references) whose target isn't
|
|
557
|
-
* published: they render empty on the live site. Walks every `blocks` field of the
|
|
558
|
-
* document for reuse embeds; a missing target is an `unknown-relation-target` error,
|
|
559
|
-
* an unpublished one an `unpublished-relation-target` error. Publishing is
|
|
560
|
-
* confirm-through (not blocked), so these feed the Issues panel + the publish
|
|
561
|
-
* confirmation, never the button's disabled gate. No reuse capability → none.
|
|
562
|
-
*
|
|
563
|
-
* EC-467: the capability honours the optional `reuse` studio-config declaration
|
|
564
|
-
* (absent → the same inference as before). Config-level reuse ISSUES belong to the
|
|
565
|
-
* shell rail, not to this document-scoped list — so only the capability is read here.
|
|
566
|
-
*/
|
|
567
|
-
function unpublishedEmbedIssues(collection, draft, source) {
|
|
568
|
-
const capability = resolveReuseCapability(getStudioRegistry().registry.list(), source.collections, resolveDeploymentReuse());
|
|
569
|
-
if (!capability)
|
|
570
|
-
return [];
|
|
571
|
-
const issues = [];
|
|
572
|
-
const seen = new Set();
|
|
573
|
-
for (const field of collection.fields) {
|
|
574
|
-
if (!isBlocksField(field))
|
|
575
|
-
continue;
|
|
576
|
-
for (const ref of collectEmbedRefs(blockRootsOf(draft.values?.[field.name]), capability)) {
|
|
577
|
-
const key = `${ref.collection}:${ref.id}`;
|
|
578
|
-
if (seen.has(key))
|
|
579
|
-
continue;
|
|
580
|
-
seen.add(key);
|
|
581
|
-
const exists = source.get(ref) !== undefined;
|
|
582
|
-
if (exists && (source.publishing?.isPublished(ref) ?? false))
|
|
583
|
-
continue;
|
|
584
|
-
issues.push(unpublishedEmbedIssue(collection, draft, field.name, ref, exists, source));
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
return issues;
|
|
588
|
-
}
|
|
589
|
-
/** One unpublished/missing reuse-embed issue (EC-300). */
|
|
590
|
-
function unpublishedEmbedIssue(collection, draft, field, ref, exists, source) {
|
|
591
|
-
return {
|
|
592
|
-
code: exists ? 'unpublished-relation-target' : 'unknown-relation-target',
|
|
593
|
-
severity: 'error',
|
|
594
|
-
message: exists
|
|
595
|
-
? translate('cms.editor.embedUnpublishedIssue', { title: source.titleOf(ref) })
|
|
596
|
-
: translate('cms.editor.embedMissingIssue', { id: ref.id }),
|
|
597
|
-
path: [field],
|
|
598
|
-
collectionId: collection.id,
|
|
599
|
-
documentId: draft.id,
|
|
600
|
-
meta: { field, target: ref.collection, id: ref.id },
|
|
601
|
-
};
|
|
547
|
+
setDiscardConfirm(false); }, children: _jsxs(DialogContent, { children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: translate('cms.editor.discardDraftTitle') }), _jsx(DialogDescription, { children: translate('cms.editor.discardDraftDescription') })] }), _jsxs(DialogFooter, { className: "mt-4", children: [_jsx(Button, { variant: "ghost", size: "sm", onClick: () => setDiscardConfirm(false), disabled: discardBusy, children: translate('common.cancel') }), _jsx(Button, { variant: "destructive", size: "sm", "data-testid": "discard-draft-confirm", disabled: discardBusy, onClick: () => void handleDiscardDraft(), children: discardBusy ? (_jsxs(_Fragment, { children: [_jsx(Loader2, { className: "animate-spin" }), " ", translate('cms.editor.discardingDraft')] })) : (_jsxs(_Fragment, { children: [_jsx(RotateCcw, {}), " ", translate('cms.editor.discardDraftConfirm')] })) })] })] }) })] }) }) }));
|
|
602
548
|
}
|
|
603
549
|
/**
|
|
604
550
|
* A segmented-control button for the Draft/Published view toggle (EC-226). The
|