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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (300) hide show
  1. package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js +12 -2
  2. package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js.map +1 -1
  3. package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js +1 -2
  4. package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js.map +1 -1
  5. package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue2.js.map +1 -1
  6. package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue3.js +0 -3
  7. package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue3.js.map +1 -1
  8. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js.map +1 -1
  9. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js +1 -2
  10. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js.map +1 -1
  11. package/dist/esm/components/CnBoardView/CnBoardView.vue2.js +12 -5
  12. package/dist/esm/components/CnBoardView/CnBoardView.vue2.js.map +1 -1
  13. package/dist/esm/components/CnBoardView/CnBoardView.vue3.js +2 -1
  14. package/dist/esm/components/CnBoardView/CnBoardView.vue3.js.map +1 -1
  15. package/dist/esm/components/CnCardGrid/CnCardGrid.vue2.js +9 -1
  16. package/dist/esm/components/CnCardGrid/CnCardGrid.vue2.js.map +1 -1
  17. package/dist/esm/components/CnCardGrid/CnCardGrid.vue3.js.map +1 -1
  18. package/dist/esm/components/CnContextMenu/CnContextMenu.vue2.js +6 -1
  19. package/dist/esm/components/CnContextMenu/CnContextMenu.vue2.js.map +1 -1
  20. package/dist/esm/components/CnContextMenu/CnContextMenu.vue3.js.map +1 -1
  21. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +116 -15
  22. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
  23. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +27 -14
  24. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
  25. package/dist/esm/components/CnDateAxisView/CnDateAxisView.vue2.js +13 -6
  26. package/dist/esm/components/CnDateAxisView/CnDateAxisView.vue2.js.map +1 -1
  27. package/dist/esm/components/CnDateAxisView/CnDateAxisView.vue3.js +2 -0
  28. package/dist/esm/components/CnDateAxisView/CnDateAxisView.vue3.js.map +1 -1
  29. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +29 -1
  30. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  31. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  32. package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue2.js.map +1 -1
  33. package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue3.js +1 -2
  34. package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue3.js.map +1 -1
  35. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +3 -1
  36. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
  37. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +17 -17
  38. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
  39. package/dist/esm/components/CnFilesWidget/CnFilesWidget.vue2.js +4 -21
  40. package/dist/esm/components/CnFilesWidget/CnFilesWidget.vue2.js.map +1 -1
  41. package/dist/esm/components/CnFilesWidget/CnFilesWidget.vue3.js.map +1 -1
  42. package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue2.js +0 -13
  43. package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue2.js.map +1 -1
  44. package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue3.js +8 -8
  45. package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue3.js.map +1 -1
  46. package/dist/esm/components/CnFlowsPage/CnFlowsPage.vue2.js.map +1 -1
  47. package/dist/esm/components/CnFlowsPage/CnFlowsPage.vue3.js +3 -2
  48. package/dist/esm/components/CnFlowsPage/CnFlowsPage.vue3.js.map +1 -1
  49. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +67 -13
  50. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  51. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +15 -8
  52. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  53. package/dist/esm/components/CnIndexPage/manifestActionDispatch.js +31 -11
  54. package/dist/esm/components/CnIndexPage/manifestActionDispatch.js.map +1 -1
  55. package/dist/esm/components/CnIntegrationWidget/CnIntegrationWidgetEmpty.vue2.js.map +1 -1
  56. package/dist/esm/components/CnIntegrationWidget/CnIntegrationWidgetEmpty.vue3.js +1 -2
  57. package/dist/esm/components/CnIntegrationWidget/CnIntegrationWidgetEmpty.vue3.js.map +1 -1
  58. package/dist/esm/components/CnInteractionFormWidget/CnInteractionFormWidget.vue2.js.map +1 -1
  59. package/dist/esm/components/CnInteractionFormWidget/CnInteractionFormWidget.vue3.js +0 -1
  60. package/dist/esm/components/CnInteractionFormWidget/CnInteractionFormWidget.vue3.js.map +1 -1
  61. package/dist/esm/components/CnLinkButtonWidget/CnLinkButtonWidget.vue2.js +2 -17
  62. package/dist/esm/components/CnLinkButtonWidget/CnLinkButtonWidget.vue2.js.map +1 -1
  63. package/dist/esm/components/CnLinkButtonWidget/CnLinkButtonWidget.vue3.js.map +1 -1
  64. package/dist/esm/components/CnLogsPage/CnLogsPage.vue2.js +35 -11
  65. package/dist/esm/components/CnLogsPage/CnLogsPage.vue2.js.map +1 -1
  66. package/dist/esm/components/CnLogsPage/CnLogsPage.vue3.js +3 -2
  67. package/dist/esm/components/CnLogsPage/CnLogsPage.vue3.js.map +1 -1
  68. package/dist/esm/components/CnObjectCard/CnObjectCard.vue2.js +23 -5
  69. package/dist/esm/components/CnObjectCard/CnObjectCard.vue2.js.map +1 -1
  70. package/dist/esm/components/CnObjectCard/CnObjectCard.vue3.js +1 -1
  71. package/dist/esm/components/CnObjectCard/CnObjectCard.vue3.js.map +1 -1
  72. package/dist/esm/components/CnObjectList/CnObjectList.vue2.js +19 -3
  73. package/dist/esm/components/CnObjectList/CnObjectList.vue2.js.map +1 -1
  74. package/dist/esm/components/CnObjectList/CnObjectList.vue3.js.map +1 -1
  75. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +45 -15
  76. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
  77. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js +4 -2
  78. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
  79. package/dist/esm/components/CnObjectRow/CnObjectRow.vue2.js +24 -6
  80. package/dist/esm/components/CnObjectRow/CnObjectRow.vue2.js.map +1 -1
  81. package/dist/esm/components/CnObjectRow/CnObjectRow.vue3.js +1 -1
  82. package/dist/esm/components/CnObjectRow/CnObjectRow.vue3.js.map +1 -1
  83. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
  84. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +0 -1
  85. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
  86. package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue2.js.map +1 -1
  87. package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue3.js +4 -2
  88. package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue3.js.map +1 -1
  89. package/dist/esm/components/CnRowActions/CnRowActions.vue2.js +7 -2
  90. package/dist/esm/components/CnRowActions/CnRowActions.vue2.js.map +1 -1
  91. package/dist/esm/components/CnRowActions/CnRowActions.vue3.js.map +1 -1
  92. package/dist/esm/components/CnSpendAnalyticsWidget/CnSpendAnalyticsWidget.vue2.js +5 -3
  93. package/dist/esm/components/CnSpendAnalyticsWidget/CnSpendAnalyticsWidget.vue2.js.map +1 -1
  94. package/dist/esm/components/CnSpendAnalyticsWidget/CnSpendAnalyticsWidget.vue3.js.map +1 -1
  95. package/dist/esm/components/CnTabs/CnTabs.vue2.js.map +1 -1
  96. package/dist/esm/components/CnTabs/CnTabs.vue3.js.map +1 -1
  97. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue2.js +3 -1
  98. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue2.js.map +1 -1
  99. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue3.js +1 -0
  100. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue3.js.map +1 -1
  101. package/dist/esm/integrations/builtin/analytics/CnAnalyticsCard.vue2.js +3 -1
  102. package/dist/esm/integrations/builtin/analytics/CnAnalyticsCard.vue2.js.map +1 -1
  103. package/dist/esm/integrations/builtin/analytics/CnAnalyticsCard.vue3.js.map +1 -1
  104. package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue2.js +3 -1
  105. package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue2.js.map +1 -1
  106. package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue3.js +2 -4
  107. package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue3.js.map +1 -1
  108. package/dist/esm/integrations/builtin/bookmarks/CnBookmarksCard.vue2.js +6 -0
  109. package/dist/esm/integrations/builtin/bookmarks/CnBookmarksCard.vue2.js.map +1 -1
  110. package/dist/esm/integrations/builtin/bookmarks/CnBookmarksCard.vue3.js +68 -53
  111. package/dist/esm/integrations/builtin/bookmarks/CnBookmarksCard.vue3.js.map +1 -1
  112. package/dist/esm/integrations/builtin/bookmarks/CnBookmarksTab.vue2.js +17 -0
  113. package/dist/esm/integrations/builtin/bookmarks/CnBookmarksTab.vue2.js.map +1 -1
  114. package/dist/esm/integrations/builtin/bookmarks/CnBookmarksTab.vue3.js +21 -19
  115. package/dist/esm/integrations/builtin/bookmarks/CnBookmarksTab.vue3.js.map +1 -1
  116. package/dist/esm/integrations/builtin/collectives/CnCollectivesCard.vue2.js +3 -1
  117. package/dist/esm/integrations/builtin/collectives/CnCollectivesCard.vue2.js.map +1 -1
  118. package/dist/esm/integrations/builtin/collectives/CnCollectivesCard.vue3.js.map +1 -1
  119. package/dist/esm/integrations/builtin/collectives/CnCollectivesTab.vue2.js +3 -1
  120. package/dist/esm/integrations/builtin/collectives/CnCollectivesTab.vue2.js.map +1 -1
  121. package/dist/esm/integrations/builtin/collectives/CnCollectivesTab.vue3.js +2 -4
  122. package/dist/esm/integrations/builtin/collectives/CnCollectivesTab.vue3.js.map +1 -1
  123. package/dist/esm/integrations/builtin/cospend/CnCospendCard.vue2.js +2 -1
  124. package/dist/esm/integrations/builtin/cospend/CnCospendCard.vue2.js.map +1 -1
  125. package/dist/esm/integrations/builtin/cospend/CnCospendCard.vue3.js.map +1 -1
  126. package/dist/esm/integrations/builtin/cospend/CnCospendTab.vue2.js +2 -1
  127. package/dist/esm/integrations/builtin/cospend/CnCospendTab.vue2.js.map +1 -1
  128. package/dist/esm/integrations/builtin/cospend/CnCospendTab.vue3.js +1 -2
  129. package/dist/esm/integrations/builtin/cospend/CnCospendTab.vue3.js.map +1 -1
  130. package/dist/esm/integrations/builtin/deck/CnDeckCard.vue2.js +2 -1
  131. package/dist/esm/integrations/builtin/deck/CnDeckCard.vue2.js.map +1 -1
  132. package/dist/esm/integrations/builtin/deck/CnDeckCard.vue3.js.map +1 -1
  133. package/dist/esm/integrations/builtin/deck/CnDeckTab.vue2.js +2 -1
  134. package/dist/esm/integrations/builtin/deck/CnDeckTab.vue2.js.map +1 -1
  135. package/dist/esm/integrations/builtin/deck/CnDeckTab.vue3.js +1 -2
  136. package/dist/esm/integrations/builtin/deck/CnDeckTab.vue3.js.map +1 -1
  137. package/dist/esm/integrations/builtin/email/CnEmailCard.vue2.js.map +1 -1
  138. package/dist/esm/integrations/builtin/email/CnEmailCard.vue3.js.map +1 -1
  139. package/dist/esm/integrations/builtin/email/CnEmailTab.vue2.js.map +1 -1
  140. package/dist/esm/integrations/builtin/email/CnEmailTab.vue3.js +1 -2
  141. package/dist/esm/integrations/builtin/email/CnEmailTab.vue3.js.map +1 -1
  142. package/dist/esm/integrations/builtin/flow/CnFlowCard.vue2.js +2 -1
  143. package/dist/esm/integrations/builtin/flow/CnFlowCard.vue2.js.map +1 -1
  144. package/dist/esm/integrations/builtin/flow/CnFlowCard.vue3.js.map +1 -1
  145. package/dist/esm/integrations/builtin/flow/CnFlowTab.vue2.js +2 -1
  146. package/dist/esm/integrations/builtin/flow/CnFlowTab.vue2.js.map +1 -1
  147. package/dist/esm/integrations/builtin/flow/CnFlowTab.vue3.js +1 -2
  148. package/dist/esm/integrations/builtin/flow/CnFlowTab.vue3.js.map +1 -1
  149. package/dist/esm/integrations/builtin/forms/CnFormsCard.vue2.js +3 -1
  150. package/dist/esm/integrations/builtin/forms/CnFormsCard.vue2.js.map +1 -1
  151. package/dist/esm/integrations/builtin/forms/CnFormsCard.vue3.js.map +1 -1
  152. package/dist/esm/integrations/builtin/forms/CnFormsTab.vue2.js +3 -1
  153. package/dist/esm/integrations/builtin/forms/CnFormsTab.vue2.js.map +1 -1
  154. package/dist/esm/integrations/builtin/forms/CnFormsTab.vue3.js +2 -4
  155. package/dist/esm/integrations/builtin/forms/CnFormsTab.vue3.js.map +1 -1
  156. package/dist/esm/integrations/builtin/maps/CnMapsCard.vue2.js +3 -1
  157. package/dist/esm/integrations/builtin/maps/CnMapsCard.vue2.js.map +1 -1
  158. package/dist/esm/integrations/builtin/maps/CnMapsCard.vue3.js.map +1 -1
  159. package/dist/esm/integrations/builtin/maps/CnMapsTab.vue2.js +3 -1
  160. package/dist/esm/integrations/builtin/maps/CnMapsTab.vue2.js.map +1 -1
  161. package/dist/esm/integrations/builtin/maps/CnMapsTab.vue3.js +2 -4
  162. package/dist/esm/integrations/builtin/maps/CnMapsTab.vue3.js.map +1 -1
  163. package/dist/esm/integrations/builtin/openproject/CnOpenprojectCard.vue2.js +3 -1
  164. package/dist/esm/integrations/builtin/openproject/CnOpenprojectCard.vue2.js.map +1 -1
  165. package/dist/esm/integrations/builtin/openproject/CnOpenprojectCard.vue3.js.map +1 -1
  166. package/dist/esm/integrations/builtin/openproject/CnOpenprojectTab.vue2.js +3 -1
  167. package/dist/esm/integrations/builtin/openproject/CnOpenprojectTab.vue2.js.map +1 -1
  168. package/dist/esm/integrations/builtin/openproject/CnOpenprojectTab.vue3.js +2 -4
  169. package/dist/esm/integrations/builtin/openproject/CnOpenprojectTab.vue3.js.map +1 -1
  170. package/dist/esm/integrations/builtin/photos/CnPhotosCard.vue2.js +2 -1
  171. package/dist/esm/integrations/builtin/photos/CnPhotosCard.vue2.js.map +1 -1
  172. package/dist/esm/integrations/builtin/photos/CnPhotosCard.vue3.js.map +1 -1
  173. package/dist/esm/integrations/builtin/photos/CnPhotosTab.vue2.js +2 -1
  174. package/dist/esm/integrations/builtin/photos/CnPhotosTab.vue2.js.map +1 -1
  175. package/dist/esm/integrations/builtin/photos/CnPhotosTab.vue3.js +1 -2
  176. package/dist/esm/integrations/builtin/photos/CnPhotosTab.vue3.js.map +1 -1
  177. package/dist/esm/integrations/builtin/polls/CnPollsCard.vue2.js +2 -1
  178. package/dist/esm/integrations/builtin/polls/CnPollsCard.vue2.js.map +1 -1
  179. package/dist/esm/integrations/builtin/polls/CnPollsCard.vue3.js.map +1 -1
  180. package/dist/esm/integrations/builtin/polls/CnPollsTab.vue2.js +2 -1
  181. package/dist/esm/integrations/builtin/polls/CnPollsTab.vue2.js.map +1 -1
  182. package/dist/esm/integrations/builtin/polls/CnPollsTab.vue3.js +1 -2
  183. package/dist/esm/integrations/builtin/polls/CnPollsTab.vue3.js.map +1 -1
  184. package/dist/esm/integrations/builtin/shares/CnSharesTab.vue2.js.map +1 -1
  185. package/dist/esm/integrations/builtin/shares/CnSharesTab.vue3.js +3 -6
  186. package/dist/esm/integrations/builtin/shares/CnSharesTab.vue3.js.map +1 -1
  187. package/dist/esm/integrations/builtin/talk/CnTalkCard.vue2.js +3 -1
  188. package/dist/esm/integrations/builtin/talk/CnTalkCard.vue2.js.map +1 -1
  189. package/dist/esm/integrations/builtin/talk/CnTalkCard.vue3.js.map +1 -1
  190. package/dist/esm/integrations/builtin/talk/CnTalkTab.vue2.js +3 -1
  191. package/dist/esm/integrations/builtin/talk/CnTalkTab.vue2.js.map +1 -1
  192. package/dist/esm/integrations/builtin/talk/CnTalkTab.vue3.js +2 -4
  193. package/dist/esm/integrations/builtin/talk/CnTalkTab.vue3.js.map +1 -1
  194. package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerCard.vue2.js +2 -1
  195. package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerCard.vue2.js.map +1 -1
  196. package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerCard.vue3.js.map +1 -1
  197. package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerTab.vue2.js +2 -1
  198. package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerTab.vue2.js.map +1 -1
  199. package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerTab.vue3.js +1 -2
  200. package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerTab.vue3.js.map +1 -1
  201. package/dist/esm/integrations/builtin/xwiki/CnXwikiCard.vue2.js +4 -1
  202. package/dist/esm/integrations/builtin/xwiki/CnXwikiCard.vue2.js.map +1 -1
  203. package/dist/esm/integrations/builtin/xwiki/CnXwikiCard.vue3.js.map +1 -1
  204. package/dist/esm/integrations/builtin/xwiki/CnXwikiTab.vue2.js +6 -3
  205. package/dist/esm/integrations/builtin/xwiki/CnXwikiTab.vue2.js.map +1 -1
  206. package/dist/esm/integrations/builtin/xwiki/CnXwikiTab.vue3.js +2 -4
  207. package/dist/esm/integrations/builtin/xwiki/CnXwikiTab.vue3.js.map +1 -1
  208. package/dist/esm/l10n/en.json.js +1 -0
  209. package/dist/esm/l10n/en.json.js.map +1 -1
  210. package/dist/esm/l10n/nl.json.js +1 -0
  211. package/dist/esm/l10n/nl.json.js.map +1 -1
  212. package/dist/esm/nextcloud-vue.css +71 -7
  213. package/dist/esm/utils/actionLink.js +5 -0
  214. package/dist/esm/utils/actionLink.js.map +1 -1
  215. package/dist/esm/utils/actionsDispatcher.js +16 -1
  216. package/dist/esm/utils/actionsDispatcher.js.map +1 -1
  217. package/dist/esm/utils/linkNavigation.js +6 -3
  218. package/dist/esm/utils/linkNavigation.js.map +1 -1
  219. package/dist/esm/utils/rowAuxClick.js +30 -5
  220. package/dist/esm/utils/rowAuxClick.js.map +1 -1
  221. package/dist/esm/utils/safeHref.js +18 -3
  222. package/dist/esm/utils/safeHref.js.map +1 -1
  223. package/dist/nextcloud-vue.cjs.js +809 -383
  224. package/dist/nextcloud-vue.cjs.js.map +2 -2
  225. package/dist/nextcloud-vue.css +71 -7
  226. package/l10n/en.json +1 -0
  227. package/l10n/nl.json +1 -0
  228. package/package.json +1 -1
  229. package/src/components/CnActionsBar/CnActionsBar.vue +14 -5
  230. package/src/components/CnActionsMenu/CnActionsMenu.vue +0 -3
  231. package/src/components/CnAdminSettingsShell/CnAdminSettingsShell.vue +1 -2
  232. package/src/components/CnBoardView/CnBoardView.vue +13 -5
  233. package/src/components/CnCardGrid/CnCardGrid.vue +9 -1
  234. package/src/components/CnContextMenu/CnContextMenu.vue +6 -1
  235. package/src/components/CnDataTable/CnDataTable.md +1 -1
  236. package/src/components/CnDataTable/CnDataTable.vue +131 -17
  237. package/src/components/CnDateAxisView/CnDateAxisView.vue +15 -6
  238. package/src/components/CnDetailPage/CnDetailPage.md +1 -1
  239. package/src/components/CnDetailPage/CnDetailPage.vue +29 -1
  240. package/src/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue +1 -2
  241. package/src/components/CnFilesBrowser/CnFilesBrowser.vue +5 -3
  242. package/src/components/CnFilesWidget/CnFilesWidget.vue +4 -21
  243. package/src/components/CnFlowRunsWidget/CnFlowRunsWidget.vue +8 -21
  244. package/src/components/CnFlowsPage/CnFlowsPage.vue +2 -1
  245. package/src/components/CnIndexPage/CnIndexPage.vue +75 -14
  246. package/src/components/CnIndexPage/manifestActionDispatch.js +31 -11
  247. package/src/components/CnIntegrationWidget/CnIntegrationWidgetEmpty.vue +1 -2
  248. package/src/components/CnInteractionFormWidget/CnInteractionFormWidget.vue +0 -1
  249. package/src/components/CnLinkButtonWidget/CnLinkButtonWidget.vue +2 -17
  250. package/src/components/CnLogsPage/CnLogsPage.vue +37 -12
  251. package/src/components/CnObjectCard/CnObjectCard.vue +24 -6
  252. package/src/components/CnObjectList/CnObjectList.vue +19 -3
  253. package/src/components/CnObjectListWidget/CnObjectListWidget.vue +47 -15
  254. package/src/components/CnObjectRow/CnObjectRow.vue +25 -7
  255. package/src/components/CnObjectSidebar/CnFilesTab.vue +0 -1
  256. package/src/components/CnPageRenderer/CnPageRenderer.vue +2 -0
  257. package/src/components/CnRowActions/CnRowActions.vue +7 -2
  258. package/src/components/CnSpendAnalyticsWidget/CnSpendAnalyticsWidget.vue +5 -3
  259. package/src/components/CnTabs/CnTabs.vue +7 -3
  260. package/src/components/CnTasksWidget/CnTasksWidget.vue +4 -1
  261. package/src/css/table.css +26 -0
  262. package/src/integrations/builtin/analytics/CnAnalyticsCard.vue +3 -2
  263. package/src/integrations/builtin/analytics/CnAnalyticsTab.vue +5 -6
  264. package/src/integrations/builtin/bookmarks/CnBookmarksCard.vue +12 -1
  265. package/src/integrations/builtin/bookmarks/CnBookmarksTab.vue +23 -6
  266. package/src/integrations/builtin/bookmarks/__tests__/CnBookmarksTab.spec.js +7 -2
  267. package/src/integrations/builtin/collectives/CnCollectivesCard.vue +3 -2
  268. package/src/integrations/builtin/collectives/CnCollectivesTab.vue +5 -6
  269. package/src/integrations/builtin/cospend/CnCospendCard.vue +2 -2
  270. package/src/integrations/builtin/cospend/CnCospendTab.vue +3 -4
  271. package/src/integrations/builtin/deck/CnDeckCard.vue +2 -2
  272. package/src/integrations/builtin/deck/CnDeckTab.vue +3 -4
  273. package/src/integrations/builtin/email/CnEmailCard.vue +6 -2
  274. package/src/integrations/builtin/email/CnEmailTab.vue +7 -4
  275. package/src/integrations/builtin/flow/CnFlowCard.vue +2 -2
  276. package/src/integrations/builtin/flow/CnFlowTab.vue +3 -4
  277. package/src/integrations/builtin/forms/CnFormsCard.vue +3 -2
  278. package/src/integrations/builtin/forms/CnFormsTab.vue +5 -6
  279. package/src/integrations/builtin/maps/CnMapsCard.vue +3 -2
  280. package/src/integrations/builtin/maps/CnMapsTab.vue +5 -6
  281. package/src/integrations/builtin/maps/__tests__/CnMapsTab.spec.js +9 -0
  282. package/src/integrations/builtin/openproject/CnOpenprojectCard.vue +3 -2
  283. package/src/integrations/builtin/openproject/CnOpenprojectTab.vue +5 -6
  284. package/src/integrations/builtin/photos/CnPhotosCard.vue +2 -2
  285. package/src/integrations/builtin/photos/CnPhotosTab.vue +3 -4
  286. package/src/integrations/builtin/polls/CnPollsCard.vue +2 -2
  287. package/src/integrations/builtin/polls/CnPollsTab.vue +3 -4
  288. package/src/integrations/builtin/shares/CnSharesTab.vue +3 -6
  289. package/src/integrations/builtin/talk/CnTalkCard.vue +3 -2
  290. package/src/integrations/builtin/talk/CnTalkTab.vue +5 -6
  291. package/src/integrations/builtin/time-tracker/CnTimeTrackerCard.vue +2 -2
  292. package/src/integrations/builtin/time-tracker/CnTimeTrackerTab.vue +3 -4
  293. package/src/integrations/builtin/xwiki/CnXwikiCard.vue +4 -2
  294. package/src/integrations/builtin/xwiki/CnXwikiTab.vue +7 -7
  295. package/src/integrations/builtin/xwiki/__tests__/CnXwikiTab.spec.js +9 -0
  296. package/src/utils/actionLink.js +5 -0
  297. package/src/utils/actionsDispatcher.js +15 -0
  298. package/src/utils/linkNavigation.js +6 -3
  299. package/src/utils/rowAuxClick.js +29 -4
  300. package/src/utils/safeHref.js +18 -3
@@ -1 +1 @@
1
- {"version":3,"file":"CnCollectivesTab.vue3.js","sources":["../../../../../src/integrations/builtin/collectives/CnCollectivesTab.vue?vue&type=template&id=23e94f50&lang.js"],"sourcesContent":["<!--\n CnCollectivesTab — bespoke sidebar tab for the `collectives` integration.\n\n Replaces the generic CnIntegrationTab for the `collectives` leaf:\n renders linked Knowledge pages grouped by collective (emoji + title +\n optional content snippet + last-modified hint). Each row deep-links to\n the page inside the NC Collectives app via\n /index.php/apps/collectives/{collectiveSlug}/{pageSlug}\n (with a sane fallback to /index.php/apps/collectives/{pageId} when the\n provider only carries an id).\n\n Talks to the same OpenRegister pluggable-integration sub-resource\n `/api/objects/{register}/{schema}/{objectId}/integrations/collectives`\n served by `OCA\\OpenRegister\\Service\\Integration\\Providers\\CollectivesProvider`\n (storage strategy `link-table` — the marker `[or:{uuid}]` lives in the\n Collectives `collectives_pages.slug` column). Provider payload shape\n (verified against `CollectivesProvider::list`):\n { id, title, url, data: { id, slug, emoji, last_user_id, ... } }\n The richer \"spec\" shape (title, slug, emoji, content, fileId,\n lastModified, collectiveId, collectiveName, url) is accepted as well —\n the UI reads both top-level and `data.*` fields defensively so a future\n provider expansion stays drop-in.\n\n Bespoke-vs-generic rationale: the generic tab renders a flat title\n list which loses the collective grouping, the emoji glyph, and the\n per-page content snippet — three signals that make Knowledge linkage\n legible at-a-glance. The bespoke tab surfaces them per row.\n\n Surface behaviour:\n - Empty state (\"No Knowledge pages linked yet\") + \"Open Knowledge\"\n CTA when zero rows.\n - Loading state via NcLoadingIcon for the full-tab spin.\n - 503 unavailable banner (matches CnIntegrationTab's AD-23 graceful\n degradation), generic error path on non-OK / fetch throw.\n\n See `openregister/openspec/changes/integration-collectives/` for the\n spec delta and ADR-019 (registry mechanism), ADR-022 (consumption).\n-->\n<template>\n\t<div class=\"cn-sidebar-tab cn-collectives-tab\">\n\t\t<div v-if=\"degraded\" class=\"cn-collectives-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-collectives-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 page') }}\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 page') }}\n\t\t\t</NcButton>\n\t\t</div>\n\n\t\t<NcLoadingIcon v-if=\"loading\" />\n\n\t\t<div v-else-if=\"error\" class=\"cn-collectives-tab__error\" role=\"alert\">\n\t\t\t{{ error }}\n\t\t</div>\n\n\t\t<div\n\t\t\tv-else-if=\"pages.length === 0 && !degraded\"\n\t\t\tclass=\"cn-sidebar-tab__empty cn-collectives-tab__empty\">\n\t\t\t<BookOpenPageVariant :size=\"32\" class=\"cn-collectives-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=\"collectivesAppUrl\"\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<BookOpenPageVariant :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ openCollectivesLabel }}\n\t\t\t</NcButton>\n\t\t</div>\n\n\t\t<div v-else-if=\"pages.length > 0\" class=\"cn-collectives-tab__groups\">\n\t\t\t<section\n\t\t\t\tv-for=\"group in groupedPages\"\n\t\t\t\t:key=\"group.key\"\n\t\t\t\tclass=\"cn-collectives-tab__group\">\n\t\t\t\t<header v-if=\"group.label\" class=\"cn-collectives-tab__group-header\">\n\t\t\t\t\t<FolderTextOutline :size=\"14\" />\n\t\t\t\t\t<span>{{ group.label }}</span>\n\t\t\t\t</header>\n\t\t\t\t<ul class=\"cn-collectives-tab__list\">\n\t\t\t\t\t<NcListItem\n\t\t\t\t\t\tv-for=\"page in group.pages\"\n\t\t\t\t\t\t:key=\"pageKey(page)\"\n\t\t\t\t\t\tclass=\"cn-collectives-tab__row\"\n\t\t\t\t\t\t:name=\"pageTitle(page)\"\n\t\t\t\t\t\t:bold=\"true\"\n\t\t\t\t\t\t:href=\"pageUrl(page)\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t:forceDisplayActions=\"true\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<span class=\"cn-collectives-tab__emoji\" :aria-hidden=\"true\">\n\t\t\t\t\t\t\t\t<template v-if=\"pageEmoji(page)\">{{ pageEmoji(page) }}</template>\n\t\t\t\t\t\t\t\t<FileDocumentOutline v-else :size=\"22\" />\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t<template #subname>\n\t\t\t\t\t\t\t<span class=\"cn-collectives-tab__subname\">\n\t\t\t\t\t\t\t\t<span v-if=\"sublineLabel(page)\" class=\"cn-collectives-tab__subname-line\">{{ sublineLabel(page) }}</span>\n\t\t\t\t\t\t\t\t<span v-if=\"pageSnippet(page)\" class=\"cn-collectives-tab__snippet\">{{ pageSnippet(page) }}</span>\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t<template v-if=\"modifiedMs(page)\" #details>\n\t\t\t\t\t\t\t<NcDateTime\n\t\t\t\t\t\t\t\tclass=\"cn-collectives-tab__time\"\n\t\t\t\t\t\t\t\t:timestamp=\"modifiedMs(page)\"\n\t\t\t\t\t\t\t\trelativeTime=\"short\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t<template #actions>\n\t\t\t\t\t\t\t<NcActionLink\n\t\t\t\t\t\t\t\t:closeAfterClick=\"true\"\n\t\t\t\t\t\t\t\t:href=\"pageUrl(page)\"\n\t\t\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t\t\trel=\"noopener noreferrer\">\n\t\t\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t\t\t<OpenInNew :size=\"20\" />\n\t\t\t\t\t\t\t\t</template>\n\t\t\t\t\t\t\t\t{{ t('nextcloud-vue', 'Open in Knowledge') }}\n\t\t\t\t\t\t\t</NcActionLink>\n\t\t\t\t\t\t\t<NcActionButton :closeAfterClick=\"true\" @click=\"unlinkPage(page)\">\n\t\t\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t\t\t<LinkOff :size=\"20\" />\n\t\t\t\t\t\t\t\t</template>\n\t\t\t\t\t\t\t\t{{ t('nextcloud-vue', 'Unlink page') }}\n\t\t\t\t\t\t\t</NcActionButton>\n\t\t\t\t\t\t</template>\n\t\t\t\t\t</NcListItem>\n\t\t\t\t</ul>\n\t\t\t</section>\n\t\t</div>\n\n\t\t<CnCollectivePagePicker\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<CnCollectivePageCreate\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, NcActionLink, NcButton, NcDateTime, NcListItem, NcLoadingIcon } from '@nextcloud/vue'\nimport AlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue'\nimport BookOpenPageVariant from 'vue-material-design-icons/BookOpenPageVariant.vue'\nimport FileDocumentOutline from 'vue-material-design-icons/FileDocumentOutline.vue'\nimport FolderTextOutline from 'vue-material-design-icons/FolderTextOutline.vue'\nimport LinkOff from 'vue-material-design-icons/LinkOff.vue'\nimport LinkVariant from 'vue-material-design-icons/LinkVariant.vue'\nimport OpenInNew from 'vue-material-design-icons/OpenInNew.vue'\nimport Plus from 'vue-material-design-icons/Plus.vue'\nimport CnCollectivePageCreate from '../../../components/CnCollectivePageCreate/CnCollectivePageCreate.vue'\nimport CnCollectivePagePicker from '../../../components/CnCollectivePagePicker/CnCollectivePagePicker.vue'\nimport { buildHeaders, prefixUrl } from '../../../utils/index.js'\nimport { stripMarker } from '../../utils/marker.js'\n\nconst MS_PER_DAY = 24 * 60 * 60 * 1000\nconst MS_PER_HOUR = 60 * 60 * 1000\nconst SNIPPET_MAX_CHARS = 160\n\n/**\n * CnCollectivesTab — bespoke sidebar list for the `collectives`\n * integration.\n *\n * Renders Knowledge pages grouped by parent collective; each row has\n * emoji + title + content snippet + last-modified hint and deep-links\n * to the page inside NC Collectives.\n */\nexport default {\n\tname: 'CnCollectivesTab',\n\n\tcomponents: {\n\t\tNcActionButton,\n\t\tNcActionLink,\n\t\tNcButton,\n\t\tNcDateTime,\n\t\tNcListItem,\n\t\tNcLoadingIcon,\n\t\tAlertCircleOutline,\n\t\tBookOpenPageVariant,\n\t\tFileDocumentOutline,\n\t\tFolderTextOutline,\n\t\tLinkOff,\n\t\tLinkVariant,\n\t\tOpenInNew,\n\t\tPlus,\n\t\tCnCollectivePagePicker,\n\t\tCnCollectivePageCreate,\n\t},\n\n\tprops: {\n\t\t/** Stable integration id (forwarded from the registry — always `'collectives'`). */\n\t\tintegrationId: { type: String, default: 'collectives' },\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 Knowledge pages linked yet') },\n\t\t/** Pre-translated \"Open Knowledge\" CTA label. */\n\t\topenCollectivesLabel: { type: String, default: () => t('nextcloud-vue', 'Open Knowledge') },\n\t\t/** Pre-translated banner when Collectives is unavailable. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Knowledge is currently unavailable.') },\n\t\t/** URL of the NC Collectives app entry. */\n\t\tcollectivesAppUrl: { type: String, default: '/index.php/apps/collectives' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tpages: [],\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\tgroupedPages() {\n\t\t\tconst buckets = new Map()\n\t\t\tfor (const page of this.pages) {\n\t\t\t\tconst key = String(this.collectiveKey(page) || '__default__')\n\t\t\t\tif (!buckets.has(key)) {\n\t\t\t\t\tbuckets.set(key, { key, label: this.collectiveLabel(page), pages: [] })\n\t\t\t\t}\n\t\t\t\tbuckets.get(key).pages.push(page)\n\t\t\t}\n\t\t\treturn Array.from(buckets.values())\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.fetchPages()\n\t\t\t}\n\t\t} },\n\n\t\tregister() {\n\t\t\tthis.fetchPages()\n\t\t},\n\n\t\tschema() {\n\t\t\tthis.fetchPages()\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\t/**\n\t\t * Base for the Tier-2 collectives endpoints (list/link/new/destroy).\n\t\t *\n\t\t * @return {string} The endpoint URL.\n\t\t */\n\t\tcollectivesEndpoint() {\n\t\t\treturn `${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/collectives`\n\t\t},\n\n\t\t/**\n\t\t * Resolve the numeric page id from a row (top-level or `data.*`).\n\t\t *\n\t\t * @param {object} page Provider/link row.\n\t\t *\n\t\t * @return {(number|string)} The page id, or '' when absent.\n\t\t */\n\t\tpageId(page) {\n\t\t\tconst d = this.dataOf(page)\n\t\t\treturn page.pageId ?? d.pageId ?? page.id ?? d.id ?? ''\n\t\t},\n\n\t\tdataOf(page) {\n\t\t\treturn (page && typeof page.data === 'object' && page.data !== null) ? page.data : {}\n\t\t},\n\n\t\tpageKey(page) {\n\t\t\tconst d = this.dataOf(page)\n\t\t\treturn page.pageId ?? d.pageId ?? page.id ?? d.id ?? page.slug ?? d.slug ?? ''\n\t\t},\n\n\t\tpageTitle(page) {\n\t\t\tconst d = this.dataOf(page)\n\t\t\tconst candidate = page.pageTitle ?? d.pageTitle ?? page.title ?? d.title ?? d.slug ?? page.slug ?? ''\n\t\t\t// Provider returns the slug (which carries the `[or:{uuid}]`\n\t\t\t// marker) when no real title is available. Strip the marker\n\t\t\t// AND any dangling slug separator (`-`, `_`, `.`) left\n\t\t\t// behind — `phase-d1-page-[or:UUID]` would otherwise render\n\t\t\t// as `phase-d1-page-` with a trailing hyphen (D-1 bug).\n\t\t\tconst stripped = stripMarker(candidate).replace(/[-_.]+$/, '').replace(/^[-_.]+/, '').trim()\n\t\t\treturn stripped || String(this.pageKey(page))\n\t\t},\n\n\t\tpageEmoji(page) {\n\t\t\tconst d = this.dataOf(page)\n\t\t\tconst emoji = page.emoji ?? d.emoji ?? ''\n\t\t\treturn typeof emoji === 'string' ? emoji.trim() : ''\n\t\t},\n\n\t\tpageSnippet(page) {\n\t\t\tconst d = this.dataOf(page)\n\t\t\tconst raw = page.content ?? d.content ?? ''\n\t\t\tconst text = String(raw).replace(/\\s+/g, ' ').trim()\n\t\t\tif (text === '') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tif (text.length > SNIPPET_MAX_CHARS) {\n\t\t\t\treturn text.slice(0, SNIPPET_MAX_CHARS).trimEnd() + '…'\n\t\t\t}\n\t\t\treturn text\n\t\t},\n\n\t\tpageUrl(page) {\n\t\t\tif (typeof page.url === 'string' && page.url !== '') {\n\t\t\t\treturn page.url\n\t\t\t}\n\t\t\tconst d = this.dataOf(page)\n\t\t\tconst collectiveSlug = page.collectiveSlug ?? d.collectiveSlug ?? d.collective_slug ?? ''\n\t\t\tconst pageSlug = page.slug ?? d.slug ?? this.pageKey(page)\n\t\t\tif (collectiveSlug !== '' && pageSlug !== '') {\n\t\t\t\treturn `/index.php/apps/collectives/${collectiveSlug}/${pageSlug}`\n\t\t\t}\n\t\t\treturn `/index.php/apps/collectives/${this.pageKey(page)}`\n\t\t},\n\n\t\tcollectiveKey(page) {\n\t\t\tconst d = this.dataOf(page)\n\t\t\treturn page.collectiveId ?? d.collectiveId ?? d.collective_id ?? page.collectiveSlug ?? d.collectiveSlug ?? d.collective_slug ?? ''\n\t\t},\n\n\t\tcollectiveLabel(page) {\n\t\t\tconst d = this.dataOf(page)\n\t\t\tconst name = page.collectiveName ?? d.collectiveName ?? d.collective_name ?? page.collectiveSlug ?? d.collectiveSlug ?? d.collective_slug ?? ''\n\t\t\treturn String(name).trim()\n\t\t},\n\n\t\tmodifiedMs(page) {\n\t\t\tconst d = this.dataOf(page)\n\t\t\tconst v = page.lastModified ?? page.modified ?? page.updated ?? d.lastModified ?? d.modified ?? d.last_modified ?? null\n\t\t\tif (v === null || v === undefined || v === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tif (typeof v === 'number') {\n\t\t\t\treturn v < 1e12 ? v * 1000 : v\n\t\t\t}\n\t\t\tconst parsed = Date.parse(String(v))\n\t\t\treturn Number.isNaN(parsed) === true ? null : parsed\n\t\t},\n\n\t\tmetaLabel(page) {\n\t\t\tconst ms = this.modifiedMs(page)\n\t\t\tif (ms === null) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst diff = Date.now() - ms\n\t\t\tif (diff < 0) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tif (diff < MS_PER_HOUR) {\n\t\t\t\tconst minutes = Math.max(1, Math.floor(diff / (60 * 1000)))\n\t\t\t\treturn t('nextcloud-vue', 'Updated {n} minutes ago', { n: minutes })\n\t\t\t}\n\t\t\tif (diff < MS_PER_DAY) {\n\t\t\t\tconst hours = Math.max(1, Math.floor(diff / MS_PER_HOUR))\n\t\t\t\treturn t('nextcloud-vue', 'Updated {n} hours ago', { n: hours })\n\t\t\t}\n\t\t\tconst days = Math.max(1, Math.floor(diff / MS_PER_DAY))\n\t\t\treturn t('nextcloud-vue', 'Updated {n} days ago', { n: days })\n\t\t},\n\n\t\t/**\n\t\t * Subname line for a row: the parent collective name. NcDateTime\n\t\t * renders the \"last edited\" moment separately in the row's\n\t\t * #details slot, so the subname carries the collective only.\n\t\t *\n\t\t * @param {object} page Provider/link row.\n\t\t * @return {string} The collective name, or '' when unknown.\n\t\t */\n\t\tsublineLabel(page) {\n\t\t\treturn this.collectiveLabel(page)\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(prefixUrl(this.collectivesEndpoint()), {\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.fetchPages()\n\t\t\t\t} else if (response.status === 409) {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'This page is already linked.')\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not link page.')\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('[CnCollectivesTab] link failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not link page.')\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(prefixUrl(`${this.collectivesEndpoint()}/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.fetchPages()\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not create page.')\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('[CnCollectivesTab] create failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not create page.')\n\t\t\t}\n\t\t},\n\n\t\tasync unlinkPage(page) {\n\t\t\tconst id = this.pageId(page)\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(prefixUrl(`${this.collectivesEndpoint()}/${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.fetchPages()\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not unlink page.')\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('[CnCollectivesTab] unlink failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not unlink page.')\n\t\t\t}\n\t\t},\n\n\t\tasync fetchPages() {\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(prefixUrl(this.collectivesEndpoint()), { 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.pages = rows\n\t\t\t\t} else if (response.status === 503 || response.status === 501) {\n\t\t\t\t\tthis.pages = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.pages = []\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load Knowledge pages.')\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('[CnCollectivesTab] failed to fetch Knowledge pages', err)\n\t\t\t\tthis.pages = []\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load Knowledge pages.')\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-collectives-tab__actions {\n\tdisplay: flex;\n\tgap: 8px;\n\tmargin-bottom: 10px;\n\tflex-wrap: wrap;\n}\n\n.cn-collectives-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-collectives-tab__error {\n\tcolor: var(--color-error);\n\tfont-size: 0.9em;\n\tmargin: 4px 0 8px;\n}\n\n.cn-collectives-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-collectives-tab__empty-icon {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-collectives-tab__groups {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 12px;\n}\n\n.cn-collectives-tab__group-header {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 6px;\n\tfont-size: 0.8em;\n\tfont-weight: 600;\n\ttext-transform: uppercase;\n\tletter-spacing: 0.04em;\n\tcolor: var(--color-text-maxcontrast);\n\tmargin-bottom: 2px;\n\tpadding: 0 4px;\n}\n\n.cn-collectives-tab__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n/* Emoji glyph stands in for the NcListItem avatar — circular tile\n tinted with the collective accent, mirroring NC Collectives pages. */\n.cn-collectives-tab__emoji {\n\tflex-shrink: 0;\n\twidth: 40px;\n\theight: 40px;\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\tfont-size: 1.25em;\n\tline-height: 1;\n\tborder-radius: 50%;\n\tbackground: var(--color-background-hover);\n\tcolor: var(--color-primary-element);\n}\n\n.cn-collectives-tab__subname {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n\tmin-width: 0;\n}\n\n.cn-collectives-tab__subname-line {\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-collectives-tab__snippet {\n\tfont-size: 0.9em;\n\tcolor: var(--color-text-maxcontrast);\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\tdisplay: -webkit-box;\n\t-webkit-line-clamp: 2;\n\t-webkit-box-orient: vertical;\n}\n\n.cn-collectives-tab__time {\n\tfont-size: 0.8em;\n\tcolor: var(--color-text-maxcontrast);\n}\n</style>\n"],"names":["_openBlock","_createElementBlock","_createElementVNode","_createVNode","_createTextVNode","_toDisplayString","_renderList","_createBlock"],"mappings":"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"}
1
+ {"version":3,"file":"CnCollectivesTab.vue3.js","sources":["../../../../../src/integrations/builtin/collectives/CnCollectivesTab.vue?vue&type=template&id=23e94f50&lang.js"],"sourcesContent":["<!--\n CnCollectivesTab — bespoke sidebar tab for the `collectives` integration.\n\n Replaces the generic CnIntegrationTab for the `collectives` leaf:\n renders linked Knowledge pages grouped by collective (emoji + title +\n optional content snippet + last-modified hint). Each row deep-links to\n the page inside the NC Collectives app via\n /index.php/apps/collectives/{collectiveSlug}/{pageSlug}\n (with a sane fallback to /index.php/apps/collectives/{pageId} when the\n provider only carries an id).\n\n Talks to the same OpenRegister pluggable-integration sub-resource\n `/api/objects/{register}/{schema}/{objectId}/integrations/collectives`\n served by `OCA\\OpenRegister\\Service\\Integration\\Providers\\CollectivesProvider`\n (storage strategy `link-table` — the marker `[or:{uuid}]` lives in the\n Collectives `collectives_pages.slug` column). Provider payload shape\n (verified against `CollectivesProvider::list`):\n { id, title, url, data: { id, slug, emoji, last_user_id, ... } }\n The richer \"spec\" shape (title, slug, emoji, content, fileId,\n lastModified, collectiveId, collectiveName, url) is accepted as well —\n the UI reads both top-level and `data.*` fields defensively so a future\n provider expansion stays drop-in.\n\n Bespoke-vs-generic rationale: the generic tab renders a flat title\n list which loses the collective grouping, the emoji glyph, and the\n per-page content snippet — three signals that make Knowledge linkage\n legible at-a-glance. The bespoke tab surfaces them per row.\n\n Surface behaviour:\n - Empty state (\"No Knowledge pages linked yet\") + \"Open Knowledge\"\n CTA when zero rows.\n - Loading state via NcLoadingIcon for the full-tab spin.\n - 503 unavailable banner (matches CnIntegrationTab's AD-23 graceful\n degradation), generic error path on non-OK / fetch throw.\n\n See `openregister/openspec/changes/integration-collectives/` for the\n spec delta and ADR-019 (registry mechanism), ADR-022 (consumption).\n-->\n<template>\n\t<div class=\"cn-sidebar-tab cn-collectives-tab\">\n\t\t<div v-if=\"degraded\" class=\"cn-collectives-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-collectives-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 page') }}\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 page') }}\n\t\t\t</NcButton>\n\t\t</div>\n\n\t\t<NcLoadingIcon v-if=\"loading\" />\n\n\t\t<div v-else-if=\"error\" class=\"cn-collectives-tab__error\" role=\"alert\">\n\t\t\t{{ error }}\n\t\t</div>\n\n\t\t<div\n\t\t\tv-else-if=\"pages.length === 0 && !degraded\"\n\t\t\tclass=\"cn-sidebar-tab__empty cn-collectives-tab__empty\">\n\t\t\t<BookOpenPageVariant :size=\"32\" class=\"cn-collectives-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=\"collectivesAppUrl\"\n\t\t\t\ttarget=\"_blank\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<BookOpenPageVariant :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ openCollectivesLabel }}\n\t\t\t</NcButton>\n\t\t</div>\n\n\t\t<div v-else-if=\"pages.length > 0\" class=\"cn-collectives-tab__groups\">\n\t\t\t<section\n\t\t\t\tv-for=\"group in groupedPages\"\n\t\t\t\t:key=\"group.key\"\n\t\t\t\tclass=\"cn-collectives-tab__group\">\n\t\t\t\t<header v-if=\"group.label\" class=\"cn-collectives-tab__group-header\">\n\t\t\t\t\t<FolderTextOutline :size=\"14\" />\n\t\t\t\t\t<span>{{ group.label }}</span>\n\t\t\t\t</header>\n\t\t\t\t<ul class=\"cn-collectives-tab__list\">\n\t\t\t\t\t<NcListItem\n\t\t\t\t\t\tv-for=\"page in group.pages\"\n\t\t\t\t\t\t:key=\"pageKey(page)\"\n\t\t\t\t\t\tclass=\"cn-collectives-tab__row\"\n\t\t\t\t\t\t:name=\"pageTitle(page)\"\n\t\t\t\t\t\t:bold=\"true\"\n\t\t\t\t\t\t:href=\"pageUrl(page)\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t:forceDisplayActions=\"true\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<span class=\"cn-collectives-tab__emoji\" :aria-hidden=\"true\">\n\t\t\t\t\t\t\t\t<template v-if=\"pageEmoji(page)\">{{ pageEmoji(page) }}</template>\n\t\t\t\t\t\t\t\t<FileDocumentOutline v-else :size=\"22\" />\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t<template #subname>\n\t\t\t\t\t\t\t<span class=\"cn-collectives-tab__subname\">\n\t\t\t\t\t\t\t\t<span v-if=\"sublineLabel(page)\" class=\"cn-collectives-tab__subname-line\">{{ sublineLabel(page) }}</span>\n\t\t\t\t\t\t\t\t<span v-if=\"pageSnippet(page)\" class=\"cn-collectives-tab__snippet\">{{ pageSnippet(page) }}</span>\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t<template v-if=\"modifiedMs(page)\" #details>\n\t\t\t\t\t\t\t<NcDateTime\n\t\t\t\t\t\t\t\tclass=\"cn-collectives-tab__time\"\n\t\t\t\t\t\t\t\t:timestamp=\"modifiedMs(page)\"\n\t\t\t\t\t\t\t\trelativeTime=\"short\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t<template #actions>\n\t\t\t\t\t\t\t<NcActionLink\n\t\t\t\t\t\t\t\t:closeAfterClick=\"true\"\n\t\t\t\t\t\t\t\t:href=\"pageUrl(page)\"\n\t\t\t\t\t\t\t\ttarget=\"_blank\">\n\t\t\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t\t\t<OpenInNew :size=\"20\" />\n\t\t\t\t\t\t\t\t</template>\n\t\t\t\t\t\t\t\t{{ t('nextcloud-vue', 'Open in Knowledge') }}\n\t\t\t\t\t\t\t</NcActionLink>\n\t\t\t\t\t\t\t<NcActionButton :closeAfterClick=\"true\" @click=\"unlinkPage(page)\">\n\t\t\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t\t\t<LinkOff :size=\"20\" />\n\t\t\t\t\t\t\t\t</template>\n\t\t\t\t\t\t\t\t{{ t('nextcloud-vue', 'Unlink page') }}\n\t\t\t\t\t\t\t</NcActionButton>\n\t\t\t\t\t\t</template>\n\t\t\t\t\t</NcListItem>\n\t\t\t\t</ul>\n\t\t\t</section>\n\t\t</div>\n\n\t\t<CnCollectivePagePicker\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<CnCollectivePageCreate\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, NcActionLink, NcButton, NcDateTime, NcListItem, NcLoadingIcon } from '@nextcloud/vue'\nimport AlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue'\nimport BookOpenPageVariant from 'vue-material-design-icons/BookOpenPageVariant.vue'\nimport FileDocumentOutline from 'vue-material-design-icons/FileDocumentOutline.vue'\nimport FolderTextOutline from 'vue-material-design-icons/FolderTextOutline.vue'\nimport LinkOff from 'vue-material-design-icons/LinkOff.vue'\nimport LinkVariant from 'vue-material-design-icons/LinkVariant.vue'\nimport OpenInNew from 'vue-material-design-icons/OpenInNew.vue'\nimport Plus from 'vue-material-design-icons/Plus.vue'\nimport CnCollectivePageCreate from '../../../components/CnCollectivePageCreate/CnCollectivePageCreate.vue'\nimport CnCollectivePagePicker from '../../../components/CnCollectivePagePicker/CnCollectivePagePicker.vue'\nimport { buildHeaders, prefixUrl, safeHref } from '../../../utils/index.js'\nimport { stripMarker } from '../../utils/marker.js'\n\nconst MS_PER_DAY = 24 * 60 * 60 * 1000\nconst MS_PER_HOUR = 60 * 60 * 1000\nconst SNIPPET_MAX_CHARS = 160\n\n/**\n * CnCollectivesTab — bespoke sidebar list for the `collectives`\n * integration.\n *\n * Renders Knowledge pages grouped by parent collective; each row has\n * emoji + title + content snippet + last-modified hint and deep-links\n * to the page inside NC Collectives.\n */\nexport default {\n\tname: 'CnCollectivesTab',\n\n\tcomponents: {\n\t\tNcActionButton,\n\t\tNcActionLink,\n\t\tNcButton,\n\t\tNcDateTime,\n\t\tNcListItem,\n\t\tNcLoadingIcon,\n\t\tAlertCircleOutline,\n\t\tBookOpenPageVariant,\n\t\tFileDocumentOutline,\n\t\tFolderTextOutline,\n\t\tLinkOff,\n\t\tLinkVariant,\n\t\tOpenInNew,\n\t\tPlus,\n\t\tCnCollectivePagePicker,\n\t\tCnCollectivePageCreate,\n\t},\n\n\tprops: {\n\t\t/** Stable integration id (forwarded from the registry — always `'collectives'`). */\n\t\tintegrationId: { type: String, default: 'collectives' },\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 Knowledge pages linked yet') },\n\t\t/** Pre-translated \"Open Knowledge\" CTA label. */\n\t\topenCollectivesLabel: { type: String, default: () => t('nextcloud-vue', 'Open Knowledge') },\n\t\t/** Pre-translated banner when Collectives is unavailable. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Knowledge is currently unavailable.') },\n\t\t/** URL of the NC Collectives app entry. */\n\t\tcollectivesAppUrl: { type: String, default: '/index.php/apps/collectives' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tpages: [],\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\tgroupedPages() {\n\t\t\tconst buckets = new Map()\n\t\t\tfor (const page of this.pages) {\n\t\t\t\tconst key = String(this.collectiveKey(page) || '__default__')\n\t\t\t\tif (!buckets.has(key)) {\n\t\t\t\t\tbuckets.set(key, { key, label: this.collectiveLabel(page), pages: [] })\n\t\t\t\t}\n\t\t\t\tbuckets.get(key).pages.push(page)\n\t\t\t}\n\t\t\treturn Array.from(buckets.values())\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.fetchPages()\n\t\t\t}\n\t\t} },\n\n\t\tregister() {\n\t\t\tthis.fetchPages()\n\t\t},\n\n\t\tschema() {\n\t\t\tthis.fetchPages()\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\t/**\n\t\t * Base for the Tier-2 collectives endpoints (list/link/new/destroy).\n\t\t *\n\t\t * @return {string} The endpoint URL.\n\t\t */\n\t\tcollectivesEndpoint() {\n\t\t\treturn `${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/collectives`\n\t\t},\n\n\t\t/**\n\t\t * Resolve the numeric page id from a row (top-level or `data.*`).\n\t\t *\n\t\t * @param {object} page Provider/link row.\n\t\t *\n\t\t * @return {(number|string)} The page id, or '' when absent.\n\t\t */\n\t\tpageId(page) {\n\t\t\tconst d = this.dataOf(page)\n\t\t\treturn page.pageId ?? d.pageId ?? page.id ?? d.id ?? ''\n\t\t},\n\n\t\tdataOf(page) {\n\t\t\treturn (page && typeof page.data === 'object' && page.data !== null) ? page.data : {}\n\t\t},\n\n\t\tpageKey(page) {\n\t\t\tconst d = this.dataOf(page)\n\t\t\treturn page.pageId ?? d.pageId ?? page.id ?? d.id ?? page.slug ?? d.slug ?? ''\n\t\t},\n\n\t\tpageTitle(page) {\n\t\t\tconst d = this.dataOf(page)\n\t\t\tconst candidate = page.pageTitle ?? d.pageTitle ?? page.title ?? d.title ?? d.slug ?? page.slug ?? ''\n\t\t\t// Provider returns the slug (which carries the `[or:{uuid}]`\n\t\t\t// marker) when no real title is available. Strip the marker\n\t\t\t// AND any dangling slug separator (`-`, `_`, `.`) left\n\t\t\t// behind — `phase-d1-page-[or:UUID]` would otherwise render\n\t\t\t// as `phase-d1-page-` with a trailing hyphen (D-1 bug).\n\t\t\tconst stripped = stripMarker(candidate).replace(/[-_.]+$/, '').replace(/^[-_.]+/, '').trim()\n\t\t\treturn stripped || String(this.pageKey(page))\n\t\t},\n\n\t\tpageEmoji(page) {\n\t\t\tconst d = this.dataOf(page)\n\t\t\tconst emoji = page.emoji ?? d.emoji ?? ''\n\t\t\treturn typeof emoji === 'string' ? emoji.trim() : ''\n\t\t},\n\n\t\tpageSnippet(page) {\n\t\t\tconst d = this.dataOf(page)\n\t\t\tconst raw = page.content ?? d.content ?? ''\n\t\t\tconst text = String(raw).replace(/\\s+/g, ' ').trim()\n\t\t\tif (text === '') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tif (text.length > SNIPPET_MAX_CHARS) {\n\t\t\t\treturn text.slice(0, SNIPPET_MAX_CHARS).trimEnd() + '…'\n\t\t\t}\n\t\t\treturn text\n\t\t},\n\n\t\t// A provider URL is used only when it is a safe scheme; else the app link.\n\t\tpageUrl(page) {\n\t\t\tif (typeof page.url === 'string' && page.url !== '' && safeHref(page.url) !== '#') {\n\t\t\t\treturn page.url\n\t\t\t}\n\t\t\tconst d = this.dataOf(page)\n\t\t\tconst collectiveSlug = page.collectiveSlug ?? d.collectiveSlug ?? d.collective_slug ?? ''\n\t\t\tconst pageSlug = page.slug ?? d.slug ?? this.pageKey(page)\n\t\t\tif (collectiveSlug !== '' && pageSlug !== '') {\n\t\t\t\treturn `/index.php/apps/collectives/${collectiveSlug}/${pageSlug}`\n\t\t\t}\n\t\t\treturn `/index.php/apps/collectives/${this.pageKey(page)}`\n\t\t},\n\n\t\tcollectiveKey(page) {\n\t\t\tconst d = this.dataOf(page)\n\t\t\treturn page.collectiveId ?? d.collectiveId ?? d.collective_id ?? page.collectiveSlug ?? d.collectiveSlug ?? d.collective_slug ?? ''\n\t\t},\n\n\t\tcollectiveLabel(page) {\n\t\t\tconst d = this.dataOf(page)\n\t\t\tconst name = page.collectiveName ?? d.collectiveName ?? d.collective_name ?? page.collectiveSlug ?? d.collectiveSlug ?? d.collective_slug ?? ''\n\t\t\treturn String(name).trim()\n\t\t},\n\n\t\tmodifiedMs(page) {\n\t\t\tconst d = this.dataOf(page)\n\t\t\tconst v = page.lastModified ?? page.modified ?? page.updated ?? d.lastModified ?? d.modified ?? d.last_modified ?? null\n\t\t\tif (v === null || v === undefined || v === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tif (typeof v === 'number') {\n\t\t\t\treturn v < 1e12 ? v * 1000 : v\n\t\t\t}\n\t\t\tconst parsed = Date.parse(String(v))\n\t\t\treturn Number.isNaN(parsed) === true ? null : parsed\n\t\t},\n\n\t\tmetaLabel(page) {\n\t\t\tconst ms = this.modifiedMs(page)\n\t\t\tif (ms === null) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst diff = Date.now() - ms\n\t\t\tif (diff < 0) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tif (diff < MS_PER_HOUR) {\n\t\t\t\tconst minutes = Math.max(1, Math.floor(diff / (60 * 1000)))\n\t\t\t\treturn t('nextcloud-vue', 'Updated {n} minutes ago', { n: minutes })\n\t\t\t}\n\t\t\tif (diff < MS_PER_DAY) {\n\t\t\t\tconst hours = Math.max(1, Math.floor(diff / MS_PER_HOUR))\n\t\t\t\treturn t('nextcloud-vue', 'Updated {n} hours ago', { n: hours })\n\t\t\t}\n\t\t\tconst days = Math.max(1, Math.floor(diff / MS_PER_DAY))\n\t\t\treturn t('nextcloud-vue', 'Updated {n} days ago', { n: days })\n\t\t},\n\n\t\t/**\n\t\t * Subname line for a row: the parent collective name. NcDateTime\n\t\t * renders the \"last edited\" moment separately in the row's\n\t\t * #details slot, so the subname carries the collective only.\n\t\t *\n\t\t * @param {object} page Provider/link row.\n\t\t * @return {string} The collective name, or '' when unknown.\n\t\t */\n\t\tsublineLabel(page) {\n\t\t\treturn this.collectiveLabel(page)\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(prefixUrl(this.collectivesEndpoint()), {\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.fetchPages()\n\t\t\t\t} else if (response.status === 409) {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'This page is already linked.')\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not link page.')\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('[CnCollectivesTab] link failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not link page.')\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(prefixUrl(`${this.collectivesEndpoint()}/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.fetchPages()\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not create page.')\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('[CnCollectivesTab] create failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not create page.')\n\t\t\t}\n\t\t},\n\n\t\tasync unlinkPage(page) {\n\t\t\tconst id = this.pageId(page)\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(prefixUrl(`${this.collectivesEndpoint()}/${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.fetchPages()\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not unlink page.')\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('[CnCollectivesTab] unlink failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not unlink page.')\n\t\t\t}\n\t\t},\n\n\t\tasync fetchPages() {\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(prefixUrl(this.collectivesEndpoint()), { 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.pages = rows\n\t\t\t\t} else if (response.status === 503 || response.status === 501) {\n\t\t\t\t\tthis.pages = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.pages = []\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load Knowledge pages.')\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('[CnCollectivesTab] failed to fetch Knowledge pages', err)\n\t\t\t\tthis.pages = []\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load Knowledge pages.')\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-collectives-tab__actions {\n\tdisplay: flex;\n\tgap: 8px;\n\tmargin-bottom: 10px;\n\tflex-wrap: wrap;\n}\n\n.cn-collectives-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-collectives-tab__error {\n\tcolor: var(--color-error);\n\tfont-size: 0.9em;\n\tmargin: 4px 0 8px;\n}\n\n.cn-collectives-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-collectives-tab__empty-icon {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-collectives-tab__groups {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 12px;\n}\n\n.cn-collectives-tab__group-header {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 6px;\n\tfont-size: 0.8em;\n\tfont-weight: 600;\n\ttext-transform: uppercase;\n\tletter-spacing: 0.04em;\n\tcolor: var(--color-text-maxcontrast);\n\tmargin-bottom: 2px;\n\tpadding: 0 4px;\n}\n\n.cn-collectives-tab__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n/* Emoji glyph stands in for the NcListItem avatar — circular tile\n tinted with the collective accent, mirroring NC Collectives pages. */\n.cn-collectives-tab__emoji {\n\tflex-shrink: 0;\n\twidth: 40px;\n\theight: 40px;\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\tfont-size: 1.25em;\n\tline-height: 1;\n\tborder-radius: 50%;\n\tbackground: var(--color-background-hover);\n\tcolor: var(--color-primary-element);\n}\n\n.cn-collectives-tab__subname {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n\tmin-width: 0;\n}\n\n.cn-collectives-tab__subname-line {\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-collectives-tab__snippet {\n\tfont-size: 0.9em;\n\tcolor: var(--color-text-maxcontrast);\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\tdisplay: -webkit-box;\n\t-webkit-line-clamp: 2;\n\t-webkit-box-orient: vertical;\n}\n\n.cn-collectives-tab__time {\n\tfont-size: 0.8em;\n\tcolor: var(--color-text-maxcontrast);\n}\n</style>\n"],"names":["_createVNode","_createElementVNode","_openBlock","_createElementBlock","_createTextVNode","_createBlock","_toDisplayString","_renderList","_Fragment"],"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 CurrencyEur from 'vue-material-design-icons/CurrencyEur.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'];
@@ -144,7 +145,7 @@ var script = {
144
145
  },
145
146
 
146
147
  rowUrl(row) {
147
- if (row.url) {
148
+ if (row.url && safeHref(row.url) !== '#') {
148
149
  return row.url
149
150
  }
150
151
  const projectId = row.projectId ?? row.project_id ?? row.data?.projectid ?? row.data?.project_id ?? null;
@@ -1 +1 @@
1
- {"version":3,"file":"CnCospendCard.vue2.js","sources":["../../../../../src/integrations/builtin/cospend/CnCospendCard.vue"],"sourcesContent":["<!--\n CnCospendCard — bespoke surface-aware widget for the `cospend`\n integration.\n\n Replaces the generic CnIntegrationCard for the `cospend` leaf. Branches\n on `surface` per AD-19:\n - user-dashboard / app-dashboard : headline \"N costs linked\";\n sub-line with total amount across all linked bills (per\n currency); plus a most-recent row link.\n - detail-page : bounded row list with type chip,\n amount, currency, payer — same shape as the tab but read-only.\n - single-entity : chip with row title + type chip\n (referenceType: 'cospend').\n\n Pulls rows from the same OR pluggable-integration sub-resource as\n CnCospendTab; for `single-entity` the optional `value` prop addresses\n a single row by id.\n\n See `openregister/openspec/changes/integration-cospend/` for the spec\n delta, ADR-019 (registry mechanism), AD-19 (surface fallback).\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-cospend-card__chip\" :title=\"chipSubtitle(entity)\">\n\t\t\t\t<CurrencyEur :size=\"14\" />\n\t\t\t\t<a\n\t\t\t\t\t:href=\"rowUrl(entity)\"\n\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\trel=\"noopener\">{{ rowTitle(entity) }}</a>\n\t\t\t\t<span class=\"cn-cospend-card__chip-pill\" :class=\"typeChipClass(entity)\">\n\t\t\t\t\t{{ typeLabel(entity) }}\n\t\t\t\t</span>\n\t\t\t</span>\n\t\t\t<span v-else class=\"cn-cospend-card__empty\">{{ emptyLabel }}</span>\n\t\t</template>\n\n\t\t<!-- dashboard surfaces: headline + total amount + 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-cospend-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"rows.length === 0\" class=\"cn-cospend-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<div v-else class=\"cn-cospend-card__headline\">\n\t\t\t\t<div class=\"cn-cospend-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=\"totals.length > 0\" class=\"cn-cospend-card__totals\">\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-card__totals-row\">\n\t\t\t\t\t\t<span class=\"cn-cospend-card__totals-label\">\n\t\t\t\t\t\t\t<CurrencyEur :size=\"12\" />\n\t\t\t\t\t\t\t{{ bucket.currency }}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<span class=\"cn-cospend-card__totals-amount\">{{ bucket.amountLabel }}</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-cospend-card__headline-recent\">\n\t\t\t\t\t<CurrencyEur :size=\"14\" />\n\t\t\t\t\t<a\n\t\t\t\t\t\t:href=\"rowUrl(mostRecent)\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener\">{{ rowTitle(mostRecent) }}</a>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</template>\n\n\t\t<!-- detail-page surface: bounded row list -->\n\t\t<template v-else>\n\t\t\t<div v-if=\"degraded\" class=\"cn-cospend-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"rows.length === 0\" class=\"cn-cospend-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<ul v-else class=\"cn-cospend-card__rows\">\n\t\t\t\t<li\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-card__row\"\n\t\t\t\t\t:class=\"{ 'cn-cospend-card__row--highlight': isLinked(row) }\">\n\t\t\t\t\t<a\n\t\t\t\t\t\t:href=\"rowUrl(row)\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener\"\n\t\t\t\t\t\tclass=\"cn-cospend-card__title\">{{ rowTitle(row) }}</a>\n\t\t\t\t\t<span class=\"cn-cospend-card__chip-pill\" :class=\"typeChipClass(row)\">\n\t\t\t\t\t\t{{ typeLabel(row) }}\n\t\t\t\t\t</span>\n\t\t\t\t\t<span v-if=\"amountLabel(row)\" class=\"cn-cospend-card__amount\">\n\t\t\t\t\t\t{{ amountLabel(row) }}\n\t\t\t\t\t</span>\n\t\t\t\t</li>\n\t\t\t</ul>\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 CurrencyEur from 'vue-material-design-icons/CurrencyEur.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 * CnCospendCard — bespoke surface-aware widget for the `cospend`\n * integration. See the file-level docblock for surface-by-surface\n * behaviour.\n */\nexport default {\n\tname: 'CnCospendCard',\n\n\tcomponents: { CnDetailCard, NcLoadingIcon, CurrencyEur },\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/** 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 (row id). */\n\t\tvalue: { type: [String, Number], default: '' },\n\t\t/** Pre-translated card title. */\n\t\ttitle: { type: String, default: () => t('nextcloud-vue', 'Costs') },\n\t\t/** Optional Material Design Icon component. */\n\t\ticon: { type: Object, default: () => CurrencyEur },\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 costs linked yet') },\n\t\t/** Pre-translated unavailable label. */\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\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 * Totals grouped by currency code so dashboards can show a\n\t\t * per-currency sum (Cospend supports multi-currency projects).\n\t\t *\n\t\t * @return {Array<{currency: string, total: number, 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\tif (this.rowType(row) !== 'bill' && this.amountRaw(row) === null) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\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\tcurrency: b.currency || t('nextcloud-vue', 'Unspecified'),\n\t\t\t\ttotal: b.total,\n\t\t\t\tamountLabel: b.total.toFixed(2),\n\t\t\t}))\n\t\t},\n\n\t\tcountHeadline() {\n\t\t\tconst total = this.rows.length\n\t\t\treturn n('nextcloud-vue', '{count} cost', '{count} costs', total, { count: total })\n\t\t},\n\n\t\tmostRecent() {\n\t\t\tif (this.rows.length === 0) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst sorted = [...this.rows].sort((a, b) => {\n\t\t\t\tconst ta = Date.parse(a.date ?? a.linkedAt ?? a.created_at ?? '') || 0\n\t\t\t\tconst tb = Date.parse(b.date ?? b.linkedAt ?? b.created_at ?? '') || 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\trowKey(row) {\n\t\t\treturn row.id ?? row.uuid ?? ''\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\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-card__chip-pill--bill'\n\t\t\t\t: 'cn-cospend-card__chip-pill--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 num = this.amountRaw(row)\n\t\t\tif (num === null) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst currency = this.currencyOf(row)\n\t\t\tconst fixed = num.toFixed(2)\n\t\t\treturn currency ? `${fixed} ${currency}` : fixed\n\t\t},\n\n\t\tisLinked(row) {\n\t\t\tif (!this.value) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treturn String(this.rowKey(row)) === String(this.value)\n\t\t},\n\n\t\tchipSubtitle(row) {\n\t\t\treturn this.amountLabel(row) || this.typeLabel(row)\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.rows = data.results || data.items || (Array.isArray(data) ? data : []) || []\n\t\t\t\t} else if (response.status === 503) {\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}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnCospendCard] failed to fetch rows', err)\n\t\t\t\tthis.rows = []\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('[CnCospendCard] failed to fetch single row', 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-cospend-card__empty {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n\tpadding: 8px 0;\n}\n\n.cn-cospend-card__headline {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 6px;\n}\n\n.cn-cospend-card__headline-line {\n\tfont-size: 1.05em;\n\tcolor: var(--color-main-text);\n}\n\n.cn-cospend-card__totals {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n}\n\n.cn-cospend-card__totals-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-cospend-card__totals-label {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 3px;\n}\n\n.cn-cospend-card__totals-amount {\n\tfont-weight: 600;\n\tcolor: var(--color-main-text);\n}\n\n.cn-cospend-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-cospend-card__headline-recent a {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\n.cn-cospend-card__headline-recent a:hover {\n\ttext-decoration: underline;\n}\n\n.cn-cospend-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-cospend-card__chip a {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\n.cn-cospend-card__chip a:hover {\n\ttext-decoration: underline;\n}\n\n.cn-cospend-card__chip-pill {\n\tdisplay: inline-block;\n\tpadding: 1px 6px;\n\tfont-size: 0.7em;\n\tfont-weight: 600;\n\tborder-radius: 8px;\n\tbackground: var(--color-background-dark);\n\tcolor: var(--color-main-text);\n\ttext-transform: uppercase;\n\tletter-spacing: 0.04em;\n\twhite-space: nowrap;\n}\n\n.cn-cospend-card__chip-pill--bill {\n\tbackground: var(--color-warning, #e9a40f);\n\tcolor: var(--color-main-background);\n}\n\n.cn-cospend-card__chip-pill--project {\n\tbackground: var(--color-primary-element, #21468B);\n\tcolor: var(--color-main-background);\n}\n\n.cn-cospend-card__rows {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n}\n\n.cn-cospend-card__row {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 6px;\n\tpadding: 4px 8px;\n\tborder-radius: 4px;\n\tbackground: var(--color-main-background);\n\tborder: 1px solid var(--color-border);\n\tfont-size: 0.85em;\n}\n\n.cn-cospend-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-cospend-card__title {\n\tflex: 1;\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\na.cn-cospend-card__title:hover {\n\ttext-decoration: underline;\n}\n\n.cn-cospend-card__amount {\n\tfont-weight: 600;\n\tcolor: var(--color-main-text);\n\tfont-size: 0.85em;\n}\n</style>\n"],"names":["CnDetailCard","t","n"],"mappings":";;;;;;AAgHA,MAAM,cAAe,GAAE,CAAC,gBAAgB,EAAE,eAAe,EAAE,aAAa,EAAE,eAAe,EAAA;;AAEzF;;;;;AAKA,aAAe;AACd,CAAA,IAAI,EAAE,eAAe;;CAErB,UAAU,EAAE,gBAAEA,UAAY,EAAE,aAAa,EAAE,WAAU,EAAG;;AAExD,CAAA,KAAK,EAAE;AACN;AACA,EAAA,aAAa,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW;AACnD;AACA,EAAA,QAAQ,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;AAC1C;AACA,EAAA,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;AACxC;AACA,EAAA,QAAQ,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;;AAE1C,EAAA,OAAO,EAAE;GACR,IAAI,EAAE,MAAM;AACZ,GAAA,OAAO,EAAE,aAAa;AACtB,GAAA,SAAS,EAAE,CAAC,CAAC,KAAK,cAAc,CAAC,QAAQ,CAAC,CAAC,CAAC;GAC5C;;AAED;AACA,EAAA,KAAK,EAAE,EAAE,IAAI,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,IAAI;;EAE9C,KAAK,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAMC,SAAC,CAAC,eAAe,EAAE,OAAO,CAAA,EAAG;AACnE;AACA,EAAA,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,aAAa;AAClD;EACA,OAAO,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,wBAAuB,EAAG;AAC5D;AACA,EAAA,WAAW,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM;AAC7C;AACA,EAAA,UAAU,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAMA,SAAC,CAAC,eAAe,EAAE,qBAAqB,GAAG;AACtF;EACA,gBAAgB,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAMA,SAAC,CAAC,eAAe,EAAE,oCAAoC,GAAG;AAC3G;EACA,aAAa,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,yBAAwB,EAAG;EACnE;;AAED,CAAA,IAAI,GAAG;AACN,EAAA,OAAO;AACN,GAAA,IAAI,EAAE,EAAE;GACR,MAAM,EAAE,IAAI;GACZ,OAAO,EAAE,KAAK;GACd,QAAQ,EAAE,EAAE;AACb,GAAA;EACA;;AAED,CAAA,QAAQ,EAAE;AACT,EAAA,eAAe,GAAG;AACjB,GAAA,OAAO,IAAI,CAAC,KAAM,IAAG,IAAI,CAAC,aAAY;GACtC;;EAED,QAAQ,GAAG;AACV,GAAA,OAAO,IAAI,CAAC,IAAG;GACf;;AAED;;;AAGC;AACA;;AAED,EAAA,MAAM,GAAG;AACR,GAAA,MAAM,MAAK,GAAI,IAAI,GAAG,GAAC;GACvB,KAAK,MAAM,GAAE,IAAK,IAAI,CAAC,IAAI,EAAE;IAC5B,IAAI,IAAI,CAAC,OAAO,CAAC,GAAG,CAAE,KAAI,MAAK,IAAK,IAAI,CAAC,SAAS,CAAC,GAAG,CAAE,KAAI,IAAI,EAAE;AACjE,KAAA,QAAO;AACR,KAAA;AACA,IAAA,MAAM,MAAK,GAAI,IAAI,CAAC,SAAS,CAAC,GAAG,EAAA;AACjC,IAAA,IAAI,MAAO,KAAI,IAAI,EAAE;AACpB,KAAA,QAAO;AACR,KAAA;AACA,IAAA,MAAM,QAAO,GAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAE,IAAG,GAAC;IAC1C,MAAM,WAAW,MAAM,CAAC,GAAG,CAAC,QAAQ,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAE,GAAA;AAC9D,IAAA,QAAQ,CAAC,SAAS,OAAK;IACvB,MAAM,CAAC,GAAG,CAAC,QAAQ,EAAE,QAAQ,EAAA;AAC9B,IAAA;AACA,GAAA,OAAO,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM;IAC9C,QAAQ,EAAE,CAAC,CAAC,QAAO,IAAKA,SAAC,CAAC,eAAe,EAAE,aAAa,CAAC;IACzD,KAAK,EAAE,CAAC,CAAC,KAAK;AACd,IAAA,WAAW,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;AAChC,IAAC,CAAC,CAAA;GACF;;AAED,EAAA,aAAa,GAAG;GACf,MAAM,KAAM,GAAE,IAAI,CAAC,IAAI,CAAC,OAAK;AAC7B,GAAA,OAAOC,eAAC,CAAC,eAAe,EAAE,cAAc,EAAE,eAAe,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,OAAO,CAAA;GAClF;;EAED,UAAU,GAAG;GACZ,IAAI,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE;AAC3B,IAAA,OAAO,IAAG;AACX,IAAA;AACA,GAAA,MAAM,MAAK,GAAI,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK;IAC5C,MAAM,EAAG,GAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,IAAG,IAAK,CAAC,CAAC,QAAS,IAAG,CAAC,CAAC,UAAS,IAAK,EAAE,CAAE,IAAG,EAAA;IACrE,MAAM,EAAG,GAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,IAAG,IAAK,CAAC,CAAC,QAAS,IAAG,CAAC,CAAC,UAAS,IAAK,EAAE,CAAE,IAAG,EAAA;IACrE,OAAO,EAAC,GAAI,EAAC;IACb,EAAA;AACD,GAAA,OAAO,MAAM,CAAC,CAAC,CAAA;GACf;EACD;;AAED,CAAA,KAAK,EAAE;AACN,EAAA,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,GAAG;GACtC,IAAI,CAAC,KAAK,GAAC;AACZ,GAAA,EAAG;;AAEH,EAAA,OAAO,GAAG;GACT,IAAI,CAAC,KAAK,GAAC;GACX;;AAED,EAAA,KAAK,GAAG;AACP,GAAA,IAAI,IAAI,CAAC,YAAY,eAAe,EAAE;AACrC,IAAA,IAAI,CAAC,WAAW,GAAC;AAClB,IAAA;GACA;EACD;;AAED,CAAA,OAAO,EAAE;AACR,EAAA,OAAO,GAAG;GACT,OAAO,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,EAAE,IAAI,CAAC,QAAQ,CAAC,cAAc,EAAE,IAAI,CAAC,aAAa,CAAC,CAAA;GACnH;;EAED,MAAM,CAAC,GAAG,EAAE;GACX,OAAO,GAAG,CAAC,EAAC,IAAK,GAAG,CAAC,IAAG,IAAK,EAAC;GAC9B;;EAED,QAAQ,CAAC,GAAG,EAAE;AACb,GAAA,OAAO,GAAG,CAAC,KAAI,IAAK,GAAG,CAAC,IAAK,IAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAA;GAC/C;;EAED,MAAM,CAAC,GAAG,EAAE;GACX,IAAI,GAAG,CAAC,GAAG,EAAE;IACZ,OAAO,GAAG,CAAC,GAAE;AACd,IAAA;AACA,GAAA,MAAM,YAAY,GAAG,CAAC,SAAQ,IAAK,GAAG,CAAC,cAAc,GAAG,CAAC,IAAI,EAAE,SAAU,IAAG,GAAG,CAAC,IAAI,EAAE,UAAS,IAAK,KAAG;AACvG,GAAA,IAAI,SAAU,KAAI,IAAK,IAAG,cAAc,EAAE,EAAE;AAC3C,IAAA,OAAO,CAAC,0BAA0B,EAAE,SAAS,CAAC,CAAA;AAC/C,IAAA;GACA,MAAM,EAAG,GAAE,IAAI,CAAC,MAAM,CAAC,GAAG,EAAA;GAC1B,IAAI,EAAC,KAAM,EAAE,EAAE;AACd,IAAA,OAAO,CAAC,0BAA0B,EAAE,EAAE,CAAC,CAAA;AACxC,IAAA;GACA,OAAO,IAAI,CAAC,aAAY;GACxB;;EAED,OAAO,CAAC,GAAG,EAAE;GACZ,MAAM,IAAI,MAAM,CAAC,GAAG,CAAC,IAAK,IAAG,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAC,WAAW,GAAC;GACzD,IAAI,CAAA,KAAM,MAAO,IAAG,CAAA,KAAM,SAAS,EAAE;IACpC,OAAO,MAAK;AACb,IAAA;AACA,GAAA,OAAO,SAAQ;GACf;;AAED,EAAA,SAAS,CAAC,GAAG,EAAE;AACd,GAAA,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAA,KAAM,MAAK;MAC/BD,SAAC,CAAC,eAAe,EAAE,MAAM,CAAA;MACzBA,SAAC,CAAC,eAAe,EAAE,SAAS,CAAA;GAC/B;;AAED,EAAA,aAAa,CAAC,GAAG,EAAE;AAClB,GAAA,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAA,KAAM,MAAK;AACjC,MAAE,kCAAiC;AACnC,MAAE,qCAAoC;GACvC;;AAED,EAAA,SAAS,CAAC,GAAG,EAAE;AACd,GAAA,MAAM,MAAM,GAAG,CAAC,UAAU,GAAG,CAAC,IAAI,EAAE,MAAK,IAAK,KAAG;AACjD,GAAA,IAAI,GAAE,KAAM,IAAK,IAAG,QAAQ,SAAU,IAAG,GAAI,KAAI,EAAE,EAAE;AACpD,IAAA,OAAO,IAAG;AACX,IAAA;AACA,GAAA,MAAM,GAAI,GAAE,MAAM,CAAC,GAAG,EAAA;AACtB,GAAA,OAAO,MAAM,CAAC,KAAK,CAAC,GAAG,CAAA,GAAI,IAAK,GAAE,GAAE;GACpC;;AAED,EAAA,UAAU,CAAC,GAAG,EAAE;AACf,GAAA,OAAO,GAAG,CAAC,YAAY,GAAG,CAAC,iBAAiB,GAAG,CAAC,IAAI,EAAE,iBAAiB,GAAG,CAAC,IAAI,EAAE,QAAS,IAAG,EAAC;GAC9F;;AAED,EAAA,WAAW,CAAC,GAAG,EAAE;GAChB,MAAM,GAAI,GAAE,IAAI,CAAC,SAAS,CAAC,GAAG,EAAA;AAC9B,GAAA,IAAI,GAAI,KAAI,IAAI,EAAE;AACjB,IAAA,OAAO,EAAC;AACT,IAAA;AACA,GAAA,MAAM,QAAO,GAAI,IAAI,CAAC,UAAU,CAAC,GAAG,EAAA;GACpC,MAAM,KAAI,GAAI,GAAG,CAAC,OAAO,CAAC,CAAC,EAAA;AAC3B,GAAA,OAAO,QAAO,GAAI,CAAC,EAAE,KAAK,CAAE,CAAA,EAAE,QAAQ,CAAC,CAAA,GAAI,KAAI;GAC/C;;EAED,QAAQ,CAAC,GAAG,EAAE;AACb,GAAA,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE;IAChB,OAAO,KAAI;AACZ,IAAA;GACA,OAAO,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAE,KAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAA;GACrD;;AAED,EAAA,YAAY,CAAC,GAAG,EAAE;GACjB,OAAO,IAAI,CAAC,WAAW,CAAC,GAAG,KAAK,IAAI,CAAC,SAAS,CAAC,GAAG,CAAA;GAClD;;AAED,EAAA,KAAK,GAAG;AACP,GAAA,IAAI,IAAI,CAAC,YAAY,eAAe,EAAE;AACrC,IAAA,IAAI,CAAC,WAAW,GAAC;IACjB,MAAK;AACN,IAAA;AACA,GAAA,IAAI,CAAC,SAAS,GAAC;GACf;;AAED,EAAA,MAAM,SAAS,GAAG;GACjB,IAAI,CAAC,IAAI,CAAC,QAAO,IAAK,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,QAAQ,EAAE;IACrD,MAAK;AACN,IAAA;AACA,GAAA,IAAI,CAAC,OAAM,GAAI,KAAG;AAClB,GAAA,IAAI,CAAC,QAAO,GAAI,GAAC;AACjB,GAAA,IAAI;AACH,IAAA,MAAM,QAAO,GAAI,MAAM,KAAK,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,EAAE,EAAE,OAAO,EAAE,YAAY,IAAI,EAAA;AACnF,IAAA,IAAI,QAAQ,CAAC,EAAE,EAAE;KAChB,MAAM,IAAK,GAAE,MAAM,QAAQ,CAAC,IAAI,GAAC;AACjC,KAAA,IAAI,CAAC,IAAG,GAAI,IAAI,CAAC,OAAM,IAAK,IAAI,CAAC,KAAI,KAAM,KAAK,CAAC,OAAO,CAAC,IAAI,CAAE,GAAE,IAAK,GAAE,EAAE,CAAE,IAAG,GAAC;AACjF,KAAA,MAAO,IAAI,QAAQ,CAAC,MAAK,KAAM,GAAG,EAAE;KACnC,IAAI,CAAC,IAAK,GAAE,GAAC;AACb,KAAA,IAAI,CAAC,QAAS,GAAE,IAAI,CAAC,iBAAe;KACnC,MAAK;KACN,IAAI,CAAC,IAAK,GAAE,GAAC;AACd,KAAA;IACC,CAAA,OAAO,GAAG,EAAE;AACb;IACA,OAAO,CAAC,KAAK,CAAC,sCAAsC,EAAE,GAAG,EAAA;IACzD,IAAI,CAAC,IAAK,GAAE,GAAC;AACd,IAAA,SAAU;AACT,IAAA,IAAI,CAAC,OAAQ,GAAE,MAAI;AACpB,IAAA;GACA;;AAED,EAAA,MAAM,WAAW,GAAG;GACnB,IAAI,CAAC,IAAI,CAAC,KAAI,IAAK,CAAC,IAAI,CAAC,QAAO,IAAK,CAAC,IAAI,CAAC,MAAK,IAAK,CAAC,IAAI,CAAC,QAAQ,EAAE;AACpE,IAAA,IAAI,CAAC,MAAK,GAAI,KAAG;IACjB,MAAK;AACN,IAAA;AACA,GAAA,IAAI,CAAC,OAAM,GAAI,KAAG;AAClB,GAAA,IAAI,CAAC,QAAO,GAAI,GAAC;AACjB,GAAA,IAAI;IACH,MAAM,QAAO,GAAI,MAAM,KAAK,CAAC,SAAS,CAAC,CAAC,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,EAAE,kBAAkB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,OAAO,EAAE,YAAY,EAAC,EAAG,EAAA;AAC1H,IAAA,IAAI,QAAQ,CAAC,EAAE,EAAE;AAChB,KAAA,IAAI,CAAC,MAAK,GAAI,MAAM,QAAQ,CAAC,IAAI,GAAC;AACnC,KAAA,MAAO,IAAI,QAAQ,CAAC,MAAK,KAAM,GAAG,EAAE;AACnC,KAAA,IAAI,CAAC,MAAK,GAAI,KAAG;AACjB,KAAA,IAAI,CAAC,QAAS,GAAE,IAAI,CAAC,iBAAe;KACnC,MAAK;AACN,KAAA,IAAI,CAAC,MAAK,GAAI,KAAG;AAClB,KAAA;IACC,CAAA,OAAO,GAAG,EAAE;AACb;IACA,OAAO,CAAC,KAAK,CAAC,4CAA4C,EAAE,GAAG,EAAA;AAC/D,IAAA,IAAI,CAAC,MAAK,GAAI,KAAG;AAClB,IAAA,SAAU;AACT,IAAA,IAAI,CAAC,OAAQ,GAAE,MAAI;AACpB,IAAA;GACA;EACD;AACF;;;;"}
1
+ {"version":3,"file":"CnCospendCard.vue2.js","sources":["../../../../../src/integrations/builtin/cospend/CnCospendCard.vue"],"sourcesContent":["<!--\n CnCospendCard — bespoke surface-aware widget for the `cospend`\n integration.\n\n Replaces the generic CnIntegrationCard for the `cospend` leaf. Branches\n on `surface` per AD-19:\n - user-dashboard / app-dashboard : headline \"N costs linked\";\n sub-line with total amount across all linked bills (per\n currency); plus a most-recent row link.\n - detail-page : bounded row list with type chip,\n amount, currency, payer — same shape as the tab but read-only.\n - single-entity : chip with row title + type chip\n (referenceType: 'cospend').\n\n Pulls rows from the same OR pluggable-integration sub-resource as\n CnCospendTab; for `single-entity` the optional `value` prop addresses\n a single row by id.\n\n See `openregister/openspec/changes/integration-cospend/` for the spec\n delta, ADR-019 (registry mechanism), AD-19 (surface fallback).\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-cospend-card__chip\" :title=\"chipSubtitle(entity)\">\n\t\t\t\t<CurrencyEur :size=\"14\" />\n\t\t\t\t<a\n\t\t\t\t\t:href=\"rowUrl(entity)\"\n\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\trel=\"noopener\">{{ rowTitle(entity) }}</a>\n\t\t\t\t<span class=\"cn-cospend-card__chip-pill\" :class=\"typeChipClass(entity)\">\n\t\t\t\t\t{{ typeLabel(entity) }}\n\t\t\t\t</span>\n\t\t\t</span>\n\t\t\t<span v-else class=\"cn-cospend-card__empty\">{{ emptyLabel }}</span>\n\t\t</template>\n\n\t\t<!-- dashboard surfaces: headline + total amount + 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-cospend-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"rows.length === 0\" class=\"cn-cospend-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<div v-else class=\"cn-cospend-card__headline\">\n\t\t\t\t<div class=\"cn-cospend-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=\"totals.length > 0\" class=\"cn-cospend-card__totals\">\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-card__totals-row\">\n\t\t\t\t\t\t<span class=\"cn-cospend-card__totals-label\">\n\t\t\t\t\t\t\t<CurrencyEur :size=\"12\" />\n\t\t\t\t\t\t\t{{ bucket.currency }}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<span class=\"cn-cospend-card__totals-amount\">{{ bucket.amountLabel }}</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-cospend-card__headline-recent\">\n\t\t\t\t\t<CurrencyEur :size=\"14\" />\n\t\t\t\t\t<a\n\t\t\t\t\t\t:href=\"rowUrl(mostRecent)\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener\">{{ rowTitle(mostRecent) }}</a>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</template>\n\n\t\t<!-- detail-page surface: bounded row list -->\n\t\t<template v-else>\n\t\t\t<div v-if=\"degraded\" class=\"cn-cospend-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"rows.length === 0\" class=\"cn-cospend-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<ul v-else class=\"cn-cospend-card__rows\">\n\t\t\t\t<li\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-card__row\"\n\t\t\t\t\t:class=\"{ 'cn-cospend-card__row--highlight': isLinked(row) }\">\n\t\t\t\t\t<a\n\t\t\t\t\t\t:href=\"rowUrl(row)\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener\"\n\t\t\t\t\t\tclass=\"cn-cospend-card__title\">{{ rowTitle(row) }}</a>\n\t\t\t\t\t<span class=\"cn-cospend-card__chip-pill\" :class=\"typeChipClass(row)\">\n\t\t\t\t\t\t{{ typeLabel(row) }}\n\t\t\t\t\t</span>\n\t\t\t\t\t<span v-if=\"amountLabel(row)\" class=\"cn-cospend-card__amount\">\n\t\t\t\t\t\t{{ amountLabel(row) }}\n\t\t\t\t\t</span>\n\t\t\t\t</li>\n\t\t\t</ul>\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 CurrencyEur from 'vue-material-design-icons/CurrencyEur.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 * CnCospendCard — bespoke surface-aware widget for the `cospend`\n * integration. See the file-level docblock for surface-by-surface\n * behaviour.\n */\nexport default {\n\tname: 'CnCospendCard',\n\n\tcomponents: { CnDetailCard, NcLoadingIcon, CurrencyEur },\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/** 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 (row id). */\n\t\tvalue: { type: [String, Number], default: '' },\n\t\t/** Pre-translated card title. */\n\t\ttitle: { type: String, default: () => t('nextcloud-vue', 'Costs') },\n\t\t/** Optional Material Design Icon component. */\n\t\ticon: { type: Object, default: () => CurrencyEur },\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 costs linked yet') },\n\t\t/** Pre-translated unavailable label. */\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\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 * Totals grouped by currency code so dashboards can show a\n\t\t * per-currency sum (Cospend supports multi-currency projects).\n\t\t *\n\t\t * @return {Array<{currency: string, total: number, 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\tif (this.rowType(row) !== 'bill' && this.amountRaw(row) === null) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\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\tcurrency: b.currency || t('nextcloud-vue', 'Unspecified'),\n\t\t\t\ttotal: b.total,\n\t\t\t\tamountLabel: b.total.toFixed(2),\n\t\t\t}))\n\t\t},\n\n\t\tcountHeadline() {\n\t\t\tconst total = this.rows.length\n\t\t\treturn n('nextcloud-vue', '{count} cost', '{count} costs', total, { count: total })\n\t\t},\n\n\t\tmostRecent() {\n\t\t\tif (this.rows.length === 0) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst sorted = [...this.rows].sort((a, b) => {\n\t\t\t\tconst ta = Date.parse(a.date ?? a.linkedAt ?? a.created_at ?? '') || 0\n\t\t\t\tconst tb = Date.parse(b.date ?? b.linkedAt ?? b.created_at ?? '') || 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\trowKey(row) {\n\t\t\treturn row.id ?? row.uuid ?? ''\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\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-card__chip-pill--bill'\n\t\t\t\t: 'cn-cospend-card__chip-pill--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 num = this.amountRaw(row)\n\t\t\tif (num === null) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst currency = this.currencyOf(row)\n\t\t\tconst fixed = num.toFixed(2)\n\t\t\treturn currency ? `${fixed} ${currency}` : fixed\n\t\t},\n\n\t\tisLinked(row) {\n\t\t\tif (!this.value) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treturn String(this.rowKey(row)) === String(this.value)\n\t\t},\n\n\t\tchipSubtitle(row) {\n\t\t\treturn this.amountLabel(row) || this.typeLabel(row)\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.rows = data.results || data.items || (Array.isArray(data) ? data : []) || []\n\t\t\t\t} else if (response.status === 503) {\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}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnCospendCard] failed to fetch rows', err)\n\t\t\t\tthis.rows = []\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('[CnCospendCard] failed to fetch single row', 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-cospend-card__empty {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n\tpadding: 8px 0;\n}\n\n.cn-cospend-card__headline {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 6px;\n}\n\n.cn-cospend-card__headline-line {\n\tfont-size: 1.05em;\n\tcolor: var(--color-main-text);\n}\n\n.cn-cospend-card__totals {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n}\n\n.cn-cospend-card__totals-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-cospend-card__totals-label {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 3px;\n}\n\n.cn-cospend-card__totals-amount {\n\tfont-weight: 600;\n\tcolor: var(--color-main-text);\n}\n\n.cn-cospend-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-cospend-card__headline-recent a {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\n.cn-cospend-card__headline-recent a:hover {\n\ttext-decoration: underline;\n}\n\n.cn-cospend-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-cospend-card__chip a {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\n.cn-cospend-card__chip a:hover {\n\ttext-decoration: underline;\n}\n\n.cn-cospend-card__chip-pill {\n\tdisplay: inline-block;\n\tpadding: 1px 6px;\n\tfont-size: 0.7em;\n\tfont-weight: 600;\n\tborder-radius: 8px;\n\tbackground: var(--color-background-dark);\n\tcolor: var(--color-main-text);\n\ttext-transform: uppercase;\n\tletter-spacing: 0.04em;\n\twhite-space: nowrap;\n}\n\n.cn-cospend-card__chip-pill--bill {\n\tbackground: var(--color-warning, #e9a40f);\n\tcolor: var(--color-main-background);\n}\n\n.cn-cospend-card__chip-pill--project {\n\tbackground: var(--color-primary-element, #21468B);\n\tcolor: var(--color-main-background);\n}\n\n.cn-cospend-card__rows {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n}\n\n.cn-cospend-card__row {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 6px;\n\tpadding: 4px 8px;\n\tborder-radius: 4px;\n\tbackground: var(--color-main-background);\n\tborder: 1px solid var(--color-border);\n\tfont-size: 0.85em;\n}\n\n.cn-cospend-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-cospend-card__title {\n\tflex: 1;\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\na.cn-cospend-card__title:hover {\n\ttext-decoration: underline;\n}\n\n.cn-cospend-card__amount {\n\tfont-weight: 600;\n\tcolor: var(--color-main-text);\n\tfont-size: 0.85em;\n}\n</style>\n"],"names":["CnDetailCard","t","n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"CnCospendCard.vue3.js","sources":["../../../../../src/integrations/builtin/cospend/CnCospendCard.vue?vue&type=template&id=b777ec12&lang.js"],"sourcesContent":["<!--\n CnCospendCard — bespoke surface-aware widget for the `cospend`\n integration.\n\n Replaces the generic CnIntegrationCard for the `cospend` leaf. Branches\n on `surface` per AD-19:\n - user-dashboard / app-dashboard : headline \"N costs linked\";\n sub-line with total amount across all linked bills (per\n currency); plus a most-recent row link.\n - detail-page : bounded row list with type chip,\n amount, currency, payer — same shape as the tab but read-only.\n - single-entity : chip with row title + type chip\n (referenceType: 'cospend').\n\n Pulls rows from the same OR pluggable-integration sub-resource as\n CnCospendTab; for `single-entity` the optional `value` prop addresses\n a single row by id.\n\n See `openregister/openspec/changes/integration-cospend/` for the spec\n delta, ADR-019 (registry mechanism), AD-19 (surface fallback).\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-cospend-card__chip\" :title=\"chipSubtitle(entity)\">\n\t\t\t\t<CurrencyEur :size=\"14\" />\n\t\t\t\t<a\n\t\t\t\t\t:href=\"rowUrl(entity)\"\n\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\trel=\"noopener\">{{ rowTitle(entity) }}</a>\n\t\t\t\t<span class=\"cn-cospend-card__chip-pill\" :class=\"typeChipClass(entity)\">\n\t\t\t\t\t{{ typeLabel(entity) }}\n\t\t\t\t</span>\n\t\t\t</span>\n\t\t\t<span v-else class=\"cn-cospend-card__empty\">{{ emptyLabel }}</span>\n\t\t</template>\n\n\t\t<!-- dashboard surfaces: headline + total amount + 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-cospend-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"rows.length === 0\" class=\"cn-cospend-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<div v-else class=\"cn-cospend-card__headline\">\n\t\t\t\t<div class=\"cn-cospend-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=\"totals.length > 0\" class=\"cn-cospend-card__totals\">\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-card__totals-row\">\n\t\t\t\t\t\t<span class=\"cn-cospend-card__totals-label\">\n\t\t\t\t\t\t\t<CurrencyEur :size=\"12\" />\n\t\t\t\t\t\t\t{{ bucket.currency }}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<span class=\"cn-cospend-card__totals-amount\">{{ bucket.amountLabel }}</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-cospend-card__headline-recent\">\n\t\t\t\t\t<CurrencyEur :size=\"14\" />\n\t\t\t\t\t<a\n\t\t\t\t\t\t:href=\"rowUrl(mostRecent)\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener\">{{ rowTitle(mostRecent) }}</a>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</template>\n\n\t\t<!-- detail-page surface: bounded row list -->\n\t\t<template v-else>\n\t\t\t<div v-if=\"degraded\" class=\"cn-cospend-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"rows.length === 0\" class=\"cn-cospend-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<ul v-else class=\"cn-cospend-card__rows\">\n\t\t\t\t<li\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-card__row\"\n\t\t\t\t\t:class=\"{ 'cn-cospend-card__row--highlight': isLinked(row) }\">\n\t\t\t\t\t<a\n\t\t\t\t\t\t:href=\"rowUrl(row)\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener\"\n\t\t\t\t\t\tclass=\"cn-cospend-card__title\">{{ rowTitle(row) }}</a>\n\t\t\t\t\t<span class=\"cn-cospend-card__chip-pill\" :class=\"typeChipClass(row)\">\n\t\t\t\t\t\t{{ typeLabel(row) }}\n\t\t\t\t\t</span>\n\t\t\t\t\t<span v-if=\"amountLabel(row)\" class=\"cn-cospend-card__amount\">\n\t\t\t\t\t\t{{ amountLabel(row) }}\n\t\t\t\t\t</span>\n\t\t\t\t</li>\n\t\t\t</ul>\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 CurrencyEur from 'vue-material-design-icons/CurrencyEur.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 * CnCospendCard — bespoke surface-aware widget for the `cospend`\n * integration. See the file-level docblock for surface-by-surface\n * behaviour.\n */\nexport default {\n\tname: 'CnCospendCard',\n\n\tcomponents: { CnDetailCard, NcLoadingIcon, CurrencyEur },\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/** 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 (row id). */\n\t\tvalue: { type: [String, Number], default: '' },\n\t\t/** Pre-translated card title. */\n\t\ttitle: { type: String, default: () => t('nextcloud-vue', 'Costs') },\n\t\t/** Optional Material Design Icon component. */\n\t\ticon: { type: Object, default: () => CurrencyEur },\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 costs linked yet') },\n\t\t/** Pre-translated unavailable label. */\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\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 * Totals grouped by currency code so dashboards can show a\n\t\t * per-currency sum (Cospend supports multi-currency projects).\n\t\t *\n\t\t * @return {Array<{currency: string, total: number, 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\tif (this.rowType(row) !== 'bill' && this.amountRaw(row) === null) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\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\tcurrency: b.currency || t('nextcloud-vue', 'Unspecified'),\n\t\t\t\ttotal: b.total,\n\t\t\t\tamountLabel: b.total.toFixed(2),\n\t\t\t}))\n\t\t},\n\n\t\tcountHeadline() {\n\t\t\tconst total = this.rows.length\n\t\t\treturn n('nextcloud-vue', '{count} cost', '{count} costs', total, { count: total })\n\t\t},\n\n\t\tmostRecent() {\n\t\t\tif (this.rows.length === 0) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst sorted = [...this.rows].sort((a, b) => {\n\t\t\t\tconst ta = Date.parse(a.date ?? a.linkedAt ?? a.created_at ?? '') || 0\n\t\t\t\tconst tb = Date.parse(b.date ?? b.linkedAt ?? b.created_at ?? '') || 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\trowKey(row) {\n\t\t\treturn row.id ?? row.uuid ?? ''\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\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-card__chip-pill--bill'\n\t\t\t\t: 'cn-cospend-card__chip-pill--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 num = this.amountRaw(row)\n\t\t\tif (num === null) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst currency = this.currencyOf(row)\n\t\t\tconst fixed = num.toFixed(2)\n\t\t\treturn currency ? `${fixed} ${currency}` : fixed\n\t\t},\n\n\t\tisLinked(row) {\n\t\t\tif (!this.value) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treturn String(this.rowKey(row)) === String(this.value)\n\t\t},\n\n\t\tchipSubtitle(row) {\n\t\t\treturn this.amountLabel(row) || this.typeLabel(row)\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.rows = data.results || data.items || (Array.isArray(data) ? data : []) || []\n\t\t\t\t} else if (response.status === 503) {\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}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnCospendCard] failed to fetch rows', err)\n\t\t\t\tthis.rows = []\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('[CnCospendCard] failed to fetch single row', 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-cospend-card__empty {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n\tpadding: 8px 0;\n}\n\n.cn-cospend-card__headline {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 6px;\n}\n\n.cn-cospend-card__headline-line {\n\tfont-size: 1.05em;\n\tcolor: var(--color-main-text);\n}\n\n.cn-cospend-card__totals {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n}\n\n.cn-cospend-card__totals-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-cospend-card__totals-label {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 3px;\n}\n\n.cn-cospend-card__totals-amount {\n\tfont-weight: 600;\n\tcolor: var(--color-main-text);\n}\n\n.cn-cospend-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-cospend-card__headline-recent a {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\n.cn-cospend-card__headline-recent a:hover {\n\ttext-decoration: underline;\n}\n\n.cn-cospend-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-cospend-card__chip a {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\n.cn-cospend-card__chip a:hover {\n\ttext-decoration: underline;\n}\n\n.cn-cospend-card__chip-pill {\n\tdisplay: inline-block;\n\tpadding: 1px 6px;\n\tfont-size: 0.7em;\n\tfont-weight: 600;\n\tborder-radius: 8px;\n\tbackground: var(--color-background-dark);\n\tcolor: var(--color-main-text);\n\ttext-transform: uppercase;\n\tletter-spacing: 0.04em;\n\twhite-space: nowrap;\n}\n\n.cn-cospend-card__chip-pill--bill {\n\tbackground: var(--color-warning, #e9a40f);\n\tcolor: var(--color-main-background);\n}\n\n.cn-cospend-card__chip-pill--project {\n\tbackground: var(--color-primary-element, #21468B);\n\tcolor: var(--color-main-background);\n}\n\n.cn-cospend-card__rows {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n}\n\n.cn-cospend-card__row {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 6px;\n\tpadding: 4px 8px;\n\tborder-radius: 4px;\n\tbackground: var(--color-main-background);\n\tborder: 1px solid var(--color-border);\n\tfont-size: 0.85em;\n}\n\n.cn-cospend-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-cospend-card__title {\n\tflex: 1;\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\na.cn-cospend-card__title:hover {\n\ttext-decoration: underline;\n}\n\n.cn-cospend-card__amount {\n\tfont-weight: 600;\n\tcolor: var(--color-main-text);\n\tfont-size: 0.85em;\n}\n</style>\n"],"names":["_Fragment","_createElementBlock","_createVNode","_createElementVNode","_toDisplayString","_normalizeClass","_openBlock","_renderList"],"mappings":"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"}
1
+ {"version":3,"file":"CnCospendCard.vue3.js","sources":["../../../../../src/integrations/builtin/cospend/CnCospendCard.vue?vue&type=template&id=b777ec12&lang.js"],"sourcesContent":["<!--\n CnCospendCard — bespoke surface-aware widget for the `cospend`\n integration.\n\n Replaces the generic CnIntegrationCard for the `cospend` leaf. Branches\n on `surface` per AD-19:\n - user-dashboard / app-dashboard : headline \"N costs linked\";\n sub-line with total amount across all linked bills (per\n currency); plus a most-recent row link.\n - detail-page : bounded row list with type chip,\n amount, currency, payer — same shape as the tab but read-only.\n - single-entity : chip with row title + type chip\n (referenceType: 'cospend').\n\n Pulls rows from the same OR pluggable-integration sub-resource as\n CnCospendTab; for `single-entity` the optional `value` prop addresses\n a single row by id.\n\n See `openregister/openspec/changes/integration-cospend/` for the spec\n delta, ADR-019 (registry mechanism), AD-19 (surface fallback).\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-cospend-card__chip\" :title=\"chipSubtitle(entity)\">\n\t\t\t\t<CurrencyEur :size=\"14\" />\n\t\t\t\t<a\n\t\t\t\t\t:href=\"rowUrl(entity)\"\n\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\trel=\"noopener\">{{ rowTitle(entity) }}</a>\n\t\t\t\t<span class=\"cn-cospend-card__chip-pill\" :class=\"typeChipClass(entity)\">\n\t\t\t\t\t{{ typeLabel(entity) }}\n\t\t\t\t</span>\n\t\t\t</span>\n\t\t\t<span v-else class=\"cn-cospend-card__empty\">{{ emptyLabel }}</span>\n\t\t</template>\n\n\t\t<!-- dashboard surfaces: headline + total amount + 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-cospend-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"rows.length === 0\" class=\"cn-cospend-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<div v-else class=\"cn-cospend-card__headline\">\n\t\t\t\t<div class=\"cn-cospend-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=\"totals.length > 0\" class=\"cn-cospend-card__totals\">\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-card__totals-row\">\n\t\t\t\t\t\t<span class=\"cn-cospend-card__totals-label\">\n\t\t\t\t\t\t\t<CurrencyEur :size=\"12\" />\n\t\t\t\t\t\t\t{{ bucket.currency }}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<span class=\"cn-cospend-card__totals-amount\">{{ bucket.amountLabel }}</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-cospend-card__headline-recent\">\n\t\t\t\t\t<CurrencyEur :size=\"14\" />\n\t\t\t\t\t<a\n\t\t\t\t\t\t:href=\"rowUrl(mostRecent)\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener\">{{ rowTitle(mostRecent) }}</a>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</template>\n\n\t\t<!-- detail-page surface: bounded row list -->\n\t\t<template v-else>\n\t\t\t<div v-if=\"degraded\" class=\"cn-cospend-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"rows.length === 0\" class=\"cn-cospend-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<ul v-else class=\"cn-cospend-card__rows\">\n\t\t\t\t<li\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-card__row\"\n\t\t\t\t\t:class=\"{ 'cn-cospend-card__row--highlight': isLinked(row) }\">\n\t\t\t\t\t<a\n\t\t\t\t\t\t:href=\"rowUrl(row)\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener\"\n\t\t\t\t\t\tclass=\"cn-cospend-card__title\">{{ rowTitle(row) }}</a>\n\t\t\t\t\t<span class=\"cn-cospend-card__chip-pill\" :class=\"typeChipClass(row)\">\n\t\t\t\t\t\t{{ typeLabel(row) }}\n\t\t\t\t\t</span>\n\t\t\t\t\t<span v-if=\"amountLabel(row)\" class=\"cn-cospend-card__amount\">\n\t\t\t\t\t\t{{ amountLabel(row) }}\n\t\t\t\t\t</span>\n\t\t\t\t</li>\n\t\t\t</ul>\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 CurrencyEur from 'vue-material-design-icons/CurrencyEur.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 * CnCospendCard — bespoke surface-aware widget for the `cospend`\n * integration. See the file-level docblock for surface-by-surface\n * behaviour.\n */\nexport default {\n\tname: 'CnCospendCard',\n\n\tcomponents: { CnDetailCard, NcLoadingIcon, CurrencyEur },\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/** 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 (row id). */\n\t\tvalue: { type: [String, Number], default: '' },\n\t\t/** Pre-translated card title. */\n\t\ttitle: { type: String, default: () => t('nextcloud-vue', 'Costs') },\n\t\t/** Optional Material Design Icon component. */\n\t\ticon: { type: Object, default: () => CurrencyEur },\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 costs linked yet') },\n\t\t/** Pre-translated unavailable label. */\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\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 * Totals grouped by currency code so dashboards can show a\n\t\t * per-currency sum (Cospend supports multi-currency projects).\n\t\t *\n\t\t * @return {Array<{currency: string, total: number, 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\tif (this.rowType(row) !== 'bill' && this.amountRaw(row) === null) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\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\tcurrency: b.currency || t('nextcloud-vue', 'Unspecified'),\n\t\t\t\ttotal: b.total,\n\t\t\t\tamountLabel: b.total.toFixed(2),\n\t\t\t}))\n\t\t},\n\n\t\tcountHeadline() {\n\t\t\tconst total = this.rows.length\n\t\t\treturn n('nextcloud-vue', '{count} cost', '{count} costs', total, { count: total })\n\t\t},\n\n\t\tmostRecent() {\n\t\t\tif (this.rows.length === 0) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst sorted = [...this.rows].sort((a, b) => {\n\t\t\t\tconst ta = Date.parse(a.date ?? a.linkedAt ?? a.created_at ?? '') || 0\n\t\t\t\tconst tb = Date.parse(b.date ?? b.linkedAt ?? b.created_at ?? '') || 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\trowKey(row) {\n\t\t\treturn row.id ?? row.uuid ?? ''\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\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-card__chip-pill--bill'\n\t\t\t\t: 'cn-cospend-card__chip-pill--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 num = this.amountRaw(row)\n\t\t\tif (num === null) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst currency = this.currencyOf(row)\n\t\t\tconst fixed = num.toFixed(2)\n\t\t\treturn currency ? `${fixed} ${currency}` : fixed\n\t\t},\n\n\t\tisLinked(row) {\n\t\t\tif (!this.value) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treturn String(this.rowKey(row)) === String(this.value)\n\t\t},\n\n\t\tchipSubtitle(row) {\n\t\t\treturn this.amountLabel(row) || this.typeLabel(row)\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.rows = data.results || data.items || (Array.isArray(data) ? data : []) || []\n\t\t\t\t} else if (response.status === 503) {\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}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnCospendCard] failed to fetch rows', err)\n\t\t\t\tthis.rows = []\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('[CnCospendCard] failed to fetch single row', 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-cospend-card__empty {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n\tpadding: 8px 0;\n}\n\n.cn-cospend-card__headline {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 6px;\n}\n\n.cn-cospend-card__headline-line {\n\tfont-size: 1.05em;\n\tcolor: var(--color-main-text);\n}\n\n.cn-cospend-card__totals {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n}\n\n.cn-cospend-card__totals-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-cospend-card__totals-label {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 3px;\n}\n\n.cn-cospend-card__totals-amount {\n\tfont-weight: 600;\n\tcolor: var(--color-main-text);\n}\n\n.cn-cospend-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-cospend-card__headline-recent a {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\n.cn-cospend-card__headline-recent a:hover {\n\ttext-decoration: underline;\n}\n\n.cn-cospend-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-cospend-card__chip a {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\n.cn-cospend-card__chip a:hover {\n\ttext-decoration: underline;\n}\n\n.cn-cospend-card__chip-pill {\n\tdisplay: inline-block;\n\tpadding: 1px 6px;\n\tfont-size: 0.7em;\n\tfont-weight: 600;\n\tborder-radius: 8px;\n\tbackground: var(--color-background-dark);\n\tcolor: var(--color-main-text);\n\ttext-transform: uppercase;\n\tletter-spacing: 0.04em;\n\twhite-space: nowrap;\n}\n\n.cn-cospend-card__chip-pill--bill {\n\tbackground: var(--color-warning, #e9a40f);\n\tcolor: var(--color-main-background);\n}\n\n.cn-cospend-card__chip-pill--project {\n\tbackground: var(--color-primary-element, #21468B);\n\tcolor: var(--color-main-background);\n}\n\n.cn-cospend-card__rows {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n}\n\n.cn-cospend-card__row {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 6px;\n\tpadding: 4px 8px;\n\tborder-radius: 4px;\n\tbackground: var(--color-main-background);\n\tborder: 1px solid var(--color-border);\n\tfont-size: 0.85em;\n}\n\n.cn-cospend-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-cospend-card__title {\n\tflex: 1;\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\na.cn-cospend-card__title:hover {\n\ttext-decoration: underline;\n}\n\n.cn-cospend-card__amount {\n\tfont-weight: 600;\n\tcolor: var(--color-main-text);\n\tfont-size: 0.85em;\n}\n</style>\n"],"names":["_Fragment","_createElementBlock","_createVNode","_createElementVNode","_toDisplayString","_normalizeClass","_openBlock","_renderList"],"mappings":"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"}
@@ -11,6 +11,7 @@ import __sfc_main$2 from '../../../components/CnCospendCreate/CnCospendCreate.vu
11
11
  import __sfc_main$1 from '../../../components/CnCospendPicker/CnCospendPicker.vue.js';
12
12
  import __sfc_main from '../../../components/CnStatusBadge/CnStatusBadge.vue.js';
13
13
  import { prefixUrl, buildHeaders } from '../../../utils/headers.js';
14
+ import { safeHref } from '../../../utils/safeHref.js';
14
15
 
15
16
  /**
16
17
  * CnCospendTab — bespoke sidebar tab for the `cospend` integration.
@@ -224,7 +225,7 @@ var script = {
224
225
  },
225
226
 
226
227
  rowUrl(row) {
227
- if (row.url) {
228
+ if (row.url && safeHref(row.url) !== '#') {
228
229
  return row.url
229
230
  }
230
231
  const projectId = row.projectId ?? row.project_id ?? row.data?.projectid ?? row.data?.project_id ?? null;
@@ -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":";;;;;;;;;;;;;;AAmLA;;AAEC;;;AAGA;;AAED,aAAe;AACd,CAAA,IAAI,EAAE,cAAc;;CAEpB,UAAU,EAAE;AACX,EAAA,cAAc;AACd,EAAA,QAAQ;EACR,UAAU;EACV,aAAa;AACb,EAAA,kBAAkB;gBAClBA,gBAAY;EACZ,WAAW;EACX,aAAa;AACb,EAAA,OAAO;EACP,WAAW;EACX,IAAI;iBACJC,UAAa;AACb,mBAAAC,YAAe;AACf,mBAAAC,YAAe;EACf;;AAED,CAAA,KAAK,EAAE;AACN;AACA,EAAA,aAAa,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW;AACnD;AACA,EAAA,QAAQ,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;AAC1C;AACA,EAAA,QAAQ,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI;AACvC;AACA,EAAA,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI;AACrC;EACA,OAAO,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,wBAAuB,EAAG;AAC5D;AACA,EAAA,UAAU,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAMC,SAAC,CAAC,eAAe,EAAE,qBAAqB,GAAG;AACtF;AACA,EAAA,gBAAgB,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAMA,SAAC,CAAC,eAAe,EAAE,YAAY,GAAG;;EAEnF,gBAAgB,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAMA,SAAC,CAAC,eAAe,EAAE,oCAAoC,GAAG;AAC3G;EACA,aAAa,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,yBAAwB,EAAG;EACnE;;AAED,CAAA,IAAI,GAAG;AACN,EAAA,OAAO;AACN,GAAA,IAAI,EAAE,EAAE;GACR,OAAO,EAAE,KAAK;AACd,GAAA,KAAK,EAAE,EAAE;GACT,QAAQ,EAAE,EAAE;AACZ,GAAA,UAAU,EAAE,KAAK;AACjB,GAAA,UAAU,EAAE,KAAK;AAClB,GAAA;EACA;;AAED,CAAA,QAAQ,EAAE;AACT;;AAEC;AACA;;;AAGD,EAAA,MAAM,GAAG;AACR,GAAA,MAAM,MAAK,GAAI,IAAI,GAAG,GAAC;GACvB,KAAK,MAAM,GAAE,IAAK,IAAI,CAAC,IAAI,EAAE;AAC5B,IAAA,MAAM,MAAK,GAAI,IAAI,CAAC,SAAS,CAAC,GAAG,EAAA;AACjC,IAAA,IAAI,MAAO,KAAI,IAAI,EAAE;AACpB,KAAA,QAAO;AACR,KAAA;AACA,IAAA,MAAM,QAAO,GAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAE,IAAG,GAAC;IAC1C,MAAM,WAAW,MAAM,CAAC,GAAG,CAAC,QAAQ,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAE,GAAA;AAC9D,IAAA,QAAQ,CAAC,SAAS,OAAK;IACvB,MAAM,CAAC,GAAG,CAAC,QAAQ,EAAE,QAAQ,EAAA;AAC9B,IAAA;AACA,GAAA,OAAO,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK;IAC7C,MAAM,IAAK,GAAE,CAAC,CAAC,YAAYA,SAAC,CAAC,eAAe,EAAE,aAAa,EAAA;AAC3D,IAAA,OAAO;KACN,QAAQ,EAAE,IAAI;AACd,KAAA,WAAW,EAAE,CAAC,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAE,CAAA,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAA,GAAI,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;AACrF,KAAA;IACA,CAAA;GACD;EACD;;AAED,CAAA,KAAK,EAAE;AACN,EAAA,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,CAAC,EAAE,EAAE;AACxC,GAAA,IAAI,EAAE,EAAE;AACP,IAAA,IAAI,CAAC,SAAS,GAAC;AAChB,IAAA;AACD,GAAA,EAAG;;EAEH,QAAQ,GAAG;AACV,GAAA,IAAI,CAAC,SAAS,GAAC;GACf;;AAED,EAAA,MAAM,GAAG;AACR,GAAA,IAAI,CAAC,SAAS,GAAC;GACf;EACD;;AAED,CAAA,OAAO,EAAE;AACR,EAAA,OAAO,GAAG;GACT,OAAO,SAAS,CAAC,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,EAAE,IAAI,CAAC,QAAQ,CAAC,cAAc,EAAE,IAAI,CAAC,aAAa,CAAC,CAAC,CAAA;GAC9H;;AAED;;AAEC;AACA;;AAED,EAAA,eAAe,GAAG;GACjB,OAAO,SAAS,CAAC,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,EAAE,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAA;GACnG;;EAED,MAAM,CAAC,GAAG,EAAE;GACX,OAAO,GAAG,CAAC,EAAC,IAAK,GAAG,CAAC,IAAG,IAAK,EAAC;GAC9B;;AAED;;;AAGC;AACA;AACA;;;AAGD,EAAA,SAAS,CAAC,GAAG,EAAE;AACd,GAAA,MAAM,CAAE,GAAE,CAAC,GAAE,IAAK,OAAO,GAAG,CAAC,IAAK,KAAI,QAAS,IAAG,GAAG,CAAC,SAAS,IAAI,IAAI,GAAG,CAAC,IAAG,GAAI,GAAC;GACnF,OAAO,GAAG,CAAC,OAAQ,IAAG,CAAC,CAAC,EAAC,IAAK,EAAC;GAC/B;;EAED,UAAU,GAAG;AACZ,GAAA,IAAI,CAAC,aAAa,KAAG;GACrB;;EAED,UAAU,GAAG;AACZ,GAAA,IAAI,CAAC,aAAa,KAAG;GACrB;;EAED,MAAM,UAAU,CAAC,OAAO,EAAE;AACzB,GAAA,IAAI,CAAC,UAAS,GAAI,MAAI;AACtB,GAAA,IAAI;IACH,MAAM,QAAS,GAAE,MAAM,KAAK,CAAC,IAAI,CAAC,eAAe,EAAE,EAAE;KACpD,MAAM,EAAE,MAAM;KACd,OAAO,EAAE,EAAE,GAAG,YAAY,EAAE,EAAE,cAAc,EAAE,kBAAiB,EAAG;KAClE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC;KAC7B,EAAA;AACD,IAAA,IAAI,QAAQ,CAAC,EAAE,EAAE;AAChB,KAAA,MAAM,IAAI,CAAC,SAAS,GAAC;AACtB,KAAA,MAAO,IAAI,QAAQ,CAAC,MAAK,KAAM,GAAG,EAAE;KACnC,IAAI,CAAC,KAAM,GAAEA,SAAC,CAAC,eAAe,EAAE,+BAA+B,EAAA;KAC9D,MAAK;KACN,IAAI,CAAC,KAAM,GAAEA,SAAC,CAAC,eAAe,EAAE,uBAAuB,EAAA;AACxD,KAAA;IACC,CAAA,OAAO,GAAG,EAAE;AACb;AACA,IAAA,OAAO,CAAC,KAAK,CAAC,4BAA4B,EAAE,GAAG,EAAA;IAC/C,IAAI,CAAC,KAAM,GAAEA,SAAC,CAAC,eAAe,EAAE,uBAAuB,EAAA;AACxD,IAAA;GACA;;EAED,MAAM,YAAY,CAAC,OAAO,EAAE;AAC3B,GAAA,IAAI,CAAC,UAAS,GAAI,MAAI;AACtB,GAAA,IAAI;IACH,MAAM,QAAO,GAAI,MAAM,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,eAAe,EAAE,CAAC,IAAI,CAAC,EAAE;KAC7D,MAAM,EAAE,MAAM;KACd,OAAO,EAAE,EAAE,GAAG,YAAY,EAAE,EAAE,cAAc,EAAE,kBAAiB,EAAG;KAClE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC;KAC7B,EAAA;AACD,IAAA,IAAI,QAAQ,CAAC,EAAE,EAAE;AAChB,KAAA,MAAM,IAAI,CAAC,SAAS,GAAC;KACpB,MAAK;KACN,IAAI,CAAC,KAAI,GAAIA,SAAC,CAAC,eAAe,EAAE,2BAA2B,EAAA;AAC5D,KAAA;IACC,CAAA,OAAO,GAAG,EAAE;AACb;IACA,OAAO,CAAC,KAAK,CAAC,8BAA8B,EAAE,GAAG,EAAA;IACjD,IAAI,CAAC,KAAI,GAAIA,SAAC,CAAC,eAAe,EAAE,2BAA2B,EAAA;AAC5D,IAAA;GACA;;AAED,EAAA,MAAM,WAAW,CAAC,GAAG,EAAE;GACtB,MAAM,KAAK,IAAI,CAAC,SAAS,CAAC,GAAG,EAAA;AAC7B,GAAA,IAAI,CAAC,EAAE,EAAE;IACR,MAAK;AACN,IAAA;AACA,GAAA,IAAI;IACH,MAAM,QAAS,GAAE,MAAM,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,eAAe,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE;AAC/D,KAAA,MAAM,EAAE,QAAQ;KAChB,OAAO,EAAE,YAAY,EAAE;KACvB,EAAA;AACD,IAAA,IAAI,QAAQ,CAAC,EAAE,EAAE;AAChB,KAAA,MAAM,IAAI,CAAC,SAAS,GAAC;KACpB,MAAK;KACN,IAAI,CAAC,KAAI,GAAIA,SAAC,CAAC,eAAe,EAAE,yBAAyB,EAAA;AAC1D,KAAA;IACC,CAAA,OAAO,GAAG,EAAE;AACb;IACA,OAAO,CAAC,KAAK,CAAC,8BAA8B,EAAE,GAAG,EAAA;IACjD,IAAI,CAAC,KAAI,GAAIA,SAAC,CAAC,eAAe,EAAE,yBAAyB,EAAA;AAC1D,IAAA;GACA;;EAED,QAAQ,CAAC,GAAG,EAAE;AACb,GAAA,OAAO,GAAG,CAAC,KAAI,IAAK,GAAG,CAAC,IAAK,IAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAA;GAC/C;;EAED,MAAM,CAAC,GAAG,EAAE;GACX,IAAI,GAAG,CAAC,GAAG,EAAE;IACZ,OAAO,GAAG,CAAC,GAAE;AACd,IAAA;AACA,GAAA,MAAM,YAAY,GAAG,CAAC,SAAQ,IAAK,GAAG,CAAC,cAAc,GAAG,CAAC,IAAI,EAAE,SAAU,IAAG,GAAG,CAAC,IAAI,EAAE,UAAS,IAAK,KAAG;AACvG,GAAA,IAAI,SAAU,KAAI,IAAK,IAAG,cAAc,EAAE,EAAE;AAC3C,IAAA,OAAO,CAAC,0BAA0B,EAAE,SAAS,CAAC,CAAA;AAC/C,IAAA;GACA,MAAM,EAAG,GAAE,IAAI,CAAC,MAAM,CAAC,GAAG,EAAA;GAC1B,IAAI,EAAC,KAAM,EAAE,EAAE;AACd,IAAA,OAAO,CAAC,0BAA0B,EAAE,EAAE,CAAC,CAAA;AACxC,IAAA;GACA,OAAO,IAAI,CAAC,aAAY;GACxB;;AAED;;;;;AAKC;;AAEA;;EAED,OAAO,CAAC,GAAG,EAAE;GACZ,MAAM,IAAI,MAAM,CAAC,GAAG,CAAC,IAAK,IAAG,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAC,WAAW,GAAC;GACzD,IAAI,CAAA,KAAM,MAAO,IAAG,CAAA,KAAM,SAAS,EAAE;IACpC,OAAO,MAAK;AACb,IAAA;AACA,GAAA,OAAO,SAAQ;GACf;;AAED,EAAA,SAAS,CAAC,GAAG,EAAE;AACd,GAAA,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAA,KAAM,MAAK;MAC/BA,SAAC,CAAC,eAAe,EAAE,MAAM,CAAA;MACzBA,SAAC,CAAC,eAAe,EAAE,SAAS,CAAA;GAC/B;;AAED,EAAA,aAAa,CAAC,GAAG,EAAE;AAClB,GAAA,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAA,KAAM,MAAK;AACjC,MAAE,iCAAgC;AAClC,MAAE,oCAAmC;GACtC;;AAED,EAAA,WAAW,CAAC,GAAG,EAAE;GAChB,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAA,KAAM,SAAS,SAAU,GAAE,SAAQ;GAC1D;;EAED,QAAQ,CAAC,GAAG,EAAE;AACb,GAAA,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAA,KAAM,MAAK;MAC/B,2BAA0B;AAC5B,MAAE,8BAA6B;GAChC;;AAED,EAAA,SAAS,CAAC,GAAG,EAAE;AACd,GAAA,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAA,KAAM,MAAK;AACjC,MAAE,gCAA+B;AACjC,MAAE,mCAAkC;GACrC;;AAED,EAAA,WAAW,CAAC,GAAG,EAAE;AAChB,GAAA,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAA,KAAM,MAAK;AACjC,MAAE,8BAA6B;AAC/B,MAAE,iCAAgC;GACnC;;AAED,EAAA,SAAS,CAAC,GAAG,EAAE;AACd,GAAA,MAAM,MAAM,GAAG,CAAC,UAAU,GAAG,CAAC,IAAI,EAAE,MAAK,IAAK,KAAG;AACjD,GAAA,IAAI,GAAE,KAAM,IAAK,IAAG,QAAQ,SAAU,IAAG,GAAI,KAAI,EAAE,EAAE;AACpD,IAAA,OAAO,IAAG;AACX,IAAA;AACA,GAAA,MAAM,GAAI,GAAE,MAAM,CAAC,GAAG,EAAA;AACtB,GAAA,OAAO,MAAM,CAAC,KAAK,CAAC,GAAG,CAAA,GAAI,IAAK,GAAE,GAAE;GACpC;;AAED,EAAA,UAAU,CAAC,GAAG,EAAE;AACf,GAAA,OAAO,GAAG,CAAC,YAAY,GAAG,CAAC,iBAAiB,GAAG,CAAC,IAAI,EAAE,iBAAiB,GAAG,CAAC,IAAI,EAAE,QAAS,IAAG,EAAC;GAC9F;;AAED,EAAA,WAAW,CAAC,GAAG,EAAE;AAChB,GAAA,MAAM,MAAM,GAAG,CAAC,UAAU,GAAG,CAAC,IAAI,EAAE,MAAK,IAAK,KAAG;AACjD,GAAA,IAAI,GAAE,KAAM,IAAK,IAAG,QAAQ,SAAU,IAAG,GAAI,KAAI,EAAE,EAAE;AACpD,IAAA,OAAO,EAAC;AACT,IAAA;AACA,GAAA,MAAM,QAAO,GAAI,IAAI,CAAC,UAAU,CAAC,GAAG,EAAA;AACpC,GAAA,MAAM,GAAI,GAAE,MAAM,CAAC,GAAG,EAAA;AACtB,GAAA,IAAI,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE;AACtB,IAAA,OAAO,WAAW,CAAC,EAAE,GAAG,CAAA,CAAE,EAAE,QAAQ,CAAC,CAAA,GAAI,MAAM,CAAC,GAAG,CAAA;AACpD,IAAA;GACA,MAAM,KAAI,GAAI,GAAG,CAAC,OAAO,CAAC,CAAC,EAAA;AAC3B,GAAA,OAAO,QAAO,GAAI,CAAC,EAAE,KAAK,CAAE,CAAA,EAAE,QAAQ,CAAC,CAAA,GAAI,KAAI;GAC/C;;AAED,EAAA,UAAU,CAAC,GAAG,EAAE;AACf,GAAA,OAAO,GAAG,CAAC,KAAM,IAAG,GAAG,CAAC,OAAM,IAAK,GAAG,CAAC,IAAI,EAAE,OAAM,IAAK,GAAG,CAAC,IAAI,EAAE,KAAM,IAAG,EAAC;GAC5E;;AAED,EAAA,SAAS,CAAC,GAAG,EAAE;AACd,GAAA,MAAM,GAAE,GAAI,GAAG,CAAC,IAAK,IAAG,GAAG,CAAC,UAAW,IAAG,GAAG,CAAC,IAAI,EAAE,QAAQ,GAAG,CAAC,IAAI,EAAE,SAAU,IAAG,KAAG;AACtF,GAAA,IAAI,CAAC,GAAG,EAAE;AACT,IAAA,OAAO,EAAC;AACT,IAAA;GACA,MAAM,IAAK,GAAE,OAAO,GAAI,KAAI,QAAO;AAClC,MAAE,IAAI,IAAI,CAAC,GAAI,GAAE,IAAI,CAAA;MACnB,IAAI,IAAI,CAAC,GAAG,EAAA;AACf,GAAA,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,EAAE;AACjC,IAAA,OAAO,EAAC;AACT,IAAA;GACA,OAAO,IAAI,CAAC,kBAAkB,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,QAAO,EAAG,CAAA;GACjE;;AAED;;;AAGC;AACA;;;;AAID,EAAA,WAAW,CAAC,GAAG,EAAE;GAChB,MAAM,KAAM,GAAE,GAAC;AACf,GAAA,IAAI,IAAI,CAAC,OAAO,CAAC,GAAG,CAAE,KAAI,MAAM,EAAE;AACjC,IAAA,MAAM,KAAM,GAAE,IAAI,CAAC,UAAU,CAAC,GAAG,EAAA;AACjC,IAAA,IAAI,KAAK,EAAE;AACV,KAAA,KAAK,CAAC,IAAI,CAAC,KAAK,EAAA;AACjB,KAAA;IACA,MAAM,IAAK,GAAE,IAAI,CAAC,SAAS,CAAC,GAAG,EAAA;AAC/B,IAAA,IAAI,IAAI,EAAE;AACT,KAAA,KAAK,CAAC,IAAI,CAAC,IAAI,EAAA;AAChB,KAAA;IACC,MAAK;AACN,IAAA,MAAM,QAAO,GAAI,IAAI,CAAC,UAAU,CAAC,GAAG,EAAA;IACpC,IAAI,QAAQ,EAAE;AACb,KAAA,KAAK,CAAC,IAAI,CAAC,QAAQ,EAAA;AACpB,KAAA;AACA,IAAA,MAAM,KAAM,GAAE,IAAI,CAAC,UAAU,CAAC,GAAG,EAAA;AACjC,IAAA,IAAI,KAAK,EAAE;AACV,KAAA,KAAK,CAAC,IAAI,CAAC,KAAK,EAAA;AACjB,KAAA;AACD,IAAA;AACA,GAAA,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,CAAA;GACvB;;AAED,EAAA,MAAM,SAAS,GAAG;GACjB,IAAI,CAAC,IAAI,CAAC,QAAO,IAAK,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,QAAQ,EAAE;IACrD,MAAK;AACN,IAAA;AACA,GAAA,IAAI,CAAC,OAAM,GAAI,KAAG;GAClB,IAAI,CAAC,KAAM,GAAE,GAAC;AACd,GAAA,IAAI,CAAC,QAAO,GAAI,GAAC;AACjB,GAAA,IAAI;AACH,IAAA,MAAM,QAAO,GAAI,MAAM,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,YAAY,IAAI,EAAA;AACxE,IAAA,IAAI,QAAQ,CAAC,EAAE,EAAE;KAChB,MAAM,IAAK,GAAE,MAAM,QAAQ,CAAC,IAAI,GAAC;AACjC,KAAA,MAAM,IAAG,GAAI,IAAI,CAAC,OAAM,IAAK,IAAI,CAAC,KAAI,KAAM,KAAK,CAAC,OAAO,CAAC,IAAI,CAAE,GAAE,IAAK,GAAE,EAAE,CAAE,IAAG,GAAC;KACjF,IAAI,CAAC,IAAK,GAAE,KAAG;KACd,MAAK,IAAI,QAAQ,CAAC,MAAO,KAAI,GAAE,IAAK,QAAQ,CAAC,MAAO,KAAI,GAAG,EAAE;KAC9D,IAAI,CAAC,IAAK,GAAE,GAAC;AACb,KAAA,IAAI,CAAC,QAAS,GAAE,IAAI,CAAC,iBAAe;KACnC,MAAK;KACN,IAAI,CAAC,IAAK,GAAE,GAAC;KACb,IAAI,CAAC,KAAM,GAAEA,SAAC,CAAC,eAAe,EAAE,uBAAuB,EAAA;AACxD,KAAA;IACC,CAAA,OAAO,GAAG,EAAE;AACb;IACA,OAAO,CAAC,KAAK,CAAC,qCAAqC,EAAE,GAAG,EAAA;IACxD,IAAI,CAAC,IAAK,GAAE,GAAC;IACb,IAAI,CAAC,KAAM,GAAEA,SAAC,CAAC,eAAe,EAAE,uBAAuB,EAAA;AACxD,IAAA,SAAU;AACT,IAAA,IAAI,CAAC,OAAQ,GAAE,MAAI;AACpB,IAAA;GACA;EACD;AACF;;;;"}
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 })