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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (314) hide show
  1. package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js +12 -2
  2. package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js.map +1 -1
  3. package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js +1 -2
  4. package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js.map +1 -1
  5. package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue2.js.map +1 -1
  6. package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue3.js +0 -3
  7. package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue3.js.map +1 -1
  8. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js.map +1 -1
  9. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js +1 -2
  10. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js.map +1 -1
  11. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +21 -1
  12. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
  13. package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js +1 -1
  14. package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
  15. package/dist/esm/components/CnBoardView/CnBoardView.vue2.js +12 -5
  16. package/dist/esm/components/CnBoardView/CnBoardView.vue2.js.map +1 -1
  17. package/dist/esm/components/CnBoardView/CnBoardView.vue3.js +2 -1
  18. package/dist/esm/components/CnBoardView/CnBoardView.vue3.js.map +1 -1
  19. package/dist/esm/components/CnCardGrid/CnCardGrid.vue2.js +9 -1
  20. package/dist/esm/components/CnCardGrid/CnCardGrid.vue2.js.map +1 -1
  21. package/dist/esm/components/CnCardGrid/CnCardGrid.vue3.js.map +1 -1
  22. package/dist/esm/components/CnContextMenu/CnContextMenu.vue2.js +6 -1
  23. package/dist/esm/components/CnContextMenu/CnContextMenu.vue2.js.map +1 -1
  24. package/dist/esm/components/CnContextMenu/CnContextMenu.vue3.js.map +1 -1
  25. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +116 -15
  26. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
  27. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +27 -14
  28. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
  29. package/dist/esm/components/CnDateAxisView/CnDateAxisView.vue2.js +13 -6
  30. package/dist/esm/components/CnDateAxisView/CnDateAxisView.vue2.js.map +1 -1
  31. package/dist/esm/components/CnDateAxisView/CnDateAxisView.vue3.js +2 -0
  32. package/dist/esm/components/CnDateAxisView/CnDateAxisView.vue3.js.map +1 -1
  33. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +29 -1
  34. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  35. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  36. package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue2.js.map +1 -1
  37. package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue3.js +1 -2
  38. package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue3.js.map +1 -1
  39. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +3 -1
  40. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
  41. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +17 -17
  42. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
  43. package/dist/esm/components/CnFilesWidget/CnFilesWidget.vue2.js +4 -21
  44. package/dist/esm/components/CnFilesWidget/CnFilesWidget.vue2.js.map +1 -1
  45. package/dist/esm/components/CnFilesWidget/CnFilesWidget.vue3.js.map +1 -1
  46. package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue2.js +0 -13
  47. package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue2.js.map +1 -1
  48. package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue3.js +8 -8
  49. package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue3.js.map +1 -1
  50. package/dist/esm/components/CnFlowsPage/CnFlowsPage.vue2.js.map +1 -1
  51. package/dist/esm/components/CnFlowsPage/CnFlowsPage.vue3.js +3 -2
  52. package/dist/esm/components/CnFlowsPage/CnFlowsPage.vue3.js.map +1 -1
  53. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +67 -13
  54. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  55. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +15 -8
  56. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  57. package/dist/esm/components/CnIndexPage/manifestActionDispatch.js +31 -11
  58. package/dist/esm/components/CnIndexPage/manifestActionDispatch.js.map +1 -1
  59. package/dist/esm/components/CnIntegrationWidget/CnIntegrationWidgetEmpty.vue2.js.map +1 -1
  60. package/dist/esm/components/CnIntegrationWidget/CnIntegrationWidgetEmpty.vue3.js +1 -2
  61. package/dist/esm/components/CnIntegrationWidget/CnIntegrationWidgetEmpty.vue3.js.map +1 -1
  62. package/dist/esm/components/CnInteractionFormWidget/CnInteractionFormWidget.vue2.js.map +1 -1
  63. package/dist/esm/components/CnInteractionFormWidget/CnInteractionFormWidget.vue3.js +0 -1
  64. package/dist/esm/components/CnInteractionFormWidget/CnInteractionFormWidget.vue3.js.map +1 -1
  65. package/dist/esm/components/CnLinkButtonWidget/CnLinkButtonWidget.vue2.js +2 -17
  66. package/dist/esm/components/CnLinkButtonWidget/CnLinkButtonWidget.vue2.js.map +1 -1
  67. package/dist/esm/components/CnLinkButtonWidget/CnLinkButtonWidget.vue3.js.map +1 -1
  68. package/dist/esm/components/CnLogsPage/CnLogsPage.vue2.js +35 -11
  69. package/dist/esm/components/CnLogsPage/CnLogsPage.vue2.js.map +1 -1
  70. package/dist/esm/components/CnLogsPage/CnLogsPage.vue3.js +3 -2
  71. package/dist/esm/components/CnLogsPage/CnLogsPage.vue3.js.map +1 -1
  72. package/dist/esm/components/CnObjectCard/CnObjectCard.vue2.js +23 -5
  73. package/dist/esm/components/CnObjectCard/CnObjectCard.vue2.js.map +1 -1
  74. package/dist/esm/components/CnObjectCard/CnObjectCard.vue3.js +1 -1
  75. package/dist/esm/components/CnObjectCard/CnObjectCard.vue3.js.map +1 -1
  76. package/dist/esm/components/CnObjectList/CnObjectList.vue2.js +19 -3
  77. package/dist/esm/components/CnObjectList/CnObjectList.vue2.js.map +1 -1
  78. package/dist/esm/components/CnObjectList/CnObjectList.vue3.js.map +1 -1
  79. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +45 -15
  80. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
  81. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js +4 -2
  82. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
  83. package/dist/esm/components/CnObjectRow/CnObjectRow.vue2.js +24 -6
  84. package/dist/esm/components/CnObjectRow/CnObjectRow.vue2.js.map +1 -1
  85. package/dist/esm/components/CnObjectRow/CnObjectRow.vue3.js +1 -1
  86. package/dist/esm/components/CnObjectRow/CnObjectRow.vue3.js.map +1 -1
  87. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
  88. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +0 -1
  89. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
  90. package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue2.js.map +1 -1
  91. package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue3.js +4 -2
  92. package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue3.js.map +1 -1
  93. package/dist/esm/components/CnRowActions/CnRowActions.vue2.js +7 -2
  94. package/dist/esm/components/CnRowActions/CnRowActions.vue2.js.map +1 -1
  95. package/dist/esm/components/CnRowActions/CnRowActions.vue3.js.map +1 -1
  96. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js +49 -5
  97. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js.map +1 -1
  98. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js +1 -1
  99. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js.map +1 -1
  100. package/dist/esm/components/CnSpendAnalyticsWidget/CnSpendAnalyticsWidget.vue2.js +5 -3
  101. package/dist/esm/components/CnSpendAnalyticsWidget/CnSpendAnalyticsWidget.vue2.js.map +1 -1
  102. package/dist/esm/components/CnSpendAnalyticsWidget/CnSpendAnalyticsWidget.vue3.js.map +1 -1
  103. package/dist/esm/components/CnTabs/CnTabs.vue2.js.map +1 -1
  104. package/dist/esm/components/CnTabs/CnTabs.vue3.js.map +1 -1
  105. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue2.js +3 -1
  106. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue2.js.map +1 -1
  107. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue3.js +1 -0
  108. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue3.js.map +1 -1
  109. package/dist/esm/integrations/builtin/analytics/CnAnalyticsCard.vue2.js +3 -1
  110. package/dist/esm/integrations/builtin/analytics/CnAnalyticsCard.vue2.js.map +1 -1
  111. package/dist/esm/integrations/builtin/analytics/CnAnalyticsCard.vue3.js.map +1 -1
  112. package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue2.js +3 -1
  113. package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue2.js.map +1 -1
  114. package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue3.js +2 -4
  115. package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue3.js.map +1 -1
  116. package/dist/esm/integrations/builtin/bookmarks/CnBookmarksCard.vue2.js +6 -0
  117. package/dist/esm/integrations/builtin/bookmarks/CnBookmarksCard.vue2.js.map +1 -1
  118. package/dist/esm/integrations/builtin/bookmarks/CnBookmarksCard.vue3.js +68 -53
  119. package/dist/esm/integrations/builtin/bookmarks/CnBookmarksCard.vue3.js.map +1 -1
  120. package/dist/esm/integrations/builtin/bookmarks/CnBookmarksTab.vue2.js +17 -0
  121. package/dist/esm/integrations/builtin/bookmarks/CnBookmarksTab.vue2.js.map +1 -1
  122. package/dist/esm/integrations/builtin/bookmarks/CnBookmarksTab.vue3.js +21 -19
  123. package/dist/esm/integrations/builtin/bookmarks/CnBookmarksTab.vue3.js.map +1 -1
  124. package/dist/esm/integrations/builtin/collectives/CnCollectivesCard.vue2.js +3 -1
  125. package/dist/esm/integrations/builtin/collectives/CnCollectivesCard.vue2.js.map +1 -1
  126. package/dist/esm/integrations/builtin/collectives/CnCollectivesCard.vue3.js.map +1 -1
  127. package/dist/esm/integrations/builtin/collectives/CnCollectivesTab.vue2.js +3 -1
  128. package/dist/esm/integrations/builtin/collectives/CnCollectivesTab.vue2.js.map +1 -1
  129. package/dist/esm/integrations/builtin/collectives/CnCollectivesTab.vue3.js +2 -4
  130. package/dist/esm/integrations/builtin/collectives/CnCollectivesTab.vue3.js.map +1 -1
  131. package/dist/esm/integrations/builtin/cospend/CnCospendCard.vue2.js +2 -1
  132. package/dist/esm/integrations/builtin/cospend/CnCospendCard.vue2.js.map +1 -1
  133. package/dist/esm/integrations/builtin/cospend/CnCospendCard.vue3.js.map +1 -1
  134. package/dist/esm/integrations/builtin/cospend/CnCospendTab.vue2.js +2 -1
  135. package/dist/esm/integrations/builtin/cospend/CnCospendTab.vue2.js.map +1 -1
  136. package/dist/esm/integrations/builtin/cospend/CnCospendTab.vue3.js +1 -2
  137. package/dist/esm/integrations/builtin/cospend/CnCospendTab.vue3.js.map +1 -1
  138. package/dist/esm/integrations/builtin/deck/CnDeckCard.vue2.js +2 -1
  139. package/dist/esm/integrations/builtin/deck/CnDeckCard.vue2.js.map +1 -1
  140. package/dist/esm/integrations/builtin/deck/CnDeckCard.vue3.js.map +1 -1
  141. package/dist/esm/integrations/builtin/deck/CnDeckTab.vue2.js +2 -1
  142. package/dist/esm/integrations/builtin/deck/CnDeckTab.vue2.js.map +1 -1
  143. package/dist/esm/integrations/builtin/deck/CnDeckTab.vue3.js +1 -2
  144. package/dist/esm/integrations/builtin/deck/CnDeckTab.vue3.js.map +1 -1
  145. package/dist/esm/integrations/builtin/email/CnEmailCard.vue2.js.map +1 -1
  146. package/dist/esm/integrations/builtin/email/CnEmailCard.vue3.js.map +1 -1
  147. package/dist/esm/integrations/builtin/email/CnEmailTab.vue2.js.map +1 -1
  148. package/dist/esm/integrations/builtin/email/CnEmailTab.vue3.js +1 -2
  149. package/dist/esm/integrations/builtin/email/CnEmailTab.vue3.js.map +1 -1
  150. package/dist/esm/integrations/builtin/flow/CnFlowCard.vue2.js +2 -1
  151. package/dist/esm/integrations/builtin/flow/CnFlowCard.vue2.js.map +1 -1
  152. package/dist/esm/integrations/builtin/flow/CnFlowCard.vue3.js.map +1 -1
  153. package/dist/esm/integrations/builtin/flow/CnFlowTab.vue2.js +2 -1
  154. package/dist/esm/integrations/builtin/flow/CnFlowTab.vue2.js.map +1 -1
  155. package/dist/esm/integrations/builtin/flow/CnFlowTab.vue3.js +1 -2
  156. package/dist/esm/integrations/builtin/flow/CnFlowTab.vue3.js.map +1 -1
  157. package/dist/esm/integrations/builtin/forms/CnFormsCard.vue2.js +3 -1
  158. package/dist/esm/integrations/builtin/forms/CnFormsCard.vue2.js.map +1 -1
  159. package/dist/esm/integrations/builtin/forms/CnFormsCard.vue3.js.map +1 -1
  160. package/dist/esm/integrations/builtin/forms/CnFormsTab.vue2.js +3 -1
  161. package/dist/esm/integrations/builtin/forms/CnFormsTab.vue2.js.map +1 -1
  162. package/dist/esm/integrations/builtin/forms/CnFormsTab.vue3.js +2 -4
  163. package/dist/esm/integrations/builtin/forms/CnFormsTab.vue3.js.map +1 -1
  164. package/dist/esm/integrations/builtin/maps/CnMapsCard.vue2.js +3 -1
  165. package/dist/esm/integrations/builtin/maps/CnMapsCard.vue2.js.map +1 -1
  166. package/dist/esm/integrations/builtin/maps/CnMapsCard.vue3.js.map +1 -1
  167. package/dist/esm/integrations/builtin/maps/CnMapsTab.vue2.js +3 -1
  168. package/dist/esm/integrations/builtin/maps/CnMapsTab.vue2.js.map +1 -1
  169. package/dist/esm/integrations/builtin/maps/CnMapsTab.vue3.js +2 -4
  170. package/dist/esm/integrations/builtin/maps/CnMapsTab.vue3.js.map +1 -1
  171. package/dist/esm/integrations/builtin/openproject/CnOpenprojectCard.vue2.js +3 -1
  172. package/dist/esm/integrations/builtin/openproject/CnOpenprojectCard.vue2.js.map +1 -1
  173. package/dist/esm/integrations/builtin/openproject/CnOpenprojectCard.vue3.js.map +1 -1
  174. package/dist/esm/integrations/builtin/openproject/CnOpenprojectTab.vue2.js +3 -1
  175. package/dist/esm/integrations/builtin/openproject/CnOpenprojectTab.vue2.js.map +1 -1
  176. package/dist/esm/integrations/builtin/openproject/CnOpenprojectTab.vue3.js +2 -4
  177. package/dist/esm/integrations/builtin/openproject/CnOpenprojectTab.vue3.js.map +1 -1
  178. package/dist/esm/integrations/builtin/photos/CnPhotosCard.vue2.js +2 -1
  179. package/dist/esm/integrations/builtin/photos/CnPhotosCard.vue2.js.map +1 -1
  180. package/dist/esm/integrations/builtin/photos/CnPhotosCard.vue3.js.map +1 -1
  181. package/dist/esm/integrations/builtin/photos/CnPhotosTab.vue2.js +2 -1
  182. package/dist/esm/integrations/builtin/photos/CnPhotosTab.vue2.js.map +1 -1
  183. package/dist/esm/integrations/builtin/photos/CnPhotosTab.vue3.js +1 -2
  184. package/dist/esm/integrations/builtin/photos/CnPhotosTab.vue3.js.map +1 -1
  185. package/dist/esm/integrations/builtin/polls/CnPollsCard.vue2.js +2 -1
  186. package/dist/esm/integrations/builtin/polls/CnPollsCard.vue2.js.map +1 -1
  187. package/dist/esm/integrations/builtin/polls/CnPollsCard.vue3.js.map +1 -1
  188. package/dist/esm/integrations/builtin/polls/CnPollsTab.vue2.js +2 -1
  189. package/dist/esm/integrations/builtin/polls/CnPollsTab.vue2.js.map +1 -1
  190. package/dist/esm/integrations/builtin/polls/CnPollsTab.vue3.js +1 -2
  191. package/dist/esm/integrations/builtin/polls/CnPollsTab.vue3.js.map +1 -1
  192. package/dist/esm/integrations/builtin/shares/CnSharesTab.vue2.js.map +1 -1
  193. package/dist/esm/integrations/builtin/shares/CnSharesTab.vue3.js +3 -6
  194. package/dist/esm/integrations/builtin/shares/CnSharesTab.vue3.js.map +1 -1
  195. package/dist/esm/integrations/builtin/talk/CnTalkCard.vue2.js +3 -1
  196. package/dist/esm/integrations/builtin/talk/CnTalkCard.vue2.js.map +1 -1
  197. package/dist/esm/integrations/builtin/talk/CnTalkCard.vue3.js.map +1 -1
  198. package/dist/esm/integrations/builtin/talk/CnTalkTab.vue2.js +3 -1
  199. package/dist/esm/integrations/builtin/talk/CnTalkTab.vue2.js.map +1 -1
  200. package/dist/esm/integrations/builtin/talk/CnTalkTab.vue3.js +2 -4
  201. package/dist/esm/integrations/builtin/talk/CnTalkTab.vue3.js.map +1 -1
  202. package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerCard.vue2.js +2 -1
  203. package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerCard.vue2.js.map +1 -1
  204. package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerCard.vue3.js.map +1 -1
  205. package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerTab.vue2.js +2 -1
  206. package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerTab.vue2.js.map +1 -1
  207. package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerTab.vue3.js +1 -2
  208. package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerTab.vue3.js.map +1 -1
  209. package/dist/esm/integrations/builtin/xwiki/CnXwikiCard.vue2.js +4 -1
  210. package/dist/esm/integrations/builtin/xwiki/CnXwikiCard.vue2.js.map +1 -1
  211. package/dist/esm/integrations/builtin/xwiki/CnXwikiCard.vue3.js.map +1 -1
  212. package/dist/esm/integrations/builtin/xwiki/CnXwikiTab.vue2.js +6 -3
  213. package/dist/esm/integrations/builtin/xwiki/CnXwikiTab.vue2.js.map +1 -1
  214. package/dist/esm/integrations/builtin/xwiki/CnXwikiTab.vue3.js +2 -4
  215. package/dist/esm/integrations/builtin/xwiki/CnXwikiTab.vue3.js.map +1 -1
  216. package/dist/esm/l10n/en.json.js +2 -0
  217. package/dist/esm/l10n/en.json.js.map +1 -1
  218. package/dist/esm/l10n/nl.json.js +2 -0
  219. package/dist/esm/l10n/nl.json.js.map +1 -1
  220. package/dist/esm/nextcloud-vue.css +78 -7
  221. package/dist/esm/utils/actionLink.js +5 -0
  222. package/dist/esm/utils/actionLink.js.map +1 -1
  223. package/dist/esm/utils/actionsDispatcher.js +16 -1
  224. package/dist/esm/utils/actionsDispatcher.js.map +1 -1
  225. package/dist/esm/utils/linkNavigation.js +6 -3
  226. package/dist/esm/utils/linkNavigation.js.map +1 -1
  227. package/dist/esm/utils/rowAuxClick.js +30 -5
  228. package/dist/esm/utils/rowAuxClick.js.map +1 -1
  229. package/dist/esm/utils/safeHref.js +18 -3
  230. package/dist/esm/utils/safeHref.js.map +1 -1
  231. package/dist/esm/utils/validateManifestV2.compiled.js +2 -2
  232. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  233. package/dist/nextcloud-vue.cjs.js +885 -393
  234. package/dist/nextcloud-vue.cjs.js.map +2 -2
  235. package/dist/nextcloud-vue.css +78 -7
  236. package/l10n/en.json +2 -0
  237. package/l10n/nl.json +2 -0
  238. package/package.json +1 -1
  239. package/src/components/CnActionsBar/CnActionsBar.vue +14 -5
  240. package/src/components/CnActionsMenu/CnActionsMenu.vue +0 -3
  241. package/src/components/CnAdminSettingsShell/CnAdminSettingsShell.vue +1 -2
  242. package/src/components/CnAppRoot/CnAppRoot.vue +31 -2
  243. package/src/components/CnBoardView/CnBoardView.vue +13 -5
  244. package/src/components/CnCardGrid/CnCardGrid.vue +9 -1
  245. package/src/components/CnContextMenu/CnContextMenu.vue +6 -1
  246. package/src/components/CnDataTable/CnDataTable.md +1 -1
  247. package/src/components/CnDataTable/CnDataTable.vue +131 -17
  248. package/src/components/CnDateAxisView/CnDateAxisView.vue +15 -6
  249. package/src/components/CnDetailPage/CnDetailPage.md +1 -1
  250. package/src/components/CnDetailPage/CnDetailPage.vue +29 -1
  251. package/src/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue +1 -2
  252. package/src/components/CnFilesBrowser/CnFilesBrowser.vue +5 -3
  253. package/src/components/CnFilesWidget/CnFilesWidget.vue +4 -21
  254. package/src/components/CnFlowRunsWidget/CnFlowRunsWidget.vue +8 -21
  255. package/src/components/CnFlowsPage/CnFlowsPage.vue +2 -1
  256. package/src/components/CnIndexPage/CnIndexPage.vue +75 -14
  257. package/src/components/CnIndexPage/manifestActionDispatch.js +31 -11
  258. package/src/components/CnIntegrationWidget/CnIntegrationWidgetEmpty.vue +1 -2
  259. package/src/components/CnInteractionFormWidget/CnInteractionFormWidget.vue +0 -1
  260. package/src/components/CnLinkButtonWidget/CnLinkButtonWidget.vue +2 -17
  261. package/src/components/CnLogsPage/CnLogsPage.vue +37 -12
  262. package/src/components/CnObjectCard/CnObjectCard.vue +24 -6
  263. package/src/components/CnObjectList/CnObjectList.vue +19 -3
  264. package/src/components/CnObjectListWidget/CnObjectListWidget.vue +47 -15
  265. package/src/components/CnObjectRow/CnObjectRow.vue +25 -7
  266. package/src/components/CnObjectSidebar/CnFilesTab.vue +0 -1
  267. package/src/components/CnPageRenderer/CnPageRenderer.vue +2 -0
  268. package/src/components/CnRowActions/CnRowActions.vue +7 -2
  269. package/src/components/CnSetupWizard/CnSetupWizard.vue +57 -6
  270. package/src/components/CnSpendAnalyticsWidget/CnSpendAnalyticsWidget.vue +5 -3
  271. package/src/components/CnTabs/CnTabs.vue +7 -3
  272. package/src/components/CnTasksWidget/CnTasksWidget.vue +4 -1
  273. package/src/css/table.css +26 -0
  274. package/src/integrations/builtin/analytics/CnAnalyticsCard.vue +3 -2
  275. package/src/integrations/builtin/analytics/CnAnalyticsTab.vue +5 -6
  276. package/src/integrations/builtin/bookmarks/CnBookmarksCard.vue +12 -1
  277. package/src/integrations/builtin/bookmarks/CnBookmarksTab.vue +23 -6
  278. package/src/integrations/builtin/bookmarks/__tests__/CnBookmarksTab.spec.js +7 -2
  279. package/src/integrations/builtin/collectives/CnCollectivesCard.vue +3 -2
  280. package/src/integrations/builtin/collectives/CnCollectivesTab.vue +5 -6
  281. package/src/integrations/builtin/cospend/CnCospendCard.vue +2 -2
  282. package/src/integrations/builtin/cospend/CnCospendTab.vue +3 -4
  283. package/src/integrations/builtin/deck/CnDeckCard.vue +2 -2
  284. package/src/integrations/builtin/deck/CnDeckTab.vue +3 -4
  285. package/src/integrations/builtin/email/CnEmailCard.vue +6 -2
  286. package/src/integrations/builtin/email/CnEmailTab.vue +7 -4
  287. package/src/integrations/builtin/flow/CnFlowCard.vue +2 -2
  288. package/src/integrations/builtin/flow/CnFlowTab.vue +3 -4
  289. package/src/integrations/builtin/forms/CnFormsCard.vue +3 -2
  290. package/src/integrations/builtin/forms/CnFormsTab.vue +5 -6
  291. package/src/integrations/builtin/maps/CnMapsCard.vue +3 -2
  292. package/src/integrations/builtin/maps/CnMapsTab.vue +5 -6
  293. package/src/integrations/builtin/maps/__tests__/CnMapsTab.spec.js +9 -0
  294. package/src/integrations/builtin/openproject/CnOpenprojectCard.vue +3 -2
  295. package/src/integrations/builtin/openproject/CnOpenprojectTab.vue +5 -6
  296. package/src/integrations/builtin/photos/CnPhotosCard.vue +2 -2
  297. package/src/integrations/builtin/photos/CnPhotosTab.vue +3 -4
  298. package/src/integrations/builtin/polls/CnPollsCard.vue +2 -2
  299. package/src/integrations/builtin/polls/CnPollsTab.vue +3 -4
  300. package/src/integrations/builtin/shares/CnSharesTab.vue +3 -6
  301. package/src/integrations/builtin/talk/CnTalkCard.vue +3 -2
  302. package/src/integrations/builtin/talk/CnTalkTab.vue +5 -6
  303. package/src/integrations/builtin/time-tracker/CnTimeTrackerCard.vue +2 -2
  304. package/src/integrations/builtin/time-tracker/CnTimeTrackerTab.vue +3 -4
  305. package/src/integrations/builtin/xwiki/CnXwikiCard.vue +4 -2
  306. package/src/integrations/builtin/xwiki/CnXwikiTab.vue +7 -7
  307. package/src/integrations/builtin/xwiki/__tests__/CnXwikiTab.spec.js +9 -0
  308. package/src/schemas/app-manifest-v2.schema.json +2 -1
  309. package/src/utils/actionLink.js +5 -0
  310. package/src/utils/actionsDispatcher.js +15 -0
  311. package/src/utils/linkNavigation.js +6 -3
  312. package/src/utils/rowAuxClick.js +29 -4
  313. package/src/utils/safeHref.js +18 -3
  314. package/src/utils/validateManifestV2.compiled.js +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"CnCospendTab.vue2.js","sources":["../../../../../src/integrations/builtin/cospend/CnCospendTab.vue"],"sourcesContent":["<!--\n CnCospendTab — bespoke sidebar tab for the `cospend` integration.\n\n Replaces the generic CnIntegrationTab for the `cospend` leaf: renders\n the NC Cospend rows linked to the parent OR object as a Cospend-style\n list of projects and bills.\n Cospend exposes BOTH project rows AND bill rows in the same payload\n (per wave 2.3 design — see `CospendProvider.php`); each row carries\n a `type` discriminator (`'project'` | `'bill'`) which this tab shows\n as a `[Project]` / `[Bill]` status badge alongside the title.\n\n Rows render via NcListItem to mirror real Cospend:\n - project: name + [Project] badge + currency, payer (when present)\n - bill: title + [Bill] badge + payer · date subline, prominent\n right-aligned amount with currency\n A per-currency total footer summarises the linked spend, matching\n Cospend's project balance row.\n\n Clicking a project row deep-links to\n /index.php/apps/cospend/p/{id}\n Bills inherit the same URL (Cospend doesn't have a stable per-bill\n permalink — the project page lists its bills); a row's own `url`\n field wins if the provider already constructed one.\n\n Talks to the OpenRegister pluggable-integration sub-resource\n `/api/objects/{register}/{schema}/{objectId}/integrations/cospend`\n served by `OCA\\OpenRegister\\Service\\Integration\\Providers\\CospendProvider`\n (link-table strategy — marker `[or:{uuid}]` in `cospend_projects.name`).\n\n Surface behaviour (per ADR-017 graceful degradation):\n - Empty state with \"Open Costs\" CTA when no linked rows.\n - Loading spinner during fetch.\n - 503 \"currently unavailable\" banner when Cospend is down.\n - Generic error label when fetch throws.\n\n Bespoke-vs-generic rationale: the generic tab renders a flat link\n list which loses Cospend's primary signals — *whether the row is a\n project or a bill*, the amount, and the currency. Surfacing them\n inline lets case handlers triage spending at a glance without\n context-switching into Cospend.\n\n See `openregister/openspec/changes/integration-cospend/` for the\n spec delta, ADR-019 (registry mechanism), ADR-022 (sidebar tab\n contract), `openregister#1324` for provider follow-ups.\n-->\n<template>\n\t<div class=\"cn-sidebar-tab cn-cospend-tab\">\n\t\t<div v-if=\"degraded\" class=\"cn-cospend-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 v-if=\"!degraded\" class=\"cn-cospend-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 project') }}\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 project') }}\n\t\t\t</NcButton>\n\t\t</div>\n\n\t\t<NcLoadingIcon v-if=\"loading\" />\n\t\t<div v-else-if=\"error\" class=\"cn-cospend-tab__error\" role=\"alert\">\n\t\t\t{{ error }}\n\t\t</div>\n\t\t<div v-else-if=\"rows.length === 0\" class=\"cn-sidebar-tab__empty cn-cospend-tab__empty\">\n\t\t\t<CurrencyEur :size=\"32\" class=\"cn-cospend-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=\"cospendAppUrl\"\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<CurrencyEur :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ openCospendLabel }}\n\t\t\t</NcButton>\n\t\t</div>\n\t\t<template v-else>\n\t\t\t<ul class=\"cn-cospend-tab__list\">\n\t\t\t\t<NcListItem\n\t\t\t\t\tv-for=\"row in rows\"\n\t\t\t\t\t:key=\"rowKey(row)\"\n\t\t\t\t\tclass=\"cn-cospend-tab__row\"\n\t\t\t\t\t:class=\"rowClass(row)\"\n\t\t\t\t\t:href=\"rowUrl(row)\"\n\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t:forceDisplayActions=\"true\">\n\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t<span class=\"cn-cospend-tab__row-icon\" :class=\"iconClass(row)\">\n\t\t\t\t\t\t\t<CashMultiple v-if=\"rowType(row) === 'bill'\" :size=\"20\" />\n\t\t\t\t\t\t\t<FolderOutline v-else :size=\"20\" />\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</template>\n\t\t\t\t\t<template #name>\n\t\t\t\t\t\t<span class=\"cn-cospend-tab__title\">{{ rowTitle(row) }}</span>\n\t\t\t\t\t</template>\n\t\t\t\t\t<template #subname>\n\t\t\t\t\t\t<span class=\"cn-cospend-tab__subline\">\n\t\t\t\t\t\t\t<CnStatusBadge\n\t\t\t\t\t\t\t\tclass=\"cn-cospend-tab__type-chip\"\n\t\t\t\t\t\t\t\t:class=\"typeChipClass(row)\"\n\t\t\t\t\t\t\t\tsize=\"small\"\n\t\t\t\t\t\t\t\t:variant=\"typeVariant(row)\"\n\t\t\t\t\t\t\t\t:label=\"typeLabel(row)\" />\n\t\t\t\t\t\t\t<span v-if=\"sublineText(row)\" class=\"cn-cospend-tab__subline-text\">\n\t\t\t\t\t\t\t\t{{ sublineText(row) }}\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</template>\n\t\t\t\t\t<template #indicator>\n\t\t\t\t\t\t<span v-if=\"amountLabel(row)\" class=\"cn-cospend-tab__amount\" :class=\"amountClass(row)\">\n\t\t\t\t\t\t\t{{ amountLabel(row) }}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</template>\n\t\t\t\t\t<template #actions>\n\t\t\t\t\t\t<NcActionButton\n\t\t\t\t\t\t\tv-if=\"entryIdOf(row)\"\n\t\t\t\t\t\t\t:closeAfterClick=\"true\"\n\t\t\t\t\t\t\t@click=\"unlinkEntry(row)\">\n\t\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t\t<LinkOff :size=\"20\" />\n\t\t\t\t\t\t\t</template>\n\t\t\t\t\t\t\t{{ t('nextcloud-vue', 'Unlink entry') }}\n\t\t\t\t\t\t</NcActionButton>\n\t\t\t\t\t</template>\n\t\t\t\t</NcListItem>\n\t\t\t</ul>\n\n\t\t\t<div v-if=\"totals.length > 0\" class=\"cn-cospend-tab__totals\">\n\t\t\t\t<span class=\"cn-cospend-tab__totals-label\">{{ t('nextcloud-vue', 'Total') }}</span>\n\t\t\t\t<ul class=\"cn-cospend-tab__totals-list\">\n\t\t\t\t\t<li\n\t\t\t\t\t\tv-for=\"bucket in totals\"\n\t\t\t\t\t\t:key=\"bucket.currency\"\n\t\t\t\t\t\tclass=\"cn-cospend-tab__totals-row\">\n\t\t\t\t\t\t<span class=\"cn-cospend-tab__totals-currency\">{{ bucket.currency }}</span>\n\t\t\t\t\t\t<span class=\"cn-cospend-tab__totals-amount\">{{ bucket.amountLabel }}</span>\n\t\t\t\t\t</li>\n\t\t\t\t</ul>\n\t\t\t</div>\n\t\t</template>\n\n\t\t<CnCospendPicker\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<CnCospendCreate\n\t\t\tv-if=\"createOpen\"\n\t\t\t:apiBase=\"apiBase\"\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, NcButton, NcListItem, NcLoadingIcon } from '@nextcloud/vue'\nimport AlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue'\nimport CashMultiple from 'vue-material-design-icons/CashMultiple.vue'\nimport CurrencyEur from 'vue-material-design-icons/CurrencyEur.vue'\nimport FolderOutline from 'vue-material-design-icons/FolderOutline.vue'\nimport LinkOff from 'vue-material-design-icons/LinkOff.vue'\nimport LinkVariant from 'vue-material-design-icons/LinkVariant.vue'\nimport Plus from 'vue-material-design-icons/Plus.vue'\nimport CnCospendCreate from '../../../components/CnCospendCreate/CnCospendCreate.vue'\nimport CnCospendPicker from '../../../components/CnCospendPicker/CnCospendPicker.vue'\nimport CnStatusBadge from '../../../components/CnStatusBadge/CnStatusBadge.vue'\nimport { buildHeaders, prefixUrl } from '../../../utils/index.js'\n\n/**\n * CnCospendTab — bespoke sidebar tab for the `cospend` integration.\n *\n * Renders linked Cospend projects + bills with a type badge, amount,\n * currency, payer, date and a per-currency total footer. See the\n * file-level docblock for surface behaviour.\n */\nexport default {\n\tname: 'CnCospendTab',\n\n\tcomponents: {\n\t\tNcActionButton,\n\t\tNcButton,\n\t\tNcListItem,\n\t\tNcLoadingIcon,\n\t\tAlertCircleOutline,\n\t\tCashMultiple,\n\t\tCurrencyEur,\n\t\tFolderOutline,\n\t\tLinkOff,\n\t\tLinkVariant,\n\t\tPlus,\n\t\tCnStatusBadge,\n\t\tCnCospendPicker,\n\t\tCnCospendCreate,\n\t},\n\n\tprops: {\n\t\t/** Stable integration id (forwarded from the registry — always `'cospend'`). */\n\t\tintegrationId: { type: String, default: 'cospend' },\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 costs linked yet') },\n\t\t/** Pre-translated label for the \"Open Costs\" CTA. */\n\t\topenCospendLabel: { type: String, default: () => t('nextcloud-vue', 'Open Costs') },\n\t\t/** Pre-translated banner when Cospend is unavailable. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Costs is currently unavailable.') },\n\t\t/** URL of the NC Cospend app entry. */\n\t\tcospendAppUrl: { type: String, default: '/index.php/apps/cospend' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\trows: [],\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\tcomputed: {\n\t\t/**\n\t\t * Per-currency totals of every row carrying an amount, mirroring\n\t\t * the balance row Cospend renders under a project.\n\t\t *\n\t\t * @return {Array<{currency: string, amountLabel: string}>}\n\t\t */\n\t\ttotals() {\n\t\t\tconst groups = new Map()\n\t\t\tfor (const row of this.rows) {\n\t\t\t\tconst amount = this.amountRaw(row)\n\t\t\t\tif (amount === null) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tconst currency = this.currencyOf(row) || ''\n\t\t\t\tconst existing = groups.get(currency) || { currency, total: 0 }\n\t\t\t\texisting.total += amount\n\t\t\t\tgroups.set(currency, existing)\n\t\t\t}\n\t\t\treturn Array.from(groups.values()).map((b) => {\n\t\t\t\tconst code = b.currency || t('nextcloud-vue', 'Unspecified')\n\t\t\t\treturn {\n\t\t\t\t\tcurrency: code,\n\t\t\t\t\tamountLabel: b.currency ? `${b.total.toFixed(2)} ${b.currency}` : b.total.toFixed(2),\n\t\t\t\t}\n\t\t\t})\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.fetchRows()\n\t\t\t}\n\t\t} },\n\n\t\tregister() {\n\t\t\tthis.fetchRows()\n\t\t},\n\n\t\tschema() {\n\t\t\tthis.fetchRows()\n\t\t},\n\t},\n\n\tmethods: {\n\t\tbaseUrl() {\n\t\t\treturn prefixUrl(`${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/integrations/${this.integrationId}`)\n\t\t},\n\n\t\t/**\n\t\t * Base for the Tier-2 cospend endpoints (list/link/new/destroy).\n\t\t *\n\t\t * @return {string} The endpoint URL.\n\t\t */\n\t\tcospendEndpoint() {\n\t\t\treturn prefixUrl(`${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/cospend`)\n\t\t},\n\n\t\trowKey(row) {\n\t\t\treturn row.id ?? row.uuid ?? ''\n\t\t},\n\n\t\t/**\n\t\t * Resolve the link-row id for a Tier-2 row (used for unlink).\n\t\t * Legacy marker rows have no `entryId` so the unlink button hides.\n\t\t *\n\t\t * @param {object} row Provider/link row.\n\t\t *\n\t\t * @return {(number|string)} The link row id, or '' when absent.\n\t\t */\n\t\tentryIdOf(row) {\n\t\t\tconst d = (row && typeof row.data === 'object' && row.data !== null) ? row.data : {}\n\t\t\treturn row.entryId ?? d.id ?? ''\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.cospendEndpoint(), {\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.fetchRows()\n\t\t\t\t} else if (response.status === 409) {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'This entry is already linked.')\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not link entry.')\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('[CnCospendTab] link failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not link entry.')\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.cospendEndpoint()}/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.fetchRows()\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not create project.')\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('[CnCospendTab] create failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not create project.')\n\t\t\t}\n\t\t},\n\n\t\tasync unlinkEntry(row) {\n\t\t\tconst id = this.entryIdOf(row)\n\t\t\tif (!id) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(`${this.cospendEndpoint()}/${id}`, {\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.fetchRows()\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not unlink entry.')\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('[CnCospendTab] unlink failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not unlink entry.')\n\t\t\t}\n\t\t},\n\n\t\trowTitle(row) {\n\t\t\treturn row.title ?? row.name ?? this.rowKey(row)\n\t\t},\n\n\t\trowUrl(row) {\n\t\t\tif (row.url) {\n\t\t\t\treturn row.url\n\t\t\t}\n\t\t\tconst projectId = row.projectId ?? row.project_id ?? row.data?.projectid ?? row.data?.project_id ?? null\n\t\t\tif (projectId !== null && projectId !== '') {\n\t\t\t\treturn `/index.php/apps/cospend/p/${projectId}`\n\t\t\t}\n\t\t\tconst id = this.rowKey(row)\n\t\t\tif (id !== '') {\n\t\t\t\treturn `/index.php/apps/cospend/p/${id}`\n\t\t\t}\n\t\t\treturn this.cospendAppUrl\n\t\t},\n\n\t\t/**\n\t\t * Discriminator. Defaults to 'project' when the provider doesn't\n\t\t * surface a `type` field (the current shipping CospendProvider\n\t\t * only returns projects); when wave-2.3 ships bills the chip\n\t\t * label flips accordingly.\n\t\t *\n\t\t * @param {object} row Provider row.\n\t\t * @return {'project'|'bill'} Row type.\n\t\t */\n\t\trowType(row) {\n\t\t\tconst t = String(row.type ?? row.kind ?? '').toLowerCase()\n\t\t\tif (t === 'bill' || t === 'expense') {\n\t\t\t\treturn 'bill'\n\t\t\t}\n\t\t\treturn 'project'\n\t\t},\n\n\t\ttypeLabel(row) {\n\t\t\treturn this.rowType(row) === 'bill'\n\t\t\t\t? t('nextcloud-vue', 'Bill')\n\t\t\t\t: t('nextcloud-vue', 'Project')\n\t\t},\n\n\t\ttypeChipClass(row) {\n\t\t\treturn this.rowType(row) === 'bill'\n\t\t\t\t? 'cn-cospend-tab__type-chip--bill'\n\t\t\t\t: 'cn-cospend-tab__type-chip--project'\n\t\t},\n\n\t\ttypeVariant(row) {\n\t\t\treturn this.rowType(row) === 'bill' ? 'warning' : 'primary'\n\t\t},\n\n\t\trowClass(row) {\n\t\t\treturn this.rowType(row) === 'bill'\n\t\t\t\t? 'cn-cospend-tab__row--bill'\n\t\t\t\t: 'cn-cospend-tab__row--project'\n\t\t},\n\n\t\ticonClass(row) {\n\t\t\treturn this.rowType(row) === 'bill'\n\t\t\t\t? 'cn-cospend-tab__row-icon--bill'\n\t\t\t\t: 'cn-cospend-tab__row-icon--project'\n\t\t},\n\n\t\tamountClass(row) {\n\t\t\treturn this.rowType(row) === 'bill'\n\t\t\t\t? 'cn-cospend-tab__amount--bill'\n\t\t\t\t: 'cn-cospend-tab__amount--project'\n\t\t},\n\n\t\tamountRaw(row) {\n\t\t\tconst raw = row.amount ?? row.data?.amount ?? null\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.isNaN(num) ? null : num\n\t\t},\n\n\t\tcurrencyOf(row) {\n\t\t\treturn row.currency ?? row.currency_name ?? row.data?.currency_name ?? row.data?.currency ?? ''\n\t\t},\n\n\t\tamountLabel(row) {\n\t\t\tconst raw = row.amount ?? row.data?.amount ?? null\n\t\t\tif (raw === null || raw === undefined || raw === '') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst currency = this.currencyOf(row)\n\t\t\tconst num = Number(raw)\n\t\t\tif (Number.isNaN(num)) {\n\t\t\t\treturn currency ? `${raw} ${currency}` : String(raw)\n\t\t\t}\n\t\t\tconst fixed = num.toFixed(2)\n\t\t\treturn currency ? `${fixed} ${currency}` : fixed\n\t\t},\n\n\t\tpayerLabel(row) {\n\t\t\treturn row.payer ?? row.user_id ?? row.data?.user_id ?? row.data?.payer ?? ''\n\t\t},\n\n\t\tdateLabel(row) {\n\t\t\tconst raw = row.date ?? row.created_at ?? row.data?.date ?? row.data?.timestamp ?? null\n\t\t\tif (!raw) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst date = typeof raw === 'number'\n\t\t\t\t? new Date(raw * 1000)\n\t\t\t\t: new Date(raw)\n\t\t\tif (Number.isNaN(date.getTime())) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn date.toLocaleDateString(undefined, { dateStyle: 'medium' })\n\t\t},\n\n\t\t/**\n\t\t * Subline shown under the title. For bills this is the payer and\n\t\t * date joined with a middot (\"alice · 20 May 2026\"); for projects\n\t\t * it is the currency (or payer) when present.\n\t\t *\n\t\t * @param {object} row Provider row.\n\t\t * @return {string} Subline text, or '' when nothing to show.\n\t\t */\n\t\tsublineText(row) {\n\t\t\tconst parts = []\n\t\t\tif (this.rowType(row) === 'bill') {\n\t\t\t\tconst payer = this.payerLabel(row)\n\t\t\t\tif (payer) {\n\t\t\t\t\tparts.push(payer)\n\t\t\t\t}\n\t\t\t\tconst date = this.dateLabel(row)\n\t\t\t\tif (date) {\n\t\t\t\t\tparts.push(date)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tconst currency = this.currencyOf(row)\n\t\t\t\tif (currency) {\n\t\t\t\t\tparts.push(currency)\n\t\t\t\t}\n\t\t\t\tconst payer = this.payerLabel(row)\n\t\t\t\tif (payer) {\n\t\t\t\t\tparts.push(payer)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn parts.join(' · ')\n\t\t},\n\n\t\tasync fetchRows() {\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.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\tconst rows = data.results || data.items || (Array.isArray(data) ? data : []) || []\n\t\t\t\t\tthis.rows = rows\n\t\t\t\t} else if (response.status === 503 || response.status === 501) {\n\t\t\t\t\tthis.rows = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.rows = []\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load costs.')\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('[CnCospendTab] failed to fetch rows', err)\n\t\t\t\tthis.rows = []\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load costs.')\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-cospend-tab__actions {\n\tdisplay: flex;\n\tgap: 8px;\n\tmargin-bottom: 10px;\n\tflex-wrap: wrap;\n}\n\n.cn-cospend-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-cospend-tab__error {\n\tcolor: var(--color-error);\n\tfont-size: 0.9em;\n\tmargin: 4px 0 8px;\n}\n\n.cn-cospend-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-cospend-tab__empty-icon {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-cospend-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/* Coloured left rail per row type, echoing Cospend's project/bill split. */\n.cn-cospend-tab__row {\n\tborder-radius: var(--border-radius);\n}\n\n.cn-cospend-tab__row--bill {\n\tborder-left: 3px solid var(--color-warning, #e9a40f);\n}\n\n.cn-cospend-tab__row--project {\n\tborder-left: 3px solid var(--color-primary-element, #21468B);\n}\n\n.cn-cospend-tab__row-icon {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tjustify-content: center;\n\twidth: 32px;\n\theight: 32px;\n\tborder-radius: 50%;\n\tcolor: var(--color-main-background);\n}\n\n.cn-cospend-tab__row-icon--bill {\n\tbackground: var(--color-warning, #e9a40f);\n}\n\n.cn-cospend-tab__row-icon--project {\n\tbackground: var(--color-primary-element, #21468B);\n}\n\n.cn-cospend-tab__title {\n\tfont-weight: 600;\n\tcolor: var(--color-main-text);\n}\n\n.cn-cospend-tab__subline {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 6px;\n\tfont-size: 0.85em;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-cospend-tab__subline-text {\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-cospend-tab__type-chip {\n\tflex-shrink: 0;\n}\n\n.cn-cospend-tab__amount {\n\tfont-weight: 700;\n\tfont-size: 0.95em;\n\twhite-space: nowrap;\n\tcolor: var(--color-main-text);\n}\n\n.cn-cospend-tab__amount--bill {\n\tcolor: var(--color-warning-text, var(--color-main-text));\n}\n\n.cn-cospend-tab__totals {\n\tdisplay: flex;\n\talign-items: flex-start;\n\tjustify-content: space-between;\n\tgap: 8px;\n\tmargin-top: 8px;\n\tpadding: 8px 10px;\n\tborder-top: 1px solid var(--color-border);\n}\n\n.cn-cospend-tab__totals-label {\n\tfont-weight: 600;\n\tcolor: var(--color-main-text);\n\ttext-transform: uppercase;\n\tfont-size: 0.78em;\n\tletter-spacing: 0.04em;\n}\n\n.cn-cospend-tab__totals-list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n\ttext-align: right;\n}\n\n.cn-cospend-tab__totals-row {\n\tdisplay: flex;\n\talign-items: baseline;\n\tjustify-content: flex-end;\n\tgap: 8px;\n\tfont-size: 0.85em;\n}\n\n.cn-cospend-tab__totals-currency {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-cospend-tab__totals-amount {\n\tfont-weight: 700;\n\tcolor: var(--color-main-text);\n}\n</style>\n"],"names":["CashMultiple","CnStatusBadge","CnCospendPicker","CnCospendCreate","t"],"mappings":"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"}
1
+ {"version":3,"file":"CnCospendTab.vue2.js","sources":["../../../../../src/integrations/builtin/cospend/CnCospendTab.vue"],"sourcesContent":["<!--\n CnCospendTab — bespoke sidebar tab for the `cospend` integration.\n\n Replaces the generic CnIntegrationTab for the `cospend` leaf: renders\n the NC Cospend rows linked to the parent OR object as a Cospend-style\n list of projects and bills.\n Cospend exposes BOTH project rows AND bill rows in the same payload\n (per wave 2.3 design — see `CospendProvider.php`); each row carries\n a `type` discriminator (`'project'` | `'bill'`) which this tab shows\n as a `[Project]` / `[Bill]` status badge alongside the title.\n\n Rows render via NcListItem to mirror real Cospend:\n - project: name + [Project] badge + currency, payer (when present)\n - bill: title + [Bill] badge + payer · date subline, prominent\n right-aligned amount with currency\n A per-currency total footer summarises the linked spend, matching\n Cospend's project balance row.\n\n Clicking a project row deep-links to\n /index.php/apps/cospend/p/{id}\n Bills inherit the same URL (Cospend doesn't have a stable per-bill\n permalink — the project page lists its bills); a row's own `url`\n field wins if the provider already constructed one.\n\n Talks to the OpenRegister pluggable-integration sub-resource\n `/api/objects/{register}/{schema}/{objectId}/integrations/cospend`\n served by `OCA\\OpenRegister\\Service\\Integration\\Providers\\CospendProvider`\n (link-table strategy — marker `[or:{uuid}]` in `cospend_projects.name`).\n\n Surface behaviour (per ADR-017 graceful degradation):\n - Empty state with \"Open Costs\" CTA when no linked rows.\n - Loading spinner during fetch.\n - 503 \"currently unavailable\" banner when Cospend is down.\n - Generic error label when fetch throws.\n\n Bespoke-vs-generic rationale: the generic tab renders a flat link\n list which loses Cospend's primary signals — *whether the row is a\n project or a bill*, the amount, and the currency. Surfacing them\n inline lets case handlers triage spending at a glance without\n context-switching into Cospend.\n\n See `openregister/openspec/changes/integration-cospend/` for the\n spec delta, ADR-019 (registry mechanism), ADR-022 (sidebar tab\n contract), `openregister#1324` for provider follow-ups.\n-->\n<template>\n\t<div class=\"cn-sidebar-tab cn-cospend-tab\">\n\t\t<div v-if=\"degraded\" class=\"cn-cospend-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 v-if=\"!degraded\" class=\"cn-cospend-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 project') }}\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 project') }}\n\t\t\t</NcButton>\n\t\t</div>\n\n\t\t<NcLoadingIcon v-if=\"loading\" />\n\t\t<div v-else-if=\"error\" class=\"cn-cospend-tab__error\" role=\"alert\">\n\t\t\t{{ error }}\n\t\t</div>\n\t\t<div v-else-if=\"rows.length === 0\" class=\"cn-sidebar-tab__empty cn-cospend-tab__empty\">\n\t\t\t<CurrencyEur :size=\"32\" class=\"cn-cospend-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=\"cospendAppUrl\"\n\t\t\t\ttarget=\"_blank\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<CurrencyEur :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ openCospendLabel }}\n\t\t\t</NcButton>\n\t\t</div>\n\t\t<template v-else>\n\t\t\t<ul class=\"cn-cospend-tab__list\">\n\t\t\t\t<NcListItem\n\t\t\t\t\tv-for=\"row in rows\"\n\t\t\t\t\t:key=\"rowKey(row)\"\n\t\t\t\t\tclass=\"cn-cospend-tab__row\"\n\t\t\t\t\t:class=\"rowClass(row)\"\n\t\t\t\t\t:href=\"rowUrl(row)\"\n\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t:forceDisplayActions=\"true\">\n\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t<span class=\"cn-cospend-tab__row-icon\" :class=\"iconClass(row)\">\n\t\t\t\t\t\t\t<CashMultiple v-if=\"rowType(row) === 'bill'\" :size=\"20\" />\n\t\t\t\t\t\t\t<FolderOutline v-else :size=\"20\" />\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</template>\n\t\t\t\t\t<template #name>\n\t\t\t\t\t\t<span class=\"cn-cospend-tab__title\">{{ rowTitle(row) }}</span>\n\t\t\t\t\t</template>\n\t\t\t\t\t<template #subname>\n\t\t\t\t\t\t<span class=\"cn-cospend-tab__subline\">\n\t\t\t\t\t\t\t<CnStatusBadge\n\t\t\t\t\t\t\t\tclass=\"cn-cospend-tab__type-chip\"\n\t\t\t\t\t\t\t\t:class=\"typeChipClass(row)\"\n\t\t\t\t\t\t\t\tsize=\"small\"\n\t\t\t\t\t\t\t\t:variant=\"typeVariant(row)\"\n\t\t\t\t\t\t\t\t:label=\"typeLabel(row)\" />\n\t\t\t\t\t\t\t<span v-if=\"sublineText(row)\" class=\"cn-cospend-tab__subline-text\">\n\t\t\t\t\t\t\t\t{{ sublineText(row) }}\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</template>\n\t\t\t\t\t<template #indicator>\n\t\t\t\t\t\t<span v-if=\"amountLabel(row)\" class=\"cn-cospend-tab__amount\" :class=\"amountClass(row)\">\n\t\t\t\t\t\t\t{{ amountLabel(row) }}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</template>\n\t\t\t\t\t<template #actions>\n\t\t\t\t\t\t<NcActionButton\n\t\t\t\t\t\t\tv-if=\"entryIdOf(row)\"\n\t\t\t\t\t\t\t:closeAfterClick=\"true\"\n\t\t\t\t\t\t\t@click=\"unlinkEntry(row)\">\n\t\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t\t<LinkOff :size=\"20\" />\n\t\t\t\t\t\t\t</template>\n\t\t\t\t\t\t\t{{ t('nextcloud-vue', 'Unlink entry') }}\n\t\t\t\t\t\t</NcActionButton>\n\t\t\t\t\t</template>\n\t\t\t\t</NcListItem>\n\t\t\t</ul>\n\n\t\t\t<div v-if=\"totals.length > 0\" class=\"cn-cospend-tab__totals\">\n\t\t\t\t<span class=\"cn-cospend-tab__totals-label\">{{ t('nextcloud-vue', 'Total') }}</span>\n\t\t\t\t<ul class=\"cn-cospend-tab__totals-list\">\n\t\t\t\t\t<li\n\t\t\t\t\t\tv-for=\"bucket in totals\"\n\t\t\t\t\t\t:key=\"bucket.currency\"\n\t\t\t\t\t\tclass=\"cn-cospend-tab__totals-row\">\n\t\t\t\t\t\t<span class=\"cn-cospend-tab__totals-currency\">{{ bucket.currency }}</span>\n\t\t\t\t\t\t<span class=\"cn-cospend-tab__totals-amount\">{{ bucket.amountLabel }}</span>\n\t\t\t\t\t</li>\n\t\t\t\t</ul>\n\t\t\t</div>\n\t\t</template>\n\n\t\t<CnCospendPicker\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<CnCospendCreate\n\t\t\tv-if=\"createOpen\"\n\t\t\t:apiBase=\"apiBase\"\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, NcButton, NcListItem, NcLoadingIcon } from '@nextcloud/vue'\nimport AlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue'\nimport CashMultiple from 'vue-material-design-icons/CashMultiple.vue'\nimport CurrencyEur from 'vue-material-design-icons/CurrencyEur.vue'\nimport FolderOutline from 'vue-material-design-icons/FolderOutline.vue'\nimport LinkOff from 'vue-material-design-icons/LinkOff.vue'\nimport LinkVariant from 'vue-material-design-icons/LinkVariant.vue'\nimport Plus from 'vue-material-design-icons/Plus.vue'\nimport CnCospendCreate from '../../../components/CnCospendCreate/CnCospendCreate.vue'\nimport CnCospendPicker from '../../../components/CnCospendPicker/CnCospendPicker.vue'\nimport CnStatusBadge from '../../../components/CnStatusBadge/CnStatusBadge.vue'\nimport { buildHeaders, prefixUrl, safeHref } from '../../../utils/index.js'\n\n/**\n * CnCospendTab — bespoke sidebar tab for the `cospend` integration.\n *\n * Renders linked Cospend projects + bills with a type badge, amount,\n * currency, payer, date and a per-currency total footer. See the\n * file-level docblock for surface behaviour.\n */\nexport default {\n\tname: 'CnCospendTab',\n\n\tcomponents: {\n\t\tNcActionButton,\n\t\tNcButton,\n\t\tNcListItem,\n\t\tNcLoadingIcon,\n\t\tAlertCircleOutline,\n\t\tCashMultiple,\n\t\tCurrencyEur,\n\t\tFolderOutline,\n\t\tLinkOff,\n\t\tLinkVariant,\n\t\tPlus,\n\t\tCnStatusBadge,\n\t\tCnCospendPicker,\n\t\tCnCospendCreate,\n\t},\n\n\tprops: {\n\t\t/** Stable integration id (forwarded from the registry — always `'cospend'`). */\n\t\tintegrationId: { type: String, default: 'cospend' },\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 costs linked yet') },\n\t\t/** Pre-translated label for the \"Open Costs\" CTA. */\n\t\topenCospendLabel: { type: String, default: () => t('nextcloud-vue', 'Open Costs') },\n\t\t/** Pre-translated banner when Cospend is unavailable. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Costs is currently unavailable.') },\n\t\t/** URL of the NC Cospend app entry. */\n\t\tcospendAppUrl: { type: String, default: '/index.php/apps/cospend' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\trows: [],\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\tcomputed: {\n\t\t/**\n\t\t * Per-currency totals of every row carrying an amount, mirroring\n\t\t * the balance row Cospend renders under a project.\n\t\t *\n\t\t * @return {Array<{currency: string, amountLabel: string}>}\n\t\t */\n\t\ttotals() {\n\t\t\tconst groups = new Map()\n\t\t\tfor (const row of this.rows) {\n\t\t\t\tconst amount = this.amountRaw(row)\n\t\t\t\tif (amount === null) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tconst currency = this.currencyOf(row) || ''\n\t\t\t\tconst existing = groups.get(currency) || { currency, total: 0 }\n\t\t\t\texisting.total += amount\n\t\t\t\tgroups.set(currency, existing)\n\t\t\t}\n\t\t\treturn Array.from(groups.values()).map((b) => {\n\t\t\t\tconst code = b.currency || t('nextcloud-vue', 'Unspecified')\n\t\t\t\treturn {\n\t\t\t\t\tcurrency: code,\n\t\t\t\t\tamountLabel: b.currency ? `${b.total.toFixed(2)} ${b.currency}` : b.total.toFixed(2),\n\t\t\t\t}\n\t\t\t})\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.fetchRows()\n\t\t\t}\n\t\t} },\n\n\t\tregister() {\n\t\t\tthis.fetchRows()\n\t\t},\n\n\t\tschema() {\n\t\t\tthis.fetchRows()\n\t\t},\n\t},\n\n\tmethods: {\n\t\tbaseUrl() {\n\t\t\treturn prefixUrl(`${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/integrations/${this.integrationId}`)\n\t\t},\n\n\t\t/**\n\t\t * Base for the Tier-2 cospend endpoints (list/link/new/destroy).\n\t\t *\n\t\t * @return {string} The endpoint URL.\n\t\t */\n\t\tcospendEndpoint() {\n\t\t\treturn prefixUrl(`${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/cospend`)\n\t\t},\n\n\t\trowKey(row) {\n\t\t\treturn row.id ?? row.uuid ?? ''\n\t\t},\n\n\t\t/**\n\t\t * Resolve the link-row id for a Tier-2 row (used for unlink).\n\t\t * Legacy marker rows have no `entryId` so the unlink button hides.\n\t\t *\n\t\t * @param {object} row Provider/link row.\n\t\t *\n\t\t * @return {(number|string)} The link row id, or '' when absent.\n\t\t */\n\t\tentryIdOf(row) {\n\t\t\tconst d = (row && typeof row.data === 'object' && row.data !== null) ? row.data : {}\n\t\t\treturn row.entryId ?? d.id ?? ''\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.cospendEndpoint(), {\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.fetchRows()\n\t\t\t\t} else if (response.status === 409) {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'This entry is already linked.')\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not link entry.')\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('[CnCospendTab] link failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not link entry.')\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.cospendEndpoint()}/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.fetchRows()\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not create project.')\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('[CnCospendTab] create failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not create project.')\n\t\t\t}\n\t\t},\n\n\t\tasync unlinkEntry(row) {\n\t\t\tconst id = this.entryIdOf(row)\n\t\t\tif (!id) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(`${this.cospendEndpoint()}/${id}`, {\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.fetchRows()\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not unlink entry.')\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('[CnCospendTab] unlink failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not unlink entry.')\n\t\t\t}\n\t\t},\n\n\t\trowTitle(row) {\n\t\t\treturn row.title ?? row.name ?? this.rowKey(row)\n\t\t},\n\n\t\trowUrl(row) {\n\t\t\tif (row.url && safeHref(row.url) !== '#') {\n\t\t\t\treturn row.url\n\t\t\t}\n\t\t\tconst projectId = row.projectId ?? row.project_id ?? row.data?.projectid ?? row.data?.project_id ?? null\n\t\t\tif (projectId !== null && projectId !== '') {\n\t\t\t\treturn `/index.php/apps/cospend/p/${projectId}`\n\t\t\t}\n\t\t\tconst id = this.rowKey(row)\n\t\t\tif (id !== '') {\n\t\t\t\treturn `/index.php/apps/cospend/p/${id}`\n\t\t\t}\n\t\t\treturn this.cospendAppUrl\n\t\t},\n\n\t\t/**\n\t\t * Discriminator. Defaults to 'project' when the provider doesn't\n\t\t * surface a `type` field (the current shipping CospendProvider\n\t\t * only returns projects); when wave-2.3 ships bills the chip\n\t\t * label flips accordingly.\n\t\t *\n\t\t * @param {object} row Provider row.\n\t\t * @return {'project'|'bill'} Row type.\n\t\t */\n\t\trowType(row) {\n\t\t\tconst t = String(row.type ?? row.kind ?? '').toLowerCase()\n\t\t\tif (t === 'bill' || t === 'expense') {\n\t\t\t\treturn 'bill'\n\t\t\t}\n\t\t\treturn 'project'\n\t\t},\n\n\t\ttypeLabel(row) {\n\t\t\treturn this.rowType(row) === 'bill'\n\t\t\t\t? t('nextcloud-vue', 'Bill')\n\t\t\t\t: t('nextcloud-vue', 'Project')\n\t\t},\n\n\t\ttypeChipClass(row) {\n\t\t\treturn this.rowType(row) === 'bill'\n\t\t\t\t? 'cn-cospend-tab__type-chip--bill'\n\t\t\t\t: 'cn-cospend-tab__type-chip--project'\n\t\t},\n\n\t\ttypeVariant(row) {\n\t\t\treturn this.rowType(row) === 'bill' ? 'warning' : 'primary'\n\t\t},\n\n\t\trowClass(row) {\n\t\t\treturn this.rowType(row) === 'bill'\n\t\t\t\t? 'cn-cospend-tab__row--bill'\n\t\t\t\t: 'cn-cospend-tab__row--project'\n\t\t},\n\n\t\ticonClass(row) {\n\t\t\treturn this.rowType(row) === 'bill'\n\t\t\t\t? 'cn-cospend-tab__row-icon--bill'\n\t\t\t\t: 'cn-cospend-tab__row-icon--project'\n\t\t},\n\n\t\tamountClass(row) {\n\t\t\treturn this.rowType(row) === 'bill'\n\t\t\t\t? 'cn-cospend-tab__amount--bill'\n\t\t\t\t: 'cn-cospend-tab__amount--project'\n\t\t},\n\n\t\tamountRaw(row) {\n\t\t\tconst raw = row.amount ?? row.data?.amount ?? null\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.isNaN(num) ? null : num\n\t\t},\n\n\t\tcurrencyOf(row) {\n\t\t\treturn row.currency ?? row.currency_name ?? row.data?.currency_name ?? row.data?.currency ?? ''\n\t\t},\n\n\t\tamountLabel(row) {\n\t\t\tconst raw = row.amount ?? row.data?.amount ?? null\n\t\t\tif (raw === null || raw === undefined || raw === '') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst currency = this.currencyOf(row)\n\t\t\tconst num = Number(raw)\n\t\t\tif (Number.isNaN(num)) {\n\t\t\t\treturn currency ? `${raw} ${currency}` : String(raw)\n\t\t\t}\n\t\t\tconst fixed = num.toFixed(2)\n\t\t\treturn currency ? `${fixed} ${currency}` : fixed\n\t\t},\n\n\t\tpayerLabel(row) {\n\t\t\treturn row.payer ?? row.user_id ?? row.data?.user_id ?? row.data?.payer ?? ''\n\t\t},\n\n\t\tdateLabel(row) {\n\t\t\tconst raw = row.date ?? row.created_at ?? row.data?.date ?? row.data?.timestamp ?? null\n\t\t\tif (!raw) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst date = typeof raw === 'number'\n\t\t\t\t? new Date(raw * 1000)\n\t\t\t\t: new Date(raw)\n\t\t\tif (Number.isNaN(date.getTime())) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn date.toLocaleDateString(undefined, { dateStyle: 'medium' })\n\t\t},\n\n\t\t/**\n\t\t * Subline shown under the title. For bills this is the payer and\n\t\t * date joined with a middot (\"alice · 20 May 2026\"); for projects\n\t\t * it is the currency (or payer) when present.\n\t\t *\n\t\t * @param {object} row Provider row.\n\t\t * @return {string} Subline text, or '' when nothing to show.\n\t\t */\n\t\tsublineText(row) {\n\t\t\tconst parts = []\n\t\t\tif (this.rowType(row) === 'bill') {\n\t\t\t\tconst payer = this.payerLabel(row)\n\t\t\t\tif (payer) {\n\t\t\t\t\tparts.push(payer)\n\t\t\t\t}\n\t\t\t\tconst date = this.dateLabel(row)\n\t\t\t\tif (date) {\n\t\t\t\t\tparts.push(date)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tconst currency = this.currencyOf(row)\n\t\t\t\tif (currency) {\n\t\t\t\t\tparts.push(currency)\n\t\t\t\t}\n\t\t\t\tconst payer = this.payerLabel(row)\n\t\t\t\tif (payer) {\n\t\t\t\t\tparts.push(payer)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn parts.join(' · ')\n\t\t},\n\n\t\tasync fetchRows() {\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.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\tconst rows = data.results || data.items || (Array.isArray(data) ? data : []) || []\n\t\t\t\t\tthis.rows = rows\n\t\t\t\t} else if (response.status === 503 || response.status === 501) {\n\t\t\t\t\tthis.rows = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.rows = []\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load costs.')\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('[CnCospendTab] failed to fetch rows', err)\n\t\t\t\tthis.rows = []\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load costs.')\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-cospend-tab__actions {\n\tdisplay: flex;\n\tgap: 8px;\n\tmargin-bottom: 10px;\n\tflex-wrap: wrap;\n}\n\n.cn-cospend-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-cospend-tab__error {\n\tcolor: var(--color-error);\n\tfont-size: 0.9em;\n\tmargin: 4px 0 8px;\n}\n\n.cn-cospend-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-cospend-tab__empty-icon {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-cospend-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/* Coloured left rail per row type, echoing Cospend's project/bill split. */\n.cn-cospend-tab__row {\n\tborder-radius: var(--border-radius);\n}\n\n.cn-cospend-tab__row--bill {\n\tborder-left: 3px solid var(--color-warning, #e9a40f);\n}\n\n.cn-cospend-tab__row--project {\n\tborder-left: 3px solid var(--color-primary-element, #21468B);\n}\n\n.cn-cospend-tab__row-icon {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tjustify-content: center;\n\twidth: 32px;\n\theight: 32px;\n\tborder-radius: 50%;\n\tcolor: var(--color-main-background);\n}\n\n.cn-cospend-tab__row-icon--bill {\n\tbackground: var(--color-warning, #e9a40f);\n}\n\n.cn-cospend-tab__row-icon--project {\n\tbackground: var(--color-primary-element, #21468B);\n}\n\n.cn-cospend-tab__title {\n\tfont-weight: 600;\n\tcolor: var(--color-main-text);\n}\n\n.cn-cospend-tab__subline {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 6px;\n\tfont-size: 0.85em;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-cospend-tab__subline-text {\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-cospend-tab__type-chip {\n\tflex-shrink: 0;\n}\n\n.cn-cospend-tab__amount {\n\tfont-weight: 700;\n\tfont-size: 0.95em;\n\twhite-space: nowrap;\n\tcolor: var(--color-main-text);\n}\n\n.cn-cospend-tab__amount--bill {\n\tcolor: var(--color-warning-text, var(--color-main-text));\n}\n\n.cn-cospend-tab__totals {\n\tdisplay: flex;\n\talign-items: flex-start;\n\tjustify-content: space-between;\n\tgap: 8px;\n\tmargin-top: 8px;\n\tpadding: 8px 10px;\n\tborder-top: 1px solid var(--color-border);\n}\n\n.cn-cospend-tab__totals-label {\n\tfont-weight: 600;\n\tcolor: var(--color-main-text);\n\ttext-transform: uppercase;\n\tfont-size: 0.78em;\n\tletter-spacing: 0.04em;\n}\n\n.cn-cospend-tab__totals-list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n\ttext-align: right;\n}\n\n.cn-cospend-tab__totals-row {\n\tdisplay: flex;\n\talign-items: baseline;\n\tjustify-content: flex-end;\n\tgap: 8px;\n\tfont-size: 0.85em;\n}\n\n.cn-cospend-tab__totals-currency {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-cospend-tab__totals-amount {\n\tfont-weight: 700;\n\tcolor: var(--color-main-text);\n}\n</style>\n"],"names":["CashMultiple","CnStatusBadge","CnCospendPicker","CnCospendCreate","t"],"mappings":"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"}
@@ -100,8 +100,7 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
100
100
  createVNode(_component_NcButton, {
101
101
  variant: "primary",
102
102
  href: $props.cospendAppUrl,
103
- target: "_blank",
104
- rel: "noopener noreferrer"
103
+ target: "_blank"
105
104
  }, {
106
105
  icon: withCtx(() => [
107
106
  createVNode(_component_CurrencyEur, { size: 20 })
@@ -1 +1 @@
1
- {"version":3,"file":"CnCospendTab.vue3.js","sources":["../../../../../src/integrations/builtin/cospend/CnCospendTab.vue?vue&type=template&id=037c757e&lang.js"],"sourcesContent":["<!--\n CnCospendTab — bespoke sidebar tab for the `cospend` integration.\n\n Replaces the generic CnIntegrationTab for the `cospend` leaf: renders\n the NC Cospend rows linked to the parent OR object as a Cospend-style\n list of projects and bills.\n Cospend exposes BOTH project rows AND bill rows in the same payload\n (per wave 2.3 design — see `CospendProvider.php`); each row carries\n a `type` discriminator (`'project'` | `'bill'`) which this tab shows\n as a `[Project]` / `[Bill]` status badge alongside the title.\n\n Rows render via NcListItem to mirror real Cospend:\n - project: name + [Project] badge + currency, payer (when present)\n - bill: title + [Bill] badge + payer · date subline, prominent\n right-aligned amount with currency\n A per-currency total footer summarises the linked spend, matching\n Cospend's project balance row.\n\n Clicking a project row deep-links to\n /index.php/apps/cospend/p/{id}\n Bills inherit the same URL (Cospend doesn't have a stable per-bill\n permalink — the project page lists its bills); a row's own `url`\n field wins if the provider already constructed one.\n\n Talks to the OpenRegister pluggable-integration sub-resource\n `/api/objects/{register}/{schema}/{objectId}/integrations/cospend`\n served by `OCA\\OpenRegister\\Service\\Integration\\Providers\\CospendProvider`\n (link-table strategy — marker `[or:{uuid}]` in `cospend_projects.name`).\n\n Surface behaviour (per ADR-017 graceful degradation):\n - Empty state with \"Open Costs\" CTA when no linked rows.\n - Loading spinner during fetch.\n - 503 \"currently unavailable\" banner when Cospend is down.\n - Generic error label when fetch throws.\n\n Bespoke-vs-generic rationale: the generic tab renders a flat link\n list which loses Cospend's primary signals — *whether the row is a\n project or a bill*, the amount, and the currency. Surfacing them\n inline lets case handlers triage spending at a glance without\n context-switching into Cospend.\n\n See `openregister/openspec/changes/integration-cospend/` for the\n spec delta, ADR-019 (registry mechanism), ADR-022 (sidebar tab\n contract), `openregister#1324` for provider follow-ups.\n-->\n<template>\n\t<div class=\"cn-sidebar-tab cn-cospend-tab\">\n\t\t<div v-if=\"degraded\" class=\"cn-cospend-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 v-if=\"!degraded\" class=\"cn-cospend-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 project') }}\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 project') }}\n\t\t\t</NcButton>\n\t\t</div>\n\n\t\t<NcLoadingIcon v-if=\"loading\" />\n\t\t<div v-else-if=\"error\" class=\"cn-cospend-tab__error\" role=\"alert\">\n\t\t\t{{ error }}\n\t\t</div>\n\t\t<div v-else-if=\"rows.length === 0\" class=\"cn-sidebar-tab__empty cn-cospend-tab__empty\">\n\t\t\t<CurrencyEur :size=\"32\" class=\"cn-cospend-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=\"cospendAppUrl\"\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<CurrencyEur :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ openCospendLabel }}\n\t\t\t</NcButton>\n\t\t</div>\n\t\t<template v-else>\n\t\t\t<ul class=\"cn-cospend-tab__list\">\n\t\t\t\t<NcListItem\n\t\t\t\t\tv-for=\"row in rows\"\n\t\t\t\t\t:key=\"rowKey(row)\"\n\t\t\t\t\tclass=\"cn-cospend-tab__row\"\n\t\t\t\t\t:class=\"rowClass(row)\"\n\t\t\t\t\t:href=\"rowUrl(row)\"\n\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t:forceDisplayActions=\"true\">\n\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t<span class=\"cn-cospend-tab__row-icon\" :class=\"iconClass(row)\">\n\t\t\t\t\t\t\t<CashMultiple v-if=\"rowType(row) === 'bill'\" :size=\"20\" />\n\t\t\t\t\t\t\t<FolderOutline v-else :size=\"20\" />\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</template>\n\t\t\t\t\t<template #name>\n\t\t\t\t\t\t<span class=\"cn-cospend-tab__title\">{{ rowTitle(row) }}</span>\n\t\t\t\t\t</template>\n\t\t\t\t\t<template #subname>\n\t\t\t\t\t\t<span class=\"cn-cospend-tab__subline\">\n\t\t\t\t\t\t\t<CnStatusBadge\n\t\t\t\t\t\t\t\tclass=\"cn-cospend-tab__type-chip\"\n\t\t\t\t\t\t\t\t:class=\"typeChipClass(row)\"\n\t\t\t\t\t\t\t\tsize=\"small\"\n\t\t\t\t\t\t\t\t:variant=\"typeVariant(row)\"\n\t\t\t\t\t\t\t\t:label=\"typeLabel(row)\" />\n\t\t\t\t\t\t\t<span v-if=\"sublineText(row)\" class=\"cn-cospend-tab__subline-text\">\n\t\t\t\t\t\t\t\t{{ sublineText(row) }}\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</template>\n\t\t\t\t\t<template #indicator>\n\t\t\t\t\t\t<span v-if=\"amountLabel(row)\" class=\"cn-cospend-tab__amount\" :class=\"amountClass(row)\">\n\t\t\t\t\t\t\t{{ amountLabel(row) }}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</template>\n\t\t\t\t\t<template #actions>\n\t\t\t\t\t\t<NcActionButton\n\t\t\t\t\t\t\tv-if=\"entryIdOf(row)\"\n\t\t\t\t\t\t\t:closeAfterClick=\"true\"\n\t\t\t\t\t\t\t@click=\"unlinkEntry(row)\">\n\t\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t\t<LinkOff :size=\"20\" />\n\t\t\t\t\t\t\t</template>\n\t\t\t\t\t\t\t{{ t('nextcloud-vue', 'Unlink entry') }}\n\t\t\t\t\t\t</NcActionButton>\n\t\t\t\t\t</template>\n\t\t\t\t</NcListItem>\n\t\t\t</ul>\n\n\t\t\t<div v-if=\"totals.length > 0\" class=\"cn-cospend-tab__totals\">\n\t\t\t\t<span class=\"cn-cospend-tab__totals-label\">{{ t('nextcloud-vue', 'Total') }}</span>\n\t\t\t\t<ul class=\"cn-cospend-tab__totals-list\">\n\t\t\t\t\t<li\n\t\t\t\t\t\tv-for=\"bucket in totals\"\n\t\t\t\t\t\t:key=\"bucket.currency\"\n\t\t\t\t\t\tclass=\"cn-cospend-tab__totals-row\">\n\t\t\t\t\t\t<span class=\"cn-cospend-tab__totals-currency\">{{ bucket.currency }}</span>\n\t\t\t\t\t\t<span class=\"cn-cospend-tab__totals-amount\">{{ bucket.amountLabel }}</span>\n\t\t\t\t\t</li>\n\t\t\t\t</ul>\n\t\t\t</div>\n\t\t</template>\n\n\t\t<CnCospendPicker\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<CnCospendCreate\n\t\t\tv-if=\"createOpen\"\n\t\t\t:apiBase=\"apiBase\"\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, NcButton, NcListItem, NcLoadingIcon } from '@nextcloud/vue'\nimport AlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue'\nimport CashMultiple from 'vue-material-design-icons/CashMultiple.vue'\nimport CurrencyEur from 'vue-material-design-icons/CurrencyEur.vue'\nimport FolderOutline from 'vue-material-design-icons/FolderOutline.vue'\nimport LinkOff from 'vue-material-design-icons/LinkOff.vue'\nimport LinkVariant from 'vue-material-design-icons/LinkVariant.vue'\nimport Plus from 'vue-material-design-icons/Plus.vue'\nimport CnCospendCreate from '../../../components/CnCospendCreate/CnCospendCreate.vue'\nimport CnCospendPicker from '../../../components/CnCospendPicker/CnCospendPicker.vue'\nimport CnStatusBadge from '../../../components/CnStatusBadge/CnStatusBadge.vue'\nimport { buildHeaders, prefixUrl } from '../../../utils/index.js'\n\n/**\n * CnCospendTab — bespoke sidebar tab for the `cospend` integration.\n *\n * Renders linked Cospend projects + bills with a type badge, amount,\n * currency, payer, date and a per-currency total footer. See the\n * file-level docblock for surface behaviour.\n */\nexport default {\n\tname: 'CnCospendTab',\n\n\tcomponents: {\n\t\tNcActionButton,\n\t\tNcButton,\n\t\tNcListItem,\n\t\tNcLoadingIcon,\n\t\tAlertCircleOutline,\n\t\tCashMultiple,\n\t\tCurrencyEur,\n\t\tFolderOutline,\n\t\tLinkOff,\n\t\tLinkVariant,\n\t\tPlus,\n\t\tCnStatusBadge,\n\t\tCnCospendPicker,\n\t\tCnCospendCreate,\n\t},\n\n\tprops: {\n\t\t/** Stable integration id (forwarded from the registry — always `'cospend'`). */\n\t\tintegrationId: { type: String, default: 'cospend' },\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 costs linked yet') },\n\t\t/** Pre-translated label for the \"Open Costs\" CTA. */\n\t\topenCospendLabel: { type: String, default: () => t('nextcloud-vue', 'Open Costs') },\n\t\t/** Pre-translated banner when Cospend is unavailable. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Costs is currently unavailable.') },\n\t\t/** URL of the NC Cospend app entry. */\n\t\tcospendAppUrl: { type: String, default: '/index.php/apps/cospend' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\trows: [],\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\tcomputed: {\n\t\t/**\n\t\t * Per-currency totals of every row carrying an amount, mirroring\n\t\t * the balance row Cospend renders under a project.\n\t\t *\n\t\t * @return {Array<{currency: string, amountLabel: string}>}\n\t\t */\n\t\ttotals() {\n\t\t\tconst groups = new Map()\n\t\t\tfor (const row of this.rows) {\n\t\t\t\tconst amount = this.amountRaw(row)\n\t\t\t\tif (amount === null) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tconst currency = this.currencyOf(row) || ''\n\t\t\t\tconst existing = groups.get(currency) || { currency, total: 0 }\n\t\t\t\texisting.total += amount\n\t\t\t\tgroups.set(currency, existing)\n\t\t\t}\n\t\t\treturn Array.from(groups.values()).map((b) => {\n\t\t\t\tconst code = b.currency || t('nextcloud-vue', 'Unspecified')\n\t\t\t\treturn {\n\t\t\t\t\tcurrency: code,\n\t\t\t\t\tamountLabel: b.currency ? `${b.total.toFixed(2)} ${b.currency}` : b.total.toFixed(2),\n\t\t\t\t}\n\t\t\t})\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.fetchRows()\n\t\t\t}\n\t\t} },\n\n\t\tregister() {\n\t\t\tthis.fetchRows()\n\t\t},\n\n\t\tschema() {\n\t\t\tthis.fetchRows()\n\t\t},\n\t},\n\n\tmethods: {\n\t\tbaseUrl() {\n\t\t\treturn prefixUrl(`${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/integrations/${this.integrationId}`)\n\t\t},\n\n\t\t/**\n\t\t * Base for the Tier-2 cospend endpoints (list/link/new/destroy).\n\t\t *\n\t\t * @return {string} The endpoint URL.\n\t\t */\n\t\tcospendEndpoint() {\n\t\t\treturn prefixUrl(`${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/cospend`)\n\t\t},\n\n\t\trowKey(row) {\n\t\t\treturn row.id ?? row.uuid ?? ''\n\t\t},\n\n\t\t/**\n\t\t * Resolve the link-row id for a Tier-2 row (used for unlink).\n\t\t * Legacy marker rows have no `entryId` so the unlink button hides.\n\t\t *\n\t\t * @param {object} row Provider/link row.\n\t\t *\n\t\t * @return {(number|string)} The link row id, or '' when absent.\n\t\t */\n\t\tentryIdOf(row) {\n\t\t\tconst d = (row && typeof row.data === 'object' && row.data !== null) ? row.data : {}\n\t\t\treturn row.entryId ?? d.id ?? ''\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.cospendEndpoint(), {\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.fetchRows()\n\t\t\t\t} else if (response.status === 409) {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'This entry is already linked.')\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not link entry.')\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('[CnCospendTab] link failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not link entry.')\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.cospendEndpoint()}/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.fetchRows()\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not create project.')\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('[CnCospendTab] create failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not create project.')\n\t\t\t}\n\t\t},\n\n\t\tasync unlinkEntry(row) {\n\t\t\tconst id = this.entryIdOf(row)\n\t\t\tif (!id) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(`${this.cospendEndpoint()}/${id}`, {\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.fetchRows()\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not unlink entry.')\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('[CnCospendTab] unlink failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not unlink entry.')\n\t\t\t}\n\t\t},\n\n\t\trowTitle(row) {\n\t\t\treturn row.title ?? row.name ?? this.rowKey(row)\n\t\t},\n\n\t\trowUrl(row) {\n\t\t\tif (row.url) {\n\t\t\t\treturn row.url\n\t\t\t}\n\t\t\tconst projectId = row.projectId ?? row.project_id ?? row.data?.projectid ?? row.data?.project_id ?? null\n\t\t\tif (projectId !== null && projectId !== '') {\n\t\t\t\treturn `/index.php/apps/cospend/p/${projectId}`\n\t\t\t}\n\t\t\tconst id = this.rowKey(row)\n\t\t\tif (id !== '') {\n\t\t\t\treturn `/index.php/apps/cospend/p/${id}`\n\t\t\t}\n\t\t\treturn this.cospendAppUrl\n\t\t},\n\n\t\t/**\n\t\t * Discriminator. Defaults to 'project' when the provider doesn't\n\t\t * surface a `type` field (the current shipping CospendProvider\n\t\t * only returns projects); when wave-2.3 ships bills the chip\n\t\t * label flips accordingly.\n\t\t *\n\t\t * @param {object} row Provider row.\n\t\t * @return {'project'|'bill'} Row type.\n\t\t */\n\t\trowType(row) {\n\t\t\tconst t = String(row.type ?? row.kind ?? '').toLowerCase()\n\t\t\tif (t === 'bill' || t === 'expense') {\n\t\t\t\treturn 'bill'\n\t\t\t}\n\t\t\treturn 'project'\n\t\t},\n\n\t\ttypeLabel(row) {\n\t\t\treturn this.rowType(row) === 'bill'\n\t\t\t\t? t('nextcloud-vue', 'Bill')\n\t\t\t\t: t('nextcloud-vue', 'Project')\n\t\t},\n\n\t\ttypeChipClass(row) {\n\t\t\treturn this.rowType(row) === 'bill'\n\t\t\t\t? 'cn-cospend-tab__type-chip--bill'\n\t\t\t\t: 'cn-cospend-tab__type-chip--project'\n\t\t},\n\n\t\ttypeVariant(row) {\n\t\t\treturn this.rowType(row) === 'bill' ? 'warning' : 'primary'\n\t\t},\n\n\t\trowClass(row) {\n\t\t\treturn this.rowType(row) === 'bill'\n\t\t\t\t? 'cn-cospend-tab__row--bill'\n\t\t\t\t: 'cn-cospend-tab__row--project'\n\t\t},\n\n\t\ticonClass(row) {\n\t\t\treturn this.rowType(row) === 'bill'\n\t\t\t\t? 'cn-cospend-tab__row-icon--bill'\n\t\t\t\t: 'cn-cospend-tab__row-icon--project'\n\t\t},\n\n\t\tamountClass(row) {\n\t\t\treturn this.rowType(row) === 'bill'\n\t\t\t\t? 'cn-cospend-tab__amount--bill'\n\t\t\t\t: 'cn-cospend-tab__amount--project'\n\t\t},\n\n\t\tamountRaw(row) {\n\t\t\tconst raw = row.amount ?? row.data?.amount ?? null\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.isNaN(num) ? null : num\n\t\t},\n\n\t\tcurrencyOf(row) {\n\t\t\treturn row.currency ?? row.currency_name ?? row.data?.currency_name ?? row.data?.currency ?? ''\n\t\t},\n\n\t\tamountLabel(row) {\n\t\t\tconst raw = row.amount ?? row.data?.amount ?? null\n\t\t\tif (raw === null || raw === undefined || raw === '') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst currency = this.currencyOf(row)\n\t\t\tconst num = Number(raw)\n\t\t\tif (Number.isNaN(num)) {\n\t\t\t\treturn currency ? `${raw} ${currency}` : String(raw)\n\t\t\t}\n\t\t\tconst fixed = num.toFixed(2)\n\t\t\treturn currency ? `${fixed} ${currency}` : fixed\n\t\t},\n\n\t\tpayerLabel(row) {\n\t\t\treturn row.payer ?? row.user_id ?? row.data?.user_id ?? row.data?.payer ?? ''\n\t\t},\n\n\t\tdateLabel(row) {\n\t\t\tconst raw = row.date ?? row.created_at ?? row.data?.date ?? row.data?.timestamp ?? null\n\t\t\tif (!raw) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst date = typeof raw === 'number'\n\t\t\t\t? new Date(raw * 1000)\n\t\t\t\t: new Date(raw)\n\t\t\tif (Number.isNaN(date.getTime())) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn date.toLocaleDateString(undefined, { dateStyle: 'medium' })\n\t\t},\n\n\t\t/**\n\t\t * Subline shown under the title. For bills this is the payer and\n\t\t * date joined with a middot (\"alice · 20 May 2026\"); for projects\n\t\t * it is the currency (or payer) when present.\n\t\t *\n\t\t * @param {object} row Provider row.\n\t\t * @return {string} Subline text, or '' when nothing to show.\n\t\t */\n\t\tsublineText(row) {\n\t\t\tconst parts = []\n\t\t\tif (this.rowType(row) === 'bill') {\n\t\t\t\tconst payer = this.payerLabel(row)\n\t\t\t\tif (payer) {\n\t\t\t\t\tparts.push(payer)\n\t\t\t\t}\n\t\t\t\tconst date = this.dateLabel(row)\n\t\t\t\tif (date) {\n\t\t\t\t\tparts.push(date)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tconst currency = this.currencyOf(row)\n\t\t\t\tif (currency) {\n\t\t\t\t\tparts.push(currency)\n\t\t\t\t}\n\t\t\t\tconst payer = this.payerLabel(row)\n\t\t\t\tif (payer) {\n\t\t\t\t\tparts.push(payer)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn parts.join(' · ')\n\t\t},\n\n\t\tasync fetchRows() {\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.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\tconst rows = data.results || data.items || (Array.isArray(data) ? data : []) || []\n\t\t\t\t\tthis.rows = rows\n\t\t\t\t} else if (response.status === 503 || response.status === 501) {\n\t\t\t\t\tthis.rows = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.rows = []\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load costs.')\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('[CnCospendTab] failed to fetch rows', err)\n\t\t\t\tthis.rows = []\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load costs.')\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-cospend-tab__actions {\n\tdisplay: flex;\n\tgap: 8px;\n\tmargin-bottom: 10px;\n\tflex-wrap: wrap;\n}\n\n.cn-cospend-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-cospend-tab__error {\n\tcolor: var(--color-error);\n\tfont-size: 0.9em;\n\tmargin: 4px 0 8px;\n}\n\n.cn-cospend-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-cospend-tab__empty-icon {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-cospend-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/* Coloured left rail per row type, echoing Cospend's project/bill split. */\n.cn-cospend-tab__row {\n\tborder-radius: var(--border-radius);\n}\n\n.cn-cospend-tab__row--bill {\n\tborder-left: 3px solid var(--color-warning, #e9a40f);\n}\n\n.cn-cospend-tab__row--project {\n\tborder-left: 3px solid var(--color-primary-element, #21468B);\n}\n\n.cn-cospend-tab__row-icon {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tjustify-content: center;\n\twidth: 32px;\n\theight: 32px;\n\tborder-radius: 50%;\n\tcolor: var(--color-main-background);\n}\n\n.cn-cospend-tab__row-icon--bill {\n\tbackground: var(--color-warning, #e9a40f);\n}\n\n.cn-cospend-tab__row-icon--project {\n\tbackground: var(--color-primary-element, #21468B);\n}\n\n.cn-cospend-tab__title {\n\tfont-weight: 600;\n\tcolor: var(--color-main-text);\n}\n\n.cn-cospend-tab__subline {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 6px;\n\tfont-size: 0.85em;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-cospend-tab__subline-text {\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-cospend-tab__type-chip {\n\tflex-shrink: 0;\n}\n\n.cn-cospend-tab__amount {\n\tfont-weight: 700;\n\tfont-size: 0.95em;\n\twhite-space: nowrap;\n\tcolor: var(--color-main-text);\n}\n\n.cn-cospend-tab__amount--bill {\n\tcolor: var(--color-warning-text, var(--color-main-text));\n}\n\n.cn-cospend-tab__totals {\n\tdisplay: flex;\n\talign-items: flex-start;\n\tjustify-content: space-between;\n\tgap: 8px;\n\tmargin-top: 8px;\n\tpadding: 8px 10px;\n\tborder-top: 1px solid var(--color-border);\n}\n\n.cn-cospend-tab__totals-label {\n\tfont-weight: 600;\n\tcolor: var(--color-main-text);\n\ttext-transform: uppercase;\n\tfont-size: 0.78em;\n\tletter-spacing: 0.04em;\n}\n\n.cn-cospend-tab__totals-list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n\ttext-align: right;\n}\n\n.cn-cospend-tab__totals-row {\n\tdisplay: flex;\n\talign-items: baseline;\n\tjustify-content: flex-end;\n\tgap: 8px;\n\tfont-size: 0.85em;\n}\n\n.cn-cospend-tab__totals-currency {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-cospend-tab__totals-amount {\n\tfont-weight: 700;\n\tcolor: var(--color-main-text);\n}\n</style>\n"],"names":["_openBlock","_createElementBlock","_createVNode","_createTextVNode","_createBlock","_toDisplayString","_createElementVNode","_normalizeClass"],"mappings":"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"}
1
+ {"version":3,"file":"CnCospendTab.vue3.js","sources":["../../../../../src/integrations/builtin/cospend/CnCospendTab.vue?vue&type=template&id=037c757e&lang.js"],"sourcesContent":["<!--\n CnCospendTab — bespoke sidebar tab for the `cospend` integration.\n\n Replaces the generic CnIntegrationTab for the `cospend` leaf: renders\n the NC Cospend rows linked to the parent OR object as a Cospend-style\n list of projects and bills.\n Cospend exposes BOTH project rows AND bill rows in the same payload\n (per wave 2.3 design — see `CospendProvider.php`); each row carries\n a `type` discriminator (`'project'` | `'bill'`) which this tab shows\n as a `[Project]` / `[Bill]` status badge alongside the title.\n\n Rows render via NcListItem to mirror real Cospend:\n - project: name + [Project] badge + currency, payer (when present)\n - bill: title + [Bill] badge + payer · date subline, prominent\n right-aligned amount with currency\n A per-currency total footer summarises the linked spend, matching\n Cospend's project balance row.\n\n Clicking a project row deep-links to\n /index.php/apps/cospend/p/{id}\n Bills inherit the same URL (Cospend doesn't have a stable per-bill\n permalink — the project page lists its bills); a row's own `url`\n field wins if the provider already constructed one.\n\n Talks to the OpenRegister pluggable-integration sub-resource\n `/api/objects/{register}/{schema}/{objectId}/integrations/cospend`\n served by `OCA\\OpenRegister\\Service\\Integration\\Providers\\CospendProvider`\n (link-table strategy — marker `[or:{uuid}]` in `cospend_projects.name`).\n\n Surface behaviour (per ADR-017 graceful degradation):\n - Empty state with \"Open Costs\" CTA when no linked rows.\n - Loading spinner during fetch.\n - 503 \"currently unavailable\" banner when Cospend is down.\n - Generic error label when fetch throws.\n\n Bespoke-vs-generic rationale: the generic tab renders a flat link\n list which loses Cospend's primary signals — *whether the row is a\n project or a bill*, the amount, and the currency. Surfacing them\n inline lets case handlers triage spending at a glance without\n context-switching into Cospend.\n\n See `openregister/openspec/changes/integration-cospend/` for the\n spec delta, ADR-019 (registry mechanism), ADR-022 (sidebar tab\n contract), `openregister#1324` for provider follow-ups.\n-->\n<template>\n\t<div class=\"cn-sidebar-tab cn-cospend-tab\">\n\t\t<div v-if=\"degraded\" class=\"cn-cospend-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 v-if=\"!degraded\" class=\"cn-cospend-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 project') }}\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 project') }}\n\t\t\t</NcButton>\n\t\t</div>\n\n\t\t<NcLoadingIcon v-if=\"loading\" />\n\t\t<div v-else-if=\"error\" class=\"cn-cospend-tab__error\" role=\"alert\">\n\t\t\t{{ error }}\n\t\t</div>\n\t\t<div v-else-if=\"rows.length === 0\" class=\"cn-sidebar-tab__empty cn-cospend-tab__empty\">\n\t\t\t<CurrencyEur :size=\"32\" class=\"cn-cospend-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=\"cospendAppUrl\"\n\t\t\t\ttarget=\"_blank\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<CurrencyEur :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ openCospendLabel }}\n\t\t\t</NcButton>\n\t\t</div>\n\t\t<template v-else>\n\t\t\t<ul class=\"cn-cospend-tab__list\">\n\t\t\t\t<NcListItem\n\t\t\t\t\tv-for=\"row in rows\"\n\t\t\t\t\t:key=\"rowKey(row)\"\n\t\t\t\t\tclass=\"cn-cospend-tab__row\"\n\t\t\t\t\t:class=\"rowClass(row)\"\n\t\t\t\t\t:href=\"rowUrl(row)\"\n\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t:forceDisplayActions=\"true\">\n\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t<span class=\"cn-cospend-tab__row-icon\" :class=\"iconClass(row)\">\n\t\t\t\t\t\t\t<CashMultiple v-if=\"rowType(row) === 'bill'\" :size=\"20\" />\n\t\t\t\t\t\t\t<FolderOutline v-else :size=\"20\" />\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</template>\n\t\t\t\t\t<template #name>\n\t\t\t\t\t\t<span class=\"cn-cospend-tab__title\">{{ rowTitle(row) }}</span>\n\t\t\t\t\t</template>\n\t\t\t\t\t<template #subname>\n\t\t\t\t\t\t<span class=\"cn-cospend-tab__subline\">\n\t\t\t\t\t\t\t<CnStatusBadge\n\t\t\t\t\t\t\t\tclass=\"cn-cospend-tab__type-chip\"\n\t\t\t\t\t\t\t\t:class=\"typeChipClass(row)\"\n\t\t\t\t\t\t\t\tsize=\"small\"\n\t\t\t\t\t\t\t\t:variant=\"typeVariant(row)\"\n\t\t\t\t\t\t\t\t:label=\"typeLabel(row)\" />\n\t\t\t\t\t\t\t<span v-if=\"sublineText(row)\" class=\"cn-cospend-tab__subline-text\">\n\t\t\t\t\t\t\t\t{{ sublineText(row) }}\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</template>\n\t\t\t\t\t<template #indicator>\n\t\t\t\t\t\t<span v-if=\"amountLabel(row)\" class=\"cn-cospend-tab__amount\" :class=\"amountClass(row)\">\n\t\t\t\t\t\t\t{{ amountLabel(row) }}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</template>\n\t\t\t\t\t<template #actions>\n\t\t\t\t\t\t<NcActionButton\n\t\t\t\t\t\t\tv-if=\"entryIdOf(row)\"\n\t\t\t\t\t\t\t:closeAfterClick=\"true\"\n\t\t\t\t\t\t\t@click=\"unlinkEntry(row)\">\n\t\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t\t<LinkOff :size=\"20\" />\n\t\t\t\t\t\t\t</template>\n\t\t\t\t\t\t\t{{ t('nextcloud-vue', 'Unlink entry') }}\n\t\t\t\t\t\t</NcActionButton>\n\t\t\t\t\t</template>\n\t\t\t\t</NcListItem>\n\t\t\t</ul>\n\n\t\t\t<div v-if=\"totals.length > 0\" class=\"cn-cospend-tab__totals\">\n\t\t\t\t<span class=\"cn-cospend-tab__totals-label\">{{ t('nextcloud-vue', 'Total') }}</span>\n\t\t\t\t<ul class=\"cn-cospend-tab__totals-list\">\n\t\t\t\t\t<li\n\t\t\t\t\t\tv-for=\"bucket in totals\"\n\t\t\t\t\t\t:key=\"bucket.currency\"\n\t\t\t\t\t\tclass=\"cn-cospend-tab__totals-row\">\n\t\t\t\t\t\t<span class=\"cn-cospend-tab__totals-currency\">{{ bucket.currency }}</span>\n\t\t\t\t\t\t<span class=\"cn-cospend-tab__totals-amount\">{{ bucket.amountLabel }}</span>\n\t\t\t\t\t</li>\n\t\t\t\t</ul>\n\t\t\t</div>\n\t\t</template>\n\n\t\t<CnCospendPicker\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<CnCospendCreate\n\t\t\tv-if=\"createOpen\"\n\t\t\t:apiBase=\"apiBase\"\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, NcButton, NcListItem, NcLoadingIcon } from '@nextcloud/vue'\nimport AlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue'\nimport CashMultiple from 'vue-material-design-icons/CashMultiple.vue'\nimport CurrencyEur from 'vue-material-design-icons/CurrencyEur.vue'\nimport FolderOutline from 'vue-material-design-icons/FolderOutline.vue'\nimport LinkOff from 'vue-material-design-icons/LinkOff.vue'\nimport LinkVariant from 'vue-material-design-icons/LinkVariant.vue'\nimport Plus from 'vue-material-design-icons/Plus.vue'\nimport CnCospendCreate from '../../../components/CnCospendCreate/CnCospendCreate.vue'\nimport CnCospendPicker from '../../../components/CnCospendPicker/CnCospendPicker.vue'\nimport CnStatusBadge from '../../../components/CnStatusBadge/CnStatusBadge.vue'\nimport { buildHeaders, prefixUrl, safeHref } from '../../../utils/index.js'\n\n/**\n * CnCospendTab — bespoke sidebar tab for the `cospend` integration.\n *\n * Renders linked Cospend projects + bills with a type badge, amount,\n * currency, payer, date and a per-currency total footer. See the\n * file-level docblock for surface behaviour.\n */\nexport default {\n\tname: 'CnCospendTab',\n\n\tcomponents: {\n\t\tNcActionButton,\n\t\tNcButton,\n\t\tNcListItem,\n\t\tNcLoadingIcon,\n\t\tAlertCircleOutline,\n\t\tCashMultiple,\n\t\tCurrencyEur,\n\t\tFolderOutline,\n\t\tLinkOff,\n\t\tLinkVariant,\n\t\tPlus,\n\t\tCnStatusBadge,\n\t\tCnCospendPicker,\n\t\tCnCospendCreate,\n\t},\n\n\tprops: {\n\t\t/** Stable integration id (forwarded from the registry — always `'cospend'`). */\n\t\tintegrationId: { type: String, default: 'cospend' },\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 costs linked yet') },\n\t\t/** Pre-translated label for the \"Open Costs\" CTA. */\n\t\topenCospendLabel: { type: String, default: () => t('nextcloud-vue', 'Open Costs') },\n\t\t/** Pre-translated banner when Cospend is unavailable. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Costs is currently unavailable.') },\n\t\t/** URL of the NC Cospend app entry. */\n\t\tcospendAppUrl: { type: String, default: '/index.php/apps/cospend' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\trows: [],\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\tcomputed: {\n\t\t/**\n\t\t * Per-currency totals of every row carrying an amount, mirroring\n\t\t * the balance row Cospend renders under a project.\n\t\t *\n\t\t * @return {Array<{currency: string, amountLabel: string}>}\n\t\t */\n\t\ttotals() {\n\t\t\tconst groups = new Map()\n\t\t\tfor (const row of this.rows) {\n\t\t\t\tconst amount = this.amountRaw(row)\n\t\t\t\tif (amount === null) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tconst currency = this.currencyOf(row) || ''\n\t\t\t\tconst existing = groups.get(currency) || { currency, total: 0 }\n\t\t\t\texisting.total += amount\n\t\t\t\tgroups.set(currency, existing)\n\t\t\t}\n\t\t\treturn Array.from(groups.values()).map((b) => {\n\t\t\t\tconst code = b.currency || t('nextcloud-vue', 'Unspecified')\n\t\t\t\treturn {\n\t\t\t\t\tcurrency: code,\n\t\t\t\t\tamountLabel: b.currency ? `${b.total.toFixed(2)} ${b.currency}` : b.total.toFixed(2),\n\t\t\t\t}\n\t\t\t})\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.fetchRows()\n\t\t\t}\n\t\t} },\n\n\t\tregister() {\n\t\t\tthis.fetchRows()\n\t\t},\n\n\t\tschema() {\n\t\t\tthis.fetchRows()\n\t\t},\n\t},\n\n\tmethods: {\n\t\tbaseUrl() {\n\t\t\treturn prefixUrl(`${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/integrations/${this.integrationId}`)\n\t\t},\n\n\t\t/**\n\t\t * Base for the Tier-2 cospend endpoints (list/link/new/destroy).\n\t\t *\n\t\t * @return {string} The endpoint URL.\n\t\t */\n\t\tcospendEndpoint() {\n\t\t\treturn prefixUrl(`${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/cospend`)\n\t\t},\n\n\t\trowKey(row) {\n\t\t\treturn row.id ?? row.uuid ?? ''\n\t\t},\n\n\t\t/**\n\t\t * Resolve the link-row id for a Tier-2 row (used for unlink).\n\t\t * Legacy marker rows have no `entryId` so the unlink button hides.\n\t\t *\n\t\t * @param {object} row Provider/link row.\n\t\t *\n\t\t * @return {(number|string)} The link row id, or '' when absent.\n\t\t */\n\t\tentryIdOf(row) {\n\t\t\tconst d = (row && typeof row.data === 'object' && row.data !== null) ? row.data : {}\n\t\t\treturn row.entryId ?? d.id ?? ''\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.cospendEndpoint(), {\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.fetchRows()\n\t\t\t\t} else if (response.status === 409) {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'This entry is already linked.')\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not link entry.')\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('[CnCospendTab] link failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not link entry.')\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.cospendEndpoint()}/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.fetchRows()\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not create project.')\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('[CnCospendTab] create failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not create project.')\n\t\t\t}\n\t\t},\n\n\t\tasync unlinkEntry(row) {\n\t\t\tconst id = this.entryIdOf(row)\n\t\t\tif (!id) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(`${this.cospendEndpoint()}/${id}`, {\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.fetchRows()\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not unlink entry.')\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('[CnCospendTab] unlink failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not unlink entry.')\n\t\t\t}\n\t\t},\n\n\t\trowTitle(row) {\n\t\t\treturn row.title ?? row.name ?? this.rowKey(row)\n\t\t},\n\n\t\trowUrl(row) {\n\t\t\tif (row.url && safeHref(row.url) !== '#') {\n\t\t\t\treturn row.url\n\t\t\t}\n\t\t\tconst projectId = row.projectId ?? row.project_id ?? row.data?.projectid ?? row.data?.project_id ?? null\n\t\t\tif (projectId !== null && projectId !== '') {\n\t\t\t\treturn `/index.php/apps/cospend/p/${projectId}`\n\t\t\t}\n\t\t\tconst id = this.rowKey(row)\n\t\t\tif (id !== '') {\n\t\t\t\treturn `/index.php/apps/cospend/p/${id}`\n\t\t\t}\n\t\t\treturn this.cospendAppUrl\n\t\t},\n\n\t\t/**\n\t\t * Discriminator. Defaults to 'project' when the provider doesn't\n\t\t * surface a `type` field (the current shipping CospendProvider\n\t\t * only returns projects); when wave-2.3 ships bills the chip\n\t\t * label flips accordingly.\n\t\t *\n\t\t * @param {object} row Provider row.\n\t\t * @return {'project'|'bill'} Row type.\n\t\t */\n\t\trowType(row) {\n\t\t\tconst t = String(row.type ?? row.kind ?? '').toLowerCase()\n\t\t\tif (t === 'bill' || t === 'expense') {\n\t\t\t\treturn 'bill'\n\t\t\t}\n\t\t\treturn 'project'\n\t\t},\n\n\t\ttypeLabel(row) {\n\t\t\treturn this.rowType(row) === 'bill'\n\t\t\t\t? t('nextcloud-vue', 'Bill')\n\t\t\t\t: t('nextcloud-vue', 'Project')\n\t\t},\n\n\t\ttypeChipClass(row) {\n\t\t\treturn this.rowType(row) === 'bill'\n\t\t\t\t? 'cn-cospend-tab__type-chip--bill'\n\t\t\t\t: 'cn-cospend-tab__type-chip--project'\n\t\t},\n\n\t\ttypeVariant(row) {\n\t\t\treturn this.rowType(row) === 'bill' ? 'warning' : 'primary'\n\t\t},\n\n\t\trowClass(row) {\n\t\t\treturn this.rowType(row) === 'bill'\n\t\t\t\t? 'cn-cospend-tab__row--bill'\n\t\t\t\t: 'cn-cospend-tab__row--project'\n\t\t},\n\n\t\ticonClass(row) {\n\t\t\treturn this.rowType(row) === 'bill'\n\t\t\t\t? 'cn-cospend-tab__row-icon--bill'\n\t\t\t\t: 'cn-cospend-tab__row-icon--project'\n\t\t},\n\n\t\tamountClass(row) {\n\t\t\treturn this.rowType(row) === 'bill'\n\t\t\t\t? 'cn-cospend-tab__amount--bill'\n\t\t\t\t: 'cn-cospend-tab__amount--project'\n\t\t},\n\n\t\tamountRaw(row) {\n\t\t\tconst raw = row.amount ?? row.data?.amount ?? null\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.isNaN(num) ? null : num\n\t\t},\n\n\t\tcurrencyOf(row) {\n\t\t\treturn row.currency ?? row.currency_name ?? row.data?.currency_name ?? row.data?.currency ?? ''\n\t\t},\n\n\t\tamountLabel(row) {\n\t\t\tconst raw = row.amount ?? row.data?.amount ?? null\n\t\t\tif (raw === null || raw === undefined || raw === '') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst currency = this.currencyOf(row)\n\t\t\tconst num = Number(raw)\n\t\t\tif (Number.isNaN(num)) {\n\t\t\t\treturn currency ? `${raw} ${currency}` : String(raw)\n\t\t\t}\n\t\t\tconst fixed = num.toFixed(2)\n\t\t\treturn currency ? `${fixed} ${currency}` : fixed\n\t\t},\n\n\t\tpayerLabel(row) {\n\t\t\treturn row.payer ?? row.user_id ?? row.data?.user_id ?? row.data?.payer ?? ''\n\t\t},\n\n\t\tdateLabel(row) {\n\t\t\tconst raw = row.date ?? row.created_at ?? row.data?.date ?? row.data?.timestamp ?? null\n\t\t\tif (!raw) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst date = typeof raw === 'number'\n\t\t\t\t? new Date(raw * 1000)\n\t\t\t\t: new Date(raw)\n\t\t\tif (Number.isNaN(date.getTime())) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn date.toLocaleDateString(undefined, { dateStyle: 'medium' })\n\t\t},\n\n\t\t/**\n\t\t * Subline shown under the title. For bills this is the payer and\n\t\t * date joined with a middot (\"alice · 20 May 2026\"); for projects\n\t\t * it is the currency (or payer) when present.\n\t\t *\n\t\t * @param {object} row Provider row.\n\t\t * @return {string} Subline text, or '' when nothing to show.\n\t\t */\n\t\tsublineText(row) {\n\t\t\tconst parts = []\n\t\t\tif (this.rowType(row) === 'bill') {\n\t\t\t\tconst payer = this.payerLabel(row)\n\t\t\t\tif (payer) {\n\t\t\t\t\tparts.push(payer)\n\t\t\t\t}\n\t\t\t\tconst date = this.dateLabel(row)\n\t\t\t\tif (date) {\n\t\t\t\t\tparts.push(date)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tconst currency = this.currencyOf(row)\n\t\t\t\tif (currency) {\n\t\t\t\t\tparts.push(currency)\n\t\t\t\t}\n\t\t\t\tconst payer = this.payerLabel(row)\n\t\t\t\tif (payer) {\n\t\t\t\t\tparts.push(payer)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn parts.join(' · ')\n\t\t},\n\n\t\tasync fetchRows() {\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.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\tconst rows = data.results || data.items || (Array.isArray(data) ? data : []) || []\n\t\t\t\t\tthis.rows = rows\n\t\t\t\t} else if (response.status === 503 || response.status === 501) {\n\t\t\t\t\tthis.rows = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.rows = []\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load costs.')\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('[CnCospendTab] failed to fetch rows', err)\n\t\t\t\tthis.rows = []\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load costs.')\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-cospend-tab__actions {\n\tdisplay: flex;\n\tgap: 8px;\n\tmargin-bottom: 10px;\n\tflex-wrap: wrap;\n}\n\n.cn-cospend-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-cospend-tab__error {\n\tcolor: var(--color-error);\n\tfont-size: 0.9em;\n\tmargin: 4px 0 8px;\n}\n\n.cn-cospend-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-cospend-tab__empty-icon {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-cospend-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/* Coloured left rail per row type, echoing Cospend's project/bill split. */\n.cn-cospend-tab__row {\n\tborder-radius: var(--border-radius);\n}\n\n.cn-cospend-tab__row--bill {\n\tborder-left: 3px solid var(--color-warning, #e9a40f);\n}\n\n.cn-cospend-tab__row--project {\n\tborder-left: 3px solid var(--color-primary-element, #21468B);\n}\n\n.cn-cospend-tab__row-icon {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tjustify-content: center;\n\twidth: 32px;\n\theight: 32px;\n\tborder-radius: 50%;\n\tcolor: var(--color-main-background);\n}\n\n.cn-cospend-tab__row-icon--bill {\n\tbackground: var(--color-warning, #e9a40f);\n}\n\n.cn-cospend-tab__row-icon--project {\n\tbackground: var(--color-primary-element, #21468B);\n}\n\n.cn-cospend-tab__title {\n\tfont-weight: 600;\n\tcolor: var(--color-main-text);\n}\n\n.cn-cospend-tab__subline {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 6px;\n\tfont-size: 0.85em;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-cospend-tab__subline-text {\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-cospend-tab__type-chip {\n\tflex-shrink: 0;\n}\n\n.cn-cospend-tab__amount {\n\tfont-weight: 700;\n\tfont-size: 0.95em;\n\twhite-space: nowrap;\n\tcolor: var(--color-main-text);\n}\n\n.cn-cospend-tab__amount--bill {\n\tcolor: var(--color-warning-text, var(--color-main-text));\n}\n\n.cn-cospend-tab__totals {\n\tdisplay: flex;\n\talign-items: flex-start;\n\tjustify-content: space-between;\n\tgap: 8px;\n\tmargin-top: 8px;\n\tpadding: 8px 10px;\n\tborder-top: 1px solid var(--color-border);\n}\n\n.cn-cospend-tab__totals-label {\n\tfont-weight: 600;\n\tcolor: var(--color-main-text);\n\ttext-transform: uppercase;\n\tfont-size: 0.78em;\n\tletter-spacing: 0.04em;\n}\n\n.cn-cospend-tab__totals-list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n\ttext-align: right;\n}\n\n.cn-cospend-tab__totals-row {\n\tdisplay: flex;\n\talign-items: baseline;\n\tjustify-content: flex-end;\n\tgap: 8px;\n\tfont-size: 0.85em;\n}\n\n.cn-cospend-tab__totals-currency {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-cospend-tab__totals-amount {\n\tfont-weight: 700;\n\tcolor: var(--color-main-text);\n}\n</style>\n"],"names":["_openBlock","_createElementBlock","_createElementVNode","_createVNode","_createTextVNode","_createBlock","_toDisplayString","_normalizeClass"],"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 ViewColumnOutline from 'vue-material-design-icons/ViewColumnOutline.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'];
@@ -168,7 +169,7 @@ var script = {
168
169
  },
169
170
 
170
171
  cardUrl(card) {
171
- if (card.url) {
172
+ if (card.url && safeHref(card.url) !== '#') {
172
173
  return card.url
173
174
  }
174
175
  const boardId = card.boardId ?? card.board?.id ?? '';
@@ -1 +1 @@
1
- {"version":3,"file":"CnDeckCard.vue2.js","sources":["../../../../../src/integrations/builtin/deck/CnDeckCard.vue"],"sourcesContent":["<!--\n CnDeckCard — bespoke surface-aware widget for the `deck` integration.\n\n Replaces the generic CnIntegrationCard for the `deck` leaf. Branches on\n `surface` per AD-19:\n - user-dashboard / app-dashboard : headline \"N cards across M stacks\";\n secondary line names the most recent card.\n - detail-page : mini three-column kanban view of\n the linked card's board with the linked card highlighted in its\n current stack (per design.md AD-2).\n - single-entity : chip with card title + stack name\n (referenceType: 'deck').\n\n Pulls rows from the same OR pluggable-integration sub-resource as\n CnDeckTab; for `single-entity` the optional `value` prop addresses a\n single card by id (matching CnIntegrationCard's fetchSingle contract).\n\n See `openregister/openspec/changes/integration-deck/` for the spec\n delta and ADR-019 (registry mechanism), AD-19 (surface fallback),\n AD-2 (mini-kanban on detail-page).\n-->\n<template>\n\t<CnDetailCard :title=\"cardHeaderTitle\" :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-deck-card__chip\" :title=\"chipSubtitle(entity)\">\n\t\t\t\t<ViewColumnOutline :size=\"14\" />\n\t\t\t\t<a\n\t\t\t\t\t:href=\"cardUrl(entity)\"\n\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\trel=\"noopener\">{{ cardTitle(entity) }}</a>\n\t\t\t\t<span v-if=\"stackTitle(entity)\" class=\"cn-deck-card__chip-stack\">\n\t\t\t\t\t· {{ stackTitle(entity) }}\n\t\t\t\t</span>\n\t\t\t</span>\n\t\t\t<span v-else class=\"cn-deck-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-deck-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"cards.length === 0\" class=\"cn-deck-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<div v-else class=\"cn-deck-card__headline\">\n\t\t\t\t<div class=\"cn-deck-card__headline-line\">\n\t\t\t\t\t<strong>{{ countHeadline }}</strong>\n\t\t\t\t</div>\n\t\t\t\t<ul v-if=\"stackDistribution.length > 0\" class=\"cn-deck-card__distribution\">\n\t\t\t\t\t<li\n\t\t\t\t\t\tv-for=\"bucket in stackDistribution\"\n\t\t\t\t\t\t:key=\"bucket.key\"\n\t\t\t\t\t\tclass=\"cn-deck-card__distribution-row\">\n\t\t\t\t\t\t<span class=\"cn-deck-card__distribution-label\">{{ bucket.label }}</span>\n\t\t\t\t\t\t<span class=\"cn-deck-card__distribution-count\">{{ bucket.count }}</span>\n\t\t\t\t\t</li>\n\t\t\t\t</ul>\n\t\t\t\t<div v-if=\"mostRecent\" class=\"cn-deck-card__headline-recent\">\n\t\t\t\t\t<ViewColumnOutline :size=\"14\" />\n\t\t\t\t\t<a\n\t\t\t\t\t\t:href=\"cardUrl(mostRecent)\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener\">{{ cardTitle(mostRecent) }}</a>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</template>\n\n\t\t<!-- detail-page surface: mini-kanban -->\n\t\t<template v-else>\n\t\t\t<div v-if=\"degraded\" class=\"cn-deck-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"cards.length === 0\" class=\"cn-deck-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<div v-else class=\"cn-deck-card__kanban\">\n\t\t\t\t<section\n\t\t\t\t\tv-for=\"stack in miniColumns\"\n\t\t\t\t\t:key=\"stack.key\"\n\t\t\t\t\tclass=\"cn-deck-card__column\">\n\t\t\t\t\t<header class=\"cn-deck-card__column-header\">\n\t\t\t\t\t\t<span class=\"cn-deck-card__column-title\">{{ stack.label }}</span>\n\t\t\t\t\t\t<span class=\"cn-deck-card__column-count\">{{ stack.cards.length }}</span>\n\t\t\t\t\t</header>\n\t\t\t\t\t<ul class=\"cn-deck-card__column-list\">\n\t\t\t\t\t\t<li\n\t\t\t\t\t\t\tv-for=\"card in stack.cards\"\n\t\t\t\t\t\t\t:key=\"cardKey(card)\"\n\t\t\t\t\t\t\tclass=\"cn-deck-card__row\"\n\t\t\t\t\t\t\t:class=\"{ 'cn-deck-card__row--highlight': isLinked(card) }\">\n\t\t\t\t\t\t\t<a\n\t\t\t\t\t\t\t\t:href=\"cardUrl(card)\"\n\t\t\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t\t\trel=\"noopener\"\n\t\t\t\t\t\t\t\tclass=\"cn-deck-card__title\">{{ cardTitle(card) }}</a>\n\t\t\t\t\t\t</li>\n\t\t\t\t\t</ul>\n\t\t\t\t</section>\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 ViewColumnOutline from 'vue-material-design-icons/ViewColumnOutline.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']\n\n/**\n * CnDeckCard — bespoke surface-aware widget for the `deck` integration.\n *\n * Renders Deck-aware metadata across all four surfaces. See the\n * file-level docblock for surface-by-surface behaviour.\n */\nexport default {\n\tname: 'CnDeckCard',\n\n\tcomponents: { CnDetailCard, NcLoadingIcon, ViewColumnOutline },\n\n\tprops: {\n\t\t/** Stable integration id (forwarded from the registry — always `'deck'`). */\n\t\tintegrationId: { type: String, default: 'deck' },\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 (card id). */\n\t\tvalue: { type: [String, Number], default: '' },\n\t\t/** Pre-translated card title. */\n\t\ttitle: { type: String, default: () => t('nextcloud-vue', 'Cards') },\n\t\t/** Optional Material Design Icon component. */\n\t\ticon: { type: Object, default: () => ViewColumnOutline },\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 cards linked yet') },\n\t\t/** Pre-translated unavailable label. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Deck is currently unavailable.') },\n\t\t/** URL of the NC Deck app entry. */\n\t\tdeckAppUrl: { type: String, default: '/index.php/apps/deck' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tcards: [],\n\t\t\tentity: null,\n\t\t\tloading: false,\n\t\t\tdegraded: '',\n\t\t}\n\t},\n\n\tcomputed: {\n\t\tcardHeaderTitle() {\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\t/**\n\t\t * Cards grouped by stack for both the mini-kanban (detail-page)\n\t\t * and the stack-distribution list (dashboard surfaces).\n\t\t *\n\t\t * @return {Array<{key: string, label: string, cards: Array}>}\n\t\t */\n\t\tcolumns() {\n\t\t\tconst groups = new Map()\n\t\t\tfor (const card of this.cards) {\n\t\t\t\tconst stackId = card.stackId ?? card.stack?.id ?? 'unstacked'\n\t\t\t\tconst stackLabel = card.stackTitle\n\t\t\t\t\t?? card.stack?.title\n\t\t\t\t\t?? (stackId === 'unstacked'\n\t\t\t\t\t\t? t('nextcloud-vue', 'No stack')\n\t\t\t\t\t\t: t('nextcloud-vue', 'Stack {n}', { n: stackId }))\n\t\t\t\tconst key = String(stackId)\n\t\t\t\tif (!groups.has(key)) {\n\t\t\t\t\tgroups.set(key, { key, label: stackLabel, cards: [] })\n\t\t\t\t}\n\t\t\t\tgroups.get(key).cards.push(card)\n\t\t\t}\n\t\t\treturn Array.from(groups.values())\n\t\t},\n\n\t\t/**\n\t\t * Up to three columns for the mini-kanban view on `detail-page`.\n\t\t *\n\t\t * Per AD-2 (design.md): when the linked card has its own stack\n\t\t * we always include that stack; otherwise the first three stacks\n\t\t * by first-seen order are surfaced.\n\t\t *\n\t\t * @return {Array<{key: string, label: string, cards: Array}>}\n\t\t */\n\t\tminiColumns() {\n\t\t\treturn this.columns.slice(0, 3)\n\t\t},\n\n\t\tstackDistribution() {\n\t\t\treturn this.columns.map((col) => ({\n\t\t\t\tkey: col.key,\n\t\t\t\tlabel: col.label,\n\t\t\t\tcount: col.cards.length,\n\t\t\t}))\n\t\t},\n\n\t\tcountHeadline() {\n\t\t\tconst total = this.cards.length\n\t\t\tconst stackCount = this.columns.length\n\t\t\tif (stackCount <= 1) {\n\t\t\t\treturn n('nextcloud-vue', '{count} card', '{count} cards', total, { count: total })\n\t\t\t}\n\t\t\tconst cardFragment = n('nextcloud-vue', '{count} card', '{count} cards', total, { count: total })\n\t\t\tconst stacksFragment = n('nextcloud-vue', 'across {count} stack', 'across {count} stacks', stackCount, { count: stackCount })\n\t\t\treturn `${cardFragment} ${stacksFragment}`\n\t\t},\n\n\t\tmostRecent() {\n\t\t\tif (this.cards.length === 0) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst sorted = [...this.cards].sort((a, b) => {\n\t\t\t\tconst ta = Date.parse(a.linkedAt ?? a.lastActivity ?? '') || 0\n\t\t\t\tconst tb = Date.parse(b.linkedAt ?? b.lastActivity ?? '') || 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\tcardKey(card) {\n\t\t\treturn card.cardId ?? card.id ?? ''\n\t\t},\n\n\t\tcardTitle(card) {\n\t\t\treturn card.cardTitle ?? card.title ?? this.cardKey(card)\n\t\t},\n\n\t\tcardUrl(card) {\n\t\t\tif (card.url) {\n\t\t\t\treturn card.url\n\t\t\t}\n\t\t\tconst boardId = card.boardId ?? card.board?.id ?? ''\n\t\t\tconst cardId = card.cardId ?? card.id ?? ''\n\t\t\tif (boardId && cardId) {\n\t\t\t\treturn `/index.php/apps/deck/board/${boardId}/card/${cardId}`\n\t\t\t}\n\t\t\treturn this.deckAppUrl\n\t\t},\n\n\t\tstackTitle(card) {\n\t\t\treturn card.stackTitle ?? card.stack?.title ?? ''\n\t\t},\n\n\t\tisLinked(card) {\n\t\t\tif (!this.value) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treturn String(this.cardKey(card)) === String(this.value)\n\t\t},\n\n\t\tchipSubtitle(card) {\n\t\t\treturn this.stackTitle(card) || this.cardTitle(card)\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.cards = data.results || data.items || (Array.isArray(data) ? data : []) || []\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.cards = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.cards = []\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('[CnDeckCard] failed to fetch cards', err)\n\t\t\t\tthis.cards = []\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.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('[CnDeckCard] failed to fetch single card', 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-deck-card__empty {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n\tpadding: 8px 0;\n}\n\n.cn-deck-card__headline {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 6px;\n}\n\n.cn-deck-card__headline-line {\n\tfont-size: 1.1em;\n\tcolor: var(--color-main-text);\n}\n\n.cn-deck-card__distribution {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n}\n\n.cn-deck-card__distribution-row {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\tgap: 8px;\n\tfont-size: 0.85em;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-deck-card__distribution-count {\n\tfont-weight: 600;\n\tcolor: var(--color-main-text);\n}\n\n.cn-deck-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-deck-card__headline-recent a {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\n.cn-deck-card__headline-recent a:hover {\n\ttext-decoration: underline;\n}\n\n.cn-deck-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-deck-card__chip a {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\n.cn-deck-card__chip a:hover {\n\ttext-decoration: underline;\n}\n\n.cn-deck-card__chip-stack {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.85em;\n}\n\n.cn-deck-card__kanban {\n\tdisplay: flex;\n\tgap: 8px;\n\talign-items: flex-start;\n}\n\n.cn-deck-card__column {\n\tflex: 1;\n\tmin-width: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n\tpadding: 6px;\n\tborder-radius: var(--border-radius);\n\tbackground: var(--color-background-hover);\n}\n\n.cn-deck-card__column-header {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\tgap: 4px;\n}\n\n.cn-deck-card__column-title {\n\tfont-size: 0.85em;\n\tfont-weight: 600;\n\tcolor: var(--color-main-text);\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-deck-card__column-count {\n\tfont-size: 0.7em;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-deck-card__column-list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 3px;\n}\n\n.cn-deck-card__row {\n\tpadding: 4px 6px;\n\tborder-radius: 4px;\n\tbackground: var(--color-main-background);\n\tborder: 1px solid var(--color-border);\n\tfont-size: 0.8em;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-deck-card__row--highlight {\n\tborder-color: var(--color-primary-element, #21468B);\n\tbackground: var(--color-primary-element-light, var(--color-background-darker));\n\tfont-weight: 600;\n}\n\n.cn-deck-card__title {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\na.cn-deck-card__title:hover {\n\ttext-decoration: underline;\n}\n</style>\n"],"names":["CnDetailCard","t","n"],"mappings":"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"}
1
+ {"version":3,"file":"CnDeckCard.vue2.js","sources":["../../../../../src/integrations/builtin/deck/CnDeckCard.vue"],"sourcesContent":["<!--\n CnDeckCard — bespoke surface-aware widget for the `deck` integration.\n\n Replaces the generic CnIntegrationCard for the `deck` leaf. Branches on\n `surface` per AD-19:\n - user-dashboard / app-dashboard : headline \"N cards across M stacks\";\n secondary line names the most recent card.\n - detail-page : mini three-column kanban view of\n the linked card's board with the linked card highlighted in its\n current stack (per design.md AD-2).\n - single-entity : chip with card title + stack name\n (referenceType: 'deck').\n\n Pulls rows from the same OR pluggable-integration sub-resource as\n CnDeckTab; for `single-entity` the optional `value` prop addresses a\n single card by id (matching CnIntegrationCard's fetchSingle contract).\n\n See `openregister/openspec/changes/integration-deck/` for the spec\n delta and ADR-019 (registry mechanism), AD-19 (surface fallback),\n AD-2 (mini-kanban on detail-page).\n-->\n<template>\n\t<CnDetailCard :title=\"cardHeaderTitle\" :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-deck-card__chip\" :title=\"chipSubtitle(entity)\">\n\t\t\t\t<ViewColumnOutline :size=\"14\" />\n\t\t\t\t<a\n\t\t\t\t\t:href=\"cardUrl(entity)\"\n\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\trel=\"noopener\">{{ cardTitle(entity) }}</a>\n\t\t\t\t<span v-if=\"stackTitle(entity)\" class=\"cn-deck-card__chip-stack\">\n\t\t\t\t\t· {{ stackTitle(entity) }}\n\t\t\t\t</span>\n\t\t\t</span>\n\t\t\t<span v-else class=\"cn-deck-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-deck-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"cards.length === 0\" class=\"cn-deck-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<div v-else class=\"cn-deck-card__headline\">\n\t\t\t\t<div class=\"cn-deck-card__headline-line\">\n\t\t\t\t\t<strong>{{ countHeadline }}</strong>\n\t\t\t\t</div>\n\t\t\t\t<ul v-if=\"stackDistribution.length > 0\" class=\"cn-deck-card__distribution\">\n\t\t\t\t\t<li\n\t\t\t\t\t\tv-for=\"bucket in stackDistribution\"\n\t\t\t\t\t\t:key=\"bucket.key\"\n\t\t\t\t\t\tclass=\"cn-deck-card__distribution-row\">\n\t\t\t\t\t\t<span class=\"cn-deck-card__distribution-label\">{{ bucket.label }}</span>\n\t\t\t\t\t\t<span class=\"cn-deck-card__distribution-count\">{{ bucket.count }}</span>\n\t\t\t\t\t</li>\n\t\t\t\t</ul>\n\t\t\t\t<div v-if=\"mostRecent\" class=\"cn-deck-card__headline-recent\">\n\t\t\t\t\t<ViewColumnOutline :size=\"14\" />\n\t\t\t\t\t<a\n\t\t\t\t\t\t:href=\"cardUrl(mostRecent)\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener\">{{ cardTitle(mostRecent) }}</a>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</template>\n\n\t\t<!-- detail-page surface: mini-kanban -->\n\t\t<template v-else>\n\t\t\t<div v-if=\"degraded\" class=\"cn-deck-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"cards.length === 0\" class=\"cn-deck-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<div v-else class=\"cn-deck-card__kanban\">\n\t\t\t\t<section\n\t\t\t\t\tv-for=\"stack in miniColumns\"\n\t\t\t\t\t:key=\"stack.key\"\n\t\t\t\t\tclass=\"cn-deck-card__column\">\n\t\t\t\t\t<header class=\"cn-deck-card__column-header\">\n\t\t\t\t\t\t<span class=\"cn-deck-card__column-title\">{{ stack.label }}</span>\n\t\t\t\t\t\t<span class=\"cn-deck-card__column-count\">{{ stack.cards.length }}</span>\n\t\t\t\t\t</header>\n\t\t\t\t\t<ul class=\"cn-deck-card__column-list\">\n\t\t\t\t\t\t<li\n\t\t\t\t\t\t\tv-for=\"card in stack.cards\"\n\t\t\t\t\t\t\t:key=\"cardKey(card)\"\n\t\t\t\t\t\t\tclass=\"cn-deck-card__row\"\n\t\t\t\t\t\t\t:class=\"{ 'cn-deck-card__row--highlight': isLinked(card) }\">\n\t\t\t\t\t\t\t<a\n\t\t\t\t\t\t\t\t:href=\"cardUrl(card)\"\n\t\t\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t\t\trel=\"noopener\"\n\t\t\t\t\t\t\t\tclass=\"cn-deck-card__title\">{{ cardTitle(card) }}</a>\n\t\t\t\t\t\t</li>\n\t\t\t\t\t</ul>\n\t\t\t\t</section>\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 ViewColumnOutline from 'vue-material-design-icons/ViewColumnOutline.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']\n\n/**\n * CnDeckCard — bespoke surface-aware widget for the `deck` integration.\n *\n * Renders Deck-aware metadata across all four surfaces. See the\n * file-level docblock for surface-by-surface behaviour.\n */\nexport default {\n\tname: 'CnDeckCard',\n\n\tcomponents: { CnDetailCard, NcLoadingIcon, ViewColumnOutline },\n\n\tprops: {\n\t\t/** Stable integration id (forwarded from the registry — always `'deck'`). */\n\t\tintegrationId: { type: String, default: 'deck' },\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 (card id). */\n\t\tvalue: { type: [String, Number], default: '' },\n\t\t/** Pre-translated card title. */\n\t\ttitle: { type: String, default: () => t('nextcloud-vue', 'Cards') },\n\t\t/** Optional Material Design Icon component. */\n\t\ticon: { type: Object, default: () => ViewColumnOutline },\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 cards linked yet') },\n\t\t/** Pre-translated unavailable label. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Deck is currently unavailable.') },\n\t\t/** URL of the NC Deck app entry. */\n\t\tdeckAppUrl: { type: String, default: '/index.php/apps/deck' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tcards: [],\n\t\t\tentity: null,\n\t\t\tloading: false,\n\t\t\tdegraded: '',\n\t\t}\n\t},\n\n\tcomputed: {\n\t\tcardHeaderTitle() {\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\t/**\n\t\t * Cards grouped by stack for both the mini-kanban (detail-page)\n\t\t * and the stack-distribution list (dashboard surfaces).\n\t\t *\n\t\t * @return {Array<{key: string, label: string, cards: Array}>}\n\t\t */\n\t\tcolumns() {\n\t\t\tconst groups = new Map()\n\t\t\tfor (const card of this.cards) {\n\t\t\t\tconst stackId = card.stackId ?? card.stack?.id ?? 'unstacked'\n\t\t\t\tconst stackLabel = card.stackTitle\n\t\t\t\t\t?? card.stack?.title\n\t\t\t\t\t?? (stackId === 'unstacked'\n\t\t\t\t\t\t? t('nextcloud-vue', 'No stack')\n\t\t\t\t\t\t: t('nextcloud-vue', 'Stack {n}', { n: stackId }))\n\t\t\t\tconst key = String(stackId)\n\t\t\t\tif (!groups.has(key)) {\n\t\t\t\t\tgroups.set(key, { key, label: stackLabel, cards: [] })\n\t\t\t\t}\n\t\t\t\tgroups.get(key).cards.push(card)\n\t\t\t}\n\t\t\treturn Array.from(groups.values())\n\t\t},\n\n\t\t/**\n\t\t * Up to three columns for the mini-kanban view on `detail-page`.\n\t\t *\n\t\t * Per AD-2 (design.md): when the linked card has its own stack\n\t\t * we always include that stack; otherwise the first three stacks\n\t\t * by first-seen order are surfaced.\n\t\t *\n\t\t * @return {Array<{key: string, label: string, cards: Array}>}\n\t\t */\n\t\tminiColumns() {\n\t\t\treturn this.columns.slice(0, 3)\n\t\t},\n\n\t\tstackDistribution() {\n\t\t\treturn this.columns.map((col) => ({\n\t\t\t\tkey: col.key,\n\t\t\t\tlabel: col.label,\n\t\t\t\tcount: col.cards.length,\n\t\t\t}))\n\t\t},\n\n\t\tcountHeadline() {\n\t\t\tconst total = this.cards.length\n\t\t\tconst stackCount = this.columns.length\n\t\t\tif (stackCount <= 1) {\n\t\t\t\treturn n('nextcloud-vue', '{count} card', '{count} cards', total, { count: total })\n\t\t\t}\n\t\t\tconst cardFragment = n('nextcloud-vue', '{count} card', '{count} cards', total, { count: total })\n\t\t\tconst stacksFragment = n('nextcloud-vue', 'across {count} stack', 'across {count} stacks', stackCount, { count: stackCount })\n\t\t\treturn `${cardFragment} ${stacksFragment}`\n\t\t},\n\n\t\tmostRecent() {\n\t\t\tif (this.cards.length === 0) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst sorted = [...this.cards].sort((a, b) => {\n\t\t\t\tconst ta = Date.parse(a.linkedAt ?? a.lastActivity ?? '') || 0\n\t\t\t\tconst tb = Date.parse(b.linkedAt ?? b.lastActivity ?? '') || 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\tcardKey(card) {\n\t\t\treturn card.cardId ?? card.id ?? ''\n\t\t},\n\n\t\tcardTitle(card) {\n\t\t\treturn card.cardTitle ?? card.title ?? this.cardKey(card)\n\t\t},\n\n\t\tcardUrl(card) {\n\t\t\tif (card.url && safeHref(card.url) !== '#') {\n\t\t\t\treturn card.url\n\t\t\t}\n\t\t\tconst boardId = card.boardId ?? card.board?.id ?? ''\n\t\t\tconst cardId = card.cardId ?? card.id ?? ''\n\t\t\tif (boardId && cardId) {\n\t\t\t\treturn `/index.php/apps/deck/board/${boardId}/card/${cardId}`\n\t\t\t}\n\t\t\treturn this.deckAppUrl\n\t\t},\n\n\t\tstackTitle(card) {\n\t\t\treturn card.stackTitle ?? card.stack?.title ?? ''\n\t\t},\n\n\t\tisLinked(card) {\n\t\t\tif (!this.value) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treturn String(this.cardKey(card)) === String(this.value)\n\t\t},\n\n\t\tchipSubtitle(card) {\n\t\t\treturn this.stackTitle(card) || this.cardTitle(card)\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.cards = data.results || data.items || (Array.isArray(data) ? data : []) || []\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.cards = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.cards = []\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('[CnDeckCard] failed to fetch cards', err)\n\t\t\t\tthis.cards = []\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.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('[CnDeckCard] failed to fetch single card', 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-deck-card__empty {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n\tpadding: 8px 0;\n}\n\n.cn-deck-card__headline {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 6px;\n}\n\n.cn-deck-card__headline-line {\n\tfont-size: 1.1em;\n\tcolor: var(--color-main-text);\n}\n\n.cn-deck-card__distribution {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n}\n\n.cn-deck-card__distribution-row {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\tgap: 8px;\n\tfont-size: 0.85em;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-deck-card__distribution-count {\n\tfont-weight: 600;\n\tcolor: var(--color-main-text);\n}\n\n.cn-deck-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-deck-card__headline-recent a {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\n.cn-deck-card__headline-recent a:hover {\n\ttext-decoration: underline;\n}\n\n.cn-deck-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-deck-card__chip a {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\n.cn-deck-card__chip a:hover {\n\ttext-decoration: underline;\n}\n\n.cn-deck-card__chip-stack {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.85em;\n}\n\n.cn-deck-card__kanban {\n\tdisplay: flex;\n\tgap: 8px;\n\talign-items: flex-start;\n}\n\n.cn-deck-card__column {\n\tflex: 1;\n\tmin-width: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n\tpadding: 6px;\n\tborder-radius: var(--border-radius);\n\tbackground: var(--color-background-hover);\n}\n\n.cn-deck-card__column-header {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\tgap: 4px;\n}\n\n.cn-deck-card__column-title {\n\tfont-size: 0.85em;\n\tfont-weight: 600;\n\tcolor: var(--color-main-text);\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-deck-card__column-count {\n\tfont-size: 0.7em;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-deck-card__column-list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 3px;\n}\n\n.cn-deck-card__row {\n\tpadding: 4px 6px;\n\tborder-radius: 4px;\n\tbackground: var(--color-main-background);\n\tborder: 1px solid var(--color-border);\n\tfont-size: 0.8em;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-deck-card__row--highlight {\n\tborder-color: var(--color-primary-element, #21468B);\n\tbackground: var(--color-primary-element-light, var(--color-background-darker));\n\tfont-weight: 600;\n}\n\n.cn-deck-card__title {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\na.cn-deck-card__title:hover {\n\ttext-decoration: underline;\n}\n</style>\n"],"names":["CnDetailCard","t","n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"CnDeckCard.vue3.js","sources":["../../../../../src/integrations/builtin/deck/CnDeckCard.vue?vue&type=template&id=02981da7&lang.js"],"sourcesContent":["<!--\n CnDeckCard — bespoke surface-aware widget for the `deck` integration.\n\n Replaces the generic CnIntegrationCard for the `deck` leaf. Branches on\n `surface` per AD-19:\n - user-dashboard / app-dashboard : headline \"N cards across M stacks\";\n secondary line names the most recent card.\n - detail-page : mini three-column kanban view of\n the linked card's board with the linked card highlighted in its\n current stack (per design.md AD-2).\n - single-entity : chip with card title + stack name\n (referenceType: 'deck').\n\n Pulls rows from the same OR pluggable-integration sub-resource as\n CnDeckTab; for `single-entity` the optional `value` prop addresses a\n single card by id (matching CnIntegrationCard's fetchSingle contract).\n\n See `openregister/openspec/changes/integration-deck/` for the spec\n delta and ADR-019 (registry mechanism), AD-19 (surface fallback),\n AD-2 (mini-kanban on detail-page).\n-->\n<template>\n\t<CnDetailCard :title=\"cardHeaderTitle\" :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-deck-card__chip\" :title=\"chipSubtitle(entity)\">\n\t\t\t\t<ViewColumnOutline :size=\"14\" />\n\t\t\t\t<a\n\t\t\t\t\t:href=\"cardUrl(entity)\"\n\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\trel=\"noopener\">{{ cardTitle(entity) }}</a>\n\t\t\t\t<span v-if=\"stackTitle(entity)\" class=\"cn-deck-card__chip-stack\">\n\t\t\t\t\t· {{ stackTitle(entity) }}\n\t\t\t\t</span>\n\t\t\t</span>\n\t\t\t<span v-else class=\"cn-deck-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-deck-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"cards.length === 0\" class=\"cn-deck-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<div v-else class=\"cn-deck-card__headline\">\n\t\t\t\t<div class=\"cn-deck-card__headline-line\">\n\t\t\t\t\t<strong>{{ countHeadline }}</strong>\n\t\t\t\t</div>\n\t\t\t\t<ul v-if=\"stackDistribution.length > 0\" class=\"cn-deck-card__distribution\">\n\t\t\t\t\t<li\n\t\t\t\t\t\tv-for=\"bucket in stackDistribution\"\n\t\t\t\t\t\t:key=\"bucket.key\"\n\t\t\t\t\t\tclass=\"cn-deck-card__distribution-row\">\n\t\t\t\t\t\t<span class=\"cn-deck-card__distribution-label\">{{ bucket.label }}</span>\n\t\t\t\t\t\t<span class=\"cn-deck-card__distribution-count\">{{ bucket.count }}</span>\n\t\t\t\t\t</li>\n\t\t\t\t</ul>\n\t\t\t\t<div v-if=\"mostRecent\" class=\"cn-deck-card__headline-recent\">\n\t\t\t\t\t<ViewColumnOutline :size=\"14\" />\n\t\t\t\t\t<a\n\t\t\t\t\t\t:href=\"cardUrl(mostRecent)\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener\">{{ cardTitle(mostRecent) }}</a>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</template>\n\n\t\t<!-- detail-page surface: mini-kanban -->\n\t\t<template v-else>\n\t\t\t<div v-if=\"degraded\" class=\"cn-deck-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"cards.length === 0\" class=\"cn-deck-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<div v-else class=\"cn-deck-card__kanban\">\n\t\t\t\t<section\n\t\t\t\t\tv-for=\"stack in miniColumns\"\n\t\t\t\t\t:key=\"stack.key\"\n\t\t\t\t\tclass=\"cn-deck-card__column\">\n\t\t\t\t\t<header class=\"cn-deck-card__column-header\">\n\t\t\t\t\t\t<span class=\"cn-deck-card__column-title\">{{ stack.label }}</span>\n\t\t\t\t\t\t<span class=\"cn-deck-card__column-count\">{{ stack.cards.length }}</span>\n\t\t\t\t\t</header>\n\t\t\t\t\t<ul class=\"cn-deck-card__column-list\">\n\t\t\t\t\t\t<li\n\t\t\t\t\t\t\tv-for=\"card in stack.cards\"\n\t\t\t\t\t\t\t:key=\"cardKey(card)\"\n\t\t\t\t\t\t\tclass=\"cn-deck-card__row\"\n\t\t\t\t\t\t\t:class=\"{ 'cn-deck-card__row--highlight': isLinked(card) }\">\n\t\t\t\t\t\t\t<a\n\t\t\t\t\t\t\t\t:href=\"cardUrl(card)\"\n\t\t\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t\t\trel=\"noopener\"\n\t\t\t\t\t\t\t\tclass=\"cn-deck-card__title\">{{ cardTitle(card) }}</a>\n\t\t\t\t\t\t</li>\n\t\t\t\t\t</ul>\n\t\t\t\t</section>\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 ViewColumnOutline from 'vue-material-design-icons/ViewColumnOutline.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']\n\n/**\n * CnDeckCard — bespoke surface-aware widget for the `deck` integration.\n *\n * Renders Deck-aware metadata across all four surfaces. See the\n * file-level docblock for surface-by-surface behaviour.\n */\nexport default {\n\tname: 'CnDeckCard',\n\n\tcomponents: { CnDetailCard, NcLoadingIcon, ViewColumnOutline },\n\n\tprops: {\n\t\t/** Stable integration id (forwarded from the registry — always `'deck'`). */\n\t\tintegrationId: { type: String, default: 'deck' },\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 (card id). */\n\t\tvalue: { type: [String, Number], default: '' },\n\t\t/** Pre-translated card title. */\n\t\ttitle: { type: String, default: () => t('nextcloud-vue', 'Cards') },\n\t\t/** Optional Material Design Icon component. */\n\t\ticon: { type: Object, default: () => ViewColumnOutline },\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 cards linked yet') },\n\t\t/** Pre-translated unavailable label. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Deck is currently unavailable.') },\n\t\t/** URL of the NC Deck app entry. */\n\t\tdeckAppUrl: { type: String, default: '/index.php/apps/deck' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tcards: [],\n\t\t\tentity: null,\n\t\t\tloading: false,\n\t\t\tdegraded: '',\n\t\t}\n\t},\n\n\tcomputed: {\n\t\tcardHeaderTitle() {\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\t/**\n\t\t * Cards grouped by stack for both the mini-kanban (detail-page)\n\t\t * and the stack-distribution list (dashboard surfaces).\n\t\t *\n\t\t * @return {Array<{key: string, label: string, cards: Array}>}\n\t\t */\n\t\tcolumns() {\n\t\t\tconst groups = new Map()\n\t\t\tfor (const card of this.cards) {\n\t\t\t\tconst stackId = card.stackId ?? card.stack?.id ?? 'unstacked'\n\t\t\t\tconst stackLabel = card.stackTitle\n\t\t\t\t\t?? card.stack?.title\n\t\t\t\t\t?? (stackId === 'unstacked'\n\t\t\t\t\t\t? t('nextcloud-vue', 'No stack')\n\t\t\t\t\t\t: t('nextcloud-vue', 'Stack {n}', { n: stackId }))\n\t\t\t\tconst key = String(stackId)\n\t\t\t\tif (!groups.has(key)) {\n\t\t\t\t\tgroups.set(key, { key, label: stackLabel, cards: [] })\n\t\t\t\t}\n\t\t\t\tgroups.get(key).cards.push(card)\n\t\t\t}\n\t\t\treturn Array.from(groups.values())\n\t\t},\n\n\t\t/**\n\t\t * Up to three columns for the mini-kanban view on `detail-page`.\n\t\t *\n\t\t * Per AD-2 (design.md): when the linked card has its own stack\n\t\t * we always include that stack; otherwise the first three stacks\n\t\t * by first-seen order are surfaced.\n\t\t *\n\t\t * @return {Array<{key: string, label: string, cards: Array}>}\n\t\t */\n\t\tminiColumns() {\n\t\t\treturn this.columns.slice(0, 3)\n\t\t},\n\n\t\tstackDistribution() {\n\t\t\treturn this.columns.map((col) => ({\n\t\t\t\tkey: col.key,\n\t\t\t\tlabel: col.label,\n\t\t\t\tcount: col.cards.length,\n\t\t\t}))\n\t\t},\n\n\t\tcountHeadline() {\n\t\t\tconst total = this.cards.length\n\t\t\tconst stackCount = this.columns.length\n\t\t\tif (stackCount <= 1) {\n\t\t\t\treturn n('nextcloud-vue', '{count} card', '{count} cards', total, { count: total })\n\t\t\t}\n\t\t\tconst cardFragment = n('nextcloud-vue', '{count} card', '{count} cards', total, { count: total })\n\t\t\tconst stacksFragment = n('nextcloud-vue', 'across {count} stack', 'across {count} stacks', stackCount, { count: stackCount })\n\t\t\treturn `${cardFragment} ${stacksFragment}`\n\t\t},\n\n\t\tmostRecent() {\n\t\t\tif (this.cards.length === 0) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst sorted = [...this.cards].sort((a, b) => {\n\t\t\t\tconst ta = Date.parse(a.linkedAt ?? a.lastActivity ?? '') || 0\n\t\t\t\tconst tb = Date.parse(b.linkedAt ?? b.lastActivity ?? '') || 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\tcardKey(card) {\n\t\t\treturn card.cardId ?? card.id ?? ''\n\t\t},\n\n\t\tcardTitle(card) {\n\t\t\treturn card.cardTitle ?? card.title ?? this.cardKey(card)\n\t\t},\n\n\t\tcardUrl(card) {\n\t\t\tif (card.url) {\n\t\t\t\treturn card.url\n\t\t\t}\n\t\t\tconst boardId = card.boardId ?? card.board?.id ?? ''\n\t\t\tconst cardId = card.cardId ?? card.id ?? ''\n\t\t\tif (boardId && cardId) {\n\t\t\t\treturn `/index.php/apps/deck/board/${boardId}/card/${cardId}`\n\t\t\t}\n\t\t\treturn this.deckAppUrl\n\t\t},\n\n\t\tstackTitle(card) {\n\t\t\treturn card.stackTitle ?? card.stack?.title ?? ''\n\t\t},\n\n\t\tisLinked(card) {\n\t\t\tif (!this.value) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treturn String(this.cardKey(card)) === String(this.value)\n\t\t},\n\n\t\tchipSubtitle(card) {\n\t\t\treturn this.stackTitle(card) || this.cardTitle(card)\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.cards = data.results || data.items || (Array.isArray(data) ? data : []) || []\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.cards = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.cards = []\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('[CnDeckCard] failed to fetch cards', err)\n\t\t\t\tthis.cards = []\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.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('[CnDeckCard] failed to fetch single card', 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-deck-card__empty {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n\tpadding: 8px 0;\n}\n\n.cn-deck-card__headline {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 6px;\n}\n\n.cn-deck-card__headline-line {\n\tfont-size: 1.1em;\n\tcolor: var(--color-main-text);\n}\n\n.cn-deck-card__distribution {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n}\n\n.cn-deck-card__distribution-row {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\tgap: 8px;\n\tfont-size: 0.85em;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-deck-card__distribution-count {\n\tfont-weight: 600;\n\tcolor: var(--color-main-text);\n}\n\n.cn-deck-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-deck-card__headline-recent a {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\n.cn-deck-card__headline-recent a:hover {\n\ttext-decoration: underline;\n}\n\n.cn-deck-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-deck-card__chip a {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\n.cn-deck-card__chip a:hover {\n\ttext-decoration: underline;\n}\n\n.cn-deck-card__chip-stack {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.85em;\n}\n\n.cn-deck-card__kanban {\n\tdisplay: flex;\n\tgap: 8px;\n\talign-items: flex-start;\n}\n\n.cn-deck-card__column {\n\tflex: 1;\n\tmin-width: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n\tpadding: 6px;\n\tborder-radius: var(--border-radius);\n\tbackground: var(--color-background-hover);\n}\n\n.cn-deck-card__column-header {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\tgap: 4px;\n}\n\n.cn-deck-card__column-title {\n\tfont-size: 0.85em;\n\tfont-weight: 600;\n\tcolor: var(--color-main-text);\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-deck-card__column-count {\n\tfont-size: 0.7em;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-deck-card__column-list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 3px;\n}\n\n.cn-deck-card__row {\n\tpadding: 4px 6px;\n\tborder-radius: 4px;\n\tbackground: var(--color-main-background);\n\tborder: 1px solid var(--color-border);\n\tfont-size: 0.8em;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-deck-card__row--highlight {\n\tborder-color: var(--color-primary-element, #21468B);\n\tbackground: var(--color-primary-element-light, var(--color-background-darker));\n\tfont-weight: 600;\n}\n\n.cn-deck-card__title {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\na.cn-deck-card__title:hover {\n\ttext-decoration: underline;\n}\n</style>\n"],"names":["_Fragment","_createElementBlock","_createVNode","_createElementVNode","_toDisplayString","_openBlock","_renderList"],"mappings":"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"}
1
+ {"version":3,"file":"CnDeckCard.vue3.js","sources":["../../../../../src/integrations/builtin/deck/CnDeckCard.vue?vue&type=template&id=02981da7&lang.js"],"sourcesContent":["<!--\n CnDeckCard — bespoke surface-aware widget for the `deck` integration.\n\n Replaces the generic CnIntegrationCard for the `deck` leaf. Branches on\n `surface` per AD-19:\n - user-dashboard / app-dashboard : headline \"N cards across M stacks\";\n secondary line names the most recent card.\n - detail-page : mini three-column kanban view of\n the linked card's board with the linked card highlighted in its\n current stack (per design.md AD-2).\n - single-entity : chip with card title + stack name\n (referenceType: 'deck').\n\n Pulls rows from the same OR pluggable-integration sub-resource as\n CnDeckTab; for `single-entity` the optional `value` prop addresses a\n single card by id (matching CnIntegrationCard's fetchSingle contract).\n\n See `openregister/openspec/changes/integration-deck/` for the spec\n delta and ADR-019 (registry mechanism), AD-19 (surface fallback),\n AD-2 (mini-kanban on detail-page).\n-->\n<template>\n\t<CnDetailCard :title=\"cardHeaderTitle\" :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-deck-card__chip\" :title=\"chipSubtitle(entity)\">\n\t\t\t\t<ViewColumnOutline :size=\"14\" />\n\t\t\t\t<a\n\t\t\t\t\t:href=\"cardUrl(entity)\"\n\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\trel=\"noopener\">{{ cardTitle(entity) }}</a>\n\t\t\t\t<span v-if=\"stackTitle(entity)\" class=\"cn-deck-card__chip-stack\">\n\t\t\t\t\t· {{ stackTitle(entity) }}\n\t\t\t\t</span>\n\t\t\t</span>\n\t\t\t<span v-else class=\"cn-deck-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-deck-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"cards.length === 0\" class=\"cn-deck-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<div v-else class=\"cn-deck-card__headline\">\n\t\t\t\t<div class=\"cn-deck-card__headline-line\">\n\t\t\t\t\t<strong>{{ countHeadline }}</strong>\n\t\t\t\t</div>\n\t\t\t\t<ul v-if=\"stackDistribution.length > 0\" class=\"cn-deck-card__distribution\">\n\t\t\t\t\t<li\n\t\t\t\t\t\tv-for=\"bucket in stackDistribution\"\n\t\t\t\t\t\t:key=\"bucket.key\"\n\t\t\t\t\t\tclass=\"cn-deck-card__distribution-row\">\n\t\t\t\t\t\t<span class=\"cn-deck-card__distribution-label\">{{ bucket.label }}</span>\n\t\t\t\t\t\t<span class=\"cn-deck-card__distribution-count\">{{ bucket.count }}</span>\n\t\t\t\t\t</li>\n\t\t\t\t</ul>\n\t\t\t\t<div v-if=\"mostRecent\" class=\"cn-deck-card__headline-recent\">\n\t\t\t\t\t<ViewColumnOutline :size=\"14\" />\n\t\t\t\t\t<a\n\t\t\t\t\t\t:href=\"cardUrl(mostRecent)\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener\">{{ cardTitle(mostRecent) }}</a>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</template>\n\n\t\t<!-- detail-page surface: mini-kanban -->\n\t\t<template v-else>\n\t\t\t<div v-if=\"degraded\" class=\"cn-deck-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"cards.length === 0\" class=\"cn-deck-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<div v-else class=\"cn-deck-card__kanban\">\n\t\t\t\t<section\n\t\t\t\t\tv-for=\"stack in miniColumns\"\n\t\t\t\t\t:key=\"stack.key\"\n\t\t\t\t\tclass=\"cn-deck-card__column\">\n\t\t\t\t\t<header class=\"cn-deck-card__column-header\">\n\t\t\t\t\t\t<span class=\"cn-deck-card__column-title\">{{ stack.label }}</span>\n\t\t\t\t\t\t<span class=\"cn-deck-card__column-count\">{{ stack.cards.length }}</span>\n\t\t\t\t\t</header>\n\t\t\t\t\t<ul class=\"cn-deck-card__column-list\">\n\t\t\t\t\t\t<li\n\t\t\t\t\t\t\tv-for=\"card in stack.cards\"\n\t\t\t\t\t\t\t:key=\"cardKey(card)\"\n\t\t\t\t\t\t\tclass=\"cn-deck-card__row\"\n\t\t\t\t\t\t\t:class=\"{ 'cn-deck-card__row--highlight': isLinked(card) }\">\n\t\t\t\t\t\t\t<a\n\t\t\t\t\t\t\t\t:href=\"cardUrl(card)\"\n\t\t\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t\t\trel=\"noopener\"\n\t\t\t\t\t\t\t\tclass=\"cn-deck-card__title\">{{ cardTitle(card) }}</a>\n\t\t\t\t\t\t</li>\n\t\t\t\t\t</ul>\n\t\t\t\t</section>\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 ViewColumnOutline from 'vue-material-design-icons/ViewColumnOutline.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']\n\n/**\n * CnDeckCard — bespoke surface-aware widget for the `deck` integration.\n *\n * Renders Deck-aware metadata across all four surfaces. See the\n * file-level docblock for surface-by-surface behaviour.\n */\nexport default {\n\tname: 'CnDeckCard',\n\n\tcomponents: { CnDetailCard, NcLoadingIcon, ViewColumnOutline },\n\n\tprops: {\n\t\t/** Stable integration id (forwarded from the registry — always `'deck'`). */\n\t\tintegrationId: { type: String, default: 'deck' },\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 (card id). */\n\t\tvalue: { type: [String, Number], default: '' },\n\t\t/** Pre-translated card title. */\n\t\ttitle: { type: String, default: () => t('nextcloud-vue', 'Cards') },\n\t\t/** Optional Material Design Icon component. */\n\t\ticon: { type: Object, default: () => ViewColumnOutline },\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 cards linked yet') },\n\t\t/** Pre-translated unavailable label. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Deck is currently unavailable.') },\n\t\t/** URL of the NC Deck app entry. */\n\t\tdeckAppUrl: { type: String, default: '/index.php/apps/deck' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tcards: [],\n\t\t\tentity: null,\n\t\t\tloading: false,\n\t\t\tdegraded: '',\n\t\t}\n\t},\n\n\tcomputed: {\n\t\tcardHeaderTitle() {\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\t/**\n\t\t * Cards grouped by stack for both the mini-kanban (detail-page)\n\t\t * and the stack-distribution list (dashboard surfaces).\n\t\t *\n\t\t * @return {Array<{key: string, label: string, cards: Array}>}\n\t\t */\n\t\tcolumns() {\n\t\t\tconst groups = new Map()\n\t\t\tfor (const card of this.cards) {\n\t\t\t\tconst stackId = card.stackId ?? card.stack?.id ?? 'unstacked'\n\t\t\t\tconst stackLabel = card.stackTitle\n\t\t\t\t\t?? card.stack?.title\n\t\t\t\t\t?? (stackId === 'unstacked'\n\t\t\t\t\t\t? t('nextcloud-vue', 'No stack')\n\t\t\t\t\t\t: t('nextcloud-vue', 'Stack {n}', { n: stackId }))\n\t\t\t\tconst key = String(stackId)\n\t\t\t\tif (!groups.has(key)) {\n\t\t\t\t\tgroups.set(key, { key, label: stackLabel, cards: [] })\n\t\t\t\t}\n\t\t\t\tgroups.get(key).cards.push(card)\n\t\t\t}\n\t\t\treturn Array.from(groups.values())\n\t\t},\n\n\t\t/**\n\t\t * Up to three columns for the mini-kanban view on `detail-page`.\n\t\t *\n\t\t * Per AD-2 (design.md): when the linked card has its own stack\n\t\t * we always include that stack; otherwise the first three stacks\n\t\t * by first-seen order are surfaced.\n\t\t *\n\t\t * @return {Array<{key: string, label: string, cards: Array}>}\n\t\t */\n\t\tminiColumns() {\n\t\t\treturn this.columns.slice(0, 3)\n\t\t},\n\n\t\tstackDistribution() {\n\t\t\treturn this.columns.map((col) => ({\n\t\t\t\tkey: col.key,\n\t\t\t\tlabel: col.label,\n\t\t\t\tcount: col.cards.length,\n\t\t\t}))\n\t\t},\n\n\t\tcountHeadline() {\n\t\t\tconst total = this.cards.length\n\t\t\tconst stackCount = this.columns.length\n\t\t\tif (stackCount <= 1) {\n\t\t\t\treturn n('nextcloud-vue', '{count} card', '{count} cards', total, { count: total })\n\t\t\t}\n\t\t\tconst cardFragment = n('nextcloud-vue', '{count} card', '{count} cards', total, { count: total })\n\t\t\tconst stacksFragment = n('nextcloud-vue', 'across {count} stack', 'across {count} stacks', stackCount, { count: stackCount })\n\t\t\treturn `${cardFragment} ${stacksFragment}`\n\t\t},\n\n\t\tmostRecent() {\n\t\t\tif (this.cards.length === 0) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst sorted = [...this.cards].sort((a, b) => {\n\t\t\t\tconst ta = Date.parse(a.linkedAt ?? a.lastActivity ?? '') || 0\n\t\t\t\tconst tb = Date.parse(b.linkedAt ?? b.lastActivity ?? '') || 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\tcardKey(card) {\n\t\t\treturn card.cardId ?? card.id ?? ''\n\t\t},\n\n\t\tcardTitle(card) {\n\t\t\treturn card.cardTitle ?? card.title ?? this.cardKey(card)\n\t\t},\n\n\t\tcardUrl(card) {\n\t\t\tif (card.url && safeHref(card.url) !== '#') {\n\t\t\t\treturn card.url\n\t\t\t}\n\t\t\tconst boardId = card.boardId ?? card.board?.id ?? ''\n\t\t\tconst cardId = card.cardId ?? card.id ?? ''\n\t\t\tif (boardId && cardId) {\n\t\t\t\treturn `/index.php/apps/deck/board/${boardId}/card/${cardId}`\n\t\t\t}\n\t\t\treturn this.deckAppUrl\n\t\t},\n\n\t\tstackTitle(card) {\n\t\t\treturn card.stackTitle ?? card.stack?.title ?? ''\n\t\t},\n\n\t\tisLinked(card) {\n\t\t\tif (!this.value) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treturn String(this.cardKey(card)) === String(this.value)\n\t\t},\n\n\t\tchipSubtitle(card) {\n\t\t\treturn this.stackTitle(card) || this.cardTitle(card)\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.cards = data.results || data.items || (Array.isArray(data) ? data : []) || []\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.cards = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.cards = []\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('[CnDeckCard] failed to fetch cards', err)\n\t\t\t\tthis.cards = []\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.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('[CnDeckCard] failed to fetch single card', 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-deck-card__empty {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n\tpadding: 8px 0;\n}\n\n.cn-deck-card__headline {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 6px;\n}\n\n.cn-deck-card__headline-line {\n\tfont-size: 1.1em;\n\tcolor: var(--color-main-text);\n}\n\n.cn-deck-card__distribution {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n}\n\n.cn-deck-card__distribution-row {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\tgap: 8px;\n\tfont-size: 0.85em;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-deck-card__distribution-count {\n\tfont-weight: 600;\n\tcolor: var(--color-main-text);\n}\n\n.cn-deck-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-deck-card__headline-recent a {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\n.cn-deck-card__headline-recent a:hover {\n\ttext-decoration: underline;\n}\n\n.cn-deck-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-deck-card__chip a {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\n.cn-deck-card__chip a:hover {\n\ttext-decoration: underline;\n}\n\n.cn-deck-card__chip-stack {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.85em;\n}\n\n.cn-deck-card__kanban {\n\tdisplay: flex;\n\tgap: 8px;\n\talign-items: flex-start;\n}\n\n.cn-deck-card__column {\n\tflex: 1;\n\tmin-width: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n\tpadding: 6px;\n\tborder-radius: var(--border-radius);\n\tbackground: var(--color-background-hover);\n}\n\n.cn-deck-card__column-header {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\tgap: 4px;\n}\n\n.cn-deck-card__column-title {\n\tfont-size: 0.85em;\n\tfont-weight: 600;\n\tcolor: var(--color-main-text);\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-deck-card__column-count {\n\tfont-size: 0.7em;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-deck-card__column-list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 3px;\n}\n\n.cn-deck-card__row {\n\tpadding: 4px 6px;\n\tborder-radius: 4px;\n\tbackground: var(--color-main-background);\n\tborder: 1px solid var(--color-border);\n\tfont-size: 0.8em;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-deck-card__row--highlight {\n\tborder-color: var(--color-primary-element, #21468B);\n\tbackground: var(--color-primary-element-light, var(--color-background-darker));\n\tfont-weight: 600;\n}\n\n.cn-deck-card__title {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\na.cn-deck-card__title:hover {\n\ttext-decoration: underline;\n}\n</style>\n"],"names":["_Fragment","_createElementBlock","_createVNode","_createElementVNode","_toDisplayString","_openBlock","_renderList"],"mappings":"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"}
@@ -9,6 +9,7 @@ import __sfc_main$1 from '../../../components/CnDeckCardCreate/CnDeckCardCreate.
9
9
  import __sfc_main from '../../../components/CnDeckCardPicker/CnDeckCardPicker.vue.js';
10
10
  import __sfc_main$2 from '../../../components/CnStatusBadge/CnStatusBadge.vue.js';
11
11
  import { prefixUrl, buildHeaders } from '../../../utils/headers.js';
12
+ import { safeHref } from '../../../utils/safeHref.js';
12
13
 
13
14
  /** Maximum assignee avatars shown before collapsing the rest into a +N overflow badge. */
14
15
  const MAX_VISIBLE_ASSIGNEES = 3;
@@ -193,7 +194,7 @@ var script = {
193
194
  },
194
195
 
195
196
  cardUrl(card) {
196
- if (card.url) {
197
+ if (card.url && safeHref(card.url) !== '#') {
197
198
  return card.url
198
199
  }
199
200
  const boardId = card.boardId ?? card.board?.id ?? '';