@conduction/nextcloud-vue 2.56.0-beta.4 → 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 (300) 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/CnRowActions/CnRowActions.vue2.js +7 -2
  90. package/dist/esm/components/CnRowActions/CnRowActions.vue2.js.map +1 -1
  91. package/dist/esm/components/CnRowActions/CnRowActions.vue3.js.map +1 -1
  92. package/dist/esm/components/CnSpendAnalyticsWidget/CnSpendAnalyticsWidget.vue2.js +5 -3
  93. package/dist/esm/components/CnSpendAnalyticsWidget/CnSpendAnalyticsWidget.vue2.js.map +1 -1
  94. package/dist/esm/components/CnSpendAnalyticsWidget/CnSpendAnalyticsWidget.vue3.js.map +1 -1
  95. package/dist/esm/components/CnTabs/CnTabs.vue2.js.map +1 -1
  96. package/dist/esm/components/CnTabs/CnTabs.vue3.js.map +1 -1
  97. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue2.js +3 -1
  98. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue2.js.map +1 -1
  99. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue3.js +1 -0
  100. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue3.js.map +1 -1
  101. package/dist/esm/integrations/builtin/analytics/CnAnalyticsCard.vue2.js +3 -1
  102. package/dist/esm/integrations/builtin/analytics/CnAnalyticsCard.vue2.js.map +1 -1
  103. package/dist/esm/integrations/builtin/analytics/CnAnalyticsCard.vue3.js.map +1 -1
  104. package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue2.js +3 -1
  105. package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue2.js.map +1 -1
  106. package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue3.js +2 -4
  107. package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue3.js.map +1 -1
  108. package/dist/esm/integrations/builtin/bookmarks/CnBookmarksCard.vue2.js +6 -0
  109. package/dist/esm/integrations/builtin/bookmarks/CnBookmarksCard.vue2.js.map +1 -1
  110. package/dist/esm/integrations/builtin/bookmarks/CnBookmarksCard.vue3.js +68 -53
  111. package/dist/esm/integrations/builtin/bookmarks/CnBookmarksCard.vue3.js.map +1 -1
  112. package/dist/esm/integrations/builtin/bookmarks/CnBookmarksTab.vue2.js +17 -0
  113. package/dist/esm/integrations/builtin/bookmarks/CnBookmarksTab.vue2.js.map +1 -1
  114. package/dist/esm/integrations/builtin/bookmarks/CnBookmarksTab.vue3.js +21 -19
  115. package/dist/esm/integrations/builtin/bookmarks/CnBookmarksTab.vue3.js.map +1 -1
  116. package/dist/esm/integrations/builtin/collectives/CnCollectivesCard.vue2.js +3 -1
  117. package/dist/esm/integrations/builtin/collectives/CnCollectivesCard.vue2.js.map +1 -1
  118. package/dist/esm/integrations/builtin/collectives/CnCollectivesCard.vue3.js.map +1 -1
  119. package/dist/esm/integrations/builtin/collectives/CnCollectivesTab.vue2.js +3 -1
  120. package/dist/esm/integrations/builtin/collectives/CnCollectivesTab.vue2.js.map +1 -1
  121. package/dist/esm/integrations/builtin/collectives/CnCollectivesTab.vue3.js +2 -4
  122. package/dist/esm/integrations/builtin/collectives/CnCollectivesTab.vue3.js.map +1 -1
  123. package/dist/esm/integrations/builtin/cospend/CnCospendCard.vue2.js +2 -1
  124. package/dist/esm/integrations/builtin/cospend/CnCospendCard.vue2.js.map +1 -1
  125. package/dist/esm/integrations/builtin/cospend/CnCospendCard.vue3.js.map +1 -1
  126. package/dist/esm/integrations/builtin/cospend/CnCospendTab.vue2.js +2 -1
  127. package/dist/esm/integrations/builtin/cospend/CnCospendTab.vue2.js.map +1 -1
  128. package/dist/esm/integrations/builtin/cospend/CnCospendTab.vue3.js +1 -2
  129. package/dist/esm/integrations/builtin/cospend/CnCospendTab.vue3.js.map +1 -1
  130. package/dist/esm/integrations/builtin/deck/CnDeckCard.vue2.js +2 -1
  131. package/dist/esm/integrations/builtin/deck/CnDeckCard.vue2.js.map +1 -1
  132. package/dist/esm/integrations/builtin/deck/CnDeckCard.vue3.js.map +1 -1
  133. package/dist/esm/integrations/builtin/deck/CnDeckTab.vue2.js +2 -1
  134. package/dist/esm/integrations/builtin/deck/CnDeckTab.vue2.js.map +1 -1
  135. package/dist/esm/integrations/builtin/deck/CnDeckTab.vue3.js +1 -2
  136. package/dist/esm/integrations/builtin/deck/CnDeckTab.vue3.js.map +1 -1
  137. package/dist/esm/integrations/builtin/email/CnEmailCard.vue2.js.map +1 -1
  138. package/dist/esm/integrations/builtin/email/CnEmailCard.vue3.js.map +1 -1
  139. package/dist/esm/integrations/builtin/email/CnEmailTab.vue2.js.map +1 -1
  140. package/dist/esm/integrations/builtin/email/CnEmailTab.vue3.js +1 -2
  141. package/dist/esm/integrations/builtin/email/CnEmailTab.vue3.js.map +1 -1
  142. package/dist/esm/integrations/builtin/flow/CnFlowCard.vue2.js +2 -1
  143. package/dist/esm/integrations/builtin/flow/CnFlowCard.vue2.js.map +1 -1
  144. package/dist/esm/integrations/builtin/flow/CnFlowCard.vue3.js.map +1 -1
  145. package/dist/esm/integrations/builtin/flow/CnFlowTab.vue2.js +2 -1
  146. package/dist/esm/integrations/builtin/flow/CnFlowTab.vue2.js.map +1 -1
  147. package/dist/esm/integrations/builtin/flow/CnFlowTab.vue3.js +1 -2
  148. package/dist/esm/integrations/builtin/flow/CnFlowTab.vue3.js.map +1 -1
  149. package/dist/esm/integrations/builtin/forms/CnFormsCard.vue2.js +3 -1
  150. package/dist/esm/integrations/builtin/forms/CnFormsCard.vue2.js.map +1 -1
  151. package/dist/esm/integrations/builtin/forms/CnFormsCard.vue3.js.map +1 -1
  152. package/dist/esm/integrations/builtin/forms/CnFormsTab.vue2.js +3 -1
  153. package/dist/esm/integrations/builtin/forms/CnFormsTab.vue2.js.map +1 -1
  154. package/dist/esm/integrations/builtin/forms/CnFormsTab.vue3.js +2 -4
  155. package/dist/esm/integrations/builtin/forms/CnFormsTab.vue3.js.map +1 -1
  156. package/dist/esm/integrations/builtin/maps/CnMapsCard.vue2.js +3 -1
  157. package/dist/esm/integrations/builtin/maps/CnMapsCard.vue2.js.map +1 -1
  158. package/dist/esm/integrations/builtin/maps/CnMapsCard.vue3.js.map +1 -1
  159. package/dist/esm/integrations/builtin/maps/CnMapsTab.vue2.js +3 -1
  160. package/dist/esm/integrations/builtin/maps/CnMapsTab.vue2.js.map +1 -1
  161. package/dist/esm/integrations/builtin/maps/CnMapsTab.vue3.js +2 -4
  162. package/dist/esm/integrations/builtin/maps/CnMapsTab.vue3.js.map +1 -1
  163. package/dist/esm/integrations/builtin/openproject/CnOpenprojectCard.vue2.js +3 -1
  164. package/dist/esm/integrations/builtin/openproject/CnOpenprojectCard.vue2.js.map +1 -1
  165. package/dist/esm/integrations/builtin/openproject/CnOpenprojectCard.vue3.js.map +1 -1
  166. package/dist/esm/integrations/builtin/openproject/CnOpenprojectTab.vue2.js +3 -1
  167. package/dist/esm/integrations/builtin/openproject/CnOpenprojectTab.vue2.js.map +1 -1
  168. package/dist/esm/integrations/builtin/openproject/CnOpenprojectTab.vue3.js +2 -4
  169. package/dist/esm/integrations/builtin/openproject/CnOpenprojectTab.vue3.js.map +1 -1
  170. package/dist/esm/integrations/builtin/photos/CnPhotosCard.vue2.js +2 -1
  171. package/dist/esm/integrations/builtin/photos/CnPhotosCard.vue2.js.map +1 -1
  172. package/dist/esm/integrations/builtin/photos/CnPhotosCard.vue3.js.map +1 -1
  173. package/dist/esm/integrations/builtin/photos/CnPhotosTab.vue2.js +2 -1
  174. package/dist/esm/integrations/builtin/photos/CnPhotosTab.vue2.js.map +1 -1
  175. package/dist/esm/integrations/builtin/photos/CnPhotosTab.vue3.js +1 -2
  176. package/dist/esm/integrations/builtin/photos/CnPhotosTab.vue3.js.map +1 -1
  177. package/dist/esm/integrations/builtin/polls/CnPollsCard.vue2.js +2 -1
  178. package/dist/esm/integrations/builtin/polls/CnPollsCard.vue2.js.map +1 -1
  179. package/dist/esm/integrations/builtin/polls/CnPollsCard.vue3.js.map +1 -1
  180. package/dist/esm/integrations/builtin/polls/CnPollsTab.vue2.js +2 -1
  181. package/dist/esm/integrations/builtin/polls/CnPollsTab.vue2.js.map +1 -1
  182. package/dist/esm/integrations/builtin/polls/CnPollsTab.vue3.js +1 -2
  183. package/dist/esm/integrations/builtin/polls/CnPollsTab.vue3.js.map +1 -1
  184. package/dist/esm/integrations/builtin/shares/CnSharesTab.vue2.js.map +1 -1
  185. package/dist/esm/integrations/builtin/shares/CnSharesTab.vue3.js +3 -6
  186. package/dist/esm/integrations/builtin/shares/CnSharesTab.vue3.js.map +1 -1
  187. package/dist/esm/integrations/builtin/talk/CnTalkCard.vue2.js +3 -1
  188. package/dist/esm/integrations/builtin/talk/CnTalkCard.vue2.js.map +1 -1
  189. package/dist/esm/integrations/builtin/talk/CnTalkCard.vue3.js.map +1 -1
  190. package/dist/esm/integrations/builtin/talk/CnTalkTab.vue2.js +3 -1
  191. package/dist/esm/integrations/builtin/talk/CnTalkTab.vue2.js.map +1 -1
  192. package/dist/esm/integrations/builtin/talk/CnTalkTab.vue3.js +2 -4
  193. package/dist/esm/integrations/builtin/talk/CnTalkTab.vue3.js.map +1 -1
  194. package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerCard.vue2.js +2 -1
  195. package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerCard.vue2.js.map +1 -1
  196. package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerCard.vue3.js.map +1 -1
  197. package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerTab.vue2.js +2 -1
  198. package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerTab.vue2.js.map +1 -1
  199. package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerTab.vue3.js +1 -2
  200. package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerTab.vue3.js.map +1 -1
  201. package/dist/esm/integrations/builtin/xwiki/CnXwikiCard.vue2.js +4 -1
  202. package/dist/esm/integrations/builtin/xwiki/CnXwikiCard.vue2.js.map +1 -1
  203. package/dist/esm/integrations/builtin/xwiki/CnXwikiCard.vue3.js.map +1 -1
  204. package/dist/esm/integrations/builtin/xwiki/CnXwikiTab.vue2.js +6 -3
  205. package/dist/esm/integrations/builtin/xwiki/CnXwikiTab.vue2.js.map +1 -1
  206. package/dist/esm/integrations/builtin/xwiki/CnXwikiTab.vue3.js +2 -4
  207. package/dist/esm/integrations/builtin/xwiki/CnXwikiTab.vue3.js.map +1 -1
  208. package/dist/esm/l10n/en.json.js +1 -0
  209. package/dist/esm/l10n/en.json.js.map +1 -1
  210. package/dist/esm/l10n/nl.json.js +1 -0
  211. package/dist/esm/l10n/nl.json.js.map +1 -1
  212. package/dist/esm/nextcloud-vue.css +71 -7
  213. package/dist/esm/utils/actionLink.js +5 -0
  214. package/dist/esm/utils/actionLink.js.map +1 -1
  215. package/dist/esm/utils/actionsDispatcher.js +16 -1
  216. package/dist/esm/utils/actionsDispatcher.js.map +1 -1
  217. package/dist/esm/utils/linkNavigation.js +6 -3
  218. package/dist/esm/utils/linkNavigation.js.map +1 -1
  219. package/dist/esm/utils/rowAuxClick.js +30 -5
  220. package/dist/esm/utils/rowAuxClick.js.map +1 -1
  221. package/dist/esm/utils/safeHref.js +18 -3
  222. package/dist/esm/utils/safeHref.js.map +1 -1
  223. package/dist/nextcloud-vue.cjs.js +809 -383
  224. package/dist/nextcloud-vue.cjs.js.map +2 -2
  225. package/dist/nextcloud-vue.css +71 -7
  226. package/l10n/en.json +1 -0
  227. package/l10n/nl.json +1 -0
  228. package/package.json +1 -1
  229. package/src/components/CnActionsBar/CnActionsBar.vue +14 -5
  230. package/src/components/CnActionsMenu/CnActionsMenu.vue +0 -3
  231. package/src/components/CnAdminSettingsShell/CnAdminSettingsShell.vue +1 -2
  232. package/src/components/CnBoardView/CnBoardView.vue +13 -5
  233. package/src/components/CnCardGrid/CnCardGrid.vue +9 -1
  234. package/src/components/CnContextMenu/CnContextMenu.vue +6 -1
  235. package/src/components/CnDataTable/CnDataTable.md +1 -1
  236. package/src/components/CnDataTable/CnDataTable.vue +131 -17
  237. package/src/components/CnDateAxisView/CnDateAxisView.vue +15 -6
  238. package/src/components/CnDetailPage/CnDetailPage.md +1 -1
  239. package/src/components/CnDetailPage/CnDetailPage.vue +29 -1
  240. package/src/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue +1 -2
  241. package/src/components/CnFilesBrowser/CnFilesBrowser.vue +5 -3
  242. package/src/components/CnFilesWidget/CnFilesWidget.vue +4 -21
  243. package/src/components/CnFlowRunsWidget/CnFlowRunsWidget.vue +8 -21
  244. package/src/components/CnFlowsPage/CnFlowsPage.vue +2 -1
  245. package/src/components/CnIndexPage/CnIndexPage.vue +75 -14
  246. package/src/components/CnIndexPage/manifestActionDispatch.js +31 -11
  247. package/src/components/CnIntegrationWidget/CnIntegrationWidgetEmpty.vue +1 -2
  248. package/src/components/CnInteractionFormWidget/CnInteractionFormWidget.vue +0 -1
  249. package/src/components/CnLinkButtonWidget/CnLinkButtonWidget.vue +2 -17
  250. package/src/components/CnLogsPage/CnLogsPage.vue +37 -12
  251. package/src/components/CnObjectCard/CnObjectCard.vue +24 -6
  252. package/src/components/CnObjectList/CnObjectList.vue +19 -3
  253. package/src/components/CnObjectListWidget/CnObjectListWidget.vue +47 -15
  254. package/src/components/CnObjectRow/CnObjectRow.vue +25 -7
  255. package/src/components/CnObjectSidebar/CnFilesTab.vue +0 -1
  256. package/src/components/CnPageRenderer/CnPageRenderer.vue +2 -0
  257. package/src/components/CnRowActions/CnRowActions.vue +7 -2
  258. package/src/components/CnSpendAnalyticsWidget/CnSpendAnalyticsWidget.vue +5 -3
  259. package/src/components/CnTabs/CnTabs.vue +7 -3
  260. package/src/components/CnTasksWidget/CnTasksWidget.vue +4 -1
  261. package/src/css/table.css +26 -0
  262. package/src/integrations/builtin/analytics/CnAnalyticsCard.vue +3 -2
  263. package/src/integrations/builtin/analytics/CnAnalyticsTab.vue +5 -6
  264. package/src/integrations/builtin/bookmarks/CnBookmarksCard.vue +12 -1
  265. package/src/integrations/builtin/bookmarks/CnBookmarksTab.vue +23 -6
  266. package/src/integrations/builtin/bookmarks/__tests__/CnBookmarksTab.spec.js +7 -2
  267. package/src/integrations/builtin/collectives/CnCollectivesCard.vue +3 -2
  268. package/src/integrations/builtin/collectives/CnCollectivesTab.vue +5 -6
  269. package/src/integrations/builtin/cospend/CnCospendCard.vue +2 -2
  270. package/src/integrations/builtin/cospend/CnCospendTab.vue +3 -4
  271. package/src/integrations/builtin/deck/CnDeckCard.vue +2 -2
  272. package/src/integrations/builtin/deck/CnDeckTab.vue +3 -4
  273. package/src/integrations/builtin/email/CnEmailCard.vue +6 -2
  274. package/src/integrations/builtin/email/CnEmailTab.vue +7 -4
  275. package/src/integrations/builtin/flow/CnFlowCard.vue +2 -2
  276. package/src/integrations/builtin/flow/CnFlowTab.vue +3 -4
  277. package/src/integrations/builtin/forms/CnFormsCard.vue +3 -2
  278. package/src/integrations/builtin/forms/CnFormsTab.vue +5 -6
  279. package/src/integrations/builtin/maps/CnMapsCard.vue +3 -2
  280. package/src/integrations/builtin/maps/CnMapsTab.vue +5 -6
  281. package/src/integrations/builtin/maps/__tests__/CnMapsTab.spec.js +9 -0
  282. package/src/integrations/builtin/openproject/CnOpenprojectCard.vue +3 -2
  283. package/src/integrations/builtin/openproject/CnOpenprojectTab.vue +5 -6
  284. package/src/integrations/builtin/photos/CnPhotosCard.vue +2 -2
  285. package/src/integrations/builtin/photos/CnPhotosTab.vue +3 -4
  286. package/src/integrations/builtin/polls/CnPollsCard.vue +2 -2
  287. package/src/integrations/builtin/polls/CnPollsTab.vue +3 -4
  288. package/src/integrations/builtin/shares/CnSharesTab.vue +3 -6
  289. package/src/integrations/builtin/talk/CnTalkCard.vue +3 -2
  290. package/src/integrations/builtin/talk/CnTalkTab.vue +5 -6
  291. package/src/integrations/builtin/time-tracker/CnTimeTrackerCard.vue +2 -2
  292. package/src/integrations/builtin/time-tracker/CnTimeTrackerTab.vue +3 -4
  293. package/src/integrations/builtin/xwiki/CnXwikiCard.vue +4 -2
  294. package/src/integrations/builtin/xwiki/CnXwikiTab.vue +7 -7
  295. package/src/integrations/builtin/xwiki/__tests__/CnXwikiTab.spec.js +9 -0
  296. package/src/utils/actionLink.js +5 -0
  297. package/src/utils/actionsDispatcher.js +15 -0
  298. package/src/utils/linkNavigation.js +6 -3
  299. package/src/utils/rowAuxClick.js +29 -4
  300. package/src/utils/safeHref.js +18 -3
@@ -63,8 +63,7 @@
63
63
  <NcButton
64
64
  variant="primary"
65
65
  :href="pollsAppUrl"
66
- target="_blank"
67
- rel="noopener noreferrer">
66
+ target="_blank">
68
67
  <template #icon>
69
68
  <Poll :size="20" />
70
69
  </template>
@@ -179,7 +178,7 @@ import Poll from 'vue-material-design-icons/Poll.vue'
179
178
  import CnPollCreate from '../../../components/CnPollCreate/CnPollCreate.vue'
180
179
  import CnPollPicker from '../../../components/CnPollPicker/CnPollPicker.vue'
181
180
  import CnStatusBadge from '../../../components/CnStatusBadge/CnStatusBadge.vue'
182
- import { buildHeaders, prefixUrl } from '../../../utils/index.js'
181
+ import { buildHeaders, prefixUrl, safeHref } from '../../../utils/index.js'
183
182
  import { stripMarker } from '../../utils/marker.js'
184
183
 
185
184
  /**
@@ -365,7 +364,7 @@ export default {
365
364
  },
366
365
 
367
366
  pollUrl(poll) {
368
- if (poll.url) {
367
+ if (poll.url && safeHref(poll.url) !== '#') {
369
368
  return poll.url
370
369
  }
371
370
  const id = this.pollKey(poll)
@@ -50,8 +50,7 @@
50
50
  <NcButton
51
51
  variant="primary"
52
52
  :href="filesAppUrl"
53
- target="_blank"
54
- rel="noopener noreferrer">
53
+ target="_blank">
55
54
  <template #icon>
56
55
  <FolderOutline :size="20" />
57
56
  </template>
@@ -125,8 +124,7 @@
125
124
  <NcActionLink
126
125
  :closeAfterClick="true"
127
126
  :href="filesAppUrl"
128
- target="_blank"
129
- rel="noopener noreferrer">
127
+ target="_blank">
130
128
  <template #icon>
131
129
  <FolderOutline :size="20" />
132
130
  </template>
@@ -140,8 +138,7 @@
140
138
  <NcButton
141
139
  variant="tertiary"
142
140
  :href="filesAppUrl"
143
- target="_blank"
144
- rel="noopener noreferrer">
141
+ target="_blank">
145
142
  <template #icon>
146
143
  <FolderOutline :size="18" />
147
144
  </template>
@@ -111,7 +111,7 @@ import { translatePlural as n, translate as t } from '@nextcloud/l10n'
111
111
  import { NcAvatar, NcCounterBubble, NcLoadingIcon } from '@nextcloud/vue'
112
112
  import ChatOutline from 'vue-material-design-icons/ChatOutline.vue'
113
113
  import CnDetailCard from '../../../components/CnDetailCard/CnDetailCard.vue'
114
- import { buildHeaders, prefixUrl } from '../../../utils/index.js'
114
+ import { buildHeaders, prefixUrl, safeHref } from '../../../utils/index.js'
115
115
  import { stripMarker } from '../../utils/marker.js'
116
116
 
117
117
  const VALID_SURFACES = ['user-dashboard', 'app-dashboard', 'detail-page', 'single-entity']
@@ -252,8 +252,9 @@ export default {
252
252
  return stripMarker(raw) || String(this.roomKey(room))
253
253
  },
254
254
 
255
+ // A provider URL is used only when it is a safe scheme; else the app link.
255
256
  roomUrl(room) {
256
- if (room.url) {
257
+ if (room.url && safeHref(room.url) !== '#') {
257
258
  return room.url
258
259
  }
259
260
  const token = room.token ?? room.id ?? ''
@@ -60,8 +60,7 @@
60
60
  <NcButton
61
61
  variant="primary"
62
62
  :href="talkAppUrl"
63
- target="_blank"
64
- rel="noopener noreferrer">
63
+ target="_blank">
65
64
  <template #icon>
66
65
  <ChatOutline :size="20" />
67
66
  </template>
@@ -107,8 +106,7 @@
107
106
  <NcActionLink
108
107
  :closeAfterClick="true"
109
108
  :href="roomUrl(room)"
110
- target="_blank"
111
- rel="noopener noreferrer">
109
+ target="_blank">
112
110
  <template #icon>
113
111
  <OpenInNew :size="20" />
114
112
  </template>
@@ -149,7 +147,7 @@ import OpenInNew from 'vue-material-design-icons/OpenInNew.vue'
149
147
  import Plus from 'vue-material-design-icons/Plus.vue'
150
148
  import CnTalkRoomCreate from '../../../components/CnTalkRoomCreate/CnTalkRoomCreate.vue'
151
149
  import CnTalkRoomPicker from '../../../components/CnTalkRoomPicker/CnTalkRoomPicker.vue'
152
- import { buildHeaders, prefixUrl } from '../../../utils/index.js'
150
+ import { buildHeaders, prefixUrl, safeHref } from '../../../utils/index.js'
153
151
  import { stripMarker } from '../../utils/marker.js'
154
152
 
155
153
  /**
@@ -247,8 +245,9 @@ export default {
247
245
  return stripMarker(raw) || String(this.roomKey(room))
248
246
  },
249
247
 
248
+ // A provider URL is used only when it is a safe scheme; else the app link.
250
249
  roomUrl(room) {
251
- if (room.url) {
250
+ if (room.url && safeHref(room.url) !== '#') {
252
251
  return room.url
253
252
  }
254
253
  const token = room.roomToken ?? room.token ?? room.id ?? ''
@@ -119,7 +119,7 @@ import { NcLoadingIcon } from '@nextcloud/vue'
119
119
  import Clock from 'vue-material-design-icons/Clock.vue'
120
120
  import Timer from 'vue-material-design-icons/Timer.vue'
121
121
  import CnDetailCard from '../../../components/CnDetailCard/CnDetailCard.vue'
122
- import { buildHeaders, prefixUrl } from '../../../utils/index.js'
122
+ import { buildHeaders, prefixUrl, safeHref } from '../../../utils/index.js'
123
123
 
124
124
  const VALID_SURFACES = ['user-dashboard', 'app-dashboard', 'detail-page', 'single-entity']
125
125
 
@@ -307,7 +307,7 @@ export default {
307
307
  },
308
308
 
309
309
  rowUrl(row) {
310
- if (row.url) {
310
+ if (row.url && safeHref(row.url) !== '#') {
311
311
  return row.url
312
312
  }
313
313
  const kind = this.rowKind(row)
@@ -81,8 +81,7 @@
81
81
  <NcButton
82
82
  variant="primary"
83
83
  :href="timeTrackerAppUrl"
84
- target="_blank"
85
- rel="noopener noreferrer">
84
+ target="_blank">
86
85
  <template #icon>
87
86
  <Clock :size="20" />
88
87
  </template>
@@ -185,7 +184,7 @@ import Plus from 'vue-material-design-icons/Plus.vue'
185
184
  import Timer from 'vue-material-design-icons/Timer.vue'
186
185
  import CnTimeTrackerCreate from '../../../components/CnTimeTrackerCreate/CnTimeTrackerCreate.vue'
187
186
  import CnTimeTrackerPicker from '../../../components/CnTimeTrackerPicker/CnTimeTrackerPicker.vue'
188
- import { buildHeaders, prefixUrl } from '../../../utils/index.js'
187
+ import { buildHeaders, prefixUrl, safeHref } from '../../../utils/index.js'
189
188
 
190
189
  /**
191
190
  * CnTimeTrackerTab — bespoke sidebar tab for the `time-tracker`
@@ -361,7 +360,7 @@ export default {
361
360
  },
362
361
 
363
362
  rowUrl(row) {
364
- if (row.url) {
363
+ if (row.url && safeHref(row.url) !== '#') {
365
364
  return row.url
366
365
  }
367
366
  const kind = this.rowKind(row)
@@ -138,7 +138,7 @@ import { translatePlural as n, translate as t } from '@nextcloud/l10n'
138
138
  import { NcLoadingIcon } from '@nextcloud/vue'
139
139
  import FileDocumentMultiple from 'vue-material-design-icons/FileDocumentMultiple.vue'
140
140
  import CnDetailCard from '../../../components/CnDetailCard/CnDetailCard.vue'
141
- import { buildHeaders, prefixUrl } from '../../../utils/index.js'
141
+ import { buildHeaders, prefixUrl, safeHref } from '../../../utils/index.js'
142
142
 
143
143
  const VALID_SURFACES = ['user-dashboard', 'app-dashboard', 'detail-page', 'single-entity']
144
144
  const PREVIEW_MAX_CHARS = 500
@@ -303,8 +303,10 @@ export default {
303
303
  return String(page.title ?? page.page ?? page.reference ?? this.pageKey(page))
304
304
  },
305
305
 
306
+ // An unsafe scheme is treated like a missing URL.
306
307
  pageUrl(page) {
307
- return page.url ?? ''
308
+ const url = page.url ?? ''
309
+ return url && safeHref(url) !== '#' ? url : ''
308
310
  },
309
311
 
310
312
  breadcrumbLabel(page) {
@@ -64,7 +64,6 @@
64
64
  class="cn-xwiki-tab__banner-cta"
65
65
  :href="banner.ctaHref || undefined"
66
66
  :target="banner.ctaHref ? '_blank' : undefined"
67
- :rel="banner.ctaHref ? 'noopener noreferrer' : undefined"
68
67
  @click="banner.ctaHandler">
69
68
  {{ banner.ctaLabel }}
70
69
  </NcButton>
@@ -132,8 +131,7 @@
132
131
  class="cn-xwiki-tab__open"
133
132
  :closeAfterClick="true"
134
133
  :href="pageHref(page)"
135
- target="_blank"
136
- rel="noopener noreferrer">
134
+ target="_blank">
137
135
  <template #icon>
138
136
  <OpenInNew :size="20" />
139
137
  </template>
@@ -181,7 +179,7 @@ import OpenInNew from 'vue-material-design-icons/OpenInNew.vue'
181
179
  import Plus from 'vue-material-design-icons/Plus.vue'
182
180
  import CnXwikiPageCreate from '../../../components/CnXwikiPageCreate/CnXwikiPageCreate.vue'
183
181
  import CnXwikiPagePicker from '../../../components/CnXwikiPagePicker/CnXwikiPagePicker.vue'
184
- import { buildHeaders, prefixUrl } from '../../../utils/index.js'
182
+ import { buildHeaders, prefixUrl, safeHref } from '../../../utils/index.js'
185
183
 
186
184
  const EXCERPT_MAX_CHARS = 140
187
185
 
@@ -386,15 +384,17 @@ export default {
386
384
 
387
385
  /**
388
386
  * External XWiki deep-link for the page. Empty string when the
389
- * provider didn't surface a `url`, so the row renders as a plain
390
- * (non-anchored) NcListItem rather than a dead link.
387
+ * provider didn't surface a `url`, or surfaced an unsafe scheme, so
388
+ * the row renders as a plain (non-anchored) NcListItem rather than a
389
+ * dead link.
391
390
  *
392
391
  * @param {object} page Provider row.
393
392
  *
394
393
  * @return {string} Absolute XWiki URL or ''.
395
394
  */
396
395
  pageHref(page) {
397
- return String(page.url ?? '')
396
+ const url = String(page.url ?? '')
397
+ return url && safeHref(url) !== '#' ? url : ''
398
398
  },
399
399
 
400
400
  breadcrumbLabel(page) {
@@ -55,6 +55,15 @@ describe('CnXwikiTab', () => {
55
55
  delete global.fetch
56
56
  })
57
57
 
58
+ it('gives a page with an unsafe URL no link', async () => {
59
+ global.fetch = jest.fn().mockResolvedValue({ ok: true, status: 200, json: () => Promise.resolve({ results: [] }) })
60
+ const wrapper = mount(CnXwikiTab, { propsData: { ...DEFAULT_PROPS } })
61
+ await flushPromises()
62
+ expect(wrapper.vm.pageHref({ url: 'javascript:alert(1)' })).toBe('')
63
+ expect(wrapper.vm.pageHref({ url: 'https://wiki.example/p' })).toBe('https://wiki.example/p')
64
+ wrapper.unmount()
65
+ })
66
+
58
67
  it('renders the empty state when no pages are linked', async () => {
59
68
  global.fetch = jest.fn().mockResolvedValueOnce({ ok: true, status: 200, json: () => Promise.resolve({ results: [] }) })
60
69
  const wrapper = mount(CnXwikiTab, { propsData: { ...DEFAULT_PROPS } })
@@ -62,6 +62,11 @@ export function resolveItemActionLink(action, item, router) {
62
62
  * on an in-app link that opens in place goes through the router; a URL, a
63
63
  * new-tab target or a modified click is left to the browser.
64
64
  *
65
+ * Hand-rolled rather than `NcActionRouter` / `NcButton :to`: the menus and
66
+ * bars using it must still emit their own events on click, and without an
67
+ * installed router `NcButton :to` falls back to a button that goes nowhere,
68
+ * while an `<a href>` still navigates.
69
+ *
65
70
  * @param {MouseEvent} event The click event.
66
71
  * @param {{to: (string|object|null), target: (string|undefined)}} link The resolved link.
67
72
  * @param {object} [router] The app's router.
@@ -341,6 +341,21 @@ export function interpolateActionTarget(target, ctx) {
341
341
  return interpolateActionString(target || '', ctx || {})
342
342
  }
343
343
 
344
+ // The tokens interpolateActionString resolves; a bare `@` or `{` is not one.
345
+ const ACTION_TARGET_TOKEN = /@(?:objectId|object\.|page\.|workspace\.|config\.|range\.)|\{objectId\}/
346
+
347
+ /**
348
+ * Whether an action target carries a token that interpolation would resolve,
349
+ * so it can only be known once the page context is. A `mailto:` address or a
350
+ * URL with an `@handle` in it has none.
351
+ *
352
+ * @param {string} target The action's raw `target`.
353
+ * @return {boolean}
354
+ */
355
+ export function hasActionTargetTokens(target) {
356
+ return typeof target === 'string' && ACTION_TARGET_TOKEN.test(target)
357
+ }
358
+
344
359
  /**
345
360
  * The link an action renders as when its only job is to navigate to a target
346
361
  * known at render time: `navigate` (token-interpolated like the dispatcher)
@@ -69,8 +69,11 @@ export function resolveHref(target, router) {
69
69
  * click is routed through the router; a modified click, or one a handler
70
70
  * already prevented, is left to the browser.
71
71
  *
72
+ * Unlike `resolveHref`, a string target is a router path, so pair the two
73
+ * with a location object (or `{ path }`) for the href and click to agree.
74
+ *
72
75
  * @param {MouseEvent} event The click event.
73
- * @param {string|object} target The router location the link points at.
76
+ * @param {string|object} target The router location the link points at; a string is a router path.
74
77
  * @param {object} [router] The app's router.
75
78
  * @return {boolean} True when the app navigated.
76
79
  */
@@ -96,8 +99,8 @@ export function followLinkClick(event, target, router) {
96
99
  *
97
100
  * A click that came from a control inside the surface (a button, a link, a
98
101
  * checkbox) is left to that control. Opening a new tab marks the event with
99
- * `preventDefault()`, and an event already marked that way is skipped, so
100
- * two listeners on the same click never open two tabs.
102
+ * `markNewTabHandled`, and an event already marked is skipped, so two
103
+ * listeners on the same click never open two tabs.
101
104
  *
102
105
  * @param {MouseEvent|KeyboardEvent|null|undefined} event The click event.
103
106
  * @param {string|object} target A URL, or a vue-router location.
@@ -9,13 +9,17 @@
9
9
  * also fire `auxclick` for the right button, so a row that listens to it must
10
10
  * filter both out itself.
11
11
  *
12
- * Whoever opens a row in a new tab calls `preventDefault()` on the event, so a
13
- * second listener for the same click (a host behind a library default) can
12
+ * Whoever opens a row in a new tab marks the event with `markNewTabHandled`, so
13
+ * a second listener for the same click (a host behind a library default) can
14
14
  * check `isNewTabHandled` and not open a second tab.
15
15
  *
16
16
  * @module utils/rowAuxClick
17
17
  */
18
18
 
19
+ // Events that opened a new tab. Not `defaultPrevented`, which any unrelated
20
+ // handler can set on a modified or middle click.
21
+ const newTabHandled = new WeakSet()
22
+
19
23
  const NESTED_CONTROL = 'a, button, input, select, textarea, label, summary, [role="button"], [role="checkbox"], [role="link"], [contenteditable="true"]'
20
24
 
21
25
  /**
@@ -49,6 +53,21 @@ export function isRowMiddleClick(event) {
49
53
  return Boolean(event && event.button === 1 && !isFromNestedControl(event))
50
54
  }
51
55
 
56
+ /**
57
+ * Cancel a middle-button press on a clickable row or card body, so the
58
+ * browser does not start autoscroll (Chrome/Edge on Windows) instead of the
59
+ * middle click opening the row. A press on a nested control, a link
60
+ * included, keeps its default.
61
+ *
62
+ * @param {MouseEvent} event The mousedown event.
63
+ * @return {void}
64
+ */
65
+ export function preventMiddleClickAutoscroll(event) {
66
+ if (event && event.button === 1 && !isFromNestedControl(event) && typeof event.preventDefault === 'function') {
67
+ event.preventDefault()
68
+ }
69
+ }
70
+
52
71
  /**
53
72
  * Whether `openRowTarget` would open this click in a new tab.
54
73
  *
@@ -66,18 +85,24 @@ export function isNewTabClick(event) {
66
85
  * @return {boolean}
67
86
  */
68
87
  export function isNewTabHandled(event) {
69
- return Boolean(isNewTabClick(event) && event.defaultPrevented)
88
+ return Boolean(isNewTabClick(event) && newTabHandled.has(event))
70
89
  }
71
90
 
72
91
  /**
73
92
  * Mark a new-tab click as handled after opening it, see `isNewTabHandled`.
93
+ * Also prevents the event's default, so the browser does not act on the
94
+ * click as well.
74
95
  *
75
96
  * @param {MouseEvent|KeyboardEvent|null|undefined} event The click event.
76
97
  * @param {boolean} opened Whether the target was opened.
77
98
  * @return {void}
78
99
  */
79
100
  export function markNewTabHandled(event, opened) {
80
- if (opened && isNewTabClick(event) && typeof event.preventDefault === 'function') {
101
+ if (!opened || !isNewTabClick(event)) {
102
+ return
103
+ }
104
+ newTabHandled.add(event)
105
+ if (typeof event.preventDefault === 'function') {
81
106
  event.preventDefault()
82
107
  }
83
108
  }
@@ -18,6 +18,20 @@
18
18
  * @module utils/safeHref
19
19
  */
20
20
 
21
+ /**
22
+ * Whether a URL is protocol-relative the way a browser reads it: leading
23
+ * control characters and spaces are dropped, tabs and newlines are ignored
24
+ * anywhere, and a backslash counts as `/`.
25
+ *
26
+ * @param {string} url The URL.
27
+ * @return {boolean}
28
+ */
29
+ function isProtocolRelative(url) {
30
+ // eslint-disable-next-line no-control-regex
31
+ const normalized = url.replace(/[\t\n\r]/g, '').replace(/^[\u0000-\u0020]+/, '')
32
+ return /^[/\\]{2}/.test(normalized)
33
+ }
34
+
21
35
  /**
22
36
  * Validate a URL and return it only if the scheme is safe for `:href`.
23
37
  *
@@ -30,7 +44,8 @@
30
44
  * - `javascript:...`
31
45
  * - `data:...`
32
46
  * - `vbscript:...`
33
- * - Protocol-relative `//attacker.com/...` (ambiguous origin)
47
+ * - Protocol-relative `//attacker.com/...` (ambiguous origin), also as
48
+ * `/\attacker.com` or with leading whitespace
34
49
  * - Any other unrecognised scheme
35
50
  * - `null`, `undefined`, empty string
36
51
  *
@@ -52,7 +67,7 @@ export function safeHref(url) {
52
67
  }
53
68
  // Reject protocol-relative URLs — `//attacker.com/x` looks like a relative
54
69
  // path but resolves to an arbitrary origin, bypassing same-origin intent.
55
- if (url.startsWith('//')) {
70
+ if (isProtocolRelative(url)) {
56
71
  return '#'
57
72
  }
58
73
  // Allow root-relative paths (same-origin navigation)
@@ -97,7 +112,7 @@ export function safeImageSrc(url) {
97
112
  return url
98
113
  }
99
114
  // Reject protocol-relative
100
- if (url.startsWith('//')) {
115
+ if (isProtocolRelative(url)) {
101
116
  return ''
102
117
  }
103
118
  try {