@conduction/nextcloud-vue 2.56.0-beta.37 → 2.56.0-beta.38

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 (287) 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 +28 -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/CnTasksCard/CnTasksCard.vue.js +1 -1
  139. package/dist/esm/components/CnTasksCard/CnTasksCard.vue2.js +2 -1
  140. package/dist/esm/components/CnTasksCard/CnTasksCard.vue2.js.map +1 -1
  141. package/dist/esm/components/CnTasksCard/CnTasksCard.vue3.js.map +1 -1
  142. package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue.js +1 -1
  143. package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue2.js +2 -1
  144. package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue2.js.map +1 -1
  145. package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue3.js.map +1 -1
  146. package/dist/esm/components/CnTranslatedBadge/CnTranslatedBadge.vue.js +1 -1
  147. package/dist/esm/components/CnTranslatedBadge/CnTranslatedBadge.vue2.js +2 -1
  148. package/dist/esm/components/CnTranslatedBadge/CnTranslatedBadge.vue2.js.map +1 -1
  149. package/dist/esm/components/CnVersionHistory/CnVersionHistory.vue.js +1 -1
  150. package/dist/esm/components/CnVersionHistory/CnVersionHistory.vue2.js +2 -1
  151. package/dist/esm/components/CnVersionHistory/CnVersionHistory.vue2.js.map +1 -1
  152. package/dist/esm/components/CnVersionHistory/CnVersionHistory.vue3.js.map +1 -1
  153. package/dist/esm/dialogs/CnNoteHistoryDialog.vue.js +1 -1
  154. package/dist/esm/dialogs/CnNoteHistoryDialog.vue2.js +2 -1
  155. package/dist/esm/dialogs/CnNoteHistoryDialog.vue2.js.map +1 -1
  156. package/dist/esm/dialogs/CnNoteHistoryDialog.vue3.js.map +1 -1
  157. package/dist/esm/integrations/builtin/activity/CnActivityCard.vue.js +1 -1
  158. package/dist/esm/integrations/builtin/activity/CnActivityCard.vue2.js +3 -2
  159. package/dist/esm/integrations/builtin/activity/CnActivityCard.vue2.js.map +1 -1
  160. package/dist/esm/integrations/builtin/activity/CnActivityCard.vue3.js.map +1 -1
  161. package/dist/esm/integrations/builtin/activity/CnActivityTab.vue.js +1 -1
  162. package/dist/esm/integrations/builtin/activity/CnActivityTab.vue2.js +4 -3
  163. package/dist/esm/integrations/builtin/activity/CnActivityTab.vue2.js.map +1 -1
  164. package/dist/esm/integrations/builtin/activity/CnActivityTab.vue3.js.map +1 -1
  165. package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue.js +1 -1
  166. package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue2.js +3 -2
  167. package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue2.js.map +1 -1
  168. package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue3.js.map +1 -1
  169. package/dist/esm/integrations/builtin/calendar/CnCalendarCard.vue.js +1 -1
  170. package/dist/esm/integrations/builtin/calendar/CnCalendarCard.vue2.js +2 -1
  171. package/dist/esm/integrations/builtin/calendar/CnCalendarCard.vue2.js.map +1 -1
  172. package/dist/esm/integrations/builtin/calendar/CnCalendarCard.vue3.js.map +1 -1
  173. package/dist/esm/integrations/builtin/calendar/CnCalendarTab.vue.js +1 -1
  174. package/dist/esm/integrations/builtin/calendar/CnCalendarTab.vue2.js +6 -5
  175. package/dist/esm/integrations/builtin/calendar/CnCalendarTab.vue2.js.map +1 -1
  176. package/dist/esm/integrations/builtin/calendar/CnCalendarTab.vue3.js.map +1 -1
  177. package/dist/esm/integrations/builtin/contactmoment/CnContactmomentCard.vue.js +1 -1
  178. package/dist/esm/integrations/builtin/contactmoment/CnContactmomentCard.vue2.js +2 -1
  179. package/dist/esm/integrations/builtin/contactmoment/CnContactmomentCard.vue2.js.map +1 -1
  180. package/dist/esm/integrations/builtin/contactmoment/CnContactmomentCard.vue3.js.map +1 -1
  181. package/dist/esm/integrations/builtin/cospend/CnCospendTab.vue.js +1 -1
  182. package/dist/esm/integrations/builtin/cospend/CnCospendTab.vue2.js +2 -1
  183. package/dist/esm/integrations/builtin/cospend/CnCospendTab.vue2.js.map +1 -1
  184. package/dist/esm/integrations/builtin/cospend/CnCospendTab.vue3.js.map +1 -1
  185. package/dist/esm/integrations/builtin/deck/CnDeckTab.vue.js +1 -1
  186. package/dist/esm/integrations/builtin/deck/CnDeckTab.vue2.js +2 -1
  187. package/dist/esm/integrations/builtin/deck/CnDeckTab.vue2.js.map +1 -1
  188. package/dist/esm/integrations/builtin/deck/CnDeckTab.vue3.js.map +1 -1
  189. package/dist/esm/integrations/builtin/email/CnEmailCard.vue.js +1 -1
  190. package/dist/esm/integrations/builtin/email/CnEmailCard.vue2.js +2 -1
  191. package/dist/esm/integrations/builtin/email/CnEmailCard.vue2.js.map +1 -1
  192. package/dist/esm/integrations/builtin/email/CnEmailCard.vue3.js.map +1 -1
  193. package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionCard.vue.js +1 -1
  194. package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionCard.vue2.js +2 -1
  195. package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionCard.vue2.js.map +1 -1
  196. package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionCard.vue3.js.map +1 -1
  197. package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionTab.vue.js +1 -1
  198. package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionTab.vue2.js +2 -1
  199. package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionTab.vue2.js.map +1 -1
  200. package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionTab.vue3.js.map +1 -1
  201. package/dist/esm/integrations/builtin/photos/CnPhotosTab.vue.js +1 -1
  202. package/dist/esm/integrations/builtin/photos/CnPhotosTab.vue2.js +2 -1
  203. package/dist/esm/integrations/builtin/photos/CnPhotosTab.vue2.js.map +1 -1
  204. package/dist/esm/integrations/builtin/photos/CnPhotosTab.vue3.js.map +1 -1
  205. package/dist/esm/l10n/en.json.js +35 -2
  206. package/dist/esm/l10n/en.json.js.map +1 -1
  207. package/dist/esm/l10n/nl.json.js +38 -2
  208. package/dist/esm/l10n/nl.json.js.map +1 -1
  209. package/dist/esm/library-style.js +1 -1
  210. package/dist/esm/nextcloud-vue.css +994 -909
  211. package/dist/esm/store/useObjectStore.js +7 -2
  212. package/dist/esm/store/useObjectStore.js.map +1 -1
  213. package/dist/esm/utils/boardColumns.js +50 -1
  214. package/dist/esm/utils/boardColumns.js.map +1 -1
  215. package/dist/esm/utils/boardReferenceColumns.js +55 -3
  216. package/dist/esm/utils/boardReferenceColumns.js.map +1 -1
  217. package/dist/esm/utils/schema.js +5 -4
  218. package/dist/esm/utils/schema.js.map +1 -1
  219. package/dist/esm/utils/userLocale.js +30 -0
  220. package/dist/esm/utils/userLocale.js.map +1 -0
  221. package/dist/esm/utils/validateManifestV2.compiled.js +2 -2
  222. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  223. package/dist/nextcloud-vue.cjs.js +837 -359
  224. package/dist/nextcloud-vue.cjs.js.map +2 -2
  225. package/dist/nextcloud-vue.css +994 -909
  226. package/l10n/en.json +35 -2
  227. package/l10n/nl.json +38 -2
  228. package/package.json +1 -1
  229. package/src/components/CnAdvancedFormDialog/CnMetadataTab.vue +3 -2
  230. package/src/components/CnAdvancedFormDialog/CnPropertyValueCell.vue +5 -4
  231. package/src/components/CnAnalyticsReportPicker/CnAnalyticsReportPicker.vue +2 -1
  232. package/src/components/CnAuditTrailCard/CnAuditTrailCard.vue +2 -1
  233. package/src/components/CnBoardView/CnBoardView.vue +189 -24
  234. package/src/components/CnCalendarEventPicker/CnCalendarEventPicker.vue +2 -1
  235. package/src/components/CnCalendarWidget/CnCalendarWidget.vue +2 -1
  236. package/src/components/CnChartWidget/CnChartWidget.vue +6 -5
  237. package/src/components/CnCollectivePagePicker/CnCollectivePagePicker.vue +2 -1
  238. package/src/components/CnConversationThread/CnConversationThread.vue +2 -1
  239. package/src/components/CnCountdownWidget/CnCountdownWidget.vue +2 -1
  240. package/src/components/CnDashboardPage/CnDashboardPage.vue +2 -1
  241. package/src/components/CnDataMatrix/CnDataMatrix.vue +2 -1
  242. package/src/components/CnDetailPage/CnDetailHeaderChips.vue +2 -1
  243. package/src/components/CnEmailPicker/CnEmailPicker.vue +2 -1
  244. package/src/components/CnFederationStatus/CnFederationStatus.vue +3 -1
  245. package/src/components/CnFileManager/CnFileManager.vue +2 -1
  246. package/src/components/CnFilePreview/CnFilePreview.vue +2 -1
  247. package/src/components/CnFilesBrowser/CnFilesBrowser.vue +54 -6
  248. package/src/components/CnFollowToggle/CnFollowToggle.vue +2 -1
  249. package/src/components/CnIndexPage/CnIndexPage.vue +28 -4
  250. package/src/components/CnNotesCard/CnNotesCard.vue +2 -1
  251. package/src/components/CnObjectCalendar/CnObjectCalendar.vue +3 -2
  252. package/src/components/CnObjectDataWidget/CnObjectDataWidget.vue +36 -0
  253. package/src/components/CnObjectMetadataWidget/CnObjectMetadataWidget.vue +5 -4
  254. package/src/components/CnObjectSidebar/CnAuditTrailTab.vue +2 -1
  255. package/src/components/CnObjectSidebar/CnFlowTasksPanel.vue +2 -1
  256. package/src/components/CnObjectSidebar/CnNotesTab.vue +2 -1
  257. package/src/components/CnObjectSidebar/CnTasksTab.vue +2 -1
  258. package/src/components/CnPropertySourceField/CnPropertySourceField.vue +2 -1
  259. package/src/components/CnRoadmapItem/CnRoadmapItem.vue +2 -1
  260. package/src/components/CnSchemaFormDialog/CnSchemaFormDialog.vue +4 -2
  261. package/src/components/CnStatsBlock/CnStatsBlock.vue +2 -1
  262. package/src/components/CnSummaryAggregates/CnSummaryAggregates.vue +2 -1
  263. package/src/components/CnTasksCard/CnTasksCard.vue +2 -1
  264. package/src/components/CnTimelineWidget/CnTimelineWidget.vue +2 -1
  265. package/src/components/CnTranslatedBadge/CnTranslatedBadge.vue +2 -1
  266. package/src/components/CnVersionHistory/CnVersionHistory.vue +2 -1
  267. package/src/css/board.css +40 -0
  268. package/src/dialogs/CnNoteHistoryDialog.vue +2 -1
  269. package/src/integrations/builtin/activity/CnActivityCard.vue +3 -2
  270. package/src/integrations/builtin/activity/CnActivityTab.vue +4 -3
  271. package/src/integrations/builtin/analytics/CnAnalyticsTab.vue +3 -2
  272. package/src/integrations/builtin/calendar/CnCalendarCard.vue +2 -1
  273. package/src/integrations/builtin/calendar/CnCalendarTab.vue +6 -5
  274. package/src/integrations/builtin/contactmoment/CnContactmomentCard.vue +2 -1
  275. package/src/integrations/builtin/cospend/CnCospendTab.vue +2 -1
  276. package/src/integrations/builtin/deck/CnDeckTab.vue +2 -1
  277. package/src/integrations/builtin/email/CnEmailCard.vue +2 -1
  278. package/src/integrations/builtin/field-inspection/CnFieldInspectionCard.vue +2 -1
  279. package/src/integrations/builtin/field-inspection/CnFieldInspectionTab.vue +2 -1
  280. package/src/integrations/builtin/photos/CnPhotosTab.vue +2 -1
  281. package/src/schemas/app-manifest-v2.schema.json +2 -1
  282. package/src/store/useObjectStore.js +7 -2
  283. package/src/utils/boardColumns.js +49 -0
  284. package/src/utils/boardReferenceColumns.js +54 -2
  285. package/src/utils/schema.js +5 -4
  286. package/src/utils/userLocale.js +26 -0
  287. package/src/utils/validateManifestV2.compiled.js +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"CnSummaryAggregates.vue3.js","sources":["../../../../src/components/CnSummaryAggregates/CnSummaryAggregates.vue?vue&type=template&id=eda589ba&lang.js"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n<template>\n\t<div v-if=\"aggregates.length > 0\" class=\"cn-summary-aggregates\" data-testid=\"cn-summary-aggregates\">\n\t\t<div\n\t\t\tv-for=\"(agg, i) in resolvedChips\"\n\t\t\t:key=\"i\"\n\t\t\tclass=\"cn-summary-aggregates__chip\"\n\t\t\t:data-testid=\"`cn-summary-aggregate-${i}`\">\n\t\t\t<span class=\"cn-summary-aggregates__value\">\n\t\t\t\t<NcLoadingIcon v-if=\"agg.loading\" :size=\"16\" />\n\t\t\t\t<template v-else>{{ agg.display }}</template>\n\t\t\t</span>\n\t\t\t<span class=\"cn-summary-aggregates__label\">{{ agg.label }}</span>\n\t\t</div>\n\t</div>\n</template>\n\n<script>\nimport { NcLoadingIcon } from '@nextcloud/vue'\nimport { fetchAggregateValue } from '../../utils/fetchAggregate.js'\n\n/**\n * CnSummaryAggregates — declarative count/sum/avg stat chips for a\n * `type:\"detail\"` page header.\n *\n * Each entry runs ONE aggregation (via OpenRegister's ad-hoc `/value`\n * endpoint — the `runAdhocByRef` contract) over a related schema, scoped to\n * the current detail-page object through `@objectId` / `@object.<field>`\n * filter tokens, and shows the result as a small labelled chip. Lets a detail\n * page surface \"12 open cases / €4.2k outstanding\" without per-app code.\n *\n * Mounted by `CnDetailPage` when the manifest page config declares\n * `summaryAggregates`. Reads the detail page's `cnObjectContext` inject so the\n * filter tokens resolve against the live object.\n *\n * Example config (consumed by CnDetailPage):\n * ```js\n * summaryAggregates: [\n * { label: 'Open cases', register: 'pipelinq', schema: 'case', metric: 'count', filter: { client: '@objectId', status: 'open' } },\n * { label: 'Outstanding', register: 'pipelinq', schema: 'invoice', metric: 'sum', field: 'amount', filter: { client: '@objectId', paid: false }, format: 'currency' },\n * ]\n * ```\n */\nexport default {\n\tname: 'CnSummaryAggregates',\n\n\tcomponents: { NcLoadingIcon },\n\n\tinject: {\n\t\t/**\n\t\t * Detail-page object context (`{ objectId, object, register, schema }`)\n\t\t * provided by CnDetailPage. Resolves `@objectId` / `@object.<field>` tokens\n\t\t * in each aggregate's filter. Null on surfaces that don't provide one.\n\t\t */\n\t\tcnObjectContext: { default: null },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The aggregate descriptors.\n\t\t *\n\t\t * @type {Array<{label: string, register: string, schema: string, metric?: string, field?: string, filter?: object, format?: string}>}\n\t\t */\n\t\taggregates: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\t/** Per-aggregate fetched values, keyed by index. */\n\t\t\tvalues: {},\n\t\t\t/** Per-aggregate loading flags, keyed by index. */\n\t\t\tloadingMap: {},\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/** The unwrapped object context (or null). */\n\t\tobjectCtx() {\n\t\t\tconst c = this.cnObjectContext\n\t\t\tif (!c) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn (typeof c === 'object' && 'value' in c) ? c.value : c\n\t\t},\n\n\t\t/**\n\t\t * The chips with their fetched value formatted for display.\n\t\t *\n\t\t * @return {Array<{label: string, display: (string|number), loading: boolean}>}\n\t\t */\n\t\tresolvedChips() {\n\t\t\treturn this.aggregates.map((agg, i) => {\n\t\t\t\tconst raw = this.values[i]\n\t\t\t\treturn {\n\t\t\t\t\tlabel: agg.label || '',\n\t\t\t\t\tdisplay: this.formatChip(raw, agg.format),\n\t\t\t\t\tloading: !!this.loadingMap[i],\n\t\t\t\t}\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * A stable signature of the aggregate query set + the object id it is\n\t\t * scoped to, so the watcher only refetches on a real change.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tsourceKey() {\n\t\t\treturn JSON.stringify({\n\t\t\t\taggs: this.aggregates,\n\t\t\t\tobjectId: this.objectCtx ? this.objectCtx.objectId : null,\n\t\t\t})\n\t\t},\n\t},\n\n\twatch: {\n\t\tsourceKey: {\n\t\t\timmediate: true,\n\t\t\thandler() {\n\t\t\t\tthis.fetchAll()\n\t\t\t},\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Format a fetched value for the chip. Defaults to a thousands-grouped\n\t\t * number; `format: 'currency'` renders a EUR amount, `format: 'number'`\n\t\t * forces numeric grouping.\n\t\t *\n\t\t * @param {number|null} raw The fetched value.\n\t\t * @param {string} [format] Optional format key (`currency` | `number`).\n\t\t * @return {string|number}\n\t\t */\n\t\tformatChip(raw, format) {\n\t\t\tif (raw === null || raw === undefined) {\n\t\t\t\treturn '—'\n\t\t\t}\n\t\t\tif (format === 'currency') {\n\t\t\t\tconst num = Number(raw)\n\t\t\t\tif (Number.isNaN(num)) {\n\t\t\t\t\treturn String(raw)\n\t\t\t\t}\n\t\t\t\treturn new Intl.NumberFormat(undefined, { style: 'currency', currency: 'EUR' }).format(num)\n\t\t\t}\n\t\t\tif (typeof raw === 'number') {\n\t\t\t\treturn raw.toLocaleString()\n\t\t\t}\n\t\t\treturn raw\n\t\t},\n\n\t\t/**\n\t\t * Fetch every aggregate, scoping the filter tokens to the current object.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync fetchAll() {\n\t\t\tconst ctx = this.objectCtx || {}\n\t\t\tawait Promise.all(this.aggregates.map((agg, i) => this.fetchOne(agg, i, ctx)))\n\t\t},\n\n\t\t/**\n\t\t * Fetch one aggregate value into `values[i]`.\n\t\t *\n\t\t * @param {object} agg The aggregate descriptor.\n\t\t * @param {number} i The index in the list.\n\t\t * @param {object} ctx The object context for token resolution.\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync fetchOne(agg, i, ctx) {\n\t\t\tif (!agg.register || !agg.schema) {\n\t\t\t\tthis.values[i] = null\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loadingMap[i] = true\n\t\t\ttry {\n\t\t\t\tconst value = await fetchAggregateValue({\n\t\t\t\t\tregister: agg.register,\n\t\t\t\t\tschema: agg.schema,\n\t\t\t\t\tmetric: agg.metric || 'count',\n\t\t\t\t\tfield: agg.field,\n\t\t\t\t\tfilter: agg.filter || {},\n\t\t\t\t}, ctx)\n\t\t\t\tthis.values[i] = value\n\t\t\t} catch {\n\t\t\t\tthis.values[i] = null\n\t\t\t} finally {\n\t\t\t\tthis.loadingMap[i] = false\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-summary-aggregates {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tgap: 12px;\n\tmargin: 0 0 4px;\n}\n\n.cn-summary-aggregates__chip {\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: flex-start;\n\tpadding: 6px 12px;\n\tborder-radius: var(--border-radius-large, 12px);\n\tbackground: var(--color-background-hover);\n\tmin-width: 72px;\n}\n\n.cn-summary-aggregates__value {\n\tfont-size: 1.25em;\n\tfont-weight: 600;\n\tline-height: 1.2;\n\tcolor: var(--color-main-text);\n}\n\n.cn-summary-aggregates__label {\n\tfont-size: 0.8em;\n\tcolor: var(--color-text-maxcontrast);\n}\n</style>\n"],"names":["_openBlock","_createElementBlock","_renderList","_createElementVNode","_createBlock","_Fragment","_createTextVNode","_toDisplayString"],"mappings":";;;;AASmC,EAAA,KAAE,EAAA,uBAAA;AAAA,EAAA,aAAA,EAAA,uBAAA;;;AAM5B,MAAA,UAAA,GAAA,EAAA,KAAK,EAAC,8BAA8B,GAAA;;;;;;AANjC,EAAA,OAAA,CAAA,MAAA,CAAA,UAAU,CAAC,MAAM,GAAA,CAAA;AAA1B,OAAAA,SAAA,EAAA,EAAAC,kBAAA,QAAA;AACA,SAAAD,SAAA,CAAA,IAAA,CAAA,EAAAC,kBAAA,SACmB,EAAA,IAAA,EAAAC,UAAA,CAAA,QAAA,CAAA,aAAa,EAAxB,CAAA,GAAG,EAAE,CAAC,KAAA;+BADdD;AAEC,YAAA,GAAG,EAAE,CAAC;AACN,YAAA,KAAI,EAAC,6BAA6B;AAClC,YAAA,aAAK,0BAAA,CAAA,CAAA,CAAA;;AACN,YAAAE,kBAAA,SAAA;AACI,cAAA,CAAA,GAAA,CAAA,OAAA;gCAAHC,WAAG,CAAA,wBAAA,EAAA;;AAAA,oBAAA,IAAA,EAAA,EAAA;;gCACHH,kBAAE,CAAAI,QAAA,EAAA,EAAA,GAAA,EAAA,CAAA,EAAA,EAAA;AAAA,oBAAAC,eAAA,CAAAC,eAAA,CAAA,GAAA,CAAA,OAAA,CAAA,EAAA,CAAA,YAAA;;;;;;;;;;;;"}
1
+ {"version":3,"file":"CnSummaryAggregates.vue3.js","sources":["../../../../src/components/CnSummaryAggregates/CnSummaryAggregates.vue?vue&type=template&id=2ea9593e&lang.js"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n<template>\n\t<div v-if=\"aggregates.length > 0\" class=\"cn-summary-aggregates\" data-testid=\"cn-summary-aggregates\">\n\t\t<div\n\t\t\tv-for=\"(agg, i) in resolvedChips\"\n\t\t\t:key=\"i\"\n\t\t\tclass=\"cn-summary-aggregates__chip\"\n\t\t\t:data-testid=\"`cn-summary-aggregate-${i}`\">\n\t\t\t<span class=\"cn-summary-aggregates__value\">\n\t\t\t\t<NcLoadingIcon v-if=\"agg.loading\" :size=\"16\" />\n\t\t\t\t<template v-else>{{ agg.display }}</template>\n\t\t\t</span>\n\t\t\t<span class=\"cn-summary-aggregates__label\">{{ agg.label }}</span>\n\t\t</div>\n\t</div>\n</template>\n\n<script>\nimport { NcLoadingIcon } from '@nextcloud/vue'\nimport { fetchAggregateValue } from '../../utils/fetchAggregate.js'\nimport { userLocale } from '../../utils/userLocale.js'\n\n/**\n * CnSummaryAggregates — declarative count/sum/avg stat chips for a\n * `type:\"detail\"` page header.\n *\n * Each entry runs ONE aggregation (via OpenRegister's ad-hoc `/value`\n * endpoint — the `runAdhocByRef` contract) over a related schema, scoped to\n * the current detail-page object through `@objectId` / `@object.<field>`\n * filter tokens, and shows the result as a small labelled chip. Lets a detail\n * page surface \"12 open cases / €4.2k outstanding\" without per-app code.\n *\n * Mounted by `CnDetailPage` when the manifest page config declares\n * `summaryAggregates`. Reads the detail page's `cnObjectContext` inject so the\n * filter tokens resolve against the live object.\n *\n * Example config (consumed by CnDetailPage):\n * ```js\n * summaryAggregates: [\n * { label: 'Open cases', register: 'pipelinq', schema: 'case', metric: 'count', filter: { client: '@objectId', status: 'open' } },\n * { label: 'Outstanding', register: 'pipelinq', schema: 'invoice', metric: 'sum', field: 'amount', filter: { client: '@objectId', paid: false }, format: 'currency' },\n * ]\n * ```\n */\nexport default {\n\tname: 'CnSummaryAggregates',\n\n\tcomponents: { NcLoadingIcon },\n\n\tinject: {\n\t\t/**\n\t\t * Detail-page object context (`{ objectId, object, register, schema }`)\n\t\t * provided by CnDetailPage. Resolves `@objectId` / `@object.<field>` tokens\n\t\t * in each aggregate's filter. Null on surfaces that don't provide one.\n\t\t */\n\t\tcnObjectContext: { default: null },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The aggregate descriptors.\n\t\t *\n\t\t * @type {Array<{label: string, register: string, schema: string, metric?: string, field?: string, filter?: object, format?: string}>}\n\t\t */\n\t\taggregates: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\t/** Per-aggregate fetched values, keyed by index. */\n\t\t\tvalues: {},\n\t\t\t/** Per-aggregate loading flags, keyed by index. */\n\t\t\tloadingMap: {},\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/** The unwrapped object context (or null). */\n\t\tobjectCtx() {\n\t\t\tconst c = this.cnObjectContext\n\t\t\tif (!c) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn (typeof c === 'object' && 'value' in c) ? c.value : c\n\t\t},\n\n\t\t/**\n\t\t * The chips with their fetched value formatted for display.\n\t\t *\n\t\t * @return {Array<{label: string, display: (string|number), loading: boolean}>}\n\t\t */\n\t\tresolvedChips() {\n\t\t\treturn this.aggregates.map((agg, i) => {\n\t\t\t\tconst raw = this.values[i]\n\t\t\t\treturn {\n\t\t\t\t\tlabel: agg.label || '',\n\t\t\t\t\tdisplay: this.formatChip(raw, agg.format),\n\t\t\t\t\tloading: !!this.loadingMap[i],\n\t\t\t\t}\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * A stable signature of the aggregate query set + the object id it is\n\t\t * scoped to, so the watcher only refetches on a real change.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tsourceKey() {\n\t\t\treturn JSON.stringify({\n\t\t\t\taggs: this.aggregates,\n\t\t\t\tobjectId: this.objectCtx ? this.objectCtx.objectId : null,\n\t\t\t})\n\t\t},\n\t},\n\n\twatch: {\n\t\tsourceKey: {\n\t\t\timmediate: true,\n\t\t\thandler() {\n\t\t\t\tthis.fetchAll()\n\t\t\t},\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Format a fetched value for the chip. Defaults to a thousands-grouped\n\t\t * number; `format: 'currency'` renders a EUR amount, `format: 'number'`\n\t\t * forces numeric grouping.\n\t\t *\n\t\t * @param {number|null} raw The fetched value.\n\t\t * @param {string} [format] Optional format key (`currency` | `number`).\n\t\t * @return {string|number}\n\t\t */\n\t\tformatChip(raw, format) {\n\t\t\tif (raw === null || raw === undefined) {\n\t\t\t\treturn '—'\n\t\t\t}\n\t\t\tif (format === 'currency') {\n\t\t\t\tconst num = Number(raw)\n\t\t\t\tif (Number.isNaN(num)) {\n\t\t\t\t\treturn String(raw)\n\t\t\t\t}\n\t\t\t\treturn new Intl.NumberFormat(undefined, { style: 'currency', currency: 'EUR' }).format(num)\n\t\t\t}\n\t\t\tif (typeof raw === 'number') {\n\t\t\t\treturn raw.toLocaleString(userLocale())\n\t\t\t}\n\t\t\treturn raw\n\t\t},\n\n\t\t/**\n\t\t * Fetch every aggregate, scoping the filter tokens to the current object.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync fetchAll() {\n\t\t\tconst ctx = this.objectCtx || {}\n\t\t\tawait Promise.all(this.aggregates.map((agg, i) => this.fetchOne(agg, i, ctx)))\n\t\t},\n\n\t\t/**\n\t\t * Fetch one aggregate value into `values[i]`.\n\t\t *\n\t\t * @param {object} agg The aggregate descriptor.\n\t\t * @param {number} i The index in the list.\n\t\t * @param {object} ctx The object context for token resolution.\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync fetchOne(agg, i, ctx) {\n\t\t\tif (!agg.register || !agg.schema) {\n\t\t\t\tthis.values[i] = null\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loadingMap[i] = true\n\t\t\ttry {\n\t\t\t\tconst value = await fetchAggregateValue({\n\t\t\t\t\tregister: agg.register,\n\t\t\t\t\tschema: agg.schema,\n\t\t\t\t\tmetric: agg.metric || 'count',\n\t\t\t\t\tfield: agg.field,\n\t\t\t\t\tfilter: agg.filter || {},\n\t\t\t\t}, ctx)\n\t\t\t\tthis.values[i] = value\n\t\t\t} catch {\n\t\t\t\tthis.values[i] = null\n\t\t\t} finally {\n\t\t\t\tthis.loadingMap[i] = false\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-summary-aggregates {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tgap: 12px;\n\tmargin: 0 0 4px;\n}\n\n.cn-summary-aggregates__chip {\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: flex-start;\n\tpadding: 6px 12px;\n\tborder-radius: var(--border-radius-large, 12px);\n\tbackground: var(--color-background-hover);\n\tmin-width: 72px;\n}\n\n.cn-summary-aggregates__value {\n\tfont-size: 1.25em;\n\tfont-weight: 600;\n\tline-height: 1.2;\n\tcolor: var(--color-main-text);\n}\n\n.cn-summary-aggregates__label {\n\tfont-size: 0.8em;\n\tcolor: var(--color-text-maxcontrast);\n}\n</style>\n"],"names":["_openBlock","_createElementBlock","_renderList","_createElementVNode","_createBlock","_Fragment","_createTextVNode","_toDisplayString"],"mappings":";;;;AASmC,EAAA,KAAE,EAAA,uBAAA;AAAA,EAAA,aAAA,EAAA,uBAAA;;;AAM5B,MAAA,UAAA,GAAA,EAAA,KAAK,EAAC,8BAA8B,GAAA;;;;;;AANjC,EAAA,OAAA,CAAA,MAAA,CAAA,UAAU,CAAC,MAAM,GAAA,CAAA;AAA1B,OAAAA,SAAA,EAAA,EAAAC,kBAAA,QAAA;AACA,SAAAD,SAAA,CAAA,IAAA,CAAA,EAAAC,kBAAA,SACmB,EAAA,IAAA,EAAAC,UAAA,CAAA,QAAA,CAAA,aAAa,EAAxB,CAAA,GAAG,EAAE,CAAC,KAAA;+BADdD;AAEC,YAAA,GAAG,EAAE,CAAC;AACN,YAAA,KAAI,EAAC,6BAA6B;AAClC,YAAA,aAAK,0BAAA,CAAA,CAAA,CAAA;;AACN,YAAAE,kBAAA,SAAA;AACI,cAAA,CAAA,GAAA,CAAA,OAAA;gCAAHC,WAAG,CAAA,wBAAA,EAAA;;AAAA,oBAAA,IAAA,EAAA,EAAA;;gCACHH,kBAAE,CAAAI,QAAA,EAAA,EAAA,GAAA,EAAA,CAAA,EAAA,EAAA;AAAA,oBAAAC,eAAA,CAAAC,eAAA,CAAA,GAAA,CAAA,OAAA,CAAA,EAAA,CAAA,YAAA;;;;;;;;;;;;"}
@@ -2,7 +2,7 @@ import script from './CnTasksCard.vue2.js';
2
2
  import { render } from './CnTasksCard.vue3.js';
3
3
 
4
4
  script.render = render;
5
- script.__scopeId = "data-v-5dad4d65";
5
+ script.__scopeId = "data-v-1333abd5";
6
6
  const __sfc_main = script;
7
7
 
8
8
  export { __sfc_main as default };
@@ -7,6 +7,7 @@ import CloseCircleOutline from 'vue-material-design-icons/CloseCircleOutline.vue
7
7
  import ProgressClock from 'vue-material-design-icons/ProgressClock.vue';
8
8
  import __sfc_main from '../CnDetailCard/CnDetailCard.vue.js';
9
9
  import __sfc_main$1 from '../CnUserActionMenu/CnUserActionMenu.vue.js';
10
+ import { userLocale } from '../../utils/userLocale.js';
10
11
  import { prefixUrl, buildHeaders } from '../../utils/headers.js';
11
12
 
12
13
  /**
@@ -172,7 +173,7 @@ var script = {
172
173
  return ''
173
174
  }
174
175
  try {
175
- return new Date(dateStr).toLocaleDateString(undefined, {
176
+ return new Date(dateStr).toLocaleDateString(userLocale(), {
176
177
  year: 'numeric',
177
178
  month: 'short',
178
179
  day: 'numeric',
@@ -1 +1 @@
1
- {"version":3,"file":"CnTasksCard.vue2.js","sources":["../../../../src/components/CnTasksCard/CnTasksCard.vue"],"sourcesContent":["<!--\n CnTasksCard — Inline tasks card for detail pages.\n\n Displays up to 5 tasks with status indicators, assignee, and due date.\n Integrates CnUserActionMenu on assignee names. Highlights overdue tasks.\n Wraps CnDetailCard for consistent styling.\n-->\n<template>\n\t<CnDetailCard :title=\"titleLabel\" :icon=\"CheckboxMarkedOutline\" :collapsible=\"collapsible\">\n\t\t<div class=\"cn-tasks-card\">\n\t\t\t<!-- Loading state -->\n\t\t\t<NcLoadingIcon v-if=\"loading\" />\n\n\t\t\t<!-- Empty state -->\n\t\t\t<div v-else-if=\"allTasks.length === 0\" class=\"cn-tasks-card__empty\">\n\t\t\t\t{{ noTasksLabel }}\n\t\t\t</div>\n\n\t\t\t<!-- Tasks list -->\n\t\t\t<div v-else class=\"cn-tasks-card__list\">\n\t\t\t\t<div\n\t\t\t\t\tv-for=\"task in displayedTasks\"\n\t\t\t\t\t:key=\"task.id\"\n\t\t\t\t\tclass=\"cn-tasks-card__task\">\n\t\t\t\t\t<!-- Status icon -->\n\t\t\t\t\t<div class=\"cn-tasks-card__status-icon\">\n\t\t\t\t\t\t<CheckboxMarkedOutline\n\t\t\t\t\t\t\tv-if=\"task.status === 'completed'\"\n\t\t\t\t\t\t\t:size=\"20\"\n\t\t\t\t\t\t\tclass=\"cn-tasks-card__icon--completed\" />\n\t\t\t\t\t\t<ProgressClock\n\t\t\t\t\t\t\tv-else-if=\"task.status === 'active' || task.status === 'in-process'\"\n\t\t\t\t\t\t\t:size=\"20\"\n\t\t\t\t\t\t\tclass=\"cn-tasks-card__icon--active\" />\n\t\t\t\t\t\t<CloseCircleOutline\n\t\t\t\t\t\t\tv-else-if=\"task.status === 'terminated'\"\n\t\t\t\t\t\t\t:size=\"20\"\n\t\t\t\t\t\t\tclass=\"cn-tasks-card__icon--terminated\" />\n\t\t\t\t\t\t<CheckboxBlankOutline\n\t\t\t\t\t\t\tv-else\n\t\t\t\t\t\t\t:size=\"20\"\n\t\t\t\t\t\t\tclass=\"cn-tasks-card__icon--available\" />\n\t\t\t\t\t</div>\n\n\t\t\t\t\t<!-- Task content -->\n\t\t\t\t\t<div class=\"cn-tasks-card__content\">\n\t\t\t\t\t\t<span class=\"cn-tasks-card__title\">{{ task.title || task.name }}</span>\n\t\t\t\t\t\t<div class=\"cn-tasks-card__meta\">\n\t\t\t\t\t\t\t<!-- Assignee -->\n\t\t\t\t\t\t\t<span v-if=\"hasAssignee(task)\" class=\"cn-tasks-card__assignee\">\n\t\t\t\t\t\t\t\t<CnUserActionMenu\n\t\t\t\t\t\t\t\t\tv-if=\"!isCurrentUser(task.assignee)\"\n\t\t\t\t\t\t\t\t\t:userId=\"task.assignee\"\n\t\t\t\t\t\t\t\t\t:displayName=\"task.assignee\">\n\t\t\t\t\t\t\t\t\t<span class=\"cn-tasks-card__assignee-name\">{{ task.assignee }}</span>\n\t\t\t\t\t\t\t\t</CnUserActionMenu>\n\t\t\t\t\t\t\t\t<span v-else class=\"cn-tasks-card__assignee-name cn-tasks-card__assignee-name--self\">\n\t\t\t\t\t\t\t\t\t{{ task.assignee }}\n\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t<span v-else class=\"cn-tasks-card__unassigned\">\n\t\t\t\t\t\t\t\t{{ unassignedLabel }}\n\t\t\t\t\t\t\t</span>\n\n\t\t\t\t\t\t\t<!-- Due date -->\n\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\tv-if=\"task.dueDate\"\n\t\t\t\t\t\t\t\tclass=\"cn-tasks-card__due-date\"\n\t\t\t\t\t\t\t\t:class=\"{ 'cn-tasks-card__due-date--overdue': isOverdue(task) }\">\n\t\t\t\t\t\t\t\t{{ formatDate(task.dueDate) }}\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\n\t\t<!-- Footer: \"Show all\" link -->\n\t\t<template v-if=\"allTasks.length > maxDisplay\" #footer>\n\t\t\t<button\n\t\t\t\tclass=\"cn-tasks-card__show-all\"\n\t\t\t\t@click=\"$emit('show-all')\">\n\t\t\t\t{{ showAllLabel }} ({{ allTasks.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 { NcLoadingIcon } from '@nextcloud/vue'\nimport { markRaw } from 'vue'\nimport CheckboxBlankOutline from 'vue-material-design-icons/CheckboxBlankOutline.vue'\nimport CheckboxMarkedOutline from 'vue-material-design-icons/CheckboxMarkedOutline.vue'\nimport CloseCircleOutline from 'vue-material-design-icons/CloseCircleOutline.vue'\nimport ProgressClock from 'vue-material-design-icons/ProgressClock.vue'\nimport CnDetailCard from '../CnDetailCard/CnDetailCard.vue'\nimport CnUserActionMenu from '../CnUserActionMenu/CnUserActionMenu.vue'\nimport { buildHeaders, prefixUrl } from '../../utils/index.js'\n\n/**\n * CnTasksCard — Inline tasks widget for detail pages.\n *\n * Shows up to 5 tasks sorted by due date with status indicators.\n * Highlights overdue tasks and integrates CnUserActionMenu on assignees.\n *\n * Basic usage\n * ```vue\n * <CnTasksCard\n * register-id=\"uuid-register\"\n * schema-id=\"uuid-schema\"\n * object-id=\"uuid-object\" />\n * ```\n *\n * With sidebar sync\n * ```vue\n * <CnTasksCard\n * register-id=\"reg\"\n * schema-id=\"schema\"\n * object-id=\"obj\"\n * @show-all=\"openSidebarTasksTab\" />\n * ```\n */\nexport default {\n\tname: 'CnTasksCard',\n\n\tcomponents: {\n\t\tCnDetailCard,\n\t\tCnUserActionMenu,\n\t\tNcLoadingIcon,\n\t\tCheckboxMarkedOutline,\n\t\tCheckboxBlankOutline,\n\t\tProgressClock,\n\t\tCloseCircleOutline,\n\t},\n\n\tprops: {\n\t\t/** OpenRegister register ID */\n\t\tregisterId: {\n\t\t\ttype: String,\n\t\t\trequired: true,\n\t\t},\n\n\t\t/** OpenRegister schema ID */\n\t\tschemaId: {\n\t\t\ttype: String,\n\t\t\trequired: true,\n\t\t},\n\n\t\t/** Object UUID */\n\t\tobjectId: {\n\t\t\ttype: String,\n\t\t\trequired: true,\n\t\t},\n\n\t\t/** Base API URL for OpenRegister */\n\t\tapiBase: {\n\t\t\ttype: String,\n\t\t\tdefault: '/apps/openregister/api',\n\t\t},\n\n\t\t/** Maximum number of tasks to display */\n\t\tmaxDisplay: {\n\t\t\ttype: Number,\n\t\t\tdefault: 5,\n\t\t},\n\n\t\t/** Whether the card is collapsible */\n\t\tcollapsible: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t// --- Pre-translated labels ---\n\t\t/** Label / heading for the card title */\n\t\ttitleLabel: { type: String, default: () => t('nextcloud-vue', 'Tasks') },\n\t\t/** Text shown when no tasks are linked */\n\t\tnoTasksLabel: { type: String, default: () => t('nextcloud-vue', 'No tasks') },\n\t\t/** Label for the show-all link */\n\t\tshowAllLabel: { type: String, default: () => t('nextcloud-vue', 'Show all') },\n\t\t/** Label shown when no assignee is set */\n\t\tunassignedLabel: { type: String, default: () => t('nextcloud-vue', 'Unassigned') },\n\t},\n\n\temits: ['show-all'],\n\n\tdata() {\n\t\treturn {\n\t\t\tCheckboxMarkedOutline: markRaw(CheckboxMarkedOutline),\n\t\t\tallTasks: [],\n\t\t\tloading: false,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\tdisplayedTasks() {\n\t\t\t// Sort by due date (soonest first), then limit\n\t\t\tconst sorted = [...this.allTasks].sort((a, b) => {\n\t\t\t\tconst dateA = a.dueDate ? new Date(a.dueDate) : new Date('9999-12-31')\n\t\t\t\tconst dateB = b.dueDate ? new Date(b.dueDate) : new Date('9999-12-31')\n\t\t\t\treturn dateA - dateB\n\t\t\t})\n\t\t\treturn sorted.slice(0, this.maxDisplay)\n\t\t},\n\t},\n\n\twatch: {\n\t\tobjectId: {\n\t\t\timmediate: true,\n\t\t\thandler(newId) {\n\t\t\t\tif (newId && this.registerId && this.schemaId) {\n\t\t\t\t\tthis.fetchTasks()\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t},\n\n\tmethods: {\n\t\thasAssignee(task) {\n\t\t\treturn task.assignee && task.assignee.trim() !== ''\n\t\t},\n\n\t\tisCurrentUser(userId) {\n\t\t\tconst currentUser = typeof OC !== 'undefined' ? OC?.currentUser : null\n\t\t\treturn userId === currentUser\n\t\t},\n\n\t\tisOverdue(task) {\n\t\t\tif (!task.dueDate || task.status === 'completed') {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\ttry {\n\t\t\t\treturn new Date(task.dueDate) < new Date()\n\t\t\t} catch {\n\t\t\t\treturn false\n\t\t\t}\n\t\t},\n\n\t\tasync fetchTasks() {\n\t\t\tif (!this.registerId || !this.schemaId || !this.objectId) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\ttry {\n\t\t\t\tconst url = `${this.apiBase}/objects/${this.registerId}/${this.schemaId}/${this.objectId}/tasks`\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.allTasks = data.results || data || []\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console -- the failure is already handled; the console is the only channel a host app can read the detail on\n\t\t\t\tconsole.error('CnTasksCard: Failed to fetch tasks', err)\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\tformatDate(dateStr) {\n\t\t\tif (!dateStr) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\ttry {\n\t\t\t\treturn new Date(dateStr).toLocaleDateString(undefined, {\n\t\t\t\t\tyear: 'numeric',\n\t\t\t\t\tmonth: 'short',\n\t\t\t\t\tday: 'numeric',\n\t\t\t\t})\n\t\t\t} catch {\n\t\t\t\treturn dateStr\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-tasks-card__empty {\n\ttext-align: center;\n\tpadding: 16px 12px;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 13px;\n}\n\n.cn-tasks-card__list {\n\tdisplay: flex;\n\tflex-direction: column;\n}\n\n.cn-tasks-card__task {\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-tasks-card__task:last-child {\n\tborder-bottom: none;\n}\n\n.cn-tasks-card__status-icon {\n\tflex-shrink: 0;\n\tpadding-top: 1px;\n}\n\n.cn-tasks-card__icon--completed {\n\tcolor: var(--color-success);\n}\n\n.cn-tasks-card__icon--active {\n\tcolor: var(--color-primary-element);\n}\n\n.cn-tasks-card__icon--terminated {\n\tcolor: var(--color-error);\n}\n\n.cn-tasks-card__icon--available {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-tasks-card__content {\n\tflex: 1;\n\tmin-width: 0;\n}\n\n.cn-tasks-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-tasks-card__meta {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\tmargin-top: 2px;\n\tfont-size: 12px;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-tasks-card__assignee-name {\n\tcolor: var(--color-primary-element);\n\tfont-weight: 500;\n\tcursor: pointer;\n}\n\n.cn-tasks-card__assignee-name:hover {\n\ttext-decoration: underline;\n}\n\n.cn-tasks-card__assignee-name--self {\n\tcolor: var(--color-text-maxcontrast);\n\tcursor: default;\n}\n\n.cn-tasks-card__assignee-name--self:hover {\n\ttext-decoration: none;\n}\n\n.cn-tasks-card__unassigned {\n\tfont-style: italic;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-tasks-card__due-date {\n\twhite-space: nowrap;\n}\n\n.cn-tasks-card__due-date--overdue {\n\tcolor: var(--color-error);\n\tfont-weight: 500;\n}\n\n.cn-tasks-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}\n\n.cn-tasks-card__show-all:hover {\n\ttext-decoration: underline;\n}\n</style>\n"],"names":["CnDetailCard","CnUserActionMenu","t"],"mappings":"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"}
1
+ {"version":3,"file":"CnTasksCard.vue2.js","sources":["../../../../src/components/CnTasksCard/CnTasksCard.vue"],"sourcesContent":["<!--\n CnTasksCard — Inline tasks card for detail pages.\n\n Displays up to 5 tasks with status indicators, assignee, and due date.\n Integrates CnUserActionMenu on assignee names. Highlights overdue tasks.\n Wraps CnDetailCard for consistent styling.\n-->\n<template>\n\t<CnDetailCard :title=\"titleLabel\" :icon=\"CheckboxMarkedOutline\" :collapsible=\"collapsible\">\n\t\t<div class=\"cn-tasks-card\">\n\t\t\t<!-- Loading state -->\n\t\t\t<NcLoadingIcon v-if=\"loading\" />\n\n\t\t\t<!-- Empty state -->\n\t\t\t<div v-else-if=\"allTasks.length === 0\" class=\"cn-tasks-card__empty\">\n\t\t\t\t{{ noTasksLabel }}\n\t\t\t</div>\n\n\t\t\t<!-- Tasks list -->\n\t\t\t<div v-else class=\"cn-tasks-card__list\">\n\t\t\t\t<div\n\t\t\t\t\tv-for=\"task in displayedTasks\"\n\t\t\t\t\t:key=\"task.id\"\n\t\t\t\t\tclass=\"cn-tasks-card__task\">\n\t\t\t\t\t<!-- Status icon -->\n\t\t\t\t\t<div class=\"cn-tasks-card__status-icon\">\n\t\t\t\t\t\t<CheckboxMarkedOutline\n\t\t\t\t\t\t\tv-if=\"task.status === 'completed'\"\n\t\t\t\t\t\t\t:size=\"20\"\n\t\t\t\t\t\t\tclass=\"cn-tasks-card__icon--completed\" />\n\t\t\t\t\t\t<ProgressClock\n\t\t\t\t\t\t\tv-else-if=\"task.status === 'active' || task.status === 'in-process'\"\n\t\t\t\t\t\t\t:size=\"20\"\n\t\t\t\t\t\t\tclass=\"cn-tasks-card__icon--active\" />\n\t\t\t\t\t\t<CloseCircleOutline\n\t\t\t\t\t\t\tv-else-if=\"task.status === 'terminated'\"\n\t\t\t\t\t\t\t:size=\"20\"\n\t\t\t\t\t\t\tclass=\"cn-tasks-card__icon--terminated\" />\n\t\t\t\t\t\t<CheckboxBlankOutline\n\t\t\t\t\t\t\tv-else\n\t\t\t\t\t\t\t:size=\"20\"\n\t\t\t\t\t\t\tclass=\"cn-tasks-card__icon--available\" />\n\t\t\t\t\t</div>\n\n\t\t\t\t\t<!-- Task content -->\n\t\t\t\t\t<div class=\"cn-tasks-card__content\">\n\t\t\t\t\t\t<span class=\"cn-tasks-card__title\">{{ task.title || task.name }}</span>\n\t\t\t\t\t\t<div class=\"cn-tasks-card__meta\">\n\t\t\t\t\t\t\t<!-- Assignee -->\n\t\t\t\t\t\t\t<span v-if=\"hasAssignee(task)\" class=\"cn-tasks-card__assignee\">\n\t\t\t\t\t\t\t\t<CnUserActionMenu\n\t\t\t\t\t\t\t\t\tv-if=\"!isCurrentUser(task.assignee)\"\n\t\t\t\t\t\t\t\t\t:userId=\"task.assignee\"\n\t\t\t\t\t\t\t\t\t:displayName=\"task.assignee\">\n\t\t\t\t\t\t\t\t\t<span class=\"cn-tasks-card__assignee-name\">{{ task.assignee }}</span>\n\t\t\t\t\t\t\t\t</CnUserActionMenu>\n\t\t\t\t\t\t\t\t<span v-else class=\"cn-tasks-card__assignee-name cn-tasks-card__assignee-name--self\">\n\t\t\t\t\t\t\t\t\t{{ task.assignee }}\n\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t<span v-else class=\"cn-tasks-card__unassigned\">\n\t\t\t\t\t\t\t\t{{ unassignedLabel }}\n\t\t\t\t\t\t\t</span>\n\n\t\t\t\t\t\t\t<!-- Due date -->\n\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\tv-if=\"task.dueDate\"\n\t\t\t\t\t\t\t\tclass=\"cn-tasks-card__due-date\"\n\t\t\t\t\t\t\t\t:class=\"{ 'cn-tasks-card__due-date--overdue': isOverdue(task) }\">\n\t\t\t\t\t\t\t\t{{ formatDate(task.dueDate) }}\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\n\t\t<!-- Footer: \"Show all\" link -->\n\t\t<template v-if=\"allTasks.length > maxDisplay\" #footer>\n\t\t\t<button\n\t\t\t\tclass=\"cn-tasks-card__show-all\"\n\t\t\t\t@click=\"$emit('show-all')\">\n\t\t\t\t{{ showAllLabel }} ({{ allTasks.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 { NcLoadingIcon } from '@nextcloud/vue'\nimport { markRaw } from 'vue'\nimport CheckboxBlankOutline from 'vue-material-design-icons/CheckboxBlankOutline.vue'\nimport CheckboxMarkedOutline from 'vue-material-design-icons/CheckboxMarkedOutline.vue'\nimport CloseCircleOutline from 'vue-material-design-icons/CloseCircleOutline.vue'\nimport ProgressClock from 'vue-material-design-icons/ProgressClock.vue'\nimport CnDetailCard from '../CnDetailCard/CnDetailCard.vue'\nimport CnUserActionMenu from '../CnUserActionMenu/CnUserActionMenu.vue'\nimport { buildHeaders, prefixUrl } from '../../utils/index.js'\nimport { userLocale } from '../../utils/userLocale.js'\n\n/**\n * CnTasksCard — Inline tasks widget for detail pages.\n *\n * Shows up to 5 tasks sorted by due date with status indicators.\n * Highlights overdue tasks and integrates CnUserActionMenu on assignees.\n *\n * Basic usage\n * ```vue\n * <CnTasksCard\n * register-id=\"uuid-register\"\n * schema-id=\"uuid-schema\"\n * object-id=\"uuid-object\" />\n * ```\n *\n * With sidebar sync\n * ```vue\n * <CnTasksCard\n * register-id=\"reg\"\n * schema-id=\"schema\"\n * object-id=\"obj\"\n * @show-all=\"openSidebarTasksTab\" />\n * ```\n */\nexport default {\n\tname: 'CnTasksCard',\n\n\tcomponents: {\n\t\tCnDetailCard,\n\t\tCnUserActionMenu,\n\t\tNcLoadingIcon,\n\t\tCheckboxMarkedOutline,\n\t\tCheckboxBlankOutline,\n\t\tProgressClock,\n\t\tCloseCircleOutline,\n\t},\n\n\tprops: {\n\t\t/** OpenRegister register ID */\n\t\tregisterId: {\n\t\t\ttype: String,\n\t\t\trequired: true,\n\t\t},\n\n\t\t/** OpenRegister schema ID */\n\t\tschemaId: {\n\t\t\ttype: String,\n\t\t\trequired: true,\n\t\t},\n\n\t\t/** Object UUID */\n\t\tobjectId: {\n\t\t\ttype: String,\n\t\t\trequired: true,\n\t\t},\n\n\t\t/** Base API URL for OpenRegister */\n\t\tapiBase: {\n\t\t\ttype: String,\n\t\t\tdefault: '/apps/openregister/api',\n\t\t},\n\n\t\t/** Maximum number of tasks to display */\n\t\tmaxDisplay: {\n\t\t\ttype: Number,\n\t\t\tdefault: 5,\n\t\t},\n\n\t\t/** Whether the card is collapsible */\n\t\tcollapsible: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t// --- Pre-translated labels ---\n\t\t/** Label / heading for the card title */\n\t\ttitleLabel: { type: String, default: () => t('nextcloud-vue', 'Tasks') },\n\t\t/** Text shown when no tasks are linked */\n\t\tnoTasksLabel: { type: String, default: () => t('nextcloud-vue', 'No tasks') },\n\t\t/** Label for the show-all link */\n\t\tshowAllLabel: { type: String, default: () => t('nextcloud-vue', 'Show all') },\n\t\t/** Label shown when no assignee is set */\n\t\tunassignedLabel: { type: String, default: () => t('nextcloud-vue', 'Unassigned') },\n\t},\n\n\temits: ['show-all'],\n\n\tdata() {\n\t\treturn {\n\t\t\tCheckboxMarkedOutline: markRaw(CheckboxMarkedOutline),\n\t\t\tallTasks: [],\n\t\t\tloading: false,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\tdisplayedTasks() {\n\t\t\t// Sort by due date (soonest first), then limit\n\t\t\tconst sorted = [...this.allTasks].sort((a, b) => {\n\t\t\t\tconst dateA = a.dueDate ? new Date(a.dueDate) : new Date('9999-12-31')\n\t\t\t\tconst dateB = b.dueDate ? new Date(b.dueDate) : new Date('9999-12-31')\n\t\t\t\treturn dateA - dateB\n\t\t\t})\n\t\t\treturn sorted.slice(0, this.maxDisplay)\n\t\t},\n\t},\n\n\twatch: {\n\t\tobjectId: {\n\t\t\timmediate: true,\n\t\t\thandler(newId) {\n\t\t\t\tif (newId && this.registerId && this.schemaId) {\n\t\t\t\t\tthis.fetchTasks()\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t},\n\n\tmethods: {\n\t\thasAssignee(task) {\n\t\t\treturn task.assignee && task.assignee.trim() !== ''\n\t\t},\n\n\t\tisCurrentUser(userId) {\n\t\t\tconst currentUser = typeof OC !== 'undefined' ? OC?.currentUser : null\n\t\t\treturn userId === currentUser\n\t\t},\n\n\t\tisOverdue(task) {\n\t\t\tif (!task.dueDate || task.status === 'completed') {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\ttry {\n\t\t\t\treturn new Date(task.dueDate) < new Date()\n\t\t\t} catch {\n\t\t\t\treturn false\n\t\t\t}\n\t\t},\n\n\t\tasync fetchTasks() {\n\t\t\tif (!this.registerId || !this.schemaId || !this.objectId) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\ttry {\n\t\t\t\tconst url = `${this.apiBase}/objects/${this.registerId}/${this.schemaId}/${this.objectId}/tasks`\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.allTasks = data.results || data || []\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console -- the failure is already handled; the console is the only channel a host app can read the detail on\n\t\t\t\tconsole.error('CnTasksCard: Failed to fetch tasks', err)\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\tformatDate(dateStr) {\n\t\t\tif (!dateStr) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\ttry {\n\t\t\t\treturn new Date(dateStr).toLocaleDateString(userLocale(), {\n\t\t\t\t\tyear: 'numeric',\n\t\t\t\t\tmonth: 'short',\n\t\t\t\t\tday: 'numeric',\n\t\t\t\t})\n\t\t\t} catch {\n\t\t\t\treturn dateStr\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-tasks-card__empty {\n\ttext-align: center;\n\tpadding: 16px 12px;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 13px;\n}\n\n.cn-tasks-card__list {\n\tdisplay: flex;\n\tflex-direction: column;\n}\n\n.cn-tasks-card__task {\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-tasks-card__task:last-child {\n\tborder-bottom: none;\n}\n\n.cn-tasks-card__status-icon {\n\tflex-shrink: 0;\n\tpadding-top: 1px;\n}\n\n.cn-tasks-card__icon--completed {\n\tcolor: var(--color-success);\n}\n\n.cn-tasks-card__icon--active {\n\tcolor: var(--color-primary-element);\n}\n\n.cn-tasks-card__icon--terminated {\n\tcolor: var(--color-error);\n}\n\n.cn-tasks-card__icon--available {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-tasks-card__content {\n\tflex: 1;\n\tmin-width: 0;\n}\n\n.cn-tasks-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-tasks-card__meta {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\tmargin-top: 2px;\n\tfont-size: 12px;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-tasks-card__assignee-name {\n\tcolor: var(--color-primary-element);\n\tfont-weight: 500;\n\tcursor: pointer;\n}\n\n.cn-tasks-card__assignee-name:hover {\n\ttext-decoration: underline;\n}\n\n.cn-tasks-card__assignee-name--self {\n\tcolor: var(--color-text-maxcontrast);\n\tcursor: default;\n}\n\n.cn-tasks-card__assignee-name--self:hover {\n\ttext-decoration: none;\n}\n\n.cn-tasks-card__unassigned {\n\tfont-style: italic;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-tasks-card__due-date {\n\twhite-space: nowrap;\n}\n\n.cn-tasks-card__due-date--overdue {\n\tcolor: var(--color-error);\n\tfont-weight: 500;\n}\n\n.cn-tasks-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}\n\n.cn-tasks-card__show-all:hover {\n\ttext-decoration: underline;\n}\n</style>\n"],"names":["CnDetailCard","CnUserActionMenu","t"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"CnTasksCard.vue3.js","sources":["../../../../src/components/CnTasksCard/CnTasksCard.vue?vue&type=template&id=5dad4d65&lang.js"],"sourcesContent":["<!--\n CnTasksCard — Inline tasks card for detail pages.\n\n Displays up to 5 tasks with status indicators, assignee, and due date.\n Integrates CnUserActionMenu on assignee names. Highlights overdue tasks.\n Wraps CnDetailCard for consistent styling.\n-->\n<template>\n\t<CnDetailCard :title=\"titleLabel\" :icon=\"CheckboxMarkedOutline\" :collapsible=\"collapsible\">\n\t\t<div class=\"cn-tasks-card\">\n\t\t\t<!-- Loading state -->\n\t\t\t<NcLoadingIcon v-if=\"loading\" />\n\n\t\t\t<!-- Empty state -->\n\t\t\t<div v-else-if=\"allTasks.length === 0\" class=\"cn-tasks-card__empty\">\n\t\t\t\t{{ noTasksLabel }}\n\t\t\t</div>\n\n\t\t\t<!-- Tasks list -->\n\t\t\t<div v-else class=\"cn-tasks-card__list\">\n\t\t\t\t<div\n\t\t\t\t\tv-for=\"task in displayedTasks\"\n\t\t\t\t\t:key=\"task.id\"\n\t\t\t\t\tclass=\"cn-tasks-card__task\">\n\t\t\t\t\t<!-- Status icon -->\n\t\t\t\t\t<div class=\"cn-tasks-card__status-icon\">\n\t\t\t\t\t\t<CheckboxMarkedOutline\n\t\t\t\t\t\t\tv-if=\"task.status === 'completed'\"\n\t\t\t\t\t\t\t:size=\"20\"\n\t\t\t\t\t\t\tclass=\"cn-tasks-card__icon--completed\" />\n\t\t\t\t\t\t<ProgressClock\n\t\t\t\t\t\t\tv-else-if=\"task.status === 'active' || task.status === 'in-process'\"\n\t\t\t\t\t\t\t:size=\"20\"\n\t\t\t\t\t\t\tclass=\"cn-tasks-card__icon--active\" />\n\t\t\t\t\t\t<CloseCircleOutline\n\t\t\t\t\t\t\tv-else-if=\"task.status === 'terminated'\"\n\t\t\t\t\t\t\t:size=\"20\"\n\t\t\t\t\t\t\tclass=\"cn-tasks-card__icon--terminated\" />\n\t\t\t\t\t\t<CheckboxBlankOutline\n\t\t\t\t\t\t\tv-else\n\t\t\t\t\t\t\t:size=\"20\"\n\t\t\t\t\t\t\tclass=\"cn-tasks-card__icon--available\" />\n\t\t\t\t\t</div>\n\n\t\t\t\t\t<!-- Task content -->\n\t\t\t\t\t<div class=\"cn-tasks-card__content\">\n\t\t\t\t\t\t<span class=\"cn-tasks-card__title\">{{ task.title || task.name }}</span>\n\t\t\t\t\t\t<div class=\"cn-tasks-card__meta\">\n\t\t\t\t\t\t\t<!-- Assignee -->\n\t\t\t\t\t\t\t<span v-if=\"hasAssignee(task)\" class=\"cn-tasks-card__assignee\">\n\t\t\t\t\t\t\t\t<CnUserActionMenu\n\t\t\t\t\t\t\t\t\tv-if=\"!isCurrentUser(task.assignee)\"\n\t\t\t\t\t\t\t\t\t:userId=\"task.assignee\"\n\t\t\t\t\t\t\t\t\t:displayName=\"task.assignee\">\n\t\t\t\t\t\t\t\t\t<span class=\"cn-tasks-card__assignee-name\">{{ task.assignee }}</span>\n\t\t\t\t\t\t\t\t</CnUserActionMenu>\n\t\t\t\t\t\t\t\t<span v-else class=\"cn-tasks-card__assignee-name cn-tasks-card__assignee-name--self\">\n\t\t\t\t\t\t\t\t\t{{ task.assignee }}\n\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t<span v-else class=\"cn-tasks-card__unassigned\">\n\t\t\t\t\t\t\t\t{{ unassignedLabel }}\n\t\t\t\t\t\t\t</span>\n\n\t\t\t\t\t\t\t<!-- Due date -->\n\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\tv-if=\"task.dueDate\"\n\t\t\t\t\t\t\t\tclass=\"cn-tasks-card__due-date\"\n\t\t\t\t\t\t\t\t:class=\"{ 'cn-tasks-card__due-date--overdue': isOverdue(task) }\">\n\t\t\t\t\t\t\t\t{{ formatDate(task.dueDate) }}\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\n\t\t<!-- Footer: \"Show all\" link -->\n\t\t<template v-if=\"allTasks.length > maxDisplay\" #footer>\n\t\t\t<button\n\t\t\t\tclass=\"cn-tasks-card__show-all\"\n\t\t\t\t@click=\"$emit('show-all')\">\n\t\t\t\t{{ showAllLabel }} ({{ allTasks.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 { NcLoadingIcon } from '@nextcloud/vue'\nimport { markRaw } from 'vue'\nimport CheckboxBlankOutline from 'vue-material-design-icons/CheckboxBlankOutline.vue'\nimport CheckboxMarkedOutline from 'vue-material-design-icons/CheckboxMarkedOutline.vue'\nimport CloseCircleOutline from 'vue-material-design-icons/CloseCircleOutline.vue'\nimport ProgressClock from 'vue-material-design-icons/ProgressClock.vue'\nimport CnDetailCard from '../CnDetailCard/CnDetailCard.vue'\nimport CnUserActionMenu from '../CnUserActionMenu/CnUserActionMenu.vue'\nimport { buildHeaders, prefixUrl } from '../../utils/index.js'\n\n/**\n * CnTasksCard — Inline tasks widget for detail pages.\n *\n * Shows up to 5 tasks sorted by due date with status indicators.\n * Highlights overdue tasks and integrates CnUserActionMenu on assignees.\n *\n * Basic usage\n * ```vue\n * <CnTasksCard\n * register-id=\"uuid-register\"\n * schema-id=\"uuid-schema\"\n * object-id=\"uuid-object\" />\n * ```\n *\n * With sidebar sync\n * ```vue\n * <CnTasksCard\n * register-id=\"reg\"\n * schema-id=\"schema\"\n * object-id=\"obj\"\n * @show-all=\"openSidebarTasksTab\" />\n * ```\n */\nexport default {\n\tname: 'CnTasksCard',\n\n\tcomponents: {\n\t\tCnDetailCard,\n\t\tCnUserActionMenu,\n\t\tNcLoadingIcon,\n\t\tCheckboxMarkedOutline,\n\t\tCheckboxBlankOutline,\n\t\tProgressClock,\n\t\tCloseCircleOutline,\n\t},\n\n\tprops: {\n\t\t/** OpenRegister register ID */\n\t\tregisterId: {\n\t\t\ttype: String,\n\t\t\trequired: true,\n\t\t},\n\n\t\t/** OpenRegister schema ID */\n\t\tschemaId: {\n\t\t\ttype: String,\n\t\t\trequired: true,\n\t\t},\n\n\t\t/** Object UUID */\n\t\tobjectId: {\n\t\t\ttype: String,\n\t\t\trequired: true,\n\t\t},\n\n\t\t/** Base API URL for OpenRegister */\n\t\tapiBase: {\n\t\t\ttype: String,\n\t\t\tdefault: '/apps/openregister/api',\n\t\t},\n\n\t\t/** Maximum number of tasks to display */\n\t\tmaxDisplay: {\n\t\t\ttype: Number,\n\t\t\tdefault: 5,\n\t\t},\n\n\t\t/** Whether the card is collapsible */\n\t\tcollapsible: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t// --- Pre-translated labels ---\n\t\t/** Label / heading for the card title */\n\t\ttitleLabel: { type: String, default: () => t('nextcloud-vue', 'Tasks') },\n\t\t/** Text shown when no tasks are linked */\n\t\tnoTasksLabel: { type: String, default: () => t('nextcloud-vue', 'No tasks') },\n\t\t/** Label for the show-all link */\n\t\tshowAllLabel: { type: String, default: () => t('nextcloud-vue', 'Show all') },\n\t\t/** Label shown when no assignee is set */\n\t\tunassignedLabel: { type: String, default: () => t('nextcloud-vue', 'Unassigned') },\n\t},\n\n\temits: ['show-all'],\n\n\tdata() {\n\t\treturn {\n\t\t\tCheckboxMarkedOutline: markRaw(CheckboxMarkedOutline),\n\t\t\tallTasks: [],\n\t\t\tloading: false,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\tdisplayedTasks() {\n\t\t\t// Sort by due date (soonest first), then limit\n\t\t\tconst sorted = [...this.allTasks].sort((a, b) => {\n\t\t\t\tconst dateA = a.dueDate ? new Date(a.dueDate) : new Date('9999-12-31')\n\t\t\t\tconst dateB = b.dueDate ? new Date(b.dueDate) : new Date('9999-12-31')\n\t\t\t\treturn dateA - dateB\n\t\t\t})\n\t\t\treturn sorted.slice(0, this.maxDisplay)\n\t\t},\n\t},\n\n\twatch: {\n\t\tobjectId: {\n\t\t\timmediate: true,\n\t\t\thandler(newId) {\n\t\t\t\tif (newId && this.registerId && this.schemaId) {\n\t\t\t\t\tthis.fetchTasks()\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t},\n\n\tmethods: {\n\t\thasAssignee(task) {\n\t\t\treturn task.assignee && task.assignee.trim() !== ''\n\t\t},\n\n\t\tisCurrentUser(userId) {\n\t\t\tconst currentUser = typeof OC !== 'undefined' ? OC?.currentUser : null\n\t\t\treturn userId === currentUser\n\t\t},\n\n\t\tisOverdue(task) {\n\t\t\tif (!task.dueDate || task.status === 'completed') {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\ttry {\n\t\t\t\treturn new Date(task.dueDate) < new Date()\n\t\t\t} catch {\n\t\t\t\treturn false\n\t\t\t}\n\t\t},\n\n\t\tasync fetchTasks() {\n\t\t\tif (!this.registerId || !this.schemaId || !this.objectId) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\ttry {\n\t\t\t\tconst url = `${this.apiBase}/objects/${this.registerId}/${this.schemaId}/${this.objectId}/tasks`\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.allTasks = data.results || data || []\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console -- the failure is already handled; the console is the only channel a host app can read the detail on\n\t\t\t\tconsole.error('CnTasksCard: Failed to fetch tasks', err)\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\tformatDate(dateStr) {\n\t\t\tif (!dateStr) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\ttry {\n\t\t\t\treturn new Date(dateStr).toLocaleDateString(undefined, {\n\t\t\t\t\tyear: 'numeric',\n\t\t\t\t\tmonth: 'short',\n\t\t\t\t\tday: 'numeric',\n\t\t\t\t})\n\t\t\t} catch {\n\t\t\t\treturn dateStr\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-tasks-card__empty {\n\ttext-align: center;\n\tpadding: 16px 12px;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 13px;\n}\n\n.cn-tasks-card__list {\n\tdisplay: flex;\n\tflex-direction: column;\n}\n\n.cn-tasks-card__task {\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-tasks-card__task:last-child {\n\tborder-bottom: none;\n}\n\n.cn-tasks-card__status-icon {\n\tflex-shrink: 0;\n\tpadding-top: 1px;\n}\n\n.cn-tasks-card__icon--completed {\n\tcolor: var(--color-success);\n}\n\n.cn-tasks-card__icon--active {\n\tcolor: var(--color-primary-element);\n}\n\n.cn-tasks-card__icon--terminated {\n\tcolor: var(--color-error);\n}\n\n.cn-tasks-card__icon--available {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-tasks-card__content {\n\tflex: 1;\n\tmin-width: 0;\n}\n\n.cn-tasks-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-tasks-card__meta {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\tmargin-top: 2px;\n\tfont-size: 12px;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-tasks-card__assignee-name {\n\tcolor: var(--color-primary-element);\n\tfont-weight: 500;\n\tcursor: pointer;\n}\n\n.cn-tasks-card__assignee-name:hover {\n\ttext-decoration: underline;\n}\n\n.cn-tasks-card__assignee-name--self {\n\tcolor: var(--color-text-maxcontrast);\n\tcursor: default;\n}\n\n.cn-tasks-card__assignee-name--self:hover {\n\ttext-decoration: none;\n}\n\n.cn-tasks-card__unassigned {\n\tfont-style: italic;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-tasks-card__due-date {\n\twhite-space: nowrap;\n}\n\n.cn-tasks-card__due-date--overdue {\n\tcolor: var(--color-error);\n\tfont-weight: 500;\n}\n\n.cn-tasks-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}\n\n.cn-tasks-card__show-all:hover {\n\ttext-decoration: underline;\n}\n</style>\n"],"names":["_createBlock","_toDisplayString","_openBlock","_createElementBlock","_Fragment","_renderList","_createElementVNode","_normalizeClass"],"mappings":"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"}
1
+ {"version":3,"file":"CnTasksCard.vue3.js","sources":["../../../../src/components/CnTasksCard/CnTasksCard.vue?vue&type=template&id=1333abd5&lang.js"],"sourcesContent":["<!--\n CnTasksCard — Inline tasks card for detail pages.\n\n Displays up to 5 tasks with status indicators, assignee, and due date.\n Integrates CnUserActionMenu on assignee names. Highlights overdue tasks.\n Wraps CnDetailCard for consistent styling.\n-->\n<template>\n\t<CnDetailCard :title=\"titleLabel\" :icon=\"CheckboxMarkedOutline\" :collapsible=\"collapsible\">\n\t\t<div class=\"cn-tasks-card\">\n\t\t\t<!-- Loading state -->\n\t\t\t<NcLoadingIcon v-if=\"loading\" />\n\n\t\t\t<!-- Empty state -->\n\t\t\t<div v-else-if=\"allTasks.length === 0\" class=\"cn-tasks-card__empty\">\n\t\t\t\t{{ noTasksLabel }}\n\t\t\t</div>\n\n\t\t\t<!-- Tasks list -->\n\t\t\t<div v-else class=\"cn-tasks-card__list\">\n\t\t\t\t<div\n\t\t\t\t\tv-for=\"task in displayedTasks\"\n\t\t\t\t\t:key=\"task.id\"\n\t\t\t\t\tclass=\"cn-tasks-card__task\">\n\t\t\t\t\t<!-- Status icon -->\n\t\t\t\t\t<div class=\"cn-tasks-card__status-icon\">\n\t\t\t\t\t\t<CheckboxMarkedOutline\n\t\t\t\t\t\t\tv-if=\"task.status === 'completed'\"\n\t\t\t\t\t\t\t:size=\"20\"\n\t\t\t\t\t\t\tclass=\"cn-tasks-card__icon--completed\" />\n\t\t\t\t\t\t<ProgressClock\n\t\t\t\t\t\t\tv-else-if=\"task.status === 'active' || task.status === 'in-process'\"\n\t\t\t\t\t\t\t:size=\"20\"\n\t\t\t\t\t\t\tclass=\"cn-tasks-card__icon--active\" />\n\t\t\t\t\t\t<CloseCircleOutline\n\t\t\t\t\t\t\tv-else-if=\"task.status === 'terminated'\"\n\t\t\t\t\t\t\t:size=\"20\"\n\t\t\t\t\t\t\tclass=\"cn-tasks-card__icon--terminated\" />\n\t\t\t\t\t\t<CheckboxBlankOutline\n\t\t\t\t\t\t\tv-else\n\t\t\t\t\t\t\t:size=\"20\"\n\t\t\t\t\t\t\tclass=\"cn-tasks-card__icon--available\" />\n\t\t\t\t\t</div>\n\n\t\t\t\t\t<!-- Task content -->\n\t\t\t\t\t<div class=\"cn-tasks-card__content\">\n\t\t\t\t\t\t<span class=\"cn-tasks-card__title\">{{ task.title || task.name }}</span>\n\t\t\t\t\t\t<div class=\"cn-tasks-card__meta\">\n\t\t\t\t\t\t\t<!-- Assignee -->\n\t\t\t\t\t\t\t<span v-if=\"hasAssignee(task)\" class=\"cn-tasks-card__assignee\">\n\t\t\t\t\t\t\t\t<CnUserActionMenu\n\t\t\t\t\t\t\t\t\tv-if=\"!isCurrentUser(task.assignee)\"\n\t\t\t\t\t\t\t\t\t:userId=\"task.assignee\"\n\t\t\t\t\t\t\t\t\t:displayName=\"task.assignee\">\n\t\t\t\t\t\t\t\t\t<span class=\"cn-tasks-card__assignee-name\">{{ task.assignee }}</span>\n\t\t\t\t\t\t\t\t</CnUserActionMenu>\n\t\t\t\t\t\t\t\t<span v-else class=\"cn-tasks-card__assignee-name cn-tasks-card__assignee-name--self\">\n\t\t\t\t\t\t\t\t\t{{ task.assignee }}\n\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t<span v-else class=\"cn-tasks-card__unassigned\">\n\t\t\t\t\t\t\t\t{{ unassignedLabel }}\n\t\t\t\t\t\t\t</span>\n\n\t\t\t\t\t\t\t<!-- Due date -->\n\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\tv-if=\"task.dueDate\"\n\t\t\t\t\t\t\t\tclass=\"cn-tasks-card__due-date\"\n\t\t\t\t\t\t\t\t:class=\"{ 'cn-tasks-card__due-date--overdue': isOverdue(task) }\">\n\t\t\t\t\t\t\t\t{{ formatDate(task.dueDate) }}\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\n\t\t<!-- Footer: \"Show all\" link -->\n\t\t<template v-if=\"allTasks.length > maxDisplay\" #footer>\n\t\t\t<button\n\t\t\t\tclass=\"cn-tasks-card__show-all\"\n\t\t\t\t@click=\"$emit('show-all')\">\n\t\t\t\t{{ showAllLabel }} ({{ allTasks.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 { NcLoadingIcon } from '@nextcloud/vue'\nimport { markRaw } from 'vue'\nimport CheckboxBlankOutline from 'vue-material-design-icons/CheckboxBlankOutline.vue'\nimport CheckboxMarkedOutline from 'vue-material-design-icons/CheckboxMarkedOutline.vue'\nimport CloseCircleOutline from 'vue-material-design-icons/CloseCircleOutline.vue'\nimport ProgressClock from 'vue-material-design-icons/ProgressClock.vue'\nimport CnDetailCard from '../CnDetailCard/CnDetailCard.vue'\nimport CnUserActionMenu from '../CnUserActionMenu/CnUserActionMenu.vue'\nimport { buildHeaders, prefixUrl } from '../../utils/index.js'\nimport { userLocale } from '../../utils/userLocale.js'\n\n/**\n * CnTasksCard — Inline tasks widget for detail pages.\n *\n * Shows up to 5 tasks sorted by due date with status indicators.\n * Highlights overdue tasks and integrates CnUserActionMenu on assignees.\n *\n * Basic usage\n * ```vue\n * <CnTasksCard\n * register-id=\"uuid-register\"\n * schema-id=\"uuid-schema\"\n * object-id=\"uuid-object\" />\n * ```\n *\n * With sidebar sync\n * ```vue\n * <CnTasksCard\n * register-id=\"reg\"\n * schema-id=\"schema\"\n * object-id=\"obj\"\n * @show-all=\"openSidebarTasksTab\" />\n * ```\n */\nexport default {\n\tname: 'CnTasksCard',\n\n\tcomponents: {\n\t\tCnDetailCard,\n\t\tCnUserActionMenu,\n\t\tNcLoadingIcon,\n\t\tCheckboxMarkedOutline,\n\t\tCheckboxBlankOutline,\n\t\tProgressClock,\n\t\tCloseCircleOutline,\n\t},\n\n\tprops: {\n\t\t/** OpenRegister register ID */\n\t\tregisterId: {\n\t\t\ttype: String,\n\t\t\trequired: true,\n\t\t},\n\n\t\t/** OpenRegister schema ID */\n\t\tschemaId: {\n\t\t\ttype: String,\n\t\t\trequired: true,\n\t\t},\n\n\t\t/** Object UUID */\n\t\tobjectId: {\n\t\t\ttype: String,\n\t\t\trequired: true,\n\t\t},\n\n\t\t/** Base API URL for OpenRegister */\n\t\tapiBase: {\n\t\t\ttype: String,\n\t\t\tdefault: '/apps/openregister/api',\n\t\t},\n\n\t\t/** Maximum number of tasks to display */\n\t\tmaxDisplay: {\n\t\t\ttype: Number,\n\t\t\tdefault: 5,\n\t\t},\n\n\t\t/** Whether the card is collapsible */\n\t\tcollapsible: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t// --- Pre-translated labels ---\n\t\t/** Label / heading for the card title */\n\t\ttitleLabel: { type: String, default: () => t('nextcloud-vue', 'Tasks') },\n\t\t/** Text shown when no tasks are linked */\n\t\tnoTasksLabel: { type: String, default: () => t('nextcloud-vue', 'No tasks') },\n\t\t/** Label for the show-all link */\n\t\tshowAllLabel: { type: String, default: () => t('nextcloud-vue', 'Show all') },\n\t\t/** Label shown when no assignee is set */\n\t\tunassignedLabel: { type: String, default: () => t('nextcloud-vue', 'Unassigned') },\n\t},\n\n\temits: ['show-all'],\n\n\tdata() {\n\t\treturn {\n\t\t\tCheckboxMarkedOutline: markRaw(CheckboxMarkedOutline),\n\t\t\tallTasks: [],\n\t\t\tloading: false,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\tdisplayedTasks() {\n\t\t\t// Sort by due date (soonest first), then limit\n\t\t\tconst sorted = [...this.allTasks].sort((a, b) => {\n\t\t\t\tconst dateA = a.dueDate ? new Date(a.dueDate) : new Date('9999-12-31')\n\t\t\t\tconst dateB = b.dueDate ? new Date(b.dueDate) : new Date('9999-12-31')\n\t\t\t\treturn dateA - dateB\n\t\t\t})\n\t\t\treturn sorted.slice(0, this.maxDisplay)\n\t\t},\n\t},\n\n\twatch: {\n\t\tobjectId: {\n\t\t\timmediate: true,\n\t\t\thandler(newId) {\n\t\t\t\tif (newId && this.registerId && this.schemaId) {\n\t\t\t\t\tthis.fetchTasks()\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t},\n\n\tmethods: {\n\t\thasAssignee(task) {\n\t\t\treturn task.assignee && task.assignee.trim() !== ''\n\t\t},\n\n\t\tisCurrentUser(userId) {\n\t\t\tconst currentUser = typeof OC !== 'undefined' ? OC?.currentUser : null\n\t\t\treturn userId === currentUser\n\t\t},\n\n\t\tisOverdue(task) {\n\t\t\tif (!task.dueDate || task.status === 'completed') {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\ttry {\n\t\t\t\treturn new Date(task.dueDate) < new Date()\n\t\t\t} catch {\n\t\t\t\treturn false\n\t\t\t}\n\t\t},\n\n\t\tasync fetchTasks() {\n\t\t\tif (!this.registerId || !this.schemaId || !this.objectId) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\ttry {\n\t\t\t\tconst url = `${this.apiBase}/objects/${this.registerId}/${this.schemaId}/${this.objectId}/tasks`\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.allTasks = data.results || data || []\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console -- the failure is already handled; the console is the only channel a host app can read the detail on\n\t\t\t\tconsole.error('CnTasksCard: Failed to fetch tasks', err)\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\tformatDate(dateStr) {\n\t\t\tif (!dateStr) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\ttry {\n\t\t\t\treturn new Date(dateStr).toLocaleDateString(userLocale(), {\n\t\t\t\t\tyear: 'numeric',\n\t\t\t\t\tmonth: 'short',\n\t\t\t\t\tday: 'numeric',\n\t\t\t\t})\n\t\t\t} catch {\n\t\t\t\treturn dateStr\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-tasks-card__empty {\n\ttext-align: center;\n\tpadding: 16px 12px;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 13px;\n}\n\n.cn-tasks-card__list {\n\tdisplay: flex;\n\tflex-direction: column;\n}\n\n.cn-tasks-card__task {\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-tasks-card__task:last-child {\n\tborder-bottom: none;\n}\n\n.cn-tasks-card__status-icon {\n\tflex-shrink: 0;\n\tpadding-top: 1px;\n}\n\n.cn-tasks-card__icon--completed {\n\tcolor: var(--color-success);\n}\n\n.cn-tasks-card__icon--active {\n\tcolor: var(--color-primary-element);\n}\n\n.cn-tasks-card__icon--terminated {\n\tcolor: var(--color-error);\n}\n\n.cn-tasks-card__icon--available {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-tasks-card__content {\n\tflex: 1;\n\tmin-width: 0;\n}\n\n.cn-tasks-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-tasks-card__meta {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\tmargin-top: 2px;\n\tfont-size: 12px;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-tasks-card__assignee-name {\n\tcolor: var(--color-primary-element);\n\tfont-weight: 500;\n\tcursor: pointer;\n}\n\n.cn-tasks-card__assignee-name:hover {\n\ttext-decoration: underline;\n}\n\n.cn-tasks-card__assignee-name--self {\n\tcolor: var(--color-text-maxcontrast);\n\tcursor: default;\n}\n\n.cn-tasks-card__assignee-name--self:hover {\n\ttext-decoration: none;\n}\n\n.cn-tasks-card__unassigned {\n\tfont-style: italic;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-tasks-card__due-date {\n\twhite-space: nowrap;\n}\n\n.cn-tasks-card__due-date--overdue {\n\tcolor: var(--color-error);\n\tfont-weight: 500;\n}\n\n.cn-tasks-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}\n\n.cn-tasks-card__show-all:hover {\n\ttext-decoration: underline;\n}\n</style>\n"],"names":["_createBlock","_toDisplayString","_openBlock","_createElementBlock","_Fragment","_renderList","_createElementVNode","_normalizeClass"],"mappings":"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"}
@@ -2,7 +2,7 @@ import script from './CnTimelineWidget.vue2.js';
2
2
  import { render } from './CnTimelineWidget.vue3.js';
3
3
 
4
4
  script.render = render;
5
- script.__scopeId = "data-v-60089a65";
5
+ script.__scopeId = "data-v-091aa98d";
6
6
  const __sfc_main = script;
7
7
 
8
8
  export { __sfc_main as default };
@@ -1,6 +1,7 @@
1
1
  import { translate, getLanguage } from '@nextcloud/l10n';
2
2
  import { NcLoadingIcon, NcNoteCard } from '@nextcloud/vue';
3
3
  import { fieldEvents, listEvents, relatedEvents, auditEvents, timelineEntryEvents, sortEvents } from '../../utils/timelineEvents.js';
4
+ import { userLocale } from '../../utils/userLocale.js';
4
5
 
5
6
  /**
6
7
  * CnTimelineWidget — an object's dated events in time order (`timeline`
@@ -243,7 +244,7 @@ var script = {
243
244
  try {
244
245
  return new Intl.DateTimeFormat(locale, options).format(event.at)
245
246
  } catch {
246
- return event.at.toLocaleString()
247
+ return event.at.toLocaleString(userLocale())
247
248
  }
248
249
  },
249
250
 
@@ -1 +1 @@
1
- {"version":3,"file":"CnTimelineWidget.vue2.js","sources":["../../../../src/components/CnTimelineWidget/CnTimelineWidget.vue"],"sourcesContent":["<!-- SPDX-License-Identifier: EUPL-1.2 -->\n<!-- SPDX-FileCopyrightText: 2026 Conduction B.V. <info@conduction.nl> -->\n<template>\n\t<section class=\"cn-timeline-widget\" data-testid=\"cn-timeline-widget\" :aria-busy=\"loading ? 'true' : 'false'\">\n\t\t<h3 v-if=\"resolvedTitle\" class=\"cn-timeline-widget__title\">\n\t\t\t{{ resolvedTitle }}\n\t\t</h3>\n\n\t\t<div v-if=\"loading && events.length === 0\" class=\"cn-timeline-widget__loading\">\n\t\t\t<NcLoadingIcon :size=\"24\" aria-hidden=\"true\" />\n\t\t\t<span>{{ loadingLabel }}</span>\n\t\t</div>\n\n\t\t<template v-else>\n\t\t\t<!-- A failed read is not an empty history: say which part is missing. -->\n\t\t\t<NcNoteCard v-if=\"failedSources.length > 0\" type=\"warning\" data-testid=\"cn-timeline-widget-error\">\n\t\t\t\t{{ failedText }}\n\t\t\t</NcNoteCard>\n\n\t\t\t<p v-if=\"events.length === 0\" class=\"cn-timeline-widget__empty\" data-testid=\"cn-timeline-widget-empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</p>\n\n\t\t\t<ol v-else class=\"cn-timeline-widget__list\">\n\t\t\t\t<li\n\t\t\t\t\tv-for=\"event in events\"\n\t\t\t\t\t:key=\"event.id\"\n\t\t\t\t\tclass=\"cn-timeline-widget__event\"\n\t\t\t\t\t:class=\"{ 'cn-timeline-widget__event--upcoming': event.upcoming }\"\n\t\t\t\t\t:data-source=\"event.source\"\n\t\t\t\t\tdata-testid=\"cn-timeline-widget-event\">\n\t\t\t\t\t<span class=\"cn-timeline-widget__dot\" aria-hidden=\"true\" />\n\t\t\t\t\t<div class=\"cn-timeline-widget__body\">\n\t\t\t\t\t\t<time class=\"cn-timeline-widget__when\" :datetime=\"event.at.toISOString()\">\n\t\t\t\t\t\t\t{{ formatMoment(event) }}\n\t\t\t\t\t\t</time>\n\t\t\t\t\t\t<span v-if=\"event.upcoming\" class=\"cn-timeline-widget__badge\">\n\t\t\t\t\t\t\t{{ upcomingLabel }}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<p class=\"cn-timeline-widget__label\">\n\t\t\t\t\t\t\t{{ translate(event.label) }}\n\t\t\t\t\t\t</p>\n\t\t\t\t\t\t<p v-if=\"event.detail\" class=\"cn-timeline-widget__detail\">\n\t\t\t\t\t\t\t{{ event.detail }}\n\t\t\t\t\t\t</p>\n\t\t\t\t\t</div>\n\t\t\t\t</li>\n\t\t\t</ol>\n\t\t</template>\n\t</section>\n</template>\n\n<script>\nimport { getLanguage, translate as t } from '@nextcloud/l10n'\nimport { NcLoadingIcon, NcNoteCard } from '@nextcloud/vue'\nimport { auditEvents, fieldEvents, listEvents, relatedEvents, sortEvents, timelineEntryEvents } from '../../utils/timelineEvents.js'\n\n/**\n * CnTimelineWidget — an object's dated events in time order (`timeline`\n * widget type for detail pages).\n *\n * Generalises dossiq's case timeline. It merges up to four sources, all\n * optional, into one list:\n *\n * - `fields`: the object's own date properties, `[{ field, label }]`. A\n * dotted path reaches metadata, such as `@self.created`.\n * - `related`: related objects, `[{ register?, schema, field, dateField?,\n * label, titleField? }]`. Rows of `schema` whose `field` holds this object's\n * id become events at their `dateField` (default `@self.created`).\n * - `auditTrail: true`: the object's audit trail, one event per change.\n * - `timeline: true`: OpenRegister's timeline of notes, calls and messages.\n *\n * Oldest first by default (`order: 'desc'` flips it). A moment after now is\n * marked upcoming, in words as well as style.\n *\n * ```json\n * { \"type\": \"timeline\", \"content\": {\n * \"title\": \"Timeline\",\n * \"fields\": [\n * { \"field\": \"@self.created\", \"label\": \"Booking created\" },\n * { \"field\": \"depositClearedAt\", \"label\": \"Deposit cleared\" },\n * { \"field\": \"confirmationSentAt\", \"label\": \"Confirmation mail sent\" },\n * { \"field\": \"startsAt\", \"label\": \"Starts\" },\n * { \"field\": \"endsAt\", \"label\": \"Ends\" }\n * ] } }\n * ```\n *\n * The object comes from the detail page's context, like `audit-trail`:\n * explicit props win, then the injects, then `content`.\n *\n * @spec openspec/changes/timeline-widget/specs/timeline-widget/spec.md#requirement-a-timeline-widget-shows-an-objects-dated-events-in-order\n */\nexport default {\n\tname: 'CnTimelineWidget',\n\n\tcomponents: {\n\t\tNcLoadingIcon,\n\t\tNcNoteCard,\n\t},\n\n\tinject: {\n\t\t/** Detail-page object context from CnDetailPage (a ref or a raw bag). */\n\t\tcnObjectContext: { default: null },\n\t\t/** Detail object context holder from CnPageRenderer (`{ value: { objectData, ... } }`). */\n\t\tcnDetailObjectContext: { default: null },\n\t\t/** Consumer translation function; labels are authored in English. */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/** Register slug; object context spread as props by CnWidgetGrid. */\n\t\tregister: { type: String, default: '' },\n\t\t/**\n\t\t * Schema slug, or the schema object the detail context supplies.\n\t\t *\n\t\t * @type {string|object}\n\t\t */\n\t\tschema: { type: [String, Object], default: '' },\n\t\t/** The object's id. */\n\t\tobjectId: { type: String, default: '' },\n\t\t/** The object itself, when the host already has it. */\n\t\tobject: { type: Object, default: null },\n\t\t/** Card title override. */\n\t\ttitle: { type: String, default: '' },\n\t\t/** Stored widget content: `{ title, fields, related, auditTrail, timeline, order }`. */\n\t\tcontent: { type: Object, default: () => ({}) },\n\t\t/** OpenRegister API base. */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tloading: false,\n\t\t\trelatedRows: {},\n\t\t\tauditEntries: [],\n\t\t\ttimelineEntries: [],\n\t\t\tfailedSources: [],\n\t\t\tfetchedObject: null,\n\t\t\tloadSeq: 0,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\tctx() {\n\t\t\tconst raw = this.cnObjectContext\n\t\t\tconst inj = raw && (raw.value !== undefined ? raw.value : raw)\n\t\t\tconst holder = this.cnDetailObjectContext && this.cnDetailObjectContext.value\n\t\t\treturn inj || holder || {}\n\t\t},\n\n\t\tresolvedObjectId() {\n\t\t\treturn this.objectId || this.ctx.objectId || this.content.objectId || ''\n\t\t},\n\n\t\tresolvedRegister() {\n\t\t\treturn this.register || this.ctx.register || this.content.register || ''\n\t\t},\n\n\t\tresolvedSchema() {\n\t\t\tconst s = this.schema || this.ctx.schema || this.content.schema || ''\n\t\t\treturn typeof s === 'string' ? s : (s && (s.slug || s.name || s.id)) || ''\n\t\t},\n\n\t\t/**\n\t\t * The object: explicit prop, then the page context, then a fetch of\n\t\t * our own when only the id is known.\n\t\t *\n\t\t * @return {object|null}\n\t\t */\n\t\tresolvedObject() {\n\t\t\treturn this.object || this.ctx.object || this.ctx.objectData || this.fetchedObject || null\n\t\t},\n\n\t\tresolvedTitle() {\n\t\t\tconst raw = this.title || this.content.title || ''\n\t\t\treturn raw ? this.translate(raw) : ''\n\t\t},\n\n\t\tfieldsConfig() {\n\t\t\treturn Array.isArray(this.content.fields) ? this.content.fields : []\n\t\t},\n\n\t\trelatedConfig() {\n\t\t\treturn Array.isArray(this.content.related) ? this.content.related.filter((r) => r && r.schema && r.field) : []\n\t\t},\n\n\t\t/**\n\t\t * Every event from every source, in time order.\n\t\t *\n\t\t * @return {Array<object>}\n\t\t */\n\t\tevents() {\n\t\t\tconst now = new Date()\n\t\t\tconst all = [\n\t\t\t\t...fieldEvents(this.resolvedObject || {}, this.fieldsConfig, now),\n\t\t\t\t...listEvents(this.resolvedObject || {}, this.content.lists, now),\n\t\t\t\t...this.relatedConfig.flatMap((cfg, i) => relatedEvents(this.relatedRows[i] || [], cfg, now)),\n\t\t\t\t...auditEvents(this.auditEntries, this.describeAudit, now),\n\t\t\t\t...timelineEntryEvents(this.timelineEntries, t('nextcloud-vue', 'Note'), now),\n\t\t\t]\n\t\t\treturn sortEvents(all, this.content.order === 'desc' ? 'desc' : 'asc')\n\t\t},\n\n\t\t/**\n\t\t * A signature of what to fetch, so a change refetches once.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tloadKey() {\n\t\t\treturn JSON.stringify({\n\t\t\t\tid: this.resolvedObjectId,\n\t\t\t\tregister: this.resolvedRegister,\n\t\t\t\tschema: this.resolvedSchema,\n\t\t\t\trelated: this.relatedConfig,\n\t\t\t\taudit: this.content.auditTrail === true,\n\t\t\t\ttimeline: this.content.timeline === true,\n\t\t\t\tneedObject: this.fieldsConfig.length > 0 && !(this.object || this.ctx.object || this.ctx.objectData),\n\t\t\t})\n\t\t},\n\n\t\tfailedText() {\n\t\t\treturn t('nextcloud-vue', 'Part of the timeline could not load: {parts}.', {\n\t\t\t\tparts: this.failedSources.map((s) => this.sourceName(s)).join(', '),\n\t\t\t})\n\t\t},\n\n\t\tloadingLabel() {\n\t\t\treturn t('nextcloud-vue', 'Loading…')\n\t\t},\n\n\t\temptyLabel() {\n\t\t\treturn t('nextcloud-vue', 'Nothing has happened yet.')\n\t\t},\n\n\t\tupcomingLabel() {\n\t\t\treturn t('nextcloud-vue', 'Upcoming')\n\t\t},\n\t},\n\n\twatch: {\n\t\tloadKey: {\n\t\t\timmediate: true,\n\t\t\thandler() {\n\t\t\t\tthis.load()\n\t\t\t},\n\t\t},\n\t},\n\n\tmethods: {\n\t\ttranslate(text) {\n\t\t\treturn text ? this.cnTranslate(text) : ''\n\t\t},\n\n\t\tsourceName(source) {\n\t\t\tif (source === 'audit') {\n\t\t\t\treturn t('nextcloud-vue', 'changes')\n\t\t\t}\n\t\t\tif (source === 'timeline') {\n\t\t\t\treturn t('nextcloud-vue', 'notes and messages')\n\t\t\t}\n\t\t\tif (source === 'object') {\n\t\t\t\treturn t('nextcloud-vue', 'dates')\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', 'related items')\n\t\t},\n\n\t\t/**\n\t\t * The label of an audit event, from its action and who did it.\n\t\t *\n\t\t * @param {string} action The audit action (create, update, delete).\n\t\t * @param {string} actor Who did it.\n\t\t * @return {string}\n\t\t */\n\t\tdescribeAudit(action, actor) {\n\t\t\tconst known = {\n\t\t\t\tcreate: t('nextcloud-vue', 'Created'),\n\t\t\t\tupdate: t('nextcloud-vue', 'Updated'),\n\t\t\t\tdelete: t('nextcloud-vue', 'Deleted'),\n\t\t\t}\n\t\t\tconst what = known[action] || action || t('nextcloud-vue', 'Changed')\n\t\t\treturn actor ? t('nextcloud-vue', '{action} by {actor}', { action: what, actor }) : what\n\t\t},\n\n\t\t/**\n\t\t * Show a moment in the reader's language: a date for a calendar day,\n\t\t * date and time otherwise.\n\t\t *\n\t\t * @param {object} event The event.\n\t\t * @return {string}\n\t\t */\n\t\tformatMoment(event) {\n\t\t\tconst locale = (typeof getLanguage === 'function' && getLanguage()) || undefined\n\t\t\tconst options = event.dateOnly\n\t\t\t\t? { dateStyle: 'medium' }\n\t\t\t\t: { dateStyle: 'medium', timeStyle: 'short' }\n\t\t\ttry {\n\t\t\t\treturn new Intl.DateTimeFormat(locale, options).format(event.at)\n\t\t\t} catch {\n\t\t\t\treturn event.at.toLocaleString()\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Fetch every configured source. Each one fails on its own, so one\n\t\t * unreachable source never hides the others.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t * @spec openspec/changes/timeline-widget/specs/timeline-widget/spec.md#requirement-a-timeline-widget-shows-an-objects-dated-events-in-order\n\t\t */\n\t\tasync load() {\n\t\t\tconst id = this.resolvedObjectId\n\t\t\tconst register = this.resolvedRegister\n\t\t\tconst schema = this.resolvedSchema\n\t\t\tconst seq = ++this.loadSeq\n\t\t\tif (!id || !register || !schema) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst [{ default: axios }, { generateUrl }] = await Promise.all([\n\t\t\t\timport('@nextcloud/axios'),\n\t\t\t\timport('@nextcloud/router'),\n\t\t\t])\n\t\t\tconst base = `${this.apiBase}/objects/${register}/${schema}/${id}`\n\t\t\tconst failed = []\n\t\t\tconst jobs = []\n\n\t\t\tif (this.fieldsConfig.length > 0 && !(this.object || this.ctx.object || this.ctx.objectData)) {\n\t\t\t\tjobs.push(axios.get(generateUrl(base))\n\t\t\t\t\t.then((r) => {\n\t\t\t\t\t\tthis.fetchedObject = (r && r.data) || null\n\t\t\t\t\t})\n\t\t\t\t\t.catch(() => failed.push('object')))\n\t\t\t}\n\n\t\t\tconst rows = {}\n\t\t\tthis.relatedConfig.forEach((cfg, i) => {\n\t\t\t\tconst url = generateUrl(`${this.apiBase}/objects/${cfg.register || register}/${cfg.schema}`)\n\t\t\t\tjobs.push(axios.get(url, { params: { [cfg.field]: id, _limit: cfg.limit || 50 } })\n\t\t\t\t\t.then((r) => {\n\t\t\t\t\t\tconst data = r && r.data\n\t\t\t\t\t\trows[i] = (data && (data.results || data)) || []\n\t\t\t\t\t})\n\t\t\t\t\t.catch(() => {\n\t\t\t\t\t\trows[i] = []\n\t\t\t\t\t\tif (!failed.includes('related')) {\n\t\t\t\t\t\t\tfailed.push('related')\n\t\t\t\t\t\t}\n\t\t\t\t\t}))\n\t\t\t})\n\n\t\t\tif (this.content.auditTrail === true) {\n\t\t\t\tjobs.push(axios.get(generateUrl(`${base}/audit-trails`), { params: { limit: this.content.auditLimit || 50 } })\n\t\t\t\t\t.then((r) => {\n\t\t\t\t\t\tconst data = r && r.data\n\t\t\t\t\t\tthis.auditEntries = (data && (data.results || data)) || []\n\t\t\t\t\t})\n\t\t\t\t\t.catch(() => {\n\t\t\t\t\t\tthis.auditEntries = []\n\t\t\t\t\t\tfailed.push('audit')\n\t\t\t\t\t}))\n\t\t\t}\n\n\t\t\tif (this.content.timeline === true) {\n\t\t\t\tjobs.push(axios.get(generateUrl(`${base}/timeline`))\n\t\t\t\t\t.then((r) => {\n\t\t\t\t\t\tconst data = r && r.data\n\t\t\t\t\t\tthis.timelineEntries = (data && (data.results || data)) || []\n\t\t\t\t\t})\n\t\t\t\t\t.catch(() => {\n\t\t\t\t\t\tthis.timelineEntries = []\n\t\t\t\t\t\tfailed.push('timeline')\n\t\t\t\t\t}))\n\t\t\t}\n\n\t\t\tif (jobs.length === 0) {\n\t\t\t\tthis.failedSources = []\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\tawait Promise.all(jobs)\n\t\t\tif (seq !== this.loadSeq) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.relatedRows = rows\n\t\t\tthis.failedSources = failed\n\t\t\tthis.loading = false\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-timeline-widget {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 8px;\n}\n\n.cn-timeline-widget__title {\n\tmargin: 0;\n\tfont-size: var(--font-size-large, 1.1em);\n}\n\n.cn-timeline-widget__loading {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-timeline-widget__empty {\n\tmargin: 0;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-timeline-widget__list {\n\tmargin: 0;\n\tpadding: 0;\n\tlist-style: none;\n}\n\n.cn-timeline-widget__event {\n\tposition: relative;\n\tdisplay: flex;\n\tgap: 12px;\n\tpadding-block-end: 12px;\n}\n\n/* The line between dots: drawn from each dot down to the next event. */\n.cn-timeline-widget__event:not(:last-child)::before {\n\tcontent: '';\n\tposition: absolute;\n\tinset-block: 14px 0;\n\tinset-inline-start: 5px;\n\tborder-inline-start: 2px solid var(--color-border-dark);\n}\n\n.cn-timeline-widget__dot {\n\tflex: 0 0 auto;\n\twidth: 12px;\n\theight: 12px;\n\tmargin-block-start: 4px;\n\tborder-radius: 50%;\n\tbackground-color: var(--color-primary-element);\n}\n\n/* Upcoming: a hollow dot, plus the word \"Upcoming\" beside the date. */\n.cn-timeline-widget__event--upcoming .cn-timeline-widget__dot {\n\tbox-sizing: border-box;\n\tborder: 2px solid var(--color-primary-element);\n\tbackground-color: var(--color-main-background);\n}\n\n.cn-timeline-widget__body {\n\tmin-width: 0;\n}\n\n.cn-timeline-widget__when {\n\tfont-size: var(--font-size-small, 13px);\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-timeline-widget__badge {\n\tmargin-inline-start: 8px;\n\tfont-size: var(--font-size-small, 13px);\n\tfont-weight: bold;\n\tcolor: var(--color-main-text);\n}\n\n.cn-timeline-widget__label {\n\tmargin: 0;\n\tfont-weight: bold;\n}\n\n.cn-timeline-widget__detail {\n\tmargin: 0;\n\toverflow-wrap: anywhere;\n}\n</style>\n"],"names":["t"],"mappings":"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"}
1
+ {"version":3,"file":"CnTimelineWidget.vue2.js","sources":["../../../../src/components/CnTimelineWidget/CnTimelineWidget.vue"],"sourcesContent":["<!-- SPDX-License-Identifier: EUPL-1.2 -->\n<!-- SPDX-FileCopyrightText: 2026 Conduction B.V. <info@conduction.nl> -->\n<template>\n\t<section class=\"cn-timeline-widget\" data-testid=\"cn-timeline-widget\" :aria-busy=\"loading ? 'true' : 'false'\">\n\t\t<h3 v-if=\"resolvedTitle\" class=\"cn-timeline-widget__title\">\n\t\t\t{{ resolvedTitle }}\n\t\t</h3>\n\n\t\t<div v-if=\"loading && events.length === 0\" class=\"cn-timeline-widget__loading\">\n\t\t\t<NcLoadingIcon :size=\"24\" aria-hidden=\"true\" />\n\t\t\t<span>{{ loadingLabel }}</span>\n\t\t</div>\n\n\t\t<template v-else>\n\t\t\t<!-- A failed read is not an empty history: say which part is missing. -->\n\t\t\t<NcNoteCard v-if=\"failedSources.length > 0\" type=\"warning\" data-testid=\"cn-timeline-widget-error\">\n\t\t\t\t{{ failedText }}\n\t\t\t</NcNoteCard>\n\n\t\t\t<p v-if=\"events.length === 0\" class=\"cn-timeline-widget__empty\" data-testid=\"cn-timeline-widget-empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</p>\n\n\t\t\t<ol v-else class=\"cn-timeline-widget__list\">\n\t\t\t\t<li\n\t\t\t\t\tv-for=\"event in events\"\n\t\t\t\t\t:key=\"event.id\"\n\t\t\t\t\tclass=\"cn-timeline-widget__event\"\n\t\t\t\t\t:class=\"{ 'cn-timeline-widget__event--upcoming': event.upcoming }\"\n\t\t\t\t\t:data-source=\"event.source\"\n\t\t\t\t\tdata-testid=\"cn-timeline-widget-event\">\n\t\t\t\t\t<span class=\"cn-timeline-widget__dot\" aria-hidden=\"true\" />\n\t\t\t\t\t<div class=\"cn-timeline-widget__body\">\n\t\t\t\t\t\t<time class=\"cn-timeline-widget__when\" :datetime=\"event.at.toISOString()\">\n\t\t\t\t\t\t\t{{ formatMoment(event) }}\n\t\t\t\t\t\t</time>\n\t\t\t\t\t\t<span v-if=\"event.upcoming\" class=\"cn-timeline-widget__badge\">\n\t\t\t\t\t\t\t{{ upcomingLabel }}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<p class=\"cn-timeline-widget__label\">\n\t\t\t\t\t\t\t{{ translate(event.label) }}\n\t\t\t\t\t\t</p>\n\t\t\t\t\t\t<p v-if=\"event.detail\" class=\"cn-timeline-widget__detail\">\n\t\t\t\t\t\t\t{{ event.detail }}\n\t\t\t\t\t\t</p>\n\t\t\t\t\t</div>\n\t\t\t\t</li>\n\t\t\t</ol>\n\t\t</template>\n\t</section>\n</template>\n\n<script>\nimport { getLanguage, translate as t } from '@nextcloud/l10n'\nimport { NcLoadingIcon, NcNoteCard } from '@nextcloud/vue'\nimport { auditEvents, fieldEvents, listEvents, relatedEvents, sortEvents, timelineEntryEvents } from '../../utils/timelineEvents.js'\nimport { userLocale } from '../../utils/userLocale.js'\n\n/**\n * CnTimelineWidget — an object's dated events in time order (`timeline`\n * widget type for detail pages).\n *\n * Generalises dossiq's case timeline. It merges up to four sources, all\n * optional, into one list:\n *\n * - `fields`: the object's own date properties, `[{ field, label }]`. A\n * dotted path reaches metadata, such as `@self.created`.\n * - `related`: related objects, `[{ register?, schema, field, dateField?,\n * label, titleField? }]`. Rows of `schema` whose `field` holds this object's\n * id become events at their `dateField` (default `@self.created`).\n * - `auditTrail: true`: the object's audit trail, one event per change.\n * - `timeline: true`: OpenRegister's timeline of notes, calls and messages.\n *\n * Oldest first by default (`order: 'desc'` flips it). A moment after now is\n * marked upcoming, in words as well as style.\n *\n * ```json\n * { \"type\": \"timeline\", \"content\": {\n * \"title\": \"Timeline\",\n * \"fields\": [\n * { \"field\": \"@self.created\", \"label\": \"Booking created\" },\n * { \"field\": \"depositClearedAt\", \"label\": \"Deposit cleared\" },\n * { \"field\": \"confirmationSentAt\", \"label\": \"Confirmation mail sent\" },\n * { \"field\": \"startsAt\", \"label\": \"Starts\" },\n * { \"field\": \"endsAt\", \"label\": \"Ends\" }\n * ] } }\n * ```\n *\n * The object comes from the detail page's context, like `audit-trail`:\n * explicit props win, then the injects, then `content`.\n *\n * @spec openspec/changes/timeline-widget/specs/timeline-widget/spec.md#requirement-a-timeline-widget-shows-an-objects-dated-events-in-order\n */\nexport default {\n\tname: 'CnTimelineWidget',\n\n\tcomponents: {\n\t\tNcLoadingIcon,\n\t\tNcNoteCard,\n\t},\n\n\tinject: {\n\t\t/** Detail-page object context from CnDetailPage (a ref or a raw bag). */\n\t\tcnObjectContext: { default: null },\n\t\t/** Detail object context holder from CnPageRenderer (`{ value: { objectData, ... } }`). */\n\t\tcnDetailObjectContext: { default: null },\n\t\t/** Consumer translation function; labels are authored in English. */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/** Register slug; object context spread as props by CnWidgetGrid. */\n\t\tregister: { type: String, default: '' },\n\t\t/**\n\t\t * Schema slug, or the schema object the detail context supplies.\n\t\t *\n\t\t * @type {string|object}\n\t\t */\n\t\tschema: { type: [String, Object], default: '' },\n\t\t/** The object's id. */\n\t\tobjectId: { type: String, default: '' },\n\t\t/** The object itself, when the host already has it. */\n\t\tobject: { type: Object, default: null },\n\t\t/** Card title override. */\n\t\ttitle: { type: String, default: '' },\n\t\t/** Stored widget content: `{ title, fields, related, auditTrail, timeline, order }`. */\n\t\tcontent: { type: Object, default: () => ({}) },\n\t\t/** OpenRegister API base. */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tloading: false,\n\t\t\trelatedRows: {},\n\t\t\tauditEntries: [],\n\t\t\ttimelineEntries: [],\n\t\t\tfailedSources: [],\n\t\t\tfetchedObject: null,\n\t\t\tloadSeq: 0,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\tctx() {\n\t\t\tconst raw = this.cnObjectContext\n\t\t\tconst inj = raw && (raw.value !== undefined ? raw.value : raw)\n\t\t\tconst holder = this.cnDetailObjectContext && this.cnDetailObjectContext.value\n\t\t\treturn inj || holder || {}\n\t\t},\n\n\t\tresolvedObjectId() {\n\t\t\treturn this.objectId || this.ctx.objectId || this.content.objectId || ''\n\t\t},\n\n\t\tresolvedRegister() {\n\t\t\treturn this.register || this.ctx.register || this.content.register || ''\n\t\t},\n\n\t\tresolvedSchema() {\n\t\t\tconst s = this.schema || this.ctx.schema || this.content.schema || ''\n\t\t\treturn typeof s === 'string' ? s : (s && (s.slug || s.name || s.id)) || ''\n\t\t},\n\n\t\t/**\n\t\t * The object: explicit prop, then the page context, then a fetch of\n\t\t * our own when only the id is known.\n\t\t *\n\t\t * @return {object|null}\n\t\t */\n\t\tresolvedObject() {\n\t\t\treturn this.object || this.ctx.object || this.ctx.objectData || this.fetchedObject || null\n\t\t},\n\n\t\tresolvedTitle() {\n\t\t\tconst raw = this.title || this.content.title || ''\n\t\t\treturn raw ? this.translate(raw) : ''\n\t\t},\n\n\t\tfieldsConfig() {\n\t\t\treturn Array.isArray(this.content.fields) ? this.content.fields : []\n\t\t},\n\n\t\trelatedConfig() {\n\t\t\treturn Array.isArray(this.content.related) ? this.content.related.filter((r) => r && r.schema && r.field) : []\n\t\t},\n\n\t\t/**\n\t\t * Every event from every source, in time order.\n\t\t *\n\t\t * @return {Array<object>}\n\t\t */\n\t\tevents() {\n\t\t\tconst now = new Date()\n\t\t\tconst all = [\n\t\t\t\t...fieldEvents(this.resolvedObject || {}, this.fieldsConfig, now),\n\t\t\t\t...listEvents(this.resolvedObject || {}, this.content.lists, now),\n\t\t\t\t...this.relatedConfig.flatMap((cfg, i) => relatedEvents(this.relatedRows[i] || [], cfg, now)),\n\t\t\t\t...auditEvents(this.auditEntries, this.describeAudit, now),\n\t\t\t\t...timelineEntryEvents(this.timelineEntries, t('nextcloud-vue', 'Note'), now),\n\t\t\t]\n\t\t\treturn sortEvents(all, this.content.order === 'desc' ? 'desc' : 'asc')\n\t\t},\n\n\t\t/**\n\t\t * A signature of what to fetch, so a change refetches once.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tloadKey() {\n\t\t\treturn JSON.stringify({\n\t\t\t\tid: this.resolvedObjectId,\n\t\t\t\tregister: this.resolvedRegister,\n\t\t\t\tschema: this.resolvedSchema,\n\t\t\t\trelated: this.relatedConfig,\n\t\t\t\taudit: this.content.auditTrail === true,\n\t\t\t\ttimeline: this.content.timeline === true,\n\t\t\t\tneedObject: this.fieldsConfig.length > 0 && !(this.object || this.ctx.object || this.ctx.objectData),\n\t\t\t})\n\t\t},\n\n\t\tfailedText() {\n\t\t\treturn t('nextcloud-vue', 'Part of the timeline could not load: {parts}.', {\n\t\t\t\tparts: this.failedSources.map((s) => this.sourceName(s)).join(', '),\n\t\t\t})\n\t\t},\n\n\t\tloadingLabel() {\n\t\t\treturn t('nextcloud-vue', 'Loading…')\n\t\t},\n\n\t\temptyLabel() {\n\t\t\treturn t('nextcloud-vue', 'Nothing has happened yet.')\n\t\t},\n\n\t\tupcomingLabel() {\n\t\t\treturn t('nextcloud-vue', 'Upcoming')\n\t\t},\n\t},\n\n\twatch: {\n\t\tloadKey: {\n\t\t\timmediate: true,\n\t\t\thandler() {\n\t\t\t\tthis.load()\n\t\t\t},\n\t\t},\n\t},\n\n\tmethods: {\n\t\ttranslate(text) {\n\t\t\treturn text ? this.cnTranslate(text) : ''\n\t\t},\n\n\t\tsourceName(source) {\n\t\t\tif (source === 'audit') {\n\t\t\t\treturn t('nextcloud-vue', 'changes')\n\t\t\t}\n\t\t\tif (source === 'timeline') {\n\t\t\t\treturn t('nextcloud-vue', 'notes and messages')\n\t\t\t}\n\t\t\tif (source === 'object') {\n\t\t\t\treturn t('nextcloud-vue', 'dates')\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', 'related items')\n\t\t},\n\n\t\t/**\n\t\t * The label of an audit event, from its action and who did it.\n\t\t *\n\t\t * @param {string} action The audit action (create, update, delete).\n\t\t * @param {string} actor Who did it.\n\t\t * @return {string}\n\t\t */\n\t\tdescribeAudit(action, actor) {\n\t\t\tconst known = {\n\t\t\t\tcreate: t('nextcloud-vue', 'Created'),\n\t\t\t\tupdate: t('nextcloud-vue', 'Updated'),\n\t\t\t\tdelete: t('nextcloud-vue', 'Deleted'),\n\t\t\t}\n\t\t\tconst what = known[action] || action || t('nextcloud-vue', 'Changed')\n\t\t\treturn actor ? t('nextcloud-vue', '{action} by {actor}', { action: what, actor }) : what\n\t\t},\n\n\t\t/**\n\t\t * Show a moment in the reader's language: a date for a calendar day,\n\t\t * date and time otherwise.\n\t\t *\n\t\t * @param {object} event The event.\n\t\t * @return {string}\n\t\t */\n\t\tformatMoment(event) {\n\t\t\tconst locale = (typeof getLanguage === 'function' && getLanguage()) || undefined\n\t\t\tconst options = event.dateOnly\n\t\t\t\t? { dateStyle: 'medium' }\n\t\t\t\t: { dateStyle: 'medium', timeStyle: 'short' }\n\t\t\ttry {\n\t\t\t\treturn new Intl.DateTimeFormat(locale, options).format(event.at)\n\t\t\t} catch {\n\t\t\t\treturn event.at.toLocaleString(userLocale())\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Fetch every configured source. Each one fails on its own, so one\n\t\t * unreachable source never hides the others.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t * @spec openspec/changes/timeline-widget/specs/timeline-widget/spec.md#requirement-a-timeline-widget-shows-an-objects-dated-events-in-order\n\t\t */\n\t\tasync load() {\n\t\t\tconst id = this.resolvedObjectId\n\t\t\tconst register = this.resolvedRegister\n\t\t\tconst schema = this.resolvedSchema\n\t\t\tconst seq = ++this.loadSeq\n\t\t\tif (!id || !register || !schema) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst [{ default: axios }, { generateUrl }] = await Promise.all([\n\t\t\t\timport('@nextcloud/axios'),\n\t\t\t\timport('@nextcloud/router'),\n\t\t\t])\n\t\t\tconst base = `${this.apiBase}/objects/${register}/${schema}/${id}`\n\t\t\tconst failed = []\n\t\t\tconst jobs = []\n\n\t\t\tif (this.fieldsConfig.length > 0 && !(this.object || this.ctx.object || this.ctx.objectData)) {\n\t\t\t\tjobs.push(axios.get(generateUrl(base))\n\t\t\t\t\t.then((r) => {\n\t\t\t\t\t\tthis.fetchedObject = (r && r.data) || null\n\t\t\t\t\t})\n\t\t\t\t\t.catch(() => failed.push('object')))\n\t\t\t}\n\n\t\t\tconst rows = {}\n\t\t\tthis.relatedConfig.forEach((cfg, i) => {\n\t\t\t\tconst url = generateUrl(`${this.apiBase}/objects/${cfg.register || register}/${cfg.schema}`)\n\t\t\t\tjobs.push(axios.get(url, { params: { [cfg.field]: id, _limit: cfg.limit || 50 } })\n\t\t\t\t\t.then((r) => {\n\t\t\t\t\t\tconst data = r && r.data\n\t\t\t\t\t\trows[i] = (data && (data.results || data)) || []\n\t\t\t\t\t})\n\t\t\t\t\t.catch(() => {\n\t\t\t\t\t\trows[i] = []\n\t\t\t\t\t\tif (!failed.includes('related')) {\n\t\t\t\t\t\t\tfailed.push('related')\n\t\t\t\t\t\t}\n\t\t\t\t\t}))\n\t\t\t})\n\n\t\t\tif (this.content.auditTrail === true) {\n\t\t\t\tjobs.push(axios.get(generateUrl(`${base}/audit-trails`), { params: { limit: this.content.auditLimit || 50 } })\n\t\t\t\t\t.then((r) => {\n\t\t\t\t\t\tconst data = r && r.data\n\t\t\t\t\t\tthis.auditEntries = (data && (data.results || data)) || []\n\t\t\t\t\t})\n\t\t\t\t\t.catch(() => {\n\t\t\t\t\t\tthis.auditEntries = []\n\t\t\t\t\t\tfailed.push('audit')\n\t\t\t\t\t}))\n\t\t\t}\n\n\t\t\tif (this.content.timeline === true) {\n\t\t\t\tjobs.push(axios.get(generateUrl(`${base}/timeline`))\n\t\t\t\t\t.then((r) => {\n\t\t\t\t\t\tconst data = r && r.data\n\t\t\t\t\t\tthis.timelineEntries = (data && (data.results || data)) || []\n\t\t\t\t\t})\n\t\t\t\t\t.catch(() => {\n\t\t\t\t\t\tthis.timelineEntries = []\n\t\t\t\t\t\tfailed.push('timeline')\n\t\t\t\t\t}))\n\t\t\t}\n\n\t\t\tif (jobs.length === 0) {\n\t\t\t\tthis.failedSources = []\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\tawait Promise.all(jobs)\n\t\t\tif (seq !== this.loadSeq) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.relatedRows = rows\n\t\t\tthis.failedSources = failed\n\t\t\tthis.loading = false\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-timeline-widget {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 8px;\n}\n\n.cn-timeline-widget__title {\n\tmargin: 0;\n\tfont-size: var(--font-size-large, 1.1em);\n}\n\n.cn-timeline-widget__loading {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-timeline-widget__empty {\n\tmargin: 0;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-timeline-widget__list {\n\tmargin: 0;\n\tpadding: 0;\n\tlist-style: none;\n}\n\n.cn-timeline-widget__event {\n\tposition: relative;\n\tdisplay: flex;\n\tgap: 12px;\n\tpadding-block-end: 12px;\n}\n\n/* The line between dots: drawn from each dot down to the next event. */\n.cn-timeline-widget__event:not(:last-child)::before {\n\tcontent: '';\n\tposition: absolute;\n\tinset-block: 14px 0;\n\tinset-inline-start: 5px;\n\tborder-inline-start: 2px solid var(--color-border-dark);\n}\n\n.cn-timeline-widget__dot {\n\tflex: 0 0 auto;\n\twidth: 12px;\n\theight: 12px;\n\tmargin-block-start: 4px;\n\tborder-radius: 50%;\n\tbackground-color: var(--color-primary-element);\n}\n\n/* Upcoming: a hollow dot, plus the word \"Upcoming\" beside the date. */\n.cn-timeline-widget__event--upcoming .cn-timeline-widget__dot {\n\tbox-sizing: border-box;\n\tborder: 2px solid var(--color-primary-element);\n\tbackground-color: var(--color-main-background);\n}\n\n.cn-timeline-widget__body {\n\tmin-width: 0;\n}\n\n.cn-timeline-widget__when {\n\tfont-size: var(--font-size-small, 13px);\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-timeline-widget__badge {\n\tmargin-inline-start: 8px;\n\tfont-size: var(--font-size-small, 13px);\n\tfont-weight: bold;\n\tcolor: var(--color-main-text);\n}\n\n.cn-timeline-widget__label {\n\tmargin: 0;\n\tfont-weight: bold;\n}\n\n.cn-timeline-widget__detail {\n\tmargin: 0;\n\toverflow-wrap: anywhere;\n}\n</style>\n"],"names":["t"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"CnTimelineWidget.vue3.js","sources":["../../../../src/components/CnTimelineWidget/CnTimelineWidget.vue?vue&type=template&id=60089a65&lang.js"],"sourcesContent":["<!-- SPDX-License-Identifier: EUPL-1.2 -->\n<!-- SPDX-FileCopyrightText: 2026 Conduction B.V. <info@conduction.nl> -->\n<template>\n\t<section class=\"cn-timeline-widget\" data-testid=\"cn-timeline-widget\" :aria-busy=\"loading ? 'true' : 'false'\">\n\t\t<h3 v-if=\"resolvedTitle\" class=\"cn-timeline-widget__title\">\n\t\t\t{{ resolvedTitle }}\n\t\t</h3>\n\n\t\t<div v-if=\"loading && events.length === 0\" class=\"cn-timeline-widget__loading\">\n\t\t\t<NcLoadingIcon :size=\"24\" aria-hidden=\"true\" />\n\t\t\t<span>{{ loadingLabel }}</span>\n\t\t</div>\n\n\t\t<template v-else>\n\t\t\t<!-- A failed read is not an empty history: say which part is missing. -->\n\t\t\t<NcNoteCard v-if=\"failedSources.length > 0\" type=\"warning\" data-testid=\"cn-timeline-widget-error\">\n\t\t\t\t{{ failedText }}\n\t\t\t</NcNoteCard>\n\n\t\t\t<p v-if=\"events.length === 0\" class=\"cn-timeline-widget__empty\" data-testid=\"cn-timeline-widget-empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</p>\n\n\t\t\t<ol v-else class=\"cn-timeline-widget__list\">\n\t\t\t\t<li\n\t\t\t\t\tv-for=\"event in events\"\n\t\t\t\t\t:key=\"event.id\"\n\t\t\t\t\tclass=\"cn-timeline-widget__event\"\n\t\t\t\t\t:class=\"{ 'cn-timeline-widget__event--upcoming': event.upcoming }\"\n\t\t\t\t\t:data-source=\"event.source\"\n\t\t\t\t\tdata-testid=\"cn-timeline-widget-event\">\n\t\t\t\t\t<span class=\"cn-timeline-widget__dot\" aria-hidden=\"true\" />\n\t\t\t\t\t<div class=\"cn-timeline-widget__body\">\n\t\t\t\t\t\t<time class=\"cn-timeline-widget__when\" :datetime=\"event.at.toISOString()\">\n\t\t\t\t\t\t\t{{ formatMoment(event) }}\n\t\t\t\t\t\t</time>\n\t\t\t\t\t\t<span v-if=\"event.upcoming\" class=\"cn-timeline-widget__badge\">\n\t\t\t\t\t\t\t{{ upcomingLabel }}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<p class=\"cn-timeline-widget__label\">\n\t\t\t\t\t\t\t{{ translate(event.label) }}\n\t\t\t\t\t\t</p>\n\t\t\t\t\t\t<p v-if=\"event.detail\" class=\"cn-timeline-widget__detail\">\n\t\t\t\t\t\t\t{{ event.detail }}\n\t\t\t\t\t\t</p>\n\t\t\t\t\t</div>\n\t\t\t\t</li>\n\t\t\t</ol>\n\t\t</template>\n\t</section>\n</template>\n\n<script>\nimport { getLanguage, translate as t } from '@nextcloud/l10n'\nimport { NcLoadingIcon, NcNoteCard } from '@nextcloud/vue'\nimport { auditEvents, fieldEvents, listEvents, relatedEvents, sortEvents, timelineEntryEvents } from '../../utils/timelineEvents.js'\n\n/**\n * CnTimelineWidget — an object's dated events in time order (`timeline`\n * widget type for detail pages).\n *\n * Generalises dossiq's case timeline. It merges up to four sources, all\n * optional, into one list:\n *\n * - `fields`: the object's own date properties, `[{ field, label }]`. A\n * dotted path reaches metadata, such as `@self.created`.\n * - `related`: related objects, `[{ register?, schema, field, dateField?,\n * label, titleField? }]`. Rows of `schema` whose `field` holds this object's\n * id become events at their `dateField` (default `@self.created`).\n * - `auditTrail: true`: the object's audit trail, one event per change.\n * - `timeline: true`: OpenRegister's timeline of notes, calls and messages.\n *\n * Oldest first by default (`order: 'desc'` flips it). A moment after now is\n * marked upcoming, in words as well as style.\n *\n * ```json\n * { \"type\": \"timeline\", \"content\": {\n * \"title\": \"Timeline\",\n * \"fields\": [\n * { \"field\": \"@self.created\", \"label\": \"Booking created\" },\n * { \"field\": \"depositClearedAt\", \"label\": \"Deposit cleared\" },\n * { \"field\": \"confirmationSentAt\", \"label\": \"Confirmation mail sent\" },\n * { \"field\": \"startsAt\", \"label\": \"Starts\" },\n * { \"field\": \"endsAt\", \"label\": \"Ends\" }\n * ] } }\n * ```\n *\n * The object comes from the detail page's context, like `audit-trail`:\n * explicit props win, then the injects, then `content`.\n *\n * @spec openspec/changes/timeline-widget/specs/timeline-widget/spec.md#requirement-a-timeline-widget-shows-an-objects-dated-events-in-order\n */\nexport default {\n\tname: 'CnTimelineWidget',\n\n\tcomponents: {\n\t\tNcLoadingIcon,\n\t\tNcNoteCard,\n\t},\n\n\tinject: {\n\t\t/** Detail-page object context from CnDetailPage (a ref or a raw bag). */\n\t\tcnObjectContext: { default: null },\n\t\t/** Detail object context holder from CnPageRenderer (`{ value: { objectData, ... } }`). */\n\t\tcnDetailObjectContext: { default: null },\n\t\t/** Consumer translation function; labels are authored in English. */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/** Register slug; object context spread as props by CnWidgetGrid. */\n\t\tregister: { type: String, default: '' },\n\t\t/**\n\t\t * Schema slug, or the schema object the detail context supplies.\n\t\t *\n\t\t * @type {string|object}\n\t\t */\n\t\tschema: { type: [String, Object], default: '' },\n\t\t/** The object's id. */\n\t\tobjectId: { type: String, default: '' },\n\t\t/** The object itself, when the host already has it. */\n\t\tobject: { type: Object, default: null },\n\t\t/** Card title override. */\n\t\ttitle: { type: String, default: '' },\n\t\t/** Stored widget content: `{ title, fields, related, auditTrail, timeline, order }`. */\n\t\tcontent: { type: Object, default: () => ({}) },\n\t\t/** OpenRegister API base. */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tloading: false,\n\t\t\trelatedRows: {},\n\t\t\tauditEntries: [],\n\t\t\ttimelineEntries: [],\n\t\t\tfailedSources: [],\n\t\t\tfetchedObject: null,\n\t\t\tloadSeq: 0,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\tctx() {\n\t\t\tconst raw = this.cnObjectContext\n\t\t\tconst inj = raw && (raw.value !== undefined ? raw.value : raw)\n\t\t\tconst holder = this.cnDetailObjectContext && this.cnDetailObjectContext.value\n\t\t\treturn inj || holder || {}\n\t\t},\n\n\t\tresolvedObjectId() {\n\t\t\treturn this.objectId || this.ctx.objectId || this.content.objectId || ''\n\t\t},\n\n\t\tresolvedRegister() {\n\t\t\treturn this.register || this.ctx.register || this.content.register || ''\n\t\t},\n\n\t\tresolvedSchema() {\n\t\t\tconst s = this.schema || this.ctx.schema || this.content.schema || ''\n\t\t\treturn typeof s === 'string' ? s : (s && (s.slug || s.name || s.id)) || ''\n\t\t},\n\n\t\t/**\n\t\t * The object: explicit prop, then the page context, then a fetch of\n\t\t * our own when only the id is known.\n\t\t *\n\t\t * @return {object|null}\n\t\t */\n\t\tresolvedObject() {\n\t\t\treturn this.object || this.ctx.object || this.ctx.objectData || this.fetchedObject || null\n\t\t},\n\n\t\tresolvedTitle() {\n\t\t\tconst raw = this.title || this.content.title || ''\n\t\t\treturn raw ? this.translate(raw) : ''\n\t\t},\n\n\t\tfieldsConfig() {\n\t\t\treturn Array.isArray(this.content.fields) ? this.content.fields : []\n\t\t},\n\n\t\trelatedConfig() {\n\t\t\treturn Array.isArray(this.content.related) ? this.content.related.filter((r) => r && r.schema && r.field) : []\n\t\t},\n\n\t\t/**\n\t\t * Every event from every source, in time order.\n\t\t *\n\t\t * @return {Array<object>}\n\t\t */\n\t\tevents() {\n\t\t\tconst now = new Date()\n\t\t\tconst all = [\n\t\t\t\t...fieldEvents(this.resolvedObject || {}, this.fieldsConfig, now),\n\t\t\t\t...listEvents(this.resolvedObject || {}, this.content.lists, now),\n\t\t\t\t...this.relatedConfig.flatMap((cfg, i) => relatedEvents(this.relatedRows[i] || [], cfg, now)),\n\t\t\t\t...auditEvents(this.auditEntries, this.describeAudit, now),\n\t\t\t\t...timelineEntryEvents(this.timelineEntries, t('nextcloud-vue', 'Note'), now),\n\t\t\t]\n\t\t\treturn sortEvents(all, this.content.order === 'desc' ? 'desc' : 'asc')\n\t\t},\n\n\t\t/**\n\t\t * A signature of what to fetch, so a change refetches once.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tloadKey() {\n\t\t\treturn JSON.stringify({\n\t\t\t\tid: this.resolvedObjectId,\n\t\t\t\tregister: this.resolvedRegister,\n\t\t\t\tschema: this.resolvedSchema,\n\t\t\t\trelated: this.relatedConfig,\n\t\t\t\taudit: this.content.auditTrail === true,\n\t\t\t\ttimeline: this.content.timeline === true,\n\t\t\t\tneedObject: this.fieldsConfig.length > 0 && !(this.object || this.ctx.object || this.ctx.objectData),\n\t\t\t})\n\t\t},\n\n\t\tfailedText() {\n\t\t\treturn t('nextcloud-vue', 'Part of the timeline could not load: {parts}.', {\n\t\t\t\tparts: this.failedSources.map((s) => this.sourceName(s)).join(', '),\n\t\t\t})\n\t\t},\n\n\t\tloadingLabel() {\n\t\t\treturn t('nextcloud-vue', 'Loading…')\n\t\t},\n\n\t\temptyLabel() {\n\t\t\treturn t('nextcloud-vue', 'Nothing has happened yet.')\n\t\t},\n\n\t\tupcomingLabel() {\n\t\t\treturn t('nextcloud-vue', 'Upcoming')\n\t\t},\n\t},\n\n\twatch: {\n\t\tloadKey: {\n\t\t\timmediate: true,\n\t\t\thandler() {\n\t\t\t\tthis.load()\n\t\t\t},\n\t\t},\n\t},\n\n\tmethods: {\n\t\ttranslate(text) {\n\t\t\treturn text ? this.cnTranslate(text) : ''\n\t\t},\n\n\t\tsourceName(source) {\n\t\t\tif (source === 'audit') {\n\t\t\t\treturn t('nextcloud-vue', 'changes')\n\t\t\t}\n\t\t\tif (source === 'timeline') {\n\t\t\t\treturn t('nextcloud-vue', 'notes and messages')\n\t\t\t}\n\t\t\tif (source === 'object') {\n\t\t\t\treturn t('nextcloud-vue', 'dates')\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', 'related items')\n\t\t},\n\n\t\t/**\n\t\t * The label of an audit event, from its action and who did it.\n\t\t *\n\t\t * @param {string} action The audit action (create, update, delete).\n\t\t * @param {string} actor Who did it.\n\t\t * @return {string}\n\t\t */\n\t\tdescribeAudit(action, actor) {\n\t\t\tconst known = {\n\t\t\t\tcreate: t('nextcloud-vue', 'Created'),\n\t\t\t\tupdate: t('nextcloud-vue', 'Updated'),\n\t\t\t\tdelete: t('nextcloud-vue', 'Deleted'),\n\t\t\t}\n\t\t\tconst what = known[action] || action || t('nextcloud-vue', 'Changed')\n\t\t\treturn actor ? t('nextcloud-vue', '{action} by {actor}', { action: what, actor }) : what\n\t\t},\n\n\t\t/**\n\t\t * Show a moment in the reader's language: a date for a calendar day,\n\t\t * date and time otherwise.\n\t\t *\n\t\t * @param {object} event The event.\n\t\t * @return {string}\n\t\t */\n\t\tformatMoment(event) {\n\t\t\tconst locale = (typeof getLanguage === 'function' && getLanguage()) || undefined\n\t\t\tconst options = event.dateOnly\n\t\t\t\t? { dateStyle: 'medium' }\n\t\t\t\t: { dateStyle: 'medium', timeStyle: 'short' }\n\t\t\ttry {\n\t\t\t\treturn new Intl.DateTimeFormat(locale, options).format(event.at)\n\t\t\t} catch {\n\t\t\t\treturn event.at.toLocaleString()\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Fetch every configured source. Each one fails on its own, so one\n\t\t * unreachable source never hides the others.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t * @spec openspec/changes/timeline-widget/specs/timeline-widget/spec.md#requirement-a-timeline-widget-shows-an-objects-dated-events-in-order\n\t\t */\n\t\tasync load() {\n\t\t\tconst id = this.resolvedObjectId\n\t\t\tconst register = this.resolvedRegister\n\t\t\tconst schema = this.resolvedSchema\n\t\t\tconst seq = ++this.loadSeq\n\t\t\tif (!id || !register || !schema) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst [{ default: axios }, { generateUrl }] = await Promise.all([\n\t\t\t\timport('@nextcloud/axios'),\n\t\t\t\timport('@nextcloud/router'),\n\t\t\t])\n\t\t\tconst base = `${this.apiBase}/objects/${register}/${schema}/${id}`\n\t\t\tconst failed = []\n\t\t\tconst jobs = []\n\n\t\t\tif (this.fieldsConfig.length > 0 && !(this.object || this.ctx.object || this.ctx.objectData)) {\n\t\t\t\tjobs.push(axios.get(generateUrl(base))\n\t\t\t\t\t.then((r) => {\n\t\t\t\t\t\tthis.fetchedObject = (r && r.data) || null\n\t\t\t\t\t})\n\t\t\t\t\t.catch(() => failed.push('object')))\n\t\t\t}\n\n\t\t\tconst rows = {}\n\t\t\tthis.relatedConfig.forEach((cfg, i) => {\n\t\t\t\tconst url = generateUrl(`${this.apiBase}/objects/${cfg.register || register}/${cfg.schema}`)\n\t\t\t\tjobs.push(axios.get(url, { params: { [cfg.field]: id, _limit: cfg.limit || 50 } })\n\t\t\t\t\t.then((r) => {\n\t\t\t\t\t\tconst data = r && r.data\n\t\t\t\t\t\trows[i] = (data && (data.results || data)) || []\n\t\t\t\t\t})\n\t\t\t\t\t.catch(() => {\n\t\t\t\t\t\trows[i] = []\n\t\t\t\t\t\tif (!failed.includes('related')) {\n\t\t\t\t\t\t\tfailed.push('related')\n\t\t\t\t\t\t}\n\t\t\t\t\t}))\n\t\t\t})\n\n\t\t\tif (this.content.auditTrail === true) {\n\t\t\t\tjobs.push(axios.get(generateUrl(`${base}/audit-trails`), { params: { limit: this.content.auditLimit || 50 } })\n\t\t\t\t\t.then((r) => {\n\t\t\t\t\t\tconst data = r && r.data\n\t\t\t\t\t\tthis.auditEntries = (data && (data.results || data)) || []\n\t\t\t\t\t})\n\t\t\t\t\t.catch(() => {\n\t\t\t\t\t\tthis.auditEntries = []\n\t\t\t\t\t\tfailed.push('audit')\n\t\t\t\t\t}))\n\t\t\t}\n\n\t\t\tif (this.content.timeline === true) {\n\t\t\t\tjobs.push(axios.get(generateUrl(`${base}/timeline`))\n\t\t\t\t\t.then((r) => {\n\t\t\t\t\t\tconst data = r && r.data\n\t\t\t\t\t\tthis.timelineEntries = (data && (data.results || data)) || []\n\t\t\t\t\t})\n\t\t\t\t\t.catch(() => {\n\t\t\t\t\t\tthis.timelineEntries = []\n\t\t\t\t\t\tfailed.push('timeline')\n\t\t\t\t\t}))\n\t\t\t}\n\n\t\t\tif (jobs.length === 0) {\n\t\t\t\tthis.failedSources = []\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\tawait Promise.all(jobs)\n\t\t\tif (seq !== this.loadSeq) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.relatedRows = rows\n\t\t\tthis.failedSources = failed\n\t\t\tthis.loading = false\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-timeline-widget {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 8px;\n}\n\n.cn-timeline-widget__title {\n\tmargin: 0;\n\tfont-size: var(--font-size-large, 1.1em);\n}\n\n.cn-timeline-widget__loading {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-timeline-widget__empty {\n\tmargin: 0;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-timeline-widget__list {\n\tmargin: 0;\n\tpadding: 0;\n\tlist-style: none;\n}\n\n.cn-timeline-widget__event {\n\tposition: relative;\n\tdisplay: flex;\n\tgap: 12px;\n\tpadding-block-end: 12px;\n}\n\n/* The line between dots: drawn from each dot down to the next event. */\n.cn-timeline-widget__event:not(:last-child)::before {\n\tcontent: '';\n\tposition: absolute;\n\tinset-block: 14px 0;\n\tinset-inline-start: 5px;\n\tborder-inline-start: 2px solid var(--color-border-dark);\n}\n\n.cn-timeline-widget__dot {\n\tflex: 0 0 auto;\n\twidth: 12px;\n\theight: 12px;\n\tmargin-block-start: 4px;\n\tborder-radius: 50%;\n\tbackground-color: var(--color-primary-element);\n}\n\n/* Upcoming: a hollow dot, plus the word \"Upcoming\" beside the date. */\n.cn-timeline-widget__event--upcoming .cn-timeline-widget__dot {\n\tbox-sizing: border-box;\n\tborder: 2px solid var(--color-primary-element);\n\tbackground-color: var(--color-main-background);\n}\n\n.cn-timeline-widget__body {\n\tmin-width: 0;\n}\n\n.cn-timeline-widget__when {\n\tfont-size: var(--font-size-small, 13px);\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-timeline-widget__badge {\n\tmargin-inline-start: 8px;\n\tfont-size: var(--font-size-small, 13px);\n\tfont-weight: bold;\n\tcolor: var(--color-main-text);\n}\n\n.cn-timeline-widget__label {\n\tmargin: 0;\n\tfont-weight: bold;\n}\n\n.cn-timeline-widget__detail {\n\tmargin: 0;\n\toverflow-wrap: anywhere;\n}\n</style>\n"],"names":["_createElementBlock","_toDisplayString","_createVNode","_createBlock","_openBlock","_Fragment","_renderList","_normalizeClass","_createElementVNode"],"mappings":";;;;;AAMM,EAAA,KAAA,EAAA,2BAAA;;;;AAI2B,EAAA,KAAA,EAAA,6BAAA;;;;AAW3B,EAAA,KAAA,EAAA,2BAAA;AAAA,EAAA,aAAA,EAAA,0BAAA;;;;AAIQ,EAAA,KAAK,EAAE,0BAAM;;;AASjB,MAAA,UAAA,GAAA,EAAA,KAAK,EAAC,0BAAe,GAAA;;;;AAInB,EAAA,KAAA,EAAA,2BAAA;;AAGH,MAAA,WAAA,GAAA,EAAA,KAAA,EAAA,2BAAA,GAAA;;;AAGA,EAAA,KAAA,EAAA,4BAAA;;;;;;;uBAvCNA;AAAO,IAAA,KAAK,EAAC,oBAAK;AAAA,IAAA,aAAA,EAAA,oBAAA;AAAA,IAAA,WAAA,EAAA,KAAA,CAAA,OAAA,GAAA,MAAA,GAAA,OAAA;;KACf,QAAA,CAAA,aAAA;sBAAJA,kBAEK,CAAA,IAAA,EAFL,UAEK,EAAAC,eAAA;;KAEM,KAAO,CAAA,OAAA,IAAK,QAAK,CAAA,MAAA,CAAC,MAAE,KAAA,CAAA;yCAGzB,iBAAA,EAAA;UAFLC,WAAI,CAAA,wBAAA,EAAA;AAAA,YAAA,IAAA,EAAA,EAAA;AAAA,YAAA,aAAA,EAAA,MAAA;;;;sBAIJF;AAEY,UAAA,CAAA,KAAA,CAAA,aAAA,CAAA,MAAA,GAAA,CAAA;4BAAZG,WAEa,CAAA,qBAAA,EAAA;;AAFD,gBAAA,IAAA,EAAA,SAAA;AAAA,gBAAA,aAAA,EAAA,0BAAA;;;;;;;;AAIT,UAAA,CAAA,QAAA,CAAA,MAAA,CAAA,MAAA,KAAA,CAAA;4BAAHH,kBAEI,CAAA,GAAA,EAFJ,UAEI,EAAAC,eAAA;AAEF,eAAAG,SAAA,EAAA,EAAAJ,kBAAA,CAwBG,MAxBH,UAwBG,EAAA;kCAvBHA,kBAsBI,CAAAK,QAAA,EAAA,IAAA,EAAAC,UAAA,CArBY,QAAM,CAAA,MAAA,EAAA,CAAf,KAAK,KAAA;uCADZN,kBAsBI,CAAA,IAAA,EAAA;oBApBH,GAAG,EAAE,KAAK,CAAC,EAAE;AACd,oBAAA,KAAK,EAACO,cAAA,CAAA,CAAA,2BAAoB,EACY,EAAA,qCAAA,EAAA,KAAA,CAAA,QAAA,EAAA,CAAA,CAAA;oBACrC,aAAW,EAAE,KAAK,CAAC,MAAM;AAC1B,oBAAA,aAAW,EAAC,0BAAyB;;8CACpCC,kBAA0D,CAAA,MAAA,EAAA;AAAtD,sBAAA,KAAM,EAAC,yBAAyB;AAAC,sBAAA,aAAW,EAAC,MAAM;;uCActD,iBAAA,EAAA;sBAZDA,kBAEO,CAAA,MAAA,EAAA;AAFD,wBAAA,KAAA,EAAA,0BAAA;AAAA,wBAAA,QAAA,EAAA,KAAA,CAAA,EAAA,CAAA,WAAA,EAAA;AACF,uBAAA,EAAAP,eAAA,CAAA,QAAA,CAAA,YAAY,CAAC,KAAK,CAAA,CAAA,EAAA,CAAA,oBAAA,UAAA,CAAA;AAEhB,sBAAA,CAAA,KAAA,CAAA,QAAA;wCAAND,kBAEO,CAAA,MAAA,EAFP,UAEO,EAAAC,eAAA,CADH,QAAa,CAAA,aAAA,CAAA,EAAA,CAAA,YAAA;;AAEjB,sBAAAO,kBAAA,CAEI,KAFJ,WAEI,EAAAP,eAAA,CADA,kBAAS,CAAC,KAAK,CAAC,KAAK,CAAA,CAAA,EAAA,CAAA,YAAA;AAEtB,sBAAA,CAAA,KAAA,CAAA,MAAA;AAAH,2BAAAG,SAAA,EAAA,EAAAJ,kBAAA,CAEE,GAFF,EAAA,WAEE,EADEC,eAAA,CAAA,KAAA,CAAA,MAAA,CAAA,EAAA,CAAA,YAAA;;;;;;;;;;;;"}
1
+ {"version":3,"file":"CnTimelineWidget.vue3.js","sources":["../../../../src/components/CnTimelineWidget/CnTimelineWidget.vue?vue&type=template&id=091aa98d&lang.js"],"sourcesContent":["<!-- SPDX-License-Identifier: EUPL-1.2 -->\n<!-- SPDX-FileCopyrightText: 2026 Conduction B.V. <info@conduction.nl> -->\n<template>\n\t<section class=\"cn-timeline-widget\" data-testid=\"cn-timeline-widget\" :aria-busy=\"loading ? 'true' : 'false'\">\n\t\t<h3 v-if=\"resolvedTitle\" class=\"cn-timeline-widget__title\">\n\t\t\t{{ resolvedTitle }}\n\t\t</h3>\n\n\t\t<div v-if=\"loading && events.length === 0\" class=\"cn-timeline-widget__loading\">\n\t\t\t<NcLoadingIcon :size=\"24\" aria-hidden=\"true\" />\n\t\t\t<span>{{ loadingLabel }}</span>\n\t\t</div>\n\n\t\t<template v-else>\n\t\t\t<!-- A failed read is not an empty history: say which part is missing. -->\n\t\t\t<NcNoteCard v-if=\"failedSources.length > 0\" type=\"warning\" data-testid=\"cn-timeline-widget-error\">\n\t\t\t\t{{ failedText }}\n\t\t\t</NcNoteCard>\n\n\t\t\t<p v-if=\"events.length === 0\" class=\"cn-timeline-widget__empty\" data-testid=\"cn-timeline-widget-empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</p>\n\n\t\t\t<ol v-else class=\"cn-timeline-widget__list\">\n\t\t\t\t<li\n\t\t\t\t\tv-for=\"event in events\"\n\t\t\t\t\t:key=\"event.id\"\n\t\t\t\t\tclass=\"cn-timeline-widget__event\"\n\t\t\t\t\t:class=\"{ 'cn-timeline-widget__event--upcoming': event.upcoming }\"\n\t\t\t\t\t:data-source=\"event.source\"\n\t\t\t\t\tdata-testid=\"cn-timeline-widget-event\">\n\t\t\t\t\t<span class=\"cn-timeline-widget__dot\" aria-hidden=\"true\" />\n\t\t\t\t\t<div class=\"cn-timeline-widget__body\">\n\t\t\t\t\t\t<time class=\"cn-timeline-widget__when\" :datetime=\"event.at.toISOString()\">\n\t\t\t\t\t\t\t{{ formatMoment(event) }}\n\t\t\t\t\t\t</time>\n\t\t\t\t\t\t<span v-if=\"event.upcoming\" class=\"cn-timeline-widget__badge\">\n\t\t\t\t\t\t\t{{ upcomingLabel }}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<p class=\"cn-timeline-widget__label\">\n\t\t\t\t\t\t\t{{ translate(event.label) }}\n\t\t\t\t\t\t</p>\n\t\t\t\t\t\t<p v-if=\"event.detail\" class=\"cn-timeline-widget__detail\">\n\t\t\t\t\t\t\t{{ event.detail }}\n\t\t\t\t\t\t</p>\n\t\t\t\t\t</div>\n\t\t\t\t</li>\n\t\t\t</ol>\n\t\t</template>\n\t</section>\n</template>\n\n<script>\nimport { getLanguage, translate as t } from '@nextcloud/l10n'\nimport { NcLoadingIcon, NcNoteCard } from '@nextcloud/vue'\nimport { auditEvents, fieldEvents, listEvents, relatedEvents, sortEvents, timelineEntryEvents } from '../../utils/timelineEvents.js'\nimport { userLocale } from '../../utils/userLocale.js'\n\n/**\n * CnTimelineWidget — an object's dated events in time order (`timeline`\n * widget type for detail pages).\n *\n * Generalises dossiq's case timeline. It merges up to four sources, all\n * optional, into one list:\n *\n * - `fields`: the object's own date properties, `[{ field, label }]`. A\n * dotted path reaches metadata, such as `@self.created`.\n * - `related`: related objects, `[{ register?, schema, field, dateField?,\n * label, titleField? }]`. Rows of `schema` whose `field` holds this object's\n * id become events at their `dateField` (default `@self.created`).\n * - `auditTrail: true`: the object's audit trail, one event per change.\n * - `timeline: true`: OpenRegister's timeline of notes, calls and messages.\n *\n * Oldest first by default (`order: 'desc'` flips it). A moment after now is\n * marked upcoming, in words as well as style.\n *\n * ```json\n * { \"type\": \"timeline\", \"content\": {\n * \"title\": \"Timeline\",\n * \"fields\": [\n * { \"field\": \"@self.created\", \"label\": \"Booking created\" },\n * { \"field\": \"depositClearedAt\", \"label\": \"Deposit cleared\" },\n * { \"field\": \"confirmationSentAt\", \"label\": \"Confirmation mail sent\" },\n * { \"field\": \"startsAt\", \"label\": \"Starts\" },\n * { \"field\": \"endsAt\", \"label\": \"Ends\" }\n * ] } }\n * ```\n *\n * The object comes from the detail page's context, like `audit-trail`:\n * explicit props win, then the injects, then `content`.\n *\n * @spec openspec/changes/timeline-widget/specs/timeline-widget/spec.md#requirement-a-timeline-widget-shows-an-objects-dated-events-in-order\n */\nexport default {\n\tname: 'CnTimelineWidget',\n\n\tcomponents: {\n\t\tNcLoadingIcon,\n\t\tNcNoteCard,\n\t},\n\n\tinject: {\n\t\t/** Detail-page object context from CnDetailPage (a ref or a raw bag). */\n\t\tcnObjectContext: { default: null },\n\t\t/** Detail object context holder from CnPageRenderer (`{ value: { objectData, ... } }`). */\n\t\tcnDetailObjectContext: { default: null },\n\t\t/** Consumer translation function; labels are authored in English. */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/** Register slug; object context spread as props by CnWidgetGrid. */\n\t\tregister: { type: String, default: '' },\n\t\t/**\n\t\t * Schema slug, or the schema object the detail context supplies.\n\t\t *\n\t\t * @type {string|object}\n\t\t */\n\t\tschema: { type: [String, Object], default: '' },\n\t\t/** The object's id. */\n\t\tobjectId: { type: String, default: '' },\n\t\t/** The object itself, when the host already has it. */\n\t\tobject: { type: Object, default: null },\n\t\t/** Card title override. */\n\t\ttitle: { type: String, default: '' },\n\t\t/** Stored widget content: `{ title, fields, related, auditTrail, timeline, order }`. */\n\t\tcontent: { type: Object, default: () => ({}) },\n\t\t/** OpenRegister API base. */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tloading: false,\n\t\t\trelatedRows: {},\n\t\t\tauditEntries: [],\n\t\t\ttimelineEntries: [],\n\t\t\tfailedSources: [],\n\t\t\tfetchedObject: null,\n\t\t\tloadSeq: 0,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\tctx() {\n\t\t\tconst raw = this.cnObjectContext\n\t\t\tconst inj = raw && (raw.value !== undefined ? raw.value : raw)\n\t\t\tconst holder = this.cnDetailObjectContext && this.cnDetailObjectContext.value\n\t\t\treturn inj || holder || {}\n\t\t},\n\n\t\tresolvedObjectId() {\n\t\t\treturn this.objectId || this.ctx.objectId || this.content.objectId || ''\n\t\t},\n\n\t\tresolvedRegister() {\n\t\t\treturn this.register || this.ctx.register || this.content.register || ''\n\t\t},\n\n\t\tresolvedSchema() {\n\t\t\tconst s = this.schema || this.ctx.schema || this.content.schema || ''\n\t\t\treturn typeof s === 'string' ? s : (s && (s.slug || s.name || s.id)) || ''\n\t\t},\n\n\t\t/**\n\t\t * The object: explicit prop, then the page context, then a fetch of\n\t\t * our own when only the id is known.\n\t\t *\n\t\t * @return {object|null}\n\t\t */\n\t\tresolvedObject() {\n\t\t\treturn this.object || this.ctx.object || this.ctx.objectData || this.fetchedObject || null\n\t\t},\n\n\t\tresolvedTitle() {\n\t\t\tconst raw = this.title || this.content.title || ''\n\t\t\treturn raw ? this.translate(raw) : ''\n\t\t},\n\n\t\tfieldsConfig() {\n\t\t\treturn Array.isArray(this.content.fields) ? this.content.fields : []\n\t\t},\n\n\t\trelatedConfig() {\n\t\t\treturn Array.isArray(this.content.related) ? this.content.related.filter((r) => r && r.schema && r.field) : []\n\t\t},\n\n\t\t/**\n\t\t * Every event from every source, in time order.\n\t\t *\n\t\t * @return {Array<object>}\n\t\t */\n\t\tevents() {\n\t\t\tconst now = new Date()\n\t\t\tconst all = [\n\t\t\t\t...fieldEvents(this.resolvedObject || {}, this.fieldsConfig, now),\n\t\t\t\t...listEvents(this.resolvedObject || {}, this.content.lists, now),\n\t\t\t\t...this.relatedConfig.flatMap((cfg, i) => relatedEvents(this.relatedRows[i] || [], cfg, now)),\n\t\t\t\t...auditEvents(this.auditEntries, this.describeAudit, now),\n\t\t\t\t...timelineEntryEvents(this.timelineEntries, t('nextcloud-vue', 'Note'), now),\n\t\t\t]\n\t\t\treturn sortEvents(all, this.content.order === 'desc' ? 'desc' : 'asc')\n\t\t},\n\n\t\t/**\n\t\t * A signature of what to fetch, so a change refetches once.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tloadKey() {\n\t\t\treturn JSON.stringify({\n\t\t\t\tid: this.resolvedObjectId,\n\t\t\t\tregister: this.resolvedRegister,\n\t\t\t\tschema: this.resolvedSchema,\n\t\t\t\trelated: this.relatedConfig,\n\t\t\t\taudit: this.content.auditTrail === true,\n\t\t\t\ttimeline: this.content.timeline === true,\n\t\t\t\tneedObject: this.fieldsConfig.length > 0 && !(this.object || this.ctx.object || this.ctx.objectData),\n\t\t\t})\n\t\t},\n\n\t\tfailedText() {\n\t\t\treturn t('nextcloud-vue', 'Part of the timeline could not load: {parts}.', {\n\t\t\t\tparts: this.failedSources.map((s) => this.sourceName(s)).join(', '),\n\t\t\t})\n\t\t},\n\n\t\tloadingLabel() {\n\t\t\treturn t('nextcloud-vue', 'Loading…')\n\t\t},\n\n\t\temptyLabel() {\n\t\t\treturn t('nextcloud-vue', 'Nothing has happened yet.')\n\t\t},\n\n\t\tupcomingLabel() {\n\t\t\treturn t('nextcloud-vue', 'Upcoming')\n\t\t},\n\t},\n\n\twatch: {\n\t\tloadKey: {\n\t\t\timmediate: true,\n\t\t\thandler() {\n\t\t\t\tthis.load()\n\t\t\t},\n\t\t},\n\t},\n\n\tmethods: {\n\t\ttranslate(text) {\n\t\t\treturn text ? this.cnTranslate(text) : ''\n\t\t},\n\n\t\tsourceName(source) {\n\t\t\tif (source === 'audit') {\n\t\t\t\treturn t('nextcloud-vue', 'changes')\n\t\t\t}\n\t\t\tif (source === 'timeline') {\n\t\t\t\treturn t('nextcloud-vue', 'notes and messages')\n\t\t\t}\n\t\t\tif (source === 'object') {\n\t\t\t\treturn t('nextcloud-vue', 'dates')\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', 'related items')\n\t\t},\n\n\t\t/**\n\t\t * The label of an audit event, from its action and who did it.\n\t\t *\n\t\t * @param {string} action The audit action (create, update, delete).\n\t\t * @param {string} actor Who did it.\n\t\t * @return {string}\n\t\t */\n\t\tdescribeAudit(action, actor) {\n\t\t\tconst known = {\n\t\t\t\tcreate: t('nextcloud-vue', 'Created'),\n\t\t\t\tupdate: t('nextcloud-vue', 'Updated'),\n\t\t\t\tdelete: t('nextcloud-vue', 'Deleted'),\n\t\t\t}\n\t\t\tconst what = known[action] || action || t('nextcloud-vue', 'Changed')\n\t\t\treturn actor ? t('nextcloud-vue', '{action} by {actor}', { action: what, actor }) : what\n\t\t},\n\n\t\t/**\n\t\t * Show a moment in the reader's language: a date for a calendar day,\n\t\t * date and time otherwise.\n\t\t *\n\t\t * @param {object} event The event.\n\t\t * @return {string}\n\t\t */\n\t\tformatMoment(event) {\n\t\t\tconst locale = (typeof getLanguage === 'function' && getLanguage()) || undefined\n\t\t\tconst options = event.dateOnly\n\t\t\t\t? { dateStyle: 'medium' }\n\t\t\t\t: { dateStyle: 'medium', timeStyle: 'short' }\n\t\t\ttry {\n\t\t\t\treturn new Intl.DateTimeFormat(locale, options).format(event.at)\n\t\t\t} catch {\n\t\t\t\treturn event.at.toLocaleString(userLocale())\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Fetch every configured source. Each one fails on its own, so one\n\t\t * unreachable source never hides the others.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t * @spec openspec/changes/timeline-widget/specs/timeline-widget/spec.md#requirement-a-timeline-widget-shows-an-objects-dated-events-in-order\n\t\t */\n\t\tasync load() {\n\t\t\tconst id = this.resolvedObjectId\n\t\t\tconst register = this.resolvedRegister\n\t\t\tconst schema = this.resolvedSchema\n\t\t\tconst seq = ++this.loadSeq\n\t\t\tif (!id || !register || !schema) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst [{ default: axios }, { generateUrl }] = await Promise.all([\n\t\t\t\timport('@nextcloud/axios'),\n\t\t\t\timport('@nextcloud/router'),\n\t\t\t])\n\t\t\tconst base = `${this.apiBase}/objects/${register}/${schema}/${id}`\n\t\t\tconst failed = []\n\t\t\tconst jobs = []\n\n\t\t\tif (this.fieldsConfig.length > 0 && !(this.object || this.ctx.object || this.ctx.objectData)) {\n\t\t\t\tjobs.push(axios.get(generateUrl(base))\n\t\t\t\t\t.then((r) => {\n\t\t\t\t\t\tthis.fetchedObject = (r && r.data) || null\n\t\t\t\t\t})\n\t\t\t\t\t.catch(() => failed.push('object')))\n\t\t\t}\n\n\t\t\tconst rows = {}\n\t\t\tthis.relatedConfig.forEach((cfg, i) => {\n\t\t\t\tconst url = generateUrl(`${this.apiBase}/objects/${cfg.register || register}/${cfg.schema}`)\n\t\t\t\tjobs.push(axios.get(url, { params: { [cfg.field]: id, _limit: cfg.limit || 50 } })\n\t\t\t\t\t.then((r) => {\n\t\t\t\t\t\tconst data = r && r.data\n\t\t\t\t\t\trows[i] = (data && (data.results || data)) || []\n\t\t\t\t\t})\n\t\t\t\t\t.catch(() => {\n\t\t\t\t\t\trows[i] = []\n\t\t\t\t\t\tif (!failed.includes('related')) {\n\t\t\t\t\t\t\tfailed.push('related')\n\t\t\t\t\t\t}\n\t\t\t\t\t}))\n\t\t\t})\n\n\t\t\tif (this.content.auditTrail === true) {\n\t\t\t\tjobs.push(axios.get(generateUrl(`${base}/audit-trails`), { params: { limit: this.content.auditLimit || 50 } })\n\t\t\t\t\t.then((r) => {\n\t\t\t\t\t\tconst data = r && r.data\n\t\t\t\t\t\tthis.auditEntries = (data && (data.results || data)) || []\n\t\t\t\t\t})\n\t\t\t\t\t.catch(() => {\n\t\t\t\t\t\tthis.auditEntries = []\n\t\t\t\t\t\tfailed.push('audit')\n\t\t\t\t\t}))\n\t\t\t}\n\n\t\t\tif (this.content.timeline === true) {\n\t\t\t\tjobs.push(axios.get(generateUrl(`${base}/timeline`))\n\t\t\t\t\t.then((r) => {\n\t\t\t\t\t\tconst data = r && r.data\n\t\t\t\t\t\tthis.timelineEntries = (data && (data.results || data)) || []\n\t\t\t\t\t})\n\t\t\t\t\t.catch(() => {\n\t\t\t\t\t\tthis.timelineEntries = []\n\t\t\t\t\t\tfailed.push('timeline')\n\t\t\t\t\t}))\n\t\t\t}\n\n\t\t\tif (jobs.length === 0) {\n\t\t\t\tthis.failedSources = []\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\tawait Promise.all(jobs)\n\t\t\tif (seq !== this.loadSeq) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.relatedRows = rows\n\t\t\tthis.failedSources = failed\n\t\t\tthis.loading = false\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-timeline-widget {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 8px;\n}\n\n.cn-timeline-widget__title {\n\tmargin: 0;\n\tfont-size: var(--font-size-large, 1.1em);\n}\n\n.cn-timeline-widget__loading {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-timeline-widget__empty {\n\tmargin: 0;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-timeline-widget__list {\n\tmargin: 0;\n\tpadding: 0;\n\tlist-style: none;\n}\n\n.cn-timeline-widget__event {\n\tposition: relative;\n\tdisplay: flex;\n\tgap: 12px;\n\tpadding-block-end: 12px;\n}\n\n/* The line between dots: drawn from each dot down to the next event. */\n.cn-timeline-widget__event:not(:last-child)::before {\n\tcontent: '';\n\tposition: absolute;\n\tinset-block: 14px 0;\n\tinset-inline-start: 5px;\n\tborder-inline-start: 2px solid var(--color-border-dark);\n}\n\n.cn-timeline-widget__dot {\n\tflex: 0 0 auto;\n\twidth: 12px;\n\theight: 12px;\n\tmargin-block-start: 4px;\n\tborder-radius: 50%;\n\tbackground-color: var(--color-primary-element);\n}\n\n/* Upcoming: a hollow dot, plus the word \"Upcoming\" beside the date. */\n.cn-timeline-widget__event--upcoming .cn-timeline-widget__dot {\n\tbox-sizing: border-box;\n\tborder: 2px solid var(--color-primary-element);\n\tbackground-color: var(--color-main-background);\n}\n\n.cn-timeline-widget__body {\n\tmin-width: 0;\n}\n\n.cn-timeline-widget__when {\n\tfont-size: var(--font-size-small, 13px);\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-timeline-widget__badge {\n\tmargin-inline-start: 8px;\n\tfont-size: var(--font-size-small, 13px);\n\tfont-weight: bold;\n\tcolor: var(--color-main-text);\n}\n\n.cn-timeline-widget__label {\n\tmargin: 0;\n\tfont-weight: bold;\n}\n\n.cn-timeline-widget__detail {\n\tmargin: 0;\n\toverflow-wrap: anywhere;\n}\n</style>\n"],"names":["_createElementBlock","_toDisplayString","_createVNode","_createBlock","_openBlock","_Fragment","_renderList","_normalizeClass","_createElementVNode"],"mappings":";;;;;AAMM,EAAA,KAAA,EAAA,2BAAA;;;;AAI2B,EAAA,KAAA,EAAA,6BAAA;;;;AAW3B,EAAA,KAAA,EAAA,2BAAA;AAAA,EAAA,aAAA,EAAA,0BAAA;;;;AAIQ,EAAA,KAAK,EAAE,0BAAM;;;AASjB,MAAA,UAAA,GAAA,EAAA,KAAK,EAAC,0BAAe,GAAA;;;;AAInB,EAAA,KAAA,EAAA,2BAAA;;AAGH,MAAA,WAAA,GAAA,EAAA,KAAA,EAAA,2BAAA,GAAA;;;AAGA,EAAA,KAAA,EAAA,4BAAA;;;;;;;uBAvCNA;AAAO,IAAA,KAAK,EAAC,oBAAK;AAAA,IAAA,aAAA,EAAA,oBAAA;AAAA,IAAA,WAAA,EAAA,KAAA,CAAA,OAAA,GAAA,MAAA,GAAA,OAAA;;KACf,QAAA,CAAA,aAAA;sBAAJA,kBAEK,CAAA,IAAA,EAFL,UAEK,EAAAC,eAAA;;KAEM,KAAO,CAAA,OAAA,IAAK,QAAK,CAAA,MAAA,CAAC,MAAE,KAAA,CAAA;yCAGzB,iBAAA,EAAA;UAFLC,WAAI,CAAA,wBAAA,EAAA;AAAA,YAAA,IAAA,EAAA,EAAA;AAAA,YAAA,aAAA,EAAA,MAAA;;;;sBAIJF;AAEY,UAAA,CAAA,KAAA,CAAA,aAAA,CAAA,MAAA,GAAA,CAAA;4BAAZG,WAEa,CAAA,qBAAA,EAAA;;AAFD,gBAAA,IAAA,EAAA,SAAA;AAAA,gBAAA,aAAA,EAAA,0BAAA;;;;;;;;AAIT,UAAA,CAAA,QAAA,CAAA,MAAA,CAAA,MAAA,KAAA,CAAA;4BAAHH,kBAEI,CAAA,GAAA,EAFJ,UAEI,EAAAC,eAAA;AAEF,eAAAG,SAAA,EAAA,EAAAJ,kBAAA,CAwBG,MAxBH,UAwBG,EAAA;kCAvBHA,kBAsBI,CAAAK,QAAA,EAAA,IAAA,EAAAC,UAAA,CArBY,QAAM,CAAA,MAAA,EAAA,CAAf,KAAK,KAAA;uCADZN,kBAsBI,CAAA,IAAA,EAAA;oBApBH,GAAG,EAAE,KAAK,CAAC,EAAE;AACd,oBAAA,KAAK,EAACO,cAAA,CAAA,CAAA,2BAAoB,EACY,EAAA,qCAAA,EAAA,KAAA,CAAA,QAAA,EAAA,CAAA,CAAA;oBACrC,aAAW,EAAE,KAAK,CAAC,MAAM;AAC1B,oBAAA,aAAW,EAAC,0BAAyB;;8CACpCC,kBAA0D,CAAA,MAAA,EAAA;AAAtD,sBAAA,KAAM,EAAC,yBAAyB;AAAC,sBAAA,aAAW,EAAC,MAAM;;uCActD,iBAAA,EAAA;sBAZDA,kBAEO,CAAA,MAAA,EAAA;AAFD,wBAAA,KAAA,EAAA,0BAAA;AAAA,wBAAA,QAAA,EAAA,KAAA,CAAA,EAAA,CAAA,WAAA,EAAA;AACF,uBAAA,EAAAP,eAAA,CAAA,QAAA,CAAA,YAAY,CAAC,KAAK,CAAA,CAAA,EAAA,CAAA,oBAAA,UAAA,CAAA;AAEhB,sBAAA,CAAA,KAAA,CAAA,QAAA;wCAAND,kBAEO,CAAA,MAAA,EAFP,UAEO,EAAAC,eAAA,CADH,QAAa,CAAA,aAAA,CAAA,EAAA,CAAA,YAAA;;AAEjB,sBAAAO,kBAAA,CAEI,KAFJ,WAEI,EAAAP,eAAA,CADA,kBAAS,CAAC,KAAK,CAAC,KAAK,CAAA,CAAA,EAAA,CAAA,YAAA;AAEtB,sBAAA,CAAA,KAAA,CAAA,MAAA;AAAH,2BAAAG,SAAA,EAAA,EAAAJ,kBAAA,CAEE,GAFF,EAAA,WAEE,EADEC,eAAA,CAAA,KAAA,CAAA,MAAA,CAAA,EAAA,CAAA,YAAA;;;;;;;;;;;;"}
@@ -2,7 +2,7 @@ import script from './CnTranslatedBadge.vue2.js';
2
2
  import { render } from './CnTranslatedBadge.vue3.js';
3
3
 
4
4
  script.render = render;
5
- script.__scopeId = "data-v-355f5b08";
5
+ script.__scopeId = "data-v-37e3a382";
6
6
  const __sfc_main = script;
7
7
 
8
8
  export { __sfc_main as default };
@@ -1,4 +1,5 @@
1
1
  import { translate } from '@nextcloud/l10n';
2
+ import { userLocale } from '../../utils/userLocale.js';
2
3
 
3
4
  /**
4
5
  * CnTranslatedBadge — surfaces OR's `_translationMeta.translatedFrom`
@@ -144,7 +145,7 @@ var script = {
144
145
  try {
145
146
  const d = new Date(this.translatedAt);
146
147
  if (!Number.isNaN(d.getTime())) {
147
- return translate('nextcloud-vue', 'Translated at {when}', { when: d.toLocaleString() })
148
+ return translate('nextcloud-vue', 'Translated at {when}', { when: d.toLocaleString(userLocale()) })
148
149
  }
149
150
  } catch {
150
151
  // fall through to raw ISO
@@ -1 +1 @@
1
- {"version":3,"file":"CnTranslatedBadge.vue2.js","sources":["../../../../src/components/CnTranslatedBadge/CnTranslatedBadge.vue"],"sourcesContent":["<!--\n CnTranslatedBadge — stateless chip that surfaces OR's\n `_translationMeta` block.\n\n The badge auto-hides when the bound object has no `_translationMeta`\n block, or when `_translationMeta.translatedFrom` is null. When the\n field IS set, the badge renders \"(translated from {sourceLanguage})\"\n and exposes the `translatedAt` timestamp via the `title` attribute.\n\n Pairs with the language-negotiation getters on `createObjectStore`\n (see `i18n-language-negotiation-getters`): apps that wire\n `languageGetter` get translated bytes from OR; the badge then\n signals to the user that those bytes are a translation projection,\n not the source row.\n-->\n<template>\n\t<span\n\t\tv-if=\"visible\"\n\t\tclass=\"cn-translated-badge\"\n\t\t:title=\"hoverTitle\"\n\t\tdata-testid=\"cn-translated-badge\">\n\t\t<span class=\"cn-translated-badge__icon\" aria-hidden=\"true\">⇄</span>\n\t\t<span class=\"cn-translated-badge__label\">\n\t\t\t{{ label }}\n\t\t</span>\n\t</span>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\n\n/**\n * CnTranslatedBadge — surfaces OR's `_translationMeta.translatedFrom`\n * as a small chip in detail surfaces.\n *\n * Basic:\n * ```vue\n * <CnTranslatedBadge :object=\"object\" />\n * ```\n *\n * With custom locale name formatter:\n * ```vue\n * <CnTranslatedBadge\n * :object=\"object\"\n * :locale-name-formatter=\"(bcp47) => myLanguageLabels[bcp47] || bcp47\" />\n * ```\n */\nexport default {\n\tname: 'CnTranslatedBadge',\n\n\tprops: {\n\t\t/**\n\t\t * The OR object whose `_translationMeta` is surfaced. The\n\t\t * badge auto-hides when `object` is null, when\n\t\t * `object._translationMeta` is missing, or when\n\t\t * `_translationMeta.translatedFrom` is null/empty.\n\t\t *\n\t\t * @type {object|null}\n\t\t */\n\t\tobject: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Optional BCP-47 → display name function. Receives the\n\t\t * BCP-47 string from `_translationMeta.translatedFrom` and\n\t\t * returns a user-facing label (e.g. `'nl' → 'Nederlands'`).\n\t\t * When omitted, falls back to `Intl.DisplayNames` when\n\t\t * available, then to the raw BCP-47 string.\n\t\t *\n\t\t * @type {((locale: string) => string)|null}\n\t\t */\n\t\tlocaleNameFormatter: {\n\t\t\ttype: Function,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The translatedFrom BCP-47 string when present, else null.\n\t\t *\n\t\t * @return {string|null}\n\t\t */\n\t\ttranslatedFrom() {\n\t\t\tconst meta = this.object && this.object._translationMeta\n\t\t\tif (!meta) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst v = meta.translatedFrom\n\t\t\treturn typeof v === 'string' && v.length > 0 ? v : null\n\t\t},\n\n\t\t/**\n\t\t * The translatedAt ISO timestamp when present, else null.\n\t\t *\n\t\t * @return {string|null}\n\t\t */\n\t\ttranslatedAt() {\n\t\t\tconst meta = this.object && this.object._translationMeta\n\t\t\tif (!meta) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst v = meta.translatedAt\n\t\t\treturn typeof v === 'string' && v.length > 0 ? v : null\n\t\t},\n\n\t\t/**\n\t\t * Whether the badge should render at all.\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\tvisible() {\n\t\t\treturn this.translatedFrom !== null\n\t\t},\n\n\t\t/**\n\t\t * Display name for the source language. Falls back through\n\t\t * `localeNameFormatter` → `Intl.DisplayNames` → raw BCP-47.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tsourceDisplayName() {\n\t\t\tconst bcp47 = this.translatedFrom\n\t\t\tif (!bcp47) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tif (typeof this.localeNameFormatter === 'function') {\n\t\t\t\ttry {\n\t\t\t\t\tconst v = this.localeNameFormatter(bcp47)\n\t\t\t\t\tif (typeof v === 'string' && v.length > 0) {\n\t\t\t\t\t\treturn v\n\t\t\t\t\t}\n\t\t\t\t} catch {\n\t\t\t\t\t// fall through to Intl\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (typeof Intl !== 'undefined' && typeof Intl.DisplayNames === 'function') {\n\t\t\t\ttry {\n\t\t\t\t\tconst navLang = (typeof navigator !== 'undefined' && navigator.language) || 'en'\n\t\t\t\t\tconst dn = new Intl.DisplayNames([navLang], { type: 'language' })\n\t\t\t\t\tconst v = dn.of(bcp47)\n\t\t\t\t\tif (typeof v === 'string' && v.length > 0) {\n\t\t\t\t\t\treturn v\n\t\t\t\t\t}\n\t\t\t\t} catch {\n\t\t\t\t\t// fall through to raw\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn bcp47\n\t\t},\n\n\t\t/**\n\t\t * The user-facing label, e.g. `(translated from Dutch)`.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tlabel() {\n\t\t\treturn t('nextcloud-vue', '(translated from {language})', { language: this.sourceDisplayName })\n\t\t},\n\n\t\t/**\n\t\t * Hover title — exposes `translatedAt` so users can see how\n\t\t * old the projection is.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\thoverTitle() {\n\t\t\tif (!this.translatedAt) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst d = new Date(this.translatedAt)\n\t\t\t\tif (!Number.isNaN(d.getTime())) {\n\t\t\t\t\treturn t('nextcloud-vue', 'Translated at {when}', { when: d.toLocaleString() })\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// fall through to raw ISO\n\t\t\t}\n\t\t\treturn this.translatedAt\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-translated-badge {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: var(--default-grid-baseline, 4px);\n\tpadding: calc(0.5 * var(--default-grid-baseline, 4px)) calc(2 * var(--default-grid-baseline, 4px));\n\tborder-radius: var(--border-radius-pill, 999px);\n\tbackground: var(--color-background-hover);\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.75em;\n\tfont-weight: 500;\n\tline-height: 1;\n}\n\n.cn-translated-badge__icon {\n\tfont-size: 0.9em;\n}\n</style>\n"],"names":["t"],"mappings":"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"}
1
+ {"version":3,"file":"CnTranslatedBadge.vue2.js","sources":["../../../../src/components/CnTranslatedBadge/CnTranslatedBadge.vue"],"sourcesContent":["<!--\n CnTranslatedBadge — stateless chip that surfaces OR's\n `_translationMeta` block.\n\n The badge auto-hides when the bound object has no `_translationMeta`\n block, or when `_translationMeta.translatedFrom` is null. When the\n field IS set, the badge renders \"(translated from {sourceLanguage})\"\n and exposes the `translatedAt` timestamp via the `title` attribute.\n\n Pairs with the language-negotiation getters on `createObjectStore`\n (see `i18n-language-negotiation-getters`): apps that wire\n `languageGetter` get translated bytes from OR; the badge then\n signals to the user that those bytes are a translation projection,\n not the source row.\n-->\n<template>\n\t<span\n\t\tv-if=\"visible\"\n\t\tclass=\"cn-translated-badge\"\n\t\t:title=\"hoverTitle\"\n\t\tdata-testid=\"cn-translated-badge\">\n\t\t<span class=\"cn-translated-badge__icon\" aria-hidden=\"true\">⇄</span>\n\t\t<span class=\"cn-translated-badge__label\">\n\t\t\t{{ label }}\n\t\t</span>\n\t</span>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { userLocale } from '../../utils/userLocale.js'\n\n/**\n * CnTranslatedBadge — surfaces OR's `_translationMeta.translatedFrom`\n * as a small chip in detail surfaces.\n *\n * Basic:\n * ```vue\n * <CnTranslatedBadge :object=\"object\" />\n * ```\n *\n * With custom locale name formatter:\n * ```vue\n * <CnTranslatedBadge\n * :object=\"object\"\n * :locale-name-formatter=\"(bcp47) => myLanguageLabels[bcp47] || bcp47\" />\n * ```\n */\nexport default {\n\tname: 'CnTranslatedBadge',\n\n\tprops: {\n\t\t/**\n\t\t * The OR object whose `_translationMeta` is surfaced. The\n\t\t * badge auto-hides when `object` is null, when\n\t\t * `object._translationMeta` is missing, or when\n\t\t * `_translationMeta.translatedFrom` is null/empty.\n\t\t *\n\t\t * @type {object|null}\n\t\t */\n\t\tobject: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Optional BCP-47 → display name function. Receives the\n\t\t * BCP-47 string from `_translationMeta.translatedFrom` and\n\t\t * returns a user-facing label (e.g. `'nl' → 'Nederlands'`).\n\t\t * When omitted, falls back to `Intl.DisplayNames` when\n\t\t * available, then to the raw BCP-47 string.\n\t\t *\n\t\t * @type {((locale: string) => string)|null}\n\t\t */\n\t\tlocaleNameFormatter: {\n\t\t\ttype: Function,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The translatedFrom BCP-47 string when present, else null.\n\t\t *\n\t\t * @return {string|null}\n\t\t */\n\t\ttranslatedFrom() {\n\t\t\tconst meta = this.object && this.object._translationMeta\n\t\t\tif (!meta) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst v = meta.translatedFrom\n\t\t\treturn typeof v === 'string' && v.length > 0 ? v : null\n\t\t},\n\n\t\t/**\n\t\t * The translatedAt ISO timestamp when present, else null.\n\t\t *\n\t\t * @return {string|null}\n\t\t */\n\t\ttranslatedAt() {\n\t\t\tconst meta = this.object && this.object._translationMeta\n\t\t\tif (!meta) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst v = meta.translatedAt\n\t\t\treturn typeof v === 'string' && v.length > 0 ? v : null\n\t\t},\n\n\t\t/**\n\t\t * Whether the badge should render at all.\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\tvisible() {\n\t\t\treturn this.translatedFrom !== null\n\t\t},\n\n\t\t/**\n\t\t * Display name for the source language. Falls back through\n\t\t * `localeNameFormatter` → `Intl.DisplayNames` → raw BCP-47.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tsourceDisplayName() {\n\t\t\tconst bcp47 = this.translatedFrom\n\t\t\tif (!bcp47) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tif (typeof this.localeNameFormatter === 'function') {\n\t\t\t\ttry {\n\t\t\t\t\tconst v = this.localeNameFormatter(bcp47)\n\t\t\t\t\tif (typeof v === 'string' && v.length > 0) {\n\t\t\t\t\t\treturn v\n\t\t\t\t\t}\n\t\t\t\t} catch {\n\t\t\t\t\t// fall through to Intl\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (typeof Intl !== 'undefined' && typeof Intl.DisplayNames === 'function') {\n\t\t\t\ttry {\n\t\t\t\t\tconst navLang = (typeof navigator !== 'undefined' && navigator.language) || 'en'\n\t\t\t\t\tconst dn = new Intl.DisplayNames([navLang], { type: 'language' })\n\t\t\t\t\tconst v = dn.of(bcp47)\n\t\t\t\t\tif (typeof v === 'string' && v.length > 0) {\n\t\t\t\t\t\treturn v\n\t\t\t\t\t}\n\t\t\t\t} catch {\n\t\t\t\t\t// fall through to raw\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn bcp47\n\t\t},\n\n\t\t/**\n\t\t * The user-facing label, e.g. `(translated from Dutch)`.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tlabel() {\n\t\t\treturn t('nextcloud-vue', '(translated from {language})', { language: this.sourceDisplayName })\n\t\t},\n\n\t\t/**\n\t\t * Hover title — exposes `translatedAt` so users can see how\n\t\t * old the projection is.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\thoverTitle() {\n\t\t\tif (!this.translatedAt) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst d = new Date(this.translatedAt)\n\t\t\t\tif (!Number.isNaN(d.getTime())) {\n\t\t\t\t\treturn t('nextcloud-vue', 'Translated at {when}', { when: d.toLocaleString(userLocale()) })\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// fall through to raw ISO\n\t\t\t}\n\t\t\treturn this.translatedAt\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-translated-badge {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: var(--default-grid-baseline, 4px);\n\tpadding: calc(0.5 * var(--default-grid-baseline, 4px)) calc(2 * var(--default-grid-baseline, 4px));\n\tborder-radius: var(--border-radius-pill, 999px);\n\tbackground: var(--color-background-hover);\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.75em;\n\tfont-weight: 500;\n\tline-height: 1;\n}\n\n.cn-translated-badge__icon {\n\tfont-size: 0.9em;\n}\n</style>\n"],"names":["t"],"mappings":"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"}
@@ -2,7 +2,7 @@ import script from './CnVersionHistory.vue2.js';
2
2
  import { render } from './CnVersionHistory.vue3.js';
3
3
 
4
4
  script.render = render;
5
- script.__scopeId = "data-v-5888d95c";
5
+ script.__scopeId = "data-v-0fca5a52";
6
6
  const __sfc_main = script;
7
7
 
8
8
  export { __sfc_main as default };
@@ -7,6 +7,7 @@ import FileCompare from 'vue-material-design-icons/FileCompare.vue';
7
7
  import __sfc_main from '../CnDetailCard/CnDetailCard.vue.js';
8
8
  import { foldAuditTrailEntries } from '../../utils/auditTrailDiff.js';
9
9
  import { computeObjectDiff } from '../../utils/computeObjectDiff.js';
10
+ import { userLocale } from '../../utils/userLocale.js';
10
11
  import { buildHeaders } from '../../utils/headers.js';
11
12
 
12
13
  /**
@@ -348,7 +349,7 @@ var script = {
348
349
  if (Number.isNaN(d.getTime()) === true) {
349
350
  return String(raw)
350
351
  }
351
- return d.toLocaleString()
352
+ return d.toLocaleString(userLocale())
352
353
  },
353
354
  },
354
355
  };