@conduction/nextcloud-vue 2.115.0 → 2.116.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/dist/esm/components/CnActionButtons/CnActionButtons.vue.js +1 -1
  2. package/dist/esm/components/CnActionButtons/CnActionButtons.vue2.js +92 -0
  3. package/dist/esm/components/CnActionButtons/CnActionButtons.vue2.js.map +1 -1
  4. package/dist/esm/components/CnActionButtons/CnActionButtons.vue3.js +2 -2
  5. package/dist/esm/components/CnActionButtons/CnActionButtons.vue3.js.map +1 -1
  6. package/dist/esm/components/CnChecklistWidget/CnChecklistWidget.vue.js +9 -0
  7. package/dist/esm/components/CnChecklistWidget/CnChecklistWidget.vue.js.map +1 -0
  8. package/dist/esm/components/CnChecklistWidget/CnChecklistWidget.vue2.js +145 -0
  9. package/dist/esm/components/CnChecklistWidget/CnChecklistWidget.vue2.js.map +1 -0
  10. package/dist/esm/components/CnChecklistWidget/CnChecklistWidget.vue3.js +58 -0
  11. package/dist/esm/components/CnChecklistWidget/CnChecklistWidget.vue3.js.map +1 -0
  12. package/dist/esm/components/CnChecklistWidget/index.js +28 -0
  13. package/dist/esm/components/CnChecklistWidget/index.js.map +1 -0
  14. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
  15. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +25 -0
  16. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
  17. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +1 -1
  18. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
  19. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +376 -51
  20. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  21. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +364 -285
  22. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  23. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue.js +1 -1
  24. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js +11 -0
  25. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
  26. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js +6 -4
  27. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
  28. package/dist/esm/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue.js +1 -1
  29. package/dist/esm/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue2.js +48 -1
  30. package/dist/esm/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue2.js.map +1 -1
  31. package/dist/esm/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue3.js +11 -2
  32. package/dist/esm/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue3.js.map +1 -1
  33. package/dist/esm/components/CnMacrosWidget/CnMacrosWidget.vue.js +1 -1
  34. package/dist/esm/components/CnMacrosWidget/CnMacrosWidget.vue2.js +11 -3
  35. package/dist/esm/components/CnMacrosWidget/CnMacrosWidget.vue2.js.map +1 -1
  36. package/dist/esm/components/CnMacrosWidget/CnMacrosWidget.vue3.js +8 -3
  37. package/dist/esm/components/CnMacrosWidget/CnMacrosWidget.vue3.js.map +1 -1
  38. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue.js +1 -1
  39. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +55 -2
  40. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
  41. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
  42. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js +1 -1
  43. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +94 -4
  44. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
  45. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js +248 -239
  46. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -1
  47. package/dist/esm/components/CnRecordListWidget/CnRecordNoteRows.vue.js +9 -0
  48. package/dist/esm/components/CnRecordListWidget/CnRecordNoteRows.vue.js.map +1 -0
  49. package/dist/esm/components/CnRecordListWidget/CnRecordNoteRows.vue2.js +282 -0
  50. package/dist/esm/components/CnRecordListWidget/CnRecordNoteRows.vue2.js.map +1 -0
  51. package/dist/esm/components/CnRecordListWidget/CnRecordNoteRows.vue3.js +202 -0
  52. package/dist/esm/components/CnRecordListWidget/CnRecordNoteRows.vue3.js.map +1 -0
  53. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue.js +1 -1
  54. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +10 -5
  55. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
  56. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
  57. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +1 -0
  58. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
  59. package/dist/esm/composables/indexSources.js +64 -0
  60. package/dist/esm/composables/indexSources.js.map +1 -1
  61. package/dist/esm/composables/useTaskInboxStore.js +85 -0
  62. package/dist/esm/composables/useTaskInboxStore.js.map +1 -1
  63. package/dist/esm/dialogs/CnAccessPurposeDialog.vue.js +9 -0
  64. package/dist/esm/dialogs/CnAccessPurposeDialog.vue.js.map +1 -0
  65. package/dist/esm/dialogs/CnAccessPurposeDialog.vue2.js +107 -0
  66. package/dist/esm/dialogs/CnAccessPurposeDialog.vue2.js.map +1 -0
  67. package/dist/esm/dialogs/CnAccessPurposeDialog.vue3.js +110 -0
  68. package/dist/esm/dialogs/CnAccessPurposeDialog.vue3.js.map +1 -0
  69. package/dist/esm/index.js +1 -0
  70. package/dist/esm/index.js.map +1 -1
  71. package/dist/esm/l10n/en.json.js +19 -1
  72. package/dist/esm/l10n/en.json.js.map +1 -1
  73. package/dist/esm/l10n/nl.json.js +23 -1
  74. package/dist/esm/l10n/nl.json.js.map +1 -1
  75. package/dist/esm/library-style.js +1 -1
  76. package/dist/esm/nextcloud-vue.css +1054 -911
  77. package/dist/esm/utils/actionsDispatcher.js +10 -5
  78. package/dist/esm/utils/actionsDispatcher.js.map +1 -1
  79. package/dist/esm/utils/detailActionModel.js +6 -2
  80. package/dist/esm/utils/detailActionModel.js.map +1 -1
  81. package/dist/esm/utils/libraryWidgetKeys.js +1 -0
  82. package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
  83. package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
  84. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  85. package/dist/esm/utils/widgetDispatch.js +1 -1
  86. package/dist/esm/utils/widgetDispatch.js.map +1 -1
  87. package/dist/nextcloud-vue.cjs.js +17078 -15224
  88. package/dist/nextcloud-vue.cjs.js.map +2 -2
  89. package/dist/nextcloud-vue.css +1054 -911
  90. package/l10n/en.json +19 -1
  91. package/l10n/nl.json +23 -1
  92. package/package.json +1 -1
  93. package/src/components/CnActionButtons/CnActionButtons.vue +93 -1
  94. package/src/components/CnChecklistWidget/CnChecklistWidget.vue +206 -0
  95. package/src/components/CnChecklistWidget/index.js +27 -0
  96. package/src/components/CnDashboardPage/CnDashboardPage.vue +26 -1
  97. package/src/components/CnDetailPage/CnDetailPage.md +2 -0
  98. package/src/components/CnDetailPage/CnDetailPage.vue +377 -10
  99. package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +15 -2
  100. package/src/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue +59 -1
  101. package/src/components/CnMacrosWidget/CnMacrosWidget.vue +16 -6
  102. package/src/components/CnObjectListWidget/CnObjectListWidget.vue +55 -2
  103. package/src/components/CnRecordListWidget/CnRecordListWidget.vue +101 -3
  104. package/src/components/CnRecordListWidget/CnRecordNoteRows.vue +508 -0
  105. package/src/components/CnTabsWidget/CnTabsWidget.vue +10 -5
  106. package/src/components/CnWidgetGrid/registerDashboardWidgets.js +1 -0
  107. package/src/components/index.js +1 -0
  108. package/src/composables/indexSources.js +64 -0
  109. package/src/composables/useTaskInboxStore.js +85 -0
  110. package/src/dialogs/CnAccessPurposeDialog.vue +183 -0
  111. package/src/index.js +1 -0
  112. package/src/schemas/app-manifest-v2.schema.json +49 -3
  113. package/src/utils/actionsDispatcher.js +10 -5
  114. package/src/utils/detailActionModel.js +6 -2
  115. package/src/utils/libraryWidgetKeys.js +1 -0
  116. package/src/utils/validateManifestV2.compiled.js +3 -3
  117. package/src/utils/widgetDispatch.js +1 -1
@@ -0,0 +1,282 @@
1
+ import { emit } from '@nextcloud/event-bus';
2
+ import { translate } from '@nextcloud/l10n';
3
+ import { NcButton, NcTextField } from '@nextcloud/vue';
4
+ import __sfc_main from '../CnIcon/CnIcon.vue.js';
5
+ import __sfc_main$1 from '../CnLeafMountHost/CnLeafMountHost.vue.js';
6
+ import __sfc_main$2 from '../CnStatusBadge/CnStatusBadge.vue.js';
7
+ import { useIntegrationRegistry } from '../../composables/useIntegrationRegistry.js';
8
+ import { isAppInstalled } from '../../utils/appInstalled.js';
9
+ import { prefixUrl } from '../../utils/headers.js';
10
+ import { readPath } from '../../utils/readPath.js';
11
+
12
+ /** The bus channel every endpoint-bound widget on the page refetches on. */
13
+ const PAGE_REFRESH_CHANNEL = 'cn:page:refresh';
14
+
15
+ /**
16
+ * The rows of a record list drawn as notes to record (`presentation:
17
+ * "notes"`, DqDagAfsluiten "Nog open van vandaag"): per row its pill, title,
18
+ * meta and due text, a field for what happened, an optional integration leaf
19
+ * (humaniq's hours) and a button that records the note on the row's own
20
+ * notes endpoint. What is typed and not yet recorded stays in this browser
21
+ * (session storage) and is counted as the "ready to record" figure; one
22
+ * button records every filled row. After a note is recorded the page's
23
+ * endpoint widgets refetch, so the row moves to whatever lists the server
24
+ * keeps of recorded work.
25
+ *
26
+ * @spec openspec/changes/record-list-notes/specs/record-list-notes/spec.md#requirement-a-record-list-can-collect-and-record-a-note-per-row
27
+ */
28
+ var script = {
29
+ name: 'CnRecordNoteRows',
30
+
31
+ components: { CnIcon: __sfc_main, CnLeafMountHost: __sfc_main$1, CnStatusBadge: __sfc_main$2, NcButton, NcTextField },
32
+
33
+ props: {
34
+ /** The list's rows as the record list builds them (`key`, `row`, `tag`, `attrs`, `title`, `meta`, `pill`, `due`). */
35
+ items: {
36
+ type: Array,
37
+ default: () => [],
38
+ },
39
+
40
+ /**
41
+ * `noteAction`, already translated: `{ urlField?, url?, bodyField?,
42
+ * label, fieldLabel, placeholder?, readyLabel, draftsKey?, errorText? }`.
43
+ */
44
+ action: {
45
+ type: Object,
46
+ required: true,
47
+ },
48
+
49
+ /** `recordAll`, already translated: `{ label, title?, text?, icon?, cancel?: { label, to } }`, or null. */
50
+ recordAll: {
51
+ type: Object,
52
+ default: null,
53
+ },
54
+
55
+ /** `rowLeaf`: `{ id, app?, register?, objectIdField, schemaField?, schema?, unavailableText? }`, or null. */
56
+ leaf: {
57
+ type: Object,
58
+ default: null,
59
+ },
60
+ },
61
+
62
+ setup() {
63
+ const { getById } = useIntegrationRegistry();
64
+ return { getProvider: getById }
65
+ },
66
+
67
+ data() {
68
+ return { drafts: this.readDrafts(), busy: false, error: '' }
69
+ },
70
+
71
+ computed: {
72
+ /** @return {string} The session storage key of this list's drafts. */
73
+ draftsKey() {
74
+ return `cn-record-list-notes:${this.action.draftsKey || 'default'}`
75
+ },
76
+
77
+ /** @return {number} The rows with a note typed and not yet recorded. */
78
+ readyCount() {
79
+ return this.items.filter((item) => this.hasDraft(item.key)).length
80
+ },
81
+
82
+ /** @return {string} The label of the ready figure. */
83
+ readyLabel() {
84
+ return this.action.readyLabel || translate('nextcloud-vue', 'filled in, not recorded yet')
85
+ },
86
+
87
+ /** @return {string} */
88
+ recordLabel() {
89
+ return this.action.label || translate('nextcloud-vue', 'Record')
90
+ },
91
+
92
+ /** @return {string} */
93
+ fieldLabel() {
94
+ return this.action.fieldLabel || translate('nextcloud-vue', 'Note')
95
+ },
96
+
97
+ /** @return {string} */
98
+ placeholder() {
99
+ return this.action.placeholder || ''
100
+ },
101
+
102
+ /** @return {object|null} The leaf's integration provider, when one is registered. */
103
+ leafProvider() {
104
+ return this.leaf && this.leaf.id ? (this.getProvider(this.leaf.id) || null) : null
105
+ },
106
+
107
+ /** @return {boolean} Whether the leaf mounts itself (`renderMode: "mount"`). */
108
+ leafIsMount() {
109
+ const provider = this.leafProvider;
110
+ return Boolean(provider) && provider.renderMode === 'mount' && typeof provider.mount === 'function'
111
+ },
112
+
113
+ /** @return {object|null} The leaf's component, in component mode. */
114
+ leafComponent() {
115
+ const provider = this.leafProvider;
116
+ return provider ? (provider.tab || provider.widget || null) : null
117
+ },
118
+
119
+ /**
120
+ * Whether the leaf's app is installed but the leaf is not available, so
121
+ * the row says so instead of silently showing no field.
122
+ *
123
+ * @return {boolean}
124
+ */
125
+ leafUnavailable() {
126
+ return Boolean(this.leaf && this.leaf.app && !this.leafProvider && isAppInstalled(this.leaf.app))
127
+ },
128
+
129
+ /** @return {string} */
130
+ leafUnavailableText() {
131
+ return this.leaf?.unavailableText || translate('nextcloud-vue', 'This field is not available here.')
132
+ },
133
+ },
134
+
135
+ watch: {
136
+ drafts: {
137
+ deep: true,
138
+ handler() {
139
+ this.saveDrafts();
140
+ },
141
+ },
142
+ },
143
+
144
+ methods: {
145
+ /**
146
+ * The drafts this browser session kept, by row key.
147
+ *
148
+ * @return {Object<string, string>}
149
+ */
150
+ readDrafts() {
151
+ try {
152
+ const raw = window.sessionStorage.getItem(`cn-record-list-notes:${this.action?.draftsKey || 'default'}`);
153
+ const parsed = raw ? JSON.parse(raw) : {};
154
+ return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {}
155
+ } catch {
156
+ return {}
157
+ }
158
+ },
159
+
160
+ /** @return {void} */
161
+ saveDrafts() {
162
+ try {
163
+ window.sessionStorage.setItem(this.draftsKey, JSON.stringify(this.drafts));
164
+ } catch {
165
+ // Without session storage the drafts live as long as the page.
166
+ }
167
+ },
168
+
169
+ /**
170
+ * The colour of a due text: a term that ended or ends today in the
171
+ * error colour, one still to come in the warning colour.
172
+ *
173
+ * @param {{tone: string}} due The due text.
174
+ * @return {string} `error`, `warning` or `muted`.
175
+ */
176
+ dueToneOf(due) {
177
+ if (due.tone === 'danger' || due.tone === 'today') {
178
+ return 'error'
179
+ }
180
+ return due.tone === 'upcoming' ? 'warning' : 'muted'
181
+ },
182
+
183
+ /**
184
+ * @param {string} key The row key.
185
+ * @param {string} value The typed note.
186
+ * @return {void}
187
+ */
188
+ setDraft(key, value) {
189
+ this.drafts = { ...this.drafts, [key]: String(value ?? '') };
190
+ },
191
+
192
+ /**
193
+ * @param {string} key The row key.
194
+ * @return {boolean} Whether the row has a note typed.
195
+ */
196
+ hasDraft(key) {
197
+ return String(this.drafts[key] ?? '').trim() !== ''
198
+ },
199
+
200
+ /**
201
+ * The props the row's leaf gets: the row's object, register and schema.
202
+ *
203
+ * @param {object} row The row.
204
+ * @return {object}
205
+ */
206
+ leafProps(row) {
207
+ const leaf = this.leaf || {};
208
+ return {
209
+ objectId: String(readPath(row, leaf.objectIdField || 'id') ?? ''),
210
+ register: leaf.register || '',
211
+ schema: leaf.schemaField ? String(readPath(row, leaf.schemaField) ?? '') : (leaf.schema || ''),
212
+ surface: 'row',
213
+ }
214
+ },
215
+
216
+ /**
217
+ * The row's notes endpoint: the row's `urlField`, else `url` with
218
+ * `{field}` filled from the row.
219
+ *
220
+ * @param {object} row The row.
221
+ * @return {string} The URL, or ''.
222
+ */
223
+ urlOf(row) {
224
+ if (this.action.urlField) {
225
+ return String(readPath(row, this.action.urlField) ?? '')
226
+ }
227
+ const template = String(this.action.url || '');
228
+ return template.replace(/\{([A-Za-z0-9_.]+)\}/g, (_, path) => encodeURIComponent(String(readPath(row, path) ?? '')))
229
+ },
230
+
231
+ /**
232
+ * Record one row's note, then let the page refetch.
233
+ *
234
+ * @param {object} item The row item.
235
+ * @param {boolean} [refresh] Whether to refresh the page after it.
236
+ * @return {Promise<boolean>} Whether it was recorded.
237
+ */
238
+ async record(item, refresh = true) {
239
+ const text = String(this.drafts[item.key] ?? '').trim();
240
+ const url = this.urlOf(item.row);
241
+ if (text === '' || url === '') {
242
+ return false
243
+ }
244
+ this.busy = true;
245
+ this.error = '';
246
+ try {
247
+ const { default: axios } = await import('@nextcloud/axios');
248
+ await axios.post(prefixUrl(url), { [this.action.bodyField || 'message']: text });
249
+ const { [item.key]: done, ...rest } = this.drafts;
250
+ this.drafts = rest;
251
+ if (refresh) {
252
+ emit(PAGE_REFRESH_CHANNEL, {});
253
+ }
254
+ return true
255
+ } catch {
256
+ this.error = this.action.errorText || translate('nextcloud-vue', 'The note could not be recorded. It is kept here; try again.');
257
+ return false
258
+ } finally {
259
+ this.busy = false;
260
+ }
261
+ },
262
+
263
+ /**
264
+ * Record every row with a note, one after another, then refresh once.
265
+ *
266
+ * @return {Promise<void>}
267
+ */
268
+ async recordEvery() {
269
+ const filled = this.items.filter((item) => this.hasDraft(item.key));
270
+ let any = false;
271
+ for (const item of filled) {
272
+ any = (await this.record(item, false)) || any;
273
+ }
274
+ if (any) {
275
+ emit(PAGE_REFRESH_CHANNEL, {});
276
+ }
277
+ },
278
+ },
279
+ };
280
+
281
+ export { script as default };
282
+ //# sourceMappingURL=CnRecordNoteRows.vue2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CnRecordNoteRows.vue2.js","sources":["../../../../src/components/CnRecordListWidget/CnRecordNoteRows.vue"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<template>\n\t<div class=\"cn-record-notes\" data-testid=\"cn-record-list-notes\">\n\t\t<p class=\"cn-record-notes__ready\" data-testid=\"cn-record-list-notes-ready\">\n\t\t\t<span class=\"cn-record-notes__ready-figure\">{{ readyCount }}</span>\n\t\t\t<span class=\"cn-record-notes__ready-label\">{{ readyLabel }}</span>\n\t\t</p>\n\t\t<div\n\t\t\tv-for=\"item in items\"\n\t\t\t:key=\"item.key\"\n\t\t\tclass=\"cn-record-notes__row\"\n\t\t\tdata-testid=\"cn-record-list-item\">\n\t\t\t<div class=\"cn-record-notes__head\">\n\t\t\t\t<CnStatusBadge\n\t\t\t\t\tv-if=\"item.pill\"\n\t\t\t\t\t:label=\"item.pill.label\"\n\t\t\t\t\t:variant=\"item.pill.variant\"\n\t\t\t\t\tsize=\"small\" />\n\t\t\t\t<component\n\t\t\t\t\t:is=\"item.tag\"\n\t\t\t\t\tv-if=\"item.title\"\n\t\t\t\t\tclass=\"cn-record-notes__title\"\n\t\t\t\t\t:class=\"{ 'cn-record-notes__title--link': item.tag !== 'div' }\"\n\t\t\t\t\tv-bind=\"item.attrs\">\n\t\t\t\t\t{{ item.title }}\n\t\t\t\t</component>\n\t\t\t\t<span v-if=\"item.meta\" class=\"cn-record-notes__meta\">{{ item.meta }}</span>\n\t\t\t\t<span class=\"cn-record-notes__spacer\" />\n\t\t\t\t<span\n\t\t\t\t\tv-if=\"item.due\"\n\t\t\t\t\tclass=\"cn-record-notes__due\"\n\t\t\t\t\t:class=\"`cn-record-notes__due--${dueToneOf(item.due)}`\"\n\t\t\t\t\tdata-testid=\"cn-record-list-due\">{{ item.due.text }}</span>\n\t\t\t</div>\n\t\t\t<div class=\"cn-record-notes__fields\">\n\t\t\t\t<NcTextField\n\t\t\t\t\tclass=\"cn-record-notes__field\"\n\t\t\t\t\t:modelValue=\"drafts[item.key] || ''\"\n\t\t\t\t\t:label=\"fieldLabel\"\n\t\t\t\t\t:labelOutside=\"false\"\n\t\t\t\t\t:placeholder=\"placeholder\"\n\t\t\t\t\t:disabled=\"busy\"\n\t\t\t\t\t:data-testid=\"`cn-record-list-note-${item.key}`\"\n\t\t\t\t\t@update:modelValue=\"setDraft(item.key, $event)\" />\n\t\t\t\t<div v-if=\"leaf && leafProvider\" class=\"cn-record-notes__leaf\" :data-testid=\"`cn-record-list-leaf-${item.key}`\">\n\t\t\t\t\t<CnLeafMountHost\n\t\t\t\t\t\tv-if=\"leafIsMount\"\n\t\t\t\t\t\t:provider=\"leafProvider\"\n\t\t\t\t\t\t:mountProps=\"leafProps(item.row)\" />\n\t\t\t\t\t<component :is=\"leafComponent\" v-else-if=\"leafComponent\" v-bind=\"leafProps(item.row)\" />\n\t\t\t\t</div>\n\t\t\t\t<NcButton\n\t\t\t\t\tvariant=\"secondary\"\n\t\t\t\t\t:disabled=\"busy || !hasDraft(item.key)\"\n\t\t\t\t\t:data-testid=\"`cn-record-list-record-${item.key}`\"\n\t\t\t\t\t@click=\"record(item)\">\n\t\t\t\t\t{{ recordLabel }}\n\t\t\t\t</NcButton>\n\t\t\t</div>\n\t\t\t<p\n\t\t\t\tv-if=\"leafUnavailable\"\n\t\t\t\tclass=\"cn-record-notes__leaf-missing\"\n\t\t\t\t:data-testid=\"`cn-record-list-leaf-missing-${item.key}`\">\n\t\t\t\t{{ leafUnavailableText }}\n\t\t\t</p>\n\t\t</div>\n\t\t<p v-if=\"error\"\n\t\t\tclass=\"cn-record-notes__error\"\n\t\t\trole=\"alert\"\n\t\t\tdata-testid=\"cn-record-list-notes-error\">\n\t\t\t{{ error }}\n\t\t</p>\n\t\t<div v-if=\"recordAll\" class=\"cn-record-notes__all\" data-testid=\"cn-record-list-record-all\">\n\t\t\t<div class=\"cn-record-notes__all-text\">\n\t\t\t\t<h3 v-if=\"recordAll.title\" class=\"cn-record-notes__all-title\">\n\t\t\t\t\t{{ recordAll.title }}\n\t\t\t\t</h3>\n\t\t\t\t<p v-if=\"recordAll.text\" class=\"cn-record-notes__all-sentence\">\n\t\t\t\t\t{{ recordAll.text }}\n\t\t\t\t</p>\n\t\t\t</div>\n\t\t\t<div class=\"cn-record-notes__all-actions\">\n\t\t\t\t<NcButton v-if=\"recordAll.cancel\" variant=\"secondary\" :to=\"recordAll.cancel.to\">\n\t\t\t\t\t{{ recordAll.cancel.label }}\n\t\t\t\t</NcButton>\n\t\t\t\t<NcButton\n\t\t\t\t\tvariant=\"primary\"\n\t\t\t\t\t:disabled=\"busy || readyCount === 0\"\n\t\t\t\t\tdata-testid=\"cn-record-list-record-all-button\"\n\t\t\t\t\t@click=\"recordEvery\">\n\t\t\t\t\t<template v-if=\"recordAll.icon\" #icon>\n\t\t\t\t\t\t<CnIcon :name=\"recordAll.icon\" :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t{{ recordAll.label }}\n\t\t\t\t</NcButton>\n\t\t\t</div>\n\t\t</div>\n\t</div>\n</template>\n\n<script>\nimport { emit } from '@nextcloud/event-bus'\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcButton, NcTextField } from '@nextcloud/vue'\nimport CnIcon from '../CnIcon/CnIcon.vue'\nimport CnLeafMountHost from '../CnLeafMountHost/CnLeafMountHost.vue'\nimport CnStatusBadge from '../CnStatusBadge/CnStatusBadge.vue'\nimport { useIntegrationRegistry } from '../../composables/useIntegrationRegistry.js'\nimport { isAppInstalled } from '../../utils/appInstalled.js'\nimport { prefixUrl } from '../../utils/headers.js'\nimport { readPath } from '../../utils/readPath.js'\n\n/** The bus channel every endpoint-bound widget on the page refetches on. */\nconst PAGE_REFRESH_CHANNEL = 'cn:page:refresh'\n\n/**\n * The rows of a record list drawn as notes to record (`presentation:\n * \"notes\"`, DqDagAfsluiten \"Nog open van vandaag\"): per row its pill, title,\n * meta and due text, a field for what happened, an optional integration leaf\n * (humaniq's hours) and a button that records the note on the row's own\n * notes endpoint. What is typed and not yet recorded stays in this browser\n * (session storage) and is counted as the \"ready to record\" figure; one\n * button records every filled row. After a note is recorded the page's\n * endpoint widgets refetch, so the row moves to whatever lists the server\n * keeps of recorded work.\n *\n * @spec openspec/changes/record-list-notes/specs/record-list-notes/spec.md#requirement-a-record-list-can-collect-and-record-a-note-per-row\n */\nexport default {\n\tname: 'CnRecordNoteRows',\n\n\tcomponents: { CnIcon, CnLeafMountHost, CnStatusBadge, NcButton, NcTextField },\n\n\tprops: {\n\t\t/** The list's rows as the record list builds them (`key`, `row`, `tag`, `attrs`, `title`, `meta`, `pill`, `due`). */\n\t\titems: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/**\n\t\t * `noteAction`, already translated: `{ urlField?, url?, bodyField?,\n\t\t * label, fieldLabel, placeholder?, readyLabel, draftsKey?, errorText? }`.\n\t\t */\n\t\taction: {\n\t\t\ttype: Object,\n\t\t\trequired: true,\n\t\t},\n\n\t\t/** `recordAll`, already translated: `{ label, title?, text?, icon?, cancel?: { label, to } }`, or null. */\n\t\trecordAll: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** `rowLeaf`: `{ id, app?, register?, objectIdField, schemaField?, schema?, unavailableText? }`, or null. */\n\t\tleaf: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\tsetup() {\n\t\tconst { getById } = useIntegrationRegistry()\n\t\treturn { getProvider: getById }\n\t},\n\n\tdata() {\n\t\treturn { drafts: this.readDrafts(), busy: false, error: '' }\n\t},\n\n\tcomputed: {\n\t\t/** @return {string} The session storage key of this list's drafts. */\n\t\tdraftsKey() {\n\t\t\treturn `cn-record-list-notes:${this.action.draftsKey || 'default'}`\n\t\t},\n\n\t\t/** @return {number} The rows with a note typed and not yet recorded. */\n\t\treadyCount() {\n\t\t\treturn this.items.filter((item) => this.hasDraft(item.key)).length\n\t\t},\n\n\t\t/** @return {string} The label of the ready figure. */\n\t\treadyLabel() {\n\t\t\treturn this.action.readyLabel || t('nextcloud-vue', 'filled in, not recorded yet')\n\t\t},\n\n\t\t/** @return {string} */\n\t\trecordLabel() {\n\t\t\treturn this.action.label || t('nextcloud-vue', 'Record')\n\t\t},\n\n\t\t/** @return {string} */\n\t\tfieldLabel() {\n\t\t\treturn this.action.fieldLabel || t('nextcloud-vue', 'Note')\n\t\t},\n\n\t\t/** @return {string} */\n\t\tplaceholder() {\n\t\t\treturn this.action.placeholder || ''\n\t\t},\n\n\t\t/** @return {object|null} The leaf's integration provider, when one is registered. */\n\t\tleafProvider() {\n\t\t\treturn this.leaf && this.leaf.id ? (this.getProvider(this.leaf.id) || null) : null\n\t\t},\n\n\t\t/** @return {boolean} Whether the leaf mounts itself (`renderMode: \"mount\"`). */\n\t\tleafIsMount() {\n\t\t\tconst provider = this.leafProvider\n\t\t\treturn Boolean(provider) && provider.renderMode === 'mount' && typeof provider.mount === 'function'\n\t\t},\n\n\t\t/** @return {object|null} The leaf's component, in component mode. */\n\t\tleafComponent() {\n\t\t\tconst provider = this.leafProvider\n\t\t\treturn provider ? (provider.tab || provider.widget || null) : null\n\t\t},\n\n\t\t/**\n\t\t * Whether the leaf's app is installed but the leaf is not available, so\n\t\t * the row says so instead of silently showing no field.\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\tleafUnavailable() {\n\t\t\treturn Boolean(this.leaf && this.leaf.app && !this.leafProvider && isAppInstalled(this.leaf.app))\n\t\t},\n\n\t\t/** @return {string} */\n\t\tleafUnavailableText() {\n\t\t\treturn this.leaf?.unavailableText || t('nextcloud-vue', 'This field is not available here.')\n\t\t},\n\t},\n\n\twatch: {\n\t\tdrafts: {\n\t\t\tdeep: true,\n\t\t\thandler() {\n\t\t\t\tthis.saveDrafts()\n\t\t\t},\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * The drafts this browser session kept, by row key.\n\t\t *\n\t\t * @return {Object<string, string>}\n\t\t */\n\t\treadDrafts() {\n\t\t\ttry {\n\t\t\t\tconst raw = window.sessionStorage.getItem(`cn-record-list-notes:${this.action?.draftsKey || 'default'}`)\n\t\t\t\tconst parsed = raw ? JSON.parse(raw) : {}\n\t\t\t\treturn parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {}\n\t\t\t} catch {\n\t\t\t\treturn {}\n\t\t\t}\n\t\t},\n\n\t\t/** @return {void} */\n\t\tsaveDrafts() {\n\t\t\ttry {\n\t\t\t\twindow.sessionStorage.setItem(this.draftsKey, JSON.stringify(this.drafts))\n\t\t\t} catch {\n\t\t\t\t// Without session storage the drafts live as long as the page.\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * The colour of a due text: a term that ended or ends today in the\n\t\t * error colour, one still to come in the warning colour.\n\t\t *\n\t\t * @param {{tone: string}} due The due text.\n\t\t * @return {string} `error`, `warning` or `muted`.\n\t\t */\n\t\tdueToneOf(due) {\n\t\t\tif (due.tone === 'danger' || due.tone === 'today') {\n\t\t\t\treturn 'error'\n\t\t\t}\n\t\t\treturn due.tone === 'upcoming' ? 'warning' : 'muted'\n\t\t},\n\n\t\t/**\n\t\t * @param {string} key The row key.\n\t\t * @param {string} value The typed note.\n\t\t * @return {void}\n\t\t */\n\t\tsetDraft(key, value) {\n\t\t\tthis.drafts = { ...this.drafts, [key]: String(value ?? '') }\n\t\t},\n\n\t\t/**\n\t\t * @param {string} key The row key.\n\t\t * @return {boolean} Whether the row has a note typed.\n\t\t */\n\t\thasDraft(key) {\n\t\t\treturn String(this.drafts[key] ?? '').trim() !== ''\n\t\t},\n\n\t\t/**\n\t\t * The props the row's leaf gets: the row's object, register and schema.\n\t\t *\n\t\t * @param {object} row The row.\n\t\t * @return {object}\n\t\t */\n\t\tleafProps(row) {\n\t\t\tconst leaf = this.leaf || {}\n\t\t\treturn {\n\t\t\t\tobjectId: String(readPath(row, leaf.objectIdField || 'id') ?? ''),\n\t\t\t\tregister: leaf.register || '',\n\t\t\t\tschema: leaf.schemaField ? String(readPath(row, leaf.schemaField) ?? '') : (leaf.schema || ''),\n\t\t\t\tsurface: 'row',\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * The row's notes endpoint: the row's `urlField`, else `url` with\n\t\t * `{field}` filled from the row.\n\t\t *\n\t\t * @param {object} row The row.\n\t\t * @return {string} The URL, or ''.\n\t\t */\n\t\turlOf(row) {\n\t\t\tif (this.action.urlField) {\n\t\t\t\treturn String(readPath(row, this.action.urlField) ?? '')\n\t\t\t}\n\t\t\tconst template = String(this.action.url || '')\n\t\t\treturn template.replace(/\\{([A-Za-z0-9_.]+)\\}/g, (_, path) => encodeURIComponent(String(readPath(row, path) ?? '')))\n\t\t},\n\n\t\t/**\n\t\t * Record one row's note, then let the page refetch.\n\t\t *\n\t\t * @param {object} item The row item.\n\t\t * @param {boolean} [refresh] Whether to refresh the page after it.\n\t\t * @return {Promise<boolean>} Whether it was recorded.\n\t\t */\n\t\tasync record(item, refresh = true) {\n\t\t\tconst text = String(this.drafts[item.key] ?? '').trim()\n\t\t\tconst url = this.urlOf(item.row)\n\t\t\tif (text === '' || url === '') {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tthis.busy = true\n\t\t\tthis.error = ''\n\t\t\ttry {\n\t\t\t\tconst { default: axios } = await import('@nextcloud/axios')\n\t\t\t\tawait axios.post(prefixUrl(url), { [this.action.bodyField || 'message']: text })\n\t\t\t\tconst { [item.key]: done, ...rest } = this.drafts\n\t\t\t\tthis.drafts = rest\n\t\t\t\tif (refresh) {\n\t\t\t\t\temit(PAGE_REFRESH_CHANNEL, {})\n\t\t\t\t}\n\t\t\t\treturn true\n\t\t\t} catch {\n\t\t\t\tthis.error = this.action.errorText || t('nextcloud-vue', 'The note could not be recorded. It is kept here; try again.')\n\t\t\t\treturn false\n\t\t\t} finally {\n\t\t\t\tthis.busy = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Record every row with a note, one after another, then refresh once.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync recordEvery() {\n\t\t\tconst filled = this.items.filter((item) => this.hasDraft(item.key))\n\t\t\tlet any = false\n\t\t\tfor (const item of filled) {\n\t\t\t\tany = (await this.record(item, false)) || any\n\t\t\t}\n\t\t\tif (any) {\n\t\t\t\temit(PAGE_REFRESH_CHANNEL, {})\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-record-notes {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 0;\n}\n\n.cn-record-notes__ready {\n\tdisplay: flex;\n\talign-items: baseline;\n\tgap: 6px;\n\tmargin: 0 0 8px;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-record-notes__ready-figure {\n\tfont-weight: 700;\n\tcolor: var(--color-main-text);\n}\n\n.cn-record-notes__row {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 10px;\n\tpadding: 14px 0;\n\tborder-top: 1px solid var(--color-border);\n}\n\n.cn-record-notes__head {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tgap: 8px;\n}\n\n.cn-record-notes__title {\n\tfont-weight: 600;\n\tcolor: var(--color-main-text);\n}\n\n.cn-record-notes__title--link {\n\ttext-decoration: underline;\n}\n\n.cn-record-notes__meta {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 13px;\n}\n\n.cn-record-notes__spacer {\n\tflex: 1 1 auto;\n}\n\n.cn-record-notes__due {\n\tfont-weight: 600;\n}\n\n.cn-record-notes__due--error {\n\tcolor: var(--color-error-text);\n}\n\n.cn-record-notes__due--warning {\n\tcolor: var(--color-warning-text);\n}\n\n.cn-record-notes__due--muted {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-weight: 400;\n}\n\n.cn-record-notes__fields {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: flex-end;\n\tgap: 10px;\n}\n\n.cn-record-notes__field {\n\tflex: 1 1 260px;\n}\n\n.cn-record-notes__leaf {\n\tflex: 0 0 auto;\n}\n\n.cn-record-notes__leaf-missing,\n.cn-record-notes__error {\n\tmargin: 0;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 13px;\n}\n\n.cn-record-notes__error {\n\tcolor: var(--color-error-text);\n}\n\n.cn-record-notes__all {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tjustify-content: space-between;\n\tgap: 12px;\n\tmargin-top: 8px;\n\tpadding-top: 14px;\n\tborder-top: 1px solid var(--color-border);\n}\n\n.cn-record-notes__all-title {\n\tmargin: 0;\n\tfont-size: 16px;\n}\n\n.cn-record-notes__all-sentence {\n\tmargin: 4px 0 0;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-record-notes__all-actions {\n\tdisplay: flex;\n\tgap: 8px;\n}\n</style>\n"],"names":["CnIcon","CnLeafMountHost","CnStatusBadge","t"],"mappings":"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"}
@@ -0,0 +1,202 @@
1
+ import { resolveComponent, openBlock, createElementBlock, createElementVNode, toDisplayString, Fragment, renderList, createBlock, createCommentVNode, resolveDynamicComponent, mergeProps, withCtx, createTextVNode, normalizeClass, createVNode, createSlots } from 'vue';
2
+
3
+ const _hoisted_1 = {
4
+ class: "cn-record-notes",
5
+ "data-testid": "cn-record-list-notes"
6
+ };
7
+ const _hoisted_2 = {
8
+ class: "cn-record-notes__ready",
9
+ "data-testid": "cn-record-list-notes-ready"
10
+ };
11
+ const _hoisted_3 = { class: "cn-record-notes__ready-figure" };
12
+ const _hoisted_4 = { class: "cn-record-notes__ready-label" };
13
+ const _hoisted_5 = { class: "cn-record-notes__head" };
14
+ const _hoisted_6 = {
15
+ key: 2,
16
+ class: "cn-record-notes__meta"
17
+ };
18
+ const _hoisted_7 = { class: "cn-record-notes__fields" };
19
+ const _hoisted_8 = ["data-testid"];
20
+ const _hoisted_9 = ["data-testid"];
21
+ const _hoisted_10 = {
22
+ key: 0,
23
+ class: "cn-record-notes__error",
24
+ role: "alert",
25
+ "data-testid": "cn-record-list-notes-error"
26
+ };
27
+ const _hoisted_11 = {
28
+ key: 1,
29
+ class: "cn-record-notes__all",
30
+ "data-testid": "cn-record-list-record-all"
31
+ };
32
+ const _hoisted_12 = { class: "cn-record-notes__all-text" };
33
+ const _hoisted_13 = {
34
+ key: 0,
35
+ class: "cn-record-notes__all-title"
36
+ };
37
+ const _hoisted_14 = {
38
+ key: 1,
39
+ class: "cn-record-notes__all-sentence"
40
+ };
41
+ const _hoisted_15 = { class: "cn-record-notes__all-actions" };
42
+
43
+ function render(_ctx, _cache, $props, $setup, $data, $options) {
44
+ const _component_CnStatusBadge = resolveComponent("CnStatusBadge");
45
+ const _component_NcTextField = resolveComponent("NcTextField");
46
+ const _component_CnLeafMountHost = resolveComponent("CnLeafMountHost");
47
+ const _component_NcButton = resolveComponent("NcButton");
48
+ const _component_CnIcon = resolveComponent("CnIcon");
49
+
50
+ return (openBlock(), createElementBlock("div", _hoisted_1, [
51
+ createElementVNode("p", _hoisted_2, [
52
+ createElementVNode("span", _hoisted_3, toDisplayString($options.readyCount), 1 /* TEXT */),
53
+ createElementVNode("span", _hoisted_4, toDisplayString($options.readyLabel), 1 /* TEXT */)
54
+ ]),
55
+ (openBlock(true), createElementBlock(Fragment, null, renderList($props.items, (item) => {
56
+ return (openBlock(), createElementBlock("div", {
57
+ key: item.key,
58
+ class: "cn-record-notes__row",
59
+ "data-testid": "cn-record-list-item"
60
+ }, [
61
+ createElementVNode("div", _hoisted_5, [
62
+ (item.pill)
63
+ ? (openBlock(), createBlock(_component_CnStatusBadge, {
64
+ key: 0,
65
+ label: item.pill.label,
66
+ variant: item.pill.variant,
67
+ size: "small"
68
+ }, null, 8 /* PROPS */, ["label", "variant"]))
69
+ : createCommentVNode("v-if", true),
70
+ (item.title)
71
+ ? (openBlock(), createBlock(resolveDynamicComponent(item.tag), mergeProps({
72
+ key: 1,
73
+ class: ["cn-record-notes__title", { 'cn-record-notes__title--link': item.tag !== 'div' }]
74
+ }, { ref_for: true }, item.attrs), {
75
+ default: withCtx(() => [
76
+ createTextVNode(toDisplayString(item.title), 1 /* TEXT */)
77
+ ]),
78
+ _: 2 /* DYNAMIC */
79
+ }, 1040 /* FULL_PROPS, DYNAMIC_SLOTS */, ["class"]))
80
+ : createCommentVNode("v-if", true),
81
+ (item.meta)
82
+ ? (openBlock(), createElementBlock("span", _hoisted_6, toDisplayString(item.meta), 1 /* TEXT */))
83
+ : createCommentVNode("v-if", true),
84
+ _cache[0] || (_cache[0] = createElementVNode("span", { class: "cn-record-notes__spacer" }, null, -1 /* CACHED */)),
85
+ (item.due)
86
+ ? (openBlock(), createElementBlock("span", {
87
+ key: 3,
88
+ class: normalizeClass(["cn-record-notes__due", `cn-record-notes__due--${$options.dueToneOf(item.due)}`]),
89
+ "data-testid": "cn-record-list-due"
90
+ }, toDisplayString(item.due.text), 3 /* TEXT, CLASS */))
91
+ : createCommentVNode("v-if", true)
92
+ ]),
93
+ createElementVNode("div", _hoisted_7, [
94
+ createVNode(_component_NcTextField, {
95
+ class: "cn-record-notes__field",
96
+ modelValue: $data.drafts[item.key] || '',
97
+ label: $options.fieldLabel,
98
+ labelOutside: false,
99
+ placeholder: $options.placeholder,
100
+ disabled: $data.busy,
101
+ "data-testid": `cn-record-list-note-${item.key}`,
102
+ "onUpdate:modelValue": $event => ($options.setDraft(item.key, $event))
103
+ }, null, 8 /* PROPS */, ["modelValue", "label", "placeholder", "disabled", "data-testid", "onUpdate:modelValue"]),
104
+ ($props.leaf && $options.leafProvider)
105
+ ? (openBlock(), createElementBlock("div", {
106
+ key: 0,
107
+ class: "cn-record-notes__leaf",
108
+ "data-testid": `cn-record-list-leaf-${item.key}`
109
+ }, [
110
+ ($options.leafIsMount)
111
+ ? (openBlock(), createBlock(_component_CnLeafMountHost, {
112
+ key: 0,
113
+ provider: $options.leafProvider,
114
+ mountProps: $options.leafProps(item.row)
115
+ }, null, 8 /* PROPS */, ["provider", "mountProps"]))
116
+ : ($options.leafComponent)
117
+ ? (openBlock(), createBlock(resolveDynamicComponent($options.leafComponent), mergeProps({
118
+ key: 1,
119
+ ref_for: true
120
+ }, $options.leafProps(item.row)), null, 16 /* FULL_PROPS */))
121
+ : createCommentVNode("v-if", true)
122
+ ], 8 /* PROPS */, _hoisted_8))
123
+ : createCommentVNode("v-if", true),
124
+ createVNode(_component_NcButton, {
125
+ variant: "secondary",
126
+ disabled: $data.busy || !$options.hasDraft(item.key),
127
+ "data-testid": `cn-record-list-record-${item.key}`,
128
+ onClick: $event => ($options.record(item))
129
+ }, {
130
+ default: withCtx(() => [
131
+ createTextVNode(toDisplayString($options.recordLabel), 1 /* TEXT */)
132
+ ]),
133
+ _: 1 /* STABLE */
134
+ }, 8 /* PROPS */, ["disabled", "data-testid", "onClick"])
135
+ ]),
136
+ ($options.leafUnavailable)
137
+ ? (openBlock(), createElementBlock("p", {
138
+ key: 0,
139
+ class: "cn-record-notes__leaf-missing",
140
+ "data-testid": `cn-record-list-leaf-missing-${item.key}`
141
+ }, toDisplayString($options.leafUnavailableText), 9 /* TEXT, PROPS */, _hoisted_9))
142
+ : createCommentVNode("v-if", true)
143
+ ]))
144
+ }), 128 /* KEYED_FRAGMENT */)),
145
+ ($data.error)
146
+ ? (openBlock(), createElementBlock("p", _hoisted_10, toDisplayString($data.error), 1 /* TEXT */))
147
+ : createCommentVNode("v-if", true),
148
+ ($props.recordAll)
149
+ ? (openBlock(), createElementBlock("div", _hoisted_11, [
150
+ createElementVNode("div", _hoisted_12, [
151
+ ($props.recordAll.title)
152
+ ? (openBlock(), createElementBlock("h3", _hoisted_13, toDisplayString($props.recordAll.title), 1 /* TEXT */))
153
+ : createCommentVNode("v-if", true),
154
+ ($props.recordAll.text)
155
+ ? (openBlock(), createElementBlock("p", _hoisted_14, toDisplayString($props.recordAll.text), 1 /* TEXT */))
156
+ : createCommentVNode("v-if", true)
157
+ ]),
158
+ createElementVNode("div", _hoisted_15, [
159
+ ($props.recordAll.cancel)
160
+ ? (openBlock(), createBlock(_component_NcButton, {
161
+ key: 0,
162
+ variant: "secondary",
163
+ to: $props.recordAll.cancel.to
164
+ }, {
165
+ default: withCtx(() => [
166
+ createTextVNode(toDisplayString($props.recordAll.cancel.label), 1 /* TEXT */)
167
+ ]),
168
+ _: 1 /* STABLE */
169
+ }, 8 /* PROPS */, ["to"]))
170
+ : createCommentVNode("v-if", true),
171
+ createVNode(_component_NcButton, {
172
+ variant: "primary",
173
+ disabled: $data.busy || $options.readyCount === 0,
174
+ "data-testid": "cn-record-list-record-all-button",
175
+ onClick: $options.recordEvery
176
+ }, createSlots({
177
+ default: withCtx(() => [
178
+ createTextVNode(" " + toDisplayString($props.recordAll.label), 1 /* TEXT */)
179
+ ]),
180
+ _: 2 /* DYNAMIC */
181
+ }, [
182
+ ($props.recordAll.icon)
183
+ ? {
184
+ name: "icon",
185
+ fn: withCtx(() => [
186
+ createVNode(_component_CnIcon, {
187
+ name: $props.recordAll.icon,
188
+ size: 20
189
+ }, null, 8 /* PROPS */, ["name"])
190
+ ]),
191
+ key: "0"
192
+ }
193
+ : undefined
194
+ ]), 1032 /* PROPS, DYNAMIC_SLOTS */, ["disabled", "onClick"])
195
+ ])
196
+ ]))
197
+ : createCommentVNode("v-if", true)
198
+ ]))
199
+ }
200
+
201
+ export { render };
202
+ //# sourceMappingURL=CnRecordNoteRows.vue3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CnRecordNoteRows.vue3.js","sources":["../../../../src/components/CnRecordListWidget/CnRecordNoteRows.vue?vue&type=template&id=6d7ff53b&lang.js"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<template>\n\t<div class=\"cn-record-notes\" data-testid=\"cn-record-list-notes\">\n\t\t<p class=\"cn-record-notes__ready\" data-testid=\"cn-record-list-notes-ready\">\n\t\t\t<span class=\"cn-record-notes__ready-figure\">{{ readyCount }}</span>\n\t\t\t<span class=\"cn-record-notes__ready-label\">{{ readyLabel }}</span>\n\t\t</p>\n\t\t<div\n\t\t\tv-for=\"item in items\"\n\t\t\t:key=\"item.key\"\n\t\t\tclass=\"cn-record-notes__row\"\n\t\t\tdata-testid=\"cn-record-list-item\">\n\t\t\t<div class=\"cn-record-notes__head\">\n\t\t\t\t<CnStatusBadge\n\t\t\t\t\tv-if=\"item.pill\"\n\t\t\t\t\t:label=\"item.pill.label\"\n\t\t\t\t\t:variant=\"item.pill.variant\"\n\t\t\t\t\tsize=\"small\" />\n\t\t\t\t<component\n\t\t\t\t\t:is=\"item.tag\"\n\t\t\t\t\tv-if=\"item.title\"\n\t\t\t\t\tclass=\"cn-record-notes__title\"\n\t\t\t\t\t:class=\"{ 'cn-record-notes__title--link': item.tag !== 'div' }\"\n\t\t\t\t\tv-bind=\"item.attrs\">\n\t\t\t\t\t{{ item.title }}\n\t\t\t\t</component>\n\t\t\t\t<span v-if=\"item.meta\" class=\"cn-record-notes__meta\">{{ item.meta }}</span>\n\t\t\t\t<span class=\"cn-record-notes__spacer\" />\n\t\t\t\t<span\n\t\t\t\t\tv-if=\"item.due\"\n\t\t\t\t\tclass=\"cn-record-notes__due\"\n\t\t\t\t\t:class=\"`cn-record-notes__due--${dueToneOf(item.due)}`\"\n\t\t\t\t\tdata-testid=\"cn-record-list-due\">{{ item.due.text }}</span>\n\t\t\t</div>\n\t\t\t<div class=\"cn-record-notes__fields\">\n\t\t\t\t<NcTextField\n\t\t\t\t\tclass=\"cn-record-notes__field\"\n\t\t\t\t\t:modelValue=\"drafts[item.key] || ''\"\n\t\t\t\t\t:label=\"fieldLabel\"\n\t\t\t\t\t:labelOutside=\"false\"\n\t\t\t\t\t:placeholder=\"placeholder\"\n\t\t\t\t\t:disabled=\"busy\"\n\t\t\t\t\t:data-testid=\"`cn-record-list-note-${item.key}`\"\n\t\t\t\t\t@update:modelValue=\"setDraft(item.key, $event)\" />\n\t\t\t\t<div v-if=\"leaf && leafProvider\" class=\"cn-record-notes__leaf\" :data-testid=\"`cn-record-list-leaf-${item.key}`\">\n\t\t\t\t\t<CnLeafMountHost\n\t\t\t\t\t\tv-if=\"leafIsMount\"\n\t\t\t\t\t\t:provider=\"leafProvider\"\n\t\t\t\t\t\t:mountProps=\"leafProps(item.row)\" />\n\t\t\t\t\t<component :is=\"leafComponent\" v-else-if=\"leafComponent\" v-bind=\"leafProps(item.row)\" />\n\t\t\t\t</div>\n\t\t\t\t<NcButton\n\t\t\t\t\tvariant=\"secondary\"\n\t\t\t\t\t:disabled=\"busy || !hasDraft(item.key)\"\n\t\t\t\t\t:data-testid=\"`cn-record-list-record-${item.key}`\"\n\t\t\t\t\t@click=\"record(item)\">\n\t\t\t\t\t{{ recordLabel }}\n\t\t\t\t</NcButton>\n\t\t\t</div>\n\t\t\t<p\n\t\t\t\tv-if=\"leafUnavailable\"\n\t\t\t\tclass=\"cn-record-notes__leaf-missing\"\n\t\t\t\t:data-testid=\"`cn-record-list-leaf-missing-${item.key}`\">\n\t\t\t\t{{ leafUnavailableText }}\n\t\t\t</p>\n\t\t</div>\n\t\t<p v-if=\"error\"\n\t\t\tclass=\"cn-record-notes__error\"\n\t\t\trole=\"alert\"\n\t\t\tdata-testid=\"cn-record-list-notes-error\">\n\t\t\t{{ error }}\n\t\t</p>\n\t\t<div v-if=\"recordAll\" class=\"cn-record-notes__all\" data-testid=\"cn-record-list-record-all\">\n\t\t\t<div class=\"cn-record-notes__all-text\">\n\t\t\t\t<h3 v-if=\"recordAll.title\" class=\"cn-record-notes__all-title\">\n\t\t\t\t\t{{ recordAll.title }}\n\t\t\t\t</h3>\n\t\t\t\t<p v-if=\"recordAll.text\" class=\"cn-record-notes__all-sentence\">\n\t\t\t\t\t{{ recordAll.text }}\n\t\t\t\t</p>\n\t\t\t</div>\n\t\t\t<div class=\"cn-record-notes__all-actions\">\n\t\t\t\t<NcButton v-if=\"recordAll.cancel\" variant=\"secondary\" :to=\"recordAll.cancel.to\">\n\t\t\t\t\t{{ recordAll.cancel.label }}\n\t\t\t\t</NcButton>\n\t\t\t\t<NcButton\n\t\t\t\t\tvariant=\"primary\"\n\t\t\t\t\t:disabled=\"busy || readyCount === 0\"\n\t\t\t\t\tdata-testid=\"cn-record-list-record-all-button\"\n\t\t\t\t\t@click=\"recordEvery\">\n\t\t\t\t\t<template v-if=\"recordAll.icon\" #icon>\n\t\t\t\t\t\t<CnIcon :name=\"recordAll.icon\" :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t{{ recordAll.label }}\n\t\t\t\t</NcButton>\n\t\t\t</div>\n\t\t</div>\n\t</div>\n</template>\n\n<script>\nimport { emit } from '@nextcloud/event-bus'\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcButton, NcTextField } from '@nextcloud/vue'\nimport CnIcon from '../CnIcon/CnIcon.vue'\nimport CnLeafMountHost from '../CnLeafMountHost/CnLeafMountHost.vue'\nimport CnStatusBadge from '../CnStatusBadge/CnStatusBadge.vue'\nimport { useIntegrationRegistry } from '../../composables/useIntegrationRegistry.js'\nimport { isAppInstalled } from '../../utils/appInstalled.js'\nimport { prefixUrl } from '../../utils/headers.js'\nimport { readPath } from '../../utils/readPath.js'\n\n/** The bus channel every endpoint-bound widget on the page refetches on. */\nconst PAGE_REFRESH_CHANNEL = 'cn:page:refresh'\n\n/**\n * The rows of a record list drawn as notes to record (`presentation:\n * \"notes\"`, DqDagAfsluiten \"Nog open van vandaag\"): per row its pill, title,\n * meta and due text, a field for what happened, an optional integration leaf\n * (humaniq's hours) and a button that records the note on the row's own\n * notes endpoint. What is typed and not yet recorded stays in this browser\n * (session storage) and is counted as the \"ready to record\" figure; one\n * button records every filled row. After a note is recorded the page's\n * endpoint widgets refetch, so the row moves to whatever lists the server\n * keeps of recorded work.\n *\n * @spec openspec/changes/record-list-notes/specs/record-list-notes/spec.md#requirement-a-record-list-can-collect-and-record-a-note-per-row\n */\nexport default {\n\tname: 'CnRecordNoteRows',\n\n\tcomponents: { CnIcon, CnLeafMountHost, CnStatusBadge, NcButton, NcTextField },\n\n\tprops: {\n\t\t/** The list's rows as the record list builds them (`key`, `row`, `tag`, `attrs`, `title`, `meta`, `pill`, `due`). */\n\t\titems: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/**\n\t\t * `noteAction`, already translated: `{ urlField?, url?, bodyField?,\n\t\t * label, fieldLabel, placeholder?, readyLabel, draftsKey?, errorText? }`.\n\t\t */\n\t\taction: {\n\t\t\ttype: Object,\n\t\t\trequired: true,\n\t\t},\n\n\t\t/** `recordAll`, already translated: `{ label, title?, text?, icon?, cancel?: { label, to } }`, or null. */\n\t\trecordAll: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** `rowLeaf`: `{ id, app?, register?, objectIdField, schemaField?, schema?, unavailableText? }`, or null. */\n\t\tleaf: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\tsetup() {\n\t\tconst { getById } = useIntegrationRegistry()\n\t\treturn { getProvider: getById }\n\t},\n\n\tdata() {\n\t\treturn { drafts: this.readDrafts(), busy: false, error: '' }\n\t},\n\n\tcomputed: {\n\t\t/** @return {string} The session storage key of this list's drafts. */\n\t\tdraftsKey() {\n\t\t\treturn `cn-record-list-notes:${this.action.draftsKey || 'default'}`\n\t\t},\n\n\t\t/** @return {number} The rows with a note typed and not yet recorded. */\n\t\treadyCount() {\n\t\t\treturn this.items.filter((item) => this.hasDraft(item.key)).length\n\t\t},\n\n\t\t/** @return {string} The label of the ready figure. */\n\t\treadyLabel() {\n\t\t\treturn this.action.readyLabel || t('nextcloud-vue', 'filled in, not recorded yet')\n\t\t},\n\n\t\t/** @return {string} */\n\t\trecordLabel() {\n\t\t\treturn this.action.label || t('nextcloud-vue', 'Record')\n\t\t},\n\n\t\t/** @return {string} */\n\t\tfieldLabel() {\n\t\t\treturn this.action.fieldLabel || t('nextcloud-vue', 'Note')\n\t\t},\n\n\t\t/** @return {string} */\n\t\tplaceholder() {\n\t\t\treturn this.action.placeholder || ''\n\t\t},\n\n\t\t/** @return {object|null} The leaf's integration provider, when one is registered. */\n\t\tleafProvider() {\n\t\t\treturn this.leaf && this.leaf.id ? (this.getProvider(this.leaf.id) || null) : null\n\t\t},\n\n\t\t/** @return {boolean} Whether the leaf mounts itself (`renderMode: \"mount\"`). */\n\t\tleafIsMount() {\n\t\t\tconst provider = this.leafProvider\n\t\t\treturn Boolean(provider) && provider.renderMode === 'mount' && typeof provider.mount === 'function'\n\t\t},\n\n\t\t/** @return {object|null} The leaf's component, in component mode. */\n\t\tleafComponent() {\n\t\t\tconst provider = this.leafProvider\n\t\t\treturn provider ? (provider.tab || provider.widget || null) : null\n\t\t},\n\n\t\t/**\n\t\t * Whether the leaf's app is installed but the leaf is not available, so\n\t\t * the row says so instead of silently showing no field.\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\tleafUnavailable() {\n\t\t\treturn Boolean(this.leaf && this.leaf.app && !this.leafProvider && isAppInstalled(this.leaf.app))\n\t\t},\n\n\t\t/** @return {string} */\n\t\tleafUnavailableText() {\n\t\t\treturn this.leaf?.unavailableText || t('nextcloud-vue', 'This field is not available here.')\n\t\t},\n\t},\n\n\twatch: {\n\t\tdrafts: {\n\t\t\tdeep: true,\n\t\t\thandler() {\n\t\t\t\tthis.saveDrafts()\n\t\t\t},\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * The drafts this browser session kept, by row key.\n\t\t *\n\t\t * @return {Object<string, string>}\n\t\t */\n\t\treadDrafts() {\n\t\t\ttry {\n\t\t\t\tconst raw = window.sessionStorage.getItem(`cn-record-list-notes:${this.action?.draftsKey || 'default'}`)\n\t\t\t\tconst parsed = raw ? JSON.parse(raw) : {}\n\t\t\t\treturn parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {}\n\t\t\t} catch {\n\t\t\t\treturn {}\n\t\t\t}\n\t\t},\n\n\t\t/** @return {void} */\n\t\tsaveDrafts() {\n\t\t\ttry {\n\t\t\t\twindow.sessionStorage.setItem(this.draftsKey, JSON.stringify(this.drafts))\n\t\t\t} catch {\n\t\t\t\t// Without session storage the drafts live as long as the page.\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * The colour of a due text: a term that ended or ends today in the\n\t\t * error colour, one still to come in the warning colour.\n\t\t *\n\t\t * @param {{tone: string}} due The due text.\n\t\t * @return {string} `error`, `warning` or `muted`.\n\t\t */\n\t\tdueToneOf(due) {\n\t\t\tif (due.tone === 'danger' || due.tone === 'today') {\n\t\t\t\treturn 'error'\n\t\t\t}\n\t\t\treturn due.tone === 'upcoming' ? 'warning' : 'muted'\n\t\t},\n\n\t\t/**\n\t\t * @param {string} key The row key.\n\t\t * @param {string} value The typed note.\n\t\t * @return {void}\n\t\t */\n\t\tsetDraft(key, value) {\n\t\t\tthis.drafts = { ...this.drafts, [key]: String(value ?? '') }\n\t\t},\n\n\t\t/**\n\t\t * @param {string} key The row key.\n\t\t * @return {boolean} Whether the row has a note typed.\n\t\t */\n\t\thasDraft(key) {\n\t\t\treturn String(this.drafts[key] ?? '').trim() !== ''\n\t\t},\n\n\t\t/**\n\t\t * The props the row's leaf gets: the row's object, register and schema.\n\t\t *\n\t\t * @param {object} row The row.\n\t\t * @return {object}\n\t\t */\n\t\tleafProps(row) {\n\t\t\tconst leaf = this.leaf || {}\n\t\t\treturn {\n\t\t\t\tobjectId: String(readPath(row, leaf.objectIdField || 'id') ?? ''),\n\t\t\t\tregister: leaf.register || '',\n\t\t\t\tschema: leaf.schemaField ? String(readPath(row, leaf.schemaField) ?? '') : (leaf.schema || ''),\n\t\t\t\tsurface: 'row',\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * The row's notes endpoint: the row's `urlField`, else `url` with\n\t\t * `{field}` filled from the row.\n\t\t *\n\t\t * @param {object} row The row.\n\t\t * @return {string} The URL, or ''.\n\t\t */\n\t\turlOf(row) {\n\t\t\tif (this.action.urlField) {\n\t\t\t\treturn String(readPath(row, this.action.urlField) ?? '')\n\t\t\t}\n\t\t\tconst template = String(this.action.url || '')\n\t\t\treturn template.replace(/\\{([A-Za-z0-9_.]+)\\}/g, (_, path) => encodeURIComponent(String(readPath(row, path) ?? '')))\n\t\t},\n\n\t\t/**\n\t\t * Record one row's note, then let the page refetch.\n\t\t *\n\t\t * @param {object} item The row item.\n\t\t * @param {boolean} [refresh] Whether to refresh the page after it.\n\t\t * @return {Promise<boolean>} Whether it was recorded.\n\t\t */\n\t\tasync record(item, refresh = true) {\n\t\t\tconst text = String(this.drafts[item.key] ?? '').trim()\n\t\t\tconst url = this.urlOf(item.row)\n\t\t\tif (text === '' || url === '') {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tthis.busy = true\n\t\t\tthis.error = ''\n\t\t\ttry {\n\t\t\t\tconst { default: axios } = await import('@nextcloud/axios')\n\t\t\t\tawait axios.post(prefixUrl(url), { [this.action.bodyField || 'message']: text })\n\t\t\t\tconst { [item.key]: done, ...rest } = this.drafts\n\t\t\t\tthis.drafts = rest\n\t\t\t\tif (refresh) {\n\t\t\t\t\temit(PAGE_REFRESH_CHANNEL, {})\n\t\t\t\t}\n\t\t\t\treturn true\n\t\t\t} catch {\n\t\t\t\tthis.error = this.action.errorText || t('nextcloud-vue', 'The note could not be recorded. It is kept here; try again.')\n\t\t\t\treturn false\n\t\t\t} finally {\n\t\t\t\tthis.busy = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Record every row with a note, one after another, then refresh once.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync recordEvery() {\n\t\t\tconst filled = this.items.filter((item) => this.hasDraft(item.key))\n\t\t\tlet any = false\n\t\t\tfor (const item of filled) {\n\t\t\t\tany = (await this.record(item, false)) || any\n\t\t\t}\n\t\t\tif (any) {\n\t\t\t\temit(PAGE_REFRESH_CHANNEL, {})\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-record-notes {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 0;\n}\n\n.cn-record-notes__ready {\n\tdisplay: flex;\n\talign-items: baseline;\n\tgap: 6px;\n\tmargin: 0 0 8px;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-record-notes__ready-figure {\n\tfont-weight: 700;\n\tcolor: var(--color-main-text);\n}\n\n.cn-record-notes__row {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 10px;\n\tpadding: 14px 0;\n\tborder-top: 1px solid var(--color-border);\n}\n\n.cn-record-notes__head {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tgap: 8px;\n}\n\n.cn-record-notes__title {\n\tfont-weight: 600;\n\tcolor: var(--color-main-text);\n}\n\n.cn-record-notes__title--link {\n\ttext-decoration: underline;\n}\n\n.cn-record-notes__meta {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 13px;\n}\n\n.cn-record-notes__spacer {\n\tflex: 1 1 auto;\n}\n\n.cn-record-notes__due {\n\tfont-weight: 600;\n}\n\n.cn-record-notes__due--error {\n\tcolor: var(--color-error-text);\n}\n\n.cn-record-notes__due--warning {\n\tcolor: var(--color-warning-text);\n}\n\n.cn-record-notes__due--muted {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-weight: 400;\n}\n\n.cn-record-notes__fields {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: flex-end;\n\tgap: 10px;\n}\n\n.cn-record-notes__field {\n\tflex: 1 1 260px;\n}\n\n.cn-record-notes__leaf {\n\tflex: 0 0 auto;\n}\n\n.cn-record-notes__leaf-missing,\n.cn-record-notes__error {\n\tmargin: 0;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 13px;\n}\n\n.cn-record-notes__error {\n\tcolor: var(--color-error-text);\n}\n\n.cn-record-notes__all {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tjustify-content: space-between;\n\tgap: 12px;\n\tmargin-top: 8px;\n\tpadding-top: 14px;\n\tborder-top: 1px solid var(--color-border);\n}\n\n.cn-record-notes__all-title {\n\tmargin: 0;\n\tfont-size: 16px;\n}\n\n.cn-record-notes__all-sentence {\n\tmargin: 4px 0 0;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-record-notes__all-actions {\n\tdisplay: flex;\n\tgap: 8px;\n}\n</style>\n"],"names":["_openBlock","_createElementBlock","_createElementVNode","_toDisplayString","_Fragment","_renderList","_createBlock","_resolveDynamicComponent","_mergeProps","_createTextVNode","_createVNode"],"mappings":"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"}
@@ -2,7 +2,7 @@ import script from './CnTabsWidget.vue2.js';
2
2
  import { render } from './CnTabsWidget.vue3.js';
3
3
 
4
4
  script.render = render;
5
- script.__scopeId = "data-v-029f78ce";
5
+ script.__scopeId = "data-v-5704a09b";
6
6
  const __sfc_main = script;
7
7
 
8
8
  export { __sfc_main as default };
@@ -196,13 +196,16 @@ var script = {
196
196
  * `layout: [{ widgetId, gridWidth }]` sets a width (of 12) per widget, as
197
197
  * the page grid does. Widgets wrap onto the next row.
198
198
  *
199
+ * A tab with `default: true` is the one the strip opens on, unless a
200
+ * route hash names another tab or the marked tab sits under "More".
201
+ *
199
202
  * `maxVisibleTabs` caps the strip: later tabs go under "More" (in the
200
203
  * board look: into the Actions menu, with History kept in the strip).
201
204
  * `hideEmpty: true` moves a tab whose count is 0 there too.
202
205
  * `moreLabel` names that menu. `variant: "segmented"` draws the strip
203
206
  * as a pill switch (CnTabs' segmented variant); `line` is the default.
204
207
  *
205
- * @type {{ tabs?: Array<{widgetId?: string, widgetIds?: string[], label?: string, icon?: string, count?: number, countField?: string, countFrom?: ('widget'|{register: string, schema: string, filter?: object}), overflow?: boolean, columns?: number, layout?: Array<{widgetId: string, gridWidth: number}>}>, ariaLabel?: string, maxVisibleTabs?: number, hideEmpty?: boolean, moreLabel?: string, variant?: ('line'|'segmented') }}
208
+ * @type {{ tabs?: Array<{widgetId?: string, widgetIds?: string[], label?: string, icon?: string, count?: number, countField?: string, countFrom?: ('widget'|{register: string, schema: string, filter?: object}), overflow?: boolean, default?: boolean, columns?: number, layout?: Array<{widgetId: string, gridWidth: number}>}>, ariaLabel?: string, maxVisibleTabs?: number, hideEmpty?: boolean, moreLabel?: string, variant?: ('line'|'segmented') }}
206
209
  */
207
210
  content: {
208
211
  type: Object,
@@ -492,6 +495,7 @@ var script = {
492
495
  count,
493
496
  overflow,
494
497
  pending: visibility === 'pending',
498
+ opensFirst: Boolean(tab && typeof tab === 'object' && tab.default === true),
495
499
  });
496
500
  });
497
501
  return resolved
@@ -677,9 +681,9 @@ var script = {
677
681
  },
678
682
 
679
683
  /**
680
- * Open on the first tab that is in the strip. Tab 0 is the default, and
681
- * when tab 0 sits under "More" the strip would open on a tab the author
682
- * chose to tuck away.
684
+ * Open on the tab marked `default: true`, else on the first tab that is in
685
+ * the strip. Tab 0 is not enough: when tab 0 sits under "More" the strip
686
+ * would open on a tab the author chose to tuck away.
683
687
  *
684
688
  * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-tab-counts-and-overflow
685
689
  */
@@ -695,8 +699,9 @@ var script = {
695
699
  if (named >= 0) {
696
700
  this.hashTargetId = wanted;
697
701
  }
702
+ const marked = this.resolvedTabs.findIndex((tab) => tab.opensFirst && !tab.overflow && !tab.pending);
698
703
  const first = this.resolvedTabs.findIndex((tab) => !tab.overflow && !tab.pending);
699
- const fallback = first >= 0 ? first : this.resolvedTabs.findIndex((tab) => !tab.overflow);
704
+ const fallback = marked >= 0 ? marked : first >= 0 ? first : this.resolvedTabs.findIndex((tab) => !tab.overflow);
700
705
  if (fallback > 0) {
701
706
  this.activeIndex = fallback;
702
707
  }