@conduction/nextcloud-vue 2.82.0 → 2.83.0
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/CnActionsBar/CnActionsBar.vue2.js +2 -2
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js.map +1 -1
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue.js +1 -1
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js +24 -0
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js.map +1 -1
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js +9 -4
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +9 -0
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +96 -12
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +95 -50
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnMassActionBar/CnMassActionBar.vue2.js +2 -2
- package/dist/esm/components/CnMassActionBar/CnMassActionBar.vue2.js.map +1 -1
- package/dist/esm/components/CnMassImportDialog/CnMassImportDialog.vue2.js +2 -2
- package/dist/esm/components/CnMassImportDialog/CnMassImportDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue.js +1 -1
- package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue2.js +16 -2
- package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue3.js +3 -3
- package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnReportsPage/CnReportsPage.vue.js +1 -1
- package/dist/esm/components/CnReportsPage/CnReportsPage.vue2.js +43 -0
- package/dist/esm/components/CnReportsPage/CnReportsPage.vue2.js.map +1 -1
- package/dist/esm/components/CnReportsPage/CnReportsPage.vue3.js +130 -53
- package/dist/esm/components/CnReportsPage/CnReportsPage.vue3.js.map +1 -1
- package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue.js +1 -1
- package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue2.js +70 -5
- package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue3.js +61 -35
- package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue.js +1 -1
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js +3 -1
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue.js +1 -1
- package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue2.js +55 -3
- package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue3.js +88 -71
- package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue.js +1 -1
- package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue2.js +18 -4
- package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue3.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +3 -3
- package/dist/esm/library-style.js +1 -1
- package/dist/esm/nextcloud-vue.css +549 -338
- package/dist/esm/utils/fetchAggregate.js +25 -2
- package/dist/esm/utils/fetchAggregate.js.map +1 -1
- package/dist/esm/utils/switchValues.js +62 -0
- package/dist/esm/utils/switchValues.js.map +1 -0
- package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
- package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +36086 -35536
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +549 -338
- package/l10n/nl.json +3 -3
- package/package.json +1 -1
- package/src/components/CnAdminSettingsShell/CnAdminSettingsShell.vue +31 -2
- package/src/components/CnDashboardPage/CnDashboardPage.vue +9 -0
- package/src/components/CnIndexPage/CnIndexPage.vue +125 -15
- package/src/components/CnRadioCardsWidget/CnRadioCardsWidget.vue +54 -3
- package/src/components/CnReportsPage/CnReportsPage.vue +84 -4
- package/src/components/CnSettingsFormWidget/CnSettingsFormWidget.vue +110 -18
- package/src/components/CnStatWidget/CnStatWidget.vue +3 -1
- package/src/components/CnStatsBlockWidget/CnStatsBlockWidget.vue +92 -5
- package/src/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue +18 -4
- package/src/css/index-page.css +11 -0
- package/src/css/index.css +1 -0
- package/src/css/look-board-reports.css +117 -0
- package/src/css/look-board.css +32 -1
- package/src/schemas/app-manifest-v2.schema.json +2 -2
- package/src/utils/fetchAggregate.js +24 -1
- package/src/utils/switchValues.js +59 -0
- package/src/utils/validateManifestV2.compiled.js +3 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnSettingsFormWidget.vue3.js","sources":["../../../../src/components/CnSettingsFormWidget/CnSettingsFormWidget.vue?vue&type=template&id=66086856&lang.js"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<template>\n\t<form class=\"cn-settings-form\"\n\t\tdata-testid=\"cn-settings-form\"\n\t\tnovalidate\n\t\t@submit.prevent=\"save\">\n\t\t<div class=\"cn-settings-form__grid\" :class=\"`cn-settings-form__grid--${columns}`\">\n\t\t\t<div\n\t\t\t\tv-for=\"field in fields\"\n\t\t\t\t:key=\"field.key\"\n\t\t\t\tclass=\"cn-settings-form__field\"\n\t\t\t\t:class=\"{ 'cn-settings-form__field--wide': field.wide }\"\n\t\t\t\t:data-testid=\"`cn-settings-form-field-${field.key}`\">\n\t\t\t\t<NcCheckboxRadioSwitch\n\t\t\t\t\tv-if=\"field.type === 'switch'\"\n\t\t\t\t\tv-model=\"form[field.key]\"\n\t\t\t\t\ttype=\"switch\"\n\t\t\t\t\t:disabled=\"field.readOnly\">\n\t\t\t\t\t{{ field.label }}\n\t\t\t\t</NcCheckboxRadioSwitch>\n\t\t\t\t<CnChipsFieldWidget\n\t\t\t\t\tv-else-if=\"field.type === 'chips'\"\n\t\t\t\t\t:modelValue=\"form[field.key]\"\n\t\t\t\t\t:content=\"chipsContent(field)\"\n\t\t\t\t\t@update:modelValue=\"form[field.key] = $event\" />\n\t\t\t\t<template v-else>\n\t\t\t\t\t<label class=\"cn-settings-form__label\" :for=\"inputId(field)\">{{ field.label }}</label>\n\t\t\t\t\t<div v-if=\"field.type === 'file'\" class=\"cn-settings-form__file\" :data-testid=\"`cn-settings-form-file-${field.key}`\">\n\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t:id=\"inputId(field)\"\n\t\t\t\t\t\t\tclass=\"cn-settings-form__control cn-settings-form__control--file\"\n\t\t\t\t\t\t\t:class=\"{ 'cn-settings-form__control--placeholder': !form[field.key] }\">\n\t\t\t\t\t\t\t{{ form[field.key] ? fileName(form[field.key]) : field.placeholder }}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<NcButton\n\t\t\t\t\t\t\tv-if=\"!field.readOnly\"\n\t\t\t\t\t\t\tvariant=\"secondary\"\n\t\t\t\t\t\t\t:aria-describedby=\"inputId(field)\"\n\t\t\t\t\t\t\t:data-testid=\"`cn-settings-form-choose-${field.key}`\"\n\t\t\t\t\t\t\t@click=\"chooseFile(field)\">\n\t\t\t\t\t\t\t{{ field.chooseLabel }}\n\t\t\t\t\t\t</NcButton>\n\t\t\t\t\t\t<NcButton\n\t\t\t\t\t\t\tv-if=\"!field.readOnly && form[field.key]\"\n\t\t\t\t\t\t\tvariant=\"tertiary\"\n\t\t\t\t\t\t\t:data-testid=\"`cn-settings-form-clear-${field.key}`\"\n\t\t\t\t\t\t\t@click=\"form[field.key] = ''\">\n\t\t\t\t\t\t\t{{ field.clearLabel }}\n\t\t\t\t\t\t</NcButton>\n\t\t\t\t\t</div>\n\t\t\t\t\t<select\n\t\t\t\t\t\tv-else-if=\"field.type === 'reference'\"\n\t\t\t\t\t\t:id=\"inputId(field)\"\n\t\t\t\t\t\tv-model=\"form[field.key]\"\n\t\t\t\t\t\tclass=\"cn-settings-form__control\"\n\t\t\t\t\t\t:disabled=\"field.readOnly\"\n\t\t\t\t\t\t:aria-busy=\"referenceState[field.key] === 'loading' ? 'true' : null\"\n\t\t\t\t\t\t:data-testid=\"`cn-settings-form-reference-${field.key}`\">\n\t\t\t\t\t\t<option v-for=\"option in referenceChoices(field)\" :key=\"option.value\" :value=\"option.value\">\n\t\t\t\t\t\t\t{{ option.label }}\n\t\t\t\t\t\t</option>\n\t\t\t\t\t</select>\n\t\t\t\t\t<select\n\t\t\t\t\t\tv-else-if=\"field.type === 'select'\"\n\t\t\t\t\t\t:id=\"inputId(field)\"\n\t\t\t\t\t\tv-model=\"form[field.key]\"\n\t\t\t\t\t\tclass=\"cn-settings-form__control\"\n\t\t\t\t\t\t:disabled=\"field.readOnly\">\n\t\t\t\t\t\t<option v-for=\"option in field.options\" :key=\"String(option.value)\" :value=\"option.value\">\n\t\t\t\t\t\t\t{{ option.label }}\n\t\t\t\t\t\t</option>\n\t\t\t\t\t</select>\n\t\t\t\t\t<textarea\n\t\t\t\t\t\tv-else-if=\"field.type === 'textarea'\"\n\t\t\t\t\t\t:id=\"inputId(field)\"\n\t\t\t\t\t\tv-model=\"form[field.key]\"\n\t\t\t\t\t\tclass=\"cn-settings-form__control cn-settings-form__control--area\"\n\t\t\t\t\t\t:rows=\"field.rows\"\n\t\t\t\t\t\t:placeholder=\"field.placeholder\"\n\t\t\t\t\t\t:readonly=\"field.readOnly\" />\n\t\t\t\t\t<input\n\t\t\t\t\t\tv-else\n\t\t\t\t\t\t:id=\"inputId(field)\"\n\t\t\t\t\t\tv-model=\"form[field.key]\"\n\t\t\t\t\t\tclass=\"cn-settings-form__control\"\n\t\t\t\t\t\t:type=\"field.type === 'number' ? 'number' : 'text'\"\n\t\t\t\t\t\t:min=\"field.min\"\n\t\t\t\t\t\t:max=\"field.max\"\n\t\t\t\t\t\t:placeholder=\"field.placeholder\"\n\t\t\t\t\t\t:readonly=\"field.readOnly\"\n\t\t\t\t\t\t:aria-invalid=\"errors[field.key] ? 'true' : null\">\n\t\t\t\t</template>\n\t\t\t\t<p v-if=\"errors[field.key]\" class=\"cn-settings-form__error\" role=\"alert\">\n\t\t\t\t\t{{ errors[field.key] }}\n\t\t\t\t</p>\n\t\t\t\t<p v-else-if=\"field.hint\" class=\"cn-settings-form__hint\">\n\t\t\t\t\t{{ field.hint }}\n\t\t\t\t</p>\n\t\t\t</div>\n\t\t</div>\n\n\t\t<NcNoteCard v-if=\"notice\" :type=\"noticeType\" data-testid=\"cn-settings-form-notice\">\n\t\t\t{{ notice }}\n\t\t</NcNoteCard>\n\n\t\t<div v-if=\"!readOnly\" class=\"cn-settings-form__actions\">\n\t\t\t<NcButton\n\t\t\t\tvariant=\"primary\"\n\t\t\t\ttype=\"submit\"\n\t\t\t\t:disabled=\"saving\"\n\t\t\t\tdata-testid=\"cn-settings-form-save\">\n\t\t\t\t{{ saveLabel }}\n\t\t\t</NcButton>\n\t\t</div>\n\t</form>\n</template>\n\n<script>\nimport { FilePickerClosed, getFilePickerBuilder } from '@nextcloud/dialogs'\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcButton, NcCheckboxRadioSwitch, NcNoteCard } from '@nextcloud/vue'\nimport CnChipsFieldWidget from '../CnChipsFieldWidget/CnChipsFieldWidget.vue'\nimport { pickRefLabel } from '../../composables/useRefLabels.js'\nimport { fillTemplate } from '../../utils/fillTemplate.js'\nimport { prefixUrl } from '../../utils/headers.js'\nimport { changedFields, objectUrlOf, writeFields } from '../../utils/objectWrite.js'\nimport { readPath } from '../../utils/readPath.js'\nimport { serverErrorMessage } from '../../utils/serverErrorMessage.js'\n\n// The native file-picker modal ships its chrome styles here; without this the\n// spawned picker renders unstyled. Rules are scoped to the picker/dialog classes.\nimport '@nextcloud/dialogs/style.css'\n\nconst TYPES = ['text', 'textarea', 'select', 'switch', 'number', 'file', 'chips', 'reference']\n\nlet uid = 0\n\n/**\n * CnSettingsFormWidget — the `settings-form` widget: fields of the CURRENT\n * object as a one- or two-column grid of framed inputs with light labels and\n * hints, saved by ONE PATCH of the fields that changed since the last save.\n *\n * \"Last save\" is the baseline this widget keeps: a save moves it forward, so\n * editing a field back to the value the page loaded with, after a save, still\n * counts as a change (comparing against the loaded object skipped that PATCH).\n *\n * @spec openspec/changes/abstract-settings-form/specs/settings-form/spec.md\n */\nexport default {\n\tname: 'CnSettingsFormWidget',\n\n\tcomponents: { CnChipsFieldWidget, NcButton, NcCheckboxRadioSwitch, NcNoteCard },\n\n\tinject: {\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The widget configuration.\n\t\t *\n\t\t * - `fields` (required) — `[{ key, label, type?: 'text'|'textarea'|'select'|'switch'|'number'|'file'|'chips'|'reference', register?, schema?, labelField?, valueTemplate?, filter?, limit?, emptyLabel?, default?, minItems?, required?, minItemsMessage?, lowercase?, addLabel?, confirmLabel?, removeLabel?, picker?: { title?, mimeTypes?, startAt?, valueTemplate? }, chooseLabel?, clearLabel?, hint?, placeholder?, options?: [{ value, label }], required?, pattern?, patternMessage?, min?, max?, rows?, wide?, readOnly? }]`; `key` is a field of the object, a top-level name or a dot-path.\n\t\t * - `save` — `{ url, method?, payload? }`, write through an app endpoint (tokens `{value}`, `{field}`, `{changes}`, `{object.<path>}`).\n\t\t * - `columns` — 1 or 2 (default 2).\n\t\t * - `saveLabel`, `savedText`, `readOnly`.\n\t\t *\n\t\t * @type {object}\n\t\t */\n\t\tcontent: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/** The current detail-page object. */\n\t\tobjectData: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** The current object id. */\n\t\tobjectId: {\n\t\t\ttype: [String, Number],\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** OpenRegister register slug of the surface. */\n\t\tregister: {\n\t\t\ttype: [String, Object],\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** OpenRegister schema slug of the surface. */\n\t\tschema: {\n\t\t\ttype: [String, Object],\n\t\t\tdefault: '',\n\t\t},\n\t},\n\n\temits: ['saved'],\n\n\tdata() {\n\t\tuid += 1\n\t\treturn {\n\t\t\tuid,\n\t\t\tform: {},\n\t\t\tbaseline: {},\n\t\t\tloaded: {},\n\t\t\t// `reference` fields: the objects to choose from and the read's state (`loading`, `ready`, `error`), by field key.\n\t\t\treferenceOptions: {},\n\t\t\treferenceState: {},\n\t\t\terrors: {},\n\t\t\tsaving: false,\n\t\t\tnotice: '',\n\t\t\tnoticeType: 'success',\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The configured fields, normalised and translated.\n\t\t *\n\t\t * @return {Array<object>}\n\t\t */\n\t\tfields() {\n\t\t\tconst list = Array.isArray(this.content.fields) ? this.content.fields : []\n\t\t\treturn list\n\t\t\t\t.filter((field) => field && field.key)\n\t\t\t\t.map((field) => ({\n\t\t\t\t\t...field,\n\t\t\t\t\ttype: TYPES.includes(field.type) ? field.type : 'text',\n\t\t\t\t\tlabel: this.tr(field.label || field.key),\n\t\t\t\t\thint: this.tr(field.hint || ''),\n\t\t\t\t\tplaceholder: this.tr(field.placeholder || ''),\n\t\t\t\t\tchooseLabel: this.tr(field.chooseLabel || 'Choose a file'),\n\t\t\t\t\tclearLabel: this.tr(field.clearLabel || 'Remove'),\n\t\t\t\t\trows: field.rows || 3,\n\t\t\t\t\twide: field.wide === true || field.type === 'textarea',\n\t\t\t\t\treadOnly: this.readOnly || field.readOnly === true,\n\t\t\t\t\toptions: (Array.isArray(field.options) ? field.options : []).map((option) => (\n\t\t\t\t\t\ttypeof option === 'object' && option !== null\n\t\t\t\t\t\t\t? { value: option.value, label: this.tr(String(option.label ?? option.value)) }\n\t\t\t\t\t\t\t: { value: option, label: this.tr(String(option)) }\n\t\t\t\t\t)),\n\t\t\t\t}))\n\t\t},\n\n\t\tcolumns() {\n\t\t\treturn this.content.columns === 1 ? 1 : 2\n\t\t},\n\n\t\treadOnly() {\n\t\t\treturn this.content.readOnly === true\n\t\t},\n\n\t\tsaveLabel() {\n\t\t\treturn this.tr(this.content.saveLabel || 'Save')\n\t\t},\n\n\t\t/** The object URL the form is saved to, or '' when it is not known. */\n\t\tobjectUrl() {\n\t\t\treturn objectUrlOf(this.register, this.schema, this.objectId || (this.objectData && this.objectData.id))\n\t\t},\n\t},\n\n\twatch: {\n\t\tobjectData() {\n\t\t\tthis.adopt()\n\t\t},\n\t},\n\n\tcreated() {\n\t\tthis.adopt(true)\n\t\tthis.loadReferences()\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Read the objects each `reference` field chooses from: one list read\n\t\t * per field of its `register` and `schema` (`filter` as query\n\t\t * parameters, at most `limit`, 100 by default). A failed read leaves the\n\t\t * field with its stored value only, and says so in the hint's place.\n\t\t *\n\t\t * @spec openspec/changes/screens-settings-form-reference/specs/settings-form/spec.md#requirement-a-settings-form-field-can-pick-an-object\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync loadReferences() {\n\t\t\tconst fields = this.fields.filter((field) => field.type === 'reference' && typeof field.register === 'string' && field.register !== '' && typeof field.schema === 'string' && field.schema !== '')\n\t\t\tif (fields.length === 0) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst { default: axios } = await import('@nextcloud/axios')\n\t\t\tawait Promise.all(fields.map(async (field) => {\n\t\t\t\tthis.referenceState = { ...this.referenceState, [field.key]: 'loading' }\n\t\t\t\ttry {\n\t\t\t\t\tconst url = `/apps/openregister/api/objects/${encodeURIComponent(field.register)}/${encodeURIComponent(field.schema)}`\n\t\t\t\t\tconst params = { ...(field.filter && typeof field.filter === 'object' ? field.filter : {}), _limit: Number(field.limit) > 0 ? Number(field.limit) : 100 }\n\t\t\t\t\tconst { data } = await axios.get(prefixUrl(url), { params })\n\t\t\t\t\tconst rows = Array.isArray(data) ? data : (Array.isArray(data?.results) ? data.results : [])\n\t\t\t\t\tconst options = []\n\t\t\t\t\tfor (const row of rows) {\n\t\t\t\t\t\tconst id = row && (row.id ?? row.uuid ?? (row['@self'] && row['@self'].id))\n\t\t\t\t\t\tif (id === undefined || id === null || id === '') {\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst scope = { ...row, id, register: field.register, schema: field.schema }\n\t\t\t\t\t\tconst value = fillTemplate(typeof field.valueTemplate === 'string' && field.valueTemplate !== '' ? field.valueTemplate : '{id}', scope)\n\t\t\t\t\t\tif (value !== '') {\n\t\t\t\t\t\t\toptions.push({ value, label: pickRefLabel(row, field.labelField) || String(id) })\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tthis.referenceOptions = { ...this.referenceOptions, [field.key]: options }\n\t\t\t\t\tthis.referenceState = { ...this.referenceState, [field.key]: 'ready' }\n\t\t\t\t} catch {\n\t\t\t\t\tthis.referenceOptions = { ...this.referenceOptions, [field.key]: [] }\n\t\t\t\t\tthis.referenceState = { ...this.referenceState, [field.key]: 'error' }\n\t\t\t\t\tthis.errors = { ...this.errors, [field.key]: this.tr('The list to choose from could not be loaded.') }\n\t\t\t\t}\n\t\t\t}))\n\t\t},\n\n\t\t/**\n\t\t * The choices of a `reference` field: \"none\" (unless required), the\n\t\t * objects read, and the value the form holds when it is not one of\n\t\t * them (a free string such as a URL stays selectable, as it is stored).\n\t\t *\n\t\t * @param {object} field The field.\n\t\t * @return {Array<{value: string, label: string}>}\n\t\t */\n\t\treferenceChoices(field) {\n\t\t\tconst options = this.referenceOptions[field.key] || []\n\t\t\tconst current = this.form[field.key]\n\t\t\tconst choices = field.required ? [] : [{ value: '', label: this.tr(field.emptyLabel || 'None') }]\n\t\t\tif (typeof current === 'string' && current !== '' && !options.some((option) => option.value === current)) {\n\t\t\t\tchoices.push({ value: current, label: current })\n\t\t\t}\n\t\t\treturn [...choices, ...options]\n\t\t},\n\n\t\ttr(text) {\n\t\t\tif (typeof text !== 'string' || text === '') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn typeof this.cnTranslate === 'function' ? this.cnTranslate(text) : text\n\t\t},\n\n\t\tinputId(field) {\n\t\t\treturn `cn-settings-form-${this.uid}-${field.key}`\n\t\t},\n\n\t\t/**\n\t\t * The name shown for a stored file value: its last path segment.\n\t\t *\n\t\t * @param {string} value The stored value.\n\t\t * @return {string}\n\t\t */\n\t\tfileName(value) {\n\t\t\tconst text = String(value)\n\t\t\treturn text.split('/').filter(Boolean).pop() || text\n\t\t},\n\n\t\t/**\n\t\t * Open the Nextcloud file picker and take the chosen file as the value.\n\t\t * `field.picker` = `{ title?, mimeTypes?, startAt?, valueTemplate? }`; the\n\t\t * template's `{path}`, `{fileid}` and `{name}` are filled from the chosen\n\t\t * node (default `{path}`).\n\t\t *\n\t\t * @param {object} field The field.\n\t\t * @return {Promise<void>}\n\t\t * @spec openspec/changes/abstract-settings-form-file/specs/settings-form/spec.md#requirement-a-file-field-takes-a-file-from-the-nextcloud-picker\n\t\t */\n\t\tasync chooseFile(field) {\n\t\t\tconst picker = field.picker || {}\n\t\t\tlet builder = getFilePickerBuilder(this.tr(picker.title || field.label))\n\t\t\t\t.setMultiSelect(false)\n\t\t\t\t.allowDirectories(false)\n\t\t\t\t.startAt(picker.startAt || '/')\n\t\t\t\t.addButton({ label: t('nextcloud-vue', 'Choose'), type: 'primary', callback: () => {} })\n\t\t\tif (Array.isArray(picker.mimeTypes) && picker.mimeTypes.length > 0) {\n\t\t\t\tbuilder = builder.setMimeTypeFilter(picker.mimeTypes)\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst nodes = await builder.build().pickNodes()\n\t\t\t\tconst node = Array.isArray(nodes) ? nodes[0] : nodes\n\t\t\t\tif (!node) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tthis.form[field.key] = String(picker.valueTemplate || '{path}').replace(/\\{(path|fileid|name)\\}/g, (match, part) => {\n\t\t\t\t\tconst value = part === 'name' ? (node.basename || this.fileName(node.path)) : node[part]\n\t\t\t\t\treturn value === undefined || value === null ? '' : String(value)\n\t\t\t\t})\n\t\t\t} catch (e) {\n\t\t\t\tif (e instanceof FilePickerClosed) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tthis.noticeType = 'error'\n\t\t\t\tthis.notice = this.tr('The file picker could not be opened.')\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * The stored value of a field as the input holds it.\n\t\t *\n\t\t * @param {object} field The field.\n\t\t * @return {unknown}\n\t\t */\n\t\tstored(field) {\n\t\t\tconst value = readPath(this.objectData || {}, field.key)\n\t\t\tif (field.type === 'switch') {\n\t\t\t\treturn value === true\n\t\t\t}\n\t\t\tif (field.type === 'chips') {\n\t\t\t\treturn Array.isArray(value) ? value.map((item) => String(item)) : []\n\t\t\t}\n\t\t\treturn value === undefined || value === null ? '' : value\n\t\t},\n\n\t\t/**\n\t\t * The value the input shows for a field: the stored one, else the field's\n\t\t * `default` when nothing is stored. The baseline stays the stored value, so\n\t\t * a default that was never saved counts as a change and the next Save writes it.\n\t\t *\n\t\t * @param {object} field The field.\n\t\t * @return {unknown}\n\t\t */\n\t\tshown(field) {\n\t\t\tconst value = this.stored(field)\n\t\t\tconst absent = readPath(this.objectData || {}, field.key) === undefined || readPath(this.objectData || {}, field.key) === null || value === '' || (field.type === 'chips' && value.length === 0)\n\t\t\tif (absent && field.default !== undefined) {\n\t\t\t\treturn field.type === 'chips' && Array.isArray(field.default) ? [...field.default] : field.default\n\t\t\t}\n\t\t\treturn value\n\t\t},\n\n\t\t/**\n\t\t * Take the object's values as the new baseline; a field the person has\n\t\t * not touched follows it, a touched one keeps the typed value.\n\t\t *\n\t\t * @param {boolean} [first] Whether this is the first read.\n\t\t */\n\t\tadopt(first = false) {\n\t\t\tconst baseline = {}\n\t\t\tconst loaded = {}\n\t\t\tconst form = {}\n\t\t\tfor (const field of this.fields) {\n\t\t\t\tbaseline[field.key] = this.stored(field)\n\t\t\t\tloaded[field.key] = this.shown(field)\n\t\t\t\tconst untouched = first || JSON.stringify(this.form[field.key]) === JSON.stringify(this.loaded[field.key])\n\t\t\t\tform[field.key] = untouched ? loaded[field.key] : this.form[field.key]\n\t\t\t}\n\t\t\tthis.baseline = baseline\n\t\t\tthis.loaded = loaded\n\t\t\tthis.form = form\n\t\t},\n\n\t\t/**\n\t\t * The configuration of the embedded chips widget for a `chips` field.\n\t\t *\n\t\t * @param {object} field The field.\n\t\t * @return {object}\n\t\t */\n\t\tchipsContent(field) {\n\t\t\treturn {\n\t\t\t\tlabel: field.label,\n\t\t\t\thint: field.hint,\n\t\t\t\tplaceholder: field.placeholder,\n\t\t\t\taddLabel: field.addLabel,\n\t\t\t\tconfirmLabel: field.confirmLabel,\n\t\t\t\tremoveLabel: field.removeLabel,\n\t\t\t\tpattern: field.pattern,\n\t\t\t\tpatternMessage: field.patternMessage,\n\t\t\t\tlowercase: field.lowercase,\n\t\t\t\toptions: field.options,\n\t\t\t\tminItems: field.minItems,\n\t\t\t\trequired: field.required,\n\t\t\t\tminItemsMessage: field.minItemsMessage,\n\t\t\t\treadOnly: field.readOnly,\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * The value to send for a field: numbers as numbers, an empty value as null.\n\t\t *\n\t\t * @param {object} field The field.\n\t\t * @return {unknown}\n\t\t */\n\t\toutgoing(field) {\n\t\t\tconst value = this.form[field.key]\n\t\t\tif (field.type === 'number') {\n\t\t\t\treturn value === '' || value === null || value === undefined ? null : Number(value)\n\t\t\t}\n\t\t\tif (field.type === 'switch') {\n\t\t\t\treturn value === true\n\t\t\t}\n\t\t\tif (field.type === 'chips') {\n\t\t\t\treturn Array.isArray(value) ? [...value] : []\n\t\t\t}\n\t\t\tif (typeof value === 'string') {\n\t\t\t\treturn value.trim() === '' ? null : value.trim()\n\t\t\t}\n\t\t\treturn value\n\t\t},\n\n\t\t/**\n\t\t * Check required, number range and pattern before any write.\n\t\t *\n\t\t * @return {object} Error text by field key.\n\t\t */\n\t\tvalidate() {\n\t\t\tconst errors = {}\n\t\t\tfor (const field of this.fields) {\n\t\t\t\tif (field.type === 'switch' || field.readOnly) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tconst value = this.outgoing(field)\n\t\t\t\tif (field.type === 'chips') {\n\t\t\t\t\tconst least = Number(field.minItems) > 0 ? Number(field.minItems) : (field.required ? 1 : 0)\n\t\t\t\t\tif (value.length < least) {\n\t\t\t\t\t\terrors[field.key] = this.tr(field.minItemsMessage || 'Keep at least {count}.').replace('{count}', String(least))\n\t\t\t\t\t}\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tconst empty = value === null || value === undefined || value === ''\n\t\t\t\tif (empty) {\n\t\t\t\t\tif (field.required) {\n\t\t\t\t\t\terrors[field.key] = this.tr('Fill this in.')\n\t\t\t\t\t}\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif (field.type === 'number') {\n\t\t\t\t\tif (Number.isNaN(value) || (field.min !== undefined && value < field.min) || (field.max !== undefined && value > field.max)) {\n\t\t\t\t\t\terrors[field.key] = this.tr('This number is not valid.')\n\t\t\t\t\t}\n\t\t\t\t} else if (field.pattern && !new RegExp(field.pattern).test(String(value))) {\n\t\t\t\t\terrors[field.key] = this.tr(field.patternMessage || 'This value is not valid.')\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn errors\n\t\t},\n\n\t\t/**\n\t\t * Save: PATCH the fields that differ from the last saved values.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t * @spec openspec/changes/abstract-settings-form/specs/settings-form/spec.md#requirement-a-settings-form-saves-only-what-changed\n\t\t */\n\t\tasync save() {\n\t\t\tthis.errors = this.validate()\n\t\t\tthis.notice = ''\n\t\t\tif (Object.keys(this.errors).length > 0) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (!this.objectUrl && !(this.content.save && this.content.save.url)) {\n\t\t\t\tthis.noticeType = 'error'\n\t\t\t\tthis.notice = this.tr('This form cannot be saved here.')\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst next = {}\n\t\t\tconst compare = {}\n\t\t\tfor (const field of this.fields) {\n\t\t\t\tif (!field.readOnly) {\n\t\t\t\t\tnext[field.key] = this.outgoing(field)\n\t\t\t\t\tcompare[field.key] = this.baseline[field.key] === '' ? null : this.baseline[field.key]\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst patch = changedFields(compare, next)\n\t\t\tthis.saving = true\n\t\t\ttry {\n\t\t\t\tif (Object.keys(patch).length > 0) {\n\t\t\t\t\tconst data = await writeFields(this.objectUrl, patch, { save: this.content.save, object: this.objectData })\n\t\t\t\t\tconst baseline = { ...this.baseline }\n\t\t\t\t\tconst loaded = { ...this.loaded }\n\t\t\t\t\tconst form = { ...this.form }\n\t\t\t\t\tfor (const key of Object.keys(patch)) {\n\t\t\t\t\t\tconst field = this.fields.find((candidate) => candidate.key === key)\n\t\t\t\t\t\tconst answered = readPath(data, key)\n\t\t\t\t\t\tconst written = answered !== undefined ? answered : patch[key]\n\t\t\t\t\t\tbaseline[key] = field.type === 'switch' ? written === true : (written === null ? (field.type === 'chips' ? [] : '') : written)\n\t\t\t\t\t\tform[key] = baseline[key]\n\t\t\t\t\t\tloaded[key] = baseline[key]\n\t\t\t\t\t}\n\t\t\t\t\tthis.baseline = baseline\n\t\t\t\t\tthis.loaded = loaded\n\t\t\t\t\tthis.form = form\n\t\t\t\t}\n\t\t\t\tthis.noticeType = 'success'\n\t\t\t\tthis.notice = this.tr(this.content.savedText || 'Saved.')\n\t\t\t\t/**\n\t\t\t\t * @event saved The changed fields were written.\n\t\t\t\t * @type {{patch: object}}\n\t\t\t\t */\n\t\t\t\tthis.$emit('saved', { patch })\n\t\t\t} catch (e) {\n\t\t\t\tthis.noticeType = 'error'\n\t\t\t\tthis.notice = serverErrorMessage(e)\n\t\t\t} finally {\n\t\t\t\tthis.saving = false\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-settings-form {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 14px;\n\tmin-width: 0;\n}\n\n.cn-settings-form__grid {\n\tdisplay: grid;\n\tgap: 14px 16px;\n\tgrid-template-columns: minmax(0, 1fr);\n}\n\n.cn-settings-form__grid--2 {\n\tgrid-template-columns: repeat(2, minmax(0, 1fr));\n}\n\n.cn-settings-form__field {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n\tmin-width: 0;\n}\n\n.cn-settings-form__field--wide {\n\tgrid-column: 1 / -1;\n}\n\n.cn-settings-form__label {\n\tfont-size: 13px;\n\tfont-weight: 600;\n\tcolor: var(--color-main-text);\n}\n\n.cn-settings-form__control {\n\twidth: 100%;\n\tbox-sizing: border-box;\n\theight: var(--cn-board-control-height, 40px);\n\tpadding: 0 12px;\n\tborder: 1px solid var(--color-border-maxcontrast);\n\tborder-radius: var(--cn-board-control-radius, 8px);\n\tbackground: var(--color-main-background);\n\tcolor: var(--color-main-text);\n\tfont: inherit;\n}\n\n.cn-settings-form__file {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tgap: 8px;\n}\n\n.cn-settings-form__control--file {\n\tdisplay: inline-flex;\n\talign-items: center;\n\twidth: auto;\n\tmin-width: 120px;\n\tflex: 0 1 auto;\n\toverflow-wrap: anywhere;\n}\n\n.cn-settings-form__control--placeholder {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-settings-form__control--area {\n\theight: auto;\n\tpadding: 10px 12px;\n\tresize: vertical;\n}\n\n.cn-settings-form__control:focus-visible {\n\toutline: 2px solid var(--color-primary-element);\n\toutline-offset: 1px;\n}\n\n.cn-settings-form__hint,\n.cn-settings-form__error {\n\tmargin: 0;\n\tfont-size: 12px;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-settings-form__error {\n\tcolor: var(--color-error-text);\n}\n\n.cn-settings-form__actions {\n\tdisplay: flex;\n\tjustify-content: flex-start;\n}\n\n@media (max-width: 640px) {\n\t.cn-settings-form__grid--2 {\n\t\tgrid-template-columns: minmax(0, 1fr);\n\t}\n}\n</style>\n"],"names":["_createElementBlock","_normalizeClass","_Fragment","_renderList","_createBlock","_createTextVNode","_toDisplayString","_createElementVNode","_openBlock"],"mappings":";;;;;;;;;;;;;;AAqGO,EAAA,KAAA,EAAA,yBAAA;AAAA,EAAA,IAAA,EAAA,OAAA;;;;;;;;AAaiB,EAAA,KAAK,EAAC,2BAAS;;;;;;;;;uBAvGpCA;AAAG,IAAA,KAAA,EAAA,kBAAA;AACF,IAAA,aAAS,EAAC,kBAAY;AACtB,IAAA,UAAQ,EAAR,EAAQ;AACR,IAAA,QAAK,wDAAU,QAAI,CAAA,IAAA,IAAA,QAAA,CAAA,IAAA,CAAA,GAAA,IAAA,CAAA,CAAA,EAAA,CAAA,SAAA,CAAA,CAAA,CAAA;;;MAChB,KAAK,EAAAC,cAAA,CAAA,CAAE,wBAAuB,EAAA,CAAA,wBAAA,EAAsB,QAAA,CAAA,OAAA,CAAA,CAAA,CAAA,CAAA;;0CA4FlD,CAAAC,QAAA,EAAA,IAAA,EAAAC,UAAA,CA1FW,QAAK,CAAA,MAAA,EAAA,CAAd,KAAK,KAAA;+CA0FP,CAAA,KAAA,EAAA;UAzFJ,GAAG,EAAE,KAAK,CAAC,GAAG;eACV,EAACF,cAAA,CAAA,CAAA,yBAAmB,EACZ,EAAA,+BAAA,EAAA,KAAA,CAAA,IAAA,EAAA,CAAA,CAAA;UACZ,aAAW,EAAA,CAAA,uBAAA,EAAe,KAAA,CAAA,GAAA,CAAA,CAAA;;AAEpB,UAAA,CAAA,KAAK,CAAC,IAAI,KAAA,QAAA;4BADhBG,WAMuB,CAAA,gCAAA,EAAA;;4BAJd,KAAI,CAAA,IAAA,CAAC,KAAG,CAAA,GAAA,CAAA;mDAAR,KAAI,CAAA,IAAA,CAAC,KAAG,CAAA,GAAA,CAAA,IAAA,MAAA,CAAA;AACjB,gBAAA,IAAI,EAAC,QAAQ;gBACZ,QAAQ,EAAE,KAAK,CAAC,QAAQ;;iCACzB,MAAiB;AAAd,kBAAAC,eAAA,CAAAC,eAAA,CAAA,KAAK,CAAC,KAAK,CAAA,EAAA,CAAA,YAAA;;;;AAGH,cAAA,CAAA,KAAK,CAAC,IAAI,KAAA,OAAA;8BADtBF,WAIsB,CAAA,6BAAA,EAAA;;AAFpB,kBAAA,UAAU,EAAE,KAAA,CAAA,IAAI,CAAC,KAAK,CAAC,GAAG,CAAA;kBAC1B,OAAI,EAAA,QAAA,CAAA,YAAA,CAAA,KAAA,CAAA;AACH,kBAAA,qBAAgB,aAAE,KAAC,CAAA,IAAA,CAAA,KAAA,CAAA,GAAA,IAAA,MAAA,CAAA;;8BACnBJ,kBAkEQ,CAAAE,QAAA,EAAA,EAAA,GAAA,EAAA,CAAA,EAAA,EAAA;kBAjERK,kBAAuE,CAAA,OAAA,EAAA;AAAlE,oBAAA,KAAK,EAAC,yBAAyB;oBAAE,GAAG,EAAE,QAAO,CAAA,OAAA,CAAE,KAAI,CAAA;AAAM,mBAAA,EAAAD,eAAA,CAAA,KAAK,CAAA,KAAI,CAAA,EAAA,CAAA,oBAAA,UAAA,CAAA;AAC9D,kBAAA,CAAA,KAAK,CAAC,IAAI,KAAA,MAAA;oCAAnBN,kBAsBI,CAAA,KAAA,EAAA;;AAtB4B,wBAAA,KAAK,EAAC,wBAAwB;wBAAE,aAAI,EAAA,CAAA,sBAAA,EAAA,KAAA,CAAA,GAAA,CAAA,CAAA;;wBACrEO,kBAKO,CAAA,MAAA,EAAA;0BAJL,EAAE,EAAE,QAAE,CAAA,OAAA,CAAA,KAAA,CAAA;AACP,0BAAA,KAAK,kBAAC,2DAAe,EAAA,EAAA,wCAAA,EAAA,CACH,UAAA,CAAA,KAAA,CAAA,GAAA,CAAA,EAAA,CAAA,CAAA;AACf,yBAAA,EAAAD,eAAA,CAAA,KAAA,CAAA,IAAI,CAAC,KAAK,CAAC,GAAG,IAAI,QAAQ,CAAA,QAAA,CAAC,KAAG,CAAA,IAAA,CAAA,KAAA,CAAA,GAAA,CAAA,CAAA,GAAA,KAAA,CAAA,WAAA,CAAA,EAAA,EAAA,2BAAA,UAAA,CAAA;AAG1B,wBAAA,CAAA,CAAA,KAAK,CAAC,QAAQ;qDAMX,CAAA,mBAAA,EAAA;;AALV,8BAAA,OAAO,EAAC,WAAC;8BACR,kBAAM,EAAA,QAAA,CAAA,OAAA,CAAA,KAAA,CAAA;8BACN,aAAW,EAAA,CAAA,wBAAA,EAA4B,KAAA,CAAA,GAAA,CAAA,CAAA;8BACvC,OAAK,EAAA,MAAA,KAAE,QAAS,CAAA,UAAA,CAAA,KAAA,CAAA,CAAA;;+CACjB,MAAuB;AAApB,gCAAAD,eAAA,CAAAC,eAAA,CAAA,KAAK,CAAC,WAAW,CAAA,EAAA,CAAA,YAAA;;;;;AAGb,wBAAA,CAAA,CAAA,KAAE,CAAA,QAAA,IAAA,UAAA,CAAA,KAAA,CAAA,GAAA,CAAA;0CADTF,WAMU,CAAA,mBAAA,EAAA;;AAJV,8BAAA,OAAG,EAAA,UAAA;8BACF,aAAG,EAAA,CAAA,uBAAA,EAAA,KAAA,CAAA,GAAA,CAAA,CAAA;AACH,8BAAA,OAAK,EAAE,MAAA,KAAA,KAAA,CAAA,IAAI,CAAC,KAAK,CAAC,GAAG,CAAA,GAAA,EAAA,CAAA;;+CACtB,MAAkB;AAAf,gCAAAC,eAAA,CAAAC,eAAA,CAAA,KAAK,CAAC,UAAS,CAAA,EAAA,CAAA,YAAA;;;;;;AAIR,sBAAA,CAAA,KAAK,CAAC,IAAI,KAAA,WAAA;uEAUb,CAAA,QAAA,EAAA;;0BATP,EAAE,EAAE,QAAO,CAAA,OAAA,CAAC,KAAK,CAAA;6DACT,KAAI,CAAA,IAAA,CAAC,KAAK,CAAC,GAAG,CAAA,IAAA,MAAA,CAAA;AACtB,0BAAA,KAAI,EAAC,2BAAY;0BACjB,QAAO,EAAA,KAAA,CAAA,QAAA;AACP,0BAAA,WAAM,EAAA,KAAA,CAAA,cAAA,CAAA,KAAA,CAAA,GAAA,CAAA,KAAA,SAAA,GAAA,MAAA,GAAA,IAAA;0BACN,aAAI,EAAA,CAAA,2BAAA,EAAA,KAAA,CAAA,GAAA,CAAA,CAAA;;AACL,2BAAAE,SAAA,CAAA,IAAA,CAAA,EAAAR,kBAAA,CAESE,QAFe,EAAA,IAAA,EAAAC,UAAA,CAAA,QAAA,CAAA,gBAAU,CAAA,KAAA,IAAnB,MAAO,KAAA;iDAAtBH,kBAES,CAAA,QAAA,EAAA;8BAFyB,GAAA,EAAA,MAAA,CAAA,KAAA;8BAAA,KAAA,EAAA,MAAA,CAAA,KAAA;AAC9B,6BAAA,EAAAM,eAAA,CAAA,MAAM,CAAC,KAAK,CAAA,EAAA,CAAA,oBAAA,UAAA,CAAA,CAAA;;;yCANP,KAAI,CAAA,IAAA,CAAC,KAAK,CAAC,GAAG,CAAA,CAAA;;AAUZ,wBAAA,CAAA,KAAK,CAAC,IAAG,KAAA,QAAA;yEAQZ,CAAA,QAAA,EAAA;;4BAPP,EAAG,EAAC,QAAO,CAAA,OAAA,CAAC,KAAK,CAAA;+DACV,KAAA,CAAA,IAAA,CAAA,KAAA,CAAA,GAAA,CAAA,IAAA,MAAA,CAAA;AACR,4BAAA,KAAK,EAAC,2BAAC;4BACN,QAAM,EAAA,KAAA,CAAA,QAAA;;AACP,6BAAAE,SAAA,CAAA,IAAA,CAAA,EAAAR,kBAAA,CAESE,gCAFqB,CAAC,OAAK,GAArB,MAAK,KAAA;mDAApBF,kBAES,CAAA,QAAA,EAAA;AAF2B,gCAAA,GAAA,EAAA,MAAA,CAAA,MAAA,CAAA,KAAA,CAAA;gCAAA,KAAA,EAAA,MAAA,CAAA,KAAA;AAChC,+BAAA,EAAAM,eAAA,CAAA,MAAM,CAAC,KAAK,CAAA,EAAA,CAAA,oBAAA,UAAA,CAAA,CAAA;;;2CAJR,KAAA,CAAA,IAAA,CAAA,KAAA,CAAA,GAAA,CAAA,CAAA;;AAQG,0BAAA,CAAA,KAAK,CAAC,IAAI,KAAA,UAAA;yDADrBN,kBAO6B,CAAA,UAAA,EAAA;;8BAL5B,EAAE,EAAE,QAAO,CAAA,OAAA,CAAC,KAAK,CAAA;iEACd,KAAA,CAAA,IAAA,CAAA,KAAA,CAAA,GAAA,CAAA,IAAA,MAAA,CAAA;AACJ,8BAAA,KAAK,EAAA,2DAAA;8BACJ,IAAI,EAAE,KAAK,CAAC,IAAI;8BAChB,WAAW,EAAE,KAAK,CAAC,WAAI;8BACvB,QAAQ,EAAE,KAAK,CAAC,QAAQ;;2CAJrB,KAAA,CAAA,IAAA,CAAA,KAAA,CAAA,GAAA,CAAA,CAAA;;yDAKJA,kBAUkD,CAAA,OAAA,EAAA;;8BARjD,EAAE,EAAE,QAAO,CAAA,OAAA,CAAC,KAAE,CAAA;iEACN,KAAI,CAAA,IAAA,CAAC,KAAK,CAAC,GAAG,CAAA,IAAA,MAAA,CAAA;AACvB,8BAAA,KAAK,EAAC,2BAAmB;8BACxB,IAAI,EAAE,KAAK,CAAC,IAAI,KAAA,QAAA,GAAA,QAAA,GAAA,MAAA;8BAChB,GAAG,EAAE,KAAE,CAAA,GAAA;8BACP,GAAG,EAAE,KAAK,CAAC,GAAG;yCACH,EAAE,MAAM,WAAC;8BACpB,QAAA,EAAA,KAAA,CAAA,QAAA;AACA,8BAAA,cAAY,EAAE,KAAA,CAAA,MAAM,CAAC,KAAK,CAAC,GAAG,CAAA,GAAA,MAAA,GAAA,IAAA;;8CAPtB,KAAI,CAAA,IAAA,CAAC,KAAK,CAAC,GAAG,CAAA,CAAA;;;WAStB,KAAA,CAAA,MAAA,CAAA,KAAA,CAAA,GAAA,CAAA;AAAH,eAAAQ,SAAA,EAAA,EAAAR,kBAAA,CAEG,KAFH,WAEG,EAAAM,eAAA,CADC,YAAA,CAAA,KAAA,CAAA,GAAA,CAAA,CAAA,EAAA,CAAA,YAAA;;iDAIA,gBAAA,EADAA,eAAA,CAAA,KAAI,CAAE,IAAI,CAAA,EAAA,CAAA,YAAA;;;;;KAKP,KAAA,CAAA,MAAA;sBAARF,WAEY,CAAA,qBAAA,EAAA;;AAFJ,UAAA,IAAA,EAAA,KAAA,CAAA,UAAA;AAAA,UAAA,aAAA,EAAA,yBAAA;;2BACP,MAAW;4CAAT,KAAM,CAAA,MAAA,CAAA,EAAA,CAAA,YAAA;;;;;MAGE,QAAQ,CAAA,QAAA;AAAlB,SAAAI,SAAA,EAAA,EAAAR,kBAAA,QAAA;;AAEA,YAAA,OAAO,EAAC,SAAC;AACT,YAAA,IAAG,EAAA,QAAA;AACF,YAAA,QAAE,EAAA,KAAA,CAAA,MAAA;;;;;;;;;;;;;;;"}
|
|
1
|
+
{"version":3,"file":"CnSettingsFormWidget.vue3.js","sources":["../../../../src/components/CnSettingsFormWidget/CnSettingsFormWidget.vue?vue&type=template&id=7325a0ee&lang.js"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<template>\n\t<form class=\"cn-settings-form\"\n\t\tdata-testid=\"cn-settings-form\"\n\t\tnovalidate\n\t\t@submit.prevent=\"save\">\n\t\t<div class=\"cn-settings-form__grid\" :class=\"`cn-settings-form__grid--${columns}`\">\n\t\t\t<div\n\t\t\t\tv-for=\"field in fields\"\n\t\t\t\t:key=\"field.key\"\n\t\t\t\tclass=\"cn-settings-form__field\"\n\t\t\t\t:class=\"{ 'cn-settings-form__field--wide': field.wide }\"\n\t\t\t\t:data-testid=\"`cn-settings-form-field-${field.key}`\">\n\t\t\t\t<NcCheckboxRadioSwitch\n\t\t\t\t\tv-if=\"field.type === 'switch'\"\n\t\t\t\t\tv-model=\"form[field.key]\"\n\t\t\t\t\ttype=\"switch\"\n\t\t\t\t\t:disabled=\"field.readOnly\">\n\t\t\t\t\t{{ field.label }}\n\t\t\t\t</NcCheckboxRadioSwitch>\n\t\t\t\t<CnChipsFieldWidget\n\t\t\t\t\tv-else-if=\"field.type === 'chips'\"\n\t\t\t\t\t:modelValue=\"form[field.key]\"\n\t\t\t\t\t:content=\"chipsContent(field)\"\n\t\t\t\t\t@update:modelValue=\"form[field.key] = $event\" />\n\t\t\t\t<template v-else>\n\t\t\t\t\t<label class=\"cn-settings-form__label\" :for=\"inputId(field)\">{{ field.label }}</label>\n\t\t\t\t\t<div v-if=\"field.type === 'file'\" class=\"cn-settings-form__file\" :data-testid=\"`cn-settings-form-file-${field.key}`\">\n\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t:id=\"inputId(field)\"\n\t\t\t\t\t\t\tclass=\"cn-settings-form__control cn-settings-form__control--file\"\n\t\t\t\t\t\t\t:class=\"{ 'cn-settings-form__control--placeholder': !form[field.key] }\">\n\t\t\t\t\t\t\t{{ form[field.key] ? fileName(form[field.key]) : field.placeholder }}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<NcButton\n\t\t\t\t\t\t\tv-if=\"!field.readOnly\"\n\t\t\t\t\t\t\tvariant=\"secondary\"\n\t\t\t\t\t\t\t:aria-describedby=\"inputId(field)\"\n\t\t\t\t\t\t\t:data-testid=\"`cn-settings-form-choose-${field.key}`\"\n\t\t\t\t\t\t\t@click=\"chooseFile(field)\">\n\t\t\t\t\t\t\t{{ field.chooseLabel }}\n\t\t\t\t\t\t</NcButton>\n\t\t\t\t\t\t<NcButton\n\t\t\t\t\t\t\tv-if=\"!field.readOnly && form[field.key]\"\n\t\t\t\t\t\t\tvariant=\"tertiary\"\n\t\t\t\t\t\t\t:data-testid=\"`cn-settings-form-clear-${field.key}`\"\n\t\t\t\t\t\t\t@click=\"form[field.key] = ''\">\n\t\t\t\t\t\t\t{{ field.clearLabel }}\n\t\t\t\t\t\t</NcButton>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div v-else-if=\"field.type === 'reference'\" class=\"cn-settings-form__reference\">\n\t\t\t\t\t\t<select\n\t\t\t\t\t\t\t:id=\"inputId(field)\"\n\t\t\t\t\t\t\tv-model=\"form[field.key]\"\n\t\t\t\t\t\t\tclass=\"cn-settings-form__control\"\n\t\t\t\t\t\t\t:disabled=\"field.readOnly\"\n\t\t\t\t\t\t\t:aria-busy=\"referenceState[field.key] === 'loading' ? 'true' : null\"\n\t\t\t\t\t\t\t:data-testid=\"`cn-settings-form-reference-${field.key}`\">\n\t\t\t\t\t\t\t<option v-for=\"option in referenceChoices(field)\" :key=\"option.value\" :value=\"option.value\">\n\t\t\t\t\t\t\t\t{{ option.label }}\n\t\t\t\t\t\t\t</option>\n\t\t\t\t\t\t</select>\n\t\t\t\t\t\t<!-- `free`: a typed value (a URL) besides picking an object. -->\n\t\t\t\t\t\t<template v-if=\"field.free && !field.readOnly\">\n\t\t\t\t\t\t\t<label class=\"cn-settings-form__sublabel\" :for=\"`${inputId(field)}-free`\">{{ field.freeLabel }}</label>\n\t\t\t\t\t\t\t<input\n\t\t\t\t\t\t\t\t:id=\"`${inputId(field)}-free`\"\n\t\t\t\t\t\t\t\tclass=\"cn-settings-form__control\"\n\t\t\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\t\t\tinputmode=\"url\"\n\t\t\t\t\t\t\t\t:value=\"freeValue(field)\"\n\t\t\t\t\t\t\t\t:placeholder=\"field.freePlaceholder\"\n\t\t\t\t\t\t\t\t:aria-invalid=\"errors[field.key] ? 'true' : null\"\n\t\t\t\t\t\t\t\t:data-testid=\"`cn-settings-form-reference-free-${field.key}`\"\n\t\t\t\t\t\t\t\t@input=\"form[field.key] = $event.target.value.trim()\">\n\t\t\t\t\t\t</template>\n\t\t\t\t\t</div>\n\t\t\t\t\t<select\n\t\t\t\t\t\tv-else-if=\"field.type === 'select'\"\n\t\t\t\t\t\t:id=\"inputId(field)\"\n\t\t\t\t\t\tv-model=\"form[field.key]\"\n\t\t\t\t\t\tclass=\"cn-settings-form__control\"\n\t\t\t\t\t\t:disabled=\"field.readOnly\">\n\t\t\t\t\t\t<option v-for=\"option in field.options\" :key=\"String(option.value)\" :value=\"option.value\">\n\t\t\t\t\t\t\t{{ option.label }}\n\t\t\t\t\t\t</option>\n\t\t\t\t\t</select>\n\t\t\t\t\t<textarea\n\t\t\t\t\t\tv-else-if=\"field.type === 'textarea'\"\n\t\t\t\t\t\t:id=\"inputId(field)\"\n\t\t\t\t\t\tv-model=\"form[field.key]\"\n\t\t\t\t\t\tclass=\"cn-settings-form__control cn-settings-form__control--area\"\n\t\t\t\t\t\t:rows=\"field.rows\"\n\t\t\t\t\t\t:placeholder=\"field.placeholder\"\n\t\t\t\t\t\t:readonly=\"field.readOnly\" />\n\t\t\t\t\t<input\n\t\t\t\t\t\tv-else\n\t\t\t\t\t\t:id=\"inputId(field)\"\n\t\t\t\t\t\tv-model=\"form[field.key]\"\n\t\t\t\t\t\tclass=\"cn-settings-form__control\"\n\t\t\t\t\t\t:type=\"field.type === 'number' ? 'number' : 'text'\"\n\t\t\t\t\t\t:min=\"field.min\"\n\t\t\t\t\t\t:max=\"field.max\"\n\t\t\t\t\t\t:placeholder=\"field.placeholder\"\n\t\t\t\t\t\t:readonly=\"field.readOnly\"\n\t\t\t\t\t\t:aria-invalid=\"errors[field.key] ? 'true' : null\">\n\t\t\t\t</template>\n\t\t\t\t<p v-if=\"errors[field.key]\" class=\"cn-settings-form__error\" role=\"alert\">\n\t\t\t\t\t{{ errors[field.key] }}\n\t\t\t\t</p>\n\t\t\t\t<!-- A chips field prints its own hint (it gets it in chipsContent). -->\n\t\t\t\t<p v-else-if=\"field.hint && field.type !== 'chips'\" class=\"cn-settings-form__hint\">\n\t\t\t\t\t{{ field.hint }}\n\t\t\t\t</p>\n\t\t\t</div>\n\t\t</div>\n\n\t\t<NcNoteCard v-if=\"notice\" :type=\"noticeType\" data-testid=\"cn-settings-form-notice\">\n\t\t\t{{ notice }}\n\t\t</NcNoteCard>\n\n\t\t<div v-if=\"!readOnly\" class=\"cn-settings-form__actions\">\n\t\t\t<NcButton\n\t\t\t\tvariant=\"primary\"\n\t\t\t\ttype=\"submit\"\n\t\t\t\t:disabled=\"saving\"\n\t\t\t\tdata-testid=\"cn-settings-form-save\">\n\t\t\t\t{{ saveLabel }}\n\t\t\t</NcButton>\n\t\t</div>\n\t</form>\n</template>\n\n<script>\nimport { FilePickerClosed, getFilePickerBuilder } from '@nextcloud/dialogs'\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcButton, NcCheckboxRadioSwitch, NcNoteCard } from '@nextcloud/vue'\nimport CnChipsFieldWidget from '../CnChipsFieldWidget/CnChipsFieldWidget.vue'\nimport { pickRefLabel } from '../../composables/useRefLabels.js'\nimport { fillTemplate } from '../../utils/fillTemplate.js'\nimport { prefixUrl } from '../../utils/headers.js'\nimport { changedFields, objectUrlOf, writeFields } from '../../utils/objectWrite.js'\nimport { readPath } from '../../utils/readPath.js'\nimport { hasUnresolvedTokens, resolveFilterTokens } from '../../utils/resolveFilterTokens.js'\nimport { serverErrorMessage } from '../../utils/serverErrorMessage.js'\nimport { switchIsOn, switchStoredValue } from '../../utils/switchValues.js'\n\n// The native file-picker modal ships its chrome styles here; without this the\n// spawned picker renders unstyled. Rules are scoped to the picker/dialog classes.\nimport '@nextcloud/dialogs/style.css'\n\nconst TYPES = ['text', 'textarea', 'select', 'switch', 'number', 'file', 'chips', 'reference']\n\nlet uid = 0\n\n/**\n * CnSettingsFormWidget — the `settings-form` widget: fields of the CURRENT\n * object as a one- or two-column grid of framed inputs with light labels and\n * hints, saved by ONE PATCH of the fields that changed since the last save.\n *\n * \"Last save\" is the baseline this widget keeps: a save moves it forward, so\n * editing a field back to the value the page loaded with, after a save, still\n * counts as a change (comparing against the loaded object skipped that PATCH).\n *\n * @spec openspec/changes/abstract-settings-form/specs/settings-form/spec.md\n */\nexport default {\n\tname: 'CnSettingsFormWidget',\n\n\tcomponents: { CnChipsFieldWidget, NcButton, NcCheckboxRadioSwitch, NcNoteCard },\n\n\tinject: {\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The widget configuration.\n\t\t *\n\t\t * - `fields` (required) — `[{ key, label, type?: 'text'|'textarea'|'select'|'switch'|'number'|'file'|'chips'|'reference', register?, schema?, labelField?, valueTemplate?, filter?, limit?, emptyLabel?, free?, freeLabel?, freePlaceholder?, freePattern?, freePatternMessage?, default?, minItems?, required?, minItemsMessage?, values?: { on, off }, lowercase?, addLabel?, confirmLabel?, removeLabel?, picker?: { title?, mimeTypes?, startAt?, valueTemplate? }, chooseLabel?, clearLabel?, hint?, placeholder?, options?: [{ value, label }], required?, pattern?, patternMessage?, min?, max?, rows?, wide?, readOnly? }]`; `key` is a field of the object, a top-level name or a dot-path. A `reference` field's `filter` values may be `@objectId` and `@object.<field>` (the record being edited). A `switch` field's `values: { on, off }` reads and writes those values instead of true/false.\n\t\t * - `save` — `{ url, method?, payload? }`, write through an app endpoint (tokens `{value}`, `{field}`, `{changes}`, `{object.<path>}`).\n\t\t * - `columns` — 1 or 2 (default 2).\n\t\t * - `saveLabel`, `savedText`, `readOnly`.\n\t\t *\n\t\t * @type {object}\n\t\t */\n\t\tcontent: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/** The current detail-page object. */\n\t\tobjectData: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** The current object id. */\n\t\tobjectId: {\n\t\t\ttype: [String, Number],\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** OpenRegister register slug of the surface. */\n\t\tregister: {\n\t\t\ttype: [String, Object],\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** OpenRegister schema slug of the surface. */\n\t\tschema: {\n\t\t\ttype: [String, Object],\n\t\t\tdefault: '',\n\t\t},\n\t},\n\n\temits: ['saved'],\n\n\tdata() {\n\t\tuid += 1\n\t\treturn {\n\t\t\tuid,\n\t\t\tform: {},\n\t\t\tbaseline: {},\n\t\t\tloaded: {},\n\t\t\t// `reference` fields: the objects to choose from and the read's state (`loading`, `ready`, `error`), by field key.\n\t\t\treferenceOptions: {},\n\t\t\treferenceState: {},\n\t\t\t// The query each `reference` field was last read with, by field key, so a\n\t\t\t// new object re-reads only the fields whose filter changes with it.\n\t\t\treferenceQuery: {},\n\t\t\terrors: {},\n\t\t\tsaving: false,\n\t\t\tnotice: '',\n\t\t\tnoticeType: 'success',\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The configured fields, normalised and translated.\n\t\t *\n\t\t * @return {Array<object>}\n\t\t */\n\t\tfields() {\n\t\t\tconst list = Array.isArray(this.content.fields) ? this.content.fields : []\n\t\t\treturn list\n\t\t\t\t.filter((field) => field && field.key)\n\t\t\t\t.map((field) => ({\n\t\t\t\t\t...field,\n\t\t\t\t\ttype: TYPES.includes(field.type) ? field.type : 'text',\n\t\t\t\t\tlabel: this.tr(field.label || field.key),\n\t\t\t\t\thint: this.tr(field.hint || ''),\n\t\t\t\t\tplaceholder: this.tr(field.placeholder || ''),\n\t\t\t\t\tchooseLabel: this.tr(field.chooseLabel || 'Choose a file'),\n\t\t\t\t\tfree: field.type === 'reference' && field.free === true,\n\t\t\t\t\tfreeLabel: this.tr(field.freeLabel || 'Or type a value'),\n\t\t\t\t\tfreePlaceholder: this.tr(field.freePlaceholder || ''),\n\t\t\t\t\tclearLabel: this.tr(field.clearLabel || 'Remove'),\n\t\t\t\t\trows: field.rows || 3,\n\t\t\t\t\twide: field.wide === true || field.type === 'textarea',\n\t\t\t\t\treadOnly: this.readOnly || field.readOnly === true,\n\t\t\t\t\toptions: (Array.isArray(field.options) ? field.options : []).map((option) => (\n\t\t\t\t\t\ttypeof option === 'object' && option !== null\n\t\t\t\t\t\t\t? { value: option.value, label: this.tr(String(option.label ?? option.value)) }\n\t\t\t\t\t\t\t: { value: option, label: this.tr(String(option)) }\n\t\t\t\t\t)),\n\t\t\t\t}))\n\t\t},\n\n\t\tcolumns() {\n\t\t\treturn this.content.columns === 1 ? 1 : 2\n\t\t},\n\n\t\treadOnly() {\n\t\t\treturn this.content.readOnly === true\n\t\t},\n\n\t\tsaveLabel() {\n\t\t\treturn this.tr(this.content.saveLabel || 'Save')\n\t\t},\n\n\t\t/** The object URL the form is saved to, or '' when it is not known. */\n\t\tobjectUrl() {\n\t\t\treturn objectUrlOf(this.register, this.schema, this.objectId || (this.objectData && this.objectData.id))\n\t\t},\n\t},\n\n\twatch: {\n\t\tobjectData() {\n\t\t\tthis.adopt()\n\t\t\tthis.loadReferences()\n\t\t},\n\t},\n\n\tcreated() {\n\t\tthis.adopt(true)\n\t\tthis.loadReferences()\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Read the objects each `reference` field chooses from: one list read\n\t\t * per field of its `register` and `schema` (`filter` as query\n\t\t * parameters, at most `limit`, 100 by default). A failed read leaves the\n\t\t * field with its stored value only, and says so in the hint's place.\n\t\t *\n\t\t * `filter` values may be `@objectId` and `@object.<field>`: the record\n\t\t * the form edits, so a list can be narrowed to it (the logos of this\n\t\t * portal). Until the record has that value the field is not read; it is\n\t\t * read again when the record changes the resolved query, never twice\n\t\t * for the same query.\n\t\t *\n\t\t * @spec openspec/changes/screens-settings-form-reference/specs/settings-form/spec.md#requirement-a-settings-form-field-can-pick-an-object\n\t\t * @spec openspec/changes/screens-settings-form-reference-object-filter/specs/settings-form/spec.md#requirement-a-reference-fields-filter-can-name-the-record-being-edited\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync loadReferences() {\n\t\t\tconst fields = this.fields.filter((field) => field.type === 'reference' && typeof field.register === 'string' && field.register !== '' && typeof field.schema === 'string' && field.schema !== '')\n\t\t\tif (fields.length === 0) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst ctx = { objectId: this.objectId || (this.objectData && this.objectData.id) || undefined, object: this.objectData || undefined }\n\t\t\tconst pending = []\n\t\t\tfor (const field of fields) {\n\t\t\t\tconst filter = resolveFilterTokens(field.filter && typeof field.filter === 'object' ? field.filter : {}, ctx)\n\t\t\t\tif (hasUnresolvedTokens(filter)) {\n\t\t\t\t\t// The record has no value for the token yet: nothing to read.\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tconst params = { ...filter, _limit: Number(field.limit) > 0 ? Number(field.limit) : 100 }\n\t\t\t\tconst query = JSON.stringify(params)\n\t\t\t\tif (this.referenceQuery[field.key] === query) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tthis.referenceQuery = { ...this.referenceQuery, [field.key]: query }\n\t\t\t\tpending.push({ field, params, query })\n\t\t\t}\n\t\t\tif (pending.length === 0) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst { default: axios } = await import('@nextcloud/axios')\n\t\t\tawait Promise.all(pending.map(async ({ field, params, query }) => {\n\t\t\t\tthis.referenceState = { ...this.referenceState, [field.key]: 'loading' }\n\t\t\t\ttry {\n\t\t\t\t\tconst url = `/apps/openregister/api/objects/${encodeURIComponent(field.register)}/${encodeURIComponent(field.schema)}`\n\t\t\t\t\tconst { data } = await axios.get(prefixUrl(url), { params })\n\t\t\t\t\tif (this.referenceQuery[field.key] !== query) {\n\t\t\t\t\t\t// A newer record asked for another list meanwhile.\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tconst rows = Array.isArray(data) ? data : (Array.isArray(data?.results) ? data.results : [])\n\t\t\t\t\tconst options = []\n\t\t\t\t\tfor (const row of rows) {\n\t\t\t\t\t\tconst id = row && (row.id ?? row.uuid ?? (row['@self'] && row['@self'].id))\n\t\t\t\t\t\tif (id === undefined || id === null || id === '') {\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst scope = { ...row, id, register: field.register, schema: field.schema }\n\t\t\t\t\t\tconst value = fillTemplate(typeof field.valueTemplate === 'string' && field.valueTemplate !== '' ? field.valueTemplate : '{id}', scope)\n\t\t\t\t\t\tif (value !== '') {\n\t\t\t\t\t\t\toptions.push({ value, label: pickRefLabel(row, field.labelField) || String(id) })\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tthis.referenceOptions = { ...this.referenceOptions, [field.key]: options }\n\t\t\t\t\tthis.referenceState = { ...this.referenceState, [field.key]: 'ready' }\n\t\t\t\t} catch {\n\t\t\t\t\tthis.referenceOptions = { ...this.referenceOptions, [field.key]: [] }\n\t\t\t\t\tthis.referenceState = { ...this.referenceState, [field.key]: 'error' }\n\t\t\t\t\tthis.errors = { ...this.errors, [field.key]: this.tr('The list to choose from could not be loaded.') }\n\t\t\t\t}\n\t\t\t}))\n\t\t},\n\n\t\t/**\n\t\t * The choices of a `reference` field: \"none\" (unless required), the\n\t\t * objects read, and the value the form holds when it is not one of\n\t\t * them (a free string such as a URL stays selectable, as it is stored).\n\t\t *\n\t\t * @param {object} field The field.\n\t\t * @return {Array<{value: string, label: string}>}\n\t\t */\n\t\treferenceChoices(field) {\n\t\t\tconst options = this.referenceOptions[field.key] || []\n\t\t\tconst current = this.form[field.key]\n\t\t\tconst choices = field.required ? [] : [{ value: '', label: this.tr(field.emptyLabel || 'None') }]\n\t\t\tif (typeof current === 'string' && current !== '' && !options.some((option) => option.value === current)) {\n\t\t\t\tchoices.push({ value: current, label: current })\n\t\t\t}\n\t\t\treturn [...choices, ...options]\n\t\t},\n\n\t\t/**\n\t\t * The typed value of a `reference` field with `free`: the value the form\n\t\t * holds when it is not one of the objects to pick, else empty (picking\n\t\t * an object clears the typed value).\n\t\t *\n\t\t * @spec openspec/changes/screens-settings-form-reference-free-value/specs/settings-form/spec.md#requirement-a-reference-field-can-also-take-a-typed-value\n\t\t * @param {object} field The field.\n\t\t * @return {string}\n\t\t */\n\t\tfreeValue(field) {\n\t\t\tconst current = this.form[field.key]\n\t\t\tif (typeof current !== 'string' || current === '') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst options = this.referenceOptions[field.key] || []\n\t\t\treturn options.some((option) => option.value === current) ? '' : current\n\t\t},\n\n\t\ttr(text) {\n\t\t\tif (typeof text !== 'string' || text === '') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn typeof this.cnTranslate === 'function' ? this.cnTranslate(text) : text\n\t\t},\n\n\t\tinputId(field) {\n\t\t\treturn `cn-settings-form-${this.uid}-${field.key}`\n\t\t},\n\n\t\t/**\n\t\t * The name shown for a stored file value: its last path segment.\n\t\t *\n\t\t * @param {string} value The stored value.\n\t\t * @return {string}\n\t\t */\n\t\tfileName(value) {\n\t\t\tconst text = String(value)\n\t\t\treturn text.split('/').filter(Boolean).pop() || text\n\t\t},\n\n\t\t/**\n\t\t * Open the Nextcloud file picker and take the chosen file as the value.\n\t\t * `field.picker` = `{ title?, mimeTypes?, startAt?, valueTemplate? }`; the\n\t\t * template's `{path}`, `{fileid}` and `{name}` are filled from the chosen\n\t\t * node (default `{path}`).\n\t\t *\n\t\t * @param {object} field The field.\n\t\t * @return {Promise<void>}\n\t\t * @spec openspec/changes/abstract-settings-form-file/specs/settings-form/spec.md#requirement-a-file-field-takes-a-file-from-the-nextcloud-picker\n\t\t */\n\t\tasync chooseFile(field) {\n\t\t\tconst picker = field.picker || {}\n\t\t\tlet builder = getFilePickerBuilder(this.tr(picker.title || field.label))\n\t\t\t\t.setMultiSelect(false)\n\t\t\t\t.allowDirectories(false)\n\t\t\t\t.startAt(picker.startAt || '/')\n\t\t\t\t.addButton({ label: t('nextcloud-vue', 'Choose'), type: 'primary', callback: () => {} })\n\t\t\tif (Array.isArray(picker.mimeTypes) && picker.mimeTypes.length > 0) {\n\t\t\t\tbuilder = builder.setMimeTypeFilter(picker.mimeTypes)\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst nodes = await builder.build().pickNodes()\n\t\t\t\tconst node = Array.isArray(nodes) ? nodes[0] : nodes\n\t\t\t\tif (!node) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tthis.form[field.key] = String(picker.valueTemplate || '{path}').replace(/\\{(path|fileid|name)\\}/g, (match, part) => {\n\t\t\t\t\tconst value = part === 'name' ? (node.basename || this.fileName(node.path)) : node[part]\n\t\t\t\t\treturn value === undefined || value === null ? '' : String(value)\n\t\t\t\t})\n\t\t\t} catch (e) {\n\t\t\t\tif (e instanceof FilePickerClosed) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tthis.noticeType = 'error'\n\t\t\t\tthis.notice = this.tr('The file picker could not be opened.')\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * The stored value of a field as the input holds it.\n\t\t *\n\t\t * @param {object} field The field.\n\t\t * @return {unknown}\n\t\t */\n\t\tstored(field) {\n\t\t\tconst value = readPath(this.objectData || {}, field.key)\n\t\t\tif (field.type === 'switch') {\n\t\t\t\treturn switchIsOn(value, field.values)\n\t\t\t}\n\t\t\tif (field.type === 'chips') {\n\t\t\t\treturn Array.isArray(value) ? value.map((item) => String(item)) : []\n\t\t\t}\n\t\t\treturn value === undefined || value === null ? '' : value\n\t\t},\n\n\t\t/**\n\t\t * The value the input shows for a field: the stored one, else the field's\n\t\t * `default` when nothing is stored. The baseline stays the stored value, so\n\t\t * a default that was never saved counts as a change and the next Save writes it.\n\t\t *\n\t\t * @param {object} field The field.\n\t\t * @return {unknown}\n\t\t */\n\t\tshown(field) {\n\t\t\tconst value = this.stored(field)\n\t\t\tconst absent = readPath(this.objectData || {}, field.key) === undefined || readPath(this.objectData || {}, field.key) === null || value === '' || (field.type === 'chips' && value.length === 0)\n\t\t\tif (absent && field.default !== undefined) {\n\t\t\t\treturn field.type === 'chips' && Array.isArray(field.default) ? [...field.default] : field.default\n\t\t\t}\n\t\t\treturn value\n\t\t},\n\n\t\t/**\n\t\t * Take the object's values as the new baseline; a field the person has\n\t\t * not touched follows it, a touched one keeps the typed value.\n\t\t *\n\t\t * @param {boolean} [first] Whether this is the first read.\n\t\t */\n\t\tadopt(first = false) {\n\t\t\tconst baseline = {}\n\t\t\tconst loaded = {}\n\t\t\tconst form = {}\n\t\t\tfor (const field of this.fields) {\n\t\t\t\tbaseline[field.key] = this.stored(field)\n\t\t\t\tloaded[field.key] = this.shown(field)\n\t\t\t\tconst untouched = first || JSON.stringify(this.form[field.key]) === JSON.stringify(this.loaded[field.key])\n\t\t\t\tform[field.key] = untouched ? loaded[field.key] : this.form[field.key]\n\t\t\t}\n\t\t\tthis.baseline = baseline\n\t\t\tthis.loaded = loaded\n\t\t\tthis.form = form\n\t\t},\n\n\t\t/**\n\t\t * The configuration of the embedded chips widget for a `chips` field.\n\t\t *\n\t\t * @param {object} field The field.\n\t\t * @return {object}\n\t\t */\n\t\tchipsContent(field) {\n\t\t\treturn {\n\t\t\t\tlabel: field.label,\n\t\t\t\thint: field.hint,\n\t\t\t\tplaceholder: field.placeholder,\n\t\t\t\taddLabel: field.addLabel,\n\t\t\t\tconfirmLabel: field.confirmLabel,\n\t\t\t\tremoveLabel: field.removeLabel,\n\t\t\t\tpattern: field.pattern,\n\t\t\t\tpatternMessage: field.patternMessage,\n\t\t\t\tlowercase: field.lowercase,\n\t\t\t\toptions: field.options,\n\t\t\t\tminItems: field.minItems,\n\t\t\t\trequired: field.required,\n\t\t\t\tminItemsMessage: field.minItemsMessage,\n\t\t\t\treadOnly: field.readOnly,\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * The value to send for a field: numbers as numbers, an empty value as null.\n\t\t *\n\t\t * @param {object} field The field.\n\t\t * @return {unknown}\n\t\t */\n\t\toutgoing(field) {\n\t\t\tconst value = this.form[field.key]\n\t\t\tif (field.type === 'number') {\n\t\t\t\treturn value === '' || value === null || value === undefined ? null : Number(value)\n\t\t\t}\n\t\t\tif (field.type === 'switch') {\n\t\t\t\treturn value === true\n\t\t\t}\n\t\t\tif (field.type === 'chips') {\n\t\t\t\treturn Array.isArray(value) ? [...value] : []\n\t\t\t}\n\t\t\tif (typeof value === 'string') {\n\t\t\t\treturn value.trim() === '' ? null : value.trim()\n\t\t\t}\n\t\t\treturn value\n\t\t},\n\n\t\t/**\n\t\t * Check required, number range and pattern before any write.\n\t\t *\n\t\t * @return {object} Error text by field key.\n\t\t */\n\t\tvalidate() {\n\t\t\tconst errors = {}\n\t\t\tfor (const field of this.fields) {\n\t\t\t\tif (field.type === 'switch' || field.readOnly) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tconst value = this.outgoing(field)\n\t\t\t\tif (field.type === 'chips') {\n\t\t\t\t\tconst least = Number(field.minItems) > 0 ? Number(field.minItems) : (field.required ? 1 : 0)\n\t\t\t\t\tif (value.length < least) {\n\t\t\t\t\t\terrors[field.key] = this.tr(field.minItemsMessage || 'Keep at least {count}.').replace('{count}', String(least))\n\t\t\t\t\t}\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tconst empty = value === null || value === undefined || value === ''\n\t\t\t\tif (empty) {\n\t\t\t\t\tif (field.required) {\n\t\t\t\t\t\terrors[field.key] = this.tr('Fill this in.')\n\t\t\t\t\t}\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif (field.type === 'number') {\n\t\t\t\t\tif (Number.isNaN(value) || (field.min !== undefined && value < field.min) || (field.max !== undefined && value > field.max)) {\n\t\t\t\t\t\terrors[field.key] = this.tr('This number is not valid.')\n\t\t\t\t\t}\n\t\t\t\t} else if (field.free && field.freePattern && this.freeValue(field) !== '' && !new RegExp(field.freePattern).test(String(value))) {\n\t\t\t\t\t// A typed value (not one of the objects) answers to `freePattern`.\n\t\t\t\t\terrors[field.key] = this.tr(field.freePatternMessage || 'This value is not valid.')\n\t\t\t\t} else if (field.pattern && !new RegExp(field.pattern).test(String(value))) {\n\t\t\t\t\terrors[field.key] = this.tr(field.patternMessage || 'This value is not valid.')\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn errors\n\t\t},\n\n\t\t/**\n\t\t * Save: PATCH the fields that differ from the last saved values.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t * @spec openspec/changes/abstract-settings-form/specs/settings-form/spec.md#requirement-a-settings-form-saves-only-what-changed\n\t\t */\n\t\tasync save() {\n\t\t\tthis.errors = this.validate()\n\t\t\tthis.notice = ''\n\t\t\tif (Object.keys(this.errors).length > 0) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (!this.objectUrl && !(this.content.save && this.content.save.url)) {\n\t\t\t\tthis.noticeType = 'error'\n\t\t\t\tthis.notice = this.tr('This form cannot be saved here.')\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst next = {}\n\t\t\tconst compare = {}\n\t\t\tfor (const field of this.fields) {\n\t\t\t\tif (!field.readOnly) {\n\t\t\t\t\tnext[field.key] = this.outgoing(field)\n\t\t\t\t\tcompare[field.key] = this.baseline[field.key] === '' ? null : this.baseline[field.key]\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst patch = changedFields(compare, next)\n\t\t\t// A switch with a value map (`values: { on, off }`) writes those values.\n\t\t\tfor (const field of this.fields) {\n\t\t\t\tif (field.type === 'switch' && Object.hasOwn(patch, field.key)) {\n\t\t\t\t\tpatch[field.key] = switchStoredValue(patch[field.key], field.values)\n\t\t\t\t}\n\t\t\t}\n\t\t\tthis.saving = true\n\t\t\ttry {\n\t\t\t\tif (Object.keys(patch).length > 0) {\n\t\t\t\t\tconst data = await writeFields(this.objectUrl, patch, { save: this.content.save, object: this.objectData })\n\t\t\t\t\tconst baseline = { ...this.baseline }\n\t\t\t\t\tconst loaded = { ...this.loaded }\n\t\t\t\t\tconst form = { ...this.form }\n\t\t\t\t\tfor (const key of Object.keys(patch)) {\n\t\t\t\t\t\tconst field = this.fields.find((candidate) => candidate.key === key)\n\t\t\t\t\t\tconst answered = readPath(data, key)\n\t\t\t\t\t\tconst written = answered !== undefined ? answered : patch[key]\n\t\t\t\t\t\tbaseline[key] = field.type === 'switch' ? switchIsOn(written, field.values) : (written === null ? (field.type === 'chips' ? [] : '') : written)\n\t\t\t\t\t\tform[key] = baseline[key]\n\t\t\t\t\t\tloaded[key] = baseline[key]\n\t\t\t\t\t}\n\t\t\t\t\tthis.baseline = baseline\n\t\t\t\t\tthis.loaded = loaded\n\t\t\t\t\tthis.form = form\n\t\t\t\t}\n\t\t\t\tthis.noticeType = 'success'\n\t\t\t\tthis.notice = this.tr(this.content.savedText || 'Saved.')\n\t\t\t\t/**\n\t\t\t\t * @event saved The changed fields were written.\n\t\t\t\t * @type {{patch: object}}\n\t\t\t\t */\n\t\t\t\tthis.$emit('saved', { patch })\n\t\t\t} catch (e) {\n\t\t\t\tthis.noticeType = 'error'\n\t\t\t\tthis.notice = serverErrorMessage(e)\n\t\t\t} finally {\n\t\t\t\tthis.saving = false\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-settings-form {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 14px;\n\tmin-width: 0;\n}\n\n.cn-settings-form__grid {\n\tdisplay: grid;\n\tgap: 14px 16px;\n\tgrid-template-columns: minmax(0, 1fr);\n}\n\n.cn-settings-form__grid--2 {\n\tgrid-template-columns: repeat(2, minmax(0, 1fr));\n}\n\n.cn-settings-form__field {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n\tmin-width: 0;\n}\n\n.cn-settings-form__field--wide {\n\tgrid-column: 1 / -1;\n}\n\n.cn-settings-form__label {\n\tfont-size: 13px;\n\tfont-weight: 600;\n\tcolor: var(--color-main-text);\n}\n\n.cn-settings-form__reference {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 6px;\n}\n\n.cn-settings-form__sublabel {\n\tfont-size: 12px;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-settings-form__control {\n\twidth: 100%;\n\tbox-sizing: border-box;\n\theight: var(--cn-board-control-height, 40px);\n\tpadding: 0 12px;\n\tborder: 1px solid var(--color-border-maxcontrast);\n\tborder-radius: var(--cn-board-control-radius, 8px);\n\tbackground: var(--color-main-background);\n\tcolor: var(--color-main-text);\n\tfont: inherit;\n}\n\n.cn-settings-form__file {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tgap: 8px;\n}\n\n.cn-settings-form__control--file {\n\tdisplay: inline-flex;\n\talign-items: center;\n\twidth: auto;\n\tmin-width: 120px;\n\tflex: 0 1 auto;\n\toverflow-wrap: anywhere;\n}\n\n.cn-settings-form__control--placeholder {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-settings-form__control--area {\n\theight: auto;\n\tpadding: 10px 12px;\n\tresize: vertical;\n}\n\n.cn-settings-form__control:focus-visible {\n\toutline: 2px solid var(--color-primary-element);\n\toutline-offset: 1px;\n}\n\n.cn-settings-form__hint,\n.cn-settings-form__error {\n\tmargin: 0;\n\tfont-size: 12px;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-settings-form__error {\n\tcolor: var(--color-error-text);\n}\n\n.cn-settings-form__actions {\n\tdisplay: flex;\n\tjustify-content: flex-start;\n}\n\n@media (max-width: 640px) {\n\t.cn-settings-form__grid--2 {\n\t\tgrid-template-columns: minmax(0, 1fr);\n\t}\n}\n</style>\n"],"names":["_createElementBlock","_normalizeClass","_Fragment","_renderList","_createBlock","_createTextVNode","_toDisplayString","_createElementVNode","_openBlock"],"mappings":"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"}
|
|
@@ -2,7 +2,7 @@ import script from './CnStatWidget.vue2.js';
|
|
|
2
2
|
import { render } from './CnStatWidget.vue3.js';
|
|
3
3
|
|
|
4
4
|
script.render = render;
|
|
5
|
-
script.__scopeId = "data-v-
|
|
5
|
+
script.__scopeId = "data-v-644c38a1";
|
|
6
6
|
const __sfc_main = script;
|
|
7
7
|
|
|
8
8
|
export { __sfc_main as default };
|
|
@@ -12,6 +12,7 @@ import widgetLink from '../../mixins/widgetLink.js';
|
|
|
12
12
|
import { useObjectStore } from '../../store/useObjectStore.js';
|
|
13
13
|
import { resolveObjectOpType } from '../../utils/actionsDispatcher.js';
|
|
14
14
|
import { resolveObjectTokenContext } from '../../utils/detailObjectContext.js';
|
|
15
|
+
import { aggFilterParam } from '../../utils/fetchAggregate.js';
|
|
15
16
|
import { needsListEndpoint, fetchListTotal } from '../../utils/fetchFilterCounts.js';
|
|
16
17
|
import { unwrapAppConfig, formatMetricValue } from '../../utils/formatMetric.js';
|
|
17
18
|
import { dropOptionalUnresolved, resolveFilterTokens } from '../../utils/resolveFilterTokens.js';
|
|
@@ -1623,7 +1624,8 @@ var script = {
|
|
|
1623
1624
|
target[`filter[${k}][${op}]`] = ov;
|
|
1624
1625
|
}
|
|
1625
1626
|
} else if (v !== '' && v !== null && v !== undefined) {
|
|
1626
|
-
|
|
1627
|
+
// `field[gte]` goes out as `filter[field][gte]`, not `filter[field[gte]]`.
|
|
1628
|
+
target[aggFilterParam(k)] = v;
|
|
1627
1629
|
}
|
|
1628
1630
|
}
|
|
1629
1631
|
},
|