@conduction/nextcloud-vue 2.56.0-beta.3 → 2.56.0-beta.5

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 (314) hide show
  1. package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js +12 -2
  2. package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js.map +1 -1
  3. package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js +1 -2
  4. package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js.map +1 -1
  5. package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue2.js.map +1 -1
  6. package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue3.js +0 -3
  7. package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue3.js.map +1 -1
  8. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js.map +1 -1
  9. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js +1 -2
  10. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js.map +1 -1
  11. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +21 -1
  12. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
  13. package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js +1 -1
  14. package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
  15. package/dist/esm/components/CnBoardView/CnBoardView.vue2.js +12 -5
  16. package/dist/esm/components/CnBoardView/CnBoardView.vue2.js.map +1 -1
  17. package/dist/esm/components/CnBoardView/CnBoardView.vue3.js +2 -1
  18. package/dist/esm/components/CnBoardView/CnBoardView.vue3.js.map +1 -1
  19. package/dist/esm/components/CnCardGrid/CnCardGrid.vue2.js +9 -1
  20. package/dist/esm/components/CnCardGrid/CnCardGrid.vue2.js.map +1 -1
  21. package/dist/esm/components/CnCardGrid/CnCardGrid.vue3.js.map +1 -1
  22. package/dist/esm/components/CnContextMenu/CnContextMenu.vue2.js +6 -1
  23. package/dist/esm/components/CnContextMenu/CnContextMenu.vue2.js.map +1 -1
  24. package/dist/esm/components/CnContextMenu/CnContextMenu.vue3.js.map +1 -1
  25. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +116 -15
  26. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
  27. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +27 -14
  28. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
  29. package/dist/esm/components/CnDateAxisView/CnDateAxisView.vue2.js +13 -6
  30. package/dist/esm/components/CnDateAxisView/CnDateAxisView.vue2.js.map +1 -1
  31. package/dist/esm/components/CnDateAxisView/CnDateAxisView.vue3.js +2 -0
  32. package/dist/esm/components/CnDateAxisView/CnDateAxisView.vue3.js.map +1 -1
  33. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +29 -1
  34. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  35. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  36. package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue2.js.map +1 -1
  37. package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue3.js +1 -2
  38. package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue3.js.map +1 -1
  39. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +3 -1
  40. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
  41. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +17 -17
  42. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
  43. package/dist/esm/components/CnFilesWidget/CnFilesWidget.vue2.js +4 -21
  44. package/dist/esm/components/CnFilesWidget/CnFilesWidget.vue2.js.map +1 -1
  45. package/dist/esm/components/CnFilesWidget/CnFilesWidget.vue3.js.map +1 -1
  46. package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue2.js +0 -13
  47. package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue2.js.map +1 -1
  48. package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue3.js +8 -8
  49. package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue3.js.map +1 -1
  50. package/dist/esm/components/CnFlowsPage/CnFlowsPage.vue2.js.map +1 -1
  51. package/dist/esm/components/CnFlowsPage/CnFlowsPage.vue3.js +3 -2
  52. package/dist/esm/components/CnFlowsPage/CnFlowsPage.vue3.js.map +1 -1
  53. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +67 -13
  54. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  55. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +15 -8
  56. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  57. package/dist/esm/components/CnIndexPage/manifestActionDispatch.js +31 -11
  58. package/dist/esm/components/CnIndexPage/manifestActionDispatch.js.map +1 -1
  59. package/dist/esm/components/CnIntegrationWidget/CnIntegrationWidgetEmpty.vue2.js.map +1 -1
  60. package/dist/esm/components/CnIntegrationWidget/CnIntegrationWidgetEmpty.vue3.js +1 -2
  61. package/dist/esm/components/CnIntegrationWidget/CnIntegrationWidgetEmpty.vue3.js.map +1 -1
  62. package/dist/esm/components/CnInteractionFormWidget/CnInteractionFormWidget.vue2.js.map +1 -1
  63. package/dist/esm/components/CnInteractionFormWidget/CnInteractionFormWidget.vue3.js +0 -1
  64. package/dist/esm/components/CnInteractionFormWidget/CnInteractionFormWidget.vue3.js.map +1 -1
  65. package/dist/esm/components/CnLinkButtonWidget/CnLinkButtonWidget.vue2.js +2 -17
  66. package/dist/esm/components/CnLinkButtonWidget/CnLinkButtonWidget.vue2.js.map +1 -1
  67. package/dist/esm/components/CnLinkButtonWidget/CnLinkButtonWidget.vue3.js.map +1 -1
  68. package/dist/esm/components/CnLogsPage/CnLogsPage.vue2.js +35 -11
  69. package/dist/esm/components/CnLogsPage/CnLogsPage.vue2.js.map +1 -1
  70. package/dist/esm/components/CnLogsPage/CnLogsPage.vue3.js +3 -2
  71. package/dist/esm/components/CnLogsPage/CnLogsPage.vue3.js.map +1 -1
  72. package/dist/esm/components/CnObjectCard/CnObjectCard.vue2.js +23 -5
  73. package/dist/esm/components/CnObjectCard/CnObjectCard.vue2.js.map +1 -1
  74. package/dist/esm/components/CnObjectCard/CnObjectCard.vue3.js +1 -1
  75. package/dist/esm/components/CnObjectCard/CnObjectCard.vue3.js.map +1 -1
  76. package/dist/esm/components/CnObjectList/CnObjectList.vue2.js +19 -3
  77. package/dist/esm/components/CnObjectList/CnObjectList.vue2.js.map +1 -1
  78. package/dist/esm/components/CnObjectList/CnObjectList.vue3.js.map +1 -1
  79. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +45 -15
  80. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
  81. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js +4 -2
  82. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
  83. package/dist/esm/components/CnObjectRow/CnObjectRow.vue2.js +24 -6
  84. package/dist/esm/components/CnObjectRow/CnObjectRow.vue2.js.map +1 -1
  85. package/dist/esm/components/CnObjectRow/CnObjectRow.vue3.js +1 -1
  86. package/dist/esm/components/CnObjectRow/CnObjectRow.vue3.js.map +1 -1
  87. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
  88. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +0 -1
  89. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
  90. package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue2.js.map +1 -1
  91. package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue3.js +4 -2
  92. package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue3.js.map +1 -1
  93. package/dist/esm/components/CnRowActions/CnRowActions.vue2.js +7 -2
  94. package/dist/esm/components/CnRowActions/CnRowActions.vue2.js.map +1 -1
  95. package/dist/esm/components/CnRowActions/CnRowActions.vue3.js.map +1 -1
  96. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js +49 -5
  97. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js.map +1 -1
  98. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js +1 -1
  99. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js.map +1 -1
  100. package/dist/esm/components/CnSpendAnalyticsWidget/CnSpendAnalyticsWidget.vue2.js +5 -3
  101. package/dist/esm/components/CnSpendAnalyticsWidget/CnSpendAnalyticsWidget.vue2.js.map +1 -1
  102. package/dist/esm/components/CnSpendAnalyticsWidget/CnSpendAnalyticsWidget.vue3.js.map +1 -1
  103. package/dist/esm/components/CnTabs/CnTabs.vue2.js.map +1 -1
  104. package/dist/esm/components/CnTabs/CnTabs.vue3.js.map +1 -1
  105. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue2.js +3 -1
  106. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue2.js.map +1 -1
  107. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue3.js +1 -0
  108. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue3.js.map +1 -1
  109. package/dist/esm/integrations/builtin/analytics/CnAnalyticsCard.vue2.js +3 -1
  110. package/dist/esm/integrations/builtin/analytics/CnAnalyticsCard.vue2.js.map +1 -1
  111. package/dist/esm/integrations/builtin/analytics/CnAnalyticsCard.vue3.js.map +1 -1
  112. package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue2.js +3 -1
  113. package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue2.js.map +1 -1
  114. package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue3.js +2 -4
  115. package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue3.js.map +1 -1
  116. package/dist/esm/integrations/builtin/bookmarks/CnBookmarksCard.vue2.js +6 -0
  117. package/dist/esm/integrations/builtin/bookmarks/CnBookmarksCard.vue2.js.map +1 -1
  118. package/dist/esm/integrations/builtin/bookmarks/CnBookmarksCard.vue3.js +68 -53
  119. package/dist/esm/integrations/builtin/bookmarks/CnBookmarksCard.vue3.js.map +1 -1
  120. package/dist/esm/integrations/builtin/bookmarks/CnBookmarksTab.vue2.js +17 -0
  121. package/dist/esm/integrations/builtin/bookmarks/CnBookmarksTab.vue2.js.map +1 -1
  122. package/dist/esm/integrations/builtin/bookmarks/CnBookmarksTab.vue3.js +21 -19
  123. package/dist/esm/integrations/builtin/bookmarks/CnBookmarksTab.vue3.js.map +1 -1
  124. package/dist/esm/integrations/builtin/collectives/CnCollectivesCard.vue2.js +3 -1
  125. package/dist/esm/integrations/builtin/collectives/CnCollectivesCard.vue2.js.map +1 -1
  126. package/dist/esm/integrations/builtin/collectives/CnCollectivesCard.vue3.js.map +1 -1
  127. package/dist/esm/integrations/builtin/collectives/CnCollectivesTab.vue2.js +3 -1
  128. package/dist/esm/integrations/builtin/collectives/CnCollectivesTab.vue2.js.map +1 -1
  129. package/dist/esm/integrations/builtin/collectives/CnCollectivesTab.vue3.js +2 -4
  130. package/dist/esm/integrations/builtin/collectives/CnCollectivesTab.vue3.js.map +1 -1
  131. package/dist/esm/integrations/builtin/cospend/CnCospendCard.vue2.js +2 -1
  132. package/dist/esm/integrations/builtin/cospend/CnCospendCard.vue2.js.map +1 -1
  133. package/dist/esm/integrations/builtin/cospend/CnCospendCard.vue3.js.map +1 -1
  134. package/dist/esm/integrations/builtin/cospend/CnCospendTab.vue2.js +2 -1
  135. package/dist/esm/integrations/builtin/cospend/CnCospendTab.vue2.js.map +1 -1
  136. package/dist/esm/integrations/builtin/cospend/CnCospendTab.vue3.js +1 -2
  137. package/dist/esm/integrations/builtin/cospend/CnCospendTab.vue3.js.map +1 -1
  138. package/dist/esm/integrations/builtin/deck/CnDeckCard.vue2.js +2 -1
  139. package/dist/esm/integrations/builtin/deck/CnDeckCard.vue2.js.map +1 -1
  140. package/dist/esm/integrations/builtin/deck/CnDeckCard.vue3.js.map +1 -1
  141. package/dist/esm/integrations/builtin/deck/CnDeckTab.vue2.js +2 -1
  142. package/dist/esm/integrations/builtin/deck/CnDeckTab.vue2.js.map +1 -1
  143. package/dist/esm/integrations/builtin/deck/CnDeckTab.vue3.js +1 -2
  144. package/dist/esm/integrations/builtin/deck/CnDeckTab.vue3.js.map +1 -1
  145. package/dist/esm/integrations/builtin/email/CnEmailCard.vue2.js.map +1 -1
  146. package/dist/esm/integrations/builtin/email/CnEmailCard.vue3.js.map +1 -1
  147. package/dist/esm/integrations/builtin/email/CnEmailTab.vue2.js.map +1 -1
  148. package/dist/esm/integrations/builtin/email/CnEmailTab.vue3.js +1 -2
  149. package/dist/esm/integrations/builtin/email/CnEmailTab.vue3.js.map +1 -1
  150. package/dist/esm/integrations/builtin/flow/CnFlowCard.vue2.js +2 -1
  151. package/dist/esm/integrations/builtin/flow/CnFlowCard.vue2.js.map +1 -1
  152. package/dist/esm/integrations/builtin/flow/CnFlowCard.vue3.js.map +1 -1
  153. package/dist/esm/integrations/builtin/flow/CnFlowTab.vue2.js +2 -1
  154. package/dist/esm/integrations/builtin/flow/CnFlowTab.vue2.js.map +1 -1
  155. package/dist/esm/integrations/builtin/flow/CnFlowTab.vue3.js +1 -2
  156. package/dist/esm/integrations/builtin/flow/CnFlowTab.vue3.js.map +1 -1
  157. package/dist/esm/integrations/builtin/forms/CnFormsCard.vue2.js +3 -1
  158. package/dist/esm/integrations/builtin/forms/CnFormsCard.vue2.js.map +1 -1
  159. package/dist/esm/integrations/builtin/forms/CnFormsCard.vue3.js.map +1 -1
  160. package/dist/esm/integrations/builtin/forms/CnFormsTab.vue2.js +3 -1
  161. package/dist/esm/integrations/builtin/forms/CnFormsTab.vue2.js.map +1 -1
  162. package/dist/esm/integrations/builtin/forms/CnFormsTab.vue3.js +2 -4
  163. package/dist/esm/integrations/builtin/forms/CnFormsTab.vue3.js.map +1 -1
  164. package/dist/esm/integrations/builtin/maps/CnMapsCard.vue2.js +3 -1
  165. package/dist/esm/integrations/builtin/maps/CnMapsCard.vue2.js.map +1 -1
  166. package/dist/esm/integrations/builtin/maps/CnMapsCard.vue3.js.map +1 -1
  167. package/dist/esm/integrations/builtin/maps/CnMapsTab.vue2.js +3 -1
  168. package/dist/esm/integrations/builtin/maps/CnMapsTab.vue2.js.map +1 -1
  169. package/dist/esm/integrations/builtin/maps/CnMapsTab.vue3.js +2 -4
  170. package/dist/esm/integrations/builtin/maps/CnMapsTab.vue3.js.map +1 -1
  171. package/dist/esm/integrations/builtin/openproject/CnOpenprojectCard.vue2.js +3 -1
  172. package/dist/esm/integrations/builtin/openproject/CnOpenprojectCard.vue2.js.map +1 -1
  173. package/dist/esm/integrations/builtin/openproject/CnOpenprojectCard.vue3.js.map +1 -1
  174. package/dist/esm/integrations/builtin/openproject/CnOpenprojectTab.vue2.js +3 -1
  175. package/dist/esm/integrations/builtin/openproject/CnOpenprojectTab.vue2.js.map +1 -1
  176. package/dist/esm/integrations/builtin/openproject/CnOpenprojectTab.vue3.js +2 -4
  177. package/dist/esm/integrations/builtin/openproject/CnOpenprojectTab.vue3.js.map +1 -1
  178. package/dist/esm/integrations/builtin/photos/CnPhotosCard.vue2.js +2 -1
  179. package/dist/esm/integrations/builtin/photos/CnPhotosCard.vue2.js.map +1 -1
  180. package/dist/esm/integrations/builtin/photos/CnPhotosCard.vue3.js.map +1 -1
  181. package/dist/esm/integrations/builtin/photos/CnPhotosTab.vue2.js +2 -1
  182. package/dist/esm/integrations/builtin/photos/CnPhotosTab.vue2.js.map +1 -1
  183. package/dist/esm/integrations/builtin/photos/CnPhotosTab.vue3.js +1 -2
  184. package/dist/esm/integrations/builtin/photos/CnPhotosTab.vue3.js.map +1 -1
  185. package/dist/esm/integrations/builtin/polls/CnPollsCard.vue2.js +2 -1
  186. package/dist/esm/integrations/builtin/polls/CnPollsCard.vue2.js.map +1 -1
  187. package/dist/esm/integrations/builtin/polls/CnPollsCard.vue3.js.map +1 -1
  188. package/dist/esm/integrations/builtin/polls/CnPollsTab.vue2.js +2 -1
  189. package/dist/esm/integrations/builtin/polls/CnPollsTab.vue2.js.map +1 -1
  190. package/dist/esm/integrations/builtin/polls/CnPollsTab.vue3.js +1 -2
  191. package/dist/esm/integrations/builtin/polls/CnPollsTab.vue3.js.map +1 -1
  192. package/dist/esm/integrations/builtin/shares/CnSharesTab.vue2.js.map +1 -1
  193. package/dist/esm/integrations/builtin/shares/CnSharesTab.vue3.js +3 -6
  194. package/dist/esm/integrations/builtin/shares/CnSharesTab.vue3.js.map +1 -1
  195. package/dist/esm/integrations/builtin/talk/CnTalkCard.vue2.js +3 -1
  196. package/dist/esm/integrations/builtin/talk/CnTalkCard.vue2.js.map +1 -1
  197. package/dist/esm/integrations/builtin/talk/CnTalkCard.vue3.js.map +1 -1
  198. package/dist/esm/integrations/builtin/talk/CnTalkTab.vue2.js +3 -1
  199. package/dist/esm/integrations/builtin/talk/CnTalkTab.vue2.js.map +1 -1
  200. package/dist/esm/integrations/builtin/talk/CnTalkTab.vue3.js +2 -4
  201. package/dist/esm/integrations/builtin/talk/CnTalkTab.vue3.js.map +1 -1
  202. package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerCard.vue2.js +2 -1
  203. package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerCard.vue2.js.map +1 -1
  204. package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerCard.vue3.js.map +1 -1
  205. package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerTab.vue2.js +2 -1
  206. package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerTab.vue2.js.map +1 -1
  207. package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerTab.vue3.js +1 -2
  208. package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerTab.vue3.js.map +1 -1
  209. package/dist/esm/integrations/builtin/xwiki/CnXwikiCard.vue2.js +4 -1
  210. package/dist/esm/integrations/builtin/xwiki/CnXwikiCard.vue2.js.map +1 -1
  211. package/dist/esm/integrations/builtin/xwiki/CnXwikiCard.vue3.js.map +1 -1
  212. package/dist/esm/integrations/builtin/xwiki/CnXwikiTab.vue2.js +6 -3
  213. package/dist/esm/integrations/builtin/xwiki/CnXwikiTab.vue2.js.map +1 -1
  214. package/dist/esm/integrations/builtin/xwiki/CnXwikiTab.vue3.js +2 -4
  215. package/dist/esm/integrations/builtin/xwiki/CnXwikiTab.vue3.js.map +1 -1
  216. package/dist/esm/l10n/en.json.js +2 -0
  217. package/dist/esm/l10n/en.json.js.map +1 -1
  218. package/dist/esm/l10n/nl.json.js +2 -0
  219. package/dist/esm/l10n/nl.json.js.map +1 -1
  220. package/dist/esm/nextcloud-vue.css +78 -7
  221. package/dist/esm/utils/actionLink.js +5 -0
  222. package/dist/esm/utils/actionLink.js.map +1 -1
  223. package/dist/esm/utils/actionsDispatcher.js +16 -1
  224. package/dist/esm/utils/actionsDispatcher.js.map +1 -1
  225. package/dist/esm/utils/linkNavigation.js +6 -3
  226. package/dist/esm/utils/linkNavigation.js.map +1 -1
  227. package/dist/esm/utils/rowAuxClick.js +30 -5
  228. package/dist/esm/utils/rowAuxClick.js.map +1 -1
  229. package/dist/esm/utils/safeHref.js +18 -3
  230. package/dist/esm/utils/safeHref.js.map +1 -1
  231. package/dist/esm/utils/validateManifestV2.compiled.js +2 -2
  232. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  233. package/dist/nextcloud-vue.cjs.js +885 -393
  234. package/dist/nextcloud-vue.cjs.js.map +2 -2
  235. package/dist/nextcloud-vue.css +78 -7
  236. package/l10n/en.json +2 -0
  237. package/l10n/nl.json +2 -0
  238. package/package.json +1 -1
  239. package/src/components/CnActionsBar/CnActionsBar.vue +14 -5
  240. package/src/components/CnActionsMenu/CnActionsMenu.vue +0 -3
  241. package/src/components/CnAdminSettingsShell/CnAdminSettingsShell.vue +1 -2
  242. package/src/components/CnAppRoot/CnAppRoot.vue +31 -2
  243. package/src/components/CnBoardView/CnBoardView.vue +13 -5
  244. package/src/components/CnCardGrid/CnCardGrid.vue +9 -1
  245. package/src/components/CnContextMenu/CnContextMenu.vue +6 -1
  246. package/src/components/CnDataTable/CnDataTable.md +1 -1
  247. package/src/components/CnDataTable/CnDataTable.vue +131 -17
  248. package/src/components/CnDateAxisView/CnDateAxisView.vue +15 -6
  249. package/src/components/CnDetailPage/CnDetailPage.md +1 -1
  250. package/src/components/CnDetailPage/CnDetailPage.vue +29 -1
  251. package/src/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue +1 -2
  252. package/src/components/CnFilesBrowser/CnFilesBrowser.vue +5 -3
  253. package/src/components/CnFilesWidget/CnFilesWidget.vue +4 -21
  254. package/src/components/CnFlowRunsWidget/CnFlowRunsWidget.vue +8 -21
  255. package/src/components/CnFlowsPage/CnFlowsPage.vue +2 -1
  256. package/src/components/CnIndexPage/CnIndexPage.vue +75 -14
  257. package/src/components/CnIndexPage/manifestActionDispatch.js +31 -11
  258. package/src/components/CnIntegrationWidget/CnIntegrationWidgetEmpty.vue +1 -2
  259. package/src/components/CnInteractionFormWidget/CnInteractionFormWidget.vue +0 -1
  260. package/src/components/CnLinkButtonWidget/CnLinkButtonWidget.vue +2 -17
  261. package/src/components/CnLogsPage/CnLogsPage.vue +37 -12
  262. package/src/components/CnObjectCard/CnObjectCard.vue +24 -6
  263. package/src/components/CnObjectList/CnObjectList.vue +19 -3
  264. package/src/components/CnObjectListWidget/CnObjectListWidget.vue +47 -15
  265. package/src/components/CnObjectRow/CnObjectRow.vue +25 -7
  266. package/src/components/CnObjectSidebar/CnFilesTab.vue +0 -1
  267. package/src/components/CnPageRenderer/CnPageRenderer.vue +2 -0
  268. package/src/components/CnRowActions/CnRowActions.vue +7 -2
  269. package/src/components/CnSetupWizard/CnSetupWizard.vue +57 -6
  270. package/src/components/CnSpendAnalyticsWidget/CnSpendAnalyticsWidget.vue +5 -3
  271. package/src/components/CnTabs/CnTabs.vue +7 -3
  272. package/src/components/CnTasksWidget/CnTasksWidget.vue +4 -1
  273. package/src/css/table.css +26 -0
  274. package/src/integrations/builtin/analytics/CnAnalyticsCard.vue +3 -2
  275. package/src/integrations/builtin/analytics/CnAnalyticsTab.vue +5 -6
  276. package/src/integrations/builtin/bookmarks/CnBookmarksCard.vue +12 -1
  277. package/src/integrations/builtin/bookmarks/CnBookmarksTab.vue +23 -6
  278. package/src/integrations/builtin/bookmarks/__tests__/CnBookmarksTab.spec.js +7 -2
  279. package/src/integrations/builtin/collectives/CnCollectivesCard.vue +3 -2
  280. package/src/integrations/builtin/collectives/CnCollectivesTab.vue +5 -6
  281. package/src/integrations/builtin/cospend/CnCospendCard.vue +2 -2
  282. package/src/integrations/builtin/cospend/CnCospendTab.vue +3 -4
  283. package/src/integrations/builtin/deck/CnDeckCard.vue +2 -2
  284. package/src/integrations/builtin/deck/CnDeckTab.vue +3 -4
  285. package/src/integrations/builtin/email/CnEmailCard.vue +6 -2
  286. package/src/integrations/builtin/email/CnEmailTab.vue +7 -4
  287. package/src/integrations/builtin/flow/CnFlowCard.vue +2 -2
  288. package/src/integrations/builtin/flow/CnFlowTab.vue +3 -4
  289. package/src/integrations/builtin/forms/CnFormsCard.vue +3 -2
  290. package/src/integrations/builtin/forms/CnFormsTab.vue +5 -6
  291. package/src/integrations/builtin/maps/CnMapsCard.vue +3 -2
  292. package/src/integrations/builtin/maps/CnMapsTab.vue +5 -6
  293. package/src/integrations/builtin/maps/__tests__/CnMapsTab.spec.js +9 -0
  294. package/src/integrations/builtin/openproject/CnOpenprojectCard.vue +3 -2
  295. package/src/integrations/builtin/openproject/CnOpenprojectTab.vue +5 -6
  296. package/src/integrations/builtin/photos/CnPhotosCard.vue +2 -2
  297. package/src/integrations/builtin/photos/CnPhotosTab.vue +3 -4
  298. package/src/integrations/builtin/polls/CnPollsCard.vue +2 -2
  299. package/src/integrations/builtin/polls/CnPollsTab.vue +3 -4
  300. package/src/integrations/builtin/shares/CnSharesTab.vue +3 -6
  301. package/src/integrations/builtin/talk/CnTalkCard.vue +3 -2
  302. package/src/integrations/builtin/talk/CnTalkTab.vue +5 -6
  303. package/src/integrations/builtin/time-tracker/CnTimeTrackerCard.vue +2 -2
  304. package/src/integrations/builtin/time-tracker/CnTimeTrackerTab.vue +3 -4
  305. package/src/integrations/builtin/xwiki/CnXwikiCard.vue +4 -2
  306. package/src/integrations/builtin/xwiki/CnXwikiTab.vue +7 -7
  307. package/src/integrations/builtin/xwiki/__tests__/CnXwikiTab.spec.js +9 -0
  308. package/src/schemas/app-manifest-v2.schema.json +2 -1
  309. package/src/utils/actionLink.js +5 -0
  310. package/src/utils/actionsDispatcher.js +15 -0
  311. package/src/utils/linkNavigation.js +6 -3
  312. package/src/utils/rowAuxClick.js +29 -4
  313. package/src/utils/safeHref.js +18 -3
  314. package/src/utils/validateManifestV2.compiled.js +2 -2
@@ -2,7 +2,7 @@
2
2
  <div
3
3
  class="cn-object-row"
4
4
  :class="{ 'cn-object-row--selected': selected }"
5
- @mousedown="onPointerDown"
5
+ @mousedown="onRowMouseDown"
6
6
  @click="onRowClick($event)"
7
7
  @auxclick="onRowAuxClick($event)">
8
8
  <!-- Selection checkbox -->
@@ -68,7 +68,7 @@
68
68
  import { NcCheckboxRadioSwitch } from '@nextcloud/vue'
69
69
  import { useClickDragGuard } from '../../composables/useClickDragGuard.js'
70
70
  import { resolveImageUrl } from '../../utils/resolveImageUrl.js'
71
- import { isRowMiddleClick } from '../../utils/rowAuxClick.js'
71
+ import { isRowMiddleClick, preventMiddleClickAutoscroll } from '../../utils/rowAuxClick.js'
72
72
  import { CnIcon } from '../CnIcon/index.js'
73
73
  import { CnStatusBadge } from '../CnStatusBadge/index.js'
74
74
 
@@ -146,7 +146,7 @@ export default {
146
146
  // again from `$emit('click', object)` — twice per click, with different
147
147
  // payloads. Declaring the names here removes them from `$attrs`, which is
148
148
  // also what stops VTU v2 recording the native DOM event into `emitted()`.
149
- emits: ['select', 'click'],
149
+ emits: ['select', 'click', 'aux-click'],
150
150
 
151
151
  setup() {
152
152
  // Tell a deliberate row click apart from a text-selection drag.
@@ -237,15 +237,29 @@ export default {
237
237
  return
238
238
  }
239
239
  /**
240
- * @event click Emitted when a non-selectable row is clicked or middle-clicked (navigation). Payload: `(object, event)` — the row's object and the native click/auxclick event, for opening it in a new tab on a ctrl/cmd/shift or middle click.
240
+ * @event click Emitted when a non-selectable row is clicked (navigation). Payload: `(object, event)` — the row's object and the native click event, for opening it in a new tab on a ctrl/cmd/shift click. A middle click emits `aux-click` instead.
241
241
  * @type {object} The row's object.
242
242
  */
243
243
  this.$emit('click', this.object, event)
244
244
  },
245
245
 
246
246
  /**
247
- * Row-body middle click: emits `click` like a click on a
248
- * non-selectable row.
247
+ * Row mousedown: record the press for the drag guard, and on a
248
+ * non-selectable row keep a middle press from starting autoscroll.
249
+ *
250
+ * @param {MouseEvent} event The mousedown event.
251
+ */
252
+ onRowMouseDown(event) {
253
+ this.onPointerDown(event)
254
+ if (!this.selectable) {
255
+ preventMiddleClickAutoscroll(event)
256
+ }
257
+ },
258
+
259
+ /**
260
+ * Row-body middle click on a non-selectable row: emits `aux-click`, not
261
+ * `click`, so an existing `click` listener that navigates never moves
262
+ * the current tab away.
249
263
  *
250
264
  * @param {MouseEvent} event The auxclick event.
251
265
  */
@@ -253,7 +267,11 @@ export default {
253
267
  if (this.selectable || !isRowMiddleClick(event)) {
254
268
  return
255
269
  }
256
- this.$emit('click', this.object, event)
270
+ /**
271
+ * @event aux-click Emitted when a non-selectable row is middle-clicked, for opening it in a new tab (see `openRowTarget`). Payload: `(object, event)` — the row's object and the native auxclick event.
272
+ * @type {object} The row's object.
273
+ */
274
+ this.$emit('aux-click', this.object, event)
257
275
  },
258
276
  },
259
277
  }
@@ -153,7 +153,6 @@
153
153
  v-if="file.id"
154
154
  :href="showInFilesUrl(file)"
155
155
  target="_blank"
156
- rel="noopener noreferrer"
157
156
  :closeAfterClick="true">
158
157
  <template #icon>
159
158
  <FolderOutline :size="20" />
@@ -128,6 +128,7 @@
128
128
  v-bind="{ ...$attrs, ...resolvedProps }"
129
129
  @view="onRowOpen"
130
130
  @rowClick="onRowOpen"
131
+ @rowAuxClick="onRowOpen"
131
132
  @editOpen="onRowOpen"
132
133
  @configure="showConfigModal = true">
133
134
  <!-- This `<template v-for>` defines dynamic SLOTS, not a
@@ -203,6 +204,7 @@
203
204
  v-bind="{ ...$attrs, ...resolvedProps }"
204
205
  @view="onRowOpen"
205
206
  @rowClick="onRowOpen"
207
+ @rowAuxClick="onRowOpen"
206
208
  @editOpen="onRowOpen"
207
209
  @configure="showConfigModal = true">
208
210
  <!-- Dynamic slot definition, not a rendered list — see the note on
@@ -41,6 +41,7 @@
41
41
  <script>
42
42
  import { NcActionButton, NcActionLink, NcActions } from '@nextcloud/vue'
43
43
  import { followItemActionLink, resolveItemActionLink } from '../../utils/actionLink.js'
44
+ import { isModifiedClick } from '../../utils/linkNavigation.js'
44
45
  import { evaluateVisibleWhenLocal, isLocallyDecidableVisibleWhen } from '../../utils/visibleWhen.js'
45
46
  import { CnIcon } from '../CnIcon/index.js'
46
47
 
@@ -215,16 +216,20 @@ export default {
215
216
  /**
216
217
  * A link entry was clicked: route a plain in-app click, leave the rest to
217
218
  * the browser, and emit `action` as a button would. The `handler` is not
218
- * called, since navigating is what the link already does.
219
+ * called, since navigating is what the link already does. A modified
220
+ * click opens a new tab, so nothing is emitted in this one.
219
221
  *
220
222
  * @param {object} action The action definition.
221
223
  * @param {object} link The resolved link.
222
224
  * @param {MouseEvent} event The click event.
223
225
  */
224
226
  onLinkAction(action, link, event) {
227
+ if (isModifiedClick(event)) {
228
+ return
229
+ }
225
230
  followItemActionLink(event, link, this.$router)
226
231
  /**
227
- * @event action User picked an entry. Payload: the action's label and the row. A button entry has already run its `handler`; a link entry navigates instead.
232
+ * @event action User picked an entry. Payload: the action's label and the row. A button entry has already run its `handler`; a link entry navigates instead, and a modified (new-tab) click on a link emits nothing.
228
233
  * @type {{ action: string, row: object|null }}
229
234
  */
230
235
  this.$emit('action', { action: action.label, row: this.row })
@@ -133,7 +133,7 @@
133
133
  v-for="item in summaryItems"
134
134
  :key="item.id"
135
135
  class="cn-setup-summary__item"
136
- :class="{ 'cn-setup-summary__item--done': item.done }">
136
+ :class="{ 'cn-setup-summary__item--done': item.done, 'cn-setup-summary__item--not-run': item.notRun }">
137
137
  <span class="cn-setup-summary__mark">{{ item.done ? '✓' : '○' }}</span>
138
138
  <span class="cn-setup-summary__label">{{ item.title }}</span>
139
139
  <span v-if="item.value" class="cn-setup-summary__value">{{ item.value }}</span>
@@ -380,7 +380,14 @@ export default {
380
380
  * a done marker (info steps always done; choice steps done when picked;
381
381
  * run-action steps done when the action succeeded).
382
382
  *
383
- * @return {Array<{ id: string, title: string, value: string, done: boolean }>}
383
+ * An on-demand step (`onDemand: true`) is the exception: it is done
384
+ * only when its action succeeded in THIS session. Apps report such a
385
+ * step as `done` on purpose so it never auto-runs (learniq's "Remove
386
+ * the example data"), and reading that flag here ticked a removal
387
+ * nobody ran. When it did not run it gets `notRun: true` and the value
388
+ * "Not run" instead.
389
+ *
390
+ * @return {Array<{ id: string, title: string, value: string, done: boolean, notRun: boolean }>}
384
391
  */
385
392
  summaryItems() {
386
393
  return this.setupSteps
@@ -399,6 +406,16 @@ export default {
399
406
  .map((f) => `${f.label}: ${this.configModel[f.key] !== null && this.configModel[f.key] !== undefined ? this.configModel[f.key] : ''}`)
400
407
  .join(', ')
401
408
  }
409
+ if (this.isOnDemand(step)) {
410
+ const ran = this.ranThisSession(step.id)
411
+ return {
412
+ id: step.id,
413
+ title: this.stepTitle(step),
414
+ value: ran ? '' : t('nextcloud-vue', 'Not run'),
415
+ done: ran,
416
+ notRun: !ran,
417
+ }
418
+ }
402
419
  let done
403
420
  if (step.type === 'info') {
404
421
  done = true
@@ -407,7 +424,7 @@ export default {
407
424
  } else {
408
425
  done = this.isStepDone(step.id)
409
426
  }
410
- return { id: step.id, title: this.stepTitle(step), value, done }
427
+ return { id: step.id, title: this.stepTitle(step), value, done, notRun: false }
411
428
  })
412
429
  },
413
430
 
@@ -427,7 +444,9 @@ export default {
427
444
  if (this.completedStepIds.length === 0) {
428
445
  return ''
429
446
  }
430
- const actionable = this.setupSteps.filter((s) => s.type !== 'info' && s.type !== 'summary')
447
+ // An on-demand step is never outstanding work, so it is never the
448
+ // step to resume on.
449
+ const actionable = this.setupSteps.filter((s) => s.type !== 'info' && s.type !== 'summary' && !this.isOnDemand(s))
431
450
  const firstUnmetIndex = actionable.findIndex((s) => {
432
451
  if (s.type === 'choice') {
433
452
  return !(this.hasChoice(s) || this.isServerDone(s.id))
@@ -459,14 +478,16 @@ export default {
459
478
  * the step has nothing else for the user to decide by then (the
460
479
  * choice it acts on was already made on an earlier step), so
461
480
  * requiring a click before showing progress just adds a stall.
462
- * No-ops for any other step type, or one already run/running.
481
+ * No-ops for any other step type, or one already run/running, and
482
+ * never fires an on-demand step (`onDemand: true`): those run only
483
+ * when the user clicks Run.
463
484
  *
464
485
  * @param {string} stepId The step id becoming current.
465
486
  * @return {void}
466
487
  */
467
488
  maybeAutoRunStep(stepId) {
468
489
  const step = this.setupSteps.find((s) => s.id === stepId)
469
- if (step && step.type === 'run-action' && !this.isStepDone(step.id) && !this.running[step.id]) {
490
+ if (step && step.type === 'run-action' && !this.isOnDemand(step) && !this.isStepDone(step.id) && !this.running[step.id]) {
470
491
  this.runAction(step)
471
492
  }
472
493
  },
@@ -847,6 +868,29 @@ export default {
847
868
  }
848
869
  },
849
870
 
871
+ /**
872
+ * Whether a step is marked on-demand (`onDemand: true`): it runs only
873
+ * when the user asks, never automatically, and the summary ticks it
874
+ * only when it ran in this session.
875
+ *
876
+ * @param {object} step The manifest step.
877
+ * @return {boolean}
878
+ */
879
+ isOnDemand(step) {
880
+ return !!step && step.onDemand === true
881
+ },
882
+
883
+ /**
884
+ * Whether a step's action succeeded in the CURRENT session, ignoring
885
+ * whatever the server reports.
886
+ *
887
+ * @param {string} id Step id.
888
+ * @return {boolean}
889
+ */
890
+ ranThisSession(id) {
891
+ return !!(this.actionResult[id] && this.actionResult[id].success)
892
+ },
893
+
850
894
  isStepDone(id) {
851
895
  return this.isServerDone(id) || this.localDone[id] === true || (this.actionResult[id] && this.actionResult[id].success)
852
896
  },
@@ -964,4 +1008,11 @@ export default {
964
1008
  font-weight: 600;
965
1009
  color: var(--color-main-text);
966
1010
  }
1011
+
1012
+ /* An on-demand step that did not run says so quietly: it is a choice the
1013
+ user did not make, not a failure. */
1014
+ .cn-setup-summary__item--not-run .cn-setup-summary__value {
1015
+ font-weight: normal;
1016
+ color: var(--color-text-maxcontrast);
1017
+ }
967
1018
  </style>
@@ -145,7 +145,8 @@ const DEBOUNCE_MS = 200
145
145
  * Data origin (decoupling seam): all data flows through a consumer-supplied
146
146
  * `dataSource` prop OR the `cnSpendAnalyticsSource` injection — an object
147
147
  * exposing `fetchSummary(args)`, `fetchVendorCommitments(args)`,
148
- * `fetchNarrative(args)`, and optionally `resolveDeepLink(app, kind, id)`. The
148
+ * `fetchNarrative(args)`, and optionally `resolveDeepLink(app, kind, id)`
149
+ * (pure and synchronous: it runs for every row on every render). The
149
150
  * renderer imports NO financeq / procest / launchpad service module and never
150
151
  * calls axios itself; the consuming app (typically over GraphQL) owns the
151
152
  * transport. The `requires.graphql` hint lives only in the registry entry, not
@@ -182,8 +183,9 @@ export default {
182
183
  * Consumer-supplied data source overriding the
183
184
  * `cnSpendAnalyticsSource` injection. Must expose `fetchSummary`,
184
185
  * `fetchVendorCommitments`, and `fetchNarrative`; may expose
185
- * `resolveDeepLink`. When `null` the injection (then the empty state)
186
- * is used.
186
+ * `resolveDeepLink`, which must be pure and synchronous, since it is
187
+ * called for every row on every render. When `null` the injection
188
+ * (then the empty state) is used.
187
189
  *
188
190
  * @type {object|null}
189
191
  */
@@ -467,7 +467,11 @@ export default defineComponent({
467
467
  }
468
468
 
469
469
  /* A fade over the clipped edge, only on the side that hides tabs. Stops a
470
- pixel above the bar's rule so the rule keeps its full colour. */
470
+ pixel above the bar's rule so the rule keeps its full colour.
471
+
472
+ z-index 2 so the fades outrank a lifted tab: CnTabsWidget puts the open tab
473
+ at 1, and a tie goes to DOM order, which would draw that tab over the start
474
+ fade (it precedes the tabs) while the end fade still covered it. */
471
475
  .cn-tabs__strip::before,
472
476
  .cn-tabs__strip::after {
473
477
  content: '';
@@ -476,7 +480,7 @@ export default defineComponent({
476
480
  pointer-events: none;
477
481
  position: absolute;
478
482
  width: 56px;
479
- z-index: 1;
483
+ z-index: 2;
480
484
  }
481
485
 
482
486
  .cn-tabs__strip::before {
@@ -513,7 +517,7 @@ export default defineComponent({
513
517
  padding: 0;
514
518
  position: absolute;
515
519
  width: 28px;
516
- z-index: 2;
520
+ z-index: 3;
517
521
  }
518
522
 
519
523
  /* Nextcloud's `button:hover` rule would otherwise fill the chevron. */
@@ -31,6 +31,7 @@
31
31
  class="cn-tasks-widget__row"
32
32
  :data-state="task.state"
33
33
  @click="onRowClick(task, $event)"
34
+ @mousedown="preventMiddleClickAutoscroll"
34
35
  @auxclick="onRowAuxClick(task, $event)">
35
36
  <span class="cn-tasks-widget__body">
36
37
  <span class="cn-tasks-widget__name">{{ titleOf(task) }}</span>
@@ -90,7 +91,7 @@ import { inject, ref } from 'vue'
90
91
  import { taskDeepLink, taskDueLabel } from '../../composables/indexSources.js'
91
92
  import { useEndpointSource } from '../../composables/useEndpointSource.js'
92
93
  import { openRowTarget } from '../../utils/linkNavigation.js'
93
- import { isRowMiddleClick } from '../../utils/rowAuxClick.js'
94
+ import { isRowMiddleClick, preventMiddleClickAutoscroll } from '../../utils/rowAuxClick.js'
94
95
 
95
96
  /**
96
97
  * The OpenRegister inbox read (openregister flow-task-entity). One endpoint
@@ -367,6 +368,8 @@ export default {
367
368
  },
368
369
 
369
370
  methods: {
371
+ preventMiddleClickAutoscroll,
372
+
370
373
  /**
371
374
  * Start (or restart) the refetch interval, unless polling is off.
372
375
  *
package/src/css/table.css CHANGED
@@ -334,6 +334,32 @@
334
334
  background-color: var(--color-primary-light);
335
335
  }
336
336
 
337
+ /* A row with a route is a real link (see CnDataTable's row link): the anchor
338
+ in its first cell is stretched over the whole row. The row's own controls
339
+ sit above it so they stay usable; keep the list in step with NESTED_CONTROL
340
+ in utils/rowAuxClick.js. */
341
+ .cn-table-row--linked {
342
+ position: relative;
343
+ }
344
+
345
+ .cn-table-row__link {
346
+ position: absolute;
347
+ inset: 0;
348
+ cursor: pointer;
349
+ }
350
+
351
+ .cn-table-row__link:focus-visible {
352
+ outline: 2px solid var(--color-main-text);
353
+ outline-offset: -2px;
354
+ }
355
+
356
+ .cn-table-row--linked .cn-table-col--checkbox,
357
+ .cn-table-row--linked .cn-table-col--actions,
358
+ .cn-table-row--linked td :is(a, button, input, select, textarea, label, summary, [role='button'], [role='checkbox'], [role='link'], [contenteditable='true']):not(.cn-table-row__link) {
359
+ position: relative;
360
+ z-index: 1;
361
+ }
362
+
337
363
  /* Column Types */
338
364
  .cn-table-col--checkbox {
339
365
  width: 50px;
@@ -122,7 +122,7 @@ import ChartPie from 'vue-material-design-icons/ChartPie.vue'
122
122
  import TableIcon from 'vue-material-design-icons/Table.vue'
123
123
  import ViewDashboard from 'vue-material-design-icons/ViewDashboard.vue'
124
124
  import CnDetailCard from '../../../components/CnDetailCard/CnDetailCard.vue'
125
- import { buildHeaders, prefixUrl } from '../../../utils/index.js'
125
+ import { buildHeaders, prefixUrl, safeHref } from '../../../utils/index.js'
126
126
 
127
127
  const VALID_SURFACES = ['user-dashboard', 'app-dashboard', 'detail-page', 'single-entity']
128
128
  const COMPACT_LIMIT = 5
@@ -287,8 +287,9 @@ export default {
287
287
  return `cn-analytics-card__badge--type-${type ?? 'unknown'}`
288
288
  },
289
289
 
290
+ // A provider URL is used only when it is a safe scheme; else the app link.
290
291
  reportUrl(report) {
291
- if (report && report.url) {
292
+ if (report && report.url && safeHref(report.url) !== '#') {
292
293
  return report.url
293
294
  }
294
295
  const id = this.reportKey(report)
@@ -82,8 +82,7 @@
82
82
  <NcButton
83
83
  variant="primary"
84
84
  :href="analyticsAppUrl"
85
- target="_blank"
86
- rel="noopener noreferrer">
85
+ target="_blank">
87
86
  <template #icon>
88
87
  <ChartBar :size="20" />
89
88
  </template>
@@ -131,8 +130,7 @@
131
130
  <NcActionLink
132
131
  :closeAfterClick="true"
133
132
  :href="reportUrl(report)"
134
- target="_blank"
135
- rel="noopener noreferrer">
133
+ target="_blank">
136
134
  <template #icon>
137
135
  <OpenInNew :size="20" />
138
136
  </template>
@@ -178,7 +176,7 @@ import ViewDashboard from 'vue-material-design-icons/ViewDashboard.vue'
178
176
  import CnAnalyticsReportCreate from '../../../components/CnAnalyticsReportCreate/CnAnalyticsReportCreate.vue'
179
177
  import CnAnalyticsReportPicker from '../../../components/CnAnalyticsReportPicker/CnAnalyticsReportPicker.vue'
180
178
  import CnStatusBadge from '../../../components/CnStatusBadge/CnStatusBadge.vue'
181
- import { buildHeaders, prefixUrl } from '../../../utils/index.js'
179
+ import { buildHeaders, prefixUrl, safeHref } from '../../../utils/index.js'
182
180
 
183
181
  /**
184
182
  * CnAnalyticsTab — bespoke dataset list for the `analytics` integration.
@@ -408,8 +406,9 @@ export default {
408
406
  return t('nextcloud-vue', 'Latest value')
409
407
  },
410
408
 
409
+ // A provider URL is used only when it is a safe scheme; else the app link.
411
410
  reportUrl(report) {
412
- if (report && report.url) {
411
+ if (report && report.url && safeHref(report.url) !== '#') {
413
412
  return report.url
414
413
  }
415
414
  const id = this.reportKey(report)
@@ -34,9 +34,11 @@
34
34
  @error="onFaviconError(entity)">
35
35
  <Bookmark v-else :size="14" />
36
36
  <a
37
+ v-if="hasSafeUrl(entity)"
37
38
  :href="entity.url"
38
39
  target="_blank"
39
40
  rel="noopener noreferrer">{{ bookmarkTitle(entity) }}</a>
41
+ <span v-else>{{ bookmarkTitle(entity) }}</span>
40
42
  </span>
41
43
  <span v-else class="cn-bookmarks-card__empty">{{ emptyLabel }}</span>
42
44
  </template>
@@ -62,9 +64,11 @@
62
64
  @error="onFaviconError(mostRecent)">
63
65
  <Bookmark v-else :size="14" />
64
66
  <a
67
+ v-if="hasSafeUrl(mostRecent)"
65
68
  :href="mostRecent.url"
66
69
  target="_blank"
67
70
  rel="noopener noreferrer">{{ bookmarkTitle(mostRecent) }}</a>
71
+ <span v-else>{{ bookmarkTitle(mostRecent) }}</span>
68
72
  </div>
69
73
  <div v-if="bookmarks.length > 1" class="cn-bookmarks-card__view-all">
70
74
  <a
@@ -99,10 +103,12 @@
99
103
  </div>
100
104
  <div class="cn-bookmarks-card__row-main">
101
105
  <a
106
+ v-if="hasSafeUrl(bookmark)"
102
107
  :href="bookmark.url"
103
108
  target="_blank"
104
109
  rel="noopener noreferrer"
105
110
  class="cn-bookmarks-card__title">{{ bookmarkTitle(bookmark) }}</a>
111
+ <span v-else class="cn-bookmarks-card__title">{{ bookmarkTitle(bookmark) }}</span>
106
112
  </div>
107
113
  </li>
108
114
  </ul>
@@ -121,7 +127,7 @@ import { translatePlural as n, translate as t } from '@nextcloud/l10n'
121
127
  import { NcLoadingIcon } from '@nextcloud/vue'
122
128
  import Bookmark from 'vue-material-design-icons/Bookmark.vue'
123
129
  import CnDetailCard from '../../../components/CnDetailCard/CnDetailCard.vue'
124
- import { buildHeaders, prefixUrl } from '../../../utils/index.js'
130
+ import { buildHeaders, prefixUrl, safeHref } from '../../../utils/index.js'
125
131
 
126
132
  const VALID_SURFACES = ['user-dashboard', 'app-dashboard', 'detail-page', 'single-entity']
127
133
  const COMPACT_LIMIT = 5
@@ -245,6 +251,11 @@ export default {
245
251
  return bookmark.title || bookmark.url || ''
246
252
  },
247
253
 
254
+ // Bookmark URLs are user-entered (bookmarklets are `javascript:`), so only a safe one is a link.
255
+ hasSafeUrl(bookmark) {
256
+ return safeHref(bookmark.url) !== '#'
257
+ },
258
+
248
259
  chipSubtitle(bookmark) {
249
260
  return bookmark.description || bookmark.url || this.bookmarkTitle(bookmark)
250
261
  },
@@ -57,8 +57,7 @@
57
57
  <NcButton
58
58
  variant="primary"
59
59
  :href="bookmarksAppUrl"
60
- target="_blank"
61
- rel="noopener noreferrer">
60
+ target="_blank">
62
61
  <template #icon>
63
62
  <Bookmark :size="20" />
64
63
  </template>
@@ -92,8 +91,9 @@
92
91
  :name="bookmarkTitle(bookmark)"
93
92
  :bold="true"
94
93
  :href="bookmarkHref(bookmark)"
95
- target="_blank"
96
- :forceDisplayActions="true">
94
+ :target="hasSafeUrl(bookmark) ? '_blank' : undefined"
95
+ :forceDisplayActions="true"
96
+ @click="onBookmarkClick(bookmark, $event)">
97
97
  <template #icon>
98
98
  <span class="cn-bookmarks-tab__row-icon">
99
99
  <Bookmark :size="20" />
@@ -121,10 +121,10 @@
121
121
  </template>
122
122
  <template #actions>
123
123
  <NcActionLink
124
+ v-if="hasSafeUrl(bookmark)"
124
125
  :closeAfterClick="true"
125
126
  :href="bookmarkHref(bookmark)"
126
- target="_blank"
127
- rel="noopener noreferrer">
127
+ target="_blank">
128
128
  <template #icon>
129
129
  <OpenInNew :size="20" />
130
130
  </template>
@@ -393,6 +393,23 @@ export default {
393
393
  return safeHref(bookmark.url)
394
394
  },
395
395
 
396
+ /**
397
+ * Whether the bookmark's URL passed `safeHref`, so its row is a link.
398
+ *
399
+ * @param {object} bookmark The bookmark row.
400
+ * @return {boolean}
401
+ */
402
+ hasSafeUrl(bookmark) {
403
+ return this.bookmarkHref(bookmark) !== '#'
404
+ },
405
+
406
+ // NcListItem always renders an anchor; one without a safe URL must not follow `#`.
407
+ onBookmarkClick(bookmark, event) {
408
+ if (!this.hasSafeUrl(bookmark)) {
409
+ event?.preventDefault?.()
410
+ }
411
+ },
412
+
396
413
  bookmarkTags(bookmark) {
397
414
  const tags = bookmark.tags
398
415
  if (!Array.isArray(tags)) {
@@ -78,7 +78,7 @@ describe('CnBookmarksTab', () => {
78
78
  expect(openLink.attributes('target')).toBe('_blank')
79
79
  })
80
80
 
81
- it('neutralises unsafe bookmark URL schemes in the row and action hrefs', async () => {
81
+ it('renders a bookmark with an unsafe URL as an inert row with no Open action', async () => {
82
82
  global.fetch = jest.fn().mockResolvedValueOnce({
83
83
  ok: true,
84
84
  status: 200,
@@ -90,7 +90,12 @@ describe('CnBookmarksTab', () => {
90
90
  await flushPromises()
91
91
  const row = wrapper.find('.cn-bookmarks-tab__row')
92
92
  expect(row.attributes('href')).toBe('#')
93
- expect(row.find('.stub.NcActionLink').attributes('href')).toBe('#')
93
+ expect(row.attributes('target')).toBeUndefined()
94
+ expect(row.find('.stub.NcActionLink').exists()).toBe(false)
95
+
96
+ const event = { preventDefault: jest.fn() }
97
+ wrapper.vm.onBookmarkClick(wrapper.vm.filteredBookmarks[0], event)
98
+ expect(event.preventDefault).toHaveBeenCalled()
94
99
  wrapper.unmount()
95
100
  })
96
101
 
@@ -126,7 +126,7 @@ import { translatePlural as n, translate as t } from '@nextcloud/l10n'
126
126
  import { NcLoadingIcon } from '@nextcloud/vue'
127
127
  import BookOpenPageVariant from 'vue-material-design-icons/BookOpenPageVariant.vue'
128
128
  import CnDetailCard from '../../../components/CnDetailCard/CnDetailCard.vue'
129
- import { buildHeaders, prefixUrl } from '../../../utils/index.js'
129
+ import { buildHeaders, prefixUrl, safeHref } from '../../../utils/index.js'
130
130
  import { stripMarker } from '../../utils/marker.js'
131
131
 
132
132
  const VALID_SURFACES = ['user-dashboard', 'app-dashboard', 'detail-page', 'single-entity']
@@ -263,8 +263,9 @@ export default {
263
263
  return typeof emoji === 'string' ? emoji.trim() : ''
264
264
  },
265
265
 
266
+ // A provider URL is used only when it is a safe scheme; else the app link.
266
267
  pageUrl(page) {
267
- if (typeof page.url === 'string' && page.url !== '') {
268
+ if (typeof page.url === 'string' && page.url !== '' && safeHref(page.url) !== '#') {
268
269
  return page.url
269
270
  }
270
271
  const d = this.dataOf(page)
@@ -72,8 +72,7 @@
72
72
  <NcButton
73
73
  variant="primary"
74
74
  :href="collectivesAppUrl"
75
- target="_blank"
76
- rel="noopener noreferrer">
75
+ target="_blank">
77
76
  <template #icon>
78
77
  <BookOpenPageVariant :size="20" />
79
78
  </template>
@@ -122,8 +121,7 @@
122
121
  <NcActionLink
123
122
  :closeAfterClick="true"
124
123
  :href="pageUrl(page)"
125
- target="_blank"
126
- rel="noopener noreferrer">
124
+ target="_blank">
127
125
  <template #icon>
128
126
  <OpenInNew :size="20" />
129
127
  </template>
@@ -168,7 +166,7 @@ import OpenInNew from 'vue-material-design-icons/OpenInNew.vue'
168
166
  import Plus from 'vue-material-design-icons/Plus.vue'
169
167
  import CnCollectivePageCreate from '../../../components/CnCollectivePageCreate/CnCollectivePageCreate.vue'
170
168
  import CnCollectivePagePicker from '../../../components/CnCollectivePagePicker/CnCollectivePagePicker.vue'
171
- import { buildHeaders, prefixUrl } from '../../../utils/index.js'
169
+ import { buildHeaders, prefixUrl, safeHref } from '../../../utils/index.js'
172
170
  import { stripMarker } from '../../utils/marker.js'
173
171
 
174
172
  const MS_PER_DAY = 24 * 60 * 60 * 1000
@@ -333,8 +331,9 @@ export default {
333
331
  return text
334
332
  },
335
333
 
334
+ // A provider URL is used only when it is a safe scheme; else the app link.
336
335
  pageUrl(page) {
337
- if (typeof page.url === 'string' && page.url !== '') {
336
+ if (typeof page.url === 'string' && page.url !== '' && safeHref(page.url) !== '#') {
338
337
  return page.url
339
338
  }
340
339
  const d = this.dataOf(page)
@@ -108,7 +108,7 @@ import { translatePlural as n, translate as t } from '@nextcloud/l10n'
108
108
  import { NcLoadingIcon } from '@nextcloud/vue'
109
109
  import CurrencyEur from 'vue-material-design-icons/CurrencyEur.vue'
110
110
  import CnDetailCard from '../../../components/CnDetailCard/CnDetailCard.vue'
111
- import { buildHeaders, prefixUrl } from '../../../utils/index.js'
111
+ import { buildHeaders, prefixUrl, safeHref } from '../../../utils/index.js'
112
112
 
113
113
  const VALID_SURFACES = ['user-dashboard', 'app-dashboard', 'detail-page', 'single-entity']
114
114
 
@@ -250,7 +250,7 @@ export default {
250
250
  },
251
251
 
252
252
  rowUrl(row) {
253
- if (row.url) {
253
+ if (row.url && safeHref(row.url) !== '#') {
254
254
  return row.url
255
255
  }
256
256
  const projectId = row.projectId ?? row.project_id ?? row.data?.projectid ?? row.data?.project_id ?? null