@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":"CnTalkCard.vue2.js","sources":["../../../../../src/integrations/builtin/talk/CnTalkCard.vue"],"sourcesContent":["<!--\n CnTalkCard — bespoke surface-aware widget for the `talk` integration.\n\n Replaces the generic CnIntegrationCard for the `talk` leaf. Branches on\n `surface` per AD-19:\n - user-dashboard / app-dashboard : headline \"N unread across M\n conversations\" (per Talk integration-talk spec — unread is the\n single glanceable signal); secondary line names the most recent\n conversation.\n - detail-page : compact list of linked\n conversations with last-message preview + unread badge per row.\n - single-entity : chip with conversation name + an\n unread dot when applicable (referenceType: 'talk').\n\n Pulls rows from the same OR pluggable-integration sub-resource as\n CnTalkTab; for `single-entity` the optional `value` prop addresses a\n single conversation by token (matching CnIntegrationCard's fetchSingle\n contract).\n\n See `openregister/openspec/changes/integration-talk/` for the spec\n delta and ADR-019 (registry mechanism), AD-19 (surface fallback).\n-->\n<template>\n\t<CnDetailCard :title=\"cardTitle\" :icon=\"cardIcon\" :collapsible=\"collapsible\">\n\t\t<NcLoadingIcon v-if=\"loading\" />\n\n\t\t<!-- single-entity surface: chip -->\n\t\t<template v-else-if=\"surface === 'single-entity'\">\n\t\t\t<span v-if=\"entity\" class=\"cn-talk-card__chip\" :title=\"chipSubtitle(entity)\">\n\t\t\t\t<ChatOutline :size=\"14\" />\n\t\t\t\t<a\n\t\t\t\t\t:href=\"chipUrl(entity)\"\n\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\trel=\"noopener\">{{ roomTitle(entity) }}</a>\n\t\t\t\t<span v-if=\"hasUnread(entity)\" class=\"cn-talk-card__chip-badge\" :aria-label=\"unreadAriaLabel(entity)\">\n\t\t\t\t\t{{ formatUnread(entity) }}\n\t\t\t\t</span>\n\t\t\t</span>\n\t\t\t<span v-else class=\"cn-talk-card__empty\">{{ emptyLabel }}</span>\n\t\t</template>\n\n\t\t<!-- dashboard surfaces: headline + most-recent -->\n\t\t<template v-else-if=\"surface === 'user-dashboard' || surface === 'app-dashboard'\">\n\t\t\t<div v-if=\"degraded\" class=\"cn-talk-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"rooms.length === 0\" class=\"cn-talk-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<div v-else class=\"cn-talk-card__headline\">\n\t\t\t\t<div class=\"cn-talk-card__headline-line\">\n\t\t\t\t\t<strong>{{ unreadHeadline }}</strong>\n\t\t\t\t</div>\n\t\t\t\t<div v-if=\"mostRecent\" class=\"cn-talk-card__headline-recent\">\n\t\t\t\t\t<ChatOutline :size=\"14\" />\n\t\t\t\t\t<a\n\t\t\t\t\t\t:href=\"roomUrl(mostRecent)\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener\">{{ roomTitle(mostRecent) }}</a>\n\t\t\t\t\t<span v-if=\"roomPreview(mostRecent)\" class=\"cn-talk-card__headline-preview\">\n\t\t\t\t\t\t· {{ roomPreview(mostRecent) }}\n\t\t\t\t\t</span>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</template>\n\n\t\t<!-- detail-page surface: compact list -->\n\t\t<template v-else>\n\t\t\t<div v-if=\"degraded\" class=\"cn-talk-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"rooms.length === 0\" class=\"cn-talk-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<ul v-else class=\"cn-talk-card__list\">\n\t\t\t\t<li\n\t\t\t\t\tv-for=\"room in displayedRooms\"\n\t\t\t\t\t:key=\"roomKey(room)\"\n\t\t\t\t\tclass=\"cn-talk-card__row\"\n\t\t\t\t\t:class=\"{ 'cn-talk-card__row--unread': hasUnread(room) }\">\n\t\t\t\t\t<div class=\"cn-talk-card__row-icon\">\n\t\t\t\t\t\t<NcAvatar\n\t\t\t\t\t\t\t:displayName=\"roomTitle(room)\"\n\t\t\t\t\t\t\t:size=\"32\"\n\t\t\t\t\t\t\t:isNoUser=\"true\"\n\t\t\t\t\t\t\thideStatus />\n\t\t\t\t\t\t<NcCounterBubble\n\t\t\t\t\t\t\tv-if=\"hasUnread(room)\"\n\t\t\t\t\t\t\tclass=\"cn-talk-card__badge\"\n\t\t\t\t\t\t\ttype=\"highlighted\"\n\t\t\t\t\t\t\t:aria-label=\"unreadAriaLabel(room)\">\n\t\t\t\t\t\t\t{{ formatUnread(room) }}\n\t\t\t\t\t\t</NcCounterBubble>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div class=\"cn-talk-card__row-main\">\n\t\t\t\t\t\t<a\n\t\t\t\t\t\t\t:href=\"roomUrl(room)\"\n\t\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t\trel=\"noopener\"\n\t\t\t\t\t\t\tclass=\"cn-talk-card__title\">{{ roomTitle(room) }}</a>\n\t\t\t\t\t\t<span v-if=\"roomPreview(room)\" class=\"cn-talk-card__subtitle\">{{ roomPreview(room) }}</span>\n\t\t\t\t\t</div>\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 { NcAvatar, NcCounterBubble, NcLoadingIcon } from '@nextcloud/vue'\nimport ChatOutline from 'vue-material-design-icons/ChatOutline.vue'\nimport CnDetailCard from '../../../components/CnDetailCard/CnDetailCard.vue'\nimport { buildHeaders, prefixUrl } from '../../../utils/index.js'\nimport { stripMarker } from '../../utils/marker.js'\n\nconst VALID_SURFACES = ['user-dashboard', 'app-dashboard', 'detail-page', 'single-entity']\nconst COMPACT_LIMIT = 5\n\n/**\n * CnTalkCard — bespoke surface-aware widget for the `talk` integration.\n *\n * Renders Talk-aware metadata across all four surfaces. See the\n * file-level docblock for surface-by-surface behaviour.\n */\nexport default {\n\tname: 'CnTalkCard',\n\n\tcomponents: { CnDetailCard, NcAvatar, NcCounterBubble, NcLoadingIcon, ChatOutline },\n\n\tprops: {\n\t\t/** Stable integration id (forwarded from the registry — always `'talk'`). */\n\t\tintegrationId: { type: String, default: 'talk' },\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 (room token). */\n\t\tvalue: { type: String, default: '' },\n\t\t/** Pre-translated card title. */\n\t\ttitle: { type: String, default: () => t('nextcloud-vue', 'Chat') },\n\t\t/** Optional Material Design Icon component. */\n\t\ticon: { type: Object, default: () => ChatOutline },\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 conversations linked yet') },\n\t\t/** Pre-translated unavailable label. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Talk is currently unavailable.') },\n\t\t/** URL of the NC Talk app entry. */\n\t\ttalkAppUrl: { type: String, default: '/index.php/apps/spreed' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\trooms: [],\n\t\t\tentity: null,\n\t\t\tloading: false,\n\t\t\tdegraded: '',\n\t\t}\n\t},\n\n\tcomputed: {\n\t\tcardTitle() {\n\t\t\treturn this.title || this.integrationId\n\t\t},\n\n\t\tcardIcon() {\n\t\t\treturn this.icon\n\t\t},\n\n\t\tdisplayedRooms() {\n\t\t\tif (this.surface === 'user-dashboard' || this.surface === 'app-dashboard') {\n\t\t\t\treturn this.rooms.slice(0, COMPACT_LIMIT)\n\t\t\t}\n\t\t\treturn this.rooms\n\t\t},\n\n\t\ttotalUnread() {\n\t\t\treturn this.rooms.reduce((sum, room) => {\n\t\t\t\tconst count = Number(room.unreadMessages ?? room.unread ?? 0)\n\t\t\t\treturn sum + (Number.isFinite(count) && count > 0 ? count : 0)\n\t\t\t}, 0)\n\t\t},\n\n\t\tunreadHeadline() {\n\t\t\tconst unread = this.totalUnread\n\t\t\tconst total = this.rooms.length\n\t\t\tif (unread === 0) {\n\t\t\t\t// \"M conversations\" via translatePlural.\n\t\t\t\treturn n('nextcloud-vue', '{count} conversation', '{count} conversations', total, { count: total })\n\t\t\t}\n\t\t\t// Pre-format the two plural fragments, then compose so we don't\n\t\t\t// trip the @nextcloud/l10n single-placeholder constraint.\n\t\t\tconst unreadFragment = n('nextcloud-vue', '{count} unread message', '{count} unread messages', unread, { count: unread })\n\t\t\tconst acrossFragment = n('nextcloud-vue', 'across {count} conversation', 'across {count} conversations', total, { count: total })\n\t\t\treturn `${unreadFragment} ${acrossFragment}`\n\t\t},\n\n\t\tmostRecent() {\n\t\t\tif (this.rooms.length === 0) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\t// Sort defensive copy by lastActivity desc.\n\t\t\tconst sorted = [...this.rooms].sort((a, b) => {\n\t\t\t\tconst ta = Number(a.lastActivity ?? 0)\n\t\t\t\tconst tb = Number(b.lastActivity ?? 0)\n\t\t\t\treturn tb - ta\n\t\t\t})\n\t\t\treturn sorted[0]\n\t\t},\n\t},\n\n\twatch: {\n\t\tobjectId: { immediate: true, handler() {\n\t\t\tthis.fetch()\n\t\t} },\n\n\t\tsurface() {\n\t\t\tthis.fetch()\n\t\t},\n\n\t\tvalue() {\n\t\t\tif (this.surface === 'single-entity') {\n\t\t\t\tthis.fetchSingle()\n\t\t\t}\n\t\t},\n\t},\n\n\tmethods: {\n\t\tbaseUrl() {\n\t\t\treturn `${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/integrations/${this.integrationId}`\n\t\t},\n\n\t\troomKey(room) {\n\t\t\treturn room.id ?? room.token ?? room.reference ?? ''\n\t\t},\n\n\t\troomTitle(room) {\n\t\t\tconst raw = room.title ?? room.displayName ?? room.name ?? this.roomKey(room)\n\t\t\treturn stripMarker(raw) || String(this.roomKey(room))\n\t\t},\n\n\t\troomUrl(room) {\n\t\t\tif (room.url) {\n\t\t\t\treturn room.url\n\t\t\t}\n\t\t\tconst token = room.token ?? room.id ?? ''\n\t\t\treturn token ? `/index.php/call/${token}` : this.talkAppUrl\n\t\t},\n\n\t\troomPreview(room) {\n\t\t\tconst msg = room.lastMessage\n\t\t\tif (msg && typeof msg === 'object') {\n\t\t\t\treturn msg.message ?? msg.text ?? ''\n\t\t\t}\n\t\t\treturn room.lastMessageText ?? room.preview ?? ''\n\t\t},\n\n\t\thasUnread(room) {\n\t\t\tconst count = Number(room.unreadMessages ?? room.unread ?? 0)\n\t\t\treturn Number.isFinite(count) && count > 0\n\t\t},\n\n\t\tformatUnread(room) {\n\t\t\tconst count = Number(room.unreadMessages ?? room.unread ?? 0)\n\t\t\tif (!Number.isFinite(count) || count <= 0) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn count > 99 ? '99+' : String(count)\n\t\t},\n\n\t\tunreadAriaLabel(room) {\n\t\t\tconst count = Number(room.unreadMessages ?? room.unread ?? 0)\n\t\t\treturn t('nextcloud-vue', '{n} unread', { n: count })\n\t\t},\n\n\t\tchipSubtitle(room) {\n\t\t\tconst preview = this.roomPreview(room)\n\t\t\treturn preview || this.roomTitle(room)\n\t\t},\n\n\t\tchipUrl(room) {\n\t\t\treturn this.roomUrl(room)\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.rooms = data.results || data.items || (Array.isArray(data) ? data : []) || []\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.rooms = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.rooms = []\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('[CnTalkCard] failed to fetch rooms', err)\n\t\t\t\tthis.rooms = []\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('[CnTalkCard] failed to fetch single conversation', 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-talk-card__empty {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n\tpadding: 8px 0;\n}\n\n.cn-talk-card__headline {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n}\n\n.cn-talk-card__headline-line {\n\tfont-size: 1.1em;\n\tcolor: var(--color-main-text);\n}\n\n.cn-talk-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-talk-card__headline-recent a {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\n.cn-talk-card__headline-recent a:hover {\n\ttext-decoration: underline;\n}\n\n.cn-talk-card__headline-preview {\n\tcolor: var(--color-text-maxcontrast);\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-talk-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-talk-card__chip a {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\n.cn-talk-card__chip a:hover {\n\ttext-decoration: underline;\n}\n\n.cn-talk-card__chip-badge {\n\tdisplay: inline-block;\n\tmin-width: 16px;\n\theight: 16px;\n\tpadding: 0 5px;\n\tborder-radius: 8px;\n\tbackground: var(--color-primary-element, #4376FC);\n\tcolor: var(--color-primary-element-text, #ffffff);\n\tfont-size: 0.7em;\n\tfont-weight: bold;\n\tline-height: 16px;\n\ttext-align: center;\n}\n\n.cn-talk-card__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n.cn-talk-card__row {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\tpadding: 6px 0;\n\tborder-bottom: 1px solid var(--color-border);\n}\n\n.cn-talk-card__row:last-child {\n\tborder-bottom: none;\n}\n\n.cn-talk-card__row-icon {\n\tposition: relative;\n\tflex-shrink: 0;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-talk-card__badge {\n\tposition: absolute;\n\ttop: -4px;\n\tright: -8px;\n\tmin-width: 16px;\n\theight: 16px;\n\tpadding: 0 5px;\n\tborder-radius: 8px;\n\tbackground: var(--color-primary-element, #4376FC);\n\tcolor: var(--color-primary-element-text, #ffffff);\n\tfont-size: 0.7em;\n\tfont-weight: bold;\n\tline-height: 16px;\n\ttext-align: center;\n}\n\n.cn-talk-card__row-main {\n\tflex: 1;\n\tmin-width: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n}\n\n.cn-talk-card__title {\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\n.cn-talk-card__row--unread .cn-talk-card__title {\n\tfont-weight: 600;\n}\n\na.cn-talk-card__title:hover {\n\ttext-decoration: underline;\n}\n\n.cn-talk-card__subtitle {\n\tfont-size: 0.8em;\n\tcolor: var(--color-text-maxcontrast);\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n</style>\n"],"names":["CnDetailCard","t","n"],"mappings":"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"}
1
+ {"version":3,"file":"CnTalkCard.vue2.js","sources":["../../../../../src/integrations/builtin/talk/CnTalkCard.vue"],"sourcesContent":["<!--\n CnTalkCard — bespoke surface-aware widget for the `talk` integration.\n\n Replaces the generic CnIntegrationCard for the `talk` leaf. Branches on\n `surface` per AD-19:\n - user-dashboard / app-dashboard : headline \"N unread across M\n conversations\" (per Talk integration-talk spec — unread is the\n single glanceable signal); secondary line names the most recent\n conversation.\n - detail-page : compact list of linked\n conversations with last-message preview + unread badge per row.\n - single-entity : chip with conversation name + an\n unread dot when applicable (referenceType: 'talk').\n\n Pulls rows from the same OR pluggable-integration sub-resource as\n CnTalkTab; for `single-entity` the optional `value` prop addresses a\n single conversation by token (matching CnIntegrationCard's fetchSingle\n contract).\n\n See `openregister/openspec/changes/integration-talk/` for the spec\n delta and ADR-019 (registry mechanism), AD-19 (surface fallback).\n-->\n<template>\n\t<CnDetailCard :title=\"cardTitle\" :icon=\"cardIcon\" :collapsible=\"collapsible\">\n\t\t<NcLoadingIcon v-if=\"loading\" />\n\n\t\t<!-- single-entity surface: chip -->\n\t\t<template v-else-if=\"surface === 'single-entity'\">\n\t\t\t<span v-if=\"entity\" class=\"cn-talk-card__chip\" :title=\"chipSubtitle(entity)\">\n\t\t\t\t<ChatOutline :size=\"14\" />\n\t\t\t\t<a\n\t\t\t\t\t:href=\"chipUrl(entity)\"\n\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\trel=\"noopener\">{{ roomTitle(entity) }}</a>\n\t\t\t\t<span v-if=\"hasUnread(entity)\" class=\"cn-talk-card__chip-badge\" :aria-label=\"unreadAriaLabel(entity)\">\n\t\t\t\t\t{{ formatUnread(entity) }}\n\t\t\t\t</span>\n\t\t\t</span>\n\t\t\t<span v-else class=\"cn-talk-card__empty\">{{ emptyLabel }}</span>\n\t\t</template>\n\n\t\t<!-- dashboard surfaces: headline + most-recent -->\n\t\t<template v-else-if=\"surface === 'user-dashboard' || surface === 'app-dashboard'\">\n\t\t\t<div v-if=\"degraded\" class=\"cn-talk-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"rooms.length === 0\" class=\"cn-talk-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<div v-else class=\"cn-talk-card__headline\">\n\t\t\t\t<div class=\"cn-talk-card__headline-line\">\n\t\t\t\t\t<strong>{{ unreadHeadline }}</strong>\n\t\t\t\t</div>\n\t\t\t\t<div v-if=\"mostRecent\" class=\"cn-talk-card__headline-recent\">\n\t\t\t\t\t<ChatOutline :size=\"14\" />\n\t\t\t\t\t<a\n\t\t\t\t\t\t:href=\"roomUrl(mostRecent)\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener\">{{ roomTitle(mostRecent) }}</a>\n\t\t\t\t\t<span v-if=\"roomPreview(mostRecent)\" class=\"cn-talk-card__headline-preview\">\n\t\t\t\t\t\t· {{ roomPreview(mostRecent) }}\n\t\t\t\t\t</span>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</template>\n\n\t\t<!-- detail-page surface: compact list -->\n\t\t<template v-else>\n\t\t\t<div v-if=\"degraded\" class=\"cn-talk-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"rooms.length === 0\" class=\"cn-talk-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<ul v-else class=\"cn-talk-card__list\">\n\t\t\t\t<li\n\t\t\t\t\tv-for=\"room in displayedRooms\"\n\t\t\t\t\t:key=\"roomKey(room)\"\n\t\t\t\t\tclass=\"cn-talk-card__row\"\n\t\t\t\t\t:class=\"{ 'cn-talk-card__row--unread': hasUnread(room) }\">\n\t\t\t\t\t<div class=\"cn-talk-card__row-icon\">\n\t\t\t\t\t\t<NcAvatar\n\t\t\t\t\t\t\t:displayName=\"roomTitle(room)\"\n\t\t\t\t\t\t\t:size=\"32\"\n\t\t\t\t\t\t\t:isNoUser=\"true\"\n\t\t\t\t\t\t\thideStatus />\n\t\t\t\t\t\t<NcCounterBubble\n\t\t\t\t\t\t\tv-if=\"hasUnread(room)\"\n\t\t\t\t\t\t\tclass=\"cn-talk-card__badge\"\n\t\t\t\t\t\t\ttype=\"highlighted\"\n\t\t\t\t\t\t\t:aria-label=\"unreadAriaLabel(room)\">\n\t\t\t\t\t\t\t{{ formatUnread(room) }}\n\t\t\t\t\t\t</NcCounterBubble>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div class=\"cn-talk-card__row-main\">\n\t\t\t\t\t\t<a\n\t\t\t\t\t\t\t:href=\"roomUrl(room)\"\n\t\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t\trel=\"noopener\"\n\t\t\t\t\t\t\tclass=\"cn-talk-card__title\">{{ roomTitle(room) }}</a>\n\t\t\t\t\t\t<span v-if=\"roomPreview(room)\" class=\"cn-talk-card__subtitle\">{{ roomPreview(room) }}</span>\n\t\t\t\t\t</div>\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 { NcAvatar, NcCounterBubble, NcLoadingIcon } from '@nextcloud/vue'\nimport ChatOutline from 'vue-material-design-icons/ChatOutline.vue'\nimport CnDetailCard from '../../../components/CnDetailCard/CnDetailCard.vue'\nimport { buildHeaders, prefixUrl, safeHref } from '../../../utils/index.js'\nimport { stripMarker } from '../../utils/marker.js'\n\nconst VALID_SURFACES = ['user-dashboard', 'app-dashboard', 'detail-page', 'single-entity']\nconst COMPACT_LIMIT = 5\n\n/**\n * CnTalkCard — bespoke surface-aware widget for the `talk` integration.\n *\n * Renders Talk-aware metadata across all four surfaces. See the\n * file-level docblock for surface-by-surface behaviour.\n */\nexport default {\n\tname: 'CnTalkCard',\n\n\tcomponents: { CnDetailCard, NcAvatar, NcCounterBubble, NcLoadingIcon, ChatOutline },\n\n\tprops: {\n\t\t/** Stable integration id (forwarded from the registry — always `'talk'`). */\n\t\tintegrationId: { type: String, default: 'talk' },\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 (room token). */\n\t\tvalue: { type: String, default: '' },\n\t\t/** Pre-translated card title. */\n\t\ttitle: { type: String, default: () => t('nextcloud-vue', 'Chat') },\n\t\t/** Optional Material Design Icon component. */\n\t\ticon: { type: Object, default: () => ChatOutline },\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 conversations linked yet') },\n\t\t/** Pre-translated unavailable label. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Talk is currently unavailable.') },\n\t\t/** URL of the NC Talk app entry. */\n\t\ttalkAppUrl: { type: String, default: '/index.php/apps/spreed' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\trooms: [],\n\t\t\tentity: null,\n\t\t\tloading: false,\n\t\t\tdegraded: '',\n\t\t}\n\t},\n\n\tcomputed: {\n\t\tcardTitle() {\n\t\t\treturn this.title || this.integrationId\n\t\t},\n\n\t\tcardIcon() {\n\t\t\treturn this.icon\n\t\t},\n\n\t\tdisplayedRooms() {\n\t\t\tif (this.surface === 'user-dashboard' || this.surface === 'app-dashboard') {\n\t\t\t\treturn this.rooms.slice(0, COMPACT_LIMIT)\n\t\t\t}\n\t\t\treturn this.rooms\n\t\t},\n\n\t\ttotalUnread() {\n\t\t\treturn this.rooms.reduce((sum, room) => {\n\t\t\t\tconst count = Number(room.unreadMessages ?? room.unread ?? 0)\n\t\t\t\treturn sum + (Number.isFinite(count) && count > 0 ? count : 0)\n\t\t\t}, 0)\n\t\t},\n\n\t\tunreadHeadline() {\n\t\t\tconst unread = this.totalUnread\n\t\t\tconst total = this.rooms.length\n\t\t\tif (unread === 0) {\n\t\t\t\t// \"M conversations\" via translatePlural.\n\t\t\t\treturn n('nextcloud-vue', '{count} conversation', '{count} conversations', total, { count: total })\n\t\t\t}\n\t\t\t// Pre-format the two plural fragments, then compose so we don't\n\t\t\t// trip the @nextcloud/l10n single-placeholder constraint.\n\t\t\tconst unreadFragment = n('nextcloud-vue', '{count} unread message', '{count} unread messages', unread, { count: unread })\n\t\t\tconst acrossFragment = n('nextcloud-vue', 'across {count} conversation', 'across {count} conversations', total, { count: total })\n\t\t\treturn `${unreadFragment} ${acrossFragment}`\n\t\t},\n\n\t\tmostRecent() {\n\t\t\tif (this.rooms.length === 0) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\t// Sort defensive copy by lastActivity desc.\n\t\t\tconst sorted = [...this.rooms].sort((a, b) => {\n\t\t\t\tconst ta = Number(a.lastActivity ?? 0)\n\t\t\t\tconst tb = Number(b.lastActivity ?? 0)\n\t\t\t\treturn tb - ta\n\t\t\t})\n\t\t\treturn sorted[0]\n\t\t},\n\t},\n\n\twatch: {\n\t\tobjectId: { immediate: true, handler() {\n\t\t\tthis.fetch()\n\t\t} },\n\n\t\tsurface() {\n\t\t\tthis.fetch()\n\t\t},\n\n\t\tvalue() {\n\t\t\tif (this.surface === 'single-entity') {\n\t\t\t\tthis.fetchSingle()\n\t\t\t}\n\t\t},\n\t},\n\n\tmethods: {\n\t\tbaseUrl() {\n\t\t\treturn `${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/integrations/${this.integrationId}`\n\t\t},\n\n\t\troomKey(room) {\n\t\t\treturn room.id ?? room.token ?? room.reference ?? ''\n\t\t},\n\n\t\troomTitle(room) {\n\t\t\tconst raw = room.title ?? room.displayName ?? room.name ?? this.roomKey(room)\n\t\t\treturn stripMarker(raw) || String(this.roomKey(room))\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\troomUrl(room) {\n\t\t\tif (room.url && safeHref(room.url) !== '#') {\n\t\t\t\treturn room.url\n\t\t\t}\n\t\t\tconst token = room.token ?? room.id ?? ''\n\t\t\treturn token ? `/index.php/call/${token}` : this.talkAppUrl\n\t\t},\n\n\t\troomPreview(room) {\n\t\t\tconst msg = room.lastMessage\n\t\t\tif (msg && typeof msg === 'object') {\n\t\t\t\treturn msg.message ?? msg.text ?? ''\n\t\t\t}\n\t\t\treturn room.lastMessageText ?? room.preview ?? ''\n\t\t},\n\n\t\thasUnread(room) {\n\t\t\tconst count = Number(room.unreadMessages ?? room.unread ?? 0)\n\t\t\treturn Number.isFinite(count) && count > 0\n\t\t},\n\n\t\tformatUnread(room) {\n\t\t\tconst count = Number(room.unreadMessages ?? room.unread ?? 0)\n\t\t\tif (!Number.isFinite(count) || count <= 0) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn count > 99 ? '99+' : String(count)\n\t\t},\n\n\t\tunreadAriaLabel(room) {\n\t\t\tconst count = Number(room.unreadMessages ?? room.unread ?? 0)\n\t\t\treturn t('nextcloud-vue', '{n} unread', { n: count })\n\t\t},\n\n\t\tchipSubtitle(room) {\n\t\t\tconst preview = this.roomPreview(room)\n\t\t\treturn preview || this.roomTitle(room)\n\t\t},\n\n\t\tchipUrl(room) {\n\t\t\treturn this.roomUrl(room)\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.rooms = data.results || data.items || (Array.isArray(data) ? data : []) || []\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.rooms = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.rooms = []\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('[CnTalkCard] failed to fetch rooms', err)\n\t\t\t\tthis.rooms = []\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('[CnTalkCard] failed to fetch single conversation', 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-talk-card__empty {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n\tpadding: 8px 0;\n}\n\n.cn-talk-card__headline {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n}\n\n.cn-talk-card__headline-line {\n\tfont-size: 1.1em;\n\tcolor: var(--color-main-text);\n}\n\n.cn-talk-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-talk-card__headline-recent a {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\n.cn-talk-card__headline-recent a:hover {\n\ttext-decoration: underline;\n}\n\n.cn-talk-card__headline-preview {\n\tcolor: var(--color-text-maxcontrast);\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-talk-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-talk-card__chip a {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\n.cn-talk-card__chip a:hover {\n\ttext-decoration: underline;\n}\n\n.cn-talk-card__chip-badge {\n\tdisplay: inline-block;\n\tmin-width: 16px;\n\theight: 16px;\n\tpadding: 0 5px;\n\tborder-radius: 8px;\n\tbackground: var(--color-primary-element, #4376FC);\n\tcolor: var(--color-primary-element-text, #ffffff);\n\tfont-size: 0.7em;\n\tfont-weight: bold;\n\tline-height: 16px;\n\ttext-align: center;\n}\n\n.cn-talk-card__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n.cn-talk-card__row {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\tpadding: 6px 0;\n\tborder-bottom: 1px solid var(--color-border);\n}\n\n.cn-talk-card__row:last-child {\n\tborder-bottom: none;\n}\n\n.cn-talk-card__row-icon {\n\tposition: relative;\n\tflex-shrink: 0;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-talk-card__badge {\n\tposition: absolute;\n\ttop: -4px;\n\tright: -8px;\n\tmin-width: 16px;\n\theight: 16px;\n\tpadding: 0 5px;\n\tborder-radius: 8px;\n\tbackground: var(--color-primary-element, #4376FC);\n\tcolor: var(--color-primary-element-text, #ffffff);\n\tfont-size: 0.7em;\n\tfont-weight: bold;\n\tline-height: 16px;\n\ttext-align: center;\n}\n\n.cn-talk-card__row-main {\n\tflex: 1;\n\tmin-width: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n}\n\n.cn-talk-card__title {\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\n.cn-talk-card__row--unread .cn-talk-card__title {\n\tfont-weight: 600;\n}\n\na.cn-talk-card__title:hover {\n\ttext-decoration: underline;\n}\n\n.cn-talk-card__subtitle {\n\tfont-size: 0.8em;\n\tcolor: var(--color-text-maxcontrast);\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n</style>\n"],"names":["CnDetailCard","t","n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"CnTalkCard.vue3.js","sources":["../../../../../src/integrations/builtin/talk/CnTalkCard.vue?vue&type=template&id=5f0c0447&lang.js"],"sourcesContent":["<!--\n CnTalkCard — bespoke surface-aware widget for the `talk` integration.\n\n Replaces the generic CnIntegrationCard for the `talk` leaf. Branches on\n `surface` per AD-19:\n - user-dashboard / app-dashboard : headline \"N unread across M\n conversations\" (per Talk integration-talk spec — unread is the\n single glanceable signal); secondary line names the most recent\n conversation.\n - detail-page : compact list of linked\n conversations with last-message preview + unread badge per row.\n - single-entity : chip with conversation name + an\n unread dot when applicable (referenceType: 'talk').\n\n Pulls rows from the same OR pluggable-integration sub-resource as\n CnTalkTab; for `single-entity` the optional `value` prop addresses a\n single conversation by token (matching CnIntegrationCard's fetchSingle\n contract).\n\n See `openregister/openspec/changes/integration-talk/` for the spec\n delta and ADR-019 (registry mechanism), AD-19 (surface fallback).\n-->\n<template>\n\t<CnDetailCard :title=\"cardTitle\" :icon=\"cardIcon\" :collapsible=\"collapsible\">\n\t\t<NcLoadingIcon v-if=\"loading\" />\n\n\t\t<!-- single-entity surface: chip -->\n\t\t<template v-else-if=\"surface === 'single-entity'\">\n\t\t\t<span v-if=\"entity\" class=\"cn-talk-card__chip\" :title=\"chipSubtitle(entity)\">\n\t\t\t\t<ChatOutline :size=\"14\" />\n\t\t\t\t<a\n\t\t\t\t\t:href=\"chipUrl(entity)\"\n\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\trel=\"noopener\">{{ roomTitle(entity) }}</a>\n\t\t\t\t<span v-if=\"hasUnread(entity)\" class=\"cn-talk-card__chip-badge\" :aria-label=\"unreadAriaLabel(entity)\">\n\t\t\t\t\t{{ formatUnread(entity) }}\n\t\t\t\t</span>\n\t\t\t</span>\n\t\t\t<span v-else class=\"cn-talk-card__empty\">{{ emptyLabel }}</span>\n\t\t</template>\n\n\t\t<!-- dashboard surfaces: headline + most-recent -->\n\t\t<template v-else-if=\"surface === 'user-dashboard' || surface === 'app-dashboard'\">\n\t\t\t<div v-if=\"degraded\" class=\"cn-talk-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"rooms.length === 0\" class=\"cn-talk-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<div v-else class=\"cn-talk-card__headline\">\n\t\t\t\t<div class=\"cn-talk-card__headline-line\">\n\t\t\t\t\t<strong>{{ unreadHeadline }}</strong>\n\t\t\t\t</div>\n\t\t\t\t<div v-if=\"mostRecent\" class=\"cn-talk-card__headline-recent\">\n\t\t\t\t\t<ChatOutline :size=\"14\" />\n\t\t\t\t\t<a\n\t\t\t\t\t\t:href=\"roomUrl(mostRecent)\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener\">{{ roomTitle(mostRecent) }}</a>\n\t\t\t\t\t<span v-if=\"roomPreview(mostRecent)\" class=\"cn-talk-card__headline-preview\">\n\t\t\t\t\t\t· {{ roomPreview(mostRecent) }}\n\t\t\t\t\t</span>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</template>\n\n\t\t<!-- detail-page surface: compact list -->\n\t\t<template v-else>\n\t\t\t<div v-if=\"degraded\" class=\"cn-talk-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"rooms.length === 0\" class=\"cn-talk-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<ul v-else class=\"cn-talk-card__list\">\n\t\t\t\t<li\n\t\t\t\t\tv-for=\"room in displayedRooms\"\n\t\t\t\t\t:key=\"roomKey(room)\"\n\t\t\t\t\tclass=\"cn-talk-card__row\"\n\t\t\t\t\t:class=\"{ 'cn-talk-card__row--unread': hasUnread(room) }\">\n\t\t\t\t\t<div class=\"cn-talk-card__row-icon\">\n\t\t\t\t\t\t<NcAvatar\n\t\t\t\t\t\t\t:displayName=\"roomTitle(room)\"\n\t\t\t\t\t\t\t:size=\"32\"\n\t\t\t\t\t\t\t:isNoUser=\"true\"\n\t\t\t\t\t\t\thideStatus />\n\t\t\t\t\t\t<NcCounterBubble\n\t\t\t\t\t\t\tv-if=\"hasUnread(room)\"\n\t\t\t\t\t\t\tclass=\"cn-talk-card__badge\"\n\t\t\t\t\t\t\ttype=\"highlighted\"\n\t\t\t\t\t\t\t:aria-label=\"unreadAriaLabel(room)\">\n\t\t\t\t\t\t\t{{ formatUnread(room) }}\n\t\t\t\t\t\t</NcCounterBubble>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div class=\"cn-talk-card__row-main\">\n\t\t\t\t\t\t<a\n\t\t\t\t\t\t\t:href=\"roomUrl(room)\"\n\t\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t\trel=\"noopener\"\n\t\t\t\t\t\t\tclass=\"cn-talk-card__title\">{{ roomTitle(room) }}</a>\n\t\t\t\t\t\t<span v-if=\"roomPreview(room)\" class=\"cn-talk-card__subtitle\">{{ roomPreview(room) }}</span>\n\t\t\t\t\t</div>\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 { NcAvatar, NcCounterBubble, NcLoadingIcon } from '@nextcloud/vue'\nimport ChatOutline from 'vue-material-design-icons/ChatOutline.vue'\nimport CnDetailCard from '../../../components/CnDetailCard/CnDetailCard.vue'\nimport { buildHeaders, prefixUrl } from '../../../utils/index.js'\nimport { stripMarker } from '../../utils/marker.js'\n\nconst VALID_SURFACES = ['user-dashboard', 'app-dashboard', 'detail-page', 'single-entity']\nconst COMPACT_LIMIT = 5\n\n/**\n * CnTalkCard — bespoke surface-aware widget for the `talk` integration.\n *\n * Renders Talk-aware metadata across all four surfaces. See the\n * file-level docblock for surface-by-surface behaviour.\n */\nexport default {\n\tname: 'CnTalkCard',\n\n\tcomponents: { CnDetailCard, NcAvatar, NcCounterBubble, NcLoadingIcon, ChatOutline },\n\n\tprops: {\n\t\t/** Stable integration id (forwarded from the registry — always `'talk'`). */\n\t\tintegrationId: { type: String, default: 'talk' },\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 (room token). */\n\t\tvalue: { type: String, default: '' },\n\t\t/** Pre-translated card title. */\n\t\ttitle: { type: String, default: () => t('nextcloud-vue', 'Chat') },\n\t\t/** Optional Material Design Icon component. */\n\t\ticon: { type: Object, default: () => ChatOutline },\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 conversations linked yet') },\n\t\t/** Pre-translated unavailable label. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Talk is currently unavailable.') },\n\t\t/** URL of the NC Talk app entry. */\n\t\ttalkAppUrl: { type: String, default: '/index.php/apps/spreed' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\trooms: [],\n\t\t\tentity: null,\n\t\t\tloading: false,\n\t\t\tdegraded: '',\n\t\t}\n\t},\n\n\tcomputed: {\n\t\tcardTitle() {\n\t\t\treturn this.title || this.integrationId\n\t\t},\n\n\t\tcardIcon() {\n\t\t\treturn this.icon\n\t\t},\n\n\t\tdisplayedRooms() {\n\t\t\tif (this.surface === 'user-dashboard' || this.surface === 'app-dashboard') {\n\t\t\t\treturn this.rooms.slice(0, COMPACT_LIMIT)\n\t\t\t}\n\t\t\treturn this.rooms\n\t\t},\n\n\t\ttotalUnread() {\n\t\t\treturn this.rooms.reduce((sum, room) => {\n\t\t\t\tconst count = Number(room.unreadMessages ?? room.unread ?? 0)\n\t\t\t\treturn sum + (Number.isFinite(count) && count > 0 ? count : 0)\n\t\t\t}, 0)\n\t\t},\n\n\t\tunreadHeadline() {\n\t\t\tconst unread = this.totalUnread\n\t\t\tconst total = this.rooms.length\n\t\t\tif (unread === 0) {\n\t\t\t\t// \"M conversations\" via translatePlural.\n\t\t\t\treturn n('nextcloud-vue', '{count} conversation', '{count} conversations', total, { count: total })\n\t\t\t}\n\t\t\t// Pre-format the two plural fragments, then compose so we don't\n\t\t\t// trip the @nextcloud/l10n single-placeholder constraint.\n\t\t\tconst unreadFragment = n('nextcloud-vue', '{count} unread message', '{count} unread messages', unread, { count: unread })\n\t\t\tconst acrossFragment = n('nextcloud-vue', 'across {count} conversation', 'across {count} conversations', total, { count: total })\n\t\t\treturn `${unreadFragment} ${acrossFragment}`\n\t\t},\n\n\t\tmostRecent() {\n\t\t\tif (this.rooms.length === 0) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\t// Sort defensive copy by lastActivity desc.\n\t\t\tconst sorted = [...this.rooms].sort((a, b) => {\n\t\t\t\tconst ta = Number(a.lastActivity ?? 0)\n\t\t\t\tconst tb = Number(b.lastActivity ?? 0)\n\t\t\t\treturn tb - ta\n\t\t\t})\n\t\t\treturn sorted[0]\n\t\t},\n\t},\n\n\twatch: {\n\t\tobjectId: { immediate: true, handler() {\n\t\t\tthis.fetch()\n\t\t} },\n\n\t\tsurface() {\n\t\t\tthis.fetch()\n\t\t},\n\n\t\tvalue() {\n\t\t\tif (this.surface === 'single-entity') {\n\t\t\t\tthis.fetchSingle()\n\t\t\t}\n\t\t},\n\t},\n\n\tmethods: {\n\t\tbaseUrl() {\n\t\t\treturn `${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/integrations/${this.integrationId}`\n\t\t},\n\n\t\troomKey(room) {\n\t\t\treturn room.id ?? room.token ?? room.reference ?? ''\n\t\t},\n\n\t\troomTitle(room) {\n\t\t\tconst raw = room.title ?? room.displayName ?? room.name ?? this.roomKey(room)\n\t\t\treturn stripMarker(raw) || String(this.roomKey(room))\n\t\t},\n\n\t\troomUrl(room) {\n\t\t\tif (room.url) {\n\t\t\t\treturn room.url\n\t\t\t}\n\t\t\tconst token = room.token ?? room.id ?? ''\n\t\t\treturn token ? `/index.php/call/${token}` : this.talkAppUrl\n\t\t},\n\n\t\troomPreview(room) {\n\t\t\tconst msg = room.lastMessage\n\t\t\tif (msg && typeof msg === 'object') {\n\t\t\t\treturn msg.message ?? msg.text ?? ''\n\t\t\t}\n\t\t\treturn room.lastMessageText ?? room.preview ?? ''\n\t\t},\n\n\t\thasUnread(room) {\n\t\t\tconst count = Number(room.unreadMessages ?? room.unread ?? 0)\n\t\t\treturn Number.isFinite(count) && count > 0\n\t\t},\n\n\t\tformatUnread(room) {\n\t\t\tconst count = Number(room.unreadMessages ?? room.unread ?? 0)\n\t\t\tif (!Number.isFinite(count) || count <= 0) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn count > 99 ? '99+' : String(count)\n\t\t},\n\n\t\tunreadAriaLabel(room) {\n\t\t\tconst count = Number(room.unreadMessages ?? room.unread ?? 0)\n\t\t\treturn t('nextcloud-vue', '{n} unread', { n: count })\n\t\t},\n\n\t\tchipSubtitle(room) {\n\t\t\tconst preview = this.roomPreview(room)\n\t\t\treturn preview || this.roomTitle(room)\n\t\t},\n\n\t\tchipUrl(room) {\n\t\t\treturn this.roomUrl(room)\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.rooms = data.results || data.items || (Array.isArray(data) ? data : []) || []\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.rooms = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.rooms = []\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('[CnTalkCard] failed to fetch rooms', err)\n\t\t\t\tthis.rooms = []\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('[CnTalkCard] failed to fetch single conversation', 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-talk-card__empty {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n\tpadding: 8px 0;\n}\n\n.cn-talk-card__headline {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n}\n\n.cn-talk-card__headline-line {\n\tfont-size: 1.1em;\n\tcolor: var(--color-main-text);\n}\n\n.cn-talk-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-talk-card__headline-recent a {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\n.cn-talk-card__headline-recent a:hover {\n\ttext-decoration: underline;\n}\n\n.cn-talk-card__headline-preview {\n\tcolor: var(--color-text-maxcontrast);\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-talk-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-talk-card__chip a {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\n.cn-talk-card__chip a:hover {\n\ttext-decoration: underline;\n}\n\n.cn-talk-card__chip-badge {\n\tdisplay: inline-block;\n\tmin-width: 16px;\n\theight: 16px;\n\tpadding: 0 5px;\n\tborder-radius: 8px;\n\tbackground: var(--color-primary-element, #4376FC);\n\tcolor: var(--color-primary-element-text, #ffffff);\n\tfont-size: 0.7em;\n\tfont-weight: bold;\n\tline-height: 16px;\n\ttext-align: center;\n}\n\n.cn-talk-card__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n.cn-talk-card__row {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\tpadding: 6px 0;\n\tborder-bottom: 1px solid var(--color-border);\n}\n\n.cn-talk-card__row:last-child {\n\tborder-bottom: none;\n}\n\n.cn-talk-card__row-icon {\n\tposition: relative;\n\tflex-shrink: 0;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-talk-card__badge {\n\tposition: absolute;\n\ttop: -4px;\n\tright: -8px;\n\tmin-width: 16px;\n\theight: 16px;\n\tpadding: 0 5px;\n\tborder-radius: 8px;\n\tbackground: var(--color-primary-element, #4376FC);\n\tcolor: var(--color-primary-element-text, #ffffff);\n\tfont-size: 0.7em;\n\tfont-weight: bold;\n\tline-height: 16px;\n\ttext-align: center;\n}\n\n.cn-talk-card__row-main {\n\tflex: 1;\n\tmin-width: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n}\n\n.cn-talk-card__title {\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\n.cn-talk-card__row--unread .cn-talk-card__title {\n\tfont-weight: 600;\n}\n\na.cn-talk-card__title:hover {\n\ttext-decoration: underline;\n}\n\n.cn-talk-card__subtitle {\n\tfont-size: 0.8em;\n\tcolor: var(--color-text-maxcontrast);\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n</style>\n"],"names":["_createElementBlock","_Fragment","_createVNode","_createElementVNode","_toDisplayString","_openBlock"],"mappings":"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"}
1
+ {"version":3,"file":"CnTalkCard.vue3.js","sources":["../../../../../src/integrations/builtin/talk/CnTalkCard.vue?vue&type=template&id=5f0c0447&lang.js"],"sourcesContent":["<!--\n CnTalkCard — bespoke surface-aware widget for the `talk` integration.\n\n Replaces the generic CnIntegrationCard for the `talk` leaf. Branches on\n `surface` per AD-19:\n - user-dashboard / app-dashboard : headline \"N unread across M\n conversations\" (per Talk integration-talk spec — unread is the\n single glanceable signal); secondary line names the most recent\n conversation.\n - detail-page : compact list of linked\n conversations with last-message preview + unread badge per row.\n - single-entity : chip with conversation name + an\n unread dot when applicable (referenceType: 'talk').\n\n Pulls rows from the same OR pluggable-integration sub-resource as\n CnTalkTab; for `single-entity` the optional `value` prop addresses a\n single conversation by token (matching CnIntegrationCard's fetchSingle\n contract).\n\n See `openregister/openspec/changes/integration-talk/` for the spec\n delta and ADR-019 (registry mechanism), AD-19 (surface fallback).\n-->\n<template>\n\t<CnDetailCard :title=\"cardTitle\" :icon=\"cardIcon\" :collapsible=\"collapsible\">\n\t\t<NcLoadingIcon v-if=\"loading\" />\n\n\t\t<!-- single-entity surface: chip -->\n\t\t<template v-else-if=\"surface === 'single-entity'\">\n\t\t\t<span v-if=\"entity\" class=\"cn-talk-card__chip\" :title=\"chipSubtitle(entity)\">\n\t\t\t\t<ChatOutline :size=\"14\" />\n\t\t\t\t<a\n\t\t\t\t\t:href=\"chipUrl(entity)\"\n\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\trel=\"noopener\">{{ roomTitle(entity) }}</a>\n\t\t\t\t<span v-if=\"hasUnread(entity)\" class=\"cn-talk-card__chip-badge\" :aria-label=\"unreadAriaLabel(entity)\">\n\t\t\t\t\t{{ formatUnread(entity) }}\n\t\t\t\t</span>\n\t\t\t</span>\n\t\t\t<span v-else class=\"cn-talk-card__empty\">{{ emptyLabel }}</span>\n\t\t</template>\n\n\t\t<!-- dashboard surfaces: headline + most-recent -->\n\t\t<template v-else-if=\"surface === 'user-dashboard' || surface === 'app-dashboard'\">\n\t\t\t<div v-if=\"degraded\" class=\"cn-talk-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"rooms.length === 0\" class=\"cn-talk-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<div v-else class=\"cn-talk-card__headline\">\n\t\t\t\t<div class=\"cn-talk-card__headline-line\">\n\t\t\t\t\t<strong>{{ unreadHeadline }}</strong>\n\t\t\t\t</div>\n\t\t\t\t<div v-if=\"mostRecent\" class=\"cn-talk-card__headline-recent\">\n\t\t\t\t\t<ChatOutline :size=\"14\" />\n\t\t\t\t\t<a\n\t\t\t\t\t\t:href=\"roomUrl(mostRecent)\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener\">{{ roomTitle(mostRecent) }}</a>\n\t\t\t\t\t<span v-if=\"roomPreview(mostRecent)\" class=\"cn-talk-card__headline-preview\">\n\t\t\t\t\t\t· {{ roomPreview(mostRecent) }}\n\t\t\t\t\t</span>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</template>\n\n\t\t<!-- detail-page surface: compact list -->\n\t\t<template v-else>\n\t\t\t<div v-if=\"degraded\" class=\"cn-talk-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"rooms.length === 0\" class=\"cn-talk-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<ul v-else class=\"cn-talk-card__list\">\n\t\t\t\t<li\n\t\t\t\t\tv-for=\"room in displayedRooms\"\n\t\t\t\t\t:key=\"roomKey(room)\"\n\t\t\t\t\tclass=\"cn-talk-card__row\"\n\t\t\t\t\t:class=\"{ 'cn-talk-card__row--unread': hasUnread(room) }\">\n\t\t\t\t\t<div class=\"cn-talk-card__row-icon\">\n\t\t\t\t\t\t<NcAvatar\n\t\t\t\t\t\t\t:displayName=\"roomTitle(room)\"\n\t\t\t\t\t\t\t:size=\"32\"\n\t\t\t\t\t\t\t:isNoUser=\"true\"\n\t\t\t\t\t\t\thideStatus />\n\t\t\t\t\t\t<NcCounterBubble\n\t\t\t\t\t\t\tv-if=\"hasUnread(room)\"\n\t\t\t\t\t\t\tclass=\"cn-talk-card__badge\"\n\t\t\t\t\t\t\ttype=\"highlighted\"\n\t\t\t\t\t\t\t:aria-label=\"unreadAriaLabel(room)\">\n\t\t\t\t\t\t\t{{ formatUnread(room) }}\n\t\t\t\t\t\t</NcCounterBubble>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div class=\"cn-talk-card__row-main\">\n\t\t\t\t\t\t<a\n\t\t\t\t\t\t\t:href=\"roomUrl(room)\"\n\t\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t\trel=\"noopener\"\n\t\t\t\t\t\t\tclass=\"cn-talk-card__title\">{{ roomTitle(room) }}</a>\n\t\t\t\t\t\t<span v-if=\"roomPreview(room)\" class=\"cn-talk-card__subtitle\">{{ roomPreview(room) }}</span>\n\t\t\t\t\t</div>\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 { NcAvatar, NcCounterBubble, NcLoadingIcon } from '@nextcloud/vue'\nimport ChatOutline from 'vue-material-design-icons/ChatOutline.vue'\nimport CnDetailCard from '../../../components/CnDetailCard/CnDetailCard.vue'\nimport { buildHeaders, prefixUrl, safeHref } from '../../../utils/index.js'\nimport { stripMarker } from '../../utils/marker.js'\n\nconst VALID_SURFACES = ['user-dashboard', 'app-dashboard', 'detail-page', 'single-entity']\nconst COMPACT_LIMIT = 5\n\n/**\n * CnTalkCard — bespoke surface-aware widget for the `talk` integration.\n *\n * Renders Talk-aware metadata across all four surfaces. See the\n * file-level docblock for surface-by-surface behaviour.\n */\nexport default {\n\tname: 'CnTalkCard',\n\n\tcomponents: { CnDetailCard, NcAvatar, NcCounterBubble, NcLoadingIcon, ChatOutline },\n\n\tprops: {\n\t\t/** Stable integration id (forwarded from the registry — always `'talk'`). */\n\t\tintegrationId: { type: String, default: 'talk' },\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 (room token). */\n\t\tvalue: { type: String, default: '' },\n\t\t/** Pre-translated card title. */\n\t\ttitle: { type: String, default: () => t('nextcloud-vue', 'Chat') },\n\t\t/** Optional Material Design Icon component. */\n\t\ticon: { type: Object, default: () => ChatOutline },\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 conversations linked yet') },\n\t\t/** Pre-translated unavailable label. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Talk is currently unavailable.') },\n\t\t/** URL of the NC Talk app entry. */\n\t\ttalkAppUrl: { type: String, default: '/index.php/apps/spreed' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\trooms: [],\n\t\t\tentity: null,\n\t\t\tloading: false,\n\t\t\tdegraded: '',\n\t\t}\n\t},\n\n\tcomputed: {\n\t\tcardTitle() {\n\t\t\treturn this.title || this.integrationId\n\t\t},\n\n\t\tcardIcon() {\n\t\t\treturn this.icon\n\t\t},\n\n\t\tdisplayedRooms() {\n\t\t\tif (this.surface === 'user-dashboard' || this.surface === 'app-dashboard') {\n\t\t\t\treturn this.rooms.slice(0, COMPACT_LIMIT)\n\t\t\t}\n\t\t\treturn this.rooms\n\t\t},\n\n\t\ttotalUnread() {\n\t\t\treturn this.rooms.reduce((sum, room) => {\n\t\t\t\tconst count = Number(room.unreadMessages ?? room.unread ?? 0)\n\t\t\t\treturn sum + (Number.isFinite(count) && count > 0 ? count : 0)\n\t\t\t}, 0)\n\t\t},\n\n\t\tunreadHeadline() {\n\t\t\tconst unread = this.totalUnread\n\t\t\tconst total = this.rooms.length\n\t\t\tif (unread === 0) {\n\t\t\t\t// \"M conversations\" via translatePlural.\n\t\t\t\treturn n('nextcloud-vue', '{count} conversation', '{count} conversations', total, { count: total })\n\t\t\t}\n\t\t\t// Pre-format the two plural fragments, then compose so we don't\n\t\t\t// trip the @nextcloud/l10n single-placeholder constraint.\n\t\t\tconst unreadFragment = n('nextcloud-vue', '{count} unread message', '{count} unread messages', unread, { count: unread })\n\t\t\tconst acrossFragment = n('nextcloud-vue', 'across {count} conversation', 'across {count} conversations', total, { count: total })\n\t\t\treturn `${unreadFragment} ${acrossFragment}`\n\t\t},\n\n\t\tmostRecent() {\n\t\t\tif (this.rooms.length === 0) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\t// Sort defensive copy by lastActivity desc.\n\t\t\tconst sorted = [...this.rooms].sort((a, b) => {\n\t\t\t\tconst ta = Number(a.lastActivity ?? 0)\n\t\t\t\tconst tb = Number(b.lastActivity ?? 0)\n\t\t\t\treturn tb - ta\n\t\t\t})\n\t\t\treturn sorted[0]\n\t\t},\n\t},\n\n\twatch: {\n\t\tobjectId: { immediate: true, handler() {\n\t\t\tthis.fetch()\n\t\t} },\n\n\t\tsurface() {\n\t\t\tthis.fetch()\n\t\t},\n\n\t\tvalue() {\n\t\t\tif (this.surface === 'single-entity') {\n\t\t\t\tthis.fetchSingle()\n\t\t\t}\n\t\t},\n\t},\n\n\tmethods: {\n\t\tbaseUrl() {\n\t\t\treturn `${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/integrations/${this.integrationId}`\n\t\t},\n\n\t\troomKey(room) {\n\t\t\treturn room.id ?? room.token ?? room.reference ?? ''\n\t\t},\n\n\t\troomTitle(room) {\n\t\t\tconst raw = room.title ?? room.displayName ?? room.name ?? this.roomKey(room)\n\t\t\treturn stripMarker(raw) || String(this.roomKey(room))\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\troomUrl(room) {\n\t\t\tif (room.url && safeHref(room.url) !== '#') {\n\t\t\t\treturn room.url\n\t\t\t}\n\t\t\tconst token = room.token ?? room.id ?? ''\n\t\t\treturn token ? `/index.php/call/${token}` : this.talkAppUrl\n\t\t},\n\n\t\troomPreview(room) {\n\t\t\tconst msg = room.lastMessage\n\t\t\tif (msg && typeof msg === 'object') {\n\t\t\t\treturn msg.message ?? msg.text ?? ''\n\t\t\t}\n\t\t\treturn room.lastMessageText ?? room.preview ?? ''\n\t\t},\n\n\t\thasUnread(room) {\n\t\t\tconst count = Number(room.unreadMessages ?? room.unread ?? 0)\n\t\t\treturn Number.isFinite(count) && count > 0\n\t\t},\n\n\t\tformatUnread(room) {\n\t\t\tconst count = Number(room.unreadMessages ?? room.unread ?? 0)\n\t\t\tif (!Number.isFinite(count) || count <= 0) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn count > 99 ? '99+' : String(count)\n\t\t},\n\n\t\tunreadAriaLabel(room) {\n\t\t\tconst count = Number(room.unreadMessages ?? room.unread ?? 0)\n\t\t\treturn t('nextcloud-vue', '{n} unread', { n: count })\n\t\t},\n\n\t\tchipSubtitle(room) {\n\t\t\tconst preview = this.roomPreview(room)\n\t\t\treturn preview || this.roomTitle(room)\n\t\t},\n\n\t\tchipUrl(room) {\n\t\t\treturn this.roomUrl(room)\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.rooms = data.results || data.items || (Array.isArray(data) ? data : []) || []\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.rooms = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.rooms = []\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('[CnTalkCard] failed to fetch rooms', err)\n\t\t\t\tthis.rooms = []\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('[CnTalkCard] failed to fetch single conversation', 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-talk-card__empty {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n\tpadding: 8px 0;\n}\n\n.cn-talk-card__headline {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n}\n\n.cn-talk-card__headline-line {\n\tfont-size: 1.1em;\n\tcolor: var(--color-main-text);\n}\n\n.cn-talk-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-talk-card__headline-recent a {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\n.cn-talk-card__headline-recent a:hover {\n\ttext-decoration: underline;\n}\n\n.cn-talk-card__headline-preview {\n\tcolor: var(--color-text-maxcontrast);\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-talk-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-talk-card__chip a {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\n.cn-talk-card__chip a:hover {\n\ttext-decoration: underline;\n}\n\n.cn-talk-card__chip-badge {\n\tdisplay: inline-block;\n\tmin-width: 16px;\n\theight: 16px;\n\tpadding: 0 5px;\n\tborder-radius: 8px;\n\tbackground: var(--color-primary-element, #4376FC);\n\tcolor: var(--color-primary-element-text, #ffffff);\n\tfont-size: 0.7em;\n\tfont-weight: bold;\n\tline-height: 16px;\n\ttext-align: center;\n}\n\n.cn-talk-card__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n.cn-talk-card__row {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\tpadding: 6px 0;\n\tborder-bottom: 1px solid var(--color-border);\n}\n\n.cn-talk-card__row:last-child {\n\tborder-bottom: none;\n}\n\n.cn-talk-card__row-icon {\n\tposition: relative;\n\tflex-shrink: 0;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-talk-card__badge {\n\tposition: absolute;\n\ttop: -4px;\n\tright: -8px;\n\tmin-width: 16px;\n\theight: 16px;\n\tpadding: 0 5px;\n\tborder-radius: 8px;\n\tbackground: var(--color-primary-element, #4376FC);\n\tcolor: var(--color-primary-element-text, #ffffff);\n\tfont-size: 0.7em;\n\tfont-weight: bold;\n\tline-height: 16px;\n\ttext-align: center;\n}\n\n.cn-talk-card__row-main {\n\tflex: 1;\n\tmin-width: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n}\n\n.cn-talk-card__title {\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\n.cn-talk-card__row--unread .cn-talk-card__title {\n\tfont-weight: 600;\n}\n\na.cn-talk-card__title:hover {\n\ttext-decoration: underline;\n}\n\n.cn-talk-card__subtitle {\n\tfont-size: 0.8em;\n\tcolor: var(--color-text-maxcontrast);\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n</style>\n"],"names":["_createElementBlock","_Fragment","_createVNode","_createElementVNode","_toDisplayString","_openBlock"],"mappings":"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"}
@@ -9,6 +9,7 @@ import Plus from 'vue-material-design-icons/Plus.vue';
9
9
  import __sfc_main$1 from '../../../components/CnTalkRoomCreate/CnTalkRoomCreate.vue.js';
10
10
  import __sfc_main from '../../../components/CnTalkRoomPicker/CnTalkRoomPicker.vue.js';
11
11
  import { stripMarker } from '../../utils/marker.js';
12
+ import { safeHref } from '../../../utils/safeHref.js';
12
13
  import { prefixUrl, buildHeaders } from '../../../utils/headers.js';
13
14
 
14
15
  /**
@@ -106,8 +107,9 @@ var script = {
106
107
  return stripMarker(raw) || String(this.roomKey(room))
107
108
  },
108
109
 
110
+ // A provider URL is used only when it is a safe scheme; else the app link.
109
111
  roomUrl(room) {
110
- if (room.url) {
112
+ if (room.url && safeHref(room.url) !== '#') {
111
113
  return room.url
112
114
  }
113
115
  const token = room.roomToken ?? room.token ?? room.id ?? '';
@@ -1 +1 @@
1
- {"version":3,"file":"CnTalkTab.vue2.js","sources":["../../../../../src/integrations/builtin/talk/CnTalkTab.vue"],"sourcesContent":["<!--\n CnTalkTab — bespoke sidebar tab for the `talk` integration.\n\n Replaces the generic CnIntegrationTab for the `talk` leaf: mirrors the\n real NC Talk conversation list — each row is an NcListItem with the\n conversation avatar (NcAvatar keyed on the room name), a bold\n conversation name, a last-message preview as the subname, a relative\n timestamp (NcDateTime) in the details slot, and an unread-count bubble\n (NcCounterBubble) when the room has unread messages. Per-row unlink and\n \"Open in Talk\" live in the row action menu. Tier-2: adds explicit\n \"Link existing room\" + \"Create new room\" actions powered by\n CnTalkRoomPicker / CnTalkRoomCreate; unlink does NOT destroy the\n underlying Talk room.\n\n Talks to the OpenRegister Tier-2 endpoint\n GET /api/objects/{register}/{schema}/{objectId}/talk\n POST /api/objects/{register}/{schema}/{objectId}/talk — link\n POST /api/objects/{register}/{schema}/{objectId}/talk/new — create\n DELETE /api/objects/{register}/{schema}/{objectId}/talk/{roomToken} — unlink\n served by OCA\\OpenRegister\\Controller\\TalkLinksController.\n\n Surface behaviour:\n - Empty state with \"Open Talk\" + Link/Create CTAs when no linked rooms.\n - Loading + 503 \"currently unavailable\" + generic error states match\n CnIntegrationTab's behaviour for AD-23 graceful degradation.\n\n See `openregister/openspec/changes/integration-talk/` for the spec\n delta and ADR-019 (registry mechanism).\n-->\n<template>\n\t<div class=\"cn-sidebar-tab cn-talk-tab\">\n\t\t<div v-if=\"degraded\" class=\"cn-talk-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<!-- Tier-2 action bar — always visible when not in error/loading -->\n\t\t<div v-if=\"!loading && !error\" class=\"cn-talk-tab__actions\">\n\t\t\t<NcButton @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 room') }}\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 room') }}\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-talk-tab__error\" role=\"alert\">\n\t\t\t{{ error }}\n\t\t</div>\n\t\t<div v-else-if=\"rooms.length === 0\" class=\"cn-sidebar-tab__empty cn-talk-tab__empty\">\n\t\t\t<ChatOutline :size=\"32\" class=\"cn-talk-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=\"talkAppUrl\"\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<ChatOutline :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ openTalkLabel }}\n\t\t\t</NcButton>\n\t\t</div>\n\t\t<ul v-else class=\"cn-talk-tab__list\">\n\t\t\t<NcListItem\n\t\t\t\tv-for=\"room in rooms\"\n\t\t\t\t:key=\"roomKey(room)\"\n\t\t\t\tclass=\"cn-talk-tab__row\"\n\t\t\t\t:class=\"{ 'cn-talk-tab__row--unread': hasUnread(room) }\"\n\t\t\t\t:name=\"roomTitle(room)\"\n\t\t\t\t:bold=\"hasUnread(room)\"\n\t\t\t\t:href=\"roomUrl(room)\"\n\t\t\t\ttarget=\"_blank\"\n\t\t\t\t:forceDisplayActions=\"true\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<NcAvatar\n\t\t\t\t\t\t:displayName=\"roomTitle(room)\"\n\t\t\t\t\t\t:size=\"40\"\n\t\t\t\t\t\t:isNoUser=\"true\"\n\t\t\t\t\t\thideStatus />\n\t\t\t\t</template>\n\t\t\t\t<template #subname>\n\t\t\t\t\t<span class=\"cn-talk-tab__preview\">{{ roomSubname(room) }}</span>\n\t\t\t\t</template>\n\t\t\t\t<template v-if=\"roomTimestamp(room)\" #details>\n\t\t\t\t\t<NcDateTime\n\t\t\t\t\t\tclass=\"cn-talk-tab__time\"\n\t\t\t\t\t\t:timestamp=\"roomTimestamp(room)\"\n\t\t\t\t\t\trelativeTime=\"short\" />\n\t\t\t\t</template>\n\t\t\t\t<template v-if=\"hasUnread(room)\" #indicator>\n\t\t\t\t\t<NcCounterBubble\n\t\t\t\t\t\tclass=\"cn-talk-tab__badge\"\n\t\t\t\t\t\ttype=\"highlighted\"\n\t\t\t\t\t\t:aria-label=\"unreadAriaLabel(room)\">\n\t\t\t\t\t\t{{ formatUnread(room) }}\n\t\t\t\t\t</NcCounterBubble>\n\t\t\t\t</template>\n\t\t\t\t<template #actions>\n\t\t\t\t\t<NcActionLink\n\t\t\t\t\t\t:closeAfterClick=\"true\"\n\t\t\t\t\t\t:href=\"roomUrl(room)\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener noreferrer\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<OpenInNew :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ t('nextcloud-vue', 'Open in Talk') }}\n\t\t\t\t\t</NcActionLink>\n\t\t\t\t\t<NcActionButton :closeAfterClick=\"true\" @click=\"unlinkRoom(room)\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<Close :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ t('nextcloud-vue', 'Unlink from object (the room stays in Talk)') }}\n\t\t\t\t\t</NcActionButton>\n\t\t\t\t</template>\n\t\t\t</NcListItem>\n\t\t</ul>\n\n\t\t<!-- Picker + Create modals (mounted lazily) -->\n\t\t<CnTalkRoomPicker\n\t\t\tv-if=\"pickerOpen\"\n\t\t\t:apiBase=\"apiBase\"\n\t\t\t@close=\"pickerOpen = false\"\n\t\t\t@link=\"onPickerLink\" />\n\n\t\t<CnTalkRoomCreate\n\t\t\tv-if=\"createOpen\"\n\t\t\t@close=\"createOpen = false\"\n\t\t\t@create=\"onCreateSubmit\" />\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcActionButton, NcActionLink, NcAvatar, NcButton, NcCounterBubble, NcDateTime, NcListItem, NcLoadingIcon } from '@nextcloud/vue'\nimport AlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue'\nimport ChatOutline from 'vue-material-design-icons/ChatOutline.vue'\nimport Close from 'vue-material-design-icons/Close.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 CnTalkRoomCreate from '../../../components/CnTalkRoomCreate/CnTalkRoomCreate.vue'\nimport CnTalkRoomPicker from '../../../components/CnTalkRoomPicker/CnTalkRoomPicker.vue'\nimport { buildHeaders, prefixUrl } from '../../../utils/index.js'\nimport { stripMarker } from '../../utils/marker.js'\n\n/**\n * CnTalkTab — bespoke conversation list for the `talk` integration.\n *\n * Renders rows pulled from the OR Tier-2 link table as NcListItem rows\n * styled to mirror NC Talk: conversation avatar, bold name,\n * last-message preview, relative timestamp, and an unread counter\n * bubble. Tier-2: supports link / create / unlink via picker + create\n * dialogs.\n */\nexport default {\n\tname: 'CnTalkTab',\n\n\tcomponents: {\n\t\tNcActionButton,\n\t\tNcActionLink,\n\t\tNcAvatar,\n\t\tNcButton,\n\t\tNcCounterBubble,\n\t\tNcDateTime,\n\t\tNcListItem,\n\t\tNcLoadingIcon,\n\t\tAlertCircleOutline,\n\t\tChatOutline,\n\t\tClose,\n\t\tLinkVariant,\n\t\tOpenInNew,\n\t\tPlus,\n\t\tCnTalkRoomPicker,\n\t\tCnTalkRoomCreate,\n\t},\n\n\tprops: {\n\t\t/** Stable integration id (matches the PHP-side provider id, always `talk`). */\n\t\tintegrationId: { type: String, default: 'talk' },\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 for OpenRegister. */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\t\t/** Empty state shown when no conversation is linked yet. */\n\t\temptyLabel: { type: String, default: () => t('nextcloud-vue', 'No conversations linked yet') },\n\t\t/** Label for the link that opens the conversation in Talk. */\n\t\topenTalkLabel: { type: String, default: () => t('nextcloud-vue', 'Open Talk') },\n\t\t/** Message shown when the Talk app cannot be reached. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Talk is currently unavailable.') },\n\t\t/** Base URL of the Talk app, used to build a conversation link. */\n\t\ttalkAppUrl: { type: String, default: '/index.php/apps/spreed' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\trooms: [],\n\t\t\tloading: false,\n\t\t\terror: '',\n\t\t\tdegraded: '',\n\t\t\tpickerOpen: false,\n\t\t\tcreateOpen: false,\n\t\t}\n\t},\n\n\twatch: {\n\t\tobjectId: { immediate: true, handler(id) {\n\t\t\tif (id) {\n\t\t\t\tthis.fetchRooms()\n\t\t\t}\n\t\t} },\n\n\t\tregister() {\n\t\t\tthis.fetchRooms()\n\t\t},\n\n\t\tschema() {\n\t\t\tthis.fetchRooms()\n\t\t},\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\tbaseUrl() {\n\t\t\treturn `${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/${this.integrationId}`\n\t\t},\n\n\t\troomKey(room) {\n\t\t\treturn room.roomToken ?? room.id ?? room.token ?? room.reference ?? ''\n\t\t},\n\n\t\troomTitle(room) {\n\t\t\tconst raw = room.roomName ?? room.title ?? room.displayName ?? room.name ?? this.roomKey(room)\n\t\t\treturn stripMarker(raw) || String(this.roomKey(room))\n\t\t},\n\n\t\troomUrl(room) {\n\t\t\tif (room.url) {\n\t\t\t\treturn room.url\n\t\t\t}\n\t\t\tconst token = room.roomToken ?? room.token ?? room.id ?? ''\n\t\t\treturn token ? `/index.php/call/${token}` : this.talkAppUrl\n\t\t},\n\n\t\thasUnread(room) {\n\t\t\tconst count = Number(room.unreadMessages ?? room.unread ?? 0)\n\t\t\treturn Number.isFinite(count) && count > 0\n\t\t},\n\n\t\tformatUnread(room) {\n\t\t\tconst count = Number(room.unreadMessages ?? room.unread ?? 0)\n\t\t\tif (!Number.isFinite(count) || count <= 0) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn count > 99 ? '99+' : String(count)\n\t\t},\n\n\t\tunreadAriaLabel(room) {\n\t\t\tconst count = Number(room.unreadMessages ?? room.unread ?? 0)\n\t\t\treturn t('nextcloud-vue', '{n} unread', { n: count })\n\t\t},\n\n\t\t/**\n\t\t * Last-message preview line. Falls back to a participant-count\n\t\t * summary so the subname is never empty in NC-Talk style.\n\t\t *\n\t\t * @param {object} room Conversation row.\n\t\t * @return {string} Preview or participant summary.\n\t\t */\n\t\troomSubname(room) {\n\t\t\tconst preview = this.roomPreview(room)\n\t\t\tif (preview) {\n\t\t\t\treturn preview\n\t\t\t}\n\t\t\tconst participants = Number(room.participantCount ?? room.numParticipants ?? 0)\n\t\t\tif (Number.isFinite(participants) && participants > 0) {\n\t\t\t\treturn t('nextcloud-vue', '{n} participants', { n: participants })\n\t\t\t}\n\t\t\treturn ''\n\t\t},\n\n\t\troomPreview(room) {\n\t\t\tlet msg = room.lastMessage\n\t\t\t// The provider may hand us the message as a JSON string; parse it\n\t\t\t// so we never render a raw `{\"message\":...}` blob in the preview.\n\t\t\tif (typeof msg === 'string') {\n\t\t\t\tconst trimmed = msg.trim()\n\t\t\t\tif (trimmed.charAt(0) === '{' || trimmed.charAt(0) === '[') {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tmsg = JSON.parse(trimmed)\n\t\t\t\t\t} catch {\n\t\t\t\t\t\treturn this.cleanPreview(trimmed)\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\treturn this.cleanPreview(msg)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (msg && typeof msg === 'object') {\n\t\t\t\t// Talk system messages carry a snake_case key (e.g. `user_added`)\n\t\t\t\t// in `systemMessage`/`message` plus rich `parameters`. Humanise\n\t\t\t\t// them instead of dumping the placeholder/JSON.\n\t\t\t\tconst isSystem = (typeof msg.systemMessage === 'string' && msg.systemMessage !== '')\n\t\t\t\t\t|| msg.messageType === 'system'\n\t\t\t\tconst raw = msg.message ?? msg.text ?? ''\n\t\t\t\tif (isSystem) {\n\t\t\t\t\treturn this.humaniseSystemMessage(msg)\n\t\t\t\t}\n\t\t\t\treturn this.cleanPreview(typeof raw === 'string' ? raw : '')\n\t\t\t}\n\t\t\treturn this.cleanPreview(room.lastMessageText ?? room.preview ?? '')\n\t\t},\n\n\t\t/**\n\t\t * Turn a Talk system message into readable text. Substitutes rich\n\t\t * `{placeholder}` parameters with their display name; falls back to a\n\t\t * title-cased version of the snake_case key (e.g. `user_added` →\n\t\t * \"User added\").\n\t\t *\n\t\t * @param {object} msg The last-message object.\n\t\t * @return {string} Human-readable line.\n\t\t */\n\t\thumaniseSystemMessage(msg) {\n\t\t\tconst key = (typeof msg.systemMessage === 'string' && msg.systemMessage !== '')\n\t\t\t\t? msg.systemMessage\n\t\t\t\t: (typeof msg.message === 'string' ? msg.message : '')\n\t\t\tconst template = (typeof msg.message === 'string' && msg.message.indexOf('{') !== -1)\n\t\t\t\t? msg.message\n\t\t\t\t: ''\n\t\t\tconst params = msg.messageParameters || msg.parameters || {}\n\t\t\tif (template !== '' && params && typeof params === 'object') {\n\t\t\t\treturn template.replace(/\\{(\\w+)\\}/g, (whole, name) => {\n\t\t\t\t\tconst p = params[name]\n\t\t\t\t\tif (p && typeof p === 'object') {\n\t\t\t\t\t\treturn p.name || p.displayName || p.id || name\n\t\t\t\t\t}\n\t\t\t\t\treturn (p === undefined || p === null) ? name : String(p)\n\t\t\t\t})\n\t\t\t}\n\t\t\t// Bare key like `user_added` / `conversation_created`.\n\t\t\tif (key !== '') {\n\t\t\t\tconst words = key.replace(/[_-]+/g, ' ').trim()\n\t\t\t\treturn words.charAt(0).toUpperCase() + words.slice(1)\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', 'System message')\n\t\t},\n\n\t\t/**\n\t\t * Guard against a raw JSON blob ever reaching the preview line.\n\t\t *\n\t\t * @param {string} text Candidate preview text.\n\t\t * @return {string} The text, or '' when it still looks like JSON.\n\t\t */\n\t\tcleanPreview(text) {\n\t\t\tconst s = String(text || '').trim()\n\t\t\tif (s.charAt(0) === '{' || s.charAt(0) === '[') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn s\n\t\t},\n\n\t\t/**\n\t\t * Resolve the room's last-activity moment as an epoch-millisecond\n\t\t * value for NcDateTime. Accepts unix seconds, millisecond numbers,\n\t\t * or an ISO-8601 string; returns 0 when unparseable (no timestamp).\n\t\t *\n\t\t * @param {object} room Conversation row.\n\t\t * @return {number} Epoch milliseconds, or 0 when unknown.\n\t\t */\n\t\troomTimestamp(room) {\n\t\t\tconst raw = room.lastActivity ?? room.lastActivityAt ?? null\n\t\t\tif (raw === null || raw === undefined || raw === '') {\n\t\t\t\treturn 0\n\t\t\t}\n\t\t\tif (typeof raw === 'number') {\n\t\t\t\t// Heuristic: treat 10-digit values as unix seconds.\n\t\t\t\treturn raw < 1e12 ? raw * 1000 : raw\n\t\t\t}\n\t\t\tconst parsed = new Date(raw).getTime()\n\t\t\treturn Number.isNaN(parsed) ? 0 : parsed\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 onPickerLink(payload) {\n\t\t\tthis.pickerOpen = false\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(this.baseUrl()), {\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({ roomToken: payload.roomToken }),\n\t\t\t\t})\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tawait this.fetchRooms()\n\t\t\t\t} else if (response.status === 409) {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'This room is already linked.')\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not link the room.')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console -- the failure is already handled; the console is the only channel a host app can read the detail on\n\t\t\t\tconsole.error('[CnTalkTab] link room failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not link the room.')\n\t\t\t}\n\t\t},\n\n\t\tasync onCreateSubmit(payload) {\n\t\t\tthis.createOpen = false\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(`${this.baseUrl()}/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.fetchRooms()\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not create the room.')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console -- the failure is already handled; the console is the only channel a host app can read the detail on\n\t\t\t\tconsole.error('[CnTalkTab] create room failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not create the room.')\n\t\t\t}\n\t\t},\n\n\t\tasync unlinkRoom(room) {\n\t\t\tconst token = room.roomToken ?? room.token ?? room.id\n\t\t\tif (!token) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(`${this.baseUrl()}/${encodeURIComponent(token)}`), {\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.fetchRooms()\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not unlink the room.')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console -- the failure is already handled; the console is the only channel a host app can read the detail on\n\t\t\t\tconsole.error('[CnTalkTab] unlink room failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not unlink the room.')\n\t\t\t}\n\t\t},\n\n\t\tasync fetchRooms() {\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.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.rooms = rows\n\t\t\t\t} else if (response.status === 503 || response.status === 501) {\n\t\t\t\t\tthis.rooms = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.rooms = []\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load conversations.')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console -- the failure is already handled; the console is the only channel a host app can read the detail on\n\t\t\t\tconsole.error('[CnTalkTab] failed to fetch rooms', err)\n\t\t\t\tthis.rooms = []\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load conversations.')\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-talk-tab {\n\tpadding: 8px 0;\n}\n\n.cn-talk-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-talk-tab__actions {\n\tdisplay: flex;\n\tgap: 8px;\n\tmargin-bottom: 10px;\n\tflex-wrap: wrap;\n}\n\n.cn-talk-tab__error {\n\tcolor: var(--color-error);\n\tfont-size: 0.9em;\n\tmargin: 4px 0 8px;\n}\n\n.cn-talk-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-talk-tab__empty-icon {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-talk-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/* Last-message preview — single line, muted, like NC Talk. */\n.cn-talk-tab__preview {\n\tdisplay: block;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-talk-tab__time {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.8em;\n\twhite-space: nowrap;\n}\n</style>\n"],"names":["CnTalkRoomPicker","CnTalkRoomCreate","t"],"mappings":"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"}
1
+ {"version":3,"file":"CnTalkTab.vue2.js","sources":["../../../../../src/integrations/builtin/talk/CnTalkTab.vue"],"sourcesContent":["<!--\n CnTalkTab — bespoke sidebar tab for the `talk` integration.\n\n Replaces the generic CnIntegrationTab for the `talk` leaf: mirrors the\n real NC Talk conversation list — each row is an NcListItem with the\n conversation avatar (NcAvatar keyed on the room name), a bold\n conversation name, a last-message preview as the subname, a relative\n timestamp (NcDateTime) in the details slot, and an unread-count bubble\n (NcCounterBubble) when the room has unread messages. Per-row unlink and\n \"Open in Talk\" live in the row action menu. Tier-2: adds explicit\n \"Link existing room\" + \"Create new room\" actions powered by\n CnTalkRoomPicker / CnTalkRoomCreate; unlink does NOT destroy the\n underlying Talk room.\n\n Talks to the OpenRegister Tier-2 endpoint\n GET /api/objects/{register}/{schema}/{objectId}/talk\n POST /api/objects/{register}/{schema}/{objectId}/talk — link\n POST /api/objects/{register}/{schema}/{objectId}/talk/new — create\n DELETE /api/objects/{register}/{schema}/{objectId}/talk/{roomToken} — unlink\n served by OCA\\OpenRegister\\Controller\\TalkLinksController.\n\n Surface behaviour:\n - Empty state with \"Open Talk\" + Link/Create CTAs when no linked rooms.\n - Loading + 503 \"currently unavailable\" + generic error states match\n CnIntegrationTab's behaviour for AD-23 graceful degradation.\n\n See `openregister/openspec/changes/integration-talk/` for the spec\n delta and ADR-019 (registry mechanism).\n-->\n<template>\n\t<div class=\"cn-sidebar-tab cn-talk-tab\">\n\t\t<div v-if=\"degraded\" class=\"cn-talk-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<!-- Tier-2 action bar — always visible when not in error/loading -->\n\t\t<div v-if=\"!loading && !error\" class=\"cn-talk-tab__actions\">\n\t\t\t<NcButton @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 room') }}\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 room') }}\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-talk-tab__error\" role=\"alert\">\n\t\t\t{{ error }}\n\t\t</div>\n\t\t<div v-else-if=\"rooms.length === 0\" class=\"cn-sidebar-tab__empty cn-talk-tab__empty\">\n\t\t\t<ChatOutline :size=\"32\" class=\"cn-talk-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=\"talkAppUrl\"\n\t\t\t\ttarget=\"_blank\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<ChatOutline :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ openTalkLabel }}\n\t\t\t</NcButton>\n\t\t</div>\n\t\t<ul v-else class=\"cn-talk-tab__list\">\n\t\t\t<NcListItem\n\t\t\t\tv-for=\"room in rooms\"\n\t\t\t\t:key=\"roomKey(room)\"\n\t\t\t\tclass=\"cn-talk-tab__row\"\n\t\t\t\t:class=\"{ 'cn-talk-tab__row--unread': hasUnread(room) }\"\n\t\t\t\t:name=\"roomTitle(room)\"\n\t\t\t\t:bold=\"hasUnread(room)\"\n\t\t\t\t:href=\"roomUrl(room)\"\n\t\t\t\ttarget=\"_blank\"\n\t\t\t\t:forceDisplayActions=\"true\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<NcAvatar\n\t\t\t\t\t\t:displayName=\"roomTitle(room)\"\n\t\t\t\t\t\t:size=\"40\"\n\t\t\t\t\t\t:isNoUser=\"true\"\n\t\t\t\t\t\thideStatus />\n\t\t\t\t</template>\n\t\t\t\t<template #subname>\n\t\t\t\t\t<span class=\"cn-talk-tab__preview\">{{ roomSubname(room) }}</span>\n\t\t\t\t</template>\n\t\t\t\t<template v-if=\"roomTimestamp(room)\" #details>\n\t\t\t\t\t<NcDateTime\n\t\t\t\t\t\tclass=\"cn-talk-tab__time\"\n\t\t\t\t\t\t:timestamp=\"roomTimestamp(room)\"\n\t\t\t\t\t\trelativeTime=\"short\" />\n\t\t\t\t</template>\n\t\t\t\t<template v-if=\"hasUnread(room)\" #indicator>\n\t\t\t\t\t<NcCounterBubble\n\t\t\t\t\t\tclass=\"cn-talk-tab__badge\"\n\t\t\t\t\t\ttype=\"highlighted\"\n\t\t\t\t\t\t:aria-label=\"unreadAriaLabel(room)\">\n\t\t\t\t\t\t{{ formatUnread(room) }}\n\t\t\t\t\t</NcCounterBubble>\n\t\t\t\t</template>\n\t\t\t\t<template #actions>\n\t\t\t\t\t<NcActionLink\n\t\t\t\t\t\t:closeAfterClick=\"true\"\n\t\t\t\t\t\t:href=\"roomUrl(room)\"\n\t\t\t\t\t\ttarget=\"_blank\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<OpenInNew :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ t('nextcloud-vue', 'Open in Talk') }}\n\t\t\t\t\t</NcActionLink>\n\t\t\t\t\t<NcActionButton :closeAfterClick=\"true\" @click=\"unlinkRoom(room)\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<Close :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ t('nextcloud-vue', 'Unlink from object (the room stays in Talk)') }}\n\t\t\t\t\t</NcActionButton>\n\t\t\t\t</template>\n\t\t\t</NcListItem>\n\t\t</ul>\n\n\t\t<!-- Picker + Create modals (mounted lazily) -->\n\t\t<CnTalkRoomPicker\n\t\t\tv-if=\"pickerOpen\"\n\t\t\t:apiBase=\"apiBase\"\n\t\t\t@close=\"pickerOpen = false\"\n\t\t\t@link=\"onPickerLink\" />\n\n\t\t<CnTalkRoomCreate\n\t\t\tv-if=\"createOpen\"\n\t\t\t@close=\"createOpen = false\"\n\t\t\t@create=\"onCreateSubmit\" />\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcActionButton, NcActionLink, NcAvatar, NcButton, NcCounterBubble, NcDateTime, NcListItem, NcLoadingIcon } from '@nextcloud/vue'\nimport AlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue'\nimport ChatOutline from 'vue-material-design-icons/ChatOutline.vue'\nimport Close from 'vue-material-design-icons/Close.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 CnTalkRoomCreate from '../../../components/CnTalkRoomCreate/CnTalkRoomCreate.vue'\nimport CnTalkRoomPicker from '../../../components/CnTalkRoomPicker/CnTalkRoomPicker.vue'\nimport { buildHeaders, prefixUrl, safeHref } from '../../../utils/index.js'\nimport { stripMarker } from '../../utils/marker.js'\n\n/**\n * CnTalkTab — bespoke conversation list for the `talk` integration.\n *\n * Renders rows pulled from the OR Tier-2 link table as NcListItem rows\n * styled to mirror NC Talk: conversation avatar, bold name,\n * last-message preview, relative timestamp, and an unread counter\n * bubble. Tier-2: supports link / create / unlink via picker + create\n * dialogs.\n */\nexport default {\n\tname: 'CnTalkTab',\n\n\tcomponents: {\n\t\tNcActionButton,\n\t\tNcActionLink,\n\t\tNcAvatar,\n\t\tNcButton,\n\t\tNcCounterBubble,\n\t\tNcDateTime,\n\t\tNcListItem,\n\t\tNcLoadingIcon,\n\t\tAlertCircleOutline,\n\t\tChatOutline,\n\t\tClose,\n\t\tLinkVariant,\n\t\tOpenInNew,\n\t\tPlus,\n\t\tCnTalkRoomPicker,\n\t\tCnTalkRoomCreate,\n\t},\n\n\tprops: {\n\t\t/** Stable integration id (matches the PHP-side provider id, always `talk`). */\n\t\tintegrationId: { type: String, default: 'talk' },\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 for OpenRegister. */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\t\t/** Empty state shown when no conversation is linked yet. */\n\t\temptyLabel: { type: String, default: () => t('nextcloud-vue', 'No conversations linked yet') },\n\t\t/** Label for the link that opens the conversation in Talk. */\n\t\topenTalkLabel: { type: String, default: () => t('nextcloud-vue', 'Open Talk') },\n\t\t/** Message shown when the Talk app cannot be reached. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Talk is currently unavailable.') },\n\t\t/** Base URL of the Talk app, used to build a conversation link. */\n\t\ttalkAppUrl: { type: String, default: '/index.php/apps/spreed' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\trooms: [],\n\t\t\tloading: false,\n\t\t\terror: '',\n\t\t\tdegraded: '',\n\t\t\tpickerOpen: false,\n\t\t\tcreateOpen: false,\n\t\t}\n\t},\n\n\twatch: {\n\t\tobjectId: { immediate: true, handler(id) {\n\t\t\tif (id) {\n\t\t\t\tthis.fetchRooms()\n\t\t\t}\n\t\t} },\n\n\t\tregister() {\n\t\t\tthis.fetchRooms()\n\t\t},\n\n\t\tschema() {\n\t\t\tthis.fetchRooms()\n\t\t},\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\tbaseUrl() {\n\t\t\treturn `${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/${this.integrationId}`\n\t\t},\n\n\t\troomKey(room) {\n\t\t\treturn room.roomToken ?? room.id ?? room.token ?? room.reference ?? ''\n\t\t},\n\n\t\troomTitle(room) {\n\t\t\tconst raw = room.roomName ?? room.title ?? room.displayName ?? room.name ?? this.roomKey(room)\n\t\t\treturn stripMarker(raw) || String(this.roomKey(room))\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\troomUrl(room) {\n\t\t\tif (room.url && safeHref(room.url) !== '#') {\n\t\t\t\treturn room.url\n\t\t\t}\n\t\t\tconst token = room.roomToken ?? room.token ?? room.id ?? ''\n\t\t\treturn token ? `/index.php/call/${token}` : this.talkAppUrl\n\t\t},\n\n\t\thasUnread(room) {\n\t\t\tconst count = Number(room.unreadMessages ?? room.unread ?? 0)\n\t\t\treturn Number.isFinite(count) && count > 0\n\t\t},\n\n\t\tformatUnread(room) {\n\t\t\tconst count = Number(room.unreadMessages ?? room.unread ?? 0)\n\t\t\tif (!Number.isFinite(count) || count <= 0) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn count > 99 ? '99+' : String(count)\n\t\t},\n\n\t\tunreadAriaLabel(room) {\n\t\t\tconst count = Number(room.unreadMessages ?? room.unread ?? 0)\n\t\t\treturn t('nextcloud-vue', '{n} unread', { n: count })\n\t\t},\n\n\t\t/**\n\t\t * Last-message preview line. Falls back to a participant-count\n\t\t * summary so the subname is never empty in NC-Talk style.\n\t\t *\n\t\t * @param {object} room Conversation row.\n\t\t * @return {string} Preview or participant summary.\n\t\t */\n\t\troomSubname(room) {\n\t\t\tconst preview = this.roomPreview(room)\n\t\t\tif (preview) {\n\t\t\t\treturn preview\n\t\t\t}\n\t\t\tconst participants = Number(room.participantCount ?? room.numParticipants ?? 0)\n\t\t\tif (Number.isFinite(participants) && participants > 0) {\n\t\t\t\treturn t('nextcloud-vue', '{n} participants', { n: participants })\n\t\t\t}\n\t\t\treturn ''\n\t\t},\n\n\t\troomPreview(room) {\n\t\t\tlet msg = room.lastMessage\n\t\t\t// The provider may hand us the message as a JSON string; parse it\n\t\t\t// so we never render a raw `{\"message\":...}` blob in the preview.\n\t\t\tif (typeof msg === 'string') {\n\t\t\t\tconst trimmed = msg.trim()\n\t\t\t\tif (trimmed.charAt(0) === '{' || trimmed.charAt(0) === '[') {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tmsg = JSON.parse(trimmed)\n\t\t\t\t\t} catch {\n\t\t\t\t\t\treturn this.cleanPreview(trimmed)\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\treturn this.cleanPreview(msg)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (msg && typeof msg === 'object') {\n\t\t\t\t// Talk system messages carry a snake_case key (e.g. `user_added`)\n\t\t\t\t// in `systemMessage`/`message` plus rich `parameters`. Humanise\n\t\t\t\t// them instead of dumping the placeholder/JSON.\n\t\t\t\tconst isSystem = (typeof msg.systemMessage === 'string' && msg.systemMessage !== '')\n\t\t\t\t\t|| msg.messageType === 'system'\n\t\t\t\tconst raw = msg.message ?? msg.text ?? ''\n\t\t\t\tif (isSystem) {\n\t\t\t\t\treturn this.humaniseSystemMessage(msg)\n\t\t\t\t}\n\t\t\t\treturn this.cleanPreview(typeof raw === 'string' ? raw : '')\n\t\t\t}\n\t\t\treturn this.cleanPreview(room.lastMessageText ?? room.preview ?? '')\n\t\t},\n\n\t\t/**\n\t\t * Turn a Talk system message into readable text. Substitutes rich\n\t\t * `{placeholder}` parameters with their display name; falls back to a\n\t\t * title-cased version of the snake_case key (e.g. `user_added` →\n\t\t * \"User added\").\n\t\t *\n\t\t * @param {object} msg The last-message object.\n\t\t * @return {string} Human-readable line.\n\t\t */\n\t\thumaniseSystemMessage(msg) {\n\t\t\tconst key = (typeof msg.systemMessage === 'string' && msg.systemMessage !== '')\n\t\t\t\t? msg.systemMessage\n\t\t\t\t: (typeof msg.message === 'string' ? msg.message : '')\n\t\t\tconst template = (typeof msg.message === 'string' && msg.message.indexOf('{') !== -1)\n\t\t\t\t? msg.message\n\t\t\t\t: ''\n\t\t\tconst params = msg.messageParameters || msg.parameters || {}\n\t\t\tif (template !== '' && params && typeof params === 'object') {\n\t\t\t\treturn template.replace(/\\{(\\w+)\\}/g, (whole, name) => {\n\t\t\t\t\tconst p = params[name]\n\t\t\t\t\tif (p && typeof p === 'object') {\n\t\t\t\t\t\treturn p.name || p.displayName || p.id || name\n\t\t\t\t\t}\n\t\t\t\t\treturn (p === undefined || p === null) ? name : String(p)\n\t\t\t\t})\n\t\t\t}\n\t\t\t// Bare key like `user_added` / `conversation_created`.\n\t\t\tif (key !== '') {\n\t\t\t\tconst words = key.replace(/[_-]+/g, ' ').trim()\n\t\t\t\treturn words.charAt(0).toUpperCase() + words.slice(1)\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', 'System message')\n\t\t},\n\n\t\t/**\n\t\t * Guard against a raw JSON blob ever reaching the preview line.\n\t\t *\n\t\t * @param {string} text Candidate preview text.\n\t\t * @return {string} The text, or '' when it still looks like JSON.\n\t\t */\n\t\tcleanPreview(text) {\n\t\t\tconst s = String(text || '').trim()\n\t\t\tif (s.charAt(0) === '{' || s.charAt(0) === '[') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn s\n\t\t},\n\n\t\t/**\n\t\t * Resolve the room's last-activity moment as an epoch-millisecond\n\t\t * value for NcDateTime. Accepts unix seconds, millisecond numbers,\n\t\t * or an ISO-8601 string; returns 0 when unparseable (no timestamp).\n\t\t *\n\t\t * @param {object} room Conversation row.\n\t\t * @return {number} Epoch milliseconds, or 0 when unknown.\n\t\t */\n\t\troomTimestamp(room) {\n\t\t\tconst raw = room.lastActivity ?? room.lastActivityAt ?? null\n\t\t\tif (raw === null || raw === undefined || raw === '') {\n\t\t\t\treturn 0\n\t\t\t}\n\t\t\tif (typeof raw === 'number') {\n\t\t\t\t// Heuristic: treat 10-digit values as unix seconds.\n\t\t\t\treturn raw < 1e12 ? raw * 1000 : raw\n\t\t\t}\n\t\t\tconst parsed = new Date(raw).getTime()\n\t\t\treturn Number.isNaN(parsed) ? 0 : parsed\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 onPickerLink(payload) {\n\t\t\tthis.pickerOpen = false\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(this.baseUrl()), {\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({ roomToken: payload.roomToken }),\n\t\t\t\t})\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tawait this.fetchRooms()\n\t\t\t\t} else if (response.status === 409) {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'This room is already linked.')\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not link the room.')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console -- the failure is already handled; the console is the only channel a host app can read the detail on\n\t\t\t\tconsole.error('[CnTalkTab] link room failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not link the room.')\n\t\t\t}\n\t\t},\n\n\t\tasync onCreateSubmit(payload) {\n\t\t\tthis.createOpen = false\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(`${this.baseUrl()}/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.fetchRooms()\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not create the room.')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console -- the failure is already handled; the console is the only channel a host app can read the detail on\n\t\t\t\tconsole.error('[CnTalkTab] create room failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not create the room.')\n\t\t\t}\n\t\t},\n\n\t\tasync unlinkRoom(room) {\n\t\t\tconst token = room.roomToken ?? room.token ?? room.id\n\t\t\tif (!token) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(`${this.baseUrl()}/${encodeURIComponent(token)}`), {\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.fetchRooms()\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not unlink the room.')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console -- the failure is already handled; the console is the only channel a host app can read the detail on\n\t\t\t\tconsole.error('[CnTalkTab] unlink room failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not unlink the room.')\n\t\t\t}\n\t\t},\n\n\t\tasync fetchRooms() {\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.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.rooms = rows\n\t\t\t\t} else if (response.status === 503 || response.status === 501) {\n\t\t\t\t\tthis.rooms = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.rooms = []\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load conversations.')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console -- the failure is already handled; the console is the only channel a host app can read the detail on\n\t\t\t\tconsole.error('[CnTalkTab] failed to fetch rooms', err)\n\t\t\t\tthis.rooms = []\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load conversations.')\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-talk-tab {\n\tpadding: 8px 0;\n}\n\n.cn-talk-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-talk-tab__actions {\n\tdisplay: flex;\n\tgap: 8px;\n\tmargin-bottom: 10px;\n\tflex-wrap: wrap;\n}\n\n.cn-talk-tab__error {\n\tcolor: var(--color-error);\n\tfont-size: 0.9em;\n\tmargin: 4px 0 8px;\n}\n\n.cn-talk-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-talk-tab__empty-icon {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-talk-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/* Last-message preview — single line, muted, like NC Talk. */\n.cn-talk-tab__preview {\n\tdisplay: block;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-talk-tab__time {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.8em;\n\twhite-space: nowrap;\n}\n</style>\n"],"names":["CnTalkRoomPicker","CnTalkRoomCreate","t"],"mappings":"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"}
@@ -89,8 +89,7 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
89
89
  createVNode(_component_NcButton, {
90
90
  variant: "primary",
91
91
  href: $props.talkAppUrl,
92
- target: "_blank",
93
- rel: "noopener noreferrer"
92
+ target: "_blank"
94
93
  }, {
95
94
  icon: withCtx(() => [
96
95
  createVNode(_component_ChatOutline, { size: 20 })
@@ -127,8 +126,7 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
127
126
  createVNode(_component_NcActionLink, {
128
127
  closeAfterClick: true,
129
128
  href: $options.roomUrl(room),
130
- target: "_blank",
131
- rel: "noopener noreferrer"
129
+ target: "_blank"
132
130
  }, {
133
131
  icon: withCtx(() => [
134
132
  createVNode(_component_OpenInNew, { size: 20 })
@@ -1 +1 @@
1
- {"version":3,"file":"CnTalkTab.vue3.js","sources":["../../../../../src/integrations/builtin/talk/CnTalkTab.vue?vue&type=template&id=e4b81da4&lang.js"],"sourcesContent":["<!--\n CnTalkTab — bespoke sidebar tab for the `talk` integration.\n\n Replaces the generic CnIntegrationTab for the `talk` leaf: mirrors the\n real NC Talk conversation list — each row is an NcListItem with the\n conversation avatar (NcAvatar keyed on the room name), a bold\n conversation name, a last-message preview as the subname, a relative\n timestamp (NcDateTime) in the details slot, and an unread-count bubble\n (NcCounterBubble) when the room has unread messages. Per-row unlink and\n \"Open in Talk\" live in the row action menu. Tier-2: adds explicit\n \"Link existing room\" + \"Create new room\" actions powered by\n CnTalkRoomPicker / CnTalkRoomCreate; unlink does NOT destroy the\n underlying Talk room.\n\n Talks to the OpenRegister Tier-2 endpoint\n GET /api/objects/{register}/{schema}/{objectId}/talk\n POST /api/objects/{register}/{schema}/{objectId}/talk — link\n POST /api/objects/{register}/{schema}/{objectId}/talk/new — create\n DELETE /api/objects/{register}/{schema}/{objectId}/talk/{roomToken} — unlink\n served by OCA\\OpenRegister\\Controller\\TalkLinksController.\n\n Surface behaviour:\n - Empty state with \"Open Talk\" + Link/Create CTAs when no linked rooms.\n - Loading + 503 \"currently unavailable\" + generic error states match\n CnIntegrationTab's behaviour for AD-23 graceful degradation.\n\n See `openregister/openspec/changes/integration-talk/` for the spec\n delta and ADR-019 (registry mechanism).\n-->\n<template>\n\t<div class=\"cn-sidebar-tab cn-talk-tab\">\n\t\t<div v-if=\"degraded\" class=\"cn-talk-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<!-- Tier-2 action bar — always visible when not in error/loading -->\n\t\t<div v-if=\"!loading && !error\" class=\"cn-talk-tab__actions\">\n\t\t\t<NcButton @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 room') }}\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 room') }}\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-talk-tab__error\" role=\"alert\">\n\t\t\t{{ error }}\n\t\t</div>\n\t\t<div v-else-if=\"rooms.length === 0\" class=\"cn-sidebar-tab__empty cn-talk-tab__empty\">\n\t\t\t<ChatOutline :size=\"32\" class=\"cn-talk-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=\"talkAppUrl\"\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<ChatOutline :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ openTalkLabel }}\n\t\t\t</NcButton>\n\t\t</div>\n\t\t<ul v-else class=\"cn-talk-tab__list\">\n\t\t\t<NcListItem\n\t\t\t\tv-for=\"room in rooms\"\n\t\t\t\t:key=\"roomKey(room)\"\n\t\t\t\tclass=\"cn-talk-tab__row\"\n\t\t\t\t:class=\"{ 'cn-talk-tab__row--unread': hasUnread(room) }\"\n\t\t\t\t:name=\"roomTitle(room)\"\n\t\t\t\t:bold=\"hasUnread(room)\"\n\t\t\t\t:href=\"roomUrl(room)\"\n\t\t\t\ttarget=\"_blank\"\n\t\t\t\t:forceDisplayActions=\"true\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<NcAvatar\n\t\t\t\t\t\t:displayName=\"roomTitle(room)\"\n\t\t\t\t\t\t:size=\"40\"\n\t\t\t\t\t\t:isNoUser=\"true\"\n\t\t\t\t\t\thideStatus />\n\t\t\t\t</template>\n\t\t\t\t<template #subname>\n\t\t\t\t\t<span class=\"cn-talk-tab__preview\">{{ roomSubname(room) }}</span>\n\t\t\t\t</template>\n\t\t\t\t<template v-if=\"roomTimestamp(room)\" #details>\n\t\t\t\t\t<NcDateTime\n\t\t\t\t\t\tclass=\"cn-talk-tab__time\"\n\t\t\t\t\t\t:timestamp=\"roomTimestamp(room)\"\n\t\t\t\t\t\trelativeTime=\"short\" />\n\t\t\t\t</template>\n\t\t\t\t<template v-if=\"hasUnread(room)\" #indicator>\n\t\t\t\t\t<NcCounterBubble\n\t\t\t\t\t\tclass=\"cn-talk-tab__badge\"\n\t\t\t\t\t\ttype=\"highlighted\"\n\t\t\t\t\t\t:aria-label=\"unreadAriaLabel(room)\">\n\t\t\t\t\t\t{{ formatUnread(room) }}\n\t\t\t\t\t</NcCounterBubble>\n\t\t\t\t</template>\n\t\t\t\t<template #actions>\n\t\t\t\t\t<NcActionLink\n\t\t\t\t\t\t:closeAfterClick=\"true\"\n\t\t\t\t\t\t:href=\"roomUrl(room)\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener noreferrer\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<OpenInNew :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ t('nextcloud-vue', 'Open in Talk') }}\n\t\t\t\t\t</NcActionLink>\n\t\t\t\t\t<NcActionButton :closeAfterClick=\"true\" @click=\"unlinkRoom(room)\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<Close :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ t('nextcloud-vue', 'Unlink from object (the room stays in Talk)') }}\n\t\t\t\t\t</NcActionButton>\n\t\t\t\t</template>\n\t\t\t</NcListItem>\n\t\t</ul>\n\n\t\t<!-- Picker + Create modals (mounted lazily) -->\n\t\t<CnTalkRoomPicker\n\t\t\tv-if=\"pickerOpen\"\n\t\t\t:apiBase=\"apiBase\"\n\t\t\t@close=\"pickerOpen = false\"\n\t\t\t@link=\"onPickerLink\" />\n\n\t\t<CnTalkRoomCreate\n\t\t\tv-if=\"createOpen\"\n\t\t\t@close=\"createOpen = false\"\n\t\t\t@create=\"onCreateSubmit\" />\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcActionButton, NcActionLink, NcAvatar, NcButton, NcCounterBubble, NcDateTime, NcListItem, NcLoadingIcon } from '@nextcloud/vue'\nimport AlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue'\nimport ChatOutline from 'vue-material-design-icons/ChatOutline.vue'\nimport Close from 'vue-material-design-icons/Close.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 CnTalkRoomCreate from '../../../components/CnTalkRoomCreate/CnTalkRoomCreate.vue'\nimport CnTalkRoomPicker from '../../../components/CnTalkRoomPicker/CnTalkRoomPicker.vue'\nimport { buildHeaders, prefixUrl } from '../../../utils/index.js'\nimport { stripMarker } from '../../utils/marker.js'\n\n/**\n * CnTalkTab — bespoke conversation list for the `talk` integration.\n *\n * Renders rows pulled from the OR Tier-2 link table as NcListItem rows\n * styled to mirror NC Talk: conversation avatar, bold name,\n * last-message preview, relative timestamp, and an unread counter\n * bubble. Tier-2: supports link / create / unlink via picker + create\n * dialogs.\n */\nexport default {\n\tname: 'CnTalkTab',\n\n\tcomponents: {\n\t\tNcActionButton,\n\t\tNcActionLink,\n\t\tNcAvatar,\n\t\tNcButton,\n\t\tNcCounterBubble,\n\t\tNcDateTime,\n\t\tNcListItem,\n\t\tNcLoadingIcon,\n\t\tAlertCircleOutline,\n\t\tChatOutline,\n\t\tClose,\n\t\tLinkVariant,\n\t\tOpenInNew,\n\t\tPlus,\n\t\tCnTalkRoomPicker,\n\t\tCnTalkRoomCreate,\n\t},\n\n\tprops: {\n\t\t/** Stable integration id (matches the PHP-side provider id, always `talk`). */\n\t\tintegrationId: { type: String, default: 'talk' },\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 for OpenRegister. */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\t\t/** Empty state shown when no conversation is linked yet. */\n\t\temptyLabel: { type: String, default: () => t('nextcloud-vue', 'No conversations linked yet') },\n\t\t/** Label for the link that opens the conversation in Talk. */\n\t\topenTalkLabel: { type: String, default: () => t('nextcloud-vue', 'Open Talk') },\n\t\t/** Message shown when the Talk app cannot be reached. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Talk is currently unavailable.') },\n\t\t/** Base URL of the Talk app, used to build a conversation link. */\n\t\ttalkAppUrl: { type: String, default: '/index.php/apps/spreed' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\trooms: [],\n\t\t\tloading: false,\n\t\t\terror: '',\n\t\t\tdegraded: '',\n\t\t\tpickerOpen: false,\n\t\t\tcreateOpen: false,\n\t\t}\n\t},\n\n\twatch: {\n\t\tobjectId: { immediate: true, handler(id) {\n\t\t\tif (id) {\n\t\t\t\tthis.fetchRooms()\n\t\t\t}\n\t\t} },\n\n\t\tregister() {\n\t\t\tthis.fetchRooms()\n\t\t},\n\n\t\tschema() {\n\t\t\tthis.fetchRooms()\n\t\t},\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\tbaseUrl() {\n\t\t\treturn `${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/${this.integrationId}`\n\t\t},\n\n\t\troomKey(room) {\n\t\t\treturn room.roomToken ?? room.id ?? room.token ?? room.reference ?? ''\n\t\t},\n\n\t\troomTitle(room) {\n\t\t\tconst raw = room.roomName ?? room.title ?? room.displayName ?? room.name ?? this.roomKey(room)\n\t\t\treturn stripMarker(raw) || String(this.roomKey(room))\n\t\t},\n\n\t\troomUrl(room) {\n\t\t\tif (room.url) {\n\t\t\t\treturn room.url\n\t\t\t}\n\t\t\tconst token = room.roomToken ?? room.token ?? room.id ?? ''\n\t\t\treturn token ? `/index.php/call/${token}` : this.talkAppUrl\n\t\t},\n\n\t\thasUnread(room) {\n\t\t\tconst count = Number(room.unreadMessages ?? room.unread ?? 0)\n\t\t\treturn Number.isFinite(count) && count > 0\n\t\t},\n\n\t\tformatUnread(room) {\n\t\t\tconst count = Number(room.unreadMessages ?? room.unread ?? 0)\n\t\t\tif (!Number.isFinite(count) || count <= 0) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn count > 99 ? '99+' : String(count)\n\t\t},\n\n\t\tunreadAriaLabel(room) {\n\t\t\tconst count = Number(room.unreadMessages ?? room.unread ?? 0)\n\t\t\treturn t('nextcloud-vue', '{n} unread', { n: count })\n\t\t},\n\n\t\t/**\n\t\t * Last-message preview line. Falls back to a participant-count\n\t\t * summary so the subname is never empty in NC-Talk style.\n\t\t *\n\t\t * @param {object} room Conversation row.\n\t\t * @return {string} Preview or participant summary.\n\t\t */\n\t\troomSubname(room) {\n\t\t\tconst preview = this.roomPreview(room)\n\t\t\tif (preview) {\n\t\t\t\treturn preview\n\t\t\t}\n\t\t\tconst participants = Number(room.participantCount ?? room.numParticipants ?? 0)\n\t\t\tif (Number.isFinite(participants) && participants > 0) {\n\t\t\t\treturn t('nextcloud-vue', '{n} participants', { n: participants })\n\t\t\t}\n\t\t\treturn ''\n\t\t},\n\n\t\troomPreview(room) {\n\t\t\tlet msg = room.lastMessage\n\t\t\t// The provider may hand us the message as a JSON string; parse it\n\t\t\t// so we never render a raw `{\"message\":...}` blob in the preview.\n\t\t\tif (typeof msg === 'string') {\n\t\t\t\tconst trimmed = msg.trim()\n\t\t\t\tif (trimmed.charAt(0) === '{' || trimmed.charAt(0) === '[') {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tmsg = JSON.parse(trimmed)\n\t\t\t\t\t} catch {\n\t\t\t\t\t\treturn this.cleanPreview(trimmed)\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\treturn this.cleanPreview(msg)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (msg && typeof msg === 'object') {\n\t\t\t\t// Talk system messages carry a snake_case key (e.g. `user_added`)\n\t\t\t\t// in `systemMessage`/`message` plus rich `parameters`. Humanise\n\t\t\t\t// them instead of dumping the placeholder/JSON.\n\t\t\t\tconst isSystem = (typeof msg.systemMessage === 'string' && msg.systemMessage !== '')\n\t\t\t\t\t|| msg.messageType === 'system'\n\t\t\t\tconst raw = msg.message ?? msg.text ?? ''\n\t\t\t\tif (isSystem) {\n\t\t\t\t\treturn this.humaniseSystemMessage(msg)\n\t\t\t\t}\n\t\t\t\treturn this.cleanPreview(typeof raw === 'string' ? raw : '')\n\t\t\t}\n\t\t\treturn this.cleanPreview(room.lastMessageText ?? room.preview ?? '')\n\t\t},\n\n\t\t/**\n\t\t * Turn a Talk system message into readable text. Substitutes rich\n\t\t * `{placeholder}` parameters with their display name; falls back to a\n\t\t * title-cased version of the snake_case key (e.g. `user_added` →\n\t\t * \"User added\").\n\t\t *\n\t\t * @param {object} msg The last-message object.\n\t\t * @return {string} Human-readable line.\n\t\t */\n\t\thumaniseSystemMessage(msg) {\n\t\t\tconst key = (typeof msg.systemMessage === 'string' && msg.systemMessage !== '')\n\t\t\t\t? msg.systemMessage\n\t\t\t\t: (typeof msg.message === 'string' ? msg.message : '')\n\t\t\tconst template = (typeof msg.message === 'string' && msg.message.indexOf('{') !== -1)\n\t\t\t\t? msg.message\n\t\t\t\t: ''\n\t\t\tconst params = msg.messageParameters || msg.parameters || {}\n\t\t\tif (template !== '' && params && typeof params === 'object') {\n\t\t\t\treturn template.replace(/\\{(\\w+)\\}/g, (whole, name) => {\n\t\t\t\t\tconst p = params[name]\n\t\t\t\t\tif (p && typeof p === 'object') {\n\t\t\t\t\t\treturn p.name || p.displayName || p.id || name\n\t\t\t\t\t}\n\t\t\t\t\treturn (p === undefined || p === null) ? name : String(p)\n\t\t\t\t})\n\t\t\t}\n\t\t\t// Bare key like `user_added` / `conversation_created`.\n\t\t\tif (key !== '') {\n\t\t\t\tconst words = key.replace(/[_-]+/g, ' ').trim()\n\t\t\t\treturn words.charAt(0).toUpperCase() + words.slice(1)\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', 'System message')\n\t\t},\n\n\t\t/**\n\t\t * Guard against a raw JSON blob ever reaching the preview line.\n\t\t *\n\t\t * @param {string} text Candidate preview text.\n\t\t * @return {string} The text, or '' when it still looks like JSON.\n\t\t */\n\t\tcleanPreview(text) {\n\t\t\tconst s = String(text || '').trim()\n\t\t\tif (s.charAt(0) === '{' || s.charAt(0) === '[') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn s\n\t\t},\n\n\t\t/**\n\t\t * Resolve the room's last-activity moment as an epoch-millisecond\n\t\t * value for NcDateTime. Accepts unix seconds, millisecond numbers,\n\t\t * or an ISO-8601 string; returns 0 when unparseable (no timestamp).\n\t\t *\n\t\t * @param {object} room Conversation row.\n\t\t * @return {number} Epoch milliseconds, or 0 when unknown.\n\t\t */\n\t\troomTimestamp(room) {\n\t\t\tconst raw = room.lastActivity ?? room.lastActivityAt ?? null\n\t\t\tif (raw === null || raw === undefined || raw === '') {\n\t\t\t\treturn 0\n\t\t\t}\n\t\t\tif (typeof raw === 'number') {\n\t\t\t\t// Heuristic: treat 10-digit values as unix seconds.\n\t\t\t\treturn raw < 1e12 ? raw * 1000 : raw\n\t\t\t}\n\t\t\tconst parsed = new Date(raw).getTime()\n\t\t\treturn Number.isNaN(parsed) ? 0 : parsed\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 onPickerLink(payload) {\n\t\t\tthis.pickerOpen = false\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(this.baseUrl()), {\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({ roomToken: payload.roomToken }),\n\t\t\t\t})\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tawait this.fetchRooms()\n\t\t\t\t} else if (response.status === 409) {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'This room is already linked.')\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not link the room.')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console -- the failure is already handled; the console is the only channel a host app can read the detail on\n\t\t\t\tconsole.error('[CnTalkTab] link room failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not link the room.')\n\t\t\t}\n\t\t},\n\n\t\tasync onCreateSubmit(payload) {\n\t\t\tthis.createOpen = false\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(`${this.baseUrl()}/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.fetchRooms()\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not create the room.')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console -- the failure is already handled; the console is the only channel a host app can read the detail on\n\t\t\t\tconsole.error('[CnTalkTab] create room failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not create the room.')\n\t\t\t}\n\t\t},\n\n\t\tasync unlinkRoom(room) {\n\t\t\tconst token = room.roomToken ?? room.token ?? room.id\n\t\t\tif (!token) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(`${this.baseUrl()}/${encodeURIComponent(token)}`), {\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.fetchRooms()\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not unlink the room.')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console -- the failure is already handled; the console is the only channel a host app can read the detail on\n\t\t\t\tconsole.error('[CnTalkTab] unlink room failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not unlink the room.')\n\t\t\t}\n\t\t},\n\n\t\tasync fetchRooms() {\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.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.rooms = rows\n\t\t\t\t} else if (response.status === 503 || response.status === 501) {\n\t\t\t\t\tthis.rooms = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.rooms = []\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load conversations.')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console -- the failure is already handled; the console is the only channel a host app can read the detail on\n\t\t\t\tconsole.error('[CnTalkTab] failed to fetch rooms', err)\n\t\t\t\tthis.rooms = []\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load conversations.')\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-talk-tab {\n\tpadding: 8px 0;\n}\n\n.cn-talk-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-talk-tab__actions {\n\tdisplay: flex;\n\tgap: 8px;\n\tmargin-bottom: 10px;\n\tflex-wrap: wrap;\n}\n\n.cn-talk-tab__error {\n\tcolor: var(--color-error);\n\tfont-size: 0.9em;\n\tmargin: 4px 0 8px;\n}\n\n.cn-talk-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-talk-tab__empty-icon {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-talk-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/* Last-message preview — single line, muted, like NC Talk. */\n.cn-talk-tab__preview {\n\tdisplay: block;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-talk-tab__time {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.8em;\n\twhite-space: nowrap;\n}\n</style>\n"],"names":["_openBlock","_createElementBlock","_createVNode","_createElementVNode","_createTextVNode","_createBlock","_toDisplayString","_normalizeClass"],"mappings":"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"}
1
+ {"version":3,"file":"CnTalkTab.vue3.js","sources":["../../../../../src/integrations/builtin/talk/CnTalkTab.vue?vue&type=template&id=e4b81da4&lang.js"],"sourcesContent":["<!--\n CnTalkTab — bespoke sidebar tab for the `talk` integration.\n\n Replaces the generic CnIntegrationTab for the `talk` leaf: mirrors the\n real NC Talk conversation list — each row is an NcListItem with the\n conversation avatar (NcAvatar keyed on the room name), a bold\n conversation name, a last-message preview as the subname, a relative\n timestamp (NcDateTime) in the details slot, and an unread-count bubble\n (NcCounterBubble) when the room has unread messages. Per-row unlink and\n \"Open in Talk\" live in the row action menu. Tier-2: adds explicit\n \"Link existing room\" + \"Create new room\" actions powered by\n CnTalkRoomPicker / CnTalkRoomCreate; unlink does NOT destroy the\n underlying Talk room.\n\n Talks to the OpenRegister Tier-2 endpoint\n GET /api/objects/{register}/{schema}/{objectId}/talk\n POST /api/objects/{register}/{schema}/{objectId}/talk — link\n POST /api/objects/{register}/{schema}/{objectId}/talk/new — create\n DELETE /api/objects/{register}/{schema}/{objectId}/talk/{roomToken} — unlink\n served by OCA\\OpenRegister\\Controller\\TalkLinksController.\n\n Surface behaviour:\n - Empty state with \"Open Talk\" + Link/Create CTAs when no linked rooms.\n - Loading + 503 \"currently unavailable\" + generic error states match\n CnIntegrationTab's behaviour for AD-23 graceful degradation.\n\n See `openregister/openspec/changes/integration-talk/` for the spec\n delta and ADR-019 (registry mechanism).\n-->\n<template>\n\t<div class=\"cn-sidebar-tab cn-talk-tab\">\n\t\t<div v-if=\"degraded\" class=\"cn-talk-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<!-- Tier-2 action bar — always visible when not in error/loading -->\n\t\t<div v-if=\"!loading && !error\" class=\"cn-talk-tab__actions\">\n\t\t\t<NcButton @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 room') }}\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 room') }}\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-talk-tab__error\" role=\"alert\">\n\t\t\t{{ error }}\n\t\t</div>\n\t\t<div v-else-if=\"rooms.length === 0\" class=\"cn-sidebar-tab__empty cn-talk-tab__empty\">\n\t\t\t<ChatOutline :size=\"32\" class=\"cn-talk-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=\"talkAppUrl\"\n\t\t\t\ttarget=\"_blank\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<ChatOutline :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ openTalkLabel }}\n\t\t\t</NcButton>\n\t\t</div>\n\t\t<ul v-else class=\"cn-talk-tab__list\">\n\t\t\t<NcListItem\n\t\t\t\tv-for=\"room in rooms\"\n\t\t\t\t:key=\"roomKey(room)\"\n\t\t\t\tclass=\"cn-talk-tab__row\"\n\t\t\t\t:class=\"{ 'cn-talk-tab__row--unread': hasUnread(room) }\"\n\t\t\t\t:name=\"roomTitle(room)\"\n\t\t\t\t:bold=\"hasUnread(room)\"\n\t\t\t\t:href=\"roomUrl(room)\"\n\t\t\t\ttarget=\"_blank\"\n\t\t\t\t:forceDisplayActions=\"true\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<NcAvatar\n\t\t\t\t\t\t:displayName=\"roomTitle(room)\"\n\t\t\t\t\t\t:size=\"40\"\n\t\t\t\t\t\t:isNoUser=\"true\"\n\t\t\t\t\t\thideStatus />\n\t\t\t\t</template>\n\t\t\t\t<template #subname>\n\t\t\t\t\t<span class=\"cn-talk-tab__preview\">{{ roomSubname(room) }}</span>\n\t\t\t\t</template>\n\t\t\t\t<template v-if=\"roomTimestamp(room)\" #details>\n\t\t\t\t\t<NcDateTime\n\t\t\t\t\t\tclass=\"cn-talk-tab__time\"\n\t\t\t\t\t\t:timestamp=\"roomTimestamp(room)\"\n\t\t\t\t\t\trelativeTime=\"short\" />\n\t\t\t\t</template>\n\t\t\t\t<template v-if=\"hasUnread(room)\" #indicator>\n\t\t\t\t\t<NcCounterBubble\n\t\t\t\t\t\tclass=\"cn-talk-tab__badge\"\n\t\t\t\t\t\ttype=\"highlighted\"\n\t\t\t\t\t\t:aria-label=\"unreadAriaLabel(room)\">\n\t\t\t\t\t\t{{ formatUnread(room) }}\n\t\t\t\t\t</NcCounterBubble>\n\t\t\t\t</template>\n\t\t\t\t<template #actions>\n\t\t\t\t\t<NcActionLink\n\t\t\t\t\t\t:closeAfterClick=\"true\"\n\t\t\t\t\t\t:href=\"roomUrl(room)\"\n\t\t\t\t\t\ttarget=\"_blank\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<OpenInNew :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ t('nextcloud-vue', 'Open in Talk') }}\n\t\t\t\t\t</NcActionLink>\n\t\t\t\t\t<NcActionButton :closeAfterClick=\"true\" @click=\"unlinkRoom(room)\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<Close :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ t('nextcloud-vue', 'Unlink from object (the room stays in Talk)') }}\n\t\t\t\t\t</NcActionButton>\n\t\t\t\t</template>\n\t\t\t</NcListItem>\n\t\t</ul>\n\n\t\t<!-- Picker + Create modals (mounted lazily) -->\n\t\t<CnTalkRoomPicker\n\t\t\tv-if=\"pickerOpen\"\n\t\t\t:apiBase=\"apiBase\"\n\t\t\t@close=\"pickerOpen = false\"\n\t\t\t@link=\"onPickerLink\" />\n\n\t\t<CnTalkRoomCreate\n\t\t\tv-if=\"createOpen\"\n\t\t\t@close=\"createOpen = false\"\n\t\t\t@create=\"onCreateSubmit\" />\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcActionButton, NcActionLink, NcAvatar, NcButton, NcCounterBubble, NcDateTime, NcListItem, NcLoadingIcon } from '@nextcloud/vue'\nimport AlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue'\nimport ChatOutline from 'vue-material-design-icons/ChatOutline.vue'\nimport Close from 'vue-material-design-icons/Close.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 CnTalkRoomCreate from '../../../components/CnTalkRoomCreate/CnTalkRoomCreate.vue'\nimport CnTalkRoomPicker from '../../../components/CnTalkRoomPicker/CnTalkRoomPicker.vue'\nimport { buildHeaders, prefixUrl, safeHref } from '../../../utils/index.js'\nimport { stripMarker } from '../../utils/marker.js'\n\n/**\n * CnTalkTab — bespoke conversation list for the `talk` integration.\n *\n * Renders rows pulled from the OR Tier-2 link table as NcListItem rows\n * styled to mirror NC Talk: conversation avatar, bold name,\n * last-message preview, relative timestamp, and an unread counter\n * bubble. Tier-2: supports link / create / unlink via picker + create\n * dialogs.\n */\nexport default {\n\tname: 'CnTalkTab',\n\n\tcomponents: {\n\t\tNcActionButton,\n\t\tNcActionLink,\n\t\tNcAvatar,\n\t\tNcButton,\n\t\tNcCounterBubble,\n\t\tNcDateTime,\n\t\tNcListItem,\n\t\tNcLoadingIcon,\n\t\tAlertCircleOutline,\n\t\tChatOutline,\n\t\tClose,\n\t\tLinkVariant,\n\t\tOpenInNew,\n\t\tPlus,\n\t\tCnTalkRoomPicker,\n\t\tCnTalkRoomCreate,\n\t},\n\n\tprops: {\n\t\t/** Stable integration id (matches the PHP-side provider id, always `talk`). */\n\t\tintegrationId: { type: String, default: 'talk' },\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 for OpenRegister. */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\t\t/** Empty state shown when no conversation is linked yet. */\n\t\temptyLabel: { type: String, default: () => t('nextcloud-vue', 'No conversations linked yet') },\n\t\t/** Label for the link that opens the conversation in Talk. */\n\t\topenTalkLabel: { type: String, default: () => t('nextcloud-vue', 'Open Talk') },\n\t\t/** Message shown when the Talk app cannot be reached. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Talk is currently unavailable.') },\n\t\t/** Base URL of the Talk app, used to build a conversation link. */\n\t\ttalkAppUrl: { type: String, default: '/index.php/apps/spreed' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\trooms: [],\n\t\t\tloading: false,\n\t\t\terror: '',\n\t\t\tdegraded: '',\n\t\t\tpickerOpen: false,\n\t\t\tcreateOpen: false,\n\t\t}\n\t},\n\n\twatch: {\n\t\tobjectId: { immediate: true, handler(id) {\n\t\t\tif (id) {\n\t\t\t\tthis.fetchRooms()\n\t\t\t}\n\t\t} },\n\n\t\tregister() {\n\t\t\tthis.fetchRooms()\n\t\t},\n\n\t\tschema() {\n\t\t\tthis.fetchRooms()\n\t\t},\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\tbaseUrl() {\n\t\t\treturn `${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/${this.integrationId}`\n\t\t},\n\n\t\troomKey(room) {\n\t\t\treturn room.roomToken ?? room.id ?? room.token ?? room.reference ?? ''\n\t\t},\n\n\t\troomTitle(room) {\n\t\t\tconst raw = room.roomName ?? room.title ?? room.displayName ?? room.name ?? this.roomKey(room)\n\t\t\treturn stripMarker(raw) || String(this.roomKey(room))\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\troomUrl(room) {\n\t\t\tif (room.url && safeHref(room.url) !== '#') {\n\t\t\t\treturn room.url\n\t\t\t}\n\t\t\tconst token = room.roomToken ?? room.token ?? room.id ?? ''\n\t\t\treturn token ? `/index.php/call/${token}` : this.talkAppUrl\n\t\t},\n\n\t\thasUnread(room) {\n\t\t\tconst count = Number(room.unreadMessages ?? room.unread ?? 0)\n\t\t\treturn Number.isFinite(count) && count > 0\n\t\t},\n\n\t\tformatUnread(room) {\n\t\t\tconst count = Number(room.unreadMessages ?? room.unread ?? 0)\n\t\t\tif (!Number.isFinite(count) || count <= 0) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn count > 99 ? '99+' : String(count)\n\t\t},\n\n\t\tunreadAriaLabel(room) {\n\t\t\tconst count = Number(room.unreadMessages ?? room.unread ?? 0)\n\t\t\treturn t('nextcloud-vue', '{n} unread', { n: count })\n\t\t},\n\n\t\t/**\n\t\t * Last-message preview line. Falls back to a participant-count\n\t\t * summary so the subname is never empty in NC-Talk style.\n\t\t *\n\t\t * @param {object} room Conversation row.\n\t\t * @return {string} Preview or participant summary.\n\t\t */\n\t\troomSubname(room) {\n\t\t\tconst preview = this.roomPreview(room)\n\t\t\tif (preview) {\n\t\t\t\treturn preview\n\t\t\t}\n\t\t\tconst participants = Number(room.participantCount ?? room.numParticipants ?? 0)\n\t\t\tif (Number.isFinite(participants) && participants > 0) {\n\t\t\t\treturn t('nextcloud-vue', '{n} participants', { n: participants })\n\t\t\t}\n\t\t\treturn ''\n\t\t},\n\n\t\troomPreview(room) {\n\t\t\tlet msg = room.lastMessage\n\t\t\t// The provider may hand us the message as a JSON string; parse it\n\t\t\t// so we never render a raw `{\"message\":...}` blob in the preview.\n\t\t\tif (typeof msg === 'string') {\n\t\t\t\tconst trimmed = msg.trim()\n\t\t\t\tif (trimmed.charAt(0) === '{' || trimmed.charAt(0) === '[') {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tmsg = JSON.parse(trimmed)\n\t\t\t\t\t} catch {\n\t\t\t\t\t\treturn this.cleanPreview(trimmed)\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\treturn this.cleanPreview(msg)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (msg && typeof msg === 'object') {\n\t\t\t\t// Talk system messages carry a snake_case key (e.g. `user_added`)\n\t\t\t\t// in `systemMessage`/`message` plus rich `parameters`. Humanise\n\t\t\t\t// them instead of dumping the placeholder/JSON.\n\t\t\t\tconst isSystem = (typeof msg.systemMessage === 'string' && msg.systemMessage !== '')\n\t\t\t\t\t|| msg.messageType === 'system'\n\t\t\t\tconst raw = msg.message ?? msg.text ?? ''\n\t\t\t\tif (isSystem) {\n\t\t\t\t\treturn this.humaniseSystemMessage(msg)\n\t\t\t\t}\n\t\t\t\treturn this.cleanPreview(typeof raw === 'string' ? raw : '')\n\t\t\t}\n\t\t\treturn this.cleanPreview(room.lastMessageText ?? room.preview ?? '')\n\t\t},\n\n\t\t/**\n\t\t * Turn a Talk system message into readable text. Substitutes rich\n\t\t * `{placeholder}` parameters with their display name; falls back to a\n\t\t * title-cased version of the snake_case key (e.g. `user_added` →\n\t\t * \"User added\").\n\t\t *\n\t\t * @param {object} msg The last-message object.\n\t\t * @return {string} Human-readable line.\n\t\t */\n\t\thumaniseSystemMessage(msg) {\n\t\t\tconst key = (typeof msg.systemMessage === 'string' && msg.systemMessage !== '')\n\t\t\t\t? msg.systemMessage\n\t\t\t\t: (typeof msg.message === 'string' ? msg.message : '')\n\t\t\tconst template = (typeof msg.message === 'string' && msg.message.indexOf('{') !== -1)\n\t\t\t\t? msg.message\n\t\t\t\t: ''\n\t\t\tconst params = msg.messageParameters || msg.parameters || {}\n\t\t\tif (template !== '' && params && typeof params === 'object') {\n\t\t\t\treturn template.replace(/\\{(\\w+)\\}/g, (whole, name) => {\n\t\t\t\t\tconst p = params[name]\n\t\t\t\t\tif (p && typeof p === 'object') {\n\t\t\t\t\t\treturn p.name || p.displayName || p.id || name\n\t\t\t\t\t}\n\t\t\t\t\treturn (p === undefined || p === null) ? name : String(p)\n\t\t\t\t})\n\t\t\t}\n\t\t\t// Bare key like `user_added` / `conversation_created`.\n\t\t\tif (key !== '') {\n\t\t\t\tconst words = key.replace(/[_-]+/g, ' ').trim()\n\t\t\t\treturn words.charAt(0).toUpperCase() + words.slice(1)\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', 'System message')\n\t\t},\n\n\t\t/**\n\t\t * Guard against a raw JSON blob ever reaching the preview line.\n\t\t *\n\t\t * @param {string} text Candidate preview text.\n\t\t * @return {string} The text, or '' when it still looks like JSON.\n\t\t */\n\t\tcleanPreview(text) {\n\t\t\tconst s = String(text || '').trim()\n\t\t\tif (s.charAt(0) === '{' || s.charAt(0) === '[') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn s\n\t\t},\n\n\t\t/**\n\t\t * Resolve the room's last-activity moment as an epoch-millisecond\n\t\t * value for NcDateTime. Accepts unix seconds, millisecond numbers,\n\t\t * or an ISO-8601 string; returns 0 when unparseable (no timestamp).\n\t\t *\n\t\t * @param {object} room Conversation row.\n\t\t * @return {number} Epoch milliseconds, or 0 when unknown.\n\t\t */\n\t\troomTimestamp(room) {\n\t\t\tconst raw = room.lastActivity ?? room.lastActivityAt ?? null\n\t\t\tif (raw === null || raw === undefined || raw === '') {\n\t\t\t\treturn 0\n\t\t\t}\n\t\t\tif (typeof raw === 'number') {\n\t\t\t\t// Heuristic: treat 10-digit values as unix seconds.\n\t\t\t\treturn raw < 1e12 ? raw * 1000 : raw\n\t\t\t}\n\t\t\tconst parsed = new Date(raw).getTime()\n\t\t\treturn Number.isNaN(parsed) ? 0 : parsed\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 onPickerLink(payload) {\n\t\t\tthis.pickerOpen = false\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(this.baseUrl()), {\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({ roomToken: payload.roomToken }),\n\t\t\t\t})\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tawait this.fetchRooms()\n\t\t\t\t} else if (response.status === 409) {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'This room is already linked.')\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not link the room.')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console -- the failure is already handled; the console is the only channel a host app can read the detail on\n\t\t\t\tconsole.error('[CnTalkTab] link room failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not link the room.')\n\t\t\t}\n\t\t},\n\n\t\tasync onCreateSubmit(payload) {\n\t\t\tthis.createOpen = false\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(`${this.baseUrl()}/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.fetchRooms()\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not create the room.')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console -- the failure is already handled; the console is the only channel a host app can read the detail on\n\t\t\t\tconsole.error('[CnTalkTab] create room failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not create the room.')\n\t\t\t}\n\t\t},\n\n\t\tasync unlinkRoom(room) {\n\t\t\tconst token = room.roomToken ?? room.token ?? room.id\n\t\t\tif (!token) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(`${this.baseUrl()}/${encodeURIComponent(token)}`), {\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.fetchRooms()\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not unlink the room.')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console -- the failure is already handled; the console is the only channel a host app can read the detail on\n\t\t\t\tconsole.error('[CnTalkTab] unlink room failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not unlink the room.')\n\t\t\t}\n\t\t},\n\n\t\tasync fetchRooms() {\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.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.rooms = rows\n\t\t\t\t} else if (response.status === 503 || response.status === 501) {\n\t\t\t\t\tthis.rooms = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.rooms = []\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load conversations.')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console -- the failure is already handled; the console is the only channel a host app can read the detail on\n\t\t\t\tconsole.error('[CnTalkTab] failed to fetch rooms', err)\n\t\t\t\tthis.rooms = []\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load conversations.')\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-talk-tab {\n\tpadding: 8px 0;\n}\n\n.cn-talk-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-talk-tab__actions {\n\tdisplay: flex;\n\tgap: 8px;\n\tmargin-bottom: 10px;\n\tflex-wrap: wrap;\n}\n\n.cn-talk-tab__error {\n\tcolor: var(--color-error);\n\tfont-size: 0.9em;\n\tmargin: 4px 0 8px;\n}\n\n.cn-talk-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-talk-tab__empty-icon {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-talk-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/* Last-message preview — single line, muted, like NC Talk. */\n.cn-talk-tab__preview {\n\tdisplay: block;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-talk-tab__time {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.8em;\n\twhite-space: nowrap;\n}\n</style>\n"],"names":["_openBlock","_createElementBlock","_createVNode","_createElementVNode","_createTextVNode","_createBlock","_toDisplayString","_normalizeClass"],"mappings":"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"}
@@ -3,6 +3,7 @@ import { NcLoadingIcon } from '@nextcloud/vue';
3
3
  import Clock from 'vue-material-design-icons/Clock.vue';
4
4
  import Timer from 'vue-material-design-icons/Timer.vue';
5
5
  import __sfc_main from '../../../components/CnDetailCard/CnDetailCard.vue.js';
6
+ import { safeHref } from '../../../utils/safeHref.js';
6
7
  import { prefixUrl, buildHeaders } from '../../../utils/headers.js';
7
8
 
8
9
  const VALID_SURFACES = ['user-dashboard', 'app-dashboard', 'detail-page', 'single-entity'];
@@ -191,7 +192,7 @@ var script = {
191
192
  },
192
193
 
193
194
  rowUrl(row) {
194
- if (row.url) {
195
+ if (row.url && safeHref(row.url) !== '#') {
195
196
  return row.url
196
197
  }
197
198
  const kind = this.rowKind(row);