@conduction/nextcloud-vue 2.117.0 → 2.118.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/CnActionDialog/CnActionDialog.vue2.js +2 -1
- package/dist/esm/components/CnActionDialog/CnActionDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js.map +1 -1
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js +6 -6
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js.map +1 -1
- package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue2.js +2 -1
- package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +18 -0
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +14 -8
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue.js +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js +11 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +9 -4
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnMailTab/CnMailTab.vue2.js +2 -1
- package/dist/esm/components/CnMailTab/CnMailTab.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue.js +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +71 -0
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js +25 -9
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +11 -2
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnSearchPage/CnSearchPage.vue.js +1 -1
- package/dist/esm/components/CnSearchPage/CnSearchPage.vue2.js +161 -5
- package/dist/esm/components/CnSearchPage/CnSearchPage.vue2.js.map +1 -1
- package/dist/esm/components/CnSearchPage/CnSearchPage.vue3.js +9 -9
- package/dist/esm/components/CnSearchPage/CnSearchPage.vue3.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue.js +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +35 -5
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnTransitionInputFields/CnTransitionInputFields.vue.js +9 -0
- package/dist/esm/components/CnTransitionInputFields/CnTransitionInputFields.vue.js.map +1 -0
- package/dist/esm/components/CnTransitionInputFields/CnTransitionInputFields.vue2.js +435 -0
- package/dist/esm/components/CnTransitionInputFields/CnTransitionInputFields.vue2.js.map +1 -0
- package/dist/esm/components/CnTransitionInputFields/CnTransitionInputFields.vue3.js +300 -0
- package/dist/esm/components/CnTransitionInputFields/CnTransitionInputFields.vue3.js.map +1 -0
- package/dist/esm/composables/useEndpointSource.js +1 -36
- package/dist/esm/composables/useEndpointSource.js.map +1 -1
- package/dist/esm/dialogs/CnTransitionInputDialog.vue.js +1 -1
- package/dist/esm/dialogs/CnTransitionInputDialog.vue2.js +57 -336
- package/dist/esm/dialogs/CnTransitionInputDialog.vue2.js.map +1 -1
- package/dist/esm/dialogs/CnTransitionInputDialog.vue3.js +18 -155
- package/dist/esm/dialogs/CnTransitionInputDialog.vue3.js.map +1 -1
- package/dist/esm/index.js +2 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/l10n/en.json.js +2 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +2 -1
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/library-style.js +1 -1
- package/dist/esm/nextcloud-vue.css +308 -229
- package/dist/esm/utils/actionsDispatcher.js +3 -2
- package/dist/esm/utils/actionsDispatcher.js.map +1 -1
- package/dist/esm/utils/interpolateUrlTokens.js +43 -0
- package/dist/esm/utils/interpolateUrlTokens.js.map +1 -0
- package/dist/esm/utils/labelsByField.js +72 -0
- package/dist/esm/utils/labelsByField.js.map +1 -0
- package/dist/esm/utils/listCreateAction.js +91 -0
- package/dist/esm/utils/listCreateAction.js.map +1 -0
- package/dist/esm/utils/transitionInputs.js +493 -0
- package/dist/esm/utils/transitionInputs.js.map +1 -0
- package/dist/esm/utils/visibleWhen.js +9 -2
- package/dist/esm/utils/visibleWhen.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +6690 -5404
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +308 -229
- package/l10n/en.json +2 -1
- package/l10n/nl.json +2 -1
- package/package.json +1 -1
- package/src/components/CnActionsBar/CnActionsBar.vue +6 -6
- package/src/components/CnDashboardPage/CnDashboardPage.vue +27 -5
- package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +11 -1
- package/src/components/CnIndexPage/CnIndexPage.vue +9 -4
- package/src/components/CnObjectListWidget/CnObjectListWidget.vue +86 -0
- package/src/components/CnRecordListWidget/CnRecordListWidget.vue +11 -2
- package/src/components/CnSearchPage/CnSearchPage.vue +170 -14
- package/src/components/CnTabsWidget/CnTabsWidget.vue +35 -5
- package/src/components/CnTransitionInputFields/CnTransitionInputFields.vue +750 -0
- package/src/components/CnTransitionInputFields/index.js +11 -0
- package/src/components/index.js +1 -0
- package/src/composables/useEndpointSource.js +3 -36
- package/src/dialogs/CnTransitionInputDialog.vue +73 -465
- package/src/index.js +2 -0
- package/src/utils/interpolateUrlTokens.js +40 -0
- package/src/utils/labelsByField.js +69 -0
- package/src/utils/listCreateAction.js +87 -0
- package/src/utils/transitionInputs.js +489 -0
- package/src/utils/visibleWhen.js +9 -2
|
@@ -25,112 +25,19 @@
|
|
|
25
25
|
data-testid="cn-transition-input-error">
|
|
26
26
|
{{ error }}
|
|
27
27
|
</p>
|
|
28
|
-
<
|
|
29
|
-
:
|
|
30
|
-
|
|
31
|
-
:
|
|
32
|
-
:
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
@update:modelValue="setValue(field.key, $event && $event.value !== undefined ? $event.value : '')" />
|
|
42
|
-
|
|
43
|
-
<!-- An input that points at another record is picked, never typed. -->
|
|
44
|
-
<div v-else-if="field.widget === 'reference'" class="cn-transition-input__reference">
|
|
45
|
-
<label class="cn-transition-input__label">{{ requiredLabel(field) }}</label>
|
|
46
|
-
<CnResourceSelect
|
|
47
|
-
:register="field.referenceRegister"
|
|
48
|
-
:schema="String(field.reference.schema)"
|
|
49
|
-
:labelField="(field.picker && field.picker.labelField) || field.reference.labelField || 'name'"
|
|
50
|
-
:filter="(field.picker && field.picker.filter) || {}"
|
|
51
|
-
:exclude="excludedIds(field)"
|
|
52
|
-
:allowCreate="false"
|
|
53
|
-
:preload="true"
|
|
54
|
-
:inputLabel="requiredLabel(field)"
|
|
55
|
-
:modelValue="String(values[field.key] ?? '')"
|
|
56
|
-
:data-testid="`cn-transition-input-picker-${field.key}`"
|
|
57
|
-
@update:modelValue="setValue(field.key, $event)" />
|
|
58
|
-
</div>
|
|
59
|
-
|
|
60
|
-
<!-- An object input: its sub-properties, one field at a time. -->
|
|
61
|
-
<fieldset v-else-if="field.widget === 'object'" class="cn-transition-input__object">
|
|
62
|
-
<legend>{{ requiredLabel(field) }}</legend>
|
|
63
|
-
<div v-for="sub in field.subFields"
|
|
64
|
-
:key="sub.key"
|
|
65
|
-
class="cn-transition-input__subfield"
|
|
66
|
-
:data-testid="`cn-transition-input-${field.key}-${sub.key}`">
|
|
67
|
-
<NcTextArea v-if="sub.widget === 'textarea'"
|
|
68
|
-
:modelValue="String(subValue(field.key, sub.key) ?? '')"
|
|
69
|
-
:label="subLabel(sub)"
|
|
70
|
-
rows="3"
|
|
71
|
-
@update:modelValue="setSubValue(field.key, sub.key, $event)" />
|
|
72
|
-
<NcCheckboxRadioSwitch v-else-if="sub.widget === 'checkbox'"
|
|
73
|
-
:modelValue="subValue(field.key, sub.key) === true"
|
|
74
|
-
type="switch"
|
|
75
|
-
@update:modelValue="setSubValue(field.key, sub.key, $event === true)">
|
|
76
|
-
{{ subLabel(sub) }}
|
|
77
|
-
</NcCheckboxRadioSwitch>
|
|
78
|
-
<NcTextField v-else
|
|
79
|
-
:modelValue="String(subValue(field.key, sub.key) ?? '')"
|
|
80
|
-
:type="sub.widget === 'number' ? 'number' : 'text'"
|
|
81
|
-
:label="subLabel(sub)"
|
|
82
|
-
@update:modelValue="setSubValue(field.key, sub.key, $event)" />
|
|
83
|
-
<p v-if="isSubRefused(field.key, sub.key)"
|
|
84
|
-
class="cn-transition-input__field-error"
|
|
85
|
-
:data-testid="`cn-transition-input-error-${field.key}.${sub.key}`">
|
|
86
|
-
{{ t('nextcloud-vue', 'This value was not accepted.') }}
|
|
87
|
-
</p>
|
|
88
|
-
</div>
|
|
89
|
-
</fieldset>
|
|
90
|
-
|
|
91
|
-
<NcCheckboxRadioSwitch v-else-if="field.widget === 'checkbox'"
|
|
92
|
-
:modelValue="values[field.key] === true"
|
|
93
|
-
type="switch"
|
|
94
|
-
@update:modelValue="setValue(field.key, $event === true)">
|
|
95
|
-
{{ requiredLabel(field) }}
|
|
96
|
-
</NcCheckboxRadioSwitch>
|
|
97
|
-
|
|
98
|
-
<NcTextArea v-else-if="field.widget === 'textarea'"
|
|
99
|
-
:modelValue="String(values[field.key] ?? '')"
|
|
100
|
-
:label="requiredLabel(field)"
|
|
101
|
-
:helperText="field.description || ''"
|
|
102
|
-
rows="4"
|
|
103
|
-
@update:modelValue="setValue(field.key, $event)" />
|
|
104
|
-
|
|
105
|
-
<NcTextField v-else-if="field.widget === 'number'"
|
|
106
|
-
:modelValue="String(values[field.key] ?? '')"
|
|
107
|
-
type="number"
|
|
108
|
-
:label="requiredLabel(field)"
|
|
109
|
-
:helperText="field.description || ''"
|
|
110
|
-
@update:modelValue="setValue(field.key, $event)" />
|
|
111
|
-
|
|
112
|
-
<NcTextField v-else
|
|
113
|
-
:modelValue="String(values[field.key] ?? '')"
|
|
114
|
-
:label="requiredLabel(field)"
|
|
115
|
-
:helperText="field.description || ''"
|
|
116
|
-
@update:modelValue="setValue(field.key, $event)" />
|
|
117
|
-
|
|
118
|
-
<p v-if="isRefused(field.key)"
|
|
119
|
-
class="cn-transition-input__field-error"
|
|
120
|
-
:data-testid="`cn-transition-input-error-${field.key}`">
|
|
121
|
-
{{ refusalFor(field.key) }}
|
|
122
|
-
</p>
|
|
123
|
-
</div>
|
|
124
|
-
|
|
125
|
-
<!-- A key the refusal names that this dialog never offered is not a
|
|
126
|
-
field anybody can correct here. Saying so beats saying nothing:
|
|
127
|
-
the request sent something the transition does not accept. -->
|
|
128
|
-
<p v-for="key in unofferedRefusals"
|
|
129
|
-
:key="key"
|
|
130
|
-
class="cn-transition-input__field-error"
|
|
131
|
-
:data-testid="`cn-transition-input-error-${key}`">
|
|
132
|
-
{{ undeclaredMessage(key) }}
|
|
133
|
-
</p>
|
|
28
|
+
<CnTransitionInputFields
|
|
29
|
+
:inputs="transition.inputs || []"
|
|
30
|
+
:modelValue="values"
|
|
31
|
+
:schema="schema"
|
|
32
|
+
:currentObject="currentObject"
|
|
33
|
+
:register="register"
|
|
34
|
+
:context="context"
|
|
35
|
+
:fieldErrors="fieldErrors"
|
|
36
|
+
:disabled="busy"
|
|
37
|
+
:columns="transition.columns === 2 ? 2 : 1"
|
|
38
|
+
:summary="transition.summary || null"
|
|
39
|
+
:notice="transition.notice || null"
|
|
40
|
+
@update:modelValue="values = $event" />
|
|
134
41
|
</div>
|
|
135
42
|
|
|
136
43
|
<template #actions>
|
|
@@ -159,11 +66,16 @@
|
|
|
159
66
|
|
|
160
67
|
<script>
|
|
161
68
|
import { translate as t } from '@nextcloud/l10n'
|
|
162
|
-
import { NcButton,
|
|
69
|
+
import { NcButton, NcLoadingIcon } from '@nextcloud/vue'
|
|
163
70
|
import CnDialog from '../components/CnDialog/CnDialog.vue'
|
|
164
|
-
import
|
|
71
|
+
import CnTransitionInputFields from '../components/CnTransitionInputFields/CnTransitionInputFields.vue'
|
|
165
72
|
import { dialogBoardMixin } from '../mixins/dialogBoard.js'
|
|
166
|
-
import {
|
|
73
|
+
import {
|
|
74
|
+
initialInputValues,
|
|
75
|
+
inputFieldsComplete,
|
|
76
|
+
resolveInputFields,
|
|
77
|
+
submittedValue,
|
|
78
|
+
} from '../utils/transitionInputs.js'
|
|
167
79
|
|
|
168
80
|
/**
|
|
169
81
|
* CnTransitionInputDialog — collects a lifecycle transition's declared
|
|
@@ -172,25 +84,27 @@ import { fieldsFromSchema } from '../utils/schema.js'
|
|
|
172
84
|
* A schema's `x-openregister-lifecycle.transitions.<action>` block may declare
|
|
173
85
|
* `inputs: [{ field, required }]`; the transition endpoint then accepts
|
|
174
86
|
* `{ action, data: { <field>: <value> } }`. This dialog renders one input per
|
|
175
|
-
* declared field, disables the confirm button until every
|
|
176
|
-
* field is filled, and emits the collected values — the
|
|
177
|
-
* (`CnLifecycleActions`) performs the actual POST. Cancel emits `close`
|
|
87
|
+
* declared field, disables the confirm button until every shown
|
|
88
|
+
* `required: true` field is filled, and emits the collected values — the
|
|
89
|
+
* parent (`CnLifecycleActions`) performs the actual POST. Cancel emits `close`
|
|
178
90
|
* without any confirm, so no request is made.
|
|
179
91
|
*
|
|
180
|
-
*
|
|
181
|
-
*
|
|
182
|
-
*
|
|
183
|
-
*
|
|
184
|
-
*
|
|
185
|
-
*
|
|
92
|
+
* The inputs are drawn by `CnTransitionInputFields`, which a host can also
|
|
93
|
+
* use on its own to open an act's inputs in place. Field rendering resolves
|
|
94
|
+
* each declared field against the object's JSON Schema via
|
|
95
|
+
* `fieldsFromSchema()` when a `schema` is given: the label comes from the
|
|
96
|
+
* property `title`, booleans render as a switch, numbers as a number field,
|
|
97
|
+
* and long text (`maxLength > 255` or a `textarea`/`markdown` format) as a
|
|
98
|
+
* textarea. Anything else — including a field the schema does not declare —
|
|
99
|
+
* falls back to a plain labelled text input.
|
|
186
100
|
*
|
|
187
101
|
* An input may also say how it is asked for, which matters when the choices
|
|
188
102
|
* depend on the record rather than on its schema: `label` names the field,
|
|
189
|
-
*
|
|
190
|
-
*
|
|
191
|
-
*
|
|
192
|
-
*
|
|
193
|
-
*
|
|
103
|
+
* `options: [{ value, label }]` or `optionsSource` (a path into `context`)
|
|
104
|
+
* turn it into a picker, and `widget` names `text`, `textarea`, `number`,
|
|
105
|
+
* `date` or `template` outright. `other`, `helperFrom`, `preview` and
|
|
106
|
+
* `default` are described in `utils/transitionInputs.js`. The transition may
|
|
107
|
+
* carry `columns`, `summary` and `notice` for the layout around the inputs.
|
|
194
108
|
*
|
|
195
109
|
* Lives in its own file under `src/dialogs/` per the modal-isolation rule.
|
|
196
110
|
*
|
|
@@ -207,14 +121,10 @@ export default {
|
|
|
207
121
|
name: 'CnTransitionInputDialog',
|
|
208
122
|
|
|
209
123
|
components: {
|
|
210
|
-
CnResourceSelect,
|
|
211
124
|
CnDialog,
|
|
125
|
+
CnTransitionInputFields,
|
|
212
126
|
NcButton,
|
|
213
127
|
NcLoadingIcon,
|
|
214
|
-
NcTextField,
|
|
215
|
-
NcTextArea,
|
|
216
|
-
NcCheckboxRadioSwitch,
|
|
217
|
-
NcSelect,
|
|
218
128
|
},
|
|
219
129
|
|
|
220
130
|
mixins: [dialogBoardMixin],
|
|
@@ -224,12 +134,14 @@ export default {
|
|
|
224
134
|
* The transition being applied. `inputs` is the declared input list from
|
|
225
135
|
* `x-openregister-lifecycle.transitions.<action>.inputs` (server-derived
|
|
226
136
|
* or config-declared); `label` doubles as dialog title + confirm label.
|
|
137
|
+
* `columns`, `summary` and `notice` lay out the inputs (see
|
|
138
|
+
* `CnTransitionInputFields`).
|
|
227
139
|
*
|
|
228
140
|
* An input may carry a `label` for its field and `options` to pick
|
|
229
141
|
* from, each `{ value, label }`; the picker shows the label and sends
|
|
230
142
|
* the value.
|
|
231
143
|
*
|
|
232
|
-
* @type {{action?: string, label?: string, inputs?: Array<{field: string, required?: boolean, label?: string, options?: Array<{value: string, label?: string}
|
|
144
|
+
* @type {{action?: string, label?: string, columns?: number, summary?: object, notice?: object, inputs?: Array<{field: string, required?: boolean, label?: string, widget?: string, options?: Array<{value: string, label?: string}>, optionsSource?: string}>}}
|
|
233
145
|
*/
|
|
234
146
|
transition: {
|
|
235
147
|
type: Object,
|
|
@@ -270,6 +182,16 @@ export default {
|
|
|
270
182
|
default: '',
|
|
271
183
|
},
|
|
272
184
|
|
|
185
|
+
/**
|
|
186
|
+
* What an input's `optionsSource` and the summary paths read.
|
|
187
|
+
*
|
|
188
|
+
* @type {object}
|
|
189
|
+
*/
|
|
190
|
+
context: {
|
|
191
|
+
type: Object,
|
|
192
|
+
default: () => ({}),
|
|
193
|
+
},
|
|
194
|
+
|
|
273
195
|
/**
|
|
274
196
|
* The sentence a refused transition answered with. Present means the
|
|
275
197
|
* dialog stayed open BECAUSE the attempt was refused, with everything
|
|
@@ -311,66 +233,18 @@ export default {
|
|
|
311
233
|
data() {
|
|
312
234
|
return {
|
|
313
235
|
/** @type {{[key: string]: unknown}} Collected input values, keyed by declared field name. */
|
|
314
|
-
values: this.
|
|
236
|
+
values: initialInputValues(this.transition?.inputs, this.schema, this.currentObject),
|
|
315
237
|
}
|
|
316
238
|
},
|
|
317
239
|
|
|
318
240
|
computed: {
|
|
319
|
-
/**
|
|
320
|
-
inputs() {
|
|
321
|
-
const declared = Array.isArray(this.transition.inputs) ? this.transition.inputs : []
|
|
322
|
-
return declared
|
|
323
|
-
.filter((input) => input && typeof input.field === 'string' && input.field !== '')
|
|
324
|
-
.map((input) => ({
|
|
325
|
-
field: input.field,
|
|
326
|
-
required: input.required === true,
|
|
327
|
-
picker: input.picker && typeof input.picker === 'object' ? input.picker : null,
|
|
328
|
-
fields: Array.isArray(input.fields) ? input.fields.filter((f) => typeof f === 'string') : null,
|
|
329
|
-
label: typeof input.label === 'string' && input.label.trim() !== '' ? input.label.trim() : null,
|
|
330
|
-
options: this.declaredOptions(input.options),
|
|
331
|
-
}))
|
|
332
|
-
},
|
|
333
|
-
|
|
334
|
-
/**
|
|
335
|
-
* One renderable field descriptor per declared input. Schema-declared
|
|
336
|
-
* fields come from `fieldsFromSchema()` (label from the property title,
|
|
337
|
-
* widget from the type/format); the widget is then clamped to the minimal
|
|
338
|
-
* set this dialog renders (text / textarea / number / checkbox). Fields
|
|
339
|
-
* the schema does not declare fall back to a plain text input.
|
|
340
|
-
*
|
|
341
|
-
* An input that declares `options` is a picker whatever the schema
|
|
342
|
-
* says, because its choices are the ones this transition offers on
|
|
343
|
-
* this record. A declared `label` names the field over the schema's.
|
|
344
|
-
*/
|
|
241
|
+
/** One field descriptor per declared input (see `resolveInputFields`). */
|
|
345
242
|
fields() {
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
return {
|
|
352
|
-
key: input.field,
|
|
353
|
-
label: input.label || (field && field.label) || input.field,
|
|
354
|
-
description: (field && field.description) || '',
|
|
355
|
-
widget: 'select',
|
|
356
|
-
options: input.options,
|
|
357
|
-
required: input.required,
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
const prop = (this.schema && this.schema.properties && this.schema.properties[input.field]) || null
|
|
361
|
-
if (prop && prop.type === 'object' && prop.properties && typeof prop.properties === 'object') {
|
|
362
|
-
return this.objectField(input, prop)
|
|
363
|
-
}
|
|
364
|
-
if (!field) {
|
|
365
|
-
return { key: input.field, label: input.label || input.field, description: '', widget: 'text', required: input.required }
|
|
366
|
-
}
|
|
367
|
-
if (field.reference && field.reference.multiple !== true) {
|
|
368
|
-
const register = field.reference.register || this.register
|
|
369
|
-
if (register !== '') {
|
|
370
|
-
return { ...field, label: input.label || field.label, widget: 'reference', referenceRegister: register, picker: input.picker, required: input.required }
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
return { ...field, label: input.label || field.label, widget: this.clampWidget(field.widget), required: input.required }
|
|
243
|
+
return resolveInputFields(this.transition.inputs, {
|
|
244
|
+
schema: this.schema,
|
|
245
|
+
register: this.register,
|
|
246
|
+
context: this.context,
|
|
247
|
+
values: this.values,
|
|
374
248
|
})
|
|
375
249
|
},
|
|
376
250
|
|
|
@@ -384,167 +258,27 @@ export default {
|
|
|
384
258
|
return this.transition.label || t('nextcloud-vue', 'Confirm')
|
|
385
259
|
},
|
|
386
260
|
|
|
387
|
-
/** True when every `required: true` input holds a
|
|
261
|
+
/** True when every shown `required: true` input holds a value. */
|
|
388
262
|
canConfirm() {
|
|
389
|
-
|
|
390
|
-
return false
|
|
391
|
-
}
|
|
392
|
-
return this.fields.every((field) => (field.widget === 'object'
|
|
393
|
-
? field.subFields.every((sub) => !sub.required || this.isSubFilled(field.key, sub))
|
|
394
|
-
: (!field.required || this.isFilled(field))))
|
|
395
|
-
},
|
|
396
|
-
|
|
397
|
-
/** The refused keys this dialog offers no field for. */
|
|
398
|
-
unofferedRefusals() {
|
|
399
|
-
const offered = this.fields.map((field) => field.key)
|
|
400
|
-
return this.refusedKeys.filter((key) => !offered.includes(key))
|
|
401
|
-
},
|
|
402
|
-
|
|
403
|
-
/** The refusal's field keys, normalised to strings. */
|
|
404
|
-
refusedKeys() {
|
|
405
|
-
if (!Array.isArray(this.fieldErrors)) {
|
|
406
|
-
return []
|
|
407
|
-
}
|
|
408
|
-
return this.fieldErrors.filter((key) => typeof key === 'string' && key !== '')
|
|
263
|
+
return this.busy !== true && inputFieldsComplete(this.fields, this.values)
|
|
409
264
|
},
|
|
410
265
|
},
|
|
411
266
|
|
|
412
267
|
methods: {
|
|
413
|
-
// Exposed to the template for the static button labels — the same
|
|
414
|
-
// `methods: { t }` pattern the other dialogs in this folder use.
|
|
415
268
|
t,
|
|
416
269
|
|
|
417
270
|
/**
|
|
418
|
-
*
|
|
419
|
-
*
|
|
420
|
-
* @param {string} key The field key.
|
|
421
|
-
* @return {boolean}
|
|
422
|
-
*/
|
|
423
|
-
isRefused(key) {
|
|
424
|
-
return this.refusedKeys.includes(key)
|
|
425
|
-
},
|
|
426
|
-
|
|
427
|
-
/**
|
|
428
|
-
* What to say beside a refused field.
|
|
429
|
-
*
|
|
430
|
-
* The KIND is decided here rather than read out of the refusal's
|
|
431
|
-
* sentence. A field this dialog offers and the person left empty is a
|
|
432
|
-
* missing required input; one it offers that carries a value was
|
|
433
|
-
* refused for what is in it. Parsing the server's prose to tell those
|
|
434
|
-
* apart would break the first time the sentence is reworded, and a
|
|
435
|
-
* reworded sentence is not a contract change.
|
|
436
|
-
*
|
|
437
|
-
* @param {string} key The field key.
|
|
438
|
-
* @return {string}
|
|
439
|
-
*/
|
|
440
|
-
refusalFor(key) {
|
|
441
|
-
const field = this.fields.find((candidate) => candidate.key === key)
|
|
442
|
-
if (field && !this.isFilled(field)) {
|
|
443
|
-
return t('nextcloud-vue', 'This field is required.')
|
|
444
|
-
}
|
|
445
|
-
return t('nextcloud-vue', 'This value was not accepted.')
|
|
446
|
-
},
|
|
447
|
-
|
|
448
|
-
/**
|
|
449
|
-
* What to say about a refused key this dialog does not offer.
|
|
450
|
-
*
|
|
451
|
-
* @param {string} key The field key.
|
|
452
|
-
* @return {string}
|
|
453
|
-
*/
|
|
454
|
-
undeclaredMessage(key) {
|
|
455
|
-
return t('nextcloud-vue', 'This action does not accept the field "{field}".', { field: key })
|
|
456
|
-
},
|
|
457
|
-
|
|
458
|
-
/**
|
|
459
|
-
* The descriptor of an object input: its sub-properties (narrowed to the
|
|
460
|
-
* input's `fields` when it names them), each with its own widget.
|
|
461
|
-
* A sub-field is required when the input names it in `fields` and the
|
|
462
|
-
* sub-schema lists it as required, or when the whole input is required
|
|
463
|
-
* and it is the only one asked.
|
|
464
|
-
*
|
|
465
|
-
* @param {object} input The normalised input.
|
|
466
|
-
* @param {object} prop The object property's schema.
|
|
467
|
-
* @return {object} The field descriptor.
|
|
468
|
-
*/
|
|
469
|
-
objectField(input, prop) {
|
|
470
|
-
const subs = fieldsFromSchema(prop, { includeReadOnly: true, ...(input.fields ? { include: input.fields } : {}) })
|
|
471
|
-
const required = Array.isArray(prop.required) ? prop.required : []
|
|
472
|
-
const only = subs.length === 1
|
|
473
|
-
return {
|
|
474
|
-
key: input.field,
|
|
475
|
-
label: prop.title || input.field,
|
|
476
|
-
description: prop.description || '',
|
|
477
|
-
widget: 'object',
|
|
478
|
-
required: input.required,
|
|
479
|
-
subFields: subs.map((sub) => ({
|
|
480
|
-
...sub,
|
|
481
|
-
widget: this.clampWidget(sub.widget),
|
|
482
|
-
required: (input.fields ? required.includes(sub.key) : sub.required === true) || (input.required && only),
|
|
483
|
-
})),
|
|
484
|
-
}
|
|
485
|
-
},
|
|
486
|
-
|
|
487
|
-
/**
|
|
488
|
-
* The choices an input declares, as `[{ value, label }]`, or null when
|
|
489
|
-
* it declares none. An option without a value is dropped, because it
|
|
490
|
-
* cannot be sent; one without a label shows its value.
|
|
491
|
-
*
|
|
492
|
-
* @param {unknown} options The input's declared `options`.
|
|
493
|
-
* @return {Array<{value: string, label: string}>|null}
|
|
494
|
-
*/
|
|
495
|
-
declaredOptions(options) {
|
|
496
|
-
if (!Array.isArray(options)) {
|
|
497
|
-
return null
|
|
498
|
-
}
|
|
499
|
-
const usable = options
|
|
500
|
-
.filter((option) => option && typeof option === 'object'
|
|
501
|
-
&& (typeof option.value === 'string' || typeof option.value === 'number')
|
|
502
|
-
&& String(option.value).trim() !== '')
|
|
503
|
-
.map((option) => ({
|
|
504
|
-
value: String(option.value),
|
|
505
|
-
label: typeof option.label === 'string' && option.label.trim() !== '' ? option.label.trim() : String(option.value),
|
|
506
|
-
}))
|
|
507
|
-
return usable.length > 0 ? usable : null
|
|
508
|
-
},
|
|
509
|
-
|
|
510
|
-
/**
|
|
511
|
-
* The option a picker field currently holds, or null.
|
|
512
|
-
*
|
|
513
|
-
* @param {object} field The select field descriptor.
|
|
514
|
-
* @return {{value: string, label: string}|null}
|
|
515
|
-
*/
|
|
516
|
-
selectedOption(field) {
|
|
517
|
-
const value = this.values[field.key]
|
|
518
|
-
return field.options.find((option) => option.value === String(value ?? '')) || null
|
|
519
|
-
},
|
|
520
|
-
|
|
521
|
-
/**
|
|
522
|
-
* Ids a reference picker must not offer: the current record, when the
|
|
523
|
-
* input's `picker.excludeSelf` says so.
|
|
524
|
-
*
|
|
525
|
-
* @param {object} field The reference field descriptor.
|
|
526
|
-
* @return {string[]} The ids to leave out.
|
|
527
|
-
*/
|
|
528
|
-
excludedIds(field) {
|
|
529
|
-
if (!field.picker || field.picker.excludeSelf !== true || !this.currentObject) {
|
|
530
|
-
return []
|
|
531
|
-
}
|
|
532
|
-
const self = this.currentObject
|
|
533
|
-
return [self.id, self.uuid, self['@self'] && self['@self'].id].filter((id) => id !== undefined && id !== null).map(String)
|
|
534
|
-
},
|
|
535
|
-
|
|
536
|
-
/**
|
|
537
|
-
* Label of a sub-field, with the required marker.
|
|
271
|
+
* Store one input's value.
|
|
538
272
|
*
|
|
539
|
-
* @param {
|
|
540
|
-
* @
|
|
273
|
+
* @param {string} key The declared field name.
|
|
274
|
+
* @param {unknown} value The new value.
|
|
541
275
|
*/
|
|
542
|
-
|
|
543
|
-
|
|
276
|
+
setValue(key, value) {
|
|
277
|
+
this.values = { ...this.values, [key]: value }
|
|
544
278
|
},
|
|
545
279
|
|
|
546
280
|
/**
|
|
547
|
-
* A typed sub-value.
|
|
281
|
+
* A typed sub-value of an object input.
|
|
548
282
|
*
|
|
549
283
|
* @param {string} key The input key.
|
|
550
284
|
* @param {string} sub The sub-field key.
|
|
@@ -567,110 +301,6 @@ export default {
|
|
|
567
301
|
this.setValue(key, { ...group, [sub]: value })
|
|
568
302
|
},
|
|
569
303
|
|
|
570
|
-
/**
|
|
571
|
-
* Whether a required sub-field holds a value.
|
|
572
|
-
*
|
|
573
|
-
* @param {string} key The input key.
|
|
574
|
-
* @param {object} sub The sub-field descriptor.
|
|
575
|
-
* @return {boolean}
|
|
576
|
-
*/
|
|
577
|
-
isSubFilled(key, sub) {
|
|
578
|
-
const value = this.subValue(key, sub.key)
|
|
579
|
-
return sub.widget === 'checkbox' ? value === true : String(value ?? '').trim() !== ''
|
|
580
|
-
},
|
|
581
|
-
|
|
582
|
-
/**
|
|
583
|
-
* Whether a refusal named `<input>.<sub>`.
|
|
584
|
-
*
|
|
585
|
-
* @param {string} key The input key.
|
|
586
|
-
* @param {string} sub The sub-field key.
|
|
587
|
-
* @return {boolean}
|
|
588
|
-
*/
|
|
589
|
-
isSubRefused(key, sub) {
|
|
590
|
-
return this.refusedKeys.includes(`${key}.${sub}`)
|
|
591
|
-
},
|
|
592
|
-
|
|
593
|
-
/** Seed each declared input from its schema default (booleans start false). */
|
|
594
|
-
initialValues() {
|
|
595
|
-
const values = {}
|
|
596
|
-
const declared = Array.isArray(this.transition?.inputs) ? this.transition.inputs : []
|
|
597
|
-
const properties = (this.schema && this.schema.properties) || {}
|
|
598
|
-
for (const input of declared) {
|
|
599
|
-
if (!input || typeof input.field !== 'string' || input.field === '') {
|
|
600
|
-
continue
|
|
601
|
-
}
|
|
602
|
-
const prop = properties[input.field] || {}
|
|
603
|
-
if (prop.type === 'object' && prop.properties && typeof prop.properties === 'object') {
|
|
604
|
-
// An object input starts from the record's present value, so
|
|
605
|
-
// the sub-fields it asks for show what is there.
|
|
606
|
-
const present = this.currentObject ? this.currentObject[input.field] : null
|
|
607
|
-
values[input.field] = present && typeof present === 'object' ? { ...present } : {}
|
|
608
|
-
continue
|
|
609
|
-
}
|
|
610
|
-
values[input.field] = prop.default !== undefined
|
|
611
|
-
? prop.default
|
|
612
|
-
: (prop.type === 'boolean' ? false : '')
|
|
613
|
-
}
|
|
614
|
-
return values
|
|
615
|
-
},
|
|
616
|
-
|
|
617
|
-
/**
|
|
618
|
-
* Clamp a `fieldsFromSchema()` widget to the minimal set this dialog
|
|
619
|
-
* renders. Anything richer (select, date, user picker, …) degrades to a
|
|
620
|
-
* plain text input rather than pulling a form engine into the dialog.
|
|
621
|
-
*
|
|
622
|
-
* @param {string} widget The resolved widget name.
|
|
623
|
-
* @return {'text'|'textarea'|'number'|'checkbox'}
|
|
624
|
-
*/
|
|
625
|
-
clampWidget(widget) {
|
|
626
|
-
if (widget === 'checkbox' || widget === 'switch') {
|
|
627
|
-
return 'checkbox'
|
|
628
|
-
}
|
|
629
|
-
if (widget === 'textarea') {
|
|
630
|
-
return 'textarea'
|
|
631
|
-
}
|
|
632
|
-
if (widget === 'number') {
|
|
633
|
-
return 'number'
|
|
634
|
-
}
|
|
635
|
-
return 'text'
|
|
636
|
-
},
|
|
637
|
-
|
|
638
|
-
/**
|
|
639
|
-
* The field's visible label, with a `*` marker on required inputs so the
|
|
640
|
-
* confirm-gating is visually explained.
|
|
641
|
-
*
|
|
642
|
-
* @param {object} field The field descriptor.
|
|
643
|
-
* @return {string}
|
|
644
|
-
*/
|
|
645
|
-
requiredLabel(field) {
|
|
646
|
-
return field.required ? `${field.label} *` : field.label
|
|
647
|
-
},
|
|
648
|
-
|
|
649
|
-
/**
|
|
650
|
-
* Store one input's value.
|
|
651
|
-
*
|
|
652
|
-
* @param {string} key The declared field name.
|
|
653
|
-
* @param {unknown} value The new value.
|
|
654
|
-
*/
|
|
655
|
-
setValue(key, value) {
|
|
656
|
-
this.values = { ...this.values, [key]: value }
|
|
657
|
-
},
|
|
658
|
-
|
|
659
|
-
/**
|
|
660
|
-
* Whether a required field counts as filled: a checked switch for
|
|
661
|
-
* booleans, a non-blank string otherwise.
|
|
662
|
-
*
|
|
663
|
-
* @param {object} field The field descriptor.
|
|
664
|
-
* @return {boolean}
|
|
665
|
-
*/
|
|
666
|
-
isFilled(field) {
|
|
667
|
-
const value = this.values[field.key]
|
|
668
|
-
if (field.widget === 'checkbox') {
|
|
669
|
-
return value === true
|
|
670
|
-
}
|
|
671
|
-
return String(value ?? '').trim() !== ''
|
|
672
|
-
},
|
|
673
|
-
|
|
674
304
|
/**
|
|
675
305
|
* The object an object input sends: the record's current value with the
|
|
676
306
|
* asked sub-fields merged over it, so a sub-field that was not asked for
|
|
@@ -698,18 +328,21 @@ export default {
|
|
|
698
328
|
return base
|
|
699
329
|
},
|
|
700
330
|
|
|
701
|
-
/** Confirm: emit
|
|
331
|
+
/** Confirm: emit the asked keys (numbers cast, "Other" as '') and let the parent POST. */
|
|
702
332
|
onConfirm() {
|
|
703
333
|
if (!this.canConfirm) {
|
|
704
334
|
return
|
|
705
335
|
}
|
|
706
336
|
const data = {}
|
|
707
337
|
for (const field of this.fields) {
|
|
338
|
+
if (field.hidden || field.widget === 'template') {
|
|
339
|
+
continue
|
|
340
|
+
}
|
|
708
341
|
if (field.widget === 'object') {
|
|
709
342
|
data[field.key] = this.mergedObject(field)
|
|
710
343
|
continue
|
|
711
344
|
}
|
|
712
|
-
let value = this.values[field.key]
|
|
345
|
+
let value = submittedValue(this.values[field.key])
|
|
713
346
|
if (field.widget === 'number' && String(value ?? '').trim() !== '') {
|
|
714
347
|
const parsed = Number(value)
|
|
715
348
|
if (!Number.isNaN(parsed)) {
|
|
@@ -720,8 +353,8 @@ export default {
|
|
|
720
353
|
}
|
|
721
354
|
/**
|
|
722
355
|
* @event confirm Emitted when the user confirms with all required
|
|
723
|
-
* inputs filled. Payload holds
|
|
724
|
-
* parent POSTs `{ action, data }`.
|
|
356
|
+
* inputs filled. Payload holds the declared input keys that were
|
|
357
|
+
* asked; the parent POSTs `{ action, data }`.
|
|
725
358
|
* @type {{[key: string]: unknown}}
|
|
726
359
|
*/
|
|
727
360
|
this.$emit('confirm', data)
|
|
@@ -742,29 +375,4 @@ export default {
|
|
|
742
375
|
margin: 0;
|
|
743
376
|
color: var(--color-error-text, var(--color-error));
|
|
744
377
|
}
|
|
745
|
-
|
|
746
|
-
.cn-transition-input__field-error {
|
|
747
|
-
margin: 4px 0 0;
|
|
748
|
-
font-size: 0.9em;
|
|
749
|
-
color: var(--color-error-text, var(--color-error));
|
|
750
|
-
}
|
|
751
|
-
|
|
752
|
-
.cn-transition-input__object {
|
|
753
|
-
border: 1px solid var(--color-border);
|
|
754
|
-
border-radius: var(--border-radius);
|
|
755
|
-
padding: 8px 12px;
|
|
756
|
-
display: flex;
|
|
757
|
-
flex-direction: column;
|
|
758
|
-
gap: 8px;
|
|
759
|
-
}
|
|
760
|
-
|
|
761
|
-
.cn-transition-input__label {
|
|
762
|
-
display: block;
|
|
763
|
-
margin-bottom: 4px;
|
|
764
|
-
}
|
|
765
|
-
|
|
766
|
-
.cn-transition-input__field--refused {
|
|
767
|
-
border-inline-start: 2px solid var(--color-error);
|
|
768
|
-
padding-inline-start: 8px;
|
|
769
|
-
}
|
|
770
378
|
</style>
|
package/src/index.js
CHANGED
|
@@ -350,6 +350,7 @@ export {
|
|
|
350
350
|
CnTimelineView,
|
|
351
351
|
CnTimelineWidget,
|
|
352
352
|
CnToggleTableWidget,
|
|
353
|
+
CnTransitionInputFields,
|
|
353
354
|
CnTranslatedBadge,
|
|
354
355
|
CnTreeView,
|
|
355
356
|
CnUnreadMarker,
|
|
@@ -579,6 +580,7 @@ export { currentTheme, useCurrentTheme } from './composables/useCurrentTheme.js'
|
|
|
579
580
|
export { ROADMAP_LABEL_BLOCKLIST } from './utils/roadmapLabelBlocklist.js'
|
|
580
581
|
// The words a person reads for a notification rule key (never the raw key).
|
|
581
582
|
export { humaniseRuleKey, notificationRuleLabel } from './utils/notificationRuleLabel.js'
|
|
583
|
+
export { readTransitionInputs } from './utils/transitionInputs.js'
|
|
582
584
|
// An identifier-shaped key made readable (`shortDescription` → "Short description").
|
|
583
585
|
export { humaniseKey } from './utils/humaniseKey.js'
|
|
584
586
|
|