@conduction/nextcloud-vue 2.104.0 → 2.106.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 (224) hide show
  1. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue.js +1 -1
  2. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js.map +1 -1
  3. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js +1 -1
  4. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js.map +1 -1
  5. package/dist/esm/components/CnCardGrid/CnCardGrid.vue.js +1 -1
  6. package/dist/esm/components/CnCardGrid/CnCardGrid.vue2.js +18 -0
  7. package/dist/esm/components/CnCardGrid/CnCardGrid.vue2.js.map +1 -1
  8. package/dist/esm/components/CnCardGrid/CnCardGrid.vue3.js +3 -1
  9. package/dist/esm/components/CnCardGrid/CnCardGrid.vue3.js.map +1 -1
  10. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue.js +1 -1
  11. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +18 -0
  12. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
  13. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +1 -1
  14. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
  15. package/dist/esm/components/CnChartWidget/CnChartWidget.vue.js +1 -1
  16. package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js +93 -1
  17. package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js.map +1 -1
  18. package/dist/esm/components/CnChartWidget/CnChartWidget.vue3.js.map +1 -1
  19. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
  20. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +6 -3
  21. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
  22. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +3 -3
  23. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
  24. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +112 -5
  25. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  26. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  27. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue.js +9 -0
  28. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue.js.map +1 -0
  29. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue2.js +24 -0
  30. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue2.js.map +1 -0
  31. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue3.js +49 -0
  32. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue3.js.map +1 -0
  33. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue.js +1 -1
  34. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js +36 -18
  35. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
  36. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js +17 -38
  37. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
  38. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue.js +1 -1
  39. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js.map +1 -1
  40. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js.map +1 -1
  41. package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue.js +1 -1
  42. package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue2.js +1 -1
  43. package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue2.js.map +1 -1
  44. package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue3.js.map +1 -1
  45. package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue.js +1 -1
  46. package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue2.js +2 -0
  47. package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue2.js.map +1 -1
  48. package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue3.js +32 -29
  49. package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue3.js.map +1 -1
  50. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue.js +1 -1
  51. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +53 -2
  52. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
  53. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +119 -111
  54. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
  55. package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue.js +1 -1
  56. package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue2.js +62 -1
  57. package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue2.js.map +1 -1
  58. package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue3.js.map +1 -1
  59. package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue.js +1 -1
  60. package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue2.js +73 -4
  61. package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue2.js.map +1 -1
  62. package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue3.js +74 -27
  63. package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue3.js.map +1 -1
  64. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +208 -46
  65. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  66. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +121 -98
  67. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  68. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue.js +1 -1
  69. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue2.js +24 -0
  70. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue2.js.map +1 -1
  71. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue3.js +20 -4
  72. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue3.js.map +1 -1
  73. package/dist/esm/components/CnIndexPage/useSelfFetchList.js +25 -6
  74. package/dist/esm/components/CnIndexPage/useSelfFetchList.js.map +1 -1
  75. package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue2.js +112 -1
  76. package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue2.js.map +1 -1
  77. package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue3.js +100 -44
  78. package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue3.js.map +1 -1
  79. package/dist/esm/components/CnNcWidgetWidget/CnNcWidgetWidget.vue.js +1 -1
  80. package/dist/esm/components/CnNcWidgetWidget/CnNcWidgetWidget.vue2.js +67 -4
  81. package/dist/esm/components/CnNcWidgetWidget/CnNcWidgetWidget.vue2.js.map +1 -1
  82. package/dist/esm/components/CnNcWidgetWidget/CnNcWidgetWidget.vue3.js +1 -1
  83. package/dist/esm/components/CnNcWidgetWidget/CnNcWidgetWidget.vue3.js.map +1 -1
  84. package/dist/esm/components/CnNoticeWidget/CnNoticeWidget.vue.js +9 -0
  85. package/dist/esm/components/CnNoticeWidget/CnNoticeWidget.vue.js.map +1 -0
  86. package/dist/esm/components/CnNoticeWidget/CnNoticeWidget.vue2.js +88 -0
  87. package/dist/esm/components/CnNoticeWidget/CnNoticeWidget.vue2.js.map +1 -0
  88. package/dist/esm/components/CnNoticeWidget/CnNoticeWidget.vue3.js +39 -0
  89. package/dist/esm/components/CnNoticeWidget/CnNoticeWidget.vue3.js.map +1 -0
  90. package/dist/esm/components/CnObjectCard/CnObjectCard.vue.js +1 -1
  91. package/dist/esm/components/CnObjectCard/CnObjectCard.vue2.js +295 -12
  92. package/dist/esm/components/CnObjectCard/CnObjectCard.vue2.js.map +1 -1
  93. package/dist/esm/components/CnObjectCard/CnObjectCard.vue3.js +267 -144
  94. package/dist/esm/components/CnObjectCard/CnObjectCard.vue3.js.map +1 -1
  95. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue.js +1 -1
  96. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js +4 -0
  97. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
  98. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +3 -1
  99. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
  100. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js +1 -1
  101. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +93 -1
  102. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
  103. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js +322 -246
  104. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -1
  105. package/dist/esm/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue.js +1 -1
  106. package/dist/esm/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue2.js +122 -7
  107. package/dist/esm/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue2.js.map +1 -1
  108. package/dist/esm/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue3.js +189 -93
  109. package/dist/esm/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue3.js.map +1 -1
  110. package/dist/esm/components/CnSettingsField/CnSettingsField.vue.js +1 -1
  111. package/dist/esm/components/CnSettingsField/CnSettingsField.vue2.js.map +1 -1
  112. package/dist/esm/components/CnSettingsField/CnSettingsField.vue3.js +3 -3
  113. package/dist/esm/components/CnSettingsField/CnSettingsField.vue3.js.map +1 -1
  114. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue.js +1 -1
  115. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js.map +1 -1
  116. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js +16 -4
  117. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js.map +1 -1
  118. package/dist/esm/components/CnTabs/CnTabs.vue.js +1 -1
  119. package/dist/esm/components/CnTabs/CnTabs.vue2.js.map +1 -1
  120. package/dist/esm/components/CnTabs/CnTabs.vue3.js.map +1 -1
  121. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue.js +1 -1
  122. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +42 -0
  123. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
  124. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js +38 -19
  125. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
  126. package/dist/esm/components/CnTeamWorkloadWidget/CnTeamWorkloadWidget.vue.js +9 -0
  127. package/dist/esm/components/CnTeamWorkloadWidget/CnTeamWorkloadWidget.vue.js.map +1 -0
  128. package/dist/esm/components/CnTeamWorkloadWidget/CnTeamWorkloadWidget.vue2.js +258 -0
  129. package/dist/esm/components/CnTeamWorkloadWidget/CnTeamWorkloadWidget.vue2.js.map +1 -0
  130. package/dist/esm/components/CnTeamWorkloadWidget/CnTeamWorkloadWidget.vue3.js +69 -0
  131. package/dist/esm/components/CnTeamWorkloadWidget/CnTeamWorkloadWidget.vue3.js.map +1 -0
  132. package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue.js +1 -1
  133. package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue2.js +11 -0
  134. package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue2.js.map +1 -1
  135. package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue3.js +4 -4
  136. package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue3.js.map +1 -1
  137. package/dist/esm/components/CnWidgetGrid/builtInWidgets.js +16 -14
  138. package/dist/esm/components/CnWidgetGrid/builtInWidgets.js.map +1 -1
  139. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +12 -0
  140. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
  141. package/dist/esm/index.js +2 -0
  142. package/dist/esm/index.js.map +1 -1
  143. package/dist/esm/l10n/en.json.js +24 -1
  144. package/dist/esm/l10n/en.json.js.map +1 -1
  145. package/dist/esm/l10n/nl.json.js +24 -1
  146. package/dist/esm/l10n/nl.json.js.map +1 -1
  147. package/dist/esm/library-style.js +1 -1
  148. package/dist/esm/nextcloud-vue.css +1369 -748
  149. package/dist/esm/utils/dueText.js +18 -3
  150. package/dist/esm/utils/dueText.js.map +1 -1
  151. package/dist/esm/utils/libraryWidgetKeys.js +2 -0
  152. package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
  153. package/dist/esm/utils/nextcloudApps.js +45 -0
  154. package/dist/esm/utils/nextcloudApps.js.map +1 -0
  155. package/dist/esm/utils/scopeListLayout.js +9 -2
  156. package/dist/esm/utils/scopeListLayout.js.map +1 -1
  157. package/dist/esm/utils/sortOrder.js +11 -1
  158. package/dist/esm/utils/sortOrder.js.map +1 -1
  159. package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
  160. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  161. package/dist/esm/utils/visibleWhen.js +12 -4
  162. package/dist/esm/utils/visibleWhen.js.map +1 -1
  163. package/dist/nextcloud-vue.cjs.js +8965 -6497
  164. package/dist/nextcloud-vue.cjs.js.map +2 -2
  165. package/dist/nextcloud-vue.css +1369 -748
  166. package/l10n/en.json +24 -1
  167. package/l10n/nl.json +24 -1
  168. package/package.json +1 -1
  169. package/src/components/CnAdminSettingsShell/CnAdminSettingsShell.vue +1 -1
  170. package/src/components/CnCardGrid/CnCardGrid.md +2 -0
  171. package/src/components/CnCardGrid/CnCardGrid.vue +20 -0
  172. package/src/components/CnCellRenderer/CnCellRenderer.vue +19 -1
  173. package/src/components/CnChartWidget/CnChartWidget.md +2 -0
  174. package/src/components/CnChartWidget/CnChartWidget.vue +93 -1
  175. package/src/components/CnDashboardPage/CnDashboardPage.vue +9 -6
  176. package/src/components/CnDetailPage/CnDetailPage.md +3 -2
  177. package/src/components/CnDetailPage/CnDetailPage.vue +112 -5
  178. package/src/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue +60 -0
  179. package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +36 -29
  180. package/src/components/CnFactsWidget/CnFactsWidget.vue +4 -0
  181. package/src/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue +1 -1
  182. package/src/components/CnFeaturesBoard/CnFeaturesBoard.vue +4 -1
  183. package/src/components/CnFilesBrowser/CnFilesBrowser.vue +70 -4
  184. package/src/components/CnFlowRunsWidget/CnFlowRunsWidget.vue +62 -1
  185. package/src/components/CnFlowsPage/CnFlowBoard.vue +132 -8
  186. package/src/components/CnIndexPage/CnIndexPage.md +8 -5
  187. package/src/components/CnIndexPage/CnIndexPage.vue +236 -54
  188. package/src/components/CnIndexPage/CnIndexPageWidgets.vue +36 -1
  189. package/src/components/CnIndexPage/useSelfFetchList.js +25 -6
  190. package/src/components/CnIndexSidebar/CnIndexSidebar.md +2 -0
  191. package/src/components/CnIndexSidebar/CnIndexSidebar.vue +148 -1
  192. package/src/components/CnNcWidgetWidget/CnNcWidgetWidget.vue +68 -5
  193. package/src/components/CnNoticeWidget/CnNoticeWidget.vue +153 -0
  194. package/src/components/CnNoticeWidget/index.js +12 -0
  195. package/src/components/CnObjectCard/CnObjectCard.md +1 -0
  196. package/src/components/CnObjectCard/CnObjectCard.vue +354 -13
  197. package/src/components/CnObjectSidebar/CnFilesTab.vue +6 -0
  198. package/src/components/CnRecordListWidget/CnRecordListWidget.vue +190 -4
  199. package/src/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue +209 -9
  200. package/src/components/CnSettingsField/CnSettingsField.vue +4 -1
  201. package/src/components/CnSetupWizard/CnSetupWizard.vue +9 -1
  202. package/src/components/CnTabs/CnTabs.vue +11 -0
  203. package/src/components/CnTabsWidget/CnTabsWidget.vue +91 -18
  204. package/src/components/CnTeamWorkloadWidget/CnTeamWorkloadWidget.md +11 -0
  205. package/src/components/CnTeamWorkloadWidget/CnTeamWorkloadWidget.vue +382 -0
  206. package/src/components/CnTeamWorkloadWidget/index.js +14 -0
  207. package/src/components/CnVersionInfoCard/CnVersionInfoCard.vue +15 -4
  208. package/src/components/CnWidgetGrid/builtInWidgets.js +2 -0
  209. package/src/components/CnWidgetGrid/registerDashboardWidgets.js +12 -0
  210. package/src/components/index.js +2 -0
  211. package/src/css/dialog.css +20 -2
  212. package/src/css/index-page.css +14 -2
  213. package/src/css/look-board-detail.css +125 -8
  214. package/src/css/look-board-index.css +100 -4
  215. package/src/css/look-board.css +122 -14
  216. package/src/index.js +2 -1
  217. package/src/schemas/app-manifest-v2.schema.json +110 -8
  218. package/src/utils/dueText.js +18 -3
  219. package/src/utils/libraryWidgetKeys.js +2 -0
  220. package/src/utils/nextcloudApps.js +50 -0
  221. package/src/utils/scopeListLayout.js +9 -2
  222. package/src/utils/sortOrder.js +10 -0
  223. package/src/utils/validateManifestV2.compiled.js +3 -3
  224. package/src/utils/visibleWhen.js +12 -4
@@ -390,6 +390,7 @@ export default {
390
390
  | `showViewToggle` | Boolean | `true` | Whether to show the Cards/Table view toggle |
391
391
  | `inlineSearch` | Boolean | `false` | Show an inline search field in the actions bar (in addition to / instead of the sidebar search). Fed from the manifest as `pages[].config.inlineSearch`. The field takes the place of the "Showing X of Y" counter unless `showCountWithSearch` is set. |
392
392
  | `search` | Boolean | `true` | `false` draws no inline search in the actions bar, also in the board look, which otherwise draws one on every index page (manifest: `config.search`). Wins over `inlineSearch`. |
393
+ | `appConfig` | Object | `{}` | The app config `@config.<key>` tokens resolve against (manifest: `config.appConfig`), as on the dashboard and detail pages, merged over a config an ancestor provides on `cnAppConfig`. A quick filter's `filter` and its `label` / `activeLabel` take the token: `"Residence @config.municipality"` reads "Residence Zuiddrecht" (board DqContacten); the label is translated first, and a key the config does not hold leaves the token as written. |
393
394
  | `showCountWithSearch` | Boolean | `false` | Keep the "Showing X of Y" counter visible beside the inline search field, after the search and any `#after-search` controls (only relevant with `inlineSearch`, which otherwise takes the counter's place). Fed from the manifest as `pages[].config.showCountWithSearch`. |
394
395
  | `searchInFiles` | Boolean | `false` | "Also search inside files" switch beside the search box (manifest `config.searchInFiles`). On, a search with a term sends `_content_search=true`; rows found through a file show "Found in {file}" (`@self.matchedFile`). State kept in the route as `contentSearch=1`. Emits `content-search`. |
395
396
  | `searchPlaceholder` | String | `''` | Placeholder for the inline search field (manifest `config.searchPlaceholder`). |
@@ -415,7 +416,7 @@ export default {
415
416
  | `quickFilterMode` | String | `'chips'` | How the quick filters render: `'chips'` (pill strip) or `'dropdown'` (a single `NcSelect`). Fed from `pages[].config.quickFilterMode`. |
416
417
  | `quickFilterMultiple` | Boolean | `false` | Allow several quick filters active at once; selected tabs' filters are OR-ed into the fetch (same field → `field[]=` array). Fed from `pages[].config.quickFilterMultiple`. |
417
418
  | `quickFilterMaxVisible` | Number | `0` | Chips mode only — how many quick-filter pills render inline before the rest move behind one more chip — a `⋯` pill that opens a panel of the hidden lenses. `0` renders every tab, which wraps the actions bar onto a second line once a page declares more than a handful. The visible set is the first *n* entries of `quickFilters`. Fed from `pages[].config.quickFilterMaxVisible`. |
418
- | `quickFilterPlacement` | String | `''` | Where the quick-filter chips sit. `'toolbar'` keeps them on one row with the Filter button and the view switch, in whatever width that row has beside a side panel (DqTeamwachtrij): the chips take the room the buttons leave, and those that do not fit move behind the overflow chip. Fed from `pages[].config.quickFilterPlacement`. |
419
+ | `quickFilterPlacement` | String | `''` | Where the quick-filter chips sit. `'toolbar'` keeps them on one row with the Filter button and the view switch, in whatever width that row has beside a side panel (DqTeamwachtrij): the chips take the room the buttons leave, and those that do not fit move behind the overflow chip. Fed from `pages[].config.quickFilterPlacement`. `'row'` puts them on a row of their own above the toolbar, wrapping onto more lines instead of folding into the overflow chip (DqTeamwachtrij); beside a side panel the row sits in the list column. |
419
420
  | `cardComponent` | String | `''` | Optional name of a consumer-provided card component (registered in the v2 `registry` — any kind carrying a `component` — or in the legacy `customComponents` map on `CnAppRoot`) to render in place of the default `CnObjectCard` when the page is in card-grid view mode. Resolution priority: `#card` scoped slot → `cardComponent` registry entry → default `CnObjectCard`. Unknown names log `console.warn` once and fall back to the default. |
420
421
  | `customComponents` | Object | `null` | Optional explicit `customComponents` registry. Overrides the registry injected from `CnAppRoot` via `cnCustomComponents`. Mostly used by unit tests; production consumers register components on `CnAppRoot` instead. |
421
422
  | `subscribe` | Boolean | `true` | Self-fetch mode only. When `register` + `schema` are set (and no `objects` prop), the page subscribes to the collection's `or-collection-{register}-{schema}` live-update scope on mount and refetches on an update event; released on unmount. Set `false` (manifest: `config.subscribe: false`) for static / read-once views. No-op in consumer-managed mode or on stores without live-updates support. |
@@ -878,9 +879,9 @@ The list view (`view-mode="list"`) and standalone sort dropdown add these props:
878
879
  | `listIcon` | String | `''` | MDI icon for the list view-toggle option. |
879
880
  | `listConfig` | Object | `{}` | Field mapping for the default list rows (`CnObjectRow`). |
880
881
  | `listComponent` | String | `''` | Custom row component, resolved against the v2 `registry` (any kind carrying a `component`) and then the legacy `customComponents` map. |
881
- | `showSortSelect` | Boolean | `false` | Show a standalone sort dropdown in the actions bar. |
882
- | `sortSelectOptions` | Array | `[]` | Options `{ value, label, order? }` for the sort dropdown. On a self-fetch page a choice orders the list as a header click does (`_order`, kept in the address): by the option's `order`, else its `value` read as an order (`"-createdAt"`, `"priority:desc,title"`). |
883
- | `sortSelectValue` | String | `''` | Selected sort dropdown value. Empty lets a self-fetch page show the option matching its current order, so a header click that matches an option shows it. |
882
+ | `showSortSelect` | Boolean | `false` | Deprecated, without effect: the toolbar draws no sort select. The table sorts by its column headers; the list, card and map views by the Sort section in the index sidebar. |
883
+ | `sortSelectOptions` | Array | `[]` | The orders the index sidebar's Sort section offers in the list, card and map views: `{ value, label, order? }`, the order its `order` or its `value` read as one (`"-createdAt"`, `"priority:desc,title"`). Without them the section offers the sortable columns. |
884
+ | `sortSelectValue` | String | `''` | Deprecated, without effect: the Sort section shows the current order. |
884
885
  | `defaultOrder` | String, Object or Array | `''` | The order a self-fetch page opens on when neither the address nor `sortKeys`/`sortKey` gives one (manifest `config.defaultOrder`): `"-createdAt"`, `"priority:desc,title"`, an `_order` map `{ "priority": "desc" }` or a `[{ key, order }]` list. |
885
886
 
886
887
  The `#list-item`, `#row-icon`, `#row-badges`, and `#row-actions` slots override the list rows (see [CnObjectList](./cn-object-list.md)). Emits `@sort-change` with the chosen sort value.
@@ -1096,6 +1097,8 @@ Sources: `register` (fetch the folder list from an OpenRegister `register`/`sche
1096
1097
  }
1097
1098
  ```
1098
1099
 
1100
+ `scopeViews` limits the scope's look (columns, title, count line and the other scope keys) to the view modes it names, e.g. `["table", "list"]`, so a board view keeps the page's generic look (DqWerkbord) while the scope still filters it.
1101
+
1099
1102
  `pane: false` draws no pane. The folder still comes from the deep-link filter (`/cases?caseType=<uuid>`) and its scope look (title, count line, columns, quick filters, `afterList` from the folder or its row's `x-index`) still applies, so a list narrowed to one case type looks like that case type's list without a folder column (boards DqWooVerzoeken, DqKlachten):
1100
1103
 
1101
1104
  ```json
@@ -1148,7 +1151,7 @@ columns are right.
1148
1151
 
1149
1152
  ## Board look
1150
1153
 
1151
- With `look: "board"` (the page's `look` prop, `config.look`, or the app's) the index page takes the DqZaken setup: a header with the count line from `countText` (`{shown}`, `{total}`), the buttons Download, Actions, the buildiq square and the primary one in that order, a two-row toolbar (saved-view chips, Filter with a count and the view switch; then search and active filter chips), a bulk band between toolbar and table (`bulkHint`), the table as a white card with one menu button per row, and a footer inside the card (`footerNote`). `cardFields` names the facts of each card in the cards view. Without the look nothing changes.
1154
+ With `look: "board"` (the page's `look` prop, `config.look`, or the app's) the index page takes the DqZaken setup: a header with the count line from `countText` (`{shown}`, `{total}`), the buttons Download, Actions, the buildiq square and the primary one in that order, a two-row toolbar (saved-view chips, Filter with a count and the view switch; then search and active filter chips), a bulk band between toolbar and table (`bulkHint`), the table as a white card with one menu button per row, and a footer inside the card (`footerNote`). `cardFields` names the facts of each card in the cards view, and `cardLayout` draws each card as a record instead: a pill and the number on the head row, the title, a meta line, and a footer with a pill and a date, every part from a field (`{ headPill: { field, colorMap, labels }, number, title, meta: [...], footerPill, footerDate: { field, label } }`). Without the look nothing changes.
1152
1155
 
1153
1156
  `bulkBandAlways` (`config.bulkBandAlways`) draws the board bulk band before a selection too, its buttons
1154
1157
  disabled until rows are selected (DqZaken, DqWerkbord).
@@ -157,6 +157,25 @@
157
157
  :widgets="sidePanel" />
158
158
 
159
159
  <!-- Actions bar -->
160
+ <!-- `quickFilterPlacement: "row"`: the chips on a row of their own above
161
+ the toolbar, wrapping onto more lines (DqTeamwachtrij). -->
162
+ <div
163
+ v-if="quickFilterPlacement === 'row' && tabStripEntries && tabStripEntries.length > 0"
164
+ class="cn-index-page__quick-filter-row"
165
+ data-testid="cn-index-page-quick-filter-row">
166
+ <CnQuickFilterBar
167
+ inline
168
+ :tabs="tabStripEntries"
169
+ :mode="quickFilterMode"
170
+ :maxVisible="quickFilterMaxVisible"
171
+ :fit="false"
172
+ :multiple="quickFilterMultiple"
173
+ :activeIndex="activeQuickFilterIndex"
174
+ :selectedIndices="selectedQuickFilterIndices"
175
+ :counts="effectiveTabCounts"
176
+ @update:activeIndex="onQuickFilterChange"
177
+ @update:selectedIndices="onQuickFilterMultiChange" />
178
+ </div>
160
179
  <CnActionsBar
161
180
  :layout="isBoardLook ? 'board' : 'nextcloud'"
162
181
  :activeFilterChips="activeFilterChips"
@@ -189,9 +208,6 @@
189
208
  :mapLabel="mapLabel"
190
209
  :mapIcon="mapIcon"
191
210
  :listIcon="listIcon"
192
- :showSortSelect="showSortSelect"
193
- :sortOptions="sortSelectOptions"
194
- :sortValue="effectiveSortSelectValue"
195
211
  :showSearch="search !== false && (inlineSearch || isBoardLook)"
196
212
  :searchPlacement="searchPlacement"
197
213
  :searchValue="effectiveSearchValue"
@@ -210,7 +226,6 @@
210
226
  :bulkActions="mergedBulkActions"
211
227
  :documentationUrl="documentationUrl"
212
228
  :documentationLabel="documentationLabel || undefined"
213
- @sortChange="onSortSelectChange"
214
229
  @add="onAddClick"
215
230
  @clearSelection="onSelect([])"
216
231
  @removeFilter="onRemoveActiveFilter"
@@ -365,7 +380,7 @@
365
380
  (between the view toggle and the actions) when the manifest
366
381
  declares `config.quickFilters`. Switching tabs re-fetches with
367
382
  the merged filter; @event quick-filter-change. -->
368
- <template v-if="tabStripEntries && tabStripEntries.length > 0" #filters>
383
+ <template v-if="tabStripEntries && tabStripEntries.length > 0 && quickFilterPlacement !== 'row'" #filters>
369
384
  <CnQuickFilterBar
370
385
  inline
371
386
  :tabs="tabStripEntries"
@@ -373,7 +388,7 @@
373
388
  :maxVisible="quickFilterMaxVisible"
374
389
  :fit="quickFilterPlacement === 'toolbar'"
375
390
  :multiple="quickFilterMultiple"
376
- :activeIndex="activeQuickFilterIndex"
391
+ :activeIndex="shownQuickFilterIndex"
377
392
  :selectedIndices="selectedQuickFilterIndices"
378
393
  :counts="effectiveTabCounts"
379
394
  @update:activeIndex="onQuickFilterChange"
@@ -772,7 +787,7 @@
772
787
  :rowTitle="rowTitle"
773
788
  :showUnread="showUnreadRows"
774
789
  :filterable="tableHeaderFilters"
775
- :activeFilters="effectiveActiveFilters"
790
+ :activeFilters="tableActiveFilters"
776
791
  :filterRegister="typeof register === 'string' ? register : ''"
777
792
  @columnFilter="onColumnFilterEvent"
778
793
  @sort="onSortEvent"
@@ -1057,6 +1072,8 @@
1057
1072
  :selectable="selectable"
1058
1073
  :clickToView="rowClickOpens"
1059
1074
  :cardFields="resolvedCardFields"
1075
+ :cardLayout="cardLayout"
1076
+ :cardFieldColumns="cardFieldColumns"
1060
1077
  :accentOf="cardAccent"
1061
1078
  :selectedIds="internalSelectedIds"
1062
1079
  :rowKey="rowKey"
@@ -1229,6 +1246,8 @@
1229
1246
  :showMetadata="resolvedSidebar.showMetadata !== false"
1230
1247
  :personalColumns="personalColumns"
1231
1248
  :pinnedCount="pinnedColumnCount"
1249
+ :sortOptions="sidebarSortOptions"
1250
+ :currentSort="currentSortKeys"
1232
1251
  v-bind="sidebarSearchProps"
1233
1252
  @update:open="sidebarOpen = $event"
1234
1253
  @search="onSearchEvent"
@@ -1237,7 +1256,8 @@
1237
1256
  @pinChange="onPinChange"
1238
1257
  @columnsReset="onColumnsReset"
1239
1258
  @filterChange="onFilterEvent"
1240
- @clearFilters="onClearFilters" />
1259
+ @clearFilters="onClearFilters"
1260
+ @sortChange="onSidebarSortChange" />
1241
1261
  </div>
1242
1262
  </template>
1243
1263
 
@@ -1285,9 +1305,11 @@ import {
1285
1305
  refIdOf,
1286
1306
  resolveDropValue,
1287
1307
  } from '../../utils/boardReferenceColumns.js'
1308
+ import { isColumnSortable } from '../../utils/columnFilters.js'
1288
1309
  import { reportBindingProblems } from '../../utils/diagnostics.js'
1289
1310
  import { fetchGroupedCounts } from '../../utils/fetchAggregate.js'
1290
1311
  import { fetchFilterCounts, fetchListTotal } from '../../utils/fetchFilterCounts.js'
1312
+ import { unwrapAppConfig } from '../../utils/formatMetric.js'
1291
1313
  import { buildExportUrl } from '../../utils/indexExportHelpers.js'
1292
1314
  import { lensUnavailableText } from '../../utils/lensAvailability.js'
1293
1315
  import { isModifiedClick, openRowTarget } from '../../utils/linkNavigation.js'
@@ -1310,7 +1332,7 @@ import { DEFAULT_ROW_INDICATOR_CAP } from '../../utils/rowIndicators.js'
1310
1332
  import { buildRouteQueryFromViewState, buildViewCreatePayload, extractViewState, extractViewStateFromRouteQuery, normalizeSharedWith, savedViewScope, viewMatchesScope } from '../../utils/savedViewHelpers.js'
1311
1333
  import { columnsFromSchema, fieldsFromSchema } from '../../utils/schema.js'
1312
1334
  import { resolveScopeLayout, resolveScopePresentation } from '../../utils/scopeListLayout.js'
1313
- import { orderToSortKeys, sameSortKeys } from '../../utils/sortOrder.js'
1335
+ import { orderToSortKeys } from '../../utils/sortOrder.js'
1314
1336
  import { dispatchObjectCreated } from '../../utils/walkthroughSignals.js'
1315
1337
  import { CnActionButtons } from '../CnActionButtons/index.js'
1316
1338
  import { CnActionsBar } from '../CnActionsBar/index.js'
@@ -1349,7 +1371,7 @@ import { orderColumns, personalColumnsKey, reconcilePersonalColumns } from './pe
1349
1371
  import { createSelfModeActions, resolveRowTarget } from './selfModeActions.js'
1350
1372
  import { applyRowPatches, normalisePaneWidth, rowIdOf, splitLayoutFor } from './splitView.js'
1351
1373
  import { useNamedSource } from './useNamedSource.js'
1352
- import { BOARD_PAGE_SIZE, useSelfFetchList } from './useSelfFetchList.js'
1374
+ import { BOARD_PAGE_SIZE, resolveInitialQuickFilterIndex, useSelfFetchList } from './useSelfFetchList.js'
1353
1375
 
1354
1376
  /** Rows fetched for one month in calendar mode. */
1355
1377
  const CALENDAR_PAGE_SIZE = 200
@@ -1677,6 +1699,8 @@ export default {
1677
1699
  cnCustomComponents: { default: () => ({}) },
1678
1700
  /** The permissions the user holds, provided by CnAppRoot; for gated page tabs. */
1679
1701
  cnPermissions: { default: () => [] },
1702
+ /** The app config an ancestor provides, for `@config.<key>` in quick-filter labels. */
1703
+ cnAppConfig: { default: null },
1680
1704
  /** The app id CnAppRoot provides, for the plural form of `countSubtitle`. */
1681
1705
  cnAppId: { default: '' },
1682
1706
  /**
@@ -1931,6 +1955,20 @@ export default {
1931
1955
  default: null,
1932
1956
  },
1933
1957
 
1958
+ /**
1959
+ * Board look: the record layout of each card in the cards view
1960
+ * (manifest key `config.cardLayout`): `{ headPill, number, title,
1961
+ * meta, footerPill, footerDate }`, each part naming a field. See
1962
+ * CnObjectCard `layout`.
1963
+ *
1964
+ * @type {object}
1965
+ * @spec openspec/changes/board-record-card-layout/specs/board-record-card-layout/spec.md#requirement-a-board-card-can-draw-the-record-layout-from-config
1966
+ */
1967
+ cardLayout: {
1968
+ type: Object,
1969
+ default: null,
1970
+ },
1971
+
1934
1972
  /**
1935
1973
  * Gives each card in the card view a status accent: a colored start
1936
1974
  * border and icon. See CnObjectCard `accent`.
@@ -2154,13 +2192,15 @@ export default {
2154
2192
  * the Filter button and the view switch, in whatever width that row has
2155
2193
  * beside a side panel (DqTeamwachtrij): the chips take the room the
2156
2194
  * buttons leave, and those that do not fit move into the overflow chip.
2195
+ * `'row'` puts them on a row of their own above the toolbar, wrapping
2196
+ * onto more lines instead of folding into the overflow chip.
2157
2197
  *
2158
- * @type {''|'toolbar'}
2198
+ * @type {''|'toolbar'|'row'}
2159
2199
  */
2160
2200
  quickFilterPlacement: {
2161
2201
  type: String,
2162
2202
  default: '',
2163
- validator: (v) => ['', 'toolbar'].includes(v),
2203
+ validator: (v) => ['', 'toolbar', 'row'].includes(v),
2164
2204
  },
2165
2205
 
2166
2206
  /**
@@ -3133,6 +3173,19 @@ export default {
3133
3173
  default: true,
3134
3174
  },
3135
3175
 
3176
+ /**
3177
+ * The app config `@config.<key>` tokens resolve against, in quick-filter
3178
+ * filters and labels (`"Residence @config.municipality"`), as on the
3179
+ * dashboard and detail pages. Merged over a config an ancestor provides
3180
+ * on `cnAppConfig`. Manifest: `config.appConfig`.
3181
+ *
3182
+ * @type {object}
3183
+ */
3184
+ appConfig: {
3185
+ type: Object,
3186
+ default: () => ({}),
3187
+ },
3188
+
3136
3189
  /**
3137
3190
  * Keep the "Showing X of Y" counter visible beside the inline search field,
3138
3191
  * after any `#after-search` controls (only relevant with `inlineSearch`,
@@ -3181,9 +3234,11 @@ export default {
3181
3234
  },
3182
3235
 
3183
3236
  /**
3184
- * Show a standalone sort dropdown in the actions bar (for card/list
3185
- * views without sortable headers). Emits `@sort-change` with the value.
3186
- * Fed from the manifest as `pages[].config.showSortSelect`.
3237
+ * Deprecated, without effect: the toolbar draws no sort select. The
3238
+ * table sorts by its column headers, the list, card and map views by
3239
+ * the Sort section in the index sidebar.
3240
+ *
3241
+ * @deprecated
3187
3242
  */
3188
3243
  showSortSelect: {
3189
3244
  type: Boolean,
@@ -3191,7 +3246,11 @@ export default {
3191
3246
  },
3192
3247
 
3193
3248
  /**
3194
- * Options for the standalone sort dropdown (manifest `config.sortSelectOptions`).
3249
+ * The orders the index sidebar's Sort section offers in the list, card
3250
+ * and map views (manifest `config.sortSelectOptions`): `{ value, label,
3251
+ * order? }`, the order its `order` (an `orderToSortKeys` form) or its
3252
+ * `value` read as one. Without them the section offers the sortable
3253
+ * columns.
3195
3254
  *
3196
3255
  * @type {Array<{ value: string, label: string }>}
3197
3256
  */
@@ -3201,8 +3260,10 @@ export default {
3201
3260
  },
3202
3261
 
3203
3262
  /**
3204
- * Selected value of the standalone sort dropdown. Empty lets a
3205
- * self-fetch page show the option matching its current order.
3263
+ * Deprecated, without effect: the toolbar sort select is gone; the
3264
+ * Sort section in the index sidebar shows the current order.
3265
+ *
3266
+ * @deprecated
3206
3267
  */
3207
3268
  sortSelectValue: {
3208
3269
  type: String,
@@ -3255,6 +3316,9 @@ export default {
3255
3316
  * defaults to `field`).
3256
3317
  * - `folders` — explicit folder list for `source:'custom'`.
3257
3318
  * - `allLabel` / `title` / `allowCreate` — passed to CnFolderSidebar.
3319
+ * - `scopeViews` — the view modes the scope's look (columns, title,
3320
+ * count line and the other scope keys) applies in, e.g. `["table",
3321
+ * "list"]`; other views keep the page's own look. Default: every view.
3258
3322
  * - `pane` — `false` draws no pane: the scope still comes from the
3259
3323
  * deep-link filter (`?caseType=<id>`) and still applies its look, as
3260
3324
  * a list narrowed to one case type does without a folder column.
@@ -4986,22 +5050,45 @@ export default {
4986
5050
  },
4987
5051
 
4988
5052
  /**
4989
- * The sort select's shown value: `sortSelectValue` when the host
4990
- * controls it, else on a self-fetch page the option whose order is the
4991
- * list's current order (a header click that matches one shows it).
5053
+ * The list's current order, `[{ key, order }]`: the self-fetched list's,
5054
+ * else the `sortKeys` / `sortKey` props.
4992
5055
  *
4993
- * @return {string} The option value, or '' when none matches.
5056
+ * @return {Array<{key: string, order: string}>} The order.
4994
5057
  */
4995
- effectiveSortSelectValue() {
4996
- if (this.sortSelectValue) {
4997
- return this.sortSelectValue
5058
+ currentSortKeys() {
5059
+ if (this.isSelfFetchMode && this.list && this.list.sortKeys) {
5060
+ return this.list.sortKeys.value || []
4998
5061
  }
4999
- if (!this.isSelfFetchMode || !this.list || !this.list.sortKeys) {
5000
- return ''
5062
+ if (Array.isArray(this.sortKeys) && this.sortKeys.length > 0) {
5063
+ return this.sortKeys
5001
5064
  }
5002
- const current = this.list.sortKeys.value || []
5003
- const match = (this.sortSelectOptions || []).find((o) => o && sameSortKeys(this.sortSelectKeys(o), current))
5004
- return match ? match.value : ''
5065
+ return this.sortKey ? [{ key: this.sortKey, order: this.sortOrder || 'asc' }] : []
5066
+ },
5067
+
5068
+ /**
5069
+ * The orders the index sidebar's Sort section offers: none in the table
5070
+ * view, which sorts by its column headers; else `sortSelectOptions`,
5071
+ * or else the sortable columns, ascending.
5072
+ *
5073
+ * @return {Array<{value: string, label: string, keys: Array<{key: string, order: string}>}>} The options.
5074
+ */
5075
+ sidebarSortOptions() {
5076
+ if (this.currentViewMode === 'table') {
5077
+ return []
5078
+ }
5079
+ if (Array.isArray(this.sortSelectOptions) && this.sortSelectOptions.length > 0) {
5080
+ return this.sortSelectOptions
5081
+ .filter((o) => o && o.value !== undefined && o.value !== null)
5082
+ .map((o) => ({
5083
+ value: String(o.value),
5084
+ label: this.cnTranslate(o.label || String(o.value)),
5085
+ keys: orderToSortKeys(o.order !== undefined ? o.order : o.value),
5086
+ }))
5087
+ .filter((o) => o.keys.length > 0)
5088
+ }
5089
+ return (this.tableColumns || [])
5090
+ .filter((c) => c && typeof c === 'object' && c.key && isColumnSortable(c, this.effectiveSchema))
5091
+ .map((c) => ({ value: c.key, label: this.cnTranslate(c.label || c.key), keys: [{ key: c.key, order: 'asc' }] }))
5005
5092
  },
5006
5093
 
5007
5094
  /**
@@ -5033,7 +5120,7 @@ export default {
5033
5120
  * @spec openspec/changes/index-columns-per-scope/specs/index-page/spec.md
5034
5121
  */
5035
5122
  activeScopeLayout() {
5036
- if (!this.folderSidebar) {
5123
+ if (!this.folderSidebar || !this.scopeShapesView) {
5037
5124
  return { columns: null, sortKeys: [], searchFields: [] }
5038
5125
  }
5039
5126
  return resolveScopeLayout({
@@ -5043,6 +5130,23 @@ export default {
5043
5130
  })
5044
5131
  },
5045
5132
 
5133
+ /**
5134
+ * Whether the active scope's look applies in the current view mode:
5135
+ * always, unless `folderSidebar.scopeViews` names the view modes it is
5136
+ * for (`["table", "list"]`), so the board keeps the page's generic
5137
+ * look (DqWerkbord). The scope's filter applies in every view.
5138
+ *
5139
+ * @spec openspec/changes/index-scope-views/specs/index-page/spec.md#requirement-a-scope-look-can-be-limited-to-view-modes
5140
+ * @return {boolean}
5141
+ */
5142
+ scopeShapesView() {
5143
+ const views = this.folderSidebar && this.folderSidebar.scopeViews
5144
+ if (!Array.isArray(views) || views.length === 0) {
5145
+ return true
5146
+ }
5147
+ return views.includes(this.currentViewMode)
5148
+ },
5149
+
5046
5150
  /**
5047
5151
  * This page's own value of each presentation key, which a query preset
5048
5152
  * may have set: what `presented()` falls back to without a scope.
@@ -5077,7 +5181,7 @@ export default {
5077
5181
  * @spec openspec/changes/index-scope-presentation/specs/index-page/spec.md#requirement-a-preset-or-a-scope-sets-the-list-s-presentation
5078
5182
  */
5079
5183
  activeScopePresentation() {
5080
- if (!this.folderSidebar) {
5184
+ if (!this.folderSidebar || !this.scopeShapesView) {
5081
5185
  return {}
5082
5186
  }
5083
5187
  return resolveScopePresentation({ scope: this.activeScope, row: this.activeScopeRow })
@@ -5319,6 +5423,20 @@ export default {
5319
5423
  * @return {(Array<object>|null)} The tabs.
5320
5424
  * @spec openspec/changes/working-list-row-actions/specs/index-page/spec.md
5321
5425
  */
5426
+ /**
5427
+ * The chip the strip lights: the active tab, else the default tab of
5428
+ * tabs that arrived after setup (`quickFiltersFrom`), which is the
5429
+ * one the fetch applies.
5430
+ *
5431
+ * @return {number|null} The tab index, or null without tabs.
5432
+ */
5433
+ shownQuickFilterIndex() {
5434
+ if (this.activeQuickFilterIndex !== null && this.activeQuickFilterIndex !== undefined) {
5435
+ return this.activeQuickFilterIndex
5436
+ }
5437
+ return this.quickFilterMultiple ? null : resolveInitialQuickFilterIndex(this.tabStripEntries)
5438
+ },
5439
+
5322
5440
  tabStripEntries() {
5323
5441
  if (this.lensTabs.length > 0) {
5324
5442
  return this.lensTabs
@@ -5545,6 +5663,22 @@ export default {
5545
5663
  return specs
5546
5664
  },
5547
5665
 
5666
+ /**
5667
+ * The rendered columns by key, so a card fact shows what its table
5668
+ * cell shows (label, formatter, widget).
5669
+ *
5670
+ * @return {object} Column definitions keyed by property.
5671
+ */
5672
+ cardFieldColumns() {
5673
+ const byKey = {}
5674
+ for (const col of this.renderedColumns || []) {
5675
+ if (col && typeof col === 'object' && typeof col.key === 'string') {
5676
+ byKey[col.key] = col
5677
+ }
5678
+ }
5679
+ return byKey
5680
+ },
5681
+
5548
5682
  /**
5549
5683
  * The property keys the board look's card facts show: the page's
5550
5684
  * `cardFields`, else the first four list columns. Null without the
@@ -6394,8 +6528,11 @@ export default {
6394
6528
 
6395
6529
  const present = new Set(cols.map((c) => (typeof c === 'string' ? c : c.key)))
6396
6530
  const byKey = new Map(this.governedColumns.map((c) => [c.key, c]))
6531
+ // A scope that sets its own columns does not get its folder field back:
6532
+ // every row of the scope holds the same value there.
6533
+ const scopeField = this.activeScopeLayout.columns ? this.folderFilterKey : ''
6397
6534
  visible.forEach((key) => {
6398
- if (present.has(key)) {
6535
+ if (present.has(key) || key === scopeField) {
6399
6536
  return
6400
6537
  }
6401
6538
  const def = byKey.get(key)
@@ -6678,11 +6815,21 @@ export default {
6678
6815
  effectiveQuickFilters() {
6679
6816
  const own = withPersonalLenses(this.presented('quickFilters') || this.generatedQuickFilters, this.personalLenses)
6680
6817
  if (own && own.length > 0) {
6681
- return own
6818
+ return own.map((tab) => this.withConfigLabels(tab))
6682
6819
  }
6683
6820
  return (this.isNamedSource && this.namedQuickFilters) || null
6684
6821
  },
6685
6822
 
6823
+ /**
6824
+ * The app config `@config.<key>` tokens resolve against: the `appConfig`
6825
+ * prop over a config an ancestor provides.
6826
+ *
6827
+ * @return {object} The config.
6828
+ */
6829
+ effectiveAppConfig() {
6830
+ return { ...unwrapAppConfig(this.cnAppConfig), ...(this.appConfig || {}) }
6831
+ },
6832
+
6686
6833
  /**
6687
6834
  * Whether the Add button renders. A named source that declares
6688
6835
  * `showAdd: false` suppresses it (a task is created by a flow, never
@@ -6860,6 +7007,28 @@ export default {
6860
7007
  return count > 0 ? `${name}, ${t('nextcloud-vue', '{count} active', { count })}` : name
6861
7008
  },
6862
7009
 
7010
+ /** @return {string} The field the folder sidebar filters by, or ''. */
7011
+ folderFilterKey() {
7012
+ return (this.folderSidebar && (this.folderSidebar.filterField || this.folderSidebar.field)) || ''
7013
+ },
7014
+
7015
+ /**
7016
+ * The filters the table's header filters show: every active filter but
7017
+ * the selected folder's own, which the Active chip already names, so
7018
+ * the table draws no summary chip for it.
7019
+ *
7020
+ * @return {object} The filters.
7021
+ */
7022
+ tableActiveFilters() {
7023
+ const filters = this.effectiveActiveFilters || {}
7024
+ const key = this.folderFilterKey
7025
+ if (!key || this.selectedFolderId === null || this.selectedFolderId === undefined || !Object.hasOwn(filters, key)) {
7026
+ return filters
7027
+ }
7028
+ const { [key]: _folder, ...rest } = filters
7029
+ return rest
7030
+ },
7031
+
6863
7032
  /**
6864
7033
  * The active filters as chips for the board toolbar's second row, one
6865
7034
  * per filter: "Team: Woo". A filter with no value is not active.
@@ -7263,6 +7432,8 @@ export default {
7263
7432
  showMetadata: this.resolvedSidebar.showMetadata !== false,
7264
7433
  personalColumns: this.personalColumns,
7265
7434
  pinnedCount: this.pinnedColumnCount,
7435
+ sortOptions: this.sidebarSortOptions,
7436
+ currentSort: this.currentSortKeys,
7266
7437
  ...this.sidebarSearchProps,
7267
7438
  }
7268
7439
  },
@@ -8735,35 +8906,45 @@ export default {
8735
8906
  },
8736
8907
 
8737
8908
  /**
8738
- * The sort keys a sort-select option stands for: its `order` (any
8739
- * `orderToSortKeys` form), else its `value` read as an order.
8909
+ * An order chosen in the index sidebar's Sort section. It goes the way
8910
+ * a header click goes: the self-fetched list sorts (`_order`, kept in
8911
+ * the address), and `sort` is emitted for a host that sorts itself.
8740
8912
  *
8741
- * @param {object} option A `sortSelectOptions` entry.
8742
- * @return {Array<{key: string, order: string}>} The sort keys.
8913
+ * @param {Array<{key: string, order: string}>} keys The chosen order.
8914
+ * @return {void}
8743
8915
  */
8744
- sortSelectKeys(option) {
8745
- if (!option) {
8746
- return []
8916
+ onSidebarSortChange(keys) {
8917
+ if (!Array.isArray(keys) || keys.length === 0) {
8918
+ return
8747
8919
  }
8748
- return orderToSortKeys(option.order !== undefined ? option.order : option.value)
8920
+ this.onSortEvent({ key: keys[0].key, order: keys[0].order, keys })
8749
8921
  },
8750
8922
 
8751
8923
  /**
8752
- * A choice in the sort select. On a self-fetch page it orders the list
8753
- * as a header click does (`_order`, kept in the address); every page
8754
- * also emits `sort-change` with the value.
8924
+ * A quick filter with `@config.<key>` in its `label` or `activeLabel`
8925
+ * (`"Residence @config.municipality"`): the label translated first, then
8926
+ * each token replaced by its configured value. A key the config does
8927
+ * not hold leaves its token in place. Other tabs pass unchanged.
8755
8928
  *
8756
- * @param {string} value The chosen option's value.
8757
- * @return {void}
8929
+ * @param {object} tab A quick-filter tab.
8930
+ * @return {object} The tab, with its labels resolved.
8758
8931
  */
8759
- onSortSelectChange(value) {
8760
- const option = (this.sortSelectOptions || []).find((o) => o && o.value === value)
8761
- const keys = this.sortSelectKeys(option)
8762
- if (option && keys.length > 0 && this.isSelfFetchMode && typeof this.list.onSort === 'function') {
8763
- this.list.onSort({ key: keys[0].key, order: keys[0].order, keys })
8764
- this.persistViewStateToRoute(this.currentViewState())
8932
+ withConfigLabels(tab) {
8933
+ if (!tab || typeof tab !== 'object') {
8934
+ return tab
8765
8935
  }
8766
- this.$emit('sort-change', value)
8936
+ const hasToken = (value) => typeof value === 'string' && value.includes('@config.')
8937
+ if (!hasToken(tab.label) && !hasToken(tab.activeLabel)) {
8938
+ return tab
8939
+ }
8940
+ const config = this.effectiveAppConfig
8941
+ const fill = (value) => (hasToken(value)
8942
+ ? this.cnTranslate(value).replace(/@config\.([A-Za-z0-9_.-]*[A-Za-z0-9_])/g, (token, key) => {
8943
+ const found = this.getByPath(config, key)
8944
+ return found === undefined || found === null || found === '' ? token : String(found)
8945
+ })
8946
+ : value)
8947
+ return { ...tab, label: fill(tab.label), activeLabel: fill(tab.activeLabel) }
8767
8948
  },
8768
8949
 
8769
8950
  /**
@@ -9924,6 +10105,7 @@ export default {
9924
10105
  'columns-reset': () => this.onColumnsReset(),
9925
10106
  'filter-change': (event) => this.onFilterEvent(event),
9926
10107
  'clear-filters': () => this.onClearFilters(),
10108
+ 'sort-change': (keys) => this.onSidebarSortChange(keys),
9927
10109
  },
9928
10110
  }
9929
10111
  },
@@ -30,11 +30,15 @@
30
30
  :showRefresh="false"
31
31
  :headerLink="entry.headerLink"
32
32
  :flush="variant === 'stat-row'">
33
+ <template v-if="headerNotes[entry.id]" #actions>
34
+ <span class="cn-index-page-widgets__note" data-testid="cn-index-page-widget-note">{{ headerNotes[entry.id] }}</span>
35
+ </template>
33
36
  <component
34
37
  :is="entry.renderer"
35
38
  :widgetId="entry.id"
36
39
  :content="entry.content"
37
- v-bind="declaredContent(entry)" />
40
+ v-bind="declaredContent(entry)"
41
+ @header="onHeader(entry.id, $event)" />
38
42
  </CnWidgetWrapper>
39
43
  </div>
40
44
  </component>
@@ -85,6 +89,13 @@ export default {
85
89
  },
86
90
  },
87
91
 
92
+ data() {
93
+ return {
94
+ /** The header note each widget reported (`header` event), by widget id. */
95
+ headerNotes: {},
96
+ }
97
+ },
98
+
88
99
  computed: {
89
100
  /**
90
101
  * The renderable entries, in the listed order.
@@ -122,6 +133,23 @@ export default {
122
133
  },
123
134
 
124
135
  methods: {
136
+ /**
137
+ * Keep the header note a widget reports ("4 van 5 aanwezig"); only a
138
+ * changed note is stored, so a report on render does not loop.
139
+ *
140
+ * @param {string} id The widget id.
141
+ * @param {object} header `{ note?, link? }` from the widget.
142
+ * @return {void}
143
+ * @spec openspec/changes/side-panel-header-note/specs/side-panel-header-note/spec.md#requirement-a-side-panel-card-shows-the-note-its-widget-reports
144
+ */
145
+ onHeader(id, header) {
146
+ const note = header && typeof header === 'object' && typeof header.note === 'string' ? header.note : ''
147
+ if ((this.headerNotes[id] || '') === note) {
148
+ return
149
+ }
150
+ this.headerNotes = { ...this.headerNotes, [id]: note }
151
+ },
152
+
125
153
  /**
126
154
  * The content keys an entry's renderer declares as props. An undeclared
127
155
  * key would fall through onto the renderer's root; the whole content
@@ -158,6 +186,13 @@ export default {
158
186
  </script>
159
187
 
160
188
  <style scoped>
189
+ .cn-index-page-widgets__note {
190
+ align-self: center;
191
+ color: var(--color-text-maxcontrast);
192
+ font-size: 14px;
193
+ white-space: nowrap;
194
+ }
195
+
161
196
  .cn-index-page-widgets--stat-row {
162
197
  margin-bottom: var(--cn-board-section-gap, 20px);
163
198
  }