@conduction/nextcloud-vue 2.56.0-beta.31 → 2.56.0-beta.32
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/CnBannerWidget/CnBannerWidget.vue2.js +55 -4
- package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue3.js +51 -15
- package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +13 -2
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +63 -52
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
- package/dist/esm/components/CnChipsFieldWidget/CnChipsFieldWidget.vue.js +10 -0
- package/dist/esm/components/CnChipsFieldWidget/CnChipsFieldWidget.vue.js.map +1 -0
- package/dist/esm/components/CnChipsFieldWidget/CnChipsFieldWidget.vue2.js +252 -0
- package/dist/esm/components/CnChipsFieldWidget/CnChipsFieldWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnChipsFieldWidget/CnChipsFieldWidget.vue3.js +140 -0
- package/dist/esm/components/CnChipsFieldWidget/CnChipsFieldWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnChipsFieldWidget/index.js +26 -0
- package/dist/esm/components/CnChipsFieldWidget/index.js.map +1 -0
- package/dist/esm/components/CnChoicePickerWidget/CnChoicePickerWidget.vue.js +10 -0
- package/dist/esm/components/CnChoicePickerWidget/CnChoicePickerWidget.vue.js.map +1 -0
- package/dist/esm/components/CnChoicePickerWidget/CnChoicePickerWidget.vue2.js +354 -0
- package/dist/esm/components/CnChoicePickerWidget/CnChoicePickerWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnChoicePickerWidget/CnChoicePickerWidget.vue3.js +172 -0
- package/dist/esm/components/CnChoicePickerWidget/CnChoicePickerWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnChoicePickerWidget/index.js +27 -0
- package/dist/esm/components/CnChoicePickerWidget/index.js.map +1 -0
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +25 -2
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +2 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +3 -2
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +29 -0
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +20 -3
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js +7 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue2.js +77 -3
- package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue3.js +282 -69
- package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +0 -1
- 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/CnObjectListWidget/CnObjectListWidget.vue2.js +65 -2
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js +6 -2
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue.js +10 -0
- package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue.js.map +1 -0
- package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue2.js +174 -0
- package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue3.js +75 -0
- package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnRadioCardsWidget/index.js +28 -0
- package/dist/esm/components/CnRadioCardsWidget/index.js.map +1 -0
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +18 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js +34 -25
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnSavedViewsWidget/CnSavedViewsWidget.vue.js +10 -0
- package/dist/esm/components/CnSavedViewsWidget/CnSavedViewsWidget.vue.js.map +1 -0
- package/dist/esm/components/CnSavedViewsWidget/CnSavedViewsWidget.vue2.js +222 -0
- package/dist/esm/components/CnSavedViewsWidget/CnSavedViewsWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnSavedViewsWidget/CnSavedViewsWidget.vue3.js +64 -0
- package/dist/esm/components/CnSavedViewsWidget/CnSavedViewsWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnSavedViewsWidget/index.js +30 -0
- package/dist/esm/components/CnSavedViewsWidget/index.js.map +1 -0
- package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue2.js.map +1 -1
- package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue3.js.map +1 -1
- package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue.js +10 -0
- package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue.js.map +1 -0
- package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue2.js +347 -0
- package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue3.js +184 -0
- package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnSettingsFormWidget/index.js +27 -0
- package/dist/esm/components/CnSettingsFormWidget/index.js.map +1 -0
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js +87 -6
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue.js +10 -0
- package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue.js.map +1 -0
- package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue2.js +159 -0
- package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue3.js +60 -0
- package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnSwitchRowsWidget/index.js +26 -0
- package/dist/esm/components/CnSwitchRowsWidget/index.js.map +1 -0
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +47 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js +16 -14
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue2.js +80 -1
- package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue3.js +75 -32
- package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue.js +10 -0
- package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue.js.map +1 -0
- package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue2.js +269 -0
- package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue3.js +106 -0
- package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnToggleTableWidget/index.js +27 -0
- package/dist/esm/components/CnToggleTableWidget/index.js.map +1 -0
- package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +7 -0
- package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
- package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue3.js +4 -2
- package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue3.js.map +1 -1
- package/dist/esm/index.js +7 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/l10n/en.json.js +1 -0
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +1 -0
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/nextcloud-vue.css +843 -4
- package/dist/esm/utils/contentHeaderLink.js +51 -0
- package/dist/esm/utils/contentHeaderLink.js.map +1 -0
- package/dist/esm/utils/fetchFilterCounts.js +40 -5
- package/dist/esm/utils/fetchFilterCounts.js.map +1 -1
- package/dist/esm/utils/initials.js +32 -0
- package/dist/esm/utils/initials.js.map +1 -0
- package/dist/esm/utils/libraryWidgetKeys.js +7 -0
- package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
- package/dist/esm/utils/objectWrite.js +59 -0
- package/dist/esm/utils/objectWrite.js.map +1 -0
- package/dist/esm/utils/widgetDispatch.js +1 -1
- package/dist/esm/utils/widgetDispatch.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +10510 -6830
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +843 -4
- package/l10n/en.json +1 -0
- package/l10n/nl.json +1 -0
- package/package.json +1 -1
- package/src/components/CnBannerWidget/CnBannerWidget.vue +93 -13
- package/src/components/CnCellRenderer/CnCellRenderer.md +2 -0
- package/src/components/CnCellRenderer/CnCellRenderer.vue +43 -3
- package/src/components/CnChipsFieldWidget/CnChipsFieldWidget.vue +433 -0
- package/src/components/CnChipsFieldWidget/index.js +25 -0
- package/src/components/CnChoicePickerWidget/CnChoicePickerWidget.vue +556 -0
- package/src/components/CnChoicePickerWidget/index.js +26 -0
- package/src/components/CnDashboardPage/CnDashboardPage.vue +38 -2
- package/src/components/CnDataTable/CnDataTable.vue +2 -1
- package/src/components/CnDetailPage/CnDetailPage.vue +45 -2
- package/src/components/CnFactsWidget/CnFactsWidget.vue +18 -3
- package/src/components/CnFieldListWidget/CnFieldListWidget.vue +265 -4
- package/src/components/CnIndexPage/CnIndexPage.vue +0 -1
- package/src/components/CnObjectListWidget/CnObjectListWidget.vue +69 -2
- package/src/components/CnRadioCardsWidget/CnRadioCardsWidget.vue +313 -0
- package/src/components/CnRadioCardsWidget/index.js +27 -0
- package/src/components/CnRecordListWidget/CnRecordListWidget.vue +43 -1
- package/src/components/CnSavedViewsWidget/CnSavedViewsWidget.vue +296 -0
- package/src/components/CnSavedViewsWidget/index.js +29 -0
- package/src/components/CnSegmentedControl/CnSegmentedControl.vue +13 -0
- package/src/components/CnSettingsFormWidget/CnSettingsFormWidget.vue +555 -0
- package/src/components/CnSettingsFormWidget/index.js +26 -0
- package/src/components/CnStatWidget/CnStatWidget.vue +87 -6
- package/src/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue +226 -0
- package/src/components/CnSwitchRowsWidget/index.js +25 -0
- package/src/components/CnTabsWidget/CnTabsWidget.vue +82 -3
- package/src/components/CnTasksWidget/CnTasksWidget.vue +175 -15
- package/src/components/CnToggleTableWidget/CnToggleTableWidget.vue +386 -0
- package/src/components/CnToggleTableWidget/index.js +26 -0
- package/src/components/CnWidgetGrid/registerDashboardWidgets.js +7 -0
- package/src/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue +28 -1
- package/src/components/index.js +7 -0
- package/src/css/look-board-detail.css +36 -2
- package/src/css/look-board-index.css +23 -0
- package/src/css/look-board.css +72 -0
- package/src/index.js +7 -0
- package/src/utils/contentHeaderLink.js +48 -0
- package/src/utils/fetchFilterCounts.js +39 -4
- package/src/utils/initials.js +29 -0
- package/src/utils/libraryWidgetKeys.js +7 -0
- package/src/utils/objectWrite.js +55 -0
- package/src/utils/widgetDispatch.js +1 -1
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
- SPDX-License-Identifier: EUPL-1.2
|
|
4
4
|
-->
|
|
5
5
|
<template>
|
|
6
|
-
<div class="cn-tasks-widget">
|
|
6
|
+
<div class="cn-tasks-widget" :class="{ 'cn-tasks-widget--list': isList }">
|
|
7
7
|
<!-- One quiet line on failure, never a leaked axios status string
|
|
8
8
|
(ADR-062). The real error goes to the console. -->
|
|
9
9
|
<p v-if="error" class="cn-tasks-widget__error">
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
<!-- The count states the server TOTAL, never the rendered length
|
|
22
22
|
(ADR-062): the number a person acts on is how much work
|
|
23
23
|
exists, not how much fits the cell. -->
|
|
24
|
-
<p class="cn-tasks-widget__count">
|
|
24
|
+
<p v-if="!isList" class="cn-tasks-widget__count">
|
|
25
25
|
{{ countLabel }}
|
|
26
26
|
</p>
|
|
27
27
|
<ul class="cn-tasks-widget__list">
|
|
@@ -33,23 +33,53 @@
|
|
|
33
33
|
@click="onRowClick(task, $event)"
|
|
34
34
|
@mousedown="preventMiddleClickAutoscroll"
|
|
35
35
|
@auxclick="onRowAuxClick(task, $event)">
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
36
|
+
<!-- List variant: a checkbox completes the viewer's own task
|
|
37
|
+
(the task with a declared outcome list keeps the menu, as
|
|
38
|
+
a checkbox cannot choose an outcome). Other rows get a
|
|
39
|
+
spacer so the titles stay aligned. -->
|
|
40
|
+
<template v-if="isList">
|
|
41
|
+
<input
|
|
42
|
+
v-if="canCheck(task)"
|
|
43
|
+
type="checkbox"
|
|
44
|
+
class="cn-tasks-widget__check"
|
|
45
|
+
:checked="completing.includes(String(task.uuid))"
|
|
46
|
+
:disabled="completing.includes(String(task.uuid))"
|
|
47
|
+
:aria-label="tr('Mark {title} as done').replace('{title}', titleOf(task))"
|
|
48
|
+
data-testid="cn-tasks-widget-check"
|
|
49
|
+
@click.stop
|
|
50
|
+
@change="check(task)">
|
|
51
|
+
<span v-else class="cn-tasks-widget__check-spacer" aria-hidden="true" />
|
|
52
|
+
<span class="cn-tasks-widget__body">
|
|
53
|
+
<span class="cn-tasks-widget__name">{{ titleOf(task) }}</span>
|
|
54
|
+
<span class="cn-tasks-widget__meta cn-tasks-widget__meta--split">
|
|
55
|
+
<span class="cn-tasks-widget__subject">{{ subjectOf(task) }}</span>
|
|
56
|
+
<span
|
|
57
|
+
class="cn-tasks-widget__due"
|
|
58
|
+
:class="{ 'cn-tasks-widget__due--overdue': task.overdue === true }">
|
|
59
|
+
{{ dueLabel(task) }}
|
|
60
|
+
</span>
|
|
61
|
+
</span>
|
|
62
|
+
</span>
|
|
63
|
+
</template>
|
|
64
|
+
<template v-else>
|
|
65
|
+
<span class="cn-tasks-widget__body">
|
|
66
|
+
<span class="cn-tasks-widget__name">{{ titleOf(task) }}</span>
|
|
67
|
+
<span class="cn-tasks-widget__meta">{{ metaLine(task) }}</span>
|
|
68
|
+
</span>
|
|
69
|
+
<!-- Overdue is carried by the WORDING (taskDueLabel) and by
|
|
70
|
+
weight; the colour only reinforces it. -->
|
|
71
|
+
<span
|
|
72
|
+
class="cn-tasks-widget__due"
|
|
73
|
+
:class="{ 'cn-tasks-widget__due--overdue': task.overdue === true }">
|
|
74
|
+
{{ dueLabel(task) }}
|
|
75
|
+
</span>
|
|
76
|
+
</template>
|
|
47
77
|
<!-- Only verbs the contract can accept are offered; the
|
|
48
78
|
server still authorizes, and a refusal is toasted in
|
|
49
79
|
its own words. Stopped clicks: the menu must not also
|
|
50
80
|
open the row. -->
|
|
51
81
|
<NcActions
|
|
52
|
-
v-if="canClaim(task) || canComplete(task)"
|
|
82
|
+
v-if="(canClaim(task) || canComplete(task)) && !(isList && canCheck(task))"
|
|
53
83
|
class="cn-tasks-widget__actions"
|
|
54
84
|
:forceMenu="true"
|
|
55
85
|
:aria-label="tr('Task actions')"
|
|
@@ -90,6 +120,7 @@ import { NcActionButton, NcActions, NcLoadingIcon } from '@nextcloud/vue'
|
|
|
90
120
|
import { inject, ref } from 'vue'
|
|
91
121
|
import { taskDeepLink, taskDueLabel } from '../../composables/indexSources.js'
|
|
92
122
|
import { useEndpointSource } from '../../composables/useEndpointSource.js'
|
|
123
|
+
import { contentHeaderLink } from '../../utils/contentHeaderLink.js'
|
|
93
124
|
import { openRowTarget } from '../../utils/linkNavigation.js'
|
|
94
125
|
import { isRowMiddleClick, preventMiddleClickAutoscroll } from '../../utils/rowAuxClick.js'
|
|
95
126
|
|
|
@@ -185,8 +216,14 @@ export default {
|
|
|
185
216
|
* receiving the task uuid as `:id`. Omitted = the click opens the
|
|
186
217
|
* task's openregister deep link instead.
|
|
187
218
|
* - `emptyText` — override for the empty-inbox line.
|
|
219
|
+
* - `variant` — `list` draws a checkbox list: a checkbox that completes
|
|
220
|
+
* the viewer's own task, the title, the subject on a second muted
|
|
221
|
+
* line and the due date at its right. Default: the compact rows.
|
|
222
|
+
* - `viewAllRoute` / `viewAllLabel` — a "View all" text link in the
|
|
223
|
+
* card header: a route name or location `{ name, params?, query? }`,
|
|
224
|
+
* and its label (default "View all"). `headerLink` works too.
|
|
188
225
|
*
|
|
189
|
-
* @type {{scope?: string, limit?: number, pollSeconds?: number, rowRoute?: string, emptyText?: string}}
|
|
226
|
+
* @type {{scope?: string, limit?: number, pollSeconds?: number, rowRoute?: string, emptyText?: string, variant?: string, viewAllRoute?: (string|object), viewAllLabel?: string}}
|
|
190
227
|
*/
|
|
191
228
|
content: {
|
|
192
229
|
type: Object,
|
|
@@ -214,6 +251,8 @@ export default {
|
|
|
214
251
|
},
|
|
215
252
|
},
|
|
216
253
|
|
|
254
|
+
emits: ['header'],
|
|
255
|
+
|
|
217
256
|
setup(props) {
|
|
218
257
|
// The shared endpoint engine owns request dedup, the short-TTL cache
|
|
219
258
|
// and the cn:page:refresh / cn:widget:refresh subscriptions — the
|
|
@@ -253,10 +292,30 @@ export default {
|
|
|
253
292
|
data() {
|
|
254
293
|
return {
|
|
255
294
|
pollTimer: null,
|
|
295
|
+
/** Uuids of the tasks a checkbox is completing, until the next read. */
|
|
296
|
+
completing: [],
|
|
256
297
|
}
|
|
257
298
|
},
|
|
258
299
|
|
|
259
300
|
computed: {
|
|
301
|
+
/**
|
|
302
|
+
* Whether the rows draw as the checkbox list (`content.variant: 'list'`).
|
|
303
|
+
*
|
|
304
|
+
* @return {boolean}
|
|
305
|
+
*/
|
|
306
|
+
isList() {
|
|
307
|
+
return this.content.variant === 'list'
|
|
308
|
+
},
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* The header "View all" link the content declares, or null.
|
|
312
|
+
*
|
|
313
|
+
* @return {object|null}
|
|
314
|
+
*/
|
|
315
|
+
headerLink() {
|
|
316
|
+
return contentHeaderLink(this.content)
|
|
317
|
+
},
|
|
318
|
+
|
|
260
319
|
/**
|
|
261
320
|
* Effective translate function: the explicit prop, then the injected
|
|
262
321
|
* host one, then `@nextcloud/l10n` under the library's own app id.
|
|
@@ -357,6 +416,19 @@ export default {
|
|
|
357
416
|
},
|
|
358
417
|
},
|
|
359
418
|
|
|
419
|
+
watch: {
|
|
420
|
+
headerLink: {
|
|
421
|
+
immediate: true,
|
|
422
|
+
handler(link) {
|
|
423
|
+
/**
|
|
424
|
+
* @event header Emitted with the card header's parts: the "View all" link, if any.
|
|
425
|
+
* @type {{link: (object|null), note: string}}
|
|
426
|
+
*/
|
|
427
|
+
this.$emit('header', { link, note: '' })
|
|
428
|
+
},
|
|
429
|
+
},
|
|
430
|
+
},
|
|
431
|
+
|
|
360
432
|
mounted() {
|
|
361
433
|
this.startPolling()
|
|
362
434
|
document.addEventListener('visibilitychange', this.onVisibilityChange)
|
|
@@ -423,6 +495,43 @@ export default {
|
|
|
423
495
|
return task.displayTitle || task.title || String(task.uuid || '')
|
|
424
496
|
},
|
|
425
497
|
|
|
498
|
+
/**
|
|
499
|
+
* The subject (the case a task sits on), for the list variant's second line.
|
|
500
|
+
*
|
|
501
|
+
* @param {object} task The task row.
|
|
502
|
+
* @return {string} The subject's title, or ''.
|
|
503
|
+
*/
|
|
504
|
+
subjectOf(task) {
|
|
505
|
+
return task.subject && task.subject.title ? String(task.subject.title) : ''
|
|
506
|
+
},
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* Whether the list variant draws a checkbox on the row: the viewer's own
|
|
510
|
+
* open task, with no outcome list to choose from.
|
|
511
|
+
*
|
|
512
|
+
* @param {object} task The task row.
|
|
513
|
+
* @return {boolean}
|
|
514
|
+
*/
|
|
515
|
+
canCheck(task) {
|
|
516
|
+
return this.canComplete(task) && !(Array.isArray(task.outcomes) && task.outcomes.length > 0)
|
|
517
|
+
},
|
|
518
|
+
|
|
519
|
+
/**
|
|
520
|
+
* Complete a task from its checkbox, with the server's default outcome.
|
|
521
|
+
*
|
|
522
|
+
* @param {object} task The task row.
|
|
523
|
+
* @return {Promise<void>}
|
|
524
|
+
*/
|
|
525
|
+
async check(task) {
|
|
526
|
+
const uuid = String(task.uuid || '')
|
|
527
|
+
if (uuid === '' || this.completing.includes(uuid)) {
|
|
528
|
+
return
|
|
529
|
+
}
|
|
530
|
+
this.completing = [...this.completing, uuid]
|
|
531
|
+
await this.postVerb(task, 'complete', { outcome: 'done' })
|
|
532
|
+
this.completing = this.completing.filter((id) => id !== uuid)
|
|
533
|
+
},
|
|
534
|
+
|
|
426
535
|
/**
|
|
427
536
|
* The secondary line: subject, state and priority — whichever of
|
|
428
537
|
* those the row actually carries, in words.
|
|
@@ -721,6 +830,57 @@ function normaliseScope(raw) {
|
|
|
721
830
|
font-variant-numeric: tabular-nums;
|
|
722
831
|
}
|
|
723
832
|
|
|
833
|
+
/* List variant, after the dashboard board's My tasks card: a checkbox, the
|
|
834
|
+
title, the subject on a muted second line and the due date at its right. */
|
|
835
|
+
.cn-tasks-widget--list .cn-tasks-widget__row {
|
|
836
|
+
align-items: flex-start;
|
|
837
|
+
gap: 12px;
|
|
838
|
+
padding: 12px 0;
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
.cn-tasks-widget--list .cn-tasks-widget__name {
|
|
842
|
+
font-weight: 400;
|
|
843
|
+
font-size: 15px;
|
|
844
|
+
line-height: 1.35;
|
|
845
|
+
color: var(--color-main-text);
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
.cn-tasks-widget__check,
|
|
849
|
+
.cn-tasks-widget__check-spacer {
|
|
850
|
+
flex: none;
|
|
851
|
+
width: 20px;
|
|
852
|
+
height: 20px;
|
|
853
|
+
margin: 1px 0 0;
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
.cn-tasks-widget__check {
|
|
857
|
+
accent-color: var(--color-primary-element);
|
|
858
|
+
cursor: pointer;
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
.cn-tasks-widget__meta--split {
|
|
862
|
+
display: flex;
|
|
863
|
+
justify-content: space-between;
|
|
864
|
+
gap: 8px;
|
|
865
|
+
font-size: 13px;
|
|
866
|
+
overflow: visible;
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
.cn-tasks-widget__subject {
|
|
870
|
+
min-width: 0;
|
|
871
|
+
overflow: hidden;
|
|
872
|
+
text-overflow: ellipsis;
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
.cn-tasks-widget--list .cn-tasks-widget__due {
|
|
876
|
+
font-size: 13px;
|
|
877
|
+
font-weight: 500;
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
.cn-tasks-widget--list .cn-tasks-widget__due--overdue {
|
|
881
|
+
font-weight: 600;
|
|
882
|
+
}
|
|
883
|
+
|
|
724
884
|
/* Overdue: weight AND colour on top of the wording, which already says
|
|
725
885
|
"Overdue by N days" — the signal survives monochrome. */
|
|
726
886
|
.cn-tasks-widget__due--overdue {
|
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
- SPDX-FileCopyrightText: 2026 Conduction B.V.
|
|
3
|
+
- SPDX-License-Identifier: EUPL-1.2
|
|
4
|
+
-->
|
|
5
|
+
|
|
6
|
+
<template>
|
|
7
|
+
<div class="cn-toggle-table" data-testid="cn-toggle-table">
|
|
8
|
+
<p v-if="rows.length === 0" class="cn-toggle-table__empty" data-testid="cn-toggle-table-empty">
|
|
9
|
+
{{ emptyLabel }}
|
|
10
|
+
</p>
|
|
11
|
+
<table v-else class="cn-toggle-table__table">
|
|
12
|
+
<thead v-if="showHeader">
|
|
13
|
+
<tr>
|
|
14
|
+
<th scope="col" class="cn-toggle-table__head">
|
|
15
|
+
{{ titleHeader }}
|
|
16
|
+
</th>
|
|
17
|
+
<th
|
|
18
|
+
v-for="column in columns"
|
|
19
|
+
:key="column.path"
|
|
20
|
+
scope="col"
|
|
21
|
+
class="cn-toggle-table__head">
|
|
22
|
+
{{ column.header }}
|
|
23
|
+
</th>
|
|
24
|
+
<th scope="col" class="cn-toggle-table__head cn-toggle-table__head--toggle">
|
|
25
|
+
{{ toggleHeader }}
|
|
26
|
+
</th>
|
|
27
|
+
</tr>
|
|
28
|
+
</thead>
|
|
29
|
+
<tbody>
|
|
30
|
+
<tr
|
|
31
|
+
v-for="row in rows"
|
|
32
|
+
:key="row.key"
|
|
33
|
+
class="cn-toggle-table__row"
|
|
34
|
+
:data-testid="`cn-toggle-table-row-${row.key}`">
|
|
35
|
+
<td class="cn-toggle-table__cell cn-toggle-table__cell--main">
|
|
36
|
+
<span class="cn-toggle-table__title">{{ row.title }}</span>
|
|
37
|
+
<span v-if="row.hint" class="cn-toggle-table__hint">{{ row.hint }}</span>
|
|
38
|
+
</td>
|
|
39
|
+
<td
|
|
40
|
+
v-for="column in columns"
|
|
41
|
+
:key="column.path"
|
|
42
|
+
class="cn-toggle-table__cell">
|
|
43
|
+
{{ cell(row, column) }}
|
|
44
|
+
</td>
|
|
45
|
+
<td class="cn-toggle-table__cell cn-toggle-table__cell--toggle">
|
|
46
|
+
<NcCheckboxRadioSwitch
|
|
47
|
+
:key="`${row.key}-${undone[row.key] || 0}`"
|
|
48
|
+
:modelValue="isOn(row)"
|
|
49
|
+
type="switch"
|
|
50
|
+
:disabled="readOnly || busy === row.key"
|
|
51
|
+
:aria-label="row.title"
|
|
52
|
+
@update:modelValue="toggle(row, $event)" />
|
|
53
|
+
</td>
|
|
54
|
+
</tr>
|
|
55
|
+
</tbody>
|
|
56
|
+
</table>
|
|
57
|
+
<NcNoteCard v-if="notice" type="error" data-testid="cn-toggle-table-notice">
|
|
58
|
+
{{ notice }}
|
|
59
|
+
</NcNoteCard>
|
|
60
|
+
</div>
|
|
61
|
+
</template>
|
|
62
|
+
|
|
63
|
+
<script>
|
|
64
|
+
import { NcCheckboxRadioSwitch, NcNoteCard } from '@nextcloud/vue'
|
|
65
|
+
import { prefixUrl } from '../../utils/headers.js'
|
|
66
|
+
import { objectUrlOf, patchObject } from '../../utils/objectWrite.js'
|
|
67
|
+
import { readPath } from '../../utils/readPath.js'
|
|
68
|
+
import { serverErrorMessage } from '../../utils/serverErrorMessage.js'
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* CnToggleTableWidget — the `toggle-table` widget: rows from a configured list
|
|
72
|
+
* (`rows`) or read from a source (`source`), each with a switch bound to the
|
|
73
|
+
* CURRENT object, and optional extra columns read off the row. A switch is
|
|
74
|
+
* stored in one of two ways:
|
|
75
|
+
*
|
|
76
|
+
* - `bind.mode: 'flag'` — each row names its own boolean field of the object
|
|
77
|
+
* (`row.field`); the switch is that field.
|
|
78
|
+
* - `bind.mode: 'list'` — ONE array field of the object (`bind.field`) holds
|
|
79
|
+
* the keys of the rows that are in it; the switch is on when the row's key is
|
|
80
|
+
* in the list, or NOT in it with `bind.invert` (a list of hidden items shown
|
|
81
|
+
* as "visible" switches).
|
|
82
|
+
*
|
|
83
|
+
* A toggle saves at once with ONE PATCH of that field; a refused PATCH puts the
|
|
84
|
+
* switch back and shows the server's words.
|
|
85
|
+
*
|
|
86
|
+
* @spec openspec/changes/abstract-toggle-table/specs/toggle-table/spec.md
|
|
87
|
+
*/
|
|
88
|
+
export default {
|
|
89
|
+
name: 'CnToggleTableWidget',
|
|
90
|
+
|
|
91
|
+
components: { NcCheckboxRadioSwitch, NcNoteCard },
|
|
92
|
+
|
|
93
|
+
inject: {
|
|
94
|
+
cnTranslate: { default: () => (key) => key },
|
|
95
|
+
},
|
|
96
|
+
|
|
97
|
+
props: {
|
|
98
|
+
/**
|
|
99
|
+
* The widget configuration.
|
|
100
|
+
*
|
|
101
|
+
* - `bind` — `{ mode: 'flag' }` (default) or `{ mode: 'list', field, invert? }`.
|
|
102
|
+
* - `rows` — static rows `[{ key, label, hint?, field?, ...extra }]`.
|
|
103
|
+
* - `source` — `{ url, path?, key, label, hint? }`: rows read from an endpoint (tokens `@objectId`, `@object.<field>`), in addition to `rows`.
|
|
104
|
+
* - `columns` — extra columns `[{ header, path, map? }]` read off each row.
|
|
105
|
+
* - `titleHeader`, `toggleHeader`, `emptyText`, `readOnly`.
|
|
106
|
+
*
|
|
107
|
+
* @type {object}
|
|
108
|
+
*/
|
|
109
|
+
content: {
|
|
110
|
+
type: Object,
|
|
111
|
+
default: () => ({}),
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
/** The current detail-page object. */
|
|
115
|
+
objectData: {
|
|
116
|
+
type: Object,
|
|
117
|
+
default: null,
|
|
118
|
+
},
|
|
119
|
+
|
|
120
|
+
/** The current object id. */
|
|
121
|
+
objectId: {
|
|
122
|
+
type: [String, Number],
|
|
123
|
+
default: '',
|
|
124
|
+
},
|
|
125
|
+
|
|
126
|
+
/** OpenRegister register slug of the surface. */
|
|
127
|
+
register: {
|
|
128
|
+
type: [String, Object],
|
|
129
|
+
default: '',
|
|
130
|
+
},
|
|
131
|
+
|
|
132
|
+
/** OpenRegister schema slug of the surface. */
|
|
133
|
+
schema: {
|
|
134
|
+
type: [String, Object],
|
|
135
|
+
default: '',
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
|
|
139
|
+
emits: ['saved'],
|
|
140
|
+
|
|
141
|
+
data() {
|
|
142
|
+
return {
|
|
143
|
+
/** Rows read from `source`. */
|
|
144
|
+
sourced: [],
|
|
145
|
+
/** What the object holds, by field: the flags and the list, as last saved. */
|
|
146
|
+
saved: {},
|
|
147
|
+
busy: '',
|
|
148
|
+
notice: '',
|
|
149
|
+
/** Per row, how often a refused toggle forced the switch to be drawn again from the stored value. */
|
|
150
|
+
undone: {},
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
|
|
154
|
+
computed: {
|
|
155
|
+
bind() {
|
|
156
|
+
const bind = this.content.bind || {}
|
|
157
|
+
return bind.mode === 'list' ? { mode: 'list', field: bind.field, invert: bind.invert === true } : { mode: 'flag' }
|
|
158
|
+
},
|
|
159
|
+
|
|
160
|
+
/** The drawn rows: configured first, then sourced, each with a key, title and hint. */
|
|
161
|
+
rows() {
|
|
162
|
+
const configured = (Array.isArray(this.content.rows) ? this.content.rows : [])
|
|
163
|
+
.filter((row) => row && row.key !== undefined)
|
|
164
|
+
.map((row) => ({ ...row, key: String(row.key), title: this.tr(row.label || String(row.key)), hint: this.tr(row.hint || '') }))
|
|
165
|
+
return [...configured, ...this.sourced]
|
|
166
|
+
},
|
|
167
|
+
|
|
168
|
+
columns() {
|
|
169
|
+
return (Array.isArray(this.content.columns) ? this.content.columns : [])
|
|
170
|
+
.filter((column) => column && column.path)
|
|
171
|
+
.map((column) => ({ ...column, header: this.tr(column.header || '') }))
|
|
172
|
+
},
|
|
173
|
+
|
|
174
|
+
showHeader() {
|
|
175
|
+
return this.columns.length > 0 || Boolean(this.content.titleHeader || this.content.toggleHeader)
|
|
176
|
+
},
|
|
177
|
+
|
|
178
|
+
titleHeader() {
|
|
179
|
+
return this.tr(this.content.titleHeader || '')
|
|
180
|
+
},
|
|
181
|
+
|
|
182
|
+
toggleHeader() {
|
|
183
|
+
return this.tr(this.content.toggleHeader || '')
|
|
184
|
+
},
|
|
185
|
+
|
|
186
|
+
emptyLabel() {
|
|
187
|
+
return this.tr(this.content.emptyText || 'Nothing listed yet')
|
|
188
|
+
},
|
|
189
|
+
|
|
190
|
+
readOnly() {
|
|
191
|
+
return this.content.readOnly === true
|
|
192
|
+
},
|
|
193
|
+
|
|
194
|
+
objectUrl() {
|
|
195
|
+
return objectUrlOf(this.register, this.schema, this.objectId || (this.objectData && this.objectData.id))
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
|
|
199
|
+
watch: {
|
|
200
|
+
objectData() {
|
|
201
|
+
this.saved = {}
|
|
202
|
+
this.loadSource()
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
|
|
206
|
+
created() {
|
|
207
|
+
this.loadSource()
|
|
208
|
+
},
|
|
209
|
+
|
|
210
|
+
methods: {
|
|
211
|
+
tr(text) {
|
|
212
|
+
if (typeof text !== 'string' || text === '') {
|
|
213
|
+
return ''
|
|
214
|
+
}
|
|
215
|
+
return typeof this.cnTranslate === 'function' ? this.cnTranslate(text) : text
|
|
216
|
+
},
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* The stored value of a field: what this widget last saved, else the object's.
|
|
220
|
+
*
|
|
221
|
+
* @param {string} field The field.
|
|
222
|
+
* @return {unknown}
|
|
223
|
+
*/
|
|
224
|
+
stored(field) {
|
|
225
|
+
return Object.hasOwn(this.saved, field) ? this.saved[field] : readPath(this.objectData || {}, field)
|
|
226
|
+
},
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Whether a row's switch is on.
|
|
230
|
+
*
|
|
231
|
+
* @param {object} row The row.
|
|
232
|
+
* @return {boolean}
|
|
233
|
+
*/
|
|
234
|
+
isOn(row) {
|
|
235
|
+
if (this.bind.mode === 'list') {
|
|
236
|
+
const list = this.stored(this.bind.field)
|
|
237
|
+
const inList = Array.isArray(list) && list.map(String).includes(row.key)
|
|
238
|
+
return this.bind.invert ? !inList : inList
|
|
239
|
+
}
|
|
240
|
+
return row.field ? this.stored(row.field) === true : false
|
|
241
|
+
},
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* The text of an extra column for a row.
|
|
245
|
+
*
|
|
246
|
+
* @param {object} row The row.
|
|
247
|
+
* @param {object} column The column.
|
|
248
|
+
* @return {string}
|
|
249
|
+
*/
|
|
250
|
+
cell(row, column) {
|
|
251
|
+
const raw = readPath(row, column.path)
|
|
252
|
+
if (raw === undefined || raw === null || raw === '') {
|
|
253
|
+
return ''
|
|
254
|
+
}
|
|
255
|
+
if (column.map && Object.hasOwn(column.map, String(raw))) {
|
|
256
|
+
return this.tr(column.map[String(raw)])
|
|
257
|
+
}
|
|
258
|
+
return typeof raw === 'object' ? '' : String(raw)
|
|
259
|
+
},
|
|
260
|
+
|
|
261
|
+
/** Read the rows of `source`, resolving tokens in its url. */
|
|
262
|
+
async loadSource() {
|
|
263
|
+
const source = this.content.source
|
|
264
|
+
if (!source || !source.url || !source.key) {
|
|
265
|
+
return
|
|
266
|
+
}
|
|
267
|
+
const object = this.objectData || {}
|
|
268
|
+
const url = String(source.url)
|
|
269
|
+
.replace(/@objectId/g, () => encodeURIComponent(String(this.objectId || object.id || '')))
|
|
270
|
+
.replace(/@object\.([\w.@]+)/g, (m, path) => encodeURIComponent(String(readPath(object, path) ?? '')))
|
|
271
|
+
try {
|
|
272
|
+
const { default: axios } = await import('@nextcloud/axios')
|
|
273
|
+
const { data } = await axios.get(prefixUrl(url))
|
|
274
|
+
const list = source.path ? readPath(data, source.path) : data
|
|
275
|
+
this.sourced = (Array.isArray(list) ? list : [])
|
|
276
|
+
.filter((item) => item && readPath(item, source.key) !== undefined)
|
|
277
|
+
.map((item) => ({
|
|
278
|
+
...item,
|
|
279
|
+
key: String(readPath(item, source.key)),
|
|
280
|
+
title: String(readPath(item, source.label || source.key) ?? readPath(item, source.key)),
|
|
281
|
+
hint: source.hint ? String(readPath(item, source.hint) ?? '') : '',
|
|
282
|
+
}))
|
|
283
|
+
} catch (e) {
|
|
284
|
+
this.notice = serverErrorMessage(e)
|
|
285
|
+
}
|
|
286
|
+
},
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Flip one switch and save that one field.
|
|
290
|
+
*
|
|
291
|
+
* @param {object} row The row.
|
|
292
|
+
* @param {boolean} on The new switch state.
|
|
293
|
+
* @return {Promise<void>}
|
|
294
|
+
* @spec openspec/changes/abstract-toggle-table/specs/toggle-table/spec.md#requirement-a-toggle-saves-one-field
|
|
295
|
+
*/
|
|
296
|
+
async toggle(row, on) {
|
|
297
|
+
const field = this.bind.mode === 'list' ? this.bind.field : row.field
|
|
298
|
+
if (!this.objectUrl || !field) {
|
|
299
|
+
this.notice = this.tr('This switch cannot be saved here.')
|
|
300
|
+
return
|
|
301
|
+
}
|
|
302
|
+
let next
|
|
303
|
+
if (this.bind.mode === 'list') {
|
|
304
|
+
const list = (Array.isArray(this.stored(field)) ? this.stored(field) : []).map(String)
|
|
305
|
+
const wantInList = this.bind.invert ? !on : on
|
|
306
|
+
next = wantInList ? (list.includes(row.key) ? list : [...list, row.key]) : list.filter((key) => key !== row.key)
|
|
307
|
+
} else {
|
|
308
|
+
next = on
|
|
309
|
+
}
|
|
310
|
+
this.notice = ''
|
|
311
|
+
this.busy = row.key
|
|
312
|
+
try {
|
|
313
|
+
const data = await patchObject(this.objectUrl, { [field]: next })
|
|
314
|
+
const written = data[field] !== undefined ? data[field] : next
|
|
315
|
+
this.saved = { ...this.saved, [field]: written }
|
|
316
|
+
/**
|
|
317
|
+
* @event saved A switch was written.
|
|
318
|
+
* @type {{field: string, value: unknown}}
|
|
319
|
+
*/
|
|
320
|
+
this.$emit('saved', { field, value: written })
|
|
321
|
+
} catch (e) {
|
|
322
|
+
this.notice = serverErrorMessage(e)
|
|
323
|
+
this.undone = { ...this.undone, [row.key]: (this.undone[row.key] || 0) + 1 }
|
|
324
|
+
} finally {
|
|
325
|
+
this.busy = ''
|
|
326
|
+
}
|
|
327
|
+
},
|
|
328
|
+
},
|
|
329
|
+
}
|
|
330
|
+
</script>
|
|
331
|
+
|
|
332
|
+
<style scoped>
|
|
333
|
+
.cn-toggle-table {
|
|
334
|
+
display: flex;
|
|
335
|
+
flex-direction: column;
|
|
336
|
+
gap: 8px;
|
|
337
|
+
min-width: 0;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
.cn-toggle-table__table {
|
|
341
|
+
width: 100%;
|
|
342
|
+
border-collapse: collapse;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
.cn-toggle-table__head {
|
|
346
|
+
padding: 8px 12px;
|
|
347
|
+
text-align: start;
|
|
348
|
+
font-size: 13px;
|
|
349
|
+
font-weight: 600;
|
|
350
|
+
color: var(--color-text-maxcontrast);
|
|
351
|
+
background: var(--color-background-hover);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
.cn-toggle-table__head--toggle,
|
|
355
|
+
.cn-toggle-table__cell--toggle {
|
|
356
|
+
width: 1%;
|
|
357
|
+
white-space: nowrap;
|
|
358
|
+
text-align: end;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
.cn-toggle-table__row {
|
|
362
|
+
border-top: 1px solid var(--cn-board-hairline, var(--color-border));
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
.cn-toggle-table__cell {
|
|
366
|
+
padding: 10px 12px;
|
|
367
|
+
vertical-align: middle;
|
|
368
|
+
overflow-wrap: anywhere;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
.cn-toggle-table__title {
|
|
372
|
+
display: block;
|
|
373
|
+
font-weight: 600;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
.cn-toggle-table__hint {
|
|
377
|
+
display: block;
|
|
378
|
+
font-size: 12px;
|
|
379
|
+
color: var(--color-text-maxcontrast);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
.cn-toggle-table__empty {
|
|
383
|
+
margin: 0;
|
|
384
|
+
color: var(--color-text-maxcontrast);
|
|
385
|
+
}
|
|
386
|
+
</style>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPDX-FileCopyrightText: 2026 Conduction B.V.
|
|
3
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
4
|
+
*
|
|
5
|
+
* CnToggleTableWidget — self-registers the `toggle-table` widget type (rows
|
|
6
|
+
* from a configured list or a source, each with a switch bound to the current
|
|
7
|
+
* object, and optional extra columns) into the shared dashboardWidgetRegistry.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import CnToggleTableWidget from './CnToggleTableWidget.vue'
|
|
11
|
+
import { registerDashboardWidget } from '../CnWidgetGrid/dashboardWidgetRegistry.js'
|
|
12
|
+
|
|
13
|
+
registerDashboardWidget('toggle-table', {
|
|
14
|
+
renderer: CnToggleTableWidget,
|
|
15
|
+
form: null,
|
|
16
|
+
defaultContent: {
|
|
17
|
+
bind: { mode: 'flag' },
|
|
18
|
+
rows: [],
|
|
19
|
+
},
|
|
20
|
+
displayName: 'Toggle table',
|
|
21
|
+
icon: 'TableCheck',
|
|
22
|
+
surfaces: ['detail-page'],
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
export { CnToggleTableWidget }
|
|
26
|
+
export default CnToggleTableWidget
|
|
@@ -75,7 +75,14 @@ import '../CnTasksWidget/index.js'
|
|
|
75
75
|
import '../CnSectionsWidget/index.js'
|
|
76
76
|
import '../CnFactsWidget/index.js'
|
|
77
77
|
import '../CnFieldListWidget/index.js'
|
|
78
|
+
import '../CnSettingsFormWidget/index.js'
|
|
79
|
+
import '../CnSwitchRowsWidget/index.js'
|
|
80
|
+
import '../CnRadioCardsWidget/index.js'
|
|
81
|
+
import '../CnChipsFieldWidget/index.js'
|
|
82
|
+
import '../CnToggleTableWidget/index.js'
|
|
83
|
+
import '../CnChoicePickerWidget/index.js'
|
|
78
84
|
import '../CnRecordListWidget/index.js'
|
|
85
|
+
import '../CnSavedViewsWidget/index.js'
|
|
79
86
|
import '../CnNotepadWidget/index.js'
|
|
80
87
|
import '../CnObjectDataWidget/dashboardRegistration.js'
|
|
81
88
|
import '../CnObjectGeoWidget/dashboardRegistration.js'
|