@elytracms/studio 0.0.49 → 0.0.51
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/dist/components/cms/cell.js +48 -7
- package/dist/components/cms/cell.js.map +1 -1
- package/dist/components/cms/collection-table.js +9 -3
- package/dist/components/cms/collection-table.js.map +1 -1
- package/dist/components/cms/doc-state.d.ts +2 -1
- package/dist/components/cms/doc-state.js +4 -2
- package/dist/components/cms/doc-state.js.map +1 -1
- package/dist/components/cms/filter-builder.d.ts +9 -3
- package/dist/components/cms/filter-builder.js +87 -20
- package/dist/components/cms/filter-builder.js.map +1 -1
- package/dist/components/section-surface.d.ts +11 -2
- package/dist/components/section-surface.js +12 -3
- package/dist/components/section-surface.js.map +1 -1
- package/dist/lib/cms-views/view-model.d.ts +56 -6
- package/dist/lib/cms-views/view-model.js +111 -23
- package/dist/lib/cms-views/view-model.js.map +1 -1
- package/dist/lib/i18n/messages.en.js +25 -0
- package/dist/lib/i18n/messages.en.js.map +1 -1
- package/package.json +2 -2
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { documentDefaultLocale, localizedStringSchema, resolveField, resolveLabel, titleFieldOf, } from '@elytracms/core/cms-core';
|
|
3
3
|
import { localizedLabel } from '@elytracms/studio/lib/i18n';
|
|
4
|
-
import { FILTER_OPERATORS, compareListEntries, filterScalarOf, filterableFields,
|
|
4
|
+
import { FILTER_OPERATORS, compareListEntries, filterScalarOf, filterableFields, matchesFieldValue, validateWhere, } from '@elytracms/core/content';
|
|
5
5
|
/**
|
|
6
6
|
* The pure table-view model for the CMS collection table (EC-169).
|
|
7
7
|
*
|
|
@@ -76,20 +76,20 @@ export function filterableFieldOptions(collection) {
|
|
|
76
76
|
export function filterOptionFor(collection, field) {
|
|
77
77
|
return filterableFieldOptions(collection).find((option) => option.field === field);
|
|
78
78
|
}
|
|
79
|
-
// --- Operators per field type
|
|
80
|
-
/** Display order + labels for the closed vocabulary. */
|
|
81
|
-
export const OPERATOR_LABELS = {
|
|
82
|
-
eq: 'equals',
|
|
83
|
-
in: 'is one of',
|
|
84
|
-
lt: 'less than',
|
|
85
|
-
lte: 'at most',
|
|
86
|
-
gt: 'greater than',
|
|
87
|
-
gte: 'at least',
|
|
88
|
-
};
|
|
89
|
-
const ORDERED_OPERATORS = ['eq', 'in', 'lt', 'lte', 'gt', 'gte'];
|
|
79
|
+
// --- Operators per field type (EC-345) ------------------------------------------
|
|
90
80
|
/**
|
|
91
|
-
* The
|
|
92
|
-
*
|
|
81
|
+
* The per-type operator SET the builder offers (EC-345), always a subset of the
|
|
82
|
+
* closed `FILTER_OPERATORS` — the UI narrows the vocabulary, it never extends it.
|
|
83
|
+
* Ratified sets (Oli, 2026-07-08):
|
|
84
|
+
*
|
|
85
|
+
* - text: contains (default) · equals · starts with · ends with · empty
|
|
86
|
+
* - number: equals · < · ≤ · > · ≥ · empty
|
|
87
|
+
* - date: equals · before · … · after · empty (same ops as number, date labels)
|
|
88
|
+
* - boolean: is
|
|
89
|
+
* - select: is · is any of · empty
|
|
90
|
+
* - relation / asset: is · is any of · empty (relation value = the relation picker;
|
|
91
|
+
* `eq` on a `multiple` relation is membership — array-contains, EC-255 semantics)
|
|
92
|
+
* - state pseudo-field: is · is any of (unchanged — a publication state is never empty)
|
|
93
93
|
*/
|
|
94
94
|
export function operatorsFor(option) {
|
|
95
95
|
if (option.def === null)
|
|
@@ -97,15 +97,78 @@ export function operatorsFor(option) {
|
|
|
97
97
|
switch (option.def.type) {
|
|
98
98
|
case 'boolean':
|
|
99
99
|
return ['eq'];
|
|
100
|
+
case 'number':
|
|
101
|
+
case 'date':
|
|
102
|
+
return ['eq', 'lt', 'lte', 'gt', 'gte', 'empty'];
|
|
100
103
|
case 'select':
|
|
101
104
|
case 'relation':
|
|
102
105
|
case 'asset':
|
|
103
|
-
return ['eq', 'in'];
|
|
106
|
+
return ['eq', 'in', 'empty'];
|
|
104
107
|
default:
|
|
105
|
-
|
|
108
|
+
// text (and any other string-shaped field)
|
|
109
|
+
return ['contains', 'eq', 'startsWith', 'endsWith', 'empty'];
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* The operator dropdown entries for a field (EC-345): {@link operatorsFor} expanded
|
|
114
|
+
* for display, with `empty` shown as the two self-describing choices.
|
|
115
|
+
*/
|
|
116
|
+
export function operatorChoices(option) {
|
|
117
|
+
const choices = [];
|
|
118
|
+
for (const op of operatorsFor(option)) {
|
|
119
|
+
if (op === 'empty') {
|
|
120
|
+
choices.push({ token: 'empty:true', op, presetValue: true, labelKey: 'cms.filter.op.isEmpty' });
|
|
121
|
+
choices.push({
|
|
122
|
+
token: 'empty:false',
|
|
123
|
+
op,
|
|
124
|
+
presetValue: false,
|
|
125
|
+
labelKey: 'cms.filter.op.isNotEmpty',
|
|
126
|
+
});
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
choices.push({ token: op, op, labelKey: operatorLabelKey(op, option) });
|
|
130
|
+
}
|
|
131
|
+
return choices;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* The catalog key for an operator's label (EC-241/EC-345), keyed by operator AND
|
|
135
|
+
* field type — `eq` reads "equals" on text/number, "on" on a date, "is" on an
|
|
136
|
+
* enum-like field; `lt` reads "less than" on a number, "before" on a date. `empty`
|
|
137
|
+
* additionally depends on its VALUE (is empty / is not empty). Pure → unit-tested.
|
|
138
|
+
*/
|
|
139
|
+
export function operatorLabelKey(op, option, value) {
|
|
140
|
+
const type = option?.def?.type;
|
|
141
|
+
switch (op) {
|
|
142
|
+
case 'contains':
|
|
143
|
+
return 'cms.filter.op.contains';
|
|
144
|
+
case 'startsWith':
|
|
145
|
+
return 'cms.filter.op.startsWith';
|
|
146
|
+
case 'endsWith':
|
|
147
|
+
return 'cms.filter.op.endsWith';
|
|
148
|
+
case 'in':
|
|
149
|
+
return 'cms.filter.op.isAnyOf';
|
|
150
|
+
case 'empty':
|
|
151
|
+
return value === false ? 'cms.filter.op.isNotEmpty' : 'cms.filter.op.isEmpty';
|
|
152
|
+
case 'eq':
|
|
153
|
+
// "on" for a date; "is" for enum-like refs; "equals" for text/number/unknown
|
|
154
|
+
// (and the publication-state pseudo-field, whose `def` is null → unchanged).
|
|
155
|
+
if (type === 'date')
|
|
156
|
+
return 'cms.filter.op.on';
|
|
157
|
+
if (type === 'boolean' || type === 'select' || type === 'relation' || type === 'asset') {
|
|
158
|
+
return 'cms.filter.op.is';
|
|
159
|
+
}
|
|
160
|
+
return 'cms.filter.op.equals';
|
|
161
|
+
case 'lt':
|
|
162
|
+
return type === 'date' ? 'cms.filter.op.before' : 'cms.filter.op.lessThan';
|
|
163
|
+
case 'lte':
|
|
164
|
+
return type === 'date' ? 'cms.filter.op.onOrBefore' : 'cms.filter.op.atMost';
|
|
165
|
+
case 'gt':
|
|
166
|
+
return type === 'date' ? 'cms.filter.op.after' : 'cms.filter.op.greaterThan';
|
|
167
|
+
case 'gte':
|
|
168
|
+
return type === 'date' ? 'cms.filter.op.onOrAfter' : 'cms.filter.op.atLeast';
|
|
106
169
|
}
|
|
107
170
|
}
|
|
108
|
-
/** A sensible starting clause when a field is added to the builder. */
|
|
171
|
+
/** A sensible starting clause when a field is added to the builder (EC-345). */
|
|
109
172
|
export function defaultClause(option) {
|
|
110
173
|
if (option.def === null)
|
|
111
174
|
return { field: STATE_FIELD, op: 'eq', value: 'published' };
|
|
@@ -113,11 +176,16 @@ export function defaultClause(option) {
|
|
|
113
176
|
case 'boolean':
|
|
114
177
|
return { field: option.field, op: 'eq', value: true };
|
|
115
178
|
case 'number':
|
|
116
|
-
|
|
179
|
+
case 'date':
|
|
180
|
+
return { field: option.field, op: 'eq', value: option.def.type === 'number' ? 0 : '' };
|
|
117
181
|
case 'select':
|
|
118
182
|
return { field: option.field, op: 'eq', value: option.def.options[0]?.value ?? '' };
|
|
119
|
-
|
|
183
|
+
case 'relation':
|
|
184
|
+
case 'asset':
|
|
120
185
|
return { field: option.field, op: 'eq', value: '' };
|
|
186
|
+
default:
|
|
187
|
+
// text (and any other string-shaped field): `contains` is the natural default
|
|
188
|
+
return { field: option.field, op: 'contains', value: '' };
|
|
121
189
|
}
|
|
122
190
|
}
|
|
123
191
|
// --- Text ⇄ scalar coercion (the value inputs of the builder) -------------------
|
|
@@ -183,7 +251,22 @@ export function validateClauses(collection, clauses) {
|
|
|
183
251
|
return issues;
|
|
184
252
|
}
|
|
185
253
|
const defaultIsPublished = () => false;
|
|
186
|
-
/**
|
|
254
|
+
/**
|
|
255
|
+
* The RAW locale-resolved value a document exposes for a clause field — an array
|
|
256
|
+
* for a `multiple` relation/asset, a ref object for a single one, a scalar
|
|
257
|
+
* otherwise. Matching runs on this (via `matchesFieldValue`) so it agrees with the
|
|
258
|
+
* delivery/listing path exactly: array-contains for `multiple` relations (EC-255)
|
|
259
|
+
* and whole-value `empty` semantics (EC-345). Sorting keeps its own scalar reduction.
|
|
260
|
+
*/
|
|
261
|
+
function rawValueOf(collection, doc, field, localeConfig, isPublished) {
|
|
262
|
+
if (field === STATE_FIELD)
|
|
263
|
+
return isPublished(doc) ? 'published' : 'draft';
|
|
264
|
+
if (!collection.fields.some((f) => f.name === field))
|
|
265
|
+
return undefined;
|
|
266
|
+
const locale = documentDefaultLocale(doc, localeConfig);
|
|
267
|
+
return resolveField(collection, doc, field, locale, localeConfig).value;
|
|
268
|
+
}
|
|
269
|
+
/** The scalar a document exposes for a SORT field, locale-resolved. */
|
|
187
270
|
function scalarOf(collection, doc, field, localeConfig, isPublished) {
|
|
188
271
|
if (field === STATE_FIELD)
|
|
189
272
|
return isPublished(doc) ? 'published' : 'draft';
|
|
@@ -192,11 +275,16 @@ function scalarOf(collection, doc, field, localeConfig, isPublished) {
|
|
|
192
275
|
const locale = documentDefaultLocale(doc, localeConfig);
|
|
193
276
|
return filterScalarOf(resolveField(collection, doc, field, locale, localeConfig).value);
|
|
194
277
|
}
|
|
195
|
-
/**
|
|
278
|
+
/**
|
|
279
|
+
* Does a document satisfy one clause? Identical verdict to the content accessors'
|
|
280
|
+
* delivery/listing path: both reduce to `@elytracms/core/content`'s
|
|
281
|
+
* `matchesFieldValue` over the RAW field value (so `contains`/`empty`/multiple-
|
|
282
|
+
* relation membership all behave the same on the table as on the live site).
|
|
283
|
+
*/
|
|
196
284
|
export function clauseMatches(collection, doc, localeConfig, clause, isPublished = defaultIsPublished) {
|
|
197
|
-
const value =
|
|
285
|
+
const value = rawValueOf(collection, doc, clause.field, localeConfig, isPublished);
|
|
198
286
|
const condition = { [clause.op]: clause.value };
|
|
199
|
-
return
|
|
287
|
+
return matchesFieldValue(value, condition);
|
|
200
288
|
}
|
|
201
289
|
/**
|
|
202
290
|
* Sort documents by one column in the canonical deterministic order
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"view-model.js","sourceRoot":"","sources":["../../../src/lib/cms-views/view-model.ts"],"names":[],"mappings":"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","sourcesContent":["import { z } from 'zod'\nimport {\n documentDefaultLocale,\n localizedStringSchema,\n resolveField,\n resolveLabel,\n titleFieldOf,\n} from '@elytracms/core/cms-core'\nimport { localizedLabel } from '@elytracms/studio/lib/i18n'\nimport type {\n CmsDocument,\n CollectionDef,\n FieldDef,\n LocaleConfig,\n LocalizedString,\n} from '@elytracms/core/cms-core'\nimport {\n FILTER_OPERATORS,\n compareListEntries,\n filterScalarOf,\n filterableFields,\n matchesCondition,\n validateWhere,\n} from '@elytracms/core/content'\nimport type {\n FilterCondition,\n FilterOperator,\n FilterScalar,\n NormalizedCondition,\n WhereInput,\n} from '@elytracms/core/content'\n\n/**\n * The pure table-view model for the CMS collection table (EC-169).\n *\n * A view bundles three things: filters in the closed AD-3 vocabulary\n * (`eq`/`in`/`lt`/`lte`/`gt`/`gte`, AND-only — exactly `@elytracms/core/content`'s\n * query model, evaluated with the same matching/ordering primitives), a sort\n * (field + direction), and the visible/ordered columns. Everything here is\n * plain data + functions so filtering, dirtiness, and column edits are\n * testable without a DOM.\n *\n * Filters are offered ONLY on fields the schema declares `filterable: true`\n * (EC-143), plus one built-in option: the document's publication state. The\n * state is document metadata, not a schema field — it powers the\n * \"show me drafts → bulk publish\" working set and still speaks only the\n * closed vocabulary (`eq`/`in`).\n */\n\n// --- Schemas (single source of truth — persisted view records reuse these) ----\n\nexport const filterScalarSchema = z.union([z.string(), z.number(), z.boolean()])\n\nexport const filterClauseSchema = z.object({\n /** A schema field name, or {@link STATE_FIELD} for the publication state. */\n field: z.string().min(1),\n op: z.enum(FILTER_OPERATORS),\n /** `in` carries an array of scalars; every other operator one scalar. */\n value: z.union([filterScalarSchema, z.array(filterScalarSchema)]),\n})\nexport type FilterClause = z.infer<typeof filterClauseSchema>\n\nexport const viewSortSchema = z.object({\n field: z.string().min(1),\n dir: z.enum(['asc', 'desc']),\n})\nexport type ViewSort = z.infer<typeof viewSortSchema>\n\nexport const tableViewStateSchema = z.object({\n /** AND-only list of conditions (no OR, no nesting — vision AD-3). */\n filters: z.array(filterClauseSchema),\n sort: viewSortSchema.nullable(),\n /**\n * The canonical column order (field names) — EVERY column, visible or not\n * (EC-213). Order is independent of visibility: hiding then re-showing a\n * column keeps its place. Schema fields absent here resolve in at the end\n * (see {@link resolveColumns}).\n */\n columns: z.array(z.string().min(1)),\n /**\n * The subset of {@link TableViewState.columns} that is hidden. Defaults to\n * `[]` so views persisted before EC-213 (no `hidden` key) load unchanged.\n */\n hidden: z.array(z.string().min(1)).default([]),\n})\nexport type TableViewState = z.infer<typeof tableViewStateSchema>\n\n// --- The built-in publication-state filter column ------------------------------\n\n/**\n * Sentinel field key for the publication state. `$`-prefixed so it can never\n * collide with a schema field name (`fieldDefSchema` names are identifiers).\n */\nexport const STATE_FIELD = '$state'\n\nconst STATE_VALUES = ['draft', 'published'] as const\n\n/** One field the filter builder may offer. */\nexport interface FilterableFieldOption {\n /** Clause field key: a schema field name, or {@link STATE_FIELD}. */\n field: string\n label: string\n /** The schema field def, or `null` for the built-in publication state. */\n def: FieldDef | null\n}\n\nexport const STATE_OPTION: FilterableFieldOption = {\n field: STATE_FIELD,\n label: 'State',\n def: null,\n}\n\n/**\n * The closed list of fields the filter builder offers: exactly the schema's\n * `filterable: true` fields (EC-143) plus the publication state. Nothing else\n * is ever offered — the UI builds queries, it does not invent a language.\n */\nexport function filterableFieldOptions(collection: CollectionDef): FilterableFieldOption[] {\n const schemaOptions = filterableFields(collection).map((field) => ({\n field: field.name,\n label: localizedLabel(field.form?.label, field.name),\n def: field,\n }))\n return [...schemaOptions, STATE_OPTION]\n}\n\nexport function filterOptionFor(\n collection: CollectionDef,\n field: string,\n): FilterableFieldOption | undefined {\n return filterableFieldOptions(collection).find((option) => option.field === field)\n}\n\n// --- Operators per field type ---------------------------------------------------\n\n/** Display order + labels for the closed vocabulary. */\nexport const OPERATOR_LABELS: Record<FilterOperator, string> = {\n eq: 'equals',\n in: 'is one of',\n lt: 'less than',\n lte: 'at most',\n gt: 'greater than',\n gte: 'at least',\n}\n\nconst ORDERED_OPERATORS: readonly FilterOperator[] = ['eq', 'in', 'lt', 'lte', 'gt', 'gte']\n\n/**\n * The vocabulary subset that makes sense for a field's type. Always a subset\n * of the closed `FILTER_OPERATORS` — never an extension.\n */\nexport function operatorsFor(option: FilterableFieldOption): FilterOperator[] {\n if (option.def === null) return ['eq', 'in'] // publication state\n switch (option.def.type) {\n case 'boolean':\n return ['eq']\n case 'select':\n case 'relation':\n case 'asset':\n return ['eq', 'in']\n default:\n return [...ORDERED_OPERATORS]\n }\n}\n\n/** A sensible starting clause when a field is added to the builder. */\nexport function defaultClause(option: FilterableFieldOption): FilterClause {\n if (option.def === null) return { field: STATE_FIELD, op: 'eq', value: 'published' }\n switch (option.def.type) {\n case 'boolean':\n return { field: option.field, op: 'eq', value: true }\n case 'number':\n return { field: option.field, op: 'eq', value: 0 }\n case 'select':\n return { field: option.field, op: 'eq', value: option.def.options[0]?.value ?? '' }\n default:\n return { field: option.field, op: 'eq', value: '' }\n }\n}\n\n// --- Text ⇄ scalar coercion (the value inputs of the builder) -------------------\n\nconst DATE_ONLY = /^\\d{4}-\\d{2}-\\d{2}$/\n\n/** Coerce one text input into the scalar a clause compares against. */\nexport function scalarFromText(\n option: FilterableFieldOption | undefined,\n raw: string,\n): FilterScalar {\n const type = option?.def?.type\n if (type === 'number') {\n const trimmed = raw.trim()\n if (trimmed === '') return raw // an emptied input must not become eq 0\n const parsed = Number(trimmed)\n return Number.isNaN(parsed) ? raw : parsed\n }\n if (type === 'boolean') return raw === 'true'\n // Stored dates are full ISO strings; a date-input value compares correctly\n // once expanded to midnight UTC (ISO strings order by code units).\n if (type === 'date' && DATE_ONLY.test(raw)) return `${raw}T00:00:00.000Z`\n return raw\n}\n\n/** Parse a comma-separated list for the `in` operator. */\nexport function scalarListFromText(\n option: FilterableFieldOption | undefined,\n raw: string,\n): FilterScalar[] {\n return raw\n .split(',')\n .map((part) => part.trim())\n .filter((part) => part !== '')\n .map((part) => scalarFromText(option, part))\n}\n\nexport function scalarListToText(value: FilterScalar | FilterScalar[]): string {\n const list = Array.isArray(value) ? value : [value]\n return list.map(String).join(', ')\n}\n\n// --- Clause validation -----------------------------------------------------------\n\n/** A structured problem with one clause (by index in the filter list). */\nexport interface ClauseIssue {\n index: number\n message: string\n}\n\n/**\n * Validate clauses against the collection schema. Schema-field clauses run\n * through the canonical `validateWhere` (EC-143: `filterable` enforcement +\n * operator/value shape); state clauses check the `eq`/`in` subset and the\n * draft/published value set. Invalid clauses (e.g. a persisted view whose\n * field is no longer filterable) are reported — never silently applied.\n */\nexport function validateClauses(\n collection: CollectionDef,\n clauses: readonly FilterClause[],\n): ClauseIssue[] {\n const issues: ClauseIssue[] = []\n clauses.forEach((clause, index) => {\n if (clause.field === STATE_FIELD) {\n if (clause.op !== 'eq' && clause.op !== 'in') {\n issues.push({ index, message: 'State filters support only \"eq\" and \"in\".' })\n return\n }\n const values = Array.isArray(clause.value) ? clause.value : [clause.value]\n if (!values.every((value) => (STATE_VALUES as readonly unknown[]).includes(value))) {\n issues.push({ index, message: 'State must be \"draft\" or \"published\".' })\n }\n return\n }\n const where: WhereInput = {\n [clause.field]: { [clause.op]: clause.value } as FilterCondition,\n }\n const errors = validateWhere(collection, where)\n if (errors.length > 0) issues.push({ index, message: errors[0]!.message })\n })\n return issues\n}\n\n// --- Matching & sorting (delegates to @elytracms/core/content's primitives) -------------\n\n/**\n * Published-ness source for the `$state` pseudo-field (EC-224): the version\n * POINTER, supplied by the caller as `cms.isPublished`. Defaults to \"nothing\n * published\" — a bare row carries no publish signal now that the flat `state`\n * field is gone, so the table (which knows the pointer) passes the real\n * predicate; fixtures/tests that don't surface `$state` get the safe default.\n */\nexport type IsPublished = (doc: CmsDocument) => boolean\nconst defaultIsPublished: IsPublished = () => false\n\n/** The scalar a document exposes for a clause/sort field, locale-resolved. */\nfunction scalarOf(\n collection: CollectionDef,\n doc: CmsDocument,\n field: string,\n localeConfig: LocaleConfig,\n isPublished: IsPublished,\n): FilterScalar | null {\n if (field === STATE_FIELD) return isPublished(doc) ? 'published' : 'draft'\n if (!collection.fields.some((f) => f.name === field)) return null\n const locale = documentDefaultLocale(doc, localeConfig)\n return filterScalarOf(resolveField(collection, doc, field, locale, localeConfig).value)\n}\n\n/** Does a document satisfy one clause? Same semantics as the content accessors. */\nexport function clauseMatches(\n collection: CollectionDef,\n doc: CmsDocument,\n localeConfig: LocaleConfig,\n clause: FilterClause,\n isPublished: IsPublished = defaultIsPublished,\n): boolean {\n const value = scalarOf(collection, doc, clause.field, localeConfig, isPublished)\n const condition = { [clause.op]: clause.value } as NormalizedCondition\n return matchesCondition(value, condition)\n}\n\n/**\n * Sort documents by one column in the canonical deterministic order\n * (`compareListEntries`: typed value order, id-ascending tiebreak). Unlike the\n * content accessors, the *table* may sort any rendered column — sorting is a\n * presentation concern here; the `filterable` flag governs filters.\n */\nexport function sortViewDocuments(\n collection: CollectionDef,\n docs: readonly CmsDocument[],\n localeConfig: LocaleConfig,\n sort: ViewSort | null,\n isPublished: IsPublished = defaultIsPublished,\n): CmsDocument[] {\n if (sort === null) return [...docs]\n const positions = new Map(\n docs.map((doc) => [doc.id, scalarOf(collection, doc, sort.field, localeConfig, isPublished)]),\n )\n return [...docs].sort((a, b) =>\n compareListEntries(\n { id: a.id, sortValue: positions.get(a.id) ?? null },\n { id: b.id, sortValue: positions.get(b.id) ?? null },\n sort.dir,\n ),\n )\n}\n\n/** The filtered + sorted documents of a view, plus any clause issues. */\nexport interface AppliedViewState {\n docs: CmsDocument[]\n /** Clauses that failed validation — reported AND excluded from matching. */\n issues: ClauseIssue[]\n}\n\n/**\n * Apply a view state to a document list: validate clauses, AND-match the valid\n * ones, sort. Invalid clauses never filter silently — they come back as\n * issues for the UI to surface.\n */\nexport function applyViewState(\n collection: CollectionDef,\n docs: readonly CmsDocument[],\n localeConfig: LocaleConfig,\n state: TableViewState,\n isPublished: IsPublished = defaultIsPublished,\n): AppliedViewState {\n const issues = validateClauses(collection, state.filters)\n const invalid = new Set(issues.map((issue) => issue.index))\n const active = state.filters.filter((_, index) => !invalid.has(index))\n const matched = docs.filter((doc) =>\n active.every((clause) => clauseMatches(collection, doc, localeConfig, clause, isPublished)),\n )\n return {\n docs: sortViewDocuments(collection, matched, localeConfig, state.sort, isPublished),\n issues,\n }\n}\n\n// --- View state construction, equality, columns ----------------------------------\n\n/** The id of the unnamed built-in \"All documents\" view. */\nexport const ALL_VIEW_ID = 'all'\n\n/** The default (unnamed) view: no filters, no sort, every field visible. */\n/**\n * Field types kept OFF the default table columns (EC-246b): the composite editing\n * surfaces — a blocks canvas, rich text, a structured object/repeater — read as\n * noise in a scalar list. `filterable` no longer forces (or governs) a column; the\n * default lens is \"all scalars\". These stay in the column picker one toggle away,\n * and any named view may list them in its `columns`; they're just off by default.\n */\nconst NON_DEFAULT_COLUMN_TYPES = new Set<FieldDef['type']>(['blocks', 'richText', 'object'])\n\n/**\n * Field names OFF the built-in \"All\" lens by default: the composite editing surfaces\n * (blocks/richText/object) — always noise in a scalar list — PLUS any scalar the\n * schema opts out with `form.column: false` (EC-343). Both only affect the default\n * columns: the field stays in the picker one toggle away, its form and filterability\n * are untouched, and a named view's explicit `columns` list ignores this entirely.\n */\nfunction defaultHiddenColumns(collection: CollectionDef): string[] {\n return collection.fields\n .filter((f) => NON_DEFAULT_COLUMN_TYPES.has(f.type) || f.form?.column === false)\n .map((f) => f.name)\n}\n\nexport function defaultViewState(collection: CollectionDef): TableViewState {\n return {\n filters: [],\n sort: null,\n // Every field keeps a slot (order + picker), but the composite surfaces and any\n // `form.column: false` scalars start hidden so the default lens is the\n // collection's chosen scalar columns (EC-246b / EC-343).\n columns: orderedColumnNames(collection),\n hidden: defaultHiddenColumns(collection),\n }\n}\n\n/**\n * A config-declared view (EC-246b) — the flat authoring shape a collection\n * declares in `cms/` config: `{ name, filters?, sort?, columns?, hidden? }`.\n * Filters/sort/columns sit at the top level (no nested `state` wrapper). The\n * studio validates + interprets these; `cms-core` stores them opaquely (it\n * can't reference the filter vocabulary without a layering cycle).\n */\nexport const configViewSchema = z.object({\n /** Optional stable id; defaults to a slug of `name`. */\n id: z.string().min(1).optional(),\n /**\n * View label; localizable (EC-343): a plain string or a `{ locale }` map, resolved\n * at the render site via the same `resolveLabel` path as `form.label` (EC-241).\n */\n name: localizedStringSchema,\n /**\n * When true this view seeds the collection's INITIAL active view (EC-343) — the\n * curated lens an editor lands on before picking one. At most one per collection;\n * a duplicate is a visible validation issue ({@link configViewIssues}), never\n * silent. The built-in \"All\" lens stays available and stays the fallback.\n */\n default: z.boolean().optional(),\n filters: z.array(filterClauseSchema).optional(),\n sort: viewSortSchema.nullable().optional(),\n /** Column order; when omitted, the view inherits the collection's default columns. */\n columns: z.array(z.string().min(1)).optional(),\n hidden: z.array(z.string().min(1)).optional(),\n})\nexport type ConfigView = z.infer<typeof configViewSchema>\n\n/**\n * A resolved view: identity + the table state it restores (matches `SavedView`).\n * `name` stays a {@link LocalizedString} — resolved at the render site (EC-343), like\n * `form.label`. `default` marks the initial-view seed (see {@link defaultConfigViewId}).\n */\nexport interface ResolvedView {\n id: string\n name: LocalizedString\n state: TableViewState\n default?: boolean\n}\n\n/**\n * Slug used to derive a config view's stable id from its name. A localized name\n * (EC-343) slugs its DEFAULT-locale text so the id is stable across the active UI\n * locale (an id must not shift when a German editor loads the same config).\n */\nfunction configViewId(name: LocalizedString, index: number): string {\n const text = resolveLabel(name) ?? ''\n const slug = text\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '')\n return `config:${slug || index}`\n}\n\n/**\n * Resolve a collection's config-declared `views` into full {@link ResolvedView}s\n * (EC-246b). Each is validated with {@link configViewSchema}; malformed entries\n * and duplicate ids are dropped (defensive). Columns fall back to the collection\n * default when a view doesn't pin them, so a filter-only view still renders every\n * column. Config views are shared and change only in\n * code — the runtime never mutates them.\n */\nexport function configViewsFor(collection: CollectionDef): ResolvedView[] {\n const declared = collection.views\n if (!Array.isArray(declared)) return []\n const base = defaultViewState(collection)\n const seen = new Set<string>()\n const resolved: ResolvedView[] = []\n declared.forEach((raw, index) => {\n const parsed = configViewSchema.safeParse(raw)\n if (!parsed.success) return\n const view = parsed.data\n const id = view.id ?? configViewId(view.name, index)\n if (seen.has(id)) return\n seen.add(id)\n resolved.push({\n id,\n name: view.name,\n ...(view.default ? { default: true } : {}),\n state: normalizeViewState({\n filters: view.filters ?? [],\n sort: view.sort ?? null,\n columns: view.columns && view.columns.length > 0 ? view.columns : base.columns,\n hidden: view.hidden ?? [],\n }),\n })\n })\n return resolved\n}\n\n/**\n * The id of the config view flagged `default: true` (EC-343) — the seed for a\n * collection's INITIAL active view. When several are flagged (a misconfiguration,\n * reported by {@link configViewIssues}) the FIRST in declaration order wins\n * deterministically; when none is flagged the caller falls back to the built-in\n * \"All\" lens. `undefined` when the collection declares no default view.\n */\nexport function defaultConfigViewId(collection: CollectionDef): string | undefined {\n return configViewsFor(collection).find((view) => view.default)?.id\n}\n\n/** A config-level problem with a collection's declared views (EC-343). */\nexport interface ConfigViewIssue {\n code: 'duplicate-default'\n message: string\n /** The offending view ids (all views flagged `default`, in declaration order). */\n viewIds: string[]\n}\n\n/**\n * Validate a collection's config-declared views (EC-343): at most one may set\n * `default: true`. A duplicate is returned as an explicit, structured issue —\n * never silently swallowed — so the studio can surface it (the first default still\n * wins deterministically for seeding, per {@link defaultConfigViewId}). Pure and\n * schema-only; the DOM-free counterpart of the \"failures are first-class\" contract.\n */\nexport function configViewIssues(collection: CollectionDef): ConfigViewIssue[] {\n const defaults = configViewsFor(collection).filter((view) => view.default)\n if (defaults.length <= 1) return []\n const viewIds = defaults.map((view) => view.id)\n return [\n {\n code: 'duplicate-default',\n message: `Collection \"${collection.id}\" declares ${defaults.length} default views (${viewIds.join(', ')}); at most one view may set \\`default: true\\`. Seeding \"${viewIds[0]}\".`,\n viewIds,\n },\n ]\n}\n\n/**\n * The default column order: the schema's title field first (it becomes the\n * default title/link column — EC-213 drops the separate primary-column\n * selector, treating the first visible column as the title), then the\n * remaining fields in schema order.\n */\nfunction orderedColumnNames(collection: CollectionDef): string[] {\n const names = collection.fields.map((field) => field.name)\n const title = titleFieldOf(collection)?.name\n if (title === undefined || !names.includes(title)) return names\n return [title, ...names.filter((name) => name !== title)]\n}\n\n/** Deep-cloned canonical form (stable key order via the Zod shape). */\nexport function normalizeViewState(state: TableViewState): TableViewState {\n return tableViewStateSchema.parse(state)\n}\n\n/** Structural equality on normalized states — drives the \"unsaved changes\" affordance. */\nexport function viewStatesEqual(a: TableViewState, b: TableViewState): boolean {\n return JSON.stringify(normalizeViewState(a)) === JSON.stringify(normalizeViewState(b))\n}\n\n/** One column of a view resolved against the current schema (EC-213). */\nexport interface ResolvedColumn {\n /** The schema field name. */\n field: string\n /** Whether the column renders (`false` ⇒ in {@link TableViewState.hidden}). */\n visible: boolean\n}\n\n/**\n * Resolve a view's columns against the live schema (EC-213): every schema\n * field exactly once, in the view's saved order, with any fields the view\n * predates appended (visible) and any fields no longer in the schema dropped.\n * This is the single source of truth for \"which columns, in what order, shown\n * or hidden\" — order is preserved across hide/show because visibility lives in\n * `hidden`, never in the order.\n */\nexport function resolveColumns(\n collection: CollectionDef,\n state: TableViewState,\n): ResolvedColumn[] {\n const schemaNames = collection.fields.map((field) => field.name)\n const schema = new Set(schemaNames)\n const hidden = new Set(state.hidden)\n // Keep the saved order, restricted to live fields and de-duplicated (a\n // corrupted persisted state must never render one field as two columns).\n const ordered: string[] = []\n const seen = new Set<string>()\n for (const name of state.columns) {\n if (schema.has(name) && !seen.has(name)) {\n ordered.push(name)\n seen.add(name)\n }\n }\n const appended = schemaNames.filter((name) => !seen.has(name))\n return [...ordered, ...appended].map((field) => ({ field, visible: !hidden.has(field) }))\n}\n\n/** The visible columns of a view, in render order (EC-213). */\nexport function visibleColumns(collection: CollectionDef, state: TableViewState): string[] {\n return resolveColumns(collection, state)\n .filter((column) => column.visible)\n .map((column) => column.field)\n}\n\n/**\n * The title/link column — the document IDENTITY column (EC-242): the schema title\n * field, shown as the leftmost linked cell whose content is `resolveDocumentTitle`\n * (a string `titleField` or a function pattern). Always present regardless of\n * column visibility — it is the document's identity, not a toggleable data column —\n * and excluded from the data columns so the title never shows twice. Falls back to\n * the first visible column, then the first field, then `id`, when no title field\n * is resolvable (e.g. a function title with no backing `title` field).\n */\nexport function primaryColumn(collection: CollectionDef, state: TableViewState): string {\n return (\n titleFieldOf(collection)?.name ??\n visibleColumns(collection, state)[0] ??\n collection.fields[0]?.name ??\n 'id'\n )\n}\n\n/**\n * Reconcile `columns` to the full resolved order so column edits operate on a\n * complete, stable order (drops removed fields, pins appended ones), and prune\n * `hidden` to fields the live schema still has (a column removed from the schema\n * must not leave dead `hidden` cruft that trips dirty-detection / persists).\n */\nfunction withResolvedOrder(collection: CollectionDef, state: TableViewState): TableViewState {\n const columns = resolveColumns(collection, state).map((column) => column.field)\n const live = new Set(columns)\n const hidden = state.hidden.filter((name) => live.has(name))\n return { ...state, columns, hidden }\n}\n\n/**\n * The canonical, schema-resolved form of a view state — full column order with\n * `hidden` pruned to live fields (EC-213). Two states that render identically\n * share one canonical form, so dirty-detection never trips on a mere column\n * reconciliation or a stale `hidden` entry (only a real filter/sort/visibility/\n * order change counts as \"unsaved\").\n */\nexport function canonicalViewState(\n collection: CollectionDef,\n state: TableViewState,\n): TableViewState {\n return normalizeViewState(withResolvedOrder(collection, state))\n}\n\n/**\n * Toggle a column's visibility — position is preserved (EC-213): visibility\n * lives in `hidden`, the order in `columns` is untouched. Columns the saved\n * order predates are pinned into `columns` first so their place is stable.\n */\nexport function toggleColumn(\n collection: CollectionDef,\n state: TableViewState,\n field: string,\n): TableViewState {\n const base = withResolvedOrder(collection, state)\n const hidden = base.hidden.includes(field)\n ? base.hidden.filter((name) => name !== field)\n : [...base.hidden, field]\n return { ...base, hidden }\n}\n\n/** Move a column one position left (-1) or right (+1) in the full column order. */\nexport function moveColumn(\n collection: CollectionDef,\n state: TableViewState,\n field: string,\n delta: -1 | 1,\n): TableViewState {\n const base = withResolvedOrder(collection, state)\n const from = base.columns.indexOf(field)\n const to = from + delta\n if (from < 0 || to < 0 || to >= base.columns.length) return state\n const columns = [...base.columns]\n columns.splice(from, 1)\n columns.splice(to, 0, field)\n return { ...base, columns }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"view-model.js","sourceRoot":"","sources":["../../../src/lib/cms-views/view-model.ts"],"names":[],"mappings":"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","sourcesContent":["import { z } from 'zod'\nimport {\n documentDefaultLocale,\n localizedStringSchema,\n resolveField,\n resolveLabel,\n titleFieldOf,\n} from '@elytracms/core/cms-core'\nimport { localizedLabel } from '@elytracms/studio/lib/i18n'\nimport type {\n CmsDocument,\n CollectionDef,\n FieldDef,\n LocaleConfig,\n LocalizedString,\n} from '@elytracms/core/cms-core'\nimport {\n FILTER_OPERATORS,\n compareListEntries,\n filterScalarOf,\n filterableFields,\n matchesFieldValue,\n validateWhere,\n} from '@elytracms/core/content'\nimport type {\n FilterCondition,\n FilterOperator,\n FilterScalar,\n NormalizedCondition,\n WhereInput,\n} from '@elytracms/core/content'\n\n/**\n * The pure table-view model for the CMS collection table (EC-169).\n *\n * A view bundles three things: filters in the closed AD-3 vocabulary\n * (`eq`/`in`/`lt`/`lte`/`gt`/`gte`, AND-only — exactly `@elytracms/core/content`'s\n * query model, evaluated with the same matching/ordering primitives), a sort\n * (field + direction), and the visible/ordered columns. Everything here is\n * plain data + functions so filtering, dirtiness, and column edits are\n * testable without a DOM.\n *\n * Filters are offered ONLY on fields the schema declares `filterable: true`\n * (EC-143), plus one built-in option: the document's publication state. The\n * state is document metadata, not a schema field — it powers the\n * \"show me drafts → bulk publish\" working set and still speaks only the\n * closed vocabulary (`eq`/`in`).\n */\n\n// --- Schemas (single source of truth — persisted view records reuse these) ----\n\nexport const filterScalarSchema = z.union([z.string(), z.number(), z.boolean()])\n\nexport const filterClauseSchema = z.object({\n /** A schema field name, or {@link STATE_FIELD} for the publication state. */\n field: z.string().min(1),\n op: z.enum(FILTER_OPERATORS),\n /** `in` carries an array of scalars; every other operator one scalar. */\n value: z.union([filterScalarSchema, z.array(filterScalarSchema)]),\n})\nexport type FilterClause = z.infer<typeof filterClauseSchema>\n\nexport const viewSortSchema = z.object({\n field: z.string().min(1),\n dir: z.enum(['asc', 'desc']),\n})\nexport type ViewSort = z.infer<typeof viewSortSchema>\n\nexport const tableViewStateSchema = z.object({\n /** AND-only list of conditions (no OR, no nesting — vision AD-3). */\n filters: z.array(filterClauseSchema),\n sort: viewSortSchema.nullable(),\n /**\n * The canonical column order (field names) — EVERY column, visible or not\n * (EC-213). Order is independent of visibility: hiding then re-showing a\n * column keeps its place. Schema fields absent here resolve in at the end\n * (see {@link resolveColumns}).\n */\n columns: z.array(z.string().min(1)),\n /**\n * The subset of {@link TableViewState.columns} that is hidden. Defaults to\n * `[]` so views persisted before EC-213 (no `hidden` key) load unchanged.\n */\n hidden: z.array(z.string().min(1)).default([]),\n})\nexport type TableViewState = z.infer<typeof tableViewStateSchema>\n\n// --- The built-in publication-state filter column ------------------------------\n\n/**\n * Sentinel field key for the publication state. `$`-prefixed so it can never\n * collide with a schema field name (`fieldDefSchema` names are identifiers).\n */\nexport const STATE_FIELD = '$state'\n\nconst STATE_VALUES = ['draft', 'published'] as const\n\n/** One field the filter builder may offer. */\nexport interface FilterableFieldOption {\n /** Clause field key: a schema field name, or {@link STATE_FIELD}. */\n field: string\n label: string\n /** The schema field def, or `null` for the built-in publication state. */\n def: FieldDef | null\n}\n\nexport const STATE_OPTION: FilterableFieldOption = {\n field: STATE_FIELD,\n label: 'State',\n def: null,\n}\n\n/**\n * The closed list of fields the filter builder offers: exactly the schema's\n * `filterable: true` fields (EC-143) plus the publication state. Nothing else\n * is ever offered — the UI builds queries, it does not invent a language.\n */\nexport function filterableFieldOptions(collection: CollectionDef): FilterableFieldOption[] {\n const schemaOptions = filterableFields(collection).map((field) => ({\n field: field.name,\n label: localizedLabel(field.form?.label, field.name),\n def: field,\n }))\n return [...schemaOptions, STATE_OPTION]\n}\n\nexport function filterOptionFor(\n collection: CollectionDef,\n field: string,\n): FilterableFieldOption | undefined {\n return filterableFieldOptions(collection).find((option) => option.field === field)\n}\n\n// --- Operators per field type (EC-345) ------------------------------------------\n\n/**\n * The per-type operator SET the builder offers (EC-345), always a subset of the\n * closed `FILTER_OPERATORS` — the UI narrows the vocabulary, it never extends it.\n * Ratified sets (Oli, 2026-07-08):\n *\n * - text: contains (default) · equals · starts with · ends with · empty\n * - number: equals · < · ≤ · > · ≥ · empty\n * - date: equals · before · … · after · empty (same ops as number, date labels)\n * - boolean: is\n * - select: is · is any of · empty\n * - relation / asset: is · is any of · empty (relation value = the relation picker;\n * `eq` on a `multiple` relation is membership — array-contains, EC-255 semantics)\n * - state pseudo-field: is · is any of (unchanged — a publication state is never empty)\n */\nexport function operatorsFor(option: FilterableFieldOption): FilterOperator[] {\n if (option.def === null) return ['eq', 'in'] // publication state\n switch (option.def.type) {\n case 'boolean':\n return ['eq']\n case 'number':\n case 'date':\n return ['eq', 'lt', 'lte', 'gt', 'gte', 'empty']\n case 'select':\n case 'relation':\n case 'asset':\n return ['eq', 'in', 'empty']\n default:\n // text (and any other string-shaped field)\n return ['contains', 'eq', 'startsWith', 'endsWith', 'empty']\n }\n}\n\n/**\n * A visual choice in the operator dropdown (EC-345). Usually one per operator,\n * but `empty` splits into two — \"is empty\" (`true`) and \"is not empty\" (`false`) —\n * so an editor never has to reason about a boolean value; the choice carries the\n * preset. `token` is the `<option value>`, `labelKey` a catalog key ({@link operatorLabelKey}).\n */\nexport interface OperatorChoice {\n token: string\n op: FilterOperator\n /** Value forced when this choice is picked (the `empty` true/false), else undefined. */\n presetValue?: FilterScalar\n labelKey: string\n}\n\n/**\n * The operator dropdown entries for a field (EC-345): {@link operatorsFor} expanded\n * for display, with `empty` shown as the two self-describing choices.\n */\nexport function operatorChoices(option: FilterableFieldOption): OperatorChoice[] {\n const choices: OperatorChoice[] = []\n for (const op of operatorsFor(option)) {\n if (op === 'empty') {\n choices.push({ token: 'empty:true', op, presetValue: true, labelKey: 'cms.filter.op.isEmpty' })\n choices.push({\n token: 'empty:false',\n op,\n presetValue: false,\n labelKey: 'cms.filter.op.isNotEmpty',\n })\n continue\n }\n choices.push({ token: op, op, labelKey: operatorLabelKey(op, option) })\n }\n return choices\n}\n\n/**\n * The catalog key for an operator's label (EC-241/EC-345), keyed by operator AND\n * field type — `eq` reads \"equals\" on text/number, \"on\" on a date, \"is\" on an\n * enum-like field; `lt` reads \"less than\" on a number, \"before\" on a date. `empty`\n * additionally depends on its VALUE (is empty / is not empty). Pure → unit-tested.\n */\nexport function operatorLabelKey(\n op: FilterOperator,\n option: FilterableFieldOption | undefined,\n value?: FilterScalar | readonly FilterScalar[],\n): string {\n const type = option?.def?.type\n switch (op) {\n case 'contains':\n return 'cms.filter.op.contains'\n case 'startsWith':\n return 'cms.filter.op.startsWith'\n case 'endsWith':\n return 'cms.filter.op.endsWith'\n case 'in':\n return 'cms.filter.op.isAnyOf'\n case 'empty':\n return value === false ? 'cms.filter.op.isNotEmpty' : 'cms.filter.op.isEmpty'\n case 'eq':\n // \"on\" for a date; \"is\" for enum-like refs; \"equals\" for text/number/unknown\n // (and the publication-state pseudo-field, whose `def` is null → unchanged).\n if (type === 'date') return 'cms.filter.op.on'\n if (type === 'boolean' || type === 'select' || type === 'relation' || type === 'asset') {\n return 'cms.filter.op.is'\n }\n return 'cms.filter.op.equals'\n case 'lt':\n return type === 'date' ? 'cms.filter.op.before' : 'cms.filter.op.lessThan'\n case 'lte':\n return type === 'date' ? 'cms.filter.op.onOrBefore' : 'cms.filter.op.atMost'\n case 'gt':\n return type === 'date' ? 'cms.filter.op.after' : 'cms.filter.op.greaterThan'\n case 'gte':\n return type === 'date' ? 'cms.filter.op.onOrAfter' : 'cms.filter.op.atLeast'\n }\n}\n\n/** A sensible starting clause when a field is added to the builder (EC-345). */\nexport function defaultClause(option: FilterableFieldOption): FilterClause {\n if (option.def === null) return { field: STATE_FIELD, op: 'eq', value: 'published' }\n switch (option.def.type) {\n case 'boolean':\n return { field: option.field, op: 'eq', value: true }\n case 'number':\n case 'date':\n return { field: option.field, op: 'eq', value: option.def.type === 'number' ? 0 : '' }\n case 'select':\n return { field: option.field, op: 'eq', value: option.def.options[0]?.value ?? '' }\n case 'relation':\n case 'asset':\n return { field: option.field, op: 'eq', value: '' }\n default:\n // text (and any other string-shaped field): `contains` is the natural default\n return { field: option.field, op: 'contains', value: '' }\n }\n}\n\n// --- Text ⇄ scalar coercion (the value inputs of the builder) -------------------\n\nconst DATE_ONLY = /^\\d{4}-\\d{2}-\\d{2}$/\n\n/** Coerce one text input into the scalar a clause compares against. */\nexport function scalarFromText(\n option: FilterableFieldOption | undefined,\n raw: string,\n): FilterScalar {\n const type = option?.def?.type\n if (type === 'number') {\n const trimmed = raw.trim()\n if (trimmed === '') return raw // an emptied input must not become eq 0\n const parsed = Number(trimmed)\n return Number.isNaN(parsed) ? raw : parsed\n }\n if (type === 'boolean') return raw === 'true'\n // Stored dates are full ISO strings; a date-input value compares correctly\n // once expanded to midnight UTC (ISO strings order by code units).\n if (type === 'date' && DATE_ONLY.test(raw)) return `${raw}T00:00:00.000Z`\n return raw\n}\n\n/** Parse a comma-separated list for the `in` operator. */\nexport function scalarListFromText(\n option: FilterableFieldOption | undefined,\n raw: string,\n): FilterScalar[] {\n return raw\n .split(',')\n .map((part) => part.trim())\n .filter((part) => part !== '')\n .map((part) => scalarFromText(option, part))\n}\n\nexport function scalarListToText(value: FilterScalar | FilterScalar[]): string {\n const list = Array.isArray(value) ? value : [value]\n return list.map(String).join(', ')\n}\n\n// --- Clause validation -----------------------------------------------------------\n\n/** A structured problem with one clause (by index in the filter list). */\nexport interface ClauseIssue {\n index: number\n message: string\n}\n\n/**\n * Validate clauses against the collection schema. Schema-field clauses run\n * through the canonical `validateWhere` (EC-143: `filterable` enforcement +\n * operator/value shape); state clauses check the `eq`/`in` subset and the\n * draft/published value set. Invalid clauses (e.g. a persisted view whose\n * field is no longer filterable) are reported — never silently applied.\n */\nexport function validateClauses(\n collection: CollectionDef,\n clauses: readonly FilterClause[],\n): ClauseIssue[] {\n const issues: ClauseIssue[] = []\n clauses.forEach((clause, index) => {\n if (clause.field === STATE_FIELD) {\n if (clause.op !== 'eq' && clause.op !== 'in') {\n issues.push({ index, message: 'State filters support only \"eq\" and \"in\".' })\n return\n }\n const values = Array.isArray(clause.value) ? clause.value : [clause.value]\n if (!values.every((value) => (STATE_VALUES as readonly unknown[]).includes(value))) {\n issues.push({ index, message: 'State must be \"draft\" or \"published\".' })\n }\n return\n }\n const where: WhereInput = {\n [clause.field]: { [clause.op]: clause.value } as FilterCondition,\n }\n const errors = validateWhere(collection, where)\n if (errors.length > 0) issues.push({ index, message: errors[0]!.message })\n })\n return issues\n}\n\n// --- Matching & sorting (delegates to @elytracms/core/content's primitives) -------------\n\n/**\n * Published-ness source for the `$state` pseudo-field (EC-224): the version\n * POINTER, supplied by the caller as `cms.isPublished`. Defaults to \"nothing\n * published\" — a bare row carries no publish signal now that the flat `state`\n * field is gone, so the table (which knows the pointer) passes the real\n * predicate; fixtures/tests that don't surface `$state` get the safe default.\n */\nexport type IsPublished = (doc: CmsDocument) => boolean\nconst defaultIsPublished: IsPublished = () => false\n\n/**\n * The RAW locale-resolved value a document exposes for a clause field — an array\n * for a `multiple` relation/asset, a ref object for a single one, a scalar\n * otherwise. Matching runs on this (via `matchesFieldValue`) so it agrees with the\n * delivery/listing path exactly: array-contains for `multiple` relations (EC-255)\n * and whole-value `empty` semantics (EC-345). Sorting keeps its own scalar reduction.\n */\nfunction rawValueOf(\n collection: CollectionDef,\n doc: CmsDocument,\n field: string,\n localeConfig: LocaleConfig,\n isPublished: IsPublished,\n): unknown {\n if (field === STATE_FIELD) return isPublished(doc) ? 'published' : 'draft'\n if (!collection.fields.some((f) => f.name === field)) return undefined\n const locale = documentDefaultLocale(doc, localeConfig)\n return resolveField(collection, doc, field, locale, localeConfig).value\n}\n\n/** The scalar a document exposes for a SORT field, locale-resolved. */\nfunction scalarOf(\n collection: CollectionDef,\n doc: CmsDocument,\n field: string,\n localeConfig: LocaleConfig,\n isPublished: IsPublished,\n): FilterScalar | null {\n if (field === STATE_FIELD) return isPublished(doc) ? 'published' : 'draft'\n if (!collection.fields.some((f) => f.name === field)) return null\n const locale = documentDefaultLocale(doc, localeConfig)\n return filterScalarOf(resolveField(collection, doc, field, locale, localeConfig).value)\n}\n\n/**\n * Does a document satisfy one clause? Identical verdict to the content accessors'\n * delivery/listing path: both reduce to `@elytracms/core/content`'s\n * `matchesFieldValue` over the RAW field value (so `contains`/`empty`/multiple-\n * relation membership all behave the same on the table as on the live site).\n */\nexport function clauseMatches(\n collection: CollectionDef,\n doc: CmsDocument,\n localeConfig: LocaleConfig,\n clause: FilterClause,\n isPublished: IsPublished = defaultIsPublished,\n): boolean {\n const value = rawValueOf(collection, doc, clause.field, localeConfig, isPublished)\n const condition = { [clause.op]: clause.value } as NormalizedCondition\n return matchesFieldValue(value, condition)\n}\n\n/**\n * Sort documents by one column in the canonical deterministic order\n * (`compareListEntries`: typed value order, id-ascending tiebreak). Unlike the\n * content accessors, the *table* may sort any rendered column — sorting is a\n * presentation concern here; the `filterable` flag governs filters.\n */\nexport function sortViewDocuments(\n collection: CollectionDef,\n docs: readonly CmsDocument[],\n localeConfig: LocaleConfig,\n sort: ViewSort | null,\n isPublished: IsPublished = defaultIsPublished,\n): CmsDocument[] {\n if (sort === null) return [...docs]\n const positions = new Map(\n docs.map((doc) => [doc.id, scalarOf(collection, doc, sort.field, localeConfig, isPublished)]),\n )\n return [...docs].sort((a, b) =>\n compareListEntries(\n { id: a.id, sortValue: positions.get(a.id) ?? null },\n { id: b.id, sortValue: positions.get(b.id) ?? null },\n sort.dir,\n ),\n )\n}\n\n/** The filtered + sorted documents of a view, plus any clause issues. */\nexport interface AppliedViewState {\n docs: CmsDocument[]\n /** Clauses that failed validation — reported AND excluded from matching. */\n issues: ClauseIssue[]\n}\n\n/**\n * Apply a view state to a document list: validate clauses, AND-match the valid\n * ones, sort. Invalid clauses never filter silently — they come back as\n * issues for the UI to surface.\n */\nexport function applyViewState(\n collection: CollectionDef,\n docs: readonly CmsDocument[],\n localeConfig: LocaleConfig,\n state: TableViewState,\n isPublished: IsPublished = defaultIsPublished,\n): AppliedViewState {\n const issues = validateClauses(collection, state.filters)\n const invalid = new Set(issues.map((issue) => issue.index))\n const active = state.filters.filter((_, index) => !invalid.has(index))\n const matched = docs.filter((doc) =>\n active.every((clause) => clauseMatches(collection, doc, localeConfig, clause, isPublished)),\n )\n return {\n docs: sortViewDocuments(collection, matched, localeConfig, state.sort, isPublished),\n issues,\n }\n}\n\n// --- View state construction, equality, columns ----------------------------------\n\n/** The id of the unnamed built-in \"All documents\" view. */\nexport const ALL_VIEW_ID = 'all'\n\n/** The default (unnamed) view: no filters, no sort, every field visible. */\n/**\n * Field types kept OFF the default table columns (EC-246b): the composite editing\n * surfaces — a blocks canvas, rich text, a structured object/repeater — read as\n * noise in a scalar list. `filterable` no longer forces (or governs) a column; the\n * default lens is \"all scalars\". These stay in the column picker one toggle away,\n * and any named view may list them in its `columns`; they're just off by default.\n */\nconst NON_DEFAULT_COLUMN_TYPES = new Set<FieldDef['type']>(['blocks', 'richText', 'object'])\n\n/**\n * Field names OFF the built-in \"All\" lens by default: the composite editing surfaces\n * (blocks/richText/object) — always noise in a scalar list — PLUS any scalar the\n * schema opts out with `form.column: false` (EC-343). Both only affect the default\n * columns: the field stays in the picker one toggle away, its form and filterability\n * are untouched, and a named view's explicit `columns` list ignores this entirely.\n */\nfunction defaultHiddenColumns(collection: CollectionDef): string[] {\n return collection.fields\n .filter((f) => NON_DEFAULT_COLUMN_TYPES.has(f.type) || f.form?.column === false)\n .map((f) => f.name)\n}\n\nexport function defaultViewState(collection: CollectionDef): TableViewState {\n return {\n filters: [],\n sort: null,\n // Every field keeps a slot (order + picker), but the composite surfaces and any\n // `form.column: false` scalars start hidden so the default lens is the\n // collection's chosen scalar columns (EC-246b / EC-343).\n columns: orderedColumnNames(collection),\n hidden: defaultHiddenColumns(collection),\n }\n}\n\n/**\n * A config-declared view (EC-246b) — the flat authoring shape a collection\n * declares in `cms/` config: `{ name, filters?, sort?, columns?, hidden? }`.\n * Filters/sort/columns sit at the top level (no nested `state` wrapper). The\n * studio validates + interprets these; `cms-core` stores them opaquely (it\n * can't reference the filter vocabulary without a layering cycle).\n */\nexport const configViewSchema = z.object({\n /** Optional stable id; defaults to a slug of `name`. */\n id: z.string().min(1).optional(),\n /**\n * View label; localizable (EC-343): a plain string or a `{ locale }` map, resolved\n * at the render site via the same `resolveLabel` path as `form.label` (EC-241).\n */\n name: localizedStringSchema,\n /**\n * When true this view seeds the collection's INITIAL active view (EC-343) — the\n * curated lens an editor lands on before picking one. At most one per collection;\n * a duplicate is a visible validation issue ({@link configViewIssues}), never\n * silent. The built-in \"All\" lens stays available and stays the fallback.\n */\n default: z.boolean().optional(),\n filters: z.array(filterClauseSchema).optional(),\n sort: viewSortSchema.nullable().optional(),\n /** Column order; when omitted, the view inherits the collection's default columns. */\n columns: z.array(z.string().min(1)).optional(),\n hidden: z.array(z.string().min(1)).optional(),\n})\nexport type ConfigView = z.infer<typeof configViewSchema>\n\n/**\n * A resolved view: identity + the table state it restores (matches `SavedView`).\n * `name` stays a {@link LocalizedString} — resolved at the render site (EC-343), like\n * `form.label`. `default` marks the initial-view seed (see {@link defaultConfigViewId}).\n */\nexport interface ResolvedView {\n id: string\n name: LocalizedString\n state: TableViewState\n default?: boolean\n}\n\n/**\n * Slug used to derive a config view's stable id from its name. A localized name\n * (EC-343) slugs its DEFAULT-locale text so the id is stable across the active UI\n * locale (an id must not shift when a German editor loads the same config).\n */\nfunction configViewId(name: LocalizedString, index: number): string {\n const text = resolveLabel(name) ?? ''\n const slug = text\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '')\n return `config:${slug || index}`\n}\n\n/**\n * Resolve a collection's config-declared `views` into full {@link ResolvedView}s\n * (EC-246b). Each is validated with {@link configViewSchema}; malformed entries\n * and duplicate ids are dropped (defensive). Columns fall back to the collection\n * default when a view doesn't pin them, so a filter-only view still renders every\n * column. Config views are shared and change only in\n * code — the runtime never mutates them.\n */\nexport function configViewsFor(collection: CollectionDef): ResolvedView[] {\n const declared = collection.views\n if (!Array.isArray(declared)) return []\n const base = defaultViewState(collection)\n const seen = new Set<string>()\n const resolved: ResolvedView[] = []\n declared.forEach((raw, index) => {\n const parsed = configViewSchema.safeParse(raw)\n if (!parsed.success) return\n const view = parsed.data\n const id = view.id ?? configViewId(view.name, index)\n if (seen.has(id)) return\n seen.add(id)\n resolved.push({\n id,\n name: view.name,\n ...(view.default ? { default: true } : {}),\n state: normalizeViewState({\n filters: view.filters ?? [],\n sort: view.sort ?? null,\n columns: view.columns && view.columns.length > 0 ? view.columns : base.columns,\n hidden: view.hidden ?? [],\n }),\n })\n })\n return resolved\n}\n\n/**\n * The id of the config view flagged `default: true` (EC-343) — the seed for a\n * collection's INITIAL active view. When several are flagged (a misconfiguration,\n * reported by {@link configViewIssues}) the FIRST in declaration order wins\n * deterministically; when none is flagged the caller falls back to the built-in\n * \"All\" lens. `undefined` when the collection declares no default view.\n */\nexport function defaultConfigViewId(collection: CollectionDef): string | undefined {\n return configViewsFor(collection).find((view) => view.default)?.id\n}\n\n/** A config-level problem with a collection's declared views (EC-343). */\nexport interface ConfigViewIssue {\n code: 'duplicate-default'\n message: string\n /** The offending view ids (all views flagged `default`, in declaration order). */\n viewIds: string[]\n}\n\n/**\n * Validate a collection's config-declared views (EC-343): at most one may set\n * `default: true`. A duplicate is returned as an explicit, structured issue —\n * never silently swallowed — so the studio can surface it (the first default still\n * wins deterministically for seeding, per {@link defaultConfigViewId}). Pure and\n * schema-only; the DOM-free counterpart of the \"failures are first-class\" contract.\n */\nexport function configViewIssues(collection: CollectionDef): ConfigViewIssue[] {\n const defaults = configViewsFor(collection).filter((view) => view.default)\n if (defaults.length <= 1) return []\n const viewIds = defaults.map((view) => view.id)\n return [\n {\n code: 'duplicate-default',\n message: `Collection \"${collection.id}\" declares ${defaults.length} default views (${viewIds.join(', ')}); at most one view may set \\`default: true\\`. Seeding \"${viewIds[0]}\".`,\n viewIds,\n },\n ]\n}\n\n/**\n * The default column order: the schema's title field first (it becomes the\n * default title/link column — EC-213 drops the separate primary-column\n * selector, treating the first visible column as the title), then the\n * remaining fields in schema order.\n */\nfunction orderedColumnNames(collection: CollectionDef): string[] {\n const names = collection.fields.map((field) => field.name)\n const title = titleFieldOf(collection)?.name\n if (title === undefined || !names.includes(title)) return names\n return [title, ...names.filter((name) => name !== title)]\n}\n\n/** Deep-cloned canonical form (stable key order via the Zod shape). */\nexport function normalizeViewState(state: TableViewState): TableViewState {\n return tableViewStateSchema.parse(state)\n}\n\n/** Structural equality on normalized states — drives the \"unsaved changes\" affordance. */\nexport function viewStatesEqual(a: TableViewState, b: TableViewState): boolean {\n return JSON.stringify(normalizeViewState(a)) === JSON.stringify(normalizeViewState(b))\n}\n\n/** One column of a view resolved against the current schema (EC-213). */\nexport interface ResolvedColumn {\n /** The schema field name. */\n field: string\n /** Whether the column renders (`false` ⇒ in {@link TableViewState.hidden}). */\n visible: boolean\n}\n\n/**\n * Resolve a view's columns against the live schema (EC-213): every schema\n * field exactly once, in the view's saved order, with any fields the view\n * predates appended (visible) and any fields no longer in the schema dropped.\n * This is the single source of truth for \"which columns, in what order, shown\n * or hidden\" — order is preserved across hide/show because visibility lives in\n * `hidden`, never in the order.\n */\nexport function resolveColumns(\n collection: CollectionDef,\n state: TableViewState,\n): ResolvedColumn[] {\n const schemaNames = collection.fields.map((field) => field.name)\n const schema = new Set(schemaNames)\n const hidden = new Set(state.hidden)\n // Keep the saved order, restricted to live fields and de-duplicated (a\n // corrupted persisted state must never render one field as two columns).\n const ordered: string[] = []\n const seen = new Set<string>()\n for (const name of state.columns) {\n if (schema.has(name) && !seen.has(name)) {\n ordered.push(name)\n seen.add(name)\n }\n }\n const appended = schemaNames.filter((name) => !seen.has(name))\n return [...ordered, ...appended].map((field) => ({ field, visible: !hidden.has(field) }))\n}\n\n/** The visible columns of a view, in render order (EC-213). */\nexport function visibleColumns(collection: CollectionDef, state: TableViewState): string[] {\n return resolveColumns(collection, state)\n .filter((column) => column.visible)\n .map((column) => column.field)\n}\n\n/**\n * The title/link column — the document IDENTITY column (EC-242): the schema title\n * field, shown as the leftmost linked cell whose content is `resolveDocumentTitle`\n * (a string `titleField` or a function pattern). Always present regardless of\n * column visibility — it is the document's identity, not a toggleable data column —\n * and excluded from the data columns so the title never shows twice. Falls back to\n * the first visible column, then the first field, then `id`, when no title field\n * is resolvable (e.g. a function title with no backing `title` field).\n */\nexport function primaryColumn(collection: CollectionDef, state: TableViewState): string {\n return (\n titleFieldOf(collection)?.name ??\n visibleColumns(collection, state)[0] ??\n collection.fields[0]?.name ??\n 'id'\n )\n}\n\n/**\n * Reconcile `columns` to the full resolved order so column edits operate on a\n * complete, stable order (drops removed fields, pins appended ones), and prune\n * `hidden` to fields the live schema still has (a column removed from the schema\n * must not leave dead `hidden` cruft that trips dirty-detection / persists).\n */\nfunction withResolvedOrder(collection: CollectionDef, state: TableViewState): TableViewState {\n const columns = resolveColumns(collection, state).map((column) => column.field)\n const live = new Set(columns)\n const hidden = state.hidden.filter((name) => live.has(name))\n return { ...state, columns, hidden }\n}\n\n/**\n * The canonical, schema-resolved form of a view state — full column order with\n * `hidden` pruned to live fields (EC-213). Two states that render identically\n * share one canonical form, so dirty-detection never trips on a mere column\n * reconciliation or a stale `hidden` entry (only a real filter/sort/visibility/\n * order change counts as \"unsaved\").\n */\nexport function canonicalViewState(\n collection: CollectionDef,\n state: TableViewState,\n): TableViewState {\n return normalizeViewState(withResolvedOrder(collection, state))\n}\n\n/**\n * Toggle a column's visibility — position is preserved (EC-213): visibility\n * lives in `hidden`, the order in `columns` is untouched. Columns the saved\n * order predates are pinned into `columns` first so their place is stable.\n */\nexport function toggleColumn(\n collection: CollectionDef,\n state: TableViewState,\n field: string,\n): TableViewState {\n const base = withResolvedOrder(collection, state)\n const hidden = base.hidden.includes(field)\n ? base.hidden.filter((name) => name !== field)\n : [...base.hidden, field]\n return { ...base, hidden }\n}\n\n/** Move a column one position left (-1) or right (+1) in the full column order. */\nexport function moveColumn(\n collection: CollectionDef,\n state: TableViewState,\n field: string,\n delta: -1 | 1,\n): TableViewState {\n const base = withResolvedOrder(collection, state)\n const from = base.columns.indexOf(field)\n const to = from + delta\n if (from < 0 || to < 0 || to >= base.columns.length) return state\n const columns = [...base.columns]\n columns.splice(from, 1)\n columns.splice(to, 0, field)\n return { ...base, columns }\n}\n"]}
|
|
@@ -169,6 +169,12 @@ export const EN_MESSAGES = {
|
|
|
169
169
|
'cms.list.column.title': msg('Title'),
|
|
170
170
|
'cms.list.column.state': msg('State'),
|
|
171
171
|
'cms.list.column.url': msg('URL'),
|
|
172
|
+
// EC-346 — the State column's per-row badge (`DocStateBadge`); was hardcoded
|
|
173
|
+
// English. Distinct keys from `cms.editor.draftState`/`publishedState` (title
|
|
174
|
+
// case, editor header) and `cms.filter.state.*`/`cms.reference.*Badge` (filter
|
|
175
|
+
// builder / reference dialog) — same words, different UI contexts (EC-241).
|
|
176
|
+
'cms.docState.published': msg('published', 'Row State-column badge — lowercase token.'),
|
|
177
|
+
'cms.docState.draft': msg('draft', 'Row State-column badge — lowercase token.'),
|
|
172
178
|
'cms.list.view.all': msg('All documents', 'Name of the built-in default view.'),
|
|
173
179
|
'cms.list.unknownCollection': msg('Unknown collection {id}.'),
|
|
174
180
|
'cms.list.import.noId': msg('(no id)', 'Placeholder id in an import-rejected report.'),
|
|
@@ -213,8 +219,27 @@ export const EN_MESSAGES = {
|
|
|
213
219
|
'cms.filter.remove': msg('Remove filter'),
|
|
214
220
|
'cms.filter.valueAria': msg('Filter value'),
|
|
215
221
|
'cms.filter.valueListPlaceholder': msg('value, value, …'),
|
|
222
|
+
'cms.filter.pickRelation': msg('Pick…', 'Placeholder on a relation filter value before a target is picked.'),
|
|
216
223
|
'cms.filter.state.published': msg('published'),
|
|
217
224
|
'cms.filter.state.draft': msg('draft'),
|
|
225
|
+
// Operator labels (EC-345) — keyed by operator AND field type in `operatorLabelKey`.
|
|
226
|
+
'cms.filter.op.contains': msg('contains'),
|
|
227
|
+
'cms.filter.op.equals': msg('equals', 'Equality operator on text/number fields.'),
|
|
228
|
+
'cms.filter.op.is': msg('is', 'Equality operator on enum-like fields (boolean/select/relation/asset/state).'),
|
|
229
|
+
'cms.filter.op.startsWith': msg('starts with'),
|
|
230
|
+
'cms.filter.op.endsWith': msg('ends with'),
|
|
231
|
+
'cms.filter.op.isAnyOf': msg('is any of', 'The `in` operator: matches any value in the picked set.'),
|
|
232
|
+
'cms.filter.op.lessThan': msg('less than'),
|
|
233
|
+
'cms.filter.op.atMost': msg('at most', 'The ≤ operator on a number field.'),
|
|
234
|
+
'cms.filter.op.greaterThan': msg('greater than'),
|
|
235
|
+
'cms.filter.op.atLeast': msg('at least', 'The ≥ operator on a number field.'),
|
|
236
|
+
'cms.filter.op.on': msg('on', 'Date equality operator (e.g. “Published on”).'),
|
|
237
|
+
'cms.filter.op.before': msg('before', 'The < operator on a date field.'),
|
|
238
|
+
'cms.filter.op.onOrBefore': msg('on or before', 'The ≤ operator on a date field.'),
|
|
239
|
+
'cms.filter.op.after': msg('after', 'The > operator on a date field.'),
|
|
240
|
+
'cms.filter.op.onOrAfter': msg('on or after', 'The ≥ operator on a date field.'),
|
|
241
|
+
'cms.filter.op.isEmpty': msg('is empty'),
|
|
242
|
+
'cms.filter.op.isNotEmpty': msg('is not empty'),
|
|
218
243
|
'cms.cell.fallbackTitle': msg('Showing {source} (fallback) — no {requested} translation yet. Edit to translate.'),
|
|
219
244
|
'cms.cell.richText': msg('rich text', 'Placeholder for a rich-text cell whose preview is empty.'),
|
|
220
245
|
'cms.cell.itemCount.one': msg('{count} item'),
|