@elytracms/studio 0.0.92 → 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 +25 -11
- package/convex/contract-parity.test.ts +18 -3
- package/convex/document-revalidate.test.ts +10 -12
- package/convex/orphan-hydration.test.ts +116 -0
- package/convex/rail-write-parity.test.ts +11 -8
- package/convex/schema.ts +6 -0
- 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-dialog.js +4 -2
- 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/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 +104 -15
- package/dist/components/builder/builder-store.js +156 -31
- 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 +27 -19
- package/dist/components/builder/canvas-render-context.js +42 -43
- 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.js +32 -5
- 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 +44 -7
- package/dist/components/builder/clipboard-actions.js.map +1 -1
- package/dist/components/builder/composition-field-editor.d.ts +3 -15
- package/dist/components/builder/composition-field-editor.js +29 -40
- package/dist/components/builder/composition-field-editor.js.map +1 -1
- package/dist/components/builder/delete-key.d.ts +5 -0
- package/dist/components/builder/delete-key.js +4 -0
- package/dist/components/builder/delete-key.js.map +1 -1
- package/dist/components/builder/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 -27
- package/dist/components/builder/reuse-context.js.map +1 -1
- package/dist/components/builder/reuse.d.ts +79 -1
- package/dist/components/builder/reuse.js +69 -2
- 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 +3 -19
- package/dist/components/cms/bulk-actions.js +3 -24
- package/dist/components/cms/bulk-actions.js.map +1 -1
- package/dist/components/cms/cell.js.map +1 -1
- package/dist/components/cms/cms-store.d.ts +5 -4
- package/dist/components/cms/cms-store.js +16 -36
- package/dist/components/cms/cms-store.js.map +1 -1
- package/dist/components/cms/detail-editor.js +0 -1
- 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/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-control-lib.d.ts +15 -1
- package/dist/components/cms/field-control-lib.js +22 -3
- package/dist/components/cms/field-control-lib.js.map +1 -1
- package/dist/components/cms/gate-validation.d.ts +21 -30
- package/dist/components/cms/gate-validation.js +11 -32
- package/dist/components/cms/gate-validation.js.map +1 -1
- package/dist/components/cms/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/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-publish-controller.d.ts +1 -2
- package/dist/components/cms/use-publish-controller.js +7 -19
- package/dist/components/cms/use-publish-controller.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 +3 -1
- package/dist/components/collection-view/cms-collection-view-source.js.map +1 -1
- package/dist/components/components/transfer-dialog.js +2 -1
- package/dist/components/components/transfer-dialog.js.map +1 -1
- package/dist/components/components/transfer.d.ts +10 -10
- package/dist/components/components/transfer.js +13 -12
- package/dist/components/components/transfer.js.map +1 -1
- package/dist/lib/ai/apply-content.js +56 -116
- package/dist/lib/ai/apply-content.js.map +1 -1
- package/dist/lib/ai/chat-context.d.ts +8 -0
- package/dist/lib/ai/chat-context.js +37 -6
- package/dist/lib/ai/chat-context.js.map +1 -1
- package/dist/lib/ai/chat-state.d.ts +6 -0
- package/dist/lib/ai/chat-state.js.map +1 -1
- package/dist/lib/ai/constraints.d.ts +20 -5
- package/dist/lib/ai/constraints.js +125 -33
- package/dist/lib/ai/constraints.js.map +1 -1
- package/dist/lib/ai/fake-provider.d.ts +16 -3
- package/dist/lib/ai/fake-provider.js +22 -6
- package/dist/lib/ai/fake-provider.js.map +1 -1
- package/dist/lib/ai/index.d.ts +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/rich-text-input.d.ts +8 -8
- package/dist/lib/ai/rich-text-input.js +2 -1
- package/dist/lib/ai/rich-text-input.js.map +1 -1
- package/dist/lib/ai/rich-text-translate.d.ts +27 -4
- package/dist/lib/ai/rich-text-translate.js +28 -10
- package/dist/lib/ai/rich-text-translate.js.map +1 -1
- package/dist/lib/ai/shape.d.ts +13 -13
- package/dist/lib/ai/shape.js +69 -93
- package/dist/lib/ai/shape.js.map +1 -1
- package/dist/lib/ai/tools.d.ts +36 -41
- package/dist/lib/ai/tools.js +83 -87
- package/dist/lib/ai/tools.js.map +1 -1
- package/dist/lib/ai/translate-document.d.ts +32 -4
- package/dist/lib/ai/translate-document.js +144 -46
- package/dist/lib/ai/translate-document.js.map +1 -1
- package/dist/lib/ai/types.d.ts +34 -23
- package/dist/lib/ai/types.js.map +1 -1
- package/dist/lib/ai/update-fields.d.ts +15 -12
- package/dist/lib/ai/update-fields.js +112 -188
- package/dist/lib/ai/update-fields.js.map +1 -1
- package/dist/lib/ai/writer-form.d.ts +116 -0
- package/dist/lib/ai/writer-form.js +429 -0
- package/dist/lib/ai/writer-form.js.map +1 -0
- package/dist/lib/assets/upload.d.ts +31 -2
- package/dist/lib/assets/upload.js +61 -6
- package/dist/lib/assets/upload.js.map +1 -1
- package/dist/lib/clipboard/os-clipboard.d.ts +10 -2
- package/dist/lib/clipboard/os-clipboard.js +8 -4
- package/dist/lib/clipboard/os-clipboard.js.map +1 -1
- package/dist/lib/clipboard/paste-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 +0 -10
- package/dist/lib/cms/field-patch.js +4 -30
- package/dist/lib/cms/field-patch.js.map +1 -1
- package/dist/lib/cms-views/view-model.d.ts +3 -3
- package/dist/lib/collection-view/collection-view-source.d.ts +7 -3
- package/dist/lib/collection-view/collection-view-source.js.map +1 -1
- package/dist/lib/data/collection-content.js +3 -2
- package/dist/lib/data/collection-content.js.map +1 -1
- package/dist/lib/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 +40 -3
- package/dist/lib/i18n/messages.de.js.map +1 -1
- package/dist/lib/i18n/messages.en.d.ts +34 -2
- package/dist/lib/i18n/messages.en.js +43 -3
- package/dist/lib/i18n/messages.en.js.map +1 -1
- package/dist/lib/operations/project-runtime-config.d.ts +25 -6
- package/dist/lib/operations/project-runtime-config.js +37 -10
- package/dist/lib/operations/project-runtime-config.js.map +1 -1
- package/dist/lib/operations/publish-with-revision.d.ts +33 -1
- package/dist/lib/operations/publish-with-revision.js +32 -0
- package/dist/lib/operations/publish-with-revision.js.map +1 -1
- package/dist/lib/operations/runtime-validator.d.ts +19 -68
- package/dist/lib/operations/runtime-validator.js +12 -138
- package/dist/lib/operations/runtime-validator.js.map +1 -1
- package/dist/lib/operations/studio-operations.d.ts +9 -2
- package/dist/lib/operations/studio-operations.js +13 -11
- package/dist/lib/operations/studio-operations.js.map +1 -1
- package/dist/lib/persistence/convex-adapter.js +6 -1
- package/dist/lib/persistence/convex-adapter.js.map +1 -1
- package/dist/lib/persona/labels.js +3 -1
- package/dist/lib/persona/labels.js.map +1 -1
- package/dist/lib/workspace/index.d.ts +8 -11
- package/dist/lib/workspace/index.js +12 -24
- package/dist/lib/workspace/index.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/lib/assets/asset-placements.d.ts +0 -28
- package/dist/lib/assets/asset-placements.js +0 -83
- package/dist/lib/assets/asset-placements.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"versions-pane.js","sourceRoot":"","sources":["../../../src/components/cms/versions-pane.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useCallback, useState } from 'react'\nimport type { KeyboardEvent } from 'react'\nimport { Eye, GitCompareArrows, MoreHorizontal, RotateCcw } from 'lucide-react'\nimport {\n Badge,\n Button,\n Code,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n Row,\n Spacer,\n Stack,\n StatusDot,\n Text,\n} from '@elytracms/studio/ui'\nimport { documentsSemanticEqual } from '@elytracms/core/content'\nimport { useOperationCache } from '@elytracms/studio/lib/query'\nimport { translate, useUiLocale } from '@elytracms/studio/lib/i18n'\nimport { cn } from '@elytracms/studio/lib/utils'\nimport { useCollectionView } from '../collection-view/collection-view-context'\nimport { useDocumentEditor } from './document-editor-context'\nimport { useDocumentVersionsQuery } from './use-document-versions'\nimport { useRestoreVersion } from './use-restore-version'\nimport { formatRelativeVersionTimestamp, formatVersionTimestamp } from './versions-model'\n\n/**\n * EC-224 — the document-level **Versions** inspector pane. Re-homes the EC-180\n * VersionsPanel at the document level (the Sanity model): the continuous draft\n * on top with **Publish**, then the recorded version history (the live pin\n * badged), each older version **restorable** into the draft.\n *\n * Publish routes through the same pointer op as the header control\n * (`cms.publishDocument`); restore loads a recorded snapshot as the new working\n * draft (the operation snapshots the current draft first, so a rollback is\n * itself undoable) and the open editor adopts it via `replaceDraft`. Unpublish\n * deliberately stays in the header — it routes through the EC-173\n * reference-aware confirmation, which this pane does not duplicate.\n *\n * EC-298: each row also opens the compare view (⤢) and, once comparing, doubles\n * as the navigator — clicking a row sets it as the base of the diff.\n */\nexport function VersionsPane() {\n const source = useCollectionView()\n const {\n docRef,\n draft,\n dirty,\n hasErrors,\n viewReadOnly,\n save,\n replaceDraft,\n compare,\n compareActive,\n startCompare,\n setCompareBase,\n } = useDocumentEditor()\n const [busy, setBusy] = useState(false)\n const published = source.publishing?.isPublished(docRef) ?? false\n const cache = useOperationCache()\n // EC-446 — the active studio locale + a render-time clock, so each row can show\n // a locale-aware RELATIVE timestamp (\"vor 3 Tagen\") with the absolute value on\n // hover. `now` is captured per render (good enough — the list re-renders on any\n // version mutation; a version's age doesn't need per-second reactivity).\n const locale = useUiLocale()\n const now = Date.now()\n\n // The recorded version history, read through the one shared query (EC-308/AD-17,\n // deduped with the compare view). The flush-before-read ordering lives in the\n // loader, so the listed history is authoritative regardless of which surface\n // triggered the change; it refreshes on any version-mutating command\n // (publish/unpublish/snapshot/restore/pin/delete — EC-322) from anywhere, but\n // NOT on a plain draft save (which appends no version). Editor draft/dirty/busy\n // stays local — it is not server state (principle 12).\n const { versionsQuery, versionKey } = useDocumentVersionsQuery(docRef)\n const view = versionsQuery.data ?? null\n // EC-325 — the shared restore sequence (flush → restore → replaceDraft →\n // invalidate). Its `restoreBusy` is OR-ed with the local publish `busy` below so\n // the pane keeps its original single-in-flight gate: while EITHER a publish or a\n // restore is round-tripping, all of the pane's actions disable together.\n const { restore: restoreVersion, busy: restoreBusy } = useRestoreVersion({\n source,\n docRef,\n versionKey,\n dirty,\n save,\n replaceDraft,\n })\n const anyBusy = busy || restoreBusy\n\n const liveSnapshot =\n view?.publishedVersion !== undefined\n ? view.versions.find((v) => v.version === view.publishedVersion)?.snapshot\n : undefined\n // SEMANTIC equality (EC-298) — the same engine the compare view uses, so the two\n // never contradict: a doc that only differs by EC-277 envelope drift reads as\n // \"up to date\", not \"unpublished changes\".\n const liveMatchesDraft =\n liveSnapshot !== undefined && documentsSemanticEqual(liveSnapshot, draft)\n const canPublish = !liveMatchesDraft\n\n const handlePublish = useCallback(async () => {\n setBusy(true)\n try {\n // Publish snapshots the SAVED draft — capture any unsaved edits first so\n // the published version is exactly what the editor shows. Await the\n // publish (it flushes + reconciles the pin, EC-225) before re-reading the\n // version list so the new version shows.\n if (dirty) save()\n await source.publishing?.publish(docRef)\n await cache.invalidate(versionKey)\n } finally {\n setBusy(false)\n }\n }, [source, docRef, dirty, save, cache, versionKey])\n\n // Restore a recorded version into the working draft — the whole-document\n // restore (no scope) via the shared EC-325 helper, which flushes any unsaved\n // buffer FIRST so the operation's pre-restore snapshot records it (else the\n // rollback wouldn't be undoable — restore snapshots the persisted draft).\n const handleRestore = useCallback(\n (version: number) => restoreVersion(version),\n [restoreVersion],\n )\n\n return (\n <Stack gap=\"3\" className=\"p-3\">\n {canPublish ? (\n <Row gap=\"2\" align=\"center\" className=\"rounded-md border border-primary/40 bg-primary/5 px-2.5 py-2\">\n <Stack gap=\"none\" className=\"min-w-0\">\n <Text size=\"sm\" weight=\"medium\">\n {translate('cms.editor.draftState')}\n </Text>\n <Text size=\"xs\" tone=\"muted\">\n {published\n ? translate('cms.version.unpublishedChanges')\n : translate('cms.version.notPublishedYet')}\n </Text>\n </Stack>\n <Spacer />\n <Button\n size=\"sm\"\n onClick={handlePublish}\n disabled={anyBusy || hasErrors || viewReadOnly}\n title={\n viewReadOnly\n ? translate('cms.editor.switchToDraftToPublish')\n : hasErrors\n ? translate('cms.version.fixValidationToPublish')\n : undefined\n }\n >\n <Eye /> {translate('cms.editor.publish')}\n </Button>\n </Row>\n ) : (\n <Text size=\"xs\" tone=\"muted\" className=\"px-0.5\">\n {translate('cms.version.liveUpToDate')}\n </Text>\n )}\n\n {compareActive && compare ? (\n <Text size=\"xs\" tone=\"muted\" className=\"px-0.5\">\n {translate('cms.version.comparingHint', {\n base: compare.base,\n target:\n compare.target === 'draft'\n ? translate('cms.version.currentDraft')\n : `v${compare.target}`,\n })}\n </Text>\n ) : null}\n\n <Stack gap=\"1\">\n {view === null ? (\n <Text size=\"xs\" tone=\"muted\" className=\"px-0.5\">\n {translate('common.loading')}\n </Text>\n ) : view.versions.length === 0 ? (\n <Text size=\"xs\" tone=\"muted\" className=\"px-0.5\">\n {translate('cms.version.noVersionsYet')}\n </Text>\n ) : (\n view.versions.map((entry) => {\n const isLive = entry.version === view.publishedVersion\n const isBase = compareActive && compare?.base === entry.version\n const isTarget = compareActive && compare?.target === entry.version\n const selectable = compareActive && !isTarget\n return (\n <Row\n key={entry.version}\n gap=\"2\"\n align=\"center\"\n {...(selectable\n ? {\n role: 'button',\n tabIndex: 0,\n onClick: () => setCompareBase(entry.version),\n onKeyDown: (e: KeyboardEvent) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault()\n setCompareBase(entry.version)\n }\n },\n }\n : {})}\n className={cn(\n 'rounded-md border px-2.5 py-1.5',\n isBase ? 'border-primary bg-primary/5' : 'border-border/60',\n selectable && !isBase && 'cursor-pointer hover:bg-muted/60',\n )}\n >\n {/* EC-446 — the live/published version leads with a green STATUS\n DOT (hover/focus reveals \"Published\"); historical versions\n reserve the same footprint so the version codes stay aligned. */}\n {isLive ? (\n <StatusDot tone=\"success\" label={translate('cms.editor.publishedState')} />\n ) : (\n <span aria-hidden className=\"size-2 shrink-0\" />\n )}\n <Code tone={isLive ? 'primary' : 'muted'}>v{entry.version}</Code>\n {isBase ? (\n <Badge variant=\"outline\">{translate('cms.version.baseBadge')}</Badge>\n ) : isTarget ? (\n <Badge variant=\"outline\">{translate('cms.version.comparedBadge')}</Badge>\n ) : null}\n {entry.origin === 'assistant' ? (\n <Badge variant=\"outline\" title={translate('cms.version.aiAuthoredTitle')}>\n {translate('cms.version.aiAuthored')}\n </Badge>\n ) : null}\n {/* EC-446 — readable RELATIVE date; the full absolute timestamp\n on hover (`title`), replacing the old truncated stub. */}\n <Text\n size=\"xs\"\n tone=\"subtle\"\n className=\"truncate\"\n title={formatVersionTimestamp(entry.createdAt)}\n >\n {formatRelativeVersionTimestamp(entry.createdAt, now, locale)}\n </Text>\n <Spacer />\n {/* EC-446 — actions collapse into a per-row `⋯` overflow menu\n (mirrors the collection-table row menu). Suppressed in compare\n mode exactly as the buttons were, so the row stays a base picker. */}\n {compareActive ? null : (\n <VersionRowActions\n version={entry.version}\n isLive={isLive}\n restoreDisabled={anyBusy || viewReadOnly}\n restoreDisabledTitle={\n viewReadOnly ? translate('cms.version.switchToDraftToRestore') : undefined\n }\n onCompare={() => startCompare(entry.version)}\n onRestore={() => void handleRestore(entry.version)}\n />\n )}\n </Row>\n )\n })\n )}\n </Stack>\n </Stack>\n )\n}\n\n/**\n * EC-446 — one version row's `⋯` overflow menu, mirroring the collection-table\n * row-actions menu (`RowActionsMenu`, built on the same `DropdownMenu`\n * primitive). Compare (⤢) is always offered; Restore (↺) is offered on every\n * version EXCEPT the live/published one (which is already the draft's baseline —\n * matching the pre-redesign behavior). The live version therefore shows a\n * Compare-only menu, never a bare/empty trigger.\n *\n * Restore keeps the pane's original gates: `restoreDisabled` (the single\n * in-flight gate `anyBusy` OR `viewReadOnly`) disables the item, and the\n * read-only hint rides its `title`.\n */\nfunction VersionRowActions({\n version,\n isLive,\n restoreDisabled,\n restoreDisabledTitle,\n onCompare,\n onRestore,\n}: {\n version: number\n isLive: boolean\n restoreDisabled: boolean\n restoreDisabledTitle: string | undefined\n onCompare: () => void\n onRestore: () => void\n}) {\n return (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button\n variant=\"ghost\"\n size=\"icon-sm\"\n aria-label={translate('cms.version.rowActionsLabel', { version })}\n data-testid={`version-actions-${version}`}\n >\n <MoreHorizontal />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n <DropdownMenuItem\n onClick={onCompare}\n data-testid={`version-action-compare-${version}`}\n >\n <GitCompareArrows /> {translate('cms.version.compare')}\n </DropdownMenuItem>\n {isLive ? null : (\n <DropdownMenuItem\n onClick={onRestore}\n disabled={restoreDisabled}\n {...(restoreDisabledTitle ? { title: restoreDisabledTitle } : {})}\n data-testid={`version-action-restore-${version}`}\n >\n <RotateCcw /> {translate('cms.version.restore')}\n </DropdownMenuItem>\n )}\n </DropdownMenuContent>\n </DropdownMenu>\n )\n}\n"]}
|
|
1
|
+
{"version":3,"file":"versions-pane.js","sourceRoot":"","sources":["../../../src/components/cms/versions-pane.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useCallback, useState } from 'react'\nimport type { KeyboardEvent } from 'react'\nimport { Eye, GitCompareArrows, MoreHorizontal, RotateCcw } from 'lucide-react'\nimport {\n Badge,\n Button,\n Code,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n Row,\n Spacer,\n Stack,\n StatusDot,\n Text,\n toast,\n} from '@elytracms/studio/ui'\nimport { documentsSemanticEqual } from '@elytracms/core/content'\nimport { useOperationCache } from '@elytracms/studio/lib/query'\nimport { translate, useUiLocale } from '@elytracms/studio/lib/i18n'\nimport { cn } from '@elytracms/studio/lib/utils'\nimport { useCollectionView } from '../collection-view/collection-view-context'\nimport { useDocumentEditor } from './document-editor-context'\nimport { useDocumentVersionsQuery } from './use-document-versions'\nimport { useRestoreVersion } from './use-restore-version'\nimport { formatRelativeVersionTimestamp, formatVersionTimestamp } from './versions-model'\n\n/**\n * EC-224 — the document-level **Versions** inspector pane. Re-homes the EC-180\n * VersionsPanel at the document level (the Sanity model): the continuous draft\n * on top with **Publish**, then the recorded version history (the live pin\n * badged), each older version **restorable** into the draft.\n *\n * Publish routes through the same pointer op as the header control\n * (`cms.publishDocument`); restore loads a recorded snapshot as the new working\n * draft (the operation snapshots the current draft first, so a rollback is\n * itself undoable) and the open editor adopts it via `replaceDraft`. Unpublish\n * deliberately stays in the header — it routes through the EC-173\n * reference-aware confirmation, which this pane does not duplicate.\n *\n * EC-298: each row also opens the compare view (⤢) and, once comparing, doubles\n * as the navigator — clicking a row sets it as the base of the diff.\n */\nexport function VersionsPane() {\n const source = useCollectionView()\n const {\n docRef,\n draft,\n dirty,\n hasErrors,\n viewReadOnly,\n save,\n replaceDraft,\n compare,\n compareActive,\n startCompare,\n setCompareBase,\n } = useDocumentEditor()\n const [busy, setBusy] = useState(false)\n const published = source.publishing?.isPublished(docRef) ?? false\n const cache = useOperationCache()\n // EC-446 — the active studio locale + a render-time clock, so each row can show\n // a locale-aware RELATIVE timestamp (\"vor 3 Tagen\") with the absolute value on\n // hover. `now` is captured per render (good enough — the list re-renders on any\n // version mutation; a version's age doesn't need per-second reactivity).\n const locale = useUiLocale()\n const now = Date.now()\n\n // The recorded version history, read through the one shared query (EC-308/AD-17,\n // deduped with the compare view). The flush-before-read ordering lives in the\n // loader, so the listed history is authoritative regardless of which surface\n // triggered the change; it refreshes on any version-mutating command\n // (publish/unpublish/snapshot/restore/pin/delete — EC-322) from anywhere, but\n // NOT on a plain draft save (which appends no version). Editor draft/dirty/busy\n // stays local — it is not server state (principle 12).\n const { versionsQuery, versionKey } = useDocumentVersionsQuery(docRef)\n const view = versionsQuery.data ?? null\n // EC-325 — the shared restore sequence (flush → restore → replaceDraft →\n // invalidate). Its `restoreBusy` is OR-ed with the local publish `busy` below so\n // the pane keeps its original single-in-flight gate: while EITHER a publish or a\n // restore is round-tripping, all of the pane's actions disable together.\n const { restore: restoreVersion, busy: restoreBusy } = useRestoreVersion({\n source,\n docRef,\n versionKey,\n dirty,\n save,\n replaceDraft,\n })\n const anyBusy = busy || restoreBusy\n\n const liveSnapshot =\n view?.publishedVersion !== undefined\n ? view.versions.find((v) => v.version === view.publishedVersion)?.snapshot\n : undefined\n // SEMANTIC equality (EC-298) — the same engine the compare view uses, so the two\n // never contradict: a doc that only differs by EC-277 envelope drift reads as\n // \"up to date\", not \"unpublished changes\".\n const liveMatchesDraft =\n liveSnapshot !== undefined && documentsSemanticEqual(liveSnapshot, draft)\n const canPublish = !liveMatchesDraft\n\n const handlePublish = useCallback(async () => {\n setBusy(true)\n try {\n // Publish snapshots the SAVED draft — capture any unsaved edits first so\n // the published version is exactly what the editor shows. Await the\n // publish (it flushes + reconciles the pin, EC-225) before re-reading the\n // version list so the new version shows.\n if (dirty) save()\n await source.publishing?.publish(docRef)\n await cache.invalidate(versionKey)\n } catch (error) {\n // EC-585: a refused publish rejects (it is also on the persist banner) — say so\n // here too rather than let the rejection escape the click handler unhandled.\n toast.error(\n error instanceof Error\n ? translate('cms.editor.publishFailedReason', { error: error.message })\n : translate('cms.editor.publishFailed'),\n )\n } finally {\n setBusy(false)\n }\n }, [source, docRef, dirty, save, cache, versionKey])\n\n // Restore a recorded version into the working draft — the whole-document\n // restore (no scope) via the shared EC-325 helper, which flushes any unsaved\n // buffer FIRST so the operation's pre-restore snapshot records it (else the\n // rollback wouldn't be undoable — restore snapshots the persisted draft).\n const handleRestore = useCallback(\n (version: number) => restoreVersion(version),\n [restoreVersion],\n )\n\n return (\n <Stack gap=\"3\" className=\"p-3\">\n {canPublish ? (\n <Row gap=\"2\" align=\"center\" className=\"rounded-md border border-primary/40 bg-primary/5 px-2.5 py-2\">\n <Stack gap=\"none\" className=\"min-w-0\">\n <Text size=\"sm\" weight=\"medium\">\n {translate('cms.editor.draftState')}\n </Text>\n <Text size=\"xs\" tone=\"muted\">\n {published\n ? translate('cms.version.unpublishedChanges')\n : translate('cms.version.notPublishedYet')}\n </Text>\n </Stack>\n <Spacer />\n <Button\n size=\"sm\"\n onClick={handlePublish}\n disabled={anyBusy || hasErrors || viewReadOnly}\n title={\n viewReadOnly\n ? translate('cms.editor.switchToDraftToPublish')\n : hasErrors\n ? translate('cms.version.fixValidationToPublish')\n : undefined\n }\n >\n <Eye /> {translate('cms.editor.publish')}\n </Button>\n </Row>\n ) : (\n <Text size=\"xs\" tone=\"muted\" className=\"px-0.5\">\n {translate('cms.version.liveUpToDate')}\n </Text>\n )}\n\n {compareActive && compare ? (\n <Text size=\"xs\" tone=\"muted\" className=\"px-0.5\">\n {translate('cms.version.comparingHint', {\n base: compare.base,\n target:\n compare.target === 'draft'\n ? translate('cms.version.currentDraft')\n : `v${compare.target}`,\n })}\n </Text>\n ) : null}\n\n <Stack gap=\"1\">\n {view === null ? (\n <Text size=\"xs\" tone=\"muted\" className=\"px-0.5\">\n {translate('common.loading')}\n </Text>\n ) : view.versions.length === 0 ? (\n <Text size=\"xs\" tone=\"muted\" className=\"px-0.5\">\n {translate('cms.version.noVersionsYet')}\n </Text>\n ) : (\n view.versions.map((entry) => {\n const isLive = entry.version === view.publishedVersion\n const isBase = compareActive && compare?.base === entry.version\n const isTarget = compareActive && compare?.target === entry.version\n const selectable = compareActive && !isTarget\n return (\n <Row\n key={entry.version}\n gap=\"2\"\n align=\"center\"\n {...(selectable\n ? {\n role: 'button',\n tabIndex: 0,\n onClick: () => setCompareBase(entry.version),\n onKeyDown: (e: KeyboardEvent) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault()\n setCompareBase(entry.version)\n }\n },\n }\n : {})}\n className={cn(\n 'rounded-md border px-2.5 py-1.5',\n isBase ? 'border-primary bg-primary/5' : 'border-border/60',\n selectable && !isBase && 'cursor-pointer hover:bg-muted/60',\n )}\n >\n {/* EC-446 — the live/published version leads with a green STATUS\n DOT (hover/focus reveals \"Published\"); historical versions\n reserve the same footprint so the version codes stay aligned. */}\n {isLive ? (\n <StatusDot tone=\"success\" label={translate('cms.editor.publishedState')} />\n ) : (\n <span aria-hidden className=\"size-2 shrink-0\" />\n )}\n <Code tone={isLive ? 'primary' : 'muted'}>v{entry.version}</Code>\n {isBase ? (\n <Badge variant=\"outline\">{translate('cms.version.baseBadge')}</Badge>\n ) : isTarget ? (\n <Badge variant=\"outline\">{translate('cms.version.comparedBadge')}</Badge>\n ) : null}\n {entry.origin === 'assistant' ? (\n <Badge variant=\"outline\" title={translate('cms.version.aiAuthoredTitle')}>\n {translate('cms.version.aiAuthored')}\n </Badge>\n ) : null}\n {/* EC-446 — readable RELATIVE date; the full absolute timestamp\n on hover (`title`), replacing the old truncated stub. */}\n <Text\n size=\"xs\"\n tone=\"subtle\"\n className=\"truncate\"\n title={formatVersionTimestamp(entry.createdAt)}\n >\n {formatRelativeVersionTimestamp(entry.createdAt, now, locale)}\n </Text>\n <Spacer />\n {/* EC-446 — actions collapse into a per-row `⋯` overflow menu\n (mirrors the collection-table row menu). Suppressed in compare\n mode exactly as the buttons were, so the row stays a base picker. */}\n {compareActive ? null : (\n <VersionRowActions\n version={entry.version}\n isLive={isLive}\n restoreDisabled={anyBusy || viewReadOnly}\n restoreDisabledTitle={\n viewReadOnly ? translate('cms.version.switchToDraftToRestore') : undefined\n }\n onCompare={() => startCompare(entry.version)}\n onRestore={() => void handleRestore(entry.version)}\n />\n )}\n </Row>\n )\n })\n )}\n </Stack>\n </Stack>\n )\n}\n\n/**\n * EC-446 — one version row's `⋯` overflow menu, mirroring the collection-table\n * row-actions menu (`RowActionsMenu`, built on the same `DropdownMenu`\n * primitive). Compare (⤢) is always offered; Restore (↺) is offered on every\n * version EXCEPT the live/published one (which is already the draft's baseline —\n * matching the pre-redesign behavior). The live version therefore shows a\n * Compare-only menu, never a bare/empty trigger.\n *\n * Restore keeps the pane's original gates: `restoreDisabled` (the single\n * in-flight gate `anyBusy` OR `viewReadOnly`) disables the item, and the\n * read-only hint rides its `title`.\n */\nfunction VersionRowActions({\n version,\n isLive,\n restoreDisabled,\n restoreDisabledTitle,\n onCompare,\n onRestore,\n}: {\n version: number\n isLive: boolean\n restoreDisabled: boolean\n restoreDisabledTitle: string | undefined\n onCompare: () => void\n onRestore: () => void\n}) {\n return (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button\n variant=\"ghost\"\n size=\"icon-sm\"\n aria-label={translate('cms.version.rowActionsLabel', { version })}\n data-testid={`version-actions-${version}`}\n >\n <MoreHorizontal />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n <DropdownMenuItem\n onClick={onCompare}\n data-testid={`version-action-compare-${version}`}\n >\n <GitCompareArrows /> {translate('cms.version.compare')}\n </DropdownMenuItem>\n {isLive ? null : (\n <DropdownMenuItem\n onClick={onRestore}\n disabled={restoreDisabled}\n {...(restoreDisabledTitle ? { title: restoreDisabledTitle } : {})}\n data-testid={`version-action-restore-${version}`}\n >\n <RotateCcw /> {translate('cms.version.restore')}\n </DropdownMenuItem>\n )}\n </DropdownMenuContent>\n </DropdownMenu>\n )\n}\n"]}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useCallback, useMemo } from 'react';
|
|
2
2
|
import { isHierarchical } from '@elytracms/core/cms-core';
|
|
3
|
+
import { assetAlt } from '@elytracms/core/persistence';
|
|
3
4
|
import { studioPath } from '@elytracms/studio/router';
|
|
4
5
|
import { resolveDeploymentFrontendUrl, resolveDeploymentRoutes } from '@elytracms/studio/lib/workspace';
|
|
5
6
|
import { useCms } from '../cms/cms-store.js';
|
|
@@ -68,12 +69,13 @@ export function useCmsCollectionViewSource(collectionId) {
|
|
|
68
69
|
const record = assetStore?.assets.find((asset) => asset.id === id);
|
|
69
70
|
if (!record)
|
|
70
71
|
return undefined;
|
|
72
|
+
const alt = assetAlt(record); // EC-587: the one reader
|
|
71
73
|
return {
|
|
72
74
|
...(record.url !== undefined ? { url: record.url } : {}),
|
|
73
75
|
contentType: record.contentType,
|
|
74
76
|
...(record.title !== undefined ? { title: record.title } : {}),
|
|
75
77
|
filename: record.filename,
|
|
76
|
-
...(
|
|
78
|
+
...(alt !== null ? { alt } : {}),
|
|
77
79
|
};
|
|
78
80
|
}, [assetStore]);
|
|
79
81
|
const liveUrls = useCallback(() => liveUrlMap, [liveUrlMap]);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cms-collection-view-source.js","sourceRoot":"","sources":["../../../src/components/collection-view/cms-collection-view-source.ts"],"names":[],"mappings":"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","sourcesContent":["import { useCallback, useMemo } from 'react'\nimport { isHierarchical } from '@elytracms/core/cms-core'\nimport type { DocumentRef, DocumentUrl } from '@elytracms/core/cms-core'\nimport { studioPath } from '@elytracms/studio/router'\nimport { resolveDeploymentFrontendUrl, resolveDeploymentRoutes } from '@elytracms/studio/lib/workspace'\nimport type {\n CollectionViewCapabilities,\n CollectionViewSource,\n DocumentWrites,\n ResolvedAsset,\n} from '@elytracms/studio/lib/collection-view/collection-view-source'\nimport { useCms } from '../cms/cms-store'\nimport { useOptionalAssets } from '../assets/assets-store'\nimport { usePublishedDocuments } from '@elytracms/studio/lib/cms/published-documents'\nimport { resolveDocumentLiveUrl } from '../cms/use-live-url'\n\n/**\n * Assemble a {@link CollectionViewSource} for a `cmsDocuments` collection from the\n * ambient `CmsStore` + its side contexts (assets, published pool) + the\n * deployment config reads (EC-240 / AD-19). This is the CMS adapter: a\n * behaviour-preserving pass-through so the portable list + edit surfaces render\n * over the source instead of `useCms()` directly, byte-for-byte as before.\n *\n * Returns `null` for an unknown collection id (the caller renders the fallback).\n *\n * Referential stability: collection-parameterised methods are `useCallback`-bound\n * and the source object is `useMemo`-ed, so the surfaces' `useMemo` dependency\n * arrays stay stable across renders (the inline-edit order snapshot depends on it).\n */\nexport function useCmsCollectionViewSource(collectionId: string): CollectionViewSource | null {\n const cms = useCms()\n const assetStore = useOptionalAssets()\n const collection = cms.getCollection(collectionId)\n const publishedDocs = usePublishedDocuments(cms.projectId, collectionId)\n\n // The asset collection id is resolved once from the schema (as the cell does).\n const assetCollectionId = useMemo(\n () => cms.collections.find((c) => c.kind === 'asset')?.id ?? 'asset',\n [cms.collections],\n )\n\n // Live-URL column: the published pool × deployment routes, memoised exactly as\n // the table did — derived from PUBLISHED snapshots, never draft rows (EC-205).\n const isRoutable = useMemo(\n () => resolveDeploymentRoutes().some((route) => route.document?.collection === collectionId),\n [collectionId],\n )\n // EC-384: `resolveDocumentLiveUrl` merges the stored routes with the kit's\n // fallback-home rule (same as the document editor's header affordance,\n // `use-live-url.ts`) — otherwise a fallback-addressed home page showed no URL\n // in this column even though the live site serves it.\n const liveUrlMap = useMemo(() => {\n if (!collection || !isRoutable) return null\n const siblings = [...publishedDocs.byId.values()]\n const map = new Map<string, DocumentUrl>()\n for (const published of siblings) {\n const url = resolveDocumentLiveUrl({\n routes: resolveDeploymentRoutes(),\n frontendUrl: resolveDeploymentFrontendUrl(),\n localeConfig: cms.localeConfig,\n collection,\n doc: published,\n siblings,\n locale: cms.activeLocale,\n })\n if (url) map.set(published.id, url)\n }\n return map\n }, [collection, isRoutable, publishedDocs, cms.localeConfig, cms.activeLocale])\n\n const documents = useCallback(() => cms.documentsOf(collectionId), [cms, collectionId])\n const viewState = useCallback(() => cms.tableStateOf(collectionId), [cms, collectionId])\n const setViewState = useCallback(\n (update: Parameters<CollectionViewSource['setViewState']>[0]) =>\n cms.setTableState(collectionId, update),\n [cms, collectionId],\n )\n // Named views (EC-246b): config-as-code lenses the header selector switches between.\n const views = useMemo(\n () => ({\n list: () => cms.viewsOf(collectionId),\n activeId: () => cms.activeViewId(collectionId),\n setActive: (id: string) => cms.setActiveView(collectionId, id),\n }),\n [cms, collectionId],\n )\n const create = useCallback(() => cms.createDocument(collectionId), [cms, collectionId])\n const imports = useMemo(\n () => ({ importDocuments: (items: unknown[]) => cms.importDocuments(collectionId, items) }),\n [cms, collectionId],\n )\n const resolveAsset = useCallback(\n (id: string): ResolvedAsset | undefined => {\n const record = assetStore?.assets.find((asset) => asset.id === id)\n if (!record) return undefined\n return {\n ...(record.url !== undefined ? { url: record.url } : {}),\n contentType: record.contentType,\n ...(record.title !== undefined ? { title: record.title } : {}),\n filename: record.filename,\n ...(record.alt !== undefined ? { alt: record.alt } : {}),\n }\n },\n [assetStore],\n )\n const liveUrls = useCallback(() => liveUrlMap, [liveUrlMap])\n // EC-595 A2: the CMS writes revisioned documents — patches at the source.\n const writes = useMemo<DocumentWrites>(\n () => ({\n kind: 'document',\n commit: cms.commitPatches,\n revisionOf: cms.revisionOf,\n insert: cms.insertDocument,\n newId: cms.newDocumentId,\n conflicts: cms.documentConflicts,\n resolveConflict: cms.resolveConflict,\n adoptSnapshot: cms.adoptDocumentSnapshot,\n flush: (ref) => cms.flushPersistence(ref),\n replace: cms.replaceDocument,\n subscribe: cms.subscribeDocument,\n reportPresence: cms.reportPresence,\n clearPresence: cms.clearPresence,\n presenceSessionId: cms.presenceSessionId,\n }),\n [cms],\n )\n\n return useMemo(() => {\n if (!collection) return null\n const capabilities: CollectionViewCapabilities = {\n publish: true,\n versions: true,\n locales: true,\n inlineEdit: true,\n create: true,\n duplicate: true, // EC-442: CMS collections only (owner call — rails skip)\n remove: true,\n bulk: true,\n liveUrl: isRoutable,\n hierarchy: isHierarchical(collection),\n importExport: true,\n }\n const source: CollectionViewSource = {\n collectionId,\n schema: collection,\n capabilities,\n ...(cms.projectId !== undefined ? { projectId: cms.projectId } : {}),\n assetCollectionId,\n localeConfig: cms.localeConfig,\n activeLocale: cms.activeLocale,\n documents,\n get: cms.getDocument,\n titleOf: cms.titleOf,\n // References are collection-based (EC-072): resolve candidates of ANY target\n // collection the schema declares — the workspace doc store already serves them.\n referenceCandidates: cms.documentsOf,\n collections: cms.collections,\n isFieldReadOnly: cms.isFieldReadOnly,\n validate: cms.validate,\n resolveAsset,\n lastError: cms.persistError,\n clearLastError: cms.clearPersistError,\n // Queue flush, so a pre-flight reference-index read is current.\n flush: () => cms.flushPersistence(),\n detailPath: (id: string) => studioPath.cmsDocument(collectionId, id),\n listPath: studioPath.cmsCollection(collectionId),\n viewState,\n setViewState,\n views,\n imports,\n create,\n writes,\n remove: cms.deleteDocument,\n bulk: (ids, action, setField) => cms.runBulk(collectionId, ids, action, setField),\n publishing: {\n isPublished: cms.isPublished,\n // EC-449: the materialized changed flag behind the amber `$state` dot\n // (chokepoint-computed; the store re-stamps it on save/publish/unpublish).\n hasUnpublishedChanges: cms.documentHasUnpublishedChanges,\n publish: cms.publishDocument,\n unpublish: cms.unpublishDocument,\n liveUrls,\n },\n // EC-445/EC-447: per-document timestamps behind the $updatedAt + $publishedAt\n // columns, sourced from the store (hydration seeds them from the persisted\n // `cmsDocuments.updatedAt` / `.publishedAt`; the store re-stamps on the edit +\n // publish/unpublish paths). Both are referentially-stable store accessors.\n timestamps: {\n updatedAt: cms.documentUpdatedAt,\n publishedAt: cms.documentPublishedAt,\n },\n versioning: {\n restore: (ref: DocumentRef, version: number, scope) =>\n cms.restoreDocumentVersion(ref, version, scope),\n // Flush-before-read ordering folds into the loader (EC-224) so any surface\n // that mutated the history still lists an authoritative version set.\n load: async (ref: DocumentRef) => {\n await cms.flushPersistence()\n return cms.loadDocumentVersions(ref)\n },\n },\n inlineEditing: {\n commitField: (input) => cms.commitFieldEdit(input),\n },\n }\n return source\n }, [\n collection,\n collectionId,\n cms,\n isRoutable,\n documents,\n viewState,\n setViewState,\n views,\n imports,\n create,\n resolveAsset,\n liveUrls,\n assetCollectionId,\n writes,\n ])\n}\n"]}
|
|
1
|
+
{"version":3,"file":"cms-collection-view-source.js","sourceRoot":"","sources":["../../../src/components/collection-view/cms-collection-view-source.ts"],"names":[],"mappings":"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","sourcesContent":["import { useCallback, useMemo } from 'react'\nimport { isHierarchical } from '@elytracms/core/cms-core'\nimport type { DocumentRef, DocumentUrl } from '@elytracms/core/cms-core'\nimport { assetAlt } from '@elytracms/core/persistence'\nimport { studioPath } from '@elytracms/studio/router'\nimport { resolveDeploymentFrontendUrl, resolveDeploymentRoutes } from '@elytracms/studio/lib/workspace'\nimport type {\n CollectionViewCapabilities,\n CollectionViewSource,\n DocumentWrites,\n AssetDisplay,\n} from '@elytracms/studio/lib/collection-view/collection-view-source'\nimport { useCms } from '../cms/cms-store'\nimport { useOptionalAssets } from '../assets/assets-store'\nimport { usePublishedDocuments } from '@elytracms/studio/lib/cms/published-documents'\nimport { resolveDocumentLiveUrl } from '../cms/use-live-url'\n\n/**\n * Assemble a {@link CollectionViewSource} for a `cmsDocuments` collection from the\n * ambient `CmsStore` + its side contexts (assets, published pool) + the\n * deployment config reads (EC-240 / AD-19). This is the CMS adapter: a\n * behaviour-preserving pass-through so the portable list + edit surfaces render\n * over the source instead of `useCms()` directly, byte-for-byte as before.\n *\n * Returns `null` for an unknown collection id (the caller renders the fallback).\n *\n * Referential stability: collection-parameterised methods are `useCallback`-bound\n * and the source object is `useMemo`-ed, so the surfaces' `useMemo` dependency\n * arrays stay stable across renders (the inline-edit order snapshot depends on it).\n */\nexport function useCmsCollectionViewSource(collectionId: string): CollectionViewSource | null {\n const cms = useCms()\n const assetStore = useOptionalAssets()\n const collection = cms.getCollection(collectionId)\n const publishedDocs = usePublishedDocuments(cms.projectId, collectionId)\n\n // The asset collection id is resolved once from the schema (as the cell does).\n const assetCollectionId = useMemo(\n () => cms.collections.find((c) => c.kind === 'asset')?.id ?? 'asset',\n [cms.collections],\n )\n\n // Live-URL column: the published pool × deployment routes, memoised exactly as\n // the table did — derived from PUBLISHED snapshots, never draft rows (EC-205).\n const isRoutable = useMemo(\n () => resolveDeploymentRoutes().some((route) => route.document?.collection === collectionId),\n [collectionId],\n )\n // EC-384: `resolveDocumentLiveUrl` merges the stored routes with the kit's\n // fallback-home rule (same as the document editor's header affordance,\n // `use-live-url.ts`) — otherwise a fallback-addressed home page showed no URL\n // in this column even though the live site serves it.\n const liveUrlMap = useMemo(() => {\n if (!collection || !isRoutable) return null\n const siblings = [...publishedDocs.byId.values()]\n const map = new Map<string, DocumentUrl>()\n for (const published of siblings) {\n const url = resolveDocumentLiveUrl({\n routes: resolveDeploymentRoutes(),\n frontendUrl: resolveDeploymentFrontendUrl(),\n localeConfig: cms.localeConfig,\n collection,\n doc: published,\n siblings,\n locale: cms.activeLocale,\n })\n if (url) map.set(published.id, url)\n }\n return map\n }, [collection, isRoutable, publishedDocs, cms.localeConfig, cms.activeLocale])\n\n const documents = useCallback(() => cms.documentsOf(collectionId), [cms, collectionId])\n const viewState = useCallback(() => cms.tableStateOf(collectionId), [cms, collectionId])\n const setViewState = useCallback(\n (update: Parameters<CollectionViewSource['setViewState']>[0]) =>\n cms.setTableState(collectionId, update),\n [cms, collectionId],\n )\n // Named views (EC-246b): config-as-code lenses the header selector switches between.\n const views = useMemo(\n () => ({\n list: () => cms.viewsOf(collectionId),\n activeId: () => cms.activeViewId(collectionId),\n setActive: (id: string) => cms.setActiveView(collectionId, id),\n }),\n [cms, collectionId],\n )\n const create = useCallback(() => cms.createDocument(collectionId), [cms, collectionId])\n const imports = useMemo(\n () => ({ importDocuments: (items: unknown[]) => cms.importDocuments(collectionId, items) }),\n [cms, collectionId],\n )\n const resolveAsset = useCallback(\n (id: string): AssetDisplay | undefined => {\n const record = assetStore?.assets.find((asset) => asset.id === id)\n if (!record) return undefined\n const alt = assetAlt(record) // EC-587: the one reader\n return {\n ...(record.url !== undefined ? { url: record.url } : {}),\n contentType: record.contentType,\n ...(record.title !== undefined ? { title: record.title } : {}),\n filename: record.filename,\n ...(alt !== null ? { alt } : {}),\n }\n },\n [assetStore],\n )\n const liveUrls = useCallback(() => liveUrlMap, [liveUrlMap])\n // EC-595 A2: the CMS writes revisioned documents — patches at the source.\n const writes = useMemo<DocumentWrites>(\n () => ({\n kind: 'document',\n commit: cms.commitPatches,\n revisionOf: cms.revisionOf,\n insert: cms.insertDocument,\n newId: cms.newDocumentId,\n conflicts: cms.documentConflicts,\n resolveConflict: cms.resolveConflict,\n adoptSnapshot: cms.adoptDocumentSnapshot,\n flush: (ref) => cms.flushPersistence(ref),\n replace: cms.replaceDocument,\n subscribe: cms.subscribeDocument,\n reportPresence: cms.reportPresence,\n clearPresence: cms.clearPresence,\n presenceSessionId: cms.presenceSessionId,\n }),\n [cms],\n )\n\n return useMemo(() => {\n if (!collection) return null\n const capabilities: CollectionViewCapabilities = {\n publish: true,\n versions: true,\n locales: true,\n inlineEdit: true,\n create: true,\n duplicate: true, // EC-442: CMS collections only (owner call — rails skip)\n remove: true,\n bulk: true,\n liveUrl: isRoutable,\n hierarchy: isHierarchical(collection),\n importExport: true,\n }\n const source: CollectionViewSource = {\n collectionId,\n schema: collection,\n capabilities,\n ...(cms.projectId !== undefined ? { projectId: cms.projectId } : {}),\n assetCollectionId,\n localeConfig: cms.localeConfig,\n activeLocale: cms.activeLocale,\n documents,\n get: cms.getDocument,\n titleOf: cms.titleOf,\n // References are collection-based (EC-072): resolve candidates of ANY target\n // collection the schema declares — the workspace doc store already serves them.\n referenceCandidates: cms.documentsOf,\n collections: cms.collections,\n isFieldReadOnly: cms.isFieldReadOnly,\n validate: cms.validate,\n resolveAsset,\n lastError: cms.persistError,\n clearLastError: cms.clearPersistError,\n // Queue flush, so a pre-flight reference-index read is current.\n flush: () => cms.flushPersistence(),\n detailPath: (id: string) => studioPath.cmsDocument(collectionId, id),\n listPath: studioPath.cmsCollection(collectionId),\n viewState,\n setViewState,\n views,\n imports,\n create,\n writes,\n remove: cms.deleteDocument,\n bulk: (ids, action, setField) => cms.runBulk(collectionId, ids, action, setField),\n publishing: {\n isPublished: cms.isPublished,\n // EC-449: the materialized changed flag behind the amber `$state` dot\n // (chokepoint-computed; the store re-stamps it on save/publish/unpublish).\n hasUnpublishedChanges: cms.documentHasUnpublishedChanges,\n publish: cms.publishDocument,\n unpublish: cms.unpublishDocument,\n liveUrls,\n },\n // EC-445/EC-447: per-document timestamps behind the $updatedAt + $publishedAt\n // columns, sourced from the store (hydration seeds them from the persisted\n // `cmsDocuments.updatedAt` / `.publishedAt`; the store re-stamps on the edit +\n // publish/unpublish paths). Both are referentially-stable store accessors.\n timestamps: {\n updatedAt: cms.documentUpdatedAt,\n publishedAt: cms.documentPublishedAt,\n },\n versioning: {\n restore: (ref: DocumentRef, version: number, scope) =>\n cms.restoreDocumentVersion(ref, version, scope),\n // Flush-before-read ordering folds into the loader (EC-224) so any surface\n // that mutated the history still lists an authoritative version set.\n load: async (ref: DocumentRef) => {\n await cms.flushPersistence()\n return cms.loadDocumentVersions(ref)\n },\n },\n inlineEditing: {\n commitField: (input) => cms.commitFieldEdit(input),\n },\n }\n return source\n }, [\n collection,\n collectionId,\n cms,\n isRoutable,\n documents,\n viewState,\n setViewState,\n views,\n imports,\n create,\n resolveAsset,\n liveUrls,\n assetCollectionId,\n writes,\n ])\n}\n"]}
|
|
@@ -4,6 +4,7 @@ import { FilePlus2, FileText, Loader2, Search } from 'lucide-react';
|
|
|
4
4
|
import { STUDIO_PROJECT_ROUTE, studioPath, useNavigate } from '@elytracms/studio/router';
|
|
5
5
|
import { Button, Code, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, Input, Row, Stack, Text, } from '@elytracms/studio/ui';
|
|
6
6
|
import { cn } from '@elytracms/studio/lib/utils';
|
|
7
|
+
import { getStudioRegistry } from '@elytracms/studio/lib/registry';
|
|
7
8
|
import { localizedLabel, translate } from '@elytracms/studio/lib/i18n';
|
|
8
9
|
import { CmsSessionGate } from '../project-session.js';
|
|
9
10
|
import { CmsProvider, useCms } from '../cms/cms-store.js';
|
|
@@ -69,7 +70,7 @@ export function TransferPicker({ projectId, node, onClose, }) {
|
|
|
69
70
|
// same session. Allow-gated (0.0.44 follow-up): a target
|
|
70
71
|
// whose field's `allow` vocabulary rejects this component is never offered,
|
|
71
72
|
// new-document or existing-document alike — the validator would flag it.
|
|
72
|
-
const targets = useMemo(() => collectionsWithBlocks(cms.collections).filter((t) => isComponentAllowed(t.field, node.componentId)), [cms.collections, node.componentId]);
|
|
73
|
+
const targets = useMemo(() => collectionsWithBlocks(cms.collections).filter((t) => isComponentAllowed(t.field, node.componentId, getStudioRegistry().registry.lookup)), [cms.collections, node.componentId]);
|
|
73
74
|
/**
|
|
74
75
|
* Persist (awaited) + stage + navigate — shared by both transfer paths. EC-595 A2: a
|
|
75
76
|
* NEW document is one insert-if-absent create; an EXISTING one gets the transfer's
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transfer-dialog.js","sourceRoot":"","sources":["../../../src/components/components/transfer-dialog.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useMemo, useState } from 'react'\nimport { FilePlus2, FileText, Loader2, Search } from 'lucide-react'\nimport type { ReactNode } from 'react'\nimport type { ComponentManifest } from '@elytracms/core/component-registry'\nimport type { CmsDocument, DocumentPatch } from '@elytracms/core/cms-core'\nimport type { ComponentNode } from '@elytracms/core/project-graph'\nimport { STUDIO_PROJECT_ROUTE, studioPath, useNavigate } from '@elytracms/studio/router'\nimport {\n Button,\n Code,\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n Input,\n Row,\n Stack,\n Text,\n} from '@elytracms/studio/ui'\nimport { cn } from '@elytracms/studio/lib/utils'\nimport { localizedLabel, translate } from '@elytracms/studio/lib/i18n'\nimport { CmsSessionGate } from '../project-session'\nimport { CmsProvider, useCms } from '../cms/cms-store'\nimport { stageDocument } from '../cms/document-staging'\nimport { resolveCmsInitialCollections } from '../app-views/default-modules'\nimport {\n appendToBlockDocument,\n buildBlockDocument,\n canAppendTo,\n collectionsWithBlocks,\n isComponentAllowed,\n} from './transfer'\nimport type { CollectionBlocksTarget } from './transfer'\n\n/** Existing documents offered per target, beyond which the rest are hidden (filter to find them). */\nconst MAX_EXISTING_TARGETS = 8\n\n/**\n * EC-320 (+ 0.0.44 follow-up) — \"Use in document…\". Transfers the current\n * component combination (the live preview {@link ComponentNode}, edited props\n * and all) into a document's blocks field, then opens that document. Each\n * eligible collection+field target offers a NEW draft AND its existing\n * documents to append into.\n *\n * The dialog mounts a CMS session (so it reuses the store's create/save machinery,\n * collision-free ids and persistence) only while open. On confirm it builds the\n * draft or the append (pure {@link buildBlockDocument} / {@link appendToBlockDocument}),\n * saves it and AWAITS the persisted write landing (belt and braces alongside the\n * STAGE for the next CMS mount, see `document-staging` — both matter: staging\n * covers the very next mount regardless of mode, the await guards any later\n * reload), then navigates to the document — focused on its blocks field so it\n * opens on the canvas.\n */\nexport function TransferToDocumentDialog({\n projectId,\n manifest,\n node,\n open,\n onOpenChange,\n}: {\n projectId: string\n manifest: ComponentManifest\n node: ComponentNode\n open: boolean\n onOpenChange: (open: boolean) => void\n}) {\n return (\n <Dialog open={open} onOpenChange={onOpenChange}>\n <DialogContent className=\"max-w-md\">\n <DialogHeader>\n <DialogTitle>\n {translate('components.transfer.title', { name: localizedLabel(manifest.title, manifest.id) })}\n </DialogTitle>\n <DialogDescription>{translate('components.transfer.description')}</DialogDescription>\n </DialogHeader>\n {open ? (\n <CmsSessionGate>\n {(cms) => {\n // Delegate to the shared, tested resolver (also used by the CMS/Assets\n // tabs) so this dialog's targets stay in sync with what those tabs show,\n // rather than reimplementing the ladder inline. (EC-479 removed the\n // reason the old inline version was subtly wrong: `CmsProvider` had a\n // `?? sampleFilterableCollections` fixture fallback that an inline `[]`\n // \"suppressed\" — there is no fixture fallback to suppress now, so the\n // distinction between `[]` and `undefined` no longer carries meaning.)\n const collections = resolveCmsInitialCollections(cms)\n return (\n <CmsProvider\n projectId={projectId}\n initialCollections={collections}\n initialDocuments={cms?.documents}\n initialPublishedKeys={cms?.publishedKeys}\n initialRevisions={cms?.revisions}\n // EC-461: hydrated seed (both modes now) supersedes staged docs.\n seedReflectsWrites={cms !== null}\n >\n <TransferPicker projectId={projectId} node={node} onClose={() => onOpenChange(false)} />\n </CmsProvider>\n )\n }}\n </CmsSessionGate>\n ) : null}\n </DialogContent>\n </Dialog>\n )\n}\n\n/** A pending transfer's in-flight target key: `<collectionId>/new` or `<collectionId>/<docId>`. */\nfunction newTargetKey(target: CollectionBlocksTarget): string {\n return `${target.collection.id}/new`\n}\nfunction existingTargetKey(target: CollectionBlocksTarget, doc: CmsDocument): string {\n return `${target.collection.id}/${doc.id}`\n}\n\n/**\n * Exported for the fresh-Convex-project regression test (bug fix,\n * transfer-dialog.test.tsx) — `TransferToDocumentDialog` is otherwise its only\n * caller.\n */\nexport function TransferPicker({\n projectId,\n node,\n onClose,\n}: {\n projectId: string\n node: ComponentNode\n onClose: () => void\n}) {\n const cms = useCms()\n const navigate = useNavigate()\n const [query, setQuery] = useState('')\n // The in-flight target's key while its save is awaited (disables every row\n // + shows a spinner on the clicked one) — `null` when idle.\n const [pendingKey, setPendingKey] = useState<string | null>(null)\n\n // Read the LIVE store schema (`cms.collections`) rather than re-deriving it\n // from the same `resolveCmsInitialCollections` result passed to `CmsProvider`\n // above — `cms.collections` is what the provider actually resolved to, so the\n // targets list can never drift from what the CMS/Assets tabs show for this\n // same session. Allow-gated (0.0.44 follow-up): a target\n // whose field's `allow` vocabulary rejects this component is never offered,\n // new-document or existing-document alike — the validator would flag it.\n const targets = useMemo(\n () => collectionsWithBlocks(cms.collections).filter((t) => isComponentAllowed(t.field, node.componentId)),\n [cms.collections, node.componentId],\n )\n\n /**\n * Persist (awaited) + stage + navigate — shared by both transfer paths. EC-595 A2: a\n * NEW document is one insert-if-absent create; an EXISTING one gets the transfer's\n * patch (one `insert` of the embed node at the end of its field) — never a\n * whole-document write, so a concurrent edit of that document is never overwritten.\n */\n async function persistAndOpen(\n doc: CmsDocument,\n target: CollectionBlocksTarget,\n patches?: readonly DocumentPatch[],\n ) {\n if (patches) await cms.commitPatches({ collection: doc.collection, id: doc.id }, patches)\n else cms.insertDocument(doc)\n // AWAIT the queued write landing, so a reload of the destination editor — not just\n // its very next mount — finds the document.\n await cms.flushPersistence({ collection: doc.collection, id: doc.id })\n // Cross-mount handoff for the very next `CmsProvider` mount, in EVERY mode (see\n // document-staging.ts): the session's view of the written document.\n stageDocument(projectId, cms.getDocument({ collection: doc.collection, id: doc.id }) ?? doc)\n onClose()\n void navigate({\n to: STUDIO_PROJECT_ROUTE,\n params: { projectId, _splat: studioPath.cmsDocument(target.collection.id, doc.id) },\n search: { field: `values/${target.field.name}` },\n })\n }\n\n async function transferNew(target: CollectionBlocksTarget) {\n const key = newTargetKey(target)\n setPendingKey(key)\n try {\n const doc = buildBlockDocument({\n collection: target.collection,\n blocksField: target.field,\n node,\n newId: cms.newDocumentId,\n locales: cms.localeConfig,\n })\n await persistAndOpen(doc, target)\n } finally {\n setPendingKey(null)\n }\n }\n\n async function transferInto(target: CollectionBlocksTarget, existing: CmsDocument) {\n const key = existingTargetKey(target, existing)\n setPendingKey(key)\n try {\n // The FORCED FLUSH before the transfer reads the document (the ruling).\n await cms.flushPersistence({ collection: existing.collection, id: existing.id })\n const current = cms.getDocument({ collection: existing.collection, id: existing.id }) ?? existing\n const { document: doc, patches } = appendToBlockDocument({\n collection: target.collection,\n document: current,\n blocksField: target.field,\n node,\n locale: cms.activeLocale,\n newId: cms.newDocumentId,\n })\n await persistAndOpen(doc, target, patches)\n } finally {\n setPendingKey(null)\n }\n }\n\n if (targets.length === 0) {\n return (\n <Text size=\"sm\" tone=\"muted\" data-testid=\"transfer-no-targets\">\n {translate('components.transfer.noTargets')}\n </Text>\n )\n }\n\n const trimmedQuery = query.trim().toLowerCase()\n const hasAnyExistingDocs = targets.some((t) => cms.documentsOf(t.collection.id).length > 0)\n\n return (\n <>\n {hasAnyExistingDocs ? (\n <div className=\"relative\">\n <Search className=\"pointer-events-none absolute left-2.5 top-1/2 size-3.5 -translate-y-1/2 text-muted-foreground\" />\n <Input\n value={query}\n onChange={(e) => setQuery(e.target.value)}\n placeholder={translate('common.searchPlaceholder')}\n className=\"pl-8\"\n />\n </div>\n ) : null}\n <Stack gap=\"4\" className=\"max-h-[50vh] overflow-y-auto\">\n {targets.map((target) => {\n const existing = cms\n .documentsOf(target.collection.id)\n .filter((doc) => canAppendTo({ collection: target.collection, document: doc, blocksField: target.field, locale: cms.activeLocale }))\n .filter(\n (doc) =>\n trimmedQuery === '' ||\n cms.titleOf({ collection: target.collection.id, id: doc.id }).toLowerCase().includes(trimmedQuery),\n )\n .slice(0, MAX_EXISTING_TARGETS)\n\n return (\n <Stack key={target.collection.id} gap=\"2\">\n <Stack gap=\"px\">\n <Text size=\"sm\" weight=\"medium\" className=\"truncate\">\n {localizedLabel(target.collection.form?.label, target.collection.id)}\n </Text>\n <Row gap=\"1\" align=\"center\">\n <Text size=\"xs\" tone=\"muted\">\n {translate('components.transfer.intoField')}\n </Text>\n <Code tone=\"muted\">{target.field.name}</Code>\n </Row>\n </Stack>\n <Stack gap=\"1\">\n <TransferTargetRow\n testId=\"transfer-target\"\n icon={<FilePlus2 className=\"size-4\" />}\n label={translate('components.transfer.newDocument', {\n collection: localizedLabel(target.collection.form?.labelSingular, target.collection.id),\n })}\n pending={pendingKey === newTargetKey(target)}\n disabled={pendingKey !== null}\n onClick={() => void transferNew(target)}\n />\n {existing.length > 0 ? (\n <Text size=\"xs\" tone=\"muted\" className=\"px-1 pt-1\">\n {translate('components.transfer.orExisting')}\n </Text>\n ) : null}\n {existing.map((doc) => (\n <TransferTargetRow\n key={doc.id}\n testId=\"transfer-target-existing\"\n icon={<FileText className=\"size-4\" />}\n label={cms.titleOf({ collection: target.collection.id, id: doc.id })}\n pending={pendingKey === existingTargetKey(target, doc)}\n disabled={pendingKey !== null}\n onClick={() => void transferInto(target, doc)}\n />\n ))}\n </Stack>\n </Stack>\n )\n })}\n </Stack>\n <DialogFooter>\n <Button type=\"button\" variant=\"ghost\" onClick={onClose} disabled={pendingKey !== null}>\n {translate('common.cancel')}\n </Button>\n </DialogFooter>\n </>\n )\n}\n\n/** One selectable transfer target row — a new draft, or an existing document to append into. */\nfunction TransferTargetRow({\n testId,\n icon,\n label,\n pending,\n disabled,\n onClick,\n}: {\n testId: string\n icon: ReactNode\n label: string\n pending: boolean\n disabled: boolean\n onClick: () => void\n}) {\n return (\n <button\n type=\"button\"\n data-testid={testId}\n disabled={disabled}\n onClick={onClick}\n className={cn(\n 'flex items-center gap-3 rounded-md border border-border p-3 text-left transition-colors hover:border-primary/40 hover:bg-muted',\n disabled && 'cursor-not-allowed opacity-60',\n )}\n >\n <span className=\"grid size-8 shrink-0 place-items-center rounded-md bg-muted text-muted-foreground\">\n {pending ? <Loader2 className=\"size-4 animate-spin\" /> : icon}\n </span>\n <Text size=\"sm\" weight=\"medium\" className=\"min-w-0 flex-1 truncate\">\n {label}\n </Text>\n </button>\n )\n}\n"]}
|
|
1
|
+
{"version":3,"file":"transfer-dialog.js","sourceRoot":"","sources":["../../../src/components/components/transfer-dialog.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AACzC,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AAKnE,OAAO,EAAE,oBAAoB,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAA;AACxF,OAAO,EACL,MAAM,EACN,IAAI,EACJ,MAAM,EACN,aAAa,EACb,iBAAiB,EACjB,YAAY,EACZ,YAAY,EACZ,WAAW,EACX,KAAK,EACL,GAAG,EACH,KAAK,EACL,IAAI,GACL,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EAAE,EAAE,EAAE,MAAM,6BAA6B,CAAA;AAChD,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAA;AAClE,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAA;AACtE,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AACnD,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAA;AACvD,OAAO,EAAE,4BAA4B,EAAE,MAAM,8BAA8B,CAAA;AAC3E,OAAO,EACL,qBAAqB,EACrB,kBAAkB,EAClB,WAAW,EACX,qBAAqB,EACrB,kBAAkB,GACnB,MAAM,YAAY,CAAA;AAGnB,qGAAqG;AACrG,MAAM,oBAAoB,GAAG,CAAC,CAAA;AAE9B;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,wBAAwB,CAAC,EACvC,SAAS,EACT,QAAQ,EACR,IAAI,EACJ,IAAI,EACJ,YAAY,GAOb;IACC,OAAO,CACL,KAAC,MAAM,IAAC,IAAI,EAAE,IAAI,EAAE,YAAY,EAAE,YAAY,YAC5C,MAAC,aAAa,IAAC,SAAS,EAAC,UAAU,aACjC,MAAC,YAAY,eACX,KAAC,WAAW,cACT,SAAS,CAAC,2BAA2B,EAAE,EAAE,IAAI,EAAE,cAAc,CAAC,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC,GAClF,EACd,KAAC,iBAAiB,cAAE,SAAS,CAAC,iCAAiC,CAAC,GAAqB,IACxE,EACd,IAAI,CAAC,CAAC,CAAC,CACN,KAAC,cAAc,cACZ,CAAC,GAAG,EAAE,EAAE;wBACP,uEAAuE;wBACvE,yEAAyE;wBACzE,oEAAoE;wBACpE,sEAAsE;wBACtE,wEAAwE;wBACxE,sEAAsE;wBACtE,uEAAuE;wBACvE,MAAM,WAAW,GAAG,4BAA4B,CAAC,GAAG,CAAC,CAAA;wBACrD,OAAO,CACL,KAAC,WAAW,IACV,SAAS,EAAE,SAAS,EACpB,kBAAkB,EAAE,WAAW,EAC/B,gBAAgB,EAAE,GAAG,EAAE,SAAS,EAChC,oBAAoB,EAAE,GAAG,EAAE,aAAa,EACxC,gBAAgB,EAAE,GAAG,EAAE,SAAS;4BAChC,iEAAiE;4BACjE,kBAAkB,EAAE,GAAG,KAAK,IAAI,YAEhC,KAAC,cAAc,IAAC,SAAS,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,YAAY,CAAC,KAAK,CAAC,GAAI,GAC5E,CACf,CAAA;oBACH,CAAC,GACc,CAClB,CAAC,CAAC,CAAC,IAAI,IACM,GACT,CACV,CAAA;AACH,CAAC;AAED,mGAAmG;AACnG,SAAS,YAAY,CAAC,MAA8B;IAClD,OAAO,GAAG,MAAM,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;AACtC,CAAC;AACD,SAAS,iBAAiB,CAAC,MAA8B,EAAE,GAAgB;IACzE,OAAO,GAAG,MAAM,CAAC,UAAU,CAAC,EAAE,IAAI,GAAG,CAAC,EAAE,EAAE,CAAA;AAC5C,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,EAC7B,SAAS,EACT,IAAI,EACJ,OAAO,GAKR;IACC,MAAM,GAAG,GAAG,MAAM,EAAE,CAAA;IACpB,MAAM,QAAQ,GAAG,WAAW,EAAE,CAAA;IAC9B,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAC,EAAE,CAAC,CAAA;IACtC,2EAA2E;IAC3E,4DAA4D;IAC5D,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,GAAG,QAAQ,CAAgB,IAAI,CAAC,CAAA;IAEjE,4EAA4E;IAC5E,8EAA8E;IAC9E,8EAA8E;IAC9E,2EAA2E;IAC3E,yDAAyD;IACzD,4EAA4E;IAC5E,yEAAyE;IACzE,MAAM,OAAO,GAAG,OAAO,CACrB,GAAG,EAAE,CACH,qBAAqB,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAClD,kBAAkB,CAAC,CAAC,CAAC,KAAK,EAAE,IAAI,CAAC,WAAW,EAAE,iBAAiB,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,CACnF,EACH,CAAC,GAAG,CAAC,WAAW,EAAE,IAAI,CAAC,WAAW,CAAC,CACpC,CAAA;IAED;;;;;OAKG;IACH,KAAK,UAAU,cAAc,CAC3B,GAAgB,EAChB,MAA8B,EAC9B,OAAkC;QAElC,IAAI,OAAO;YAAE,MAAM,GAAG,CAAC,aAAa,CAAC,EAAE,UAAU,EAAE,GAAG,CAAC,UAAU,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,EAAE,OAAO,CAAC,CAAA;;YACpF,GAAG,CAAC,cAAc,CAAC,GAAG,CAAC,CAAA;QAC5B,mFAAmF;QACnF,4CAA4C;QAC5C,MAAM,GAAG,CAAC,gBAAgB,CAAC,EAAE,UAAU,EAAE,GAAG,CAAC,UAAU,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,CAAC,CAAA;QACtE,gFAAgF;QAChF,oEAAoE;QACpE,aAAa,CAAC,SAAS,EAAE,GAAG,CAAC,WAAW,CAAC,EAAE,UAAU,EAAE,GAAG,CAAC,UAAU,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC,CAAA;QAC5F,OAAO,EAAE,CAAA;QACT,KAAK,QAAQ,CAAC;YACZ,EAAE,EAAE,oBAAoB;YACxB,MAAM,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,CAAC,WAAW,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,EAAE,GAAG,CAAC,EAAE,CAAC,EAAE;YACnF,MAAM,EAAE,EAAE,KAAK,EAAE,UAAU,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE;SACjD,CAAC,CAAA;IACJ,CAAC;IAED,KAAK,UAAU,WAAW,CAAC,MAA8B;QACvD,MAAM,GAAG,GAAG,YAAY,CAAC,MAAM,CAAC,CAAA;QAChC,aAAa,CAAC,GAAG,CAAC,CAAA;QAClB,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,kBAAkB,CAAC;gBAC7B,UAAU,EAAE,MAAM,CAAC,UAAU;gBAC7B,WAAW,EAAE,MAAM,CAAC,KAAK;gBACzB,IAAI;gBACJ,KAAK,EAAE,GAAG,CAAC,aAAa;gBACxB,OAAO,EAAE,GAAG,CAAC,YAAY;aAC1B,CAAC,CAAA;YACF,MAAM,cAAc,CAAC,GAAG,EAAE,MAAM,CAAC,CAAA;QACnC,CAAC;gBAAS,CAAC;YACT,aAAa,CAAC,IAAI,CAAC,CAAA;QACrB,CAAC;IACH,CAAC;IAED,KAAK,UAAU,YAAY,CAAC,MAA8B,EAAE,QAAqB;QAC/E,MAAM,GAAG,GAAG,iBAAiB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAA;QAC/C,aAAa,CAAC,GAAG,CAAC,CAAA;QAClB,IAAI,CAAC;YACH,wEAAwE;YACxE,MAAM,GAAG,CAAC,gBAAgB,CAAC,EAAE,UAAU,EAAE,QAAQ,CAAC,UAAU,EAAE,EAAE,EAAE,QAAQ,CAAC,EAAE,EAAE,CAAC,CAAA;YAChF,MAAM,OAAO,GAAG,GAAG,CAAC,WAAW,CAAC,EAAE,UAAU,EAAE,QAAQ,CAAC,UAAU,EAAE,EAAE,EAAE,QAAQ,CAAC,EAAE,EAAE,CAAC,IAAI,QAAQ,CAAA;YACjG,MAAM,EAAE,QAAQ,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,qBAAqB,CAAC;gBACvD,UAAU,EAAE,MAAM,CAAC,UAAU;gBAC7B,QAAQ,EAAE,OAAO;gBACjB,WAAW,EAAE,MAAM,CAAC,KAAK;gBACzB,IAAI;gBACJ,MAAM,EAAE,GAAG,CAAC,YAAY;gBACxB,KAAK,EAAE,GAAG,CAAC,aAAa;aACzB,CAAC,CAAA;YACF,MAAM,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,CAAA;QAC5C,CAAC;gBAAS,CAAC;YACT,aAAa,CAAC,IAAI,CAAC,CAAA;QACrB,CAAC;IACH,CAAC;IAED,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,CACL,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,iBAAa,qBAAqB,YAC3D,SAAS,CAAC,+BAA+B,CAAC,GACtC,CACR,CAAA;IACH,CAAC;IAED,MAAM,YAAY,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAA;IAC/C,MAAM,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;IAE3F,OAAO,CACL,8BACG,kBAAkB,CAAC,CAAC,CAAC,CACpB,eAAK,SAAS,EAAC,UAAU,aACvB,KAAC,MAAM,IAAC,SAAS,EAAC,+FAA+F,GAAG,EACpH,KAAC,KAAK,IACJ,KAAK,EAAE,KAAK,EACZ,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EACzC,WAAW,EAAE,SAAS,CAAC,0BAA0B,CAAC,EAClD,SAAS,EAAC,MAAM,GAChB,IACE,CACP,CAAC,CAAC,CAAC,IAAI,EACR,KAAC,KAAK,IAAC,GAAG,EAAC,GAAG,EAAC,SAAS,EAAC,8BAA8B,YACpD,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE;oBACtB,MAAM,QAAQ,GAAG,GAAG;yBACjB,WAAW,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC;yBACjC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,WAAW,CAAC,EAAE,UAAU,EAAE,MAAM,CAAC,UAAU,EAAE,QAAQ,EAAE,GAAG,EAAE,WAAW,EAAE,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,CAAC,YAAY,EAAE,CAAC,CAAC;yBACnI,MAAM,CACL,CAAC,GAAG,EAAE,EAAE,CACN,YAAY,KAAK,EAAE;wBACnB,GAAG,CAAC,OAAO,CAAC,EAAE,UAAU,EAAE,MAAM,CAAC,UAAU,CAAC,EAAE,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,YAAY,CAAC,CACrG;yBACA,KAAK,CAAC,CAAC,EAAE,oBAAoB,CAAC,CAAA;oBAEjC,OAAO,CACL,MAAC,KAAK,IAA4B,GAAG,EAAC,GAAG,aACvC,MAAC,KAAK,IAAC,GAAG,EAAC,IAAI,aACb,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,MAAM,EAAC,QAAQ,EAAC,SAAS,EAAC,UAAU,YACjD,cAAc,CAAC,MAAM,CAAC,UAAU,CAAC,IAAI,EAAE,KAAK,EAAE,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC,GAC/D,EACP,MAAC,GAAG,IAAC,GAAG,EAAC,GAAG,EAAC,KAAK,EAAC,QAAQ,aACzB,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,YACzB,SAAS,CAAC,+BAA+B,CAAC,GACtC,EACP,KAAC,IAAI,IAAC,IAAI,EAAC,OAAO,YAAE,MAAM,CAAC,KAAK,CAAC,IAAI,GAAQ,IACzC,IACA,EACR,MAAC,KAAK,IAAC,GAAG,EAAC,GAAG,aACZ,KAAC,iBAAiB,IAChB,MAAM,EAAC,iBAAiB,EACxB,IAAI,EAAE,KAAC,SAAS,IAAC,SAAS,EAAC,QAAQ,GAAG,EACtC,KAAK,EAAE,SAAS,CAAC,iCAAiC,EAAE;4CAClD,UAAU,EAAE,cAAc,CAAC,MAAM,CAAC,UAAU,CAAC,IAAI,EAAE,aAAa,EAAE,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC;yCACxF,CAAC,EACF,OAAO,EAAE,UAAU,KAAK,YAAY,CAAC,MAAM,CAAC,EAC5C,QAAQ,EAAE,UAAU,KAAK,IAAI,EAC7B,OAAO,EAAE,GAAG,EAAE,CAAC,KAAK,WAAW,CAAC,MAAM,CAAC,GACvC,EACD,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CACrB,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,EAAC,SAAS,EAAC,WAAW,YAC/C,SAAS,CAAC,gCAAgC,CAAC,GACvC,CACR,CAAC,CAAC,CAAC,IAAI,EACP,QAAQ,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CACrB,KAAC,iBAAiB,IAEhB,MAAM,EAAC,0BAA0B,EACjC,IAAI,EAAE,KAAC,QAAQ,IAAC,SAAS,EAAC,QAAQ,GAAG,EACrC,KAAK,EAAE,GAAG,CAAC,OAAO,CAAC,EAAE,UAAU,EAAE,MAAM,CAAC,UAAU,CAAC,EAAE,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,CAAC,EACpE,OAAO,EAAE,UAAU,KAAK,iBAAiB,CAAC,MAAM,EAAE,GAAG,CAAC,EACtD,QAAQ,EAAE,UAAU,KAAK,IAAI,EAC7B,OAAO,EAAE,GAAG,EAAE,CAAC,KAAK,YAAY,CAAC,MAAM,EAAE,GAAG,CAAC,IANxC,GAAG,CAAC,EAAE,CAOX,CACH,CAAC,IACI,KAvCE,MAAM,CAAC,UAAU,CAAC,EAAE,CAwCxB,CACT,CAAA;gBACH,CAAC,CAAC,GACI,EACR,KAAC,YAAY,cACX,KAAC,MAAM,IAAC,IAAI,EAAC,QAAQ,EAAC,OAAO,EAAC,OAAO,EAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,UAAU,KAAK,IAAI,YAClF,SAAS,CAAC,eAAe,CAAC,GACpB,GACI,IACd,CACJ,CAAA;AACH,CAAC;AAED,gGAAgG;AAChG,SAAS,iBAAiB,CAAC,EACzB,MAAM,EACN,IAAI,EACJ,KAAK,EACL,OAAO,EACP,QAAQ,EACR,OAAO,GAQR;IACC,OAAO,CACL,kBACE,IAAI,EAAC,QAAQ,iBACA,MAAM,EACnB,QAAQ,EAAE,QAAQ,EAClB,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,EAAE,CACX,gIAAgI,EAChI,QAAQ,IAAI,+BAA+B,CAC5C,aAED,eAAM,SAAS,EAAC,mFAAmF,YAChG,OAAO,CAAC,CAAC,CAAC,KAAC,OAAO,IAAC,SAAS,EAAC,qBAAqB,GAAG,CAAC,CAAC,CAAC,IAAI,GACxD,EACP,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,MAAM,EAAC,QAAQ,EAAC,SAAS,EAAC,yBAAyB,YAChE,KAAK,GACD,IACA,CACV,CAAA;AACH,CAAC","sourcesContent":["import { useMemo, useState } from 'react'\nimport { FilePlus2, FileText, Loader2, Search } from 'lucide-react'\nimport type { ReactNode } from 'react'\nimport type { ComponentManifest } from '@elytracms/core/component-registry'\nimport type { CmsDocument, DocumentPatch } from '@elytracms/core/cms-core'\nimport type { ComponentNode } from '@elytracms/core/project-graph'\nimport { STUDIO_PROJECT_ROUTE, studioPath, useNavigate } from '@elytracms/studio/router'\nimport {\n Button,\n Code,\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n Input,\n Row,\n Stack,\n Text,\n} from '@elytracms/studio/ui'\nimport { cn } from '@elytracms/studio/lib/utils'\nimport { getStudioRegistry } from '@elytracms/studio/lib/registry'\nimport { localizedLabel, translate } from '@elytracms/studio/lib/i18n'\nimport { CmsSessionGate } from '../project-session'\nimport { CmsProvider, useCms } from '../cms/cms-store'\nimport { stageDocument } from '../cms/document-staging'\nimport { resolveCmsInitialCollections } from '../app-views/default-modules'\nimport {\n appendToBlockDocument,\n buildBlockDocument,\n canAppendTo,\n collectionsWithBlocks,\n isComponentAllowed,\n} from './transfer'\nimport type { CollectionBlocksTarget } from './transfer'\n\n/** Existing documents offered per target, beyond which the rest are hidden (filter to find them). */\nconst MAX_EXISTING_TARGETS = 8\n\n/**\n * EC-320 (+ 0.0.44 follow-up) — \"Use in document…\". Transfers the current\n * component combination (the live preview {@link ComponentNode}, edited props\n * and all) into a document's blocks field, then opens that document. Each\n * eligible collection+field target offers a NEW draft AND its existing\n * documents to append into.\n *\n * The dialog mounts a CMS session (so it reuses the store's create/save machinery,\n * collision-free ids and persistence) only while open. On confirm it builds the\n * draft or the append (pure {@link buildBlockDocument} / {@link appendToBlockDocument}),\n * saves it and AWAITS the persisted write landing (belt and braces alongside the\n * STAGE for the next CMS mount, see `document-staging` — both matter: staging\n * covers the very next mount regardless of mode, the await guards any later\n * reload), then navigates to the document — focused on its blocks field so it\n * opens on the canvas.\n */\nexport function TransferToDocumentDialog({\n projectId,\n manifest,\n node,\n open,\n onOpenChange,\n}: {\n projectId: string\n manifest: ComponentManifest\n node: ComponentNode\n open: boolean\n onOpenChange: (open: boolean) => void\n}) {\n return (\n <Dialog open={open} onOpenChange={onOpenChange}>\n <DialogContent className=\"max-w-md\">\n <DialogHeader>\n <DialogTitle>\n {translate('components.transfer.title', { name: localizedLabel(manifest.title, manifest.id) })}\n </DialogTitle>\n <DialogDescription>{translate('components.transfer.description')}</DialogDescription>\n </DialogHeader>\n {open ? (\n <CmsSessionGate>\n {(cms) => {\n // Delegate to the shared, tested resolver (also used by the CMS/Assets\n // tabs) so this dialog's targets stay in sync with what those tabs show,\n // rather than reimplementing the ladder inline. (EC-479 removed the\n // reason the old inline version was subtly wrong: `CmsProvider` had a\n // `?? sampleFilterableCollections` fixture fallback that an inline `[]`\n // \"suppressed\" — there is no fixture fallback to suppress now, so the\n // distinction between `[]` and `undefined` no longer carries meaning.)\n const collections = resolveCmsInitialCollections(cms)\n return (\n <CmsProvider\n projectId={projectId}\n initialCollections={collections}\n initialDocuments={cms?.documents}\n initialPublishedKeys={cms?.publishedKeys}\n initialRevisions={cms?.revisions}\n // EC-461: hydrated seed (both modes now) supersedes staged docs.\n seedReflectsWrites={cms !== null}\n >\n <TransferPicker projectId={projectId} node={node} onClose={() => onOpenChange(false)} />\n </CmsProvider>\n )\n }}\n </CmsSessionGate>\n ) : null}\n </DialogContent>\n </Dialog>\n )\n}\n\n/** A pending transfer's in-flight target key: `<collectionId>/new` or `<collectionId>/<docId>`. */\nfunction newTargetKey(target: CollectionBlocksTarget): string {\n return `${target.collection.id}/new`\n}\nfunction existingTargetKey(target: CollectionBlocksTarget, doc: CmsDocument): string {\n return `${target.collection.id}/${doc.id}`\n}\n\n/**\n * Exported for the fresh-Convex-project regression test (bug fix,\n * transfer-dialog.test.tsx) — `TransferToDocumentDialog` is otherwise its only\n * caller.\n */\nexport function TransferPicker({\n projectId,\n node,\n onClose,\n}: {\n projectId: string\n node: ComponentNode\n onClose: () => void\n}) {\n const cms = useCms()\n const navigate = useNavigate()\n const [query, setQuery] = useState('')\n // The in-flight target's key while its save is awaited (disables every row\n // + shows a spinner on the clicked one) — `null` when idle.\n const [pendingKey, setPendingKey] = useState<string | null>(null)\n\n // Read the LIVE store schema (`cms.collections`) rather than re-deriving it\n // from the same `resolveCmsInitialCollections` result passed to `CmsProvider`\n // above — `cms.collections` is what the provider actually resolved to, so the\n // targets list can never drift from what the CMS/Assets tabs show for this\n // same session. Allow-gated (0.0.44 follow-up): a target\n // whose field's `allow` vocabulary rejects this component is never offered,\n // new-document or existing-document alike — the validator would flag it.\n const targets = useMemo(\n () =>\n collectionsWithBlocks(cms.collections).filter((t) =>\n isComponentAllowed(t.field, node.componentId, getStudioRegistry().registry.lookup),\n ),\n [cms.collections, node.componentId],\n )\n\n /**\n * Persist (awaited) + stage + navigate — shared by both transfer paths. EC-595 A2: a\n * NEW document is one insert-if-absent create; an EXISTING one gets the transfer's\n * patch (one `insert` of the embed node at the end of its field) — never a\n * whole-document write, so a concurrent edit of that document is never overwritten.\n */\n async function persistAndOpen(\n doc: CmsDocument,\n target: CollectionBlocksTarget,\n patches?: readonly DocumentPatch[],\n ) {\n if (patches) await cms.commitPatches({ collection: doc.collection, id: doc.id }, patches)\n else cms.insertDocument(doc)\n // AWAIT the queued write landing, so a reload of the destination editor — not just\n // its very next mount — finds the document.\n await cms.flushPersistence({ collection: doc.collection, id: doc.id })\n // Cross-mount handoff for the very next `CmsProvider` mount, in EVERY mode (see\n // document-staging.ts): the session's view of the written document.\n stageDocument(projectId, cms.getDocument({ collection: doc.collection, id: doc.id }) ?? doc)\n onClose()\n void navigate({\n to: STUDIO_PROJECT_ROUTE,\n params: { projectId, _splat: studioPath.cmsDocument(target.collection.id, doc.id) },\n search: { field: `values/${target.field.name}` },\n })\n }\n\n async function transferNew(target: CollectionBlocksTarget) {\n const key = newTargetKey(target)\n setPendingKey(key)\n try {\n const doc = buildBlockDocument({\n collection: target.collection,\n blocksField: target.field,\n node,\n newId: cms.newDocumentId,\n locales: cms.localeConfig,\n })\n await persistAndOpen(doc, target)\n } finally {\n setPendingKey(null)\n }\n }\n\n async function transferInto(target: CollectionBlocksTarget, existing: CmsDocument) {\n const key = existingTargetKey(target, existing)\n setPendingKey(key)\n try {\n // The FORCED FLUSH before the transfer reads the document (the ruling).\n await cms.flushPersistence({ collection: existing.collection, id: existing.id })\n const current = cms.getDocument({ collection: existing.collection, id: existing.id }) ?? existing\n const { document: doc, patches } = appendToBlockDocument({\n collection: target.collection,\n document: current,\n blocksField: target.field,\n node,\n locale: cms.activeLocale,\n newId: cms.newDocumentId,\n })\n await persistAndOpen(doc, target, patches)\n } finally {\n setPendingKey(null)\n }\n }\n\n if (targets.length === 0) {\n return (\n <Text size=\"sm\" tone=\"muted\" data-testid=\"transfer-no-targets\">\n {translate('components.transfer.noTargets')}\n </Text>\n )\n }\n\n const trimmedQuery = query.trim().toLowerCase()\n const hasAnyExistingDocs = targets.some((t) => cms.documentsOf(t.collection.id).length > 0)\n\n return (\n <>\n {hasAnyExistingDocs ? (\n <div className=\"relative\">\n <Search className=\"pointer-events-none absolute left-2.5 top-1/2 size-3.5 -translate-y-1/2 text-muted-foreground\" />\n <Input\n value={query}\n onChange={(e) => setQuery(e.target.value)}\n placeholder={translate('common.searchPlaceholder')}\n className=\"pl-8\"\n />\n </div>\n ) : null}\n <Stack gap=\"4\" className=\"max-h-[50vh] overflow-y-auto\">\n {targets.map((target) => {\n const existing = cms\n .documentsOf(target.collection.id)\n .filter((doc) => canAppendTo({ collection: target.collection, document: doc, blocksField: target.field, locale: cms.activeLocale }))\n .filter(\n (doc) =>\n trimmedQuery === '' ||\n cms.titleOf({ collection: target.collection.id, id: doc.id }).toLowerCase().includes(trimmedQuery),\n )\n .slice(0, MAX_EXISTING_TARGETS)\n\n return (\n <Stack key={target.collection.id} gap=\"2\">\n <Stack gap=\"px\">\n <Text size=\"sm\" weight=\"medium\" className=\"truncate\">\n {localizedLabel(target.collection.form?.label, target.collection.id)}\n </Text>\n <Row gap=\"1\" align=\"center\">\n <Text size=\"xs\" tone=\"muted\">\n {translate('components.transfer.intoField')}\n </Text>\n <Code tone=\"muted\">{target.field.name}</Code>\n </Row>\n </Stack>\n <Stack gap=\"1\">\n <TransferTargetRow\n testId=\"transfer-target\"\n icon={<FilePlus2 className=\"size-4\" />}\n label={translate('components.transfer.newDocument', {\n collection: localizedLabel(target.collection.form?.labelSingular, target.collection.id),\n })}\n pending={pendingKey === newTargetKey(target)}\n disabled={pendingKey !== null}\n onClick={() => void transferNew(target)}\n />\n {existing.length > 0 ? (\n <Text size=\"xs\" tone=\"muted\" className=\"px-1 pt-1\">\n {translate('components.transfer.orExisting')}\n </Text>\n ) : null}\n {existing.map((doc) => (\n <TransferTargetRow\n key={doc.id}\n testId=\"transfer-target-existing\"\n icon={<FileText className=\"size-4\" />}\n label={cms.titleOf({ collection: target.collection.id, id: doc.id })}\n pending={pendingKey === existingTargetKey(target, doc)}\n disabled={pendingKey !== null}\n onClick={() => void transferInto(target, doc)}\n />\n ))}\n </Stack>\n </Stack>\n )\n })}\n </Stack>\n <DialogFooter>\n <Button type=\"button\" variant=\"ghost\" onClick={onClose} disabled={pendingKey !== null}>\n {translate('common.cancel')}\n </Button>\n </DialogFooter>\n </>\n )\n}\n\n/** One selectable transfer target row — a new draft, or an existing document to append into. */\nfunction TransferTargetRow({\n testId,\n icon,\n label,\n pending,\n disabled,\n onClick,\n}: {\n testId: string\n icon: ReactNode\n label: string\n pending: boolean\n disabled: boolean\n onClick: () => void\n}) {\n return (\n <button\n type=\"button\"\n data-testid={testId}\n disabled={disabled}\n onClick={onClick}\n className={cn(\n 'flex items-center gap-3 rounded-md border border-border p-3 text-left transition-colors hover:border-primary/40 hover:bg-muted',\n disabled && 'cursor-not-allowed opacity-60',\n )}\n >\n <span className=\"grid size-8 shrink-0 place-items-center rounded-md bg-muted text-muted-foreground\">\n {pending ? <Loader2 className=\"size-4 animate-spin\" /> : icon}\n </span>\n <Text size=\"sm\" weight=\"medium\" className=\"min-w-0 flex-1 truncate\">\n {label}\n </Text>\n </button>\n )\n}\n"]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { BlocksFieldDef, CmsDocument, CollectionDef, DocumentPatch, Locale, LocaleConfig } from '@elytracms/core/cms-core';
|
|
2
|
-
import type { ComponentNode } from '@elytracms/core/project-graph';
|
|
2
|
+
import type { ComponentLookup, ComponentNode } from '@elytracms/core/project-graph';
|
|
3
3
|
import type { IdFactory } from '@elytracms/core/persistence';
|
|
4
4
|
/**
|
|
5
5
|
* EC-320 (+ 0.0.44 follow-up) — the "design guide → real content" bridge. A
|
|
@@ -27,16 +27,16 @@ export interface CollectionBlocksTarget {
|
|
|
27
27
|
*/
|
|
28
28
|
export declare function collectionsWithBlocks(collections: readonly CollectionDef[]): CollectionBlocksTarget[];
|
|
29
29
|
/**
|
|
30
|
-
* Is `componentId` permitted as a top-level block of `field`?
|
|
31
|
-
*
|
|
32
|
-
* `allow
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
* targets the component isn't allowed into (new AND existing
|
|
37
|
-
* alike).
|
|
30
|
+
* Is `componentId` permitted as a top-level block of `field`? EC-584: a READER of the
|
|
31
|
+
* one core placement judge over the field's effective vocabulary (`fieldAllowList` —
|
|
32
|
+
* its `allow`, else the blocks default), the verdict `validateCompositionValue`
|
|
33
|
+
* reaches at the gate — so a target this predicate accepts can never fail its
|
|
34
|
+
* `forbidden-component` / `server-only-component` check. With `components` (the
|
|
35
|
+
* project's manifest lookup) canvas eligibility is judged too. The transfer dialog
|
|
36
|
+
* uses this to hide targets the component isn't allowed into (new AND existing
|
|
37
|
+
* document targets alike); the value-level sinks stay on the gate.
|
|
38
38
|
*/
|
|
39
|
-
export declare function isComponentAllowed(field: BlocksFieldDef, componentId: string): boolean;
|
|
39
|
+
export declare function isComponentAllowed(field: BlocksFieldDef, componentId: string, components?: ComponentLookup): boolean;
|
|
40
40
|
export interface BuildBlockDocumentInput {
|
|
41
41
|
collection: CollectionDef;
|
|
42
42
|
/** The blocks field the node lands in (from {@link collectionsWithBlocks}). */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { fieldPath, isBlocksField, isFieldLocalized, isSequenceValue, itemLimit, seedDefaults, writeField, } from '@elytracms/core/cms-core';
|
|
2
|
-
import { blockRootsOf, editBlockRoots } from '@elytracms/core/project-graph';
|
|
1
|
+
import { fieldAllowList, fieldPath, isBlocksField, isFieldLocalized, isSequenceValue, itemLimit, seedDefaults, writeField, } from '@elytracms/core/cms-core';
|
|
2
|
+
import { ANY_COMPONENT, blockRootsOf, editBlockRoots, placementRefusal } from '@elytracms/core/project-graph';
|
|
3
3
|
import { embedNodeFrom } from './component-preview.js';
|
|
4
4
|
/**
|
|
5
5
|
* Collections that can receive a transferred combination — those declaring a
|
|
@@ -19,17 +19,18 @@ export function collectionsWithBlocks(collections) {
|
|
|
19
19
|
return targets;
|
|
20
20
|
}
|
|
21
21
|
/**
|
|
22
|
-
* Is `componentId` permitted as a top-level block of `field`?
|
|
23
|
-
*
|
|
24
|
-
* `allow
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* targets the component isn't allowed into (new AND existing
|
|
29
|
-
* alike).
|
|
22
|
+
* Is `componentId` permitted as a top-level block of `field`? EC-584: a READER of the
|
|
23
|
+
* one core placement judge over the field's effective vocabulary (`fieldAllowList` —
|
|
24
|
+
* its `allow`, else the blocks default), the verdict `validateCompositionValue`
|
|
25
|
+
* reaches at the gate — so a target this predicate accepts can never fail its
|
|
26
|
+
* `forbidden-component` / `server-only-component` check. With `components` (the
|
|
27
|
+
* project's manifest lookup) canvas eligibility is judged too. The transfer dialog
|
|
28
|
+
* uses this to hide targets the component isn't allowed into (new AND existing
|
|
29
|
+
* document targets alike); the value-level sinks stay on the gate.
|
|
30
30
|
*/
|
|
31
|
-
export function isComponentAllowed(field, componentId) {
|
|
32
|
-
return
|
|
31
|
+
export function isComponentAllowed(field, componentId, components) {
|
|
32
|
+
return (placementRefusal({ componentId, view: components?.get(componentId), allow: fieldAllowList(field) ?? ANY_COMPONENT }) ===
|
|
33
|
+
null);
|
|
33
34
|
}
|
|
34
35
|
/**
|
|
35
36
|
* Re-embed `node` under a fresh id from the injected factory (EC-578 / EC-595 A2): UUIDs
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transfer.js","sourceRoot":"","sources":["../../../src/components/components/transfer.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n fieldPath,\n isBlocksField,\n isFieldLocalized,\n isSequenceValue,\n itemLimit,\n seedDefaults,\n writeField,\n} from '@elytracms/core/cms-core'\nimport type {\n BlocksFieldDef,\n CmsDocument,\n CollectionDef,\n DocumentPatch,\n Locale,\n LocaleConfig,\n} from '@elytracms/core/cms-core'\nimport { blockRootsOf, editBlockRoots } from '@elytracms/core/project-graph'\nimport type { ComponentNode } from '@elytracms/core/project-graph'\nimport type { IdFactory } from '@elytracms/core/persistence'\nimport { embedNodeFrom } from './component-preview'\n\n/**\n * EC-320 (+ 0.0.44 follow-up) — the \"design guide → real content\" bridge. A\n * component combination the user has arranged in the Components view (a\n * manifest + edited prop values, captured as a {@link ComponentNode}) is\n * transferred into a document's blocks/composition field — either a NEW DRAFT\n * document created for it ({@link buildBlockDocument}) or an EXISTING\n * document's field, appended alongside whatever it already holds\n * ({@link appendToBlockDocument}). Pure so id-minting/embedding/appending are\n * unit-testable; the React dialog owns the picker, persistence, and\n * navigation.\n */\n\nexport interface CollectionBlocksTarget {\n collection: CollectionDef\n /** The (first) blocks field the node lands in. */\n field: BlocksFieldDef\n}\n\n/**\n * Collections that can receive a transferred combination — those declaring a\n * `blocks` (composition) field. The first blocks field wins (a collection with a\n * single canvas is the norm; a second is an unusual edge we don't disambiguate).\n * Callers additionally gate each target through {@link isComponentAllowed} —\n * this only reports which collections HAVE a blocks field, not which ones\n * accept a given component.\n */\nexport function collectionsWithBlocks(\n collections: readonly CollectionDef[],\n): CollectionBlocksTarget[] {\n const targets: CollectionBlocksTarget[] = []\n for (const collection of collections) {\n const field = collection.fields.find(isBlocksField)\n if (field) targets.push({ collection, field })\n }\n return targets\n}\n\n/**\n * Is `componentId` permitted as a top-level block of `field`? Mirrors\n * `validateCompositionValue`'s (`@elytracms/core/project-graph`) top-level\n * `allow` rule EXACTLY (EC-544): an absent list permits any registered\n * component; a list is the closed vocabulary (an empty one permits nothing).\n * A target this predicate accepts can never fail that validator's\n * `forbidden-component` check on save — the transfer dialog uses this to hide\n * targets the component isn't allowed into (new AND existing document targets\n * alike).\n */\nexport function isComponentAllowed(field: BlocksFieldDef, componentId: string): boolean {\n return !field.allow || field.allow.includes(componentId)\n}\n\n/**\n * Re-embed `node` under a fresh id from the injected factory (EC-578 / EC-595 A2): UUIDs\n * in the studio, so an embed appended into a document can never collide with an id\n * already in the tree — or with one another session appends at the same time (the old\n * `node-<n>` counter seeded past the tree's ids minted the same id in both).\n */\nfunction embedFreshNode(node: ComponentNode, newId: IdFactory): ComponentNode {\n return embedNodeFrom(node, newId())\n}\n\nexport interface BuildBlockDocumentInput {\n collection: CollectionDef\n /** The blocks field the node lands in (from {@link collectionsWithBlocks}). */\n blocksField: BlocksFieldDef\n /** The component node — a manifest id + the edited static props (+ slots). */\n node: ComponentNode\n /** THE id factory (EC-578) — mints the new document's id and the embed's node id. */\n newId: IdFactory\n /** The project's locale config — a new document is written in its default locale (EC-580). */\n locales: LocaleConfig\n}\n\n/**\n * Build the draft document that embeds `node` in the collection's blocks field.\n * Reuses the CMS create machinery: the injected id factory (EC-578) for the id\n * and {@link seedDefaults} so declared field defaults are present (as a\n * normal `+ New` would be). The field stores the converged v2 sequence holding\n * the one tagged root, written through the core write path (`editBlockRoots`,\n * EC-576) — cardinality is a constraint (`validation.max`), not a shape (EC-500 D2,\n * EC-581). The\n * transferred node REPLACES whatever the field's declared default seeded, as it\n * always has.\n *\n * EC-580: both writes go through core's `writeField` in the project's DEFAULT\n * locale — a new document is created in its default bucket, so a localized blocks\n * field holds the node under `localized[default]`, never on the shared bag.\n */\nexport function buildBlockDocument({\n collection,\n blocksField,\n node,\n newId,\n locales,\n}: BuildBlockDocumentInput): CmsDocument {\n const seeded: CmsDocument = { collection: collection.id, id: newId(), ...seedDefaults(collection, locales) }\n return writeField(\n collection,\n seeded,\n blocksField.name,\n locales.default,\n editBlockRoots(undefined, () => [embedFreshNode(node, newId)]),\n )\n}\n\n/** The blocks field's current value on `document`, respecting locale (EC-018). */\nfunction readBlocksValue(\n collection: CollectionDef,\n document: CmsDocument,\n blocksField: BlocksFieldDef,\n locale: Locale,\n): unknown {\n return isFieldLocalized(collection, blocksField.name)\n ? document.localized?.[locale]?.[blocksField.name]\n : document.values[blocksField.name]\n}\n\nexport interface CanAppendToInput {\n collection: CollectionDef\n document: CmsDocument\n blocksField: BlocksFieldDef\n /** The locale being edited (EC-176 ambient active locale) for a localized field. */\n locale: Locale\n}\n\n/**\n * Can `node` be appended to `document`'s blocks field without destroying\n * existing content? Always true without a declared item limit (an ordered list\n * has room for one more); a field limited by `validation.max` (EC-581: `max: 1` is\n * the single-block field `multiple: false` used to spell) only while it is below\n * the limit — at it, there is no room without overwriting an existing block, so\n * the transfer dialog excludes that document from its offered targets rather than\n * risk data loss.\n */\nexport function canAppendTo({ collection, document, blocksField, locale }: CanAppendToInput): boolean {\n const limit = itemLimit(blocksField)\n if (limit === undefined) return true\n return blockRootsOf(readBlocksValue(collection, document, blocksField, locale)).length < limit\n}\n\nexport interface AppendToBlockDocumentInput {\n collection: CollectionDef\n /** The document to append into (the transfer dialog's \"insert into existing document\"). */\n document: CmsDocument\n blocksField: BlocksFieldDef\n /** The component node to append — not yet re-ided (fresh id minted here). */\n node: ComponentNode\n /** The locale being edited (EC-176 ambient active locale) for a localized field. */\n locale: Locale\n /** THE id factory (EC-578) — mints the embed's node id. */\n newId: IdFactory\n}\n\n/** The appended document AND the patch that says so (EC-595: the writer emits its patch at the source). */\nexport interface AppendToBlockDocumentResult {\n document: CmsDocument\n /**\n * ONE `insert` of the new embed node at the end of the field's top-level sequence,\n * anchored after the last existing component root — what is persisted\n * (`documents.patch`), so a concurrent edit elsewhere in the document (or a\n * concurrent append) is never overwritten by this one.\n */\n patches: DocumentPatch[]\n}\n\n/**\n * Append `node` into an EXISTING document's blocks field (EC-320 follow-up,\n * 0.0.44 — \"insert into existing document\"), leaving everything else about\n * the document untouched: other fields, other locale bundles, and the\n * field's own existing content, which the embed always lands AFTER.\n *\n * Writes through the core write path (`editBlockRoots`,\n * `@elytracms/core/project-graph`, EC-576): the new root is appended after\n * the existing roots, every typography item keeps its place and order, and\n * the value written is the v2 sequence — undefined-safe for a never-set\n * field. Locale-aware writes go through core's `writeField` (the same\n * locale-write every editor uses, EC-580) so a localized\n * blocks field's OTHER locale bundles are never touched, only the edited\n * locale's.\n *\n * Node-id collision guard: `node` is re-embedded under a fresh id minted past\n * every existing root's ids ({@link embedFreshNode}), never a fixed `node-1`\n * — a document whose blocks already hold that id would otherwise collide.\n *\n * Throws if `blocksField` already holds its declared item limit (`validation.max`,\n * EC-581) — a defensive invariant guard, not a user-facing\n * validation path: the caller must pre-filter candidates with\n * {@link canAppendTo} so this is never reached in that state.\n */\nexport function appendToBlockDocument({\n collection,\n document,\n blocksField,\n node,\n locale,\n newId,\n}: AppendToBlockDocumentInput): AppendToBlockDocumentResult {\n const current = readBlocksValue(collection, document, blocksField, locale)\n const embedded = embedFreshNode(node, newId)\n let lastRoot: string | undefined\n const nextValue = editBlockRoots(current, (existingRoots) => {\n const limit = itemLimit(blocksField)\n if (limit !== undefined && existingRoots.length >= limit) {\n throw new Error(\n `Cannot append to blocks field \"${blocksField.name}\": it already holds its limit of ${limit} (caller must gate with canAppendTo).`,\n )\n }\n lastRoot = existingRoots.at(-1)?.id\n return [...existingRoots, embedded]\n })\n const written = writeField(collection, document, blocksField.name, locale, nextValue)\n // The stored node is the canonical one `writeField` wrote (tagged, canonical props).\n const storedValue = readBlocksValue(collection, written, blocksField, locale)\n const storedNode = isSequenceValue(storedValue)\n ? (storedValue.content.find(\n (entry): entry is ComponentNode =>\n typeof entry === 'object' && entry !== null && (entry as ComponentNode).id === embedded.id,\n ) ?? embedded)\n : embedded\n const parent = fieldPath(blocksField.name, isFieldLocalized(collection, blocksField.name) ? locale : undefined)\n const index = isSequenceValue(current) ? current.content.length : 0\n return {\n document: written,\n patches: [\n {\n op: 'insert',\n at: { parent, index, ...(lastRoot !== undefined ? { after: lastRoot } : {}) },\n node: storedNode,\n },\n ],\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"transfer.js","sourceRoot":"","sources":["../../../src/components/components/transfer.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n fieldAllowList,\n fieldPath,\n isBlocksField,\n isFieldLocalized,\n isSequenceValue,\n itemLimit,\n seedDefaults,\n writeField,\n} from '@elytracms/core/cms-core'\nimport type {\n BlocksFieldDef,\n CmsDocument,\n CollectionDef,\n DocumentPatch,\n Locale,\n LocaleConfig,\n} from '@elytracms/core/cms-core'\nimport { ANY_COMPONENT, blockRootsOf, editBlockRoots, placementRefusal } from '@elytracms/core/project-graph'\nimport type { ComponentLookup, ComponentNode } from '@elytracms/core/project-graph'\nimport type { IdFactory } from '@elytracms/core/persistence'\nimport { embedNodeFrom } from './component-preview'\n\n/**\n * EC-320 (+ 0.0.44 follow-up) — the \"design guide → real content\" bridge. A\n * component combination the user has arranged in the Components view (a\n * manifest + edited prop values, captured as a {@link ComponentNode}) is\n * transferred into a document's blocks/composition field — either a NEW DRAFT\n * document created for it ({@link buildBlockDocument}) or an EXISTING\n * document's field, appended alongside whatever it already holds\n * ({@link appendToBlockDocument}). Pure so id-minting/embedding/appending are\n * unit-testable; the React dialog owns the picker, persistence, and\n * navigation.\n */\n\nexport interface CollectionBlocksTarget {\n collection: CollectionDef\n /** The (first) blocks field the node lands in. */\n field: BlocksFieldDef\n}\n\n/**\n * Collections that can receive a transferred combination — those declaring a\n * `blocks` (composition) field. The first blocks field wins (a collection with a\n * single canvas is the norm; a second is an unusual edge we don't disambiguate).\n * Callers additionally gate each target through {@link isComponentAllowed} —\n * this only reports which collections HAVE a blocks field, not which ones\n * accept a given component.\n */\nexport function collectionsWithBlocks(\n collections: readonly CollectionDef[],\n): CollectionBlocksTarget[] {\n const targets: CollectionBlocksTarget[] = []\n for (const collection of collections) {\n const field = collection.fields.find(isBlocksField)\n if (field) targets.push({ collection, field })\n }\n return targets\n}\n\n/**\n * Is `componentId` permitted as a top-level block of `field`? EC-584: a READER of the\n * one core placement judge over the field's effective vocabulary (`fieldAllowList` —\n * its `allow`, else the blocks default), the verdict `validateCompositionValue`\n * reaches at the gate — so a target this predicate accepts can never fail its\n * `forbidden-component` / `server-only-component` check. With `components` (the\n * project's manifest lookup) canvas eligibility is judged too. The transfer dialog\n * uses this to hide targets the component isn't allowed into (new AND existing\n * document targets alike); the value-level sinks stay on the gate.\n */\nexport function isComponentAllowed(field: BlocksFieldDef, componentId: string, components?: ComponentLookup): boolean {\n return (\n placementRefusal({ componentId, view: components?.get(componentId), allow: fieldAllowList(field) ?? ANY_COMPONENT }) ===\n null\n )\n}\n\n/**\n * Re-embed `node` under a fresh id from the injected factory (EC-578 / EC-595 A2): UUIDs\n * in the studio, so an embed appended into a document can never collide with an id\n * already in the tree — or with one another session appends at the same time (the old\n * `node-<n>` counter seeded past the tree's ids minted the same id in both).\n */\nfunction embedFreshNode(node: ComponentNode, newId: IdFactory): ComponentNode {\n return embedNodeFrom(node, newId())\n}\n\nexport interface BuildBlockDocumentInput {\n collection: CollectionDef\n /** The blocks field the node lands in (from {@link collectionsWithBlocks}). */\n blocksField: BlocksFieldDef\n /** The component node — a manifest id + the edited static props (+ slots). */\n node: ComponentNode\n /** THE id factory (EC-578) — mints the new document's id and the embed's node id. */\n newId: IdFactory\n /** The project's locale config — a new document is written in its default locale (EC-580). */\n locales: LocaleConfig\n}\n\n/**\n * Build the draft document that embeds `node` in the collection's blocks field.\n * Reuses the CMS create machinery: the injected id factory (EC-578) for the id\n * and {@link seedDefaults} so declared field defaults are present (as a\n * normal `+ New` would be). The field stores the converged v2 sequence holding\n * the one tagged root, written through the core write path (`editBlockRoots`,\n * EC-576) — cardinality is a constraint (`validation.max`), not a shape (EC-500 D2,\n * EC-581). The\n * transferred node REPLACES whatever the field's declared default seeded, as it\n * always has.\n *\n * EC-580: both writes go through core's `writeField` in the project's DEFAULT\n * locale — a new document is created in its default bucket, so a localized blocks\n * field holds the node under `localized[default]`, never on the shared bag.\n */\nexport function buildBlockDocument({\n collection,\n blocksField,\n node,\n newId,\n locales,\n}: BuildBlockDocumentInput): CmsDocument {\n const seeded: CmsDocument = { collection: collection.id, id: newId(), ...seedDefaults(collection, locales) }\n return writeField(\n collection,\n seeded,\n blocksField.name,\n locales.default,\n editBlockRoots(undefined, () => [embedFreshNode(node, newId)]),\n )\n}\n\n/** The blocks field's current value on `document`, respecting locale (EC-018). */\nfunction readBlocksValue(\n collection: CollectionDef,\n document: CmsDocument,\n blocksField: BlocksFieldDef,\n locale: Locale,\n): unknown {\n return isFieldLocalized(collection, blocksField.name)\n ? document.localized?.[locale]?.[blocksField.name]\n : document.values[blocksField.name]\n}\n\nexport interface CanAppendToInput {\n collection: CollectionDef\n document: CmsDocument\n blocksField: BlocksFieldDef\n /** The locale being edited (EC-176 ambient active locale) for a localized field. */\n locale: Locale\n}\n\n/**\n * Can `node` be appended to `document`'s blocks field without destroying\n * existing content? Always true without a declared item limit (an ordered list\n * has room for one more); a field limited by `validation.max` (EC-581: `max: 1` is\n * the single-block field `multiple: false` used to spell) only while it is below\n * the limit — at it, there is no room without overwriting an existing block, so\n * the transfer dialog excludes that document from its offered targets rather than\n * risk data loss.\n */\nexport function canAppendTo({ collection, document, blocksField, locale }: CanAppendToInput): boolean {\n const limit = itemLimit(blocksField)\n if (limit === undefined) return true\n return blockRootsOf(readBlocksValue(collection, document, blocksField, locale)).length < limit\n}\n\nexport interface AppendToBlockDocumentInput {\n collection: CollectionDef\n /** The document to append into (the transfer dialog's \"insert into existing document\"). */\n document: CmsDocument\n blocksField: BlocksFieldDef\n /** The component node to append — not yet re-ided (fresh id minted here). */\n node: ComponentNode\n /** The locale being edited (EC-176 ambient active locale) for a localized field. */\n locale: Locale\n /** THE id factory (EC-578) — mints the embed's node id. */\n newId: IdFactory\n}\n\n/** The appended document AND the patch that says so (EC-595: the writer emits its patch at the source). */\nexport interface AppendToBlockDocumentResult {\n document: CmsDocument\n /**\n * ONE `insert` of the new embed node at the end of the field's top-level sequence,\n * anchored after the last existing component root — what is persisted\n * (`documents.patch`), so a concurrent edit elsewhere in the document (or a\n * concurrent append) is never overwritten by this one.\n */\n patches: DocumentPatch[]\n}\n\n/**\n * Append `node` into an EXISTING document's blocks field (EC-320 follow-up,\n * 0.0.44 — \"insert into existing document\"), leaving everything else about\n * the document untouched: other fields, other locale bundles, and the\n * field's own existing content, which the embed always lands AFTER.\n *\n * Writes through the core write path (`editBlockRoots`,\n * `@elytracms/core/project-graph`, EC-576): the new root is appended after\n * the existing roots, every typography item keeps its place and order, and\n * the value written is the v2 sequence — undefined-safe for a never-set\n * field. Locale-aware writes go through core's `writeField` (the same\n * locale-write every editor uses, EC-580) so a localized\n * blocks field's OTHER locale bundles are never touched, only the edited\n * locale's.\n *\n * Node-id collision guard: `node` is re-embedded under a fresh id minted past\n * every existing root's ids ({@link embedFreshNode}), never a fixed `node-1`\n * — a document whose blocks already hold that id would otherwise collide.\n *\n * Throws if `blocksField` already holds its declared item limit (`validation.max`,\n * EC-581) — a defensive invariant guard, not a user-facing\n * validation path: the caller must pre-filter candidates with\n * {@link canAppendTo} so this is never reached in that state.\n */\nexport function appendToBlockDocument({\n collection,\n document,\n blocksField,\n node,\n locale,\n newId,\n}: AppendToBlockDocumentInput): AppendToBlockDocumentResult {\n const current = readBlocksValue(collection, document, blocksField, locale)\n const embedded = embedFreshNode(node, newId)\n let lastRoot: string | undefined\n const nextValue = editBlockRoots(current, (existingRoots) => {\n const limit = itemLimit(blocksField)\n if (limit !== undefined && existingRoots.length >= limit) {\n throw new Error(\n `Cannot append to blocks field \"${blocksField.name}\": it already holds its limit of ${limit} (caller must gate with canAppendTo).`,\n )\n }\n lastRoot = existingRoots.at(-1)?.id\n return [...existingRoots, embedded]\n })\n const written = writeField(collection, document, blocksField.name, locale, nextValue)\n // The stored node is the canonical one `writeField` wrote (tagged, canonical props).\n const storedValue = readBlocksValue(collection, written, blocksField, locale)\n const storedNode = isSequenceValue(storedValue)\n ? (storedValue.content.find(\n (entry): entry is ComponentNode =>\n typeof entry === 'object' && entry !== null && (entry as ComponentNode).id === embedded.id,\n ) ?? embedded)\n : embedded\n const parent = fieldPath(blocksField.name, isFieldLocalized(collection, blocksField.name) ? locale : undefined)\n const index = isSequenceValue(current) ? current.content.length : 0\n return {\n document: written,\n patches: [\n {\n op: 'insert',\n at: { parent, index, ...(lastRoot !== undefined ? { after: lastRoot } : {}) },\n node: storedNode,\n },\n ],\n }\n}\n"]}
|