@conduction/nextcloud-vue 2.59.0 → 2.61.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 (254) hide show
  1. package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue2.js +14 -1
  2. package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue2.js.map +1 -1
  3. package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue3.js.map +1 -1
  4. package/dist/esm/components/CnAppNav/CnAppNav.vue2.js +80 -1
  5. package/dist/esm/components/CnAppNav/CnAppNav.vue2.js.map +1 -1
  6. package/dist/esm/components/CnAppNav/CnAppNav.vue3.js +46 -4
  7. package/dist/esm/components/CnAppNav/CnAppNav.vue3.js.map +1 -1
  8. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js +232 -10
  9. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js.map +1 -1
  10. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue3.js +79 -25
  11. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue3.js.map +1 -1
  12. package/dist/esm/components/CnBannerWidgetForm/CnBannerWidgetForm.vue2.js +42 -3
  13. package/dist/esm/components/CnBannerWidgetForm/CnBannerWidgetForm.vue2.js.map +1 -1
  14. package/dist/esm/components/CnBannerWidgetForm/CnBannerWidgetForm.vue3.js +39 -6
  15. package/dist/esm/components/CnBannerWidgetForm/CnBannerWidgetForm.vue3.js.map +1 -1
  16. package/dist/esm/components/CnBoardView/CnBoardView.vue2.js +46 -0
  17. package/dist/esm/components/CnBoardView/CnBoardView.vue2.js.map +1 -1
  18. package/dist/esm/components/CnBoardView/CnBoardView.vue3.js +11 -4
  19. package/dist/esm/components/CnBoardView/CnBoardView.vue3.js.map +1 -1
  20. package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue.js +10 -0
  21. package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue.js.map +1 -0
  22. package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue2.js +39 -0
  23. package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue2.js.map +1 -0
  24. package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue3.js +16 -0
  25. package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue3.js.map +1 -0
  26. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +117 -1
  27. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
  28. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +147 -89
  29. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
  30. package/dist/esm/components/CnConversationThread/CnConversationThread.vue.js +10 -0
  31. package/dist/esm/components/CnConversationThread/CnConversationThread.vue.js.map +1 -0
  32. package/dist/esm/components/CnConversationThread/CnConversationThread.vue2.js +259 -0
  33. package/dist/esm/components/CnConversationThread/CnConversationThread.vue2.js.map +1 -0
  34. package/dist/esm/components/CnConversationThread/CnConversationThread.vue3.js +131 -0
  35. package/dist/esm/components/CnConversationThread/CnConversationThread.vue3.js.map +1 -0
  36. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue.js +10 -0
  37. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue.js.map +1 -0
  38. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue2.js +116 -0
  39. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue2.js.map +1 -0
  40. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue3.js +24 -0
  41. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue3.js.map +1 -0
  42. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +10 -2
  43. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
  44. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
  45. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +503 -28
  46. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  47. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +303 -144
  48. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  49. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewList.vue.js +10 -0
  50. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewList.vue.js.map +1 -0
  51. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewList.vue2.js +237 -0
  52. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewList.vue2.js.map +1 -0
  53. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewList.vue3.js +120 -0
  54. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewList.vue3.js.map +1 -0
  55. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue.js +10 -0
  56. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue.js.map +1 -0
  57. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue2.js +77 -0
  58. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue2.js.map +1 -0
  59. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue3.js +27 -0
  60. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue3.js.map +1 -0
  61. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue2.js +171 -12
  62. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue2.js.map +1 -1
  63. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue3.js +15 -7
  64. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue3.js.map +1 -1
  65. package/dist/esm/components/CnHeaderWidgetForm/CnHeaderWidgetForm.vue2.js +37 -3
  66. package/dist/esm/components/CnHeaderWidgetForm/CnHeaderWidgetForm.vue2.js.map +1 -1
  67. package/dist/esm/components/CnHeaderWidgetForm/CnHeaderWidgetForm.vue3.js +44 -16
  68. package/dist/esm/components/CnHeaderWidgetForm/CnHeaderWidgetForm.vue3.js.map +1 -1
  69. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +132 -3
  70. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  71. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +6 -3
  72. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  73. package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue.js +10 -0
  74. package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue.js.map +1 -0
  75. package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue2.js +316 -0
  76. package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue2.js.map +1 -0
  77. package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue3.js +105 -0
  78. package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue3.js.map +1 -0
  79. package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue.js +10 -0
  80. package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue.js.map +1 -0
  81. package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue2.js +150 -0
  82. package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue2.js.map +1 -0
  83. package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue3.js +106 -0
  84. package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue3.js.map +1 -0
  85. package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue.js +9 -0
  86. package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue.js.map +1 -0
  87. package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue2.js +47 -0
  88. package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue2.js.map +1 -0
  89. package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue3.js +17 -0
  90. package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue3.js.map +1 -0
  91. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js +49 -0
  92. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js.map +1 -1
  93. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue3.js.map +1 -1
  94. package/dist/esm/components/CnObjectDataWidgetForm/CnObjectDataWidgetForm.vue2.js +6 -0
  95. package/dist/esm/components/CnObjectDataWidgetForm/CnObjectDataWidgetForm.vue2.js.map +1 -1
  96. package/dist/esm/components/CnObjectDataWidgetForm/CnObjectDataWidgetForm.vue3.js.map +1 -1
  97. package/dist/esm/components/CnObjectKanban/CnObjectKanban.vue2.js +46 -0
  98. package/dist/esm/components/CnObjectKanban/CnObjectKanban.vue2.js.map +1 -1
  99. package/dist/esm/components/CnObjectKanban/CnObjectKanban.vue3.js +15 -3
  100. package/dist/esm/components/CnObjectKanban/CnObjectKanban.vue3.js.map +1 -1
  101. package/dist/esm/components/CnPageRenderer/pageTypes.js +1 -0
  102. package/dist/esm/components/CnPageRenderer/pageTypes.js.map +1 -1
  103. package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue2.js +37 -1
  104. package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue2.js.map +1 -1
  105. package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue3.js +29 -13
  106. package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue3.js.map +1 -1
  107. package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue2.js +50 -3
  108. package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue2.js.map +1 -1
  109. package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue3.js.map +1 -1
  110. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue.js +10 -0
  111. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue.js.map +1 -0
  112. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue2.js +186 -0
  113. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue2.js.map +1 -0
  114. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue3.js +46 -0
  115. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue3.js.map +1 -0
  116. package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue.js +11 -0
  117. package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue.js.map +1 -0
  118. package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue2.js +298 -0
  119. package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue2.js.map +1 -0
  120. package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue3.js +77 -0
  121. package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue3.js.map +1 -0
  122. package/dist/esm/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue.js +10 -0
  123. package/dist/esm/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue.js.map +1 -0
  124. package/dist/esm/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue2.js +169 -0
  125. package/dist/esm/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue2.js.map +1 -0
  126. package/dist/esm/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue3.js +48 -0
  127. package/dist/esm/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue3.js.map +1 -0
  128. package/dist/esm/components/CnTabs/CnTab.vue2.js +35 -0
  129. package/dist/esm/components/CnTabs/CnTab.vue2.js.map +1 -1
  130. package/dist/esm/components/CnTabs/CnTab.vue3.js.map +1 -1
  131. package/dist/esm/components/CnTabs/CnTabs.vue2.js +120 -7
  132. package/dist/esm/components/CnTabs/CnTabs.vue2.js.map +1 -1
  133. package/dist/esm/components/CnTabs/CnTabs.vue3.js +57 -5
  134. package/dist/esm/components/CnTabs/CnTabs.vue3.js.map +1 -1
  135. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +59 -4
  136. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
  137. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js +6 -2
  138. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
  139. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue.js +10 -0
  140. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue.js.map +1 -0
  141. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue2.js +484 -0
  142. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue2.js.map +1 -0
  143. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue3.js +115 -0
  144. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue3.js.map +1 -0
  145. package/dist/esm/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue.js +10 -0
  146. package/dist/esm/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue.js.map +1 -0
  147. package/dist/esm/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue2.js +189 -0
  148. package/dist/esm/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue2.js.map +1 -0
  149. package/dist/esm/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue3.js +74 -0
  150. package/dist/esm/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue3.js.map +1 -0
  151. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +82 -8
  152. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
  153. package/dist/esm/index.js +16 -0
  154. package/dist/esm/index.js.map +1 -1
  155. package/dist/esm/l10n/en.json.js +55 -1
  156. package/dist/esm/l10n/en.json.js.map +1 -1
  157. package/dist/esm/l10n/nl.json.js +55 -1
  158. package/dist/esm/l10n/nl.json.js.map +1 -1
  159. package/dist/esm/nextcloud-vue.css +1036 -19
  160. package/dist/esm/utils/dateVariant.js +124 -0
  161. package/dist/esm/utils/dateVariant.js.map +1 -0
  162. package/dist/esm/utils/detailActionModel.js +260 -0
  163. package/dist/esm/utils/detailActionModel.js.map +1 -0
  164. package/dist/esm/utils/dueRule.js +84 -0
  165. package/dist/esm/utils/dueRule.js.map +1 -0
  166. package/dist/esm/utils/fetchAggregate.js +37 -1
  167. package/dist/esm/utils/fetchAggregate.js.map +1 -1
  168. package/dist/esm/utils/fetchFilterCounts.js +186 -0
  169. package/dist/esm/utils/fetchFilterCounts.js.map +1 -0
  170. package/dist/esm/utils/headers.js +28 -2
  171. package/dist/esm/utils/headers.js.map +1 -1
  172. package/dist/esm/utils/libraryWidgetKeys.js +5 -0
  173. package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
  174. package/dist/esm/utils/readPath.js +30 -0
  175. package/dist/esm/utils/readPath.js.map +1 -0
  176. package/dist/esm/utils/validateManifestV2.compiled.js +2 -2
  177. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  178. package/dist/nextcloud-vue.cjs.js +16019 -9528
  179. package/dist/nextcloud-vue.cjs.js.map +2 -2
  180. package/dist/nextcloud-vue.css +1036 -19
  181. package/l10n/en.json +55 -1
  182. package/l10n/nl.json +55 -1
  183. package/package.json +1 -1
  184. package/scripts/.jsdoc-baselines.json +4 -0
  185. package/src/components/CnActionsMenu/CnActionsMenu.vue +14 -1
  186. package/src/components/CnAppNav/CnAppNav.md +26 -0
  187. package/src/components/CnAppNav/CnAppNav.vue +145 -2
  188. package/src/components/CnBannerWidget/CnBannerWidget.vue +373 -11
  189. package/src/components/CnBannerWidgetForm/CnBannerWidgetForm.vue +65 -3
  190. package/src/components/CnBoardView/CnBoardView.vue +77 -0
  191. package/src/components/CnBrandStripe/CnBrandStripe.md +22 -0
  192. package/src/components/CnBrandStripe/CnBrandStripe.vue +100 -0
  193. package/src/components/CnBrandStripe/index.js +4 -0
  194. package/src/components/CnCellRenderer/CnCellRenderer.vue +207 -1
  195. package/src/components/CnConversationThread/CnConversationThread.md +51 -0
  196. package/src/components/CnConversationThread/CnConversationThread.vue +457 -0
  197. package/src/components/CnConversationThread/CnConversationWidget.vue +144 -0
  198. package/src/components/CnConversationThread/index.js +4 -0
  199. package/src/components/CnDashboardPage/CnDashboardPage.vue +10 -2
  200. package/src/components/CnDetailPage/CnDetailPage.md +16 -0
  201. package/src/components/CnDetailPage/CnDetailPage.vue +687 -49
  202. package/src/components/CnDocumentReviewList/CnDocumentReviewList.md +55 -0
  203. package/src/components/CnDocumentReviewList/CnDocumentReviewList.vue +384 -0
  204. package/src/components/CnDocumentReviewList/CnDocumentReviewWidget.vue +108 -0
  205. package/src/components/CnDocumentReviewList/index.js +4 -0
  206. package/src/components/CnHeaderWidget/CnHeaderWidget.vue +191 -14
  207. package/src/components/CnHeaderWidgetForm/CnHeaderWidgetForm.vue +53 -3
  208. package/src/components/CnIndexPage/CnIndexPage.vue +135 -3
  209. package/src/components/CnLinkCardsPage/CnLinkCardsPage.vue +484 -0
  210. package/src/components/CnLinkCardsPage/index.js +4 -0
  211. package/src/components/CnNextStepCard/CnNextStepCard.md +40 -0
  212. package/src/components/CnNextStepCard/CnNextStepCard.vue +319 -0
  213. package/src/components/CnNextStepCard/CnNextStepWidget.vue +59 -0
  214. package/src/components/CnNextStepCard/index.js +4 -0
  215. package/src/components/CnObjectDataWidget/CnObjectDataWidget.vue +49 -0
  216. package/src/components/CnObjectDataWidgetForm/CnObjectDataWidgetForm.vue +6 -0
  217. package/src/components/CnObjectKanban/CnObjectKanban.vue +86 -2
  218. package/src/components/CnPageRenderer/pageTypes.js +1 -0
  219. package/src/components/CnQuickFilterBar/CnQuickFilterBar.md +3 -0
  220. package/src/components/CnQuickFilterBar/CnQuickFilterBar.vue +60 -1
  221. package/src/components/CnSavedViewsControl/CnSavedViewsControl.vue +50 -3
  222. package/src/components/CnSegmentedControl/CnSegmentedControl.md +46 -0
  223. package/src/components/CnSegmentedControl/CnSegmentedControl.vue +291 -0
  224. package/src/components/CnSegmentedControl/index.js +4 -0
  225. package/src/components/CnStackedBarWidget/CnStackedBarWidget.md +15 -0
  226. package/src/components/CnStackedBarWidget/CnStackedBarWidget.vue +414 -0
  227. package/src/components/CnStackedBarWidget/index.js +14 -0
  228. package/src/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue +224 -0
  229. package/src/components/CnStackedBarWidgetForm/index.js +4 -0
  230. package/src/components/CnTabs/CnTab.vue +35 -0
  231. package/src/components/CnTabs/CnTabs.vue +244 -10
  232. package/src/components/CnTabsWidget/CnTabsWidget.vue +63 -4
  233. package/src/components/CnWeekStripWidget/CnWeekStripWidget.md +31 -0
  234. package/src/components/CnWeekStripWidget/CnWeekStripWidget.vue +668 -0
  235. package/src/components/CnWeekStripWidget/index.js +14 -0
  236. package/src/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue +265 -0
  237. package/src/components/CnWeekStripWidgetForm/index.js +4 -0
  238. package/src/components/CnWidgetGrid/registerDashboardWidgets.js +74 -0
  239. package/src/components/index.js +12 -0
  240. package/src/css/detail-page.css +57 -2
  241. package/src/index.js +16 -0
  242. package/src/schemas/app-manifest-v2.schema.json +433 -3
  243. package/src/schemas/app-manifest.schema.json +2 -1
  244. package/src/types/manifest.d.ts +1 -1
  245. package/src/utils/dateVariant.js +120 -0
  246. package/src/utils/detailActionModel.js +256 -0
  247. package/src/utils/dueRule.js +94 -0
  248. package/src/utils/fetchAggregate.js +36 -0
  249. package/src/utils/fetchFilterCounts.js +182 -0
  250. package/src/utils/headers.js +28 -2
  251. package/src/utils/index.js +1 -0
  252. package/src/utils/libraryWidgetKeys.js +5 -0
  253. package/src/utils/readPath.js +27 -0
  254. package/src/utils/validateManifestV2.compiled.js +2 -2
@@ -0,0 +1,484 @@
1
+ <!--
2
+ - SPDX-FileCopyrightText: 2026 Conduction B.V.
3
+ - SPDX-License-Identifier: EUPL-1.2
4
+ -->
5
+
6
+ <template>
7
+ <div class="cn-link-cards-page" data-testid="cn-link-cards-page">
8
+ <div class="cn-link-cards-page__header">
9
+ <h2 v-if="resolvedTitle" class="cn-link-cards-page__title">
10
+ {{ resolvedTitle }}
11
+ </h2>
12
+ <p v-if="resolvedDescription" class="cn-link-cards-page__description">
13
+ {{ resolvedDescription }}
14
+ </p>
15
+ </div>
16
+
17
+ <p
18
+ v-if="groups.length === 0"
19
+ class="cn-link-cards-page__empty"
20
+ data-testid="cn-link-cards-empty">
21
+ {{ resolvedEmptyLabel }}
22
+ </p>
23
+
24
+ <section
25
+ v-for="group in groups"
26
+ :key="group.key"
27
+ class="cn-link-cards-page__group"
28
+ :aria-labelledby="group.label ? group.headingId : null"
29
+ :aria-label="group.label ? null : resolvedTitle || null"
30
+ data-testid="cn-link-cards-group"
31
+ :data-group="group.key">
32
+ <h3
33
+ v-if="group.label"
34
+ :id="group.headingId"
35
+ class="cn-link-cards-page__caption">
36
+ {{ group.label }}
37
+ </h3>
38
+ <ul class="cn-link-cards-page__grid">
39
+ <li v-for="card in group.cards" :key="card.id" class="cn-link-cards-page__cell">
40
+ <!-- A real <a href>, so the card can be middle-clicked, copied
41
+ and announced as a link. A plain click on an in-app card
42
+ is routed, which keeps the page from reloading. -->
43
+ <a
44
+ class="cn-link-cards-page__card"
45
+ data-testid="cn-link-card"
46
+ :data-card-id="card.id"
47
+ :href="card.url"
48
+ :target="card.external ? '_blank' : null"
49
+ :rel="card.external ? 'noopener noreferrer' : null"
50
+ :aria-describedby="card.description ? card.descriptionId : null"
51
+ @click="open(card, $event)">
52
+ <CnIcon
53
+ v-if="card.icon"
54
+ :name="card.icon"
55
+ :size="24"
56
+ class="cn-link-cards-page__icon"
57
+ aria-hidden="true" />
58
+ <span class="cn-link-cards-page__body">
59
+ <span class="cn-link-cards-page__label">{{ card.label }}</span>
60
+ <span
61
+ v-if="card.description"
62
+ :id="card.descriptionId"
63
+ class="cn-link-cards-page__card-description">
64
+ {{ card.description }}
65
+ </span>
66
+ </span>
67
+ </a>
68
+ </li>
69
+ </ul>
70
+ </section>
71
+ </div>
72
+ </template>
73
+
74
+ <script>
75
+ import { translate as t } from '@nextcloud/l10n'
76
+ import { isAppInstalled } from '../../utils/appInstalled.js'
77
+ import { followLinkClick, resolveHref } from '../../utils/linkNavigation.js'
78
+ import { passesContextPredicates } from '../../utils/visibleIfContext.js'
79
+ import { CnIcon } from '../CnIcon/index.js'
80
+
81
+ let linkCardsPageUid = 0
82
+
83
+ /**
84
+ * CnLinkCardsPage: the `type: "links"` page. Link cards, grouped under captions.
85
+ *
86
+ * One page that opens every page a short menu leaves out. A card has a
87
+ * label, a one-line description, an optional icon and a target. Groups come
88
+ * from `categories`, in the order they are declared; a card names its group
89
+ * with `category`.
90
+ *
91
+ * ```json
92
+ * { "id": "Modules", "route": "/modules", "type": "links", "title": "Modules and more",
93
+ * "config": {
94
+ * "description": "Every page that is not in the daily menu.",
95
+ * "categories": { "sales": "Sales", "marketing": "Marketing" },
96
+ * "cards": [
97
+ * { "id": "Leads", "label": "Leads", "description": "Every deal you are working on.",
98
+ * "icon": "CashMultiple", "category": "sales", "route": "Leads" }
99
+ * ]
100
+ * } }
101
+ * ```
102
+ *
103
+ * Every string goes through the app's translate function. A card is hidden
104
+ * by the same `visibleIf` and `permission` a menu entry takes, and a group
105
+ * with no visible card is not drawn.
106
+ */
107
+ export default {
108
+ name: 'CnLinkCardsPage',
109
+
110
+ components: { CnIcon },
111
+
112
+ inject: {
113
+ /** The app's translate function, provided by CnAppRoot. Identity outside one. */
114
+ cnTranslate: { default: () => (key) => key },
115
+ /** The effective manifest, for `visibleIf` predicates against `runtime`. */
116
+ cnManifest: { default: null },
117
+ /** The permissions the user holds. Empty means the app did not say, and allows. */
118
+ cnPermissions: { default: () => [] },
119
+ },
120
+
121
+ props: {
122
+ /** The whole manifest page, for a host that mounts this component itself. CnPageRenderer passes the flattened props below instead. */
123
+ page: {
124
+ type: Object,
125
+ default: () => ({}),
126
+ },
127
+
128
+ /**
129
+ * The cards: `{ id, label, description?, icon?, category?, route?, params?, query?, href?, visibleIf?, permission? }`.
130
+ * `route` is a route name, or a path when it starts with `/`.
131
+ *
132
+ * @type {Array<object>|null}
133
+ */
134
+ cards: {
135
+ type: Array,
136
+ default: null,
137
+ },
138
+
139
+ /**
140
+ * Group key to caption. Sets the order of the groups.
141
+ *
142
+ * @type {Record<string, string>|null}
143
+ */
144
+ categories: {
145
+ type: Object,
146
+ default: null,
147
+ },
148
+
149
+ /** The heading. */
150
+ title: {
151
+ type: String,
152
+ default: null,
153
+ },
154
+
155
+ /** The lead paragraph under the heading. */
156
+ description: {
157
+ type: String,
158
+ default: null,
159
+ },
160
+
161
+ /** Text shown when no card is visible. */
162
+ emptyLabel: {
163
+ type: String,
164
+ default: null,
165
+ },
166
+
167
+ /**
168
+ * Translate function. Falls back to the injected `cnTranslate`.
169
+ *
170
+ * @type {((key: string) => string)|null}
171
+ */
172
+ translate: {
173
+ type: Function,
174
+ default: null,
175
+ },
176
+ },
177
+
178
+ data() {
179
+ linkCardsPageUid += 1
180
+ return {
181
+ uid: linkCardsPageUid,
182
+ }
183
+ },
184
+
185
+ computed: {
186
+ /**
187
+ * The config, from the flattened props or from `page.config`. Props win.
188
+ *
189
+ * @return {object} The config.
190
+ * @spec openspec/changes/link-cards-page/specs/link-cards-page/spec.md#requirement-a-links-page-renders-grouped-link-cards
191
+ */
192
+ config() {
193
+ const fromPage = (this.page && this.page.config) || {}
194
+ const fromProps = {}
195
+ for (const key of ['cards', 'categories', 'description', 'title', 'emptyLabel']) {
196
+ if (this[key] !== null && this[key] !== undefined) {
197
+ fromProps[key] = this[key]
198
+ }
199
+ }
200
+ return { ...fromPage, ...fromProps }
201
+ },
202
+
203
+ /**
204
+ * The heading, translated.
205
+ *
206
+ * @return {string} The title, or '' when the page declares none.
207
+ * @spec openspec/changes/link-cards-page/specs/link-cards-page/spec.md#requirement-a-links-page-renders-grouped-link-cards
208
+ */
209
+ resolvedTitle() {
210
+ return this.tr(this.config.title || (this.page && this.page.title) || '')
211
+ },
212
+
213
+ /**
214
+ * The lead paragraph, translated.
215
+ *
216
+ * @return {string} The description.
217
+ * @spec openspec/changes/link-cards-page/specs/link-cards-page/spec.md#requirement-a-links-page-renders-grouped-link-cards
218
+ */
219
+ resolvedDescription() {
220
+ return this.tr(this.config.description || '')
221
+ },
222
+
223
+ /**
224
+ * The empty-state text.
225
+ *
226
+ * @return {string} The text.
227
+ * @spec openspec/changes/link-cards-page/specs/link-cards-page/spec.md#requirement-a-links-page-renders-grouped-link-cards
228
+ */
229
+ resolvedEmptyLabel() {
230
+ return this.config.emptyLabel ? this.tr(this.config.emptyLabel) : t('nextcloud-vue', 'Nothing to open here.')
231
+ },
232
+
233
+ /**
234
+ * The cards a reader may see and can open, with text translated and
235
+ * the link resolved. A card that is gated away, or has no target that
236
+ * resolves, is left out: a card that does nothing is worse than none.
237
+ *
238
+ * @return {Array<object>} The cards.
239
+ * @spec openspec/changes/link-cards-page/specs/link-cards-page/spec.md#requirement-a-card-is-a-real-link
240
+ */
241
+ visibleCards() {
242
+ const declared = Array.isArray(this.config.cards) ? this.config.cards : []
243
+ const runtime = this.cnManifest?.runtime ?? null
244
+ return declared
245
+ .filter((card) => card && card.id && card.label)
246
+ .filter((card) => this.passesPermission(card) && this.passesVisibleIf(card, runtime))
247
+ .map((card) => {
248
+ const target = this.targetOf(card)
249
+ return {
250
+ id: String(card.id),
251
+ label: this.tr(card.label),
252
+ description: this.tr(card.description || ''),
253
+ descriptionId: `cn-link-card-desc-${this.uid}-${String(card.id).replace(/[^A-Za-z0-9_-]/g, '-')}`,
254
+ icon: card.icon || '',
255
+ category: card.category || '',
256
+ external: target.external,
257
+ location: target.location,
258
+ url: target.url,
259
+ }
260
+ })
261
+ .filter((card) => card.url !== '')
262
+ },
263
+
264
+ /**
265
+ * The groups to draw: ungrouped cards first under no caption, then
266
+ * one group per category in declaration order. A group without a
267
+ * visible card is left out.
268
+ *
269
+ * @return {Array<{key: string, label: string, headingId: string, cards: Array<object>}>} The groups.
270
+ * @spec openspec/changes/link-cards-page/specs/link-cards-page/spec.md#requirement-a-links-page-renders-grouped-link-cards
271
+ */
272
+ groups() {
273
+ const declared = (this.config.categories && typeof this.config.categories === 'object') ? this.config.categories : {}
274
+ const keys = Object.keys(declared)
275
+ const loose = this.visibleCards.filter((card) => !keys.includes(card.category))
276
+ const groups = loose.length > 0 ? [{ key: '', label: '', headingId: '', cards: loose }] : []
277
+ keys.forEach((key, index) => {
278
+ const cards = this.visibleCards.filter((card) => card.category === key)
279
+ if (cards.length > 0) {
280
+ groups.push({
281
+ key,
282
+ label: this.tr(declared[key]),
283
+ headingId: `cn-link-cards-group-${this.uid}-${index}`,
284
+ cards,
285
+ })
286
+ }
287
+ })
288
+ return groups
289
+ },
290
+ },
291
+
292
+ methods: {
293
+ /**
294
+ * Translate a manifest string through the app's translate function.
295
+ *
296
+ * @param {string} value The declared string.
297
+ * @return {string} The translation, or the input unchanged.
298
+ * @spec openspec/changes/link-cards-page/specs/link-cards-page/spec.md#requirement-a-links-page-renders-grouped-link-cards
299
+ */
300
+ tr(value) {
301
+ if (typeof value !== 'string' || value === '') {
302
+ return value
303
+ }
304
+ return (this.translate ?? this.cnTranslate)(value)
305
+ },
306
+
307
+ /**
308
+ * Whether the user holds the card's `permission`. Same rule as the
309
+ * menu: no permission declared, or no permissions known, allows.
310
+ *
311
+ * @param {object} card The card.
312
+ * @return {boolean} True when the card may render.
313
+ * @spec openspec/changes/link-cards-page/specs/link-cards-page/spec.md#requirement-cards-honour-menu-visibility-conditions
314
+ */
315
+ passesPermission(card) {
316
+ const held = Array.isArray(this.cnPermissions) ? this.cnPermissions : []
317
+ if (!card.permission || held.length === 0) {
318
+ return true
319
+ }
320
+ return held.includes(card.permission)
321
+ },
322
+
323
+ /**
324
+ * Evaluate a card's `visibleIf` the way CnAppNav evaluates a menu
325
+ * entry's: `appInstalled` first, then dot-path predicates against
326
+ * the manifest's `runtime`.
327
+ *
328
+ * @param {object} card The card.
329
+ * @param {object|null} runtime `manifest.runtime`, or null.
330
+ * @return {boolean} True when the card may render.
331
+ * @spec openspec/changes/link-cards-page/specs/link-cards-page/spec.md#requirement-cards-honour-menu-visibility-conditions
332
+ */
333
+ passesVisibleIf(card, runtime) {
334
+ const condition = card.visibleIf
335
+ if (!condition || typeof condition !== 'object') {
336
+ return true
337
+ }
338
+ if (condition.appInstalled && !isAppInstalled(condition.appInstalled)) {
339
+ return false
340
+ }
341
+ return passesContextPredicates(condition, runtime)
342
+ },
343
+
344
+ /**
345
+ * Where a card goes. An `href` is an external URL. A `route` is a
346
+ * route name, or a path when it starts with `/`, resolved by the
347
+ * router so the link carries the app's base with or without
348
+ * `/index.php`.
349
+ *
350
+ * @param {object} card The card.
351
+ * @return {{external: boolean, location: object|null, url: string}} The target; `url` is '' when it cannot be resolved.
352
+ * @spec openspec/changes/link-cards-page/specs/link-cards-page/spec.md#requirement-a-card-is-a-real-link
353
+ */
354
+ targetOf(card) {
355
+ if (typeof card.route === 'string' && card.route !== '') {
356
+ const location = card.route.startsWith('/') ? { path: card.route } : { name: card.route }
357
+ if (card.params && typeof card.params === 'object') {
358
+ location.params = card.params
359
+ }
360
+ if (card.query && typeof card.query === 'object') {
361
+ location.query = card.query
362
+ }
363
+ return { external: false, location, url: resolveHref(location, this.$router) }
364
+ }
365
+ if (typeof card.href === 'string' && card.href !== '') {
366
+ return { external: true, location: null, url: card.href }
367
+ }
368
+ return { external: false, location: null, url: '' }
369
+ },
370
+
371
+ /**
372
+ * Route a plain click on an in-app card. A modified or middle click,
373
+ * and every click on an external card, is left to the browser.
374
+ *
375
+ * @param {object} card The resolved card.
376
+ * @param {MouseEvent} event The click.
377
+ * @return {void}
378
+ * @spec openspec/changes/link-cards-page/specs/link-cards-page/spec.md#requirement-a-card-is-a-real-link
379
+ */
380
+ open(card, event) {
381
+ if (card.external || !card.location) {
382
+ return
383
+ }
384
+ followLinkClick(event, card.location, this.$router)
385
+ },
386
+ },
387
+ }
388
+ </script>
389
+
390
+ <style scoped>
391
+ .cn-link-cards-page {
392
+ display: flex;
393
+ flex-direction: column;
394
+ gap: calc(6 * var(--default-grid-baseline));
395
+ padding: calc(5 * var(--default-grid-baseline));
396
+ }
397
+
398
+ .cn-link-cards-page__title {
399
+ margin: 0 0 var(--default-grid-baseline);
400
+ }
401
+
402
+ .cn-link-cards-page__description,
403
+ .cn-link-cards-page__empty {
404
+ color: var(--color-text-maxcontrast);
405
+ margin: 0;
406
+ max-width: 70ch;
407
+ }
408
+
409
+ .cn-link-cards-page__group {
410
+ display: flex;
411
+ flex-direction: column;
412
+ gap: calc(3 * var(--default-grid-baseline));
413
+ }
414
+
415
+ .cn-link-cards-page__caption {
416
+ color: var(--color-text-maxcontrast);
417
+ font-size: 0.85em;
418
+ font-weight: 600;
419
+ letter-spacing: 0.06em;
420
+ margin: 0;
421
+ text-transform: uppercase;
422
+ }
423
+
424
+ .cn-link-cards-page__grid {
425
+ display: grid;
426
+ gap: calc(3 * var(--default-grid-baseline));
427
+ grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
428
+ list-style: none;
429
+ margin: 0;
430
+ padding: 0;
431
+ }
432
+
433
+ .cn-link-cards-page__cell {
434
+ display: flex;
435
+ }
436
+
437
+ .cn-link-cards-page__card {
438
+ align-items: flex-start;
439
+ background: var(--color-main-background);
440
+ border: 1px solid var(--color-border-dark);
441
+ border-radius: var(--border-radius-large);
442
+ color: var(--color-main-text);
443
+ display: flex;
444
+ flex: 1;
445
+ gap: calc(3 * var(--default-grid-baseline));
446
+ min-width: 0;
447
+ padding: calc(4 * var(--default-grid-baseline));
448
+ text-decoration: none;
449
+ }
450
+
451
+ .cn-link-cards-page__card:hover {
452
+ background: var(--color-background-hover);
453
+ border-color: var(--color-primary-element);
454
+ }
455
+
456
+ .cn-link-cards-page__card:focus-visible {
457
+ border-color: var(--color-primary-element);
458
+ outline: 2px solid var(--color-primary-element);
459
+ outline-offset: 2px;
460
+ }
461
+
462
+ .cn-link-cards-page__icon {
463
+ color: var(--color-primary-element);
464
+ flex: none;
465
+ }
466
+
467
+ .cn-link-cards-page__body {
468
+ display: flex;
469
+ flex-direction: column;
470
+ gap: var(--default-grid-baseline);
471
+ min-width: 0;
472
+ }
473
+
474
+ .cn-link-cards-page__label {
475
+ font-weight: 600;
476
+ overflow-wrap: anywhere;
477
+ }
478
+
479
+ .cn-link-cards-page__card-description {
480
+ color: var(--color-text-maxcontrast);
481
+ font-size: 0.9em;
482
+ line-height: 1.4;
483
+ }
484
+ </style>
@@ -0,0 +1,4 @@
1
+ import CnLinkCardsPage from './CnLinkCardsPage.vue'
2
+
3
+ export default CnLinkCardsPage
4
+ export { CnLinkCardsPage }
@@ -0,0 +1,40 @@
1
+ A step with one thing done and two to go:
2
+
3
+ ```vue
4
+ <CnNextStepCard
5
+ title="What now? Step 2: handling"
6
+ :items="[
7
+ { label: 'Receipt confirmed to the resident', done: true },
8
+ { label: 'Review the 3 documents that are still open', hint: '2 of 5 done' },
9
+ { label: 'Draft the decision' },
10
+ ]"
11
+ action-label="Continue reviewing"
12
+ after="Then: step 3, decision" />
13
+ ```
14
+
15
+ A checklist only, with no button:
16
+
17
+ ```vue
18
+ <CnNextStepCard
19
+ :items="[
20
+ { label: 'Inform the resident', done: true },
21
+ { label: 'Archive the case', done: true },
22
+ ]" />
23
+ ```
24
+
25
+ As a page section, with the button waiting for its request. `title-tag` sets the heading level, `action-id` gives the button an id a skip link can target, and `done-label` and `todo-label` are the states a screen reader hears:
26
+
27
+ ```vue
28
+ <CnNextStepCard
29
+ title="What now?"
30
+ title-tag="h2"
31
+ action-label="Publish"
32
+ action-id="case-primary-action"
33
+ :action-disabled="true"
34
+ done-label="Klaar"
35
+ todo-label="Nog doen"
36
+ :items="[
37
+ { label: 'Check what can be made public', done: true },
38
+ { label: 'Create the publication' },
39
+ ]" />
40
+ ```