@conduction/nextcloud-vue 2.58.0 → 2.59.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 (304) 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/CnBoardView/CnBoardView.vue2.js +12 -5
  12. package/dist/esm/components/CnBoardView/CnBoardView.vue2.js.map +1 -1
  13. package/dist/esm/components/CnBoardView/CnBoardView.vue3.js +2 -1
  14. package/dist/esm/components/CnBoardView/CnBoardView.vue3.js.map +1 -1
  15. package/dist/esm/components/CnCardGrid/CnCardGrid.vue2.js +9 -1
  16. package/dist/esm/components/CnCardGrid/CnCardGrid.vue2.js.map +1 -1
  17. package/dist/esm/components/CnCardGrid/CnCardGrid.vue3.js.map +1 -1
  18. package/dist/esm/components/CnContextMenu/CnContextMenu.vue2.js +6 -1
  19. package/dist/esm/components/CnContextMenu/CnContextMenu.vue2.js.map +1 -1
  20. package/dist/esm/components/CnContextMenu/CnContextMenu.vue3.js.map +1 -1
  21. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +116 -15
  22. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
  23. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +27 -14
  24. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
  25. package/dist/esm/components/CnDateAxisView/CnDateAxisView.vue2.js +13 -6
  26. package/dist/esm/components/CnDateAxisView/CnDateAxisView.vue2.js.map +1 -1
  27. package/dist/esm/components/CnDateAxisView/CnDateAxisView.vue3.js +2 -0
  28. package/dist/esm/components/CnDateAxisView/CnDateAxisView.vue3.js.map +1 -1
  29. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +29 -1
  30. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  31. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  32. package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue2.js.map +1 -1
  33. package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue3.js +1 -2
  34. package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue3.js.map +1 -1
  35. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +3 -1
  36. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
  37. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +17 -17
  38. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
  39. package/dist/esm/components/CnFilesWidget/CnFilesWidget.vue2.js +4 -21
  40. package/dist/esm/components/CnFilesWidget/CnFilesWidget.vue2.js.map +1 -1
  41. package/dist/esm/components/CnFilesWidget/CnFilesWidget.vue3.js.map +1 -1
  42. package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue2.js +0 -13
  43. package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue2.js.map +1 -1
  44. package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue3.js +8 -8
  45. package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue3.js.map +1 -1
  46. package/dist/esm/components/CnFlowsPage/CnFlowsPage.vue2.js.map +1 -1
  47. package/dist/esm/components/CnFlowsPage/CnFlowsPage.vue3.js +3 -2
  48. package/dist/esm/components/CnFlowsPage/CnFlowsPage.vue3.js.map +1 -1
  49. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +67 -13
  50. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  51. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +15 -8
  52. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  53. package/dist/esm/components/CnIndexPage/manifestActionDispatch.js +31 -11
  54. package/dist/esm/components/CnIndexPage/manifestActionDispatch.js.map +1 -1
  55. package/dist/esm/components/CnIntegrationWidget/CnIntegrationWidgetEmpty.vue2.js.map +1 -1
  56. package/dist/esm/components/CnIntegrationWidget/CnIntegrationWidgetEmpty.vue3.js +1 -2
  57. package/dist/esm/components/CnIntegrationWidget/CnIntegrationWidgetEmpty.vue3.js.map +1 -1
  58. package/dist/esm/components/CnInteractionFormWidget/CnInteractionFormWidget.vue2.js.map +1 -1
  59. package/dist/esm/components/CnInteractionFormWidget/CnInteractionFormWidget.vue3.js +0 -1
  60. package/dist/esm/components/CnInteractionFormWidget/CnInteractionFormWidget.vue3.js.map +1 -1
  61. package/dist/esm/components/CnLinkButtonWidget/CnLinkButtonWidget.vue2.js +2 -17
  62. package/dist/esm/components/CnLinkButtonWidget/CnLinkButtonWidget.vue2.js.map +1 -1
  63. package/dist/esm/components/CnLinkButtonWidget/CnLinkButtonWidget.vue3.js.map +1 -1
  64. package/dist/esm/components/CnLogsPage/CnLogsPage.vue2.js +35 -11
  65. package/dist/esm/components/CnLogsPage/CnLogsPage.vue2.js.map +1 -1
  66. package/dist/esm/components/CnLogsPage/CnLogsPage.vue3.js +3 -2
  67. package/dist/esm/components/CnLogsPage/CnLogsPage.vue3.js.map +1 -1
  68. package/dist/esm/components/CnObjectCard/CnObjectCard.vue2.js +23 -5
  69. package/dist/esm/components/CnObjectCard/CnObjectCard.vue2.js.map +1 -1
  70. package/dist/esm/components/CnObjectCard/CnObjectCard.vue3.js +1 -1
  71. package/dist/esm/components/CnObjectCard/CnObjectCard.vue3.js.map +1 -1
  72. package/dist/esm/components/CnObjectList/CnObjectList.vue2.js +19 -3
  73. package/dist/esm/components/CnObjectList/CnObjectList.vue2.js.map +1 -1
  74. package/dist/esm/components/CnObjectList/CnObjectList.vue3.js.map +1 -1
  75. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +45 -15
  76. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
  77. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js +4 -2
  78. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
  79. package/dist/esm/components/CnObjectRow/CnObjectRow.vue2.js +24 -6
  80. package/dist/esm/components/CnObjectRow/CnObjectRow.vue2.js.map +1 -1
  81. package/dist/esm/components/CnObjectRow/CnObjectRow.vue3.js +1 -1
  82. package/dist/esm/components/CnObjectRow/CnObjectRow.vue3.js.map +1 -1
  83. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
  84. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +0 -1
  85. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
  86. package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue2.js.map +1 -1
  87. package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue3.js +4 -2
  88. package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue3.js.map +1 -1
  89. package/dist/esm/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue2.js +100 -3
  90. package/dist/esm/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue2.js.map +1 -1
  91. package/dist/esm/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue3.js.map +1 -1
  92. package/dist/esm/components/CnRowActions/CnRowActions.vue2.js +7 -2
  93. package/dist/esm/components/CnRowActions/CnRowActions.vue2.js.map +1 -1
  94. package/dist/esm/components/CnRowActions/CnRowActions.vue3.js.map +1 -1
  95. package/dist/esm/components/CnSpendAnalyticsWidget/CnSpendAnalyticsWidget.vue2.js +5 -3
  96. package/dist/esm/components/CnSpendAnalyticsWidget/CnSpendAnalyticsWidget.vue2.js.map +1 -1
  97. package/dist/esm/components/CnSpendAnalyticsWidget/CnSpendAnalyticsWidget.vue3.js.map +1 -1
  98. package/dist/esm/components/CnTabs/CnTabs.vue2.js.map +1 -1
  99. package/dist/esm/components/CnTabs/CnTabs.vue3.js.map +1 -1
  100. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue2.js +3 -1
  101. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue2.js.map +1 -1
  102. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue3.js +1 -0
  103. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue3.js.map +1 -1
  104. package/dist/esm/integrations/builtin/analytics/CnAnalyticsCard.vue2.js +3 -1
  105. package/dist/esm/integrations/builtin/analytics/CnAnalyticsCard.vue2.js.map +1 -1
  106. package/dist/esm/integrations/builtin/analytics/CnAnalyticsCard.vue3.js.map +1 -1
  107. package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue2.js +3 -1
  108. package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue2.js.map +1 -1
  109. package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue3.js +2 -4
  110. package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue3.js.map +1 -1
  111. package/dist/esm/integrations/builtin/bookmarks/CnBookmarksCard.vue2.js +6 -0
  112. package/dist/esm/integrations/builtin/bookmarks/CnBookmarksCard.vue2.js.map +1 -1
  113. package/dist/esm/integrations/builtin/bookmarks/CnBookmarksCard.vue3.js +68 -53
  114. package/dist/esm/integrations/builtin/bookmarks/CnBookmarksCard.vue3.js.map +1 -1
  115. package/dist/esm/integrations/builtin/bookmarks/CnBookmarksTab.vue2.js +17 -0
  116. package/dist/esm/integrations/builtin/bookmarks/CnBookmarksTab.vue2.js.map +1 -1
  117. package/dist/esm/integrations/builtin/bookmarks/CnBookmarksTab.vue3.js +21 -19
  118. package/dist/esm/integrations/builtin/bookmarks/CnBookmarksTab.vue3.js.map +1 -1
  119. package/dist/esm/integrations/builtin/collectives/CnCollectivesCard.vue2.js +3 -1
  120. package/dist/esm/integrations/builtin/collectives/CnCollectivesCard.vue2.js.map +1 -1
  121. package/dist/esm/integrations/builtin/collectives/CnCollectivesCard.vue3.js.map +1 -1
  122. package/dist/esm/integrations/builtin/collectives/CnCollectivesTab.vue2.js +3 -1
  123. package/dist/esm/integrations/builtin/collectives/CnCollectivesTab.vue2.js.map +1 -1
  124. package/dist/esm/integrations/builtin/collectives/CnCollectivesTab.vue3.js +2 -4
  125. package/dist/esm/integrations/builtin/collectives/CnCollectivesTab.vue3.js.map +1 -1
  126. package/dist/esm/integrations/builtin/cospend/CnCospendCard.vue2.js +2 -1
  127. package/dist/esm/integrations/builtin/cospend/CnCospendCard.vue2.js.map +1 -1
  128. package/dist/esm/integrations/builtin/cospend/CnCospendCard.vue3.js.map +1 -1
  129. package/dist/esm/integrations/builtin/cospend/CnCospendTab.vue2.js +2 -1
  130. package/dist/esm/integrations/builtin/cospend/CnCospendTab.vue2.js.map +1 -1
  131. package/dist/esm/integrations/builtin/cospend/CnCospendTab.vue3.js +1 -2
  132. package/dist/esm/integrations/builtin/cospend/CnCospendTab.vue3.js.map +1 -1
  133. package/dist/esm/integrations/builtin/deck/CnDeckCard.vue2.js +2 -1
  134. package/dist/esm/integrations/builtin/deck/CnDeckCard.vue2.js.map +1 -1
  135. package/dist/esm/integrations/builtin/deck/CnDeckCard.vue3.js.map +1 -1
  136. package/dist/esm/integrations/builtin/deck/CnDeckTab.vue2.js +2 -1
  137. package/dist/esm/integrations/builtin/deck/CnDeckTab.vue2.js.map +1 -1
  138. package/dist/esm/integrations/builtin/deck/CnDeckTab.vue3.js +1 -2
  139. package/dist/esm/integrations/builtin/deck/CnDeckTab.vue3.js.map +1 -1
  140. package/dist/esm/integrations/builtin/email/CnEmailCard.vue2.js.map +1 -1
  141. package/dist/esm/integrations/builtin/email/CnEmailCard.vue3.js.map +1 -1
  142. package/dist/esm/integrations/builtin/email/CnEmailTab.vue2.js.map +1 -1
  143. package/dist/esm/integrations/builtin/email/CnEmailTab.vue3.js +1 -2
  144. package/dist/esm/integrations/builtin/email/CnEmailTab.vue3.js.map +1 -1
  145. package/dist/esm/integrations/builtin/flow/CnFlowCard.vue2.js +2 -1
  146. package/dist/esm/integrations/builtin/flow/CnFlowCard.vue2.js.map +1 -1
  147. package/dist/esm/integrations/builtin/flow/CnFlowCard.vue3.js.map +1 -1
  148. package/dist/esm/integrations/builtin/flow/CnFlowTab.vue2.js +2 -1
  149. package/dist/esm/integrations/builtin/flow/CnFlowTab.vue2.js.map +1 -1
  150. package/dist/esm/integrations/builtin/flow/CnFlowTab.vue3.js +1 -2
  151. package/dist/esm/integrations/builtin/flow/CnFlowTab.vue3.js.map +1 -1
  152. package/dist/esm/integrations/builtin/forms/CnFormsCard.vue2.js +3 -1
  153. package/dist/esm/integrations/builtin/forms/CnFormsCard.vue2.js.map +1 -1
  154. package/dist/esm/integrations/builtin/forms/CnFormsCard.vue3.js.map +1 -1
  155. package/dist/esm/integrations/builtin/forms/CnFormsTab.vue2.js +3 -1
  156. package/dist/esm/integrations/builtin/forms/CnFormsTab.vue2.js.map +1 -1
  157. package/dist/esm/integrations/builtin/forms/CnFormsTab.vue3.js +2 -4
  158. package/dist/esm/integrations/builtin/forms/CnFormsTab.vue3.js.map +1 -1
  159. package/dist/esm/integrations/builtin/maps/CnMapsCard.vue2.js +3 -1
  160. package/dist/esm/integrations/builtin/maps/CnMapsCard.vue2.js.map +1 -1
  161. package/dist/esm/integrations/builtin/maps/CnMapsCard.vue3.js.map +1 -1
  162. package/dist/esm/integrations/builtin/maps/CnMapsTab.vue2.js +3 -1
  163. package/dist/esm/integrations/builtin/maps/CnMapsTab.vue2.js.map +1 -1
  164. package/dist/esm/integrations/builtin/maps/CnMapsTab.vue3.js +2 -4
  165. package/dist/esm/integrations/builtin/maps/CnMapsTab.vue3.js.map +1 -1
  166. package/dist/esm/integrations/builtin/openproject/CnOpenprojectCard.vue2.js +3 -1
  167. package/dist/esm/integrations/builtin/openproject/CnOpenprojectCard.vue2.js.map +1 -1
  168. package/dist/esm/integrations/builtin/openproject/CnOpenprojectCard.vue3.js.map +1 -1
  169. package/dist/esm/integrations/builtin/openproject/CnOpenprojectTab.vue2.js +3 -1
  170. package/dist/esm/integrations/builtin/openproject/CnOpenprojectTab.vue2.js.map +1 -1
  171. package/dist/esm/integrations/builtin/openproject/CnOpenprojectTab.vue3.js +2 -4
  172. package/dist/esm/integrations/builtin/openproject/CnOpenprojectTab.vue3.js.map +1 -1
  173. package/dist/esm/integrations/builtin/photos/CnPhotosCard.vue2.js +2 -1
  174. package/dist/esm/integrations/builtin/photos/CnPhotosCard.vue2.js.map +1 -1
  175. package/dist/esm/integrations/builtin/photos/CnPhotosCard.vue3.js.map +1 -1
  176. package/dist/esm/integrations/builtin/photos/CnPhotosTab.vue2.js +2 -1
  177. package/dist/esm/integrations/builtin/photos/CnPhotosTab.vue2.js.map +1 -1
  178. package/dist/esm/integrations/builtin/photos/CnPhotosTab.vue3.js +1 -2
  179. package/dist/esm/integrations/builtin/photos/CnPhotosTab.vue3.js.map +1 -1
  180. package/dist/esm/integrations/builtin/polls/CnPollsCard.vue2.js +2 -1
  181. package/dist/esm/integrations/builtin/polls/CnPollsCard.vue2.js.map +1 -1
  182. package/dist/esm/integrations/builtin/polls/CnPollsCard.vue3.js.map +1 -1
  183. package/dist/esm/integrations/builtin/polls/CnPollsTab.vue2.js +2 -1
  184. package/dist/esm/integrations/builtin/polls/CnPollsTab.vue2.js.map +1 -1
  185. package/dist/esm/integrations/builtin/polls/CnPollsTab.vue3.js +1 -2
  186. package/dist/esm/integrations/builtin/polls/CnPollsTab.vue3.js.map +1 -1
  187. package/dist/esm/integrations/builtin/shares/CnSharesTab.vue2.js.map +1 -1
  188. package/dist/esm/integrations/builtin/shares/CnSharesTab.vue3.js +3 -6
  189. package/dist/esm/integrations/builtin/shares/CnSharesTab.vue3.js.map +1 -1
  190. package/dist/esm/integrations/builtin/talk/CnTalkCard.vue2.js +3 -1
  191. package/dist/esm/integrations/builtin/talk/CnTalkCard.vue2.js.map +1 -1
  192. package/dist/esm/integrations/builtin/talk/CnTalkCard.vue3.js.map +1 -1
  193. package/dist/esm/integrations/builtin/talk/CnTalkTab.vue2.js +3 -1
  194. package/dist/esm/integrations/builtin/talk/CnTalkTab.vue2.js.map +1 -1
  195. package/dist/esm/integrations/builtin/talk/CnTalkTab.vue3.js +2 -4
  196. package/dist/esm/integrations/builtin/talk/CnTalkTab.vue3.js.map +1 -1
  197. package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerCard.vue2.js +2 -1
  198. package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerCard.vue2.js.map +1 -1
  199. package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerCard.vue3.js.map +1 -1
  200. package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerTab.vue2.js +2 -1
  201. package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerTab.vue2.js.map +1 -1
  202. package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerTab.vue3.js +1 -2
  203. package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerTab.vue3.js.map +1 -1
  204. package/dist/esm/integrations/builtin/xwiki/CnXwikiCard.vue2.js +4 -1
  205. package/dist/esm/integrations/builtin/xwiki/CnXwikiCard.vue2.js.map +1 -1
  206. package/dist/esm/integrations/builtin/xwiki/CnXwikiCard.vue3.js.map +1 -1
  207. package/dist/esm/integrations/builtin/xwiki/CnXwikiTab.vue2.js +6 -3
  208. package/dist/esm/integrations/builtin/xwiki/CnXwikiTab.vue2.js.map +1 -1
  209. package/dist/esm/integrations/builtin/xwiki/CnXwikiTab.vue3.js +2 -4
  210. package/dist/esm/integrations/builtin/xwiki/CnXwikiTab.vue3.js.map +1 -1
  211. package/dist/esm/l10n/en.json.js +1 -0
  212. package/dist/esm/l10n/en.json.js.map +1 -1
  213. package/dist/esm/l10n/nl.json.js +1 -0
  214. package/dist/esm/l10n/nl.json.js.map +1 -1
  215. package/dist/esm/nextcloud-vue.css +71 -7
  216. package/dist/esm/utils/actionLink.js +5 -0
  217. package/dist/esm/utils/actionLink.js.map +1 -1
  218. package/dist/esm/utils/actionsDispatcher.js +16 -1
  219. package/dist/esm/utils/actionsDispatcher.js.map +1 -1
  220. package/dist/esm/utils/linkNavigation.js +6 -3
  221. package/dist/esm/utils/linkNavigation.js.map +1 -1
  222. package/dist/esm/utils/rowAuxClick.js +30 -5
  223. package/dist/esm/utils/rowAuxClick.js.map +1 -1
  224. package/dist/esm/utils/safeHref.js +18 -3
  225. package/dist/esm/utils/safeHref.js.map +1 -1
  226. package/dist/nextcloud-vue.cjs.js +909 -386
  227. package/dist/nextcloud-vue.cjs.js.map +2 -2
  228. package/dist/nextcloud-vue.css +71 -7
  229. package/l10n/en.json +1 -0
  230. package/l10n/nl.json +1 -0
  231. package/package.json +1 -1
  232. package/src/components/CnActionsBar/CnActionsBar.vue +14 -5
  233. package/src/components/CnActionsMenu/CnActionsMenu.vue +0 -3
  234. package/src/components/CnAdminSettingsShell/CnAdminSettingsShell.vue +1 -2
  235. package/src/components/CnBoardView/CnBoardView.vue +13 -5
  236. package/src/components/CnCardGrid/CnCardGrid.vue +9 -1
  237. package/src/components/CnContextMenu/CnContextMenu.vue +6 -1
  238. package/src/components/CnDataTable/CnDataTable.md +1 -1
  239. package/src/components/CnDataTable/CnDataTable.vue +131 -17
  240. package/src/components/CnDateAxisView/CnDateAxisView.vue +15 -6
  241. package/src/components/CnDetailPage/CnDetailPage.md +1 -1
  242. package/src/components/CnDetailPage/CnDetailPage.vue +29 -1
  243. package/src/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue +1 -2
  244. package/src/components/CnFilesBrowser/CnFilesBrowser.vue +5 -3
  245. package/src/components/CnFilesWidget/CnFilesWidget.vue +4 -21
  246. package/src/components/CnFlowRunsWidget/CnFlowRunsWidget.vue +8 -21
  247. package/src/components/CnFlowsPage/CnFlowsPage.vue +2 -1
  248. package/src/components/CnIndexPage/CnIndexPage.vue +75 -14
  249. package/src/components/CnIndexPage/manifestActionDispatch.js +31 -11
  250. package/src/components/CnIntegrationWidget/CnIntegrationWidgetEmpty.vue +1 -2
  251. package/src/components/CnInteractionFormWidget/CnInteractionFormWidget.vue +0 -1
  252. package/src/components/CnLinkButtonWidget/CnLinkButtonWidget.vue +2 -17
  253. package/src/components/CnLogsPage/CnLogsPage.vue +37 -12
  254. package/src/components/CnObjectCard/CnObjectCard.vue +24 -6
  255. package/src/components/CnObjectList/CnObjectList.vue +19 -3
  256. package/src/components/CnObjectListWidget/CnObjectListWidget.vue +47 -15
  257. package/src/components/CnObjectRow/CnObjectRow.vue +25 -7
  258. package/src/components/CnObjectSidebar/CnFilesTab.vue +0 -1
  259. package/src/components/CnPageRenderer/CnPageRenderer.vue +2 -0
  260. package/src/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue +100 -3
  261. package/src/components/CnRowActions/CnRowActions.vue +7 -2
  262. package/src/components/CnSpendAnalyticsWidget/CnSpendAnalyticsWidget.vue +5 -3
  263. package/src/components/CnTabs/CnTabs.vue +7 -3
  264. package/src/components/CnTasksWidget/CnTasksWidget.vue +4 -1
  265. package/src/css/table.css +26 -0
  266. package/src/integrations/builtin/analytics/CnAnalyticsCard.vue +3 -2
  267. package/src/integrations/builtin/analytics/CnAnalyticsTab.vue +5 -6
  268. package/src/integrations/builtin/bookmarks/CnBookmarksCard.vue +12 -1
  269. package/src/integrations/builtin/bookmarks/CnBookmarksTab.vue +23 -6
  270. package/src/integrations/builtin/bookmarks/__tests__/CnBookmarksTab.spec.js +7 -2
  271. package/src/integrations/builtin/collectives/CnCollectivesCard.vue +3 -2
  272. package/src/integrations/builtin/collectives/CnCollectivesTab.vue +5 -6
  273. package/src/integrations/builtin/cospend/CnCospendCard.vue +2 -2
  274. package/src/integrations/builtin/cospend/CnCospendTab.vue +3 -4
  275. package/src/integrations/builtin/deck/CnDeckCard.vue +2 -2
  276. package/src/integrations/builtin/deck/CnDeckTab.vue +3 -4
  277. package/src/integrations/builtin/email/CnEmailCard.vue +6 -2
  278. package/src/integrations/builtin/email/CnEmailTab.vue +7 -4
  279. package/src/integrations/builtin/flow/CnFlowCard.vue +2 -2
  280. package/src/integrations/builtin/flow/CnFlowTab.vue +3 -4
  281. package/src/integrations/builtin/forms/CnFormsCard.vue +3 -2
  282. package/src/integrations/builtin/forms/CnFormsTab.vue +5 -6
  283. package/src/integrations/builtin/maps/CnMapsCard.vue +3 -2
  284. package/src/integrations/builtin/maps/CnMapsTab.vue +5 -6
  285. package/src/integrations/builtin/maps/__tests__/CnMapsTab.spec.js +9 -0
  286. package/src/integrations/builtin/openproject/CnOpenprojectCard.vue +3 -2
  287. package/src/integrations/builtin/openproject/CnOpenprojectTab.vue +5 -6
  288. package/src/integrations/builtin/photos/CnPhotosCard.vue +2 -2
  289. package/src/integrations/builtin/photos/CnPhotosTab.vue +3 -4
  290. package/src/integrations/builtin/polls/CnPollsCard.vue +2 -2
  291. package/src/integrations/builtin/polls/CnPollsTab.vue +3 -4
  292. package/src/integrations/builtin/shares/CnSharesTab.vue +3 -6
  293. package/src/integrations/builtin/talk/CnTalkCard.vue +3 -2
  294. package/src/integrations/builtin/talk/CnTalkTab.vue +5 -6
  295. package/src/integrations/builtin/time-tracker/CnTimeTrackerCard.vue +2 -2
  296. package/src/integrations/builtin/time-tracker/CnTimeTrackerTab.vue +3 -4
  297. package/src/integrations/builtin/xwiki/CnXwikiCard.vue +4 -2
  298. package/src/integrations/builtin/xwiki/CnXwikiTab.vue +7 -7
  299. package/src/integrations/builtin/xwiki/__tests__/CnXwikiTab.spec.js +9 -0
  300. package/src/utils/actionLink.js +5 -0
  301. package/src/utils/actionsDispatcher.js +15 -0
  302. package/src/utils/linkNavigation.js +6 -3
  303. package/src/utils/rowAuxClick.js +29 -4
  304. package/src/utils/safeHref.js +18 -3
@@ -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
@@ -75,8 +75,7 @@
75
75
  <NcButton
76
76
  variant="primary"
77
77
  :href="cospendAppUrl"
78
- target="_blank"
79
- rel="noopener noreferrer">
78
+ target="_blank">
80
79
  <template #icon>
81
80
  <CurrencyEur :size="20" />
82
81
  </template>
@@ -175,7 +174,7 @@ import Plus from 'vue-material-design-icons/Plus.vue'
175
174
  import CnCospendCreate from '../../../components/CnCospendCreate/CnCospendCreate.vue'
176
175
  import CnCospendPicker from '../../../components/CnCospendPicker/CnCospendPicker.vue'
177
176
  import CnStatusBadge from '../../../components/CnStatusBadge/CnStatusBadge.vue'
178
- import { buildHeaders, prefixUrl } from '../../../utils/index.js'
177
+ import { buildHeaders, prefixUrl, safeHref } from '../../../utils/index.js'
179
178
 
180
179
  /**
181
180
  * CnCospendTab — bespoke sidebar tab for the `cospend` integration.
@@ -389,7 +388,7 @@ export default {
389
388
  },
390
389
 
391
390
  rowUrl(row) {
392
- if (row.url) {
391
+ if (row.url && safeHref(row.url) !== '#') {
393
392
  return row.url
394
393
  }
395
394
  const projectId = row.projectId ?? row.project_id ?? row.data?.projectid ?? row.data?.project_id ?? null
@@ -110,7 +110,7 @@ import { translatePlural as n, translate as t } from '@nextcloud/l10n'
110
110
  import { NcLoadingIcon } from '@nextcloud/vue'
111
111
  import ViewColumnOutline from 'vue-material-design-icons/ViewColumnOutline.vue'
112
112
  import CnDetailCard from '../../../components/CnDetailCard/CnDetailCard.vue'
113
- import { buildHeaders, prefixUrl } from '../../../utils/index.js'
113
+ import { buildHeaders, prefixUrl, safeHref } from '../../../utils/index.js'
114
114
 
115
115
  const VALID_SURFACES = ['user-dashboard', 'app-dashboard', 'detail-page', 'single-entity']
116
116
 
@@ -276,7 +276,7 @@ export default {
276
276
  },
277
277
 
278
278
  cardUrl(card) {
279
- if (card.url) {
279
+ if (card.url && safeHref(card.url) !== '#') {
280
280
  return card.url
281
281
  }
282
282
  const boardId = card.boardId ?? card.board?.id ?? ''
@@ -58,8 +58,7 @@
58
58
  <NcButton
59
59
  variant="primary"
60
60
  :href="deckAppUrl"
61
- target="_blank"
62
- rel="noopener noreferrer">
61
+ target="_blank">
63
62
  <template #icon>
64
63
  <ViewColumnOutline :size="20" />
65
64
  </template>
@@ -168,7 +167,7 @@ import ViewColumnOutline from 'vue-material-design-icons/ViewColumnOutline.vue'
168
167
  import CnDeckCardCreate from '../../../components/CnDeckCardCreate/CnDeckCardCreate.vue'
169
168
  import CnDeckCardPicker from '../../../components/CnDeckCardPicker/CnDeckCardPicker.vue'
170
169
  import CnStatusBadge from '../../../components/CnStatusBadge/CnStatusBadge.vue'
171
- import { buildHeaders, prefixUrl } from '../../../utils/index.js'
170
+ import { buildHeaders, prefixUrl, safeHref } from '../../../utils/index.js'
172
171
 
173
172
  /** Maximum assignee avatars shown before collapsing the rest into a +N overflow badge. */
174
173
  const MAX_VISIBLE_ASSIGNEES = 3
@@ -353,7 +352,7 @@ export default {
353
352
  },
354
353
 
355
354
  cardUrl(card) {
356
- if (card.url) {
355
+ if (card.url && safeHref(card.url) !== '#') {
357
356
  return card.url
358
357
  }
359
358
  const boardId = card.boardId ?? card.board?.id ?? ''
@@ -321,7 +321,6 @@ export default {
321
321
  background: none;
322
322
  border: none;
323
323
  padding: 6px 0;
324
- cursor: pointer;
325
324
  text-align: left;
326
325
  color: inherit;
327
326
  font: inherit;
@@ -329,7 +328,12 @@ export default {
329
328
  box-sizing: border-box;
330
329
  }
331
330
 
332
- .cn-email-card__open:hover .cn-email-card__subject {
331
+ /* Only a row with Mail ids is a link; the rest must not look clickable. */
332
+ a.cn-email-card__open {
333
+ cursor: pointer;
334
+ }
335
+
336
+ a.cn-email-card__open:hover .cn-email-card__subject {
333
337
  color: var(--color-primary-element);
334
338
  }
335
339
 
@@ -27,8 +27,7 @@
27
27
  <NcButton
28
28
  variant="primary"
29
29
  :href="composeUrl"
30
- target="_blank"
31
- rel="noopener noreferrer">
30
+ target="_blank">
32
31
  <template #icon>
33
32
  <EmailEditOutline :size="18" />
34
33
  </template>
@@ -497,11 +496,15 @@ export default {
497
496
  border-radius: var(--border-radius-large, 8px);
498
497
  color: inherit;
499
498
  text-decoration: none;
500
- cursor: pointer;
501
499
  position: relative;
502
500
  }
503
501
 
504
- .cn-email-tab__row:hover {
502
+ /* Only a row with Mail ids is a link; the rest must not look clickable. */
503
+ a.cn-email-tab__row {
504
+ cursor: pointer;
505
+ }
506
+
507
+ a.cn-email-tab__row:hover {
505
508
  background-color: var(--color-background-hover);
506
509
  }
507
510
 
@@ -104,7 +104,7 @@ import { translatePlural as n, translate as t } from '@nextcloud/l10n'
104
104
  import { NcLoadingIcon } from '@nextcloud/vue'
105
105
  import SitemapOutline from 'vue-material-design-icons/SitemapOutline.vue'
106
106
  import CnDetailCard from '../../../components/CnDetailCard/CnDetailCard.vue'
107
- import { buildHeaders, prefixUrl } from '../../../utils/index.js'
107
+ import { buildHeaders, prefixUrl, safeHref } from '../../../utils/index.js'
108
108
 
109
109
  const VALID_SURFACES = ['user-dashboard', 'app-dashboard', 'detail-page', 'single-entity']
110
110
  const COMPACT_LIMIT = 5
@@ -239,7 +239,7 @@ export default {
239
239
  },
240
240
 
241
241
  opUrl(op) {
242
- return op.url || this.flowSettingsUrl
242
+ return op.url && safeHref(op.url) !== '#' ? op.url : this.flowSettingsUrl
243
243
  },
244
244
 
245
245
  shortClass(className) {
@@ -58,8 +58,7 @@
58
58
  <NcButton
59
59
  variant="primary"
60
60
  :href="flowSettingsUrl"
61
- target="_blank"
62
- rel="noopener noreferrer">
61
+ target="_blank">
63
62
  <template #icon>
64
63
  <SitemapOutline :size="20" />
65
64
  </template>
@@ -139,7 +138,7 @@ import Plus from 'vue-material-design-icons/Plus.vue'
139
138
  import SitemapOutline from 'vue-material-design-icons/SitemapOutline.vue'
140
139
  import CnFlowOperationPicker from '../../../components/CnFlowOperationPicker/CnFlowOperationPicker.vue'
141
140
  import CnStatusBadge from '../../../components/CnStatusBadge/CnStatusBadge.vue'
142
- import { buildHeaders, prefixUrl } from '../../../utils/index.js'
141
+ import { buildHeaders, prefixUrl, safeHref } from '../../../utils/index.js'
143
142
 
144
143
  /**
145
144
  * CnFlowTab — bespoke automation-rule list for the `flow` integration.
@@ -218,7 +217,7 @@ export default {
218
217
  },
219
218
 
220
219
  opUrl(op) {
221
- return op.url || this.flowSettingsUrl
220
+ return op.url && safeHref(op.url) !== '#' ? op.url : this.flowSettingsUrl
222
221
  },
223
222
 
224
223
  shortClass(className) {
@@ -100,7 +100,7 @@ import { translatePlural as n, translate as t } from '@nextcloud/l10n'
100
100
  import { NcLoadingIcon } from '@nextcloud/vue'
101
101
  import ClipboardText from 'vue-material-design-icons/ClipboardText.vue'
102
102
  import CnDetailCard from '../../../components/CnDetailCard/CnDetailCard.vue'
103
- import { buildHeaders, prefixUrl } from '../../../utils/index.js'
103
+ import { buildHeaders, prefixUrl, safeHref } from '../../../utils/index.js'
104
104
 
105
105
  const VALID_SURFACES = ['user-dashboard', 'app-dashboard', 'detail-page', 'single-entity']
106
106
  const COMPACT_LIMIT = 5
@@ -289,8 +289,9 @@ export default {
289
289
  return form.title || form.id || t('nextcloud-vue', 'Untitled form')
290
290
  },
291
291
 
292
+ // A provider URL is used only when it is a safe scheme; else the app link.
292
293
  formUrl(form) {
293
- if (form.url) {
294
+ if (form.url && safeHref(form.url) !== '#') {
294
295
  return form.url
295
296
  }
296
297
  if (form.hash) {
@@ -67,8 +67,7 @@
67
67
  <NcButton
68
68
  variant="primary"
69
69
  :href="formsAppUrl"
70
- target="_blank"
71
- rel="noopener noreferrer">
70
+ target="_blank">
72
71
  <template #icon>
73
72
  <ClipboardText :size="20" />
74
73
  </template>
@@ -118,8 +117,7 @@
118
117
  <NcActionLink
119
118
  :closeAfterClick="true"
120
119
  :href="formUrl(form)"
121
- target="_blank"
122
- rel="noopener noreferrer">
120
+ target="_blank">
123
121
  <template #icon>
124
122
  <OpenInNew :size="20" />
125
123
  </template>
@@ -168,7 +166,7 @@ import Plus from 'vue-material-design-icons/Plus.vue'
168
166
  import CnStatusBadge from '../../../components/CnStatusBadge/CnStatusBadge.vue'
169
167
  import CnFormCreate from './CnFormCreate.vue'
170
168
  import CnFormPicker from './CnFormPicker.vue'
171
- import { buildHeaders, prefixUrl } from '../../../utils/index.js'
169
+ import { buildHeaders, prefixUrl, safeHref } from '../../../utils/index.js'
172
170
 
173
171
  /**
174
172
  * CnFormsTab — bespoke linked-forms list for the Tier-2 `forms` leaf.
@@ -332,8 +330,9 @@ export default {
332
330
  return form.description || ''
333
331
  },
334
332
 
333
+ // A provider URL is used only when it is a safe scheme; else the app link.
335
334
  formUrl(form) {
336
- if (form.url) {
335
+ if (form.url && safeHref(form.url) !== '#') {
337
336
  return form.url
338
337
  }
339
338
  if (form.hash) {
@@ -125,7 +125,7 @@ import { translatePlural as n, translate as t } from '@nextcloud/l10n'
125
125
  import { NcLoadingIcon } from '@nextcloud/vue'
126
126
  import MapMarker from 'vue-material-design-icons/MapMarker.vue'
127
127
  import CnDetailCard from '../../../components/CnDetailCard/CnDetailCard.vue'
128
- import { buildHeaders, prefixUrl } from '../../../utils/index.js'
128
+ import { buildHeaders, prefixUrl, safeHref } from '../../../utils/index.js'
129
129
  import { stripMarker } from '../../utils/marker.js'
130
130
 
131
131
  const VALID_SURFACES = ['user-dashboard', 'app-dashboard', 'detail-page', 'single-entity']
@@ -291,8 +291,9 @@ export default {
291
291
  return `${lat.toFixed(5)}, ${lng.toFixed(5)}`
292
292
  },
293
293
 
294
+ // A provider URL is used only when it is a safe scheme; else the app link.
294
295
  pointUrl(point) {
295
- if (typeof point.url === 'string' && point.url !== '') {
296
+ if (typeof point.url === 'string' && point.url !== '' && safeHref(point.url) !== '#') {
296
297
  return point.url
297
298
  }
298
299
  const lat = this.pointLat(point)
@@ -86,8 +86,7 @@
86
86
  <NcButton
87
87
  variant="primary"
88
88
  :href="mapsAppUrl"
89
- target="_blank"
90
- rel="noopener noreferrer">
89
+ target="_blank">
91
90
  <template #icon>
92
91
  <MapMarker :size="20" />
93
92
  </template>
@@ -146,8 +145,7 @@
146
145
  <NcActionLink
147
146
  :closeAfterClick="true"
148
147
  :href="pointUrl(point)"
149
- target="_blank"
150
- rel="noopener noreferrer">
148
+ target="_blank">
151
149
  <template #icon>
152
150
  <OpenInNew :size="20" />
153
151
  </template>
@@ -188,7 +186,7 @@ import OpenInNew from 'vue-material-design-icons/OpenInNew.vue'
188
186
  import Plus from 'vue-material-design-icons/Plus.vue'
189
187
  import CnMapPoiCreate from '../../../components/CnMapPoiCreate/CnMapPoiCreate.vue'
190
188
  import CnMapPoiPicker from '../../../components/CnMapPoiPicker/CnMapPoiPicker.vue'
191
- import { buildHeaders, prefixUrl } from '../../../utils/index.js'
189
+ import { buildHeaders, prefixUrl, safeHref } from '../../../utils/index.js'
192
190
  import { stripMarker } from '../../utils/marker.js'
193
191
 
194
192
  const COMMENT_MAX_CHARS = 160
@@ -389,8 +387,9 @@ export default {
389
387
  return `${lat.toFixed(5)}, ${lng.toFixed(5)}`
390
388
  },
391
389
 
390
+ // A provider URL is used only when it is a safe scheme; else the app link.
392
391
  pointUrl(point) {
393
- if (typeof point.url === 'string' && point.url !== '') {
392
+ if (typeof point.url === 'string' && point.url !== '' && safeHref(point.url) !== '#') {
394
393
  return point.url
395
394
  }
396
395
  if (this.pointHasCoords(point)) {
@@ -45,6 +45,15 @@ describe('CnMapsTab', () => {
45
45
  delete global.fetch
46
46
  })
47
47
 
48
+ it('links to the Maps app instead of a provider URL with an unsafe scheme', async () => {
49
+ global.fetch = jest.fn().mockResolvedValueOnce({ ok: true, status: 200, json: () => Promise.resolve({ results: [] }) })
50
+ const wrapper = mount(CnMapsTab, { propsData: { ...DEFAULT_PROPS } })
51
+ await flushPromises()
52
+ expect(wrapper.vm.pointUrl({ url: 'javascript:alert(1)' })).toBe('/index.php/apps/maps')
53
+ expect(wrapper.vm.pointUrl({ url: 'https://maps.example/p/1' })).toBe('https://maps.example/p/1')
54
+ wrapper.unmount()
55
+ })
56
+
48
57
  it('renders the empty state with an "Open Locations" CTA when no points', async () => {
49
58
  global.fetch = jest.fn().mockResolvedValueOnce({ ok: true, status: 200, json: () => Promise.resolve({ results: [] }) })
50
59
  const wrapper = mount(CnMapsTab, { propsData: { ...DEFAULT_PROPS } })