@elytracms/studio 0.0.69 → 0.0.71
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/account/self-account-view-source.js +1 -0
- package/dist/components/account/self-account-view-source.js.map +1 -1
- package/dist/components/app-views/studio-config.d.ts +22 -0
- package/dist/components/app-views/studio-config.js.map +1 -1
- package/dist/components/assets/assets-collection-view-source.js +1 -0
- package/dist/components/assets/assets-collection-view-source.js.map +1 -1
- package/dist/components/builder/blocks-model.d.ts +91 -29
- package/dist/components/builder/blocks-model.js +83 -54
- package/dist/components/builder/blocks-model.js.map +1 -1
- package/dist/components/builder/blocks-panel.d.ts +40 -12
- package/dist/components/builder/blocks-panel.js +88 -34
- package/dist/components/builder/blocks-panel.js.map +1 -1
- package/dist/components/builder/fixture-thumbnail.d.ts +13 -23
- package/dist/components/builder/fixture-thumbnail.js +27 -99
- package/dist/components/builder/fixture-thumbnail.js.map +1 -1
- package/dist/components/builder/spacing-handles.d.ts +14 -11
- package/dist/components/builder/spacing-handles.js +33 -57
- package/dist/components/builder/spacing-handles.js.map +1 -1
- package/dist/components/cms/collection-table.js +19 -2
- package/dist/components/cms/collection-table.js.map +1 -1
- package/dist/components/cms/detail-editor.js +73 -51
- package/dist/components/cms/detail-editor.js.map +1 -1
- package/dist/components/cms/duplicate-document.d.ts +45 -0
- package/dist/components/cms/duplicate-document.js +141 -0
- package/dist/components/cms/duplicate-document.js.map +1 -0
- package/dist/components/cms/field-anchors.d.ts +67 -0
- package/dist/components/cms/field-anchors.js +91 -0
- package/dist/components/cms/field-anchors.js.map +1 -0
- package/dist/components/cms/issue-copy.d.ts +45 -0
- package/dist/components/cms/issue-copy.js +89 -0
- package/dist/components/cms/issue-copy.js.map +1 -0
- package/dist/components/cms/issues-pane.d.ts +11 -2
- package/dist/components/cms/issues-pane.js +22 -4
- package/dist/components/cms/issues-pane.js.map +1 -1
- package/dist/components/cms/object-array-field.d.ts +2 -0
- package/dist/components/cms/object-array-field.js +75 -21
- package/dist/components/cms/object-array-field.js.map +1 -1
- package/dist/components/cms/row-actions.d.ts +10 -3
- package/dist/components/cms/row-actions.js +8 -0
- package/dist/components/cms/row-actions.js.map +1 -1
- package/dist/components/cms/use-field-focus.d.ts +25 -9
- package/dist/components/cms/use-field-focus.js +80 -15
- package/dist/components/cms/use-field-focus.js.map +1 -1
- package/dist/components/collection-view/cms-collection-view-source.js +1 -0
- package/dist/components/collection-view/cms-collection-view-source.js.map +1 -1
- package/dist/components/redirects/redirects-collection-view-source.js +1 -0
- package/dist/components/redirects/redirects-collection-view-source.js.map +1 -1
- package/dist/components/session-expired-overlay.d.ts +16 -0
- package/dist/components/session-expired-overlay.js +22 -6
- package/dist/components/session-expired-overlay.js.map +1 -1
- package/dist/components/sign-in-view.d.ts +10 -1
- package/dist/components/sign-in-view.js +14 -3
- package/dist/components/sign-in-view.js.map +1 -1
- package/dist/components/users/users-collection-view-source.js +1 -0
- package/dist/components/users/users-collection-view-source.js.map +1 -1
- package/dist/customization.d.ts +18 -1
- package/dist/customization.js +8 -0
- package/dist/customization.js.map +1 -1
- package/dist/lib/cms-views/view-model.d.ts +6 -6
- package/dist/lib/collection-view/collection-view-source.d.ts +8 -0
- package/dist/lib/collection-view/collection-view-source.js.map +1 -1
- package/dist/lib/i18n/messages.de.js +44 -1
- package/dist/lib/i18n/messages.de.js.map +1 -1
- package/dist/lib/i18n/messages.en.d.ts +39 -0
- package/dist/lib/i18n/messages.en.js +47 -1
- package/dist/lib/i18n/messages.en.js.map +1 -1
- package/dist/lib/persona/labels.d.ts +5 -1
- package/dist/lib/persona/labels.js +43 -30
- package/dist/lib/persona/labels.js.map +1 -1
- package/dist/lib/workspace/index.d.ts +10 -0
- package/dist/lib/workspace/index.js +11 -0
- package/dist/lib/workspace/index.js.map +1 -1
- package/dist/previews/enumerate.d.ts +68 -0
- package/dist/previews/enumerate.js +78 -0
- package/dist/previews/enumerate.js.map +1 -0
- package/dist/previews/harness.d.ts +50 -0
- package/dist/previews/harness.js +123 -0
- package/dist/previews/harness.js.map +1 -0
- package/dist/previews/manifest-format.d.ts +57 -0
- package/dist/previews/manifest-format.js +59 -0
- package/dist/previews/manifest-format.js.map +1 -0
- package/dist/ui/dialog.d.ts +9 -1
- package/dist/ui/dialog.js +8 -2
- package/dist/ui/dialog.js.map +1 -1
- package/dist/ui/field.d.ts +24 -1
- package/dist/ui/field.js +23 -2
- package/dist/ui/field.js.map +1 -1
- package/package.json +10 -6
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"users-collection-view-source.js","sourceRoot":"","sources":["../../../src/components/users/users-collection-view-source.ts"],"names":[],"mappings":"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","sourcesContent":["import { useCallback } from 'react'\nimport type { CmsDocument, CmsValidationIssue, DocumentRef, FieldDef, Locale, LocaleConfig } from '@elytracms/core/cms-core'\nimport type { ProjectRole, SaveUserInput, UserRecord } from '@elytracms/core/persistence'\nimport { studioPath } from '@elytracms/studio/router'\nimport type {\n CollectionViewCapabilities,\n CollectionViewSource,\n FieldAccess,\n} from '@elytracms/studio/lib/collection-view/collection-view-source'\nimport { useAuth } from '../auth-context'\nimport { useUsers } from './users-store'\nimport { usersCollectionDef } from './users-schema'\nimport { metaBagProjection, normalizeOkResult, useRailViewSource } from '../rail-view-source'\n\n/** The users rail is not localized — one implicit locale keeps the lens honest. */\nconst USERS_LOCALE: Locale = 'en'\nconst USERS_LOCALE_CONFIG: LocaleConfig = { default: USERS_LOCALE, locales: [USERS_LOCALE] }\n\n/**\n * The capability profile of the users rail (EC-240): admin-only, no\n * publish/versions/create/import (identities are minted by sign-up and their\n * bytes are auth-owned), remove allowed (revoke membership). Hoisted to a\n * module-level constant (no prop/state dependency, EC-327) — mirrors\n * `ASSET_COLLECTION_CAPABILITIES`.\n */\nexport const USER_COLLECTION_CAPABILITIES: CollectionViewCapabilities = {\n publish: false,\n versions: false,\n locales: false,\n inlineEdit: false,\n create: false, // identities are minted by sign-up, never here\n remove: true, // revoke membership (does not delete the auth identity)\n bulk: true, // EC-433: bulk delete = the same per-id revoke, honestly reported\n liveUrl: false,\n hierarchy: false,\n importExport: false, // credential records are never dumped to raw JSON\n adminOnly: true,\n}\n\n/** The built-in field names — everything else in `values` is a custom `meta` field. */\nconst IDENTITY_FIELDS = new Set(['name', 'email', 'role'])\n\n/** Project the auth-identity + membership join into the lens's `values` shape. */\nexport function userToDocument(record: UserRecord): CmsDocument {\n return {\n collection: usersCollectionDef.id,\n id: record.id,\n values: {\n // Display title falls through empties (name → email → id) so a nameless,\n // blank-email member never renders as a blank row; `email` keeps its real value.\n name: record.name || record.email || record.id,\n email: record.email,\n role: record.roles[0] ?? 'viewer',\n ...(record.meta ?? {}),\n },\n }\n}\n\n/**\n * Principal-based field access for the users rail (EC-240, AD-18). `role` is\n * authority: **read-only when you are editing YOURSELF** — you don't change your\n * own role (the server integrity floor also guards it) — and **editable otherwise**\n * (the rail is admin-only, so a non-self editor is an admin acting on someone else).\n * Identity fields (name/email) follow their schema `form.readOnly`; everything else\n * is editable. Pure, so the self/other rule is unit-testable without the auth hook.\n */\nexport function userFieldAccess(\n field: FieldDef,\n record: CmsDocument | undefined,\n currentUserId: string | undefined,\n): FieldAccess {\n if (field.name === 'role') {\n return record?.id === currentUserId ? 'read' : 'edit'\n }\n return field.form?.readOnly ? 'read' : 'edit'\n}\n\n/** Map an edited lens document back to the write contract (role + custom `meta`). */\nexport function documentToSaveInput(doc: CmsDocument): SaveUserInput {\n const values = doc.values ?? {}\n const meta = metaBagProjection(values, IDENTITY_FIELDS)\n const role = typeof values.role === 'string' ? (values.role as ProjectRole) : 'viewer'\n return { id: doc.id, roles: [role], ...(Object.keys(meta).length > 0 ? { meta } : {}) }\n}\n\n/** `validate` for the users rail: no-op — a member's identity fields aren't\n * schema-validated the way CMS documents are (240a-era; a registry-driven\n * validation may land alongside a future custom-field schema). */\nfunction noValidation(): CmsValidationIssue[] {\n return []\n}\n\n/** `create` when `capabilities.create === false` — never reached (identities\n * are minted by sign-up, not the users rail). */\nfunction createUnsupported(): DocumentRef {\n throw new Error('users are created by sign-up, not the users rail')\n}\n\n/**\n * Assemble a {@link CollectionViewSource} for the **users rail** (EC-240, AD-19) —\n * the reuse proof that the portable list/edit surfaces serve a non-`cmsDocuments`\n * rail. Mirrors {@link useCmsCollectionViewSource}, over the users store instead of\n * the CMS store, with the users capability profile: admin-only, no\n * publish/versions/create/import (identities are minted by sign-up and their bytes\n * are auth-owned), remove allowed (revoke membership). Identity fields are\n * read-only; an admin edits a member's role (and future custom `meta` fields).\n *\n * Must be mounted under a `UsersProvider` (for {@link useUsers}).\n *\n * The shared scaffold (`useRailViewSource`, EC-327) owns `viewState`/`lastError`\n * plumbing and the assembled source; this hook supplies only the users-store\n * reads/writes, the capability profile, and the principal-based `fieldAccess`.\n */\nexport function useUsersCollectionViewSource(projectId: string): CollectionViewSource {\n const users = useUsers()\n // The signed-in principal — so `role` locks to read-only when an admin opens\n // their OWN record (self ≠ other; the server floor enforces it regardless).\n const auth = useAuth()\n const currentUserId = auth.session?.user?.id\n\n const documents = useCallback(() => users.users.map(userToDocument), [users.users])\n const get = useCallback(\n (ref: DocumentRef): CmsDocument | undefined => {\n const record = users.users.find((u) => u.id === ref.id)\n return record ? userToDocument(record) : undefined\n },\n [users.users],\n )\n const titleOf = useCallback(\n (ref: DocumentRef): string => {\n const record = users.users.find((u) => u.id === ref.id)\n return record?.name || record?.email || ref.id\n },\n [users.users],\n )\n const fieldAccess = useCallback(\n (field: FieldDef, record?: CmsDocument): FieldAccess => userFieldAccess(field, record, currentUserId),\n [currentUserId],\n )\n const save = useCallback((doc: CmsDocument) => users.save(documentToSaveInput(doc)), [users])\n const remove = useCallback((ref: DocumentRef) => users.remove(ref.id), [users])\n\n return useRailViewSource({\n projectId,\n schema: usersCollectionDef,\n capabilities: USER_COLLECTION_CAPABILITIES,\n localeConfig: USERS_LOCALE_CONFIG,\n documents,\n get,\n titleOf,\n // No `referenceCandidates`/`collections` override: users carry no relation\n // fields in v1, so there are no cross-collection candidates to resolve; the\n // lens is the only collection in scope (both factory defaults apply).\n // The users rail now has a per-member detail surface (EC-240): rows link to\n // it, and the edit shell's back-crumb / post-delete nav return to the list.\n detailPath: (id: string) => studioPath.user(id),\n listPath: studioPath.users(),\n // Principal-based tri-state: `role` is read-only for self, editable for an\n // admin acting on another; identity fields follow their schema readOnly.\n fieldAccess,\n validate: noValidation,\n create: createUnsupported,\n save,\n remove,\n normalizeSaveResult: normalizeOkResult,\n normalizeRemoveResult: normalizeOkResult,\n })\n}\n"]}
|
|
1
|
+
{"version":3,"file":"users-collection-view-source.js","sourceRoot":"","sources":["../../../src/components/users/users-collection-view-source.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,OAAO,CAAA;AAGnC,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAA;AAMrD,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAA;AACzC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AACxC,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAA;AACnD,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAA;AAE7F,mFAAmF;AACnF,MAAM,YAAY,GAAW,IAAI,CAAA;AACjC,MAAM,mBAAmB,GAAiB,EAAE,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,CAAC,YAAY,CAAC,EAAE,CAAA;AAE5F;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,4BAA4B,GAA+B;IACtE,OAAO,EAAE,KAAK;IACd,QAAQ,EAAE,KAAK;IACf,OAAO,EAAE,KAAK;IACd,UAAU,EAAE,KAAK;IACjB,MAAM,EAAE,KAAK,EAAE,+CAA+C;IAC9D,SAAS,EAAE,KAAK,EAAE,6CAA6C;IAC/D,MAAM,EAAE,IAAI,EAAE,wDAAwD;IACtE,IAAI,EAAE,IAAI,EAAE,kEAAkE;IAC9E,OAAO,EAAE,KAAK;IACd,SAAS,EAAE,KAAK;IAChB,YAAY,EAAE,KAAK,EAAE,kDAAkD;IACvE,SAAS,EAAE,IAAI;CAChB,CAAA;AAED,uFAAuF;AACvF,MAAM,eAAe,GAAG,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC,CAAA;AAE1D,kFAAkF;AAClF,MAAM,UAAU,cAAc,CAAC,MAAkB;IAC/C,OAAO;QACL,UAAU,EAAE,kBAAkB,CAAC,EAAE;QACjC,EAAE,EAAE,MAAM,CAAC,EAAE;QACb,MAAM,EAAE;YACN,yEAAyE;YACzE,iFAAiF;YACjF,IAAI,EAAE,MAAM,CAAC,IAAI,IAAI,MAAM,CAAC,KAAK,IAAI,MAAM,CAAC,EAAE;YAC9C,KAAK,EAAE,MAAM,CAAC,KAAK;YACnB,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,QAAQ;YACjC,GAAG,CAAC,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;SACvB;KACF,CAAA;AACH,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,eAAe,CAC7B,KAAe,EACf,MAA+B,EAC/B,aAAiC;IAEjC,IAAI,KAAK,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;QAC1B,OAAO,MAAM,EAAE,EAAE,KAAK,aAAa,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAA;IACvD,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAA;AAC/C,CAAC;AAED,qFAAqF;AACrF,MAAM,UAAU,mBAAmB,CAAC,GAAgB;IAClD,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,IAAI,EAAE,CAAA;IAC/B,MAAM,IAAI,GAAG,iBAAiB,CAAC,MAAM,EAAE,eAAe,CAAC,CAAA;IACvD,MAAM,IAAI,GAAG,OAAO,MAAM,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAE,MAAM,CAAC,IAAoB,CAAC,CAAC,CAAC,QAAQ,CAAA;IACtF,OAAO,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,KAAK,EAAE,CAAC,IAAI,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAA;AACzF,CAAC;AAED;;mEAEmE;AACnE,SAAS,YAAY;IACnB,OAAO,EAAE,CAAA;AACX,CAAC;AAED;kDACkD;AAClD,SAAS,iBAAiB;IACxB,MAAM,IAAI,KAAK,CAAC,kDAAkD,CAAC,CAAA;AACrE,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,4BAA4B,CAAC,SAAiB;IAC5D,MAAM,KAAK,GAAG,QAAQ,EAAE,CAAA;IACxB,6EAA6E;IAC7E,4EAA4E;IAC5E,MAAM,IAAI,GAAG,OAAO,EAAE,CAAA;IACtB,MAAM,aAAa,GAAG,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE,CAAA;IAE5C,MAAM,SAAS,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAA;IACnF,MAAM,GAAG,GAAG,WAAW,CACrB,CAAC,GAAgB,EAA2B,EAAE;QAC5C,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,GAAG,CAAC,EAAE,CAAC,CAAA;QACvD,OAAO,MAAM,CAAC,CAAC,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;IACpD,CAAC,EACD,CAAC,KAAK,CAAC,KAAK,CAAC,CACd,CAAA;IACD,MAAM,OAAO,GAAG,WAAW,CACzB,CAAC,GAAgB,EAAU,EAAE;QAC3B,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,GAAG,CAAC,EAAE,CAAC,CAAA;QACvD,OAAO,MAAM,EAAE,IAAI,IAAI,MAAM,EAAE,KAAK,IAAI,GAAG,CAAC,EAAE,CAAA;IAChD,CAAC,EACD,CAAC,KAAK,CAAC,KAAK,CAAC,CACd,CAAA;IACD,MAAM,WAAW,GAAG,WAAW,CAC7B,CAAC,KAAe,EAAE,MAAoB,EAAe,EAAE,CAAC,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,aAAa,CAAC,EACrG,CAAC,aAAa,CAAC,CAChB,CAAA;IACD,MAAM,IAAI,GAAG,WAAW,CAAC,CAAC,GAAgB,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,mBAAmB,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAA;IAC7F,MAAM,MAAM,GAAG,WAAW,CAAC,CAAC,GAAgB,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAA;IAE/E,OAAO,iBAAiB,CAAC;QACvB,SAAS;QACT,MAAM,EAAE,kBAAkB;QAC1B,YAAY,EAAE,4BAA4B;QAC1C,YAAY,EAAE,mBAAmB;QACjC,SAAS;QACT,GAAG;QACH,OAAO;QACP,2EAA2E;QAC3E,4EAA4E;QAC5E,sEAAsE;QACtE,4EAA4E;QAC5E,4EAA4E;QAC5E,UAAU,EAAE,CAAC,EAAU,EAAE,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QAC/C,QAAQ,EAAE,UAAU,CAAC,KAAK,EAAE;QAC5B,2EAA2E;QAC3E,yEAAyE;QACzE,WAAW;QACX,QAAQ,EAAE,YAAY;QACtB,MAAM,EAAE,iBAAiB;QACzB,IAAI;QACJ,MAAM;QACN,mBAAmB,EAAE,iBAAiB;QACtC,qBAAqB,EAAE,iBAAiB;KACzC,CAAC,CAAA;AACJ,CAAC","sourcesContent":["import { useCallback } from 'react'\nimport type { CmsDocument, CmsValidationIssue, DocumentRef, FieldDef, Locale, LocaleConfig } from '@elytracms/core/cms-core'\nimport type { ProjectRole, SaveUserInput, UserRecord } from '@elytracms/core/persistence'\nimport { studioPath } from '@elytracms/studio/router'\nimport type {\n CollectionViewCapabilities,\n CollectionViewSource,\n FieldAccess,\n} from '@elytracms/studio/lib/collection-view/collection-view-source'\nimport { useAuth } from '../auth-context'\nimport { useUsers } from './users-store'\nimport { usersCollectionDef } from './users-schema'\nimport { metaBagProjection, normalizeOkResult, useRailViewSource } from '../rail-view-source'\n\n/** The users rail is not localized — one implicit locale keeps the lens honest. */\nconst USERS_LOCALE: Locale = 'en'\nconst USERS_LOCALE_CONFIG: LocaleConfig = { default: USERS_LOCALE, locales: [USERS_LOCALE] }\n\n/**\n * The capability profile of the users rail (EC-240): admin-only, no\n * publish/versions/create/import (identities are minted by sign-up and their\n * bytes are auth-owned), remove allowed (revoke membership). Hoisted to a\n * module-level constant (no prop/state dependency, EC-327) — mirrors\n * `ASSET_COLLECTION_CAPABILITIES`.\n */\nexport const USER_COLLECTION_CAPABILITIES: CollectionViewCapabilities = {\n publish: false,\n versions: false,\n locales: false,\n inlineEdit: false,\n create: false, // identities are minted by sign-up, never here\n duplicate: false, // EC-442: identities aren't copyable content\n remove: true, // revoke membership (does not delete the auth identity)\n bulk: true, // EC-433: bulk delete = the same per-id revoke, honestly reported\n liveUrl: false,\n hierarchy: false,\n importExport: false, // credential records are never dumped to raw JSON\n adminOnly: true,\n}\n\n/** The built-in field names — everything else in `values` is a custom `meta` field. */\nconst IDENTITY_FIELDS = new Set(['name', 'email', 'role'])\n\n/** Project the auth-identity + membership join into the lens's `values` shape. */\nexport function userToDocument(record: UserRecord): CmsDocument {\n return {\n collection: usersCollectionDef.id,\n id: record.id,\n values: {\n // Display title falls through empties (name → email → id) so a nameless,\n // blank-email member never renders as a blank row; `email` keeps its real value.\n name: record.name || record.email || record.id,\n email: record.email,\n role: record.roles[0] ?? 'viewer',\n ...(record.meta ?? {}),\n },\n }\n}\n\n/**\n * Principal-based field access for the users rail (EC-240, AD-18). `role` is\n * authority: **read-only when you are editing YOURSELF** — you don't change your\n * own role (the server integrity floor also guards it) — and **editable otherwise**\n * (the rail is admin-only, so a non-self editor is an admin acting on someone else).\n * Identity fields (name/email) follow their schema `form.readOnly`; everything else\n * is editable. Pure, so the self/other rule is unit-testable without the auth hook.\n */\nexport function userFieldAccess(\n field: FieldDef,\n record: CmsDocument | undefined,\n currentUserId: string | undefined,\n): FieldAccess {\n if (field.name === 'role') {\n return record?.id === currentUserId ? 'read' : 'edit'\n }\n return field.form?.readOnly ? 'read' : 'edit'\n}\n\n/** Map an edited lens document back to the write contract (role + custom `meta`). */\nexport function documentToSaveInput(doc: CmsDocument): SaveUserInput {\n const values = doc.values ?? {}\n const meta = metaBagProjection(values, IDENTITY_FIELDS)\n const role = typeof values.role === 'string' ? (values.role as ProjectRole) : 'viewer'\n return { id: doc.id, roles: [role], ...(Object.keys(meta).length > 0 ? { meta } : {}) }\n}\n\n/** `validate` for the users rail: no-op — a member's identity fields aren't\n * schema-validated the way CMS documents are (240a-era; a registry-driven\n * validation may land alongside a future custom-field schema). */\nfunction noValidation(): CmsValidationIssue[] {\n return []\n}\n\n/** `create` when `capabilities.create === false` — never reached (identities\n * are minted by sign-up, not the users rail). */\nfunction createUnsupported(): DocumentRef {\n throw new Error('users are created by sign-up, not the users rail')\n}\n\n/**\n * Assemble a {@link CollectionViewSource} for the **users rail** (EC-240, AD-19) —\n * the reuse proof that the portable list/edit surfaces serve a non-`cmsDocuments`\n * rail. Mirrors {@link useCmsCollectionViewSource}, over the users store instead of\n * the CMS store, with the users capability profile: admin-only, no\n * publish/versions/create/import (identities are minted by sign-up and their bytes\n * are auth-owned), remove allowed (revoke membership). Identity fields are\n * read-only; an admin edits a member's role (and future custom `meta` fields).\n *\n * Must be mounted under a `UsersProvider` (for {@link useUsers}).\n *\n * The shared scaffold (`useRailViewSource`, EC-327) owns `viewState`/`lastError`\n * plumbing and the assembled source; this hook supplies only the users-store\n * reads/writes, the capability profile, and the principal-based `fieldAccess`.\n */\nexport function useUsersCollectionViewSource(projectId: string): CollectionViewSource {\n const users = useUsers()\n // The signed-in principal — so `role` locks to read-only when an admin opens\n // their OWN record (self ≠ other; the server floor enforces it regardless).\n const auth = useAuth()\n const currentUserId = auth.session?.user?.id\n\n const documents = useCallback(() => users.users.map(userToDocument), [users.users])\n const get = useCallback(\n (ref: DocumentRef): CmsDocument | undefined => {\n const record = users.users.find((u) => u.id === ref.id)\n return record ? userToDocument(record) : undefined\n },\n [users.users],\n )\n const titleOf = useCallback(\n (ref: DocumentRef): string => {\n const record = users.users.find((u) => u.id === ref.id)\n return record?.name || record?.email || ref.id\n },\n [users.users],\n )\n const fieldAccess = useCallback(\n (field: FieldDef, record?: CmsDocument): FieldAccess => userFieldAccess(field, record, currentUserId),\n [currentUserId],\n )\n const save = useCallback((doc: CmsDocument) => users.save(documentToSaveInput(doc)), [users])\n const remove = useCallback((ref: DocumentRef) => users.remove(ref.id), [users])\n\n return useRailViewSource({\n projectId,\n schema: usersCollectionDef,\n capabilities: USER_COLLECTION_CAPABILITIES,\n localeConfig: USERS_LOCALE_CONFIG,\n documents,\n get,\n titleOf,\n // No `referenceCandidates`/`collections` override: users carry no relation\n // fields in v1, so there are no cross-collection candidates to resolve; the\n // lens is the only collection in scope (both factory defaults apply).\n // The users rail now has a per-member detail surface (EC-240): rows link to\n // it, and the edit shell's back-crumb / post-delete nav return to the list.\n detailPath: (id: string) => studioPath.user(id),\n listPath: studioPath.users(),\n // Principal-based tri-state: `role` is read-only for self, editable for an\n // admin acting on another; identity fields follow their schema readOnly.\n fieldAccess,\n validate: noValidation,\n create: createUnsupported,\n save,\n remove,\n normalizeSaveResult: normalizeOkResult,\n normalizeRemoveResult: normalizeOkResult,\n })\n}\n"]}
|
package/dist/customization.d.ts
CHANGED
|
@@ -39,7 +39,24 @@
|
|
|
39
39
|
* ```
|
|
40
40
|
*/
|
|
41
41
|
export { defineStudio } from './components/app-views/studio-config.js';
|
|
42
|
-
export type { StudioConfig, StudioCanvasTheme, CustomFieldRenderer, CustomFieldRendererProps, } from './components/app-views/studio-config.js';
|
|
42
|
+
export type { StudioConfig, StudioCanvasTheme, StudioPreviewsConfig, CustomFieldRenderer, CustomFieldRendererProps, } from './components/app-views/studio-config.js';
|
|
43
|
+
/**
|
|
44
|
+
* The generated-preview seam (EC-439, filled by EC-440's `elytra previews`):
|
|
45
|
+
* `defineStudio({ previews: { generated } })` supplies a pure
|
|
46
|
+
* `componentId + fixtureName → URL` lookup for build-time fixture preview
|
|
47
|
+
* images. Curated `manifest.previewImage` wins over it; without either, tiles
|
|
48
|
+
* render an icon+name placeholder.
|
|
49
|
+
*/
|
|
50
|
+
export type { GeneratedPreviewLookup } from './components/builder/blocks-model.js';
|
|
51
|
+
/**
|
|
52
|
+
* The EC-440 previews-manifest adapter: `elytra previews` writes
|
|
53
|
+
* `studio/elytra-previews.json` + images under `public/elytra-previews/`; the
|
|
54
|
+
* consumer wires the seam in two lines —
|
|
55
|
+
* `import previewsManifest from './elytra-previews.json'` and
|
|
56
|
+
* `previews: { generated: generatedPreviewsFromManifest(previewsManifest) }`.
|
|
57
|
+
*/
|
|
58
|
+
export { generatedPreviewsFromManifest } from './previews/manifest-format.js';
|
|
59
|
+
export type { GeneratedPreviewsManifest, GeneratedPreviewsManifestEntry, } from './previews/manifest-format.js';
|
|
43
60
|
export { nav } from './components/app-views/default-modules.js';
|
|
44
61
|
export type { CmsViewConfig, BuiltinViewConfig, CustomViewConfig, } from './components/app-views/default-modules.js';
|
|
45
62
|
export type { AppViewContext, AppViewModule, SidebarNode } from './components/app-views/module.js';
|
package/dist/customization.js
CHANGED
|
@@ -39,6 +39,14 @@
|
|
|
39
39
|
* ```
|
|
40
40
|
*/
|
|
41
41
|
export { defineStudio } from './components/app-views/studio-config.js';
|
|
42
|
+
/**
|
|
43
|
+
* The EC-440 previews-manifest adapter: `elytra previews` writes
|
|
44
|
+
* `studio/elytra-previews.json` + images under `public/elytra-previews/`; the
|
|
45
|
+
* consumer wires the seam in two lines —
|
|
46
|
+
* `import previewsManifest from './elytra-previews.json'` and
|
|
47
|
+
* `previews: { generated: generatedPreviewsFromManifest(previewsManifest) }`.
|
|
48
|
+
*/
|
|
49
|
+
export { generatedPreviewsFromManifest } from './previews/manifest-format.js';
|
|
42
50
|
export { nav } from './components/app-views/default-modules.js';
|
|
43
51
|
export { createStudio } from './lib/workspace/index.js';
|
|
44
52
|
/** A ready-made example renderer; a consuming repo may use it or author its own. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"customization.js","sourceRoot":"","sources":["../src/customization.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,sCAAsC,CAAA;
|
|
1
|
+
{"version":3,"file":"customization.js","sourceRoot":"","sources":["../src/customization.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,sCAAsC,CAAA;AAgBnE;;;;;;GAMG;AACH,OAAO,EAAE,6BAA6B,EAAE,MAAM,4BAA4B,CAAA;AAK1E,OAAO,EAAE,GAAG,EAAE,MAAM,wCAAwC,CAAA;AAO5D,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAC9C,oFAAoF;AACpF,OAAO,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAA;AACpE;;;;;;;GAOG;AACH,OAAO,EAAE,GAAG,EAAE,MAAM,YAAY,CAAA","sourcesContent":["/**\n * `@elytracms/studio/customization` — the public customization API for a consuming\n * repo's `studio.config.tsx` (EC-236/237/238/239).\n *\n * Everything a repo customizes about the builder UI is **live studio code passed by\n * reference** through `defineStudio({...})` — the section rail (`nav.*`), custom\n * field renderers, and (in time) branding. This config is studio-only: it is handed\n * to `createStudio(config, studioConfig)` at the mount and never enters the shared\n * `elytra.config.ts` / the `frontend/` delivery bundle. The customization principle\n * in one import surface: direct values, no registries, no string ids — the lone\n * string seam is `fieldRenderers`' control key, which the delivery-shared schema\n * references via `form.control`.\n *\n * @example\n * ```tsx\n * // studio.config.tsx\n * import { defineStudio, nav } from '@elytracms/studio/customization'\n * import { ColorField } from './fields/color'\n *\n * export default defineStudio({\n * sidebar: [\n * nav.cms({ label: 'Content' }),\n * nav.group('Catalog', [\n * nav.cms({ label: 'Catalog', collections: ['product', 'productCategory'] }),\n * nav.cms({ label: 'Categories', collection: 'productCategory' }),\n * ]),\n * nav.separator(),\n * nav.assets(),\n * nav.settings(),\n * ],\n * fieldRenderers: { color: ColorField },\n * })\n *\n * // router.tsx (the mount)\n * import { createStudio } from '@elytracms/studio/customization'\n * import config from '../../elytra.config'\n * import studioConfig from './studio.config'\n * createStudio(config, studioConfig)\n * ```\n */\nexport { defineStudio } from './components/app-views/studio-config'\nexport type {\n StudioConfig,\n StudioCanvasTheme,\n StudioPreviewsConfig,\n CustomFieldRenderer,\n CustomFieldRendererProps,\n} from './components/app-views/studio-config'\n/**\n * The generated-preview seam (EC-439, filled by EC-440's `elytra previews`):\n * `defineStudio({ previews: { generated } })` supplies a pure\n * `componentId + fixtureName → URL` lookup for build-time fixture preview\n * images. Curated `manifest.previewImage` wins over it; without either, tiles\n * render an icon+name placeholder.\n */\nexport type { GeneratedPreviewLookup } from './components/builder/blocks-model'\n/**\n * The EC-440 previews-manifest adapter: `elytra previews` writes\n * `studio/elytra-previews.json` + images under `public/elytra-previews/`; the\n * consumer wires the seam in two lines —\n * `import previewsManifest from './elytra-previews.json'` and\n * `previews: { generated: generatedPreviewsFromManifest(previewsManifest) }`.\n */\nexport { generatedPreviewsFromManifest } from './previews/manifest-format'\nexport type {\n GeneratedPreviewsManifest,\n GeneratedPreviewsManifestEntry,\n} from './previews/manifest-format'\nexport { nav } from './components/app-views/default-modules'\nexport type {\n CmsViewConfig,\n BuiltinViewConfig,\n CustomViewConfig,\n} from './components/app-views/default-modules'\nexport type { AppViewContext, AppViewModule, SidebarNode } from './components/app-views/module'\nexport { createStudio } from './lib/workspace'\n/** A ready-made example renderer; a consuming repo may use it or author its own. */\nexport { colorFieldRenderer } from './components/cms/field-renderer'\n/**\n * Studio-UI localization (EC-241): author a translation catalog for `defineStudio({\n * i18n })`. `msg` is only needed to build an in-repo *source* catalog with translator\n * hints; a plain `MessageCatalog` (flat key → string) is all a consumer supplies.\n * Prefer `TranslationCatalog` (EC-398) when authoring: it is typed against the EN key\n * set, so unknown/stale keys fail typecheck — engine-shipped locales\n * (`@elytracms/studio/locales/de`) and spread-overrides of them use it.\n */\nexport { msg } from './lib/i18n'\nexport type {\n MessageCatalog,\n MessageKey,\n SourceMessage,\n StudioI18nConfig,\n TranslationCatalog,\n} from './lib/i18n'\n"]}
|
|
@@ -24,13 +24,13 @@ export declare const filterClauseSchema: z.ZodObject<{
|
|
|
24
24
|
empty: "empty";
|
|
25
25
|
contains: "contains";
|
|
26
26
|
in: "in";
|
|
27
|
+
endsWith: "endsWith";
|
|
28
|
+
startsWith: "startsWith";
|
|
27
29
|
eq: "eq";
|
|
28
30
|
gt: "gt";
|
|
29
31
|
gte: "gte";
|
|
30
32
|
lt: "lt";
|
|
31
33
|
lte: "lte";
|
|
32
|
-
endsWith: "endsWith";
|
|
33
|
-
startsWith: "startsWith";
|
|
34
34
|
}>;
|
|
35
35
|
value: z.ZodUnion<readonly [z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>, z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>]>;
|
|
36
36
|
}, z.core.$strip>;
|
|
@@ -50,13 +50,13 @@ export declare const tableViewStateSchema: z.ZodObject<{
|
|
|
50
50
|
empty: "empty";
|
|
51
51
|
contains: "contains";
|
|
52
52
|
in: "in";
|
|
53
|
+
endsWith: "endsWith";
|
|
54
|
+
startsWith: "startsWith";
|
|
53
55
|
eq: "eq";
|
|
54
56
|
gt: "gt";
|
|
55
57
|
gte: "gte";
|
|
56
58
|
lt: "lt";
|
|
57
59
|
lte: "lte";
|
|
58
|
-
endsWith: "endsWith";
|
|
59
|
-
startsWith: "startsWith";
|
|
60
60
|
}>;
|
|
61
61
|
value: z.ZodUnion<readonly [z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>, z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>]>;
|
|
62
62
|
}, z.core.$strip>>;
|
|
@@ -206,13 +206,13 @@ export declare const configViewSchema: z.ZodObject<{
|
|
|
206
206
|
empty: "empty";
|
|
207
207
|
contains: "contains";
|
|
208
208
|
in: "in";
|
|
209
|
+
endsWith: "endsWith";
|
|
210
|
+
startsWith: "startsWith";
|
|
209
211
|
eq: "eq";
|
|
210
212
|
gt: "gt";
|
|
211
213
|
gte: "gte";
|
|
212
214
|
lt: "lt";
|
|
213
215
|
lte: "lte";
|
|
214
|
-
endsWith: "endsWith";
|
|
215
|
-
startsWith: "startsWith";
|
|
216
216
|
}>;
|
|
217
217
|
value: z.ZodUnion<readonly [z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>, z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>]>;
|
|
218
218
|
}, z.core.$strip>>>;
|
|
@@ -174,6 +174,14 @@ export interface CollectionViewCapabilities {
|
|
|
174
174
|
readonly locales: boolean;
|
|
175
175
|
readonly inlineEdit: boolean;
|
|
176
176
|
readonly create: boolean;
|
|
177
|
+
/**
|
|
178
|
+
* Duplicate-document action (EC-442, amended same day). Deliberately its own
|
|
179
|
+
* flag, never derived from `create`: the owner call (2026-07-17) is CMS
|
|
180
|
+
* collections ONLY — no primitive rail offers Duplicate for now (identities
|
|
181
|
+
* and uploads aren't copyable content; redirects deferred until requested as
|
|
182
|
+
* a product feature).
|
|
183
|
+
*/
|
|
184
|
+
readonly duplicate: boolean;
|
|
177
185
|
readonly remove: boolean;
|
|
178
186
|
readonly bulk: boolean;
|
|
179
187
|
/** The derived live-URL column (published pool × deployment routes). */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"collection-view-source.js","sourceRoot":"","sources":["../../../src/lib/collection-view/collection-view-source.ts"],"names":[],"mappings":"AA+NA;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAChC,MAA4B,EAC5B,KAAe,EACf,MAAoB;IAEpB,OAAO,MAAM,CAAC,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAA;AACjG,CAAC","sourcesContent":["import type {\n CmsDocument,\n CollectionDef,\n DocumentRef,\n DocumentUrl,\n FieldDef,\n Locale,\n LocaleConfig,\n LocalizedString,\n RestoreScope,\n} from '@elytracms/core/cms-core'\nimport type { CmsValidationIssue } from '@elytracms/core/cms-core'\nimport type { OperationError } from '@elytracms/core/operations'\nimport type { ReferenceTarget } from '@elytracms/core/persistence'\nimport type { TableViewState } from '../cms-views/view-model'\n// The concrete inline-edit + bulk vocabulary (Stream A relocates it into `lib` as it settles).\nimport type { CellCommitInput, CellCommitOutcome } from '../../components/cms/cell-edit'\nimport type { BulkAction, BulkReport, BulkSetField } from '../../components/cms/bulk-actions'\nimport type { DocumentVersionsView, ImportReport } from '../../components/cms/cms-store'\n\n/**\n * `CollectionViewSource` — the studio's UNIVERSAL interaction seam (EC-240,\n * vision AD-19).\n *\n * The list surface (`CollectionTable`) and the edit-shell (`DetailEditor`) are\n * the studio's universal interaction surfaces. Historically they read the\n * concrete `CmsStore` through `useCms()`, so they only served `cmsDocuments`.\n * This interface is the data-agnostic seam they render over instead, so the SAME\n * surfaces drive the users rail (EC-240), redirects (EC-303), the Components app\n * edit-shell, and any future rail — no hand-rolled UI per rail.\n *\n * **Shape:** the source is scoped to ONE collection (it carries `schema` +\n * `collectionId`), and its methods mirror the subset of `CmsStore` the surfaces\n * actually use — dropping the `collectionId` argument (the source already knows\n * it). Keeping the names/signatures aligned makes the CMS adapter a\n * behaviour-preserving pass-through and the migration near-mechanical.\n *\n * **Referential stability:** the surfaces put `documents`, `isPublished`,\n * `localeConfig`, etc. in `useMemo` dependency arrays. An implementation MUST\n * return stable references across renders (delegate to the underlying store's\n * already-stable methods; never wrap in a fresh closure each render) or memos\n * bust and the inline-edit order snapshot / perf regress.\n *\n * **Capabilities:** flags gate whole shell regions AND method presence — a source\n * with `capabilities.versions === false` (the users rail) never mounts the\n * Versions pane, so no `listVersions` read fires on credential records. Optional\n * method groups are present iff the flag is set.\n */\nexport interface CollectionViewSource {\n /** The rail/collection this source serves. */\n readonly collectionId: string\n /** The schema lens: fields, form tabs/groups, `titleField`, display nouns. */\n readonly schema: CollectionDef\n /** Which regions/methods this source supports. */\n readonly capabilities: CollectionViewCapabilities\n\n /** The project scope (asset picker, published-pool queries). Absent in fixture sessions. */\n readonly projectId?: string\n /**\n * The collection id assets live in (for a cell/field's `titleOf` label fallback\n * when {@link resolveAsset} finds no record). Present only for sources that\n * reference assets — a rail without assets (users) omits it.\n */\n readonly assetCollectionId?: string\n /** The ambient locale lens (only meaningful when `capabilities.locales`). */\n readonly localeConfig: LocaleConfig\n readonly activeLocale: Locale\n\n // --- Reads -----------------------------------------------------------------\n /** The full row set (view mechanics filter/sort this client-side). */\n documents(): CmsDocument[]\n get(ref: DocumentRef): CmsDocument | undefined\n /** A relation/asset target's display title (hides the string-vs-function `titleField`). */\n titleOf(ref: DocumentRef): string\n /**\n * List the candidate records of a relation field's declared `target` collection\n * — the pick-list for a relation picker (EC-072). References are first-class and\n * collection-based: the field's schema declares which collection(s) it targets,\n * and this resolves those rows for ANY target (cross-collection), not just this\n * source's own collection. The caller passes `field.target`; a multi-target field\n * (future) merges one call per declared target. An unknown/empty target yields `[]`.\n */\n referenceCandidates(collectionId: string): CmsDocument[]\n /**\n * The workspace schema a reference/asset can point at — the schema counterpart to\n * {@link referenceCandidates}. Needed for the cases that read a TARGET collection's\n * fields (an asset field editing the asset collection's own metadata; the reuse\n * capability correlating embed manifests against their target collection). A\n * reference is collection-based, so resolving one sometimes needs the target's\n * schema, not just its rows.\n */\n readonly collections: readonly CollectionDef[]\n isFieldReadOnly(field: FieldDef): boolean\n /**\n * Principal-based field access (EC-240, vision AD-18): `edit` (writable), `read`\n * (visible but read-only), or `hidden` (not rendered for this principal). This is\n * IDENTITY-driven and distinct from a field's content-conditional `hidden(values)`\n * — e.g. an editor's own `role` resolves to `read`, a viewer never SEES a\n * sensitive field (`hidden`). A single tri-state (never two independent flags) so\n * the nonsensical writable-but-invisible state can't be expressed. Optional:\n * absent ⇒ derived from {@link isFieldReadOnly} (`read`|`edit`), i.e. default-open\n * (permissions RESTRICT, never grant a baseline). Per-rail policy lives on the\n * source; the starter's policy flows into its construction (engine = mechanism).\n * Prefer {@link resolveFieldAccess} over calling this directly (it applies the\n * default-open fallback).\n */\n fieldAccess?(field: FieldDef, record?: CmsDocument): FieldAccess\n validate(doc: CmsDocument): CmsValidationIssue[]\n /**\n * Resolve an asset id to its display metadata for a cell/field, or `undefined`\n * when there is no asset backend (a source without assets degrades to a label\n * via {@link titleOf}, matching today's \"no AssetsProvider\" behaviour).\n */\n resolveAsset?(id: string): ResolvedAsset | undefined\n /**\n * How this rail's records are addressed in the EC-155 reference index — the\n * target shape the delete/unpublish confirmation's inbound pre-flight queries\n * (EC-433). Absent ⇒ the document shape (`{ type: 'document', id: 'collection:id' }`),\n * which every `cmsDocuments` collection uses. The assets rail supplies\n * `{ type: 'asset' }` so its list/bulk delete confirmation enumerates the SAME\n * inbound references its detail surface does — a document-shaped lookup would\n * report a referenced asset as \"not referenced\" (the EC-319 objection that\n * originally kept `remove` off the assets rail).\n */\n referenceTarget?(ref: DocumentRef): ReferenceTarget\n /** The last failed/denied mutation, surfaced by the shell (never a silent failure). */\n readonly lastError: OperationError | null\n clearLastError(): void\n /**\n * Land any queued/optimistic writes so a subsequent pre-flight READ sees them\n * (the reference index is maintained on write). A source with no async\n * persistence omits it — its reads are already authoritative.\n */\n flush?(): Promise<void>\n\n /**\n * The studio route splat for a record's detail surface (the row link + the\n * post-create navigation target), e.g. `studioPath.cmsDocument(collectionId, id)`.\n * Rail-owned so the list never hard-codes the CMS route. `undefined` ⇒ the rail\n * has no detail surface, so rows render as plain text (no navigation).\n */\n detailPath?(id: string): string\n /**\n * The studio route splat for this rail's LIST surface — the edit shell's\n * \"back to list\" breadcrumb crumb and its post-delete navigation target, e.g.\n * `studioPath.cmsCollection(collectionId)`. Rail-owned so the edit shell never\n * hard-codes the CMS route (the counterpart to {@link detailPath}). `undefined`\n * ⇒ the crumb is unlinked and delete falls back to the project index.\n */\n readonly listPath?: string\n\n // --- View state (list) -----------------------------------------------------\n viewState(): TableViewState\n setViewState(update: TableViewState | ((prev: TableViewState) => TableViewState)): void\n\n // --- Writes ----------------------------------------------------------------\n create(): DocumentRef\n save(doc: CmsDocument): void\n remove(ref: DocumentRef): void\n /**\n * Bulk publish/unpublish/delete/set-field (only when `capabilities.bulk`): one\n * command PER item, per-item outcomes enumerated in the report (partial failure\n * is never silent).\n */\n bulk?(ids: readonly string[], action: BulkAction, setField?: BulkSetField): Promise<BulkReport>\n\n // --- Capability method groups (present iff the matching flag is set) --------\n /** `publish` — published-pool predicate + publish/unpublish. */\n readonly publishing?: PublishingCapability\n /** `versions` — version history + restore. */\n readonly versioning?: VersioningCapability\n /** `inlineEdit` — the list's single-command optimistic cell write. */\n readonly inlineEditing?: InlineEditCapability\n /**\n * Config-as-code named lenses (EC-246b) the header view selector switches\n * between. Optional: a source that declares no named views (a non-CMS rail\n * like users) omits it, and the selector renders nothing (single default).\n */\n readonly views?: NamedViewsCapability\n /**\n * `importExport` — JSON round-trip of the collection's records. CMS-specific;\n * a rail whose records are credential-bearing (users) omits it, so neither the\n * import dialog nor the export action mounts (identities aren't dumped to JSON).\n */\n readonly imports?: ImportCapability\n}\n\n/**\n * The capability matrix. Each flag gates a shell region AND the presence of its\n * method group. The **users rail** (EC-240) sets `publish:false`, `versions:false`\n * (credential caution), `create:false` (identity minted by sign-up), and no\n * `liveUrl`/`hierarchy`.\n */\nexport interface CollectionViewCapabilities {\n readonly publish: boolean\n readonly versions: boolean\n readonly locales: boolean\n readonly inlineEdit: boolean\n readonly create: boolean\n readonly remove: boolean\n readonly bulk: boolean\n /** The derived live-URL column (published pool × deployment routes). */\n readonly liveUrl: boolean\n /** Parent-self-relation tree mode (hierarchical collections only). */\n readonly hierarchy: boolean\n /** JSON import/export of the collection's records (off for credential rails). */\n readonly importExport: boolean\n /**\n * The rail is administration-scoped (mirrors `ADMIN_ONLY_OPERATIONS`). The\n * nav-permission layer reads this to gate the rail item. Absent ⇒ not admin-only.\n */\n readonly adminOnly?: boolean\n}\n\n/**\n * Principal-based field access (EC-240, AD-18). One tri-state drives BOTH visibility\n * and writability: `edit` (writable), `read` (visible, read-only), `hidden` (not\n * rendered). Default `edit` (default-open). Distinct from content-`hidden(values)`;\n * they compose — a field shows iff *not content-hidden AND access ≠ hidden*, and is\n * editable iff *access = edit AND not otherwise read-only* (published view, `form.readOnly`).\n */\nexport type FieldAccess = 'edit' | 'read' | 'hidden'\n\n/**\n * Resolve a field's effective principal access for a source — its {@link\n * CollectionViewSource.fieldAccess} policy when present, else derived from the\n * boolean `isFieldReadOnly` (default-open). The one place the shell asks \"may this\n * principal edit / see this field?\", so the default-open fallback lives in one spot.\n */\nexport function resolveFieldAccess(\n source: CollectionViewSource,\n field: FieldDef,\n record?: CmsDocument,\n): FieldAccess {\n return source.fieldAccess?.(field, record) ?? (source.isFieldReadOnly(field) ? 'read' : 'edit')\n}\n\n/** Asset display metadata a cell/field renders (thumbnail or labelled badge). */\nexport interface ResolvedAsset {\n readonly url?: string\n readonly contentType?: string\n readonly title?: string\n readonly filename?: string\n readonly alt?: string\n}\n\n/** `publish` capability. */\nexport interface PublishingCapability {\n /** Published-ness from the version pointer (NOT a row field). */\n isPublished(ref: DocumentRef): boolean\n publish(ref: DocumentRef): Promise<void>\n unpublish(ref: DocumentRef): Promise<void>\n /**\n * Live URLs for the routable documents, keyed by id — derived from the\n * PUBLISHED pool (never draft rows) so an unpublished slug/parent edit can't\n * leak into a live link (EC-205/EC-216). `null` when the collection is not\n * routable → the live-URL column is hidden. Only present when `capabilities.liveUrl`.\n */\n liveUrls?(): Map<string, DocumentUrl> | null\n}\n\n/** `versions` capability. */\nexport interface VersioningCapability {\n restore(ref: DocumentRef, version: number, scope?: RestoreScope): Promise<CmsDocument | null>\n /**\n * The document's recorded version history + live pin. Folds the flush-before-read\n * ordering INTO the loader (the adapter's concern), so every fetch lists an\n * authoritative history regardless of which surface triggered the change (EC-224).\n */\n load(ref: DocumentRef): Promise<DocumentVersionsView>\n}\n\n/** `inlineEdit` capability: the list's single-command optimistic cell write (EC-168). */\nexport interface InlineEditCapability {\n commitField(input: CellCommitInput): Promise<CellCommitOutcome>\n}\n\n/** A named lens the header selector offers — id + display name only (the header never needs its state). */\nexport interface NamedView {\n readonly id: string\n /** Display name; a {@link LocalizedString} (EC-343), resolved at the render site like `form.label`. */\n readonly name: LocalizedString\n}\n\n/** `importExport` capability: validate + fold external JSON records into the collection (EC-079). */\nexport interface ImportCapability {\n importDocuments(items: unknown[]): ImportReport\n}\n\n/**\n * `views` capability: config-as-code named lenses (EC-246b). The header selector\n * only *picks* a lens — it never creates/edits/deletes one (shared views are code).\n */\nexport interface NamedViewsCapability {\n /** The built-in default + config-declared named views. */\n list(): readonly NamedView[]\n /** The currently-selected view id. */\n activeId(): string\n /** Switch views (resets the working table state to the view's baseline). */\n setActive(id: string): void\n}\n\nexport type { CellCommitInput, CellCommitOutcome, ImportReport }\n"]}
|
|
1
|
+
{"version":3,"file":"collection-view-source.js","sourceRoot":"","sources":["../../../src/lib/collection-view/collection-view-source.ts"],"names":[],"mappings":"AAuOA;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAChC,MAA4B,EAC5B,KAAe,EACf,MAAoB;IAEpB,OAAO,MAAM,CAAC,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAA;AACjG,CAAC","sourcesContent":["import type {\n CmsDocument,\n CollectionDef,\n DocumentRef,\n DocumentUrl,\n FieldDef,\n Locale,\n LocaleConfig,\n LocalizedString,\n RestoreScope,\n} from '@elytracms/core/cms-core'\nimport type { CmsValidationIssue } from '@elytracms/core/cms-core'\nimport type { OperationError } from '@elytracms/core/operations'\nimport type { ReferenceTarget } from '@elytracms/core/persistence'\nimport type { TableViewState } from '../cms-views/view-model'\n// The concrete inline-edit + bulk vocabulary (Stream A relocates it into `lib` as it settles).\nimport type { CellCommitInput, CellCommitOutcome } from '../../components/cms/cell-edit'\nimport type { BulkAction, BulkReport, BulkSetField } from '../../components/cms/bulk-actions'\nimport type { DocumentVersionsView, ImportReport } from '../../components/cms/cms-store'\n\n/**\n * `CollectionViewSource` — the studio's UNIVERSAL interaction seam (EC-240,\n * vision AD-19).\n *\n * The list surface (`CollectionTable`) and the edit-shell (`DetailEditor`) are\n * the studio's universal interaction surfaces. Historically they read the\n * concrete `CmsStore` through `useCms()`, so they only served `cmsDocuments`.\n * This interface is the data-agnostic seam they render over instead, so the SAME\n * surfaces drive the users rail (EC-240), redirects (EC-303), the Components app\n * edit-shell, and any future rail — no hand-rolled UI per rail.\n *\n * **Shape:** the source is scoped to ONE collection (it carries `schema` +\n * `collectionId`), and its methods mirror the subset of `CmsStore` the surfaces\n * actually use — dropping the `collectionId` argument (the source already knows\n * it). Keeping the names/signatures aligned makes the CMS adapter a\n * behaviour-preserving pass-through and the migration near-mechanical.\n *\n * **Referential stability:** the surfaces put `documents`, `isPublished`,\n * `localeConfig`, etc. in `useMemo` dependency arrays. An implementation MUST\n * return stable references across renders (delegate to the underlying store's\n * already-stable methods; never wrap in a fresh closure each render) or memos\n * bust and the inline-edit order snapshot / perf regress.\n *\n * **Capabilities:** flags gate whole shell regions AND method presence — a source\n * with `capabilities.versions === false` (the users rail) never mounts the\n * Versions pane, so no `listVersions` read fires on credential records. Optional\n * method groups are present iff the flag is set.\n */\nexport interface CollectionViewSource {\n /** The rail/collection this source serves. */\n readonly collectionId: string\n /** The schema lens: fields, form tabs/groups, `titleField`, display nouns. */\n readonly schema: CollectionDef\n /** Which regions/methods this source supports. */\n readonly capabilities: CollectionViewCapabilities\n\n /** The project scope (asset picker, published-pool queries). Absent in fixture sessions. */\n readonly projectId?: string\n /**\n * The collection id assets live in (for a cell/field's `titleOf` label fallback\n * when {@link resolveAsset} finds no record). Present only for sources that\n * reference assets — a rail without assets (users) omits it.\n */\n readonly assetCollectionId?: string\n /** The ambient locale lens (only meaningful when `capabilities.locales`). */\n readonly localeConfig: LocaleConfig\n readonly activeLocale: Locale\n\n // --- Reads -----------------------------------------------------------------\n /** The full row set (view mechanics filter/sort this client-side). */\n documents(): CmsDocument[]\n get(ref: DocumentRef): CmsDocument | undefined\n /** A relation/asset target's display title (hides the string-vs-function `titleField`). */\n titleOf(ref: DocumentRef): string\n /**\n * List the candidate records of a relation field's declared `target` collection\n * — the pick-list for a relation picker (EC-072). References are first-class and\n * collection-based: the field's schema declares which collection(s) it targets,\n * and this resolves those rows for ANY target (cross-collection), not just this\n * source's own collection. The caller passes `field.target`; a multi-target field\n * (future) merges one call per declared target. An unknown/empty target yields `[]`.\n */\n referenceCandidates(collectionId: string): CmsDocument[]\n /**\n * The workspace schema a reference/asset can point at — the schema counterpart to\n * {@link referenceCandidates}. Needed for the cases that read a TARGET collection's\n * fields (an asset field editing the asset collection's own metadata; the reuse\n * capability correlating embed manifests against their target collection). A\n * reference is collection-based, so resolving one sometimes needs the target's\n * schema, not just its rows.\n */\n readonly collections: readonly CollectionDef[]\n isFieldReadOnly(field: FieldDef): boolean\n /**\n * Principal-based field access (EC-240, vision AD-18): `edit` (writable), `read`\n * (visible but read-only), or `hidden` (not rendered for this principal). This is\n * IDENTITY-driven and distinct from a field's content-conditional `hidden(values)`\n * — e.g. an editor's own `role` resolves to `read`, a viewer never SEES a\n * sensitive field (`hidden`). A single tri-state (never two independent flags) so\n * the nonsensical writable-but-invisible state can't be expressed. Optional:\n * absent ⇒ derived from {@link isFieldReadOnly} (`read`|`edit`), i.e. default-open\n * (permissions RESTRICT, never grant a baseline). Per-rail policy lives on the\n * source; the starter's policy flows into its construction (engine = mechanism).\n * Prefer {@link resolveFieldAccess} over calling this directly (it applies the\n * default-open fallback).\n */\n fieldAccess?(field: FieldDef, record?: CmsDocument): FieldAccess\n validate(doc: CmsDocument): CmsValidationIssue[]\n /**\n * Resolve an asset id to its display metadata for a cell/field, or `undefined`\n * when there is no asset backend (a source without assets degrades to a label\n * via {@link titleOf}, matching today's \"no AssetsProvider\" behaviour).\n */\n resolveAsset?(id: string): ResolvedAsset | undefined\n /**\n * How this rail's records are addressed in the EC-155 reference index — the\n * target shape the delete/unpublish confirmation's inbound pre-flight queries\n * (EC-433). Absent ⇒ the document shape (`{ type: 'document', id: 'collection:id' }`),\n * which every `cmsDocuments` collection uses. The assets rail supplies\n * `{ type: 'asset' }` so its list/bulk delete confirmation enumerates the SAME\n * inbound references its detail surface does — a document-shaped lookup would\n * report a referenced asset as \"not referenced\" (the EC-319 objection that\n * originally kept `remove` off the assets rail).\n */\n referenceTarget?(ref: DocumentRef): ReferenceTarget\n /** The last failed/denied mutation, surfaced by the shell (never a silent failure). */\n readonly lastError: OperationError | null\n clearLastError(): void\n /**\n * Land any queued/optimistic writes so a subsequent pre-flight READ sees them\n * (the reference index is maintained on write). A source with no async\n * persistence omits it — its reads are already authoritative.\n */\n flush?(): Promise<void>\n\n /**\n * The studio route splat for a record's detail surface (the row link + the\n * post-create navigation target), e.g. `studioPath.cmsDocument(collectionId, id)`.\n * Rail-owned so the list never hard-codes the CMS route. `undefined` ⇒ the rail\n * has no detail surface, so rows render as plain text (no navigation).\n */\n detailPath?(id: string): string\n /**\n * The studio route splat for this rail's LIST surface — the edit shell's\n * \"back to list\" breadcrumb crumb and its post-delete navigation target, e.g.\n * `studioPath.cmsCollection(collectionId)`. Rail-owned so the edit shell never\n * hard-codes the CMS route (the counterpart to {@link detailPath}). `undefined`\n * ⇒ the crumb is unlinked and delete falls back to the project index.\n */\n readonly listPath?: string\n\n // --- View state (list) -----------------------------------------------------\n viewState(): TableViewState\n setViewState(update: TableViewState | ((prev: TableViewState) => TableViewState)): void\n\n // --- Writes ----------------------------------------------------------------\n create(): DocumentRef\n save(doc: CmsDocument): void\n remove(ref: DocumentRef): void\n /**\n * Bulk publish/unpublish/delete/set-field (only when `capabilities.bulk`): one\n * command PER item, per-item outcomes enumerated in the report (partial failure\n * is never silent).\n */\n bulk?(ids: readonly string[], action: BulkAction, setField?: BulkSetField): Promise<BulkReport>\n\n // --- Capability method groups (present iff the matching flag is set) --------\n /** `publish` — published-pool predicate + publish/unpublish. */\n readonly publishing?: PublishingCapability\n /** `versions` — version history + restore. */\n readonly versioning?: VersioningCapability\n /** `inlineEdit` — the list's single-command optimistic cell write. */\n readonly inlineEditing?: InlineEditCapability\n /**\n * Config-as-code named lenses (EC-246b) the header view selector switches\n * between. Optional: a source that declares no named views (a non-CMS rail\n * like users) omits it, and the selector renders nothing (single default).\n */\n readonly views?: NamedViewsCapability\n /**\n * `importExport` — JSON round-trip of the collection's records. CMS-specific;\n * a rail whose records are credential-bearing (users) omits it, so neither the\n * import dialog nor the export action mounts (identities aren't dumped to JSON).\n */\n readonly imports?: ImportCapability\n}\n\n/**\n * The capability matrix. Each flag gates a shell region AND the presence of its\n * method group. The **users rail** (EC-240) sets `publish:false`, `versions:false`\n * (credential caution), `create:false` (identity minted by sign-up), and no\n * `liveUrl`/`hierarchy`.\n */\nexport interface CollectionViewCapabilities {\n readonly publish: boolean\n readonly versions: boolean\n readonly locales: boolean\n readonly inlineEdit: boolean\n readonly create: boolean\n /**\n * Duplicate-document action (EC-442, amended same day). Deliberately its own\n * flag, never derived from `create`: the owner call (2026-07-17) is CMS\n * collections ONLY — no primitive rail offers Duplicate for now (identities\n * and uploads aren't copyable content; redirects deferred until requested as\n * a product feature).\n */\n readonly duplicate: boolean\n readonly remove: boolean\n readonly bulk: boolean\n /** The derived live-URL column (published pool × deployment routes). */\n readonly liveUrl: boolean\n /** Parent-self-relation tree mode (hierarchical collections only). */\n readonly hierarchy: boolean\n /** JSON import/export of the collection's records (off for credential rails). */\n readonly importExport: boolean\n /**\n * The rail is administration-scoped (mirrors `ADMIN_ONLY_OPERATIONS`). The\n * nav-permission layer reads this to gate the rail item. Absent ⇒ not admin-only.\n */\n readonly adminOnly?: boolean\n}\n\n/**\n * Principal-based field access (EC-240, AD-18). One tri-state drives BOTH visibility\n * and writability: `edit` (writable), `read` (visible, read-only), `hidden` (not\n * rendered). Default `edit` (default-open). Distinct from content-`hidden(values)`;\n * they compose — a field shows iff *not content-hidden AND access ≠ hidden*, and is\n * editable iff *access = edit AND not otherwise read-only* (published view, `form.readOnly`).\n */\nexport type FieldAccess = 'edit' | 'read' | 'hidden'\n\n/**\n * Resolve a field's effective principal access for a source — its {@link\n * CollectionViewSource.fieldAccess} policy when present, else derived from the\n * boolean `isFieldReadOnly` (default-open). The one place the shell asks \"may this\n * principal edit / see this field?\", so the default-open fallback lives in one spot.\n */\nexport function resolveFieldAccess(\n source: CollectionViewSource,\n field: FieldDef,\n record?: CmsDocument,\n): FieldAccess {\n return source.fieldAccess?.(field, record) ?? (source.isFieldReadOnly(field) ? 'read' : 'edit')\n}\n\n/** Asset display metadata a cell/field renders (thumbnail or labelled badge). */\nexport interface ResolvedAsset {\n readonly url?: string\n readonly contentType?: string\n readonly title?: string\n readonly filename?: string\n readonly alt?: string\n}\n\n/** `publish` capability. */\nexport interface PublishingCapability {\n /** Published-ness from the version pointer (NOT a row field). */\n isPublished(ref: DocumentRef): boolean\n publish(ref: DocumentRef): Promise<void>\n unpublish(ref: DocumentRef): Promise<void>\n /**\n * Live URLs for the routable documents, keyed by id — derived from the\n * PUBLISHED pool (never draft rows) so an unpublished slug/parent edit can't\n * leak into a live link (EC-205/EC-216). `null` when the collection is not\n * routable → the live-URL column is hidden. Only present when `capabilities.liveUrl`.\n */\n liveUrls?(): Map<string, DocumentUrl> | null\n}\n\n/** `versions` capability. */\nexport interface VersioningCapability {\n restore(ref: DocumentRef, version: number, scope?: RestoreScope): Promise<CmsDocument | null>\n /**\n * The document's recorded version history + live pin. Folds the flush-before-read\n * ordering INTO the loader (the adapter's concern), so every fetch lists an\n * authoritative history regardless of which surface triggered the change (EC-224).\n */\n load(ref: DocumentRef): Promise<DocumentVersionsView>\n}\n\n/** `inlineEdit` capability: the list's single-command optimistic cell write (EC-168). */\nexport interface InlineEditCapability {\n commitField(input: CellCommitInput): Promise<CellCommitOutcome>\n}\n\n/** A named lens the header selector offers — id + display name only (the header never needs its state). */\nexport interface NamedView {\n readonly id: string\n /** Display name; a {@link LocalizedString} (EC-343), resolved at the render site like `form.label`. */\n readonly name: LocalizedString\n}\n\n/** `importExport` capability: validate + fold external JSON records into the collection (EC-079). */\nexport interface ImportCapability {\n importDocuments(items: unknown[]): ImportReport\n}\n\n/**\n * `views` capability: config-as-code named lenses (EC-246b). The header selector\n * only *picks* a lens — it never creates/edits/deletes one (shared views are code).\n */\nexport interface NamedViewsCapability {\n /** The built-in default + config-declared named views. */\n list(): readonly NamedView[]\n /** The currently-selected view id. */\n activeId(): string\n /** Switch views (resets the working table state to the view's baseline). */\n setActive(id: string): void\n}\n\nexport type { CellCommitInput, CellCommitOutcome, ImportReport }\n"]}
|
|
@@ -331,6 +331,7 @@ export const deMessages = {
|
|
|
331
331
|
'cms.editor.publishActions': 'Veröffentlichungsoptionen',
|
|
332
332
|
'cms.editor.publishAndClose': 'Veröffentlichen & schließen',
|
|
333
333
|
'cms.editor.saveAndClose': 'Speichern & schließen',
|
|
334
|
+
'cms.editor.duplicateCreated': 'Duplikat als neuer Entwurf erstellt',
|
|
334
335
|
'cms.editor.discardDraft': 'Änderungen verwerfen',
|
|
335
336
|
'cms.editor.discardDraftUpToDateHint': 'Der Entwurf entspricht der veröffentlichten Version — es gibt nichts zu verwerfen.',
|
|
336
337
|
'cms.editor.switchToDraftToDiscard': 'Zum Entwurf wechseln, um Änderungen zu verwerfen',
|
|
@@ -375,7 +376,43 @@ export const deMessages = {
|
|
|
375
376
|
// ─────────────────────────────────────────────────────────────────────────
|
|
376
377
|
'cms.issues.none': 'Keine Probleme',
|
|
377
378
|
'cms.issues.satisfied': 'Dieses Dokument entspricht dem Sammlungsschema.',
|
|
378
|
-
|
|
379
|
+
// EC-443: value fix — the calque „Zu label“ read as broken German.
|
|
380
|
+
'cms.issues.goToField': 'Zum Feld „{field}“',
|
|
381
|
+
// EC-443 — issue-card titles (one per CmsIssueCode, see messages.en.ts).
|
|
382
|
+
'cms.issue.duplicate-collection': 'Doppelte Sammlung',
|
|
383
|
+
'cms.issue.duplicate-field': 'Doppeltes Feld',
|
|
384
|
+
'cms.issue.unknown-collection': 'Unbekannte Sammlung',
|
|
385
|
+
'cms.issue.invalid-collection-config': 'Ungültige Sammlungskonfiguration',
|
|
386
|
+
'cms.issue.invalid-field-config': 'Ungültige Feldkonfiguration',
|
|
387
|
+
'cms.issue.reserved-field-name': 'Reservierter Feldname',
|
|
388
|
+
'cms.issue.missing-required-field': 'Pflichtfeld fehlt',
|
|
389
|
+
'cms.issue.invalid-field-value': 'Ungültiger Wert',
|
|
390
|
+
'cms.issue.unknown-relation-target': 'Fehlende Referenz',
|
|
391
|
+
'cms.issue.unpublished-relation-target': 'Unveröffentlichte Referenz',
|
|
392
|
+
'cms.issue.cardinality-violation': 'Falsche Anzahl von Werten',
|
|
393
|
+
'cms.issue.unknown-asset': 'Fehlende Mediendatei',
|
|
394
|
+
'cms.issue.duplicate-document': 'Doppelter Eintrag',
|
|
395
|
+
'cms.issue.unknown-locale': 'Unbekannte Sprache',
|
|
396
|
+
'cms.issue.missing-localized-value': 'Fehlende Übersetzung',
|
|
397
|
+
'cms.issue.route-conflict': 'URL-Konflikt',
|
|
398
|
+
'cms.issue.redirect-loop': 'Weiterleitungsschleife',
|
|
399
|
+
'cms.issue.unknown-route-target': 'Fehlende Seite',
|
|
400
|
+
'cms.issue.hierarchy-cycle': 'Schleife in der Seitenhierarchie',
|
|
401
|
+
'cms.issue.document-nesting-depth': 'Inhalt zu tief verschachtelt',
|
|
402
|
+
'cms.issue.singleton-document-drift': 'Mehr als ein Dokument',
|
|
403
|
+
'cms.issue.duplicate-alias': 'Slot doppelt belegt',
|
|
404
|
+
'cms.issue.unknown-alias': 'Unbekannter Slot',
|
|
405
|
+
'cms.issue.unfilled-slot': 'Leerer Slot',
|
|
406
|
+
'cms.issue.custom-validation': 'Validierungsregel',
|
|
407
|
+
'cms.issue.unknown-version': 'Unbekannte Version',
|
|
408
|
+
// EC-443 — issue-card body templates (see messages.en.ts for the fallback contract).
|
|
409
|
+
'cms.issueBody.missing-required-field': 'Das Pflichtfeld „{field}“ ist nicht ausgefüllt.',
|
|
410
|
+
'cms.issueBody.missing-localized-value.default': 'Das Pflichtfeld „{field}“ hat keinen Wert für die Standardsprache „{locale}“.',
|
|
411
|
+
'cms.issueBody.missing-localized-value.fallback': 'Das Feld „{field}“ hat keinen Wert für „{locale}“ und keinen Fallback.',
|
|
412
|
+
'cms.issueBody.unknown-asset': 'Das Feld „{field}“ verweist auf die unbekannte Mediendatei „{asset}“.',
|
|
413
|
+
'cms.issueBody.document-nesting-depth.error': 'Dieser Inhalt ist {depth} Ebenen tief verschachtelt und überschreitet damit das Speicherlimit von {limit} Ebenen – teilen Sie ihn in kleinere verknüpfte Einträge auf.',
|
|
414
|
+
'cms.issueBody.document-nesting-depth.warning': 'Dieser Inhalt ist {depth} Ebenen tief verschachtelt und nähert sich dem Speicherlimit von {limit} Ebenen.',
|
|
415
|
+
'cms.issueBody.hierarchy-cycle': 'Dieser Eintrag ist sein eigener Vorfahr – die Kette der übergeordneten Einträge bildet eine Schleife.',
|
|
379
416
|
// ─────────────────────────────────────────────────────────────────────────
|
|
380
417
|
// CMS — details pane (slot + sidebar fields)
|
|
381
418
|
// ─────────────────────────────────────────────────────────────────────────
|
|
@@ -820,6 +857,12 @@ export const deMessages = {
|
|
|
820
857
|
'builder.blocksPanel.copyExactAria': 'Exakte Konfiguration kopieren — {title} ({variant})',
|
|
821
858
|
'builder.blocksPanel.copyExactConfiguration': 'Exakte Konfiguration kopieren',
|
|
822
859
|
'builder.blocksPanel.previewFailed': 'Vorschau fehlgeschlagen: {error}',
|
|
860
|
+
// EC-439: one tile per component; a 2+-fixture component gets a variants chip + flyout.
|
|
861
|
+
'builder.blocksPanel.insertComponentAria': '{title} einfügen',
|
|
862
|
+
'builder.blocksPanel.variantCount': '{count} Varianten',
|
|
863
|
+
'builder.blocksPanel.variantsAria': '{count} Varianten von {title} anzeigen',
|
|
864
|
+
'builder.blocksPanel.variantsHeading': 'Varianten',
|
|
865
|
+
'builder.blocksPanel.variantIssuesHint': 'Einige Varianten haben Validierungsprobleme.',
|
|
823
866
|
'builder.pageTree.title': 'Seitenstruktur',
|
|
824
867
|
'builder.pageTree.hiddenByCondition': 'Durch Bedingung ausgeblendet',
|
|
825
868
|
'builder.pageTree.conditional': 'Bedingt',
|