@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
|
@@ -43,6 +43,13 @@
|
|
|
43
43
|
<p v-else class="cn-grouped-cards__group-empty">
|
|
44
44
|
{{ groupEmptyLabel }}
|
|
45
45
|
</p>
|
|
46
|
+
<!-- `groupAction`: one action per group ("Add a task" on a phase),
|
|
47
|
+
its `@group.<path>` tokens filled from the group. -->
|
|
48
|
+
<CnActionButtons
|
|
49
|
+
v-if="group.action"
|
|
50
|
+
class="cn-grouped-cards__group-action"
|
|
51
|
+
:actions="[group.action]"
|
|
52
|
+
data-testid="cn-grouped-cards-group-action" />
|
|
46
53
|
</section>
|
|
47
54
|
<section
|
|
48
55
|
v-if="extra"
|
|
@@ -71,6 +78,7 @@ import { fillTemplate } from '../../utils/fillTemplate.js'
|
|
|
71
78
|
import { unwrapAppConfig } from '../../utils/formatMetric.js'
|
|
72
79
|
import { languageMapText } from '../../utils/languageMapText.js'
|
|
73
80
|
import { readPath } from '../../utils/readPath.js'
|
|
81
|
+
import { CnActionButtons } from '../CnActionButtons/index.js'
|
|
74
82
|
|
|
75
83
|
let uid = 0
|
|
76
84
|
|
|
@@ -121,7 +129,7 @@ function readable(row) {
|
|
|
121
129
|
export default {
|
|
122
130
|
name: 'CnGroupedCardsWidget',
|
|
123
131
|
|
|
124
|
-
components: { NcLoadingIcon },
|
|
132
|
+
components: { CnActionButtons, NcLoadingIcon },
|
|
125
133
|
|
|
126
134
|
inject: {
|
|
127
135
|
/** The host app's translate function. */
|
|
@@ -147,6 +155,11 @@ export default {
|
|
|
147
155
|
* - `extraGroup` (`{ title, path, itemText, join? }`) closes the grid with a
|
|
148
156
|
* dashed box whose entries, read at `path` of the payload, are one line.
|
|
149
157
|
* - `columns` (default 2) is the number of groups side by side.
|
|
158
|
+
* - `groupAction` is an action drawn at the foot of every group (as
|
|
159
|
+
* CnActionButtons draws it), with `@group.<path>` in its strings read
|
|
160
|
+
* from that group: `{ type: "open-form", endpoint: { url:
|
|
161
|
+
* "/apps/x/api/phases/@group.id/steps", method: "POST" }, ... }`
|
|
162
|
+
* adds a card to the group.
|
|
150
163
|
*
|
|
151
164
|
* A language map (`{ nl, en }`) reads in the user's language, and a list
|
|
152
165
|
* of plain values reads joined with ", ". Texts go through the app's
|
|
@@ -231,6 +244,7 @@ export default {
|
|
|
231
244
|
title: this.fill(c.groupTitle || '{name}', group),
|
|
232
245
|
count: this.countOf(items.length),
|
|
233
246
|
items,
|
|
247
|
+
action: this.groupActionFor(raw, index),
|
|
234
248
|
}
|
|
235
249
|
})
|
|
236
250
|
},
|
|
@@ -285,6 +299,46 @@ export default {
|
|
|
285
299
|
},
|
|
286
300
|
|
|
287
301
|
methods: {
|
|
302
|
+
/**
|
|
303
|
+
* The `groupAction` for one group, with every `@group.<path>` token in
|
|
304
|
+
* its strings read from that group, and its id made unique per group.
|
|
305
|
+
* Null without a `groupAction`, or for a group missing a value a token
|
|
306
|
+
* names (the action would address no group).
|
|
307
|
+
*
|
|
308
|
+
* @param {object} raw The group as read.
|
|
309
|
+
* @param {number} index The group's position.
|
|
310
|
+
* @return {object|null} The action.
|
|
311
|
+
* @spec openspec/changes/grouped-cards-group-action/specs/grouped-cards-group-action/spec.md#requirement-every-group-can-carry-an-action
|
|
312
|
+
*/
|
|
313
|
+
groupActionFor(raw, index) {
|
|
314
|
+
const action = this.content?.groupAction
|
|
315
|
+
if (!action || typeof action !== 'object') {
|
|
316
|
+
return null
|
|
317
|
+
}
|
|
318
|
+
let missing = false
|
|
319
|
+
const fill = (value) => {
|
|
320
|
+
if (typeof value === 'string') {
|
|
321
|
+
return value.replace(/@group\.([\w.]+)/g, (token, path) => {
|
|
322
|
+
const found = readPath(raw, path)
|
|
323
|
+
if (found === undefined || found === null || found === '') {
|
|
324
|
+
missing = true
|
|
325
|
+
return ''
|
|
326
|
+
}
|
|
327
|
+
return encodeURIComponent(String(found))
|
|
328
|
+
})
|
|
329
|
+
}
|
|
330
|
+
if (Array.isArray(value)) {
|
|
331
|
+
return value.map(fill)
|
|
332
|
+
}
|
|
333
|
+
if (value && typeof value === 'object') {
|
|
334
|
+
return Object.fromEntries(Object.entries(value).map(([key, inner]) => [key, fill(inner)]))
|
|
335
|
+
}
|
|
336
|
+
return value
|
|
337
|
+
}
|
|
338
|
+
const filled = fill(action)
|
|
339
|
+
return missing ? null : { ...filled, id: `${action.id || 'group-action'}-${index}` }
|
|
340
|
+
},
|
|
341
|
+
|
|
288
342
|
/**
|
|
289
343
|
* Translate a text through the host app's catalogue.
|
|
290
344
|
*
|
|
@@ -382,6 +436,10 @@ export default {
|
|
|
382
436
|
color: var(--color-main-text);
|
|
383
437
|
}
|
|
384
438
|
|
|
439
|
+
.cn-grouped-cards__group-action {
|
|
440
|
+
margin-top: 8px;
|
|
441
|
+
}
|
|
442
|
+
|
|
385
443
|
.cn-grouped-cards__count {
|
|
386
444
|
padding: 2px 8px;
|
|
387
445
|
border-radius: var(--border-radius-pill, 12px);
|
|
@@ -33,8 +33,8 @@
|
|
|
33
33
|
class="cn-macros-widget__row"
|
|
34
34
|
:data-testid="`cn-macros-widget-row-${macro.key}`">
|
|
35
35
|
<div class="cn-macros-widget__text">
|
|
36
|
-
<span class="cn-macros-widget__name">{{ macro.name }}</span>
|
|
37
|
-
<span v-if="macro.description" class="cn-macros-widget__hint">{{ macro.description }}</span>
|
|
36
|
+
<span class="cn-macros-widget__name">{{ tr(macro.name) }}</span>
|
|
37
|
+
<span v-if="macro.description" class="cn-macros-widget__hint">{{ tr(macro.description) }}</span>
|
|
38
38
|
</div>
|
|
39
39
|
<NcButton
|
|
40
40
|
:disabled="running !== ''"
|
|
@@ -54,7 +54,9 @@
|
|
|
54
54
|
v-else-if="lastRun"
|
|
55
55
|
type="success"
|
|
56
56
|
data-testid="cn-macros-widget-last-run">
|
|
57
|
-
{{ t('nextcloud-vue', 'Last run: {name}, {date}', { name: lastRun.name, date: lastRun.date }) }}
|
|
57
|
+
{{ t('nextcloud-vue', 'Last run: {name}, {date}', { name: lastRun.name, date: lastRun.date }) }}<template v-if="lastRun.changes.length">
|
|
58
|
+
{{ n('nextcloud-vue', '{count} change: {list}', '{count} changes: {list}', lastRun.changes.length, { count: lastRun.changes.length, list: lastRun.changes.join(', ') }) }}
|
|
59
|
+
</template>
|
|
58
60
|
</NcNoteCard>
|
|
59
61
|
</div>
|
|
60
62
|
</template>
|
|
@@ -62,7 +64,7 @@
|
|
|
62
64
|
<script>
|
|
63
65
|
import axios from '@nextcloud/axios'
|
|
64
66
|
import { emit } from '@nextcloud/event-bus'
|
|
65
|
-
import { translate as t } from '@nextcloud/l10n'
|
|
67
|
+
import { translatePlural as n, translate as t } from '@nextcloud/l10n'
|
|
66
68
|
import { generateUrl } from '@nextcloud/router'
|
|
67
69
|
import { NcButton, NcNoteCard } from '@nextcloud/vue'
|
|
68
70
|
import { declaredMacros } from '../../utils/declaredMacros.js'
|
|
@@ -135,6 +137,7 @@ export default {
|
|
|
135
137
|
|
|
136
138
|
methods: {
|
|
137
139
|
t,
|
|
140
|
+
n,
|
|
138
141
|
|
|
139
142
|
/**
|
|
140
143
|
* Translate an app-supplied text through the host app when it offers it.
|
|
@@ -188,8 +191,11 @@ export default {
|
|
|
188
191
|
const entry = (res?.data?.results || [])[0]
|
|
189
192
|
const key = entry?.changed?.declaredAction
|
|
190
193
|
const macro = this.macros.find((candidate) => candidate.key === key)
|
|
194
|
+
const changes = Array.isArray(entry?.changed?.changes)
|
|
195
|
+
? entry.changed.changes.map((change) => (change && typeof change.step === 'string' ? change.step : '')).filter(Boolean)
|
|
196
|
+
: []
|
|
191
197
|
this.lastRun = macro && entry.created
|
|
192
|
-
? { name: macro.name, date: new Date(entry.created).toLocaleString() }
|
|
198
|
+
? { name: this.tr(macro.name), date: new Date(entry.created).toLocaleString(), changes }
|
|
193
199
|
: null
|
|
194
200
|
} catch {
|
|
195
201
|
this.lastRun = null
|
|
@@ -208,8 +214,12 @@ export default {
|
|
|
208
214
|
this.error = ''
|
|
209
215
|
try {
|
|
210
216
|
await axios.post(generateUrl('/apps/openregister/api/objects/{register}/{schema}/{id}/actions/{action}', { register, schema, id, action: macro.key }))
|
|
211
|
-
this.lastRun = { name: macro.name, date: new Date().toLocaleString() }
|
|
217
|
+
this.lastRun = { name: this.tr(macro.name), date: new Date().toLocaleString(), changes: [] }
|
|
212
218
|
emit('cn:page:refresh', { source: 'cn-macros-widget' })
|
|
219
|
+
// The audit entry of this run says what it changed.
|
|
220
|
+
const local = this.lastRun
|
|
221
|
+
await this.loadLastRun()
|
|
222
|
+
this.lastRun = this.lastRun || local
|
|
213
223
|
} catch (e) {
|
|
214
224
|
this.error = t('nextcloud-vue', 'The macro did not run: {error}', { error: e?.response?.data?.error || e?.message || '' })
|
|
215
225
|
} finally {
|
|
@@ -315,6 +315,7 @@ import CnPagination from '../CnPagination/CnPagination.vue'
|
|
|
315
315
|
import CnWidgetEmptyState from '../CnWidgetEmptyState/CnWidgetEmptyState.vue'
|
|
316
316
|
import { useWriteFeedback } from '../../composables/useWriteFeedback.js'
|
|
317
317
|
import { actionLink, dispatchAction, hasActionTargetTokens } from '../../utils/actionsDispatcher.js'
|
|
318
|
+
import { buildExportUrl } from '../../utils/indexExportHelpers.js'
|
|
318
319
|
import { followLinkClick, openRowTarget, resolveHref } from '../../utils/linkNavigation.js'
|
|
319
320
|
import { objectDisplayName, objectFieldValue } from '../../utils/objectName.js'
|
|
320
321
|
import { moveIndex, renumberRows, rowIdOf } from '../../utils/reorderRows.js'
|
|
@@ -322,6 +323,7 @@ import { dropOptionalUnresolved, hasUnresolvedTokens, resolveFilterTokens } from
|
|
|
322
323
|
import { markNewTabHandled } from '../../utils/rowAuxClick.js'
|
|
323
324
|
import { changedKeys } from '../../utils/saveEdited.js'
|
|
324
325
|
import { dispatchObjectCreated } from '../../utils/walkthroughSignals.js'
|
|
326
|
+
import { getCurrentUserGroups } from '../../utils/widgetVisibility.js'
|
|
325
327
|
import { CnRowActions } from '../CnRowActions/index.js'
|
|
326
328
|
|
|
327
329
|
/**
|
|
@@ -450,8 +452,11 @@ export default {
|
|
|
450
452
|
*
|
|
451
453
|
* `footnote` is a line under the table, through the host translate.
|
|
452
454
|
*
|
|
455
|
+
* `export` (`{ label?, format?, group? }`) offers the filtered list as a
|
|
456
|
+
* download in the card's header; `group` shows it to one group only.
|
|
457
|
+
*
|
|
453
458
|
* @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-small-opt-ins-from-the-decidiq-and-learniq-lanes
|
|
454
|
-
* @type {{register?: string, schema?: string, filter?: object, sort?: {field?: string, dir?: string}, limit?: number, extend?: Array<string>, columns?: Array, rowActions?: Array<object>, dropZone?: object, upload?: boolean, groupBy?: string, groupLabel?: string, groupLabelResolve?: {register: string, schema: string, labelField?: string}, selectable?: boolean, bulkActions?: Array<object>, sortable?: boolean, facet?: {field: string, label?: string}, rowRoute?: string, prompt?: string, emptyText?: string, footnote?: string, viewAllRoute?: string, viewAllQuery?: object, hideHeader?: boolean}}
|
|
459
|
+
* @type {{register?: string, schema?: string, filter?: object, sort?: {field?: string, dir?: string}, limit?: number, extend?: Array<string>, columns?: Array, rowActions?: Array<object>, dropZone?: object, upload?: boolean, groupBy?: string, groupLabel?: string, groupLabelResolve?: {register: string, schema: string, labelField?: string}, selectable?: boolean, bulkActions?: Array<object>, sortable?: boolean, facet?: {field: string, label?: string}, rowRoute?: string, prompt?: string, emptyText?: string, footnote?: string, viewAllRoute?: string, viewAllQuery?: object, hideHeader?: boolean, export?: {label?: string, format?: ('csv'|'excel'), group?: string}}}
|
|
455
460
|
*/
|
|
456
461
|
content: {
|
|
457
462
|
type: Object,
|
|
@@ -459,7 +464,7 @@ export default {
|
|
|
459
464
|
},
|
|
460
465
|
},
|
|
461
466
|
|
|
462
|
-
emits: ['created', 'updated', 'row-click', 'row-aux-click', 'view-all', 'files-dropped'],
|
|
467
|
+
emits: ['created', 'updated', 'row-click', 'row-aux-click', 'view-all', 'files-dropped', 'header'],
|
|
463
468
|
|
|
464
469
|
data() {
|
|
465
470
|
return {
|
|
@@ -1268,6 +1273,7 @@ export default {
|
|
|
1268
1273
|
mounted() {
|
|
1269
1274
|
this.fetchRows()
|
|
1270
1275
|
this.loadHeadingsIfNeeded()
|
|
1276
|
+
this.offerExport()
|
|
1271
1277
|
// A table wider than the widget fits its columns to the width instead of
|
|
1272
1278
|
// scrolling sideways (no inner scrollbars).
|
|
1273
1279
|
if (typeof ResizeObserver !== 'undefined') {
|
|
@@ -1318,6 +1324,53 @@ export default {
|
|
|
1318
1324
|
},
|
|
1319
1325
|
|
|
1320
1326
|
methods: {
|
|
1327
|
+
/**
|
|
1328
|
+
* Offer `content.export` as a button in the card's header: the list as
|
|
1329
|
+
* it is filtered here, through OpenRegister's export (which applies its
|
|
1330
|
+
* own read rights to every row). `group` keeps the button to the members
|
|
1331
|
+
* of one Nextcloud group, a role such as AI oversight; it hides the
|
|
1332
|
+
* button, and the export endpoint still decides what anyone may read.
|
|
1333
|
+
*
|
|
1334
|
+
* @return {Promise<void>}
|
|
1335
|
+
* @spec openspec/changes/widget-subtitle-and-list-export/specs/object-list-widget/spec.md#requirement-a-list-can-offer-its-rows-as-an-export
|
|
1336
|
+
*/
|
|
1337
|
+
/**
|
|
1338
|
+
* The export URL for this list as it is filtered here.
|
|
1339
|
+
*
|
|
1340
|
+
* @param {'csv'|'excel'} format The export format.
|
|
1341
|
+
* @return {string} The URL.
|
|
1342
|
+
*/
|
|
1343
|
+
exportUrl(format) {
|
|
1344
|
+
return buildExportUrl(this.content.register, this.content.schema, this.resolvedFilter, format)
|
|
1345
|
+
},
|
|
1346
|
+
|
|
1347
|
+
async offerExport() {
|
|
1348
|
+
const config = this.content && this.content.export
|
|
1349
|
+
if (!config || typeof config !== 'object' || !this.content.register || !this.content.schema) {
|
|
1350
|
+
return
|
|
1351
|
+
}
|
|
1352
|
+
if (typeof config.group === 'string' && config.group !== '') {
|
|
1353
|
+
const groups = await getCurrentUserGroups().catch(() => [])
|
|
1354
|
+
if (!groups.includes(config.group)) {
|
|
1355
|
+
return
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
const format = config.format === 'excel' ? 'excel' : 'csv'
|
|
1359
|
+
const label = typeof config.label === 'string' && config.label !== '' ? (typeof this.cnTranslate === 'function' ? this.cnTranslate(config.label) : config.label) : t('nextcloud-vue', 'Download')
|
|
1360
|
+
/**
|
|
1361
|
+
* @event header The card header's Download action for the list as
|
|
1362
|
+
* filtered here (`content.export`), handed to the host card.
|
|
1363
|
+
* @type {{action: {label: string, icon: string, run: Function}}}
|
|
1364
|
+
*/
|
|
1365
|
+
this.$emit('header', {
|
|
1366
|
+
action: {
|
|
1367
|
+
label,
|
|
1368
|
+
icon: 'Download',
|
|
1369
|
+
run: () => window.location.assign(this.exportUrl(format)),
|
|
1370
|
+
},
|
|
1371
|
+
})
|
|
1372
|
+
},
|
|
1373
|
+
|
|
1321
1374
|
/**
|
|
1322
1375
|
* Move a row one place up or down (the row actions).
|
|
1323
1376
|
*
|
|
@@ -58,6 +58,14 @@
|
|
|
58
58
|
</tr>
|
|
59
59
|
</tbody>
|
|
60
60
|
</table>
|
|
61
|
+
<!-- notes: a field per row for what happened, recorded on the row's
|
|
62
|
+
own notes endpoint (DqDagAfsluiten "Nog open van vandaag"). -->
|
|
63
|
+
<CnRecordNoteRows
|
|
64
|
+
v-else-if="presentation === 'notes' && noteAction"
|
|
65
|
+
:items="items"
|
|
66
|
+
:action="noteAction"
|
|
67
|
+
:recordAll="noteRecordAll"
|
|
68
|
+
:leaf="content.rowLeaf || null" />
|
|
61
69
|
<ul v-else class="cn-record-list__list">
|
|
62
70
|
<li
|
|
63
71
|
v-for="item in items"
|
|
@@ -277,6 +285,7 @@ import { inject, ref } from 'vue'
|
|
|
277
285
|
import ChevronRight from 'vue-material-design-icons/ChevronRight.vue'
|
|
278
286
|
import CnIcon from '../CnIcon/CnIcon.vue'
|
|
279
287
|
import CnStatusBadge from '../CnStatusBadge/CnStatusBadge.vue'
|
|
288
|
+
import CnRecordNoteRows from './CnRecordNoteRows.vue'
|
|
280
289
|
import { useEndpointSource } from '../../composables/useEndpointSource.js'
|
|
281
290
|
import { actionLink, dispatchAction, interpolateActionTarget } from '../../utils/actionsDispatcher.js'
|
|
282
291
|
import { isBadgeVariant } from '../../utils/badgeVariants.js'
|
|
@@ -295,7 +304,7 @@ const PAGE_REFRESH_CHANNEL = 'cn:page:refresh'
|
|
|
295
304
|
|
|
296
305
|
/** Gives each list's footer reason its own element id. */
|
|
297
306
|
let footerSeq = 0
|
|
298
|
-
const PRESENTATIONS = ['rows', 'feed', 'inline', 'cards', 'notice', 'table']
|
|
307
|
+
const PRESENTATIONS = ['rows', 'feed', 'inline', 'cards', 'notice', 'table', 'notes']
|
|
299
308
|
|
|
300
309
|
/** The tones a `notice` list takes. */
|
|
301
310
|
const NOTICE_VARIANTS = ['info', 'warning', 'error']
|
|
@@ -348,7 +357,7 @@ function unwrap(value) {
|
|
|
348
357
|
export default {
|
|
349
358
|
name: 'CnRecordListWidget',
|
|
350
359
|
|
|
351
|
-
components: { ChevronRight, CnIcon, CnStatusBadge, NcButton, NcLoadingIcon },
|
|
360
|
+
components: { ChevronRight, CnIcon, CnRecordNoteRows, CnStatusBadge, NcButton, NcLoadingIcon },
|
|
352
361
|
|
|
353
362
|
inject: {
|
|
354
363
|
/** Detail-page object context (`{ objectId, object, register, schema }`). */
|
|
@@ -446,6 +455,8 @@ export default {
|
|
|
446
455
|
/** Whether `footerAction.disabledWhen` holds. */
|
|
447
456
|
footerDisabled: false,
|
|
448
457
|
footerReasonId: `cn-record-list-footer-reason-${++footerSeq}`,
|
|
458
|
+
/** The first `emptyTexts` entry whose condition holds, translated; '' for none. */
|
|
459
|
+
conditionalEmptyText: '',
|
|
449
460
|
}
|
|
450
461
|
},
|
|
451
462
|
|
|
@@ -731,10 +742,58 @@ export default {
|
|
|
731
742
|
},
|
|
732
743
|
|
|
733
744
|
/** @return {string} The empty line. */
|
|
745
|
+
/**
|
|
746
|
+
* `noteAction` with its labels through the host translate, or null
|
|
747
|
+
* (`presentation: "notes"`).
|
|
748
|
+
*
|
|
749
|
+
* @spec openspec/changes/record-list-notes/specs/record-list-notes/spec.md#requirement-a-record-list-can-collect-and-record-a-note-per-row
|
|
750
|
+
* @return {object|null}
|
|
751
|
+
*/
|
|
752
|
+
noteAction() {
|
|
753
|
+
const action = this.content?.noteAction
|
|
754
|
+
if (!action || typeof action !== 'object' || (!action.urlField && !action.url)) {
|
|
755
|
+
return null
|
|
756
|
+
}
|
|
757
|
+
const tr = (text) => (typeof text === 'string' && text !== '' ? this.translate(text) : '')
|
|
758
|
+
return {
|
|
759
|
+
...action,
|
|
760
|
+
label: tr(action.label),
|
|
761
|
+
fieldLabel: tr(action.fieldLabel),
|
|
762
|
+
placeholder: tr(action.placeholder),
|
|
763
|
+
readyLabel: tr(action.readyLabel),
|
|
764
|
+
errorText: tr(action.errorText),
|
|
765
|
+
}
|
|
766
|
+
},
|
|
767
|
+
|
|
768
|
+
/**
|
|
769
|
+
* `noteAction.recordAll` translated, its cancel link as a route, or null.
|
|
770
|
+
*
|
|
771
|
+
* @return {object|null}
|
|
772
|
+
*/
|
|
773
|
+
noteRecordAll() {
|
|
774
|
+
const all = this.content?.noteAction?.recordAll
|
|
775
|
+
if (!all || typeof all !== 'object' || !all.label) {
|
|
776
|
+
return null
|
|
777
|
+
}
|
|
778
|
+
const cancel = all.cancel && all.cancel.label && all.cancel.route
|
|
779
|
+
? { label: this.translate(all.cancel.label), to: typeof all.cancel.route === 'string' ? { name: all.cancel.route } : all.cancel.route }
|
|
780
|
+
: null
|
|
781
|
+
return {
|
|
782
|
+
label: this.translate(all.label),
|
|
783
|
+
title: all.title ? this.translate(all.title) : '',
|
|
784
|
+
text: all.text ? this.translate(all.text) : '',
|
|
785
|
+
icon: all.icon || '',
|
|
786
|
+
cancel,
|
|
787
|
+
}
|
|
788
|
+
},
|
|
789
|
+
|
|
734
790
|
emptyLabel() {
|
|
735
791
|
if (this.lensReasonText !== '') {
|
|
736
792
|
return this.lensReasonText
|
|
737
793
|
}
|
|
794
|
+
if (this.conditionalEmptyText !== '') {
|
|
795
|
+
return this.conditionalEmptyText
|
|
796
|
+
}
|
|
738
797
|
return this.content?.emptyText ? this.translate(this.content.emptyText) : t('nextcloud-vue', 'Nothing here yet')
|
|
739
798
|
},
|
|
740
799
|
|
|
@@ -754,6 +813,10 @@ export default {
|
|
|
754
813
|
this.fetchRows()
|
|
755
814
|
},
|
|
756
815
|
|
|
816
|
+
rows() {
|
|
817
|
+
this.evaluateEmptyTexts()
|
|
818
|
+
},
|
|
819
|
+
|
|
757
820
|
footerDisabledKey: {
|
|
758
821
|
immediate: true,
|
|
759
822
|
handler() {
|
|
@@ -839,6 +902,38 @@ export default {
|
|
|
839
902
|
* @spec openspec/changes/record-list-footer-action/specs/record-list/spec.md#requirement-a-record-list-can-end-with-an-action-that-says-why-it-is-disabled
|
|
840
903
|
* @return {Promise<void>}
|
|
841
904
|
*/
|
|
905
|
+
/**
|
|
906
|
+
* Pick the empty text (`emptyTexts`: `[{ when, text }]`, the first whose
|
|
907
|
+
* visibleWhen condition holds), for a list that is empty for more than
|
|
908
|
+
* one reason ("Everything you opened today has a note" or "You have not
|
|
909
|
+
* opened anything today"). `emptyText` stays the fallback.
|
|
910
|
+
*
|
|
911
|
+
* @spec openspec/changes/record-list-notes/specs/record-list-notes/spec.md#requirement-a-record-list-can-say-why-it-is-empty
|
|
912
|
+
* @return {Promise<void>}
|
|
913
|
+
*/
|
|
914
|
+
async evaluateEmptyTexts() {
|
|
915
|
+
const options = Array.isArray(this.content?.emptyTexts) ? this.content.emptyTexts : []
|
|
916
|
+
if (options.length === 0 || this.rows.length > 0) {
|
|
917
|
+
this.conditionalEmptyText = ''
|
|
918
|
+
return
|
|
919
|
+
}
|
|
920
|
+
const run = this.emptyRun = (this.emptyRun || 0) + 1
|
|
921
|
+
for (const option of options) {
|
|
922
|
+
if (!option || typeof option.text !== 'string' || !option.when) {
|
|
923
|
+
continue
|
|
924
|
+
}
|
|
925
|
+
const holds = await evaluateVisibleWhen(option.when, this.tokenCtx)
|
|
926
|
+
if (run !== this.emptyRun) {
|
|
927
|
+
return
|
|
928
|
+
}
|
|
929
|
+
if (holds) {
|
|
930
|
+
this.conditionalEmptyText = this.translate(option.text)
|
|
931
|
+
return
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
this.conditionalEmptyText = ''
|
|
935
|
+
},
|
|
936
|
+
|
|
842
937
|
async evaluateFooterDisabled() {
|
|
843
938
|
const cond = this.content?.footerAction?.disabledWhen
|
|
844
939
|
if (!cond) {
|
|
@@ -1103,7 +1198,10 @@ export default {
|
|
|
1103
1198
|
},
|
|
1104
1199
|
|
|
1105
1200
|
rowLink(row) {
|
|
1106
|
-
|
|
1201
|
+
// `rowRouteField`: the route name read from the row, for a list
|
|
1202
|
+
// whose rows open different pages (a case and a task).
|
|
1203
|
+
const byField = this.content?.rowRouteField ? readPath(row, this.content.rowRouteField) : null
|
|
1204
|
+
const route = (typeof byField === 'string' && byField !== '') ? byField : this.content?.rowRoute
|
|
1107
1205
|
if (!route || !this.$router || !row) {
|
|
1108
1206
|
return null
|
|
1109
1207
|
}
|