@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
@@ -1 +1 @@
1
- {"version":3,"file":"CnAnalyticsCard.vue3.js","sources":["../../../../../src/integrations/builtin/analytics/CnAnalyticsCard.vue?vue&type=template&id=4000c99f&lang.js"],"sourcesContent":["<!--\n CnAnalyticsCard — bespoke surface-aware widget for the `analytics`\n integration.\n\n Replaces the generic CnIntegrationCard for the `analytics` leaf.\n Branches on `surface` per ADR-019 AD-19:\n - user-dashboard / app-dashboard : count headline \"N reports\" + the\n most-recently-modified report's name + type badge.\n - detail-page : a compact list of linked reports\n (type icon + name + type badge, up to COMPACT_LIMIT) with\n \"view all\" trail-off.\n - single-entity : a chip with type icon + name for\n reference-property auto-rendering.\n\n Pulls rows from the same OR pluggable-integration sub-resource as\n CnAnalyticsTab; for `single-entity` the optional `value` prop\n addresses a single report by id.\n\n This is the widget LaunchPad adopts to consume the analytics leaf. v1\n deliberately does NOT fetch chart data inline per row — that would be\n N HTTP calls per dashboard. The type-coded icon + badge surface the\n report kind cheaply. Phase D / follow-up can wire one inline chart\n via the existing analytics_facts endpoint if the perf budget allows.\n\n See ADR-019 (registry mechanism), AD-19 (surface fallback), ADR-022\n (consumption principle). Refs openregister#1321.\n-->\n<template>\n\t<CnDetailCard :title=\"cardTitle\" :icon=\"cardIcon\" :collapsible=\"collapsible\">\n\t\t<NcLoadingIcon v-if=\"loading\" />\n\n\t\t<!-- single-entity surface: chip -->\n\t\t<template v-else-if=\"surface === 'single-entity'\">\n\t\t\t<span v-if=\"entity\" class=\"cn-analytics-card__chip\" :title=\"reportTitle(entity)\">\n\t\t\t\t<component :is=\"reportIcon(entity)\" :size=\"14\" />\n\t\t\t\t<a\n\t\t\t\t\t:href=\"reportUrl(entity)\"\n\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\trel=\"noopener noreferrer\">{{ reportTitle(entity) }}</a>\n\t\t\t</span>\n\t\t\t<span v-else class=\"cn-analytics-card__empty\">{{ emptyLabel }}</span>\n\t\t</template>\n\n\t\t<!-- dashboard surfaces: headline + most-recent -->\n\t\t<template v-else-if=\"surface === 'user-dashboard' || surface === 'app-dashboard'\">\n\t\t\t<div v-if=\"degraded\" class=\"cn-analytics-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"reports.length === 0\" class=\"cn-analytics-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<div v-else class=\"cn-analytics-card__headline\">\n\t\t\t\t<div class=\"cn-analytics-card__headline-line\">\n\t\t\t\t\t<strong>{{ reportCountLabel }}</strong>\n\t\t\t\t</div>\n\t\t\t\t<div v-if=\"mostRecent\" class=\"cn-analytics-card__headline-recent\">\n\t\t\t\t\t<component :is=\"reportIcon(mostRecent)\" :size=\"16\" />\n\t\t\t\t\t<a\n\t\t\t\t\t\t:href=\"reportUrl(mostRecent)\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener noreferrer\"\n\t\t\t\t\t\tclass=\"cn-analytics-card__headline-name\">\n\t\t\t\t\t\t{{ reportTitle(mostRecent) }}\n\t\t\t\t\t</a>\n\t\t\t\t\t<span class=\"cn-analytics-card__badge\" :class=\"badgeClass(mostRecent)\">\n\t\t\t\t\t\t{{ reportTypeLabel(mostRecent) }}\n\t\t\t\t\t</span>\n\t\t\t\t</div>\n\t\t\t\t<div v-if=\"reports.length > 1\" class=\"cn-analytics-card__view-all\">\n\t\t\t\t\t<a\n\t\t\t\t\t\t:href=\"analyticsAppUrl\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener noreferrer\">{{ viewAllLabel }}</a>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</template>\n\n\t\t<!-- detail-page surface: compact list -->\n\t\t<template v-else>\n\t\t\t<div v-if=\"degraded\" class=\"cn-analytics-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"reports.length === 0\" class=\"cn-analytics-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<ul v-else class=\"cn-analytics-card__list\">\n\t\t\t\t<li\n\t\t\t\t\tv-for=\"report in displayedReports\"\n\t\t\t\t\t:key=\"reportKey(report)\"\n\t\t\t\t\tclass=\"cn-analytics-card__row\">\n\t\t\t\t\t<div class=\"cn-analytics-card__row-icon\">\n\t\t\t\t\t\t<component :is=\"reportIcon(report)\" :size=\"16\" />\n\t\t\t\t\t</div>\n\t\t\t\t\t<div class=\"cn-analytics-card__row-main\">\n\t\t\t\t\t\t<a\n\t\t\t\t\t\t\t:href=\"reportUrl(report)\"\n\t\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t\trel=\"noopener noreferrer\"\n\t\t\t\t\t\t\tclass=\"cn-analytics-card__title\">{{ reportTitle(report) }}</a>\n\t\t\t\t\t\t<span class=\"cn-analytics-card__badge\" :class=\"badgeClass(report)\">\n\t\t\t\t\t\t\t{{ reportTypeLabel(report) }}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</div>\n\t\t\t\t</li>\n\t\t\t</ul>\n\t\t\t<div v-if=\"reports.length > COMPACT_LIMIT\" class=\"cn-analytics-card__view-all\">\n\t\t\t\t<a\n\t\t\t\t\t:href=\"analyticsAppUrl\"\n\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\trel=\"noopener noreferrer\">{{ viewAllLabel }}</a>\n\t\t\t</div>\n\t\t</template>\n\t</CnDetailCard>\n</template>\n\n<script>\nimport { translatePlural as n, translate as t } from '@nextcloud/l10n'\nimport { NcLoadingIcon } from '@nextcloud/vue'\nimport ChartBar from 'vue-material-design-icons/ChartBar.vue'\nimport ChartLine from 'vue-material-design-icons/ChartLine.vue'\nimport ChartPie from 'vue-material-design-icons/ChartPie.vue'\nimport TableIcon from 'vue-material-design-icons/Table.vue'\nimport ViewDashboard from 'vue-material-design-icons/ViewDashboard.vue'\nimport CnDetailCard from '../../../components/CnDetailCard/CnDetailCard.vue'\nimport { buildHeaders, prefixUrl } from '../../../utils/index.js'\n\nconst VALID_SURFACES = ['user-dashboard', 'app-dashboard', 'detail-page', 'single-entity']\nconst COMPACT_LIMIT = 5\n\n/**\n * CnAnalyticsCard — bespoke surface-aware widget for the `analytics`\n * integration.\n *\n * Renders Analytics-aware metadata (report count, type icon, modified\n * date) across all four AD-19 surfaces. See the file-level docblock\n * for surface-by-surface behaviour.\n */\nexport default {\n\tname: 'CnAnalyticsCard',\n\n\tcomponents: { CnDetailCard, NcLoadingIcon, ChartBar, ChartLine, ChartPie, TableIcon, ViewDashboard },\n\n\tprops: {\n\t\t/** Stable integration id (forwarded from the registry — always `'analytics'`). */\n\t\tintegrationId: { type: String, default: 'analytics' },\n\t\t/** OpenRegister register id (slug or uuid). */\n\t\tregister: { type: String, required: true },\n\t\t/** OpenRegister schema id (slug or uuid). */\n\t\tschema: { type: String, required: true },\n\t\t/** Parent object id. */\n\t\tobjectId: { type: String, required: true },\n\t\t/** Rendering surface (AD-19). */\n\t\tsurface: {\n\t\t\ttype: String,\n\t\t\tdefault: 'detail-page',\n\t\t\tvalidator: (s) => VALID_SURFACES.includes(s),\n\t\t},\n\n\t\t/** Optional single-entity reference (report id). */\n\t\tvalue: { type: [String, Number], default: '' },\n\t\t/** Pre-translated card title. */\n\t\ttitle: { type: String, default: () => t('nextcloud-vue', 'Analytics') },\n\t\t/** Optional Material Design Icon component. */\n\t\ticon: { type: Object, default: () => ChartBar },\n\t\t/** Base API URL. */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\t\t/** Whether the card body is collapsible. */\n\t\tcollapsible: { type: Boolean, default: true },\n\t\t/** Pre-translated empty-state label. */\n\t\temptyLabel: { type: String, default: () => t('nextcloud-vue', 'No reports linked yet') },\n\t\t/** Pre-translated 503 unavailable label. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Analytics is currently unavailable.') },\n\t\t/** Pre-translated \"view all\" trail-off link label. */\n\t\tviewAllLabel: { type: String, default: () => t('nextcloud-vue', 'View all in Analytics') },\n\t\t/** URL of the NC Analytics app entry. */\n\t\tanalyticsAppUrl: { type: String, default: '/index.php/apps/analytics' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tCOMPACT_LIMIT,\n\t\t\treports: [],\n\t\t\tentity: null,\n\t\t\tloading: false,\n\t\t\tdegraded: '',\n\t\t}\n\t},\n\n\tcomputed: {\n\t\tcardTitle() {\n\t\t\treturn this.title || this.integrationId\n\t\t},\n\n\t\tcardIcon() {\n\t\t\treturn this.icon\n\t\t},\n\n\t\tdisplayedReports() {\n\t\t\treturn this.reports.slice(0, COMPACT_LIMIT)\n\t\t},\n\n\t\treportCountLabel() {\n\t\t\tconst total = this.reports.length\n\t\t\treturn n('nextcloud-vue', '{count} report', '{count} reports', total, { count: total })\n\t\t},\n\n\t\tmostRecent() {\n\t\t\tif (this.reports.length === 0) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\t// Most recently modified first; falls back to most recently\n\t\t\t// created when no modified timestamp exists.\n\t\t\tconst sorted = [...this.reports].sort((a, b) => {\n\t\t\t\tconst ta = Date.parse(a.modifiedAt || a.data?.modifiedAt || a.createdAt || a.data?.createdAt || 0) || 0\n\t\t\t\tconst tb = Date.parse(b.modifiedAt || b.data?.modifiedAt || b.createdAt || b.data?.createdAt || 0) || 0\n\t\t\t\treturn tb - ta\n\t\t\t})\n\t\t\treturn sorted[0]\n\t\t},\n\t},\n\n\twatch: {\n\t\tobjectId: { immediate: true, handler() {\n\t\t\tthis.fetch()\n\t\t} },\n\n\t\tsurface() {\n\t\t\tthis.fetch()\n\t\t},\n\n\t\tvalue() {\n\t\t\tif (this.surface === 'single-entity') {\n\t\t\t\tthis.fetchSingle()\n\t\t\t}\n\t\t},\n\t},\n\n\tmethods: {\n\t\tbaseUrl() {\n\t\t\treturn `${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/integrations/${this.integrationId}`\n\t\t},\n\n\t\treportKey(report) {\n\t\t\treturn (report && (report.id ?? report.data?.id)) || ''\n\t\t},\n\n\t\treportTitle(report) {\n\t\t\tconst raw = (report && (report.title || report.name || report.data?.name)) || ''\n\t\t\treturn String(raw).replace(/\\s*\\[or:[^\\]]+\\]\\s*/g, ' ').trim() || t('nextcloud-vue', 'Untitled report')\n\t\t},\n\n\t\treportType(report) {\n\t\t\tconst raw = report?.reportType ?? report?.type ?? report?.data?.type\n\t\t\tif (raw === null || raw === undefined || raw === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst num = Number(raw)\n\t\t\treturn Number.isFinite(num) ? num : null\n\t\t},\n\n\t\treportTypeLabel(report) {\n\t\t\tconst type = this.reportType(report)\n\t\t\tswitch (type) {\n\t\t\t\tcase 1: return t('nextcloud-vue', 'Group')\n\t\t\t\tcase 2: return t('nextcloud-vue', 'Remote')\n\t\t\t\tcase 3: return t('nextcloud-vue', 'File')\n\t\t\t\tcase 4: return t('nextcloud-vue', 'Internal')\n\t\t\t\tcase 5: return t('nextcloud-vue', 'Database')\n\t\t\t\tcase 6: return t('nextcloud-vue', 'External')\n\t\t\t\tdefault: return t('nextcloud-vue', 'Report')\n\t\t\t}\n\t\t},\n\n\t\treportIcon(report) {\n\t\t\tconst type = this.reportType(report)\n\t\t\tswitch (type) {\n\t\t\t\tcase 3: return TableIcon\n\t\t\t\tcase 4: return ViewDashboard\n\t\t\t\tcase 2: return ChartLine\n\t\t\t\tcase 5: return ChartPie\n\t\t\t\tdefault: return ChartBar\n\t\t\t}\n\t\t},\n\n\t\tbadgeClass(report) {\n\t\t\tconst type = this.reportType(report)\n\t\t\treturn `cn-analytics-card__badge--type-${type ?? 'unknown'}`\n\t\t},\n\n\t\treportUrl(report) {\n\t\t\tif (report && report.url) {\n\t\t\t\treturn report.url\n\t\t\t}\n\t\t\tconst id = this.reportKey(report)\n\t\t\treturn id ? `/index.php/apps/analytics/#/r/${encodeURIComponent(id)}` : this.analyticsAppUrl\n\t\t},\n\n\t\tfetch() {\n\t\t\tif (this.surface === 'single-entity') {\n\t\t\t\tthis.fetchSingle()\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.fetchList()\n\t\t},\n\n\t\tasync fetchList() {\n\t\t\tif (!this.register || !this.schema || !this.objectId) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\tthis.degraded = ''\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(this.baseUrl()), { headers: buildHeaders() })\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tconst data = await response.json()\n\t\t\t\t\tthis.reports = data.results || data.items || (Array.isArray(data) ? data : []) || []\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.reports = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.reports = []\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnAnalyticsCard] failed to fetch reports', err)\n\t\t\t\tthis.reports = []\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\tasync fetchSingle() {\n\t\t\tif (this.value === '' || this.value === undefined || this.value === null || !this.register || !this.schema || !this.objectId) {\n\t\t\t\tthis.entity = null\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\tthis.degraded = ''\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(`${this.baseUrl()}/${encodeURIComponent(this.value)}`), { headers: buildHeaders() })\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tthis.entity = await response.json()\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.entity = null\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.entity = null\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnAnalyticsCard] failed to fetch single report', err)\n\t\t\t\tthis.entity = null\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-analytics-card__empty {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n\tpadding: 8px 0;\n}\n\n.cn-analytics-card__headline {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 6px;\n}\n\n.cn-analytics-card__headline-line {\n\tfont-size: 1.1em;\n\tcolor: var(--color-main-text);\n}\n\n.cn-analytics-card__headline-recent {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 6px;\n\tfont-size: 0.9em;\n\tcolor: var(--color-text-maxcontrast);\n\toverflow: hidden;\n}\n\n.cn-analytics-card__headline-name {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n\tflex: 1;\n\tmin-width: 0;\n}\n\n.cn-analytics-card__headline-name:hover {\n\ttext-decoration: underline;\n}\n\n.cn-analytics-card__chip {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 6px;\n\tpadding: 4px 10px;\n\tborder-radius: 12px;\n\tbackground: var(--color-background-hover);\n\tfont-size: 0.9em;\n\tmax-width: 100%;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n.cn-analytics-card__chip a {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-analytics-card__chip a:hover {\n\ttext-decoration: underline;\n}\n\n.cn-analytics-card__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n.cn-analytics-card__row {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\tpadding: 6px 0;\n\tborder-bottom: 1px solid var(--color-border);\n}\n\n.cn-analytics-card__row:last-child {\n\tborder-bottom: none;\n}\n\n.cn-analytics-card__row-icon {\n\tflex-shrink: 0;\n\tcolor: var(--color-primary-element);\n\twidth: 18px;\n\theight: 18px;\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n}\n\n.cn-analytics-card__row-main {\n\tflex: 1;\n\tmin-width: 0;\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 6px;\n}\n\n.cn-analytics-card__title {\n\tflex: 1;\n\tmin-width: 0;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n\tfont-size: 13px;\n}\n\na.cn-analytics-card__title:hover {\n\ttext-decoration: underline;\n}\n\n.cn-analytics-card__badge {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tpadding: 1px 6px;\n\tborder-radius: 8px;\n\tbackground: var(--color-background-hover);\n\tcolor: var(--color-main-text);\n\tfont-size: 10px;\n\tfont-weight: 600;\n\tletter-spacing: 0.04em;\n\ttext-transform: uppercase;\n\tflex-shrink: 0;\n}\n\n.cn-analytics-card__badge--type-3 { background: var(--color-primary-element-light, var(--color-primary-light)); color: var(--color-primary-element-text); }\n\n.cn-analytics-card__badge--type-4 { background: var(--color-success, #46ba61); color: var(--color-main-background); }\n\n.cn-analytics-card__badge--type-2 { background: var(--color-warning, #e9a40f); color: var(--color-main-background); }\n\n.cn-analytics-card__view-all {\n\tmargin-top: 6px;\n\tfont-size: 0.8em;\n}\n\n/* stylelint-disable-next-line no-descending-specificity */\n.cn-analytics-card__view-all a {\n\tcolor: var(--color-primary-element);\n\ttext-decoration: none;\n}\n\n.cn-analytics-card__view-all a:hover {\n\ttext-decoration: underline;\n}\n</style>\n"],"names":["_createBlock","_createElementBlock","_Fragment","_openBlock","_resolveDynamicComponent","_toDisplayString","_createElementVNode","_normalizeClass"],"mappings":"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"}
1
+ {"version":3,"file":"CnAnalyticsCard.vue3.js","sources":["../../../../../src/integrations/builtin/analytics/CnAnalyticsCard.vue?vue&type=template&id=4000c99f&lang.js"],"sourcesContent":["<!--\n CnAnalyticsCard — bespoke surface-aware widget for the `analytics`\n integration.\n\n Replaces the generic CnIntegrationCard for the `analytics` leaf.\n Branches on `surface` per ADR-019 AD-19:\n - user-dashboard / app-dashboard : count headline \"N reports\" + the\n most-recently-modified report's name + type badge.\n - detail-page : a compact list of linked reports\n (type icon + name + type badge, up to COMPACT_LIMIT) with\n \"view all\" trail-off.\n - single-entity : a chip with type icon + name for\n reference-property auto-rendering.\n\n Pulls rows from the same OR pluggable-integration sub-resource as\n CnAnalyticsTab; for `single-entity` the optional `value` prop\n addresses a single report by id.\n\n This is the widget LaunchPad adopts to consume the analytics leaf. v1\n deliberately does NOT fetch chart data inline per row — that would be\n N HTTP calls per dashboard. The type-coded icon + badge surface the\n report kind cheaply. Phase D / follow-up can wire one inline chart\n via the existing analytics_facts endpoint if the perf budget allows.\n\n See ADR-019 (registry mechanism), AD-19 (surface fallback), ADR-022\n (consumption principle). Refs openregister#1321.\n-->\n<template>\n\t<CnDetailCard :title=\"cardTitle\" :icon=\"cardIcon\" :collapsible=\"collapsible\">\n\t\t<NcLoadingIcon v-if=\"loading\" />\n\n\t\t<!-- single-entity surface: chip -->\n\t\t<template v-else-if=\"surface === 'single-entity'\">\n\t\t\t<span v-if=\"entity\" class=\"cn-analytics-card__chip\" :title=\"reportTitle(entity)\">\n\t\t\t\t<component :is=\"reportIcon(entity)\" :size=\"14\" />\n\t\t\t\t<a\n\t\t\t\t\t:href=\"reportUrl(entity)\"\n\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\trel=\"noopener noreferrer\">{{ reportTitle(entity) }}</a>\n\t\t\t</span>\n\t\t\t<span v-else class=\"cn-analytics-card__empty\">{{ emptyLabel }}</span>\n\t\t</template>\n\n\t\t<!-- dashboard surfaces: headline + most-recent -->\n\t\t<template v-else-if=\"surface === 'user-dashboard' || surface === 'app-dashboard'\">\n\t\t\t<div v-if=\"degraded\" class=\"cn-analytics-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"reports.length === 0\" class=\"cn-analytics-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<div v-else class=\"cn-analytics-card__headline\">\n\t\t\t\t<div class=\"cn-analytics-card__headline-line\">\n\t\t\t\t\t<strong>{{ reportCountLabel }}</strong>\n\t\t\t\t</div>\n\t\t\t\t<div v-if=\"mostRecent\" class=\"cn-analytics-card__headline-recent\">\n\t\t\t\t\t<component :is=\"reportIcon(mostRecent)\" :size=\"16\" />\n\t\t\t\t\t<a\n\t\t\t\t\t\t:href=\"reportUrl(mostRecent)\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener noreferrer\"\n\t\t\t\t\t\tclass=\"cn-analytics-card__headline-name\">\n\t\t\t\t\t\t{{ reportTitle(mostRecent) }}\n\t\t\t\t\t</a>\n\t\t\t\t\t<span class=\"cn-analytics-card__badge\" :class=\"badgeClass(mostRecent)\">\n\t\t\t\t\t\t{{ reportTypeLabel(mostRecent) }}\n\t\t\t\t\t</span>\n\t\t\t\t</div>\n\t\t\t\t<div v-if=\"reports.length > 1\" class=\"cn-analytics-card__view-all\">\n\t\t\t\t\t<a\n\t\t\t\t\t\t:href=\"analyticsAppUrl\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener noreferrer\">{{ viewAllLabel }}</a>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</template>\n\n\t\t<!-- detail-page surface: compact list -->\n\t\t<template v-else>\n\t\t\t<div v-if=\"degraded\" class=\"cn-analytics-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"reports.length === 0\" class=\"cn-analytics-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<ul v-else class=\"cn-analytics-card__list\">\n\t\t\t\t<li\n\t\t\t\t\tv-for=\"report in displayedReports\"\n\t\t\t\t\t:key=\"reportKey(report)\"\n\t\t\t\t\tclass=\"cn-analytics-card__row\">\n\t\t\t\t\t<div class=\"cn-analytics-card__row-icon\">\n\t\t\t\t\t\t<component :is=\"reportIcon(report)\" :size=\"16\" />\n\t\t\t\t\t</div>\n\t\t\t\t\t<div class=\"cn-analytics-card__row-main\">\n\t\t\t\t\t\t<a\n\t\t\t\t\t\t\t:href=\"reportUrl(report)\"\n\t\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t\trel=\"noopener noreferrer\"\n\t\t\t\t\t\t\tclass=\"cn-analytics-card__title\">{{ reportTitle(report) }}</a>\n\t\t\t\t\t\t<span class=\"cn-analytics-card__badge\" :class=\"badgeClass(report)\">\n\t\t\t\t\t\t\t{{ reportTypeLabel(report) }}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</div>\n\t\t\t\t</li>\n\t\t\t</ul>\n\t\t\t<div v-if=\"reports.length > COMPACT_LIMIT\" class=\"cn-analytics-card__view-all\">\n\t\t\t\t<a\n\t\t\t\t\t:href=\"analyticsAppUrl\"\n\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\trel=\"noopener noreferrer\">{{ viewAllLabel }}</a>\n\t\t\t</div>\n\t\t</template>\n\t</CnDetailCard>\n</template>\n\n<script>\nimport { translatePlural as n, translate as t } from '@nextcloud/l10n'\nimport { NcLoadingIcon } from '@nextcloud/vue'\nimport ChartBar from 'vue-material-design-icons/ChartBar.vue'\nimport ChartLine from 'vue-material-design-icons/ChartLine.vue'\nimport ChartPie from 'vue-material-design-icons/ChartPie.vue'\nimport TableIcon from 'vue-material-design-icons/Table.vue'\nimport ViewDashboard from 'vue-material-design-icons/ViewDashboard.vue'\nimport CnDetailCard from '../../../components/CnDetailCard/CnDetailCard.vue'\nimport { buildHeaders, prefixUrl, safeHref } from '../../../utils/index.js'\n\nconst VALID_SURFACES = ['user-dashboard', 'app-dashboard', 'detail-page', 'single-entity']\nconst COMPACT_LIMIT = 5\n\n/**\n * CnAnalyticsCard — bespoke surface-aware widget for the `analytics`\n * integration.\n *\n * Renders Analytics-aware metadata (report count, type icon, modified\n * date) across all four AD-19 surfaces. See the file-level docblock\n * for surface-by-surface behaviour.\n */\nexport default {\n\tname: 'CnAnalyticsCard',\n\n\tcomponents: { CnDetailCard, NcLoadingIcon, ChartBar, ChartLine, ChartPie, TableIcon, ViewDashboard },\n\n\tprops: {\n\t\t/** Stable integration id (forwarded from the registry — always `'analytics'`). */\n\t\tintegrationId: { type: String, default: 'analytics' },\n\t\t/** OpenRegister register id (slug or uuid). */\n\t\tregister: { type: String, required: true },\n\t\t/** OpenRegister schema id (slug or uuid). */\n\t\tschema: { type: String, required: true },\n\t\t/** Parent object id. */\n\t\tobjectId: { type: String, required: true },\n\t\t/** Rendering surface (AD-19). */\n\t\tsurface: {\n\t\t\ttype: String,\n\t\t\tdefault: 'detail-page',\n\t\t\tvalidator: (s) => VALID_SURFACES.includes(s),\n\t\t},\n\n\t\t/** Optional single-entity reference (report id). */\n\t\tvalue: { type: [String, Number], default: '' },\n\t\t/** Pre-translated card title. */\n\t\ttitle: { type: String, default: () => t('nextcloud-vue', 'Analytics') },\n\t\t/** Optional Material Design Icon component. */\n\t\ticon: { type: Object, default: () => ChartBar },\n\t\t/** Base API URL. */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\t\t/** Whether the card body is collapsible. */\n\t\tcollapsible: { type: Boolean, default: true },\n\t\t/** Pre-translated empty-state label. */\n\t\temptyLabel: { type: String, default: () => t('nextcloud-vue', 'No reports linked yet') },\n\t\t/** Pre-translated 503 unavailable label. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Analytics is currently unavailable.') },\n\t\t/** Pre-translated \"view all\" trail-off link label. */\n\t\tviewAllLabel: { type: String, default: () => t('nextcloud-vue', 'View all in Analytics') },\n\t\t/** URL of the NC Analytics app entry. */\n\t\tanalyticsAppUrl: { type: String, default: '/index.php/apps/analytics' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tCOMPACT_LIMIT,\n\t\t\treports: [],\n\t\t\tentity: null,\n\t\t\tloading: false,\n\t\t\tdegraded: '',\n\t\t}\n\t},\n\n\tcomputed: {\n\t\tcardTitle() {\n\t\t\treturn this.title || this.integrationId\n\t\t},\n\n\t\tcardIcon() {\n\t\t\treturn this.icon\n\t\t},\n\n\t\tdisplayedReports() {\n\t\t\treturn this.reports.slice(0, COMPACT_LIMIT)\n\t\t},\n\n\t\treportCountLabel() {\n\t\t\tconst total = this.reports.length\n\t\t\treturn n('nextcloud-vue', '{count} report', '{count} reports', total, { count: total })\n\t\t},\n\n\t\tmostRecent() {\n\t\t\tif (this.reports.length === 0) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\t// Most recently modified first; falls back to most recently\n\t\t\t// created when no modified timestamp exists.\n\t\t\tconst sorted = [...this.reports].sort((a, b) => {\n\t\t\t\tconst ta = Date.parse(a.modifiedAt || a.data?.modifiedAt || a.createdAt || a.data?.createdAt || 0) || 0\n\t\t\t\tconst tb = Date.parse(b.modifiedAt || b.data?.modifiedAt || b.createdAt || b.data?.createdAt || 0) || 0\n\t\t\t\treturn tb - ta\n\t\t\t})\n\t\t\treturn sorted[0]\n\t\t},\n\t},\n\n\twatch: {\n\t\tobjectId: { immediate: true, handler() {\n\t\t\tthis.fetch()\n\t\t} },\n\n\t\tsurface() {\n\t\t\tthis.fetch()\n\t\t},\n\n\t\tvalue() {\n\t\t\tif (this.surface === 'single-entity') {\n\t\t\t\tthis.fetchSingle()\n\t\t\t}\n\t\t},\n\t},\n\n\tmethods: {\n\t\tbaseUrl() {\n\t\t\treturn `${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/integrations/${this.integrationId}`\n\t\t},\n\n\t\treportKey(report) {\n\t\t\treturn (report && (report.id ?? report.data?.id)) || ''\n\t\t},\n\n\t\treportTitle(report) {\n\t\t\tconst raw = (report && (report.title || report.name || report.data?.name)) || ''\n\t\t\treturn String(raw).replace(/\\s*\\[or:[^\\]]+\\]\\s*/g, ' ').trim() || t('nextcloud-vue', 'Untitled report')\n\t\t},\n\n\t\treportType(report) {\n\t\t\tconst raw = report?.reportType ?? report?.type ?? report?.data?.type\n\t\t\tif (raw === null || raw === undefined || raw === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst num = Number(raw)\n\t\t\treturn Number.isFinite(num) ? num : null\n\t\t},\n\n\t\treportTypeLabel(report) {\n\t\t\tconst type = this.reportType(report)\n\t\t\tswitch (type) {\n\t\t\t\tcase 1: return t('nextcloud-vue', 'Group')\n\t\t\t\tcase 2: return t('nextcloud-vue', 'Remote')\n\t\t\t\tcase 3: return t('nextcloud-vue', 'File')\n\t\t\t\tcase 4: return t('nextcloud-vue', 'Internal')\n\t\t\t\tcase 5: return t('nextcloud-vue', 'Database')\n\t\t\t\tcase 6: return t('nextcloud-vue', 'External')\n\t\t\t\tdefault: return t('nextcloud-vue', 'Report')\n\t\t\t}\n\t\t},\n\n\t\treportIcon(report) {\n\t\t\tconst type = this.reportType(report)\n\t\t\tswitch (type) {\n\t\t\t\tcase 3: return TableIcon\n\t\t\t\tcase 4: return ViewDashboard\n\t\t\t\tcase 2: return ChartLine\n\t\t\t\tcase 5: return ChartPie\n\t\t\t\tdefault: return ChartBar\n\t\t\t}\n\t\t},\n\n\t\tbadgeClass(report) {\n\t\t\tconst type = this.reportType(report)\n\t\t\treturn `cn-analytics-card__badge--type-${type ?? 'unknown'}`\n\t\t},\n\n\t\t// A provider URL is used only when it is a safe scheme; else the app link.\n\t\treportUrl(report) {\n\t\t\tif (report && report.url && safeHref(report.url) !== '#') {\n\t\t\t\treturn report.url\n\t\t\t}\n\t\t\tconst id = this.reportKey(report)\n\t\t\treturn id ? `/index.php/apps/analytics/#/r/${encodeURIComponent(id)}` : this.analyticsAppUrl\n\t\t},\n\n\t\tfetch() {\n\t\t\tif (this.surface === 'single-entity') {\n\t\t\t\tthis.fetchSingle()\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.fetchList()\n\t\t},\n\n\t\tasync fetchList() {\n\t\t\tif (!this.register || !this.schema || !this.objectId) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\tthis.degraded = ''\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(this.baseUrl()), { headers: buildHeaders() })\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tconst data = await response.json()\n\t\t\t\t\tthis.reports = data.results || data.items || (Array.isArray(data) ? data : []) || []\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.reports = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.reports = []\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnAnalyticsCard] failed to fetch reports', err)\n\t\t\t\tthis.reports = []\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\tasync fetchSingle() {\n\t\t\tif (this.value === '' || this.value === undefined || this.value === null || !this.register || !this.schema || !this.objectId) {\n\t\t\t\tthis.entity = null\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\tthis.degraded = ''\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(`${this.baseUrl()}/${encodeURIComponent(this.value)}`), { headers: buildHeaders() })\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tthis.entity = await response.json()\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.entity = null\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.entity = null\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnAnalyticsCard] failed to fetch single report', err)\n\t\t\t\tthis.entity = null\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-analytics-card__empty {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n\tpadding: 8px 0;\n}\n\n.cn-analytics-card__headline {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 6px;\n}\n\n.cn-analytics-card__headline-line {\n\tfont-size: 1.1em;\n\tcolor: var(--color-main-text);\n}\n\n.cn-analytics-card__headline-recent {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 6px;\n\tfont-size: 0.9em;\n\tcolor: var(--color-text-maxcontrast);\n\toverflow: hidden;\n}\n\n.cn-analytics-card__headline-name {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n\tflex: 1;\n\tmin-width: 0;\n}\n\n.cn-analytics-card__headline-name:hover {\n\ttext-decoration: underline;\n}\n\n.cn-analytics-card__chip {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 6px;\n\tpadding: 4px 10px;\n\tborder-radius: 12px;\n\tbackground: var(--color-background-hover);\n\tfont-size: 0.9em;\n\tmax-width: 100%;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n.cn-analytics-card__chip a {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-analytics-card__chip a:hover {\n\ttext-decoration: underline;\n}\n\n.cn-analytics-card__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n.cn-analytics-card__row {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\tpadding: 6px 0;\n\tborder-bottom: 1px solid var(--color-border);\n}\n\n.cn-analytics-card__row:last-child {\n\tborder-bottom: none;\n}\n\n.cn-analytics-card__row-icon {\n\tflex-shrink: 0;\n\tcolor: var(--color-primary-element);\n\twidth: 18px;\n\theight: 18px;\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n}\n\n.cn-analytics-card__row-main {\n\tflex: 1;\n\tmin-width: 0;\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 6px;\n}\n\n.cn-analytics-card__title {\n\tflex: 1;\n\tmin-width: 0;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n\tfont-size: 13px;\n}\n\na.cn-analytics-card__title:hover {\n\ttext-decoration: underline;\n}\n\n.cn-analytics-card__badge {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tpadding: 1px 6px;\n\tborder-radius: 8px;\n\tbackground: var(--color-background-hover);\n\tcolor: var(--color-main-text);\n\tfont-size: 10px;\n\tfont-weight: 600;\n\tletter-spacing: 0.04em;\n\ttext-transform: uppercase;\n\tflex-shrink: 0;\n}\n\n.cn-analytics-card__badge--type-3 { background: var(--color-primary-element-light, var(--color-primary-light)); color: var(--color-primary-element-text); }\n\n.cn-analytics-card__badge--type-4 { background: var(--color-success, #46ba61); color: var(--color-main-background); }\n\n.cn-analytics-card__badge--type-2 { background: var(--color-warning, #e9a40f); color: var(--color-main-background); }\n\n.cn-analytics-card__view-all {\n\tmargin-top: 6px;\n\tfont-size: 0.8em;\n}\n\n/* stylelint-disable-next-line no-descending-specificity */\n.cn-analytics-card__view-all a {\n\tcolor: var(--color-primary-element);\n\ttext-decoration: none;\n}\n\n.cn-analytics-card__view-all a:hover {\n\ttext-decoration: underline;\n}\n</style>\n"],"names":["_createBlock","_createElementBlock","_Fragment","_openBlock","_resolveDynamicComponent","_toDisplayString","_createElementVNode","_normalizeClass"],"mappings":"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"}
@@ -15,6 +15,7 @@ import __sfc_main$2 from '../../../components/CnAnalyticsReportCreate/CnAnalytic
15
15
  import __sfc_main$1 from '../../../components/CnAnalyticsReportPicker/CnAnalyticsReportPicker.vue.js';
16
16
  import __sfc_main from '../../../components/CnStatusBadge/CnStatusBadge.vue.js';
17
17
  import { prefixUrl, buildHeaders } from '../../../utils/headers.js';
18
+ import { safeHref } from '../../../utils/safeHref.js';
18
19
 
19
20
  /**
20
21
  * CnAnalyticsTab — bespoke dataset list for the `analytics` integration.
@@ -244,8 +245,9 @@ var script = {
244
245
  return translate('nextcloud-vue', 'Latest value')
245
246
  },
246
247
 
248
+ // A provider URL is used only when it is a safe scheme; else the app link.
247
249
  reportUrl(report) {
248
- if (report && report.url) {
250
+ if (report && report.url && safeHref(report.url) !== '#') {
249
251
  return report.url
250
252
  }
251
253
  const id = this.reportKey(report);
@@ -1 +1 @@
1
- {"version":3,"file":"CnAnalyticsTab.vue2.js","sources":["../../../../../src/integrations/builtin/analytics/CnAnalyticsTab.vue"],"sourcesContent":["<!--\n CnAnalyticsTab — bespoke sidebar tab for the `analytics` integration\n leaf.\n\n Replaces the generic `CnIntegrationTab` for the `analytics` leaf:\n renders the linked NC Analytics reports / datasets so the tab reads\n like the real Analytics app — each row is an `NcListItem` showing the\n chart-type icon (bar / line / pie / dataset), the bold report name, a\n data-source / type subtitle, an optional headline KPI value, and a\n colour-coded type badge (CnStatusBadge). Clicking a row deep-links\n into the NC Analytics app at `/index.php/apps/analytics/#/r/{id}`\n (`#/r/` is NC Analytics 6.x's report route). Per-row \"Open in\n Analytics\" + \"Unlink\" live in the NcListItem action menu.\n\n Tier-2 surface (this commit):\n - \"Link existing report\" → opens CnAnalyticsReportPicker (modal)\n - \"Create new report\" → opens CnAnalyticsReportCreate (modal)\n - Per-row unlink → DELETE …/analytics/{reportId}\n\n Talks to the OpenRegister Tier-2 analytics-link endpoints\n GET /api/objects/{r}/{s}/{id}/analytics — list\n POST /api/objects/{r}/{s}/{id}/analytics — link existing\n POST /api/objects/{r}/{s}/{id}/analytics/new — create + link\n DELETE /api/objects/{r}/{s}/{id}/analytics/{reportId} — unlink\n GET /api/integrations/analytics/available — picker source\n served by `OCA\\OpenRegister\\Controller\\AnalyticsLinksController`.\n\n The endpoint returns rows shaped:\n { id, reportId, reportTitle, reportType, subheader, value, kpi,\n createdAt, modifiedAt, url, … }\n\n Empty + loading + error + 501/503 unavailable states follow ADR-017\n and AD-23 graceful degradation. All UI strings pass through\n `t('nextcloud-vue', ...)` per ADR-007. Styling uses Nextcloud CSS\n variables only so the nldesign overrides apply transparently\n (ADR-010).\n\n The headline KPI (`value` / `kpi`) is rendered only when the provider\n supplies it — there is no extra HTTP call per row. A type-coded icon\n communicates the chart kind without fetching chart data; inline\n mini-charts remain a follow-up enhancement.\n\n Bespoke-vs-generic rationale: LaunchPad needs to surface \"what kind of\n report is this + its headline number\" without opening it; the generic\n link list strips the chart type, the data source and the KPI.\n-->\n<template>\n\t<div class=\"cn-sidebar-tab cn-analytics-tab\">\n\t\t<!-- 503 unavailable banner (AD-23) -->\n\t\t<div v-if=\"degraded\" class=\"cn-analytics-tab__banner\" role=\"alert\">\n\t\t\t<AlertCircleOutline :size=\"18\" />\n\t\t\t<span>{{ degraded }}</span>\n\t\t</div>\n\n\t\t<div class=\"cn-analytics-tab__actions\">\n\t\t\t<NcButton variant=\"secondary\" @click=\"openPicker\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<LinkVariant :size=\"18\" />\n\t\t\t\t</template>\n\t\t\t\t{{ t('nextcloud-vue', 'Link existing report') }}\n\t\t\t</NcButton>\n\t\t\t<NcButton variant=\"primary\" @click=\"openCreate\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<Plus :size=\"18\" />\n\t\t\t\t</template>\n\t\t\t\t{{ t('nextcloud-vue', 'Create new report') }}\n\t\t\t</NcButton>\n\t\t</div>\n\n\t\t<!-- Loading -->\n\t\t<NcLoadingIcon v-if=\"loading\" />\n\n\t\t<!-- Error -->\n\t\t<div v-else-if=\"error\" class=\"cn-analytics-tab__error\" role=\"alert\">\n\t\t\t{{ error }}\n\t\t</div>\n\n\t\t<!-- Empty -->\n\t\t<div v-else-if=\"reports.length === 0\" class=\"cn-sidebar-tab__empty cn-analytics-tab__empty\">\n\t\t\t<ChartBar :size=\"32\" class=\"cn-analytics-tab__empty-icon\" />\n\t\t\t<p>{{ emptyLabel }}</p>\n\t\t\t<NcButton\n\t\t\t\tvariant=\"primary\"\n\t\t\t\t:href=\"analyticsAppUrl\"\n\t\t\t\ttarget=\"_blank\"\n\t\t\t\trel=\"noopener noreferrer\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<ChartBar :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ openAnalyticsLabel }}\n\t\t\t</NcButton>\n\t\t</div>\n\n\t\t<!-- Report list — NcListItem rows mirror the NC Analytics dataset list -->\n\t\t<ul v-else class=\"cn-analytics-tab__list\">\n\t\t\t<NcListItem\n\t\t\t\tv-for=\"report in reports\"\n\t\t\t\t:key=\"reportKey(report)\"\n\t\t\t\tclass=\"cn-analytics-tab__row\"\n\t\t\t\t:name=\"reportTitle(report)\"\n\t\t\t\t:bold=\"true\"\n\t\t\t\t:href=\"reportUrl(report)\"\n\t\t\t\ttarget=\"_blank\"\n\t\t\t\t:forceDisplayActions=\"true\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<span class=\"cn-analytics-tab__row-icon\">\n\t\t\t\t\t\t<component :is=\"reportIcon(report)\" :size=\"22\" />\n\t\t\t\t\t</span>\n\t\t\t\t</template>\n\t\t\t\t<template #subname>\n\t\t\t\t\t<span class=\"cn-analytics-tab__sub\">\n\t\t\t\t\t\t<CnStatusBadge\n\t\t\t\t\t\t\tclass=\"cn-analytics-tab__badge\"\n\t\t\t\t\t\t\tsize=\"small\"\n\t\t\t\t\t\t\t:variant=\"badgeVariant(report)\"\n\t\t\t\t\t\t\t:label=\"reportTypeLabel(report)\" />\n\t\t\t\t\t\t<span v-if=\"reportSubheader(report)\" class=\"cn-analytics-tab__subheader\">\n\t\t\t\t\t\t\t{{ reportSubheader(report) }}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<span v-if=\"modifiedAt(report)\" class=\"cn-analytics-tab__when\">\n\t\t\t\t\t\t\t· {{ formatWhen(modifiedAt(report)) }}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</span>\n\t\t\t\t</template>\n\t\t\t\t<template v-if=\"reportValue(report)\" #details>\n\t\t\t\t\t<span class=\"cn-analytics-tab__kpi\" :title=\"kpiTitle(report)\">\n\t\t\t\t\t\t{{ reportValue(report) }}\n\t\t\t\t\t</span>\n\t\t\t\t</template>\n\t\t\t\t<template #actions>\n\t\t\t\t\t<NcActionLink\n\t\t\t\t\t\t:closeAfterClick=\"true\"\n\t\t\t\t\t\t:href=\"reportUrl(report)\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener noreferrer\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<OpenInNew :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ t('nextcloud-vue', 'Open in Analytics') }}\n\t\t\t\t\t</NcActionLink>\n\t\t\t\t\t<NcActionButton :closeAfterClick=\"true\" @click=\"unlinkReport(report)\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<LinkOff :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ t('nextcloud-vue', 'Unlink report') }}\n\t\t\t\t\t</NcActionButton>\n\t\t\t\t</template>\n\t\t\t</NcListItem>\n\t\t</ul>\n\n\t\t<CnAnalyticsReportPicker\n\t\t\tv-if=\"pickerOpen\"\n\t\t\t:apiBase=\"apiBase\"\n\t\t\t@close=\"pickerOpen = false\"\n\t\t\t@link=\"onLinkPick\" />\n\n\t\t<CnAnalyticsReportCreate\n\t\t\tv-if=\"createOpen\"\n\t\t\t@close=\"createOpen = false\"\n\t\t\t@create=\"onCreatePick\" />\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcActionButton, NcActionLink, NcButton, NcListItem, NcLoadingIcon } from '@nextcloud/vue'\nimport AlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue'\nimport ChartBar from 'vue-material-design-icons/ChartBar.vue'\nimport ChartLine from 'vue-material-design-icons/ChartLine.vue'\nimport ChartPie from 'vue-material-design-icons/ChartPie.vue'\nimport DatabaseOutline from 'vue-material-design-icons/DatabaseOutline.vue'\nimport LinkOff from 'vue-material-design-icons/LinkOff.vue'\nimport LinkVariant from 'vue-material-design-icons/LinkVariant.vue'\nimport OpenInNew from 'vue-material-design-icons/OpenInNew.vue'\nimport Plus from 'vue-material-design-icons/Plus.vue'\nimport TableIcon from 'vue-material-design-icons/Table.vue'\nimport ViewDashboard from 'vue-material-design-icons/ViewDashboard.vue'\nimport CnAnalyticsReportCreate from '../../../components/CnAnalyticsReportCreate/CnAnalyticsReportCreate.vue'\nimport CnAnalyticsReportPicker from '../../../components/CnAnalyticsReportPicker/CnAnalyticsReportPicker.vue'\nimport CnStatusBadge from '../../../components/CnStatusBadge/CnStatusBadge.vue'\nimport { buildHeaders, prefixUrl } from '../../../utils/index.js'\n\n/**\n * CnAnalyticsTab — bespoke dataset list for the `analytics` integration.\n *\n * Renders linked NC Analytics reports / datasets as NcListItem rows\n * styled to mirror the Analytics app: chart-type icon, bold report\n * name, data-source / type subtitle, type badge and an optional\n * headline KPI value. Clicking a row opens the report in NC Analytics\n * in a new tab. Tier-2: includes link / create modals and per-row\n * unlink via the row action menu. See the file-level docblock for\n * design notes.\n */\nexport default {\n\tname: 'CnAnalyticsTab',\n\n\tcomponents: {\n\t\tNcActionButton,\n\t\tNcActionLink,\n\t\tNcButton,\n\t\tNcListItem,\n\t\tNcLoadingIcon,\n\t\tCnStatusBadge,\n\t\tAlertCircleOutline,\n\t\tChartBar,\n\t\tChartLine,\n\t\tChartPie,\n\t\tDatabaseOutline,\n\t\tLinkOff,\n\t\tLinkVariant,\n\t\tOpenInNew,\n\t\tPlus,\n\t\tTableIcon,\n\t\tViewDashboard,\n\t\tCnAnalyticsReportPicker,\n\t\tCnAnalyticsReportCreate,\n\t},\n\n\tprops: {\n\t\t/* eslint-disable vue/no-unused-properties -- the integration dispatch binds integrationId on every integration component (see CnIntegrationWidgetGrid), so declaring it keeps it out of $attrs */\n\t\t/** Stable integration id (forwarded from the registry — always `'analytics'`). */\n\t\tintegrationId: { type: String, default: 'analytics' },\n\t\t/* eslint-enable vue/no-unused-properties */\n\t\t/** Parent object id. */\n\t\tobjectId: { type: String, required: true },\n\t\t/** OpenRegister register id (slug or uuid). */\n\t\tregister: { type: String, default: '' },\n\t\t/** OpenRegister schema id (slug or uuid). */\n\t\tschema: { type: String, default: '' },\n\t\t/** Base API URL. */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\t\t/** Pre-translated empty-state label. */\n\t\temptyLabel: { type: String, default: () => t('nextcloud-vue', 'No reports linked yet') },\n\t\t/** Pre-translated \"Open Analytics\" CTA label. */\n\t\topenAnalyticsLabel: { type: String, default: () => t('nextcloud-vue', 'Open Analytics') },\n\t\t/** Pre-translated 503 unavailable banner copy. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Analytics is currently unavailable.') },\n\t\t/** URL of the NC Analytics app entry. */\n\t\tanalyticsAppUrl: { type: String, default: '/index.php/apps/analytics' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\treports: [],\n\t\t\tloading: false,\n\t\t\terror: '',\n\t\t\tdegraded: '',\n\t\t\tpickerOpen: false,\n\t\t\tcreateOpen: false,\n\t\t}\n\t},\n\n\twatch: {\n\t\tobjectId: { immediate: true, handler(id) {\n\t\t\tif (id) {\n\t\t\t\tthis.fetchReports()\n\t\t\t}\n\t\t} },\n\n\t\tregister() {\n\t\t\tthis.fetchReports()\n\t\t},\n\n\t\tschema() {\n\t\t\tthis.fetchReports()\n\t\t},\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/**\n\t\t * Base for the Tier-2 analytics endpoints (list/link/new/destroy).\n\t\t *\n\t\t * @return {string} The endpoint URL.\n\t\t */\n\t\tanalyticsEndpoint() {\n\t\t\treturn prefixUrl(`${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/analytics`)\n\t\t},\n\n\t\treportKey(report) {\n\t\t\treturn (report && (report.reportId ?? report.id ?? report.data?.id)) || ''\n\t\t},\n\n\t\t/**\n\t\t * Resolve the displayable report title, stripping the\n\t\t * `[or:{uuid}]` marker (pre-Tier-2 reports embedded the marker\n\t\t * inside `name`).\n\t\t *\n\t\t * @param {object} report Provider row.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\treportTitle(report) {\n\t\t\tconst raw = (report && (report.reportTitle || report.title || report.name || report.data?.name)) || ''\n\t\t\treturn String(raw).replace(/\\s*\\[or:[^\\]]+\\]\\s*/g, ' ').trim() || t('nextcloud-vue', 'Untitled report')\n\t\t},\n\n\t\t/**\n\t\t * Resolve the secondary \"subheader\" line, stripping any leaked\n\t\t * marker defensively.\n\t\t *\n\t\t * @param {object} report Provider row.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\treportSubheader(report) {\n\t\t\tconst raw = report?.subheader ?? report?.dataSource ?? report?.data?.subheader ?? ''\n\t\t\treturn String(raw).replace(/\\s*\\[or:[^\\]]+\\]\\s*/g, ' ').trim()\n\t\t},\n\n\t\treportType(report) {\n\t\t\tconst raw = report?.reportType ?? report?.type ?? report?.data?.type\n\t\t\tif (raw === null || raw === undefined || raw === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst num = Number(raw)\n\t\t\treturn Number.isFinite(num) ? num : null\n\t\t},\n\n\t\t/**\n\t\t * Resolve a translatable label for the report type. Falls back\n\t\t * to \"Report\" when the type code is unknown. Type codes mirror\n\t\t * NC Analytics 6.x (datasource types).\n\t\t *\n\t\t * @param {object} report Provider row.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\treportTypeLabel(report) {\n\t\t\tconst type = this.reportType(report)\n\t\t\tswitch (type) {\n\t\t\t\tcase 0: return t('nextcloud-vue', 'Group')\n\t\t\t\tcase 1: return t('nextcloud-vue', 'File')\n\t\t\t\tcase 2: return t('nextcloud-vue', 'Database')\n\t\t\t\tcase 3: return t('nextcloud-vue', 'Git')\n\t\t\t\tcase 4: return t('nextcloud-vue', 'External')\n\t\t\t\tcase 6: return t('nextcloud-vue', 'JSON')\n\t\t\t\tdefault: return t('nextcloud-vue', 'Report')\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * CnStatusBadge colour variant for the report type. Mirrors NC\n\t\t * Analytics' datasource-type accenting without inventing colours\n\t\t * (uses the shared variant palette).\n\t\t *\n\t\t * @param {object} report Provider row.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tbadgeVariant(report) {\n\t\t\tconst type = this.reportType(report)\n\t\t\tswitch (type) {\n\t\t\t\tcase 1: return 'primary'\n\t\t\t\tcase 2: return 'success'\n\t\t\t\tcase 4: return 'warning'\n\t\t\t\tcase 6: return 'info'\n\t\t\t\tdefault: return 'default'\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * MDI component for the report type. Tries to express the\n\t\t * \"kind\" without fetching chart data.\n\t\t *\n\t\t * @param {object} report Provider row.\n\t\t *\n\t\t * @return {object} Vue component.\n\t\t */\n\t\treportIcon(report) {\n\t\t\tconst type = this.reportType(report)\n\t\t\tswitch (type) {\n\t\t\t\tcase 1: return TableIcon\n\t\t\t\tcase 0: return ViewDashboard\n\t\t\t\tcase 2: return DatabaseOutline\n\t\t\t\tcase 4: return ChartLine\n\t\t\t\tcase 6: return ChartPie\n\t\t\t\tdefault: return ChartBar\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Headline KPI / value for the report, when the provider supplies\n\t\t * one. Numbers are locale-formatted; strings pass through.\n\t\t *\n\t\t * @param {object} report Provider row.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\treportValue(report) {\n\t\t\tconst raw = report?.value ?? report?.kpi ?? report?.data?.value ?? ''\n\t\t\tif (raw === null || raw === undefined || raw === '') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst num = Number(raw)\n\t\t\tif (Number.isFinite(num) && String(raw).trim() !== '') {\n\t\t\t\ttry {\n\t\t\t\t\treturn num.toLocaleString()\n\t\t\t\t} catch {\n\t\t\t\t\treturn String(num)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn String(raw)\n\t\t},\n\n\t\tkpiTitle(_report) {\n\t\t\treturn t('nextcloud-vue', 'Latest value')\n\t\t},\n\n\t\treportUrl(report) {\n\t\t\tif (report && report.url) {\n\t\t\t\treturn report.url\n\t\t\t}\n\t\t\tconst id = this.reportKey(report)\n\t\t\treturn id ? `/index.php/apps/analytics/#/r/${encodeURIComponent(id)}` : this.analyticsAppUrl\n\t\t},\n\n\t\tmodifiedAt(report) {\n\t\t\treturn report?.modifiedAt\n\t\t\t\t|| report?.data?.modifiedAt\n\t\t\t\t|| report?.data?.modified\n\t\t\t\t|| report?.createdAt\n\t\t\t\t|| report?.data?.createdAt\n\t\t\t\t|| ''\n\t\t},\n\n\t\tformatWhen(value) {\n\t\t\tif (!value) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst num = Number(value)\n\t\t\t\tconst d = Number.isFinite(num) && num > 0 && String(value).length <= 12\n\t\t\t\t\t? new Date(num * 1000)\n\t\t\t\t\t: new Date(value)\n\t\t\t\tif (Number.isNaN(d.getTime())) {\n\t\t\t\t\treturn String(value)\n\t\t\t\t}\n\t\t\t\treturn d.toLocaleDateString(undefined, { dateStyle: 'medium' })\n\t\t\t} catch {\n\t\t\t\treturn String(value)\n\t\t\t}\n\t\t},\n\n\t\topenPicker() {\n\t\t\tthis.pickerOpen = true\n\t\t},\n\n\t\topenCreate() {\n\t\t\tthis.createOpen = true\n\t\t},\n\n\t\tasync onLinkPick(payload) {\n\t\t\tthis.pickerOpen = false\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(this.analyticsEndpoint(), {\n\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\theaders: { ...buildHeaders(), 'Content-Type': 'application/json' },\n\t\t\t\t\tbody: JSON.stringify(payload),\n\t\t\t\t})\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tawait this.fetchReports()\n\t\t\t\t} else if (response.status === 409) {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'This report is already linked.')\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not link report.')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnAnalyticsTab] link failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not link report.')\n\t\t\t}\n\t\t},\n\n\t\tasync onCreatePick(payload) {\n\t\t\tthis.createOpen = false\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(`${this.analyticsEndpoint()}/new`, {\n\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\theaders: { ...buildHeaders(), 'Content-Type': 'application/json' },\n\t\t\t\t\tbody: JSON.stringify(payload),\n\t\t\t\t})\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tawait this.fetchReports()\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not create report.')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnAnalyticsTab] create failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not create report.')\n\t\t\t}\n\t\t},\n\n\t\tasync unlinkReport(report) {\n\t\t\tconst reportId = this.reportKey(report)\n\t\t\tif (!reportId) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(`${this.analyticsEndpoint()}/${reportId}`, {\n\t\t\t\t\tmethod: 'DELETE',\n\t\t\t\t\theaders: buildHeaders(),\n\t\t\t\t})\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tawait this.fetchReports()\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not unlink report.')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnAnalyticsTab] unlink failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not unlink report.')\n\t\t\t}\n\t\t},\n\n\t\tasync fetchReports() {\n\t\t\tif (!this.register || !this.schema || !this.objectId) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\tthis.error = ''\n\t\t\tthis.degraded = ''\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(this.analyticsEndpoint(), { headers: buildHeaders() })\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tconst data = await response.json()\n\t\t\t\t\tthis.reports = data.results || data.items || (Array.isArray(data) ? data : []) || []\n\t\t\t\t} else if (response.status === 503 || response.status === 501) {\n\t\t\t\t\tthis.reports = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.reports = []\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load reports.')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnAnalyticsTab] failed to fetch reports', err)\n\t\t\t\tthis.reports = []\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load reports.')\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-analytics-tab {\n\tpadding: 12px;\n\toverflow-x: hidden;\n}\n\n.cn-analytics-tab__actions {\n\tdisplay: flex;\n\tgap: 8px;\n\tmargin-bottom: 10px;\n\tflex-wrap: wrap;\n}\n\n.cn-analytics-tab__banner {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\tpadding: 8px 10px;\n\tmargin-bottom: 10px;\n\tborder-radius: var(--border-radius);\n\tbackground: var(--color-warning, #e9a40f);\n\tcolor: var(--color-main-background);\n\tfont-size: 0.9em;\n}\n\n.cn-analytics-tab__error {\n\tcolor: var(--color-error);\n\tfont-size: 0.9em;\n\tmargin: 4px 0 8px;\n}\n\n.cn-analytics-tab__empty {\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\tgap: 8px;\n\tpadding: 16px 8px;\n\tcolor: var(--color-text-maxcontrast);\n\ttext-align: center;\n}\n\n.cn-analytics-tab__empty-icon {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-analytics-tab__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n}\n\n/* Chart-type icon avatar — tinted with the descriptor accent (or NC primary). */\n.cn-analytics-tab__row-icon {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\twidth: 40px;\n\theight: 40px;\n\tborder-radius: var(--border-radius-large, 8px);\n\tbackground: var(--color-primary-element-light, var(--color-background-hover));\n\tcolor: var(--cn-iw-accent, var(--color-primary-element, #0082c9));\n}\n\n/* Subtitle row: type badge + data source + modified date, single muted line. */\n.cn-analytics-tab__sub {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tgap: 6px;\n\tfont-size: 11px;\n\tcolor: var(--color-text-maxcontrast);\n\tmin-width: 0;\n}\n\n.cn-analytics-tab__badge {\n\tflex-shrink: 0;\n}\n\n.cn-analytics-tab__subheader {\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n\tmax-width: 100%;\n}\n\n.cn-analytics-tab__when {\n\twhite-space: nowrap;\n}\n\n/* Headline KPI value, right-aligned in the details slot — Analytics style. */\n.cn-analytics-tab__kpi {\n\tfont-weight: 700;\n\tfont-size: 15px;\n\tcolor: var(--color-main-text);\n\twhite-space: nowrap;\n}\n</style>\n"],"names":["CnStatusBadge","CnAnalyticsReportPicker","CnAnalyticsReportCreate","t"],"mappings":"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"}
1
+ {"version":3,"file":"CnAnalyticsTab.vue2.js","sources":["../../../../../src/integrations/builtin/analytics/CnAnalyticsTab.vue"],"sourcesContent":["<!--\n CnAnalyticsTab — bespoke sidebar tab for the `analytics` integration\n leaf.\n\n Replaces the generic `CnIntegrationTab` for the `analytics` leaf:\n renders the linked NC Analytics reports / datasets so the tab reads\n like the real Analytics app — each row is an `NcListItem` showing the\n chart-type icon (bar / line / pie / dataset), the bold report name, a\n data-source / type subtitle, an optional headline KPI value, and a\n colour-coded type badge (CnStatusBadge). Clicking a row deep-links\n into the NC Analytics app at `/index.php/apps/analytics/#/r/{id}`\n (`#/r/` is NC Analytics 6.x's report route). Per-row \"Open in\n Analytics\" + \"Unlink\" live in the NcListItem action menu.\n\n Tier-2 surface (this commit):\n - \"Link existing report\" → opens CnAnalyticsReportPicker (modal)\n - \"Create new report\" → opens CnAnalyticsReportCreate (modal)\n - Per-row unlink → DELETE …/analytics/{reportId}\n\n Talks to the OpenRegister Tier-2 analytics-link endpoints\n GET /api/objects/{r}/{s}/{id}/analytics — list\n POST /api/objects/{r}/{s}/{id}/analytics — link existing\n POST /api/objects/{r}/{s}/{id}/analytics/new — create + link\n DELETE /api/objects/{r}/{s}/{id}/analytics/{reportId} — unlink\n GET /api/integrations/analytics/available — picker source\n served by `OCA\\OpenRegister\\Controller\\AnalyticsLinksController`.\n\n The endpoint returns rows shaped:\n { id, reportId, reportTitle, reportType, subheader, value, kpi,\n createdAt, modifiedAt, url, … }\n\n Empty + loading + error + 501/503 unavailable states follow ADR-017\n and AD-23 graceful degradation. All UI strings pass through\n `t('nextcloud-vue', ...)` per ADR-007. Styling uses Nextcloud CSS\n variables only so the nldesign overrides apply transparently\n (ADR-010).\n\n The headline KPI (`value` / `kpi`) is rendered only when the provider\n supplies it — there is no extra HTTP call per row. A type-coded icon\n communicates the chart kind without fetching chart data; inline\n mini-charts remain a follow-up enhancement.\n\n Bespoke-vs-generic rationale: LaunchPad needs to surface \"what kind of\n report is this + its headline number\" without opening it; the generic\n link list strips the chart type, the data source and the KPI.\n-->\n<template>\n\t<div class=\"cn-sidebar-tab cn-analytics-tab\">\n\t\t<!-- 503 unavailable banner (AD-23) -->\n\t\t<div v-if=\"degraded\" class=\"cn-analytics-tab__banner\" role=\"alert\">\n\t\t\t<AlertCircleOutline :size=\"18\" />\n\t\t\t<span>{{ degraded }}</span>\n\t\t</div>\n\n\t\t<div class=\"cn-analytics-tab__actions\">\n\t\t\t<NcButton variant=\"secondary\" @click=\"openPicker\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<LinkVariant :size=\"18\" />\n\t\t\t\t</template>\n\t\t\t\t{{ t('nextcloud-vue', 'Link existing report') }}\n\t\t\t</NcButton>\n\t\t\t<NcButton variant=\"primary\" @click=\"openCreate\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<Plus :size=\"18\" />\n\t\t\t\t</template>\n\t\t\t\t{{ t('nextcloud-vue', 'Create new report') }}\n\t\t\t</NcButton>\n\t\t</div>\n\n\t\t<!-- Loading -->\n\t\t<NcLoadingIcon v-if=\"loading\" />\n\n\t\t<!-- Error -->\n\t\t<div v-else-if=\"error\" class=\"cn-analytics-tab__error\" role=\"alert\">\n\t\t\t{{ error }}\n\t\t</div>\n\n\t\t<!-- Empty -->\n\t\t<div v-else-if=\"reports.length === 0\" class=\"cn-sidebar-tab__empty cn-analytics-tab__empty\">\n\t\t\t<ChartBar :size=\"32\" class=\"cn-analytics-tab__empty-icon\" />\n\t\t\t<p>{{ emptyLabel }}</p>\n\t\t\t<NcButton\n\t\t\t\tvariant=\"primary\"\n\t\t\t\t:href=\"analyticsAppUrl\"\n\t\t\t\ttarget=\"_blank\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<ChartBar :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ openAnalyticsLabel }}\n\t\t\t</NcButton>\n\t\t</div>\n\n\t\t<!-- Report list — NcListItem rows mirror the NC Analytics dataset list -->\n\t\t<ul v-else class=\"cn-analytics-tab__list\">\n\t\t\t<NcListItem\n\t\t\t\tv-for=\"report in reports\"\n\t\t\t\t:key=\"reportKey(report)\"\n\t\t\t\tclass=\"cn-analytics-tab__row\"\n\t\t\t\t:name=\"reportTitle(report)\"\n\t\t\t\t:bold=\"true\"\n\t\t\t\t:href=\"reportUrl(report)\"\n\t\t\t\ttarget=\"_blank\"\n\t\t\t\t:forceDisplayActions=\"true\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<span class=\"cn-analytics-tab__row-icon\">\n\t\t\t\t\t\t<component :is=\"reportIcon(report)\" :size=\"22\" />\n\t\t\t\t\t</span>\n\t\t\t\t</template>\n\t\t\t\t<template #subname>\n\t\t\t\t\t<span class=\"cn-analytics-tab__sub\">\n\t\t\t\t\t\t<CnStatusBadge\n\t\t\t\t\t\t\tclass=\"cn-analytics-tab__badge\"\n\t\t\t\t\t\t\tsize=\"small\"\n\t\t\t\t\t\t\t:variant=\"badgeVariant(report)\"\n\t\t\t\t\t\t\t:label=\"reportTypeLabel(report)\" />\n\t\t\t\t\t\t<span v-if=\"reportSubheader(report)\" class=\"cn-analytics-tab__subheader\">\n\t\t\t\t\t\t\t{{ reportSubheader(report) }}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<span v-if=\"modifiedAt(report)\" class=\"cn-analytics-tab__when\">\n\t\t\t\t\t\t\t· {{ formatWhen(modifiedAt(report)) }}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</span>\n\t\t\t\t</template>\n\t\t\t\t<template v-if=\"reportValue(report)\" #details>\n\t\t\t\t\t<span class=\"cn-analytics-tab__kpi\" :title=\"kpiTitle(report)\">\n\t\t\t\t\t\t{{ reportValue(report) }}\n\t\t\t\t\t</span>\n\t\t\t\t</template>\n\t\t\t\t<template #actions>\n\t\t\t\t\t<NcActionLink\n\t\t\t\t\t\t:closeAfterClick=\"true\"\n\t\t\t\t\t\t:href=\"reportUrl(report)\"\n\t\t\t\t\t\ttarget=\"_blank\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<OpenInNew :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ t('nextcloud-vue', 'Open in Analytics') }}\n\t\t\t\t\t</NcActionLink>\n\t\t\t\t\t<NcActionButton :closeAfterClick=\"true\" @click=\"unlinkReport(report)\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<LinkOff :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ t('nextcloud-vue', 'Unlink report') }}\n\t\t\t\t\t</NcActionButton>\n\t\t\t\t</template>\n\t\t\t</NcListItem>\n\t\t</ul>\n\n\t\t<CnAnalyticsReportPicker\n\t\t\tv-if=\"pickerOpen\"\n\t\t\t:apiBase=\"apiBase\"\n\t\t\t@close=\"pickerOpen = false\"\n\t\t\t@link=\"onLinkPick\" />\n\n\t\t<CnAnalyticsReportCreate\n\t\t\tv-if=\"createOpen\"\n\t\t\t@close=\"createOpen = false\"\n\t\t\t@create=\"onCreatePick\" />\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcActionButton, NcActionLink, NcButton, NcListItem, NcLoadingIcon } from '@nextcloud/vue'\nimport AlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue'\nimport ChartBar from 'vue-material-design-icons/ChartBar.vue'\nimport ChartLine from 'vue-material-design-icons/ChartLine.vue'\nimport ChartPie from 'vue-material-design-icons/ChartPie.vue'\nimport DatabaseOutline from 'vue-material-design-icons/DatabaseOutline.vue'\nimport LinkOff from 'vue-material-design-icons/LinkOff.vue'\nimport LinkVariant from 'vue-material-design-icons/LinkVariant.vue'\nimport OpenInNew from 'vue-material-design-icons/OpenInNew.vue'\nimport Plus from 'vue-material-design-icons/Plus.vue'\nimport TableIcon from 'vue-material-design-icons/Table.vue'\nimport ViewDashboard from 'vue-material-design-icons/ViewDashboard.vue'\nimport CnAnalyticsReportCreate from '../../../components/CnAnalyticsReportCreate/CnAnalyticsReportCreate.vue'\nimport CnAnalyticsReportPicker from '../../../components/CnAnalyticsReportPicker/CnAnalyticsReportPicker.vue'\nimport CnStatusBadge from '../../../components/CnStatusBadge/CnStatusBadge.vue'\nimport { buildHeaders, prefixUrl, safeHref } from '../../../utils/index.js'\n\n/**\n * CnAnalyticsTab — bespoke dataset list for the `analytics` integration.\n *\n * Renders linked NC Analytics reports / datasets as NcListItem rows\n * styled to mirror the Analytics app: chart-type icon, bold report\n * name, data-source / type subtitle, type badge and an optional\n * headline KPI value. Clicking a row opens the report in NC Analytics\n * in a new tab. Tier-2: includes link / create modals and per-row\n * unlink via the row action menu. See the file-level docblock for\n * design notes.\n */\nexport default {\n\tname: 'CnAnalyticsTab',\n\n\tcomponents: {\n\t\tNcActionButton,\n\t\tNcActionLink,\n\t\tNcButton,\n\t\tNcListItem,\n\t\tNcLoadingIcon,\n\t\tCnStatusBadge,\n\t\tAlertCircleOutline,\n\t\tChartBar,\n\t\tChartLine,\n\t\tChartPie,\n\t\tDatabaseOutline,\n\t\tLinkOff,\n\t\tLinkVariant,\n\t\tOpenInNew,\n\t\tPlus,\n\t\tTableIcon,\n\t\tViewDashboard,\n\t\tCnAnalyticsReportPicker,\n\t\tCnAnalyticsReportCreate,\n\t},\n\n\tprops: {\n\t\t/* eslint-disable vue/no-unused-properties -- the integration dispatch binds integrationId on every integration component (see CnIntegrationWidgetGrid), so declaring it keeps it out of $attrs */\n\t\t/** Stable integration id (forwarded from the registry — always `'analytics'`). */\n\t\tintegrationId: { type: String, default: 'analytics' },\n\t\t/* eslint-enable vue/no-unused-properties */\n\t\t/** Parent object id. */\n\t\tobjectId: { type: String, required: true },\n\t\t/** OpenRegister register id (slug or uuid). */\n\t\tregister: { type: String, default: '' },\n\t\t/** OpenRegister schema id (slug or uuid). */\n\t\tschema: { type: String, default: '' },\n\t\t/** Base API URL. */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\t\t/** Pre-translated empty-state label. */\n\t\temptyLabel: { type: String, default: () => t('nextcloud-vue', 'No reports linked yet') },\n\t\t/** Pre-translated \"Open Analytics\" CTA label. */\n\t\topenAnalyticsLabel: { type: String, default: () => t('nextcloud-vue', 'Open Analytics') },\n\t\t/** Pre-translated 503 unavailable banner copy. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Analytics is currently unavailable.') },\n\t\t/** URL of the NC Analytics app entry. */\n\t\tanalyticsAppUrl: { type: String, default: '/index.php/apps/analytics' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\treports: [],\n\t\t\tloading: false,\n\t\t\terror: '',\n\t\t\tdegraded: '',\n\t\t\tpickerOpen: false,\n\t\t\tcreateOpen: false,\n\t\t}\n\t},\n\n\twatch: {\n\t\tobjectId: { immediate: true, handler(id) {\n\t\t\tif (id) {\n\t\t\t\tthis.fetchReports()\n\t\t\t}\n\t\t} },\n\n\t\tregister() {\n\t\t\tthis.fetchReports()\n\t\t},\n\n\t\tschema() {\n\t\t\tthis.fetchReports()\n\t\t},\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/**\n\t\t * Base for the Tier-2 analytics endpoints (list/link/new/destroy).\n\t\t *\n\t\t * @return {string} The endpoint URL.\n\t\t */\n\t\tanalyticsEndpoint() {\n\t\t\treturn prefixUrl(`${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/analytics`)\n\t\t},\n\n\t\treportKey(report) {\n\t\t\treturn (report && (report.reportId ?? report.id ?? report.data?.id)) || ''\n\t\t},\n\n\t\t/**\n\t\t * Resolve the displayable report title, stripping the\n\t\t * `[or:{uuid}]` marker (pre-Tier-2 reports embedded the marker\n\t\t * inside `name`).\n\t\t *\n\t\t * @param {object} report Provider row.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\treportTitle(report) {\n\t\t\tconst raw = (report && (report.reportTitle || report.title || report.name || report.data?.name)) || ''\n\t\t\treturn String(raw).replace(/\\s*\\[or:[^\\]]+\\]\\s*/g, ' ').trim() || t('nextcloud-vue', 'Untitled report')\n\t\t},\n\n\t\t/**\n\t\t * Resolve the secondary \"subheader\" line, stripping any leaked\n\t\t * marker defensively.\n\t\t *\n\t\t * @param {object} report Provider row.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\treportSubheader(report) {\n\t\t\tconst raw = report?.subheader ?? report?.dataSource ?? report?.data?.subheader ?? ''\n\t\t\treturn String(raw).replace(/\\s*\\[or:[^\\]]+\\]\\s*/g, ' ').trim()\n\t\t},\n\n\t\treportType(report) {\n\t\t\tconst raw = report?.reportType ?? report?.type ?? report?.data?.type\n\t\t\tif (raw === null || raw === undefined || raw === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst num = Number(raw)\n\t\t\treturn Number.isFinite(num) ? num : null\n\t\t},\n\n\t\t/**\n\t\t * Resolve a translatable label for the report type. Falls back\n\t\t * to \"Report\" when the type code is unknown. Type codes mirror\n\t\t * NC Analytics 6.x (datasource types).\n\t\t *\n\t\t * @param {object} report Provider row.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\treportTypeLabel(report) {\n\t\t\tconst type = this.reportType(report)\n\t\t\tswitch (type) {\n\t\t\t\tcase 0: return t('nextcloud-vue', 'Group')\n\t\t\t\tcase 1: return t('nextcloud-vue', 'File')\n\t\t\t\tcase 2: return t('nextcloud-vue', 'Database')\n\t\t\t\tcase 3: return t('nextcloud-vue', 'Git')\n\t\t\t\tcase 4: return t('nextcloud-vue', 'External')\n\t\t\t\tcase 6: return t('nextcloud-vue', 'JSON')\n\t\t\t\tdefault: return t('nextcloud-vue', 'Report')\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * CnStatusBadge colour variant for the report type. Mirrors NC\n\t\t * Analytics' datasource-type accenting without inventing colours\n\t\t * (uses the shared variant palette).\n\t\t *\n\t\t * @param {object} report Provider row.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tbadgeVariant(report) {\n\t\t\tconst type = this.reportType(report)\n\t\t\tswitch (type) {\n\t\t\t\tcase 1: return 'primary'\n\t\t\t\tcase 2: return 'success'\n\t\t\t\tcase 4: return 'warning'\n\t\t\t\tcase 6: return 'info'\n\t\t\t\tdefault: return 'default'\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * MDI component for the report type. Tries to express the\n\t\t * \"kind\" without fetching chart data.\n\t\t *\n\t\t * @param {object} report Provider row.\n\t\t *\n\t\t * @return {object} Vue component.\n\t\t */\n\t\treportIcon(report) {\n\t\t\tconst type = this.reportType(report)\n\t\t\tswitch (type) {\n\t\t\t\tcase 1: return TableIcon\n\t\t\t\tcase 0: return ViewDashboard\n\t\t\t\tcase 2: return DatabaseOutline\n\t\t\t\tcase 4: return ChartLine\n\t\t\t\tcase 6: return ChartPie\n\t\t\t\tdefault: return ChartBar\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Headline KPI / value for the report, when the provider supplies\n\t\t * one. Numbers are locale-formatted; strings pass through.\n\t\t *\n\t\t * @param {object} report Provider row.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\treportValue(report) {\n\t\t\tconst raw = report?.value ?? report?.kpi ?? report?.data?.value ?? ''\n\t\t\tif (raw === null || raw === undefined || raw === '') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst num = Number(raw)\n\t\t\tif (Number.isFinite(num) && String(raw).trim() !== '') {\n\t\t\t\ttry {\n\t\t\t\t\treturn num.toLocaleString()\n\t\t\t\t} catch {\n\t\t\t\t\treturn String(num)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn String(raw)\n\t\t},\n\n\t\tkpiTitle(_report) {\n\t\t\treturn t('nextcloud-vue', 'Latest value')\n\t\t},\n\n\t\t// A provider URL is used only when it is a safe scheme; else the app link.\n\t\treportUrl(report) {\n\t\t\tif (report && report.url && safeHref(report.url) !== '#') {\n\t\t\t\treturn report.url\n\t\t\t}\n\t\t\tconst id = this.reportKey(report)\n\t\t\treturn id ? `/index.php/apps/analytics/#/r/${encodeURIComponent(id)}` : this.analyticsAppUrl\n\t\t},\n\n\t\tmodifiedAt(report) {\n\t\t\treturn report?.modifiedAt\n\t\t\t\t|| report?.data?.modifiedAt\n\t\t\t\t|| report?.data?.modified\n\t\t\t\t|| report?.createdAt\n\t\t\t\t|| report?.data?.createdAt\n\t\t\t\t|| ''\n\t\t},\n\n\t\tformatWhen(value) {\n\t\t\tif (!value) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst num = Number(value)\n\t\t\t\tconst d = Number.isFinite(num) && num > 0 && String(value).length <= 12\n\t\t\t\t\t? new Date(num * 1000)\n\t\t\t\t\t: new Date(value)\n\t\t\t\tif (Number.isNaN(d.getTime())) {\n\t\t\t\t\treturn String(value)\n\t\t\t\t}\n\t\t\t\treturn d.toLocaleDateString(undefined, { dateStyle: 'medium' })\n\t\t\t} catch {\n\t\t\t\treturn String(value)\n\t\t\t}\n\t\t},\n\n\t\topenPicker() {\n\t\t\tthis.pickerOpen = true\n\t\t},\n\n\t\topenCreate() {\n\t\t\tthis.createOpen = true\n\t\t},\n\n\t\tasync onLinkPick(payload) {\n\t\t\tthis.pickerOpen = false\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(this.analyticsEndpoint(), {\n\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\theaders: { ...buildHeaders(), 'Content-Type': 'application/json' },\n\t\t\t\t\tbody: JSON.stringify(payload),\n\t\t\t\t})\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tawait this.fetchReports()\n\t\t\t\t} else if (response.status === 409) {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'This report is already linked.')\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not link report.')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnAnalyticsTab] link failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not link report.')\n\t\t\t}\n\t\t},\n\n\t\tasync onCreatePick(payload) {\n\t\t\tthis.createOpen = false\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(`${this.analyticsEndpoint()}/new`, {\n\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\theaders: { ...buildHeaders(), 'Content-Type': 'application/json' },\n\t\t\t\t\tbody: JSON.stringify(payload),\n\t\t\t\t})\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tawait this.fetchReports()\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not create report.')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnAnalyticsTab] create failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not create report.')\n\t\t\t}\n\t\t},\n\n\t\tasync unlinkReport(report) {\n\t\t\tconst reportId = this.reportKey(report)\n\t\t\tif (!reportId) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(`${this.analyticsEndpoint()}/${reportId}`, {\n\t\t\t\t\tmethod: 'DELETE',\n\t\t\t\t\theaders: buildHeaders(),\n\t\t\t\t})\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tawait this.fetchReports()\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not unlink report.')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnAnalyticsTab] unlink failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not unlink report.')\n\t\t\t}\n\t\t},\n\n\t\tasync fetchReports() {\n\t\t\tif (!this.register || !this.schema || !this.objectId) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\tthis.error = ''\n\t\t\tthis.degraded = ''\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(this.analyticsEndpoint(), { headers: buildHeaders() })\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tconst data = await response.json()\n\t\t\t\t\tthis.reports = data.results || data.items || (Array.isArray(data) ? data : []) || []\n\t\t\t\t} else if (response.status === 503 || response.status === 501) {\n\t\t\t\t\tthis.reports = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.reports = []\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load reports.')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnAnalyticsTab] failed to fetch reports', err)\n\t\t\t\tthis.reports = []\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load reports.')\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-analytics-tab {\n\tpadding: 12px;\n\toverflow-x: hidden;\n}\n\n.cn-analytics-tab__actions {\n\tdisplay: flex;\n\tgap: 8px;\n\tmargin-bottom: 10px;\n\tflex-wrap: wrap;\n}\n\n.cn-analytics-tab__banner {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\tpadding: 8px 10px;\n\tmargin-bottom: 10px;\n\tborder-radius: var(--border-radius);\n\tbackground: var(--color-warning, #e9a40f);\n\tcolor: var(--color-main-background);\n\tfont-size: 0.9em;\n}\n\n.cn-analytics-tab__error {\n\tcolor: var(--color-error);\n\tfont-size: 0.9em;\n\tmargin: 4px 0 8px;\n}\n\n.cn-analytics-tab__empty {\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\tgap: 8px;\n\tpadding: 16px 8px;\n\tcolor: var(--color-text-maxcontrast);\n\ttext-align: center;\n}\n\n.cn-analytics-tab__empty-icon {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-analytics-tab__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n}\n\n/* Chart-type icon avatar — tinted with the descriptor accent (or NC primary). */\n.cn-analytics-tab__row-icon {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\twidth: 40px;\n\theight: 40px;\n\tborder-radius: var(--border-radius-large, 8px);\n\tbackground: var(--color-primary-element-light, var(--color-background-hover));\n\tcolor: var(--cn-iw-accent, var(--color-primary-element, #0082c9));\n}\n\n/* Subtitle row: type badge + data source + modified date, single muted line. */\n.cn-analytics-tab__sub {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tgap: 6px;\n\tfont-size: 11px;\n\tcolor: var(--color-text-maxcontrast);\n\tmin-width: 0;\n}\n\n.cn-analytics-tab__badge {\n\tflex-shrink: 0;\n}\n\n.cn-analytics-tab__subheader {\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n\tmax-width: 100%;\n}\n\n.cn-analytics-tab__when {\n\twhite-space: nowrap;\n}\n\n/* Headline KPI value, right-aligned in the details slot — Analytics style. */\n.cn-analytics-tab__kpi {\n\tfont-weight: 700;\n\tfont-size: 15px;\n\tcolor: var(--color-main-text);\n\twhite-space: nowrap;\n}\n</style>\n"],"names":["CnStatusBadge","CnAnalyticsReportPicker","CnAnalyticsReportCreate","t"],"mappings":"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"}
@@ -95,8 +95,7 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
95
95
  createVNode(_component_NcButton, {
96
96
  variant: "primary",
97
97
  href: $props.analyticsAppUrl,
98
- target: "_blank",
99
- rel: "noopener noreferrer"
98
+ target: "_blank"
100
99
  }, {
101
100
  icon: withCtx(() => [
102
101
  createVNode(_component_ChartBar, { size: 20 })
@@ -143,8 +142,7 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
143
142
  createVNode(_component_NcActionLink, {
144
143
  closeAfterClick: true,
145
144
  href: $options.reportUrl(report),
146
- target: "_blank",
147
- rel: "noopener noreferrer"
145
+ target: "_blank"
148
146
  }, {
149
147
  icon: withCtx(() => [
150
148
  createVNode(_component_OpenInNew, { size: 20 })
@@ -1 +1 @@
1
- {"version":3,"file":"CnAnalyticsTab.vue3.js","sources":["../../../../../src/integrations/builtin/analytics/CnAnalyticsTab.vue?vue&type=template&id=52d520d6&lang.js"],"sourcesContent":["<!--\n CnAnalyticsTab — bespoke sidebar tab for the `analytics` integration\n leaf.\n\n Replaces the generic `CnIntegrationTab` for the `analytics` leaf:\n renders the linked NC Analytics reports / datasets so the tab reads\n like the real Analytics app — each row is an `NcListItem` showing the\n chart-type icon (bar / line / pie / dataset), the bold report name, a\n data-source / type subtitle, an optional headline KPI value, and a\n colour-coded type badge (CnStatusBadge). Clicking a row deep-links\n into the NC Analytics app at `/index.php/apps/analytics/#/r/{id}`\n (`#/r/` is NC Analytics 6.x's report route). Per-row \"Open in\n Analytics\" + \"Unlink\" live in the NcListItem action menu.\n\n Tier-2 surface (this commit):\n - \"Link existing report\" → opens CnAnalyticsReportPicker (modal)\n - \"Create new report\" → opens CnAnalyticsReportCreate (modal)\n - Per-row unlink → DELETE …/analytics/{reportId}\n\n Talks to the OpenRegister Tier-2 analytics-link endpoints\n GET /api/objects/{r}/{s}/{id}/analytics — list\n POST /api/objects/{r}/{s}/{id}/analytics — link existing\n POST /api/objects/{r}/{s}/{id}/analytics/new — create + link\n DELETE /api/objects/{r}/{s}/{id}/analytics/{reportId} — unlink\n GET /api/integrations/analytics/available — picker source\n served by `OCA\\OpenRegister\\Controller\\AnalyticsLinksController`.\n\n The endpoint returns rows shaped:\n { id, reportId, reportTitle, reportType, subheader, value, kpi,\n createdAt, modifiedAt, url, … }\n\n Empty + loading + error + 501/503 unavailable states follow ADR-017\n and AD-23 graceful degradation. All UI strings pass through\n `t('nextcloud-vue', ...)` per ADR-007. Styling uses Nextcloud CSS\n variables only so the nldesign overrides apply transparently\n (ADR-010).\n\n The headline KPI (`value` / `kpi`) is rendered only when the provider\n supplies it — there is no extra HTTP call per row. A type-coded icon\n communicates the chart kind without fetching chart data; inline\n mini-charts remain a follow-up enhancement.\n\n Bespoke-vs-generic rationale: LaunchPad needs to surface \"what kind of\n report is this + its headline number\" without opening it; the generic\n link list strips the chart type, the data source and the KPI.\n-->\n<template>\n\t<div class=\"cn-sidebar-tab cn-analytics-tab\">\n\t\t<!-- 503 unavailable banner (AD-23) -->\n\t\t<div v-if=\"degraded\" class=\"cn-analytics-tab__banner\" role=\"alert\">\n\t\t\t<AlertCircleOutline :size=\"18\" />\n\t\t\t<span>{{ degraded }}</span>\n\t\t</div>\n\n\t\t<div class=\"cn-analytics-tab__actions\">\n\t\t\t<NcButton variant=\"secondary\" @click=\"openPicker\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<LinkVariant :size=\"18\" />\n\t\t\t\t</template>\n\t\t\t\t{{ t('nextcloud-vue', 'Link existing report') }}\n\t\t\t</NcButton>\n\t\t\t<NcButton variant=\"primary\" @click=\"openCreate\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<Plus :size=\"18\" />\n\t\t\t\t</template>\n\t\t\t\t{{ t('nextcloud-vue', 'Create new report') }}\n\t\t\t</NcButton>\n\t\t</div>\n\n\t\t<!-- Loading -->\n\t\t<NcLoadingIcon v-if=\"loading\" />\n\n\t\t<!-- Error -->\n\t\t<div v-else-if=\"error\" class=\"cn-analytics-tab__error\" role=\"alert\">\n\t\t\t{{ error }}\n\t\t</div>\n\n\t\t<!-- Empty -->\n\t\t<div v-else-if=\"reports.length === 0\" class=\"cn-sidebar-tab__empty cn-analytics-tab__empty\">\n\t\t\t<ChartBar :size=\"32\" class=\"cn-analytics-tab__empty-icon\" />\n\t\t\t<p>{{ emptyLabel }}</p>\n\t\t\t<NcButton\n\t\t\t\tvariant=\"primary\"\n\t\t\t\t:href=\"analyticsAppUrl\"\n\t\t\t\ttarget=\"_blank\"\n\t\t\t\trel=\"noopener noreferrer\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<ChartBar :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ openAnalyticsLabel }}\n\t\t\t</NcButton>\n\t\t</div>\n\n\t\t<!-- Report list — NcListItem rows mirror the NC Analytics dataset list -->\n\t\t<ul v-else class=\"cn-analytics-tab__list\">\n\t\t\t<NcListItem\n\t\t\t\tv-for=\"report in reports\"\n\t\t\t\t:key=\"reportKey(report)\"\n\t\t\t\tclass=\"cn-analytics-tab__row\"\n\t\t\t\t:name=\"reportTitle(report)\"\n\t\t\t\t:bold=\"true\"\n\t\t\t\t:href=\"reportUrl(report)\"\n\t\t\t\ttarget=\"_blank\"\n\t\t\t\t:forceDisplayActions=\"true\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<span class=\"cn-analytics-tab__row-icon\">\n\t\t\t\t\t\t<component :is=\"reportIcon(report)\" :size=\"22\" />\n\t\t\t\t\t</span>\n\t\t\t\t</template>\n\t\t\t\t<template #subname>\n\t\t\t\t\t<span class=\"cn-analytics-tab__sub\">\n\t\t\t\t\t\t<CnStatusBadge\n\t\t\t\t\t\t\tclass=\"cn-analytics-tab__badge\"\n\t\t\t\t\t\t\tsize=\"small\"\n\t\t\t\t\t\t\t:variant=\"badgeVariant(report)\"\n\t\t\t\t\t\t\t:label=\"reportTypeLabel(report)\" />\n\t\t\t\t\t\t<span v-if=\"reportSubheader(report)\" class=\"cn-analytics-tab__subheader\">\n\t\t\t\t\t\t\t{{ reportSubheader(report) }}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<span v-if=\"modifiedAt(report)\" class=\"cn-analytics-tab__when\">\n\t\t\t\t\t\t\t· {{ formatWhen(modifiedAt(report)) }}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</span>\n\t\t\t\t</template>\n\t\t\t\t<template v-if=\"reportValue(report)\" #details>\n\t\t\t\t\t<span class=\"cn-analytics-tab__kpi\" :title=\"kpiTitle(report)\">\n\t\t\t\t\t\t{{ reportValue(report) }}\n\t\t\t\t\t</span>\n\t\t\t\t</template>\n\t\t\t\t<template #actions>\n\t\t\t\t\t<NcActionLink\n\t\t\t\t\t\t:closeAfterClick=\"true\"\n\t\t\t\t\t\t:href=\"reportUrl(report)\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener noreferrer\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<OpenInNew :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ t('nextcloud-vue', 'Open in Analytics') }}\n\t\t\t\t\t</NcActionLink>\n\t\t\t\t\t<NcActionButton :closeAfterClick=\"true\" @click=\"unlinkReport(report)\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<LinkOff :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ t('nextcloud-vue', 'Unlink report') }}\n\t\t\t\t\t</NcActionButton>\n\t\t\t\t</template>\n\t\t\t</NcListItem>\n\t\t</ul>\n\n\t\t<CnAnalyticsReportPicker\n\t\t\tv-if=\"pickerOpen\"\n\t\t\t:apiBase=\"apiBase\"\n\t\t\t@close=\"pickerOpen = false\"\n\t\t\t@link=\"onLinkPick\" />\n\n\t\t<CnAnalyticsReportCreate\n\t\t\tv-if=\"createOpen\"\n\t\t\t@close=\"createOpen = false\"\n\t\t\t@create=\"onCreatePick\" />\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcActionButton, NcActionLink, NcButton, NcListItem, NcLoadingIcon } from '@nextcloud/vue'\nimport AlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue'\nimport ChartBar from 'vue-material-design-icons/ChartBar.vue'\nimport ChartLine from 'vue-material-design-icons/ChartLine.vue'\nimport ChartPie from 'vue-material-design-icons/ChartPie.vue'\nimport DatabaseOutline from 'vue-material-design-icons/DatabaseOutline.vue'\nimport LinkOff from 'vue-material-design-icons/LinkOff.vue'\nimport LinkVariant from 'vue-material-design-icons/LinkVariant.vue'\nimport OpenInNew from 'vue-material-design-icons/OpenInNew.vue'\nimport Plus from 'vue-material-design-icons/Plus.vue'\nimport TableIcon from 'vue-material-design-icons/Table.vue'\nimport ViewDashboard from 'vue-material-design-icons/ViewDashboard.vue'\nimport CnAnalyticsReportCreate from '../../../components/CnAnalyticsReportCreate/CnAnalyticsReportCreate.vue'\nimport CnAnalyticsReportPicker from '../../../components/CnAnalyticsReportPicker/CnAnalyticsReportPicker.vue'\nimport CnStatusBadge from '../../../components/CnStatusBadge/CnStatusBadge.vue'\nimport { buildHeaders, prefixUrl } from '../../../utils/index.js'\n\n/**\n * CnAnalyticsTab — bespoke dataset list for the `analytics` integration.\n *\n * Renders linked NC Analytics reports / datasets as NcListItem rows\n * styled to mirror the Analytics app: chart-type icon, bold report\n * name, data-source / type subtitle, type badge and an optional\n * headline KPI value. Clicking a row opens the report in NC Analytics\n * in a new tab. Tier-2: includes link / create modals and per-row\n * unlink via the row action menu. See the file-level docblock for\n * design notes.\n */\nexport default {\n\tname: 'CnAnalyticsTab',\n\n\tcomponents: {\n\t\tNcActionButton,\n\t\tNcActionLink,\n\t\tNcButton,\n\t\tNcListItem,\n\t\tNcLoadingIcon,\n\t\tCnStatusBadge,\n\t\tAlertCircleOutline,\n\t\tChartBar,\n\t\tChartLine,\n\t\tChartPie,\n\t\tDatabaseOutline,\n\t\tLinkOff,\n\t\tLinkVariant,\n\t\tOpenInNew,\n\t\tPlus,\n\t\tTableIcon,\n\t\tViewDashboard,\n\t\tCnAnalyticsReportPicker,\n\t\tCnAnalyticsReportCreate,\n\t},\n\n\tprops: {\n\t\t/* eslint-disable vue/no-unused-properties -- the integration dispatch binds integrationId on every integration component (see CnIntegrationWidgetGrid), so declaring it keeps it out of $attrs */\n\t\t/** Stable integration id (forwarded from the registry — always `'analytics'`). */\n\t\tintegrationId: { type: String, default: 'analytics' },\n\t\t/* eslint-enable vue/no-unused-properties */\n\t\t/** Parent object id. */\n\t\tobjectId: { type: String, required: true },\n\t\t/** OpenRegister register id (slug or uuid). */\n\t\tregister: { type: String, default: '' },\n\t\t/** OpenRegister schema id (slug or uuid). */\n\t\tschema: { type: String, default: '' },\n\t\t/** Base API URL. */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\t\t/** Pre-translated empty-state label. */\n\t\temptyLabel: { type: String, default: () => t('nextcloud-vue', 'No reports linked yet') },\n\t\t/** Pre-translated \"Open Analytics\" CTA label. */\n\t\topenAnalyticsLabel: { type: String, default: () => t('nextcloud-vue', 'Open Analytics') },\n\t\t/** Pre-translated 503 unavailable banner copy. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Analytics is currently unavailable.') },\n\t\t/** URL of the NC Analytics app entry. */\n\t\tanalyticsAppUrl: { type: String, default: '/index.php/apps/analytics' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\treports: [],\n\t\t\tloading: false,\n\t\t\terror: '',\n\t\t\tdegraded: '',\n\t\t\tpickerOpen: false,\n\t\t\tcreateOpen: false,\n\t\t}\n\t},\n\n\twatch: {\n\t\tobjectId: { immediate: true, handler(id) {\n\t\t\tif (id) {\n\t\t\t\tthis.fetchReports()\n\t\t\t}\n\t\t} },\n\n\t\tregister() {\n\t\t\tthis.fetchReports()\n\t\t},\n\n\t\tschema() {\n\t\t\tthis.fetchReports()\n\t\t},\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/**\n\t\t * Base for the Tier-2 analytics endpoints (list/link/new/destroy).\n\t\t *\n\t\t * @return {string} The endpoint URL.\n\t\t */\n\t\tanalyticsEndpoint() {\n\t\t\treturn prefixUrl(`${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/analytics`)\n\t\t},\n\n\t\treportKey(report) {\n\t\t\treturn (report && (report.reportId ?? report.id ?? report.data?.id)) || ''\n\t\t},\n\n\t\t/**\n\t\t * Resolve the displayable report title, stripping the\n\t\t * `[or:{uuid}]` marker (pre-Tier-2 reports embedded the marker\n\t\t * inside `name`).\n\t\t *\n\t\t * @param {object} report Provider row.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\treportTitle(report) {\n\t\t\tconst raw = (report && (report.reportTitle || report.title || report.name || report.data?.name)) || ''\n\t\t\treturn String(raw).replace(/\\s*\\[or:[^\\]]+\\]\\s*/g, ' ').trim() || t('nextcloud-vue', 'Untitled report')\n\t\t},\n\n\t\t/**\n\t\t * Resolve the secondary \"subheader\" line, stripping any leaked\n\t\t * marker defensively.\n\t\t *\n\t\t * @param {object} report Provider row.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\treportSubheader(report) {\n\t\t\tconst raw = report?.subheader ?? report?.dataSource ?? report?.data?.subheader ?? ''\n\t\t\treturn String(raw).replace(/\\s*\\[or:[^\\]]+\\]\\s*/g, ' ').trim()\n\t\t},\n\n\t\treportType(report) {\n\t\t\tconst raw = report?.reportType ?? report?.type ?? report?.data?.type\n\t\t\tif (raw === null || raw === undefined || raw === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst num = Number(raw)\n\t\t\treturn Number.isFinite(num) ? num : null\n\t\t},\n\n\t\t/**\n\t\t * Resolve a translatable label for the report type. Falls back\n\t\t * to \"Report\" when the type code is unknown. Type codes mirror\n\t\t * NC Analytics 6.x (datasource types).\n\t\t *\n\t\t * @param {object} report Provider row.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\treportTypeLabel(report) {\n\t\t\tconst type = this.reportType(report)\n\t\t\tswitch (type) {\n\t\t\t\tcase 0: return t('nextcloud-vue', 'Group')\n\t\t\t\tcase 1: return t('nextcloud-vue', 'File')\n\t\t\t\tcase 2: return t('nextcloud-vue', 'Database')\n\t\t\t\tcase 3: return t('nextcloud-vue', 'Git')\n\t\t\t\tcase 4: return t('nextcloud-vue', 'External')\n\t\t\t\tcase 6: return t('nextcloud-vue', 'JSON')\n\t\t\t\tdefault: return t('nextcloud-vue', 'Report')\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * CnStatusBadge colour variant for the report type. Mirrors NC\n\t\t * Analytics' datasource-type accenting without inventing colours\n\t\t * (uses the shared variant palette).\n\t\t *\n\t\t * @param {object} report Provider row.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tbadgeVariant(report) {\n\t\t\tconst type = this.reportType(report)\n\t\t\tswitch (type) {\n\t\t\t\tcase 1: return 'primary'\n\t\t\t\tcase 2: return 'success'\n\t\t\t\tcase 4: return 'warning'\n\t\t\t\tcase 6: return 'info'\n\t\t\t\tdefault: return 'default'\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * MDI component for the report type. Tries to express the\n\t\t * \"kind\" without fetching chart data.\n\t\t *\n\t\t * @param {object} report Provider row.\n\t\t *\n\t\t * @return {object} Vue component.\n\t\t */\n\t\treportIcon(report) {\n\t\t\tconst type = this.reportType(report)\n\t\t\tswitch (type) {\n\t\t\t\tcase 1: return TableIcon\n\t\t\t\tcase 0: return ViewDashboard\n\t\t\t\tcase 2: return DatabaseOutline\n\t\t\t\tcase 4: return ChartLine\n\t\t\t\tcase 6: return ChartPie\n\t\t\t\tdefault: return ChartBar\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Headline KPI / value for the report, when the provider supplies\n\t\t * one. Numbers are locale-formatted; strings pass through.\n\t\t *\n\t\t * @param {object} report Provider row.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\treportValue(report) {\n\t\t\tconst raw = report?.value ?? report?.kpi ?? report?.data?.value ?? ''\n\t\t\tif (raw === null || raw === undefined || raw === '') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst num = Number(raw)\n\t\t\tif (Number.isFinite(num) && String(raw).trim() !== '') {\n\t\t\t\ttry {\n\t\t\t\t\treturn num.toLocaleString()\n\t\t\t\t} catch {\n\t\t\t\t\treturn String(num)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn String(raw)\n\t\t},\n\n\t\tkpiTitle(_report) {\n\t\t\treturn t('nextcloud-vue', 'Latest value')\n\t\t},\n\n\t\treportUrl(report) {\n\t\t\tif (report && report.url) {\n\t\t\t\treturn report.url\n\t\t\t}\n\t\t\tconst id = this.reportKey(report)\n\t\t\treturn id ? `/index.php/apps/analytics/#/r/${encodeURIComponent(id)}` : this.analyticsAppUrl\n\t\t},\n\n\t\tmodifiedAt(report) {\n\t\t\treturn report?.modifiedAt\n\t\t\t\t|| report?.data?.modifiedAt\n\t\t\t\t|| report?.data?.modified\n\t\t\t\t|| report?.createdAt\n\t\t\t\t|| report?.data?.createdAt\n\t\t\t\t|| ''\n\t\t},\n\n\t\tformatWhen(value) {\n\t\t\tif (!value) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst num = Number(value)\n\t\t\t\tconst d = Number.isFinite(num) && num > 0 && String(value).length <= 12\n\t\t\t\t\t? new Date(num * 1000)\n\t\t\t\t\t: new Date(value)\n\t\t\t\tif (Number.isNaN(d.getTime())) {\n\t\t\t\t\treturn String(value)\n\t\t\t\t}\n\t\t\t\treturn d.toLocaleDateString(undefined, { dateStyle: 'medium' })\n\t\t\t} catch {\n\t\t\t\treturn String(value)\n\t\t\t}\n\t\t},\n\n\t\topenPicker() {\n\t\t\tthis.pickerOpen = true\n\t\t},\n\n\t\topenCreate() {\n\t\t\tthis.createOpen = true\n\t\t},\n\n\t\tasync onLinkPick(payload) {\n\t\t\tthis.pickerOpen = false\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(this.analyticsEndpoint(), {\n\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\theaders: { ...buildHeaders(), 'Content-Type': 'application/json' },\n\t\t\t\t\tbody: JSON.stringify(payload),\n\t\t\t\t})\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tawait this.fetchReports()\n\t\t\t\t} else if (response.status === 409) {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'This report is already linked.')\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not link report.')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnAnalyticsTab] link failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not link report.')\n\t\t\t}\n\t\t},\n\n\t\tasync onCreatePick(payload) {\n\t\t\tthis.createOpen = false\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(`${this.analyticsEndpoint()}/new`, {\n\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\theaders: { ...buildHeaders(), 'Content-Type': 'application/json' },\n\t\t\t\t\tbody: JSON.stringify(payload),\n\t\t\t\t})\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tawait this.fetchReports()\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not create report.')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnAnalyticsTab] create failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not create report.')\n\t\t\t}\n\t\t},\n\n\t\tasync unlinkReport(report) {\n\t\t\tconst reportId = this.reportKey(report)\n\t\t\tif (!reportId) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(`${this.analyticsEndpoint()}/${reportId}`, {\n\t\t\t\t\tmethod: 'DELETE',\n\t\t\t\t\theaders: buildHeaders(),\n\t\t\t\t})\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tawait this.fetchReports()\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not unlink report.')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnAnalyticsTab] unlink failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not unlink report.')\n\t\t\t}\n\t\t},\n\n\t\tasync fetchReports() {\n\t\t\tif (!this.register || !this.schema || !this.objectId) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\tthis.error = ''\n\t\t\tthis.degraded = ''\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(this.analyticsEndpoint(), { headers: buildHeaders() })\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tconst data = await response.json()\n\t\t\t\t\tthis.reports = data.results || data.items || (Array.isArray(data) ? data : []) || []\n\t\t\t\t} else if (response.status === 503 || response.status === 501) {\n\t\t\t\t\tthis.reports = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.reports = []\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load reports.')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnAnalyticsTab] failed to fetch reports', err)\n\t\t\t\tthis.reports = []\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load reports.')\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-analytics-tab {\n\tpadding: 12px;\n\toverflow-x: hidden;\n}\n\n.cn-analytics-tab__actions {\n\tdisplay: flex;\n\tgap: 8px;\n\tmargin-bottom: 10px;\n\tflex-wrap: wrap;\n}\n\n.cn-analytics-tab__banner {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\tpadding: 8px 10px;\n\tmargin-bottom: 10px;\n\tborder-radius: var(--border-radius);\n\tbackground: var(--color-warning, #e9a40f);\n\tcolor: var(--color-main-background);\n\tfont-size: 0.9em;\n}\n\n.cn-analytics-tab__error {\n\tcolor: var(--color-error);\n\tfont-size: 0.9em;\n\tmargin: 4px 0 8px;\n}\n\n.cn-analytics-tab__empty {\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\tgap: 8px;\n\tpadding: 16px 8px;\n\tcolor: var(--color-text-maxcontrast);\n\ttext-align: center;\n}\n\n.cn-analytics-tab__empty-icon {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-analytics-tab__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n}\n\n/* Chart-type icon avatar — tinted with the descriptor accent (or NC primary). */\n.cn-analytics-tab__row-icon {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\twidth: 40px;\n\theight: 40px;\n\tborder-radius: var(--border-radius-large, 8px);\n\tbackground: var(--color-primary-element-light, var(--color-background-hover));\n\tcolor: var(--cn-iw-accent, var(--color-primary-element, #0082c9));\n}\n\n/* Subtitle row: type badge + data source + modified date, single muted line. */\n.cn-analytics-tab__sub {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tgap: 6px;\n\tfont-size: 11px;\n\tcolor: var(--color-text-maxcontrast);\n\tmin-width: 0;\n}\n\n.cn-analytics-tab__badge {\n\tflex-shrink: 0;\n}\n\n.cn-analytics-tab__subheader {\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n\tmax-width: 100%;\n}\n\n.cn-analytics-tab__when {\n\twhite-space: nowrap;\n}\n\n/* Headline KPI value, right-aligned in the details slot — Analytics style. */\n.cn-analytics-tab__kpi {\n\tfont-weight: 700;\n\tfont-size: 15px;\n\tcolor: var(--color-main-text);\n\twhite-space: nowrap;\n}\n</style>\n"],"names":["_openBlock","_createElementBlock","_createVNode","_createTextVNode","_createBlock","_toDisplayString","_createElementVNode","_resolveDynamicComponent"],"mappings":";;AA6FO,MAAA,UAAA,GAAA,EAAA,KAAI,EAAC,iCAAiC,GAAA;;;AAEhC,EAAA,KAAA,EAAA,0BAAA;AAAA,EAAA,IAAA,EAAA,OAAA;;AAKN,MAAA,UAAA,GAAA,EAAA,KAAK,EAAC,2BAAE,GAAA;;;AAmBU,EAAA,KAAK,EAAC,yBAAyB;AAAC,EAAA,IAAI,EAAC,OAAM;;;;AAK7B,EAAA,KAAM,EAAC,+CAAG;;;;AAgBpC,EAAA,KAAK,EAAC,wBAAwB;;AAWhC,MAAA,UAAA,GAAA,EAAA,KAAK,EAAC,4BAAE,GAAA;AAKR,MAAA,UAAA,GAAA,EAAA,KAAK,EAAC,uBAAQ,GAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;AA/DvB,EAAA,QAAAA,SAAA,EAAA,EAAAC,kBAAA,QAAA;KAEW,KAAA,CAAA,QAAA;yCAGL,iBAAA,EAAA;qBAFoB,CAAA,6BAAA,EAAA,EAAJ,IAAI,EAAA,EAAA,EAAA,CAAA;6BACD,8BAAf,KAAQ,CAAA,QAAA,CAAA,EAAA,CAAA,YAAA;;;uBAgBZ,iBAAA,EAAA;iBAPM,CAAA,mBAAA,EAAA;AALD,QAAA,OAAO,EAAC,WAAO;AAAA,QAAA,OAAA,EAAA,QAAA,CAAA,UAAA;;AACb,QAAA,IAAG,UACb,MAA0B;UAA1BC,WAA0B,CAAA,sBAAA,EAAA,EAAZ,IAAI,EAAE,EAAE,EAAA,CAAA;;yBACZ,MACV;AADU,UAAAC,eAAA,CAAA,GACV,mBAAE,QAAC,CAAA,CAAA,CAAA,eAAA,EAAA,sBAAA,CAAA,CAAA,EAAA,CAAA,YAAA;;;;MAEHD,WAKS,CAAA,mBAAA,EAAA;AALH,QAAA,OAAA,EAAA,SAAA;AAAA,QAAA,OAAA,EAAA,QAAA,CAAA,UAAA;;AACG,QAAA,IAAA,UACT,MAAiB;UAAjBA,WAAiB,CAAA,eAAA,EAAA,EAAV,IAAI,EAAE,EAAE,EAAA,CAAA;;yBACL;AAAA,UAAAC,eAAA,CAAA,sBACR,QAAC,CAAA,CAAA,CAAA,eAAA,EAAA,mBAAA,CAAA,CAAA,EAAA,CAAA,YAAA;;;;;KAKe,KAAO,CAAA,OAAA;sBAAxBC,WAA4B,CAAA,wBAAA,EAAA,EAAA,GAAA,EAAA,CAAA,EAAA,CAAA;SAGhB,KAAK,CAAA,KAAA;0CAEf,CAAA,KAAA,YAAA,EAAAC,eAAA,CADD,KAAG,CAAA,KAAA,CAAA,EAAA,CAAA,YAAA;AAIQ,UAAA,CAAA,KAAA,CAAA,OAAO,CAAC,MAAM,KAAA,CAAA;AAA5B,aAAAL,SAAA,EAAA,EAAAC,kBAAA,CAaI,OAbJ,UAaI,EAAA;yBAZuD,CAAA,mBAAA,EAAA;AAAjD,gBAAA,IAAI,EAAE,EAAE;AAAE,gBAAA,KAAK,EAAC,8BAA8B;;iCAClC,2BAAjB,MAAU,CAAA,UAAA,CAAA,EAAA,CAAA,YAAA;cACdC,WASS,CAAA,mBAAA,EAAA;AARV,gBAAA,OAAO,EAAC,SAAE;AACT,gBAAA,IAAI,EAAE,MAAW,CAAA,eAAA;sBACZ,EAAC,QAAM;mBACV,EAAC,qBAAoB;;AACb,gBAAA,IAAI,UACb,MAAc;kBAAdA,WAAc,CAAA,mBAAA,EAAA,EAAJ,IAAI,EAAA,EAAA,EAAA,CAAA;;iCACL,MACT;AADS,kBAAAC,eAAA,CAAA,GACT,mBAAC,MAAc,CAAA,kBAAA,CAAA,EAAA,CAAA,YAAA;;;;;AAKhB,aAAAH,SAAA,EAAA,EAAAC,kBAAA,OAAA;gCACCA,8CACe,KAAO,CAAA,OAAA,EAAA,CAAjB,MAAM,KAAA;qCADXG;qBAEE,EAAE,QAAM,CAAA,SAAA,CAAA,MAAA,CAAA;AACV,kBAAA,KAAG,EAAC,uBAAuB;kBAC5B,IAAI,EAAE,QAAU,CAAA,WAAA,CAAA,MAAA,CAAA;AAChB,kBAAA,IAAI,EAAE,IAAG;kBACT,IAAI,EAAE,QAAI,CAAA,SAAA,CAAA,MAAA,CAAA;AACX,kBAAA,MAAE,EAAA,QAAA;;;AAES,kBAAA,IAAI,UACd,MAEO;AAFP,oBAAAE,kBAAA,CAEO,QAFP,UAEO,EAAA;AADN,uBAAAN,SAAA,EAAA,EAAAI,WAAA,CAAcG,wBAAA,QAAA,CAAA,UAAA,CAAA,MAAA,CAAA,CAAA,EAAA,EAAA,IAAA,EAAA,EAAA,EAAA,CAAA;;;mCAIf;AAAA,oBAAAD,kBAAA,SAAA;sBACCJ;AACC,wBAAA,KAAK,EAAC,yBAAgB;AACtB,wBAAA,IAAI,EAAC,OAAO;wBACb,OAAA,EAAA,QAAA,CAAA,YAAA,CAAA,MAAA,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"CnAnalyticsTab.vue3.js","sources":["../../../../../src/integrations/builtin/analytics/CnAnalyticsTab.vue?vue&type=template&id=52d520d6&lang.js"],"sourcesContent":["<!--\n CnAnalyticsTab — bespoke sidebar tab for the `analytics` integration\n leaf.\n\n Replaces the generic `CnIntegrationTab` for the `analytics` leaf:\n renders the linked NC Analytics reports / datasets so the tab reads\n like the real Analytics app — each row is an `NcListItem` showing the\n chart-type icon (bar / line / pie / dataset), the bold report name, a\n data-source / type subtitle, an optional headline KPI value, and a\n colour-coded type badge (CnStatusBadge). Clicking a row deep-links\n into the NC Analytics app at `/index.php/apps/analytics/#/r/{id}`\n (`#/r/` is NC Analytics 6.x's report route). Per-row \"Open in\n Analytics\" + \"Unlink\" live in the NcListItem action menu.\n\n Tier-2 surface (this commit):\n - \"Link existing report\" → opens CnAnalyticsReportPicker (modal)\n - \"Create new report\" → opens CnAnalyticsReportCreate (modal)\n - Per-row unlink → DELETE …/analytics/{reportId}\n\n Talks to the OpenRegister Tier-2 analytics-link endpoints\n GET /api/objects/{r}/{s}/{id}/analytics — list\n POST /api/objects/{r}/{s}/{id}/analytics — link existing\n POST /api/objects/{r}/{s}/{id}/analytics/new — create + link\n DELETE /api/objects/{r}/{s}/{id}/analytics/{reportId} — unlink\n GET /api/integrations/analytics/available — picker source\n served by `OCA\\OpenRegister\\Controller\\AnalyticsLinksController`.\n\n The endpoint returns rows shaped:\n { id, reportId, reportTitle, reportType, subheader, value, kpi,\n createdAt, modifiedAt, url, … }\n\n Empty + loading + error + 501/503 unavailable states follow ADR-017\n and AD-23 graceful degradation. All UI strings pass through\n `t('nextcloud-vue', ...)` per ADR-007. Styling uses Nextcloud CSS\n variables only so the nldesign overrides apply transparently\n (ADR-010).\n\n The headline KPI (`value` / `kpi`) is rendered only when the provider\n supplies it — there is no extra HTTP call per row. A type-coded icon\n communicates the chart kind without fetching chart data; inline\n mini-charts remain a follow-up enhancement.\n\n Bespoke-vs-generic rationale: LaunchPad needs to surface \"what kind of\n report is this + its headline number\" without opening it; the generic\n link list strips the chart type, the data source and the KPI.\n-->\n<template>\n\t<div class=\"cn-sidebar-tab cn-analytics-tab\">\n\t\t<!-- 503 unavailable banner (AD-23) -->\n\t\t<div v-if=\"degraded\" class=\"cn-analytics-tab__banner\" role=\"alert\">\n\t\t\t<AlertCircleOutline :size=\"18\" />\n\t\t\t<span>{{ degraded }}</span>\n\t\t</div>\n\n\t\t<div class=\"cn-analytics-tab__actions\">\n\t\t\t<NcButton variant=\"secondary\" @click=\"openPicker\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<LinkVariant :size=\"18\" />\n\t\t\t\t</template>\n\t\t\t\t{{ t('nextcloud-vue', 'Link existing report') }}\n\t\t\t</NcButton>\n\t\t\t<NcButton variant=\"primary\" @click=\"openCreate\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<Plus :size=\"18\" />\n\t\t\t\t</template>\n\t\t\t\t{{ t('nextcloud-vue', 'Create new report') }}\n\t\t\t</NcButton>\n\t\t</div>\n\n\t\t<!-- Loading -->\n\t\t<NcLoadingIcon v-if=\"loading\" />\n\n\t\t<!-- Error -->\n\t\t<div v-else-if=\"error\" class=\"cn-analytics-tab__error\" role=\"alert\">\n\t\t\t{{ error }}\n\t\t</div>\n\n\t\t<!-- Empty -->\n\t\t<div v-else-if=\"reports.length === 0\" class=\"cn-sidebar-tab__empty cn-analytics-tab__empty\">\n\t\t\t<ChartBar :size=\"32\" class=\"cn-analytics-tab__empty-icon\" />\n\t\t\t<p>{{ emptyLabel }}</p>\n\t\t\t<NcButton\n\t\t\t\tvariant=\"primary\"\n\t\t\t\t:href=\"analyticsAppUrl\"\n\t\t\t\ttarget=\"_blank\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<ChartBar :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ openAnalyticsLabel }}\n\t\t\t</NcButton>\n\t\t</div>\n\n\t\t<!-- Report list — NcListItem rows mirror the NC Analytics dataset list -->\n\t\t<ul v-else class=\"cn-analytics-tab__list\">\n\t\t\t<NcListItem\n\t\t\t\tv-for=\"report in reports\"\n\t\t\t\t:key=\"reportKey(report)\"\n\t\t\t\tclass=\"cn-analytics-tab__row\"\n\t\t\t\t:name=\"reportTitle(report)\"\n\t\t\t\t:bold=\"true\"\n\t\t\t\t:href=\"reportUrl(report)\"\n\t\t\t\ttarget=\"_blank\"\n\t\t\t\t:forceDisplayActions=\"true\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<span class=\"cn-analytics-tab__row-icon\">\n\t\t\t\t\t\t<component :is=\"reportIcon(report)\" :size=\"22\" />\n\t\t\t\t\t</span>\n\t\t\t\t</template>\n\t\t\t\t<template #subname>\n\t\t\t\t\t<span class=\"cn-analytics-tab__sub\">\n\t\t\t\t\t\t<CnStatusBadge\n\t\t\t\t\t\t\tclass=\"cn-analytics-tab__badge\"\n\t\t\t\t\t\t\tsize=\"small\"\n\t\t\t\t\t\t\t:variant=\"badgeVariant(report)\"\n\t\t\t\t\t\t\t:label=\"reportTypeLabel(report)\" />\n\t\t\t\t\t\t<span v-if=\"reportSubheader(report)\" class=\"cn-analytics-tab__subheader\">\n\t\t\t\t\t\t\t{{ reportSubheader(report) }}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<span v-if=\"modifiedAt(report)\" class=\"cn-analytics-tab__when\">\n\t\t\t\t\t\t\t· {{ formatWhen(modifiedAt(report)) }}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</span>\n\t\t\t\t</template>\n\t\t\t\t<template v-if=\"reportValue(report)\" #details>\n\t\t\t\t\t<span class=\"cn-analytics-tab__kpi\" :title=\"kpiTitle(report)\">\n\t\t\t\t\t\t{{ reportValue(report) }}\n\t\t\t\t\t</span>\n\t\t\t\t</template>\n\t\t\t\t<template #actions>\n\t\t\t\t\t<NcActionLink\n\t\t\t\t\t\t:closeAfterClick=\"true\"\n\t\t\t\t\t\t:href=\"reportUrl(report)\"\n\t\t\t\t\t\ttarget=\"_blank\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<OpenInNew :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ t('nextcloud-vue', 'Open in Analytics') }}\n\t\t\t\t\t</NcActionLink>\n\t\t\t\t\t<NcActionButton :closeAfterClick=\"true\" @click=\"unlinkReport(report)\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<LinkOff :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ t('nextcloud-vue', 'Unlink report') }}\n\t\t\t\t\t</NcActionButton>\n\t\t\t\t</template>\n\t\t\t</NcListItem>\n\t\t</ul>\n\n\t\t<CnAnalyticsReportPicker\n\t\t\tv-if=\"pickerOpen\"\n\t\t\t:apiBase=\"apiBase\"\n\t\t\t@close=\"pickerOpen = false\"\n\t\t\t@link=\"onLinkPick\" />\n\n\t\t<CnAnalyticsReportCreate\n\t\t\tv-if=\"createOpen\"\n\t\t\t@close=\"createOpen = false\"\n\t\t\t@create=\"onCreatePick\" />\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcActionButton, NcActionLink, NcButton, NcListItem, NcLoadingIcon } from '@nextcloud/vue'\nimport AlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue'\nimport ChartBar from 'vue-material-design-icons/ChartBar.vue'\nimport ChartLine from 'vue-material-design-icons/ChartLine.vue'\nimport ChartPie from 'vue-material-design-icons/ChartPie.vue'\nimport DatabaseOutline from 'vue-material-design-icons/DatabaseOutline.vue'\nimport LinkOff from 'vue-material-design-icons/LinkOff.vue'\nimport LinkVariant from 'vue-material-design-icons/LinkVariant.vue'\nimport OpenInNew from 'vue-material-design-icons/OpenInNew.vue'\nimport Plus from 'vue-material-design-icons/Plus.vue'\nimport TableIcon from 'vue-material-design-icons/Table.vue'\nimport ViewDashboard from 'vue-material-design-icons/ViewDashboard.vue'\nimport CnAnalyticsReportCreate from '../../../components/CnAnalyticsReportCreate/CnAnalyticsReportCreate.vue'\nimport CnAnalyticsReportPicker from '../../../components/CnAnalyticsReportPicker/CnAnalyticsReportPicker.vue'\nimport CnStatusBadge from '../../../components/CnStatusBadge/CnStatusBadge.vue'\nimport { buildHeaders, prefixUrl, safeHref } from '../../../utils/index.js'\n\n/**\n * CnAnalyticsTab — bespoke dataset list for the `analytics` integration.\n *\n * Renders linked NC Analytics reports / datasets as NcListItem rows\n * styled to mirror the Analytics app: chart-type icon, bold report\n * name, data-source / type subtitle, type badge and an optional\n * headline KPI value. Clicking a row opens the report in NC Analytics\n * in a new tab. Tier-2: includes link / create modals and per-row\n * unlink via the row action menu. See the file-level docblock for\n * design notes.\n */\nexport default {\n\tname: 'CnAnalyticsTab',\n\n\tcomponents: {\n\t\tNcActionButton,\n\t\tNcActionLink,\n\t\tNcButton,\n\t\tNcListItem,\n\t\tNcLoadingIcon,\n\t\tCnStatusBadge,\n\t\tAlertCircleOutline,\n\t\tChartBar,\n\t\tChartLine,\n\t\tChartPie,\n\t\tDatabaseOutline,\n\t\tLinkOff,\n\t\tLinkVariant,\n\t\tOpenInNew,\n\t\tPlus,\n\t\tTableIcon,\n\t\tViewDashboard,\n\t\tCnAnalyticsReportPicker,\n\t\tCnAnalyticsReportCreate,\n\t},\n\n\tprops: {\n\t\t/* eslint-disable vue/no-unused-properties -- the integration dispatch binds integrationId on every integration component (see CnIntegrationWidgetGrid), so declaring it keeps it out of $attrs */\n\t\t/** Stable integration id (forwarded from the registry — always `'analytics'`). */\n\t\tintegrationId: { type: String, default: 'analytics' },\n\t\t/* eslint-enable vue/no-unused-properties */\n\t\t/** Parent object id. */\n\t\tobjectId: { type: String, required: true },\n\t\t/** OpenRegister register id (slug or uuid). */\n\t\tregister: { type: String, default: '' },\n\t\t/** OpenRegister schema id (slug or uuid). */\n\t\tschema: { type: String, default: '' },\n\t\t/** Base API URL. */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\t\t/** Pre-translated empty-state label. */\n\t\temptyLabel: { type: String, default: () => t('nextcloud-vue', 'No reports linked yet') },\n\t\t/** Pre-translated \"Open Analytics\" CTA label. */\n\t\topenAnalyticsLabel: { type: String, default: () => t('nextcloud-vue', 'Open Analytics') },\n\t\t/** Pre-translated 503 unavailable banner copy. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Analytics is currently unavailable.') },\n\t\t/** URL of the NC Analytics app entry. */\n\t\tanalyticsAppUrl: { type: String, default: '/index.php/apps/analytics' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\treports: [],\n\t\t\tloading: false,\n\t\t\terror: '',\n\t\t\tdegraded: '',\n\t\t\tpickerOpen: false,\n\t\t\tcreateOpen: false,\n\t\t}\n\t},\n\n\twatch: {\n\t\tobjectId: { immediate: true, handler(id) {\n\t\t\tif (id) {\n\t\t\t\tthis.fetchReports()\n\t\t\t}\n\t\t} },\n\n\t\tregister() {\n\t\t\tthis.fetchReports()\n\t\t},\n\n\t\tschema() {\n\t\t\tthis.fetchReports()\n\t\t},\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/**\n\t\t * Base for the Tier-2 analytics endpoints (list/link/new/destroy).\n\t\t *\n\t\t * @return {string} The endpoint URL.\n\t\t */\n\t\tanalyticsEndpoint() {\n\t\t\treturn prefixUrl(`${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/analytics`)\n\t\t},\n\n\t\treportKey(report) {\n\t\t\treturn (report && (report.reportId ?? report.id ?? report.data?.id)) || ''\n\t\t},\n\n\t\t/**\n\t\t * Resolve the displayable report title, stripping the\n\t\t * `[or:{uuid}]` marker (pre-Tier-2 reports embedded the marker\n\t\t * inside `name`).\n\t\t *\n\t\t * @param {object} report Provider row.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\treportTitle(report) {\n\t\t\tconst raw = (report && (report.reportTitle || report.title || report.name || report.data?.name)) || ''\n\t\t\treturn String(raw).replace(/\\s*\\[or:[^\\]]+\\]\\s*/g, ' ').trim() || t('nextcloud-vue', 'Untitled report')\n\t\t},\n\n\t\t/**\n\t\t * Resolve the secondary \"subheader\" line, stripping any leaked\n\t\t * marker defensively.\n\t\t *\n\t\t * @param {object} report Provider row.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\treportSubheader(report) {\n\t\t\tconst raw = report?.subheader ?? report?.dataSource ?? report?.data?.subheader ?? ''\n\t\t\treturn String(raw).replace(/\\s*\\[or:[^\\]]+\\]\\s*/g, ' ').trim()\n\t\t},\n\n\t\treportType(report) {\n\t\t\tconst raw = report?.reportType ?? report?.type ?? report?.data?.type\n\t\t\tif (raw === null || raw === undefined || raw === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst num = Number(raw)\n\t\t\treturn Number.isFinite(num) ? num : null\n\t\t},\n\n\t\t/**\n\t\t * Resolve a translatable label for the report type. Falls back\n\t\t * to \"Report\" when the type code is unknown. Type codes mirror\n\t\t * NC Analytics 6.x (datasource types).\n\t\t *\n\t\t * @param {object} report Provider row.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\treportTypeLabel(report) {\n\t\t\tconst type = this.reportType(report)\n\t\t\tswitch (type) {\n\t\t\t\tcase 0: return t('nextcloud-vue', 'Group')\n\t\t\t\tcase 1: return t('nextcloud-vue', 'File')\n\t\t\t\tcase 2: return t('nextcloud-vue', 'Database')\n\t\t\t\tcase 3: return t('nextcloud-vue', 'Git')\n\t\t\t\tcase 4: return t('nextcloud-vue', 'External')\n\t\t\t\tcase 6: return t('nextcloud-vue', 'JSON')\n\t\t\t\tdefault: return t('nextcloud-vue', 'Report')\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * CnStatusBadge colour variant for the report type. Mirrors NC\n\t\t * Analytics' datasource-type accenting without inventing colours\n\t\t * (uses the shared variant palette).\n\t\t *\n\t\t * @param {object} report Provider row.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tbadgeVariant(report) {\n\t\t\tconst type = this.reportType(report)\n\t\t\tswitch (type) {\n\t\t\t\tcase 1: return 'primary'\n\t\t\t\tcase 2: return 'success'\n\t\t\t\tcase 4: return 'warning'\n\t\t\t\tcase 6: return 'info'\n\t\t\t\tdefault: return 'default'\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * MDI component for the report type. Tries to express the\n\t\t * \"kind\" without fetching chart data.\n\t\t *\n\t\t * @param {object} report Provider row.\n\t\t *\n\t\t * @return {object} Vue component.\n\t\t */\n\t\treportIcon(report) {\n\t\t\tconst type = this.reportType(report)\n\t\t\tswitch (type) {\n\t\t\t\tcase 1: return TableIcon\n\t\t\t\tcase 0: return ViewDashboard\n\t\t\t\tcase 2: return DatabaseOutline\n\t\t\t\tcase 4: return ChartLine\n\t\t\t\tcase 6: return ChartPie\n\t\t\t\tdefault: return ChartBar\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Headline KPI / value for the report, when the provider supplies\n\t\t * one. Numbers are locale-formatted; strings pass through.\n\t\t *\n\t\t * @param {object} report Provider row.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\treportValue(report) {\n\t\t\tconst raw = report?.value ?? report?.kpi ?? report?.data?.value ?? ''\n\t\t\tif (raw === null || raw === undefined || raw === '') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst num = Number(raw)\n\t\t\tif (Number.isFinite(num) && String(raw).trim() !== '') {\n\t\t\t\ttry {\n\t\t\t\t\treturn num.toLocaleString()\n\t\t\t\t} catch {\n\t\t\t\t\treturn String(num)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn String(raw)\n\t\t},\n\n\t\tkpiTitle(_report) {\n\t\t\treturn t('nextcloud-vue', 'Latest value')\n\t\t},\n\n\t\t// A provider URL is used only when it is a safe scheme; else the app link.\n\t\treportUrl(report) {\n\t\t\tif (report && report.url && safeHref(report.url) !== '#') {\n\t\t\t\treturn report.url\n\t\t\t}\n\t\t\tconst id = this.reportKey(report)\n\t\t\treturn id ? `/index.php/apps/analytics/#/r/${encodeURIComponent(id)}` : this.analyticsAppUrl\n\t\t},\n\n\t\tmodifiedAt(report) {\n\t\t\treturn report?.modifiedAt\n\t\t\t\t|| report?.data?.modifiedAt\n\t\t\t\t|| report?.data?.modified\n\t\t\t\t|| report?.createdAt\n\t\t\t\t|| report?.data?.createdAt\n\t\t\t\t|| ''\n\t\t},\n\n\t\tformatWhen(value) {\n\t\t\tif (!value) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst num = Number(value)\n\t\t\t\tconst d = Number.isFinite(num) && num > 0 && String(value).length <= 12\n\t\t\t\t\t? new Date(num * 1000)\n\t\t\t\t\t: new Date(value)\n\t\t\t\tif (Number.isNaN(d.getTime())) {\n\t\t\t\t\treturn String(value)\n\t\t\t\t}\n\t\t\t\treturn d.toLocaleDateString(undefined, { dateStyle: 'medium' })\n\t\t\t} catch {\n\t\t\t\treturn String(value)\n\t\t\t}\n\t\t},\n\n\t\topenPicker() {\n\t\t\tthis.pickerOpen = true\n\t\t},\n\n\t\topenCreate() {\n\t\t\tthis.createOpen = true\n\t\t},\n\n\t\tasync onLinkPick(payload) {\n\t\t\tthis.pickerOpen = false\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(this.analyticsEndpoint(), {\n\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\theaders: { ...buildHeaders(), 'Content-Type': 'application/json' },\n\t\t\t\t\tbody: JSON.stringify(payload),\n\t\t\t\t})\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tawait this.fetchReports()\n\t\t\t\t} else if (response.status === 409) {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'This report is already linked.')\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not link report.')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnAnalyticsTab] link failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not link report.')\n\t\t\t}\n\t\t},\n\n\t\tasync onCreatePick(payload) {\n\t\t\tthis.createOpen = false\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(`${this.analyticsEndpoint()}/new`, {\n\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\theaders: { ...buildHeaders(), 'Content-Type': 'application/json' },\n\t\t\t\t\tbody: JSON.stringify(payload),\n\t\t\t\t})\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tawait this.fetchReports()\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not create report.')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnAnalyticsTab] create failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not create report.')\n\t\t\t}\n\t\t},\n\n\t\tasync unlinkReport(report) {\n\t\t\tconst reportId = this.reportKey(report)\n\t\t\tif (!reportId) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(`${this.analyticsEndpoint()}/${reportId}`, {\n\t\t\t\t\tmethod: 'DELETE',\n\t\t\t\t\theaders: buildHeaders(),\n\t\t\t\t})\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tawait this.fetchReports()\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not unlink report.')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnAnalyticsTab] unlink failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not unlink report.')\n\t\t\t}\n\t\t},\n\n\t\tasync fetchReports() {\n\t\t\tif (!this.register || !this.schema || !this.objectId) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\tthis.error = ''\n\t\t\tthis.degraded = ''\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(this.analyticsEndpoint(), { headers: buildHeaders() })\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tconst data = await response.json()\n\t\t\t\t\tthis.reports = data.results || data.items || (Array.isArray(data) ? data : []) || []\n\t\t\t\t} else if (response.status === 503 || response.status === 501) {\n\t\t\t\t\tthis.reports = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.reports = []\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load reports.')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnAnalyticsTab] failed to fetch reports', err)\n\t\t\t\tthis.reports = []\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load reports.')\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-analytics-tab {\n\tpadding: 12px;\n\toverflow-x: hidden;\n}\n\n.cn-analytics-tab__actions {\n\tdisplay: flex;\n\tgap: 8px;\n\tmargin-bottom: 10px;\n\tflex-wrap: wrap;\n}\n\n.cn-analytics-tab__banner {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\tpadding: 8px 10px;\n\tmargin-bottom: 10px;\n\tborder-radius: var(--border-radius);\n\tbackground: var(--color-warning, #e9a40f);\n\tcolor: var(--color-main-background);\n\tfont-size: 0.9em;\n}\n\n.cn-analytics-tab__error {\n\tcolor: var(--color-error);\n\tfont-size: 0.9em;\n\tmargin: 4px 0 8px;\n}\n\n.cn-analytics-tab__empty {\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\tgap: 8px;\n\tpadding: 16px 8px;\n\tcolor: var(--color-text-maxcontrast);\n\ttext-align: center;\n}\n\n.cn-analytics-tab__empty-icon {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-analytics-tab__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n}\n\n/* Chart-type icon avatar — tinted with the descriptor accent (or NC primary). */\n.cn-analytics-tab__row-icon {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\twidth: 40px;\n\theight: 40px;\n\tborder-radius: var(--border-radius-large, 8px);\n\tbackground: var(--color-primary-element-light, var(--color-background-hover));\n\tcolor: var(--cn-iw-accent, var(--color-primary-element, #0082c9));\n}\n\n/* Subtitle row: type badge + data source + modified date, single muted line. */\n.cn-analytics-tab__sub {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tgap: 6px;\n\tfont-size: 11px;\n\tcolor: var(--color-text-maxcontrast);\n\tmin-width: 0;\n}\n\n.cn-analytics-tab__badge {\n\tflex-shrink: 0;\n}\n\n.cn-analytics-tab__subheader {\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n\tmax-width: 100%;\n}\n\n.cn-analytics-tab__when {\n\twhite-space: nowrap;\n}\n\n/* Headline KPI value, right-aligned in the details slot — Analytics style. */\n.cn-analytics-tab__kpi {\n\tfont-weight: 700;\n\tfont-size: 15px;\n\tcolor: var(--color-main-text);\n\twhite-space: nowrap;\n}\n</style>\n"],"names":["_openBlock","_createElementBlock","_createVNode","_createTextVNode","_toDisplayString","_createElementVNode","_createBlock","_resolveDynamicComponent"],"mappings":"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"}
@@ -2,6 +2,7 @@ import { translate, translatePlural } from '@nextcloud/l10n';
2
2
  import { NcLoadingIcon } from '@nextcloud/vue';
3
3
  import Bookmark from 'vue-material-design-icons/Bookmark.vue';
4
4
  import __sfc_main from '../../../components/CnDetailCard/CnDetailCard.vue.js';
5
+ import { safeHref } from '../../../utils/safeHref.js';
5
6
  import { prefixUrl, buildHeaders } from '../../../utils/headers.js';
6
7
 
7
8
  const VALID_SURFACES = ['user-dashboard', 'app-dashboard', 'detail-page', 'single-entity'];
@@ -126,6 +127,11 @@ var script = {
126
127
  return bookmark.title || bookmark.url || ''
127
128
  },
128
129
 
130
+ // Bookmark URLs are user-entered (bookmarklets are `javascript:`), so only a safe one is a link.
131
+ hasSafeUrl(bookmark) {
132
+ return safeHref(bookmark.url) !== '#'
133
+ },
134
+
129
135
  chipSubtitle(bookmark) {
130
136
  return bookmark.description || bookmark.url || this.bookmarkTitle(bookmark)
131
137
  },
@@ -1 +1 @@
1
- {"version":3,"file":"CnBookmarksCard.vue2.js","sources":["../../../../../src/integrations/builtin/bookmarks/CnBookmarksCard.vue"],"sourcesContent":["<!--\n CnBookmarksCard — bespoke surface-aware widget for the `bookmarks`\n integration.\n\n Replaces the generic CnIntegrationCard for the `bookmarks` leaf.\n Branches on `surface` per AD-19:\n - user-dashboard / app-dashboard : headline \"N bookmarks\" + the most\n recently added bookmark's favicon + title.\n - detail-page : compact list of linked bookmarks\n (favicon + title, up to COMPACT_LIMIT) with \"view all\" trail-off.\n - single-entity : chip with favicon + title for\n reference-property auto-rendering (referenceType: 'bookmarks').\n\n Pulls rows from the same OR pluggable-integration sub-resource as\n CnBookmarksTab; for `single-entity` the optional `value` prop addresses\n a single bookmark by id (matching CnIntegrationCard's fetchSingle\n contract).\n\n See `openregister/openspec/changes/integration-bookmarks/` for the spec\n delta and ADR-019 (registry mechanism), AD-19 (surface fallback).\n-->\n<template>\n\t<CnDetailCard :title=\"cardTitle\" :icon=\"cardIcon\" :collapsible=\"collapsible\">\n\t\t<NcLoadingIcon v-if=\"loading\" />\n\n\t\t<!-- single-entity surface: chip -->\n\t\t<template v-else-if=\"surface === 'single-entity'\">\n\t\t\t<span v-if=\"entity\" class=\"cn-bookmarks-card__chip\" :title=\"chipSubtitle(entity)\">\n\t\t\t\t<img\n\t\t\t\t\tv-if=\"faviconUrl(entity)\"\n\t\t\t\t\t:src=\"faviconUrl(entity)\"\n\t\t\t\t\talt=\"\"\n\t\t\t\t\tclass=\"cn-bookmarks-card__favicon\"\n\t\t\t\t\t@error=\"onFaviconError(entity)\">\n\t\t\t\t<Bookmark v-else :size=\"14\" />\n\t\t\t\t<a\n\t\t\t\t\t:href=\"entity.url\"\n\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\trel=\"noopener noreferrer\">{{ bookmarkTitle(entity) }}</a>\n\t\t\t</span>\n\t\t\t<span v-else class=\"cn-bookmarks-card__empty\">{{ emptyLabel }}</span>\n\t\t</template>\n\n\t\t<!-- dashboard surfaces: headline + most-recent -->\n\t\t<template v-else-if=\"surface === 'user-dashboard' || surface === 'app-dashboard'\">\n\t\t\t<div v-if=\"degraded\" class=\"cn-bookmarks-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"bookmarks.length === 0\" class=\"cn-bookmarks-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<div v-else class=\"cn-bookmarks-card__headline\">\n\t\t\t\t<div class=\"cn-bookmarks-card__headline-line\">\n\t\t\t\t\t<strong>{{ countHeadline }}</strong>\n\t\t\t\t</div>\n\t\t\t\t<div v-if=\"mostRecent\" class=\"cn-bookmarks-card__headline-recent\">\n\t\t\t\t\t<img\n\t\t\t\t\t\tv-if=\"faviconUrl(mostRecent)\"\n\t\t\t\t\t\t:src=\"faviconUrl(mostRecent)\"\n\t\t\t\t\t\talt=\"\"\n\t\t\t\t\t\tclass=\"cn-bookmarks-card__favicon\"\n\t\t\t\t\t\t@error=\"onFaviconError(mostRecent)\">\n\t\t\t\t\t<Bookmark v-else :size=\"14\" />\n\t\t\t\t\t<a\n\t\t\t\t\t\t:href=\"mostRecent.url\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener noreferrer\">{{ bookmarkTitle(mostRecent) }}</a>\n\t\t\t\t</div>\n\t\t\t\t<div v-if=\"bookmarks.length > 1\" class=\"cn-bookmarks-card__view-all\">\n\t\t\t\t\t<a\n\t\t\t\t\t\t:href=\"bookmarksAppUrl\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener noreferrer\">{{ viewAllLabel }}</a>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</template>\n\n\t\t<!-- detail-page surface: compact list -->\n\t\t<template v-else>\n\t\t\t<div v-if=\"degraded\" class=\"cn-bookmarks-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"bookmarks.length === 0\" class=\"cn-bookmarks-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<ul v-else class=\"cn-bookmarks-card__list\">\n\t\t\t\t<li\n\t\t\t\t\tv-for=\"bookmark in displayedBookmarks\"\n\t\t\t\t\t:key=\"bookmarkKey(bookmark)\"\n\t\t\t\t\tclass=\"cn-bookmarks-card__row\">\n\t\t\t\t\t<div class=\"cn-bookmarks-card__row-icon\">\n\t\t\t\t\t\t<img\n\t\t\t\t\t\t\tv-if=\"faviconUrl(bookmark)\"\n\t\t\t\t\t\t\t:src=\"faviconUrl(bookmark)\"\n\t\t\t\t\t\t\talt=\"\"\n\t\t\t\t\t\t\tclass=\"cn-bookmarks-card__favicon\"\n\t\t\t\t\t\t\t@error=\"onFaviconError(bookmark)\">\n\t\t\t\t\t\t<Bookmark v-else :size=\"14\" />\n\t\t\t\t\t</div>\n\t\t\t\t\t<div class=\"cn-bookmarks-card__row-main\">\n\t\t\t\t\t\t<a\n\t\t\t\t\t\t\t:href=\"bookmark.url\"\n\t\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t\trel=\"noopener noreferrer\"\n\t\t\t\t\t\t\tclass=\"cn-bookmarks-card__title\">{{ bookmarkTitle(bookmark) }}</a>\n\t\t\t\t\t</div>\n\t\t\t\t</li>\n\t\t\t</ul>\n\t\t\t<div v-if=\"bookmarks.length > COMPACT_LIMIT\" class=\"cn-bookmarks-card__view-all\">\n\t\t\t\t<a\n\t\t\t\t\t:href=\"bookmarksAppUrl\"\n\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\trel=\"noopener noreferrer\">{{ viewAllLabel }}</a>\n\t\t\t</div>\n\t\t</template>\n\t</CnDetailCard>\n</template>\n\n<script>\nimport { translatePlural as n, translate as t } from '@nextcloud/l10n'\nimport { NcLoadingIcon } from '@nextcloud/vue'\nimport Bookmark from 'vue-material-design-icons/Bookmark.vue'\nimport CnDetailCard from '../../../components/CnDetailCard/CnDetailCard.vue'\nimport { buildHeaders, prefixUrl } from '../../../utils/index.js'\n\nconst VALID_SURFACES = ['user-dashboard', 'app-dashboard', 'detail-page', 'single-entity']\nconst COMPACT_LIMIT = 5\n\n/**\n * CnBookmarksCard — bespoke surface-aware widget for the `bookmarks`\n * integration.\n *\n * Renders Bookmarks-aware metadata across all four AD-19 surfaces. See\n * the file-level docblock for surface-by-surface behaviour.\n */\nexport default {\n\tname: 'CnBookmarksCard',\n\n\tcomponents: { CnDetailCard, NcLoadingIcon, Bookmark },\n\n\tprops: {\n\t\t/** Stable integration id (forwarded from the registry — always `'bookmarks'`). */\n\t\tintegrationId: { type: String, default: 'bookmarks' },\n\t\t/** OpenRegister register id (slug or uuid). */\n\t\tregister: { type: String, required: true },\n\t\t/** OpenRegister schema id (slug or uuid). */\n\t\tschema: { type: String, required: true },\n\t\t/** Parent object id. */\n\t\tobjectId: { type: String, required: true },\n\t\t/** Rendering surface (AD-19). */\n\t\tsurface: {\n\t\t\ttype: String,\n\t\t\tdefault: 'detail-page',\n\t\t\tvalidator: (s) => VALID_SURFACES.includes(s),\n\t\t},\n\n\t\t/** Optional single-entity reference (bookmark id). */\n\t\tvalue: { type: [String, Number], default: '' },\n\t\t/** Pre-translated card title. */\n\t\ttitle: { type: String, default: () => t('nextcloud-vue', 'Bookmarks') },\n\t\t/** Optional Material Design Icon component. */\n\t\ticon: { type: Object, default: () => Bookmark },\n\t\t/** Base API URL. */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\t\t/** Whether the card body is collapsible. */\n\t\tcollapsible: { type: Boolean, default: true },\n\t\t/** Pre-translated empty-state label. */\n\t\temptyLabel: { type: String, default: () => t('nextcloud-vue', 'No bookmarks linked yet') },\n\t\t/** Pre-translated unavailable label. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Bookmarks is currently unavailable.') },\n\t\t/** Pre-translated \"view all\" trail-off link label. */\n\t\tviewAllLabel: { type: String, default: () => t('nextcloud-vue', 'View all in Bookmarks') },\n\t\t/** URL of the NC Bookmarks app entry. */\n\t\tbookmarksAppUrl: { type: String, default: '/index.php/apps/bookmarks' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tCOMPACT_LIMIT,\n\t\t\tbookmarks: [],\n\t\t\tentity: null,\n\t\t\tloading: false,\n\t\t\tdegraded: '',\n\t\t\tbrokenFavicons: {},\n\t\t}\n\t},\n\n\tcomputed: {\n\t\tcardTitle() {\n\t\t\treturn this.title || this.integrationId\n\t\t},\n\n\t\tcardIcon() {\n\t\t\treturn this.icon\n\t\t},\n\n\t\tdisplayedBookmarks() {\n\t\t\treturn this.bookmarks.slice(0, COMPACT_LIMIT)\n\t\t},\n\n\t\tcountHeadline() {\n\t\t\tconst total = this.bookmarks.length\n\t\t\treturn n('nextcloud-vue', '{count} bookmark', '{count} bookmarks', total, { count: total })\n\t\t},\n\n\t\tmostRecent() {\n\t\t\tif (this.bookmarks.length === 0) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst sorted = [...this.bookmarks].sort((a, b) => {\n\t\t\t\tconst ta = Number(a.added ?? a.addedAt ?? 0)\n\t\t\t\tconst tb = Number(b.added ?? b.addedAt ?? 0)\n\t\t\t\treturn tb - ta\n\t\t\t})\n\t\t\treturn sorted[0]\n\t\t},\n\t},\n\n\twatch: {\n\t\tobjectId: { immediate: true, handler() {\n\t\t\tthis.fetch()\n\t\t} },\n\n\t\tsurface() {\n\t\t\tthis.fetch()\n\t\t},\n\n\t\tvalue() {\n\t\t\tif (this.surface === 'single-entity') {\n\t\t\t\tthis.fetchSingle()\n\t\t\t}\n\t\t},\n\t},\n\n\tmethods: {\n\t\tbaseUrl() {\n\t\t\treturn `${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/integrations/${this.integrationId}`\n\t\t},\n\n\t\tbookmarkKey(bookmark) {\n\t\t\treturn bookmark.id ?? bookmark.url ?? ''\n\t\t},\n\n\t\tbookmarkTitle(bookmark) {\n\t\t\treturn bookmark.title || bookmark.url || ''\n\t\t},\n\n\t\tchipSubtitle(bookmark) {\n\t\t\treturn bookmark.description || bookmark.url || this.bookmarkTitle(bookmark)\n\t\t},\n\n\t\tfaviconUrl(bookmark) {\n\t\t\tif (!bookmark || this.brokenFavicons[this.bookmarkKey(bookmark)]) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst url = new URL(bookmark.url)\n\t\t\t\treturn `${url.origin}/favicon.ico`\n\t\t\t} catch {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t},\n\n\t\tonFaviconError(bookmark) {\n\t\t\tthis.brokenFavicons[this.bookmarkKey(bookmark)] = true\n\t\t},\n\n\t\tfetch() {\n\t\t\tif (this.surface === 'single-entity') {\n\t\t\t\tthis.fetchSingle()\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.fetchList()\n\t\t},\n\n\t\tasync fetchList() {\n\t\t\tif (!this.register || !this.schema || !this.objectId) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\tthis.degraded = ''\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(this.baseUrl()), { headers: buildHeaders() })\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tconst data = await response.json()\n\t\t\t\t\tthis.bookmarks = data.results || data.items || (Array.isArray(data) ? data : []) || []\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.bookmarks = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.bookmarks = []\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnBookmarksCard] failed to fetch bookmarks', err)\n\t\t\t\tthis.bookmarks = []\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\tasync fetchSingle() {\n\t\t\tif (this.value === '' || this.value === undefined || this.value === null || !this.register || !this.schema || !this.objectId) {\n\t\t\t\tthis.entity = null\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\tthis.degraded = ''\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(`${this.baseUrl()}/${encodeURIComponent(this.value)}`), { headers: buildHeaders() })\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tthis.entity = await response.json()\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.entity = null\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.entity = null\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnBookmarksCard] failed to fetch single bookmark', err)\n\t\t\t\tthis.entity = null\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-bookmarks-card__empty {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n\tpadding: 8px 0;\n}\n\n.cn-bookmarks-card__headline {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n}\n\n.cn-bookmarks-card__headline-line {\n\tfont-size: 1.1em;\n\tcolor: var(--color-main-text);\n}\n\n.cn-bookmarks-card__headline-recent {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 6px;\n\tfont-size: 0.9em;\n\tcolor: var(--color-text-maxcontrast);\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-bookmarks-card__headline-recent a {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-bookmarks-card__headline-recent a:hover {\n\ttext-decoration: underline;\n}\n\n.cn-bookmarks-card__chip {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 6px;\n\tpadding: 4px 8px;\n\tborder-radius: 12px;\n\tbackground: var(--color-background-hover);\n\tfont-size: 0.9em;\n\tmax-width: 100%;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n/* stylelint-disable-next-line no-descending-specificity */\n.cn-bookmarks-card__chip a {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-bookmarks-card__chip a:hover {\n\ttext-decoration: underline;\n}\n\n.cn-bookmarks-card__favicon {\n\twidth: 14px;\n\theight: 14px;\n\tobject-fit: contain;\n\tflex-shrink: 0;\n}\n\n.cn-bookmarks-card__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n.cn-bookmarks-card__row {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\tpadding: 6px 0;\n\tborder-bottom: 1px solid var(--color-border);\n}\n\n.cn-bookmarks-card__row:last-child {\n\tborder-bottom: none;\n}\n\n.cn-bookmarks-card__row-icon {\n\tflex-shrink: 0;\n\tcolor: var(--color-text-maxcontrast);\n\twidth: 16px;\n\theight: 16px;\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n}\n\n.cn-bookmarks-card__row-main {\n\tflex: 1;\n\tmin-width: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n}\n\n.cn-bookmarks-card__title {\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\na.cn-bookmarks-card__title:hover {\n\ttext-decoration: underline;\n}\n\n.cn-bookmarks-card__view-all {\n\tmargin-top: 6px;\n\tfont-size: 0.8em;\n}\n\n/* stylelint-disable-next-line no-descending-specificity */\n.cn-bookmarks-card__view-all a {\n\tcolor: var(--color-primary-element);\n\ttext-decoration: none;\n}\n\n.cn-bookmarks-card__view-all a:hover {\n\ttext-decoration: underline;\n}\n</style>\n"],"names":["CnDetailCard","t","n"],"mappings":"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"}
1
+ {"version":3,"file":"CnBookmarksCard.vue2.js","sources":["../../../../../src/integrations/builtin/bookmarks/CnBookmarksCard.vue"],"sourcesContent":["<!--\n CnBookmarksCard — bespoke surface-aware widget for the `bookmarks`\n integration.\n\n Replaces the generic CnIntegrationCard for the `bookmarks` leaf.\n Branches on `surface` per AD-19:\n - user-dashboard / app-dashboard : headline \"N bookmarks\" + the most\n recently added bookmark's favicon + title.\n - detail-page : compact list of linked bookmarks\n (favicon + title, up to COMPACT_LIMIT) with \"view all\" trail-off.\n - single-entity : chip with favicon + title for\n reference-property auto-rendering (referenceType: 'bookmarks').\n\n Pulls rows from the same OR pluggable-integration sub-resource as\n CnBookmarksTab; for `single-entity` the optional `value` prop addresses\n a single bookmark by id (matching CnIntegrationCard's fetchSingle\n contract).\n\n See `openregister/openspec/changes/integration-bookmarks/` for the spec\n delta and ADR-019 (registry mechanism), AD-19 (surface fallback).\n-->\n<template>\n\t<CnDetailCard :title=\"cardTitle\" :icon=\"cardIcon\" :collapsible=\"collapsible\">\n\t\t<NcLoadingIcon v-if=\"loading\" />\n\n\t\t<!-- single-entity surface: chip -->\n\t\t<template v-else-if=\"surface === 'single-entity'\">\n\t\t\t<span v-if=\"entity\" class=\"cn-bookmarks-card__chip\" :title=\"chipSubtitle(entity)\">\n\t\t\t\t<img\n\t\t\t\t\tv-if=\"faviconUrl(entity)\"\n\t\t\t\t\t:src=\"faviconUrl(entity)\"\n\t\t\t\t\talt=\"\"\n\t\t\t\t\tclass=\"cn-bookmarks-card__favicon\"\n\t\t\t\t\t@error=\"onFaviconError(entity)\">\n\t\t\t\t<Bookmark v-else :size=\"14\" />\n\t\t\t\t<a\n\t\t\t\t\tv-if=\"hasSafeUrl(entity)\"\n\t\t\t\t\t:href=\"entity.url\"\n\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\trel=\"noopener noreferrer\">{{ bookmarkTitle(entity) }}</a>\n\t\t\t\t<span v-else>{{ bookmarkTitle(entity) }}</span>\n\t\t\t</span>\n\t\t\t<span v-else class=\"cn-bookmarks-card__empty\">{{ emptyLabel }}</span>\n\t\t</template>\n\n\t\t<!-- dashboard surfaces: headline + most-recent -->\n\t\t<template v-else-if=\"surface === 'user-dashboard' || surface === 'app-dashboard'\">\n\t\t\t<div v-if=\"degraded\" class=\"cn-bookmarks-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"bookmarks.length === 0\" class=\"cn-bookmarks-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<div v-else class=\"cn-bookmarks-card__headline\">\n\t\t\t\t<div class=\"cn-bookmarks-card__headline-line\">\n\t\t\t\t\t<strong>{{ countHeadline }}</strong>\n\t\t\t\t</div>\n\t\t\t\t<div v-if=\"mostRecent\" class=\"cn-bookmarks-card__headline-recent\">\n\t\t\t\t\t<img\n\t\t\t\t\t\tv-if=\"faviconUrl(mostRecent)\"\n\t\t\t\t\t\t:src=\"faviconUrl(mostRecent)\"\n\t\t\t\t\t\talt=\"\"\n\t\t\t\t\t\tclass=\"cn-bookmarks-card__favicon\"\n\t\t\t\t\t\t@error=\"onFaviconError(mostRecent)\">\n\t\t\t\t\t<Bookmark v-else :size=\"14\" />\n\t\t\t\t\t<a\n\t\t\t\t\t\tv-if=\"hasSafeUrl(mostRecent)\"\n\t\t\t\t\t\t:href=\"mostRecent.url\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener noreferrer\">{{ bookmarkTitle(mostRecent) }}</a>\n\t\t\t\t\t<span v-else>{{ bookmarkTitle(mostRecent) }}</span>\n\t\t\t\t</div>\n\t\t\t\t<div v-if=\"bookmarks.length > 1\" class=\"cn-bookmarks-card__view-all\">\n\t\t\t\t\t<a\n\t\t\t\t\t\t:href=\"bookmarksAppUrl\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener noreferrer\">{{ viewAllLabel }}</a>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</template>\n\n\t\t<!-- detail-page surface: compact list -->\n\t\t<template v-else>\n\t\t\t<div v-if=\"degraded\" class=\"cn-bookmarks-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"bookmarks.length === 0\" class=\"cn-bookmarks-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<ul v-else class=\"cn-bookmarks-card__list\">\n\t\t\t\t<li\n\t\t\t\t\tv-for=\"bookmark in displayedBookmarks\"\n\t\t\t\t\t:key=\"bookmarkKey(bookmark)\"\n\t\t\t\t\tclass=\"cn-bookmarks-card__row\">\n\t\t\t\t\t<div class=\"cn-bookmarks-card__row-icon\">\n\t\t\t\t\t\t<img\n\t\t\t\t\t\t\tv-if=\"faviconUrl(bookmark)\"\n\t\t\t\t\t\t\t:src=\"faviconUrl(bookmark)\"\n\t\t\t\t\t\t\talt=\"\"\n\t\t\t\t\t\t\tclass=\"cn-bookmarks-card__favicon\"\n\t\t\t\t\t\t\t@error=\"onFaviconError(bookmark)\">\n\t\t\t\t\t\t<Bookmark v-else :size=\"14\" />\n\t\t\t\t\t</div>\n\t\t\t\t\t<div class=\"cn-bookmarks-card__row-main\">\n\t\t\t\t\t\t<a\n\t\t\t\t\t\t\tv-if=\"hasSafeUrl(bookmark)\"\n\t\t\t\t\t\t\t:href=\"bookmark.url\"\n\t\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t\trel=\"noopener noreferrer\"\n\t\t\t\t\t\t\tclass=\"cn-bookmarks-card__title\">{{ bookmarkTitle(bookmark) }}</a>\n\t\t\t\t\t\t<span v-else class=\"cn-bookmarks-card__title\">{{ bookmarkTitle(bookmark) }}</span>\n\t\t\t\t\t</div>\n\t\t\t\t</li>\n\t\t\t</ul>\n\t\t\t<div v-if=\"bookmarks.length > COMPACT_LIMIT\" class=\"cn-bookmarks-card__view-all\">\n\t\t\t\t<a\n\t\t\t\t\t:href=\"bookmarksAppUrl\"\n\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\trel=\"noopener noreferrer\">{{ viewAllLabel }}</a>\n\t\t\t</div>\n\t\t</template>\n\t</CnDetailCard>\n</template>\n\n<script>\nimport { translatePlural as n, translate as t } from '@nextcloud/l10n'\nimport { NcLoadingIcon } from '@nextcloud/vue'\nimport Bookmark from 'vue-material-design-icons/Bookmark.vue'\nimport CnDetailCard from '../../../components/CnDetailCard/CnDetailCard.vue'\nimport { buildHeaders, prefixUrl, safeHref } from '../../../utils/index.js'\n\nconst VALID_SURFACES = ['user-dashboard', 'app-dashboard', 'detail-page', 'single-entity']\nconst COMPACT_LIMIT = 5\n\n/**\n * CnBookmarksCard — bespoke surface-aware widget for the `bookmarks`\n * integration.\n *\n * Renders Bookmarks-aware metadata across all four AD-19 surfaces. See\n * the file-level docblock for surface-by-surface behaviour.\n */\nexport default {\n\tname: 'CnBookmarksCard',\n\n\tcomponents: { CnDetailCard, NcLoadingIcon, Bookmark },\n\n\tprops: {\n\t\t/** Stable integration id (forwarded from the registry — always `'bookmarks'`). */\n\t\tintegrationId: { type: String, default: 'bookmarks' },\n\t\t/** OpenRegister register id (slug or uuid). */\n\t\tregister: { type: String, required: true },\n\t\t/** OpenRegister schema id (slug or uuid). */\n\t\tschema: { type: String, required: true },\n\t\t/** Parent object id. */\n\t\tobjectId: { type: String, required: true },\n\t\t/** Rendering surface (AD-19). */\n\t\tsurface: {\n\t\t\ttype: String,\n\t\t\tdefault: 'detail-page',\n\t\t\tvalidator: (s) => VALID_SURFACES.includes(s),\n\t\t},\n\n\t\t/** Optional single-entity reference (bookmark id). */\n\t\tvalue: { type: [String, Number], default: '' },\n\t\t/** Pre-translated card title. */\n\t\ttitle: { type: String, default: () => t('nextcloud-vue', 'Bookmarks') },\n\t\t/** Optional Material Design Icon component. */\n\t\ticon: { type: Object, default: () => Bookmark },\n\t\t/** Base API URL. */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\t\t/** Whether the card body is collapsible. */\n\t\tcollapsible: { type: Boolean, default: true },\n\t\t/** Pre-translated empty-state label. */\n\t\temptyLabel: { type: String, default: () => t('nextcloud-vue', 'No bookmarks linked yet') },\n\t\t/** Pre-translated unavailable label. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Bookmarks is currently unavailable.') },\n\t\t/** Pre-translated \"view all\" trail-off link label. */\n\t\tviewAllLabel: { type: String, default: () => t('nextcloud-vue', 'View all in Bookmarks') },\n\t\t/** URL of the NC Bookmarks app entry. */\n\t\tbookmarksAppUrl: { type: String, default: '/index.php/apps/bookmarks' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tCOMPACT_LIMIT,\n\t\t\tbookmarks: [],\n\t\t\tentity: null,\n\t\t\tloading: false,\n\t\t\tdegraded: '',\n\t\t\tbrokenFavicons: {},\n\t\t}\n\t},\n\n\tcomputed: {\n\t\tcardTitle() {\n\t\t\treturn this.title || this.integrationId\n\t\t},\n\n\t\tcardIcon() {\n\t\t\treturn this.icon\n\t\t},\n\n\t\tdisplayedBookmarks() {\n\t\t\treturn this.bookmarks.slice(0, COMPACT_LIMIT)\n\t\t},\n\n\t\tcountHeadline() {\n\t\t\tconst total = this.bookmarks.length\n\t\t\treturn n('nextcloud-vue', '{count} bookmark', '{count} bookmarks', total, { count: total })\n\t\t},\n\n\t\tmostRecent() {\n\t\t\tif (this.bookmarks.length === 0) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst sorted = [...this.bookmarks].sort((a, b) => {\n\t\t\t\tconst ta = Number(a.added ?? a.addedAt ?? 0)\n\t\t\t\tconst tb = Number(b.added ?? b.addedAt ?? 0)\n\t\t\t\treturn tb - ta\n\t\t\t})\n\t\t\treturn sorted[0]\n\t\t},\n\t},\n\n\twatch: {\n\t\tobjectId: { immediate: true, handler() {\n\t\t\tthis.fetch()\n\t\t} },\n\n\t\tsurface() {\n\t\t\tthis.fetch()\n\t\t},\n\n\t\tvalue() {\n\t\t\tif (this.surface === 'single-entity') {\n\t\t\t\tthis.fetchSingle()\n\t\t\t}\n\t\t},\n\t},\n\n\tmethods: {\n\t\tbaseUrl() {\n\t\t\treturn `${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/integrations/${this.integrationId}`\n\t\t},\n\n\t\tbookmarkKey(bookmark) {\n\t\t\treturn bookmark.id ?? bookmark.url ?? ''\n\t\t},\n\n\t\tbookmarkTitle(bookmark) {\n\t\t\treturn bookmark.title || bookmark.url || ''\n\t\t},\n\n\t\t// Bookmark URLs are user-entered (bookmarklets are `javascript:`), so only a safe one is a link.\n\t\thasSafeUrl(bookmark) {\n\t\t\treturn safeHref(bookmark.url) !== '#'\n\t\t},\n\n\t\tchipSubtitle(bookmark) {\n\t\t\treturn bookmark.description || bookmark.url || this.bookmarkTitle(bookmark)\n\t\t},\n\n\t\tfaviconUrl(bookmark) {\n\t\t\tif (!bookmark || this.brokenFavicons[this.bookmarkKey(bookmark)]) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst url = new URL(bookmark.url)\n\t\t\t\treturn `${url.origin}/favicon.ico`\n\t\t\t} catch {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t},\n\n\t\tonFaviconError(bookmark) {\n\t\t\tthis.brokenFavicons[this.bookmarkKey(bookmark)] = true\n\t\t},\n\n\t\tfetch() {\n\t\t\tif (this.surface === 'single-entity') {\n\t\t\t\tthis.fetchSingle()\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.fetchList()\n\t\t},\n\n\t\tasync fetchList() {\n\t\t\tif (!this.register || !this.schema || !this.objectId) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\tthis.degraded = ''\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(this.baseUrl()), { headers: buildHeaders() })\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tconst data = await response.json()\n\t\t\t\t\tthis.bookmarks = data.results || data.items || (Array.isArray(data) ? data : []) || []\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.bookmarks = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.bookmarks = []\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnBookmarksCard] failed to fetch bookmarks', err)\n\t\t\t\tthis.bookmarks = []\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\tasync fetchSingle() {\n\t\t\tif (this.value === '' || this.value === undefined || this.value === null || !this.register || !this.schema || !this.objectId) {\n\t\t\t\tthis.entity = null\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\tthis.degraded = ''\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(`${this.baseUrl()}/${encodeURIComponent(this.value)}`), { headers: buildHeaders() })\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tthis.entity = await response.json()\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.entity = null\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.entity = null\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnBookmarksCard] failed to fetch single bookmark', err)\n\t\t\t\tthis.entity = null\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-bookmarks-card__empty {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n\tpadding: 8px 0;\n}\n\n.cn-bookmarks-card__headline {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n}\n\n.cn-bookmarks-card__headline-line {\n\tfont-size: 1.1em;\n\tcolor: var(--color-main-text);\n}\n\n.cn-bookmarks-card__headline-recent {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 6px;\n\tfont-size: 0.9em;\n\tcolor: var(--color-text-maxcontrast);\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-bookmarks-card__headline-recent a {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-bookmarks-card__headline-recent a:hover {\n\ttext-decoration: underline;\n}\n\n.cn-bookmarks-card__chip {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 6px;\n\tpadding: 4px 8px;\n\tborder-radius: 12px;\n\tbackground: var(--color-background-hover);\n\tfont-size: 0.9em;\n\tmax-width: 100%;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n/* stylelint-disable-next-line no-descending-specificity */\n.cn-bookmarks-card__chip a {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-bookmarks-card__chip a:hover {\n\ttext-decoration: underline;\n}\n\n.cn-bookmarks-card__favicon {\n\twidth: 14px;\n\theight: 14px;\n\tobject-fit: contain;\n\tflex-shrink: 0;\n}\n\n.cn-bookmarks-card__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n.cn-bookmarks-card__row {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\tpadding: 6px 0;\n\tborder-bottom: 1px solid var(--color-border);\n}\n\n.cn-bookmarks-card__row:last-child {\n\tborder-bottom: none;\n}\n\n.cn-bookmarks-card__row-icon {\n\tflex-shrink: 0;\n\tcolor: var(--color-text-maxcontrast);\n\twidth: 16px;\n\theight: 16px;\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n}\n\n.cn-bookmarks-card__row-main {\n\tflex: 1;\n\tmin-width: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n}\n\n.cn-bookmarks-card__title {\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\na.cn-bookmarks-card__title:hover {\n\ttext-decoration: underline;\n}\n\n.cn-bookmarks-card__view-all {\n\tmargin-top: 6px;\n\tfont-size: 0.8em;\n}\n\n/* stylelint-disable-next-line no-descending-specificity */\n.cn-bookmarks-card__view-all a {\n\tcolor: var(--color-primary-element);\n\ttext-decoration: none;\n}\n\n.cn-bookmarks-card__view-all a:hover {\n\ttext-decoration: underline;\n}\n</style>\n"],"names":["CnDetailCard","t","n"],"mappings":"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"}