@elytracms/studio 0.0.86 → 0.0.87
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-revalidate.test.ts +186 -0
- package/convex/assets.ts +53 -19
- package/convex/cms.ts +22 -0
- package/convex/delivery.ts +33 -116
- package/convex/functions.ts +3 -3
- package/convex/rail-write-parity.test.ts +180 -0
- package/convex/redirects.ts +33 -11
- package/convex/revalidate.ts +7 -5
- package/convex/triggers.ts +53 -3
- package/dist/components/app-views/default-modules.d.ts +20 -20
- package/dist/components/app-views/default-modules.js +27 -48
- package/dist/components/app-views/default-modules.js.map +1 -1
- package/dist/components/app-views/studio-config.d.ts +5 -6
- package/dist/components/app-views/studio-config.js.map +1 -1
- package/dist/components/assets/asset-metadata.d.ts +28 -19
- package/dist/components/assets/asset-metadata.js +24 -31
- package/dist/components/assets/asset-metadata.js.map +1 -1
- package/dist/components/assets/asset-selection.d.ts +24 -5
- package/dist/components/assets/asset-selection.js +39 -9
- package/dist/components/assets/asset-selection.js.map +1 -1
- package/dist/components/assets/assets-collection-view-source.d.ts +17 -14
- package/dist/components/assets/assets-collection-view-source.js +25 -31
- package/dist/components/assets/assets-collection-view-source.js.map +1 -1
- package/dist/components/builder/blocks-panel.js +3 -3
- package/dist/components/builder/blocks-panel.js.map +1 -1
- package/dist/components/builder/builder-store.d.ts +25 -1
- package/dist/components/builder/builder-store.js +3 -2
- package/dist/components/builder/builder-store.js.map +1 -1
- package/dist/components/builder/canvas-chrome-css.js +0 -17
- package/dist/components/builder/canvas-chrome-css.js.map +1 -1
- package/dist/components/builder/canvas-chrome.d.ts +10 -12
- package/dist/components/builder/canvas-chrome.js +12 -13
- package/dist/components/builder/canvas-chrome.js.map +1 -1
- package/dist/components/builder/canvas-dnd.d.ts +8 -0
- package/dist/components/builder/canvas-dnd.js +5 -3
- package/dist/components/builder/canvas-dnd.js.map +1 -1
- package/dist/components/builder/canvas-render-context.d.ts +12 -24
- package/dist/components/builder/canvas-render-context.js +15 -44
- package/dist/components/builder/canvas-render-context.js.map +1 -1
- package/dist/components/builder/canvas.js +5 -11
- package/dist/components/builder/canvas.js.map +1 -1
- package/dist/components/builder/clipboard-actions.d.ts +14 -6
- package/dist/components/builder/clipboard-actions.js +29 -20
- package/dist/components/builder/clipboard-actions.js.map +1 -1
- package/dist/components/builder/composition-field-editor.d.ts +19 -26
- package/dist/components/builder/composition-field-editor.js +30 -52
- package/dist/components/builder/composition-field-editor.js.map +1 -1
- package/dist/components/builder/insert-context.d.ts +3 -1
- package/dist/components/builder/insert-context.js +15 -10
- package/dist/components/builder/insert-context.js.map +1 -1
- package/dist/components/builder/page-tree.d.ts +29 -0
- package/dist/components/builder/page-tree.js +56 -58
- package/dist/components/builder/page-tree.js.map +1 -1
- package/dist/components/builder/reuse.js +5 -2
- package/dist/components/builder/reuse.js.map +1 -1
- package/dist/components/cms/asset-field.js +20 -32
- package/dist/components/cms/asset-field.js.map +1 -1
- package/dist/components/cms/cms-composition-validators.js +11 -2
- package/dist/components/cms/cms-composition-validators.js.map +1 -1
- package/dist/components/cms/editable-cell.js +5 -2
- package/dist/components/cms/editable-cell.js.map +1 -1
- package/dist/components/cms/use-document-versions.js +1 -0
- package/dist/components/cms/use-document-versions.js.map +1 -1
- package/dist/components/cms/versions-model.d.ts +0 -12
- package/dist/components/cms/versions-model.js +3 -15
- package/dist/components/cms/versions-model.js.map +1 -1
- package/dist/components/components/component-detail.js +1 -1
- package/dist/components/components/component-detail.js.map +1 -1
- package/dist/components/components/transfer-dialog.js +2 -4
- package/dist/components/components/transfer-dialog.js.map +1 -1
- package/dist/components/components/transfer.d.ts +13 -10
- package/dist/components/components/transfer.js +21 -19
- package/dist/components/components/transfer.js.map +1 -1
- package/dist/components/project-session.d.ts +9 -0
- package/dist/components/project-session.js +1 -1
- package/dist/components/project-session.js.map +1 -1
- package/dist/components/redirects/redirects-collection-view-source.d.ts +17 -2
- package/dist/components/redirects/redirects-collection-view-source.js +20 -6
- package/dist/components/redirects/redirects-collection-view-source.js.map +1 -1
- package/dist/components/section-surface.js +3 -2
- package/dist/components/section-surface.js.map +1 -1
- package/dist/components/settings-form.js +1 -2
- package/dist/components/settings-form.js.map +1 -1
- package/dist/components/studio-context.js +1 -18
- package/dist/components/studio-context.js.map +1 -1
- package/dist/components/studio-project-screen.js +3 -5
- package/dist/components/studio-project-screen.js.map +1 -1
- package/dist/lib/ai/apply-content.js +73 -29
- package/dist/lib/ai/apply-content.js.map +1 -1
- package/dist/lib/ai/constraints.d.ts +35 -0
- package/dist/lib/ai/constraints.js +104 -4
- package/dist/lib/ai/constraints.js.map +1 -1
- package/dist/lib/ai/route.js +4 -1
- package/dist/lib/ai/route.js.map +1 -1
- package/dist/lib/ai/shape.d.ts +3 -2
- package/dist/lib/ai/shape.js +16 -24
- package/dist/lib/ai/shape.js.map +1 -1
- package/dist/lib/ai/types.d.ts +8 -0
- package/dist/lib/ai/types.js.map +1 -1
- package/dist/lib/ai/update-fields.js +33 -12
- package/dist/lib/ai/update-fields.js.map +1 -1
- package/dist/lib/assets/asset-placements.d.ts +35 -0
- package/dist/lib/assets/asset-placements.js +74 -0
- package/dist/lib/assets/asset-placements.js.map +1 -0
- package/dist/lib/assets/backfill-thumbs.d.ts +9 -8
- package/dist/lib/assets/backfill-thumbs.js +13 -25
- package/dist/lib/assets/backfill-thumbs.js.map +1 -1
- package/dist/lib/clipboard/paste-issues.d.ts +17 -15
- package/dist/lib/clipboard/paste-issues.js +8 -45
- package/dist/lib/clipboard/paste-issues.js.map +1 -1
- package/dist/lib/clipboard/paste-target.d.ts +25 -1
- package/dist/lib/clipboard/paste-target.js +13 -6
- package/dist/lib/clipboard/paste-target.js.map +1 -1
- package/dist/lib/cms/published-documents.js +1 -0
- package/dist/lib/cms/published-documents.js.map +1 -1
- package/dist/lib/cms-views/view-model.d.ts +3 -3
- package/dist/lib/data/index.d.ts +0 -3
- package/dist/lib/data/index.js +4 -6
- package/dist/lib/data/index.js.map +1 -1
- package/dist/lib/dnd/drop-rules.d.ts +45 -2
- package/dist/lib/dnd/drop-rules.js +64 -9
- 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 +4 -56
- package/dist/lib/i18n/messages.de.js.map +1 -1
- package/dist/lib/i18n/messages.en.d.ts +4 -53
- package/dist/lib/i18n/messages.en.js +8 -57
- package/dist/lib/i18n/messages.en.js.map +1 -1
- package/dist/lib/locale/fallback.js +8 -2
- package/dist/lib/locale/fallback.js.map +1 -1
- package/dist/lib/locale/index.d.ts +0 -1
- package/dist/lib/locale/index.js +0 -1
- package/dist/lib/locale/index.js.map +1 -1
- package/dist/lib/locale/locale-store.d.ts +10 -3
- package/dist/lib/locale/locale-store.js +12 -5
- package/dist/lib/locale/locale-store.js.map +1 -1
- package/dist/lib/operations/session-hydration.d.ts +9 -5
- package/dist/lib/operations/session-hydration.js +10 -10
- package/dist/lib/operations/session-hydration.js.map +1 -1
- package/dist/lib/persistence/convex-adapter.js +63 -75
- package/dist/lib/persistence/convex-adapter.js.map +1 -1
- package/dist/lib/persona/labels.d.ts +0 -2
- package/dist/lib/persona/labels.js +1 -7
- package/dist/lib/persona/labels.js.map +1 -1
- package/dist/lib/registry/index.d.ts +4 -2
- package/dist/lib/registry/index.js +26 -21
- package/dist/lib/registry/index.js.map +1 -1
- package/dist/lib/registry/project-components.js +0 -3
- package/dist/lib/registry/project-components.js.map +1 -1
- package/dist/lib/routes/route-issues.js +4 -5
- package/dist/lib/routes/route-issues.js.map +1 -1
- package/dist/lib/theme.js +3 -2
- package/dist/lib/theme.js.map +1 -1
- package/dist/lib/webhooks/tags.js +11 -14
- package/dist/lib/webhooks/tags.js.map +1 -1
- package/dist/lib/workspace/index.d.ts +1 -1
- package/dist/lib/workspace/index.js +1 -1
- package/dist/lib/workspace/index.js.map +1 -1
- package/dist/previews/enumerate.d.ts +27 -9
- package/dist/previews/enumerate.js +27 -6
- package/dist/previews/enumerate.js.map +1 -1
- package/dist/previews/manifest-format.d.ts +9 -0
- package/dist/previews/manifest-format.js.map +1 -1
- package/dist/router/match.d.ts +0 -2
- package/dist/router/match.js +0 -2
- package/dist/router/match.js.map +1 -1
- package/dist/ui/badge.d.ts +1 -1
- package/package.json +2 -2
- package/src/styles/studio.css +5 -4
- package/src/styles/tokens.css +72 -55
- package/convex/content.ts +0 -291
- package/dist/components/collab/collab-context.d.ts +0 -27
- package/dist/components/collab/collab-context.js +0 -39
- package/dist/components/collab/collab-context.js.map +0 -1
- package/dist/components/collab/collab-seed.d.ts +0 -15
- package/dist/components/collab/collab-seed.js +0 -20
- package/dist/components/collab/collab-seed.js.map +0 -1
- package/dist/components/extensions/extensions-view.d.ts +0 -7
- package/dist/components/extensions/extensions-view.js +0 -127
- package/dist/components/extensions/extensions-view.js.map +0 -1
- package/dist/lib/bindings/section-bindings.d.ts +0 -24
- package/dist/lib/bindings/section-bindings.js +0 -9
- package/dist/lib/bindings/section-bindings.js.map +0 -1
- package/dist/lib/data/data-store.d.ts +0 -98
- package/dist/lib/data/data-store.js +0 -211
- package/dist/lib/data/data-store.js.map +0 -1
- package/dist/lib/data/providers.d.ts +0 -143
- package/dist/lib/data/providers.js +0 -255
- package/dist/lib/data/providers.js.map +0 -1
- package/dist/lib/data/sample-capture.d.ts +0 -30
- package/dist/lib/data/sample-capture.js +0 -70
- package/dist/lib/data/sample-capture.js.map +0 -1
- package/dist/lib/data/sources-store.d.ts +0 -42
- package/dist/lib/data/sources-store.js +0 -120
- package/dist/lib/data/sources-store.js.map +0 -1
- package/dist/lib/locale/binding-fallback.d.ts +0 -35
- package/dist/lib/locale/binding-fallback.js +0 -73
- package/dist/lib/locale/binding-fallback.js.map +0 -1
- package/dist/lib/starters/kit-session.d.ts +0 -5
- package/dist/lib/starters/kit-session.js +0 -17
- package/dist/lib/starters/kit-session.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"page-tree.js","sourceRoot":"","sources":["../../../src/components/builder/page-tree.tsx"],"names":[],"mappings":"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","sourcesContent":["import {\n Boxes,\n ChevronUp,\n ChevronDown,\n ClipboardCopy,\n ClipboardPaste,\n ClipboardX,\n Copy,\n EyeOff,\n Filter,\n MoreHorizontal,\n Plus,\n Trash2,\n Unlink,\n} from 'lucide-react'\nimport { useEffect, useRef, useState } from 'react'\nimport type { Condition, ComponentNode } from '@elytracms/core/project-graph'\nimport { getParentInfo } from '@elytracms/core/project-graph'\nimport {\n canNestInto,\n containerFlowAxis,\n dropIndicatorFor,\n dropPositionForPointer,\n manifestRules,\n resolveDrop,\n} from '@elytracms/studio/lib/dnd/drop-rules'\nimport type { DropIndicator, DropPlan, DropPosition } from '@elytracms/studio/lib/dnd/drop-rules'\nimport { getStudioRegistry } from '@elytracms/studio/lib/registry'\nimport { shortId } from '@elytracms/studio/lib/persona/labels'\nimport { translate, useUiLocale } from '@elytracms/studio/lib/i18n'\nimport { useData } from '@elytracms/studio/lib/data'\nimport {\n Button,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n Row,\n Spacer,\n Text,\n} from '@elytracms/studio/ui'\nimport { cn } from '@elytracms/studio/lib/utils'\nimport { useCollab } from '../collab/collab-context'\nimport { useBuilder } from './builder-store'\nimport { attachClipboardListeners, useStudioClipboard } from './clipboard-actions'\nimport { attachDeleteKeyListener } from './delete-key'\nimport { nodeDisplayTitle } from './node-title'\nimport { useReuse } from './reuse-context'\nimport { activeDrag, beginDrag, endDrag, onDragSessionEnd } from './drag-session'\n\ninterface TreeRow {\n id: string\n componentId: string\n /** The node itself — EC-407 derives the instance title from its prop values. */\n node: ComponentNode\n depth: number\n isRoot: boolean\n isFirst: boolean\n isLast: boolean\n condition?: Condition\n}\n\nfunction flatten(\n node: ComponentNode,\n depth: number,\n rows: TreeRow[],\n meta: { isFirst: boolean; isLast: boolean; isRoot: boolean },\n): void {\n rows.push({ id: node.id, componentId: node.componentId, node, depth, ...meta, condition: node.condition })\n for (const children of Object.values(node.slots ?? {})) {\n children.forEach((child, i) =>\n flatten(child, depth + 1, rows, {\n isFirst: i === 0,\n isLast: i === children.length - 1,\n isRoot: false,\n }),\n )\n }\n}\n\n/** Tailwind classes per drop indicator state (EC-160/EC-162) — rejection is visible. */\nconst ROW_DROP_CLASSES: Record<Exclude<DropIndicator, null>, string> = {\n into: 'ring-1 ring-inset ring-emerald-400 bg-emerald-400/10',\n before: 'shadow-[inset_0_2px_0_0_#38bdf8]',\n after: 'shadow-[inset_0_-2px_0_0_#38bdf8]',\n // Wrap-into-columns gesture (EC-162): the tree can execute wrap plans, the\n // bands themselves are a canvas gesture (rows have no spatial \"beside\").\n left: 'shadow-[inset_2px_0_0_0_#a78bfa] bg-violet-400/10',\n right: 'shadow-[inset_-2px_0_0_0_#a78bfa] bg-violet-400/10',\n blocked: 'ring-1 ring-inset ring-red-500/80 bg-red-500/10 cursor-not-allowed',\n}\n\nexport function PageTree({ onAddChild }: { onAddChild: (parentId: string) => void }) {\n const builder = useBuilder()\n const studio = getStudioRegistry()\n const data = useData()\n const collab = useCollab()\n const uiLocale = useUiLocale()\n const rows: TreeRow[] = []\n flatten(builder.present, 0, rows, { isFirst: true, isLast: true, isRoot: true })\n\n // Copy/cut/paste (EC-175): keyboard shortcuts for the whole studio window\n // (the canvas iframe wires its own document) plus per-row affordances. The\n // shared listener keeps shortcuts inert in form fields/inline editors, and\n // the published preview is read-only.\n const clipboard = useStudioClipboard()\n const readOnly = collab.previewMode === 'published'\n const selectedIdRef = useRef(builder.selectedId)\n selectedIdRef.current = builder.selectedId\n useEffect(() => {\n if (readOnly) return\n return attachClipboardListeners(window, {\n clipboard,\n getSelectedId: () => selectedIdRef.current,\n getSelection: () => window.getSelection(),\n })\n }, [clipboard, readOnly])\n\n // Delete/Backspace removes the selected node from the tree side too (EC-404),\n // beside the clipboard shortcuts. Inert in the read-only published preview and\n // while focus is in a form field / inline editor (isEditingContext).\n const { dispatch } = builder\n const rootId = builder.present.id\n useEffect(() => {\n if (readOnly) return\n return attachDeleteKeyListener(window, {\n getSelectedId: () => selectedIdRef.current,\n remove: (id) => dispatch({ type: 'remove', id }),\n getRootId: () => rootId,\n })\n }, [readOnly, rootId, dispatch])\n\n // Drag & drop (EC-160): eligibility comes from the same pure rule set the\n // canvas uses (`lib/dnd/drop-rules`); the tree only renders the resolution.\n // One eligible drop dispatches exactly one move/insert action.\n // The current plan lives in a ref: `drop` fires synchronously after the\n // last `dragover`, before React flushes indicator state.\n const dropPlanRef = useRef<DropPlan | null>(null)\n const [drop, setDrop] = useState<{ rowId: string; indicator: DropIndicator } | null>(null)\n const clearDrop = () => {\n dropPlanRef.current = null\n setDrop(null)\n }\n useEffect(() => onDragSessionEnd(() => clearDrop()), [])\n // EC-386: adapt through `manifestRules` so blocks-prop regions (e.g. a\n // Section's `body`) are drop targets with their `allow` leash, like raw slots.\n const getManifest = (id: string) => manifestRules(studio.registry.getManifest(id))\n\n function handleRowDragStart(row: TreeRow, e: React.DragEvent) {\n if (row.isRoot) {\n e.preventDefault()\n return\n }\n beginDrag(\n {\n subject: { kind: 'node', nodeId: row.id, componentId: row.componentId },\n source: 'tree',\n label: shortId(row.componentId),\n },\n e.dataTransfer,\n )\n }\n\n function handleRowDragOver(row: TreeRow, e: React.DragEvent) {\n const drag = activeDrag()\n if (!drag) return\n let position: DropPosition = 'into'\n if (!row.isRoot) {\n const rect = e.currentTarget.getBoundingClientRect()\n // The hit-band axis follows the parent container's flow (EC-162):\n // children of horizontal Stacks/Grids reorder along the row's x axis.\n const flow = containerFlowAxis(getParentInfo(builder.present, row.id)?.parent)\n const ratio =\n flow === 'horizontal'\n ? rect.width > 0\n ? (e.clientX - rect.left) / rect.width\n : 0.5\n : rect.height > 0\n ? (e.clientY - rect.top) / rect.height\n : 0.5\n position = dropPositionForPointer({\n ratio,\n nestable: canNestInto(getManifest, row.componentId),\n edges: true,\n })\n }\n const resolution = resolveDrop({\n root: builder.present,\n getManifest,\n subject: drag.subject,\n location: { nodeId: row.id, position },\n })\n const indicator = dropIndicatorFor(resolution, position)\n dropPlanRef.current = resolution.eligible ? resolution : null\n setDrop({ rowId: row.id, indicator })\n if (resolution.eligible) {\n e.preventDefault()\n e.dataTransfer.dropEffect = drag.subject.kind === 'node' ? 'move' : 'copy'\n }\n }\n\n function handleRowDrop(e: React.DragEvent) {\n const drag = activeDrag()\n if (!drag) return\n const plan = dropPlanRef.current\n clearDrop()\n if (!plan) return\n e.preventDefault()\n // Exactly one dispatch per completed drop (one undo step, one command).\n if (plan.kind === 'move') {\n builder.dispatch({ type: 'move', id: plan.nodeId, target: plan.target })\n } else if (plan.kind === 'wrap') {\n // EC-162: shared plan vocabulary — the tree executes wrap plans too.\n const subject =\n plan.nodeId !== undefined\n ? { kind: 'node' as const, nodeId: plan.nodeId }\n : drag.buildNode\n ? { kind: 'component' as const, node: drag.buildNode() }\n : null\n if (!subject) {\n endDrag('cancelled')\n return\n }\n builder.dispatch({ type: 'wrap', referenceId: plan.referenceId, side: plan.side, subject })\n } else if (drag.buildNode) {\n builder.dispatch({ type: 'insert', target: plan.target, node: drag.buildNode() })\n } else {\n endDrag('cancelled')\n return\n }\n endDrag('dropped')\n }\n\n function handleRowDragEnd() {\n clearDrop()\n endDrag('cancelled')\n }\n\n return (\n <div className=\"flex h-full min-h-0 flex-col\">\n <Row gap=\"2\" className=\"h-10 shrink-0 border-b border-border px-3\">\n <Text size=\"sm\" weight=\"semibold\">\n {translate('builder.pageTree.title')}\n </Text>\n <Spacer />\n <Text size=\"xs\" tone=\"subtle\" mono>\n {rows.length}\n </Text>\n </Row>\n <div\n className=\"min-h-0 flex-1 overflow-auto p-1.5\"\n onMouseLeave={() => builder.dispatch({ type: 'hover', id: null })}\n onDragLeave={(e) => {\n // Clear the indicator when the drag exits the tree panel.\n if (!e.currentTarget.contains(e.relatedTarget as Node | null)) clearDrop()\n }}\n >\n {rows.map((row) => {\n const manifest = studio.registry.getManifest(row.componentId)\n const title = nodeDisplayTitle(manifest, row.node, uiLocale)\n const selected = row.id === builder.selectedId\n const hovered = row.id === builder.hoveredId\n // EC-088: indicate condition-gated nodes; flag the currently-hidden ones.\n let condState: 'none' | 'visible' | 'hidden' = 'none'\n if (row.condition) {\n const r = data.resolveCondition(row.condition)\n condState = r.kind === 'condition' && r.pass ? 'visible' : 'hidden'\n }\n return (\n <div\n key={row.id}\n onMouseEnter={() => builder.dispatch({ type: 'hover', id: row.id })}\n draggable={!row.isRoot}\n onDragStart={(e) => handleRowDragStart(row, e)}\n onDragOver={(e) => handleRowDragOver(row, e)}\n onDrop={handleRowDrop}\n onDragEnd={handleRowDragEnd}\n data-tree-row={row.id}\n data-tree-drop={drop?.rowId === row.id ? (drop.indicator ?? undefined) : undefined}\n className={cn(\n 'group flex items-center rounded-md pr-1 text-left text-xs transition-colors',\n selected\n ? 'bg-accent text-accent-foreground'\n : hovered\n ? 'bg-muted text-foreground'\n : 'text-muted-foreground',\n drop?.rowId === row.id && drop.indicator\n ? ROW_DROP_CLASSES[drop.indicator]\n : undefined,\n )}\n >\n <button\n type=\"button\"\n onClick={() => builder.dispatch({ type: 'select', id: row.id })}\n title={row.componentId}\n className=\"flex min-w-0 flex-1 items-center gap-1.5 py-1\"\n style={{ paddingLeft: `${6 + row.depth * 14}px` }}\n >\n {/* EC-407: instance title (its own text content) is primary; the\n localized component name is secondary context. The raw mono id\n left the row — it stays in the `title=` above + inspector header. */}\n <span className={cn('truncate', condState === 'hidden' && 'line-through opacity-70')}>\n {title.primary}\n </span>\n {title.secondary ? (\n <span className=\"truncate text-muted-foreground/70\">{title.secondary}</span>\n ) : null}\n {condState === 'hidden' ? (\n <EyeOff\n className=\"size-3 shrink-0 text-warning\"\n aria-label={translate('builder.pageTree.hiddenByCondition')}\n />\n ) : condState === 'visible' ? (\n <Filter\n className=\"size-3 shrink-0 text-muted-foreground/60\"\n aria-label={translate('builder.pageTree.conditional')}\n />\n ) : null}\n </button>\n\n <div\n className={cn(\n 'flex shrink-0 items-center gap-0.5',\n selected\n ? 'opacity-100'\n : 'opacity-0 group-hover:opacity-100 group-focus-within:opacity-100',\n )}\n >\n {/* Add child stays inline — the one constructive, high-frequency\n action. Everything else collapses into the ••• menu so labels\n stop cropping on the 200–240px panel (EC-211). */}\n <IconAction label={translate('builder.pageTree.addChild')} onClick={() => onAddChild(row.id)}>\n <Plus />\n </IconAction>\n <RowActionsMenu row={row} builder={builder} clipboard={clipboard} />\n </div>\n </div>\n )\n })}\n </div>\n </div>\n )\n}\n\n/** Right-aligned keyboard-shortcut hint in a menu item — informational only; the\n * shortcut fires globally via the clipboard listeners regardless of the menu. */\nfunction MenuShortcut({ children }: { children: React.ReactNode }) {\n return <span className=\"ml-auto pl-6 text-xs tracking-widest text-muted-foreground\">{children}</span>\n}\n\n/**\n * The per-row \"•••\" action menu (EC-211). Copy/Paste on every row; Cut, Move,\n * Duplicate, Delete for non-root rows. Collapsing the old inline icon cluster\n * (up to 8 buttons, cropping on the 200–240px panel) into a menu changes no\n * behavior — same clipboard fragment + dispatch paths, same Cmd/Ctrl shortcuts.\n * Delete stays a one-click (undoable) destructive item, consistent with the rest\n * of the studio.\n */\nfunction RowActionsMenu({\n row,\n builder,\n clipboard,\n}: {\n row: { id: string; isRoot: boolean; isFirst: boolean; isLast: boolean }\n builder: ReturnType<typeof useBuilder>\n clipboard: ReturnType<typeof useStudioClipboard>\n}) {\n // EC-282 in-canvas reuse gestures, present only where a reuse capability is\n // configured (and the row is eligible). Null outside the composition editor.\n const reuse = useReuse()\n const canMakeReusable = !row.isRoot && (reuse?.canMakeReusable(row.id) ?? false)\n const canDetach = reuse?.canDetach(row.id) ?? false\n return (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button\n size=\"icon-sm\"\n variant=\"ghost\"\n aria-label={translate('builder.pageTree.moreActions')}\n title={translate('builder.pageTree.moreActions')}\n className=\"size-6\"\n >\n <MoreHorizontal />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n <DropdownMenuItem onClick={() => clipboard.copyNode(row.id)}>\n <ClipboardCopy /> {translate('common.copy')} <MenuShortcut>⌘C</MenuShortcut>\n </DropdownMenuItem>\n <DropdownMenuItem onClick={() => void clipboard.pasteFromClipboard(row.id)}>\n <ClipboardPaste /> {translate('builder.clipboard.paste')} <MenuShortcut>⌘V</MenuShortcut>\n </DropdownMenuItem>\n {!row.isRoot ? (\n <>\n <DropdownMenuItem onClick={() => clipboard.cutNode(row.id)}>\n <ClipboardX /> {translate('builder.clipboard.cut')} <MenuShortcut>⌘X</MenuShortcut>\n </DropdownMenuItem>\n <DropdownMenuSeparator />\n <DropdownMenuItem\n disabled={row.isFirst}\n onClick={() => builder.dispatch({ type: 'reorder', id: row.id, delta: -1 })}\n >\n <ChevronUp /> {translate('builder.pageTree.moveUp')}\n </DropdownMenuItem>\n <DropdownMenuItem\n disabled={row.isLast}\n onClick={() => builder.dispatch({ type: 'reorder', id: row.id, delta: 1 })}\n >\n <ChevronDown /> {translate('builder.pageTree.moveDown')}\n </DropdownMenuItem>\n <DropdownMenuItem onClick={() => builder.dispatch({ type: 'duplicate', id: row.id })}>\n <Copy /> {translate('common.duplicate')}\n </DropdownMenuItem>\n {canMakeReusable || canDetach ? <DropdownMenuSeparator /> : null}\n {canMakeReusable ? (\n <DropdownMenuItem onClick={() => reuse?.startMakeReusable(row.id)}>\n <Boxes /> {translate('builder.reuse.makeReusable')}\n </DropdownMenuItem>\n ) : null}\n {canDetach ? (\n <DropdownMenuItem onClick={() => reuse?.detach(row.id)}>\n <Unlink /> {translate('builder.reuse.detachToCopy')}\n </DropdownMenuItem>\n ) : null}\n <DropdownMenuSeparator />\n <DropdownMenuItem\n variant=\"destructive\"\n onClick={() => builder.dispatch({ type: 'remove', id: row.id })}\n >\n <Trash2 /> {translate('common.delete')}\n </DropdownMenuItem>\n </>\n ) : null}\n </DropdownMenuContent>\n </DropdownMenu>\n )\n}\n\nfunction IconAction({\n label,\n onClick,\n disabled,\n destructive,\n children,\n}: {\n label: string\n onClick: () => void\n disabled?: boolean\n destructive?: boolean\n children: React.ReactNode\n}) {\n return (\n <Button\n size=\"icon-sm\"\n variant=\"ghost\"\n aria-label={label}\n title={label}\n disabled={disabled}\n onClick={onClick}\n className={cn('size-6', destructive && 'hover:text-destructive')}\n >\n {children}\n </Button>\n )\n}\n"]}
|
|
1
|
+
{"version":3,"file":"page-tree.js","sourceRoot":"","sources":["../../../src/components/builder/page-tree.tsx"],"names":[],"mappings":"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","sourcesContent":["import {\n Boxes,\n ChevronUp,\n ChevronDown,\n ClipboardCopy,\n ClipboardPaste,\n ClipboardX,\n Copy,\n MoreHorizontal,\n Plus,\n Trash2,\n Unlink,\n} from 'lucide-react'\nimport { useEffect, useRef, useState } from 'react'\nimport type { ComponentNode } from '@elytracms/core/project-graph'\nimport { getParentInfo } from '@elytracms/core/project-graph'\nimport {\n canNestInto,\n containerFlowAxis,\n dropIndicatorFor,\n dropPositionForPointer,\n manifestRules,\n resolveDrop,\n} from '@elytracms/studio/lib/dnd/drop-rules'\nimport type {\n DragSubject,\n DropIndicator,\n DropPlan,\n DropPosition,\n DropResolution,\n ManifestLookup,\n} from '@elytracms/studio/lib/dnd/drop-rules'\nimport { getStudioRegistry } from '@elytracms/studio/lib/registry'\nimport { shortId } from '@elytracms/studio/lib/persona/labels'\nimport { translate, useUiLocale } from '@elytracms/studio/lib/i18n'\nimport {\n Button,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n Row,\n Spacer,\n Text,\n} from '@elytracms/studio/ui'\nimport { cn } from '@elytracms/studio/lib/utils'\nimport { useBuilder } from './builder-store'\nimport { attachClipboardListeners, useStudioClipboard } from './clipboard-actions'\nimport { attachDeleteKeyListener } from './delete-key'\nimport { nodeDisplayTitle } from './node-title'\nimport { useReuse } from './reuse-context'\nimport { activeDrag, beginDrag, endDrag, onDragSessionEnd } from './drag-session'\n\ninterface TreeRow {\n id: string\n componentId: string\n /** The node itself — EC-407 derives the instance title from its prop values. */\n node: ComponentNode\n depth: number\n isRoot: boolean\n isFirst: boolean\n isLast: boolean\n}\n\nfunction flatten(\n node: ComponentNode,\n depth: number,\n rows: TreeRow[],\n meta: { isFirst: boolean; isLast: boolean; isRoot: boolean },\n): void {\n rows.push({ id: node.id, componentId: node.componentId, node, depth, ...meta })\n for (const children of Object.values(node.slots ?? {})) {\n children.forEach((child, i) =>\n flatten(child, depth + 1, rows, {\n isFirst: i === 0,\n isLast: i === children.length - 1,\n isRoot: false,\n }),\n )\n }\n}\n\n/**\n * The tree's dragover resolution (EC-160/EC-162), pure: the row under the pointer\n * → a drop position (the hit band along the parent's flow axis; the root row is\n * always `into`) → the shared {@link resolveDrop} verdict → the row indicator.\n * Extracted from the React handler (EC-554) so the tree surface's placement rules\n * are pinned without a DOM — the handler only measures the pointer and renders.\n */\nexport function resolveTreeRowDrop(args: {\n root: ComponentNode\n getManifest: ManifestLookup\n /** The composition field's top-level `allow` (EC-554) — `builder.field.allow`. */\n fieldAllow: readonly string[] | undefined\n subject: DragSubject\n row: { id: string; componentId: string; isRoot: boolean }\n /** Pointer offset within the row box, 0..1 per axis (unused for the root row). */\n pointer: { x: number; y: number }\n}): { resolution: DropResolution; indicator: DropIndicator } {\n const { root, getManifest, fieldAllow, subject, row, pointer } = args\n let position: DropPosition = 'into'\n if (!row.isRoot) {\n // The hit-band axis follows the parent container's flow (EC-162):\n // children of horizontal Stacks/Grids reorder along the row's x axis.\n const flow = containerFlowAxis(getParentInfo(root, row.id)?.parent)\n position = dropPositionForPointer({\n ratio: flow === 'horizontal' ? pointer.x : pointer.y,\n nestable: canNestInto(getManifest, row.componentId),\n edges: true,\n })\n }\n const resolution = resolveDrop({\n root,\n getManifest,\n fieldAllow,\n subject,\n location: { nodeId: row.id, position },\n })\n return { resolution, indicator: dropIndicatorFor(resolution, position) }\n}\n\n/** Tailwind classes per drop indicator state (EC-160/EC-162) — rejection is visible. */\nconst ROW_DROP_CLASSES: Record<Exclude<DropIndicator, null>, string> = {\n into: 'ring-1 ring-inset ring-emerald-400 bg-emerald-400/10',\n before: 'shadow-[inset_0_2px_0_0_#38bdf8]',\n after: 'shadow-[inset_0_-2px_0_0_#38bdf8]',\n // Wrap-into-columns gesture (EC-162): the tree can execute wrap plans, the\n // bands themselves are a canvas gesture (rows have no spatial \"beside\").\n left: 'shadow-[inset_2px_0_0_0_#a78bfa] bg-violet-400/10',\n right: 'shadow-[inset_-2px_0_0_0_#a78bfa] bg-violet-400/10',\n blocked: 'ring-1 ring-inset ring-red-500/80 bg-red-500/10 cursor-not-allowed',\n}\n\nexport function PageTree({ onAddChild }: { onAddChild: (parentId: string) => void }) {\n const builder = useBuilder()\n const studio = getStudioRegistry()\n const uiLocale = useUiLocale()\n const rows: TreeRow[] = []\n flatten(builder.present, 0, rows, { isFirst: true, isLast: true, isRoot: true })\n\n // Copy/cut/paste (EC-175): keyboard shortcuts for the whole studio window\n // (the canvas iframe wires its own document) plus per-row affordances. The\n // shared listener keeps shortcuts inert in form fields/inline editors. (A\n // \"published preview is read-only\" guard used to sit here, keyed on the\n // collaboration store's `previewMode`; that mode was a constant `'draft'` with\n // no setter caller, so the guard never fired — EC-574 deleted both.)\n const clipboard = useStudioClipboard()\n const selectedIdRef = useRef(builder.selectedId)\n selectedIdRef.current = builder.selectedId\n useEffect(\n () =>\n attachClipboardListeners(window, {\n clipboard,\n getSelectedId: () => selectedIdRef.current,\n getSelection: () => window.getSelection(),\n }),\n [clipboard],\n )\n\n // Delete/Backspace removes the selected node from the tree side too (EC-404),\n // beside the clipboard shortcuts. Inert while focus is in a form field /\n // inline editor (isEditingContext).\n const { dispatch } = builder\n const rootId = builder.present.id\n useEffect(\n () =>\n attachDeleteKeyListener(window, {\n getSelectedId: () => selectedIdRef.current,\n remove: (id) => dispatch({ type: 'remove', id }),\n getRootId: () => rootId,\n }),\n [rootId, dispatch],\n )\n\n // Drag & drop (EC-160): eligibility comes from the same pure rule set the\n // canvas uses (`lib/dnd/drop-rules`); the tree only renders the resolution.\n // One eligible drop dispatches exactly one move/insert action.\n // The current plan lives in a ref: `drop` fires synchronously after the\n // last `dragover`, before React flushes indicator state.\n const dropPlanRef = useRef<DropPlan | null>(null)\n const [drop, setDrop] = useState<{ rowId: string; indicator: DropIndicator } | null>(null)\n const clearDrop = () => {\n dropPlanRef.current = null\n setDrop(null)\n }\n useEffect(() => onDragSessionEnd(() => clearDrop()), [])\n // EC-386: adapt through `manifestRules` so blocks-prop regions (e.g. a\n // Section's `body`) are drop targets with their `allow` leash, like raw slots.\n const getManifest = (id: string) => manifestRules(studio.registry.getManifest(id))\n\n function handleRowDragStart(row: TreeRow, e: React.DragEvent) {\n if (row.isRoot) {\n e.preventDefault()\n return\n }\n beginDrag(\n {\n subject: { kind: 'node', nodeId: row.id, componentId: row.componentId },\n source: 'tree',\n label: shortId(row.componentId),\n },\n e.dataTransfer,\n )\n }\n\n function handleRowDragOver(row: TreeRow, e: React.DragEvent) {\n const drag = activeDrag()\n if (!drag) return\n const rect = e.currentTarget.getBoundingClientRect()\n const { resolution, indicator } = resolveTreeRowDrop({\n root: builder.present,\n getManifest,\n fieldAllow: builder.field.allow,\n subject: drag.subject,\n row,\n pointer: {\n x: rect.width > 0 ? (e.clientX - rect.left) / rect.width : 0.5,\n y: rect.height > 0 ? (e.clientY - rect.top) / rect.height : 0.5,\n },\n })\n dropPlanRef.current = resolution.eligible ? resolution : null\n setDrop({ rowId: row.id, indicator })\n if (resolution.eligible) {\n e.preventDefault()\n e.dataTransfer.dropEffect = drag.subject.kind === 'node' ? 'move' : 'copy'\n }\n }\n\n function handleRowDrop(e: React.DragEvent) {\n const drag = activeDrag()\n if (!drag) return\n const plan = dropPlanRef.current\n clearDrop()\n if (!plan) return\n e.preventDefault()\n // Exactly one dispatch per completed drop (one undo step, one command).\n if (plan.kind === 'move') {\n builder.dispatch({ type: 'move', id: plan.nodeId, target: plan.target })\n } else if (plan.kind === 'wrap') {\n // EC-162: shared plan vocabulary — the tree executes wrap plans too.\n const subject =\n plan.nodeId !== undefined\n ? { kind: 'node' as const, nodeId: plan.nodeId }\n : drag.buildNode\n ? { kind: 'component' as const, node: drag.buildNode() }\n : null\n if (!subject) {\n endDrag('cancelled')\n return\n }\n builder.dispatch({ type: 'wrap', referenceId: plan.referenceId, side: plan.side, subject })\n } else if (drag.buildNode) {\n builder.dispatch({ type: 'insert', target: plan.target, node: drag.buildNode() })\n } else {\n endDrag('cancelled')\n return\n }\n endDrag('dropped')\n }\n\n function handleRowDragEnd() {\n clearDrop()\n endDrag('cancelled')\n }\n\n return (\n <div className=\"flex h-full min-h-0 flex-col\">\n <Row gap=\"2\" className=\"h-10 shrink-0 border-b border-border px-3\">\n <Text size=\"sm\" weight=\"semibold\">\n {translate('builder.pageTree.title')}\n </Text>\n <Spacer />\n <Text size=\"xs\" tone=\"subtle\" mono>\n {rows.length}\n </Text>\n </Row>\n <div\n className=\"min-h-0 flex-1 overflow-auto p-1.5\"\n onMouseLeave={() => builder.dispatch({ type: 'hover', id: null })}\n onDragLeave={(e) => {\n // Clear the indicator when the drag exits the tree panel.\n if (!e.currentTarget.contains(e.relatedTarget as Node | null)) clearDrop()\n }}\n >\n {rows.map((row) => {\n const manifest = studio.registry.getManifest(row.componentId)\n const title = nodeDisplayTitle(manifest, row.node, uiLocale)\n const selected = row.id === builder.selectedId\n const hovered = row.id === builder.hoveredId\n return (\n <div\n key={row.id}\n onMouseEnter={() => builder.dispatch({ type: 'hover', id: row.id })}\n draggable={!row.isRoot}\n onDragStart={(e) => handleRowDragStart(row, e)}\n onDragOver={(e) => handleRowDragOver(row, e)}\n onDrop={handleRowDrop}\n onDragEnd={handleRowDragEnd}\n data-tree-row={row.id}\n data-tree-drop={drop?.rowId === row.id ? (drop.indicator ?? undefined) : undefined}\n className={cn(\n 'group flex items-center rounded-md pr-1 text-left text-xs transition-colors',\n selected\n ? 'bg-accent text-accent-foreground'\n : hovered\n ? 'bg-muted text-foreground'\n : 'text-muted-foreground',\n drop?.rowId === row.id && drop.indicator\n ? ROW_DROP_CLASSES[drop.indicator]\n : undefined,\n )}\n >\n <button\n type=\"button\"\n onClick={() => builder.dispatch({ type: 'select', id: row.id })}\n title={row.componentId}\n className=\"flex min-w-0 flex-1 items-center gap-1.5 py-1\"\n style={{ paddingLeft: `${6 + row.depth * 14}px` }}\n >\n {/* EC-407: instance title (its own text content) is primary; the\n localized component name is secondary context. The raw mono id\n left the row — it stays in the `title=` above + inspector header. */}\n <span className=\"truncate\">{title.primary}</span>\n {title.secondary ? (\n <span className=\"truncate text-muted-foreground/70\">{title.secondary}</span>\n ) : null}\n </button>\n\n <div\n className={cn(\n 'flex shrink-0 items-center gap-0.5',\n selected\n ? 'opacity-100'\n : 'opacity-0 group-hover:opacity-100 group-focus-within:opacity-100',\n )}\n >\n {/* Add child stays inline — the one constructive, high-frequency\n action. Everything else collapses into the ••• menu so labels\n stop cropping on the 200–240px panel (EC-211). */}\n <IconAction label={translate('builder.pageTree.addChild')} onClick={() => onAddChild(row.id)}>\n <Plus />\n </IconAction>\n <RowActionsMenu row={row} builder={builder} clipboard={clipboard} />\n </div>\n </div>\n )\n })}\n </div>\n </div>\n )\n}\n\n/** Right-aligned keyboard-shortcut hint in a menu item — informational only; the\n * shortcut fires globally via the clipboard listeners regardless of the menu. */\nfunction MenuShortcut({ children }: { children: React.ReactNode }) {\n return <span className=\"ml-auto pl-6 text-xs tracking-widest text-muted-foreground\">{children}</span>\n}\n\n/**\n * The per-row \"•••\" action menu (EC-211). Copy/Paste on every row; Cut, Move,\n * Duplicate, Delete for non-root rows. Collapsing the old inline icon cluster\n * (up to 8 buttons, cropping on the 200–240px panel) into a menu changes no\n * behavior — same clipboard fragment + dispatch paths, same Cmd/Ctrl shortcuts.\n * Delete stays a one-click (undoable) destructive item, consistent with the rest\n * of the studio.\n */\nfunction RowActionsMenu({\n row,\n builder,\n clipboard,\n}: {\n row: { id: string; isRoot: boolean; isFirst: boolean; isLast: boolean }\n builder: ReturnType<typeof useBuilder>\n clipboard: ReturnType<typeof useStudioClipboard>\n}) {\n // EC-282 in-canvas reuse gestures, present only where a reuse capability is\n // configured (and the row is eligible). Null outside the composition editor.\n const reuse = useReuse()\n const canMakeReusable = !row.isRoot && (reuse?.canMakeReusable(row.id) ?? false)\n const canDetach = reuse?.canDetach(row.id) ?? false\n return (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button\n size=\"icon-sm\"\n variant=\"ghost\"\n aria-label={translate('builder.pageTree.moreActions')}\n title={translate('builder.pageTree.moreActions')}\n className=\"size-6\"\n >\n <MoreHorizontal />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n <DropdownMenuItem onClick={() => clipboard.copyNode(row.id)}>\n <ClipboardCopy /> {translate('common.copy')} <MenuShortcut>⌘C</MenuShortcut>\n </DropdownMenuItem>\n <DropdownMenuItem onClick={() => void clipboard.pasteFromClipboard(row.id)}>\n <ClipboardPaste /> {translate('builder.clipboard.paste')} <MenuShortcut>⌘V</MenuShortcut>\n </DropdownMenuItem>\n {!row.isRoot ? (\n <>\n <DropdownMenuItem onClick={() => clipboard.cutNode(row.id)}>\n <ClipboardX /> {translate('builder.clipboard.cut')} <MenuShortcut>⌘X</MenuShortcut>\n </DropdownMenuItem>\n <DropdownMenuSeparator />\n <DropdownMenuItem\n disabled={row.isFirst}\n onClick={() => builder.dispatch({ type: 'reorder', id: row.id, delta: -1 })}\n >\n <ChevronUp /> {translate('builder.pageTree.moveUp')}\n </DropdownMenuItem>\n <DropdownMenuItem\n disabled={row.isLast}\n onClick={() => builder.dispatch({ type: 'reorder', id: row.id, delta: 1 })}\n >\n <ChevronDown /> {translate('builder.pageTree.moveDown')}\n </DropdownMenuItem>\n <DropdownMenuItem onClick={() => builder.dispatch({ type: 'duplicate', id: row.id })}>\n <Copy /> {translate('common.duplicate')}\n </DropdownMenuItem>\n {canMakeReusable || canDetach ? <DropdownMenuSeparator /> : null}\n {canMakeReusable ? (\n <DropdownMenuItem onClick={() => reuse?.startMakeReusable(row.id)}>\n <Boxes /> {translate('builder.reuse.makeReusable')}\n </DropdownMenuItem>\n ) : null}\n {canDetach ? (\n <DropdownMenuItem onClick={() => reuse?.detach(row.id)}>\n <Unlink /> {translate('builder.reuse.detachToCopy')}\n </DropdownMenuItem>\n ) : null}\n <DropdownMenuSeparator />\n <DropdownMenuItem\n variant=\"destructive\"\n onClick={() => builder.dispatch({ type: 'remove', id: row.id })}\n >\n <Trash2 /> {translate('common.delete')}\n </DropdownMenuItem>\n </>\n ) : null}\n </DropdownMenuContent>\n </DropdownMenu>\n )\n}\n\nfunction IconAction({\n label,\n onClick,\n disabled,\n destructive,\n children,\n}: {\n label: string\n onClick: () => void\n disabled?: boolean\n destructive?: boolean\n children: React.ReactNode\n}) {\n return (\n <Button\n size=\"icon-sm\"\n variant=\"ghost\"\n aria-label={label}\n title={label}\n disabled={disabled}\n onClick={onClick}\n className={cn('size-6', destructive && 'hover:text-destructive')}\n >\n {children}\n </Button>\n )\n}\n"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { manifestPropFields } from '@elytracms/core/component-registry';
|
|
2
2
|
import { readDocumentRef, seedDefaults, titleFieldOf } from '@elytracms/core/cms-core';
|
|
3
|
-
import { isStaticProp } from '@elytracms/core/project-graph';
|
|
3
|
+
import { editBlockRoots, isStaticProp } from '@elytracms/core/project-graph';
|
|
4
4
|
import { slugify } from '@elytracms/studio/lib/utils';
|
|
5
5
|
/** The customization point name stamped onto every reuse issue (EC-236 locator). */
|
|
6
6
|
const REUSE_POINT = 'reuse';
|
|
@@ -258,7 +258,10 @@ export function buildReusableValues(collection, capability, name, node, existing
|
|
|
258
258
|
values[field.name] = uniqueSlug(name, taken);
|
|
259
259
|
}
|
|
260
260
|
}
|
|
261
|
-
|
|
261
|
+
// EC-576: the converged v2 sequence holding the tagged node, through the core
|
|
262
|
+
// write path — never the pre-0.0.84 bare node/array. Cardinality is a
|
|
263
|
+
// constraint over that one shape, not a second shape (EC-500 D2).
|
|
264
|
+
values[capability.contentField] = editBlockRoots(undefined, () => [node]);
|
|
262
265
|
return values;
|
|
263
266
|
}
|
|
264
267
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reuse.js","sourceRoot":"","sources":["../../../src/components/builder/reuse.ts"],"names":[],"mappings":"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","sourcesContent":["import { manifestPropFields } from '@elytracms/core/component-registry'\nimport type { ComponentManifest } from '@elytracms/core/component-registry'\nimport { readDocumentRef, seedDefaults, titleFieldOf } from '@elytracms/core/cms-core'\nimport type { CollectionDef, DocumentRef } from '@elytracms/core/cms-core'\nimport { isStaticProp } from '@elytracms/core/project-graph'\nimport type { ComponentNode } from '@elytracms/core/project-graph'\nimport type { CustomizationIssue, CustomizationMessageRef } from '@elytracms/core/studio-core'\nimport { slugify } from '@elytracms/studio/lib/utils'\n\n/**\n * The reuse capability (EC-282): the engine-generic description of how a project\n * models reusable elements. **AD-a (EC-467): reusable elements are SINGULAR per\n * project** — exactly one embed component and exactly one reusable collection.\n * Multiple reuse collections are deliberately not supported, which is why this is\n * one value and never a list.\n *\n * The capability is normally DERIVED from config (manifests + collections) — no\n * declaration needed, so a consumer that already has a transcluding embed gets the\n * in-canvas gestures for free (the EC-296 \"works automatically\" pattern). The\n * derivation predicate for a \"reusable embed\" component: it contributes no slots of\n * its own and has exactly ONE `relation` prop (cardinality `one`) whose target\n * collection holds a `blocks` field — i.e. it embeds a single document and renders\n * that document's composition by transclusion (EC-254/EC-281). For Konditorei that\n * is uniquely `project.Reusable` → `element` → `reusable.content`.\n *\n * EC-462 — the predicate additionally DISQUALIFIES a candidate whose own manifest\n * explicitly declares its relation prop to be something other than an embed\n * pointer. The shape test alone proved unsound the moment a relation prop's\n * target collection gains a `blocks` field of its own (a category/page with a\n * canvas — a completely natural content shape): the EC-276 sprint's ProductGrid\n * and the demo's OptionBox were both claimed as \"the embed\", so their ordinary\n * relation picks rendered the reusable-element placeholder, grew Detach/Make-\n * reusable gestures, and fed embed-worded publish-integrity issues. Two\n * explicit-declaration disqualifiers close the observed class:\n *\n * - a manifest with a `listing` declaration is a QUERY host (EC-255) — its\n * relation prop is the query's filter source (`listing.filter.fromProp`),\n * never a transclusion pointer;\n * - a relation prop with `form.hidden` (predicate or `true`) is conditional/\n * auxiliary — EC-394 strips a hidden relation's ref at publish, so it cannot\n * coherently be what the component transcludes.\n *\n * Both read only the CANDIDATE manifest's own declarations (never the target's\n * contents) and can only EXCLUDE, so a genuine pure embed (which declares\n * neither) is always kept. EC-462's residual — a bare unconditional\n * single-relation display component (e.g. a page teaser) is at rest\n * indistinguishable from a genuine embed — is closed by EC-467's optional\n * {@link StudioReuseConfig} declaration: a project that hits ambiguity declares\n * its way out, and the ambiguity is LOUD instead of silently disabling the\n * gestures. See {@link resolveReuse}.\n */\nexport interface ReuseCapability {\n /** The embed component, e.g. `project.Reusable`. */\n embedComponentId: string\n /** The embed's relation prop that points at the reusable document, e.g. `element`. */\n relationProp: string\n /** The collection reusable documents live in, e.g. `reusable`. */\n collectionId: string\n /** The collection's composition field the embed transcludes, e.g. `content`. */\n contentField: string\n /** Whether that field holds a single block or a list. */\n contentCardinality: 'one' | 'many'\n}\n\n/**\n * EC-467 (AD-b) — the OPTIONAL studio-config declaration of the reuse embed:\n * `defineStudio({ reuse: { component: 'project.Reusable' } })`. See\n * {@link import('../app-views/studio-config').StudioConfig.reuse} for the\n * consumer-facing contract; this is the shape {@link resolveReuse} consumes.\n *\n * One identifier is enough because the rest DERIVES from it (its single-document\n * relation prop → that prop's target collection → that collection's `blocks`\n * field), and because reusable elements are singular per project (AD-a).\n */\nexport interface StudioReuseConfig {\n /** The embed component's registry id, e.g. `'project.Reusable'`. */\n component: string\n /**\n * Which relation prop points at the reusable document. Needed ONLY when the\n * declared component legitimately has more than one single-document relation\n * prop — otherwise the sole one is derived.\n */\n relationProp?: string\n}\n\n/**\n * EC-467 — the resolution of the project's reuse capability: the capability (or\n * `null`), plus the config issues a developer must SEE.\n *\n * The issues ride the EC-236 {@link CustomizationIssue} surface (`point: 'reuse'`),\n * exactly like the sidebar's and the field renderers' — so they render at the\n * studio rail foot on every surface, which is the studio's home for\n * project-CONFIGURATION problems (the document-scoped Issues pane is the wrong\n * home: this is not a content problem in one document).\n *\n * EC-478: each issue carries a `messageRef` (catalog key + identifier values)\n * alongside its prebuilt English, so the rail foot renders it in the active UI\n * locale. This module stays pure — it names a key, it never calls `translate`;\n * `lib/persona/labels.ts` resolves it at the render site. That is what makes the\n * whole list localize together, which is the mixed-language outcome EC-467\n * declined to create by localizing only its own half.\n */\nexport interface ReuseResolution {\n capability: ReuseCapability | null\n /** Empty whenever the capability is unambiguous — including \"this project has none\". */\n issues: CustomizationIssue[]\n}\n\n/** The customization point name stamped onto every reuse issue (EC-236 locator). */\nconst REUSE_POINT = 'reuse'\n\n/** A `relation` prop of a manifest, normalized for both resolution paths. */\ninterface RelationProp {\n name: string\n /** The target collection id; `undefined` when the manifest declares none. */\n target: string | undefined\n multiple: boolean\n /** EC-462: `form.hidden` present and not literally `false` (predicate or `true`). */\n conditional: boolean\n}\n\n/** Every `relation` prop of a manifest, in declaration order. */\nfunction relationPropsOf(manifest: ComponentManifest): RelationProp[] {\n const props: RelationProp[] = []\n for (const field of manifestPropFields(manifest)) {\n if (field.type !== 'relation') continue\n const hidden = field.form?.hidden\n props.push({\n name: field.name,\n target: field.target,\n multiple: field.multiple === true,\n conditional: hidden !== undefined && hidden !== false,\n })\n }\n return props\n}\n\n/**\n * The relation props a DECLARED embed could plausibly transclude through: targeted\n * and cardinality-one. A cardinality-`many` (or targetless) relation is excluded\n * structurally, not heuristically — an embed transcludes exactly one document and\n * {@link makeEmbedNode} writes exactly one `{collection,id}`, which a list-valued\n * or target-less prop cannot hold.\n */\nfunction embeddableRelationProps(manifest: ComponentManifest): TargetedRelationProp[] {\n return relationPropsOf(manifest).filter(\n (r): r is TargetedRelationProp => r.target !== undefined && !r.multiple,\n )\n}\ntype TargetedRelationProp = RelationProp & { target: string }\n\n/** The collection's first composition field — what an embed of it transcludes. */\nfunction blocksFieldOf(collection: CollectionDef) {\n const field = collection.fields.find((f) => f.type === 'blocks')\n return field?.type === 'blocks' ? field : undefined\n}\n\n/**\n * The catalog keys for the reuse config issues (EC-478), in the `builder.reuse.config.*`\n * namespace. Named here so the producer and the catalog-parity test agree on one list.\n */\nconst REUSE_KEYS = {\n unknownComponent: 'builder.reuse.config.unknownComponent',\n noRelationProp: 'builder.reuse.config.noRelationProp',\n unknownRelationProp: 'builder.reuse.config.unknownRelationProp',\n ambiguousRelationProp: 'builder.reuse.config.ambiguousRelationProp',\n unknownCollection: 'builder.reuse.config.unknownCollection',\n noBlocksField: 'builder.reuse.config.noBlocksField',\n ambiguousInference: 'builder.reuse.config.ambiguousInference',\n} as const\n\nfunction reuseIssue(\n code: CustomizationIssue['code'],\n severity: CustomizationIssue['severity'],\n message: string,\n messageRef: CustomizationMessageRef,\n id?: string,\n): CustomizationIssue {\n return {\n code,\n severity,\n message,\n messageRef,\n point: REUSE_POINT,\n ...(id === undefined ? {} : { id }),\n }\n}\n\n/** `\"a\", \"b\"` — a readable list of prop/component ids for an issue message. */\nfunction quotedList(names: readonly string[]): string {\n return names.map((name) => `\"${name}\"`).join(', ')\n}\n\n/**\n * The components whose SHAPE makes them plausible reuse embeds (the zero-config\n * heuristic + EC-462's disqualifiers), in manifest order. Exported for the tests\n * and for issue copy; the resolution rule lives in {@link resolveReuse}.\n */\nexport function inferredEmbedCandidates(\n manifests: readonly ComponentManifest[],\n collections: readonly CollectionDef[],\n): ReuseCapability[] {\n const matches: ReuseCapability[] = []\n for (const manifest of manifests) {\n if (manifest.slots.length > 0) continue\n // EC-462: a `listing` manifest is a query host by its own declaration — its\n // relation prop is the filter source, never an embed pointer.\n if (manifest.listing) continue\n // The heuristic requires the relation to be UNAMBIGUOUS within the manifest:\n // exactly ONE relation prop of any kind, which must then be targeted,\n // cardinality-one and unconditional. (Counting all relation props — not just\n // the embeddable ones — is deliberate: a component with a second, list-valued\n // relation is not the single-purpose shape the heuristic is allowed to claim.)\n const relations = relationPropsOf(manifest)\n const relation = relations.length === 1 ? relations[0]! : undefined\n if (!relation || relation.target === undefined || relation.multiple || relation.conditional)\n continue\n const collection = collections.find((c) => c.id === relation.target)\n if (!collection) continue\n const blocksField = blocksFieldOf(collection)\n if (!blocksField) continue\n matches.push({\n embedComponentId: manifest.id,\n relationProp: relation.name,\n collectionId: collection.id,\n contentField: blocksField.name,\n contentCardinality: blocksField.multiple === false ? 'one' : 'many',\n })\n }\n return matches\n}\n\n/**\n * Resolve the DECLARED embed (EC-467, AD-b). An explicit declaration is the\n * developer's authority, so the inference disqualifiers are NOT applied here: a\n * declared component may have slots, may declare `listing`, and its relation prop\n * may be `form.hidden` — those exist to stop the engine GUESSING, not to overrule\n * a stated intent. Only structural impossibilities are issues (see AD-c row 2):\n * an unknown component id, no usable relation prop (or an ambiguous set with no\n * `relationProp`), an undefined target collection, or a target with no `blocks`\n * field — each of which would leave nothing coherent to transclude.\n */\nfunction resolveDeclaredReuse(\n declaration: StudioReuseConfig,\n manifests: readonly ComponentManifest[],\n collections: readonly CollectionDef[],\n): ReuseResolution {\n const { component, relationProp } = declaration\n const fail = (message: string, messageRef: CustomizationMessageRef): ReuseResolution => ({\n capability: null,\n issues: [reuseIssue('invalid-override', 'error', message, messageRef, component)],\n })\n\n const manifest = manifests.find((m) => m.id === component)\n if (!manifest) {\n return {\n capability: null,\n issues: [\n reuseIssue(\n 'unknown-override-target',\n 'error',\n `Reusable elements: studio config declares reuse.component \"${component}\", which is not a registered component. Fix the id, or remove the reuse block to let the studio infer the embed.`,\n { key: REUSE_KEYS.unknownComponent, vars: { component } },\n component,\n ),\n ],\n }\n }\n\n const relations = embeddableRelationProps(manifest)\n if (relations.length === 0) {\n return fail(\n `Reusable elements: the declared embed \"${component}\" has no single-document relation prop, so it cannot point at a reusable document. An embed needs one relation prop with cardinality one.`,\n { key: REUSE_KEYS.noRelationProp, vars: { component } },\n )\n }\n const names = relations.map((r) => r.name)\n let relation: TargetedRelationProp\n if (relationProp !== undefined) {\n const named = relations.find((r) => r.name === relationProp)\n if (!named) {\n return fail(\n `Reusable elements: the declared embed \"${component}\" has no single-document relation prop named \"${relationProp}\". Its relation props are: ${quotedList(names)}.`,\n {\n key: REUSE_KEYS.unknownRelationProp,\n vars: { component, relationProp, props: quotedList(names) },\n },\n )\n }\n relation = named\n } else if (relations.length === 1) {\n relation = relations[0]!\n } else {\n return fail(\n `Reusable elements: the declared embed \"${component}\" has ${relations.length} single-document relation props (${quotedList(names)}) — add reuse.relationProp to say which one points at the reusable document.`,\n {\n key: REUSE_KEYS.ambiguousRelationProp,\n vars: { component, count: relations.length, props: quotedList(names) },\n },\n )\n }\n\n const collection = collections.find((c) => c.id === relation.target)\n if (!collection) {\n return fail(\n `Reusable elements: \"${component}.${relation.name}\" targets the collection \"${relation.target}\", which this project does not define.`,\n {\n key: REUSE_KEYS.unknownCollection,\n vars: { component, relationProp: relation.name, collection: relation.target },\n },\n )\n }\n const blocksField = blocksFieldOf(collection)\n if (!blocksField) {\n return fail(\n `Reusable elements: the collection \"${collection.id}\" behind \"${component}.${relation.name}\" has no blocks (composition) field, so there is nothing for the embed to transclude.`,\n {\n key: REUSE_KEYS.noBlocksField,\n vars: { component, relationProp: relation.name, collection: collection.id },\n },\n )\n }\n\n return {\n capability: {\n embedComponentId: manifest.id,\n relationProp: relation.name,\n collectionId: collection.id,\n contentField: blocksField.name,\n contentCardinality: blocksField.multiple === false ? 'one' : 'many',\n },\n issues: [],\n }\n}\n\n/**\n * Resolve the project's ONE reuse capability (AD-a) plus any config issues —\n * EC-467's full matrix (AD-c):\n *\n * | Declaration | Candidates | Result |\n * | --- | --- | --- |\n * | present, resolves | any | the declared capability, no issue |\n * | present, does not resolve | any | `null` + an ERROR issue (misconfiguration) |\n * | absent | exactly 1 | that candidate, no issue (the zero-config default) |\n * | absent | 2 or more | `null` + a WARNING issue naming every candidate |\n * | absent | 0 | `null`, **no issue** — a project need not use reusable elements |\n *\n * Severity is the honest split: a declaration that cannot resolve is broken\n * config the developer asked for (error); inference ambiguity is nothing broken —\n * the engine declines to guess and asks for one line (warning). Both disable the\n * gestures, and both render identically in the rail's issue list.\n *\n * The zero-candidate row is deliberately silent: crying wolf at every project\n * that has no reusable elements would train developers to ignore the surface.\n */\nexport function resolveReuse(\n manifests: readonly ComponentManifest[],\n collections: readonly CollectionDef[],\n declaration?: StudioReuseConfig,\n): ReuseResolution {\n if (declaration) return resolveDeclaredReuse(declaration, manifests, collections)\n\n const candidates = inferredEmbedCandidates(manifests, collections)\n if (candidates.length === 1) return { capability: candidates[0]!, issues: [] }\n if (candidates.length === 0) return { capability: null, issues: [] }\n return {\n capability: null,\n issues: [\n reuseIssue(\n 'ambiguous-inference',\n 'warning',\n `Reusable elements: ${candidates.length} components have the reusable-embed shape (${quotedList(\n candidates.map((c) => c.embedComponentId),\n )}). A project has exactly one reusable-element embed, so the studio will not guess — declare it in studio.config as reuse: { component: '<id>' }. Until then the reuse gestures are off.`,\n {\n key: REUSE_KEYS.ambiguousInference,\n vars: {\n count: candidates.length,\n components: quotedList(candidates.map((c) => c.embedComponentId)),\n },\n },\n ),\n ],\n }\n}\n\n/**\n * The project's reuse capability alone, or `null` when it is absent, ambiguous, or\n * misdeclared — for callers that act on the capability and do not surface config\n * issues themselves (the publish-integrity walk). The issues are surfaced ONCE, by\n * the shell rail; see {@link resolveReuse}.\n */\nexport function resolveReuseCapability(\n manifests: readonly ComponentManifest[],\n collections: readonly CollectionDef[],\n declaration?: StudioReuseConfig,\n): ReuseCapability | null {\n return resolveReuse(manifests, collections, declaration).capability\n}\n\n/** A slug for `name`, suffixed `-2`/`-3`/… until it is free among `taken`. */\nexport function uniqueSlug(name: string, taken: ReadonlySet<string>): string {\n const base = slugify(name) || 'element'\n let id = base\n for (let n = 2; taken.has(id); n += 1) id = `${base}-${n}`\n return id\n}\n\n/**\n * The values for a new reusable document promoted from `node` (EC-282): seeded\n * defaults, the given `name` on the title field, the node on the content field,\n * and a collision-safe slug on any UNIQUE text field (e.g. Konditorei's\n * `internalId`) so the created document is publish-valid without hardcoding that\n * field's name. Other required fields, if any, are completed by the editor when\n * they open the new reusable.\n */\nexport function buildReusableValues(\n collection: CollectionDef,\n capability: ReuseCapability,\n name: string,\n node: ComponentNode,\n existing: readonly { values?: Record<string, unknown> }[],\n): Record<string, unknown> {\n const values: Record<string, unknown> = { ...seedDefaults(collection) }\n const titleField = titleFieldOf(collection)?.name\n if (titleField) values[titleField] = name\n for (const field of collection.fields) {\n if (\n field.type === 'text' &&\n field.validation?.unique === true &&\n field.name !== titleField &&\n field.name !== capability.contentField\n ) {\n const taken = new Set(\n existing\n .map((d) => d.values?.[field.name])\n .filter((v): v is string => typeof v === 'string'),\n )\n values[field.name] = uniqueSlug(name, taken)\n }\n }\n values[capability.contentField] = capability.contentCardinality === 'one' ? node : [node]\n return values\n}\n\n/**\n * The document a reuse-embed `node` targets (EC-300), or null when `node` is not the\n * embed component or has no static pick yet. Lets the canvas detect a HARD reference\n * whose target doesn't resolve (→ placeholder) without reaching into prop internals.\n */\nexport function reuseEmbedRef(node: ComponentNode, capability: ReuseCapability): DocumentRef | null {\n if (node.componentId !== capability.embedComponentId) return null\n const prop = node.props?.[capability.relationProp]\n const ref = isStaticProp(prop) ? readDocumentRef(prop.value) : undefined\n return ref ?? null\n}\n\n/**\n * Every reuse-embed target ref in a composition `blocks` value, at any depth\n * (EC-300) — the hard references a publish-integrity check walks to find\n * unpublished targets. Recurses slots; order is document order. Duplicates are\n * kept (the caller dedupes by key if needed).\n */\nexport function collectEmbedRefs(\n blocks: readonly ComponentNode[],\n capability: ReuseCapability,\n): DocumentRef[] {\n const refs: DocumentRef[] = []\n const visit = (node: ComponentNode): void => {\n const ref = reuseEmbedRef(node, capability)\n if (ref) refs.push(ref)\n for (const children of Object.values(node.slots ?? {})) for (const child of children) visit(child)\n }\n for (const node of blocks) visit(node)\n return refs\n}\n\n/** The `Reusable` embed node referencing the new document (id minted by the action). */\nexport function makeEmbedNode(capability: ReuseCapability, documentId: string): ComponentNode {\n return {\n id: 'reuse-embed',\n componentId: capability.embedComponentId,\n props: {\n [capability.relationProp]: {\n kind: 'static',\n value: { collection: capability.collectionId, id: documentId },\n },\n },\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"reuse.js","sourceRoot":"","sources":["../../../src/components/builder/reuse.ts"],"names":[],"mappings":"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","sourcesContent":["import { manifestPropFields } from '@elytracms/core/component-registry'\nimport type { ComponentManifest } from '@elytracms/core/component-registry'\nimport { readDocumentRef, seedDefaults, titleFieldOf } from '@elytracms/core/cms-core'\nimport type { CollectionDef, DocumentRef } from '@elytracms/core/cms-core'\nimport { editBlockRoots, isStaticProp } from '@elytracms/core/project-graph'\nimport type { ComponentNode } from '@elytracms/core/project-graph'\nimport type { CustomizationIssue, CustomizationMessageRef } from '@elytracms/core/studio-core'\nimport { slugify } from '@elytracms/studio/lib/utils'\n\n/**\n * The reuse capability (EC-282): the engine-generic description of how a project\n * models reusable elements. **AD-a (EC-467): reusable elements are SINGULAR per\n * project** — exactly one embed component and exactly one reusable collection.\n * Multiple reuse collections are deliberately not supported, which is why this is\n * one value and never a list.\n *\n * The capability is normally DERIVED from config (manifests + collections) — no\n * declaration needed, so a consumer that already has a transcluding embed gets the\n * in-canvas gestures for free (the EC-296 \"works automatically\" pattern). The\n * derivation predicate for a \"reusable embed\" component: it contributes no slots of\n * its own and has exactly ONE `relation` prop (cardinality `one`) whose target\n * collection holds a `blocks` field — i.e. it embeds a single document and renders\n * that document's composition by transclusion (EC-254/EC-281). For Konditorei that\n * is uniquely `project.Reusable` → `element` → `reusable.content`.\n *\n * EC-462 — the predicate additionally DISQUALIFIES a candidate whose own manifest\n * explicitly declares its relation prop to be something other than an embed\n * pointer. The shape test alone proved unsound the moment a relation prop's\n * target collection gains a `blocks` field of its own (a category/page with a\n * canvas — a completely natural content shape): the EC-276 sprint's ProductGrid\n * and the demo's OptionBox were both claimed as \"the embed\", so their ordinary\n * relation picks rendered the reusable-element placeholder, grew Detach/Make-\n * reusable gestures, and fed embed-worded publish-integrity issues. Two\n * explicit-declaration disqualifiers close the observed class:\n *\n * - a manifest with a `listing` declaration is a QUERY host (EC-255) — its\n * relation prop is the query's filter source (`listing.filter.fromProp`),\n * never a transclusion pointer;\n * - a relation prop with `form.hidden` (predicate or `true`) is conditional/\n * auxiliary — EC-394 strips a hidden relation's ref at publish, so it cannot\n * coherently be what the component transcludes.\n *\n * Both read only the CANDIDATE manifest's own declarations (never the target's\n * contents) and can only EXCLUDE, so a genuine pure embed (which declares\n * neither) is always kept. EC-462's residual — a bare unconditional\n * single-relation display component (e.g. a page teaser) is at rest\n * indistinguishable from a genuine embed — is closed by EC-467's optional\n * {@link StudioReuseConfig} declaration: a project that hits ambiguity declares\n * its way out, and the ambiguity is LOUD instead of silently disabling the\n * gestures. See {@link resolveReuse}.\n */\nexport interface ReuseCapability {\n /** The embed component, e.g. `project.Reusable`. */\n embedComponentId: string\n /** The embed's relation prop that points at the reusable document, e.g. `element`. */\n relationProp: string\n /** The collection reusable documents live in, e.g. `reusable`. */\n collectionId: string\n /** The collection's composition field the embed transcludes, e.g. `content`. */\n contentField: string\n /** Whether that field holds a single block or a list. */\n contentCardinality: 'one' | 'many'\n}\n\n/**\n * EC-467 (AD-b) — the OPTIONAL studio-config declaration of the reuse embed:\n * `defineStudio({ reuse: { component: 'project.Reusable' } })`. See\n * {@link import('../app-views/studio-config').StudioConfig.reuse} for the\n * consumer-facing contract; this is the shape {@link resolveReuse} consumes.\n *\n * One identifier is enough because the rest DERIVES from it (its single-document\n * relation prop → that prop's target collection → that collection's `blocks`\n * field), and because reusable elements are singular per project (AD-a).\n */\nexport interface StudioReuseConfig {\n /** The embed component's registry id, e.g. `'project.Reusable'`. */\n component: string\n /**\n * Which relation prop points at the reusable document. Needed ONLY when the\n * declared component legitimately has more than one single-document relation\n * prop — otherwise the sole one is derived.\n */\n relationProp?: string\n}\n\n/**\n * EC-467 — the resolution of the project's reuse capability: the capability (or\n * `null`), plus the config issues a developer must SEE.\n *\n * The issues ride the EC-236 {@link CustomizationIssue} surface (`point: 'reuse'`),\n * exactly like the sidebar's and the field renderers' — so they render at the\n * studio rail foot on every surface, which is the studio's home for\n * project-CONFIGURATION problems (the document-scoped Issues pane is the wrong\n * home: this is not a content problem in one document).\n *\n * EC-478: each issue carries a `messageRef` (catalog key + identifier values)\n * alongside its prebuilt English, so the rail foot renders it in the active UI\n * locale. This module stays pure — it names a key, it never calls `translate`;\n * `lib/persona/labels.ts` resolves it at the render site. That is what makes the\n * whole list localize together, which is the mixed-language outcome EC-467\n * declined to create by localizing only its own half.\n */\nexport interface ReuseResolution {\n capability: ReuseCapability | null\n /** Empty whenever the capability is unambiguous — including \"this project has none\". */\n issues: CustomizationIssue[]\n}\n\n/** The customization point name stamped onto every reuse issue (EC-236 locator). */\nconst REUSE_POINT = 'reuse'\n\n/** A `relation` prop of a manifest, normalized for both resolution paths. */\ninterface RelationProp {\n name: string\n /** The target collection id; `undefined` when the manifest declares none. */\n target: string | undefined\n multiple: boolean\n /** EC-462: `form.hidden` present and not literally `false` (predicate or `true`). */\n conditional: boolean\n}\n\n/** Every `relation` prop of a manifest, in declaration order. */\nfunction relationPropsOf(manifest: ComponentManifest): RelationProp[] {\n const props: RelationProp[] = []\n for (const field of manifestPropFields(manifest)) {\n if (field.type !== 'relation') continue\n const hidden = field.form?.hidden\n props.push({\n name: field.name,\n target: field.target,\n multiple: field.multiple === true,\n conditional: hidden !== undefined && hidden !== false,\n })\n }\n return props\n}\n\n/**\n * The relation props a DECLARED embed could plausibly transclude through: targeted\n * and cardinality-one. A cardinality-`many` (or targetless) relation is excluded\n * structurally, not heuristically — an embed transcludes exactly one document and\n * {@link makeEmbedNode} writes exactly one `{collection,id}`, which a list-valued\n * or target-less prop cannot hold.\n */\nfunction embeddableRelationProps(manifest: ComponentManifest): TargetedRelationProp[] {\n return relationPropsOf(manifest).filter(\n (r): r is TargetedRelationProp => r.target !== undefined && !r.multiple,\n )\n}\ntype TargetedRelationProp = RelationProp & { target: string }\n\n/** The collection's first composition field — what an embed of it transcludes. */\nfunction blocksFieldOf(collection: CollectionDef) {\n const field = collection.fields.find((f) => f.type === 'blocks')\n return field?.type === 'blocks' ? field : undefined\n}\n\n/**\n * The catalog keys for the reuse config issues (EC-478), in the `builder.reuse.config.*`\n * namespace. Named here so the producer and the catalog-parity test agree on one list.\n */\nconst REUSE_KEYS = {\n unknownComponent: 'builder.reuse.config.unknownComponent',\n noRelationProp: 'builder.reuse.config.noRelationProp',\n unknownRelationProp: 'builder.reuse.config.unknownRelationProp',\n ambiguousRelationProp: 'builder.reuse.config.ambiguousRelationProp',\n unknownCollection: 'builder.reuse.config.unknownCollection',\n noBlocksField: 'builder.reuse.config.noBlocksField',\n ambiguousInference: 'builder.reuse.config.ambiguousInference',\n} as const\n\nfunction reuseIssue(\n code: CustomizationIssue['code'],\n severity: CustomizationIssue['severity'],\n message: string,\n messageRef: CustomizationMessageRef,\n id?: string,\n): CustomizationIssue {\n return {\n code,\n severity,\n message,\n messageRef,\n point: REUSE_POINT,\n ...(id === undefined ? {} : { id }),\n }\n}\n\n/** `\"a\", \"b\"` — a readable list of prop/component ids for an issue message. */\nfunction quotedList(names: readonly string[]): string {\n return names.map((name) => `\"${name}\"`).join(', ')\n}\n\n/**\n * The components whose SHAPE makes them plausible reuse embeds (the zero-config\n * heuristic + EC-462's disqualifiers), in manifest order. Exported for the tests\n * and for issue copy; the resolution rule lives in {@link resolveReuse}.\n */\nexport function inferredEmbedCandidates(\n manifests: readonly ComponentManifest[],\n collections: readonly CollectionDef[],\n): ReuseCapability[] {\n const matches: ReuseCapability[] = []\n for (const manifest of manifests) {\n if (manifest.slots.length > 0) continue\n // EC-462: a `listing` manifest is a query host by its own declaration — its\n // relation prop is the filter source, never an embed pointer.\n if (manifest.listing) continue\n // The heuristic requires the relation to be UNAMBIGUOUS within the manifest:\n // exactly ONE relation prop of any kind, which must then be targeted,\n // cardinality-one and unconditional. (Counting all relation props — not just\n // the embeddable ones — is deliberate: a component with a second, list-valued\n // relation is not the single-purpose shape the heuristic is allowed to claim.)\n const relations = relationPropsOf(manifest)\n const relation = relations.length === 1 ? relations[0]! : undefined\n if (!relation || relation.target === undefined || relation.multiple || relation.conditional)\n continue\n const collection = collections.find((c) => c.id === relation.target)\n if (!collection) continue\n const blocksField = blocksFieldOf(collection)\n if (!blocksField) continue\n matches.push({\n embedComponentId: manifest.id,\n relationProp: relation.name,\n collectionId: collection.id,\n contentField: blocksField.name,\n contentCardinality: blocksField.multiple === false ? 'one' : 'many',\n })\n }\n return matches\n}\n\n/**\n * Resolve the DECLARED embed (EC-467, AD-b). An explicit declaration is the\n * developer's authority, so the inference disqualifiers are NOT applied here: a\n * declared component may have slots, may declare `listing`, and its relation prop\n * may be `form.hidden` — those exist to stop the engine GUESSING, not to overrule\n * a stated intent. Only structural impossibilities are issues (see AD-c row 2):\n * an unknown component id, no usable relation prop (or an ambiguous set with no\n * `relationProp`), an undefined target collection, or a target with no `blocks`\n * field — each of which would leave nothing coherent to transclude.\n */\nfunction resolveDeclaredReuse(\n declaration: StudioReuseConfig,\n manifests: readonly ComponentManifest[],\n collections: readonly CollectionDef[],\n): ReuseResolution {\n const { component, relationProp } = declaration\n const fail = (message: string, messageRef: CustomizationMessageRef): ReuseResolution => ({\n capability: null,\n issues: [reuseIssue('invalid-override', 'error', message, messageRef, component)],\n })\n\n const manifest = manifests.find((m) => m.id === component)\n if (!manifest) {\n return {\n capability: null,\n issues: [\n reuseIssue(\n 'unknown-override-target',\n 'error',\n `Reusable elements: studio config declares reuse.component \"${component}\", which is not a registered component. Fix the id, or remove the reuse block to let the studio infer the embed.`,\n { key: REUSE_KEYS.unknownComponent, vars: { component } },\n component,\n ),\n ],\n }\n }\n\n const relations = embeddableRelationProps(manifest)\n if (relations.length === 0) {\n return fail(\n `Reusable elements: the declared embed \"${component}\" has no single-document relation prop, so it cannot point at a reusable document. An embed needs one relation prop with cardinality one.`,\n { key: REUSE_KEYS.noRelationProp, vars: { component } },\n )\n }\n const names = relations.map((r) => r.name)\n let relation: TargetedRelationProp\n if (relationProp !== undefined) {\n const named = relations.find((r) => r.name === relationProp)\n if (!named) {\n return fail(\n `Reusable elements: the declared embed \"${component}\" has no single-document relation prop named \"${relationProp}\". Its relation props are: ${quotedList(names)}.`,\n {\n key: REUSE_KEYS.unknownRelationProp,\n vars: { component, relationProp, props: quotedList(names) },\n },\n )\n }\n relation = named\n } else if (relations.length === 1) {\n relation = relations[0]!\n } else {\n return fail(\n `Reusable elements: the declared embed \"${component}\" has ${relations.length} single-document relation props (${quotedList(names)}) — add reuse.relationProp to say which one points at the reusable document.`,\n {\n key: REUSE_KEYS.ambiguousRelationProp,\n vars: { component, count: relations.length, props: quotedList(names) },\n },\n )\n }\n\n const collection = collections.find((c) => c.id === relation.target)\n if (!collection) {\n return fail(\n `Reusable elements: \"${component}.${relation.name}\" targets the collection \"${relation.target}\", which this project does not define.`,\n {\n key: REUSE_KEYS.unknownCollection,\n vars: { component, relationProp: relation.name, collection: relation.target },\n },\n )\n }\n const blocksField = blocksFieldOf(collection)\n if (!blocksField) {\n return fail(\n `Reusable elements: the collection \"${collection.id}\" behind \"${component}.${relation.name}\" has no blocks (composition) field, so there is nothing for the embed to transclude.`,\n {\n key: REUSE_KEYS.noBlocksField,\n vars: { component, relationProp: relation.name, collection: collection.id },\n },\n )\n }\n\n return {\n capability: {\n embedComponentId: manifest.id,\n relationProp: relation.name,\n collectionId: collection.id,\n contentField: blocksField.name,\n contentCardinality: blocksField.multiple === false ? 'one' : 'many',\n },\n issues: [],\n }\n}\n\n/**\n * Resolve the project's ONE reuse capability (AD-a) plus any config issues —\n * EC-467's full matrix (AD-c):\n *\n * | Declaration | Candidates | Result |\n * | --- | --- | --- |\n * | present, resolves | any | the declared capability, no issue |\n * | present, does not resolve | any | `null` + an ERROR issue (misconfiguration) |\n * | absent | exactly 1 | that candidate, no issue (the zero-config default) |\n * | absent | 2 or more | `null` + a WARNING issue naming every candidate |\n * | absent | 0 | `null`, **no issue** — a project need not use reusable elements |\n *\n * Severity is the honest split: a declaration that cannot resolve is broken\n * config the developer asked for (error); inference ambiguity is nothing broken —\n * the engine declines to guess and asks for one line (warning). Both disable the\n * gestures, and both render identically in the rail's issue list.\n *\n * The zero-candidate row is deliberately silent: crying wolf at every project\n * that has no reusable elements would train developers to ignore the surface.\n */\nexport function resolveReuse(\n manifests: readonly ComponentManifest[],\n collections: readonly CollectionDef[],\n declaration?: StudioReuseConfig,\n): ReuseResolution {\n if (declaration) return resolveDeclaredReuse(declaration, manifests, collections)\n\n const candidates = inferredEmbedCandidates(manifests, collections)\n if (candidates.length === 1) return { capability: candidates[0]!, issues: [] }\n if (candidates.length === 0) return { capability: null, issues: [] }\n return {\n capability: null,\n issues: [\n reuseIssue(\n 'ambiguous-inference',\n 'warning',\n `Reusable elements: ${candidates.length} components have the reusable-embed shape (${quotedList(\n candidates.map((c) => c.embedComponentId),\n )}). A project has exactly one reusable-element embed, so the studio will not guess — declare it in studio.config as reuse: { component: '<id>' }. Until then the reuse gestures are off.`,\n {\n key: REUSE_KEYS.ambiguousInference,\n vars: {\n count: candidates.length,\n components: quotedList(candidates.map((c) => c.embedComponentId)),\n },\n },\n ),\n ],\n }\n}\n\n/**\n * The project's reuse capability alone, or `null` when it is absent, ambiguous, or\n * misdeclared — for callers that act on the capability and do not surface config\n * issues themselves (the publish-integrity walk). The issues are surfaced ONCE, by\n * the shell rail; see {@link resolveReuse}.\n */\nexport function resolveReuseCapability(\n manifests: readonly ComponentManifest[],\n collections: readonly CollectionDef[],\n declaration?: StudioReuseConfig,\n): ReuseCapability | null {\n return resolveReuse(manifests, collections, declaration).capability\n}\n\n/** A slug for `name`, suffixed `-2`/`-3`/… until it is free among `taken`. */\nexport function uniqueSlug(name: string, taken: ReadonlySet<string>): string {\n const base = slugify(name) || 'element'\n let id = base\n for (let n = 2; taken.has(id); n += 1) id = `${base}-${n}`\n return id\n}\n\n/**\n * The values for a new reusable document promoted from `node` (EC-282): seeded\n * defaults, the given `name` on the title field, the node on the content field,\n * and a collision-safe slug on any UNIQUE text field (e.g. Konditorei's\n * `internalId`) so the created document is publish-valid without hardcoding that\n * field's name. Other required fields, if any, are completed by the editor when\n * they open the new reusable.\n */\nexport function buildReusableValues(\n collection: CollectionDef,\n capability: ReuseCapability,\n name: string,\n node: ComponentNode,\n existing: readonly { values?: Record<string, unknown> }[],\n): Record<string, unknown> {\n const values: Record<string, unknown> = { ...seedDefaults(collection) }\n const titleField = titleFieldOf(collection)?.name\n if (titleField) values[titleField] = name\n for (const field of collection.fields) {\n if (\n field.type === 'text' &&\n field.validation?.unique === true &&\n field.name !== titleField &&\n field.name !== capability.contentField\n ) {\n const taken = new Set(\n existing\n .map((d) => d.values?.[field.name])\n .filter((v): v is string => typeof v === 'string'),\n )\n values[field.name] = uniqueSlug(name, taken)\n }\n }\n // EC-576: the converged v2 sequence holding the tagged node, through the core\n // write path — never the pre-0.0.84 bare node/array. Cardinality is a\n // constraint over that one shape, not a second shape (EC-500 D2).\n values[capability.contentField] = editBlockRoots(undefined, () => [node])\n return values\n}\n\n/**\n * The document a reuse-embed `node` targets (EC-300), or null when `node` is not the\n * embed component or has no static pick yet. Lets the canvas detect a HARD reference\n * whose target doesn't resolve (→ placeholder) without reaching into prop internals.\n */\nexport function reuseEmbedRef(node: ComponentNode, capability: ReuseCapability): DocumentRef | null {\n if (node.componentId !== capability.embedComponentId) return null\n const prop = node.props?.[capability.relationProp]\n const ref = isStaticProp(prop) ? readDocumentRef(prop.value) : undefined\n return ref ?? null\n}\n\n/**\n * Every reuse-embed target ref in a composition `blocks` value, at any depth\n * (EC-300) — the hard references a publish-integrity check walks to find\n * unpublished targets. Recurses slots; order is document order. Duplicates are\n * kept (the caller dedupes by key if needed).\n */\nexport function collectEmbedRefs(\n blocks: readonly ComponentNode[],\n capability: ReuseCapability,\n): DocumentRef[] {\n const refs: DocumentRef[] = []\n const visit = (node: ComponentNode): void => {\n const ref = reuseEmbedRef(node, capability)\n if (ref) refs.push(ref)\n for (const children of Object.values(node.slots ?? {})) for (const child of children) visit(child)\n }\n for (const node of blocks) visit(node)\n return refs\n}\n\n/** The `Reusable` embed node referencing the new document (id minted by the action). */\nexport function makeEmbedNode(capability: ReuseCapability, documentId: string): ComponentNode {\n return {\n id: 'reuse-embed',\n componentId: capability.embedComponentId,\n props: {\n [capability.relationProp]: {\n kind: 'static',\n value: { collection: capability.collectionId, id: documentId },\n },\n },\n }\n}\n"]}
|
|
@@ -5,7 +5,7 @@ import { assetAcceptAttr, assetMatchesAccept, assetNode, assetSubfieldsOf, descr
|
|
|
5
5
|
import { Button, Card, Code, Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, Field, Row, Spacer, Stack, Text, } from '@elytracms/studio/ui';
|
|
6
6
|
import { cn } from '@elytracms/studio/lib/utils';
|
|
7
7
|
import { localizedLabel, translate } from '@elytracms/studio/lib/i18n';
|
|
8
|
-
import { applyAssetSelection, assetIdsOf, singleAssetIdOf } from '../assets/asset-selection.js';
|
|
8
|
+
import { appendAssetSelections, applyAssetSelection, assetIdsOf, assetPickForm, singleAssetIdOf, } from '../assets/asset-selection.js';
|
|
9
9
|
import { useOptionalAssets } from '../assets/assets-store.js';
|
|
10
10
|
import { AssetThumb, FocalCropPreviews, FocalPointPicker, useAssetRecord } from '../assets/asset-thumb.js';
|
|
11
11
|
import { uploadBatchProgress } from './asset-picker-core.js';
|
|
@@ -55,6 +55,9 @@ export function AssetField({ field, value, onChange, readOnly, }) {
|
|
|
55
55
|
// stays bare ids (self-typed on save). Picks/edits preserve a placement's sub-fields.
|
|
56
56
|
const subFields = field.fields ?? [];
|
|
57
57
|
const hasSub = subFields.length > 0;
|
|
58
|
+
// How a pick writes a NEW placement: an envelope when the field has sub-fields,
|
|
59
|
+
// else a bare id. The table cell reads the same decision (EC-558).
|
|
60
|
+
const form = assetPickForm(field);
|
|
58
61
|
// The raw `many` entries (envelopes or bare ids), index-aligned with `ids`, so each
|
|
59
62
|
// gallery item can read/write its own sub-fields.
|
|
60
63
|
const entries = many && Array.isArray(value) ? value : [];
|
|
@@ -80,24 +83,21 @@ export function AssetField({ field, value, onChange, readOnly, }) {
|
|
|
80
83
|
return;
|
|
81
84
|
}
|
|
82
85
|
if (many) {
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
if (fresh.length > 0) {
|
|
87
|
-
onChange([...current, ...fresh.map((id) => (hasSub ? assetNode(id) : id))]);
|
|
88
|
-
}
|
|
86
|
+
const next = appendAssetSelections(value, progress.added, form);
|
|
87
|
+
if (next !== value)
|
|
88
|
+
onChange(next);
|
|
89
89
|
if (progress.settled)
|
|
90
90
|
pendingRef.current = null;
|
|
91
91
|
return;
|
|
92
92
|
}
|
|
93
93
|
pendingRef.current = null;
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
94
|
+
const next = applyAssetSelection(value, progress.added[0], false, form);
|
|
95
|
+
if (next !== value)
|
|
96
|
+
onChange(next);
|
|
97
97
|
// Reacts to the library set growing (an upload landing) and the upload
|
|
98
98
|
// queue draining (a batch settling) — `value`/`onChange` are read from the
|
|
99
99
|
// closure on purpose.
|
|
100
|
-
}, [libraryIds, uploadsInFlight, many,
|
|
100
|
+
}, [libraryIds, uploadsInFlight, many, form, onChange, value]);
|
|
101
101
|
function uploadFiles(files) {
|
|
102
102
|
if (!assets || files.length === 0)
|
|
103
103
|
return;
|
|
@@ -120,32 +120,20 @@ export function AssetField({ field, value, onChange, readOnly, }) {
|
|
|
120
120
|
pendingRef.current = { before: new Set(libraryIds), expected: ok.length };
|
|
121
121
|
assets.upload(ok);
|
|
122
122
|
}
|
|
123
|
-
// Apply a picked asset id
|
|
124
|
-
//
|
|
123
|
+
// Apply a picked asset id through the pick operation the table cell also commits
|
|
124
|
+
// (EC-558), so a placement's per-use sub-fields (EC-301) survive a swap or an append.
|
|
125
125
|
function applyPicked(picked) {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
}
|
|
130
|
-
if (many) {
|
|
131
|
-
if (picked === null || entries.some((e) => readAssetId(e) === picked))
|
|
132
|
-
return;
|
|
133
|
-
onChange([...entries, assetNode(picked)]);
|
|
134
|
-
}
|
|
135
|
-
else {
|
|
136
|
-
onChange(picked ? assetNode(picked, assetSubfieldsOf(value)) : undefined);
|
|
137
|
-
}
|
|
126
|
+
const next = applyAssetSelection(value, picked, many, form);
|
|
127
|
+
if (next !== value)
|
|
128
|
+
onChange(next);
|
|
138
129
|
}
|
|
139
130
|
// EC-460: attach a whole settled in-picker upload batch to a `many` field —
|
|
140
131
|
// every id not already attached, in arrival order (envelopes when the field
|
|
141
132
|
// declares per-use sub-fields). Only wired for `many` fields (see the picker).
|
|
142
133
|
function applyPickedMany(picked) {
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
if (fresh.length === 0)
|
|
147
|
-
return;
|
|
148
|
-
onChange([...current, ...fresh.map((id) => (hasSub ? assetNode(id) : id))]);
|
|
134
|
+
const next = appendAssetSelections(value, picked, form);
|
|
135
|
+
if (next !== value)
|
|
136
|
+
onChange(next);
|
|
149
137
|
}
|
|
150
138
|
const canUpload = !readOnly && assets !== null;
|
|
151
139
|
// Drag-to-upload onto the field (EC-301): a depth counter keeps the overlay
|
|
@@ -200,7 +188,7 @@ export function AssetField({ field, value, onChange, readOnly, }) {
|
|
|
200
188
|
nextEntries[i] = assetNode(aid, next);
|
|
201
189
|
onChange(nextEntries);
|
|
202
190
|
}, onRemove: readOnly ? undefined : () => onChange(entries.filter((_, j) => j !== i)) }, `${aid}-${i}`));
|
|
203
|
-
}) })) : (_jsx(EmptyAssetState, { compact: compact, canUpload: canUpload, readOnly: readOnly, onSelect: () => setOpen(true) })), _jsx(Row, { gap: "2", wrap: true, align: "center", children: selectButton })] })) : many ? (_jsxs(Stack, { gap: "2", children: [ids.length > 0 ? (_jsx("div", { className: cn('grid gap-2', compact ? 'grid-cols-2' : 'grid-cols-3 sm:grid-cols-4'), children: ids.map((aid, i) => (_jsx(AssetChip, { assetId: aid, collectionId: assetCollectionId, onRemove: readOnly ? undefined : () => onChange(ids.filter((x) => x !== aid)) }, `${aid}-${i}`))) })) : (_jsx(EmptyAssetState, { compact: compact, canUpload: canUpload, readOnly: readOnly, onSelect: () => setOpen(true) })), _jsx(Row, { gap: "2", wrap: true, align: "center", children: selectButton })] })) : single && hasSub ? (_jsx(Card, { className: "border-l-2 border-l-border p-3", children: _jsxs(Stack, { gap: "3", children: [_jsx(AssetSinglePreview, { assetId: single, compact: compact, readOnly: readOnly, hasLibrary: assets !== null, fallbackTitle: source.titleOf({ collection: assetCollectionId, id: single }), selectButton: selectButton, onEdit: () => setEditing(true), onRemove: () => onChange(applyAssetSelection(value, null,
|
|
191
|
+
}) })) : (_jsx(EmptyAssetState, { compact: compact, canUpload: canUpload, readOnly: readOnly, onSelect: () => setOpen(true) })), _jsx(Row, { gap: "2", wrap: true, align: "center", children: selectButton })] })) : many ? (_jsxs(Stack, { gap: "2", children: [ids.length > 0 ? (_jsx("div", { className: cn('grid gap-2', compact ? 'grid-cols-2' : 'grid-cols-3 sm:grid-cols-4'), children: ids.map((aid, i) => (_jsx(AssetChip, { assetId: aid, collectionId: assetCollectionId, onRemove: readOnly ? undefined : () => onChange(ids.filter((x) => x !== aid)) }, `${aid}-${i}`))) })) : (_jsx(EmptyAssetState, { compact: compact, canUpload: canUpload, readOnly: readOnly, onSelect: () => setOpen(true) })), _jsx(Row, { gap: "2", wrap: true, align: "center", children: selectButton })] })) : single && hasSub ? (_jsx(Card, { className: "border-l-2 border-l-border p-3", children: _jsxs(Stack, { gap: "3", children: [_jsx(AssetSinglePreview, { assetId: single, compact: compact, readOnly: readOnly, hasLibrary: assets !== null, fallbackTitle: source.titleOf({ collection: assetCollectionId, id: single }), selectButton: selectButton, onEdit: () => setEditing(true), onRemove: () => onChange(applyAssetSelection(value, null, many, form)) }), _jsx("div", { className: "border-t border-border/60 pt-3", children: _jsx(ObjectItemForm, { fields: subFields, value: assetSubfieldsOf(value), readOnly: readOnly, idPrefix: "asset-sub", onChange: (next) => onChange(assetNode(single, next)) }) })] }) })) : single ? (_jsx(AssetSinglePreview, { assetId: single, compact: compact, readOnly: readOnly, hasLibrary: assets !== null, fallbackTitle: source.titleOf({ collection: assetCollectionId, id: single }), selectButton: selectButton, onEdit: () => setEditing(true), onRemove: () => onChange(applyAssetSelection(value, null, many, form)) })) : (_jsxs(Stack, { gap: "2", children: [_jsx(EmptyAssetState, { compact: compact, canUpload: canUpload, readOnly: readOnly, onSelect: () => setOpen(true) }), _jsx(Row, { gap: "2", wrap: true, align: "center", children: selectButton })] })), !many && single && assets ? (_jsx(AssetOptionsDialog, { assetId: single, readOnly: readOnly, open: editing, onOpenChange: setEditing })) : null, _jsx(AssetPicker, { value: single, open: open, onOpenChange: setOpen, accept: assetAcceptAttr(field.accept), onChange: applyPicked, multiple: many, ...(many ? { onChangeMany: applyPickedMany } : {}) }), rejected.length > 0 ? (_jsxs(Row, { gap: "2", align: "center", children: [_jsx(AlertTriangle, { className: "size-4 shrink-0 text-warning" }), _jsx(Text, { size: "xs", tone: "muted", children: rejected.length === 1
|
|
204
192
|
? translate('cms.asset.rejected.one', {
|
|
205
193
|
name: rejected[0],
|
|
206
194
|
types: describeAccept(field.accept),
|