@conduction/nextcloud-vue 2.99.0 → 2.101.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (180) hide show
  1. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue.js +1 -1
  2. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js +6 -1
  3. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js.map +1 -1
  4. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js +2 -1
  5. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js.map +1 -1
  6. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue.js +1 -1
  7. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue2.js +22 -1
  8. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue2.js.map +1 -1
  9. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue3.js +26 -16
  10. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue3.js.map +1 -1
  11. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
  12. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +51 -7
  13. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
  14. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +104 -73
  15. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
  16. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +25 -8
  17. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  18. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +5 -3
  19. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  20. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue.js +1 -1
  21. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js +11 -8
  22. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
  23. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js +13 -4
  24. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
  25. package/dist/esm/components/CnExportsWidget/CnExportsWidget.vue.js +9 -0
  26. package/dist/esm/components/CnExportsWidget/CnExportsWidget.vue.js.map +1 -0
  27. package/dist/esm/components/CnExportsWidget/CnExportsWidget.vue2.js +139 -0
  28. package/dist/esm/components/CnExportsWidget/CnExportsWidget.vue2.js.map +1 -0
  29. package/dist/esm/components/CnExportsWidget/CnExportsWidget.vue3.js +76 -0
  30. package/dist/esm/components/CnExportsWidget/CnExportsWidget.vue3.js.map +1 -0
  31. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue.js +1 -1
  32. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js +54 -3
  33. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js.map +1 -1
  34. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js +16 -2
  35. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js.map +1 -1
  36. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue.js +1 -1
  37. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +160 -2
  38. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
  39. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +206 -94
  40. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
  41. package/dist/esm/components/CnFilesBrowser/filesReview.js +61 -0
  42. package/dist/esm/components/CnFilesBrowser/filesReview.js.map +1 -0
  43. package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue.js +1 -1
  44. package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue2.js +42 -7
  45. package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue2.js.map +1 -1
  46. package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue3.js +121 -57
  47. package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue3.js.map +1 -1
  48. package/dist/esm/components/CnFlowsPage/CnFlowEditorPage.vue2.js +10 -0
  49. package/dist/esm/components/CnFlowsPage/CnFlowEditorPage.vue2.js.map +1 -1
  50. package/dist/esm/components/CnFlowsPage/CnFlowEditorPage.vue3.js +2 -1
  51. package/dist/esm/components/CnFlowsPage/CnFlowEditorPage.vue3.js.map +1 -1
  52. package/dist/esm/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue.js +9 -0
  53. package/dist/esm/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue.js.map +1 -0
  54. package/dist/esm/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue2.js +279 -0
  55. package/dist/esm/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue2.js.map +1 -0
  56. package/dist/esm/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue3.js +122 -0
  57. package/dist/esm/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue3.js.map +1 -0
  58. package/dist/esm/components/CnGroupedCardsWidget/index.js +29 -0
  59. package/dist/esm/components/CnGroupedCardsWidget/index.js.map +1 -0
  60. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +387 -78
  61. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  62. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +317 -209
  63. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  64. package/dist/esm/components/CnIndexPage/useNamedSource.js +4 -1
  65. package/dist/esm/components/CnIndexPage/useNamedSource.js.map +1 -1
  66. package/dist/esm/components/CnMapWidget/CnMapWidget.vue.js +1 -1
  67. package/dist/esm/components/CnMapWidget/CnMapWidget.vue2.js +15 -0
  68. package/dist/esm/components/CnMapWidget/CnMapWidget.vue2.js.map +1 -1
  69. package/dist/esm/components/CnMapWidget/CnMapWidget.vue3.js.map +1 -1
  70. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue.js +1 -1
  71. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +14 -1
  72. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
  73. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js +14 -6
  74. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
  75. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue.js +1 -1
  76. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js +10 -0
  77. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
  78. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +6 -1
  79. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
  80. package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue.js +1 -1
  81. package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue2.js +91 -4
  82. package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue2.js.map +1 -1
  83. package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue3.js +63 -38
  84. package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue3.js.map +1 -1
  85. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js +1 -1
  86. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +68 -7
  87. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
  88. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js +256 -240
  89. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -1
  90. package/dist/esm/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue.js +9 -0
  91. package/dist/esm/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue.js.map +1 -0
  92. package/dist/esm/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue2.js +175 -0
  93. package/dist/esm/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue2.js.map +1 -0
  94. package/dist/esm/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue3.js +77 -0
  95. package/dist/esm/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue3.js.map +1 -0
  96. package/dist/esm/components/CnSettingsFieldsSection/CnSettingsFieldsSection.vue.js +1 -1
  97. package/dist/esm/components/CnSettingsFieldsSection/CnSettingsFieldsSection.vue2.js +38 -9
  98. package/dist/esm/components/CnSettingsFieldsSection/CnSettingsFieldsSection.vue2.js.map +1 -1
  99. package/dist/esm/components/CnSettingsFieldsSection/CnSettingsFieldsSection.vue3.js.map +1 -1
  100. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue.js +1 -1
  101. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js +51 -4
  102. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js.map +1 -1
  103. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js +40 -25
  104. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js.map +1 -1
  105. package/dist/esm/components/CnWidgetGrid/builtInWidgets.js +4 -0
  106. package/dist/esm/components/CnWidgetGrid/builtInWidgets.js.map +1 -1
  107. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +1 -0
  108. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
  109. package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue.js +1 -1
  110. package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue2.js +7 -7
  111. package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue2.js.map +1 -1
  112. package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue3.js.map +1 -1
  113. package/dist/esm/composables/indexSources.js +167 -20
  114. package/dist/esm/composables/indexSources.js.map +1 -1
  115. package/dist/esm/composables/useEndpointSource.js +28 -6
  116. package/dist/esm/composables/useEndpointSource.js.map +1 -1
  117. package/dist/esm/index.js +1 -0
  118. package/dist/esm/index.js.map +1 -1
  119. package/dist/esm/l10n/en.json.js +43 -1
  120. package/dist/esm/l10n/en.json.js.map +1 -1
  121. package/dist/esm/l10n/nl.json.js +41 -1
  122. package/dist/esm/l10n/nl.json.js.map +1 -1
  123. package/dist/esm/library-style.js +1 -1
  124. package/dist/esm/nextcloud-vue.css +1101 -396
  125. package/dist/esm/utils/languageMapText.js +50 -0
  126. package/dist/esm/utils/languageMapText.js.map +1 -0
  127. package/dist/esm/utils/libraryWidgetKeys.js +3 -0
  128. package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
  129. package/dist/esm/utils/mapList.js +71 -0
  130. package/dist/esm/utils/mapList.js.map +1 -0
  131. package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
  132. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  133. package/dist/nextcloud-vue.cjs.js +9996 -7378
  134. package/dist/nextcloud-vue.cjs.js.map +2 -2
  135. package/dist/nextcloud-vue.css +1101 -396
  136. package/l10n/en.json +43 -1
  137. package/l10n/nl.json +41 -1
  138. package/package.json +1 -1
  139. package/src/components/CnAdminSettingsShell/CnAdminSettingsShell.vue +7 -1
  140. package/src/components/CnChoiceCards/CnChoiceCards.md +39 -0
  141. package/src/components/CnChoiceCards/CnChoiceCards.vue +93 -16
  142. package/src/components/CnDashboardPage/CnDashboardPage.vue +89 -20
  143. package/src/components/CnDetailPage/CnDetailPage.vue +28 -9
  144. package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +13 -4
  145. package/src/components/CnExportsWidget/CnExportsWidget.vue +233 -0
  146. package/src/components/CnExportsWidget/index.js +7 -0
  147. package/src/components/CnFactsWidget/CnFactsWidget.vue +65 -3
  148. package/src/components/CnFilesBrowser/CnFilesBrowser.vue +324 -0
  149. package/src/components/CnFilesBrowser/filesReview.js +57 -0
  150. package/src/components/CnFlowsPage/CnFlowBoard.vue +77 -2
  151. package/src/components/CnFlowsPage/CnFlowEditorPage.vue +11 -0
  152. package/src/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue +433 -0
  153. package/src/components/CnGroupedCardsWidget/index.js +28 -0
  154. package/src/components/CnIndexPage/CnIndexPage.vue +376 -11
  155. package/src/components/CnIndexPage/useNamedSource.js +4 -1
  156. package/src/components/CnMapWidget/CnMapWidget.vue +37 -0
  157. package/src/components/CnObjectListWidget/CnObjectListWidget.vue +29 -1
  158. package/src/components/CnObjectSidebar/CnFilesTab.vue +15 -0
  159. package/src/components/CnOrderedListField/CnOrderedListField.vue +151 -5
  160. package/src/components/CnRecordListWidget/CnRecordListWidget.vue +114 -7
  161. package/src/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue +272 -0
  162. package/src/components/CnSavedViewAlertWidget/index.js +7 -0
  163. package/src/components/CnSettingsFieldsSection/CnSettingsFieldsSection.vue +38 -9
  164. package/src/components/CnSetupWizard/CnSetupWizard.vue +72 -5
  165. package/src/components/CnWidgetGrid/builtInWidgets.js +4 -0
  166. package/src/components/CnWidgetGrid/registerDashboardWidgets.js +1 -0
  167. package/src/components/CnWizardDialog/CnWizardDialog.vue +7 -7
  168. package/src/components/index.js +3 -0
  169. package/src/composables/indexSources.js +166 -19
  170. package/src/composables/useEndpointSource.js +28 -6
  171. package/src/css/detail-page.css +15 -0
  172. package/src/css/dialog.css +48 -0
  173. package/src/css/index-page.css +117 -0
  174. package/src/css/look-board.css +127 -1
  175. package/src/index.js +1 -0
  176. package/src/schemas/app-manifest-v2.schema.json +55 -1
  177. package/src/utils/languageMapText.js +46 -0
  178. package/src/utils/libraryWidgetKeys.js +3 -0
  179. package/src/utils/mapList.js +67 -0
  180. package/src/utils/validateManifestV2.compiled.js +3 -3
@@ -99,6 +99,50 @@
99
99
  padding-inline-start: 56px;
100
100
  }
101
101
 
102
+ /* The dashboard header as the board draws it (DqTermijnen): the actions on
103
+ the title's row, the description and its segmented period under both, at
104
+ the header's full width. As two flex columns the description sat in the
105
+ title's column only, so a long period group wrapped under its text and the
106
+ actions dropped to a line of their own. The title column never shrinks
107
+ below its longest word; the actions wrap their buttons first.
108
+ @spec openspec/changes/dashboard-board-header-row/specs/dashboard-page/spec.md#requirement-the-board-dashboard-header-keeps-its-actions-on-the-title-row */
109
+ .cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-dashboard-page__header {
110
+ display: grid;
111
+ grid-template-columns: minmax(min-content, 1fr) auto;
112
+ align-items: start;
113
+ column-gap: 16px;
114
+ row-gap: 0;
115
+ }
116
+
117
+ .cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-dashboard-page__header-left {
118
+ display: contents;
119
+ }
120
+
121
+ .cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-dashboard-page__title-row {
122
+ grid-column: 1;
123
+ grid-row: 1;
124
+ align-self: center;
125
+ }
126
+
127
+ .cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-dashboard-page__header-left > :not(.cn-dashboard-page__title-row) {
128
+ grid-column: 1 / -1;
129
+ }
130
+
131
+ .cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-dashboard-page__header > .cn-dashboard-page__header-actions {
132
+ grid-column: 2;
133
+ grid-row: 1;
134
+ justify-content: flex-end;
135
+ margin-inline-start: 0;
136
+ }
137
+
138
+ /* A stat tile in the board's KPI row carries the board's own 18px 20px inset
139
+ (`cn-kpi-card--board`), so the card-fit cell adds none of its own: with
140
+ both the label sat 29px from the tile's top and 37px from its side, and
141
+ the row stood about 1.4 times the board's height. */
142
+ .cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-dashboard-page__kpi-cell .cn-dashboard-page__card-fit .cn-widget-wrapper__content:has(> .cn-kpi-card--board.cn-kpi-card--stacked) {
143
+ padding: 0;
144
+ }
145
+
102
146
  /* ---- The header button ------------------------------------------------- */
103
147
  .cn-look-board .cn-index-page__header-buttons:not(.cn-look-nextcloud *),
104
148
  .cn-look-board .cn-detail-page__header-actions:not(.cn-look-nextcloud *),
@@ -302,6 +346,40 @@
302
346
  color: inherit;
303
347
  }
304
348
 
349
+ /* A caption's edit pencil ("Mijn zaaktypen") sits on the caption's line at
350
+ its end, 4px in: a 28px round muted button with a 16px icon that adds no
351
+ height (DqZijbalk). It is placed absolutely because a theme may force the
352
+ caption to `display: block` at id strength (thematiq's Zuiddrecht sheet),
353
+ which put the pencil on a line of its own. */
354
+ .cn-look-board .app-navigation[data-testid="cn-nav"] .app-navigation-caption:has(.app-navigation-caption__actions) {
355
+ position: relative;
356
+ padding-inline-end: 36px;
357
+ }
358
+
359
+ .cn-look-board .app-navigation[data-testid="cn-nav"] .app-navigation-caption .app-navigation-caption__actions {
360
+ --default-clickable-area: 28px;
361
+ position: absolute;
362
+ inset-block-start: -6px;
363
+ inset-inline-end: 4px;
364
+ margin: 0;
365
+ }
366
+
367
+ .cn-look-board .app-navigation[data-testid="cn-nav"] .app-navigation-caption .app-navigation-caption__actions .button-vue {
368
+ --nldesign-color-on-surface: var(--color-text-maxcontrast);
369
+ width: 28px;
370
+ min-width: 28px;
371
+ height: 28px;
372
+ min-height: 28px;
373
+ padding: 0;
374
+ border-radius: 14px;
375
+ color: var(--color-text-maxcontrast);
376
+ }
377
+
378
+ .cn-look-board .app-navigation[data-testid="cn-nav"] .app-navigation-caption .app-navigation-caption__actions .button-vue svg {
379
+ width: 16px;
380
+ height: 16px;
381
+ }
382
+
305
383
  .cn-look-board .app-navigation[data-testid="cn-nav"] .app-navigation-entry,
306
384
  .cn-look-board .app-navigation[data-testid="cn-nav"] .app-navigation-entry-link {
307
385
  min-height: 42px;
@@ -316,7 +394,15 @@
316
394
  color: var(--color-main-text);
317
395
  }
318
396
 
397
+ /* The open entry sits on the tonal primary. NcAppNavigationItem paints an
398
+ active entry with a 16% (22% hovered) primary mix at `!important`, so the
399
+ board value needs `!important` too; the selector is the more specific one.
400
+ The link carries the same ground. */
319
401
  .cn-look-board .app-navigation[data-testid="cn-nav"] .app-navigation-entry.active,
402
+ .cn-look-board .app-navigation[data-testid="cn-nav"] .app-navigation-entry.active:hover {
403
+ background-color: var(--color-primary-element-light) !important;
404
+ }
405
+
320
406
  .cn-look-board .app-navigation[data-testid="cn-nav"] .app-navigation-entry.active .app-navigation-entry-link {
321
407
  background: var(--color-primary-element-light);
322
408
  color: var(--color-primary-element-light-text);
@@ -362,9 +448,11 @@
362
448
  font-weight: 700;
363
449
  }
364
450
 
451
+ /* White on the error colour: --color-error-text is the error colour for text on
452
+ a light ground (#8A0000 under thematiq), not the text on an error pill. */
365
453
  .cn-look-board .cn-app-nav__count--attention {
366
454
  background: var(--color-error);
367
- color: var(--color-error-text, var(--color-primary-element-text));
455
+ color: var(--color-primary-element-text);
368
456
  }
369
457
 
370
458
  /* The footer: Help, then Advanced, whatever the declared order. */
@@ -951,6 +1039,44 @@
951
1039
  --nldesign-color-on-surface: var(--cn-status-badge-ink);
952
1040
  }
953
1041
 
1042
+ /* ---- Info notes (NcNoteCard type="info") -------------------------------- */
1043
+ /* The board's info note is a light-blue rounded box: no edge bar, 12px 14px
1044
+ of padding, radius 8, an 18px icon 10px before the text, 14px text in the
1045
+ deep primary ink at 1.5, and a bold heading line (DqBesluit and 13 other
1046
+ boards). NcNoteCard paints its ground and ink with `!important`, and a
1047
+ theme forces the body ink onto every p and span (thematiq), so the ground,
1048
+ the ink and the paragraph colour carry `!important` here too, scoped to
1049
+ the board look; the ink also goes to --nldesign-color-on-surface. */
1050
+ .cn-look-board .notecard.notecard--info:not(.cn-look-nextcloud *) {
1051
+ --nldesign-color-on-surface: var(--color-primary-element-light-text);
1052
+ --note-card-icon-size: 18px;
1053
+ --note-theme: var(--color-primary-element-light-text);
1054
+ gap: 10px;
1055
+ padding: 12px 14px;
1056
+ border-inline-start: 0;
1057
+ border-radius: 8px;
1058
+ background-color: var(--color-primary-element-light) !important;
1059
+ color: var(--color-primary-element-light-text) !important;
1060
+ font-size: 14px;
1061
+ line-height: 1.5;
1062
+ }
1063
+
1064
+ .cn-look-board .notecard.notecard--info:not(.cn-look-nextcloud *) p {
1065
+ color: var(--color-primary-element-light-text) !important;
1066
+ font-size: inherit;
1067
+ line-height: inherit;
1068
+ }
1069
+
1070
+ .cn-look-board .notecard.notecard--info:not(.cn-look-nextcloud *) .notecard__heading {
1071
+ font-size: inherit;
1072
+ font-weight: 700 !important;
1073
+ }
1074
+
1075
+ .cn-look-board .notecard.notecard--info:not(.cn-look-nextcloud *) .notecard__icon {
1076
+ padding-top: 1px;
1077
+ color: var(--color-primary-element-light-text);
1078
+ }
1079
+
954
1080
  /* ---- Date and age cells (screens-cell-date-parity) ---------------------- */
955
1081
  /* The board sets every waiting time ("3 dagen", "4 uur") in weight 600; a
956
1082
  late one is red through its variant class. */
package/src/index.js CHANGED
@@ -158,6 +158,7 @@ export {
158
158
  CnGaugeWidget,
159
159
  CnGaugeWidgetForm,
160
160
  CnGraphCanvas,
161
+ CnGroupedCardsWidget,
161
162
  CnGuardianHome,
162
163
  CnHeaderWidget,
163
164
  CnHeaderWidgetForm,
@@ -3,7 +3,7 @@
3
3
  "$id": "https://raw.githubusercontent.com/ConductionNL/nextcloud-vue/main/src/schemas/app-manifest-v2.schema.json",
4
4
  "title": "Conduction App Manifest v2",
5
5
  "description": "v2 schema for the JSON-driven page and navigation manifest consumed by @conduction/nextcloud-vue. Introduces a uniform widgets[] array on every page type with a per-slot grid coordinate system, a typed actions[] discriminator, and the required $schema field for version detection. v1 manifests continue to validate against app-manifest.schema.json (unchanged).",
6
- "version": "2.122.0",
6
+ "version": "2.124.0",
7
7
  "type": "object",
8
8
  "required": ["$schema", "version"],
9
9
  "allOf": [
@@ -97,6 +97,9 @@
97
97
  "type": { "type": "string", "enum": ["info", "config-fields", "choice", "run-action", "summary", "component"], "description": "Built-in step renderer." },
98
98
  "title": { "type": "string" },
99
99
  "body": { "type": "string", "description": "info: explanatory copy." },
100
+ "note": { "type": "string", "minLength": 1, "description": "choice: a footnote under the options, translated like body." },
101
+ "visibleWhen": { "description": "Offer the step only when the setup status says so: a dotted path into the setup status document whose value must be truthy, or { path, equals } for an exact value. Hidden while the status loads.", "oneOf": [ { "type": "string", "minLength": 1 }, { "type": "object", "required": ["path"], "additionalProperties": false, "properties": { "path": { "type": "string", "minLength": 1 }, "equals": {} } } ] },
102
+ "footnote": { "type": "string", "minLength": 1, "description": "choice: the same footnote under another name; note wins when both are set." },
100
103
  "required": { "type": "boolean", "default": false, "description": "When true the step gates the app until done." },
101
104
  "schema": { "description": "config-fields: a JSON Schema ref (string) or inline object rendered via fieldsFromSchema." },
102
105
  "configKey": { "type": "string", "description": "choice: app-config key the selection is written to." },
@@ -4024,6 +4027,14 @@
4024
4027
  "type": "boolean",
4025
4028
  "description": "false removes Request a feature, Report a bug and Documentation, for an app that offers them in its own help menu."
4026
4029
  },
4030
+ "showMarkUnread": {
4031
+ "type": "boolean",
4032
+ "description": "false removes the built-in Mark as unread entry."
4033
+ },
4034
+ "showOpenSidebar": {
4035
+ "type": "boolean",
4036
+ "description": "false removes the built-in Open sidebar entry of the board look."
4037
+ },
4027
4038
  "label": {
4028
4039
  "type": "string",
4029
4040
  "minLength": 1,
@@ -4333,6 +4344,20 @@
4333
4344
  "minLength": 1,
4334
4345
  "description": "type='detail', board look: the meta line on row 2 of the header, after the breadcrumb and a middle dot (screens-detail-page-parity). A field template such as `\"via {channel}\"`: every `{path}` is read from the record, and a line with an empty value is dropped. Maps to CnDetailPage `headerMeta`. type='index' | 'dashboard' | 'settings' (page-header-breadcrumb): the note after the breadcrumb and a middle dot under the title; on an index page `{total}` and `{shown}` are the list's counts."
4335
4346
  },
4347
+ "headerMetaCounts": {
4348
+ "type": "object",
4349
+ "description": "type='index': the other lists headerMeta counts, by name. Each is read as one list total and fills `{count:<name>}` in headerMeta (\"{total} contracten bij {count:suppliers} leveranciers\"); while a count loads or when it fails, the note is left out.",
4350
+ "additionalProperties": {
4351
+ "type": "object",
4352
+ "required": ["register", "schema"],
4353
+ "additionalProperties": false,
4354
+ "properties": {
4355
+ "register": { "type": "string", "minLength": 1 },
4356
+ "schema": { "type": "string", "minLength": 1 },
4357
+ "filter": { "type": "object" }
4358
+ }
4359
+ }
4360
+ },
4336
4361
  "tabsLabel": {
4337
4362
  "type": "string",
4338
4363
  "minLength": 1,
@@ -4348,6 +4373,35 @@
4348
4373
  "default": true,
4349
4374
  "description": "type='dashboard' and type='detail': `false` drops the overflow Actions menu from every widget that does not set `showActions` itself, for a dashboard whose widget headers carry a header link and nothing else (zuiddrecht-pixel-gaps-2), or a record page whose data cards carry none (zuiddrecht-pixel-gaps-3; a card with an Add action keeps it). When the key is not set a detail page keeps the menu, except under the board look, where side cards carry none (screens-detail-page-parity). Maps to CnDashboardPage / CnDetailPage `showWidgetActions`."
4350
4375
  },
4376
+ "pageFilters": {
4377
+ "type": "array",
4378
+ "description": "type='dashboard': page-level filter controls in the header. Each selection is written into the page's workspace context, so a widget reads it through a `@page.<key>` / `@workspace.<key>` token (an endpoint widget also in its `responsePath`). Maps to the CnDashboardPage `pageFilters` prop.",
4379
+ "items": {
4380
+ "type": "object",
4381
+ "required": ["key", "options"],
4382
+ "additionalProperties": false,
4383
+ "properties": {
4384
+ "key": { "type": "string", "minLength": 1, "description": "Workspace-context key written on change (the `@page.<key>` / `@workspace.<key>` token name)." },
4385
+ "label": { "type": "string", "description": "Control label (i18n key or plain text)." },
4386
+ "type": { "type": "string", "enum": ["select"], "default": "select", "description": "Control type, carried over from v1." },
4387
+ "control": { "type": "string", "enum": ["select", "segmented"], "default": "select", "description": "How the filter is drawn: a select, or a compact segmented group." },
4388
+ "placement": { "type": "string", "enum": ["row", "description"], "default": "row", "description": "Where the filter sits: in the header's filter row, or (a segmented group) on the description line after the period group (DqProcesanalyse)." },
4389
+ "default": { "type": ["string", "number"], "description": "Initial value seeded into the context on mount; the first option's value when omitted." },
4390
+ "options": {
4391
+ "type": "array",
4392
+ "items": {
4393
+ "type": "object",
4394
+ "required": ["value", "label"],
4395
+ "additionalProperties": false,
4396
+ "properties": {
4397
+ "value": { "type": ["string", "number"], "description": "Value written into the context when picked." },
4398
+ "label": { "type": "string", "description": "Display text (i18n key or plain text)." }
4399
+ }
4400
+ }
4401
+ }
4402
+ }
4403
+ }
4404
+ },
4351
4405
  "kpiRow": {
4352
4406
  "type": "array",
4353
4407
  "items": { "type": "string" },
@@ -0,0 +1,46 @@
1
+ /**
2
+ * SPDX-FileCopyrightText: 2026 Conduction B.V.
3
+ * SPDX-License-Identifier: EUPL-1.2
4
+ */
5
+
6
+ import { getLanguage } from '@nextcloud/l10n'
7
+
8
+ /**
9
+ * The user's Nextcloud language as a bare code (`nl_NL` reads `nl`), or ''.
10
+ *
11
+ * @return {string} The code.
12
+ */
13
+ function userLanguage() {
14
+ try {
15
+ return String(getLanguage() || '').split(/[-_]/)[0].toLowerCase()
16
+ } catch {
17
+ return ''
18
+ }
19
+ }
20
+
21
+ /**
22
+ * The text of a value that may be a language map (`{ nl: 'Ontvangst', en:
23
+ * 'Intake' }`), in the user's Nextcloud language, then Dutch, then English,
24
+ * then the first text it holds. A plain value comes back as text; anything
25
+ * else as ''.
26
+ *
27
+ * @param {(string|number|object|null|undefined)} value The value.
28
+ * @return {string} The text.
29
+ * @spec openspec/changes/grouped-cards-widget/specs/grouped-cards-widget/spec.md#requirement-a-widget-draws-groups-of-cards-from-an-endpoint
30
+ */
31
+ export function languageMapText(value) {
32
+ if (typeof value === 'string' || typeof value === 'number') {
33
+ return String(value)
34
+ }
35
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
36
+ return ''
37
+ }
38
+ const language = userLanguage()
39
+ for (const key of [language, 'nl', 'en']) {
40
+ if (key && typeof value[key] === 'string' && value[key] !== '') {
41
+ return value[key]
42
+ }
43
+ }
44
+ const first = Object.values(value).find((text) => typeof text === 'string' && text !== '')
45
+ return first || ''
46
+ }
@@ -71,6 +71,8 @@ export const BUILT_IN_WIDGET_KEYS = Object.freeze([
71
71
  'header',
72
72
  'text',
73
73
  'divider',
74
+ 'saved-view-alert',
75
+ 'exports',
74
76
  ])
75
77
 
76
78
  /**
@@ -109,6 +111,7 @@ export const DASHBOARD_CATALOG_WIDGET_KEYS = Object.freeze([
109
111
  'files',
110
112
  'flow-runs',
111
113
  'gauge',
114
+ 'grouped-cards',
112
115
  'header',
113
116
  'image',
114
117
  'interaction-form',
@@ -0,0 +1,67 @@
1
+ /**
2
+ * SPDX-FileCopyrightText: 2026 Conduction B.V.
3
+ * SPDX-License-Identifier: EUPL-1.2
4
+ */
5
+ import { isBadgeVariant } from './badgeVariants.js'
6
+ import { fillTemplate } from './fillTemplate.js'
7
+ import { readPath } from './readPath.js'
8
+
9
+ /**
10
+ * The numbered list beside an index map (DqZakenKaart): one entry per row
11
+ * that has a place on the map, numbered in list order so the number on the
12
+ * pin and the number in the list are the same, and the rows without one.
13
+ *
14
+ * `spec` is `mapConfig.list`: `{ title, sub?, pills?: [{ path?, text?, map?,
15
+ * colorMap? }], due? }`. `title`, `sub` and `due` are field templates
16
+ * (`"{title}"`, `"{address} · {identifier}"`, `"{deadline|date}"`), already
17
+ * translated by the caller; a pill reads `path` (or shows `text`), maps it
18
+ * through `map`, and takes its tone from `colorMap` by the raw value.
19
+ *
20
+ * @param {Array<object>} rows The rows, in display order.
21
+ * @param {object} spec The list declaration.
22
+ * @param {object} options How to read a row.
23
+ * @param {string} options.rowKey The row's key field.
24
+ * @param {Function} options.located Whether a row has a place on the map.
25
+ * @param {Function} [options.translate] The host's translate, for pill labels.
26
+ * @return {{items: Array<{key: string, n: number, row: object, title: string, sub: string, due: string, pills: Array<{label: string, variant: string}>}>, missing: Array<object>}}
27
+ */
28
+ export function mapListItems(rows, spec, { rowKey, located, translate = (text) => text }) {
29
+ const items = []
30
+ const missing = []
31
+ const list = spec && typeof spec === 'object' ? spec : {}
32
+ for (const row of Array.isArray(rows) ? rows : []) {
33
+ if (!located(row)) {
34
+ missing.push(row)
35
+ continue
36
+ }
37
+ const pills = (Array.isArray(list.pills) ? list.pills : [])
38
+ .map((pill) => {
39
+ if (!pill || typeof pill !== 'object') {
40
+ return null
41
+ }
42
+ const raw = typeof pill.path === 'string' && pill.path !== '' ? readPath(row, pill.path) : undefined
43
+ const mapped = pill.map && (typeof raw === 'string' || typeof raw === 'number') && Object.hasOwn(pill.map, raw) ? pill.map[raw] : undefined
44
+ const text = mapped ?? pill.text ?? raw
45
+ if (typeof text !== 'string' && typeof text !== 'number') {
46
+ return null
47
+ }
48
+ const label = translate(String(text))
49
+ if (label === '') {
50
+ return null
51
+ }
52
+ const tone = pill.colorMap && (typeof raw === 'string' || typeof raw === 'number') ? pill.colorMap[raw] : pill.variant
53
+ return { label, variant: isBadgeVariant(tone) ? tone : 'default' }
54
+ })
55
+ .filter(Boolean)
56
+ items.push({
57
+ key: String(row[rowKey]),
58
+ n: items.length + 1,
59
+ row,
60
+ title: fillTemplate(list.title || '', row),
61
+ sub: fillTemplate(list.sub || '', row),
62
+ due: fillTemplate(list.due || '', row),
63
+ pills,
64
+ })
65
+ }
66
+ return { items, missing }
67
+ }