@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
@@ -452,6 +452,7 @@
452
452
  :layout="section.layout"
453
453
  :editable="section.editable"
454
454
  :columns="columns"
455
+ :columnOpts="gridColumnOpts"
455
456
  :cellHeight="cellHeight"
456
457
  :margin="gridMargin"
457
458
  :float="gridFloat"
@@ -933,7 +934,8 @@ import { normalizeLook } from '../../composables/useLook.js'
933
934
  import { pageViews } from '../../mixins/pageViews.js'
934
935
  import { mergeUserLayout, resolveUserLayoutApi } from '../../store/plugins/dashboardLayouts.js'
935
936
  import { isAppInstalled } from '../../utils/appInstalled.js'
936
- import { compareVisibleWhen, readVisibleWhenValue } from '../../utils/visibleWhen.js'
937
+ import { contentHeaderLink } from '../../utils/contentHeaderLink.js'
938
+ import { compareVisibleWhen, evaluateWorkspaceCondition, isWorkspaceCondition, readVisibleWhenValue } from '../../utils/visibleWhen.js'
937
939
  import { canonicalWidgetType } from '../../utils/widgetTypeAliases.js'
938
940
  import { CnActionButtons } from '../CnActionButtons/index.js'
939
941
  import { CnActionsMenu } from '../CnActionsMenu/index.js'
@@ -2082,6 +2084,22 @@ export default {
2082
2084
  return this.compactDisplayLayout(visible)
2083
2085
  },
2084
2086
 
2087
+ /**
2088
+ * The responsive columns of the grid. Under the board look a phone gets
2089
+ * one column: below 640px the widgets stack in reading order (top to
2090
+ * bottom, then left to right) instead of squeezing six columns into
2091
+ * 150px. Without the board look the grid keeps its fixed columns.
2092
+ *
2093
+ * @return {object|null} GridStack `columnOpts`, or null.
2094
+ * @spec openspec/changes/screens-dashboard-phone/specs/dashboard-phone/spec.md#requirement-a-board-dashboard-stacks-on-a-phone
2095
+ */
2096
+ gridColumnOpts() {
2097
+ if (!this.isBoardLook) {
2098
+ return null
2099
+ }
2100
+ return { columnMax: this.columns, breakpoints: [{ w: 640, c: 1 }], layout: 'list', breakpointForWindow: true }
2101
+ },
2102
+
2085
2103
  /**
2086
2104
  * The layout this page renders: the user's arrangement when they have
2087
2105
  * one, the manifest otherwise.
@@ -2256,6 +2274,10 @@ export default {
2256
2274
  const items = []
2257
2275
  for (const id of ids) {
2258
2276
  if (this.getWidgetDef(id) || this.hasWidgetSlot(id)) {
2277
+ // A tile a page control hides surrenders its cell.
2278
+ if (!this.gridEditable && this.isHiddenByWorkspace(id)) {
2279
+ continue
2280
+ }
2259
2281
  items.push({ widgetId: id })
2260
2282
  } else if (process.env.NODE_ENV !== 'production') {
2261
2283
  // eslint-disable-next-line no-console
@@ -3401,7 +3423,8 @@ export default {
3401
3423
  const layout = content.layout || props.layout || ''
3402
3424
  const title = layout === 'attention' ? (content.title || props.title || '') : ''
3403
3425
  return {
3404
- text: content.text || props.text || title || '',
3426
+ // A banner with only a bold `lead` has words to show too.
3427
+ text: content.text || props.text || content.lead || props.lead || title || '',
3405
3428
  visibleWhen: (def && def.visibleWhen) || content.visibleWhen || props.visibleWhen || null,
3406
3429
  layout,
3407
3430
  }
@@ -3435,6 +3458,11 @@ export default {
3435
3458
  if (!visibleWhen) {
3436
3459
  return false
3437
3460
  }
3461
+ // A workspace condition (a page control's choice) is answered at
3462
+ // once from the reactive bag; nothing is fetched for it.
3463
+ if (isWorkspaceCondition(visibleWhen)) {
3464
+ return !evaluateWorkspaceCondition(visibleWhen, this.workspaceContext)
3465
+ }
3438
3466
  const outcome = this.widgetConditionOutcome[item.widgetId]
3439
3467
  if (outcome && outcome.failed === true && layout === 'attention' && this.isBannerDef(def)) {
3440
3468
  return false
@@ -3442,6 +3470,23 @@ export default {
3442
3470
  return !outcome || outcome.met !== true
3443
3471
  },
3444
3472
 
3473
+ /**
3474
+ * Whether a page control (a workspace condition on `visibleWhen`)
3475
+ * hides this widget right now. Other conditions are not looked at.
3476
+ *
3477
+ * @param {string} widgetId The widget id.
3478
+ * @return {boolean} True when the widget must not draw.
3479
+ * @spec openspec/changes/screens-page-lenses/specs/page-lenses/spec.md#requirement-a-widget-can-be-shown-by-a-workspace-value
3480
+ */
3481
+ isHiddenByWorkspace(widgetId) {
3482
+ const def = this.getWidgetDef(widgetId)
3483
+ if (!def || !def.type) {
3484
+ return false
3485
+ }
3486
+ const { visibleWhen } = this.widgetDisplayConfig(def)
3487
+ return isWorkspaceCondition(visibleWhen) && !evaluateWorkspaceCondition(visibleWhen, this.workspaceContext)
3488
+ },
3489
+
3445
3490
  /**
3446
3491
  * Evaluate every conditional widget's `visibleWhen` into
3447
3492
  * `widgetConditionOutcome` — the verdict AND the raw field value,
@@ -3464,7 +3509,8 @@ export default {
3464
3509
  async evaluateWidgetConditions() {
3465
3510
  const defs = [...(this.widgets || []), ...this.normalizedViews.flatMap((view) => view.widgets)]
3466
3511
  const conditional = defs.filter((def) => def && def.id && def.type
3467
- && this.widgetDisplayConfig(def).visibleWhen)
3512
+ && this.widgetDisplayConfig(def).visibleWhen
3513
+ && !isWorkspaceCondition(this.widgetDisplayConfig(def).visibleWhen))
3468
3514
  // Skip when the CONDITIONAL SET is unchanged: the deep `widgets`
3469
3515
  // watch fires on any def edit, but only an added/removed/edited
3470
3516
  // predicate warrants refetching every conditional endpoint.
@@ -3484,8 +3530,13 @@ export default {
3484
3530
  const cond = this.widgetDisplayConfig(def).visibleWhen
3485
3531
  let outcome
3486
3532
  try {
3487
- const value = await readVisibleWhenValue(cond)
3533
+ // `sink` keeps the first row of a source query, for a banner that names it.
3534
+ const sink = {}
3535
+ const value = await readVisibleWhenValue(cond, undefined, sink)
3488
3536
  outcome = { met: compareVisibleWhen(value, cond.op || 'eq', cond.value), value }
3537
+ if (sink.row) {
3538
+ outcome.row = sink.row
3539
+ }
3489
3540
  } catch (error) {
3490
3541
  // Fail-safe: not met. But the failure is kept, and said
3491
3542
  // out loud: a failed count and "nothing to report" look
@@ -3985,6 +4036,11 @@ export default {
3985
4036
  const def = this.getWidgetDef(item.widgetId)
3986
4037
  const value = item.showTitle !== undefined ? item.showTitle : def?.showTitle
3987
4038
  if (value === undefined || value === null) {
4039
+ // A banner that names no title draws no title row: it used to show
4040
+ // its widget id ("attention-first") as the card title.
4041
+ if (this.isBannerDef(def) && !item.customTitle && !def?.customTitle && !def?.title) {
4042
+ return false
4043
+ }
3988
4044
  return !this.isCardWidget(item)
3989
4045
  }
3990
4046
  return value !== false
@@ -4093,7 +4149,12 @@ export default {
4093
4149
  return item.headerLink
4094
4150
  }
4095
4151
  const def = this.getWidgetDef(item.widgetId)
4096
- return (def?.headerLink && typeof def.headerLink === 'object') ? def.headerLink : null
4152
+ if (def?.headerLink && typeof def.headerLink === 'object') {
4153
+ return def.headerLink
4154
+ }
4155
+ // Last, what the widget's own content asks for (`headerLink`, or the
4156
+ // `viewAllRoute` / `viewAllLabel` of the tasks widget).
4157
+ return contentHeaderLink(item?.content) || contentHeaderLink(def?.content)
4097
4158
  },
4098
4159
 
4099
4160
  /**
@@ -4842,6 +4903,18 @@ export default {
4842
4903
  .cn-dashboard-page__content-item {
4843
4904
  width: 100%;
4844
4905
  }
4906
+ /* On a phone (screens-dashboard-phone) the touch targets of a board dashboard
4907
+ are 44px: the widget Actions menu and the KPI tiles. */
4908
+ @media (max-width: 640px) {
4909
+ .cn-dashboard-page--board :deep(.cn-widget-wrapper__actions .action-item__menutoggle),
4910
+ .cn-dashboard-page--board :deep(.cn-widget-wrapper__actions .button-vue) {
4911
+ min-height: 44px;
4912
+ }
4913
+
4914
+ .cn-dashboard-page--board .cn-dashboard-page__kpi-cell {
4915
+ min-height: 44px;
4916
+ }
4917
+ }
4845
4918
  </style>
4846
4919
 
4847
4920
  <!-- Unscoped, anchored on the stable `data-testid` NcActions renders on both
@@ -321,6 +321,7 @@
321
321
  :formatterOptions="col.formatterOptions || null"
322
322
  :widget="col.widget || null"
323
323
  :widgetProps="col.widgetProps || undefined"
324
+ :secondary="col.widget === 'avatar' ? secondaryValue(row, col) : ''"
324
325
  :format="columnFormat(col)"
325
326
  :row="row"
326
327
  :rowKey="rowKey" />
@@ -330,7 +331,7 @@
330
331
  of the row. Lets one column read "title" over "number ·
331
332
  requester" without a custom cell. -->
332
333
  <span
333
- v-if="secondaryValue(row, col)"
334
+ v-if="col.widget !== 'avatar' && secondaryValue(row, col)"
334
335
  class="cn-table-cell__secondary"
335
336
  data-testid="cn-cell-secondary">{{ secondaryValue(row, col) }}</span>
336
337
  <!-- A row that entered a file-content search through an attached
@@ -1765,17 +1766,6 @@ export default {
1765
1766
  return (col && typeof col.format === 'object') ? col.format : null
1766
1767
  },
1767
1768
 
1768
- /**
1769
- * Value to render in a cell. For a column that declares `aggregate`
1770
- * (a count of related objects), returns the cached count once
1771
- * `loadAggregates()` has resolved it (or `'…'` while pending, `'—'`
1772
- * if it failed / there's nothing to count). Otherwise the row's
1773
- * property value via `getCellValue`.
1774
- *
1775
- * @param {object} row The row data.
1776
- * @param {object} col The column definition.
1777
- * @return {unknown} The value handed to the slot / CnCellRenderer.
1778
- */
1779
1769
  /**
1780
1770
  * The secondary line of a cell, from `col.secondary`: a function is
1781
1771
  * called with the row; a string with `{field}` placeholders is a
@@ -1806,6 +1796,21 @@ export default {
1806
1796
  return asText(this.getCellValue(row, spec))
1807
1797
  },
1808
1798
 
1799
+ /**
1800
+ * Value to render in a cell. For a column that declares `aggregate`
1801
+ * (a count of related objects), returns the cached count once
1802
+ * `loadAggregates()` has resolved it (or `'…'` while pending, `'—'`
1803
+ * if it failed / there's nothing to count). A count of zero reads as
1804
+ * `aggregate.zeroLabel` when the column sets one ("None", which the
1805
+ * app's `cnTranslate` turns into "Geen" as the boards draw it); without
1806
+ * it the cell shows `0` as before. Otherwise the row's property value
1807
+ * via `getCellValue`.
1808
+ *
1809
+ * @spec openspec/changes/aggregate-zero-label/specs/aggregate-zero-label/spec.md#requirement-a-count-column-can-name-zero
1810
+ * @param {object} row The row data.
1811
+ * @param {object} col The column definition.
1812
+ * @return {unknown} The value handed to the slot / CnCellRenderer.
1813
+ */
1809
1814
  cellValue(row, col) {
1810
1815
  if (col && col.aggregate) {
1811
1816
  const cached = this.aggregateValues[String(row[this.rowKey])]
@@ -1813,6 +1818,10 @@ export default {
1813
1818
  if (v === undefined) {
1814
1819
  return this.aggregateLoading ? '…' : '—'
1815
1820
  }
1821
+ const zeroLabel = col.aggregate.zeroLabel
1822
+ if ((v === 0 || v === '0') && typeof zeroLabel === 'string' && zeroLabel !== '') {
1823
+ return typeof this.cnTranslate === 'function' ? this.cnTranslate(zeroLabel) : zeroLabel
1824
+ }
1816
1825
  return v
1817
1826
  }
1818
1827
  return this.getCellValue(row, col.key)
@@ -379,7 +379,7 @@
379
379
  :showDocumentation="showDocumentation && menuShowsHelpLinks"
380
380
  :actionsMenuLabel="boardMenuLabel"
381
381
  :variant="isBoard ? 'secondary' : undefined"
382
- :hasPrimaryItems="headerMenuGroups.length > 0 || showMarkUnread"
382
+ :hasPrimaryItems="headerMenuGroups.length > 0 || showMarkUnread || showOpenSidebar"
383
383
  :documentationUrl="documentationUrl"
384
384
  :docsAnchor="resolvedPageId"
385
385
  :documentationLabel="documentationLabel || undefined"
@@ -390,7 +390,7 @@
390
390
  testidBase="cn-detail-page"
391
391
  @refresh="onHeaderRefresh"
392
392
  @requestFeature="onHeaderRequestFeature">
393
- <template v-if="headerMenuGroups.length || showMarkUnread" #primary-items>
393
+ <template v-if="headerMenuGroups.length || showMarkUnread || showOpenSidebar" #primary-items>
394
394
  <!-- One block per group. A page that groups nothing has
395
395
  a single captionless group, and renders exactly the
396
396
  flat list it always did. -->
@@ -449,6 +449,20 @@
449
449
  </template>
450
450
  {{ markUnreadLabel }}
451
451
  </NcActionButton>
452
+ <!-- Board look: the sidebar's own open toggle is hidden (it sat
453
+ over the header's last button, where the board draws none), so
454
+ the menu opens it. -->
455
+ <NcActionButton
456
+ v-if="showOpenSidebar"
457
+ key="open-sidebar"
458
+ data-testid="cn-detail-page-open-sidebar"
459
+ :closeAfterClick="true"
460
+ @click="openSidebar">
461
+ <template #icon>
462
+ <DockRight :size="20" />
463
+ </template>
464
+ {{ openSidebarLabel }}
465
+ </NcActionButton>
452
466
  <!-- Divides the page's own actions from the help links
453
467
  below. With the help links switched off there is
454
468
  nothing below to divide from. -->
@@ -745,7 +759,7 @@
745
759
  :layout="section.layout"
746
760
  :editable="section.editable"
747
761
  :columns="12"
748
- :columnOpts="columnOpts"
762
+ :columnOpts="bodyColumnOpts"
749
763
  class="cn-detail-page__grid"
750
764
  @layoutChange="section.view ? onViewLayoutChange($event) : onBodyLayoutChange($event)">
751
765
  <template #widget="{ item }">
@@ -1191,6 +1205,7 @@ import Check from 'vue-material-design-icons/Check.vue'
1191
1205
  import ChevronLeft from 'vue-material-design-icons/ChevronLeft.vue'
1192
1206
  import ChevronRight from 'vue-material-design-icons/ChevronRight.vue'
1193
1207
  import Cog from 'vue-material-design-icons/Cog.vue'
1208
+ import DockRight from 'vue-material-design-icons/DockRight.vue'
1194
1209
  import EyeOffOutline from 'vue-material-design-icons/EyeOffOutline.vue'
1195
1210
  import FileQuestionOutline from 'vue-material-design-icons/FileQuestionOutline.vue'
1196
1211
  import InformationOutline from 'vue-material-design-icons/InformationOutline.vue'
@@ -1295,6 +1310,35 @@ const DEFAULT_DETAIL_COLUMN_OPTS = Object.freeze({
1295
1310
  breakpointForWindow: false,
1296
1311
  })
1297
1312
 
1313
+ /**
1314
+ * Where the default table stacks when a side column sits beside the body grid.
1315
+ *
1316
+ * The side column takes about 320px, so the grid it leaves is far narrower than
1317
+ * the page: 790px at a 1100px window. Stacking that at 1000px made a row of
1318
+ * small tiles (a KPI row of 5 or 6 stat tiles) impossible beside a side column.
1319
+ * 1000px exists to keep a `gridWidth: 3` side PANEL inside the grid readable; a
1320
+ * page with a real side column has no such panel, and 12 columns at 600px is
1321
+ * still 50px a column, enough for a tile. Below 600px it stacks as before.
1322
+ */
1323
+ const SIDE_COLUMN_STACK_WIDTH = 600
1324
+
1325
+ /**
1326
+ * Whether `opts` is the untouched default table, so a page that passed its own
1327
+ * `columnOpts` keeps exactly what it asked for.
1328
+ *
1329
+ * @param {object|null} opts The `columnOpts` value.
1330
+ * @return {boolean}
1331
+ */
1332
+ function isDefaultColumnOpts(opts) {
1333
+ const breakpoints = opts && opts.breakpoints
1334
+ return !!opts
1335
+ && opts.columnMax === DEFAULT_DETAIL_COLUMN_OPTS.columnMax
1336
+ && Array.isArray(breakpoints)
1337
+ && breakpoints.length === 1
1338
+ && breakpoints[0].w === DEFAULT_DETAIL_COLUMN_OPTS.breakpoints[0].w
1339
+ && breakpoints[0].c === 1
1340
+ }
1341
+
1298
1342
  /**
1299
1343
  * Event-bus channel a page-level refresh is announced on. The page's own
1300
1344
  * Actions menu emits it, the manifest `refresh` / `api-call` actions bump it,
@@ -1411,6 +1455,7 @@ export default {
1411
1455
  CnSummaryAggregates,
1412
1456
  CnDetailHeaderChips,
1413
1457
  CnFollowToggle,
1458
+ DockRight,
1414
1459
  EyeOffOutline,
1415
1460
  CnRelatedCollections,
1416
1461
  CnBodySections,
@@ -3335,6 +3380,20 @@ export default {
3335
3380
  .map(({ widget }) => widget)
3336
3381
  },
3337
3382
 
3383
+ /**
3384
+ * The `columnOpts` the body grid gets: the default table stacks at 1000px
3385
+ * of grid width, or at 600px when a side column narrows the grid. An
3386
+ * explicit `columnOpts` prop is passed through untouched.
3387
+ *
3388
+ * @return {object|null} The column options.
3389
+ */
3390
+ bodyColumnOpts() {
3391
+ if (!this.showsSideColumn || !isDefaultColumnOpts(this.columnOpts)) {
3392
+ return this.columnOpts
3393
+ }
3394
+ return { ...this.columnOpts, breakpoints: [{ w: SIDE_COLUMN_STACK_WIDTH, c: 1 }] }
3395
+ },
3396
+
3338
3397
  /**
3339
3398
  * Whether the side column renders.
3340
3399
  *
@@ -3866,6 +3925,22 @@ export default {
3866
3925
  && this.register !== '' && this.schema !== '' && this.objectId !== ''
3867
3926
  },
3868
3927
 
3928
+ /**
3929
+ * Whether the header menu offers "Open sidebar": the board look, where the
3930
+ * sidebar's own toggle is hidden, on a page whose sidebar is active and
3931
+ * closed.
3932
+ *
3933
+ * @return {boolean}
3934
+ */
3935
+ showOpenSidebar() {
3936
+ return this.isBoard && this.hasExternalSidebar && this.sidebarActive && this.objectSidebarState.open !== true
3937
+ },
3938
+
3939
+ /** @return {string} Label of the Open sidebar entry. */
3940
+ openSidebarLabel() {
3941
+ return t('nextcloud-vue', 'Open sidebar')
3942
+ },
3943
+
3869
3944
  /** @return {string} Label of the Mark as unread entry. */
3870
3945
  markUnreadLabel() {
3871
3946
  return t('nextcloud-vue', 'Mark as unread')
@@ -4576,6 +4651,17 @@ export default {
4576
4651
  },
4577
4652
 
4578
4653
  methods: {
4654
+ /**
4655
+ * Open the object sidebar through the shared channel.
4656
+ *
4657
+ * @return {void}
4658
+ */
4659
+ openSidebar() {
4660
+ if (this.objectSidebarState) {
4661
+ this.objectSidebarState.open = true
4662
+ }
4663
+ },
4664
+
4579
4665
  /**
4580
4666
  * Start or finish arranging the chosen view. Finishing stores the
4581
4667
  * arrangement of every view the user moved, once.
@@ -31,7 +31,7 @@ import { readPath } from '../../utils/readPath.js'
31
31
  * Renders CnDocumentReviewList from a list on the bound record:
32
32
  * `content: { field, title?, statusField?, statuses?, rowAction?: { label, route } }`.
33
33
  * A row action with a `route` opens that route with the row's key as `id`.
34
- * Resolved by its registry key, not exported.
34
+ * Resolved by its registry key, and exported for an app that renders it itself.
35
35
  */
36
36
  export default {
37
37
  name: 'CnDocumentReviewWidget',
@@ -62,7 +62,7 @@
62
62
  <script>
63
63
  import { translate as t } from '@nextcloud/l10n'
64
64
  import { NcButton, NcCheckboxRadioSwitch, NcLoadingIcon, NcSelect, NcTextField } from '@nextcloud/vue'
65
- import { facetOptionLabel } from '../../utils/facets.js'
65
+ import { facetOptions } from '../../utils/facets.js'
66
66
  import { filtersFromSchema } from '../../utils/schema.js'
67
67
 
68
68
  /**
@@ -175,12 +175,12 @@ export default {
175
175
 
176
176
  getFilterOptions(filter) {
177
177
  // Use facet data if available (live values with counts)
178
- const facet = this.facetData[filter.key]
179
- if (facet?.values?.length > 0) {
180
- return facet.values.map((v) => ({
181
- id: v.value,
182
- label: facetOptionLabel(v),
183
- }))
178
+ const live = facetOptions(
179
+ this.facetData[filter.key],
180
+ (count) => t('nextcloud-vue', 'No value ({count})', { count }),
181
+ )
182
+ if (live !== null) {
183
+ return live
184
184
  }
185
185
  // Fall back to static enum options from schema
186
186
  return filter.options || []
@@ -140,6 +140,7 @@ import EyeOutline from 'vue-material-design-icons/EyeOutline.vue'
140
140
  import CnIcon from '../CnIcon/CnIcon.vue'
141
141
  import CnStatusBadge from '../CnStatusBadge/CnStatusBadge.vue'
142
142
  import { resolveEndpointRequest } from '../../composables/useEndpointSource.js'
143
+ import { useRefLabels } from '../../composables/useRefLabels.js'
143
144
  import { dispatchAction } from '../../utils/actionsDispatcher.js'
144
145
  import { isBadgeVariant } from '../../utils/badgeVariants.js'
145
146
  import { formatBoardDate } from '../../utils/boardDate.js'
@@ -150,6 +151,35 @@ import { hasUnresolvedDeepTokens, resolveDeepTokens } from '../../utils/resolveF
150
151
  /** Kinds of entry a `fields[]` item can be. A labelled item without `as` is a row. */
151
152
  const KINDS = Object.freeze(['row', 'title', 'line', 'pill', 'lead', 'text'])
152
153
 
154
+ /**
155
+ * The label resolver every facts widget on the page shares, made on first use
156
+ * (the object store needs an active pinia). One batched request per register and
157
+ * schema for the ids it has not seen; a label it has read stays until the page
158
+ * reloads, and an object the store already holds is not fetched at all.
159
+ *
160
+ * @type {{resolve: Function}|null}
161
+ */
162
+ let refLabels = null
163
+
164
+ /** The page-wide reference label resolver. */
165
+ function sharedRefLabels() {
166
+ if (!refLabels) {
167
+ refLabels = useRefLabels()
168
+ }
169
+ return refLabels
170
+ }
171
+
172
+ /**
173
+ * Key of a resolved reference in the widget's `labels` data.
174
+ *
175
+ * @param {{register: string, schema: string, labelField?: string}} resolve The entry's `resolve` config.
176
+ * @param {string} id The referenced object's id.
177
+ * @return {string} The key.
178
+ */
179
+ function refKey(resolve, id) {
180
+ return `${resolve.register}/${resolve.schema}/${resolve.labelField || ''}/${id}`
181
+ }
182
+
153
183
  /** Event-bus channel a reveal of a masked value is announced on, so an app can log it. */
154
184
  export const FACTS_REVEAL_CHANNEL = 'cn:facts:reveal'
155
185
 
@@ -213,6 +243,7 @@ export default {
213
243
  * `as` is `row` (default when `label` or `icon` is set), `title`, `line`, `text` (a line in the normal ink), `lead` (an icon tile or initials beside `title` and `subtitle`) or `pill`.
214
244
  * A row's `format` may also be `email`, `phone` or `url` (a link) or `masked` (the last four characters; `reveal: true` adds Show). `copy: true` adds a copy button; `icon` replaces the label.
215
245
  * A part may be `{ text }`, a constant; a `line` or `text` entry without `path` or `parts` shows its `text`.
246
+ * `resolve: { register, schema, labelField? }` on an entry or a part turns a value that is a reference (an id, or a list of ids) into the label of the referenced object (`labelField`, else `title`, then `name`): the case type or status of a task shows its name, not its uuid. The ids are fetched once per register and schema through the object store and shared by every facts widget on the page; the entry stays empty until its label is known, and when no label can be read.
216
247
  * - `action` — an action drawn as a button under the facts; `headerAction` — one drawn in the card header (reported through the `header` event).
217
248
  * `format` is `text` (default), `date` (board short date), `date-long` (always with the year) or `list` (an array joined by ", ", each item read at `itemPath`).
218
249
  * `map` turns a raw value into a label (translated). `parts` (on a `line`) lists paths, or `{ path, label?, format? }`, joined by `separator` (default " · "); empty parts are dropped.
@@ -255,6 +286,8 @@ export default {
255
286
  revealed: {},
256
287
  /** Key of the row whose value was just copied. */
257
288
  copied: '',
289
+ /** Labels of the referenced objects read so far, by `refKey`. */
290
+ labels: {},
258
291
  }
259
292
  },
260
293
 
@@ -430,6 +463,13 @@ export default {
430
463
  },
431
464
  },
432
465
 
466
+ record: {
467
+ immediate: true,
468
+ handler() {
469
+ this.loadLabels()
470
+ },
471
+ },
472
+
433
473
  headerAction: {
434
474
  immediate: true,
435
475
  handler() {
@@ -668,6 +708,94 @@ export default {
668
708
  })
669
709
  },
670
710
 
711
+ /**
712
+ * The text of a reference: the labels of the objects its id(s) name, joined
713
+ * by ", ". An id whose label is not known yet (or cannot be read) adds nothing.
714
+ *
715
+ * @param {unknown} raw The id, or a list of ids (or of `{ id }` objects).
716
+ * @param {object} spec `{ resolve: { register, schema, labelField? }, itemPath? }`.
717
+ * @return {string}
718
+ */
719
+ refText(raw, spec) {
720
+ return this.refIds(raw, spec)
721
+ .map((id) => this.labels[refKey(spec.resolve, id)])
722
+ .filter((label) => typeof label === 'string' && label !== '')
723
+ .join(', ')
724
+ },
725
+
726
+ /**
727
+ * The ids a reference value holds.
728
+ *
729
+ * @param {unknown} raw The value.
730
+ * @param {object} spec `{ itemPath? }`.
731
+ * @return {string[]}
732
+ */
733
+ refIds(raw, spec) {
734
+ return (Array.isArray(raw) ? raw : [raw])
735
+ .map((item) => (item && typeof item === 'object' ? (spec.itemPath ? readPath(item, spec.itemPath) : (item.id ?? item.uuid)) : item))
736
+ .filter((id) => id !== null && id !== undefined && id !== '' && typeof id !== 'object')
737
+ .map(String)
738
+ },
739
+
740
+ /**
741
+ * Every entry or part that carries `resolve`, with the record's value at its path.
742
+ *
743
+ * @param {object} record The object.
744
+ * @return {Array<{spec: object, raw: unknown}>}
745
+ */
746
+ refSpecs(record) {
747
+ const fields = Array.isArray(this.content?.fields) ? this.content.fields : []
748
+ const specs = []
749
+ const visit = (spec) => {
750
+ if (!spec || typeof spec !== 'object') {
751
+ return
752
+ }
753
+ if (spec.resolve && typeof spec.resolve === 'object' && spec.path) {
754
+ specs.push({ spec, raw: readPath(record, spec.path) })
755
+ }
756
+ const nested = [...(Array.isArray(spec.parts) ? spec.parts : []), ...(Array.isArray(spec.title) ? spec.title : [spec.title]), ...(Array.isArray(spec.subtitle) ? spec.subtitle : [spec.subtitle])]
757
+ nested.forEach(visit)
758
+ }
759
+ fields.forEach(visit)
760
+ return specs
761
+ },
762
+
763
+ /**
764
+ * Read the labels of the referenced objects, one batched request per
765
+ * register and schema, and keep them in `labels`.
766
+ *
767
+ * @param {object|null} record The object shown (defaults to the current record).
768
+ * @return {Promise<void>}
769
+ */
770
+ async loadLabels(record = this.record) {
771
+ if (!record) {
772
+ return
773
+ }
774
+ const groups = new Map()
775
+ for (const { spec, raw } of this.refSpecs(record)) {
776
+ const { register, schema, labelField } = spec.resolve
777
+ if (!register || !schema) {
778
+ continue
779
+ }
780
+ const groupKey = `${register}/${schema}/${labelField || ''}`
781
+ const group = groups.get(groupKey) || { resolve: spec.resolve, ids: new Set() }
782
+ this.refIds(raw, spec).forEach((id) => group.ids.add(id))
783
+ groups.set(groupKey, group)
784
+ }
785
+ if (groups.size === 0) {
786
+ return
787
+ }
788
+ const resolver = sharedRefLabels()
789
+ const next = { ...this.labels }
790
+ await Promise.all([...groups.values()].map(async ({ resolve, ids }) => {
791
+ const found = await resolver.resolve(resolve.register, resolve.schema, [...ids], resolve.labelField)
792
+ Object.entries(found).forEach(([id, label]) => {
793
+ next[refKey(resolve, id)] = label
794
+ })
795
+ }))
796
+ this.labels = next
797
+ },
798
+
671
799
  /**
672
800
  * Read the source object.
673
801
  *
@@ -774,6 +902,9 @@ export default {
774
902
  if (raw === null || raw === undefined || raw === '') {
775
903
  return ''
776
904
  }
905
+ if (spec.resolve && typeof spec.resolve === 'object') {
906
+ return this.refText(raw, spec)
907
+ }
777
908
  const format = spec.format || 'text'
778
909
  if (format === 'list' || Array.isArray(raw)) {
779
910
  const items = (Array.isArray(raw) ? raw : [raw])
@@ -887,12 +1018,21 @@ export default {
887
1018
  display: contents;
888
1019
  }
889
1020
 
890
- .cn-facts-widget__label {
1021
+ /* Nextcloud core styles `dt` and `dd` (text-align end, 12px padding), which
1022
+ pushed the label to the right of its column and added 12px around both. The
1023
+ class is doubled so these resets outrank that rule, and a dossiq-style
1024
+ `:deep()` override in an app is not needed. */
1025
+ .cn-facts-widget__label.cn-facts-widget__label {
1026
+ margin: 0;
1027
+ padding: 0;
1028
+ text-align: start;
891
1029
  color: var(--color-text-maxcontrast);
892
1030
  }
893
1031
 
894
- .cn-facts-widget__value {
1032
+ .cn-facts-widget__value.cn-facts-widget__value {
895
1033
  margin: 0;
1034
+ padding: 0;
1035
+ text-align: start;
896
1036
  font-weight: 500;
897
1037
  min-width: 0;
898
1038
  overflow-wrap: anywhere;