@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":"CnAnalyticsTab.vue3.js","sources":["../../../../../src/integrations/builtin/analytics/CnAnalyticsTab.vue?vue&type=template&id=0a1fb354&lang.js"],"sourcesContent":["<!--\n CnAnalyticsTab — bespoke sidebar tab for the `analytics` integration\n leaf.\n\n Replaces the generic `CnIntegrationTab` for the `analytics` leaf:\n renders the linked NC Analytics reports / datasets so the tab reads\n like the real Analytics app — each row is an `NcListItem` showing the\n chart-type icon (bar / line / pie / dataset), the bold report name, a\n data-source / type subtitle, an optional headline KPI value, and a\n colour-coded type badge (CnStatusBadge). Clicking a row deep-links\n into the NC Analytics app at `/index.php/apps/analytics/#/r/{id}`\n (`#/r/` is NC Analytics 6.x's report route). Per-row \"Open in\n Analytics\" + \"Unlink\" live in the NcListItem action menu.\n\n Tier-2 surface (this commit):\n - \"Link existing report\" → opens CnAnalyticsReportPicker (modal)\n - \"Create new report\" → opens CnAnalyticsReportCreate (modal)\n - Per-row unlink → DELETE …/analytics/{reportId}\n\n Talks to the OpenRegister Tier-2 analytics-link endpoints\n GET /api/objects/{r}/{s}/{id}/analytics — list\n POST /api/objects/{r}/{s}/{id}/analytics — link existing\n POST /api/objects/{r}/{s}/{id}/analytics/new — create + link\n DELETE /api/objects/{r}/{s}/{id}/analytics/{reportId} — unlink\n GET /api/integrations/analytics/available — picker source\n served by `OCA\\OpenRegister\\Controller\\AnalyticsLinksController`.\n\n The endpoint returns rows shaped:\n { id, reportId, reportTitle, reportType, subheader, value, kpi,\n createdAt, modifiedAt, url, … }\n\n Empty + loading + error + 501/503 unavailable states follow ADR-017\n and AD-23 graceful degradation. All UI strings pass through\n `t('nextcloud-vue', ...)` per ADR-007. Styling uses Nextcloud CSS\n variables only so the nldesign overrides apply transparently\n (ADR-010).\n\n The headline KPI (`value` / `kpi`) is rendered only when the provider\n supplies it — there is no extra HTTP call per row. A type-coded icon\n communicates the chart kind without fetching chart data; inline\n mini-charts remain a follow-up enhancement.\n\n Bespoke-vs-generic rationale: LaunchPad needs to surface \"what kind of\n report is this + its headline number\" without opening it; the generic\n link list strips the chart type, the data source and the KPI.\n-->\n<template>\n\t<div class=\"cn-sidebar-tab cn-analytics-tab\">\n\t\t<!-- 503 unavailable banner (AD-23) -->\n\t\t<div v-if=\"degraded\" class=\"cn-analytics-tab__banner\" role=\"alert\">\n\t\t\t<AlertCircleOutline :size=\"18\" />\n\t\t\t<span>{{ degraded }}</span>\n\t\t</div>\n\n\t\t<div class=\"cn-analytics-tab__actions\">\n\t\t\t<NcButton variant=\"secondary\" @click=\"openPicker\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<LinkVariant :size=\"18\" />\n\t\t\t\t</template>\n\t\t\t\t{{ t('nextcloud-vue', 'Link existing report') }}\n\t\t\t</NcButton>\n\t\t\t<NcButton variant=\"primary\" @click=\"openCreate\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<Plus :size=\"18\" />\n\t\t\t\t</template>\n\t\t\t\t{{ t('nextcloud-vue', 'Create new report') }}\n\t\t\t</NcButton>\n\t\t</div>\n\n\t\t<!-- Loading -->\n\t\t<NcLoadingIcon v-if=\"loading\" />\n\n\t\t<!-- Error -->\n\t\t<div v-else-if=\"error\" class=\"cn-analytics-tab__error\" role=\"alert\">\n\t\t\t{{ error }}\n\t\t</div>\n\n\t\t<!-- Empty -->\n\t\t<div v-else-if=\"reports.length === 0\" class=\"cn-sidebar-tab__empty cn-analytics-tab__empty\">\n\t\t\t<ChartBar :size=\"32\" class=\"cn-analytics-tab__empty-icon\" />\n\t\t\t<p>{{ emptyLabel }}</p>\n\t\t\t<NcButton\n\t\t\t\tvariant=\"primary\"\n\t\t\t\t:href=\"analyticsAppUrl\"\n\t\t\t\ttarget=\"_blank\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<ChartBar :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ openAnalyticsLabel }}\n\t\t\t</NcButton>\n\t\t</div>\n\n\t\t<!-- Report list — NcListItem rows mirror the NC Analytics dataset list -->\n\t\t<ul v-else class=\"cn-analytics-tab__list\">\n\t\t\t<NcListItem\n\t\t\t\tv-for=\"report in reports\"\n\t\t\t\t:key=\"reportKey(report)\"\n\t\t\t\tclass=\"cn-analytics-tab__row\"\n\t\t\t\t:name=\"reportTitle(report)\"\n\t\t\t\t:bold=\"true\"\n\t\t\t\t:href=\"reportUrl(report)\"\n\t\t\t\ttarget=\"_blank\"\n\t\t\t\t:forceDisplayActions=\"true\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<span class=\"cn-analytics-tab__row-icon\">\n\t\t\t\t\t\t<component :is=\"reportIcon(report)\" :size=\"22\" />\n\t\t\t\t\t</span>\n\t\t\t\t</template>\n\t\t\t\t<template #subname>\n\t\t\t\t\t<span class=\"cn-analytics-tab__sub\">\n\t\t\t\t\t\t<CnStatusBadge\n\t\t\t\t\t\t\tclass=\"cn-analytics-tab__badge\"\n\t\t\t\t\t\t\tsize=\"small\"\n\t\t\t\t\t\t\t:variant=\"badgeVariant(report)\"\n\t\t\t\t\t\t\t:label=\"reportTypeLabel(report)\" />\n\t\t\t\t\t\t<span v-if=\"reportSubheader(report)\" class=\"cn-analytics-tab__subheader\">\n\t\t\t\t\t\t\t{{ reportSubheader(report) }}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<span v-if=\"modifiedAt(report)\" class=\"cn-analytics-tab__when\">\n\t\t\t\t\t\t\t· {{ formatWhen(modifiedAt(report)) }}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</span>\n\t\t\t\t</template>\n\t\t\t\t<template v-if=\"reportValue(report)\" #details>\n\t\t\t\t\t<span class=\"cn-analytics-tab__kpi\" :title=\"kpiTitle(report)\">\n\t\t\t\t\t\t{{ reportValue(report) }}\n\t\t\t\t\t</span>\n\t\t\t\t</template>\n\t\t\t\t<template #actions>\n\t\t\t\t\t<NcActionLink\n\t\t\t\t\t\t:closeAfterClick=\"true\"\n\t\t\t\t\t\t:href=\"reportUrl(report)\"\n\t\t\t\t\t\ttarget=\"_blank\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<OpenInNew :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ t('nextcloud-vue', 'Open in Analytics') }}\n\t\t\t\t\t</NcActionLink>\n\t\t\t\t\t<NcActionButton :closeAfterClick=\"true\" @click=\"unlinkReport(report)\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<LinkOff :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ t('nextcloud-vue', 'Unlink report') }}\n\t\t\t\t\t</NcActionButton>\n\t\t\t\t</template>\n\t\t\t</NcListItem>\n\t\t</ul>\n\n\t\t<CnAnalyticsReportPicker\n\t\t\tv-if=\"pickerOpen\"\n\t\t\t:apiBase=\"apiBase\"\n\t\t\t@close=\"pickerOpen = false\"\n\t\t\t@link=\"onLinkPick\" />\n\n\t\t<CnAnalyticsReportCreate\n\t\t\tv-if=\"createOpen\"\n\t\t\t@close=\"createOpen = false\"\n\t\t\t@create=\"onCreatePick\" />\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcActionButton, NcActionLink, NcButton, NcListItem, NcLoadingIcon } from '@nextcloud/vue'\nimport AlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue'\nimport ChartBar from 'vue-material-design-icons/ChartBar.vue'\nimport ChartLine from 'vue-material-design-icons/ChartLine.vue'\nimport ChartPie from 'vue-material-design-icons/ChartPie.vue'\nimport DatabaseOutline from 'vue-material-design-icons/DatabaseOutline.vue'\nimport LinkOff from 'vue-material-design-icons/LinkOff.vue'\nimport LinkVariant from 'vue-material-design-icons/LinkVariant.vue'\nimport OpenInNew from 'vue-material-design-icons/OpenInNew.vue'\nimport Plus from 'vue-material-design-icons/Plus.vue'\nimport TableIcon from 'vue-material-design-icons/Table.vue'\nimport ViewDashboard from 'vue-material-design-icons/ViewDashboard.vue'\nimport CnAnalyticsReportCreate from '../../../components/CnAnalyticsReportCreate/CnAnalyticsReportCreate.vue'\nimport CnAnalyticsReportPicker from '../../../components/CnAnalyticsReportPicker/CnAnalyticsReportPicker.vue'\nimport CnStatusBadge from '../../../components/CnStatusBadge/CnStatusBadge.vue'\nimport { buildHeaders, prefixUrl, safeHref } from '../../../utils/index.js'\n\n/**\n * CnAnalyticsTab — bespoke dataset list for the `analytics` integration.\n *\n * Renders linked NC Analytics reports / datasets as NcListItem rows\n * styled to mirror the Analytics app: chart-type icon, bold report\n * name, data-source / type subtitle, type badge and an optional\n * headline KPI value. Clicking a row opens the report in NC Analytics\n * in a new tab. Tier-2: includes link / create modals and per-row\n * unlink via the row action menu. See the file-level docblock for\n * design notes.\n */\nexport default {\n\tname: 'CnAnalyticsTab',\n\n\tcomponents: {\n\t\tNcActionButton,\n\t\tNcActionLink,\n\t\tNcButton,\n\t\tNcListItem,\n\t\tNcLoadingIcon,\n\t\tCnStatusBadge,\n\t\tAlertCircleOutline,\n\t\tChartBar,\n\t\tChartLine,\n\t\tChartPie,\n\t\tDatabaseOutline,\n\t\tLinkOff,\n\t\tLinkVariant,\n\t\tOpenInNew,\n\t\tPlus,\n\t\tTableIcon,\n\t\tViewDashboard,\n\t\tCnAnalyticsReportPicker,\n\t\tCnAnalyticsReportCreate,\n\t},\n\n\tprops: {\n\t\t/* eslint-disable vue/no-unused-properties -- the integration dispatch binds integrationId on every integration component (see CnIntegrationWidgetGrid), so declaring it keeps it out of $attrs */\n\t\t/** Stable integration id (forwarded from the registry — always `'analytics'`). */\n\t\tintegrationId: { type: String, default: 'analytics' },\n\t\t/* eslint-enable vue/no-unused-properties */\n\t\t/** Parent object id. */\n\t\tobjectId: { type: String, required: true },\n\t\t/** OpenRegister register id (slug or uuid). */\n\t\tregister: { type: String, default: '' },\n\t\t/** OpenRegister schema id (slug or uuid). */\n\t\tschema: { type: String, default: '' },\n\t\t/** Base API URL. */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\t\t/** Pre-translated empty-state label. */\n\t\temptyLabel: { type: String, default: () => t('nextcloud-vue', 'No reports linked yet') },\n\t\t/** Pre-translated \"Open Analytics\" CTA label. */\n\t\topenAnalyticsLabel: { type: String, default: () => t('nextcloud-vue', 'Open Analytics') },\n\t\t/** Pre-translated 503 unavailable banner copy. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Analytics is currently unavailable.') },\n\t\t/** URL of the NC Analytics app entry. */\n\t\tanalyticsAppUrl: { type: String, default: '/index.php/apps/analytics' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\treports: [],\n\t\t\tloading: false,\n\t\t\terror: '',\n\t\t\tdegraded: '',\n\t\t\tpickerOpen: false,\n\t\t\tcreateOpen: false,\n\t\t}\n\t},\n\n\twatch: {\n\t\tobjectId: { immediate: true, handler(id) {\n\t\t\tif (id) {\n\t\t\t\tthis.fetchReports()\n\t\t\t}\n\t\t} },\n\n\t\tregister() {\n\t\t\tthis.fetchReports()\n\t\t},\n\n\t\tschema() {\n\t\t\tthis.fetchReports()\n\t\t},\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/**\n\t\t * Base for the Tier-2 analytics endpoints (list/link/new/destroy).\n\t\t *\n\t\t * @return {string} The endpoint URL.\n\t\t */\n\t\tanalyticsEndpoint() {\n\t\t\treturn prefixUrl(`${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/analytics`)\n\t\t},\n\n\t\treportKey(report) {\n\t\t\treturn (report && (report.reportId ?? report.id ?? report.data?.id)) || ''\n\t\t},\n\n\t\t/**\n\t\t * Resolve the displayable report title, stripping the\n\t\t * `[or:{uuid}]` marker (pre-Tier-2 reports embedded the marker\n\t\t * inside `name`).\n\t\t *\n\t\t * @param {object} report Provider row.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\treportTitle(report) {\n\t\t\tconst raw = (report && (report.reportTitle || report.title || report.name || report.data?.name)) || ''\n\t\t\treturn String(raw).replace(/\\s*\\[or:[^\\]]+\\]\\s*/g, ' ').trim() || t('nextcloud-vue', 'Untitled report')\n\t\t},\n\n\t\t/**\n\t\t * Resolve the secondary \"subheader\" line, stripping any leaked\n\t\t * marker defensively.\n\t\t *\n\t\t * @param {object} report Provider row.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\treportSubheader(report) {\n\t\t\tconst raw = report?.subheader ?? report?.dataSource ?? report?.data?.subheader ?? ''\n\t\t\treturn String(raw).replace(/\\s*\\[or:[^\\]]+\\]\\s*/g, ' ').trim()\n\t\t},\n\n\t\treportType(report) {\n\t\t\tconst raw = report?.reportType ?? report?.type ?? report?.data?.type\n\t\t\tif (raw === null || raw === undefined || raw === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst num = Number(raw)\n\t\t\treturn Number.isFinite(num) ? num : null\n\t\t},\n\n\t\t/**\n\t\t * Resolve a translatable label for the report type. Falls back\n\t\t * to \"Report\" when the type code is unknown. Type codes mirror\n\t\t * NC Analytics 6.x (datasource types).\n\t\t *\n\t\t * @param {object} report Provider row.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\treportTypeLabel(report) {\n\t\t\tconst type = this.reportType(report)\n\t\t\tswitch (type) {\n\t\t\t\tcase 0: return t('nextcloud-vue', 'Group')\n\t\t\t\tcase 1: return t('nextcloud-vue', 'File')\n\t\t\t\tcase 2: return t('nextcloud-vue', 'Database')\n\t\t\t\tcase 3: return t('nextcloud-vue', 'Git')\n\t\t\t\tcase 4: return t('nextcloud-vue', 'External')\n\t\t\t\tcase 6: return t('nextcloud-vue', 'JSON')\n\t\t\t\tdefault: return t('nextcloud-vue', 'Report')\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * CnStatusBadge colour variant for the report type. Mirrors NC\n\t\t * Analytics' datasource-type accenting without inventing colours\n\t\t * (uses the shared variant palette).\n\t\t *\n\t\t * @param {object} report Provider row.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tbadgeVariant(report) {\n\t\t\tconst type = this.reportType(report)\n\t\t\tswitch (type) {\n\t\t\t\tcase 1: return 'primary'\n\t\t\t\tcase 2: return 'success'\n\t\t\t\tcase 4: return 'warning'\n\t\t\t\tcase 6: return 'info'\n\t\t\t\tdefault: return 'default'\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * MDI component for the report type. Tries to express the\n\t\t * \"kind\" without fetching chart data.\n\t\t *\n\t\t * @param {object} report Provider row.\n\t\t *\n\t\t * @return {object} Vue component.\n\t\t */\n\t\treportIcon(report) {\n\t\t\tconst type = this.reportType(report)\n\t\t\tswitch (type) {\n\t\t\t\tcase 1: return TableIcon\n\t\t\t\tcase 0: return ViewDashboard\n\t\t\t\tcase 2: return DatabaseOutline\n\t\t\t\tcase 4: return ChartLine\n\t\t\t\tcase 6: return ChartPie\n\t\t\t\tdefault: return ChartBar\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Headline KPI / value for the report, when the provider supplies\n\t\t * one. Numbers are locale-formatted; strings pass through.\n\t\t *\n\t\t * @param {object} report Provider row.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\treportValue(report) {\n\t\t\tconst raw = report?.value ?? report?.kpi ?? report?.data?.value ?? ''\n\t\t\tif (raw === null || raw === undefined || raw === '') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst num = Number(raw)\n\t\t\tif (Number.isFinite(num) && String(raw).trim() !== '') {\n\t\t\t\ttry {\n\t\t\t\t\treturn num.toLocaleString()\n\t\t\t\t} catch {\n\t\t\t\t\treturn String(num)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn String(raw)\n\t\t},\n\n\t\tkpiTitle(_report) {\n\t\t\treturn t('nextcloud-vue', 'Latest value')\n\t\t},\n\n\t\t// A provider URL is used only when it is a safe scheme; else the app link.\n\t\treportUrl(report) {\n\t\t\tif (report && report.url && safeHref(report.url) !== '#') {\n\t\t\t\treturn report.url\n\t\t\t}\n\t\t\tconst id = this.reportKey(report)\n\t\t\treturn id ? `/index.php/apps/analytics/#/r/${encodeURIComponent(id)}` : this.analyticsAppUrl\n\t\t},\n\n\t\tmodifiedAt(report) {\n\t\t\treturn report?.modifiedAt\n\t\t\t\t|| report?.data?.modifiedAt\n\t\t\t\t|| report?.data?.modified\n\t\t\t\t|| report?.createdAt\n\t\t\t\t|| report?.data?.createdAt\n\t\t\t\t|| ''\n\t\t},\n\n\t\tformatWhen(value) {\n\t\t\tif (!value) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst num = Number(value)\n\t\t\t\tconst d = Number.isFinite(num) && num > 0 && String(value).length <= 12\n\t\t\t\t\t? new Date(num * 1000)\n\t\t\t\t\t: new Date(value)\n\t\t\t\tif (Number.isNaN(d.getTime())) {\n\t\t\t\t\treturn String(value)\n\t\t\t\t}\n\t\t\t\treturn d.toLocaleDateString(undefined, { dateStyle: 'medium' })\n\t\t\t} catch {\n\t\t\t\treturn String(value)\n\t\t\t}\n\t\t},\n\n\t\topenPicker() {\n\t\t\tthis.pickerOpen = true\n\t\t},\n\n\t\topenCreate() {\n\t\t\tthis.createOpen = true\n\t\t},\n\n\t\tasync onLinkPick(payload) {\n\t\t\tthis.pickerOpen = false\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(this.analyticsEndpoint(), {\n\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\theaders: { ...buildHeaders(), 'Content-Type': 'application/json' },\n\t\t\t\t\tbody: JSON.stringify(payload),\n\t\t\t\t})\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tawait this.fetchReports()\n\t\t\t\t} else if (response.status === 409) {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'This report is already linked.')\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not link report.')\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('[CnAnalyticsTab] link failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not link report.')\n\t\t\t}\n\t\t},\n\n\t\tasync onCreatePick(payload) {\n\t\t\tthis.createOpen = false\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(`${this.analyticsEndpoint()}/new`, {\n\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\theaders: { ...buildHeaders(), 'Content-Type': 'application/json' },\n\t\t\t\t\tbody: JSON.stringify(payload),\n\t\t\t\t})\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tawait this.fetchReports()\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not create report.')\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('[CnAnalyticsTab] create failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not create report.')\n\t\t\t}\n\t\t},\n\n\t\tasync unlinkReport(report) {\n\t\t\tconst reportId = this.reportKey(report)\n\t\t\tif (!reportId) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(`${this.analyticsEndpoint()}/${reportId}`, {\n\t\t\t\t\tmethod: 'DELETE',\n\t\t\t\t\theaders: buildHeaders(),\n\t\t\t\t})\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tawait this.fetchReports()\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not unlink report.')\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('[CnAnalyticsTab] unlink failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not unlink report.')\n\t\t\t}\n\t\t},\n\n\t\tasync fetchReports() {\n\t\t\tif (!this.register || !this.schema || !this.objectId) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\tthis.error = ''\n\t\t\tthis.degraded = ''\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(this.analyticsEndpoint(), { 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.reports = data.results || data.items || (Array.isArray(data) ? data : []) || []\n\t\t\t\t} else if (response.status === 503 || response.status === 501) {\n\t\t\t\t\tthis.reports = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.reports = []\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load reports.')\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('[CnAnalyticsTab] failed to fetch reports', err)\n\t\t\t\tthis.reports = []\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load reports.')\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-analytics-tab {\n\tpadding: 12px;\n\toverflow-x: hidden;\n}\n\n.cn-analytics-tab__actions {\n\tdisplay: flex;\n\tgap: 8px;\n\tmargin-bottom: 10px;\n\tflex-wrap: wrap;\n}\n\n.cn-analytics-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-analytics-tab__error {\n\tcolor: var(--color-error);\n\tfont-size: 0.9em;\n\tmargin: 4px 0 8px;\n}\n\n.cn-analytics-tab__empty {\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\tgap: 8px;\n\tpadding: 16px 8px;\n\tcolor: var(--color-text-maxcontrast);\n\ttext-align: center;\n}\n\n.cn-analytics-tab__empty-icon {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-analytics-tab__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n}\n\n/* Chart-type icon avatar — tinted with the descriptor accent (or NC primary). */\n.cn-analytics-tab__row-icon {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\twidth: 40px;\n\theight: 40px;\n\tborder-radius: var(--border-radius-large, 8px);\n\tbackground: var(--color-primary-element-light, var(--color-background-hover));\n\tcolor: var(--cn-iw-accent, var(--color-primary-element, #0082c9));\n}\n\n/* Subtitle row: type badge + data source + modified date, single muted line. */\n.cn-analytics-tab__sub {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tgap: 6px;\n\tfont-size: 11px;\n\tcolor: var(--color-text-maxcontrast);\n\tmin-width: 0;\n}\n\n.cn-analytics-tab__badge {\n\tflex-shrink: 0;\n}\n\n.cn-analytics-tab__subheader {\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n\tmax-width: 100%;\n}\n\n.cn-analytics-tab__when {\n\twhite-space: nowrap;\n}\n\n/* Headline KPI value, right-aligned in the details slot — Analytics style. */\n.cn-analytics-tab__kpi {\n\tfont-weight: 700;\n\tfont-size: 15px;\n\tcolor: var(--color-main-text);\n\twhite-space: nowrap;\n}\n</style>\n"],"names":["_openBlock","_createElementBlock","_createVNode","_createTextVNode","_toDisplayString","_createElementVNode","_createBlock","_resolveDynamicComponent"],"mappings":"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"}
1
+ {"version":3,"file":"CnAnalyticsTab.vue3.js","sources":["../../../../../src/integrations/builtin/analytics/CnAnalyticsTab.vue?vue&type=template&id=58d5577f&lang.js"],"sourcesContent":["<!--\n CnAnalyticsTab — bespoke sidebar tab for the `analytics` integration\n leaf.\n\n Replaces the generic `CnIntegrationTab` for the `analytics` leaf:\n renders the linked NC Analytics reports / datasets so the tab reads\n like the real Analytics app — each row is an `NcListItem` showing the\n chart-type icon (bar / line / pie / dataset), the bold report name, a\n data-source / type subtitle, an optional headline KPI value, and a\n colour-coded type badge (CnStatusBadge). Clicking a row deep-links\n into the NC Analytics app at `/index.php/apps/analytics/#/r/{id}`\n (`#/r/` is NC Analytics 6.x's report route). Per-row \"Open in\n Analytics\" + \"Unlink\" live in the NcListItem action menu.\n\n Tier-2 surface (this commit):\n - \"Link existing report\" → opens CnAnalyticsReportPicker (modal)\n - \"Create new report\" → opens CnAnalyticsReportCreate (modal)\n - Per-row unlink → DELETE …/analytics/{reportId}\n\n Talks to the OpenRegister Tier-2 analytics-link endpoints\n GET /api/objects/{r}/{s}/{id}/analytics — list\n POST /api/objects/{r}/{s}/{id}/analytics — link existing\n POST /api/objects/{r}/{s}/{id}/analytics/new — create + link\n DELETE /api/objects/{r}/{s}/{id}/analytics/{reportId} — unlink\n GET /api/integrations/analytics/available — picker source\n served by `OCA\\OpenRegister\\Controller\\AnalyticsLinksController`.\n\n The endpoint returns rows shaped:\n { id, reportId, reportTitle, reportType, subheader, value, kpi,\n createdAt, modifiedAt, url, … }\n\n Empty + loading + error + 501/503 unavailable states follow ADR-017\n and AD-23 graceful degradation. All UI strings pass through\n `t('nextcloud-vue', ...)` per ADR-007. Styling uses Nextcloud CSS\n variables only so the nldesign overrides apply transparently\n (ADR-010).\n\n The headline KPI (`value` / `kpi`) is rendered only when the provider\n supplies it — there is no extra HTTP call per row. A type-coded icon\n communicates the chart kind without fetching chart data; inline\n mini-charts remain a follow-up enhancement.\n\n Bespoke-vs-generic rationale: LaunchPad needs to surface \"what kind of\n report is this + its headline number\" without opening it; the generic\n link list strips the chart type, the data source and the KPI.\n-->\n<template>\n\t<div class=\"cn-sidebar-tab cn-analytics-tab\">\n\t\t<!-- 503 unavailable banner (AD-23) -->\n\t\t<div v-if=\"degraded\" class=\"cn-analytics-tab__banner\" role=\"alert\">\n\t\t\t<AlertCircleOutline :size=\"18\" />\n\t\t\t<span>{{ degraded }}</span>\n\t\t</div>\n\n\t\t<div class=\"cn-analytics-tab__actions\">\n\t\t\t<NcButton variant=\"secondary\" @click=\"openPicker\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<LinkVariant :size=\"18\" />\n\t\t\t\t</template>\n\t\t\t\t{{ t('nextcloud-vue', 'Link existing report') }}\n\t\t\t</NcButton>\n\t\t\t<NcButton variant=\"primary\" @click=\"openCreate\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<Plus :size=\"18\" />\n\t\t\t\t</template>\n\t\t\t\t{{ t('nextcloud-vue', 'Create new report') }}\n\t\t\t</NcButton>\n\t\t</div>\n\n\t\t<!-- Loading -->\n\t\t<NcLoadingIcon v-if=\"loading\" />\n\n\t\t<!-- Error -->\n\t\t<div v-else-if=\"error\" class=\"cn-analytics-tab__error\" role=\"alert\">\n\t\t\t{{ error }}\n\t\t</div>\n\n\t\t<!-- Empty -->\n\t\t<div v-else-if=\"reports.length === 0\" class=\"cn-sidebar-tab__empty cn-analytics-tab__empty\">\n\t\t\t<ChartBar :size=\"32\" class=\"cn-analytics-tab__empty-icon\" />\n\t\t\t<p>{{ emptyLabel }}</p>\n\t\t\t<NcButton\n\t\t\t\tvariant=\"primary\"\n\t\t\t\t:href=\"analyticsAppUrl\"\n\t\t\t\ttarget=\"_blank\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<ChartBar :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ openAnalyticsLabel }}\n\t\t\t</NcButton>\n\t\t</div>\n\n\t\t<!-- Report list — NcListItem rows mirror the NC Analytics dataset list -->\n\t\t<ul v-else class=\"cn-analytics-tab__list\">\n\t\t\t<NcListItem\n\t\t\t\tv-for=\"report in reports\"\n\t\t\t\t:key=\"reportKey(report)\"\n\t\t\t\tclass=\"cn-analytics-tab__row\"\n\t\t\t\t:name=\"reportTitle(report)\"\n\t\t\t\t:bold=\"true\"\n\t\t\t\t:href=\"reportUrl(report)\"\n\t\t\t\ttarget=\"_blank\"\n\t\t\t\t:forceDisplayActions=\"true\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<span class=\"cn-analytics-tab__row-icon\">\n\t\t\t\t\t\t<component :is=\"reportIcon(report)\" :size=\"22\" />\n\t\t\t\t\t</span>\n\t\t\t\t</template>\n\t\t\t\t<template #subname>\n\t\t\t\t\t<span class=\"cn-analytics-tab__sub\">\n\t\t\t\t\t\t<CnStatusBadge\n\t\t\t\t\t\t\tclass=\"cn-analytics-tab__badge\"\n\t\t\t\t\t\t\tsize=\"small\"\n\t\t\t\t\t\t\t:variant=\"badgeVariant(report)\"\n\t\t\t\t\t\t\t:label=\"reportTypeLabel(report)\" />\n\t\t\t\t\t\t<span v-if=\"reportSubheader(report)\" class=\"cn-analytics-tab__subheader\">\n\t\t\t\t\t\t\t{{ reportSubheader(report) }}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<span v-if=\"modifiedAt(report)\" class=\"cn-analytics-tab__when\">\n\t\t\t\t\t\t\t· {{ formatWhen(modifiedAt(report)) }}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</span>\n\t\t\t\t</template>\n\t\t\t\t<template v-if=\"reportValue(report)\" #details>\n\t\t\t\t\t<span class=\"cn-analytics-tab__kpi\" :title=\"kpiTitle(report)\">\n\t\t\t\t\t\t{{ reportValue(report) }}\n\t\t\t\t\t</span>\n\t\t\t\t</template>\n\t\t\t\t<template #actions>\n\t\t\t\t\t<NcActionLink\n\t\t\t\t\t\t:closeAfterClick=\"true\"\n\t\t\t\t\t\t:href=\"reportUrl(report)\"\n\t\t\t\t\t\ttarget=\"_blank\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<OpenInNew :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ t('nextcloud-vue', 'Open in Analytics') }}\n\t\t\t\t\t</NcActionLink>\n\t\t\t\t\t<NcActionButton :closeAfterClick=\"true\" @click=\"unlinkReport(report)\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<LinkOff :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ t('nextcloud-vue', 'Unlink report') }}\n\t\t\t\t\t</NcActionButton>\n\t\t\t\t</template>\n\t\t\t</NcListItem>\n\t\t</ul>\n\n\t\t<CnAnalyticsReportPicker\n\t\t\tv-if=\"pickerOpen\"\n\t\t\t:apiBase=\"apiBase\"\n\t\t\t@close=\"pickerOpen = false\"\n\t\t\t@link=\"onLinkPick\" />\n\n\t\t<CnAnalyticsReportCreate\n\t\t\tv-if=\"createOpen\"\n\t\t\t@close=\"createOpen = false\"\n\t\t\t@create=\"onCreatePick\" />\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcActionButton, NcActionLink, NcButton, NcListItem, NcLoadingIcon } from '@nextcloud/vue'\nimport AlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue'\nimport ChartBar from 'vue-material-design-icons/ChartBar.vue'\nimport ChartLine from 'vue-material-design-icons/ChartLine.vue'\nimport ChartPie from 'vue-material-design-icons/ChartPie.vue'\nimport DatabaseOutline from 'vue-material-design-icons/DatabaseOutline.vue'\nimport LinkOff from 'vue-material-design-icons/LinkOff.vue'\nimport LinkVariant from 'vue-material-design-icons/LinkVariant.vue'\nimport OpenInNew from 'vue-material-design-icons/OpenInNew.vue'\nimport Plus from 'vue-material-design-icons/Plus.vue'\nimport TableIcon from 'vue-material-design-icons/Table.vue'\nimport ViewDashboard from 'vue-material-design-icons/ViewDashboard.vue'\nimport CnAnalyticsReportCreate from '../../../components/CnAnalyticsReportCreate/CnAnalyticsReportCreate.vue'\nimport CnAnalyticsReportPicker from '../../../components/CnAnalyticsReportPicker/CnAnalyticsReportPicker.vue'\nimport CnStatusBadge from '../../../components/CnStatusBadge/CnStatusBadge.vue'\nimport { buildHeaders, prefixUrl, safeHref } from '../../../utils/index.js'\nimport { userLocale } from '../../../utils/userLocale.js'\n\n/**\n * CnAnalyticsTab — bespoke dataset list for the `analytics` integration.\n *\n * Renders linked NC Analytics reports / datasets as NcListItem rows\n * styled to mirror the Analytics app: chart-type icon, bold report\n * name, data-source / type subtitle, type badge and an optional\n * headline KPI value. Clicking a row opens the report in NC Analytics\n * in a new tab. Tier-2: includes link / create modals and per-row\n * unlink via the row action menu. See the file-level docblock for\n * design notes.\n */\nexport default {\n\tname: 'CnAnalyticsTab',\n\n\tcomponents: {\n\t\tNcActionButton,\n\t\tNcActionLink,\n\t\tNcButton,\n\t\tNcListItem,\n\t\tNcLoadingIcon,\n\t\tCnStatusBadge,\n\t\tAlertCircleOutline,\n\t\tChartBar,\n\t\tChartLine,\n\t\tChartPie,\n\t\tDatabaseOutline,\n\t\tLinkOff,\n\t\tLinkVariant,\n\t\tOpenInNew,\n\t\tPlus,\n\t\tTableIcon,\n\t\tViewDashboard,\n\t\tCnAnalyticsReportPicker,\n\t\tCnAnalyticsReportCreate,\n\t},\n\n\tprops: {\n\t\t/* eslint-disable vue/no-unused-properties -- the integration dispatch binds integrationId on every integration component (see CnIntegrationWidgetGrid), so declaring it keeps it out of $attrs */\n\t\t/** Stable integration id (forwarded from the registry — always `'analytics'`). */\n\t\tintegrationId: { type: String, default: 'analytics' },\n\t\t/* eslint-enable vue/no-unused-properties */\n\t\t/** Parent object id. */\n\t\tobjectId: { type: String, required: true },\n\t\t/** OpenRegister register id (slug or uuid). */\n\t\tregister: { type: String, default: '' },\n\t\t/** OpenRegister schema id (slug or uuid). */\n\t\tschema: { type: String, default: '' },\n\t\t/** Base API URL. */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\t\t/** Pre-translated empty-state label. */\n\t\temptyLabel: { type: String, default: () => t('nextcloud-vue', 'No reports linked yet') },\n\t\t/** Pre-translated \"Open Analytics\" CTA label. */\n\t\topenAnalyticsLabel: { type: String, default: () => t('nextcloud-vue', 'Open Analytics') },\n\t\t/** Pre-translated 503 unavailable banner copy. */\n\t\tunavailableLabel: { type: String, default: () => t('nextcloud-vue', 'NC Analytics is currently unavailable.') },\n\t\t/** URL of the NC Analytics app entry. */\n\t\tanalyticsAppUrl: { type: String, default: '/index.php/apps/analytics' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\treports: [],\n\t\t\tloading: false,\n\t\t\terror: '',\n\t\t\tdegraded: '',\n\t\t\tpickerOpen: false,\n\t\t\tcreateOpen: false,\n\t\t}\n\t},\n\n\twatch: {\n\t\tobjectId: { immediate: true, handler(id) {\n\t\t\tif (id) {\n\t\t\t\tthis.fetchReports()\n\t\t\t}\n\t\t} },\n\n\t\tregister() {\n\t\t\tthis.fetchReports()\n\t\t},\n\n\t\tschema() {\n\t\t\tthis.fetchReports()\n\t\t},\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/**\n\t\t * Base for the Tier-2 analytics endpoints (list/link/new/destroy).\n\t\t *\n\t\t * @return {string} The endpoint URL.\n\t\t */\n\t\tanalyticsEndpoint() {\n\t\t\treturn prefixUrl(`${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/analytics`)\n\t\t},\n\n\t\treportKey(report) {\n\t\t\treturn (report && (report.reportId ?? report.id ?? report.data?.id)) || ''\n\t\t},\n\n\t\t/**\n\t\t * Resolve the displayable report title, stripping the\n\t\t * `[or:{uuid}]` marker (pre-Tier-2 reports embedded the marker\n\t\t * inside `name`).\n\t\t *\n\t\t * @param {object} report Provider row.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\treportTitle(report) {\n\t\t\tconst raw = (report && (report.reportTitle || report.title || report.name || report.data?.name)) || ''\n\t\t\treturn String(raw).replace(/\\s*\\[or:[^\\]]+\\]\\s*/g, ' ').trim() || t('nextcloud-vue', 'Untitled report')\n\t\t},\n\n\t\t/**\n\t\t * Resolve the secondary \"subheader\" line, stripping any leaked\n\t\t * marker defensively.\n\t\t *\n\t\t * @param {object} report Provider row.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\treportSubheader(report) {\n\t\t\tconst raw = report?.subheader ?? report?.dataSource ?? report?.data?.subheader ?? ''\n\t\t\treturn String(raw).replace(/\\s*\\[or:[^\\]]+\\]\\s*/g, ' ').trim()\n\t\t},\n\n\t\treportType(report) {\n\t\t\tconst raw = report?.reportType ?? report?.type ?? report?.data?.type\n\t\t\tif (raw === null || raw === undefined || raw === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst num = Number(raw)\n\t\t\treturn Number.isFinite(num) ? num : null\n\t\t},\n\n\t\t/**\n\t\t * Resolve a translatable label for the report type. Falls back\n\t\t * to \"Report\" when the type code is unknown. Type codes mirror\n\t\t * NC Analytics 6.x (datasource types).\n\t\t *\n\t\t * @param {object} report Provider row.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\treportTypeLabel(report) {\n\t\t\tconst type = this.reportType(report)\n\t\t\tswitch (type) {\n\t\t\t\tcase 0: return t('nextcloud-vue', 'Group')\n\t\t\t\tcase 1: return t('nextcloud-vue', 'File')\n\t\t\t\tcase 2: return t('nextcloud-vue', 'Database')\n\t\t\t\tcase 3: return t('nextcloud-vue', 'Git')\n\t\t\t\tcase 4: return t('nextcloud-vue', 'External')\n\t\t\t\tcase 6: return t('nextcloud-vue', 'JSON')\n\t\t\t\tdefault: return t('nextcloud-vue', 'Report')\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * CnStatusBadge colour variant for the report type. Mirrors NC\n\t\t * Analytics' datasource-type accenting without inventing colours\n\t\t * (uses the shared variant palette).\n\t\t *\n\t\t * @param {object} report Provider row.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tbadgeVariant(report) {\n\t\t\tconst type = this.reportType(report)\n\t\t\tswitch (type) {\n\t\t\t\tcase 1: return 'primary'\n\t\t\t\tcase 2: return 'success'\n\t\t\t\tcase 4: return 'warning'\n\t\t\t\tcase 6: return 'info'\n\t\t\t\tdefault: return 'default'\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * MDI component for the report type. Tries to express the\n\t\t * \"kind\" without fetching chart data.\n\t\t *\n\t\t * @param {object} report Provider row.\n\t\t *\n\t\t * @return {object} Vue component.\n\t\t */\n\t\treportIcon(report) {\n\t\t\tconst type = this.reportType(report)\n\t\t\tswitch (type) {\n\t\t\t\tcase 1: return TableIcon\n\t\t\t\tcase 0: return ViewDashboard\n\t\t\t\tcase 2: return DatabaseOutline\n\t\t\t\tcase 4: return ChartLine\n\t\t\t\tcase 6: return ChartPie\n\t\t\t\tdefault: return ChartBar\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Headline KPI / value for the report, when the provider supplies\n\t\t * one. Numbers are locale-formatted; strings pass through.\n\t\t *\n\t\t * @param {object} report Provider row.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\treportValue(report) {\n\t\t\tconst raw = report?.value ?? report?.kpi ?? report?.data?.value ?? ''\n\t\t\tif (raw === null || raw === undefined || raw === '') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst num = Number(raw)\n\t\t\tif (Number.isFinite(num) && String(raw).trim() !== '') {\n\t\t\t\ttry {\n\t\t\t\t\treturn num.toLocaleString(userLocale())\n\t\t\t\t} catch {\n\t\t\t\t\treturn String(num)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn String(raw)\n\t\t},\n\n\t\tkpiTitle(_report) {\n\t\t\treturn t('nextcloud-vue', 'Latest value')\n\t\t},\n\n\t\t// A provider URL is used only when it is a safe scheme; else the app link.\n\t\treportUrl(report) {\n\t\t\tif (report && report.url && safeHref(report.url) !== '#') {\n\t\t\t\treturn report.url\n\t\t\t}\n\t\t\tconst id = this.reportKey(report)\n\t\t\treturn id ? `/index.php/apps/analytics/#/r/${encodeURIComponent(id)}` : this.analyticsAppUrl\n\t\t},\n\n\t\tmodifiedAt(report) {\n\t\t\treturn report?.modifiedAt\n\t\t\t\t|| report?.data?.modifiedAt\n\t\t\t\t|| report?.data?.modified\n\t\t\t\t|| report?.createdAt\n\t\t\t\t|| report?.data?.createdAt\n\t\t\t\t|| ''\n\t\t},\n\n\t\tformatWhen(value) {\n\t\t\tif (!value) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst num = Number(value)\n\t\t\t\tconst d = Number.isFinite(num) && num > 0 && String(value).length <= 12\n\t\t\t\t\t? new Date(num * 1000)\n\t\t\t\t\t: new Date(value)\n\t\t\t\tif (Number.isNaN(d.getTime())) {\n\t\t\t\t\treturn String(value)\n\t\t\t\t}\n\t\t\t\treturn d.toLocaleDateString(userLocale(), { dateStyle: 'medium' })\n\t\t\t} catch {\n\t\t\t\treturn String(value)\n\t\t\t}\n\t\t},\n\n\t\topenPicker() {\n\t\t\tthis.pickerOpen = true\n\t\t},\n\n\t\topenCreate() {\n\t\t\tthis.createOpen = true\n\t\t},\n\n\t\tasync onLinkPick(payload) {\n\t\t\tthis.pickerOpen = false\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(this.analyticsEndpoint(), {\n\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\theaders: { ...buildHeaders(), 'Content-Type': 'application/json' },\n\t\t\t\t\tbody: JSON.stringify(payload),\n\t\t\t\t})\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tawait this.fetchReports()\n\t\t\t\t} else if (response.status === 409) {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'This report is already linked.')\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not link report.')\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('[CnAnalyticsTab] link failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not link report.')\n\t\t\t}\n\t\t},\n\n\t\tasync onCreatePick(payload) {\n\t\t\tthis.createOpen = false\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(`${this.analyticsEndpoint()}/new`, {\n\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\theaders: { ...buildHeaders(), 'Content-Type': 'application/json' },\n\t\t\t\t\tbody: JSON.stringify(payload),\n\t\t\t\t})\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tawait this.fetchReports()\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not create report.')\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('[CnAnalyticsTab] create failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not create report.')\n\t\t\t}\n\t\t},\n\n\t\tasync unlinkReport(report) {\n\t\t\tconst reportId = this.reportKey(report)\n\t\t\tif (!reportId) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(`${this.analyticsEndpoint()}/${reportId}`, {\n\t\t\t\t\tmethod: 'DELETE',\n\t\t\t\t\theaders: buildHeaders(),\n\t\t\t\t})\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tawait this.fetchReports()\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not unlink report.')\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('[CnAnalyticsTab] unlink failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not unlink report.')\n\t\t\t}\n\t\t},\n\n\t\tasync fetchReports() {\n\t\t\tif (!this.register || !this.schema || !this.objectId) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\tthis.error = ''\n\t\t\tthis.degraded = ''\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(this.analyticsEndpoint(), { 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.reports = data.results || data.items || (Array.isArray(data) ? data : []) || []\n\t\t\t\t} else if (response.status === 503 || response.status === 501) {\n\t\t\t\t\tthis.reports = []\n\t\t\t\t\tthis.degraded = this.unavailableLabel\n\t\t\t\t} else {\n\t\t\t\t\tthis.reports = []\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load reports.')\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('[CnAnalyticsTab] failed to fetch reports', err)\n\t\t\t\tthis.reports = []\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load reports.')\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-analytics-tab {\n\tpadding: 12px;\n\toverflow-x: hidden;\n}\n\n.cn-analytics-tab__actions {\n\tdisplay: flex;\n\tgap: 8px;\n\tmargin-bottom: 10px;\n\tflex-wrap: wrap;\n}\n\n.cn-analytics-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-analytics-tab__error {\n\tcolor: var(--color-error);\n\tfont-size: 0.9em;\n\tmargin: 4px 0 8px;\n}\n\n.cn-analytics-tab__empty {\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\tgap: 8px;\n\tpadding: 16px 8px;\n\tcolor: var(--color-text-maxcontrast);\n\ttext-align: center;\n}\n\n.cn-analytics-tab__empty-icon {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-analytics-tab__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n}\n\n/* Chart-type icon avatar — tinted with the descriptor accent (or NC primary). */\n.cn-analytics-tab__row-icon {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\twidth: 40px;\n\theight: 40px;\n\tborder-radius: var(--border-radius-large, 8px);\n\tbackground: var(--color-primary-element-light, var(--color-background-hover));\n\tcolor: var(--cn-iw-accent, var(--color-primary-element, #0082c9));\n}\n\n/* Subtitle row: type badge + data source + modified date, single muted line. */\n.cn-analytics-tab__sub {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tgap: 6px;\n\tfont-size: 11px;\n\tcolor: var(--color-text-maxcontrast);\n\tmin-width: 0;\n}\n\n.cn-analytics-tab__badge {\n\tflex-shrink: 0;\n}\n\n.cn-analytics-tab__subheader {\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n\tmax-width: 100%;\n}\n\n.cn-analytics-tab__when {\n\twhite-space: nowrap;\n}\n\n/* Headline KPI value, right-aligned in the details slot — Analytics style. */\n.cn-analytics-tab__kpi {\n\tfont-weight: 700;\n\tfont-size: 15px;\n\tcolor: var(--color-main-text);\n\twhite-space: nowrap;\n}\n</style>\n"],"names":["_openBlock","_createElementBlock","_createVNode","_createTextVNode","_toDisplayString","_createElementVNode","_createBlock","_resolveDynamicComponent"],"mappings":"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"}
@@ -2,7 +2,7 @@ import script from './CnCalendarCard.vue2.js';
2
2
  import { render } from './CnCalendarCard.vue3.js';
3
3
 
4
4
  script.render = render;
5
- script.__scopeId = "data-v-3c1b2494";
5
+ script.__scopeId = "data-v-0989061d";
6
6
  const __sfc_main = script;
7
7
 
8
8
  export { __sfc_main as default };
@@ -6,6 +6,7 @@ import Calendar from 'vue-material-design-icons/Calendar.vue';
6
6
  import CalendarCheck from 'vue-material-design-icons/CalendarCheck.vue';
7
7
  import CalendarClock from 'vue-material-design-icons/CalendarClock.vue';
8
8
  import __sfc_main from '../../../components/CnDetailCard/CnDetailCard.vue.js';
9
+ import { userLocale } from '../../../utils/userLocale.js';
9
10
  import { prefixUrl, buildHeaders } from '../../../utils/headers.js';
10
11
 
11
12
  /**
@@ -223,7 +224,7 @@ var script = {
223
224
  const opts = this.surface === 'single-entity'
224
225
  ? { dateStyle: 'medium' }
225
226
  : { dateStyle: 'medium', timeStyle: 'short' };
226
- return d.toLocaleString(undefined, opts)
227
+ return d.toLocaleString(userLocale(), opts)
227
228
  } catch {
228
229
  return String(value)
229
230
  }
@@ -1 +1 @@
1
- {"version":3,"file":"CnCalendarCard.vue2.js","sources":["../../../../../src/integrations/builtin/calendar/CnCalendarCard.vue"],"sourcesContent":["<!--\n CnCalendarCard — bespoke widget for the `calendar` integration.\n\n Surface-aware (AD-19) widget that renders linked VEVENTs across the\n four pluggable-integration-registry surfaces with one component:\n\n - `user-dashboard` : compact list, \"next 5 upcoming meetings\" across\n the user's objects (one row per VEVENT). Falls\n back to a list scoped by the optional\n register/schema/objectId when given (registry\n dashboards pass the full triple) so it still\n works without a global \"across all my objects\"\n query — see AD-22.\n - `app-dashboard` : same compact list scoped to the current app's\n register/schema/objectId, when given.\n - `detail-page` : full per-object meeting list with a quick\n \"Open in Calendar\" CTA — the inline create\n form lives in CnCalendarTab, not here, to keep\n the detail card compact (design.md AD-3).\n - `single-entity` : a single VEVENT rendered as a chip with date +\n summary + status icon. Accepts `entityId`\n (composite \"calendarId/eventUri\") for the\n referenceType auto-rendering path.\n\n Loading / empty / error states follow ADR-017 component composition.\n All strings pass through t('nextcloud-vue', ...) for nl + en\n translation (ADR-007). Theming uses Nextcloud CSS variables only so\n the nldesign app's overrides apply transparently (ADR-010).\n-->\n<template>\n\t<CnDetailCard :title=\"resolvedTitle\" :icon=\"Calendar\" :collapsible=\"collapsible\">\n\t\t<!-- Loading -->\n\t\t<NcLoadingIcon v-if=\"loading\" />\n\n\t\t<!-- Error -->\n\t\t<div v-else-if=\"error\" class=\"cn-calendar-card__error\" role=\"alert\">\n\t\t\t{{ error }}\n\t\t</div>\n\n\t\t<!-- Empty -->\n\t\t<div v-else-if=\"displayedEvents.length === 0\" class=\"cn-calendar-card__empty\">\n\t\t\t{{ noEventsLabel }}\n\t\t</div>\n\n\t\t<!-- single-entity surface: one compact chip -->\n\t\t<div v-else-if=\"surface === 'single-entity'\" class=\"cn-calendar-card__chip\">\n\t\t\t<CalendarClock :size=\"18\" class=\"cn-calendar-card__chip-icon\" />\n\t\t\t<span class=\"cn-calendar-card__chip-title\">{{ displayedEvents[0].summary || untitledLabel }}</span>\n\t\t\t<span v-if=\"displayedEvents[0].dtstart\" class=\"cn-calendar-card__chip-when\">\n\t\t\t\t{{ formatShort(displayedEvents[0].dtstart) }}\n\t\t\t</span>\n\t\t\t<span\n\t\t\t\tv-if=\"displayedEvents[0].status\"\n\t\t\t\tclass=\"cn-calendar-card__chip-status\"\n\t\t\t\t:class=\"`cn-calendar-card__chip-status--${displayedEvents[0].status}`\">\n\t\t\t\t{{ displayedEvents[0].status }}\n\t\t\t</span>\n\t\t</div>\n\n\t\t<!-- list surfaces: user/app/detail share the row template -->\n\t\t<div v-else class=\"cn-calendar-card__list\">\n\t\t\t<div\n\t\t\t\tv-for=\"ev in displayedEvents\"\n\t\t\t\t:key=\"rowKey(ev)\"\n\t\t\t\tclass=\"cn-calendar-card__row\">\n\t\t\t\t<div class=\"cn-calendar-card__row-icon\">\n\t\t\t\t\t<CalendarClock v-if=\"isUpcoming(ev)\" :size=\"20\" class=\"cn-calendar-card__icon--upcoming\" />\n\t\t\t\t\t<CalendarCheck v-else :size=\"20\" class=\"cn-calendar-card__icon--past\" />\n\t\t\t\t</div>\n\t\t\t\t<div class=\"cn-calendar-card__row-main\">\n\t\t\t\t\t<span class=\"cn-calendar-card__title\">{{ ev.summary || untitledLabel }}</span>\n\t\t\t\t\t<div class=\"cn-calendar-card__meta\">\n\t\t\t\t\t\t<span v-if=\"ev.dtstart\" class=\"cn-calendar-card__when\">{{ formatShort(ev.dtstart) }}</span>\n\t\t\t\t\t\t<span v-if=\"ev.location\" class=\"cn-calendar-card__location\">· {{ ev.location }}</span>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\n\t\t<!-- Footer: detail surface gets \"Open in NC Calendar\" CTA -->\n\t\t<template v-if=\"surface === 'detail-page'\" #footer>\n\t\t\t<a\n\t\t\t\tclass=\"cn-calendar-card__cta\"\n\t\t\t\t:href=\"calendarAppUrl\"\n\t\t\t\ttarget=\"_blank\"\n\t\t\t\trel=\"noopener\">\n\t\t\t\t{{ openInCalendarLabel }}\n\t\t\t</a>\n\t\t</template>\n\n\t\t<!-- Footer: dashboard surfaces get \"Show all\" when truncated -->\n\t\t<template v-else-if=\"allEvents.length > maxDisplay && surface !== 'single-entity'\" #footer>\n\t\t\t<button class=\"cn-calendar-card__show-all\" @click=\"$emit('show-all')\">\n\t\t\t\t{{ showAllLabel }} ({{ allEvents.length }})\n\t\t\t</button>\n\t\t</template>\n\t</CnDetailCard>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { generateUrl } from '@nextcloud/router'\nimport { NcLoadingIcon } from '@nextcloud/vue'\nimport { markRaw } from 'vue'\nimport Calendar from 'vue-material-design-icons/Calendar.vue'\nimport CalendarCheck from 'vue-material-design-icons/CalendarCheck.vue'\nimport CalendarClock from 'vue-material-design-icons/CalendarClock.vue'\nimport CnDetailCard from '../../../components/CnDetailCard/CnDetailCard.vue'\nimport { buildHeaders, prefixUrl } from '../../../utils/index.js'\n\n/**\n * CnCalendarCard — surface-aware compact widget for the calendar\n * integration. Renders linked VEVENTs across all four widget surfaces\n * (AD-19) with the same component.\n */\nexport default {\n\tname: 'CnCalendarCard',\n\n\tcomponents: { CnDetailCard, NcLoadingIcon, CalendarClock, CalendarCheck },\n\n\tprops: {\n\t\t/** OpenRegister register id (slug or uuid). Optional for user-dashboard. */\n\t\tregister: { type: String, default: '' },\n\t\t/** OpenRegister schema id (slug or uuid). Optional for user-dashboard. */\n\t\tschema: { type: String, default: '' },\n\t\t/** Parent object id. Optional for user-dashboard. */\n\t\tobjectId: { type: String, default: '' },\n\t\t/** Stable integration id (matches PHP-side provider id). */\n\t\tintegrationId: { type: String, default: 'calendar' },\n\t\t/**\n\t\t * Composite entity id \"calendarId/eventUri\" for the\n\t\t * single-entity surface. Ignored on other surfaces.\n\t\t */\n\t\tentityId: { type: String, default: '' },\n\t\t/** Rendering surface per ADR-019 AD-19. */\n\t\tsurface: {\n\t\t\ttype: String,\n\t\t\tdefault: 'detail-page',\n\t\t\tvalidator: (v) => ['user-dashboard', 'app-dashboard', 'detail-page', 'single-entity'].includes(v),\n\t\t},\n\n\t\t/** Base API URL for OpenRegister. */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\t\t/** Maximum rows on list surfaces (ignored on single-entity). */\n\t\tmaxDisplay: { type: Number, default: 5 },\n\t\t/** Whether the CnDetailCard collapses. */\n\t\tcollapsible: { type: Boolean, default: false },\n\t\t/** Override the card title (defaults to the translated label). */\n\t\ttitle: { type: String, default: '' },\n\n\t\t// --- Pre-translated labels (ADR-007) ---\n\t\t/** Empty-state label. */\n\t\tnoEventsLabel: { type: String, default: () => t('nextcloud-vue', 'No meetings') },\n\t\t/** Fallback when a VEVENT has no summary. */\n\t\tuntitledLabel: { type: String, default: () => t('nextcloud-vue', '(no title)') },\n\t\t/** detail-page footer CTA label. */\n\t\topenInCalendarLabel: { type: String, default: () => t('nextcloud-vue', 'Open in Calendar') },\n\t\t/** dashboard \"show all\" footer label. */\n\t\tshowAllLabel: { type: String, default: () => t('nextcloud-vue', 'Show all') },\n\t},\n\n\temits: ['show-all'],\n\n\tdata() {\n\t\treturn {\n\t\t\tCalendar: markRaw(Calendar),\n\t\t\tallEvents: [],\n\t\t\tloading: false,\n\t\t\terror: '',\n\t\t}\n\t},\n\n\tcomputed: {\n\t\tresolvedTitle() {\n\t\t\treturn this.title || t('nextcloud-vue', 'Meetings')\n\t\t},\n\n\t\tcalendarAppUrl() {\n\t\t\ttry {\n\t\t\t\treturn generateUrl('/apps/calendar')\n\t\t\t} catch {\n\t\t\t\treturn '/apps/calendar'\n\t\t\t}\n\t\t},\n\n\t\tdisplayedEvents() {\n\t\t\tif (this.surface === 'single-entity') {\n\t\t\t\treturn this.allEvents.slice(0, 1)\n\t\t\t}\n\t\t\t// list surfaces sort by dtstart ascending, then cap at maxDisplay.\n\t\t\tconst sorted = [...this.allEvents].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\t// On dashboard surfaces, surface upcoming meetings first.\n\t\t\tif (this.surface === 'user-dashboard' || this.surface === 'app-dashboard') {\n\t\t\t\tconst now = Date.now()\n\t\t\t\tconst upcoming = sorted.filter((ev) => {\n\t\t\t\t\tif (!ev.dtstart) {\n\t\t\t\t\t\treturn true\n\t\t\t\t\t}\n\t\t\t\t\treturn new Date(ev.dtstart).getTime() >= now\n\t\t\t\t})\n\t\t\t\treturn upcoming.slice(0, this.maxDisplay)\n\t\t\t}\n\t\t\treturn sorted.slice(0, this.maxDisplay)\n\t\t},\n\t},\n\n\twatch: {\n\t\tobjectId: { immediate: true, handler() {\n\t\t\tthis.refresh()\n\t\t} },\n\n\t\tentityId() {\n\t\t\tthis.refresh()\n\t\t},\n\n\t\tsurface() {\n\t\t\tthis.refresh()\n\t\t},\n\t},\n\n\tmethods: {\n\t\tbaseUrl() {\n\t\t\treturn `${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/integrations/${this.integrationId}`\n\t\t},\n\n\t\trowKey(ev) {\n\t\t\tif (ev.calendarId && ev.id) {\n\t\t\t\treturn `${ev.calendarId}/${ev.id}`\n\t\t\t}\n\t\t\treturn ev.id || ev.uid || ''\n\t\t},\n\n\t\tisUpcoming(ev) {\n\t\t\tif (!ev.dtstart) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\ttry {\n\t\t\t\treturn new Date(ev.dtstart).getTime() >= Date.now()\n\t\t\t} catch {\n\t\t\t\treturn true\n\t\t\t}\n\t\t},\n\n\t\trefresh() {\n\t\t\tif (this.surface === 'single-entity') {\n\t\t\t\tthis.fetchSingleEntity()\n\t\t\t} else {\n\t\t\t\tthis.fetchList()\n\t\t\t}\n\t\t},\n\n\t\tasync fetchList() {\n\t\t\tif (!this.register || !this.schema || !this.objectId) {\n\t\t\t\tthis.allEvents = []\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\tthis.error = ''\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(this.baseUrl()), { headers: buildHeaders() })\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tconst data = await response.json()\n\t\t\t\t\tthis.allEvents = data.results || data.items || (Array.isArray(data) ? data : []) || []\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.allEvents = []\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Calendar is currently unavailable.')\n\t\t\t\t} else {\n\t\t\t\t\tthis.allEvents = []\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('[CnCalendarCard] failed to fetch events', err)\n\t\t\t\tthis.allEvents = []\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\tasync fetchSingleEntity() {\n\t\t\tif (!this.entityId || !this.register || !this.schema || !this.objectId) {\n\t\t\t\tthis.allEvents = []\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\tthis.error = ''\n\t\t\ttry {\n\t\t\t\tconst url = `${this.baseUrl()}/${encodeURI(this.entityId)}`\n\t\t\t\tconst response = await fetch(prefixUrl(url), { 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.allEvents = data && (data.id || data.uid) ? [data] : []\n\t\t\t\t} else {\n\t\t\t\t\tthis.allEvents = []\n\t\t\t\t\tif (response.status !== 404) {\n\t\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load meeting.')\n\t\t\t\t\t}\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('[CnCalendarCard] failed to fetch single entity', err)\n\t\t\t\tthis.allEvents = []\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load meeting.')\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\tformatShort(value) {\n\t\t\tif (!value) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst d = new Date(value)\n\t\t\t\tif (Number.isNaN(d.getTime())) {\n\t\t\t\t\treturn String(value)\n\t\t\t\t}\n\t\t\t\tconst opts = this.surface === 'single-entity'\n\t\t\t\t\t? { dateStyle: 'medium' }\n\t\t\t\t\t: { dateStyle: 'medium', timeStyle: 'short' }\n\t\t\t\treturn d.toLocaleString(undefined, opts)\n\t\t\t} catch {\n\t\t\t\treturn String(value)\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-calendar-card__empty {\n\ttext-align: center;\n\tpadding: 16px 12px;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 13px;\n}\n\n.cn-calendar-card__error {\n\tcolor: var(--color-error);\n\tpadding: 8px 0;\n\tfont-size: 13px;\n}\n\n.cn-calendar-card__list {\n\tdisplay: flex;\n\tflex-direction: column;\n}\n\n.cn-calendar-card__row {\n\tdisplay: flex;\n\talign-items: flex-start;\n\tgap: 10px;\n\tpadding: 8px 0;\n\tborder-bottom: 1px solid var(--color-border);\n}\n\n.cn-calendar-card__row:last-child {\n\tborder-bottom: none;\n}\n\n.cn-calendar-card__row-icon {\n\tflex-shrink: 0;\n\tpadding-top: 1px;\n}\n\n.cn-calendar-card__icon--upcoming { color: var(--color-primary-element); }\n\n.cn-calendar-card__icon--past { color: var(--color-text-maxcontrast); }\n\n.cn-calendar-card__row-main {\n\tflex: 1;\n\tmin-width: 0;\n}\n\n.cn-calendar-card__title {\n\tdisplay: block;\n\tfont-size: 13px;\n\tfont-weight: 500;\n\twhite-space: nowrap;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n.cn-calendar-card__meta {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 6px;\n\tmargin-top: 2px;\n\tfont-size: 12px;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-calendar-card__when {\n\twhite-space: nowrap;\n}\n\n.cn-calendar-card__location {\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-calendar-card__chip {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 6px;\n\tpadding: 4px 10px;\n\tborder-radius: var(--border-radius-pill, 999px);\n\tbackground: var(--color-background-hover);\n\tfont-size: 13px;\n\tmax-width: 100%;\n}\n\n.cn-calendar-card__chip-icon {\n\tcolor: var(--color-primary-element);\n\tflex-shrink: 0;\n}\n\n.cn-calendar-card__chip-title {\n\tfont-weight: 500;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-calendar-card__chip-when {\n\tcolor: var(--color-text-maxcontrast);\n\twhite-space: nowrap;\n}\n\n.cn-calendar-card__chip-status {\n\tfont-size: 11px;\n\ttext-transform: uppercase;\n\tletter-spacing: 0.04em;\n\tpadding: 1px 6px;\n\tborder-radius: var(--border-radius-pill, 999px);\n\tbackground: var(--color-background-darker);\n}\n\n.cn-calendar-card__chip-status--confirmed { background: var(--color-success); color: var(--color-main-background); }\n\n.cn-calendar-card__chip-status--tentative { background: var(--color-warning, #e9a40f); color: var(--color-main-background); }\n\n.cn-calendar-card__chip-status--cancelled { background: var(--color-error); color: var(--color-main-background); }\n\n.cn-calendar-card__cta,\n.cn-calendar-card__show-all {\n\tbackground: none;\n\tborder: none;\n\tcolor: var(--color-primary-element);\n\tfont-size: 13px;\n\tfont-weight: 500;\n\tcursor: pointer;\n\tpadding: 0;\n\twidth: 100%;\n\ttext-align: center;\n\ttext-decoration: none;\n\tdisplay: inline-block;\n}\n\n.cn-calendar-card__cta:hover,\n.cn-calendar-card__show-all:hover {\n\ttext-decoration: underline;\n}\n</style>\n"],"names":["CnDetailCard","t"],"mappings":"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"}
1
+ {"version":3,"file":"CnCalendarCard.vue2.js","sources":["../../../../../src/integrations/builtin/calendar/CnCalendarCard.vue"],"sourcesContent":["<!--\n CnCalendarCard — bespoke widget for the `calendar` integration.\n\n Surface-aware (AD-19) widget that renders linked VEVENTs across the\n four pluggable-integration-registry surfaces with one component:\n\n - `user-dashboard` : compact list, \"next 5 upcoming meetings\" across\n the user's objects (one row per VEVENT). Falls\n back to a list scoped by the optional\n register/schema/objectId when given (registry\n dashboards pass the full triple) so it still\n works without a global \"across all my objects\"\n query — see AD-22.\n - `app-dashboard` : same compact list scoped to the current app's\n register/schema/objectId, when given.\n - `detail-page` : full per-object meeting list with a quick\n \"Open in Calendar\" CTA — the inline create\n form lives in CnCalendarTab, not here, to keep\n the detail card compact (design.md AD-3).\n - `single-entity` : a single VEVENT rendered as a chip with date +\n summary + status icon. Accepts `entityId`\n (composite \"calendarId/eventUri\") for the\n referenceType auto-rendering path.\n\n Loading / empty / error states follow ADR-017 component composition.\n All strings pass through t('nextcloud-vue', ...) for nl + en\n translation (ADR-007). Theming uses Nextcloud CSS variables only so\n the nldesign app's overrides apply transparently (ADR-010).\n-->\n<template>\n\t<CnDetailCard :title=\"resolvedTitle\" :icon=\"Calendar\" :collapsible=\"collapsible\">\n\t\t<!-- Loading -->\n\t\t<NcLoadingIcon v-if=\"loading\" />\n\n\t\t<!-- Error -->\n\t\t<div v-else-if=\"error\" class=\"cn-calendar-card__error\" role=\"alert\">\n\t\t\t{{ error }}\n\t\t</div>\n\n\t\t<!-- Empty -->\n\t\t<div v-else-if=\"displayedEvents.length === 0\" class=\"cn-calendar-card__empty\">\n\t\t\t{{ noEventsLabel }}\n\t\t</div>\n\n\t\t<!-- single-entity surface: one compact chip -->\n\t\t<div v-else-if=\"surface === 'single-entity'\" class=\"cn-calendar-card__chip\">\n\t\t\t<CalendarClock :size=\"18\" class=\"cn-calendar-card__chip-icon\" />\n\t\t\t<span class=\"cn-calendar-card__chip-title\">{{ displayedEvents[0].summary || untitledLabel }}</span>\n\t\t\t<span v-if=\"displayedEvents[0].dtstart\" class=\"cn-calendar-card__chip-when\">\n\t\t\t\t{{ formatShort(displayedEvents[0].dtstart) }}\n\t\t\t</span>\n\t\t\t<span\n\t\t\t\tv-if=\"displayedEvents[0].status\"\n\t\t\t\tclass=\"cn-calendar-card__chip-status\"\n\t\t\t\t:class=\"`cn-calendar-card__chip-status--${displayedEvents[0].status}`\">\n\t\t\t\t{{ displayedEvents[0].status }}\n\t\t\t</span>\n\t\t</div>\n\n\t\t<!-- list surfaces: user/app/detail share the row template -->\n\t\t<div v-else class=\"cn-calendar-card__list\">\n\t\t\t<div\n\t\t\t\tv-for=\"ev in displayedEvents\"\n\t\t\t\t:key=\"rowKey(ev)\"\n\t\t\t\tclass=\"cn-calendar-card__row\">\n\t\t\t\t<div class=\"cn-calendar-card__row-icon\">\n\t\t\t\t\t<CalendarClock v-if=\"isUpcoming(ev)\" :size=\"20\" class=\"cn-calendar-card__icon--upcoming\" />\n\t\t\t\t\t<CalendarCheck v-else :size=\"20\" class=\"cn-calendar-card__icon--past\" />\n\t\t\t\t</div>\n\t\t\t\t<div class=\"cn-calendar-card__row-main\">\n\t\t\t\t\t<span class=\"cn-calendar-card__title\">{{ ev.summary || untitledLabel }}</span>\n\t\t\t\t\t<div class=\"cn-calendar-card__meta\">\n\t\t\t\t\t\t<span v-if=\"ev.dtstart\" class=\"cn-calendar-card__when\">{{ formatShort(ev.dtstart) }}</span>\n\t\t\t\t\t\t<span v-if=\"ev.location\" class=\"cn-calendar-card__location\">· {{ ev.location }}</span>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\n\t\t<!-- Footer: detail surface gets \"Open in NC Calendar\" CTA -->\n\t\t<template v-if=\"surface === 'detail-page'\" #footer>\n\t\t\t<a\n\t\t\t\tclass=\"cn-calendar-card__cta\"\n\t\t\t\t:href=\"calendarAppUrl\"\n\t\t\t\ttarget=\"_blank\"\n\t\t\t\trel=\"noopener\">\n\t\t\t\t{{ openInCalendarLabel }}\n\t\t\t</a>\n\t\t</template>\n\n\t\t<!-- Footer: dashboard surfaces get \"Show all\" when truncated -->\n\t\t<template v-else-if=\"allEvents.length > maxDisplay && surface !== 'single-entity'\" #footer>\n\t\t\t<button class=\"cn-calendar-card__show-all\" @click=\"$emit('show-all')\">\n\t\t\t\t{{ showAllLabel }} ({{ allEvents.length }})\n\t\t\t</button>\n\t\t</template>\n\t</CnDetailCard>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { generateUrl } from '@nextcloud/router'\nimport { NcLoadingIcon } from '@nextcloud/vue'\nimport { markRaw } from 'vue'\nimport Calendar from 'vue-material-design-icons/Calendar.vue'\nimport CalendarCheck from 'vue-material-design-icons/CalendarCheck.vue'\nimport CalendarClock from 'vue-material-design-icons/CalendarClock.vue'\nimport CnDetailCard from '../../../components/CnDetailCard/CnDetailCard.vue'\nimport { buildHeaders, prefixUrl } from '../../../utils/index.js'\nimport { userLocale } from '../../../utils/userLocale.js'\n\n/**\n * CnCalendarCard — surface-aware compact widget for the calendar\n * integration. Renders linked VEVENTs across all four widget surfaces\n * (AD-19) with the same component.\n */\nexport default {\n\tname: 'CnCalendarCard',\n\n\tcomponents: { CnDetailCard, NcLoadingIcon, CalendarClock, CalendarCheck },\n\n\tprops: {\n\t\t/** OpenRegister register id (slug or uuid). Optional for user-dashboard. */\n\t\tregister: { type: String, default: '' },\n\t\t/** OpenRegister schema id (slug or uuid). Optional for user-dashboard. */\n\t\tschema: { type: String, default: '' },\n\t\t/** Parent object id. Optional for user-dashboard. */\n\t\tobjectId: { type: String, default: '' },\n\t\t/** Stable integration id (matches PHP-side provider id). */\n\t\tintegrationId: { type: String, default: 'calendar' },\n\t\t/**\n\t\t * Composite entity id \"calendarId/eventUri\" for the\n\t\t * single-entity surface. Ignored on other surfaces.\n\t\t */\n\t\tentityId: { type: String, default: '' },\n\t\t/** Rendering surface per ADR-019 AD-19. */\n\t\tsurface: {\n\t\t\ttype: String,\n\t\t\tdefault: 'detail-page',\n\t\t\tvalidator: (v) => ['user-dashboard', 'app-dashboard', 'detail-page', 'single-entity'].includes(v),\n\t\t},\n\n\t\t/** Base API URL for OpenRegister. */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\t\t/** Maximum rows on list surfaces (ignored on single-entity). */\n\t\tmaxDisplay: { type: Number, default: 5 },\n\t\t/** Whether the CnDetailCard collapses. */\n\t\tcollapsible: { type: Boolean, default: false },\n\t\t/** Override the card title (defaults to the translated label). */\n\t\ttitle: { type: String, default: '' },\n\n\t\t// --- Pre-translated labels (ADR-007) ---\n\t\t/** Empty-state label. */\n\t\tnoEventsLabel: { type: String, default: () => t('nextcloud-vue', 'No meetings') },\n\t\t/** Fallback when a VEVENT has no summary. */\n\t\tuntitledLabel: { type: String, default: () => t('nextcloud-vue', '(no title)') },\n\t\t/** detail-page footer CTA label. */\n\t\topenInCalendarLabel: { type: String, default: () => t('nextcloud-vue', 'Open in Calendar') },\n\t\t/** dashboard \"show all\" footer label. */\n\t\tshowAllLabel: { type: String, default: () => t('nextcloud-vue', 'Show all') },\n\t},\n\n\temits: ['show-all'],\n\n\tdata() {\n\t\treturn {\n\t\t\tCalendar: markRaw(Calendar),\n\t\t\tallEvents: [],\n\t\t\tloading: false,\n\t\t\terror: '',\n\t\t}\n\t},\n\n\tcomputed: {\n\t\tresolvedTitle() {\n\t\t\treturn this.title || t('nextcloud-vue', 'Meetings')\n\t\t},\n\n\t\tcalendarAppUrl() {\n\t\t\ttry {\n\t\t\t\treturn generateUrl('/apps/calendar')\n\t\t\t} catch {\n\t\t\t\treturn '/apps/calendar'\n\t\t\t}\n\t\t},\n\n\t\tdisplayedEvents() {\n\t\t\tif (this.surface === 'single-entity') {\n\t\t\t\treturn this.allEvents.slice(0, 1)\n\t\t\t}\n\t\t\t// list surfaces sort by dtstart ascending, then cap at maxDisplay.\n\t\t\tconst sorted = [...this.allEvents].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\t// On dashboard surfaces, surface upcoming meetings first.\n\t\t\tif (this.surface === 'user-dashboard' || this.surface === 'app-dashboard') {\n\t\t\t\tconst now = Date.now()\n\t\t\t\tconst upcoming = sorted.filter((ev) => {\n\t\t\t\t\tif (!ev.dtstart) {\n\t\t\t\t\t\treturn true\n\t\t\t\t\t}\n\t\t\t\t\treturn new Date(ev.dtstart).getTime() >= now\n\t\t\t\t})\n\t\t\t\treturn upcoming.slice(0, this.maxDisplay)\n\t\t\t}\n\t\t\treturn sorted.slice(0, this.maxDisplay)\n\t\t},\n\t},\n\n\twatch: {\n\t\tobjectId: { immediate: true, handler() {\n\t\t\tthis.refresh()\n\t\t} },\n\n\t\tentityId() {\n\t\t\tthis.refresh()\n\t\t},\n\n\t\tsurface() {\n\t\t\tthis.refresh()\n\t\t},\n\t},\n\n\tmethods: {\n\t\tbaseUrl() {\n\t\t\treturn `${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/integrations/${this.integrationId}`\n\t\t},\n\n\t\trowKey(ev) {\n\t\t\tif (ev.calendarId && ev.id) {\n\t\t\t\treturn `${ev.calendarId}/${ev.id}`\n\t\t\t}\n\t\t\treturn ev.id || ev.uid || ''\n\t\t},\n\n\t\tisUpcoming(ev) {\n\t\t\tif (!ev.dtstart) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\ttry {\n\t\t\t\treturn new Date(ev.dtstart).getTime() >= Date.now()\n\t\t\t} catch {\n\t\t\t\treturn true\n\t\t\t}\n\t\t},\n\n\t\trefresh() {\n\t\t\tif (this.surface === 'single-entity') {\n\t\t\t\tthis.fetchSingleEntity()\n\t\t\t} else {\n\t\t\t\tthis.fetchList()\n\t\t\t}\n\t\t},\n\n\t\tasync fetchList() {\n\t\t\tif (!this.register || !this.schema || !this.objectId) {\n\t\t\t\tthis.allEvents = []\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\tthis.error = ''\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(this.baseUrl()), { headers: buildHeaders() })\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tconst data = await response.json()\n\t\t\t\t\tthis.allEvents = data.results || data.items || (Array.isArray(data) ? data : []) || []\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.allEvents = []\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Calendar is currently unavailable.')\n\t\t\t\t} else {\n\t\t\t\t\tthis.allEvents = []\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('[CnCalendarCard] failed to fetch events', err)\n\t\t\t\tthis.allEvents = []\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\tasync fetchSingleEntity() {\n\t\t\tif (!this.entityId || !this.register || !this.schema || !this.objectId) {\n\t\t\t\tthis.allEvents = []\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\tthis.error = ''\n\t\t\ttry {\n\t\t\t\tconst url = `${this.baseUrl()}/${encodeURI(this.entityId)}`\n\t\t\t\tconst response = await fetch(prefixUrl(url), { 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.allEvents = data && (data.id || data.uid) ? [data] : []\n\t\t\t\t} else {\n\t\t\t\t\tthis.allEvents = []\n\t\t\t\t\tif (response.status !== 404) {\n\t\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load meeting.')\n\t\t\t\t\t}\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('[CnCalendarCard] failed to fetch single entity', err)\n\t\t\t\tthis.allEvents = []\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load meeting.')\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\tformatShort(value) {\n\t\t\tif (!value) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst d = new Date(value)\n\t\t\t\tif (Number.isNaN(d.getTime())) {\n\t\t\t\t\treturn String(value)\n\t\t\t\t}\n\t\t\t\tconst opts = this.surface === 'single-entity'\n\t\t\t\t\t? { dateStyle: 'medium' }\n\t\t\t\t\t: { dateStyle: 'medium', timeStyle: 'short' }\n\t\t\t\treturn d.toLocaleString(userLocale(), opts)\n\t\t\t} catch {\n\t\t\t\treturn String(value)\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-calendar-card__empty {\n\ttext-align: center;\n\tpadding: 16px 12px;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 13px;\n}\n\n.cn-calendar-card__error {\n\tcolor: var(--color-error);\n\tpadding: 8px 0;\n\tfont-size: 13px;\n}\n\n.cn-calendar-card__list {\n\tdisplay: flex;\n\tflex-direction: column;\n}\n\n.cn-calendar-card__row {\n\tdisplay: flex;\n\talign-items: flex-start;\n\tgap: 10px;\n\tpadding: 8px 0;\n\tborder-bottom: 1px solid var(--color-border);\n}\n\n.cn-calendar-card__row:last-child {\n\tborder-bottom: none;\n}\n\n.cn-calendar-card__row-icon {\n\tflex-shrink: 0;\n\tpadding-top: 1px;\n}\n\n.cn-calendar-card__icon--upcoming { color: var(--color-primary-element); }\n\n.cn-calendar-card__icon--past { color: var(--color-text-maxcontrast); }\n\n.cn-calendar-card__row-main {\n\tflex: 1;\n\tmin-width: 0;\n}\n\n.cn-calendar-card__title {\n\tdisplay: block;\n\tfont-size: 13px;\n\tfont-weight: 500;\n\twhite-space: nowrap;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n.cn-calendar-card__meta {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 6px;\n\tmargin-top: 2px;\n\tfont-size: 12px;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-calendar-card__when {\n\twhite-space: nowrap;\n}\n\n.cn-calendar-card__location {\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-calendar-card__chip {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 6px;\n\tpadding: 4px 10px;\n\tborder-radius: var(--border-radius-pill, 999px);\n\tbackground: var(--color-background-hover);\n\tfont-size: 13px;\n\tmax-width: 100%;\n}\n\n.cn-calendar-card__chip-icon {\n\tcolor: var(--color-primary-element);\n\tflex-shrink: 0;\n}\n\n.cn-calendar-card__chip-title {\n\tfont-weight: 500;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-calendar-card__chip-when {\n\tcolor: var(--color-text-maxcontrast);\n\twhite-space: nowrap;\n}\n\n.cn-calendar-card__chip-status {\n\tfont-size: 11px;\n\ttext-transform: uppercase;\n\tletter-spacing: 0.04em;\n\tpadding: 1px 6px;\n\tborder-radius: var(--border-radius-pill, 999px);\n\tbackground: var(--color-background-darker);\n}\n\n.cn-calendar-card__chip-status--confirmed { background: var(--color-success); color: var(--color-main-background); }\n\n.cn-calendar-card__chip-status--tentative { background: var(--color-warning, #e9a40f); color: var(--color-main-background); }\n\n.cn-calendar-card__chip-status--cancelled { background: var(--color-error); color: var(--color-main-background); }\n\n.cn-calendar-card__cta,\n.cn-calendar-card__show-all {\n\tbackground: none;\n\tborder: none;\n\tcolor: var(--color-primary-element);\n\tfont-size: 13px;\n\tfont-weight: 500;\n\tcursor: pointer;\n\tpadding: 0;\n\twidth: 100%;\n\ttext-align: center;\n\ttext-decoration: none;\n\tdisplay: inline-block;\n}\n\n.cn-calendar-card__cta:hover,\n.cn-calendar-card__show-all:hover {\n\ttext-decoration: underline;\n}\n</style>\n"],"names":["CnDetailCard","t"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"CnCalendarCard.vue3.js","sources":["../../../../../src/integrations/builtin/calendar/CnCalendarCard.vue?vue&type=template&id=3c1b2494&lang.js"],"sourcesContent":["<!--\n CnCalendarCard — bespoke widget for the `calendar` integration.\n\n Surface-aware (AD-19) widget that renders linked VEVENTs across the\n four pluggable-integration-registry surfaces with one component:\n\n - `user-dashboard` : compact list, \"next 5 upcoming meetings\" across\n the user's objects (one row per VEVENT). Falls\n back to a list scoped by the optional\n register/schema/objectId when given (registry\n dashboards pass the full triple) so it still\n works without a global \"across all my objects\"\n query — see AD-22.\n - `app-dashboard` : same compact list scoped to the current app's\n register/schema/objectId, when given.\n - `detail-page` : full per-object meeting list with a quick\n \"Open in Calendar\" CTA — the inline create\n form lives in CnCalendarTab, not here, to keep\n the detail card compact (design.md AD-3).\n - `single-entity` : a single VEVENT rendered as a chip with date +\n summary + status icon. Accepts `entityId`\n (composite \"calendarId/eventUri\") for the\n referenceType auto-rendering path.\n\n Loading / empty / error states follow ADR-017 component composition.\n All strings pass through t('nextcloud-vue', ...) for nl + en\n translation (ADR-007). Theming uses Nextcloud CSS variables only so\n the nldesign app's overrides apply transparently (ADR-010).\n-->\n<template>\n\t<CnDetailCard :title=\"resolvedTitle\" :icon=\"Calendar\" :collapsible=\"collapsible\">\n\t\t<!-- Loading -->\n\t\t<NcLoadingIcon v-if=\"loading\" />\n\n\t\t<!-- Error -->\n\t\t<div v-else-if=\"error\" class=\"cn-calendar-card__error\" role=\"alert\">\n\t\t\t{{ error }}\n\t\t</div>\n\n\t\t<!-- Empty -->\n\t\t<div v-else-if=\"displayedEvents.length === 0\" class=\"cn-calendar-card__empty\">\n\t\t\t{{ noEventsLabel }}\n\t\t</div>\n\n\t\t<!-- single-entity surface: one compact chip -->\n\t\t<div v-else-if=\"surface === 'single-entity'\" class=\"cn-calendar-card__chip\">\n\t\t\t<CalendarClock :size=\"18\" class=\"cn-calendar-card__chip-icon\" />\n\t\t\t<span class=\"cn-calendar-card__chip-title\">{{ displayedEvents[0].summary || untitledLabel }}</span>\n\t\t\t<span v-if=\"displayedEvents[0].dtstart\" class=\"cn-calendar-card__chip-when\">\n\t\t\t\t{{ formatShort(displayedEvents[0].dtstart) }}\n\t\t\t</span>\n\t\t\t<span\n\t\t\t\tv-if=\"displayedEvents[0].status\"\n\t\t\t\tclass=\"cn-calendar-card__chip-status\"\n\t\t\t\t:class=\"`cn-calendar-card__chip-status--${displayedEvents[0].status}`\">\n\t\t\t\t{{ displayedEvents[0].status }}\n\t\t\t</span>\n\t\t</div>\n\n\t\t<!-- list surfaces: user/app/detail share the row template -->\n\t\t<div v-else class=\"cn-calendar-card__list\">\n\t\t\t<div\n\t\t\t\tv-for=\"ev in displayedEvents\"\n\t\t\t\t:key=\"rowKey(ev)\"\n\t\t\t\tclass=\"cn-calendar-card__row\">\n\t\t\t\t<div class=\"cn-calendar-card__row-icon\">\n\t\t\t\t\t<CalendarClock v-if=\"isUpcoming(ev)\" :size=\"20\" class=\"cn-calendar-card__icon--upcoming\" />\n\t\t\t\t\t<CalendarCheck v-else :size=\"20\" class=\"cn-calendar-card__icon--past\" />\n\t\t\t\t</div>\n\t\t\t\t<div class=\"cn-calendar-card__row-main\">\n\t\t\t\t\t<span class=\"cn-calendar-card__title\">{{ ev.summary || untitledLabel }}</span>\n\t\t\t\t\t<div class=\"cn-calendar-card__meta\">\n\t\t\t\t\t\t<span v-if=\"ev.dtstart\" class=\"cn-calendar-card__when\">{{ formatShort(ev.dtstart) }}</span>\n\t\t\t\t\t\t<span v-if=\"ev.location\" class=\"cn-calendar-card__location\">· {{ ev.location }}</span>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\n\t\t<!-- Footer: detail surface gets \"Open in NC Calendar\" CTA -->\n\t\t<template v-if=\"surface === 'detail-page'\" #footer>\n\t\t\t<a\n\t\t\t\tclass=\"cn-calendar-card__cta\"\n\t\t\t\t:href=\"calendarAppUrl\"\n\t\t\t\ttarget=\"_blank\"\n\t\t\t\trel=\"noopener\">\n\t\t\t\t{{ openInCalendarLabel }}\n\t\t\t</a>\n\t\t</template>\n\n\t\t<!-- Footer: dashboard surfaces get \"Show all\" when truncated -->\n\t\t<template v-else-if=\"allEvents.length > maxDisplay && surface !== 'single-entity'\" #footer>\n\t\t\t<button class=\"cn-calendar-card__show-all\" @click=\"$emit('show-all')\">\n\t\t\t\t{{ showAllLabel }} ({{ allEvents.length }})\n\t\t\t</button>\n\t\t</template>\n\t</CnDetailCard>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { generateUrl } from '@nextcloud/router'\nimport { NcLoadingIcon } from '@nextcloud/vue'\nimport { markRaw } from 'vue'\nimport Calendar from 'vue-material-design-icons/Calendar.vue'\nimport CalendarCheck from 'vue-material-design-icons/CalendarCheck.vue'\nimport CalendarClock from 'vue-material-design-icons/CalendarClock.vue'\nimport CnDetailCard from '../../../components/CnDetailCard/CnDetailCard.vue'\nimport { buildHeaders, prefixUrl } from '../../../utils/index.js'\n\n/**\n * CnCalendarCard — surface-aware compact widget for the calendar\n * integration. Renders linked VEVENTs across all four widget surfaces\n * (AD-19) with the same component.\n */\nexport default {\n\tname: 'CnCalendarCard',\n\n\tcomponents: { CnDetailCard, NcLoadingIcon, CalendarClock, CalendarCheck },\n\n\tprops: {\n\t\t/** OpenRegister register id (slug or uuid). Optional for user-dashboard. */\n\t\tregister: { type: String, default: '' },\n\t\t/** OpenRegister schema id (slug or uuid). Optional for user-dashboard. */\n\t\tschema: { type: String, default: '' },\n\t\t/** Parent object id. Optional for user-dashboard. */\n\t\tobjectId: { type: String, default: '' },\n\t\t/** Stable integration id (matches PHP-side provider id). */\n\t\tintegrationId: { type: String, default: 'calendar' },\n\t\t/**\n\t\t * Composite entity id \"calendarId/eventUri\" for the\n\t\t * single-entity surface. Ignored on other surfaces.\n\t\t */\n\t\tentityId: { type: String, default: '' },\n\t\t/** Rendering surface per ADR-019 AD-19. */\n\t\tsurface: {\n\t\t\ttype: String,\n\t\t\tdefault: 'detail-page',\n\t\t\tvalidator: (v) => ['user-dashboard', 'app-dashboard', 'detail-page', 'single-entity'].includes(v),\n\t\t},\n\n\t\t/** Base API URL for OpenRegister. */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\t\t/** Maximum rows on list surfaces (ignored on single-entity). */\n\t\tmaxDisplay: { type: Number, default: 5 },\n\t\t/** Whether the CnDetailCard collapses. */\n\t\tcollapsible: { type: Boolean, default: false },\n\t\t/** Override the card title (defaults to the translated label). */\n\t\ttitle: { type: String, default: '' },\n\n\t\t// --- Pre-translated labels (ADR-007) ---\n\t\t/** Empty-state label. */\n\t\tnoEventsLabel: { type: String, default: () => t('nextcloud-vue', 'No meetings') },\n\t\t/** Fallback when a VEVENT has no summary. */\n\t\tuntitledLabel: { type: String, default: () => t('nextcloud-vue', '(no title)') },\n\t\t/** detail-page footer CTA label. */\n\t\topenInCalendarLabel: { type: String, default: () => t('nextcloud-vue', 'Open in Calendar') },\n\t\t/** dashboard \"show all\" footer label. */\n\t\tshowAllLabel: { type: String, default: () => t('nextcloud-vue', 'Show all') },\n\t},\n\n\temits: ['show-all'],\n\n\tdata() {\n\t\treturn {\n\t\t\tCalendar: markRaw(Calendar),\n\t\t\tallEvents: [],\n\t\t\tloading: false,\n\t\t\terror: '',\n\t\t}\n\t},\n\n\tcomputed: {\n\t\tresolvedTitle() {\n\t\t\treturn this.title || t('nextcloud-vue', 'Meetings')\n\t\t},\n\n\t\tcalendarAppUrl() {\n\t\t\ttry {\n\t\t\t\treturn generateUrl('/apps/calendar')\n\t\t\t} catch {\n\t\t\t\treturn '/apps/calendar'\n\t\t\t}\n\t\t},\n\n\t\tdisplayedEvents() {\n\t\t\tif (this.surface === 'single-entity') {\n\t\t\t\treturn this.allEvents.slice(0, 1)\n\t\t\t}\n\t\t\t// list surfaces sort by dtstart ascending, then cap at maxDisplay.\n\t\t\tconst sorted = [...this.allEvents].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\t// On dashboard surfaces, surface upcoming meetings first.\n\t\t\tif (this.surface === 'user-dashboard' || this.surface === 'app-dashboard') {\n\t\t\t\tconst now = Date.now()\n\t\t\t\tconst upcoming = sorted.filter((ev) => {\n\t\t\t\t\tif (!ev.dtstart) {\n\t\t\t\t\t\treturn true\n\t\t\t\t\t}\n\t\t\t\t\treturn new Date(ev.dtstart).getTime() >= now\n\t\t\t\t})\n\t\t\t\treturn upcoming.slice(0, this.maxDisplay)\n\t\t\t}\n\t\t\treturn sorted.slice(0, this.maxDisplay)\n\t\t},\n\t},\n\n\twatch: {\n\t\tobjectId: { immediate: true, handler() {\n\t\t\tthis.refresh()\n\t\t} },\n\n\t\tentityId() {\n\t\t\tthis.refresh()\n\t\t},\n\n\t\tsurface() {\n\t\t\tthis.refresh()\n\t\t},\n\t},\n\n\tmethods: {\n\t\tbaseUrl() {\n\t\t\treturn `${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/integrations/${this.integrationId}`\n\t\t},\n\n\t\trowKey(ev) {\n\t\t\tif (ev.calendarId && ev.id) {\n\t\t\t\treturn `${ev.calendarId}/${ev.id}`\n\t\t\t}\n\t\t\treturn ev.id || ev.uid || ''\n\t\t},\n\n\t\tisUpcoming(ev) {\n\t\t\tif (!ev.dtstart) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\ttry {\n\t\t\t\treturn new Date(ev.dtstart).getTime() >= Date.now()\n\t\t\t} catch {\n\t\t\t\treturn true\n\t\t\t}\n\t\t},\n\n\t\trefresh() {\n\t\t\tif (this.surface === 'single-entity') {\n\t\t\t\tthis.fetchSingleEntity()\n\t\t\t} else {\n\t\t\t\tthis.fetchList()\n\t\t\t}\n\t\t},\n\n\t\tasync fetchList() {\n\t\t\tif (!this.register || !this.schema || !this.objectId) {\n\t\t\t\tthis.allEvents = []\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\tthis.error = ''\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(this.baseUrl()), { headers: buildHeaders() })\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tconst data = await response.json()\n\t\t\t\t\tthis.allEvents = data.results || data.items || (Array.isArray(data) ? data : []) || []\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.allEvents = []\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Calendar is currently unavailable.')\n\t\t\t\t} else {\n\t\t\t\t\tthis.allEvents = []\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('[CnCalendarCard] failed to fetch events', err)\n\t\t\t\tthis.allEvents = []\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\tasync fetchSingleEntity() {\n\t\t\tif (!this.entityId || !this.register || !this.schema || !this.objectId) {\n\t\t\t\tthis.allEvents = []\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\tthis.error = ''\n\t\t\ttry {\n\t\t\t\tconst url = `${this.baseUrl()}/${encodeURI(this.entityId)}`\n\t\t\t\tconst response = await fetch(prefixUrl(url), { 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.allEvents = data && (data.id || data.uid) ? [data] : []\n\t\t\t\t} else {\n\t\t\t\t\tthis.allEvents = []\n\t\t\t\t\tif (response.status !== 404) {\n\t\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load meeting.')\n\t\t\t\t\t}\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('[CnCalendarCard] failed to fetch single entity', err)\n\t\t\t\tthis.allEvents = []\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load meeting.')\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\tformatShort(value) {\n\t\t\tif (!value) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst d = new Date(value)\n\t\t\t\tif (Number.isNaN(d.getTime())) {\n\t\t\t\t\treturn String(value)\n\t\t\t\t}\n\t\t\t\tconst opts = this.surface === 'single-entity'\n\t\t\t\t\t? { dateStyle: 'medium' }\n\t\t\t\t\t: { dateStyle: 'medium', timeStyle: 'short' }\n\t\t\t\treturn d.toLocaleString(undefined, opts)\n\t\t\t} catch {\n\t\t\t\treturn String(value)\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-calendar-card__empty {\n\ttext-align: center;\n\tpadding: 16px 12px;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 13px;\n}\n\n.cn-calendar-card__error {\n\tcolor: var(--color-error);\n\tpadding: 8px 0;\n\tfont-size: 13px;\n}\n\n.cn-calendar-card__list {\n\tdisplay: flex;\n\tflex-direction: column;\n}\n\n.cn-calendar-card__row {\n\tdisplay: flex;\n\talign-items: flex-start;\n\tgap: 10px;\n\tpadding: 8px 0;\n\tborder-bottom: 1px solid var(--color-border);\n}\n\n.cn-calendar-card__row:last-child {\n\tborder-bottom: none;\n}\n\n.cn-calendar-card__row-icon {\n\tflex-shrink: 0;\n\tpadding-top: 1px;\n}\n\n.cn-calendar-card__icon--upcoming { color: var(--color-primary-element); }\n\n.cn-calendar-card__icon--past { color: var(--color-text-maxcontrast); }\n\n.cn-calendar-card__row-main {\n\tflex: 1;\n\tmin-width: 0;\n}\n\n.cn-calendar-card__title {\n\tdisplay: block;\n\tfont-size: 13px;\n\tfont-weight: 500;\n\twhite-space: nowrap;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n.cn-calendar-card__meta {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 6px;\n\tmargin-top: 2px;\n\tfont-size: 12px;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-calendar-card__when {\n\twhite-space: nowrap;\n}\n\n.cn-calendar-card__location {\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-calendar-card__chip {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 6px;\n\tpadding: 4px 10px;\n\tborder-radius: var(--border-radius-pill, 999px);\n\tbackground: var(--color-background-hover);\n\tfont-size: 13px;\n\tmax-width: 100%;\n}\n\n.cn-calendar-card__chip-icon {\n\tcolor: var(--color-primary-element);\n\tflex-shrink: 0;\n}\n\n.cn-calendar-card__chip-title {\n\tfont-weight: 500;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-calendar-card__chip-when {\n\tcolor: var(--color-text-maxcontrast);\n\twhite-space: nowrap;\n}\n\n.cn-calendar-card__chip-status {\n\tfont-size: 11px;\n\ttext-transform: uppercase;\n\tletter-spacing: 0.04em;\n\tpadding: 1px 6px;\n\tborder-radius: var(--border-radius-pill, 999px);\n\tbackground: var(--color-background-darker);\n}\n\n.cn-calendar-card__chip-status--confirmed { background: var(--color-success); color: var(--color-main-background); }\n\n.cn-calendar-card__chip-status--tentative { background: var(--color-warning, #e9a40f); color: var(--color-main-background); }\n\n.cn-calendar-card__chip-status--cancelled { background: var(--color-error); color: var(--color-main-background); }\n\n.cn-calendar-card__cta,\n.cn-calendar-card__show-all {\n\tbackground: none;\n\tborder: none;\n\tcolor: var(--color-primary-element);\n\tfont-size: 13px;\n\tfont-weight: 500;\n\tcursor: pointer;\n\tpadding: 0;\n\twidth: 100%;\n\ttext-align: center;\n\ttext-decoration: none;\n\tdisplay: inline-block;\n}\n\n.cn-calendar-card__cta:hover,\n.cn-calendar-card__show-all:hover {\n\ttext-decoration: underline;\n}\n</style>\n"],"names":["_createBlock","_toDisplayString","_createElementBlock","_createElementVNode","_openBlock","_normalizeClass"],"mappings":";;;;AAgEyB,EAAA,KAAK,EAAC,yBAAE;AAAA,EAAA,IAAA,EAAA,OAAA;;;;AAKU,EAAA,KAAA,EAAA,yBAAA;;;;AAKjC,EAAA,KAAA,EAAA,wBAAA;;AAEF,MAAA,UAAA,GAAA,EAAA,KAAA,EAAA,8BAAA,GAAA;;;AACD,EAAA,KAAA,EAAA,6BAAA;;;;;;AAiBE,MAAA,UAAA,GAAA,EAAA,KAAE,EAAA,4BAAA,GAAA;;;;;;;;;;;;;;;;;;;;;AAnCK,IAAA,KAAI,EAAG,QAAa,CAAA,aAAA;AAAG,IAAA,IAAK,EAAC,KAAQ,CAAA,QAAA;AAAG,IAAA,WAAO,EAAA,MAAA,CAAA,WAAA;;qBAE5D,MAAG;OAAA,KAAA,CAAA,OAAA;wBAAHA,WAAG,CAAA,wBAAA,EAAA,EAAA,GAAA,EAAA,CAAA,EAAA,CAAA;WAGY,KAAK,CAAA,KAAA;4CAEf,CAAA,KAAA,YAAA,EAAAC,eAAA,CADF,KAAK,CAAA,KAAA,CAAA,EAAA,CAAA,YAAA;AAIO,YAAA,CAAA,QAAA,CAAA,eAAe,CAAC,MAAM,KAAA,CAAA;4BAApCC,kBAEI,CAAA,KAAA,EAFJ,UAEI,EAAAD,eAAA,CADF,MAAa,CAAA,aAAA,CAAA,EAAA,CAAA,YAAA;eAIT,MAAA,CAAA,OAAA,KAAA,eAAA;iDAYF,iBAAA,EAAA;6BAXC,CAAA,wBAAA,EAAA;AAAA,oBAAA,IAAA,EAAA,EAAA;AAAA,oBAAA,KAAA,EAAA,6BAAA;;AACN,kBAAAE,kBAAA,CAAK,QAAL,UAAK,EAAAF,eAAA,CAAA,wBAAA,CAAA,CAAA,CAAA,CAAA,OAAA,IAAA,MAAA,CAAA,aAAA,CAAA,EAAA,CAAA,YAAA;AACD,kBAAA,CAAA,QAAA,CAAA,eAAA,IAAA,OAAA;AAAJ,uBAAAG,SAAA,EAAA,EAAAF,kBAAA,CAEO,QAFP,UAEO,EAAAD,eAAA;;AAEN,kBAAA,CAAA,QAAA,CAAA,eAAA,IAAA,MAAA;oCADDC,kBAKO,CAAA,MAAA,EAAA;;AAHN,wBAAA,KAAK,EAACG,cAAA,CAAA,CAAA,+BAAsB,EACP,CAAA,+BAAA,EAAA,QAAA,CAAA,eAAA,IAAA,MAAA,CAAA,CAAA,CAAA,CAAA;AAClB,uBAAA,EAAAJ,eAAA,CAAA,QAAA,CAAA,eAAW,IAAA,MAAA,CAAA,EAAA,CAAA,mBAAA;;;;oCAMfC,8CACc,QAAe,CAAA,eAAA,EAAA,CAAtB,EAAG,KAAA;yCADVA;sBAEE,GAAG,EAAC,QAAO,CAAA,MAAA,CAAC,EAAE,CAAA;AACf,sBAAA,KAAK,EAAC,uBAAuB;;AAC7B,sBAAAC,kBAAA,QAAA;AACQ,wBAAA,CAAA,QAAA,CAAA,UAAA,CAAA,EAAA,CAAA;0CAAPH,WAAO,CAAA,wBAAA,EAAA;;AAAA,8BAAA,IAAA,EAAA,EAAA;AAAA,8BAAA,KAAA,EAAA,kCAAA;;0CACPA,WAAU,CAAA,wBAAA,EAAA;;AAAA,8BAAA,IAAA,EAAA,EAAA;AAAA,8BAAA,KAAA,EAAA,8BAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"CnCalendarCard.vue3.js","sources":["../../../../../src/integrations/builtin/calendar/CnCalendarCard.vue?vue&type=template&id=0989061d&lang.js"],"sourcesContent":["<!--\n CnCalendarCard — bespoke widget for the `calendar` integration.\n\n Surface-aware (AD-19) widget that renders linked VEVENTs across the\n four pluggable-integration-registry surfaces with one component:\n\n - `user-dashboard` : compact list, \"next 5 upcoming meetings\" across\n the user's objects (one row per VEVENT). Falls\n back to a list scoped by the optional\n register/schema/objectId when given (registry\n dashboards pass the full triple) so it still\n works without a global \"across all my objects\"\n query — see AD-22.\n - `app-dashboard` : same compact list scoped to the current app's\n register/schema/objectId, when given.\n - `detail-page` : full per-object meeting list with a quick\n \"Open in Calendar\" CTA — the inline create\n form lives in CnCalendarTab, not here, to keep\n the detail card compact (design.md AD-3).\n - `single-entity` : a single VEVENT rendered as a chip with date +\n summary + status icon. Accepts `entityId`\n (composite \"calendarId/eventUri\") for the\n referenceType auto-rendering path.\n\n Loading / empty / error states follow ADR-017 component composition.\n All strings pass through t('nextcloud-vue', ...) for nl + en\n translation (ADR-007). Theming uses Nextcloud CSS variables only so\n the nldesign app's overrides apply transparently (ADR-010).\n-->\n<template>\n\t<CnDetailCard :title=\"resolvedTitle\" :icon=\"Calendar\" :collapsible=\"collapsible\">\n\t\t<!-- Loading -->\n\t\t<NcLoadingIcon v-if=\"loading\" />\n\n\t\t<!-- Error -->\n\t\t<div v-else-if=\"error\" class=\"cn-calendar-card__error\" role=\"alert\">\n\t\t\t{{ error }}\n\t\t</div>\n\n\t\t<!-- Empty -->\n\t\t<div v-else-if=\"displayedEvents.length === 0\" class=\"cn-calendar-card__empty\">\n\t\t\t{{ noEventsLabel }}\n\t\t</div>\n\n\t\t<!-- single-entity surface: one compact chip -->\n\t\t<div v-else-if=\"surface === 'single-entity'\" class=\"cn-calendar-card__chip\">\n\t\t\t<CalendarClock :size=\"18\" class=\"cn-calendar-card__chip-icon\" />\n\t\t\t<span class=\"cn-calendar-card__chip-title\">{{ displayedEvents[0].summary || untitledLabel }}</span>\n\t\t\t<span v-if=\"displayedEvents[0].dtstart\" class=\"cn-calendar-card__chip-when\">\n\t\t\t\t{{ formatShort(displayedEvents[0].dtstart) }}\n\t\t\t</span>\n\t\t\t<span\n\t\t\t\tv-if=\"displayedEvents[0].status\"\n\t\t\t\tclass=\"cn-calendar-card__chip-status\"\n\t\t\t\t:class=\"`cn-calendar-card__chip-status--${displayedEvents[0].status}`\">\n\t\t\t\t{{ displayedEvents[0].status }}\n\t\t\t</span>\n\t\t</div>\n\n\t\t<!-- list surfaces: user/app/detail share the row template -->\n\t\t<div v-else class=\"cn-calendar-card__list\">\n\t\t\t<div\n\t\t\t\tv-for=\"ev in displayedEvents\"\n\t\t\t\t:key=\"rowKey(ev)\"\n\t\t\t\tclass=\"cn-calendar-card__row\">\n\t\t\t\t<div class=\"cn-calendar-card__row-icon\">\n\t\t\t\t\t<CalendarClock v-if=\"isUpcoming(ev)\" :size=\"20\" class=\"cn-calendar-card__icon--upcoming\" />\n\t\t\t\t\t<CalendarCheck v-else :size=\"20\" class=\"cn-calendar-card__icon--past\" />\n\t\t\t\t</div>\n\t\t\t\t<div class=\"cn-calendar-card__row-main\">\n\t\t\t\t\t<span class=\"cn-calendar-card__title\">{{ ev.summary || untitledLabel }}</span>\n\t\t\t\t\t<div class=\"cn-calendar-card__meta\">\n\t\t\t\t\t\t<span v-if=\"ev.dtstart\" class=\"cn-calendar-card__when\">{{ formatShort(ev.dtstart) }}</span>\n\t\t\t\t\t\t<span v-if=\"ev.location\" class=\"cn-calendar-card__location\">· {{ ev.location }}</span>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\n\t\t<!-- Footer: detail surface gets \"Open in NC Calendar\" CTA -->\n\t\t<template v-if=\"surface === 'detail-page'\" #footer>\n\t\t\t<a\n\t\t\t\tclass=\"cn-calendar-card__cta\"\n\t\t\t\t:href=\"calendarAppUrl\"\n\t\t\t\ttarget=\"_blank\"\n\t\t\t\trel=\"noopener\">\n\t\t\t\t{{ openInCalendarLabel }}\n\t\t\t</a>\n\t\t</template>\n\n\t\t<!-- Footer: dashboard surfaces get \"Show all\" when truncated -->\n\t\t<template v-else-if=\"allEvents.length > maxDisplay && surface !== 'single-entity'\" #footer>\n\t\t\t<button class=\"cn-calendar-card__show-all\" @click=\"$emit('show-all')\">\n\t\t\t\t{{ showAllLabel }} ({{ allEvents.length }})\n\t\t\t</button>\n\t\t</template>\n\t</CnDetailCard>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { generateUrl } from '@nextcloud/router'\nimport { NcLoadingIcon } from '@nextcloud/vue'\nimport { markRaw } from 'vue'\nimport Calendar from 'vue-material-design-icons/Calendar.vue'\nimport CalendarCheck from 'vue-material-design-icons/CalendarCheck.vue'\nimport CalendarClock from 'vue-material-design-icons/CalendarClock.vue'\nimport CnDetailCard from '../../../components/CnDetailCard/CnDetailCard.vue'\nimport { buildHeaders, prefixUrl } from '../../../utils/index.js'\nimport { userLocale } from '../../../utils/userLocale.js'\n\n/**\n * CnCalendarCard — surface-aware compact widget for the calendar\n * integration. Renders linked VEVENTs across all four widget surfaces\n * (AD-19) with the same component.\n */\nexport default {\n\tname: 'CnCalendarCard',\n\n\tcomponents: { CnDetailCard, NcLoadingIcon, CalendarClock, CalendarCheck },\n\n\tprops: {\n\t\t/** OpenRegister register id (slug or uuid). Optional for user-dashboard. */\n\t\tregister: { type: String, default: '' },\n\t\t/** OpenRegister schema id (slug or uuid). Optional for user-dashboard. */\n\t\tschema: { type: String, default: '' },\n\t\t/** Parent object id. Optional for user-dashboard. */\n\t\tobjectId: { type: String, default: '' },\n\t\t/** Stable integration id (matches PHP-side provider id). */\n\t\tintegrationId: { type: String, default: 'calendar' },\n\t\t/**\n\t\t * Composite entity id \"calendarId/eventUri\" for the\n\t\t * single-entity surface. Ignored on other surfaces.\n\t\t */\n\t\tentityId: { type: String, default: '' },\n\t\t/** Rendering surface per ADR-019 AD-19. */\n\t\tsurface: {\n\t\t\ttype: String,\n\t\t\tdefault: 'detail-page',\n\t\t\tvalidator: (v) => ['user-dashboard', 'app-dashboard', 'detail-page', 'single-entity'].includes(v),\n\t\t},\n\n\t\t/** Base API URL for OpenRegister. */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\t\t/** Maximum rows on list surfaces (ignored on single-entity). */\n\t\tmaxDisplay: { type: Number, default: 5 },\n\t\t/** Whether the CnDetailCard collapses. */\n\t\tcollapsible: { type: Boolean, default: false },\n\t\t/** Override the card title (defaults to the translated label). */\n\t\ttitle: { type: String, default: '' },\n\n\t\t// --- Pre-translated labels (ADR-007) ---\n\t\t/** Empty-state label. */\n\t\tnoEventsLabel: { type: String, default: () => t('nextcloud-vue', 'No meetings') },\n\t\t/** Fallback when a VEVENT has no summary. */\n\t\tuntitledLabel: { type: String, default: () => t('nextcloud-vue', '(no title)') },\n\t\t/** detail-page footer CTA label. */\n\t\topenInCalendarLabel: { type: String, default: () => t('nextcloud-vue', 'Open in Calendar') },\n\t\t/** dashboard \"show all\" footer label. */\n\t\tshowAllLabel: { type: String, default: () => t('nextcloud-vue', 'Show all') },\n\t},\n\n\temits: ['show-all'],\n\n\tdata() {\n\t\treturn {\n\t\t\tCalendar: markRaw(Calendar),\n\t\t\tallEvents: [],\n\t\t\tloading: false,\n\t\t\terror: '',\n\t\t}\n\t},\n\n\tcomputed: {\n\t\tresolvedTitle() {\n\t\t\treturn this.title || t('nextcloud-vue', 'Meetings')\n\t\t},\n\n\t\tcalendarAppUrl() {\n\t\t\ttry {\n\t\t\t\treturn generateUrl('/apps/calendar')\n\t\t\t} catch {\n\t\t\t\treturn '/apps/calendar'\n\t\t\t}\n\t\t},\n\n\t\tdisplayedEvents() {\n\t\t\tif (this.surface === 'single-entity') {\n\t\t\t\treturn this.allEvents.slice(0, 1)\n\t\t\t}\n\t\t\t// list surfaces sort by dtstart ascending, then cap at maxDisplay.\n\t\t\tconst sorted = [...this.allEvents].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\t// On dashboard surfaces, surface upcoming meetings first.\n\t\t\tif (this.surface === 'user-dashboard' || this.surface === 'app-dashboard') {\n\t\t\t\tconst now = Date.now()\n\t\t\t\tconst upcoming = sorted.filter((ev) => {\n\t\t\t\t\tif (!ev.dtstart) {\n\t\t\t\t\t\treturn true\n\t\t\t\t\t}\n\t\t\t\t\treturn new Date(ev.dtstart).getTime() >= now\n\t\t\t\t})\n\t\t\t\treturn upcoming.slice(0, this.maxDisplay)\n\t\t\t}\n\t\t\treturn sorted.slice(0, this.maxDisplay)\n\t\t},\n\t},\n\n\twatch: {\n\t\tobjectId: { immediate: true, handler() {\n\t\t\tthis.refresh()\n\t\t} },\n\n\t\tentityId() {\n\t\t\tthis.refresh()\n\t\t},\n\n\t\tsurface() {\n\t\t\tthis.refresh()\n\t\t},\n\t},\n\n\tmethods: {\n\t\tbaseUrl() {\n\t\t\treturn `${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/integrations/${this.integrationId}`\n\t\t},\n\n\t\trowKey(ev) {\n\t\t\tif (ev.calendarId && ev.id) {\n\t\t\t\treturn `${ev.calendarId}/${ev.id}`\n\t\t\t}\n\t\t\treturn ev.id || ev.uid || ''\n\t\t},\n\n\t\tisUpcoming(ev) {\n\t\t\tif (!ev.dtstart) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\ttry {\n\t\t\t\treturn new Date(ev.dtstart).getTime() >= Date.now()\n\t\t\t} catch {\n\t\t\t\treturn true\n\t\t\t}\n\t\t},\n\n\t\trefresh() {\n\t\t\tif (this.surface === 'single-entity') {\n\t\t\t\tthis.fetchSingleEntity()\n\t\t\t} else {\n\t\t\t\tthis.fetchList()\n\t\t\t}\n\t\t},\n\n\t\tasync fetchList() {\n\t\t\tif (!this.register || !this.schema || !this.objectId) {\n\t\t\t\tthis.allEvents = []\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\tthis.error = ''\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(this.baseUrl()), { headers: buildHeaders() })\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tconst data = await response.json()\n\t\t\t\t\tthis.allEvents = data.results || data.items || (Array.isArray(data) ? data : []) || []\n\t\t\t\t} else if (response.status === 503) {\n\t\t\t\t\tthis.allEvents = []\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Calendar is currently unavailable.')\n\t\t\t\t} else {\n\t\t\t\t\tthis.allEvents = []\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('[CnCalendarCard] failed to fetch events', err)\n\t\t\t\tthis.allEvents = []\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\tasync fetchSingleEntity() {\n\t\t\tif (!this.entityId || !this.register || !this.schema || !this.objectId) {\n\t\t\t\tthis.allEvents = []\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\tthis.error = ''\n\t\t\ttry {\n\t\t\t\tconst url = `${this.baseUrl()}/${encodeURI(this.entityId)}`\n\t\t\t\tconst response = await fetch(prefixUrl(url), { 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.allEvents = data && (data.id || data.uid) ? [data] : []\n\t\t\t\t} else {\n\t\t\t\t\tthis.allEvents = []\n\t\t\t\t\tif (response.status !== 404) {\n\t\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load meeting.')\n\t\t\t\t\t}\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('[CnCalendarCard] failed to fetch single entity', err)\n\t\t\t\tthis.allEvents = []\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load meeting.')\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\tformatShort(value) {\n\t\t\tif (!value) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst d = new Date(value)\n\t\t\t\tif (Number.isNaN(d.getTime())) {\n\t\t\t\t\treturn String(value)\n\t\t\t\t}\n\t\t\t\tconst opts = this.surface === 'single-entity'\n\t\t\t\t\t? { dateStyle: 'medium' }\n\t\t\t\t\t: { dateStyle: 'medium', timeStyle: 'short' }\n\t\t\t\treturn d.toLocaleString(userLocale(), opts)\n\t\t\t} catch {\n\t\t\t\treturn String(value)\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-calendar-card__empty {\n\ttext-align: center;\n\tpadding: 16px 12px;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 13px;\n}\n\n.cn-calendar-card__error {\n\tcolor: var(--color-error);\n\tpadding: 8px 0;\n\tfont-size: 13px;\n}\n\n.cn-calendar-card__list {\n\tdisplay: flex;\n\tflex-direction: column;\n}\n\n.cn-calendar-card__row {\n\tdisplay: flex;\n\talign-items: flex-start;\n\tgap: 10px;\n\tpadding: 8px 0;\n\tborder-bottom: 1px solid var(--color-border);\n}\n\n.cn-calendar-card__row:last-child {\n\tborder-bottom: none;\n}\n\n.cn-calendar-card__row-icon {\n\tflex-shrink: 0;\n\tpadding-top: 1px;\n}\n\n.cn-calendar-card__icon--upcoming { color: var(--color-primary-element); }\n\n.cn-calendar-card__icon--past { color: var(--color-text-maxcontrast); }\n\n.cn-calendar-card__row-main {\n\tflex: 1;\n\tmin-width: 0;\n}\n\n.cn-calendar-card__title {\n\tdisplay: block;\n\tfont-size: 13px;\n\tfont-weight: 500;\n\twhite-space: nowrap;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n.cn-calendar-card__meta {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 6px;\n\tmargin-top: 2px;\n\tfont-size: 12px;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-calendar-card__when {\n\twhite-space: nowrap;\n}\n\n.cn-calendar-card__location {\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-calendar-card__chip {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 6px;\n\tpadding: 4px 10px;\n\tborder-radius: var(--border-radius-pill, 999px);\n\tbackground: var(--color-background-hover);\n\tfont-size: 13px;\n\tmax-width: 100%;\n}\n\n.cn-calendar-card__chip-icon {\n\tcolor: var(--color-primary-element);\n\tflex-shrink: 0;\n}\n\n.cn-calendar-card__chip-title {\n\tfont-weight: 500;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-calendar-card__chip-when {\n\tcolor: var(--color-text-maxcontrast);\n\twhite-space: nowrap;\n}\n\n.cn-calendar-card__chip-status {\n\tfont-size: 11px;\n\ttext-transform: uppercase;\n\tletter-spacing: 0.04em;\n\tpadding: 1px 6px;\n\tborder-radius: var(--border-radius-pill, 999px);\n\tbackground: var(--color-background-darker);\n}\n\n.cn-calendar-card__chip-status--confirmed { background: var(--color-success); color: var(--color-main-background); }\n\n.cn-calendar-card__chip-status--tentative { background: var(--color-warning, #e9a40f); color: var(--color-main-background); }\n\n.cn-calendar-card__chip-status--cancelled { background: var(--color-error); color: var(--color-main-background); }\n\n.cn-calendar-card__cta,\n.cn-calendar-card__show-all {\n\tbackground: none;\n\tborder: none;\n\tcolor: var(--color-primary-element);\n\tfont-size: 13px;\n\tfont-weight: 500;\n\tcursor: pointer;\n\tpadding: 0;\n\twidth: 100%;\n\ttext-align: center;\n\ttext-decoration: none;\n\tdisplay: inline-block;\n}\n\n.cn-calendar-card__cta:hover,\n.cn-calendar-card__show-all:hover {\n\ttext-decoration: underline;\n}\n</style>\n"],"names":["_createBlock","_toDisplayString","_createElementBlock","_createElementVNode","_openBlock","_normalizeClass"],"mappings":";;;;AAgEyB,EAAA,KAAK,EAAC,yBAAE;AAAA,EAAA,IAAA,EAAA,OAAA;;;;AAKU,EAAA,KAAA,EAAA,yBAAA;;;;AAKjC,EAAA,KAAA,EAAA,wBAAA;;AAEF,MAAA,UAAA,GAAA,EAAA,KAAA,EAAA,8BAAA,GAAA;;;AACD,EAAA,KAAA,EAAA,6BAAA;;;;;;AAiBE,MAAA,UAAA,GAAA,EAAA,KAAE,EAAA,4BAAA,GAAA;;;;;;;;;;;;;;;;;;;;;AAnCK,IAAA,KAAI,EAAG,QAAa,CAAA,aAAA;AAAG,IAAA,IAAK,EAAC,KAAQ,CAAA,QAAA;AAAG,IAAA,WAAO,EAAA,MAAA,CAAA,WAAA;;qBAE5D,MAAG;OAAA,KAAA,CAAA,OAAA;wBAAHA,WAAG,CAAA,wBAAA,EAAA,EAAA,GAAA,EAAA,CAAA,EAAA,CAAA;WAGY,KAAK,CAAA,KAAA;4CAEf,CAAA,KAAA,YAAA,EAAAC,eAAA,CADF,KAAK,CAAA,KAAA,CAAA,EAAA,CAAA,YAAA;AAIO,YAAA,CAAA,QAAA,CAAA,eAAe,CAAC,MAAM,KAAA,CAAA;4BAApCC,kBAEI,CAAA,KAAA,EAFJ,UAEI,EAAAD,eAAA,CADF,MAAa,CAAA,aAAA,CAAA,EAAA,CAAA,YAAA;eAIT,MAAA,CAAA,OAAA,KAAA,eAAA;iDAYF,iBAAA,EAAA;6BAXC,CAAA,wBAAA,EAAA;AAAA,oBAAA,IAAA,EAAA,EAAA;AAAA,oBAAA,KAAA,EAAA,6BAAA;;AACN,kBAAAE,kBAAA,CAAK,QAAL,UAAK,EAAAF,eAAA,CAAA,wBAAA,CAAA,CAAA,CAAA,CAAA,OAAA,IAAA,MAAA,CAAA,aAAA,CAAA,EAAA,CAAA,YAAA;AACD,kBAAA,CAAA,QAAA,CAAA,eAAA,IAAA,OAAA;AAAJ,uBAAAG,SAAA,EAAA,EAAAF,kBAAA,CAEO,QAFP,UAEO,EAAAD,eAAA;;AAEN,kBAAA,CAAA,QAAA,CAAA,eAAA,IAAA,MAAA;oCADDC,kBAKO,CAAA,MAAA,EAAA;;AAHN,wBAAA,KAAK,EAACG,cAAA,CAAA,CAAA,+BAAsB,EACP,CAAA,+BAAA,EAAA,QAAA,CAAA,eAAA,IAAA,MAAA,CAAA,CAAA,CAAA,CAAA;AAClB,uBAAA,EAAAJ,eAAA,CAAA,QAAA,CAAA,eAAW,IAAA,MAAA,CAAA,EAAA,CAAA,mBAAA;;;;oCAMfC,8CACc,QAAe,CAAA,eAAA,EAAA,CAAtB,EAAG,KAAA;yCADVA;sBAEE,GAAG,EAAC,QAAO,CAAA,MAAA,CAAC,EAAE,CAAA;AACf,sBAAA,KAAK,EAAC,uBAAuB;;AAC7B,sBAAAC,kBAAA,QAAA;AACQ,wBAAA,CAAA,QAAA,CAAA,UAAA,CAAA,EAAA,CAAA;0CAAPH,WAAO,CAAA,wBAAA,EAAA;;AAAA,8BAAA,IAAA,EAAA,EAAA;AAAA,8BAAA,KAAA,EAAA,kCAAA;;0CACPA,WAAU,CAAA,wBAAA,EAAA;;AAAA,8BAAA,IAAA,EAAA,EAAA;AAAA,8BAAA,KAAA,EAAA,8BAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -2,7 +2,7 @@ import script from './CnCalendarTab.vue2.js';
2
2
  import { render } from './CnCalendarTab.vue3.js';
3
3
 
4
4
  script.render = render;
5
- script.__scopeId = "data-v-576281b3";
5
+ script.__scopeId = "data-v-bc96c764";
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/CnCalendarEventCreate/CnCalendarEventCreate.vue.js';
11
11
  import __sfc_main$1 from '../../../components/CnCalendarEventPicker/CnCalendarEventPicker.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
 
15
16
  const MAX_AVATARS = 3;
@@ -295,7 +296,7 @@ var script = {
295
296
  if (!d) {
296
297
  return '—'
297
298
  }
298
- return d.toLocaleDateString(undefined, { month: 'short' })
299
+ return d.toLocaleDateString(userLocale(), { month: 'short' })
299
300
  },
300
301
 
301
302
  dayOf(ev) {
@@ -312,16 +313,16 @@ var script = {
312
313
  return ev.dtstart ? String(ev.dtstart) : translate('nextcloud-vue', 'Time not set')
313
314
  }
314
315
  const end = this.parseDate(ev.dtend);
315
- const dateLabel = start.toLocaleDateString(undefined, { weekday: 'short', day: 'numeric', month: 'short' });
316
+ const dateLabel = start.toLocaleDateString(userLocale(), { weekday: 'short', day: 'numeric', month: 'short' });
316
317
  const timeOpts = { hour: '2-digit', minute: '2-digit' };
317
- const startTime = start.toLocaleTimeString(undefined, timeOpts);
318
+ const startTime = start.toLocaleTimeString(userLocale(), timeOpts);
318
319
  if (end) {
319
320
  const sameDay = start.toDateString() === end.toDateString();
320
- const endTime = end.toLocaleTimeString(undefined, timeOpts);
321
+ const endTime = end.toLocaleTimeString(userLocale(), timeOpts);
321
322
  if (sameDay) {
322
323
  return `${dateLabel} · ${startTime} – ${endTime}`
323
324
  }
324
- const endDateLabel = end.toLocaleDateString(undefined, { day: 'numeric', month: 'short' });
325
+ const endDateLabel = end.toLocaleDateString(userLocale(), { day: 'numeric', month: 'short' });
325
326
  return `${dateLabel} ${startTime} – ${endDateLabel} ${endTime}`
326
327
  }
327
328
  return `${dateLabel} · ${startTime}`