@conduction/nextcloud-vue 2.56.0-beta.52 → 2.56.0-beta.53

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.
Files changed (84) hide show
  1. package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue2.js +12 -0
  2. package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue2.js.map +1 -1
  3. package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue3.js +11 -2
  4. package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue3.js.map +1 -1
  5. package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue.js +1 -1
  6. package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue2.js.map +1 -1
  7. package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue3.js +1 -1
  8. package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue3.js.map +1 -1
  9. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  10. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +2 -1
  11. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  12. package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue.js +1 -1
  13. package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue2.js +26 -7
  14. package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue2.js.map +1 -1
  15. package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue3.js +78 -17
  16. package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue3.js.map +1 -1
  17. package/dist/esm/components/CnSettingsField/CnSettingsField.vue.js +9 -0
  18. package/dist/esm/components/CnSettingsField/CnSettingsField.vue.js.map +1 -0
  19. package/dist/esm/components/CnSettingsField/CnSettingsField.vue2.js +265 -0
  20. package/dist/esm/components/CnSettingsField/CnSettingsField.vue2.js.map +1 -0
  21. package/dist/esm/components/CnSettingsField/CnSettingsField.vue3.js +191 -0
  22. package/dist/esm/components/CnSettingsField/CnSettingsField.vue3.js.map +1 -0
  23. package/dist/esm/components/CnSettingsFieldsSection/CnSettingsFieldsSection.vue.js +9 -0
  24. package/dist/esm/components/CnSettingsFieldsSection/CnSettingsFieldsSection.vue.js.map +1 -0
  25. package/dist/esm/components/CnSettingsFieldsSection/CnSettingsFieldsSection.vue2.js +508 -0
  26. package/dist/esm/components/CnSettingsFieldsSection/CnSettingsFieldsSection.vue2.js.map +1 -0
  27. package/dist/esm/components/CnSettingsFieldsSection/CnSettingsFieldsSection.vue3.js +99 -0
  28. package/dist/esm/components/CnSettingsFieldsSection/CnSettingsFieldsSection.vue3.js.map +1 -0
  29. package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue.js +1 -1
  30. package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue2.js +18 -70
  31. package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue2.js.map +1 -1
  32. package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue3.js +6 -44
  33. package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue3.js.map +1 -1
  34. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +1 -0
  35. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
  36. package/dist/esm/components/CnWorkingCalendarWidget/CnWorkingCalendarWidget.vue.js +9 -0
  37. package/dist/esm/components/CnWorkingCalendarWidget/CnWorkingCalendarWidget.vue.js.map +1 -0
  38. package/dist/esm/components/CnWorkingCalendarWidget/CnWorkingCalendarWidget.vue2.js +472 -0
  39. package/dist/esm/components/CnWorkingCalendarWidget/CnWorkingCalendarWidget.vue2.js.map +1 -0
  40. package/dist/esm/components/CnWorkingCalendarWidget/CnWorkingCalendarWidget.vue3.js +231 -0
  41. package/dist/esm/components/CnWorkingCalendarWidget/CnWorkingCalendarWidget.vue3.js.map +1 -0
  42. package/dist/esm/components/CnWorkingCalendarWidget/index.js +27 -0
  43. package/dist/esm/components/CnWorkingCalendarWidget/index.js.map +1 -0
  44. package/dist/esm/composables/useFlowStore.js +2 -1
  45. package/dist/esm/composables/useFlowStore.js.map +1 -1
  46. package/dist/esm/index.js +3 -0
  47. package/dist/esm/index.js.map +1 -1
  48. package/dist/esm/l10n/en.json.js +95 -1
  49. package/dist/esm/l10n/en.json.js.map +1 -1
  50. package/dist/esm/l10n/nl.json.js +109 -1
  51. package/dist/esm/l10n/nl.json.js.map +1 -1
  52. package/dist/esm/library-style.js +1 -1
  53. package/dist/esm/nextcloud-vue.css +660 -81
  54. package/dist/esm/utils/libraryWidgetKeys.js +1 -0
  55. package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
  56. package/dist/esm/utils/workingCalendarMonth.js +85 -0
  57. package/dist/esm/utils/workingCalendarMonth.js.map +1 -0
  58. package/dist/nextcloud-vue.cjs.js +9355 -7303
  59. package/dist/nextcloud-vue.cjs.js.map +2 -2
  60. package/dist/nextcloud-vue.css +660 -81
  61. package/l10n/en.json +95 -1
  62. package/l10n/nl.json +109 -1
  63. package/package.json +1 -1
  64. package/src/components/CnActionsMenu/CnActionsMenu.vue +14 -1
  65. package/src/components/CnBreadcrumbs/CnBreadcrumbs.vue +1 -0
  66. package/src/components/CnDetailPage/CnDetailPage.vue +1 -0
  67. package/src/components/CnOrderedListField/CnOrderedListField.vue +81 -9
  68. package/src/components/CnSettingsField/CnSettingsField.vue +449 -0
  69. package/src/components/CnSettingsField/index.js +4 -0
  70. package/src/components/CnSettingsFieldsSection/CnSettingsFieldsSection.vue +613 -0
  71. package/src/components/CnSettingsFieldsSection/index.js +4 -0
  72. package/src/components/CnSettingsPage/CnSettingsPage.vue +6 -86
  73. package/src/components/CnWidgetGrid/registerDashboardWidgets.js +1 -0
  74. package/src/components/CnWorkingCalendarWidget/CnWorkingCalendarWidget.vue +725 -0
  75. package/src/components/CnWorkingCalendarWidget/index.js +26 -0
  76. package/src/components/index.js +3 -0
  77. package/src/composables/useFlowStore.js +2 -1
  78. package/src/css/badge.css +32 -16
  79. package/src/css/look-board-detail.css +80 -8
  80. package/src/css/look-board-index.css +52 -8
  81. package/src/css/look-board.css +219 -1
  82. package/src/index.js +3 -0
  83. package/src/utils/libraryWidgetKeys.js +1 -0
  84. package/src/utils/workingCalendarMonth.js +82 -0
@@ -0,0 +1,265 @@
1
+ import axios__default from '@nextcloud/axios';
2
+ import { translate } from '@nextcloud/l10n';
3
+ import { NcButton, NcCheckboxRadioSwitch, NcLoadingIcon, NcSelect, NcTextArea, NcTextField } from '@nextcloud/vue';
4
+ import { prefixUrl, buildHeaders } from '../../utils/headers.js';
5
+ import { readPath } from '../../utils/readPath.js';
6
+ import { serverErrorMessage } from '../../utils/serverErrorMessage.js';
7
+
8
+ let fieldUid = 0;
9
+
10
+ /** Field types and the names CnSettingsPage's manifest uses for them. */
11
+ const KIND_ALIASES = { enum: 'select', string: 'text' };
12
+
13
+ /**
14
+ * CnSettingsField - one settings field: the control for its type with its
15
+ * label, and under it an error or a hint. Shared by CnSettingsPage and
16
+ * CnSettingsFieldsSection.
17
+ *
18
+ * Types: `text` (default), `textarea`, `number`, `switch`, `boolean` (a checkbox),
19
+ * `select` (`enum`) with static `options` or options read from `optionsUrl`,
20
+ * `password` (a masked input that shows its value back) and `secret`
21
+ * (replace-only: the input stays empty, and with `secretSet` says a value is
22
+ * stored; the host sends it only when something is typed), and `image` (a
23
+ * preview tile, the initials without an image, and an upload button that
24
+ * posts the file to `uploadUrl` at once and emits `uploaded`).
25
+ *
26
+ * ```vue
27
+ * <CnSettingsField
28
+ * :field="{ key: 'apiKey', label: 'API key', type: 'secret', secretSet: true }"
29
+ * v-model="form.apiKey" />
30
+ * ```
31
+ */
32
+ var script = {
33
+ name: 'CnSettingsField',
34
+
35
+ components: { NcButton, NcCheckboxRadioSwitch, NcLoadingIcon, NcSelect, NcTextArea, NcTextField },
36
+
37
+ props: {
38
+ /**
39
+ * The field: `{ key, label, type?, options?, optionsUrl?, optionsPath?,
40
+ * optionLabel?, optionValue?, placeholder?, rows?, min?, max?, required?,
41
+ * readOnly?, secretSet? }`. `label` arrives translated.
42
+ *
43
+ * @type {object}
44
+ */
45
+ field: {
46
+ type: Object,
47
+ required: true,
48
+ },
49
+
50
+ /**
51
+ * The value: text, a number, a boolean or a select value. Any type,
52
+ * because a Boolean in a type list would read an empty string as true.
53
+ */
54
+ modelValue: {
55
+ type: null,
56
+ default: null,
57
+ },
58
+
59
+ /** A validation message, drawn under the control and tied to it. */
60
+ error: {
61
+ type: String,
62
+ default: '',
63
+ },
64
+
65
+ /** A hint under the control (not drawn while there is an error). */
66
+ hint: {
67
+ type: String,
68
+ default: '',
69
+ },
70
+ },
71
+
72
+ emits: ['update:modelValue', 'uploaded'],
73
+
74
+ data() {
75
+ fieldUid += 1;
76
+ return {
77
+ uid: fieldUid,
78
+ fetchedOptions: null,
79
+ optionsLoading: false,
80
+ uploading: false,
81
+ uploadError: '',
82
+ }
83
+ },
84
+
85
+ computed: {
86
+ /** @return {string} The control kind. */
87
+ kind() {
88
+ const type = String(this.field.type || 'text');
89
+ return KIND_ALIASES[type] || type
90
+ },
91
+
92
+ /** @return {string} The label. */
93
+ label() {
94
+ return String(this.field.label || this.field.key || '')
95
+ },
96
+
97
+ /** @return {string} The control's id. */
98
+ inputId() {
99
+ return `cn-settings-field-${this.uid}-${String(this.field.key).replace(/[^A-Za-z0-9_-]/g, '-')}`
100
+ },
101
+
102
+ /** @return {string} The message shown under the control: an upload failure, else `error`. */
103
+ shownError() {
104
+ return this.uploadError || this.error
105
+ },
106
+
107
+ /** @return {string|null} The id of the error or hint the control is described by. */
108
+ describedBy() {
109
+ if (this.shownError) {
110
+ return `${this.inputId}-error`
111
+ }
112
+ return this.hint ? `${this.inputId}-hint` : null
113
+ },
114
+
115
+ /** @return {string} The value as text for a text control. */
116
+ stringValue() {
117
+ return this.modelValue === null || this.modelValue === undefined ? '' : String(this.modelValue)
118
+ },
119
+
120
+ /** @return {string} The placeholder of a secret that is stored. */
121
+ secretSetPlaceholder() {
122
+ return translate('nextcloud-vue', 'Stored. Type a new value to replace it.')
123
+ },
124
+
125
+ /**
126
+ * The select options as `{ label, value }`: the static `options` (strings,
127
+ * numbers or objects), or those read from `optionsUrl`.
128
+ *
129
+ * @return {Array<{label: string, value: (string|number|boolean)}>}
130
+ */
131
+ normalizedOptions() {
132
+ const raw = Array.isArray(this.field.options) ? this.field.options : (this.fetchedOptions || []);
133
+ return raw.map((option) => (option && typeof option === 'object'
134
+ ? { ...option, label: String(option.label ?? option.value ?? ''), value: option.value }
135
+ : { label: String(option), value: option }))
136
+ },
137
+
138
+ /** @return {object|null} The option the value selects, for NcSelect. */
139
+ selectedOption() {
140
+ if (this.modelValue === null || this.modelValue === undefined || this.modelValue === '') {
141
+ return null
142
+ }
143
+ return this.normalizedOptions.find((option) => option.value === this.modelValue)
144
+ || { label: String(this.modelValue), value: this.modelValue }
145
+ },
146
+ },
147
+
148
+ watch: {
149
+ 'field.optionsUrl': {
150
+ handler() {
151
+ this.loadOptions();
152
+ },
153
+
154
+ immediate: true,
155
+ },
156
+ },
157
+
158
+ methods: {
159
+ t: translate,
160
+
161
+ /**
162
+ * Upload a chosen image to `uploadUrl` as multipart form data (the
163
+ * file under `uploadField`, default `image`, plus `uploadParams`), then
164
+ * take the new image url from the answer at `uploadValuePath` (or a
165
+ * cache-busted `previewUrl`).
166
+ *
167
+ * @param {File|undefined} file The chosen file.
168
+ * @return {Promise<void>}
169
+ * @spec openspec/changes/settings-fields-section/specs/settings-fields-section/spec.md#requirement-an-image-field-uploads-at-once
170
+ */
171
+ async upload(file) {
172
+ if (!file || !this.field.uploadUrl) {
173
+ return
174
+ }
175
+ const form = new FormData();
176
+ for (const [name, value] of Object.entries(this.field.uploadParams || {})) {
177
+ form.append(name, value);
178
+ }
179
+ form.append(this.field.uploadField || 'image', file);
180
+ this.uploading = true;
181
+ this.uploadError = '';
182
+ try {
183
+ const response = await axios__default.post(prefixUrl(this.field.uploadUrl), form);
184
+ const fromAnswer = this.field.uploadValuePath ? readPath(response?.data, this.field.uploadValuePath) : '';
185
+ const next = fromAnswer || (this.field.previewUrl ? `${prefixUrl(this.field.previewUrl)}${this.field.previewUrl.includes('?') ? '&' : '?'}v=${Date.now()}` : '');
186
+ /**
187
+ * @event uploaded An image was uploaded. Payload: its new url.
188
+ */
189
+ this.$emit('uploaded', next);
190
+ } catch (error) {
191
+ this.uploadError = translate('nextcloud-vue', 'The image could not be uploaded: {reason}', { reason: serverErrorMessage(error) });
192
+ } finally {
193
+ this.uploading = false;
194
+ if (this.$refs.file) {
195
+ this.$refs.file.value = '';
196
+ }
197
+ }
198
+ },
199
+
200
+ /**
201
+ * Report a new value.
202
+ *
203
+ * @param {unknown} value The value.
204
+ * @return {void}
205
+ */
206
+ emitValue(value) {
207
+ /**
208
+ * @event update:modelValue The value changed. Payload: the new value.
209
+ */
210
+ this.$emit('update:modelValue', value);
211
+ },
212
+
213
+ /**
214
+ * The storable value of what NcSelect emits.
215
+ *
216
+ * @param {object|string|number|null} emitted An option, or null on clear.
217
+ * @return {string|number|boolean|null}
218
+ */
219
+ optionValue(emitted) {
220
+ if (emitted === null || emitted === undefined) {
221
+ return null
222
+ }
223
+ return typeof emitted === 'object' && 'value' in emitted ? emitted.value : emitted
224
+ },
225
+
226
+ /**
227
+ * Read a sourced select's options: a list (or the list at
228
+ * `optionsPath`, or under `results`) mapped through `optionLabel` and
229
+ * `optionValue` (dotted paths; default `label`/`title`/`name` and `value`/`id`).
230
+ *
231
+ * @return {Promise<void>}
232
+ */
233
+ async loadOptions() {
234
+ const url = this.kind === 'select' && !Array.isArray(this.field.options) ? this.field.optionsUrl : '';
235
+ if (!url) {
236
+ return
237
+ }
238
+ this.optionsLoading = true;
239
+ try {
240
+ const response = await fetch(prefixUrl(url), { headers: buildHeaders() });
241
+ if (!response.ok) {
242
+ this.fetchedOptions = [];
243
+ return
244
+ }
245
+ const data = await response.json();
246
+ const list = this.field.optionsPath ? readPath(data, this.field.optionsPath) : (Array.isArray(data) ? data : data?.results);
247
+ this.fetchedOptions = (Array.isArray(list) ? list : []).map((item) => {
248
+ if (!item || typeof item !== 'object') {
249
+ return { label: String(item), value: item }
250
+ }
251
+ const value = this.field.optionValue ? readPath(item, this.field.optionValue) : (item.value ?? item.id);
252
+ const label = this.field.optionLabel ? readPath(item, this.field.optionLabel) : (item.label ?? item.title ?? item.name ?? value);
253
+ return { label: String(label ?? ''), value }
254
+ });
255
+ } catch {
256
+ this.fetchedOptions = [];
257
+ } finally {
258
+ this.optionsLoading = false;
259
+ }
260
+ },
261
+ },
262
+ };
263
+
264
+ export { script as default };
265
+ //# sourceMappingURL=CnSettingsField.vue2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CnSettingsField.vue2.js","sources":["../../../../src/components/CnSettingsField/CnSettingsField.vue"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<template>\n\t<div class=\"cn-settings-field\" :data-testid=\"`cn-settings-field-${field.key}`\">\n\t\t<NcCheckboxRadioSwitch\n\t\t\tv-if=\"kind === 'switch' || kind === 'boolean'\"\n\t\t\t:type=\"kind === 'switch' ? 'switch' : 'checkbox'\"\n\t\t\t:modelValue=\"!!modelValue\"\n\t\t\t:disabled=\"field.readOnly\"\n\t\t\t:aria-describedby=\"describedBy\"\n\t\t\t@update:modelValue=\"emitValue($event)\">\n\t\t\t{{ label }}\n\t\t</NcCheckboxRadioSwitch>\n\t\t<div v-else-if=\"kind === 'image'\"\n\t\t\tclass=\"cn-settings-field__image\"\n\t\t\trole=\"group\"\n\t\t\t:aria-labelledby=\"`${inputId}-label`\">\n\t\t\t<span :id=\"`${inputId}-label`\" class=\"cn-settings-field__image-label\">{{ label }}</span>\n\t\t\t<div class=\"cn-settings-field__image-row\">\n\t\t\t\t<span class=\"cn-settings-field__image-tile\" :data-testid=\"`cn-settings-field-image-${field.key}`\">\n\t\t\t\t\t<img v-if=\"stringValue\" :src=\"stringValue\" :alt=\"t('nextcloud-vue', 'Current {name}', { name: label })\">\n\t\t\t\t\t<span v-else class=\"cn-settings-field__image-initials\" aria-hidden=\"true\">{{ field.initials || '' }}</span>\n\t\t\t\t</span>\n\t\t\t\t<input\n\t\t\t\t\t:id=\"inputId\"\n\t\t\t\t\tref=\"file\"\n\t\t\t\t\tclass=\"hidden-visually\"\n\t\t\t\t\ttype=\"file\"\n\t\t\t\t\t:accept=\"field.accept || 'image/*'\"\n\t\t\t\t\ttabindex=\"-1\"\n\t\t\t\t\t:aria-labelledby=\"`${inputId}-label`\"\n\t\t\t\t\t:aria-describedby=\"describedBy\"\n\t\t\t\t\t@change=\"upload($event.target.files && $event.target.files[0])\">\n\t\t\t\t<NcButton\n\t\t\t\t\tv-if=\"!field.readOnly && field.uploadUrl\"\n\t\t\t\t\t:disabled=\"uploading\"\n\t\t\t\t\t:aria-describedby=\"describedBy\"\n\t\t\t\t\t:data-testid=\"`cn-settings-field-upload-${field.key}`\"\n\t\t\t\t\t@click=\"$refs.file && $refs.file.click()\">\n\t\t\t\t\t<template v-if=\"uploading\" #icon>\n\t\t\t\t\t\t<NcLoadingIcon :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t{{ field.uploadLabel || t('nextcloud-vue', 'Upload {name}', { name: label }) }}\n\t\t\t\t</NcButton>\n\t\t\t</div>\n\t\t</div>\n\t\t<NcSelect\n\t\t\tv-else-if=\"kind === 'select'\"\n\t\t\t:inputId=\"inputId\"\n\t\t\t:modelValue=\"selectedOption\"\n\t\t\t:options=\"normalizedOptions\"\n\t\t\t:inputLabel=\"label\"\n\t\t\t:placeholder=\"field.placeholder || ''\"\n\t\t\t:loading=\"optionsLoading\"\n\t\t\t:disabled=\"field.readOnly\"\n\t\t\t:clearable=\"!field.required\"\n\t\t\tlabel=\"label\"\n\t\t\t:aria-describedby=\"describedBy\"\n\t\t\t@update:modelValue=\"emitValue(optionValue($event))\" />\n\t\t<NcTextArea\n\t\t\tv-else-if=\"kind === 'textarea'\"\n\t\t\t:id=\"inputId\"\n\t\t\t:label=\"label\"\n\t\t\t:modelValue=\"stringValue\"\n\t\t\t:rows=\"field.rows || 4\"\n\t\t\t:placeholder=\"field.placeholder || ''\"\n\t\t\t:readonly=\"field.readOnly\"\n\t\t\t:error=\"!!error\"\n\t\t\t:aria-describedby=\"describedBy\"\n\t\t\t@update:modelValue=\"emitValue($event)\" />\n\t\t<NcTextField\n\t\t\tv-else-if=\"kind === 'secret'\"\n\t\t\t:id=\"inputId\"\n\t\t\t:label=\"label\"\n\t\t\ttype=\"password\"\n\t\t\tautocomplete=\"new-password\"\n\t\t\t:modelValue=\"stringValue\"\n\t\t\t:placeholder=\"field.secretSet ? secretSetPlaceholder : (field.placeholder || '')\"\n\t\t\t:readonly=\"field.readOnly\"\n\t\t\t:error=\"!!error\"\n\t\t\t:aria-describedby=\"describedBy\"\n\t\t\t@update:modelValue=\"emitValue($event)\" />\n\t\t<NcTextField\n\t\t\tv-else-if=\"kind === 'number'\"\n\t\t\t:id=\"inputId\"\n\t\t\t:label=\"label\"\n\t\t\ttype=\"number\"\n\t\t\t:min=\"field.min\"\n\t\t\t:max=\"field.max\"\n\t\t\t:modelValue=\"stringValue\"\n\t\t\t:readonly=\"field.readOnly\"\n\t\t\t:error=\"!!error\"\n\t\t\t:aria-describedby=\"describedBy\"\n\t\t\t@update:modelValue=\"emitValue($event === '' ? null : Number($event))\" />\n\t\t<NcTextField\n\t\t\tv-else\n\t\t\t:id=\"inputId\"\n\t\t\t:label=\"label\"\n\t\t\t:type=\"kind === 'password' ? 'password' : 'text'\"\n\t\t\t:modelValue=\"stringValue\"\n\t\t\t:placeholder=\"field.placeholder || ''\"\n\t\t\t:readonly=\"field.readOnly\"\n\t\t\t:error=\"!!error\"\n\t\t\t:aria-describedby=\"describedBy\"\n\t\t\t@update:modelValue=\"emitValue($event)\" />\n\t\t<p v-if=\"shownError\"\n\t\t\t:id=\"`${inputId}-error`\"\n\t\t\tclass=\"cn-settings-field__error\"\n\t\t\t:data-testid=\"`cn-settings-field-error-${field.key}`\">\n\t\t\t{{ shownError }}\n\t\t</p>\n\t\t<p v-else-if=\"hint\" :id=\"`${inputId}-hint`\" class=\"cn-settings-field__hint\">\n\t\t\t{{ hint }}\n\t\t</p>\n\t</div>\n</template>\n\n<script>\nimport axios from '@nextcloud/axios'\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcButton, NcCheckboxRadioSwitch, NcLoadingIcon, NcSelect, NcTextArea, NcTextField } from '@nextcloud/vue'\nimport { buildHeaders, prefixUrl } from '../../utils/headers.js'\nimport { readPath } from '../../utils/readPath.js'\nimport { serverErrorMessage } from '../../utils/serverErrorMessage.js'\n\nlet fieldUid = 0\n\n/** Field types and the names CnSettingsPage's manifest uses for them. */\nconst KIND_ALIASES = { enum: 'select', string: 'text' }\n\n/**\n * CnSettingsField - one settings field: the control for its type with its\n * label, and under it an error or a hint. Shared by CnSettingsPage and\n * CnSettingsFieldsSection.\n *\n * Types: `text` (default), `textarea`, `number`, `switch`, `boolean` (a checkbox),\n * `select` (`enum`) with static `options` or options read from `optionsUrl`,\n * `password` (a masked input that shows its value back) and `secret`\n * (replace-only: the input stays empty, and with `secretSet` says a value is\n * stored; the host sends it only when something is typed), and `image` (a\n * preview tile, the initials without an image, and an upload button that\n * posts the file to `uploadUrl` at once and emits `uploaded`).\n *\n * ```vue\n * <CnSettingsField\n * :field=\"{ key: 'apiKey', label: 'API key', type: 'secret', secretSet: true }\"\n * v-model=\"form.apiKey\" />\n * ```\n */\nexport default {\n\tname: 'CnSettingsField',\n\n\tcomponents: { NcButton, NcCheckboxRadioSwitch, NcLoadingIcon, NcSelect, NcTextArea, NcTextField },\n\n\tprops: {\n\t\t/**\n\t\t * The field: `{ key, label, type?, options?, optionsUrl?, optionsPath?,\n\t\t * optionLabel?, optionValue?, placeholder?, rows?, min?, max?, required?,\n\t\t * readOnly?, secretSet? }`. `label` arrives translated.\n\t\t *\n\t\t * @type {object}\n\t\t */\n\t\tfield: {\n\t\t\ttype: Object,\n\t\t\trequired: true,\n\t\t},\n\n\t\t/**\n\t\t * The value: text, a number, a boolean or a select value. Any type,\n\t\t * because a Boolean in a type list would read an empty string as true.\n\t\t */\n\t\tmodelValue: {\n\t\t\ttype: null,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** A validation message, drawn under the control and tied to it. */\n\t\terror: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** A hint under the control (not drawn while there is an error). */\n\t\thint: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\t},\n\n\temits: ['update:modelValue', 'uploaded'],\n\n\tdata() {\n\t\tfieldUid += 1\n\t\treturn {\n\t\t\tuid: fieldUid,\n\t\t\tfetchedOptions: null,\n\t\t\toptionsLoading: false,\n\t\t\tuploading: false,\n\t\t\tuploadError: '',\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/** @return {string} The control kind. */\n\t\tkind() {\n\t\t\tconst type = String(this.field.type || 'text')\n\t\t\treturn KIND_ALIASES[type] || type\n\t\t},\n\n\t\t/** @return {string} The label. */\n\t\tlabel() {\n\t\t\treturn String(this.field.label || this.field.key || '')\n\t\t},\n\n\t\t/** @return {string} The control's id. */\n\t\tinputId() {\n\t\t\treturn `cn-settings-field-${this.uid}-${String(this.field.key).replace(/[^A-Za-z0-9_-]/g, '-')}`\n\t\t},\n\n\t\t/** @return {string} The message shown under the control: an upload failure, else `error`. */\n\t\tshownError() {\n\t\t\treturn this.uploadError || this.error\n\t\t},\n\n\t\t/** @return {string|null} The id of the error or hint the control is described by. */\n\t\tdescribedBy() {\n\t\t\tif (this.shownError) {\n\t\t\t\treturn `${this.inputId}-error`\n\t\t\t}\n\t\t\treturn this.hint ? `${this.inputId}-hint` : null\n\t\t},\n\n\t\t/** @return {string} The value as text for a text control. */\n\t\tstringValue() {\n\t\t\treturn this.modelValue === null || this.modelValue === undefined ? '' : String(this.modelValue)\n\t\t},\n\n\t\t/** @return {string} The placeholder of a secret that is stored. */\n\t\tsecretSetPlaceholder() {\n\t\t\treturn t('nextcloud-vue', 'Stored. Type a new value to replace it.')\n\t\t},\n\n\t\t/**\n\t\t * The select options as `{ label, value }`: the static `options` (strings,\n\t\t * numbers or objects), or those read from `optionsUrl`.\n\t\t *\n\t\t * @return {Array<{label: string, value: (string|number|boolean)}>}\n\t\t */\n\t\tnormalizedOptions() {\n\t\t\tconst raw = Array.isArray(this.field.options) ? this.field.options : (this.fetchedOptions || [])\n\t\t\treturn raw.map((option) => (option && typeof option === 'object'\n\t\t\t\t? { ...option, label: String(option.label ?? option.value ?? ''), value: option.value }\n\t\t\t\t: { label: String(option), value: option }))\n\t\t},\n\n\t\t/** @return {object|null} The option the value selects, for NcSelect. */\n\t\tselectedOption() {\n\t\t\tif (this.modelValue === null || this.modelValue === undefined || this.modelValue === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn this.normalizedOptions.find((option) => option.value === this.modelValue)\n\t\t\t\t|| { label: String(this.modelValue), value: this.modelValue }\n\t\t},\n\t},\n\n\twatch: {\n\t\t'field.optionsUrl': {\n\t\t\thandler() {\n\t\t\t\tthis.loadOptions()\n\t\t\t},\n\n\t\t\timmediate: true,\n\t\t},\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/**\n\t\t * Upload a chosen image to `uploadUrl` as multipart form data (the\n\t\t * file under `uploadField`, default `image`, plus `uploadParams`), then\n\t\t * take the new image url from the answer at `uploadValuePath` (or a\n\t\t * cache-busted `previewUrl`).\n\t\t *\n\t\t * @param {File|undefined} file The chosen file.\n\t\t * @return {Promise<void>}\n\t\t * @spec openspec/changes/settings-fields-section/specs/settings-fields-section/spec.md#requirement-an-image-field-uploads-at-once\n\t\t */\n\t\tasync upload(file) {\n\t\t\tif (!file || !this.field.uploadUrl) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst form = new FormData()\n\t\t\tfor (const [name, value] of Object.entries(this.field.uploadParams || {})) {\n\t\t\t\tform.append(name, value)\n\t\t\t}\n\t\t\tform.append(this.field.uploadField || 'image', file)\n\t\t\tthis.uploading = true\n\t\t\tthis.uploadError = ''\n\t\t\ttry {\n\t\t\t\tconst response = await axios.post(prefixUrl(this.field.uploadUrl), form)\n\t\t\t\tconst fromAnswer = this.field.uploadValuePath ? readPath(response?.data, this.field.uploadValuePath) : ''\n\t\t\t\tconst next = fromAnswer || (this.field.previewUrl ? `${prefixUrl(this.field.previewUrl)}${this.field.previewUrl.includes('?') ? '&' : '?'}v=${Date.now()}` : '')\n\t\t\t\t/**\n\t\t\t\t * @event uploaded An image was uploaded. Payload: its new url.\n\t\t\t\t */\n\t\t\t\tthis.$emit('uploaded', next)\n\t\t\t} catch (error) {\n\t\t\t\tthis.uploadError = t('nextcloud-vue', 'The image could not be uploaded: {reason}', { reason: serverErrorMessage(error) })\n\t\t\t} finally {\n\t\t\t\tthis.uploading = false\n\t\t\t\tif (this.$refs.file) {\n\t\t\t\t\tthis.$refs.file.value = ''\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Report a new value.\n\t\t *\n\t\t * @param {unknown} value The value.\n\t\t * @return {void}\n\t\t */\n\t\temitValue(value) {\n\t\t\t/**\n\t\t\t * @event update:modelValue The value changed. Payload: the new value.\n\t\t\t */\n\t\t\tthis.$emit('update:modelValue', value)\n\t\t},\n\n\t\t/**\n\t\t * The storable value of what NcSelect emits.\n\t\t *\n\t\t * @param {object|string|number|null} emitted An option, or null on clear.\n\t\t * @return {string|number|boolean|null}\n\t\t */\n\t\toptionValue(emitted) {\n\t\t\tif (emitted === null || emitted === undefined) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn typeof emitted === 'object' && 'value' in emitted ? emitted.value : emitted\n\t\t},\n\n\t\t/**\n\t\t * Read a sourced select's options: a list (or the list at\n\t\t * `optionsPath`, or under `results`) mapped through `optionLabel` and\n\t\t * `optionValue` (dotted paths; default `label`/`title`/`name` and `value`/`id`).\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync loadOptions() {\n\t\t\tconst url = this.kind === 'select' && !Array.isArray(this.field.options) ? this.field.optionsUrl : ''\n\t\t\tif (!url) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.optionsLoading = true\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(url), { headers: buildHeaders() })\n\t\t\t\tif (!response.ok) {\n\t\t\t\t\tthis.fetchedOptions = []\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tconst data = await response.json()\n\t\t\t\tconst list = this.field.optionsPath ? readPath(data, this.field.optionsPath) : (Array.isArray(data) ? data : data?.results)\n\t\t\t\tthis.fetchedOptions = (Array.isArray(list) ? list : []).map((item) => {\n\t\t\t\t\tif (!item || typeof item !== 'object') {\n\t\t\t\t\t\treturn { label: String(item), value: item }\n\t\t\t\t\t}\n\t\t\t\t\tconst value = this.field.optionValue ? readPath(item, this.field.optionValue) : (item.value ?? item.id)\n\t\t\t\t\tconst label = this.field.optionLabel ? readPath(item, this.field.optionLabel) : (item.label ?? item.title ?? item.name ?? value)\n\t\t\t\t\treturn { label: String(label ?? ''), value }\n\t\t\t\t})\n\t\t\t} catch {\n\t\t\t\tthis.fetchedOptions = []\n\t\t\t} finally {\n\t\t\t\tthis.optionsLoading = false\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-settings-field {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n\tmin-width: 0;\n}\n\n.cn-settings-field__hint,\n.cn-settings-field__error {\n\tmargin: 0;\n\tfont-size: 13px;\n\tline-height: 1.4;\n}\n\n.cn-settings-field__image {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 6px;\n}\n\n.cn-settings-field__image-label {\n\tfont-weight: 600;\n}\n\n.cn-settings-field__image-row {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tgap: 12px;\n}\n\n.cn-settings-field__image-tile {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tjustify-content: center;\n\twidth: 64px;\n\theight: 64px;\n\toverflow: hidden;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius-large);\n\tbackground: var(--color-primary-element-light);\n}\n\n.cn-settings-field__image-tile img {\n\tmax-width: 100%;\n\tmax-height: 100%;\n\tobject-fit: contain;\n}\n\n.cn-settings-field__image-initials {\n\tfont-size: 20px;\n\tfont-weight: 700;\n\tcolor: var(--color-primary-element-light-text, var(--color-primary-element));\n}\n\n.cn-settings-field__hint {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-settings-field__error {\n\tcolor: var(--color-error-text, var(--color-error));\n}\n</style>\n"],"names":["t","axios"],"mappings":";;;;;;;AAgIA,IAAI,QAAO,GAAI,EAAA;;AAEf;AACA,MAAM,YAAW,GAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAO,GAAA;;AAEtD;;;;AAIC;AACA;AACA;;AAEA;;;;AAIA;AACA;AACA;AACA;;AAEA;;AAED,aAAe;CACd,IAAI,EAAE,iBAAiB;;AAEvB,CAAA,UAAU,EAAE,EAAE,QAAQ,EAAE,qBAAqB,EAAE,aAAa,EAAE,QAAQ,EAAE,UAAU,EAAE,aAAa;;AAEjG,CAAA,KAAK,EAAE;AACN;;AAEC;;AAEA;;;AAGD,EAAA,KAAK,EAAE;GACN,IAAI,EAAE,MAAM;GACZ,QAAQ,EAAE,IAAI;GACd;;AAED;;;;EAIA,UAAU,EAAE;AACX,GAAA,IAAI,EAAE,IAAI;GACV,OAAO,EAAE,IAAI;GACb;;;AAGD,EAAA,KAAK,EAAE;GACN,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,EAAE;GACX;;;EAGD,IAAI,EAAE;GACL,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,EAAE;GACX;EACD;;AAED,CAAA,KAAK,EAAE,CAAC,mBAAmB,EAAE,UAAU,CAAC;;AAExC,CAAA,IAAI,GAAG;EACN,QAAO,IAAK,EAAA;AACZ,EAAA,OAAO;GACN,GAAG,EAAE,QAAQ;AACb,GAAA,cAAc,EAAE,IAAI;AACpB,GAAA,cAAc,EAAE,KAAK;AACrB,GAAA,SAAS,EAAE,KAAK;AAChB,GAAA,WAAW,EAAE,EAAE;AAChB,GAAA;EACA;;AAED,CAAA,QAAQ,EAAE;AACT;AACA,EAAA,IAAI,GAAG;AACN,GAAA,MAAM,IAAG,GAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,IAAG,IAAK,MAAM,EAAA;GAC7C,OAAO,YAAY,CAAC,IAAI,CAAA,IAAK,IAAG;GAChC;;;AAGD,EAAA,KAAK,GAAG;GACP,OAAO,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,KAAM,IAAG,IAAI,CAAC,KAAK,CAAC,GAAI,IAAG,EAAE,CAAA;GACtD;;AAED;AACA,EAAA,OAAO,GAAG;AACT,GAAA,OAAO,CAAC,kBAAkB,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,iBAAiB,EAAE,GAAG,CAAC,CAAC,CAAA;GAC/F;;AAED;EACA,UAAU,GAAG;AACZ,GAAA,OAAO,IAAI,CAAC,WAAU,IAAK,IAAI,CAAC,KAAI;GACpC;;AAED;EACA,WAAW,GAAG;AACb,GAAA,IAAI,IAAI,CAAC,UAAU,EAAE;IACpB,OAAO,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,MAAM,CAAA;AAC9B,IAAA;AACA,GAAA,OAAO,IAAI,CAAC,IAAG,GAAI,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,KAAK,IAAI,IAAG;GAC/C;;;EAGD,WAAW,GAAG;AACb,GAAA,OAAO,IAAI,CAAC,UAAW,KAAI,QAAQ,IAAI,CAAC,UAAW,KAAI,YAAY,KAAK,MAAM,CAAC,IAAI,CAAC,UAAU,CAAA;GAC9F;;;EAGD,oBAAoB,GAAG;GACtB,OAAOA,SAAC,CAAC,eAAe,EAAE,yCAAyC,CAAA;GACnE;;AAED;;;AAGC;;;AAGD,EAAA,iBAAiB,GAAG;AACnB,GAAA,MAAM,MAAM,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAE,GAAE,IAAI,CAAC,KAAK,CAAC,OAAQ,IAAG,IAAI,CAAC,kBAAkB,EAAE,EAAA;GAC/F,OAAO,GAAG,CAAC,GAAG,CAAC,CAAC,MAAM,MAAM,MAAO,IAAG,OAAO,MAAO,KAAI,QAAO;AAC9D,MAAE,EAAE,GAAG,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,KAAI,IAAK,MAAM,CAAC,KAAI,IAAK,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,KAAM,EAAA;AACtF,MAAE,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,MAAK,EAAG,CAAC,CAAA;GAC5C;;;AAGD,EAAA,cAAc,GAAG;AAChB,GAAA,IAAI,IAAI,CAAC,UAAS,KAAM,IAAG,IAAK,IAAI,CAAC,UAAW,KAAI,SAAU,IAAG,IAAI,CAAC,eAAe,EAAE,EAAE;AACxF,IAAA,OAAO,IAAG;AACX,IAAA;AACA,GAAA,OAAO,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC,MAAM,KAAK,MAAM,CAAC,KAAI,KAAM,IAAI,CAAC,UAAU,CAAA;OAC3E,EAAE,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,UAAW,EAAA;GAC7D;EACD;;AAED,CAAA,KAAK,EAAE;AACN,EAAA,kBAAkB,EAAE;AACnB,GAAA,OAAO,GAAG;AACT,IAAA,IAAI,CAAC,WAAW,GAAC;IACjB;;AAED,GAAA,SAAS,EAAE,IAAI;GACf;EACD;;AAED,CAAA,OAAO,EAAE;KACRA,SAAC;;AAED;;AAEC;;;AAGA;AACA;;AAEA;;AAED,EAAA,MAAM,MAAM,CAAC,IAAI,EAAE;AAClB,GAAA,IAAI,CAAC,IAAK,IAAG,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS,EAAE;IACnC,MAAK;AACN,IAAA;GACA,MAAM,OAAO,IAAI,QAAQ,GAAC;AAC1B,GAAA,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAE,IAAG,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,YAAW,IAAK,EAAE,CAAC,EAAE;IAC1E,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,EAAA;AACxB,IAAA;GACA,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,eAAe,OAAO,EAAE,IAAI,EAAA;AACnD,GAAA,IAAI,CAAC,SAAQ,GAAI,KAAG;AACpB,GAAA,IAAI,CAAC,WAAU,GAAI,GAAC;AACpB,GAAA,IAAI;AACH,IAAA,MAAM,QAAO,GAAI,MAAMC,cAAK,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE,IAAI,EAAA;IACvE,MAAM,aAAa,IAAI,CAAC,KAAK,CAAC,kBAAkB,QAAQ,CAAC,QAAQ,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,eAAe,CAAE,GAAE,GAAC;AACxG,IAAA,MAAM,IAAK,GAAE,UAAW,KAAI,IAAI,CAAC,KAAK,CAAC,UAAW,GAAE,CAAC,EAAE,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,QAAQ,CAAC,GAAG,CAAE,GAAE,GAAI,GAAE,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAE,GAAE,EAAE,EAAA;AAC/J;;;IAGA,IAAI,CAAC,KAAK,CAAC,UAAU,EAAE,IAAI,EAAA;IAC1B,CAAA,OAAO,KAAK,EAAE;IACf,IAAI,CAAC,WAAY,GAAED,SAAC,CAAC,eAAe,EAAE,2CAA2C,EAAE,EAAE,MAAM,EAAE,kBAAkB,CAAC,KAAK,CAAA,EAAG,EAAA;AACzH,IAAA,SAAU;AACT,IAAA,IAAI,CAAC,SAAQ,GAAI,MAAI;AACrB,IAAA,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE;KACpB,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAM,GAAE,GAAC;AAC1B,KAAA;AACD,IAAA;GACA;;AAED;AACC;AACA;;;;AAID,EAAA,SAAS,CAAC,KAAK,EAAE;AAChB;;;AAGA,GAAA,IAAI,CAAC,KAAK,CAAC,mBAAmB,EAAE,KAAK,EAAA;GACrC;;AAED;AACC;AACA;AACA;AACA;;AAED,EAAA,WAAW,CAAC,OAAO,EAAE;AACpB,GAAA,IAAI,OAAQ,KAAI,QAAQ,OAAM,KAAM,SAAS,EAAE;AAC9C,IAAA,OAAO,IAAG;AACX,IAAA;AACA,GAAA,OAAO,OAAO,OAAQ,KAAI,QAAS,IAAG,OAAQ,IAAG,OAAQ,GAAE,OAAO,CAAC,KAAM,GAAE,OAAM;GACjF;;AAED;AACC;;AAEA;AACA;;;AAGD,EAAA,MAAM,WAAW,GAAG;AACnB,GAAA,MAAM,MAAM,IAAI,CAAC,IAAG,KAAM,QAAO,IAAK,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,IAAI,IAAI,CAAC,KAAK,CAAC,UAAS,GAAI,GAAC;AACpG,GAAA,IAAI,CAAC,GAAG,EAAE;IACT,MAAK;AACN,IAAA;GACA,IAAI,CAAC,cAAe,GAAE,KAAG;AACzB,GAAA,IAAI;AACH,IAAA,MAAM,QAAO,GAAI,MAAM,KAAK,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,EAAE,OAAO,EAAE,YAAY,IAAI,EAAA;AACxE,IAAA,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE;AACjB,KAAA,IAAI,CAAC,cAAa,GAAI,GAAC;KACvB,MAAK;AACN,KAAA;IACA,MAAM,IAAK,GAAE,MAAM,QAAQ,CAAC,IAAI,GAAC;IACjC,MAAM,IAAG,GAAI,IAAI,CAAC,KAAK,CAAC,WAAU,GAAI,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,WAAW,KAAK,KAAK,CAAC,OAAO,CAAC,IAAI,IAAI,IAAK,GAAE,IAAI,EAAE,OAAO,EAAA;AAC1H,IAAA,IAAI,CAAC,cAAa,GAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAE,GAAE,IAAG,GAAI,EAAE,EAAE,GAAG,CAAC,CAAC,IAAI,KAAK;AACrE,KAAA,IAAI,CAAC,IAAK,IAAG,OAAO,IAAG,KAAM,QAAQ,EAAE;AACtC,MAAA,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,IAAK,EAAA;AAC3C,MAAA;AACA,KAAA,MAAM,KAAI,GAAI,IAAI,CAAC,KAAK,CAAC,WAAY,GAAE,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,WAAW,CAAE,IAAG,IAAI,CAAC,KAAM,IAAG,IAAI,CAAC,EAAE,EAAA;KACtG,MAAM,KAAM,GAAE,IAAI,CAAC,KAAK,CAAC,WAAY,GAAE,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,WAAW,KAAK,IAAI,CAAC,KAAI,IAAK,IAAI,CAAC,KAAI,IAAK,IAAI,CAAC,IAAG,IAAK,KAAK,EAAA;AAC/H,KAAA,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,SAAS,EAAE,CAAC,EAAE,KAAM,EAAA;KAC3C,EAAA;AACF,IAAA,CAAE,MAAM;AACP,IAAA,IAAI,CAAC,cAAa,GAAI,GAAC;AACxB,IAAA,SAAU;IACT,IAAI,CAAC,iBAAiB,MAAI;AAC3B,IAAA;GACA;EACD;AACF;;;;"}
@@ -0,0 +1,191 @@
1
+ import { resolveComponent, openBlock, createElementBlock, createBlock, withCtx, createTextVNode, toDisplayString, createElementVNode, createSlots, createVNode, createCommentVNode } from 'vue';
2
+
3
+ const _hoisted_1 = ["data-testid"];
4
+ const _hoisted_2 = ["aria-labelledby"];
5
+ const _hoisted_3 = ["id"];
6
+ const _hoisted_4 = { class: "cn-settings-field__image-row" };
7
+ const _hoisted_5 = ["data-testid"];
8
+ const _hoisted_6 = ["src", "alt"];
9
+ const _hoisted_7 = {
10
+ key: 1,
11
+ class: "cn-settings-field__image-initials",
12
+ "aria-hidden": "true"
13
+ };
14
+ const _hoisted_8 = ["id", "accept", "aria-labelledby", "aria-describedby"];
15
+ const _hoisted_9 = ["id", "data-testid"];
16
+ const _hoisted_10 = ["id"];
17
+
18
+ function render(_ctx, _cache, $props, $setup, $data, $options) {
19
+ const _component_NcCheckboxRadioSwitch = resolveComponent("NcCheckboxRadioSwitch");
20
+ const _component_NcLoadingIcon = resolveComponent("NcLoadingIcon");
21
+ const _component_NcButton = resolveComponent("NcButton");
22
+ const _component_NcSelect = resolveComponent("NcSelect");
23
+ const _component_NcTextArea = resolveComponent("NcTextArea");
24
+ const _component_NcTextField = resolveComponent("NcTextField");
25
+
26
+ return (openBlock(), createElementBlock("div", {
27
+ class: "cn-settings-field",
28
+ "data-testid": `cn-settings-field-${$props.field.key}`
29
+ }, [
30
+ ($options.kind === 'switch' || $options.kind === 'boolean')
31
+ ? (openBlock(), createBlock(_component_NcCheckboxRadioSwitch, {
32
+ key: 0,
33
+ type: $options.kind === 'switch' ? 'switch' : 'checkbox',
34
+ modelValue: !!$props.modelValue,
35
+ disabled: $props.field.readOnly,
36
+ "aria-describedby": $options.describedBy,
37
+ "onUpdate:modelValue": _cache[0] || (_cache[0] = $event => ($options.emitValue($event)))
38
+ }, {
39
+ default: withCtx(() => [
40
+ createTextVNode(toDisplayString($options.label), 1 /* TEXT */)
41
+ ]),
42
+ _: 1 /* STABLE */
43
+ }, 8 /* PROPS */, ["type", "modelValue", "disabled", "aria-describedby"]))
44
+ : ($options.kind === 'image')
45
+ ? (openBlock(), createElementBlock("div", {
46
+ key: 1,
47
+ class: "cn-settings-field__image",
48
+ role: "group",
49
+ "aria-labelledby": `${$options.inputId}-label`
50
+ }, [
51
+ createElementVNode("span", {
52
+ id: `${$options.inputId}-label`,
53
+ class: "cn-settings-field__image-label"
54
+ }, toDisplayString($options.label), 9 /* TEXT, PROPS */, _hoisted_3),
55
+ createElementVNode("div", _hoisted_4, [
56
+ createElementVNode("span", {
57
+ class: "cn-settings-field__image-tile",
58
+ "data-testid": `cn-settings-field-image-${$props.field.key}`
59
+ }, [
60
+ ($options.stringValue)
61
+ ? (openBlock(), createElementBlock("img", {
62
+ key: 0,
63
+ src: $options.stringValue,
64
+ alt: $options.t('nextcloud-vue', 'Current {name}', { name: $options.label })
65
+ }, null, 8 /* PROPS */, _hoisted_6))
66
+ : (openBlock(), createElementBlock("span", _hoisted_7, toDisplayString($props.field.initials || ''), 1 /* TEXT */))
67
+ ], 8 /* PROPS */, _hoisted_5),
68
+ createElementVNode("input", {
69
+ id: $options.inputId,
70
+ ref: "file",
71
+ class: "hidden-visually",
72
+ type: "file",
73
+ accept: $props.field.accept || 'image/*',
74
+ tabindex: "-1",
75
+ "aria-labelledby": `${$options.inputId}-label`,
76
+ "aria-describedby": $options.describedBy,
77
+ onChange: _cache[1] || (_cache[1] = $event => ($options.upload($event.target.files && $event.target.files[0])))
78
+ }, null, 40 /* PROPS, NEED_HYDRATION */, _hoisted_8),
79
+ (!$props.field.readOnly && $props.field.uploadUrl)
80
+ ? (openBlock(), createBlock(_component_NcButton, {
81
+ key: 0,
82
+ disabled: $data.uploading,
83
+ "aria-describedby": $options.describedBy,
84
+ "data-testid": `cn-settings-field-upload-${$props.field.key}`,
85
+ onClick: _cache[2] || (_cache[2] = $event => (_ctx.$refs.file && _ctx.$refs.file.click()))
86
+ }, createSlots({
87
+ default: withCtx(() => [
88
+ createTextVNode(" " + toDisplayString($props.field.uploadLabel || $options.t('nextcloud-vue', 'Upload {name}', { name: $options.label })), 1 /* TEXT */)
89
+ ]),
90
+ _: 2 /* DYNAMIC */
91
+ }, [
92
+ ($data.uploading)
93
+ ? {
94
+ name: "icon",
95
+ fn: withCtx(() => [
96
+ createVNode(_component_NcLoadingIcon, { size: 20 })
97
+ ]),
98
+ key: "0"
99
+ }
100
+ : undefined
101
+ ]), 1032 /* PROPS, DYNAMIC_SLOTS */, ["disabled", "aria-describedby", "data-testid"]))
102
+ : createCommentVNode("v-if", true)
103
+ ])
104
+ ], 8 /* PROPS */, _hoisted_2))
105
+ : ($options.kind === 'select')
106
+ ? (openBlock(), createBlock(_component_NcSelect, {
107
+ key: 2,
108
+ inputId: $options.inputId,
109
+ modelValue: $options.selectedOption,
110
+ options: $options.normalizedOptions,
111
+ inputLabel: $options.label,
112
+ placeholder: $props.field.placeholder || '',
113
+ loading: $data.optionsLoading,
114
+ disabled: $props.field.readOnly,
115
+ clearable: !$props.field.required,
116
+ label: "label",
117
+ "aria-describedby": $options.describedBy,
118
+ "onUpdate:modelValue": _cache[3] || (_cache[3] = $event => ($options.emitValue($options.optionValue($event))))
119
+ }, null, 8 /* PROPS */, ["inputId", "modelValue", "options", "inputLabel", "placeholder", "loading", "disabled", "clearable", "aria-describedby"]))
120
+ : ($options.kind === 'textarea')
121
+ ? (openBlock(), createBlock(_component_NcTextArea, {
122
+ key: 3,
123
+ id: $options.inputId,
124
+ label: $options.label,
125
+ modelValue: $options.stringValue,
126
+ rows: $props.field.rows || 4,
127
+ placeholder: $props.field.placeholder || '',
128
+ readonly: $props.field.readOnly,
129
+ error: !!$props.error,
130
+ "aria-describedby": $options.describedBy,
131
+ "onUpdate:modelValue": _cache[4] || (_cache[4] = $event => ($options.emitValue($event)))
132
+ }, null, 8 /* PROPS */, ["id", "label", "modelValue", "rows", "placeholder", "readonly", "error", "aria-describedby"]))
133
+ : ($options.kind === 'secret')
134
+ ? (openBlock(), createBlock(_component_NcTextField, {
135
+ key: 4,
136
+ id: $options.inputId,
137
+ label: $options.label,
138
+ type: "password",
139
+ autocomplete: "new-password",
140
+ modelValue: $options.stringValue,
141
+ placeholder: $props.field.secretSet ? $options.secretSetPlaceholder : ($props.field.placeholder || ''),
142
+ readonly: $props.field.readOnly,
143
+ error: !!$props.error,
144
+ "aria-describedby": $options.describedBy,
145
+ "onUpdate:modelValue": _cache[5] || (_cache[5] = $event => ($options.emitValue($event)))
146
+ }, null, 8 /* PROPS */, ["id", "label", "modelValue", "placeholder", "readonly", "error", "aria-describedby"]))
147
+ : ($options.kind === 'number')
148
+ ? (openBlock(), createBlock(_component_NcTextField, {
149
+ key: 5,
150
+ id: $options.inputId,
151
+ label: $options.label,
152
+ type: "number",
153
+ min: $props.field.min,
154
+ max: $props.field.max,
155
+ modelValue: $options.stringValue,
156
+ readonly: $props.field.readOnly,
157
+ error: !!$props.error,
158
+ "aria-describedby": $options.describedBy,
159
+ "onUpdate:modelValue": _cache[6] || (_cache[6] = $event => ($options.emitValue($event === '' ? null : Number($event))))
160
+ }, null, 8 /* PROPS */, ["id", "label", "min", "max", "modelValue", "readonly", "error", "aria-describedby"]))
161
+ : (openBlock(), createBlock(_component_NcTextField, {
162
+ key: 6,
163
+ id: $options.inputId,
164
+ label: $options.label,
165
+ type: $options.kind === 'password' ? 'password' : 'text',
166
+ modelValue: $options.stringValue,
167
+ placeholder: $props.field.placeholder || '',
168
+ readonly: $props.field.readOnly,
169
+ error: !!$props.error,
170
+ "aria-describedby": $options.describedBy,
171
+ "onUpdate:modelValue": _cache[7] || (_cache[7] = $event => ($options.emitValue($event)))
172
+ }, null, 8 /* PROPS */, ["id", "label", "type", "modelValue", "placeholder", "readonly", "error", "aria-describedby"])),
173
+ ($options.shownError)
174
+ ? (openBlock(), createElementBlock("p", {
175
+ key: 7,
176
+ id: `${$options.inputId}-error`,
177
+ class: "cn-settings-field__error",
178
+ "data-testid": `cn-settings-field-error-${$props.field.key}`
179
+ }, toDisplayString($options.shownError), 9 /* TEXT, PROPS */, _hoisted_9))
180
+ : ($props.hint)
181
+ ? (openBlock(), createElementBlock("p", {
182
+ key: 8,
183
+ id: `${$options.inputId}-hint`,
184
+ class: "cn-settings-field__hint"
185
+ }, toDisplayString($props.hint), 9 /* TEXT, PROPS */, _hoisted_10))
186
+ : createCommentVNode("v-if", true)
187
+ ], 8 /* PROPS */, _hoisted_1))
188
+ }
189
+
190
+ export { render };
191
+ //# sourceMappingURL=CnSettingsField.vue3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CnSettingsField.vue3.js","sources":["../../../../src/components/CnSettingsField/CnSettingsField.vue?vue&type=template&id=d2218996&lang.js"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<template>\n\t<div class=\"cn-settings-field\" :data-testid=\"`cn-settings-field-${field.key}`\">\n\t\t<NcCheckboxRadioSwitch\n\t\t\tv-if=\"kind === 'switch' || kind === 'boolean'\"\n\t\t\t:type=\"kind === 'switch' ? 'switch' : 'checkbox'\"\n\t\t\t:modelValue=\"!!modelValue\"\n\t\t\t:disabled=\"field.readOnly\"\n\t\t\t:aria-describedby=\"describedBy\"\n\t\t\t@update:modelValue=\"emitValue($event)\">\n\t\t\t{{ label }}\n\t\t</NcCheckboxRadioSwitch>\n\t\t<div v-else-if=\"kind === 'image'\"\n\t\t\tclass=\"cn-settings-field__image\"\n\t\t\trole=\"group\"\n\t\t\t:aria-labelledby=\"`${inputId}-label`\">\n\t\t\t<span :id=\"`${inputId}-label`\" class=\"cn-settings-field__image-label\">{{ label }}</span>\n\t\t\t<div class=\"cn-settings-field__image-row\">\n\t\t\t\t<span class=\"cn-settings-field__image-tile\" :data-testid=\"`cn-settings-field-image-${field.key}`\">\n\t\t\t\t\t<img v-if=\"stringValue\" :src=\"stringValue\" :alt=\"t('nextcloud-vue', 'Current {name}', { name: label })\">\n\t\t\t\t\t<span v-else class=\"cn-settings-field__image-initials\" aria-hidden=\"true\">{{ field.initials || '' }}</span>\n\t\t\t\t</span>\n\t\t\t\t<input\n\t\t\t\t\t:id=\"inputId\"\n\t\t\t\t\tref=\"file\"\n\t\t\t\t\tclass=\"hidden-visually\"\n\t\t\t\t\ttype=\"file\"\n\t\t\t\t\t:accept=\"field.accept || 'image/*'\"\n\t\t\t\t\ttabindex=\"-1\"\n\t\t\t\t\t:aria-labelledby=\"`${inputId}-label`\"\n\t\t\t\t\t:aria-describedby=\"describedBy\"\n\t\t\t\t\t@change=\"upload($event.target.files && $event.target.files[0])\">\n\t\t\t\t<NcButton\n\t\t\t\t\tv-if=\"!field.readOnly && field.uploadUrl\"\n\t\t\t\t\t:disabled=\"uploading\"\n\t\t\t\t\t:aria-describedby=\"describedBy\"\n\t\t\t\t\t:data-testid=\"`cn-settings-field-upload-${field.key}`\"\n\t\t\t\t\t@click=\"$refs.file && $refs.file.click()\">\n\t\t\t\t\t<template v-if=\"uploading\" #icon>\n\t\t\t\t\t\t<NcLoadingIcon :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t{{ field.uploadLabel || t('nextcloud-vue', 'Upload {name}', { name: label }) }}\n\t\t\t\t</NcButton>\n\t\t\t</div>\n\t\t</div>\n\t\t<NcSelect\n\t\t\tv-else-if=\"kind === 'select'\"\n\t\t\t:inputId=\"inputId\"\n\t\t\t:modelValue=\"selectedOption\"\n\t\t\t:options=\"normalizedOptions\"\n\t\t\t:inputLabel=\"label\"\n\t\t\t:placeholder=\"field.placeholder || ''\"\n\t\t\t:loading=\"optionsLoading\"\n\t\t\t:disabled=\"field.readOnly\"\n\t\t\t:clearable=\"!field.required\"\n\t\t\tlabel=\"label\"\n\t\t\t:aria-describedby=\"describedBy\"\n\t\t\t@update:modelValue=\"emitValue(optionValue($event))\" />\n\t\t<NcTextArea\n\t\t\tv-else-if=\"kind === 'textarea'\"\n\t\t\t:id=\"inputId\"\n\t\t\t:label=\"label\"\n\t\t\t:modelValue=\"stringValue\"\n\t\t\t:rows=\"field.rows || 4\"\n\t\t\t:placeholder=\"field.placeholder || ''\"\n\t\t\t:readonly=\"field.readOnly\"\n\t\t\t:error=\"!!error\"\n\t\t\t:aria-describedby=\"describedBy\"\n\t\t\t@update:modelValue=\"emitValue($event)\" />\n\t\t<NcTextField\n\t\t\tv-else-if=\"kind === 'secret'\"\n\t\t\t:id=\"inputId\"\n\t\t\t:label=\"label\"\n\t\t\ttype=\"password\"\n\t\t\tautocomplete=\"new-password\"\n\t\t\t:modelValue=\"stringValue\"\n\t\t\t:placeholder=\"field.secretSet ? secretSetPlaceholder : (field.placeholder || '')\"\n\t\t\t:readonly=\"field.readOnly\"\n\t\t\t:error=\"!!error\"\n\t\t\t:aria-describedby=\"describedBy\"\n\t\t\t@update:modelValue=\"emitValue($event)\" />\n\t\t<NcTextField\n\t\t\tv-else-if=\"kind === 'number'\"\n\t\t\t:id=\"inputId\"\n\t\t\t:label=\"label\"\n\t\t\ttype=\"number\"\n\t\t\t:min=\"field.min\"\n\t\t\t:max=\"field.max\"\n\t\t\t:modelValue=\"stringValue\"\n\t\t\t:readonly=\"field.readOnly\"\n\t\t\t:error=\"!!error\"\n\t\t\t:aria-describedby=\"describedBy\"\n\t\t\t@update:modelValue=\"emitValue($event === '' ? null : Number($event))\" />\n\t\t<NcTextField\n\t\t\tv-else\n\t\t\t:id=\"inputId\"\n\t\t\t:label=\"label\"\n\t\t\t:type=\"kind === 'password' ? 'password' : 'text'\"\n\t\t\t:modelValue=\"stringValue\"\n\t\t\t:placeholder=\"field.placeholder || ''\"\n\t\t\t:readonly=\"field.readOnly\"\n\t\t\t:error=\"!!error\"\n\t\t\t:aria-describedby=\"describedBy\"\n\t\t\t@update:modelValue=\"emitValue($event)\" />\n\t\t<p v-if=\"shownError\"\n\t\t\t:id=\"`${inputId}-error`\"\n\t\t\tclass=\"cn-settings-field__error\"\n\t\t\t:data-testid=\"`cn-settings-field-error-${field.key}`\">\n\t\t\t{{ shownError }}\n\t\t</p>\n\t\t<p v-else-if=\"hint\" :id=\"`${inputId}-hint`\" class=\"cn-settings-field__hint\">\n\t\t\t{{ hint }}\n\t\t</p>\n\t</div>\n</template>\n\n<script>\nimport axios from '@nextcloud/axios'\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcButton, NcCheckboxRadioSwitch, NcLoadingIcon, NcSelect, NcTextArea, NcTextField } from '@nextcloud/vue'\nimport { buildHeaders, prefixUrl } from '../../utils/headers.js'\nimport { readPath } from '../../utils/readPath.js'\nimport { serverErrorMessage } from '../../utils/serverErrorMessage.js'\n\nlet fieldUid = 0\n\n/** Field types and the names CnSettingsPage's manifest uses for them. */\nconst KIND_ALIASES = { enum: 'select', string: 'text' }\n\n/**\n * CnSettingsField - one settings field: the control for its type with its\n * label, and under it an error or a hint. Shared by CnSettingsPage and\n * CnSettingsFieldsSection.\n *\n * Types: `text` (default), `textarea`, `number`, `switch`, `boolean` (a checkbox),\n * `select` (`enum`) with static `options` or options read from `optionsUrl`,\n * `password` (a masked input that shows its value back) and `secret`\n * (replace-only: the input stays empty, and with `secretSet` says a value is\n * stored; the host sends it only when something is typed), and `image` (a\n * preview tile, the initials without an image, and an upload button that\n * posts the file to `uploadUrl` at once and emits `uploaded`).\n *\n * ```vue\n * <CnSettingsField\n * :field=\"{ key: 'apiKey', label: 'API key', type: 'secret', secretSet: true }\"\n * v-model=\"form.apiKey\" />\n * ```\n */\nexport default {\n\tname: 'CnSettingsField',\n\n\tcomponents: { NcButton, NcCheckboxRadioSwitch, NcLoadingIcon, NcSelect, NcTextArea, NcTextField },\n\n\tprops: {\n\t\t/**\n\t\t * The field: `{ key, label, type?, options?, optionsUrl?, optionsPath?,\n\t\t * optionLabel?, optionValue?, placeholder?, rows?, min?, max?, required?,\n\t\t * readOnly?, secretSet? }`. `label` arrives translated.\n\t\t *\n\t\t * @type {object}\n\t\t */\n\t\tfield: {\n\t\t\ttype: Object,\n\t\t\trequired: true,\n\t\t},\n\n\t\t/**\n\t\t * The value: text, a number, a boolean or a select value. Any type,\n\t\t * because a Boolean in a type list would read an empty string as true.\n\t\t */\n\t\tmodelValue: {\n\t\t\ttype: null,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** A validation message, drawn under the control and tied to it. */\n\t\terror: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** A hint under the control (not drawn while there is an error). */\n\t\thint: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\t},\n\n\temits: ['update:modelValue', 'uploaded'],\n\n\tdata() {\n\t\tfieldUid += 1\n\t\treturn {\n\t\t\tuid: fieldUid,\n\t\t\tfetchedOptions: null,\n\t\t\toptionsLoading: false,\n\t\t\tuploading: false,\n\t\t\tuploadError: '',\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/** @return {string} The control kind. */\n\t\tkind() {\n\t\t\tconst type = String(this.field.type || 'text')\n\t\t\treturn KIND_ALIASES[type] || type\n\t\t},\n\n\t\t/** @return {string} The label. */\n\t\tlabel() {\n\t\t\treturn String(this.field.label || this.field.key || '')\n\t\t},\n\n\t\t/** @return {string} The control's id. */\n\t\tinputId() {\n\t\t\treturn `cn-settings-field-${this.uid}-${String(this.field.key).replace(/[^A-Za-z0-9_-]/g, '-')}`\n\t\t},\n\n\t\t/** @return {string} The message shown under the control: an upload failure, else `error`. */\n\t\tshownError() {\n\t\t\treturn this.uploadError || this.error\n\t\t},\n\n\t\t/** @return {string|null} The id of the error or hint the control is described by. */\n\t\tdescribedBy() {\n\t\t\tif (this.shownError) {\n\t\t\t\treturn `${this.inputId}-error`\n\t\t\t}\n\t\t\treturn this.hint ? `${this.inputId}-hint` : null\n\t\t},\n\n\t\t/** @return {string} The value as text for a text control. */\n\t\tstringValue() {\n\t\t\treturn this.modelValue === null || this.modelValue === undefined ? '' : String(this.modelValue)\n\t\t},\n\n\t\t/** @return {string} The placeholder of a secret that is stored. */\n\t\tsecretSetPlaceholder() {\n\t\t\treturn t('nextcloud-vue', 'Stored. Type a new value to replace it.')\n\t\t},\n\n\t\t/**\n\t\t * The select options as `{ label, value }`: the static `options` (strings,\n\t\t * numbers or objects), or those read from `optionsUrl`.\n\t\t *\n\t\t * @return {Array<{label: string, value: (string|number|boolean)}>}\n\t\t */\n\t\tnormalizedOptions() {\n\t\t\tconst raw = Array.isArray(this.field.options) ? this.field.options : (this.fetchedOptions || [])\n\t\t\treturn raw.map((option) => (option && typeof option === 'object'\n\t\t\t\t? { ...option, label: String(option.label ?? option.value ?? ''), value: option.value }\n\t\t\t\t: { label: String(option), value: option }))\n\t\t},\n\n\t\t/** @return {object|null} The option the value selects, for NcSelect. */\n\t\tselectedOption() {\n\t\t\tif (this.modelValue === null || this.modelValue === undefined || this.modelValue === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn this.normalizedOptions.find((option) => option.value === this.modelValue)\n\t\t\t\t|| { label: String(this.modelValue), value: this.modelValue }\n\t\t},\n\t},\n\n\twatch: {\n\t\t'field.optionsUrl': {\n\t\t\thandler() {\n\t\t\t\tthis.loadOptions()\n\t\t\t},\n\n\t\t\timmediate: true,\n\t\t},\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/**\n\t\t * Upload a chosen image to `uploadUrl` as multipart form data (the\n\t\t * file under `uploadField`, default `image`, plus `uploadParams`), then\n\t\t * take the new image url from the answer at `uploadValuePath` (or a\n\t\t * cache-busted `previewUrl`).\n\t\t *\n\t\t * @param {File|undefined} file The chosen file.\n\t\t * @return {Promise<void>}\n\t\t * @spec openspec/changes/settings-fields-section/specs/settings-fields-section/spec.md#requirement-an-image-field-uploads-at-once\n\t\t */\n\t\tasync upload(file) {\n\t\t\tif (!file || !this.field.uploadUrl) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst form = new FormData()\n\t\t\tfor (const [name, value] of Object.entries(this.field.uploadParams || {})) {\n\t\t\t\tform.append(name, value)\n\t\t\t}\n\t\t\tform.append(this.field.uploadField || 'image', file)\n\t\t\tthis.uploading = true\n\t\t\tthis.uploadError = ''\n\t\t\ttry {\n\t\t\t\tconst response = await axios.post(prefixUrl(this.field.uploadUrl), form)\n\t\t\t\tconst fromAnswer = this.field.uploadValuePath ? readPath(response?.data, this.field.uploadValuePath) : ''\n\t\t\t\tconst next = fromAnswer || (this.field.previewUrl ? `${prefixUrl(this.field.previewUrl)}${this.field.previewUrl.includes('?') ? '&' : '?'}v=${Date.now()}` : '')\n\t\t\t\t/**\n\t\t\t\t * @event uploaded An image was uploaded. Payload: its new url.\n\t\t\t\t */\n\t\t\t\tthis.$emit('uploaded', next)\n\t\t\t} catch (error) {\n\t\t\t\tthis.uploadError = t('nextcloud-vue', 'The image could not be uploaded: {reason}', { reason: serverErrorMessage(error) })\n\t\t\t} finally {\n\t\t\t\tthis.uploading = false\n\t\t\t\tif (this.$refs.file) {\n\t\t\t\t\tthis.$refs.file.value = ''\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Report a new value.\n\t\t *\n\t\t * @param {unknown} value The value.\n\t\t * @return {void}\n\t\t */\n\t\temitValue(value) {\n\t\t\t/**\n\t\t\t * @event update:modelValue The value changed. Payload: the new value.\n\t\t\t */\n\t\t\tthis.$emit('update:modelValue', value)\n\t\t},\n\n\t\t/**\n\t\t * The storable value of what NcSelect emits.\n\t\t *\n\t\t * @param {object|string|number|null} emitted An option, or null on clear.\n\t\t * @return {string|number|boolean|null}\n\t\t */\n\t\toptionValue(emitted) {\n\t\t\tif (emitted === null || emitted === undefined) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn typeof emitted === 'object' && 'value' in emitted ? emitted.value : emitted\n\t\t},\n\n\t\t/**\n\t\t * Read a sourced select's options: a list (or the list at\n\t\t * `optionsPath`, or under `results`) mapped through `optionLabel` and\n\t\t * `optionValue` (dotted paths; default `label`/`title`/`name` and `value`/`id`).\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync loadOptions() {\n\t\t\tconst url = this.kind === 'select' && !Array.isArray(this.field.options) ? this.field.optionsUrl : ''\n\t\t\tif (!url) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.optionsLoading = true\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(url), { headers: buildHeaders() })\n\t\t\t\tif (!response.ok) {\n\t\t\t\t\tthis.fetchedOptions = []\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tconst data = await response.json()\n\t\t\t\tconst list = this.field.optionsPath ? readPath(data, this.field.optionsPath) : (Array.isArray(data) ? data : data?.results)\n\t\t\t\tthis.fetchedOptions = (Array.isArray(list) ? list : []).map((item) => {\n\t\t\t\t\tif (!item || typeof item !== 'object') {\n\t\t\t\t\t\treturn { label: String(item), value: item }\n\t\t\t\t\t}\n\t\t\t\t\tconst value = this.field.optionValue ? readPath(item, this.field.optionValue) : (item.value ?? item.id)\n\t\t\t\t\tconst label = this.field.optionLabel ? readPath(item, this.field.optionLabel) : (item.label ?? item.title ?? item.name ?? value)\n\t\t\t\t\treturn { label: String(label ?? ''), value }\n\t\t\t\t})\n\t\t\t} catch {\n\t\t\t\tthis.fetchedOptions = []\n\t\t\t} finally {\n\t\t\t\tthis.optionsLoading = false\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-settings-field {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n\tmin-width: 0;\n}\n\n.cn-settings-field__hint,\n.cn-settings-field__error {\n\tmargin: 0;\n\tfont-size: 13px;\n\tline-height: 1.4;\n}\n\n.cn-settings-field__image {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 6px;\n}\n\n.cn-settings-field__image-label {\n\tfont-weight: 600;\n}\n\n.cn-settings-field__image-row {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tgap: 12px;\n}\n\n.cn-settings-field__image-tile {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tjustify-content: center;\n\twidth: 64px;\n\theight: 64px;\n\toverflow: hidden;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius-large);\n\tbackground: var(--color-primary-element-light);\n}\n\n.cn-settings-field__image-tile img {\n\tmax-width: 100%;\n\tmax-height: 100%;\n\tobject-fit: contain;\n}\n\n.cn-settings-field__image-initials {\n\tfont-size: 20px;\n\tfont-weight: 700;\n\tcolor: var(--color-primary-element-light-text, var(--color-primary-element));\n}\n\n.cn-settings-field__hint {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-settings-field__error {\n\tcolor: var(--color-error-text, var(--color-error));\n}\n</style>\n"],"names":["_createBlock","_createElementBlock","_createElementVNode","_openBlock","_toDisplayString","_createTextVNode","_createVNode"],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import script from './CnSettingsFieldsSection.vue2.js';
2
+ import { render } from './CnSettingsFieldsSection.vue3.js';
3
+
4
+ script.render = render;
5
+ script.__scopeId = "data-v-65c151a8";
6
+ const __sfc_main = script;
7
+
8
+ export { __sfc_main as default };
9
+ //# sourceMappingURL=CnSettingsFieldsSection.vue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CnSettingsFieldsSection.vue.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;"}