@elytracms/studio 0.0.94 → 0.0.95
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/convex/asset-alt-migration.test.ts +2 -2
- package/convex/asset-meta-patch.test.ts +218 -0
- package/convex/asset-replace-delete-race.test.ts +127 -0
- package/convex/assets.ts +27 -2
- package/convex/delivery-redirects.test.ts +167 -0
- package/convex/delivery.ts +15 -5
- package/convex/rail-write-parity.test.ts +6 -6
- package/convex/redirects.ts +1 -1
- package/convex/schema.ts +6 -0
- package/dist/addon.d.ts +1 -1
- package/dist/addon.js.map +1 -1
- package/dist/components/assets/asset-detail.js +26 -11
- package/dist/components/assets/asset-detail.js.map +1 -1
- package/dist/components/assets/asset-locale.d.ts +9 -0
- package/dist/components/assets/asset-locale.js +16 -0
- package/dist/components/assets/asset-locale.js.map +1 -0
- package/dist/components/assets/asset-meta-label.d.ts +12 -0
- package/dist/components/assets/asset-meta-label.js +15 -0
- package/dist/components/assets/asset-meta-label.js.map +1 -0
- package/dist/components/assets/asset-metadata.d.ts +69 -15
- package/dist/components/assets/asset-metadata.js +99 -11
- package/dist/components/assets/asset-metadata.js.map +1 -1
- package/dist/components/assets/asset-search.js +8 -2
- package/dist/components/assets/asset-search.js.map +1 -1
- package/dist/components/assets/asset-thumb.js +6 -2
- package/dist/components/assets/asset-thumb.js.map +1 -1
- package/dist/components/assets/assets-collection-view-source.d.ts +23 -9
- package/dist/components/assets/assets-collection-view-source.js +78 -26
- package/dist/components/assets/assets-collection-view-source.js.map +1 -1
- package/dist/components/assets/assets-store.js +17 -4
- package/dist/components/assets/assets-store.js.map +1 -1
- package/dist/components/builder/builder-store.d.ts +21 -0
- package/dist/components/builder/builder-store.js +22 -0
- package/dist/components/builder/builder-store.js.map +1 -1
- package/dist/components/builder/canvas-commit-boundary.d.ts +22 -0
- package/dist/components/builder/canvas-commit-boundary.js +27 -0
- package/dist/components/builder/canvas-commit-boundary.js.map +1 -0
- package/dist/components/builder/canvas-rebase.d.ts +77 -0
- package/dist/components/builder/canvas-rebase.js +169 -0
- package/dist/components/builder/canvas-rebase.js.map +1 -0
- package/dist/components/builder/canvas-render-context.d.ts +6 -3
- package/dist/components/builder/canvas-render-context.js +17 -9
- package/dist/components/builder/canvas-render-context.js.map +1 -1
- package/dist/components/builder/composition-field-editor.d.ts +20 -2
- package/dist/components/builder/composition-field-editor.js +45 -27
- package/dist/components/builder/composition-field-editor.js.map +1 -1
- package/dist/components/builder/node-factory.js +15 -3
- package/dist/components/builder/node-factory.js.map +1 -1
- package/dist/components/builder/property-inspector.js +3 -1
- package/dist/components/builder/property-inspector.js.map +1 -1
- package/dist/components/builder/run-inspector.js +10 -6
- package/dist/components/builder/run-inspector.js.map +1 -1
- package/dist/components/cms/asset-field.d.ts +5 -2
- package/dist/components/cms/asset-field.js +34 -17
- package/dist/components/cms/asset-field.js.map +1 -1
- package/dist/components/cms/asset-picker.js +3 -1
- package/dist/components/cms/asset-picker.js.map +1 -1
- package/dist/components/cms/canvas-commit.d.ts +56 -0
- package/dist/components/cms/canvas-commit.js +35 -0
- package/dist/components/cms/canvas-commit.js.map +1 -0
- package/dist/components/cms/collection-table.js +23 -7
- package/dist/components/cms/collection-table.js.map +1 -1
- package/dist/components/cms/commit-policy.d.ts +6 -2
- package/dist/components/cms/commit-policy.js +1 -1
- package/dist/components/cms/commit-policy.js.map +1 -1
- package/dist/components/cms/detail-editor.js +11 -1
- package/dist/components/cms/detail-editor.js.map +1 -1
- package/dist/components/cms/use-document-draft.d.ts +62 -4
- package/dist/components/cms/use-document-draft.js +102 -32
- package/dist/components/cms/use-document-draft.js.map +1 -1
- package/dist/components/cms/use-live-url.d.ts +6 -1
- package/dist/components/cms/use-live-url.js +12 -9
- package/dist/components/cms/use-live-url.js.map +1 -1
- package/dist/components/collection-view/cms-collection-view-source.js +9 -5
- package/dist/components/collection-view/cms-collection-view-source.js.map +1 -1
- package/dist/components/deployment-setup-gate.js +10 -1
- package/dist/components/deployment-setup-gate.js.map +1 -1
- package/dist/components/records/record-lens.js +3 -8
- package/dist/components/records/record-lens.js.map +1 -1
- package/dist/lib/assets/upload.d.ts +25 -7
- package/dist/lib/assets/upload.js +30 -21
- package/dist/lib/assets/upload.js.map +1 -1
- package/dist/lib/cms/published-documents.d.ts +24 -6
- package/dist/lib/cms/published-documents.js +39 -6
- package/dist/lib/cms/published-documents.js.map +1 -1
- package/dist/lib/cms-views/view-model.d.ts +14 -101
- package/dist/lib/cms-views/view-model.js +20 -101
- package/dist/lib/cms-views/view-model.js.map +1 -1
- package/dist/lib/collection-view/collection-view-source.d.ts +6 -1
- package/dist/lib/collection-view/collection-view-source.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 +7 -3
- package/dist/lib/i18n/messages.de.js.map +1 -1
- package/dist/lib/i18n/messages.en.d.ts +5 -1
- package/dist/lib/i18n/messages.en.js +8 -4
- package/dist/lib/i18n/messages.en.js.map +1 -1
- package/dist/lib/operations/config-manifest-lookup.d.ts +1 -1
- package/dist/lib/operations/config-manifest-lookup.js +3 -3
- package/dist/lib/operations/config-manifest-lookup.js.map +1 -1
- package/dist/lib/persistence/deployment-readiness.js +14 -11
- package/dist/lib/persistence/deployment-readiness.js.map +1 -1
- package/dist/lib/persona/labels.js +1 -0
- package/dist/lib/persona/labels.js.map +1 -1
- package/dist/lib/query/index.d.ts +2 -2
- package/dist/lib/query/index.js +1 -1
- package/dist/lib/query/index.js.map +1 -1
- package/dist/lib/query/operations-query.d.ts +26 -0
- package/dist/lib/query/operations-query.js +12 -0
- package/dist/lib/query/operations-query.js.map +1 -1
- package/dist/lib/registry/index.js +2 -2
- package/dist/lib/registry/index.js.map +1 -1
- package/dist/lib/registry/preview.d.ts +10 -0
- package/dist/lib/registry/preview.js +13 -4
- package/dist/lib/registry/preview.js.map +1 -1
- package/dist/lib/routes/route-issues.js +6 -3
- package/dist/lib/routes/route-issues.js.map +1 -1
- package/dist/lib/workspace/workspace-source.js +6 -7
- package/dist/lib/workspace/workspace-source.js.map +1 -1
- package/dist/previews/entry-node.d.ts +14 -0
- package/dist/previews/entry-node.js +22 -0
- package/dist/previews/entry-node.js.map +1 -0
- package/dist/previews/harness.js +2 -16
- package/dist/previews/harness.js.map +1 -1
- package/package.json +3 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"asset-picker.js","sourceRoot":"","sources":["../../../src/components/cms/asset-picker.tsx"],"names":[],"mappings":"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","sourcesContent":["import { AlertTriangle, Check, ImageIcon, Search, Upload } from 'lucide-react'\nimport { useEffect, useRef, useState } from 'react'\nimport { assetAcceptAttr, describeAccept } from '@elytracms/core/cms-core'\nimport { assetAlt } from '@elytracms/core/persistence'\nimport type { AssetRecord } from '@elytracms/core/persistence'\nimport {\n Button,\n Dialog,\n DialogContent,\n DialogDescription,\n DialogHeader,\n DialogTitle,\n ExpandableSurface,\n Input,\n Row,\n Spacer,\n Text,\n} from '@elytracms/studio/ui'\nimport { cn } from '../../lib/utils'\nimport { translate } from '../../lib/i18n'\nimport { AssetThumb } from '../assets/asset-thumb'\nimport { AssetsProvider, useAssets, useOptionalAssets } from '../assets/assets-store'\nimport type { AssetsStore } from '../assets/assets-store'\nimport { useCollectionView } from '../collection-view/collection-view-context'\nimport {\n ASSET_PICKER_STAGE_PRESET,\n commitPickerSelection,\n batchAttachment,\n commitPickerSelectionMany,\n splitByAccept,\n uploadBatchProgress,\n visiblePickerAssets,\n} from './asset-picker-core'\nimport type { UploadBatch } from './asset-picker-core'\n\n/** The picker's props — see {@link AssetPicker}. */\nexport interface AssetPickerProps {\n value: string | null\n open: boolean\n onOpenChange: (open: boolean) => void\n onChange: (assetId: string | null) => void\n /** EC-460: commit a whole settled upload batch (a `many` field passes this). */\n onChangeMany?: (assetIds: string[]) => void\n /** Whether the consuming field holds many assets (enables batch commit). */\n multiple?: boolean\n /** The field's `accept` list (EC-588) — absent means any file. */\n accept?: readonly string[]\n}\n\n/**\n * Pick an asset for a CMS `asset` field (brief §8.4, §8.6, EC-075 → EC-152).\n * Backed by the *real* asset library (the assets store over the operations\n * client) — the same records the Assets section lists — not by fixture\n * documents. Selecting stores the asset id on the document field.\n *\n * EC-432 (AD-C): the picker is a real MEDIA BROWSER on the {@link ExpandableSurface}\n * `stage` (`grid` preset — `min(1240px, 94vw)`, near-full height), replacing the\n * old `max-w-md` (448px) Dialog. It reuses the shipped assets-rail machinery: the\n * assets store, the EC-152 search, the EC-400 thumb tile ({@link AssetThumb}), and\n * the upload-then-select path. It is a SELECTION browser, not a form — click = pick\n * (fires `onChange`, closes) plus the shipped clear/cancel affordances; the\n * forms-never-modal rule governs document forms and is untouched. Opened from the\n * expanded rich-text stage toolbar it rides ABOVE that stage on the EC-392 layer\n * stack — Escape closes the browser first, the rich-text stage stays.\n *\n * The component CONTRACT (`{ open, onOpenChange, value, accept, onChange }`) serves\n * every call site — asset fields, rich-text image insertion, editable cells. EC-460\n * adds an OPTIONAL multi-commit pair (`multiple` + `onChangeMany`): a `many` asset\n * field passes both so uploading N files in ONE picker interaction attaches ALL N\n * (the old flow committed the first upload to land and closed — the rest reached the\n * library but never the field, silent partial data loss). Grid clicks stay\n * single-pick-and-close in every mode.\n *\n * EC-588: `accept` is the field's STRUCTURED list (it was a file-input string that the\n * core re-split — two shapes for one fact). The grid filter, the file-input attribute\n * and the drop/upload split all derive from it, and an absent `accept` is unrestricted\n * everywhere (the Upload dialog used to fall back to `image/*`). A field-backed caller\n * passes `assetPickerBinding`'s output (`asset-picker-core`) rather than wiring props\n * by hand.\n */\nexport function AssetPicker({\n value,\n open,\n onOpenChange,\n onChange,\n onChangeMany,\n multiple = false,\n accept,\n}: AssetPickerProps) {\n const source = useCollectionView()\n const ambient = useOptionalAssets()\n // Mount nothing while closed: the self-provided assets store would\n // otherwise load the library eagerly for every asset field on the form.\n if (!open) return null\n if (ambient) {\n return (\n <AssetPickerStage\n value={value}\n onOpenChange={onOpenChange}\n onChange={onChange}\n {...(onChangeMany ? { onChangeMany } : {})}\n multiple={multiple}\n accept={accept}\n />\n )\n }\n if (source.projectId === undefined) {\n // No project scope (fixture-only CMS session): the library is\n // unreachable — say so instead of silently showing nothing.\n return (\n <Dialog open onOpenChange={onOpenChange}>\n <DialogContent className=\"max-w-md\">\n <DialogHeader>\n <DialogTitle>{translate('cms.asset.pickTitle')}</DialogTitle>\n <DialogDescription>{translate('cms.asset.pickDescription')}</DialogDescription>\n </DialogHeader>\n <Row gap=\"2\" data-testid=\"asset-picker-no-project\">\n <AlertTriangle className=\"size-4 text-warning\" />\n <Text size=\"sm\" tone=\"muted\">\n {translate('cms.asset.noProject')}\n </Text>\n </Row>\n </DialogContent>\n </Dialog>\n )\n }\n return (\n <AssetsProvider projectId={source.projectId}>\n <AssetPickerStage\n value={value}\n onOpenChange={onOpenChange}\n onChange={onChange}\n {...(onChangeMany ? { onChangeMany } : {})}\n multiple={multiple}\n accept={accept}\n />\n </AssetsProvider>\n )\n}\n\n/** The open media-browser stage over a mounted assets store. */\nfunction AssetPickerStage(props: Omit<AssetPickerProps, 'open'>) {\n const store = useAssets()\n return (\n <ExpandableSurface\n title={translate('cms.asset.pickTitle')}\n closeLabel={translate('common.close')}\n variant=\"stage\"\n preset={ASSET_PICKER_STAGE_PRESET}\n onDismiss={() => props.onOpenChange(false)}\n headerLeading={<ImageIcon className=\"size-4 text-muted-foreground\" aria-hidden />}\n >\n <AssetPickerBrowser store={store} {...props} />\n </ExpandableSurface>\n )\n}\n\n/** The slice of the assets store the browser reads and drives. */\nexport type AssetPickerStore = Pick<\n AssetsStore,\n 'status' | 'errorMessage' | 'assets' | 'uploads' | 'upload' | 'landedAssetId'\n>\n\n/**\n * The media browser itself — search, Upload, the grid, the drop zone — rendered inside\n * the stage. A named component of its own (EC-588) so its markup is reachable by a\n * node-env smoke render (the stage portal is `null` there): the file input's `accept`\n * attribute and the grid's filter are pinned against the field's `accept`.\n */\nexport function AssetPickerBrowser({\n store,\n value,\n onOpenChange,\n onChange,\n onChangeMany,\n multiple = false,\n accept,\n}: Omit<AssetPickerProps, 'open'> & { store: AssetPickerStore }) {\n // Fresh per open: the picker unmounts entirely while closed.\n const [query, setQuery] = useState('')\n const [dragging, setDragging] = useState(false)\n // Names of files a drop/upload refused against the field's `accept` (EC-588) — shown\n // as a visible notice and never uploaded. Cleared by the next upload attempt.\n const [rejected, setRejected] = useState<string[]>([])\n const fileInputRef = useRef<HTMLInputElement>(null)\n // The active upload BATCH (EC-460): exactly the upload ids this interaction started\n // (EC-588 A7), so the effect below attaches this batch's uploads and nothing else.\n const pendingRef = useRef<UploadBatch | null>(null)\n // Drag depth (EC-301): keeps the drop overlay steady as the pointer crosses\n // child boundaries — the same per-element dragenter/dragleave flicker the field\n // fixes. Only hide when the count returns to zero.\n const dragDepth = useRef(0)\n const assets = visiblePickerAssets(store.assets, query, accept)\n\n // Auto-select what an in-picker upload added, then close. A `many` consumer\n // (multiple + onChangeMany) waits for the batch to SETTLE and commits every\n // added id — uploading 2 files attaches 2 (EC-460); a single consumer keeps\n // the first-to-land commit. Only uploads whose record satisfies `accept` commit.\n useEffect(() => {\n const batch = pendingRef.current\n if (!batch) return\n // EC-588 2i: the one batch-attach rule the field's own drop zone runs too.\n const many = multiple === true && onChangeMany !== undefined\n const { attach, disarm } = batchAttachment(uploadBatchProgress(batch, store, accept), many)\n if (disarm) pendingRef.current = null\n if (attach.length === 0) return\n if (many) commitPickerSelectionMany(attach, { onChangeMany, onOpenChange })\n else commitPickerSelection(attach[0]!, { onChange, onOpenChange })\n }, [store, accept, multiple, onChange, onChangeMany, onOpenChange])\n\n function handleUpload(files: File[]) {\n if (files.length === 0) return\n // EC-588: the drop rule — the same split the field's drop zone uses. A drop\n // bypasses the dialog's soft filter; refused files are named, never uploaded.\n const { accepted, rejected: refused } = splitByAccept(files, accept)\n setRejected(refused)\n if (accepted.length === 0) return\n pendingRef.current = { uploadIds: store.upload(accepted) }\n }\n // The stage portals to document.body, but React synthetic events still bubble up\n // the COMPONENT tree — and this picker is rendered as a child of the asset field,\n // which has its own drag-to-upload handlers. Without stopPropagation a drop inside\n // the stage would fire BOTH the picker's and the field's `onDrop`, uploading the\n // file twice. Stop every DnD event here so the picker owns its drop entirely.\n function onDragEnter(e: React.DragEvent) {\n e.preventDefault()\n e.stopPropagation()\n dragDepth.current += 1\n if (!dragging) setDragging(true)\n }\n function onDragOver(e: React.DragEvent) {\n e.preventDefault()\n e.stopPropagation()\n }\n function onDragLeave(e: React.DragEvent) {\n e.stopPropagation()\n dragDepth.current -= 1\n if (dragDepth.current <= 0) {\n dragDepth.current = 0\n setDragging(false)\n }\n }\n function onDrop(e: React.DragEvent) {\n e.preventDefault()\n e.stopPropagation()\n dragDepth.current = 0\n setDragging(false)\n handleUpload(Array.from(e.dataTransfer.files))\n }\n\n return (\n <div\n className=\"relative flex min-h-0 flex-1 flex-col gap-3 p-4\"\n onDragEnter={onDragEnter}\n onDragOver={onDragOver}\n onDragLeave={onDragLeave}\n onDrop={onDrop}\n >\n <Row gap=\"2\" align=\"center\">\n <span className=\"relative flex-1\">\n <Search className=\"pointer-events-none absolute left-2.5 top-1/2 size-3.5 -translate-y-1/2 text-muted-foreground\" />\n <Input\n autoFocus\n value={query}\n onChange={(e) => setQuery(e.target.value)}\n placeholder={translate('cms.asset.searchPlaceholder')}\n className=\"pl-8\"\n data-testid=\"asset-picker-search\"\n />\n </span>\n <Button variant=\"outline\" size=\"sm\" onClick={() => fileInputRef.current?.click()}>\n <Upload /> {translate('common.upload')}\n </Button>\n <input\n ref={fileInputRef}\n type=\"file\"\n multiple\n accept={assetAcceptAttr(accept)}\n className=\"hidden\"\n data-testid=\"asset-picker-file-input\"\n onChange={(e) => {\n handleUpload(Array.from(e.target.files ?? []))\n e.target.value = ''\n }}\n />\n </Row>\n\n <AssetRejectedNotice rejected={rejected} accept={accept} />\n\n <div\n className=\"grid min-h-0 flex-1 auto-rows-min grid-cols-2 gap-2 overflow-y-auto sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5\"\n data-testid=\"asset-picker-grid\"\n >\n {store.status === 'loading' ? (\n <Text size=\"sm\" tone=\"muted\" className=\"col-span-full p-3\">\n {translate('cms.asset.loading')}\n </Text>\n ) : store.status === 'error' ? (\n <Row gap=\"2\" className=\"col-span-full p-3\" data-testid=\"asset-picker-error\">\n <AlertTriangle className=\"size-4 text-destructive\" />\n <Text size=\"sm\" tone=\"muted\">\n {store.errorMessage ?? translate('cms.asset.loadError')}\n </Text>\n </Row>\n ) : assets.length === 0 ? (\n <Text size=\"sm\" tone=\"muted\" className=\"col-span-full p-3\">\n {query.trim() ? translate('cms.asset.noMatches') : translate('cms.asset.empty')}\n </Text>\n ) : (\n assets.map((asset) => (\n <AssetPickerCard\n key={asset.id}\n asset={asset}\n selected={asset.id === value}\n onPick={() => commitPickerSelection(asset.id, { onChange, onOpenChange })}\n />\n ))\n )}\n </div>\n\n <Row gap=\"2\">\n <Spacer />\n {value ? (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => commitPickerSelection(null, { onChange, onOpenChange })}\n >\n {translate('common.clear')}\n </Button>\n ) : null}\n </Row>\n\n {dragging ? (\n <div className=\"pointer-events-none absolute inset-0 z-10 grid place-items-center rounded-lg border-2 border-dashed border-primary bg-background/85\">\n <Row gap=\"2\" align=\"center\">\n <Upload className=\"size-5 text-primary\" />\n <Text className=\"font-medium\">{translate('cms.asset.dropToUpload')}</Text>\n </Row>\n </div>\n ) : null}\n </div>\n )\n}\n\n/**\n * The visible notice for files an upload path refused against the field's `accept`\n * (EC-301 → EC-588): named, never silently dropped. Shared by the field's drop zone and\n * the picker, which apply the same rule. Renders nothing when nothing was refused.\n */\nexport function AssetRejectedNotice({\n rejected,\n accept,\n}: {\n rejected: readonly string[]\n accept: readonly string[] | undefined\n}) {\n if (rejected.length === 0) return null\n return (\n <Row gap=\"2\" align=\"center\" data-testid=\"asset-rejected-notice\">\n <AlertTriangle className=\"size-4 shrink-0 text-warning\" />\n <Text size=\"xs\" tone=\"muted\">\n {rejected.length === 1\n ? translate('cms.asset.rejected.one', { name: rejected[0]!, types: describeAccept(accept) })\n : translate('cms.asset.rejected.other', {\n count: rejected.length,\n types: describeAccept(accept),\n })}\n </Text>\n </Row>\n )\n}\n\n/** One selectable asset: EC-400 thumb tile + title/filename + alt text. The\n * internal asset id is intentionally not shown — it's noise to editors (EC-301). */\nfunction AssetPickerCard({\n asset,\n selected,\n onPick,\n}: {\n asset: AssetRecord\n selected: boolean\n onPick: () => void\n}) {\n return (\n <button\n type=\"button\"\n onClick={onPick}\n className={cn(\n 'flex flex-col gap-1.5 rounded-lg border p-2 text-left transition-colors',\n selected ? 'border-primary bg-primary/5' : 'border-border hover:bg-muted',\n )}\n data-testid=\"asset-picker-card\"\n aria-pressed={selected}\n >\n <span className=\"relative block\">\n <AssetThumb record={asset} className=\"aspect-video w-full\" />\n {selected ? (\n <span className=\"absolute right-1 top-1 grid size-5 place-items-center rounded-full bg-primary text-primary-foreground shadow\">\n <Check className=\"size-3\" />\n </span>\n ) : null}\n </span>\n <Text size=\"xs\" className=\"min-w-0 truncate font-medium\">\n {asset.title ?? asset.filename}\n </Text>\n {/* EC-587 (ASSETS D20): the asset's alt through THE reader, as delivery serves it. */}\n <Text size=\"xs\" tone={assetAlt(asset) ? 'muted' : 'subtle'} className=\"truncate\">\n {assetAlt(asset) || translate('cms.asset.noAltText')}\n </Text>\n </button>\n )\n}\n"]}
|
|
1
|
+
{"version":3,"file":"asset-picker.js","sourceRoot":"","sources":["../../../src/components/cms/asset-picker.tsx"],"names":[],"mappings":"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","sourcesContent":["import { AlertTriangle, Check, ImageIcon, Search, Upload } from 'lucide-react'\nimport { useEffect, useRef, useState } from 'react'\nimport { assetAcceptAttr, describeAccept } from '@elytracms/core/cms-core'\nimport { assetAlt } from '@elytracms/core/persistence'\nimport type { AssetRecord } from '@elytracms/core/persistence'\nimport {\n Button,\n Dialog,\n DialogContent,\n DialogDescription,\n DialogHeader,\n DialogTitle,\n ExpandableSurface,\n Input,\n Row,\n Spacer,\n Text,\n} from '@elytracms/studio/ui'\nimport { cn } from '../../lib/utils'\nimport { translate } from '../../lib/i18n'\nimport { AssetThumb } from '../assets/asset-thumb'\nimport { useAssetReadLocale } from '../assets/asset-locale'\nimport { AssetsProvider, useAssets, useOptionalAssets } from '../assets/assets-store'\nimport type { AssetsStore } from '../assets/assets-store'\nimport { useCollectionView } from '../collection-view/collection-view-context'\nimport {\n ASSET_PICKER_STAGE_PRESET,\n commitPickerSelection,\n batchAttachment,\n commitPickerSelectionMany,\n splitByAccept,\n uploadBatchProgress,\n visiblePickerAssets,\n} from './asset-picker-core'\nimport type { UploadBatch } from './asset-picker-core'\n\n/** The picker's props — see {@link AssetPicker}. */\nexport interface AssetPickerProps {\n value: string | null\n open: boolean\n onOpenChange: (open: boolean) => void\n onChange: (assetId: string | null) => void\n /** EC-460: commit a whole settled upload batch (a `many` field passes this). */\n onChangeMany?: (assetIds: string[]) => void\n /** Whether the consuming field holds many assets (enables batch commit). */\n multiple?: boolean\n /** The field's `accept` list (EC-588) — absent means any file. */\n accept?: readonly string[]\n}\n\n/**\n * Pick an asset for a CMS `asset` field (brief §8.4, §8.6, EC-075 → EC-152).\n * Backed by the *real* asset library (the assets store over the operations\n * client) — the same records the Assets section lists — not by fixture\n * documents. Selecting stores the asset id on the document field.\n *\n * EC-432 (AD-C): the picker is a real MEDIA BROWSER on the {@link ExpandableSurface}\n * `stage` (`grid` preset — `min(1240px, 94vw)`, near-full height), replacing the\n * old `max-w-md` (448px) Dialog. It reuses the shipped assets-rail machinery: the\n * assets store, the EC-152 search, the EC-400 thumb tile ({@link AssetThumb}), and\n * the upload-then-select path. It is a SELECTION browser, not a form — click = pick\n * (fires `onChange`, closes) plus the shipped clear/cancel affordances; the\n * forms-never-modal rule governs document forms and is untouched. Opened from the\n * expanded rich-text stage toolbar it rides ABOVE that stage on the EC-392 layer\n * stack — Escape closes the browser first, the rich-text stage stays.\n *\n * The component CONTRACT (`{ open, onOpenChange, value, accept, onChange }`) serves\n * every call site — asset fields, rich-text image insertion, editable cells. EC-460\n * adds an OPTIONAL multi-commit pair (`multiple` + `onChangeMany`): a `many` asset\n * field passes both so uploading N files in ONE picker interaction attaches ALL N\n * (the old flow committed the first upload to land and closed — the rest reached the\n * library but never the field, silent partial data loss). Grid clicks stay\n * single-pick-and-close in every mode.\n *\n * EC-588: `accept` is the field's STRUCTURED list (it was a file-input string that the\n * core re-split — two shapes for one fact). The grid filter, the file-input attribute\n * and the drop/upload split all derive from it, and an absent `accept` is unrestricted\n * everywhere (the Upload dialog used to fall back to `image/*`). A field-backed caller\n * passes `assetPickerBinding`'s output (`asset-picker-core`) rather than wiring props\n * by hand.\n */\nexport function AssetPicker({\n value,\n open,\n onOpenChange,\n onChange,\n onChangeMany,\n multiple = false,\n accept,\n}: AssetPickerProps) {\n const source = useCollectionView()\n const ambient = useOptionalAssets()\n // Mount nothing while closed: the self-provided assets store would\n // otherwise load the library eagerly for every asset field on the form.\n if (!open) return null\n if (ambient) {\n return (\n <AssetPickerStage\n value={value}\n onOpenChange={onOpenChange}\n onChange={onChange}\n {...(onChangeMany ? { onChangeMany } : {})}\n multiple={multiple}\n accept={accept}\n />\n )\n }\n if (source.projectId === undefined) {\n // No project scope (fixture-only CMS session): the library is\n // unreachable — say so instead of silently showing nothing.\n return (\n <Dialog open onOpenChange={onOpenChange}>\n <DialogContent className=\"max-w-md\">\n <DialogHeader>\n <DialogTitle>{translate('cms.asset.pickTitle')}</DialogTitle>\n <DialogDescription>{translate('cms.asset.pickDescription')}</DialogDescription>\n </DialogHeader>\n <Row gap=\"2\" data-testid=\"asset-picker-no-project\">\n <AlertTriangle className=\"size-4 text-warning\" />\n <Text size=\"sm\" tone=\"muted\">\n {translate('cms.asset.noProject')}\n </Text>\n </Row>\n </DialogContent>\n </Dialog>\n )\n }\n return (\n <AssetsProvider projectId={source.projectId}>\n <AssetPickerStage\n value={value}\n onOpenChange={onOpenChange}\n onChange={onChange}\n {...(onChangeMany ? { onChangeMany } : {})}\n multiple={multiple}\n accept={accept}\n />\n </AssetsProvider>\n )\n}\n\n/** The open media-browser stage over a mounted assets store. */\nfunction AssetPickerStage(props: Omit<AssetPickerProps, 'open'>) {\n const store = useAssets()\n return (\n <ExpandableSurface\n title={translate('cms.asset.pickTitle')}\n closeLabel={translate('common.close')}\n variant=\"stage\"\n preset={ASSET_PICKER_STAGE_PRESET}\n onDismiss={() => props.onOpenChange(false)}\n headerLeading={<ImageIcon className=\"size-4 text-muted-foreground\" aria-hidden />}\n >\n <AssetPickerBrowser store={store} {...props} />\n </ExpandableSurface>\n )\n}\n\n/** The slice of the assets store the browser reads and drives. */\nexport type AssetPickerStore = Pick<\n AssetsStore,\n 'status' | 'errorMessage' | 'assets' | 'uploads' | 'upload' | 'landedAssetId'\n>\n\n/**\n * The media browser itself — search, Upload, the grid, the drop zone — rendered inside\n * the stage. A named component of its own (EC-588) so its markup is reachable by a\n * node-env smoke render (the stage portal is `null` there): the file input's `accept`\n * attribute and the grid's filter are pinned against the field's `accept`.\n */\nexport function AssetPickerBrowser({\n store,\n value,\n onOpenChange,\n onChange,\n onChangeMany,\n multiple = false,\n accept,\n}: Omit<AssetPickerProps, 'open'> & { store: AssetPickerStore }) {\n // Fresh per open: the picker unmounts entirely while closed.\n const [query, setQuery] = useState('')\n const [dragging, setDragging] = useState(false)\n // Names of files a drop/upload refused against the field's `accept` (EC-588) — shown\n // as a visible notice and never uploaded. Cleared by the next upload attempt.\n const [rejected, setRejected] = useState<string[]>([])\n const fileInputRef = useRef<HTMLInputElement>(null)\n // The active upload BATCH (EC-460): exactly the upload ids this interaction started\n // (EC-588 A7), so the effect below attaches this batch's uploads and nothing else.\n const pendingRef = useRef<UploadBatch | null>(null)\n // Drag depth (EC-301): keeps the drop overlay steady as the pointer crosses\n // child boundaries — the same per-element dragenter/dragleave flicker the field\n // fixes. Only hide when the count returns to zero.\n const dragDepth = useRef(0)\n const assets = visiblePickerAssets(store.assets, query, accept)\n\n // Auto-select what an in-picker upload added, then close. A `many` consumer\n // (multiple + onChangeMany) waits for the batch to SETTLE and commits every\n // added id — uploading 2 files attaches 2 (EC-460); a single consumer keeps\n // the first-to-land commit. Only uploads whose record satisfies `accept` commit.\n useEffect(() => {\n const batch = pendingRef.current\n if (!batch) return\n // EC-588 2i: the one batch-attach rule the field's own drop zone runs too.\n const many = multiple === true && onChangeMany !== undefined\n const { attach, disarm } = batchAttachment(uploadBatchProgress(batch, store, accept), many)\n if (disarm) pendingRef.current = null\n if (attach.length === 0) return\n if (many) commitPickerSelectionMany(attach, { onChangeMany, onOpenChange })\n else commitPickerSelection(attach[0]!, { onChange, onOpenChange })\n }, [store, accept, multiple, onChange, onChangeMany, onOpenChange])\n\n function handleUpload(files: File[]) {\n if (files.length === 0) return\n // EC-588: the drop rule — the same split the field's drop zone uses. A drop\n // bypasses the dialog's soft filter; refused files are named, never uploaded.\n const { accepted, rejected: refused } = splitByAccept(files, accept)\n setRejected(refused)\n if (accepted.length === 0) return\n pendingRef.current = { uploadIds: store.upload(accepted) }\n }\n // The stage portals to document.body, but React synthetic events still bubble up\n // the COMPONENT tree — and this picker is rendered as a child of the asset field,\n // which has its own drag-to-upload handlers. Without stopPropagation a drop inside\n // the stage would fire BOTH the picker's and the field's `onDrop`, uploading the\n // file twice. Stop every DnD event here so the picker owns its drop entirely.\n function onDragEnter(e: React.DragEvent) {\n e.preventDefault()\n e.stopPropagation()\n dragDepth.current += 1\n if (!dragging) setDragging(true)\n }\n function onDragOver(e: React.DragEvent) {\n e.preventDefault()\n e.stopPropagation()\n }\n function onDragLeave(e: React.DragEvent) {\n e.stopPropagation()\n dragDepth.current -= 1\n if (dragDepth.current <= 0) {\n dragDepth.current = 0\n setDragging(false)\n }\n }\n function onDrop(e: React.DragEvent) {\n e.preventDefault()\n e.stopPropagation()\n dragDepth.current = 0\n setDragging(false)\n handleUpload(Array.from(e.dataTransfer.files))\n }\n\n return (\n <div\n className=\"relative flex min-h-0 flex-1 flex-col gap-3 p-4\"\n onDragEnter={onDragEnter}\n onDragOver={onDragOver}\n onDragLeave={onDragLeave}\n onDrop={onDrop}\n >\n <Row gap=\"2\" align=\"center\">\n <span className=\"relative flex-1\">\n <Search className=\"pointer-events-none absolute left-2.5 top-1/2 size-3.5 -translate-y-1/2 text-muted-foreground\" />\n <Input\n autoFocus\n value={query}\n onChange={(e) => setQuery(e.target.value)}\n placeholder={translate('cms.asset.searchPlaceholder')}\n className=\"pl-8\"\n data-testid=\"asset-picker-search\"\n />\n </span>\n <Button variant=\"outline\" size=\"sm\" onClick={() => fileInputRef.current?.click()}>\n <Upload /> {translate('common.upload')}\n </Button>\n <input\n ref={fileInputRef}\n type=\"file\"\n multiple\n accept={assetAcceptAttr(accept)}\n className=\"hidden\"\n data-testid=\"asset-picker-file-input\"\n onChange={(e) => {\n handleUpload(Array.from(e.target.files ?? []))\n e.target.value = ''\n }}\n />\n </Row>\n\n <AssetRejectedNotice rejected={rejected} accept={accept} />\n\n <div\n className=\"grid min-h-0 flex-1 auto-rows-min grid-cols-2 gap-2 overflow-y-auto sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5\"\n data-testid=\"asset-picker-grid\"\n >\n {store.status === 'loading' ? (\n <Text size=\"sm\" tone=\"muted\" className=\"col-span-full p-3\">\n {translate('cms.asset.loading')}\n </Text>\n ) : store.status === 'error' ? (\n <Row gap=\"2\" className=\"col-span-full p-3\" data-testid=\"asset-picker-error\">\n <AlertTriangle className=\"size-4 text-destructive\" />\n <Text size=\"sm\" tone=\"muted\">\n {store.errorMessage ?? translate('cms.asset.loadError')}\n </Text>\n </Row>\n ) : assets.length === 0 ? (\n <Text size=\"sm\" tone=\"muted\" className=\"col-span-full p-3\">\n {query.trim() ? translate('cms.asset.noMatches') : translate('cms.asset.empty')}\n </Text>\n ) : (\n assets.map((asset) => (\n <AssetPickerCard\n key={asset.id}\n asset={asset}\n selected={asset.id === value}\n onPick={() => commitPickerSelection(asset.id, { onChange, onOpenChange })}\n />\n ))\n )}\n </div>\n\n <Row gap=\"2\">\n <Spacer />\n {value ? (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => commitPickerSelection(null, { onChange, onOpenChange })}\n >\n {translate('common.clear')}\n </Button>\n ) : null}\n </Row>\n\n {dragging ? (\n <div className=\"pointer-events-none absolute inset-0 z-10 grid place-items-center rounded-lg border-2 border-dashed border-primary bg-background/85\">\n <Row gap=\"2\" align=\"center\">\n <Upload className=\"size-5 text-primary\" />\n <Text className=\"font-medium\">{translate('cms.asset.dropToUpload')}</Text>\n </Row>\n </div>\n ) : null}\n </div>\n )\n}\n\n/**\n * The visible notice for files an upload path refused against the field's `accept`\n * (EC-301 → EC-588): named, never silently dropped. Shared by the field's drop zone and\n * the picker, which apply the same rule. Renders nothing when nothing was refused.\n */\nexport function AssetRejectedNotice({\n rejected,\n accept,\n}: {\n rejected: readonly string[]\n accept: readonly string[] | undefined\n}) {\n if (rejected.length === 0) return null\n return (\n <Row gap=\"2\" align=\"center\" data-testid=\"asset-rejected-notice\">\n <AlertTriangle className=\"size-4 shrink-0 text-warning\" />\n <Text size=\"xs\" tone=\"muted\">\n {rejected.length === 1\n ? translate('cms.asset.rejected.one', { name: rejected[0]!, types: describeAccept(accept) })\n : translate('cms.asset.rejected.other', {\n count: rejected.length,\n types: describeAccept(accept),\n })}\n </Text>\n </Row>\n )\n}\n\n/** One selectable asset: EC-400 thumb tile + title/filename + alt text. The\n * internal asset id is intentionally not shown — it's noise to editors (EC-301). */\nfunction AssetPickerCard({\n asset,\n selected,\n onPick,\n}: {\n asset: AssetRecord\n selected: boolean\n onPick: () => void\n}) {\n const alt = assetAlt(asset, useAssetReadLocale())\n return (\n <button\n type=\"button\"\n onClick={onPick}\n className={cn(\n 'flex flex-col gap-1.5 rounded-lg border p-2 text-left transition-colors',\n selected ? 'border-primary bg-primary/5' : 'border-border hover:bg-muted',\n )}\n data-testid=\"asset-picker-card\"\n aria-pressed={selected}\n >\n <span className=\"relative block\">\n <AssetThumb record={asset} className=\"aspect-video w-full\" />\n {selected ? (\n <span className=\"absolute right-1 top-1 grid size-5 place-items-center rounded-full bg-primary text-primary-foreground shadow\">\n <Check className=\"size-3\" />\n </span>\n ) : null}\n </span>\n <Text size=\"xs\" className=\"min-w-0 truncate font-medium\">\n {asset.title ?? asset.filename}\n </Text>\n {/* EC-587 (ASSETS D20): the asset's alt through THE reader, as delivery serves it —\n EC-632: in the studio's locale. */}\n <Text size=\"xs\" tone={alt ? 'muted' : 'subtle'} className=\"truncate\">\n {alt || translate('cms.asset.noAltText')}\n </Text>\n </button>\n )\n}\n"]}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { DocumentPatch, ValuePath } from '@elytracms/core/cms-core';
|
|
2
|
+
import type { CommitPolicy } from './commit-policy.js';
|
|
3
|
+
import type { DraftCommitter } from './draft-commit.js';
|
|
4
|
+
/**
|
|
5
|
+
* EC-630 — the canvas inspector commits like every other editor: on its control kind's
|
|
6
|
+
* quiescence policy (`commit-policy.ts`, EC-595 A2), never per keystroke (pure; the house
|
|
7
|
+
* pattern).
|
|
8
|
+
*
|
|
9
|
+
* The canvas lifts every store change into the draft at once (live validation reads it).
|
|
10
|
+
* A burst of typing in an inspector control used to be committed per keystroke too —
|
|
11
|
+
* every canvas write went straight to the persister under the canvas's `change` policy:
|
|
12
|
+
* 13 revisions for one typography-run edit, measured live (wave-0094), and one per
|
|
13
|
+
* keystroke on any text prop. The canvas's own actions (insert, move, delete, undo, a
|
|
14
|
+
* discrete control) ARE discrete commits; a control's typing is not. So the inspector
|
|
15
|
+
* controls stand inside a commit boundary (focus / blur / Enter, the form's
|
|
16
|
+
* `FieldCommitBoundary`) at a POSITION — a node's prop, or a typography run, which is
|
|
17
|
+
* the field itself (prose is one position, EC-595 binding point 3) — with the policy
|
|
18
|
+
* THE declaration gives that control kind (`commitPolicyOf`), and THE committer
|
|
19
|
+
* (`draft-commit.ts`, shared with the form fields) holds the burst as that position's
|
|
20
|
+
* uncommitted edit: committed on blur, Enter or the idle pause, with the base revision
|
|
21
|
+
* of its first keystroke, overlaid on every rebase meanwhile, and in every forced flush.
|
|
22
|
+
*/
|
|
23
|
+
/** Where a canvas inspector control writes: a node's prop, or a typography run (the field). */
|
|
24
|
+
export type CanvasControlPosition = {
|
|
25
|
+
readonly node: string;
|
|
26
|
+
readonly prop: string;
|
|
27
|
+
} | {
|
|
28
|
+
readonly run: number;
|
|
29
|
+
};
|
|
30
|
+
/** A focused canvas inspector control, as the committer knows it. */
|
|
31
|
+
export interface CanvasControl {
|
|
32
|
+
/** The committer's key for it (never a field name — those carry no `:`). */
|
|
33
|
+
readonly key: string;
|
|
34
|
+
/** The position it writes. */
|
|
35
|
+
readonly path: ValuePath;
|
|
36
|
+
readonly policy: CommitPolicy;
|
|
37
|
+
}
|
|
38
|
+
/** The control at `position` of the canvas field at `field` (the field's position path, locale included). */
|
|
39
|
+
export declare function canvasControl(field: ValuePath, position: CanvasControlPosition, policy: CommitPolicy): CanvasControl;
|
|
40
|
+
/** Whether a committer key names a canvas control. */
|
|
41
|
+
export declare function isCanvasControlKey(key: string): boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Route one canvas write's patches. When they are exactly the focused control's value
|
|
44
|
+
* write at its position and its policy waits for quiescence, the write is that control's
|
|
45
|
+
* uncommitted position (`deferred` — the committer commits the latest value later).
|
|
46
|
+
* Anything else — a canvas action, a discrete control, an edit with no control focused —
|
|
47
|
+
* commits now (`committed`), after any canvas position still uncommitted, so positions
|
|
48
|
+
* reach the persister in the order the editor made them.
|
|
49
|
+
*/
|
|
50
|
+
export declare function routeCanvasWrite(input: {
|
|
51
|
+
patches: readonly DocumentPatch[];
|
|
52
|
+
/** The focused canvas control, if focus is on one. */
|
|
53
|
+
focused: CanvasControl | undefined;
|
|
54
|
+
committer: DraftCommitter;
|
|
55
|
+
commitNow: (patches: readonly DocumentPatch[]) => void;
|
|
56
|
+
}): 'deferred' | 'committed';
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { formatValuePath } from '@elytracms/core/cms-core';
|
|
2
|
+
/** The control at `position` of the canvas field at `field` (the field's position path, locale included). */
|
|
3
|
+
export function canvasControl(field, position, policy) {
|
|
4
|
+
const path = 'node' in position
|
|
5
|
+
? [...field, { kind: 'node', id: position.node }, { kind: 'prop', name: position.prop }]
|
|
6
|
+
: field;
|
|
7
|
+
return { key: `canvas:${formatValuePath(path)}`, path, policy };
|
|
8
|
+
}
|
|
9
|
+
/** Whether a committer key names a canvas control. */
|
|
10
|
+
export function isCanvasControlKey(key) {
|
|
11
|
+
return key.startsWith('canvas:');
|
|
12
|
+
}
|
|
13
|
+
function writesExactly(patch, path) {
|
|
14
|
+
return (patch.op === 'set' || patch.op === 'unset') && formatValuePath(patch.path) === formatValuePath(path);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Route one canvas write's patches. When they are exactly the focused control's value
|
|
18
|
+
* write at its position and its policy waits for quiescence, the write is that control's
|
|
19
|
+
* uncommitted position (`deferred` — the committer commits the latest value later).
|
|
20
|
+
* Anything else — a canvas action, a discrete control, an edit with no control focused —
|
|
21
|
+
* commits now (`committed`), after any canvas position still uncommitted, so positions
|
|
22
|
+
* reach the persister in the order the editor made them.
|
|
23
|
+
*/
|
|
24
|
+
export function routeCanvasWrite(input) {
|
|
25
|
+
const { patches, focused, committer } = input;
|
|
26
|
+
if (focused && focused.policy !== 'change' && patches.length === 1 && writesExactly(patches[0], focused.path)) {
|
|
27
|
+
committer.edited(focused.key, focused.path);
|
|
28
|
+
return 'deferred';
|
|
29
|
+
}
|
|
30
|
+
if (committer.uncommitted().some((position) => isCanvasControlKey(position.field)))
|
|
31
|
+
committer.commitAll();
|
|
32
|
+
input.commitNow(patches);
|
|
33
|
+
return 'committed';
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=canvas-commit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"canvas-commit.js","sourceRoot":"","sources":["../../../src/components/cms/canvas-commit.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAA;AAqC1D,6GAA6G;AAC7G,MAAM,UAAU,aAAa,CAAC,KAAgB,EAAE,QAA+B,EAAE,MAAoB;IACnG,MAAM,IAAI,GACR,MAAM,IAAI,QAAQ;QAChB,CAAC,CAAC,CAAC,GAAG,KAAK,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,QAAQ,CAAC,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,CAAC;QACxF,CAAC,CAAC,KAAK,CAAA;IACX,OAAO,EAAE,GAAG,EAAE,UAAU,eAAe,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,CAAA;AACjE,CAAC;AAED,sDAAsD;AACtD,MAAM,UAAU,kBAAkB,CAAC,GAAW;IAC5C,OAAO,GAAG,CAAC,UAAU,CAAC,SAAS,CAAC,CAAA;AAClC,CAAC;AAED,SAAS,aAAa,CAAC,KAAoB,EAAE,IAAe;IAC1D,OAAO,CAAC,KAAK,CAAC,EAAE,KAAK,KAAK,IAAI,KAAK,CAAC,EAAE,KAAK,OAAO,CAAC,IAAI,eAAe,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,eAAe,CAAC,IAAI,CAAC,CAAA;AAC9G,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAMhC;IACC,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,GAAG,KAAK,CAAA;IAC7C,IAAI,OAAO,IAAI,OAAO,CAAC,MAAM,KAAK,QAAQ,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,IAAI,aAAa,CAAC,OAAO,CAAC,CAAC,CAAE,EAAE,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QAC/G,SAAS,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,IAAI,CAAC,CAAA;QAC3C,OAAO,UAAU,CAAA;IACnB,CAAC;IACD,IAAI,SAAS,CAAC,WAAW,EAAE,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,kBAAkB,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;QAAE,SAAS,CAAC,SAAS,EAAE,CAAA;IACzG,KAAK,CAAC,SAAS,CAAC,OAAO,CAAC,CAAA;IACxB,OAAO,WAAW,CAAA;AACpB,CAAC","sourcesContent":["import { formatValuePath } from '@elytracms/core/cms-core'\nimport type { DocumentPatch, ValuePath } from '@elytracms/core/cms-core'\nimport type { CommitPolicy } from './commit-policy'\nimport type { DraftCommitter } from './draft-commit'\n\n/**\n * EC-630 — the canvas inspector commits like every other editor: on its control kind's\n * quiescence policy (`commit-policy.ts`, EC-595 A2), never per keystroke (pure; the house\n * pattern).\n *\n * The canvas lifts every store change into the draft at once (live validation reads it).\n * A burst of typing in an inspector control used to be committed per keystroke too —\n * every canvas write went straight to the persister under the canvas's `change` policy:\n * 13 revisions for one typography-run edit, measured live (wave-0094), and one per\n * keystroke on any text prop. The canvas's own actions (insert, move, delete, undo, a\n * discrete control) ARE discrete commits; a control's typing is not. So the inspector\n * controls stand inside a commit boundary (focus / blur / Enter, the form's\n * `FieldCommitBoundary`) at a POSITION — a node's prop, or a typography run, which is\n * the field itself (prose is one position, EC-595 binding point 3) — with the policy\n * THE declaration gives that control kind (`commitPolicyOf`), and THE committer\n * (`draft-commit.ts`, shared with the form fields) holds the burst as that position's\n * uncommitted edit: committed on blur, Enter or the idle pause, with the base revision\n * of its first keystroke, overlaid on every rebase meanwhile, and in every forced flush.\n */\n\n/** Where a canvas inspector control writes: a node's prop, or a typography run (the field). */\nexport type CanvasControlPosition = { readonly node: string; readonly prop: string } | { readonly run: number }\n\n/** A focused canvas inspector control, as the committer knows it. */\nexport interface CanvasControl {\n /** The committer's key for it (never a field name — those carry no `:`). */\n readonly key: string\n /** The position it writes. */\n readonly path: ValuePath\n readonly policy: CommitPolicy\n}\n\n/** The control at `position` of the canvas field at `field` (the field's position path, locale included). */\nexport function canvasControl(field: ValuePath, position: CanvasControlPosition, policy: CommitPolicy): CanvasControl {\n const path: ValuePath =\n 'node' in position\n ? [...field, { kind: 'node', id: position.node }, { kind: 'prop', name: position.prop }]\n : field\n return { key: `canvas:${formatValuePath(path)}`, path, policy }\n}\n\n/** Whether a committer key names a canvas control. */\nexport function isCanvasControlKey(key: string): boolean {\n return key.startsWith('canvas:')\n}\n\nfunction writesExactly(patch: DocumentPatch, path: ValuePath): boolean {\n return (patch.op === 'set' || patch.op === 'unset') && formatValuePath(patch.path) === formatValuePath(path)\n}\n\n/**\n * Route one canvas write's patches. When they are exactly the focused control's value\n * write at its position and its policy waits for quiescence, the write is that control's\n * uncommitted position (`deferred` — the committer commits the latest value later).\n * Anything else — a canvas action, a discrete control, an edit with no control focused —\n * commits now (`committed`), after any canvas position still uncommitted, so positions\n * reach the persister in the order the editor made them.\n */\nexport function routeCanvasWrite(input: {\n patches: readonly DocumentPatch[]\n /** The focused canvas control, if focus is on one. */\n focused: CanvasControl | undefined\n committer: DraftCommitter\n commitNow: (patches: readonly DocumentPatch[]) => void\n}): 'deferred' | 'committed' {\n const { patches, focused, committer } = input\n if (focused && focused.policy !== 'change' && patches.length === 1 && writesExactly(patches[0]!, focused.path)) {\n committer.edited(focused.key, focused.path)\n return 'deferred'\n }\n if (committer.uncommitted().some((position) => isCanvasControlKey(position.field))) committer.commitAll()\n input.commitNow(patches)\n return 'committed'\n}\n"]}
|
|
@@ -85,7 +85,8 @@ export function LoadedCollectionTable({ projectId, toolbarExtra, }) {
|
|
|
85
85
|
// EC-216 + EC-205 ("live = published"): each routable document's LIVE URL,
|
|
86
86
|
// derived from the PUBLISHED pool (never draft rows), memoised inside the
|
|
87
87
|
// source. `null` when the collection is not routable → the column is hidden.
|
|
88
|
-
|
|
88
|
+
// EC-628: a read state — a failed published read is every URL cell's error.
|
|
89
|
+
const liveUrls = source.publishing?.liveUrls?.() ?? null;
|
|
89
90
|
const visibleDocs = useMemo(
|
|
90
91
|
// EC-224: the `$state` filter/sort sources published-ness from the version
|
|
91
92
|
// POINTER (`source.publishing.isPublished`), not a flat `state` field.
|
|
@@ -272,7 +273,7 @@ export function LoadedCollectionTable({ projectId, toolbarExtra, }) {
|
|
|
272
273
|
? translate('cms.list.newPage')
|
|
273
274
|
: translate('cms.list.newNamed', {
|
|
274
275
|
noun: localizedLabel(collection.form?.labelSingular, 'document'),
|
|
275
|
-
})] })) : null] }) })) : visibleDocs.length === 0 ? (_jsx("div", { className: "grid h-full place-items-center p-8", children: _jsx(Text, { tone: "muted", "data-testid": "empty-filtered", children: translate('cms.list.emptyFiltered') }) })) : (_jsx(DocumentsGrid, { projectId: projectId, collection: collection, primary: primary, primaryFieldDef: primaryFieldDef, columns: columns, sortedDocs: orderedDocs, depthById: depthById,
|
|
276
|
+
})] })) : null] }) })) : visibleDocs.length === 0 ? (_jsx("div", { className: "grid h-full place-items-center p-8", children: _jsx(Text, { tone: "muted", "data-testid": "empty-filtered", children: translate('cms.list.emptyFiltered') }) })) : (_jsx(DocumentsGrid, { projectId: projectId, collection: collection, primary: primary, primaryFieldDef: primaryFieldDef, columns: columns, sortedDocs: orderedDocs, depthById: depthById, liveUrls: liveUrls, sort: viewState.sort, onSort: toggleSort, selectedIds: selectedIds, onToggleRow: (id, shift) => setSelection((prev) => toggleRow(prev, visibleIds, id, shift)), onToggleAll: () => setSelection(selectedIds.length === visibleIds.length && visibleIds.length > 0
|
|
276
277
|
? clearSelection()
|
|
277
278
|
: selectAll(visibleIds)), hasRowActions: hasRowActions, onRowAction: handleRowAction })) }), source.capabilities.importExport ? (_jsx(ImportDialog, { collectionId: collectionId, open: importOpen, onOpenChange: setImportOpen })) : null] }));
|
|
278
279
|
}
|
|
@@ -283,7 +284,7 @@ export function LoadedCollectionTable({ projectId, toolbarExtra, }) {
|
|
|
283
284
|
* rollback + inline cell errors on rejection. Row checkboxes (EC-169) live in
|
|
284
285
|
* their own column, so selection clicks never collide with cell editing.
|
|
285
286
|
*/
|
|
286
|
-
function DocumentsGrid({ projectId, collection, primary, primaryFieldDef, columns, sortedDocs, depthById,
|
|
287
|
+
function DocumentsGrid({ projectId, collection, primary, primaryFieldDef, columns, sortedDocs, depthById, liveUrls, sort, onSort, selectedIds, onToggleRow, onToggleAll, hasRowActions, onRowAction, }) {
|
|
287
288
|
const source = useCollectionView();
|
|
288
289
|
const collectionId = collection.id;
|
|
289
290
|
// EC-346: a rail with no publish lifecycle (assets/redirects/users) has no
|
|
@@ -334,7 +335,7 @@ function DocumentsGrid({ projectId, collection, primary, primaryFieldDef, column
|
|
|
334
335
|
id: doc.id,
|
|
335
336
|
}) ?? false) }) })) : null, _jsx("td", { className: "px-3 py-2 align-middle", children: _jsx(PrimaryCell, { projectId: projectId, collection: collection, doc: doc, primary: primary, depth: depthById?.get(doc.id) ?? 0, locale: source.activeLocale, config: source.localeConfig, detailPath: source.detailPath }) }), columns.map((name) => {
|
|
336
337
|
if (isPseudoColumn(name)) {
|
|
337
|
-
return (_jsx("td", { className: "px-3 py-2 align-middle", children: _jsx(PseudoCell, { name: name, docId: doc.id, collectionId: collectionId, timestamps: timestamps,
|
|
338
|
+
return (_jsx("td", { className: "px-3 py-2 align-middle", children: _jsx(PseudoCell, { name: name, docId: doc.id, collectionId: collectionId, timestamps: timestamps, live: liveUrlOf(liveUrls, doc.id), locale: source.activeLocale }) }, name));
|
|
338
339
|
}
|
|
339
340
|
const field = fieldByName(name);
|
|
340
341
|
const coord = { docId: doc.id, field: name };
|
|
@@ -447,11 +448,11 @@ function formatListDate(value, locale) {
|
|
|
447
448
|
* `$state` is NO LONGER dispatched here — EC-448 made it a fixed structural leading
|
|
448
449
|
* column ({@link DocStateDot}), so it never flows through the reorderable columns.
|
|
449
450
|
*/
|
|
450
|
-
function PseudoCell({ name, docId, collectionId, timestamps,
|
|
451
|
+
function PseudoCell({ name, docId, collectionId, timestamps, live, locale, }) {
|
|
451
452
|
const ref = { collection: collectionId, id: docId };
|
|
452
453
|
switch (name) {
|
|
453
454
|
case URL_FIELD:
|
|
454
|
-
return _jsx(UrlCell, {
|
|
455
|
+
return _jsx(UrlCell, { live: live });
|
|
455
456
|
case UPDATED_AT_FIELD:
|
|
456
457
|
case PUBLISHED_AT_FIELD: {
|
|
457
458
|
const value = name === UPDATED_AT_FIELD ? timestamps?.updatedAt(ref) : timestamps?.publishedAt(ref);
|
|
@@ -463,6 +464,12 @@ function PseudoCell({ name, docId, collectionId, timestamps, url, locale, }) {
|
|
|
463
464
|
return null;
|
|
464
465
|
}
|
|
465
466
|
}
|
|
467
|
+
/** One row's share of the live-URL read state (EC-628): its URL once the pool answered. */
|
|
468
|
+
function liveUrlOf(liveUrls, docId) {
|
|
469
|
+
if (!liveUrls)
|
|
470
|
+
return undefined;
|
|
471
|
+
return liveUrls.status === 'ready' ? { status: 'ready', value: liveUrls.value.get(docId) } : liveUrls;
|
|
472
|
+
}
|
|
466
473
|
/** The empty-value dash shared by the derived pseudo-cells (URL / dates). */
|
|
467
474
|
function EmDash() {
|
|
468
475
|
return (_jsx(Text, { size: "xs", tone: "subtle", children: "\u2014" }));
|
|
@@ -478,8 +485,17 @@ function EmDash() {
|
|
|
478
485
|
* reaches the document through it) is never a link, `href` or not: the path stays
|
|
479
486
|
* readable, and the shared `warning` glyph (EC-507) + the same label the Issues
|
|
480
487
|
* pane uses replace the open-live icon, so the row and the form agree.
|
|
488
|
+
*
|
|
489
|
+
* EC-628: `live` is the row's share of the published pool's read state. A FAILED read
|
|
490
|
+
* is the shared `error` glyph with the pool's sentence — never the dash, which says
|
|
491
|
+
* "not published" (the pool used to degrade a failure to empty, and every row read so).
|
|
481
492
|
*/
|
|
482
|
-
function UrlCell({
|
|
493
|
+
function UrlCell({ live }) {
|
|
494
|
+
if (live?.status === 'error') {
|
|
495
|
+
const Icon = issueSeverityIcon('error');
|
|
496
|
+
return (_jsx(Text, { as: "span", size: "xs", className: cn('inline-flex items-center', issueSeverityStyle('error').text), title: live.message, "aria-label": live.message, "data-live-severity": "error", children: _jsx(Icon, { className: "size-3 shrink-0", "aria-hidden": true }) }));
|
|
497
|
+
}
|
|
498
|
+
const url = live?.status === 'ready' ? live.value : undefined;
|
|
483
499
|
if (!url) {
|
|
484
500
|
return (_jsx(Text, { size: "xs", tone: "subtle", children: "\u2014" }));
|
|
485
501
|
}
|