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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (314) hide show
  1. package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js +12 -2
  2. package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js.map +1 -1
  3. package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js +1 -2
  4. package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js.map +1 -1
  5. package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue2.js.map +1 -1
  6. package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue3.js +0 -3
  7. package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue3.js.map +1 -1
  8. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js.map +1 -1
  9. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js +1 -2
  10. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js.map +1 -1
  11. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +21 -1
  12. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
  13. package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js +1 -1
  14. package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
  15. package/dist/esm/components/CnBoardView/CnBoardView.vue2.js +12 -5
  16. package/dist/esm/components/CnBoardView/CnBoardView.vue2.js.map +1 -1
  17. package/dist/esm/components/CnBoardView/CnBoardView.vue3.js +2 -1
  18. package/dist/esm/components/CnBoardView/CnBoardView.vue3.js.map +1 -1
  19. package/dist/esm/components/CnCardGrid/CnCardGrid.vue2.js +9 -1
  20. package/dist/esm/components/CnCardGrid/CnCardGrid.vue2.js.map +1 -1
  21. package/dist/esm/components/CnCardGrid/CnCardGrid.vue3.js.map +1 -1
  22. package/dist/esm/components/CnContextMenu/CnContextMenu.vue2.js +6 -1
  23. package/dist/esm/components/CnContextMenu/CnContextMenu.vue2.js.map +1 -1
  24. package/dist/esm/components/CnContextMenu/CnContextMenu.vue3.js.map +1 -1
  25. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +116 -15
  26. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
  27. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +27 -14
  28. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
  29. package/dist/esm/components/CnDateAxisView/CnDateAxisView.vue2.js +13 -6
  30. package/dist/esm/components/CnDateAxisView/CnDateAxisView.vue2.js.map +1 -1
  31. package/dist/esm/components/CnDateAxisView/CnDateAxisView.vue3.js +2 -0
  32. package/dist/esm/components/CnDateAxisView/CnDateAxisView.vue3.js.map +1 -1
  33. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +29 -1
  34. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  35. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  36. package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue2.js.map +1 -1
  37. package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue3.js +1 -2
  38. package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue3.js.map +1 -1
  39. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +3 -1
  40. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
  41. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +17 -17
  42. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
  43. package/dist/esm/components/CnFilesWidget/CnFilesWidget.vue2.js +4 -21
  44. package/dist/esm/components/CnFilesWidget/CnFilesWidget.vue2.js.map +1 -1
  45. package/dist/esm/components/CnFilesWidget/CnFilesWidget.vue3.js.map +1 -1
  46. package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue2.js +0 -13
  47. package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue2.js.map +1 -1
  48. package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue3.js +8 -8
  49. package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue3.js.map +1 -1
  50. package/dist/esm/components/CnFlowsPage/CnFlowsPage.vue2.js.map +1 -1
  51. package/dist/esm/components/CnFlowsPage/CnFlowsPage.vue3.js +3 -2
  52. package/dist/esm/components/CnFlowsPage/CnFlowsPage.vue3.js.map +1 -1
  53. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +67 -13
  54. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  55. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +15 -8
  56. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  57. package/dist/esm/components/CnIndexPage/manifestActionDispatch.js +31 -11
  58. package/dist/esm/components/CnIndexPage/manifestActionDispatch.js.map +1 -1
  59. package/dist/esm/components/CnIntegrationWidget/CnIntegrationWidgetEmpty.vue2.js.map +1 -1
  60. package/dist/esm/components/CnIntegrationWidget/CnIntegrationWidgetEmpty.vue3.js +1 -2
  61. package/dist/esm/components/CnIntegrationWidget/CnIntegrationWidgetEmpty.vue3.js.map +1 -1
  62. package/dist/esm/components/CnInteractionFormWidget/CnInteractionFormWidget.vue2.js.map +1 -1
  63. package/dist/esm/components/CnInteractionFormWidget/CnInteractionFormWidget.vue3.js +0 -1
  64. package/dist/esm/components/CnInteractionFormWidget/CnInteractionFormWidget.vue3.js.map +1 -1
  65. package/dist/esm/components/CnLinkButtonWidget/CnLinkButtonWidget.vue2.js +2 -17
  66. package/dist/esm/components/CnLinkButtonWidget/CnLinkButtonWidget.vue2.js.map +1 -1
  67. package/dist/esm/components/CnLinkButtonWidget/CnLinkButtonWidget.vue3.js.map +1 -1
  68. package/dist/esm/components/CnLogsPage/CnLogsPage.vue2.js +35 -11
  69. package/dist/esm/components/CnLogsPage/CnLogsPage.vue2.js.map +1 -1
  70. package/dist/esm/components/CnLogsPage/CnLogsPage.vue3.js +3 -2
  71. package/dist/esm/components/CnLogsPage/CnLogsPage.vue3.js.map +1 -1
  72. package/dist/esm/components/CnObjectCard/CnObjectCard.vue2.js +23 -5
  73. package/dist/esm/components/CnObjectCard/CnObjectCard.vue2.js.map +1 -1
  74. package/dist/esm/components/CnObjectCard/CnObjectCard.vue3.js +1 -1
  75. package/dist/esm/components/CnObjectCard/CnObjectCard.vue3.js.map +1 -1
  76. package/dist/esm/components/CnObjectList/CnObjectList.vue2.js +19 -3
  77. package/dist/esm/components/CnObjectList/CnObjectList.vue2.js.map +1 -1
  78. package/dist/esm/components/CnObjectList/CnObjectList.vue3.js.map +1 -1
  79. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +45 -15
  80. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
  81. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js +4 -2
  82. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
  83. package/dist/esm/components/CnObjectRow/CnObjectRow.vue2.js +24 -6
  84. package/dist/esm/components/CnObjectRow/CnObjectRow.vue2.js.map +1 -1
  85. package/dist/esm/components/CnObjectRow/CnObjectRow.vue3.js +1 -1
  86. package/dist/esm/components/CnObjectRow/CnObjectRow.vue3.js.map +1 -1
  87. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
  88. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +0 -1
  89. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
  90. package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue2.js.map +1 -1
  91. package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue3.js +4 -2
  92. package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue3.js.map +1 -1
  93. package/dist/esm/components/CnRowActions/CnRowActions.vue2.js +7 -2
  94. package/dist/esm/components/CnRowActions/CnRowActions.vue2.js.map +1 -1
  95. package/dist/esm/components/CnRowActions/CnRowActions.vue3.js.map +1 -1
  96. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js +49 -5
  97. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js.map +1 -1
  98. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js +1 -1
  99. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js.map +1 -1
  100. package/dist/esm/components/CnSpendAnalyticsWidget/CnSpendAnalyticsWidget.vue2.js +5 -3
  101. package/dist/esm/components/CnSpendAnalyticsWidget/CnSpendAnalyticsWidget.vue2.js.map +1 -1
  102. package/dist/esm/components/CnSpendAnalyticsWidget/CnSpendAnalyticsWidget.vue3.js.map +1 -1
  103. package/dist/esm/components/CnTabs/CnTabs.vue2.js.map +1 -1
  104. package/dist/esm/components/CnTabs/CnTabs.vue3.js.map +1 -1
  105. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue2.js +3 -1
  106. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue2.js.map +1 -1
  107. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue3.js +1 -0
  108. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue3.js.map +1 -1
  109. package/dist/esm/integrations/builtin/analytics/CnAnalyticsCard.vue2.js +3 -1
  110. package/dist/esm/integrations/builtin/analytics/CnAnalyticsCard.vue2.js.map +1 -1
  111. package/dist/esm/integrations/builtin/analytics/CnAnalyticsCard.vue3.js.map +1 -1
  112. package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue2.js +3 -1
  113. package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue2.js.map +1 -1
  114. package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue3.js +2 -4
  115. package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue3.js.map +1 -1
  116. package/dist/esm/integrations/builtin/bookmarks/CnBookmarksCard.vue2.js +6 -0
  117. package/dist/esm/integrations/builtin/bookmarks/CnBookmarksCard.vue2.js.map +1 -1
  118. package/dist/esm/integrations/builtin/bookmarks/CnBookmarksCard.vue3.js +68 -53
  119. package/dist/esm/integrations/builtin/bookmarks/CnBookmarksCard.vue3.js.map +1 -1
  120. package/dist/esm/integrations/builtin/bookmarks/CnBookmarksTab.vue2.js +17 -0
  121. package/dist/esm/integrations/builtin/bookmarks/CnBookmarksTab.vue2.js.map +1 -1
  122. package/dist/esm/integrations/builtin/bookmarks/CnBookmarksTab.vue3.js +21 -19
  123. package/dist/esm/integrations/builtin/bookmarks/CnBookmarksTab.vue3.js.map +1 -1
  124. package/dist/esm/integrations/builtin/collectives/CnCollectivesCard.vue2.js +3 -1
  125. package/dist/esm/integrations/builtin/collectives/CnCollectivesCard.vue2.js.map +1 -1
  126. package/dist/esm/integrations/builtin/collectives/CnCollectivesCard.vue3.js.map +1 -1
  127. package/dist/esm/integrations/builtin/collectives/CnCollectivesTab.vue2.js +3 -1
  128. package/dist/esm/integrations/builtin/collectives/CnCollectivesTab.vue2.js.map +1 -1
  129. package/dist/esm/integrations/builtin/collectives/CnCollectivesTab.vue3.js +2 -4
  130. package/dist/esm/integrations/builtin/collectives/CnCollectivesTab.vue3.js.map +1 -1
  131. package/dist/esm/integrations/builtin/cospend/CnCospendCard.vue2.js +2 -1
  132. package/dist/esm/integrations/builtin/cospend/CnCospendCard.vue2.js.map +1 -1
  133. package/dist/esm/integrations/builtin/cospend/CnCospendCard.vue3.js.map +1 -1
  134. package/dist/esm/integrations/builtin/cospend/CnCospendTab.vue2.js +2 -1
  135. package/dist/esm/integrations/builtin/cospend/CnCospendTab.vue2.js.map +1 -1
  136. package/dist/esm/integrations/builtin/cospend/CnCospendTab.vue3.js +1 -2
  137. package/dist/esm/integrations/builtin/cospend/CnCospendTab.vue3.js.map +1 -1
  138. package/dist/esm/integrations/builtin/deck/CnDeckCard.vue2.js +2 -1
  139. package/dist/esm/integrations/builtin/deck/CnDeckCard.vue2.js.map +1 -1
  140. package/dist/esm/integrations/builtin/deck/CnDeckCard.vue3.js.map +1 -1
  141. package/dist/esm/integrations/builtin/deck/CnDeckTab.vue2.js +2 -1
  142. package/dist/esm/integrations/builtin/deck/CnDeckTab.vue2.js.map +1 -1
  143. package/dist/esm/integrations/builtin/deck/CnDeckTab.vue3.js +1 -2
  144. package/dist/esm/integrations/builtin/deck/CnDeckTab.vue3.js.map +1 -1
  145. package/dist/esm/integrations/builtin/email/CnEmailCard.vue2.js.map +1 -1
  146. package/dist/esm/integrations/builtin/email/CnEmailCard.vue3.js.map +1 -1
  147. package/dist/esm/integrations/builtin/email/CnEmailTab.vue2.js.map +1 -1
  148. package/dist/esm/integrations/builtin/email/CnEmailTab.vue3.js +1 -2
  149. package/dist/esm/integrations/builtin/email/CnEmailTab.vue3.js.map +1 -1
  150. package/dist/esm/integrations/builtin/flow/CnFlowCard.vue2.js +2 -1
  151. package/dist/esm/integrations/builtin/flow/CnFlowCard.vue2.js.map +1 -1
  152. package/dist/esm/integrations/builtin/flow/CnFlowCard.vue3.js.map +1 -1
  153. package/dist/esm/integrations/builtin/flow/CnFlowTab.vue2.js +2 -1
  154. package/dist/esm/integrations/builtin/flow/CnFlowTab.vue2.js.map +1 -1
  155. package/dist/esm/integrations/builtin/flow/CnFlowTab.vue3.js +1 -2
  156. package/dist/esm/integrations/builtin/flow/CnFlowTab.vue3.js.map +1 -1
  157. package/dist/esm/integrations/builtin/forms/CnFormsCard.vue2.js +3 -1
  158. package/dist/esm/integrations/builtin/forms/CnFormsCard.vue2.js.map +1 -1
  159. package/dist/esm/integrations/builtin/forms/CnFormsCard.vue3.js.map +1 -1
  160. package/dist/esm/integrations/builtin/forms/CnFormsTab.vue2.js +3 -1
  161. package/dist/esm/integrations/builtin/forms/CnFormsTab.vue2.js.map +1 -1
  162. package/dist/esm/integrations/builtin/forms/CnFormsTab.vue3.js +2 -4
  163. package/dist/esm/integrations/builtin/forms/CnFormsTab.vue3.js.map +1 -1
  164. package/dist/esm/integrations/builtin/maps/CnMapsCard.vue2.js +3 -1
  165. package/dist/esm/integrations/builtin/maps/CnMapsCard.vue2.js.map +1 -1
  166. package/dist/esm/integrations/builtin/maps/CnMapsCard.vue3.js.map +1 -1
  167. package/dist/esm/integrations/builtin/maps/CnMapsTab.vue2.js +3 -1
  168. package/dist/esm/integrations/builtin/maps/CnMapsTab.vue2.js.map +1 -1
  169. package/dist/esm/integrations/builtin/maps/CnMapsTab.vue3.js +2 -4
  170. package/dist/esm/integrations/builtin/maps/CnMapsTab.vue3.js.map +1 -1
  171. package/dist/esm/integrations/builtin/openproject/CnOpenprojectCard.vue2.js +3 -1
  172. package/dist/esm/integrations/builtin/openproject/CnOpenprojectCard.vue2.js.map +1 -1
  173. package/dist/esm/integrations/builtin/openproject/CnOpenprojectCard.vue3.js.map +1 -1
  174. package/dist/esm/integrations/builtin/openproject/CnOpenprojectTab.vue2.js +3 -1
  175. package/dist/esm/integrations/builtin/openproject/CnOpenprojectTab.vue2.js.map +1 -1
  176. package/dist/esm/integrations/builtin/openproject/CnOpenprojectTab.vue3.js +2 -4
  177. package/dist/esm/integrations/builtin/openproject/CnOpenprojectTab.vue3.js.map +1 -1
  178. package/dist/esm/integrations/builtin/photos/CnPhotosCard.vue2.js +2 -1
  179. package/dist/esm/integrations/builtin/photos/CnPhotosCard.vue2.js.map +1 -1
  180. package/dist/esm/integrations/builtin/photos/CnPhotosCard.vue3.js.map +1 -1
  181. package/dist/esm/integrations/builtin/photos/CnPhotosTab.vue2.js +2 -1
  182. package/dist/esm/integrations/builtin/photos/CnPhotosTab.vue2.js.map +1 -1
  183. package/dist/esm/integrations/builtin/photos/CnPhotosTab.vue3.js +1 -2
  184. package/dist/esm/integrations/builtin/photos/CnPhotosTab.vue3.js.map +1 -1
  185. package/dist/esm/integrations/builtin/polls/CnPollsCard.vue2.js +2 -1
  186. package/dist/esm/integrations/builtin/polls/CnPollsCard.vue2.js.map +1 -1
  187. package/dist/esm/integrations/builtin/polls/CnPollsCard.vue3.js.map +1 -1
  188. package/dist/esm/integrations/builtin/polls/CnPollsTab.vue2.js +2 -1
  189. package/dist/esm/integrations/builtin/polls/CnPollsTab.vue2.js.map +1 -1
  190. package/dist/esm/integrations/builtin/polls/CnPollsTab.vue3.js +1 -2
  191. package/dist/esm/integrations/builtin/polls/CnPollsTab.vue3.js.map +1 -1
  192. package/dist/esm/integrations/builtin/shares/CnSharesTab.vue2.js.map +1 -1
  193. package/dist/esm/integrations/builtin/shares/CnSharesTab.vue3.js +3 -6
  194. package/dist/esm/integrations/builtin/shares/CnSharesTab.vue3.js.map +1 -1
  195. package/dist/esm/integrations/builtin/talk/CnTalkCard.vue2.js +3 -1
  196. package/dist/esm/integrations/builtin/talk/CnTalkCard.vue2.js.map +1 -1
  197. package/dist/esm/integrations/builtin/talk/CnTalkCard.vue3.js.map +1 -1
  198. package/dist/esm/integrations/builtin/talk/CnTalkTab.vue2.js +3 -1
  199. package/dist/esm/integrations/builtin/talk/CnTalkTab.vue2.js.map +1 -1
  200. package/dist/esm/integrations/builtin/talk/CnTalkTab.vue3.js +2 -4
  201. package/dist/esm/integrations/builtin/talk/CnTalkTab.vue3.js.map +1 -1
  202. package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerCard.vue2.js +2 -1
  203. package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerCard.vue2.js.map +1 -1
  204. package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerCard.vue3.js.map +1 -1
  205. package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerTab.vue2.js +2 -1
  206. package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerTab.vue2.js.map +1 -1
  207. package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerTab.vue3.js +1 -2
  208. package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerTab.vue3.js.map +1 -1
  209. package/dist/esm/integrations/builtin/xwiki/CnXwikiCard.vue2.js +4 -1
  210. package/dist/esm/integrations/builtin/xwiki/CnXwikiCard.vue2.js.map +1 -1
  211. package/dist/esm/integrations/builtin/xwiki/CnXwikiCard.vue3.js.map +1 -1
  212. package/dist/esm/integrations/builtin/xwiki/CnXwikiTab.vue2.js +6 -3
  213. package/dist/esm/integrations/builtin/xwiki/CnXwikiTab.vue2.js.map +1 -1
  214. package/dist/esm/integrations/builtin/xwiki/CnXwikiTab.vue3.js +2 -4
  215. package/dist/esm/integrations/builtin/xwiki/CnXwikiTab.vue3.js.map +1 -1
  216. package/dist/esm/l10n/en.json.js +2 -0
  217. package/dist/esm/l10n/en.json.js.map +1 -1
  218. package/dist/esm/l10n/nl.json.js +2 -0
  219. package/dist/esm/l10n/nl.json.js.map +1 -1
  220. package/dist/esm/nextcloud-vue.css +78 -7
  221. package/dist/esm/utils/actionLink.js +5 -0
  222. package/dist/esm/utils/actionLink.js.map +1 -1
  223. package/dist/esm/utils/actionsDispatcher.js +16 -1
  224. package/dist/esm/utils/actionsDispatcher.js.map +1 -1
  225. package/dist/esm/utils/linkNavigation.js +6 -3
  226. package/dist/esm/utils/linkNavigation.js.map +1 -1
  227. package/dist/esm/utils/rowAuxClick.js +30 -5
  228. package/dist/esm/utils/rowAuxClick.js.map +1 -1
  229. package/dist/esm/utils/safeHref.js +18 -3
  230. package/dist/esm/utils/safeHref.js.map +1 -1
  231. package/dist/esm/utils/validateManifestV2.compiled.js +2 -2
  232. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  233. package/dist/nextcloud-vue.cjs.js +885 -393
  234. package/dist/nextcloud-vue.cjs.js.map +2 -2
  235. package/dist/nextcloud-vue.css +78 -7
  236. package/l10n/en.json +2 -0
  237. package/l10n/nl.json +2 -0
  238. package/package.json +1 -1
  239. package/src/components/CnActionsBar/CnActionsBar.vue +14 -5
  240. package/src/components/CnActionsMenu/CnActionsMenu.vue +0 -3
  241. package/src/components/CnAdminSettingsShell/CnAdminSettingsShell.vue +1 -2
  242. package/src/components/CnAppRoot/CnAppRoot.vue +31 -2
  243. package/src/components/CnBoardView/CnBoardView.vue +13 -5
  244. package/src/components/CnCardGrid/CnCardGrid.vue +9 -1
  245. package/src/components/CnContextMenu/CnContextMenu.vue +6 -1
  246. package/src/components/CnDataTable/CnDataTable.md +1 -1
  247. package/src/components/CnDataTable/CnDataTable.vue +131 -17
  248. package/src/components/CnDateAxisView/CnDateAxisView.vue +15 -6
  249. package/src/components/CnDetailPage/CnDetailPage.md +1 -1
  250. package/src/components/CnDetailPage/CnDetailPage.vue +29 -1
  251. package/src/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue +1 -2
  252. package/src/components/CnFilesBrowser/CnFilesBrowser.vue +5 -3
  253. package/src/components/CnFilesWidget/CnFilesWidget.vue +4 -21
  254. package/src/components/CnFlowRunsWidget/CnFlowRunsWidget.vue +8 -21
  255. package/src/components/CnFlowsPage/CnFlowsPage.vue +2 -1
  256. package/src/components/CnIndexPage/CnIndexPage.vue +75 -14
  257. package/src/components/CnIndexPage/manifestActionDispatch.js +31 -11
  258. package/src/components/CnIntegrationWidget/CnIntegrationWidgetEmpty.vue +1 -2
  259. package/src/components/CnInteractionFormWidget/CnInteractionFormWidget.vue +0 -1
  260. package/src/components/CnLinkButtonWidget/CnLinkButtonWidget.vue +2 -17
  261. package/src/components/CnLogsPage/CnLogsPage.vue +37 -12
  262. package/src/components/CnObjectCard/CnObjectCard.vue +24 -6
  263. package/src/components/CnObjectList/CnObjectList.vue +19 -3
  264. package/src/components/CnObjectListWidget/CnObjectListWidget.vue +47 -15
  265. package/src/components/CnObjectRow/CnObjectRow.vue +25 -7
  266. package/src/components/CnObjectSidebar/CnFilesTab.vue +0 -1
  267. package/src/components/CnPageRenderer/CnPageRenderer.vue +2 -0
  268. package/src/components/CnRowActions/CnRowActions.vue +7 -2
  269. package/src/components/CnSetupWizard/CnSetupWizard.vue +57 -6
  270. package/src/components/CnSpendAnalyticsWidget/CnSpendAnalyticsWidget.vue +5 -3
  271. package/src/components/CnTabs/CnTabs.vue +7 -3
  272. package/src/components/CnTasksWidget/CnTasksWidget.vue +4 -1
  273. package/src/css/table.css +26 -0
  274. package/src/integrations/builtin/analytics/CnAnalyticsCard.vue +3 -2
  275. package/src/integrations/builtin/analytics/CnAnalyticsTab.vue +5 -6
  276. package/src/integrations/builtin/bookmarks/CnBookmarksCard.vue +12 -1
  277. package/src/integrations/builtin/bookmarks/CnBookmarksTab.vue +23 -6
  278. package/src/integrations/builtin/bookmarks/__tests__/CnBookmarksTab.spec.js +7 -2
  279. package/src/integrations/builtin/collectives/CnCollectivesCard.vue +3 -2
  280. package/src/integrations/builtin/collectives/CnCollectivesTab.vue +5 -6
  281. package/src/integrations/builtin/cospend/CnCospendCard.vue +2 -2
  282. package/src/integrations/builtin/cospend/CnCospendTab.vue +3 -4
  283. package/src/integrations/builtin/deck/CnDeckCard.vue +2 -2
  284. package/src/integrations/builtin/deck/CnDeckTab.vue +3 -4
  285. package/src/integrations/builtin/email/CnEmailCard.vue +6 -2
  286. package/src/integrations/builtin/email/CnEmailTab.vue +7 -4
  287. package/src/integrations/builtin/flow/CnFlowCard.vue +2 -2
  288. package/src/integrations/builtin/flow/CnFlowTab.vue +3 -4
  289. package/src/integrations/builtin/forms/CnFormsCard.vue +3 -2
  290. package/src/integrations/builtin/forms/CnFormsTab.vue +5 -6
  291. package/src/integrations/builtin/maps/CnMapsCard.vue +3 -2
  292. package/src/integrations/builtin/maps/CnMapsTab.vue +5 -6
  293. package/src/integrations/builtin/maps/__tests__/CnMapsTab.spec.js +9 -0
  294. package/src/integrations/builtin/openproject/CnOpenprojectCard.vue +3 -2
  295. package/src/integrations/builtin/openproject/CnOpenprojectTab.vue +5 -6
  296. package/src/integrations/builtin/photos/CnPhotosCard.vue +2 -2
  297. package/src/integrations/builtin/photos/CnPhotosTab.vue +3 -4
  298. package/src/integrations/builtin/polls/CnPollsCard.vue +2 -2
  299. package/src/integrations/builtin/polls/CnPollsTab.vue +3 -4
  300. package/src/integrations/builtin/shares/CnSharesTab.vue +3 -6
  301. package/src/integrations/builtin/talk/CnTalkCard.vue +3 -2
  302. package/src/integrations/builtin/talk/CnTalkTab.vue +5 -6
  303. package/src/integrations/builtin/time-tracker/CnTimeTrackerCard.vue +2 -2
  304. package/src/integrations/builtin/time-tracker/CnTimeTrackerTab.vue +3 -4
  305. package/src/integrations/builtin/xwiki/CnXwikiCard.vue +4 -2
  306. package/src/integrations/builtin/xwiki/CnXwikiTab.vue +7 -7
  307. package/src/integrations/builtin/xwiki/__tests__/CnXwikiTab.spec.js +9 -0
  308. package/src/schemas/app-manifest-v2.schema.json +2 -1
  309. package/src/utils/actionLink.js +5 -0
  310. package/src/utils/actionsDispatcher.js +15 -0
  311. package/src/utils/linkNavigation.js +6 -3
  312. package/src/utils/rowAuxClick.js +29 -4
  313. package/src/utils/safeHref.js +18 -3
  314. package/src/utils/validateManifestV2.compiled.js +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"CnFormsTab.vue2.js","sources":["../../../../../src/integrations/builtin/forms/CnFormsTab.vue"],"sourcesContent":["<!--\n - SPDX-License-Identifier: EUPL-1.2\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n -\n - CnFormsTab — bespoke sidebar tab for the Tier-2 `forms` integration.\n -\n - Replaces the generic CnIntegrationTab for the `forms` leaf: renders a\n - list of linked NC Forms forms styled to mirror the real Forms app —\n - each row is an NcListItem with a clipboard/form icon, the bold form\n - title, an optional description, a response-count bubble\n - (NcCounterBubble), an open/closed/draft state chip (CnStatusBadge),\n - and an expiry date (NcDateTime) when the form has one. Per-row \"Open\n - in Forms\" + unlink live in the row actions menu, with Tier-2\n - link/unlink + create-new actions on the header.\n -\n - Talks to the OpenRegister Tier-2 forms link surface:\n - GET /api/objects/{r}/{s}/{id}/forms — list linked\n - POST /api/objects/{r}/{s}/{id}/forms — link existing\n - POST /api/objects/{r}/{s}/{id}/forms/new — create + link\n - DELETE /api/objects/{r}/{s}/{id}/forms/{id} — unlink form\n -\n - The Tier-2 endpoint returns canonical form rows enriched with\n - submission roll-ups; pre-Tier-2 forms (still on the marker scan)\n - keep working through the same endpoint via the provider's\n - backwards-compat fallback.\n -\n - Per ADR-004 (modal isolation) the picker + create modals live in\n - their own .vue files (CnFormPicker, CnFormCreate) and are wired\n - in here rather than inlined.\n -\n - @spec openspec/changes/integration-forms/specs/integrations/forms/spec.md\n -->\n<template>\n\t<div class=\"cn-sidebar-tab cn-forms-tab\">\n\t\t<div class=\"cn-forms-tab__header\">\n\t\t\t<NcButton\n\t\t\t\tvariant=\"primary\"\n\t\t\t\t:aria-label=\"linkExistingLabel\"\n\t\t\t\t@click=\"showPicker = true\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<LinkVariantPlus :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ linkExistingLabel }}\n\t\t\t</NcButton>\n\t\t\t<NcButton\n\t\t\t\t:aria-label=\"createNewLabel\"\n\t\t\t\t@click=\"showCreate = true\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<Plus :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ createNewLabel }}\n\t\t\t</NcButton>\n\t\t</div>\n\n\t\t<div v-if=\"degraded\" class=\"cn-forms-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<NcLoadingIcon v-if=\"loading\" />\n\t\t<div v-else-if=\"error\" class=\"cn-forms-tab__error\" role=\"alert\">\n\t\t\t{{ error }}\n\t\t</div>\n\t\t<div v-else-if=\"forms.length === 0\" class=\"cn-sidebar-tab__empty cn-forms-tab__empty\">\n\t\t\t<ClipboardText :size=\"32\" class=\"cn-forms-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=\"formsAppUrl\"\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<ClipboardText :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ openFormsLabel }}\n\t\t\t</NcButton>\n\t\t</div>\n\t\t<ul v-else class=\"cn-forms-tab__list\">\n\t\t\t<NcListItem\n\t\t\t\tv-for=\"form in forms\"\n\t\t\t\t:key=\"formKey(form)\"\n\t\t\t\tclass=\"cn-forms-tab__row\"\n\t\t\t\t:class=\"{ 'cn-forms-tab__row--closed': isClosed(form) }\"\n\t\t\t\t:name=\"formTitle(form)\"\n\t\t\t\t:bold=\"false\"\n\t\t\t\t:href=\"formUrl(form)\"\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<ClipboardText :size=\"40\" class=\"cn-forms-tab__row-icon\" />\n\t\t\t\t</template>\n\t\t\t\t<template v-if=\"formDescription(form)\" #subname>\n\t\t\t\t\t<span class=\"cn-forms-tab__description\">{{ formDescription(form) }}</span>\n\t\t\t\t</template>\n\t\t\t\t<template #details>\n\t\t\t\t\t<div class=\"cn-forms-tab__details\">\n\t\t\t\t\t\t<CnStatusBadge\n\t\t\t\t\t\t\tclass=\"cn-forms-tab__status\"\n\t\t\t\t\t\t\t:class=\"statusClass(form)\"\n\t\t\t\t\t\t\t:label=\"statusLabel(form)\"\n\t\t\t\t\t\t\t:variant=\"statusVariant(form)\"\n\t\t\t\t\t\t\tsize=\"small\" />\n\t\t\t\t\t\t<NcDateTime\n\t\t\t\t\t\t\tv-if=\"expiresAtMs(form) !== null\"\n\t\t\t\t\t\t\tclass=\"cn-forms-tab__expiry\"\n\t\t\t\t\t\t\t:timestamp=\"expiresAtMs(form)\"\n\t\t\t\t\t\t\trelativeTime=\"short\" />\n\t\t\t\t\t</div>\n\t\t\t\t</template>\n\t\t\t\t<template v-if=\"submissionCount(form) > 0\" #indicator>\n\t\t\t\t\t<NcCounterBubble\n\t\t\t\t\t\tclass=\"cn-forms-tab__count\"\n\t\t\t\t\t\t:aria-label=\"responsesAriaLabel(form)\">\n\t\t\t\t\t\t{{ submissionCount(form) }}\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=\"formUrl(form)\"\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{{ openInFormsLabel }}\n\t\t\t\t\t</NcActionLink>\n\t\t\t\t\t<NcActionButton\n\t\t\t\t\t\tclass=\"cn-forms-tab__unlink\"\n\t\t\t\t\t\t:closeAfterClick=\"true\"\n\t\t\t\t\t\t@click=\"unlink(form)\">\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{{ unlinkLabel }}\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<CnFormPicker\n\t\t\tv-if=\"showPicker\"\n\t\t\t:objectId=\"objectId\"\n\t\t\t:register=\"register\"\n\t\t\t:schema=\"schema\"\n\t\t\t:apiBase=\"apiBase\"\n\t\t\t@link=\"onLinkSelected\"\n\t\t\t@close=\"showPicker = false\" />\n\n\t\t<CnFormCreate\n\t\t\tv-if=\"showCreate\"\n\t\t\t:submitting=\"creating\"\n\t\t\t:submitError=\"createError\"\n\t\t\t@create=\"onCreateSubmit\"\n\t\t\t@close=\"onCreateClose\" />\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcActionButton, NcActionLink, NcButton, NcCounterBubble, NcDateTime, NcListItem, NcLoadingIcon } from '@nextcloud/vue'\nimport AlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue'\nimport ClipboardText from 'vue-material-design-icons/ClipboardText.vue'\nimport Close from 'vue-material-design-icons/Close.vue'\nimport LinkVariantPlus from 'vue-material-design-icons/LinkVariantPlus.vue'\nimport OpenInNew from 'vue-material-design-icons/OpenInNew.vue'\nimport Plus from 'vue-material-design-icons/Plus.vue'\nimport CnStatusBadge from '../../../components/CnStatusBadge/CnStatusBadge.vue'\nimport CnFormCreate from './CnFormCreate.vue'\nimport CnFormPicker from './CnFormPicker.vue'\nimport { buildHeaders, prefixUrl } from '../../../utils/index.js'\n\n/**\n * CnFormsTab — bespoke linked-forms list for the Tier-2 `forms` leaf.\n *\n * Renders rows pulled from the OR Tier-2 forms link endpoint, styled to\n * mirror NC Forms (form icon, bold title, description, response-count\n * bubble, state chip, expiry date), with inline \"Link existing form\" +\n * \"Create new form\" actions on the header and per-row open/unlink in the\n * row actions menu.\n */\nexport default {\n\tname: 'CnFormsTab',\n\n\tcomponents: {\n\t\tNcActionButton,\n\t\tNcActionLink,\n\t\tNcButton,\n\t\tNcCounterBubble,\n\t\tNcDateTime,\n\t\tNcListItem,\n\t\tNcLoadingIcon,\n\t\tAlertCircleOutline,\n\t\tClipboardText,\n\t\tClose,\n\t\tLinkVariantPlus,\n\t\tOpenInNew,\n\t\tPlus,\n\t\tCnStatusBadge,\n\t\tCnFormCreate,\n\t\tCnFormPicker,\n\t},\n\n\tprops: {\n\t\t/* eslint-disable vue/no-unused-properties -- the integration dispatch binds integrationId on every integration component (see CnIntegrationWidgetGrid), so declaring it keeps it out of $attrs */\n\t\t/** Stable integration id (forwarded from the registry — always `'forms'`). */\n\t\tintegrationId: { type: String, default: 'forms' },\n\t\t/* eslint-enable vue/no-unused-properties */\n\t\t/** Parent object id. */\n\t\tobjectId: { type: String, required: true },\n\t\t/** OpenRegister register id (slug or uuid). */\n\t\tregister: { type: String, default: '' },\n\t\t/** OpenRegister schema id (slug or uuid). */\n\t\tschema: { type: String, default: '' },\n\t\t/** Base API URL. */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\t\t/** Pre-translated empty-state label. */\n\t\temptyLabel: { type: String, default: () => t('nextcloud-vue', 'No forms linked yet') },\n\t\t/** Pre-translated label for the \"Open in Forms\" CTA. */\n\t\topenFormsLabel: { type: String, default: () => t('nextcloud-vue', 'Open Forms') },\n\t\t/** Pre-translated label for the per-row \"Open in Forms\" action. */\n\t\topenInFormsLabel: { type: String, default: () => t('nextcloud-vue', 'Open in Forms') },\n\t\t/** Pre-translated banner when Forms is unavailable. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Forms is currently unavailable.') },\n\t\t/** Pre-translated label for the link-existing action. */\n\t\tlinkExistingLabel: { type: String, default: () => t('nextcloud-vue', 'Link existing form') },\n\t\t/** Pre-translated label for the create-new action. */\n\t\tcreateNewLabel: { type: String, default: () => t('nextcloud-vue', 'Create new form') },\n\t\t/** Pre-translated label for the per-row unlink action. */\n\t\tunlinkLabel: { type: String, default: () => t('nextcloud-vue', 'Unlink form') },\n\t\t/** URL of the NC Forms app entry. */\n\t\tformsAppUrl: { type: String, default: '/index.php/apps/forms' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\trawRows: [],\n\t\t\tloading: false,\n\t\t\terror: '',\n\t\t\tdegraded: '',\n\t\t\tshowPicker: false,\n\t\t\tshowCreate: false,\n\t\t\tcreating: false,\n\t\t\tcreateError: '',\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Group provider rows into a per-form list with attached\n\t\t * submissions. The Tier-2 endpoint already emits per-form rows\n\t\t * with `submissionCount` rolled up; this method also handles\n\t\t * the legacy flat shape (rows with `type: 'submission'`) for\n\t\t * forms that pre-date the link table.\n\t\t *\n\t\t * @return {object[]} Per-form display rows.\n\t\t */\n\t\tforms() {\n\t\t\tconst out = []\n\t\t\tconst byId = new Map()\n\t\t\tfor (const row of this.rawRows) {\n\t\t\t\tif (!row || typeof row !== 'object') {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif ((row.type ?? 'form') === 'submission') {\n\t\t\t\t\tconst formId = String(row.data?.formId ?? '')\n\t\t\t\t\tif (byId.has(formId) === true) {\n\t\t\t\t\t\tconst target = byId.get(formId)\n\t\t\t\t\t\ttarget.submissions = (target.submissions || 0) + 1\n\t\t\t\t\t\tif (row.lastUpdated && (!target.lastSubmissionAt || row.lastUpdated > target.lastSubmissionAt)) {\n\t\t\t\t\t\t\ttarget.lastSubmissionAt = row.lastUpdated\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tconst normalised = this.normaliseFormRow(row)\n\t\t\t\tbyId.set(String(normalised.id), normalised)\n\t\t\t\tout.push(normalised)\n\t\t\t}\n\t\t\treturn out\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.fetchForms()\n\t\t\t}\n\t\t} },\n\n\t\tregister() {\n\t\t\tthis.fetchForms()\n\t\t},\n\n\t\tschema() {\n\t\t\tthis.fetchForms()\n\t\t},\n\t},\n\n\tmethods: {\n\t\tbaseUrl() {\n\t\t\treturn prefixUrl(`${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/forms`)\n\t\t},\n\n\t\tnormaliseFormRow(row) {\n\t\t\tconst data = row.data || {}\n\t\t\treturn {\n\t\t\t\tid: row.id ?? row.formId ?? data.id ?? '',\n\t\t\t\thash: data.hash ?? row.formHash ?? row.hash ?? '',\n\t\t\t\ttitle: row.title ?? data.title ?? '',\n\t\t\t\tdescription: row.description ?? data.description ?? '',\n\t\t\t\turl: row.url ?? '',\n\t\t\t\tlastUpdated: row.lastUpdated ?? data.lastUpdated ?? null,\n\t\t\t\tstatus: row.status ?? data.status ?? null,\n\t\t\t\texpiresAt: row.expiresAt ?? data.expiresAt ?? null,\n\t\t\t\taccessible: row.accessible ?? data.accessible ?? null,\n\t\t\t\tsubmissionCount: Number(row.submissionCount ?? data.submissionCount ?? 0) || 0,\n\t\t\t\tsubmissions: 0,\n\t\t\t\tlastSubmissionAt: null,\n\t\t\t}\n\t\t},\n\n\t\tformKey(form) {\n\t\t\treturn form.id ?? form.hash ?? ''\n\t\t},\n\n\t\tformTitle(form) {\n\t\t\treturn form.title || form.id || t('nextcloud-vue', 'Untitled form')\n\t\t},\n\n\t\tformDescription(form) {\n\t\t\treturn form.description || ''\n\t\t},\n\n\t\tformUrl(form) {\n\t\t\tif (form.url) {\n\t\t\t\treturn form.url\n\t\t\t}\n\t\t\tif (form.hash) {\n\t\t\t\treturn `/index.php/apps/forms/${form.hash}`\n\t\t\t}\n\t\t\treturn this.formsAppUrl\n\t\t},\n\n\t\t/**\n\t\t * Resolve the form's expiry as a millisecond epoch, or null when\n\t\t * the form never expires. Accepts both second- and millisecond-\n\t\t * granularity numbers and ISO strings.\n\t\t *\n\t\t * @param {object} form normalised form row\n\t\t *\n\t\t * @return {?number}\n\t\t */\n\t\texpiresAtMs(form) {\n\t\t\tconst v = form.expiresAt\n\t\t\tif (v === null || v === undefined || v === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tif (typeof v === 'number') {\n\t\t\t\treturn v < 1e12 ? v * 1000 : v\n\t\t\t}\n\t\t\tconst parsed = Date.parse(String(v))\n\t\t\treturn Number.isNaN(parsed) === true ? null : parsed\n\t\t},\n\n\t\tisClosed(form) {\n\t\t\tif (form.status === 'closed' || form.status === 'archived') {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\tconst ms = this.expiresAtMs(form)\n\t\t\tif (ms !== null) {\n\t\t\t\treturn ms <= Date.now()\n\t\t\t}\n\t\t\treturn false\n\t\t},\n\n\t\tstatusLabel(form) {\n\t\t\tif (this.isClosed(form) === true) {\n\t\t\t\treturn t('nextcloud-vue', 'Closed')\n\t\t\t}\n\t\t\tif (form.status === 'draft') {\n\t\t\t\treturn t('nextcloud-vue', 'Draft')\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', 'Open')\n\t\t},\n\n\t\tstatusClass(form) {\n\t\t\tif (this.isClosed(form) === true) {\n\t\t\t\treturn 'cn-forms-tab__status--closed'\n\t\t\t}\n\t\t\tif (form.status === 'draft') {\n\t\t\t\treturn 'cn-forms-tab__status--draft'\n\t\t\t}\n\t\t\treturn 'cn-forms-tab__status--open'\n\t\t},\n\n\t\t/**\n\t\t * Map the form's state to a CnStatusBadge colour variant —\n\t\t * open = success (green), draft = warning, closed = default.\n\t\t *\n\t\t * @param {object} form normalised form row\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tstatusVariant(form) {\n\t\t\tif (this.isClosed(form) === true) {\n\t\t\t\treturn 'default'\n\t\t\t}\n\t\t\tif (form.status === 'draft') {\n\t\t\t\treturn 'warning'\n\t\t\t}\n\t\t\treturn 'success'\n\t\t},\n\n\t\tsubmissionCount(form) {\n\t\t\tif (form.submissionCount > 0) {\n\t\t\t\treturn form.submissionCount\n\t\t\t}\n\t\t\treturn form.submissions || 0\n\t\t},\n\n\t\tresponsesAriaLabel(form) {\n\t\t\treturn t('nextcloud-vue', '{n} submissions', { n: this.submissionCount(form) })\n\t\t},\n\n\t\tasync fetchForms() {\n\t\t\tif (!this.register || !this.schema || !this.objectId) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\tthis.error = ''\n\t\t\tthis.degraded = ''\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(this.baseUrl(), { headers: buildHeaders() })\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tconst data = await response.json()\n\t\t\t\t\tthis.rawRows = this.unwrapList(data)\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.rawRows = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.rawRows = []\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load forms.')\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('[CnFormsTab] failed to fetch forms', err)\n\t\t\t\tthis.rawRows = []\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load forms.')\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Canonical wrapper-key cascade — mirrors CnContactsTab.unwrapList.\n\t\t *\n\t\t * @param {object|Array<object>|null} data parsed JSON response body\n\t\t *\n\t\t * @return {Array}\n\t\t */\n\t\tunwrapList(data) {\n\t\t\tif (Array.isArray(data)) {\n\t\t\t\treturn data\n\t\t\t}\n\t\t\tif (data && typeof data === 'object') {\n\t\t\t\tif (Array.isArray(data.results)) {\n\t\t\t\t\treturn data.results\n\t\t\t\t}\n\t\t\t\tif (Array.isArray(data.items)) {\n\t\t\t\t\treturn data.items\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn []\n\t\t},\n\n\t\tasync onLinkSelected(payload) {\n\t\t\tif (!payload || !payload.formId) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(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(payload),\n\t\t\t\t})\n\t\t\t\tif (!response.ok) {\n\t\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\t\tconsole.error('[CnFormsTab] link failed', response.status, response.statusText)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tthis.showPicker = false\n\t\t\t\tawait this.fetchForms()\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('[CnFormsTab] link error', err)\n\t\t\t}\n\t\t},\n\n\t\tasync onCreateSubmit(payload) {\n\t\t\tif (!payload || !payload.title) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.creating = true\n\t\t\tthis.createError = ''\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(`${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\tconst data = await response.json().catch(() => ({}))\n\t\t\t\t\tthis.createError = data?.error || `${response.status} ${response.statusText}`\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tthis.showCreate = false\n\t\t\t\tawait this.fetchForms()\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('[CnFormsTab] create error', err)\n\t\t\t\tthis.createError = String(err?.message || err)\n\t\t\t} finally {\n\t\t\t\tthis.creating = false\n\t\t\t}\n\t\t},\n\n\t\tonCreateClose() {\n\t\t\tthis.showCreate = false\n\t\t\tthis.createError = ''\n\t\t},\n\n\t\tasync unlink(form) {\n\t\t\tif (!form?.id) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(`${this.baseUrl()}/${encodeURIComponent(form.id)}`, {\n\t\t\t\t\tmethod: 'DELETE',\n\t\t\t\t\theaders: buildHeaders(),\n\t\t\t\t})\n\t\t\t\tif (!response.ok) {\n\t\t\t\t\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\t\tconsole.error('[CnFormsTab] unlink failed', response.status, response.statusText)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tthis.rawRows = this.rawRows.filter((r) => String(r.id ?? r.formId ?? '') !== String(form.id))\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('[CnFormsTab] unlink error', err)\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-forms-tab {\n\tpadding: 12px;\n}\n\n.cn-forms-tab__header {\n\tdisplay: flex;\n\tgap: 8px;\n\tjustify-content: flex-end;\n\tmargin-bottom: 10px;\n\tflex-wrap: wrap;\n}\n\n.cn-forms-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-forms-tab__error {\n\tcolor: var(--color-error);\n\tfont-size: 0.9em;\n\tmargin: 4px 0 8px;\n}\n\n.cn-forms-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-forms-tab__empty-icon {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-forms-tab__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n/* Forms brand accent on the row icon, mirroring the real Forms app. */\n.cn-forms-tab__row-icon {\n\tcolor: var(--cn-forms-accent, #0d7000);\n\tflex-shrink: 0;\n}\n\n.cn-forms-tab__row--closed {\n\topacity: 0.85;\n}\n\n.cn-forms-tab__row--closed .cn-forms-tab__row-icon {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-forms-tab__description {\n\tdisplay: -webkit-box;\n\t-webkit-line-clamp: 2;\n\t-webkit-box-orient: vertical;\n\toverflow: hidden;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-forms-tab__details {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 6px;\n\tflex-wrap: wrap;\n\tjustify-content: flex-end;\n}\n\n.cn-forms-tab__expiry {\n\tfont-size: 0.8em;\n\tcolor: var(--color-text-maxcontrast);\n\twhite-space: nowrap;\n}\n\n/* Status chip colours layered on top of CnStatusBadge variants so the\n open/closed/draft state reads at a glance, like the real Forms app. */\n.cn-forms-tab__status--open {\n\t--cn-forms-status: var(--color-success, #46ba61);\n}\n\n.cn-forms-tab__status--closed {\n\t--cn-forms-status: var(--color-text-maxcontrast);\n}\n\n.cn-forms-tab__status--draft {\n\t--cn-forms-status: var(--color-warning, #e9a40f);\n}\n</style>\n"],"names":["CnStatusBadge","CnFormCreate","CnFormPicker","t"],"mappings":"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"}
1
+ {"version":3,"file":"CnFormsTab.vue2.js","sources":["../../../../../src/integrations/builtin/forms/CnFormsTab.vue"],"sourcesContent":["<!--\n - SPDX-License-Identifier: EUPL-1.2\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n -\n - CnFormsTab — bespoke sidebar tab for the Tier-2 `forms` integration.\n -\n - Replaces the generic CnIntegrationTab for the `forms` leaf: renders a\n - list of linked NC Forms forms styled to mirror the real Forms app —\n - each row is an NcListItem with a clipboard/form icon, the bold form\n - title, an optional description, a response-count bubble\n - (NcCounterBubble), an open/closed/draft state chip (CnStatusBadge),\n - and an expiry date (NcDateTime) when the form has one. Per-row \"Open\n - in Forms\" + unlink live in the row actions menu, with Tier-2\n - link/unlink + create-new actions on the header.\n -\n - Talks to the OpenRegister Tier-2 forms link surface:\n - GET /api/objects/{r}/{s}/{id}/forms — list linked\n - POST /api/objects/{r}/{s}/{id}/forms — link existing\n - POST /api/objects/{r}/{s}/{id}/forms/new — create + link\n - DELETE /api/objects/{r}/{s}/{id}/forms/{id} — unlink form\n -\n - The Tier-2 endpoint returns canonical form rows enriched with\n - submission roll-ups; pre-Tier-2 forms (still on the marker scan)\n - keep working through the same endpoint via the provider's\n - backwards-compat fallback.\n -\n - Per ADR-004 (modal isolation) the picker + create modals live in\n - their own .vue files (CnFormPicker, CnFormCreate) and are wired\n - in here rather than inlined.\n -\n - @spec openspec/changes/integration-forms/specs/integrations/forms/spec.md\n -->\n<template>\n\t<div class=\"cn-sidebar-tab cn-forms-tab\">\n\t\t<div class=\"cn-forms-tab__header\">\n\t\t\t<NcButton\n\t\t\t\tvariant=\"primary\"\n\t\t\t\t:aria-label=\"linkExistingLabel\"\n\t\t\t\t@click=\"showPicker = true\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<LinkVariantPlus :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ linkExistingLabel }}\n\t\t\t</NcButton>\n\t\t\t<NcButton\n\t\t\t\t:aria-label=\"createNewLabel\"\n\t\t\t\t@click=\"showCreate = true\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<Plus :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ createNewLabel }}\n\t\t\t</NcButton>\n\t\t</div>\n\n\t\t<div v-if=\"degraded\" class=\"cn-forms-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<NcLoadingIcon v-if=\"loading\" />\n\t\t<div v-else-if=\"error\" class=\"cn-forms-tab__error\" role=\"alert\">\n\t\t\t{{ error }}\n\t\t</div>\n\t\t<div v-else-if=\"forms.length === 0\" class=\"cn-sidebar-tab__empty cn-forms-tab__empty\">\n\t\t\t<ClipboardText :size=\"32\" class=\"cn-forms-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=\"formsAppUrl\"\n\t\t\t\ttarget=\"_blank\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<ClipboardText :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ openFormsLabel }}\n\t\t\t</NcButton>\n\t\t</div>\n\t\t<ul v-else class=\"cn-forms-tab__list\">\n\t\t\t<NcListItem\n\t\t\t\tv-for=\"form in forms\"\n\t\t\t\t:key=\"formKey(form)\"\n\t\t\t\tclass=\"cn-forms-tab__row\"\n\t\t\t\t:class=\"{ 'cn-forms-tab__row--closed': isClosed(form) }\"\n\t\t\t\t:name=\"formTitle(form)\"\n\t\t\t\t:bold=\"false\"\n\t\t\t\t:href=\"formUrl(form)\"\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<ClipboardText :size=\"40\" class=\"cn-forms-tab__row-icon\" />\n\t\t\t\t</template>\n\t\t\t\t<template v-if=\"formDescription(form)\" #subname>\n\t\t\t\t\t<span class=\"cn-forms-tab__description\">{{ formDescription(form) }}</span>\n\t\t\t\t</template>\n\t\t\t\t<template #details>\n\t\t\t\t\t<div class=\"cn-forms-tab__details\">\n\t\t\t\t\t\t<CnStatusBadge\n\t\t\t\t\t\t\tclass=\"cn-forms-tab__status\"\n\t\t\t\t\t\t\t:class=\"statusClass(form)\"\n\t\t\t\t\t\t\t:label=\"statusLabel(form)\"\n\t\t\t\t\t\t\t:variant=\"statusVariant(form)\"\n\t\t\t\t\t\t\tsize=\"small\" />\n\t\t\t\t\t\t<NcDateTime\n\t\t\t\t\t\t\tv-if=\"expiresAtMs(form) !== null\"\n\t\t\t\t\t\t\tclass=\"cn-forms-tab__expiry\"\n\t\t\t\t\t\t\t:timestamp=\"expiresAtMs(form)\"\n\t\t\t\t\t\t\trelativeTime=\"short\" />\n\t\t\t\t\t</div>\n\t\t\t\t</template>\n\t\t\t\t<template v-if=\"submissionCount(form) > 0\" #indicator>\n\t\t\t\t\t<NcCounterBubble\n\t\t\t\t\t\tclass=\"cn-forms-tab__count\"\n\t\t\t\t\t\t:aria-label=\"responsesAriaLabel(form)\">\n\t\t\t\t\t\t{{ submissionCount(form) }}\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=\"formUrl(form)\"\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{{ openInFormsLabel }}\n\t\t\t\t\t</NcActionLink>\n\t\t\t\t\t<NcActionButton\n\t\t\t\t\t\tclass=\"cn-forms-tab__unlink\"\n\t\t\t\t\t\t:closeAfterClick=\"true\"\n\t\t\t\t\t\t@click=\"unlink(form)\">\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{{ unlinkLabel }}\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<CnFormPicker\n\t\t\tv-if=\"showPicker\"\n\t\t\t:objectId=\"objectId\"\n\t\t\t:register=\"register\"\n\t\t\t:schema=\"schema\"\n\t\t\t:apiBase=\"apiBase\"\n\t\t\t@link=\"onLinkSelected\"\n\t\t\t@close=\"showPicker = false\" />\n\n\t\t<CnFormCreate\n\t\t\tv-if=\"showCreate\"\n\t\t\t:submitting=\"creating\"\n\t\t\t:submitError=\"createError\"\n\t\t\t@create=\"onCreateSubmit\"\n\t\t\t@close=\"onCreateClose\" />\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcActionButton, NcActionLink, NcButton, NcCounterBubble, NcDateTime, NcListItem, NcLoadingIcon } from '@nextcloud/vue'\nimport AlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue'\nimport ClipboardText from 'vue-material-design-icons/ClipboardText.vue'\nimport Close from 'vue-material-design-icons/Close.vue'\nimport LinkVariantPlus from 'vue-material-design-icons/LinkVariantPlus.vue'\nimport OpenInNew from 'vue-material-design-icons/OpenInNew.vue'\nimport Plus from 'vue-material-design-icons/Plus.vue'\nimport CnStatusBadge from '../../../components/CnStatusBadge/CnStatusBadge.vue'\nimport CnFormCreate from './CnFormCreate.vue'\nimport CnFormPicker from './CnFormPicker.vue'\nimport { buildHeaders, prefixUrl, safeHref } from '../../../utils/index.js'\n\n/**\n * CnFormsTab — bespoke linked-forms list for the Tier-2 `forms` leaf.\n *\n * Renders rows pulled from the OR Tier-2 forms link endpoint, styled to\n * mirror NC Forms (form icon, bold title, description, response-count\n * bubble, state chip, expiry date), with inline \"Link existing form\" +\n * \"Create new form\" actions on the header and per-row open/unlink in the\n * row actions menu.\n */\nexport default {\n\tname: 'CnFormsTab',\n\n\tcomponents: {\n\t\tNcActionButton,\n\t\tNcActionLink,\n\t\tNcButton,\n\t\tNcCounterBubble,\n\t\tNcDateTime,\n\t\tNcListItem,\n\t\tNcLoadingIcon,\n\t\tAlertCircleOutline,\n\t\tClipboardText,\n\t\tClose,\n\t\tLinkVariantPlus,\n\t\tOpenInNew,\n\t\tPlus,\n\t\tCnStatusBadge,\n\t\tCnFormCreate,\n\t\tCnFormPicker,\n\t},\n\n\tprops: {\n\t\t/* eslint-disable vue/no-unused-properties -- the integration dispatch binds integrationId on every integration component (see CnIntegrationWidgetGrid), so declaring it keeps it out of $attrs */\n\t\t/** Stable integration id (forwarded from the registry — always `'forms'`). */\n\t\tintegrationId: { type: String, default: 'forms' },\n\t\t/* eslint-enable vue/no-unused-properties */\n\t\t/** Parent object id. */\n\t\tobjectId: { type: String, required: true },\n\t\t/** OpenRegister register id (slug or uuid). */\n\t\tregister: { type: String, default: '' },\n\t\t/** OpenRegister schema id (slug or uuid). */\n\t\tschema: { type: String, default: '' },\n\t\t/** Base API URL. */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\t\t/** Pre-translated empty-state label. */\n\t\temptyLabel: { type: String, default: () => t('nextcloud-vue', 'No forms linked yet') },\n\t\t/** Pre-translated label for the \"Open in Forms\" CTA. */\n\t\topenFormsLabel: { type: String, default: () => t('nextcloud-vue', 'Open Forms') },\n\t\t/** Pre-translated label for the per-row \"Open in Forms\" action. */\n\t\topenInFormsLabel: { type: String, default: () => t('nextcloud-vue', 'Open in Forms') },\n\t\t/** Pre-translated banner when Forms is unavailable. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Forms is currently unavailable.') },\n\t\t/** Pre-translated label for the link-existing action. */\n\t\tlinkExistingLabel: { type: String, default: () => t('nextcloud-vue', 'Link existing form') },\n\t\t/** Pre-translated label for the create-new action. */\n\t\tcreateNewLabel: { type: String, default: () => t('nextcloud-vue', 'Create new form') },\n\t\t/** Pre-translated label for the per-row unlink action. */\n\t\tunlinkLabel: { type: String, default: () => t('nextcloud-vue', 'Unlink form') },\n\t\t/** URL of the NC Forms app entry. */\n\t\tformsAppUrl: { type: String, default: '/index.php/apps/forms' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\trawRows: [],\n\t\t\tloading: false,\n\t\t\terror: '',\n\t\t\tdegraded: '',\n\t\t\tshowPicker: false,\n\t\t\tshowCreate: false,\n\t\t\tcreating: false,\n\t\t\tcreateError: '',\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Group provider rows into a per-form list with attached\n\t\t * submissions. The Tier-2 endpoint already emits per-form rows\n\t\t * with `submissionCount` rolled up; this method also handles\n\t\t * the legacy flat shape (rows with `type: 'submission'`) for\n\t\t * forms that pre-date the link table.\n\t\t *\n\t\t * @return {object[]} Per-form display rows.\n\t\t */\n\t\tforms() {\n\t\t\tconst out = []\n\t\t\tconst byId = new Map()\n\t\t\tfor (const row of this.rawRows) {\n\t\t\t\tif (!row || typeof row !== 'object') {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif ((row.type ?? 'form') === 'submission') {\n\t\t\t\t\tconst formId = String(row.data?.formId ?? '')\n\t\t\t\t\tif (byId.has(formId) === true) {\n\t\t\t\t\t\tconst target = byId.get(formId)\n\t\t\t\t\t\ttarget.submissions = (target.submissions || 0) + 1\n\t\t\t\t\t\tif (row.lastUpdated && (!target.lastSubmissionAt || row.lastUpdated > target.lastSubmissionAt)) {\n\t\t\t\t\t\t\ttarget.lastSubmissionAt = row.lastUpdated\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tconst normalised = this.normaliseFormRow(row)\n\t\t\t\tbyId.set(String(normalised.id), normalised)\n\t\t\t\tout.push(normalised)\n\t\t\t}\n\t\t\treturn out\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.fetchForms()\n\t\t\t}\n\t\t} },\n\n\t\tregister() {\n\t\t\tthis.fetchForms()\n\t\t},\n\n\t\tschema() {\n\t\t\tthis.fetchForms()\n\t\t},\n\t},\n\n\tmethods: {\n\t\tbaseUrl() {\n\t\t\treturn prefixUrl(`${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/forms`)\n\t\t},\n\n\t\tnormaliseFormRow(row) {\n\t\t\tconst data = row.data || {}\n\t\t\treturn {\n\t\t\t\tid: row.id ?? row.formId ?? data.id ?? '',\n\t\t\t\thash: data.hash ?? row.formHash ?? row.hash ?? '',\n\t\t\t\ttitle: row.title ?? data.title ?? '',\n\t\t\t\tdescription: row.description ?? data.description ?? '',\n\t\t\t\turl: row.url ?? '',\n\t\t\t\tlastUpdated: row.lastUpdated ?? data.lastUpdated ?? null,\n\t\t\t\tstatus: row.status ?? data.status ?? null,\n\t\t\t\texpiresAt: row.expiresAt ?? data.expiresAt ?? null,\n\t\t\t\taccessible: row.accessible ?? data.accessible ?? null,\n\t\t\t\tsubmissionCount: Number(row.submissionCount ?? data.submissionCount ?? 0) || 0,\n\t\t\t\tsubmissions: 0,\n\t\t\t\tlastSubmissionAt: null,\n\t\t\t}\n\t\t},\n\n\t\tformKey(form) {\n\t\t\treturn form.id ?? form.hash ?? ''\n\t\t},\n\n\t\tformTitle(form) {\n\t\t\treturn form.title || form.id || t('nextcloud-vue', 'Untitled form')\n\t\t},\n\n\t\tformDescription(form) {\n\t\t\treturn form.description || ''\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\tformUrl(form) {\n\t\t\tif (form.url && safeHref(form.url) !== '#') {\n\t\t\t\treturn form.url\n\t\t\t}\n\t\t\tif (form.hash) {\n\t\t\t\treturn `/index.php/apps/forms/${form.hash}`\n\t\t\t}\n\t\t\treturn this.formsAppUrl\n\t\t},\n\n\t\t/**\n\t\t * Resolve the form's expiry as a millisecond epoch, or null when\n\t\t * the form never expires. Accepts both second- and millisecond-\n\t\t * granularity numbers and ISO strings.\n\t\t *\n\t\t * @param {object} form normalised form row\n\t\t *\n\t\t * @return {?number}\n\t\t */\n\t\texpiresAtMs(form) {\n\t\t\tconst v = form.expiresAt\n\t\t\tif (v === null || v === undefined || v === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tif (typeof v === 'number') {\n\t\t\t\treturn v < 1e12 ? v * 1000 : v\n\t\t\t}\n\t\t\tconst parsed = Date.parse(String(v))\n\t\t\treturn Number.isNaN(parsed) === true ? null : parsed\n\t\t},\n\n\t\tisClosed(form) {\n\t\t\tif (form.status === 'closed' || form.status === 'archived') {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\tconst ms = this.expiresAtMs(form)\n\t\t\tif (ms !== null) {\n\t\t\t\treturn ms <= Date.now()\n\t\t\t}\n\t\t\treturn false\n\t\t},\n\n\t\tstatusLabel(form) {\n\t\t\tif (this.isClosed(form) === true) {\n\t\t\t\treturn t('nextcloud-vue', 'Closed')\n\t\t\t}\n\t\t\tif (form.status === 'draft') {\n\t\t\t\treturn t('nextcloud-vue', 'Draft')\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', 'Open')\n\t\t},\n\n\t\tstatusClass(form) {\n\t\t\tif (this.isClosed(form) === true) {\n\t\t\t\treturn 'cn-forms-tab__status--closed'\n\t\t\t}\n\t\t\tif (form.status === 'draft') {\n\t\t\t\treturn 'cn-forms-tab__status--draft'\n\t\t\t}\n\t\t\treturn 'cn-forms-tab__status--open'\n\t\t},\n\n\t\t/**\n\t\t * Map the form's state to a CnStatusBadge colour variant —\n\t\t * open = success (green), draft = warning, closed = default.\n\t\t *\n\t\t * @param {object} form normalised form row\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tstatusVariant(form) {\n\t\t\tif (this.isClosed(form) === true) {\n\t\t\t\treturn 'default'\n\t\t\t}\n\t\t\tif (form.status === 'draft') {\n\t\t\t\treturn 'warning'\n\t\t\t}\n\t\t\treturn 'success'\n\t\t},\n\n\t\tsubmissionCount(form) {\n\t\t\tif (form.submissionCount > 0) {\n\t\t\t\treturn form.submissionCount\n\t\t\t}\n\t\t\treturn form.submissions || 0\n\t\t},\n\n\t\tresponsesAriaLabel(form) {\n\t\t\treturn t('nextcloud-vue', '{n} submissions', { n: this.submissionCount(form) })\n\t\t},\n\n\t\tasync fetchForms() {\n\t\t\tif (!this.register || !this.schema || !this.objectId) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\tthis.error = ''\n\t\t\tthis.degraded = ''\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(this.baseUrl(), { headers: buildHeaders() })\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tconst data = await response.json()\n\t\t\t\t\tthis.rawRows = this.unwrapList(data)\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.rawRows = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.rawRows = []\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load forms.')\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('[CnFormsTab] failed to fetch forms', err)\n\t\t\t\tthis.rawRows = []\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load forms.')\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Canonical wrapper-key cascade — mirrors CnContactsTab.unwrapList.\n\t\t *\n\t\t * @param {object|Array<object>|null} data parsed JSON response body\n\t\t *\n\t\t * @return {Array}\n\t\t */\n\t\tunwrapList(data) {\n\t\t\tif (Array.isArray(data)) {\n\t\t\t\treturn data\n\t\t\t}\n\t\t\tif (data && typeof data === 'object') {\n\t\t\t\tif (Array.isArray(data.results)) {\n\t\t\t\t\treturn data.results\n\t\t\t\t}\n\t\t\t\tif (Array.isArray(data.items)) {\n\t\t\t\t\treturn data.items\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn []\n\t\t},\n\n\t\tasync onLinkSelected(payload) {\n\t\t\tif (!payload || !payload.formId) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(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(payload),\n\t\t\t\t})\n\t\t\t\tif (!response.ok) {\n\t\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\t\tconsole.error('[CnFormsTab] link failed', response.status, response.statusText)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tthis.showPicker = false\n\t\t\t\tawait this.fetchForms()\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('[CnFormsTab] link error', err)\n\t\t\t}\n\t\t},\n\n\t\tasync onCreateSubmit(payload) {\n\t\t\tif (!payload || !payload.title) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.creating = true\n\t\t\tthis.createError = ''\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(`${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\tconst data = await response.json().catch(() => ({}))\n\t\t\t\t\tthis.createError = data?.error || `${response.status} ${response.statusText}`\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tthis.showCreate = false\n\t\t\t\tawait this.fetchForms()\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('[CnFormsTab] create error', err)\n\t\t\t\tthis.createError = String(err?.message || err)\n\t\t\t} finally {\n\t\t\t\tthis.creating = false\n\t\t\t}\n\t\t},\n\n\t\tonCreateClose() {\n\t\t\tthis.showCreate = false\n\t\t\tthis.createError = ''\n\t\t},\n\n\t\tasync unlink(form) {\n\t\t\tif (!form?.id) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(`${this.baseUrl()}/${encodeURIComponent(form.id)}`, {\n\t\t\t\t\tmethod: 'DELETE',\n\t\t\t\t\theaders: buildHeaders(),\n\t\t\t\t})\n\t\t\t\tif (!response.ok) {\n\t\t\t\t\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\t\tconsole.error('[CnFormsTab] unlink failed', response.status, response.statusText)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tthis.rawRows = this.rawRows.filter((r) => String(r.id ?? r.formId ?? '') !== String(form.id))\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('[CnFormsTab] unlink error', err)\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-forms-tab {\n\tpadding: 12px;\n}\n\n.cn-forms-tab__header {\n\tdisplay: flex;\n\tgap: 8px;\n\tjustify-content: flex-end;\n\tmargin-bottom: 10px;\n\tflex-wrap: wrap;\n}\n\n.cn-forms-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-forms-tab__error {\n\tcolor: var(--color-error);\n\tfont-size: 0.9em;\n\tmargin: 4px 0 8px;\n}\n\n.cn-forms-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-forms-tab__empty-icon {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-forms-tab__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n/* Forms brand accent on the row icon, mirroring the real Forms app. */\n.cn-forms-tab__row-icon {\n\tcolor: var(--cn-forms-accent, #0d7000);\n\tflex-shrink: 0;\n}\n\n.cn-forms-tab__row--closed {\n\topacity: 0.85;\n}\n\n.cn-forms-tab__row--closed .cn-forms-tab__row-icon {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-forms-tab__description {\n\tdisplay: -webkit-box;\n\t-webkit-line-clamp: 2;\n\t-webkit-box-orient: vertical;\n\toverflow: hidden;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-forms-tab__details {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 6px;\n\tflex-wrap: wrap;\n\tjustify-content: flex-end;\n}\n\n.cn-forms-tab__expiry {\n\tfont-size: 0.8em;\n\tcolor: var(--color-text-maxcontrast);\n\twhite-space: nowrap;\n}\n\n/* Status chip colours layered on top of CnStatusBadge variants so the\n open/closed/draft state reads at a glance, like the real Forms app. */\n.cn-forms-tab__status--open {\n\t--cn-forms-status: var(--color-success, #46ba61);\n}\n\n.cn-forms-tab__status--closed {\n\t--cn-forms-status: var(--color-text-maxcontrast);\n}\n\n.cn-forms-tab__status--draft {\n\t--cn-forms-status: var(--color-warning, #e9a40f);\n}\n</style>\n"],"names":["CnStatusBadge","CnFormCreate","CnFormPicker","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.formsAppUrl,
92
- target: "_blank",
93
- rel: "noopener noreferrer"
92
+ target: "_blank"
94
93
  }, {
95
94
  icon: withCtx(() => [
96
95
  createVNode(_component_ClipboardText, { size: 20 })
@@ -140,8 +139,7 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
140
139
  createVNode(_component_NcActionLink, {
141
140
  closeAfterClick: true,
142
141
  href: $options.formUrl(form),
143
- target: "_blank",
144
- rel: "noopener noreferrer"
142
+ target: "_blank"
145
143
  }, {
146
144
  icon: withCtx(() => [
147
145
  createVNode(_component_OpenInNew, { size: 20 })
@@ -1 +1 @@
1
- {"version":3,"file":"CnFormsTab.vue3.js","sources":["../../../../../src/integrations/builtin/forms/CnFormsTab.vue?vue&type=template&id=ea0d6578&lang.js"],"sourcesContent":["<!--\n - SPDX-License-Identifier: EUPL-1.2\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n -\n - CnFormsTab — bespoke sidebar tab for the Tier-2 `forms` integration.\n -\n - Replaces the generic CnIntegrationTab for the `forms` leaf: renders a\n - list of linked NC Forms forms styled to mirror the real Forms app —\n - each row is an NcListItem with a clipboard/form icon, the bold form\n - title, an optional description, a response-count bubble\n - (NcCounterBubble), an open/closed/draft state chip (CnStatusBadge),\n - and an expiry date (NcDateTime) when the form has one. Per-row \"Open\n - in Forms\" + unlink live in the row actions menu, with Tier-2\n - link/unlink + create-new actions on the header.\n -\n - Talks to the OpenRegister Tier-2 forms link surface:\n - GET /api/objects/{r}/{s}/{id}/forms — list linked\n - POST /api/objects/{r}/{s}/{id}/forms — link existing\n - POST /api/objects/{r}/{s}/{id}/forms/new — create + link\n - DELETE /api/objects/{r}/{s}/{id}/forms/{id} — unlink form\n -\n - The Tier-2 endpoint returns canonical form rows enriched with\n - submission roll-ups; pre-Tier-2 forms (still on the marker scan)\n - keep working through the same endpoint via the provider's\n - backwards-compat fallback.\n -\n - Per ADR-004 (modal isolation) the picker + create modals live in\n - their own .vue files (CnFormPicker, CnFormCreate) and are wired\n - in here rather than inlined.\n -\n - @spec openspec/changes/integration-forms/specs/integrations/forms/spec.md\n -->\n<template>\n\t<div class=\"cn-sidebar-tab cn-forms-tab\">\n\t\t<div class=\"cn-forms-tab__header\">\n\t\t\t<NcButton\n\t\t\t\tvariant=\"primary\"\n\t\t\t\t:aria-label=\"linkExistingLabel\"\n\t\t\t\t@click=\"showPicker = true\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<LinkVariantPlus :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ linkExistingLabel }}\n\t\t\t</NcButton>\n\t\t\t<NcButton\n\t\t\t\t:aria-label=\"createNewLabel\"\n\t\t\t\t@click=\"showCreate = true\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<Plus :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ createNewLabel }}\n\t\t\t</NcButton>\n\t\t</div>\n\n\t\t<div v-if=\"degraded\" class=\"cn-forms-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<NcLoadingIcon v-if=\"loading\" />\n\t\t<div v-else-if=\"error\" class=\"cn-forms-tab__error\" role=\"alert\">\n\t\t\t{{ error }}\n\t\t</div>\n\t\t<div v-else-if=\"forms.length === 0\" class=\"cn-sidebar-tab__empty cn-forms-tab__empty\">\n\t\t\t<ClipboardText :size=\"32\" class=\"cn-forms-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=\"formsAppUrl\"\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<ClipboardText :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ openFormsLabel }}\n\t\t\t</NcButton>\n\t\t</div>\n\t\t<ul v-else class=\"cn-forms-tab__list\">\n\t\t\t<NcListItem\n\t\t\t\tv-for=\"form in forms\"\n\t\t\t\t:key=\"formKey(form)\"\n\t\t\t\tclass=\"cn-forms-tab__row\"\n\t\t\t\t:class=\"{ 'cn-forms-tab__row--closed': isClosed(form) }\"\n\t\t\t\t:name=\"formTitle(form)\"\n\t\t\t\t:bold=\"false\"\n\t\t\t\t:href=\"formUrl(form)\"\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<ClipboardText :size=\"40\" class=\"cn-forms-tab__row-icon\" />\n\t\t\t\t</template>\n\t\t\t\t<template v-if=\"formDescription(form)\" #subname>\n\t\t\t\t\t<span class=\"cn-forms-tab__description\">{{ formDescription(form) }}</span>\n\t\t\t\t</template>\n\t\t\t\t<template #details>\n\t\t\t\t\t<div class=\"cn-forms-tab__details\">\n\t\t\t\t\t\t<CnStatusBadge\n\t\t\t\t\t\t\tclass=\"cn-forms-tab__status\"\n\t\t\t\t\t\t\t:class=\"statusClass(form)\"\n\t\t\t\t\t\t\t:label=\"statusLabel(form)\"\n\t\t\t\t\t\t\t:variant=\"statusVariant(form)\"\n\t\t\t\t\t\t\tsize=\"small\" />\n\t\t\t\t\t\t<NcDateTime\n\t\t\t\t\t\t\tv-if=\"expiresAtMs(form) !== null\"\n\t\t\t\t\t\t\tclass=\"cn-forms-tab__expiry\"\n\t\t\t\t\t\t\t:timestamp=\"expiresAtMs(form)\"\n\t\t\t\t\t\t\trelativeTime=\"short\" />\n\t\t\t\t\t</div>\n\t\t\t\t</template>\n\t\t\t\t<template v-if=\"submissionCount(form) > 0\" #indicator>\n\t\t\t\t\t<NcCounterBubble\n\t\t\t\t\t\tclass=\"cn-forms-tab__count\"\n\t\t\t\t\t\t:aria-label=\"responsesAriaLabel(form)\">\n\t\t\t\t\t\t{{ submissionCount(form) }}\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=\"formUrl(form)\"\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{{ openInFormsLabel }}\n\t\t\t\t\t</NcActionLink>\n\t\t\t\t\t<NcActionButton\n\t\t\t\t\t\tclass=\"cn-forms-tab__unlink\"\n\t\t\t\t\t\t:closeAfterClick=\"true\"\n\t\t\t\t\t\t@click=\"unlink(form)\">\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{{ unlinkLabel }}\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<CnFormPicker\n\t\t\tv-if=\"showPicker\"\n\t\t\t:objectId=\"objectId\"\n\t\t\t:register=\"register\"\n\t\t\t:schema=\"schema\"\n\t\t\t:apiBase=\"apiBase\"\n\t\t\t@link=\"onLinkSelected\"\n\t\t\t@close=\"showPicker = false\" />\n\n\t\t<CnFormCreate\n\t\t\tv-if=\"showCreate\"\n\t\t\t:submitting=\"creating\"\n\t\t\t:submitError=\"createError\"\n\t\t\t@create=\"onCreateSubmit\"\n\t\t\t@close=\"onCreateClose\" />\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcActionButton, NcActionLink, NcButton, NcCounterBubble, NcDateTime, NcListItem, NcLoadingIcon } from '@nextcloud/vue'\nimport AlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue'\nimport ClipboardText from 'vue-material-design-icons/ClipboardText.vue'\nimport Close from 'vue-material-design-icons/Close.vue'\nimport LinkVariantPlus from 'vue-material-design-icons/LinkVariantPlus.vue'\nimport OpenInNew from 'vue-material-design-icons/OpenInNew.vue'\nimport Plus from 'vue-material-design-icons/Plus.vue'\nimport CnStatusBadge from '../../../components/CnStatusBadge/CnStatusBadge.vue'\nimport CnFormCreate from './CnFormCreate.vue'\nimport CnFormPicker from './CnFormPicker.vue'\nimport { buildHeaders, prefixUrl } from '../../../utils/index.js'\n\n/**\n * CnFormsTab — bespoke linked-forms list for the Tier-2 `forms` leaf.\n *\n * Renders rows pulled from the OR Tier-2 forms link endpoint, styled to\n * mirror NC Forms (form icon, bold title, description, response-count\n * bubble, state chip, expiry date), with inline \"Link existing form\" +\n * \"Create new form\" actions on the header and per-row open/unlink in the\n * row actions menu.\n */\nexport default {\n\tname: 'CnFormsTab',\n\n\tcomponents: {\n\t\tNcActionButton,\n\t\tNcActionLink,\n\t\tNcButton,\n\t\tNcCounterBubble,\n\t\tNcDateTime,\n\t\tNcListItem,\n\t\tNcLoadingIcon,\n\t\tAlertCircleOutline,\n\t\tClipboardText,\n\t\tClose,\n\t\tLinkVariantPlus,\n\t\tOpenInNew,\n\t\tPlus,\n\t\tCnStatusBadge,\n\t\tCnFormCreate,\n\t\tCnFormPicker,\n\t},\n\n\tprops: {\n\t\t/* eslint-disable vue/no-unused-properties -- the integration dispatch binds integrationId on every integration component (see CnIntegrationWidgetGrid), so declaring it keeps it out of $attrs */\n\t\t/** Stable integration id (forwarded from the registry — always `'forms'`). */\n\t\tintegrationId: { type: String, default: 'forms' },\n\t\t/* eslint-enable vue/no-unused-properties */\n\t\t/** Parent object id. */\n\t\tobjectId: { type: String, required: true },\n\t\t/** OpenRegister register id (slug or uuid). */\n\t\tregister: { type: String, default: '' },\n\t\t/** OpenRegister schema id (slug or uuid). */\n\t\tschema: { type: String, default: '' },\n\t\t/** Base API URL. */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\t\t/** Pre-translated empty-state label. */\n\t\temptyLabel: { type: String, default: () => t('nextcloud-vue', 'No forms linked yet') },\n\t\t/** Pre-translated label for the \"Open in Forms\" CTA. */\n\t\topenFormsLabel: { type: String, default: () => t('nextcloud-vue', 'Open Forms') },\n\t\t/** Pre-translated label for the per-row \"Open in Forms\" action. */\n\t\topenInFormsLabel: { type: String, default: () => t('nextcloud-vue', 'Open in Forms') },\n\t\t/** Pre-translated banner when Forms is unavailable. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Forms is currently unavailable.') },\n\t\t/** Pre-translated label for the link-existing action. */\n\t\tlinkExistingLabel: { type: String, default: () => t('nextcloud-vue', 'Link existing form') },\n\t\t/** Pre-translated label for the create-new action. */\n\t\tcreateNewLabel: { type: String, default: () => t('nextcloud-vue', 'Create new form') },\n\t\t/** Pre-translated label for the per-row unlink action. */\n\t\tunlinkLabel: { type: String, default: () => t('nextcloud-vue', 'Unlink form') },\n\t\t/** URL of the NC Forms app entry. */\n\t\tformsAppUrl: { type: String, default: '/index.php/apps/forms' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\trawRows: [],\n\t\t\tloading: false,\n\t\t\terror: '',\n\t\t\tdegraded: '',\n\t\t\tshowPicker: false,\n\t\t\tshowCreate: false,\n\t\t\tcreating: false,\n\t\t\tcreateError: '',\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Group provider rows into a per-form list with attached\n\t\t * submissions. The Tier-2 endpoint already emits per-form rows\n\t\t * with `submissionCount` rolled up; this method also handles\n\t\t * the legacy flat shape (rows with `type: 'submission'`) for\n\t\t * forms that pre-date the link table.\n\t\t *\n\t\t * @return {object[]} Per-form display rows.\n\t\t */\n\t\tforms() {\n\t\t\tconst out = []\n\t\t\tconst byId = new Map()\n\t\t\tfor (const row of this.rawRows) {\n\t\t\t\tif (!row || typeof row !== 'object') {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif ((row.type ?? 'form') === 'submission') {\n\t\t\t\t\tconst formId = String(row.data?.formId ?? '')\n\t\t\t\t\tif (byId.has(formId) === true) {\n\t\t\t\t\t\tconst target = byId.get(formId)\n\t\t\t\t\t\ttarget.submissions = (target.submissions || 0) + 1\n\t\t\t\t\t\tif (row.lastUpdated && (!target.lastSubmissionAt || row.lastUpdated > target.lastSubmissionAt)) {\n\t\t\t\t\t\t\ttarget.lastSubmissionAt = row.lastUpdated\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tconst normalised = this.normaliseFormRow(row)\n\t\t\t\tbyId.set(String(normalised.id), normalised)\n\t\t\t\tout.push(normalised)\n\t\t\t}\n\t\t\treturn out\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.fetchForms()\n\t\t\t}\n\t\t} },\n\n\t\tregister() {\n\t\t\tthis.fetchForms()\n\t\t},\n\n\t\tschema() {\n\t\t\tthis.fetchForms()\n\t\t},\n\t},\n\n\tmethods: {\n\t\tbaseUrl() {\n\t\t\treturn prefixUrl(`${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/forms`)\n\t\t},\n\n\t\tnormaliseFormRow(row) {\n\t\t\tconst data = row.data || {}\n\t\t\treturn {\n\t\t\t\tid: row.id ?? row.formId ?? data.id ?? '',\n\t\t\t\thash: data.hash ?? row.formHash ?? row.hash ?? '',\n\t\t\t\ttitle: row.title ?? data.title ?? '',\n\t\t\t\tdescription: row.description ?? data.description ?? '',\n\t\t\t\turl: row.url ?? '',\n\t\t\t\tlastUpdated: row.lastUpdated ?? data.lastUpdated ?? null,\n\t\t\t\tstatus: row.status ?? data.status ?? null,\n\t\t\t\texpiresAt: row.expiresAt ?? data.expiresAt ?? null,\n\t\t\t\taccessible: row.accessible ?? data.accessible ?? null,\n\t\t\t\tsubmissionCount: Number(row.submissionCount ?? data.submissionCount ?? 0) || 0,\n\t\t\t\tsubmissions: 0,\n\t\t\t\tlastSubmissionAt: null,\n\t\t\t}\n\t\t},\n\n\t\tformKey(form) {\n\t\t\treturn form.id ?? form.hash ?? ''\n\t\t},\n\n\t\tformTitle(form) {\n\t\t\treturn form.title || form.id || t('nextcloud-vue', 'Untitled form')\n\t\t},\n\n\t\tformDescription(form) {\n\t\t\treturn form.description || ''\n\t\t},\n\n\t\tformUrl(form) {\n\t\t\tif (form.url) {\n\t\t\t\treturn form.url\n\t\t\t}\n\t\t\tif (form.hash) {\n\t\t\t\treturn `/index.php/apps/forms/${form.hash}`\n\t\t\t}\n\t\t\treturn this.formsAppUrl\n\t\t},\n\n\t\t/**\n\t\t * Resolve the form's expiry as a millisecond epoch, or null when\n\t\t * the form never expires. Accepts both second- and millisecond-\n\t\t * granularity numbers and ISO strings.\n\t\t *\n\t\t * @param {object} form normalised form row\n\t\t *\n\t\t * @return {?number}\n\t\t */\n\t\texpiresAtMs(form) {\n\t\t\tconst v = form.expiresAt\n\t\t\tif (v === null || v === undefined || v === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tif (typeof v === 'number') {\n\t\t\t\treturn v < 1e12 ? v * 1000 : v\n\t\t\t}\n\t\t\tconst parsed = Date.parse(String(v))\n\t\t\treturn Number.isNaN(parsed) === true ? null : parsed\n\t\t},\n\n\t\tisClosed(form) {\n\t\t\tif (form.status === 'closed' || form.status === 'archived') {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\tconst ms = this.expiresAtMs(form)\n\t\t\tif (ms !== null) {\n\t\t\t\treturn ms <= Date.now()\n\t\t\t}\n\t\t\treturn false\n\t\t},\n\n\t\tstatusLabel(form) {\n\t\t\tif (this.isClosed(form) === true) {\n\t\t\t\treturn t('nextcloud-vue', 'Closed')\n\t\t\t}\n\t\t\tif (form.status === 'draft') {\n\t\t\t\treturn t('nextcloud-vue', 'Draft')\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', 'Open')\n\t\t},\n\n\t\tstatusClass(form) {\n\t\t\tif (this.isClosed(form) === true) {\n\t\t\t\treturn 'cn-forms-tab__status--closed'\n\t\t\t}\n\t\t\tif (form.status === 'draft') {\n\t\t\t\treturn 'cn-forms-tab__status--draft'\n\t\t\t}\n\t\t\treturn 'cn-forms-tab__status--open'\n\t\t},\n\n\t\t/**\n\t\t * Map the form's state to a CnStatusBadge colour variant —\n\t\t * open = success (green), draft = warning, closed = default.\n\t\t *\n\t\t * @param {object} form normalised form row\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tstatusVariant(form) {\n\t\t\tif (this.isClosed(form) === true) {\n\t\t\t\treturn 'default'\n\t\t\t}\n\t\t\tif (form.status === 'draft') {\n\t\t\t\treturn 'warning'\n\t\t\t}\n\t\t\treturn 'success'\n\t\t},\n\n\t\tsubmissionCount(form) {\n\t\t\tif (form.submissionCount > 0) {\n\t\t\t\treturn form.submissionCount\n\t\t\t}\n\t\t\treturn form.submissions || 0\n\t\t},\n\n\t\tresponsesAriaLabel(form) {\n\t\t\treturn t('nextcloud-vue', '{n} submissions', { n: this.submissionCount(form) })\n\t\t},\n\n\t\tasync fetchForms() {\n\t\t\tif (!this.register || !this.schema || !this.objectId) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\tthis.error = ''\n\t\t\tthis.degraded = ''\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(this.baseUrl(), { headers: buildHeaders() })\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tconst data = await response.json()\n\t\t\t\t\tthis.rawRows = this.unwrapList(data)\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.rawRows = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.rawRows = []\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load forms.')\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('[CnFormsTab] failed to fetch forms', err)\n\t\t\t\tthis.rawRows = []\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load forms.')\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Canonical wrapper-key cascade — mirrors CnContactsTab.unwrapList.\n\t\t *\n\t\t * @param {object|Array<object>|null} data parsed JSON response body\n\t\t *\n\t\t * @return {Array}\n\t\t */\n\t\tunwrapList(data) {\n\t\t\tif (Array.isArray(data)) {\n\t\t\t\treturn data\n\t\t\t}\n\t\t\tif (data && typeof data === 'object') {\n\t\t\t\tif (Array.isArray(data.results)) {\n\t\t\t\t\treturn data.results\n\t\t\t\t}\n\t\t\t\tif (Array.isArray(data.items)) {\n\t\t\t\t\treturn data.items\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn []\n\t\t},\n\n\t\tasync onLinkSelected(payload) {\n\t\t\tif (!payload || !payload.formId) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(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(payload),\n\t\t\t\t})\n\t\t\t\tif (!response.ok) {\n\t\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\t\tconsole.error('[CnFormsTab] link failed', response.status, response.statusText)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tthis.showPicker = false\n\t\t\t\tawait this.fetchForms()\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('[CnFormsTab] link error', err)\n\t\t\t}\n\t\t},\n\n\t\tasync onCreateSubmit(payload) {\n\t\t\tif (!payload || !payload.title) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.creating = true\n\t\t\tthis.createError = ''\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(`${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\tconst data = await response.json().catch(() => ({}))\n\t\t\t\t\tthis.createError = data?.error || `${response.status} ${response.statusText}`\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tthis.showCreate = false\n\t\t\t\tawait this.fetchForms()\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('[CnFormsTab] create error', err)\n\t\t\t\tthis.createError = String(err?.message || err)\n\t\t\t} finally {\n\t\t\t\tthis.creating = false\n\t\t\t}\n\t\t},\n\n\t\tonCreateClose() {\n\t\t\tthis.showCreate = false\n\t\t\tthis.createError = ''\n\t\t},\n\n\t\tasync unlink(form) {\n\t\t\tif (!form?.id) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(`${this.baseUrl()}/${encodeURIComponent(form.id)}`, {\n\t\t\t\t\tmethod: 'DELETE',\n\t\t\t\t\theaders: buildHeaders(),\n\t\t\t\t})\n\t\t\t\tif (!response.ok) {\n\t\t\t\t\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\t\tconsole.error('[CnFormsTab] unlink failed', response.status, response.statusText)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tthis.rawRows = this.rawRows.filter((r) => String(r.id ?? r.formId ?? '') !== String(form.id))\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('[CnFormsTab] unlink error', err)\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-forms-tab {\n\tpadding: 12px;\n}\n\n.cn-forms-tab__header {\n\tdisplay: flex;\n\tgap: 8px;\n\tjustify-content: flex-end;\n\tmargin-bottom: 10px;\n\tflex-wrap: wrap;\n}\n\n.cn-forms-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-forms-tab__error {\n\tcolor: var(--color-error);\n\tfont-size: 0.9em;\n\tmargin: 4px 0 8px;\n}\n\n.cn-forms-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-forms-tab__empty-icon {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-forms-tab__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n/* Forms brand accent on the row icon, mirroring the real Forms app. */\n.cn-forms-tab__row-icon {\n\tcolor: var(--cn-forms-accent, #0d7000);\n\tflex-shrink: 0;\n}\n\n.cn-forms-tab__row--closed {\n\topacity: 0.85;\n}\n\n.cn-forms-tab__row--closed .cn-forms-tab__row-icon {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-forms-tab__description {\n\tdisplay: -webkit-box;\n\t-webkit-line-clamp: 2;\n\t-webkit-box-orient: vertical;\n\toverflow: hidden;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-forms-tab__details {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 6px;\n\tflex-wrap: wrap;\n\tjustify-content: flex-end;\n}\n\n.cn-forms-tab__expiry {\n\tfont-size: 0.8em;\n\tcolor: var(--color-text-maxcontrast);\n\twhite-space: nowrap;\n}\n\n/* Status chip colours layered on top of CnStatusBadge variants so the\n open/closed/draft state reads at a glance, like the real Forms app. */\n.cn-forms-tab__status--open {\n\t--cn-forms-status: var(--color-success, #46ba61);\n}\n\n.cn-forms-tab__status--closed {\n\t--cn-forms-status: var(--color-text-maxcontrast);\n}\n\n.cn-forms-tab__status--draft {\n\t--cn-forms-status: var(--color-warning, #e9a40f);\n}\n</style>\n"],"names":["_openBlock","_createElementBlock","_createElementVNode","_createVNode","_createTextVNode","_createBlock","_toDisplayString","_normalizeClass"],"mappings":"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"}
1
+ {"version":3,"file":"CnFormsTab.vue3.js","sources":["../../../../../src/integrations/builtin/forms/CnFormsTab.vue?vue&type=template&id=ea0d6578&lang.js"],"sourcesContent":["<!--\n - SPDX-License-Identifier: EUPL-1.2\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n -\n - CnFormsTab — bespoke sidebar tab for the Tier-2 `forms` integration.\n -\n - Replaces the generic CnIntegrationTab for the `forms` leaf: renders a\n - list of linked NC Forms forms styled to mirror the real Forms app —\n - each row is an NcListItem with a clipboard/form icon, the bold form\n - title, an optional description, a response-count bubble\n - (NcCounterBubble), an open/closed/draft state chip (CnStatusBadge),\n - and an expiry date (NcDateTime) when the form has one. Per-row \"Open\n - in Forms\" + unlink live in the row actions menu, with Tier-2\n - link/unlink + create-new actions on the header.\n -\n - Talks to the OpenRegister Tier-2 forms link surface:\n - GET /api/objects/{r}/{s}/{id}/forms — list linked\n - POST /api/objects/{r}/{s}/{id}/forms — link existing\n - POST /api/objects/{r}/{s}/{id}/forms/new — create + link\n - DELETE /api/objects/{r}/{s}/{id}/forms/{id} — unlink form\n -\n - The Tier-2 endpoint returns canonical form rows enriched with\n - submission roll-ups; pre-Tier-2 forms (still on the marker scan)\n - keep working through the same endpoint via the provider's\n - backwards-compat fallback.\n -\n - Per ADR-004 (modal isolation) the picker + create modals live in\n - their own .vue files (CnFormPicker, CnFormCreate) and are wired\n - in here rather than inlined.\n -\n - @spec openspec/changes/integration-forms/specs/integrations/forms/spec.md\n -->\n<template>\n\t<div class=\"cn-sidebar-tab cn-forms-tab\">\n\t\t<div class=\"cn-forms-tab__header\">\n\t\t\t<NcButton\n\t\t\t\tvariant=\"primary\"\n\t\t\t\t:aria-label=\"linkExistingLabel\"\n\t\t\t\t@click=\"showPicker = true\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<LinkVariantPlus :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ linkExistingLabel }}\n\t\t\t</NcButton>\n\t\t\t<NcButton\n\t\t\t\t:aria-label=\"createNewLabel\"\n\t\t\t\t@click=\"showCreate = true\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<Plus :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ createNewLabel }}\n\t\t\t</NcButton>\n\t\t</div>\n\n\t\t<div v-if=\"degraded\" class=\"cn-forms-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<NcLoadingIcon v-if=\"loading\" />\n\t\t<div v-else-if=\"error\" class=\"cn-forms-tab__error\" role=\"alert\">\n\t\t\t{{ error }}\n\t\t</div>\n\t\t<div v-else-if=\"forms.length === 0\" class=\"cn-sidebar-tab__empty cn-forms-tab__empty\">\n\t\t\t<ClipboardText :size=\"32\" class=\"cn-forms-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=\"formsAppUrl\"\n\t\t\t\ttarget=\"_blank\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<ClipboardText :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ openFormsLabel }}\n\t\t\t</NcButton>\n\t\t</div>\n\t\t<ul v-else class=\"cn-forms-tab__list\">\n\t\t\t<NcListItem\n\t\t\t\tv-for=\"form in forms\"\n\t\t\t\t:key=\"formKey(form)\"\n\t\t\t\tclass=\"cn-forms-tab__row\"\n\t\t\t\t:class=\"{ 'cn-forms-tab__row--closed': isClosed(form) }\"\n\t\t\t\t:name=\"formTitle(form)\"\n\t\t\t\t:bold=\"false\"\n\t\t\t\t:href=\"formUrl(form)\"\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<ClipboardText :size=\"40\" class=\"cn-forms-tab__row-icon\" />\n\t\t\t\t</template>\n\t\t\t\t<template v-if=\"formDescription(form)\" #subname>\n\t\t\t\t\t<span class=\"cn-forms-tab__description\">{{ formDescription(form) }}</span>\n\t\t\t\t</template>\n\t\t\t\t<template #details>\n\t\t\t\t\t<div class=\"cn-forms-tab__details\">\n\t\t\t\t\t\t<CnStatusBadge\n\t\t\t\t\t\t\tclass=\"cn-forms-tab__status\"\n\t\t\t\t\t\t\t:class=\"statusClass(form)\"\n\t\t\t\t\t\t\t:label=\"statusLabel(form)\"\n\t\t\t\t\t\t\t:variant=\"statusVariant(form)\"\n\t\t\t\t\t\t\tsize=\"small\" />\n\t\t\t\t\t\t<NcDateTime\n\t\t\t\t\t\t\tv-if=\"expiresAtMs(form) !== null\"\n\t\t\t\t\t\t\tclass=\"cn-forms-tab__expiry\"\n\t\t\t\t\t\t\t:timestamp=\"expiresAtMs(form)\"\n\t\t\t\t\t\t\trelativeTime=\"short\" />\n\t\t\t\t\t</div>\n\t\t\t\t</template>\n\t\t\t\t<template v-if=\"submissionCount(form) > 0\" #indicator>\n\t\t\t\t\t<NcCounterBubble\n\t\t\t\t\t\tclass=\"cn-forms-tab__count\"\n\t\t\t\t\t\t:aria-label=\"responsesAriaLabel(form)\">\n\t\t\t\t\t\t{{ submissionCount(form) }}\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=\"formUrl(form)\"\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{{ openInFormsLabel }}\n\t\t\t\t\t</NcActionLink>\n\t\t\t\t\t<NcActionButton\n\t\t\t\t\t\tclass=\"cn-forms-tab__unlink\"\n\t\t\t\t\t\t:closeAfterClick=\"true\"\n\t\t\t\t\t\t@click=\"unlink(form)\">\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{{ unlinkLabel }}\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<CnFormPicker\n\t\t\tv-if=\"showPicker\"\n\t\t\t:objectId=\"objectId\"\n\t\t\t:register=\"register\"\n\t\t\t:schema=\"schema\"\n\t\t\t:apiBase=\"apiBase\"\n\t\t\t@link=\"onLinkSelected\"\n\t\t\t@close=\"showPicker = false\" />\n\n\t\t<CnFormCreate\n\t\t\tv-if=\"showCreate\"\n\t\t\t:submitting=\"creating\"\n\t\t\t:submitError=\"createError\"\n\t\t\t@create=\"onCreateSubmit\"\n\t\t\t@close=\"onCreateClose\" />\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcActionButton, NcActionLink, NcButton, NcCounterBubble, NcDateTime, NcListItem, NcLoadingIcon } from '@nextcloud/vue'\nimport AlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue'\nimport ClipboardText from 'vue-material-design-icons/ClipboardText.vue'\nimport Close from 'vue-material-design-icons/Close.vue'\nimport LinkVariantPlus from 'vue-material-design-icons/LinkVariantPlus.vue'\nimport OpenInNew from 'vue-material-design-icons/OpenInNew.vue'\nimport Plus from 'vue-material-design-icons/Plus.vue'\nimport CnStatusBadge from '../../../components/CnStatusBadge/CnStatusBadge.vue'\nimport CnFormCreate from './CnFormCreate.vue'\nimport CnFormPicker from './CnFormPicker.vue'\nimport { buildHeaders, prefixUrl, safeHref } from '../../../utils/index.js'\n\n/**\n * CnFormsTab — bespoke linked-forms list for the Tier-2 `forms` leaf.\n *\n * Renders rows pulled from the OR Tier-2 forms link endpoint, styled to\n * mirror NC Forms (form icon, bold title, description, response-count\n * bubble, state chip, expiry date), with inline \"Link existing form\" +\n * \"Create new form\" actions on the header and per-row open/unlink in the\n * row actions menu.\n */\nexport default {\n\tname: 'CnFormsTab',\n\n\tcomponents: {\n\t\tNcActionButton,\n\t\tNcActionLink,\n\t\tNcButton,\n\t\tNcCounterBubble,\n\t\tNcDateTime,\n\t\tNcListItem,\n\t\tNcLoadingIcon,\n\t\tAlertCircleOutline,\n\t\tClipboardText,\n\t\tClose,\n\t\tLinkVariantPlus,\n\t\tOpenInNew,\n\t\tPlus,\n\t\tCnStatusBadge,\n\t\tCnFormCreate,\n\t\tCnFormPicker,\n\t},\n\n\tprops: {\n\t\t/* eslint-disable vue/no-unused-properties -- the integration dispatch binds integrationId on every integration component (see CnIntegrationWidgetGrid), so declaring it keeps it out of $attrs */\n\t\t/** Stable integration id (forwarded from the registry — always `'forms'`). */\n\t\tintegrationId: { type: String, default: 'forms' },\n\t\t/* eslint-enable vue/no-unused-properties */\n\t\t/** Parent object id. */\n\t\tobjectId: { type: String, required: true },\n\t\t/** OpenRegister register id (slug or uuid). */\n\t\tregister: { type: String, default: '' },\n\t\t/** OpenRegister schema id (slug or uuid). */\n\t\tschema: { type: String, default: '' },\n\t\t/** Base API URL. */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\t\t/** Pre-translated empty-state label. */\n\t\temptyLabel: { type: String, default: () => t('nextcloud-vue', 'No forms linked yet') },\n\t\t/** Pre-translated label for the \"Open in Forms\" CTA. */\n\t\topenFormsLabel: { type: String, default: () => t('nextcloud-vue', 'Open Forms') },\n\t\t/** Pre-translated label for the per-row \"Open in Forms\" action. */\n\t\topenInFormsLabel: { type: String, default: () => t('nextcloud-vue', 'Open in Forms') },\n\t\t/** Pre-translated banner when Forms is unavailable. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Forms is currently unavailable.') },\n\t\t/** Pre-translated label for the link-existing action. */\n\t\tlinkExistingLabel: { type: String, default: () => t('nextcloud-vue', 'Link existing form') },\n\t\t/** Pre-translated label for the create-new action. */\n\t\tcreateNewLabel: { type: String, default: () => t('nextcloud-vue', 'Create new form') },\n\t\t/** Pre-translated label for the per-row unlink action. */\n\t\tunlinkLabel: { type: String, default: () => t('nextcloud-vue', 'Unlink form') },\n\t\t/** URL of the NC Forms app entry. */\n\t\tformsAppUrl: { type: String, default: '/index.php/apps/forms' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\trawRows: [],\n\t\t\tloading: false,\n\t\t\terror: '',\n\t\t\tdegraded: '',\n\t\t\tshowPicker: false,\n\t\t\tshowCreate: false,\n\t\t\tcreating: false,\n\t\t\tcreateError: '',\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Group provider rows into a per-form list with attached\n\t\t * submissions. The Tier-2 endpoint already emits per-form rows\n\t\t * with `submissionCount` rolled up; this method also handles\n\t\t * the legacy flat shape (rows with `type: 'submission'`) for\n\t\t * forms that pre-date the link table.\n\t\t *\n\t\t * @return {object[]} Per-form display rows.\n\t\t */\n\t\tforms() {\n\t\t\tconst out = []\n\t\t\tconst byId = new Map()\n\t\t\tfor (const row of this.rawRows) {\n\t\t\t\tif (!row || typeof row !== 'object') {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif ((row.type ?? 'form') === 'submission') {\n\t\t\t\t\tconst formId = String(row.data?.formId ?? '')\n\t\t\t\t\tif (byId.has(formId) === true) {\n\t\t\t\t\t\tconst target = byId.get(formId)\n\t\t\t\t\t\ttarget.submissions = (target.submissions || 0) + 1\n\t\t\t\t\t\tif (row.lastUpdated && (!target.lastSubmissionAt || row.lastUpdated > target.lastSubmissionAt)) {\n\t\t\t\t\t\t\ttarget.lastSubmissionAt = row.lastUpdated\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tconst normalised = this.normaliseFormRow(row)\n\t\t\t\tbyId.set(String(normalised.id), normalised)\n\t\t\t\tout.push(normalised)\n\t\t\t}\n\t\t\treturn out\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.fetchForms()\n\t\t\t}\n\t\t} },\n\n\t\tregister() {\n\t\t\tthis.fetchForms()\n\t\t},\n\n\t\tschema() {\n\t\t\tthis.fetchForms()\n\t\t},\n\t},\n\n\tmethods: {\n\t\tbaseUrl() {\n\t\t\treturn prefixUrl(`${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/forms`)\n\t\t},\n\n\t\tnormaliseFormRow(row) {\n\t\t\tconst data = row.data || {}\n\t\t\treturn {\n\t\t\t\tid: row.id ?? row.formId ?? data.id ?? '',\n\t\t\t\thash: data.hash ?? row.formHash ?? row.hash ?? '',\n\t\t\t\ttitle: row.title ?? data.title ?? '',\n\t\t\t\tdescription: row.description ?? data.description ?? '',\n\t\t\t\turl: row.url ?? '',\n\t\t\t\tlastUpdated: row.lastUpdated ?? data.lastUpdated ?? null,\n\t\t\t\tstatus: row.status ?? data.status ?? null,\n\t\t\t\texpiresAt: row.expiresAt ?? data.expiresAt ?? null,\n\t\t\t\taccessible: row.accessible ?? data.accessible ?? null,\n\t\t\t\tsubmissionCount: Number(row.submissionCount ?? data.submissionCount ?? 0) || 0,\n\t\t\t\tsubmissions: 0,\n\t\t\t\tlastSubmissionAt: null,\n\t\t\t}\n\t\t},\n\n\t\tformKey(form) {\n\t\t\treturn form.id ?? form.hash ?? ''\n\t\t},\n\n\t\tformTitle(form) {\n\t\t\treturn form.title || form.id || t('nextcloud-vue', 'Untitled form')\n\t\t},\n\n\t\tformDescription(form) {\n\t\t\treturn form.description || ''\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\tformUrl(form) {\n\t\t\tif (form.url && safeHref(form.url) !== '#') {\n\t\t\t\treturn form.url\n\t\t\t}\n\t\t\tif (form.hash) {\n\t\t\t\treturn `/index.php/apps/forms/${form.hash}`\n\t\t\t}\n\t\t\treturn this.formsAppUrl\n\t\t},\n\n\t\t/**\n\t\t * Resolve the form's expiry as a millisecond epoch, or null when\n\t\t * the form never expires. Accepts both second- and millisecond-\n\t\t * granularity numbers and ISO strings.\n\t\t *\n\t\t * @param {object} form normalised form row\n\t\t *\n\t\t * @return {?number}\n\t\t */\n\t\texpiresAtMs(form) {\n\t\t\tconst v = form.expiresAt\n\t\t\tif (v === null || v === undefined || v === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tif (typeof v === 'number') {\n\t\t\t\treturn v < 1e12 ? v * 1000 : v\n\t\t\t}\n\t\t\tconst parsed = Date.parse(String(v))\n\t\t\treturn Number.isNaN(parsed) === true ? null : parsed\n\t\t},\n\n\t\tisClosed(form) {\n\t\t\tif (form.status === 'closed' || form.status === 'archived') {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\tconst ms = this.expiresAtMs(form)\n\t\t\tif (ms !== null) {\n\t\t\t\treturn ms <= Date.now()\n\t\t\t}\n\t\t\treturn false\n\t\t},\n\n\t\tstatusLabel(form) {\n\t\t\tif (this.isClosed(form) === true) {\n\t\t\t\treturn t('nextcloud-vue', 'Closed')\n\t\t\t}\n\t\t\tif (form.status === 'draft') {\n\t\t\t\treturn t('nextcloud-vue', 'Draft')\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', 'Open')\n\t\t},\n\n\t\tstatusClass(form) {\n\t\t\tif (this.isClosed(form) === true) {\n\t\t\t\treturn 'cn-forms-tab__status--closed'\n\t\t\t}\n\t\t\tif (form.status === 'draft') {\n\t\t\t\treturn 'cn-forms-tab__status--draft'\n\t\t\t}\n\t\t\treturn 'cn-forms-tab__status--open'\n\t\t},\n\n\t\t/**\n\t\t * Map the form's state to a CnStatusBadge colour variant —\n\t\t * open = success (green), draft = warning, closed = default.\n\t\t *\n\t\t * @param {object} form normalised form row\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tstatusVariant(form) {\n\t\t\tif (this.isClosed(form) === true) {\n\t\t\t\treturn 'default'\n\t\t\t}\n\t\t\tif (form.status === 'draft') {\n\t\t\t\treturn 'warning'\n\t\t\t}\n\t\t\treturn 'success'\n\t\t},\n\n\t\tsubmissionCount(form) {\n\t\t\tif (form.submissionCount > 0) {\n\t\t\t\treturn form.submissionCount\n\t\t\t}\n\t\t\treturn form.submissions || 0\n\t\t},\n\n\t\tresponsesAriaLabel(form) {\n\t\t\treturn t('nextcloud-vue', '{n} submissions', { n: this.submissionCount(form) })\n\t\t},\n\n\t\tasync fetchForms() {\n\t\t\tif (!this.register || !this.schema || !this.objectId) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\tthis.error = ''\n\t\t\tthis.degraded = ''\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(this.baseUrl(), { headers: buildHeaders() })\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tconst data = await response.json()\n\t\t\t\t\tthis.rawRows = this.unwrapList(data)\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.rawRows = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.rawRows = []\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load forms.')\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('[CnFormsTab] failed to fetch forms', err)\n\t\t\t\tthis.rawRows = []\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load forms.')\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Canonical wrapper-key cascade — mirrors CnContactsTab.unwrapList.\n\t\t *\n\t\t * @param {object|Array<object>|null} data parsed JSON response body\n\t\t *\n\t\t * @return {Array}\n\t\t */\n\t\tunwrapList(data) {\n\t\t\tif (Array.isArray(data)) {\n\t\t\t\treturn data\n\t\t\t}\n\t\t\tif (data && typeof data === 'object') {\n\t\t\t\tif (Array.isArray(data.results)) {\n\t\t\t\t\treturn data.results\n\t\t\t\t}\n\t\t\t\tif (Array.isArray(data.items)) {\n\t\t\t\t\treturn data.items\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn []\n\t\t},\n\n\t\tasync onLinkSelected(payload) {\n\t\t\tif (!payload || !payload.formId) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(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(payload),\n\t\t\t\t})\n\t\t\t\tif (!response.ok) {\n\t\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\t\tconsole.error('[CnFormsTab] link failed', response.status, response.statusText)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tthis.showPicker = false\n\t\t\t\tawait this.fetchForms()\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('[CnFormsTab] link error', err)\n\t\t\t}\n\t\t},\n\n\t\tasync onCreateSubmit(payload) {\n\t\t\tif (!payload || !payload.title) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.creating = true\n\t\t\tthis.createError = ''\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(`${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\tconst data = await response.json().catch(() => ({}))\n\t\t\t\t\tthis.createError = data?.error || `${response.status} ${response.statusText}`\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tthis.showCreate = false\n\t\t\t\tawait this.fetchForms()\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('[CnFormsTab] create error', err)\n\t\t\t\tthis.createError = String(err?.message || err)\n\t\t\t} finally {\n\t\t\t\tthis.creating = false\n\t\t\t}\n\t\t},\n\n\t\tonCreateClose() {\n\t\t\tthis.showCreate = false\n\t\t\tthis.createError = ''\n\t\t},\n\n\t\tasync unlink(form) {\n\t\t\tif (!form?.id) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(`${this.baseUrl()}/${encodeURIComponent(form.id)}`, {\n\t\t\t\t\tmethod: 'DELETE',\n\t\t\t\t\theaders: buildHeaders(),\n\t\t\t\t})\n\t\t\t\tif (!response.ok) {\n\t\t\t\t\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\t\tconsole.error('[CnFormsTab] unlink failed', response.status, response.statusText)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tthis.rawRows = this.rawRows.filter((r) => String(r.id ?? r.formId ?? '') !== String(form.id))\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('[CnFormsTab] unlink error', err)\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-forms-tab {\n\tpadding: 12px;\n}\n\n.cn-forms-tab__header {\n\tdisplay: flex;\n\tgap: 8px;\n\tjustify-content: flex-end;\n\tmargin-bottom: 10px;\n\tflex-wrap: wrap;\n}\n\n.cn-forms-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-forms-tab__error {\n\tcolor: var(--color-error);\n\tfont-size: 0.9em;\n\tmargin: 4px 0 8px;\n}\n\n.cn-forms-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-forms-tab__empty-icon {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-forms-tab__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n/* Forms brand accent on the row icon, mirroring the real Forms app. */\n.cn-forms-tab__row-icon {\n\tcolor: var(--cn-forms-accent, #0d7000);\n\tflex-shrink: 0;\n}\n\n.cn-forms-tab__row--closed {\n\topacity: 0.85;\n}\n\n.cn-forms-tab__row--closed .cn-forms-tab__row-icon {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-forms-tab__description {\n\tdisplay: -webkit-box;\n\t-webkit-line-clamp: 2;\n\t-webkit-box-orient: vertical;\n\toverflow: hidden;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-forms-tab__details {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 6px;\n\tflex-wrap: wrap;\n\tjustify-content: flex-end;\n}\n\n.cn-forms-tab__expiry {\n\tfont-size: 0.8em;\n\tcolor: var(--color-text-maxcontrast);\n\twhite-space: nowrap;\n}\n\n/* Status chip colours layered on top of CnStatusBadge variants so the\n open/closed/draft state reads at a glance, like the real Forms app. */\n.cn-forms-tab__status--open {\n\t--cn-forms-status: var(--color-success, #46ba61);\n}\n\n.cn-forms-tab__status--closed {\n\t--cn-forms-status: var(--color-text-maxcontrast);\n}\n\n.cn-forms-tab__status--draft {\n\t--cn-forms-status: var(--color-warning, #e9a40f);\n}\n</style>\n"],"names":["_openBlock","_createElementBlock","_createElementVNode","_createVNode","_createTextVNode","_createBlock","_toDisplayString","_normalizeClass"],"mappings":"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"}
@@ -3,6 +3,7 @@ import { NcLoadingIcon } from '@nextcloud/vue';
3
3
  import MapMarker from 'vue-material-design-icons/MapMarker.vue';
4
4
  import __sfc_main from '../../../components/CnDetailCard/CnDetailCard.vue.js';
5
5
  import { stripMarker } from '../../utils/marker.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'];
@@ -168,8 +169,9 @@ var script = {
168
169
  return `${lat.toFixed(5)}, ${lng.toFixed(5)}`
169
170
  },
170
171
 
172
+ // A provider URL is used only when it is a safe scheme; else the app link.
171
173
  pointUrl(point) {
172
- if (typeof point.url === 'string' && point.url !== '') {
174
+ if (typeof point.url === 'string' && point.url !== '' && safeHref(point.url) !== '#') {
173
175
  return point.url
174
176
  }
175
177
  const lat = this.pointLat(point);
@@ -1 +1 @@
1
- {"version":3,"file":"CnMapsCard.vue2.js","sources":["../../../../../src/integrations/builtin/maps/CnMapsCard.vue"],"sourcesContent":["<!--\n CnMapsCard — bespoke surface-aware widget for the `maps` integration.\n\n Replaces the generic CnIntegrationCard for the `maps` leaf. Branches\n on `surface` per AD-19:\n - user-dashboard / app-dashboard : map-pin icon + headline\n \"N linked locations\" + the most-recently added POI (name +\n category + coords).\n - detail-page : compact list of linked POIs\n (icon + name + category badge + coords) capped at COMPACT_LIMIT\n with a \"view all\" trail-off pointing at NC Maps.\n - single-entity : pin + name chip for\n reference-property auto-rendering (referenceType: 'maps').\n\n Pulls rows from the same OR pluggable-integration sub-resource as\n CnMapsTab. Provider shape and field-fallback strategy matches the\n tab (see the tab's docblock).\n\n See `openregister/openspec/changes/integration-maps/` 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\n\t\t\t\tv-if=\"entity\"\n\t\t\t\tclass=\"cn-maps-card__chip\"\n\t\t\t\t:title=\"chipSubtitle(entity)\">\n\t\t\t\t<MapMarker :size=\"14\" />\n\t\t\t\t<a\n\t\t\t\t\t:href=\"pointUrl(entity)\"\n\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\trel=\"noopener\">{{ pointName(entity) }}</a>\n\t\t\t\t<span v-if=\"pointCategory(entity)\" class=\"cn-maps-card__chip-category\">\n\t\t\t\t\t· {{ pointCategory(entity) }}\n\t\t\t\t</span>\n\t\t\t</span>\n\t\t\t<span v-else-if=\"value\" class=\"cn-maps-card__chip cn-maps-card__chip--fallback\">\n\t\t\t\t<MapMarker :size=\"14\" />\n\t\t\t\t<span>{{ value }}</span>\n\t\t\t</span>\n\t\t\t<span v-else class=\"cn-maps-card__empty\">{{ emptyLabel }}</span>\n\t\t</template>\n\n\t\t<!-- dashboard surfaces: count + 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-maps-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"points.length === 0\" class=\"cn-maps-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<div v-else class=\"cn-maps-card__headline\">\n\t\t\t\t<div class=\"cn-maps-card__headline-line\">\n\t\t\t\t\t<MapMarker :size=\"16\" class=\"cn-maps-card__headline-icon\" />\n\t\t\t\t\t<strong>{{ countHeadline }}</strong>\n\t\t\t\t</div>\n\t\t\t\t<div v-if=\"mostRecent\" class=\"cn-maps-card__headline-recent\">\n\t\t\t\t\t<a\n\t\t\t\t\t\t:href=\"pointUrl(mostRecent)\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener\">{{ pointName(mostRecent) }}</a>\n\t\t\t\t\t<span v-if=\"pointCategory(mostRecent)\" class=\"cn-maps-card__headline-category\">\n\t\t\t\t\t\t· {{ pointCategory(mostRecent) }}\n\t\t\t\t\t</span>\n\t\t\t\t</div>\n\t\t\t\t<div v-if=\"mostRecent && coordsLabel(mostRecent)\" class=\"cn-maps-card__headline-coords\">\n\t\t\t\t\t{{ coordsLabel(mostRecent) }}\n\t\t\t\t</div>\n\t\t\t\t<div v-if=\"points.length > 1\" class=\"cn-maps-card__view-all\">\n\t\t\t\t\t<a\n\t\t\t\t\t\t:href=\"mapsAppUrl\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener\">{{ viewAllLabel }}</a>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</template>\n\n\t\t<!-- detail-page surface: compact list -->\n\t\t<template v-else>\n\t\t\t<div v-if=\"degraded\" class=\"cn-maps-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"points.length === 0\" class=\"cn-maps-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<ul v-else class=\"cn-maps-card__list\">\n\t\t\t\t<li\n\t\t\t\t\tv-for=\"point in displayedPoints\"\n\t\t\t\t\t:key=\"pointKey(point)\"\n\t\t\t\t\tclass=\"cn-maps-card__row\">\n\t\t\t\t\t<MapMarker :size=\"14\" class=\"cn-maps-card__row-icon\" />\n\t\t\t\t\t<div class=\"cn-maps-card__row-main\">\n\t\t\t\t\t\t<div class=\"cn-maps-card__row-header\">\n\t\t\t\t\t\t\t<a\n\t\t\t\t\t\t\t\t:href=\"pointUrl(point)\"\n\t\t\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t\t\trel=\"noopener\"\n\t\t\t\t\t\t\t\tclass=\"cn-maps-card__title\">{{ pointName(point) }}</a>\n\t\t\t\t\t\t\t<span v-if=\"pointCategory(point)\" class=\"cn-maps-card__category\">\n\t\t\t\t\t\t\t\t{{ pointCategory(point) }}\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<span v-if=\"coordsLabel(point)\" class=\"cn-maps-card__coords\">\n\t\t\t\t\t\t\t{{ coordsLabel(point) }}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</div>\n\t\t\t\t</li>\n\t\t\t</ul>\n\t\t\t<div v-if=\"points.length > COMPACT_LIMIT\" class=\"cn-maps-card__view-all\">\n\t\t\t\t<a\n\t\t\t\t\t:href=\"mapsAppUrl\"\n\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\trel=\"noopener\">{{ viewAllLabel }}</a>\n\t\t\t</div>\n\t\t</template>\n\t</CnDetailCard>\n</template>\n\n<script>\nimport { translatePlural as n, translate as t } from '@nextcloud/l10n'\nimport { NcLoadingIcon } from '@nextcloud/vue'\nimport MapMarker from 'vue-material-design-icons/MapMarker.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 * CnMapsCard — bespoke surface-aware widget for the `maps` integration.\n *\n * Renders Maps-aware metadata across all four AD-19 surfaces. See the\n * file-level docblock for surface-by-surface behaviour.\n */\nexport default {\n\tname: 'CnMapsCard',\n\n\tcomponents: { CnDetailCard, NcLoadingIcon, MapMarker },\n\n\tprops: {\n\t\t/** Stable integration id (forwarded from the registry — always `'maps'`). */\n\t\tintegrationId: { type: String, default: 'maps' },\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 (POI id). */\n\t\tvalue: { type: [String, Number], default: '' },\n\t\t/** Pre-translated card title. */\n\t\ttitle: { type: String, default: () => t('nextcloud-vue', 'Locations') },\n\t\t/** Optional Material Design Icon component. */\n\t\ticon: { type: Object, default: () => MapMarker },\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 locations linked yet') },\n\t\t/** Pre-translated unavailable label. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Location is currently unavailable.') },\n\t\t/** Pre-translated \"view all\" trail-off link label. */\n\t\tviewAllLabel: { type: String, default: () => t('nextcloud-vue', 'View all in Locations') },\n\t\t/** URL of the NC Maps app entry. */\n\t\tmapsAppUrl: { type: String, default: '/index.php/apps/maps' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tCOMPACT_LIMIT,\n\t\t\tpoints: [],\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\tdisplayedPoints() {\n\t\t\treturn this.points.slice(0, COMPACT_LIMIT)\n\t\t},\n\n\t\tcountHeadline() {\n\t\t\tconst total = this.points.length\n\t\t\treturn n('nextcloud-vue', '{count} linked location', '{count} linked locations', total, { count: total })\n\t\t},\n\n\t\tmostRecent() {\n\t\t\tif (this.points.length === 0) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\t// Maps provider has no modified-time column; fall back to id-order\n\t\t\t// (highest id == most recently added). Defensive: also check\n\t\t\t// addedAt / created when present in a future expansion.\n\t\t\tconst sorted = [...this.points].sort((a, b) => {\n\t\t\t\tconst ka = this.recencyKey(a)\n\t\t\t\tconst kb = this.recencyKey(b)\n\t\t\t\treturn kb - ka\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\tdataOf(point) {\n\t\t\treturn (point && typeof point.data === 'object' && point.data !== null) ? point.data : {}\n\t\t},\n\n\t\tpointKey(point) {\n\t\t\tconst d = this.dataOf(point)\n\t\t\treturn point.id ?? d.id ?? this.pointName(point)\n\t\t},\n\n\t\tpointName(point) {\n\t\t\tconst d = this.dataOf(point)\n\t\t\tconst candidate = point.name ?? point.title ?? d.name ?? d.title ?? ''\n\t\t\treturn stripMarker(candidate) || String(this.pointKey(point))\n\t\t},\n\n\t\tpointCategory(point) {\n\t\t\tconst d = this.dataOf(point)\n\t\t\tconst c = point.category ?? d.category ?? ''\n\t\t\treturn stripMarker(c)\n\t\t},\n\n\t\tcoordOf(point, key) {\n\t\t\tconst d = this.dataOf(point)\n\t\t\tconst v = point[key] ?? d[key] ?? null\n\t\t\tif (v === null || v === undefined || v === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst num = typeof v === 'number' ? v : Number(v)\n\t\t\treturn Number.isFinite(num) === true ? num : null\n\t\t},\n\n\t\tpointLat(point) {\n\t\t\treturn this.coordOf(point, 'lat')\n\t\t},\n\n\t\tpointLng(point) {\n\t\t\treturn this.coordOf(point, 'lng')\n\t\t},\n\n\t\tcoordsLabel(point) {\n\t\t\tconst lat = this.pointLat(point)\n\t\t\tconst lng = this.pointLng(point)\n\t\t\tif (lat === null || lng === null) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn `${lat.toFixed(5)}, ${lng.toFixed(5)}`\n\t\t},\n\n\t\tpointUrl(point) {\n\t\t\tif (typeof point.url === 'string' && point.url !== '') {\n\t\t\t\treturn point.url\n\t\t\t}\n\t\t\tconst lat = this.pointLat(point)\n\t\t\tconst lng = this.pointLng(point)\n\t\t\tif (lat !== null && lng !== null) {\n\t\t\t\treturn `${this.mapsAppUrl}/?point=${lat},${lng}`\n\t\t\t}\n\t\t\treturn this.mapsAppUrl\n\t\t},\n\n\t\tchipSubtitle(point) {\n\t\t\treturn this.coordsLabel(point) || this.pointCategory(point) || this.pointName(point)\n\t\t},\n\n\t\trecencyKey(point) {\n\t\t\tconst d = this.dataOf(point)\n\t\t\tconst v = point.addedAt ?? point.added ?? point.created ?? d.addedAt ?? d.added ?? d.created ?? point.id ?? d.id ?? 0\n\t\t\tconst num = typeof v === 'number' ? v : Number(v)\n\t\t\treturn Number.isFinite(num) === true ? num : 0\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.points = data.results || data.items || (Array.isArray(data) ? data : []) || []\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.points = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.points = []\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('[CnMapsCard] failed to fetch locations', err)\n\t\t\t\tthis.points = []\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\tasync fetchSingle() {\n\t\t\tif (this.value === '' || this.value === undefined || this.value === null || !this.register || !this.schema || !this.objectId) {\n\t\t\t\tthis.entity = null\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\tthis.degraded = ''\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(`${this.baseUrl()}/${encodeURIComponent(this.value)}`), { headers: buildHeaders() })\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tthis.entity = await response.json()\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.entity = null\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.entity = null\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnMapsCard] failed to fetch single location', 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-maps-card__empty {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n\tpadding: 8px 0;\n}\n\n.cn-maps-card__headline {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n}\n\n.cn-maps-card__headline-line {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 6px;\n\tfont-size: 1.1em;\n\tcolor: var(--color-main-text);\n}\n\n.cn-maps-card__headline-icon {\n\tcolor: var(--color-primary-element, #21468B);\n\tflex-shrink: 0;\n}\n\n.cn-maps-card__headline-recent {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 4px;\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-maps-card__headline-recent a {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-maps-card__headline-recent a:hover {\n\ttext-decoration: underline;\n}\n\n.cn-maps-card__headline-category {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-maps-card__headline-coords {\n\tfont-family: var(--font-face-monospace, monospace);\n\tfont-size: 0.75em;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-maps-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-maps-card__chip a {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-maps-card__chip a:hover {\n\ttext-decoration: underline;\n}\n\n.cn-maps-card__chip-category {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-maps-card__chip--fallback {\n\topacity: 0.85;\n}\n\n.cn-maps-card__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n.cn-maps-card__row {\n\tdisplay: flex;\n\talign-items: flex-start;\n\tgap: 8px;\n\tpadding: 6px 0;\n\tborder-bottom: 1px solid var(--color-border);\n}\n\n.cn-maps-card__row:last-child {\n\tborder-bottom: none;\n}\n\n.cn-maps-card__row-icon {\n\tcolor: var(--color-text-maxcontrast);\n\tflex-shrink: 0;\n\tpadding-top: 3px;\n}\n\n.cn-maps-card__row-main {\n\tflex: 1;\n\tmin-width: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n}\n\n.cn-maps-card__row-header {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n}\n\n.cn-maps-card__title {\n\tflex: 1;\n\tmin-width: 0;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\na.cn-maps-card__title:hover {\n\ttext-decoration: underline;\n}\n\n.cn-maps-card__category {\n\tflex-shrink: 0;\n\tfont-size: 0.65em;\n\tpadding: 1px 6px;\n\tborder-radius: 8px;\n\tbackground: var(--color-background-hover);\n\tcolor: var(--color-text-maxcontrast);\n\ttext-transform: uppercase;\n\tletter-spacing: 0.03em;\n}\n\n.cn-maps-card__coords {\n\tfont-family: var(--font-face-monospace, monospace);\n\tfont-size: 0.7em;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-maps-card__view-all {\n\tmargin-top: 6px;\n\tfont-size: 0.8em;\n}\n\n/* stylelint-disable-next-line no-descending-specificity */\n.cn-maps-card__view-all a {\n\tcolor: var(--color-primary-element);\n\ttext-decoration: none;\n}\n\n.cn-maps-card__view-all a:hover {\n\ttext-decoration: underline;\n}\n</style>\n"],"names":["CnDetailCard","t","n"],"mappings":"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"}
1
+ {"version":3,"file":"CnMapsCard.vue2.js","sources":["../../../../../src/integrations/builtin/maps/CnMapsCard.vue"],"sourcesContent":["<!--\n CnMapsCard — bespoke surface-aware widget for the `maps` integration.\n\n Replaces the generic CnIntegrationCard for the `maps` leaf. Branches\n on `surface` per AD-19:\n - user-dashboard / app-dashboard : map-pin icon + headline\n \"N linked locations\" + the most-recently added POI (name +\n category + coords).\n - detail-page : compact list of linked POIs\n (icon + name + category badge + coords) capped at COMPACT_LIMIT\n with a \"view all\" trail-off pointing at NC Maps.\n - single-entity : pin + name chip for\n reference-property auto-rendering (referenceType: 'maps').\n\n Pulls rows from the same OR pluggable-integration sub-resource as\n CnMapsTab. Provider shape and field-fallback strategy matches the\n tab (see the tab's docblock).\n\n See `openregister/openspec/changes/integration-maps/` 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\n\t\t\t\tv-if=\"entity\"\n\t\t\t\tclass=\"cn-maps-card__chip\"\n\t\t\t\t:title=\"chipSubtitle(entity)\">\n\t\t\t\t<MapMarker :size=\"14\" />\n\t\t\t\t<a\n\t\t\t\t\t:href=\"pointUrl(entity)\"\n\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\trel=\"noopener\">{{ pointName(entity) }}</a>\n\t\t\t\t<span v-if=\"pointCategory(entity)\" class=\"cn-maps-card__chip-category\">\n\t\t\t\t\t· {{ pointCategory(entity) }}\n\t\t\t\t</span>\n\t\t\t</span>\n\t\t\t<span v-else-if=\"value\" class=\"cn-maps-card__chip cn-maps-card__chip--fallback\">\n\t\t\t\t<MapMarker :size=\"14\" />\n\t\t\t\t<span>{{ value }}</span>\n\t\t\t</span>\n\t\t\t<span v-else class=\"cn-maps-card__empty\">{{ emptyLabel }}</span>\n\t\t</template>\n\n\t\t<!-- dashboard surfaces: count + 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-maps-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"points.length === 0\" class=\"cn-maps-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<div v-else class=\"cn-maps-card__headline\">\n\t\t\t\t<div class=\"cn-maps-card__headline-line\">\n\t\t\t\t\t<MapMarker :size=\"16\" class=\"cn-maps-card__headline-icon\" />\n\t\t\t\t\t<strong>{{ countHeadline }}</strong>\n\t\t\t\t</div>\n\t\t\t\t<div v-if=\"mostRecent\" class=\"cn-maps-card__headline-recent\">\n\t\t\t\t\t<a\n\t\t\t\t\t\t:href=\"pointUrl(mostRecent)\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener\">{{ pointName(mostRecent) }}</a>\n\t\t\t\t\t<span v-if=\"pointCategory(mostRecent)\" class=\"cn-maps-card__headline-category\">\n\t\t\t\t\t\t· {{ pointCategory(mostRecent) }}\n\t\t\t\t\t</span>\n\t\t\t\t</div>\n\t\t\t\t<div v-if=\"mostRecent && coordsLabel(mostRecent)\" class=\"cn-maps-card__headline-coords\">\n\t\t\t\t\t{{ coordsLabel(mostRecent) }}\n\t\t\t\t</div>\n\t\t\t\t<div v-if=\"points.length > 1\" class=\"cn-maps-card__view-all\">\n\t\t\t\t\t<a\n\t\t\t\t\t\t:href=\"mapsAppUrl\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener\">{{ viewAllLabel }}</a>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</template>\n\n\t\t<!-- detail-page surface: compact list -->\n\t\t<template v-else>\n\t\t\t<div v-if=\"degraded\" class=\"cn-maps-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"points.length === 0\" class=\"cn-maps-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<ul v-else class=\"cn-maps-card__list\">\n\t\t\t\t<li\n\t\t\t\t\tv-for=\"point in displayedPoints\"\n\t\t\t\t\t:key=\"pointKey(point)\"\n\t\t\t\t\tclass=\"cn-maps-card__row\">\n\t\t\t\t\t<MapMarker :size=\"14\" class=\"cn-maps-card__row-icon\" />\n\t\t\t\t\t<div class=\"cn-maps-card__row-main\">\n\t\t\t\t\t\t<div class=\"cn-maps-card__row-header\">\n\t\t\t\t\t\t\t<a\n\t\t\t\t\t\t\t\t:href=\"pointUrl(point)\"\n\t\t\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t\t\trel=\"noopener\"\n\t\t\t\t\t\t\t\tclass=\"cn-maps-card__title\">{{ pointName(point) }}</a>\n\t\t\t\t\t\t\t<span v-if=\"pointCategory(point)\" class=\"cn-maps-card__category\">\n\t\t\t\t\t\t\t\t{{ pointCategory(point) }}\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<span v-if=\"coordsLabel(point)\" class=\"cn-maps-card__coords\">\n\t\t\t\t\t\t\t{{ coordsLabel(point) }}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</div>\n\t\t\t\t</li>\n\t\t\t</ul>\n\t\t\t<div v-if=\"points.length > COMPACT_LIMIT\" class=\"cn-maps-card__view-all\">\n\t\t\t\t<a\n\t\t\t\t\t:href=\"mapsAppUrl\"\n\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\trel=\"noopener\">{{ viewAllLabel }}</a>\n\t\t\t</div>\n\t\t</template>\n\t</CnDetailCard>\n</template>\n\n<script>\nimport { translatePlural as n, translate as t } from '@nextcloud/l10n'\nimport { NcLoadingIcon } from '@nextcloud/vue'\nimport MapMarker from 'vue-material-design-icons/MapMarker.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 * CnMapsCard — bespoke surface-aware widget for the `maps` integration.\n *\n * Renders Maps-aware metadata across all four AD-19 surfaces. See the\n * file-level docblock for surface-by-surface behaviour.\n */\nexport default {\n\tname: 'CnMapsCard',\n\n\tcomponents: { CnDetailCard, NcLoadingIcon, MapMarker },\n\n\tprops: {\n\t\t/** Stable integration id (forwarded from the registry — always `'maps'`). */\n\t\tintegrationId: { type: String, default: 'maps' },\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 (POI id). */\n\t\tvalue: { type: [String, Number], default: '' },\n\t\t/** Pre-translated card title. */\n\t\ttitle: { type: String, default: () => t('nextcloud-vue', 'Locations') },\n\t\t/** Optional Material Design Icon component. */\n\t\ticon: { type: Object, default: () => MapMarker },\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 locations linked yet') },\n\t\t/** Pre-translated unavailable label. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Location is currently unavailable.') },\n\t\t/** Pre-translated \"view all\" trail-off link label. */\n\t\tviewAllLabel: { type: String, default: () => t('nextcloud-vue', 'View all in Locations') },\n\t\t/** URL of the NC Maps app entry. */\n\t\tmapsAppUrl: { type: String, default: '/index.php/apps/maps' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tCOMPACT_LIMIT,\n\t\t\tpoints: [],\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\tdisplayedPoints() {\n\t\t\treturn this.points.slice(0, COMPACT_LIMIT)\n\t\t},\n\n\t\tcountHeadline() {\n\t\t\tconst total = this.points.length\n\t\t\treturn n('nextcloud-vue', '{count} linked location', '{count} linked locations', total, { count: total })\n\t\t},\n\n\t\tmostRecent() {\n\t\t\tif (this.points.length === 0) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\t// Maps provider has no modified-time column; fall back to id-order\n\t\t\t// (highest id == most recently added). Defensive: also check\n\t\t\t// addedAt / created when present in a future expansion.\n\t\t\tconst sorted = [...this.points].sort((a, b) => {\n\t\t\t\tconst ka = this.recencyKey(a)\n\t\t\t\tconst kb = this.recencyKey(b)\n\t\t\t\treturn kb - ka\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\tdataOf(point) {\n\t\t\treturn (point && typeof point.data === 'object' && point.data !== null) ? point.data : {}\n\t\t},\n\n\t\tpointKey(point) {\n\t\t\tconst d = this.dataOf(point)\n\t\t\treturn point.id ?? d.id ?? this.pointName(point)\n\t\t},\n\n\t\tpointName(point) {\n\t\t\tconst d = this.dataOf(point)\n\t\t\tconst candidate = point.name ?? point.title ?? d.name ?? d.title ?? ''\n\t\t\treturn stripMarker(candidate) || String(this.pointKey(point))\n\t\t},\n\n\t\tpointCategory(point) {\n\t\t\tconst d = this.dataOf(point)\n\t\t\tconst c = point.category ?? d.category ?? ''\n\t\t\treturn stripMarker(c)\n\t\t},\n\n\t\tcoordOf(point, key) {\n\t\t\tconst d = this.dataOf(point)\n\t\t\tconst v = point[key] ?? d[key] ?? null\n\t\t\tif (v === null || v === undefined || v === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst num = typeof v === 'number' ? v : Number(v)\n\t\t\treturn Number.isFinite(num) === true ? num : null\n\t\t},\n\n\t\tpointLat(point) {\n\t\t\treturn this.coordOf(point, 'lat')\n\t\t},\n\n\t\tpointLng(point) {\n\t\t\treturn this.coordOf(point, 'lng')\n\t\t},\n\n\t\tcoordsLabel(point) {\n\t\t\tconst lat = this.pointLat(point)\n\t\t\tconst lng = this.pointLng(point)\n\t\t\tif (lat === null || lng === null) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn `${lat.toFixed(5)}, ${lng.toFixed(5)}`\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\tpointUrl(point) {\n\t\t\tif (typeof point.url === 'string' && point.url !== '' && safeHref(point.url) !== '#') {\n\t\t\t\treturn point.url\n\t\t\t}\n\t\t\tconst lat = this.pointLat(point)\n\t\t\tconst lng = this.pointLng(point)\n\t\t\tif (lat !== null && lng !== null) {\n\t\t\t\treturn `${this.mapsAppUrl}/?point=${lat},${lng}`\n\t\t\t}\n\t\t\treturn this.mapsAppUrl\n\t\t},\n\n\t\tchipSubtitle(point) {\n\t\t\treturn this.coordsLabel(point) || this.pointCategory(point) || this.pointName(point)\n\t\t},\n\n\t\trecencyKey(point) {\n\t\t\tconst d = this.dataOf(point)\n\t\t\tconst v = point.addedAt ?? point.added ?? point.created ?? d.addedAt ?? d.added ?? d.created ?? point.id ?? d.id ?? 0\n\t\t\tconst num = typeof v === 'number' ? v : Number(v)\n\t\t\treturn Number.isFinite(num) === true ? num : 0\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.points = data.results || data.items || (Array.isArray(data) ? data : []) || []\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.points = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.points = []\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('[CnMapsCard] failed to fetch locations', err)\n\t\t\t\tthis.points = []\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\tasync fetchSingle() {\n\t\t\tif (this.value === '' || this.value === undefined || this.value === null || !this.register || !this.schema || !this.objectId) {\n\t\t\t\tthis.entity = null\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\tthis.degraded = ''\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(`${this.baseUrl()}/${encodeURIComponent(this.value)}`), { headers: buildHeaders() })\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tthis.entity = await response.json()\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.entity = null\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.entity = null\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnMapsCard] failed to fetch single location', 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-maps-card__empty {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n\tpadding: 8px 0;\n}\n\n.cn-maps-card__headline {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n}\n\n.cn-maps-card__headline-line {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 6px;\n\tfont-size: 1.1em;\n\tcolor: var(--color-main-text);\n}\n\n.cn-maps-card__headline-icon {\n\tcolor: var(--color-primary-element, #21468B);\n\tflex-shrink: 0;\n}\n\n.cn-maps-card__headline-recent {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 4px;\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-maps-card__headline-recent a {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-maps-card__headline-recent a:hover {\n\ttext-decoration: underline;\n}\n\n.cn-maps-card__headline-category {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-maps-card__headline-coords {\n\tfont-family: var(--font-face-monospace, monospace);\n\tfont-size: 0.75em;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-maps-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-maps-card__chip a {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-maps-card__chip a:hover {\n\ttext-decoration: underline;\n}\n\n.cn-maps-card__chip-category {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-maps-card__chip--fallback {\n\topacity: 0.85;\n}\n\n.cn-maps-card__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n.cn-maps-card__row {\n\tdisplay: flex;\n\talign-items: flex-start;\n\tgap: 8px;\n\tpadding: 6px 0;\n\tborder-bottom: 1px solid var(--color-border);\n}\n\n.cn-maps-card__row:last-child {\n\tborder-bottom: none;\n}\n\n.cn-maps-card__row-icon {\n\tcolor: var(--color-text-maxcontrast);\n\tflex-shrink: 0;\n\tpadding-top: 3px;\n}\n\n.cn-maps-card__row-main {\n\tflex: 1;\n\tmin-width: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n}\n\n.cn-maps-card__row-header {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n}\n\n.cn-maps-card__title {\n\tflex: 1;\n\tmin-width: 0;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\na.cn-maps-card__title:hover {\n\ttext-decoration: underline;\n}\n\n.cn-maps-card__category {\n\tflex-shrink: 0;\n\tfont-size: 0.65em;\n\tpadding: 1px 6px;\n\tborder-radius: 8px;\n\tbackground: var(--color-background-hover);\n\tcolor: var(--color-text-maxcontrast);\n\ttext-transform: uppercase;\n\tletter-spacing: 0.03em;\n}\n\n.cn-maps-card__coords {\n\tfont-family: var(--font-face-monospace, monospace);\n\tfont-size: 0.7em;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-maps-card__view-all {\n\tmargin-top: 6px;\n\tfont-size: 0.8em;\n}\n\n/* stylelint-disable-next-line no-descending-specificity */\n.cn-maps-card__view-all a {\n\tcolor: var(--color-primary-element);\n\ttext-decoration: none;\n}\n\n.cn-maps-card__view-all a:hover {\n\ttext-decoration: underline;\n}\n</style>\n"],"names":["CnDetailCard","t","n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"CnMapsCard.vue3.js","sources":["../../../../../src/integrations/builtin/maps/CnMapsCard.vue?vue&type=template&id=4c0775e7&lang.js"],"sourcesContent":["<!--\n CnMapsCard — bespoke surface-aware widget for the `maps` integration.\n\n Replaces the generic CnIntegrationCard for the `maps` leaf. Branches\n on `surface` per AD-19:\n - user-dashboard / app-dashboard : map-pin icon + headline\n \"N linked locations\" + the most-recently added POI (name +\n category + coords).\n - detail-page : compact list of linked POIs\n (icon + name + category badge + coords) capped at COMPACT_LIMIT\n with a \"view all\" trail-off pointing at NC Maps.\n - single-entity : pin + name chip for\n reference-property auto-rendering (referenceType: 'maps').\n\n Pulls rows from the same OR pluggable-integration sub-resource as\n CnMapsTab. Provider shape and field-fallback strategy matches the\n tab (see the tab's docblock).\n\n See `openregister/openspec/changes/integration-maps/` 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\n\t\t\t\tv-if=\"entity\"\n\t\t\t\tclass=\"cn-maps-card__chip\"\n\t\t\t\t:title=\"chipSubtitle(entity)\">\n\t\t\t\t<MapMarker :size=\"14\" />\n\t\t\t\t<a\n\t\t\t\t\t:href=\"pointUrl(entity)\"\n\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\trel=\"noopener\">{{ pointName(entity) }}</a>\n\t\t\t\t<span v-if=\"pointCategory(entity)\" class=\"cn-maps-card__chip-category\">\n\t\t\t\t\t· {{ pointCategory(entity) }}\n\t\t\t\t</span>\n\t\t\t</span>\n\t\t\t<span v-else-if=\"value\" class=\"cn-maps-card__chip cn-maps-card__chip--fallback\">\n\t\t\t\t<MapMarker :size=\"14\" />\n\t\t\t\t<span>{{ value }}</span>\n\t\t\t</span>\n\t\t\t<span v-else class=\"cn-maps-card__empty\">{{ emptyLabel }}</span>\n\t\t</template>\n\n\t\t<!-- dashboard surfaces: count + 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-maps-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"points.length === 0\" class=\"cn-maps-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<div v-else class=\"cn-maps-card__headline\">\n\t\t\t\t<div class=\"cn-maps-card__headline-line\">\n\t\t\t\t\t<MapMarker :size=\"16\" class=\"cn-maps-card__headline-icon\" />\n\t\t\t\t\t<strong>{{ countHeadline }}</strong>\n\t\t\t\t</div>\n\t\t\t\t<div v-if=\"mostRecent\" class=\"cn-maps-card__headline-recent\">\n\t\t\t\t\t<a\n\t\t\t\t\t\t:href=\"pointUrl(mostRecent)\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener\">{{ pointName(mostRecent) }}</a>\n\t\t\t\t\t<span v-if=\"pointCategory(mostRecent)\" class=\"cn-maps-card__headline-category\">\n\t\t\t\t\t\t· {{ pointCategory(mostRecent) }}\n\t\t\t\t\t</span>\n\t\t\t\t</div>\n\t\t\t\t<div v-if=\"mostRecent && coordsLabel(mostRecent)\" class=\"cn-maps-card__headline-coords\">\n\t\t\t\t\t{{ coordsLabel(mostRecent) }}\n\t\t\t\t</div>\n\t\t\t\t<div v-if=\"points.length > 1\" class=\"cn-maps-card__view-all\">\n\t\t\t\t\t<a\n\t\t\t\t\t\t:href=\"mapsAppUrl\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener\">{{ viewAllLabel }}</a>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</template>\n\n\t\t<!-- detail-page surface: compact list -->\n\t\t<template v-else>\n\t\t\t<div v-if=\"degraded\" class=\"cn-maps-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"points.length === 0\" class=\"cn-maps-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<ul v-else class=\"cn-maps-card__list\">\n\t\t\t\t<li\n\t\t\t\t\tv-for=\"point in displayedPoints\"\n\t\t\t\t\t:key=\"pointKey(point)\"\n\t\t\t\t\tclass=\"cn-maps-card__row\">\n\t\t\t\t\t<MapMarker :size=\"14\" class=\"cn-maps-card__row-icon\" />\n\t\t\t\t\t<div class=\"cn-maps-card__row-main\">\n\t\t\t\t\t\t<div class=\"cn-maps-card__row-header\">\n\t\t\t\t\t\t\t<a\n\t\t\t\t\t\t\t\t:href=\"pointUrl(point)\"\n\t\t\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t\t\trel=\"noopener\"\n\t\t\t\t\t\t\t\tclass=\"cn-maps-card__title\">{{ pointName(point) }}</a>\n\t\t\t\t\t\t\t<span v-if=\"pointCategory(point)\" class=\"cn-maps-card__category\">\n\t\t\t\t\t\t\t\t{{ pointCategory(point) }}\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<span v-if=\"coordsLabel(point)\" class=\"cn-maps-card__coords\">\n\t\t\t\t\t\t\t{{ coordsLabel(point) }}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</div>\n\t\t\t\t</li>\n\t\t\t</ul>\n\t\t\t<div v-if=\"points.length > COMPACT_LIMIT\" class=\"cn-maps-card__view-all\">\n\t\t\t\t<a\n\t\t\t\t\t:href=\"mapsAppUrl\"\n\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\trel=\"noopener\">{{ viewAllLabel }}</a>\n\t\t\t</div>\n\t\t</template>\n\t</CnDetailCard>\n</template>\n\n<script>\nimport { translatePlural as n, translate as t } from '@nextcloud/l10n'\nimport { NcLoadingIcon } from '@nextcloud/vue'\nimport MapMarker from 'vue-material-design-icons/MapMarker.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 * CnMapsCard — bespoke surface-aware widget for the `maps` integration.\n *\n * Renders Maps-aware metadata across all four AD-19 surfaces. See the\n * file-level docblock for surface-by-surface behaviour.\n */\nexport default {\n\tname: 'CnMapsCard',\n\n\tcomponents: { CnDetailCard, NcLoadingIcon, MapMarker },\n\n\tprops: {\n\t\t/** Stable integration id (forwarded from the registry — always `'maps'`). */\n\t\tintegrationId: { type: String, default: 'maps' },\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 (POI id). */\n\t\tvalue: { type: [String, Number], default: '' },\n\t\t/** Pre-translated card title. */\n\t\ttitle: { type: String, default: () => t('nextcloud-vue', 'Locations') },\n\t\t/** Optional Material Design Icon component. */\n\t\ticon: { type: Object, default: () => MapMarker },\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 locations linked yet') },\n\t\t/** Pre-translated unavailable label. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Location is currently unavailable.') },\n\t\t/** Pre-translated \"view all\" trail-off link label. */\n\t\tviewAllLabel: { type: String, default: () => t('nextcloud-vue', 'View all in Locations') },\n\t\t/** URL of the NC Maps app entry. */\n\t\tmapsAppUrl: { type: String, default: '/index.php/apps/maps' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tCOMPACT_LIMIT,\n\t\t\tpoints: [],\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\tdisplayedPoints() {\n\t\t\treturn this.points.slice(0, COMPACT_LIMIT)\n\t\t},\n\n\t\tcountHeadline() {\n\t\t\tconst total = this.points.length\n\t\t\treturn n('nextcloud-vue', '{count} linked location', '{count} linked locations', total, { count: total })\n\t\t},\n\n\t\tmostRecent() {\n\t\t\tif (this.points.length === 0) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\t// Maps provider has no modified-time column; fall back to id-order\n\t\t\t// (highest id == most recently added). Defensive: also check\n\t\t\t// addedAt / created when present in a future expansion.\n\t\t\tconst sorted = [...this.points].sort((a, b) => {\n\t\t\t\tconst ka = this.recencyKey(a)\n\t\t\t\tconst kb = this.recencyKey(b)\n\t\t\t\treturn kb - ka\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\tdataOf(point) {\n\t\t\treturn (point && typeof point.data === 'object' && point.data !== null) ? point.data : {}\n\t\t},\n\n\t\tpointKey(point) {\n\t\t\tconst d = this.dataOf(point)\n\t\t\treturn point.id ?? d.id ?? this.pointName(point)\n\t\t},\n\n\t\tpointName(point) {\n\t\t\tconst d = this.dataOf(point)\n\t\t\tconst candidate = point.name ?? point.title ?? d.name ?? d.title ?? ''\n\t\t\treturn stripMarker(candidate) || String(this.pointKey(point))\n\t\t},\n\n\t\tpointCategory(point) {\n\t\t\tconst d = this.dataOf(point)\n\t\t\tconst c = point.category ?? d.category ?? ''\n\t\t\treturn stripMarker(c)\n\t\t},\n\n\t\tcoordOf(point, key) {\n\t\t\tconst d = this.dataOf(point)\n\t\t\tconst v = point[key] ?? d[key] ?? null\n\t\t\tif (v === null || v === undefined || v === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst num = typeof v === 'number' ? v : Number(v)\n\t\t\treturn Number.isFinite(num) === true ? num : null\n\t\t},\n\n\t\tpointLat(point) {\n\t\t\treturn this.coordOf(point, 'lat')\n\t\t},\n\n\t\tpointLng(point) {\n\t\t\treturn this.coordOf(point, 'lng')\n\t\t},\n\n\t\tcoordsLabel(point) {\n\t\t\tconst lat = this.pointLat(point)\n\t\t\tconst lng = this.pointLng(point)\n\t\t\tif (lat === null || lng === null) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn `${lat.toFixed(5)}, ${lng.toFixed(5)}`\n\t\t},\n\n\t\tpointUrl(point) {\n\t\t\tif (typeof point.url === 'string' && point.url !== '') {\n\t\t\t\treturn point.url\n\t\t\t}\n\t\t\tconst lat = this.pointLat(point)\n\t\t\tconst lng = this.pointLng(point)\n\t\t\tif (lat !== null && lng !== null) {\n\t\t\t\treturn `${this.mapsAppUrl}/?point=${lat},${lng}`\n\t\t\t}\n\t\t\treturn this.mapsAppUrl\n\t\t},\n\n\t\tchipSubtitle(point) {\n\t\t\treturn this.coordsLabel(point) || this.pointCategory(point) || this.pointName(point)\n\t\t},\n\n\t\trecencyKey(point) {\n\t\t\tconst d = this.dataOf(point)\n\t\t\tconst v = point.addedAt ?? point.added ?? point.created ?? d.addedAt ?? d.added ?? d.created ?? point.id ?? d.id ?? 0\n\t\t\tconst num = typeof v === 'number' ? v : Number(v)\n\t\t\treturn Number.isFinite(num) === true ? num : 0\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.points = data.results || data.items || (Array.isArray(data) ? data : []) || []\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.points = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.points = []\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('[CnMapsCard] failed to fetch locations', err)\n\t\t\t\tthis.points = []\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\tasync fetchSingle() {\n\t\t\tif (this.value === '' || this.value === undefined || this.value === null || !this.register || !this.schema || !this.objectId) {\n\t\t\t\tthis.entity = null\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\tthis.degraded = ''\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(`${this.baseUrl()}/${encodeURIComponent(this.value)}`), { headers: buildHeaders() })\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tthis.entity = await response.json()\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.entity = null\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.entity = null\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnMapsCard] failed to fetch single location', 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-maps-card__empty {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n\tpadding: 8px 0;\n}\n\n.cn-maps-card__headline {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n}\n\n.cn-maps-card__headline-line {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 6px;\n\tfont-size: 1.1em;\n\tcolor: var(--color-main-text);\n}\n\n.cn-maps-card__headline-icon {\n\tcolor: var(--color-primary-element, #21468B);\n\tflex-shrink: 0;\n}\n\n.cn-maps-card__headline-recent {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 4px;\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-maps-card__headline-recent a {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-maps-card__headline-recent a:hover {\n\ttext-decoration: underline;\n}\n\n.cn-maps-card__headline-category {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-maps-card__headline-coords {\n\tfont-family: var(--font-face-monospace, monospace);\n\tfont-size: 0.75em;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-maps-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-maps-card__chip a {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-maps-card__chip a:hover {\n\ttext-decoration: underline;\n}\n\n.cn-maps-card__chip-category {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-maps-card__chip--fallback {\n\topacity: 0.85;\n}\n\n.cn-maps-card__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n.cn-maps-card__row {\n\tdisplay: flex;\n\talign-items: flex-start;\n\tgap: 8px;\n\tpadding: 6px 0;\n\tborder-bottom: 1px solid var(--color-border);\n}\n\n.cn-maps-card__row:last-child {\n\tborder-bottom: none;\n}\n\n.cn-maps-card__row-icon {\n\tcolor: var(--color-text-maxcontrast);\n\tflex-shrink: 0;\n\tpadding-top: 3px;\n}\n\n.cn-maps-card__row-main {\n\tflex: 1;\n\tmin-width: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n}\n\n.cn-maps-card__row-header {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n}\n\n.cn-maps-card__title {\n\tflex: 1;\n\tmin-width: 0;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\na.cn-maps-card__title:hover {\n\ttext-decoration: underline;\n}\n\n.cn-maps-card__category {\n\tflex-shrink: 0;\n\tfont-size: 0.65em;\n\tpadding: 1px 6px;\n\tborder-radius: 8px;\n\tbackground: var(--color-background-hover);\n\tcolor: var(--color-text-maxcontrast);\n\ttext-transform: uppercase;\n\tletter-spacing: 0.03em;\n}\n\n.cn-maps-card__coords {\n\tfont-family: var(--font-face-monospace, monospace);\n\tfont-size: 0.7em;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-maps-card__view-all {\n\tmargin-top: 6px;\n\tfont-size: 0.8em;\n}\n\n/* stylelint-disable-next-line no-descending-specificity */\n.cn-maps-card__view-all a {\n\tcolor: var(--color-primary-element);\n\ttext-decoration: none;\n}\n\n.cn-maps-card__view-all a:hover {\n\ttext-decoration: underline;\n}\n</style>\n"],"names":["_createBlock","_createElementBlock","_Fragment","_createVNode","_createElementVNode","_toDisplayString","_openBlock"],"mappings":"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"}
1
+ {"version":3,"file":"CnMapsCard.vue3.js","sources":["../../../../../src/integrations/builtin/maps/CnMapsCard.vue?vue&type=template&id=4c0775e7&lang.js"],"sourcesContent":["<!--\n CnMapsCard — bespoke surface-aware widget for the `maps` integration.\n\n Replaces the generic CnIntegrationCard for the `maps` leaf. Branches\n on `surface` per AD-19:\n - user-dashboard / app-dashboard : map-pin icon + headline\n \"N linked locations\" + the most-recently added POI (name +\n category + coords).\n - detail-page : compact list of linked POIs\n (icon + name + category badge + coords) capped at COMPACT_LIMIT\n with a \"view all\" trail-off pointing at NC Maps.\n - single-entity : pin + name chip for\n reference-property auto-rendering (referenceType: 'maps').\n\n Pulls rows from the same OR pluggable-integration sub-resource as\n CnMapsTab. Provider shape and field-fallback strategy matches the\n tab (see the tab's docblock).\n\n See `openregister/openspec/changes/integration-maps/` 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\n\t\t\t\tv-if=\"entity\"\n\t\t\t\tclass=\"cn-maps-card__chip\"\n\t\t\t\t:title=\"chipSubtitle(entity)\">\n\t\t\t\t<MapMarker :size=\"14\" />\n\t\t\t\t<a\n\t\t\t\t\t:href=\"pointUrl(entity)\"\n\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\trel=\"noopener\">{{ pointName(entity) }}</a>\n\t\t\t\t<span v-if=\"pointCategory(entity)\" class=\"cn-maps-card__chip-category\">\n\t\t\t\t\t· {{ pointCategory(entity) }}\n\t\t\t\t</span>\n\t\t\t</span>\n\t\t\t<span v-else-if=\"value\" class=\"cn-maps-card__chip cn-maps-card__chip--fallback\">\n\t\t\t\t<MapMarker :size=\"14\" />\n\t\t\t\t<span>{{ value }}</span>\n\t\t\t</span>\n\t\t\t<span v-else class=\"cn-maps-card__empty\">{{ emptyLabel }}</span>\n\t\t</template>\n\n\t\t<!-- dashboard surfaces: count + 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-maps-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"points.length === 0\" class=\"cn-maps-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<div v-else class=\"cn-maps-card__headline\">\n\t\t\t\t<div class=\"cn-maps-card__headline-line\">\n\t\t\t\t\t<MapMarker :size=\"16\" class=\"cn-maps-card__headline-icon\" />\n\t\t\t\t\t<strong>{{ countHeadline }}</strong>\n\t\t\t\t</div>\n\t\t\t\t<div v-if=\"mostRecent\" class=\"cn-maps-card__headline-recent\">\n\t\t\t\t\t<a\n\t\t\t\t\t\t:href=\"pointUrl(mostRecent)\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener\">{{ pointName(mostRecent) }}</a>\n\t\t\t\t\t<span v-if=\"pointCategory(mostRecent)\" class=\"cn-maps-card__headline-category\">\n\t\t\t\t\t\t· {{ pointCategory(mostRecent) }}\n\t\t\t\t\t</span>\n\t\t\t\t</div>\n\t\t\t\t<div v-if=\"mostRecent && coordsLabel(mostRecent)\" class=\"cn-maps-card__headline-coords\">\n\t\t\t\t\t{{ coordsLabel(mostRecent) }}\n\t\t\t\t</div>\n\t\t\t\t<div v-if=\"points.length > 1\" class=\"cn-maps-card__view-all\">\n\t\t\t\t\t<a\n\t\t\t\t\t\t:href=\"mapsAppUrl\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener\">{{ viewAllLabel }}</a>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</template>\n\n\t\t<!-- detail-page surface: compact list -->\n\t\t<template v-else>\n\t\t\t<div v-if=\"degraded\" class=\"cn-maps-card__empty\">\n\t\t\t\t{{ degraded }}\n\t\t\t</div>\n\t\t\t<div v-else-if=\"points.length === 0\" class=\"cn-maps-card__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\t\t\t<ul v-else class=\"cn-maps-card__list\">\n\t\t\t\t<li\n\t\t\t\t\tv-for=\"point in displayedPoints\"\n\t\t\t\t\t:key=\"pointKey(point)\"\n\t\t\t\t\tclass=\"cn-maps-card__row\">\n\t\t\t\t\t<MapMarker :size=\"14\" class=\"cn-maps-card__row-icon\" />\n\t\t\t\t\t<div class=\"cn-maps-card__row-main\">\n\t\t\t\t\t\t<div class=\"cn-maps-card__row-header\">\n\t\t\t\t\t\t\t<a\n\t\t\t\t\t\t\t\t:href=\"pointUrl(point)\"\n\t\t\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t\t\trel=\"noopener\"\n\t\t\t\t\t\t\t\tclass=\"cn-maps-card__title\">{{ pointName(point) }}</a>\n\t\t\t\t\t\t\t<span v-if=\"pointCategory(point)\" class=\"cn-maps-card__category\">\n\t\t\t\t\t\t\t\t{{ pointCategory(point) }}\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<span v-if=\"coordsLabel(point)\" class=\"cn-maps-card__coords\">\n\t\t\t\t\t\t\t{{ coordsLabel(point) }}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</div>\n\t\t\t\t</li>\n\t\t\t</ul>\n\t\t\t<div v-if=\"points.length > COMPACT_LIMIT\" class=\"cn-maps-card__view-all\">\n\t\t\t\t<a\n\t\t\t\t\t:href=\"mapsAppUrl\"\n\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\trel=\"noopener\">{{ viewAllLabel }}</a>\n\t\t\t</div>\n\t\t</template>\n\t</CnDetailCard>\n</template>\n\n<script>\nimport { translatePlural as n, translate as t } from '@nextcloud/l10n'\nimport { NcLoadingIcon } from '@nextcloud/vue'\nimport MapMarker from 'vue-material-design-icons/MapMarker.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 * CnMapsCard — bespoke surface-aware widget for the `maps` integration.\n *\n * Renders Maps-aware metadata across all four AD-19 surfaces. See the\n * file-level docblock for surface-by-surface behaviour.\n */\nexport default {\n\tname: 'CnMapsCard',\n\n\tcomponents: { CnDetailCard, NcLoadingIcon, MapMarker },\n\n\tprops: {\n\t\t/** Stable integration id (forwarded from the registry — always `'maps'`). */\n\t\tintegrationId: { type: String, default: 'maps' },\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 (POI id). */\n\t\tvalue: { type: [String, Number], default: '' },\n\t\t/** Pre-translated card title. */\n\t\ttitle: { type: String, default: () => t('nextcloud-vue', 'Locations') },\n\t\t/** Optional Material Design Icon component. */\n\t\ticon: { type: Object, default: () => MapMarker },\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 locations linked yet') },\n\t\t/** Pre-translated unavailable label. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Location is currently unavailable.') },\n\t\t/** Pre-translated \"view all\" trail-off link label. */\n\t\tviewAllLabel: { type: String, default: () => t('nextcloud-vue', 'View all in Locations') },\n\t\t/** URL of the NC Maps app entry. */\n\t\tmapsAppUrl: { type: String, default: '/index.php/apps/maps' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tCOMPACT_LIMIT,\n\t\t\tpoints: [],\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\tdisplayedPoints() {\n\t\t\treturn this.points.slice(0, COMPACT_LIMIT)\n\t\t},\n\n\t\tcountHeadline() {\n\t\t\tconst total = this.points.length\n\t\t\treturn n('nextcloud-vue', '{count} linked location', '{count} linked locations', total, { count: total })\n\t\t},\n\n\t\tmostRecent() {\n\t\t\tif (this.points.length === 0) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\t// Maps provider has no modified-time column; fall back to id-order\n\t\t\t// (highest id == most recently added). Defensive: also check\n\t\t\t// addedAt / created when present in a future expansion.\n\t\t\tconst sorted = [...this.points].sort((a, b) => {\n\t\t\t\tconst ka = this.recencyKey(a)\n\t\t\t\tconst kb = this.recencyKey(b)\n\t\t\t\treturn kb - ka\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\tdataOf(point) {\n\t\t\treturn (point && typeof point.data === 'object' && point.data !== null) ? point.data : {}\n\t\t},\n\n\t\tpointKey(point) {\n\t\t\tconst d = this.dataOf(point)\n\t\t\treturn point.id ?? d.id ?? this.pointName(point)\n\t\t},\n\n\t\tpointName(point) {\n\t\t\tconst d = this.dataOf(point)\n\t\t\tconst candidate = point.name ?? point.title ?? d.name ?? d.title ?? ''\n\t\t\treturn stripMarker(candidate) || String(this.pointKey(point))\n\t\t},\n\n\t\tpointCategory(point) {\n\t\t\tconst d = this.dataOf(point)\n\t\t\tconst c = point.category ?? d.category ?? ''\n\t\t\treturn stripMarker(c)\n\t\t},\n\n\t\tcoordOf(point, key) {\n\t\t\tconst d = this.dataOf(point)\n\t\t\tconst v = point[key] ?? d[key] ?? null\n\t\t\tif (v === null || v === undefined || v === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst num = typeof v === 'number' ? v : Number(v)\n\t\t\treturn Number.isFinite(num) === true ? num : null\n\t\t},\n\n\t\tpointLat(point) {\n\t\t\treturn this.coordOf(point, 'lat')\n\t\t},\n\n\t\tpointLng(point) {\n\t\t\treturn this.coordOf(point, 'lng')\n\t\t},\n\n\t\tcoordsLabel(point) {\n\t\t\tconst lat = this.pointLat(point)\n\t\t\tconst lng = this.pointLng(point)\n\t\t\tif (lat === null || lng === null) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn `${lat.toFixed(5)}, ${lng.toFixed(5)}`\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\tpointUrl(point) {\n\t\t\tif (typeof point.url === 'string' && point.url !== '' && safeHref(point.url) !== '#') {\n\t\t\t\treturn point.url\n\t\t\t}\n\t\t\tconst lat = this.pointLat(point)\n\t\t\tconst lng = this.pointLng(point)\n\t\t\tif (lat !== null && lng !== null) {\n\t\t\t\treturn `${this.mapsAppUrl}/?point=${lat},${lng}`\n\t\t\t}\n\t\t\treturn this.mapsAppUrl\n\t\t},\n\n\t\tchipSubtitle(point) {\n\t\t\treturn this.coordsLabel(point) || this.pointCategory(point) || this.pointName(point)\n\t\t},\n\n\t\trecencyKey(point) {\n\t\t\tconst d = this.dataOf(point)\n\t\t\tconst v = point.addedAt ?? point.added ?? point.created ?? d.addedAt ?? d.added ?? d.created ?? point.id ?? d.id ?? 0\n\t\t\tconst num = typeof v === 'number' ? v : Number(v)\n\t\t\treturn Number.isFinite(num) === true ? num : 0\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.points = data.results || data.items || (Array.isArray(data) ? data : []) || []\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.points = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.points = []\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('[CnMapsCard] failed to fetch locations', err)\n\t\t\t\tthis.points = []\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\tasync fetchSingle() {\n\t\t\tif (this.value === '' || this.value === undefined || this.value === null || !this.register || !this.schema || !this.objectId) {\n\t\t\t\tthis.entity = null\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\tthis.degraded = ''\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(`${this.baseUrl()}/${encodeURIComponent(this.value)}`), { headers: buildHeaders() })\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tthis.entity = await response.json()\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.entity = null\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.entity = null\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnMapsCard] failed to fetch single location', 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-maps-card__empty {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n\tpadding: 8px 0;\n}\n\n.cn-maps-card__headline {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n}\n\n.cn-maps-card__headline-line {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 6px;\n\tfont-size: 1.1em;\n\tcolor: var(--color-main-text);\n}\n\n.cn-maps-card__headline-icon {\n\tcolor: var(--color-primary-element, #21468B);\n\tflex-shrink: 0;\n}\n\n.cn-maps-card__headline-recent {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 4px;\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-maps-card__headline-recent a {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-maps-card__headline-recent a:hover {\n\ttext-decoration: underline;\n}\n\n.cn-maps-card__headline-category {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-maps-card__headline-coords {\n\tfont-family: var(--font-face-monospace, monospace);\n\tfont-size: 0.75em;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-maps-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-maps-card__chip a {\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-maps-card__chip a:hover {\n\ttext-decoration: underline;\n}\n\n.cn-maps-card__chip-category {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-maps-card__chip--fallback {\n\topacity: 0.85;\n}\n\n.cn-maps-card__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n.cn-maps-card__row {\n\tdisplay: flex;\n\talign-items: flex-start;\n\tgap: 8px;\n\tpadding: 6px 0;\n\tborder-bottom: 1px solid var(--color-border);\n}\n\n.cn-maps-card__row:last-child {\n\tborder-bottom: none;\n}\n\n.cn-maps-card__row-icon {\n\tcolor: var(--color-text-maxcontrast);\n\tflex-shrink: 0;\n\tpadding-top: 3px;\n}\n\n.cn-maps-card__row-main {\n\tflex: 1;\n\tmin-width: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n}\n\n.cn-maps-card__row-header {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n}\n\n.cn-maps-card__title {\n\tflex: 1;\n\tmin-width: 0;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n\tcolor: var(--color-main-text);\n\ttext-decoration: none;\n}\n\na.cn-maps-card__title:hover {\n\ttext-decoration: underline;\n}\n\n.cn-maps-card__category {\n\tflex-shrink: 0;\n\tfont-size: 0.65em;\n\tpadding: 1px 6px;\n\tborder-radius: 8px;\n\tbackground: var(--color-background-hover);\n\tcolor: var(--color-text-maxcontrast);\n\ttext-transform: uppercase;\n\tletter-spacing: 0.03em;\n}\n\n.cn-maps-card__coords {\n\tfont-family: var(--font-face-monospace, monospace);\n\tfont-size: 0.7em;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-maps-card__view-all {\n\tmargin-top: 6px;\n\tfont-size: 0.8em;\n}\n\n/* stylelint-disable-next-line no-descending-specificity */\n.cn-maps-card__view-all a {\n\tcolor: var(--color-primary-element);\n\ttext-decoration: none;\n}\n\n.cn-maps-card__view-all a:hover {\n\ttext-decoration: underline;\n}\n</style>\n"],"names":["_createBlock","_createElementBlock","_Fragment","_createVNode","_createElementVNode","_toDisplayString","_openBlock"],"mappings":";;;;;;AAyDuC,EAAA,KAAK,EAAC,6BAAmB;;;;AAI1D,EAAA,KAAA,EAAA,iDAAA;;;;AAIU,EAAA,KAAK,EAAC,qBAAsB;;;;AAKpB,EAAA,KAAK,EAAC,qBAAG;;;;AAG3B,EAAA,KAAA,EAAA,qBAAA;;;;AAGS,EAAA,KAAK,EAAC,wBAAqB;;AACjC,MAAA,UAAA,GAAA,EAAA,KAAA,EAAA,6BAAA,GAAA;;;AAImB,EAAA,KAAI,EAAC,+BAAU;;;;;AAKC,EAAA,KAAK,EAAC,iCAAiB;;;;AAI7D,EAAA,KAAA,EAAA,+BAAA;;;;AAGwB,EAAA,KAAA,EAAA,wBAAA;;;;;AAWjB,EAAA,KAAA,EAAA,qBAAA;;;;AAGiB,EAAA,KAAA,EAAA,qBAAA;;;;AAGtB,EAAA,KAAA,EAAA,oBAAA;;AAME,MAAA,WAAA,GAAA,EAAA,KAAK,EAAC,wBAAwB,GAAA;AAChC,MAAA,WAAA,GAAA,EAAA,KAAA,EAAA,0BAAA,GAAA;;;;;;;;;;;;;;;;;;;;;uBA1ELA;AAAM,IAAA,KAAA,EAAA,QAAA,CAAA,SAAA;AAAA,IAAA,IAAA,EAAA,QAAA,CAAA,QAAA;AAAA,IAAA,WAAA,EAAA,MAAA,CAAA,WAAA;;2BACyB;OAAX,KAAO,CAAA,OAAA;mCAAI,CAAA,wBAAA,EAAA,EAAA,GAAA,EAAA,CAAA,EAAA,CAAA;WAGX,MAAO,CAAA,OAAA,KAAA,eAAA;0BAA5BC,kBAmBW,CAAAC,QAAA,EAAA,EAAA,GAAA,EAAA,CAAA,EAAA,EAAA;eAjBH,KAAM,CAAA,MAAA;gCADbD,kBAYO,CAAA,MAAA,EAAA;;AAVN,oBAAA,KAAK,EAAC,oBAAO;oBACZ,KAAG,EAAA,QAAA,CAAA,YAAA,CAAA,KAAA,CAAA,MAAA,CAAA;;oBACJE,WAAwB,CAAA,oBAAA,EAAA,EAAZ,IAAI,EAAE,EAAE,EAAA,CAAA;oBACpBC,kBAGwC,CAAA,GAAA,EAAA;sBAFtC,IAAE,EAAA,QAAA,CAAA,QAAA,CAAA,KAAA,CAAA,MAAA,CAAA;AACH,sBAAA,MAAM,EAAC,QAAQ;AACf,sBAAA,GAAE,EAAE,UAAU;AAAI,qBAAA,EAAAC,eAAA,CAAA,QAAA,CAAA,SAAS,CAAC,KAAM,CAAA,MAAA,CAAA,CAAA,EAAA,CAAA,oBAAA,UAAA,CAAA;AACvB,oBAAA,CAAA,QAAA,CAAA,aAAa,CAAC,KAAM,CAAA,MAAA,CAAA;yDAE3B,kBAAA,EAFuD,KACzD,GAAGA,eAAA,CAAA,QAAA,CAAA,aAAa,CAAC,KAAM,CAAA,MAAA,CAAA,CAAA,EAAA,CAAA,YAAA;;;mBAGxB,MAAA,CAAA,KAAA;qDAGI,kBAAA,EAAA;iCAFkB,CAAA,oBAAA,EAAA,EAAZ,IAAI,EAAE,EAAE,EAAA,CAAA;AAClB,sBAAAD,kBAAA,CAAc,8BAAP,MAAK,CAAA,KAAA,CAAA,EAAA,CAAA,YAAA;;kCAEbH,kBAA8D,CAAA,MAAA,EAA9D,UAA8D,EAAAI,eAAA,CAApB,MAAU,CAAA,UAAA,CAAA,EAAA,CAAA,YAAA,CAAA;;AAIlC,YAAA,CAAA,MAAA,CAAA,OAAO,yBAAyB,MAAO,CAAA,OAAA,KAAA,eAAA;4BAA1DJ,kBA+BS,CAAAC,QAAA,EAAA,EAAA,GAAA,EAAA,CAAA,EAAA,EAAA;iBA9BC,KAAQ,CAAA,QAAA;oDAEb,CAAA,KAAA,YAAA,EAAAG,eAAA,CADF,KAAE,CAAA,QAAA,CAAA,EAAA,CAAA,YAAA;AAEH,oBAAA,CAAA,KAAA,CAAA,MAAA,CAAA,MAAA,KAAA,CAAA;sDAEG,CAAA,KAAA,YAAA,EAAAA,eAAA,CADF,MAAU,CAAA,UAAA,CAAA,EAAA,CAAA,YAAA;AAEX,uBAAAC,SAAA,EAAA,EAAAL,kBAAA,CAuBG,OAvBH,UAuBG,EAAA;AAtBL,wBAAAG,kBAAA,QAAA;0BACCD,WAAG,CAAA,oBAAA,EAAA;AAAA,4BAAA,IAAA,EAAA,EAAA;AAAA,4BAAA,KAAA,EAAA,6BAAA;;AACH,0BAAAC,kBAAA,CAAO,gCAAA,QAAA,CAAA,aAAA,CAAA,EAAA,CAAA,YAAA;;yBAEG,QAAU,CAAA,UAAA;AAArB,6BAAAE,SAAA,EAAA,EAAAL,kBAAA,CAQM,OARN,WAQM,EAAA;8BAPLG,kBAGG,CAAA,GAAA,EAAA;gCAFA,IAAG,EAAE,QAAQ,CAAA,QAAA,CAAC,QAAU,CAAA,UAAA,CAAA;AAC3B,gCAAA,MAAO,EAAC,QAAI;AACX,gCAAA,GAAE,EAAA,UAAA;AAAA,+BAAA,EAAAC,eAAA,CAAA,QAAA,CAAA,SAAA,CAAA,QAAA,CAAA,UAAA,CAAA,CAAA,EAAA,CAAA,oBAAA,WAAA,CAAA;AACS,8BAAA,CAAA,QAAA,CAAA,aAAa,CAAC,QAAU,CAAA,UAAA,CAAA;AAApC,mCAAAC,SAAA,EAAA,EAAAL,kBAAA,CAEG,QAFH,WAEG,EAF2D,KAC3D,GAAGI,eAAA,CAAA,QAAA,CAAA,aAAQ,CAAA,QAAA,CAAA,UAAA,CAAA,CAAA,EAAA,CAAA,YAAA;;;;yBAGb,QAAA,CAAA,UAAA,IAAA,QAAA,CAAA,WAAA,CAAA,QAAA,CAAA,UAAA,CAAA;AAAF,6BAAAC,SAAA,EAAA,EAAAL,kBAAA,CAEM,KAFN,EAAA,WAEM,EADFI,eAAA,CAAA,QAAA,CAAA,WAAW,CAAA,QAAW,CAAA,UAAA,CAAA,CAAA,EAAA,CAAA,YAAA;;AAEf,wBAAA,CAAA,KAAA,CAAA,MAAM,CAAC,MAAM,GAAA,CAAA;6DAKlB,kBAAA,EAAA;8BAJLD,kBAGG,CAAA,GAAA,EAAA;AAFD,gCAAA,IAAI,EAAE,MAAU,CAAA,UAAA;AACjB,gCAAA,MAAM,EAAC,QAAQ;mCACb,EAAA,UAAA;iDAAA,MAAA,CAAA,YAAA,CAAA,EAAA,CAAA,oBAAA,WAAA,CAAA;;;;;;iBAOK,KAAA,CAAA,QAAA;kCAANH,kBAEE,CAAA,KAAA,EAFF,WAEE,EAAAI,eAAA,CADF,KAAI,CAAA,QAAA,CAAA,EAAA,CAAA,YAAA;AAEQ,oBAAA,CAAA,KAAA,CAAA,MAAM,CAAC,MAAI,KAAA,CAAA;oCAAvBJ,kBAEE,CAAA,KAAA,EAFF,WAEE,EAAAI,eAAA,CADF,MAAK,CAAA,UAAA,CAAA,EAAA,CAAA,YAAA;;0CAGRJ,8CACiB,QAAe,CAAA,eAAA,EAAA,CAAxB,KAAK,KAAA;+CADbA;4BAEC,GAAA,EAAA,QAAA,CAAA,QAAA,CAAA,KAAA,CAAA;AACA,4BAAA,KAAK,EAAC,mBAAW;;4BACjBE,WAAc,CAAA,oBAAA,EAAA;AAAF,8BAAA,IAAE,EAAA,EAAA;AAAA,8BAAA,KAAA,EAAA,wBAAA;;AACd,4BAAAC,kBAAA,QAAA;AACC,8BAAAA,kBAAA,QAAA;gCACCA;kCACE,IAAI,EAAE,QAAA,CAAA,QAAA,CAAA,KAAA,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -10,6 +10,7 @@ import Plus from 'vue-material-design-icons/Plus.vue';
10
10
  import __sfc_main$1 from '../../../components/CnMapPoiCreate/CnMapPoiCreate.vue.js';
11
11
  import __sfc_main from '../../../components/CnMapPoiPicker/CnMapPoiPicker.vue.js';
12
12
  import { stripMarker } from '../../utils/marker.js';
13
+ import { safeHref } from '../../../utils/safeHref.js';
13
14
  import { prefixUrl, buildHeaders } from '../../../utils/headers.js';
14
15
 
15
16
  const COMMENT_MAX_CHARS = 160;
@@ -210,8 +211,9 @@ var script = {
210
211
  return `${lat.toFixed(5)}, ${lng.toFixed(5)}`
211
212
  },
212
213
 
214
+ // A provider URL is used only when it is a safe scheme; else the app link.
213
215
  pointUrl(point) {
214
- if (typeof point.url === 'string' && point.url !== '') {
216
+ if (typeof point.url === 'string' && point.url !== '' && safeHref(point.url) !== '#') {
215
217
  return point.url
216
218
  }
217
219
  if (this.pointHasCoords(point)) {