@conduction/nextcloud-vue 2.80.0 → 2.81.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 (290) hide show
  1. package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js.map +1 -1
  2. package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js +3 -3
  3. package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js.map +1 -1
  4. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +80 -4
  5. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
  6. package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js +95 -91
  7. package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
  8. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js +140 -10
  9. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js.map +1 -1
  10. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue3.js +52 -16
  11. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue3.js.map +1 -1
  12. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +17 -3
  13. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
  14. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +63 -52
  15. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
  16. package/dist/esm/components/CnChipsFieldWidget/CnChipsFieldWidget.vue.js +10 -0
  17. package/dist/esm/components/CnChipsFieldWidget/CnChipsFieldWidget.vue.js.map +1 -0
  18. package/dist/esm/components/CnChipsFieldWidget/CnChipsFieldWidget.vue2.js +252 -0
  19. package/dist/esm/components/CnChipsFieldWidget/CnChipsFieldWidget.vue2.js.map +1 -0
  20. package/dist/esm/components/CnChipsFieldWidget/CnChipsFieldWidget.vue3.js +140 -0
  21. package/dist/esm/components/CnChipsFieldWidget/CnChipsFieldWidget.vue3.js.map +1 -0
  22. package/dist/esm/components/CnChipsFieldWidget/index.js +26 -0
  23. package/dist/esm/components/CnChipsFieldWidget/index.js.map +1 -0
  24. package/dist/esm/components/CnChoicePickerWidget/CnChoicePickerWidget.vue.js +10 -0
  25. package/dist/esm/components/CnChoicePickerWidget/CnChoicePickerWidget.vue.js.map +1 -0
  26. package/dist/esm/components/CnChoicePickerWidget/CnChoicePickerWidget.vue2.js +354 -0
  27. package/dist/esm/components/CnChoicePickerWidget/CnChoicePickerWidget.vue2.js.map +1 -0
  28. package/dist/esm/components/CnChoicePickerWidget/CnChoicePickerWidget.vue3.js +172 -0
  29. package/dist/esm/components/CnChoicePickerWidget/CnChoicePickerWidget.vue3.js.map +1 -0
  30. package/dist/esm/components/CnChoicePickerWidget/index.js +27 -0
  31. package/dist/esm/components/CnChoicePickerWidget/index.js.map +1 -0
  32. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue2.js +1 -1
  33. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue2.js.map +1 -1
  34. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue3.js.map +1 -1
  35. package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue2.js +2 -2
  36. package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue2.js.map +1 -1
  37. package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue3.js.map +1 -1
  38. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +65 -5
  39. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
  40. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +2 -1
  41. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
  42. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +19 -11
  43. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
  44. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +3 -2
  45. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
  46. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +72 -0
  47. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  48. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +21 -4
  49. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  50. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue2.js +1 -1
  51. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue2.js.map +1 -1
  52. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue3.js.map +1 -1
  53. package/dist/esm/components/CnFacetSidebar/CnFacetSidebar.vue2.js +7 -7
  54. package/dist/esm/components/CnFacetSidebar/CnFacetSidebar.vue2.js.map +1 -1
  55. package/dist/esm/components/CnFacetSidebar/CnFacetSidebar.vue3.js.map +1 -1
  56. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js +131 -0
  57. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js.map +1 -1
  58. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js.map +1 -1
  59. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue2.js +80 -5
  60. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue2.js.map +1 -1
  61. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue3.js +282 -69
  62. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue3.js.map +1 -1
  63. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +302 -4
  64. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
  65. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +417 -244
  66. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
  67. package/dist/esm/components/CnFilesBrowser/filesBrowser.js +101 -1
  68. package/dist/esm/components/CnFilesBrowser/filesBrowser.js.map +1 -1
  69. package/dist/esm/components/CnFilesBrowser/filesBrowserColumns.js +141 -1
  70. package/dist/esm/components/CnFilesBrowser/filesBrowserColumns.js.map +1 -1
  71. package/dist/esm/components/CnFormPage/CnFormPage.vue2.js +60 -0
  72. package/dist/esm/components/CnFormPage/CnFormPage.vue2.js.map +1 -1
  73. package/dist/esm/components/CnFormPage/CnFormPage.vue3.js +74 -49
  74. package/dist/esm/components/CnFormPage/CnFormPage.vue3.js.map +1 -1
  75. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +29 -2
  76. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  77. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +48 -4
  78. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  79. package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue2.js +7 -7
  80. package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue2.js.map +1 -1
  81. package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue3.js.map +1 -1
  82. package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue2.js +1 -1
  83. package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue2.js.map +1 -1
  84. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +65 -2
  85. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
  86. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js +6 -2
  87. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
  88. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js +63 -1
  89. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
  90. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +8 -1
  91. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
  92. package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue2.js +14 -0
  93. package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue2.js.map +1 -1
  94. package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue3.js +5 -1
  95. package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue3.js.map +1 -1
  96. package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue.js +10 -0
  97. package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue.js.map +1 -0
  98. package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue2.js +174 -0
  99. package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue2.js.map +1 -0
  100. package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue3.js +75 -0
  101. package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue3.js.map +1 -0
  102. package/dist/esm/components/CnRadioCardsWidget/index.js +28 -0
  103. package/dist/esm/components/CnRadioCardsWidget/index.js.map +1 -0
  104. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +30 -6
  105. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
  106. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js +34 -25
  107. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -1
  108. package/dist/esm/components/CnSavedViewsWidget/CnSavedViewsWidget.vue.js +10 -0
  109. package/dist/esm/components/CnSavedViewsWidget/CnSavedViewsWidget.vue.js.map +1 -0
  110. package/dist/esm/components/CnSavedViewsWidget/CnSavedViewsWidget.vue2.js +222 -0
  111. package/dist/esm/components/CnSavedViewsWidget/CnSavedViewsWidget.vue2.js.map +1 -0
  112. package/dist/esm/components/CnSavedViewsWidget/CnSavedViewsWidget.vue3.js +64 -0
  113. package/dist/esm/components/CnSavedViewsWidget/CnSavedViewsWidget.vue3.js.map +1 -0
  114. package/dist/esm/components/CnSavedViewsWidget/index.js +30 -0
  115. package/dist/esm/components/CnSavedViewsWidget/index.js.map +1 -0
  116. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue2.js.map +1 -1
  117. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue3.js.map +1 -1
  118. package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue.js +10 -0
  119. package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue.js.map +1 -0
  120. package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue2.js +347 -0
  121. package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue2.js.map +1 -0
  122. package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue3.js +184 -0
  123. package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue3.js.map +1 -0
  124. package/dist/esm/components/CnSettingsFormWidget/index.js +27 -0
  125. package/dist/esm/components/CnSettingsFormWidget/index.js.map +1 -0
  126. package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js +87 -6
  127. package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js.map +1 -1
  128. package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js.map +1 -1
  129. package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue2.js +84 -1
  130. package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue2.js.map +1 -1
  131. package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue3.js +85 -41
  132. package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue3.js.map +1 -1
  133. package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue.js +10 -0
  134. package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue.js.map +1 -0
  135. package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue2.js +159 -0
  136. package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue2.js.map +1 -0
  137. package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue3.js +60 -0
  138. package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue3.js.map +1 -0
  139. package/dist/esm/components/CnSwitchRowsWidget/index.js +26 -0
  140. package/dist/esm/components/CnSwitchRowsWidget/index.js.map +1 -0
  141. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +47 -1
  142. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
  143. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js +16 -14
  144. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
  145. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue2.js +80 -1
  146. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue2.js.map +1 -1
  147. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue3.js +75 -32
  148. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue3.js.map +1 -1
  149. package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue.js +10 -0
  150. package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue.js.map +1 -0
  151. package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue2.js +269 -0
  152. package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue2.js.map +1 -0
  153. package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue3.js +106 -0
  154. package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue3.js.map +1 -0
  155. package/dist/esm/components/CnToggleTableWidget/index.js +27 -0
  156. package/dist/esm/components/CnToggleTableWidget/index.js.map +1 -0
  157. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +7 -0
  158. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
  159. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js +7 -2
  160. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js.map +1 -1
  161. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue3.js.map +1 -1
  162. package/dist/esm/components/CnWidgetObjectTable/dashboardRegistration.js +5 -0
  163. package/dist/esm/components/CnWidgetObjectTable/dashboardRegistration.js.map +1 -1
  164. package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue2.js +197 -6
  165. package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue2.js.map +1 -1
  166. package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue3.js +96 -50
  167. package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue3.js.map +1 -1
  168. package/dist/esm/composables/useListView.js +4 -4
  169. package/dist/esm/composables/useListView.js.map +1 -1
  170. package/dist/esm/index.js +17 -1
  171. package/dist/esm/index.js.map +1 -1
  172. package/dist/esm/l10n/en.json.js +9 -1
  173. package/dist/esm/l10n/en.json.js.map +1 -1
  174. package/dist/esm/l10n/nl.json.js +55 -1
  175. package/dist/esm/l10n/nl.json.js.map +1 -1
  176. package/dist/esm/nextcloud-vue.css +1041 -12
  177. package/dist/esm/utils/contentHeaderLink.js +51 -0
  178. package/dist/esm/utils/contentHeaderLink.js.map +1 -0
  179. package/dist/esm/utils/facets.js +75 -3
  180. package/dist/esm/utils/facets.js.map +1 -1
  181. package/dist/esm/utils/fetchFilterCounts.js +40 -5
  182. package/dist/esm/utils/fetchFilterCounts.js.map +1 -1
  183. package/dist/esm/utils/fillTemplate.js +7 -1
  184. package/dist/esm/utils/fillTemplate.js.map +1 -1
  185. package/dist/esm/utils/initials.js +32 -0
  186. package/dist/esm/utils/initials.js.map +1 -0
  187. package/dist/esm/utils/libraryWidgetKeys.js +7 -0
  188. package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
  189. package/dist/esm/utils/objectWrite.js +59 -0
  190. package/dist/esm/utils/objectWrite.js.map +1 -0
  191. package/dist/esm/utils/pageControls.js +81 -0
  192. package/dist/esm/utils/pageControls.js.map +1 -0
  193. package/dist/esm/utils/personalLenses.js +40 -3
  194. package/dist/esm/utils/personalLenses.js.map +1 -1
  195. package/dist/esm/utils/resolveFilterTokens.js +1 -1
  196. package/dist/esm/utils/resolveFilterTokens.js.map +1 -1
  197. package/dist/esm/utils/resolveManifestTokens.js +2 -0
  198. package/dist/esm/utils/resolveManifestTokens.js.map +1 -1
  199. package/dist/esm/utils/sentinelTokens.js +19 -1
  200. package/dist/esm/utils/sentinelTokens.js.map +1 -1
  201. package/dist/esm/utils/userGroupsPeek.js +91 -0
  202. package/dist/esm/utils/userGroupsPeek.js.map +1 -0
  203. package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
  204. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  205. package/dist/esm/utils/visibleWhen.js +57 -5
  206. package/dist/esm/utils/visibleWhen.js.map +1 -1
  207. package/dist/esm/utils/widgetDispatch.js +1 -1
  208. package/dist/esm/utils/widgetDispatch.js.map +1 -1
  209. package/dist/esm/utils/widgetVisibility.js +4 -57
  210. package/dist/esm/utils/widgetVisibility.js.map +1 -1
  211. package/dist/nextcloud-vue.cjs.js +13589 -7833
  212. package/dist/nextcloud-vue.cjs.js.map +2 -2
  213. package/dist/nextcloud-vue.css +1041 -12
  214. package/l10n/en.json +9 -1
  215. package/l10n/nl.json +55 -1
  216. package/package.json +2 -1
  217. package/scripts/check-docs-mdx.mjs +108 -0
  218. package/src/components/CnActionsBar/CnActionsBar.vue +6 -1
  219. package/src/components/CnAppRoot/CnAppRoot.md +4 -2
  220. package/src/components/CnAppRoot/CnAppRoot.vue +151 -59
  221. package/src/components/CnBannerWidget/CnBannerWidget.vue +205 -20
  222. package/src/components/CnCellRenderer/CnCellRenderer.md +2 -0
  223. package/src/components/CnCellRenderer/CnCellRenderer.vue +47 -4
  224. package/src/components/CnChipsFieldWidget/CnChipsFieldWidget.vue +433 -0
  225. package/src/components/CnChipsFieldWidget/index.js +25 -0
  226. package/src/components/CnChoicePickerWidget/CnChoicePickerWidget.vue +556 -0
  227. package/src/components/CnChoicePickerWidget/index.js +26 -0
  228. package/src/components/CnConversationThread/CnConversationWidget.vue +1 -1
  229. package/src/components/CnDashboardGrid/CnDashboardGrid.vue +2 -2
  230. package/src/components/CnDashboardPage/CnDashboardPage.vue +78 -5
  231. package/src/components/CnDataTable/CnDataTable.vue +21 -12
  232. package/src/components/CnDetailPage/CnDetailPage.vue +89 -3
  233. package/src/components/CnDocumentReviewList/CnDocumentReviewWidget.vue +1 -1
  234. package/src/components/CnFacetSidebar/CnFacetSidebar.vue +7 -7
  235. package/src/components/CnFactsWidget/CnFactsWidget.vue +142 -2
  236. package/src/components/CnFieldListWidget/CnFieldListWidget.vue +268 -6
  237. package/src/components/CnFilesBrowser/CnFilesBrowser.vue +588 -117
  238. package/src/components/CnFilesBrowser/filesBrowser.js +100 -0
  239. package/src/components/CnFilesBrowser/filesBrowserColumns.js +140 -0
  240. package/src/components/CnFormPage/CnFormPage.vue +81 -0
  241. package/src/components/CnIndexPage/CnIndexPage.vue +56 -4
  242. package/src/components/CnIndexSidebar/CnIndexSidebar.vue +7 -7
  243. package/src/components/CnNextStepCard/CnNextStepWidget.vue +1 -1
  244. package/src/components/CnObjectListWidget/CnObjectListWidget.vue +69 -2
  245. package/src/components/CnObjectSidebar/CnFilesTab.vue +70 -1
  246. package/src/components/CnObjectSidebar/CnNotesTab.vue +16 -0
  247. package/src/components/CnRadioCardsWidget/CnRadioCardsWidget.vue +313 -0
  248. package/src/components/CnRadioCardsWidget/index.js +27 -0
  249. package/src/components/CnRecordListWidget/CnRecordListWidget.vue +55 -6
  250. package/src/components/CnSavedViewsWidget/CnSavedViewsWidget.vue +296 -0
  251. package/src/components/CnSavedViewsWidget/index.js +29 -0
  252. package/src/components/CnSegmentedControl/CnSegmentedControl.vue +13 -0
  253. package/src/components/CnSettingsFormWidget/CnSettingsFormWidget.vue +555 -0
  254. package/src/components/CnSettingsFormWidget/index.js +26 -0
  255. package/src/components/CnStatWidget/CnStatWidget.vue +87 -6
  256. package/src/components/CnStatsBlockWidget/CnStatsBlockWidget.vue +180 -3
  257. package/src/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue +226 -0
  258. package/src/components/CnSwitchRowsWidget/index.js +25 -0
  259. package/src/components/CnTabsWidget/CnTabsWidget.vue +82 -3
  260. package/src/components/CnTasksWidget/CnTasksWidget.vue +175 -15
  261. package/src/components/CnToggleTableWidget/CnToggleTableWidget.vue +386 -0
  262. package/src/components/CnToggleTableWidget/index.js +26 -0
  263. package/src/components/CnWidgetGrid/registerDashboardWidgets.js +7 -0
  264. package/src/components/CnWidgetObjectTable/CnWidgetObjectTable.vue +7 -2
  265. package/src/components/CnWidgetObjectTable/dashboardRegistration.js +5 -0
  266. package/src/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue +309 -11
  267. package/src/components/index.js +15 -0
  268. package/src/composables/useListView.js +4 -4
  269. package/src/css/look-board-detail.css +36 -2
  270. package/src/css/look-board-index.css +39 -8
  271. package/src/css/look-board.css +72 -0
  272. package/src/index.js +15 -0
  273. package/src/schemas/app-manifest-v2.schema.json +93 -9
  274. package/src/utils/contentHeaderLink.js +48 -0
  275. package/src/utils/facets.js +74 -2
  276. package/src/utils/fetchFilterCounts.js +39 -4
  277. package/src/utils/fillTemplate.js +7 -1
  278. package/src/utils/initials.js +29 -0
  279. package/src/utils/libraryWidgetKeys.js +7 -0
  280. package/src/utils/objectWrite.js +55 -0
  281. package/src/utils/pageControls.js +78 -0
  282. package/src/utils/personalLenses.js +39 -2
  283. package/src/utils/resolveFilterTokens.js +3 -1
  284. package/src/utils/resolveManifestTokens.js +2 -0
  285. package/src/utils/sentinelTokens.js +19 -1
  286. package/src/utils/userGroupsPeek.js +87 -0
  287. package/src/utils/validateManifestV2.compiled.js +3 -3
  288. package/src/utils/visibleWhen.js +56 -4
  289. package/src/utils/widgetDispatch.js +1 -1
  290. package/src/utils/widgetVisibility.js +7 -56
@@ -220,3 +220,103 @@ export async function resolveObjectFolderInfo({ apiBase, register, schema, objec
220
220
  return null
221
221
  }
222
222
  }
223
+
224
+ /**
225
+ * Read a dotted path off a value without throwing on a missing segment.
226
+ *
227
+ * @param {(Array|object|string|number|boolean|null|undefined)} value The value.
228
+ * @param {string} path A dotted path; empty answers the value itself.
229
+ * @return {(Array|object|string|number|boolean|null|undefined)} What sits at the path, or undefined.
230
+ */
231
+ function readPath(value, path) {
232
+ if (typeof path !== 'string' || path === '') {
233
+ return value
234
+ }
235
+ return path.split('.').reduce((acc, key) => (acc === null || acc === undefined ? undefined : acc[key]), value)
236
+ }
237
+
238
+ /**
239
+ * Turn a host endpoint's answer into the browser's `rowData`: the host's own
240
+ * record for each file, keyed by file id.
241
+ *
242
+ * The answer may be a list of records (directly, at `path`, or under `items`
243
+ * or `results`), each naming its file in `key` (`fileId` unless the host says
244
+ * otherwise); a record without one is skipped. An object that is not a list
245
+ * and holds neither is taken as already keyed by file id. Anything else is
246
+ * an empty map: the extra columns stay empty, the files still list.
247
+ *
248
+ * @param {(Array|object|string|number|boolean|null|undefined)} data The endpoint answer.
249
+ * @param {object} [options] Where to look.
250
+ * @param {string} [options.path] A dotted path to the list in the answer.
251
+ * @param {string} [options.key] The record field that holds the file id.
252
+ * @return {object} The records by file id.
253
+ */
254
+ export function indexRowData(data, { path = '', key = 'fileId' } = {}) {
255
+ let picked = readPath(data, path)
256
+ if (picked && typeof picked === 'object' && !Array.isArray(picked)) {
257
+ if (Array.isArray(picked.items)) {
258
+ picked = picked.items
259
+ } else if (Array.isArray(picked.results)) {
260
+ picked = picked.results
261
+ } else {
262
+ return { ...picked }
263
+ }
264
+ }
265
+ if (!Array.isArray(picked)) {
266
+ return {}
267
+ }
268
+ const byId = {}
269
+ picked.forEach((record) => {
270
+ if (!record || typeof record !== 'object') {
271
+ return
272
+ }
273
+ const id = record[key]
274
+ if (id === null || id === undefined || id === '') {
275
+ return
276
+ }
277
+ byId[String(id)] = record
278
+ })
279
+ return byId
280
+ }
281
+
282
+ /**
283
+ * Whether a host row action is offered on a file, by its `visibleIf`.
284
+ *
285
+ * `visibleIf.extension` lists file extensions (no case, a leading dot is
286
+ * allowed); `visibleIf.mime` lists mime types, where one ending in `/` or
287
+ * `/*` names the whole family. Every condition named must hold. An action
288
+ * without `visibleIf` is offered on every file, as before.
289
+ *
290
+ * @param {object} action The declared action.
291
+ * @param {object} node The file (`basename`, `mime`).
292
+ * @return {boolean} True when the action belongs on this file.
293
+ */
294
+ export function hostActionApplies(action, node) {
295
+ const condition = action && action.visibleIf
296
+ if (!condition || typeof condition !== 'object') {
297
+ return true
298
+ }
299
+ if (Array.isArray(condition.extension)) {
300
+ const name = String((node && node.basename) || '')
301
+ const dot = name.lastIndexOf('.')
302
+ const extension = dot > 0 ? name.slice(dot + 1).toLowerCase() : ''
303
+ const wanted = condition.extension.map((entry) => String(entry).replace(/^\./, '').toLowerCase())
304
+ if (extension === '' || !wanted.includes(extension)) {
305
+ return false
306
+ }
307
+ }
308
+ if (Array.isArray(condition.mime)) {
309
+ const mime = String((node && node.mime) || '').toLowerCase()
310
+ const matches = condition.mime.some((entry) => {
311
+ const wanted = String(entry).toLowerCase()
312
+ if (wanted.endsWith('/*') || wanted.endsWith('/')) {
313
+ return mime.startsWith(wanted.replace(/\*$/, ''))
314
+ }
315
+ return mime === wanted
316
+ })
317
+ if (!matches) {
318
+ return false
319
+ }
320
+ }
321
+ return true
322
+ }
@@ -219,3 +219,143 @@ export function sortNodesByColumn(nodes, column, ascending, rowData = null, isFo
219
219
  return String(left).localeCompare(String(right)) * direction
220
220
  })
221
221
  }
222
+
223
+ /**
224
+ * Whether a cell value counts as filled in.
225
+ *
226
+ * @param {(Array|object|string|number|boolean|null|undefined)} value The value.
227
+ * @return {boolean} False for nothing, an empty string or an empty list.
228
+ */
229
+ function isFilled(value) {
230
+ if (value === null || value === undefined || value === '') {
231
+ return false
232
+ }
233
+ return !(Array.isArray(value) && value.length === 0)
234
+ }
235
+
236
+ /**
237
+ * The single value a node is grouped under, as a string key: a list groups
238
+ * under its first entry, an object under its label, title, name or id.
239
+ *
240
+ * @param {(Array|object|string|number|boolean|null|undefined)} value The cell value.
241
+ * @return {(string|null)} The group key, or null when nothing is filled in.
242
+ */
243
+ function groupKeyOf(value) {
244
+ const first = Array.isArray(value) ? value[0] : value
245
+ if (!isFilled(first)) {
246
+ return null
247
+ }
248
+ if (typeof first === 'object') {
249
+ const label = first.label ?? first.title ?? first.name ?? first.id
250
+ return isFilled(label) ? String(label) : null
251
+ }
252
+ return String(first)
253
+ }
254
+
255
+ /**
256
+ * The values a node carries in a column, as strings, for counting and
257
+ * filtering: every entry of a list, the one value otherwise.
258
+ *
259
+ * @param {object} column A resolved column.
260
+ * @param {object} node The node.
261
+ * @param {object} [rowData] The host's per-file data.
262
+ * @return {string[]} The values, deduplicated.
263
+ */
264
+ function facetValuesOf(column, node, rowData) {
265
+ const value = fileColumnValue(column, node, rowData)
266
+ const entries = Array.isArray(value) ? value : [value]
267
+ const out = []
268
+ entries.forEach((entry) => {
269
+ const key = groupKeyOf(entry)
270
+ if (key !== null && !out.includes(key)) {
271
+ out.push(key)
272
+ }
273
+ })
274
+ return out
275
+ }
276
+
277
+ /**
278
+ * Group nodes on a column's value, in the order each value is first met.
279
+ * Nodes with no value come last, in one group whose value is null.
280
+ *
281
+ * @param {Array<object>} nodes The nodes, already in display order.
282
+ * @param {object} column The resolved column.
283
+ * @param {object} [rowData] The host's per-file data.
284
+ * @return {Array<{value: (string|null), nodes: Array<object>}>} The groups.
285
+ */
286
+ export function groupNodes(nodes, column, rowData = null) {
287
+ const groups = []
288
+ const byKey = new Map()
289
+ const empty = { value: null, nodes: [] }
290
+ ;(Array.isArray(nodes) ? nodes : []).forEach((node) => {
291
+ const key = column ? groupKeyOf(fileColumnValue(column, node, rowData)) : null
292
+ if (key === null) {
293
+ empty.nodes.push(node)
294
+ return
295
+ }
296
+ if (!byKey.has(key)) {
297
+ const group = { value: key, nodes: [] }
298
+ byKey.set(key, group)
299
+ groups.push(group)
300
+ }
301
+ byKey.get(key).nodes.push(node)
302
+ })
303
+ if (empty.nodes.length > 0) {
304
+ groups.push(empty)
305
+ }
306
+ return groups
307
+ }
308
+
309
+ /**
310
+ * The values in use in a column across the nodes, with how many nodes carry
311
+ * each; a list counts once per entry. Most used first, then in first-seen
312
+ * order.
313
+ *
314
+ * @param {Array<object>} nodes The nodes.
315
+ * @param {object} column The resolved column.
316
+ * @param {object} [rowData] The host's per-file data.
317
+ * @return {Array<{value: string, count: number}>} The values and counts.
318
+ */
319
+ export function facetCounts(nodes, column, rowData = null) {
320
+ const counts = []
321
+ const byValue = new Map()
322
+ ;(Array.isArray(nodes) ? nodes : []).forEach((node) => {
323
+ facetValuesOf(column, node, rowData).forEach((value) => {
324
+ if (!byValue.has(value)) {
325
+ const entry = { value, count: 0 }
326
+ byValue.set(value, entry)
327
+ counts.push(entry)
328
+ }
329
+ byValue.get(value).count += 1
330
+ })
331
+ })
332
+ return counts
333
+ .map((entry, index) => ({ entry, index }))
334
+ .sort((a, b) => (b.entry.count - a.entry.count) || (a.index - b.index))
335
+ .map(({ entry }) => entry)
336
+ }
337
+
338
+ /**
339
+ * Whether a node passes the chosen facet values: within one facet any chosen
340
+ * value will do, and every facet with a choice must pass. A facet with
341
+ * nothing chosen does not filter.
342
+ *
343
+ * @param {object} node The node.
344
+ * @param {object} selections Chosen values by column key.
345
+ * @param {object} columnsByKey Resolved columns by key.
346
+ * @param {object} [rowData] The host's per-file data.
347
+ * @return {boolean} True when the node stays in the list.
348
+ */
349
+ export function nodeMatchesFacets(node, selections, columnsByKey, rowData = null) {
350
+ return Object.entries(selections || {}).every(([key, chosen]) => {
351
+ if (!Array.isArray(chosen) || chosen.length === 0) {
352
+ return true
353
+ }
354
+ const column = columnsByKey && columnsByKey[key]
355
+ if (!column) {
356
+ return true
357
+ }
358
+ const values = facetValuesOf(column, node, rowData)
359
+ return chosen.some((value) => values.includes(value))
360
+ })
361
+ }
@@ -116,6 +116,19 @@
116
116
  v-if="!submitted || mode !== 'public'"
117
117
  class="cn-form-page__form"
118
118
  @submit.prevent="submit">
119
+ <!-- Spam protection: a field no person sees or reaches. A filled one
120
+ means a bot; the submit then sends nothing (see `honeypot`). -->
121
+ <div v-if="honeypot" class="cn-form-page__honeypot" aria-hidden="true">
122
+ <label :for="`cn-form-page-hp-${honeypot}`">{{ t('nextcloud-vue', 'Leave this field empty') }}</label>
123
+ <input
124
+ :id="`cn-form-page-hp-${honeypot}`"
125
+ v-model="honeypotValue"
126
+ :name="honeypot"
127
+ type="text"
128
+ tabindex="-1"
129
+ autocomplete="off"
130
+ data-testid="cn-form-page-honeypot">
131
+ </div>
119
132
  <!-- Paste to fill: only where it may be used (see smartPasteAvailable). -->
120
133
  <div v-if="smartPasteAvailable" class="cn-form-page__smart-paste">
121
134
  <NcButton type="button" data-testid="cn-form-page-smart-paste" @click="smartPasteOpen = true">
@@ -610,6 +623,17 @@ export default {
610
623
  default: '',
611
624
  },
612
625
 
626
+ /**
627
+ * Name of a honeypot field for a form anonymous visitors fill in.
628
+ * The field is rendered out of sight and out of the tab order; when
629
+ * it is filled, submit sends nothing and shows the normal success,
630
+ * so a bot learns nothing. Empty (default): no honeypot.
631
+ */
632
+ honeypot: {
633
+ type: String,
634
+ default: '',
635
+ },
636
+
613
637
  /** HTTP method for endpoint mode. POST | PUT | PATCH. */
614
638
  submitMethod: {
615
639
  type: String,
@@ -748,6 +772,8 @@ export default {
748
772
  summaryKeys: [],
749
773
  /** Per-field validation error messages, keyed by field.key. */
750
774
  fieldErrors: {},
775
+ /** What a bot typed into the honeypot field; stays empty for a person. */
776
+ honeypotValue: '',
751
777
  /**
752
778
  * Cache of resolved `endpoint`/`source` visibleWhen outcomes,
753
779
  * keyed by field.key. Resolved ONCE in `mounted()` — see the
@@ -1783,6 +1809,11 @@ export default {
1783
1809
  }
1784
1810
 
1785
1811
  this.lastError = null
1812
+ if (this.honeypot && this.honeypotValue !== '') {
1813
+ // A bot filled the field no person sees: send nothing, look done.
1814
+ this.submitted = true
1815
+ return
1816
+ }
1786
1817
  this.submitting = true
1787
1818
  try {
1788
1819
  if (this.submitEndpoint) {
@@ -1808,6 +1839,9 @@ export default {
1808
1839
  // describes the transport ("Request failed with status code 400")
1809
1840
  // and leaves the user to guess which field it meant.
1810
1841
  this.lastError = serverErrorMessage(err)
1842
+ // A refusal that names its fields (`findings[]`, as OpenRegister's
1843
+ // form destination check answers 422) is marked on each field.
1844
+ this.showServerFindings(err)
1811
1845
  /**
1812
1846
  * Submit failure event. Payload is the thrown error / rejected reason.
1813
1847
  *
@@ -1820,6 +1854,45 @@ export default {
1820
1854
  }
1821
1855
  },
1822
1856
 
1857
+ /**
1858
+ * Put each finding of a refused submit on its field.
1859
+ *
1860
+ * Reads `response.data.findings[]`, each `{ field?, property, message }`,
1861
+ * and marks the field whose key is the finding's `field`, or else its
1862
+ * `property`. Findings on no field of this form stay in the general
1863
+ * error above the form. Nothing happens for an answer without findings.
1864
+ *
1865
+ * @param {object} err The rejected request.
1866
+ * @return {void}
1867
+ */
1868
+ showServerFindings(err) {
1869
+ const findings = err?.response?.data?.findings
1870
+ if (!Array.isArray(findings) || findings.length === 0) {
1871
+ return
1872
+ }
1873
+ const keys = new Set(this.fields.map((f) => f && f.key))
1874
+ const marked = []
1875
+ for (const finding of findings) {
1876
+ const key = finding && (finding.field || finding.property)
1877
+ if (!key || !keys.has(key) || this.fieldErrors[key]) {
1878
+ continue
1879
+ }
1880
+ this.fieldErrors[key] = String(finding.message || t('nextcloud-vue', 'This answer was refused.'))
1881
+ marked.push(key)
1882
+ }
1883
+ if (marked.length === 0) {
1884
+ return
1885
+ }
1886
+ this.summaryKeys = this.fields.map((f) => f.key).filter((key) => marked.includes(key))
1887
+ if (this.hasSteps) {
1888
+ const stepIndex = this.steps.findIndex((step) => this.stepFields(step).some((f) => f.key === this.summaryKeys[0]))
1889
+ if (stepIndex >= 0) {
1890
+ this.currentStepIndex = stepIndex
1891
+ }
1892
+ }
1893
+ this.focusFirstError(this.summaryKeys[0])
1894
+ },
1895
+
1823
1896
  async submitViaEndpoint() {
1824
1897
  const method = (this.submitMethod || 'POST').toLowerCase()
1825
1898
  const url = resolveParams(this.submitEndpoint, this.$route?.params || {})
@@ -1843,6 +1916,14 @@ export default {
1843
1916
  </script>
1844
1917
 
1845
1918
  <style>
1919
+ .cn-form-page__honeypot {
1920
+ position: absolute;
1921
+ left: -10000px;
1922
+ width: 1px;
1923
+ height: 1px;
1924
+ overflow: hidden;
1925
+ }
1926
+
1846
1927
  .cn-form-page {
1847
1928
  display: flex;
1848
1929
  flex-direction: column;
@@ -263,6 +263,31 @@
263
263
  @update:activeIndex="onQuickFilterChange"
264
264
  @update:selectedIndices="onQuickFilterMultiChange" />
265
265
  </template>
266
+ <!-- Board look, `filterMenu` on a page with no filter sidebar: the
267
+ board's Filter button, a menu of each enum column's values as
268
+ toggleable filters. -->
269
+ <template v-if="boardFilterMenu" #filter-menu>
270
+ <NcActions
271
+ class="cn-actions-bar__filter-menu"
272
+ variant="secondary"
273
+ :forceMenu="true"
274
+ :menuName="t('nextcloud-vue', 'Filter')"
275
+ data-testid="cn-index-filter-menu">
276
+ <template #icon>
277
+ <Tune :size="16" />
278
+ </template>
279
+ <template v-for="field in filterableFields" :key="field.key">
280
+ <NcActionCaption :name="field.label" />
281
+ <NcActionCheckbox
282
+ v-for="val in field.values"
283
+ :key="`${field.key}-${val}`"
284
+ :modelValue="isFilterActive(field.key, val)"
285
+ @update:modelValue="toggleFilter(field.key, val)">
286
+ {{ val }}
287
+ </NcActionCheckbox>
288
+ </template>
289
+ </NcActions>
290
+ </template>
266
291
  </CnActionsBar>
267
292
 
268
293
  <!-- Quick edit: a few fields of one row, over the list, with the list
@@ -625,10 +650,10 @@
625
650
  the column menu lists every governed column as a visibility
626
651
  checkbox — compact, in-table alternatives to the sidebar. -->
627
652
  <template
628
- v-if="(filterMenu && filterableFields.length) || (columnMenu && governedColumns.length)"
653
+ v-if="(showHeaderFilterMenu) || (columnMenu && governedColumns.length)"
629
654
  #actions-header>
630
655
  <NcActions
631
- v-if="filterMenu && filterableFields.length"
656
+ v-if="showHeaderFilterMenu"
632
657
  :forceMenu="true"
633
658
  :aria-label="t('nextcloud-vue', 'Filter')">
634
659
  <template #icon>
@@ -968,6 +993,7 @@ import DotsHorizontal from 'vue-material-design-icons/DotsHorizontal.vue'
968
993
  import Export from 'vue-material-design-icons/Export.vue'
969
994
  import Eye from 'vue-material-design-icons/Eye.vue'
970
995
  import FilterOutline from 'vue-material-design-icons/FilterOutline.vue'
996
+ import Tune from 'vue-material-design-icons/Tune.vue'
971
997
  import ViewColumnOutline from 'vue-material-design-icons/ViewColumnOutline.vue'
972
998
  import CnConfirmDialog from '../../dialogs/CnConfirmDialog.vue'
973
999
  import CnQuickEditDialog from '../../dialogs/CnQuickEditDialog.vue'
@@ -1275,6 +1301,7 @@ export default {
1275
1301
  DatabaseSearch,
1276
1302
  Export,
1277
1303
  FilterOutline,
1304
+ Tune,
1278
1305
  ViewColumnOutline,
1279
1306
  NcCheckboxRadioSwitch,
1280
1307
  CnPageHeader,
@@ -1456,7 +1483,6 @@ export default {
1456
1483
  default: true,
1457
1484
  },
1458
1485
 
1459
- // eslint-disable-next-line vue/no-unused-properties
1460
1486
  /**
1461
1487
  * Draw this page in the `board` look (or `nextcloud`) whatever the app
1462
1488
  * does. Empty follows the `cnLook` CnAppRoot provides. Manifest key
@@ -1607,7 +1633,10 @@ export default {
1607
1633
  * unchanged. Under the board look a tab with `activeLabel` ("Pipeline:
1608
1634
  * Sales") adds a removable chip with that text to the Active line and
1609
1635
  * counts on the Filter badge while it is selected; removing the chip
1610
- * goes back to the default tab.
1636
+ * goes back to the default tab. A tab can say `empty: "field"` (or a
1637
+ * list of fields) for the rows where the field has no value, and
1638
+ * `notEmpty` for the rows where it has one: it reads as the filter
1639
+ * `{ field: { isnull: true } }`.
1611
1640
  */
1612
1641
  quickFilters: {
1613
1642
  type: Array,
@@ -2892,6 +2921,8 @@ export default {
2892
2921
  * Show a filter menu (funnel button) in the table header, above the
2893
2922
  * row-actions column. Its menu lists every enum/badge column's values as
2894
2923
  * toggleable facet filters — a compact alternative to the facet sidebar.
2924
+ * Under the board look, on a page with no filter sidebar, it is the
2925
+ * toolbar's labelled Filter button instead (row 1, before the view switch).
2895
2926
  * Fed from the manifest as `pages[].config.filterMenu`.
2896
2927
  */
2897
2928
  filterMenu: {
@@ -5606,6 +5637,27 @@ export default {
5606
5637
  return !!this.resolvedSidebar.enabled && this.resolvedSidebar.show !== false
5607
5638
  },
5608
5639
 
5640
+ /**
5641
+ * Whether the board toolbar draws the Filter button as a menu: the
5642
+ * board look, `filterMenu` on, something to filter by, and no filter
5643
+ * sidebar (whose toggle is that button otherwise).
5644
+ *
5645
+ * @return {boolean}
5646
+ */
5647
+ boardFilterMenu() {
5648
+ return this.isBoardLook && this.filterMenu && !this.hasSidebar && this.filterableFields.length > 0
5649
+ },
5650
+
5651
+ /**
5652
+ * Whether the funnel menu sits in the table header: `filterMenu` with
5653
+ * values to offer, unless the toolbar's Filter button is that menu.
5654
+ *
5655
+ * @return {boolean}
5656
+ */
5657
+ showHeaderFilterMenu() {
5658
+ return this.filterMenu && this.filterableFields.length > 0 && !this.boardFilterMenu
5659
+ },
5660
+
5609
5661
  /** Search props forwarded to the embedded CnIndexSidebar (defaults applied per CnIndexSidebar). */
5610
5662
  sidebarSearchProps() {
5611
5663
  return (this.sidebar && this.sidebar.search) || {}
@@ -270,7 +270,7 @@ import Pin from 'vue-material-design-icons/Pin.vue'
270
270
  import PinOutline from 'vue-material-design-icons/PinOutline.vue'
271
271
  import ViewColumnOutline from 'vue-material-design-icons/ViewColumnOutline.vue'
272
272
  import { METADATA_COLUMNS } from '../../constants/metadata.js'
273
- import { facetOptionLabel } from '../../utils/facets.js'
273
+ import { facetOptions } from '../../utils/facets.js'
274
274
  import { labelLang } from '../../utils/manifestTranslate.js'
275
275
  import { columnsFromSchema, filtersFromSchema } from '../../utils/schema.js'
276
276
  import { CnDateRangePicker, DEFAULT_DATE_RANGE_PRESETS } from '../CnDateRangePicker/index.js'
@@ -867,12 +867,12 @@ export default {
867
867
  * @param {object} filter Filter object
868
868
  */
869
869
  getFilterOptions(filter) {
870
- const facet = this.facetData[filter.key]
871
- if (facet?.values?.length > 0) {
872
- return facet.values.map((v) => ({
873
- id: v.value,
874
- label: facetOptionLabel(v),
875
- }))
870
+ const live = facetOptions(
871
+ this.facetData[filter.key],
872
+ (count) => t('nextcloud-vue', 'No value ({count})', { count }),
873
+ )
874
+ if (live !== null) {
875
+ return live
876
876
  }
877
877
  // A reference picker's rows come from another list, and neither a
878
878
  // schema enum nor a facet bucket can carry them.
@@ -21,7 +21,7 @@ import { resolveNextStep } from '../../utils/detailActionModel.js'
21
21
  * Renders CnNextStepCard for the bound record's stage from
22
22
  * `content: { field?, stages: { <stage>: { title?, checklist, after? } } }`.
23
23
  * The checklist only: the page's own primary button stays where the page
24
- * puts it. Resolved by its registry key, not exported.
24
+ * puts it. Resolved by its registry key, and exported for an app that renders it itself.
25
25
  */
26
26
  export default {
27
27
  name: 'CnNextStepWidget',
@@ -151,6 +151,8 @@
151
151
  :sortKey="localSort.field || null"
152
152
  :sortOrder="localSort.dir || 'asc'"
153
153
  borderless
154
+ :fitWidth="fitWidthActive"
155
+ :fixedLayout="fixedLayoutActive"
154
156
  :hideHeader="content.hideHeader === true"
155
157
  @rowClick="onRowClick"
156
158
  @rowAuxClick="onRowAuxClick"
@@ -173,6 +175,8 @@
173
175
  :sortKey="localSort.field || null"
174
176
  :sortOrder="localSort.dir || 'asc'"
175
177
  borderless
178
+ :fitWidth="fitWidthActive"
179
+ :fixedLayout="fixedLayoutActive"
176
180
  :hideHeader="content.hideHeader === true"
177
181
  @rowClick="onRowClick"
178
182
  @rowAuxClick="onRowAuxClick"
@@ -448,6 +452,15 @@ export default {
448
452
  page: 1,
449
453
  /** Rows that fit the host cell; null = unconstrained (dashboards). */
450
454
  fitRows: null,
455
+ /**
456
+ * How far the table has been squeezed to the widget's width, so the
457
+ * card never scrolls sideways: 0 = its own auto layout, 1 = one column
458
+ * takes what is left and is cut with an ellipsis while the rest keep
459
+ * their content on one line, 2 = a fixed layout, every column an equal
460
+ * share with its text wrapped. Each step is taken only while the table
461
+ * still scrolls sideways. `content.fitWidth` forces stage 1 on or off.
462
+ */
463
+ fitStage: 0,
451
464
  /** Whether the create dialog is open. */
452
465
  showCreate: false,
453
466
  /**
@@ -480,6 +493,27 @@ export default {
480
493
  },
481
494
 
482
495
  computed: {
496
+ /**
497
+ * Whether the table fits the widget's width (stage 1): `content.fitWidth`
498
+ * when it says, else once the columns were measured not to fit.
499
+ *
500
+ * @return {boolean}
501
+ */
502
+ fitWidthActive() {
503
+ const forced = this.content?.fitWidth
504
+ return typeof forced === 'boolean' ? forced : this.fitStage === 1
505
+ },
506
+
507
+ /**
508
+ * Whether the table takes a fixed layout, the last resort when even the
509
+ * fitted table is wider than the widget (not with `content.fitWidth` set).
510
+ *
511
+ * @return {boolean}
512
+ */
513
+ fixedLayoutActive() {
514
+ return typeof this.content?.fitWidth !== 'boolean' && this.fitStage >= 2
515
+ },
516
+
483
517
  /**
484
518
  * The unwrapped detail-page object context for token resolution, or null
485
519
  * on surfaces (dashboards) that don't provide one.
@@ -619,7 +653,7 @@ export default {
619
653
  // `secondary` (a second, muted line under the cell) rides
620
654
  // along too: the table draws it, and dropping it here took the
621
655
  // list rows' second line away (zuiddrecht-pixel-gaps-3).
622
- for (const k of ['format', 'widget', 'widgetProps', 'formatter', 'align', 'width', 'type', 'enum', 'enumLabels', 'sortable', 'secondary']) {
656
+ for (const k of ['format', 'widget', 'widgetProps', 'formatter', 'align', 'width', 'type', 'enum', 'enumLabels', 'sortable', 'secondary', 'colorMap', 'grow']) {
623
657
  if (c[k] !== undefined) {
624
658
  out[k] = c[k]
625
659
  }
@@ -1165,6 +1199,12 @@ export default {
1165
1199
  mounted() {
1166
1200
  this.fetchRows()
1167
1201
  this.loadHeadingsIfNeeded()
1202
+ // A table wider than the widget fits its columns to the width instead of
1203
+ // scrolling sideways (no inner scrollbars).
1204
+ if (typeof ResizeObserver !== 'undefined') {
1205
+ this._widthObserver = new ResizeObserver(() => this.measureWidthFit())
1206
+ this._widthObserver.observe(this.$el)
1207
+ }
1168
1208
  // Re-read on a page-level refresh. This list fetches its own rows from
1169
1209
  // OpenRegister and subscribed to nothing, so after a write elsewhere on
1170
1210
  // the page it went on rendering the result set it fetched on mount —
@@ -1203,6 +1243,9 @@ export default {
1203
1243
  if (this._fitObserver) {
1204
1244
  this._fitObserver.disconnect()
1205
1245
  }
1246
+ if (this._widthObserver) {
1247
+ this._widthObserver.disconnect()
1248
+ }
1206
1249
  },
1207
1250
 
1208
1251
  methods: {
@@ -1277,7 +1320,10 @@ export default {
1277
1320
  const res = await axios.get(url, { params })
1278
1321
  this.rows = (res && res.data && res.data.results) || []
1279
1322
  this.total = (res && res.data && typeof res.data.total === 'number') ? res.data.total : this.rows.length
1280
- this.$nextTick(() => this.measureFit())
1323
+ this.$nextTick(() => {
1324
+ this.measureFit()
1325
+ this.measureWidthFit()
1326
+ })
1281
1327
  } catch (e) {
1282
1328
  // Keep the raw message OUT of the template — log it for
1283
1329
  // debugging, surface only the quiet `loadErrorLabel` line.
@@ -1291,6 +1337,27 @@ export default {
1291
1337
  }
1292
1338
  },
1293
1339
 
1340
+ /**
1341
+ * Squeeze the table to the widget's width while it scrolls sideways: its
1342
+ * `.cn-data-table__scroll` wrapper is wider than it is visible. First the
1343
+ * fit (stage 1), then, if that is still too wide, a fixed layout
1344
+ * (stage 2). It never goes back (a squeezed table does not overflow, so
1345
+ * there is nothing to measure to relax it). No-op when `content.fitWidth`
1346
+ * is set.
1347
+ *
1348
+ * @return {void}
1349
+ */
1350
+ measureWidthFit() {
1351
+ if (this.fitStage >= 2 || typeof this.content?.fitWidth === 'boolean') {
1352
+ return
1353
+ }
1354
+ const scroller = this.$el && this.$el.querySelector ? this.$el.querySelector('.cn-data-table__scroll') : null
1355
+ if (scroller && scroller.scrollWidth > scroller.clientWidth + 1) {
1356
+ this.fitStage += 1
1357
+ this.$nextTick(() => this.measureWidthFit())
1358
+ }
1359
+ },
1360
+
1294
1361
  /**
1295
1362
  * Fit the visible row count to the host grid cell (ADR-062 — the cell
1296
1363
  * is the budget). Measures the cell's remaining height below the table