@conduction/nextcloud-vue 2.76.0 → 2.78.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 (173) hide show
  1. package/dist/esm/components/CnAiCompanion/CnAiCompanion.vue2.js +23 -0
  2. package/dist/esm/components/CnAiCompanion/CnAiCompanion.vue2.js.map +1 -1
  3. package/dist/esm/components/CnAiCompanion/CnAiCompanion.vue3.js.map +1 -1
  4. package/dist/esm/components/CnAppNav/CnAppNav.vue2.js +18 -3
  5. package/dist/esm/components/CnAppNav/CnAppNav.vue2.js.map +1 -1
  6. package/dist/esm/components/CnAppNav/CnAppNav.vue3.js +49 -3
  7. package/dist/esm/components/CnAppNav/CnAppNav.vue3.js.map +1 -1
  8. package/dist/esm/components/CnBoardView/CnBoardView.vue2.js +2 -1
  9. package/dist/esm/components/CnBoardView/CnBoardView.vue2.js.map +1 -1
  10. package/dist/esm/components/CnBoardView/CnBoardView.vue3.js.map +1 -1
  11. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +79 -1
  12. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
  13. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +229 -203
  14. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
  15. package/dist/esm/components/CnCellRenderer/CnGroupNameCell.vue.js +9 -0
  16. package/dist/esm/components/CnCellRenderer/CnGroupNameCell.vue.js.map +1 -0
  17. package/dist/esm/components/CnCellRenderer/CnGroupNameCell.vue2.js +50 -0
  18. package/dist/esm/components/CnCellRenderer/CnGroupNameCell.vue2.js.map +1 -0
  19. package/dist/esm/components/CnCellRenderer/CnGroupNameCell.vue3.js +13 -0
  20. package/dist/esm/components/CnCellRenderer/CnGroupNameCell.vue3.js.map +1 -0
  21. package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js +37 -0
  22. package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js.map +1 -1
  23. package/dist/esm/components/CnChartWidget/CnChartWidget.vue3.js +2 -2
  24. package/dist/esm/components/CnChartWidget/CnChartWidget.vue3.js.map +1 -1
  25. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +29 -4
  26. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
  27. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
  28. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +48 -6
  29. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
  30. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +90 -51
  31. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
  32. package/dist/esm/components/CnDateRangePicker/CnDateRangePicker.vue.js +1 -1
  33. package/dist/esm/components/CnDateRangePicker/CnDateRangePicker.vue2.js +45 -2
  34. package/dist/esm/components/CnDateRangePicker/CnDateRangePicker.vue2.js.map +1 -1
  35. package/dist/esm/components/CnDateRangePicker/CnDateRangePicker.vue3.js.map +1 -1
  36. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +16 -28
  37. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  38. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +12 -24
  39. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  40. package/dist/esm/components/CnFavouriteToggle/CnFavouriteToggle.vue2.js +7 -2
  41. package/dist/esm/components/CnFavouriteToggle/CnFavouriteToggle.vue2.js.map +1 -1
  42. package/dist/esm/components/CnFavouriteToggle/CnFavouriteToggle.vue3.js.map +1 -1
  43. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +114 -1
  44. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
  45. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +115 -62
  46. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
  47. package/dist/esm/components/CnFollowToggle/CnFollowToggle.vue2.js +89 -7
  48. package/dist/esm/components/CnFollowToggle/CnFollowToggle.vue2.js.map +1 -1
  49. package/dist/esm/components/CnFollowToggle/CnFollowToggle.vue3.js +62 -30
  50. package/dist/esm/components/CnFollowToggle/CnFollowToggle.vue3.js.map +1 -1
  51. package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js +7 -1
  52. package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
  53. package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
  54. package/dist/esm/components/CnFormPage/CnFormPage.vue2.js +4 -1
  55. package/dist/esm/components/CnFormPage/CnFormPage.vue2.js.map +1 -1
  56. package/dist/esm/components/CnFormPage/CnFormPage.vue3.js.map +1 -1
  57. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +104 -13
  58. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  59. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +13 -9
  60. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  61. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js +199 -7
  62. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js.map +1 -1
  63. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue3.js +155 -119
  64. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue3.js.map +1 -1
  65. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js +6 -0
  66. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
  67. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +4 -1
  68. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
  69. package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js +36 -0
  70. package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js.map +1 -1
  71. package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js +35 -23
  72. package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js.map +1 -1
  73. package/dist/esm/components/CnStatusBadge/CnStatusBadge.vue2.js +6 -2
  74. package/dist/esm/components/CnStatusBadge/CnStatusBadge.vue2.js.map +1 -1
  75. package/dist/esm/components/CnStatusBadge/CnStatusBadge.vue3.js.map +1 -1
  76. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js +13 -0
  77. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js.map +1 -1
  78. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue3.js.map +1 -1
  79. package/dist/esm/composables/useFormDraft.js +11 -1
  80. package/dist/esm/composables/useFormDraft.js.map +1 -1
  81. package/dist/esm/composables/useListView.js +4 -0
  82. package/dist/esm/composables/useListView.js.map +1 -1
  83. package/dist/esm/composables/useObjectPresence.js +15 -4
  84. package/dist/esm/composables/useObjectPresence.js.map +1 -1
  85. package/dist/esm/index.js +2 -1
  86. package/dist/esm/index.js.map +1 -1
  87. package/dist/esm/l10n/en.json.js +32 -1
  88. package/dist/esm/l10n/en.json.js.map +1 -1
  89. package/dist/esm/l10n/nl.json.js +32 -1
  90. package/dist/esm/l10n/nl.json.js.map +1 -1
  91. package/dist/esm/nextcloud-vue.css +500 -75
  92. package/dist/esm/store/plugins/interactions.js +20 -10
  93. package/dist/esm/store/plugins/interactions.js.map +1 -1
  94. package/dist/esm/store/useObjectStore.js +17 -0
  95. package/dist/esm/store/useObjectStore.js.map +1 -1
  96. package/dist/esm/utils/badgeVariants.js +32 -0
  97. package/dist/esm/utils/badgeVariants.js.map +1 -0
  98. package/dist/esm/utils/builtInFormatters.js +15 -7
  99. package/dist/esm/utils/builtInFormatters.js.map +1 -1
  100. package/dist/esm/utils/groupAutocomplete.js +63 -1
  101. package/dist/esm/utils/groupAutocomplete.js.map +1 -1
  102. package/dist/esm/utils/lensAvailability.js +90 -0
  103. package/dist/esm/utils/lensAvailability.js.map +1 -0
  104. package/dist/esm/utils/personalLenses.js +13 -6
  105. package/dist/esm/utils/personalLenses.js.map +1 -1
  106. package/dist/esm/utils/recordInteractions.js +31 -13
  107. package/dist/esm/utils/recordInteractions.js.map +1 -1
  108. package/dist/esm/utils/resolveFilterTokens.js +46 -3
  109. package/dist/esm/utils/resolveFilterTokens.js.map +1 -1
  110. package/dist/esm/utils/schema.js +1 -1
  111. package/dist/esm/utils/schema.js.map +1 -1
  112. package/dist/esm/utils/sentinelTokens.js +4 -4
  113. package/dist/esm/utils/sentinelTokens.js.map +1 -1
  114. package/dist/esm/utils/validateManifestV2.compiled.js +2 -2
  115. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  116. package/dist/esm/utils/widgetVisibility.js +46 -1
  117. package/dist/esm/utils/widgetVisibility.js.map +1 -1
  118. package/dist/nextcloud-vue.cjs.js +4389 -2890
  119. package/dist/nextcloud-vue.cjs.js.map +2 -2
  120. package/dist/nextcloud-vue.css +500 -75
  121. package/l10n/en.json +32 -1
  122. package/l10n/nl.json +32 -1
  123. package/package.json +1 -1
  124. package/src/components/CnAiCompanion/CnAiCompanion.vue +23 -0
  125. package/src/components/CnAppNav/CnAppNav.vue +44 -5
  126. package/src/components/CnBoardView/CnBoardView.vue +2 -1
  127. package/src/components/CnCellRenderer/CnCellRenderer.md +1 -0
  128. package/src/components/CnCellRenderer/CnCellRenderer.vue +106 -9
  129. package/src/components/CnCellRenderer/CnGroupNameCell.vue +61 -0
  130. package/src/components/CnChartWidget/CnChartWidget.vue +39 -2
  131. package/src/components/CnDashboardPage/CnDashboardPage.vue +29 -4
  132. package/src/components/CnDataTable/CnDataTable.md +9 -0
  133. package/src/components/CnDataTable/CnDataTable.vue +87 -22
  134. package/src/components/CnDateRangePicker/CnDateRangePicker.vue +44 -1
  135. package/src/components/CnDetailPage/CnDetailPage.vue +10 -27
  136. package/src/components/CnFavouriteToggle/CnFavouriteToggle.vue +7 -2
  137. package/src/components/CnFilesBrowser/CnFilesBrowser.vue +204 -5
  138. package/src/components/CnFollowToggle/CnFollowToggle.vue +118 -13
  139. package/src/components/CnFormDialog/CnFormDialog.vue +7 -1
  140. package/src/components/CnFormPage/CnFormPage.vue +4 -1
  141. package/src/components/CnIndexPage/CnIndexPage.md +36 -0
  142. package/src/components/CnIndexPage/CnIndexPage.vue +112 -19
  143. package/src/components/CnObjectDataWidget/CnObjectDataWidget.vue +226 -2
  144. package/src/components/CnObjectSidebar/CnFilesTab.vue +9 -0
  145. package/src/components/CnStatWidget/CnStatWidget.vue +52 -2
  146. package/src/components/CnStatusBadge/CnStatusBadge.vue +6 -2
  147. package/src/components/CnWidgetObjectTable/CnWidgetObjectTable.vue +13 -0
  148. package/src/composables/__tests__/useObjectPresence.spec.js +24 -0
  149. package/src/composables/useFormDraft.js +11 -1
  150. package/src/composables/useListView.js +4 -0
  151. package/src/composables/useObjectPresence.js +15 -4
  152. package/src/css/badge.css +31 -0
  153. package/src/css/kpi-card.css +51 -0
  154. package/src/css/look-board-detail.css +40 -2
  155. package/src/css/look-board-index.css +36 -0
  156. package/src/css/look-board.css +8 -0
  157. package/src/css/table.css +82 -17
  158. package/src/index.js +2 -1
  159. package/src/schemas/app-manifest-v2.schema.json +15 -11
  160. package/src/store/plugins/interactions.js +20 -10
  161. package/src/store/useObjectStore.js +17 -0
  162. package/src/utils/badgeVariants.js +29 -0
  163. package/src/utils/builtInFormatters.js +15 -7
  164. package/src/utils/groupAutocomplete.js +75 -0
  165. package/src/utils/index.js +1 -1
  166. package/src/utils/lensAvailability.js +86 -0
  167. package/src/utils/personalLenses.js +13 -6
  168. package/src/utils/recordInteractions.js +31 -13
  169. package/src/utils/resolveFilterTokens.js +45 -2
  170. package/src/utils/schema.js +1 -1
  171. package/src/utils/sentinelTokens.js +4 -4
  172. package/src/utils/validateManifestV2.compiled.js +2 -2
  173. package/src/utils/widgetVisibility.js +45 -0
package/l10n/en.json CHANGED
@@ -114,6 +114,7 @@
114
114
  "Saved just now": "Saved just now",
115
115
  "Saved {time}": "Saved {time}",
116
116
  "Saved": "Saved",
117
+ "Draft kept on this device": "Draft kept on this device",
117
118
  "Publish": "Publish",
118
119
  "Edit {title} (draft)": "Edit {title} (draft)",
119
120
  "Also search inside files": "Also search inside files",
@@ -453,6 +454,12 @@
453
454
  "NC Activity is currently unavailable.": "NC Activity is currently unavailable.",
454
455
  "Could not load activity.": "Could not load activity.",
455
456
  "Today": "Today",
457
+ "Last 8 hours": "Last 8 hours",
458
+ "Last 24 hours": "Last 24 hours",
459
+ "Last 7 days": "Last 7 days",
460
+ "Last 30 days": "Last 30 days",
461
+ "Last 90 days": "Last 90 days",
462
+ "Custom range": "Custom range",
456
463
  "Yesterday": "Yesterday",
457
464
  "Earlier": "Earlier",
458
465
  "System": "System",
@@ -1290,7 +1297,19 @@
1290
1297
  "Test environment": "Test environment",
1291
1298
  "Acceptance environment": "Acceptance environment",
1292
1299
  "About {label}": "About {label}",
1293
- "Could not load the form for this item.": "Could not load the form for this item."
1300
+ "Could not load the form for this item.": "Could not load the form for this item.",
1301
+ "Drop to add": "Drop to add",
1302
+ "Or drag files onto this list.": "Or drag files onto this list.",
1303
+ "This server does not keep track of what you open.": "This server does not keep track of what you open.",
1304
+ "Log in to see what you opened recently.": "Log in to see what you opened recently.",
1305
+ "Your recent items are not available right now.": "Your recent items are not available right now.",
1306
+ "yesterday": "yesterday",
1307
+ "tomorrow": "tomorrow",
1308
+ "Filter by {column}": "Filter by {column}",
1309
+ "Filter by {column}, active": "Filter by {column}, active",
1310
+ "Turn notifications off": "Turn notifications off",
1311
+ "Turn notifications on": "Turn notifications on",
1312
+ "Could not change notifications.": "Could not change notifications."
1294
1313
  },
1295
1314
  "plurals": {
1296
1315
  "%n year": [
@@ -1344,6 +1363,18 @@
1344
1363
  "You see {count} capability of {total}.": [
1345
1364
  "You see {count} capability of {total}.",
1346
1365
  "You see {count} capabilities of {total}."
1366
+ ],
1367
+ "%n file added": [
1368
+ "%n file added",
1369
+ "%n files added"
1370
+ ],
1371
+ "{count} day ago": [
1372
+ "{count} day ago",
1373
+ "{count} days ago"
1374
+ ],
1375
+ "in {count} day": [
1376
+ "in {count} day",
1377
+ "in {count} days"
1347
1378
  ]
1348
1379
  }
1349
1380
  }
package/l10n/nl.json CHANGED
@@ -114,6 +114,7 @@
114
114
  "Saved just now": "Zojuist opgeslagen",
115
115
  "Saved {time}": "Opgeslagen {time}",
116
116
  "Saved": "Opgeslagen",
117
+ "Draft kept on this device": "Concept bewaard op dit apparaat",
117
118
  "Edit {title} (draft)": "{title} bewerken (concept)",
118
119
  "Also search inside files": "Zoek ook in bestanden",
119
120
  "File matches are limited to the best 50.": "Bestandstreffers zijn beperkt tot de beste 50.",
@@ -452,6 +453,12 @@
452
453
  "NC Activity is currently unavailable.": "NC Activiteit is momenteel niet beschikbaar.",
453
454
  "Could not load activity.": "Kon activiteit niet laden.",
454
455
  "Today": "Vandaag",
456
+ "Last 8 hours": "Laatste 8 uur",
457
+ "Last 24 hours": "Laatste 24 uur",
458
+ "Last 7 days": "Laatste 7 dagen",
459
+ "Last 30 days": "Laatste 30 dagen",
460
+ "Last 90 days": "Laatste 90 dagen",
461
+ "Custom range": "Aangepaste periode",
455
462
  "Yesterday": "Gisteren",
456
463
  "Earlier": "Eerder",
457
464
  "System": "Systeem",
@@ -1341,7 +1348,19 @@
1341
1348
  "Test environment": "Testomgeving",
1342
1349
  "Acceptance environment": "Acceptatieomgeving",
1343
1350
  "About {label}": "Over {label}",
1344
- "Could not load the form for this item.": "Kon het formulier voor dit item niet laden."
1351
+ "Could not load the form for this item.": "Kon het formulier voor dit item niet laden.",
1352
+ "Drop to add": "Laat los om toe te voegen",
1353
+ "Or drag files onto this list.": "Of sleep bestanden op deze lijst.",
1354
+ "This server does not keep track of what you open.": "Deze server houdt niet bij welke items je opent.",
1355
+ "Log in to see what you opened recently.": "Log in om te zien wat je onlangs opende.",
1356
+ "Your recent items are not available right now.": "Je recente items zijn nu niet beschikbaar.",
1357
+ "yesterday": "gisteren",
1358
+ "tomorrow": "morgen",
1359
+ "Filter by {column}": "Filteren op {column}",
1360
+ "Filter by {column}, active": "Filteren op {column}, actief",
1361
+ "Turn notifications off": "Meldingen uitzetten",
1362
+ "Turn notifications on": "Meldingen aanzetten",
1363
+ "Could not change notifications.": "Meldingen konden niet worden gewijzigd."
1345
1364
  },
1346
1365
  "plurals": {
1347
1366
  "%n year": [
@@ -1395,6 +1414,18 @@
1395
1414
  "You see {count} capability of {total}.": [
1396
1415
  "Je ziet {count} functionaliteit van {total}.",
1397
1416
  "Je ziet {count} functionaliteiten van {total}."
1417
+ ],
1418
+ "%n file added": [
1419
+ "%n bestand toegevoegd",
1420
+ "%n bestanden toegevoegd"
1421
+ ],
1422
+ "{count} day ago": [
1423
+ "{count} dag geleden",
1424
+ "{count} dagen geleden"
1425
+ ],
1426
+ "in {count} day": [
1427
+ "over {count} dag",
1428
+ "over {count} dagen"
1398
1429
  ]
1399
1430
  }
1400
1431
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@conduction/nextcloud-vue",
3
- "version": "2.76.0",
3
+ "version": "2.78.0",
4
4
  "description": "Shared Vue component library for Conduction Nextcloud apps — complements @nextcloud/vue with higher-level components, OpenRegister integration, and NL Design System support",
5
5
  "homepage": "https://github.com/ConductionNL/nextcloud-vue#readme",
6
6
  "bugs": {
@@ -66,6 +66,7 @@ import CnAiChatPanel from './CnAiChatPanel.vue'
66
66
  import CnAiFloatingButton from './CnAiFloatingButton.vue'
67
67
  import { chatHealthUrl, DEFAULT_CHAT_APP_ID } from '../../composables/aiChatConfig.js'
68
68
  import { useAiChatStream } from '../../composables/useAiChatStream.js'
69
+ import { isAppInstalled } from '../../utils/appInstalled.js'
69
70
 
70
71
  const HEALTH_TIMEOUT = 5000
71
72
 
@@ -340,6 +341,15 @@ export default {
340
341
  }
341
342
  },
342
343
 
344
+ /**
345
+ * Ask the chat backend whether it can answer, and render only if so.
346
+ *
347
+ * Skipped without a request when the backend app is not installed for
348
+ * this user.
349
+ *
350
+ * @return {Promise<void>}
351
+ * @spec openspec/changes/no-calls-to-missing-apps-and-presence-beacon/specs/ai-companion/spec.md#requirement-the-companion-does-not-probe-a-backend-that-is-not-installed
352
+ */
343
353
  async runHealthProbe() {
344
354
  // RETRY before hiding. A single probe makes the whole companion
345
355
  // disappear on one slow response, and "slow" is normal: measured on a
@@ -351,6 +361,19 @@ export default {
351
361
  //
352
362
  // A backend that is genuinely down fails all attempts and is still
353
363
  // reported; this only stops one unlucky request from deciding.
364
+ //
365
+ // 🔴 NO PROBE AT AN APP THAT IS NOT THERE. Nextcloud already tells
366
+ // the page which apps the user has (`OC.appswebroots`), so asking a
367
+ // missing backend for its health is a guaranteed 404, three times
368
+ // per page, in every console. Measured on cloud.conduction.nl
369
+ // (round-5 check): hermiq not installed, three
370
+ // `/apps/hermiq/api/chat/health` 404s on every pipelinq and dossiq
371
+ // page. Hidden either way; now hidden without the requests.
372
+ if (!isAppInstalled(this.chatAppId || DEFAULT_CHAT_APP_ID)) {
373
+ this.probeSucceeded = false
374
+ return
375
+ }
376
+
354
377
  for (let attempt = 1; attempt <= HEALTH_PROBE_ATTEMPTS; attempt++) {
355
378
  try {
356
379
  const response = await axios.get(chatHealthUrl(this.chatAppId), {
@@ -183,7 +183,19 @@
183
183
  v-if="isCaption(item)"
184
184
  :key="item.id"
185
185
  :name="resolveLabel(item)"
186
- :data-testid="`cn-nav-caption-${item.id}`" />
186
+ :data-testid="`cn-nav-caption-${item.id}`">
187
+ <template v-if="item.href" #actions>
188
+ <NcActionLink
189
+ :href="item.href"
190
+ :aria-label="captionEditLabel(item)"
191
+ :data-testid="`cn-nav-caption-edit-${item.id}`">
192
+ <template #icon>
193
+ <Pencil :size="20" />
194
+ </template>
195
+ {{ captionEditLabel(item) }}
196
+ </NcActionLink>
197
+ </template>
198
+ </NcAppNavigationCaption>
187
199
  <NcAppNavigationItem
188
200
  v-else
189
201
  :key="item.id"
@@ -424,7 +436,19 @@
424
436
  v-if="isCaption(item)"
425
437
  :key="item.id"
426
438
  :name="resolveLabel(item)"
427
- :data-testid="`cn-nav-caption-${item.id}`" />
439
+ :data-testid="`cn-nav-caption-${item.id}`">
440
+ <template v-if="item.href" #actions>
441
+ <NcActionLink
442
+ :href="item.href"
443
+ :aria-label="captionEditLabel(item)"
444
+ :data-testid="`cn-nav-caption-edit-${item.id}`">
445
+ <template #icon>
446
+ <Pencil :size="20" />
447
+ </template>
448
+ {{ captionEditLabel(item) }}
449
+ </NcActionLink>
450
+ </template>
451
+ </NcAppNavigationCaption>
428
452
  <NcAppNavigationItem
429
453
  v-else
430
454
  :key="item.id"
@@ -466,7 +490,7 @@
466
490
  <script>
467
491
  import { translate as t } from '@nextcloud/l10n'
468
492
  import { generateUrl } from '@nextcloud/router'
469
- import { NcAppNavigation, NcAppNavigationCaption, NcAppNavigationItem, NcAppNavigationList, NcAppNavigationNew, NcAppNavigationSettings, NcButton, NcCounterBubble } from '@nextcloud/vue'
493
+ import { NcActionLink, NcAppNavigation, NcAppNavigationCaption, NcAppNavigationItem, NcAppNavigationList, NcAppNavigationNew, NcAppNavigationSettings, NcButton, NcCounterBubble } from '@nextcloud/vue'
470
494
  import { toRaw } from 'vue'
471
495
  import BookOpenVariant from 'vue-material-design-icons/BookOpenVariant.vue'
472
496
  import Cog from 'vue-material-design-icons/Cog.vue'
@@ -474,6 +498,7 @@ import Cog from 'vue-material-design-icons/Cog.vue'
474
498
  import HelpCircleOutline from 'vue-material-design-icons/HelpCircleOutline.vue'
475
499
  import MapMarkerPath from 'vue-material-design-icons/MapMarkerPath.vue'
476
500
  import OpenInNew from 'vue-material-design-icons/OpenInNew.vue'
501
+ import Pencil from 'vue-material-design-icons/Pencil.vue'
477
502
  import Plus from 'vue-material-design-icons/Plus.vue'
478
503
  import ShieldAccountOutline from 'vue-material-design-icons/ShieldAccountOutline.vue'
479
504
  import { ICON_MAP } from '../CnIcon/CnIcon.vue'
@@ -560,6 +585,8 @@ export default {
560
585
  name: 'CnAppNav',
561
586
 
562
587
  components: {
588
+ NcActionLink,
589
+ Pencil,
563
590
  NcAppNavigation,
564
591
  NcAppNavigationCaption,
565
592
  NcAppNavigationItem,
@@ -1921,8 +1948,10 @@ export default {
1921
1948
  /**
1922
1949
  * Whether a menu entry renders as a `NcAppNavigationCaption`
1923
1950
  * (`type: "caption"`) rather than a clickable
1924
- * `NcAppNavigationItem`. Caption entries ignore `route`, `href`,
1925
- * `action`, `icon`, `count`, `children`, and `pinned`.
1951
+ * `NcAppNavigationItem`. Caption entries ignore `route`, `action`,
1952
+ * `icon`, `count`, `children`, and `pinned`. A caption's `href` is
1953
+ * drawn as a pencil link beside the heading: the place where what
1954
+ * stands under it is changed (a user's own choice of entries, say).
1926
1955
  *
1927
1956
  * @param {{ type?: string }} item Menu entry descriptor.
1928
1957
  * @return {boolean}
@@ -1931,6 +1960,16 @@ export default {
1931
1960
  return item?.type === 'caption'
1932
1961
  },
1933
1962
 
1963
+ /**
1964
+ * The accessible name of a caption's pencil link.
1965
+ *
1966
+ * @param {object} item The caption entry.
1967
+ * @return {string} "Change {caption}", translated.
1968
+ */
1969
+ captionEditLabel(item) {
1970
+ return t('nextcloud-vue', 'Change {name}', { name: this.resolveLabel(item) })
1971
+ },
1972
+
1934
1973
  /**
1935
1974
  * Whether the host has registered a scoped slot named
1936
1975
  * `item-${id}-actions` for this menu item. Used to gate template
@@ -308,6 +308,7 @@
308
308
  import { translate as t } from '@nextcloud/l10n'
309
309
  import CnStatusBadge from '../CnStatusBadge/CnStatusBadge.vue'
310
310
  import { normalizeLook } from '../../composables/useLook.js'
311
+ import { isBadgeVariant } from '../../utils/badgeVariants.js'
311
312
  import { buildBoardColumns, resolveStageColor } from '../../utils/boardColumns.js'
312
313
  import { buildSwimlanes } from '../../utils/boardSwimlanes.js'
313
314
  import { DROP_OUTCOMES, runBoardDrop } from '../../utils/boardTransition.js'
@@ -688,7 +689,7 @@ export default {
688
689
  pillVariant(card) {
689
690
  const raw = this.textOf(readPath(card, this.roles.pill))
690
691
  const variant = this.roles.pillColors[raw] ?? this.roles.pillColors[raw.toLowerCase()]
691
- return ['default', 'primary', 'success', 'warning', 'error', 'info'].includes(variant) ? variant : 'default'
692
+ return isBadgeVariant(variant) ? variant : 'default'
692
693
  },
693
694
 
694
695
  /**
@@ -89,6 +89,7 @@ fall back to the normal rendering. See `docs/migrating-to-manifest.md` →
89
89
  Column widget — `widget="badge"`, `widget="fkResolve"` (uuid → related object label via the shared object store with per-schema caching; config `widgetProps { register, schema, labelField }`), and `widget="link"` are built-in; any other id resolves against the app's `cellWidgets` registry (`CnAppRoot`'s `cellWidgets` prop → `cnCellWidgets`), and that component receives `{ value, row, property, formatted, ...widgetProps }`. `widget` takes precedence over `formatter`; when both are set the widget gets the formatter-shaped value as `formatted`. See `docs/migrating-to-manifest.md` → "Column widgets" + "Built-in cell formatters / widgets".
90
90
 
91
91
  - `widget="badge"` — `CnStatusBadge` pill; `widgetProps.variant` picks the colour.
92
+ An enum value shows its `x-enum-labels` label, translated, as the plain enum cell does; a column `formatter` still wins. `widgetProps.colorMap` is looked up by the stored value when it has an entry for it, else by the label. The `link` widget and the `swatch` format show the same label.
92
93
  - `widget="link"` — `<router-link>` when `widgetProps.route` is a manifest page id, or when `widgetProps.routeMap` maps the row's `widgetProps.routeField` value to one (a value the map does not hold falls back to `route`, then to text); `<a target="_blank">` when `widgetProps.href` is set (with `{key}` placeholders substituted from the row); plain text + once-per-session `console.warn` when neither resolves (silence with `widgetProps.fallback:"silent"`). For non-`id` route params: `widgetProps.params: { routeParam: "rowField" }`. Default param map is `{ id: row[rowKey] }` — see the `row-key` prop below.
93
94
 
94
95
  ```vue
@@ -10,12 +10,29 @@
10
10
  :formatted="formattedValue"
11
11
  v-bind="widgetProps" />
12
12
 
13
- <!-- Built-in "badge" widget — renders the (possibly formatter-shaped) value as a status pill -->
13
+ <!-- Built-in "group" widget: a Nextcloud group id (or a list of them)
14
+ shown as the group's display name, cached per id. Used on its own
15
+ for a property marked `referenceType: nextcloud-group` /
16
+ `format: nc-group` unless the column has a formatter. -->
17
+ <template v-else-if="isGroupCell">
18
+ <span v-if="!groupIds.length" class="cn-cell-renderer__dash">—</span>
19
+ <span v-else class="cn-cell-renderer__groups">
20
+ <template v-for="(gid, i) in groupIds" :key="gid">
21
+ <span v-if="i > 0">, </span>
22
+ <CnGroupNameCell :gid="gid" />
23
+ </template>
24
+ </span>
25
+ </template>
26
+
27
+ <!-- Built-in "badge" widget: renders the value as a status pill. An enum
28
+ value shows its label (x-enum-labels, translated) unless a formatter
29
+ shapes it; the colour stays keyed on the raw value. -->
14
30
  <template v-else-if="widget === 'badge'">
15
31
  <CnStatusBadge v-if="hasValue"
16
- :label="String(formattedValue)"
32
+ :label="labelledValue"
17
33
  :variant="badgeVariant"
18
- :colorMap="badgeColorMap" />
34
+ :colorMap="badgeColorMap"
35
+ :colorKey="badgeColorKey" />
19
36
  <span v-else class="cn-cell-renderer__dash">—</span>
20
37
  </template>
21
38
 
@@ -64,7 +81,7 @@
64
81
  v-if="linkRoute"
65
82
  :to="linkRoute"
66
83
  class="cn-cell-renderer__link">
67
- {{ formattedValue }}
84
+ {{ labelledValue }}
68
85
  </router-link>
69
86
  <a
70
87
  v-else-if="linkHref"
@@ -72,9 +89,9 @@
72
89
  target="_blank"
73
90
  rel="noopener"
74
91
  class="cn-cell-renderer__link">
75
- {{ formattedValue }}
92
+ {{ labelledValue }}
76
93
  </a>
77
- <span v-else :title="rawTitle">{{ formattedValue }}</span>
94
+ <span v-else :title="rawTitle">{{ labelledValue }}</span>
78
95
  </template>
79
96
 
80
97
  <!-- Built-in "avatar" widget: a person as an avatar with the name beside
@@ -127,7 +144,7 @@
127
144
  class="cn-cell-renderer__swatch-dot"
128
145
  :style="{ backgroundColor: swatchColor }"
129
146
  aria-hidden="true" />
130
- <span v-if="hasValue" :title="rawTitle">{{ formattedValue }}</span>
147
+ <span v-if="hasValue" :title="rawTitle">{{ labelledValue }}</span>
131
148
  <span v-else class="cn-cell-renderer__dash">—</span>
132
149
  </span>
133
150
  </template>
@@ -192,11 +209,12 @@ import { getCanonicalLocale } from '@nextcloud/l10n'
192
209
  import { NcAvatar, NcDateTime } from '@nextcloud/vue'
193
210
  import CheckBold from 'vue-material-design-icons/CheckBold.vue'
194
211
  import CnFkResolveCell from '../CnFkResolveCell/CnFkResolveCell.vue'
212
+ import CnGroupNameCell from './CnGroupNameCell.vue'
195
213
  import { parseDateValue, resolveDateVariant } from '../../utils/dateVariant.js'
196
214
  import { safeCurrencyCode } from '../../utils/formatMetric.js'
197
215
  import { objectFieldValue } from '../../utils/objectName.js'
198
216
  import { safeHref } from '../../utils/safeHref.js'
199
- import { formatValue } from '../../utils/schema.js'
217
+ import { formatValue, isGroupProp } from '../../utils/schema.js'
200
218
  import { CnStatusBadge } from '../CnStatusBadge/index.js'
201
219
 
202
220
  /** A uuid in any version, the shape OpenRegister ids take. */
@@ -226,6 +244,7 @@ export default {
226
244
  components: {
227
245
  CnStatusBadge,
228
246
  CnFkResolveCell,
247
+ CnGroupNameCell,
229
248
  CheckBold,
230
249
  NcAvatar,
231
250
  NcDateTime,
@@ -307,6 +326,9 @@ export default {
307
326
  * a date whose colour follows `widgetProps.variantWhen`, rules on the
308
327
  * number of days until the date (`[{ op: "lt", value: 0, variant:
309
328
  * "error" }, { op: "lte", value: 5, variant: "warning" }]`).
329
+ * The built-in id `"group"` shows a Nextcloud group id (or a list of
330
+ * them) as the group's display name; a column whose property is
331
+ * marked `referenceType: "nextcloud-group"` gets it without asking.
310
332
  * Takes precedence over `formatter`/the type-aware rendering, but the
311
333
  * value handed to the widget is the formatter-shaped `formatted` when
312
334
  * `formatter` is also set.
@@ -577,6 +599,79 @@ export default {
577
599
  return c || null
578
600
  },
579
601
 
602
+ /**
603
+ * Whether this cell shows Nextcloud group names: the column asks for
604
+ * `widget: "group"`, or it names no widget and no formatter and its
605
+ * property is a group (or a list of groups).
606
+ *
607
+ * @spec openspec/changes/nextcloud-group-surfaces/specs/data-display/spec.md#requirement-a-group-cell-shows-the-groups-display-name
608
+ * @return {boolean}
609
+ */
610
+ isGroupCell() {
611
+ if (this.widget === 'group') {
612
+ return true
613
+ }
614
+ if (this.widget || this.hasFormatter) {
615
+ return false
616
+ }
617
+ const p = this.property
618
+ if (!p || typeof p !== 'object') {
619
+ return false
620
+ }
621
+ return isGroupProp(p) || (p.type === 'array' && isGroupProp(p.items))
622
+ },
623
+
624
+ /**
625
+ * The group ids in this cell, empty ones dropped.
626
+ *
627
+ * @spec openspec/changes/nextcloud-group-surfaces/specs/data-display/spec.md#requirement-a-group-cell-shows-the-groups-display-name
628
+ * @return {string[]}
629
+ */
630
+ groupIds() {
631
+ const raw = this.value
632
+ const list = Array.isArray(raw) ? raw : [raw]
633
+ return list
634
+ .filter((gid) => (typeof gid === 'string' && gid !== '') || typeof gid === 'number')
635
+ .map((gid) => String(gid))
636
+ },
637
+
638
+ /**
639
+ * The text a built-in widget (`badge`, `link`) or a swatch shows. An
640
+ * enum value reads its label the way the plain enum cell does
641
+ * (`enumLabel`: `x-enum-labels`, translated), so a list says "Active"
642
+ * where the detail page does, not the stored "active". A column
643
+ * formatter still wins: an app that shapes the value itself gets
644
+ * exactly what its formatter returns.
645
+ *
646
+ * @return {string}
647
+ * @spec openspec/changes/cell-labels-and-draft-indicator/specs/cell-labels-and-draft-indicator/spec.md#requirement-a-built-in-cell-widget-shows-an-enum-value-by-its-label
648
+ */
649
+ labelledValue() {
650
+ if (!this.hasFormatter && this.isEnum && this.hasValue && typeof this.value !== 'object') {
651
+ return this.enumLabel
652
+ }
653
+ return String(this.formattedValue)
654
+ },
655
+
656
+ /**
657
+ * The key the built-in `badge` looks its colour up by: the raw value
658
+ * when `widgetProps.colorMap` has an entry for it (case-insensitive),
659
+ * so a translated label keeps the colour the manifest keyed on the
660
+ * stored code. Otherwise '' and the badge looks up its label, as before,
661
+ * which keeps a map keyed on a formatter's output working.
662
+ *
663
+ * @return {string}
664
+ * @spec openspec/changes/cell-labels-and-draft-indicator/specs/cell-labels-and-draft-indicator/spec.md#requirement-a-built-in-cell-widget-shows-an-enum-value-by-its-label
665
+ */
666
+ badgeColorKey() {
667
+ const map = this.badgeColorMap
668
+ if (!map || !this.hasValue || typeof this.value === 'object') {
669
+ return ''
670
+ }
671
+ const raw = String(this.value).toLowerCase()
672
+ return Object.keys(map).some((key) => key.toLowerCase() === raw) ? String(this.value) : ''
673
+ },
674
+
580
675
  /** Variant for the built-in `badge` widget — `widgetProps.variant` or `'default'`. */
581
676
  badgeVariant() {
582
677
  return (this.widgetProps && this.widgetProps.variant) || 'default'
@@ -1048,8 +1143,10 @@ export default {
1048
1143
  color: var(--color-text-error, var(--color-error-text));
1049
1144
  }
1050
1145
 
1146
+ /* The text variant, not the fill: from Nextcloud 32 `--color-success` is a
1147
+ background colour and too pale for a 16px glyph on a light theme. */
1051
1148
  .cn-cell-renderer__icon--success {
1052
- color: var(--color-success);
1149
+ color: var(--color-text-success, var(--color-success-text, var(--color-success)));
1053
1150
  }
1054
1151
 
1055
1152
  .cn-cell-renderer__swatch-wrap {
@@ -0,0 +1,61 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: 2026 Conduction B.V.
3
+ SPDX-License-Identifier: EUPL-1.2
4
+
5
+ CnGroupNameCell: one Nextcloud group id shown as the group's display name.
6
+ The name is looked up once per page (utils/groupAutocomplete.js); until it
7
+ arrives, and when it cannot be found, the cell shows the id.
8
+ -->
9
+ <template>
10
+ <span class="cn-group-name-cell" :title="gid">{{ label }}</span>
11
+ </template>
12
+
13
+ <script>
14
+ import { groupDisplayName, loadGroupDisplayName } from '../../utils/groupAutocomplete.js'
15
+
16
+ /**
17
+ * CnGroupNameCell renders a Nextcloud group id as its display name.
18
+ *
19
+ * ```vue
20
+ * <CnGroupNameCell gid="toezicht" />
21
+ * ```
22
+ *
23
+ * @spec openspec/changes/nextcloud-group-surfaces/specs/data-display/spec.md#requirement-a-group-cell-shows-the-groups-display-name
24
+ */
25
+ export default {
26
+ name: 'CnGroupNameCell',
27
+
28
+ props: {
29
+ /** The group id to show. */
30
+ gid: {
31
+ type: String,
32
+ required: true,
33
+ },
34
+ },
35
+
36
+ computed: {
37
+ /**
38
+ * The cached display name, or the id while it loads or when unknown.
39
+ *
40
+ * @return {string}
41
+ */
42
+ label() {
43
+ return groupDisplayName(this.gid) || this.gid
44
+ },
45
+ },
46
+
47
+ watch: {
48
+ gid: {
49
+ immediate: true,
50
+ /**
51
+ * Ask for the name of a new id (cached, so once per id per page).
52
+ *
53
+ * @param {string} gid The group id.
54
+ */
55
+ handler(gid) {
56
+ loadGroupDisplayName(gid)
57
+ },
58
+ },
59
+ },
60
+ }
61
+ </script>
@@ -29,7 +29,7 @@
29
29
  :aria-pressed="String(Boolean(activeView && activeView.key === view.key))"
30
30
  :data-testid="`cn-chart-widget-view-${view.key}`"
31
31
  @click="activeViewKey = view.key">
32
- {{ view.label || view.key }}
32
+ {{ view.label ? hostTranslate(view.label) : view.key }}
33
33
  </button>
34
34
  </div>
35
35
  <div v-if="showEmptyState" class="cn-chart-widget__empty" data-testid="cn-chart-widget-empty">
@@ -53,7 +53,7 @@
53
53
  :height="computedHeight"
54
54
  :width="computedWidth"
55
55
  :options="mergedOptions"
56
- :series="displayedSeries" />
56
+ :series="plottedSeries" />
57
57
  </div>
58
58
  <div v-else class="cn-chart-widget__fallback">
59
59
  <!-- @slot Rendered when the ApexCharts peer dependency is not available (defaults to the unavailableLabel text). -->
@@ -213,6 +213,11 @@ export default {
213
213
  * always a ref to inject, even outside a dashboard.
214
214
  */
215
215
  cnDashboardDateRange: { default: () => ref(null) },
216
+ /**
217
+ * Host translate function provided by CnAppRoot: manifest-authored
218
+ * view labels and series names are i18n keys (screens-dashboard-i18n).
219
+ */
220
+ cnTranslate: { default: () => (key) => key },
216
221
  },
217
222
 
218
223
  props: {
@@ -918,6 +923,25 @@ export default {
918
923
  *
919
924
  * @return {Array} The displayed series.
920
925
  */
926
+ /**
927
+ * The displayed series with each series name through the host
928
+ * translate function, so a manifest series named "Revenue" reads in
929
+ * the user's language in the legend and the tooltip. Views still
930
+ * filter on the name as written (`displayedSeries`).
931
+ *
932
+ * @spec openspec/changes/screens-dashboard-i18n/specs/dashboard-page/spec.md#requirement-chart-view-labels-and-series-names-are-translated
933
+ * @return {Array} The series handed to ApexCharts.
934
+ */
935
+ plottedSeries() {
936
+ const series = this.displayedSeries
937
+ if (!Array.isArray(series)) {
938
+ return series
939
+ }
940
+ return series.map((entry) => (entry && typeof entry === 'object' && typeof entry.name === 'string' && entry.name !== '')
941
+ ? { ...entry, name: this.hostTranslate(entry.name) }
942
+ : entry)
943
+ },
944
+
921
945
  displayedSeries() {
922
946
  const series = this.resolvedSeries
923
947
  const view = this.activeView
@@ -1417,6 +1441,19 @@ export default {
1417
1441
  },
1418
1442
 
1419
1443
  methods: {
1444
+ /**
1445
+ * A manifest-authored label through the host translate function.
1446
+ *
1447
+ * @spec openspec/changes/screens-dashboard-i18n/specs/dashboard-page/spec.md#requirement-chart-view-labels-and-series-names-are-translated
1448
+ * @param {string} key The label.
1449
+ * @return {string} The translated label.
1450
+ */
1451
+ hostTranslate(key) {
1452
+ const translate = typeof this.cnTranslate === 'function' ? this.cnTranslate : null
1453
+ const out = translate ? translate(key) : key
1454
+ return (typeof out === 'string' && out !== '') ? out : key
1455
+ },
1456
+
1420
1457
  /**
1421
1458
  * Re-query the chart's dataSource / endpointSource. Exposed as a
1422
1459
  * ref-callable method (B3 canonical refresh mode) AND invoked by the