@conduction/nextcloud-vue 2.108.0 → 2.110.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 (161) hide show
  1. package/dist/esm/components/CnActionDialog/CnActionDialog.vue.js +1 -1
  2. package/dist/esm/components/CnActionDialog/CnActionDialog.vue2.js +115 -12
  3. package/dist/esm/components/CnActionDialog/CnActionDialog.vue2.js.map +1 -1
  4. package/dist/esm/components/CnActionDialog/CnActionDialog.vue3.js +36 -9
  5. package/dist/esm/components/CnActionDialog/CnActionDialog.vue3.js.map +1 -1
  6. package/dist/esm/components/CnAddressSearch/CnAddressSearch.vue.js +10 -0
  7. package/dist/esm/components/CnAddressSearch/CnAddressSearch.vue.js.map +1 -0
  8. package/dist/esm/components/CnAddressSearch/CnAddressSearch.vue2.js +305 -0
  9. package/dist/esm/components/CnAddressSearch/CnAddressSearch.vue2.js.map +1 -0
  10. package/dist/esm/components/CnAddressSearch/CnAddressSearch.vue3.js +38 -0
  11. package/dist/esm/components/CnAddressSearch/CnAddressSearch.vue3.js.map +1 -0
  12. package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue.js +10 -0
  13. package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue.js.map +1 -0
  14. package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue2.js +398 -0
  15. package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue2.js.map +1 -0
  16. package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue3.js +155 -0
  17. package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue3.js.map +1 -0
  18. package/dist/esm/components/CnDecisionListWidget/CnDecisionListWidget.vue.js +9 -0
  19. package/dist/esm/components/CnDecisionListWidget/CnDecisionListWidget.vue.js.map +1 -0
  20. package/dist/esm/components/CnDecisionListWidget/CnDecisionListWidget.vue2.js +281 -0
  21. package/dist/esm/components/CnDecisionListWidget/CnDecisionListWidget.vue2.js.map +1 -0
  22. package/dist/esm/components/CnDecisionListWidget/CnDecisionListWidget.vue3.js +137 -0
  23. package/dist/esm/components/CnDecisionListWidget/CnDecisionListWidget.vue3.js.map +1 -0
  24. package/dist/esm/components/CnDuplicateWarning/CnDuplicateWarning.vue.js +9 -0
  25. package/dist/esm/components/CnDuplicateWarning/CnDuplicateWarning.vue.js.map +1 -0
  26. package/dist/esm/components/CnDuplicateWarning/CnDuplicateWarning.vue2.js +225 -0
  27. package/dist/esm/components/CnDuplicateWarning/CnDuplicateWarning.vue2.js.map +1 -0
  28. package/dist/esm/components/CnDuplicateWarning/CnDuplicateWarning.vue3.js +110 -0
  29. package/dist/esm/components/CnDuplicateWarning/CnDuplicateWarning.vue3.js.map +1 -0
  30. package/dist/esm/components/CnFormDialog/CnFormDialog.vue.js +1 -1
  31. package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js +19 -3
  32. package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
  33. package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
  34. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +30 -0
  35. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  36. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +513 -481
  37. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  38. package/dist/esm/components/CnNcDashboardList/CnNcDashboardList.vue.js +9 -0
  39. package/dist/esm/components/CnNcDashboardList/CnNcDashboardList.vue.js.map +1 -0
  40. package/dist/esm/components/CnNcDashboardList/CnNcDashboardList.vue2.js +181 -0
  41. package/dist/esm/components/CnNcDashboardList/CnNcDashboardList.vue2.js.map +1 -0
  42. package/dist/esm/components/CnNcDashboardList/CnNcDashboardList.vue3.js +50 -0
  43. package/dist/esm/components/CnNcDashboardList/CnNcDashboardList.vue3.js.map +1 -0
  44. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue.js +1 -1
  45. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +102 -1
  46. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
  47. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js +6 -6
  48. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
  49. package/dist/esm/components/CnPlanWidget/CnPlanWidget.vue.js +9 -0
  50. package/dist/esm/components/CnPlanWidget/CnPlanWidget.vue.js.map +1 -0
  51. package/dist/esm/components/CnPlanWidget/CnPlanWidget.vue2.js +190 -0
  52. package/dist/esm/components/CnPlanWidget/CnPlanWidget.vue2.js.map +1 -0
  53. package/dist/esm/components/CnPlanWidget/CnPlanWidget.vue3.js +91 -0
  54. package/dist/esm/components/CnPlanWidget/CnPlanWidget.vue3.js.map +1 -0
  55. package/dist/esm/components/CnPlanWidget/CnPlanWidgetRow.vue.js +9 -0
  56. package/dist/esm/components/CnPlanWidget/CnPlanWidgetRow.vue.js.map +1 -0
  57. package/dist/esm/components/CnPlanWidget/CnPlanWidgetRow.vue2.js +64 -0
  58. package/dist/esm/components/CnPlanWidget/CnPlanWidgetRow.vue2.js.map +1 -0
  59. package/dist/esm/components/CnPlanWidget/CnPlanWidgetRow.vue3.js +49 -0
  60. package/dist/esm/components/CnPlanWidget/CnPlanWidgetRow.vue3.js.map +1 -0
  61. package/dist/esm/components/CnRedactionEditor/CnRedactionEditor.vue.js +9 -0
  62. package/dist/esm/components/CnRedactionEditor/CnRedactionEditor.vue.js.map +1 -0
  63. package/dist/esm/components/CnRedactionEditor/CnRedactionEditor.vue2.js +382 -0
  64. package/dist/esm/components/CnRedactionEditor/CnRedactionEditor.vue2.js.map +1 -0
  65. package/dist/esm/components/CnRedactionEditor/CnRedactionEditor.vue3.js +285 -0
  66. package/dist/esm/components/CnRedactionEditor/CnRedactionEditor.vue3.js.map +1 -0
  67. package/dist/esm/components/CnRetentionWidget/CnRetentionWidget.vue.js +9 -0
  68. package/dist/esm/components/CnRetentionWidget/CnRetentionWidget.vue.js.map +1 -0
  69. package/dist/esm/components/CnRetentionWidget/CnRetentionWidget.vue2.js +277 -0
  70. package/dist/esm/components/CnRetentionWidget/CnRetentionWidget.vue2.js.map +1 -0
  71. package/dist/esm/components/CnRetentionWidget/CnRetentionWidget.vue3.js +183 -0
  72. package/dist/esm/components/CnRetentionWidget/CnRetentionWidget.vue3.js.map +1 -0
  73. package/dist/esm/components/CnWidgetGrid/builtInWidgets.js +16 -14
  74. package/dist/esm/components/CnWidgetGrid/builtInWidgets.js.map +1 -1
  75. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +53 -22
  76. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
  77. package/dist/esm/components/CnWorkQueue/CnWorkQueue.vue.js +9 -0
  78. package/dist/esm/components/CnWorkQueue/CnWorkQueue.vue.js.map +1 -0
  79. package/dist/esm/components/CnWorkQueue/CnWorkQueue.vue2.js +253 -0
  80. package/dist/esm/components/CnWorkQueue/CnWorkQueue.vue2.js.map +1 -0
  81. package/dist/esm/components/CnWorkQueue/CnWorkQueue.vue3.js +197 -0
  82. package/dist/esm/components/CnWorkQueue/CnWorkQueue.vue3.js.map +1 -0
  83. package/dist/esm/index.js +12 -0
  84. package/dist/esm/index.js.map +1 -1
  85. package/dist/esm/l10n/en.json.js +106 -1
  86. package/dist/esm/l10n/en.json.js.map +1 -1
  87. package/dist/esm/l10n/nl.json.js +105 -1
  88. package/dist/esm/l10n/nl.json.js.map +1 -1
  89. package/dist/esm/library-style.js +1 -1
  90. package/dist/esm/nextcloud-vue.css +510 -76
  91. package/dist/esm/utils/actionsDispatcher.js +11 -3
  92. package/dist/esm/utils/actionsDispatcher.js.map +1 -1
  93. package/dist/esm/utils/builtInModals.js +3 -1
  94. package/dist/esm/utils/builtInModals.js.map +1 -1
  95. package/dist/esm/utils/duplicateWarning.js +62 -0
  96. package/dist/esm/utils/duplicateWarning.js.map +1 -0
  97. package/dist/esm/utils/errors.js +4 -0
  98. package/dist/esm/utils/errors.js.map +1 -1
  99. package/dist/esm/utils/libraryWidgetKeys.js +4 -0
  100. package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
  101. package/dist/esm/utils/openDuplicateWarning.js +39 -0
  102. package/dist/esm/utils/openDuplicateWarning.js.map +1 -0
  103. package/dist/esm/utils/optionsFrom.js +69 -2
  104. package/dist/esm/utils/optionsFrom.js.map +1 -1
  105. package/dist/esm/utils/planTree.js +57 -0
  106. package/dist/esm/utils/planTree.js.map +1 -0
  107. package/dist/esm/utils/redactionSegments.js +53 -0
  108. package/dist/esm/utils/redactionSegments.js.map +1 -0
  109. package/dist/esm/utils/registerNcDashboardWidgets.js +61 -0
  110. package/dist/esm/utils/registerNcDashboardWidgets.js.map +1 -0
  111. package/dist/esm/utils/searchRefusal.js +62 -0
  112. package/dist/esm/utils/searchRefusal.js.map +1 -0
  113. package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
  114. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  115. package/dist/nextcloud-vue.cjs.js +17560 -12844
  116. package/dist/nextcloud-vue.cjs.js.map +2 -2
  117. package/dist/nextcloud-vue.css +510 -76
  118. package/l10n/en.json +106 -1
  119. package/l10n/nl.json +105 -1
  120. package/package.json +1 -1
  121. package/src/components/CnActionDialog/CnActionDialog.vue +134 -13
  122. package/src/components/CnAddressSearch/CnAddressSearch.vue +343 -0
  123. package/src/components/CnAddressSearch/index.js +12 -0
  124. package/src/components/CnAssignDialog/CnAssignDialog.vue +525 -0
  125. package/src/components/CnAssignDialog/index.js +12 -0
  126. package/src/components/CnDecisionListWidget/CnDecisionListWidget.vue +388 -0
  127. package/src/components/CnDecisionListWidget/index.js +12 -0
  128. package/src/components/CnDuplicateWarning/CnDuplicateWarning.vue +318 -0
  129. package/src/components/CnDuplicateWarning/index.js +12 -0
  130. package/src/components/CnFormDialog/CnFormDialog.vue +19 -3
  131. package/src/components/CnIndexPage/CnIndexPage.vue +52 -0
  132. package/src/components/CnNcDashboardList/CnNcDashboardList.vue +218 -0
  133. package/src/components/CnNcDashboardList/index.js +12 -0
  134. package/src/components/CnObjectListWidget/CnObjectListWidget.vue +107 -6
  135. package/src/components/CnPlanWidget/CnPlanWidget.vue +246 -0
  136. package/src/components/CnPlanWidget/CnPlanWidgetRow.vue +117 -0
  137. package/src/components/CnPlanWidget/index.js +12 -0
  138. package/src/components/CnRedactionEditor/CnRedactionEditor.vue +655 -0
  139. package/src/components/CnRedactionEditor/index.js +13 -0
  140. package/src/components/CnRetentionWidget/CnRetentionWidget.vue +413 -0
  141. package/src/components/CnRetentionWidget/index.js +12 -0
  142. package/src/components/CnWidgetGrid/builtInWidgets.js +2 -0
  143. package/src/components/CnWidgetGrid/registerDashboardWidgets.js +31 -0
  144. package/src/components/CnWorkQueue/CnWorkQueue.vue +463 -0
  145. package/src/components/CnWorkQueue/index.js +12 -0
  146. package/src/components/index.js +9 -0
  147. package/src/css/index-page.css +24 -0
  148. package/src/index.js +12 -0
  149. package/src/schemas/app-manifest-v2.schema.json +119 -3
  150. package/src/utils/actionsDispatcher.js +10 -2
  151. package/src/utils/builtInModals.js +3 -1
  152. package/src/utils/duplicateWarning.js +59 -0
  153. package/src/utils/errors.js +4 -0
  154. package/src/utils/libraryWidgetKeys.js +4 -0
  155. package/src/utils/openDuplicateWarning.js +35 -0
  156. package/src/utils/optionsFrom.js +68 -1
  157. package/src/utils/planTree.js +54 -0
  158. package/src/utils/redactionSegments.js +50 -0
  159. package/src/utils/registerNcDashboardWidgets.js +57 -0
  160. package/src/utils/searchRefusal.js +59 -0
  161. package/src/utils/validateManifestV2.compiled.js +3 -3
@@ -0,0 +1,9 @@
1
+ import script from './CnNcDashboardList.vue2.js';
2
+ import { render } from './CnNcDashboardList.vue3.js';
3
+
4
+ script.render = render;
5
+ script.__scopeId = "data-v-3c7adfed";
6
+ const __sfc_main = script;
7
+
8
+ export { __sfc_main as default };
9
+ //# sourceMappingURL=CnNcDashboardList.vue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CnNcDashboardList.vue.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;"}
@@ -0,0 +1,181 @@
1
+ import { translate } from '@nextcloud/l10n';
2
+ import { generateUrl } from '@nextcloud/router';
3
+ import { fetchEndpointSource } from '../../composables/useEndpointSource.js';
4
+ import { fillTemplate } from '../../utils/fillTemplate.js';
5
+ import { openRowTarget } from '../../utils/linkNavigation.js';
6
+ import { readPath } from '../../utils/readPath.js';
7
+ import { resolveDeepTokens } from '../../utils/resolveFilterTokens.js';
8
+ import __sfc_main from '../CnDataTable/CnDataTable.vue.js';
9
+
10
+ /** A name and a muted, right-aligned trailing line. */
11
+ const COLUMNS = [
12
+ { key: 'mainText', cellClass: 'cn-cell--strong' },
13
+ { key: 'subText', cellClass: 'cn-cell--muted cn-cell--end' },
14
+ ];
15
+
16
+ /**
17
+ * A url for an anchor: absolute and app-root paths as given, else through `generateUrl`.
18
+ *
19
+ * @param {string} url The url.
20
+ * @return {string}
21
+ */
22
+ function appUrl(url) {
23
+ return /^(https?:)?\/\//i.test(url) || url.startsWith('/index.php') ? url : generateUrl(url)
24
+ }
25
+
26
+ /**
27
+ * CnNcDashboardList — the compact list a Nextcloud Dashboard widget shows:
28
+ * a bold name, a muted trailing line, and a "View all" link, read from
29
+ * OpenRegister or any endpoint. Each row opens its record in place (a
30
+ * modifier or middle click opens a new tab), because the Nextcloud Dashboard
31
+ * has no app router. `registerNcDashboardWidgets` mounts it per widget id, so
32
+ * an app's dashboard widgets are configuration rather than one component each.
33
+ *
34
+ * `config`:
35
+ * - `source`: `{ register, schema, params? }` (OpenRegister objects, rows in
36
+ * `results`) or an endpointSource `{ url, params?, responsePath? }`. Filter
37
+ * tokens (`@me`, `@today`, `@today-7d`) resolve in `params`, and an operator
38
+ * map (`{ deadline: { lt: '@today' } }`) goes out as `deadline[lt]=…`.
39
+ * - `itemsPath`: the rows' path in the response, when not `results`.
40
+ * - `limit`: the most rows shown (default 7).
41
+ * - `title`, `subtitle`: templates filled from a row (default `{title}`, none).
42
+ * - `href`: a template for a row's page (`/apps/dossiq/cases/{id}`).
43
+ * - `viewAll`: `{ href, label? }`.
44
+ * - `emptyText`: text for an empty list.
45
+ *
46
+ * Texts go through the host translate.
47
+ */
48
+ var script = {
49
+ name: 'CnNcDashboardList',
50
+
51
+ components: { CnDataTable: __sfc_main },
52
+
53
+ inject: {
54
+ /** The host app's translate (provided by `registerNcDashboardWidgets`). */
55
+ cnTranslate: { default: () => (key) => key },
56
+ },
57
+
58
+ props: {
59
+ /**
60
+ * The list's configuration (see the component docs).
61
+ *
62
+ * @type {object}
63
+ */
64
+ config: {
65
+ type: Object,
66
+ default: () => ({}),
67
+ },
68
+ },
69
+
70
+ data() {
71
+ return {
72
+ items: [],
73
+ loading: true,
74
+ error: false,
75
+ columns: COLUMNS,
76
+ }
77
+ },
78
+
79
+ computed: {
80
+ /** @return {number} The most rows shown. */
81
+ limit() {
82
+ return Number(this.config.limit) > 0 ? Number(this.config.limit) : 7
83
+ },
84
+
85
+ /**
86
+ * The rows as the table draws them.
87
+ *
88
+ * @spec openspec/changes/nc-dashboard-widgets/specs/nc-dashboard-widgets/spec.md#requirement-dashboard-widgets-are-configuration
89
+ * @return {Array<{id: string, mainText: string, subText: string, href: string}>}
90
+ */
91
+ rows() {
92
+ return this.items.slice(0, this.limit).map((item, index) => {
93
+ const id = item.id ?? (item['@self'] && item['@self'].id) ?? index;
94
+ return {
95
+ id: String(id),
96
+ mainText: this.fill(this.config.title || '{title}', item) || String(id),
97
+ subText: this.fill(this.config.subtitle, item),
98
+ href: this.config.href ? appUrl(fillTemplate(this.config.href, { ...item, id })) : '',
99
+ }
100
+ })
101
+ },
102
+
103
+ /** @return {string} The "View all" href, or ''. */
104
+ viewAllHref() {
105
+ const viewAll = this.config.viewAll;
106
+ return viewAll && viewAll.href ? appUrl(viewAll.href) : ''
107
+ },
108
+ },
109
+
110
+ mounted() {
111
+ this.load();
112
+ },
113
+
114
+ methods: {
115
+ t: translate,
116
+
117
+ /**
118
+ * A config text through the host translate.
119
+ *
120
+ * @param {unknown} value The text.
121
+ * @return {string}
122
+ */
123
+ text(value) {
124
+ return typeof value === 'string' && value !== '' ? this.cnTranslate(value) : ''
125
+ },
126
+
127
+ /**
128
+ * A template filled from a row, translated first.
129
+ *
130
+ * @param {unknown} template The template.
131
+ * @param {object} item The row.
132
+ * @return {string}
133
+ */
134
+ fill(template, item) {
135
+ return typeof template === 'string' && template !== '' ? fillTemplate(this.cnTranslate(template), item).trim() : ''
136
+ },
137
+
138
+ /**
139
+ * Read the rows: OpenRegister objects or an endpoint.
140
+ *
141
+ * @spec openspec/changes/nc-dashboard-widgets/specs/nc-dashboard-widgets/spec.md#requirement-dashboard-widgets-are-configuration
142
+ * @return {Promise<void>}
143
+ */
144
+ async load() {
145
+ const source = this.config.source || {};
146
+ this.loading = true;
147
+ this.error = false;
148
+ try {
149
+ // Tokens nested below an operator map (`'@self': { lastUserActivity: { lt: '@today-7d' } }`)
150
+ // resolve here; the endpoint path resolves one level only.
151
+ const params = resolveDeepTokens(source.params || {}, {});
152
+ const request = source.register && source.schema
153
+ ? { url: `/apps/openregister/api/objects/${source.register}/${source.schema}`, params: { _limit: this.limit, ...params } }
154
+ : { ...source, params };
155
+ const data = await fetchEndpointSource(request, {}, { fresh: true });
156
+ const list = readPath(data, this.config.itemsPath || (source.register ? 'results' : '')) ?? data;
157
+ this.items = Array.isArray(list) ? list.filter((item) => item && typeof item === 'object') : [];
158
+ } catch {
159
+ this.error = true;
160
+ this.items = [];
161
+ } finally {
162
+ this.loading = false;
163
+ }
164
+ },
165
+
166
+ /**
167
+ * Open a row's page.
168
+ *
169
+ * @param {object} row The row.
170
+ * @param {MouseEvent} event The click.
171
+ */
172
+ onRowClick(row, event) {
173
+ if (row && row.href) {
174
+ openRowTarget(event, row.href);
175
+ }
176
+ },
177
+ },
178
+ };
179
+
180
+ export { script as default };
181
+ //# sourceMappingURL=CnNcDashboardList.vue2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CnNcDashboardList.vue2.js","sources":["../../../../src/components/CnNcDashboardList/CnNcDashboardList.vue"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n<template>\n\t<div class=\"cn-nc-dashboard-list\" data-testid=\"cn-nc-dashboard-list\">\n\t\t<p v-if=\"error\"\n\t\t\tclass=\"cn-nc-dashboard-list__error\"\n\t\t\trole=\"status\"\n\t\t\tdata-testid=\"cn-nc-dashboard-list-error\">\n\t\t\t{{ t('nextcloud-vue', 'This list could not be read.') }}\n\t\t</p>\n\t\t<CnDataTable v-else\n\t\t\t:rows=\"rows\"\n\t\t\t:columns=\"columns\"\n\t\t\t:loading=\"loading\"\n\t\t\thideHeader\n\t\t\tborderless\n\t\t\t:emptyText=\"text(config.emptyText) || t('nextcloud-vue', 'Nothing to show')\"\n\t\t\t@rowClick=\"onRowClick\"\n\t\t\t@rowAuxClick=\"onRowClick\">\n\t\t\t<template v-if=\"viewAllHref\" #footer>\n\t\t\t\t<a class=\"cn-data-table__view-all\" :href=\"viewAllHref\" data-testid=\"cn-nc-dashboard-list-view-all\">\n\t\t\t\t\t{{ text(config.viewAll.label) || t('nextcloud-vue', 'View all') }} →\n\t\t\t\t</a>\n\t\t\t</template>\n\t\t</CnDataTable>\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { generateUrl } from '@nextcloud/router'\nimport { fetchEndpointSource } from '../../composables/useEndpointSource.js'\nimport { fillTemplate } from '../../utils/fillTemplate.js'\nimport { openRowTarget } from '../../utils/linkNavigation.js'\nimport { readPath } from '../../utils/readPath.js'\nimport { resolveDeepTokens } from '../../utils/resolveFilterTokens.js'\nimport { CnDataTable } from '../CnDataTable/index.js'\n\n/** A name and a muted, right-aligned trailing line. */\nconst COLUMNS = [\n\t{ key: 'mainText', cellClass: 'cn-cell--strong' },\n\t{ key: 'subText', cellClass: 'cn-cell--muted cn-cell--end' },\n]\n\n/**\n * A url for an anchor: absolute and app-root paths as given, else through `generateUrl`.\n *\n * @param {string} url The url.\n * @return {string}\n */\nfunction appUrl(url) {\n\treturn /^(https?:)?\\/\\//i.test(url) || url.startsWith('/index.php') ? url : generateUrl(url)\n}\n\n/**\n * CnNcDashboardList — the compact list a Nextcloud Dashboard widget shows:\n * a bold name, a muted trailing line, and a \"View all\" link, read from\n * OpenRegister or any endpoint. Each row opens its record in place (a\n * modifier or middle click opens a new tab), because the Nextcloud Dashboard\n * has no app router. `registerNcDashboardWidgets` mounts it per widget id, so\n * an app's dashboard widgets are configuration rather than one component each.\n *\n * `config`:\n * - `source`: `{ register, schema, params? }` (OpenRegister objects, rows in\n * `results`) or an endpointSource `{ url, params?, responsePath? }`. Filter\n * tokens (`@me`, `@today`, `@today-7d`) resolve in `params`, and an operator\n * map (`{ deadline: { lt: '@today' } }`) goes out as `deadline[lt]=…`.\n * - `itemsPath`: the rows' path in the response, when not `results`.\n * - `limit`: the most rows shown (default 7).\n * - `title`, `subtitle`: templates filled from a row (default `{title}`, none).\n * - `href`: a template for a row's page (`/apps/dossiq/cases/{id}`).\n * - `viewAll`: `{ href, label? }`.\n * - `emptyText`: text for an empty list.\n *\n * Texts go through the host translate.\n */\nexport default {\n\tname: 'CnNcDashboardList',\n\n\tcomponents: { CnDataTable },\n\n\tinject: {\n\t\t/** The host app's translate (provided by `registerNcDashboardWidgets`). */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The list's configuration (see the component docs).\n\t\t *\n\t\t * @type {object}\n\t\t */\n\t\tconfig: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\titems: [],\n\t\t\tloading: true,\n\t\t\terror: false,\n\t\t\tcolumns: COLUMNS,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/** @return {number} The most rows shown. */\n\t\tlimit() {\n\t\t\treturn Number(this.config.limit) > 0 ? Number(this.config.limit) : 7\n\t\t},\n\n\t\t/**\n\t\t * The rows as the table draws them.\n\t\t *\n\t\t * @spec openspec/changes/nc-dashboard-widgets/specs/nc-dashboard-widgets/spec.md#requirement-dashboard-widgets-are-configuration\n\t\t * @return {Array<{id: string, mainText: string, subText: string, href: string}>}\n\t\t */\n\t\trows() {\n\t\t\treturn this.items.slice(0, this.limit).map((item, index) => {\n\t\t\t\tconst id = item.id ?? (item['@self'] && item['@self'].id) ?? index\n\t\t\t\treturn {\n\t\t\t\t\tid: String(id),\n\t\t\t\t\tmainText: this.fill(this.config.title || '{title}', item) || String(id),\n\t\t\t\t\tsubText: this.fill(this.config.subtitle, item),\n\t\t\t\t\thref: this.config.href ? appUrl(fillTemplate(this.config.href, { ...item, id })) : '',\n\t\t\t\t}\n\t\t\t})\n\t\t},\n\n\t\t/** @return {string} The \"View all\" href, or ''. */\n\t\tviewAllHref() {\n\t\t\tconst viewAll = this.config.viewAll\n\t\t\treturn viewAll && viewAll.href ? appUrl(viewAll.href) : ''\n\t\t},\n\t},\n\n\tmounted() {\n\t\tthis.load()\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/**\n\t\t * A config text through the host translate.\n\t\t *\n\t\t * @param {unknown} value The text.\n\t\t * @return {string}\n\t\t */\n\t\ttext(value) {\n\t\t\treturn typeof value === 'string' && value !== '' ? this.cnTranslate(value) : ''\n\t\t},\n\n\t\t/**\n\t\t * A template filled from a row, translated first.\n\t\t *\n\t\t * @param {unknown} template The template.\n\t\t * @param {object} item The row.\n\t\t * @return {string}\n\t\t */\n\t\tfill(template, item) {\n\t\t\treturn typeof template === 'string' && template !== '' ? fillTemplate(this.cnTranslate(template), item).trim() : ''\n\t\t},\n\n\t\t/**\n\t\t * Read the rows: OpenRegister objects or an endpoint.\n\t\t *\n\t\t * @spec openspec/changes/nc-dashboard-widgets/specs/nc-dashboard-widgets/spec.md#requirement-dashboard-widgets-are-configuration\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync load() {\n\t\t\tconst source = this.config.source || {}\n\t\t\tthis.loading = true\n\t\t\tthis.error = false\n\t\t\ttry {\n\t\t\t\t// Tokens nested below an operator map (`'@self': { lastUserActivity: { lt: '@today-7d' } }`)\n\t\t\t\t// resolve here; the endpoint path resolves one level only.\n\t\t\t\tconst params = resolveDeepTokens(source.params || {}, {})\n\t\t\t\tconst request = source.register && source.schema\n\t\t\t\t\t? { url: `/apps/openregister/api/objects/${source.register}/${source.schema}`, params: { _limit: this.limit, ...params } }\n\t\t\t\t\t: { ...source, params }\n\t\t\t\tconst data = await fetchEndpointSource(request, {}, { fresh: true })\n\t\t\t\tconst list = readPath(data, this.config.itemsPath || (source.register ? 'results' : '')) ?? data\n\t\t\t\tthis.items = Array.isArray(list) ? list.filter((item) => item && typeof item === 'object') : []\n\t\t\t} catch {\n\t\t\t\tthis.error = true\n\t\t\t\tthis.items = []\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Open a row's page.\n\t\t *\n\t\t * @param {object} row The row.\n\t\t * @param {MouseEvent} event The click.\n\t\t */\n\t\tonRowClick(row, event) {\n\t\t\tif (row && row.href) {\n\t\t\t\topenRowTarget(event, row.href)\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-nc-dashboard-list__error {\n\tmargin: 0;\n\tpadding: 8px 0;\n\tcolor: var(--color-text-maxcontrast);\n}\n</style>\n"],"names":["CnDataTable","t"],"mappings":"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"}
@@ -0,0 +1,50 @@
1
+ import { resolveComponent, openBlock, createElementBlock, toDisplayString, createBlock, createSlots, withCtx, createElementVNode } from 'vue';
2
+
3
+ const _hoisted_1 = {
4
+ class: "cn-nc-dashboard-list",
5
+ "data-testid": "cn-nc-dashboard-list"
6
+ };
7
+ const _hoisted_2 = {
8
+ key: 0,
9
+ class: "cn-nc-dashboard-list__error",
10
+ role: "status",
11
+ "data-testid": "cn-nc-dashboard-list-error"
12
+ };
13
+ const _hoisted_3 = ["href"];
14
+
15
+ function render(_ctx, _cache, $props, $setup, $data, $options) {
16
+ const _component_CnDataTable = resolveComponent("CnDataTable");
17
+
18
+ return (openBlock(), createElementBlock("div", _hoisted_1, [
19
+ ($data.error)
20
+ ? (openBlock(), createElementBlock("p", _hoisted_2, toDisplayString($options.t('nextcloud-vue', 'This list could not be read.')), 1 /* TEXT */))
21
+ : (openBlock(), createBlock(_component_CnDataTable, {
22
+ key: 1,
23
+ rows: $options.rows,
24
+ columns: $data.columns,
25
+ loading: $data.loading,
26
+ hideHeader: "",
27
+ borderless: "",
28
+ emptyText: $options.text($props.config.emptyText) || $options.t('nextcloud-vue', 'Nothing to show'),
29
+ onRowClick: $options.onRowClick,
30
+ onRowAuxClick: $options.onRowClick
31
+ }, createSlots({ _: 2 /* DYNAMIC */ }, [
32
+ ($options.viewAllHref)
33
+ ? {
34
+ name: "footer",
35
+ fn: withCtx(() => [
36
+ createElementVNode("a", {
37
+ class: "cn-data-table__view-all",
38
+ href: $options.viewAllHref,
39
+ "data-testid": "cn-nc-dashboard-list-view-all"
40
+ }, toDisplayString($options.text($props.config.viewAll.label) || $options.t('nextcloud-vue', 'View all')) + " → ", 9 /* TEXT, PROPS */, _hoisted_3)
41
+ ]),
42
+ key: "0"
43
+ }
44
+ : undefined
45
+ ]), 1032 /* PROPS, DYNAMIC_SLOTS */, ["rows", "columns", "loading", "emptyText", "onRowClick", "onRowAuxClick"]))
46
+ ]))
47
+ }
48
+
49
+ export { render };
50
+ //# sourceMappingURL=CnNcDashboardList.vue3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CnNcDashboardList.vue3.js","sources":["../../../../src/components/CnNcDashboardList/CnNcDashboardList.vue?vue&type=template&id=3c7adfed&lang.js"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n<template>\n\t<div class=\"cn-nc-dashboard-list\" data-testid=\"cn-nc-dashboard-list\">\n\t\t<p v-if=\"error\"\n\t\t\tclass=\"cn-nc-dashboard-list__error\"\n\t\t\trole=\"status\"\n\t\t\tdata-testid=\"cn-nc-dashboard-list-error\">\n\t\t\t{{ t('nextcloud-vue', 'This list could not be read.') }}\n\t\t</p>\n\t\t<CnDataTable v-else\n\t\t\t:rows=\"rows\"\n\t\t\t:columns=\"columns\"\n\t\t\t:loading=\"loading\"\n\t\t\thideHeader\n\t\t\tborderless\n\t\t\t:emptyText=\"text(config.emptyText) || t('nextcloud-vue', 'Nothing to show')\"\n\t\t\t@rowClick=\"onRowClick\"\n\t\t\t@rowAuxClick=\"onRowClick\">\n\t\t\t<template v-if=\"viewAllHref\" #footer>\n\t\t\t\t<a class=\"cn-data-table__view-all\" :href=\"viewAllHref\" data-testid=\"cn-nc-dashboard-list-view-all\">\n\t\t\t\t\t{{ text(config.viewAll.label) || t('nextcloud-vue', 'View all') }} →\n\t\t\t\t</a>\n\t\t\t</template>\n\t\t</CnDataTable>\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { generateUrl } from '@nextcloud/router'\nimport { fetchEndpointSource } from '../../composables/useEndpointSource.js'\nimport { fillTemplate } from '../../utils/fillTemplate.js'\nimport { openRowTarget } from '../../utils/linkNavigation.js'\nimport { readPath } from '../../utils/readPath.js'\nimport { resolveDeepTokens } from '../../utils/resolveFilterTokens.js'\nimport { CnDataTable } from '../CnDataTable/index.js'\n\n/** A name and a muted, right-aligned trailing line. */\nconst COLUMNS = [\n\t{ key: 'mainText', cellClass: 'cn-cell--strong' },\n\t{ key: 'subText', cellClass: 'cn-cell--muted cn-cell--end' },\n]\n\n/**\n * A url for an anchor: absolute and app-root paths as given, else through `generateUrl`.\n *\n * @param {string} url The url.\n * @return {string}\n */\nfunction appUrl(url) {\n\treturn /^(https?:)?\\/\\//i.test(url) || url.startsWith('/index.php') ? url : generateUrl(url)\n}\n\n/**\n * CnNcDashboardList — the compact list a Nextcloud Dashboard widget shows:\n * a bold name, a muted trailing line, and a \"View all\" link, read from\n * OpenRegister or any endpoint. Each row opens its record in place (a\n * modifier or middle click opens a new tab), because the Nextcloud Dashboard\n * has no app router. `registerNcDashboardWidgets` mounts it per widget id, so\n * an app's dashboard widgets are configuration rather than one component each.\n *\n * `config`:\n * - `source`: `{ register, schema, params? }` (OpenRegister objects, rows in\n * `results`) or an endpointSource `{ url, params?, responsePath? }`. Filter\n * tokens (`@me`, `@today`, `@today-7d`) resolve in `params`, and an operator\n * map (`{ deadline: { lt: '@today' } }`) goes out as `deadline[lt]=…`.\n * - `itemsPath`: the rows' path in the response, when not `results`.\n * - `limit`: the most rows shown (default 7).\n * - `title`, `subtitle`: templates filled from a row (default `{title}`, none).\n * - `href`: a template for a row's page (`/apps/dossiq/cases/{id}`).\n * - `viewAll`: `{ href, label? }`.\n * - `emptyText`: text for an empty list.\n *\n * Texts go through the host translate.\n */\nexport default {\n\tname: 'CnNcDashboardList',\n\n\tcomponents: { CnDataTable },\n\n\tinject: {\n\t\t/** The host app's translate (provided by `registerNcDashboardWidgets`). */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The list's configuration (see the component docs).\n\t\t *\n\t\t * @type {object}\n\t\t */\n\t\tconfig: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\titems: [],\n\t\t\tloading: true,\n\t\t\terror: false,\n\t\t\tcolumns: COLUMNS,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/** @return {number} The most rows shown. */\n\t\tlimit() {\n\t\t\treturn Number(this.config.limit) > 0 ? Number(this.config.limit) : 7\n\t\t},\n\n\t\t/**\n\t\t * The rows as the table draws them.\n\t\t *\n\t\t * @spec openspec/changes/nc-dashboard-widgets/specs/nc-dashboard-widgets/spec.md#requirement-dashboard-widgets-are-configuration\n\t\t * @return {Array<{id: string, mainText: string, subText: string, href: string}>}\n\t\t */\n\t\trows() {\n\t\t\treturn this.items.slice(0, this.limit).map((item, index) => {\n\t\t\t\tconst id = item.id ?? (item['@self'] && item['@self'].id) ?? index\n\t\t\t\treturn {\n\t\t\t\t\tid: String(id),\n\t\t\t\t\tmainText: this.fill(this.config.title || '{title}', item) || String(id),\n\t\t\t\t\tsubText: this.fill(this.config.subtitle, item),\n\t\t\t\t\thref: this.config.href ? appUrl(fillTemplate(this.config.href, { ...item, id })) : '',\n\t\t\t\t}\n\t\t\t})\n\t\t},\n\n\t\t/** @return {string} The \"View all\" href, or ''. */\n\t\tviewAllHref() {\n\t\t\tconst viewAll = this.config.viewAll\n\t\t\treturn viewAll && viewAll.href ? appUrl(viewAll.href) : ''\n\t\t},\n\t},\n\n\tmounted() {\n\t\tthis.load()\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/**\n\t\t * A config text through the host translate.\n\t\t *\n\t\t * @param {unknown} value The text.\n\t\t * @return {string}\n\t\t */\n\t\ttext(value) {\n\t\t\treturn typeof value === 'string' && value !== '' ? this.cnTranslate(value) : ''\n\t\t},\n\n\t\t/**\n\t\t * A template filled from a row, translated first.\n\t\t *\n\t\t * @param {unknown} template The template.\n\t\t * @param {object} item The row.\n\t\t * @return {string}\n\t\t */\n\t\tfill(template, item) {\n\t\t\treturn typeof template === 'string' && template !== '' ? fillTemplate(this.cnTranslate(template), item).trim() : ''\n\t\t},\n\n\t\t/**\n\t\t * Read the rows: OpenRegister objects or an endpoint.\n\t\t *\n\t\t * @spec openspec/changes/nc-dashboard-widgets/specs/nc-dashboard-widgets/spec.md#requirement-dashboard-widgets-are-configuration\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync load() {\n\t\t\tconst source = this.config.source || {}\n\t\t\tthis.loading = true\n\t\t\tthis.error = false\n\t\t\ttry {\n\t\t\t\t// Tokens nested below an operator map (`'@self': { lastUserActivity: { lt: '@today-7d' } }`)\n\t\t\t\t// resolve here; the endpoint path resolves one level only.\n\t\t\t\tconst params = resolveDeepTokens(source.params || {}, {})\n\t\t\t\tconst request = source.register && source.schema\n\t\t\t\t\t? { url: `/apps/openregister/api/objects/${source.register}/${source.schema}`, params: { _limit: this.limit, ...params } }\n\t\t\t\t\t: { ...source, params }\n\t\t\t\tconst data = await fetchEndpointSource(request, {}, { fresh: true })\n\t\t\t\tconst list = readPath(data, this.config.itemsPath || (source.register ? 'results' : '')) ?? data\n\t\t\t\tthis.items = Array.isArray(list) ? list.filter((item) => item && typeof item === 'object') : []\n\t\t\t} catch {\n\t\t\t\tthis.error = true\n\t\t\t\tthis.items = []\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Open a row's page.\n\t\t *\n\t\t * @param {object} row The row.\n\t\t * @param {MouseEvent} event The click.\n\t\t */\n\t\tonRowClick(row, event) {\n\t\t\tif (row && row.href) {\n\t\t\t\topenRowTarget(event, row.href)\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-nc-dashboard-list__error {\n\tmargin: 0;\n\tpadding: 8px 0;\n\tcolor: var(--color-text-maxcontrast);\n}\n</style>\n"],"names":["_openBlock","_createElementBlock","_toDisplayString","_createBlock","_createElementVNode"],"mappings":";;;AASM,EAAA,KAAK,EAAC,sBAAsB;AAAC,EAAA,aAAQ,EAAA,sBAAA;;;;AAExC,EAAA,KAAE,EAAA,6BAAA;AACF,EAAA,IAAI,EAAC,QAAQ;AACb,EAAA,aAAW,EAAA,4BAAA;;;;;;;AAJX,EAAA,QAAAA,SAAA,EAAA,EAAAC,kBAAA,QAAA;KACQ,KAAK,CAAA,KAAA;wCAKV,CAAA,GAAA,YAAA,EAAAC,eAAA,CADA,QAAC,CAAA,CAAA,CAAA,eAAA,EAAA,8BAAA,CAAA,CAAA,EAAA,CAAA,YAAA;sBAEJC;;AACC,UAAA,IAAI,EAAE,QAAE,CAAA,IAAA;AACR,UAAA,OAAO,EAAE,KAAO,CAAA,OAAA;AAChB,UAAA,OAAO,EAAE,KAAO,CAAA,OAAA;AACjB,UAAA,UAAU,EAAV,EAAU;AACV,UAAA,UAAU,EAAV,EAAU;AACT,UAAA,SAAS,EAAE,QAAI,CAAA,IAAA,CAAC,aAAM,CAAC,SAAS,KAAK,QAAC,CAAA,CAAA,CAAA,eAAA,EAAA,iBAAA,CAAA;oBAC9B,EAAE,QAAU,CAAA,UAAA;AACpB,UAAA,aAAG,EAAA,QAAA,CAAA,UAAA;;WACM,QAAA,CAAA,WAAA;;sBAAA,QAAA;4BACT;kBAAAC;AAAG,oBAAA,KAAK,EAAC,yBAAE;AAAA,oBAAA,IAAA,EAAA,QAAA,CAAA,WAAA;AAAA,oBAAA,aAAA,EAAA,+BAAA;qCACT,QAAA,CAAA,IAAA,CAAA,aAAA,CAAA,OAAA,CAAA,KAAA,CAAA,IAAA,QAAA,CAAA,CAAA,CAAA,eAAA,EAAA,UAAA,CAAA,CAAA,GAAA;;;;;;;;;;;"}
@@ -2,7 +2,7 @@ import script from './CnObjectListWidget.vue2.js';
2
2
  import { render } from './CnObjectListWidget.vue3.js';
3
3
 
4
4
  script.render = render;
5
- script.__scopeId = "data-v-20aeb092";
5
+ script.__scopeId = "data-v-78ec6d64";
6
6
  const __sfc_main = script;
7
7
 
8
8
  export { __sfc_main as default };
@@ -14,6 +14,7 @@ import { objectFieldValue, objectDisplayName } from '../../utils/objectName.js';
14
14
  import { rowIdOf, renumberRows, moveIndex } from '../../utils/reorderRows.js';
15
15
  import { dropOptionalUnresolved, resolveFilterTokens, hasUnresolvedTokens } from '../../utils/resolveFilterTokens.js';
16
16
  import { markNewTabHandled } from '../../utils/rowAuxClick.js';
17
+ import { changedKeys } from '../../utils/saveEdited.js';
17
18
  import { dispatchObjectCreated } from '../../utils/walkthroughSignals.js';
18
19
  import __sfc_main$4 from '../CnRowActions/CnRowActions.vue.js';
19
20
 
@@ -152,7 +153,7 @@ var script = {
152
153
  },
153
154
  },
154
155
 
155
- emits: ['created', 'row-click', 'row-aux-click', 'view-all', 'files-dropped'],
156
+ emits: ['created', 'updated', 'row-click', 'row-aux-click', 'view-all', 'files-dropped'],
156
157
 
157
158
  data() {
158
159
  return {
@@ -178,6 +179,8 @@ var script = {
178
179
  fitStage: 0,
179
180
  /** Whether the create dialog is open. */
180
181
  showCreate: false,
182
+ /** The row the form edits (`content.editable`), or null while it creates. */
183
+ editingRow: null,
181
184
  /**
182
185
  * The list's own schema definition, once fetched.
183
186
  *
@@ -426,6 +429,12 @@ var script = {
426
429
  ...this.rowActionLink(action),
427
430
  handler: (row) => this.runRowAction(action, row),
428
431
  }));
432
+ if (this.content.editable === true) {
433
+ mapped.unshift({ label: translate('nextcloud-vue', 'Edit'), icon: 'Pencil', handler: (row) => this.openEdit(row) });
434
+ }
435
+ if (this.content.deletable === true) {
436
+ mapped.push({ label: translate('nextcloud-vue', 'Delete'), icon: 'TrashCanOutline', destructive: true, handler: (row) => this.deleteRow(row) });
437
+ }
429
438
  if (this.reorderField === '') {
430
439
  return mapped
431
440
  }
@@ -1240,6 +1249,7 @@ var script = {
1240
1249
  forgetCreateSchema() {
1241
1250
  this.createSchema = null;
1242
1251
  this.showCreate = false;
1252
+ this.editingRow = null;
1243
1253
  },
1244
1254
 
1245
1255
  /**
@@ -1250,6 +1260,97 @@ var script = {
1250
1260
  *
1251
1261
  * @return {Promise<void>}
1252
1262
  */
1263
+ /**
1264
+ * Edit a row in the list's own form (`content.editable`): the form the
1265
+ * Add button opens, filled with the row.
1266
+ *
1267
+ * @param {object} row The row.
1268
+ * @return {Promise<void>}
1269
+ * @spec openspec/changes/object-list-edit-in-place/specs/object-list-widget/spec.md#requirement-a-list-can-edit-a-row-in-its-own-form
1270
+ */
1271
+ async openEdit(row) {
1272
+ try {
1273
+ await this.ensureSchema();
1274
+ this.showCreate = false;
1275
+ this.editingRow = row ? { ...row } : null;
1276
+ } catch (e) {
1277
+ this.error = (e && e.message) || 'error';
1278
+ }
1279
+ },
1280
+
1281
+ /**
1282
+ * Delete a row of the list's own schema (`content.deletable`). The
1283
+ * toast offers Undo; a refusal toasts the server's message.
1284
+ *
1285
+ * @param {object} row The row.
1286
+ * @return {Promise<void>}
1287
+ * @spec openspec/changes/object-list-edit-in-place/specs/object-list-widget/spec.md#requirement-a-list-can-edit-a-row-in-its-own-form
1288
+ */
1289
+ async deleteRow(row) {
1290
+ const c = this.content || {};
1291
+ const id = rowIdOf(row);
1292
+ if (id === '') {
1293
+ return
1294
+ }
1295
+ const pending = (async () => {
1296
+ const [{ default: axios }, { generateUrl }] = await Promise.all([import('@nextcloud/axios'), import('@nextcloud/router')]);
1297
+ try {
1298
+ await axios.delete(generateUrl('/apps/openregister/api/objects/{register}/{schema}/{id}', { register: c.register, schema: c.schema, id }));
1299
+ } catch (e) {
1300
+ const data = e && e.response && e.response.data;
1301
+ const refusal = data && (data.message || data.error);
1302
+ throw new Error((typeof refusal === 'string' && refusal) || (e && e.message) || translate('nextcloud-vue', 'The delete failed'), { cause: e })
1303
+ }
1304
+ return true
1305
+ })();
1306
+ await this.reportDelete(row, pending);
1307
+ await pending.catch(() => {});
1308
+ this.fetchRows();
1309
+ },
1310
+
1311
+ /** Close the form, whether it created or edited. */
1312
+ closeForm() {
1313
+ this.showCreate = false;
1314
+ this.editingRow = null;
1315
+ },
1316
+
1317
+ /**
1318
+ * Save an edited row: a PATCH of the keys the form changed, so every
1319
+ * key it does not show keeps its stored value; nothing when nothing
1320
+ * changed. A refusal shows the server's message in the dialog.
1321
+ *
1322
+ * @param {object} formData The edited values.
1323
+ * @return {Promise<void>}
1324
+ * @spec openspec/changes/object-list-edit-in-place/specs/object-list-widget/spec.md#requirement-a-list-can-edit-a-row-in-its-own-form
1325
+ */
1326
+ async onEditConfirm(formData) {
1327
+ const c = this.content || {};
1328
+ const row = this.editingRow;
1329
+ const id = rowIdOf(row);
1330
+ const changes = changedKeys(row, formData);
1331
+ try {
1332
+ if (id !== '' && Object.keys(changes).length > 0) {
1333
+ const [{ default: axios }, { generateUrl }] = await Promise.all([import('@nextcloud/axios'), import('@nextcloud/router')]);
1334
+ await axios.patch(generateUrl('/apps/openregister/api/objects/{register}/{schema}/{id}', { register: c.register, schema: c.schema, id }), changes);
1335
+ }
1336
+ if (this.$refs.createDialog) {
1337
+ this.$refs.createDialog.setResult({ success: true });
1338
+ }
1339
+ /**
1340
+ * @event updated Emitted after a row was edited, with the keys that changed.
1341
+ * @type {object}
1342
+ */
1343
+ this.$emit('updated', { id, changes });
1344
+ this.fetchRows();
1345
+ } catch (e) {
1346
+ const data = e && e.response && e.response.data;
1347
+ const refusal = data && (data.message || data.error);
1348
+ if (this.$refs.createDialog) {
1349
+ this.$refs.createDialog.setResult({ error: (typeof refusal === 'string' && refusal) || (e && e.message) || 'error' });
1350
+ }
1351
+ }
1352
+ },
1353
+
1253
1354
  async openCreate() {
1254
1355
  const c = this.content || {};
1255
1356
  if (!c.schema) {