@conduction/nextcloud-vue 2.56.0-beta.12 → 2.56.0-beta.13
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/esm/components/CnAppNav/CnAppNav.vue2.js +99 -3
- package/dist/esm/components/CnAppNav/CnAppNav.vue2.js.map +1 -1
- package/dist/esm/components/CnAppNav/CnAppNav.vue3.js +23 -7
- package/dist/esm/components/CnAppNav/CnAppNav.vue3.js.map +1 -1
- package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue2.js +13 -0
- package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue2.js.map +1 -1
- package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue3.js +2 -1
- package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +125 -4
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +439 -381
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue2.js +9 -3
- package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue2.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue3.js +1 -1
- package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +142 -26
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +195 -124
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
- package/dist/esm/components/CnFkResolveCell/CnFkResolveCell.vue2.js +3 -1
- package/dist/esm/components/CnFkResolveCell/CnFkResolveCell.vue2.js.map +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js +144 -4
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
- package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue2.js +84 -11
- package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue3.js +3 -2
- package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue2.js +11 -0
- package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue2.js.map +1 -1
- package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue3.js +2 -1
- package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue3.js.map +1 -1
- package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js +22 -3
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js +10 -3
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js.map +1 -1
- package/dist/esm/l10n/en.json.js +3 -0
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +3 -0
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/mixins/pageViews.js +400 -0
- package/dist/esm/mixins/pageViews.js.map +1 -0
- package/dist/esm/nextcloud-vue.css +182 -0
- package/dist/esm/store/useObjectStore.js +118 -33
- package/dist/esm/store/useObjectStore.js.map +1 -1
- package/dist/esm/utils/actionsDispatcher.js +25 -2
- package/dist/esm/utils/actionsDispatcher.js.map +1 -1
- package/dist/esm/utils/columnFilters.js +27 -3
- package/dist/esm/utils/columnFilters.js.map +1 -1
- package/dist/esm/utils/schema.js +40 -2
- package/dist/esm/utils/schema.js.map +1 -1
- package/dist/esm/utils/validateManifestV2.compiled.js +2 -2
- package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +2001 -684
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +182 -0
- package/l10n/en.json +3 -0
- package/l10n/nl.json +3 -0
- package/package.json +1 -1
- package/src/components/CnAppNav/CnAppNav.vue +148 -7
- package/src/components/CnBreadcrumbs/CnBreadcrumbs.vue +14 -0
- package/src/components/CnDashboardPage/CnDashboardPage.vue +194 -10
- package/src/components/CnDataTable/CnColumnFilterPopover.vue +11 -5
- package/src/components/CnDetailPage/CnDetailPage.md +5 -1
- package/src/components/CnDetailPage/CnDetailPage.vue +208 -11
- package/src/components/CnFkResolveCell/CnFkResolveCell.vue +3 -1
- package/src/components/CnFormDialog/CnFormDialog.vue +144 -4
- package/src/components/CnHeaderWidget/CnHeaderWidget.vue +100 -13
- package/src/components/CnSegmentedControl/CnSegmentedControl.md +1 -1
- package/src/components/CnSegmentedControl/CnSegmentedControl.vue +12 -0
- package/src/components/CnStagesWidget/CnStagesWidget.vue +9 -0
- package/src/components/CnStatWidget/CnStatWidget.vue +33 -5
- package/src/css/detail-page.css +27 -0
- package/src/css/kpi-card.css +47 -0
- package/src/mixins/pageViews.js +396 -0
- package/src/schemas/app-manifest-v2.schema.json +51 -2
- package/src/store/useObjectStore.js +118 -33
- package/src/utils/actionsDispatcher.js +25 -2
- package/src/utils/columnFilters.js +29 -2
- package/src/utils/schema.js +40 -2
- package/src/utils/validateManifestV2.compiled.js +2 -2
|
@@ -125,7 +125,9 @@ var script = {
|
|
|
125
125
|
if (!store) {
|
|
126
126
|
return
|
|
127
127
|
}
|
|
128
|
-
|
|
128
|
+
// `schema` is a slug the manifest author wrote, not a `$ref` title:
|
|
129
|
+
// use it exactly as given (a camelCase slug must not be kebab-cased).
|
|
130
|
+
const type = resolveObjectOpType(store, { register: this.register, schema: this.schema }, { exactSchema: true });
|
|
129
131
|
await Promise.all(this.ids.map(async (id) => {
|
|
130
132
|
if (this.labels[id]) {
|
|
131
133
|
return
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnFkResolveCell.vue2.js","sources":["../../../../src/components/CnFkResolveCell/CnFkResolveCell.vue"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<template>\n\t<span class=\"cn-fk-resolve-cell\" :title=\"ids.join(', ')\">{{ display }}</span>\n</template>\n\n<script>\nimport { useObjectStore } from '../../store/useObjectStore.js'\nimport { resolveObjectOpType } from '../../utils/actionsDispatcher.js'\n\n/**\n * CnFkResolveCell — built-in `fkResolve` cell widget: resolves a reference\n * uuid (or an array of uuids) to the related object's display label.\n *\n * Fetches through the shared object store (`useObjectStore`), which gives\n * per-schema caching for free: objects are cached under the\n * `<register>/<schema>` type slug and concurrent fetches for the same id are\n * de-duplicated, so a column of 50 rows pointing at the same client resolves\n * with a single request. Unresolvable ids (fetch failure, missing label\n * field, no Pinia store) degrade to the raw id — the cell never blanks out\n * or throws.\n *\n * Declared on a table column via the cell-widget mechanism:\n * ```js\n * {\n * key: 'client',\n * label: 'Client',\n * widget: 'fkResolve',\n * widgetProps: { register: 'crm', schema: 'client', labelField: 'name' },\n * }\n * ```\n */\nexport default {\n\tname: 'CnFkResolveCell',\n\n\tprops: {\n\t\t/**\n\t\t * The reference value: a single object uuid/id, or an array of them\n\t\t * (multi-reference properties render comma-joined labels).\n\t\t *\n\t\t * @type {string|number|Array<string|number>|null}\n\t\t */\n\t\tvalue: {\n\t\t\ttype: [String, Number, Array],\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** OpenRegister register slug (or id) the reference points into. */\n\t\tregister: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** OpenRegister schema slug (or id) the reference points into. */\n\t\tschema: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Property on the referenced object used as the display label.\n\t\t * Falls back to `title`, then `@self.name`, then the raw id.\n\t\t */\n\t\tlabelField: {\n\t\t\ttype: String,\n\t\t\tdefault: 'name',\n\t\t},\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\t/**\n\t\t\t * Resolved labels keyed by String(id). Filled from the store cache\n\t\t\t * synchronously where possible, otherwise after the fetch resolves.\n\t\t\t *\n\t\t\t * @type {Record<string, string>}\n\t\t\t */\n\t\t\tlabels: {},\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The reference ids as a normalized string array (empty when the\n\t\t * value is unset).\n\t\t *\n\t\t * @return {string[]}\n\t\t */\n\t\tids() {\n\t\t\tif (this.value === null || this.value === undefined || this.value === '') {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\tconst list = Array.isArray(this.value) ? this.value : [this.value]\n\t\t\treturn list\n\t\t\t\t.filter((id) => id !== null && id !== undefined && id !== '')\n\t\t\t\t.map((id) => String(id))\n\t\t},\n\n\t\t/**\n\t\t * The rendered text: each id's resolved label (raw id while\n\t\t * resolving / when unresolvable), comma-joined for multi-refs.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tdisplay() {\n\t\t\treturn this.ids.map((id) => this.labels[id] || id).join(', ')\n\t\t},\n\t},\n\n\twatch: {\n\t\tids: {\n\t\t\timmediate: true,\n\t\t\thandler() {\n\t\t\t\tthis.resolveAll()\n\t\t\t},\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Resolve every id via the shared object store: cache hit renders\n\t\t * synchronously, misses fetch (store-level in-flight dedup applies).\n\t\t * A failed / label-less fetch leaves the raw id in place.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync resolveAll() {\n\t\t\tif (!this.register || !this.schema || this.ids.length === 0) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst store = this.getObjectStore()\n\t\t\tif (!store) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst type = resolveObjectOpType(store, { register: this.register, schema: this.schema })\n\t\t\tawait Promise.all(this.ids.map(async (id) => {\n\t\t\t\tif (this.labels[id]) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tconst cached = store.objects && store.objects[type] && store.objects[type][id]\n\t\t\t\tconst obj = cached || await this.fetchOne(store, type, id)\n\t\t\t\tconst label = this.pickLabel(obj)\n\t\t\t\tif (label) {\n\t\t\t\t\tthis.labels[id] = label\n\t\t\t\t}\n\t\t\t}))\n\t\t},\n\n\t\t/**\n\t\t * Fetch one referenced object; null on any failure so the raw id\n\t\t * stays visible.\n\t\t *\n\t\t * @param {object} store The object store instance.\n\t\t * @param {string} type The `<register>/<schema>` type slug.\n\t\t * @param {string} id The object id.\n\t\t * @return {Promise<object|null>}\n\t\t */\n\t\tasync fetchOne(store, type, id) {\n\t\t\ttry {\n\t\t\t\treturn await store.fetchObject(type, id)\n\t\t\t} catch {\n\t\t\t\treturn null\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Extract the display label off a resolved object: `labelField`\n\t\t * first, then `title`, then `@self.name`. Translatable per-language\n\t\t * maps collapse to their first value.\n\t\t *\n\t\t * @param {object|null} obj The referenced object.\n\t\t * @return {string} The label, or '' when none is usable.\n\t\t */\n\t\tpickLabel(obj) {\n\t\t\tif (!obj || typeof obj !== 'object') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst candidates = [obj[this.labelField], obj.title, obj['@self'] && obj['@self'].name]\n\t\t\tfor (const raw of candidates) {\n\t\t\t\tif (typeof raw === 'string' && raw !== '') {\n\t\t\t\t\treturn raw\n\t\t\t\t}\n\t\t\t\tif (typeof raw === 'number') {\n\t\t\t\t\treturn String(raw)\n\t\t\t\t}\n\t\t\t\tif (raw && typeof raw === 'object' && !Array.isArray(raw)) {\n\t\t\t\t\tconst first = Object.values(raw).find((v) => typeof v === 'string' && v !== '')\n\t\t\t\t\tif (first) {\n\t\t\t\t\t\treturn first\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn ''\n\t\t},\n\n\t\t/**\n\t\t * The shared object store, or null when no Pinia is active (the cell\n\t\t * then renders the raw id).\n\t\t *\n\t\t * @return {object|null}\n\t\t */\n\t\tgetObjectStore() {\n\t\t\ttry {\n\t\t\t\treturn useObjectStore()\n\t\t\t} catch {\n\t\t\t\treturn null\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-fk-resolve-cell {\n\twhite-space: nowrap;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n</style>\n"],"names":[],"mappings":";;;AAaA;AACC;;AAEA;AACA;;AAEA;AACA;;;AAGA;AACA;;;;;AAKA;AACA;AACA;;AAEA;;AAED,aAAe;CACd,IAAI,EAAE,iBAAiB;;AAEvB,CAAA,KAAK,EAAE;AACN;;;AAGC;;;AAGD,EAAA,KAAK,EAAE;GACN,IAAI,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC;GAC7B,OAAO,EAAE,IAAI;GACb;;;AAGD,EAAA,QAAQ,EAAE;GACT,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,EAAE;GACX;;;AAGD,EAAA,MAAM,EAAE;GACP,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,EAAE;GACX;;AAED;;;;EAIA,UAAU,EAAE;GACX,IAAI,EAAE,MAAM;AACZ,GAAA,OAAO,EAAE,MAAM;GACf;EACD;;AAED,CAAA,IAAI,GAAG;AACN,EAAA,OAAO;AACN;;;AAGC;;;AAGD,GAAA,MAAM,EAAE,EAAE;AACX,GAAA;EACA;;AAED,CAAA,QAAQ,EAAE;AACT;;AAEC;AACA;AACA;;EAED,GAAG,GAAG;AACL,GAAA,IAAI,IAAI,CAAC,KAAI,KAAM,IAAK,IAAG,IAAI,CAAC,KAAM,KAAI,SAAU,IAAG,IAAI,CAAC,KAAM,KAAI,EAAE,EAAE;AACzE,IAAA,OAAO,EAAC;AACT,IAAA;GACA,MAAM,IAAK,GAAE,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAE,GAAE,IAAI,CAAC,KAAM,GAAE,CAAC,IAAI,CAAC,KAAK,EAAA;AACjE,GAAA,OAAO,IAAG;KACR,MAAM,CAAC,CAAC,EAAE,KAAK,EAAG,KAAI,IAAK,IAAG,EAAC,KAAM,SAAQ,IAAK,OAAO,EAAE,CAAA;AAC5D,KAAC,GAAG,CAAC,CAAC,EAAE,KAAK,MAAM,CAAC,EAAE,CAAC,CAAA;GACxB;;AAED;;;AAGC;AACA;;AAED,EAAA,OAAO,GAAG;GACT,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,IAAI,CAAC,MAAM,CAAC,EAAE,CAAE,IAAG,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAA;GAC5D;EACD;;AAED,CAAA,KAAK,EAAE;EACN,GAAG,EAAE;AACJ,GAAA,SAAS,EAAE,IAAI;AACf,GAAA,OAAO,GAAG;AACT,IAAA,IAAI,CAAC,UAAU,GAAC;IAChB;GACD;EACD;;AAED,CAAA,OAAO,EAAE;AACR;;;AAGC;AACA;;;AAGD,EAAA,MAAM,UAAU,GAAG;GAClB,IAAI,CAAC,IAAI,CAAC,QAAO,IAAK,CAAC,IAAI,CAAC,MAAK,IAAK,IAAI,CAAC,GAAG,CAAC,WAAW,CAAC,EAAE;IAC5D,MAAK;AACN,IAAA;AACA,GAAA,MAAM,KAAM,GAAE,IAAI,CAAC,cAAc,GAAC;GAClC,IAAI,CAAC,KAAK,EAAE;IACX,MAAK;AACN,IAAA
|
|
1
|
+
{"version":3,"file":"CnFkResolveCell.vue2.js","sources":["../../../../src/components/CnFkResolveCell/CnFkResolveCell.vue"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<template>\n\t<span class=\"cn-fk-resolve-cell\" :title=\"ids.join(', ')\">{{ display }}</span>\n</template>\n\n<script>\nimport { useObjectStore } from '../../store/useObjectStore.js'\nimport { resolveObjectOpType } from '../../utils/actionsDispatcher.js'\n\n/**\n * CnFkResolveCell — built-in `fkResolve` cell widget: resolves a reference\n * uuid (or an array of uuids) to the related object's display label.\n *\n * Fetches through the shared object store (`useObjectStore`), which gives\n * per-schema caching for free: objects are cached under the\n * `<register>/<schema>` type slug and concurrent fetches for the same id are\n * de-duplicated, so a column of 50 rows pointing at the same client resolves\n * with a single request. Unresolvable ids (fetch failure, missing label\n * field, no Pinia store) degrade to the raw id — the cell never blanks out\n * or throws.\n *\n * Declared on a table column via the cell-widget mechanism:\n * ```js\n * {\n * key: 'client',\n * label: 'Client',\n * widget: 'fkResolve',\n * widgetProps: { register: 'crm', schema: 'client', labelField: 'name' },\n * }\n * ```\n */\nexport default {\n\tname: 'CnFkResolveCell',\n\n\tprops: {\n\t\t/**\n\t\t * The reference value: a single object uuid/id, or an array of them\n\t\t * (multi-reference properties render comma-joined labels).\n\t\t *\n\t\t * @type {string|number|Array<string|number>|null}\n\t\t */\n\t\tvalue: {\n\t\t\ttype: [String, Number, Array],\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** OpenRegister register slug (or id) the reference points into. */\n\t\tregister: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** OpenRegister schema slug (or id) the reference points into. */\n\t\tschema: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Property on the referenced object used as the display label.\n\t\t * Falls back to `title`, then `@self.name`, then the raw id.\n\t\t */\n\t\tlabelField: {\n\t\t\ttype: String,\n\t\t\tdefault: 'name',\n\t\t},\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\t/**\n\t\t\t * Resolved labels keyed by String(id). Filled from the store cache\n\t\t\t * synchronously where possible, otherwise after the fetch resolves.\n\t\t\t *\n\t\t\t * @type {Record<string, string>}\n\t\t\t */\n\t\t\tlabels: {},\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The reference ids as a normalized string array (empty when the\n\t\t * value is unset).\n\t\t *\n\t\t * @return {string[]}\n\t\t */\n\t\tids() {\n\t\t\tif (this.value === null || this.value === undefined || this.value === '') {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\tconst list = Array.isArray(this.value) ? this.value : [this.value]\n\t\t\treturn list\n\t\t\t\t.filter((id) => id !== null && id !== undefined && id !== '')\n\t\t\t\t.map((id) => String(id))\n\t\t},\n\n\t\t/**\n\t\t * The rendered text: each id's resolved label (raw id while\n\t\t * resolving / when unresolvable), comma-joined for multi-refs.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tdisplay() {\n\t\t\treturn this.ids.map((id) => this.labels[id] || id).join(', ')\n\t\t},\n\t},\n\n\twatch: {\n\t\tids: {\n\t\t\timmediate: true,\n\t\t\thandler() {\n\t\t\t\tthis.resolveAll()\n\t\t\t},\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Resolve every id via the shared object store: cache hit renders\n\t\t * synchronously, misses fetch (store-level in-flight dedup applies).\n\t\t * A failed / label-less fetch leaves the raw id in place.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync resolveAll() {\n\t\t\tif (!this.register || !this.schema || this.ids.length === 0) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst store = this.getObjectStore()\n\t\t\tif (!store) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\t// `schema` is a slug the manifest author wrote, not a `$ref` title:\n\t\t\t// use it exactly as given (a camelCase slug must not be kebab-cased).\n\t\t\tconst type = resolveObjectOpType(store, { register: this.register, schema: this.schema }, { exactSchema: true })\n\t\t\tawait Promise.all(this.ids.map(async (id) => {\n\t\t\t\tif (this.labels[id]) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tconst cached = store.objects && store.objects[type] && store.objects[type][id]\n\t\t\t\tconst obj = cached || await this.fetchOne(store, type, id)\n\t\t\t\tconst label = this.pickLabel(obj)\n\t\t\t\tif (label) {\n\t\t\t\t\tthis.labels[id] = label\n\t\t\t\t}\n\t\t\t}))\n\t\t},\n\n\t\t/**\n\t\t * Fetch one referenced object; null on any failure so the raw id\n\t\t * stays visible.\n\t\t *\n\t\t * @param {object} store The object store instance.\n\t\t * @param {string} type The `<register>/<schema>` type slug.\n\t\t * @param {string} id The object id.\n\t\t * @return {Promise<object|null>}\n\t\t */\n\t\tasync fetchOne(store, type, id) {\n\t\t\ttry {\n\t\t\t\treturn await store.fetchObject(type, id)\n\t\t\t} catch {\n\t\t\t\treturn null\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Extract the display label off a resolved object: `labelField`\n\t\t * first, then `title`, then `@self.name`. Translatable per-language\n\t\t * maps collapse to their first value.\n\t\t *\n\t\t * @param {object|null} obj The referenced object.\n\t\t * @return {string} The label, or '' when none is usable.\n\t\t */\n\t\tpickLabel(obj) {\n\t\t\tif (!obj || typeof obj !== 'object') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst candidates = [obj[this.labelField], obj.title, obj['@self'] && obj['@self'].name]\n\t\t\tfor (const raw of candidates) {\n\t\t\t\tif (typeof raw === 'string' && raw !== '') {\n\t\t\t\t\treturn raw\n\t\t\t\t}\n\t\t\t\tif (typeof raw === 'number') {\n\t\t\t\t\treturn String(raw)\n\t\t\t\t}\n\t\t\t\tif (raw && typeof raw === 'object' && !Array.isArray(raw)) {\n\t\t\t\t\tconst first = Object.values(raw).find((v) => typeof v === 'string' && v !== '')\n\t\t\t\t\tif (first) {\n\t\t\t\t\t\treturn first\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn ''\n\t\t},\n\n\t\t/**\n\t\t * The shared object store, or null when no Pinia is active (the cell\n\t\t * then renders the raw id).\n\t\t *\n\t\t * @return {object|null}\n\t\t */\n\t\tgetObjectStore() {\n\t\t\ttry {\n\t\t\t\treturn useObjectStore()\n\t\t\t} catch {\n\t\t\t\treturn null\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-fk-resolve-cell {\n\twhite-space: nowrap;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n</style>\n"],"names":[],"mappings":"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"}
|
|
@@ -10,6 +10,7 @@ import { formDraftMixin, draftKey, readDraft } from '../../composables/useFormDr
|
|
|
10
10
|
import { useIntegrationRegistry } from '../../composables/useIntegrationRegistry.js';
|
|
11
11
|
import { TENANT_CONTEXT_KEY } from '../../composables/useTenantContext.js';
|
|
12
12
|
import { useObjectStore } from '../../store/useObjectStore.js';
|
|
13
|
+
import { resolveCreateOverrideHandler } from '../../utils/actionsDispatcher.js';
|
|
13
14
|
import { extendsFormDeclarations, prefillDeclarations, definitionQueryParams, propertiesFromDefinitions, prefillValues, splitDynamicFormData, DYNAMIC_KEY_PREFIX } from '../../utils/dynamicProperties.js';
|
|
14
15
|
import { shouldShow } from '../../utils/fieldCondition.js';
|
|
15
16
|
import { resolveNextcloudGroup, searchNextcloudGroups } from '../../utils/groupAutocomplete.js';
|
|
@@ -246,6 +247,18 @@ var script = {
|
|
|
246
247
|
* identity when used standalone (no CnAppRoot ancestor).
|
|
247
248
|
*/
|
|
248
249
|
cnTranslate: { default: () => (key) => key },
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* The app manifest, the v2 registry, the legacy customComponents map and
|
|
253
|
+
* the registry-modal opener, all provided by CnAppRoot. A select-or-create
|
|
254
|
+
* picker reads them to create an object the way the app's own index page
|
|
255
|
+
* does (`config.createOverride` / `config.createModal`). Standalone (no
|
|
256
|
+
* CnAppRoot) they are absent and the generic nested form is used.
|
|
257
|
+
*/
|
|
258
|
+
cnManifest: { default: null },
|
|
259
|
+
cnRegistry: { default: null },
|
|
260
|
+
cnCustomComponents: { default: null },
|
|
261
|
+
cnOpenModal: { default: null },
|
|
249
262
|
},
|
|
250
263
|
|
|
251
264
|
props: {
|
|
@@ -2138,6 +2151,16 @@ var script = {
|
|
|
2138
2151
|
store.registerObjectType(slug, schemaRef, register);
|
|
2139
2152
|
}
|
|
2140
2153
|
const labelField = this.referenceLabelField(field);
|
|
2154
|
+
// The app may own how this schema is created (pipelinq's client and
|
|
2155
|
+
// contact need a contact-first endpoint; a plain save 400s). A
|
|
2156
|
+
// create override keeps this form and saves through the app's
|
|
2157
|
+
// handler; a create modal (only when no override exists) opens the
|
|
2158
|
+
// app's own dialog instead of the generic form.
|
|
2159
|
+
const appCreate = this.appCreateFor(register, schemaRef);
|
|
2160
|
+
if (!appCreate.override && appCreate.modal) {
|
|
2161
|
+
return this.openAppCreateModal(appCreate.modal, store, slug, labelField, term)
|
|
2162
|
+
}
|
|
2163
|
+
const override = appCreate.override;
|
|
2141
2164
|
let schema = null;
|
|
2142
2165
|
try {
|
|
2143
2166
|
schema = await store.fetchSchema(slug);
|
|
@@ -2145,14 +2168,113 @@ var script = {
|
|
|
2145
2168
|
schema = null;
|
|
2146
2169
|
}
|
|
2147
2170
|
if (!schema || !schema.properties) {
|
|
2148
|
-
|
|
2171
|
+
const payload = { [labelField]: term };
|
|
2172
|
+
return override
|
|
2173
|
+
? override(payload, { register, schema: schemaRef, objectType: slug, effectiveSchema: null })
|
|
2174
|
+
: store.saveObject(slug, payload)
|
|
2149
2175
|
}
|
|
2150
2176
|
const initialData = {};
|
|
2151
2177
|
if (term && schema.properties[labelField]) {
|
|
2152
2178
|
initialData[labelField] = term;
|
|
2153
2179
|
}
|
|
2154
2180
|
return new Promise((resolve) => {
|
|
2155
|
-
this.nestedCreate = { field, schema, register, slug, initialData, resolve };
|
|
2181
|
+
this.nestedCreate = { field, schema, register, slug, schemaRef, initialData, override, resolve };
|
|
2182
|
+
})
|
|
2183
|
+
},
|
|
2184
|
+
|
|
2185
|
+
/**
|
|
2186
|
+
* How the app creates objects of a schema, read off the manifest page
|
|
2187
|
+
* that lists them: the index page whose `config.schema` (and, when it
|
|
2188
|
+
* names one, `config.register`) matches. Its `config.createOverride` is
|
|
2189
|
+
* resolved to the registered handler; its `config.createModal` is the
|
|
2190
|
+
* registry key of a `kind: 'modal'` dialog.
|
|
2191
|
+
*
|
|
2192
|
+
* @spec openspec/changes/review-round-two/specs/schema-utilities/spec.md
|
|
2193
|
+
* @param {string} register The reference's register.
|
|
2194
|
+
* @param {string} schemaRef The reference's schema slug or id.
|
|
2195
|
+
* @return {{override: (((formData: object, ctx: object) => Promise<object>)|null), modal: (string|null)}} What the app declared.
|
|
2196
|
+
*/
|
|
2197
|
+
appCreateFor(register, schemaRef) {
|
|
2198
|
+
const none = { override: null, modal: null };
|
|
2199
|
+
const pages = this.cnManifest && Array.isArray(this.cnManifest.pages) ? this.cnManifest.pages : [];
|
|
2200
|
+
const wanted = String(schemaRef).toLowerCase();
|
|
2201
|
+
const page = pages.find((p) => {
|
|
2202
|
+
const config = p && p.config;
|
|
2203
|
+
if (!config || config.schema === undefined || config.schema === null) {
|
|
2204
|
+
return false
|
|
2205
|
+
}
|
|
2206
|
+
if (String(config.schema).toLowerCase() !== wanted) {
|
|
2207
|
+
return false
|
|
2208
|
+
}
|
|
2209
|
+
return !config.register || String(config.register) === String(register)
|
|
2210
|
+
});
|
|
2211
|
+
if (!page) {
|
|
2212
|
+
return none
|
|
2213
|
+
}
|
|
2214
|
+
const config = page.config;
|
|
2215
|
+
const override = typeof config.createOverride === 'function'
|
|
2216
|
+
? config.createOverride
|
|
2217
|
+
: resolveCreateOverrideHandler(config.createOverride, this.cnRegistry, this.cnCustomComponents);
|
|
2218
|
+
const modalEntry = typeof config.createModal === 'string' && config.createModal !== ''
|
|
2219
|
+
? (this.cnRegistry || {})[config.createModal]
|
|
2220
|
+
: null;
|
|
2221
|
+
const modal = modalEntry && modalEntry.kind === 'modal' && typeof this.cnOpenModal === 'function'
|
|
2222
|
+
? config.createModal
|
|
2223
|
+
: null;
|
|
2224
|
+
return { override: override || null, modal }
|
|
2225
|
+
},
|
|
2226
|
+
|
|
2227
|
+
/**
|
|
2228
|
+
* Open the app's own create dialog (a registry modal) for a picker and
|
|
2229
|
+
* wait for it. The dialog reports the new object (or its id) with a
|
|
2230
|
+
* `created` event; `close` without one resolves null so the field stays
|
|
2231
|
+
* as it was. A reported id is fetched so the picker can label it.
|
|
2232
|
+
*
|
|
2233
|
+
* @spec openspec/changes/review-round-two/specs/schema-utilities/spec.md
|
|
2234
|
+
* @param {string} key The registry key of the modal.
|
|
2235
|
+
* @param {object} store The object store.
|
|
2236
|
+
* @param {string} slug The object type slug.
|
|
2237
|
+
* @param {string} labelField The property the typed term belongs in.
|
|
2238
|
+
* @param {string} term The typed search term.
|
|
2239
|
+
* @return {Promise<object|null>} The created object, or null.
|
|
2240
|
+
*/
|
|
2241
|
+
openAppCreateModal(key, store, slug, labelField, term) {
|
|
2242
|
+
return new Promise((resolve) => {
|
|
2243
|
+
let settled = false;
|
|
2244
|
+
let reported = false;
|
|
2245
|
+
const settle = (value) => {
|
|
2246
|
+
if (!settled) {
|
|
2247
|
+
settled = true;
|
|
2248
|
+
resolve(value);
|
|
2249
|
+
}
|
|
2250
|
+
};
|
|
2251
|
+
this.cnOpenModal(key, {
|
|
2252
|
+
initialData: term ? { [labelField]: term } : {},
|
|
2253
|
+
onCreated: async (created) => {
|
|
2254
|
+
if (created && typeof created === 'object') {
|
|
2255
|
+
settle(created);
|
|
2256
|
+
return
|
|
2257
|
+
}
|
|
2258
|
+
if (typeof created !== 'string' && typeof created !== 'number') {
|
|
2259
|
+
return
|
|
2260
|
+
}
|
|
2261
|
+
reported = true;
|
|
2262
|
+
let obj;
|
|
2263
|
+
try {
|
|
2264
|
+
obj = await store.fetchObject(slug, String(created));
|
|
2265
|
+
} catch {
|
|
2266
|
+
obj = null;
|
|
2267
|
+
}
|
|
2268
|
+
settle(obj || { id: String(created), [labelField]: term });
|
|
2269
|
+
},
|
|
2270
|
+
onClose: () => {
|
|
2271
|
+
// A dialog often emits `close` right after `created`,
|
|
2272
|
+
// while the id is still being fetched: that fetch settles.
|
|
2273
|
+
if (!reported) {
|
|
2274
|
+
settle(null);
|
|
2275
|
+
}
|
|
2276
|
+
},
|
|
2277
|
+
});
|
|
2156
2278
|
})
|
|
2157
2279
|
},
|
|
2158
2280
|
|
|
@@ -2171,10 +2293,28 @@ var script = {
|
|
|
2171
2293
|
return
|
|
2172
2294
|
}
|
|
2173
2295
|
const store = this.getObjectStore();
|
|
2174
|
-
|
|
2296
|
+
let created = null;
|
|
2297
|
+
let thrown = null;
|
|
2298
|
+
if (pending.override) {
|
|
2299
|
+
// The app's create override owns persistence (same contract as
|
|
2300
|
+
// CnIndexPage's `createOverride`).
|
|
2301
|
+
try {
|
|
2302
|
+
created = await pending.override(formData, {
|
|
2303
|
+
register: pending.register,
|
|
2304
|
+
schema: pending.schemaRef,
|
|
2305
|
+
objectType: pending.slug,
|
|
2306
|
+
effectiveSchema: pending.schema,
|
|
2307
|
+
});
|
|
2308
|
+
} catch (err) {
|
|
2309
|
+
thrown = (err && err.response && err.response.data && err.response.data.error) || (err && err.message) || null;
|
|
2310
|
+
}
|
|
2311
|
+
} else {
|
|
2312
|
+
created = store ? await store.saveObject(pending.slug, formData) : null;
|
|
2313
|
+
}
|
|
2175
2314
|
if (!created) {
|
|
2176
2315
|
const error = store && store.errors ? store.errors[pending.slug] : null;
|
|
2177
|
-
const message =
|
|
2316
|
+
const message = thrown
|
|
2317
|
+
|| (!pending.override && error && error.message)
|
|
2178
2318
|
|| translate('nextcloud-vue', 'The item could not be created.');
|
|
2179
2319
|
const dialog = this.$refs.nestedCreateDialog;
|
|
2180
2320
|
if (dialog && typeof dialog.setValidationErrors === 'function') {
|