@conduction/nextcloud-vue 2.115.0 → 2.116.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/CnActionButtons/CnActionButtons.vue.js +1 -1
- package/dist/esm/components/CnActionButtons/CnActionButtons.vue2.js +92 -0
- package/dist/esm/components/CnActionButtons/CnActionButtons.vue2.js.map +1 -1
- package/dist/esm/components/CnActionButtons/CnActionButtons.vue3.js +2 -2
- package/dist/esm/components/CnActionButtons/CnActionButtons.vue3.js.map +1 -1
- package/dist/esm/components/CnChecklistWidget/CnChecklistWidget.vue.js +9 -0
- package/dist/esm/components/CnChecklistWidget/CnChecklistWidget.vue.js.map +1 -0
- package/dist/esm/components/CnChecklistWidget/CnChecklistWidget.vue2.js +145 -0
- package/dist/esm/components/CnChecklistWidget/CnChecklistWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnChecklistWidget/CnChecklistWidget.vue3.js +58 -0
- package/dist/esm/components/CnChecklistWidget/CnChecklistWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnChecklistWidget/index.js +28 -0
- package/dist/esm/components/CnChecklistWidget/index.js.map +1 -0
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +25 -0
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +376 -51
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +364 -285
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue.js +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js +11 -0
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js +6 -4
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
- package/dist/esm/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue.js +1 -1
- package/dist/esm/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue2.js +48 -1
- package/dist/esm/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue3.js +11 -2
- package/dist/esm/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnMacrosWidget/CnMacrosWidget.vue.js +1 -1
- package/dist/esm/components/CnMacrosWidget/CnMacrosWidget.vue2.js +11 -3
- package/dist/esm/components/CnMacrosWidget/CnMacrosWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnMacrosWidget/CnMacrosWidget.vue3.js +8 -3
- package/dist/esm/components/CnMacrosWidget/CnMacrosWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue.js +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +55 -2
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
- 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 +94 -4
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js +248 -239
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordNoteRows.vue.js +9 -0
- package/dist/esm/components/CnRecordListWidget/CnRecordNoteRows.vue.js.map +1 -0
- package/dist/esm/components/CnRecordListWidget/CnRecordNoteRows.vue2.js +282 -0
- package/dist/esm/components/CnRecordListWidget/CnRecordNoteRows.vue2.js.map +1 -0
- package/dist/esm/components/CnRecordListWidget/CnRecordNoteRows.vue3.js +202 -0
- package/dist/esm/components/CnRecordListWidget/CnRecordNoteRows.vue3.js.map +1 -0
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue.js +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +10 -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/CnWidgetGrid/registerDashboardWidgets.js +1 -0
- package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
- package/dist/esm/composables/indexSources.js +64 -0
- package/dist/esm/composables/indexSources.js.map +1 -1
- package/dist/esm/composables/useTaskInboxStore.js +85 -0
- package/dist/esm/composables/useTaskInboxStore.js.map +1 -1
- package/dist/esm/dialogs/CnAccessPurposeDialog.vue.js +9 -0
- package/dist/esm/dialogs/CnAccessPurposeDialog.vue.js.map +1 -0
- package/dist/esm/dialogs/CnAccessPurposeDialog.vue2.js +107 -0
- package/dist/esm/dialogs/CnAccessPurposeDialog.vue2.js.map +1 -0
- package/dist/esm/dialogs/CnAccessPurposeDialog.vue3.js +110 -0
- package/dist/esm/dialogs/CnAccessPurposeDialog.vue3.js.map +1 -0
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/l10n/en.json.js +19 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +23 -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 +1054 -911
- package/dist/esm/utils/actionsDispatcher.js +10 -5
- package/dist/esm/utils/actionsDispatcher.js.map +1 -1
- package/dist/esm/utils/detailActionModel.js +6 -2
- package/dist/esm/utils/detailActionModel.js.map +1 -1
- package/dist/esm/utils/libraryWidgetKeys.js +1 -0
- package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
- package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
- package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
- package/dist/esm/utils/widgetDispatch.js +1 -1
- package/dist/esm/utils/widgetDispatch.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +17078 -15224
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +1054 -911
- package/l10n/en.json +19 -1
- package/l10n/nl.json +23 -1
- package/package.json +1 -1
- package/src/components/CnActionButtons/CnActionButtons.vue +93 -1
- package/src/components/CnChecklistWidget/CnChecklistWidget.vue +206 -0
- package/src/components/CnChecklistWidget/index.js +27 -0
- package/src/components/CnDashboardPage/CnDashboardPage.vue +26 -1
- package/src/components/CnDetailPage/CnDetailPage.md +2 -0
- package/src/components/CnDetailPage/CnDetailPage.vue +377 -10
- package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +15 -2
- package/src/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue +59 -1
- package/src/components/CnMacrosWidget/CnMacrosWidget.vue +16 -6
- package/src/components/CnObjectListWidget/CnObjectListWidget.vue +55 -2
- package/src/components/CnRecordListWidget/CnRecordListWidget.vue +101 -3
- package/src/components/CnRecordListWidget/CnRecordNoteRows.vue +508 -0
- package/src/components/CnTabsWidget/CnTabsWidget.vue +10 -5
- package/src/components/CnWidgetGrid/registerDashboardWidgets.js +1 -0
- package/src/components/index.js +1 -0
- package/src/composables/indexSources.js +64 -0
- package/src/composables/useTaskInboxStore.js +85 -0
- package/src/dialogs/CnAccessPurposeDialog.vue +183 -0
- package/src/index.js +1 -0
- package/src/schemas/app-manifest-v2.schema.json +49 -3
- package/src/utils/actionsDispatcher.js +10 -5
- package/src/utils/detailActionModel.js +6 -2
- package/src/utils/libraryWidgetKeys.js +1 -0
- package/src/utils/validateManifestV2.compiled.js +3 -3
- package/src/utils/widgetDispatch.js +1 -1
|
@@ -223,6 +223,91 @@ export const useTaskInboxStore = defineStore('cnTaskInbox', {
|
|
|
223
223
|
}
|
|
224
224
|
},
|
|
225
225
|
|
|
226
|
+
/**
|
|
227
|
+
* Run one lifecycle verb on a task read for a detail page, and keep
|
|
228
|
+
* that record in step with the engine's answer.
|
|
229
|
+
*
|
|
230
|
+
* `url` replaces the engine's own verb route for an app that puts a
|
|
231
|
+
* step of its own in front of the engine (dossiq completes a task
|
|
232
|
+
* through its own endpoint, which checks the case type's form first).
|
|
233
|
+
* `{id}` in it is the task uuid. A refusal is returned with the
|
|
234
|
+
* server's message as-is and leaves the record as it was.
|
|
235
|
+
*
|
|
236
|
+
* @param {string} uuid The task uuid.
|
|
237
|
+
* @param {string} verb The verb.
|
|
238
|
+
* @param {{url?: string}} [options] An app route to post the verb to instead.
|
|
239
|
+
* @return {Promise<{ok: boolean, status: (number|null), record: (object|null), message: string}>} The outcome.
|
|
240
|
+
* @spec openspec/changes/record-source-verbs/specs/record-source-verbs/spec.md#requirement-a-record-source-offers-header-verbs
|
|
241
|
+
*/
|
|
242
|
+
async runRecordVerb(uuid, verb, options = {}) {
|
|
243
|
+
const key = String(uuid || '').trim()
|
|
244
|
+
const name = String(verb || '').trim()
|
|
245
|
+
if (key === '' || name === '') {
|
|
246
|
+
return { ok: false, status: null, record: null, message: '' }
|
|
247
|
+
}
|
|
248
|
+
const route = typeof options.url === 'string' && options.url !== ''
|
|
249
|
+
? options.url.replace('{id}', encodeURIComponent(key))
|
|
250
|
+
: `${FLOW_TASKS_URL}/${encodeURIComponent(key)}/${encodeURIComponent(name)}`
|
|
251
|
+
try {
|
|
252
|
+
const response = await axios.post(generateUrl(route), {})
|
|
253
|
+
return { ok: true, status: response.status || 200, record: await this.keepRecord(key, response.data), message: '' }
|
|
254
|
+
} catch (error) {
|
|
255
|
+
return { ok: false, status: error?.response?.status ?? null, record: null, message: serverMessage(error) }
|
|
256
|
+
}
|
|
257
|
+
},
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Tick or untick one checklist item on a task read for a detail page
|
|
261
|
+
* (`PATCH /flow-tasks/{uuid}/checklist/{itemId}?checked=`). The engine
|
|
262
|
+
* answers with the whole task, which replaces the record, so a refused
|
|
263
|
+
* tick leaves the item as it was on screen.
|
|
264
|
+
*
|
|
265
|
+
* @param {string} uuid The task uuid.
|
|
266
|
+
* @param {string} itemId The checklist item id.
|
|
267
|
+
* @param {boolean} checked The new state.
|
|
268
|
+
* @return {Promise<{ok: boolean, status: (number|null), record: (object|null), message: string}>} The outcome.
|
|
269
|
+
* @spec openspec/changes/record-checklist-widget/specs/record-checklist-widget/spec.md#requirement-a-checklist-widget-ticks-items-through-the-record-source
|
|
270
|
+
*/
|
|
271
|
+
async checkRecordItem(uuid, itemId, checked) {
|
|
272
|
+
const key = String(uuid || '').trim()
|
|
273
|
+
const item = String(itemId || '').trim()
|
|
274
|
+
if (key === '' || item === '') {
|
|
275
|
+
return { ok: false, status: null, record: null, message: '' }
|
|
276
|
+
}
|
|
277
|
+
try {
|
|
278
|
+
const response = await axios.patch(
|
|
279
|
+
generateUrl(`${FLOW_TASKS_URL}/${encodeURIComponent(key)}/checklist/${encodeURIComponent(item)}`),
|
|
280
|
+
null,
|
|
281
|
+
{ params: { checked: checked === true ? 'true' : 'false' } },
|
|
282
|
+
)
|
|
283
|
+
return { ok: true, status: response.status || 200, record: await this.keepRecord(key, response.data), message: '' }
|
|
284
|
+
} catch (error) {
|
|
285
|
+
return { ok: false, status: error?.response?.status ?? null, record: null, message: serverMessage(error) }
|
|
286
|
+
}
|
|
287
|
+
},
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Store the task a write answered with as the detail record. An answer
|
|
291
|
+
* that carries no task (an app route that returns only a status) is
|
|
292
|
+
* followed by a fresh read, so the page never shows the state from
|
|
293
|
+
* before the write.
|
|
294
|
+
*
|
|
295
|
+
* @param {string} key The task uuid.
|
|
296
|
+
* @param {unknown} data The response body.
|
|
297
|
+
* @return {Promise<object|null>} The task now held.
|
|
298
|
+
*/
|
|
299
|
+
async keepRecord(key, data) {
|
|
300
|
+
const body = data && typeof data === 'object' ? data : null
|
|
301
|
+
const task = body && (body.results || body.task) && typeof (body.results || body.task) === 'object' && !Array.isArray(body.results || body.task)
|
|
302
|
+
? (body.results || body.task)
|
|
303
|
+
: body
|
|
304
|
+
if (task && typeof task === 'object' && !Array.isArray(task) && String(task.uuid || '') === key) {
|
|
305
|
+
this.records = { ...this.records, [key]: task }
|
|
306
|
+
return task
|
|
307
|
+
}
|
|
308
|
+
return this.fetchRecord(key)
|
|
309
|
+
},
|
|
310
|
+
|
|
226
311
|
/**
|
|
227
312
|
* Load one inbox page.
|
|
228
313
|
*
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
- SPDX-FileCopyrightText: 2026 Conduction B.V.
|
|
3
|
+
- SPDX-License-Identifier: EUPL-1.2
|
|
4
|
+
-->
|
|
5
|
+
<template>
|
|
6
|
+
<CnDialog
|
|
7
|
+
:name="title || t('nextcloud-vue', 'Why are you opening this?')"
|
|
8
|
+
defaultWidth="confirm"
|
|
9
|
+
size="small"
|
|
10
|
+
:noClose="loading"
|
|
11
|
+
data-testid="cn-access-purpose-dialog"
|
|
12
|
+
@closing="$emit('cancel')">
|
|
13
|
+
<p class="cn-access-purpose__sentence">
|
|
14
|
+
{{ description || t('nextcloud-vue', 'This record holds special categories of personal data. Your purpose is recorded with your name and the time.') }}
|
|
15
|
+
</p>
|
|
16
|
+
<fieldset class="cn-access-purpose__choices">
|
|
17
|
+
<legend class="cn-access-purpose__legend">
|
|
18
|
+
{{ t('nextcloud-vue', 'Purpose') }}
|
|
19
|
+
</legend>
|
|
20
|
+
<NcCheckboxRadioSwitch
|
|
21
|
+
v-for="option in choices"
|
|
22
|
+
:key="option.value"
|
|
23
|
+
v-model="purpose"
|
|
24
|
+
type="radio"
|
|
25
|
+
name="cn-access-purpose"
|
|
26
|
+
:value="option.value"
|
|
27
|
+
:disabled="loading"
|
|
28
|
+
:data-testid="`cn-access-purpose-option-${option.value}`">
|
|
29
|
+
{{ option.label }}
|
|
30
|
+
</NcCheckboxRadioSwitch>
|
|
31
|
+
</fieldset>
|
|
32
|
+
<NcTextArea
|
|
33
|
+
v-if="purpose === OTHER"
|
|
34
|
+
v-model="note"
|
|
35
|
+
:label="t('nextcloud-vue', 'Describe your purpose')"
|
|
36
|
+
:maxlength="500"
|
|
37
|
+
:disabled="loading"
|
|
38
|
+
data-testid="cn-access-purpose-note" />
|
|
39
|
+
<NcNoteCard v-if="error" type="error" data-testid="cn-access-purpose-error">
|
|
40
|
+
{{ error }}
|
|
41
|
+
</NcNoteCard>
|
|
42
|
+
|
|
43
|
+
<template #actions>
|
|
44
|
+
<NcButton :disabled="loading" data-testid="cn-access-purpose-cancel" @click="$emit('cancel')">
|
|
45
|
+
{{ t('nextcloud-vue', 'Cancel') }}
|
|
46
|
+
</NcButton>
|
|
47
|
+
<NcButton
|
|
48
|
+
variant="primary"
|
|
49
|
+
:disabled="!complete || loading"
|
|
50
|
+
data-testid="cn-access-purpose-submit"
|
|
51
|
+
@click="submit">
|
|
52
|
+
<template v-if="loading" #icon>
|
|
53
|
+
<NcLoadingIcon :size="20" :name="t('nextcloud-vue', 'Loading …')" />
|
|
54
|
+
</template>
|
|
55
|
+
{{ t('nextcloud-vue', 'Open') }}
|
|
56
|
+
</NcButton>
|
|
57
|
+
</template>
|
|
58
|
+
</CnDialog>
|
|
59
|
+
</template>
|
|
60
|
+
|
|
61
|
+
<script>
|
|
62
|
+
import { translate as t } from '@nextcloud/l10n'
|
|
63
|
+
import { NcButton, NcCheckboxRadioSwitch, NcLoadingIcon, NcNoteCard, NcTextArea } from '@nextcloud/vue'
|
|
64
|
+
import CnDialog from '../components/CnDialog/CnDialog.vue'
|
|
65
|
+
|
|
66
|
+
/** The value of the "Other" choice, which asks for a written purpose. */
|
|
67
|
+
const OTHER = 'other'
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* CnAccessPurposeDialog — asks why the reader opens a record before the page
|
|
71
|
+
* shows it (detail page `accessPurpose`). A short list of purposes plus
|
|
72
|
+
* "Other", which needs a written purpose. Emits the choice; the page records
|
|
73
|
+
* it and decides whether to open.
|
|
74
|
+
*
|
|
75
|
+
* @spec openspec/changes/detail-access-purpose/specs/detail-access-purpose/spec.md#requirement-a-detail-page-can-ask-for-a-purpose-before-it-opens
|
|
76
|
+
*/
|
|
77
|
+
export default {
|
|
78
|
+
name: 'CnAccessPurposeDialog',
|
|
79
|
+
|
|
80
|
+
components: { CnDialog, NcButton, NcCheckboxRadioSwitch, NcLoadingIcon, NcNoteCard, NcTextArea },
|
|
81
|
+
|
|
82
|
+
props: {
|
|
83
|
+
/** The dialog's title; a default question when empty. */
|
|
84
|
+
title: {
|
|
85
|
+
type: String,
|
|
86
|
+
default: '',
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
/** The sentence under the title; a default when empty. */
|
|
90
|
+
description: {
|
|
91
|
+
type: String,
|
|
92
|
+
default: '',
|
|
93
|
+
},
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* The purposes offered, already translated. "Other" is added at the end.
|
|
97
|
+
*
|
|
98
|
+
* @type {Array<{value: string, label: string}>}
|
|
99
|
+
*/
|
|
100
|
+
options: {
|
|
101
|
+
type: Array,
|
|
102
|
+
default: () => [],
|
|
103
|
+
},
|
|
104
|
+
|
|
105
|
+
/** Whether the purpose is being recorded. */
|
|
106
|
+
loading: {
|
|
107
|
+
type: Boolean,
|
|
108
|
+
default: false,
|
|
109
|
+
},
|
|
110
|
+
|
|
111
|
+
/** Why the last attempt was refused, shown in the dialog. */
|
|
112
|
+
error: {
|
|
113
|
+
type: String,
|
|
114
|
+
default: '',
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
|
|
118
|
+
emits: [
|
|
119
|
+
/**
|
|
120
|
+
* The reader chose a purpose.
|
|
121
|
+
*
|
|
122
|
+
* @type {{purpose: string, note: string}}
|
|
123
|
+
*/
|
|
124
|
+
'submit',
|
|
125
|
+
/** The reader closed the dialog without a purpose. */
|
|
126
|
+
'cancel',
|
|
127
|
+
],
|
|
128
|
+
|
|
129
|
+
data() {
|
|
130
|
+
return { purpose: '', note: '', OTHER }
|
|
131
|
+
},
|
|
132
|
+
|
|
133
|
+
computed: {
|
|
134
|
+
/** @return {Array<{value: string, label: string}>} The offered purposes and "Other". */
|
|
135
|
+
choices() {
|
|
136
|
+
const own = (Array.isArray(this.options) ? this.options : [])
|
|
137
|
+
.filter((option) => option && typeof option.value === 'string' && option.value !== '' && option.value !== OTHER)
|
|
138
|
+
return [...own, { value: OTHER, label: t('nextcloud-vue', 'Other') }]
|
|
139
|
+
},
|
|
140
|
+
|
|
141
|
+
/** @return {boolean} Whether a purpose is chosen and, for "Other", written. */
|
|
142
|
+
complete() {
|
|
143
|
+
if (this.purpose === '') {
|
|
144
|
+
return false
|
|
145
|
+
}
|
|
146
|
+
return this.purpose !== OTHER || this.note.trim() !== ''
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
|
|
150
|
+
methods: {
|
|
151
|
+
t,
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Emit the chosen purpose; the written note only goes with "Other".
|
|
155
|
+
*
|
|
156
|
+
* @return {void}
|
|
157
|
+
*/
|
|
158
|
+
submit() {
|
|
159
|
+
if (!this.complete) {
|
|
160
|
+
return
|
|
161
|
+
}
|
|
162
|
+
this.$emit('submit', { purpose: this.purpose, note: this.purpose === OTHER ? this.note.trim() : '' })
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
}
|
|
166
|
+
</script>
|
|
167
|
+
|
|
168
|
+
<style scoped>
|
|
169
|
+
.cn-access-purpose__sentence {
|
|
170
|
+
margin: 0 0 12px;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
.cn-access-purpose__choices {
|
|
174
|
+
margin: 0 0 12px;
|
|
175
|
+
padding: 0;
|
|
176
|
+
border: 0;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
.cn-access-purpose__legend {
|
|
180
|
+
margin-bottom: 4px;
|
|
181
|
+
font-weight: 600;
|
|
182
|
+
}
|
|
183
|
+
</style>
|
package/src/index.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"$id": "https://raw.githubusercontent.com/ConductionNL/nextcloud-vue/main/src/schemas/app-manifest-v2.schema.json",
|
|
4
4
|
"title": "Conduction App Manifest v2",
|
|
5
5
|
"description": "v2 schema for the JSON-driven page and navigation manifest consumed by @conduction/nextcloud-vue. Introduces a uniform widgets[] array on every page type with a per-slot grid coordinate system, a typed actions[] discriminator, and the required $schema field for version detection. v1 manifests continue to validate against app-manifest.schema.json (unchanged).",
|
|
6
|
-
"version": "2.
|
|
6
|
+
"version": "2.137.0",
|
|
7
7
|
"type": "object",
|
|
8
8
|
"required": ["$schema", "version"],
|
|
9
9
|
"allOf": [
|
|
@@ -1363,6 +1363,11 @@
|
|
|
1363
1363
|
"minLength": 1,
|
|
1364
1364
|
"description": "Dot-path to the value on the record."
|
|
1365
1365
|
},
|
|
1366
|
+
"values": {
|
|
1367
|
+
"type": "array",
|
|
1368
|
+
"items": { "type": "string" },
|
|
1369
|
+
"description": "With `field`: draw the pill only when the raw value is one of these (a priority pill for high and urgent only)."
|
|
1370
|
+
},
|
|
1366
1371
|
"text": {
|
|
1367
1372
|
"type": "string",
|
|
1368
1373
|
"minLength": 1,
|
|
@@ -3202,6 +3207,23 @@
|
|
|
3202
3207
|
"default": 1,
|
|
3203
3208
|
"description": "open-form: how many columns the fields flow into. 2 pairs them up, which halves the scrolling on a longer form. Textareas and JSON editors still span the full width, and the layout collapses to one column below 700px. Widen the dialog with size: 'large' as well, or two columns are merely two narrow ones."
|
|
3204
3209
|
},
|
|
3210
|
+
"endpoint": {
|
|
3211
|
+
"type": "object",
|
|
3212
|
+
"description": "open-form only: edit a record that is not a register object through an endpoint (an OpenRegister organisation). `url` is where the save goes (token-interpolated, app-relative or absolute), `method` is PATCH (default) or PUT, or POST to create a record with an empty form, `loadUrl` reads the record when it differs from `url`, `responsePath` plucks it from the answer. The fields are `formProperties`. Without `endpoint` the action is the schema-driven create form.",
|
|
3213
|
+
"properties": {
|
|
3214
|
+
"url": { "type": "string" },
|
|
3215
|
+
"method": { "type": "string", "enum": ["PATCH", "PUT", "POST"] },
|
|
3216
|
+
"loadUrl": { "type": "string" },
|
|
3217
|
+
"responsePath": { "type": "string" }
|
|
3218
|
+
},
|
|
3219
|
+
"required": ["url"],
|
|
3220
|
+
"additionalProperties": false
|
|
3221
|
+
},
|
|
3222
|
+
"formProperties": {
|
|
3223
|
+
"type": "object",
|
|
3224
|
+
"description": "open-form with `endpoint` only: the fields the form shows and sends, as JSON-schema properties (`{ \"name\": { \"type\": \"string\", \"title\": \"Name\" } }`).",
|
|
3225
|
+
"additionalProperties": { "type": "object" }
|
|
3226
|
+
},
|
|
3205
3227
|
"handler": {
|
|
3206
3228
|
"type": "string",
|
|
3207
3229
|
"description": "Registry key for type: 'handler'. Resolved across both registries passed to CnAppRoot: a v2 `registry` entry of `kind: 'handler'` exposing the function as `.handler` (or `.fn`), a directly function-valued registry entry, or a function-valued entry in the deprecated `customComponents` map. The same resolution serves actions[], bulkActions[] and headerActions[]. Also the built-in dispatch verb when paired with `route` (handler: 'navigate' → $router.push to `route`)."
|
|
@@ -3255,8 +3277,8 @@
|
|
|
3255
3277
|
},
|
|
3256
3278
|
"method": {
|
|
3257
3279
|
"type": "string",
|
|
3258
|
-
"enum": ["POST", "PUT"],
|
|
3259
|
-
"description": "api-call: HTTP method (default POST). toggle writeUrl: default PUT."
|
|
3280
|
+
"enum": ["POST", "PUT", "GET"],
|
|
3281
|
+
"description": "api-call: HTTP method (default POST); GET sends the resolved params as the query string, and with download: true downloads the file the endpoint answers (DqDoorlooptijd). toggle writeUrl: default PUT."
|
|
3260
3282
|
},
|
|
3261
3283
|
"payload": {
|
|
3262
3284
|
"type": "object",
|
|
@@ -5506,6 +5528,30 @@
|
|
|
5506
5528
|
}
|
|
5507
5529
|
}
|
|
5508
5530
|
},
|
|
5531
|
+
"accessPurpose": {
|
|
5532
|
+
"type": "object",
|
|
5533
|
+
"required": ["when", "url"],
|
|
5534
|
+
"additionalProperties": false,
|
|
5535
|
+
"description": "type='detail': ask why the reader opens the record before the page shows it, and record the answer (detail-access-purpose). `when` is a condition in the visibleWhen grammar on the record and its visibilityContext records; `options` the purposes offered (\"Other\" with a written purpose is always added); `url` the app endpoint that records the purpose (POST { purpose, note }, {objectId} filled in). Nothing of the record shows until the endpoint accepts; the same record is not asked again in the same browser session. Maps to CnDetailPage `accessPurpose`.",
|
|
5536
|
+
"properties": {
|
|
5537
|
+
"when": { "type": "object", "description": "The condition under which the page asks, e.g. { \"field\": \"@context.caseType.specialDataCategories\", \"op\": \"notEmpty\" }." },
|
|
5538
|
+
"options": {
|
|
5539
|
+
"type": "array",
|
|
5540
|
+
"items": {
|
|
5541
|
+
"type": "object",
|
|
5542
|
+
"required": ["value", "label"],
|
|
5543
|
+
"additionalProperties": false,
|
|
5544
|
+
"properties": {
|
|
5545
|
+
"value": { "type": "string", "minLength": 1 },
|
|
5546
|
+
"label": { "type": "string", "minLength": 1, "description": "i18n key." }
|
|
5547
|
+
}
|
|
5548
|
+
}
|
|
5549
|
+
},
|
|
5550
|
+
"url": { "type": "string", "minLength": 1, "description": "App endpoint that records the purpose; {objectId} is filled in." },
|
|
5551
|
+
"title": { "type": "string", "description": "i18n key of the dialog's title." },
|
|
5552
|
+
"description": { "type": "string", "description": "i18n key of the sentence under the title." }
|
|
5553
|
+
}
|
|
5554
|
+
},
|
|
5509
5555
|
"mapConfig": {
|
|
5510
5556
|
"type": "object",
|
|
5511
5557
|
"additionalProperties": true,
|
|
@@ -466,7 +466,8 @@ async function executeApiCall(action, context) {
|
|
|
466
466
|
console.warn('[dispatchAction] api-call is missing its url or a required token is unresolved — skipping.', action)
|
|
467
467
|
return { ok: false, error: new Error('api-call blocked') }
|
|
468
468
|
}
|
|
469
|
-
const
|
|
469
|
+
const verb = String(action.method || 'POST').toUpperCase()
|
|
470
|
+
const method = verb === 'PUT' ? 'put' : (verb === 'GET' ? 'get' : 'post')
|
|
470
471
|
const isDownload = action.download === true
|
|
471
472
|
const [{ default: axios }, { generateUrl }, dialogs] = await Promise.all([
|
|
472
473
|
import('@nextcloud/axios'),
|
|
@@ -478,9 +479,13 @@ async function executeApiCall(action, context) {
|
|
|
478
479
|
// Only pass a third axios config arg for a download call — an
|
|
479
480
|
// explicit `undefined` third argument would otherwise change the
|
|
480
481
|
// call shape for every existing (non-download) api-call consumer/test.
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
482
|
+
// A GET sends the resolved params as the query string: it reads, as a
|
|
483
|
+
// download of what the page shows (DqDoorlooptijd "Downloaden").
|
|
484
|
+
const res = method === 'get'
|
|
485
|
+
? await axios.get(target, isDownload ? { params: body, responseType: 'blob' } : { params: body })
|
|
486
|
+
: (isDownload
|
|
487
|
+
? await axios[method](target, body, { responseType: 'blob' })
|
|
488
|
+
: await axios[method](target, body))
|
|
484
489
|
if (isDownload) {
|
|
485
490
|
const filename = parseDispositionFilename(
|
|
486
491
|
res && res.headers && res.headers['content-disposition'],
|
|
@@ -731,7 +736,7 @@ export async function postRunNode(action, context, config = {}) {
|
|
|
731
736
|
* through generateUrl) or absolute. May interpolate the shared URL tokens
|
|
732
737
|
* (`@objectId`, `@object.<field>`, `@workspace.<key>`, `@config.<key>`, and the
|
|
733
738
|
* `{objectId}` brace form).
|
|
734
|
-
* @param {string} [action.method] "api-call" only: "POST" (default) | "PUT".
|
|
739
|
+
* @param {string} [action.method] "api-call" only: "POST" (default) | "PUT" | "GET" (the resolved params as the query string).
|
|
735
740
|
* @param {object} [action.payload] "api-call" only: the JSON body — preferred over `params`.
|
|
736
741
|
* Values resolve the shared `@`-token grammar RECURSIVELY at any nesting depth (objects
|
|
737
742
|
* and arrays of objects, e.g. `{ dataRefs: [{ id: '@objectId' }] }`); optional (`…?`)
|
|
@@ -225,9 +225,10 @@ export function resolveNextStep(config, object, stageField = 'status') {
|
|
|
225
225
|
*
|
|
226
226
|
* A pill with `field` reads it off the record; one without `field` and with
|
|
227
227
|
* `text` is a constant. A count pill (`countFrom`) is resolved by
|
|
228
|
-
* {@link resolveCountPill} once its count is known.
|
|
228
|
+
* {@link resolveCountPill} once its count is known. `values` limits a field
|
|
229
|
+
* pill to those raw values: a priority pill drawn only for high and urgent.
|
|
229
230
|
*
|
|
230
|
-
* @param {{field?: string, text?: string, colorMap?: object, labels?: object, variant?: string}|null} config The pill config.
|
|
231
|
+
* @param {{field?: string, text?: string, colorMap?: object, labels?: object, variant?: string, values?: Array<string>}|null} config The pill config.
|
|
231
232
|
* @param {object|null} object The record.
|
|
232
233
|
* @return {{label: string, colorKey: string, colorMap: object|null, variant: string}|null} What CnStatusBadge needs, or null when the field is empty.
|
|
233
234
|
* @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-header-pills
|
|
@@ -252,6 +253,9 @@ export function resolvePill(config, object) {
|
|
|
252
253
|
if (raw === '') {
|
|
253
254
|
return null
|
|
254
255
|
}
|
|
256
|
+
if (Array.isArray(config.values) && !config.values.map(String).includes(raw)) {
|
|
257
|
+
return null
|
|
258
|
+
}
|
|
255
259
|
const mapped = stageEntry(config.labels, raw)
|
|
256
260
|
return {
|
|
257
261
|
label: typeof mapped === 'string' && mapped !== '' ? mapped : raw,
|