@elytracms/studio 0.0.27 → 0.0.28
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/assets.ts +19 -10
- package/convex/authority.ts +60 -0
- package/convex/cliTokens.ts +4 -4
- package/convex/cms.ts +12 -12
- package/convex/content.ts +10 -3
- package/convex/delivery.ts +22 -4
- package/convex/guard.ts +28 -11
- package/convex/members.ts +40 -7
- package/convex/redirects.ts +67 -0
- package/convex/references.ts +11 -11
- package/convex/schema.ts +30 -1
- package/convex/sync.ts +17 -10
- package/convex/users.ts +63 -0
- package/dist/components/app-views/default-modules.d.ts +33 -14
- package/dist/components/app-views/default-modules.js +59 -20
- package/dist/components/app-views/default-modules.js.map +1 -1
- package/dist/components/app-views/module.d.ts +32 -0
- package/dist/components/app-views/module.js +16 -0
- package/dist/components/app-views/module.js.map +1 -1
- package/dist/components/app-views/sidebar.d.ts +15 -1
- package/dist/components/app-views/sidebar.js +10 -0
- package/dist/components/app-views/sidebar.js.map +1 -1
- package/dist/components/cms/asset-picker.js +4 -4
- package/dist/components/cms/asset-picker.js.map +1 -1
- package/dist/components/cms/cell.js +16 -13
- package/dist/components/cms/cell.js.map +1 -1
- package/dist/components/cms/cms-store.d.ts +11 -26
- package/dist/components/cms/cms-store.js +25 -160
- package/dist/components/cms/cms-store.js.map +1 -1
- package/dist/components/cms/collection-table.d.ts +10 -0
- package/dist/components/cms/collection-table.js +72 -72
- package/dist/components/cms/collection-table.js.map +1 -1
- package/dist/components/cms/detail-editor.d.ts +14 -0
- package/dist/components/cms/detail-editor.js +224 -72
- package/dist/components/cms/detail-editor.js.map +1 -1
- package/dist/components/cms/document-editor-context.d.ts +26 -0
- package/dist/components/cms/document-editor-context.js.map +1 -1
- package/dist/components/cms/editable-cell.js +12 -7
- package/dist/components/cms/editable-cell.js.map +1 -1
- package/dist/components/cms/field-control.d.ts +2 -0
- package/dist/components/cms/field-control.js +40 -30
- package/dist/components/cms/field-control.js.map +1 -1
- package/dist/components/cms/filter-builder.d.ts +18 -0
- package/dist/components/cms/filter-builder.js +47 -6
- package/dist/components/cms/filter-builder.js.map +1 -1
- package/dist/components/cms/import-export.js +5 -3
- package/dist/components/cms/import-export.js.map +1 -1
- package/dist/components/cms/reference-confirm.js +8 -9
- package/dist/components/cms/reference-confirm.js.map +1 -1
- package/dist/components/cms/relation-picker.js +13 -10
- package/dist/components/cms/relation-picker.js.map +1 -1
- package/dist/components/cms/use-cell-edit.js +13 -7
- package/dist/components/cms/use-cell-edit.js.map +1 -1
- package/dist/components/cms/use-document-versions.d.ts +16 -0
- package/dist/components/cms/use-document-versions.js +31 -0
- package/dist/components/cms/use-document-versions.js.map +1 -0
- package/dist/components/cms/version-compare-text.d.ts +30 -0
- package/dist/components/cms/version-compare-text.js +113 -0
- package/dist/components/cms/version-compare-text.js.map +1 -0
- package/dist/components/cms/version-compare.d.ts +12 -0
- package/dist/components/cms/version-compare.js +271 -0
- package/dist/components/cms/version-compare.js.map +1 -0
- package/dist/components/cms/versions-pane.d.ts +3 -0
- package/dist/components/cms/versions-pane.js +47 -38
- package/dist/components/cms/versions-pane.js.map +1 -1
- package/dist/components/cms/view-bar.d.ts +21 -6
- package/dist/components/cms/view-bar.js +42 -57
- package/dist/components/cms/view-bar.js.map +1 -1
- package/dist/components/collection-view/cms-collection-view-source.d.ts +15 -0
- package/dist/components/collection-view/cms-collection-view-source.js +158 -0
- package/dist/components/collection-view/cms-collection-view-source.js.map +1 -0
- package/dist/components/collection-view/collection-view-context.d.ts +10 -0
- package/dist/components/collection-view/collection-view-context.js +26 -0
- package/dist/components/collection-view/collection-view-context.js.map +1 -0
- package/dist/components/current-user-menu.js +8 -1
- package/dist/components/current-user-menu.js.map +1 -1
- package/dist/components/redirects/redirects-collection-view-source.d.ts +18 -0
- package/dist/components/redirects/redirects-collection-view-source.js +114 -0
- package/dist/components/redirects/redirects-collection-view-source.js.map +1 -0
- package/dist/components/redirects/redirects-module.d.ts +18 -0
- package/dist/components/redirects/redirects-module.js +43 -0
- package/dist/components/redirects/redirects-module.js.map +1 -0
- package/dist/components/redirects/redirects-schema.d.ts +10 -0
- package/dist/components/redirects/redirects-schema.js +42 -0
- package/dist/components/redirects/redirects-schema.js.map +1 -0
- package/dist/components/redirects/redirects-store.d.ts +46 -0
- package/dist/components/redirects/redirects-store.js +124 -0
- package/dist/components/redirects/redirects-store.js.map +1 -0
- package/dist/components/references/references-panel.js +2 -2
- package/dist/components/references/references-panel.js.map +1 -1
- package/dist/components/section-nav.js +14 -5
- package/dist/components/section-nav.js.map +1 -1
- package/dist/components/users/user-search.d.ts +8 -0
- package/dist/components/users/user-search.js +13 -0
- package/dist/components/users/user-search.js.map +1 -0
- package/dist/components/users/users-collection-view-source.d.ts +28 -0
- package/dist/components/users/users-collection-view-source.js +157 -0
- package/dist/components/users/users-collection-view-source.js.map +1 -0
- package/dist/components/users/users-module.d.ts +23 -0
- package/dist/components/users/users-module.js +61 -0
- package/dist/components/users/users-module.js.map +1 -0
- package/dist/components/users/users-schema.d.ts +19 -0
- package/dist/components/users/users-schema.js +38 -0
- package/dist/components/users/users-schema.js.map +1 -0
- package/dist/components/users/users-store.d.ts +40 -0
- package/dist/components/users/users-store.js +87 -0
- package/dist/components/users/users-store.js.map +1 -0
- package/dist/lib/cms-views/index.d.ts +4 -5
- package/dist/lib/cms-views/index.js +4 -5
- package/dist/lib/cms-views/index.js.map +1 -1
- package/dist/lib/cms-views/view-model.d.ts +48 -1
- package/dist/lib/cms-views/view-model.js +77 -1
- package/dist/lib/cms-views/view-model.js.map +1 -1
- package/dist/lib/collection-view/collection-view-source.d.ts +251 -0
- package/dist/lib/collection-view/collection-view-source.js +10 -0
- package/dist/lib/collection-view/collection-view-source.js.map +1 -0
- package/dist/lib/operations/session-hydration.d.ts +7 -0
- package/dist/lib/operations/session-hydration.js +7 -1
- package/dist/lib/operations/session-hydration.js.map +1 -1
- package/dist/lib/persistence/convex-adapter.d.ts +5 -1
- package/dist/lib/persistence/convex-adapter.js +148 -1
- package/dist/lib/persistence/convex-adapter.js.map +1 -1
- package/dist/lib/workspace/index.d.ts +1 -8
- package/dist/lib/workspace/index.js +3 -9
- package/dist/lib/workspace/index.js.map +1 -1
- package/dist/router/match.d.ts +4 -0
- package/dist/router/match.js +8 -0
- package/dist/router/match.js.map +1 -1
- package/dist/ui/index.d.ts +2 -0
- package/dist/ui/index.js +2 -0
- package/dist/ui/index.js.map +1 -1
- package/dist/ui/popover.d.ts +13 -0
- package/dist/ui/popover.js +17 -0
- package/dist/ui/popover.js.map +1 -0
- package/dist/ui/row-item.d.ts +16 -0
- package/dist/ui/row-item.js +18 -0
- package/dist/ui/row-item.js.map +1 -0
- package/package.json +3 -2
|
@@ -4,8 +4,7 @@ import { readAssetId, readDocumentRef } from '@elytracms/core/cms-core';
|
|
|
4
4
|
import { blockRootsOf } from '@elytracms/core/project-graph';
|
|
5
5
|
import { Badge, Text } from '@elytracms/studio/ui';
|
|
6
6
|
import { localeChipLabel, resolveDisplayedValue } from '@elytracms/studio/lib/locale';
|
|
7
|
-
import {
|
|
8
|
-
import { useCms } from './cms-store';
|
|
7
|
+
import { useCollectionView } from '../collection-view/collection-view-context';
|
|
9
8
|
/**
|
|
10
9
|
* Render a document's field value for a table cell, by field type (brief
|
|
11
10
|
* §8.4, EC-070), resolved in the studio's ambient active locale (EC-176).
|
|
@@ -13,8 +12,8 @@ import { useCms } from './cms-store';
|
|
|
13
12
|
* silently mixed in as if it were translated.
|
|
14
13
|
*/
|
|
15
14
|
export function CellValue({ collection, doc, field, }) {
|
|
16
|
-
const
|
|
17
|
-
const { value, fallback } = resolveDisplayedValue(collection, doc, field.name,
|
|
15
|
+
const source = useCollectionView();
|
|
16
|
+
const { value, fallback } = resolveDisplayedValue(collection, doc, field.name, source.activeLocale, source.localeConfig);
|
|
18
17
|
if (value === undefined || value === null || value === '') {
|
|
19
18
|
return _jsx(Minus, { className: "size-3.5 text-muted-foreground/50" });
|
|
20
19
|
}
|
|
@@ -65,11 +64,14 @@ function TypedCellValue({ field, value }) {
|
|
|
65
64
|
}
|
|
66
65
|
}
|
|
67
66
|
function RelationCell({ value }) {
|
|
68
|
-
const
|
|
67
|
+
const source = useCollectionView();
|
|
69
68
|
const refs = toRefArray(value);
|
|
70
69
|
if (refs.length === 0)
|
|
71
70
|
return _jsx(Minus, { className: "size-3.5 text-muted-foreground/50" });
|
|
72
|
-
|
|
71
|
+
// EC-246c: calm de-pilled read — a single muted link glyph + the title(s), not
|
|
72
|
+
// a row of info pills. Matches the relation FIELD's row treatment; the pill was
|
|
73
|
+
// a design cue the list doesn't need.
|
|
74
|
+
return (_jsxs("span", { className: "inline-flex min-w-0 items-center gap-1.5 text-sm", children: [_jsx(Link2, { className: "size-3.5 shrink-0 text-muted-foreground" }), _jsx("span", { className: "min-w-0 truncate", children: refs.slice(0, 3).map((ref) => source.titleOf(ref)).join(', ') }), refs.length > 3 ? (_jsxs(Text, { size: "xs", tone: "subtle", className: "shrink-0", children: ["+", refs.length - 3] })) : null] }));
|
|
73
75
|
}
|
|
74
76
|
/**
|
|
75
77
|
* A blocks/composition cell: a count of TOP-LEVEL blocks (EC-212) — matching
|
|
@@ -82,9 +84,8 @@ function BlocksCell({ value }) {
|
|
|
82
84
|
return (_jsxs(Badge, { variant: "outline", children: [_jsx(Boxes, { className: "size-3" }), " ", count, " ", count === 1 ? 'block' : 'blocks'] }));
|
|
83
85
|
}
|
|
84
86
|
function AssetCell({ value }) {
|
|
85
|
-
const
|
|
86
|
-
const
|
|
87
|
-
const assetCollectionId = cms.collections.find((c) => c.kind === 'asset')?.id ?? 'asset';
|
|
87
|
+
const source = useCollectionView();
|
|
88
|
+
const assetCollectionId = source.assetCollectionId ?? 'asset';
|
|
88
89
|
// EC-277: tolerate self-typed `{ type:'asset', id }` envelopes and legacy bare ids.
|
|
89
90
|
const ids = (Array.isArray(value) ? value : [value])
|
|
90
91
|
.map(readAssetId)
|
|
@@ -92,17 +93,19 @@ function AssetCell({ value }) {
|
|
|
92
93
|
if (ids.length === 0)
|
|
93
94
|
return _jsx(Minus, { className: "size-3.5 text-muted-foreground/50" });
|
|
94
95
|
const first = ids[0];
|
|
95
|
-
|
|
96
|
-
|
|
96
|
+
// AD-19: the source resolves the asset record (or nothing, when the rail has no
|
|
97
|
+
// asset backend — degrading to the labelled badge, as with no `<AssetsProvider>`).
|
|
98
|
+
const record = source.resolveAsset?.(first);
|
|
99
|
+
const label = record?.title ?? record?.filename ?? source.titleOf({ collection: assetCollectionId, id: first });
|
|
97
100
|
return (_jsxs("div", { className: "flex flex-wrap items-center gap-1.5", children: [_jsx(AssetPreview, { record: record, label: label }), ids.length > 1 ? _jsxs(Text, { size: "xs", tone: "subtle", children: ["+", ids.length - 1] }) : null] }));
|
|
98
101
|
}
|
|
99
102
|
/**
|
|
100
103
|
* An asset reference in a cell: an image thumbnail for image assets with a
|
|
101
104
|
* resolvable URL (EC-212), otherwise the labelled icon badge (and the same
|
|
102
|
-
* badge whenever
|
|
105
|
+
* badge whenever the source can't resolve the record — e.g. no asset backend).
|
|
103
106
|
*/
|
|
104
107
|
function AssetPreview({ record, label }) {
|
|
105
|
-
const isImage = record?.contentType
|
|
108
|
+
const isImage = record?.contentType?.startsWith('image/') ?? false;
|
|
106
109
|
const url = record?.url;
|
|
107
110
|
if (isImage && url && url.startsWith('http')) {
|
|
108
111
|
return (_jsxs("span", { className: "inline-flex min-w-0 items-center gap-1.5", children: [_jsx("img", { src: url, alt: record?.alt ?? label, loading: "lazy", className: "size-7 shrink-0 rounded border border-border bg-muted object-cover" }), _jsx(Text, { size: "xs", tone: "subtle", className: "max-w-[10rem] truncate", children: label })] }));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cell.js","sourceRoot":"","sources":["../../../src/components/cms/cell.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,cAAc,CAAA;AAChE,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAA;AAEvE,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAA;AAE5D,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAA;AAClD,OAAO,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAA;AAErF,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAC1D,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAEpC;;;;;GAKG;AACH,MAAM,UAAU,SAAS,CAAC,EACxB,UAAU,EACV,GAAG,EACH,KAAK,GAKN;IACC,MAAM,GAAG,GAAG,MAAM,EAAE,CAAA;IACpB,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,qBAAqB,CAC/C,UAAU,EACV,GAAG,EACH,KAAK,CAAC,IAAI,EACV,GAAG,CAAC,YAAY,EAChB,GAAG,CAAC,YAAY,CACjB,CAAA;IAED,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,EAAE,EAAE,CAAC;QAC1D,OAAO,KAAC,KAAK,IAAC,SAAS,EAAC,mCAAmC,GAAG,CAAA;IAChE,CAAC;IAED,OAAO,CACL,gBAAM,SAAS,EAAC,qDAAqD,aACnE,KAAC,cAAc,IAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,GAAI,EAC9C,KAAC,YAAY,IAAC,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,iBAAiB,GAAG,CAAC,EAAE,IAAI,KAAK,CAAC,IAAI,EAAE,GAAI,IAChF,CACR,CAAA;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,EAC3B,QAAQ,EACR,MAAM,GAIP;IACC,IAAI,QAAQ,KAAK,IAAI;QAAE,OAAO,IAAI,CAAA;IAClC,OAAO,CACL,KAAC,KAAK,IACJ,OAAO,EAAC,SAAS,iBACJ,MAAM,EACnB,KAAK,EAAE,aAAa,CAAC,QAAQ,CAAC,EAC9B,SAAS,EAAC,gFAAgF,YAEzF,eAAe,CAAC,QAAQ,CAAC,YAAY,CAAC,GACjC,CACT,CAAA;AACH,CAAC;AAED,iDAAiD;AACjD,MAAM,UAAU,aAAa,CAAC,QAA2B;IACvD,OAAO,WAAW,eAAe,CAAC,QAAQ,CAAC,YAAY,CAAC,oBAAoB,eAAe,CACzF,QAAQ,CAAC,eAAe,CACzB,sCAAsC,CAAA;AACzC,CAAC;AAED,SAAS,cAAc,CAAC,EAAE,KAAK,EAAE,KAAK,EAAuC;IAC3E,QAAQ,KAAK,CAAC,IAAI,EAAE,CAAC;QACnB,KAAK,SAAS;YACZ,OAAO,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,CACtB,KAAC,KAAK,IAAC,SAAS,EAAC,qBAAqB,GAAG,CAC1C,CAAC,CAAC,CAAC,CACF,KAAC,KAAK,IAAC,SAAS,EAAC,mCAAmC,GAAG,CACxD,CAAA;QACH,KAAK,QAAQ;YACX,OAAO,KAAC,KAAK,IAAC,OAAO,EAAC,SAAS,YAAE,MAAM,CAAC,KAAK,CAAC,GAAS,CAAA;QACzD,KAAK,QAAQ;YACX,OAAO,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,kBAAE,MAAM,CAAC,KAAK,CAAC,GAAQ,CAAA;QACpD,KAAK,MAAM;YACT,OAAO,CACL,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,EAAC,IAAI,kBAC9B,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,GACtB,CACR,CAAA;QACH,KAAK,UAAU;YACb,OAAO,CACL,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,QAAQ,YAC1B,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,WAAW,GAChD,CACR,CAAA;QACH,KAAK,UAAU;YACb,OAAO,KAAC,YAAY,IAAC,KAAK,EAAE,KAAK,GAAI,CAAA;QACvC,KAAK,OAAO;YACV,OAAO,KAAC,SAAS,IAAC,KAAK,EAAE,KAAK,GAAI,CAAA;QACpC,KAAK,QAAQ;YACX,OAAO,KAAC,UAAU,IAAC,KAAK,EAAE,KAAK,GAAI,CAAA;QACrC,KAAK,QAAQ,CAAC,CAAC,CAAC;YACd,2EAA2E;YAC3E,0EAA0E;YAC1E,IAAI,KAAK,CAAC,WAAW,KAAK,MAAM,EAAE,CAAC;gBACjC,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAA;gBACrD,OAAO,CACL,MAAC,KAAK,IAAC,OAAO,EAAC,SAAS,aACrB,KAAK,OAAG,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,IACjC,CACT,CAAA;YACH,CAAC;YACD,OAAO,CACL,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,QAAQ,YAC1B,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,GAC/C,CACR,CAAA;QACH,CAAC;QACD;YACE,OAAO,CACL,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,SAAS,EAAC,UAAU,YACjC,MAAM,CAAC,KAAK,CAAC,GACT,CACR,CAAA;IACL,CAAC;AACH,CAAC;AAED,SAAS,YAAY,CAAC,EAAE,KAAK,EAAsB;IACjD,MAAM,GAAG,GAAG,MAAM,EAAE,CAAA;IACpB,MAAM,IAAI,GAAG,UAAU,CAAC,KAAK,CAAC,CAAA;IAC9B,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,KAAC,KAAK,IAAC,SAAS,EAAC,mCAAmC,GAAG,CAAA;IACrF,OAAO,CACL,eAAK,SAAS,EAAC,mCAAmC,aAC/C,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,CAChC,MAAC,KAAK,IAAwB,OAAO,EAAC,MAAM,aAC1C,KAAC,KAAK,IAAC,SAAS,EAAC,QAAQ,GAAG,OAAE,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,KADpC,GAAG,GAAG,CAAC,EAAE,IAAI,CAAC,EAAE,CAEpB,CACT,CAAC,EACD,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,QAAQ,kBAAG,IAAI,CAAC,MAAM,GAAG,CAAC,IAAQ,CAAC,CAAC,CAAC,IAAI,IAC7E,CACP,CAAA;AACH,CAAC;AAED;;;GAGG;AACH,SAAS,UAAU,CAAC,EAAE,KAAK,EAAsB;IAC/C,MAAM,KAAK,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC,MAAM,CAAA;IACxC,IAAI,KAAK,KAAK,CAAC;QAAE,OAAO,KAAC,KAAK,IAAC,SAAS,EAAC,mCAAmC,GAAG,CAAA;IAC/E,OAAO,CACL,MAAC,KAAK,IAAC,OAAO,EAAC,SAAS,aACtB,KAAC,KAAK,IAAC,SAAS,EAAC,QAAQ,GAAG,OAAE,KAAK,OAAG,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,IAChE,CACT,CAAA;AACH,CAAC;AAED,SAAS,SAAS,CAAC,EAAE,KAAK,EAAsB;IAC9C,MAAM,GAAG,GAAG,MAAM,EAAE,CAAA;IACpB,MAAM,UAAU,GAAG,iBAAiB,EAAE,CAAA;IACtC,MAAM,iBAAiB,GAAG,GAAG,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,OAAO,CAAC,EAAE,EAAE,IAAI,OAAO,CAAA;IACxF,oFAAoF;IACpF,MAAM,GAAG,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;SACjD,GAAG,CAAC,WAAW,CAAC;SAChB,MAAM,CAAC,CAAC,EAAE,EAAgB,EAAE,CAAC,EAAE,KAAK,SAAS,CAAC,CAAA;IACjD,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,KAAC,KAAK,IAAC,SAAS,EAAC,mCAAmC,GAAG,CAAA;IACpF,MAAM,KAAK,GAAG,GAAG,CAAC,CAAC,CAAE,CAAA;IACrB,MAAM,MAAM,GAAG,UAAU,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,KAAK,CAAC,CAAA;IACrE,MAAM,KAAK,GACT,MAAM,EAAE,KAAK,IAAI,MAAM,EAAE,QAAQ,IAAI,GAAG,CAAC,OAAO,CAAC,EAAE,UAAU,EAAE,iBAAiB,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,CAAA;IAChG,OAAO,CACL,eAAK,SAAS,EAAC,qCAAqC,aAClD,KAAC,YAAY,IAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,GAAI,EAC7C,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,QAAQ,kBAAG,GAAG,CAAC,MAAM,GAAG,CAAC,IAAQ,CAAC,CAAC,CAAC,IAAI,IAC3E,CACP,CAAA;AACH,CAAC;AAED;;;;GAIG;AACH,SAAS,YAAY,CAAC,EAAE,MAAM,EAAE,KAAK,EAAsD;IACzF,MAAM,OAAO,GAAG,MAAM,EAAE,WAAW,CAAC,UAAU,CAAC,QAAQ,CAAC,IAAI,KAAK,CAAA;IACjE,MAAM,GAAG,GAAG,MAAM,EAAE,GAAG,CAAA;IACvB,IAAI,OAAO,IAAI,GAAG,IAAI,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;QAC7C,OAAO,CACL,gBAAM,SAAS,EAAC,0CAA0C,aACxD,cACE,GAAG,EAAE,GAAG,EACR,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI,KAAK,EACzB,OAAO,EAAC,MAAM,EACd,SAAS,EAAC,oEAAoE,GAC9E,EACF,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,QAAQ,EAAC,SAAS,EAAC,wBAAwB,YAC7D,KAAK,GACD,IACF,CACR,CAAA;IACH,CAAC;IACD,OAAO,CACL,MAAC,KAAK,IAAC,OAAO,EAAC,SAAS,aACtB,KAAC,KAAK,IAAC,SAAS,EAAC,QAAQ,GAAG,OAAE,KAAK,IAC7B,CACT,CAAA;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,UAAU,CAAC,KAAc;IACvC,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAA;IACnD,OAAO,IAAI,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,EAAsB,EAAE,CAAC,GAAG,KAAK,SAAS,CAAC,CAAA;AACzF,CAAC;AAED,SAAS,SAAS,CAAC,IAAY;IAC7B,OAAO,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAA;AAClE,CAAC","sourcesContent":["import { Boxes, Check, Image, Link2, Minus } from 'lucide-react'\nimport { readAssetId, readDocumentRef } from '@elytracms/core/cms-core'\nimport type { CmsDocument, CollectionDef, DocumentRef, FieldDef } from '@elytracms/core/cms-core'\nimport { blockRootsOf } from '@elytracms/core/project-graph'\nimport type { AssetRecord } from '@elytracms/core/persistence'\nimport { Badge, Text } from '@elytracms/studio/ui'\nimport { localeChipLabel, resolveDisplayedValue } from '@elytracms/studio/lib/locale'\nimport type { DisplayedFallback } from '@elytracms/studio/lib/locale'\nimport { useOptionalAssets } from '../assets/assets-store'\nimport { useCms } from './cms-store'\n\n/**\n * Render a document's field value for a table cell, by field type (brief\n * §8.4, EC-070), resolved in the studio's ambient active locale (EC-176).\n * A value served via EC-018 locale fallback carries a visible chip — never\n * silently mixed in as if it were translated.\n */\nexport function CellValue({\n collection,\n doc,\n field,\n}: {\n collection: CollectionDef\n doc: CmsDocument\n field: FieldDef\n}) {\n const cms = useCms()\n const { value, fallback } = resolveDisplayedValue(\n collection,\n doc,\n field.name,\n cms.activeLocale,\n cms.localeConfig,\n )\n\n if (value === undefined || value === null || value === '') {\n return <Minus className=\"size-3.5 text-muted-foreground/50\" />\n }\n\n return (\n <span className=\"inline-flex min-w-0 max-w-full items-center gap-1.5\">\n <TypedCellValue field={field} value={value} />\n <FallbackChip fallback={fallback} testId={`cell-fallback-${doc.id}-${field.name}`} />\n </span>\n )\n}\n\n/**\n * Subtle source-locale chip on a fallback-displayed value (EC-176): e.g. a\n * German view showing untranslated English content gets an \"EN\" marker.\n */\nexport function FallbackChip({\n fallback,\n testId,\n}: {\n fallback: DisplayedFallback | null\n testId: string\n}) {\n if (fallback === null) return null\n return (\n <Badge\n variant=\"outline\"\n data-testid={testId}\n title={fallbackTitle(fallback)}\n className=\"shrink-0 px-1 py-0 text-[0.6rem] uppercase tracking-wide text-muted-foreground\"\n >\n {localeChipLabel(fallback.sourceLocale)}\n </Badge>\n )\n}\n\n/** Plain-language tooltip of a fallback chip. */\nexport function fallbackTitle(fallback: DisplayedFallback): string {\n return `Showing ${localeChipLabel(fallback.sourceLocale)} (fallback) — no ${localeChipLabel(\n fallback.requestedLocale,\n )} translation yet. Edit to translate.`\n}\n\nfunction TypedCellValue({ field, value }: { field: FieldDef; value: unknown }) {\n switch (field.type) {\n case 'boolean':\n return value === true ? (\n <Check className=\"size-4 text-success\" />\n ) : (\n <Minus className=\"size-3.5 text-muted-foreground/50\" />\n )\n case 'select':\n return <Badge variant=\"outline\">{String(value)}</Badge>\n case 'number':\n return <Text size=\"sm\" mono>{String(value)}</Text>\n case 'date':\n return (\n <Text size=\"sm\" tone=\"muted\" mono>\n {String(value).slice(0, 10)}\n </Text>\n )\n case 'richText':\n return (\n <Text size=\"xs\" tone=\"subtle\">\n {stripHtml(String(value)).slice(0, 40) || 'rich text'}\n </Text>\n )\n case 'relation':\n return <RelationCell value={value} />\n case 'asset':\n return <AssetCell value={value} />\n case 'blocks':\n return <BlocksCell value={value} />\n case 'object': {\n // EC-253: list-view summary, never `[object Object]`. A repeater shows its\n // item count; a single object is edited in the detail form, not the cell.\n if (field.cardinality === 'many') {\n const count = Array.isArray(value) ? value.length : 0\n return (\n <Badge variant=\"outline\">\n {count} {count === 1 ? 'item' : 'items'}\n </Badge>\n )\n }\n return (\n <Text size=\"xs\" tone=\"subtle\">\n {value && typeof value === 'object' ? 'object' : '—'}\n </Text>\n )\n }\n default:\n return (\n <Text size=\"sm\" className=\"truncate\">\n {String(value)}\n </Text>\n )\n }\n}\n\nfunction RelationCell({ value }: { value: unknown }) {\n const cms = useCms()\n const refs = toRefArray(value)\n if (refs.length === 0) return <Minus className=\"size-3.5 text-muted-foreground/50\" />\n return (\n <div className=\"flex flex-wrap items-center gap-1\">\n {refs.slice(0, 3).map((ref, i) => (\n <Badge key={`${ref.id}-${i}`} variant=\"info\">\n <Link2 className=\"size-3\" /> {cms.titleOf(ref)}\n </Badge>\n ))}\n {refs.length > 3 ? <Text size=\"xs\" tone=\"subtle\">+{refs.length - 3}</Text> : null}\n </div>\n )\n}\n\n/**\n * A blocks/composition cell: a count of TOP-LEVEL blocks (EC-212) — matching\n * what the canvas/page-tree present as the block list, never `[object Object]`.\n */\nfunction BlocksCell({ value }: { value: unknown }) {\n const count = blockRootsOf(value).length\n if (count === 0) return <Minus className=\"size-3.5 text-muted-foreground/50\" />\n return (\n <Badge variant=\"outline\">\n <Boxes className=\"size-3\" /> {count} {count === 1 ? 'block' : 'blocks'}\n </Badge>\n )\n}\n\nfunction AssetCell({ value }: { value: unknown }) {\n const cms = useCms()\n const assetStore = useOptionalAssets()\n const assetCollectionId = cms.collections.find((c) => c.kind === 'asset')?.id ?? 'asset'\n // EC-277: tolerate self-typed `{ type:'asset', id }` envelopes and legacy bare ids.\n const ids = (Array.isArray(value) ? value : [value])\n .map(readAssetId)\n .filter((id): id is string => id !== undefined)\n if (ids.length === 0) return <Minus className=\"size-3.5 text-muted-foreground/50\" />\n const first = ids[0]!\n const record = assetStore?.assets.find((asset) => asset.id === first)\n const label =\n record?.title ?? record?.filename ?? cms.titleOf({ collection: assetCollectionId, id: first })\n return (\n <div className=\"flex flex-wrap items-center gap-1.5\">\n <AssetPreview record={record} label={label} />\n {ids.length > 1 ? <Text size=\"xs\" tone=\"subtle\">+{ids.length - 1}</Text> : null}\n </div>\n )\n}\n\n/**\n * An asset reference in a cell: an image thumbnail for image assets with a\n * resolvable URL (EC-212), otherwise the labelled icon badge (and the same\n * badge whenever no `<AssetsProvider>` is mounted to resolve the record).\n */\nfunction AssetPreview({ record, label }: { record: AssetRecord | undefined; label: string }) {\n const isImage = record?.contentType.startsWith('image/') ?? false\n const url = record?.url\n if (isImage && url && url.startsWith('http')) {\n return (\n <span className=\"inline-flex min-w-0 items-center gap-1.5\">\n <img\n src={url}\n alt={record?.alt ?? label}\n loading=\"lazy\"\n className=\"size-7 shrink-0 rounded border border-border bg-muted object-cover\"\n />\n <Text size=\"xs\" tone=\"subtle\" className=\"max-w-[10rem] truncate\">\n {label}\n </Text>\n </span>\n )\n }\n return (\n <Badge variant=\"outline\">\n <Image className=\"size-3\" /> {label}\n </Badge>\n )\n}\n\n/**\n * Normalize a relation value (single ref or array of refs) into an array of\n * `DocumentRef`s. EC-277: unwraps self-typed `{ type:'reference', ... }` envelopes\n * and legacy bare `{ collection, id }` via the shared tolerant reader.\n */\nexport function toRefArray(value: unknown): DocumentRef[] {\n const list = Array.isArray(value) ? value : [value]\n return list.map(readDocumentRef).filter((ref): ref is DocumentRef => ref !== undefined)\n}\n\nfunction stripHtml(html: string): string {\n return html.replace(/<[^>]*>/g, ' ').replace(/\\s+/g, ' ').trim()\n}\n"]}
|
|
1
|
+
{"version":3,"file":"cell.js","sourceRoot":"","sources":["../../../src/components/cms/cell.tsx"],"names":[],"mappings":"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","sourcesContent":["import { Boxes, Check, Image, Link2, Minus } from 'lucide-react'\nimport { readAssetId, readDocumentRef } from '@elytracms/core/cms-core'\nimport type { CmsDocument, CollectionDef, DocumentRef, FieldDef } from '@elytracms/core/cms-core'\nimport { blockRootsOf } from '@elytracms/core/project-graph'\nimport { Badge, Text } from '@elytracms/studio/ui'\nimport { localeChipLabel, resolveDisplayedValue } from '@elytracms/studio/lib/locale'\nimport type { DisplayedFallback } from '@elytracms/studio/lib/locale'\nimport type { ResolvedAsset } from '@elytracms/studio/lib/collection-view/collection-view-source'\nimport { useCollectionView } from '../collection-view/collection-view-context'\n\n/**\n * Render a document's field value for a table cell, by field type (brief\n * §8.4, EC-070), resolved in the studio's ambient active locale (EC-176).\n * A value served via EC-018 locale fallback carries a visible chip — never\n * silently mixed in as if it were translated.\n */\nexport function CellValue({\n collection,\n doc,\n field,\n}: {\n collection: CollectionDef\n doc: CmsDocument\n field: FieldDef\n}) {\n const source = useCollectionView()\n const { value, fallback } = resolveDisplayedValue(\n collection,\n doc,\n field.name,\n source.activeLocale,\n source.localeConfig,\n )\n\n if (value === undefined || value === null || value === '') {\n return <Minus className=\"size-3.5 text-muted-foreground/50\" />\n }\n\n return (\n <span className=\"inline-flex min-w-0 max-w-full items-center gap-1.5\">\n <TypedCellValue field={field} value={value} />\n <FallbackChip fallback={fallback} testId={`cell-fallback-${doc.id}-${field.name}`} />\n </span>\n )\n}\n\n/**\n * Subtle source-locale chip on a fallback-displayed value (EC-176): e.g. a\n * German view showing untranslated English content gets an \"EN\" marker.\n */\nexport function FallbackChip({\n fallback,\n testId,\n}: {\n fallback: DisplayedFallback | null\n testId: string\n}) {\n if (fallback === null) return null\n return (\n <Badge\n variant=\"outline\"\n data-testid={testId}\n title={fallbackTitle(fallback)}\n className=\"shrink-0 px-1 py-0 text-[0.6rem] uppercase tracking-wide text-muted-foreground\"\n >\n {localeChipLabel(fallback.sourceLocale)}\n </Badge>\n )\n}\n\n/** Plain-language tooltip of a fallback chip. */\nexport function fallbackTitle(fallback: DisplayedFallback): string {\n return `Showing ${localeChipLabel(fallback.sourceLocale)} (fallback) — no ${localeChipLabel(\n fallback.requestedLocale,\n )} translation yet. Edit to translate.`\n}\n\nfunction TypedCellValue({ field, value }: { field: FieldDef; value: unknown }) {\n switch (field.type) {\n case 'boolean':\n return value === true ? (\n <Check className=\"size-4 text-success\" />\n ) : (\n <Minus className=\"size-3.5 text-muted-foreground/50\" />\n )\n case 'select':\n return <Badge variant=\"outline\">{String(value)}</Badge>\n case 'number':\n return <Text size=\"sm\" mono>{String(value)}</Text>\n case 'date':\n return (\n <Text size=\"sm\" tone=\"muted\" mono>\n {String(value).slice(0, 10)}\n </Text>\n )\n case 'richText':\n return (\n <Text size=\"xs\" tone=\"subtle\">\n {stripHtml(String(value)).slice(0, 40) || 'rich text'}\n </Text>\n )\n case 'relation':\n return <RelationCell value={value} />\n case 'asset':\n return <AssetCell value={value} />\n case 'blocks':\n return <BlocksCell value={value} />\n case 'object': {\n // EC-253: list-view summary, never `[object Object]`. A repeater shows its\n // item count; a single object is edited in the detail form, not the cell.\n if (field.cardinality === 'many') {\n const count = Array.isArray(value) ? value.length : 0\n return (\n <Badge variant=\"outline\">\n {count} {count === 1 ? 'item' : 'items'}\n </Badge>\n )\n }\n return (\n <Text size=\"xs\" tone=\"subtle\">\n {value && typeof value === 'object' ? 'object' : '—'}\n </Text>\n )\n }\n default:\n return (\n <Text size=\"sm\" className=\"truncate\">\n {String(value)}\n </Text>\n )\n }\n}\n\nfunction RelationCell({ value }: { value: unknown }) {\n const source = useCollectionView()\n const refs = toRefArray(value)\n if (refs.length === 0) return <Minus className=\"size-3.5 text-muted-foreground/50\" />\n // EC-246c: calm de-pilled read — a single muted link glyph + the title(s), not\n // a row of info pills. Matches the relation FIELD's row treatment; the pill was\n // a design cue the list doesn't need.\n return (\n <span className=\"inline-flex min-w-0 items-center gap-1.5 text-sm\">\n <Link2 className=\"size-3.5 shrink-0 text-muted-foreground\" />\n <span className=\"min-w-0 truncate\">\n {refs.slice(0, 3).map((ref) => source.titleOf(ref)).join(', ')}\n </span>\n {refs.length > 3 ? (\n <Text size=\"xs\" tone=\"subtle\" className=\"shrink-0\">\n +{refs.length - 3}\n </Text>\n ) : null}\n </span>\n )\n}\n\n/**\n * A blocks/composition cell: a count of TOP-LEVEL blocks (EC-212) — matching\n * what the canvas/page-tree present as the block list, never `[object Object]`.\n */\nfunction BlocksCell({ value }: { value: unknown }) {\n const count = blockRootsOf(value).length\n if (count === 0) return <Minus className=\"size-3.5 text-muted-foreground/50\" />\n return (\n <Badge variant=\"outline\">\n <Boxes className=\"size-3\" /> {count} {count === 1 ? 'block' : 'blocks'}\n </Badge>\n )\n}\n\nfunction AssetCell({ value }: { value: unknown }) {\n const source = useCollectionView()\n const assetCollectionId = source.assetCollectionId ?? 'asset'\n // EC-277: tolerate self-typed `{ type:'asset', id }` envelopes and legacy bare ids.\n const ids = (Array.isArray(value) ? value : [value])\n .map(readAssetId)\n .filter((id): id is string => id !== undefined)\n if (ids.length === 0) return <Minus className=\"size-3.5 text-muted-foreground/50\" />\n const first = ids[0]!\n // AD-19: the source resolves the asset record (or nothing, when the rail has no\n // asset backend — degrading to the labelled badge, as with no `<AssetsProvider>`).\n const record = source.resolveAsset?.(first)\n const label =\n record?.title ?? record?.filename ?? source.titleOf({ collection: assetCollectionId, id: first })\n return (\n <div className=\"flex flex-wrap items-center gap-1.5\">\n <AssetPreview record={record} label={label} />\n {ids.length > 1 ? <Text size=\"xs\" tone=\"subtle\">+{ids.length - 1}</Text> : null}\n </div>\n )\n}\n\n/**\n * An asset reference in a cell: an image thumbnail for image assets with a\n * resolvable URL (EC-212), otherwise the labelled icon badge (and the same\n * badge whenever the source can't resolve the record — e.g. no asset backend).\n */\nfunction AssetPreview({ record, label }: { record: ResolvedAsset | undefined; label: string }) {\n const isImage = record?.contentType?.startsWith('image/') ?? false\n const url = record?.url\n if (isImage && url && url.startsWith('http')) {\n return (\n <span className=\"inline-flex min-w-0 items-center gap-1.5\">\n <img\n src={url}\n alt={record?.alt ?? label}\n loading=\"lazy\"\n className=\"size-7 shrink-0 rounded border border-border bg-muted object-cover\"\n />\n <Text size=\"xs\" tone=\"subtle\" className=\"max-w-[10rem] truncate\">\n {label}\n </Text>\n </span>\n )\n }\n return (\n <Badge variant=\"outline\">\n <Image className=\"size-3\" /> {label}\n </Badge>\n )\n}\n\n/**\n * Normalize a relation value (single ref or array of refs) into an array of\n * `DocumentRef`s. EC-277: unwraps self-typed `{ type:'reference', ... }` envelopes\n * and legacy bare `{ collection, id }` via the shared tolerant reader.\n */\nexport function toRefArray(value: unknown): DocumentRef[] {\n const list = Array.isArray(value) ? value : [value]\n return list.map(readDocumentRef).filter((ref): ref is DocumentRef => ref !== undefined)\n}\n\nfunction stripHtml(html: string): string {\n return html.replace(/<[^>]*>/g, ' ').replace(/\\s+/g, ' ').trim()\n}\n"]}
|
|
@@ -1,29 +1,22 @@
|
|
|
1
1
|
import type { ReactNode } from 'react';
|
|
2
|
-
import type { CmsDocument, CmsValidationIssue, CollectionDef, DocumentRef, FieldDef, Locale, LocaleConfig } from '@elytracms/core/cms-core';
|
|
2
|
+
import type { CmsDocument, CmsValidationIssue, CollectionDef, DocumentRef, FieldDef, Locale, LocaleConfig, RestoreScope } from '@elytracms/core/cms-core';
|
|
3
3
|
import type { DocumentVersionRecord } from '@elytracms/core/persistence';
|
|
4
4
|
import type { OperationError } from '@elytracms/studio/lib/operations';
|
|
5
5
|
import type { TableViewState } from '@elytracms/studio/lib/cms-views';
|
|
6
6
|
import type { CellCommitInput, CellCommitOutcome } from './cell-edit';
|
|
7
7
|
import type { BulkAction, BulkReport, BulkSetField } from './bulk-actions';
|
|
8
8
|
/**
|
|
9
|
-
* A
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* per collection
|
|
9
|
+
* A resolved table view: a named bundle of filters (closed AD-3 vocabulary),
|
|
10
|
+
* sort, and visible/ordered columns. EC-246b: named views are **config-as-code**
|
|
11
|
+
* (declared in `cms/`, read live via `configViewsFor`) — the editor-owned DB
|
|
12
|
+
* saved-view store (EC-169) is retired. The unnamed "All documents" default is
|
|
13
|
+
* derived per collection; editors keep only transient in-session tweaks + Reset.
|
|
14
14
|
*/
|
|
15
15
|
export interface SavedView {
|
|
16
16
|
id: string;
|
|
17
17
|
name: string;
|
|
18
18
|
state: TableViewState;
|
|
19
19
|
}
|
|
20
|
-
/**
|
|
21
|
-
* Mint a collision-safe, slug-derived page id (EC-192): the slug of the page name,
|
|
22
|
-
* suffixed `-2`/`-3`/… until it is free among the ids already taken. Empty or
|
|
23
|
-
* symbol-only names fall back to `page`. Pure so the id rule is testable without
|
|
24
|
-
* the store; `createPage` applies it against the live page documents.
|
|
25
|
-
*/
|
|
26
|
-
export declare function nextPageId(name: string, taken: ReadonlySet<string>): string;
|
|
27
20
|
export interface ImportReport {
|
|
28
21
|
added: number;
|
|
29
22
|
rejected: {
|
|
@@ -65,10 +58,6 @@ export interface CmsStore {
|
|
|
65
58
|
*/
|
|
66
59
|
commitFieldEdit(input: CellCommitInput): Promise<CellCommitOutcome>;
|
|
67
60
|
createDocument(collectionId: string): DocumentRef;
|
|
68
|
-
/** Create a named page (`page` collection) with a stable, slug-derived id. */
|
|
69
|
-
createPage(input: {
|
|
70
|
-
name: string;
|
|
71
|
-
}): DocumentRef;
|
|
72
61
|
/**
|
|
73
62
|
* Ensure a singleton collection's lone document exists and return its ref
|
|
74
63
|
* (EC-274). Resolves the existing single document, or creates one with a normal
|
|
@@ -105,10 +94,12 @@ export interface CmsStore {
|
|
|
105
94
|
/**
|
|
106
95
|
* Restore a recorded version as the new working draft (EC-224): the operation
|
|
107
96
|
* snapshots the current draft first (so the rollback is itself undoable), then
|
|
108
|
-
* loads the chosen snapshot.
|
|
109
|
-
*
|
|
97
|
+
* loads the chosen snapshot. An optional `scope` (EC-298) restores only one
|
|
98
|
+
* field or block, laid over the current draft, instead of the whole document.
|
|
99
|
+
* Returns the restored draft (or null on failure/fixture session) so the open
|
|
100
|
+
* editor can adopt it.
|
|
110
101
|
*/
|
|
111
|
-
restoreDocumentVersion(ref: DocumentRef, version: number): Promise<CmsDocument | null>;
|
|
102
|
+
restoreDocumentVersion(ref: DocumentRef, version: number, scope?: RestoreScope): Promise<CmsDocument | null>;
|
|
112
103
|
importDocuments(collectionId: string, docs: unknown[]): ImportReport;
|
|
113
104
|
validate(doc: CmsDocument): CmsValidationIssue[];
|
|
114
105
|
isFieldReadOnly(field: FieldDef): boolean;
|
|
@@ -127,12 +118,6 @@ export interface CmsStore {
|
|
|
127
118
|
isViewDirty(collectionId: string): boolean;
|
|
128
119
|
/** Discard working-state edits, back to the active view. */
|
|
129
120
|
resetTableState(collectionId: string): void;
|
|
130
|
-
/** Persist the working state into the active named view. False on rejection. */
|
|
131
|
-
saveActiveView(collectionId: string): Promise<boolean>;
|
|
132
|
-
/** Persist the working state as a new named view and activate it. */
|
|
133
|
-
saveViewAs(collectionId: string, name: string): Promise<boolean>;
|
|
134
|
-
/** Delete a named view — a view-only operation; documents are never touched. */
|
|
135
|
-
deleteView(collectionId: string, viewId: string): Promise<boolean>;
|
|
136
121
|
/**
|
|
137
122
|
* Bulk publish/unpublish/delete (EC-169) + set-field (EC-247): one operations
|
|
138
123
|
* command PER item, per-item outcomes in the report — partial failure is
|
|
@@ -5,25 +5,12 @@ import { validateCompositionValue } from '@elytracms/core/project-graph';
|
|
|
5
5
|
import { collectComponentEmbeds, parseRichTextValue } from '@elytracms/core/rich-text';
|
|
6
6
|
import { configureStudioLocales, useStudioLocale } from '@elytracms/studio/lib/locale';
|
|
7
7
|
import { getStudioRegistry, studioManifestLookup } from '@elytracms/studio/lib/registry';
|
|
8
|
-
import {
|
|
8
|
+
import { resolveDeploymentCollections } from '@elytracms/studio/lib/workspace';
|
|
9
9
|
import { createCmsPersister, getStudioOperations } from '@elytracms/studio/lib/operations';
|
|
10
|
-
import { ALL_VIEW_ID, canonicalViewState,
|
|
10
|
+
import { ALL_VIEW_ID, canonicalViewState, configViewsFor, defaultViewState, viewStatesEqual, } from '@elytracms/studio/lib/cms-views';
|
|
11
11
|
import { useOptionalAssets } from '../assets/assets-store';
|
|
12
12
|
import { applyCellValue, commitCellEdit } from './cell-edit';
|
|
13
13
|
import { runBulkAction } from './bulk-actions';
|
|
14
|
-
/**
|
|
15
|
-
* Mint a collision-safe, slug-derived page id (EC-192): the slug of the page name,
|
|
16
|
-
* suffixed `-2`/`-3`/… until it is free among the ids already taken. Empty or
|
|
17
|
-
* symbol-only names fall back to `page`. Pure so the id rule is testable without
|
|
18
|
-
* the store; `createPage` applies it against the live page documents.
|
|
19
|
-
*/
|
|
20
|
-
export function nextPageId(name, taken) {
|
|
21
|
-
const base = slugify(name) || 'page';
|
|
22
|
-
let id = base;
|
|
23
|
-
for (let n = 2; taken.has(id); n += 1)
|
|
24
|
-
id = `${base}-${n}`;
|
|
25
|
-
return id;
|
|
26
|
-
}
|
|
27
14
|
/**
|
|
28
15
|
* EC-188: validate a `blocks` composition field value against the field's `allow`
|
|
29
16
|
* vocabulary + the code-owned component registry (the leash), mapping graph
|
|
@@ -124,9 +111,6 @@ export function CmsProvider({ children, projectId, initialCollections, initialDo
|
|
|
124
111
|
return new Set(initialPublishedKeys);
|
|
125
112
|
return new Set(initialDocuments ? [] : samplePublishedKeys);
|
|
126
113
|
});
|
|
127
|
-
// Named saved views per collection (EC-169): loaded from the operations
|
|
128
|
-
// layer (project-scoped, shared across editors) and mutated through it.
|
|
129
|
-
const [savedViews, setSavedViews] = useState({});
|
|
130
114
|
const [activeViews, setActiveViews] = useState({});
|
|
131
115
|
// The live table state per collection where it diverges from the active
|
|
132
116
|
// view; absent key = exactly the active view's state (not dirty).
|
|
@@ -260,26 +244,6 @@ export function CmsProvider({ children, projectId, initialCollections, initialDo
|
|
|
260
244
|
persisterRef.current?.upsertDocument(doc);
|
|
261
245
|
return { collection: collectionId, id };
|
|
262
246
|
}, [registry]);
|
|
263
|
-
// EC-192: create a named page document with a stable, slug-derived id (not an
|
|
264
|
-
// opaque `new-page-N`), so the route bound to it reads as a real address and
|
|
265
|
-
// the id survives as the canonical key. Collision-safe against existing page
|
|
266
|
-
// ids. The route itself is written by the caller (NewPageDialog) through the
|
|
267
|
-
// operations layer, so the two writes stay in their own concerns.
|
|
268
|
-
const createPage = useCallback((input) => {
|
|
269
|
-
const collectionId = 'page';
|
|
270
|
-
const taken = new Set(documentsRef.current.filter((d) => d.collection === collectionId).map((d) => d.id));
|
|
271
|
-
const id = nextPageId(input.name, taken);
|
|
272
|
-
// EC-297: seed the page collection's defaults, then the given name.
|
|
273
|
-
const collection = registry.get(collectionId);
|
|
274
|
-
const doc = {
|
|
275
|
-
collection: collectionId,
|
|
276
|
-
id,
|
|
277
|
-
values: { ...(collection ? seedDefaults(collection) : {}), name: input.name },
|
|
278
|
-
};
|
|
279
|
-
setDocuments((prev) => [...prev, doc]);
|
|
280
|
-
persisterRef.current?.upsertDocument(doc);
|
|
281
|
-
return { collection: collectionId, id };
|
|
282
|
-
}, [registry]);
|
|
283
247
|
// EC-274 (re-scopes EC-217): a singleton is a UI lens, not a fixed id. Resolve
|
|
284
248
|
// the collection's lone document and return its ref; if none exists yet, create
|
|
285
249
|
// one with a NORMAL generated id (never the collection id — no code ever names a
|
|
@@ -391,14 +355,20 @@ export function CmsProvider({ children, projectId, initialCollections, initialDo
|
|
|
391
355
|
...(publishedVersion !== undefined ? { publishedVersion } : {}),
|
|
392
356
|
};
|
|
393
357
|
}, [projectId]);
|
|
394
|
-
const restoreDocumentVersion = useCallback(async (ref, version) => {
|
|
358
|
+
const restoreDocumentVersion = useCallback(async (ref, version, scope) => {
|
|
395
359
|
if (projectId === undefined)
|
|
396
360
|
return null;
|
|
397
361
|
// Land any queued edits before the restore reads the current draft to
|
|
398
|
-
// snapshot it (same ordering contract as commitFieldEdit/runBulk).
|
|
362
|
+
// snapshot it (same ordering contract as commitFieldEdit/runBulk). This is
|
|
363
|
+
// what makes a granular (field/block) restore merge over the LATEST draft.
|
|
399
364
|
await persisterRef.current?.ensureBaseline();
|
|
400
365
|
const { operations } = await getStudioOperations();
|
|
401
|
-
const result = await operations.documents.restore({
|
|
366
|
+
const result = await operations.documents.restore({
|
|
367
|
+
projectId,
|
|
368
|
+
ref,
|
|
369
|
+
version,
|
|
370
|
+
...(scope ? { scope } : {}),
|
|
371
|
+
});
|
|
402
372
|
if (!result.ok) {
|
|
403
373
|
setPersistError(result.error);
|
|
404
374
|
return null;
|
|
@@ -476,41 +446,20 @@ export function CmsProvider({ children, projectId, initialCollections, initialDo
|
|
|
476
446
|
return report;
|
|
477
447
|
}, [registry, documents, localeConfig, assetExists]);
|
|
478
448
|
const isFieldReadOnly = useCallback((field) => field.form?.readOnly === true, []);
|
|
479
|
-
// ---
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
return;
|
|
494
|
-
}
|
|
495
|
-
const grouped = {};
|
|
496
|
-
for (const record of result.value) {
|
|
497
|
-
;
|
|
498
|
-
(grouped[record.collection] ??= []).push({
|
|
499
|
-
id: record.id,
|
|
500
|
-
name: record.name,
|
|
501
|
-
state: record.state,
|
|
502
|
-
});
|
|
503
|
-
}
|
|
504
|
-
setSavedViews(grouped);
|
|
505
|
-
});
|
|
506
|
-
return () => {
|
|
507
|
-
cancelled = true;
|
|
508
|
-
};
|
|
509
|
-
}, [projectId]);
|
|
510
|
-
const viewsOf = useCallback((collectionId) => [
|
|
511
|
-
defaultView(registry.get(collectionId)),
|
|
512
|
-
...(savedViews[collectionId] ?? []),
|
|
513
|
-
], [registry, savedViews]);
|
|
449
|
+
// --- Views (EC-246b: config-as-code) --------------------------------------
|
|
450
|
+
const viewsOf = useCallback((collectionId) => {
|
|
451
|
+
// The built-in default plus the collection's config-declared views (EC-246b).
|
|
452
|
+
// Views are PRESENTATION, read live from elytra.config.ts (AD-11) — not the
|
|
453
|
+
// persisted content schema the session booted from — so they reflect code
|
|
454
|
+
// immediately without a re-seed (the persisted schema is only baselined on
|
|
455
|
+
// first run). The EC-169 DB saved-view store is retired: shared views are
|
|
456
|
+
// code, editors keep only transient in-session tweaks + Reset.
|
|
457
|
+
const configCollection = resolveDeploymentCollections().find((c) => c.id === collectionId);
|
|
458
|
+
return [
|
|
459
|
+
defaultView(registry.get(collectionId)),
|
|
460
|
+
...(configCollection ? configViewsFor(configCollection) : []),
|
|
461
|
+
];
|
|
462
|
+
}, [registry]);
|
|
514
463
|
const activeViewOf = useCallback((collectionId) => {
|
|
515
464
|
const id = activeViews[collectionId] ?? ALL_VIEW_ID;
|
|
516
465
|
return (viewsOf(collectionId).find((view) => view.id === id) ??
|
|
@@ -530,82 +479,6 @@ export function CmsProvider({ children, projectId, initialCollections, initialDo
|
|
|
530
479
|
setActiveViews((prev) => ({ ...prev, [collectionId]: viewId }));
|
|
531
480
|
dropTableState(collectionId);
|
|
532
481
|
}, [dropTableState]);
|
|
533
|
-
/**
|
|
534
|
-
* Run one persisted view mutation: pending content mutations + the persisted
|
|
535
|
-
* baseline land first (so the system collection never pre-empts the content
|
|
536
|
-
* schema), then exactly one operations command. Failures surface as
|
|
537
|
-
* `persistError` and report `false` — the session is only updated on success.
|
|
538
|
-
*/
|
|
539
|
-
const dispatchViewMutation = useCallback(async (run) => {
|
|
540
|
-
if (projectId === undefined)
|
|
541
|
-
return true; // fixture-only session: views stay session-local
|
|
542
|
-
await persisterRef.current?.ensureBaseline();
|
|
543
|
-
const { operations } = await getStudioOperations();
|
|
544
|
-
const result = await run(operations, projectId);
|
|
545
|
-
if (!result.ok) {
|
|
546
|
-
setPersistError(result.error);
|
|
547
|
-
return false;
|
|
548
|
-
}
|
|
549
|
-
return true;
|
|
550
|
-
}, [projectId]);
|
|
551
|
-
const saveViewAs = useCallback(async (collectionId, name) => {
|
|
552
|
-
const trimmed = name.trim();
|
|
553
|
-
if (trimmed === '')
|
|
554
|
-
return false;
|
|
555
|
-
const id = newViewId();
|
|
556
|
-
const state = normalizeViewState(tableStateOf(collectionId));
|
|
557
|
-
const ok = await dispatchViewMutation((operations, pid) => saveSavedView(operations, pid, { id, name: trimmed, collection: collectionId, state }));
|
|
558
|
-
if (!ok)
|
|
559
|
-
return false;
|
|
560
|
-
setSavedViews((prev) => ({
|
|
561
|
-
...prev,
|
|
562
|
-
[collectionId]: [...(prev[collectionId] ?? []), { id, name: trimmed, state }],
|
|
563
|
-
}));
|
|
564
|
-
setActiveViews((prev) => ({ ...prev, [collectionId]: id }));
|
|
565
|
-
dropTableState(collectionId);
|
|
566
|
-
return true;
|
|
567
|
-
}, [dispatchViewMutation, tableStateOf, dropTableState]);
|
|
568
|
-
const saveActiveView = useCallback(async (collectionId) => {
|
|
569
|
-
const activeId = activeViews[collectionId] ?? ALL_VIEW_ID;
|
|
570
|
-
if (activeId === ALL_VIEW_ID)
|
|
571
|
-
return false; // the default view is not persistable
|
|
572
|
-
const current = (savedViews[collectionId] ?? []).find((view) => view.id === activeId);
|
|
573
|
-
if (!current)
|
|
574
|
-
return false;
|
|
575
|
-
const state = normalizeViewState(tableStateOf(collectionId));
|
|
576
|
-
const ok = await dispatchViewMutation((operations, pid) => saveSavedView(operations, pid, {
|
|
577
|
-
id: activeId,
|
|
578
|
-
name: current.name,
|
|
579
|
-
collection: collectionId,
|
|
580
|
-
state,
|
|
581
|
-
}));
|
|
582
|
-
if (!ok)
|
|
583
|
-
return false;
|
|
584
|
-
setSavedViews((prev) => ({
|
|
585
|
-
...prev,
|
|
586
|
-
[collectionId]: (prev[collectionId] ?? []).map((view) => view.id === activeId ? { ...view, state } : view),
|
|
587
|
-
}));
|
|
588
|
-
dropTableState(collectionId);
|
|
589
|
-
return true;
|
|
590
|
-
}, [activeViews, savedViews, dispatchViewMutation, tableStateOf, dropTableState]);
|
|
591
|
-
const deleteView = useCallback(async (collectionId, viewId) => {
|
|
592
|
-
if (viewId === ALL_VIEW_ID)
|
|
593
|
-
return false;
|
|
594
|
-
// One documents.delete against the views system collection — content
|
|
595
|
-
// documents are untouched by construction.
|
|
596
|
-
const ok = await dispatchViewMutation((operations, pid) => deleteSavedView(operations, pid, viewId));
|
|
597
|
-
if (!ok)
|
|
598
|
-
return false;
|
|
599
|
-
setSavedViews((prev) => ({
|
|
600
|
-
...prev,
|
|
601
|
-
[collectionId]: (prev[collectionId] ?? []).filter((view) => view.id !== viewId),
|
|
602
|
-
}));
|
|
603
|
-
if ((activeViews[collectionId] ?? ALL_VIEW_ID) === viewId) {
|
|
604
|
-
setActiveViews((prev) => ({ ...prev, [collectionId]: ALL_VIEW_ID }));
|
|
605
|
-
dropTableState(collectionId);
|
|
606
|
-
}
|
|
607
|
-
return true;
|
|
608
|
-
}, [dispatchViewMutation, activeViews, dropTableState]);
|
|
609
482
|
// --- Bulk actions (EC-169) ------------------------------------------------
|
|
610
483
|
const runBulk = useCallback(async (collectionId, ids, action, setField) => {
|
|
611
484
|
const collection = registry.get(collectionId);
|
|
@@ -698,7 +571,6 @@ export function CmsProvider({ children, projectId, initialCollections, initialDo
|
|
|
698
571
|
saveDocument,
|
|
699
572
|
commitFieldEdit,
|
|
700
573
|
createDocument,
|
|
701
|
-
createPage,
|
|
702
574
|
ensureSingletonDocument,
|
|
703
575
|
deleteDocument,
|
|
704
576
|
isPublished,
|
|
@@ -731,9 +603,6 @@ export function CmsProvider({ children, projectId, initialCollections, initialDo
|
|
|
731
603
|
return !viewStatesEqual(canonicalViewState(collection, working), canonicalViewState(collection, active));
|
|
732
604
|
},
|
|
733
605
|
resetTableState: dropTableState,
|
|
734
|
-
saveActiveView,
|
|
735
|
-
saveViewAs,
|
|
736
|
-
deleteView,
|
|
737
606
|
runBulk,
|
|
738
607
|
addCollection: (def) => setCollections((prev) => [...prev.filter((c) => c.id !== def.id), def]),
|
|
739
608
|
persistError,
|
|
@@ -752,7 +621,6 @@ export function CmsProvider({ children, projectId, initialCollections, initialDo
|
|
|
752
621
|
saveDocument,
|
|
753
622
|
commitFieldEdit,
|
|
754
623
|
createDocument,
|
|
755
|
-
createPage,
|
|
756
624
|
ensureSingletonDocument,
|
|
757
625
|
deleteDocument,
|
|
758
626
|
isPublished,
|
|
@@ -770,9 +638,6 @@ export function CmsProvider({ children, projectId, initialCollections, initialDo
|
|
|
770
638
|
tableStateOf,
|
|
771
639
|
setActiveView,
|
|
772
640
|
dropTableState,
|
|
773
|
-
saveActiveView,
|
|
774
|
-
saveViewAs,
|
|
775
|
-
deleteView,
|
|
776
641
|
runBulk,
|
|
777
642
|
registry,
|
|
778
643
|
persistError,
|