@conduction/nextcloud-vue 2.79.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 (390) hide show
  1. package/dist/esm/components/CnActionButtons/CnActionButtons.vue2.js.map +1 -1
  2. package/dist/esm/components/CnActionButtons/CnActionButtons.vue3.js +3 -1
  3. package/dist/esm/components/CnActionButtons/CnActionButtons.vue3.js.map +1 -1
  4. package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js +32 -3
  5. package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js.map +1 -1
  6. package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js +13 -11
  7. package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js.map +1 -1
  8. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +80 -4
  9. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
  10. package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js +95 -91
  11. package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
  12. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js +179 -13
  13. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js.map +1 -1
  14. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue3.js +52 -16
  15. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue3.js.map +1 -1
  16. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +96 -4
  17. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
  18. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +158 -110
  19. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
  20. package/dist/esm/components/CnChipsFieldWidget/CnChipsFieldWidget.vue.js +10 -0
  21. package/dist/esm/components/CnChipsFieldWidget/CnChipsFieldWidget.vue.js.map +1 -0
  22. package/dist/esm/components/CnChipsFieldWidget/CnChipsFieldWidget.vue2.js +252 -0
  23. package/dist/esm/components/CnChipsFieldWidget/CnChipsFieldWidget.vue2.js.map +1 -0
  24. package/dist/esm/components/CnChipsFieldWidget/CnChipsFieldWidget.vue3.js +140 -0
  25. package/dist/esm/components/CnChipsFieldWidget/CnChipsFieldWidget.vue3.js.map +1 -0
  26. package/dist/esm/components/CnChipsFieldWidget/index.js +26 -0
  27. package/dist/esm/components/CnChipsFieldWidget/index.js.map +1 -0
  28. package/dist/esm/components/CnChoicePickerWidget/CnChoicePickerWidget.vue.js +10 -0
  29. package/dist/esm/components/CnChoicePickerWidget/CnChoicePickerWidget.vue.js.map +1 -0
  30. package/dist/esm/components/CnChoicePickerWidget/CnChoicePickerWidget.vue2.js +354 -0
  31. package/dist/esm/components/CnChoicePickerWidget/CnChoicePickerWidget.vue2.js.map +1 -0
  32. package/dist/esm/components/CnChoicePickerWidget/CnChoicePickerWidget.vue3.js +172 -0
  33. package/dist/esm/components/CnChoicePickerWidget/CnChoicePickerWidget.vue3.js.map +1 -0
  34. package/dist/esm/components/CnChoicePickerWidget/index.js +27 -0
  35. package/dist/esm/components/CnChoicePickerWidget/index.js.map +1 -0
  36. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue2.js +1 -1
  37. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue2.js.map +1 -1
  38. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue3.js.map +1 -1
  39. package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue2.js +2 -2
  40. package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue2.js.map +1 -1
  41. package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue3.js.map +1 -1
  42. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +65 -5
  43. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
  44. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +2 -1
  45. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
  46. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +19 -11
  47. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
  48. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +4 -2
  49. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
  50. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +134 -4
  51. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  52. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +21 -4
  53. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  54. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js +24 -0
  55. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
  56. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js +43 -4
  57. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
  58. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue2.js +1 -1
  59. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue2.js.map +1 -1
  60. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue3.js.map +1 -1
  61. package/dist/esm/components/CnFacetSidebar/CnFacetSidebar.vue2.js +7 -7
  62. package/dist/esm/components/CnFacetSidebar/CnFacetSidebar.vue2.js.map +1 -1
  63. package/dist/esm/components/CnFacetSidebar/CnFacetSidebar.vue3.js.map +1 -1
  64. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue.js +11 -0
  65. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue.js.map +1 -0
  66. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js +867 -0
  67. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js.map +1 -0
  68. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js +238 -0
  69. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js.map +1 -0
  70. package/dist/esm/components/CnFactsWidget/index.js +30 -0
  71. package/dist/esm/components/CnFactsWidget/index.js.map +1 -0
  72. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue.js +10 -0
  73. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue.js.map +1 -0
  74. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue2.js +626 -0
  75. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue2.js.map +1 -0
  76. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue3.js +396 -0
  77. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue3.js.map +1 -0
  78. package/dist/esm/components/CnFieldListWidget/index.js +29 -0
  79. package/dist/esm/components/CnFieldListWidget/index.js.map +1 -0
  80. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +302 -4
  81. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
  82. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +417 -244
  83. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
  84. package/dist/esm/components/CnFilesBrowser/filesBrowser.js +101 -1
  85. package/dist/esm/components/CnFilesBrowser/filesBrowser.js.map +1 -1
  86. package/dist/esm/components/CnFilesBrowser/filesBrowserColumns.js +141 -1
  87. package/dist/esm/components/CnFilesBrowser/filesBrowserColumns.js.map +1 -1
  88. package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js +65 -17
  89. package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
  90. package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js +4 -4
  91. package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
  92. package/dist/esm/components/CnFormPage/CnFormPage.vue2.js +78 -3
  93. package/dist/esm/components/CnFormPage/CnFormPage.vue2.js.map +1 -1
  94. package/dist/esm/components/CnFormPage/CnFormPage.vue3.js +74 -49
  95. package/dist/esm/components/CnFormPage/CnFormPage.vue3.js.map +1 -1
  96. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +333 -73
  97. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  98. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +79 -17
  99. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  100. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue.js +10 -0
  101. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue.js.map +1 -0
  102. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue2.js +99 -0
  103. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue2.js.map +1 -0
  104. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue3.js +48 -0
  105. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue3.js.map +1 -0
  106. package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue2.js +7 -7
  107. package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue2.js.map +1 -1
  108. package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue3.js.map +1 -1
  109. package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue2.js +1 -1
  110. package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue2.js.map +1 -1
  111. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +65 -2
  112. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
  113. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js +6 -2
  114. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
  115. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js +63 -1
  116. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
  117. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +8 -1
  118. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
  119. package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue2.js +14 -0
  120. package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue2.js.map +1 -1
  121. package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue3.js +5 -1
  122. package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue3.js.map +1 -1
  123. package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue2.js +29 -1
  124. package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue2.js.map +1 -1
  125. package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue3.js.map +1 -1
  126. package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue.js +10 -0
  127. package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue.js.map +1 -0
  128. package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue2.js +174 -0
  129. package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue2.js.map +1 -0
  130. package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue3.js +75 -0
  131. package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue3.js.map +1 -0
  132. package/dist/esm/components/CnRadioCardsWidget/index.js +28 -0
  133. package/dist/esm/components/CnRadioCardsWidget/index.js.map +1 -0
  134. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js +10 -0
  135. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js.map +1 -0
  136. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +411 -0
  137. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -0
  138. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js +178 -0
  139. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -0
  140. package/dist/esm/components/CnRecordListWidget/index.js +31 -0
  141. package/dist/esm/components/CnRecordListWidget/index.js.map +1 -0
  142. package/dist/esm/components/CnSavedViewsWidget/CnSavedViewsWidget.vue.js +10 -0
  143. package/dist/esm/components/CnSavedViewsWidget/CnSavedViewsWidget.vue.js.map +1 -0
  144. package/dist/esm/components/CnSavedViewsWidget/CnSavedViewsWidget.vue2.js +222 -0
  145. package/dist/esm/components/CnSavedViewsWidget/CnSavedViewsWidget.vue2.js.map +1 -0
  146. package/dist/esm/components/CnSavedViewsWidget/CnSavedViewsWidget.vue3.js +64 -0
  147. package/dist/esm/components/CnSavedViewsWidget/CnSavedViewsWidget.vue3.js.map +1 -0
  148. package/dist/esm/components/CnSavedViewsWidget/index.js +30 -0
  149. package/dist/esm/components/CnSavedViewsWidget/index.js.map +1 -0
  150. package/dist/esm/components/CnSectionsWidget/CnSectionsWidget.vue.js +10 -0
  151. package/dist/esm/components/CnSectionsWidget/CnSectionsWidget.vue.js.map +1 -0
  152. package/dist/esm/components/CnSectionsWidget/CnSectionsWidget.vue2.js +351 -0
  153. package/dist/esm/components/CnSectionsWidget/CnSectionsWidget.vue2.js.map +1 -0
  154. package/dist/esm/components/CnSectionsWidget/CnSectionsWidget.vue3.js +71 -0
  155. package/dist/esm/components/CnSectionsWidget/CnSectionsWidget.vue3.js.map +1 -0
  156. package/dist/esm/components/CnSectionsWidget/index.js +35 -0
  157. package/dist/esm/components/CnSectionsWidget/index.js.map +1 -0
  158. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue2.js.map +1 -1
  159. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue3.js.map +1 -1
  160. package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue.js +10 -0
  161. package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue.js.map +1 -0
  162. package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue2.js +347 -0
  163. package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue2.js.map +1 -0
  164. package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue3.js +184 -0
  165. package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue3.js.map +1 -0
  166. package/dist/esm/components/CnSettingsFormWidget/index.js +27 -0
  167. package/dist/esm/components/CnSettingsFormWidget/index.js.map +1 -0
  168. package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue2.js +81 -2
  169. package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue2.js.map +1 -1
  170. package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue3.js.map +1 -1
  171. package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js +87 -6
  172. package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js.map +1 -1
  173. package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js +1 -0
  174. package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js.map +1 -1
  175. package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue2.js +84 -1
  176. package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue2.js.map +1 -1
  177. package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue3.js +85 -41
  178. package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue3.js.map +1 -1
  179. package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue.js +10 -0
  180. package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue.js.map +1 -0
  181. package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue2.js +159 -0
  182. package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue2.js.map +1 -0
  183. package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue3.js +60 -0
  184. package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue3.js.map +1 -0
  185. package/dist/esm/components/CnSwitchRowsWidget/index.js +26 -0
  186. package/dist/esm/components/CnSwitchRowsWidget/index.js.map +1 -0
  187. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +168 -7
  188. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
  189. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js +86 -23
  190. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
  191. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue2.js +80 -1
  192. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue2.js.map +1 -1
  193. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue3.js +75 -32
  194. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue3.js.map +1 -1
  195. package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue.js +10 -0
  196. package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue.js.map +1 -0
  197. package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue2.js +269 -0
  198. package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue2.js.map +1 -0
  199. package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue3.js +106 -0
  200. package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue3.js.map +1 -0
  201. package/dist/esm/components/CnToggleTableWidget/index.js +27 -0
  202. package/dist/esm/components/CnToggleTableWidget/index.js.map +1 -0
  203. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +11 -0
  204. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
  205. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js +7 -2
  206. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js.map +1 -1
  207. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue3.js.map +1 -1
  208. package/dist/esm/components/CnWidgetObjectTable/dashboardRegistration.js +5 -0
  209. package/dist/esm/components/CnWidgetObjectTable/dashboardRegistration.js.map +1 -1
  210. package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue2.js +301 -6
  211. package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue2.js.map +1 -1
  212. package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue3.js +153 -55
  213. package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue3.js.map +1 -1
  214. package/dist/esm/composables/useFormDraft.js +140 -9
  215. package/dist/esm/composables/useFormDraft.js.map +1 -1
  216. package/dist/esm/composables/useListView.js +4 -4
  217. package/dist/esm/composables/useListView.js.map +1 -1
  218. package/dist/esm/index.js +21 -1
  219. package/dist/esm/index.js.map +1 -1
  220. package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionCard.vue2.js +28 -20
  221. package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionCard.vue2.js.map +1 -1
  222. package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionCard.vue3.js +9 -0
  223. package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionCard.vue3.js.map +1 -1
  224. package/dist/esm/integrations/builtin/field-inspection.js +21 -0
  225. package/dist/esm/integrations/builtin/field-inspection.js.map +1 -1
  226. package/dist/esm/integrations/offline/checklistSubmission.js +141 -0
  227. package/dist/esm/integrations/offline/checklistSubmission.js.map +1 -0
  228. package/dist/esm/integrations/offline/fieldCollectionHelpers.js +70 -5
  229. package/dist/esm/integrations/offline/fieldCollectionHelpers.js.map +1 -1
  230. package/dist/esm/integrations/offline/index.js +2 -1
  231. package/dist/esm/integrations/offline/index.js.map +1 -1
  232. package/dist/esm/integrations/offline/offlineDb.js +6 -1
  233. package/dist/esm/integrations/offline/offlineDb.js.map +1 -1
  234. package/dist/esm/integrations/offline/syncReplayService.js +8 -2
  235. package/dist/esm/integrations/offline/syncReplayService.js.map +1 -1
  236. package/dist/esm/l10n/en.json.js +20 -1
  237. package/dist/esm/l10n/en.json.js.map +1 -1
  238. package/dist/esm/l10n/nl.json.js +70 -2
  239. package/dist/esm/l10n/nl.json.js.map +1 -1
  240. package/dist/esm/nextcloud-vue.css +2192 -222
  241. package/dist/esm/utils/contentHeaderLink.js +51 -0
  242. package/dist/esm/utils/contentHeaderLink.js.map +1 -0
  243. package/dist/esm/utils/detailActionModel.js +60 -3
  244. package/dist/esm/utils/detailActionModel.js.map +1 -1
  245. package/dist/esm/utils/facets.js +75 -3
  246. package/dist/esm/utils/facets.js.map +1 -1
  247. package/dist/esm/utils/fetchFilterCounts.js +40 -5
  248. package/dist/esm/utils/fetchFilterCounts.js.map +1 -1
  249. package/dist/esm/utils/fillTemplate.js +120 -0
  250. package/dist/esm/utils/fillTemplate.js.map +1 -0
  251. package/dist/esm/utils/initials.js +32 -0
  252. package/dist/esm/utils/initials.js.map +1 -0
  253. package/dist/esm/utils/libraryWidgetKeys.js +11 -0
  254. package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
  255. package/dist/esm/utils/objectWrite.js +59 -0
  256. package/dist/esm/utils/objectWrite.js.map +1 -0
  257. package/dist/esm/utils/pageControls.js +81 -0
  258. package/dist/esm/utils/pageControls.js.map +1 -0
  259. package/dist/esm/utils/personalLenses.js +40 -3
  260. package/dist/esm/utils/personalLenses.js.map +1 -1
  261. package/dist/esm/utils/queryPresets.js +96 -0
  262. package/dist/esm/utils/queryPresets.js.map +1 -0
  263. package/dist/esm/utils/resolveFilterTokens.js +12 -1
  264. package/dist/esm/utils/resolveFilterTokens.js.map +1 -1
  265. package/dist/esm/utils/resolveManifestTokens.js +2 -0
  266. package/dist/esm/utils/resolveManifestTokens.js.map +1 -1
  267. package/dist/esm/utils/schema.js +21 -0
  268. package/dist/esm/utils/schema.js.map +1 -1
  269. package/dist/esm/utils/sentinelTokens.js +22 -4
  270. package/dist/esm/utils/sentinelTokens.js.map +1 -1
  271. package/dist/esm/utils/userGroupsPeek.js +91 -0
  272. package/dist/esm/utils/userGroupsPeek.js.map +1 -0
  273. package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
  274. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  275. package/dist/esm/utils/visibleWhen.js +57 -5
  276. package/dist/esm/utils/visibleWhen.js.map +1 -1
  277. package/dist/esm/utils/widgetDispatch.js +7 -1
  278. package/dist/esm/utils/widgetDispatch.js.map +1 -1
  279. package/dist/esm/utils/widgetVisibility.js +4 -57
  280. package/dist/esm/utils/widgetVisibility.js.map +1 -1
  281. package/dist/nextcloud-vue.cjs.js +19708 -9301
  282. package/dist/nextcloud-vue.cjs.js.map +2 -2
  283. package/dist/nextcloud-vue.css +2192 -222
  284. package/l10n/en.json +20 -1
  285. package/l10n/nl.json +70 -2
  286. package/package.json +2 -1
  287. package/scripts/check-docs-mdx.mjs +108 -0
  288. package/src/components/CnActionButtons/CnActionButtons.vue +2 -0
  289. package/src/components/CnActionsBar/CnActionsBar.md +4 -0
  290. package/src/components/CnActionsBar/CnActionsBar.vue +48 -11
  291. package/src/components/CnAppRoot/CnAppRoot.md +4 -2
  292. package/src/components/CnAppRoot/CnAppRoot.vue +151 -59
  293. package/src/components/CnBannerWidget/CnBannerWidget.vue +244 -23
  294. package/src/components/CnCellRenderer/CnCellRenderer.md +2 -0
  295. package/src/components/CnCellRenderer/CnCellRenderer.vue +235 -5
  296. package/src/components/CnChipsFieldWidget/CnChipsFieldWidget.vue +433 -0
  297. package/src/components/CnChipsFieldWidget/index.js +25 -0
  298. package/src/components/CnChoicePickerWidget/CnChoicePickerWidget.vue +556 -0
  299. package/src/components/CnChoicePickerWidget/index.js +26 -0
  300. package/src/components/CnConversationThread/CnConversationWidget.vue +1 -1
  301. package/src/components/CnDashboardGrid/CnDashboardGrid.vue +2 -2
  302. package/src/components/CnDashboardPage/CnDashboardPage.vue +78 -5
  303. package/src/components/CnDataTable/CnDataTable.vue +22 -12
  304. package/src/components/CnDetailPage/CnDetailPage.md +1 -1
  305. package/src/components/CnDetailPage/CnDetailPage.vue +152 -6
  306. package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +66 -0
  307. package/src/components/CnDocumentReviewList/CnDocumentReviewWidget.vue +1 -1
  308. package/src/components/CnFacetSidebar/CnFacetSidebar.vue +7 -7
  309. package/src/components/CnFactsWidget/CnFactsWidget.vue +1190 -0
  310. package/src/components/CnFactsWidget/index.js +29 -0
  311. package/src/components/CnFieldListWidget/CnFieldListWidget.vue +1008 -0
  312. package/src/components/CnFieldListWidget/index.js +28 -0
  313. package/src/components/CnFilesBrowser/CnFilesBrowser.vue +588 -117
  314. package/src/components/CnFilesBrowser/filesBrowser.js +100 -0
  315. package/src/components/CnFilesBrowser/filesBrowserColumns.js +140 -0
  316. package/src/components/CnFormDialog/CnFormDialog.md +9 -3
  317. package/src/components/CnFormDialog/CnFormDialog.vue +69 -21
  318. package/src/components/CnFormPage/CnFormPage.vue +99 -3
  319. package/src/components/CnIndexPage/CnIndexPage.vue +309 -9
  320. package/src/components/CnIndexPage/CnIndexPageWidgets.vue +169 -0
  321. package/src/components/CnIndexSidebar/CnIndexSidebar.vue +7 -7
  322. package/src/components/CnNextStepCard/CnNextStepWidget.vue +1 -1
  323. package/src/components/CnObjectListWidget/CnObjectListWidget.vue +69 -2
  324. package/src/components/CnObjectSidebar/CnFilesTab.vue +70 -1
  325. package/src/components/CnObjectSidebar/CnNotesTab.vue +16 -0
  326. package/src/components/CnPageRenderer/CnPageRenderer.vue +29 -1
  327. package/src/components/CnRadioCardsWidget/CnRadioCardsWidget.vue +313 -0
  328. package/src/components/CnRadioCardsWidget/index.js +27 -0
  329. package/src/components/CnRecordListWidget/CnRecordListWidget.vue +652 -0
  330. package/src/components/CnRecordListWidget/index.js +30 -0
  331. package/src/components/CnSavedViewsWidget/CnSavedViewsWidget.vue +296 -0
  332. package/src/components/CnSavedViewsWidget/index.js +29 -0
  333. package/src/components/CnSectionsWidget/CnSectionsWidget.vue +489 -0
  334. package/src/components/CnSectionsWidget/index.js +34 -0
  335. package/src/components/CnSegmentedControl/CnSegmentedControl.vue +13 -0
  336. package/src/components/CnSettingsFormWidget/CnSettingsFormWidget.vue +555 -0
  337. package/src/components/CnSettingsFormWidget/index.js +26 -0
  338. package/src/components/CnStagesWidget/CnStagesWidget.vue +81 -2
  339. package/src/components/CnStatWidget/CnStatWidget.vue +88 -7
  340. package/src/components/CnStatsBlockWidget/CnStatsBlockWidget.vue +180 -3
  341. package/src/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue +226 -0
  342. package/src/components/CnSwitchRowsWidget/index.js +25 -0
  343. package/src/components/CnTabsWidget/CnTabsWidget.vue +263 -8
  344. package/src/components/CnTasksWidget/CnTasksWidget.vue +175 -15
  345. package/src/components/CnToggleTableWidget/CnToggleTableWidget.vue +386 -0
  346. package/src/components/CnToggleTableWidget/index.js +26 -0
  347. package/src/components/CnWidgetGrid/registerDashboardWidgets.js +11 -0
  348. package/src/components/CnWidgetObjectTable/CnWidgetObjectTable.vue +7 -2
  349. package/src/components/CnWidgetObjectTable/dashboardRegistration.js +5 -0
  350. package/src/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue +493 -11
  351. package/src/components/index.js +19 -0
  352. package/src/composables/__tests__/useFormDraft.spec.js +69 -0
  353. package/src/composables/useFormDraft.js +139 -8
  354. package/src/composables/useListView.js +4 -4
  355. package/src/css/form-field.css +14 -0
  356. package/src/css/index-page.css +71 -0
  357. package/src/css/kpi-card.css +32 -0
  358. package/src/css/look-board-detail.css +113 -2
  359. package/src/css/look-board-index.css +90 -8
  360. package/src/css/look-board.css +109 -2
  361. package/src/css/table.css +19 -0
  362. package/src/index.js +19 -0
  363. package/src/integrations/builtin/field-inspection/CnFieldInspectionCard.vue +35 -20
  364. package/src/integrations/builtin/field-inspection.js +21 -0
  365. package/src/integrations/offline/checklistSubmission.js +137 -0
  366. package/src/integrations/offline/fieldCollectionHelpers.js +69 -4
  367. package/src/integrations/offline/index.js +3 -0
  368. package/src/integrations/offline/offlineDb.js +6 -1
  369. package/src/integrations/offline/syncReplayService.js +8 -2
  370. package/src/schemas/app-manifest-v2.schema.json +196 -13
  371. package/src/utils/contentHeaderLink.js +48 -0
  372. package/src/utils/detailActionModel.js +59 -2
  373. package/src/utils/facets.js +74 -2
  374. package/src/utils/fetchFilterCounts.js +39 -4
  375. package/src/utils/fillTemplate.js +116 -0
  376. package/src/utils/initials.js +29 -0
  377. package/src/utils/libraryWidgetKeys.js +11 -0
  378. package/src/utils/objectWrite.js +55 -0
  379. package/src/utils/pageControls.js +78 -0
  380. package/src/utils/personalLenses.js +39 -2
  381. package/src/utils/queryPresets.js +93 -0
  382. package/src/utils/resolveFilterTokens.js +14 -1
  383. package/src/utils/resolveManifestTokens.js +2 -0
  384. package/src/utils/schema.js +21 -0
  385. package/src/utils/sentinelTokens.js +22 -4
  386. package/src/utils/userGroupsPeek.js +87 -0
  387. package/src/utils/validateManifestV2.compiled.js +3 -3
  388. package/src/utils/visibleWhen.js +56 -4
  389. package/src/utils/widgetDispatch.js +7 -1
  390. package/src/utils/widgetVisibility.js +7 -56
@@ -37,14 +37,29 @@ export function normalizeFacetBucket(bucket) {
37
37
  return option
38
38
  }
39
39
 
40
+ /**
41
+ * The option id that stands for "this property holds no value".
42
+ *
43
+ * Chosen so no real bucket key can collide with it. Selecting it is sent to
44
+ * OpenRegister as `<property>_isnull=true` by {@link facetFilterParams}.
45
+ *
46
+ * @type {string}
47
+ */
48
+ export const MISSING_VALUE = '__cn_missing__'
49
+
40
50
  /**
41
51
  * Normalise the `facets` block of a collection response into sidebar format.
42
52
  *
43
53
  * Fields whose facet carries no bucket list are skipped entirely, so a caller
44
54
  * can tell an unfaceted field from one that faceted to nothing.
45
55
  *
56
+ * What OpenRegister puts beside the buckets is carried through rather than
57
+ * dropped: `missing` (how many objects in scope hold no value, sent as
58
+ * `{ results }` or `{ count }`), `type` and `title`. A missing count is only
59
+ * set when the backend sent one, for the same reason a bucket count is.
60
+ *
46
61
  * @param {object} facets The `facets` block of an API response
47
- * @return {object} `{ fieldName: { values: [{ value, count?, label? }] } }`
62
+ * @return {object} `{ fieldName: { values: [{ value, count?, label? }], missing?, type?, title? } }`
48
63
  */
49
64
  export function normalizeFacets(facets) {
50
65
  const transformed = {}
@@ -54,12 +69,69 @@ export function normalizeFacets(facets) {
54
69
  if (!buckets) {
55
70
  continue
56
71
  }
57
- transformed[key] = { values: buckets.map(normalizeFacetBucket) }
72
+ const normalized = { values: buckets.map(normalizeFacetBucket) }
73
+
74
+ const missing = facet.missing?.results ?? facet.missing?.count
75
+ if (typeof missing === 'number') {
76
+ normalized.missing = missing
77
+ }
78
+ for (const name of ['type', 'title']) {
79
+ if (facet[name] !== undefined && facet[name] !== null) {
80
+ normalized[name] = facet[name]
81
+ }
82
+ }
83
+
84
+ transformed[key] = normalized
58
85
  }
59
86
 
60
87
  return transformed
61
88
  }
62
89
 
90
+ /**
91
+ * The sidebar options for one normalised facet, with the missing-value option
92
+ * appended when some objects hold no value.
93
+ *
94
+ * @param {object|undefined} facet A normalised facet from {@link normalizeFacets}
95
+ * @param {function(number): string} missingLabel Renders the missing option's label from its count
96
+ * @return {Array<{id: unknown, label: string}>|null} The options, or null when the
97
+ * facet offers nothing, so the caller can fall back to its static options
98
+ */
99
+ export function facetOptions(facet, missingLabel) {
100
+ const options = (facet?.values || []).map((v) => ({
101
+ id: v.value,
102
+ label: facetOptionLabel(v),
103
+ }))
104
+
105
+ if (typeof facet?.missing === 'number' && facet.missing > 0) {
106
+ options.push({ id: MISSING_VALUE, label: missingLabel(facet.missing) })
107
+ }
108
+
109
+ return options.length > 0 ? options : null
110
+ }
111
+
112
+ /**
113
+ * The query parameters one facet selection sends.
114
+ *
115
+ * An ordinary selection is the property itself, one value unwrapped and
116
+ * several as a list. The missing-value option is exclusive: no object both
117
+ * holds one of the chosen values and holds none, so combining them would
118
+ * always answer an empty list. It is sent as `<property>_isnull=true`.
119
+ *
120
+ * @param {string} key The filtered property
121
+ * @param {Array} values The selected option ids
122
+ * @return {object} The parameters to merge into the request
123
+ */
124
+ export function facetFilterParams(key, values) {
125
+ if (!Array.isArray(values) || values.length === 0) {
126
+ return {}
127
+ }
128
+ if (values.includes(MISSING_VALUE)) {
129
+ return { [`${key}_isnull`]: 'true' }
130
+ }
131
+
132
+ return { [key]: values.length === 1 ? values[0] : values }
133
+ }
134
+
63
135
  /**
64
136
  * Build the visible label for one faceted filter option.
65
137
  *
@@ -9,8 +9,9 @@
9
9
  * `status: closed`, ...). Those are answered by one `/grouped` request on that
10
10
  * field. A filter that narrows on something else (two fields, an operator, a
11
11
  * list of values) cannot be read off a grouped result, so each of those costs
12
- * one `/value` count. A filter that holds a LIST of values is counted through
13
- * the list endpoint, because the aggregation endpoints cannot express it.
12
+ * one `/value` count. A filter that holds a LIST of values, or tests a field
13
+ * for no value (`isnull`), is counted through the list endpoint, because the
14
+ * aggregation endpoints cannot express it.
14
15
  * Nothing is requested for an entry that did not ask.
15
16
  *
16
17
  * @module utils/fetchFilterCounts
@@ -38,6 +39,37 @@ export function hasListValue(filter) {
38
39
  return Object.values(filter).some((value) => Array.isArray(value))
39
40
  }
40
41
 
42
+ /**
43
+ * Whether a filter tests a field for "no value" (`{ assignee: null }` or
44
+ * `{ supersededBy: { isnull: true } }`).
45
+ *
46
+ * OpenRegister's aggregation endpoints have no null operator: `isnull` is
47
+ * refused there and an empty value is dropped or matches nothing, so such a
48
+ * filter counted 0 while the list showed rows. The list endpoint does read
49
+ * `field[isnull]=true`, so a filter like this is counted through it.
50
+ *
51
+ * @param {object} filter A filter map.
52
+ * @return {boolean} True when any value is null or carries an `isnull` operator.
53
+ */
54
+ export function hasNullTest(filter) {
55
+ if (!filter || typeof filter !== 'object') {
56
+ return false
57
+ }
58
+ return Object.values(filter).some((value) => value === null
59
+ || (value && typeof value === 'object' && !Array.isArray(value) && Object.keys(value).some((op) => op.toLowerCase() === 'isnull')))
60
+ }
61
+
62
+ /**
63
+ * Whether the aggregation endpoints cannot express a filter, so it is counted
64
+ * through the list endpoint: a list of values, or a test for no value.
65
+ *
66
+ * @param {object} filter A filter map.
67
+ * @return {boolean} True when the filter needs the list endpoint.
68
+ */
69
+ export function needsListEndpoint(filter) {
70
+ return hasListValue(filter) || hasNullTest(filter)
71
+ }
72
+
41
73
  /**
42
74
  * Count the records a filter matches by asking the LIST endpoint for one
43
75
  * record and reading its `total`. The filter is sent the way the list request
@@ -59,6 +91,9 @@ export async function fetchListTotal(register, schema, filter, ctx) {
59
91
  for (const [op, operand] of Object.entries(value)) {
60
92
  params[`${key}[${op}]`] = operand
61
93
  }
94
+ } else if (value === null) {
95
+ // "No value": the list endpoint reads it as the isnull operator.
96
+ params[`${key}[isnull]`] = 'true'
62
97
  } else {
63
98
  params[key] = value
64
99
  }
@@ -125,12 +160,12 @@ export async function fetchFilterCounts({ register, schema, entries, baseFilter,
125
160
  // and `/value` returns exactly its number.
126
161
  // A list of values anywhere in a request rules the aggregation endpoints
127
162
  // out for it. A base filter with one rules them out for every entry.
128
- const baseHasList = hasListValue(base)
163
+ const baseHasList = needsListEndpoint(base)
129
164
  const byField = {}
130
165
  const single = []
131
166
  const viaList = []
132
167
  for (const entry of entries) {
133
- if (baseHasList || hasListValue(entry.filter)) {
168
+ if (baseHasList || needsListEndpoint(entry.filter)) {
134
169
  viaList.push(entry)
135
170
  continue
136
171
  }
@@ -0,0 +1,116 @@
1
+ // SPDX-FileCopyrightText: 2026 Conduction B.V.
2
+ // SPDX-License-Identifier: EUPL-1.2
3
+
4
+ import { boardDateLocale, formatBoardDate } from './boardDate.js'
5
+ import { parseDateValue } from './dateVariant.js'
6
+ import { readPath } from './readPath.js'
7
+
8
+ const TOKEN = /\{([^{}\s|]+)(?:\|([a-z]+))?\}/g
9
+
10
+ /**
11
+ * A moment in the board's short form with the time: "4 okt, 10:52", or
12
+ * "14 feb 2024, 09:00" outside the current year.
13
+ *
14
+ * @param {unknown} value A Date, a timestamp or an ISO string.
15
+ * @param {Date} [now] The reference moment.
16
+ * @return {string} The text, or '' when the value is not a date.
17
+ * @spec openspec/changes/screens-relation-pages/specs/relation-pages/spec.md#requirement-a-record-list-shows-the-objects-that-point-at-this-record
18
+ */
19
+ export function formatBoardDateTime(value, now = new Date()) {
20
+ const day = formatBoardDate(value, now)
21
+ const date = parseDateValue(value)
22
+ if (day === '' || date === null) {
23
+ return ''
24
+ }
25
+ let time
26
+ try {
27
+ time = new Intl.DateTimeFormat(boardDateLocale(), { hour: '2-digit', minute: '2-digit' }).format(date)
28
+ } catch {
29
+ time = new Intl.DateTimeFormat(undefined, { hour: '2-digit', minute: '2-digit' }).format(date)
30
+ }
31
+ return `${day}, ${time}`
32
+ }
33
+
34
+ /**
35
+ * Format one token value.
36
+ *
37
+ * @param {unknown} value The raw value.
38
+ * @param {string} format `date`, `datetime` or '' (as is).
39
+ * @param {Date} now The reference moment.
40
+ * @return {string} The text, '' when nothing can be shown.
41
+ */
42
+ function formatValue(value, format, now) {
43
+ if (value === null || value === undefined || value === '' || typeof value === 'object') {
44
+ return ''
45
+ }
46
+ if (format === 'date') {
47
+ return formatBoardDate(value, now)
48
+ }
49
+ if (format === 'datetime') {
50
+ return formatBoardDateTime(value, now)
51
+ }
52
+ if (format === 'days') {
53
+ // Whole days from the date to now (0 for today; negative for a future date).
54
+ const date = parseDateValue(value)
55
+ return date === null ? '' : String(Math.floor((now.getTime() - date.getTime()) / 86400000))
56
+ }
57
+ return String(value)
58
+ }
59
+
60
+ /**
61
+ * Fill a row template such as `"{identifier} · {caseTypeName}"` or
62
+ * `"Termijn {deadline|date}"` from an object.
63
+ *
64
+ * Every `{path}` token is a dot path into the object; `{path|date}` and
65
+ * `{path|datetime}` format it as the boards write dates, and `{path|days}` is the
66
+ * whole days since that date ("has waited {createdAt|days} days"). A token without a
67
+ * value empties the WHOLE template, because "Termijn " is worse than nothing.
68
+ * A template without tokens is returned as written (a constant label).
69
+ *
70
+ * @param {string} template The template, already translated.
71
+ * @param {object|null} object The object to read.
72
+ * @param {Date} [now] The reference moment for dates.
73
+ * @return {string} The filled text, or ''.
74
+ * @spec openspec/changes/screens-relation-pages/specs/relation-pages/spec.md#requirement-a-record-list-shows-the-objects-that-point-at-this-record
75
+ */
76
+ export function fillTemplate(template, object, now = new Date()) {
77
+ if (typeof template !== 'string' || template.trim() === '') {
78
+ return ''
79
+ }
80
+ let missing = false
81
+ const text = template.replace(TOKEN, (_match, path, format) => {
82
+ const value = object && typeof object === 'object' ? readPath(object, path) : undefined
83
+ const shown = formatValue(value, format || '', now)
84
+ if (shown === '') {
85
+ missing = true
86
+ }
87
+ return shown
88
+ })
89
+ return missing ? '' : text.trim()
90
+ }
91
+
92
+ /**
93
+ * The first of several templates that fills: `["Klaar {closedAt|date}",
94
+ * "Termijn {deadline|date}"]` reads "Klaar ..." once the case is closed and
95
+ * "Termijn ..." while it is open. A single string is one candidate.
96
+ *
97
+ * @param {string|string[]} templates The candidates, in order.
98
+ * @param {object|null} object The object to read.
99
+ * @param {(key: string) => string} [translate] Applied to each template before filling.
100
+ * @param {Date} [now] The reference moment for dates.
101
+ * @return {string} The first filled text, or ''.
102
+ * @spec openspec/changes/screens-relation-pages/specs/relation-pages/spec.md#requirement-a-record-list-shows-the-objects-that-point-at-this-record
103
+ */
104
+ export function firstFilled(templates, object, translate = (key) => key, now = new Date()) {
105
+ const list = Array.isArray(templates) ? templates : [templates]
106
+ for (const template of list) {
107
+ if (typeof template !== 'string') {
108
+ continue
109
+ }
110
+ const text = fillTemplate(translate(template), object, now)
111
+ if (text !== '') {
112
+ return text
113
+ }
114
+ }
115
+ return ''
116
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * SPDX-FileCopyrightText: 2026 Conduction B.V.
3
+ * SPDX-License-Identifier: EUPL-1.2
4
+ *
5
+ * initials: the letters an avatar shows for a free name.
6
+ *
7
+ * @module utils/initials
8
+ */
9
+
10
+ /**
11
+ * The initials of a name: the first letters of its first and its last word,
12
+ * upper-cased ("Sanne de Vries" reads "SV", "Jan" reads "J").
13
+ *
14
+ * @param {unknown} name The name.
15
+ * @return {string} One or two letters, or '' when there is no name.
16
+ * @spec openspec/changes/screens-record-list-avatar/specs/record-list-avatar/spec.md#requirement-a-record-list-row-can-carry-an-avatar
17
+ */
18
+ export function initialsOf(name) {
19
+ if (typeof name !== 'string') {
20
+ return ''
21
+ }
22
+ const words = name.trim().split(/\s+/).filter((word) => word !== '')
23
+ if (words.length === 0) {
24
+ return ''
25
+ }
26
+ const first = Array.from(words[0])[0] || ''
27
+ const last = words.length > 1 ? (Array.from(words[words.length - 1])[0] || '') : ''
28
+ return (first + last).toLocaleUpperCase()
29
+ }
@@ -93,6 +93,8 @@ export const DASHBOARD_CATALOG_WIDGET_KEYS = Object.freeze([
93
93
  'banner',
94
94
  'calendar',
95
95
  'chart',
96
+ 'chips-field',
97
+ 'choice-picker',
96
98
  'container',
97
99
  'conversation',
98
100
  'countdown',
@@ -100,6 +102,8 @@ export const DASHBOARD_CATALOG_WIDGET_KEYS = Object.freeze([
100
102
  'delta',
101
103
  'divider',
102
104
  'document-review',
105
+ 'facts',
106
+ 'field-list',
103
107
  'files',
104
108
  'flow-runs',
105
109
  'gauge',
@@ -123,19 +127,26 @@ export const DASHBOARD_CATALOG_WIDGET_KEYS = Object.freeze([
123
127
  'object-table',
124
128
  'people',
125
129
  'quicklinks',
130
+ 'radio-cards',
131
+ 'record-list',
126
132
  'related',
127
133
  'saved-view',
134
+ 'saved-views',
135
+ 'sections',
136
+ 'settings-form',
128
137
  'spend-analytics',
129
138
  'stacked-bar',
130
139
  'stages',
131
140
  'stat',
132
141
  'stats-block',
142
+ 'switch-rows',
133
143
  'table',
134
144
  'tabs',
135
145
  'tasks',
136
146
  'text',
137
147
  'tile',
138
148
  'timeline',
149
+ 'toggle-table',
139
150
  'video',
140
151
  'week-strip',
141
152
  'workspace-filter',
@@ -0,0 +1,55 @@
1
+ /**
2
+ * SPDX-FileCopyrightText: 2026 Conduction B.V.
3
+ * SPDX-License-Identifier: EUPL-1.2
4
+ *
5
+ * objectWrite: the one-object PATCH the settings widgets share.
6
+ *
7
+ * @module utils/objectWrite
8
+ */
9
+
10
+ import { prefixUrl } from './headers.js'
11
+
12
+ /**
13
+ * The OpenRegister URL of one object, or '' when register, schema or id is missing.
14
+ *
15
+ * @param {string|object} register Register slug.
16
+ * @param {string|object} schema Schema slug.
17
+ * @param {string|number} id Object id.
18
+ * @return {string} The object URL.
19
+ */
20
+ export function objectUrlOf(register, schema, id) {
21
+ if (typeof register !== 'string' || typeof schema !== 'string' || !register || !schema || id === undefined || id === null || id === '') {
22
+ return ''
23
+ }
24
+ return `/apps/openregister/api/objects/${encodeURIComponent(register)}/${encodeURIComponent(schema)}/${encodeURIComponent(String(id))}`
25
+ }
26
+
27
+ /**
28
+ * PATCH only the given fields of one object.
29
+ *
30
+ * @param {string} url The object URL (see objectUrlOf).
31
+ * @param {object} patch The fields to write.
32
+ * @return {Promise<object>} The object the server answered with.
33
+ */
34
+ export async function patchObject(url, patch) {
35
+ const { default: axios } = await import('@nextcloud/axios')
36
+ const { data } = await axios.patch(prefixUrl(url), patch)
37
+ return data && typeof data === 'object' ? data : {}
38
+ }
39
+
40
+ /**
41
+ * The fields of `next` that differ from `base`, compared by value.
42
+ *
43
+ * @param {object} base What the server last held.
44
+ * @param {object} next What the form holds.
45
+ * @return {object} The changed fields only.
46
+ */
47
+ export function changedFields(base, next) {
48
+ const patch = {}
49
+ for (const [key, value] of Object.entries(next)) {
50
+ if (JSON.stringify(value === undefined ? null : value) !== JSON.stringify(base[key] === undefined ? null : base[key])) {
51
+ patch[key] = value
52
+ }
53
+ }
54
+ return patch
55
+ }
@@ -0,0 +1,78 @@
1
+ /**
2
+ * SPDX-FileCopyrightText: 2026 Conduction B.V.
3
+ * SPDX-License-Identifier: EUPL-1.2
4
+ *
5
+ * pageControls: how a list widget listens to the page's controls.
6
+ *
7
+ * A page control (a `workspace-filter` select or switch) writes a value to the
8
+ * page workspace. A list widget declares which control it listens to and what
9
+ * the value means for it:
10
+ *
11
+ * pageControls: {
12
+ * sort: { key: "sort", map: { deadline: { field: "slaDeadline", dir: "asc" } } },
13
+ * toggle: { key: "showDone", removes: ["status"] }
14
+ * }
15
+ *
16
+ * `sort`: the workspace value at `key` picks an entry of `map` (the sort the
17
+ * list uses); a value with no entry leaves the list's own sort.
18
+ * `toggle`: while the workspace value at `key` is on (true, "1", "true"), the
19
+ * filter keys in `removes` are dropped, so a list that hides finished work
20
+ * shows it.
21
+ *
22
+ * @module utils/pageControls
23
+ */
24
+
25
+ /**
26
+ * Whether a workspace value reads as on.
27
+ *
28
+ * @param {unknown} value The value.
29
+ * @return {boolean} True for true, 1, "1" and "true".
30
+ */
31
+ export function isOn(value) {
32
+ return value === true || value === 1 || value === '1' || value === 'true'
33
+ }
34
+
35
+ /**
36
+ * The sort a list uses once the page's sort control is read.
37
+ *
38
+ * @param {object|null} pageControls The widget's `pageControls`.
39
+ * @param {object} workspace The plain workspace bag.
40
+ * @param {{field: string, dir?: string}|null} own The list's own sort.
41
+ * @return {{field: string, dir?: string}|null} The sort to use.
42
+ * @spec openspec/changes/screens-page-controls/specs/page-controls/spec.md#requirement-a-list-listens-to-the-pages-controls
43
+ */
44
+ export function pageSort(pageControls, workspace, own) {
45
+ const listen = pageControls && typeof pageControls === 'object' ? pageControls.sort : null
46
+ if (!listen || typeof listen.key !== 'string' || !listen.map || typeof listen.map !== 'object') {
47
+ return own
48
+ }
49
+ const chosen = workspace ? workspace[listen.key] : undefined
50
+ const entry = chosen !== undefined && chosen !== null ? listen.map[String(chosen)] : null
51
+ return entry && typeof entry === 'object' && typeof entry.field === 'string' && entry.field !== '' ? entry : own
52
+ }
53
+
54
+ /**
55
+ * The filter a list uses once the page's toggle is read: with the toggle on,
56
+ * the keys it `removes` are gone.
57
+ *
58
+ * @param {object|null} pageControls The widget's `pageControls`.
59
+ * @param {object} workspace The plain workspace bag.
60
+ * @param {object} filter The list's own filter (before token resolution).
61
+ * @return {object} The filter to use (the same object when nothing is removed).
62
+ * @spec openspec/changes/screens-page-controls/specs/page-controls/spec.md#requirement-a-list-listens-to-the-pages-controls
63
+ */
64
+ export function pageFilter(pageControls, workspace, filter) {
65
+ const listen = pageControls && typeof pageControls === 'object' ? pageControls.toggle : null
66
+ const base = filter && typeof filter === 'object' ? filter : {}
67
+ if (!listen || typeof listen.key !== 'string' || !Array.isArray(listen.removes)) {
68
+ return base
69
+ }
70
+ if (!isOn(workspace ? workspace[listen.key] : undefined)) {
71
+ return base
72
+ }
73
+ const out = { ...base }
74
+ for (const key of listen.removes) {
75
+ delete out[key]
76
+ }
77
+ return out
78
+ }
@@ -37,6 +37,42 @@ export function personalLensTabs(lenses) {
37
37
  .map((id) => ({ label: labels[id], filter: { [LENS_FILTER_KEYS[id]]: true }, lens: id }))
38
38
  }
39
39
 
40
+ /**
41
+ * Turn the `empty` / `notEmpty` shorthand of quick filters into a filter.
42
+ *
43
+ * `{ label: "Not logged in yet", empty: "lastLoginAt" }` is the rows where the
44
+ * field has no value: the filter `{ lastLoginAt: { isnull: true } }`, which
45
+ * OpenRegister reads as `lastLoginAt[isnull]=true`. `notEmpty` is the rows
46
+ * where it has one. Either takes a field or a list of fields and merges with
47
+ * the entry's own `filter`. Returns the same array when no entry uses it.
48
+ *
49
+ * @param {Array<object>|null} quickFilters The page's quick filters.
50
+ * @return {Array<object>|null} The quick filters with the shorthand expanded.
51
+ */
52
+ export function expandEmptyFilters(quickFilters) {
53
+ if (!Array.isArray(quickFilters)) {
54
+ return quickFilters
55
+ }
56
+ const uses = (tab) => tab && typeof tab === 'object' && (tab.empty || tab.notEmpty)
57
+ if (!quickFilters.some(uses)) {
58
+ return quickFilters
59
+ }
60
+ const fieldsOf = (value) => (Array.isArray(value) ? value : [value]).filter((f) => typeof f === 'string' && f !== '')
61
+ return quickFilters.map((tab) => {
62
+ if (!uses(tab)) {
63
+ return tab
64
+ }
65
+ const filter = { ...(tab.filter && typeof tab.filter === 'object' ? tab.filter : {}) }
66
+ for (const field of fieldsOf(tab.empty)) {
67
+ filter[field] = { isnull: true }
68
+ }
69
+ for (const field of fieldsOf(tab.notEmpty)) {
70
+ filter[field] = { isnull: false }
71
+ }
72
+ return { ...tab, filter }
73
+ })
74
+ }
75
+
40
76
  /**
41
77
  * The page's quick filters with its personal lenses appended. A page with no
42
78
  * quick filters of its own gets an "All" tab first, so a lens is not active
@@ -48,10 +84,11 @@ export function personalLensTabs(lenses) {
48
84
  */
49
85
  export function withPersonalLenses(quickFilters, lenses) {
50
86
  const tabs = personalLensTabs(lenses)
87
+ const expanded = expandEmptyFilters(quickFilters)
51
88
  if (tabs.length === 0) {
52
- return quickFilters
89
+ return expanded
53
90
  }
54
- const own = Array.isArray(quickFilters) ? quickFilters : []
91
+ const own = Array.isArray(expanded) ? expanded : []
55
92
  return own.length > 0
56
93
  ? [...own, ...tabs]
57
94
  : [{ label: t('nextcloud-vue', 'All'), filter: {}, default: true }, ...tabs]
@@ -0,0 +1,93 @@
1
+ /**
2
+ * SPDX-FileCopyrightText: 2026 Conduction B.V.
3
+ * SPDX-License-Identifier: EUPL-1.2
4
+ *
5
+ * Query presets of an index page (`config.queryPresets`): a menu entry that
6
+ * deep-links to a list with a query (`{ caseType: '<uuid>' }`, the shape hydra
7
+ * gate-68 asks for instead of a second page) can bring its own lenses,
8
+ * columns and copy. The page names, per preset, the query it answers to and
9
+ * the keys it replaces; the first preset whose every `match` pair is in the
10
+ * current route query wins.
11
+ *
12
+ * @module utils/queryPresets
13
+ * @spec openspec/changes/screens-index-query-presets/specs/index-query-presets/spec.md#requirement-a-query-preset-overlays-lenses-columns-and-copy
14
+ */
15
+
16
+ /**
17
+ * The config keys a preset may replace. Anything else in a preset is ignored:
18
+ * a preset changes what the list shows, never which register or schema it
19
+ * reads or what it may write.
20
+ *
21
+ * @type {ReadonlyArray<string>}
22
+ */
23
+ export const QUERY_PRESET_KEYS = Object.freeze([
24
+ 'title',
25
+ 'quickFilters',
26
+ 'columns',
27
+ 'cardFields',
28
+ 'countText',
29
+ 'searchPlaceholder',
30
+ 'footerNote',
31
+ 'defaultSort',
32
+ 'viewSwitch',
33
+ ])
34
+
35
+ /**
36
+ * Whether one route query value satisfies one `match` value. A repeated query
37
+ * parameter (an array) matches when it holds the value.
38
+ *
39
+ * @param {unknown} actual The route query value.
40
+ * @param {string|number|boolean} expected The preset's value.
41
+ * @return {boolean} True when they match.
42
+ */
43
+ function queryValueMatches(actual, expected) {
44
+ const want = String(expected)
45
+ if (Array.isArray(actual)) {
46
+ return actual.some((v) => String(v) === want)
47
+ }
48
+ return actual !== undefined && actual !== null && String(actual) === want
49
+ }
50
+
51
+ /**
52
+ * The index of the first preset whose every `match` pair is in the query.
53
+ * A preset without a non-empty `match` never matches.
54
+ *
55
+ * @param {Array<object>} presets The page's `queryPresets`.
56
+ * @param {object} query The current route query.
57
+ * @return {number} The preset index, or -1.
58
+ */
59
+ export function matchQueryPreset(presets, query) {
60
+ if (!Array.isArray(presets)) {
61
+ return -1
62
+ }
63
+ const q = query && typeof query === 'object' ? query : {}
64
+ return presets.findIndex((preset) => {
65
+ const match = preset && typeof preset.match === 'object' && preset.match !== null ? preset.match : null
66
+ if (!match || Object.keys(match).length === 0) {
67
+ return false
68
+ }
69
+ return Object.entries(match).every(([key, value]) => queryValueMatches(q[key], value))
70
+ })
71
+ }
72
+
73
+ /**
74
+ * The page config with the preset's allowed keys laid over it, and without
75
+ * `queryPresets` itself (the page component has no such prop).
76
+ *
77
+ * @param {object} config The page config.
78
+ * @param {object|null} preset The matched preset, or null.
79
+ * @return {object} The effective config.
80
+ */
81
+ export function applyQueryPreset(config, preset) {
82
+ const { queryPresets, ...rest } = config || {}
83
+ if (!preset || typeof preset !== 'object') {
84
+ return rest
85
+ }
86
+ const out = { ...rest }
87
+ for (const key of QUERY_PRESET_KEYS) {
88
+ if (key !== 'title' && preset[key] !== undefined) {
89
+ out[key] = preset[key]
90
+ }
91
+ }
92
+ return out
93
+ }
@@ -22,6 +22,10 @@
22
22
  * - `@today±Nd` → relative-date arithmetic at day granularity: N days
23
23
  * from today (e.g. `@today-30d`, `@today+7d`)
24
24
  * - `@monthStart` → first day of the current month, `YYYY-MM-DD`
25
+ * - `@monthEnd` → last day of the current month, `YYYY-MM-DD`; on a
26
+ * date-time field use `lt: '@nextMonthStart'` instead,
27
+ * since `lte` a date stops at that day's midnight
28
+ * - `@nextMonthStart` → first day of the next month, `YYYY-MM-DD`
25
29
  * - `@quarterStart` → first day of the current quarter, `YYYY-MM-DD`
26
30
  * - `@yearStart` → first day of the current year, `YYYY-MM-DD`
27
31
  * - `@currentFiscalYear` → the current calendar year as a number string, e.g. `2026`
@@ -63,7 +67,9 @@
63
67
 
64
68
  import { getCurrentUser } from '@nextcloud/auth'
65
69
  import { TODAY_DELTA_RE } from './sentinelTokens.js'
66
- import { peekCurrentUserGroups } from './widgetVisibility.js'
70
+ // userGroupsPeek, not widgetVisibility: that one imports @nextcloud/axios, and
71
+ // this resolver runs on public pages and in node (see userGroupsPeek.js).
72
+ import { peekCurrentUserGroups } from './userGroupsPeek.js'
67
73
 
68
74
  /** The filter token for the current user's group ids. */
69
75
  export const MY_GROUPS_TOKEN = '@myGroups'
@@ -214,6 +220,13 @@ export function resolveFilterValue(v, ctx) {
214
220
  if (v === '@monthStart') {
215
221
  return ymd(new Date(now.getFullYear(), now.getMonth(), 1))
216
222
  }
223
+ if (v === '@monthEnd') {
224
+ // Day 0 of the next month is the last day of this one.
225
+ return ymd(new Date(now.getFullYear(), now.getMonth() + 1, 0))
226
+ }
227
+ if (v === '@nextMonthStart') {
228
+ return ymd(new Date(now.getFullYear(), now.getMonth() + 1, 1))
229
+ }
217
230
  if (v === '@quarterStart') {
218
231
  return ymd(new Date(now.getFullYear(), Math.floor(now.getMonth() / 3) * 3, 1))
219
232
  }