@conduction/nextcloud-vue 2.99.0 → 2.101.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.
Files changed (180) hide show
  1. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue.js +1 -1
  2. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js +6 -1
  3. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js.map +1 -1
  4. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js +2 -1
  5. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js.map +1 -1
  6. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue.js +1 -1
  7. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue2.js +22 -1
  8. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue2.js.map +1 -1
  9. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue3.js +26 -16
  10. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue3.js.map +1 -1
  11. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
  12. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +51 -7
  13. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
  14. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +104 -73
  15. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
  16. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +25 -8
  17. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  18. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +5 -3
  19. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  20. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue.js +1 -1
  21. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js +11 -8
  22. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
  23. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js +13 -4
  24. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
  25. package/dist/esm/components/CnExportsWidget/CnExportsWidget.vue.js +9 -0
  26. package/dist/esm/components/CnExportsWidget/CnExportsWidget.vue.js.map +1 -0
  27. package/dist/esm/components/CnExportsWidget/CnExportsWidget.vue2.js +139 -0
  28. package/dist/esm/components/CnExportsWidget/CnExportsWidget.vue2.js.map +1 -0
  29. package/dist/esm/components/CnExportsWidget/CnExportsWidget.vue3.js +76 -0
  30. package/dist/esm/components/CnExportsWidget/CnExportsWidget.vue3.js.map +1 -0
  31. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue.js +1 -1
  32. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js +54 -3
  33. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js.map +1 -1
  34. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js +16 -2
  35. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js.map +1 -1
  36. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue.js +1 -1
  37. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +160 -2
  38. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
  39. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +206 -94
  40. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
  41. package/dist/esm/components/CnFilesBrowser/filesReview.js +61 -0
  42. package/dist/esm/components/CnFilesBrowser/filesReview.js.map +1 -0
  43. package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue.js +1 -1
  44. package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue2.js +42 -7
  45. package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue2.js.map +1 -1
  46. package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue3.js +121 -57
  47. package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue3.js.map +1 -1
  48. package/dist/esm/components/CnFlowsPage/CnFlowEditorPage.vue2.js +10 -0
  49. package/dist/esm/components/CnFlowsPage/CnFlowEditorPage.vue2.js.map +1 -1
  50. package/dist/esm/components/CnFlowsPage/CnFlowEditorPage.vue3.js +2 -1
  51. package/dist/esm/components/CnFlowsPage/CnFlowEditorPage.vue3.js.map +1 -1
  52. package/dist/esm/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue.js +9 -0
  53. package/dist/esm/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue.js.map +1 -0
  54. package/dist/esm/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue2.js +279 -0
  55. package/dist/esm/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue2.js.map +1 -0
  56. package/dist/esm/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue3.js +122 -0
  57. package/dist/esm/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue3.js.map +1 -0
  58. package/dist/esm/components/CnGroupedCardsWidget/index.js +29 -0
  59. package/dist/esm/components/CnGroupedCardsWidget/index.js.map +1 -0
  60. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +387 -78
  61. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  62. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +317 -209
  63. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  64. package/dist/esm/components/CnIndexPage/useNamedSource.js +4 -1
  65. package/dist/esm/components/CnIndexPage/useNamedSource.js.map +1 -1
  66. package/dist/esm/components/CnMapWidget/CnMapWidget.vue.js +1 -1
  67. package/dist/esm/components/CnMapWidget/CnMapWidget.vue2.js +15 -0
  68. package/dist/esm/components/CnMapWidget/CnMapWidget.vue2.js.map +1 -1
  69. package/dist/esm/components/CnMapWidget/CnMapWidget.vue3.js.map +1 -1
  70. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue.js +1 -1
  71. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +14 -1
  72. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
  73. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js +14 -6
  74. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
  75. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue.js +1 -1
  76. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js +10 -0
  77. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
  78. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +6 -1
  79. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
  80. package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue.js +1 -1
  81. package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue2.js +91 -4
  82. package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue2.js.map +1 -1
  83. package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue3.js +63 -38
  84. package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue3.js.map +1 -1
  85. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js +1 -1
  86. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +68 -7
  87. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
  88. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js +256 -240
  89. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -1
  90. package/dist/esm/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue.js +9 -0
  91. package/dist/esm/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue.js.map +1 -0
  92. package/dist/esm/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue2.js +175 -0
  93. package/dist/esm/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue2.js.map +1 -0
  94. package/dist/esm/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue3.js +77 -0
  95. package/dist/esm/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue3.js.map +1 -0
  96. package/dist/esm/components/CnSettingsFieldsSection/CnSettingsFieldsSection.vue.js +1 -1
  97. package/dist/esm/components/CnSettingsFieldsSection/CnSettingsFieldsSection.vue2.js +38 -9
  98. package/dist/esm/components/CnSettingsFieldsSection/CnSettingsFieldsSection.vue2.js.map +1 -1
  99. package/dist/esm/components/CnSettingsFieldsSection/CnSettingsFieldsSection.vue3.js.map +1 -1
  100. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue.js +1 -1
  101. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js +51 -4
  102. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js.map +1 -1
  103. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js +40 -25
  104. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js.map +1 -1
  105. package/dist/esm/components/CnWidgetGrid/builtInWidgets.js +4 -0
  106. package/dist/esm/components/CnWidgetGrid/builtInWidgets.js.map +1 -1
  107. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +1 -0
  108. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
  109. package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue.js +1 -1
  110. package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue2.js +7 -7
  111. package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue2.js.map +1 -1
  112. package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue3.js.map +1 -1
  113. package/dist/esm/composables/indexSources.js +167 -20
  114. package/dist/esm/composables/indexSources.js.map +1 -1
  115. package/dist/esm/composables/useEndpointSource.js +28 -6
  116. package/dist/esm/composables/useEndpointSource.js.map +1 -1
  117. package/dist/esm/index.js +1 -0
  118. package/dist/esm/index.js.map +1 -1
  119. package/dist/esm/l10n/en.json.js +43 -1
  120. package/dist/esm/l10n/en.json.js.map +1 -1
  121. package/dist/esm/l10n/nl.json.js +41 -1
  122. package/dist/esm/l10n/nl.json.js.map +1 -1
  123. package/dist/esm/library-style.js +1 -1
  124. package/dist/esm/nextcloud-vue.css +1101 -396
  125. package/dist/esm/utils/languageMapText.js +50 -0
  126. package/dist/esm/utils/languageMapText.js.map +1 -0
  127. package/dist/esm/utils/libraryWidgetKeys.js +3 -0
  128. package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
  129. package/dist/esm/utils/mapList.js +71 -0
  130. package/dist/esm/utils/mapList.js.map +1 -0
  131. package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
  132. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  133. package/dist/nextcloud-vue.cjs.js +9996 -7378
  134. package/dist/nextcloud-vue.cjs.js.map +2 -2
  135. package/dist/nextcloud-vue.css +1101 -396
  136. package/l10n/en.json +43 -1
  137. package/l10n/nl.json +41 -1
  138. package/package.json +1 -1
  139. package/src/components/CnAdminSettingsShell/CnAdminSettingsShell.vue +7 -1
  140. package/src/components/CnChoiceCards/CnChoiceCards.md +39 -0
  141. package/src/components/CnChoiceCards/CnChoiceCards.vue +93 -16
  142. package/src/components/CnDashboardPage/CnDashboardPage.vue +89 -20
  143. package/src/components/CnDetailPage/CnDetailPage.vue +28 -9
  144. package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +13 -4
  145. package/src/components/CnExportsWidget/CnExportsWidget.vue +233 -0
  146. package/src/components/CnExportsWidget/index.js +7 -0
  147. package/src/components/CnFactsWidget/CnFactsWidget.vue +65 -3
  148. package/src/components/CnFilesBrowser/CnFilesBrowser.vue +324 -0
  149. package/src/components/CnFilesBrowser/filesReview.js +57 -0
  150. package/src/components/CnFlowsPage/CnFlowBoard.vue +77 -2
  151. package/src/components/CnFlowsPage/CnFlowEditorPage.vue +11 -0
  152. package/src/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue +433 -0
  153. package/src/components/CnGroupedCardsWidget/index.js +28 -0
  154. package/src/components/CnIndexPage/CnIndexPage.vue +376 -11
  155. package/src/components/CnIndexPage/useNamedSource.js +4 -1
  156. package/src/components/CnMapWidget/CnMapWidget.vue +37 -0
  157. package/src/components/CnObjectListWidget/CnObjectListWidget.vue +29 -1
  158. package/src/components/CnObjectSidebar/CnFilesTab.vue +15 -0
  159. package/src/components/CnOrderedListField/CnOrderedListField.vue +151 -5
  160. package/src/components/CnRecordListWidget/CnRecordListWidget.vue +114 -7
  161. package/src/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue +272 -0
  162. package/src/components/CnSavedViewAlertWidget/index.js +7 -0
  163. package/src/components/CnSettingsFieldsSection/CnSettingsFieldsSection.vue +38 -9
  164. package/src/components/CnSetupWizard/CnSetupWizard.vue +72 -5
  165. package/src/components/CnWidgetGrid/builtInWidgets.js +4 -0
  166. package/src/components/CnWidgetGrid/registerDashboardWidgets.js +1 -0
  167. package/src/components/CnWizardDialog/CnWizardDialog.vue +7 -7
  168. package/src/components/index.js +3 -0
  169. package/src/composables/indexSources.js +166 -19
  170. package/src/composables/useEndpointSource.js +28 -6
  171. package/src/css/detail-page.css +15 -0
  172. package/src/css/dialog.css +48 -0
  173. package/src/css/index-page.css +117 -0
  174. package/src/css/look-board.css +127 -1
  175. package/src/index.js +1 -0
  176. package/src/schemas/app-manifest-v2.schema.json +55 -1
  177. package/src/utils/languageMapText.js +46 -0
  178. package/src/utils/libraryWidgetKeys.js +3 -0
  179. package/src/utils/mapList.js +67 -0
  180. package/src/utils/validateManifestV2.compiled.js +3 -3
@@ -0,0 +1,272 @@
1
+ <!--
2
+ - SPDX-FileCopyrightText: 2026 Conduction B.V.
3
+ - SPDX-License-Identifier: EUPL-1.2
4
+ -->
5
+ <template>
6
+ <div class="cn-saved-view-alert" data-testid="cn-saved-view-alert">
7
+ <p v-if="!view" class="cn-saved-view-alert__empty" data-testid="cn-saved-view-alert-empty">
8
+ {{ emptyText }}
9
+ </p>
10
+ <template v-else>
11
+ <dl class="cn-saved-view-alert__facts">
12
+ <dt>{{ t('nextcloud-vue', 'Name') }}</dt>
13
+ <dd data-testid="cn-saved-view-alert-name">
14
+ {{ view.name }}
15
+ </dd>
16
+ <template v-if="countText">
17
+ <dt>{{ t('nextcloud-vue', 'Now') }}</dt>
18
+ <dd data-testid="cn-saved-view-alert-count">
19
+ {{ countText }}
20
+ </dd>
21
+ </template>
22
+ </dl>
23
+ <NcCheckboxRadioSwitch
24
+ type="switch"
25
+ :modelValue="enabled"
26
+ :disabled="saving || !canEdit"
27
+ data-testid="cn-saved-view-alert-switch"
28
+ @update:modelValue="onToggle">
29
+ <span class="cn-saved-view-alert__switch-text">
30
+ <span>{{ t('nextcloud-vue', 'Tell me above a number') }}</span>
31
+ <span class="cn-saved-view-alert__hint">{{ hintText }}</span>
32
+ </span>
33
+ </NcCheckboxRadioSwitch>
34
+ <NcTextField v-if="enabled"
35
+ :modelValue="String(threshold)"
36
+ type="number"
37
+ min="0"
38
+ :label="t('nextcloud-vue', 'Threshold')"
39
+ :disabled="saving || !canEdit"
40
+ data-testid="cn-saved-view-alert-threshold"
41
+ @update:modelValue="onThreshold" />
42
+ <p v-if="error"
43
+ class="cn-saved-view-alert__error"
44
+ role="alert"
45
+ data-testid="cn-saved-view-alert-error">
46
+ {{ error }}
47
+ </p>
48
+ </template>
49
+ </div>
50
+ </template>
51
+
52
+ <script>
53
+ import { getCurrentUser } from '@nextcloud/auth'
54
+ import { translatePlural as n, translate as t } from '@nextcloud/l10n'
55
+ import { NcCheckboxRadioSwitch, NcTextField } from '@nextcloud/vue'
56
+ import { useSavedViewsApi } from '../../composables/useSavedViewsApi.js'
57
+
58
+ /**
59
+ * CnSavedViewAlertWidget — the saved view an index page has applied, with its
60
+ * count alert (DqZoekresultaten "Opgeslagen zoekopdracht"): its name, how many
61
+ * rows it holds now, and a switch that asks OpenRegister to tell the reader
62
+ * when the count reaches a threshold.
63
+ *
64
+ * The alert is the view's own `alert` (`{ operator: 'gte', threshold,
65
+ * recipients }`), saved with a PATCH on the view; OpenRegister evaluates it on
66
+ * its sweep and fires on the crossing, not on the state. Switching it off
67
+ * clears it. The widget reads the page through the `cnIndexList` CnIndexPage
68
+ * provides, so it belongs in an index page's `sidePanel`
69
+ * (`type: "saved-view-alert"`).
70
+ *
71
+ * Example (manifest):
72
+ * ```json
73
+ * { "id": "alert", "type": "saved-view-alert", "title": "Saved search", "content": { "noun": { "one": "case", "other": "cases" } } }
74
+ * ```
75
+ */
76
+ export default {
77
+ name: 'CnSavedViewAlertWidget',
78
+
79
+ components: { NcCheckboxRadioSwitch, NcTextField },
80
+
81
+ inject: {
82
+ /** The index page's list: its applied saved view and its total. */
83
+ cnIndexList: { default: null },
84
+ /** Host translate function provided by CnAppRoot. */
85
+ cnTranslate: { default: () => (key) => key },
86
+ },
87
+
88
+ props: {
89
+ /**
90
+ * The widget content: `{ noun?: { one, other }, emptyText? }`. `noun`
91
+ * names what is counted ("9 zaken"); `emptyText` shows while no saved
92
+ * view is applied. Both are translated with the app's catalogue.
93
+ */
94
+ content: {
95
+ type: Object,
96
+ default: () => ({}),
97
+ },
98
+ },
99
+
100
+ data() {
101
+ return { saving: false, error: '', draft: null }
102
+ },
103
+
104
+ computed: {
105
+ /** @return {object|null} The applied saved view, or null. */
106
+ view() {
107
+ return (this.cnIndexList && this.cnIndexList.appliedView) || null
108
+ },
109
+
110
+ /** @return {boolean} Whether the view carries an alert. */
111
+ enabled() {
112
+ const alert = this.view && this.view.alert
113
+ return Boolean(alert && typeof alert === 'object' && Number.isFinite(Number(alert.threshold)))
114
+ },
115
+
116
+ /** @return {number} The threshold being edited, else the view's, else 10. */
117
+ threshold() {
118
+ if (this.draft !== null) {
119
+ return this.draft
120
+ }
121
+ const value = this.enabled ? Number(this.view.alert.threshold) : 10
122
+ return Number.isFinite(value) ? value : 10
123
+ },
124
+
125
+ /** @return {boolean} Whether the reader may change the view: its owner, or when the view says nothing. */
126
+ canEdit() {
127
+ const owner = this.view && this.view.owner
128
+ if (!owner) {
129
+ return true
130
+ }
131
+ const user = getCurrentUser()
132
+ return Boolean(user && user.uid === owner)
133
+ },
134
+
135
+ /** @return {string} "9 zaken", or '' while the total is unknown. */
136
+ countText() {
137
+ const total = this.cnIndexList ? this.cnIndexList.total : undefined
138
+ if (typeof total !== 'number') {
139
+ return ''
140
+ }
141
+ const noun = this.content && this.content.noun
142
+ if (noun && typeof noun === 'object' && noun.one && noun.other) {
143
+ return `${total} ${this.cnTranslate(total === 1 ? noun.one : noun.other)}`
144
+ }
145
+ return n('nextcloud-vue', '%n result', '%n results', total)
146
+ },
147
+
148
+ /** @return {string} The line under the switch. */
149
+ hintText() {
150
+ return t('nextcloud-vue', 'A message to you when there are {threshold} or more', { threshold: this.threshold })
151
+ },
152
+
153
+ /** @return {string} What shows while no saved view is applied. */
154
+ emptyText() {
155
+ const own = this.content && this.content.emptyText
156
+ return own ? this.cnTranslate(own) : t('nextcloud-vue', 'Save this search or open a saved one to set an alert on it.')
157
+ },
158
+ },
159
+
160
+ methods: {
161
+ t,
162
+
163
+ /**
164
+ * Switch the alert on (at the shown threshold) or off.
165
+ *
166
+ * @param {boolean} on The new state.
167
+ * @return {Promise<void>}
168
+ */
169
+ async onToggle(on) {
170
+ await this.save(on ? this.alertFor(this.threshold) : null)
171
+ },
172
+
173
+ /**
174
+ * Save a new threshold, when the alert is on.
175
+ *
176
+ * @param {string} value The typed value.
177
+ * @return {Promise<void>}
178
+ */
179
+ async onThreshold(value) {
180
+ const number = Math.max(0, Math.floor(Number(value)))
181
+ if (!Number.isFinite(number)) {
182
+ return
183
+ }
184
+ this.draft = number
185
+ await this.save(this.alertFor(number))
186
+ this.draft = null
187
+ },
188
+
189
+ /**
190
+ * The alert the view stores: reaching the threshold tells the reader.
191
+ *
192
+ * @param {number} threshold The threshold.
193
+ * @return {{operator: string, threshold: number, recipients: Array<string>}}
194
+ */
195
+ alertFor(threshold) {
196
+ const user = getCurrentUser()
197
+ return { operator: 'gte', threshold, recipients: user && user.uid ? [user.uid] : [] }
198
+ },
199
+
200
+ /**
201
+ * PATCH the view's alert and hand the saved view back to the page.
202
+ *
203
+ * @param {object|null} alert The alert, or null to clear it.
204
+ * @return {Promise<void>}
205
+ */
206
+ async save(alert) {
207
+ if (!this.view) {
208
+ return
209
+ }
210
+ this.saving = true
211
+ this.error = ''
212
+ try {
213
+ const saved = await useSavedViewsApi().patchView(this.view.uuid || this.view.id, { alert })
214
+ if (saved && this.cnIndexList && typeof this.cnIndexList.replaceSavedView === 'function') {
215
+ this.cnIndexList.replaceSavedView(saved)
216
+ }
217
+ } catch (error) {
218
+ this.error = (error && error.response && error.response.data && error.response.data.error) || t('nextcloud-vue', 'The alert could not be saved.')
219
+ } finally {
220
+ this.saving = false
221
+ }
222
+ },
223
+ },
224
+ }
225
+ </script>
226
+
227
+ <style scoped>
228
+ .cn-saved-view-alert {
229
+ display: flex;
230
+ flex-direction: column;
231
+ gap: 12px;
232
+ font-size: 14px;
233
+ }
234
+
235
+ .cn-saved-view-alert__facts {
236
+ display: grid;
237
+ grid-template-columns: max-content 1fr;
238
+ gap: 6px 16px;
239
+ margin: 0;
240
+ }
241
+
242
+ .cn-saved-view-alert__facts dt {
243
+ color: var(--color-text-maxcontrast);
244
+ }
245
+
246
+ .cn-saved-view-alert__facts dd {
247
+ margin: 0;
248
+ font-weight: 500;
249
+ }
250
+
251
+ .cn-saved-view-alert__switch-text {
252
+ display: flex;
253
+ flex-direction: column;
254
+ gap: 2px;
255
+ }
256
+
257
+ .cn-saved-view-alert__hint,
258
+ .cn-saved-view-alert__empty {
259
+ color: var(--color-text-maxcontrast);
260
+ font-size: 13px;
261
+ }
262
+
263
+ .cn-saved-view-alert__empty,
264
+ .cn-saved-view-alert__error {
265
+ margin: 0;
266
+ }
267
+
268
+ .cn-saved-view-alert__error {
269
+ color: var(--color-error-text, var(--color-error));
270
+ font-size: 13px;
271
+ }
272
+ </style>
@@ -0,0 +1,7 @@
1
+ // SPDX-FileCopyrightText: 2026 Conduction B.V.
2
+ // SPDX-License-Identifier: EUPL-1.2
3
+
4
+ import CnSavedViewAlertWidget from './CnSavedViewAlertWidget.vue'
5
+
6
+ export default CnSavedViewAlertWidget
7
+ export { CnSavedViewAlertWidget }
@@ -68,6 +68,32 @@ import { serverErrorMessage } from '../../utils/serverErrorMessage.js'
68
68
  import { CnSettingsField } from '../CnSettingsField/index.js'
69
69
  import { CnSettingsSection } from '../CnSettingsSection/index.js'
70
70
 
71
+ /**
72
+ * A value by key: the key as written when the object holds it (a flat map
73
+ * with dotted keys such as `integration.brk.mode`), else as a dotted path.
74
+ *
75
+ * @param {object} data The object.
76
+ * @param {string} key The key or dotted path.
77
+ * @return {unknown}
78
+ */
79
+ function literalFirst(data, key) {
80
+ if (data && typeof data === 'object' && Object.hasOwn(data, key)) {
81
+ return data[key]
82
+ }
83
+ return readPath(data, key)
84
+ }
85
+
86
+ /**
87
+ * The request options for a target: an OCS endpoint (`/ocs/...`) needs the
88
+ * `OCS-APIRequest` header; `prefixUrl` already serves it outside index.php.
89
+ *
90
+ * @param {string} url The target.
91
+ * @return {object}
92
+ */
93
+ function ocsConfig(url) {
94
+ return /^\/ocs\//.test(String(url)) ? { headers: { 'OCS-APIRequest': 'true' } } : {}
95
+ }
96
+
71
97
  /**
72
98
  * Up to two initials of a name, for an image field without an image.
73
99
  *
@@ -146,15 +172,16 @@ export default {
146
172
  * The fields: `[{ key, label, type?, hint?, placeholder?, required?,
147
173
  * pattern?, patternMessage?, min?, max?, rows?, options?, optionsUrl?,
148
174
  * optionsPath?, optionLabel?, optionValue?, default?, readOnly?, wide?,
149
- * url?, valuePath?, saveUrl?, saveMethod?, saveKey?, uploadUrl?,
175
+ * url?, valuePath?, saveUrl?, saveMethod?, saveKey?, saveParams?, uploadUrl?,
150
176
  * uploadField?, uploadParams?, uploadValuePath?, initials?, initialsFrom? }]`.
151
177
  * `type` is `text` (default), `textarea`, `number`, `switch`, `select`,
152
178
  * `secret` or `image`. `key` names the setting; a dotted key reads a
153
179
  * nested value and is written as written. A field with its own `url`
154
180
  * is read there (at `valuePath`, default its key) and saved to its
155
181
  * `saveUrl` (default that `url`) with `saveMethod` (default POST) under
156
- * `saveKey` (default its key), so one section can hold the settings of
157
- * several apps. An `image` uploads at once to `uploadUrl`. Labels,
182
+ * `saveKey` (default its key), with `saveParams` merged into the body,
183
+ * so one section can hold the settings of several apps. A target under
184
+ * `/ocs/` is sent the `OCS-APIRequest` header. An `image` uploads at once to `uploadUrl`. Labels,
158
185
  * hints, placeholders and messages go through the app's translate
159
186
  * function.
160
187
  *
@@ -343,7 +370,7 @@ export default {
343
370
  const failures = {}
344
371
  await Promise.all(targets.map(async (target) => {
345
372
  try {
346
- const response = await axios.get(prefixUrl(target))
373
+ const response = await axios.get(prefixUrl(target), ocsConfig(target))
347
374
  answers[target] = response?.data ?? {}
348
375
  } catch (error) {
349
376
  failures[target] = error
@@ -396,7 +423,8 @@ export default {
396
423
 
397
424
  /**
398
425
  * A field's value in its read answer: at its `valuePath` (a field with its
399
- * own `url`), else at the section's `valuesPath`, then its `key`.
426
+ * own `url`), else at the section's `valuesPath`, then its `key`. A key
427
+ * the answer holds as written wins over reading it as a dotted path.
400
428
  *
401
429
  * @param {object} field The field.
402
430
  * @param {object|null} data The answer of the field's read target.
@@ -407,10 +435,10 @@ export default {
407
435
  return undefined
408
436
  }
409
437
  if (field.url) {
410
- return readPath(data, field.valuePath || field.key)
438
+ return literalFirst(data, field.valuePath || field.key)
411
439
  }
412
- const values = (this.valuesPath ? readPath(data, this.valuesPath) : data) || {}
413
- return Object.hasOwn(values, field.key) ? values[field.key] : readPath(values, field.key)
440
+ const values = (this.valuesPath ? literalFirst(data, this.valuesPath) : data) || {}
441
+ return literalFirst(values, field.key)
414
442
  },
415
443
 
416
444
  /**
@@ -494,6 +522,7 @@ export default {
494
522
  }
495
523
  const group = groups.get(id)
496
524
  group.fields.push(field)
525
+ Object.assign(group.data, field.saveParams || {})
497
526
  group.data[field.saveKey || field.key] = value
498
527
  }
499
528
  this.saving = true
@@ -502,7 +531,7 @@ export default {
502
531
  const sent = {}
503
532
  for (const group of groups.values()) {
504
533
  try {
505
- await axios.request({ url: prefixUrl(group.url), method: group.method, data: group.data })
534
+ await axios.request({ url: prefixUrl(group.url), method: group.method, data: group.data, ...ocsConfig(group.url) })
506
535
  Object.assign(sent, group.data)
507
536
  this.acceptSaved(group.fields)
508
537
  } catch (error) {
@@ -69,7 +69,12 @@
69
69
 
70
70
  <!-- choice -->
71
71
  <template v-else-if="step.type === 'choice'">
72
- <NcNoteCard v-if="step.body" type="info">
72
+ <!-- The board sets the intro as a plain paragraph above the
73
+ cards (DqInstellen); the Nextcloud look keeps the note. -->
74
+ <p v-if="step.body && isBoard" class="cn-setup-step__intro" data-testid="cn-setup-step-intro">
75
+ {{ stepBody(step) }}
76
+ </p>
77
+ <NcNoteCard v-else-if="step.body" type="info">
73
78
  {{ stepBody(step) }}
74
79
  </NcNoteCard>
75
80
  <CnChoiceCards
@@ -119,6 +124,9 @@
119
124
  <NcNoteCard v-if="isChoiceDisabled(step)" type="warning">
120
125
  {{ dependsOnHint(step) }}
121
126
  </NcNoteCard>
127
+ <p v-if="stepNote(step)" class="cn-setup-step__note" data-testid="cn-setup-step-note">
128
+ {{ stepNote(step) }}
129
+ </p>
122
130
  </template>
123
131
 
124
132
  <!-- config-fields -->
@@ -212,6 +220,7 @@ import CnChoiceCards from '../CnChoiceCards/CnChoiceCards.vue'
212
220
  import CnLeafDependencySettings from '../CnLeafDependencySettings/CnLeafDependencySettings.vue'
213
221
  import CnWizardDialog from '../CnWizardDialog/CnWizardDialog.vue'
214
222
  import { checkDependencies, isDependencyResolved, missingRequiredApps, readServerAppStatuses } from '../../composables/useDependencyCheck.js'
223
+ import { useLook } from '../../composables/useLook.js'
215
224
  import { useSetupStatus } from '../../composables/useSetupStatus.js'
216
225
  import { fieldsFromSchema } from '../../utils/schema.js'
217
226
 
@@ -428,12 +437,13 @@ export default {
428
437
  * @return {object} `setupStatus` / `setupStatusLoading` for the option resolver.
429
438
  */
430
439
  setup(props) {
431
- const wantsSource = (props.steps || []).some((s) => s && s.optionsSource)
440
+ const { isBoard } = useLook()
441
+ const wantsSource = (props.steps || []).some((s) => s && (s.optionsSource || s.visibleWhen))
432
442
  if (!wantsSource) {
433
- return {}
443
+ return { isBoard }
434
444
  }
435
445
  const { status, loading } = useSetupStatus(props.appId, { setup: { steps: props.steps } })
436
- return { setupStatus: status, setupStatusLoading: loading }
446
+ return { isBoard, setupStatus: status, setupStatusLoading: loading }
437
447
  },
438
448
 
439
449
  data() {
@@ -496,7 +506,7 @@ export default {
496
506
 
497
507
  setupSteps() {
498
508
  const skipped = new Set(this.skippedSteps.map((entry) => entry.step.id))
499
- return this.declaredSteps.filter((s) => !skipped.has(s.id))
509
+ return this.declaredSteps.filter((s) => !skipped.has(s.id) && this.stepConditionHolds(s))
500
510
  },
501
511
 
502
512
  /**
@@ -793,6 +803,18 @@ export default {
793
803
  return raw === '' ? '' : this.cnTranslate(raw)
794
804
  },
795
805
 
806
+ /**
807
+ * The footnote under a choice step's options, from the step's `note`
808
+ * (or `footnote`), translated like the body.
809
+ *
810
+ * @param {object} step The step declaration.
811
+ * @return {string} The translated footnote, or ''.
812
+ */
813
+ stepNote(step) {
814
+ const raw = (step && (step.note || step.footnote)) || ''
815
+ return raw === '' ? '' : this.cnTranslate(raw)
816
+ },
817
+
796
818
  stepSlot(step) {
797
819
  return 'step-' + step.id
798
820
  },
@@ -847,6 +869,39 @@ export default {
847
869
  })
848
870
  },
849
871
 
872
+ /**
873
+ * Whether a step's `visibleWhen` holds: the value at that path in the
874
+ * setup status (`"flags.hasPayouts"`, dots walk into objects) is truthy,
875
+ * or equals `equals` when the step gives `{ path, equals }`. A step
876
+ * without `visibleWhen` always shows; one with it stays hidden until the
877
+ * status says so, also while the status loads (DqInstellen: the dwangsom
878
+ * step only when the app pays penalties).
879
+ *
880
+ * @param {object} step The step declaration.
881
+ * @return {boolean}
882
+ */
883
+ stepConditionHolds(step) {
884
+ const when = step && step.visibleWhen
885
+ if (when === undefined || when === null || when === '') {
886
+ return true
887
+ }
888
+ const path = typeof when === 'string' ? when : when.path
889
+ if (typeof path !== 'string' || path === '') {
890
+ return true
891
+ }
892
+ let node = this.setupStatus
893
+ for (const key of path.split('.')) {
894
+ if (node === null || node === undefined || typeof node !== 'object') {
895
+ return false
896
+ }
897
+ node = node[key]
898
+ }
899
+ if (typeof when === 'object' && Object.hasOwn(when, 'equals')) {
900
+ return node === when.equals
901
+ }
902
+ return Boolean(node)
903
+ },
904
+
850
905
  /**
851
906
  * The option list a `choice` step declares `optionsSource` for, read
852
907
  * from the setup status document the app already serves.
@@ -1338,6 +1393,18 @@ export default {
1338
1393
  gap: 12px;
1339
1394
  }
1340
1395
 
1396
+ .cn-setup-step__intro {
1397
+ margin: 0;
1398
+ font-size: 15px;
1399
+ line-height: 1.5;
1400
+ }
1401
+
1402
+ .cn-setup-step__note {
1403
+ margin: 0;
1404
+ font-size: 13px;
1405
+ color: var(--color-text-maxcontrast);
1406
+ }
1407
+
1341
1408
  .cn-setup-step__nav {
1342
1409
  display: flex;
1343
1410
  justify-content: flex-end;
@@ -17,6 +17,7 @@
17
17
  import CnAuditTrailWidget from '../CnAuditTrailWidget/CnAuditTrailWidget.vue'
18
18
  import CnBannerWidget from '../CnBannerWidget/CnBannerWidget.vue'
19
19
  import CnDividerWidget from '../CnDividerWidget/CnDividerWidget.vue'
20
+ import CnExportsWidget from '../CnExportsWidget/CnExportsWidget.vue'
20
21
  import CnHeaderWidget from '../CnHeaderWidget/CnHeaderWidget.vue'
21
22
  import CnIntegrationWidget from '../CnIntegrationWidget/CnIntegrationWidget.vue'
22
23
  import CnNavCardGrid from '../CnNavCardGrid/CnNavCardGrid.vue'
@@ -24,6 +25,7 @@ import CnObjectDataWidget from '../CnObjectDataWidget/CnObjectDataWidget.vue'
24
25
  import CnObjectGeoWidget from '../CnObjectGeoWidget/CnObjectGeoWidget.vue'
25
26
  import CnObjectMetadataWidget from '../CnObjectMetadataWidget/CnObjectMetadataWidget.vue'
26
27
  import CnRelatedObjectsWidget from '../CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue'
28
+ import CnSavedViewAlertWidget from '../CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue'
27
29
  import CnTextWidget from '../CnTextWidget/CnTextWidget.vue'
28
30
  import CnTimelineWidget from '../CnTimelineWidget/CnTimelineWidget.vue'
29
31
  import CnWidgetCardGrid from '../CnWidgetCardGrid/CnWidgetCardGrid.vue'
@@ -89,4 +91,6 @@ export const BUILT_IN_WIDGETS = {
89
91
  header: CnHeaderWidget,
90
92
  text: CnTextWidget,
91
93
  divider: CnDividerWidget,
94
+ 'saved-view-alert': CnSavedViewAlertWidget,
95
+ exports: CnExportsWidget,
92
96
  }
@@ -75,6 +75,7 @@ import '../CnTasksWidget/index.js'
75
75
  import '../CnSectionsWidget/index.js'
76
76
  import '../CnFactsWidget/index.js'
77
77
  import '../CnDeviceFramesWidget/index.js'
78
+ import '../CnGroupedCardsWidget/index.js'
78
79
  import '../CnChecklistRunWidget/index.js'
79
80
  import '../CnWorkingCalendarWidget/index.js'
80
81
  import '../CnPartyActionsWidget/index.js'
@@ -208,7 +208,7 @@ export default {
208
208
  */
209
209
  dialogTitle: {
210
210
  type: String,
211
- default: 'Wizard',
211
+ default: () => t('nextcloud-vue', 'Wizard'),
212
212
  },
213
213
 
214
214
  /**
@@ -279,17 +279,17 @@ export default {
279
279
  submitIcon: { type: [Object, Function], default: null },
280
280
 
281
281
  /** Cancel-button label (wizard phase). */
282
- cancelLabel: { type: String, default: 'Cancel' },
282
+ cancelLabel: { type: String, default: () => t('nextcloud-vue', 'Cancel') },
283
283
  /** Back-button label. */
284
- backLabel: { type: String, default: 'Back' },
284
+ backLabel: { type: String, default: () => t('nextcloud-vue', 'Back') },
285
285
  /** Next-button label (intermediate steps). */
286
- nextLabel: { type: String, default: 'Next' },
286
+ nextLabel: { type: String, default: () => t('nextcloud-vue', 'Next') },
287
287
  /** Submit-button label (last step). */
288
- submitLabel: { type: String, default: 'Submit' },
288
+ submitLabel: { type: String, default: () => t('nextcloud-vue', 'Submit') },
289
289
  /** Close-button label (result phase). */
290
- closeLabel: { type: String, default: 'Close' },
290
+ closeLabel: { type: String, default: () => t('nextcloud-vue', 'Close') },
291
291
  /** Success banner default text when result.message is empty. */
292
- successText: { type: String, default: 'Done.' },
292
+ successText: { type: String, default: () => t('nextcloud-vue', 'Done.') },
293
293
  /**
294
294
  * Whether the dialog can be dismissed (Cancel button, ESC, backdrop
295
295
  * click) before reaching the result phase. `false` hides Cancel and
@@ -30,6 +30,8 @@ export { CnDateAxisView } from './CnDateAxisView/index.js'
30
30
  export { CnObjectRow } from './CnObjectRow/index.js'
31
31
  export { CnObjectList } from './CnObjectList/index.js'
32
32
  export { CnOrderedListField } from './CnOrderedListField/index.js'
33
+ export { CnExportsWidget } from './CnExportsWidget/index.js'
34
+ export { CnSavedViewAlertWidget } from './CnSavedViewAlertWidget/index.js'
33
35
  export { CnObjectKanban } from './CnObjectKanban/index.js'
34
36
  export { CnObjectCalendar } from './CnObjectCalendar/index.js'
35
37
  export { CnFolderTree } from './CnFolderTree/index.js'
@@ -224,6 +226,7 @@ export { CnTasksWidgetForm } from './CnTasksWidgetForm/index.js'
224
226
  export { CnSectionsWidget } from './CnSectionsWidget/index.js'
225
227
  export { CnFactsWidget } from './CnFactsWidget/index.js'
226
228
  export { CnDeviceFramesWidget } from './CnDeviceFramesWidget/index.js'
229
+ export { CnGroupedCardsWidget } from './CnGroupedCardsWidget/index.js'
227
230
  export { CnChecklistRunWidget } from './CnChecklistRunWidget/index.js'
228
231
  export { CnWorkingCalendarWidget } from './CnWorkingCalendarWidget/index.js'
229
232
  export { CnPartyActionsWidget } from './CnPartyActionsWidget/index.js'