@conduction/nextcloud-vue 2.85.0 → 2.87.0

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 (301) hide show
  1. package/dist/esm/components/CnAdvancedFormDialog/CnMetadataTab.vue.js +1 -1
  2. package/dist/esm/components/CnAdvancedFormDialog/CnMetadataTab.vue2.js +3 -2
  3. package/dist/esm/components/CnAdvancedFormDialog/CnMetadataTab.vue2.js.map +1 -1
  4. package/dist/esm/components/CnAdvancedFormDialog/CnMetadataTab.vue3.js.map +1 -1
  5. package/dist/esm/components/CnAdvancedFormDialog/CnPropertyValueCell.vue.js +1 -1
  6. package/dist/esm/components/CnAdvancedFormDialog/CnPropertyValueCell.vue2.js +5 -4
  7. package/dist/esm/components/CnAdvancedFormDialog/CnPropertyValueCell.vue2.js.map +1 -1
  8. package/dist/esm/components/CnAdvancedFormDialog/CnPropertyValueCell.vue3.js.map +1 -1
  9. package/dist/esm/components/CnAnalyticsReportPicker/CnAnalyticsReportPicker.vue.js +1 -1
  10. package/dist/esm/components/CnAnalyticsReportPicker/CnAnalyticsReportPicker.vue2.js +2 -1
  11. package/dist/esm/components/CnAnalyticsReportPicker/CnAnalyticsReportPicker.vue2.js.map +1 -1
  12. package/dist/esm/components/CnAnalyticsReportPicker/CnAnalyticsReportPicker.vue3.js.map +1 -1
  13. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue.js +1 -1
  14. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue2.js +2 -1
  15. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue2.js.map +1 -1
  16. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue3.js.map +1 -1
  17. package/dist/esm/components/CnBoardView/CnBoardView.vue.js +1 -1
  18. package/dist/esm/components/CnBoardView/CnBoardView.vue2.js +156 -14
  19. package/dist/esm/components/CnBoardView/CnBoardView.vue2.js.map +1 -1
  20. package/dist/esm/components/CnBoardView/CnBoardView.vue3.js +105 -74
  21. package/dist/esm/components/CnBoardView/CnBoardView.vue3.js.map +1 -1
  22. package/dist/esm/components/CnCalendarEventPicker/CnCalendarEventPicker.vue.js +1 -1
  23. package/dist/esm/components/CnCalendarEventPicker/CnCalendarEventPicker.vue2.js +2 -1
  24. package/dist/esm/components/CnCalendarEventPicker/CnCalendarEventPicker.vue2.js.map +1 -1
  25. package/dist/esm/components/CnCalendarEventPicker/CnCalendarEventPicker.vue3.js.map +1 -1
  26. package/dist/esm/components/CnCalendarWidget/CnCalendarWidget.vue.js +1 -1
  27. package/dist/esm/components/CnCalendarWidget/CnCalendarWidget.vue2.js +2 -1
  28. package/dist/esm/components/CnCalendarWidget/CnCalendarWidget.vue2.js.map +1 -1
  29. package/dist/esm/components/CnCalendarWidget/CnCalendarWidget.vue3.js.map +1 -1
  30. package/dist/esm/components/CnChartWidget/CnChartWidget.vue.js +1 -1
  31. package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js +6 -5
  32. package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js.map +1 -1
  33. package/dist/esm/components/CnChartWidget/CnChartWidget.vue3.js.map +1 -1
  34. package/dist/esm/components/CnCollectivePagePicker/CnCollectivePagePicker.vue.js +1 -1
  35. package/dist/esm/components/CnCollectivePagePicker/CnCollectivePagePicker.vue2.js +2 -1
  36. package/dist/esm/components/CnCollectivePagePicker/CnCollectivePagePicker.vue2.js.map +1 -1
  37. package/dist/esm/components/CnCollectivePagePicker/CnCollectivePagePicker.vue3.js.map +1 -1
  38. package/dist/esm/components/CnConversationThread/CnConversationThread.vue.js +1 -1
  39. package/dist/esm/components/CnConversationThread/CnConversationThread.vue2.js +2 -1
  40. package/dist/esm/components/CnConversationThread/CnConversationThread.vue2.js.map +1 -1
  41. package/dist/esm/components/CnConversationThread/CnConversationThread.vue3.js.map +1 -1
  42. package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue.js +1 -1
  43. package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue2.js +2 -1
  44. package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue2.js.map +1 -1
  45. package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue3.js.map +1 -1
  46. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
  47. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +2 -1
  48. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
  49. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
  50. package/dist/esm/components/CnDataMatrix/CnDataMatrix.vue.js +1 -1
  51. package/dist/esm/components/CnDataMatrix/CnDataMatrix.vue2.js +3 -1
  52. package/dist/esm/components/CnDataMatrix/CnDataMatrix.vue2.js.map +1 -1
  53. package/dist/esm/components/CnDataMatrix/CnDataMatrix.vue3.js.map +1 -1
  54. package/dist/esm/components/CnDetailPage/CnDetailHeaderChips.vue2.js +2 -1
  55. package/dist/esm/components/CnDetailPage/CnDetailHeaderChips.vue2.js.map +1 -1
  56. package/dist/esm/components/CnDetailPage/CnDetailHeaderChips.vue3.js.map +1 -1
  57. package/dist/esm/components/CnEmailPicker/CnEmailPicker.vue.js +1 -1
  58. package/dist/esm/components/CnEmailPicker/CnEmailPicker.vue2.js +2 -1
  59. package/dist/esm/components/CnEmailPicker/CnEmailPicker.vue2.js.map +1 -1
  60. package/dist/esm/components/CnEmailPicker/CnEmailPicker.vue3.js.map +1 -1
  61. package/dist/esm/components/CnFederationStatus/CnFederationStatus.vue.js +1 -1
  62. package/dist/esm/components/CnFederationStatus/CnFederationStatus.vue2.js +3 -1
  63. package/dist/esm/components/CnFederationStatus/CnFederationStatus.vue2.js.map +1 -1
  64. package/dist/esm/components/CnFederationStatus/CnFederationStatus.vue3.js.map +1 -1
  65. package/dist/esm/components/CnFileManager/CnFileManager.vue.js +1 -1
  66. package/dist/esm/components/CnFileManager/CnFileManager.vue2.js +3 -1
  67. package/dist/esm/components/CnFileManager/CnFileManager.vue2.js.map +1 -1
  68. package/dist/esm/components/CnFileManager/CnFileManager.vue3.js.map +1 -1
  69. package/dist/esm/components/CnFilePreview/CnFilePreview.vue.js +1 -1
  70. package/dist/esm/components/CnFilePreview/CnFilePreview.vue2.js +2 -1
  71. package/dist/esm/components/CnFilePreview/CnFilePreview.vue2.js.map +1 -1
  72. package/dist/esm/components/CnFilePreview/CnFilePreview.vue3.js.map +1 -1
  73. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue.js +1 -1
  74. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +49 -1
  75. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
  76. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +3 -3
  77. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
  78. package/dist/esm/components/CnFollowToggle/CnFollowToggle.vue.js +1 -1
  79. package/dist/esm/components/CnFollowToggle/CnFollowToggle.vue2.js +2 -1
  80. package/dist/esm/components/CnFollowToggle/CnFollowToggle.vue2.js.map +1 -1
  81. package/dist/esm/components/CnFollowToggle/CnFollowToggle.vue3.js.map +1 -1
  82. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +30 -5
  83. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  84. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  85. package/dist/esm/components/CnNotesCard/CnNotesCard.vue.js +1 -1
  86. package/dist/esm/components/CnNotesCard/CnNotesCard.vue2.js +2 -1
  87. package/dist/esm/components/CnNotesCard/CnNotesCard.vue2.js.map +1 -1
  88. package/dist/esm/components/CnNotesCard/CnNotesCard.vue3.js.map +1 -1
  89. package/dist/esm/components/CnObjectCalendar/CnObjectCalendar.vue.js +1 -1
  90. package/dist/esm/components/CnObjectCalendar/CnObjectCalendar.vue2.js +3 -2
  91. package/dist/esm/components/CnObjectCalendar/CnObjectCalendar.vue2.js.map +1 -1
  92. package/dist/esm/components/CnObjectCalendar/CnObjectCalendar.vue3.js.map +1 -1
  93. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue.js +1 -1
  94. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js +28 -0
  95. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js.map +1 -1
  96. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue3.js +82 -75
  97. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue3.js.map +1 -1
  98. package/dist/esm/components/CnObjectMetadataWidget/CnObjectMetadataWidget.vue.js +1 -1
  99. package/dist/esm/components/CnObjectMetadataWidget/CnObjectMetadataWidget.vue2.js +5 -4
  100. package/dist/esm/components/CnObjectMetadataWidget/CnObjectMetadataWidget.vue2.js.map +1 -1
  101. package/dist/esm/components/CnObjectMetadataWidget/CnObjectMetadataWidget.vue3.js.map +1 -1
  102. package/dist/esm/components/CnObjectSidebar/CnAuditTrailTab.vue.js +1 -1
  103. package/dist/esm/components/CnObjectSidebar/CnAuditTrailTab.vue2.js +2 -1
  104. package/dist/esm/components/CnObjectSidebar/CnAuditTrailTab.vue2.js.map +1 -1
  105. package/dist/esm/components/CnObjectSidebar/CnAuditTrailTab.vue3.js.map +1 -1
  106. package/dist/esm/components/CnObjectSidebar/CnFlowTasksPanel.vue.js +1 -1
  107. package/dist/esm/components/CnObjectSidebar/CnFlowTasksPanel.vue2.js +2 -1
  108. package/dist/esm/components/CnObjectSidebar/CnFlowTasksPanel.vue2.js.map +1 -1
  109. package/dist/esm/components/CnObjectSidebar/CnFlowTasksPanel.vue3.js.map +1 -1
  110. package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue.js +1 -1
  111. package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue2.js +2 -1
  112. package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue2.js.map +1 -1
  113. package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue3.js.map +1 -1
  114. package/dist/esm/components/CnObjectSidebar/CnTasksTab.vue.js +1 -1
  115. package/dist/esm/components/CnObjectSidebar/CnTasksTab.vue2.js +2 -1
  116. package/dist/esm/components/CnObjectSidebar/CnTasksTab.vue2.js.map +1 -1
  117. package/dist/esm/components/CnObjectSidebar/CnTasksTab.vue3.js.map +1 -1
  118. package/dist/esm/components/CnPropertySourceField/CnPropertySourceField.vue.js +1 -1
  119. package/dist/esm/components/CnPropertySourceField/CnPropertySourceField.vue2.js +2 -1
  120. package/dist/esm/components/CnPropertySourceField/CnPropertySourceField.vue2.js.map +1 -1
  121. package/dist/esm/components/CnPropertySourceField/CnPropertySourceField.vue3.js.map +1 -1
  122. package/dist/esm/components/CnRoadmapItem/CnRoadmapItem.vue.js +1 -1
  123. package/dist/esm/components/CnRoadmapItem/CnRoadmapItem.vue2.js +2 -1
  124. package/dist/esm/components/CnRoadmapItem/CnRoadmapItem.vue2.js.map +1 -1
  125. package/dist/esm/components/CnRoadmapItem/CnRoadmapItem.vue3.js.map +1 -1
  126. package/dist/esm/components/CnSchemaFormDialog/CnSchemaFormDialog.vue2.js +2 -0
  127. package/dist/esm/components/CnSchemaFormDialog/CnSchemaFormDialog.vue2.js.map +1 -1
  128. package/dist/esm/components/CnSchemaFormDialog/CnSchemaFormDialog.vue3.js +2 -2
  129. package/dist/esm/components/CnSchemaFormDialog/CnSchemaFormDialog.vue3.js.map +1 -1
  130. package/dist/esm/components/CnStatsBlock/CnStatsBlock.vue.js +1 -1
  131. package/dist/esm/components/CnStatsBlock/CnStatsBlock.vue2.js +2 -1
  132. package/dist/esm/components/CnStatsBlock/CnStatsBlock.vue2.js.map +1 -1
  133. package/dist/esm/components/CnStatsBlock/CnStatsBlock.vue3.js.map +1 -1
  134. package/dist/esm/components/CnSummaryAggregates/CnSummaryAggregates.vue.js +1 -1
  135. package/dist/esm/components/CnSummaryAggregates/CnSummaryAggregates.vue2.js +2 -1
  136. package/dist/esm/components/CnSummaryAggregates/CnSummaryAggregates.vue2.js.map +1 -1
  137. package/dist/esm/components/CnSummaryAggregates/CnSummaryAggregates.vue3.js.map +1 -1
  138. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue.js +1 -1
  139. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +36 -0
  140. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
  141. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js +55 -40
  142. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
  143. package/dist/esm/components/CnTasksCard/CnTasksCard.vue.js +1 -1
  144. package/dist/esm/components/CnTasksCard/CnTasksCard.vue2.js +2 -1
  145. package/dist/esm/components/CnTasksCard/CnTasksCard.vue2.js.map +1 -1
  146. package/dist/esm/components/CnTasksCard/CnTasksCard.vue3.js.map +1 -1
  147. package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue.js +1 -1
  148. package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue2.js +2 -1
  149. package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue2.js.map +1 -1
  150. package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue3.js.map +1 -1
  151. package/dist/esm/components/CnTranslatedBadge/CnTranslatedBadge.vue.js +1 -1
  152. package/dist/esm/components/CnTranslatedBadge/CnTranslatedBadge.vue2.js +2 -1
  153. package/dist/esm/components/CnTranslatedBadge/CnTranslatedBadge.vue2.js.map +1 -1
  154. package/dist/esm/components/CnVersionHistory/CnVersionHistory.vue.js +1 -1
  155. package/dist/esm/components/CnVersionHistory/CnVersionHistory.vue2.js +2 -1
  156. package/dist/esm/components/CnVersionHistory/CnVersionHistory.vue2.js.map +1 -1
  157. package/dist/esm/components/CnVersionHistory/CnVersionHistory.vue3.js.map +1 -1
  158. package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue.js +1 -1
  159. package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue2.js +3 -26
  160. package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue2.js.map +1 -1
  161. package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue3.js.map +1 -1
  162. package/dist/esm/dialogs/CnNoteHistoryDialog.vue.js +1 -1
  163. package/dist/esm/dialogs/CnNoteHistoryDialog.vue2.js +2 -1
  164. package/dist/esm/dialogs/CnNoteHistoryDialog.vue2.js.map +1 -1
  165. package/dist/esm/dialogs/CnNoteHistoryDialog.vue3.js.map +1 -1
  166. package/dist/esm/integrations/builtin/activity/CnActivityCard.vue.js +1 -1
  167. package/dist/esm/integrations/builtin/activity/CnActivityCard.vue2.js +3 -2
  168. package/dist/esm/integrations/builtin/activity/CnActivityCard.vue2.js.map +1 -1
  169. package/dist/esm/integrations/builtin/activity/CnActivityCard.vue3.js.map +1 -1
  170. package/dist/esm/integrations/builtin/activity/CnActivityTab.vue.js +1 -1
  171. package/dist/esm/integrations/builtin/activity/CnActivityTab.vue2.js +4 -3
  172. package/dist/esm/integrations/builtin/activity/CnActivityTab.vue2.js.map +1 -1
  173. package/dist/esm/integrations/builtin/activity/CnActivityTab.vue3.js.map +1 -1
  174. package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue.js +1 -1
  175. package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue2.js +3 -2
  176. package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue2.js.map +1 -1
  177. package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue3.js.map +1 -1
  178. package/dist/esm/integrations/builtin/calendar/CnCalendarCard.vue.js +1 -1
  179. package/dist/esm/integrations/builtin/calendar/CnCalendarCard.vue2.js +2 -1
  180. package/dist/esm/integrations/builtin/calendar/CnCalendarCard.vue2.js.map +1 -1
  181. package/dist/esm/integrations/builtin/calendar/CnCalendarCard.vue3.js.map +1 -1
  182. package/dist/esm/integrations/builtin/calendar/CnCalendarTab.vue.js +1 -1
  183. package/dist/esm/integrations/builtin/calendar/CnCalendarTab.vue2.js +6 -5
  184. package/dist/esm/integrations/builtin/calendar/CnCalendarTab.vue2.js.map +1 -1
  185. package/dist/esm/integrations/builtin/calendar/CnCalendarTab.vue3.js.map +1 -1
  186. package/dist/esm/integrations/builtin/contactmoment/CnContactmomentCard.vue.js +1 -1
  187. package/dist/esm/integrations/builtin/contactmoment/CnContactmomentCard.vue2.js +2 -1
  188. package/dist/esm/integrations/builtin/contactmoment/CnContactmomentCard.vue2.js.map +1 -1
  189. package/dist/esm/integrations/builtin/contactmoment/CnContactmomentCard.vue3.js.map +1 -1
  190. package/dist/esm/integrations/builtin/cospend/CnCospendTab.vue.js +1 -1
  191. package/dist/esm/integrations/builtin/cospend/CnCospendTab.vue2.js +2 -1
  192. package/dist/esm/integrations/builtin/cospend/CnCospendTab.vue2.js.map +1 -1
  193. package/dist/esm/integrations/builtin/cospend/CnCospendTab.vue3.js.map +1 -1
  194. package/dist/esm/integrations/builtin/deck/CnDeckTab.vue.js +1 -1
  195. package/dist/esm/integrations/builtin/deck/CnDeckTab.vue2.js +2 -1
  196. package/dist/esm/integrations/builtin/deck/CnDeckTab.vue2.js.map +1 -1
  197. package/dist/esm/integrations/builtin/deck/CnDeckTab.vue3.js.map +1 -1
  198. package/dist/esm/integrations/builtin/email/CnEmailCard.vue.js +1 -1
  199. package/dist/esm/integrations/builtin/email/CnEmailCard.vue2.js +2 -1
  200. package/dist/esm/integrations/builtin/email/CnEmailCard.vue2.js.map +1 -1
  201. package/dist/esm/integrations/builtin/email/CnEmailCard.vue3.js.map +1 -1
  202. package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionCard.vue.js +1 -1
  203. package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionCard.vue2.js +2 -1
  204. package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionCard.vue2.js.map +1 -1
  205. package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionCard.vue3.js.map +1 -1
  206. package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionTab.vue.js +1 -1
  207. package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionTab.vue2.js +2 -1
  208. package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionTab.vue2.js.map +1 -1
  209. package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionTab.vue3.js.map +1 -1
  210. package/dist/esm/integrations/builtin/photos/CnPhotosTab.vue.js +1 -1
  211. package/dist/esm/integrations/builtin/photos/CnPhotosTab.vue2.js +2 -1
  212. package/dist/esm/integrations/builtin/photos/CnPhotosTab.vue2.js.map +1 -1
  213. package/dist/esm/integrations/builtin/photos/CnPhotosTab.vue3.js.map +1 -1
  214. package/dist/esm/l10n/en.json.js +35 -2
  215. package/dist/esm/l10n/en.json.js.map +1 -1
  216. package/dist/esm/l10n/nl.json.js +38 -2
  217. package/dist/esm/l10n/nl.json.js.map +1 -1
  218. package/dist/esm/library-style.js +1 -1
  219. package/dist/esm/nextcloud-vue.css +1068 -962
  220. package/dist/esm/store/useObjectStore.js +7 -2
  221. package/dist/esm/store/useObjectStore.js.map +1 -1
  222. package/dist/esm/utils/boardColumns.js +50 -1
  223. package/dist/esm/utils/boardColumns.js.map +1 -1
  224. package/dist/esm/utils/boardReferenceColumns.js +115 -4
  225. package/dist/esm/utils/boardReferenceColumns.js.map +1 -1
  226. package/dist/esm/utils/contentHeaderLink.js +48 -1
  227. package/dist/esm/utils/contentHeaderLink.js.map +1 -1
  228. package/dist/esm/utils/schema.js +5 -4
  229. package/dist/esm/utils/schema.js.map +1 -1
  230. package/dist/esm/utils/userLocale.js +30 -0
  231. package/dist/esm/utils/userLocale.js.map +1 -0
  232. package/dist/esm/utils/validateManifestV2.compiled.js +2 -2
  233. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  234. package/dist/nextcloud-vue.cjs.js +1088 -476
  235. package/dist/nextcloud-vue.cjs.js.map +2 -2
  236. package/dist/nextcloud-vue.css +1068 -962
  237. package/l10n/en.json +35 -2
  238. package/l10n/nl.json +38 -2
  239. package/package.json +1 -1
  240. package/src/components/CnAdvancedFormDialog/CnMetadataTab.vue +3 -2
  241. package/src/components/CnAdvancedFormDialog/CnPropertyValueCell.vue +5 -4
  242. package/src/components/CnAnalyticsReportPicker/CnAnalyticsReportPicker.vue +2 -1
  243. package/src/components/CnAuditTrailCard/CnAuditTrailCard.vue +2 -1
  244. package/src/components/CnBoardView/CnBoardView.vue +189 -24
  245. package/src/components/CnCalendarEventPicker/CnCalendarEventPicker.vue +2 -1
  246. package/src/components/CnCalendarWidget/CnCalendarWidget.vue +2 -1
  247. package/src/components/CnChartWidget/CnChartWidget.vue +6 -5
  248. package/src/components/CnCollectivePagePicker/CnCollectivePagePicker.vue +2 -1
  249. package/src/components/CnConversationThread/CnConversationThread.vue +2 -1
  250. package/src/components/CnCountdownWidget/CnCountdownWidget.vue +2 -1
  251. package/src/components/CnDashboardPage/CnDashboardPage.vue +2 -1
  252. package/src/components/CnDataMatrix/CnDataMatrix.vue +2 -1
  253. package/src/components/CnDetailPage/CnDetailHeaderChips.vue +2 -1
  254. package/src/components/CnEmailPicker/CnEmailPicker.vue +2 -1
  255. package/src/components/CnFederationStatus/CnFederationStatus.vue +3 -1
  256. package/src/components/CnFileManager/CnFileManager.vue +2 -1
  257. package/src/components/CnFilePreview/CnFilePreview.vue +2 -1
  258. package/src/components/CnFilesBrowser/CnFilesBrowser.vue +54 -6
  259. package/src/components/CnFollowToggle/CnFollowToggle.vue +2 -1
  260. package/src/components/CnIndexPage/CnIndexPage.vue +30 -4
  261. package/src/components/CnNotesCard/CnNotesCard.vue +2 -1
  262. package/src/components/CnObjectCalendar/CnObjectCalendar.vue +3 -2
  263. package/src/components/CnObjectDataWidget/CnObjectDataWidget.vue +36 -0
  264. package/src/components/CnObjectMetadataWidget/CnObjectMetadataWidget.vue +5 -4
  265. package/src/components/CnObjectSidebar/CnAuditTrailTab.vue +2 -1
  266. package/src/components/CnObjectSidebar/CnFlowTasksPanel.vue +2 -1
  267. package/src/components/CnObjectSidebar/CnNotesTab.vue +2 -1
  268. package/src/components/CnObjectSidebar/CnTasksTab.vue +2 -1
  269. package/src/components/CnPropertySourceField/CnPropertySourceField.vue +2 -1
  270. package/src/components/CnRoadmapItem/CnRoadmapItem.vue +2 -1
  271. package/src/components/CnSchemaFormDialog/CnSchemaFormDialog.vue +4 -2
  272. package/src/components/CnStatsBlock/CnStatsBlock.vue +2 -1
  273. package/src/components/CnSummaryAggregates/CnSummaryAggregates.vue +2 -1
  274. package/src/components/CnTabsWidget/CnTabsWidget.vue +71 -0
  275. package/src/components/CnTasksCard/CnTasksCard.vue +2 -1
  276. package/src/components/CnTimelineWidget/CnTimelineWidget.vue +2 -1
  277. package/src/components/CnTranslatedBadge/CnTranslatedBadge.vue +2 -1
  278. package/src/components/CnVersionHistory/CnVersionHistory.vue +2 -1
  279. package/src/components/CnWidgetWrapper/CnWidgetWrapper.vue +3 -26
  280. package/src/css/board.css +40 -0
  281. package/src/dialogs/CnNoteHistoryDialog.vue +2 -1
  282. package/src/integrations/builtin/activity/CnActivityCard.vue +3 -2
  283. package/src/integrations/builtin/activity/CnActivityTab.vue +4 -3
  284. package/src/integrations/builtin/analytics/CnAnalyticsTab.vue +3 -2
  285. package/src/integrations/builtin/calendar/CnCalendarCard.vue +2 -1
  286. package/src/integrations/builtin/calendar/CnCalendarTab.vue +6 -5
  287. package/src/integrations/builtin/contactmoment/CnContactmomentCard.vue +2 -1
  288. package/src/integrations/builtin/cospend/CnCospendTab.vue +2 -1
  289. package/src/integrations/builtin/deck/CnDeckTab.vue +2 -1
  290. package/src/integrations/builtin/email/CnEmailCard.vue +2 -1
  291. package/src/integrations/builtin/field-inspection/CnFieldInspectionCard.vue +2 -1
  292. package/src/integrations/builtin/field-inspection/CnFieldInspectionTab.vue +2 -1
  293. package/src/integrations/builtin/photos/CnPhotosTab.vue +2 -1
  294. package/src/schemas/app-manifest-v2.schema.json +4 -1
  295. package/src/store/useObjectStore.js +7 -2
  296. package/src/utils/boardColumns.js +49 -0
  297. package/src/utils/boardReferenceColumns.js +114 -3
  298. package/src/utils/contentHeaderLink.js +47 -0
  299. package/src/utils/schema.js +5 -4
  300. package/src/utils/userLocale.js +26 -0
  301. package/src/utils/validateManifestV2.compiled.js +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"CnCalendarTab.vue2.js","sources":["../../../../../src/integrations/builtin/calendar/CnCalendarTab.vue"],"sourcesContent":["<!--\n CnCalendarTab — bespoke sidebar tab for the `calendar` integration.\n\n Tier-2 rewire (additive link-table strategy):\n - \"Add meeting\" button opens CnCalendarEventCreate modal that POSTs\n to /api/objects/{r}/{s}/{id}/events (writes X-OR-* on the VEVENT\n AND a link-table row).\n - \"Link existing meeting\" button opens CnCalendarEventPicker modal\n that POSTs to /api/objects/{r}/{s}/{id}/events/link (writes ONLY\n a link-table row — we may not own the VEVENT).\n - Per-row UNLINK action: DELETE /events/{eventUid}/link — removes\n the link only, the VEVENT survives on the user's calendar.\n - Per-row DELETE action: DELETE /events/{eventId} — destroys the\n VEVENT (legacy semantics — also cleans up the link-table row).\n\n Read path: GET /api/objects/{r}/{s}/{id}/events returns the UNION of\n link-table rows and the legacy X-OR-* CalDAV scan, deduped by\n (calendarUri, eventUid) and annotated with a `source` field\n (link-table / xor-only / both).\n\n Visual fidelity (NC Calendar): each row foregrounds a date block (month\n abbreviation + day-of-month, accented like the Calendar app's agenda),\n a start–end time range, a bold event title, the location with a\n map-marker icon, an attendee avatar stack with an overflow count, and a\n CnStatusBadge for the VEVENT status (confirmed / tentative / cancelled).\n\n ADR-004 modal isolation: every modal lives in its own .vue file under\n `src/components/CnCalendar*/` and is imported here as a child component.\n-->\n<template>\n\t<div class=\"cn-sidebar-tab cn-calendar-tab\">\n\t\t<!-- Action bar (replaces the inline create form) -->\n\t\t<div class=\"cn-calendar-tab__action-row\">\n\t\t\t<NcButton variant=\"primary\" @click=\"openCreate\">\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{{ addEventLabel }}\n\t\t\t</NcButton>\n\t\t\t<NcButton variant=\"secondary\" @click=\"openPicker\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<LinkVariant :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ linkExistingLabel }}\n\t\t\t</NcButton>\n\t\t</div>\n\n\t\t<!-- Error banner -->\n\t\t<div v-if=\"error\" class=\"cn-calendar-tab__banner cn-calendar-tab__banner--error\" role=\"alert\">\n\t\t\t<AlertCircleOutline :size=\"18\" />\n\t\t\t<span>{{ error }}</span>\n\t\t</div>\n\n\t\t<!-- Unavailable banner (503) -->\n\t\t<div v-if=\"degraded\" class=\"cn-calendar-tab__banner\" role=\"status\">\n\t\t\t<AlertCircleOutline :size=\"18\" />\n\t\t\t<span>{{ unavailableLabel }}</span>\n\t\t</div>\n\n\t\t<NcLoadingIcon v-if=\"loading\" />\n\t\t<div v-else-if=\"sortedEvents.length === 0\" class=\"cn-sidebar-tab__empty cn-calendar-tab__empty\">\n\t\t\t{{ noEventsLabel }}\n\t\t</div>\n\n\t\t<!-- Agenda -->\n\t\t<div v-else class=\"cn-calendar-tab__agenda\">\n\t\t\t<div v-if=\"upcomingEvents.length > 0\" class=\"cn-calendar-tab__group\">\n\t\t\t\t<h4 class=\"cn-calendar-tab__group-title\">\n\t\t\t\t\t{{ upcomingLabel }}\n\t\t\t\t</h4>\n\t\t\t\t<div\n\t\t\t\t\tv-for=\"ev in upcomingEvents\"\n\t\t\t\t\t:key=\"rowKey(ev)\"\n\t\t\t\t\tclass=\"cn-calendar-tab__event\">\n\t\t\t\t\t<!-- Date block (month + day) -->\n\t\t\t\t\t<div class=\"cn-calendar-tab__date\" aria-hidden=\"true\">\n\t\t\t\t\t\t<span class=\"cn-calendar-tab__date-month\">{{ monthOf(ev) }}</span>\n\t\t\t\t\t\t<span class=\"cn-calendar-tab__date-day\">{{ dayOf(ev) }}</span>\n\t\t\t\t\t</div>\n\n\t\t\t\t\t<!-- Main column: title, time range, location, attendees -->\n\t\t\t\t\t<div class=\"cn-calendar-tab__main\">\n\t\t\t\t\t\t<div class=\"cn-calendar-tab__title-row\">\n\t\t\t\t\t\t\t<span class=\"cn-calendar-tab__title\">{{ ev.summary || untitledLabel }}</span>\n\t\t\t\t\t\t\t<CnStatusBadge\n\t\t\t\t\t\t\t\tv-if=\"statusLabel(ev)\"\n\t\t\t\t\t\t\t\t:label=\"statusLabel(ev)\"\n\t\t\t\t\t\t\t\t:variant=\"statusVariant(ev)\"\n\t\t\t\t\t\t\t\tsize=\"small\" />\n\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t<div class=\"cn-calendar-tab__when\">\n\t\t\t\t\t\t\t<ClockOutline :size=\"14\" class=\"cn-calendar-tab__meta-icon\" />\n\t\t\t\t\t\t\t<span>{{ formatTimeRange(ev) }}</span>\n\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t<div v-if=\"ev.location\" class=\"cn-calendar-tab__location\">\n\t\t\t\t\t\t\t<MapMarkerOutline :size=\"14\" class=\"cn-calendar-tab__meta-icon\" />\n\t\t\t\t\t\t\t<span class=\"cn-calendar-tab__location-text\">{{ ev.location }}</span>\n\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t<div v-if=\"attendeeList(ev).length > 0\" class=\"cn-calendar-tab__attendees\">\n\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\tv-for=\"(att, idx) in visibleAttendees(ev)\"\n\t\t\t\t\t\t\t\t:key=\"idx\"\n\t\t\t\t\t\t\t\tclass=\"cn-calendar-tab__avatar\"\n\t\t\t\t\t\t\t\t:title=\"att.name\">\n\t\t\t\t\t\t\t\t{{ att.initials }}\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\tv-if=\"extraAttendees(ev) > 0\"\n\t\t\t\t\t\t\t\tclass=\"cn-calendar-tab__avatar cn-calendar-tab__avatar--more\"\n\t\t\t\t\t\t\t\t:title=\"attendeeCountLabel(ev)\">\n\t\t\t\t\t\t\t\t+{{ extraAttendees(ev) }}\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\n\t\t\t\t\t<!-- Row actions -->\n\t\t\t\t\t<NcActions :forceMenu=\"true\" class=\"cn-calendar-tab__actions\">\n\t\t\t\t\t\t<NcActionButton :disabled=\"rowBusyKey === rowKey(ev)\" @click=\"unlink(ev)\">\n\t\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t\t<LinkVariantOff :size=\"20\" />\n\t\t\t\t\t\t\t</template>\n\t\t\t\t\t\t\t{{ unlinkLabel }}\n\t\t\t\t\t\t</NcActionButton>\n\t\t\t\t\t\t<NcActionButton :disabled=\"rowBusyKey === rowKey(ev)\" @click=\"deleteEvent(ev)\">\n\t\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t\t<Delete :size=\"20\" />\n\t\t\t\t\t\t\t</template>\n\t\t\t\t\t\t\t{{ deleteLabel }}\n\t\t\t\t\t\t</NcActionButton>\n\t\t\t\t\t</NcActions>\n\t\t\t\t</div>\n\t\t\t</div>\n\n\t\t\t<div v-if=\"pastEvents.length > 0\" class=\"cn-calendar-tab__group\">\n\t\t\t\t<h4 class=\"cn-calendar-tab__group-title cn-calendar-tab__group-title--past\">\n\t\t\t\t\t{{ pastLabel }}\n\t\t\t\t</h4>\n\t\t\t\t<div\n\t\t\t\t\tv-for=\"ev in pastEvents\"\n\t\t\t\t\t:key=\"rowKey(ev)\"\n\t\t\t\t\tclass=\"cn-calendar-tab__event cn-calendar-tab__event--past\">\n\t\t\t\t\t<div class=\"cn-calendar-tab__date cn-calendar-tab__date--past\" aria-hidden=\"true\">\n\t\t\t\t\t\t<span class=\"cn-calendar-tab__date-month\">{{ monthOf(ev) }}</span>\n\t\t\t\t\t\t<span class=\"cn-calendar-tab__date-day\">{{ dayOf(ev) }}</span>\n\t\t\t\t\t</div>\n\n\t\t\t\t\t<div class=\"cn-calendar-tab__main\">\n\t\t\t\t\t\t<div class=\"cn-calendar-tab__title-row\">\n\t\t\t\t\t\t\t<span class=\"cn-calendar-tab__title\">{{ ev.summary || untitledLabel }}</span>\n\t\t\t\t\t\t\t<CnStatusBadge\n\t\t\t\t\t\t\t\tv-if=\"statusLabel(ev)\"\n\t\t\t\t\t\t\t\t:label=\"statusLabel(ev)\"\n\t\t\t\t\t\t\t\t:variant=\"statusVariant(ev)\"\n\t\t\t\t\t\t\t\tsize=\"small\" />\n\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t<div class=\"cn-calendar-tab__when\">\n\t\t\t\t\t\t\t<ClockOutline :size=\"14\" class=\"cn-calendar-tab__meta-icon\" />\n\t\t\t\t\t\t\t<span>{{ formatTimeRange(ev) }}</span>\n\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t<div v-if=\"ev.location\" class=\"cn-calendar-tab__location\">\n\t\t\t\t\t\t\t<MapMarkerOutline :size=\"14\" class=\"cn-calendar-tab__meta-icon\" />\n\t\t\t\t\t\t\t<span class=\"cn-calendar-tab__location-text\">{{ ev.location }}</span>\n\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t<div v-if=\"attendeeList(ev).length > 0\" class=\"cn-calendar-tab__attendees\">\n\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\tv-for=\"(att, idx) in visibleAttendees(ev)\"\n\t\t\t\t\t\t\t\t:key=\"idx\"\n\t\t\t\t\t\t\t\tclass=\"cn-calendar-tab__avatar\"\n\t\t\t\t\t\t\t\t:title=\"att.name\">\n\t\t\t\t\t\t\t\t{{ att.initials }}\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\tv-if=\"extraAttendees(ev) > 0\"\n\t\t\t\t\t\t\t\tclass=\"cn-calendar-tab__avatar cn-calendar-tab__avatar--more\"\n\t\t\t\t\t\t\t\t:title=\"attendeeCountLabel(ev)\">\n\t\t\t\t\t\t\t\t+{{ extraAttendees(ev) }}\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\n\t\t\t\t\t<NcActions :forceMenu=\"true\" class=\"cn-calendar-tab__actions\">\n\t\t\t\t\t\t<NcActionButton :disabled=\"rowBusyKey === rowKey(ev)\" @click=\"unlink(ev)\">\n\t\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t\t<LinkVariantOff :size=\"20\" />\n\t\t\t\t\t\t\t</template>\n\t\t\t\t\t\t\t{{ unlinkLabel }}\n\t\t\t\t\t\t</NcActionButton>\n\t\t\t\t\t\t<NcActionButton :disabled=\"rowBusyKey === rowKey(ev)\" @click=\"deleteEvent(ev)\">\n\t\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t\t<Delete :size=\"20\" />\n\t\t\t\t\t\t\t</template>\n\t\t\t\t\t\t\t{{ deleteLabel }}\n\t\t\t\t\t\t</NcActionButton>\n\t\t\t\t\t</NcActions>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\n\t\t<!-- Picker modal (Tier-2 link existing flow) -->\n\t\t<CnCalendarEventPicker\n\t\t\tv-if=\"showPicker\"\n\t\t\t:apiBase=\"apiBase\"\n\t\t\t@link=\"onPickerLink\"\n\t\t\t@close=\"closePicker\" />\n\n\t\t<!-- Create modal (Tier-2 create-and-link flow) -->\n\t\t<CnCalendarEventCreate\n\t\t\tv-if=\"showCreate\"\n\t\t\t:register=\"register\"\n\t\t\t:schema=\"schema\"\n\t\t\t:objectId=\"objectId\"\n\t\t\t:apiBase=\"apiBase\"\n\t\t\t@created=\"onEventCreated\"\n\t\t\t@close=\"closeCreate\" />\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport {\n\tNcActionButton,\n\tNcActions,\n\tNcButton,\n\tNcLoadingIcon,\n} from '@nextcloud/vue'\nimport AlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue'\nimport ClockOutline from 'vue-material-design-icons/ClockOutline.vue'\nimport Delete from 'vue-material-design-icons/Delete.vue'\nimport LinkVariant from 'vue-material-design-icons/LinkVariant.vue'\nimport LinkVariantOff from 'vue-material-design-icons/LinkVariantOff.vue'\nimport MapMarkerOutline from 'vue-material-design-icons/MapMarkerOutline.vue'\nimport Plus from 'vue-material-design-icons/Plus.vue'\nimport CnCalendarEventCreate from '../../../components/CnCalendarEventCreate/CnCalendarEventCreate.vue'\nimport CnCalendarEventPicker from '../../../components/CnCalendarEventPicker/CnCalendarEventPicker.vue'\nimport CnStatusBadge from '../../../components/CnStatusBadge/CnStatusBadge.vue'\nimport { buildHeaders, prefixUrl } from '../../../utils/index.js'\n\nconst MAX_AVATARS = 3\n\n/**\n * CnCalendarTab — bespoke sidebar tab for the calendar integration.\n *\n * Tier-2 wiring: link/create flows are modal-driven (ADR-004 isolation),\n * and per-row actions distinguish UNLINK (preserves the VEVENT) from\n * DELETE (destroys the VEVENT). Rows mirror NC Calendar's agenda look:\n * date block, time range, title, location, attendee avatars + status.\n */\nexport default {\n\tname: 'CnCalendarTab',\n\n\tcomponents: {\n\t\tNcButton,\n\t\tNcActions,\n\t\tNcActionButton,\n\t\tNcLoadingIcon,\n\t\tCnStatusBadge,\n\t\tPlus,\n\t\tAlertCircleOutline,\n\t\tLinkVariant,\n\t\tLinkVariantOff,\n\t\tClockOutline,\n\t\tMapMarkerOutline,\n\t\tDelete,\n\t\tCnCalendarEventPicker,\n\t\tCnCalendarEventCreate,\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 (matches PHP-side provider id, always `calendar`). */\n\t\tintegrationId: { type: String, default: 'calendar' },\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 for OpenRegister. */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\n\t\t// --- Pre-translated labels (ADR-007) ---\n\t\t/** Label for the button that creates a meeting. */\n\t\taddEventLabel: { type: String, default: () => t('nextcloud-vue', 'Add meeting') },\n\t\t/** Label for the button that links an existing meeting. */\n\t\tlinkExistingLabel: { type: String, default: () => t('nextcloud-vue', 'Link existing') },\n\t\t/** Label for the action that unlinks a meeting from the object. */\n\t\tunlinkLabel: { type: String, default: () => t('nextcloud-vue', 'Unlink') },\n\t\t/** Label for the action that deletes a meeting. */\n\t\tdeleteLabel: { type: String, default: () => t('nextcloud-vue', 'Delete meeting') },\n\t\t/** Empty state shown when no meeting is linked yet. */\n\t\tnoEventsLabel: { type: String, default: () => t('nextcloud-vue', 'No meetings linked yet') },\n\t\t/** Stand-in title for a meeting that has none. */\n\t\tuntitledLabel: { type: String, default: () => t('nextcloud-vue', '(no title)') },\n\t\t/** Heading above the meetings that are still to come. */\n\t\tupcomingLabel: { type: String, default: () => t('nextcloud-vue', 'Upcoming') },\n\t\t/** Heading above the meetings that already happened. */\n\t\tpastLabel: { type: String, default: () => t('nextcloud-vue', 'Past') },\n\t\t/** Message shown when the Calendar app cannot be reached. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'Nextcloud Calendar is currently unavailable.') },\n\t},\n\n\temits: ['linked', 'unlinked', 'deleted'],\n\n\tdata() {\n\t\treturn {\n\t\t\tevents: [],\n\t\t\tloading: false,\n\t\t\terror: '',\n\t\t\tdegraded: false,\n\t\t\trowBusyKey: null,\n\t\t\tshowPicker: false,\n\t\t\tshowCreate: false,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\tsortedEvents() {\n\t\t\tconst copy = Array.isArray(this.events) ? [...this.events] : []\n\t\t\tcopy.sort((a, b) => {\n\t\t\t\tconst ta = a.dtstart ? new Date(a.dtstart).getTime() : Number.POSITIVE_INFINITY\n\t\t\t\tconst tb = b.dtstart ? new Date(b.dtstart).getTime() : Number.POSITIVE_INFINITY\n\t\t\t\treturn ta - tb\n\t\t\t})\n\t\t\treturn copy\n\t\t},\n\n\t\tupcomingEvents() {\n\t\t\tconst now = Date.now()\n\t\t\treturn this.sortedEvents.filter((ev) => {\n\t\t\t\tif (!ev.dtstart) {\n\t\t\t\t\treturn true\n\t\t\t\t}\n\t\t\t\treturn new Date(ev.dtstart).getTime() >= now\n\t\t\t})\n\t\t},\n\n\t\tpastEvents() {\n\t\t\tconst now = Date.now()\n\t\t\treturn this.sortedEvents.filter((ev) => {\n\t\t\t\tif (!ev.dtstart) {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t\treturn new Date(ev.dtstart).getTime() < now\n\t\t\t})\n\t\t},\n\t},\n\n\twatch: {\n\t\tobjectId: {\n\t\t\timmediate: true,\n\t\t\thandler(id) {\n\t\t\t\tif (id) {\n\t\t\t\t\tthis.fetchEvents()\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t},\n\n\tmethods: {\n\t\tbaseObjectUrl() {\n\t\t\treturn prefixUrl(`${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}`)\n\t\t},\n\n\t\trowKey(ev) {\n\t\t\t// Prefer eventUid (Tier-2 stable identifier across calendars).\n\t\t\treturn ev.uid || ev.id || ''\n\t\t},\n\n\t\tasync fetchEvents() {\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 = false\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(`${this.baseObjectUrl()}/events`, { 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.events = data.results || data.items || (Array.isArray(data) ? data : []) || []\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.events = []\n\t\t\t\t\tthis.degraded = true\n\t\t\t\t} else {\n\t\t\t\t\tthis.events = []\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load meetings.')\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('[CnCalendarTab] failed to fetch events', err)\n\t\t\t\tthis.events = []\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load meetings.')\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\topenCreate() {\n\t\t\tthis.showCreate = true\n\t\t},\n\n\t\tcloseCreate() {\n\t\t\tthis.showCreate = false\n\t\t},\n\n\t\tasync onEventCreated() {\n\t\t\tthis.closeCreate()\n\t\t\tawait this.fetchEvents()\n\t\t\tthis.$emit('linked')\n\t\t},\n\n\t\topenPicker() {\n\t\t\tthis.showPicker = true\n\t\t},\n\n\t\tclosePicker() {\n\t\t\tthis.showPicker = false\n\t\t},\n\n\t\tasync onPickerLink(payload) {\n\t\t\t// payload: { calendarUri, eventUid }\n\t\t\tthis.closePicker()\n\t\t\tthis.error = ''\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(`${this.baseObjectUrl()}/events/link`, {\n\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\theaders: buildHeaders(),\n\t\t\t\t\tbody: JSON.stringify(payload),\n\t\t\t\t})\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tawait this.fetchEvents()\n\t\t\t\t\tthis.$emit('linked', payload)\n\t\t\t\t} else {\n\t\t\t\t\tlet message = t('nextcloud-vue', 'Could not link the meeting.')\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst body = await response.json()\n\t\t\t\t\t\tif (body && typeof body.error === 'string') {\n\t\t\t\t\t\t\tmessage = body.error\n\t\t\t\t\t\t}\n\t\t\t\t\t} catch { /* ignore */ }\n\t\t\t\t\tthis.error = message\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('[CnCalendarTab] failed to link event', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not link the meeting.')\n\t\t\t}\n\t\t},\n\n\t\tasync unlink(ev) {\n\t\t\tconst key = this.rowKey(ev)\n\t\t\tif (this.rowBusyKey || !key) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.rowBusyKey = key\n\t\t\tthis.error = ''\n\t\t\ttry {\n\t\t\t\t// Tier-2 unlink-only endpoint: DELETE /events/{eventUid}/link\n\t\t\t\tconst url = `${this.baseObjectUrl()}/events/${encodeURIComponent(key)}/link`\n\t\t\t\tconst response = await fetch(url, { method: 'DELETE', headers: buildHeaders() })\n\t\t\t\tif (response.ok || response.status === 204) {\n\t\t\t\t\tthis.events = this.events.filter((row) => this.rowKey(row) !== key)\n\t\t\t\t\tthis.$emit('unlinked', key)\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.degraded = true\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not unlink the meeting.')\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('[CnCalendarTab] failed to unlink event', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not unlink the meeting.')\n\t\t\t} finally {\n\t\t\t\tthis.rowBusyKey = null\n\t\t\t}\n\t\t},\n\n\t\tasync deleteEvent(ev) {\n\t\t\tconst key = this.rowKey(ev)\n\t\t\tconst eventUri = ev.id\n\t\t\tif (this.rowBusyKey || !eventUri) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.rowBusyKey = key\n\t\t\tthis.error = ''\n\t\t\ttry {\n\t\t\t\t// Legacy destroy endpoint — destroys the VEVENT and cleans the link row.\n\t\t\t\tconst url = `${this.baseObjectUrl()}/events/${encodeURIComponent(eventUri)}`\n\t\t\t\tconst response = await fetch(url, { method: 'DELETE', headers: buildHeaders() })\n\t\t\t\tif (response.ok || response.status === 204) {\n\t\t\t\t\tthis.events = this.events.filter((row) => this.rowKey(row) !== key)\n\t\t\t\t\tthis.$emit('deleted', key)\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not delete the meeting.')\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('[CnCalendarTab] failed to delete event', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not delete the meeting.')\n\t\t\t} finally {\n\t\t\t\tthis.rowBusyKey = null\n\t\t\t}\n\t\t},\n\n\t\t// --- Presentation helpers (NC Calendar agenda look) ---\n\n\t\tparseDate(value) {\n\t\t\tif (!value) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst d = new Date(value)\n\t\t\treturn Number.isNaN(d.getTime()) ? null : d\n\t\t},\n\n\t\tmonthOf(ev) {\n\t\t\tconst d = this.parseDate(ev.dtstart)\n\t\t\tif (!d) {\n\t\t\t\treturn '—'\n\t\t\t}\n\t\t\treturn d.toLocaleDateString(undefined, { month: 'short' })\n\t\t},\n\n\t\tdayOf(ev) {\n\t\t\tconst d = this.parseDate(ev.dtstart)\n\t\t\tif (!d) {\n\t\t\t\treturn '?'\n\t\t\t}\n\t\t\treturn String(d.getDate())\n\t\t},\n\n\t\tformatTimeRange(ev) {\n\t\t\tconst start = this.parseDate(ev.dtstart)\n\t\t\tif (!start) {\n\t\t\t\treturn ev.dtstart ? String(ev.dtstart) : t('nextcloud-vue', 'Time not set')\n\t\t\t}\n\t\t\tconst end = this.parseDate(ev.dtend)\n\t\t\tconst dateLabel = start.toLocaleDateString(undefined, { weekday: 'short', day: 'numeric', month: 'short' })\n\t\t\tconst timeOpts = { hour: '2-digit', minute: '2-digit' }\n\t\t\tconst startTime = start.toLocaleTimeString(undefined, timeOpts)\n\t\t\tif (end) {\n\t\t\t\tconst sameDay = start.toDateString() === end.toDateString()\n\t\t\t\tconst endTime = end.toLocaleTimeString(undefined, timeOpts)\n\t\t\t\tif (sameDay) {\n\t\t\t\t\treturn `${dateLabel} · ${startTime} – ${endTime}`\n\t\t\t\t}\n\t\t\t\tconst endDateLabel = end.toLocaleDateString(undefined, { day: 'numeric', month: 'short' })\n\t\t\t\treturn `${dateLabel} ${startTime} – ${endDateLabel} ${endTime}`\n\t\t\t}\n\t\t\treturn `${dateLabel} · ${startTime}`\n\t\t},\n\n\t\tstatusLabel(ev) {\n\t\t\tconst status = (ev.status || '').toString().toLowerCase()\n\t\t\tif (status === 'confirmed') {\n\t\t\t\treturn t('nextcloud-vue', 'Confirmed')\n\t\t\t}\n\t\t\tif (status === 'tentative') {\n\t\t\t\treturn t('nextcloud-vue', 'Tentative')\n\t\t\t}\n\t\t\tif (status === 'cancelled') {\n\t\t\t\treturn t('nextcloud-vue', 'Cancelled')\n\t\t\t}\n\t\t\treturn ''\n\t\t},\n\n\t\tstatusVariant(ev) {\n\t\t\tconst status = (ev.status || '').toString().toLowerCase()\n\t\t\tif (status === 'confirmed') {\n\t\t\t\treturn 'success'\n\t\t\t}\n\t\t\tif (status === 'tentative') {\n\t\t\t\treturn 'warning'\n\t\t\t}\n\t\t\tif (status === 'cancelled') {\n\t\t\t\treturn 'error'\n\t\t\t}\n\t\t\treturn 'default'\n\t\t},\n\n\t\tattendeeList(ev) {\n\t\t\tconst raw = ev.attendees || ev.participants || []\n\t\t\tif (!Array.isArray(raw)) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\treturn raw.map((att) => {\n\t\t\t\tif (typeof att === 'string') {\n\t\t\t\t\treturn { name: att, initials: this.initialsFor(att) }\n\t\t\t\t}\n\t\t\t\tconst name = att.name || att.displayName || att.cn || att.email || ''\n\t\t\t\treturn { name, initials: this.initialsFor(name) }\n\t\t\t}).filter((a) => a.name)\n\t\t},\n\n\t\tvisibleAttendees(ev) {\n\t\t\treturn this.attendeeList(ev).slice(0, MAX_AVATARS)\n\t\t},\n\n\t\textraAttendees(ev) {\n\t\t\tconst total = this.attendeeList(ev).length\n\t\t\treturn total > MAX_AVATARS ? total - MAX_AVATARS : 0\n\t\t},\n\n\t\tattendeeCountLabel(ev) {\n\t\t\tconst total = this.attendeeList(ev).length\n\t\t\treturn t('nextcloud-vue', '{count} attendees', { count: total })\n\t\t},\n\n\t\tinitialsFor(name) {\n\t\t\tconst clean = (name || '').trim()\n\t\t\tif (!clean) {\n\t\t\t\treturn '?'\n\t\t\t}\n\t\t\tconst local = clean.includes('@') ? clean.split('@')[0] : clean\n\t\t\tconst parts = local.split(/[\\s._-]+/).filter(Boolean)\n\t\t\tif (parts.length === 0) {\n\t\t\t\treturn clean.charAt(0).toUpperCase()\n\t\t\t}\n\t\t\tif (parts.length === 1) {\n\t\t\t\treturn parts[0].charAt(0).toUpperCase()\n\t\t\t}\n\t\t\treturn (parts[0].charAt(0) + parts[parts.length - 1].charAt(0)).toUpperCase()\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-calendar-tab {\n\tpadding: 12px;\n\toverflow-x: hidden;\n}\n\n.cn-calendar-tab__action-row {\n\tdisplay: flex;\n\tgap: 8px;\n\tmargin-bottom: 12px;\n}\n\n.cn-calendar-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-calendar-tab__banner--error {\n\tbackground: var(--color-error, #e9322d);\n}\n\n.cn-calendar-tab__empty {\n\ttext-align: center;\n\tpadding: 24px 12px;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 13px;\n}\n\n.cn-calendar-tab__agenda {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 14px;\n}\n\n.cn-calendar-tab__group {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n}\n\n.cn-calendar-tab__group-title {\n\tmargin: 0 0 4px 0;\n\tfont-size: 12px;\n\tfont-weight: 600;\n\ttext-transform: uppercase;\n\tletter-spacing: 0.04em;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-calendar-tab__group-title--past {\n\topacity: 0.7;\n}\n\n/* Event row */\n.cn-calendar-tab__event {\n\tdisplay: flex;\n\talign-items: flex-start;\n\tgap: 12px;\n\tpadding: 8px;\n\tborder-radius: var(--border-radius-large, 8px);\n\tborder-left: 3px solid var(--color-primary-element);\n\tbackground: var(--color-background-hover);\n}\n\n.cn-calendar-tab__event + .cn-calendar-tab__event {\n\tmargin-top: 4px;\n}\n\n.cn-calendar-tab__event--past {\n\tborder-left-color: var(--color-border-dark, var(--color-border));\n\tbackground: transparent;\n\topacity: 0.72;\n}\n\n/* Date block */\n.cn-calendar-tab__date {\n\tflex-shrink: 0;\n\twidth: 44px;\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\tjustify-content: center;\n\tpadding: 4px 0;\n\tborder-radius: var(--border-radius);\n\tbackground: var(--color-primary-element);\n\tcolor: var(--color-primary-element-text, #fff);\n\tline-height: 1.1;\n}\n\n.cn-calendar-tab__date--past {\n\tbackground: var(--color-background-dark);\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-calendar-tab__date-month {\n\tfont-size: 10px;\n\tfont-weight: 600;\n\ttext-transform: uppercase;\n\tletter-spacing: 0.05em;\n}\n\n.cn-calendar-tab__date-day {\n\tfont-size: 18px;\n\tfont-weight: 700;\n}\n\n/* Main column */\n.cn-calendar-tab__main {\n\tflex: 1;\n\tmin-width: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 3px;\n}\n\n.cn-calendar-tab__title-row {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 6px;\n\tmin-width: 0;\n}\n\n.cn-calendar-tab__title {\n\tfont-size: 14px;\n\tfont-weight: 600;\n\tcolor: var(--color-main-text);\n\twhite-space: nowrap;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n.cn-calendar-tab__when,\n.cn-calendar-tab__location {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 5px;\n\tfont-size: 12px;\n\tcolor: var(--color-text-maxcontrast);\n\tmin-width: 0;\n}\n\n.cn-calendar-tab__meta-icon {\n\tflex-shrink: 0;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-calendar-tab__location-text {\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n/* Attendees */\n.cn-calendar-tab__attendees {\n\tdisplay: flex;\n\talign-items: center;\n\tmargin-top: 2px;\n}\n\n.cn-calendar-tab__avatar {\n\twidth: 24px;\n\theight: 24px;\n\tborder-radius: 50%;\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\tfont-size: 10px;\n\tfont-weight: 600;\n\tbackground: var(--color-primary-element-light, var(--color-primary-light));\n\tcolor: var(--color-primary-element-light-text, var(--color-primary-element));\n\tborder: 2px solid var(--color-main-background);\n\tmargin-left: -6px;\n\tflex-shrink: 0;\n}\n\n.cn-calendar-tab__avatar:first-child {\n\tmargin-left: 0;\n}\n\n.cn-calendar-tab__avatar--more {\n\tbackground: var(--color-background-dark);\n\tcolor: var(--color-text-maxcontrast);\n}\n\n/* Actions */\n.cn-calendar-tab__actions {\n\tflex-shrink: 0;\n}\n</style>\n"],"names":["CnStatusBadge","CnCalendarEventPicker","CnCalendarEventCreate","t"],"mappings":"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"}
1
+ {"version":3,"file":"CnCalendarTab.vue2.js","sources":["../../../../../src/integrations/builtin/calendar/CnCalendarTab.vue"],"sourcesContent":["<!--\n CnCalendarTab — bespoke sidebar tab for the `calendar` integration.\n\n Tier-2 rewire (additive link-table strategy):\n - \"Add meeting\" button opens CnCalendarEventCreate modal that POSTs\n to /api/objects/{r}/{s}/{id}/events (writes X-OR-* on the VEVENT\n AND a link-table row).\n - \"Link existing meeting\" button opens CnCalendarEventPicker modal\n that POSTs to /api/objects/{r}/{s}/{id}/events/link (writes ONLY\n a link-table row — we may not own the VEVENT).\n - Per-row UNLINK action: DELETE /events/{eventUid}/link — removes\n the link only, the VEVENT survives on the user's calendar.\n - Per-row DELETE action: DELETE /events/{eventId} — destroys the\n VEVENT (legacy semantics — also cleans up the link-table row).\n\n Read path: GET /api/objects/{r}/{s}/{id}/events returns the UNION of\n link-table rows and the legacy X-OR-* CalDAV scan, deduped by\n (calendarUri, eventUid) and annotated with a `source` field\n (link-table / xor-only / both).\n\n Visual fidelity (NC Calendar): each row foregrounds a date block (month\n abbreviation + day-of-month, accented like the Calendar app's agenda),\n a start–end time range, a bold event title, the location with a\n map-marker icon, an attendee avatar stack with an overflow count, and a\n CnStatusBadge for the VEVENT status (confirmed / tentative / cancelled).\n\n ADR-004 modal isolation: every modal lives in its own .vue file under\n `src/components/CnCalendar*/` and is imported here as a child component.\n-->\n<template>\n\t<div class=\"cn-sidebar-tab cn-calendar-tab\">\n\t\t<!-- Action bar (replaces the inline create form) -->\n\t\t<div class=\"cn-calendar-tab__action-row\">\n\t\t\t<NcButton variant=\"primary\" @click=\"openCreate\">\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{{ addEventLabel }}\n\t\t\t</NcButton>\n\t\t\t<NcButton variant=\"secondary\" @click=\"openPicker\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<LinkVariant :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ linkExistingLabel }}\n\t\t\t</NcButton>\n\t\t</div>\n\n\t\t<!-- Error banner -->\n\t\t<div v-if=\"error\" class=\"cn-calendar-tab__banner cn-calendar-tab__banner--error\" role=\"alert\">\n\t\t\t<AlertCircleOutline :size=\"18\" />\n\t\t\t<span>{{ error }}</span>\n\t\t</div>\n\n\t\t<!-- Unavailable banner (503) -->\n\t\t<div v-if=\"degraded\" class=\"cn-calendar-tab__banner\" role=\"status\">\n\t\t\t<AlertCircleOutline :size=\"18\" />\n\t\t\t<span>{{ unavailableLabel }}</span>\n\t\t</div>\n\n\t\t<NcLoadingIcon v-if=\"loading\" />\n\t\t<div v-else-if=\"sortedEvents.length === 0\" class=\"cn-sidebar-tab__empty cn-calendar-tab__empty\">\n\t\t\t{{ noEventsLabel }}\n\t\t</div>\n\n\t\t<!-- Agenda -->\n\t\t<div v-else class=\"cn-calendar-tab__agenda\">\n\t\t\t<div v-if=\"upcomingEvents.length > 0\" class=\"cn-calendar-tab__group\">\n\t\t\t\t<h4 class=\"cn-calendar-tab__group-title\">\n\t\t\t\t\t{{ upcomingLabel }}\n\t\t\t\t</h4>\n\t\t\t\t<div\n\t\t\t\t\tv-for=\"ev in upcomingEvents\"\n\t\t\t\t\t:key=\"rowKey(ev)\"\n\t\t\t\t\tclass=\"cn-calendar-tab__event\">\n\t\t\t\t\t<!-- Date block (month + day) -->\n\t\t\t\t\t<div class=\"cn-calendar-tab__date\" aria-hidden=\"true\">\n\t\t\t\t\t\t<span class=\"cn-calendar-tab__date-month\">{{ monthOf(ev) }}</span>\n\t\t\t\t\t\t<span class=\"cn-calendar-tab__date-day\">{{ dayOf(ev) }}</span>\n\t\t\t\t\t</div>\n\n\t\t\t\t\t<!-- Main column: title, time range, location, attendees -->\n\t\t\t\t\t<div class=\"cn-calendar-tab__main\">\n\t\t\t\t\t\t<div class=\"cn-calendar-tab__title-row\">\n\t\t\t\t\t\t\t<span class=\"cn-calendar-tab__title\">{{ ev.summary || untitledLabel }}</span>\n\t\t\t\t\t\t\t<CnStatusBadge\n\t\t\t\t\t\t\t\tv-if=\"statusLabel(ev)\"\n\t\t\t\t\t\t\t\t:label=\"statusLabel(ev)\"\n\t\t\t\t\t\t\t\t:variant=\"statusVariant(ev)\"\n\t\t\t\t\t\t\t\tsize=\"small\" />\n\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t<div class=\"cn-calendar-tab__when\">\n\t\t\t\t\t\t\t<ClockOutline :size=\"14\" class=\"cn-calendar-tab__meta-icon\" />\n\t\t\t\t\t\t\t<span>{{ formatTimeRange(ev) }}</span>\n\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t<div v-if=\"ev.location\" class=\"cn-calendar-tab__location\">\n\t\t\t\t\t\t\t<MapMarkerOutline :size=\"14\" class=\"cn-calendar-tab__meta-icon\" />\n\t\t\t\t\t\t\t<span class=\"cn-calendar-tab__location-text\">{{ ev.location }}</span>\n\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t<div v-if=\"attendeeList(ev).length > 0\" class=\"cn-calendar-tab__attendees\">\n\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\tv-for=\"(att, idx) in visibleAttendees(ev)\"\n\t\t\t\t\t\t\t\t:key=\"idx\"\n\t\t\t\t\t\t\t\tclass=\"cn-calendar-tab__avatar\"\n\t\t\t\t\t\t\t\t:title=\"att.name\">\n\t\t\t\t\t\t\t\t{{ att.initials }}\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\tv-if=\"extraAttendees(ev) > 0\"\n\t\t\t\t\t\t\t\tclass=\"cn-calendar-tab__avatar cn-calendar-tab__avatar--more\"\n\t\t\t\t\t\t\t\t:title=\"attendeeCountLabel(ev)\">\n\t\t\t\t\t\t\t\t+{{ extraAttendees(ev) }}\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\n\t\t\t\t\t<!-- Row actions -->\n\t\t\t\t\t<NcActions :forceMenu=\"true\" class=\"cn-calendar-tab__actions\">\n\t\t\t\t\t\t<NcActionButton :disabled=\"rowBusyKey === rowKey(ev)\" @click=\"unlink(ev)\">\n\t\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t\t<LinkVariantOff :size=\"20\" />\n\t\t\t\t\t\t\t</template>\n\t\t\t\t\t\t\t{{ unlinkLabel }}\n\t\t\t\t\t\t</NcActionButton>\n\t\t\t\t\t\t<NcActionButton :disabled=\"rowBusyKey === rowKey(ev)\" @click=\"deleteEvent(ev)\">\n\t\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t\t<Delete :size=\"20\" />\n\t\t\t\t\t\t\t</template>\n\t\t\t\t\t\t\t{{ deleteLabel }}\n\t\t\t\t\t\t</NcActionButton>\n\t\t\t\t\t</NcActions>\n\t\t\t\t</div>\n\t\t\t</div>\n\n\t\t\t<div v-if=\"pastEvents.length > 0\" class=\"cn-calendar-tab__group\">\n\t\t\t\t<h4 class=\"cn-calendar-tab__group-title cn-calendar-tab__group-title--past\">\n\t\t\t\t\t{{ pastLabel }}\n\t\t\t\t</h4>\n\t\t\t\t<div\n\t\t\t\t\tv-for=\"ev in pastEvents\"\n\t\t\t\t\t:key=\"rowKey(ev)\"\n\t\t\t\t\tclass=\"cn-calendar-tab__event cn-calendar-tab__event--past\">\n\t\t\t\t\t<div class=\"cn-calendar-tab__date cn-calendar-tab__date--past\" aria-hidden=\"true\">\n\t\t\t\t\t\t<span class=\"cn-calendar-tab__date-month\">{{ monthOf(ev) }}</span>\n\t\t\t\t\t\t<span class=\"cn-calendar-tab__date-day\">{{ dayOf(ev) }}</span>\n\t\t\t\t\t</div>\n\n\t\t\t\t\t<div class=\"cn-calendar-tab__main\">\n\t\t\t\t\t\t<div class=\"cn-calendar-tab__title-row\">\n\t\t\t\t\t\t\t<span class=\"cn-calendar-tab__title\">{{ ev.summary || untitledLabel }}</span>\n\t\t\t\t\t\t\t<CnStatusBadge\n\t\t\t\t\t\t\t\tv-if=\"statusLabel(ev)\"\n\t\t\t\t\t\t\t\t:label=\"statusLabel(ev)\"\n\t\t\t\t\t\t\t\t:variant=\"statusVariant(ev)\"\n\t\t\t\t\t\t\t\tsize=\"small\" />\n\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t<div class=\"cn-calendar-tab__when\">\n\t\t\t\t\t\t\t<ClockOutline :size=\"14\" class=\"cn-calendar-tab__meta-icon\" />\n\t\t\t\t\t\t\t<span>{{ formatTimeRange(ev) }}</span>\n\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t<div v-if=\"ev.location\" class=\"cn-calendar-tab__location\">\n\t\t\t\t\t\t\t<MapMarkerOutline :size=\"14\" class=\"cn-calendar-tab__meta-icon\" />\n\t\t\t\t\t\t\t<span class=\"cn-calendar-tab__location-text\">{{ ev.location }}</span>\n\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t<div v-if=\"attendeeList(ev).length > 0\" class=\"cn-calendar-tab__attendees\">\n\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\tv-for=\"(att, idx) in visibleAttendees(ev)\"\n\t\t\t\t\t\t\t\t:key=\"idx\"\n\t\t\t\t\t\t\t\tclass=\"cn-calendar-tab__avatar\"\n\t\t\t\t\t\t\t\t:title=\"att.name\">\n\t\t\t\t\t\t\t\t{{ att.initials }}\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\tv-if=\"extraAttendees(ev) > 0\"\n\t\t\t\t\t\t\t\tclass=\"cn-calendar-tab__avatar cn-calendar-tab__avatar--more\"\n\t\t\t\t\t\t\t\t:title=\"attendeeCountLabel(ev)\">\n\t\t\t\t\t\t\t\t+{{ extraAttendees(ev) }}\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\n\t\t\t\t\t<NcActions :forceMenu=\"true\" class=\"cn-calendar-tab__actions\">\n\t\t\t\t\t\t<NcActionButton :disabled=\"rowBusyKey === rowKey(ev)\" @click=\"unlink(ev)\">\n\t\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t\t<LinkVariantOff :size=\"20\" />\n\t\t\t\t\t\t\t</template>\n\t\t\t\t\t\t\t{{ unlinkLabel }}\n\t\t\t\t\t\t</NcActionButton>\n\t\t\t\t\t\t<NcActionButton :disabled=\"rowBusyKey === rowKey(ev)\" @click=\"deleteEvent(ev)\">\n\t\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t\t<Delete :size=\"20\" />\n\t\t\t\t\t\t\t</template>\n\t\t\t\t\t\t\t{{ deleteLabel }}\n\t\t\t\t\t\t</NcActionButton>\n\t\t\t\t\t</NcActions>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\n\t\t<!-- Picker modal (Tier-2 link existing flow) -->\n\t\t<CnCalendarEventPicker\n\t\t\tv-if=\"showPicker\"\n\t\t\t:apiBase=\"apiBase\"\n\t\t\t@link=\"onPickerLink\"\n\t\t\t@close=\"closePicker\" />\n\n\t\t<!-- Create modal (Tier-2 create-and-link flow) -->\n\t\t<CnCalendarEventCreate\n\t\t\tv-if=\"showCreate\"\n\t\t\t:register=\"register\"\n\t\t\t:schema=\"schema\"\n\t\t\t:objectId=\"objectId\"\n\t\t\t:apiBase=\"apiBase\"\n\t\t\t@created=\"onEventCreated\"\n\t\t\t@close=\"closeCreate\" />\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport {\n\tNcActionButton,\n\tNcActions,\n\tNcButton,\n\tNcLoadingIcon,\n} from '@nextcloud/vue'\nimport AlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue'\nimport ClockOutline from 'vue-material-design-icons/ClockOutline.vue'\nimport Delete from 'vue-material-design-icons/Delete.vue'\nimport LinkVariant from 'vue-material-design-icons/LinkVariant.vue'\nimport LinkVariantOff from 'vue-material-design-icons/LinkVariantOff.vue'\nimport MapMarkerOutline from 'vue-material-design-icons/MapMarkerOutline.vue'\nimport Plus from 'vue-material-design-icons/Plus.vue'\nimport CnCalendarEventCreate from '../../../components/CnCalendarEventCreate/CnCalendarEventCreate.vue'\nimport CnCalendarEventPicker from '../../../components/CnCalendarEventPicker/CnCalendarEventPicker.vue'\nimport CnStatusBadge from '../../../components/CnStatusBadge/CnStatusBadge.vue'\nimport { buildHeaders, prefixUrl } from '../../../utils/index.js'\nimport { userLocale } from '../../../utils/userLocale.js'\n\nconst MAX_AVATARS = 3\n\n/**\n * CnCalendarTab — bespoke sidebar tab for the calendar integration.\n *\n * Tier-2 wiring: link/create flows are modal-driven (ADR-004 isolation),\n * and per-row actions distinguish UNLINK (preserves the VEVENT) from\n * DELETE (destroys the VEVENT). Rows mirror NC Calendar's agenda look:\n * date block, time range, title, location, attendee avatars + status.\n */\nexport default {\n\tname: 'CnCalendarTab',\n\n\tcomponents: {\n\t\tNcButton,\n\t\tNcActions,\n\t\tNcActionButton,\n\t\tNcLoadingIcon,\n\t\tCnStatusBadge,\n\t\tPlus,\n\t\tAlertCircleOutline,\n\t\tLinkVariant,\n\t\tLinkVariantOff,\n\t\tClockOutline,\n\t\tMapMarkerOutline,\n\t\tDelete,\n\t\tCnCalendarEventPicker,\n\t\tCnCalendarEventCreate,\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 (matches PHP-side provider id, always `calendar`). */\n\t\tintegrationId: { type: String, default: 'calendar' },\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 for OpenRegister. */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\n\t\t// --- Pre-translated labels (ADR-007) ---\n\t\t/** Label for the button that creates a meeting. */\n\t\taddEventLabel: { type: String, default: () => t('nextcloud-vue', 'Add meeting') },\n\t\t/** Label for the button that links an existing meeting. */\n\t\tlinkExistingLabel: { type: String, default: () => t('nextcloud-vue', 'Link existing') },\n\t\t/** Label for the action that unlinks a meeting from the object. */\n\t\tunlinkLabel: { type: String, default: () => t('nextcloud-vue', 'Unlink') },\n\t\t/** Label for the action that deletes a meeting. */\n\t\tdeleteLabel: { type: String, default: () => t('nextcloud-vue', 'Delete meeting') },\n\t\t/** Empty state shown when no meeting is linked yet. */\n\t\tnoEventsLabel: { type: String, default: () => t('nextcloud-vue', 'No meetings linked yet') },\n\t\t/** Stand-in title for a meeting that has none. */\n\t\tuntitledLabel: { type: String, default: () => t('nextcloud-vue', '(no title)') },\n\t\t/** Heading above the meetings that are still to come. */\n\t\tupcomingLabel: { type: String, default: () => t('nextcloud-vue', 'Upcoming') },\n\t\t/** Heading above the meetings that already happened. */\n\t\tpastLabel: { type: String, default: () => t('nextcloud-vue', 'Past') },\n\t\t/** Message shown when the Calendar app cannot be reached. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'Nextcloud Calendar is currently unavailable.') },\n\t},\n\n\temits: ['linked', 'unlinked', 'deleted'],\n\n\tdata() {\n\t\treturn {\n\t\t\tevents: [],\n\t\t\tloading: false,\n\t\t\terror: '',\n\t\t\tdegraded: false,\n\t\t\trowBusyKey: null,\n\t\t\tshowPicker: false,\n\t\t\tshowCreate: false,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\tsortedEvents() {\n\t\t\tconst copy = Array.isArray(this.events) ? [...this.events] : []\n\t\t\tcopy.sort((a, b) => {\n\t\t\t\tconst ta = a.dtstart ? new Date(a.dtstart).getTime() : Number.POSITIVE_INFINITY\n\t\t\t\tconst tb = b.dtstart ? new Date(b.dtstart).getTime() : Number.POSITIVE_INFINITY\n\t\t\t\treturn ta - tb\n\t\t\t})\n\t\t\treturn copy\n\t\t},\n\n\t\tupcomingEvents() {\n\t\t\tconst now = Date.now()\n\t\t\treturn this.sortedEvents.filter((ev) => {\n\t\t\t\tif (!ev.dtstart) {\n\t\t\t\t\treturn true\n\t\t\t\t}\n\t\t\t\treturn new Date(ev.dtstart).getTime() >= now\n\t\t\t})\n\t\t},\n\n\t\tpastEvents() {\n\t\t\tconst now = Date.now()\n\t\t\treturn this.sortedEvents.filter((ev) => {\n\t\t\t\tif (!ev.dtstart) {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t\treturn new Date(ev.dtstart).getTime() < now\n\t\t\t})\n\t\t},\n\t},\n\n\twatch: {\n\t\tobjectId: {\n\t\t\timmediate: true,\n\t\t\thandler(id) {\n\t\t\t\tif (id) {\n\t\t\t\t\tthis.fetchEvents()\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t},\n\n\tmethods: {\n\t\tbaseObjectUrl() {\n\t\t\treturn prefixUrl(`${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}`)\n\t\t},\n\n\t\trowKey(ev) {\n\t\t\t// Prefer eventUid (Tier-2 stable identifier across calendars).\n\t\t\treturn ev.uid || ev.id || ''\n\t\t},\n\n\t\tasync fetchEvents() {\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 = false\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(`${this.baseObjectUrl()}/events`, { 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.events = data.results || data.items || (Array.isArray(data) ? data : []) || []\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.events = []\n\t\t\t\t\tthis.degraded = true\n\t\t\t\t} else {\n\t\t\t\t\tthis.events = []\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load meetings.')\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('[CnCalendarTab] failed to fetch events', err)\n\t\t\t\tthis.events = []\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load meetings.')\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\topenCreate() {\n\t\t\tthis.showCreate = true\n\t\t},\n\n\t\tcloseCreate() {\n\t\t\tthis.showCreate = false\n\t\t},\n\n\t\tasync onEventCreated() {\n\t\t\tthis.closeCreate()\n\t\t\tawait this.fetchEvents()\n\t\t\tthis.$emit('linked')\n\t\t},\n\n\t\topenPicker() {\n\t\t\tthis.showPicker = true\n\t\t},\n\n\t\tclosePicker() {\n\t\t\tthis.showPicker = false\n\t\t},\n\n\t\tasync onPickerLink(payload) {\n\t\t\t// payload: { calendarUri, eventUid }\n\t\t\tthis.closePicker()\n\t\t\tthis.error = ''\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(`${this.baseObjectUrl()}/events/link`, {\n\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\theaders: buildHeaders(),\n\t\t\t\t\tbody: JSON.stringify(payload),\n\t\t\t\t})\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tawait this.fetchEvents()\n\t\t\t\t\tthis.$emit('linked', payload)\n\t\t\t\t} else {\n\t\t\t\t\tlet message = t('nextcloud-vue', 'Could not link the meeting.')\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst body = await response.json()\n\t\t\t\t\t\tif (body && typeof body.error === 'string') {\n\t\t\t\t\t\t\tmessage = body.error\n\t\t\t\t\t\t}\n\t\t\t\t\t} catch { /* ignore */ }\n\t\t\t\t\tthis.error = message\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('[CnCalendarTab] failed to link event', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not link the meeting.')\n\t\t\t}\n\t\t},\n\n\t\tasync unlink(ev) {\n\t\t\tconst key = this.rowKey(ev)\n\t\t\tif (this.rowBusyKey || !key) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.rowBusyKey = key\n\t\t\tthis.error = ''\n\t\t\ttry {\n\t\t\t\t// Tier-2 unlink-only endpoint: DELETE /events/{eventUid}/link\n\t\t\t\tconst url = `${this.baseObjectUrl()}/events/${encodeURIComponent(key)}/link`\n\t\t\t\tconst response = await fetch(url, { method: 'DELETE', headers: buildHeaders() })\n\t\t\t\tif (response.ok || response.status === 204) {\n\t\t\t\t\tthis.events = this.events.filter((row) => this.rowKey(row) !== key)\n\t\t\t\t\tthis.$emit('unlinked', key)\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.degraded = true\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not unlink the meeting.')\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('[CnCalendarTab] failed to unlink event', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not unlink the meeting.')\n\t\t\t} finally {\n\t\t\t\tthis.rowBusyKey = null\n\t\t\t}\n\t\t},\n\n\t\tasync deleteEvent(ev) {\n\t\t\tconst key = this.rowKey(ev)\n\t\t\tconst eventUri = ev.id\n\t\t\tif (this.rowBusyKey || !eventUri) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.rowBusyKey = key\n\t\t\tthis.error = ''\n\t\t\ttry {\n\t\t\t\t// Legacy destroy endpoint — destroys the VEVENT and cleans the link row.\n\t\t\t\tconst url = `${this.baseObjectUrl()}/events/${encodeURIComponent(eventUri)}`\n\t\t\t\tconst response = await fetch(url, { method: 'DELETE', headers: buildHeaders() })\n\t\t\t\tif (response.ok || response.status === 204) {\n\t\t\t\t\tthis.events = this.events.filter((row) => this.rowKey(row) !== key)\n\t\t\t\t\tthis.$emit('deleted', key)\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not delete the meeting.')\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('[CnCalendarTab] failed to delete event', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not delete the meeting.')\n\t\t\t} finally {\n\t\t\t\tthis.rowBusyKey = null\n\t\t\t}\n\t\t},\n\n\t\t// --- Presentation helpers (NC Calendar agenda look) ---\n\n\t\tparseDate(value) {\n\t\t\tif (!value) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst d = new Date(value)\n\t\t\treturn Number.isNaN(d.getTime()) ? null : d\n\t\t},\n\n\t\tmonthOf(ev) {\n\t\t\tconst d = this.parseDate(ev.dtstart)\n\t\t\tif (!d) {\n\t\t\t\treturn '—'\n\t\t\t}\n\t\t\treturn d.toLocaleDateString(userLocale(), { month: 'short' })\n\t\t},\n\n\t\tdayOf(ev) {\n\t\t\tconst d = this.parseDate(ev.dtstart)\n\t\t\tif (!d) {\n\t\t\t\treturn '?'\n\t\t\t}\n\t\t\treturn String(d.getDate())\n\t\t},\n\n\t\tformatTimeRange(ev) {\n\t\t\tconst start = this.parseDate(ev.dtstart)\n\t\t\tif (!start) {\n\t\t\t\treturn ev.dtstart ? String(ev.dtstart) : t('nextcloud-vue', 'Time not set')\n\t\t\t}\n\t\t\tconst end = this.parseDate(ev.dtend)\n\t\t\tconst dateLabel = start.toLocaleDateString(userLocale(), { weekday: 'short', day: 'numeric', month: 'short' })\n\t\t\tconst timeOpts = { hour: '2-digit', minute: '2-digit' }\n\t\t\tconst startTime = start.toLocaleTimeString(userLocale(), timeOpts)\n\t\t\tif (end) {\n\t\t\t\tconst sameDay = start.toDateString() === end.toDateString()\n\t\t\t\tconst endTime = end.toLocaleTimeString(userLocale(), timeOpts)\n\t\t\t\tif (sameDay) {\n\t\t\t\t\treturn `${dateLabel} · ${startTime} – ${endTime}`\n\t\t\t\t}\n\t\t\t\tconst endDateLabel = end.toLocaleDateString(userLocale(), { day: 'numeric', month: 'short' })\n\t\t\t\treturn `${dateLabel} ${startTime} – ${endDateLabel} ${endTime}`\n\t\t\t}\n\t\t\treturn `${dateLabel} · ${startTime}`\n\t\t},\n\n\t\tstatusLabel(ev) {\n\t\t\tconst status = (ev.status || '').toString().toLowerCase()\n\t\t\tif (status === 'confirmed') {\n\t\t\t\treturn t('nextcloud-vue', 'Confirmed')\n\t\t\t}\n\t\t\tif (status === 'tentative') {\n\t\t\t\treturn t('nextcloud-vue', 'Tentative')\n\t\t\t}\n\t\t\tif (status === 'cancelled') {\n\t\t\t\treturn t('nextcloud-vue', 'Cancelled')\n\t\t\t}\n\t\t\treturn ''\n\t\t},\n\n\t\tstatusVariant(ev) {\n\t\t\tconst status = (ev.status || '').toString().toLowerCase()\n\t\t\tif (status === 'confirmed') {\n\t\t\t\treturn 'success'\n\t\t\t}\n\t\t\tif (status === 'tentative') {\n\t\t\t\treturn 'warning'\n\t\t\t}\n\t\t\tif (status === 'cancelled') {\n\t\t\t\treturn 'error'\n\t\t\t}\n\t\t\treturn 'default'\n\t\t},\n\n\t\tattendeeList(ev) {\n\t\t\tconst raw = ev.attendees || ev.participants || []\n\t\t\tif (!Array.isArray(raw)) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\treturn raw.map((att) => {\n\t\t\t\tif (typeof att === 'string') {\n\t\t\t\t\treturn { name: att, initials: this.initialsFor(att) }\n\t\t\t\t}\n\t\t\t\tconst name = att.name || att.displayName || att.cn || att.email || ''\n\t\t\t\treturn { name, initials: this.initialsFor(name) }\n\t\t\t}).filter((a) => a.name)\n\t\t},\n\n\t\tvisibleAttendees(ev) {\n\t\t\treturn this.attendeeList(ev).slice(0, MAX_AVATARS)\n\t\t},\n\n\t\textraAttendees(ev) {\n\t\t\tconst total = this.attendeeList(ev).length\n\t\t\treturn total > MAX_AVATARS ? total - MAX_AVATARS : 0\n\t\t},\n\n\t\tattendeeCountLabel(ev) {\n\t\t\tconst total = this.attendeeList(ev).length\n\t\t\treturn t('nextcloud-vue', '{count} attendees', { count: total })\n\t\t},\n\n\t\tinitialsFor(name) {\n\t\t\tconst clean = (name || '').trim()\n\t\t\tif (!clean) {\n\t\t\t\treturn '?'\n\t\t\t}\n\t\t\tconst local = clean.includes('@') ? clean.split('@')[0] : clean\n\t\t\tconst parts = local.split(/[\\s._-]+/).filter(Boolean)\n\t\t\tif (parts.length === 0) {\n\t\t\t\treturn clean.charAt(0).toUpperCase()\n\t\t\t}\n\t\t\tif (parts.length === 1) {\n\t\t\t\treturn parts[0].charAt(0).toUpperCase()\n\t\t\t}\n\t\t\treturn (parts[0].charAt(0) + parts[parts.length - 1].charAt(0)).toUpperCase()\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-calendar-tab {\n\tpadding: 12px;\n\toverflow-x: hidden;\n}\n\n.cn-calendar-tab__action-row {\n\tdisplay: flex;\n\tgap: 8px;\n\tmargin-bottom: 12px;\n}\n\n.cn-calendar-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-calendar-tab__banner--error {\n\tbackground: var(--color-error, #e9322d);\n}\n\n.cn-calendar-tab__empty {\n\ttext-align: center;\n\tpadding: 24px 12px;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 13px;\n}\n\n.cn-calendar-tab__agenda {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 14px;\n}\n\n.cn-calendar-tab__group {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n}\n\n.cn-calendar-tab__group-title {\n\tmargin: 0 0 4px 0;\n\tfont-size: 12px;\n\tfont-weight: 600;\n\ttext-transform: uppercase;\n\tletter-spacing: 0.04em;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-calendar-tab__group-title--past {\n\topacity: 0.7;\n}\n\n/* Event row */\n.cn-calendar-tab__event {\n\tdisplay: flex;\n\talign-items: flex-start;\n\tgap: 12px;\n\tpadding: 8px;\n\tborder-radius: var(--border-radius-large, 8px);\n\tborder-left: 3px solid var(--color-primary-element);\n\tbackground: var(--color-background-hover);\n}\n\n.cn-calendar-tab__event + .cn-calendar-tab__event {\n\tmargin-top: 4px;\n}\n\n.cn-calendar-tab__event--past {\n\tborder-left-color: var(--color-border-dark, var(--color-border));\n\tbackground: transparent;\n\topacity: 0.72;\n}\n\n/* Date block */\n.cn-calendar-tab__date {\n\tflex-shrink: 0;\n\twidth: 44px;\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\tjustify-content: center;\n\tpadding: 4px 0;\n\tborder-radius: var(--border-radius);\n\tbackground: var(--color-primary-element);\n\tcolor: var(--color-primary-element-text, #fff);\n\tline-height: 1.1;\n}\n\n.cn-calendar-tab__date--past {\n\tbackground: var(--color-background-dark);\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-calendar-tab__date-month {\n\tfont-size: 10px;\n\tfont-weight: 600;\n\ttext-transform: uppercase;\n\tletter-spacing: 0.05em;\n}\n\n.cn-calendar-tab__date-day {\n\tfont-size: 18px;\n\tfont-weight: 700;\n}\n\n/* Main column */\n.cn-calendar-tab__main {\n\tflex: 1;\n\tmin-width: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 3px;\n}\n\n.cn-calendar-tab__title-row {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 6px;\n\tmin-width: 0;\n}\n\n.cn-calendar-tab__title {\n\tfont-size: 14px;\n\tfont-weight: 600;\n\tcolor: var(--color-main-text);\n\twhite-space: nowrap;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n.cn-calendar-tab__when,\n.cn-calendar-tab__location {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 5px;\n\tfont-size: 12px;\n\tcolor: var(--color-text-maxcontrast);\n\tmin-width: 0;\n}\n\n.cn-calendar-tab__meta-icon {\n\tflex-shrink: 0;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-calendar-tab__location-text {\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n/* Attendees */\n.cn-calendar-tab__attendees {\n\tdisplay: flex;\n\talign-items: center;\n\tmargin-top: 2px;\n}\n\n.cn-calendar-tab__avatar {\n\twidth: 24px;\n\theight: 24px;\n\tborder-radius: 50%;\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\tfont-size: 10px;\n\tfont-weight: 600;\n\tbackground: var(--color-primary-element-light, var(--color-primary-light));\n\tcolor: var(--color-primary-element-light-text, var(--color-primary-element));\n\tborder: 2px solid var(--color-main-background);\n\tmargin-left: -6px;\n\tflex-shrink: 0;\n}\n\n.cn-calendar-tab__avatar:first-child {\n\tmargin-left: 0;\n}\n\n.cn-calendar-tab__avatar--more {\n\tbackground: var(--color-background-dark);\n\tcolor: var(--color-text-maxcontrast);\n}\n\n/* Actions */\n.cn-calendar-tab__actions {\n\tflex-shrink: 0;\n}\n</style>\n"],"names":["CnStatusBadge","CnCalendarEventPicker","CnCalendarEventCreate","t"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"CnCalendarTab.vue3.js","sources":["../../../../../src/integrations/builtin/calendar/CnCalendarTab.vue?vue&type=template&id=576281b3&lang.js"],"sourcesContent":["<!--\n CnCalendarTab — bespoke sidebar tab for the `calendar` integration.\n\n Tier-2 rewire (additive link-table strategy):\n - \"Add meeting\" button opens CnCalendarEventCreate modal that POSTs\n to /api/objects/{r}/{s}/{id}/events (writes X-OR-* on the VEVENT\n AND a link-table row).\n - \"Link existing meeting\" button opens CnCalendarEventPicker modal\n that POSTs to /api/objects/{r}/{s}/{id}/events/link (writes ONLY\n a link-table row — we may not own the VEVENT).\n - Per-row UNLINK action: DELETE /events/{eventUid}/link — removes\n the link only, the VEVENT survives on the user's calendar.\n - Per-row DELETE action: DELETE /events/{eventId} — destroys the\n VEVENT (legacy semantics — also cleans up the link-table row).\n\n Read path: GET /api/objects/{r}/{s}/{id}/events returns the UNION of\n link-table rows and the legacy X-OR-* CalDAV scan, deduped by\n (calendarUri, eventUid) and annotated with a `source` field\n (link-table / xor-only / both).\n\n Visual fidelity (NC Calendar): each row foregrounds a date block (month\n abbreviation + day-of-month, accented like the Calendar app's agenda),\n a start–end time range, a bold event title, the location with a\n map-marker icon, an attendee avatar stack with an overflow count, and a\n CnStatusBadge for the VEVENT status (confirmed / tentative / cancelled).\n\n ADR-004 modal isolation: every modal lives in its own .vue file under\n `src/components/CnCalendar*/` and is imported here as a child component.\n-->\n<template>\n\t<div class=\"cn-sidebar-tab cn-calendar-tab\">\n\t\t<!-- Action bar (replaces the inline create form) -->\n\t\t<div class=\"cn-calendar-tab__action-row\">\n\t\t\t<NcButton variant=\"primary\" @click=\"openCreate\">\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{{ addEventLabel }}\n\t\t\t</NcButton>\n\t\t\t<NcButton variant=\"secondary\" @click=\"openPicker\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<LinkVariant :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ linkExistingLabel }}\n\t\t\t</NcButton>\n\t\t</div>\n\n\t\t<!-- Error banner -->\n\t\t<div v-if=\"error\" class=\"cn-calendar-tab__banner cn-calendar-tab__banner--error\" role=\"alert\">\n\t\t\t<AlertCircleOutline :size=\"18\" />\n\t\t\t<span>{{ error }}</span>\n\t\t</div>\n\n\t\t<!-- Unavailable banner (503) -->\n\t\t<div v-if=\"degraded\" class=\"cn-calendar-tab__banner\" role=\"status\">\n\t\t\t<AlertCircleOutline :size=\"18\" />\n\t\t\t<span>{{ unavailableLabel }}</span>\n\t\t</div>\n\n\t\t<NcLoadingIcon v-if=\"loading\" />\n\t\t<div v-else-if=\"sortedEvents.length === 0\" class=\"cn-sidebar-tab__empty cn-calendar-tab__empty\">\n\t\t\t{{ noEventsLabel }}\n\t\t</div>\n\n\t\t<!-- Agenda -->\n\t\t<div v-else class=\"cn-calendar-tab__agenda\">\n\t\t\t<div v-if=\"upcomingEvents.length > 0\" class=\"cn-calendar-tab__group\">\n\t\t\t\t<h4 class=\"cn-calendar-tab__group-title\">\n\t\t\t\t\t{{ upcomingLabel }}\n\t\t\t\t</h4>\n\t\t\t\t<div\n\t\t\t\t\tv-for=\"ev in upcomingEvents\"\n\t\t\t\t\t:key=\"rowKey(ev)\"\n\t\t\t\t\tclass=\"cn-calendar-tab__event\">\n\t\t\t\t\t<!-- Date block (month + day) -->\n\t\t\t\t\t<div class=\"cn-calendar-tab__date\" aria-hidden=\"true\">\n\t\t\t\t\t\t<span class=\"cn-calendar-tab__date-month\">{{ monthOf(ev) }}</span>\n\t\t\t\t\t\t<span class=\"cn-calendar-tab__date-day\">{{ dayOf(ev) }}</span>\n\t\t\t\t\t</div>\n\n\t\t\t\t\t<!-- Main column: title, time range, location, attendees -->\n\t\t\t\t\t<div class=\"cn-calendar-tab__main\">\n\t\t\t\t\t\t<div class=\"cn-calendar-tab__title-row\">\n\t\t\t\t\t\t\t<span class=\"cn-calendar-tab__title\">{{ ev.summary || untitledLabel }}</span>\n\t\t\t\t\t\t\t<CnStatusBadge\n\t\t\t\t\t\t\t\tv-if=\"statusLabel(ev)\"\n\t\t\t\t\t\t\t\t:label=\"statusLabel(ev)\"\n\t\t\t\t\t\t\t\t:variant=\"statusVariant(ev)\"\n\t\t\t\t\t\t\t\tsize=\"small\" />\n\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t<div class=\"cn-calendar-tab__when\">\n\t\t\t\t\t\t\t<ClockOutline :size=\"14\" class=\"cn-calendar-tab__meta-icon\" />\n\t\t\t\t\t\t\t<span>{{ formatTimeRange(ev) }}</span>\n\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t<div v-if=\"ev.location\" class=\"cn-calendar-tab__location\">\n\t\t\t\t\t\t\t<MapMarkerOutline :size=\"14\" class=\"cn-calendar-tab__meta-icon\" />\n\t\t\t\t\t\t\t<span class=\"cn-calendar-tab__location-text\">{{ ev.location }}</span>\n\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t<div v-if=\"attendeeList(ev).length > 0\" class=\"cn-calendar-tab__attendees\">\n\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\tv-for=\"(att, idx) in visibleAttendees(ev)\"\n\t\t\t\t\t\t\t\t:key=\"idx\"\n\t\t\t\t\t\t\t\tclass=\"cn-calendar-tab__avatar\"\n\t\t\t\t\t\t\t\t:title=\"att.name\">\n\t\t\t\t\t\t\t\t{{ att.initials }}\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\tv-if=\"extraAttendees(ev) > 0\"\n\t\t\t\t\t\t\t\tclass=\"cn-calendar-tab__avatar cn-calendar-tab__avatar--more\"\n\t\t\t\t\t\t\t\t:title=\"attendeeCountLabel(ev)\">\n\t\t\t\t\t\t\t\t+{{ extraAttendees(ev) }}\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\n\t\t\t\t\t<!-- Row actions -->\n\t\t\t\t\t<NcActions :forceMenu=\"true\" class=\"cn-calendar-tab__actions\">\n\t\t\t\t\t\t<NcActionButton :disabled=\"rowBusyKey === rowKey(ev)\" @click=\"unlink(ev)\">\n\t\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t\t<LinkVariantOff :size=\"20\" />\n\t\t\t\t\t\t\t</template>\n\t\t\t\t\t\t\t{{ unlinkLabel }}\n\t\t\t\t\t\t</NcActionButton>\n\t\t\t\t\t\t<NcActionButton :disabled=\"rowBusyKey === rowKey(ev)\" @click=\"deleteEvent(ev)\">\n\t\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t\t<Delete :size=\"20\" />\n\t\t\t\t\t\t\t</template>\n\t\t\t\t\t\t\t{{ deleteLabel }}\n\t\t\t\t\t\t</NcActionButton>\n\t\t\t\t\t</NcActions>\n\t\t\t\t</div>\n\t\t\t</div>\n\n\t\t\t<div v-if=\"pastEvents.length > 0\" class=\"cn-calendar-tab__group\">\n\t\t\t\t<h4 class=\"cn-calendar-tab__group-title cn-calendar-tab__group-title--past\">\n\t\t\t\t\t{{ pastLabel }}\n\t\t\t\t</h4>\n\t\t\t\t<div\n\t\t\t\t\tv-for=\"ev in pastEvents\"\n\t\t\t\t\t:key=\"rowKey(ev)\"\n\t\t\t\t\tclass=\"cn-calendar-tab__event cn-calendar-tab__event--past\">\n\t\t\t\t\t<div class=\"cn-calendar-tab__date cn-calendar-tab__date--past\" aria-hidden=\"true\">\n\t\t\t\t\t\t<span class=\"cn-calendar-tab__date-month\">{{ monthOf(ev) }}</span>\n\t\t\t\t\t\t<span class=\"cn-calendar-tab__date-day\">{{ dayOf(ev) }}</span>\n\t\t\t\t\t</div>\n\n\t\t\t\t\t<div class=\"cn-calendar-tab__main\">\n\t\t\t\t\t\t<div class=\"cn-calendar-tab__title-row\">\n\t\t\t\t\t\t\t<span class=\"cn-calendar-tab__title\">{{ ev.summary || untitledLabel }}</span>\n\t\t\t\t\t\t\t<CnStatusBadge\n\t\t\t\t\t\t\t\tv-if=\"statusLabel(ev)\"\n\t\t\t\t\t\t\t\t:label=\"statusLabel(ev)\"\n\t\t\t\t\t\t\t\t:variant=\"statusVariant(ev)\"\n\t\t\t\t\t\t\t\tsize=\"small\" />\n\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t<div class=\"cn-calendar-tab__when\">\n\t\t\t\t\t\t\t<ClockOutline :size=\"14\" class=\"cn-calendar-tab__meta-icon\" />\n\t\t\t\t\t\t\t<span>{{ formatTimeRange(ev) }}</span>\n\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t<div v-if=\"ev.location\" class=\"cn-calendar-tab__location\">\n\t\t\t\t\t\t\t<MapMarkerOutline :size=\"14\" class=\"cn-calendar-tab__meta-icon\" />\n\t\t\t\t\t\t\t<span class=\"cn-calendar-tab__location-text\">{{ ev.location }}</span>\n\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t<div v-if=\"attendeeList(ev).length > 0\" class=\"cn-calendar-tab__attendees\">\n\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\tv-for=\"(att, idx) in visibleAttendees(ev)\"\n\t\t\t\t\t\t\t\t:key=\"idx\"\n\t\t\t\t\t\t\t\tclass=\"cn-calendar-tab__avatar\"\n\t\t\t\t\t\t\t\t:title=\"att.name\">\n\t\t\t\t\t\t\t\t{{ att.initials }}\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\tv-if=\"extraAttendees(ev) > 0\"\n\t\t\t\t\t\t\t\tclass=\"cn-calendar-tab__avatar cn-calendar-tab__avatar--more\"\n\t\t\t\t\t\t\t\t:title=\"attendeeCountLabel(ev)\">\n\t\t\t\t\t\t\t\t+{{ extraAttendees(ev) }}\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\n\t\t\t\t\t<NcActions :forceMenu=\"true\" class=\"cn-calendar-tab__actions\">\n\t\t\t\t\t\t<NcActionButton :disabled=\"rowBusyKey === rowKey(ev)\" @click=\"unlink(ev)\">\n\t\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t\t<LinkVariantOff :size=\"20\" />\n\t\t\t\t\t\t\t</template>\n\t\t\t\t\t\t\t{{ unlinkLabel }}\n\t\t\t\t\t\t</NcActionButton>\n\t\t\t\t\t\t<NcActionButton :disabled=\"rowBusyKey === rowKey(ev)\" @click=\"deleteEvent(ev)\">\n\t\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t\t<Delete :size=\"20\" />\n\t\t\t\t\t\t\t</template>\n\t\t\t\t\t\t\t{{ deleteLabel }}\n\t\t\t\t\t\t</NcActionButton>\n\t\t\t\t\t</NcActions>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\n\t\t<!-- Picker modal (Tier-2 link existing flow) -->\n\t\t<CnCalendarEventPicker\n\t\t\tv-if=\"showPicker\"\n\t\t\t:apiBase=\"apiBase\"\n\t\t\t@link=\"onPickerLink\"\n\t\t\t@close=\"closePicker\" />\n\n\t\t<!-- Create modal (Tier-2 create-and-link flow) -->\n\t\t<CnCalendarEventCreate\n\t\t\tv-if=\"showCreate\"\n\t\t\t:register=\"register\"\n\t\t\t:schema=\"schema\"\n\t\t\t:objectId=\"objectId\"\n\t\t\t:apiBase=\"apiBase\"\n\t\t\t@created=\"onEventCreated\"\n\t\t\t@close=\"closeCreate\" />\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport {\n\tNcActionButton,\n\tNcActions,\n\tNcButton,\n\tNcLoadingIcon,\n} from '@nextcloud/vue'\nimport AlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue'\nimport ClockOutline from 'vue-material-design-icons/ClockOutline.vue'\nimport Delete from 'vue-material-design-icons/Delete.vue'\nimport LinkVariant from 'vue-material-design-icons/LinkVariant.vue'\nimport LinkVariantOff from 'vue-material-design-icons/LinkVariantOff.vue'\nimport MapMarkerOutline from 'vue-material-design-icons/MapMarkerOutline.vue'\nimport Plus from 'vue-material-design-icons/Plus.vue'\nimport CnCalendarEventCreate from '../../../components/CnCalendarEventCreate/CnCalendarEventCreate.vue'\nimport CnCalendarEventPicker from '../../../components/CnCalendarEventPicker/CnCalendarEventPicker.vue'\nimport CnStatusBadge from '../../../components/CnStatusBadge/CnStatusBadge.vue'\nimport { buildHeaders, prefixUrl } from '../../../utils/index.js'\n\nconst MAX_AVATARS = 3\n\n/**\n * CnCalendarTab — bespoke sidebar tab for the calendar integration.\n *\n * Tier-2 wiring: link/create flows are modal-driven (ADR-004 isolation),\n * and per-row actions distinguish UNLINK (preserves the VEVENT) from\n * DELETE (destroys the VEVENT). Rows mirror NC Calendar's agenda look:\n * date block, time range, title, location, attendee avatars + status.\n */\nexport default {\n\tname: 'CnCalendarTab',\n\n\tcomponents: {\n\t\tNcButton,\n\t\tNcActions,\n\t\tNcActionButton,\n\t\tNcLoadingIcon,\n\t\tCnStatusBadge,\n\t\tPlus,\n\t\tAlertCircleOutline,\n\t\tLinkVariant,\n\t\tLinkVariantOff,\n\t\tClockOutline,\n\t\tMapMarkerOutline,\n\t\tDelete,\n\t\tCnCalendarEventPicker,\n\t\tCnCalendarEventCreate,\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 (matches PHP-side provider id, always `calendar`). */\n\t\tintegrationId: { type: String, default: 'calendar' },\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 for OpenRegister. */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\n\t\t// --- Pre-translated labels (ADR-007) ---\n\t\t/** Label for the button that creates a meeting. */\n\t\taddEventLabel: { type: String, default: () => t('nextcloud-vue', 'Add meeting') },\n\t\t/** Label for the button that links an existing meeting. */\n\t\tlinkExistingLabel: { type: String, default: () => t('nextcloud-vue', 'Link existing') },\n\t\t/** Label for the action that unlinks a meeting from the object. */\n\t\tunlinkLabel: { type: String, default: () => t('nextcloud-vue', 'Unlink') },\n\t\t/** Label for the action that deletes a meeting. */\n\t\tdeleteLabel: { type: String, default: () => t('nextcloud-vue', 'Delete meeting') },\n\t\t/** Empty state shown when no meeting is linked yet. */\n\t\tnoEventsLabel: { type: String, default: () => t('nextcloud-vue', 'No meetings linked yet') },\n\t\t/** Stand-in title for a meeting that has none. */\n\t\tuntitledLabel: { type: String, default: () => t('nextcloud-vue', '(no title)') },\n\t\t/** Heading above the meetings that are still to come. */\n\t\tupcomingLabel: { type: String, default: () => t('nextcloud-vue', 'Upcoming') },\n\t\t/** Heading above the meetings that already happened. */\n\t\tpastLabel: { type: String, default: () => t('nextcloud-vue', 'Past') },\n\t\t/** Message shown when the Calendar app cannot be reached. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'Nextcloud Calendar is currently unavailable.') },\n\t},\n\n\temits: ['linked', 'unlinked', 'deleted'],\n\n\tdata() {\n\t\treturn {\n\t\t\tevents: [],\n\t\t\tloading: false,\n\t\t\terror: '',\n\t\t\tdegraded: false,\n\t\t\trowBusyKey: null,\n\t\t\tshowPicker: false,\n\t\t\tshowCreate: false,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\tsortedEvents() {\n\t\t\tconst copy = Array.isArray(this.events) ? [...this.events] : []\n\t\t\tcopy.sort((a, b) => {\n\t\t\t\tconst ta = a.dtstart ? new Date(a.dtstart).getTime() : Number.POSITIVE_INFINITY\n\t\t\t\tconst tb = b.dtstart ? new Date(b.dtstart).getTime() : Number.POSITIVE_INFINITY\n\t\t\t\treturn ta - tb\n\t\t\t})\n\t\t\treturn copy\n\t\t},\n\n\t\tupcomingEvents() {\n\t\t\tconst now = Date.now()\n\t\t\treturn this.sortedEvents.filter((ev) => {\n\t\t\t\tif (!ev.dtstart) {\n\t\t\t\t\treturn true\n\t\t\t\t}\n\t\t\t\treturn new Date(ev.dtstart).getTime() >= now\n\t\t\t})\n\t\t},\n\n\t\tpastEvents() {\n\t\t\tconst now = Date.now()\n\t\t\treturn this.sortedEvents.filter((ev) => {\n\t\t\t\tif (!ev.dtstart) {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t\treturn new Date(ev.dtstart).getTime() < now\n\t\t\t})\n\t\t},\n\t},\n\n\twatch: {\n\t\tobjectId: {\n\t\t\timmediate: true,\n\t\t\thandler(id) {\n\t\t\t\tif (id) {\n\t\t\t\t\tthis.fetchEvents()\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t},\n\n\tmethods: {\n\t\tbaseObjectUrl() {\n\t\t\treturn prefixUrl(`${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}`)\n\t\t},\n\n\t\trowKey(ev) {\n\t\t\t// Prefer eventUid (Tier-2 stable identifier across calendars).\n\t\t\treturn ev.uid || ev.id || ''\n\t\t},\n\n\t\tasync fetchEvents() {\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 = false\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(`${this.baseObjectUrl()}/events`, { 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.events = data.results || data.items || (Array.isArray(data) ? data : []) || []\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.events = []\n\t\t\t\t\tthis.degraded = true\n\t\t\t\t} else {\n\t\t\t\t\tthis.events = []\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load meetings.')\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('[CnCalendarTab] failed to fetch events', err)\n\t\t\t\tthis.events = []\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load meetings.')\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\topenCreate() {\n\t\t\tthis.showCreate = true\n\t\t},\n\n\t\tcloseCreate() {\n\t\t\tthis.showCreate = false\n\t\t},\n\n\t\tasync onEventCreated() {\n\t\t\tthis.closeCreate()\n\t\t\tawait this.fetchEvents()\n\t\t\tthis.$emit('linked')\n\t\t},\n\n\t\topenPicker() {\n\t\t\tthis.showPicker = true\n\t\t},\n\n\t\tclosePicker() {\n\t\t\tthis.showPicker = false\n\t\t},\n\n\t\tasync onPickerLink(payload) {\n\t\t\t// payload: { calendarUri, eventUid }\n\t\t\tthis.closePicker()\n\t\t\tthis.error = ''\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(`${this.baseObjectUrl()}/events/link`, {\n\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\theaders: buildHeaders(),\n\t\t\t\t\tbody: JSON.stringify(payload),\n\t\t\t\t})\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tawait this.fetchEvents()\n\t\t\t\t\tthis.$emit('linked', payload)\n\t\t\t\t} else {\n\t\t\t\t\tlet message = t('nextcloud-vue', 'Could not link the meeting.')\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst body = await response.json()\n\t\t\t\t\t\tif (body && typeof body.error === 'string') {\n\t\t\t\t\t\t\tmessage = body.error\n\t\t\t\t\t\t}\n\t\t\t\t\t} catch { /* ignore */ }\n\t\t\t\t\tthis.error = message\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('[CnCalendarTab] failed to link event', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not link the meeting.')\n\t\t\t}\n\t\t},\n\n\t\tasync unlink(ev) {\n\t\t\tconst key = this.rowKey(ev)\n\t\t\tif (this.rowBusyKey || !key) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.rowBusyKey = key\n\t\t\tthis.error = ''\n\t\t\ttry {\n\t\t\t\t// Tier-2 unlink-only endpoint: DELETE /events/{eventUid}/link\n\t\t\t\tconst url = `${this.baseObjectUrl()}/events/${encodeURIComponent(key)}/link`\n\t\t\t\tconst response = await fetch(url, { method: 'DELETE', headers: buildHeaders() })\n\t\t\t\tif (response.ok || response.status === 204) {\n\t\t\t\t\tthis.events = this.events.filter((row) => this.rowKey(row) !== key)\n\t\t\t\t\tthis.$emit('unlinked', key)\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.degraded = true\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not unlink the meeting.')\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('[CnCalendarTab] failed to unlink event', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not unlink the meeting.')\n\t\t\t} finally {\n\t\t\t\tthis.rowBusyKey = null\n\t\t\t}\n\t\t},\n\n\t\tasync deleteEvent(ev) {\n\t\t\tconst key = this.rowKey(ev)\n\t\t\tconst eventUri = ev.id\n\t\t\tif (this.rowBusyKey || !eventUri) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.rowBusyKey = key\n\t\t\tthis.error = ''\n\t\t\ttry {\n\t\t\t\t// Legacy destroy endpoint — destroys the VEVENT and cleans the link row.\n\t\t\t\tconst url = `${this.baseObjectUrl()}/events/${encodeURIComponent(eventUri)}`\n\t\t\t\tconst response = await fetch(url, { method: 'DELETE', headers: buildHeaders() })\n\t\t\t\tif (response.ok || response.status === 204) {\n\t\t\t\t\tthis.events = this.events.filter((row) => this.rowKey(row) !== key)\n\t\t\t\t\tthis.$emit('deleted', key)\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not delete the meeting.')\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('[CnCalendarTab] failed to delete event', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not delete the meeting.')\n\t\t\t} finally {\n\t\t\t\tthis.rowBusyKey = null\n\t\t\t}\n\t\t},\n\n\t\t// --- Presentation helpers (NC Calendar agenda look) ---\n\n\t\tparseDate(value) {\n\t\t\tif (!value) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst d = new Date(value)\n\t\t\treturn Number.isNaN(d.getTime()) ? null : d\n\t\t},\n\n\t\tmonthOf(ev) {\n\t\t\tconst d = this.parseDate(ev.dtstart)\n\t\t\tif (!d) {\n\t\t\t\treturn '—'\n\t\t\t}\n\t\t\treturn d.toLocaleDateString(undefined, { month: 'short' })\n\t\t},\n\n\t\tdayOf(ev) {\n\t\t\tconst d = this.parseDate(ev.dtstart)\n\t\t\tif (!d) {\n\t\t\t\treturn '?'\n\t\t\t}\n\t\t\treturn String(d.getDate())\n\t\t},\n\n\t\tformatTimeRange(ev) {\n\t\t\tconst start = this.parseDate(ev.dtstart)\n\t\t\tif (!start) {\n\t\t\t\treturn ev.dtstart ? String(ev.dtstart) : t('nextcloud-vue', 'Time not set')\n\t\t\t}\n\t\t\tconst end = this.parseDate(ev.dtend)\n\t\t\tconst dateLabel = start.toLocaleDateString(undefined, { weekday: 'short', day: 'numeric', month: 'short' })\n\t\t\tconst timeOpts = { hour: '2-digit', minute: '2-digit' }\n\t\t\tconst startTime = start.toLocaleTimeString(undefined, timeOpts)\n\t\t\tif (end) {\n\t\t\t\tconst sameDay = start.toDateString() === end.toDateString()\n\t\t\t\tconst endTime = end.toLocaleTimeString(undefined, timeOpts)\n\t\t\t\tif (sameDay) {\n\t\t\t\t\treturn `${dateLabel} · ${startTime} – ${endTime}`\n\t\t\t\t}\n\t\t\t\tconst endDateLabel = end.toLocaleDateString(undefined, { day: 'numeric', month: 'short' })\n\t\t\t\treturn `${dateLabel} ${startTime} – ${endDateLabel} ${endTime}`\n\t\t\t}\n\t\t\treturn `${dateLabel} · ${startTime}`\n\t\t},\n\n\t\tstatusLabel(ev) {\n\t\t\tconst status = (ev.status || '').toString().toLowerCase()\n\t\t\tif (status === 'confirmed') {\n\t\t\t\treturn t('nextcloud-vue', 'Confirmed')\n\t\t\t}\n\t\t\tif (status === 'tentative') {\n\t\t\t\treturn t('nextcloud-vue', 'Tentative')\n\t\t\t}\n\t\t\tif (status === 'cancelled') {\n\t\t\t\treturn t('nextcloud-vue', 'Cancelled')\n\t\t\t}\n\t\t\treturn ''\n\t\t},\n\n\t\tstatusVariant(ev) {\n\t\t\tconst status = (ev.status || '').toString().toLowerCase()\n\t\t\tif (status === 'confirmed') {\n\t\t\t\treturn 'success'\n\t\t\t}\n\t\t\tif (status === 'tentative') {\n\t\t\t\treturn 'warning'\n\t\t\t}\n\t\t\tif (status === 'cancelled') {\n\t\t\t\treturn 'error'\n\t\t\t}\n\t\t\treturn 'default'\n\t\t},\n\n\t\tattendeeList(ev) {\n\t\t\tconst raw = ev.attendees || ev.participants || []\n\t\t\tif (!Array.isArray(raw)) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\treturn raw.map((att) => {\n\t\t\t\tif (typeof att === 'string') {\n\t\t\t\t\treturn { name: att, initials: this.initialsFor(att) }\n\t\t\t\t}\n\t\t\t\tconst name = att.name || att.displayName || att.cn || att.email || ''\n\t\t\t\treturn { name, initials: this.initialsFor(name) }\n\t\t\t}).filter((a) => a.name)\n\t\t},\n\n\t\tvisibleAttendees(ev) {\n\t\t\treturn this.attendeeList(ev).slice(0, MAX_AVATARS)\n\t\t},\n\n\t\textraAttendees(ev) {\n\t\t\tconst total = this.attendeeList(ev).length\n\t\t\treturn total > MAX_AVATARS ? total - MAX_AVATARS : 0\n\t\t},\n\n\t\tattendeeCountLabel(ev) {\n\t\t\tconst total = this.attendeeList(ev).length\n\t\t\treturn t('nextcloud-vue', '{count} attendees', { count: total })\n\t\t},\n\n\t\tinitialsFor(name) {\n\t\t\tconst clean = (name || '').trim()\n\t\t\tif (!clean) {\n\t\t\t\treturn '?'\n\t\t\t}\n\t\t\tconst local = clean.includes('@') ? clean.split('@')[0] : clean\n\t\t\tconst parts = local.split(/[\\s._-]+/).filter(Boolean)\n\t\t\tif (parts.length === 0) {\n\t\t\t\treturn clean.charAt(0).toUpperCase()\n\t\t\t}\n\t\t\tif (parts.length === 1) {\n\t\t\t\treturn parts[0].charAt(0).toUpperCase()\n\t\t\t}\n\t\t\treturn (parts[0].charAt(0) + parts[parts.length - 1].charAt(0)).toUpperCase()\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-calendar-tab {\n\tpadding: 12px;\n\toverflow-x: hidden;\n}\n\n.cn-calendar-tab__action-row {\n\tdisplay: flex;\n\tgap: 8px;\n\tmargin-bottom: 12px;\n}\n\n.cn-calendar-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-calendar-tab__banner--error {\n\tbackground: var(--color-error, #e9322d);\n}\n\n.cn-calendar-tab__empty {\n\ttext-align: center;\n\tpadding: 24px 12px;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 13px;\n}\n\n.cn-calendar-tab__agenda {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 14px;\n}\n\n.cn-calendar-tab__group {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n}\n\n.cn-calendar-tab__group-title {\n\tmargin: 0 0 4px 0;\n\tfont-size: 12px;\n\tfont-weight: 600;\n\ttext-transform: uppercase;\n\tletter-spacing: 0.04em;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-calendar-tab__group-title--past {\n\topacity: 0.7;\n}\n\n/* Event row */\n.cn-calendar-tab__event {\n\tdisplay: flex;\n\talign-items: flex-start;\n\tgap: 12px;\n\tpadding: 8px;\n\tborder-radius: var(--border-radius-large, 8px);\n\tborder-left: 3px solid var(--color-primary-element);\n\tbackground: var(--color-background-hover);\n}\n\n.cn-calendar-tab__event + .cn-calendar-tab__event {\n\tmargin-top: 4px;\n}\n\n.cn-calendar-tab__event--past {\n\tborder-left-color: var(--color-border-dark, var(--color-border));\n\tbackground: transparent;\n\topacity: 0.72;\n}\n\n/* Date block */\n.cn-calendar-tab__date {\n\tflex-shrink: 0;\n\twidth: 44px;\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\tjustify-content: center;\n\tpadding: 4px 0;\n\tborder-radius: var(--border-radius);\n\tbackground: var(--color-primary-element);\n\tcolor: var(--color-primary-element-text, #fff);\n\tline-height: 1.1;\n}\n\n.cn-calendar-tab__date--past {\n\tbackground: var(--color-background-dark);\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-calendar-tab__date-month {\n\tfont-size: 10px;\n\tfont-weight: 600;\n\ttext-transform: uppercase;\n\tletter-spacing: 0.05em;\n}\n\n.cn-calendar-tab__date-day {\n\tfont-size: 18px;\n\tfont-weight: 700;\n}\n\n/* Main column */\n.cn-calendar-tab__main {\n\tflex: 1;\n\tmin-width: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 3px;\n}\n\n.cn-calendar-tab__title-row {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 6px;\n\tmin-width: 0;\n}\n\n.cn-calendar-tab__title {\n\tfont-size: 14px;\n\tfont-weight: 600;\n\tcolor: var(--color-main-text);\n\twhite-space: nowrap;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n.cn-calendar-tab__when,\n.cn-calendar-tab__location {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 5px;\n\tfont-size: 12px;\n\tcolor: var(--color-text-maxcontrast);\n\tmin-width: 0;\n}\n\n.cn-calendar-tab__meta-icon {\n\tflex-shrink: 0;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-calendar-tab__location-text {\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n/* Attendees */\n.cn-calendar-tab__attendees {\n\tdisplay: flex;\n\talign-items: center;\n\tmargin-top: 2px;\n}\n\n.cn-calendar-tab__avatar {\n\twidth: 24px;\n\theight: 24px;\n\tborder-radius: 50%;\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\tfont-size: 10px;\n\tfont-weight: 600;\n\tbackground: var(--color-primary-element-light, var(--color-primary-light));\n\tcolor: var(--color-primary-element-light-text, var(--color-primary-element));\n\tborder: 2px solid var(--color-main-background);\n\tmargin-left: -6px;\n\tflex-shrink: 0;\n}\n\n.cn-calendar-tab__avatar:first-child {\n\tmargin-left: 0;\n}\n\n.cn-calendar-tab__avatar--more {\n\tbackground: var(--color-background-dark);\n\tcolor: var(--color-text-maxcontrast);\n}\n\n/* Actions */\n.cn-calendar-tab__actions {\n\tflex-shrink: 0;\n}\n</style>\n"],"names":["_createElementVNode","_createVNode","_createTextVNode","_openBlock","_createElementBlock","_toDisplayString","_Fragment","_renderList"],"mappings":"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"}
1
+ {"version":3,"file":"CnCalendarTab.vue3.js","sources":["../../../../../src/integrations/builtin/calendar/CnCalendarTab.vue?vue&type=template&id=bc96c764&lang.js"],"sourcesContent":["<!--\n CnCalendarTab — bespoke sidebar tab for the `calendar` integration.\n\n Tier-2 rewire (additive link-table strategy):\n - \"Add meeting\" button opens CnCalendarEventCreate modal that POSTs\n to /api/objects/{r}/{s}/{id}/events (writes X-OR-* on the VEVENT\n AND a link-table row).\n - \"Link existing meeting\" button opens CnCalendarEventPicker modal\n that POSTs to /api/objects/{r}/{s}/{id}/events/link (writes ONLY\n a link-table row — we may not own the VEVENT).\n - Per-row UNLINK action: DELETE /events/{eventUid}/link — removes\n the link only, the VEVENT survives on the user's calendar.\n - Per-row DELETE action: DELETE /events/{eventId} — destroys the\n VEVENT (legacy semantics — also cleans up the link-table row).\n\n Read path: GET /api/objects/{r}/{s}/{id}/events returns the UNION of\n link-table rows and the legacy X-OR-* CalDAV scan, deduped by\n (calendarUri, eventUid) and annotated with a `source` field\n (link-table / xor-only / both).\n\n Visual fidelity (NC Calendar): each row foregrounds a date block (month\n abbreviation + day-of-month, accented like the Calendar app's agenda),\n a start–end time range, a bold event title, the location with a\n map-marker icon, an attendee avatar stack with an overflow count, and a\n CnStatusBadge for the VEVENT status (confirmed / tentative / cancelled).\n\n ADR-004 modal isolation: every modal lives in its own .vue file under\n `src/components/CnCalendar*/` and is imported here as a child component.\n-->\n<template>\n\t<div class=\"cn-sidebar-tab cn-calendar-tab\">\n\t\t<!-- Action bar (replaces the inline create form) -->\n\t\t<div class=\"cn-calendar-tab__action-row\">\n\t\t\t<NcButton variant=\"primary\" @click=\"openCreate\">\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{{ addEventLabel }}\n\t\t\t</NcButton>\n\t\t\t<NcButton variant=\"secondary\" @click=\"openPicker\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<LinkVariant :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ linkExistingLabel }}\n\t\t\t</NcButton>\n\t\t</div>\n\n\t\t<!-- Error banner -->\n\t\t<div v-if=\"error\" class=\"cn-calendar-tab__banner cn-calendar-tab__banner--error\" role=\"alert\">\n\t\t\t<AlertCircleOutline :size=\"18\" />\n\t\t\t<span>{{ error }}</span>\n\t\t</div>\n\n\t\t<!-- Unavailable banner (503) -->\n\t\t<div v-if=\"degraded\" class=\"cn-calendar-tab__banner\" role=\"status\">\n\t\t\t<AlertCircleOutline :size=\"18\" />\n\t\t\t<span>{{ unavailableLabel }}</span>\n\t\t</div>\n\n\t\t<NcLoadingIcon v-if=\"loading\" />\n\t\t<div v-else-if=\"sortedEvents.length === 0\" class=\"cn-sidebar-tab__empty cn-calendar-tab__empty\">\n\t\t\t{{ noEventsLabel }}\n\t\t</div>\n\n\t\t<!-- Agenda -->\n\t\t<div v-else class=\"cn-calendar-tab__agenda\">\n\t\t\t<div v-if=\"upcomingEvents.length > 0\" class=\"cn-calendar-tab__group\">\n\t\t\t\t<h4 class=\"cn-calendar-tab__group-title\">\n\t\t\t\t\t{{ upcomingLabel }}\n\t\t\t\t</h4>\n\t\t\t\t<div\n\t\t\t\t\tv-for=\"ev in upcomingEvents\"\n\t\t\t\t\t:key=\"rowKey(ev)\"\n\t\t\t\t\tclass=\"cn-calendar-tab__event\">\n\t\t\t\t\t<!-- Date block (month + day) -->\n\t\t\t\t\t<div class=\"cn-calendar-tab__date\" aria-hidden=\"true\">\n\t\t\t\t\t\t<span class=\"cn-calendar-tab__date-month\">{{ monthOf(ev) }}</span>\n\t\t\t\t\t\t<span class=\"cn-calendar-tab__date-day\">{{ dayOf(ev) }}</span>\n\t\t\t\t\t</div>\n\n\t\t\t\t\t<!-- Main column: title, time range, location, attendees -->\n\t\t\t\t\t<div class=\"cn-calendar-tab__main\">\n\t\t\t\t\t\t<div class=\"cn-calendar-tab__title-row\">\n\t\t\t\t\t\t\t<span class=\"cn-calendar-tab__title\">{{ ev.summary || untitledLabel }}</span>\n\t\t\t\t\t\t\t<CnStatusBadge\n\t\t\t\t\t\t\t\tv-if=\"statusLabel(ev)\"\n\t\t\t\t\t\t\t\t:label=\"statusLabel(ev)\"\n\t\t\t\t\t\t\t\t:variant=\"statusVariant(ev)\"\n\t\t\t\t\t\t\t\tsize=\"small\" />\n\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t<div class=\"cn-calendar-tab__when\">\n\t\t\t\t\t\t\t<ClockOutline :size=\"14\" class=\"cn-calendar-tab__meta-icon\" />\n\t\t\t\t\t\t\t<span>{{ formatTimeRange(ev) }}</span>\n\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t<div v-if=\"ev.location\" class=\"cn-calendar-tab__location\">\n\t\t\t\t\t\t\t<MapMarkerOutline :size=\"14\" class=\"cn-calendar-tab__meta-icon\" />\n\t\t\t\t\t\t\t<span class=\"cn-calendar-tab__location-text\">{{ ev.location }}</span>\n\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t<div v-if=\"attendeeList(ev).length > 0\" class=\"cn-calendar-tab__attendees\">\n\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\tv-for=\"(att, idx) in visibleAttendees(ev)\"\n\t\t\t\t\t\t\t\t:key=\"idx\"\n\t\t\t\t\t\t\t\tclass=\"cn-calendar-tab__avatar\"\n\t\t\t\t\t\t\t\t:title=\"att.name\">\n\t\t\t\t\t\t\t\t{{ att.initials }}\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\tv-if=\"extraAttendees(ev) > 0\"\n\t\t\t\t\t\t\t\tclass=\"cn-calendar-tab__avatar cn-calendar-tab__avatar--more\"\n\t\t\t\t\t\t\t\t:title=\"attendeeCountLabel(ev)\">\n\t\t\t\t\t\t\t\t+{{ extraAttendees(ev) }}\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\n\t\t\t\t\t<!-- Row actions -->\n\t\t\t\t\t<NcActions :forceMenu=\"true\" class=\"cn-calendar-tab__actions\">\n\t\t\t\t\t\t<NcActionButton :disabled=\"rowBusyKey === rowKey(ev)\" @click=\"unlink(ev)\">\n\t\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t\t<LinkVariantOff :size=\"20\" />\n\t\t\t\t\t\t\t</template>\n\t\t\t\t\t\t\t{{ unlinkLabel }}\n\t\t\t\t\t\t</NcActionButton>\n\t\t\t\t\t\t<NcActionButton :disabled=\"rowBusyKey === rowKey(ev)\" @click=\"deleteEvent(ev)\">\n\t\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t\t<Delete :size=\"20\" />\n\t\t\t\t\t\t\t</template>\n\t\t\t\t\t\t\t{{ deleteLabel }}\n\t\t\t\t\t\t</NcActionButton>\n\t\t\t\t\t</NcActions>\n\t\t\t\t</div>\n\t\t\t</div>\n\n\t\t\t<div v-if=\"pastEvents.length > 0\" class=\"cn-calendar-tab__group\">\n\t\t\t\t<h4 class=\"cn-calendar-tab__group-title cn-calendar-tab__group-title--past\">\n\t\t\t\t\t{{ pastLabel }}\n\t\t\t\t</h4>\n\t\t\t\t<div\n\t\t\t\t\tv-for=\"ev in pastEvents\"\n\t\t\t\t\t:key=\"rowKey(ev)\"\n\t\t\t\t\tclass=\"cn-calendar-tab__event cn-calendar-tab__event--past\">\n\t\t\t\t\t<div class=\"cn-calendar-tab__date cn-calendar-tab__date--past\" aria-hidden=\"true\">\n\t\t\t\t\t\t<span class=\"cn-calendar-tab__date-month\">{{ monthOf(ev) }}</span>\n\t\t\t\t\t\t<span class=\"cn-calendar-tab__date-day\">{{ dayOf(ev) }}</span>\n\t\t\t\t\t</div>\n\n\t\t\t\t\t<div class=\"cn-calendar-tab__main\">\n\t\t\t\t\t\t<div class=\"cn-calendar-tab__title-row\">\n\t\t\t\t\t\t\t<span class=\"cn-calendar-tab__title\">{{ ev.summary || untitledLabel }}</span>\n\t\t\t\t\t\t\t<CnStatusBadge\n\t\t\t\t\t\t\t\tv-if=\"statusLabel(ev)\"\n\t\t\t\t\t\t\t\t:label=\"statusLabel(ev)\"\n\t\t\t\t\t\t\t\t:variant=\"statusVariant(ev)\"\n\t\t\t\t\t\t\t\tsize=\"small\" />\n\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t<div class=\"cn-calendar-tab__when\">\n\t\t\t\t\t\t\t<ClockOutline :size=\"14\" class=\"cn-calendar-tab__meta-icon\" />\n\t\t\t\t\t\t\t<span>{{ formatTimeRange(ev) }}</span>\n\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t<div v-if=\"ev.location\" class=\"cn-calendar-tab__location\">\n\t\t\t\t\t\t\t<MapMarkerOutline :size=\"14\" class=\"cn-calendar-tab__meta-icon\" />\n\t\t\t\t\t\t\t<span class=\"cn-calendar-tab__location-text\">{{ ev.location }}</span>\n\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t<div v-if=\"attendeeList(ev).length > 0\" class=\"cn-calendar-tab__attendees\">\n\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\tv-for=\"(att, idx) in visibleAttendees(ev)\"\n\t\t\t\t\t\t\t\t:key=\"idx\"\n\t\t\t\t\t\t\t\tclass=\"cn-calendar-tab__avatar\"\n\t\t\t\t\t\t\t\t:title=\"att.name\">\n\t\t\t\t\t\t\t\t{{ att.initials }}\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\tv-if=\"extraAttendees(ev) > 0\"\n\t\t\t\t\t\t\t\tclass=\"cn-calendar-tab__avatar cn-calendar-tab__avatar--more\"\n\t\t\t\t\t\t\t\t:title=\"attendeeCountLabel(ev)\">\n\t\t\t\t\t\t\t\t+{{ extraAttendees(ev) }}\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\n\t\t\t\t\t<NcActions :forceMenu=\"true\" class=\"cn-calendar-tab__actions\">\n\t\t\t\t\t\t<NcActionButton :disabled=\"rowBusyKey === rowKey(ev)\" @click=\"unlink(ev)\">\n\t\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t\t<LinkVariantOff :size=\"20\" />\n\t\t\t\t\t\t\t</template>\n\t\t\t\t\t\t\t{{ unlinkLabel }}\n\t\t\t\t\t\t</NcActionButton>\n\t\t\t\t\t\t<NcActionButton :disabled=\"rowBusyKey === rowKey(ev)\" @click=\"deleteEvent(ev)\">\n\t\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t\t<Delete :size=\"20\" />\n\t\t\t\t\t\t\t</template>\n\t\t\t\t\t\t\t{{ deleteLabel }}\n\t\t\t\t\t\t</NcActionButton>\n\t\t\t\t\t</NcActions>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\n\t\t<!-- Picker modal (Tier-2 link existing flow) -->\n\t\t<CnCalendarEventPicker\n\t\t\tv-if=\"showPicker\"\n\t\t\t:apiBase=\"apiBase\"\n\t\t\t@link=\"onPickerLink\"\n\t\t\t@close=\"closePicker\" />\n\n\t\t<!-- Create modal (Tier-2 create-and-link flow) -->\n\t\t<CnCalendarEventCreate\n\t\t\tv-if=\"showCreate\"\n\t\t\t:register=\"register\"\n\t\t\t:schema=\"schema\"\n\t\t\t:objectId=\"objectId\"\n\t\t\t:apiBase=\"apiBase\"\n\t\t\t@created=\"onEventCreated\"\n\t\t\t@close=\"closeCreate\" />\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport {\n\tNcActionButton,\n\tNcActions,\n\tNcButton,\n\tNcLoadingIcon,\n} from '@nextcloud/vue'\nimport AlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue'\nimport ClockOutline from 'vue-material-design-icons/ClockOutline.vue'\nimport Delete from 'vue-material-design-icons/Delete.vue'\nimport LinkVariant from 'vue-material-design-icons/LinkVariant.vue'\nimport LinkVariantOff from 'vue-material-design-icons/LinkVariantOff.vue'\nimport MapMarkerOutline from 'vue-material-design-icons/MapMarkerOutline.vue'\nimport Plus from 'vue-material-design-icons/Plus.vue'\nimport CnCalendarEventCreate from '../../../components/CnCalendarEventCreate/CnCalendarEventCreate.vue'\nimport CnCalendarEventPicker from '../../../components/CnCalendarEventPicker/CnCalendarEventPicker.vue'\nimport CnStatusBadge from '../../../components/CnStatusBadge/CnStatusBadge.vue'\nimport { buildHeaders, prefixUrl } from '../../../utils/index.js'\nimport { userLocale } from '../../../utils/userLocale.js'\n\nconst MAX_AVATARS = 3\n\n/**\n * CnCalendarTab — bespoke sidebar tab for the calendar integration.\n *\n * Tier-2 wiring: link/create flows are modal-driven (ADR-004 isolation),\n * and per-row actions distinguish UNLINK (preserves the VEVENT) from\n * DELETE (destroys the VEVENT). Rows mirror NC Calendar's agenda look:\n * date block, time range, title, location, attendee avatars + status.\n */\nexport default {\n\tname: 'CnCalendarTab',\n\n\tcomponents: {\n\t\tNcButton,\n\t\tNcActions,\n\t\tNcActionButton,\n\t\tNcLoadingIcon,\n\t\tCnStatusBadge,\n\t\tPlus,\n\t\tAlertCircleOutline,\n\t\tLinkVariant,\n\t\tLinkVariantOff,\n\t\tClockOutline,\n\t\tMapMarkerOutline,\n\t\tDelete,\n\t\tCnCalendarEventPicker,\n\t\tCnCalendarEventCreate,\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 (matches PHP-side provider id, always `calendar`). */\n\t\tintegrationId: { type: String, default: 'calendar' },\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 for OpenRegister. */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\n\t\t// --- Pre-translated labels (ADR-007) ---\n\t\t/** Label for the button that creates a meeting. */\n\t\taddEventLabel: { type: String, default: () => t('nextcloud-vue', 'Add meeting') },\n\t\t/** Label for the button that links an existing meeting. */\n\t\tlinkExistingLabel: { type: String, default: () => t('nextcloud-vue', 'Link existing') },\n\t\t/** Label for the action that unlinks a meeting from the object. */\n\t\tunlinkLabel: { type: String, default: () => t('nextcloud-vue', 'Unlink') },\n\t\t/** Label for the action that deletes a meeting. */\n\t\tdeleteLabel: { type: String, default: () => t('nextcloud-vue', 'Delete meeting') },\n\t\t/** Empty state shown when no meeting is linked yet. */\n\t\tnoEventsLabel: { type: String, default: () => t('nextcloud-vue', 'No meetings linked yet') },\n\t\t/** Stand-in title for a meeting that has none. */\n\t\tuntitledLabel: { type: String, default: () => t('nextcloud-vue', '(no title)') },\n\t\t/** Heading above the meetings that are still to come. */\n\t\tupcomingLabel: { type: String, default: () => t('nextcloud-vue', 'Upcoming') },\n\t\t/** Heading above the meetings that already happened. */\n\t\tpastLabel: { type: String, default: () => t('nextcloud-vue', 'Past') },\n\t\t/** Message shown when the Calendar app cannot be reached. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'Nextcloud Calendar is currently unavailable.') },\n\t},\n\n\temits: ['linked', 'unlinked', 'deleted'],\n\n\tdata() {\n\t\treturn {\n\t\t\tevents: [],\n\t\t\tloading: false,\n\t\t\terror: '',\n\t\t\tdegraded: false,\n\t\t\trowBusyKey: null,\n\t\t\tshowPicker: false,\n\t\t\tshowCreate: false,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\tsortedEvents() {\n\t\t\tconst copy = Array.isArray(this.events) ? [...this.events] : []\n\t\t\tcopy.sort((a, b) => {\n\t\t\t\tconst ta = a.dtstart ? new Date(a.dtstart).getTime() : Number.POSITIVE_INFINITY\n\t\t\t\tconst tb = b.dtstart ? new Date(b.dtstart).getTime() : Number.POSITIVE_INFINITY\n\t\t\t\treturn ta - tb\n\t\t\t})\n\t\t\treturn copy\n\t\t},\n\n\t\tupcomingEvents() {\n\t\t\tconst now = Date.now()\n\t\t\treturn this.sortedEvents.filter((ev) => {\n\t\t\t\tif (!ev.dtstart) {\n\t\t\t\t\treturn true\n\t\t\t\t}\n\t\t\t\treturn new Date(ev.dtstart).getTime() >= now\n\t\t\t})\n\t\t},\n\n\t\tpastEvents() {\n\t\t\tconst now = Date.now()\n\t\t\treturn this.sortedEvents.filter((ev) => {\n\t\t\t\tif (!ev.dtstart) {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t\treturn new Date(ev.dtstart).getTime() < now\n\t\t\t})\n\t\t},\n\t},\n\n\twatch: {\n\t\tobjectId: {\n\t\t\timmediate: true,\n\t\t\thandler(id) {\n\t\t\t\tif (id) {\n\t\t\t\t\tthis.fetchEvents()\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t},\n\n\tmethods: {\n\t\tbaseObjectUrl() {\n\t\t\treturn prefixUrl(`${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}`)\n\t\t},\n\n\t\trowKey(ev) {\n\t\t\t// Prefer eventUid (Tier-2 stable identifier across calendars).\n\t\t\treturn ev.uid || ev.id || ''\n\t\t},\n\n\t\tasync fetchEvents() {\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 = false\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(`${this.baseObjectUrl()}/events`, { 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.events = data.results || data.items || (Array.isArray(data) ? data : []) || []\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.events = []\n\t\t\t\t\tthis.degraded = true\n\t\t\t\t} else {\n\t\t\t\t\tthis.events = []\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load meetings.')\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('[CnCalendarTab] failed to fetch events', err)\n\t\t\t\tthis.events = []\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load meetings.')\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\topenCreate() {\n\t\t\tthis.showCreate = true\n\t\t},\n\n\t\tcloseCreate() {\n\t\t\tthis.showCreate = false\n\t\t},\n\n\t\tasync onEventCreated() {\n\t\t\tthis.closeCreate()\n\t\t\tawait this.fetchEvents()\n\t\t\tthis.$emit('linked')\n\t\t},\n\n\t\topenPicker() {\n\t\t\tthis.showPicker = true\n\t\t},\n\n\t\tclosePicker() {\n\t\t\tthis.showPicker = false\n\t\t},\n\n\t\tasync onPickerLink(payload) {\n\t\t\t// payload: { calendarUri, eventUid }\n\t\t\tthis.closePicker()\n\t\t\tthis.error = ''\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(`${this.baseObjectUrl()}/events/link`, {\n\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\theaders: buildHeaders(),\n\t\t\t\t\tbody: JSON.stringify(payload),\n\t\t\t\t})\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tawait this.fetchEvents()\n\t\t\t\t\tthis.$emit('linked', payload)\n\t\t\t\t} else {\n\t\t\t\t\tlet message = t('nextcloud-vue', 'Could not link the meeting.')\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst body = await response.json()\n\t\t\t\t\t\tif (body && typeof body.error === 'string') {\n\t\t\t\t\t\t\tmessage = body.error\n\t\t\t\t\t\t}\n\t\t\t\t\t} catch { /* ignore */ }\n\t\t\t\t\tthis.error = message\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('[CnCalendarTab] failed to link event', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not link the meeting.')\n\t\t\t}\n\t\t},\n\n\t\tasync unlink(ev) {\n\t\t\tconst key = this.rowKey(ev)\n\t\t\tif (this.rowBusyKey || !key) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.rowBusyKey = key\n\t\t\tthis.error = ''\n\t\t\ttry {\n\t\t\t\t// Tier-2 unlink-only endpoint: DELETE /events/{eventUid}/link\n\t\t\t\tconst url = `${this.baseObjectUrl()}/events/${encodeURIComponent(key)}/link`\n\t\t\t\tconst response = await fetch(url, { method: 'DELETE', headers: buildHeaders() })\n\t\t\t\tif (response.ok || response.status === 204) {\n\t\t\t\t\tthis.events = this.events.filter((row) => this.rowKey(row) !== key)\n\t\t\t\t\tthis.$emit('unlinked', key)\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.degraded = true\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not unlink the meeting.')\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('[CnCalendarTab] failed to unlink event', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not unlink the meeting.')\n\t\t\t} finally {\n\t\t\t\tthis.rowBusyKey = null\n\t\t\t}\n\t\t},\n\n\t\tasync deleteEvent(ev) {\n\t\t\tconst key = this.rowKey(ev)\n\t\t\tconst eventUri = ev.id\n\t\t\tif (this.rowBusyKey || !eventUri) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.rowBusyKey = key\n\t\t\tthis.error = ''\n\t\t\ttry {\n\t\t\t\t// Legacy destroy endpoint — destroys the VEVENT and cleans the link row.\n\t\t\t\tconst url = `${this.baseObjectUrl()}/events/${encodeURIComponent(eventUri)}`\n\t\t\t\tconst response = await fetch(url, { method: 'DELETE', headers: buildHeaders() })\n\t\t\t\tif (response.ok || response.status === 204) {\n\t\t\t\t\tthis.events = this.events.filter((row) => this.rowKey(row) !== key)\n\t\t\t\t\tthis.$emit('deleted', key)\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not delete the meeting.')\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('[CnCalendarTab] failed to delete event', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not delete the meeting.')\n\t\t\t} finally {\n\t\t\t\tthis.rowBusyKey = null\n\t\t\t}\n\t\t},\n\n\t\t// --- Presentation helpers (NC Calendar agenda look) ---\n\n\t\tparseDate(value) {\n\t\t\tif (!value) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst d = new Date(value)\n\t\t\treturn Number.isNaN(d.getTime()) ? null : d\n\t\t},\n\n\t\tmonthOf(ev) {\n\t\t\tconst d = this.parseDate(ev.dtstart)\n\t\t\tif (!d) {\n\t\t\t\treturn '—'\n\t\t\t}\n\t\t\treturn d.toLocaleDateString(userLocale(), { month: 'short' })\n\t\t},\n\n\t\tdayOf(ev) {\n\t\t\tconst d = this.parseDate(ev.dtstart)\n\t\t\tif (!d) {\n\t\t\t\treturn '?'\n\t\t\t}\n\t\t\treturn String(d.getDate())\n\t\t},\n\n\t\tformatTimeRange(ev) {\n\t\t\tconst start = this.parseDate(ev.dtstart)\n\t\t\tif (!start) {\n\t\t\t\treturn ev.dtstart ? String(ev.dtstart) : t('nextcloud-vue', 'Time not set')\n\t\t\t}\n\t\t\tconst end = this.parseDate(ev.dtend)\n\t\t\tconst dateLabel = start.toLocaleDateString(userLocale(), { weekday: 'short', day: 'numeric', month: 'short' })\n\t\t\tconst timeOpts = { hour: '2-digit', minute: '2-digit' }\n\t\t\tconst startTime = start.toLocaleTimeString(userLocale(), timeOpts)\n\t\t\tif (end) {\n\t\t\t\tconst sameDay = start.toDateString() === end.toDateString()\n\t\t\t\tconst endTime = end.toLocaleTimeString(userLocale(), timeOpts)\n\t\t\t\tif (sameDay) {\n\t\t\t\t\treturn `${dateLabel} · ${startTime} – ${endTime}`\n\t\t\t\t}\n\t\t\t\tconst endDateLabel = end.toLocaleDateString(userLocale(), { day: 'numeric', month: 'short' })\n\t\t\t\treturn `${dateLabel} ${startTime} – ${endDateLabel} ${endTime}`\n\t\t\t}\n\t\t\treturn `${dateLabel} · ${startTime}`\n\t\t},\n\n\t\tstatusLabel(ev) {\n\t\t\tconst status = (ev.status || '').toString().toLowerCase()\n\t\t\tif (status === 'confirmed') {\n\t\t\t\treturn t('nextcloud-vue', 'Confirmed')\n\t\t\t}\n\t\t\tif (status === 'tentative') {\n\t\t\t\treturn t('nextcloud-vue', 'Tentative')\n\t\t\t}\n\t\t\tif (status === 'cancelled') {\n\t\t\t\treturn t('nextcloud-vue', 'Cancelled')\n\t\t\t}\n\t\t\treturn ''\n\t\t},\n\n\t\tstatusVariant(ev) {\n\t\t\tconst status = (ev.status || '').toString().toLowerCase()\n\t\t\tif (status === 'confirmed') {\n\t\t\t\treturn 'success'\n\t\t\t}\n\t\t\tif (status === 'tentative') {\n\t\t\t\treturn 'warning'\n\t\t\t}\n\t\t\tif (status === 'cancelled') {\n\t\t\t\treturn 'error'\n\t\t\t}\n\t\t\treturn 'default'\n\t\t},\n\n\t\tattendeeList(ev) {\n\t\t\tconst raw = ev.attendees || ev.participants || []\n\t\t\tif (!Array.isArray(raw)) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\treturn raw.map((att) => {\n\t\t\t\tif (typeof att === 'string') {\n\t\t\t\t\treturn { name: att, initials: this.initialsFor(att) }\n\t\t\t\t}\n\t\t\t\tconst name = att.name || att.displayName || att.cn || att.email || ''\n\t\t\t\treturn { name, initials: this.initialsFor(name) }\n\t\t\t}).filter((a) => a.name)\n\t\t},\n\n\t\tvisibleAttendees(ev) {\n\t\t\treturn this.attendeeList(ev).slice(0, MAX_AVATARS)\n\t\t},\n\n\t\textraAttendees(ev) {\n\t\t\tconst total = this.attendeeList(ev).length\n\t\t\treturn total > MAX_AVATARS ? total - MAX_AVATARS : 0\n\t\t},\n\n\t\tattendeeCountLabel(ev) {\n\t\t\tconst total = this.attendeeList(ev).length\n\t\t\treturn t('nextcloud-vue', '{count} attendees', { count: total })\n\t\t},\n\n\t\tinitialsFor(name) {\n\t\t\tconst clean = (name || '').trim()\n\t\t\tif (!clean) {\n\t\t\t\treturn '?'\n\t\t\t}\n\t\t\tconst local = clean.includes('@') ? clean.split('@')[0] : clean\n\t\t\tconst parts = local.split(/[\\s._-]+/).filter(Boolean)\n\t\t\tif (parts.length === 0) {\n\t\t\t\treturn clean.charAt(0).toUpperCase()\n\t\t\t}\n\t\t\tif (parts.length === 1) {\n\t\t\t\treturn parts[0].charAt(0).toUpperCase()\n\t\t\t}\n\t\t\treturn (parts[0].charAt(0) + parts[parts.length - 1].charAt(0)).toUpperCase()\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-calendar-tab {\n\tpadding: 12px;\n\toverflow-x: hidden;\n}\n\n.cn-calendar-tab__action-row {\n\tdisplay: flex;\n\tgap: 8px;\n\tmargin-bottom: 12px;\n}\n\n.cn-calendar-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-calendar-tab__banner--error {\n\tbackground: var(--color-error, #e9322d);\n}\n\n.cn-calendar-tab__empty {\n\ttext-align: center;\n\tpadding: 24px 12px;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 13px;\n}\n\n.cn-calendar-tab__agenda {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 14px;\n}\n\n.cn-calendar-tab__group {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n}\n\n.cn-calendar-tab__group-title {\n\tmargin: 0 0 4px 0;\n\tfont-size: 12px;\n\tfont-weight: 600;\n\ttext-transform: uppercase;\n\tletter-spacing: 0.04em;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-calendar-tab__group-title--past {\n\topacity: 0.7;\n}\n\n/* Event row */\n.cn-calendar-tab__event {\n\tdisplay: flex;\n\talign-items: flex-start;\n\tgap: 12px;\n\tpadding: 8px;\n\tborder-radius: var(--border-radius-large, 8px);\n\tborder-left: 3px solid var(--color-primary-element);\n\tbackground: var(--color-background-hover);\n}\n\n.cn-calendar-tab__event + .cn-calendar-tab__event {\n\tmargin-top: 4px;\n}\n\n.cn-calendar-tab__event--past {\n\tborder-left-color: var(--color-border-dark, var(--color-border));\n\tbackground: transparent;\n\topacity: 0.72;\n}\n\n/* Date block */\n.cn-calendar-tab__date {\n\tflex-shrink: 0;\n\twidth: 44px;\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\tjustify-content: center;\n\tpadding: 4px 0;\n\tborder-radius: var(--border-radius);\n\tbackground: var(--color-primary-element);\n\tcolor: var(--color-primary-element-text, #fff);\n\tline-height: 1.1;\n}\n\n.cn-calendar-tab__date--past {\n\tbackground: var(--color-background-dark);\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-calendar-tab__date-month {\n\tfont-size: 10px;\n\tfont-weight: 600;\n\ttext-transform: uppercase;\n\tletter-spacing: 0.05em;\n}\n\n.cn-calendar-tab__date-day {\n\tfont-size: 18px;\n\tfont-weight: 700;\n}\n\n/* Main column */\n.cn-calendar-tab__main {\n\tflex: 1;\n\tmin-width: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 3px;\n}\n\n.cn-calendar-tab__title-row {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 6px;\n\tmin-width: 0;\n}\n\n.cn-calendar-tab__title {\n\tfont-size: 14px;\n\tfont-weight: 600;\n\tcolor: var(--color-main-text);\n\twhite-space: nowrap;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n.cn-calendar-tab__when,\n.cn-calendar-tab__location {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 5px;\n\tfont-size: 12px;\n\tcolor: var(--color-text-maxcontrast);\n\tmin-width: 0;\n}\n\n.cn-calendar-tab__meta-icon {\n\tflex-shrink: 0;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-calendar-tab__location-text {\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n/* Attendees */\n.cn-calendar-tab__attendees {\n\tdisplay: flex;\n\talign-items: center;\n\tmargin-top: 2px;\n}\n\n.cn-calendar-tab__avatar {\n\twidth: 24px;\n\theight: 24px;\n\tborder-radius: 50%;\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\tfont-size: 10px;\n\tfont-weight: 600;\n\tbackground: var(--color-primary-element-light, var(--color-primary-light));\n\tcolor: var(--color-primary-element-light-text, var(--color-primary-element));\n\tborder: 2px solid var(--color-main-background);\n\tmargin-left: -6px;\n\tflex-shrink: 0;\n}\n\n.cn-calendar-tab__avatar:first-child {\n\tmargin-left: 0;\n}\n\n.cn-calendar-tab__avatar--more {\n\tbackground: var(--color-background-dark);\n\tcolor: var(--color-text-maxcontrast);\n}\n\n/* Actions */\n.cn-calendar-tab__actions {\n\tflex-shrink: 0;\n}\n</style>\n"],"names":["_createElementVNode","_createVNode","_createTextVNode","_openBlock","_createElementBlock","_toDisplayString","_Fragment","_renderList"],"mappings":"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"}
@@ -2,7 +2,7 @@ import script from './CnContactmomentCard.vue2.js';
2
2
  import { render } from './CnContactmomentCard.vue3.js';
3
3
 
4
4
  script.render = render;
5
- script.__scopeId = "data-v-5c6657ff";
5
+ script.__scopeId = "data-v-d9c8e564";
6
6
  const __sfc_main = script;
7
7
 
8
8
  export { __sfc_main as default };
@@ -2,6 +2,7 @@ import { translate } from '@nextcloud/l10n';
2
2
  import { NcLoadingIcon } from '@nextcloud/vue';
3
3
  import PhoneOutline from 'vue-material-design-icons/PhoneOutline.vue';
4
4
  import __sfc_main from '../../../components/CnDetailCard/CnDetailCard.vue.js';
5
+ import { userLocale } from '../../../utils/userLocale.js';
5
6
  import { prefixUrl } from '../../../utils/headers.js';
6
7
 
7
8
  var script = {
@@ -124,7 +125,7 @@ var script = {
124
125
  */
125
126
  formatWhen(value) {
126
127
  const d = new Date(value);
127
- return isNaN(d.getTime()) ? value : d.toLocaleString()
128
+ return isNaN(d.getTime()) ? value : d.toLocaleString(userLocale())
128
129
  },
129
130
  },
130
131
  };
@@ -1 +1 @@
1
- {"version":3,"file":"CnContactmomentCard.vue2.js","sources":["../../../../../src/integrations/builtin/contactmoment/CnContactmomentCard.vue"],"sourcesContent":["<!--\n - SPDX-License-Identifier: EUPL-1.2\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n -\n - CnContactmomentCard — contact moments recorded against one object.\n -\n - A contactmoment is a logged interaction with a person: a phone call, a\n - counter visit, an email. pipelinq owns them; this surfaces the ones\n - attached to whatever object the surrounding page is showing, so a case\n - handler can see the conversation history without leaving the case.\n -\n - Reads pipelinq's aggregation endpoint rather than the objects API, because\n - that endpoint already applies the per-object owner access policy and\n - resolves the link field for the entity type.\n-->\n<template>\n\t<component :is=\"wrapper\" v-bind=\"wrapperProps\">\n\t\t<div class=\"cn-contactmoment-card\" data-testid=\"cn-contactmoment-card\">\n\t\t\t<div v-if=\"loading\" class=\"cn-contactmoment-card__state\">\n\t\t\t\t<NcLoadingIcon :size=\"20\" />\n\t\t\t\t<span>{{ loadingLabel }}</span>\n\t\t\t</div>\n\n\t\t\t<div v-else-if=\"error\" class=\"cn-contactmoment-card__state cn-contactmoment-card__state--error\">\n\t\t\t\t{{ errorLabel }}\n\t\t\t</div>\n\n\t\t\t<div v-else-if=\"!items.length\" class=\"cn-contactmoment-card__state\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\n\t\t\t<ul v-else class=\"cn-contactmoment-card__list\">\n\t\t\t\t<li\n\t\t\t\t\tv-for=\"item in items\"\n\t\t\t\t\t:key=\"item.id\"\n\t\t\t\t\tclass=\"cn-contactmoment-card__item\">\n\t\t\t\t\t<div class=\"cn-contactmoment-card__head\">\n\t\t\t\t\t\t<span class=\"cn-contactmoment-card__subject\">{{ item.subject || unknownLabel }}</span>\n\t\t\t\t\t\t<span v-if=\"item.channel\" class=\"cn-contactmoment-card__channel\">{{ item.channel }}</span>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div v-if=\"item.summary\" class=\"cn-contactmoment-card__summary\">\n\t\t\t\t\t\t{{ item.summary }}\n\t\t\t\t\t</div>\n\t\t\t\t\t<div class=\"cn-contactmoment-card__meta\">\n\t\t\t\t\t\t<span v-if=\"item.agent\">{{ item.agent }}</span>\n\t\t\t\t\t\t<span v-if=\"item.timestamp\">{{ formatWhen(item.timestamp) }}</span>\n\t\t\t\t\t</div>\n\t\t\t\t</li>\n\t\t\t</ul>\n\n\t\t\t<div v-if=\"total > items.length\" class=\"cn-contactmoment-card__more\">\n\t\t\t\t{{ moreLabel }}\n\t\t\t</div>\n\t\t</div>\n\t</component>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcLoadingIcon } from '@nextcloud/vue'\nimport PhoneOutline from 'vue-material-design-icons/PhoneOutline.vue'\nimport CnDetailCard from '../../../components/CnDetailCard/CnDetailCard.vue'\nimport { prefixUrl } from '../../../utils/index.js'\n\nexport default {\n\tname: 'CnContactmomentCard',\n\n\tcomponents: { NcLoadingIcon, CnDetailCard },\n\n\tprops: {\n\t\t/** The object whose contact moments are shown. */\n\t\tobjectId: { type: String, required: true },\n\t\t/**\n\t\t * The entity type pipelinq keys its link field on, such as\n\t\t * `dossiq:case`. Defaults to the surrounding schema when not given.\n\t\t */\n\t\tentityType: { type: String, default: '' },\n\t\t/** OpenRegister schema slug of the surrounding object. */\n\t\tschema: { type: String, default: '' },\n\t\t/* eslint-disable vue/no-unused-properties -- every integration surface is handed `surface` by its host; this card renders one layout and does not branch on it */\n\t\t/** Rendering surface, forwarded by the registry (AD-19). */\n\t\tsurface: { type: String, default: 'detail-page' },\n\t\t/* eslint-enable vue/no-unused-properties */\n\t\t/** How many interactions to show before the \"more\" line. */\n\t\tlimit: { type: Number, default: 5 },\n\t\t/**\n\t\t * Render without the surrounding CnDetailCard, for a surface that\n\t\t * already supplies the card and the title.\n\t\t */\n\t\tchromeless: { type: Boolean, default: false },\n\n\t\t// --- Pre-translated labels ---\n\t\t/** Card title. */\n\t\ttitleLabel: { type: String, default: () => t('nextcloud-vue', 'Contact moments') },\n\t\t/** Accessible text shown while the interactions load. */\n\t\tloadingLabel: { type: String, default: () => t('nextcloud-vue', 'Loading contact moments') },\n\t\t/** Empty state shown when the object has no interaction. */\n\t\temptyLabel: { type: String, default: () => t('nextcloud-vue', 'No contact moments recorded yet') },\n\t\t/** Message shown when the interactions could not be loaded. */\n\t\terrorLabel: { type: String, default: () => t('nextcloud-vue', 'Could not load contact moments') },\n\t\t/** Stand-in title for an interaction that has none. */\n\t\tunknownLabel: { type: String, default: () => t('nextcloud-vue', 'Untitled interaction') },\n\t},\n\n\tdata() {\n\t\treturn { items: [], total: 0, loading: false, error: false }\n\t},\n\n\tcomputed: {\n\t\t/** The card wrapper, or a plain div when rendering chromeless. */\n\t\twrapper() {\n\t\t\treturn this.chromeless ? 'div' : CnDetailCard\n\t\t},\n\n\t\t/** Card props, omitted entirely when there is no card to configure. */\n\t\twrapperProps() {\n\t\t\treturn this.chromeless ? {} : { title: this.titleLabel, icon: PhoneOutline }\n\t\t},\n\n\t\t/** The entity type to query, falling back to the surrounding schema. */\n\t\tresolvedEntityType() {\n\t\t\treturn this.entityType || this.schema || ''\n\t\t},\n\n\t\t/** Line shown when more interactions exist than are displayed. */\n\t\tmoreLabel() {\n\t\t\treturn t('nextcloud-vue', '{count} more', { count: this.total - this.items.length })\n\t\t},\n\t},\n\n\twatch: {\n\t\tobjectId: 'load',\n\t\tresolvedEntityType: 'load',\n\t},\n\n\tmounted() {\n\t\tthis.load()\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Load this object's contact moments from pipelinq.\n\t\t *\n\t\t * @return {Promise<void>} Resolves once state reflects the response.\n\t\t */\n\t\tasync load() {\n\t\t\tif (!this.objectId || !this.resolvedEntityType) {\n\t\t\t\tthis.items = []\n\t\t\t\tthis.total = 0\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\tthis.error = false\n\t\t\ttry {\n\t\t\t\tconst url = `/apps/pipelinq/api/activity/${encodeURIComponent(this.resolvedEntityType)}/${encodeURIComponent(this.objectId)}`\n\t\t\t\t\t+ `?type=contactmomenten&_limit=${encodeURIComponent(this.limit)}`\n\t\t\t\tconst res = await fetch(prefixUrl(url), { headers: { Accept: 'application/json' } })\n\t\t\t\tif (!res.ok) {\n\t\t\t\t\tthrow new Error(String(res.status))\n\t\t\t\t}\n\t\t\t\tconst body = await res.json()\n\t\t\t\tthis.items = Array.isArray(body.results) ? body.results : []\n\t\t\t\tthis.total = Number(body.total) || this.items.length\n\t\t\t} catch {\n\t\t\t\t// pipelinq absent, or the object is not visible to this user.\n\t\t\t\t// Either way the card says so rather than rendering an empty\n\t\t\t\t// list, which would read as \"no interactions\" when the truth is\n\t\t\t\t// \"we could not ask\".\n\t\t\t\tthis.error = true\n\t\t\t\tthis.items = []\n\t\t\t\tthis.total = 0\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 * Format an interaction timestamp for display.\n\t\t *\n\t\t * @param {string} value ISO-8601 timestamp.\n\t\t * @return {string} A locale date-time, or the raw value if unparseable.\n\t\t */\n\t\tformatWhen(value) {\n\t\t\tconst d = new Date(value)\n\t\t\treturn isNaN(d.getTime()) ? value : d.toLocaleString()\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-contactmoment-card__list {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 8px;\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n.cn-contactmoment-card__item {\n\tborder-left: 3px solid var(--color-primary-element);\n\tbackground: var(--color-background-hover);\n\tborder-radius: 0 var(--border-radius) var(--border-radius) 0;\n\tpadding: 8px 12px;\n}\n\n.cn-contactmoment-card__head {\n\tdisplay: flex;\n\tjustify-content: space-between;\n\tgap: 8px;\n}\n\n.cn-contactmoment-card__subject {\n\tfont-weight: 500;\n}\n\n.cn-contactmoment-card__channel,\n.cn-contactmoment-card__meta {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.85em;\n}\n\n.cn-contactmoment-card__meta {\n\tdisplay: flex;\n\tgap: 12px;\n\tmargin-top: 4px;\n}\n\n.cn-contactmoment-card__summary {\n\tmargin-top: 2px;\n\toverflow-wrap: anywhere;\n}\n\n.cn-contactmoment-card__state {\n\talign-items: center;\n\tcolor: var(--color-text-maxcontrast);\n\tdisplay: flex;\n\tgap: 8px;\n\tpadding: 8px 0;\n}\n\n.cn-contactmoment-card__state--error {\n\tcolor: var(--color-error);\n}\n\n.cn-contactmoment-card__more {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.85em;\n\tmargin-top: 8px;\n}\n</style>\n"],"names":["CnDetailCard","t"],"mappings":"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"}
1
+ {"version":3,"file":"CnContactmomentCard.vue2.js","sources":["../../../../../src/integrations/builtin/contactmoment/CnContactmomentCard.vue"],"sourcesContent":["<!--\n - SPDX-License-Identifier: EUPL-1.2\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n -\n - CnContactmomentCard — contact moments recorded against one object.\n -\n - A contactmoment is a logged interaction with a person: a phone call, a\n - counter visit, an email. pipelinq owns them; this surfaces the ones\n - attached to whatever object the surrounding page is showing, so a case\n - handler can see the conversation history without leaving the case.\n -\n - Reads pipelinq's aggregation endpoint rather than the objects API, because\n - that endpoint already applies the per-object owner access policy and\n - resolves the link field for the entity type.\n-->\n<template>\n\t<component :is=\"wrapper\" v-bind=\"wrapperProps\">\n\t\t<div class=\"cn-contactmoment-card\" data-testid=\"cn-contactmoment-card\">\n\t\t\t<div v-if=\"loading\" class=\"cn-contactmoment-card__state\">\n\t\t\t\t<NcLoadingIcon :size=\"20\" />\n\t\t\t\t<span>{{ loadingLabel }}</span>\n\t\t\t</div>\n\n\t\t\t<div v-else-if=\"error\" class=\"cn-contactmoment-card__state cn-contactmoment-card__state--error\">\n\t\t\t\t{{ errorLabel }}\n\t\t\t</div>\n\n\t\t\t<div v-else-if=\"!items.length\" class=\"cn-contactmoment-card__state\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\n\t\t\t<ul v-else class=\"cn-contactmoment-card__list\">\n\t\t\t\t<li\n\t\t\t\t\tv-for=\"item in items\"\n\t\t\t\t\t:key=\"item.id\"\n\t\t\t\t\tclass=\"cn-contactmoment-card__item\">\n\t\t\t\t\t<div class=\"cn-contactmoment-card__head\">\n\t\t\t\t\t\t<span class=\"cn-contactmoment-card__subject\">{{ item.subject || unknownLabel }}</span>\n\t\t\t\t\t\t<span v-if=\"item.channel\" class=\"cn-contactmoment-card__channel\">{{ item.channel }}</span>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div v-if=\"item.summary\" class=\"cn-contactmoment-card__summary\">\n\t\t\t\t\t\t{{ item.summary }}\n\t\t\t\t\t</div>\n\t\t\t\t\t<div class=\"cn-contactmoment-card__meta\">\n\t\t\t\t\t\t<span v-if=\"item.agent\">{{ item.agent }}</span>\n\t\t\t\t\t\t<span v-if=\"item.timestamp\">{{ formatWhen(item.timestamp) }}</span>\n\t\t\t\t\t</div>\n\t\t\t\t</li>\n\t\t\t</ul>\n\n\t\t\t<div v-if=\"total > items.length\" class=\"cn-contactmoment-card__more\">\n\t\t\t\t{{ moreLabel }}\n\t\t\t</div>\n\t\t</div>\n\t</component>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcLoadingIcon } from '@nextcloud/vue'\nimport PhoneOutline from 'vue-material-design-icons/PhoneOutline.vue'\nimport CnDetailCard from '../../../components/CnDetailCard/CnDetailCard.vue'\nimport { prefixUrl } from '../../../utils/index.js'\nimport { userLocale } from '../../../utils/userLocale.js'\n\nexport default {\n\tname: 'CnContactmomentCard',\n\n\tcomponents: { NcLoadingIcon, CnDetailCard },\n\n\tprops: {\n\t\t/** The object whose contact moments are shown. */\n\t\tobjectId: { type: String, required: true },\n\t\t/**\n\t\t * The entity type pipelinq keys its link field on, such as\n\t\t * `dossiq:case`. Defaults to the surrounding schema when not given.\n\t\t */\n\t\tentityType: { type: String, default: '' },\n\t\t/** OpenRegister schema slug of the surrounding object. */\n\t\tschema: { type: String, default: '' },\n\t\t/* eslint-disable vue/no-unused-properties -- every integration surface is handed `surface` by its host; this card renders one layout and does not branch on it */\n\t\t/** Rendering surface, forwarded by the registry (AD-19). */\n\t\tsurface: { type: String, default: 'detail-page' },\n\t\t/* eslint-enable vue/no-unused-properties */\n\t\t/** How many interactions to show before the \"more\" line. */\n\t\tlimit: { type: Number, default: 5 },\n\t\t/**\n\t\t * Render without the surrounding CnDetailCard, for a surface that\n\t\t * already supplies the card and the title.\n\t\t */\n\t\tchromeless: { type: Boolean, default: false },\n\n\t\t// --- Pre-translated labels ---\n\t\t/** Card title. */\n\t\ttitleLabel: { type: String, default: () => t('nextcloud-vue', 'Contact moments') },\n\t\t/** Accessible text shown while the interactions load. */\n\t\tloadingLabel: { type: String, default: () => t('nextcloud-vue', 'Loading contact moments') },\n\t\t/** Empty state shown when the object has no interaction. */\n\t\temptyLabel: { type: String, default: () => t('nextcloud-vue', 'No contact moments recorded yet') },\n\t\t/** Message shown when the interactions could not be loaded. */\n\t\terrorLabel: { type: String, default: () => t('nextcloud-vue', 'Could not load contact moments') },\n\t\t/** Stand-in title for an interaction that has none. */\n\t\tunknownLabel: { type: String, default: () => t('nextcloud-vue', 'Untitled interaction') },\n\t},\n\n\tdata() {\n\t\treturn { items: [], total: 0, loading: false, error: false }\n\t},\n\n\tcomputed: {\n\t\t/** The card wrapper, or a plain div when rendering chromeless. */\n\t\twrapper() {\n\t\t\treturn this.chromeless ? 'div' : CnDetailCard\n\t\t},\n\n\t\t/** Card props, omitted entirely when there is no card to configure. */\n\t\twrapperProps() {\n\t\t\treturn this.chromeless ? {} : { title: this.titleLabel, icon: PhoneOutline }\n\t\t},\n\n\t\t/** The entity type to query, falling back to the surrounding schema. */\n\t\tresolvedEntityType() {\n\t\t\treturn this.entityType || this.schema || ''\n\t\t},\n\n\t\t/** Line shown when more interactions exist than are displayed. */\n\t\tmoreLabel() {\n\t\t\treturn t('nextcloud-vue', '{count} more', { count: this.total - this.items.length })\n\t\t},\n\t},\n\n\twatch: {\n\t\tobjectId: 'load',\n\t\tresolvedEntityType: 'load',\n\t},\n\n\tmounted() {\n\t\tthis.load()\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Load this object's contact moments from pipelinq.\n\t\t *\n\t\t * @return {Promise<void>} Resolves once state reflects the response.\n\t\t */\n\t\tasync load() {\n\t\t\tif (!this.objectId || !this.resolvedEntityType) {\n\t\t\t\tthis.items = []\n\t\t\t\tthis.total = 0\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\tthis.error = false\n\t\t\ttry {\n\t\t\t\tconst url = `/apps/pipelinq/api/activity/${encodeURIComponent(this.resolvedEntityType)}/${encodeURIComponent(this.objectId)}`\n\t\t\t\t\t+ `?type=contactmomenten&_limit=${encodeURIComponent(this.limit)}`\n\t\t\t\tconst res = await fetch(prefixUrl(url), { headers: { Accept: 'application/json' } })\n\t\t\t\tif (!res.ok) {\n\t\t\t\t\tthrow new Error(String(res.status))\n\t\t\t\t}\n\t\t\t\tconst body = await res.json()\n\t\t\t\tthis.items = Array.isArray(body.results) ? body.results : []\n\t\t\t\tthis.total = Number(body.total) || this.items.length\n\t\t\t} catch {\n\t\t\t\t// pipelinq absent, or the object is not visible to this user.\n\t\t\t\t// Either way the card says so rather than rendering an empty\n\t\t\t\t// list, which would read as \"no interactions\" when the truth is\n\t\t\t\t// \"we could not ask\".\n\t\t\t\tthis.error = true\n\t\t\t\tthis.items = []\n\t\t\t\tthis.total = 0\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 * Format an interaction timestamp for display.\n\t\t *\n\t\t * @param {string} value ISO-8601 timestamp.\n\t\t * @return {string} A locale date-time, or the raw value if unparseable.\n\t\t */\n\t\tformatWhen(value) {\n\t\t\tconst d = new Date(value)\n\t\t\treturn isNaN(d.getTime()) ? value : d.toLocaleString(userLocale())\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-contactmoment-card__list {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 8px;\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n.cn-contactmoment-card__item {\n\tborder-left: 3px solid var(--color-primary-element);\n\tbackground: var(--color-background-hover);\n\tborder-radius: 0 var(--border-radius) var(--border-radius) 0;\n\tpadding: 8px 12px;\n}\n\n.cn-contactmoment-card__head {\n\tdisplay: flex;\n\tjustify-content: space-between;\n\tgap: 8px;\n}\n\n.cn-contactmoment-card__subject {\n\tfont-weight: 500;\n}\n\n.cn-contactmoment-card__channel,\n.cn-contactmoment-card__meta {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.85em;\n}\n\n.cn-contactmoment-card__meta {\n\tdisplay: flex;\n\tgap: 12px;\n\tmargin-top: 4px;\n}\n\n.cn-contactmoment-card__summary {\n\tmargin-top: 2px;\n\toverflow-wrap: anywhere;\n}\n\n.cn-contactmoment-card__state {\n\talign-items: center;\n\tcolor: var(--color-text-maxcontrast);\n\tdisplay: flex;\n\tgap: 8px;\n\tpadding: 8px 0;\n}\n\n.cn-contactmoment-card__state--error {\n\tcolor: var(--color-error);\n}\n\n.cn-contactmoment-card__more {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.85em;\n\tmargin-top: 8px;\n}\n</style>\n"],"names":["CnDetailCard","t"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"CnContactmomentCard.vue3.js","sources":["../../../../../src/integrations/builtin/contactmoment/CnContactmomentCard.vue?vue&type=template&id=5c6657ff&lang.js"],"sourcesContent":["<!--\n - SPDX-License-Identifier: EUPL-1.2\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n -\n - CnContactmomentCard — contact moments recorded against one object.\n -\n - A contactmoment is a logged interaction with a person: a phone call, a\n - counter visit, an email. pipelinq owns them; this surfaces the ones\n - attached to whatever object the surrounding page is showing, so a case\n - handler can see the conversation history without leaving the case.\n -\n - Reads pipelinq's aggregation endpoint rather than the objects API, because\n - that endpoint already applies the per-object owner access policy and\n - resolves the link field for the entity type.\n-->\n<template>\n\t<component :is=\"wrapper\" v-bind=\"wrapperProps\">\n\t\t<div class=\"cn-contactmoment-card\" data-testid=\"cn-contactmoment-card\">\n\t\t\t<div v-if=\"loading\" class=\"cn-contactmoment-card__state\">\n\t\t\t\t<NcLoadingIcon :size=\"20\" />\n\t\t\t\t<span>{{ loadingLabel }}</span>\n\t\t\t</div>\n\n\t\t\t<div v-else-if=\"error\" class=\"cn-contactmoment-card__state cn-contactmoment-card__state--error\">\n\t\t\t\t{{ errorLabel }}\n\t\t\t</div>\n\n\t\t\t<div v-else-if=\"!items.length\" class=\"cn-contactmoment-card__state\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\n\t\t\t<ul v-else class=\"cn-contactmoment-card__list\">\n\t\t\t\t<li\n\t\t\t\t\tv-for=\"item in items\"\n\t\t\t\t\t:key=\"item.id\"\n\t\t\t\t\tclass=\"cn-contactmoment-card__item\">\n\t\t\t\t\t<div class=\"cn-contactmoment-card__head\">\n\t\t\t\t\t\t<span class=\"cn-contactmoment-card__subject\">{{ item.subject || unknownLabel }}</span>\n\t\t\t\t\t\t<span v-if=\"item.channel\" class=\"cn-contactmoment-card__channel\">{{ item.channel }}</span>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div v-if=\"item.summary\" class=\"cn-contactmoment-card__summary\">\n\t\t\t\t\t\t{{ item.summary }}\n\t\t\t\t\t</div>\n\t\t\t\t\t<div class=\"cn-contactmoment-card__meta\">\n\t\t\t\t\t\t<span v-if=\"item.agent\">{{ item.agent }}</span>\n\t\t\t\t\t\t<span v-if=\"item.timestamp\">{{ formatWhen(item.timestamp) }}</span>\n\t\t\t\t\t</div>\n\t\t\t\t</li>\n\t\t\t</ul>\n\n\t\t\t<div v-if=\"total > items.length\" class=\"cn-contactmoment-card__more\">\n\t\t\t\t{{ moreLabel }}\n\t\t\t</div>\n\t\t</div>\n\t</component>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcLoadingIcon } from '@nextcloud/vue'\nimport PhoneOutline from 'vue-material-design-icons/PhoneOutline.vue'\nimport CnDetailCard from '../../../components/CnDetailCard/CnDetailCard.vue'\nimport { prefixUrl } from '../../../utils/index.js'\n\nexport default {\n\tname: 'CnContactmomentCard',\n\n\tcomponents: { NcLoadingIcon, CnDetailCard },\n\n\tprops: {\n\t\t/** The object whose contact moments are shown. */\n\t\tobjectId: { type: String, required: true },\n\t\t/**\n\t\t * The entity type pipelinq keys its link field on, such as\n\t\t * `dossiq:case`. Defaults to the surrounding schema when not given.\n\t\t */\n\t\tentityType: { type: String, default: '' },\n\t\t/** OpenRegister schema slug of the surrounding object. */\n\t\tschema: { type: String, default: '' },\n\t\t/* eslint-disable vue/no-unused-properties -- every integration surface is handed `surface` by its host; this card renders one layout and does not branch on it */\n\t\t/** Rendering surface, forwarded by the registry (AD-19). */\n\t\tsurface: { type: String, default: 'detail-page' },\n\t\t/* eslint-enable vue/no-unused-properties */\n\t\t/** How many interactions to show before the \"more\" line. */\n\t\tlimit: { type: Number, default: 5 },\n\t\t/**\n\t\t * Render without the surrounding CnDetailCard, for a surface that\n\t\t * already supplies the card and the title.\n\t\t */\n\t\tchromeless: { type: Boolean, default: false },\n\n\t\t// --- Pre-translated labels ---\n\t\t/** Card title. */\n\t\ttitleLabel: { type: String, default: () => t('nextcloud-vue', 'Contact moments') },\n\t\t/** Accessible text shown while the interactions load. */\n\t\tloadingLabel: { type: String, default: () => t('nextcloud-vue', 'Loading contact moments') },\n\t\t/** Empty state shown when the object has no interaction. */\n\t\temptyLabel: { type: String, default: () => t('nextcloud-vue', 'No contact moments recorded yet') },\n\t\t/** Message shown when the interactions could not be loaded. */\n\t\terrorLabel: { type: String, default: () => t('nextcloud-vue', 'Could not load contact moments') },\n\t\t/** Stand-in title for an interaction that has none. */\n\t\tunknownLabel: { type: String, default: () => t('nextcloud-vue', 'Untitled interaction') },\n\t},\n\n\tdata() {\n\t\treturn { items: [], total: 0, loading: false, error: false }\n\t},\n\n\tcomputed: {\n\t\t/** The card wrapper, or a plain div when rendering chromeless. */\n\t\twrapper() {\n\t\t\treturn this.chromeless ? 'div' : CnDetailCard\n\t\t},\n\n\t\t/** Card props, omitted entirely when there is no card to configure. */\n\t\twrapperProps() {\n\t\t\treturn this.chromeless ? {} : { title: this.titleLabel, icon: PhoneOutline }\n\t\t},\n\n\t\t/** The entity type to query, falling back to the surrounding schema. */\n\t\tresolvedEntityType() {\n\t\t\treturn this.entityType || this.schema || ''\n\t\t},\n\n\t\t/** Line shown when more interactions exist than are displayed. */\n\t\tmoreLabel() {\n\t\t\treturn t('nextcloud-vue', '{count} more', { count: this.total - this.items.length })\n\t\t},\n\t},\n\n\twatch: {\n\t\tobjectId: 'load',\n\t\tresolvedEntityType: 'load',\n\t},\n\n\tmounted() {\n\t\tthis.load()\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Load this object's contact moments from pipelinq.\n\t\t *\n\t\t * @return {Promise<void>} Resolves once state reflects the response.\n\t\t */\n\t\tasync load() {\n\t\t\tif (!this.objectId || !this.resolvedEntityType) {\n\t\t\t\tthis.items = []\n\t\t\t\tthis.total = 0\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\tthis.error = false\n\t\t\ttry {\n\t\t\t\tconst url = `/apps/pipelinq/api/activity/${encodeURIComponent(this.resolvedEntityType)}/${encodeURIComponent(this.objectId)}`\n\t\t\t\t\t+ `?type=contactmomenten&_limit=${encodeURIComponent(this.limit)}`\n\t\t\t\tconst res = await fetch(prefixUrl(url), { headers: { Accept: 'application/json' } })\n\t\t\t\tif (!res.ok) {\n\t\t\t\t\tthrow new Error(String(res.status))\n\t\t\t\t}\n\t\t\t\tconst body = await res.json()\n\t\t\t\tthis.items = Array.isArray(body.results) ? body.results : []\n\t\t\t\tthis.total = Number(body.total) || this.items.length\n\t\t\t} catch {\n\t\t\t\t// pipelinq absent, or the object is not visible to this user.\n\t\t\t\t// Either way the card says so rather than rendering an empty\n\t\t\t\t// list, which would read as \"no interactions\" when the truth is\n\t\t\t\t// \"we could not ask\".\n\t\t\t\tthis.error = true\n\t\t\t\tthis.items = []\n\t\t\t\tthis.total = 0\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 * Format an interaction timestamp for display.\n\t\t *\n\t\t * @param {string} value ISO-8601 timestamp.\n\t\t * @return {string} A locale date-time, or the raw value if unparseable.\n\t\t */\n\t\tformatWhen(value) {\n\t\t\tconst d = new Date(value)\n\t\t\treturn isNaN(d.getTime()) ? value : d.toLocaleString()\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-contactmoment-card__list {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 8px;\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n.cn-contactmoment-card__item {\n\tborder-left: 3px solid var(--color-primary-element);\n\tbackground: var(--color-background-hover);\n\tborder-radius: 0 var(--border-radius) var(--border-radius) 0;\n\tpadding: 8px 12px;\n}\n\n.cn-contactmoment-card__head {\n\tdisplay: flex;\n\tjustify-content: space-between;\n\tgap: 8px;\n}\n\n.cn-contactmoment-card__subject {\n\tfont-weight: 500;\n}\n\n.cn-contactmoment-card__channel,\n.cn-contactmoment-card__meta {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.85em;\n}\n\n.cn-contactmoment-card__meta {\n\tdisplay: flex;\n\tgap: 12px;\n\tmargin-top: 4px;\n}\n\n.cn-contactmoment-card__summary {\n\tmargin-top: 2px;\n\toverflow-wrap: anywhere;\n}\n\n.cn-contactmoment-card__state {\n\talign-items: center;\n\tcolor: var(--color-text-maxcontrast);\n\tdisplay: flex;\n\tgap: 8px;\n\tpadding: 8px 0;\n}\n\n.cn-contactmoment-card__state--error {\n\tcolor: var(--color-error);\n}\n\n.cn-contactmoment-card__more {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.85em;\n\tmargin-top: 8px;\n}\n</style>\n"],"names":["_createBlock","_normalizeProps","_guardReactiveProps","_createElementVNode","_openBlock","_createElementBlock","_toDisplayString"],"mappings":";;;AAgCM,EAAA,KAAA,EAAA,uBAAA;AAAA,EAAA,aAAA,EAAA,uBAAA;;;;AACiB,EAAA,KAAE,EAAA,8BAAA;;;;AAKC,EAAA,KAAM,EAAA,kEAA+D;;;;AAIrF,EAAA,KAAA,EAAA,8BAAA;;;;AAIA,EAAA,KAAA,EAAA,6BAAA;;AAKA,MAAA,UAAA,GAAA,EAAA,KAAK,EAAA,6BAAG,GAAA;AACV,MAAA,UAAA,GAAA,EAAA,KAAA,EAAA,gCAAA,GAAA;;;AACD,EAAA,KAAA,EAAA,gCAAA;;;;;;;;;;;;;;;;;uBAtBJA,oCAAc,QAAO,CAAA,OAAA,CAAA,EAAAC,cAAA,CAAAC,kBAAA,CAAU,QAAY,CAAA,YAAA,CAAA,CAAA,EAAA;qBAC1C;AAAA,MAAAC,kBAAA,QAAA;SACU,KAAO,CAAA,OAAA;AAAhB,aAAAC,SAAA,EAAA,EAAAC,kBAAA,CAGG,OAHH,UAGG,EAAA;yBAFU,CAAA,wBAAA,EAAA,EAAA,IAAA,EAAA,EAAA,EAAA,CAAA;AACb,cAAAF,kBAAA,CAA8B,8BAAtB,MAAY,CAAA,YAAA,CAAA,EAAA,CAAA,YAAA;;aAGN,KAAK,CAAA,KAAA;4BAAlBE,kBAEE,CAAA,KAAA,EAFF,UAEE,EAAAC,eAAA,CADD,MAAG,CAAA,UAAA,CAAA,EAAA,CAAA,YAAA;AAGA,cAAA,CAAA,CAAA,KAAA,CAAA,KAAA,CAAA,MAAA;gDAED,CAAA,KAAA,YAAA,EAAAA,eAAA,CADF,MAAU,CAAA,UAAA,CAAA,EAAA,CAAA,YAAA;;oCAIbD,8CACG,KAAA,CAAA,KAAA,EAAA,CAAA,IAAA,KAAA;yCADHA;;AAGC,sBAAA,KAAK,EAAC,6BAA6B;;AACnC,sBAAAF,kBAAA,CAGM,OAHN,UAGM,EAAA;AAFL,wBAAAA,kBAAA,CAAE,QAAF,UAAE,EAAAG,eAAA,CAAA,IAAA,CAAA,OAAA,IAAA,MAAA,CAAA,YAAA,CAAA,EAAA,CAAA,YAAA;AACD,wBAAA,CAAA,IAAA,CAAA,OAAA;AAAD,6BAAAF,SAAA,EAAA,EAAAC,kBAAA,CAAC,MAAD,EAAA,UAAC,EAAAC,eAAA,CAAA,IAAA,CAAA,OAAA,CAAA,EAAA,CAAA,YAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"CnContactmomentCard.vue3.js","sources":["../../../../../src/integrations/builtin/contactmoment/CnContactmomentCard.vue?vue&type=template&id=d9c8e564&lang.js"],"sourcesContent":["<!--\n - SPDX-License-Identifier: EUPL-1.2\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n -\n - CnContactmomentCard — contact moments recorded against one object.\n -\n - A contactmoment is a logged interaction with a person: a phone call, a\n - counter visit, an email. pipelinq owns them; this surfaces the ones\n - attached to whatever object the surrounding page is showing, so a case\n - handler can see the conversation history without leaving the case.\n -\n - Reads pipelinq's aggregation endpoint rather than the objects API, because\n - that endpoint already applies the per-object owner access policy and\n - resolves the link field for the entity type.\n-->\n<template>\n\t<component :is=\"wrapper\" v-bind=\"wrapperProps\">\n\t\t<div class=\"cn-contactmoment-card\" data-testid=\"cn-contactmoment-card\">\n\t\t\t<div v-if=\"loading\" class=\"cn-contactmoment-card__state\">\n\t\t\t\t<NcLoadingIcon :size=\"20\" />\n\t\t\t\t<span>{{ loadingLabel }}</span>\n\t\t\t</div>\n\n\t\t\t<div v-else-if=\"error\" class=\"cn-contactmoment-card__state cn-contactmoment-card__state--error\">\n\t\t\t\t{{ errorLabel }}\n\t\t\t</div>\n\n\t\t\t<div v-else-if=\"!items.length\" class=\"cn-contactmoment-card__state\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\n\t\t\t<ul v-else class=\"cn-contactmoment-card__list\">\n\t\t\t\t<li\n\t\t\t\t\tv-for=\"item in items\"\n\t\t\t\t\t:key=\"item.id\"\n\t\t\t\t\tclass=\"cn-contactmoment-card__item\">\n\t\t\t\t\t<div class=\"cn-contactmoment-card__head\">\n\t\t\t\t\t\t<span class=\"cn-contactmoment-card__subject\">{{ item.subject || unknownLabel }}</span>\n\t\t\t\t\t\t<span v-if=\"item.channel\" class=\"cn-contactmoment-card__channel\">{{ item.channel }}</span>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div v-if=\"item.summary\" class=\"cn-contactmoment-card__summary\">\n\t\t\t\t\t\t{{ item.summary }}\n\t\t\t\t\t</div>\n\t\t\t\t\t<div class=\"cn-contactmoment-card__meta\">\n\t\t\t\t\t\t<span v-if=\"item.agent\">{{ item.agent }}</span>\n\t\t\t\t\t\t<span v-if=\"item.timestamp\">{{ formatWhen(item.timestamp) }}</span>\n\t\t\t\t\t</div>\n\t\t\t\t</li>\n\t\t\t</ul>\n\n\t\t\t<div v-if=\"total > items.length\" class=\"cn-contactmoment-card__more\">\n\t\t\t\t{{ moreLabel }}\n\t\t\t</div>\n\t\t</div>\n\t</component>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcLoadingIcon } from '@nextcloud/vue'\nimport PhoneOutline from 'vue-material-design-icons/PhoneOutline.vue'\nimport CnDetailCard from '../../../components/CnDetailCard/CnDetailCard.vue'\nimport { prefixUrl } from '../../../utils/index.js'\nimport { userLocale } from '../../../utils/userLocale.js'\n\nexport default {\n\tname: 'CnContactmomentCard',\n\n\tcomponents: { NcLoadingIcon, CnDetailCard },\n\n\tprops: {\n\t\t/** The object whose contact moments are shown. */\n\t\tobjectId: { type: String, required: true },\n\t\t/**\n\t\t * The entity type pipelinq keys its link field on, such as\n\t\t * `dossiq:case`. Defaults to the surrounding schema when not given.\n\t\t */\n\t\tentityType: { type: String, default: '' },\n\t\t/** OpenRegister schema slug of the surrounding object. */\n\t\tschema: { type: String, default: '' },\n\t\t/* eslint-disable vue/no-unused-properties -- every integration surface is handed `surface` by its host; this card renders one layout and does not branch on it */\n\t\t/** Rendering surface, forwarded by the registry (AD-19). */\n\t\tsurface: { type: String, default: 'detail-page' },\n\t\t/* eslint-enable vue/no-unused-properties */\n\t\t/** How many interactions to show before the \"more\" line. */\n\t\tlimit: { type: Number, default: 5 },\n\t\t/**\n\t\t * Render without the surrounding CnDetailCard, for a surface that\n\t\t * already supplies the card and the title.\n\t\t */\n\t\tchromeless: { type: Boolean, default: false },\n\n\t\t// --- Pre-translated labels ---\n\t\t/** Card title. */\n\t\ttitleLabel: { type: String, default: () => t('nextcloud-vue', 'Contact moments') },\n\t\t/** Accessible text shown while the interactions load. */\n\t\tloadingLabel: { type: String, default: () => t('nextcloud-vue', 'Loading contact moments') },\n\t\t/** Empty state shown when the object has no interaction. */\n\t\temptyLabel: { type: String, default: () => t('nextcloud-vue', 'No contact moments recorded yet') },\n\t\t/** Message shown when the interactions could not be loaded. */\n\t\terrorLabel: { type: String, default: () => t('nextcloud-vue', 'Could not load contact moments') },\n\t\t/** Stand-in title for an interaction that has none. */\n\t\tunknownLabel: { type: String, default: () => t('nextcloud-vue', 'Untitled interaction') },\n\t},\n\n\tdata() {\n\t\treturn { items: [], total: 0, loading: false, error: false }\n\t},\n\n\tcomputed: {\n\t\t/** The card wrapper, or a plain div when rendering chromeless. */\n\t\twrapper() {\n\t\t\treturn this.chromeless ? 'div' : CnDetailCard\n\t\t},\n\n\t\t/** Card props, omitted entirely when there is no card to configure. */\n\t\twrapperProps() {\n\t\t\treturn this.chromeless ? {} : { title: this.titleLabel, icon: PhoneOutline }\n\t\t},\n\n\t\t/** The entity type to query, falling back to the surrounding schema. */\n\t\tresolvedEntityType() {\n\t\t\treturn this.entityType || this.schema || ''\n\t\t},\n\n\t\t/** Line shown when more interactions exist than are displayed. */\n\t\tmoreLabel() {\n\t\t\treturn t('nextcloud-vue', '{count} more', { count: this.total - this.items.length })\n\t\t},\n\t},\n\n\twatch: {\n\t\tobjectId: 'load',\n\t\tresolvedEntityType: 'load',\n\t},\n\n\tmounted() {\n\t\tthis.load()\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Load this object's contact moments from pipelinq.\n\t\t *\n\t\t * @return {Promise<void>} Resolves once state reflects the response.\n\t\t */\n\t\tasync load() {\n\t\t\tif (!this.objectId || !this.resolvedEntityType) {\n\t\t\t\tthis.items = []\n\t\t\t\tthis.total = 0\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\tthis.error = false\n\t\t\ttry {\n\t\t\t\tconst url = `/apps/pipelinq/api/activity/${encodeURIComponent(this.resolvedEntityType)}/${encodeURIComponent(this.objectId)}`\n\t\t\t\t\t+ `?type=contactmomenten&_limit=${encodeURIComponent(this.limit)}`\n\t\t\t\tconst res = await fetch(prefixUrl(url), { headers: { Accept: 'application/json' } })\n\t\t\t\tif (!res.ok) {\n\t\t\t\t\tthrow new Error(String(res.status))\n\t\t\t\t}\n\t\t\t\tconst body = await res.json()\n\t\t\t\tthis.items = Array.isArray(body.results) ? body.results : []\n\t\t\t\tthis.total = Number(body.total) || this.items.length\n\t\t\t} catch {\n\t\t\t\t// pipelinq absent, or the object is not visible to this user.\n\t\t\t\t// Either way the card says so rather than rendering an empty\n\t\t\t\t// list, which would read as \"no interactions\" when the truth is\n\t\t\t\t// \"we could not ask\".\n\t\t\t\tthis.error = true\n\t\t\t\tthis.items = []\n\t\t\t\tthis.total = 0\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 * Format an interaction timestamp for display.\n\t\t *\n\t\t * @param {string} value ISO-8601 timestamp.\n\t\t * @return {string} A locale date-time, or the raw value if unparseable.\n\t\t */\n\t\tformatWhen(value) {\n\t\t\tconst d = new Date(value)\n\t\t\treturn isNaN(d.getTime()) ? value : d.toLocaleString(userLocale())\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-contactmoment-card__list {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 8px;\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n.cn-contactmoment-card__item {\n\tborder-left: 3px solid var(--color-primary-element);\n\tbackground: var(--color-background-hover);\n\tborder-radius: 0 var(--border-radius) var(--border-radius) 0;\n\tpadding: 8px 12px;\n}\n\n.cn-contactmoment-card__head {\n\tdisplay: flex;\n\tjustify-content: space-between;\n\tgap: 8px;\n}\n\n.cn-contactmoment-card__subject {\n\tfont-weight: 500;\n}\n\n.cn-contactmoment-card__channel,\n.cn-contactmoment-card__meta {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.85em;\n}\n\n.cn-contactmoment-card__meta {\n\tdisplay: flex;\n\tgap: 12px;\n\tmargin-top: 4px;\n}\n\n.cn-contactmoment-card__summary {\n\tmargin-top: 2px;\n\toverflow-wrap: anywhere;\n}\n\n.cn-contactmoment-card__state {\n\talign-items: center;\n\tcolor: var(--color-text-maxcontrast);\n\tdisplay: flex;\n\tgap: 8px;\n\tpadding: 8px 0;\n}\n\n.cn-contactmoment-card__state--error {\n\tcolor: var(--color-error);\n}\n\n.cn-contactmoment-card__more {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.85em;\n\tmargin-top: 8px;\n}\n</style>\n"],"names":["_createBlock","_normalizeProps","_guardReactiveProps","_createElementVNode","_openBlock","_createElementBlock","_toDisplayString"],"mappings":";;;AAgCM,EAAA,KAAA,EAAA,uBAAA;AAAA,EAAA,aAAA,EAAA,uBAAA;;;;AACiB,EAAA,KAAE,EAAA,8BAAA;;;;AAKC,EAAA,KAAM,EAAA,kEAA+D;;;;AAIrF,EAAA,KAAA,EAAA,8BAAA;;;;AAIA,EAAA,KAAA,EAAA,6BAAA;;AAKA,MAAA,UAAA,GAAA,EAAA,KAAK,EAAA,6BAAG,GAAA;AACV,MAAA,UAAA,GAAA,EAAA,KAAA,EAAA,gCAAA,GAAA;;;AACD,EAAA,KAAA,EAAA,gCAAA;;;;;;;;;;;;;;;;;uBAtBJA,oCAAc,QAAO,CAAA,OAAA,CAAA,EAAAC,cAAA,CAAAC,kBAAA,CAAU,QAAY,CAAA,YAAA,CAAA,CAAA,EAAA;qBAC1C;AAAA,MAAAC,kBAAA,QAAA;SACU,KAAO,CAAA,OAAA;AAAhB,aAAAC,SAAA,EAAA,EAAAC,kBAAA,CAGG,OAHH,UAGG,EAAA;yBAFU,CAAA,wBAAA,EAAA,EAAA,IAAA,EAAA,EAAA,EAAA,CAAA;AACb,cAAAF,kBAAA,CAA8B,8BAAtB,MAAY,CAAA,YAAA,CAAA,EAAA,CAAA,YAAA;;aAGN,KAAK,CAAA,KAAA;4BAAlBE,kBAEE,CAAA,KAAA,EAFF,UAEE,EAAAC,eAAA,CADD,MAAG,CAAA,UAAA,CAAA,EAAA,CAAA,YAAA;AAGA,cAAA,CAAA,CAAA,KAAA,CAAA,KAAA,CAAA,MAAA;gDAED,CAAA,KAAA,YAAA,EAAAA,eAAA,CADF,MAAU,CAAA,UAAA,CAAA,EAAA,CAAA,YAAA;;oCAIbD,8CACG,KAAA,CAAA,KAAA,EAAA,CAAA,IAAA,KAAA;yCADHA;;AAGC,sBAAA,KAAK,EAAC,6BAA6B;;AACnC,sBAAAF,kBAAA,CAGM,OAHN,UAGM,EAAA;AAFL,wBAAAA,kBAAA,CAAE,QAAF,UAAE,EAAAG,eAAA,CAAA,IAAA,CAAA,OAAA,IAAA,MAAA,CAAA,YAAA,CAAA,EAAA,CAAA,YAAA;AACD,wBAAA,CAAA,IAAA,CAAA,OAAA;AAAD,6BAAAF,SAAA,EAAA,EAAAC,kBAAA,CAAC,MAAD,EAAA,UAAC,EAAAC,eAAA,CAAA,IAAA,CAAA,OAAA,CAAA,EAAA,CAAA,YAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -2,7 +2,7 @@ import script from './CnCospendTab.vue2.js';
2
2
  import { render } from './CnCospendTab.vue3.js';
3
3
 
4
4
  script.render = render;
5
- script.__scopeId = "data-v-06d4adfe";
5
+ script.__scopeId = "data-v-1d48ce21";
6
6
  const __sfc_main = script;
7
7
 
8
8
  export { __sfc_main as default };
@@ -10,6 +10,7 @@ import Plus from 'vue-material-design-icons/Plus.vue';
10
10
  import __sfc_main$2 from '../../../components/CnCospendCreate/CnCospendCreate.vue.js';
11
11
  import __sfc_main$1 from '../../../components/CnCospendPicker/CnCospendPicker.vue.js';
12
12
  import __sfc_main from '../../../components/CnStatusBadge/CnStatusBadge.vue.js';
13
+ import { userLocale } from '../../../utils/userLocale.js';
13
14
  import { prefixUrl, buildHeaders } from '../../../utils/headers.js';
14
15
  import { safeHref } from '../../../utils/safeHref.js';
15
16
 
@@ -332,7 +333,7 @@ var script = {
332
333
  if (Number.isNaN(date.getTime())) {
333
334
  return ''
334
335
  }
335
- return date.toLocaleDateString(undefined, { dateStyle: 'medium' })
336
+ return date.toLocaleDateString(userLocale(), { dateStyle: 'medium' })
336
337
  },
337
338
 
338
339
  /**