@elytracms/studio 0.0.28 → 0.0.31
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/redirects.ts +3 -0
- package/convex/schema.ts +2 -0
- package/dist/components/app-views/default-modules.d.ts +20 -3
- package/dist/components/app-views/default-modules.js +27 -19
- package/dist/components/app-views/default-modules.js.map +1 -1
- package/dist/components/app-views/studio-config.d.ts +10 -0
- package/dist/components/app-views/studio-config.js.map +1 -1
- package/dist/components/builder/property-inspector.js +3 -2
- package/dist/components/builder/property-inspector.js.map +1 -1
- package/dist/components/builder/reuse-context.js +2 -1
- package/dist/components/builder/reuse-context.js.map +1 -1
- package/dist/components/cms/cms-index.js +2 -1
- package/dist/components/cms/cms-index.js.map +1 -1
- package/dist/components/cms/cms-store.d.ts +11 -2
- package/dist/components/cms/cms-store.js +17 -4
- package/dist/components/cms/cms-store.js.map +1 -1
- package/dist/components/cms/collection-table.js +5 -4
- package/dist/components/cms/collection-table.js.map +1 -1
- package/dist/components/cms/detail-editor.js +5 -4
- package/dist/components/cms/detail-editor.js.map +1 -1
- package/dist/components/cms/editable-cell.js +3 -2
- package/dist/components/cms/editable-cell.js.map +1 -1
- package/dist/components/cms/field-control.js +6 -5
- package/dist/components/cms/field-control.js.map +1 -1
- package/dist/components/cms/scalar-value-editor.js +2 -1
- package/dist/components/cms/scalar-value-editor.js.map +1 -1
- package/dist/components/components/field-inspector.js +3 -2
- package/dist/components/components/field-inspector.js.map +1 -1
- package/dist/components/current-user-menu.js +4 -2
- package/dist/components/current-user-menu.js.map +1 -1
- package/dist/components/redirects/redirects-collection-view-source.d.ts +9 -2
- package/dist/components/redirects/redirects-collection-view-source.js +51 -8
- package/dist/components/redirects/redirects-collection-view-source.js.map +1 -1
- package/dist/components/redirects/redirects-module.d.ts +4 -1
- package/dist/components/redirects/redirects-module.js +4 -4
- package/dist/components/redirects/redirects-module.js.map +1 -1
- package/dist/components/redirects/redirects-store.js +5 -1
- package/dist/components/redirects/redirects-store.js.map +1 -1
- package/dist/customization.d.ts +7 -0
- package/dist/customization.js +6 -0
- package/dist/customization.js.map +1 -1
- package/dist/lib/cms-views/view-model.js +2 -2
- package/dist/lib/cms-views/view-model.js.map +1 -1
- package/dist/lib/i18n/i18n.d.ts +45 -0
- package/dist/lib/i18n/i18n.js +125 -0
- package/dist/lib/i18n/i18n.js.map +1 -0
- package/dist/lib/i18n/index.d.ts +12 -0
- package/dist/lib/i18n/index.js +4 -0
- package/dist/lib/i18n/index.js.map +1 -0
- package/dist/lib/i18n/messages.en.d.ts +15 -0
- package/dist/lib/i18n/messages.en.js +39 -0
- package/dist/lib/i18n/messages.en.js.map +1 -0
- package/dist/lib/i18n/types.d.ts +45 -0
- package/dist/lib/i18n/types.js +19 -0
- package/dist/lib/i18n/types.js.map +1 -0
- package/dist/lib/persistence/convex-adapter.js +2 -0
- package/dist/lib/persistence/convex-adapter.js.map +1 -1
- package/dist/lib/references/reference-model.js +4 -3
- package/dist/lib/references/reference-model.js.map +1 -1
- package/dist/lib/workspace/index.d.ts +8 -0
- package/dist/lib/workspace/index.js +10 -0
- package/dist/lib/workspace/index.js.map +1 -1
- package/dist/ui/badge.d.ts +1 -1
- package/package.json +6 -2
|
@@ -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 { documentDefaultLocale, resolveField, titleFieldOf } from '@elytracms/core/cms-core'\nimport type { CmsDocument, CollectionDef, FieldDef, LocaleConfig } 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: 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/** The collection's composite (non-scalar) field names — hidden in the default view. */\nfunction nonScalarColumns(collection: CollectionDef): string[] {\n return collection.fields.filter((f) => NON_DEFAULT_COLUMN_TYPES.has(f.type)).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 start\n // hidden so the default lens is the collection's scalars (EC-246b).\n columns: orderedColumnNames(collection),\n hidden: nonScalarColumns(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 name: z.string().min(1),\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/** A resolved view: identity + the table state it restores (matches `SavedView`). */\nexport interface ResolvedView {\n id: string\n name: string\n state: TableViewState\n}\n\n/** Slug used to derive a config view's stable id from its name. */\nfunction configViewId(name: string, index: number): string {\n const slug = name\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 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 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\n/** A fresh id for a saved view (uniqueness, not determinism, is the point). */\nexport function newViewId(): string {\n const random = Math.floor(Math.random() * 36 ** 6)\n .toString(36)\n .padStart(6, '0')\n return `view-${Date.now().toString(36)}-${random}`\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 { documentDefaultLocale, resolveField, resolveLabel, titleFieldOf } from '@elytracms/core/cms-core'\nimport type { CmsDocument, CollectionDef, FieldDef, LocaleConfig } 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: resolveLabel(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/** The collection's composite (non-scalar) field names — hidden in the default view. */\nfunction nonScalarColumns(collection: CollectionDef): string[] {\n return collection.fields.filter((f) => NON_DEFAULT_COLUMN_TYPES.has(f.type)).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 start\n // hidden so the default lens is the collection's scalars (EC-246b).\n columns: orderedColumnNames(collection),\n hidden: nonScalarColumns(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 name: z.string().min(1),\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/** A resolved view: identity + the table state it restores (matches `SavedView`). */\nexport interface ResolvedView {\n id: string\n name: string\n state: TableViewState\n}\n\n/** Slug used to derive a config view's stable id from its name. */\nfunction configViewId(name: string, index: number): string {\n const slug = name\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 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 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\n/** A fresh id for a saved view (uniqueness, not determinism, is the point). */\nexport function newViewId(): string {\n const random = Math.floor(Math.random() * 36 ** 6)\n .toString(36)\n .padStart(6, '0')\n return `view-${Date.now().toString(36)}-${random}`\n}\n"]}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { LocalizedString } from '@elytracms/core/cms-core';
|
|
2
|
+
import type { MessageCatalog, SourceCatalog } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Pick the best available locale for the browser's ordered language preferences
|
|
5
|
+
* (EC-241): an exact tag match wins, else the base language (`de-DE` → `de`), else
|
|
6
|
+
* `defaultLocale`. Pure + deterministic — the browser list is the only input.
|
|
7
|
+
*/
|
|
8
|
+
export declare function resolveActiveLocale(available: readonly string[], preferred: readonly string[], defaultLocale: string): string;
|
|
9
|
+
interface ResolvedI18n {
|
|
10
|
+
locale: string;
|
|
11
|
+
defaultLocale: string;
|
|
12
|
+
/** The active-locale catalog (empty when active === default → the English source is used). */
|
|
13
|
+
active: MessageCatalog;
|
|
14
|
+
source: SourceCatalog;
|
|
15
|
+
}
|
|
16
|
+
/** Resolve the studio i18n once per boot (config static; locale from the browser). */
|
|
17
|
+
export declare function resolveStudioI18n(): ResolvedI18n;
|
|
18
|
+
/**
|
|
19
|
+
* Translate a message key for the active UI locale (EC-241): active-locale string
|
|
20
|
+
* ?? English source ?? **the key itself** (a visible fallback + a one-time console
|
|
21
|
+
* signal — "failures are first-class", never a silent blank). Non-hook — reads the
|
|
22
|
+
* per-boot singleton, so config-build and pure code can localize too; {@link useT}
|
|
23
|
+
* is the React-idiomatic wrapper.
|
|
24
|
+
*/
|
|
25
|
+
export declare function translate(key: string, vars?: Record<string, string | number>): string;
|
|
26
|
+
/**
|
|
27
|
+
* Resolve a {@link LocalizedString} (field/collection/option label) against the
|
|
28
|
+
* active UI locale (EC-241) — **non-hook**, reading the per-boot singleton, so it is
|
|
29
|
+
* safe to call anywhere: a component body, a nested render helper, or config-build
|
|
30
|
+
* code. Returns `fallback` (default `''`) when the value is absent. Prefer this over
|
|
31
|
+
* `resolveLabel(x, useUiLocale())` at render sites — it localizes without a hook, so
|
|
32
|
+
* it can't trip the rules-of-hooks in a conditional/nested position.
|
|
33
|
+
*/
|
|
34
|
+
export declare function localizedLabel(value: LocalizedString | undefined, fallback?: string): string;
|
|
35
|
+
/** The active studio-UI locale (browser-resolved). */
|
|
36
|
+
export declare function useUiLocale(): string;
|
|
37
|
+
/** A `t(key, vars?)` translator bound to the active locale. */
|
|
38
|
+
export declare function useT(): (key: string, vars?: Record<string, string | number>) => string;
|
|
39
|
+
/** Resolve a {@link LocalizedString} label (field/collection/option) for the active locale. */
|
|
40
|
+
export declare function useLabel(): (value: LocalizedString | undefined, fallback?: string) => string;
|
|
41
|
+
/** Test seam: force the active locale (and clear the per-boot cache). Never used by app code. */
|
|
42
|
+
export declare function setUiLocaleForTests(locale: string | null): void;
|
|
43
|
+
/** Test seam: clear the per-boot resolution cache. Never used by app code. */
|
|
44
|
+
export declare function resetStudioI18nForTests(): void;
|
|
45
|
+
export {};
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
import { resolveLabel } from '@elytracms/core/cms-core';
|
|
3
|
+
import { resolveDeploymentI18n } from '@elytracms/studio/lib/workspace';
|
|
4
|
+
import { EN_MESSAGES } from './messages.en';
|
|
5
|
+
/**
|
|
6
|
+
* Studio-UI localization runtime (EC-241). Resolves the active UI locale from the
|
|
7
|
+
* **browser's preferred locale** (no persisted selector in v1 — that rides EC-240's
|
|
8
|
+
* deferred account settings), then translates message keys with an
|
|
9
|
+
* active-locale → English → visible-fallback chain. The studio config is static per
|
|
10
|
+
* boot, so resolution is memoised once (like `getWorkspaceState`); a locale switch
|
|
11
|
+
* needs a reload, which is all v1 offers.
|
|
12
|
+
*/
|
|
13
|
+
const BUILTIN_DEFAULT_LOCALE = 'en';
|
|
14
|
+
/**
|
|
15
|
+
* Pick the best available locale for the browser's ordered language preferences
|
|
16
|
+
* (EC-241): an exact tag match wins, else the base language (`de-DE` → `de`), else
|
|
17
|
+
* `defaultLocale`. Pure + deterministic — the browser list is the only input.
|
|
18
|
+
*/
|
|
19
|
+
export function resolveActiveLocale(available, preferred, defaultLocale) {
|
|
20
|
+
const lower = available.map((a) => a.toLowerCase());
|
|
21
|
+
for (const pref of preferred) {
|
|
22
|
+
const p = pref.toLowerCase();
|
|
23
|
+
const base = p.split('-')[0] ?? p;
|
|
24
|
+
const at = lower.findIndex((a) => a === p || a === base);
|
|
25
|
+
if (at >= 0)
|
|
26
|
+
return available[at];
|
|
27
|
+
}
|
|
28
|
+
return defaultLocale;
|
|
29
|
+
}
|
|
30
|
+
let cached = null;
|
|
31
|
+
let localeOverride = null;
|
|
32
|
+
function navigatorLanguages() {
|
|
33
|
+
if (typeof navigator === 'undefined')
|
|
34
|
+
return [];
|
|
35
|
+
if (navigator.languages && navigator.languages.length > 0)
|
|
36
|
+
return [...navigator.languages];
|
|
37
|
+
return navigator.language ? [navigator.language] : [];
|
|
38
|
+
}
|
|
39
|
+
const warned = new Set();
|
|
40
|
+
function warnOnce(message) {
|
|
41
|
+
if (warned.has(message))
|
|
42
|
+
return;
|
|
43
|
+
warned.add(message);
|
|
44
|
+
if (typeof console !== 'undefined')
|
|
45
|
+
console.warn(message);
|
|
46
|
+
}
|
|
47
|
+
/** Resolve the studio i18n once per boot (config static; locale from the browser). */
|
|
48
|
+
export function resolveStudioI18n() {
|
|
49
|
+
if (cached)
|
|
50
|
+
return cached;
|
|
51
|
+
const config = resolveDeploymentI18n() ?? {};
|
|
52
|
+
const defaultLocale = config.defaultLocale ?? BUILTIN_DEFAULT_LOCALE;
|
|
53
|
+
const locales = config.locales ?? {};
|
|
54
|
+
const available = [defaultLocale, ...Object.keys(locales).filter((l) => l !== defaultLocale)];
|
|
55
|
+
const locale = localeOverride ?? resolveActiveLocale(available, navigatorLanguages(), defaultLocale);
|
|
56
|
+
const active = locale === defaultLocale ? {} : (locales[locale] ?? {});
|
|
57
|
+
cached = { locale, defaultLocale, active, source: EN_MESSAGES };
|
|
58
|
+
// Coverage lint: warn once when the active locale doesn't cover every English key.
|
|
59
|
+
if (locale !== defaultLocale && locales[locale]) {
|
|
60
|
+
const missing = Object.keys(EN_MESSAGES).filter((k) => !(k in active));
|
|
61
|
+
if (missing.length > 0) {
|
|
62
|
+
warnOnce(`[i18n] locale "${locale}" is missing ${missing.length} message(s); falling back to English for those.`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return cached;
|
|
66
|
+
}
|
|
67
|
+
function interpolate(text, vars) {
|
|
68
|
+
return text.replace(/\{(\w+)\}/g, (whole, key) => (key in vars ? String(vars[key]) : whole));
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Translate a message key for the active UI locale (EC-241): active-locale string
|
|
72
|
+
* ?? English source ?? **the key itself** (a visible fallback + a one-time console
|
|
73
|
+
* signal — "failures are first-class", never a silent blank). Non-hook — reads the
|
|
74
|
+
* per-boot singleton, so config-build and pure code can localize too; {@link useT}
|
|
75
|
+
* is the React-idiomatic wrapper.
|
|
76
|
+
*/
|
|
77
|
+
export function translate(key, vars) {
|
|
78
|
+
const i18n = resolveStudioI18n();
|
|
79
|
+
const hit = i18n.active[key] ?? i18n.source[key]?.message;
|
|
80
|
+
if (hit === undefined) {
|
|
81
|
+
warnOnce(`[i18n] missing message key: "${key}"`);
|
|
82
|
+
return key;
|
|
83
|
+
}
|
|
84
|
+
return vars ? interpolate(hit, vars) : hit;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Resolve a {@link LocalizedString} (field/collection/option label) against the
|
|
88
|
+
* active UI locale (EC-241) — **non-hook**, reading the per-boot singleton, so it is
|
|
89
|
+
* safe to call anywhere: a component body, a nested render helper, or config-build
|
|
90
|
+
* code. Returns `fallback` (default `''`) when the value is absent. Prefer this over
|
|
91
|
+
* `resolveLabel(x, useUiLocale())` at render sites — it localizes without a hook, so
|
|
92
|
+
* it can't trip the rules-of-hooks in a conditional/nested position.
|
|
93
|
+
*/
|
|
94
|
+
export function localizedLabel(value, fallback = '') {
|
|
95
|
+
const { locale, defaultLocale } = resolveStudioI18n();
|
|
96
|
+
return resolveLabel(value, locale, defaultLocale) ?? fallback;
|
|
97
|
+
}
|
|
98
|
+
// --- React hooks (config static per boot → stable identities) ---
|
|
99
|
+
/** The active studio-UI locale (browser-resolved). */
|
|
100
|
+
export function useUiLocale() {
|
|
101
|
+
return resolveStudioI18n().locale;
|
|
102
|
+
}
|
|
103
|
+
/** A `t(key, vars?)` translator bound to the active locale. */
|
|
104
|
+
export function useT() {
|
|
105
|
+
return translate;
|
|
106
|
+
}
|
|
107
|
+
/** Resolve a {@link LocalizedString} label (field/collection/option) for the active locale. */
|
|
108
|
+
export function useLabel() {
|
|
109
|
+
return useMemo(() => {
|
|
110
|
+
const { locale, defaultLocale } = resolveStudioI18n();
|
|
111
|
+
return (value, fallback) => resolveLabel(value, locale, defaultLocale) ?? fallback ?? '';
|
|
112
|
+
}, []);
|
|
113
|
+
}
|
|
114
|
+
/** Test seam: force the active locale (and clear the per-boot cache). Never used by app code. */
|
|
115
|
+
export function setUiLocaleForTests(locale) {
|
|
116
|
+
localeOverride = locale;
|
|
117
|
+
cached = null;
|
|
118
|
+
warned.clear();
|
|
119
|
+
}
|
|
120
|
+
/** Test seam: clear the per-boot resolution cache. Never used by app code. */
|
|
121
|
+
export function resetStudioI18nForTests() {
|
|
122
|
+
cached = null;
|
|
123
|
+
warned.clear();
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=i18n.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"i18n.js","sourceRoot":"","sources":["../../../src/lib/i18n/i18n.ts"],"names":[],"mappings":"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","sourcesContent":["import { useMemo } from 'react'\nimport { resolveLabel } from '@elytracms/core/cms-core'\nimport type { LocalizedString } from '@elytracms/core/cms-core'\nimport { resolveDeploymentI18n } from '@elytracms/studio/lib/workspace'\nimport { EN_MESSAGES } from './messages.en'\nimport type { MessageCatalog, SourceCatalog } from './types'\n\n/**\n * Studio-UI localization runtime (EC-241). Resolves the active UI locale from the\n * **browser's preferred locale** (no persisted selector in v1 — that rides EC-240's\n * deferred account settings), then translates message keys with an\n * active-locale → English → visible-fallback chain. The studio config is static per\n * boot, so resolution is memoised once (like `getWorkspaceState`); a locale switch\n * needs a reload, which is all v1 offers.\n */\n\nconst BUILTIN_DEFAULT_LOCALE = 'en'\n\n/**\n * Pick the best available locale for the browser's ordered language preferences\n * (EC-241): an exact tag match wins, else the base language (`de-DE` → `de`), else\n * `defaultLocale`. Pure + deterministic — the browser list is the only input.\n */\nexport function resolveActiveLocale(\n available: readonly string[],\n preferred: readonly string[],\n defaultLocale: string,\n): string {\n const lower = available.map((a) => a.toLowerCase())\n for (const pref of preferred) {\n const p = pref.toLowerCase()\n const base = p.split('-')[0] ?? p\n const at = lower.findIndex((a) => a === p || a === base)\n if (at >= 0) return available[at]!\n }\n return defaultLocale\n}\n\ninterface ResolvedI18n {\n locale: string\n defaultLocale: string\n /** The active-locale catalog (empty when active === default → the English source is used). */\n active: MessageCatalog\n source: SourceCatalog\n}\n\nlet cached: ResolvedI18n | null = null\nlet localeOverride: string | null = null\n\nfunction navigatorLanguages(): string[] {\n if (typeof navigator === 'undefined') return []\n if (navigator.languages && navigator.languages.length > 0) return [...navigator.languages]\n return navigator.language ? [navigator.language] : []\n}\n\nconst warned = new Set<string>()\nfunction warnOnce(message: string): void {\n if (warned.has(message)) return\n warned.add(message)\n if (typeof console !== 'undefined') console.warn(message)\n}\n\n/** Resolve the studio i18n once per boot (config static; locale from the browser). */\nexport function resolveStudioI18n(): ResolvedI18n {\n if (cached) return cached\n const config = resolveDeploymentI18n() ?? {}\n const defaultLocale = config.defaultLocale ?? BUILTIN_DEFAULT_LOCALE\n const locales = config.locales ?? {}\n const available = [defaultLocale, ...Object.keys(locales).filter((l) => l !== defaultLocale)]\n const locale = localeOverride ?? resolveActiveLocale(available, navigatorLanguages(), defaultLocale)\n const active = locale === defaultLocale ? {} : (locales[locale] ?? {})\n cached = { locale, defaultLocale, active, source: EN_MESSAGES }\n // Coverage lint: warn once when the active locale doesn't cover every English key.\n if (locale !== defaultLocale && locales[locale]) {\n const missing = Object.keys(EN_MESSAGES).filter((k) => !(k in active))\n if (missing.length > 0) {\n warnOnce(\n `[i18n] locale \"${locale}\" is missing ${missing.length} message(s); falling back to English for those.`,\n )\n }\n }\n return cached\n}\n\nfunction interpolate(text: string, vars: Record<string, string | number>): string {\n return text.replace(/\\{(\\w+)\\}/g, (whole, key: string) => (key in vars ? String(vars[key]) : whole))\n}\n\n/**\n * Translate a message key for the active UI locale (EC-241): active-locale string\n * ?? English source ?? **the key itself** (a visible fallback + a one-time console\n * signal — \"failures are first-class\", never a silent blank). Non-hook — reads the\n * per-boot singleton, so config-build and pure code can localize too; {@link useT}\n * is the React-idiomatic wrapper.\n */\nexport function translate(key: string, vars?: Record<string, string | number>): string {\n const i18n = resolveStudioI18n()\n const hit = i18n.active[key] ?? i18n.source[key]?.message\n if (hit === undefined) {\n warnOnce(`[i18n] missing message key: \"${key}\"`)\n return key\n }\n return vars ? interpolate(hit, vars) : hit\n}\n\n/**\n * Resolve a {@link LocalizedString} (field/collection/option label) against the\n * active UI locale (EC-241) — **non-hook**, reading the per-boot singleton, so it is\n * safe to call anywhere: a component body, a nested render helper, or config-build\n * code. Returns `fallback` (default `''`) when the value is absent. Prefer this over\n * `resolveLabel(x, useUiLocale())` at render sites — it localizes without a hook, so\n * it can't trip the rules-of-hooks in a conditional/nested position.\n */\nexport function localizedLabel(value: LocalizedString | undefined, fallback = ''): string {\n const { locale, defaultLocale } = resolveStudioI18n()\n return resolveLabel(value, locale, defaultLocale) ?? fallback\n}\n\n// --- React hooks (config static per boot → stable identities) ---\n\n/** The active studio-UI locale (browser-resolved). */\nexport function useUiLocale(): string {\n return resolveStudioI18n().locale\n}\n\n/** A `t(key, vars?)` translator bound to the active locale. */\nexport function useT(): (key: string, vars?: Record<string, string | number>) => string {\n return translate\n}\n\n/** Resolve a {@link LocalizedString} label (field/collection/option) for the active locale. */\nexport function useLabel(): (value: LocalizedString | undefined, fallback?: string) => string {\n return useMemo(() => {\n const { locale, defaultLocale } = resolveStudioI18n()\n return (value, fallback) => resolveLabel(value, locale, defaultLocale) ?? fallback ?? ''\n }, [])\n}\n\n/** Test seam: force the active locale (and clear the per-boot cache). Never used by app code. */\nexport function setUiLocaleForTests(locale: string | null): void {\n localeOverride = locale\n cached = null\n warned.clear()\n}\n\n/** Test seam: clear the per-boot resolution cache. Never used by app code. */\nexport function resetStudioI18nForTests(): void {\n cached = null\n warned.clear()\n}\n"]}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@elytracms/studio/lib/i18n` — studio-UI localization (EC-241).
|
|
3
|
+
*
|
|
4
|
+
* - Public config surface (`StudioI18nConfig`, `MessageCatalog`, `msg`) is re-exported
|
|
5
|
+
* from `@elytracms/studio/customization` so a consumer authors catalogs there.
|
|
6
|
+
* - Studio components localize chrome via `translate`/`useT` and schema labels via
|
|
7
|
+
* `useLabel`.
|
|
8
|
+
*/
|
|
9
|
+
export type { SourceMessage, SourceCatalog, MessageCatalog, StudioI18nConfig } from './types';
|
|
10
|
+
export { msg } from './types';
|
|
11
|
+
export { EN_MESSAGES } from './messages.en';
|
|
12
|
+
export { resolveActiveLocale, resolveStudioI18n, translate, localizedLabel, useT, useUiLocale, useLabel, setUiLocaleForTests, resetStudioI18nForTests, } from './i18n';
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { msg } from './types';
|
|
2
|
+
export { EN_MESSAGES } from './messages.en';
|
|
3
|
+
export { resolveActiveLocale, resolveStudioI18n, translate, localizedLabel, useT, useUiLocale, useLabel, setUiLocaleForTests, resetStudioI18nForTests, } from './i18n';
|
|
4
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/lib/i18n/index.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,GAAG,EAAE,MAAM,SAAS,CAAA;AAC7B,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAC3C,OAAO,EACL,mBAAmB,EACnB,iBAAiB,EACjB,SAAS,EACT,cAAc,EACd,IAAI,EACJ,WAAW,EACX,QAAQ,EACR,mBAAmB,EACnB,uBAAuB,GACxB,MAAM,QAAQ,CAAA","sourcesContent":["/**\n * `@elytracms/studio/lib/i18n` — studio-UI localization (EC-241).\n *\n * - Public config surface (`StudioI18nConfig`, `MessageCatalog`, `msg`) is re-exported\n * from `@elytracms/studio/customization` so a consumer authors catalogs there.\n * - Studio components localize chrome via `translate`/`useT` and schema labels via\n * `useLabel`.\n */\nexport type { SourceMessage, SourceCatalog, MessageCatalog, StudioI18nConfig } from './types'\nexport { msg } from './types'\nexport { EN_MESSAGES } from './messages.en'\nexport {\n resolveActiveLocale,\n resolveStudioI18n,\n translate,\n localizedLabel,\n useT,\n useUiLocale,\n useLabel,\n setUiLocaleForTests,\n resetStudioI18nForTests,\n} from './i18n'\n"]}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { SourceCatalog } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* The engine's built-in **English source catalog** (EC-241) — the source of truth
|
|
4
|
+
* and the ultimate fallback for every studio-chrome string. A consumer adds a
|
|
5
|
+
* language via `defineStudio({ i18n: { locales: { de: deMessages } } })`; a key
|
|
6
|
+
* missing from the active locale falls back to the English `message` here, then to
|
|
7
|
+
* the key itself (a visible fallback + a one-time console signal), never a silent
|
|
8
|
+
* blank.
|
|
9
|
+
*
|
|
10
|
+
* v1 seeds a representative, high-traffic slice — the section rail + the shell user
|
|
11
|
+
* menu — to prove the pipeline end-to-end. Extracting every remaining studio string
|
|
12
|
+
* into keys is an ongoing mechanical sweep (see EC-241 "scope honesty"), not a
|
|
13
|
+
* silent omission.
|
|
14
|
+
*/
|
|
15
|
+
export declare const EN_MESSAGES: SourceCatalog;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { msg } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* The engine's built-in **English source catalog** (EC-241) — the source of truth
|
|
4
|
+
* and the ultimate fallback for every studio-chrome string. A consumer adds a
|
|
5
|
+
* language via `defineStudio({ i18n: { locales: { de: deMessages } } })`; a key
|
|
6
|
+
* missing from the active locale falls back to the English `message` here, then to
|
|
7
|
+
* the key itself (a visible fallback + a one-time console signal), never a silent
|
|
8
|
+
* blank.
|
|
9
|
+
*
|
|
10
|
+
* v1 seeds a representative, high-traffic slice — the section rail + the shell user
|
|
11
|
+
* menu — to prove the pipeline end-to-end. Extracting every remaining studio string
|
|
12
|
+
* into keys is an ongoing mechanical sweep (see EC-241 "scope honesty"), not a
|
|
13
|
+
* silent omission.
|
|
14
|
+
*/
|
|
15
|
+
export const EN_MESSAGES = {
|
|
16
|
+
// --- Built-in navigation views (the section rail) ---
|
|
17
|
+
'nav.cms.label': msg('CMS'),
|
|
18
|
+
'nav.cms.description': msg('Model collections, fields, and documents.'),
|
|
19
|
+
'nav.components.label': msg('Components'),
|
|
20
|
+
'nav.components.description': msg('Browse the registry of primitives and project components.'),
|
|
21
|
+
'nav.redirects.label': msg('Redirects'),
|
|
22
|
+
'nav.redirects.description': msg('Create and manage URL redirects (301/302) for this site.'),
|
|
23
|
+
'nav.assets.label': msg('Assets'),
|
|
24
|
+
'nav.assets.description': msg('Manage uploaded media and metadata.'),
|
|
25
|
+
'nav.users.label': msg('Users'),
|
|
26
|
+
'nav.users.description': msg('Manage who has access to this deployment and their role.'),
|
|
27
|
+
'nav.extensions.label': msg('Extensions'),
|
|
28
|
+
'nav.extensions.description': msg('Install components, field types, adapters, and templates from the marketplace.'),
|
|
29
|
+
'nav.settings.label': msg('Settings'),
|
|
30
|
+
'nav.settings.description': msg('Configure project metadata and locales.'),
|
|
31
|
+
// --- Current-user menu (shell header) ---
|
|
32
|
+
'userMenu.accountMenu': msg('Account menu', 'aria-label on the avatar button that opens the user menu.'),
|
|
33
|
+
'userMenu.lightMode': msg('Light mode', 'Menu item: switch the studio to the light colour theme.'),
|
|
34
|
+
'userMenu.darkMode': msg('Dark mode', 'Menu item: switch the studio to the dark colour theme.'),
|
|
35
|
+
'userMenu.accountSettings': msg('Account settings'),
|
|
36
|
+
'userMenu.soon': msg('Soon', 'Badge on a feature that is not available yet.'),
|
|
37
|
+
'userMenu.signOut': msg('Sign out'),
|
|
38
|
+
};
|
|
39
|
+
//# sourceMappingURL=messages.en.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"messages.en.js","sourceRoot":"","sources":["../../../src/lib/i18n/messages.en.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,SAAS,CAAA;AAG7B;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,MAAM,WAAW,GAAkB;IACxC,uDAAuD;IACvD,eAAe,EAAE,GAAG,CAAC,KAAK,CAAC;IAC3B,qBAAqB,EAAE,GAAG,CAAC,2CAA2C,CAAC;IACvE,sBAAsB,EAAE,GAAG,CAAC,YAAY,CAAC;IACzC,4BAA4B,EAAE,GAAG,CAAC,2DAA2D,CAAC;IAC9F,qBAAqB,EAAE,GAAG,CAAC,WAAW,CAAC;IACvC,2BAA2B,EAAE,GAAG,CAAC,0DAA0D,CAAC;IAC5F,kBAAkB,EAAE,GAAG,CAAC,QAAQ,CAAC;IACjC,wBAAwB,EAAE,GAAG,CAAC,qCAAqC,CAAC;IACpE,iBAAiB,EAAE,GAAG,CAAC,OAAO,CAAC;IAC/B,uBAAuB,EAAE,GAAG,CAAC,0DAA0D,CAAC;IACxF,sBAAsB,EAAE,GAAG,CAAC,YAAY,CAAC;IACzC,4BAA4B,EAAE,GAAG,CAC/B,gFAAgF,CACjF;IACD,oBAAoB,EAAE,GAAG,CAAC,UAAU,CAAC;IACrC,0BAA0B,EAAE,GAAG,CAAC,yCAAyC,CAAC;IAE1E,2CAA2C;IAC3C,sBAAsB,EAAE,GAAG,CAAC,cAAc,EAAE,2DAA2D,CAAC;IACxG,oBAAoB,EAAE,GAAG,CAAC,YAAY,EAAE,yDAAyD,CAAC;IAClG,mBAAmB,EAAE,GAAG,CAAC,WAAW,EAAE,wDAAwD,CAAC;IAC/F,0BAA0B,EAAE,GAAG,CAAC,kBAAkB,CAAC;IACnD,eAAe,EAAE,GAAG,CAAC,MAAM,EAAE,+CAA+C,CAAC;IAC7E,kBAAkB,EAAE,GAAG,CAAC,UAAU,CAAC;CACpC,CAAA","sourcesContent":["import { msg } from './types'\nimport type { SourceCatalog } from './types'\n\n/**\n * The engine's built-in **English source catalog** (EC-241) — the source of truth\n * and the ultimate fallback for every studio-chrome string. A consumer adds a\n * language via `defineStudio({ i18n: { locales: { de: deMessages } } })`; a key\n * missing from the active locale falls back to the English `message` here, then to\n * the key itself (a visible fallback + a one-time console signal), never a silent\n * blank.\n *\n * v1 seeds a representative, high-traffic slice — the section rail + the shell user\n * menu — to prove the pipeline end-to-end. Extracting every remaining studio string\n * into keys is an ongoing mechanical sweep (see EC-241 \"scope honesty\"), not a\n * silent omission.\n */\nexport const EN_MESSAGES: SourceCatalog = {\n // --- Built-in navigation views (the section rail) ---\n 'nav.cms.label': msg('CMS'),\n 'nav.cms.description': msg('Model collections, fields, and documents.'),\n 'nav.components.label': msg('Components'),\n 'nav.components.description': msg('Browse the registry of primitives and project components.'),\n 'nav.redirects.label': msg('Redirects'),\n 'nav.redirects.description': msg('Create and manage URL redirects (301/302) for this site.'),\n 'nav.assets.label': msg('Assets'),\n 'nav.assets.description': msg('Manage uploaded media and metadata.'),\n 'nav.users.label': msg('Users'),\n 'nav.users.description': msg('Manage who has access to this deployment and their role.'),\n 'nav.extensions.label': msg('Extensions'),\n 'nav.extensions.description': msg(\n 'Install components, field types, adapters, and templates from the marketplace.',\n ),\n 'nav.settings.label': msg('Settings'),\n 'nav.settings.description': msg('Configure project metadata and locales.'),\n\n // --- Current-user menu (shell header) ---\n 'userMenu.accountMenu': msg('Account menu', 'aria-label on the avatar button that opens the user menu.'),\n 'userMenu.lightMode': msg('Light mode', 'Menu item: switch the studio to the light colour theme.'),\n 'userMenu.darkMode': msg('Dark mode', 'Menu item: switch the studio to the dark colour theme.'),\n 'userMenu.accountSettings': msg('Account settings'),\n 'userMenu.soon': msg('Soon', 'Badge on a feature that is not available yet.'),\n 'userMenu.signOut': msg('Sign out'),\n}\n"]}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Studio-UI localization types (EC-241) — the leaf module both the message catalog
|
|
3
|
+
* and the resolver import, so nothing here may import studio runtime code (keeps the
|
|
4
|
+
* `defineStudio` config surface free of import cycles).
|
|
5
|
+
*
|
|
6
|
+
* This is the **studio-chrome** axis (engine-owned strings). Schema labels are a
|
|
7
|
+
* separate mechanism — a {@link import('@elytracms/core/cms-core').LocalizedString}
|
|
8
|
+
* on the field/collection def, resolved via `resolveLabel`. Content-*value*
|
|
9
|
+
* localization is a third, unrelated axis (`cms-core/localization.ts`).
|
|
10
|
+
*/
|
|
11
|
+
/** A source-catalog entry: the English string + an optional translator hint. */
|
|
12
|
+
export interface SourceMessage {
|
|
13
|
+
/** The English (source-of-truth) string. */
|
|
14
|
+
message: string;
|
|
15
|
+
/**
|
|
16
|
+
* Translator hint/context — the same word can translate differently by context
|
|
17
|
+
* (the standard `msgctxt`/Lingui-comment idea). **Authoring/translation-time only:**
|
|
18
|
+
* it lives on the in-engine source catalog and is NOT shipped in the flat
|
|
19
|
+
* consumer-provided translation catalogs.
|
|
20
|
+
*/
|
|
21
|
+
description?: string;
|
|
22
|
+
}
|
|
23
|
+
/** The in-engine English source catalog: message key → {@link SourceMessage}. */
|
|
24
|
+
export type SourceCatalog = Record<string, SourceMessage>;
|
|
25
|
+
/** A consumer-provided translation catalog: flat message key → localized string. */
|
|
26
|
+
export type MessageCatalog = Record<string, string>;
|
|
27
|
+
/** The studio-UI localization config passed to `defineStudio({ i18n })` (EC-241). */
|
|
28
|
+
export interface StudioI18nConfig {
|
|
29
|
+
/**
|
|
30
|
+
* The locale used when the browser's preferred locale matches nothing available.
|
|
31
|
+
* Defaults to `'en'` (the in-engine source language).
|
|
32
|
+
*/
|
|
33
|
+
defaultLocale?: string;
|
|
34
|
+
/**
|
|
35
|
+
* Imported translation catalogs keyed by locale code, e.g. `{ de: deMessages }`.
|
|
36
|
+
* English is always available as the built-in source, so it need not be listed.
|
|
37
|
+
*/
|
|
38
|
+
locales?: Record<string, MessageCatalog>;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Author a source-catalog entry with an optional translator hint (EC-241). Kept in
|
|
42
|
+
* this leaf module so `messages.en.ts` can build the catalog without importing the
|
|
43
|
+
* resolver (which would import studio runtime code).
|
|
44
|
+
*/
|
|
45
|
+
export declare function msg(message: string, description?: string): SourceMessage;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Studio-UI localization types (EC-241) — the leaf module both the message catalog
|
|
3
|
+
* and the resolver import, so nothing here may import studio runtime code (keeps the
|
|
4
|
+
* `defineStudio` config surface free of import cycles).
|
|
5
|
+
*
|
|
6
|
+
* This is the **studio-chrome** axis (engine-owned strings). Schema labels are a
|
|
7
|
+
* separate mechanism — a {@link import('@elytracms/core/cms-core').LocalizedString}
|
|
8
|
+
* on the field/collection def, resolved via `resolveLabel`. Content-*value*
|
|
9
|
+
* localization is a third, unrelated axis (`cms-core/localization.ts`).
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Author a source-catalog entry with an optional translator hint (EC-241). Kept in
|
|
13
|
+
* this leaf module so `messages.en.ts` can build the catalog without importing the
|
|
14
|
+
* resolver (which would import studio runtime code).
|
|
15
|
+
*/
|
|
16
|
+
export function msg(message, description) {
|
|
17
|
+
return description !== undefined ? { message, description } : { message };
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/lib/i18n/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAmCH;;;;GAIG;AACH,MAAM,UAAU,GAAG,CAAC,OAAe,EAAE,WAAoB;IACvD,OAAO,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAA;AAC3E,CAAC","sourcesContent":["/**\n * Studio-UI localization types (EC-241) — the leaf module both the message catalog\n * and the resolver import, so nothing here may import studio runtime code (keeps the\n * `defineStudio` config surface free of import cycles).\n *\n * This is the **studio-chrome** axis (engine-owned strings). Schema labels are a\n * separate mechanism — a {@link import('@elytracms/core/cms-core').LocalizedString}\n * on the field/collection def, resolved via `resolveLabel`. Content-*value*\n * localization is a third, unrelated axis (`cms-core/localization.ts`).\n */\n\n/** A source-catalog entry: the English string + an optional translator hint. */\nexport interface SourceMessage {\n /** The English (source-of-truth) string. */\n message: string\n /**\n * Translator hint/context — the same word can translate differently by context\n * (the standard `msgctxt`/Lingui-comment idea). **Authoring/translation-time only:**\n * it lives on the in-engine source catalog and is NOT shipped in the flat\n * consumer-provided translation catalogs.\n */\n description?: string\n}\n\n/** The in-engine English source catalog: message key → {@link SourceMessage}. */\nexport type SourceCatalog = Record<string, SourceMessage>\n\n/** A consumer-provided translation catalog: flat message key → localized string. */\nexport type MessageCatalog = Record<string, string>\n\n/** The studio-UI localization config passed to `defineStudio({ i18n })` (EC-241). */\nexport interface StudioI18nConfig {\n /**\n * The locale used when the browser's preferred locale matches nothing available.\n * Defaults to `'en'` (the in-engine source language).\n */\n defaultLocale?: string\n /**\n * Imported translation catalogs keyed by locale code, e.g. `{ de: deMessages }`.\n * English is always available as the built-in source, so it need not be listed.\n */\n locales?: Record<string, MessageCatalog>\n}\n\n/**\n * Author a source-catalog entry with an optional translator hint (EC-241). Kept in\n * this leaf module so `messages.en.ts` can build the catalog without importing the\n * resolver (which would import studio runtime code).\n */\nexport function msg(message: string, description?: string): SourceMessage {\n return description !== undefined ? { message, description } : { message }\n}\n"]}
|
|
@@ -587,6 +587,7 @@ function redirectEntryFromDoc(doc, projectId) {
|
|
|
587
587
|
to: doc.to,
|
|
588
588
|
permanent: doc.permanent,
|
|
589
589
|
enabled: doc.enabled,
|
|
590
|
+
...(doc.meta !== undefined ? { meta: doc.meta } : {}),
|
|
590
591
|
createdAt: doc.createdAt,
|
|
591
592
|
updatedAt: doc.updatedAt,
|
|
592
593
|
};
|
|
@@ -614,6 +615,7 @@ class ConvexRedirectRepository {
|
|
|
614
615
|
to: input.to,
|
|
615
616
|
permanent: input.permanent ?? true,
|
|
616
617
|
enabled: input.enabled ?? true,
|
|
618
|
+
...(input.meta !== undefined ? { meta: input.meta } : {}),
|
|
617
619
|
createdAt: now,
|
|
618
620
|
updatedAt: now,
|
|
619
621
|
}));
|