@conduction/nextcloud-vue 2.78.0 → 2.80.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 (259) hide show
  1. package/dist/bundled-packages.json +0 -2
  2. package/dist/esm/bundled-packages.json +0 -2
  3. package/dist/esm/components/CnActionButtons/CnActionButtons.vue2.js.map +1 -1
  4. package/dist/esm/components/CnActionButtons/CnActionButtons.vue3.js +3 -1
  5. package/dist/esm/components/CnActionButtons/CnActionButtons.vue3.js.map +1 -1
  6. package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js +32 -3
  7. package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js.map +1 -1
  8. package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js +115 -104
  9. package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js.map +1 -1
  10. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +12 -2
  11. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
  12. package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
  13. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js +39 -3
  14. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js.map +1 -1
  15. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue3.js.map +1 -1
  16. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +158 -0
  17. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
  18. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +157 -96
  19. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
  20. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +139 -1
  21. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
  22. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +140 -92
  23. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
  24. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +77 -0
  25. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
  26. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +5 -2
  27. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
  28. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +62 -4
  29. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  30. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  31. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js +85 -1
  32. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
  33. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js +43 -4
  34. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
  35. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue.js +11 -0
  36. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue.js.map +1 -0
  37. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js +736 -0
  38. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js.map +1 -0
  39. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js +238 -0
  40. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js.map +1 -0
  41. package/dist/esm/components/CnFactsWidget/index.js +30 -0
  42. package/dist/esm/components/CnFactsWidget/index.js.map +1 -0
  43. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue.js +10 -0
  44. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue.js.map +1 -0
  45. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue2.js +551 -0
  46. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue2.js.map +1 -0
  47. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue3.js +183 -0
  48. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue3.js.map +1 -0
  49. package/dist/esm/components/CnFieldListWidget/index.js +29 -0
  50. package/dist/esm/components/CnFieldListWidget/index.js.map +1 -0
  51. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +173 -5
  52. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
  53. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +181 -99
  54. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
  55. package/dist/esm/components/CnFilesBrowser/filesBrowser.js +29 -11
  56. package/dist/esm/components/CnFilesBrowser/filesBrowser.js.map +1 -1
  57. package/dist/esm/components/CnFilesBrowser/openRegisterSource.js +252 -0
  58. package/dist/esm/components/CnFilesBrowser/openRegisterSource.js.map +1 -0
  59. package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js +66 -18
  60. package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
  61. package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js +4 -4
  62. package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
  63. package/dist/esm/components/CnFormPage/CnFormPage.vue2.js +18 -3
  64. package/dist/esm/components/CnFormPage/CnFormPage.vue2.js.map +1 -1
  65. package/dist/esm/components/CnFormPage/CnFormPage.vue3.js.map +1 -1
  66. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +330 -74
  67. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  68. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +34 -15
  69. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  70. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue.js +10 -0
  71. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue.js.map +1 -0
  72. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue2.js +99 -0
  73. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue2.js.map +1 -0
  74. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue3.js +48 -0
  75. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue3.js.map +1 -0
  76. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js +100 -3
  77. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
  78. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +4 -2
  79. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
  80. package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue2.js +29 -1
  81. package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue2.js.map +1 -1
  82. package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue3.js.map +1 -1
  83. package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue2.js +3 -2
  84. package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue2.js.map +1 -1
  85. package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue3.js.map +1 -1
  86. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js +10 -0
  87. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js.map +1 -0
  88. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +387 -0
  89. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -0
  90. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js +169 -0
  91. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -0
  92. package/dist/esm/components/CnRecordListWidget/index.js +31 -0
  93. package/dist/esm/components/CnRecordListWidget/index.js.map +1 -0
  94. package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue2.js.map +1 -1
  95. package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue3.js +1 -1
  96. package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue3.js.map +1 -1
  97. package/dist/esm/components/CnSectionsWidget/CnSectionsWidget.vue.js +10 -0
  98. package/dist/esm/components/CnSectionsWidget/CnSectionsWidget.vue.js.map +1 -0
  99. package/dist/esm/components/CnSectionsWidget/CnSectionsWidget.vue2.js +351 -0
  100. package/dist/esm/components/CnSectionsWidget/CnSectionsWidget.vue2.js.map +1 -0
  101. package/dist/esm/components/CnSectionsWidget/CnSectionsWidget.vue3.js +71 -0
  102. package/dist/esm/components/CnSectionsWidget/CnSectionsWidget.vue3.js.map +1 -0
  103. package/dist/esm/components/CnSectionsWidget/index.js +35 -0
  104. package/dist/esm/components/CnSectionsWidget/index.js.map +1 -0
  105. package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue2.js +81 -2
  106. package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue2.js.map +1 -1
  107. package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue3.js.map +1 -1
  108. package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js.map +1 -1
  109. package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js +1 -0
  110. package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js.map +1 -1
  111. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +152 -8
  112. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
  113. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js +84 -23
  114. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
  115. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +4 -0
  116. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
  117. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js +13 -0
  118. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js.map +1 -1
  119. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue3.js.map +1 -1
  120. package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue2.js +106 -2
  121. package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue2.js.map +1 -1
  122. package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue3.js +105 -53
  123. package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue3.js.map +1 -1
  124. package/dist/esm/composables/useAppManifest.js +8 -3
  125. package/dist/esm/composables/useAppManifest.js.map +1 -1
  126. package/dist/esm/composables/useFormDraft.js +140 -9
  127. package/dist/esm/composables/useFormDraft.js.map +1 -1
  128. package/dist/esm/index.js +5 -0
  129. package/dist/esm/index.js.map +1 -1
  130. package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionCard.vue2.js +28 -20
  131. package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionCard.vue2.js.map +1 -1
  132. package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionCard.vue3.js +9 -0
  133. package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionCard.vue3.js.map +1 -1
  134. package/dist/esm/integrations/builtin/field-inspection.js +21 -0
  135. package/dist/esm/integrations/builtin/field-inspection.js.map +1 -1
  136. package/dist/esm/integrations/offline/checklistSubmission.js +141 -0
  137. package/dist/esm/integrations/offline/checklistSubmission.js.map +1 -0
  138. package/dist/esm/integrations/offline/fieldCollectionHelpers.js +70 -5
  139. package/dist/esm/integrations/offline/fieldCollectionHelpers.js.map +1 -1
  140. package/dist/esm/integrations/offline/index.js +2 -1
  141. package/dist/esm/integrations/offline/index.js.map +1 -1
  142. package/dist/esm/integrations/offline/offlineDb.js +6 -1
  143. package/dist/esm/integrations/offline/offlineDb.js.map +1 -1
  144. package/dist/esm/integrations/offline/syncReplayService.js +8 -2
  145. package/dist/esm/integrations/offline/syncReplayService.js.map +1 -1
  146. package/dist/esm/l10n/en.json.js +72 -1
  147. package/dist/esm/l10n/en.json.js.map +1 -1
  148. package/dist/esm/l10n/nl.json.js +120 -2
  149. package/dist/esm/l10n/nl.json.js.map +1 -1
  150. package/dist/esm/nextcloud-vue.css +1314 -74
  151. package/dist/esm/utils/boardDate.js +193 -0
  152. package/dist/esm/utils/boardDate.js.map +1 -0
  153. package/dist/esm/utils/detailActionModel.js +60 -3
  154. package/dist/esm/utils/detailActionModel.js.map +1 -1
  155. package/dist/esm/utils/dynamicProperties.js +5 -1
  156. package/dist/esm/utils/dynamicProperties.js.map +1 -1
  157. package/dist/esm/utils/fillTemplate.js +114 -0
  158. package/dist/esm/utils/fillTemplate.js.map +1 -0
  159. package/dist/esm/utils/libraryWidgetKeys.js +4 -0
  160. package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
  161. package/dist/esm/utils/queryPresets.js +96 -0
  162. package/dist/esm/utils/queryPresets.js.map +1 -0
  163. package/dist/esm/utils/resolveFilterTokens.js +11 -0
  164. package/dist/esm/utils/resolveFilterTokens.js.map +1 -1
  165. package/dist/esm/utils/schema.js +21 -0
  166. package/dist/esm/utils/schema.js.map +1 -1
  167. package/dist/esm/utils/sentinelTokens.js +3 -3
  168. package/dist/esm/utils/sentinelTokens.js.map +1 -1
  169. package/dist/esm/utils/validateManifestV2.compiled.js +57 -6
  170. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  171. package/dist/esm/utils/widgetDispatch.js +7 -1
  172. package/dist/esm/utils/widgetDispatch.js.map +1 -1
  173. package/dist/nextcloud-vue.cjs.js +14278 -8169
  174. package/dist/nextcloud-vue.cjs.js.map +2 -2
  175. package/dist/nextcloud-vue.css +1314 -74
  176. package/l10n/en.json +72 -1
  177. package/l10n/nl.json +120 -2
  178. package/package.json +1 -1
  179. package/scripts/build-validators.js +26 -0
  180. package/src/components/CnActionButtons/CnActionButtons.vue +2 -0
  181. package/src/components/CnActionsBar/CnActionsBar.md +4 -0
  182. package/src/components/CnActionsBar/CnActionsBar.vue +83 -42
  183. package/src/components/CnAppRoot/CnAppRoot.vue +12 -2
  184. package/src/components/CnBannerWidget/CnBannerWidget.vue +39 -3
  185. package/src/components/CnCellRenderer/CnCellRenderer.vue +306 -2
  186. package/src/components/CnDashboardPage/CnDashboardPage.vue +219 -4
  187. package/src/components/CnDataTable/CnDataTable.md +2 -0
  188. package/src/components/CnDataTable/CnDataTable.vue +82 -2
  189. package/src/components/CnDetailPage/CnDetailPage.md +1 -1
  190. package/src/components/CnDetailPage/CnDetailPage.vue +63 -3
  191. package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +127 -1
  192. package/src/components/CnFactsWidget/CnFactsWidget.vue +1050 -0
  193. package/src/components/CnFactsWidget/index.js +29 -0
  194. package/src/components/CnFieldListWidget/CnFieldListWidget.vue +746 -0
  195. package/src/components/CnFieldListWidget/index.js +28 -0
  196. package/src/components/CnFilesBrowser/CnFilesBrowser.vue +232 -11
  197. package/src/components/CnFilesBrowser/filesBrowser.js +43 -2
  198. package/src/components/CnFilesBrowser/index.js +1 -0
  199. package/src/components/CnFilesBrowser/openRegisterSource.js +248 -0
  200. package/src/components/CnFormDialog/CnFormDialog.md +9 -3
  201. package/src/components/CnFormDialog/CnFormDialog.vue +70 -22
  202. package/src/components/CnFormPage/CnFormPage.vue +18 -3
  203. package/src/components/CnIndexPage/CnIndexPage.vue +281 -9
  204. package/src/components/CnIndexPage/CnIndexPageWidgets.vue +169 -0
  205. package/src/components/CnObjectSidebar/CnFilesTab.vue +103 -4
  206. package/src/components/CnPageRenderer/CnPageRenderer.vue +29 -1
  207. package/src/components/CnQuickFilterBar/CnQuickFilterBar.vue +3 -2
  208. package/src/components/CnRecordListWidget/CnRecordListWidget.vue +603 -0
  209. package/src/components/CnRecordListWidget/index.js +30 -0
  210. package/src/components/CnSavedViewsControl/CnSavedViewsControl.vue +1 -1
  211. package/src/components/CnSectionsWidget/CnSectionsWidget.vue +489 -0
  212. package/src/components/CnSectionsWidget/index.js +34 -0
  213. package/src/components/CnStagesWidget/CnStagesWidget.vue +81 -2
  214. package/src/components/CnStatWidget/CnStatWidget.vue +1 -1
  215. package/src/components/CnTabsWidget/CnTabsWidget.vue +214 -9
  216. package/src/components/CnWidgetGrid/registerDashboardWidgets.js +4 -0
  217. package/src/components/CnWidgetObjectTable/CnWidgetObjectTable.vue +13 -0
  218. package/src/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue +188 -4
  219. package/src/components/index.js +5 -1
  220. package/src/composables/__tests__/useFormDraft.spec.js +69 -0
  221. package/src/composables/useAppManifest.js +8 -3
  222. package/src/composables/useFormDraft.js +139 -8
  223. package/src/css/form-field.css +14 -0
  224. package/src/css/index-page.css +71 -0
  225. package/src/css/kpi-card.css +32 -0
  226. package/src/css/look-board-detail.css +87 -0
  227. package/src/css/look-board-index.css +143 -14
  228. package/src/css/look-board.css +114 -2
  229. package/src/css/table.css +56 -0
  230. package/src/index.js +8 -0
  231. package/src/integrations/builtin/field-inspection/CnFieldInspectionCard.vue +35 -20
  232. package/src/integrations/builtin/field-inspection.js +21 -0
  233. package/src/integrations/offline/checklistSubmission.js +137 -0
  234. package/src/integrations/offline/fieldCollectionHelpers.js +69 -4
  235. package/src/integrations/offline/index.js +3 -0
  236. package/src/integrations/offline/offlineDb.js +6 -1
  237. package/src/integrations/offline/syncReplayService.js +8 -2
  238. package/src/schemas/app-manifest-v2.schema.json +149 -5
  239. package/src/utils/boardDate.js +189 -0
  240. package/src/utils/detailActionModel.js +59 -2
  241. package/src/utils/dynamicProperties.js +5 -1
  242. package/src/utils/fillTemplate.js +110 -0
  243. package/src/utils/libraryWidgetKeys.js +4 -0
  244. package/src/utils/queryPresets.js +93 -0
  245. package/src/utils/resolveFilterTokens.js +11 -0
  246. package/src/utils/schema.js +21 -0
  247. package/src/utils/sentinelTokens.js +3 -3
  248. package/src/utils/validateManifestV2.compiled.js +57 -2
  249. package/src/utils/widgetDispatch.js +7 -1
  250. package/dist/esm/_virtual/equal.js +0 -4
  251. package/dist/esm/_virtual/equal.js.map +0 -1
  252. package/dist/esm/_virtual/ucs2length.js +0 -4
  253. package/dist/esm/_virtual/ucs2length.js.map +0 -1
  254. package/dist/esm/node_modules/ajv/dist/runtime/equal.js +0 -9
  255. package/dist/esm/node_modules/ajv/dist/runtime/equal.js.map +0 -1
  256. package/dist/esm/node_modules/ajv/dist/runtime/ucs2length.js +0 -25
  257. package/dist/esm/node_modules/ajv/dist/runtime/ucs2length.js.map +0 -1
  258. package/dist/esm/node_modules/fast-deep-equal/index.js +0 -47
  259. package/dist/esm/node_modules/fast-deep-equal/index.js.map +0 -1
@@ -64,6 +64,7 @@
64
64
  <script>
65
65
  import { translate as t } from '@nextcloud/l10n'
66
66
  import { NcNoteCard } from '@nextcloud/vue'
67
+ import { normalizeLook } from '../../composables/useLook.js'
67
68
  import { followLinkClick, resolveHref } from '../../utils/linkNavigation.js'
68
69
  import { safeHref } from '../../utils/safeHref.js'
69
70
  import { nextUid } from '../../utils/uid.js'
@@ -78,6 +79,12 @@ const LAYOUTS = ['banner', 'attention']
78
79
  /** An attention card offers a primary and a secondary action, no more. */
79
80
  const MAX_ACTIONS = 2
80
81
 
82
+ /**
83
+ * Under the board look the card takes what the boards draw: DqMijnWerk's
84
+ * "Vandaag eerst" has three secondary actions and the primary one.
85
+ */
86
+ const MAX_ACTIONS_BOARD = 4
87
+
81
88
  /**
82
89
  * CnBannerWidget — declarative notice banner for dashboards and v2 pages
83
90
  * (`banner` widget type, Wave 1 of nextcloud-vue#91).
@@ -148,6 +155,8 @@ export default {
148
155
  * label that is not a key renders as written.
149
156
  */
150
157
  cnTranslate: { default: () => (key) => key },
158
+ /** The app's look, provided by CnAppRoot or CnPageRenderer (`nextcloud` or `board`). */
159
+ cnLook: { default: 'nextcloud' },
151
160
  },
152
161
 
153
162
  props: {
@@ -232,7 +241,8 @@ export default {
232
241
  },
233
242
 
234
243
  /**
235
- * Attention card: at most two actions. Each is `{ label, route?,
244
+ * Attention card: its actions, two at most (four under the board look,
245
+ * or `maxActions`). Each is `{ label, route?,
236
246
  * href?, primary?, id? }`: `route` is a route name or location,
237
247
  * `href` an URL. An action with neither renders a button that emits
238
248
  * `action`. The first action is the primary one unless one sets
@@ -245,6 +255,17 @@ export default {
245
255
  default: null,
246
256
  },
247
257
 
258
+ /**
259
+ * Attention card: how many actions it shows at most. Null takes the
260
+ * default: two, four under the board look.
261
+ *
262
+ * @spec openspec/changes/screens-banner-actions-parity/specs/banner-actions/spec.md#requirement-the-board-attention-card-shows-the-boards-actions
263
+ */
264
+ maxActions: {
265
+ type: Number,
266
+ default: null,
267
+ },
268
+
248
269
  /**
249
270
  * Stored content blob (CnDashboardPage registry branch) carrying the
250
271
  * same keys as the flat props: `{ variant, text, visibleWhen, route,
@@ -403,12 +424,27 @@ export default {
403
424
  },
404
425
 
405
426
  /**
406
- * The attention card's actions: the first two that have a label, each
427
+ * The attention card's actions: the first `actionCap` that have a label, each
407
428
  * with its href and whether it is the primary one.
408
429
  *
409
430
  * @return {Array<{key: string, id: (string|undefined), label: string, href: string, target: (object|null), primary: boolean, index: number}>}
410
431
  * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-attention-card
411
432
  */
433
+ /**
434
+ * How many actions the attention card shows: `maxActions` (prop, then
435
+ * content), else four under the board look and two without it.
436
+ *
437
+ * @return {number} The cap.
438
+ * @spec openspec/changes/screens-banner-actions-parity/specs/banner-actions/spec.md#requirement-the-board-attention-card-shows-the-boards-actions
439
+ */
440
+ actionCap() {
441
+ const explicit = this.maxActions ?? (this.content ? this.content.maxActions : undefined)
442
+ if (Number.isInteger(explicit) && explicit > 0) {
443
+ return explicit
444
+ }
445
+ return normalizeLook(this.cnLook) === 'board' ? MAX_ACTIONS_BOARD : MAX_ACTIONS
446
+ },
447
+
412
448
  resolvedActions() {
413
449
  const raw = this.actions || (this.content && this.content.actions) || []
414
450
  if (!Array.isArray(raw)) {
@@ -416,7 +452,7 @@ export default {
416
452
  }
417
453
  const usable = raw
418
454
  .filter((action) => action && typeof action === 'object' && typeof action.label === 'string' && action.label !== '')
419
- .slice(0, MAX_ACTIONS)
455
+ .slice(0, this.actionCap)
420
456
  const declaredPrimary = usable.findIndex((action) => action.primary === true)
421
457
  const primaryIndex = declaredPrimary === -1 ? 0 : declaredPrimary
422
458
  return usable.map((action, index) => {
@@ -118,6 +118,19 @@
118
118
  <span v-else class="cn-cell-renderer__dash">—</span>
119
119
  </template>
120
120
 
121
+ <!-- Built-in "age" widget: how long something has waited ("4 uur",
122
+ "3 dagen"), coloured by widgetProps.variantWhen rules on the days
123
+ the cell shows. The full date is the tooltip. -->
124
+ <template v-else-if="widget === 'age'">
125
+ <time
126
+ v-if="dateTimestamp"
127
+ class="cn-cell-renderer__age"
128
+ :class="ageVariant ? 'cn-cell-renderer__age--' + ageVariant : null"
129
+ :datetime="dateTimestamp.toISOString()"
130
+ :title="absoluteDateLabel">{{ ageLabel }}</time>
131
+ <span v-else class="cn-cell-renderer__dash">—</span>
132
+ </template>
133
+
121
134
  <!-- Built-in "date" widget: a date whose colour follows
122
135
  widgetProps.variantWhen, rules on the number of days until it
123
136
  (0 = today, negative = overdue). -->
@@ -131,6 +144,30 @@
131
144
  <span v-else class="cn-cell-renderer__dash">—</span>
132
145
  </template>
133
146
 
147
+ <!-- Built-in "progress" widget: a bar with the percentage beside it,
148
+ toned by widgetProps.thresholds, and an optional age badge
149
+ ("Out of date 12 days") from widgetProps.badge. -->
150
+ <template v-else-if="widget === 'progress'">
151
+ <span v-if="progressPercent === null" class="cn-cell-renderer__dash">—</span>
152
+ <span v-else class="cn-cell-renderer__progress">
153
+ <span class="cn-cell-renderer__progress-line">
154
+ <span class="cn-cell-renderer__progress-bar" aria-hidden="true">
155
+ <span
156
+ class="cn-cell-renderer__progress-fill"
157
+ :class="'cn-cell-renderer__progress-fill--' + progressTone"
158
+ :style="{ width: progressPercent + '%' }" />
159
+ </span>
160
+ <strong
161
+ class="cn-cell-renderer__progress-value"
162
+ :class="'cn-cell-renderer__progress-value--' + progressTone">{{ progressPercent }}%</strong>
163
+ </span>
164
+ <span
165
+ v-if="progressBadge"
166
+ class="cn-cell-renderer__progress-badge"
167
+ :class="'cn-cell-renderer__progress-badge--' + progressBadge.tone">{{ progressBadge.label }}</span>
168
+ </span>
169
+ </template>
170
+
134
171
  <!-- Explicit column formatter — overrides the type-aware paths below -->
135
172
  <template v-else-if="hasFormatter">
136
173
  <span :title="rawTitle">{{ formattedValue }}</span>
@@ -156,7 +193,12 @@
156
193
 
157
194
  <!-- Date / date-time: dynamic NcDateTime (relative time, absolute on hover) -->
158
195
  <template v-else-if="isDate">
159
- <NcDateTime v-if="dateTimestamp" :timestamp="dateTimestamp" />
196
+ <time
197
+ v-if="dateTimestamp && isBoardLook"
198
+ class="cn-cell-renderer__date"
199
+ :datetime="dateTimestamp.toISOString()"
200
+ :title="absoluteDateLabel">{{ boardDateLabel }}</time>
201
+ <NcDateTime v-else-if="dateTimestamp" :timestamp="dateTimestamp" />
160
202
  <span v-else class="cn-cell-renderer__dash">—</span>
161
203
  </template>
162
204
 
@@ -210,9 +252,12 @@ import { NcAvatar, NcDateTime } from '@nextcloud/vue'
210
252
  import CheckBold from 'vue-material-design-icons/CheckBold.vue'
211
253
  import CnFkResolveCell from '../CnFkResolveCell/CnFkResolveCell.vue'
212
254
  import CnGroupNameCell from './CnGroupNameCell.vue'
255
+ import { normalizeLook } from '../../composables/useLook.js'
256
+ import { ageInDays, formatAge, formatBoardDate, resolveAgeVariant } from '../../utils/boardDate.js'
213
257
  import { parseDateValue, resolveDateVariant } from '../../utils/dateVariant.js'
214
258
  import { safeCurrencyCode } from '../../utils/formatMetric.js'
215
259
  import { objectFieldValue } from '../../utils/objectName.js'
260
+ import { readPath } from '../../utils/readPath.js'
216
261
  import { safeHref } from '../../utils/safeHref.js'
217
262
  import { formatValue, isGroupProp } from '../../utils/schema.js'
218
263
  import { CnStatusBadge } from '../CnStatusBadge/index.js'
@@ -272,6 +317,12 @@ export default {
272
317
  * Defaults to identity so standalone use is unaffected.
273
318
  */
274
319
  cnTranslate: { default: () => (key) => key },
320
+ /**
321
+ * The look the app is drawn in, provided by CnAppRoot (or a page with
322
+ * `config.look`). Under `board` a date cell reads the board's short
323
+ * form ("5 okt"). Defaults to `nextcloud`, which renders as before.
324
+ */
325
+ cnLook: { default: 'nextcloud' },
275
326
  },
276
327
 
277
328
  props: {
@@ -326,6 +377,10 @@ export default {
326
377
  * a date whose colour follows `widgetProps.variantWhen`, rules on the
327
378
  * number of days until the date (`[{ op: "lt", value: 0, variant:
328
379
  * "error" }, { op: "lte", value: 5, variant: "warning" }]`).
380
+ * The built-in id `"age"` renders how long the row has waited since
381
+ * the date ("4 hours", "3 days"), coloured by `widgetProps.variantWhen`
382
+ * rules on the days it shows (`[{ op: "gte", value: 3, variant:
383
+ * "error" }]`).
329
384
  * The built-in id `"group"` shows a Nextcloud group id (or a list of
330
385
  * them) as the group's display name; a column whose property is
331
386
  * marked `referenceType: "nextcloud-group"` gets it without asking.
@@ -528,6 +583,146 @@ export default {
528
583
  return variant === 'default' ? '' : variant
529
584
  },
530
585
 
586
+ /**
587
+ * Whether the app takes the board look (`cnLook` is `board`).
588
+ *
589
+ * @return {boolean}
590
+ * @spec openspec/changes/screens-cell-date-parity/specs/cell-date-age/spec.md#requirement-a-board-date-cell-reads-day-and-short-month-in-the-user-language
591
+ */
592
+ isBoardLook() {
593
+ return normalizeLook(this.cnLook) === 'board'
594
+ },
595
+
596
+ /**
597
+ * The board's short date ("5 okt", "14 feb 2024" outside this year).
598
+ *
599
+ * @return {string}
600
+ * @spec openspec/changes/screens-cell-date-parity/specs/cell-date-age/spec.md#requirement-a-board-date-cell-reads-day-and-short-month-in-the-user-language
601
+ */
602
+ boardDateLabel() {
603
+ return formatBoardDate(this.value)
604
+ },
605
+
606
+ /**
607
+ * The full date for a tooltip: day, month and year in the user's
608
+ * locale, with the time for a date-time value.
609
+ *
610
+ * @return {string|undefined}
611
+ * @spec openspec/changes/screens-cell-date-parity/specs/cell-date-age/spec.md#requirement-an-age-cell-says-how-long-something-has-waited
612
+ */
613
+ absoluteDateLabel() {
614
+ const date = parseDateValue(this.value)
615
+ if (date === null) {
616
+ return undefined
617
+ }
618
+ const withTime = this.property?.format === 'date-time' || this.widget === 'age'
619
+ const options = { day: 'numeric', month: 'long', year: 'numeric', ...(withTime ? { hour: '2-digit', minute: '2-digit' } : {}) }
620
+ try {
621
+ return new Intl.DateTimeFormat(getCanonicalLocale() || undefined, options).format(date)
622
+ } catch {
623
+ return new Intl.DateTimeFormat(undefined, options).format(date)
624
+ }
625
+ },
626
+
627
+ /**
628
+ * `age` widget: how long the row has waited ("4 uur", "3 dagen").
629
+ *
630
+ * @return {string}
631
+ * @spec openspec/changes/screens-cell-date-parity/specs/cell-date-age/spec.md#requirement-an-age-cell-says-how-long-something-has-waited
632
+ */
633
+ ageLabel() {
634
+ return formatAge(this.value)
635
+ },
636
+
637
+ /**
638
+ * `progress` widget: the value as a whole percentage of
639
+ * `widgetProps.max` (default 100), clamped to 0..100. An empty value
640
+ * falls back to the first `widgetProps.fallbackFields` row field that
641
+ * holds a number. Null when nothing holds one.
642
+ *
643
+ * @return {number|null}
644
+ * @spec openspec/changes/abstract-progress-cell/specs/progress-cell/spec.md#requirement-a-progress-cell-draws-a-toned-bar-with-its-percentage
645
+ */
646
+ progressPercent() {
647
+ const props = this.widgetProps || {}
648
+ const fallbacks = Array.isArray(props.fallbackFields) ? props.fallbackFields : []
649
+ const candidates = [this.value, ...fallbacks.map((field) => readPath(this.row || {}, field))]
650
+ const raw = candidates.find((v) => v !== null && v !== undefined && v !== '' && !Number.isNaN(Number(v)))
651
+ if (raw === undefined) {
652
+ return null
653
+ }
654
+ const max = Number(props.max) > 0 ? Number(props.max) : 100
655
+ return Math.max(0, Math.min(100, Math.round((Number(raw) / max) * 100)))
656
+ },
657
+
658
+ /**
659
+ * `progress` widget: the tone. `widgetProps.thresholds`
660
+ * `{ good: 60, warn: 30 }` (the defaults): at or above `good` is
661
+ * success, at or above `warn` is warning, below is error. With
662
+ * `widgetProps.lowerIsBetter` the scale turns around.
663
+ *
664
+ * @return {'success'|'warning'|'error'}
665
+ * @spec openspec/changes/abstract-progress-cell/specs/progress-cell/spec.md#requirement-a-progress-cell-draws-a-toned-bar-with-its-percentage
666
+ */
667
+ progressTone() {
668
+ const props = this.widgetProps || {}
669
+ const thresholds = props.thresholds || {}
670
+ const good = Number.isFinite(Number(thresholds.good)) ? Number(thresholds.good) : 60
671
+ const warn = Number.isFinite(Number(thresholds.warn)) ? Number(thresholds.warn) : 30
672
+ const value = props.lowerIsBetter === true ? 100 - this.progressPercent : this.progressPercent
673
+ if (value >= good) {
674
+ return 'success'
675
+ }
676
+ return value >= warn ? 'warning' : 'error'
677
+ },
678
+
679
+ /**
680
+ * `progress` widget: the age badge, or null. `widgetProps.badge`
681
+ * `{ label, dateField, afterDays, when?, tone? }`: shown when the row's
682
+ * `dateField` (default `@self.updated`) lies `afterDays` or more days
683
+ * back and every `when` field equals its value. `label` is translated
684
+ * and takes `{days}`.
685
+ *
686
+ * @return {{label: string, tone: string}|null}
687
+ * @spec openspec/changes/abstract-progress-cell/specs/progress-cell/spec.md#requirement-a-progress-cell-can-carry-an-age-badge
688
+ */
689
+ progressBadge() {
690
+ const badge = this.widgetProps && this.widgetProps.badge
691
+ if (!badge || typeof badge !== 'object' || !badge.label) {
692
+ return null
693
+ }
694
+ const row = this.row || {}
695
+ const when = badge.when && typeof badge.when === 'object' ? badge.when : {}
696
+ for (const [field, expected] of Object.entries(when)) {
697
+ const actual = readPath(row, field)
698
+ if (actual !== undefined && actual !== null && actual !== '' && String(actual) !== String(expected)) {
699
+ return null
700
+ }
701
+ }
702
+ const days = ageInDays(readPath(row, badge.dateField || '@self.updated'))
703
+ const after = Number(badge.afterDays)
704
+ if (days === null || !Number.isFinite(after) || days < after) {
705
+ return null
706
+ }
707
+ const label = this.cnTranslate(badge.label)
708
+ return {
709
+ label: String(label).replace(/\{days\}/g, String(days)),
710
+ tone: ['error', 'warning', 'success'].includes(badge.tone) ? badge.tone : 'error',
711
+ }
712
+ },
713
+
714
+ /**
715
+ * `age` widget: the variant of the first `widgetProps.variantWhen`
716
+ * rule the shown days match, or '' for none.
717
+ *
718
+ * @return {string}
719
+ * @spec openspec/changes/screens-cell-date-parity/specs/cell-date-age/spec.md#requirement-an-age-cell-turns-red-past-its-threshold
720
+ */
721
+ ageVariant() {
722
+ const variant = resolveAgeVariant(this.value, this.widgetProps && this.widgetProps.variantWhen)
723
+ return variant === 'default' ? '' : variant
724
+ },
725
+
531
726
  /**
532
727
  * `date` widget: the date written out in the user's locale.
533
728
  *
@@ -544,6 +739,11 @@ export default {
544
739
  // 08:30"); `widgetProps.timeOnly: true` shows just the time
545
740
  // ("08:30"), for a timetable row. Default: the date alone.
546
741
  const props = this.widgetProps || {}
742
+ // The board look writes a plain date in its short form ("5 okt");
743
+ // a time of day keeps the full form below.
744
+ if (this.isBoardLook && props.timeOnly !== true && props.showTime !== true) {
745
+ return formatBoardDate(date)
746
+ }
547
747
  const options = props.timeOnly === true
548
748
  ? { hour: '2-digit', minute: '2-digit' }
549
749
  : { day: 'numeric', month: 'short', year: 'numeric', ...(props.showTime === true ? { hour: '2-digit', minute: '2-digit' } : {}) }
@@ -1127,10 +1327,114 @@ export default {
1127
1327
  does not rest on colour alone. */
1128
1328
  .cn-cell-renderer__date--success,
1129
1329
  .cn-cell-renderer__date--warning,
1130
- .cn-cell-renderer__date--error {
1330
+ .cn-cell-renderer__date--error,
1331
+ .cn-cell-renderer__age--success,
1332
+ .cn-cell-renderer__age--warning,
1333
+ .cn-cell-renderer__age--error {
1131
1334
  font-weight: 600;
1132
1335
  }
1133
1336
 
1337
+ .cn-cell-renderer__age {
1338
+ white-space: nowrap;
1339
+ }
1340
+
1341
+ /* The progress cell (abstract-progress-cell): a 56 x 8 bar, the percentage
1342
+ 8px beside it in the bar's tone, and an optional age badge after them, as
1343
+ PqLeads' Winkans column. The value is bold, so the tone is not the only signal. */
1344
+ .cn-cell-renderer__progress {
1345
+ display: inline-flex;
1346
+ flex-wrap: wrap;
1347
+ align-items: center;
1348
+ gap: 4px 8px;
1349
+ }
1350
+
1351
+ .cn-cell-renderer__progress-line {
1352
+ display: inline-flex;
1353
+ align-items: center;
1354
+ gap: 8px;
1355
+ white-space: nowrap;
1356
+ }
1357
+
1358
+ .cn-cell-renderer__progress-bar {
1359
+ display: inline-block;
1360
+ width: 56px;
1361
+ height: 8px;
1362
+ border-radius: 4px;
1363
+ background: var(--color-background-dark);
1364
+ overflow: hidden;
1365
+ }
1366
+
1367
+ .cn-cell-renderer__progress-fill {
1368
+ display: block;
1369
+ height: 100%;
1370
+ }
1371
+
1372
+ .cn-cell-renderer__progress-fill--success {
1373
+ background: var(--color-element-success, var(--color-success));
1374
+ }
1375
+
1376
+ .cn-cell-renderer__progress-fill--warning {
1377
+ background: var(--color-element-warning, var(--color-warning));
1378
+ }
1379
+
1380
+ .cn-cell-renderer__progress-fill--error {
1381
+ background: var(--color-element-error, var(--color-error));
1382
+ }
1383
+
1384
+ .cn-cell-renderer__progress-value {
1385
+ font-size: 14px;
1386
+ font-weight: 700;
1387
+ }
1388
+
1389
+ .cn-cell-renderer__progress-value--success {
1390
+ color: var(--color-text-success, var(--color-success-text));
1391
+ }
1392
+
1393
+ .cn-cell-renderer__progress-value--warning {
1394
+ color: var(--color-text-warning, var(--color-warning-text));
1395
+ }
1396
+
1397
+ .cn-cell-renderer__progress-value--error {
1398
+ color: var(--color-text-error, var(--color-error-text));
1399
+ }
1400
+
1401
+ .cn-cell-renderer__progress-badge {
1402
+ display: inline-block;
1403
+ padding: 3px 10px;
1404
+ border-radius: 12px;
1405
+ font-size: 13px;
1406
+ font-weight: 600;
1407
+ white-space: nowrap;
1408
+ }
1409
+
1410
+ .cn-cell-renderer__progress-badge--error {
1411
+ color: var(--color-text-error, var(--color-error-text));
1412
+ background: var(--color-error-hover, var(--color-background-dark));
1413
+ background: color-mix(in srgb, var(--color-error) 12%, transparent);
1414
+ }
1415
+
1416
+ .cn-cell-renderer__progress-badge--warning {
1417
+ color: var(--color-text-warning, var(--color-warning-text));
1418
+ background: color-mix(in srgb, var(--color-warning) 14%, transparent);
1419
+ }
1420
+
1421
+ .cn-cell-renderer__progress-badge--success {
1422
+ color: var(--color-text-success, var(--color-success-text));
1423
+ background: color-mix(in srgb, var(--color-success) 12%, transparent);
1424
+ }
1425
+
1426
+ .cn-cell-renderer__age--success {
1427
+ color: var(--color-text-success, var(--color-success-text));
1428
+ }
1429
+
1430
+ .cn-cell-renderer__age--warning {
1431
+ color: var(--color-text-warning, var(--color-warning-text));
1432
+ }
1433
+
1434
+ .cn-cell-renderer__age--error {
1435
+ color: var(--color-text-error, var(--color-error-text));
1436
+ }
1437
+
1134
1438
  .cn-cell-renderer__date--success {
1135
1439
  color: var(--color-text-success, var(--color-success-text));
1136
1440
  }