@conduction/nextcloud-vue 2.85.0 → 2.87.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (301) hide show
  1. package/dist/esm/components/CnAdvancedFormDialog/CnMetadataTab.vue.js +1 -1
  2. package/dist/esm/components/CnAdvancedFormDialog/CnMetadataTab.vue2.js +3 -2
  3. package/dist/esm/components/CnAdvancedFormDialog/CnMetadataTab.vue2.js.map +1 -1
  4. package/dist/esm/components/CnAdvancedFormDialog/CnMetadataTab.vue3.js.map +1 -1
  5. package/dist/esm/components/CnAdvancedFormDialog/CnPropertyValueCell.vue.js +1 -1
  6. package/dist/esm/components/CnAdvancedFormDialog/CnPropertyValueCell.vue2.js +5 -4
  7. package/dist/esm/components/CnAdvancedFormDialog/CnPropertyValueCell.vue2.js.map +1 -1
  8. package/dist/esm/components/CnAdvancedFormDialog/CnPropertyValueCell.vue3.js.map +1 -1
  9. package/dist/esm/components/CnAnalyticsReportPicker/CnAnalyticsReportPicker.vue.js +1 -1
  10. package/dist/esm/components/CnAnalyticsReportPicker/CnAnalyticsReportPicker.vue2.js +2 -1
  11. package/dist/esm/components/CnAnalyticsReportPicker/CnAnalyticsReportPicker.vue2.js.map +1 -1
  12. package/dist/esm/components/CnAnalyticsReportPicker/CnAnalyticsReportPicker.vue3.js.map +1 -1
  13. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue.js +1 -1
  14. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue2.js +2 -1
  15. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue2.js.map +1 -1
  16. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue3.js.map +1 -1
  17. package/dist/esm/components/CnBoardView/CnBoardView.vue.js +1 -1
  18. package/dist/esm/components/CnBoardView/CnBoardView.vue2.js +156 -14
  19. package/dist/esm/components/CnBoardView/CnBoardView.vue2.js.map +1 -1
  20. package/dist/esm/components/CnBoardView/CnBoardView.vue3.js +105 -74
  21. package/dist/esm/components/CnBoardView/CnBoardView.vue3.js.map +1 -1
  22. package/dist/esm/components/CnCalendarEventPicker/CnCalendarEventPicker.vue.js +1 -1
  23. package/dist/esm/components/CnCalendarEventPicker/CnCalendarEventPicker.vue2.js +2 -1
  24. package/dist/esm/components/CnCalendarEventPicker/CnCalendarEventPicker.vue2.js.map +1 -1
  25. package/dist/esm/components/CnCalendarEventPicker/CnCalendarEventPicker.vue3.js.map +1 -1
  26. package/dist/esm/components/CnCalendarWidget/CnCalendarWidget.vue.js +1 -1
  27. package/dist/esm/components/CnCalendarWidget/CnCalendarWidget.vue2.js +2 -1
  28. package/dist/esm/components/CnCalendarWidget/CnCalendarWidget.vue2.js.map +1 -1
  29. package/dist/esm/components/CnCalendarWidget/CnCalendarWidget.vue3.js.map +1 -1
  30. package/dist/esm/components/CnChartWidget/CnChartWidget.vue.js +1 -1
  31. package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js +6 -5
  32. package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js.map +1 -1
  33. package/dist/esm/components/CnChartWidget/CnChartWidget.vue3.js.map +1 -1
  34. package/dist/esm/components/CnCollectivePagePicker/CnCollectivePagePicker.vue.js +1 -1
  35. package/dist/esm/components/CnCollectivePagePicker/CnCollectivePagePicker.vue2.js +2 -1
  36. package/dist/esm/components/CnCollectivePagePicker/CnCollectivePagePicker.vue2.js.map +1 -1
  37. package/dist/esm/components/CnCollectivePagePicker/CnCollectivePagePicker.vue3.js.map +1 -1
  38. package/dist/esm/components/CnConversationThread/CnConversationThread.vue.js +1 -1
  39. package/dist/esm/components/CnConversationThread/CnConversationThread.vue2.js +2 -1
  40. package/dist/esm/components/CnConversationThread/CnConversationThread.vue2.js.map +1 -1
  41. package/dist/esm/components/CnConversationThread/CnConversationThread.vue3.js.map +1 -1
  42. package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue.js +1 -1
  43. package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue2.js +2 -1
  44. package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue2.js.map +1 -1
  45. package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue3.js.map +1 -1
  46. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
  47. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +2 -1
  48. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
  49. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
  50. package/dist/esm/components/CnDataMatrix/CnDataMatrix.vue.js +1 -1
  51. package/dist/esm/components/CnDataMatrix/CnDataMatrix.vue2.js +3 -1
  52. package/dist/esm/components/CnDataMatrix/CnDataMatrix.vue2.js.map +1 -1
  53. package/dist/esm/components/CnDataMatrix/CnDataMatrix.vue3.js.map +1 -1
  54. package/dist/esm/components/CnDetailPage/CnDetailHeaderChips.vue2.js +2 -1
  55. package/dist/esm/components/CnDetailPage/CnDetailHeaderChips.vue2.js.map +1 -1
  56. package/dist/esm/components/CnDetailPage/CnDetailHeaderChips.vue3.js.map +1 -1
  57. package/dist/esm/components/CnEmailPicker/CnEmailPicker.vue.js +1 -1
  58. package/dist/esm/components/CnEmailPicker/CnEmailPicker.vue2.js +2 -1
  59. package/dist/esm/components/CnEmailPicker/CnEmailPicker.vue2.js.map +1 -1
  60. package/dist/esm/components/CnEmailPicker/CnEmailPicker.vue3.js.map +1 -1
  61. package/dist/esm/components/CnFederationStatus/CnFederationStatus.vue.js +1 -1
  62. package/dist/esm/components/CnFederationStatus/CnFederationStatus.vue2.js +3 -1
  63. package/dist/esm/components/CnFederationStatus/CnFederationStatus.vue2.js.map +1 -1
  64. package/dist/esm/components/CnFederationStatus/CnFederationStatus.vue3.js.map +1 -1
  65. package/dist/esm/components/CnFileManager/CnFileManager.vue.js +1 -1
  66. package/dist/esm/components/CnFileManager/CnFileManager.vue2.js +3 -1
  67. package/dist/esm/components/CnFileManager/CnFileManager.vue2.js.map +1 -1
  68. package/dist/esm/components/CnFileManager/CnFileManager.vue3.js.map +1 -1
  69. package/dist/esm/components/CnFilePreview/CnFilePreview.vue.js +1 -1
  70. package/dist/esm/components/CnFilePreview/CnFilePreview.vue2.js +2 -1
  71. package/dist/esm/components/CnFilePreview/CnFilePreview.vue2.js.map +1 -1
  72. package/dist/esm/components/CnFilePreview/CnFilePreview.vue3.js.map +1 -1
  73. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue.js +1 -1
  74. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +49 -1
  75. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
  76. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +3 -3
  77. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
  78. package/dist/esm/components/CnFollowToggle/CnFollowToggle.vue.js +1 -1
  79. package/dist/esm/components/CnFollowToggle/CnFollowToggle.vue2.js +2 -1
  80. package/dist/esm/components/CnFollowToggle/CnFollowToggle.vue2.js.map +1 -1
  81. package/dist/esm/components/CnFollowToggle/CnFollowToggle.vue3.js.map +1 -1
  82. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +30 -5
  83. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  84. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  85. package/dist/esm/components/CnNotesCard/CnNotesCard.vue.js +1 -1
  86. package/dist/esm/components/CnNotesCard/CnNotesCard.vue2.js +2 -1
  87. package/dist/esm/components/CnNotesCard/CnNotesCard.vue2.js.map +1 -1
  88. package/dist/esm/components/CnNotesCard/CnNotesCard.vue3.js.map +1 -1
  89. package/dist/esm/components/CnObjectCalendar/CnObjectCalendar.vue.js +1 -1
  90. package/dist/esm/components/CnObjectCalendar/CnObjectCalendar.vue2.js +3 -2
  91. package/dist/esm/components/CnObjectCalendar/CnObjectCalendar.vue2.js.map +1 -1
  92. package/dist/esm/components/CnObjectCalendar/CnObjectCalendar.vue3.js.map +1 -1
  93. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue.js +1 -1
  94. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js +28 -0
  95. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js.map +1 -1
  96. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue3.js +82 -75
  97. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue3.js.map +1 -1
  98. package/dist/esm/components/CnObjectMetadataWidget/CnObjectMetadataWidget.vue.js +1 -1
  99. package/dist/esm/components/CnObjectMetadataWidget/CnObjectMetadataWidget.vue2.js +5 -4
  100. package/dist/esm/components/CnObjectMetadataWidget/CnObjectMetadataWidget.vue2.js.map +1 -1
  101. package/dist/esm/components/CnObjectMetadataWidget/CnObjectMetadataWidget.vue3.js.map +1 -1
  102. package/dist/esm/components/CnObjectSidebar/CnAuditTrailTab.vue.js +1 -1
  103. package/dist/esm/components/CnObjectSidebar/CnAuditTrailTab.vue2.js +2 -1
  104. package/dist/esm/components/CnObjectSidebar/CnAuditTrailTab.vue2.js.map +1 -1
  105. package/dist/esm/components/CnObjectSidebar/CnAuditTrailTab.vue3.js.map +1 -1
  106. package/dist/esm/components/CnObjectSidebar/CnFlowTasksPanel.vue.js +1 -1
  107. package/dist/esm/components/CnObjectSidebar/CnFlowTasksPanel.vue2.js +2 -1
  108. package/dist/esm/components/CnObjectSidebar/CnFlowTasksPanel.vue2.js.map +1 -1
  109. package/dist/esm/components/CnObjectSidebar/CnFlowTasksPanel.vue3.js.map +1 -1
  110. package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue.js +1 -1
  111. package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue2.js +2 -1
  112. package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue2.js.map +1 -1
  113. package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue3.js.map +1 -1
  114. package/dist/esm/components/CnObjectSidebar/CnTasksTab.vue.js +1 -1
  115. package/dist/esm/components/CnObjectSidebar/CnTasksTab.vue2.js +2 -1
  116. package/dist/esm/components/CnObjectSidebar/CnTasksTab.vue2.js.map +1 -1
  117. package/dist/esm/components/CnObjectSidebar/CnTasksTab.vue3.js.map +1 -1
  118. package/dist/esm/components/CnPropertySourceField/CnPropertySourceField.vue.js +1 -1
  119. package/dist/esm/components/CnPropertySourceField/CnPropertySourceField.vue2.js +2 -1
  120. package/dist/esm/components/CnPropertySourceField/CnPropertySourceField.vue2.js.map +1 -1
  121. package/dist/esm/components/CnPropertySourceField/CnPropertySourceField.vue3.js.map +1 -1
  122. package/dist/esm/components/CnRoadmapItem/CnRoadmapItem.vue.js +1 -1
  123. package/dist/esm/components/CnRoadmapItem/CnRoadmapItem.vue2.js +2 -1
  124. package/dist/esm/components/CnRoadmapItem/CnRoadmapItem.vue2.js.map +1 -1
  125. package/dist/esm/components/CnRoadmapItem/CnRoadmapItem.vue3.js.map +1 -1
  126. package/dist/esm/components/CnSchemaFormDialog/CnSchemaFormDialog.vue2.js +2 -0
  127. package/dist/esm/components/CnSchemaFormDialog/CnSchemaFormDialog.vue2.js.map +1 -1
  128. package/dist/esm/components/CnSchemaFormDialog/CnSchemaFormDialog.vue3.js +2 -2
  129. package/dist/esm/components/CnSchemaFormDialog/CnSchemaFormDialog.vue3.js.map +1 -1
  130. package/dist/esm/components/CnStatsBlock/CnStatsBlock.vue.js +1 -1
  131. package/dist/esm/components/CnStatsBlock/CnStatsBlock.vue2.js +2 -1
  132. package/dist/esm/components/CnStatsBlock/CnStatsBlock.vue2.js.map +1 -1
  133. package/dist/esm/components/CnStatsBlock/CnStatsBlock.vue3.js.map +1 -1
  134. package/dist/esm/components/CnSummaryAggregates/CnSummaryAggregates.vue.js +1 -1
  135. package/dist/esm/components/CnSummaryAggregates/CnSummaryAggregates.vue2.js +2 -1
  136. package/dist/esm/components/CnSummaryAggregates/CnSummaryAggregates.vue2.js.map +1 -1
  137. package/dist/esm/components/CnSummaryAggregates/CnSummaryAggregates.vue3.js.map +1 -1
  138. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue.js +1 -1
  139. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +36 -0
  140. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
  141. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js +55 -40
  142. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
  143. package/dist/esm/components/CnTasksCard/CnTasksCard.vue.js +1 -1
  144. package/dist/esm/components/CnTasksCard/CnTasksCard.vue2.js +2 -1
  145. package/dist/esm/components/CnTasksCard/CnTasksCard.vue2.js.map +1 -1
  146. package/dist/esm/components/CnTasksCard/CnTasksCard.vue3.js.map +1 -1
  147. package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue.js +1 -1
  148. package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue2.js +2 -1
  149. package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue2.js.map +1 -1
  150. package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue3.js.map +1 -1
  151. package/dist/esm/components/CnTranslatedBadge/CnTranslatedBadge.vue.js +1 -1
  152. package/dist/esm/components/CnTranslatedBadge/CnTranslatedBadge.vue2.js +2 -1
  153. package/dist/esm/components/CnTranslatedBadge/CnTranslatedBadge.vue2.js.map +1 -1
  154. package/dist/esm/components/CnVersionHistory/CnVersionHistory.vue.js +1 -1
  155. package/dist/esm/components/CnVersionHistory/CnVersionHistory.vue2.js +2 -1
  156. package/dist/esm/components/CnVersionHistory/CnVersionHistory.vue2.js.map +1 -1
  157. package/dist/esm/components/CnVersionHistory/CnVersionHistory.vue3.js.map +1 -1
  158. package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue.js +1 -1
  159. package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue2.js +3 -26
  160. package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue2.js.map +1 -1
  161. package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue3.js.map +1 -1
  162. package/dist/esm/dialogs/CnNoteHistoryDialog.vue.js +1 -1
  163. package/dist/esm/dialogs/CnNoteHistoryDialog.vue2.js +2 -1
  164. package/dist/esm/dialogs/CnNoteHistoryDialog.vue2.js.map +1 -1
  165. package/dist/esm/dialogs/CnNoteHistoryDialog.vue3.js.map +1 -1
  166. package/dist/esm/integrations/builtin/activity/CnActivityCard.vue.js +1 -1
  167. package/dist/esm/integrations/builtin/activity/CnActivityCard.vue2.js +3 -2
  168. package/dist/esm/integrations/builtin/activity/CnActivityCard.vue2.js.map +1 -1
  169. package/dist/esm/integrations/builtin/activity/CnActivityCard.vue3.js.map +1 -1
  170. package/dist/esm/integrations/builtin/activity/CnActivityTab.vue.js +1 -1
  171. package/dist/esm/integrations/builtin/activity/CnActivityTab.vue2.js +4 -3
  172. package/dist/esm/integrations/builtin/activity/CnActivityTab.vue2.js.map +1 -1
  173. package/dist/esm/integrations/builtin/activity/CnActivityTab.vue3.js.map +1 -1
  174. package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue.js +1 -1
  175. package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue2.js +3 -2
  176. package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue2.js.map +1 -1
  177. package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue3.js.map +1 -1
  178. package/dist/esm/integrations/builtin/calendar/CnCalendarCard.vue.js +1 -1
  179. package/dist/esm/integrations/builtin/calendar/CnCalendarCard.vue2.js +2 -1
  180. package/dist/esm/integrations/builtin/calendar/CnCalendarCard.vue2.js.map +1 -1
  181. package/dist/esm/integrations/builtin/calendar/CnCalendarCard.vue3.js.map +1 -1
  182. package/dist/esm/integrations/builtin/calendar/CnCalendarTab.vue.js +1 -1
  183. package/dist/esm/integrations/builtin/calendar/CnCalendarTab.vue2.js +6 -5
  184. package/dist/esm/integrations/builtin/calendar/CnCalendarTab.vue2.js.map +1 -1
  185. package/dist/esm/integrations/builtin/calendar/CnCalendarTab.vue3.js.map +1 -1
  186. package/dist/esm/integrations/builtin/contactmoment/CnContactmomentCard.vue.js +1 -1
  187. package/dist/esm/integrations/builtin/contactmoment/CnContactmomentCard.vue2.js +2 -1
  188. package/dist/esm/integrations/builtin/contactmoment/CnContactmomentCard.vue2.js.map +1 -1
  189. package/dist/esm/integrations/builtin/contactmoment/CnContactmomentCard.vue3.js.map +1 -1
  190. package/dist/esm/integrations/builtin/cospend/CnCospendTab.vue.js +1 -1
  191. package/dist/esm/integrations/builtin/cospend/CnCospendTab.vue2.js +2 -1
  192. package/dist/esm/integrations/builtin/cospend/CnCospendTab.vue2.js.map +1 -1
  193. package/dist/esm/integrations/builtin/cospend/CnCospendTab.vue3.js.map +1 -1
  194. package/dist/esm/integrations/builtin/deck/CnDeckTab.vue.js +1 -1
  195. package/dist/esm/integrations/builtin/deck/CnDeckTab.vue2.js +2 -1
  196. package/dist/esm/integrations/builtin/deck/CnDeckTab.vue2.js.map +1 -1
  197. package/dist/esm/integrations/builtin/deck/CnDeckTab.vue3.js.map +1 -1
  198. package/dist/esm/integrations/builtin/email/CnEmailCard.vue.js +1 -1
  199. package/dist/esm/integrations/builtin/email/CnEmailCard.vue2.js +2 -1
  200. package/dist/esm/integrations/builtin/email/CnEmailCard.vue2.js.map +1 -1
  201. package/dist/esm/integrations/builtin/email/CnEmailCard.vue3.js.map +1 -1
  202. package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionCard.vue.js +1 -1
  203. package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionCard.vue2.js +2 -1
  204. package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionCard.vue2.js.map +1 -1
  205. package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionCard.vue3.js.map +1 -1
  206. package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionTab.vue.js +1 -1
  207. package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionTab.vue2.js +2 -1
  208. package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionTab.vue2.js.map +1 -1
  209. package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionTab.vue3.js.map +1 -1
  210. package/dist/esm/integrations/builtin/photos/CnPhotosTab.vue.js +1 -1
  211. package/dist/esm/integrations/builtin/photos/CnPhotosTab.vue2.js +2 -1
  212. package/dist/esm/integrations/builtin/photos/CnPhotosTab.vue2.js.map +1 -1
  213. package/dist/esm/integrations/builtin/photos/CnPhotosTab.vue3.js.map +1 -1
  214. package/dist/esm/l10n/en.json.js +35 -2
  215. package/dist/esm/l10n/en.json.js.map +1 -1
  216. package/dist/esm/l10n/nl.json.js +38 -2
  217. package/dist/esm/l10n/nl.json.js.map +1 -1
  218. package/dist/esm/library-style.js +1 -1
  219. package/dist/esm/nextcloud-vue.css +1068 -962
  220. package/dist/esm/store/useObjectStore.js +7 -2
  221. package/dist/esm/store/useObjectStore.js.map +1 -1
  222. package/dist/esm/utils/boardColumns.js +50 -1
  223. package/dist/esm/utils/boardColumns.js.map +1 -1
  224. package/dist/esm/utils/boardReferenceColumns.js +115 -4
  225. package/dist/esm/utils/boardReferenceColumns.js.map +1 -1
  226. package/dist/esm/utils/contentHeaderLink.js +48 -1
  227. package/dist/esm/utils/contentHeaderLink.js.map +1 -1
  228. package/dist/esm/utils/schema.js +5 -4
  229. package/dist/esm/utils/schema.js.map +1 -1
  230. package/dist/esm/utils/userLocale.js +30 -0
  231. package/dist/esm/utils/userLocale.js.map +1 -0
  232. package/dist/esm/utils/validateManifestV2.compiled.js +2 -2
  233. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  234. package/dist/nextcloud-vue.cjs.js +1088 -476
  235. package/dist/nextcloud-vue.cjs.js.map +2 -2
  236. package/dist/nextcloud-vue.css +1068 -962
  237. package/l10n/en.json +35 -2
  238. package/l10n/nl.json +38 -2
  239. package/package.json +1 -1
  240. package/src/components/CnAdvancedFormDialog/CnMetadataTab.vue +3 -2
  241. package/src/components/CnAdvancedFormDialog/CnPropertyValueCell.vue +5 -4
  242. package/src/components/CnAnalyticsReportPicker/CnAnalyticsReportPicker.vue +2 -1
  243. package/src/components/CnAuditTrailCard/CnAuditTrailCard.vue +2 -1
  244. package/src/components/CnBoardView/CnBoardView.vue +189 -24
  245. package/src/components/CnCalendarEventPicker/CnCalendarEventPicker.vue +2 -1
  246. package/src/components/CnCalendarWidget/CnCalendarWidget.vue +2 -1
  247. package/src/components/CnChartWidget/CnChartWidget.vue +6 -5
  248. package/src/components/CnCollectivePagePicker/CnCollectivePagePicker.vue +2 -1
  249. package/src/components/CnConversationThread/CnConversationThread.vue +2 -1
  250. package/src/components/CnCountdownWidget/CnCountdownWidget.vue +2 -1
  251. package/src/components/CnDashboardPage/CnDashboardPage.vue +2 -1
  252. package/src/components/CnDataMatrix/CnDataMatrix.vue +2 -1
  253. package/src/components/CnDetailPage/CnDetailHeaderChips.vue +2 -1
  254. package/src/components/CnEmailPicker/CnEmailPicker.vue +2 -1
  255. package/src/components/CnFederationStatus/CnFederationStatus.vue +3 -1
  256. package/src/components/CnFileManager/CnFileManager.vue +2 -1
  257. package/src/components/CnFilePreview/CnFilePreview.vue +2 -1
  258. package/src/components/CnFilesBrowser/CnFilesBrowser.vue +54 -6
  259. package/src/components/CnFollowToggle/CnFollowToggle.vue +2 -1
  260. package/src/components/CnIndexPage/CnIndexPage.vue +30 -4
  261. package/src/components/CnNotesCard/CnNotesCard.vue +2 -1
  262. package/src/components/CnObjectCalendar/CnObjectCalendar.vue +3 -2
  263. package/src/components/CnObjectDataWidget/CnObjectDataWidget.vue +36 -0
  264. package/src/components/CnObjectMetadataWidget/CnObjectMetadataWidget.vue +5 -4
  265. package/src/components/CnObjectSidebar/CnAuditTrailTab.vue +2 -1
  266. package/src/components/CnObjectSidebar/CnFlowTasksPanel.vue +2 -1
  267. package/src/components/CnObjectSidebar/CnNotesTab.vue +2 -1
  268. package/src/components/CnObjectSidebar/CnTasksTab.vue +2 -1
  269. package/src/components/CnPropertySourceField/CnPropertySourceField.vue +2 -1
  270. package/src/components/CnRoadmapItem/CnRoadmapItem.vue +2 -1
  271. package/src/components/CnSchemaFormDialog/CnSchemaFormDialog.vue +4 -2
  272. package/src/components/CnStatsBlock/CnStatsBlock.vue +2 -1
  273. package/src/components/CnSummaryAggregates/CnSummaryAggregates.vue +2 -1
  274. package/src/components/CnTabsWidget/CnTabsWidget.vue +71 -0
  275. package/src/components/CnTasksCard/CnTasksCard.vue +2 -1
  276. package/src/components/CnTimelineWidget/CnTimelineWidget.vue +2 -1
  277. package/src/components/CnTranslatedBadge/CnTranslatedBadge.vue +2 -1
  278. package/src/components/CnVersionHistory/CnVersionHistory.vue +2 -1
  279. package/src/components/CnWidgetWrapper/CnWidgetWrapper.vue +3 -26
  280. package/src/css/board.css +40 -0
  281. package/src/dialogs/CnNoteHistoryDialog.vue +2 -1
  282. package/src/integrations/builtin/activity/CnActivityCard.vue +3 -2
  283. package/src/integrations/builtin/activity/CnActivityTab.vue +4 -3
  284. package/src/integrations/builtin/analytics/CnAnalyticsTab.vue +3 -2
  285. package/src/integrations/builtin/calendar/CnCalendarCard.vue +2 -1
  286. package/src/integrations/builtin/calendar/CnCalendarTab.vue +6 -5
  287. package/src/integrations/builtin/contactmoment/CnContactmomentCard.vue +2 -1
  288. package/src/integrations/builtin/cospend/CnCospendTab.vue +2 -1
  289. package/src/integrations/builtin/deck/CnDeckTab.vue +2 -1
  290. package/src/integrations/builtin/email/CnEmailCard.vue +2 -1
  291. package/src/integrations/builtin/field-inspection/CnFieldInspectionCard.vue +2 -1
  292. package/src/integrations/builtin/field-inspection/CnFieldInspectionTab.vue +2 -1
  293. package/src/integrations/builtin/photos/CnPhotosTab.vue +2 -1
  294. package/src/schemas/app-manifest-v2.schema.json +4 -1
  295. package/src/store/useObjectStore.js +7 -2
  296. package/src/utils/boardColumns.js +49 -0
  297. package/src/utils/boardReferenceColumns.js +114 -3
  298. package/src/utils/contentHeaderLink.js +47 -0
  299. package/src/utils/schema.js +5 -4
  300. package/src/utils/userLocale.js +26 -0
  301. package/src/utils/validateManifestV2.compiled.js +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"CnSchemaFormDialog.vue3.js","sources":["../../../../src/components/CnSchemaFormDialog/CnSchemaFormDialog.vue?vue&type=template&id=0f2ea630&lang.js"],"sourcesContent":["<template>\n\t<CnTabbedFormDialog\n\t\tref=\"dialog\"\n\t\t:look=\"look\"\n\t\t:width=\"width\"\n\t\t:eyebrow=\"eyebrow\"\n\t\t:subtitle=\"subtitle\"\n\t\t:tabs=\"dialogTabs\"\n\t\t:item=\"item\"\n\t\t:dialogTitle=\"dialogTitle\"\n\t\tentityName=\"Schema\"\n\t\t:size=\"size\"\n\t\t:disableSave=\"!!saveDisabledReason\"\n\t\t:disableSaveTooltip=\"saveDisabledReason\"\n\t\t:successText=\"resolvedSuccessText\"\n\t\t:cancelLabel=\"cancelLabel\"\n\t\t:closeLabel=\"closeLabel\"\n\t\t:confirmLabel=\"confirmLabel\"\n\t\t@confirm=\"handleConfirm\"\n\t\t@close=\"$emit('close')\">\n\t\t<!-- Metadata Display -->\n\t\t<template #above-tabs=\"{ loading: dialogLoading }\">\n\t\t\t<div class=\"cn-schema-form__detail-grid\">\n\t\t\t\t<div v-if=\"schemaItem.id\"\n\t\t\t\t\tclass=\"cn-schema-form__detail-item cn-schema-form__id-card\">\n\t\t\t\t\t<div class=\"cn-schema-form__id-card-header\">\n\t\t\t\t\t\t<span class=\"cn-schema-form__detail-label\">{{ t('nextcloud-vue', 'ID / UUID:') }}</span>\n\t\t\t\t\t\t<NcButton class=\"cn-schema-form__copy-button\"\n\t\t\t\t\t\t\t@click=\"copyToClipboard(schemaItem.uuid || schemaItem.id)\">\n\t\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t\t<Check v-if=\"isCopied\" :size=\"20\" />\n\t\t\t\t\t\t\t\t<ContentCopy v-else :size=\"20\" />\n\t\t\t\t\t\t\t</template>\n\t\t\t\t\t\t\t{{ isCopied ? t('nextcloud-vue', 'Copied') : t('nextcloud-vue', 'Copy') }}\n\t\t\t\t\t\t</NcButton>\n\t\t\t\t\t</div>\n\t\t\t\t\t<span class=\"cn-schema-form__detail-value\">{{ schemaItem.id }}</span>\n\t\t\t\t\t<span v-if=\"schemaItem.uuid && schemaItem.uuid !== schemaItem.id\"\n\t\t\t\t\t\tclass=\"cn-schema-form__detail-value cn-schema-form__uuid-value\">{{ schemaItem.uuid }}</span>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"cn-schema-form__detail-item cn-schema-form__title-with-badge\">\n\t\t\t\t\t<NcTextField v-model=\"schemaItem.title\"\n\t\t\t\t\t\t:disabled=\"dialogLoading\"\n\t\t\t\t\t\t:label=\"t('nextcloud-vue', 'Title *')\" />\n\t\t\t\t\t<span v-if=\"schemaItem.allOf && schemaItem.allOf.length > 0\"\n\t\t\t\t\t\tclass=\"cn-schema-form__statusPill cn-schema-form__statusPill--success\">\n\t\t\t\t\t\tallOf\n\t\t\t\t\t</span>\n\t\t\t\t\t<span v-if=\"schemaItem.oneOf && schemaItem.oneOf.length > 0\"\n\t\t\t\t\t\tclass=\"cn-schema-form__statusPill cn-schema-form__statusPill--info\">\n\t\t\t\t\t\toneOf\n\t\t\t\t\t</span>\n\t\t\t\t\t<span v-if=\"schemaItem.anyOf && schemaItem.anyOf.length > 0\"\n\t\t\t\t\t\tclass=\"cn-schema-form__statusPill cn-schema-form__statusPill--info\">\n\t\t\t\t\t\tanyOf\n\t\t\t\t\t</span>\n\t\t\t\t</div>\n\t\t\t\t<div v-if=\"schemaItem.created\" class=\"cn-schema-form__detail-item\">\n\t\t\t\t\t<span class=\"cn-schema-form__detail-label\">{{ t('nextcloud-vue', 'Created:') }}</span>\n\t\t\t\t\t<span class=\"cn-schema-form__detail-value\">{{ new Date(schemaItem.created).toLocaleString() }}</span>\n\t\t\t\t</div>\n\t\t\t\t<div v-if=\"schemaItem.updated\" class=\"cn-schema-form__detail-item\">\n\t\t\t\t\t<span class=\"cn-schema-form__detail-label\">{{ t('nextcloud-vue', 'Updated:') }}</span>\n\t\t\t\t\t<span class=\"cn-schema-form__detail-value\">{{ new Date(schemaItem.updated).toLocaleString() }}</span>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"cn-schema-form__detail-item\">\n\t\t\t\t\t<span class=\"cn-schema-form__detail-label\">{{ t('nextcloud-vue', 'Version:') }}</span>\n\t\t\t\t\t<span class=\"cn-schema-form__detail-value\">{{ schemaItem.version || t('nextcloud-vue', 'Not set') }}</span>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"cn-schema-form__detail-item\">\n\t\t\t\t\t<span class=\"cn-schema-form__detail-label\">{{ t('nextcloud-vue', 'Owner:') }}</span>\n\t\t\t\t\t<span class=\"cn-schema-form__detail-value\">{{ schemaItem.owner || t('nextcloud-vue', 'Not set') }}</span>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</template>\n\n\t\t<!-- Properties Tab -->\n\t\t<template #tab-properties=\"{ loading: dialogLoading }\">\n\t\t\t<CnSchemaPropertiesTab\n\t\t\t\t:schemaItem=\"schemaItem\"\n\t\t\t\t:loading=\"dialogLoading\"\n\t\t\t\t:selectedProperty=\"selectedProperty\"\n\t\t\t\t:propertiesModified=\"propertiesModified\"\n\t\t\t\t:originalProperties=\"originalProperties\"\n\t\t\t\t:inheritedProperties=\"inheritedProperties\"\n\t\t\t\t:typeOptionsForSelect=\"typeOptionsForSelect\"\n\t\t\t\t:availableSchemas=\"availableSchemas\"\n\t\t\t\t:availableRegisters=\"availableRegisters\"\n\t\t\t\t:availableTagsOptions=\"availableTagsOptions\"\n\t\t\t\t:userGroups=\"userGroups\"\n\t\t\t\t:sortedUserGroups=\"sortedUserGroups\"\n\t\t\t\t:loadingGroups=\"loadingGroups\"\n\t\t\t\t@addProperty=\"addProperty\"\n\t\t\t\t@update:selectedProperty=\"selectedProperty = $event\"\n\t\t\t\t@update:propertyKey=\"updatePropertyKey($event.oldKey, $event.newKey)\"\n\t\t\t\t@copyProperty=\"copyProperty\"\n\t\t\t\t@deleteProperty=\"deleteProperty\" />\n\t\t</template>\n\n\t\t<!-- Configuration Tab -->\n\t\t<template #tab-configuration=\"{ loading: dialogLoading }\">\n\t\t\t<CnSchemaConfigurationTab\n\t\t\t\t:schemaItem=\"schemaItem\"\n\t\t\t\t:loading=\"dialogLoading\"\n\t\t\t\t:availableSchemas=\"availableSchemas\"\n\t\t\t\t:propertyOptions=\"propertyOptions\"\n\t\t\t\t:allOfSchemaNames=\"allOfSchemaNames\" />\n\t\t</template>\n\n\t\t<!-- Security Tab -->\n\t\t<template #tab-security>\n\t\t\t<CnSchemaSecurityTab\n\t\t\t\t:schemaItem=\"schemaItem\"\n\t\t\t\t:userGroups=\"userGroups\"\n\t\t\t\t:sortedUserGroups=\"sortedUserGroups\"\n\t\t\t\t:loadingGroups=\"loadingGroups\"\n\t\t\t\t:hasAnyPermissions=\"hasAnyPermissions\"\n\t\t\t\t:isRestrictiveSchema=\"isRestrictiveSchema\"\n\t\t\t\t:inheritedProperties=\"inheritedProperties\" />\n\t\t</template>\n\n\t\t<!-- Optional Action Buttons (edit mode only) -->\n\t\t<template #actions-right=\"{ loading: dialogLoading, isCreateMode, result: dialogResult }\">\n\t\t\t<template v-if=\"!isCreateMode && dialogResult === null\">\n\t\t\t\t<NcButton\n\t\t\t\t\tv-if=\"showExtendSchema\"\n\t\t\t\t\t:disabled=\"dialogLoading\"\n\t\t\t\t\t@click=\"$emit('extend-schema')\">\n\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t<CallSplit :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t{{ extendSchemaLabel }}\n\t\t\t\t</NcButton>\n\t\t\t\t<NcButton\n\t\t\t\t\tv-if=\"showAnalyzeProperties\"\n\t\t\t\t\t:disabled=\"dialogLoading\"\n\t\t\t\t\t@click=\"$emit('analyze-properties')\">\n\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t<DatabaseSearch :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t{{ analyzePropertiesLabel }}\n\t\t\t\t</NcButton>\n\t\t\t\t<NcButton\n\t\t\t\t\tv-if=\"showValidateObjects\"\n\t\t\t\t\t:disabled=\"dialogLoading\"\n\t\t\t\t\t@click=\"$emit('validate-objects')\">\n\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t<CheckCircle :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t{{ validateObjectsLabel }}\n\t\t\t\t</NcButton>\n\t\t\t\t<NcButton\n\t\t\t\t\tv-if=\"showDeleteObjects\"\n\t\t\t\t\t:title=\"objectCount > 0 ? deleteObjectsTooltip : noDeleteObjectsTooltip\"\n\t\t\t\t\t:disabled=\"dialogLoading || objectCount === 0\"\n\t\t\t\t\t@click=\"$emit('delete-objects')\">\n\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t<DeleteSweep :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t{{ deleteObjectsLabel }}\n\t\t\t\t</NcButton>\n\t\t\t\t<NcButton\n\t\t\t\t\tv-if=\"showPublishObjects\"\n\t\t\t\t\t:title=\"objectCount > 0 ? publishObjectsTooltip : noPublishObjectsTooltip\"\n\t\t\t\t\t:disabled=\"dialogLoading || objectCount === 0\"\n\t\t\t\t\t@click=\"$emit('publish-objects')\">\n\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t<Upload :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t{{ publishObjectsLabel }}\n\t\t\t\t</NcButton>\n\t\t\t\t<NcButton\n\t\t\t\t\tv-if=\"showDelete\"\n\t\t\t\t\t:title=\"objectCount > 0 ? cannotDeleteTooltip : ''\"\n\t\t\t\t\t:disabled=\"dialogLoading || objectCount > 0\"\n\t\t\t\t\tvariant=\"error\"\n\t\t\t\t\t@click=\"$emit('delete-schema')\">\n\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t<TrashCanOutline :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t{{ deleteLabel }}\n\t\t\t\t</NcButton>\n\t\t\t</template>\n\t\t</template>\n\t</CnTabbedFormDialog>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport {\n\tNcButton,\n\tNcTextField,\n} from '@nextcloud/vue'\nimport CallSplit from 'vue-material-design-icons/CallSplit.vue'\nimport Check from 'vue-material-design-icons/Check.vue'\nimport CheckCircle from 'vue-material-design-icons/CheckCircle.vue'\nimport ContentCopy from 'vue-material-design-icons/ContentCopy.vue'\nimport DatabaseSearch from 'vue-material-design-icons/DatabaseSearch.vue'\nimport DeleteSweep from 'vue-material-design-icons/DeleteSweep.vue'\nimport TrashCanOutline from 'vue-material-design-icons/TrashCanOutline.vue'\nimport Upload from 'vue-material-design-icons/Upload.vue'\nimport CnTabbedFormDialog from '../CnTabbedFormDialog/CnTabbedFormDialog.vue'\nimport CnSchemaConfigurationTab from './CnSchemaConfigurationTab.vue'\nimport CnSchemaPropertiesTab from './CnSchemaPropertiesTab.vue'\nimport CnSchemaSecurityTab from './CnSchemaSecurityTab.vue'\nimport { dialogBoardMixin } from '../../mixins/dialogBoard.js'\n\n/**\n * CnSchemaFormDialog — Generic JSON Schema editor dialog.\n *\n * Provides a full-featured form for creating and editing JSON Schemas with\n * properties table, configuration, and security (RBAC) tabs. Uses CnTabbedFormDialog.\n *\n * The dialog does NOT perform saves — it emits a `confirm` event with the schema data.\n * The parent performs the actual API call and calls `setResult()` via a ref.\n *\n * @event confirm Emitted when the user confirms. Payload: cleaned schema data object.\n * @event close Emitted when the dialog should be closed.\n * @event extend-schema Emitted when the Extend Schema button is clicked.\n * @event analyze-properties Emitted when the Analyze Properties button is clicked.\n * @event validate-objects Emitted when the Validate Objects button is clicked.\n * @event delete-objects Emitted when the Delete Objects button is clicked.\n * @event publish-objects Emitted when the Publish Objects button is clicked.\n * @event delete-schema Emitted when the Delete button is clicked.\n */\nexport default {\n\tname: 'CnSchemaFormDialog',\n\tcomponents: {\n\t\tNcTextField,\n\t\tNcButton,\n\t\tCnTabbedFormDialog,\n\t\tCnSchemaPropertiesTab,\n\t\tCnSchemaConfigurationTab,\n\t\tCnSchemaSecurityTab,\n\t\t// Icons\n\t\tContentCopy,\n\t\tCheck,\n\t\tTrashCanOutline,\n\t\tCallSplit,\n\t\tDatabaseSearch,\n\t\tCheckCircle,\n\t\tDeleteSweep,\n\t\tUpload,\n\t},\n\n\tmixins: [dialogBoardMixin],\n\n\tprops: {\n\t\t/** Existing schema item for edit mode. Pass null for create mode. */\n\t\titem: { type: Object, default: null },\n\t\t/** Dialog title. Defaults to \"Create Schema\" or \"Edit Schema\". */\n\t\tdialogTitle: { type: String, default: '' },\n\t\t/** NcDialog size */\n\t\tsize: { type: String, default: 'large' },\n\t\t/** Available schemas for references and composition. Array of { id, title, description, reference } */\n\t\tavailableSchemas: { type: Array, default: () => [] },\n\t\t/** Available registers. Array of { id, label } */\n\t\tavailableRegisters: { type: Array, default: () => [] },\n\t\t/** User groups for RBAC. Array of { id, displayname } */\n\t\tuserGroups: { type: Array, default: () => [] },\n\t\t/** Available tags for file property configuration */\n\t\tavailableTags: { type: Array, default: () => [] },\n\t\t/** Whether user groups are still loading */\n\t\tloadingGroups: { type: Boolean, default: false },\n\t\t/** Properties inherited from parent schemas (allOf) — shown as locked rows in the properties tab */\n\t\tinheritedProperties: { type: Object, default: () => ({}) },\n\t\t/** Number of objects attached to this schema (used for action button disable logic) */\n\t\tobjectCount: { type: Number, default: 0 },\n\t\t// Optional action button visibility\n\t\t/** Show \"Extend Schema\" button */\n\t\tshowExtendSchema: { type: Boolean, default: false },\n\t\t/** Show \"Analyze Properties\" button */\n\t\tshowAnalyzeProperties: { type: Boolean, default: false },\n\t\t/** Show \"Validate Objects\" button */\n\t\tshowValidateObjects: { type: Boolean, default: false },\n\t\t/** Show \"Delete Objects\" button */\n\t\tshowDeleteObjects: { type: Boolean, default: false },\n\t\t/** Show \"Publish Objects\" button */\n\t\tshowPublishObjects: { type: Boolean, default: false },\n\t\t/** Show \"Delete\" button */\n\t\tshowDelete: { type: Boolean, default: false },\n\t\t// Labels (pre-translated strings with English defaults)\n\t\t/** Label for the cancel button shown while editing. */\n\t\tcancelLabel: { type: String, default: () => t('nextcloud-vue', 'Cancel') },\n\t\t/** Label for the close button shown after a save or error. */\n\t\tcloseLabel: { type: String, default: () => t('nextcloud-vue', 'Close') },\n\t\t/** Confirm button label. Defaults to \"Create\" or \"Save\". */\n\t\tconfirmLabel: { type: String, default: '' },\n\t\t/** Success message. Defaults to \"Schema saved successfully.\" */\n\t\tsuccessText: { type: String, default: '' },\n\t\t/** Label for the Extend Schema action button. */\n\t\textendSchemaLabel: { type: String, default: () => t('nextcloud-vue', 'Extend schema') },\n\t\t/** Label for the Analyze Properties action button. */\n\t\tanalyzePropertiesLabel: { type: String, default: () => t('nextcloud-vue', 'Analyze properties') },\n\t\t/** Label for the Validate Objects action button. */\n\t\tvalidateObjectsLabel: { type: String, default: () => t('nextcloud-vue', 'Validate objects') },\n\t\t/** Label for the Delete Objects action button. */\n\t\tdeleteObjectsLabel: { type: String, default: () => t('nextcloud-vue', 'Delete objects') },\n\t\t/** Label for the Publish Objects action button. */\n\t\tpublishObjectsLabel: { type: String, default: () => t('nextcloud-vue', 'Publish objects') },\n\t\t/** Label for the Delete schema button shown in edit mode. */\n\t\tdeleteLabel: { type: String, default: () => t('nextcloud-vue', 'Delete') },\n\t\t/** Tooltip for the Delete Objects button when objects exist. */\n\t\tdeleteObjectsTooltip: { type: String, default: () => t('nextcloud-vue', 'Delete all objects in this schema') },\n\t\t/** Tooltip for the Publish Objects button when objects exist. */\n\t\tpublishObjectsTooltip: { type: String, default: () => t('nextcloud-vue', 'Publish all objects in this schema') },\n\t\t/** Tooltip for the Delete Objects button when no objects exist */\n\t\tnoDeleteObjectsTooltip: { type: String, default: () => t('nextcloud-vue', 'No objects to delete') },\n\t\t/** Tooltip for the Publish Objects button when no objects exist */\n\t\tnoPublishObjectsTooltip: { type: String, default: () => t('nextcloud-vue', 'No objects to publish') },\n\t\t/** Tooltip for the Delete schema button while objects are still attached. */\n\t\tcannotDeleteTooltip: { type: String, default: () => t('nextcloud-vue', 'Cannot delete: objects are still attached') },\n\t},\n\n\temits: [\n\t\t'analyze-properties',\n\t\t'close',\n\t\t'confirm',\n\t\t'delete-objects',\n\t\t'delete-schema',\n\t\t'extend-schema',\n\t\t'publish-objects',\n\t\t'validate-objects',\n\t],\n\n\tdata() {\n\t\treturn {\n\t\t\tisCopied: false,\n\t\t\tselectedProperty: null,\n\t\t\tpropertiesModified: false,\n\t\t\toriginalProperties: null,\n\t\t\tschemaItem: {\n\t\t\t\ttitle: '',\n\t\t\t\tversion: '0.0.0',\n\t\t\t\tdescription: '',\n\t\t\t\tsummary: '',\n\t\t\t\tslug: '',\n\t\t\t\tproperties: {},\n\t\t\t\tconfiguration: {\n\t\t\t\t\tobjectNameField: '',\n\t\t\t\t\tobjectDescriptionField: '',\n\t\t\t\t\tobjectImageField: '',\n\t\t\t\t\tobjectSummaryField: '',\n\t\t\t\t\tallowFiles: false,\n\t\t\t\t\tallowedTags: [],\n\t\t\t\t\tautoPublish: false,\n\t\t\t\t\tdefaultAutoShare: false,\n\t\t\t\t},\n\n\t\t\t\tauthorization: {},\n\t\t\t\thardValidation: false,\n\t\t\t\timmutable: false,\n\t\t\t\tsearchable: true,\n\t\t\t\ticon: null,\n\t\t\t\tmaxDepth: 0,\n\t\t\t},\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Tab definitions for CnTabbedFormDialog.\n\t\t *\n\t\t * @return {Array} Tab configuration\n\t\t */\n\t\tdialogTabs() {\n\t\t\treturn [\n\t\t\t\t{ id: 'properties', title: t('nextcloud-vue', 'Properties') },\n\t\t\t\t{ id: 'configuration', title: t('nextcloud-vue', 'Configuration') },\n\t\t\t\t{ id: 'security', title: t('nextcloud-vue', 'Security') },\n\t\t\t]\n\t\t},\n\n\t\tsortedUserGroups() {\n\t\t\treturn this.userGroups\n\t\t\t\t.filter((group) => group.id !== 'admin' && group.id !== 'public' && group.id !== 'authenticated')\n\t\t\t\t.sort((a, b) => {\n\t\t\t\t\tconst nameA = a.displayname || a.id\n\t\t\t\t\tconst nameB = b.displayname || b.id\n\t\t\t\t\treturn nameA.localeCompare(nameB)\n\t\t\t\t})\n\t\t},\n\n\t\thasAnyPermissions() {\n\t\t\tconst auth = this.schemaItem.authorization || {}\n\t\t\treturn Object.keys(auth).some((action) => Array.isArray(auth[action]) && auth[action].length > 0)\n\t\t},\n\n\t\tisRestrictiveSchema() {\n\t\t\tconst auth = this.schemaItem.authorization || {}\n\t\t\tconst actions = ['create', 'read', 'update', 'delete']\n\t\t\treturn actions.some((action) => Array.isArray(auth[action]) && auth[action].length > 0\n\t\t\t\t&& !auth[action].includes('public'))\n\t\t},\n\n\t\ttypeOptionsForSelect() {\n\t\t\treturn [\n\t\t\t\t{ id: 'string', label: t('nextcloud-vue', 'String') },\n\t\t\t\t{ id: 'number', label: t('nextcloud-vue', 'Number') },\n\t\t\t\t{ id: 'integer', label: t('nextcloud-vue', 'Integer') },\n\t\t\t\t{ id: 'boolean', label: t('nextcloud-vue', 'Boolean') },\n\t\t\t\t{ id: 'array', label: t('nextcloud-vue', 'Array') },\n\t\t\t\t{ id: 'object', label: t('nextcloud-vue', 'Object') },\n\t\t\t\t{ id: 'dictionary', label: t('nextcloud-vue', 'Dictionary') },\n\t\t\t\t{ id: 'file', label: t('nextcloud-vue', 'File') },\n\t\t\t\t{ id: 'oneOf', label: t('nextcloud-vue', 'One of') },\n\t\t\t]\n\t\t},\n\n\t\tpropertyOptions() {\n\t\t\tconst ownKeys = Object.keys(this.schemaItem.properties || {}).filter((k) => k !== '')\n\t\t\tconst inheritedKeys = Object.keys(this.inheritedProperties || {}).filter((k) => k !== '')\n\t\t\treturn [...new Set([...inheritedKeys, ...ownKeys])]\n\t\t},\n\n\t\tavailableTagsOptions() {\n\t\t\treturn this.availableTags.map((tag) => ({\n\t\t\t\tid: tag,\n\t\t\t\tlabel: tag,\n\t\t\t}))\n\t\t},\n\n\t\t/**\n\t\t * Resolved success text for backwards compatibility (includes trailing period).\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tresolvedSuccessText() {\n\t\t\tif (this.successText) {\n\t\t\t\treturn this.successText\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', '{title} saved successfully.', { title: t('nextcloud-vue', 'Schema') })\n\t\t},\n\n\t\t/**\n\t\t * Returns a human-readable reason the save button is disabled, or '' when saving is allowed.\n\t\t * Used for both :disable-save and the WCAG tooltip/aria-label on the button.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tsaveDisabledReason() {\n\t\t\tif (!this.schemaItem.title) {\n\t\t\t\treturn t('nextcloud-vue', 'A schema title is required before saving')\n\t\t\t}\n\t\t\tconst hasUnnamedProperty = Object.keys(this.schemaItem.properties || {}).some((key) => key === '')\n\t\t\tif (hasUnnamedProperty) {\n\t\t\t\treturn t('nextcloud-vue', 'All properties must have a name before saving')\n\t\t\t}\n\t\t\treturn ''\n\t\t},\n\n\t\tallOfSchemaNames() {\n\t\t\tif (!this.schemaItem.allOf || !Array.isArray(this.schemaItem.allOf) || this.schemaItem.allOf.length === 0) {\n\t\t\t\treturn []\n\t\t\t}\n\n\t\t\treturn this.schemaItem.allOf\n\t\t\t\t.map((ref) => {\n\t\t\t\t\tconst schemaId = typeof ref === 'object' ? ref.id : ref\n\t\t\t\t\tconst schema = this.availableSchemas.find((s) => s.id === schemaId)\n\t\t\t\t\treturn schema ? (schema.title || `Schema ${schema.id}`) : schemaId\n\t\t\t\t})\n\t\t\t\t.filter((name) => name)\n\t\t},\n\t},\n\n\twatch: {\n\t\titem: {\n\t\t\timmediate: true,\n\t\t\thandler() {\n\t\t\t\tthis.initializeSchemaItem()\n\t\t\t},\n\t\t},\n\n\t\t'schemaItem.properties': {\n\t\t\thandler(newProperties) {\n\t\t\t\tif (newProperties) {\n\t\t\t\t\tObject.keys(newProperties).forEach((key) => {\n\t\t\t\t\t\tconst property = newProperties[key]\n\t\t\t\t\t\tif (property) {\n\t\t\t\t\t\t\t// Initialize nested objects if they don't exist\n\t\t\t\t\t\t\tif (property.type === 'array' && !property.items) {\n\t\t\t\t\t\t\t\tthis.schemaItem.properties[key].items = { type: 'string' }\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (property.type === 'object' && !property.objectConfiguration) {\n\t\t\t\t\t\t\t\tthis.schemaItem.properties[key].objectConfiguration = { handling: 'nested-object' }\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (property.type === 'array' && property.items && property.items.type === 'object' && !property.items.objectConfiguration) {\n\t\t\t\t\t\t\t\tthis.schemaItem.properties[key].items.objectConfiguration = { handling: 'nested-object' }\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Convert property type from object to string\n\t\t\t\t\t\t\tif (property.type && typeof property.type === 'object' && property.type.id) {\n\t\t\t\t\t\t\t\tthis.schemaItem.properties[key].type = property.type.id\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Convert property format from object to string\n\t\t\t\t\t\t\tif (property.format && typeof property.format === 'object' && property.format.id) {\n\t\t\t\t\t\t\t\tthis.schemaItem.properties[key].format = property.format.id\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Convert array item type from object to string\n\t\t\t\t\t\t\tif (property.items && property.items.type && typeof property.items.type === 'object' && property.items.type.id) {\n\t\t\t\t\t\t\t\tthis.schemaItem.properties[key].items.type = property.items.type.id\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Convert object handling from object to string\n\t\t\t\t\t\t\tif (property.objectConfiguration && property.objectConfiguration.handling\n\t\t\t\t\t\t\t\t&& typeof property.objectConfiguration.handling === 'object' && property.objectConfiguration.handling.id) {\n\t\t\t\t\t\t\t\tthis.schemaItem.properties[key].objectConfiguration.handling = property.objectConfiguration.handling.id\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Convert register from object to ID\n\t\t\t\t\t\t\tif (property.objectConfiguration && property.objectConfiguration.register\n\t\t\t\t\t\t\t\t&& typeof property.objectConfiguration.register === 'object' && property.objectConfiguration.register.id) {\n\t\t\t\t\t\t\t\tthis.schemaItem.properties[key].objectConfiguration.register = property.objectConfiguration.register.id\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Convert array item object handling from object to string\n\t\t\t\t\t\t\tif (property.items && property.items.objectConfiguration && property.items.objectConfiguration.handling\n\t\t\t\t\t\t\t\t&& typeof property.items.objectConfiguration.handling === 'object' && property.items.objectConfiguration.handling.id) {\n\t\t\t\t\t\t\t\tthis.schemaItem.properties[key].items.objectConfiguration.handling = property.items.objectConfiguration.handling.id\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Convert array item register from object to ID\n\t\t\t\t\t\t\tif (property.items && property.items.objectConfiguration && property.items.objectConfiguration.register\n\t\t\t\t\t\t\t\t&& typeof property.items.objectConfiguration.register === 'object' && property.items.objectConfiguration.register.id) {\n\t\t\t\t\t\t\t\tthis.schemaItem.properties[key].items.objectConfiguration.register = property.items.objectConfiguration.register.id\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Ensure $ref is always a string\n\t\t\t\t\t\t\tthis.ensureRefIsString(this.schemaItem.properties, key)\n\n\t\t\t\t\t\t\t// Ensure inversedBy is always a string for regular properties\n\t\t\t\t\t\t\tif (property.inversedBy && typeof property.inversedBy === 'object' && property.inversedBy.id) {\n\t\t\t\t\t\t\t\tthis.schemaItem.properties[key].inversedBy = property.inversedBy.id\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Ensure inversedBy is always a string for array items\n\t\t\t\t\t\t\tif (property.items && property.items.inversedBy && typeof property.items.inversedBy === 'object' && property.items.inversedBy.id) {\n\t\t\t\t\t\t\t\tthis.schemaItem.properties[key].items.inversedBy = property.items.inversedBy.id\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t\tthis.checkPropertiesModified()\n\t\t\t},\n\n\t\t\tdeep: true,\n\t\t},\n\t},\n\n\tmethods: {\n\t\tt,\n\t\tfindSchemaBySlug(schemaSlug) {\n\t\t\tif (!schemaSlug) {\n\t\t\t\treturn undefined\n\t\t\t}\n\t\t\treturn this.availableSchemas.find((schema) => (schema.slug && schema.slug.toLowerCase() === schemaSlug.toLowerCase())\n\t\t\t\t|| schema.id === schemaSlug\n\t\t\t\t|| schema.title === schemaSlug)\n\t\t},\n\n\t\tensureRefIsString(obj, key) {\n\t\t\tif (!obj || !key) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif (obj[key] && typeof obj[key].$ref === 'object' && obj[key].$ref !== null) {\n\t\t\t\tif (obj[key].$ref.id) {\n\t\t\t\t\tobj[key].$ref = obj[key].$ref.id\n\t\t\t\t} else {\n\t\t\t\t\tobj[key].$ref = ''\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (obj[key] && obj[key].items && typeof obj[key].items.$ref === 'object' && obj[key].items.$ref !== null) {\n\t\t\t\tif (obj[key].items.$ref.id) {\n\t\t\t\t\tobj[key].items.$ref = obj[key].items.$ref.id\n\t\t\t\t} else {\n\t\t\t\t\tobj[key].items.$ref = ''\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\tinitializeSchemaItem() {\n\t\t\t// Reset CnTabbedFormDialog state if available (not yet mounted on first call)\n\t\t\tif (this.$refs.dialog) {\n\t\t\t\tthis.$refs.dialog.resetDialog()\n\t\t\t}\n\n\t\t\t// Always rebuild schemaItem from defaults + incoming item. This handles\n\t\t\t// create mode (item null), edit mode (item with id), and extend mode\n\t\t\t// (item non-null with no id) without leaking stale state across opens.\n\t\t\tconst defaults = {\n\t\t\t\ttitle: '',\n\t\t\t\tversion: '0.0.0',\n\t\t\t\tdescription: '',\n\t\t\t\tsummary: '',\n\t\t\t\tslug: '',\n\t\t\t\tproperties: {},\n\t\t\t\tconfiguration: {\n\t\t\t\t\tobjectNameField: '',\n\t\t\t\t\tobjectDescriptionField: '',\n\t\t\t\t\tobjectImageField: '',\n\t\t\t\t\tobjectSummaryField: '',\n\t\t\t\t\tallowFiles: false,\n\t\t\t\t\tallowedTags: [],\n\t\t\t\t\tautoPublish: false,\n\t\t\t\t\tdefaultAutoShare: false,\n\t\t\t\t},\n\n\t\t\t\tauthorization: {},\n\t\t\t\thardValidation: false,\n\t\t\t\timmutable: false,\n\t\t\t\tsearchable: true,\n\t\t\t\ticon: null,\n\t\t\t\tmaxDepth: 0,\n\t\t\t}\n\t\t\tthis.schemaItem = this.item\n\t\t\t\t? { ...defaults, ...JSON.parse(JSON.stringify(this.item)) }\n\t\t\t\t: { ...defaults }\n\n\t\t\t// Ensure configuration object has all expected keys (the spread above may\n\t\t\t// have replaced our defaults with a partial configuration from the item)\n\t\t\tif (!this.schemaItem.configuration) {\n\t\t\t\tthis.schemaItem.configuration = { ...defaults.configuration }\n\t\t\t} else {\n\t\t\t\tif (!this.schemaItem.configuration.objectNameField) {\n\t\t\t\t\tthis.schemaItem.configuration.objectNameField = ''\n\t\t\t\t}\n\t\t\t\tif (!this.schemaItem.configuration.objectDescriptionField) {\n\t\t\t\t\tthis.schemaItem.configuration.objectDescriptionField = ''\n\t\t\t\t}\n\t\t\t\tif (!this.schemaItem.configuration.objectImageField) {\n\t\t\t\t\tthis.schemaItem.configuration.objectImageField = ''\n\t\t\t\t}\n\t\t\t\tif (!this.schemaItem.configuration.objectSummaryField) {\n\t\t\t\t\tthis.schemaItem.configuration.objectSummaryField = ''\n\t\t\t\t}\n\t\t\t\tif (this.schemaItem.configuration.allowFiles === undefined) {\n\t\t\t\t\tthis.schemaItem.configuration.allowFiles = false\n\t\t\t\t}\n\t\t\t\tif (!this.schemaItem.configuration.allowedTags) {\n\t\t\t\t\tthis.schemaItem.configuration.allowedTags = []\n\t\t\t\t}\n\t\t\t\tif (this.schemaItem.configuration.autoPublish === undefined) {\n\t\t\t\t\tthis.schemaItem.configuration.autoPublish = false\n\t\t\t\t}\n\t\t\t\tif (this.schemaItem.configuration.defaultAutoShare === undefined) {\n\t\t\t\t\tthis.schemaItem.configuration.defaultAutoShare = false\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Ensure authorization object exists\n\t\t\tif (!this.schemaItem.authorization) {\n\t\t\t\tthis.schemaItem.authorization = {}\n\t\t\t}\n\n\t\t\t// Ensure existing properties have facetable set to false by default\n\t\t\tObject.keys(this.schemaItem.properties || {}).forEach((key) => {\n\t\t\t\tif (this.schemaItem.properties[key].facetable === undefined) {\n\t\t\t\t\tthis.schemaItem.properties[key].facetable = false\n\t\t\t\t}\n\n\t\t\t\tif (this.schemaItem.properties[key].enum && Array.isArray(this.schemaItem.properties[key].enum)) {\n\t\t\t\t\tthis.schemaItem.properties[key].enum = [...this.schemaItem.properties[key].enum]\n\t\t\t\t}\n\n\t\t\t\tconst property = this.schemaItem.properties[key]\n\t\t\t\tif (property.type === 'array' && property.items && property.items.type === 'object' && !property.items.objectConfiguration) {\n\t\t\t\t\tthis.schemaItem.properties[key].items.objectConfiguration = { handling: 'nested-object' }\n\t\t\t\t}\n\t\t\t})\n\n\t\t\t// Ensure all $ref values are strings and migrate old structure\n\t\t\tObject.keys(this.schemaItem.properties || {}).forEach((key) => {\n\t\t\t\tthis.ensureRefIsString(this.schemaItem.properties, key)\n\t\t\t\tthis.migratePropertyToNewStructure(key)\n\t\t\t})\n\n\t\t\t// Snapshot original properties for change detection. For new/extending\n\t\t\t// items (no id), there's no \"original\" — start empty so any added\n\t\t\t// properties register as modifications.\n\t\t\tthis.originalProperties = this.schemaItem.id\n\t\t\t\t? JSON.parse(JSON.stringify(this.schemaItem.properties || {}))\n\t\t\t\t: {}\n\n\t\t\tthis.propertiesModified = false\n\t\t},\n\n\t\tcheckPropertiesModified() {\n\t\t\tif (!this.originalProperties) {\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\tconst currentProperties = JSON.stringify(this.schemaItem.properties || {})\n\t\t\tconst originalProperties = JSON.stringify(this.originalProperties)\n\n\t\t\tthis.propertiesModified = currentProperties !== originalProperties\n\t\t},\n\n\t\tasync copyToClipboard(text) {\n\t\t\ttry {\n\t\t\t\tawait navigator.clipboard.writeText(text)\n\t\t\t\tthis.isCopied = true\n\t\t\t\tsetTimeout(() => {\n\t\t\t\t\tthis.isCopied = false\n\t\t\t\t}, 2000)\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console -- the failure is already handled; the console is the only channel a host app can read the detail on\n\t\t\t\tconsole.error('Failed to copy text:', err)\n\t\t\t}\n\t\t},\n\n\t\taddProperty() {\n\t\t\tthis.schemaItem.properties[''] = {\n\t\t\t\ttype: 'string',\n\t\t\t\tformat: '',\n\t\t\t\ttitle: '',\n\t\t\t\tdescription: '',\n\t\t\t\tfacetable: false,\n\t\t\t}\n\n\t\t\tthis.checkPropertiesModified()\n\t\t\tthis.selectedProperty = ''\n\t\t},\n\n\t\tupdatePropertyKey(oldKey, newKey) {\n\t\t\tif (newKey === oldKey) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (this.schemaItem.properties[newKey] !== undefined && newKey !== oldKey) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tconst propertyData = { ...this.schemaItem.properties[oldKey] }\n\n\t\t\tthis.schemaItem.properties[newKey] = propertyData\n\t\t\tdelete this.schemaItem.properties[oldKey]\n\n\t\t\tthis.selectedProperty = newKey\n\t\t\tthis.checkPropertiesModified()\n\t\t},\n\n\t\tdeleteProperty(key) {\n\t\t\tdelete this.schemaItem.properties[key]\n\n\t\t\tif (this.selectedProperty === key) {\n\t\t\t\tthis.selectedProperty = null\n\t\t\t}\n\n\t\t\tthis.checkPropertiesModified()\n\t\t},\n\n\t\tcopyProperty(key) {\n\t\t\tif (this.schemaItem.properties[key]) {\n\t\t\t\tconst originalProperty = JSON.parse(JSON.stringify(this.schemaItem.properties[key]))\n\n\t\t\t\tlet newPropertyName = `${key}_copy`\n\t\t\t\tlet counter = 1\n\n\t\t\t\twhile (this.schemaItem.properties[newPropertyName]) {\n\t\t\t\t\tcounter++\n\t\t\t\t\tnewPropertyName = `${key}_copy_${counter}`\n\t\t\t\t}\n\n\t\t\t\tconst originalTitle = originalProperty.title || key\n\t\t\t\tthis.schemaItem.properties[newPropertyName] = {\n\t\t\t\t\t...originalProperty,\n\t\t\t\t\ttitle: `${originalTitle} (copy)`,\n\t\t\t\t}\n\n\t\t\t\tthis.checkPropertiesModified()\n\t\t\t\tthis.selectedProperty = newPropertyName\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Handle confirm from CnTabbedFormDialog.\n\t\t * Cleans schema data and emits confirm with the cleaned payload.\n\t\t */\n\t\thandleConfirm() {\n\t\t\tconst cleanedSchemaItem = JSON.parse(JSON.stringify(this.schemaItem))\n\t\t\tObject.keys(cleanedSchemaItem.properties || {}).forEach((key) => {\n\t\t\t\tthis.ensureRefIsString(cleanedSchemaItem.properties, key)\n\n\t\t\t\tif (cleanedSchemaItem.properties[key].register\n\t\t\t\t\t&& cleanedSchemaItem.properties[key].objectConfiguration\n\t\t\t\t\t&& cleanedSchemaItem.properties[key].objectConfiguration.register) {\n\t\t\t\t\tdelete cleanedSchemaItem.properties[key].register\n\t\t\t\t}\n\n\t\t\t\tif (cleanedSchemaItem.properties[key].items\n\t\t\t\t\t&& cleanedSchemaItem.properties[key].items.register\n\t\t\t\t\t&& cleanedSchemaItem.properties[key].items.objectConfiguration\n\t\t\t\t\t&& cleanedSchemaItem.properties[key].items.objectConfiguration.register) {\n\t\t\t\t\tdelete cleanedSchemaItem.properties[key].items.register\n\t\t\t\t}\n\t\t\t})\n\n\t\t\t// NcSelect (track-by=\"id\") can convert plain IDs to full option objects.\n\t\t\t// Normalise back to plain IDs before emitting so the backend always gets scalars.\n\t\t\tfor (const field of ['allOf', 'oneOf', 'anyOf']) {\n\t\t\t\tif (Array.isArray(cleanedSchemaItem[field])) {\n\t\t\t\t\tcleanedSchemaItem[field] = cleanedSchemaItem[field]\n\t\t\t\t\t\t.map((ref) => (typeof ref === 'object' && ref !== null ? ref.id : ref))\n\t\t\t\t\t\t.filter((id) => id !== null && id !== undefined && id !== '')\n\t\t\t\t\tif (cleanedSchemaItem[field].length === 0) {\n\t\t\t\t\t\tdelete cleanedSchemaItem[field]\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tthis.$emit('confirm', cleanedSchemaItem)\n\t\t},\n\n\t\t/**\n\t\t * Set the result of the save operation. Call this from the parent\n\t\t * after the API call completes.\n\t\t *\n\t\t * @param {{ success?: boolean, error?: string }} resultData - result data\n\t\t * @public\n\t\t */\n\t\tsetResult(resultData) {\n\t\t\tthis.$refs.dialog.setResult(resultData)\n\t\t\tif (resultData.success) {\n\t\t\t\tthis.originalProperties = JSON.parse(JSON.stringify(this.schemaItem.properties || {}))\n\t\t\t\tthis.propertiesModified = false\n\t\t\t}\n\t\t},\n\n\t\tmigratePropertyToNewStructure(key) {\n\t\t\tif (!this.schemaItem.properties[key]) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tconst property = this.schemaItem.properties[key]\n\n\t\t\tif (property.$ref && property.register && !property.objectConfiguration?.register) {\n\t\t\t\tif (!property.objectConfiguration) {\n\t\t\t\t\tthis.schemaItem.properties[key].objectConfiguration = { handling: 'related-object' }\n\t\t\t\t}\n\n\t\t\t\tconst registerId = typeof property.register === 'object' && property.register.id\n\t\t\t\t\t? property.register.id\n\t\t\t\t\t: property.register\n\n\t\t\t\tthis.schemaItem.properties[key].objectConfiguration.register = registerId\n\n\t\t\t\tif (property.$ref) {\n\t\t\t\t\tlet schemaSlug = property.$ref\n\t\t\t\t\tif (schemaSlug.includes('/')) {\n\t\t\t\t\t\tschemaSlug = schemaSlug.substring(schemaSlug.lastIndexOf('/') + 1)\n\t\t\t\t\t}\n\n\t\t\t\t\tconst referencedSchema = this.findSchemaBySlug(schemaSlug)\n\t\t\t\t\tif (referencedSchema) {\n\t\t\t\t\t\tthis.schemaItem.properties[key].objectConfiguration.schema = referencedSchema.id\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (property.items && property.items.$ref && property.items.register && !property.items.objectConfiguration?.register) {\n\t\t\t\tif (!property.items.objectConfiguration) {\n\t\t\t\t\tthis.schemaItem.properties[key].items.objectConfiguration = { handling: 'related-object' }\n\t\t\t\t}\n\n\t\t\t\tconst registerId = typeof property.items.register === 'object' && property.items.register.id\n\t\t\t\t\t? property.items.register.id\n\t\t\t\t\t: property.items.register\n\n\t\t\t\tthis.schemaItem.properties[key].items.objectConfiguration.register = registerId\n\n\t\t\t\tif (property.items.$ref) {\n\t\t\t\t\tlet schemaSlug = property.items.$ref\n\t\t\t\t\tif (schemaSlug.includes('/')) {\n\t\t\t\t\t\tschemaSlug = schemaSlug.substring(schemaSlug.lastIndexOf('/') + 1)\n\t\t\t\t\t}\n\n\t\t\t\t\tconst referencedSchema = this.findSchemaBySlug(schemaSlug)\n\t\t\t\t\tif (referencedSchema) {\n\t\t\t\t\t\tthis.schemaItem.properties[key].items.objectConfiguration.schema = referencedSchema.id\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n"],"names":["_createBlock","_withCtx","_createElementVNode","_openBlock","_createElementBlock","_toDisplayString","_createVNode","_createTextVNode","_Fragment"],"mappings":"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"}
1
+ {"version":3,"file":"CnSchemaFormDialog.vue3.js","sources":["../../../../src/components/CnSchemaFormDialog/CnSchemaFormDialog.vue?vue&type=template&id=3cba1325&lang.js"],"sourcesContent":["<template>\n\t<CnTabbedFormDialog\n\t\tref=\"dialog\"\n\t\t:look=\"look\"\n\t\t:width=\"width\"\n\t\t:eyebrow=\"eyebrow\"\n\t\t:subtitle=\"subtitle\"\n\t\t:tabs=\"dialogTabs\"\n\t\t:item=\"item\"\n\t\t:dialogTitle=\"dialogTitle\"\n\t\tentityName=\"Schema\"\n\t\t:size=\"size\"\n\t\t:disableSave=\"!!saveDisabledReason\"\n\t\t:disableSaveTooltip=\"saveDisabledReason\"\n\t\t:successText=\"resolvedSuccessText\"\n\t\t:cancelLabel=\"cancelLabel\"\n\t\t:closeLabel=\"closeLabel\"\n\t\t:confirmLabel=\"confirmLabel\"\n\t\t@confirm=\"handleConfirm\"\n\t\t@close=\"$emit('close')\">\n\t\t<!-- Metadata Display -->\n\t\t<template #above-tabs=\"{ loading: dialogLoading }\">\n\t\t\t<div class=\"cn-schema-form__detail-grid\">\n\t\t\t\t<div v-if=\"schemaItem.id\"\n\t\t\t\t\tclass=\"cn-schema-form__detail-item cn-schema-form__id-card\">\n\t\t\t\t\t<div class=\"cn-schema-form__id-card-header\">\n\t\t\t\t\t\t<span class=\"cn-schema-form__detail-label\">{{ t('nextcloud-vue', 'ID / UUID:') }}</span>\n\t\t\t\t\t\t<NcButton class=\"cn-schema-form__copy-button\"\n\t\t\t\t\t\t\t@click=\"copyToClipboard(schemaItem.uuid || schemaItem.id)\">\n\t\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t\t<Check v-if=\"isCopied\" :size=\"20\" />\n\t\t\t\t\t\t\t\t<ContentCopy v-else :size=\"20\" />\n\t\t\t\t\t\t\t</template>\n\t\t\t\t\t\t\t{{ isCopied ? t('nextcloud-vue', 'Copied') : t('nextcloud-vue', 'Copy') }}\n\t\t\t\t\t\t</NcButton>\n\t\t\t\t\t</div>\n\t\t\t\t\t<span class=\"cn-schema-form__detail-value\">{{ schemaItem.id }}</span>\n\t\t\t\t\t<span v-if=\"schemaItem.uuid && schemaItem.uuid !== schemaItem.id\"\n\t\t\t\t\t\tclass=\"cn-schema-form__detail-value cn-schema-form__uuid-value\">{{ schemaItem.uuid }}</span>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"cn-schema-form__detail-item cn-schema-form__title-with-badge\">\n\t\t\t\t\t<NcTextField v-model=\"schemaItem.title\"\n\t\t\t\t\t\t:disabled=\"dialogLoading\"\n\t\t\t\t\t\t:label=\"t('nextcloud-vue', 'Title *')\" />\n\t\t\t\t\t<span v-if=\"schemaItem.allOf && schemaItem.allOf.length > 0\"\n\t\t\t\t\t\tclass=\"cn-schema-form__statusPill cn-schema-form__statusPill--success\">\n\t\t\t\t\t\tallOf\n\t\t\t\t\t</span>\n\t\t\t\t\t<span v-if=\"schemaItem.oneOf && schemaItem.oneOf.length > 0\"\n\t\t\t\t\t\tclass=\"cn-schema-form__statusPill cn-schema-form__statusPill--info\">\n\t\t\t\t\t\toneOf\n\t\t\t\t\t</span>\n\t\t\t\t\t<span v-if=\"schemaItem.anyOf && schemaItem.anyOf.length > 0\"\n\t\t\t\t\t\tclass=\"cn-schema-form__statusPill cn-schema-form__statusPill--info\">\n\t\t\t\t\t\tanyOf\n\t\t\t\t\t</span>\n\t\t\t\t</div>\n\t\t\t\t<div v-if=\"schemaItem.created\" class=\"cn-schema-form__detail-item\">\n\t\t\t\t\t<span class=\"cn-schema-form__detail-label\">{{ t('nextcloud-vue', 'Created:') }}</span>\n\t\t\t\t\t<span class=\"cn-schema-form__detail-value\">{{ new Date(schemaItem.created).toLocaleString(userLocale()) }}</span>\n\t\t\t\t</div>\n\t\t\t\t<div v-if=\"schemaItem.updated\" class=\"cn-schema-form__detail-item\">\n\t\t\t\t\t<span class=\"cn-schema-form__detail-label\">{{ t('nextcloud-vue', 'Updated:') }}</span>\n\t\t\t\t\t<span class=\"cn-schema-form__detail-value\">{{ new Date(schemaItem.updated).toLocaleString(userLocale()) }}</span>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"cn-schema-form__detail-item\">\n\t\t\t\t\t<span class=\"cn-schema-form__detail-label\">{{ t('nextcloud-vue', 'Version:') }}</span>\n\t\t\t\t\t<span class=\"cn-schema-form__detail-value\">{{ schemaItem.version || t('nextcloud-vue', 'Not set') }}</span>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"cn-schema-form__detail-item\">\n\t\t\t\t\t<span class=\"cn-schema-form__detail-label\">{{ t('nextcloud-vue', 'Owner:') }}</span>\n\t\t\t\t\t<span class=\"cn-schema-form__detail-value\">{{ schemaItem.owner || t('nextcloud-vue', 'Not set') }}</span>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</template>\n\n\t\t<!-- Properties Tab -->\n\t\t<template #tab-properties=\"{ loading: dialogLoading }\">\n\t\t\t<CnSchemaPropertiesTab\n\t\t\t\t:schemaItem=\"schemaItem\"\n\t\t\t\t:loading=\"dialogLoading\"\n\t\t\t\t:selectedProperty=\"selectedProperty\"\n\t\t\t\t:propertiesModified=\"propertiesModified\"\n\t\t\t\t:originalProperties=\"originalProperties\"\n\t\t\t\t:inheritedProperties=\"inheritedProperties\"\n\t\t\t\t:typeOptionsForSelect=\"typeOptionsForSelect\"\n\t\t\t\t:availableSchemas=\"availableSchemas\"\n\t\t\t\t:availableRegisters=\"availableRegisters\"\n\t\t\t\t:availableTagsOptions=\"availableTagsOptions\"\n\t\t\t\t:userGroups=\"userGroups\"\n\t\t\t\t:sortedUserGroups=\"sortedUserGroups\"\n\t\t\t\t:loadingGroups=\"loadingGroups\"\n\t\t\t\t@addProperty=\"addProperty\"\n\t\t\t\t@update:selectedProperty=\"selectedProperty = $event\"\n\t\t\t\t@update:propertyKey=\"updatePropertyKey($event.oldKey, $event.newKey)\"\n\t\t\t\t@copyProperty=\"copyProperty\"\n\t\t\t\t@deleteProperty=\"deleteProperty\" />\n\t\t</template>\n\n\t\t<!-- Configuration Tab -->\n\t\t<template #tab-configuration=\"{ loading: dialogLoading }\">\n\t\t\t<CnSchemaConfigurationTab\n\t\t\t\t:schemaItem=\"schemaItem\"\n\t\t\t\t:loading=\"dialogLoading\"\n\t\t\t\t:availableSchemas=\"availableSchemas\"\n\t\t\t\t:propertyOptions=\"propertyOptions\"\n\t\t\t\t:allOfSchemaNames=\"allOfSchemaNames\" />\n\t\t</template>\n\n\t\t<!-- Security Tab -->\n\t\t<template #tab-security>\n\t\t\t<CnSchemaSecurityTab\n\t\t\t\t:schemaItem=\"schemaItem\"\n\t\t\t\t:userGroups=\"userGroups\"\n\t\t\t\t:sortedUserGroups=\"sortedUserGroups\"\n\t\t\t\t:loadingGroups=\"loadingGroups\"\n\t\t\t\t:hasAnyPermissions=\"hasAnyPermissions\"\n\t\t\t\t:isRestrictiveSchema=\"isRestrictiveSchema\"\n\t\t\t\t:inheritedProperties=\"inheritedProperties\" />\n\t\t</template>\n\n\t\t<!-- Optional Action Buttons (edit mode only) -->\n\t\t<template #actions-right=\"{ loading: dialogLoading, isCreateMode, result: dialogResult }\">\n\t\t\t<template v-if=\"!isCreateMode && dialogResult === null\">\n\t\t\t\t<NcButton\n\t\t\t\t\tv-if=\"showExtendSchema\"\n\t\t\t\t\t:disabled=\"dialogLoading\"\n\t\t\t\t\t@click=\"$emit('extend-schema')\">\n\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t<CallSplit :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t{{ extendSchemaLabel }}\n\t\t\t\t</NcButton>\n\t\t\t\t<NcButton\n\t\t\t\t\tv-if=\"showAnalyzeProperties\"\n\t\t\t\t\t:disabled=\"dialogLoading\"\n\t\t\t\t\t@click=\"$emit('analyze-properties')\">\n\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t<DatabaseSearch :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t{{ analyzePropertiesLabel }}\n\t\t\t\t</NcButton>\n\t\t\t\t<NcButton\n\t\t\t\t\tv-if=\"showValidateObjects\"\n\t\t\t\t\t:disabled=\"dialogLoading\"\n\t\t\t\t\t@click=\"$emit('validate-objects')\">\n\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t<CheckCircle :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t{{ validateObjectsLabel }}\n\t\t\t\t</NcButton>\n\t\t\t\t<NcButton\n\t\t\t\t\tv-if=\"showDeleteObjects\"\n\t\t\t\t\t:title=\"objectCount > 0 ? deleteObjectsTooltip : noDeleteObjectsTooltip\"\n\t\t\t\t\t:disabled=\"dialogLoading || objectCount === 0\"\n\t\t\t\t\t@click=\"$emit('delete-objects')\">\n\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t<DeleteSweep :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t{{ deleteObjectsLabel }}\n\t\t\t\t</NcButton>\n\t\t\t\t<NcButton\n\t\t\t\t\tv-if=\"showPublishObjects\"\n\t\t\t\t\t:title=\"objectCount > 0 ? publishObjectsTooltip : noPublishObjectsTooltip\"\n\t\t\t\t\t:disabled=\"dialogLoading || objectCount === 0\"\n\t\t\t\t\t@click=\"$emit('publish-objects')\">\n\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t<Upload :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t{{ publishObjectsLabel }}\n\t\t\t\t</NcButton>\n\t\t\t\t<NcButton\n\t\t\t\t\tv-if=\"showDelete\"\n\t\t\t\t\t:title=\"objectCount > 0 ? cannotDeleteTooltip : ''\"\n\t\t\t\t\t:disabled=\"dialogLoading || objectCount > 0\"\n\t\t\t\t\tvariant=\"error\"\n\t\t\t\t\t@click=\"$emit('delete-schema')\">\n\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t<TrashCanOutline :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t{{ deleteLabel }}\n\t\t\t\t</NcButton>\n\t\t\t</template>\n\t\t</template>\n\t</CnTabbedFormDialog>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport {\n\tNcButton,\n\tNcTextField,\n} from '@nextcloud/vue'\nimport CallSplit from 'vue-material-design-icons/CallSplit.vue'\nimport Check from 'vue-material-design-icons/Check.vue'\nimport CheckCircle from 'vue-material-design-icons/CheckCircle.vue'\nimport ContentCopy from 'vue-material-design-icons/ContentCopy.vue'\nimport DatabaseSearch from 'vue-material-design-icons/DatabaseSearch.vue'\nimport DeleteSweep from 'vue-material-design-icons/DeleteSweep.vue'\nimport TrashCanOutline from 'vue-material-design-icons/TrashCanOutline.vue'\nimport Upload from 'vue-material-design-icons/Upload.vue'\nimport CnTabbedFormDialog from '../CnTabbedFormDialog/CnTabbedFormDialog.vue'\nimport CnSchemaConfigurationTab from './CnSchemaConfigurationTab.vue'\nimport CnSchemaPropertiesTab from './CnSchemaPropertiesTab.vue'\nimport CnSchemaSecurityTab from './CnSchemaSecurityTab.vue'\nimport { dialogBoardMixin } from '../../mixins/dialogBoard.js'\nimport { userLocale } from '../../utils/userLocale.js'\n\n/**\n * CnSchemaFormDialog — Generic JSON Schema editor dialog.\n *\n * Provides a full-featured form for creating and editing JSON Schemas with\n * properties table, configuration, and security (RBAC) tabs. Uses CnTabbedFormDialog.\n *\n * The dialog does NOT perform saves — it emits a `confirm` event with the schema data.\n * The parent performs the actual API call and calls `setResult()` via a ref.\n *\n * @event confirm Emitted when the user confirms. Payload: cleaned schema data object.\n * @event close Emitted when the dialog should be closed.\n * @event extend-schema Emitted when the Extend Schema button is clicked.\n * @event analyze-properties Emitted when the Analyze Properties button is clicked.\n * @event validate-objects Emitted when the Validate Objects button is clicked.\n * @event delete-objects Emitted when the Delete Objects button is clicked.\n * @event publish-objects Emitted when the Publish Objects button is clicked.\n * @event delete-schema Emitted when the Delete button is clicked.\n */\nexport default {\n\tname: 'CnSchemaFormDialog',\n\tcomponents: {\n\t\tNcTextField,\n\t\tNcButton,\n\t\tCnTabbedFormDialog,\n\t\tCnSchemaPropertiesTab,\n\t\tCnSchemaConfigurationTab,\n\t\tCnSchemaSecurityTab,\n\t\t// Icons\n\t\tContentCopy,\n\t\tCheck,\n\t\tTrashCanOutline,\n\t\tCallSplit,\n\t\tDatabaseSearch,\n\t\tCheckCircle,\n\t\tDeleteSweep,\n\t\tUpload,\n\t},\n\n\tmixins: [dialogBoardMixin],\n\n\tprops: {\n\t\t/** Existing schema item for edit mode. Pass null for create mode. */\n\t\titem: { type: Object, default: null },\n\t\t/** Dialog title. Defaults to \"Create Schema\" or \"Edit Schema\". */\n\t\tdialogTitle: { type: String, default: '' },\n\t\t/** NcDialog size */\n\t\tsize: { type: String, default: 'large' },\n\t\t/** Available schemas for references and composition. Array of { id, title, description, reference } */\n\t\tavailableSchemas: { type: Array, default: () => [] },\n\t\t/** Available registers. Array of { id, label } */\n\t\tavailableRegisters: { type: Array, default: () => [] },\n\t\t/** User groups for RBAC. Array of { id, displayname } */\n\t\tuserGroups: { type: Array, default: () => [] },\n\t\t/** Available tags for file property configuration */\n\t\tavailableTags: { type: Array, default: () => [] },\n\t\t/** Whether user groups are still loading */\n\t\tloadingGroups: { type: Boolean, default: false },\n\t\t/** Properties inherited from parent schemas (allOf) — shown as locked rows in the properties tab */\n\t\tinheritedProperties: { type: Object, default: () => ({}) },\n\t\t/** Number of objects attached to this schema (used for action button disable logic) */\n\t\tobjectCount: { type: Number, default: 0 },\n\t\t// Optional action button visibility\n\t\t/** Show \"Extend Schema\" button */\n\t\tshowExtendSchema: { type: Boolean, default: false },\n\t\t/** Show \"Analyze Properties\" button */\n\t\tshowAnalyzeProperties: { type: Boolean, default: false },\n\t\t/** Show \"Validate Objects\" button */\n\t\tshowValidateObjects: { type: Boolean, default: false },\n\t\t/** Show \"Delete Objects\" button */\n\t\tshowDeleteObjects: { type: Boolean, default: false },\n\t\t/** Show \"Publish Objects\" button */\n\t\tshowPublishObjects: { type: Boolean, default: false },\n\t\t/** Show \"Delete\" button */\n\t\tshowDelete: { type: Boolean, default: false },\n\t\t// Labels (pre-translated strings with English defaults)\n\t\t/** Label for the cancel button shown while editing. */\n\t\tcancelLabel: { type: String, default: () => t('nextcloud-vue', 'Cancel') },\n\t\t/** Label for the close button shown after a save or error. */\n\t\tcloseLabel: { type: String, default: () => t('nextcloud-vue', 'Close') },\n\t\t/** Confirm button label. Defaults to \"Create\" or \"Save\". */\n\t\tconfirmLabel: { type: String, default: '' },\n\t\t/** Success message. Defaults to \"Schema saved successfully.\" */\n\t\tsuccessText: { type: String, default: '' },\n\t\t/** Label for the Extend Schema action button. */\n\t\textendSchemaLabel: { type: String, default: () => t('nextcloud-vue', 'Extend schema') },\n\t\t/** Label for the Analyze Properties action button. */\n\t\tanalyzePropertiesLabel: { type: String, default: () => t('nextcloud-vue', 'Analyze properties') },\n\t\t/** Label for the Validate Objects action button. */\n\t\tvalidateObjectsLabel: { type: String, default: () => t('nextcloud-vue', 'Validate objects') },\n\t\t/** Label for the Delete Objects action button. */\n\t\tdeleteObjectsLabel: { type: String, default: () => t('nextcloud-vue', 'Delete objects') },\n\t\t/** Label for the Publish Objects action button. */\n\t\tpublishObjectsLabel: { type: String, default: () => t('nextcloud-vue', 'Publish objects') },\n\t\t/** Label for the Delete schema button shown in edit mode. */\n\t\tdeleteLabel: { type: String, default: () => t('nextcloud-vue', 'Delete') },\n\t\t/** Tooltip for the Delete Objects button when objects exist. */\n\t\tdeleteObjectsTooltip: { type: String, default: () => t('nextcloud-vue', 'Delete all objects in this schema') },\n\t\t/** Tooltip for the Publish Objects button when objects exist. */\n\t\tpublishObjectsTooltip: { type: String, default: () => t('nextcloud-vue', 'Publish all objects in this schema') },\n\t\t/** Tooltip for the Delete Objects button when no objects exist */\n\t\tnoDeleteObjectsTooltip: { type: String, default: () => t('nextcloud-vue', 'No objects to delete') },\n\t\t/** Tooltip for the Publish Objects button when no objects exist */\n\t\tnoPublishObjectsTooltip: { type: String, default: () => t('nextcloud-vue', 'No objects to publish') },\n\t\t/** Tooltip for the Delete schema button while objects are still attached. */\n\t\tcannotDeleteTooltip: { type: String, default: () => t('nextcloud-vue', 'Cannot delete: objects are still attached') },\n\t},\n\n\temits: [\n\t\t'analyze-properties',\n\t\t'close',\n\t\t'confirm',\n\t\t'delete-objects',\n\t\t'delete-schema',\n\t\t'extend-schema',\n\t\t'publish-objects',\n\t\t'validate-objects',\n\t],\n\n\tdata() {\n\t\treturn {\n\t\t\tisCopied: false,\n\t\t\tselectedProperty: null,\n\t\t\tpropertiesModified: false,\n\t\t\toriginalProperties: null,\n\t\t\tschemaItem: {\n\t\t\t\ttitle: '',\n\t\t\t\tversion: '0.0.0',\n\t\t\t\tdescription: '',\n\t\t\t\tsummary: '',\n\t\t\t\tslug: '',\n\t\t\t\tproperties: {},\n\t\t\t\tconfiguration: {\n\t\t\t\t\tobjectNameField: '',\n\t\t\t\t\tobjectDescriptionField: '',\n\t\t\t\t\tobjectImageField: '',\n\t\t\t\t\tobjectSummaryField: '',\n\t\t\t\t\tallowFiles: false,\n\t\t\t\t\tallowedTags: [],\n\t\t\t\t\tautoPublish: false,\n\t\t\t\t\tdefaultAutoShare: false,\n\t\t\t\t},\n\n\t\t\t\tauthorization: {},\n\t\t\t\thardValidation: false,\n\t\t\t\timmutable: false,\n\t\t\t\tsearchable: true,\n\t\t\t\ticon: null,\n\t\t\t\tmaxDepth: 0,\n\t\t\t},\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Tab definitions for CnTabbedFormDialog.\n\t\t *\n\t\t * @return {Array} Tab configuration\n\t\t */\n\t\tdialogTabs() {\n\t\t\treturn [\n\t\t\t\t{ id: 'properties', title: t('nextcloud-vue', 'Properties') },\n\t\t\t\t{ id: 'configuration', title: t('nextcloud-vue', 'Configuration') },\n\t\t\t\t{ id: 'security', title: t('nextcloud-vue', 'Security') },\n\t\t\t]\n\t\t},\n\n\t\tsortedUserGroups() {\n\t\t\treturn this.userGroups\n\t\t\t\t.filter((group) => group.id !== 'admin' && group.id !== 'public' && group.id !== 'authenticated')\n\t\t\t\t.sort((a, b) => {\n\t\t\t\t\tconst nameA = a.displayname || a.id\n\t\t\t\t\tconst nameB = b.displayname || b.id\n\t\t\t\t\treturn nameA.localeCompare(nameB)\n\t\t\t\t})\n\t\t},\n\n\t\thasAnyPermissions() {\n\t\t\tconst auth = this.schemaItem.authorization || {}\n\t\t\treturn Object.keys(auth).some((action) => Array.isArray(auth[action]) && auth[action].length > 0)\n\t\t},\n\n\t\tisRestrictiveSchema() {\n\t\t\tconst auth = this.schemaItem.authorization || {}\n\t\t\tconst actions = ['create', 'read', 'update', 'delete']\n\t\t\treturn actions.some((action) => Array.isArray(auth[action]) && auth[action].length > 0\n\t\t\t\t&& !auth[action].includes('public'))\n\t\t},\n\n\t\ttypeOptionsForSelect() {\n\t\t\treturn [\n\t\t\t\t{ id: 'string', label: t('nextcloud-vue', 'String') },\n\t\t\t\t{ id: 'number', label: t('nextcloud-vue', 'Number') },\n\t\t\t\t{ id: 'integer', label: t('nextcloud-vue', 'Integer') },\n\t\t\t\t{ id: 'boolean', label: t('nextcloud-vue', 'Boolean') },\n\t\t\t\t{ id: 'array', label: t('nextcloud-vue', 'Array') },\n\t\t\t\t{ id: 'object', label: t('nextcloud-vue', 'Object') },\n\t\t\t\t{ id: 'dictionary', label: t('nextcloud-vue', 'Dictionary') },\n\t\t\t\t{ id: 'file', label: t('nextcloud-vue', 'File') },\n\t\t\t\t{ id: 'oneOf', label: t('nextcloud-vue', 'One of') },\n\t\t\t]\n\t\t},\n\n\t\tpropertyOptions() {\n\t\t\tconst ownKeys = Object.keys(this.schemaItem.properties || {}).filter((k) => k !== '')\n\t\t\tconst inheritedKeys = Object.keys(this.inheritedProperties || {}).filter((k) => k !== '')\n\t\t\treturn [...new Set([...inheritedKeys, ...ownKeys])]\n\t\t},\n\n\t\tavailableTagsOptions() {\n\t\t\treturn this.availableTags.map((tag) => ({\n\t\t\t\tid: tag,\n\t\t\t\tlabel: tag,\n\t\t\t}))\n\t\t},\n\n\t\t/**\n\t\t * Resolved success text for backwards compatibility (includes trailing period).\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tresolvedSuccessText() {\n\t\t\tif (this.successText) {\n\t\t\t\treturn this.successText\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', '{title} saved successfully.', { title: t('nextcloud-vue', 'Schema') })\n\t\t},\n\n\t\t/**\n\t\t * Returns a human-readable reason the save button is disabled, or '' when saving is allowed.\n\t\t * Used for both :disable-save and the WCAG tooltip/aria-label on the button.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tsaveDisabledReason() {\n\t\t\tif (!this.schemaItem.title) {\n\t\t\t\treturn t('nextcloud-vue', 'A schema title is required before saving')\n\t\t\t}\n\t\t\tconst hasUnnamedProperty = Object.keys(this.schemaItem.properties || {}).some((key) => key === '')\n\t\t\tif (hasUnnamedProperty) {\n\t\t\t\treturn t('nextcloud-vue', 'All properties must have a name before saving')\n\t\t\t}\n\t\t\treturn ''\n\t\t},\n\n\t\tallOfSchemaNames() {\n\t\t\tif (!this.schemaItem.allOf || !Array.isArray(this.schemaItem.allOf) || this.schemaItem.allOf.length === 0) {\n\t\t\t\treturn []\n\t\t\t}\n\n\t\t\treturn this.schemaItem.allOf\n\t\t\t\t.map((ref) => {\n\t\t\t\t\tconst schemaId = typeof ref === 'object' ? ref.id : ref\n\t\t\t\t\tconst schema = this.availableSchemas.find((s) => s.id === schemaId)\n\t\t\t\t\treturn schema ? (schema.title || `Schema ${schema.id}`) : schemaId\n\t\t\t\t})\n\t\t\t\t.filter((name) => name)\n\t\t},\n\t},\n\n\twatch: {\n\t\titem: {\n\t\t\timmediate: true,\n\t\t\thandler() {\n\t\t\t\tthis.initializeSchemaItem()\n\t\t\t},\n\t\t},\n\n\t\t'schemaItem.properties': {\n\t\t\thandler(newProperties) {\n\t\t\t\tif (newProperties) {\n\t\t\t\t\tObject.keys(newProperties).forEach((key) => {\n\t\t\t\t\t\tconst property = newProperties[key]\n\t\t\t\t\t\tif (property) {\n\t\t\t\t\t\t\t// Initialize nested objects if they don't exist\n\t\t\t\t\t\t\tif (property.type === 'array' && !property.items) {\n\t\t\t\t\t\t\t\tthis.schemaItem.properties[key].items = { type: 'string' }\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (property.type === 'object' && !property.objectConfiguration) {\n\t\t\t\t\t\t\t\tthis.schemaItem.properties[key].objectConfiguration = { handling: 'nested-object' }\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (property.type === 'array' && property.items && property.items.type === 'object' && !property.items.objectConfiguration) {\n\t\t\t\t\t\t\t\tthis.schemaItem.properties[key].items.objectConfiguration = { handling: 'nested-object' }\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Convert property type from object to string\n\t\t\t\t\t\t\tif (property.type && typeof property.type === 'object' && property.type.id) {\n\t\t\t\t\t\t\t\tthis.schemaItem.properties[key].type = property.type.id\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Convert property format from object to string\n\t\t\t\t\t\t\tif (property.format && typeof property.format === 'object' && property.format.id) {\n\t\t\t\t\t\t\t\tthis.schemaItem.properties[key].format = property.format.id\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Convert array item type from object to string\n\t\t\t\t\t\t\tif (property.items && property.items.type && typeof property.items.type === 'object' && property.items.type.id) {\n\t\t\t\t\t\t\t\tthis.schemaItem.properties[key].items.type = property.items.type.id\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Convert object handling from object to string\n\t\t\t\t\t\t\tif (property.objectConfiguration && property.objectConfiguration.handling\n\t\t\t\t\t\t\t\t&& typeof property.objectConfiguration.handling === 'object' && property.objectConfiguration.handling.id) {\n\t\t\t\t\t\t\t\tthis.schemaItem.properties[key].objectConfiguration.handling = property.objectConfiguration.handling.id\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Convert register from object to ID\n\t\t\t\t\t\t\tif (property.objectConfiguration && property.objectConfiguration.register\n\t\t\t\t\t\t\t\t&& typeof property.objectConfiguration.register === 'object' && property.objectConfiguration.register.id) {\n\t\t\t\t\t\t\t\tthis.schemaItem.properties[key].objectConfiguration.register = property.objectConfiguration.register.id\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Convert array item object handling from object to string\n\t\t\t\t\t\t\tif (property.items && property.items.objectConfiguration && property.items.objectConfiguration.handling\n\t\t\t\t\t\t\t\t&& typeof property.items.objectConfiguration.handling === 'object' && property.items.objectConfiguration.handling.id) {\n\t\t\t\t\t\t\t\tthis.schemaItem.properties[key].items.objectConfiguration.handling = property.items.objectConfiguration.handling.id\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Convert array item register from object to ID\n\t\t\t\t\t\t\tif (property.items && property.items.objectConfiguration && property.items.objectConfiguration.register\n\t\t\t\t\t\t\t\t&& typeof property.items.objectConfiguration.register === 'object' && property.items.objectConfiguration.register.id) {\n\t\t\t\t\t\t\t\tthis.schemaItem.properties[key].items.objectConfiguration.register = property.items.objectConfiguration.register.id\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Ensure $ref is always a string\n\t\t\t\t\t\t\tthis.ensureRefIsString(this.schemaItem.properties, key)\n\n\t\t\t\t\t\t\t// Ensure inversedBy is always a string for regular properties\n\t\t\t\t\t\t\tif (property.inversedBy && typeof property.inversedBy === 'object' && property.inversedBy.id) {\n\t\t\t\t\t\t\t\tthis.schemaItem.properties[key].inversedBy = property.inversedBy.id\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Ensure inversedBy is always a string for array items\n\t\t\t\t\t\t\tif (property.items && property.items.inversedBy && typeof property.items.inversedBy === 'object' && property.items.inversedBy.id) {\n\t\t\t\t\t\t\t\tthis.schemaItem.properties[key].items.inversedBy = property.items.inversedBy.id\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t\tthis.checkPropertiesModified()\n\t\t\t},\n\n\t\t\tdeep: true,\n\t\t},\n\t},\n\n\tmethods: {\n\t\tt,\n\t\tuserLocale,\n\t\tfindSchemaBySlug(schemaSlug) {\n\t\t\tif (!schemaSlug) {\n\t\t\t\treturn undefined\n\t\t\t}\n\t\t\treturn this.availableSchemas.find((schema) => (schema.slug && schema.slug.toLowerCase() === schemaSlug.toLowerCase())\n\t\t\t\t|| schema.id === schemaSlug\n\t\t\t\t|| schema.title === schemaSlug)\n\t\t},\n\n\t\tensureRefIsString(obj, key) {\n\t\t\tif (!obj || !key) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif (obj[key] && typeof obj[key].$ref === 'object' && obj[key].$ref !== null) {\n\t\t\t\tif (obj[key].$ref.id) {\n\t\t\t\t\tobj[key].$ref = obj[key].$ref.id\n\t\t\t\t} else {\n\t\t\t\t\tobj[key].$ref = ''\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (obj[key] && obj[key].items && typeof obj[key].items.$ref === 'object' && obj[key].items.$ref !== null) {\n\t\t\t\tif (obj[key].items.$ref.id) {\n\t\t\t\t\tobj[key].items.$ref = obj[key].items.$ref.id\n\t\t\t\t} else {\n\t\t\t\t\tobj[key].items.$ref = ''\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\tinitializeSchemaItem() {\n\t\t\t// Reset CnTabbedFormDialog state if available (not yet mounted on first call)\n\t\t\tif (this.$refs.dialog) {\n\t\t\t\tthis.$refs.dialog.resetDialog()\n\t\t\t}\n\n\t\t\t// Always rebuild schemaItem from defaults + incoming item. This handles\n\t\t\t// create mode (item null), edit mode (item with id), and extend mode\n\t\t\t// (item non-null with no id) without leaking stale state across opens.\n\t\t\tconst defaults = {\n\t\t\t\ttitle: '',\n\t\t\t\tversion: '0.0.0',\n\t\t\t\tdescription: '',\n\t\t\t\tsummary: '',\n\t\t\t\tslug: '',\n\t\t\t\tproperties: {},\n\t\t\t\tconfiguration: {\n\t\t\t\t\tobjectNameField: '',\n\t\t\t\t\tobjectDescriptionField: '',\n\t\t\t\t\tobjectImageField: '',\n\t\t\t\t\tobjectSummaryField: '',\n\t\t\t\t\tallowFiles: false,\n\t\t\t\t\tallowedTags: [],\n\t\t\t\t\tautoPublish: false,\n\t\t\t\t\tdefaultAutoShare: false,\n\t\t\t\t},\n\n\t\t\t\tauthorization: {},\n\t\t\t\thardValidation: false,\n\t\t\t\timmutable: false,\n\t\t\t\tsearchable: true,\n\t\t\t\ticon: null,\n\t\t\t\tmaxDepth: 0,\n\t\t\t}\n\t\t\tthis.schemaItem = this.item\n\t\t\t\t? { ...defaults, ...JSON.parse(JSON.stringify(this.item)) }\n\t\t\t\t: { ...defaults }\n\n\t\t\t// Ensure configuration object has all expected keys (the spread above may\n\t\t\t// have replaced our defaults with a partial configuration from the item)\n\t\t\tif (!this.schemaItem.configuration) {\n\t\t\t\tthis.schemaItem.configuration = { ...defaults.configuration }\n\t\t\t} else {\n\t\t\t\tif (!this.schemaItem.configuration.objectNameField) {\n\t\t\t\t\tthis.schemaItem.configuration.objectNameField = ''\n\t\t\t\t}\n\t\t\t\tif (!this.schemaItem.configuration.objectDescriptionField) {\n\t\t\t\t\tthis.schemaItem.configuration.objectDescriptionField = ''\n\t\t\t\t}\n\t\t\t\tif (!this.schemaItem.configuration.objectImageField) {\n\t\t\t\t\tthis.schemaItem.configuration.objectImageField = ''\n\t\t\t\t}\n\t\t\t\tif (!this.schemaItem.configuration.objectSummaryField) {\n\t\t\t\t\tthis.schemaItem.configuration.objectSummaryField = ''\n\t\t\t\t}\n\t\t\t\tif (this.schemaItem.configuration.allowFiles === undefined) {\n\t\t\t\t\tthis.schemaItem.configuration.allowFiles = false\n\t\t\t\t}\n\t\t\t\tif (!this.schemaItem.configuration.allowedTags) {\n\t\t\t\t\tthis.schemaItem.configuration.allowedTags = []\n\t\t\t\t}\n\t\t\t\tif (this.schemaItem.configuration.autoPublish === undefined) {\n\t\t\t\t\tthis.schemaItem.configuration.autoPublish = false\n\t\t\t\t}\n\t\t\t\tif (this.schemaItem.configuration.defaultAutoShare === undefined) {\n\t\t\t\t\tthis.schemaItem.configuration.defaultAutoShare = false\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Ensure authorization object exists\n\t\t\tif (!this.schemaItem.authorization) {\n\t\t\t\tthis.schemaItem.authorization = {}\n\t\t\t}\n\n\t\t\t// Ensure existing properties have facetable set to false by default\n\t\t\tObject.keys(this.schemaItem.properties || {}).forEach((key) => {\n\t\t\t\tif (this.schemaItem.properties[key].facetable === undefined) {\n\t\t\t\t\tthis.schemaItem.properties[key].facetable = false\n\t\t\t\t}\n\n\t\t\t\tif (this.schemaItem.properties[key].enum && Array.isArray(this.schemaItem.properties[key].enum)) {\n\t\t\t\t\tthis.schemaItem.properties[key].enum = [...this.schemaItem.properties[key].enum]\n\t\t\t\t}\n\n\t\t\t\tconst property = this.schemaItem.properties[key]\n\t\t\t\tif (property.type === 'array' && property.items && property.items.type === 'object' && !property.items.objectConfiguration) {\n\t\t\t\t\tthis.schemaItem.properties[key].items.objectConfiguration = { handling: 'nested-object' }\n\t\t\t\t}\n\t\t\t})\n\n\t\t\t// Ensure all $ref values are strings and migrate old structure\n\t\t\tObject.keys(this.schemaItem.properties || {}).forEach((key) => {\n\t\t\t\tthis.ensureRefIsString(this.schemaItem.properties, key)\n\t\t\t\tthis.migratePropertyToNewStructure(key)\n\t\t\t})\n\n\t\t\t// Snapshot original properties for change detection. For new/extending\n\t\t\t// items (no id), there's no \"original\" — start empty so any added\n\t\t\t// properties register as modifications.\n\t\t\tthis.originalProperties = this.schemaItem.id\n\t\t\t\t? JSON.parse(JSON.stringify(this.schemaItem.properties || {}))\n\t\t\t\t: {}\n\n\t\t\tthis.propertiesModified = false\n\t\t},\n\n\t\tcheckPropertiesModified() {\n\t\t\tif (!this.originalProperties) {\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\tconst currentProperties = JSON.stringify(this.schemaItem.properties || {})\n\t\t\tconst originalProperties = JSON.stringify(this.originalProperties)\n\n\t\t\tthis.propertiesModified = currentProperties !== originalProperties\n\t\t},\n\n\t\tasync copyToClipboard(text) {\n\t\t\ttry {\n\t\t\t\tawait navigator.clipboard.writeText(text)\n\t\t\t\tthis.isCopied = true\n\t\t\t\tsetTimeout(() => {\n\t\t\t\t\tthis.isCopied = false\n\t\t\t\t}, 2000)\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console -- the failure is already handled; the console is the only channel a host app can read the detail on\n\t\t\t\tconsole.error('Failed to copy text:', err)\n\t\t\t}\n\t\t},\n\n\t\taddProperty() {\n\t\t\tthis.schemaItem.properties[''] = {\n\t\t\t\ttype: 'string',\n\t\t\t\tformat: '',\n\t\t\t\ttitle: '',\n\t\t\t\tdescription: '',\n\t\t\t\tfacetable: false,\n\t\t\t}\n\n\t\t\tthis.checkPropertiesModified()\n\t\t\tthis.selectedProperty = ''\n\t\t},\n\n\t\tupdatePropertyKey(oldKey, newKey) {\n\t\t\tif (newKey === oldKey) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (this.schemaItem.properties[newKey] !== undefined && newKey !== oldKey) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tconst propertyData = { ...this.schemaItem.properties[oldKey] }\n\n\t\t\tthis.schemaItem.properties[newKey] = propertyData\n\t\t\tdelete this.schemaItem.properties[oldKey]\n\n\t\t\tthis.selectedProperty = newKey\n\t\t\tthis.checkPropertiesModified()\n\t\t},\n\n\t\tdeleteProperty(key) {\n\t\t\tdelete this.schemaItem.properties[key]\n\n\t\t\tif (this.selectedProperty === key) {\n\t\t\t\tthis.selectedProperty = null\n\t\t\t}\n\n\t\t\tthis.checkPropertiesModified()\n\t\t},\n\n\t\tcopyProperty(key) {\n\t\t\tif (this.schemaItem.properties[key]) {\n\t\t\t\tconst originalProperty = JSON.parse(JSON.stringify(this.schemaItem.properties[key]))\n\n\t\t\t\tlet newPropertyName = `${key}_copy`\n\t\t\t\tlet counter = 1\n\n\t\t\t\twhile (this.schemaItem.properties[newPropertyName]) {\n\t\t\t\t\tcounter++\n\t\t\t\t\tnewPropertyName = `${key}_copy_${counter}`\n\t\t\t\t}\n\n\t\t\t\tconst originalTitle = originalProperty.title || key\n\t\t\t\tthis.schemaItem.properties[newPropertyName] = {\n\t\t\t\t\t...originalProperty,\n\t\t\t\t\ttitle: `${originalTitle} (copy)`,\n\t\t\t\t}\n\n\t\t\t\tthis.checkPropertiesModified()\n\t\t\t\tthis.selectedProperty = newPropertyName\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Handle confirm from CnTabbedFormDialog.\n\t\t * Cleans schema data and emits confirm with the cleaned payload.\n\t\t */\n\t\thandleConfirm() {\n\t\t\tconst cleanedSchemaItem = JSON.parse(JSON.stringify(this.schemaItem))\n\t\t\tObject.keys(cleanedSchemaItem.properties || {}).forEach((key) => {\n\t\t\t\tthis.ensureRefIsString(cleanedSchemaItem.properties, key)\n\n\t\t\t\tif (cleanedSchemaItem.properties[key].register\n\t\t\t\t\t&& cleanedSchemaItem.properties[key].objectConfiguration\n\t\t\t\t\t&& cleanedSchemaItem.properties[key].objectConfiguration.register) {\n\t\t\t\t\tdelete cleanedSchemaItem.properties[key].register\n\t\t\t\t}\n\n\t\t\t\tif (cleanedSchemaItem.properties[key].items\n\t\t\t\t\t&& cleanedSchemaItem.properties[key].items.register\n\t\t\t\t\t&& cleanedSchemaItem.properties[key].items.objectConfiguration\n\t\t\t\t\t&& cleanedSchemaItem.properties[key].items.objectConfiguration.register) {\n\t\t\t\t\tdelete cleanedSchemaItem.properties[key].items.register\n\t\t\t\t}\n\t\t\t})\n\n\t\t\t// NcSelect (track-by=\"id\") can convert plain IDs to full option objects.\n\t\t\t// Normalise back to plain IDs before emitting so the backend always gets scalars.\n\t\t\tfor (const field of ['allOf', 'oneOf', 'anyOf']) {\n\t\t\t\tif (Array.isArray(cleanedSchemaItem[field])) {\n\t\t\t\t\tcleanedSchemaItem[field] = cleanedSchemaItem[field]\n\t\t\t\t\t\t.map((ref) => (typeof ref === 'object' && ref !== null ? ref.id : ref))\n\t\t\t\t\t\t.filter((id) => id !== null && id !== undefined && id !== '')\n\t\t\t\t\tif (cleanedSchemaItem[field].length === 0) {\n\t\t\t\t\t\tdelete cleanedSchemaItem[field]\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tthis.$emit('confirm', cleanedSchemaItem)\n\t\t},\n\n\t\t/**\n\t\t * Set the result of the save operation. Call this from the parent\n\t\t * after the API call completes.\n\t\t *\n\t\t * @param {{ success?: boolean, error?: string }} resultData - result data\n\t\t * @public\n\t\t */\n\t\tsetResult(resultData) {\n\t\t\tthis.$refs.dialog.setResult(resultData)\n\t\t\tif (resultData.success) {\n\t\t\t\tthis.originalProperties = JSON.parse(JSON.stringify(this.schemaItem.properties || {}))\n\t\t\t\tthis.propertiesModified = false\n\t\t\t}\n\t\t},\n\n\t\tmigratePropertyToNewStructure(key) {\n\t\t\tif (!this.schemaItem.properties[key]) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tconst property = this.schemaItem.properties[key]\n\n\t\t\tif (property.$ref && property.register && !property.objectConfiguration?.register) {\n\t\t\t\tif (!property.objectConfiguration) {\n\t\t\t\t\tthis.schemaItem.properties[key].objectConfiguration = { handling: 'related-object' }\n\t\t\t\t}\n\n\t\t\t\tconst registerId = typeof property.register === 'object' && property.register.id\n\t\t\t\t\t? property.register.id\n\t\t\t\t\t: property.register\n\n\t\t\t\tthis.schemaItem.properties[key].objectConfiguration.register = registerId\n\n\t\t\t\tif (property.$ref) {\n\t\t\t\t\tlet schemaSlug = property.$ref\n\t\t\t\t\tif (schemaSlug.includes('/')) {\n\t\t\t\t\t\tschemaSlug = schemaSlug.substring(schemaSlug.lastIndexOf('/') + 1)\n\t\t\t\t\t}\n\n\t\t\t\t\tconst referencedSchema = this.findSchemaBySlug(schemaSlug)\n\t\t\t\t\tif (referencedSchema) {\n\t\t\t\t\t\tthis.schemaItem.properties[key].objectConfiguration.schema = referencedSchema.id\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (property.items && property.items.$ref && property.items.register && !property.items.objectConfiguration?.register) {\n\t\t\t\tif (!property.items.objectConfiguration) {\n\t\t\t\t\tthis.schemaItem.properties[key].items.objectConfiguration = { handling: 'related-object' }\n\t\t\t\t}\n\n\t\t\t\tconst registerId = typeof property.items.register === 'object' && property.items.register.id\n\t\t\t\t\t? property.items.register.id\n\t\t\t\t\t: property.items.register\n\n\t\t\t\tthis.schemaItem.properties[key].items.objectConfiguration.register = registerId\n\n\t\t\t\tif (property.items.$ref) {\n\t\t\t\t\tlet schemaSlug = property.items.$ref\n\t\t\t\t\tif (schemaSlug.includes('/')) {\n\t\t\t\t\t\tschemaSlug = schemaSlug.substring(schemaSlug.lastIndexOf('/') + 1)\n\t\t\t\t\t}\n\n\t\t\t\t\tconst referencedSchema = this.findSchemaBySlug(schemaSlug)\n\t\t\t\t\tif (referencedSchema) {\n\t\t\t\t\t\tthis.schemaItem.properties[key].items.objectConfiguration.schema = referencedSchema.id\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n"],"names":["_createBlock","_withCtx","_createElementVNode","_openBlock","_createElementBlock","_toDisplayString","_createVNode","_createTextVNode","_Fragment"],"mappings":"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"}
@@ -2,7 +2,7 @@ import script from './CnStatsBlock.vue2.js';
2
2
  import { render } from './CnStatsBlock.vue3.js';
3
3
 
4
4
  script.render = render;
5
- script.__scopeId = "data-v-770aba44";
5
+ script.__scopeId = "data-v-3ebbfa38";
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 { NcLoadingIcon } from '@nextcloud/vue';
3
3
  import { normalizeLook } from '../../composables/useLook.js';
4
+ import { userLocale } from '../../utils/userLocale.js';
4
5
 
5
6
  /**
6
7
  * CnStatsBlock — Statistics display card with icon, count, and optional breakdown.
@@ -280,7 +281,7 @@ var script = {
280
281
  },
281
282
 
282
283
  formattedCount() {
283
- return this.count.toLocaleString()
284
+ return this.count.toLocaleString(userLocale())
284
285
  },
285
286
 
286
287
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"CnStatsBlock.vue2.js","sources":["../../../../src/components/CnStatsBlock/CnStatsBlock.vue"],"sourcesContent":["<!--\n - CnStatsBlock renders the CANONICAL KPI card (src/css/kpi-card.css) — the\n - same markup and the same stylesheet CnStatWidget renders, so OpenCatalogi's\n - KPIs and dossiq's are one design rather than two. This component's own\n - `cn-stats-block*` classes stay on the same elements for backwards\n - compatibility with app CSS that targets them; the LOOK comes from the\n - shared sheet.\n -\n - The comment lives OUT here rather than inside <template>: a comment node\n - beside the root element makes the component multi-root in Vue 3, and a\n - multi-root component has no root to put class attributes on — `rootClasses`\n - silently stopped reaching the DOM.\n-->\n<template>\n\t<component\n\t\t:is=\"componentTag\"\n\t\tclass=\"cn-kpi-card cn-stats-block\"\n\t\t:class=\"rootClasses\"\n\t\tv-bind=\"componentAttrs\"\n\t\t@click=\"onClick\">\n\t\t<!-- Icon -->\n\t\t<div v-if=\"hasIcon && !isStacked && !isBoardLook\" class=\"cn-kpi-card__icon cn-stats-block__icon\" :class=\"iconClasses\">\n\t\t\t<slot name=\"icon\">\n\t\t\t\t<component :is=\"icon\" v-if=\"icon\" :size=\"iconSize\" />\n\t\t\t</slot>\n\t\t</div>\n\n\t\t<!-- Content -->\n\t\t<div class=\"cn-kpi-card__body cn-stats-block__content\">\n\t\t\t<div class=\"cn-stats-block__header\">\n\t\t\t\t<!-- Board look: an 18px glyph before the label, only on a link tile. -->\n\t\t\t\t<span\n\t\t\t\t\tv-if=\"isBoardLook && isInteractive && hasIcon\"\n\t\t\t\t\tclass=\"cn-kpi-card__glyph cn-stats-block__glyph\"\n\t\t\t\t\tdata-testid=\"cn-stats-block-glyph\"\n\t\t\t\t\taria-hidden=\"true\">\n\t\t\t\t\t<slot name=\"icon\">\n\t\t\t\t\t\t<component :is=\"icon\" v-if=\"icon\" :size=\"18\" />\n\t\t\t\t\t</slot>\n\t\t\t\t</span>\n\t\t\t\t<h4 class=\"cn-kpi-card__title\" :title=\"title || undefined\">\n\t\t\t\t\t{{ title ? tl(title) : t('nextcloud-vue', 'Objects') }}\n\t\t\t\t</h4>\n\t\t\t</div>\n\n\t\t\t<div v-if=\"hasError\" class=\"cn-stats-block__count cn-stats-block__count--error\">\n\t\t\t\t<span class=\"cn-stats-block__count-value\">&mdash;</span>\n\t\t\t\t<span class=\"cn-stats-block__count-label\">{{ errorLabel }}</span>\n\t\t\t</div>\n\t\t\t<div v-else-if=\"hasValueSlot || count > 0 || (showZeroCount && count === 0)\" class=\"cn-kpi-card__value-row cn-stats-block__count\">\n\t\t\t\t<span class=\"cn-kpi-card__value cn-stats-block__count-value\">\n\t\t\t\t\t<!-- @slot Override the prominently-displayed value — render a pre-formatted string (currency, percent, a \"—\" placeholder, …). `count` stays the raw number; this is presentation only. Defaults to the localized count. -->\n\t\t\t\t\t<!-- @binding {number} count The raw numeric count. -->\n\t\t\t\t\t<!-- @binding {string} formatted The default localized count string. -->\n\t\t\t\t\t<slot name=\"value\" :count=\"count\" :formatted=\"formattedCount\">{{ formattedCount }}</slot>\n\t\t\t\t</span>\n\t\t\t\t<span class=\"cn-kpi-card__label cn-stats-block__count-label\">{{ countLabel ? tl(countLabel) : countLabel }}</span>\n\t\t\t</div>\n\t\t\t<div v-else-if=\"loading\" class=\"cn-stats-block__loading\">\n\t\t\t\t<NcLoadingIcon :size=\"16\" />\n\t\t\t\t{{ loadingLabel }}\n\t\t\t</div>\n\t\t\t<div v-else class=\"cn-stats-block__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\n\t\t\t<!-- Breakdown details -->\n\t\t\t<div\n\t\t\t\tv-if=\"hasError === false && breakdown && (hasValueSlot || count > 0 || showZeroCount)\"\n\t\t\t\tclass=\"cn-stats-block__breakdown\">\n\t\t\t\t<div\n\t\t\t\t\tv-for=\"(value, key) in breakdown\"\n\t\t\t\t\t:key=\"key\"\n\t\t\t\t\tclass=\"cn-stats-block__breakdown-item\">\n\t\t\t\t\t<span class=\"cn-stats-block__breakdown-label\">{{ formatBreakdownLabel(key) }}</span>\n\t\t\t\t\t<span\n\t\t\t\t\t\tclass=\"cn-stats-block__breakdown-value\"\n\t\t\t\t\t\t:class=\"'cn-stats-block__breakdown-value--' + key\">\n\t\t\t\t\t\t{{ value }}\n\t\t\t\t\t</span>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t</component>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcLoadingIcon } from '@nextcloud/vue'\nimport { normalizeLook } from '../../composables/useLook.js'\n\n// The canonical KPI look, shared with CnStatWidget. Imported here so the\n// card is styled even when the consuming app has not pulled in the\n// library's global css/index.css.\nimport '../../css/kpi-card.css'\n\n/**\n * CnStatsBlock — Statistics display card with icon, count, and optional breakdown.\n *\n * Renders the canonical KPI card: a large circular icon beside a left-aligned\n * number, drawing no box of its own because the wrapper around it already\n * draws one. Supports colour variants, icons and clickable state. Use in a\n * CnKpiGrid for responsive dashboard layouts.\n *\n * Basic (canonical horizontal card)\n * ```vue\n * <CnStatsBlock title=\"Cases\" :count=\"42\" count-label=\"open cases\" />\n * ```\n *\n * With icon and variant\n * ```vue\n * <CnStatsBlock\n * title=\"Open Cases\"\n * :count=\"42\"\n * :icon=\"BriefcaseOutline\"\n * variant=\"primary\"\n * clickable\n * @click=\"goToCases\" />\n * ```\n *\n * Stacked and boxed — for a block mounted with no wrapper around it\n * ```vue\n * <CnStatsBlock title=\"Cases\" :count=\"42\" vertical filled />\n * ```\n *\n * With route-based navigation (renders as <router-link>)\n * ```vue\n * <CnStatsBlock\n * title=\"Open Cases\"\n * :count=\"42\"\n * :icon=\"BriefcaseOutline\"\n * variant=\"primary\"\n * :route=\"{ name: 'Cases', query: { status: 'open' } }\" />\n * ```\n *\n * With breakdown\n * ```vue\n * <CnStatsBlock\n * title=\"Cases\"\n * :count=\"42\"\n * :breakdown=\"{ total: 100, invalid: 3, deleted: 5, published: 92 }\" />\n * ```\n *\n * Custom icon slot\n * ```vue\n * <CnStatsBlock title=\"Files\" :count=\"128\">\n * <template #icon>\n * <FileDocumentOutline :size=\"24\" />\n * </template>\n * </CnStatsBlock>\n * ```\n */\nexport default {\n\tname: 'CnStatsBlock',\n\n\tcomponents: {\n\t\tNcLoadingIcon,\n\t},\n\n\tinject: {\n\t\t/**\n\t\t * The look the app is drawn in, provided by CnAppRoot.\n\t\t *\n\t\t * @spec openspec/changes/screens-dashboard-parity/specs/dashboard-page/spec.md#requirement-the-board-kpi-tile\n\t\t */\n\t\tcnLook: { default: 'nextcloud' },\n\t\t/**\n\t\t * The host's label lookup (CnAppRoot provides it); identity without one, so a\n\t\t * label that is not a key renders as written.\n\t\t */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/** Block title */\n\t\ttitle: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** The main count number to display prominently */\n\t\tcount: {\n\t\t\ttype: Number,\n\t\t\tdefault: 0,\n\t\t},\n\n\t\t/** Label displayed next to the count */\n\t\tcountLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'objects'),\n\t\t},\n\n\t\t/** Detailed breakdown object (key-value pairs) */\n\t\tbreakdown: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Whether data is currently loading */\n\t\tloading: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Text shown while loading */\n\t\tloadingLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Loading…'),\n\t\t},\n\n\t\t/** Text shown when count is 0 */\n\t\temptyLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'No items found'),\n\t\t},\n\n\t\t/**\n\t\t * The tile could not load its number. Anything truthy counts — pass the\n\t\t * caught error itself, or just `true`.\n\t\t *\n\t\t * The tile then shows a dash and `errorLabel` INSTEAD of a count, and\n\t\t * tints itself `error`. This takes precedence over `count`, `loading`\n\t\t * and `emptyLabel` deliberately: a stale or defaulted number rendered\n\t\t * during a failure is the exact thing this prop exists to stop. A\n\t\t * dashboard showing 0 because the backend is down is worse than one\n\t\t * showing nothing, because 0 is a number a reader will believe.\n\t\t *\n\t\t * @type {boolean|string|Error|null}\n\t\t */\n\t\terror: {\n\t\t\t// TYPE ORDER IS SEMANTIC HERE. Vue casts an empty value to `true`\n\t\t\t// when Boolean appears BEFORE String in the union, so\n\t\t\t// `[Boolean, String]` would read `error=\"\"` as an error. A caller\n\t\t\t// clearing its message back to '' is reporting RECOVERY, and that\n\t\t\t// would have pinned the tile in its error state after the fetch\n\t\t\t// succeeded again. String first keeps '' falsy.\n\t\t\t// eslint-disable-next-line vue/prefer-prop-type-boolean-first -- Boolean first would make `error=\"\"` truthy, which is the recovery signal.\n\t\t\ttype: [String, Boolean, Object],\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Text shown in place of the count when `error` is set. */\n\t\terrorLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Unavailable'),\n\t\t},\n\n\t\t/** Icon component (e.g., imported MDI icon) */\n\t\ticon: {\n\t\t\ttype: [Object, Function],\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Icon size in pixels */\n\t\ticonSize: {\n\t\t\ttype: Number,\n\t\t\tdefault: 24,\n\t\t},\n\n\t\t/** Color variant: 'default', 'primary', 'success', 'warning', 'error' */\n\t\tvariant: {\n\t\t\ttype: String,\n\t\t\tdefault: 'default',\n\t\t\tvalidator: (v) => ['default', 'primary', 'success', 'warning', 'error'].includes(v),\n\t\t},\n\n\t\t/**\n\t\t * Stack the icon above a centred number instead of placing it beside\n\t\t * one. The canonical KPI card is horizontal, so this is the opt-out;\n\t\t * `horizontal` below is kept only for callers that already pass it.\n\t\t */\n\t\tvertical: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * `stacked` is the board's tile, as CnStatWidget `content.layout:\n\t\t * \"stacked\"`: no icon circle, a 14px muted title, the number at\n\t\t * 34px/700 in the text colour. Empty (the default) keeps the KPI card.\n\t\t *\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-a-stats-block-can-take-the-stacked-board-look\n\t\t */\n\t\tlayout: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Lay the icon left of the content. No longer needed — this is the\n\t\t * canonical card's own layout — and kept so existing callers that pass\n\t\t * `horizontal` keep working. Pass `vertical` to stack instead.\n\t\t *\n\t\t * @deprecated since 2.25.0, the horizontal layout is the default.\n\t\t */\n\t\thorizontal: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Draw the card's own grey box. Off by default: a stats block is\n\t\t * normally rendered inside a wrapper that already draws a card, and a\n\t\t * second box reads as a card inside a card. Turn it on for a block\n\t\t * mounted with no wrapper around it.\n\t\t */\n\t\tfilled: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Whether the card is clickable */\n\t\tclickable: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Whether to display 0 as a count value instead of the empty label */\n\t\tshowZeroCount: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Vue Router location object for declarative navigation.\n\t\t * When set, the card renders as a <router-link> and clickable styles are implied.\n\t\t * { name: 'Cases', query: { status: 'open' } }\n\t\t * { path: '/catalogi' }\n\t\t */\n\t\troute: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\temits: ['click'],\n\n\tcomputed: {\n\t\t/**\n\t\t * Whether the app takes the board look.\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\tisBoardLook() {\n\t\t\treturn normalizeLook(this.cnLook) === 'board'\n\t\t},\n\n\t\thasIcon() {\n\t\t\treturn this.icon !== null || this.$slots.icon || this.$slots.icon\n\t\t},\n\n\t\t/**\n\t\t * Whether a consumer provided the `value` slot. When set, the value\n\t\t * area always renders (the slot decides what to show, even at count 0).\n\t\t */\n\t\thasValueSlot() {\n\t\t\treturn !!this.$slots.value || !!this.$slots.value\n\t\t},\n\n\t\t/**\n\t\t * Whether the tile is in its error state.\n\t\t *\n\t\t * Anything truthy counts, so a caller can pass the caught error itself\n\t\t * rather than converting it to a boolean first. An empty string is NOT\n\t\t * an error — that is a caller who cleared the message, not one\n\t\t * reporting a failure.\n\t\t *\n\t\t * @return {boolean} True when the tile should show `errorLabel`.\n\t\t */\n\t\thasError() {\n\t\t\treturn Boolean(this.error)\n\t\t},\n\n\t\tformattedCount() {\n\t\t\treturn this.count.toLocaleString()\n\t\t},\n\n\t\t/**\n\t\t * Whether the card is interactive (clickable or has a route).\n\t\t * Used for applying hover/focus styles.\n\t\t */\n\t\tisInteractive() {\n\t\t\treturn !!this.route || this.clickable\n\t\t},\n\n\t\t/**\n\t\t * Determines which HTML element or component to render.\n\t\t * - route set → 'router-link' (SPA navigation)\n\t\t * - clickable → 'a' (app handles click via event)\n\t\t * - neither → 'div' (static display)\n\t\t */\n\t\tcomponentTag() {\n\t\t\tif (this.route) {\n\t\t\t\treturn 'router-link'\n\t\t\t}\n\t\t\tif (this.clickable) {\n\t\t\t\treturn 'a'\n\t\t\t}\n\t\t\treturn 'div'\n\t\t},\n\n\t\t/**\n\t\t * Dynamic attributes for the root element based on rendering mode.\n\t\t */\n\t\tcomponentAttrs() {\n\t\t\tif (this.route) {\n\t\t\t\treturn { to: this.route, tabindex: '0' }\n\t\t\t}\n\t\t\tif (this.clickable) {\n\t\t\t\treturn { href: '#', role: 'button', tabindex: '0' }\n\t\t\t}\n\t\t\treturn {}\n\t\t},\n\n\t\t/**\n\t\t * Whether the block takes the stacked board look (`layout`).\n\t\t *\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-a-stats-block-can-take-the-stacked-board-look\n\t\t * @return {boolean}\n\t\t */\n\t\tisStacked() {\n\t\t\treturn this.layout === 'stacked'\n\t\t},\n\n\t\trootClasses() {\n\t\t\treturn {\n\t\t\t\t// Canonical + legacy, in pairs: the canonical class carries the\n\t\t\t\t// look, the legacy one keeps existing app CSS matching.\n\t\t\t\t// The canonical card is already horizontal, so the legacy pair\n\t\t\t\t// is emitted for app CSS but carries no look of its own.\n\t\t\t\t'cn-kpi-card--horizontal': !this.isStacked && (this.horizontal || !this.vertical),\n\t\t\t\t'cn-kpi-card--stacked': this.isStacked,\n\t\t\t\t'cn-kpi-card--board': this.isBoardLook,\n\t\t\t\t'cn-stats-block--horizontal': this.horizontal || !this.vertical,\n\t\t\t\t'cn-kpi-card--vertical': this.vertical,\n\t\t\t\t'cn-stats-block--vertical': this.vertical,\n\t\t\t\t'cn-kpi-card--filled': this.filled,\n\t\t\t\t'cn-kpi-card--clickable': this.isInteractive,\n\t\t\t\t'cn-stats-block--clickable': this.isInteractive,\n\t\t\t\t'cn-kpi-card--error': this.hasError,\n\t\t\t\t[`cn-kpi-card--${this.variant}`]: this.variant !== 'default' && this.hasError === false,\n\t\t\t\t// An errored tile tints itself. Requiring the consumer to also\n\t\t\t\t// pass `variant=\"error\"` would mean every caller remembering\n\t\t\t\t// two props to express one state, and forgetting the second is\n\t\t\t\t// invisible — a tile that reads \"Unavailable\" in the default\n\t\t\t\t// colour looks like ordinary content.\n\t\t\t\t'cn-stats-block--error': this.hasError,\n\t\t\t\t[`cn-stats-block--${this.variant}`]: this.variant !== 'default' && this.hasError === false,\n\t\t\t}\n\t\t},\n\n\t\ticonClasses() {\n\t\t\treturn {\n\t\t\t\t[`cn-stats-block__icon--${this.variant}`]: this.variant !== 'default',\n\t\t\t}\n\t\t},\n\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * A manifest string through the host's label lookup (`cnTranslate`), so a key\n\t\t * such as \"Mine\" reads in the user's language.\n\t\t *\n\t\t * @param {string} text The text as written in the manifest.\n\t\t * @return {string} The translated text.\n\t\t */\n\t\ttl(text) {\n\t\t\treturn typeof this.cnTranslate === 'function' ? this.cnTranslate(text) : text\n\t\t},\n\n\t\tformatBreakdownLabel(key) {\n\t\t\treturn key.charAt(0).toUpperCase() + key.slice(1) + ':'\n\t\t},\n\n\t\tonClick(event) {\n\t\t\t// When route is set, router-link handles navigation — do not emit click\n\t\t\tif (this.route) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (this.clickable) {\n\t\t\t\tevent.preventDefault()\n\t\t\t\tthis.$emit('click', event)\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n/*\n * The card's own look — box, body stack, icon circle, title, value, variants,\n * hover — is the CANONICAL KPI card in src/css/kpi-card.css, shared with\n * CnStatWidget. The rules that lived here duplicated it in different numbers\n * (a 1.2rem count row, a 40px icon, hardcoded rgba() tints that ignored a\n * re-themed palette), and that duplication IS how the two KPI looks drifted.\n * What is left below is this component's own extras: the breakdown block, the\n * loading line and the empty line.\n *\n * Those extras are secondary KPI text, so they read the shared\n * `--cn-kpi-label-*` scale rather than carrying sizes of their own — an empty\n * state that is a different size from the label it replaces is the same drift\n * in miniature.\n */\n\n.cn-stats-block__header {\n\tmax-width: 100%;\n\tmin-width: 0;\n}\n\n.cn-stats-block__loading {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: flex-start;\n\tgap: 6px;\n\tfont-size: var(--cn-kpi-label-size, 13px);\n\tcolor: var(--cn-kpi-label-color, var(--color-text-maxcontrast));\n}\n\n.cn-stats-block__empty {\n\ttext-align: start;\n\tfont-size: var(--cn-kpi-label-size, 13px);\n\tcolor: var(--cn-kpi-label-color, var(--color-text-maxcontrast));\n\tfont-style: italic;\n}\n\n.cn-stats-block__breakdown {\n\tmargin-top: 4px;\n\tpadding: 8px;\n\tfont-size: var(--cn-kpi-label-size, 13px);\n\tbackground: var(--color-background-hover);\n\tborder-radius: var(--border-radius);\n\tborder: 1px solid var(--color-border);\n\twidth: 100%;\n\ttext-align: start;\n}\n\n.cn-stats-block__breakdown-item {\n\tdisplay: flex;\n\tjustify-content: space-between;\n\talign-items: center;\n\tgap: 8px;\n\tmargin-bottom: 4px;\n}\n\n.cn-stats-block__breakdown-item:last-child {\n\tmargin-bottom: 0;\n}\n\n.cn-stats-block__breakdown-label {\n\tfont-weight: 500;\n\tcolor: var(--color-main-text);\n}\n\n/* The value takes the card's accent, so a breakdown reads as part of the tile\n it sits in rather than as a table that happened to land there. It used to\n carry a `--color-background-hover` chip — on a `--color-background-hover`\n breakdown box, which is to say an invisible chip drawn at the cost of two\n extra declarations and a differently-shaped hit area. The per-key modifiers\n below still win: `invalid`, `deleted` and `published` mean something\n specific and outrank the tile's own colour. */\n.cn-stats-block__breakdown-value {\n\tfont-weight: var(--cn-kpi-title-weight, 600);\n\tcolor: var(--cn-kpi-accent, var(--color-primary-element));\n}\n\n.cn-stats-block__breakdown-value--invalid { color: var(--color-element-warning); }\n\n.cn-stats-block__breakdown-value--deleted { color: var(--color-element-error); }\n\n.cn-stats-block__breakdown-value--published { color: var(--color-element-success); }\n</style>\n"],"names":["t"],"mappings":"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"}
1
+ {"version":3,"file":"CnStatsBlock.vue2.js","sources":["../../../../src/components/CnStatsBlock/CnStatsBlock.vue"],"sourcesContent":["<!--\n - CnStatsBlock renders the CANONICAL KPI card (src/css/kpi-card.css) — the\n - same markup and the same stylesheet CnStatWidget renders, so OpenCatalogi's\n - KPIs and dossiq's are one design rather than two. This component's own\n - `cn-stats-block*` classes stay on the same elements for backwards\n - compatibility with app CSS that targets them; the LOOK comes from the\n - shared sheet.\n -\n - The comment lives OUT here rather than inside <template>: a comment node\n - beside the root element makes the component multi-root in Vue 3, and a\n - multi-root component has no root to put class attributes on — `rootClasses`\n - silently stopped reaching the DOM.\n-->\n<template>\n\t<component\n\t\t:is=\"componentTag\"\n\t\tclass=\"cn-kpi-card cn-stats-block\"\n\t\t:class=\"rootClasses\"\n\t\tv-bind=\"componentAttrs\"\n\t\t@click=\"onClick\">\n\t\t<!-- Icon -->\n\t\t<div v-if=\"hasIcon && !isStacked && !isBoardLook\" class=\"cn-kpi-card__icon cn-stats-block__icon\" :class=\"iconClasses\">\n\t\t\t<slot name=\"icon\">\n\t\t\t\t<component :is=\"icon\" v-if=\"icon\" :size=\"iconSize\" />\n\t\t\t</slot>\n\t\t</div>\n\n\t\t<!-- Content -->\n\t\t<div class=\"cn-kpi-card__body cn-stats-block__content\">\n\t\t\t<div class=\"cn-stats-block__header\">\n\t\t\t\t<!-- Board look: an 18px glyph before the label, only on a link tile. -->\n\t\t\t\t<span\n\t\t\t\t\tv-if=\"isBoardLook && isInteractive && hasIcon\"\n\t\t\t\t\tclass=\"cn-kpi-card__glyph cn-stats-block__glyph\"\n\t\t\t\t\tdata-testid=\"cn-stats-block-glyph\"\n\t\t\t\t\taria-hidden=\"true\">\n\t\t\t\t\t<slot name=\"icon\">\n\t\t\t\t\t\t<component :is=\"icon\" v-if=\"icon\" :size=\"18\" />\n\t\t\t\t\t</slot>\n\t\t\t\t</span>\n\t\t\t\t<h4 class=\"cn-kpi-card__title\" :title=\"title || undefined\">\n\t\t\t\t\t{{ title ? tl(title) : t('nextcloud-vue', 'Objects') }}\n\t\t\t\t</h4>\n\t\t\t</div>\n\n\t\t\t<div v-if=\"hasError\" class=\"cn-stats-block__count cn-stats-block__count--error\">\n\t\t\t\t<span class=\"cn-stats-block__count-value\">&mdash;</span>\n\t\t\t\t<span class=\"cn-stats-block__count-label\">{{ errorLabel }}</span>\n\t\t\t</div>\n\t\t\t<div v-else-if=\"hasValueSlot || count > 0 || (showZeroCount && count === 0)\" class=\"cn-kpi-card__value-row cn-stats-block__count\">\n\t\t\t\t<span class=\"cn-kpi-card__value cn-stats-block__count-value\">\n\t\t\t\t\t<!-- @slot Override the prominently-displayed value — render a pre-formatted string (currency, percent, a \"—\" placeholder, …). `count` stays the raw number; this is presentation only. Defaults to the localized count. -->\n\t\t\t\t\t<!-- @binding {number} count The raw numeric count. -->\n\t\t\t\t\t<!-- @binding {string} formatted The default localized count string. -->\n\t\t\t\t\t<slot name=\"value\" :count=\"count\" :formatted=\"formattedCount\">{{ formattedCount }}</slot>\n\t\t\t\t</span>\n\t\t\t\t<span class=\"cn-kpi-card__label cn-stats-block__count-label\">{{ countLabel ? tl(countLabel) : countLabel }}</span>\n\t\t\t</div>\n\t\t\t<div v-else-if=\"loading\" class=\"cn-stats-block__loading\">\n\t\t\t\t<NcLoadingIcon :size=\"16\" />\n\t\t\t\t{{ loadingLabel }}\n\t\t\t</div>\n\t\t\t<div v-else class=\"cn-stats-block__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\n\t\t\t<!-- Breakdown details -->\n\t\t\t<div\n\t\t\t\tv-if=\"hasError === false && breakdown && (hasValueSlot || count > 0 || showZeroCount)\"\n\t\t\t\tclass=\"cn-stats-block__breakdown\">\n\t\t\t\t<div\n\t\t\t\t\tv-for=\"(value, key) in breakdown\"\n\t\t\t\t\t:key=\"key\"\n\t\t\t\t\tclass=\"cn-stats-block__breakdown-item\">\n\t\t\t\t\t<span class=\"cn-stats-block__breakdown-label\">{{ formatBreakdownLabel(key) }}</span>\n\t\t\t\t\t<span\n\t\t\t\t\t\tclass=\"cn-stats-block__breakdown-value\"\n\t\t\t\t\t\t:class=\"'cn-stats-block__breakdown-value--' + key\">\n\t\t\t\t\t\t{{ value }}\n\t\t\t\t\t</span>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t</component>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcLoadingIcon } from '@nextcloud/vue'\nimport { normalizeLook } from '../../composables/useLook.js'\nimport { userLocale } from '../../utils/userLocale.js'\n\n// The canonical KPI look, shared with CnStatWidget. Imported here so the\n// card is styled even when the consuming app has not pulled in the\n// library's global css/index.css.\nimport '../../css/kpi-card.css'\n\n/**\n * CnStatsBlock — Statistics display card with icon, count, and optional breakdown.\n *\n * Renders the canonical KPI card: a large circular icon beside a left-aligned\n * number, drawing no box of its own because the wrapper around it already\n * draws one. Supports colour variants, icons and clickable state. Use in a\n * CnKpiGrid for responsive dashboard layouts.\n *\n * Basic (canonical horizontal card)\n * ```vue\n * <CnStatsBlock title=\"Cases\" :count=\"42\" count-label=\"open cases\" />\n * ```\n *\n * With icon and variant\n * ```vue\n * <CnStatsBlock\n * title=\"Open Cases\"\n * :count=\"42\"\n * :icon=\"BriefcaseOutline\"\n * variant=\"primary\"\n * clickable\n * @click=\"goToCases\" />\n * ```\n *\n * Stacked and boxed — for a block mounted with no wrapper around it\n * ```vue\n * <CnStatsBlock title=\"Cases\" :count=\"42\" vertical filled />\n * ```\n *\n * With route-based navigation (renders as <router-link>)\n * ```vue\n * <CnStatsBlock\n * title=\"Open Cases\"\n * :count=\"42\"\n * :icon=\"BriefcaseOutline\"\n * variant=\"primary\"\n * :route=\"{ name: 'Cases', query: { status: 'open' } }\" />\n * ```\n *\n * With breakdown\n * ```vue\n * <CnStatsBlock\n * title=\"Cases\"\n * :count=\"42\"\n * :breakdown=\"{ total: 100, invalid: 3, deleted: 5, published: 92 }\" />\n * ```\n *\n * Custom icon slot\n * ```vue\n * <CnStatsBlock title=\"Files\" :count=\"128\">\n * <template #icon>\n * <FileDocumentOutline :size=\"24\" />\n * </template>\n * </CnStatsBlock>\n * ```\n */\nexport default {\n\tname: 'CnStatsBlock',\n\n\tcomponents: {\n\t\tNcLoadingIcon,\n\t},\n\n\tinject: {\n\t\t/**\n\t\t * The look the app is drawn in, provided by CnAppRoot.\n\t\t *\n\t\t * @spec openspec/changes/screens-dashboard-parity/specs/dashboard-page/spec.md#requirement-the-board-kpi-tile\n\t\t */\n\t\tcnLook: { default: 'nextcloud' },\n\t\t/**\n\t\t * The host's label lookup (CnAppRoot provides it); identity without one, so a\n\t\t * label that is not a key renders as written.\n\t\t */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/** Block title */\n\t\ttitle: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** The main count number to display prominently */\n\t\tcount: {\n\t\t\ttype: Number,\n\t\t\tdefault: 0,\n\t\t},\n\n\t\t/** Label displayed next to the count */\n\t\tcountLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'objects'),\n\t\t},\n\n\t\t/** Detailed breakdown object (key-value pairs) */\n\t\tbreakdown: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Whether data is currently loading */\n\t\tloading: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Text shown while loading */\n\t\tloadingLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Loading…'),\n\t\t},\n\n\t\t/** Text shown when count is 0 */\n\t\temptyLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'No items found'),\n\t\t},\n\n\t\t/**\n\t\t * The tile could not load its number. Anything truthy counts — pass the\n\t\t * caught error itself, or just `true`.\n\t\t *\n\t\t * The tile then shows a dash and `errorLabel` INSTEAD of a count, and\n\t\t * tints itself `error`. This takes precedence over `count`, `loading`\n\t\t * and `emptyLabel` deliberately: a stale or defaulted number rendered\n\t\t * during a failure is the exact thing this prop exists to stop. A\n\t\t * dashboard showing 0 because the backend is down is worse than one\n\t\t * showing nothing, because 0 is a number a reader will believe.\n\t\t *\n\t\t * @type {boolean|string|Error|null}\n\t\t */\n\t\terror: {\n\t\t\t// TYPE ORDER IS SEMANTIC HERE. Vue casts an empty value to `true`\n\t\t\t// when Boolean appears BEFORE String in the union, so\n\t\t\t// `[Boolean, String]` would read `error=\"\"` as an error. A caller\n\t\t\t// clearing its message back to '' is reporting RECOVERY, and that\n\t\t\t// would have pinned the tile in its error state after the fetch\n\t\t\t// succeeded again. String first keeps '' falsy.\n\t\t\t// eslint-disable-next-line vue/prefer-prop-type-boolean-first -- Boolean first would make `error=\"\"` truthy, which is the recovery signal.\n\t\t\ttype: [String, Boolean, Object],\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Text shown in place of the count when `error` is set. */\n\t\terrorLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Unavailable'),\n\t\t},\n\n\t\t/** Icon component (e.g., imported MDI icon) */\n\t\ticon: {\n\t\t\ttype: [Object, Function],\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Icon size in pixels */\n\t\ticonSize: {\n\t\t\ttype: Number,\n\t\t\tdefault: 24,\n\t\t},\n\n\t\t/** Color variant: 'default', 'primary', 'success', 'warning', 'error' */\n\t\tvariant: {\n\t\t\ttype: String,\n\t\t\tdefault: 'default',\n\t\t\tvalidator: (v) => ['default', 'primary', 'success', 'warning', 'error'].includes(v),\n\t\t},\n\n\t\t/**\n\t\t * Stack the icon above a centred number instead of placing it beside\n\t\t * one. The canonical KPI card is horizontal, so this is the opt-out;\n\t\t * `horizontal` below is kept only for callers that already pass it.\n\t\t */\n\t\tvertical: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * `stacked` is the board's tile, as CnStatWidget `content.layout:\n\t\t * \"stacked\"`: no icon circle, a 14px muted title, the number at\n\t\t * 34px/700 in the text colour. Empty (the default) keeps the KPI card.\n\t\t *\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-a-stats-block-can-take-the-stacked-board-look\n\t\t */\n\t\tlayout: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Lay the icon left of the content. No longer needed — this is the\n\t\t * canonical card's own layout — and kept so existing callers that pass\n\t\t * `horizontal` keep working. Pass `vertical` to stack instead.\n\t\t *\n\t\t * @deprecated since 2.25.0, the horizontal layout is the default.\n\t\t */\n\t\thorizontal: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Draw the card's own grey box. Off by default: a stats block is\n\t\t * normally rendered inside a wrapper that already draws a card, and a\n\t\t * second box reads as a card inside a card. Turn it on for a block\n\t\t * mounted with no wrapper around it.\n\t\t */\n\t\tfilled: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Whether the card is clickable */\n\t\tclickable: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Whether to display 0 as a count value instead of the empty label */\n\t\tshowZeroCount: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Vue Router location object for declarative navigation.\n\t\t * When set, the card renders as a <router-link> and clickable styles are implied.\n\t\t * { name: 'Cases', query: { status: 'open' } }\n\t\t * { path: '/catalogi' }\n\t\t */\n\t\troute: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\temits: ['click'],\n\n\tcomputed: {\n\t\t/**\n\t\t * Whether the app takes the board look.\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\tisBoardLook() {\n\t\t\treturn normalizeLook(this.cnLook) === 'board'\n\t\t},\n\n\t\thasIcon() {\n\t\t\treturn this.icon !== null || this.$slots.icon || this.$slots.icon\n\t\t},\n\n\t\t/**\n\t\t * Whether a consumer provided the `value` slot. When set, the value\n\t\t * area always renders (the slot decides what to show, even at count 0).\n\t\t */\n\t\thasValueSlot() {\n\t\t\treturn !!this.$slots.value || !!this.$slots.value\n\t\t},\n\n\t\t/**\n\t\t * Whether the tile is in its error state.\n\t\t *\n\t\t * Anything truthy counts, so a caller can pass the caught error itself\n\t\t * rather than converting it to a boolean first. An empty string is NOT\n\t\t * an error — that is a caller who cleared the message, not one\n\t\t * reporting a failure.\n\t\t *\n\t\t * @return {boolean} True when the tile should show `errorLabel`.\n\t\t */\n\t\thasError() {\n\t\t\treturn Boolean(this.error)\n\t\t},\n\n\t\tformattedCount() {\n\t\t\treturn this.count.toLocaleString(userLocale())\n\t\t},\n\n\t\t/**\n\t\t * Whether the card is interactive (clickable or has a route).\n\t\t * Used for applying hover/focus styles.\n\t\t */\n\t\tisInteractive() {\n\t\t\treturn !!this.route || this.clickable\n\t\t},\n\n\t\t/**\n\t\t * Determines which HTML element or component to render.\n\t\t * - route set → 'router-link' (SPA navigation)\n\t\t * - clickable → 'a' (app handles click via event)\n\t\t * - neither → 'div' (static display)\n\t\t */\n\t\tcomponentTag() {\n\t\t\tif (this.route) {\n\t\t\t\treturn 'router-link'\n\t\t\t}\n\t\t\tif (this.clickable) {\n\t\t\t\treturn 'a'\n\t\t\t}\n\t\t\treturn 'div'\n\t\t},\n\n\t\t/**\n\t\t * Dynamic attributes for the root element based on rendering mode.\n\t\t */\n\t\tcomponentAttrs() {\n\t\t\tif (this.route) {\n\t\t\t\treturn { to: this.route, tabindex: '0' }\n\t\t\t}\n\t\t\tif (this.clickable) {\n\t\t\t\treturn { href: '#', role: 'button', tabindex: '0' }\n\t\t\t}\n\t\t\treturn {}\n\t\t},\n\n\t\t/**\n\t\t * Whether the block takes the stacked board look (`layout`).\n\t\t *\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-a-stats-block-can-take-the-stacked-board-look\n\t\t * @return {boolean}\n\t\t */\n\t\tisStacked() {\n\t\t\treturn this.layout === 'stacked'\n\t\t},\n\n\t\trootClasses() {\n\t\t\treturn {\n\t\t\t\t// Canonical + legacy, in pairs: the canonical class carries the\n\t\t\t\t// look, the legacy one keeps existing app CSS matching.\n\t\t\t\t// The canonical card is already horizontal, so the legacy pair\n\t\t\t\t// is emitted for app CSS but carries no look of its own.\n\t\t\t\t'cn-kpi-card--horizontal': !this.isStacked && (this.horizontal || !this.vertical),\n\t\t\t\t'cn-kpi-card--stacked': this.isStacked,\n\t\t\t\t'cn-kpi-card--board': this.isBoardLook,\n\t\t\t\t'cn-stats-block--horizontal': this.horizontal || !this.vertical,\n\t\t\t\t'cn-kpi-card--vertical': this.vertical,\n\t\t\t\t'cn-stats-block--vertical': this.vertical,\n\t\t\t\t'cn-kpi-card--filled': this.filled,\n\t\t\t\t'cn-kpi-card--clickable': this.isInteractive,\n\t\t\t\t'cn-stats-block--clickable': this.isInteractive,\n\t\t\t\t'cn-kpi-card--error': this.hasError,\n\t\t\t\t[`cn-kpi-card--${this.variant}`]: this.variant !== 'default' && this.hasError === false,\n\t\t\t\t// An errored tile tints itself. Requiring the consumer to also\n\t\t\t\t// pass `variant=\"error\"` would mean every caller remembering\n\t\t\t\t// two props to express one state, and forgetting the second is\n\t\t\t\t// invisible — a tile that reads \"Unavailable\" in the default\n\t\t\t\t// colour looks like ordinary content.\n\t\t\t\t'cn-stats-block--error': this.hasError,\n\t\t\t\t[`cn-stats-block--${this.variant}`]: this.variant !== 'default' && this.hasError === false,\n\t\t\t}\n\t\t},\n\n\t\ticonClasses() {\n\t\t\treturn {\n\t\t\t\t[`cn-stats-block__icon--${this.variant}`]: this.variant !== 'default',\n\t\t\t}\n\t\t},\n\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * A manifest string through the host's label lookup (`cnTranslate`), so a key\n\t\t * such as \"Mine\" reads in the user's language.\n\t\t *\n\t\t * @param {string} text The text as written in the manifest.\n\t\t * @return {string} The translated text.\n\t\t */\n\t\ttl(text) {\n\t\t\treturn typeof this.cnTranslate === 'function' ? this.cnTranslate(text) : text\n\t\t},\n\n\t\tformatBreakdownLabel(key) {\n\t\t\treturn key.charAt(0).toUpperCase() + key.slice(1) + ':'\n\t\t},\n\n\t\tonClick(event) {\n\t\t\t// When route is set, router-link handles navigation — do not emit click\n\t\t\tif (this.route) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (this.clickable) {\n\t\t\t\tevent.preventDefault()\n\t\t\t\tthis.$emit('click', event)\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n/*\n * The card's own look — box, body stack, icon circle, title, value, variants,\n * hover — is the CANONICAL KPI card in src/css/kpi-card.css, shared with\n * CnStatWidget. The rules that lived here duplicated it in different numbers\n * (a 1.2rem count row, a 40px icon, hardcoded rgba() tints that ignored a\n * re-themed palette), and that duplication IS how the two KPI looks drifted.\n * What is left below is this component's own extras: the breakdown block, the\n * loading line and the empty line.\n *\n * Those extras are secondary KPI text, so they read the shared\n * `--cn-kpi-label-*` scale rather than carrying sizes of their own — an empty\n * state that is a different size from the label it replaces is the same drift\n * in miniature.\n */\n\n.cn-stats-block__header {\n\tmax-width: 100%;\n\tmin-width: 0;\n}\n\n.cn-stats-block__loading {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: flex-start;\n\tgap: 6px;\n\tfont-size: var(--cn-kpi-label-size, 13px);\n\tcolor: var(--cn-kpi-label-color, var(--color-text-maxcontrast));\n}\n\n.cn-stats-block__empty {\n\ttext-align: start;\n\tfont-size: var(--cn-kpi-label-size, 13px);\n\tcolor: var(--cn-kpi-label-color, var(--color-text-maxcontrast));\n\tfont-style: italic;\n}\n\n.cn-stats-block__breakdown {\n\tmargin-top: 4px;\n\tpadding: 8px;\n\tfont-size: var(--cn-kpi-label-size, 13px);\n\tbackground: var(--color-background-hover);\n\tborder-radius: var(--border-radius);\n\tborder: 1px solid var(--color-border);\n\twidth: 100%;\n\ttext-align: start;\n}\n\n.cn-stats-block__breakdown-item {\n\tdisplay: flex;\n\tjustify-content: space-between;\n\talign-items: center;\n\tgap: 8px;\n\tmargin-bottom: 4px;\n}\n\n.cn-stats-block__breakdown-item:last-child {\n\tmargin-bottom: 0;\n}\n\n.cn-stats-block__breakdown-label {\n\tfont-weight: 500;\n\tcolor: var(--color-main-text);\n}\n\n/* The value takes the card's accent, so a breakdown reads as part of the tile\n it sits in rather than as a table that happened to land there. It used to\n carry a `--color-background-hover` chip — on a `--color-background-hover`\n breakdown box, which is to say an invisible chip drawn at the cost of two\n extra declarations and a differently-shaped hit area. The per-key modifiers\n below still win: `invalid`, `deleted` and `published` mean something\n specific and outrank the tile's own colour. */\n.cn-stats-block__breakdown-value {\n\tfont-weight: var(--cn-kpi-title-weight, 600);\n\tcolor: var(--cn-kpi-accent, var(--color-primary-element));\n}\n\n.cn-stats-block__breakdown-value--invalid { color: var(--color-element-warning); }\n\n.cn-stats-block__breakdown-value--deleted { color: var(--color-element-error); }\n\n.cn-stats-block__breakdown-value--published { color: var(--color-element-success); }\n</style>\n"],"names":["t"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"CnStatsBlock.vue3.js","sources":["../../../../src/components/CnStatsBlock/CnStatsBlock.vue?vue&type=template&id=770aba44&lang.js"],"sourcesContent":["<!--\n - CnStatsBlock renders the CANONICAL KPI card (src/css/kpi-card.css) — the\n - same markup and the same stylesheet CnStatWidget renders, so OpenCatalogi's\n - KPIs and dossiq's are one design rather than two. This component's own\n - `cn-stats-block*` classes stay on the same elements for backwards\n - compatibility with app CSS that targets them; the LOOK comes from the\n - shared sheet.\n -\n - The comment lives OUT here rather than inside <template>: a comment node\n - beside the root element makes the component multi-root in Vue 3, and a\n - multi-root component has no root to put class attributes on — `rootClasses`\n - silently stopped reaching the DOM.\n-->\n<template>\n\t<component\n\t\t:is=\"componentTag\"\n\t\tclass=\"cn-kpi-card cn-stats-block\"\n\t\t:class=\"rootClasses\"\n\t\tv-bind=\"componentAttrs\"\n\t\t@click=\"onClick\">\n\t\t<!-- Icon -->\n\t\t<div v-if=\"hasIcon && !isStacked && !isBoardLook\" class=\"cn-kpi-card__icon cn-stats-block__icon\" :class=\"iconClasses\">\n\t\t\t<slot name=\"icon\">\n\t\t\t\t<component :is=\"icon\" v-if=\"icon\" :size=\"iconSize\" />\n\t\t\t</slot>\n\t\t</div>\n\n\t\t<!-- Content -->\n\t\t<div class=\"cn-kpi-card__body cn-stats-block__content\">\n\t\t\t<div class=\"cn-stats-block__header\">\n\t\t\t\t<!-- Board look: an 18px glyph before the label, only on a link tile. -->\n\t\t\t\t<span\n\t\t\t\t\tv-if=\"isBoardLook && isInteractive && hasIcon\"\n\t\t\t\t\tclass=\"cn-kpi-card__glyph cn-stats-block__glyph\"\n\t\t\t\t\tdata-testid=\"cn-stats-block-glyph\"\n\t\t\t\t\taria-hidden=\"true\">\n\t\t\t\t\t<slot name=\"icon\">\n\t\t\t\t\t\t<component :is=\"icon\" v-if=\"icon\" :size=\"18\" />\n\t\t\t\t\t</slot>\n\t\t\t\t</span>\n\t\t\t\t<h4 class=\"cn-kpi-card__title\" :title=\"title || undefined\">\n\t\t\t\t\t{{ title ? tl(title) : t('nextcloud-vue', 'Objects') }}\n\t\t\t\t</h4>\n\t\t\t</div>\n\n\t\t\t<div v-if=\"hasError\" class=\"cn-stats-block__count cn-stats-block__count--error\">\n\t\t\t\t<span class=\"cn-stats-block__count-value\">&mdash;</span>\n\t\t\t\t<span class=\"cn-stats-block__count-label\">{{ errorLabel }}</span>\n\t\t\t</div>\n\t\t\t<div v-else-if=\"hasValueSlot || count > 0 || (showZeroCount && count === 0)\" class=\"cn-kpi-card__value-row cn-stats-block__count\">\n\t\t\t\t<span class=\"cn-kpi-card__value cn-stats-block__count-value\">\n\t\t\t\t\t<!-- @slot Override the prominently-displayed value — render a pre-formatted string (currency, percent, a \"—\" placeholder, …). `count` stays the raw number; this is presentation only. Defaults to the localized count. -->\n\t\t\t\t\t<!-- @binding {number} count The raw numeric count. -->\n\t\t\t\t\t<!-- @binding {string} formatted The default localized count string. -->\n\t\t\t\t\t<slot name=\"value\" :count=\"count\" :formatted=\"formattedCount\">{{ formattedCount }}</slot>\n\t\t\t\t</span>\n\t\t\t\t<span class=\"cn-kpi-card__label cn-stats-block__count-label\">{{ countLabel ? tl(countLabel) : countLabel }}</span>\n\t\t\t</div>\n\t\t\t<div v-else-if=\"loading\" class=\"cn-stats-block__loading\">\n\t\t\t\t<NcLoadingIcon :size=\"16\" />\n\t\t\t\t{{ loadingLabel }}\n\t\t\t</div>\n\t\t\t<div v-else class=\"cn-stats-block__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\n\t\t\t<!-- Breakdown details -->\n\t\t\t<div\n\t\t\t\tv-if=\"hasError === false && breakdown && (hasValueSlot || count > 0 || showZeroCount)\"\n\t\t\t\tclass=\"cn-stats-block__breakdown\">\n\t\t\t\t<div\n\t\t\t\t\tv-for=\"(value, key) in breakdown\"\n\t\t\t\t\t:key=\"key\"\n\t\t\t\t\tclass=\"cn-stats-block__breakdown-item\">\n\t\t\t\t\t<span class=\"cn-stats-block__breakdown-label\">{{ formatBreakdownLabel(key) }}</span>\n\t\t\t\t\t<span\n\t\t\t\t\t\tclass=\"cn-stats-block__breakdown-value\"\n\t\t\t\t\t\t:class=\"'cn-stats-block__breakdown-value--' + key\">\n\t\t\t\t\t\t{{ value }}\n\t\t\t\t\t</span>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t</component>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcLoadingIcon } from '@nextcloud/vue'\nimport { normalizeLook } from '../../composables/useLook.js'\n\n// The canonical KPI look, shared with CnStatWidget. Imported here so the\n// card is styled even when the consuming app has not pulled in the\n// library's global css/index.css.\nimport '../../css/kpi-card.css'\n\n/**\n * CnStatsBlock — Statistics display card with icon, count, and optional breakdown.\n *\n * Renders the canonical KPI card: a large circular icon beside a left-aligned\n * number, drawing no box of its own because the wrapper around it already\n * draws one. Supports colour variants, icons and clickable state. Use in a\n * CnKpiGrid for responsive dashboard layouts.\n *\n * Basic (canonical horizontal card)\n * ```vue\n * <CnStatsBlock title=\"Cases\" :count=\"42\" count-label=\"open cases\" />\n * ```\n *\n * With icon and variant\n * ```vue\n * <CnStatsBlock\n * title=\"Open Cases\"\n * :count=\"42\"\n * :icon=\"BriefcaseOutline\"\n * variant=\"primary\"\n * clickable\n * @click=\"goToCases\" />\n * ```\n *\n * Stacked and boxed — for a block mounted with no wrapper around it\n * ```vue\n * <CnStatsBlock title=\"Cases\" :count=\"42\" vertical filled />\n * ```\n *\n * With route-based navigation (renders as <router-link>)\n * ```vue\n * <CnStatsBlock\n * title=\"Open Cases\"\n * :count=\"42\"\n * :icon=\"BriefcaseOutline\"\n * variant=\"primary\"\n * :route=\"{ name: 'Cases', query: { status: 'open' } }\" />\n * ```\n *\n * With breakdown\n * ```vue\n * <CnStatsBlock\n * title=\"Cases\"\n * :count=\"42\"\n * :breakdown=\"{ total: 100, invalid: 3, deleted: 5, published: 92 }\" />\n * ```\n *\n * Custom icon slot\n * ```vue\n * <CnStatsBlock title=\"Files\" :count=\"128\">\n * <template #icon>\n * <FileDocumentOutline :size=\"24\" />\n * </template>\n * </CnStatsBlock>\n * ```\n */\nexport default {\n\tname: 'CnStatsBlock',\n\n\tcomponents: {\n\t\tNcLoadingIcon,\n\t},\n\n\tinject: {\n\t\t/**\n\t\t * The look the app is drawn in, provided by CnAppRoot.\n\t\t *\n\t\t * @spec openspec/changes/screens-dashboard-parity/specs/dashboard-page/spec.md#requirement-the-board-kpi-tile\n\t\t */\n\t\tcnLook: { default: 'nextcloud' },\n\t\t/**\n\t\t * The host's label lookup (CnAppRoot provides it); identity without one, so a\n\t\t * label that is not a key renders as written.\n\t\t */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/** Block title */\n\t\ttitle: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** The main count number to display prominently */\n\t\tcount: {\n\t\t\ttype: Number,\n\t\t\tdefault: 0,\n\t\t},\n\n\t\t/** Label displayed next to the count */\n\t\tcountLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'objects'),\n\t\t},\n\n\t\t/** Detailed breakdown object (key-value pairs) */\n\t\tbreakdown: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Whether data is currently loading */\n\t\tloading: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Text shown while loading */\n\t\tloadingLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Loading…'),\n\t\t},\n\n\t\t/** Text shown when count is 0 */\n\t\temptyLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'No items found'),\n\t\t},\n\n\t\t/**\n\t\t * The tile could not load its number. Anything truthy counts — pass the\n\t\t * caught error itself, or just `true`.\n\t\t *\n\t\t * The tile then shows a dash and `errorLabel` INSTEAD of a count, and\n\t\t * tints itself `error`. This takes precedence over `count`, `loading`\n\t\t * and `emptyLabel` deliberately: a stale or defaulted number rendered\n\t\t * during a failure is the exact thing this prop exists to stop. A\n\t\t * dashboard showing 0 because the backend is down is worse than one\n\t\t * showing nothing, because 0 is a number a reader will believe.\n\t\t *\n\t\t * @type {boolean|string|Error|null}\n\t\t */\n\t\terror: {\n\t\t\t// TYPE ORDER IS SEMANTIC HERE. Vue casts an empty value to `true`\n\t\t\t// when Boolean appears BEFORE String in the union, so\n\t\t\t// `[Boolean, String]` would read `error=\"\"` as an error. A caller\n\t\t\t// clearing its message back to '' is reporting RECOVERY, and that\n\t\t\t// would have pinned the tile in its error state after the fetch\n\t\t\t// succeeded again. String first keeps '' falsy.\n\t\t\t// eslint-disable-next-line vue/prefer-prop-type-boolean-first -- Boolean first would make `error=\"\"` truthy, which is the recovery signal.\n\t\t\ttype: [String, Boolean, Object],\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Text shown in place of the count when `error` is set. */\n\t\terrorLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Unavailable'),\n\t\t},\n\n\t\t/** Icon component (e.g., imported MDI icon) */\n\t\ticon: {\n\t\t\ttype: [Object, Function],\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Icon size in pixels */\n\t\ticonSize: {\n\t\t\ttype: Number,\n\t\t\tdefault: 24,\n\t\t},\n\n\t\t/** Color variant: 'default', 'primary', 'success', 'warning', 'error' */\n\t\tvariant: {\n\t\t\ttype: String,\n\t\t\tdefault: 'default',\n\t\t\tvalidator: (v) => ['default', 'primary', 'success', 'warning', 'error'].includes(v),\n\t\t},\n\n\t\t/**\n\t\t * Stack the icon above a centred number instead of placing it beside\n\t\t * one. The canonical KPI card is horizontal, so this is the opt-out;\n\t\t * `horizontal` below is kept only for callers that already pass it.\n\t\t */\n\t\tvertical: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * `stacked` is the board's tile, as CnStatWidget `content.layout:\n\t\t * \"stacked\"`: no icon circle, a 14px muted title, the number at\n\t\t * 34px/700 in the text colour. Empty (the default) keeps the KPI card.\n\t\t *\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-a-stats-block-can-take-the-stacked-board-look\n\t\t */\n\t\tlayout: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Lay the icon left of the content. No longer needed — this is the\n\t\t * canonical card's own layout — and kept so existing callers that pass\n\t\t * `horizontal` keep working. Pass `vertical` to stack instead.\n\t\t *\n\t\t * @deprecated since 2.25.0, the horizontal layout is the default.\n\t\t */\n\t\thorizontal: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Draw the card's own grey box. Off by default: a stats block is\n\t\t * normally rendered inside a wrapper that already draws a card, and a\n\t\t * second box reads as a card inside a card. Turn it on for a block\n\t\t * mounted with no wrapper around it.\n\t\t */\n\t\tfilled: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Whether the card is clickable */\n\t\tclickable: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Whether to display 0 as a count value instead of the empty label */\n\t\tshowZeroCount: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Vue Router location object for declarative navigation.\n\t\t * When set, the card renders as a <router-link> and clickable styles are implied.\n\t\t * { name: 'Cases', query: { status: 'open' } }\n\t\t * { path: '/catalogi' }\n\t\t */\n\t\troute: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\temits: ['click'],\n\n\tcomputed: {\n\t\t/**\n\t\t * Whether the app takes the board look.\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\tisBoardLook() {\n\t\t\treturn normalizeLook(this.cnLook) === 'board'\n\t\t},\n\n\t\thasIcon() {\n\t\t\treturn this.icon !== null || this.$slots.icon || this.$slots.icon\n\t\t},\n\n\t\t/**\n\t\t * Whether a consumer provided the `value` slot. When set, the value\n\t\t * area always renders (the slot decides what to show, even at count 0).\n\t\t */\n\t\thasValueSlot() {\n\t\t\treturn !!this.$slots.value || !!this.$slots.value\n\t\t},\n\n\t\t/**\n\t\t * Whether the tile is in its error state.\n\t\t *\n\t\t * Anything truthy counts, so a caller can pass the caught error itself\n\t\t * rather than converting it to a boolean first. An empty string is NOT\n\t\t * an error — that is a caller who cleared the message, not one\n\t\t * reporting a failure.\n\t\t *\n\t\t * @return {boolean} True when the tile should show `errorLabel`.\n\t\t */\n\t\thasError() {\n\t\t\treturn Boolean(this.error)\n\t\t},\n\n\t\tformattedCount() {\n\t\t\treturn this.count.toLocaleString()\n\t\t},\n\n\t\t/**\n\t\t * Whether the card is interactive (clickable or has a route).\n\t\t * Used for applying hover/focus styles.\n\t\t */\n\t\tisInteractive() {\n\t\t\treturn !!this.route || this.clickable\n\t\t},\n\n\t\t/**\n\t\t * Determines which HTML element or component to render.\n\t\t * - route set → 'router-link' (SPA navigation)\n\t\t * - clickable → 'a' (app handles click via event)\n\t\t * - neither → 'div' (static display)\n\t\t */\n\t\tcomponentTag() {\n\t\t\tif (this.route) {\n\t\t\t\treturn 'router-link'\n\t\t\t}\n\t\t\tif (this.clickable) {\n\t\t\t\treturn 'a'\n\t\t\t}\n\t\t\treturn 'div'\n\t\t},\n\n\t\t/**\n\t\t * Dynamic attributes for the root element based on rendering mode.\n\t\t */\n\t\tcomponentAttrs() {\n\t\t\tif (this.route) {\n\t\t\t\treturn { to: this.route, tabindex: '0' }\n\t\t\t}\n\t\t\tif (this.clickable) {\n\t\t\t\treturn { href: '#', role: 'button', tabindex: '0' }\n\t\t\t}\n\t\t\treturn {}\n\t\t},\n\n\t\t/**\n\t\t * Whether the block takes the stacked board look (`layout`).\n\t\t *\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-a-stats-block-can-take-the-stacked-board-look\n\t\t * @return {boolean}\n\t\t */\n\t\tisStacked() {\n\t\t\treturn this.layout === 'stacked'\n\t\t},\n\n\t\trootClasses() {\n\t\t\treturn {\n\t\t\t\t// Canonical + legacy, in pairs: the canonical class carries the\n\t\t\t\t// look, the legacy one keeps existing app CSS matching.\n\t\t\t\t// The canonical card is already horizontal, so the legacy pair\n\t\t\t\t// is emitted for app CSS but carries no look of its own.\n\t\t\t\t'cn-kpi-card--horizontal': !this.isStacked && (this.horizontal || !this.vertical),\n\t\t\t\t'cn-kpi-card--stacked': this.isStacked,\n\t\t\t\t'cn-kpi-card--board': this.isBoardLook,\n\t\t\t\t'cn-stats-block--horizontal': this.horizontal || !this.vertical,\n\t\t\t\t'cn-kpi-card--vertical': this.vertical,\n\t\t\t\t'cn-stats-block--vertical': this.vertical,\n\t\t\t\t'cn-kpi-card--filled': this.filled,\n\t\t\t\t'cn-kpi-card--clickable': this.isInteractive,\n\t\t\t\t'cn-stats-block--clickable': this.isInteractive,\n\t\t\t\t'cn-kpi-card--error': this.hasError,\n\t\t\t\t[`cn-kpi-card--${this.variant}`]: this.variant !== 'default' && this.hasError === false,\n\t\t\t\t// An errored tile tints itself. Requiring the consumer to also\n\t\t\t\t// pass `variant=\"error\"` would mean every caller remembering\n\t\t\t\t// two props to express one state, and forgetting the second is\n\t\t\t\t// invisible — a tile that reads \"Unavailable\" in the default\n\t\t\t\t// colour looks like ordinary content.\n\t\t\t\t'cn-stats-block--error': this.hasError,\n\t\t\t\t[`cn-stats-block--${this.variant}`]: this.variant !== 'default' && this.hasError === false,\n\t\t\t}\n\t\t},\n\n\t\ticonClasses() {\n\t\t\treturn {\n\t\t\t\t[`cn-stats-block__icon--${this.variant}`]: this.variant !== 'default',\n\t\t\t}\n\t\t},\n\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * A manifest string through the host's label lookup (`cnTranslate`), so a key\n\t\t * such as \"Mine\" reads in the user's language.\n\t\t *\n\t\t * @param {string} text The text as written in the manifest.\n\t\t * @return {string} The translated text.\n\t\t */\n\t\ttl(text) {\n\t\t\treturn typeof this.cnTranslate === 'function' ? this.cnTranslate(text) : text\n\t\t},\n\n\t\tformatBreakdownLabel(key) {\n\t\t\treturn key.charAt(0).toUpperCase() + key.slice(1) + ':'\n\t\t},\n\n\t\tonClick(event) {\n\t\t\t// When route is set, router-link handles navigation — do not emit click\n\t\t\tif (this.route) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (this.clickable) {\n\t\t\t\tevent.preventDefault()\n\t\t\t\tthis.$emit('click', event)\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n/*\n * The card's own look — box, body stack, icon circle, title, value, variants,\n * hover — is the CANONICAL KPI card in src/css/kpi-card.css, shared with\n * CnStatWidget. The rules that lived here duplicated it in different numbers\n * (a 1.2rem count row, a 40px icon, hardcoded rgba() tints that ignored a\n * re-themed palette), and that duplication IS how the two KPI looks drifted.\n * What is left below is this component's own extras: the breakdown block, the\n * loading line and the empty line.\n *\n * Those extras are secondary KPI text, so they read the shared\n * `--cn-kpi-label-*` scale rather than carrying sizes of their own — an empty\n * state that is a different size from the label it replaces is the same drift\n * in miniature.\n */\n\n.cn-stats-block__header {\n\tmax-width: 100%;\n\tmin-width: 0;\n}\n\n.cn-stats-block__loading {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: flex-start;\n\tgap: 6px;\n\tfont-size: var(--cn-kpi-label-size, 13px);\n\tcolor: var(--cn-kpi-label-color, var(--color-text-maxcontrast));\n}\n\n.cn-stats-block__empty {\n\ttext-align: start;\n\tfont-size: var(--cn-kpi-label-size, 13px);\n\tcolor: var(--cn-kpi-label-color, var(--color-text-maxcontrast));\n\tfont-style: italic;\n}\n\n.cn-stats-block__breakdown {\n\tmargin-top: 4px;\n\tpadding: 8px;\n\tfont-size: var(--cn-kpi-label-size, 13px);\n\tbackground: var(--color-background-hover);\n\tborder-radius: var(--border-radius);\n\tborder: 1px solid var(--color-border);\n\twidth: 100%;\n\ttext-align: start;\n}\n\n.cn-stats-block__breakdown-item {\n\tdisplay: flex;\n\tjustify-content: space-between;\n\talign-items: center;\n\tgap: 8px;\n\tmargin-bottom: 4px;\n}\n\n.cn-stats-block__breakdown-item:last-child {\n\tmargin-bottom: 0;\n}\n\n.cn-stats-block__breakdown-label {\n\tfont-weight: 500;\n\tcolor: var(--color-main-text);\n}\n\n/* The value takes the card's accent, so a breakdown reads as part of the tile\n it sits in rather than as a table that happened to land there. It used to\n carry a `--color-background-hover` chip — on a `--color-background-hover`\n breakdown box, which is to say an invisible chip drawn at the cost of two\n extra declarations and a differently-shaped hit area. The per-key modifiers\n below still win: `invalid`, `deleted` and `published` mean something\n specific and outrank the tile's own colour. */\n.cn-stats-block__breakdown-value {\n\tfont-weight: var(--cn-kpi-title-weight, 600);\n\tcolor: var(--cn-kpi-accent, var(--color-primary-element));\n}\n\n.cn-stats-block__breakdown-value--invalid { color: var(--color-element-warning); }\n\n.cn-stats-block__breakdown-value--deleted { color: var(--color-element-error); }\n\n.cn-stats-block__breakdown-value--published { color: var(--color-element-success); }\n</style>\n"],"names":["_createElementBlock","_normalizeClass","_renderSlot","_openBlock","_createBlock","_resolveDynamicComponent","_createElementVNode","_toDisplayString","_createVNode","_createTextVNode"],"mappings":"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"}
1
+ {"version":3,"file":"CnStatsBlock.vue3.js","sources":["../../../../src/components/CnStatsBlock/CnStatsBlock.vue?vue&type=template&id=3ebbfa38&lang.js"],"sourcesContent":["<!--\n - CnStatsBlock renders the CANONICAL KPI card (src/css/kpi-card.css) — the\n - same markup and the same stylesheet CnStatWidget renders, so OpenCatalogi's\n - KPIs and dossiq's are one design rather than two. This component's own\n - `cn-stats-block*` classes stay on the same elements for backwards\n - compatibility with app CSS that targets them; the LOOK comes from the\n - shared sheet.\n -\n - The comment lives OUT here rather than inside <template>: a comment node\n - beside the root element makes the component multi-root in Vue 3, and a\n - multi-root component has no root to put class attributes on — `rootClasses`\n - silently stopped reaching the DOM.\n-->\n<template>\n\t<component\n\t\t:is=\"componentTag\"\n\t\tclass=\"cn-kpi-card cn-stats-block\"\n\t\t:class=\"rootClasses\"\n\t\tv-bind=\"componentAttrs\"\n\t\t@click=\"onClick\">\n\t\t<!-- Icon -->\n\t\t<div v-if=\"hasIcon && !isStacked && !isBoardLook\" class=\"cn-kpi-card__icon cn-stats-block__icon\" :class=\"iconClasses\">\n\t\t\t<slot name=\"icon\">\n\t\t\t\t<component :is=\"icon\" v-if=\"icon\" :size=\"iconSize\" />\n\t\t\t</slot>\n\t\t</div>\n\n\t\t<!-- Content -->\n\t\t<div class=\"cn-kpi-card__body cn-stats-block__content\">\n\t\t\t<div class=\"cn-stats-block__header\">\n\t\t\t\t<!-- Board look: an 18px glyph before the label, only on a link tile. -->\n\t\t\t\t<span\n\t\t\t\t\tv-if=\"isBoardLook && isInteractive && hasIcon\"\n\t\t\t\t\tclass=\"cn-kpi-card__glyph cn-stats-block__glyph\"\n\t\t\t\t\tdata-testid=\"cn-stats-block-glyph\"\n\t\t\t\t\taria-hidden=\"true\">\n\t\t\t\t\t<slot name=\"icon\">\n\t\t\t\t\t\t<component :is=\"icon\" v-if=\"icon\" :size=\"18\" />\n\t\t\t\t\t</slot>\n\t\t\t\t</span>\n\t\t\t\t<h4 class=\"cn-kpi-card__title\" :title=\"title || undefined\">\n\t\t\t\t\t{{ title ? tl(title) : t('nextcloud-vue', 'Objects') }}\n\t\t\t\t</h4>\n\t\t\t</div>\n\n\t\t\t<div v-if=\"hasError\" class=\"cn-stats-block__count cn-stats-block__count--error\">\n\t\t\t\t<span class=\"cn-stats-block__count-value\">&mdash;</span>\n\t\t\t\t<span class=\"cn-stats-block__count-label\">{{ errorLabel }}</span>\n\t\t\t</div>\n\t\t\t<div v-else-if=\"hasValueSlot || count > 0 || (showZeroCount && count === 0)\" class=\"cn-kpi-card__value-row cn-stats-block__count\">\n\t\t\t\t<span class=\"cn-kpi-card__value cn-stats-block__count-value\">\n\t\t\t\t\t<!-- @slot Override the prominently-displayed value — render a pre-formatted string (currency, percent, a \"—\" placeholder, …). `count` stays the raw number; this is presentation only. Defaults to the localized count. -->\n\t\t\t\t\t<!-- @binding {number} count The raw numeric count. -->\n\t\t\t\t\t<!-- @binding {string} formatted The default localized count string. -->\n\t\t\t\t\t<slot name=\"value\" :count=\"count\" :formatted=\"formattedCount\">{{ formattedCount }}</slot>\n\t\t\t\t</span>\n\t\t\t\t<span class=\"cn-kpi-card__label cn-stats-block__count-label\">{{ countLabel ? tl(countLabel) : countLabel }}</span>\n\t\t\t</div>\n\t\t\t<div v-else-if=\"loading\" class=\"cn-stats-block__loading\">\n\t\t\t\t<NcLoadingIcon :size=\"16\" />\n\t\t\t\t{{ loadingLabel }}\n\t\t\t</div>\n\t\t\t<div v-else class=\"cn-stats-block__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\n\t\t\t<!-- Breakdown details -->\n\t\t\t<div\n\t\t\t\tv-if=\"hasError === false && breakdown && (hasValueSlot || count > 0 || showZeroCount)\"\n\t\t\t\tclass=\"cn-stats-block__breakdown\">\n\t\t\t\t<div\n\t\t\t\t\tv-for=\"(value, key) in breakdown\"\n\t\t\t\t\t:key=\"key\"\n\t\t\t\t\tclass=\"cn-stats-block__breakdown-item\">\n\t\t\t\t\t<span class=\"cn-stats-block__breakdown-label\">{{ formatBreakdownLabel(key) }}</span>\n\t\t\t\t\t<span\n\t\t\t\t\t\tclass=\"cn-stats-block__breakdown-value\"\n\t\t\t\t\t\t:class=\"'cn-stats-block__breakdown-value--' + key\">\n\t\t\t\t\t\t{{ value }}\n\t\t\t\t\t</span>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t</component>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcLoadingIcon } from '@nextcloud/vue'\nimport { normalizeLook } from '../../composables/useLook.js'\nimport { userLocale } from '../../utils/userLocale.js'\n\n// The canonical KPI look, shared with CnStatWidget. Imported here so the\n// card is styled even when the consuming app has not pulled in the\n// library's global css/index.css.\nimport '../../css/kpi-card.css'\n\n/**\n * CnStatsBlock — Statistics display card with icon, count, and optional breakdown.\n *\n * Renders the canonical KPI card: a large circular icon beside a left-aligned\n * number, drawing no box of its own because the wrapper around it already\n * draws one. Supports colour variants, icons and clickable state. Use in a\n * CnKpiGrid for responsive dashboard layouts.\n *\n * Basic (canonical horizontal card)\n * ```vue\n * <CnStatsBlock title=\"Cases\" :count=\"42\" count-label=\"open cases\" />\n * ```\n *\n * With icon and variant\n * ```vue\n * <CnStatsBlock\n * title=\"Open Cases\"\n * :count=\"42\"\n * :icon=\"BriefcaseOutline\"\n * variant=\"primary\"\n * clickable\n * @click=\"goToCases\" />\n * ```\n *\n * Stacked and boxed — for a block mounted with no wrapper around it\n * ```vue\n * <CnStatsBlock title=\"Cases\" :count=\"42\" vertical filled />\n * ```\n *\n * With route-based navigation (renders as <router-link>)\n * ```vue\n * <CnStatsBlock\n * title=\"Open Cases\"\n * :count=\"42\"\n * :icon=\"BriefcaseOutline\"\n * variant=\"primary\"\n * :route=\"{ name: 'Cases', query: { status: 'open' } }\" />\n * ```\n *\n * With breakdown\n * ```vue\n * <CnStatsBlock\n * title=\"Cases\"\n * :count=\"42\"\n * :breakdown=\"{ total: 100, invalid: 3, deleted: 5, published: 92 }\" />\n * ```\n *\n * Custom icon slot\n * ```vue\n * <CnStatsBlock title=\"Files\" :count=\"128\">\n * <template #icon>\n * <FileDocumentOutline :size=\"24\" />\n * </template>\n * </CnStatsBlock>\n * ```\n */\nexport default {\n\tname: 'CnStatsBlock',\n\n\tcomponents: {\n\t\tNcLoadingIcon,\n\t},\n\n\tinject: {\n\t\t/**\n\t\t * The look the app is drawn in, provided by CnAppRoot.\n\t\t *\n\t\t * @spec openspec/changes/screens-dashboard-parity/specs/dashboard-page/spec.md#requirement-the-board-kpi-tile\n\t\t */\n\t\tcnLook: { default: 'nextcloud' },\n\t\t/**\n\t\t * The host's label lookup (CnAppRoot provides it); identity without one, so a\n\t\t * label that is not a key renders as written.\n\t\t */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/** Block title */\n\t\ttitle: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** The main count number to display prominently */\n\t\tcount: {\n\t\t\ttype: Number,\n\t\t\tdefault: 0,\n\t\t},\n\n\t\t/** Label displayed next to the count */\n\t\tcountLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'objects'),\n\t\t},\n\n\t\t/** Detailed breakdown object (key-value pairs) */\n\t\tbreakdown: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Whether data is currently loading */\n\t\tloading: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Text shown while loading */\n\t\tloadingLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Loading…'),\n\t\t},\n\n\t\t/** Text shown when count is 0 */\n\t\temptyLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'No items found'),\n\t\t},\n\n\t\t/**\n\t\t * The tile could not load its number. Anything truthy counts — pass the\n\t\t * caught error itself, or just `true`.\n\t\t *\n\t\t * The tile then shows a dash and `errorLabel` INSTEAD of a count, and\n\t\t * tints itself `error`. This takes precedence over `count`, `loading`\n\t\t * and `emptyLabel` deliberately: a stale or defaulted number rendered\n\t\t * during a failure is the exact thing this prop exists to stop. A\n\t\t * dashboard showing 0 because the backend is down is worse than one\n\t\t * showing nothing, because 0 is a number a reader will believe.\n\t\t *\n\t\t * @type {boolean|string|Error|null}\n\t\t */\n\t\terror: {\n\t\t\t// TYPE ORDER IS SEMANTIC HERE. Vue casts an empty value to `true`\n\t\t\t// when Boolean appears BEFORE String in the union, so\n\t\t\t// `[Boolean, String]` would read `error=\"\"` as an error. A caller\n\t\t\t// clearing its message back to '' is reporting RECOVERY, and that\n\t\t\t// would have pinned the tile in its error state after the fetch\n\t\t\t// succeeded again. String first keeps '' falsy.\n\t\t\t// eslint-disable-next-line vue/prefer-prop-type-boolean-first -- Boolean first would make `error=\"\"` truthy, which is the recovery signal.\n\t\t\ttype: [String, Boolean, Object],\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Text shown in place of the count when `error` is set. */\n\t\terrorLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Unavailable'),\n\t\t},\n\n\t\t/** Icon component (e.g., imported MDI icon) */\n\t\ticon: {\n\t\t\ttype: [Object, Function],\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Icon size in pixels */\n\t\ticonSize: {\n\t\t\ttype: Number,\n\t\t\tdefault: 24,\n\t\t},\n\n\t\t/** Color variant: 'default', 'primary', 'success', 'warning', 'error' */\n\t\tvariant: {\n\t\t\ttype: String,\n\t\t\tdefault: 'default',\n\t\t\tvalidator: (v) => ['default', 'primary', 'success', 'warning', 'error'].includes(v),\n\t\t},\n\n\t\t/**\n\t\t * Stack the icon above a centred number instead of placing it beside\n\t\t * one. The canonical KPI card is horizontal, so this is the opt-out;\n\t\t * `horizontal` below is kept only for callers that already pass it.\n\t\t */\n\t\tvertical: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * `stacked` is the board's tile, as CnStatWidget `content.layout:\n\t\t * \"stacked\"`: no icon circle, a 14px muted title, the number at\n\t\t * 34px/700 in the text colour. Empty (the default) keeps the KPI card.\n\t\t *\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-a-stats-block-can-take-the-stacked-board-look\n\t\t */\n\t\tlayout: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Lay the icon left of the content. No longer needed — this is the\n\t\t * canonical card's own layout — and kept so existing callers that pass\n\t\t * `horizontal` keep working. Pass `vertical` to stack instead.\n\t\t *\n\t\t * @deprecated since 2.25.0, the horizontal layout is the default.\n\t\t */\n\t\thorizontal: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Draw the card's own grey box. Off by default: a stats block is\n\t\t * normally rendered inside a wrapper that already draws a card, and a\n\t\t * second box reads as a card inside a card. Turn it on for a block\n\t\t * mounted with no wrapper around it.\n\t\t */\n\t\tfilled: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Whether the card is clickable */\n\t\tclickable: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Whether to display 0 as a count value instead of the empty label */\n\t\tshowZeroCount: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Vue Router location object for declarative navigation.\n\t\t * When set, the card renders as a <router-link> and clickable styles are implied.\n\t\t * { name: 'Cases', query: { status: 'open' } }\n\t\t * { path: '/catalogi' }\n\t\t */\n\t\troute: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\temits: ['click'],\n\n\tcomputed: {\n\t\t/**\n\t\t * Whether the app takes the board look.\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\tisBoardLook() {\n\t\t\treturn normalizeLook(this.cnLook) === 'board'\n\t\t},\n\n\t\thasIcon() {\n\t\t\treturn this.icon !== null || this.$slots.icon || this.$slots.icon\n\t\t},\n\n\t\t/**\n\t\t * Whether a consumer provided the `value` slot. When set, the value\n\t\t * area always renders (the slot decides what to show, even at count 0).\n\t\t */\n\t\thasValueSlot() {\n\t\t\treturn !!this.$slots.value || !!this.$slots.value\n\t\t},\n\n\t\t/**\n\t\t * Whether the tile is in its error state.\n\t\t *\n\t\t * Anything truthy counts, so a caller can pass the caught error itself\n\t\t * rather than converting it to a boolean first. An empty string is NOT\n\t\t * an error — that is a caller who cleared the message, not one\n\t\t * reporting a failure.\n\t\t *\n\t\t * @return {boolean} True when the tile should show `errorLabel`.\n\t\t */\n\t\thasError() {\n\t\t\treturn Boolean(this.error)\n\t\t},\n\n\t\tformattedCount() {\n\t\t\treturn this.count.toLocaleString(userLocale())\n\t\t},\n\n\t\t/**\n\t\t * Whether the card is interactive (clickable or has a route).\n\t\t * Used for applying hover/focus styles.\n\t\t */\n\t\tisInteractive() {\n\t\t\treturn !!this.route || this.clickable\n\t\t},\n\n\t\t/**\n\t\t * Determines which HTML element or component to render.\n\t\t * - route set → 'router-link' (SPA navigation)\n\t\t * - clickable → 'a' (app handles click via event)\n\t\t * - neither → 'div' (static display)\n\t\t */\n\t\tcomponentTag() {\n\t\t\tif (this.route) {\n\t\t\t\treturn 'router-link'\n\t\t\t}\n\t\t\tif (this.clickable) {\n\t\t\t\treturn 'a'\n\t\t\t}\n\t\t\treturn 'div'\n\t\t},\n\n\t\t/**\n\t\t * Dynamic attributes for the root element based on rendering mode.\n\t\t */\n\t\tcomponentAttrs() {\n\t\t\tif (this.route) {\n\t\t\t\treturn { to: this.route, tabindex: '0' }\n\t\t\t}\n\t\t\tif (this.clickable) {\n\t\t\t\treturn { href: '#', role: 'button', tabindex: '0' }\n\t\t\t}\n\t\t\treturn {}\n\t\t},\n\n\t\t/**\n\t\t * Whether the block takes the stacked board look (`layout`).\n\t\t *\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-a-stats-block-can-take-the-stacked-board-look\n\t\t * @return {boolean}\n\t\t */\n\t\tisStacked() {\n\t\t\treturn this.layout === 'stacked'\n\t\t},\n\n\t\trootClasses() {\n\t\t\treturn {\n\t\t\t\t// Canonical + legacy, in pairs: the canonical class carries the\n\t\t\t\t// look, the legacy one keeps existing app CSS matching.\n\t\t\t\t// The canonical card is already horizontal, so the legacy pair\n\t\t\t\t// is emitted for app CSS but carries no look of its own.\n\t\t\t\t'cn-kpi-card--horizontal': !this.isStacked && (this.horizontal || !this.vertical),\n\t\t\t\t'cn-kpi-card--stacked': this.isStacked,\n\t\t\t\t'cn-kpi-card--board': this.isBoardLook,\n\t\t\t\t'cn-stats-block--horizontal': this.horizontal || !this.vertical,\n\t\t\t\t'cn-kpi-card--vertical': this.vertical,\n\t\t\t\t'cn-stats-block--vertical': this.vertical,\n\t\t\t\t'cn-kpi-card--filled': this.filled,\n\t\t\t\t'cn-kpi-card--clickable': this.isInteractive,\n\t\t\t\t'cn-stats-block--clickable': this.isInteractive,\n\t\t\t\t'cn-kpi-card--error': this.hasError,\n\t\t\t\t[`cn-kpi-card--${this.variant}`]: this.variant !== 'default' && this.hasError === false,\n\t\t\t\t// An errored tile tints itself. Requiring the consumer to also\n\t\t\t\t// pass `variant=\"error\"` would mean every caller remembering\n\t\t\t\t// two props to express one state, and forgetting the second is\n\t\t\t\t// invisible — a tile that reads \"Unavailable\" in the default\n\t\t\t\t// colour looks like ordinary content.\n\t\t\t\t'cn-stats-block--error': this.hasError,\n\t\t\t\t[`cn-stats-block--${this.variant}`]: this.variant !== 'default' && this.hasError === false,\n\t\t\t}\n\t\t},\n\n\t\ticonClasses() {\n\t\t\treturn {\n\t\t\t\t[`cn-stats-block__icon--${this.variant}`]: this.variant !== 'default',\n\t\t\t}\n\t\t},\n\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * A manifest string through the host's label lookup (`cnTranslate`), so a key\n\t\t * such as \"Mine\" reads in the user's language.\n\t\t *\n\t\t * @param {string} text The text as written in the manifest.\n\t\t * @return {string} The translated text.\n\t\t */\n\t\ttl(text) {\n\t\t\treturn typeof this.cnTranslate === 'function' ? this.cnTranslate(text) : text\n\t\t},\n\n\t\tformatBreakdownLabel(key) {\n\t\t\treturn key.charAt(0).toUpperCase() + key.slice(1) + ':'\n\t\t},\n\n\t\tonClick(event) {\n\t\t\t// When route is set, router-link handles navigation — do not emit click\n\t\t\tif (this.route) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (this.clickable) {\n\t\t\t\tevent.preventDefault()\n\t\t\t\tthis.$emit('click', event)\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n/*\n * The card's own look — box, body stack, icon circle, title, value, variants,\n * hover — is the CANONICAL KPI card in src/css/kpi-card.css, shared with\n * CnStatWidget. The rules that lived here duplicated it in different numbers\n * (a 1.2rem count row, a 40px icon, hardcoded rgba() tints that ignored a\n * re-themed palette), and that duplication IS how the two KPI looks drifted.\n * What is left below is this component's own extras: the breakdown block, the\n * loading line and the empty line.\n *\n * Those extras are secondary KPI text, so they read the shared\n * `--cn-kpi-label-*` scale rather than carrying sizes of their own — an empty\n * state that is a different size from the label it replaces is the same drift\n * in miniature.\n */\n\n.cn-stats-block__header {\n\tmax-width: 100%;\n\tmin-width: 0;\n}\n\n.cn-stats-block__loading {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: flex-start;\n\tgap: 6px;\n\tfont-size: var(--cn-kpi-label-size, 13px);\n\tcolor: var(--cn-kpi-label-color, var(--color-text-maxcontrast));\n}\n\n.cn-stats-block__empty {\n\ttext-align: start;\n\tfont-size: var(--cn-kpi-label-size, 13px);\n\tcolor: var(--cn-kpi-label-color, var(--color-text-maxcontrast));\n\tfont-style: italic;\n}\n\n.cn-stats-block__breakdown {\n\tmargin-top: 4px;\n\tpadding: 8px;\n\tfont-size: var(--cn-kpi-label-size, 13px);\n\tbackground: var(--color-background-hover);\n\tborder-radius: var(--border-radius);\n\tborder: 1px solid var(--color-border);\n\twidth: 100%;\n\ttext-align: start;\n}\n\n.cn-stats-block__breakdown-item {\n\tdisplay: flex;\n\tjustify-content: space-between;\n\talign-items: center;\n\tgap: 8px;\n\tmargin-bottom: 4px;\n}\n\n.cn-stats-block__breakdown-item:last-child {\n\tmargin-bottom: 0;\n}\n\n.cn-stats-block__breakdown-label {\n\tfont-weight: 500;\n\tcolor: var(--color-main-text);\n}\n\n/* The value takes the card's accent, so a breakdown reads as part of the tile\n it sits in rather than as a table that happened to land there. It used to\n carry a `--color-background-hover` chip — on a `--color-background-hover`\n breakdown box, which is to say an invisible chip drawn at the cost of two\n extra declarations and a differently-shaped hit area. The per-key modifiers\n below still win: `invalid`, `deleted` and `published` mean something\n specific and outrank the tile's own colour. */\n.cn-stats-block__breakdown-value {\n\tfont-weight: var(--cn-kpi-title-weight, 600);\n\tcolor: var(--cn-kpi-accent, var(--color-primary-element));\n}\n\n.cn-stats-block__breakdown-value--invalid { color: var(--color-element-warning); }\n\n.cn-stats-block__breakdown-value--deleted { color: var(--color-element-error); }\n\n.cn-stats-block__breakdown-value--published { color: var(--color-element-success); }\n</style>\n"],"names":["_createElementBlock","_normalizeClass","_renderSlot","_openBlock","_createBlock","_resolveDynamicComponent","_createElementVNode","_toDisplayString","_createVNode","_createTextVNode"],"mappings":"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"}
@@ -2,7 +2,7 @@ import script from './CnSummaryAggregates.vue2.js';
2
2
  import { render } from './CnSummaryAggregates.vue3.js';
3
3
 
4
4
  script.render = render;
5
- script.__scopeId = "data-v-eda589ba";
5
+ script.__scopeId = "data-v-2ea9593e";
6
6
  const __sfc_main = script;
7
7
 
8
8
  export { __sfc_main as default };
@@ -1,5 +1,6 @@
1
1
  import { NcLoadingIcon } from '@nextcloud/vue';
2
2
  import { fetchAggregateValue } from '../../utils/fetchAggregate.js';
3
+ import { userLocale } from '../../utils/userLocale.js';
3
4
 
4
5
  /**
5
6
  * CnSummaryAggregates — declarative count/sum/avg stat chips for a
@@ -129,7 +130,7 @@ var script = {
129
130
  return new Intl.NumberFormat(undefined, { style: 'currency', currency: 'EUR' }).format(num)
130
131
  }
131
132
  if (typeof raw === 'number') {
132
- return raw.toLocaleString()
133
+ return raw.toLocaleString(userLocale())
133
134
  }
134
135
  return raw
135
136
  },
@@ -1 +1 @@
1
- {"version":3,"file":"CnSummaryAggregates.vue2.js","sources":["../../../../src/components/CnSummaryAggregates/CnSummaryAggregates.vue"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n<template>\n\t<div v-if=\"aggregates.length > 0\" class=\"cn-summary-aggregates\" data-testid=\"cn-summary-aggregates\">\n\t\t<div\n\t\t\tv-for=\"(agg, i) in resolvedChips\"\n\t\t\t:key=\"i\"\n\t\t\tclass=\"cn-summary-aggregates__chip\"\n\t\t\t:data-testid=\"`cn-summary-aggregate-${i}`\">\n\t\t\t<span class=\"cn-summary-aggregates__value\">\n\t\t\t\t<NcLoadingIcon v-if=\"agg.loading\" :size=\"16\" />\n\t\t\t\t<template v-else>{{ agg.display }}</template>\n\t\t\t</span>\n\t\t\t<span class=\"cn-summary-aggregates__label\">{{ agg.label }}</span>\n\t\t</div>\n\t</div>\n</template>\n\n<script>\nimport { NcLoadingIcon } from '@nextcloud/vue'\nimport { fetchAggregateValue } from '../../utils/fetchAggregate.js'\n\n/**\n * CnSummaryAggregates — declarative count/sum/avg stat chips for a\n * `type:\"detail\"` page header.\n *\n * Each entry runs ONE aggregation (via OpenRegister's ad-hoc `/value`\n * endpoint — the `runAdhocByRef` contract) over a related schema, scoped to\n * the current detail-page object through `@objectId` / `@object.<field>`\n * filter tokens, and shows the result as a small labelled chip. Lets a detail\n * page surface \"12 open cases / €4.2k outstanding\" without per-app code.\n *\n * Mounted by `CnDetailPage` when the manifest page config declares\n * `summaryAggregates`. Reads the detail page's `cnObjectContext` inject so the\n * filter tokens resolve against the live object.\n *\n * Example config (consumed by CnDetailPage):\n * ```js\n * summaryAggregates: [\n * { label: 'Open cases', register: 'pipelinq', schema: 'case', metric: 'count', filter: { client: '@objectId', status: 'open' } },\n * { label: 'Outstanding', register: 'pipelinq', schema: 'invoice', metric: 'sum', field: 'amount', filter: { client: '@objectId', paid: false }, format: 'currency' },\n * ]\n * ```\n */\nexport default {\n\tname: 'CnSummaryAggregates',\n\n\tcomponents: { NcLoadingIcon },\n\n\tinject: {\n\t\t/**\n\t\t * Detail-page object context (`{ objectId, object, register, schema }`)\n\t\t * provided by CnDetailPage. Resolves `@objectId` / `@object.<field>` tokens\n\t\t * in each aggregate's filter. Null on surfaces that don't provide one.\n\t\t */\n\t\tcnObjectContext: { default: null },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The aggregate descriptors.\n\t\t *\n\t\t * @type {Array<{label: string, register: string, schema: string, metric?: string, field?: string, filter?: object, format?: string}>}\n\t\t */\n\t\taggregates: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\t/** Per-aggregate fetched values, keyed by index. */\n\t\t\tvalues: {},\n\t\t\t/** Per-aggregate loading flags, keyed by index. */\n\t\t\tloadingMap: {},\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/** The unwrapped object context (or null). */\n\t\tobjectCtx() {\n\t\t\tconst c = this.cnObjectContext\n\t\t\tif (!c) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn (typeof c === 'object' && 'value' in c) ? c.value : c\n\t\t},\n\n\t\t/**\n\t\t * The chips with their fetched value formatted for display.\n\t\t *\n\t\t * @return {Array<{label: string, display: (string|number), loading: boolean}>}\n\t\t */\n\t\tresolvedChips() {\n\t\t\treturn this.aggregates.map((agg, i) => {\n\t\t\t\tconst raw = this.values[i]\n\t\t\t\treturn {\n\t\t\t\t\tlabel: agg.label || '',\n\t\t\t\t\tdisplay: this.formatChip(raw, agg.format),\n\t\t\t\t\tloading: !!this.loadingMap[i],\n\t\t\t\t}\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * A stable signature of the aggregate query set + the object id it is\n\t\t * scoped to, so the watcher only refetches on a real change.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tsourceKey() {\n\t\t\treturn JSON.stringify({\n\t\t\t\taggs: this.aggregates,\n\t\t\t\tobjectId: this.objectCtx ? this.objectCtx.objectId : null,\n\t\t\t})\n\t\t},\n\t},\n\n\twatch: {\n\t\tsourceKey: {\n\t\t\timmediate: true,\n\t\t\thandler() {\n\t\t\t\tthis.fetchAll()\n\t\t\t},\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Format a fetched value for the chip. Defaults to a thousands-grouped\n\t\t * number; `format: 'currency'` renders a EUR amount, `format: 'number'`\n\t\t * forces numeric grouping.\n\t\t *\n\t\t * @param {number|null} raw The fetched value.\n\t\t * @param {string} [format] Optional format key (`currency` | `number`).\n\t\t * @return {string|number}\n\t\t */\n\t\tformatChip(raw, format) {\n\t\t\tif (raw === null || raw === undefined) {\n\t\t\t\treturn '—'\n\t\t\t}\n\t\t\tif (format === 'currency') {\n\t\t\t\tconst num = Number(raw)\n\t\t\t\tif (Number.isNaN(num)) {\n\t\t\t\t\treturn String(raw)\n\t\t\t\t}\n\t\t\t\treturn new Intl.NumberFormat(undefined, { style: 'currency', currency: 'EUR' }).format(num)\n\t\t\t}\n\t\t\tif (typeof raw === 'number') {\n\t\t\t\treturn raw.toLocaleString()\n\t\t\t}\n\t\t\treturn raw\n\t\t},\n\n\t\t/**\n\t\t * Fetch every aggregate, scoping the filter tokens to the current object.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync fetchAll() {\n\t\t\tconst ctx = this.objectCtx || {}\n\t\t\tawait Promise.all(this.aggregates.map((agg, i) => this.fetchOne(agg, i, ctx)))\n\t\t},\n\n\t\t/**\n\t\t * Fetch one aggregate value into `values[i]`.\n\t\t *\n\t\t * @param {object} agg The aggregate descriptor.\n\t\t * @param {number} i The index in the list.\n\t\t * @param {object} ctx The object context for token resolution.\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync fetchOne(agg, i, ctx) {\n\t\t\tif (!agg.register || !agg.schema) {\n\t\t\t\tthis.values[i] = null\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loadingMap[i] = true\n\t\t\ttry {\n\t\t\t\tconst value = await fetchAggregateValue({\n\t\t\t\t\tregister: agg.register,\n\t\t\t\t\tschema: agg.schema,\n\t\t\t\t\tmetric: agg.metric || 'count',\n\t\t\t\t\tfield: agg.field,\n\t\t\t\t\tfilter: agg.filter || {},\n\t\t\t\t}, ctx)\n\t\t\t\tthis.values[i] = value\n\t\t\t} catch {\n\t\t\t\tthis.values[i] = null\n\t\t\t} finally {\n\t\t\t\tthis.loadingMap[i] = false\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-summary-aggregates {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tgap: 12px;\n\tmargin: 0 0 4px;\n}\n\n.cn-summary-aggregates__chip {\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: flex-start;\n\tpadding: 6px 12px;\n\tborder-radius: var(--border-radius-large, 12px);\n\tbackground: var(--color-background-hover);\n\tmin-width: 72px;\n}\n\n.cn-summary-aggregates__value {\n\tfont-size: 1.25em;\n\tfont-weight: 600;\n\tline-height: 1.2;\n\tcolor: var(--color-main-text);\n}\n\n.cn-summary-aggregates__label {\n\tfont-size: 0.8em;\n\tcolor: var(--color-text-maxcontrast);\n}\n</style>\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"CnSummaryAggregates.vue2.js","sources":["../../../../src/components/CnSummaryAggregates/CnSummaryAggregates.vue"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n<template>\n\t<div v-if=\"aggregates.length > 0\" class=\"cn-summary-aggregates\" data-testid=\"cn-summary-aggregates\">\n\t\t<div\n\t\t\tv-for=\"(agg, i) in resolvedChips\"\n\t\t\t:key=\"i\"\n\t\t\tclass=\"cn-summary-aggregates__chip\"\n\t\t\t:data-testid=\"`cn-summary-aggregate-${i}`\">\n\t\t\t<span class=\"cn-summary-aggregates__value\">\n\t\t\t\t<NcLoadingIcon v-if=\"agg.loading\" :size=\"16\" />\n\t\t\t\t<template v-else>{{ agg.display }}</template>\n\t\t\t</span>\n\t\t\t<span class=\"cn-summary-aggregates__label\">{{ agg.label }}</span>\n\t\t</div>\n\t</div>\n</template>\n\n<script>\nimport { NcLoadingIcon } from '@nextcloud/vue'\nimport { fetchAggregateValue } from '../../utils/fetchAggregate.js'\nimport { userLocale } from '../../utils/userLocale.js'\n\n/**\n * CnSummaryAggregates — declarative count/sum/avg stat chips for a\n * `type:\"detail\"` page header.\n *\n * Each entry runs ONE aggregation (via OpenRegister's ad-hoc `/value`\n * endpoint — the `runAdhocByRef` contract) over a related schema, scoped to\n * the current detail-page object through `@objectId` / `@object.<field>`\n * filter tokens, and shows the result as a small labelled chip. Lets a detail\n * page surface \"12 open cases / €4.2k outstanding\" without per-app code.\n *\n * Mounted by `CnDetailPage` when the manifest page config declares\n * `summaryAggregates`. Reads the detail page's `cnObjectContext` inject so the\n * filter tokens resolve against the live object.\n *\n * Example config (consumed by CnDetailPage):\n * ```js\n * summaryAggregates: [\n * { label: 'Open cases', register: 'pipelinq', schema: 'case', metric: 'count', filter: { client: '@objectId', status: 'open' } },\n * { label: 'Outstanding', register: 'pipelinq', schema: 'invoice', metric: 'sum', field: 'amount', filter: { client: '@objectId', paid: false }, format: 'currency' },\n * ]\n * ```\n */\nexport default {\n\tname: 'CnSummaryAggregates',\n\n\tcomponents: { NcLoadingIcon },\n\n\tinject: {\n\t\t/**\n\t\t * Detail-page object context (`{ objectId, object, register, schema }`)\n\t\t * provided by CnDetailPage. Resolves `@objectId` / `@object.<field>` tokens\n\t\t * in each aggregate's filter. Null on surfaces that don't provide one.\n\t\t */\n\t\tcnObjectContext: { default: null },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The aggregate descriptors.\n\t\t *\n\t\t * @type {Array<{label: string, register: string, schema: string, metric?: string, field?: string, filter?: object, format?: string}>}\n\t\t */\n\t\taggregates: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\t/** Per-aggregate fetched values, keyed by index. */\n\t\t\tvalues: {},\n\t\t\t/** Per-aggregate loading flags, keyed by index. */\n\t\t\tloadingMap: {},\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/** The unwrapped object context (or null). */\n\t\tobjectCtx() {\n\t\t\tconst c = this.cnObjectContext\n\t\t\tif (!c) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn (typeof c === 'object' && 'value' in c) ? c.value : c\n\t\t},\n\n\t\t/**\n\t\t * The chips with their fetched value formatted for display.\n\t\t *\n\t\t * @return {Array<{label: string, display: (string|number), loading: boolean}>}\n\t\t */\n\t\tresolvedChips() {\n\t\t\treturn this.aggregates.map((agg, i) => {\n\t\t\t\tconst raw = this.values[i]\n\t\t\t\treturn {\n\t\t\t\t\tlabel: agg.label || '',\n\t\t\t\t\tdisplay: this.formatChip(raw, agg.format),\n\t\t\t\t\tloading: !!this.loadingMap[i],\n\t\t\t\t}\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * A stable signature of the aggregate query set + the object id it is\n\t\t * scoped to, so the watcher only refetches on a real change.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tsourceKey() {\n\t\t\treturn JSON.stringify({\n\t\t\t\taggs: this.aggregates,\n\t\t\t\tobjectId: this.objectCtx ? this.objectCtx.objectId : null,\n\t\t\t})\n\t\t},\n\t},\n\n\twatch: {\n\t\tsourceKey: {\n\t\t\timmediate: true,\n\t\t\thandler() {\n\t\t\t\tthis.fetchAll()\n\t\t\t},\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Format a fetched value for the chip. Defaults to a thousands-grouped\n\t\t * number; `format: 'currency'` renders a EUR amount, `format: 'number'`\n\t\t * forces numeric grouping.\n\t\t *\n\t\t * @param {number|null} raw The fetched value.\n\t\t * @param {string} [format] Optional format key (`currency` | `number`).\n\t\t * @return {string|number}\n\t\t */\n\t\tformatChip(raw, format) {\n\t\t\tif (raw === null || raw === undefined) {\n\t\t\t\treturn '—'\n\t\t\t}\n\t\t\tif (format === 'currency') {\n\t\t\t\tconst num = Number(raw)\n\t\t\t\tif (Number.isNaN(num)) {\n\t\t\t\t\treturn String(raw)\n\t\t\t\t}\n\t\t\t\treturn new Intl.NumberFormat(undefined, { style: 'currency', currency: 'EUR' }).format(num)\n\t\t\t}\n\t\t\tif (typeof raw === 'number') {\n\t\t\t\treturn raw.toLocaleString(userLocale())\n\t\t\t}\n\t\t\treturn raw\n\t\t},\n\n\t\t/**\n\t\t * Fetch every aggregate, scoping the filter tokens to the current object.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync fetchAll() {\n\t\t\tconst ctx = this.objectCtx || {}\n\t\t\tawait Promise.all(this.aggregates.map((agg, i) => this.fetchOne(agg, i, ctx)))\n\t\t},\n\n\t\t/**\n\t\t * Fetch one aggregate value into `values[i]`.\n\t\t *\n\t\t * @param {object} agg The aggregate descriptor.\n\t\t * @param {number} i The index in the list.\n\t\t * @param {object} ctx The object context for token resolution.\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync fetchOne(agg, i, ctx) {\n\t\t\tif (!agg.register || !agg.schema) {\n\t\t\t\tthis.values[i] = null\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loadingMap[i] = true\n\t\t\ttry {\n\t\t\t\tconst value = await fetchAggregateValue({\n\t\t\t\t\tregister: agg.register,\n\t\t\t\t\tschema: agg.schema,\n\t\t\t\t\tmetric: agg.metric || 'count',\n\t\t\t\t\tfield: agg.field,\n\t\t\t\t\tfilter: agg.filter || {},\n\t\t\t\t}, ctx)\n\t\t\t\tthis.values[i] = value\n\t\t\t} catch {\n\t\t\t\tthis.values[i] = null\n\t\t\t} finally {\n\t\t\t\tthis.loadingMap[i] = false\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-summary-aggregates {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tgap: 12px;\n\tmargin: 0 0 4px;\n}\n\n.cn-summary-aggregates__chip {\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: flex-start;\n\tpadding: 6px 12px;\n\tborder-radius: var(--border-radius-large, 12px);\n\tbackground: var(--color-background-hover);\n\tmin-width: 72px;\n}\n\n.cn-summary-aggregates__value {\n\tfont-size: 1.25em;\n\tfont-weight: 600;\n\tline-height: 1.2;\n\tcolor: var(--color-main-text);\n}\n\n.cn-summary-aggregates__label {\n\tfont-size: 0.8em;\n\tcolor: var(--color-text-maxcontrast);\n}\n</style>\n"],"names":[],"mappings":"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"}