@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":"CnPropertyValueCell.vue3.js","sources":["../../../../src/components/CnAdvancedFormDialog/CnPropertyValueCell.vue?vue&type=template&id=2d56424c&lang.js"],"sourcesContent":["<template>\n\t<div class=\"cn-advanced-form-dialog__value-cell-wrapper\">\n\t\t<!-- Edit mode -->\n\t\t<div\n\t\t\tv-if=\"isEditable && (resolvedWidget === 'boolean' || isEditing)\"\n\t\t\tclass=\"cn-advanced-form-dialog__value-input-container\"\n\t\t\t@click.stop>\n\t\t\t<div\n\t\t\t\tv-if=\"resolvedWidget === 'boolean'\"\n\t\t\t\tclass=\"cn-advanced-form-dialog__boolean-input-row\">\n\t\t\t\t<NcCheckboxRadioSwitch\n\t\t\t\t\t:modelValue=\"!!value\"\n\t\t\t\t\ttype=\"switch\"\n\t\t\t\t\tclass=\"cn-advanced-form-dialog__boolean-input-row__input\"\n\t\t\t\t\t@update:modelValue=\"emit($event)\">\n\t\t\t\t\t{{ displayName }}\n\t\t\t\t</NcCheckboxRadioSwitch>\n\t\t\t\t<InformationOutline\n\t\t\t\t\tv-if=\"schemaProp && schemaProp.description\"\n\t\t\t\t\t:title=\"schemaProp.description\"\n\t\t\t\t\tclass=\"cn-advanced-form-dialog__info-icon\"\n\t\t\t\t\t:size=\"16\" />\n\t\t\t</div>\n\t\t\t<div\n\t\t\t\tv-else-if=\"resolvedWidget === 'color'\"\n\t\t\t\tclass=\"cn-advanced-form-dialog__color-input-row\">\n\t\t\t\t<CnColorPicker\n\t\t\t\t\t:value=\"chromePickerValue\"\n\t\t\t\t\t:disableAlpha=\"!hasAlpha\"\n\t\t\t\t\t:mode=\"colorPickerMode\"\n\t\t\t\t\t@input=\"onChromeColorInput\" />\n\t\t\t\t<NcTextField\n\t\t\t\t\tref=\"inputRef\"\n\t\t\t\t\t:modelValue=\"colorTextValue\"\n\t\t\t\t\t:placeholder=\"colorPlaceholder\"\n\t\t\t\t\t@update:modelValue=\"onColorTextInput($event)\" />\n\t\t\t</div>\n\t\t\t<NcDateTimePicker\n\t\t\t\tv-else-if=\"resolvedWidget === 'datetime'\"\n\t\t\t\t:modelValue=\"datetimeValue\"\n\t\t\t\t:type=\"datetimePickerType\"\n\t\t\t\t:placeholder=\"displayName\"\n\t\t\t\t:inputLabel=\"displayName\"\n\t\t\t\t@update:modelValue=\"emitDatetime($event)\" />\n\t\t\t<NcTextArea\n\t\t\t\tv-else-if=\"resolvedWidget === 'textarea'\"\n\t\t\t\tref=\"inputRef\"\n\t\t\t\t:modelValue=\"stringValue\"\n\t\t\t\t:placeholder=\"displayName\"\n\t\t\t\t:rows=\"textareaRows\"\n\t\t\t\t:maxlength=\"maxLengthAttr\"\n\t\t\t\tclass=\"cn-advanced-form-dialog__textarea\"\n\t\t\t\t@update:modelValue=\"emit($event)\" />\n\t\t\t<NcSelect\n\t\t\t\tv-else-if=\"resolvedWidget === 'select'\"\n\t\t\t\t:modelValue=\"effectiveSelectValue\"\n\t\t\t\t:options=\"effectiveSelectOptions\"\n\t\t\t\t:multiple=\"effectiveSelectMultiple\"\n\t\t\t\t:taggable=\"effectiveSelectTaggable\"\n\t\t\t\t:pushTags=\"effectiveSelectTaggable\"\n\t\t\t\t:keepOpen=\"effectiveSelectMultiple\"\n\t\t\t\t:inputLabel=\"displayName\"\n\t\t\t\t:placeholder=\"displayName\"\n\t\t\t\t@update:modelValue=\"emitSelect($event)\" />\n\t\t\t<CnJsonViewer\n\t\t\t\tv-else-if=\"resolvedWidget === 'object'\"\n\t\t\t\t:value=\"objectJsonString\"\n\t\t\t\t:height=\"objectEditorHeight\"\n\t\t\t\tlanguage=\"json\"\n\t\t\t\t@update:value=\"emitObject($event)\" />\n\t\t\t<div\n\t\t\t\tv-else-if=\"resolvedWidget === 'objectArray'\"\n\t\t\t\tclass=\"cn-advanced-form-dialog__object-array\">\n\t\t\t\t<div class=\"cn-advanced-form-dialog__object-array-chips\">\n\t\t\t\t\t<button\n\t\t\t\t\t\tv-for=\"(item, idx) in objectArrayItems\"\n\t\t\t\t\t\t:key=\"idx\"\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tclass=\"cn-advanced-form-dialog__object-array-chip\"\n\t\t\t\t\t\t:title=\"t('nextcloud-vue', 'Edit item')\"\n\t\t\t\t\t\t@click.stop=\"openObjectArrayItem(idx)\">\n\t\t\t\t\t\t<span class=\"cn-advanced-form-dialog__object-array-chip-label\">{{ objectArrayItemLabel(item, idx) }}</span>\n\t\t\t\t\t\t<NcButton\n\t\t\t\t\t\t\tvariant=\"tertiary-no-background\"\n\t\t\t\t\t\t\t:aria-label=\"t('nextcloud-vue', 'Remove item')\"\n\t\t\t\t\t\t\t:title=\"t('nextcloud-vue', 'Remove item')\"\n\t\t\t\t\t\t\tclass=\"cn-advanced-form-dialog__object-array-chip-remove\"\n\t\t\t\t\t\t\t@click.stop=\"removeObjectArrayItem(idx)\">\n\t\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t\t<Close :size=\"14\" />\n\t\t\t\t\t\t\t</template>\n\t\t\t\t\t\t</NcButton>\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\t\t\t\t<NcButton\n\t\t\t\t\tvariant=\"secondary\"\n\t\t\t\t\tclass=\"cn-advanced-form-dialog__object-array-add\"\n\t\t\t\t\t@click.stop=\"openObjectArrayItem(null)\">\n\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t<Plus :size=\"16\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t{{ t('nextcloud-vue', 'Add item') }}\n\t\t\t\t</NcButton>\n\t\t\t\t<CnAdvancedFormDialog\n\t\t\t\t\tv-if=\"objectArrayDialogOpen\"\n\t\t\t\t\t:schema=\"schemaProp.items\"\n\t\t\t\t\t:item=\"objectArrayDialogItem\"\n\t\t\t\t\t:dialogTitle=\"objectArrayDialogTitle\"\n\t\t\t\t\t:show-metadata-tab=\"false\"\n\t\t\t\t\t@confirm=\"onObjectArrayConfirm\"\n\t\t\t\t\t@close=\"closeObjectArrayDialog\" />\n\t\t\t</div>\n\t\t\t<NcTextField\n\t\t\t\tv-else\n\t\t\t\tref=\"inputRef\"\n\t\t\t\t:modelValue=\"stringValue\"\n\t\t\t\t:type=\"inputType\"\n\t\t\t\t:placeholder=\"displayName\"\n\t\t\t\t:min=\"minimum\"\n\t\t\t\t:max=\"maximum\"\n\t\t\t\t:step=\"step\"\n\t\t\t\t:pattern=\"pattern\"\n\t\t\t\t:minlength=\"minLengthAttr\"\n\t\t\t\t:maxlength=\"maxLengthAttr\"\n\t\t\t\t@update:modelValue=\"emitConverted($event)\" />\n\t\t</div>\n\n\t\t<!-- Display mode -->\n\t\t<div\n\t\t\tv-else\n\t\t\t:title=\"editabilityWarning\">\n\t\t\t<pre\n\t\t\t\tv-if=\"typeof value === 'object' && value !== null\"\n\t\t\t\tclass=\"cn-advanced-form-dialog__json-value\">{{ formattedObjectValue }}</pre>\n\t\t\t<span\n\t\t\t\tv-else-if=\"resolvedWidget === 'datetime' && isValidDate(value)\">{{\n\t\t\t\tformattedDateValue\n\t\t\t}}</span>\n\t\t\t<span\n\t\t\t\tv-else-if=\"resolvedWidget === 'color' && value\"\n\t\t\t\tclass=\"cn-advanced-form-dialog__color-display\">\n\t\t\t\t<span\n\t\t\t\t\tclass=\"cn-advanced-form-dialog__color-swatch cn-advanced-form-dialog__color-swatch--readonly\"\n\t\t\t\t\t:style=\"colorSwatchStyle\" />\n\t\t\t\t<span>{{ displayValue }}</span>\n\t\t\t</span>\n\t\t\t<span v-else>{{ displayValue }}</span>\n\t\t</div>\n\n\t\t<!-- Help text: description + example -->\n\t\t<div\n\t\t\tv-if=\"showHelpText && (helpDescription || helpExample)\"\n\t\t\tclass=\"cn-advanced-form-dialog__field-help\">\n\t\t\t<span\n\t\t\t\tv-if=\"helpDescription\"\n\t\t\t\tclass=\"cn-advanced-form-dialog__field-description\">{{ helpDescription }}</span>\n\t\t\t<span\n\t\t\t\tv-if=\"helpExample\"\n\t\t\t\tclass=\"cn-advanced-form-dialog__field-example\">{{ t('nextcloud-vue', 'e.g.') }} {{ helpExample }}</span>\n\t\t</div>\n\n\t\t<!-- Inline validation error -->\n\t\t<div\n\t\t\tv-if=\"showHelpText && fieldError\"\n\t\t\tclass=\"cn-advanced-form-dialog__field-error\"\n\t\t\trole=\"alert\">\n\t\t\t{{ fieldError }}\n\t\t</div>\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport {\n\tNcButton,\n\tNcCheckboxRadioSwitch,\n\tNcDateTimePicker,\n\tNcSelect,\n\tNcTextArea,\n\tNcTextField,\n} from '@nextcloud/vue'\nimport { defineAsyncComponent } from 'vue'\nimport Close from 'vue-material-design-icons/Close.vue'\nimport InformationOutline from 'vue-material-design-icons/InformationOutline.vue'\nimport Plus from 'vue-material-design-icons/Plus.vue'\nimport CnColorPicker from '../CnColorPicker/CnColorPicker.vue'\nimport CnJsonViewer from '../CnJsonViewer/CnJsonViewer.vue'\nimport { formatValue, validateValue } from '../../utils/schema.js'\n\nconst SUPPORTED_WIDGETS = ['text', 'number', 'boolean', 'datetime', 'textarea', 'array', 'select', 'object', 'objectArray', 'color']\n\n/** String formats that map to HTML5 `<input type=\"url\">`. */\nconst URL_FORMATS = new Set([\n\t'url',\n\t'uri',\n\t'uri-reference',\n\t'iri',\n\t'iri-reference',\n\t'uri-template',\n\t'accessUrl',\n\t'shareUrl',\n\t'downloadUrl',\n])\n\n/** All color-related string formats — rendered with the `color` widget (swatch + text input). */\nconst COLOR_FORMATS = new Set([\n\t'color',\n\t'color-hex',\n\t'color-hex-alpha',\n\t'color-rgb',\n\t'color-rgba',\n\t'color-hsl',\n\t'color-hsla',\n])\n\n/** String formats that render as a multi-line textarea instead of a single-line input. */\nconst TEXTAREA_FORMATS = new Set(['html', 'markdown'])\n\nexport default {\n\tname: 'CnPropertyValueCell',\n\n\tcomponents: {\n\t\tNcTextField,\n\t\tNcTextArea,\n\t\tNcCheckboxRadioSwitch,\n\t\tNcSelect,\n\t\tNcButton,\n\t\tNcDateTimePicker,\n\t\tInformationOutline,\n\t\tPlus,\n\t\tClose,\n\t\tCnJsonViewer,\n\t\tCnColorPicker,\n\t\t// Lazy-required to break the circular dep with CnAdvancedFormDialog.\n\t\t// defineAsyncComponent is MANDATORY under Vue 3: a bare function in\n\t\t// `components:` is a functional component, so Vue CALLS it on render\n\t\t// and prints the returned Promise as text instead of mounting the\n\t\t// dialog (the same \"[object Promise]\" bug CnActionsMenu shipped).\n\t\t// `.then(m => m.default || m)` unwraps the dynamic-import module namespace\n\t\t// before Vue receives it — with rollup's `inlineDynamicImports: true`\n\t\t// the namespace is frozen, and downstream code attaching bookkeeping\n\t\t// to a frozen object throws.\n\t\tCnAdvancedFormDialog: defineAsyncComponent(() => import('./CnAdvancedFormDialog.vue').then((m) => m.default || m)),\n\t},\n\n\tprops: {\n\t\t/** The schema property key */\n\t\tpropertyKey: { type: String, required: true },\n\t\t/** Full JSON schema object */\n\t\tschema: { type: Object, default: null },\n\t\t/** Resolved current value (formData[key] ?? objectValue) */\n\t\tvalue: { type: [Boolean, String, Number, Object, Array], default: null },\n\t\t/** Whether this property is editable at all */\n\t\tisEditable: { type: Boolean, default: true },\n\t\t/** Whether this row is currently selected for editing */\n\t\tisEditing: { type: Boolean, default: false },\n\t\t/** Display name for the property (used in labels/placeholders) */\n\t\tdisplayName: { type: String, default: '' },\n\t\t/** Editability warning message shown as title when not editable */\n\t\teditabilityWarning: { type: String, default: null },\n\t\t/**\n\t\t * Override the auto-detected widget for this cell. When null (default),\n\t\t * the widget is derived from the schema (boolean/datetime/text/number),\n\t\t * with auto-detection for `format: 'text'` (textarea) and `type: 'array'`.\n\t\t * Accepted: 'text', 'number', 'boolean', 'datetime', 'textarea', 'array', 'select'.\n\t\t */\n\t\twidget: {\n\t\t\ttype: String,\n\t\t\tdefault: null,\n\t\t\tvalidator: (v) => v === null || SUPPORTED_WIDGETS.includes(v),\n\t\t},\n\n\t\t/** Options for the `select` widget. Each option may be a string, or `{ id, label }`. */\n\t\tselectOptions: { type: Array, default: null },\n\t\t/** Whether the `select` widget allows multiple values. */\n\t\tselectMultiple: { type: Boolean, default: true },\n\t\t/** Number of rows for the `textarea` widget. */\n\t\ttextareaRows: { type: Number, default: 4 },\n\t\t/** CSS height for the `object` widget's CodeMirror editor. */\n\t\tobjectEditorHeight: { type: String, default: '300px' },\n\t},\n\n\temits: ['update:value'],\n\n\tdata() {\n\t\treturn {\n\t\t\t/**\n\t\t\t * Optimistic color value while the user is dragging the native picker.\n\t\t\t * Used for the swatch preview to feel instant; the upstream\n\t\t\t * `update:value` emit is debounced so validation/re-renders don't\n\t\t\t * fire on every drag tick.\n\t\t\t */\n\t\t\tpendingColor: null,\n\t\t\tpendingColorTimer: null,\n\t\t\tcolorPickerOpen: false,\n\t\t\t/** State for the object-array sub-dialog. `null` index means \"add new\". */\n\t\t\tobjectArrayDialogOpen: false,\n\t\t\tobjectArrayDialogIndex: null,\n\t\t\tobjectArrayDialogItem: null,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\tschemaProp() {\n\t\t\treturn this.schema?.properties?.[this.propertyKey] || null\n\t\t},\n\n\t\t/**\n\t\t * Resolved widget after applying explicit override + schema auto-detection.\n\t\t * Arrays and string-with-enum become a `select` widget; the array/enum\n\t\t * shape is inferred from the schema by the `effectiveSelect*` computeds.\n\t\t *\n\t\t * @return {string} one of SUPPORTED_WIDGETS\n\t\t */\n\t\tresolvedWidget() {\n\t\t\tif (this.widget === 'array') {\n\t\t\t\treturn 'select'\n\t\t\t}\n\t\t\tif (this.widget) {\n\t\t\t\treturn this.widget\n\t\t\t}\n\t\t\tconst prop = this.schemaProp\n\t\t\tif (!prop) {\n\t\t\t\treturn 'text'\n\t\t\t}\n\t\t\tif (prop.type === 'boolean') {\n\t\t\t\treturn 'boolean'\n\t\t\t}\n\t\t\tif (prop.type === 'array') {\n\t\t\t\tif (prop.items?.type === 'object') {\n\t\t\t\t\treturn 'objectArray'\n\t\t\t\t}\n\t\t\t\treturn 'select'\n\t\t\t}\n\t\t\tif (prop.type === 'object') {\n\t\t\t\treturn 'object'\n\t\t\t}\n\t\t\tif (prop.type === 'string') {\n\t\t\t\tif (Array.isArray(prop.enum) && prop.enum.length > 0) {\n\t\t\t\t\treturn 'select'\n\t\t\t\t}\n\t\t\t\tconst fmt = prop.format || ''\n\t\t\t\tif (['date', 'time', 'date-time'].includes(fmt)) {\n\t\t\t\t\treturn 'datetime'\n\t\t\t\t}\n\t\t\t\tif (TEXTAREA_FORMATS.has(fmt)) {\n\t\t\t\t\treturn 'textarea'\n\t\t\t\t}\n\t\t\t\tif (COLOR_FORMATS.has(fmt)) {\n\t\t\t\t\treturn 'color'\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (prop.type === 'number' || prop.type === 'integer') {\n\t\t\t\treturn 'number'\n\t\t\t}\n\t\t\treturn 'text'\n\t\t},\n\n\t\tinputType() {\n\t\t\tconst prop = this.schemaProp\n\t\t\tif (!prop) {\n\t\t\t\treturn 'text'\n\t\t\t}\n\t\t\tconst fmt = prop.format || ''\n\t\t\tif (prop.type === 'string') {\n\t\t\t\tif (fmt === 'email' || fmt === 'idn-email') {\n\t\t\t\t\treturn 'email'\n\t\t\t\t}\n\t\t\t\tif (URL_FORMATS.has(fmt)) {\n\t\t\t\t\treturn 'url'\n\t\t\t\t}\n\t\t\t\tif (fmt === 'password') {\n\t\t\t\t\treturn 'password'\n\t\t\t\t}\n\t\t\t\tif (fmt === 'telephone' || fmt === 'phone') {\n\t\t\t\t\treturn 'tel'\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (prop.type === 'number' || prop.type === 'integer') {\n\t\t\t\treturn 'number'\n\t\t\t}\n\t\t\treturn 'text'\n\t\t},\n\n\t\tpattern() {\n\t\t\tconst prop = this.schemaProp\n\t\t\tif (!prop || prop.type !== 'string') {\n\t\t\t\treturn undefined\n\t\t\t}\n\t\t\tif (prop.pattern) {\n\t\t\t\treturn prop.pattern\n\t\t\t}\n\t\t\treturn undefined\n\t\t},\n\n\t\tcolorPlaceholder() {\n\t\t\tconst fmt = this.schemaProp?.format\n\t\t\tswitch (fmt) {\n\t\t\t\tcase 'color-hex': return '#rrggbb'\n\t\t\t\tcase 'color-hex-alpha': return '#rrggbbaa'\n\t\t\t\tcase 'color-rgb': return 'rgb(0, 0, 0)'\n\t\t\t\tcase 'color-rgba': return 'rgba(0, 0, 0, 1)'\n\t\t\t\tcase 'color-hsl': return 'hsl(0, 0%, 0%)'\n\t\t\t\tcase 'color-hsla': return 'hsla(0, 0%, 0%, 1)'\n\t\t\t\tdefault: return this.displayName || '#rrggbb'\n\t\t\t}\n\t\t},\n\n\t\t/** CSS-renderable representation of the current color value (raw value works for all standard formats). */\n\t\tcolorPreviewValue() {\n\t\t\tif (this.pendingColor) {\n\t\t\t\treturn this.pendingColor\n\t\t\t}\n\t\t\tconst v = this.stringValue\n\t\t\tif (!v) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn v\n\t\t},\n\n\t\t/** Text-field value: shows the optimistic pendingColor while the picker is dragging. */\n\t\tcolorTextValue() {\n\t\t\tif (this.pendingColor) {\n\t\t\t\treturn this.pendingColor\n\t\t\t}\n\t\t\treturn this.stringValue\n\t\t},\n\n\t\t/** True when the schema-declared format includes an alpha channel. */\n\t\thasAlpha() {\n\t\t\tconst fmt = this.schemaProp?.format\n\t\t\treturn fmt === 'color-hex-alpha' || fmt === 'color-rgba' || fmt === 'color-hsla'\n\t\t},\n\n\t\t/**\n\t\t * Lock the picker's numeric-field mode to match the schema format so\n\t\t * the user can't edit, say, an `rgba()` value via hex inputs.\n\t\t */\n\t\tcolorPickerMode() {\n\t\t\tconst fmt = this.schemaProp?.format\n\t\t\tif (fmt === 'color-rgb' || fmt === 'color-rgba') {\n\t\t\t\treturn 'rgb'\n\t\t\t}\n\t\t\tif (fmt === 'color-hsl' || fmt === 'color-hsla') {\n\t\t\t\treturn 'hsl'\n\t\t\t}\n\t\t\treturn 'hex'\n\t\t},\n\n\t\t/**\n\t\t * Value passed to vue-color's `Chrome` picker. We feed the picker the\n\t\t * latest displayable value (pending or committed) so dragging the\n\t\t * picker stays smooth even while the upstream emit is debounced.\n\t\t */\n\t\tchromePickerValue() {\n\t\t\tconst v = this.pendingColor || this.stringValue\n\t\t\tif (v) {\n\t\t\t\treturn v\n\t\t\t}\n\t\t\treturn { hex: this.hexColorValue, a: 1 }\n\t\t},\n\n\t\t/**\n\t\t * Inline style for the color swatch: layers a solid color over the checker\n\t\t * background-image so alpha colors render against the checker pattern.\n\t\t */\n\t\tcolorSwatchStyle() {\n\t\t\tconst c = this.colorPreviewValue\n\t\t\tif (!c) {\n\t\t\t\treturn {}\n\t\t\t}\n\t\t\tconst fill = `linear-gradient(${c}, ${c})`\n\t\t\treturn {\n\t\t\t\tbackgroundImage: `${fill}, var(--cn-color-swatch-checker)`,\n\t\t\t\tbackgroundSize: '100% 100%, 8px 8px',\n\t\t\t\tbackgroundPosition: '0 0, 0 0',\n\t\t\t}\n\t\t},\n\n\t\t/** Hex string used as the value of the native `<input type=\"color\">`. */\n\t\thexColorValue() {\n\t\t\tconst v = this.stringValue\n\t\t\tif (!v) {\n\t\t\t\treturn '#000000'\n\t\t\t}\n\t\t\tconst trimmed = v.trim()\n\t\t\tconst hexMatch = trimmed.match(/^#([0-9a-f]{3}|[0-9a-f]{4}|[0-9a-f]{6}|[0-9a-f]{8})$/i)\n\t\t\tif (hexMatch) {\n\t\t\t\tconst h = hexMatch[1]\n\t\t\t\tif (h.length === 3 || h.length === 4) {\n\t\t\t\t\treturn '#' + h.slice(0, 3).split('').map((c) => c + c).join('')\n\t\t\t\t}\n\t\t\t\treturn '#' + h.slice(0, 6)\n\t\t\t}\n\t\t\tconst computed = this.cssColorToHex(trimmed)\n\t\t\treturn computed || '#000000'\n\t\t},\n\n\t\tminimum() {\n\t\t\treturn this.schemaProp?.minimum\n\t\t},\n\n\t\tmaximum() {\n\t\t\treturn this.schemaProp?.maximum\n\t\t},\n\n\t\tstep() {\n\t\t\tconst prop = this.schemaProp\n\t\t\tif (prop?.type === 'integer') {\n\t\t\t\treturn '1'\n\t\t\t}\n\t\t\tif (prop?.type === 'number') {\n\t\t\t\treturn 'any'\n\t\t\t}\n\t\t\treturn undefined\n\t\t},\n\n\t\tminLengthAttr() {\n\t\t\tconst v = this.schemaProp?.minLength\n\t\t\treturn typeof v === 'number' ? v : undefined\n\t\t},\n\n\t\tmaxLengthAttr() {\n\t\t\tconst v = this.schemaProp?.maxLength\n\t\t\treturn typeof v === 'number' ? v : undefined\n\t\t},\n\n\t\tshowHelpText() {\n\t\t\treturn this.isEditable && (this.resolvedWidget === 'boolean' || this.isEditing)\n\t\t},\n\n\t\thelpDescription() {\n\t\t\tconst prop = this.schemaProp\n\t\t\tif (!prop) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn prop.userDescription || prop.description || ''\n\t\t},\n\n\t\thelpExample() {\n\t\t\tconst ex = this.schemaProp?.example\n\t\t\tif (ex === undefined || ex === null || ex === '') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tif (typeof ex === 'object') {\n\t\t\t\ttry {\n\t\t\t\t\treturn JSON.stringify(ex)\n\t\t\t\t} catch {\n\t\t\t\t\treturn ''\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn String(ex)\n\t\t},\n\n\t\t/**\n\t\t * Inline validation message for the current value, or null when valid.\n\t\t * Required-ness is owned by the parent form so it isn't surfaced here.\n\t\t */\n\t\tfieldError() {\n\t\t\tconst raw = validateValue(this.value, this.schemaProp || {})\n\t\t\treturn raw ? t('nextcloud-vue', raw) : null\n\t\t},\n\n\t\t/** Resolved option list for the `select` widget (explicit prop, schema enum, or items.enum). */\n\t\teffectiveSelectOptions() {\n\t\t\tif (this.selectOptions) {\n\t\t\t\treturn this.selectOptions\n\t\t\t}\n\t\t\tconst prop = this.schemaProp\n\t\t\tif (!prop) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\tif (Array.isArray(prop.enum) && prop.enum.length > 0) {\n\t\t\t\treturn prop.enum\n\t\t\t}\n\t\t\tif (prop.type === 'array' && Array.isArray(prop.items?.enum) && prop.items.enum.length > 0) {\n\t\t\t\treturn prop.items.enum\n\t\t\t}\n\t\t\treturn []\n\t\t},\n\n\t\t/** Whether the `select` widget allows multiple values. */\n\t\teffectiveSelectMultiple() {\n\t\t\tif (this.widget === 'select') {\n\t\t\t\treturn this.selectMultiple\n\t\t\t}\n\t\t\tif (this.widget === 'array') {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\tconst prop = this.schemaProp\n\t\t\tif (prop?.type === 'array') {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\treturn false\n\t\t},\n\n\t\t/** Whether the `select` widget accepts free-form tags (no fixed enum). */\n\t\teffectiveSelectTaggable() {\n\t\t\tif (this.widget === 'select') {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tconst prop = this.schemaProp\n\t\t\tconst isArray = this.widget === 'array' || prop?.type === 'array'\n\t\t\treturn isArray && this.effectiveSelectOptions.length === 0\n\t\t},\n\n\t\t/** Selected-value object(s) for NcSelect, mapping ids back to option objects when needed. */\n\t\teffectiveSelectValue() {\n\t\t\tconst v = this.value\n\t\t\tconst opts = this.effectiveSelectOptions\n\t\t\tconst lookup = (id) => {\n\t\t\t\tconst match = opts.find((o) => (typeof o === 'object' ? o.id : o) === id)\n\t\t\t\treturn match !== undefined ? match : id\n\t\t\t}\n\t\t\tif (this.effectiveSelectMultiple) {\n\t\t\t\tif (!Array.isArray(v)) {\n\t\t\t\t\treturn []\n\t\t\t\t}\n\t\t\t\treturn v.map(lookup)\n\t\t\t}\n\t\t\tif (v === null || v === undefined || v === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn lookup(v)\n\t\t},\n\n\t\t/** Items array for the `objectArray` widget. Always an array. */\n\t\tobjectArrayItems() {\n\t\t\treturn Array.isArray(this.value) ? this.value : []\n\t\t},\n\n\t\t/** Title shown in the sub-dialog when adding/editing an object item. */\n\t\tobjectArrayDialogTitle() {\n\t\t\tconst itemTitle = this.schemaProp?.items?.title || this.displayName || t('nextcloud-vue', 'Item')\n\t\t\treturn this.objectArrayDialogIndex === null\n\t\t\t\t? t('nextcloud-vue', 'Add {title}', { title: itemTitle })\n\t\t\t\t: t('nextcloud-vue', 'Edit {title}', { title: itemTitle })\n\t\t},\n\n\t\t/**\n\t\t * NcDateTimePicker `type` mapped from the schema's string `format`.\n\t\t * Falls back to `datetime` for unknown date-ish formats.\n\t\t */\n\t\tdatetimePickerType() {\n\t\t\tconst fmt = this.schemaProp?.format\n\t\t\tif (fmt === 'date') {\n\t\t\t\treturn 'date'\n\t\t\t}\n\t\t\tif (fmt === 'time') {\n\t\t\t\treturn 'time'\n\t\t\t}\n\t\t\treturn 'datetime'\n\t\t},\n\n\t\t/** Current value as a `Date` instance for NcDateTimePicker, or null. */\n\t\tdatetimeValue() {\n\t\t\tconst v = this.value\n\t\t\tif (!v) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\t// Date-only strings (YYYY-MM-DD) are parsed as UTC midnight by the spec,\n\t\t\t// which shifts to the previous day in positive-UTC-offset timezones when\n\t\t\t// fed to a picker that renders in local time. Parse them as local midnight.\n\t\t\tif (this.schemaProp?.format === 'date'\n\t\t\t\t&& typeof v === 'string'\n\t\t\t\t&& /^\\d{4}-\\d{2}-\\d{2}$/.test(v)) {\n\t\t\t\tconst [year, month, day] = v.split('-').map(Number)\n\t\t\t\treturn new Date(year, month - 1, day)\n\t\t\t}\n\t\t\tconst d = new Date(v)\n\t\t\treturn Number.isNaN(d.getTime()) ? null : d\n\t\t},\n\n\t\tstringValue() {\n\t\t\tconst v = this.value\n\t\t\tif (v === null || v === undefined) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tif (typeof v === 'string') {\n\t\t\t\treturn v\n\t\t\t}\n\t\t\tif (typeof v === 'object') {\n\t\t\t\treturn JSON.stringify(v)\n\t\t\t}\n\t\t\treturn String(v)\n\t\t},\n\n\t\tobjectJsonString() {\n\t\t\tconst v = this.value\n\t\t\tif (v === null || v === undefined) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tif (typeof v === 'string') {\n\t\t\t\treturn v\n\t\t\t}\n\t\t\ttry {\n\t\t\t\treturn JSON.stringify(v, null, 2)\n\t\t\t} catch {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t},\n\n\t\tformattedObjectValue() {\n\t\t\ttry {\n\t\t\t\treturn JSON.stringify(this.value, null, 2)\n\t\t\t} catch {\n\t\t\t\treturn formatValue(this.value, this.schemaProp || {})\n\t\t\t}\n\t\t},\n\n\t\tdisplayValue() {\n\t\t\tconst prop = this.schemaProp\n\t\t\tif (prop?.const !== undefined) {\n\t\t\t\treturn prop.const\n\t\t\t}\n\t\t\tconst v = this.value\n\t\t\tif (v === null || v === undefined || v === '') {\n\t\t\t\treturn '—'\n\t\t\t}\n\t\t\treturn formatValue(v, prop || {})\n\t\t},\n\n\t\tformattedDateValue() {\n\t\t\tconst v = this.value\n\t\t\tif (!v) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst fmt = this.schemaProp?.format\n\t\t\t// Same local-midnight parse as datetimeValue to avoid UTC-shift in display.\n\t\t\tif (fmt === 'date' && typeof v === 'string' && /^\\d{4}-\\d{2}-\\d{2}$/.test(v)) {\n\t\t\t\tconst [year, month, day] = v.split('-').map(Number)\n\t\t\t\treturn new Date(year, month - 1, day).toLocaleDateString()\n\t\t\t}\n\t\t\tconst d = new Date(v)\n\t\t\tif (Number.isNaN(d.getTime())) {\n\t\t\t\treturn String(v)\n\t\t\t}\n\t\t\tif (fmt === 'date') {\n\t\t\t\treturn d.toLocaleDateString()\n\t\t\t}\n\t\t\tif (fmt === 'time') {\n\t\t\t\treturn d.toLocaleTimeString()\n\t\t\t}\n\t\t\treturn d.toLocaleString()\n\t\t},\n\t},\n\n\tbeforeUnmount() {\n\t\tif (this.pendingColorTimer) {\n\t\t\tclearTimeout(this.pendingColorTimer)\n\t\t\tthis.pendingColorTimer = null\n\t\t}\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/** Focus the underlying text input (called by parent after row click) */\n\t\tfocus() {\n\t\t\tconst ref = this.$refs.inputRef\n\t\t\tif (!ref) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst el = ref.$el || ref\n\t\t\tconst input = el?.querySelector?.('input,textarea')\n\t\t\tif (input) {\n\t\t\t\tinput.focus()\n\t\t\t\tif (typeof input.select === 'function') {\n\t\t\t\t\tinput.select()\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\temit(newVal) {\n\t\t\tthis.$emit('update:value', newVal)\n\t\t},\n\n\t\temitConverted(newVal) {\n\t\t\tconst prop = this.schemaProp\n\t\t\tlet converted = newVal\n\t\t\tif (prop) {\n\t\t\t\tswitch (prop.type) {\n\t\t\t\t\tcase 'number':\n\t\t\t\t\t\tconverted = newVal === '' ? null : parseFloat(newVal)\n\t\t\t\t\t\tif (Number.isNaN(converted)) {\n\t\t\t\t\t\t\tconverted = null\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak\n\t\t\t\t\tcase 'integer':\n\t\t\t\t\t\tconverted = newVal === '' ? null : parseInt(newVal, 10)\n\t\t\t\t\t\tif (Number.isNaN(converted)) {\n\t\t\t\t\t\t\tconverted = null\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak\n\t\t\t\t\tcase 'boolean':\n\t\t\t\t\t\tconverted = Boolean(newVal)\n\t\t\t\t\t\tbreak\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tconverted = newVal\n\t\t\t\t}\n\t\t\t}\n\t\t\tthis.$emit('update:value', converted)\n\t\t},\n\n\t\t/**\n\t\t * Emit a `Date` from NcDateTimePicker as the schema-appropriate string:\n\t\t * `date` → `YYYY-MM-DD`, `time` → `HH:MM:SS`, `date-time` → ISO 8601.\n\t\t *\n\t\t * @param {Date|null} date - Date emitted by the picker.\n\t\t */\n\t\temitDatetime(date) {\n\t\t\tif (!date) {\n\t\t\t\tthis.$emit('update:value', null)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst fmt = this.schemaProp?.format\n\t\t\tif (fmt === 'date') {\n\t\t\t\t// Use local-time accessors — toISOString() converts to UTC first,\n\t\t\t\t// which shifts midnight local time to the previous day in UTC+n zones.\n\t\t\t\tconst year = date.getFullYear()\n\t\t\t\tconst month = String(date.getMonth() + 1).padStart(2, '0')\n\t\t\t\tconst day = String(date.getDate()).padStart(2, '0')\n\t\t\t\tthis.$emit('update:value', `${year}-${month}-${day}`)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (fmt === 'time') {\n\t\t\t\tthis.$emit('update:value', date.toTimeString().slice(0, 8))\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.$emit('update:value', date.toISOString())\n\t\t},\n\n\t\temitObject(jsonString) {\n\t\t\tif (typeof jsonString !== 'string') {\n\t\t\t\tthis.$emit('update:value', jsonString)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst trimmed = jsonString.trim()\n\t\t\tif (trimmed === '') {\n\t\t\t\tthis.$emit('update:value', null)\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tthis.$emit('update:value', JSON.parse(trimmed))\n\t\t\t} catch {\n\t\t\t\t// Invalid JSON: keep the raw string so the user sees what they typed.\n\t\t\t\t// CnJsonViewer surfaces a parse error inline; the parent's save path\n\t\t\t\t// can refuse non-object values if it requires structured data.\n\t\t\t\tthis.$emit('update:value', jsonString)\n\t\t\t}\n\t\t},\n\n\t\temitSelect(selected) {\n\t\t\tconst toId = (item) => (item && typeof item === 'object' ? item.id : item)\n\t\t\tif (this.effectiveSelectMultiple) {\n\t\t\t\tconst arr = Array.isArray(selected) ? selected : []\n\t\t\t\tconst itemType = this.schemaProp?.items?.type\n\t\t\t\t// Coerce taggable input (always emitted as strings) to the\n\t\t\t\t// declared `items.type`. Drop entries that fail to coerce.\n\t\t\t\tconst coerced = arr\n\t\t\t\t\t.map(toId)\n\t\t\t\t\t.map((v) => this.coerceItem(v, itemType))\n\t\t\t\t\t.filter((v) => v !== undefined)\n\t\t\t\tthis.$emit('update:value', coerced)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.$emit('update:value', selected === null || selected === undefined ? null : toId(selected))\n\t\t},\n\n\t\t/**\n\t\t * Coerce a raw select-emitted value (typically a string from a\n\t\t * taggable NcSelect) into the array's declared `items.type`. Returns\n\t\t * `undefined` for entries that can't be coerced so the caller can drop\n\t\t * them from the array.\n\t\t *\n\t\t * @param {unknown} v - The raw value.\n\t\t * @param {string} [itemType] - Schema `items.type` (string, number, integer, boolean).\n\t\t * @return {unknown}\n\t\t */\n\t\tcoerceItem(v, itemType) {\n\t\t\tif (v === null || v === undefined) {\n\t\t\t\treturn v\n\t\t\t}\n\t\t\t// No declared item type — pass through untouched (preserves the\n\t\t\t// shape consumers may have set up via `selectOptions` etc).\n\t\t\tif (!itemType) {\n\t\t\t\treturn v\n\t\t\t}\n\t\t\t// Already the right shape — pass through.\n\t\t\tif (itemType === 'number' && typeof v === 'number') {\n\t\t\t\treturn v\n\t\t\t}\n\t\t\tif (itemType === 'integer' && typeof v === 'number' && Number.isInteger(v)) {\n\t\t\t\treturn v\n\t\t\t}\n\t\t\tif (itemType === 'boolean' && typeof v === 'boolean') {\n\t\t\t\treturn v\n\t\t\t}\n\t\t\tif (itemType === 'string' && typeof v === 'string') {\n\t\t\t\treturn v\n\t\t\t}\n\t\t\tconst s = String(v).trim()\n\t\t\tif (s === '') {\n\t\t\t\treturn undefined\n\t\t\t}\n\t\t\tif (itemType === 'number') {\n\t\t\t\tconst n = Number(s)\n\t\t\t\treturn Number.isFinite(n) ? n : undefined\n\t\t\t}\n\t\t\tif (itemType === 'integer') {\n\t\t\t\tconst n = Number(s)\n\t\t\t\treturn Number.isFinite(n) && Number.isInteger(n) ? n : undefined\n\t\t\t}\n\t\t\tif (itemType === 'boolean') {\n\t\t\t\tif (/^(true|1|yes|on)$/i.test(s)) {\n\t\t\t\t\treturn true\n\t\t\t\t}\n\t\t\t\tif (/^(false|0|no|off)$/i.test(s)) {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t\treturn undefined\n\t\t\t}\n\t\t\treturn s\n\t\t},\n\n\t\t/**\n\t\t * Open the sub-dialog to add a new object item or edit an existing\n\t\t * one. `idx === null` means add.\n\t\t *\n\t\t * @param {number|null} idx - Index of the item to edit, or `null`.\n\t\t */\n\t\topenObjectArrayItem(idx) {\n\t\t\tthis.objectArrayDialogIndex = idx\n\t\t\tthis.objectArrayDialogItem = idx === null\n\t\t\t\t? null\n\t\t\t\t: JSON.parse(JSON.stringify(this.objectArrayItems[idx] || {}))\n\t\t\tthis.objectArrayDialogOpen = true\n\t\t},\n\n\t\tcloseObjectArrayDialog() {\n\t\t\tthis.objectArrayDialogOpen = false\n\t\t\tthis.objectArrayDialogIndex = null\n\t\t\tthis.objectArrayDialogItem = null\n\t\t},\n\n\t\t/**\n\t\t * Confirmed object from the sub-dialog. Replace the existing item or\n\t\t * append a new one, then emit the updated array.\n\t\t *\n\t\t * @param {object} formData - Form data emitted by CnAdvancedFormDialog.\n\t\t */\n\t\tonObjectArrayConfirm(formData) {\n\t\t\tconst next = [...this.objectArrayItems]\n\t\t\tif (this.objectArrayDialogIndex === null) {\n\t\t\t\tnext.push(formData)\n\t\t\t} else {\n\t\t\t\tnext.splice(this.objectArrayDialogIndex, 1, formData)\n\t\t\t}\n\t\t\tthis.$emit('update:value', next)\n\t\t\tthis.closeObjectArrayDialog()\n\t\t},\n\n\t\t/**\n\t\t * Remove an item from the object array.\n\t\t *\n\t\t * @param {number} idx - Index of the item to remove.\n\t\t */\n\t\tremoveObjectArrayItem(idx) {\n\t\t\tconst next = [...this.objectArrayItems]\n\t\t\tnext.splice(idx, 1)\n\t\t\tthis.$emit('update:value', next)\n\t\t},\n\n\t\t/**\n\t\t * Pick a human-readable label for an item chip. Tries the schema-\n\t\t * declared name field first, then the first non-empty primitive\n\t\t * property, then falls back to \"Item N\".\n\t\t *\n\t\t * @param {object} item - The array item.\n\t\t * @param {number} idx - Index of the item (used for fallback label).\n\t\t * @return {string}\n\t\t */\n\t\tobjectArrayItemLabel(item, idx) {\n\t\t\tconst items = this.schemaProp?.items\n\t\t\tconst nameField = items?.objectConfiguration?.objectNameField\n\t\t\t\t|| items?.configuration?.objectNameField\n\t\t\tif (nameField && item && item[nameField] !== null && item[nameField] !== undefined && item[nameField] !== '') {\n\t\t\t\treturn String(item[nameField])\n\t\t\t}\n\t\t\tif (item && typeof item === 'object') {\n\t\t\t\tfor (const v of Object.values(item)) {\n\t\t\t\t\tif (v === null || v === undefined || v === '') {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tif (typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean') {\n\t\t\t\t\t\treturn String(v)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', 'Item {n}', { n: idx + 1 })\n\t\t},\n\n\t\tisValidDate(v) {\n\t\t\tif (!v) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tconst d = new Date(v)\n\t\t\treturn d instanceof Date && !Number.isNaN(d.getTime())\n\t\t},\n\n\t\t/**\n\t\t * Convert any CSS-recognized color string to a 6-digit hex string by\n\t\t * round-tripping through a detached DOM node. Returns null when the\n\t\t * browser cannot parse the input.\n\t\t *\n\t\t * @param {string} cssValue - The CSS color value to convert.\n\t\t * @return {string|null}\n\t\t */\n\t\tcssColorToHex(cssValue) {\n\t\t\ttry {\n\t\t\t\tconst el = document.createElement('div')\n\t\t\t\tel.style.color = ''\n\t\t\t\tel.style.color = cssValue\n\t\t\t\tif (!el.style.color) {\n\t\t\t\t\treturn null\n\t\t\t\t}\n\t\t\t\tdocument.body.appendChild(el)\n\t\t\t\tconst rgb = getComputedStyle(el).color\n\t\t\t\tdocument.body.removeChild(el)\n\t\t\t\tconst m = rgb.match(/rgba?\\((\\d+),\\s*(\\d+),\\s*(\\d+)/)\n\t\t\t\tif (!m) {\n\t\t\t\t\treturn null\n\t\t\t\t}\n\t\t\t\tconst toHex = (n) => parseInt(n, 10).toString(16).padStart(2, '0')\n\t\t\t\treturn `#${toHex(m[1])}${toHex(m[2])}${toHex(m[3])}`\n\t\t\t} catch {\n\t\t\t\treturn null\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Continuous handler for vue-color's `Chrome` picker. Updates the\n\t\t * swatch + text input synchronously via `pendingColor`, but debounces\n\t\t * the upstream `update:value` emit so validation and parent re-renders\n\t\t * don't fire on every drag tick.\n\t\t *\n\t\t * @param {object} color - vue-color emitted color object.\n\t\t * @param {string} color.hex - `#rrggbb`.\n\t\t * @param {string} [color.hex8] - `#rrggbbaa`, when alpha is enabled.\n\t\t * @param {object} color.rgba - RGBA components (`r`, `g`, `b`, `a`).\n\t\t * @param {object} color.hsl - HSL components (`h`, `s`, `l`, `a`).\n\t\t * @param {number} [color.a] - Alpha 0–1.\n\t\t */\n\t\tonChromeColorInput(color) {\n\t\t\tthis.pendingColor = this.chromeColorToFormatValue(color)\n\t\t\tif (this.pendingColorTimer) {\n\t\t\t\tclearTimeout(this.pendingColorTimer)\n\t\t\t}\n\t\t\tthis.pendingColorTimer = setTimeout(() => this.flushPendingColor(), 120)\n\t\t},\n\n\t\t/**\n\t\t * Translate vue-color's emitted color object into the schema-declared\n\t\t * color format string.\n\t\t *\n\t\t * @param {object} color - vue-color color object.\n\t\t * @return {string}\n\t\t */\n\t\tchromeColorToFormatValue(color) {\n\t\t\tconst fmt = this.schemaProp?.format || 'color-hex'\n\t\t\tconst { rgba, hex, hex8 } = color || {}\n\t\t\tconst r = rgba?.r ?? 0\n\t\t\tconst g = rgba?.g ?? 0\n\t\t\tconst b = rgba?.b ?? 0\n\t\t\tconst a = rgba?.a ?? color?.a ?? 1\n\t\t\tif (fmt === 'color-hex' || fmt === 'color') {\n\t\t\t\treturn (hex || '#000000').toLowerCase()\n\t\t\t}\n\t\t\tif (fmt === 'color-hex-alpha') {\n\t\t\t\tif (hex8) {\n\t\t\t\t\treturn hex8.toLowerCase()\n\t\t\t\t}\n\t\t\t\tconst aHex = Math.round(a * 255).toString(16).padStart(2, '0')\n\t\t\t\treturn ((hex || '#000000') + aHex).toLowerCase()\n\t\t\t}\n\t\t\tif (fmt === 'color-rgb') {\n\t\t\t\treturn `rgb(${r}, ${g}, ${b})`\n\t\t\t}\n\t\t\tif (fmt === 'color-rgba') {\n\t\t\t\treturn `rgba(${r}, ${g}, ${b}, ${this.formatAlpha(a)})`\n\t\t\t}\n\t\t\tif (fmt === 'color-hsl' || fmt === 'color-hsla') {\n\t\t\t\tconst { h, s, l } = this.rgbToHsl(r, g, b)\n\t\t\t\tif (fmt === 'color-hsla') {\n\t\t\t\t\treturn `hsla(${h}, ${s}%, ${l}%, ${this.formatAlpha(a)})`\n\t\t\t\t}\n\t\t\t\treturn `hsl(${h}, ${s}%, ${l}%)`\n\t\t\t}\n\t\t\treturn hex || '#000000'\n\t\t},\n\n\t\t/** Emit the pending color upstream and clear local state. */\n\t\tflushPendingColor() {\n\t\t\tif (this.pendingColorTimer) {\n\t\t\t\tclearTimeout(this.pendingColorTimer)\n\t\t\t\tthis.pendingColorTimer = null\n\t\t\t}\n\t\t\tif (this.pendingColor === null) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst out = this.pendingColor\n\t\t\tthis.pendingColor = null\n\t\t\tthis.$emit('update:value', out)\n\t\t},\n\n\t\t/**\n\t\t * Manual text-field edit for a color value. Cancels any in-flight\n\t\t * picker debounce so the typed value isn't immediately overwritten.\n\t\t *\n\t\t * @param {string} v - The new text value.\n\t\t */\n\t\tonColorTextInput(v) {\n\t\t\tif (this.pendingColorTimer) {\n\t\t\t\tclearTimeout(this.pendingColorTimer)\n\t\t\t\tthis.pendingColorTimer = null\n\t\t\t}\n\t\t\tthis.pendingColor = null\n\t\t\tthis.$emit('update:value', v)\n\t\t},\n\n\t\t/**\n\t\t * Format an alpha 0–1 for CSS output (max 2 decimals, drops trailing zeros).\n\t\t *\n\t\t * @param {number} a - Alpha in 0–1 range.\n\t\t * @return {string}\n\t\t */\n\t\tformatAlpha(a) {\n\t\t\treturn Number.isInteger(a) ? String(a) : a.toFixed(2).replace(/\\.?0+$/, '') || '0'\n\t\t},\n\n\t\trgbToHsl(r, g, b) {\n\t\t\tconst rN = r / 255\n\t\t\tconst gN = g / 255\n\t\t\tconst bN = b / 255\n\t\t\tconst max = Math.max(rN, gN, bN)\n\t\t\tconst min = Math.min(rN, gN, bN)\n\t\t\tconst l = (max + min) / 2\n\t\t\tlet h = 0\n\t\t\tlet s = 0\n\t\t\tif (max !== min) {\n\t\t\t\tconst d = max - min\n\t\t\t\ts = l > 0.5 ? d / (2 - max - min) : d / (max + min)\n\t\t\t\tswitch (max) {\n\t\t\t\t\tcase rN:\n\t\t\t\t\t\th = (gN - bN) / d + (gN < bN ? 6 : 0)\n\t\t\t\t\t\tbreak\n\t\t\t\t\tcase gN:\n\t\t\t\t\t\th = (bN - rN) / d + 2\n\t\t\t\t\t\tbreak\n\t\t\t\t\tcase bN:\n\t\t\t\t\t\th = (rN - gN) / d + 4\n\t\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\th /= 6\n\t\t\t}\n\t\t\treturn {\n\t\t\t\th: Math.round(h * 360),\n\t\t\t\ts: Math.round(s * 100),\n\t\t\t\tl: Math.round(l * 100),\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-advanced-form-dialog__value-input-container {\n\twidth: 100%;\n\tmin-width: 0;\n}\n\n.cn-advanced-form-dialog__value-input-container :deep(.text-field) {\n\tmargin: 0;\n\tpadding: 0;\n}\n\n.cn-advanced-form-dialog__boolean-input-row {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 6px;\n}\n\n.cn-advanced-form-dialog__array-input-row {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 6px;\n}\n\n.cn-advanced-form-dialog__array-input-row > :first-child {\n\tflex: 1;\n}\n\n/* patch extreme size in field */\n.cn-advanced-form-dialog__boolean-input-row__input > span {\n\tpadding-left: 0;\n\tpadding-block: 0;\n}\n\n.cn-advanced-form-dialog__boolean-input-row__input > input {\n\tmargin: 0;\n}\n\n.cn-advanced-form-dialog__info-icon {\n\tflex-shrink: 0;\n\tcolor: var(--color-text-maxcontrast);\n\tcursor: help;\n}\n\n.cn-advanced-form-dialog__json-value {\n\tmax-height: 200px;\n\toverflow-y: auto;\n\twhite-space: pre-wrap;\n\tfont-family: monospace;\n\tfont-size: 12px;\n\tbackground: var(--color-background-dark);\n\tpadding: 8px;\n\tborder-radius: 4px;\n\tmargin: 0;\n}\n\n/* Textarea: keep it inside the table cell instead of overflowing.\n NcTextArea's wrapper has a fixed height (`calc(var(--default-clickable-area) * 2)`)\n that ignores the `rows` attribute, which causes the textarea to render with a\n too-small viewport. Let the wrapper grow with its content instead. */\n.cn-advanced-form-dialog__textarea {\n\twidth: 100%;\n\tbox-sizing: border-box;\n}\n\n.cn-advanced-form-dialog__textarea :deep(.textarea__main-wrapper) {\n\theight: auto;\n\tmin-height: var(--default-clickable-area);\n}\n\n.cn-advanced-form-dialog__textarea :deep(textarea) {\n\twidth: 100%;\n\tmax-width: 100%;\n\tbox-sizing: border-box;\n\tresize: vertical;\n\tmin-height: 80px;\n\tmax-height: 240px;\n\tdisplay: block;\n}\n\n/* Color widget: clickable swatch (native picker) + visible text input */\n.cn-advanced-form-dialog__color-input-row {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\twidth: 100%;\n}\n\n.cn-advanced-form-dialog__color-input-row > :last-child {\n\tflex: 1;\n\tmin-width: 0;\n}\n\n.cn-advanced-form-dialog__color-swatch {\n\t--cn-color-swatch-checker:\n\t\tlinear-gradient(45deg, var(--color-background-dark) 25%, transparent 25%),\n\t\tlinear-gradient(-45deg, var(--color-background-dark) 25%, transparent 25%),\n\t\tlinear-gradient(45deg, transparent 75%, var(--color-background-dark) 75%),\n\t\tlinear-gradient(-45deg, transparent 75%, var(--color-background-dark) 75%);\n\tdisplay: inline-block;\n\twidth: 32px;\n\theight: 32px;\n\tflex-shrink: 0;\n\tpadding: 0;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius);\n\tcursor: pointer;\n\tbackground-image: var(--cn-color-swatch-checker);\n\tbackground-size: 8px 8px;\n\tbackground-position: 0 0, 0 4px, 4px -4px, -4px 0;\n\toverflow: hidden;\n\tposition: relative;\n}\n\n.cn-advanced-form-dialog__color-swatch:focus-visible {\n\toutline: 2px solid var(--color-primary-element);\n\toutline-offset: 2px;\n}\n\n.cn-advanced-form-dialog__color-swatch--readonly {\n\tcursor: default;\n\twidth: 20px;\n\theight: 20px;\n\tvertical-align: middle;\n\tmargin-inline-end: 6px;\n}\n\n.cn-advanced-form-dialog__color-display {\n\tdisplay: inline-flex;\n\talign-items: center;\n}\n\n/* Help text & inline validation under the input */\n.cn-advanced-form-dialog__field-help {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n\tmargin-top: 4px;\n\tfont-size: 0.85em;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-advanced-form-dialog__field-example {\n\tfont-style: italic;\n}\n\n.cn-advanced-form-dialog__field-error {\n\tmargin-top: 4px;\n\tfont-size: 0.85em;\n\tcolor: var(--color-error-text);\n}\n\n.cn-advanced-form-dialog__value-cell-wrapper {\n\tdisplay: flex;\n\tflex-direction: column;\n\twidth: 100%;\n}\n\n/* Object-array widget: chip list + add button */\n.cn-advanced-form-dialog__object-array {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 8px;\n\twidth: 100%;\n}\n\n.cn-advanced-form-dialog__object-array-chips {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tgap: 6px;\n}\n\n.cn-advanced-form-dialog__object-array-chip {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 4px;\n\tpadding: 2px 4px 2px 10px;\n\tbackground: var(--color-background-dark);\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius-pill, 14px);\n\tcolor: var(--color-main-text);\n\tcursor: pointer;\n\tfont: inherit;\n\tmax-width: 240px;\n}\n\n.cn-advanced-form-dialog__object-array-chip:hover {\n\tbackground: var(--color-background-hover);\n}\n\n.cn-advanced-form-dialog__object-array-chip-label {\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-advanced-form-dialog__object-array-chip-remove {\n\tflex-shrink: 0;\n}\n\n.cn-advanced-form-dialog__object-array-add {\n\talign-self: flex-start;\n}\n</style>\n"],"names":["_openBlock","_createElementBlock","_createVNode","_createBlock","_createElementVNode","_Fragment","_renderList","_withModifiers","_toDisplayString","_createTextVNode"],"mappings":";;AACM,MAAA,UAAA,GAAA,EAAA,KAAK,EAAC,6CAA6C,GAAA;;;AAQrD,EAAA,KAAK,EAAC,4CAA4C;;;;AAgBlD,EAAA,KAAK,EAAC,0CAA0C;;;;AA+ChD,EAAA,KAAK,EAAC,uCAAuC;;AACxC,MAAA,UAAA,GAAA,EAAA,KAAK,EAAC,6CAA6C,GAAA;;AAQhD,MAAA,UAAA,GAAA,EAAA,KAAK,EAAC,kDAAkD,GAAA;;;;AAoDhE,EAAA,KAAK,EAAC,qCAAqC;;;;;AAO3C,EAAA,KAAK,EAAC,wCAAwC;;;;;AAY/C,EAAA,KAAK,EAAC,qCAAqC;;;;AAG1C,EAAA,KAAK,EAAC,4CAA4C;;;;AAGlD,EAAA,KAAK,EAAC,wCAAwC;;;;AAM/C,EAAA,KAAK,EAAC,sCAAqC;AAC3C,EAAA,IAAI,EAAC,OAAO;;;;;;;;;;;;;;;;;AApKd,EAAA,QAAAA,SAAA,EAAA,EAAAC,kBAAA,CAuKK,OAvKL,UAuKK,EAAA;KApKG,MAAS,CAAA,UAAA,KAAM,QAAa,CAAA,cAAA,KAAA,SAAA,IAAmB,MAAS,CAAA,SAAA,CAAA;sBAD/DA,kBA0HK,CAAA,KAAA,EAAA;;AAxHJ,UAAA,KAAK,EAAC,gDAA+C;AACpD,UAAA,OAAK,0CAAN,MAAW,EAAA,EAAA,CAAA,MAAA,CAAA,CAAA,CAAA;;WAEJ,QAAa,CAAA,cAAA,KAAA,SAAA;AADpB,eAAAD,SAAA,EAAA,EAAAC,kBAAA,CAeK,OAfL,UAeK,EAAA;gBAZJC,WAMuB,CAAA,gCAAA,EAAA;AALrB,kBAAA,UAAU,IAAI,MAAK,CAAA,KAAA;AACpB,kBAAA,IAAI,EAAC,QAAO;AACZ,kBAAA,KAAK,EAAC,mDAAkD;kBACvD,qBAAiB,EAAA,MAAA,CAAA,CAAA,CAAA,KAAA,MAAA,CAAA,CAAA,CAAA,GAAA,MAAA,KAAE,QAAI,CAAA,IAAA,CAAC,MAAM,CAAA,CAAA,CAAA;;mCAC/B,MAAgB;oDAAb,MAAU,CAAA,WAAA,CAAA,EAAA,CAAA,YAAA;;;;iBAGP,QAAW,CAAA,UAAA,IAAG,QAAU,CAAA,UAAA,CAAC,WAAW;kCAD3CC,WAIa,CAAA,6BAAA,EAAA;;sBAFX,KAAK,EAAE,QAAU,CAAA,UAAA,CAAC,WAAW;AAC9B,sBAAA,KAAK,EAAC,oCAAmC;AACxC,sBAAA,IAAI,EAAE,EAAE;;;;eAGC,QAAe,CAAA,cAAA,KAAA,OAAA;AAD3B,iBAAAH,SAAA,EAAA,EAAAC,kBAAA,CAaK,OAbL,UAaK,EAAA;kBAVJC,WAI8B,CAAA,wBAAA,EAAA;AAH5B,oBAAA,KAAK,EAAE,QAAiB,CAAA,iBAAA;AACxB,oBAAA,YAAY,GAAG,QAAQ,CAAA,QAAA;AACvB,oBAAA,IAAI,EAAE,QAAe,CAAA,eAAA;AACrB,oBAAA,OAAK,EAAE,QAAkB,CAAA,kBAAA;;kBAC3BA,WAIgD,CAAA,sBAAA,EAAA;AAH/C,oBAAA,GAAG,EAAC,UAAS;AACZ,oBAAA,UAAU,EAAE,QAAc,CAAA,cAAA;AAC1B,oBAAA,WAAW,EAAE,QAAgB,CAAA,gBAAA;oBAC7B,qBAAiB,EAAA,MAAA,CAAA,CAAA,CAAA,KAAA,MAAA,CAAA,CAAA,CAAA,GAAA,MAAA,KAAE,QAAgB,CAAA,gBAAA,CAAC,MAAM,CAAA,CAAA,CAAA;;;iBAGjC,QAAe,CAAA,cAAA,KAAA,UAAA;gCAD3BC,WAM4C,CAAA,2BAAA,EAAA;;AAJ1C,oBAAA,UAAU,EAAE,QAAa,CAAA,aAAA;AACzB,oBAAA,IAAI,EAAE,QAAkB,CAAA,kBAAA;AACxB,oBAAA,WAAW,EAAE,MAAW,CAAA,WAAA;AACxB,oBAAA,UAAU,EAAE,MAAW,CAAA,WAAA;oBACvB,qBAAiB,EAAA,MAAA,CAAA,CAAA,CAAA,KAAA,MAAA,CAAA,CAAA,CAAA,GAAA,MAAA,KAAE,QAAY,CAAA,YAAA,CAAC,MAAM,CAAA,CAAA,CAAA;;mBAE5B,QAAe,CAAA,cAAA,KAAA,UAAA;kCAD3BA,WAQoC,CAAA,qBAAA,EAAA;;AANnC,sBAAA,GAAG,EAAC,UAAS;AACZ,sBAAA,UAAU,EAAE,QAAW,CAAA,WAAA;AACvB,sBAAA,WAAW,EAAE,MAAW,CAAA,WAAA;AACxB,sBAAA,IAAI,EAAE,MAAY,CAAA,YAAA;AAClB,sBAAA,SAAS,EAAE,QAAa,CAAA,aAAA;AACzB,sBAAA,KAAK,EAAC,mCAAkC;sBACvC,qBAAiB,EAAA,MAAA,CAAA,CAAA,CAAA,KAAA,MAAA,CAAA,CAAA,CAAA,GAAA,MAAA,KAAE,QAAI,CAAA,IAAA,CAAC,MAAM,CAAA,CAAA,CAAA;;qBAEpB;oCADZA,WAU0C,CAAA,mBAAA,EAAA;;AARxC,wBAAA,UAAU,EAAE,QAAoB,CAAA,oBAAA;AAChC,wBAAA,OAAO,EAAE,QAAsB,CAAA,sBAAA;AAC/B,wBAAA,QAAQ,EAAE,QAAuB,CAAA,uBAAA;AACjC,wBAAA,QAAQ,EAAE,QAAuB,CAAA,uBAAA;AACjC,wBAAA,QAAQ,EAAE,QAAuB,CAAA,uBAAA;AACjC,wBAAA,QAAQ,EAAE,QAAuB,CAAA,uBAAA;AACjC,wBAAA,UAAU,EAAE,MAAW,CAAA,WAAA;AACvB,wBAAA,WAAW,EAAE,MAAW,CAAA,WAAA;wBACxB,qBAAiB,EAAA,MAAA,CAAA,CAAA,CAAA,KAAA,MAAA,CAAA,CAAA,CAAA,GAAA,MAAA,KAAE,QAAU,CAAA,UAAA,CAAC,MAAM,CAAA,CAAA,CAAA;;uBAE1B;sCADZA,WAKqC,CAAA,uBAAA,EAAA;;AAHnC,0BAAA,KAAK,EAAE,QAAgB,CAAA,gBAAA;AACvB,0BAAA,MAAM,EAAE,MAAkB,CAAA,kBAAA;AAC3B,0BAAA,QAAQ,EAAC,MAAK;0BACb,gBAAY,EAAA,MAAA,CAAA,CAAA,CAAA,KAAA,MAAA,CAAA,CAAA,CAAA,GAAA,MAAA,KAAE,QAAU,CAAA,UAAA,CAAC,MAAM,CAAA,CAAA,CAAA;;yBAErB,QAAa,CAAA,cAAA,KAAA,aAAA;AADzB,2BAAAH,SAAA,EAAA,EAAAC,kBAAA,CAyCK,OAzCL,UAyCK,EAAA;AAtCJ,4BAAAG,kBAAA,CAoBK,OApBL,UAoBK,EAAA;AAnBJ,+BAAAJ,SAAA,CAAA,IAAA,CAAA,EAAAC,kBAAA,CAkBQI,QAjBe,EAAA,IAAA,EAAAC,UAAA,CAAA,QAAA,CAAA,gBAAgB,EAA9B,CAAA,IAAI,EAAE,GAAG,KAAA;qDADlBL,kBAkBQ,CAAA,QAAA,EAAA;AAhBN,kCAAA,GAAG,EAAE,GAAG;AACT,kCAAA,IAAI,EAAC,QAAO;AACZ,kCAAA,KAAK,EAAC,4CAA2C;AAChD,kCAAA,KAAK,EAAE,QAAC,CAAA,CAAA,CAAA,eAAA,EAAA,WAAA,CAAA;kCACR,OAAK,EAAAM,aAAA,CAAA,MAAA,KAAO,QAAmB,CAAA,mBAAA,CAAC,GAAG,CAAA,CAAA,EAAA,CAAA,MAAA,CAAA,CAAA;;AACpC,kCAAAH,kBAAA,CAA0G,QAA1G,UAA0G,EAAAI,eAAA,CAAxC,6BAAoB,CAAC,IAAI,EAAE,GAAG,CAAA,CAAA,EAAA,CAAA,YAAA;kCAChGN,WASU,CAAA,mBAAA,EAAA;AART,oCAAA,OAAO,EAAC,wBAAuB;AAC9B,oCAAA,YAAU,EAAE,QAAC,CAAA,CAAA,CAAA,eAAA,EAAA,aAAA,CAAA;AACb,oCAAA,KAAK,EAAE,QAAC,CAAA,CAAA,CAAA,eAAA,EAAA,aAAA,CAAA;AACT,oCAAA,KAAK,EAAC,mDAAkD;oCACvD,OAAK,EAAAK,aAAA,CAAA,MAAA,KAAO,QAAqB,CAAA,qBAAA,CAAC,GAAG,CAAA,CAAA,EAAA,CAAA,MAAA,CAAA,CAAA;;AAC3B,oCAAA,IAAI,UACd,MAAmB;sCAAnBL,WAAmB,CAAA,gBAAA,EAAA,EAAX,IAAI,EAAE,EAAE,EAAA,CAAA;;;;;;;4BAKpBA,WAQU,CAAA,mBAAA,EAAA;AAPT,8BAAA,OAAO,EAAC,WAAU;AAClB,8BAAA,KAAK,EAAC,2CAA0C;AAC/C,8BAAA,OAAK,qDAAO,QAAmB,CAAA,mBAAA,CAAA,IAAA,CAAA,CAAA,EAAA,CAAA,MAAA,CAAA,CAAA,CAAA;;AACrB,8BAAA,IAAI,UACd,MAAkB;gCAAlBA,WAAkB,CAAA,eAAA,EAAA,EAAX,IAAI,EAAE,EAAE,EAAA,CAAA;;+CACN,MACV;AADU,gCAAAO,eAAA,CAAA,GACV,mBAAG,QAAC,CAAA,CAAA,CAAA,eAAA,EAAA,UAAA,CAAA,CAAA,EAAA,CAAA,YAAA;;;;6BAGE,KAAqB,CAAA,qBAAA;8CAD5BN,WAOkC,CAAA,+BAAA,EAAA;;kCALhC,MAAM,EAAE,QAAU,CAAA,UAAA,CAAC,KAAK;AACxB,kCAAA,IAAI,EAAE,KAAqB,CAAA,qBAAA;AAC3B,kCAAA,WAAW,EAAE,QAAsB,CAAA,sBAAA;AACnC,kCAAA,mBAAiB,EAAE,KAAK;AACxB,kCAAA,SAAO,EAAE,QAAoB,CAAA,oBAAA;AAC7B,kCAAA,OAAK,EAAE,QAAsB,CAAA,sBAAA;;;;wCAEhCA,WAY6C,CAAA,sBAAA,EAAA;;AAV5C,4BAAA,GAAG,EAAC,UAAS;AACZ,4BAAA,UAAU,EAAE,QAAW,CAAA,WAAA;AACvB,4BAAA,IAAI,EAAE,QAAS,CAAA,SAAA;AACf,4BAAA,WAAW,EAAE,MAAW,CAAA,WAAA;AACxB,4BAAA,GAAG,EAAE,QAAO,CAAA,OAAA;AACZ,4BAAA,GAAG,EAAE,QAAO,CAAA,OAAA;AACZ,4BAAA,IAAI,EAAE,QAAI,CAAA,IAAA;AACV,4BAAA,OAAO,EAAE,QAAO,CAAA,OAAA;AAChB,4BAAA,SAAS,EAAE,QAAa,CAAA,aAAA;AACxB,4BAAA,SAAS,EAAE,QAAa,CAAA,aAAA;4BACxB,qBAAiB,EAAA,MAAA,CAAA,CAAA,CAAA,KAAA,MAAA,CAAA,CAAA,CAAA,GAAA,MAAA,KAAE,QAAa,CAAA,aAAA,CAAC,MAAM,CAAA,CAAA,CAAA;;;sBAI1CF,kBAmBK,CAAA,KAAA,EAAA;;AAjBH,UAAA,KAAK,EAAE,MAAkB,CAAA,kBAAA;;AAEZ,UAAA,CAAA,OAAA,MAAA,CAAA,KAAM,iBAAgB;4BADpCA,kBAE4E,CAAA,KAAA,EAF5E,UAE4E,EAAAO,eAAA,CAA5B,QAAmB,CAAA,oBAAA,CAAA,EAAA,CAAA,YAAA;eAEvD,QAAe,CAAA,cAAA,KAAA,UAAA,IAAkB,QAAW,CAAA,WAAA,CAAC,MAAK,CAAA,KAAA,CAAA;AAD9D,iBAAAR,SAAA,EAAA,EAAAC,kBAAA,CAGQ,qCADP,QAAiB,CAAA,kBAAA,CAAA,EAAA,CAAA,YAAA;AAGN,gBAAA,CAAA,QAAA,CAAA,cAAe,gBAAe,MAAK,CAAA,KAAA;AAD/C,mBAAAD,SAAA,EAAA,EAAAC,kBAAA,CAOM,QAPN,WAOM,EAAA;oBAJLG,kBAE4B,CAAA,MAAA,EAAA;AAD3B,sBAAA,KAAK,EAAC,uFAAsF;AAC3F,sBAAA,KAAK,iBAAE,QAAgB,CAAA,gBAAA,CAAA;;AACzB,oBAAAA,kBAAA,CAA8B,8BAArB,QAAa,CAAA,YAAA,CAAA,EAAA,CAAA,YAAA;;AAEvB,mBAAAJ,SAAA,EAAA,EAAAC,kBAAA,CAAqC,qCAArB,QAAW,CAAA,YAAA,CAAA,EAAA,CAAA,YAAA,CAAA;;KAKrB,QAAW,CAAA,YAAA,KAAM,QAAc,CAAA,eAAA,IAAK,QAAW,CAAA,WAAA,CAAA;AADtD,SAAAD,SAAA,EAAA,EAAAC,kBAAA,CASK,OATL,WASK,EAAA;WALG,QAAe,CAAA,eAAA;4BADtBA,kBAE+E,CAAA,MAAA,EAF/E,WAE+E,EAAAO,eAAA,CAAxB,QAAgB,CAAA,eAAA,CAAA,EAAA,CAAA,YAAA;;WAEhE,QAAW,CAAA,WAAA;AADlB,eAAAR,SAAA,EAAA,EAAAC,kBAAA,CAEwG,QAFxG,WAEwG,EAAAO,eAAA,CAArD,UAAC,CAA2B,eAAA,EAAA,MAAA,CAAA,CAAA,GAAA,GAAE,mBAAG;;;;AAK9E,IAAA,CAAA,QAAA,CAAA,YAAa,IAAG,QAAU,CAAA,UAAA;sBADjCP,kBAKK,CAAA,KAAA,EALL,WAKK,EAAAO,eAAA,CADD,QAAS,CAAA,UAAA,CAAA,EAAA,CAAA,YAAA;;;;;;;"}
1
+ {"version":3,"file":"CnPropertyValueCell.vue3.js","sources":["../../../../src/components/CnAdvancedFormDialog/CnPropertyValueCell.vue?vue&type=template&id=9ae2b758&lang.js"],"sourcesContent":["<template>\n\t<div class=\"cn-advanced-form-dialog__value-cell-wrapper\">\n\t\t<!-- Edit mode -->\n\t\t<div\n\t\t\tv-if=\"isEditable && (resolvedWidget === 'boolean' || isEditing)\"\n\t\t\tclass=\"cn-advanced-form-dialog__value-input-container\"\n\t\t\t@click.stop>\n\t\t\t<div\n\t\t\t\tv-if=\"resolvedWidget === 'boolean'\"\n\t\t\t\tclass=\"cn-advanced-form-dialog__boolean-input-row\">\n\t\t\t\t<NcCheckboxRadioSwitch\n\t\t\t\t\t:modelValue=\"!!value\"\n\t\t\t\t\ttype=\"switch\"\n\t\t\t\t\tclass=\"cn-advanced-form-dialog__boolean-input-row__input\"\n\t\t\t\t\t@update:modelValue=\"emit($event)\">\n\t\t\t\t\t{{ displayName }}\n\t\t\t\t</NcCheckboxRadioSwitch>\n\t\t\t\t<InformationOutline\n\t\t\t\t\tv-if=\"schemaProp && schemaProp.description\"\n\t\t\t\t\t:title=\"schemaProp.description\"\n\t\t\t\t\tclass=\"cn-advanced-form-dialog__info-icon\"\n\t\t\t\t\t:size=\"16\" />\n\t\t\t</div>\n\t\t\t<div\n\t\t\t\tv-else-if=\"resolvedWidget === 'color'\"\n\t\t\t\tclass=\"cn-advanced-form-dialog__color-input-row\">\n\t\t\t\t<CnColorPicker\n\t\t\t\t\t:value=\"chromePickerValue\"\n\t\t\t\t\t:disableAlpha=\"!hasAlpha\"\n\t\t\t\t\t:mode=\"colorPickerMode\"\n\t\t\t\t\t@input=\"onChromeColorInput\" />\n\t\t\t\t<NcTextField\n\t\t\t\t\tref=\"inputRef\"\n\t\t\t\t\t:modelValue=\"colorTextValue\"\n\t\t\t\t\t:placeholder=\"colorPlaceholder\"\n\t\t\t\t\t@update:modelValue=\"onColorTextInput($event)\" />\n\t\t\t</div>\n\t\t\t<NcDateTimePicker\n\t\t\t\tv-else-if=\"resolvedWidget === 'datetime'\"\n\t\t\t\t:modelValue=\"datetimeValue\"\n\t\t\t\t:type=\"datetimePickerType\"\n\t\t\t\t:placeholder=\"displayName\"\n\t\t\t\t:inputLabel=\"displayName\"\n\t\t\t\t@update:modelValue=\"emitDatetime($event)\" />\n\t\t\t<NcTextArea\n\t\t\t\tv-else-if=\"resolvedWidget === 'textarea'\"\n\t\t\t\tref=\"inputRef\"\n\t\t\t\t:modelValue=\"stringValue\"\n\t\t\t\t:placeholder=\"displayName\"\n\t\t\t\t:rows=\"textareaRows\"\n\t\t\t\t:maxlength=\"maxLengthAttr\"\n\t\t\t\tclass=\"cn-advanced-form-dialog__textarea\"\n\t\t\t\t@update:modelValue=\"emit($event)\" />\n\t\t\t<NcSelect\n\t\t\t\tv-else-if=\"resolvedWidget === 'select'\"\n\t\t\t\t:modelValue=\"effectiveSelectValue\"\n\t\t\t\t:options=\"effectiveSelectOptions\"\n\t\t\t\t:multiple=\"effectiveSelectMultiple\"\n\t\t\t\t:taggable=\"effectiveSelectTaggable\"\n\t\t\t\t:pushTags=\"effectiveSelectTaggable\"\n\t\t\t\t:keepOpen=\"effectiveSelectMultiple\"\n\t\t\t\t:inputLabel=\"displayName\"\n\t\t\t\t:placeholder=\"displayName\"\n\t\t\t\t@update:modelValue=\"emitSelect($event)\" />\n\t\t\t<CnJsonViewer\n\t\t\t\tv-else-if=\"resolvedWidget === 'object'\"\n\t\t\t\t:value=\"objectJsonString\"\n\t\t\t\t:height=\"objectEditorHeight\"\n\t\t\t\tlanguage=\"json\"\n\t\t\t\t@update:value=\"emitObject($event)\" />\n\t\t\t<div\n\t\t\t\tv-else-if=\"resolvedWidget === 'objectArray'\"\n\t\t\t\tclass=\"cn-advanced-form-dialog__object-array\">\n\t\t\t\t<div class=\"cn-advanced-form-dialog__object-array-chips\">\n\t\t\t\t\t<button\n\t\t\t\t\t\tv-for=\"(item, idx) in objectArrayItems\"\n\t\t\t\t\t\t:key=\"idx\"\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tclass=\"cn-advanced-form-dialog__object-array-chip\"\n\t\t\t\t\t\t:title=\"t('nextcloud-vue', 'Edit item')\"\n\t\t\t\t\t\t@click.stop=\"openObjectArrayItem(idx)\">\n\t\t\t\t\t\t<span class=\"cn-advanced-form-dialog__object-array-chip-label\">{{ objectArrayItemLabel(item, idx) }}</span>\n\t\t\t\t\t\t<NcButton\n\t\t\t\t\t\t\tvariant=\"tertiary-no-background\"\n\t\t\t\t\t\t\t:aria-label=\"t('nextcloud-vue', 'Remove item')\"\n\t\t\t\t\t\t\t:title=\"t('nextcloud-vue', 'Remove item')\"\n\t\t\t\t\t\t\tclass=\"cn-advanced-form-dialog__object-array-chip-remove\"\n\t\t\t\t\t\t\t@click.stop=\"removeObjectArrayItem(idx)\">\n\t\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t\t<Close :size=\"14\" />\n\t\t\t\t\t\t\t</template>\n\t\t\t\t\t\t</NcButton>\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\t\t\t\t<NcButton\n\t\t\t\t\tvariant=\"secondary\"\n\t\t\t\t\tclass=\"cn-advanced-form-dialog__object-array-add\"\n\t\t\t\t\t@click.stop=\"openObjectArrayItem(null)\">\n\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t<Plus :size=\"16\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t{{ t('nextcloud-vue', 'Add item') }}\n\t\t\t\t</NcButton>\n\t\t\t\t<CnAdvancedFormDialog\n\t\t\t\t\tv-if=\"objectArrayDialogOpen\"\n\t\t\t\t\t:schema=\"schemaProp.items\"\n\t\t\t\t\t:item=\"objectArrayDialogItem\"\n\t\t\t\t\t:dialogTitle=\"objectArrayDialogTitle\"\n\t\t\t\t\t:show-metadata-tab=\"false\"\n\t\t\t\t\t@confirm=\"onObjectArrayConfirm\"\n\t\t\t\t\t@close=\"closeObjectArrayDialog\" />\n\t\t\t</div>\n\t\t\t<NcTextField\n\t\t\t\tv-else\n\t\t\t\tref=\"inputRef\"\n\t\t\t\t:modelValue=\"stringValue\"\n\t\t\t\t:type=\"inputType\"\n\t\t\t\t:placeholder=\"displayName\"\n\t\t\t\t:min=\"minimum\"\n\t\t\t\t:max=\"maximum\"\n\t\t\t\t:step=\"step\"\n\t\t\t\t:pattern=\"pattern\"\n\t\t\t\t:minlength=\"minLengthAttr\"\n\t\t\t\t:maxlength=\"maxLengthAttr\"\n\t\t\t\t@update:modelValue=\"emitConverted($event)\" />\n\t\t</div>\n\n\t\t<!-- Display mode -->\n\t\t<div\n\t\t\tv-else\n\t\t\t:title=\"editabilityWarning\">\n\t\t\t<pre\n\t\t\t\tv-if=\"typeof value === 'object' && value !== null\"\n\t\t\t\tclass=\"cn-advanced-form-dialog__json-value\">{{ formattedObjectValue }}</pre>\n\t\t\t<span\n\t\t\t\tv-else-if=\"resolvedWidget === 'datetime' && isValidDate(value)\">{{\n\t\t\t\tformattedDateValue\n\t\t\t}}</span>\n\t\t\t<span\n\t\t\t\tv-else-if=\"resolvedWidget === 'color' && value\"\n\t\t\t\tclass=\"cn-advanced-form-dialog__color-display\">\n\t\t\t\t<span\n\t\t\t\t\tclass=\"cn-advanced-form-dialog__color-swatch cn-advanced-form-dialog__color-swatch--readonly\"\n\t\t\t\t\t:style=\"colorSwatchStyle\" />\n\t\t\t\t<span>{{ displayValue }}</span>\n\t\t\t</span>\n\t\t\t<span v-else>{{ displayValue }}</span>\n\t\t</div>\n\n\t\t<!-- Help text: description + example -->\n\t\t<div\n\t\t\tv-if=\"showHelpText && (helpDescription || helpExample)\"\n\t\t\tclass=\"cn-advanced-form-dialog__field-help\">\n\t\t\t<span\n\t\t\t\tv-if=\"helpDescription\"\n\t\t\t\tclass=\"cn-advanced-form-dialog__field-description\">{{ helpDescription }}</span>\n\t\t\t<span\n\t\t\t\tv-if=\"helpExample\"\n\t\t\t\tclass=\"cn-advanced-form-dialog__field-example\">{{ t('nextcloud-vue', 'e.g.') }} {{ helpExample }}</span>\n\t\t</div>\n\n\t\t<!-- Inline validation error -->\n\t\t<div\n\t\t\tv-if=\"showHelpText && fieldError\"\n\t\t\tclass=\"cn-advanced-form-dialog__field-error\"\n\t\t\trole=\"alert\">\n\t\t\t{{ fieldError }}\n\t\t</div>\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport {\n\tNcButton,\n\tNcCheckboxRadioSwitch,\n\tNcDateTimePicker,\n\tNcSelect,\n\tNcTextArea,\n\tNcTextField,\n} from '@nextcloud/vue'\nimport { defineAsyncComponent } from 'vue'\nimport Close from 'vue-material-design-icons/Close.vue'\nimport InformationOutline from 'vue-material-design-icons/InformationOutline.vue'\nimport Plus from 'vue-material-design-icons/Plus.vue'\nimport CnColorPicker from '../CnColorPicker/CnColorPicker.vue'\nimport CnJsonViewer from '../CnJsonViewer/CnJsonViewer.vue'\nimport { formatValue, validateValue } from '../../utils/schema.js'\nimport { userLocale } from '../../utils/userLocale.js'\n\nconst SUPPORTED_WIDGETS = ['text', 'number', 'boolean', 'datetime', 'textarea', 'array', 'select', 'object', 'objectArray', 'color']\n\n/** String formats that map to HTML5 `<input type=\"url\">`. */\nconst URL_FORMATS = new Set([\n\t'url',\n\t'uri',\n\t'uri-reference',\n\t'iri',\n\t'iri-reference',\n\t'uri-template',\n\t'accessUrl',\n\t'shareUrl',\n\t'downloadUrl',\n])\n\n/** All color-related string formats — rendered with the `color` widget (swatch + text input). */\nconst COLOR_FORMATS = new Set([\n\t'color',\n\t'color-hex',\n\t'color-hex-alpha',\n\t'color-rgb',\n\t'color-rgba',\n\t'color-hsl',\n\t'color-hsla',\n])\n\n/** String formats that render as a multi-line textarea instead of a single-line input. */\nconst TEXTAREA_FORMATS = new Set(['html', 'markdown'])\n\nexport default {\n\tname: 'CnPropertyValueCell',\n\n\tcomponents: {\n\t\tNcTextField,\n\t\tNcTextArea,\n\t\tNcCheckboxRadioSwitch,\n\t\tNcSelect,\n\t\tNcButton,\n\t\tNcDateTimePicker,\n\t\tInformationOutline,\n\t\tPlus,\n\t\tClose,\n\t\tCnJsonViewer,\n\t\tCnColorPicker,\n\t\t// Lazy-required to break the circular dep with CnAdvancedFormDialog.\n\t\t// defineAsyncComponent is MANDATORY under Vue 3: a bare function in\n\t\t// `components:` is a functional component, so Vue CALLS it on render\n\t\t// and prints the returned Promise as text instead of mounting the\n\t\t// dialog (the same \"[object Promise]\" bug CnActionsMenu shipped).\n\t\t// `.then(m => m.default || m)` unwraps the dynamic-import module namespace\n\t\t// before Vue receives it — with rollup's `inlineDynamicImports: true`\n\t\t// the namespace is frozen, and downstream code attaching bookkeeping\n\t\t// to a frozen object throws.\n\t\tCnAdvancedFormDialog: defineAsyncComponent(() => import('./CnAdvancedFormDialog.vue').then((m) => m.default || m)),\n\t},\n\n\tprops: {\n\t\t/** The schema property key */\n\t\tpropertyKey: { type: String, required: true },\n\t\t/** Full JSON schema object */\n\t\tschema: { type: Object, default: null },\n\t\t/** Resolved current value (formData[key] ?? objectValue) */\n\t\tvalue: { type: [Boolean, String, Number, Object, Array], default: null },\n\t\t/** Whether this property is editable at all */\n\t\tisEditable: { type: Boolean, default: true },\n\t\t/** Whether this row is currently selected for editing */\n\t\tisEditing: { type: Boolean, default: false },\n\t\t/** Display name for the property (used in labels/placeholders) */\n\t\tdisplayName: { type: String, default: '' },\n\t\t/** Editability warning message shown as title when not editable */\n\t\teditabilityWarning: { type: String, default: null },\n\t\t/**\n\t\t * Override the auto-detected widget for this cell. When null (default),\n\t\t * the widget is derived from the schema (boolean/datetime/text/number),\n\t\t * with auto-detection for `format: 'text'` (textarea) and `type: 'array'`.\n\t\t * Accepted: 'text', 'number', 'boolean', 'datetime', 'textarea', 'array', 'select'.\n\t\t */\n\t\twidget: {\n\t\t\ttype: String,\n\t\t\tdefault: null,\n\t\t\tvalidator: (v) => v === null || SUPPORTED_WIDGETS.includes(v),\n\t\t},\n\n\t\t/** Options for the `select` widget. Each option may be a string, or `{ id, label }`. */\n\t\tselectOptions: { type: Array, default: null },\n\t\t/** Whether the `select` widget allows multiple values. */\n\t\tselectMultiple: { type: Boolean, default: true },\n\t\t/** Number of rows for the `textarea` widget. */\n\t\ttextareaRows: { type: Number, default: 4 },\n\t\t/** CSS height for the `object` widget's CodeMirror editor. */\n\t\tobjectEditorHeight: { type: String, default: '300px' },\n\t},\n\n\temits: ['update:value'],\n\n\tdata() {\n\t\treturn {\n\t\t\t/**\n\t\t\t * Optimistic color value while the user is dragging the native picker.\n\t\t\t * Used for the swatch preview to feel instant; the upstream\n\t\t\t * `update:value` emit is debounced so validation/re-renders don't\n\t\t\t * fire on every drag tick.\n\t\t\t */\n\t\t\tpendingColor: null,\n\t\t\tpendingColorTimer: null,\n\t\t\tcolorPickerOpen: false,\n\t\t\t/** State for the object-array sub-dialog. `null` index means \"add new\". */\n\t\t\tobjectArrayDialogOpen: false,\n\t\t\tobjectArrayDialogIndex: null,\n\t\t\tobjectArrayDialogItem: null,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\tschemaProp() {\n\t\t\treturn this.schema?.properties?.[this.propertyKey] || null\n\t\t},\n\n\t\t/**\n\t\t * Resolved widget after applying explicit override + schema auto-detection.\n\t\t * Arrays and string-with-enum become a `select` widget; the array/enum\n\t\t * shape is inferred from the schema by the `effectiveSelect*` computeds.\n\t\t *\n\t\t * @return {string} one of SUPPORTED_WIDGETS\n\t\t */\n\t\tresolvedWidget() {\n\t\t\tif (this.widget === 'array') {\n\t\t\t\treturn 'select'\n\t\t\t}\n\t\t\tif (this.widget) {\n\t\t\t\treturn this.widget\n\t\t\t}\n\t\t\tconst prop = this.schemaProp\n\t\t\tif (!prop) {\n\t\t\t\treturn 'text'\n\t\t\t}\n\t\t\tif (prop.type === 'boolean') {\n\t\t\t\treturn 'boolean'\n\t\t\t}\n\t\t\tif (prop.type === 'array') {\n\t\t\t\tif (prop.items?.type === 'object') {\n\t\t\t\t\treturn 'objectArray'\n\t\t\t\t}\n\t\t\t\treturn 'select'\n\t\t\t}\n\t\t\tif (prop.type === 'object') {\n\t\t\t\treturn 'object'\n\t\t\t}\n\t\t\tif (prop.type === 'string') {\n\t\t\t\tif (Array.isArray(prop.enum) && prop.enum.length > 0) {\n\t\t\t\t\treturn 'select'\n\t\t\t\t}\n\t\t\t\tconst fmt = prop.format || ''\n\t\t\t\tif (['date', 'time', 'date-time'].includes(fmt)) {\n\t\t\t\t\treturn 'datetime'\n\t\t\t\t}\n\t\t\t\tif (TEXTAREA_FORMATS.has(fmt)) {\n\t\t\t\t\treturn 'textarea'\n\t\t\t\t}\n\t\t\t\tif (COLOR_FORMATS.has(fmt)) {\n\t\t\t\t\treturn 'color'\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (prop.type === 'number' || prop.type === 'integer') {\n\t\t\t\treturn 'number'\n\t\t\t}\n\t\t\treturn 'text'\n\t\t},\n\n\t\tinputType() {\n\t\t\tconst prop = this.schemaProp\n\t\t\tif (!prop) {\n\t\t\t\treturn 'text'\n\t\t\t}\n\t\t\tconst fmt = prop.format || ''\n\t\t\tif (prop.type === 'string') {\n\t\t\t\tif (fmt === 'email' || fmt === 'idn-email') {\n\t\t\t\t\treturn 'email'\n\t\t\t\t}\n\t\t\t\tif (URL_FORMATS.has(fmt)) {\n\t\t\t\t\treturn 'url'\n\t\t\t\t}\n\t\t\t\tif (fmt === 'password') {\n\t\t\t\t\treturn 'password'\n\t\t\t\t}\n\t\t\t\tif (fmt === 'telephone' || fmt === 'phone') {\n\t\t\t\t\treturn 'tel'\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (prop.type === 'number' || prop.type === 'integer') {\n\t\t\t\treturn 'number'\n\t\t\t}\n\t\t\treturn 'text'\n\t\t},\n\n\t\tpattern() {\n\t\t\tconst prop = this.schemaProp\n\t\t\tif (!prop || prop.type !== 'string') {\n\t\t\t\treturn undefined\n\t\t\t}\n\t\t\tif (prop.pattern) {\n\t\t\t\treturn prop.pattern\n\t\t\t}\n\t\t\treturn undefined\n\t\t},\n\n\t\tcolorPlaceholder() {\n\t\t\tconst fmt = this.schemaProp?.format\n\t\t\tswitch (fmt) {\n\t\t\t\tcase 'color-hex': return '#rrggbb'\n\t\t\t\tcase 'color-hex-alpha': return '#rrggbbaa'\n\t\t\t\tcase 'color-rgb': return 'rgb(0, 0, 0)'\n\t\t\t\tcase 'color-rgba': return 'rgba(0, 0, 0, 1)'\n\t\t\t\tcase 'color-hsl': return 'hsl(0, 0%, 0%)'\n\t\t\t\tcase 'color-hsla': return 'hsla(0, 0%, 0%, 1)'\n\t\t\t\tdefault: return this.displayName || '#rrggbb'\n\t\t\t}\n\t\t},\n\n\t\t/** CSS-renderable representation of the current color value (raw value works for all standard formats). */\n\t\tcolorPreviewValue() {\n\t\t\tif (this.pendingColor) {\n\t\t\t\treturn this.pendingColor\n\t\t\t}\n\t\t\tconst v = this.stringValue\n\t\t\tif (!v) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn v\n\t\t},\n\n\t\t/** Text-field value: shows the optimistic pendingColor while the picker is dragging. */\n\t\tcolorTextValue() {\n\t\t\tif (this.pendingColor) {\n\t\t\t\treturn this.pendingColor\n\t\t\t}\n\t\t\treturn this.stringValue\n\t\t},\n\n\t\t/** True when the schema-declared format includes an alpha channel. */\n\t\thasAlpha() {\n\t\t\tconst fmt = this.schemaProp?.format\n\t\t\treturn fmt === 'color-hex-alpha' || fmt === 'color-rgba' || fmt === 'color-hsla'\n\t\t},\n\n\t\t/**\n\t\t * Lock the picker's numeric-field mode to match the schema format so\n\t\t * the user can't edit, say, an `rgba()` value via hex inputs.\n\t\t */\n\t\tcolorPickerMode() {\n\t\t\tconst fmt = this.schemaProp?.format\n\t\t\tif (fmt === 'color-rgb' || fmt === 'color-rgba') {\n\t\t\t\treturn 'rgb'\n\t\t\t}\n\t\t\tif (fmt === 'color-hsl' || fmt === 'color-hsla') {\n\t\t\t\treturn 'hsl'\n\t\t\t}\n\t\t\treturn 'hex'\n\t\t},\n\n\t\t/**\n\t\t * Value passed to vue-color's `Chrome` picker. We feed the picker the\n\t\t * latest displayable value (pending or committed) so dragging the\n\t\t * picker stays smooth even while the upstream emit is debounced.\n\t\t */\n\t\tchromePickerValue() {\n\t\t\tconst v = this.pendingColor || this.stringValue\n\t\t\tif (v) {\n\t\t\t\treturn v\n\t\t\t}\n\t\t\treturn { hex: this.hexColorValue, a: 1 }\n\t\t},\n\n\t\t/**\n\t\t * Inline style for the color swatch: layers a solid color over the checker\n\t\t * background-image so alpha colors render against the checker pattern.\n\t\t */\n\t\tcolorSwatchStyle() {\n\t\t\tconst c = this.colorPreviewValue\n\t\t\tif (!c) {\n\t\t\t\treturn {}\n\t\t\t}\n\t\t\tconst fill = `linear-gradient(${c}, ${c})`\n\t\t\treturn {\n\t\t\t\tbackgroundImage: `${fill}, var(--cn-color-swatch-checker)`,\n\t\t\t\tbackgroundSize: '100% 100%, 8px 8px',\n\t\t\t\tbackgroundPosition: '0 0, 0 0',\n\t\t\t}\n\t\t},\n\n\t\t/** Hex string used as the value of the native `<input type=\"color\">`. */\n\t\thexColorValue() {\n\t\t\tconst v = this.stringValue\n\t\t\tif (!v) {\n\t\t\t\treturn '#000000'\n\t\t\t}\n\t\t\tconst trimmed = v.trim()\n\t\t\tconst hexMatch = trimmed.match(/^#([0-9a-f]{3}|[0-9a-f]{4}|[0-9a-f]{6}|[0-9a-f]{8})$/i)\n\t\t\tif (hexMatch) {\n\t\t\t\tconst h = hexMatch[1]\n\t\t\t\tif (h.length === 3 || h.length === 4) {\n\t\t\t\t\treturn '#' + h.slice(0, 3).split('').map((c) => c + c).join('')\n\t\t\t\t}\n\t\t\t\treturn '#' + h.slice(0, 6)\n\t\t\t}\n\t\t\tconst computed = this.cssColorToHex(trimmed)\n\t\t\treturn computed || '#000000'\n\t\t},\n\n\t\tminimum() {\n\t\t\treturn this.schemaProp?.minimum\n\t\t},\n\n\t\tmaximum() {\n\t\t\treturn this.schemaProp?.maximum\n\t\t},\n\n\t\tstep() {\n\t\t\tconst prop = this.schemaProp\n\t\t\tif (prop?.type === 'integer') {\n\t\t\t\treturn '1'\n\t\t\t}\n\t\t\tif (prop?.type === 'number') {\n\t\t\t\treturn 'any'\n\t\t\t}\n\t\t\treturn undefined\n\t\t},\n\n\t\tminLengthAttr() {\n\t\t\tconst v = this.schemaProp?.minLength\n\t\t\treturn typeof v === 'number' ? v : undefined\n\t\t},\n\n\t\tmaxLengthAttr() {\n\t\t\tconst v = this.schemaProp?.maxLength\n\t\t\treturn typeof v === 'number' ? v : undefined\n\t\t},\n\n\t\tshowHelpText() {\n\t\t\treturn this.isEditable && (this.resolvedWidget === 'boolean' || this.isEditing)\n\t\t},\n\n\t\thelpDescription() {\n\t\t\tconst prop = this.schemaProp\n\t\t\tif (!prop) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn prop.userDescription || prop.description || ''\n\t\t},\n\n\t\thelpExample() {\n\t\t\tconst ex = this.schemaProp?.example\n\t\t\tif (ex === undefined || ex === null || ex === '') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tif (typeof ex === 'object') {\n\t\t\t\ttry {\n\t\t\t\t\treturn JSON.stringify(ex)\n\t\t\t\t} catch {\n\t\t\t\t\treturn ''\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn String(ex)\n\t\t},\n\n\t\t/**\n\t\t * Inline validation message for the current value, or null when valid.\n\t\t * Required-ness is owned by the parent form so it isn't surfaced here.\n\t\t */\n\t\tfieldError() {\n\t\t\tconst raw = validateValue(this.value, this.schemaProp || {})\n\t\t\treturn raw ? t('nextcloud-vue', raw) : null\n\t\t},\n\n\t\t/** Resolved option list for the `select` widget (explicit prop, schema enum, or items.enum). */\n\t\teffectiveSelectOptions() {\n\t\t\tif (this.selectOptions) {\n\t\t\t\treturn this.selectOptions\n\t\t\t}\n\t\t\tconst prop = this.schemaProp\n\t\t\tif (!prop) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\tif (Array.isArray(prop.enum) && prop.enum.length > 0) {\n\t\t\t\treturn prop.enum\n\t\t\t}\n\t\t\tif (prop.type === 'array' && Array.isArray(prop.items?.enum) && prop.items.enum.length > 0) {\n\t\t\t\treturn prop.items.enum\n\t\t\t}\n\t\t\treturn []\n\t\t},\n\n\t\t/** Whether the `select` widget allows multiple values. */\n\t\teffectiveSelectMultiple() {\n\t\t\tif (this.widget === 'select') {\n\t\t\t\treturn this.selectMultiple\n\t\t\t}\n\t\t\tif (this.widget === 'array') {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\tconst prop = this.schemaProp\n\t\t\tif (prop?.type === 'array') {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\treturn false\n\t\t},\n\n\t\t/** Whether the `select` widget accepts free-form tags (no fixed enum). */\n\t\teffectiveSelectTaggable() {\n\t\t\tif (this.widget === 'select') {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tconst prop = this.schemaProp\n\t\t\tconst isArray = this.widget === 'array' || prop?.type === 'array'\n\t\t\treturn isArray && this.effectiveSelectOptions.length === 0\n\t\t},\n\n\t\t/** Selected-value object(s) for NcSelect, mapping ids back to option objects when needed. */\n\t\teffectiveSelectValue() {\n\t\t\tconst v = this.value\n\t\t\tconst opts = this.effectiveSelectOptions\n\t\t\tconst lookup = (id) => {\n\t\t\t\tconst match = opts.find((o) => (typeof o === 'object' ? o.id : o) === id)\n\t\t\t\treturn match !== undefined ? match : id\n\t\t\t}\n\t\t\tif (this.effectiveSelectMultiple) {\n\t\t\t\tif (!Array.isArray(v)) {\n\t\t\t\t\treturn []\n\t\t\t\t}\n\t\t\t\treturn v.map(lookup)\n\t\t\t}\n\t\t\tif (v === null || v === undefined || v === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn lookup(v)\n\t\t},\n\n\t\t/** Items array for the `objectArray` widget. Always an array. */\n\t\tobjectArrayItems() {\n\t\t\treturn Array.isArray(this.value) ? this.value : []\n\t\t},\n\n\t\t/** Title shown in the sub-dialog when adding/editing an object item. */\n\t\tobjectArrayDialogTitle() {\n\t\t\tconst itemTitle = this.schemaProp?.items?.title || this.displayName || t('nextcloud-vue', 'Item')\n\t\t\treturn this.objectArrayDialogIndex === null\n\t\t\t\t? t('nextcloud-vue', 'Add {title}', { title: itemTitle })\n\t\t\t\t: t('nextcloud-vue', 'Edit {title}', { title: itemTitle })\n\t\t},\n\n\t\t/**\n\t\t * NcDateTimePicker `type` mapped from the schema's string `format`.\n\t\t * Falls back to `datetime` for unknown date-ish formats.\n\t\t */\n\t\tdatetimePickerType() {\n\t\t\tconst fmt = this.schemaProp?.format\n\t\t\tif (fmt === 'date') {\n\t\t\t\treturn 'date'\n\t\t\t}\n\t\t\tif (fmt === 'time') {\n\t\t\t\treturn 'time'\n\t\t\t}\n\t\t\treturn 'datetime'\n\t\t},\n\n\t\t/** Current value as a `Date` instance for NcDateTimePicker, or null. */\n\t\tdatetimeValue() {\n\t\t\tconst v = this.value\n\t\t\tif (!v) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\t// Date-only strings (YYYY-MM-DD) are parsed as UTC midnight by the spec,\n\t\t\t// which shifts to the previous day in positive-UTC-offset timezones when\n\t\t\t// fed to a picker that renders in local time. Parse them as local midnight.\n\t\t\tif (this.schemaProp?.format === 'date'\n\t\t\t\t&& typeof v === 'string'\n\t\t\t\t&& /^\\d{4}-\\d{2}-\\d{2}$/.test(v)) {\n\t\t\t\tconst [year, month, day] = v.split('-').map(Number)\n\t\t\t\treturn new Date(year, month - 1, day)\n\t\t\t}\n\t\t\tconst d = new Date(v)\n\t\t\treturn Number.isNaN(d.getTime()) ? null : d\n\t\t},\n\n\t\tstringValue() {\n\t\t\tconst v = this.value\n\t\t\tif (v === null || v === undefined) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tif (typeof v === 'string') {\n\t\t\t\treturn v\n\t\t\t}\n\t\t\tif (typeof v === 'object') {\n\t\t\t\treturn JSON.stringify(v)\n\t\t\t}\n\t\t\treturn String(v)\n\t\t},\n\n\t\tobjectJsonString() {\n\t\t\tconst v = this.value\n\t\t\tif (v === null || v === undefined) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tif (typeof v === 'string') {\n\t\t\t\treturn v\n\t\t\t}\n\t\t\ttry {\n\t\t\t\treturn JSON.stringify(v, null, 2)\n\t\t\t} catch {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t},\n\n\t\tformattedObjectValue() {\n\t\t\ttry {\n\t\t\t\treturn JSON.stringify(this.value, null, 2)\n\t\t\t} catch {\n\t\t\t\treturn formatValue(this.value, this.schemaProp || {})\n\t\t\t}\n\t\t},\n\n\t\tdisplayValue() {\n\t\t\tconst prop = this.schemaProp\n\t\t\tif (prop?.const !== undefined) {\n\t\t\t\treturn prop.const\n\t\t\t}\n\t\t\tconst v = this.value\n\t\t\tif (v === null || v === undefined || v === '') {\n\t\t\t\treturn '—'\n\t\t\t}\n\t\t\treturn formatValue(v, prop || {})\n\t\t},\n\n\t\tformattedDateValue() {\n\t\t\tconst v = this.value\n\t\t\tif (!v) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst fmt = this.schemaProp?.format\n\t\t\t// Same local-midnight parse as datetimeValue to avoid UTC-shift in display.\n\t\t\tif (fmt === 'date' && typeof v === 'string' && /^\\d{4}-\\d{2}-\\d{2}$/.test(v)) {\n\t\t\t\tconst [year, month, day] = v.split('-').map(Number)\n\t\t\t\treturn new Date(year, month - 1, day).toLocaleDateString(userLocale())\n\t\t\t}\n\t\t\tconst d = new Date(v)\n\t\t\tif (Number.isNaN(d.getTime())) {\n\t\t\t\treturn String(v)\n\t\t\t}\n\t\t\tif (fmt === 'date') {\n\t\t\t\treturn d.toLocaleDateString(userLocale())\n\t\t\t}\n\t\t\tif (fmt === 'time') {\n\t\t\t\treturn d.toLocaleTimeString(userLocale())\n\t\t\t}\n\t\t\treturn d.toLocaleString(userLocale())\n\t\t},\n\t},\n\n\tbeforeUnmount() {\n\t\tif (this.pendingColorTimer) {\n\t\t\tclearTimeout(this.pendingColorTimer)\n\t\t\tthis.pendingColorTimer = null\n\t\t}\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/** Focus the underlying text input (called by parent after row click) */\n\t\tfocus() {\n\t\t\tconst ref = this.$refs.inputRef\n\t\t\tif (!ref) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst el = ref.$el || ref\n\t\t\tconst input = el?.querySelector?.('input,textarea')\n\t\t\tif (input) {\n\t\t\t\tinput.focus()\n\t\t\t\tif (typeof input.select === 'function') {\n\t\t\t\t\tinput.select()\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\temit(newVal) {\n\t\t\tthis.$emit('update:value', newVal)\n\t\t},\n\n\t\temitConverted(newVal) {\n\t\t\tconst prop = this.schemaProp\n\t\t\tlet converted = newVal\n\t\t\tif (prop) {\n\t\t\t\tswitch (prop.type) {\n\t\t\t\t\tcase 'number':\n\t\t\t\t\t\tconverted = newVal === '' ? null : parseFloat(newVal)\n\t\t\t\t\t\tif (Number.isNaN(converted)) {\n\t\t\t\t\t\t\tconverted = null\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak\n\t\t\t\t\tcase 'integer':\n\t\t\t\t\t\tconverted = newVal === '' ? null : parseInt(newVal, 10)\n\t\t\t\t\t\tif (Number.isNaN(converted)) {\n\t\t\t\t\t\t\tconverted = null\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak\n\t\t\t\t\tcase 'boolean':\n\t\t\t\t\t\tconverted = Boolean(newVal)\n\t\t\t\t\t\tbreak\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tconverted = newVal\n\t\t\t\t}\n\t\t\t}\n\t\t\tthis.$emit('update:value', converted)\n\t\t},\n\n\t\t/**\n\t\t * Emit a `Date` from NcDateTimePicker as the schema-appropriate string:\n\t\t * `date` → `YYYY-MM-DD`, `time` → `HH:MM:SS`, `date-time` → ISO 8601.\n\t\t *\n\t\t * @param {Date|null} date - Date emitted by the picker.\n\t\t */\n\t\temitDatetime(date) {\n\t\t\tif (!date) {\n\t\t\t\tthis.$emit('update:value', null)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst fmt = this.schemaProp?.format\n\t\t\tif (fmt === 'date') {\n\t\t\t\t// Use local-time accessors — toISOString() converts to UTC first,\n\t\t\t\t// which shifts midnight local time to the previous day in UTC+n zones.\n\t\t\t\tconst year = date.getFullYear()\n\t\t\t\tconst month = String(date.getMonth() + 1).padStart(2, '0')\n\t\t\t\tconst day = String(date.getDate()).padStart(2, '0')\n\t\t\t\tthis.$emit('update:value', `${year}-${month}-${day}`)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (fmt === 'time') {\n\t\t\t\tthis.$emit('update:value', date.toTimeString().slice(0, 8))\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.$emit('update:value', date.toISOString())\n\t\t},\n\n\t\temitObject(jsonString) {\n\t\t\tif (typeof jsonString !== 'string') {\n\t\t\t\tthis.$emit('update:value', jsonString)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst trimmed = jsonString.trim()\n\t\t\tif (trimmed === '') {\n\t\t\t\tthis.$emit('update:value', null)\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tthis.$emit('update:value', JSON.parse(trimmed))\n\t\t\t} catch {\n\t\t\t\t// Invalid JSON: keep the raw string so the user sees what they typed.\n\t\t\t\t// CnJsonViewer surfaces a parse error inline; the parent's save path\n\t\t\t\t// can refuse non-object values if it requires structured data.\n\t\t\t\tthis.$emit('update:value', jsonString)\n\t\t\t}\n\t\t},\n\n\t\temitSelect(selected) {\n\t\t\tconst toId = (item) => (item && typeof item === 'object' ? item.id : item)\n\t\t\tif (this.effectiveSelectMultiple) {\n\t\t\t\tconst arr = Array.isArray(selected) ? selected : []\n\t\t\t\tconst itemType = this.schemaProp?.items?.type\n\t\t\t\t// Coerce taggable input (always emitted as strings) to the\n\t\t\t\t// declared `items.type`. Drop entries that fail to coerce.\n\t\t\t\tconst coerced = arr\n\t\t\t\t\t.map(toId)\n\t\t\t\t\t.map((v) => this.coerceItem(v, itemType))\n\t\t\t\t\t.filter((v) => v !== undefined)\n\t\t\t\tthis.$emit('update:value', coerced)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.$emit('update:value', selected === null || selected === undefined ? null : toId(selected))\n\t\t},\n\n\t\t/**\n\t\t * Coerce a raw select-emitted value (typically a string from a\n\t\t * taggable NcSelect) into the array's declared `items.type`. Returns\n\t\t * `undefined` for entries that can't be coerced so the caller can drop\n\t\t * them from the array.\n\t\t *\n\t\t * @param {unknown} v - The raw value.\n\t\t * @param {string} [itemType] - Schema `items.type` (string, number, integer, boolean).\n\t\t * @return {unknown}\n\t\t */\n\t\tcoerceItem(v, itemType) {\n\t\t\tif (v === null || v === undefined) {\n\t\t\t\treturn v\n\t\t\t}\n\t\t\t// No declared item type — pass through untouched (preserves the\n\t\t\t// shape consumers may have set up via `selectOptions` etc).\n\t\t\tif (!itemType) {\n\t\t\t\treturn v\n\t\t\t}\n\t\t\t// Already the right shape — pass through.\n\t\t\tif (itemType === 'number' && typeof v === 'number') {\n\t\t\t\treturn v\n\t\t\t}\n\t\t\tif (itemType === 'integer' && typeof v === 'number' && Number.isInteger(v)) {\n\t\t\t\treturn v\n\t\t\t}\n\t\t\tif (itemType === 'boolean' && typeof v === 'boolean') {\n\t\t\t\treturn v\n\t\t\t}\n\t\t\tif (itemType === 'string' && typeof v === 'string') {\n\t\t\t\treturn v\n\t\t\t}\n\t\t\tconst s = String(v).trim()\n\t\t\tif (s === '') {\n\t\t\t\treturn undefined\n\t\t\t}\n\t\t\tif (itemType === 'number') {\n\t\t\t\tconst n = Number(s)\n\t\t\t\treturn Number.isFinite(n) ? n : undefined\n\t\t\t}\n\t\t\tif (itemType === 'integer') {\n\t\t\t\tconst n = Number(s)\n\t\t\t\treturn Number.isFinite(n) && Number.isInteger(n) ? n : undefined\n\t\t\t}\n\t\t\tif (itemType === 'boolean') {\n\t\t\t\tif (/^(true|1|yes|on)$/i.test(s)) {\n\t\t\t\t\treturn true\n\t\t\t\t}\n\t\t\t\tif (/^(false|0|no|off)$/i.test(s)) {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t\treturn undefined\n\t\t\t}\n\t\t\treturn s\n\t\t},\n\n\t\t/**\n\t\t * Open the sub-dialog to add a new object item or edit an existing\n\t\t * one. `idx === null` means add.\n\t\t *\n\t\t * @param {number|null} idx - Index of the item to edit, or `null`.\n\t\t */\n\t\topenObjectArrayItem(idx) {\n\t\t\tthis.objectArrayDialogIndex = idx\n\t\t\tthis.objectArrayDialogItem = idx === null\n\t\t\t\t? null\n\t\t\t\t: JSON.parse(JSON.stringify(this.objectArrayItems[idx] || {}))\n\t\t\tthis.objectArrayDialogOpen = true\n\t\t},\n\n\t\tcloseObjectArrayDialog() {\n\t\t\tthis.objectArrayDialogOpen = false\n\t\t\tthis.objectArrayDialogIndex = null\n\t\t\tthis.objectArrayDialogItem = null\n\t\t},\n\n\t\t/**\n\t\t * Confirmed object from the sub-dialog. Replace the existing item or\n\t\t * append a new one, then emit the updated array.\n\t\t *\n\t\t * @param {object} formData - Form data emitted by CnAdvancedFormDialog.\n\t\t */\n\t\tonObjectArrayConfirm(formData) {\n\t\t\tconst next = [...this.objectArrayItems]\n\t\t\tif (this.objectArrayDialogIndex === null) {\n\t\t\t\tnext.push(formData)\n\t\t\t} else {\n\t\t\t\tnext.splice(this.objectArrayDialogIndex, 1, formData)\n\t\t\t}\n\t\t\tthis.$emit('update:value', next)\n\t\t\tthis.closeObjectArrayDialog()\n\t\t},\n\n\t\t/**\n\t\t * Remove an item from the object array.\n\t\t *\n\t\t * @param {number} idx - Index of the item to remove.\n\t\t */\n\t\tremoveObjectArrayItem(idx) {\n\t\t\tconst next = [...this.objectArrayItems]\n\t\t\tnext.splice(idx, 1)\n\t\t\tthis.$emit('update:value', next)\n\t\t},\n\n\t\t/**\n\t\t * Pick a human-readable label for an item chip. Tries the schema-\n\t\t * declared name field first, then the first non-empty primitive\n\t\t * property, then falls back to \"Item N\".\n\t\t *\n\t\t * @param {object} item - The array item.\n\t\t * @param {number} idx - Index of the item (used for fallback label).\n\t\t * @return {string}\n\t\t */\n\t\tobjectArrayItemLabel(item, idx) {\n\t\t\tconst items = this.schemaProp?.items\n\t\t\tconst nameField = items?.objectConfiguration?.objectNameField\n\t\t\t\t|| items?.configuration?.objectNameField\n\t\t\tif (nameField && item && item[nameField] !== null && item[nameField] !== undefined && item[nameField] !== '') {\n\t\t\t\treturn String(item[nameField])\n\t\t\t}\n\t\t\tif (item && typeof item === 'object') {\n\t\t\t\tfor (const v of Object.values(item)) {\n\t\t\t\t\tif (v === null || v === undefined || v === '') {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tif (typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean') {\n\t\t\t\t\t\treturn String(v)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', 'Item {n}', { n: idx + 1 })\n\t\t},\n\n\t\tisValidDate(v) {\n\t\t\tif (!v) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tconst d = new Date(v)\n\t\t\treturn d instanceof Date && !Number.isNaN(d.getTime())\n\t\t},\n\n\t\t/**\n\t\t * Convert any CSS-recognized color string to a 6-digit hex string by\n\t\t * round-tripping through a detached DOM node. Returns null when the\n\t\t * browser cannot parse the input.\n\t\t *\n\t\t * @param {string} cssValue - The CSS color value to convert.\n\t\t * @return {string|null}\n\t\t */\n\t\tcssColorToHex(cssValue) {\n\t\t\ttry {\n\t\t\t\tconst el = document.createElement('div')\n\t\t\t\tel.style.color = ''\n\t\t\t\tel.style.color = cssValue\n\t\t\t\tif (!el.style.color) {\n\t\t\t\t\treturn null\n\t\t\t\t}\n\t\t\t\tdocument.body.appendChild(el)\n\t\t\t\tconst rgb = getComputedStyle(el).color\n\t\t\t\tdocument.body.removeChild(el)\n\t\t\t\tconst m = rgb.match(/rgba?\\((\\d+),\\s*(\\d+),\\s*(\\d+)/)\n\t\t\t\tif (!m) {\n\t\t\t\t\treturn null\n\t\t\t\t}\n\t\t\t\tconst toHex = (n) => parseInt(n, 10).toString(16).padStart(2, '0')\n\t\t\t\treturn `#${toHex(m[1])}${toHex(m[2])}${toHex(m[3])}`\n\t\t\t} catch {\n\t\t\t\treturn null\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Continuous handler for vue-color's `Chrome` picker. Updates the\n\t\t * swatch + text input synchronously via `pendingColor`, but debounces\n\t\t * the upstream `update:value` emit so validation and parent re-renders\n\t\t * don't fire on every drag tick.\n\t\t *\n\t\t * @param {object} color - vue-color emitted color object.\n\t\t * @param {string} color.hex - `#rrggbb`.\n\t\t * @param {string} [color.hex8] - `#rrggbbaa`, when alpha is enabled.\n\t\t * @param {object} color.rgba - RGBA components (`r`, `g`, `b`, `a`).\n\t\t * @param {object} color.hsl - HSL components (`h`, `s`, `l`, `a`).\n\t\t * @param {number} [color.a] - Alpha 0–1.\n\t\t */\n\t\tonChromeColorInput(color) {\n\t\t\tthis.pendingColor = this.chromeColorToFormatValue(color)\n\t\t\tif (this.pendingColorTimer) {\n\t\t\t\tclearTimeout(this.pendingColorTimer)\n\t\t\t}\n\t\t\tthis.pendingColorTimer = setTimeout(() => this.flushPendingColor(), 120)\n\t\t},\n\n\t\t/**\n\t\t * Translate vue-color's emitted color object into the schema-declared\n\t\t * color format string.\n\t\t *\n\t\t * @param {object} color - vue-color color object.\n\t\t * @return {string}\n\t\t */\n\t\tchromeColorToFormatValue(color) {\n\t\t\tconst fmt = this.schemaProp?.format || 'color-hex'\n\t\t\tconst { rgba, hex, hex8 } = color || {}\n\t\t\tconst r = rgba?.r ?? 0\n\t\t\tconst g = rgba?.g ?? 0\n\t\t\tconst b = rgba?.b ?? 0\n\t\t\tconst a = rgba?.a ?? color?.a ?? 1\n\t\t\tif (fmt === 'color-hex' || fmt === 'color') {\n\t\t\t\treturn (hex || '#000000').toLowerCase()\n\t\t\t}\n\t\t\tif (fmt === 'color-hex-alpha') {\n\t\t\t\tif (hex8) {\n\t\t\t\t\treturn hex8.toLowerCase()\n\t\t\t\t}\n\t\t\t\tconst aHex = Math.round(a * 255).toString(16).padStart(2, '0')\n\t\t\t\treturn ((hex || '#000000') + aHex).toLowerCase()\n\t\t\t}\n\t\t\tif (fmt === 'color-rgb') {\n\t\t\t\treturn `rgb(${r}, ${g}, ${b})`\n\t\t\t}\n\t\t\tif (fmt === 'color-rgba') {\n\t\t\t\treturn `rgba(${r}, ${g}, ${b}, ${this.formatAlpha(a)})`\n\t\t\t}\n\t\t\tif (fmt === 'color-hsl' || fmt === 'color-hsla') {\n\t\t\t\tconst { h, s, l } = this.rgbToHsl(r, g, b)\n\t\t\t\tif (fmt === 'color-hsla') {\n\t\t\t\t\treturn `hsla(${h}, ${s}%, ${l}%, ${this.formatAlpha(a)})`\n\t\t\t\t}\n\t\t\t\treturn `hsl(${h}, ${s}%, ${l}%)`\n\t\t\t}\n\t\t\treturn hex || '#000000'\n\t\t},\n\n\t\t/** Emit the pending color upstream and clear local state. */\n\t\tflushPendingColor() {\n\t\t\tif (this.pendingColorTimer) {\n\t\t\t\tclearTimeout(this.pendingColorTimer)\n\t\t\t\tthis.pendingColorTimer = null\n\t\t\t}\n\t\t\tif (this.pendingColor === null) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst out = this.pendingColor\n\t\t\tthis.pendingColor = null\n\t\t\tthis.$emit('update:value', out)\n\t\t},\n\n\t\t/**\n\t\t * Manual text-field edit for a color value. Cancels any in-flight\n\t\t * picker debounce so the typed value isn't immediately overwritten.\n\t\t *\n\t\t * @param {string} v - The new text value.\n\t\t */\n\t\tonColorTextInput(v) {\n\t\t\tif (this.pendingColorTimer) {\n\t\t\t\tclearTimeout(this.pendingColorTimer)\n\t\t\t\tthis.pendingColorTimer = null\n\t\t\t}\n\t\t\tthis.pendingColor = null\n\t\t\tthis.$emit('update:value', v)\n\t\t},\n\n\t\t/**\n\t\t * Format an alpha 0–1 for CSS output (max 2 decimals, drops trailing zeros).\n\t\t *\n\t\t * @param {number} a - Alpha in 0–1 range.\n\t\t * @return {string}\n\t\t */\n\t\tformatAlpha(a) {\n\t\t\treturn Number.isInteger(a) ? String(a) : a.toFixed(2).replace(/\\.?0+$/, '') || '0'\n\t\t},\n\n\t\trgbToHsl(r, g, b) {\n\t\t\tconst rN = r / 255\n\t\t\tconst gN = g / 255\n\t\t\tconst bN = b / 255\n\t\t\tconst max = Math.max(rN, gN, bN)\n\t\t\tconst min = Math.min(rN, gN, bN)\n\t\t\tconst l = (max + min) / 2\n\t\t\tlet h = 0\n\t\t\tlet s = 0\n\t\t\tif (max !== min) {\n\t\t\t\tconst d = max - min\n\t\t\t\ts = l > 0.5 ? d / (2 - max - min) : d / (max + min)\n\t\t\t\tswitch (max) {\n\t\t\t\t\tcase rN:\n\t\t\t\t\t\th = (gN - bN) / d + (gN < bN ? 6 : 0)\n\t\t\t\t\t\tbreak\n\t\t\t\t\tcase gN:\n\t\t\t\t\t\th = (bN - rN) / d + 2\n\t\t\t\t\t\tbreak\n\t\t\t\t\tcase bN:\n\t\t\t\t\t\th = (rN - gN) / d + 4\n\t\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\th /= 6\n\t\t\t}\n\t\t\treturn {\n\t\t\t\th: Math.round(h * 360),\n\t\t\t\ts: Math.round(s * 100),\n\t\t\t\tl: Math.round(l * 100),\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-advanced-form-dialog__value-input-container {\n\twidth: 100%;\n\tmin-width: 0;\n}\n\n.cn-advanced-form-dialog__value-input-container :deep(.text-field) {\n\tmargin: 0;\n\tpadding: 0;\n}\n\n.cn-advanced-form-dialog__boolean-input-row {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 6px;\n}\n\n.cn-advanced-form-dialog__array-input-row {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 6px;\n}\n\n.cn-advanced-form-dialog__array-input-row > :first-child {\n\tflex: 1;\n}\n\n/* patch extreme size in field */\n.cn-advanced-form-dialog__boolean-input-row__input > span {\n\tpadding-left: 0;\n\tpadding-block: 0;\n}\n\n.cn-advanced-form-dialog__boolean-input-row__input > input {\n\tmargin: 0;\n}\n\n.cn-advanced-form-dialog__info-icon {\n\tflex-shrink: 0;\n\tcolor: var(--color-text-maxcontrast);\n\tcursor: help;\n}\n\n.cn-advanced-form-dialog__json-value {\n\tmax-height: 200px;\n\toverflow-y: auto;\n\twhite-space: pre-wrap;\n\tfont-family: monospace;\n\tfont-size: 12px;\n\tbackground: var(--color-background-dark);\n\tpadding: 8px;\n\tborder-radius: 4px;\n\tmargin: 0;\n}\n\n/* Textarea: keep it inside the table cell instead of overflowing.\n NcTextArea's wrapper has a fixed height (`calc(var(--default-clickable-area) * 2)`)\n that ignores the `rows` attribute, which causes the textarea to render with a\n too-small viewport. Let the wrapper grow with its content instead. */\n.cn-advanced-form-dialog__textarea {\n\twidth: 100%;\n\tbox-sizing: border-box;\n}\n\n.cn-advanced-form-dialog__textarea :deep(.textarea__main-wrapper) {\n\theight: auto;\n\tmin-height: var(--default-clickable-area);\n}\n\n.cn-advanced-form-dialog__textarea :deep(textarea) {\n\twidth: 100%;\n\tmax-width: 100%;\n\tbox-sizing: border-box;\n\tresize: vertical;\n\tmin-height: 80px;\n\tmax-height: 240px;\n\tdisplay: block;\n}\n\n/* Color widget: clickable swatch (native picker) + visible text input */\n.cn-advanced-form-dialog__color-input-row {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\twidth: 100%;\n}\n\n.cn-advanced-form-dialog__color-input-row > :last-child {\n\tflex: 1;\n\tmin-width: 0;\n}\n\n.cn-advanced-form-dialog__color-swatch {\n\t--cn-color-swatch-checker:\n\t\tlinear-gradient(45deg, var(--color-background-dark) 25%, transparent 25%),\n\t\tlinear-gradient(-45deg, var(--color-background-dark) 25%, transparent 25%),\n\t\tlinear-gradient(45deg, transparent 75%, var(--color-background-dark) 75%),\n\t\tlinear-gradient(-45deg, transparent 75%, var(--color-background-dark) 75%);\n\tdisplay: inline-block;\n\twidth: 32px;\n\theight: 32px;\n\tflex-shrink: 0;\n\tpadding: 0;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius);\n\tcursor: pointer;\n\tbackground-image: var(--cn-color-swatch-checker);\n\tbackground-size: 8px 8px;\n\tbackground-position: 0 0, 0 4px, 4px -4px, -4px 0;\n\toverflow: hidden;\n\tposition: relative;\n}\n\n.cn-advanced-form-dialog__color-swatch:focus-visible {\n\toutline: 2px solid var(--color-primary-element);\n\toutline-offset: 2px;\n}\n\n.cn-advanced-form-dialog__color-swatch--readonly {\n\tcursor: default;\n\twidth: 20px;\n\theight: 20px;\n\tvertical-align: middle;\n\tmargin-inline-end: 6px;\n}\n\n.cn-advanced-form-dialog__color-display {\n\tdisplay: inline-flex;\n\talign-items: center;\n}\n\n/* Help text & inline validation under the input */\n.cn-advanced-form-dialog__field-help {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n\tmargin-top: 4px;\n\tfont-size: 0.85em;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-advanced-form-dialog__field-example {\n\tfont-style: italic;\n}\n\n.cn-advanced-form-dialog__field-error {\n\tmargin-top: 4px;\n\tfont-size: 0.85em;\n\tcolor: var(--color-error-text);\n}\n\n.cn-advanced-form-dialog__value-cell-wrapper {\n\tdisplay: flex;\n\tflex-direction: column;\n\twidth: 100%;\n}\n\n/* Object-array widget: chip list + add button */\n.cn-advanced-form-dialog__object-array {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 8px;\n\twidth: 100%;\n}\n\n.cn-advanced-form-dialog__object-array-chips {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tgap: 6px;\n}\n\n.cn-advanced-form-dialog__object-array-chip {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 4px;\n\tpadding: 2px 4px 2px 10px;\n\tbackground: var(--color-background-dark);\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius-pill, 14px);\n\tcolor: var(--color-main-text);\n\tcursor: pointer;\n\tfont: inherit;\n\tmax-width: 240px;\n}\n\n.cn-advanced-form-dialog__object-array-chip:hover {\n\tbackground: var(--color-background-hover);\n}\n\n.cn-advanced-form-dialog__object-array-chip-label {\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-advanced-form-dialog__object-array-chip-remove {\n\tflex-shrink: 0;\n}\n\n.cn-advanced-form-dialog__object-array-add {\n\talign-self: flex-start;\n}\n</style>\n"],"names":["_openBlock","_createElementBlock","_createVNode","_createBlock","_createElementVNode","_Fragment","_renderList","_withModifiers","_toDisplayString","_createTextVNode"],"mappings":"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"}
@@ -2,7 +2,7 @@ import script from './CnAnalyticsReportPicker.vue2.js';
2
2
  import { render } from './CnAnalyticsReportPicker.vue3.js';
3
3
 
4
4
  script.render = render;
5
- script.__scopeId = "data-v-607f4d58";
5
+ script.__scopeId = "data-v-0ed141ec";
6
6
  const __sfc_main = script;
7
7
 
8
8
  export { __sfc_main as default };
@@ -1,6 +1,7 @@
1
1
  import { translate } from '@nextcloud/l10n';
2
2
  import { NcButton, NcDialog, NcEmptyContent, NcLoadingIcon, NcNoteCard, NcTextField } from '@nextcloud/vue';
3
3
  import ChartBar from 'vue-material-design-icons/ChartBar.vue';
4
+ import { userLocale } from '../../utils/userLocale.js';
4
5
  import { prefixUrl, buildHeaders } from '../../utils/headers.js';
5
6
 
6
7
  var script = {
@@ -148,7 +149,7 @@ var script = {
148
149
  if (Number.isNaN(d.getTime())) {
149
150
  return ''
150
151
  }
151
- return d.toLocaleDateString(undefined, { dateStyle: 'medium' })
152
+ return d.toLocaleDateString(userLocale(), { dateStyle: 'medium' })
152
153
  } catch {
153
154
  return ''
154
155
  }
@@ -1 +1 @@
1
- {"version":3,"file":"CnAnalyticsReportPicker.vue2.js","sources":["../../../../src/components/CnAnalyticsReportPicker/CnAnalyticsReportPicker.vue"],"sourcesContent":["<!--\n CnAnalyticsReportPicker — modal for picking an existing NC Analytics\n report to link to the parent OR object.\n\n Flow:\n 1. Load reports via GET /api/integrations/analytics/available\n 2. Filter client-side via a search input (debounced; the same query\n is forwarded as `?search=` for server-side filtering)\n 3. Single-select a report row (title + report-type badge + modified\n date)\n 4. Confirm → emit `link` with `{ reportId }`\n\n All API calls are wrapped in best-effort try/catch so a transient\n Analytics failure surfaces a user-visible inline error rather than a\n modal crash. The modal stays open across errors so the user can retry\n without losing context.\n\n ADR-004: modal lives in its own .vue file under\n `src/components/CnAnalyticsReportPicker/` (NcDialog-based; matches the\n photos/deck/poll/talk picker pattern).\n\n ADR-019: drives the `analytics` integration leaf's \"link existing\"\n surface; emits `link` so the parent (CnAnalyticsTab) can POST the\n selection to the OR endpoint.\n\n SPDX-License-Identifier: EUPL-1.2\n SPDX-FileCopyrightText: 2026 Conduction B.V.\n-->\n<template>\n\t<NcDialog\n\t\t:name=\"dialogTitle\"\n\t\tsize=\"normal\"\n\t\tdata-testid=\"cn-analytics-report-picker\"\n\t\t@closing=\"onClose\">\n\t\t<div class=\"cn-analytics-report-picker\">\n\t\t\t<NcNoteCard v-if=\"error\" type=\"error\" class=\"cn-analytics-report-picker__error\">\n\t\t\t\t{{ error }}\n\t\t\t</NcNoteCard>\n\n\t\t\t<NcTextField\n\t\t\t\tv-model=\"search\"\n\t\t\t\t:label=\"t('nextcloud-vue', 'Search reports')\"\n\t\t\t\t:placeholder=\"t('nextcloud-vue', 'Type to filter…')\"\n\t\t\t\tclass=\"cn-analytics-report-picker__search\"\n\t\t\t\t@update:modelValue=\"onSearch\" />\n\n\t\t\t<NcLoadingIcon v-if=\"loading\" />\n\t\t\t<NcEmptyContent\n\t\t\t\tv-else-if=\"visibleReports.length === 0\"\n\t\t\t\t:name=\"t('nextcloud-vue', 'No reports available')\"\n\t\t\t\t:description=\"t('nextcloud-vue', 'Create a report in NC Analytics first, or use the create dialog.')\" />\n\t\t\t<ul v-else class=\"cn-analytics-report-picker__list\">\n\t\t\t\t<li\n\t\t\t\t\tv-for=\"report in visibleReports\"\n\t\t\t\t\t:key=\"report.id\"\n\t\t\t\t\tclass=\"cn-analytics-report-picker__row\"\n\t\t\t\t\t:class=\"{ 'cn-analytics-report-picker__row--selected': selectedReportId === report.id }\">\n\t\t\t\t\t<button type=\"button\" class=\"cn-analytics-report-picker__row-button\" @click=\"pickReport(report)\">\n\t\t\t\t\t\t<span class=\"cn-analytics-report-picker__row-icon\">\n\t\t\t\t\t\t\t<ChartBar :size=\"20\" />\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<span class=\"cn-analytics-report-picker__row-main\">\n\t\t\t\t\t\t\t<span class=\"cn-analytics-report-picker__name\">{{ report.name }}</span>\n\t\t\t\t\t\t\t<span class=\"cn-analytics-report-picker__sub\">\n\t\t\t\t\t\t\t\t<span class=\"cn-analytics-report-picker__badge\">{{ reportTypeLabel(report) }}</span>\n\t\t\t\t\t\t\t\t<span v-if=\"modifiedLabel(report)\" class=\"cn-analytics-report-picker__when\">\n\t\t\t\t\t\t\t\t\t· {{ modifiedLabel(report) }}\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</span>\n\t\t\t\t\t</button>\n\t\t\t\t</li>\n\t\t\t</ul>\n\t\t</div>\n\n\t\t<template #actions>\n\t\t\t<NcButton @click=\"onClose\">\n\t\t\t\t{{ t('nextcloud-vue', 'Cancel') }}\n\t\t\t</NcButton>\n\t\t\t<NcButton\n\t\t\t\tvariant=\"primary\"\n\t\t\t\t:disabled=\"selectedReportId === null\"\n\t\t\t\t@click=\"confirm\">\n\t\t\t\t{{ t('nextcloud-vue', 'Link report') }}\n\t\t\t</NcButton>\n\t\t</template>\n\t</NcDialog>\n</template>\n\n<script>\n/**\n * SPDX-License-Identifier: EUPL-1.2\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n *\n * CnAnalyticsReportPicker — pick an existing Analytics report. Emits\n * `link` with the chosen report id.\n *\n * @see ADR-019 (pluggable integrations) and ADR-022 (sidebar tabs)\n */\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcButton, NcDialog, NcEmptyContent, NcLoadingIcon, NcNoteCard, NcTextField } from '@nextcloud/vue'\nimport ChartBar from 'vue-material-design-icons/ChartBar.vue'\nimport { buildHeaders, prefixUrl } from '../../utils/index.js'\n\nexport default {\n\tname: 'CnAnalyticsReportPicker',\n\n\tcomponents: { NcButton, NcDialog, NcEmptyContent, NcLoadingIcon, NcNoteCard, NcTextField, ChartBar },\n\n\tprops: {\n\t\t/** Base API URL for OR. */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\t\t/** Pre-translated dialog title. */\n\t\tdialogTitle: { type: String, default: () => t('nextcloud-vue', 'Link an existing report') },\n\t},\n\n\temits: ['close', 'link'],\n\n\tdata() {\n\t\treturn {\n\t\t\tloading: false,\n\t\t\terror: '',\n\t\t\treports: [],\n\t\t\tsearch: '',\n\t\t\tselectedReportId: null,\n\t\t\tsearchTimer: null,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Client-side filter on top of the server-side `?search=`\n\t\t * payload — so the user sees instant feedback even between\n\t\t * debounce ticks.\n\t\t *\n\t\t * @return {Array} The filtered report rows.\n\t\t */\n\t\tvisibleReports() {\n\t\t\tconst term = this.search.trim().toLowerCase()\n\t\t\tif (term === '') {\n\t\t\t\treturn this.reports\n\t\t\t}\n\t\t\treturn this.reports.filter((report) => (report.name || '').toLowerCase().includes(term))\n\t\t},\n\t},\n\n\tmounted() {\n\t\tthis.fetchReports()\n\t},\n\n\tbeforeUnmount() {\n\t\tif (this.searchTimer) {\n\t\t\tclearTimeout(this.searchTimer)\n\t\t}\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/**\n\t\t * Dismiss the dialog.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tonClose() {\n\t\t\t/**\n\t\t\t * @event close Emitted when the dialog should be closed (cancel or close button).\n\t\t\t */\n\t\t\tthis.$emit('close')\n\t\t},\n\n\t\tasync fetchReports(searchTerm = '') {\n\t\t\tthis.loading = true\n\t\t\tthis.error = ''\n\t\t\ttry {\n\t\t\t\tconst query = searchTerm ? `?search=${encodeURIComponent(searchTerm)}` : ''\n\t\t\t\tconst response = await fetch(prefixUrl(`${this.apiBase}/integrations/analytics/available${query}`), {\n\t\t\t\t\theaders: buildHeaders(),\n\t\t\t\t})\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tconst data = await response.json()\n\t\t\t\t\tthis.reports = data.results || []\n\t\t\t\t} else if (response.status === 501) {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'NC Analytics is not installed.')\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load reports.')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnAnalyticsReportPicker] fetch reports failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load reports.')\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\tonSearch(value) {\n\t\t\t// Debounce server-side filter; client-side filter is live.\n\t\t\tthis.search = value\n\t\t\tif (this.searchTimer) {\n\t\t\t\tclearTimeout(this.searchTimer)\n\t\t\t}\n\t\t\tthis.searchTimer = setTimeout(() => {\n\t\t\t\tthis.fetchReports(this.search.trim())\n\t\t\t}, 300)\n\t\t},\n\n\t\tpickReport(report) {\n\t\t\tthis.selectedReportId = report.id\n\t\t},\n\n\t\t/**\n\t\t * Resolve a translatable label for the report type code. Falls\n\t\t * back to \"Report\" when the code is unknown (NC Analytics 6.x\n\t\t * datasource types).\n\t\t *\n\t\t * @param {object} report Picker row.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\treportTypeLabel(report) {\n\t\t\tconst raw = report?.type\n\t\t\tif (raw === null || raw === undefined || raw === '') {\n\t\t\t\treturn t('nextcloud-vue', 'Report')\n\t\t\t}\n\t\t\tswitch (Number(raw)) {\n\t\t\t\tcase 0: return t('nextcloud-vue', 'Group')\n\t\t\t\tcase 1: return t('nextcloud-vue', 'File')\n\t\t\t\tcase 2: return t('nextcloud-vue', 'Database')\n\t\t\t\tcase 3: return t('nextcloud-vue', 'Git')\n\t\t\t\tcase 4: return t('nextcloud-vue', 'External')\n\t\t\t\tcase 6: return t('nextcloud-vue', 'JSON')\n\t\t\t\tdefault: return t('nextcloud-vue', 'Report')\n\t\t\t}\n\t\t},\n\n\t\tmodifiedLabel(report) {\n\t\t\tconst value = report?.modifiedAt || report?.modified || ''\n\t\t\tif (!value) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst num = Number(value)\n\t\t\t\tconst d = Number.isFinite(num) && num > 0 && String(value).length <= 12\n\t\t\t\t\t? new Date(num * 1000)\n\t\t\t\t\t: new Date(value)\n\t\t\t\tif (Number.isNaN(d.getTime())) {\n\t\t\t\t\treturn ''\n\t\t\t\t}\n\t\t\t\treturn d.toLocaleDateString(undefined, { dateStyle: 'medium' })\n\t\t\t} catch {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t},\n\n\t\tconfirm() {\n\t\t\tif (this.selectedReportId === null) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\t/**\n\t\t\t * @event link Emitted when the user confirms the selection. Payload: `{ reportId }`.\n\t\t\t */\n\t\t\tthis.$emit('link', { reportId: this.selectedReportId })\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-analytics-report-picker {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 12px;\n\tpadding: 4px 0;\n\tmin-height: 240px;\n}\n\n.cn-analytics-report-picker__error {\n\tmargin: 4px 0;\n}\n\n.cn-analytics-report-picker__search {\n\twidth: 100%;\n}\n\n.cn-analytics-report-picker__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n}\n\n.cn-analytics-report-picker__row {\n\tborder-radius: var(--border-radius);\n}\n\n.cn-analytics-report-picker__row-button {\n\tdisplay: flex;\n\talign-items: flex-start;\n\tgap: 10px;\n\twidth: 100%;\n\tpadding: 8px 10px;\n\tbackground: var(--color-background-hover);\n\tborder: 2px solid transparent;\n\tborder-radius: var(--border-radius);\n\tcolor: var(--color-main-text);\n\tcursor: pointer;\n\ttext-align: left;\n}\n\n.cn-analytics-report-picker__row-button:hover {\n\tbackground: var(--color-background-dark, var(--color-background-hover));\n}\n\n.cn-analytics-report-picker__row--selected .cn-analytics-report-picker__row-button {\n\tborder-color: var(--color-primary-element);\n}\n\n.cn-analytics-report-picker__row-icon {\n\tflex-shrink: 0;\n\tcolor: var(--color-primary-element);\n\tpadding-top: 2px;\n}\n\n.cn-analytics-report-picker__row-main {\n\tflex: 1;\n\tmin-width: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 3px;\n}\n\n.cn-analytics-report-picker__name {\n\tfont-size: 13px;\n\tfont-weight: 500;\n\twhite-space: nowrap;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n.cn-analytics-report-picker__sub {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tgap: 6px;\n\tfont-size: 11px;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-analytics-report-picker__badge {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tpadding: 1px 6px;\n\tborder-radius: 8px;\n\tbackground: var(--color-background-darker, var(--color-background-dark));\n\tcolor: var(--color-main-text);\n\tfont-size: 10px;\n\tfont-weight: 600;\n\tletter-spacing: 0.04em;\n\ttext-transform: uppercase;\n}\n\n.cn-analytics-report-picker__when {\n\twhite-space: nowrap;\n}\n</style>\n"],"names":["t"],"mappings":"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"}
1
+ {"version":3,"file":"CnAnalyticsReportPicker.vue2.js","sources":["../../../../src/components/CnAnalyticsReportPicker/CnAnalyticsReportPicker.vue"],"sourcesContent":["<!--\n CnAnalyticsReportPicker — modal for picking an existing NC Analytics\n report to link to the parent OR object.\n\n Flow:\n 1. Load reports via GET /api/integrations/analytics/available\n 2. Filter client-side via a search input (debounced; the same query\n is forwarded as `?search=` for server-side filtering)\n 3. Single-select a report row (title + report-type badge + modified\n date)\n 4. Confirm → emit `link` with `{ reportId }`\n\n All API calls are wrapped in best-effort try/catch so a transient\n Analytics failure surfaces a user-visible inline error rather than a\n modal crash. The modal stays open across errors so the user can retry\n without losing context.\n\n ADR-004: modal lives in its own .vue file under\n `src/components/CnAnalyticsReportPicker/` (NcDialog-based; matches the\n photos/deck/poll/talk picker pattern).\n\n ADR-019: drives the `analytics` integration leaf's \"link existing\"\n surface; emits `link` so the parent (CnAnalyticsTab) can POST the\n selection to the OR endpoint.\n\n SPDX-License-Identifier: EUPL-1.2\n SPDX-FileCopyrightText: 2026 Conduction B.V.\n-->\n<template>\n\t<NcDialog\n\t\t:name=\"dialogTitle\"\n\t\tsize=\"normal\"\n\t\tdata-testid=\"cn-analytics-report-picker\"\n\t\t@closing=\"onClose\">\n\t\t<div class=\"cn-analytics-report-picker\">\n\t\t\t<NcNoteCard v-if=\"error\" type=\"error\" class=\"cn-analytics-report-picker__error\">\n\t\t\t\t{{ error }}\n\t\t\t</NcNoteCard>\n\n\t\t\t<NcTextField\n\t\t\t\tv-model=\"search\"\n\t\t\t\t:label=\"t('nextcloud-vue', 'Search reports')\"\n\t\t\t\t:placeholder=\"t('nextcloud-vue', 'Type to filter…')\"\n\t\t\t\tclass=\"cn-analytics-report-picker__search\"\n\t\t\t\t@update:modelValue=\"onSearch\" />\n\n\t\t\t<NcLoadingIcon v-if=\"loading\" />\n\t\t\t<NcEmptyContent\n\t\t\t\tv-else-if=\"visibleReports.length === 0\"\n\t\t\t\t:name=\"t('nextcloud-vue', 'No reports available')\"\n\t\t\t\t:description=\"t('nextcloud-vue', 'Create a report in NC Analytics first, or use the create dialog.')\" />\n\t\t\t<ul v-else class=\"cn-analytics-report-picker__list\">\n\t\t\t\t<li\n\t\t\t\t\tv-for=\"report in visibleReports\"\n\t\t\t\t\t:key=\"report.id\"\n\t\t\t\t\tclass=\"cn-analytics-report-picker__row\"\n\t\t\t\t\t:class=\"{ 'cn-analytics-report-picker__row--selected': selectedReportId === report.id }\">\n\t\t\t\t\t<button type=\"button\" class=\"cn-analytics-report-picker__row-button\" @click=\"pickReport(report)\">\n\t\t\t\t\t\t<span class=\"cn-analytics-report-picker__row-icon\">\n\t\t\t\t\t\t\t<ChartBar :size=\"20\" />\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<span class=\"cn-analytics-report-picker__row-main\">\n\t\t\t\t\t\t\t<span class=\"cn-analytics-report-picker__name\">{{ report.name }}</span>\n\t\t\t\t\t\t\t<span class=\"cn-analytics-report-picker__sub\">\n\t\t\t\t\t\t\t\t<span class=\"cn-analytics-report-picker__badge\">{{ reportTypeLabel(report) }}</span>\n\t\t\t\t\t\t\t\t<span v-if=\"modifiedLabel(report)\" class=\"cn-analytics-report-picker__when\">\n\t\t\t\t\t\t\t\t\t· {{ modifiedLabel(report) }}\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</span>\n\t\t\t\t\t</button>\n\t\t\t\t</li>\n\t\t\t</ul>\n\t\t</div>\n\n\t\t<template #actions>\n\t\t\t<NcButton @click=\"onClose\">\n\t\t\t\t{{ t('nextcloud-vue', 'Cancel') }}\n\t\t\t</NcButton>\n\t\t\t<NcButton\n\t\t\t\tvariant=\"primary\"\n\t\t\t\t:disabled=\"selectedReportId === null\"\n\t\t\t\t@click=\"confirm\">\n\t\t\t\t{{ t('nextcloud-vue', 'Link report') }}\n\t\t\t</NcButton>\n\t\t</template>\n\t</NcDialog>\n</template>\n\n<script>\n/**\n * SPDX-License-Identifier: EUPL-1.2\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n *\n * CnAnalyticsReportPicker — pick an existing Analytics report. Emits\n * `link` with the chosen report id.\n *\n * @see ADR-019 (pluggable integrations) and ADR-022 (sidebar tabs)\n */\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcButton, NcDialog, NcEmptyContent, NcLoadingIcon, NcNoteCard, NcTextField } from '@nextcloud/vue'\nimport ChartBar from 'vue-material-design-icons/ChartBar.vue'\nimport { buildHeaders, prefixUrl } from '../../utils/index.js'\nimport { userLocale } from '../../utils/userLocale.js'\n\nexport default {\n\tname: 'CnAnalyticsReportPicker',\n\n\tcomponents: { NcButton, NcDialog, NcEmptyContent, NcLoadingIcon, NcNoteCard, NcTextField, ChartBar },\n\n\tprops: {\n\t\t/** Base API URL for OR. */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\t\t/** Pre-translated dialog title. */\n\t\tdialogTitle: { type: String, default: () => t('nextcloud-vue', 'Link an existing report') },\n\t},\n\n\temits: ['close', 'link'],\n\n\tdata() {\n\t\treturn {\n\t\t\tloading: false,\n\t\t\terror: '',\n\t\t\treports: [],\n\t\t\tsearch: '',\n\t\t\tselectedReportId: null,\n\t\t\tsearchTimer: null,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Client-side filter on top of the server-side `?search=`\n\t\t * payload — so the user sees instant feedback even between\n\t\t * debounce ticks.\n\t\t *\n\t\t * @return {Array} The filtered report rows.\n\t\t */\n\t\tvisibleReports() {\n\t\t\tconst term = this.search.trim().toLowerCase()\n\t\t\tif (term === '') {\n\t\t\t\treturn this.reports\n\t\t\t}\n\t\t\treturn this.reports.filter((report) => (report.name || '').toLowerCase().includes(term))\n\t\t},\n\t},\n\n\tmounted() {\n\t\tthis.fetchReports()\n\t},\n\n\tbeforeUnmount() {\n\t\tif (this.searchTimer) {\n\t\t\tclearTimeout(this.searchTimer)\n\t\t}\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/**\n\t\t * Dismiss the dialog.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tonClose() {\n\t\t\t/**\n\t\t\t * @event close Emitted when the dialog should be closed (cancel or close button).\n\t\t\t */\n\t\t\tthis.$emit('close')\n\t\t},\n\n\t\tasync fetchReports(searchTerm = '') {\n\t\t\tthis.loading = true\n\t\t\tthis.error = ''\n\t\t\ttry {\n\t\t\t\tconst query = searchTerm ? `?search=${encodeURIComponent(searchTerm)}` : ''\n\t\t\t\tconst response = await fetch(prefixUrl(`${this.apiBase}/integrations/analytics/available${query}`), {\n\t\t\t\t\theaders: buildHeaders(),\n\t\t\t\t})\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tconst data = await response.json()\n\t\t\t\t\tthis.reports = data.results || []\n\t\t\t\t} else if (response.status === 501) {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'NC Analytics is not installed.')\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load reports.')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnAnalyticsReportPicker] fetch reports failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load reports.')\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\tonSearch(value) {\n\t\t\t// Debounce server-side filter; client-side filter is live.\n\t\t\tthis.search = value\n\t\t\tif (this.searchTimer) {\n\t\t\t\tclearTimeout(this.searchTimer)\n\t\t\t}\n\t\t\tthis.searchTimer = setTimeout(() => {\n\t\t\t\tthis.fetchReports(this.search.trim())\n\t\t\t}, 300)\n\t\t},\n\n\t\tpickReport(report) {\n\t\t\tthis.selectedReportId = report.id\n\t\t},\n\n\t\t/**\n\t\t * Resolve a translatable label for the report type code. Falls\n\t\t * back to \"Report\" when the code is unknown (NC Analytics 6.x\n\t\t * datasource types).\n\t\t *\n\t\t * @param {object} report Picker row.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\treportTypeLabel(report) {\n\t\t\tconst raw = report?.type\n\t\t\tif (raw === null || raw === undefined || raw === '') {\n\t\t\t\treturn t('nextcloud-vue', 'Report')\n\t\t\t}\n\t\t\tswitch (Number(raw)) {\n\t\t\t\tcase 0: return t('nextcloud-vue', 'Group')\n\t\t\t\tcase 1: return t('nextcloud-vue', 'File')\n\t\t\t\tcase 2: return t('nextcloud-vue', 'Database')\n\t\t\t\tcase 3: return t('nextcloud-vue', 'Git')\n\t\t\t\tcase 4: return t('nextcloud-vue', 'External')\n\t\t\t\tcase 6: return t('nextcloud-vue', 'JSON')\n\t\t\t\tdefault: return t('nextcloud-vue', 'Report')\n\t\t\t}\n\t\t},\n\n\t\tmodifiedLabel(report) {\n\t\t\tconst value = report?.modifiedAt || report?.modified || ''\n\t\t\tif (!value) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst num = Number(value)\n\t\t\t\tconst d = Number.isFinite(num) && num > 0 && String(value).length <= 12\n\t\t\t\t\t? new Date(num * 1000)\n\t\t\t\t\t: new Date(value)\n\t\t\t\tif (Number.isNaN(d.getTime())) {\n\t\t\t\t\treturn ''\n\t\t\t\t}\n\t\t\t\treturn d.toLocaleDateString(userLocale(), { dateStyle: 'medium' })\n\t\t\t} catch {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t},\n\n\t\tconfirm() {\n\t\t\tif (this.selectedReportId === null) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\t/**\n\t\t\t * @event link Emitted when the user confirms the selection. Payload: `{ reportId }`.\n\t\t\t */\n\t\t\tthis.$emit('link', { reportId: this.selectedReportId })\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-analytics-report-picker {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 12px;\n\tpadding: 4px 0;\n\tmin-height: 240px;\n}\n\n.cn-analytics-report-picker__error {\n\tmargin: 4px 0;\n}\n\n.cn-analytics-report-picker__search {\n\twidth: 100%;\n}\n\n.cn-analytics-report-picker__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n}\n\n.cn-analytics-report-picker__row {\n\tborder-radius: var(--border-radius);\n}\n\n.cn-analytics-report-picker__row-button {\n\tdisplay: flex;\n\talign-items: flex-start;\n\tgap: 10px;\n\twidth: 100%;\n\tpadding: 8px 10px;\n\tbackground: var(--color-background-hover);\n\tborder: 2px solid transparent;\n\tborder-radius: var(--border-radius);\n\tcolor: var(--color-main-text);\n\tcursor: pointer;\n\ttext-align: left;\n}\n\n.cn-analytics-report-picker__row-button:hover {\n\tbackground: var(--color-background-dark, var(--color-background-hover));\n}\n\n.cn-analytics-report-picker__row--selected .cn-analytics-report-picker__row-button {\n\tborder-color: var(--color-primary-element);\n}\n\n.cn-analytics-report-picker__row-icon {\n\tflex-shrink: 0;\n\tcolor: var(--color-primary-element);\n\tpadding-top: 2px;\n}\n\n.cn-analytics-report-picker__row-main {\n\tflex: 1;\n\tmin-width: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 3px;\n}\n\n.cn-analytics-report-picker__name {\n\tfont-size: 13px;\n\tfont-weight: 500;\n\twhite-space: nowrap;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n.cn-analytics-report-picker__sub {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tgap: 6px;\n\tfont-size: 11px;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-analytics-report-picker__badge {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tpadding: 1px 6px;\n\tborder-radius: 8px;\n\tbackground: var(--color-background-darker, var(--color-background-dark));\n\tcolor: var(--color-main-text);\n\tfont-size: 10px;\n\tfont-weight: 600;\n\tletter-spacing: 0.04em;\n\ttext-transform: uppercase;\n}\n\n.cn-analytics-report-picker__when {\n\twhite-space: nowrap;\n}\n</style>\n"],"names":["t"],"mappings":";;;;;;AAyGA,aAAe;AACd,CAAA,IAAI,EAAE,yBAAyB;;AAE/B,CAAA,UAAU,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,cAAc,EAAE,aAAa,EAAE,UAAU,EAAE,WAAW,EAAE,UAAU;;AAEpG,CAAA,KAAK,EAAE;AACN;EACA,OAAO,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,wBAAuB,EAAG;AAC5D;AACA,EAAA,WAAW,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAMA,SAAC,CAAC,eAAe,EAAE,yBAAyB,GAAG;EAC3F;;CAED,KAAK,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC;;AAExB,CAAA,IAAI,GAAG;AACN,EAAA,OAAO;GACN,OAAO,EAAE,KAAK;AACd,GAAA,KAAK,EAAE,EAAE;GACT,OAAO,EAAE,EAAE;AACX,GAAA,MAAM,EAAE,EAAE;AACV,GAAA,gBAAgB,EAAE,IAAI;AACtB,GAAA,WAAW,EAAE,IAAI;AAClB,GAAA;EACA;;AAED,CAAA,QAAQ,EAAE;AACT;;;AAGC;AACA;AACA;;AAED,EAAA,cAAc,GAAG;AAChB,GAAA,MAAM,IAAG,GAAI,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,WAAW,GAAC;GAC5C,IAAI,IAAK,KAAI,EAAE,EAAE;AAChB,IAAA,OAAO,IAAI,CAAC,OAAM;AACnB,IAAA;AACA,GAAA,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,MAAM,KAAK,CAAC,MAAM,CAAC,IAAG,IAAK,EAAE,EAAE,WAAW,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAA;GACvF;EACD;;AAED,CAAA,OAAO,GAAG;AACT,EAAA,IAAI,CAAC,YAAY,GAAC;EAClB;;AAED,CAAA,aAAa,GAAG;AACf,EAAA,IAAI,IAAI,CAAC,WAAW,EAAE;GACrB,YAAY,CAAC,IAAI,CAAC,WAAW,EAAA;AAC9B,GAAA;EACA;;AAED,CAAA,OAAO,EAAE;KACRA,SAAC;;AAED;AACC;AACA;;;AAGD,EAAA,OAAO,GAAG;AACT;AACC;;AAED,GAAA,IAAI,CAAC,KAAK,CAAC,OAAO,EAAA;GAClB;;AAED,EAAA,MAAM,YAAY,CAAC,UAAS,GAAI,EAAE,EAAE;AACnC,GAAA,IAAI,CAAC,OAAM,GAAI,KAAG;GAClB,IAAI,CAAC,KAAM,GAAE,GAAC;AACd,GAAA,IAAI;AACH,IAAA,MAAM,KAAM,GAAE,UAAW,GAAE,CAAC,QAAQ,EAAE,kBAAkB,CAAC,UAAU,CAAC,CAAC,CAAA,GAAI,GAAC;IAC1E,MAAM,QAAS,GAAE,MAAM,KAAK,CAAC,SAAS,CAAC,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,iCAAiC,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE;KACnG,OAAO,EAAE,YAAY,EAAE;KACvB,EAAA;AACD,IAAA,IAAI,QAAQ,CAAC,EAAE,EAAE;KAChB,MAAM,IAAK,GAAE,MAAM,QAAQ,CAAC,IAAI,GAAC;KACjC,IAAI,CAAC,OAAM,GAAI,IAAI,CAAC,OAAM,IAAK,GAAC;AACjC,KAAA,MAAO,IAAI,QAAQ,CAAC,MAAK,KAAM,GAAG,EAAE;KACnC,IAAI,CAAC,KAAM,GAAEA,SAAC,CAAC,eAAe,EAAE,gCAAgC,EAAA;KAC/D,MAAK;KACN,IAAI,CAAC,KAAI,GAAIA,SAAC,CAAC,eAAe,EAAE,yBAAyB,EAAA;AAC1D,KAAA;IACC,CAAA,OAAO,GAAG,EAAE;AACb;AACA,IAAA,OAAO,CAAC,KAAK,CAAC,gDAAgD,EAAE,GAAG,EAAA;IACnE,IAAI,CAAC,KAAI,GAAIA,SAAC,CAAC,eAAe,EAAE,yBAAyB,EAAA;AAC1D,IAAA,SAAU;AACT,IAAA,IAAI,CAAC,OAAQ,GAAE,MAAI;AACpB,IAAA;GACA;;AAED,EAAA,QAAQ,CAAC,KAAK,EAAE;;AAEf,GAAA,IAAI,CAAC,MAAO,GAAE,MAAI;AAClB,GAAA,IAAI,IAAI,CAAC,WAAW,EAAE;IACrB,YAAY,CAAC,IAAI,CAAC,WAAW,EAAA;AAC9B,IAAA;AACA,GAAA,IAAI,CAAC,WAAU,GAAI,UAAU,CAAC,MAAM;AACnC,IAAA,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,EAAA;IACpC,EAAE,GAAG,EAAA;GACN;;AAED,EAAA,UAAU,CAAC,MAAM,EAAE;AAClB,GAAA,IAAI,CAAC,gBAAe,GAAI,MAAM,CAAC,GAAC;GAChC;;AAED;;;AAGC;AACA;AACA;AACA;AACA;;EAED,eAAe,CAAC,MAAM,EAAE;AACvB,GAAA,MAAM,MAAM,MAAM,EAAE,KAAG;AACvB,GAAA,IAAI,GAAE,KAAM,IAAK,IAAG,QAAQ,SAAU,IAAG,GAAI,KAAI,EAAE,EAAE;AACpD,IAAA,OAAOA,SAAC,CAAC,eAAe,EAAE,QAAQ,CAAA;AACnC,IAAA;AACA,GAAA,QAAQ,MAAM,CAAC,GAAG,CAAC;AAClB,IAAA,KAAK,CAAC,EAAE,OAAOA,SAAC,CAAC,eAAe,EAAE,OAAO,CAAA;AACzC,IAAA,KAAK,CAAC,EAAE,OAAOA,SAAC,CAAC,eAAe,EAAE,MAAM,CAAA;AACxC,IAAA,KAAK,CAAC,EAAE,OAAOA,SAAC,CAAC,eAAe,EAAE,UAAU,CAAA;AAC5C,IAAA,KAAK,CAAC,EAAE,OAAOA,SAAC,CAAC,eAAe,EAAE,KAAK,CAAA;AACvC,IAAA,KAAK,CAAC,EAAE,OAAOA,SAAC,CAAC,eAAe,EAAE,UAAU,CAAA;AAC5C,IAAA,KAAK,CAAC,EAAE,OAAOA,SAAC,CAAC,eAAe,EAAE,MAAM,CAAA;AACxC,IAAA,SAAS,OAAOA,SAAC,CAAC,eAAe,EAAE,QAAQ,CAAA;AAC5C,IAAA;GACA;;AAED,EAAA,aAAa,CAAC,MAAM,EAAE;GACrB,MAAM,KAAI,GAAI,MAAM,EAAE,cAAc,MAAM,EAAE,QAAO,IAAK,GAAC;GACzD,IAAI,CAAC,KAAK,EAAE;AACX,IAAA,OAAO,EAAC;AACT,IAAA;AACA,GAAA,IAAI;AACH,IAAA,MAAM,MAAM,MAAM,CAAC,KAAK,EAAA;IACxB,MAAM,CAAA,GAAI,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAA,IAAK,GAAE,GAAI,CAAA,IAAK,MAAM,CAAC,KAAK,CAAC,CAAC,MAAK,IAAK,EAAC;AACrE,OAAE,IAAI,IAAI,CAAC,GAAI,GAAE,IAAI,CAAA;AACrB,OAAE,IAAI,IAAI,CAAC,KAAK,EAAA;IACjB,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,EAAE;AAC9B,KAAA,OAAO,EAAC;AACT,KAAA;IACA,OAAO,CAAC,CAAC,kBAAkB,CAAC,UAAU,EAAE,EAAE,EAAE,SAAS,EAAE,QAAO,EAAG,CAAA;AAClE,IAAA,CAAE,MAAM;AACP,IAAA,OAAO,EAAC;AACT,IAAA;GACA;;AAED,EAAA,OAAO,GAAG;AACT,GAAA,IAAI,IAAI,CAAC,gBAAe,KAAM,IAAI,EAAE;IACnC,MAAK;AACN,IAAA;AACA;AACC;;GAED,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,EAAE,QAAQ,EAAE,IAAI,CAAC,gBAAe,EAAG,EAAA;GACtD;EACD;AACF;;;;"}
@@ -1 +1 @@
1
- {"version":3,"file":"CnAnalyticsReportPicker.vue3.js","sources":["../../../../src/components/CnAnalyticsReportPicker/CnAnalyticsReportPicker.vue?vue&type=template&id=607f4d58&lang.js"],"sourcesContent":["<!--\n CnAnalyticsReportPicker — modal for picking an existing NC Analytics\n report to link to the parent OR object.\n\n Flow:\n 1. Load reports via GET /api/integrations/analytics/available\n 2. Filter client-side via a search input (debounced; the same query\n is forwarded as `?search=` for server-side filtering)\n 3. Single-select a report row (title + report-type badge + modified\n date)\n 4. Confirm → emit `link` with `{ reportId }`\n\n All API calls are wrapped in best-effort try/catch so a transient\n Analytics failure surfaces a user-visible inline error rather than a\n modal crash. The modal stays open across errors so the user can retry\n without losing context.\n\n ADR-004: modal lives in its own .vue file under\n `src/components/CnAnalyticsReportPicker/` (NcDialog-based; matches the\n photos/deck/poll/talk picker pattern).\n\n ADR-019: drives the `analytics` integration leaf's \"link existing\"\n surface; emits `link` so the parent (CnAnalyticsTab) can POST the\n selection to the OR endpoint.\n\n SPDX-License-Identifier: EUPL-1.2\n SPDX-FileCopyrightText: 2026 Conduction B.V.\n-->\n<template>\n\t<NcDialog\n\t\t:name=\"dialogTitle\"\n\t\tsize=\"normal\"\n\t\tdata-testid=\"cn-analytics-report-picker\"\n\t\t@closing=\"onClose\">\n\t\t<div class=\"cn-analytics-report-picker\">\n\t\t\t<NcNoteCard v-if=\"error\" type=\"error\" class=\"cn-analytics-report-picker__error\">\n\t\t\t\t{{ error }}\n\t\t\t</NcNoteCard>\n\n\t\t\t<NcTextField\n\t\t\t\tv-model=\"search\"\n\t\t\t\t:label=\"t('nextcloud-vue', 'Search reports')\"\n\t\t\t\t:placeholder=\"t('nextcloud-vue', 'Type to filter…')\"\n\t\t\t\tclass=\"cn-analytics-report-picker__search\"\n\t\t\t\t@update:modelValue=\"onSearch\" />\n\n\t\t\t<NcLoadingIcon v-if=\"loading\" />\n\t\t\t<NcEmptyContent\n\t\t\t\tv-else-if=\"visibleReports.length === 0\"\n\t\t\t\t:name=\"t('nextcloud-vue', 'No reports available')\"\n\t\t\t\t:description=\"t('nextcloud-vue', 'Create a report in NC Analytics first, or use the create dialog.')\" />\n\t\t\t<ul v-else class=\"cn-analytics-report-picker__list\">\n\t\t\t\t<li\n\t\t\t\t\tv-for=\"report in visibleReports\"\n\t\t\t\t\t:key=\"report.id\"\n\t\t\t\t\tclass=\"cn-analytics-report-picker__row\"\n\t\t\t\t\t:class=\"{ 'cn-analytics-report-picker__row--selected': selectedReportId === report.id }\">\n\t\t\t\t\t<button type=\"button\" class=\"cn-analytics-report-picker__row-button\" @click=\"pickReport(report)\">\n\t\t\t\t\t\t<span class=\"cn-analytics-report-picker__row-icon\">\n\t\t\t\t\t\t\t<ChartBar :size=\"20\" />\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<span class=\"cn-analytics-report-picker__row-main\">\n\t\t\t\t\t\t\t<span class=\"cn-analytics-report-picker__name\">{{ report.name }}</span>\n\t\t\t\t\t\t\t<span class=\"cn-analytics-report-picker__sub\">\n\t\t\t\t\t\t\t\t<span class=\"cn-analytics-report-picker__badge\">{{ reportTypeLabel(report) }}</span>\n\t\t\t\t\t\t\t\t<span v-if=\"modifiedLabel(report)\" class=\"cn-analytics-report-picker__when\">\n\t\t\t\t\t\t\t\t\t· {{ modifiedLabel(report) }}\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</span>\n\t\t\t\t\t</button>\n\t\t\t\t</li>\n\t\t\t</ul>\n\t\t</div>\n\n\t\t<template #actions>\n\t\t\t<NcButton @click=\"onClose\">\n\t\t\t\t{{ t('nextcloud-vue', 'Cancel') }}\n\t\t\t</NcButton>\n\t\t\t<NcButton\n\t\t\t\tvariant=\"primary\"\n\t\t\t\t:disabled=\"selectedReportId === null\"\n\t\t\t\t@click=\"confirm\">\n\t\t\t\t{{ t('nextcloud-vue', 'Link report') }}\n\t\t\t</NcButton>\n\t\t</template>\n\t</NcDialog>\n</template>\n\n<script>\n/**\n * SPDX-License-Identifier: EUPL-1.2\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n *\n * CnAnalyticsReportPicker — pick an existing Analytics report. Emits\n * `link` with the chosen report id.\n *\n * @see ADR-019 (pluggable integrations) and ADR-022 (sidebar tabs)\n */\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcButton, NcDialog, NcEmptyContent, NcLoadingIcon, NcNoteCard, NcTextField } from '@nextcloud/vue'\nimport ChartBar from 'vue-material-design-icons/ChartBar.vue'\nimport { buildHeaders, prefixUrl } from '../../utils/index.js'\n\nexport default {\n\tname: 'CnAnalyticsReportPicker',\n\n\tcomponents: { NcButton, NcDialog, NcEmptyContent, NcLoadingIcon, NcNoteCard, NcTextField, ChartBar },\n\n\tprops: {\n\t\t/** Base API URL for OR. */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\t\t/** Pre-translated dialog title. */\n\t\tdialogTitle: { type: String, default: () => t('nextcloud-vue', 'Link an existing report') },\n\t},\n\n\temits: ['close', 'link'],\n\n\tdata() {\n\t\treturn {\n\t\t\tloading: false,\n\t\t\terror: '',\n\t\t\treports: [],\n\t\t\tsearch: '',\n\t\t\tselectedReportId: null,\n\t\t\tsearchTimer: null,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Client-side filter on top of the server-side `?search=`\n\t\t * payload — so the user sees instant feedback even between\n\t\t * debounce ticks.\n\t\t *\n\t\t * @return {Array} The filtered report rows.\n\t\t */\n\t\tvisibleReports() {\n\t\t\tconst term = this.search.trim().toLowerCase()\n\t\t\tif (term === '') {\n\t\t\t\treturn this.reports\n\t\t\t}\n\t\t\treturn this.reports.filter((report) => (report.name || '').toLowerCase().includes(term))\n\t\t},\n\t},\n\n\tmounted() {\n\t\tthis.fetchReports()\n\t},\n\n\tbeforeUnmount() {\n\t\tif (this.searchTimer) {\n\t\t\tclearTimeout(this.searchTimer)\n\t\t}\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/**\n\t\t * Dismiss the dialog.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tonClose() {\n\t\t\t/**\n\t\t\t * @event close Emitted when the dialog should be closed (cancel or close button).\n\t\t\t */\n\t\t\tthis.$emit('close')\n\t\t},\n\n\t\tasync fetchReports(searchTerm = '') {\n\t\t\tthis.loading = true\n\t\t\tthis.error = ''\n\t\t\ttry {\n\t\t\t\tconst query = searchTerm ? `?search=${encodeURIComponent(searchTerm)}` : ''\n\t\t\t\tconst response = await fetch(prefixUrl(`${this.apiBase}/integrations/analytics/available${query}`), {\n\t\t\t\t\theaders: buildHeaders(),\n\t\t\t\t})\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tconst data = await response.json()\n\t\t\t\t\tthis.reports = data.results || []\n\t\t\t\t} else if (response.status === 501) {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'NC Analytics is not installed.')\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load reports.')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnAnalyticsReportPicker] fetch reports failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load reports.')\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\tonSearch(value) {\n\t\t\t// Debounce server-side filter; client-side filter is live.\n\t\t\tthis.search = value\n\t\t\tif (this.searchTimer) {\n\t\t\t\tclearTimeout(this.searchTimer)\n\t\t\t}\n\t\t\tthis.searchTimer = setTimeout(() => {\n\t\t\t\tthis.fetchReports(this.search.trim())\n\t\t\t}, 300)\n\t\t},\n\n\t\tpickReport(report) {\n\t\t\tthis.selectedReportId = report.id\n\t\t},\n\n\t\t/**\n\t\t * Resolve a translatable label for the report type code. Falls\n\t\t * back to \"Report\" when the code is unknown (NC Analytics 6.x\n\t\t * datasource types).\n\t\t *\n\t\t * @param {object} report Picker row.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\treportTypeLabel(report) {\n\t\t\tconst raw = report?.type\n\t\t\tif (raw === null || raw === undefined || raw === '') {\n\t\t\t\treturn t('nextcloud-vue', 'Report')\n\t\t\t}\n\t\t\tswitch (Number(raw)) {\n\t\t\t\tcase 0: return t('nextcloud-vue', 'Group')\n\t\t\t\tcase 1: return t('nextcloud-vue', 'File')\n\t\t\t\tcase 2: return t('nextcloud-vue', 'Database')\n\t\t\t\tcase 3: return t('nextcloud-vue', 'Git')\n\t\t\t\tcase 4: return t('nextcloud-vue', 'External')\n\t\t\t\tcase 6: return t('nextcloud-vue', 'JSON')\n\t\t\t\tdefault: return t('nextcloud-vue', 'Report')\n\t\t\t}\n\t\t},\n\n\t\tmodifiedLabel(report) {\n\t\t\tconst value = report?.modifiedAt || report?.modified || ''\n\t\t\tif (!value) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst num = Number(value)\n\t\t\t\tconst d = Number.isFinite(num) && num > 0 && String(value).length <= 12\n\t\t\t\t\t? new Date(num * 1000)\n\t\t\t\t\t: new Date(value)\n\t\t\t\tif (Number.isNaN(d.getTime())) {\n\t\t\t\t\treturn ''\n\t\t\t\t}\n\t\t\t\treturn d.toLocaleDateString(undefined, { dateStyle: 'medium' })\n\t\t\t} catch {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t},\n\n\t\tconfirm() {\n\t\t\tif (this.selectedReportId === null) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\t/**\n\t\t\t * @event link Emitted when the user confirms the selection. Payload: `{ reportId }`.\n\t\t\t */\n\t\t\tthis.$emit('link', { reportId: this.selectedReportId })\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-analytics-report-picker {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 12px;\n\tpadding: 4px 0;\n\tmin-height: 240px;\n}\n\n.cn-analytics-report-picker__error {\n\tmargin: 4px 0;\n}\n\n.cn-analytics-report-picker__search {\n\twidth: 100%;\n}\n\n.cn-analytics-report-picker__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n}\n\n.cn-analytics-report-picker__row {\n\tborder-radius: var(--border-radius);\n}\n\n.cn-analytics-report-picker__row-button {\n\tdisplay: flex;\n\talign-items: flex-start;\n\tgap: 10px;\n\twidth: 100%;\n\tpadding: 8px 10px;\n\tbackground: var(--color-background-hover);\n\tborder: 2px solid transparent;\n\tborder-radius: var(--border-radius);\n\tcolor: var(--color-main-text);\n\tcursor: pointer;\n\ttext-align: left;\n}\n\n.cn-analytics-report-picker__row-button:hover {\n\tbackground: var(--color-background-dark, var(--color-background-hover));\n}\n\n.cn-analytics-report-picker__row--selected .cn-analytics-report-picker__row-button {\n\tborder-color: var(--color-primary-element);\n}\n\n.cn-analytics-report-picker__row-icon {\n\tflex-shrink: 0;\n\tcolor: var(--color-primary-element);\n\tpadding-top: 2px;\n}\n\n.cn-analytics-report-picker__row-main {\n\tflex: 1;\n\tmin-width: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 3px;\n}\n\n.cn-analytics-report-picker__name {\n\tfont-size: 13px;\n\tfont-weight: 500;\n\twhite-space: nowrap;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n.cn-analytics-report-picker__sub {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tgap: 6px;\n\tfont-size: 11px;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-analytics-report-picker__badge {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tpadding: 1px 6px;\n\tborder-radius: 8px;\n\tbackground: var(--color-background-darker, var(--color-background-dark));\n\tcolor: var(--color-main-text);\n\tfont-size: 10px;\n\tfont-weight: 600;\n\tletter-spacing: 0.04em;\n\ttext-transform: uppercase;\n}\n\n.cn-analytics-report-picker__when {\n\twhite-space: nowrap;\n}\n</style>\n"],"names":["_createBlock","_createElementVNode","_createVNode","_openBlock","_createElementBlock"],"mappings":";;AA8DO,MAAA,UAAA,GAAA,EAAA,KAAI,EAAE,4BAA4B,GAAA;;;AAiB9B,EAAA,KAAA,EAAA,kCAAA;;;AAOA,MAAA,UAAA,GAAA,EAAA,KAAA,EAAA,sCAAA,GAAA;;;;;;;;;;;;;;;;;;;uBA7BNA;QACE,EAAE,MAAW,CAAA,WAAA;AACb,IAAA,IAAA,EAAA,QAAQ;AACT,IAAA,aAAM,EAAA,4BAAA;aACF,EAAE,QAAO,CAAA,OAAA;;;;;;;;;;;;;;;;;;;;qBACZ;AAAA,MAAAC,kBAAA,QAAA;SACc,KAAK,CAAA,KAAA;0BAAnBD,WAES,CAAA,qBAAA,EAAA;;AAFY,cAAA,IAAI,EAAC,OAAO;AAAC,cAAA,KAAK,EAAC,mCAAK;;+BAC5C,MAAO;gDAAP,KAAI,CAAA,KAAA,CAAA,EAAA,CAAA,YAAA;;;;;QAGJE,WAKD,CAAA,sBAAA,EAAA;sBAJM,KAAA,CAAA,MAAA;;kDAAA,KAAA,CAAA,MAAA,IAAA,MAAA,CAAA,CAAA;YAIN,QAAA,CAAA,QAAA;;AAHD,UAAA,KAAI,EAAE,QAAA,CAAA,CAAA,CAAA,eAAA,EAAA,gBAAA,CAAA;AACP,UAAA,WAAQ,EAAA,QAAA,CAAA,CAAA,CAAA,eAAA,EAAA,iBAAA,CAAA;AACT,UAAA,KAAI,EAAA,oCAAA;;;;AAKO,YAAA,CAAA,QAAA,CAAA,cAAc,CAAA,MAAA,KAAA,CAAA;4BAD1BF,WAGU,CAAA,yBAAA,EAAA;;AADR,gBAAA,IAAI,EAAE,QAAC,CAAA,CAAA,CAAA,eAAA,EAAA,sBAAA,CAAA;AACP,gBAAA,WAAQ,EAAA,QAAA,CAAA,CAAA,CAAA,eAAA,EAAA,kEAAA,CAAA;;AACV,eAAAG,SAAA,EAAA,EAAAC,kBAAA,OAAA;kCACCA,8CACkB,QAAc,CAAA,cAAA,EAAA,CAAxB,MAAM,KAAA;uCADdA;oBAEE,GAAG,EAAE,MAAM,CAAC,EAAE;AACf,oBAAA,KAAK,kBAAC,iCAA+B,EAAA,EAAA,2CAAA,EAC7B,sBAAA,KAAA,MAAA,CAAA,EAAA,EAAA,CAAA,CAAA;;oBACRH;AAAO,sBAAA,IAAA,EAAA,QAAA;AAAA,sBAAA,KAAA,EAAA,wCAAA;sBAAA,OAAA,EAAA,MAAA,KAAA,QAAA,CAAA,UAAA,CAAA,MAAA,CAAA,CAAA;;AACN,sBAAAA,kBAAA,SAAA;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"CnAnalyticsReportPicker.vue3.js","sources":["../../../../src/components/CnAnalyticsReportPicker/CnAnalyticsReportPicker.vue?vue&type=template&id=0ed141ec&lang.js"],"sourcesContent":["<!--\n CnAnalyticsReportPicker — modal for picking an existing NC Analytics\n report to link to the parent OR object.\n\n Flow:\n 1. Load reports via GET /api/integrations/analytics/available\n 2. Filter client-side via a search input (debounced; the same query\n is forwarded as `?search=` for server-side filtering)\n 3. Single-select a report row (title + report-type badge + modified\n date)\n 4. Confirm → emit `link` with `{ reportId }`\n\n All API calls are wrapped in best-effort try/catch so a transient\n Analytics failure surfaces a user-visible inline error rather than a\n modal crash. The modal stays open across errors so the user can retry\n without losing context.\n\n ADR-004: modal lives in its own .vue file under\n `src/components/CnAnalyticsReportPicker/` (NcDialog-based; matches the\n photos/deck/poll/talk picker pattern).\n\n ADR-019: drives the `analytics` integration leaf's \"link existing\"\n surface; emits `link` so the parent (CnAnalyticsTab) can POST the\n selection to the OR endpoint.\n\n SPDX-License-Identifier: EUPL-1.2\n SPDX-FileCopyrightText: 2026 Conduction B.V.\n-->\n<template>\n\t<NcDialog\n\t\t:name=\"dialogTitle\"\n\t\tsize=\"normal\"\n\t\tdata-testid=\"cn-analytics-report-picker\"\n\t\t@closing=\"onClose\">\n\t\t<div class=\"cn-analytics-report-picker\">\n\t\t\t<NcNoteCard v-if=\"error\" type=\"error\" class=\"cn-analytics-report-picker__error\">\n\t\t\t\t{{ error }}\n\t\t\t</NcNoteCard>\n\n\t\t\t<NcTextField\n\t\t\t\tv-model=\"search\"\n\t\t\t\t:label=\"t('nextcloud-vue', 'Search reports')\"\n\t\t\t\t:placeholder=\"t('nextcloud-vue', 'Type to filter…')\"\n\t\t\t\tclass=\"cn-analytics-report-picker__search\"\n\t\t\t\t@update:modelValue=\"onSearch\" />\n\n\t\t\t<NcLoadingIcon v-if=\"loading\" />\n\t\t\t<NcEmptyContent\n\t\t\t\tv-else-if=\"visibleReports.length === 0\"\n\t\t\t\t:name=\"t('nextcloud-vue', 'No reports available')\"\n\t\t\t\t:description=\"t('nextcloud-vue', 'Create a report in NC Analytics first, or use the create dialog.')\" />\n\t\t\t<ul v-else class=\"cn-analytics-report-picker__list\">\n\t\t\t\t<li\n\t\t\t\t\tv-for=\"report in visibleReports\"\n\t\t\t\t\t:key=\"report.id\"\n\t\t\t\t\tclass=\"cn-analytics-report-picker__row\"\n\t\t\t\t\t:class=\"{ 'cn-analytics-report-picker__row--selected': selectedReportId === report.id }\">\n\t\t\t\t\t<button type=\"button\" class=\"cn-analytics-report-picker__row-button\" @click=\"pickReport(report)\">\n\t\t\t\t\t\t<span class=\"cn-analytics-report-picker__row-icon\">\n\t\t\t\t\t\t\t<ChartBar :size=\"20\" />\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<span class=\"cn-analytics-report-picker__row-main\">\n\t\t\t\t\t\t\t<span class=\"cn-analytics-report-picker__name\">{{ report.name }}</span>\n\t\t\t\t\t\t\t<span class=\"cn-analytics-report-picker__sub\">\n\t\t\t\t\t\t\t\t<span class=\"cn-analytics-report-picker__badge\">{{ reportTypeLabel(report) }}</span>\n\t\t\t\t\t\t\t\t<span v-if=\"modifiedLabel(report)\" class=\"cn-analytics-report-picker__when\">\n\t\t\t\t\t\t\t\t\t· {{ modifiedLabel(report) }}\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</span>\n\t\t\t\t\t</button>\n\t\t\t\t</li>\n\t\t\t</ul>\n\t\t</div>\n\n\t\t<template #actions>\n\t\t\t<NcButton @click=\"onClose\">\n\t\t\t\t{{ t('nextcloud-vue', 'Cancel') }}\n\t\t\t</NcButton>\n\t\t\t<NcButton\n\t\t\t\tvariant=\"primary\"\n\t\t\t\t:disabled=\"selectedReportId === null\"\n\t\t\t\t@click=\"confirm\">\n\t\t\t\t{{ t('nextcloud-vue', 'Link report') }}\n\t\t\t</NcButton>\n\t\t</template>\n\t</NcDialog>\n</template>\n\n<script>\n/**\n * SPDX-License-Identifier: EUPL-1.2\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n *\n * CnAnalyticsReportPicker — pick an existing Analytics report. Emits\n * `link` with the chosen report id.\n *\n * @see ADR-019 (pluggable integrations) and ADR-022 (sidebar tabs)\n */\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcButton, NcDialog, NcEmptyContent, NcLoadingIcon, NcNoteCard, NcTextField } from '@nextcloud/vue'\nimport ChartBar from 'vue-material-design-icons/ChartBar.vue'\nimport { buildHeaders, prefixUrl } from '../../utils/index.js'\nimport { userLocale } from '../../utils/userLocale.js'\n\nexport default {\n\tname: 'CnAnalyticsReportPicker',\n\n\tcomponents: { NcButton, NcDialog, NcEmptyContent, NcLoadingIcon, NcNoteCard, NcTextField, ChartBar },\n\n\tprops: {\n\t\t/** Base API URL for OR. */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\t\t/** Pre-translated dialog title. */\n\t\tdialogTitle: { type: String, default: () => t('nextcloud-vue', 'Link an existing report') },\n\t},\n\n\temits: ['close', 'link'],\n\n\tdata() {\n\t\treturn {\n\t\t\tloading: false,\n\t\t\terror: '',\n\t\t\treports: [],\n\t\t\tsearch: '',\n\t\t\tselectedReportId: null,\n\t\t\tsearchTimer: null,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Client-side filter on top of the server-side `?search=`\n\t\t * payload — so the user sees instant feedback even between\n\t\t * debounce ticks.\n\t\t *\n\t\t * @return {Array} The filtered report rows.\n\t\t */\n\t\tvisibleReports() {\n\t\t\tconst term = this.search.trim().toLowerCase()\n\t\t\tif (term === '') {\n\t\t\t\treturn this.reports\n\t\t\t}\n\t\t\treturn this.reports.filter((report) => (report.name || '').toLowerCase().includes(term))\n\t\t},\n\t},\n\n\tmounted() {\n\t\tthis.fetchReports()\n\t},\n\n\tbeforeUnmount() {\n\t\tif (this.searchTimer) {\n\t\t\tclearTimeout(this.searchTimer)\n\t\t}\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/**\n\t\t * Dismiss the dialog.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tonClose() {\n\t\t\t/**\n\t\t\t * @event close Emitted when the dialog should be closed (cancel or close button).\n\t\t\t */\n\t\t\tthis.$emit('close')\n\t\t},\n\n\t\tasync fetchReports(searchTerm = '') {\n\t\t\tthis.loading = true\n\t\t\tthis.error = ''\n\t\t\ttry {\n\t\t\t\tconst query = searchTerm ? `?search=${encodeURIComponent(searchTerm)}` : ''\n\t\t\t\tconst response = await fetch(prefixUrl(`${this.apiBase}/integrations/analytics/available${query}`), {\n\t\t\t\t\theaders: buildHeaders(),\n\t\t\t\t})\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tconst data = await response.json()\n\t\t\t\t\tthis.reports = data.results || []\n\t\t\t\t} else if (response.status === 501) {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'NC Analytics is not installed.')\n\t\t\t\t} else {\n\t\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load reports.')\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnAnalyticsReportPicker] fetch reports failed', err)\n\t\t\t\tthis.error = t('nextcloud-vue', 'Could not load reports.')\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\tonSearch(value) {\n\t\t\t// Debounce server-side filter; client-side filter is live.\n\t\t\tthis.search = value\n\t\t\tif (this.searchTimer) {\n\t\t\t\tclearTimeout(this.searchTimer)\n\t\t\t}\n\t\t\tthis.searchTimer = setTimeout(() => {\n\t\t\t\tthis.fetchReports(this.search.trim())\n\t\t\t}, 300)\n\t\t},\n\n\t\tpickReport(report) {\n\t\t\tthis.selectedReportId = report.id\n\t\t},\n\n\t\t/**\n\t\t * Resolve a translatable label for the report type code. Falls\n\t\t * back to \"Report\" when the code is unknown (NC Analytics 6.x\n\t\t * datasource types).\n\t\t *\n\t\t * @param {object} report Picker row.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\treportTypeLabel(report) {\n\t\t\tconst raw = report?.type\n\t\t\tif (raw === null || raw === undefined || raw === '') {\n\t\t\t\treturn t('nextcloud-vue', 'Report')\n\t\t\t}\n\t\t\tswitch (Number(raw)) {\n\t\t\t\tcase 0: return t('nextcloud-vue', 'Group')\n\t\t\t\tcase 1: return t('nextcloud-vue', 'File')\n\t\t\t\tcase 2: return t('nextcloud-vue', 'Database')\n\t\t\t\tcase 3: return t('nextcloud-vue', 'Git')\n\t\t\t\tcase 4: return t('nextcloud-vue', 'External')\n\t\t\t\tcase 6: return t('nextcloud-vue', 'JSON')\n\t\t\t\tdefault: return t('nextcloud-vue', 'Report')\n\t\t\t}\n\t\t},\n\n\t\tmodifiedLabel(report) {\n\t\t\tconst value = report?.modifiedAt || report?.modified || ''\n\t\t\tif (!value) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst num = Number(value)\n\t\t\t\tconst d = Number.isFinite(num) && num > 0 && String(value).length <= 12\n\t\t\t\t\t? new Date(num * 1000)\n\t\t\t\t\t: new Date(value)\n\t\t\t\tif (Number.isNaN(d.getTime())) {\n\t\t\t\t\treturn ''\n\t\t\t\t}\n\t\t\t\treturn d.toLocaleDateString(userLocale(), { dateStyle: 'medium' })\n\t\t\t} catch {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t},\n\n\t\tconfirm() {\n\t\t\tif (this.selectedReportId === null) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\t/**\n\t\t\t * @event link Emitted when the user confirms the selection. Payload: `{ reportId }`.\n\t\t\t */\n\t\t\tthis.$emit('link', { reportId: this.selectedReportId })\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-analytics-report-picker {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 12px;\n\tpadding: 4px 0;\n\tmin-height: 240px;\n}\n\n.cn-analytics-report-picker__error {\n\tmargin: 4px 0;\n}\n\n.cn-analytics-report-picker__search {\n\twidth: 100%;\n}\n\n.cn-analytics-report-picker__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n}\n\n.cn-analytics-report-picker__row {\n\tborder-radius: var(--border-radius);\n}\n\n.cn-analytics-report-picker__row-button {\n\tdisplay: flex;\n\talign-items: flex-start;\n\tgap: 10px;\n\twidth: 100%;\n\tpadding: 8px 10px;\n\tbackground: var(--color-background-hover);\n\tborder: 2px solid transparent;\n\tborder-radius: var(--border-radius);\n\tcolor: var(--color-main-text);\n\tcursor: pointer;\n\ttext-align: left;\n}\n\n.cn-analytics-report-picker__row-button:hover {\n\tbackground: var(--color-background-dark, var(--color-background-hover));\n}\n\n.cn-analytics-report-picker__row--selected .cn-analytics-report-picker__row-button {\n\tborder-color: var(--color-primary-element);\n}\n\n.cn-analytics-report-picker__row-icon {\n\tflex-shrink: 0;\n\tcolor: var(--color-primary-element);\n\tpadding-top: 2px;\n}\n\n.cn-analytics-report-picker__row-main {\n\tflex: 1;\n\tmin-width: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 3px;\n}\n\n.cn-analytics-report-picker__name {\n\tfont-size: 13px;\n\tfont-weight: 500;\n\twhite-space: nowrap;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n.cn-analytics-report-picker__sub {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tgap: 6px;\n\tfont-size: 11px;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-analytics-report-picker__badge {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tpadding: 1px 6px;\n\tborder-radius: 8px;\n\tbackground: var(--color-background-darker, var(--color-background-dark));\n\tcolor: var(--color-main-text);\n\tfont-size: 10px;\n\tfont-weight: 600;\n\tletter-spacing: 0.04em;\n\ttext-transform: uppercase;\n}\n\n.cn-analytics-report-picker__when {\n\twhite-space: nowrap;\n}\n</style>\n"],"names":["_createBlock","_createElementVNode","_createVNode","_openBlock","_createElementBlock"],"mappings":";;AA8DO,MAAA,UAAA,GAAA,EAAA,KAAI,EAAE,4BAA4B,GAAA;;;AAiB9B,EAAA,KAAA,EAAA,kCAAA;;;AAOA,MAAA,UAAA,GAAA,EAAA,KAAA,EAAA,sCAAA,GAAA;;;;;;;;;;;;;;;;;;;uBA7BNA;QACE,EAAE,MAAW,CAAA,WAAA;AACb,IAAA,IAAA,EAAA,QAAQ;AACT,IAAA,aAAM,EAAA,4BAAA;aACF,EAAE,QAAO,CAAA,OAAA;;;;;;;;;;;;;;;;;;;;qBACZ;AAAA,MAAAC,kBAAA,QAAA;SACc,KAAK,CAAA,KAAA;0BAAnBD,WAES,CAAA,qBAAA,EAAA;;AAFY,cAAA,IAAI,EAAC,OAAO;AAAC,cAAA,KAAK,EAAC,mCAAK;;+BAC5C,MAAO;gDAAP,KAAI,CAAA,KAAA,CAAA,EAAA,CAAA,YAAA;;;;;QAGJE,WAKD,CAAA,sBAAA,EAAA;sBAJM,KAAA,CAAA,MAAA;;kDAAA,KAAA,CAAA,MAAA,IAAA,MAAA,CAAA,CAAA;YAIN,QAAA,CAAA,QAAA;;AAHD,UAAA,KAAI,EAAE,QAAA,CAAA,CAAA,CAAA,eAAA,EAAA,gBAAA,CAAA;AACP,UAAA,WAAQ,EAAA,QAAA,CAAA,CAAA,CAAA,eAAA,EAAA,iBAAA,CAAA;AACT,UAAA,KAAI,EAAA,oCAAA;;;;AAKO,YAAA,CAAA,QAAA,CAAA,cAAc,CAAA,MAAA,KAAA,CAAA;4BAD1BF,WAGU,CAAA,yBAAA,EAAA;;AADR,gBAAA,IAAI,EAAE,QAAC,CAAA,CAAA,CAAA,eAAA,EAAA,sBAAA,CAAA;AACP,gBAAA,WAAQ,EAAA,QAAA,CAAA,CAAA,CAAA,eAAA,EAAA,kEAAA,CAAA;;AACV,eAAAG,SAAA,EAAA,EAAAC,kBAAA,OAAA;kCACCA,8CACkB,QAAc,CAAA,cAAA,EAAA,CAAxB,MAAM,KAAA;uCADdA;oBAEE,GAAG,EAAE,MAAM,CAAC,EAAE;AACf,oBAAA,KAAK,kBAAC,iCAA+B,EAAA,EAAA,2CAAA,EAC7B,sBAAA,KAAA,MAAA,CAAA,EAAA,EAAA,CAAA,CAAA;;oBACRH;AAAO,sBAAA,IAAA,EAAA,QAAA;AAAA,sBAAA,KAAA,EAAA,wCAAA;sBAAA,OAAA,EAAA,MAAA,KAAA,QAAA,CAAA,UAAA,CAAA,MAAA,CAAA,CAAA;;AACN,sBAAAA,kBAAA,SAAA;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -2,7 +2,7 @@ import script from './CnAuditTrailCard.vue2.js';
2
2
  import { render } from './CnAuditTrailCard.vue3.js';
3
3
 
4
4
  script.render = render;
5
- script.__scopeId = "data-v-280a5ace";
5
+ script.__scopeId = "data-v-3abac146";
6
6
  const __sfc_main = script;
7
7
 
8
8
  export { __sfc_main as default };
@@ -3,6 +3,7 @@ import { NcLoadingIcon } from '@nextcloud/vue';
3
3
  import { markRaw } from 'vue';
4
4
  import History from 'vue-material-design-icons/History.vue';
5
5
  import __sfc_main from '../CnDetailCard/CnDetailCard.vue.js';
6
+ import { userLocale } from '../../utils/userLocale.js';
6
7
  import { prefixUrl, buildHeaders } from '../../utils/headers.js';
7
8
 
8
9
  /**
@@ -169,7 +170,7 @@ var script = {
169
170
  if (Number.isNaN(d.getTime()) === true) {
170
171
  return String(raw)
171
172
  }
172
- return d.toLocaleString()
173
+ return d.toLocaleString(userLocale())
173
174
  },
174
175
  },
175
176
  };
@@ -1 +1 @@
1
- {"version":3,"file":"CnAuditTrailCard.vue2.js","sources":["../../../../src/components/CnAuditTrailCard/CnAuditTrailCard.vue"],"sourcesContent":["<!--\n CnAuditTrailCard — compact audit-trail widget for the integration\n registry.\n\n Surface-aware shell around the `audit-trail` integration: fetches\n the object's recent audit-trail entries (query-time storage strategy\n per AD-22) and renders them in a CnDetailCard.\n\n Per umbrella change `pluggable-integration-registry` AD-19 (surface\n fallback), one component handles all surfaces — `surface` is\n forwarded so the component can branch internally if desired.\n-->\n<template>\n\t<CnDetailCard\n\t\t:title=\"resolvedTitle\"\n\t\t:icon=\"History\"\n\t\t:collapsible=\"collapsible\"\n\t\t:data-surface=\"surface\">\n\t\t<NcLoadingIcon v-if=\"loading\" />\n\t\t<div v-else-if=\"entries.length === 0\" class=\"cn-audit-card__empty\">\n\t\t\t{{ noEntriesLabel }}\n\t\t</div>\n\t\t<ul v-else class=\"cn-audit-card__list\">\n\t\t\t<li\n\t\t\t\tv-for=\"entry in displayedEntries\"\n\t\t\t\t:key=\"entry.id\"\n\t\t\t\tclass=\"cn-audit-card__row\">\n\t\t\t\t<div class=\"cn-audit-card__row-head\">\n\t\t\t\t\t<strong class=\"cn-audit-card__action\">{{ entry.action || entry.event || actionLabel }}</strong>\n\t\t\t\t\t<span class=\"cn-audit-card__when\">{{ formatWhen(entry) }}</span>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"cn-audit-card__row-body\">\n\t\t\t\t\t<span class=\"cn-audit-card__actor\">{{ formatActor(entry) }}</span>\n\t\t\t\t</div>\n\t\t\t</li>\n\t\t</ul>\n\t\t<template v-if=\"entries.length > maxDisplay\" #footer>\n\t\t\t<button class=\"cn-audit-card__show-all\" @click=\"$emit('show-all')\">\n\t\t\t\t{{ showAllLabel }} ({{ entries.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 History from 'vue-material-design-icons/History.vue'\nimport CnDetailCard from '../CnDetailCard/CnDetailCard.vue'\nimport { buildHeaders, prefixUrl } from '../../utils/index.js'\n\n/**\n * CnAuditTrailCard — compact audit-trail widget rendered by the\n * integration registry on dashboard and detail surfaces.\n *\n * Basic usage\n * ```vue\n * <CnAuditTrailCard\n * :register=\"registerId\"\n * :schema=\"schemaId\"\n * :object-id=\"objectId\"\n * surface=\"detail-page\" />\n * ```\n */\nexport default {\n\tname: 'CnAuditTrailCard',\n\n\tcomponents: { CnDetailCard, NcLoadingIcon },\n\n\tprops: {\n\t\t/** OpenRegister register id (slug or uuid). Optional for `scope: \"app\"`. */\n\t\tregister: { type: String, default: '' },\n\t\t/** OpenRegister schema id (slug or uuid). Optional for `scope: \"app\"`. */\n\t\tschema: { type: String, default: '' },\n\t\t/** Parent object id. Required for `scope: \"object\"` (the default); unused for `scope: \"app\"`. */\n\t\tobjectId: { type: String, default: '' },\n\t\t/**\n\t\t * What the card lists. `object` (the default) is one object's trail\n\t\t * and needs `register`, `schema` and `objectId`. `app` is the\n\t\t * app-wide feed: the entries of every object the caller may read\n\t\t * (OpenRegister's `/audit-trails/readable`), narrowed to `register`\n\t\t * and `schema` when those are set.\n\t\t *\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-the-audit-trail-widget-reads-the-app-wide-feed\n\t\t */\n\t\tscope: {\n\t\t\ttype: String,\n\t\t\tdefault: 'object',\n\t\t\tvalidator: (value) => ['object', 'app'].includes(value),\n\t\t},\n\n\t\t/** Rendering surface — passed for AD-19 surface fallback consumers. */\n\t\tsurface: {\n\t\t\ttype: String,\n\t\t\tdefault: 'detail-page',\n\t\t\tvalidator: (value) => ['user-dashboard', 'app-dashboard', 'detail-page', 'single-entity'].includes(value),\n\t\t},\n\n\t\t/** Base API URL. */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\t\t/** Maximum rows to render. */\n\t\tmaxDisplay: { type: Number, default: 5 },\n\t\t/** Whether the card collapses. */\n\t\tcollapsible: { type: Boolean, default: false },\n\t\t/** Override the card title (defaults to the translated label). */\n\t\ttitle: { type: String, default: '' },\n\t\t/** Pre-translated empty label. */\n\t\tnoEntriesLabel: { type: String, default: () => t('nextcloud-vue', 'No audit entries yet') },\n\t\t/** Pre-translated overflow label. */\n\t\tshowAllLabel: { type: String, default: () => t('nextcloud-vue', 'Show all') },\n\t\t/** Pre-translated fallback action label. */\n\t\tactionLabel: { type: String, default: () => t('nextcloud-vue', 'Change') },\n\t},\n\n\temits: ['show-all'],\n\n\tdata() {\n\t\treturn {\n\t\t\tHistory: markRaw(History),\n\t\t\tentries: [],\n\t\t\tloading: false,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\tresolvedTitle() {\n\t\t\treturn this.title || t('nextcloud-vue', 'Audit trail')\n\t\t},\n\n\t\tdisplayedEntries() {\n\t\t\treturn this.entries.slice(0, this.maxDisplay)\n\t\t},\n\n\t\t/**\n\t\t * The path the card reads (prefixed at fetch time), or '' when it has nothing to read: the\n\t\t * object's own trail, or the app-wide readable feed narrowed to the\n\t\t * register and schema when set.\n\t\t *\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-the-audit-trail-widget-reads-the-app-wide-feed\n\t\t * @return {string}\n\t\t */\n\t\tfetchUrl() {\n\t\t\tconst params = new URLSearchParams({ limit: String(this.maxDisplay) })\n\t\t\tif (this.scope === 'app') {\n\t\t\t\tif (this.register) {\n\t\t\t\t\tparams.set('register', this.register)\n\t\t\t\t}\n\t\t\t\tif (this.schema) {\n\t\t\t\t\tparams.set('schema', this.schema)\n\t\t\t\t}\n\t\t\t\treturn `${this.apiBase}/audit-trails/readable?${params.toString()}`\n\t\t\t}\n\t\t\tif (!this.register || !this.schema || !this.objectId) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn `${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/audit-trails?${params.toString()}`\n\t\t},\n\t},\n\n\twatch: {\n\t\tfetchUrl: {\n\t\t\timmediate: true,\n\t\t\thandler(url) {\n\t\t\t\tif (url) {\n\t\t\t\t\tthis.fetchEntries()\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Read the entries from `fetchUrl`. The readable feed answers\n\t\t * `{ rows, nextCursor }`, the object trail `{ results }`.\n\t\t *\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-the-audit-trail-widget-reads-the-app-wide-feed\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync fetchEntries() {\n\t\t\tconst url = this.fetchUrl\n\t\t\tif (!url) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\ttry {\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\tconst list = data.results || data.rows || data\n\t\t\t\t\tthis.entries = Array.isArray(list) ? list : []\n\t\t\t\t} else {\n\t\t\t\t\tthis.entries = []\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnAuditTrailCard] failed to fetch audit trail', err)\n\t\t\t\tthis.entries = []\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\tformatActor(entry) {\n\t\t\treturn entry.actorDisplayName || entry.actor || entry.userId || entry.user || ''\n\t\t},\n\n\t\tformatWhen(entry) {\n\t\t\tconst raw = entry.creationDateTime || entry.created || entry.timestamp\n\t\t\tif (raw === undefined || raw === null) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst d = new Date(raw)\n\t\t\tif (Number.isNaN(d.getTime()) === true) {\n\t\t\t\treturn String(raw)\n\t\t\t}\n\t\t\treturn d.toLocaleString()\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-audit-card__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n.cn-audit-card__row {\n\tpadding: 6px 0;\n\tborder-bottom: 1px solid var(--color-border);\n}\n\n.cn-audit-card__row:last-child {\n\tborder-bottom: none;\n}\n\n.cn-audit-card__row-head {\n\tdisplay: flex;\n\tjustify-content: space-between;\n\tgap: 8px;\n}\n\n.cn-audit-card__action {\n\tcolor: var(--color-main-text);\n}\n\n.cn-audit-card__when {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.85em;\n\twhite-space: nowrap;\n}\n\n.cn-audit-card__row-body {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n}\n\n.cn-audit-card__empty {\n\tcolor: var(--color-text-maxcontrast);\n\ttext-align: center;\n\tpadding: 12px 0;\n}\n\n.cn-audit-card__show-all {\n\tbackground: none;\n\tborder: none;\n\tcolor: var(--color-primary-element);\n\tcursor: pointer;\n\tpadding: 4px 0;\n\tfont: inherit;\n}\n\n.cn-audit-card__show-all:hover {\n\ttext-decoration: underline;\n}\n</style>\n"],"names":["CnDetailCard","t"],"mappings":"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"}
1
+ {"version":3,"file":"CnAuditTrailCard.vue2.js","sources":["../../../../src/components/CnAuditTrailCard/CnAuditTrailCard.vue"],"sourcesContent":["<!--\n CnAuditTrailCard — compact audit-trail widget for the integration\n registry.\n\n Surface-aware shell around the `audit-trail` integration: fetches\n the object's recent audit-trail entries (query-time storage strategy\n per AD-22) and renders them in a CnDetailCard.\n\n Per umbrella change `pluggable-integration-registry` AD-19 (surface\n fallback), one component handles all surfaces — `surface` is\n forwarded so the component can branch internally if desired.\n-->\n<template>\n\t<CnDetailCard\n\t\t:title=\"resolvedTitle\"\n\t\t:icon=\"History\"\n\t\t:collapsible=\"collapsible\"\n\t\t:data-surface=\"surface\">\n\t\t<NcLoadingIcon v-if=\"loading\" />\n\t\t<div v-else-if=\"entries.length === 0\" class=\"cn-audit-card__empty\">\n\t\t\t{{ noEntriesLabel }}\n\t\t</div>\n\t\t<ul v-else class=\"cn-audit-card__list\">\n\t\t\t<li\n\t\t\t\tv-for=\"entry in displayedEntries\"\n\t\t\t\t:key=\"entry.id\"\n\t\t\t\tclass=\"cn-audit-card__row\">\n\t\t\t\t<div class=\"cn-audit-card__row-head\">\n\t\t\t\t\t<strong class=\"cn-audit-card__action\">{{ entry.action || entry.event || actionLabel }}</strong>\n\t\t\t\t\t<span class=\"cn-audit-card__when\">{{ formatWhen(entry) }}</span>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"cn-audit-card__row-body\">\n\t\t\t\t\t<span class=\"cn-audit-card__actor\">{{ formatActor(entry) }}</span>\n\t\t\t\t</div>\n\t\t\t</li>\n\t\t</ul>\n\t\t<template v-if=\"entries.length > maxDisplay\" #footer>\n\t\t\t<button class=\"cn-audit-card__show-all\" @click=\"$emit('show-all')\">\n\t\t\t\t{{ showAllLabel }} ({{ entries.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 History from 'vue-material-design-icons/History.vue'\nimport CnDetailCard from '../CnDetailCard/CnDetailCard.vue'\nimport { buildHeaders, prefixUrl } from '../../utils/index.js'\nimport { userLocale } from '../../utils/userLocale.js'\n\n/**\n * CnAuditTrailCard — compact audit-trail widget rendered by the\n * integration registry on dashboard and detail surfaces.\n *\n * Basic usage\n * ```vue\n * <CnAuditTrailCard\n * :register=\"registerId\"\n * :schema=\"schemaId\"\n * :object-id=\"objectId\"\n * surface=\"detail-page\" />\n * ```\n */\nexport default {\n\tname: 'CnAuditTrailCard',\n\n\tcomponents: { CnDetailCard, NcLoadingIcon },\n\n\tprops: {\n\t\t/** OpenRegister register id (slug or uuid). Optional for `scope: \"app\"`. */\n\t\tregister: { type: String, default: '' },\n\t\t/** OpenRegister schema id (slug or uuid). Optional for `scope: \"app\"`. */\n\t\tschema: { type: String, default: '' },\n\t\t/** Parent object id. Required for `scope: \"object\"` (the default); unused for `scope: \"app\"`. */\n\t\tobjectId: { type: String, default: '' },\n\t\t/**\n\t\t * What the card lists. `object` (the default) is one object's trail\n\t\t * and needs `register`, `schema` and `objectId`. `app` is the\n\t\t * app-wide feed: the entries of every object the caller may read\n\t\t * (OpenRegister's `/audit-trails/readable`), narrowed to `register`\n\t\t * and `schema` when those are set.\n\t\t *\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-the-audit-trail-widget-reads-the-app-wide-feed\n\t\t */\n\t\tscope: {\n\t\t\ttype: String,\n\t\t\tdefault: 'object',\n\t\t\tvalidator: (value) => ['object', 'app'].includes(value),\n\t\t},\n\n\t\t/** Rendering surface — passed for AD-19 surface fallback consumers. */\n\t\tsurface: {\n\t\t\ttype: String,\n\t\t\tdefault: 'detail-page',\n\t\t\tvalidator: (value) => ['user-dashboard', 'app-dashboard', 'detail-page', 'single-entity'].includes(value),\n\t\t},\n\n\t\t/** Base API URL. */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\t\t/** Maximum rows to render. */\n\t\tmaxDisplay: { type: Number, default: 5 },\n\t\t/** Whether the card collapses. */\n\t\tcollapsible: { type: Boolean, default: false },\n\t\t/** Override the card title (defaults to the translated label). */\n\t\ttitle: { type: String, default: '' },\n\t\t/** Pre-translated empty label. */\n\t\tnoEntriesLabel: { type: String, default: () => t('nextcloud-vue', 'No audit entries yet') },\n\t\t/** Pre-translated overflow label. */\n\t\tshowAllLabel: { type: String, default: () => t('nextcloud-vue', 'Show all') },\n\t\t/** Pre-translated fallback action label. */\n\t\tactionLabel: { type: String, default: () => t('nextcloud-vue', 'Change') },\n\t},\n\n\temits: ['show-all'],\n\n\tdata() {\n\t\treturn {\n\t\t\tHistory: markRaw(History),\n\t\t\tentries: [],\n\t\t\tloading: false,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\tresolvedTitle() {\n\t\t\treturn this.title || t('nextcloud-vue', 'Audit trail')\n\t\t},\n\n\t\tdisplayedEntries() {\n\t\t\treturn this.entries.slice(0, this.maxDisplay)\n\t\t},\n\n\t\t/**\n\t\t * The path the card reads (prefixed at fetch time), or '' when it has nothing to read: the\n\t\t * object's own trail, or the app-wide readable feed narrowed to the\n\t\t * register and schema when set.\n\t\t *\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-the-audit-trail-widget-reads-the-app-wide-feed\n\t\t * @return {string}\n\t\t */\n\t\tfetchUrl() {\n\t\t\tconst params = new URLSearchParams({ limit: String(this.maxDisplay) })\n\t\t\tif (this.scope === 'app') {\n\t\t\t\tif (this.register) {\n\t\t\t\t\tparams.set('register', this.register)\n\t\t\t\t}\n\t\t\t\tif (this.schema) {\n\t\t\t\t\tparams.set('schema', this.schema)\n\t\t\t\t}\n\t\t\t\treturn `${this.apiBase}/audit-trails/readable?${params.toString()}`\n\t\t\t}\n\t\t\tif (!this.register || !this.schema || !this.objectId) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn `${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/audit-trails?${params.toString()}`\n\t\t},\n\t},\n\n\twatch: {\n\t\tfetchUrl: {\n\t\t\timmediate: true,\n\t\t\thandler(url) {\n\t\t\t\tif (url) {\n\t\t\t\t\tthis.fetchEntries()\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Read the entries from `fetchUrl`. The readable feed answers\n\t\t * `{ rows, nextCursor }`, the object trail `{ results }`.\n\t\t *\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-the-audit-trail-widget-reads-the-app-wide-feed\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync fetchEntries() {\n\t\t\tconst url = this.fetchUrl\n\t\t\tif (!url) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\ttry {\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\tconst list = data.results || data.rows || data\n\t\t\t\t\tthis.entries = Array.isArray(list) ? list : []\n\t\t\t\t} else {\n\t\t\t\t\tthis.entries = []\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnAuditTrailCard] failed to fetch audit trail', err)\n\t\t\t\tthis.entries = []\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\tformatActor(entry) {\n\t\t\treturn entry.actorDisplayName || entry.actor || entry.userId || entry.user || ''\n\t\t},\n\n\t\tformatWhen(entry) {\n\t\t\tconst raw = entry.creationDateTime || entry.created || entry.timestamp\n\t\t\tif (raw === undefined || raw === null) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst d = new Date(raw)\n\t\t\tif (Number.isNaN(d.getTime()) === true) {\n\t\t\t\treturn String(raw)\n\t\t\t}\n\t\t\treturn d.toLocaleString(userLocale())\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-audit-card__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n.cn-audit-card__row {\n\tpadding: 6px 0;\n\tborder-bottom: 1px solid var(--color-border);\n}\n\n.cn-audit-card__row:last-child {\n\tborder-bottom: none;\n}\n\n.cn-audit-card__row-head {\n\tdisplay: flex;\n\tjustify-content: space-between;\n\tgap: 8px;\n}\n\n.cn-audit-card__action {\n\tcolor: var(--color-main-text);\n}\n\n.cn-audit-card__when {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.85em;\n\twhite-space: nowrap;\n}\n\n.cn-audit-card__row-body {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n}\n\n.cn-audit-card__empty {\n\tcolor: var(--color-text-maxcontrast);\n\ttext-align: center;\n\tpadding: 12px 0;\n}\n\n.cn-audit-card__show-all {\n\tbackground: none;\n\tborder: none;\n\tcolor: var(--color-primary-element);\n\tcursor: pointer;\n\tpadding: 4px 0;\n\tfont: inherit;\n}\n\n.cn-audit-card__show-all:hover {\n\ttext-decoration: underline;\n}\n</style>\n"],"names":["CnDetailCard","t"],"mappings":";;;;;;;;AAqDA;;;AAGC;;AAEA;AACA;;AAEA;AACA;;AAEA;;AAED,aAAe;CACd,IAAI,EAAE,kBAAkB;;AAExB,CAAA,UAAU,EAAE,gBAAEA,UAAY,EAAE,eAAe;;AAE3C,CAAA,KAAK,EAAE;AACN;AACA,EAAA,QAAQ,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI;AACvC;AACA,EAAA,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI;AACrC;AACA,EAAA,QAAQ,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI;AACvC;;;;;;AAMC;AACA;;AAED,EAAA,KAAK,EAAE;GACN,IAAI,EAAE,MAAM;AACZ,GAAA,OAAO,EAAE,QAAQ;GACjB,SAAS,EAAE,CAAC,KAAK,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC;GACvD;;;AAGD,EAAA,OAAO,EAAE;GACR,IAAI,EAAE,MAAM;AACZ,GAAA,OAAO,EAAE,aAAa;GACtB,SAAS,EAAE,CAAC,KAAK,KAAK,CAAC,gBAAgB,EAAE,eAAe,EAAE,aAAa,EAAE,eAAe,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC;GACzG;;AAED;EACA,OAAO,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,wBAAuB,EAAG;;AAE5D,EAAA,UAAU,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG;;AAExC,EAAA,WAAW,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO;;AAE9C,EAAA,KAAK,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,EAAC,EAAG;;AAEpC,EAAA,cAAc,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAMC,SAAC,CAAC,eAAe,EAAE,sBAAsB,GAAG;AAC3F;AACA,EAAA,YAAY,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAMA,SAAC,CAAC,eAAe,EAAE,UAAU,GAAG;AAC7E;AACA,EAAA,WAAW,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAMA,SAAC,CAAC,eAAe,EAAE,QAAQ,GAAG;EAC1E;;AAED,CAAA,KAAK,EAAE,CAAC,UAAU,CAAC;;AAEnB,CAAA,IAAI,GAAG;AACN,EAAA,OAAO;GACN,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC;GACzB,OAAO,EAAE,EAAE;GACX,OAAO,EAAE,KAAK;AACf,GAAA;EACA;;AAED,CAAA,QAAQ,EAAE;AACT,EAAA,aAAa,GAAG;GACf,OAAO,IAAI,CAAC,KAAM,IAAGA,SAAC,CAAC,eAAe,EAAE,aAAa,CAAA;GACrD;;AAED,EAAA,gBAAgB,GAAG;AAClB,GAAA,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,UAAU,CAAA;GAC5C;;AAED;AACC;;;AAGA;AACA;AACA;;EAED,QAAQ,GAAG;AACV,GAAA,MAAM,MAAK,GAAI,IAAI,eAAe,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,UAAU,CAAA,EAAG,EAAA;GACrE,IAAI,IAAI,CAAC,KAAM,KAAI,KAAK,EAAE;AACzB,IAAA,IAAI,IAAI,CAAC,QAAQ,EAAE;AAClB,KAAA,MAAM,CAAC,GAAG,CAAC,UAAU,EAAE,IAAI,CAAC,QAAQ,EAAA;AACrC,KAAA;AACA,IAAA,IAAI,IAAI,CAAC,MAAM,EAAE;AAChB,KAAA,MAAM,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,CAAC,MAAM,EAAA;AACjC,KAAA;AACA,IAAA,OAAO,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,uBAAuB,EAAE,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAA;AACnE,IAAA;GACA,IAAI,CAAC,IAAI,CAAC,QAAO,IAAK,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,QAAQ,EAAE;AACrD,IAAA,OAAO,EAAC;AACT,IAAA;GACA,OAAO,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,EAAE,IAAI,CAAC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAA;GAClH;EACD;;AAED,CAAA,KAAK,EAAE;AACN,EAAA,QAAQ,EAAE;AACT,GAAA,SAAS,EAAE,IAAI;GACf,OAAO,CAAC,GAAG,EAAE;AACZ,IAAA,IAAI,GAAG,EAAE;AACR,KAAA,IAAI,CAAC,YAAY,GAAC;AACnB,KAAA;IACA;GACD;EACD;;AAED,CAAA,OAAO,EAAE;AACR;;;AAGC;AACA;;;AAGD,EAAA,MAAM,YAAY,GAAG;AACpB,GAAA,MAAM,MAAM,IAAI,CAAC,SAAO;AACxB,GAAA,IAAI,CAAC,GAAG,EAAE;IACT,MAAK;AACN,IAAA;AACA,GAAA,IAAI,CAAC,OAAM,GAAI,KAAG;AAClB,GAAA,IAAI;AACH,IAAA,MAAM,QAAO,GAAI,MAAM,KAAK,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,EAAE,OAAO,EAAE,YAAY,IAAI,EAAA;AACxE,IAAA,IAAI,QAAQ,CAAC,EAAE,EAAE;KAChB,MAAM,IAAK,GAAE,MAAM,QAAQ,CAAC,IAAI,GAAC;AACjC,KAAA,MAAM,IAAG,GAAI,IAAI,CAAC,WAAW,IAAI,CAAC,IAAK,IAAG,KAAG;AAC7C,KAAA,IAAI,CAAC,UAAU,KAAK,CAAC,OAAO,CAAC,IAAI,IAAI,IAAG,GAAI,GAAC;KAC5C,MAAK;AACN,KAAA,IAAI,CAAC,OAAQ,GAAE,GAAC;AACjB,KAAA;IACC,CAAA,OAAO,GAAG,EAAE;AACb;IACA,OAAO,CAAC,KAAK,CAAC,gDAAgD,EAAE,GAAG,EAAA;AACnE,IAAA,IAAI,CAAC,OAAQ,GAAE,GAAC;AACjB,IAAA,SAAU;AACT,IAAA,IAAI,CAAC,OAAQ,GAAE,MAAI;AACpB,IAAA;GACA;;AAED,EAAA,WAAW,CAAC,KAAK,EAAE;AAClB,GAAA,OAAO,KAAK,CAAC,gBAAe,IAAK,KAAK,CAAC,KAAI,IAAK,KAAK,CAAC,MAAK,IAAK,KAAK,CAAC,IAAK,IAAG,EAAC;GAC/E;;AAED,EAAA,UAAU,CAAC,KAAK,EAAE;AACjB,GAAA,MAAM,GAAI,GAAE,KAAK,CAAC,gBAAe,IAAK,KAAK,CAAC,OAAQ,IAAG,KAAK,CAAC,UAAQ;AACrE,GAAA,IAAI,GAAI,KAAI,SAAU,IAAG,GAAE,KAAM,IAAI,EAAE;AACtC,IAAA,OAAO,EAAC;AACT,IAAA;AACA,GAAA,MAAM,CAAA,GAAI,IAAI,IAAI,CAAC,GAAG,EAAA;AACtB,GAAA,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,EAAE,CAAE,KAAI,IAAI,EAAE;AACvC,IAAA,OAAO,MAAM,CAAC,GAAG,CAAA;AAClB,IAAA;AACA,GAAA,OAAO,CAAC,CAAC,cAAc,CAAC,UAAU,EAAE,CAAA;GACpC;EACD;AACF;;;;"}
@@ -1 +1 @@
1
- {"version":3,"file":"CnAuditTrailCard.vue3.js","sources":["../../../../src/components/CnAuditTrailCard/CnAuditTrailCard.vue?vue&type=template&id=280a5ace&lang.js"],"sourcesContent":["<!--\n CnAuditTrailCard — compact audit-trail widget for the integration\n registry.\n\n Surface-aware shell around the `audit-trail` integration: fetches\n the object's recent audit-trail entries (query-time storage strategy\n per AD-22) and renders them in a CnDetailCard.\n\n Per umbrella change `pluggable-integration-registry` AD-19 (surface\n fallback), one component handles all surfaces — `surface` is\n forwarded so the component can branch internally if desired.\n-->\n<template>\n\t<CnDetailCard\n\t\t:title=\"resolvedTitle\"\n\t\t:icon=\"History\"\n\t\t:collapsible=\"collapsible\"\n\t\t:data-surface=\"surface\">\n\t\t<NcLoadingIcon v-if=\"loading\" />\n\t\t<div v-else-if=\"entries.length === 0\" class=\"cn-audit-card__empty\">\n\t\t\t{{ noEntriesLabel }}\n\t\t</div>\n\t\t<ul v-else class=\"cn-audit-card__list\">\n\t\t\t<li\n\t\t\t\tv-for=\"entry in displayedEntries\"\n\t\t\t\t:key=\"entry.id\"\n\t\t\t\tclass=\"cn-audit-card__row\">\n\t\t\t\t<div class=\"cn-audit-card__row-head\">\n\t\t\t\t\t<strong class=\"cn-audit-card__action\">{{ entry.action || entry.event || actionLabel }}</strong>\n\t\t\t\t\t<span class=\"cn-audit-card__when\">{{ formatWhen(entry) }}</span>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"cn-audit-card__row-body\">\n\t\t\t\t\t<span class=\"cn-audit-card__actor\">{{ formatActor(entry) }}</span>\n\t\t\t\t</div>\n\t\t\t</li>\n\t\t</ul>\n\t\t<template v-if=\"entries.length > maxDisplay\" #footer>\n\t\t\t<button class=\"cn-audit-card__show-all\" @click=\"$emit('show-all')\">\n\t\t\t\t{{ showAllLabel }} ({{ entries.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 History from 'vue-material-design-icons/History.vue'\nimport CnDetailCard from '../CnDetailCard/CnDetailCard.vue'\nimport { buildHeaders, prefixUrl } from '../../utils/index.js'\n\n/**\n * CnAuditTrailCard — compact audit-trail widget rendered by the\n * integration registry on dashboard and detail surfaces.\n *\n * Basic usage\n * ```vue\n * <CnAuditTrailCard\n * :register=\"registerId\"\n * :schema=\"schemaId\"\n * :object-id=\"objectId\"\n * surface=\"detail-page\" />\n * ```\n */\nexport default {\n\tname: 'CnAuditTrailCard',\n\n\tcomponents: { CnDetailCard, NcLoadingIcon },\n\n\tprops: {\n\t\t/** OpenRegister register id (slug or uuid). Optional for `scope: \"app\"`. */\n\t\tregister: { type: String, default: '' },\n\t\t/** OpenRegister schema id (slug or uuid). Optional for `scope: \"app\"`. */\n\t\tschema: { type: String, default: '' },\n\t\t/** Parent object id. Required for `scope: \"object\"` (the default); unused for `scope: \"app\"`. */\n\t\tobjectId: { type: String, default: '' },\n\t\t/**\n\t\t * What the card lists. `object` (the default) is one object's trail\n\t\t * and needs `register`, `schema` and `objectId`. `app` is the\n\t\t * app-wide feed: the entries of every object the caller may read\n\t\t * (OpenRegister's `/audit-trails/readable`), narrowed to `register`\n\t\t * and `schema` when those are set.\n\t\t *\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-the-audit-trail-widget-reads-the-app-wide-feed\n\t\t */\n\t\tscope: {\n\t\t\ttype: String,\n\t\t\tdefault: 'object',\n\t\t\tvalidator: (value) => ['object', 'app'].includes(value),\n\t\t},\n\n\t\t/** Rendering surface — passed for AD-19 surface fallback consumers. */\n\t\tsurface: {\n\t\t\ttype: String,\n\t\t\tdefault: 'detail-page',\n\t\t\tvalidator: (value) => ['user-dashboard', 'app-dashboard', 'detail-page', 'single-entity'].includes(value),\n\t\t},\n\n\t\t/** Base API URL. */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\t\t/** Maximum rows to render. */\n\t\tmaxDisplay: { type: Number, default: 5 },\n\t\t/** Whether the card collapses. */\n\t\tcollapsible: { type: Boolean, default: false },\n\t\t/** Override the card title (defaults to the translated label). */\n\t\ttitle: { type: String, default: '' },\n\t\t/** Pre-translated empty label. */\n\t\tnoEntriesLabel: { type: String, default: () => t('nextcloud-vue', 'No audit entries yet') },\n\t\t/** Pre-translated overflow label. */\n\t\tshowAllLabel: { type: String, default: () => t('nextcloud-vue', 'Show all') },\n\t\t/** Pre-translated fallback action label. */\n\t\tactionLabel: { type: String, default: () => t('nextcloud-vue', 'Change') },\n\t},\n\n\temits: ['show-all'],\n\n\tdata() {\n\t\treturn {\n\t\t\tHistory: markRaw(History),\n\t\t\tentries: [],\n\t\t\tloading: false,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\tresolvedTitle() {\n\t\t\treturn this.title || t('nextcloud-vue', 'Audit trail')\n\t\t},\n\n\t\tdisplayedEntries() {\n\t\t\treturn this.entries.slice(0, this.maxDisplay)\n\t\t},\n\n\t\t/**\n\t\t * The path the card reads (prefixed at fetch time), or '' when it has nothing to read: the\n\t\t * object's own trail, or the app-wide readable feed narrowed to the\n\t\t * register and schema when set.\n\t\t *\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-the-audit-trail-widget-reads-the-app-wide-feed\n\t\t * @return {string}\n\t\t */\n\t\tfetchUrl() {\n\t\t\tconst params = new URLSearchParams({ limit: String(this.maxDisplay) })\n\t\t\tif (this.scope === 'app') {\n\t\t\t\tif (this.register) {\n\t\t\t\t\tparams.set('register', this.register)\n\t\t\t\t}\n\t\t\t\tif (this.schema) {\n\t\t\t\t\tparams.set('schema', this.schema)\n\t\t\t\t}\n\t\t\t\treturn `${this.apiBase}/audit-trails/readable?${params.toString()}`\n\t\t\t}\n\t\t\tif (!this.register || !this.schema || !this.objectId) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn `${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/audit-trails?${params.toString()}`\n\t\t},\n\t},\n\n\twatch: {\n\t\tfetchUrl: {\n\t\t\timmediate: true,\n\t\t\thandler(url) {\n\t\t\t\tif (url) {\n\t\t\t\t\tthis.fetchEntries()\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Read the entries from `fetchUrl`. The readable feed answers\n\t\t * `{ rows, nextCursor }`, the object trail `{ results }`.\n\t\t *\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-the-audit-trail-widget-reads-the-app-wide-feed\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync fetchEntries() {\n\t\t\tconst url = this.fetchUrl\n\t\t\tif (!url) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\ttry {\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\tconst list = data.results || data.rows || data\n\t\t\t\t\tthis.entries = Array.isArray(list) ? list : []\n\t\t\t\t} else {\n\t\t\t\t\tthis.entries = []\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnAuditTrailCard] failed to fetch audit trail', err)\n\t\t\t\tthis.entries = []\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\tformatActor(entry) {\n\t\t\treturn entry.actorDisplayName || entry.actor || entry.userId || entry.user || ''\n\t\t},\n\n\t\tformatWhen(entry) {\n\t\t\tconst raw = entry.creationDateTime || entry.created || entry.timestamp\n\t\t\tif (raw === undefined || raw === null) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst d = new Date(raw)\n\t\t\tif (Number.isNaN(d.getTime()) === true) {\n\t\t\t\treturn String(raw)\n\t\t\t}\n\t\t\treturn d.toLocaleString()\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-audit-card__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n.cn-audit-card__row {\n\tpadding: 6px 0;\n\tborder-bottom: 1px solid var(--color-border);\n}\n\n.cn-audit-card__row:last-child {\n\tborder-bottom: none;\n}\n\n.cn-audit-card__row-head {\n\tdisplay: flex;\n\tjustify-content: space-between;\n\tgap: 8px;\n}\n\n.cn-audit-card__action {\n\tcolor: var(--color-main-text);\n}\n\n.cn-audit-card__when {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.85em;\n\twhite-space: nowrap;\n}\n\n.cn-audit-card__row-body {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n}\n\n.cn-audit-card__empty {\n\tcolor: var(--color-text-maxcontrast);\n\ttext-align: center;\n\tpadding: 12px 0;\n}\n\n.cn-audit-card__show-all {\n\tbackground: none;\n\tborder: none;\n\tcolor: var(--color-primary-element);\n\tcursor: pointer;\n\tpadding: 4px 0;\n\tfont: inherit;\n}\n\n.cn-audit-card__show-all:hover {\n\ttext-decoration: underline;\n}\n</style>\n"],"names":["_createBlock","_createElementBlock","_toDisplayString","_createElementVNode"],"mappings":";;;;AA+BwC,EAAA,KAAA,EAAA,sBAAA;;;;AAGjC,EAAA,KAAA,EAAA,qBAAA;;AAKE,MAAA,UAAA,GAAA,EAAA,KAAE,EAAA,yBAAA,GAAA;AACC,MAAA,UAAA,GAAA,EAAA,KAAA,EAAA,uBAAA,GAAA;AACD,MAAA,UAAA,GAAA,EAAA,KAAI,EAAA,qBAAA,GAAA;;;;;;;;uBAhBXA;SACI,EAAE,QAAa,CAAA,aAAA;AACnB,IAAA,IAAG,EAAE,KAAO,CAAA,OAAA;AACX,IAAA,WAAS,EAAE,MAAW,CAAA,WAAA;kBACZ,EAAE,MAAO,CAAA,OAAA;;2BACf;OAAA,KAAA,CAAA,OAAA;mCAAA,CAAA,wBAAA,EAAA,EAAA,GAAA,EAAA,CAAA,EAAA,CAAA;AACS,UAAA,CAAA,KAAA,CAAA,OAAO,CAAC,MAAM,KAAA,CAAA;0BAA5BC,kBAEI,CAAA,KAAA,EAFJ,UAEI,EAAAC,eAAA,CADF,MAAc,CAAA,cAAA,CAAA,EAAA,CAAA,YAAA;;gCAGjBD,8CACiB,QAAgB,CAAA,gBAAA,EAAA,MAApB,KAAA;qCADbA;kBAEE,GAAG,EAAC,KAAM,CAAC,EAAE;AACd,kBAAA,KAAK,EAAC,oBAAoB;;AAC1B,kBAAAE,kBAAA,QAAA;oBACCA,kBAAO,CAAA,QAAA,EAAP,UAAO,EAAAD,eAAA,CAAA,KAAA,CAAA,MAAA,IAAA,KAAA,CAAA,KAAA,IAAA,MAAA,CAAA,WAAA,CAAA,EAAA,CAAA,YAAA;AACP,oBAAAC,kBAAA,CAAU,MAAV,EAAA,UAAU,EAAAD,eAAA,CAAA,QAAA,CAAA,UAAA,CAAA,KAAA,CAAA,CAAA,EAAA,CAAA,YAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"CnAuditTrailCard.vue3.js","sources":["../../../../src/components/CnAuditTrailCard/CnAuditTrailCard.vue?vue&type=template&id=3abac146&lang.js"],"sourcesContent":["<!--\n CnAuditTrailCard — compact audit-trail widget for the integration\n registry.\n\n Surface-aware shell around the `audit-trail` integration: fetches\n the object's recent audit-trail entries (query-time storage strategy\n per AD-22) and renders them in a CnDetailCard.\n\n Per umbrella change `pluggable-integration-registry` AD-19 (surface\n fallback), one component handles all surfaces — `surface` is\n forwarded so the component can branch internally if desired.\n-->\n<template>\n\t<CnDetailCard\n\t\t:title=\"resolvedTitle\"\n\t\t:icon=\"History\"\n\t\t:collapsible=\"collapsible\"\n\t\t:data-surface=\"surface\">\n\t\t<NcLoadingIcon v-if=\"loading\" />\n\t\t<div v-else-if=\"entries.length === 0\" class=\"cn-audit-card__empty\">\n\t\t\t{{ noEntriesLabel }}\n\t\t</div>\n\t\t<ul v-else class=\"cn-audit-card__list\">\n\t\t\t<li\n\t\t\t\tv-for=\"entry in displayedEntries\"\n\t\t\t\t:key=\"entry.id\"\n\t\t\t\tclass=\"cn-audit-card__row\">\n\t\t\t\t<div class=\"cn-audit-card__row-head\">\n\t\t\t\t\t<strong class=\"cn-audit-card__action\">{{ entry.action || entry.event || actionLabel }}</strong>\n\t\t\t\t\t<span class=\"cn-audit-card__when\">{{ formatWhen(entry) }}</span>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"cn-audit-card__row-body\">\n\t\t\t\t\t<span class=\"cn-audit-card__actor\">{{ formatActor(entry) }}</span>\n\t\t\t\t</div>\n\t\t\t</li>\n\t\t</ul>\n\t\t<template v-if=\"entries.length > maxDisplay\" #footer>\n\t\t\t<button class=\"cn-audit-card__show-all\" @click=\"$emit('show-all')\">\n\t\t\t\t{{ showAllLabel }} ({{ entries.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 History from 'vue-material-design-icons/History.vue'\nimport CnDetailCard from '../CnDetailCard/CnDetailCard.vue'\nimport { buildHeaders, prefixUrl } from '../../utils/index.js'\nimport { userLocale } from '../../utils/userLocale.js'\n\n/**\n * CnAuditTrailCard — compact audit-trail widget rendered by the\n * integration registry on dashboard and detail surfaces.\n *\n * Basic usage\n * ```vue\n * <CnAuditTrailCard\n * :register=\"registerId\"\n * :schema=\"schemaId\"\n * :object-id=\"objectId\"\n * surface=\"detail-page\" />\n * ```\n */\nexport default {\n\tname: 'CnAuditTrailCard',\n\n\tcomponents: { CnDetailCard, NcLoadingIcon },\n\n\tprops: {\n\t\t/** OpenRegister register id (slug or uuid). Optional for `scope: \"app\"`. */\n\t\tregister: { type: String, default: '' },\n\t\t/** OpenRegister schema id (slug or uuid). Optional for `scope: \"app\"`. */\n\t\tschema: { type: String, default: '' },\n\t\t/** Parent object id. Required for `scope: \"object\"` (the default); unused for `scope: \"app\"`. */\n\t\tobjectId: { type: String, default: '' },\n\t\t/**\n\t\t * What the card lists. `object` (the default) is one object's trail\n\t\t * and needs `register`, `schema` and `objectId`. `app` is the\n\t\t * app-wide feed: the entries of every object the caller may read\n\t\t * (OpenRegister's `/audit-trails/readable`), narrowed to `register`\n\t\t * and `schema` when those are set.\n\t\t *\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-the-audit-trail-widget-reads-the-app-wide-feed\n\t\t */\n\t\tscope: {\n\t\t\ttype: String,\n\t\t\tdefault: 'object',\n\t\t\tvalidator: (value) => ['object', 'app'].includes(value),\n\t\t},\n\n\t\t/** Rendering surface — passed for AD-19 surface fallback consumers. */\n\t\tsurface: {\n\t\t\ttype: String,\n\t\t\tdefault: 'detail-page',\n\t\t\tvalidator: (value) => ['user-dashboard', 'app-dashboard', 'detail-page', 'single-entity'].includes(value),\n\t\t},\n\n\t\t/** Base API URL. */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\t\t/** Maximum rows to render. */\n\t\tmaxDisplay: { type: Number, default: 5 },\n\t\t/** Whether the card collapses. */\n\t\tcollapsible: { type: Boolean, default: false },\n\t\t/** Override the card title (defaults to the translated label). */\n\t\ttitle: { type: String, default: '' },\n\t\t/** Pre-translated empty label. */\n\t\tnoEntriesLabel: { type: String, default: () => t('nextcloud-vue', 'No audit entries yet') },\n\t\t/** Pre-translated overflow label. */\n\t\tshowAllLabel: { type: String, default: () => t('nextcloud-vue', 'Show all') },\n\t\t/** Pre-translated fallback action label. */\n\t\tactionLabel: { type: String, default: () => t('nextcloud-vue', 'Change') },\n\t},\n\n\temits: ['show-all'],\n\n\tdata() {\n\t\treturn {\n\t\t\tHistory: markRaw(History),\n\t\t\tentries: [],\n\t\t\tloading: false,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\tresolvedTitle() {\n\t\t\treturn this.title || t('nextcloud-vue', 'Audit trail')\n\t\t},\n\n\t\tdisplayedEntries() {\n\t\t\treturn this.entries.slice(0, this.maxDisplay)\n\t\t},\n\n\t\t/**\n\t\t * The path the card reads (prefixed at fetch time), or '' when it has nothing to read: the\n\t\t * object's own trail, or the app-wide readable feed narrowed to the\n\t\t * register and schema when set.\n\t\t *\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-the-audit-trail-widget-reads-the-app-wide-feed\n\t\t * @return {string}\n\t\t */\n\t\tfetchUrl() {\n\t\t\tconst params = new URLSearchParams({ limit: String(this.maxDisplay) })\n\t\t\tif (this.scope === 'app') {\n\t\t\t\tif (this.register) {\n\t\t\t\t\tparams.set('register', this.register)\n\t\t\t\t}\n\t\t\t\tif (this.schema) {\n\t\t\t\t\tparams.set('schema', this.schema)\n\t\t\t\t}\n\t\t\t\treturn `${this.apiBase}/audit-trails/readable?${params.toString()}`\n\t\t\t}\n\t\t\tif (!this.register || !this.schema || !this.objectId) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn `${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/audit-trails?${params.toString()}`\n\t\t},\n\t},\n\n\twatch: {\n\t\tfetchUrl: {\n\t\t\timmediate: true,\n\t\t\thandler(url) {\n\t\t\t\tif (url) {\n\t\t\t\t\tthis.fetchEntries()\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Read the entries from `fetchUrl`. The readable feed answers\n\t\t * `{ rows, nextCursor }`, the object trail `{ results }`.\n\t\t *\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-the-audit-trail-widget-reads-the-app-wide-feed\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync fetchEntries() {\n\t\t\tconst url = this.fetchUrl\n\t\t\tif (!url) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\ttry {\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\tconst list = data.results || data.rows || data\n\t\t\t\t\tthis.entries = Array.isArray(list) ? list : []\n\t\t\t\t} else {\n\t\t\t\t\tthis.entries = []\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnAuditTrailCard] failed to fetch audit trail', err)\n\t\t\t\tthis.entries = []\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\tformatActor(entry) {\n\t\t\treturn entry.actorDisplayName || entry.actor || entry.userId || entry.user || ''\n\t\t},\n\n\t\tformatWhen(entry) {\n\t\t\tconst raw = entry.creationDateTime || entry.created || entry.timestamp\n\t\t\tif (raw === undefined || raw === null) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst d = new Date(raw)\n\t\t\tif (Number.isNaN(d.getTime()) === true) {\n\t\t\t\treturn String(raw)\n\t\t\t}\n\t\t\treturn d.toLocaleString(userLocale())\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-audit-card__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n.cn-audit-card__row {\n\tpadding: 6px 0;\n\tborder-bottom: 1px solid var(--color-border);\n}\n\n.cn-audit-card__row:last-child {\n\tborder-bottom: none;\n}\n\n.cn-audit-card__row-head {\n\tdisplay: flex;\n\tjustify-content: space-between;\n\tgap: 8px;\n}\n\n.cn-audit-card__action {\n\tcolor: var(--color-main-text);\n}\n\n.cn-audit-card__when {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.85em;\n\twhite-space: nowrap;\n}\n\n.cn-audit-card__row-body {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.9em;\n}\n\n.cn-audit-card__empty {\n\tcolor: var(--color-text-maxcontrast);\n\ttext-align: center;\n\tpadding: 12px 0;\n}\n\n.cn-audit-card__show-all {\n\tbackground: none;\n\tborder: none;\n\tcolor: var(--color-primary-element);\n\tcursor: pointer;\n\tpadding: 4px 0;\n\tfont: inherit;\n}\n\n.cn-audit-card__show-all:hover {\n\ttext-decoration: underline;\n}\n</style>\n"],"names":["_createBlock","_createElementBlock","_toDisplayString","_createElementVNode"],"mappings":";;;;AA+BwC,EAAA,KAAA,EAAA,sBAAA;;;;AAGjC,EAAA,KAAA,EAAA,qBAAA;;AAKE,MAAA,UAAA,GAAA,EAAA,KAAE,EAAA,yBAAA,GAAA;AACC,MAAA,UAAA,GAAA,EAAA,KAAA,EAAA,uBAAA,GAAA;AACD,MAAA,UAAA,GAAA,EAAA,KAAI,EAAA,qBAAA,GAAA;;;;;;;;uBAhBXA;SACI,EAAE,QAAa,CAAA,aAAA;AACnB,IAAA,IAAG,EAAE,KAAO,CAAA,OAAA;AACX,IAAA,WAAS,EAAE,MAAW,CAAA,WAAA;kBACZ,EAAE,MAAO,CAAA,OAAA;;2BACf;OAAA,KAAA,CAAA,OAAA;mCAAA,CAAA,wBAAA,EAAA,EAAA,GAAA,EAAA,CAAA,EAAA,CAAA;AACS,UAAA,CAAA,KAAA,CAAA,OAAO,CAAC,MAAM,KAAA,CAAA;0BAA5BC,kBAEI,CAAA,KAAA,EAFJ,UAEI,EAAAC,eAAA,CADF,MAAc,CAAA,cAAA,CAAA,EAAA,CAAA,YAAA;;gCAGjBD,8CACiB,QAAgB,CAAA,gBAAA,EAAA,MAApB,KAAA;qCADbA;kBAEE,GAAG,EAAC,KAAM,CAAC,EAAE;AACd,kBAAA,KAAK,EAAC,oBAAoB;;AAC1B,kBAAAE,kBAAA,QAAA;oBACCA,kBAAO,CAAA,QAAA,EAAP,UAAO,EAAAD,eAAA,CAAA,KAAA,CAAA,MAAA,IAAA,KAAA,CAAA,KAAA,IAAA,MAAA,CAAA,WAAA,CAAA,EAAA,CAAA,YAAA;AACP,oBAAAC,kBAAA,CAAU,MAAV,EAAA,UAAU,EAAAD,eAAA,CAAA,QAAA,CAAA,UAAA,CAAA,KAAA,CAAA,CAAA,EAAA,CAAA,YAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -2,7 +2,7 @@ import script from './CnBoardView.vue2.js';
2
2
  import { render } from './CnBoardView.vue3.js';
3
3
 
4
4
  script.render = render;
5
- script.__scopeId = "data-v-181b36b2";
5
+ script.__scopeId = "data-v-4b0d3015";
6
6
  const __sfc_main = script;
7
7
 
8
8
  export { __sfc_main as default };