@conduction/nextcloud-vue 2.56.0-beta.3 → 2.56.0-beta.5

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 (314) hide show
  1. package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js +12 -2
  2. package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js.map +1 -1
  3. package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js +1 -2
  4. package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js.map +1 -1
  5. package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue2.js.map +1 -1
  6. package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue3.js +0 -3
  7. package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue3.js.map +1 -1
  8. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js.map +1 -1
  9. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js +1 -2
  10. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js.map +1 -1
  11. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +21 -1
  12. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
  13. package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js +1 -1
  14. package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
  15. package/dist/esm/components/CnBoardView/CnBoardView.vue2.js +12 -5
  16. package/dist/esm/components/CnBoardView/CnBoardView.vue2.js.map +1 -1
  17. package/dist/esm/components/CnBoardView/CnBoardView.vue3.js +2 -1
  18. package/dist/esm/components/CnBoardView/CnBoardView.vue3.js.map +1 -1
  19. package/dist/esm/components/CnCardGrid/CnCardGrid.vue2.js +9 -1
  20. package/dist/esm/components/CnCardGrid/CnCardGrid.vue2.js.map +1 -1
  21. package/dist/esm/components/CnCardGrid/CnCardGrid.vue3.js.map +1 -1
  22. package/dist/esm/components/CnContextMenu/CnContextMenu.vue2.js +6 -1
  23. package/dist/esm/components/CnContextMenu/CnContextMenu.vue2.js.map +1 -1
  24. package/dist/esm/components/CnContextMenu/CnContextMenu.vue3.js.map +1 -1
  25. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +116 -15
  26. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
  27. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +27 -14
  28. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
  29. package/dist/esm/components/CnDateAxisView/CnDateAxisView.vue2.js +13 -6
  30. package/dist/esm/components/CnDateAxisView/CnDateAxisView.vue2.js.map +1 -1
  31. package/dist/esm/components/CnDateAxisView/CnDateAxisView.vue3.js +2 -0
  32. package/dist/esm/components/CnDateAxisView/CnDateAxisView.vue3.js.map +1 -1
  33. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +29 -1
  34. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  35. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  36. package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue2.js.map +1 -1
  37. package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue3.js +1 -2
  38. package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue3.js.map +1 -1
  39. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +3 -1
  40. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
  41. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +17 -17
  42. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
  43. package/dist/esm/components/CnFilesWidget/CnFilesWidget.vue2.js +4 -21
  44. package/dist/esm/components/CnFilesWidget/CnFilesWidget.vue2.js.map +1 -1
  45. package/dist/esm/components/CnFilesWidget/CnFilesWidget.vue3.js.map +1 -1
  46. package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue2.js +0 -13
  47. package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue2.js.map +1 -1
  48. package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue3.js +8 -8
  49. package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue3.js.map +1 -1
  50. package/dist/esm/components/CnFlowsPage/CnFlowsPage.vue2.js.map +1 -1
  51. package/dist/esm/components/CnFlowsPage/CnFlowsPage.vue3.js +3 -2
  52. package/dist/esm/components/CnFlowsPage/CnFlowsPage.vue3.js.map +1 -1
  53. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +67 -13
  54. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  55. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +15 -8
  56. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  57. package/dist/esm/components/CnIndexPage/manifestActionDispatch.js +31 -11
  58. package/dist/esm/components/CnIndexPage/manifestActionDispatch.js.map +1 -1
  59. package/dist/esm/components/CnIntegrationWidget/CnIntegrationWidgetEmpty.vue2.js.map +1 -1
  60. package/dist/esm/components/CnIntegrationWidget/CnIntegrationWidgetEmpty.vue3.js +1 -2
  61. package/dist/esm/components/CnIntegrationWidget/CnIntegrationWidgetEmpty.vue3.js.map +1 -1
  62. package/dist/esm/components/CnInteractionFormWidget/CnInteractionFormWidget.vue2.js.map +1 -1
  63. package/dist/esm/components/CnInteractionFormWidget/CnInteractionFormWidget.vue3.js +0 -1
  64. package/dist/esm/components/CnInteractionFormWidget/CnInteractionFormWidget.vue3.js.map +1 -1
  65. package/dist/esm/components/CnLinkButtonWidget/CnLinkButtonWidget.vue2.js +2 -17
  66. package/dist/esm/components/CnLinkButtonWidget/CnLinkButtonWidget.vue2.js.map +1 -1
  67. package/dist/esm/components/CnLinkButtonWidget/CnLinkButtonWidget.vue3.js.map +1 -1
  68. package/dist/esm/components/CnLogsPage/CnLogsPage.vue2.js +35 -11
  69. package/dist/esm/components/CnLogsPage/CnLogsPage.vue2.js.map +1 -1
  70. package/dist/esm/components/CnLogsPage/CnLogsPage.vue3.js +3 -2
  71. package/dist/esm/components/CnLogsPage/CnLogsPage.vue3.js.map +1 -1
  72. package/dist/esm/components/CnObjectCard/CnObjectCard.vue2.js +23 -5
  73. package/dist/esm/components/CnObjectCard/CnObjectCard.vue2.js.map +1 -1
  74. package/dist/esm/components/CnObjectCard/CnObjectCard.vue3.js +1 -1
  75. package/dist/esm/components/CnObjectCard/CnObjectCard.vue3.js.map +1 -1
  76. package/dist/esm/components/CnObjectList/CnObjectList.vue2.js +19 -3
  77. package/dist/esm/components/CnObjectList/CnObjectList.vue2.js.map +1 -1
  78. package/dist/esm/components/CnObjectList/CnObjectList.vue3.js.map +1 -1
  79. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +45 -15
  80. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
  81. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js +4 -2
  82. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
  83. package/dist/esm/components/CnObjectRow/CnObjectRow.vue2.js +24 -6
  84. package/dist/esm/components/CnObjectRow/CnObjectRow.vue2.js.map +1 -1
  85. package/dist/esm/components/CnObjectRow/CnObjectRow.vue3.js +1 -1
  86. package/dist/esm/components/CnObjectRow/CnObjectRow.vue3.js.map +1 -1
  87. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
  88. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +0 -1
  89. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
  90. package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue2.js.map +1 -1
  91. package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue3.js +4 -2
  92. package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue3.js.map +1 -1
  93. package/dist/esm/components/CnRowActions/CnRowActions.vue2.js +7 -2
  94. package/dist/esm/components/CnRowActions/CnRowActions.vue2.js.map +1 -1
  95. package/dist/esm/components/CnRowActions/CnRowActions.vue3.js.map +1 -1
  96. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js +49 -5
  97. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js.map +1 -1
  98. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js +1 -1
  99. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js.map +1 -1
  100. package/dist/esm/components/CnSpendAnalyticsWidget/CnSpendAnalyticsWidget.vue2.js +5 -3
  101. package/dist/esm/components/CnSpendAnalyticsWidget/CnSpendAnalyticsWidget.vue2.js.map +1 -1
  102. package/dist/esm/components/CnSpendAnalyticsWidget/CnSpendAnalyticsWidget.vue3.js.map +1 -1
  103. package/dist/esm/components/CnTabs/CnTabs.vue2.js.map +1 -1
  104. package/dist/esm/components/CnTabs/CnTabs.vue3.js.map +1 -1
  105. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue2.js +3 -1
  106. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue2.js.map +1 -1
  107. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue3.js +1 -0
  108. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue3.js.map +1 -1
  109. package/dist/esm/integrations/builtin/analytics/CnAnalyticsCard.vue2.js +3 -1
  110. package/dist/esm/integrations/builtin/analytics/CnAnalyticsCard.vue2.js.map +1 -1
  111. package/dist/esm/integrations/builtin/analytics/CnAnalyticsCard.vue3.js.map +1 -1
  112. package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue2.js +3 -1
  113. package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue2.js.map +1 -1
  114. package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue3.js +2 -4
  115. package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue3.js.map +1 -1
  116. package/dist/esm/integrations/builtin/bookmarks/CnBookmarksCard.vue2.js +6 -0
  117. package/dist/esm/integrations/builtin/bookmarks/CnBookmarksCard.vue2.js.map +1 -1
  118. package/dist/esm/integrations/builtin/bookmarks/CnBookmarksCard.vue3.js +68 -53
  119. package/dist/esm/integrations/builtin/bookmarks/CnBookmarksCard.vue3.js.map +1 -1
  120. package/dist/esm/integrations/builtin/bookmarks/CnBookmarksTab.vue2.js +17 -0
  121. package/dist/esm/integrations/builtin/bookmarks/CnBookmarksTab.vue2.js.map +1 -1
  122. package/dist/esm/integrations/builtin/bookmarks/CnBookmarksTab.vue3.js +21 -19
  123. package/dist/esm/integrations/builtin/bookmarks/CnBookmarksTab.vue3.js.map +1 -1
  124. package/dist/esm/integrations/builtin/collectives/CnCollectivesCard.vue2.js +3 -1
  125. package/dist/esm/integrations/builtin/collectives/CnCollectivesCard.vue2.js.map +1 -1
  126. package/dist/esm/integrations/builtin/collectives/CnCollectivesCard.vue3.js.map +1 -1
  127. package/dist/esm/integrations/builtin/collectives/CnCollectivesTab.vue2.js +3 -1
  128. package/dist/esm/integrations/builtin/collectives/CnCollectivesTab.vue2.js.map +1 -1
  129. package/dist/esm/integrations/builtin/collectives/CnCollectivesTab.vue3.js +2 -4
  130. package/dist/esm/integrations/builtin/collectives/CnCollectivesTab.vue3.js.map +1 -1
  131. package/dist/esm/integrations/builtin/cospend/CnCospendCard.vue2.js +2 -1
  132. package/dist/esm/integrations/builtin/cospend/CnCospendCard.vue2.js.map +1 -1
  133. package/dist/esm/integrations/builtin/cospend/CnCospendCard.vue3.js.map +1 -1
  134. package/dist/esm/integrations/builtin/cospend/CnCospendTab.vue2.js +2 -1
  135. package/dist/esm/integrations/builtin/cospend/CnCospendTab.vue2.js.map +1 -1
  136. package/dist/esm/integrations/builtin/cospend/CnCospendTab.vue3.js +1 -2
  137. package/dist/esm/integrations/builtin/cospend/CnCospendTab.vue3.js.map +1 -1
  138. package/dist/esm/integrations/builtin/deck/CnDeckCard.vue2.js +2 -1
  139. package/dist/esm/integrations/builtin/deck/CnDeckCard.vue2.js.map +1 -1
  140. package/dist/esm/integrations/builtin/deck/CnDeckCard.vue3.js.map +1 -1
  141. package/dist/esm/integrations/builtin/deck/CnDeckTab.vue2.js +2 -1
  142. package/dist/esm/integrations/builtin/deck/CnDeckTab.vue2.js.map +1 -1
  143. package/dist/esm/integrations/builtin/deck/CnDeckTab.vue3.js +1 -2
  144. package/dist/esm/integrations/builtin/deck/CnDeckTab.vue3.js.map +1 -1
  145. package/dist/esm/integrations/builtin/email/CnEmailCard.vue2.js.map +1 -1
  146. package/dist/esm/integrations/builtin/email/CnEmailCard.vue3.js.map +1 -1
  147. package/dist/esm/integrations/builtin/email/CnEmailTab.vue2.js.map +1 -1
  148. package/dist/esm/integrations/builtin/email/CnEmailTab.vue3.js +1 -2
  149. package/dist/esm/integrations/builtin/email/CnEmailTab.vue3.js.map +1 -1
  150. package/dist/esm/integrations/builtin/flow/CnFlowCard.vue2.js +2 -1
  151. package/dist/esm/integrations/builtin/flow/CnFlowCard.vue2.js.map +1 -1
  152. package/dist/esm/integrations/builtin/flow/CnFlowCard.vue3.js.map +1 -1
  153. package/dist/esm/integrations/builtin/flow/CnFlowTab.vue2.js +2 -1
  154. package/dist/esm/integrations/builtin/flow/CnFlowTab.vue2.js.map +1 -1
  155. package/dist/esm/integrations/builtin/flow/CnFlowTab.vue3.js +1 -2
  156. package/dist/esm/integrations/builtin/flow/CnFlowTab.vue3.js.map +1 -1
  157. package/dist/esm/integrations/builtin/forms/CnFormsCard.vue2.js +3 -1
  158. package/dist/esm/integrations/builtin/forms/CnFormsCard.vue2.js.map +1 -1
  159. package/dist/esm/integrations/builtin/forms/CnFormsCard.vue3.js.map +1 -1
  160. package/dist/esm/integrations/builtin/forms/CnFormsTab.vue2.js +3 -1
  161. package/dist/esm/integrations/builtin/forms/CnFormsTab.vue2.js.map +1 -1
  162. package/dist/esm/integrations/builtin/forms/CnFormsTab.vue3.js +2 -4
  163. package/dist/esm/integrations/builtin/forms/CnFormsTab.vue3.js.map +1 -1
  164. package/dist/esm/integrations/builtin/maps/CnMapsCard.vue2.js +3 -1
  165. package/dist/esm/integrations/builtin/maps/CnMapsCard.vue2.js.map +1 -1
  166. package/dist/esm/integrations/builtin/maps/CnMapsCard.vue3.js.map +1 -1
  167. package/dist/esm/integrations/builtin/maps/CnMapsTab.vue2.js +3 -1
  168. package/dist/esm/integrations/builtin/maps/CnMapsTab.vue2.js.map +1 -1
  169. package/dist/esm/integrations/builtin/maps/CnMapsTab.vue3.js +2 -4
  170. package/dist/esm/integrations/builtin/maps/CnMapsTab.vue3.js.map +1 -1
  171. package/dist/esm/integrations/builtin/openproject/CnOpenprojectCard.vue2.js +3 -1
  172. package/dist/esm/integrations/builtin/openproject/CnOpenprojectCard.vue2.js.map +1 -1
  173. package/dist/esm/integrations/builtin/openproject/CnOpenprojectCard.vue3.js.map +1 -1
  174. package/dist/esm/integrations/builtin/openproject/CnOpenprojectTab.vue2.js +3 -1
  175. package/dist/esm/integrations/builtin/openproject/CnOpenprojectTab.vue2.js.map +1 -1
  176. package/dist/esm/integrations/builtin/openproject/CnOpenprojectTab.vue3.js +2 -4
  177. package/dist/esm/integrations/builtin/openproject/CnOpenprojectTab.vue3.js.map +1 -1
  178. package/dist/esm/integrations/builtin/photos/CnPhotosCard.vue2.js +2 -1
  179. package/dist/esm/integrations/builtin/photos/CnPhotosCard.vue2.js.map +1 -1
  180. package/dist/esm/integrations/builtin/photos/CnPhotosCard.vue3.js.map +1 -1
  181. package/dist/esm/integrations/builtin/photos/CnPhotosTab.vue2.js +2 -1
  182. package/dist/esm/integrations/builtin/photos/CnPhotosTab.vue2.js.map +1 -1
  183. package/dist/esm/integrations/builtin/photos/CnPhotosTab.vue3.js +1 -2
  184. package/dist/esm/integrations/builtin/photos/CnPhotosTab.vue3.js.map +1 -1
  185. package/dist/esm/integrations/builtin/polls/CnPollsCard.vue2.js +2 -1
  186. package/dist/esm/integrations/builtin/polls/CnPollsCard.vue2.js.map +1 -1
  187. package/dist/esm/integrations/builtin/polls/CnPollsCard.vue3.js.map +1 -1
  188. package/dist/esm/integrations/builtin/polls/CnPollsTab.vue2.js +2 -1
  189. package/dist/esm/integrations/builtin/polls/CnPollsTab.vue2.js.map +1 -1
  190. package/dist/esm/integrations/builtin/polls/CnPollsTab.vue3.js +1 -2
  191. package/dist/esm/integrations/builtin/polls/CnPollsTab.vue3.js.map +1 -1
  192. package/dist/esm/integrations/builtin/shares/CnSharesTab.vue2.js.map +1 -1
  193. package/dist/esm/integrations/builtin/shares/CnSharesTab.vue3.js +3 -6
  194. package/dist/esm/integrations/builtin/shares/CnSharesTab.vue3.js.map +1 -1
  195. package/dist/esm/integrations/builtin/talk/CnTalkCard.vue2.js +3 -1
  196. package/dist/esm/integrations/builtin/talk/CnTalkCard.vue2.js.map +1 -1
  197. package/dist/esm/integrations/builtin/talk/CnTalkCard.vue3.js.map +1 -1
  198. package/dist/esm/integrations/builtin/talk/CnTalkTab.vue2.js +3 -1
  199. package/dist/esm/integrations/builtin/talk/CnTalkTab.vue2.js.map +1 -1
  200. package/dist/esm/integrations/builtin/talk/CnTalkTab.vue3.js +2 -4
  201. package/dist/esm/integrations/builtin/talk/CnTalkTab.vue3.js.map +1 -1
  202. package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerCard.vue2.js +2 -1
  203. package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerCard.vue2.js.map +1 -1
  204. package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerCard.vue3.js.map +1 -1
  205. package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerTab.vue2.js +2 -1
  206. package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerTab.vue2.js.map +1 -1
  207. package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerTab.vue3.js +1 -2
  208. package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerTab.vue3.js.map +1 -1
  209. package/dist/esm/integrations/builtin/xwiki/CnXwikiCard.vue2.js +4 -1
  210. package/dist/esm/integrations/builtin/xwiki/CnXwikiCard.vue2.js.map +1 -1
  211. package/dist/esm/integrations/builtin/xwiki/CnXwikiCard.vue3.js.map +1 -1
  212. package/dist/esm/integrations/builtin/xwiki/CnXwikiTab.vue2.js +6 -3
  213. package/dist/esm/integrations/builtin/xwiki/CnXwikiTab.vue2.js.map +1 -1
  214. package/dist/esm/integrations/builtin/xwiki/CnXwikiTab.vue3.js +2 -4
  215. package/dist/esm/integrations/builtin/xwiki/CnXwikiTab.vue3.js.map +1 -1
  216. package/dist/esm/l10n/en.json.js +2 -0
  217. package/dist/esm/l10n/en.json.js.map +1 -1
  218. package/dist/esm/l10n/nl.json.js +2 -0
  219. package/dist/esm/l10n/nl.json.js.map +1 -1
  220. package/dist/esm/nextcloud-vue.css +78 -7
  221. package/dist/esm/utils/actionLink.js +5 -0
  222. package/dist/esm/utils/actionLink.js.map +1 -1
  223. package/dist/esm/utils/actionsDispatcher.js +16 -1
  224. package/dist/esm/utils/actionsDispatcher.js.map +1 -1
  225. package/dist/esm/utils/linkNavigation.js +6 -3
  226. package/dist/esm/utils/linkNavigation.js.map +1 -1
  227. package/dist/esm/utils/rowAuxClick.js +30 -5
  228. package/dist/esm/utils/rowAuxClick.js.map +1 -1
  229. package/dist/esm/utils/safeHref.js +18 -3
  230. package/dist/esm/utils/safeHref.js.map +1 -1
  231. package/dist/esm/utils/validateManifestV2.compiled.js +2 -2
  232. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  233. package/dist/nextcloud-vue.cjs.js +885 -393
  234. package/dist/nextcloud-vue.cjs.js.map +2 -2
  235. package/dist/nextcloud-vue.css +78 -7
  236. package/l10n/en.json +2 -0
  237. package/l10n/nl.json +2 -0
  238. package/package.json +1 -1
  239. package/src/components/CnActionsBar/CnActionsBar.vue +14 -5
  240. package/src/components/CnActionsMenu/CnActionsMenu.vue +0 -3
  241. package/src/components/CnAdminSettingsShell/CnAdminSettingsShell.vue +1 -2
  242. package/src/components/CnAppRoot/CnAppRoot.vue +31 -2
  243. package/src/components/CnBoardView/CnBoardView.vue +13 -5
  244. package/src/components/CnCardGrid/CnCardGrid.vue +9 -1
  245. package/src/components/CnContextMenu/CnContextMenu.vue +6 -1
  246. package/src/components/CnDataTable/CnDataTable.md +1 -1
  247. package/src/components/CnDataTable/CnDataTable.vue +131 -17
  248. package/src/components/CnDateAxisView/CnDateAxisView.vue +15 -6
  249. package/src/components/CnDetailPage/CnDetailPage.md +1 -1
  250. package/src/components/CnDetailPage/CnDetailPage.vue +29 -1
  251. package/src/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue +1 -2
  252. package/src/components/CnFilesBrowser/CnFilesBrowser.vue +5 -3
  253. package/src/components/CnFilesWidget/CnFilesWidget.vue +4 -21
  254. package/src/components/CnFlowRunsWidget/CnFlowRunsWidget.vue +8 -21
  255. package/src/components/CnFlowsPage/CnFlowsPage.vue +2 -1
  256. package/src/components/CnIndexPage/CnIndexPage.vue +75 -14
  257. package/src/components/CnIndexPage/manifestActionDispatch.js +31 -11
  258. package/src/components/CnIntegrationWidget/CnIntegrationWidgetEmpty.vue +1 -2
  259. package/src/components/CnInteractionFormWidget/CnInteractionFormWidget.vue +0 -1
  260. package/src/components/CnLinkButtonWidget/CnLinkButtonWidget.vue +2 -17
  261. package/src/components/CnLogsPage/CnLogsPage.vue +37 -12
  262. package/src/components/CnObjectCard/CnObjectCard.vue +24 -6
  263. package/src/components/CnObjectList/CnObjectList.vue +19 -3
  264. package/src/components/CnObjectListWidget/CnObjectListWidget.vue +47 -15
  265. package/src/components/CnObjectRow/CnObjectRow.vue +25 -7
  266. package/src/components/CnObjectSidebar/CnFilesTab.vue +0 -1
  267. package/src/components/CnPageRenderer/CnPageRenderer.vue +2 -0
  268. package/src/components/CnRowActions/CnRowActions.vue +7 -2
  269. package/src/components/CnSetupWizard/CnSetupWizard.vue +57 -6
  270. package/src/components/CnSpendAnalyticsWidget/CnSpendAnalyticsWidget.vue +5 -3
  271. package/src/components/CnTabs/CnTabs.vue +7 -3
  272. package/src/components/CnTasksWidget/CnTasksWidget.vue +4 -1
  273. package/src/css/table.css +26 -0
  274. package/src/integrations/builtin/analytics/CnAnalyticsCard.vue +3 -2
  275. package/src/integrations/builtin/analytics/CnAnalyticsTab.vue +5 -6
  276. package/src/integrations/builtin/bookmarks/CnBookmarksCard.vue +12 -1
  277. package/src/integrations/builtin/bookmarks/CnBookmarksTab.vue +23 -6
  278. package/src/integrations/builtin/bookmarks/__tests__/CnBookmarksTab.spec.js +7 -2
  279. package/src/integrations/builtin/collectives/CnCollectivesCard.vue +3 -2
  280. package/src/integrations/builtin/collectives/CnCollectivesTab.vue +5 -6
  281. package/src/integrations/builtin/cospend/CnCospendCard.vue +2 -2
  282. package/src/integrations/builtin/cospend/CnCospendTab.vue +3 -4
  283. package/src/integrations/builtin/deck/CnDeckCard.vue +2 -2
  284. package/src/integrations/builtin/deck/CnDeckTab.vue +3 -4
  285. package/src/integrations/builtin/email/CnEmailCard.vue +6 -2
  286. package/src/integrations/builtin/email/CnEmailTab.vue +7 -4
  287. package/src/integrations/builtin/flow/CnFlowCard.vue +2 -2
  288. package/src/integrations/builtin/flow/CnFlowTab.vue +3 -4
  289. package/src/integrations/builtin/forms/CnFormsCard.vue +3 -2
  290. package/src/integrations/builtin/forms/CnFormsTab.vue +5 -6
  291. package/src/integrations/builtin/maps/CnMapsCard.vue +3 -2
  292. package/src/integrations/builtin/maps/CnMapsTab.vue +5 -6
  293. package/src/integrations/builtin/maps/__tests__/CnMapsTab.spec.js +9 -0
  294. package/src/integrations/builtin/openproject/CnOpenprojectCard.vue +3 -2
  295. package/src/integrations/builtin/openproject/CnOpenprojectTab.vue +5 -6
  296. package/src/integrations/builtin/photos/CnPhotosCard.vue +2 -2
  297. package/src/integrations/builtin/photos/CnPhotosTab.vue +3 -4
  298. package/src/integrations/builtin/polls/CnPollsCard.vue +2 -2
  299. package/src/integrations/builtin/polls/CnPollsTab.vue +3 -4
  300. package/src/integrations/builtin/shares/CnSharesTab.vue +3 -6
  301. package/src/integrations/builtin/talk/CnTalkCard.vue +3 -2
  302. package/src/integrations/builtin/talk/CnTalkTab.vue +5 -6
  303. package/src/integrations/builtin/time-tracker/CnTimeTrackerCard.vue +2 -2
  304. package/src/integrations/builtin/time-tracker/CnTimeTrackerTab.vue +3 -4
  305. package/src/integrations/builtin/xwiki/CnXwikiCard.vue +4 -2
  306. package/src/integrations/builtin/xwiki/CnXwikiTab.vue +7 -7
  307. package/src/integrations/builtin/xwiki/__tests__/CnXwikiTab.spec.js +9 -0
  308. package/src/schemas/app-manifest-v2.schema.json +2 -1
  309. package/src/utils/actionLink.js +5 -0
  310. package/src/utils/actionsDispatcher.js +15 -0
  311. package/src/utils/linkNavigation.js +6 -3
  312. package/src/utils/rowAuxClick.js +29 -4
  313. package/src/utils/safeHref.js +18 -3
  314. package/src/utils/validateManifestV2.compiled.js +2 -2
@@ -90,7 +90,8 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
90
90
  sortKeys: $options.effectiveSortKeys,
91
91
  emptyText: $props.emptyText,
92
92
  onSort: $options.onSort,
93
- onRowClick: $options.onRowClick
93
+ onRowClick: $options.onRowClick,
94
+ onRowAuxClick: $options.onRowAuxClick
94
95
  }, createSlots({ _: 2 /* DYNAMIC */ }, [
95
96
  renderList($options.slotColumns, (col) => {
96
97
  return {
@@ -112,7 +113,7 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
112
113
  key: "0"
113
114
  }
114
115
  : undefined
115
- ]), 1032 /* PROPS, DYNAMIC_SLOTS */, ["schema", "columns", "rows", "rowKey", "fixedLayout", "sortKey", "sortOrder", "sortKeys", "emptyText", "onSort", "onRowClick"]),
116
+ ]), 1032 /* PROPS, DYNAMIC_SLOTS */, ["schema", "columns", "rows", "rowKey", "fixedLayout", "sortKey", "sortOrder", "sortKeys", "emptyText", "onSort", "onRowClick", "onRowAuxClick"]),
116
117
  ($options.paginationState && $options.paginationState.pages > 1)
117
118
  ? (openBlock(), createBlock(_component_CnPagination, {
118
119
  key: 0,
@@ -1 +1 @@
1
- {"version":3,"file":"CnLogsPage.vue3.js","sources":["../../../../src/components/CnLogsPage/CnLogsPage.vue?vue&type=template&id=18d3ce6f&lang.js"],"sourcesContent":["<!--\n CnLogsPage — Read-only audit-trail / activity-log surface.\n\n Wraps CnDataTable with server-side sorting, pagination and filtering.\n Reads its data source from `config`:\n - { register, schema } → fetched via the OpenRegister object store\n - { source: '/api/url' } → fetched via axios.get(source)\n\n In store mode the list state (params, paging, sort, filters) is owned by\n the shared `useListView` composable, so a `?jobId=<uuid>`-style deep link\n lands the page pre-filtered — the same behaviour CnIndexPage has.\n\n Mounted by CnPageRenderer when a manifest page declares\n `type: \"logs\"`. Honours `headerComponent`, `actionsComponent`, and\n the generic `slots` map from `pages[]` (forwarded by the renderer\n through scoped slots; this component just exposes `#header`,\n `#actions`, and `#row-actions` slots which the renderer fills).\n\n Backwards-compat: when neither `register+schema` nor `source` is\n set, the component renders an empty-state with a console warning\n rather than throwing — this keeps a misconfigured manifest from\n breaking the app shell.\n-->\n<template>\n\t<div class=\"cn-logs-page\" data-testid=\"cn-logs-page\">\n\t\t<!-- @slot Replaces the default CnPageHeader. CnPageRenderer fills it from `pages[].headerComponent` / `slots.header`. -->\n\t\t<!-- @binding {string} title The page title. -->\n\t\t<!-- @binding {string} description The page description. -->\n\t\t<!-- @binding {string} icon The header's MDI icon name. -->\n\t\t<slot\n\t\t\tname=\"header\"\n\t\t\t:title=\"title\"\n\t\t\t:description=\"description\"\n\t\t\t:icon=\"icon\">\n\t\t\t<CnPageHeader\n\t\t\t\tv-if=\"title\"\n\t\t\t\t:title=\"title\"\n\t\t\t\t:description=\"description\"\n\t\t\t\t:icon=\"icon\"\n\t\t\t\t:visuallyHidden=\"!showTitle\" />\n\t\t</slot>\n\n\t\t<div v-if=\"$slots.actions\" class=\"cn-logs-page__actions\">\n\t\t\t<!-- @slot Right-aligned action area (refresh, export, …). CnPageRenderer fills it from `pages[].actionsComponent`. -->\n\t\t\t<slot name=\"actions\" />\n\t\t</div>\n\n\t\t<!-- Body -->\n\t\t<div class=\"cn-logs-page__body\">\n\t\t\t<!-- Loading state -->\n\t\t\t<div v-if=\"loading\" class=\"cn-logs-page__loading\">\n\t\t\t\t<NcLoadingIcon :size=\"32\" />\n\t\t\t</div>\n\n\t\t\t<!-- The error block below is a SIBLING of this chain, not part of it, so\n\t\t\t BOTH guards here are load-bearing and neither is sufficient alone.\n\t\t\t A failed fetch leaves the collection empty, so:\n\t\t\t - without `&& !error`, this branch renders \"no log entries\"\n\t\t\t stacked above \"could not load log entries\";\n\t\t\t - without the `rows.length > 0` guard on the table branch, the\n\t\t\t `v-else` catches that same state instead and mounts CnDataTable\n\t\t\t with zero rows, which renders its OWN empty row from the same\n\t\t\t `emptyText` — the identical contradiction, just inside a table.\n\t\t\t So: error + no rows shows the error alone; no error + no rows shows\n\t\t\t this block; rows present shows the table, with the error beneath it\n\t\t\t when a refresh failed over a still-populated collection. -->\n\t\t\t<div v-else-if=\"rows.length === 0 && !error\" class=\"cn-logs-page__empty\">\n\t\t\t\t<!-- @slot Replaces the empty-state block shown when there are no log entries. -->\n\t\t\t\t<slot name=\"empty\">\n\t\t\t\t\t<NcEmptyContent :name=\"emptyText\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<HistoryIcon :size=\"64\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t</NcEmptyContent>\n\t\t\t\t</slot>\n\t\t\t</div>\n\n\t\t\t<!-- Table. `v-else-if=\"rows.length > 0\"` rather than a bare `v-else` —\n\t\t\t see the guard note above. -->\n\t\t\t<template v-else-if=\"rows.length > 0\">\n\t\t\t\t<CnDataTable\n\t\t\t\t\t:schema=\"tableSchema\"\n\t\t\t\t\t:columns=\"resolvedColumns\"\n\t\t\t\t\t:rows=\"rows\"\n\t\t\t\t\t:rowKey=\"rowKey\"\n\t\t\t\t\t:fixedLayout=\"fixedLayout\"\n\t\t\t\t\t:sortKey=\"effectiveSortKey\"\n\t\t\t\t\t:sortOrder=\"effectiveSortOrder\"\n\t\t\t\t\t:sortKeys=\"effectiveSortKeys\"\n\t\t\t\t\t:emptyText=\"emptyText\"\n\t\t\t\t\t@sort=\"onSort\"\n\t\t\t\t\t@rowClick=\"onRowClick\">\n\t\t\t\t\t<template\n\t\t\t\t\t\tv-for=\"col in slotColumns\"\n\t\t\t\t\t\t#[`column-${col}`]=\"{ row, value }\">\n\t\t\t\t\t\t<!-- @slot Per-column cell renderer (`#column-<key>`), one per configured column key. -->\n\t\t\t\t\t\t<!-- @binding {object} row The log entry being rendered. -->\n\t\t\t\t\t\t<!-- @binding {*} value The cell's resolved value. -->\n\t\t\t\t\t\t<slot :name=\"'column-' + col\" :row=\"row\" :value=\"value\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t<template v-if=\"$slots['row-actions']\" #row-actions=\"{ row }\">\n\t\t\t\t\t\t<!-- @slot Per-row action menu cell. Supplying it adds the trailing actions column. -->\n\t\t\t\t\t\t<!-- @binding {object} row The log entry for this row. -->\n\t\t\t\t\t\t<slot name=\"row-actions\" :row=\"row\" />\n\t\t\t\t\t</template>\n\t\t\t\t</CnDataTable>\n\n\t\t\t\t<CnPagination\n\t\t\t\t\tv-if=\"paginationState && paginationState.pages > 1\"\n\t\t\t\t\tclass=\"cn-logs-page__pagination\"\n\t\t\t\t\t:currentPage=\"paginationState.page || 1\"\n\t\t\t\t\t:totalPages=\"paginationState.pages || 1\"\n\t\t\t\t\t:totalItems=\"paginationState.total || 0\"\n\t\t\t\t\t:currentPageSize=\"paginationState.limit || 20\"\n\t\t\t\t\t@pageChanged=\"onPageChange\"\n\t\t\t\t\t@pageSizeChanged=\"onPageSizeChange\" />\n\t\t\t</template>\n\n\t\t\t<div v-if=\"error\" class=\"cn-logs-page__error\">\n\t\t\t\t<!-- @slot Replaces the error block shown when the fetch fails. -->\n\t\t\t\t<!-- @binding {*} error The recorded fetch error. -->\n\t\t\t\t<slot name=\"error\" :error=\"error\">\n\t\t\t\t\t<NcEmptyContent :name=\"errorText\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<AlertCircleOutline :size=\"64\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t</NcEmptyContent>\n\t\t\t\t</slot>\n\t\t\t</div>\n\t\t</div>\n\n\t\t<!-- Row-detail dialog (opt-in via `rowDetail`). A log row's interesting\n\t\t payload is usually a nested object (a stack trace, an argument bag)\n\t\t that a table cell can only summarise. -->\n\t\t<NcDialog\n\t\t\tv-if=\"detailRow\"\n\t\t\t:open=\"true\"\n\t\t\t:name=\"detailTitle\"\n\t\t\tsize=\"large\"\n\t\t\t@update:open=\"closeDetail\">\n\t\t\t<!-- @slot Replaces the built-in row-detail dialog body. -->\n\t\t\t<!-- @binding {object} row The clicked log row. -->\n\t\t\t<slot name=\"row-detail\" :row=\"detailRow\">\n\t\t\t\t<div class=\"cn-logs-page__detail\">\n\t\t\t\t\t<CnDetailGrid\n\t\t\t\t\t\tv-if=\"detailScalarItems.length > 0\"\n\t\t\t\t\t\tlayout=\"horizontal\"\n\t\t\t\t\t\t:items=\"detailScalarItems\" />\n\t\t\t\t\t<template v-for=\"block in detailObjectBlocks\" :key=\"block.key\">\n\t\t\t\t\t\t<h4 class=\"cn-logs-page__detail-heading\">\n\t\t\t\t\t\t\t{{ block.label }}\n\t\t\t\t\t\t</h4>\n\t\t\t\t\t\t<CnDetailGrid\n\t\t\t\t\t\t\tv-if=\"block.items\"\n\t\t\t\t\t\t\tlayout=\"horizontal\"\n\t\t\t\t\t\t\t:items=\"block.items\" />\n\t\t\t\t\t\t<pre v-else class=\"cn-logs-page__detail-json\">{{ block.json }}</pre>\n\t\t\t\t\t</template>\n\t\t\t\t</div>\n\t\t\t</slot>\n\t\t\t<template #actions>\n\t\t\t\t<NcButton @click=\"closeDetail\">\n\t\t\t\t\t{{ closeLabel }}\n\t\t\t\t</NcButton>\n\t\t\t</template>\n\t\t</NcDialog>\n\t</div>\n</template>\n\n<script>\nimport axios from '@nextcloud/axios'\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcButton, NcDialog, NcEmptyContent, NcLoadingIcon } from '@nextcloud/vue'\nimport { getCurrentInstance, inject } from 'vue'\nimport AlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue'\nimport HistoryIcon from 'vue-material-design-icons/History.vue'\nimport { useListView } from '../../composables/index.js'\nimport { useObjectStore } from '../../store/index.js'\nimport { prefixUrl } from '../../utils/headers.js'\nimport { openRowTarget } from '../../utils/linkNavigation.js'\nimport { multiKeySort } from '../../utils/multiKeySort.js'\nimport { parseSortKeysFromQuery, resolveFilterMap, resolveQueryFilters } from '../../utils/routeFilters.js'\nimport { markNewTabHandled } from '../../utils/rowAuxClick.js'\nimport { CnDataTable } from '../CnDataTable/index.js'\nimport { CnDetailGrid } from '../CnDetailGrid/index.js'\nimport { CnPageHeader } from '../CnPageHeader/index.js'\nimport { CnPagination } from '../CnPagination/index.js'\n\n/**\n * Legacy default columns. Retained ONLY for `source` mode and for a store\n * mode whose schema could not be loaded — see `resolvedColumns`. They match\n * no OpenRegister log schema, so a store-backed page derives its columns\n * from the schema instead.\n *\n * A function, not a constant: `t()` at module scope would resolve before the\n * consuming app calls `registerTranslations()`, pinning the labels to English.\n *\n * @return {Array<{key: string, label: string, sortable?: boolean}>} The legacy column set.\n */\nfunction legacyDefaultColumns() {\n\treturn [\n\t\t{ key: 'timestamp', label: t('nextcloud-vue', 'Timestamp'), sortable: true },\n\t\t{ key: 'actor', label: t('nextcloud-vue', 'Actor'), sortable: true },\n\t\t{ key: 'action', label: t('nextcloud-vue', 'Action'), sortable: true },\n\t\t{ key: 'target', label: t('nextcloud-vue', 'Target') },\n\t\t{ key: 'details', label: t('nextcloud-vue', 'Details') },\n\t]\n}\n\n/**\n * CnLogsPage — Read-only audit-trail / activity-log page.\n *\n * Renders a CnDataTable of log entries with server-side sorting,\n * pagination and filtering. Columns come from `columns` (manifest:\n * `pages[].config.columns`); when none are given, a store-backed page\n * derives them from the schema.\n *\n * Two data-fetch modes:\n * - `register` + `schema`: fetched via `useObjectStore` as a regular\n * OpenRegister collection. The store registers the type as\n * `${register}-${schema}` per existing convention. List state is\n * owned by `useListView`, so `_limit` / `_page` / `_order` and any\n * `$route.query` / `filter` entries are sent with every request.\n * - `source`: a custom URL fetched via `axios.get(source)`. The\n * response shape may be either `{ results: [...] }` or a bare\n * array; the component handles both. This escape hatch supports\n * apps whose log surface is not OR-backed (e.g. a flat file).\n * Sorting is applied client-side; there is no pagination.\n *\n * Filtering (store mode) merges two sources, `filter` winning a collision:\n * 1. `$route.query` — every non-`_`-prefixed entry becomes a filter, so\n * `/jobs/logs?jobId=<uuid>` lands the page scoped to one job.\n * 2. `filter` — the manifest's own scoping, with `@route.<param>` /\n * `:<param>` / `@me` / `@today±Nd` tokens resolved at fetch time.\n *\n * The merged result is watched, so anything that moves it — a same-path query\n * change, a route param, the `filter` prop, or the workspace / app-config bag a\n * token in it reads — refetches from page 1, and nothing else does.\n *\n * Slots:\n * - `#header` — Replaces the default CnPageHeader.\n * - `#actions` — Renders a right-aligned action area (refresh,\n * export, etc.). The CnPageRenderer wires this from\n * `pages[].actionsComponent` when present.\n * - `#empty` — Replaces the empty-state block.\n * - `#error` — Replaces the error block. Scope: `{ error }`.\n * - `#row-actions` — Per-row action menu in the table. Scope:\n * `{ row }`.\n * - `#row-detail` — Replaces the row-detail dialog body. Scope: `{ row }`.\n * - `#column-<key>` — Per-column custom cell renderer. Scope:\n * `{ row, value }`.\n *\n * Props are intentionally permissive — every default has a sensible\n * fallback, so a manifest entry as small as\n * `{ type: \"logs\", config: { source: \"/api/logs\" } }` renders.\n */\nexport default {\n\tname: 'CnLogsPage',\n\n\tcomponents: {\n\t\tNcButton,\n\t\tNcDialog,\n\t\tNcEmptyContent,\n\t\tNcLoadingIcon,\n\t\tHistoryIcon,\n\t\tAlertCircleOutline,\n\t\tCnDataTable,\n\t\tCnDetailGrid,\n\t\tCnPageHeader,\n\t\tCnPagination,\n\t},\n\n\tprops: {\n\t\t/** Page title. Defaults to \"Activity log\" so a bare manifest entry still renders meaningfully. */\n\t\ttitle: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Activity log'),\n\t\t},\n\n\t\t/** Description shown under the title when `showTitle` is set. */\n\t\tdescription: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Whether to render the inline page header VISIBLY. Defaults to true:\n\t\t * unlike CnIndexPage (which surfaces the title in its sidebar header\n\t\t * when this is false), a logs page has no sidebar fallback, so a false\n\t\t * default left the page with no heading at all. Setting it to false is\n\t\t * now safe for accessibility either way — the `<h1>` is still rendered\n\t\t * visually-hidden, so the `<main>` landmark keeps its accessible\n\t\t * heading (WCAG 2.4.6 / 1.3.1).\n\t\t */\n\t\tshowTitle: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/** MDI icon name for the header. */\n\t\ticon: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * OpenRegister register slug. Required (with `schema`) for store-backed\n\t\t * mode. Changing it after mount requires a remount — CnPageRenderer keys\n\t\t * the dispatched page on register+schema, so a manifest page swap already\n\t\t * remounts. A direct consumer that rebinds it instead gets a development\n\t\t * console warning, since the bound data source does not follow.\n\t\t */\n\t\tregister: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * OpenRegister schema slug. Required (with `register`) for store-backed\n\t\t * mode. Changing it after mount requires a remount (see `register`).\n\t\t */\n\t\tschema: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Custom log source URL — used when `register`+`schema` is not set. */\n\t\tsource: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Column definitions for the table. When omitted, a store-backed page\n\t\t * derives its columns from the loaded schema; a `source`-backed page\n\t\t * (or one whose schema failed to load) falls back to\n\t\t * `[timestamp, actor, action, target, details]`.\n\t\t * Pass either an array of strings (treated as keys + auto-labels)\n\t\t * OR an array of `{ key, label, sortable, width }` objects.\n\t\t *\n\t\t * @type {Array<string|{key: string, label: string}>}\n\t\t */\n\t\tcolumns: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/**\n\t\t * Fixed filter map merged into every fetch, ABOVE the `$route.query`\n\t\t * deep-link filters so the page's own scoping wins on a key collision.\n\t\t * Values support the shared token grammar: `@route.<param>` / `:<param>`\n\t\t * (route params), `@me`, `@today±Nd`, `@workspace.<key>`. Null = no fixed\n\t\t * filter. Store mode only. Reactive: changing the map — or changing what a\n\t\t * token in it resolves to — refetches from page 1.\n\t\t *\n\t\t * @type {object|null}\n\t\t */\n\t\tfilter: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Pagination config; only `limit` is read, as the page size sent as\n\t\t * `_limit`. Null = the store default of 20. Store mode only.\n\t\t *\n\t\t * @type {{limit?: number}|null}\n\t\t */\n\t\tpagination: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Initial sort column. Null (the default) sends no `_order`, leaving the\n\t\t * server's own ordering in place.\n\t\t */\n\t\tsortKey: {\n\t\t\ttype: String,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Initial sort direction. Inert while `sortKey` is null. */\n\t\tsortOrder: {\n\t\t\ttype: String,\n\t\t\tdefault: 'asc',\n\t\t\tvalidator: (v) => v === null || ['asc', 'desc'].includes(v),\n\t\t},\n\n\t\t/**\n\t\t * Initial MULTI-column sort as an ordered priority list. Takes precedence\n\t\t * over `sortKey`/`sortOrder` when non-empty; empty (the default) is inert.\n\t\t * A `?_order=` param on the URL (the JSON-encoded list CnIndexPage\n\t\t * persists when a header is clicked) outranks both, so a shared or\n\t\t * reloaded logs link opens on the sort it carried.\n\t\t *\n\t\t * @type {Array<{key: string, order: 'asc'|'desc'}>}\n\t\t */\n\t\tsortKeys: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/**\n\t\t * Make the columns' declared `width` authoritative (`table-layout: fixed`)\n\t\t * instead of a hint the browser may override from cell content. Worth\n\t\t * setting on a log table: message text and long unbreakable values like a\n\t\t * PHP FQCN otherwise dictate the column widths between them. Long values\n\t\t * then wrap inside their cell rather than overflowing it. Default false\n\t\t * keeps the previous auto layout.\n\t\t */\n\t\tfixedLayout: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Open a read-only detail dialog when a row is clicked, rendering the\n\t\t * entry's fields — including nested bags like a stack trace or an\n\t\t * argument map that a table cell can only summarise. Default false\n\t\t * keeps a row click inert, as before. Ignored when `rowRoute` is set:\n\t\t * a purpose-built page beats a generic dialog.\n\t\t */\n\t\trowDetail: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Manifest page id (route name) to open on a row click, pushed as\n\t\t * `{ name: rowRoute, params: { id: row[rowKey] } }` — the same shape\n\t\t * CnIndexPage's `open-page` actions use. For a log whose detail surface\n\t\t * is a real page (a step timeline, a replay action) rather than a\n\t\t * read-only dump. Takes precedence over `rowDetail`.\n\t\t */\n\t\trowRoute: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Row identifier property. Defaults to `id` (matches OR + most custom log shapes). */\n\t\trowKey: {\n\t\t\ttype: String,\n\t\t\tdefault: 'id',\n\t\t},\n\n\t\t/** Text shown when there are no log entries. */\n\t\temptyText: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'No log entries to show'),\n\t\t},\n\n\t\t/** Text shown when the fetch fails. */\n\t\terrorText: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Could not load log entries'),\n\t\t},\n\n\t\t/** Label for the row-detail dialog's close button. */\n\t\tcloseLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Close'),\n\t\t},\n\n\t\t/**\n\t\t * Override the object store. Useful when the consuming app calls\n\t\t * `createObjectStore` with a custom ID. When null, the default\n\t\t * `useObjectStore()` is used. The component skips store wiring\n\t\t * entirely when in `source` mode.\n\t\t *\n\t\t * @type {object|null}\n\t\t */\n\t\tstore: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\temits: [\n\t\t/**\n\t\t * @event action Declared for hosts that dispatch log-row actions through\n\t\t * the `#row-actions` slot. Never emitted by this component itself; kept\n\t\t * in the surface because consumers may already listen for it.\n\t\t */\n\t\t'action',\n\t\t/**\n\t\t * @event row-click Emitted when a log row's body is clicked or\n\t\t * middle-clicked, whether or not `rowDetail` opened the detail dialog —\n\t\t * so a host can navigate instead of (or as well as) showing the dialog.\n\t\t * Payload: `(row, event)`, the second being the native click/auxclick\n\t\t * event.\n\t\t * @type {object}\n\t\t */\n\t\t'row-click',\n\t],\n\n\tsetup(props) {\n\t\t// Always returned, even when the composable is not used: Vue warns when\n\t\t// a computed reads a key setup() omitted, and `source` mode reads these.\n\t\t// `resolveFixedFilters` is included for the same reason — the\n\t\t// `filterSignature` computed calls it by name, and `source` mode (which\n\t\t// has no fixed filters at all) takes this early return.\n\t\tconst NO_LIST = {\n\t\t\tlist: null,\n\t\t\tobjectType: '',\n\t\t\tlistStore: null,\n\t\t\tresolveFixedFilters: () => ({}),\n\t\t}\n\t\tif (!props.register || !props.schema) {\n\t\t\treturn NO_LIST\n\t\t}\n\n\t\tconst store = props.store || useObjectStore()\n\t\t// The `store` prop has always accepted a hand-rolled object, but\n\t\t// useListView assumes a full OpenRegister store (pagination/errors/\n\t\t// fetchSchema). Duck-type it and fall back to the legacy no-params\n\t\t// fetch rather than throwing on a partial store.\n\t\tif (typeof store.fetchCollection !== 'function' || !store.pagination) {\n\t\t\treturn NO_LIST\n\t\t}\n\n\t\tconst objectType = `${props.register}-${props.schema}`\n\t\t// Must run synchronously: useListView's onMounted calls fetchSchema(),\n\t\t// which throws when the type is not registered yet. Positional\n\t\t// signature: (slug, schemaId, registerId).\n\t\tif (typeof store.registerObjectType === 'function') {\n\t\t\tstore.registerObjectType(objectType, props.schema, props.register)\n\t\t}\n\n\t\tconst instance = getCurrentInstance()\n\n\t\t// Token-resolution context for the `filter` prop's `@workspace.<key>` /\n\t\t// `@config.<key>` / `@object.<field>` tokens. Same injects + unwrap shape\n\t\t// as useSelfFetchList's `tokenCtx`. Without it `resolveFilterMap` was\n\t\t// called with no ctx, and `resolveFilterValue` returns an unresolvable\n\t\t// token VERBATIM — so a page declaring `filter: { client:\n\t\t// '@workspace.selectedClient' }` sent the literal string\n\t\t// \"@workspace.selectedClient\" to OpenRegister as a property filter and got\n\t\t// an empty table with no error, despite the prop doc naming that token.\n\t\t// All three default to absent, so an app that never provides them is\n\t\t// unaffected.\n\t\tconst objectCtxRaw = inject('cnObjectContext', null)\n\t\tconst workspaceCtxRaw = inject('cnWorkspaceContext', null)\n\t\tconst appConfigRaw = inject('cnAppConfig', null)\n\t\tconst unwrapCtx = (v) => ((v && typeof v === 'object' && 'value' in v) ? v.value : v)\n\t\tconst tokenCtx = () => {\n\t\t\tconst objCtx = unwrapCtx(objectCtxRaw)\n\t\t\tconst base = (objCtx && typeof objCtx === 'object') ? { ...objCtx } : {}\n\t\t\tbase.workspace = unwrapCtx(workspaceCtxRaw) || {}\n\t\t\tbase.config = unwrapCtx(appConfigRaw) || {}\n\t\t\treturn base\n\t\t}\n\t\t// The complete fetch scope: the deep-link query filters plus the `filter`\n\t\t// prop's route/`@`-token resolution. A getter, not a computed — it is\n\t\t// re-read on every fetch, so a `?jobId=` change re-scopes the list\n\t\t// without re-creating it, and `tokenCtx()` reflects the latest bags.\n\t\tconst resolveFixedFilters = () => {\n\t\t\tconst route = instance && instance.proxy && instance.proxy.$route\n\t\t\tconst ctx = tokenCtx()\n\t\t\treturn {\n\t\t\t\t...resolveQueryFilters(route && route.query, ctx),\n\t\t\t\t...resolveFilterMap(props.filter, (route && route.params) || {}, ctx),\n\t\t\t}\n\t\t}\n\n\t\tconst list = useListView(objectType, {\n\t\t\tobjectStore: store,\n\t\t\tdefaultPageSize: (props.pagination && props.pagination.limit) || undefined,\n\t\t\t// A `?_order=` deep link wins over the configured default, so a\n\t\t\t// reloaded or shared logs URL reproduces the sort the link carried —\n\t\t\t// the same precedence CnIndexPage's useSelfFetchList applies.\n\t\t\tdefaultSort: props.sortKey ? { key: props.sortKey, order: props.sortOrder || 'asc' } : undefined,\n\t\t\tdefaultSortKeys: parseSortKeysFromQuery(instance && instance.proxy && instance.proxy.$route)\n\t\t\t\t|| (props.sortKeys.length > 0 ? props.sortKeys : undefined),\n\t\t\tfixedFilters: resolveFixedFilters,\n\t\t})\n\n\t\treturn { list, objectType, listStore: store, resolveFixedFilters }\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tlocalRows: [],\n\t\t\tlocalLoading: false,\n\t\t\tlocalError: null,\n\t\t\tlocalSortKeys: [],\n\t\t\tdetailRow: null,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/** Whether the component fetches via the object store + useListView. */\n\t\tusesStore() {\n\t\t\treturn !!this.list\n\t\t},\n\n\t\t/**\n\t\t * Whether the component should fetch via axios. Store mode needs BOTH\n\t\t * `register` and `schema`, so a half-configured pair with a `source` set\n\t\t * still falls back to the URL rather than the empty state.\n\t\t */\n\t\tusesSource() {\n\t\t\treturn !(this.register && this.schema) && !!this.source\n\t\t},\n\n\t\t/** Loading state of the active fetch mode. */\n\t\tloading() {\n\t\t\t// `list` is a bag of refs returned from setup(); only TOP-LEVEL setup\n\t\t\t// refs are auto-unwrapped, so every read below goes through `.value`.\n\t\t\treturn this.list ? this.list.loading.value : this.localLoading\n\t\t},\n\n\t\t/**\n\t\t * Fetch error. `fetchCollection` records failures on the store rather\n\t\t * than throwing, so store mode reads them from there — without this the\n\t\t * `#error` slot never rendered for a store-backed page.\n\t\t */\n\t\terror() {\n\t\t\tif (this.localError) {\n\t\t\t\treturn this.localError\n\t\t\t}\n\t\t\tif (this.list && this.listStore) {\n\t\t\t\treturn this.listStore.errors[this.objectType] || null\n\t\t\t}\n\t\t\treturn null\n\t\t},\n\n\t\t/** Rows to render: the store collection, or locally sorted axios rows. */\n\t\trows() {\n\t\t\tif (this.list) {\n\t\t\t\treturn this.list.objects.value\n\t\t\t}\n\t\t\t// Legacy-store fallback: read the store's collection LIVE rather than\n\t\t\t// the snapshot `fetch()` took. `rows` used to reach the collection\n\t\t\t// through a computed; snapshotting into `localRows` meant a store that\n\t\t\t// REPLACES the array (rather than mutating it in place) no longer\n\t\t\t// reached the table. Only `this.store` is consulted — in this path it is\n\t\t\t// the partial store prop that made setup() bail — so `source` mode,\n\t\t\t// which has no store, still falls through to localRows.\n\t\t\tconst live = (this.register && this.schema && this.store)\n\t\t\t\t? this.store.collections?.[`${this.register}-${this.schema}`]\n\t\t\t\t: null\n\t\t\tconst base = Array.isArray(live) ? live : this.localRows\n\t\t\tif (this.localSortKeys.length === 0) {\n\t\t\t\treturn base\n\t\t\t}\n\t\t\t// CnDataTable is presentational — it never sorts its own rows — so\n\t\t\t// `source` mode has to apply the header sort itself.\n\t\t\treturn multiKeySort(base, this.localSortKeys.map((k) => ({ field: k.key, order: k.order })))\n\t\t},\n\n\t\t/**\n\t\t * Server pagination STATE (`{ total, page, pages, limit }`); null in\n\t\t * source mode, which has no paging. Distinct from the `pagination`\n\t\t * prop, which is the manifest's page-size config.\n\t\t */\n\t\tpaginationState() {\n\t\t\treturn this.list ? this.list.pagination.value : null\n\t\t},\n\n\t\t/**\n\t\t * The LOADED schema object (not the `schema` slug prop), forwarded to\n\t\t * CnDataTable for type-aware cell rendering and schema-derived columns.\n\t\t */\n\t\ttableSchema() {\n\t\t\treturn (this.list && this.list.schema.value) || null\n\t\t},\n\n\t\t/** Active ordered sort keys, from the composable or local source state. */\n\t\teffectiveSortKeys() {\n\t\t\treturn this.list ? this.list.sortKeys.value : this.localSortKeys\n\t\t},\n\n\t\t/** Primary sort key (mirrors `effectiveSortKeys[0]`) for CnDataTable. */\n\t\teffectiveSortKey() {\n\t\t\treturn this.effectiveSortKeys[0]?.key ?? null\n\t\t},\n\n\t\t/** Primary sort direction (mirrors `effectiveSortKeys[0]`). */\n\t\teffectiveSortOrder() {\n\t\t\treturn this.effectiveSortKeys[0]?.order ?? 'asc'\n\t\t},\n\n\t\t/**\n\t\t * Resolved columns. A consumer-provided list wins. Otherwise, when a\n\t\t * schema is loaded, return `[]` so CnDataTable derives the columns from\n\t\t * it — the legacy `timestamp/actor/action/target/details` default matched\n\t\t * no OpenRegister log schema and rendered five blank cells. `source` mode\n\t\t * and a failed schema load still get that legacy default.\n\t\t */\n\t\tresolvedColumns() {\n\t\t\tif (this.columns.length > 0) {\n\t\t\t\treturn this.columns.map((c) => (typeof c === 'string' ? { key: c, label: this.humanise(c) } : c))\n\t\t\t}\n\t\t\tif (this.tableSchema) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\treturn legacyDefaultColumns()\n\t\t},\n\n\t\t/** Column slot names that the parent has provided (for pass-through). */\n\t\tslotColumns() {\n\t\t\treturn Object.keys(this.$slots || {})\n\t\t\t\t.filter((name) => name.startsWith('column-'))\n\t\t\t\t.map((name) => name.replace('column-', ''))\n\t\t},\n\n\t\t/** Dialog heading: the entry's message when it has one, else its id. */\n\t\tdetailTitle() {\n\t\t\tif (!this.detailRow) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst message = this.detailRow.message\n\t\t\tif (typeof message === 'string' && message !== '') {\n\t\t\t\treturn message\n\t\t\t}\n\t\t\tconst id = this.detailRow[this.rowKey]\n\t\t\treturn id ? String(id) : t('nextcloud-vue', 'Log entry')\n\t\t},\n\n\t\t/** The clicked row's primitive fields, as CnDetailGrid items. */\n\t\tdetailScalarItems() {\n\t\t\treturn this.detailEntries\n\t\t\t\t.filter(([, value]) => !this.isBag(value))\n\t\t\t\t.map(([key, value]) => ({ label: this.propertyLabel(key), value: String(value) }))\n\t\t},\n\n\t\t/**\n\t\t * The clicked row's nested fields. A bag of primitives (a stack trace's\n\t\t * frames, an argument map) renders as its own labelled grid; anything\n\t\t * deeper falls back to pretty-printed JSON.\n\t\t */\n\t\tdetailObjectBlocks() {\n\t\t\treturn this.detailEntries\n\t\t\t\t.filter(([, value]) => this.isBag(value))\n\t\t\t\t.map(([key, value]) => {\n\t\t\t\t\tconst entries = Object.entries(value)\n\t\t\t\t\tconst flat = entries.every(([, v]) => v === null || typeof v !== 'object')\n\t\t\t\t\treturn {\n\t\t\t\t\t\tkey,\n\t\t\t\t\t\tlabel: this.propertyLabel(key),\n\t\t\t\t\t\titems: flat ? entries.map(([k, v]) => ({ label: this.humanise(k), value: String(v) })) : null,\n\t\t\t\t\t\tjson: flat ? null : JSON.stringify(value, null, 2),\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Stable signature of the RESOLVED fetch scope — the query deep-link\n\t\t * filters plus the `filter` prop after route-param and `@`-token\n\t\t * resolution. Reading through the same getter the fetch uses is what\n\t\t * makes it exact: it tracks `$route`, `props.filter` and the injected\n\t\t * workspace / app-config bags, and it changes only when the request the\n\t\t * list would issue actually changes. Stringified so the watcher fires on\n\t\t * content, not identity. Empty string in `source` mode (no list).\n\t\t *\n\t\t * @return {string} The serialized filter map.\n\t\t */\n\t\tfilterSignature() {\n\t\t\tif (!this.list) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn JSON.stringify(this.resolveFixedFilters())\n\t\t},\n\n\t\t/** The clicked row's renderable entries — `@self` and empties dropped. */\n\t\tdetailEntries() {\n\t\t\tif (!this.detailRow) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\treturn Object.entries(this.detailRow)\n\t\t\t\t.filter(([key, value]) => key !== '@self' && value !== null && value !== undefined && value !== '')\n\t\t},\n\t},\n\n\twatch: {\n\t\t// ONE watcher over the whole fetch scope, rather than one per reactive\n\t\t// source that feeds it. `fixedFilters` is a plain getter re-read at fetch\n\t\t// time, so a refresh is all any of them needs — but watching them\n\t\t// individually got three things wrong:\n\t\t//\n\t\t// - `$route.query` and `$route.params` both fire on a single navigation,\n\t\t// so one route change issued TWO identical concurrent fetches, which\n\t\t// `fetchCollection` does not dedup and whose responses can land in\n\t\t// either order;\n\t\t// - they fired on navigation AWAY as well, because the route updates\n\t\t// before this component tears down. The refetch was then scoped by the\n\t\t// DESTINATION route's query (`/cases?status=open` → `GET\n\t\t// …/job_log?status=open`), overwriting the shared store collection that\n\t\t// any other mounted widget on the same objectType reads;\n\t\t// - a workspace / app-config bag mutation refetched even when no\n\t\t// `@workspace.`/`@config.` token was in play, so a volatile bag reset a\n\t\t// token-free page to page 1 on every write.\n\t\t//\n\t\t// The signature is the RESOLVED filter map, so it changes only when the\n\t\t// scope actually changes, and the route-name guard drops the fire that\n\t\t// belongs to the page being navigated to. No `immediate` — useListView\n\t\t// owns the initial fetch. This also covers a reactive `filter` prop\n\t\t// change, which had no watcher of its own at all.\n\t\tfilterSignature(next, prev) {\n\t\t\tif (!this.list || next === prev) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\t// Guarded only when this page HAS a route name to compare against —\n\t\t\t// a router-less host, or one whose routes are unnamed, keeps the\n\t\t\t// unguarded behaviour rather than losing refetches to a comparison\n\t\t\t// that can never match.\n\t\t\tif (this._ownRouteName !== undefined && this.$route?.name !== this._ownRouteName) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.list.refresh(1)\n\t\t},\n\n\t\tregister(value, previous) {\n\t\t\tthis.onSourcePropChange('register', value, previous)\n\t\t},\n\n\t\tschema(value, previous) {\n\t\t\tthis.onSourcePropChange('schema', value, previous)\n\t\t},\n\n\t\tsource(value, previous) {\n\t\t\tthis.onSourcePropChange('source', value, previous)\n\t\t},\n\t},\n\n\tcreated() {\n\t\t// The route this page belongs to, captured BEFORE any navigation can\n\t\t// change it — the `filterSignature` watcher's guard. CnPageRenderer mounts\n\t\t// a page when `$route.name === page.id`, so this is the page's own id.\n\t\tthis._ownRouteName = this.$route ? this.$route.name : undefined\n\t},\n\n\tmounted() {\n\t\t// In store mode useListView's own onMounted owns the initial fetch;\n\t\t// calling fetch() here too would double-request on every page load.\n\t\tif (!this.list) {\n\t\t\tthis.fetch()\n\t\t}\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Capitalise + space a snake_case / camelCase key for a default column label.\n\t\t *\n\t\t * @param {string} key A log-entry property name, e.g. `'created_at'` or `'userAgent'`.\n\t\t * @return {string} The sentence-cased label, e.g. `'Created at'` / `'User agent'`.\n\t\t */\n\t\thumanise(key) {\n\t\t\tconst spaced = String(key).replace(/([A-Z])/g, ' $1').replace(/_/g, ' ').toLowerCase()\n\t\t\treturn spaced.charAt(0).toUpperCase() + spaced.slice(1)\n\t\t},\n\n\t\t/**\n\t\t * Label for a row property — the schema's own title when one is loaded.\n\t\t *\n\t\t * @param {string} key The property name.\n\t\t * @return {string} The display label.\n\t\t */\n\t\tpropertyLabel(key) {\n\t\t\tconst title = this.tableSchema?.properties?.[key]?.title\n\t\t\treturn (typeof title === 'string' && title !== '') ? title : this.humanise(key)\n\t\t},\n\n\t\t/**\n\t\t * Whether a value is a nested bag (object/array) rather than a scalar.\n\t\t *\n\t\t * @param {unknown} value The value to test.\n\t\t * @return {boolean} True for non-null objects and arrays.\n\t\t */\n\t\tisBag(value) {\n\t\t\treturn value !== null && typeof value === 'object'\n\t\t},\n\n\t\t/**\n\t\t * Fetch log entries from the resolved data source.\n\t\t *\n\t\t * Only used in `source` mode and in the legacy store fallback — the\n\t\t * composable owns fetching whenever it is active.\n\t\t */\n\t\tasync fetch() {\n\t\t\tif (this.register && this.schema) {\n\t\t\t\t// Legacy fallback: a partial `store` prop that useListView\n\t\t\t\t// cannot drive (see setup). Unfiltered + unpaginated, as before.\n\t\t\t\tconst store = this.store || useObjectStore()\n\t\t\t\tthis.localLoading = true\n\t\t\t\tthis.localError = null\n\t\t\t\ttry {\n\t\t\t\t\tif (typeof store.registerObjectType === 'function') {\n\t\t\t\t\t\tstore.registerObjectType(`${this.register}-${this.schema}`, this.schema, this.register)\n\t\t\t\t\t}\n\t\t\t\t\tif (typeof store.fetchCollection === 'function') {\n\t\t\t\t\t\tawait store.fetchCollection(`${this.register}-${this.schema}`)\n\t\t\t\t\t}\n\t\t\t\t\tthis.localRows = store.collections?.[`${this.register}-${this.schema}`] ?? []\n\t\t\t\t} catch (err) {\n\t\t\t\t\tthis.localError = err\n\t\t\t\t} finally {\n\t\t\t\t\tthis.localLoading = false\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (this.usesSource) {\n\t\t\t\tthis.localLoading = true\n\t\t\t\tthis.localError = null\n\t\t\t\ttry {\n\t\t\t\t\tconst response = await axios.get(prefixUrl(this.source))\n\t\t\t\t\tconst body = response?.data\n\t\t\t\t\tif (Array.isArray(body)) {\n\t\t\t\t\t\tthis.localRows = body\n\t\t\t\t\t} else if (body && Array.isArray(body.results)) {\n\t\t\t\t\t\tthis.localRows = body.results\n\t\t\t\t\t} else if (body && Array.isArray(body.entries)) {\n\t\t\t\t\t\tthis.localRows = body.entries\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthis.localRows = []\n\t\t\t\t\t}\n\t\t\t\t} catch (err) {\n\t\t\t\t\tthis.localError = err\n\t\t\t\t\tthis.localRows = []\n\t\t\t\t} finally {\n\t\t\t\t\tthis.localLoading = false\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\t// Misconfigured — surface a console warning so a developer notices.\n\t\t\t// eslint-disable-next-line no-console\n\t\t\tconsole.warn('[CnLogsPage] Neither register+schema nor source configured; rendering empty state.')\n\t\t},\n\n\t\t/**\n\t\t * React to a data-source prop changing on a mounted page.\n\t\t *\n\t\t * Only the legacy / `source` paths can act on it, by refetching. Store\n\t\t * mode cannot: the object type, its store registration and the\n\t\t * `useListView` instance are all built once in `setup()` from the\n\t\t * initial pair, so a new `register`/`schema` needs a remount to take\n\t\t * effect (which CnPageRenderer does on a manifest page swap). That is\n\t\t * documented on the props, but it used to happen in complete silence —\n\t\t * a direct consumer rebinding the prop saw the old collection and no\n\t\t * indication why. Warns in development only, once per change.\n\t\t *\n\t\t * @param {string} name The prop that changed.\n\t\t * @param {unknown} value The new value.\n\t\t * @param {unknown} previous The previous value.\n\t\t * @return {void}\n\t\t */\n\t\tonSourcePropChange(name, value, previous) {\n\t\t\tif (!this.list) {\n\t\t\t\tthis.fetch()\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (process.env.NODE_ENV !== 'production') {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.warn(`[CnLogsPage] \\`${name}\\` changed from \"${previous}\" to \"${value}\" on a mounted store-backed page. `\n\t\t\t\t\t+ 'The data source is bound once at setup, so this has no effect — remount the page (e.g. give it a '\n\t\t\t\t\t+ ':key) to pick up the new register/schema.')\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Apply a header sort. Server-side in store mode, client-side otherwise.\n\t\t *\n\t\t * @param {{key: string|null, order: string|null, keys?: Array<{key: string, order: string}>}} sort CnDataTable's sort payload.\n\t\t */\n\t\tonSort(sort) {\n\t\t\tif (this.list) {\n\t\t\t\tthis.list.onSort(sort)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.localSortKeys = Array.isArray(sort.keys)\n\t\t\t\t? sort.keys\n\t\t\t\t: (sort.key ? [{ key: sort.key, order: sort.order || 'asc' }] : [])\n\t\t},\n\n\t\t/**\n\t\t * Navigate to a page of results.\n\t\t *\n\t\t * @param {number} page The 1-based page number.\n\t\t */\n\t\tonPageChange(page) {\n\t\t\tthis.list?.onPageChange(page)\n\t\t},\n\n\t\t/**\n\t\t * Change the page size, returning to page 1.\n\t\t *\n\t\t * @param {number} size The new page size.\n\t\t */\n\t\tonPageSizeChange(size) {\n\t\t\tthis.list?.onPageSizeChange(size)\n\t\t},\n\n\t\t/**\n\t\t * Handle a row-body click: navigate to `rowRoute` when one is declared,\n\t\t * else open the detail dialog when `rowDetail` is set. Always re-emits\n\t\t * so a host can do its own thing regardless. A ctrl/cmd/shift or middle\n\t\t * click opens `rowRoute` in a new tab; a middle click never opens the\n\t\t * dialog.\n\t\t *\n\t\t * @param {object} row The clicked log entry.\n\t\t * @param {MouseEvent} [event] The originating click/auxclick event.\n\t\t */\n\t\tonRowClick(row, event) {\n\t\t\tif (this.rowRoute) {\n\t\t\t\tmarkNewTabHandled(event, openRowTarget(event, { name: this.rowRoute, params: { id: row?.[this.rowKey] } }, this.$router))\n\t\t\t} else if (this.rowDetail && !(event && event.button === 1)) {\n\t\t\t\tthis.detailRow = row\n\t\t\t}\n\t\t\t/**\n\t\t\t * @event row-click Emitted when a log row's body is clicked or middle-clicked. Payload: `(row, event)` — the log entry and the native click/auxclick event.\n\t\t\t * @type {object}\n\t\t\t */\n\t\t\tthis.$emit('row-click', row, event)\n\t\t},\n\n\t\t/** Close the row-detail dialog. */\n\t\tcloseDetail() {\n\t\t\tthis.detailRow = null\n\t\t},\n\n\t\t/**\n\t\t * Re-fetch from the source. Exposed so refresh buttons in\n\t\t * actionsComponent can call `$parent.refresh()`.\n\t\t *\n\t\t * @public\n\t\t */\n\t\trefresh() {\n\t\t\tif (this.list) {\n\t\t\t\tthis.list.refresh(1)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.fetch()\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n/* Padding matches CnIndexPage / CnDetailPage (see css/index-page.css and\n css/detail-page.css) — a logs page had none at all, so its table ran flush\n into both edges of the app content area. Gaps are expressed in the same\n grid-baseline unit as the rest of the fleet rather than raw pixels. */\n.cn-logs-page {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: calc(4 * var(--default-grid-baseline));\n\tpadding: calc(5 * var(--default-grid-baseline));\n\tbox-sizing: border-box;\n}\n\n.cn-logs-page__actions {\n\tdisplay: flex;\n\tjustify-content: flex-end;\n\tgap: calc(2 * var(--default-grid-baseline));\n}\n\n.cn-logs-page__loading,\n.cn-logs-page__empty,\n.cn-logs-page__error {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\tmin-height: 200px;\n}\n\n.cn-logs-page__pagination {\n\tmargin-top: calc(3 * var(--default-grid-baseline));\n}\n\n.cn-logs-page__detail {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: calc(2 * var(--default-grid-baseline));\n\tpadding: 0 calc(3 * var(--default-grid-baseline)) calc(3 * var(--default-grid-baseline));\n}\n\n.cn-logs-page__detail-heading {\n\tmargin: calc(3 * var(--default-grid-baseline)) 0 0;\n\tfont-weight: bold;\n}\n\n.cn-logs-page__detail-json {\n\tmargin: 0;\n\tpadding: calc(2 * var(--default-grid-baseline));\n\toverflow-x: auto;\n\tborder-radius: var(--border-radius);\n\tbackground-color: var(--color-background-dark);\n\tfont-family: monospace;\n\twhite-space: pre;\n}\n\n/* Same breakpoint + step-down as CnDetailPage, so the three page types stay in\n sync on narrow viewports. */\n@media (max-width: 768px) {\n\t.cn-logs-page {\n\t\tpadding: calc(3 * var(--default-grid-baseline));\n\t}\n}\n</style>\n"],"names":["_renderSlot","_openBlock","_createElementBlock","_createVNode","_Fragment","_renderList","_withCtx","_createElementVNode"],"mappings":"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"}
1
+ {"version":3,"file":"CnLogsPage.vue3.js","sources":["../../../../src/components/CnLogsPage/CnLogsPage.vue?vue&type=template&id=18d3ce6f&lang.js"],"sourcesContent":["<!--\n CnLogsPage — Read-only audit-trail / activity-log surface.\n\n Wraps CnDataTable with server-side sorting, pagination and filtering.\n Reads its data source from `config`:\n - { register, schema } → fetched via the OpenRegister object store\n - { source: '/api/url' } → fetched via axios.get(source)\n\n In store mode the list state (params, paging, sort, filters) is owned by\n the shared `useListView` composable, so a `?jobId=<uuid>`-style deep link\n lands the page pre-filtered — the same behaviour CnIndexPage has.\n\n Mounted by CnPageRenderer when a manifest page declares\n `type: \"logs\"`. Honours `headerComponent`, `actionsComponent`, and\n the generic `slots` map from `pages[]` (forwarded by the renderer\n through scoped slots; this component just exposes `#header`,\n `#actions`, and `#row-actions` slots which the renderer fills).\n\n Backwards-compat: when neither `register+schema` nor `source` is\n set, the component renders an empty-state with a console warning\n rather than throwing — this keeps a misconfigured manifest from\n breaking the app shell.\n-->\n<template>\n\t<div class=\"cn-logs-page\" data-testid=\"cn-logs-page\">\n\t\t<!-- @slot Replaces the default CnPageHeader. CnPageRenderer fills it from `pages[].headerComponent` / `slots.header`. -->\n\t\t<!-- @binding {string} title The page title. -->\n\t\t<!-- @binding {string} description The page description. -->\n\t\t<!-- @binding {string} icon The header's MDI icon name. -->\n\t\t<slot\n\t\t\tname=\"header\"\n\t\t\t:title=\"title\"\n\t\t\t:description=\"description\"\n\t\t\t:icon=\"icon\">\n\t\t\t<CnPageHeader\n\t\t\t\tv-if=\"title\"\n\t\t\t\t:title=\"title\"\n\t\t\t\t:description=\"description\"\n\t\t\t\t:icon=\"icon\"\n\t\t\t\t:visuallyHidden=\"!showTitle\" />\n\t\t</slot>\n\n\t\t<div v-if=\"$slots.actions\" class=\"cn-logs-page__actions\">\n\t\t\t<!-- @slot Right-aligned action area (refresh, export, …). CnPageRenderer fills it from `pages[].actionsComponent`. -->\n\t\t\t<slot name=\"actions\" />\n\t\t</div>\n\n\t\t<!-- Body -->\n\t\t<div class=\"cn-logs-page__body\">\n\t\t\t<!-- Loading state -->\n\t\t\t<div v-if=\"loading\" class=\"cn-logs-page__loading\">\n\t\t\t\t<NcLoadingIcon :size=\"32\" />\n\t\t\t</div>\n\n\t\t\t<!-- The error block below is a SIBLING of this chain, not part of it, so\n\t\t\t BOTH guards here are load-bearing and neither is sufficient alone.\n\t\t\t A failed fetch leaves the collection empty, so:\n\t\t\t - without `&& !error`, this branch renders \"no log entries\"\n\t\t\t stacked above \"could not load log entries\";\n\t\t\t - without the `rows.length > 0` guard on the table branch, the\n\t\t\t `v-else` catches that same state instead and mounts CnDataTable\n\t\t\t with zero rows, which renders its OWN empty row from the same\n\t\t\t `emptyText` — the identical contradiction, just inside a table.\n\t\t\t So: error + no rows shows the error alone; no error + no rows shows\n\t\t\t this block; rows present shows the table, with the error beneath it\n\t\t\t when a refresh failed over a still-populated collection. -->\n\t\t\t<div v-else-if=\"rows.length === 0 && !error\" class=\"cn-logs-page__empty\">\n\t\t\t\t<!-- @slot Replaces the empty-state block shown when there are no log entries. -->\n\t\t\t\t<slot name=\"empty\">\n\t\t\t\t\t<NcEmptyContent :name=\"emptyText\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<HistoryIcon :size=\"64\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t</NcEmptyContent>\n\t\t\t\t</slot>\n\t\t\t</div>\n\n\t\t\t<!-- Table. `v-else-if=\"rows.length > 0\"` rather than a bare `v-else` —\n\t\t\t see the guard note above. -->\n\t\t\t<template v-else-if=\"rows.length > 0\">\n\t\t\t\t<CnDataTable\n\t\t\t\t\t:schema=\"tableSchema\"\n\t\t\t\t\t:columns=\"resolvedColumns\"\n\t\t\t\t\t:rows=\"rows\"\n\t\t\t\t\t:rowKey=\"rowKey\"\n\t\t\t\t\t:fixedLayout=\"fixedLayout\"\n\t\t\t\t\t:sortKey=\"effectiveSortKey\"\n\t\t\t\t\t:sortOrder=\"effectiveSortOrder\"\n\t\t\t\t\t:sortKeys=\"effectiveSortKeys\"\n\t\t\t\t\t:emptyText=\"emptyText\"\n\t\t\t\t\t@sort=\"onSort\"\n\t\t\t\t\t@rowClick=\"onRowClick\"\n\t\t\t\t\t@rowAuxClick=\"onRowAuxClick\">\n\t\t\t\t\t<template\n\t\t\t\t\t\tv-for=\"col in slotColumns\"\n\t\t\t\t\t\t#[`column-${col}`]=\"{ row, value }\">\n\t\t\t\t\t\t<!-- @slot Per-column cell renderer (`#column-<key>`), one per configured column key. -->\n\t\t\t\t\t\t<!-- @binding {object} row The log entry being rendered. -->\n\t\t\t\t\t\t<!-- @binding {*} value The cell's resolved value. -->\n\t\t\t\t\t\t<slot :name=\"'column-' + col\" :row=\"row\" :value=\"value\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t<template v-if=\"$slots['row-actions']\" #row-actions=\"{ row }\">\n\t\t\t\t\t\t<!-- @slot Per-row action menu cell. Supplying it adds the trailing actions column. -->\n\t\t\t\t\t\t<!-- @binding {object} row The log entry for this row. -->\n\t\t\t\t\t\t<slot name=\"row-actions\" :row=\"row\" />\n\t\t\t\t\t</template>\n\t\t\t\t</CnDataTable>\n\n\t\t\t\t<CnPagination\n\t\t\t\t\tv-if=\"paginationState && paginationState.pages > 1\"\n\t\t\t\t\tclass=\"cn-logs-page__pagination\"\n\t\t\t\t\t:currentPage=\"paginationState.page || 1\"\n\t\t\t\t\t:totalPages=\"paginationState.pages || 1\"\n\t\t\t\t\t:totalItems=\"paginationState.total || 0\"\n\t\t\t\t\t:currentPageSize=\"paginationState.limit || 20\"\n\t\t\t\t\t@pageChanged=\"onPageChange\"\n\t\t\t\t\t@pageSizeChanged=\"onPageSizeChange\" />\n\t\t\t</template>\n\n\t\t\t<div v-if=\"error\" class=\"cn-logs-page__error\">\n\t\t\t\t<!-- @slot Replaces the error block shown when the fetch fails. -->\n\t\t\t\t<!-- @binding {*} error The recorded fetch error. -->\n\t\t\t\t<slot name=\"error\" :error=\"error\">\n\t\t\t\t\t<NcEmptyContent :name=\"errorText\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<AlertCircleOutline :size=\"64\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t</NcEmptyContent>\n\t\t\t\t</slot>\n\t\t\t</div>\n\t\t</div>\n\n\t\t<!-- Row-detail dialog (opt-in via `rowDetail`). A log row's interesting\n\t\t payload is usually a nested object (a stack trace, an argument bag)\n\t\t that a table cell can only summarise. -->\n\t\t<NcDialog\n\t\t\tv-if=\"detailRow\"\n\t\t\t:open=\"true\"\n\t\t\t:name=\"detailTitle\"\n\t\t\tsize=\"large\"\n\t\t\t@update:open=\"closeDetail\">\n\t\t\t<!-- @slot Replaces the built-in row-detail dialog body. -->\n\t\t\t<!-- @binding {object} row The clicked log row. -->\n\t\t\t<slot name=\"row-detail\" :row=\"detailRow\">\n\t\t\t\t<div class=\"cn-logs-page__detail\">\n\t\t\t\t\t<CnDetailGrid\n\t\t\t\t\t\tv-if=\"detailScalarItems.length > 0\"\n\t\t\t\t\t\tlayout=\"horizontal\"\n\t\t\t\t\t\t:items=\"detailScalarItems\" />\n\t\t\t\t\t<template v-for=\"block in detailObjectBlocks\" :key=\"block.key\">\n\t\t\t\t\t\t<h4 class=\"cn-logs-page__detail-heading\">\n\t\t\t\t\t\t\t{{ block.label }}\n\t\t\t\t\t\t</h4>\n\t\t\t\t\t\t<CnDetailGrid\n\t\t\t\t\t\t\tv-if=\"block.items\"\n\t\t\t\t\t\t\tlayout=\"horizontal\"\n\t\t\t\t\t\t\t:items=\"block.items\" />\n\t\t\t\t\t\t<pre v-else class=\"cn-logs-page__detail-json\">{{ block.json }}</pre>\n\t\t\t\t\t</template>\n\t\t\t\t</div>\n\t\t\t</slot>\n\t\t\t<template #actions>\n\t\t\t\t<NcButton @click=\"closeDetail\">\n\t\t\t\t\t{{ closeLabel }}\n\t\t\t\t</NcButton>\n\t\t\t</template>\n\t\t</NcDialog>\n\t</div>\n</template>\n\n<script>\nimport axios from '@nextcloud/axios'\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcButton, NcDialog, NcEmptyContent, NcLoadingIcon } from '@nextcloud/vue'\nimport { getCurrentInstance, inject } from 'vue'\nimport AlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue'\nimport HistoryIcon from 'vue-material-design-icons/History.vue'\nimport { useListView } from '../../composables/index.js'\nimport { useObjectStore } from '../../store/index.js'\nimport { prefixUrl } from '../../utils/headers.js'\nimport { openRowTarget } from '../../utils/linkNavigation.js'\nimport { multiKeySort } from '../../utils/multiKeySort.js'\nimport { parseSortKeysFromQuery, resolveFilterMap, resolveQueryFilters } from '../../utils/routeFilters.js'\nimport { markNewTabHandled } from '../../utils/rowAuxClick.js'\nimport { CnDataTable } from '../CnDataTable/index.js'\nimport { CnDetailGrid } from '../CnDetailGrid/index.js'\nimport { CnPageHeader } from '../CnPageHeader/index.js'\nimport { CnPagination } from '../CnPagination/index.js'\n\n/**\n * Legacy default columns. Retained ONLY for `source` mode and for a store\n * mode whose schema could not be loaded — see `resolvedColumns`. They match\n * no OpenRegister log schema, so a store-backed page derives its columns\n * from the schema instead.\n *\n * A function, not a constant: `t()` at module scope would resolve before the\n * consuming app calls `registerTranslations()`, pinning the labels to English.\n *\n * @return {Array<{key: string, label: string, sortable?: boolean}>} The legacy column set.\n */\nfunction legacyDefaultColumns() {\n\treturn [\n\t\t{ key: 'timestamp', label: t('nextcloud-vue', 'Timestamp'), sortable: true },\n\t\t{ key: 'actor', label: t('nextcloud-vue', 'Actor'), sortable: true },\n\t\t{ key: 'action', label: t('nextcloud-vue', 'Action'), sortable: true },\n\t\t{ key: 'target', label: t('nextcloud-vue', 'Target') },\n\t\t{ key: 'details', label: t('nextcloud-vue', 'Details') },\n\t]\n}\n\n/**\n * CnLogsPage — Read-only audit-trail / activity-log page.\n *\n * Renders a CnDataTable of log entries with server-side sorting,\n * pagination and filtering. Columns come from `columns` (manifest:\n * `pages[].config.columns`); when none are given, a store-backed page\n * derives them from the schema.\n *\n * Two data-fetch modes:\n * - `register` + `schema`: fetched via `useObjectStore` as a regular\n * OpenRegister collection. The store registers the type as\n * `${register}-${schema}` per existing convention. List state is\n * owned by `useListView`, so `_limit` / `_page` / `_order` and any\n * `$route.query` / `filter` entries are sent with every request.\n * - `source`: a custom URL fetched via `axios.get(source)`. The\n * response shape may be either `{ results: [...] }` or a bare\n * array; the component handles both. This escape hatch supports\n * apps whose log surface is not OR-backed (e.g. a flat file).\n * Sorting is applied client-side; there is no pagination.\n *\n * Filtering (store mode) merges two sources, `filter` winning a collision:\n * 1. `$route.query` — every non-`_`-prefixed entry becomes a filter, so\n * `/jobs/logs?jobId=<uuid>` lands the page scoped to one job.\n * 2. `filter` — the manifest's own scoping, with `@route.<param>` /\n * `:<param>` / `@me` / `@today±Nd` tokens resolved at fetch time.\n *\n * The merged result is watched, so anything that moves it — a same-path query\n * change, a route param, the `filter` prop, or the workspace / app-config bag a\n * token in it reads — refetches from page 1, and nothing else does.\n *\n * Slots:\n * - `#header` — Replaces the default CnPageHeader.\n * - `#actions` — Renders a right-aligned action area (refresh,\n * export, etc.). The CnPageRenderer wires this from\n * `pages[].actionsComponent` when present.\n * - `#empty` — Replaces the empty-state block.\n * - `#error` — Replaces the error block. Scope: `{ error }`.\n * - `#row-actions` — Per-row action menu in the table. Scope:\n * `{ row }`.\n * - `#row-detail` — Replaces the row-detail dialog body. Scope: `{ row }`.\n * - `#column-<key>` — Per-column custom cell renderer. Scope:\n * `{ row, value }`.\n *\n * Props are intentionally permissive — every default has a sensible\n * fallback, so a manifest entry as small as\n * `{ type: \"logs\", config: { source: \"/api/logs\" } }` renders.\n */\nexport default {\n\tname: 'CnLogsPage',\n\n\tcomponents: {\n\t\tNcButton,\n\t\tNcDialog,\n\t\tNcEmptyContent,\n\t\tNcLoadingIcon,\n\t\tHistoryIcon,\n\t\tAlertCircleOutline,\n\t\tCnDataTable,\n\t\tCnDetailGrid,\n\t\tCnPageHeader,\n\t\tCnPagination,\n\t},\n\n\tprops: {\n\t\t/** Page title. Defaults to \"Activity log\" so a bare manifest entry still renders meaningfully. */\n\t\ttitle: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Activity log'),\n\t\t},\n\n\t\t/** Description shown under the title when `showTitle` is set. */\n\t\tdescription: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Whether to render the inline page header VISIBLY. Defaults to true:\n\t\t * unlike CnIndexPage (which surfaces the title in its sidebar header\n\t\t * when this is false), a logs page has no sidebar fallback, so a false\n\t\t * default left the page with no heading at all. Setting it to false is\n\t\t * now safe for accessibility either way — the `<h1>` is still rendered\n\t\t * visually-hidden, so the `<main>` landmark keeps its accessible\n\t\t * heading (WCAG 2.4.6 / 1.3.1).\n\t\t */\n\t\tshowTitle: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/** MDI icon name for the header. */\n\t\ticon: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * OpenRegister register slug. Required (with `schema`) for store-backed\n\t\t * mode. Changing it after mount requires a remount — CnPageRenderer keys\n\t\t * the dispatched page on register+schema, so a manifest page swap already\n\t\t * remounts. A direct consumer that rebinds it instead gets a development\n\t\t * console warning, since the bound data source does not follow.\n\t\t */\n\t\tregister: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * OpenRegister schema slug. Required (with `register`) for store-backed\n\t\t * mode. Changing it after mount requires a remount (see `register`).\n\t\t */\n\t\tschema: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Custom log source URL — used when `register`+`schema` is not set. */\n\t\tsource: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Column definitions for the table. When omitted, a store-backed page\n\t\t * derives its columns from the loaded schema; a `source`-backed page\n\t\t * (or one whose schema failed to load) falls back to\n\t\t * `[timestamp, actor, action, target, details]`.\n\t\t * Pass either an array of strings (treated as keys + auto-labels)\n\t\t * OR an array of `{ key, label, sortable, width }` objects.\n\t\t *\n\t\t * @type {Array<string|{key: string, label: string}>}\n\t\t */\n\t\tcolumns: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/**\n\t\t * Fixed filter map merged into every fetch, ABOVE the `$route.query`\n\t\t * deep-link filters so the page's own scoping wins on a key collision.\n\t\t * Values support the shared token grammar: `@route.<param>` / `:<param>`\n\t\t * (route params), `@me`, `@today±Nd`, `@workspace.<key>`. Null = no fixed\n\t\t * filter. Store mode only. Reactive: changing the map — or changing what a\n\t\t * token in it resolves to — refetches from page 1.\n\t\t *\n\t\t * @type {object|null}\n\t\t */\n\t\tfilter: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Pagination config; only `limit` is read, as the page size sent as\n\t\t * `_limit`. Null = the store default of 20. Store mode only.\n\t\t *\n\t\t * @type {{limit?: number}|null}\n\t\t */\n\t\tpagination: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Initial sort column. Null (the default) sends no `_order`, leaving the\n\t\t * server's own ordering in place.\n\t\t */\n\t\tsortKey: {\n\t\t\ttype: String,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Initial sort direction. Inert while `sortKey` is null. */\n\t\tsortOrder: {\n\t\t\ttype: String,\n\t\t\tdefault: 'asc',\n\t\t\tvalidator: (v) => v === null || ['asc', 'desc'].includes(v),\n\t\t},\n\n\t\t/**\n\t\t * Initial MULTI-column sort as an ordered priority list. Takes precedence\n\t\t * over `sortKey`/`sortOrder` when non-empty; empty (the default) is inert.\n\t\t * A `?_order=` param on the URL (the JSON-encoded list CnIndexPage\n\t\t * persists when a header is clicked) outranks both, so a shared or\n\t\t * reloaded logs link opens on the sort it carried.\n\t\t *\n\t\t * @type {Array<{key: string, order: 'asc'|'desc'}>}\n\t\t */\n\t\tsortKeys: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/**\n\t\t * Make the columns' declared `width` authoritative (`table-layout: fixed`)\n\t\t * instead of a hint the browser may override from cell content. Worth\n\t\t * setting on a log table: message text and long unbreakable values like a\n\t\t * PHP FQCN otherwise dictate the column widths between them. Long values\n\t\t * then wrap inside their cell rather than overflowing it. Default false\n\t\t * keeps the previous auto layout.\n\t\t */\n\t\tfixedLayout: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Open a read-only detail dialog when a row is clicked, rendering the\n\t\t * entry's fields — including nested bags like a stack trace or an\n\t\t * argument map that a table cell can only summarise. Default false\n\t\t * keeps a row click inert, as before. Ignored when `rowRoute` is set:\n\t\t * a purpose-built page beats a generic dialog.\n\t\t */\n\t\trowDetail: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Manifest page id (route name) to open on a row click, pushed as\n\t\t * `{ name: rowRoute, params: { id: row[rowKey] } }` — the same shape\n\t\t * CnIndexPage's `open-page` actions use. For a log whose detail surface\n\t\t * is a real page (a step timeline, a replay action) rather than a\n\t\t * read-only dump. Takes precedence over `rowDetail`.\n\t\t */\n\t\trowRoute: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Row identifier property. Defaults to `id` (matches OR + most custom log shapes). */\n\t\trowKey: {\n\t\t\ttype: String,\n\t\t\tdefault: 'id',\n\t\t},\n\n\t\t/** Text shown when there are no log entries. */\n\t\temptyText: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'No log entries to show'),\n\t\t},\n\n\t\t/** Text shown when the fetch fails. */\n\t\terrorText: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Could not load log entries'),\n\t\t},\n\n\t\t/** Label for the row-detail dialog's close button. */\n\t\tcloseLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Close'),\n\t\t},\n\n\t\t/**\n\t\t * Override the object store. Useful when the consuming app calls\n\t\t * `createObjectStore` with a custom ID. When null, the default\n\t\t * `useObjectStore()` is used. The component skips store wiring\n\t\t * entirely when in `source` mode.\n\t\t *\n\t\t * @type {object|null}\n\t\t */\n\t\tstore: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\temits: [\n\t\t/**\n\t\t * @event action Declared for hosts that dispatch log-row actions through\n\t\t * the `#row-actions` slot. Never emitted by this component itself; kept\n\t\t * in the surface because consumers may already listen for it.\n\t\t */\n\t\t'action',\n\t\t/**\n\t\t * @event row-click Emitted when a log row's body is clicked, whether or\n\t\t * not `rowDetail` opened the detail dialog — so a host can navigate\n\t\t * instead of (or as well as) showing the dialog. Payload: `(row, event)`,\n\t\t * the second being the native click event. A middle click emits\n\t\t * `row-aux-click` instead.\n\t\t * @type {object}\n\t\t */\n\t\t'row-click',\n\t\t/**\n\t\t * @event row-aux-click Emitted when a log row's body is middle-clicked,\n\t\t * for opening it in a new tab. Payload: `(row, event)`, the second being\n\t\t * the native auxclick event.\n\t\t * @type {object}\n\t\t */\n\t\t'row-aux-click',\n\t],\n\n\tsetup(props) {\n\t\t// Always returned, even when the composable is not used: Vue warns when\n\t\t// a computed reads a key setup() omitted, and `source` mode reads these.\n\t\t// `resolveFixedFilters` is included for the same reason — the\n\t\t// `filterSignature` computed calls it by name, and `source` mode (which\n\t\t// has no fixed filters at all) takes this early return.\n\t\tconst NO_LIST = {\n\t\t\tlist: null,\n\t\t\tobjectType: '',\n\t\t\tlistStore: null,\n\t\t\tresolveFixedFilters: () => ({}),\n\t\t}\n\t\tif (!props.register || !props.schema) {\n\t\t\treturn NO_LIST\n\t\t}\n\n\t\tconst store = props.store || useObjectStore()\n\t\t// The `store` prop has always accepted a hand-rolled object, but\n\t\t// useListView assumes a full OpenRegister store (pagination/errors/\n\t\t// fetchSchema). Duck-type it and fall back to the legacy no-params\n\t\t// fetch rather than throwing on a partial store.\n\t\tif (typeof store.fetchCollection !== 'function' || !store.pagination) {\n\t\t\treturn NO_LIST\n\t\t}\n\n\t\tconst objectType = `${props.register}-${props.schema}`\n\t\t// Must run synchronously: useListView's onMounted calls fetchSchema(),\n\t\t// which throws when the type is not registered yet. Positional\n\t\t// signature: (slug, schemaId, registerId).\n\t\tif (typeof store.registerObjectType === 'function') {\n\t\t\tstore.registerObjectType(objectType, props.schema, props.register)\n\t\t}\n\n\t\tconst instance = getCurrentInstance()\n\n\t\t// Token-resolution context for the `filter` prop's `@workspace.<key>` /\n\t\t// `@config.<key>` / `@object.<field>` tokens. Same injects + unwrap shape\n\t\t// as useSelfFetchList's `tokenCtx`. Without it `resolveFilterMap` was\n\t\t// called with no ctx, and `resolveFilterValue` returns an unresolvable\n\t\t// token VERBATIM — so a page declaring `filter: { client:\n\t\t// '@workspace.selectedClient' }` sent the literal string\n\t\t// \"@workspace.selectedClient\" to OpenRegister as a property filter and got\n\t\t// an empty table with no error, despite the prop doc naming that token.\n\t\t// All three default to absent, so an app that never provides them is\n\t\t// unaffected.\n\t\tconst objectCtxRaw = inject('cnObjectContext', null)\n\t\tconst workspaceCtxRaw = inject('cnWorkspaceContext', null)\n\t\tconst appConfigRaw = inject('cnAppConfig', null)\n\t\tconst unwrapCtx = (v) => ((v && typeof v === 'object' && 'value' in v) ? v.value : v)\n\t\tconst tokenCtx = () => {\n\t\t\tconst objCtx = unwrapCtx(objectCtxRaw)\n\t\t\tconst base = (objCtx && typeof objCtx === 'object') ? { ...objCtx } : {}\n\t\t\tbase.workspace = unwrapCtx(workspaceCtxRaw) || {}\n\t\t\tbase.config = unwrapCtx(appConfigRaw) || {}\n\t\t\treturn base\n\t\t}\n\t\t// The complete fetch scope: the deep-link query filters plus the `filter`\n\t\t// prop's route/`@`-token resolution. A getter, not a computed — it is\n\t\t// re-read on every fetch, so a `?jobId=` change re-scopes the list\n\t\t// without re-creating it, and `tokenCtx()` reflects the latest bags.\n\t\tconst resolveFixedFilters = () => {\n\t\t\tconst route = instance && instance.proxy && instance.proxy.$route\n\t\t\tconst ctx = tokenCtx()\n\t\t\treturn {\n\t\t\t\t...resolveQueryFilters(route && route.query, ctx),\n\t\t\t\t...resolveFilterMap(props.filter, (route && route.params) || {}, ctx),\n\t\t\t}\n\t\t}\n\n\t\tconst list = useListView(objectType, {\n\t\t\tobjectStore: store,\n\t\t\tdefaultPageSize: (props.pagination && props.pagination.limit) || undefined,\n\t\t\t// A `?_order=` deep link wins over the configured default, so a\n\t\t\t// reloaded or shared logs URL reproduces the sort the link carried —\n\t\t\t// the same precedence CnIndexPage's useSelfFetchList applies.\n\t\t\tdefaultSort: props.sortKey ? { key: props.sortKey, order: props.sortOrder || 'asc' } : undefined,\n\t\t\tdefaultSortKeys: parseSortKeysFromQuery(instance && instance.proxy && instance.proxy.$route)\n\t\t\t\t|| (props.sortKeys.length > 0 ? props.sortKeys : undefined),\n\t\t\tfixedFilters: resolveFixedFilters,\n\t\t})\n\n\t\treturn { list, objectType, listStore: store, resolveFixedFilters }\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tlocalRows: [],\n\t\t\tlocalLoading: false,\n\t\t\tlocalError: null,\n\t\t\tlocalSortKeys: [],\n\t\t\tdetailRow: null,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/** Whether the component fetches via the object store + useListView. */\n\t\tusesStore() {\n\t\t\treturn !!this.list\n\t\t},\n\n\t\t/**\n\t\t * Whether the component should fetch via axios. Store mode needs BOTH\n\t\t * `register` and `schema`, so a half-configured pair with a `source` set\n\t\t * still falls back to the URL rather than the empty state.\n\t\t */\n\t\tusesSource() {\n\t\t\treturn !(this.register && this.schema) && !!this.source\n\t\t},\n\n\t\t/** Loading state of the active fetch mode. */\n\t\tloading() {\n\t\t\t// `list` is a bag of refs returned from setup(); only TOP-LEVEL setup\n\t\t\t// refs are auto-unwrapped, so every read below goes through `.value`.\n\t\t\treturn this.list ? this.list.loading.value : this.localLoading\n\t\t},\n\n\t\t/**\n\t\t * Fetch error. `fetchCollection` records failures on the store rather\n\t\t * than throwing, so store mode reads them from there — without this the\n\t\t * `#error` slot never rendered for a store-backed page.\n\t\t */\n\t\terror() {\n\t\t\tif (this.localError) {\n\t\t\t\treturn this.localError\n\t\t\t}\n\t\t\tif (this.list && this.listStore) {\n\t\t\t\treturn this.listStore.errors[this.objectType] || null\n\t\t\t}\n\t\t\treturn null\n\t\t},\n\n\t\t/** Rows to render: the store collection, or locally sorted axios rows. */\n\t\trows() {\n\t\t\tif (this.list) {\n\t\t\t\treturn this.list.objects.value\n\t\t\t}\n\t\t\t// Legacy-store fallback: read the store's collection LIVE rather than\n\t\t\t// the snapshot `fetch()` took. `rows` used to reach the collection\n\t\t\t// through a computed; snapshotting into `localRows` meant a store that\n\t\t\t// REPLACES the array (rather than mutating it in place) no longer\n\t\t\t// reached the table. Only `this.store` is consulted — in this path it is\n\t\t\t// the partial store prop that made setup() bail — so `source` mode,\n\t\t\t// which has no store, still falls through to localRows.\n\t\t\tconst live = (this.register && this.schema && this.store)\n\t\t\t\t? this.store.collections?.[`${this.register}-${this.schema}`]\n\t\t\t\t: null\n\t\t\tconst base = Array.isArray(live) ? live : this.localRows\n\t\t\tif (this.localSortKeys.length === 0) {\n\t\t\t\treturn base\n\t\t\t}\n\t\t\t// CnDataTable is presentational — it never sorts its own rows — so\n\t\t\t// `source` mode has to apply the header sort itself.\n\t\t\treturn multiKeySort(base, this.localSortKeys.map((k) => ({ field: k.key, order: k.order })))\n\t\t},\n\n\t\t/**\n\t\t * Server pagination STATE (`{ total, page, pages, limit }`); null in\n\t\t * source mode, which has no paging. Distinct from the `pagination`\n\t\t * prop, which is the manifest's page-size config.\n\t\t */\n\t\tpaginationState() {\n\t\t\treturn this.list ? this.list.pagination.value : null\n\t\t},\n\n\t\t/**\n\t\t * The LOADED schema object (not the `schema` slug prop), forwarded to\n\t\t * CnDataTable for type-aware cell rendering and schema-derived columns.\n\t\t */\n\t\ttableSchema() {\n\t\t\treturn (this.list && this.list.schema.value) || null\n\t\t},\n\n\t\t/** Active ordered sort keys, from the composable or local source state. */\n\t\teffectiveSortKeys() {\n\t\t\treturn this.list ? this.list.sortKeys.value : this.localSortKeys\n\t\t},\n\n\t\t/** Primary sort key (mirrors `effectiveSortKeys[0]`) for CnDataTable. */\n\t\teffectiveSortKey() {\n\t\t\treturn this.effectiveSortKeys[0]?.key ?? null\n\t\t},\n\n\t\t/** Primary sort direction (mirrors `effectiveSortKeys[0]`). */\n\t\teffectiveSortOrder() {\n\t\t\treturn this.effectiveSortKeys[0]?.order ?? 'asc'\n\t\t},\n\n\t\t/**\n\t\t * Resolved columns. A consumer-provided list wins. Otherwise, when a\n\t\t * schema is loaded, return `[]` so CnDataTable derives the columns from\n\t\t * it — the legacy `timestamp/actor/action/target/details` default matched\n\t\t * no OpenRegister log schema and rendered five blank cells. `source` mode\n\t\t * and a failed schema load still get that legacy default.\n\t\t */\n\t\tresolvedColumns() {\n\t\t\tif (this.columns.length > 0) {\n\t\t\t\treturn this.columns.map((c) => (typeof c === 'string' ? { key: c, label: this.humanise(c) } : c))\n\t\t\t}\n\t\t\tif (this.tableSchema) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\treturn legacyDefaultColumns()\n\t\t},\n\n\t\t/** Column slot names that the parent has provided (for pass-through). */\n\t\tslotColumns() {\n\t\t\treturn Object.keys(this.$slots || {})\n\t\t\t\t.filter((name) => name.startsWith('column-'))\n\t\t\t\t.map((name) => name.replace('column-', ''))\n\t\t},\n\n\t\t/** Dialog heading: the entry's message when it has one, else its id. */\n\t\tdetailTitle() {\n\t\t\tif (!this.detailRow) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst message = this.detailRow.message\n\t\t\tif (typeof message === 'string' && message !== '') {\n\t\t\t\treturn message\n\t\t\t}\n\t\t\tconst id = this.detailRow[this.rowKey]\n\t\t\treturn id ? String(id) : t('nextcloud-vue', 'Log entry')\n\t\t},\n\n\t\t/** The clicked row's primitive fields, as CnDetailGrid items. */\n\t\tdetailScalarItems() {\n\t\t\treturn this.detailEntries\n\t\t\t\t.filter(([, value]) => !this.isBag(value))\n\t\t\t\t.map(([key, value]) => ({ label: this.propertyLabel(key), value: String(value) }))\n\t\t},\n\n\t\t/**\n\t\t * The clicked row's nested fields. A bag of primitives (a stack trace's\n\t\t * frames, an argument map) renders as its own labelled grid; anything\n\t\t * deeper falls back to pretty-printed JSON.\n\t\t */\n\t\tdetailObjectBlocks() {\n\t\t\treturn this.detailEntries\n\t\t\t\t.filter(([, value]) => this.isBag(value))\n\t\t\t\t.map(([key, value]) => {\n\t\t\t\t\tconst entries = Object.entries(value)\n\t\t\t\t\tconst flat = entries.every(([, v]) => v === null || typeof v !== 'object')\n\t\t\t\t\treturn {\n\t\t\t\t\t\tkey,\n\t\t\t\t\t\tlabel: this.propertyLabel(key),\n\t\t\t\t\t\titems: flat ? entries.map(([k, v]) => ({ label: this.humanise(k), value: String(v) })) : null,\n\t\t\t\t\t\tjson: flat ? null : JSON.stringify(value, null, 2),\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Stable signature of the RESOLVED fetch scope — the query deep-link\n\t\t * filters plus the `filter` prop after route-param and `@`-token\n\t\t * resolution. Reading through the same getter the fetch uses is what\n\t\t * makes it exact: it tracks `$route`, `props.filter` and the injected\n\t\t * workspace / app-config bags, and it changes only when the request the\n\t\t * list would issue actually changes. Stringified so the watcher fires on\n\t\t * content, not identity. Empty string in `source` mode (no list).\n\t\t *\n\t\t * @return {string} The serialized filter map.\n\t\t */\n\t\tfilterSignature() {\n\t\t\tif (!this.list) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn JSON.stringify(this.resolveFixedFilters())\n\t\t},\n\n\t\t/** The clicked row's renderable entries — `@self` and empties dropped. */\n\t\tdetailEntries() {\n\t\t\tif (!this.detailRow) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\treturn Object.entries(this.detailRow)\n\t\t\t\t.filter(([key, value]) => key !== '@self' && value !== null && value !== undefined && value !== '')\n\t\t},\n\t},\n\n\twatch: {\n\t\t// ONE watcher over the whole fetch scope, rather than one per reactive\n\t\t// source that feeds it. `fixedFilters` is a plain getter re-read at fetch\n\t\t// time, so a refresh is all any of them needs — but watching them\n\t\t// individually got three things wrong:\n\t\t//\n\t\t// - `$route.query` and `$route.params` both fire on a single navigation,\n\t\t// so one route change issued TWO identical concurrent fetches, which\n\t\t// `fetchCollection` does not dedup and whose responses can land in\n\t\t// either order;\n\t\t// - they fired on navigation AWAY as well, because the route updates\n\t\t// before this component tears down. The refetch was then scoped by the\n\t\t// DESTINATION route's query (`/cases?status=open` → `GET\n\t\t// …/job_log?status=open`), overwriting the shared store collection that\n\t\t// any other mounted widget on the same objectType reads;\n\t\t// - a workspace / app-config bag mutation refetched even when no\n\t\t// `@workspace.`/`@config.` token was in play, so a volatile bag reset a\n\t\t// token-free page to page 1 on every write.\n\t\t//\n\t\t// The signature is the RESOLVED filter map, so it changes only when the\n\t\t// scope actually changes, and the route-name guard drops the fire that\n\t\t// belongs to the page being navigated to. No `immediate` — useListView\n\t\t// owns the initial fetch. This also covers a reactive `filter` prop\n\t\t// change, which had no watcher of its own at all.\n\t\tfilterSignature(next, prev) {\n\t\t\tif (!this.list || next === prev) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\t// Guarded only when this page HAS a route name to compare against —\n\t\t\t// a router-less host, or one whose routes are unnamed, keeps the\n\t\t\t// unguarded behaviour rather than losing refetches to a comparison\n\t\t\t// that can never match.\n\t\t\tif (this._ownRouteName !== undefined && this.$route?.name !== this._ownRouteName) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.list.refresh(1)\n\t\t},\n\n\t\tregister(value, previous) {\n\t\t\tthis.onSourcePropChange('register', value, previous)\n\t\t},\n\n\t\tschema(value, previous) {\n\t\t\tthis.onSourcePropChange('schema', value, previous)\n\t\t},\n\n\t\tsource(value, previous) {\n\t\t\tthis.onSourcePropChange('source', value, previous)\n\t\t},\n\t},\n\n\tcreated() {\n\t\t// The route this page belongs to, captured BEFORE any navigation can\n\t\t// change it — the `filterSignature` watcher's guard. CnPageRenderer mounts\n\t\t// a page when `$route.name === page.id`, so this is the page's own id.\n\t\tthis._ownRouteName = this.$route ? this.$route.name : undefined\n\t},\n\n\tmounted() {\n\t\t// In store mode useListView's own onMounted owns the initial fetch;\n\t\t// calling fetch() here too would double-request on every page load.\n\t\tif (!this.list) {\n\t\t\tthis.fetch()\n\t\t}\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Capitalise + space a snake_case / camelCase key for a default column label.\n\t\t *\n\t\t * @param {string} key A log-entry property name, e.g. `'created_at'` or `'userAgent'`.\n\t\t * @return {string} The sentence-cased label, e.g. `'Created at'` / `'User agent'`.\n\t\t */\n\t\thumanise(key) {\n\t\t\tconst spaced = String(key).replace(/([A-Z])/g, ' $1').replace(/_/g, ' ').toLowerCase()\n\t\t\treturn spaced.charAt(0).toUpperCase() + spaced.slice(1)\n\t\t},\n\n\t\t/**\n\t\t * Label for a row property — the schema's own title when one is loaded.\n\t\t *\n\t\t * @param {string} key The property name.\n\t\t * @return {string} The display label.\n\t\t */\n\t\tpropertyLabel(key) {\n\t\t\tconst title = this.tableSchema?.properties?.[key]?.title\n\t\t\treturn (typeof title === 'string' && title !== '') ? title : this.humanise(key)\n\t\t},\n\n\t\t/**\n\t\t * Whether a value is a nested bag (object/array) rather than a scalar.\n\t\t *\n\t\t * @param {unknown} value The value to test.\n\t\t * @return {boolean} True for non-null objects and arrays.\n\t\t */\n\t\tisBag(value) {\n\t\t\treturn value !== null && typeof value === 'object'\n\t\t},\n\n\t\t/**\n\t\t * Fetch log entries from the resolved data source.\n\t\t *\n\t\t * Only used in `source` mode and in the legacy store fallback — the\n\t\t * composable owns fetching whenever it is active.\n\t\t */\n\t\tasync fetch() {\n\t\t\tif (this.register && this.schema) {\n\t\t\t\t// Legacy fallback: a partial `store` prop that useListView\n\t\t\t\t// cannot drive (see setup). Unfiltered + unpaginated, as before.\n\t\t\t\tconst store = this.store || useObjectStore()\n\t\t\t\tthis.localLoading = true\n\t\t\t\tthis.localError = null\n\t\t\t\ttry {\n\t\t\t\t\tif (typeof store.registerObjectType === 'function') {\n\t\t\t\t\t\tstore.registerObjectType(`${this.register}-${this.schema}`, this.schema, this.register)\n\t\t\t\t\t}\n\t\t\t\t\tif (typeof store.fetchCollection === 'function') {\n\t\t\t\t\t\tawait store.fetchCollection(`${this.register}-${this.schema}`)\n\t\t\t\t\t}\n\t\t\t\t\tthis.localRows = store.collections?.[`${this.register}-${this.schema}`] ?? []\n\t\t\t\t} catch (err) {\n\t\t\t\t\tthis.localError = err\n\t\t\t\t} finally {\n\t\t\t\t\tthis.localLoading = false\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (this.usesSource) {\n\t\t\t\tthis.localLoading = true\n\t\t\t\tthis.localError = null\n\t\t\t\ttry {\n\t\t\t\t\tconst response = await axios.get(prefixUrl(this.source))\n\t\t\t\t\tconst body = response?.data\n\t\t\t\t\tif (Array.isArray(body)) {\n\t\t\t\t\t\tthis.localRows = body\n\t\t\t\t\t} else if (body && Array.isArray(body.results)) {\n\t\t\t\t\t\tthis.localRows = body.results\n\t\t\t\t\t} else if (body && Array.isArray(body.entries)) {\n\t\t\t\t\t\tthis.localRows = body.entries\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthis.localRows = []\n\t\t\t\t\t}\n\t\t\t\t} catch (err) {\n\t\t\t\t\tthis.localError = err\n\t\t\t\t\tthis.localRows = []\n\t\t\t\t} finally {\n\t\t\t\t\tthis.localLoading = false\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\t// Misconfigured — surface a console warning so a developer notices.\n\t\t\t// eslint-disable-next-line no-console\n\t\t\tconsole.warn('[CnLogsPage] Neither register+schema nor source configured; rendering empty state.')\n\t\t},\n\n\t\t/**\n\t\t * React to a data-source prop changing on a mounted page.\n\t\t *\n\t\t * Only the legacy / `source` paths can act on it, by refetching. Store\n\t\t * mode cannot: the object type, its store registration and the\n\t\t * `useListView` instance are all built once in `setup()` from the\n\t\t * initial pair, so a new `register`/`schema` needs a remount to take\n\t\t * effect (which CnPageRenderer does on a manifest page swap). That is\n\t\t * documented on the props, but it used to happen in complete silence —\n\t\t * a direct consumer rebinding the prop saw the old collection and no\n\t\t * indication why. Warns in development only, once per change.\n\t\t *\n\t\t * @param {string} name The prop that changed.\n\t\t * @param {unknown} value The new value.\n\t\t * @param {unknown} previous The previous value.\n\t\t * @return {void}\n\t\t */\n\t\tonSourcePropChange(name, value, previous) {\n\t\t\tif (!this.list) {\n\t\t\t\tthis.fetch()\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (process.env.NODE_ENV !== 'production') {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.warn(`[CnLogsPage] \\`${name}\\` changed from \"${previous}\" to \"${value}\" on a mounted store-backed page. `\n\t\t\t\t\t+ 'The data source is bound once at setup, so this has no effect — remount the page (e.g. give it a '\n\t\t\t\t\t+ ':key) to pick up the new register/schema.')\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Apply a header sort. Server-side in store mode, client-side otherwise.\n\t\t *\n\t\t * @param {{key: string|null, order: string|null, keys?: Array<{key: string, order: string}>}} sort CnDataTable's sort payload.\n\t\t */\n\t\tonSort(sort) {\n\t\t\tif (this.list) {\n\t\t\t\tthis.list.onSort(sort)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.localSortKeys = Array.isArray(sort.keys)\n\t\t\t\t? sort.keys\n\t\t\t\t: (sort.key ? [{ key: sort.key, order: sort.order || 'asc' }] : [])\n\t\t},\n\n\t\t/**\n\t\t * Navigate to a page of results.\n\t\t *\n\t\t * @param {number} page The 1-based page number.\n\t\t */\n\t\tonPageChange(page) {\n\t\t\tthis.list?.onPageChange(page)\n\t\t},\n\n\t\t/**\n\t\t * Change the page size, returning to page 1.\n\t\t *\n\t\t * @param {number} size The new page size.\n\t\t */\n\t\tonPageSizeChange(size) {\n\t\t\tthis.list?.onPageSizeChange(size)\n\t\t},\n\n\t\t/**\n\t\t * Handle a row-body click: navigate to `rowRoute` when one is declared,\n\t\t * else open the detail dialog when `rowDetail` is set. Always re-emits\n\t\t * so a host can do its own thing regardless. A ctrl/cmd/shift click\n\t\t * opens `rowRoute` in a new tab.\n\t\t *\n\t\t * @param {object} row The clicked log entry.\n\t\t * @param {MouseEvent} [event] The originating click event.\n\t\t */\n\t\tonRowClick(row, event) {\n\t\t\tif (this.rowRoute) {\n\t\t\t\tmarkNewTabHandled(event, openRowTarget(event, { name: this.rowRoute, params: { id: row?.[this.rowKey] } }, this.$router))\n\t\t\t} else if (this.rowDetail) {\n\t\t\t\tthis.detailRow = row\n\t\t\t}\n\t\t\t/**\n\t\t\t * @event row-click Emitted when a log row's body is clicked. Payload: `(row, event)` — the log entry and the native click event.\n\t\t\t * @type {object}\n\t\t\t */\n\t\t\tthis.$emit('row-click', row, event)\n\t\t},\n\n\t\t/**\n\t\t * Handle a row-body middle click: open `rowRoute` in a new tab, never\n\t\t * the detail dialog, and emit `row-aux-click` rather than `row-click`.\n\t\t *\n\t\t * @param {object} row The clicked log entry.\n\t\t * @param {MouseEvent} event The originating auxclick event.\n\t\t */\n\t\tonRowAuxClick(row, event) {\n\t\t\tif (this.rowRoute) {\n\t\t\t\tmarkNewTabHandled(event, openRowTarget(event, { name: this.rowRoute, params: { id: row?.[this.rowKey] } }, this.$router))\n\t\t\t}\n\t\t\t/**\n\t\t\t * @event row-aux-click Emitted when a log row's body is middle-clicked. Payload: `(row, event)` — the log entry and the native auxclick event.\n\t\t\t * @type {object}\n\t\t\t */\n\t\t\tthis.$emit('row-aux-click', row, event)\n\t\t},\n\n\t\t/** Close the row-detail dialog. */\n\t\tcloseDetail() {\n\t\t\tthis.detailRow = null\n\t\t},\n\n\t\t/**\n\t\t * Re-fetch from the source. Exposed so refresh buttons in\n\t\t * actionsComponent can call `$parent.refresh()`.\n\t\t *\n\t\t * @public\n\t\t */\n\t\trefresh() {\n\t\t\tif (this.list) {\n\t\t\t\tthis.list.refresh(1)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.fetch()\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n/* Padding matches CnIndexPage / CnDetailPage (see css/index-page.css and\n css/detail-page.css) — a logs page had none at all, so its table ran flush\n into both edges of the app content area. Gaps are expressed in the same\n grid-baseline unit as the rest of the fleet rather than raw pixels. */\n.cn-logs-page {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: calc(4 * var(--default-grid-baseline));\n\tpadding: calc(5 * var(--default-grid-baseline));\n\tbox-sizing: border-box;\n}\n\n.cn-logs-page__actions {\n\tdisplay: flex;\n\tjustify-content: flex-end;\n\tgap: calc(2 * var(--default-grid-baseline));\n}\n\n.cn-logs-page__loading,\n.cn-logs-page__empty,\n.cn-logs-page__error {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\tmin-height: 200px;\n}\n\n.cn-logs-page__pagination {\n\tmargin-top: calc(3 * var(--default-grid-baseline));\n}\n\n.cn-logs-page__detail {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: calc(2 * var(--default-grid-baseline));\n\tpadding: 0 calc(3 * var(--default-grid-baseline)) calc(3 * var(--default-grid-baseline));\n}\n\n.cn-logs-page__detail-heading {\n\tmargin: calc(3 * var(--default-grid-baseline)) 0 0;\n\tfont-weight: bold;\n}\n\n.cn-logs-page__detail-json {\n\tmargin: 0;\n\tpadding: calc(2 * var(--default-grid-baseline));\n\toverflow-x: auto;\n\tborder-radius: var(--border-radius);\n\tbackground-color: var(--color-background-dark);\n\tfont-family: monospace;\n\twhite-space: pre;\n}\n\n/* Same breakpoint + step-down as CnDetailPage, so the three page types stay in\n sync on narrow viewports. */\n@media (max-width: 768px) {\n\t.cn-logs-page {\n\t\tpadding: calc(3 * var(--default-grid-baseline));\n\t}\n}\n</style>\n"],"names":["_renderSlot","_openBlock","_createElementBlock","_createVNode","_Fragment","_renderList","_withCtx","_createElementVNode"],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  import { NcCheckboxRadioSwitch } from '@nextcloud/vue';
2
2
  import { useClickDragGuard } from '../../composables/useClickDragGuard.js';
3
3
  import { resolveImageUrl } from '../../utils/resolveImageUrl.js';
4
- import { isRowMiddleClick } from '../../utils/rowAuxClick.js';
4
+ import { preventMiddleClickAutoscroll, isRowMiddleClick } from '../../utils/rowAuxClick.js';
5
5
  import { formatValue } from '../../utils/schema.js';
6
6
  import __sfc_main from '../CnCellRenderer/CnCellRenderer.vue.js';
7
7
  import __sfc_main$1 from '../CnLockIndicator/CnLockIndicator.vue.js';
@@ -86,7 +86,7 @@ var script = {
86
86
  },
87
87
  },
88
88
 
89
- emits: ['click', 'select'],
89
+ emits: ['click', 'select', 'aux-click'],
90
90
 
91
91
  setup() {
92
92
  // Tell a deliberate card click apart from a text-selection drag.
@@ -212,14 +212,28 @@ var script = {
212
212
  return
213
213
  }
214
214
  /**
215
- * @event click Emitted when a non-selectable (or `clickToView`) card is clicked or middle-clicked. Payload: `(object, event)` — the card's object and the native click/auxclick event, for opening it in a new tab on a ctrl/cmd/shift or middle click. Selectable cards emit `select`; they also emit `click` (deprecated) when a `click` listener is present, so migrate selectable consumers to `@select`.
215
+ * @event click Emitted when a non-selectable (or `clickToView`) card is clicked. Payload: `(object, event)` — the card's object and the native click event, for opening it in a new tab on a ctrl/cmd/shift click. A middle click emits `aux-click` instead. Selectable cards emit `select`; they also emit `click` (deprecated) when a `click` listener is present, so migrate selectable consumers to `@select`.
216
216
  * @type {object} The card's object.
217
217
  */
218
218
  this.$emit('click', this.object, event);
219
219
  },
220
220
 
221
221
  /**
222
- * Card-body middle click: emits `click` like a navigating click.
222
+ * Card mousedown: record the press for the drag guard, and on a card a
223
+ * middle click opens, keep the browser from starting autoscroll.
224
+ *
225
+ * @param {MouseEvent} event The mousedown event.
226
+ */
227
+ onCardMouseDown(event) {
228
+ this.onPointerDown(event);
229
+ if (!this.selectable || this.clickToView) {
230
+ preventMiddleClickAutoscroll(event);
231
+ }
232
+ },
233
+
234
+ /**
235
+ * Card-body middle click: emits `aux-click`, not `click`, so an existing
236
+ * `click` listener that navigates never moves the current tab away.
223
237
  * Ignored on a select-on-click card, on nested controls and on drags.
224
238
  *
225
239
  * @param {MouseEvent} event The auxclick event.
@@ -231,7 +245,11 @@ var script = {
231
245
  if (this.selectable && !this.clickToView) {
232
246
  return
233
247
  }
234
- this.$emit('click', this.object, event);
248
+ /**
249
+ * @event aux-click Emitted when a non-selectable (or `clickToView`) card is middle-clicked, for opening it in a new tab (see `openRowTarget`). Payload: `(object, event)` — the card's object and the native auxclick event.
250
+ * @type {object} The card's object.
251
+ */
252
+ this.$emit('aux-click', this.object, event);
235
253
  },
236
254
  },
237
255
  };
@@ -1 +1 @@
1
- {"version":3,"file":"CnObjectCard.vue2.js","sources":["../../../../src/components/CnObjectCard/CnObjectCard.vue"],"sourcesContent":["<template>\n\t<div\n\t\tclass=\"cn-object-card\"\n\t\t:class=\"{ 'cn-object-card--selected': selected }\"\n\t\t@mousedown=\"onPointerDown\"\n\t\t@click=\"onCardClick($event)\"\n\t\t@auxclick=\"onCardAuxClick($event)\">\n\t\t<!-- Selection checkbox -->\n\t\t<div v-if=\"selectable\"\n\t\t\tclass=\"cn-object-card__checkbox\"\n\t\t\t@click.stop\n\t\t\t@auxclick.stop>\n\t\t\t<NcCheckboxRadioSwitch\n\t\t\t\t:modelValue=\"selected\"\n\t\t\t\t@update:modelValue=\"$emit('select', object)\" />\n\t\t</div>\n\n\t\t<!-- Card content -->\n\t\t<div class=\"cn-object-card__content\">\n\t\t\t<!-- Header: image + title -->\n\t\t\t<div class=\"cn-object-card__header\">\n\t\t\t\t<img\n\t\t\t\t\tv-if=\"imageUrl\"\n\t\t\t\t\t:src=\"imageUrl\"\n\t\t\t\t\t:alt=\"title\"\n\t\t\t\t\tclass=\"cn-object-card__image\">\n\n\t\t\t\t<div class=\"cn-object-card__title-area\">\n\t\t\t\t\t<h3 class=\"cn-object-card__title\">\n\t\t\t\t\t\t<!-- Beside the title, not in the badges row: the badges\n\t\t\t\t\t\t slot is the consumer's, and a lock is the library's\n\t\t\t\t\t\t to report. A card whose consumer passes no badges\n\t\t\t\t\t\t would otherwise show no padlock at all. -->\n\t\t\t\t\t\t<CnLockIndicator :object=\"object\" :size=\"16\" />\n\t\t\t\t\t\t{{ title }}\n\t\t\t\t\t</h3>\n\t\t\t\t\t<p v-if=\"description\" class=\"cn-object-card__description\">\n\t\t\t\t\t\t{{ truncatedDescription }}\n\t\t\t\t\t</p>\n\t\t\t\t</div>\n\t\t\t</div>\n\n\t\t\t<!-- Badges slot -->\n\t\t\t<div v-if=\"$slots.badges\" class=\"cn-object-card__badges\">\n\t\t\t\t<slot name=\"badges\" :object=\"object\" />\n\t\t\t</div>\n\n\t\t\t<!-- Metadata: visible properties as label:value pairs -->\n\t\t\t<div v-if=\"metadataFields.length > 0\" class=\"cn-object-card__metadata\">\n\t\t\t\t<slot name=\"metadata\" :object=\"object\" :fields=\"metadataFields\">\n\t\t\t\t\t<div\n\t\t\t\t\t\tv-for=\"field in metadataFields\"\n\t\t\t\t\t\t:key=\"field.key\"\n\t\t\t\t\t\tclass=\"cn-object-card__meta-item\">\n\t\t\t\t\t\t<span class=\"cn-object-card__meta-label\">{{ field.label }}</span>\n\t\t\t\t\t\t<CnCellRenderer\n\t\t\t\t\t\t\t:value=\"field.value\"\n\t\t\t\t\t\t\t:property=\"field.property\"\n\t\t\t\t\t\t\t:truncate=\"60\" />\n\t\t\t\t\t</div>\n\t\t\t\t</slot>\n\t\t\t</div>\n\t\t</div>\n\n\t\t<!-- Actions slot -->\n\t\t<div v-if=\"$slots.actions\"\n\t\t\tclass=\"cn-object-card__actions\"\n\t\t\t@click.stop\n\t\t\t@auxclick.stop>\n\t\t\t<slot name=\"actions\" :object=\"object\" />\n\t\t</div>\n\t</div>\n</template>\n\n<script>\nimport { NcCheckboxRadioSwitch } from '@nextcloud/vue'\nimport { useClickDragGuard } from '../../composables/useClickDragGuard.js'\nimport { resolveImageUrl } from '../../utils/resolveImageUrl.js'\nimport { isRowMiddleClick } from '../../utils/rowAuxClick.js'\nimport { formatValue } from '../../utils/schema.js'\nimport { CnCellRenderer } from '../CnCellRenderer/index.js'\nimport { CnLockIndicator } from '../CnLockIndicator/index.js'\n\n/**\n * CnObjectCard — Schema-configuration-driven card for object display.\n *\n * Uses `schema.configuration` to determine which fields map to the card title,\n * description, and image. Remaining visible properties are shown as metadata.\n *\n * ```vue\n * <CnObjectCard :object=\"publication\" :schema=\"pubSchema\">\n * <template #actions=\"{ object }\">\n * <NcActions><NcActionButton @click=\"edit(object)\">Edit</NcActionButton></NcActions>\n * </template>\n * </CnObjectCard>\n * ```\n */\nexport default {\n\tname: 'CnObjectCard',\n\n\tcomponents: {\n\t\tNcCheckboxRadioSwitch,\n\t\tCnCellRenderer,\n\t\tCnLockIndicator,\n\t},\n\n\tinject: {\n\t\t/**\n\t\t * Consumer translation function, provided by CnAppRoot as\n\t\t * `cnTranslate: this.translate` (bound to the host app's id). Metadata\n\t\t * labels come from schema property titles, authored in English as the\n\t\t * canonical source; the visible label is resolved through this function\n\t\t * so it follows the user's language. Defaults to identity when used\n\t\t * standalone (no CnAppRoot ancestor).\n\t\t */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/** The object data */\n\t\tobject: {\n\t\t\ttype: Object,\n\t\t\trequired: true,\n\t\t},\n\n\t\t/** Schema definition with properties and configuration */\n\t\tschema: {\n\t\t\ttype: Object,\n\t\t\trequired: true,\n\t\t},\n\n\t\t/** Whether this card is selected */\n\t\tselected: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Whether to show selection checkbox */\n\t\tselectable: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * When true, a body click on a SELECTABLE card emits `click`\n\t\t * (navigation) instead of toggling selection — selection then happens\n\t\t * via the checkbox only. The card counterpart of CnIndexPage's\n\t\t * `rowClickToView` (a table row click already navigates in that mode).\n\t\t *\n\t\t * @type {boolean}\n\t\t */\n\t\tclickToView: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Maximum number of metadata fields to show */\n\t\tmaxMetadata: {\n\t\t\ttype: Number,\n\t\t\tdefault: 4,\n\t\t},\n\t},\n\n\temits: ['click', 'select'],\n\n\tsetup() {\n\t\t// Tell a deliberate card click apart from a text-selection drag.\n\t\treturn useClickDragGuard()\n\t},\n\n\tcomputed: {\n\t\tconfig() {\n\t\t\treturn this.schema?.configuration || {}\n\t\t},\n\n\t\ttitle() {\n\t\t\tconst field = this.config.objectNameField\n\t\t\tif (field && this.object[field]) {\n\t\t\t\treturn String(this.object[field])\n\t\t\t}\n\t\t\treturn this.object.title || this.object.name || this.object.id || '—'\n\t\t},\n\n\t\tdescription() {\n\t\t\tconst field = this.config.objectDescriptionField\n\t\t\tif (field && this.object[field]) {\n\t\t\t\treturn String(this.object[field])\n\t\t\t}\n\t\t\treturn null\n\t\t},\n\n\t\ttruncatedDescription() {\n\t\t\tif (!this.description) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tif (this.description.length > 120) {\n\t\t\t\treturn this.description.substring(0, 120) + '...'\n\t\t\t}\n\t\t\treturn this.description\n\t\t},\n\n\t\timageUrl() {\n\t\t\tconst field = this.config.objectImageField\n\t\t\tif (field && this.object[field]) {\n\t\t\t\treturn resolveImageUrl(this.object[field])\n\t\t\t}\n\t\t\treturn null\n\t\t},\n\n\t\t/** Fields excluded from metadata (already shown as title/desc/image) */\n\t\tconfigFields() {\n\t\t\treturn [\n\t\t\t\tthis.config.objectNameField,\n\t\t\t\tthis.config.objectDescriptionField,\n\t\t\t\tthis.config.objectSummaryField,\n\t\t\t\tthis.config.objectImageField,\n\t\t\t].filter(Boolean)\n\t\t},\n\n\t\t/** Remaining visible properties for the metadata section */\n\t\tmetadataFields() {\n\t\t\tif (!this.schema?.properties) {\n\t\t\t\treturn []\n\t\t\t}\n\n\t\t\treturn Object.entries(this.schema.properties)\n\t\t\t\t.filter(([key, prop]) => {\n\t\t\t\t\tif (this.configFields.includes(key)) {\n\t\t\t\t\t\treturn false\n\t\t\t\t\t}\n\t\t\t\t\tif (prop.visible === false) {\n\t\t\t\t\t\treturn false\n\t\t\t\t\t}\n\t\t\t\t\tif (prop.type === 'object') {\n\t\t\t\t\t\treturn false\n\t\t\t\t\t}\n\t\t\t\t\tif (prop.format === 'markdown') {\n\t\t\t\t\t\treturn false\n\t\t\t\t\t}\n\t\t\t\t\treturn true\n\t\t\t\t})\n\t\t\t\t.sort(([, a], [, b]) => {\n\t\t\t\t\tconst orderA = typeof a.order === 'number' ? a.order : Infinity\n\t\t\t\t\tconst orderB = typeof b.order === 'number' ? b.order : Infinity\n\t\t\t\t\treturn orderA - orderB\n\t\t\t\t})\n\t\t\t\t.slice(0, this.maxMetadata)\n\t\t\t\t.map(([key, prop]) => ({\n\t\t\t\t\tkey,\n\t\t\t\t\tlabel: this.cnTranslate(prop.title || key),\n\t\t\t\t\tvalue: this.object[key],\n\t\t\t\t\tproperty: prop,\n\t\t\t\t}))\n\t\t},\n\t},\n\n\tmethods: {\n\t\tformatValue,\n\n\t\t/**\n\t\t * Card-body click: emits `select` when `selectable` (ignoring drags),\n\t\t * otherwise emits `click` for navigation. With `clickToView`, a\n\t\t * selectable card's body click navigates too — the checkbox is the\n\t\t * only selection surface, mirroring the table's rowClickToView split.\n\t\t *\n\t\t * @param {MouseEvent} [event] The originating click event.\n\t\t */\n\t\tonCardClick(event) {\n\t\t\tif (this.selectable && !this.clickToView) {\n\t\t\t\tif (this.wasDrag(event)) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\t/**\n\t\t\t\t * @event select Emitted when the card toggles selection (clicking the body of a selectable card, or its checkbox).\n\t\t\t\t * @type {object} The card's object.\n\t\t\t\t */\n\t\t\t\tthis.$emit('select', this.object)\n\t\t\t\t// Deprecation: selectable cards used to emit `click`. Keep emitting it\n\t\t\t\t// for listeners that still rely on it, but warn them to migrate to `select`.\n\t\t\t\t// `$.vnode.props`, not `$attrs`: `click` is a declared emit, and\n\t\t\t\t// Vue keeps declared emits out of `$attrs`.\n\t\t\t\tif (this.$.vnode.props?.onClick) {\n\t\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\t\tconsole.warn('[CnObjectCard] @click on selectable cards is deprecated; use @select instead.')\n\t\t\t\t\tthis.$emit('click', this.object)\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\t/**\n\t\t\t * @event click Emitted when a non-selectable (or `clickToView`) card is clicked or middle-clicked. Payload: `(object, event)` — the card's object and the native click/auxclick event, for opening it in a new tab on a ctrl/cmd/shift or middle click. Selectable cards emit `select`; they also emit `click` (deprecated) when a `click` listener is present, so migrate selectable consumers to `@select`.\n\t\t\t * @type {object} The card's object.\n\t\t\t */\n\t\t\tthis.$emit('click', this.object, event)\n\t\t},\n\n\t\t/**\n\t\t * Card-body middle click: emits `click` like a navigating click.\n\t\t * Ignored on a select-on-click card, on nested controls and on drags.\n\t\t *\n\t\t * @param {MouseEvent} event The auxclick event.\n\t\t */\n\t\tonCardAuxClick(event) {\n\t\t\tif (!isRowMiddleClick(event) || this.wasDrag(event)) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (this.selectable && !this.clickToView) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.$emit('click', this.object, event)\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-object-card {\n\tdisplay: flex;\n\tgap: 12px;\n\tpadding: 16px;\n\tbackground: var(--color-main-background);\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius-large, 10px);\n\tcursor: pointer;\n\ttransition: box-shadow 0.2s ease, border-color 0.2s ease;\n}\n\n.cn-object-card:hover {\n\tborder-color: var(--color-primary-element);\n\tbox-shadow: 0 2px 8px var(--color-box-shadow);\n}\n\n.cn-object-card--selected {\n\tborder-color: var(--color-primary-element);\n\tbackground: var(--color-primary-element-light);\n}\n\n.cn-object-card__checkbox {\n\tflex-shrink: 0;\n\tpadding-top: 2px;\n}\n\n.cn-object-card__content {\n\tflex: 1;\n\tmin-width: 0;\n}\n\n.cn-object-card__header {\n\tdisplay: flex;\n\tgap: 12px;\n\talign-items: flex-start;\n}\n\n.cn-object-card__image {\n\twidth: 48px;\n\theight: 48px;\n\tborder-radius: var(--border-radius);\n\tobject-fit: cover;\n\tflex-shrink: 0;\n}\n\n.cn-object-card__title-area {\n\tflex: 1;\n\tmin-width: 0;\n}\n\n.cn-object-card__title {\n\tmargin: 0;\n\tfont-size: 16px;\n\tfont-weight: 600;\n\tline-height: 1.3;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-object-card__description {\n\tmargin: 4px 0 0;\n\tfont-size: 13px;\n\tcolor: var(--color-text-maxcontrast);\n\tline-height: 1.4;\n}\n\n.cn-object-card__badges {\n\tmargin-top: 8px;\n}\n\n.cn-object-card__metadata {\n\tdisplay: grid;\n\tgrid-template-columns: repeat(auto-fill, minmax(160px, 1fr));\n\tgap: 8px;\n\tmargin-top: 12px;\n\tpadding-top: 12px;\n\tborder-top: 1px solid var(--color-border);\n}\n\n.cn-object-card__meta-item {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n}\n\n.cn-object-card__meta-label {\n\tfont-size: 11px;\n\tfont-weight: 500;\n\tcolor: var(--color-text-maxcontrast);\n\ttext-transform: uppercase;\n\tletter-spacing: 0.3px;\n}\n\n.cn-object-card__actions {\n\tflex-shrink: 0;\n}\n</style>\n"],"names":["CnCellRenderer","CnLockIndicator"],"mappings":"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"}
1
+ {"version":3,"file":"CnObjectCard.vue2.js","sources":["../../../../src/components/CnObjectCard/CnObjectCard.vue"],"sourcesContent":["<template>\n\t<div\n\t\tclass=\"cn-object-card\"\n\t\t:class=\"{ 'cn-object-card--selected': selected }\"\n\t\t@mousedown=\"onCardMouseDown\"\n\t\t@click=\"onCardClick($event)\"\n\t\t@auxclick=\"onCardAuxClick($event)\">\n\t\t<!-- Selection checkbox -->\n\t\t<div v-if=\"selectable\"\n\t\t\tclass=\"cn-object-card__checkbox\"\n\t\t\t@click.stop\n\t\t\t@auxclick.stop>\n\t\t\t<NcCheckboxRadioSwitch\n\t\t\t\t:modelValue=\"selected\"\n\t\t\t\t@update:modelValue=\"$emit('select', object)\" />\n\t\t</div>\n\n\t\t<!-- Card content -->\n\t\t<div class=\"cn-object-card__content\">\n\t\t\t<!-- Header: image + title -->\n\t\t\t<div class=\"cn-object-card__header\">\n\t\t\t\t<img\n\t\t\t\t\tv-if=\"imageUrl\"\n\t\t\t\t\t:src=\"imageUrl\"\n\t\t\t\t\t:alt=\"title\"\n\t\t\t\t\tclass=\"cn-object-card__image\">\n\n\t\t\t\t<div class=\"cn-object-card__title-area\">\n\t\t\t\t\t<h3 class=\"cn-object-card__title\">\n\t\t\t\t\t\t<!-- Beside the title, not in the badges row: the badges\n\t\t\t\t\t\t slot is the consumer's, and a lock is the library's\n\t\t\t\t\t\t to report. A card whose consumer passes no badges\n\t\t\t\t\t\t would otherwise show no padlock at all. -->\n\t\t\t\t\t\t<CnLockIndicator :object=\"object\" :size=\"16\" />\n\t\t\t\t\t\t{{ title }}\n\t\t\t\t\t</h3>\n\t\t\t\t\t<p v-if=\"description\" class=\"cn-object-card__description\">\n\t\t\t\t\t\t{{ truncatedDescription }}\n\t\t\t\t\t</p>\n\t\t\t\t</div>\n\t\t\t</div>\n\n\t\t\t<!-- Badges slot -->\n\t\t\t<div v-if=\"$slots.badges\" class=\"cn-object-card__badges\">\n\t\t\t\t<slot name=\"badges\" :object=\"object\" />\n\t\t\t</div>\n\n\t\t\t<!-- Metadata: visible properties as label:value pairs -->\n\t\t\t<div v-if=\"metadataFields.length > 0\" class=\"cn-object-card__metadata\">\n\t\t\t\t<slot name=\"metadata\" :object=\"object\" :fields=\"metadataFields\">\n\t\t\t\t\t<div\n\t\t\t\t\t\tv-for=\"field in metadataFields\"\n\t\t\t\t\t\t:key=\"field.key\"\n\t\t\t\t\t\tclass=\"cn-object-card__meta-item\">\n\t\t\t\t\t\t<span class=\"cn-object-card__meta-label\">{{ field.label }}</span>\n\t\t\t\t\t\t<CnCellRenderer\n\t\t\t\t\t\t\t:value=\"field.value\"\n\t\t\t\t\t\t\t:property=\"field.property\"\n\t\t\t\t\t\t\t:truncate=\"60\" />\n\t\t\t\t\t</div>\n\t\t\t\t</slot>\n\t\t\t</div>\n\t\t</div>\n\n\t\t<!-- Actions slot -->\n\t\t<div v-if=\"$slots.actions\"\n\t\t\tclass=\"cn-object-card__actions\"\n\t\t\t@click.stop\n\t\t\t@auxclick.stop>\n\t\t\t<slot name=\"actions\" :object=\"object\" />\n\t\t</div>\n\t</div>\n</template>\n\n<script>\nimport { NcCheckboxRadioSwitch } from '@nextcloud/vue'\nimport { useClickDragGuard } from '../../composables/useClickDragGuard.js'\nimport { resolveImageUrl } from '../../utils/resolveImageUrl.js'\nimport { isRowMiddleClick, preventMiddleClickAutoscroll } from '../../utils/rowAuxClick.js'\nimport { formatValue } from '../../utils/schema.js'\nimport { CnCellRenderer } from '../CnCellRenderer/index.js'\nimport { CnLockIndicator } from '../CnLockIndicator/index.js'\n\n/**\n * CnObjectCard — Schema-configuration-driven card for object display.\n *\n * Uses `schema.configuration` to determine which fields map to the card title,\n * description, and image. Remaining visible properties are shown as metadata.\n *\n * ```vue\n * <CnObjectCard :object=\"publication\" :schema=\"pubSchema\">\n * <template #actions=\"{ object }\">\n * <NcActions><NcActionButton @click=\"edit(object)\">Edit</NcActionButton></NcActions>\n * </template>\n * </CnObjectCard>\n * ```\n */\nexport default {\n\tname: 'CnObjectCard',\n\n\tcomponents: {\n\t\tNcCheckboxRadioSwitch,\n\t\tCnCellRenderer,\n\t\tCnLockIndicator,\n\t},\n\n\tinject: {\n\t\t/**\n\t\t * Consumer translation function, provided by CnAppRoot as\n\t\t * `cnTranslate: this.translate` (bound to the host app's id). Metadata\n\t\t * labels come from schema property titles, authored in English as the\n\t\t * canonical source; the visible label is resolved through this function\n\t\t * so it follows the user's language. Defaults to identity when used\n\t\t * standalone (no CnAppRoot ancestor).\n\t\t */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/** The object data */\n\t\tobject: {\n\t\t\ttype: Object,\n\t\t\trequired: true,\n\t\t},\n\n\t\t/** Schema definition with properties and configuration */\n\t\tschema: {\n\t\t\ttype: Object,\n\t\t\trequired: true,\n\t\t},\n\n\t\t/** Whether this card is selected */\n\t\tselected: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Whether to show selection checkbox */\n\t\tselectable: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * When true, a body click on a SELECTABLE card emits `click`\n\t\t * (navigation) instead of toggling selection — selection then happens\n\t\t * via the checkbox only. The card counterpart of CnIndexPage's\n\t\t * `rowClickToView` (a table row click already navigates in that mode).\n\t\t *\n\t\t * @type {boolean}\n\t\t */\n\t\tclickToView: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Maximum number of metadata fields to show */\n\t\tmaxMetadata: {\n\t\t\ttype: Number,\n\t\t\tdefault: 4,\n\t\t},\n\t},\n\n\temits: ['click', 'select', 'aux-click'],\n\n\tsetup() {\n\t\t// Tell a deliberate card click apart from a text-selection drag.\n\t\treturn useClickDragGuard()\n\t},\n\n\tcomputed: {\n\t\tconfig() {\n\t\t\treturn this.schema?.configuration || {}\n\t\t},\n\n\t\ttitle() {\n\t\t\tconst field = this.config.objectNameField\n\t\t\tif (field && this.object[field]) {\n\t\t\t\treturn String(this.object[field])\n\t\t\t}\n\t\t\treturn this.object.title || this.object.name || this.object.id || '—'\n\t\t},\n\n\t\tdescription() {\n\t\t\tconst field = this.config.objectDescriptionField\n\t\t\tif (field && this.object[field]) {\n\t\t\t\treturn String(this.object[field])\n\t\t\t}\n\t\t\treturn null\n\t\t},\n\n\t\ttruncatedDescription() {\n\t\t\tif (!this.description) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tif (this.description.length > 120) {\n\t\t\t\treturn this.description.substring(0, 120) + '...'\n\t\t\t}\n\t\t\treturn this.description\n\t\t},\n\n\t\timageUrl() {\n\t\t\tconst field = this.config.objectImageField\n\t\t\tif (field && this.object[field]) {\n\t\t\t\treturn resolveImageUrl(this.object[field])\n\t\t\t}\n\t\t\treturn null\n\t\t},\n\n\t\t/** Fields excluded from metadata (already shown as title/desc/image) */\n\t\tconfigFields() {\n\t\t\treturn [\n\t\t\t\tthis.config.objectNameField,\n\t\t\t\tthis.config.objectDescriptionField,\n\t\t\t\tthis.config.objectSummaryField,\n\t\t\t\tthis.config.objectImageField,\n\t\t\t].filter(Boolean)\n\t\t},\n\n\t\t/** Remaining visible properties for the metadata section */\n\t\tmetadataFields() {\n\t\t\tif (!this.schema?.properties) {\n\t\t\t\treturn []\n\t\t\t}\n\n\t\t\treturn Object.entries(this.schema.properties)\n\t\t\t\t.filter(([key, prop]) => {\n\t\t\t\t\tif (this.configFields.includes(key)) {\n\t\t\t\t\t\treturn false\n\t\t\t\t\t}\n\t\t\t\t\tif (prop.visible === false) {\n\t\t\t\t\t\treturn false\n\t\t\t\t\t}\n\t\t\t\t\tif (prop.type === 'object') {\n\t\t\t\t\t\treturn false\n\t\t\t\t\t}\n\t\t\t\t\tif (prop.format === 'markdown') {\n\t\t\t\t\t\treturn false\n\t\t\t\t\t}\n\t\t\t\t\treturn true\n\t\t\t\t})\n\t\t\t\t.sort(([, a], [, b]) => {\n\t\t\t\t\tconst orderA = typeof a.order === 'number' ? a.order : Infinity\n\t\t\t\t\tconst orderB = typeof b.order === 'number' ? b.order : Infinity\n\t\t\t\t\treturn orderA - orderB\n\t\t\t\t})\n\t\t\t\t.slice(0, this.maxMetadata)\n\t\t\t\t.map(([key, prop]) => ({\n\t\t\t\t\tkey,\n\t\t\t\t\tlabel: this.cnTranslate(prop.title || key),\n\t\t\t\t\tvalue: this.object[key],\n\t\t\t\t\tproperty: prop,\n\t\t\t\t}))\n\t\t},\n\t},\n\n\tmethods: {\n\t\tformatValue,\n\n\t\t/**\n\t\t * Card-body click: emits `select` when `selectable` (ignoring drags),\n\t\t * otherwise emits `click` for navigation. With `clickToView`, a\n\t\t * selectable card's body click navigates too — the checkbox is the\n\t\t * only selection surface, mirroring the table's rowClickToView split.\n\t\t *\n\t\t * @param {MouseEvent} [event] The originating click event.\n\t\t */\n\t\tonCardClick(event) {\n\t\t\tif (this.selectable && !this.clickToView) {\n\t\t\t\tif (this.wasDrag(event)) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\t/**\n\t\t\t\t * @event select Emitted when the card toggles selection (clicking the body of a selectable card, or its checkbox).\n\t\t\t\t * @type {object} The card's object.\n\t\t\t\t */\n\t\t\t\tthis.$emit('select', this.object)\n\t\t\t\t// Deprecation: selectable cards used to emit `click`. Keep emitting it\n\t\t\t\t// for listeners that still rely on it, but warn them to migrate to `select`.\n\t\t\t\t// `$.vnode.props`, not `$attrs`: `click` is a declared emit, and\n\t\t\t\t// Vue keeps declared emits out of `$attrs`.\n\t\t\t\tif (this.$.vnode.props?.onClick) {\n\t\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\t\tconsole.warn('[CnObjectCard] @click on selectable cards is deprecated; use @select instead.')\n\t\t\t\t\tthis.$emit('click', this.object)\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\t/**\n\t\t\t * @event click Emitted when a non-selectable (or `clickToView`) card is clicked. Payload: `(object, event)` — the card's object and the native click event, for opening it in a new tab on a ctrl/cmd/shift click. A middle click emits `aux-click` instead. Selectable cards emit `select`; they also emit `click` (deprecated) when a `click` listener is present, so migrate selectable consumers to `@select`.\n\t\t\t * @type {object} The card's object.\n\t\t\t */\n\t\t\tthis.$emit('click', this.object, event)\n\t\t},\n\n\t\t/**\n\t\t * Card mousedown: record the press for the drag guard, and on a card a\n\t\t * middle click opens, keep the browser from starting autoscroll.\n\t\t *\n\t\t * @param {MouseEvent} event The mousedown event.\n\t\t */\n\t\tonCardMouseDown(event) {\n\t\t\tthis.onPointerDown(event)\n\t\t\tif (!this.selectable || this.clickToView) {\n\t\t\t\tpreventMiddleClickAutoscroll(event)\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Card-body middle click: emits `aux-click`, not `click`, so an existing\n\t\t * `click` listener that navigates never moves the current tab away.\n\t\t * Ignored on a select-on-click card, on nested controls and on drags.\n\t\t *\n\t\t * @param {MouseEvent} event The auxclick event.\n\t\t */\n\t\tonCardAuxClick(event) {\n\t\t\tif (!isRowMiddleClick(event) || this.wasDrag(event)) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (this.selectable && !this.clickToView) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\t/**\n\t\t\t * @event aux-click Emitted when a non-selectable (or `clickToView`) card is middle-clicked, for opening it in a new tab (see `openRowTarget`). Payload: `(object, event)` — the card's object and the native auxclick event.\n\t\t\t * @type {object} The card's object.\n\t\t\t */\n\t\t\tthis.$emit('aux-click', this.object, event)\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-object-card {\n\tdisplay: flex;\n\tgap: 12px;\n\tpadding: 16px;\n\tbackground: var(--color-main-background);\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius-large, 10px);\n\tcursor: pointer;\n\ttransition: box-shadow 0.2s ease, border-color 0.2s ease;\n}\n\n.cn-object-card:hover {\n\tborder-color: var(--color-primary-element);\n\tbox-shadow: 0 2px 8px var(--color-box-shadow);\n}\n\n.cn-object-card--selected {\n\tborder-color: var(--color-primary-element);\n\tbackground: var(--color-primary-element-light);\n}\n\n.cn-object-card__checkbox {\n\tflex-shrink: 0;\n\tpadding-top: 2px;\n}\n\n.cn-object-card__content {\n\tflex: 1;\n\tmin-width: 0;\n}\n\n.cn-object-card__header {\n\tdisplay: flex;\n\tgap: 12px;\n\talign-items: flex-start;\n}\n\n.cn-object-card__image {\n\twidth: 48px;\n\theight: 48px;\n\tborder-radius: var(--border-radius);\n\tobject-fit: cover;\n\tflex-shrink: 0;\n}\n\n.cn-object-card__title-area {\n\tflex: 1;\n\tmin-width: 0;\n}\n\n.cn-object-card__title {\n\tmargin: 0;\n\tfont-size: 16px;\n\tfont-weight: 600;\n\tline-height: 1.3;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-object-card__description {\n\tmargin: 4px 0 0;\n\tfont-size: 13px;\n\tcolor: var(--color-text-maxcontrast);\n\tline-height: 1.4;\n}\n\n.cn-object-card__badges {\n\tmargin-top: 8px;\n}\n\n.cn-object-card__metadata {\n\tdisplay: grid;\n\tgrid-template-columns: repeat(auto-fill, minmax(160px, 1fr));\n\tgap: 8px;\n\tmargin-top: 12px;\n\tpadding-top: 12px;\n\tborder-top: 1px solid var(--color-border);\n}\n\n.cn-object-card__meta-item {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n}\n\n.cn-object-card__meta-label {\n\tfont-size: 11px;\n\tfont-weight: 500;\n\tcolor: var(--color-text-maxcontrast);\n\ttext-transform: uppercase;\n\tletter-spacing: 0.3px;\n}\n\n.cn-object-card__actions {\n\tflex-shrink: 0;\n}\n</style>\n"],"names":["CnCellRenderer","CnLockIndicator"],"mappings":"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"}
@@ -26,7 +26,7 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
26
26
 
27
27
  return (openBlock(), createElementBlock("div", {
28
28
  class: normalizeClass(["cn-object-card", { 'cn-object-card--selected': $props.selected }]),
29
- onMousedown: _cache[5] || (_cache[5] = (...args) => (_ctx.onPointerDown && _ctx.onPointerDown(...args))),
29
+ onMousedown: _cache[5] || (_cache[5] = (...args) => ($options.onCardMouseDown && $options.onCardMouseDown(...args))),
30
30
  onClick: _cache[6] || (_cache[6] = $event => ($options.onCardClick($event))),
31
31
  onAuxclick: _cache[7] || (_cache[7] = $event => ($options.onCardAuxClick($event)))
32
32
  }, [
@@ -1 +1 @@
1
- {"version":3,"file":"CnObjectCard.vue3.js","sources":["../../../../src/components/CnObjectCard/CnObjectCard.vue?vue&type=template&id=6f672c8f&lang.js"],"sourcesContent":["<template>\n\t<div\n\t\tclass=\"cn-object-card\"\n\t\t:class=\"{ 'cn-object-card--selected': selected }\"\n\t\t@mousedown=\"onPointerDown\"\n\t\t@click=\"onCardClick($event)\"\n\t\t@auxclick=\"onCardAuxClick($event)\">\n\t\t<!-- Selection checkbox -->\n\t\t<div v-if=\"selectable\"\n\t\t\tclass=\"cn-object-card__checkbox\"\n\t\t\t@click.stop\n\t\t\t@auxclick.stop>\n\t\t\t<NcCheckboxRadioSwitch\n\t\t\t\t:modelValue=\"selected\"\n\t\t\t\t@update:modelValue=\"$emit('select', object)\" />\n\t\t</div>\n\n\t\t<!-- Card content -->\n\t\t<div class=\"cn-object-card__content\">\n\t\t\t<!-- Header: image + title -->\n\t\t\t<div class=\"cn-object-card__header\">\n\t\t\t\t<img\n\t\t\t\t\tv-if=\"imageUrl\"\n\t\t\t\t\t:src=\"imageUrl\"\n\t\t\t\t\t:alt=\"title\"\n\t\t\t\t\tclass=\"cn-object-card__image\">\n\n\t\t\t\t<div class=\"cn-object-card__title-area\">\n\t\t\t\t\t<h3 class=\"cn-object-card__title\">\n\t\t\t\t\t\t<!-- Beside the title, not in the badges row: the badges\n\t\t\t\t\t\t slot is the consumer's, and a lock is the library's\n\t\t\t\t\t\t to report. A card whose consumer passes no badges\n\t\t\t\t\t\t would otherwise show no padlock at all. -->\n\t\t\t\t\t\t<CnLockIndicator :object=\"object\" :size=\"16\" />\n\t\t\t\t\t\t{{ title }}\n\t\t\t\t\t</h3>\n\t\t\t\t\t<p v-if=\"description\" class=\"cn-object-card__description\">\n\t\t\t\t\t\t{{ truncatedDescription }}\n\t\t\t\t\t</p>\n\t\t\t\t</div>\n\t\t\t</div>\n\n\t\t\t<!-- Badges slot -->\n\t\t\t<div v-if=\"$slots.badges\" class=\"cn-object-card__badges\">\n\t\t\t\t<slot name=\"badges\" :object=\"object\" />\n\t\t\t</div>\n\n\t\t\t<!-- Metadata: visible properties as label:value pairs -->\n\t\t\t<div v-if=\"metadataFields.length > 0\" class=\"cn-object-card__metadata\">\n\t\t\t\t<slot name=\"metadata\" :object=\"object\" :fields=\"metadataFields\">\n\t\t\t\t\t<div\n\t\t\t\t\t\tv-for=\"field in metadataFields\"\n\t\t\t\t\t\t:key=\"field.key\"\n\t\t\t\t\t\tclass=\"cn-object-card__meta-item\">\n\t\t\t\t\t\t<span class=\"cn-object-card__meta-label\">{{ field.label }}</span>\n\t\t\t\t\t\t<CnCellRenderer\n\t\t\t\t\t\t\t:value=\"field.value\"\n\t\t\t\t\t\t\t:property=\"field.property\"\n\t\t\t\t\t\t\t:truncate=\"60\" />\n\t\t\t\t\t</div>\n\t\t\t\t</slot>\n\t\t\t</div>\n\t\t</div>\n\n\t\t<!-- Actions slot -->\n\t\t<div v-if=\"$slots.actions\"\n\t\t\tclass=\"cn-object-card__actions\"\n\t\t\t@click.stop\n\t\t\t@auxclick.stop>\n\t\t\t<slot name=\"actions\" :object=\"object\" />\n\t\t</div>\n\t</div>\n</template>\n\n<script>\nimport { NcCheckboxRadioSwitch } from '@nextcloud/vue'\nimport { useClickDragGuard } from '../../composables/useClickDragGuard.js'\nimport { resolveImageUrl } from '../../utils/resolveImageUrl.js'\nimport { isRowMiddleClick } from '../../utils/rowAuxClick.js'\nimport { formatValue } from '../../utils/schema.js'\nimport { CnCellRenderer } from '../CnCellRenderer/index.js'\nimport { CnLockIndicator } from '../CnLockIndicator/index.js'\n\n/**\n * CnObjectCard — Schema-configuration-driven card for object display.\n *\n * Uses `schema.configuration` to determine which fields map to the card title,\n * description, and image. Remaining visible properties are shown as metadata.\n *\n * ```vue\n * <CnObjectCard :object=\"publication\" :schema=\"pubSchema\">\n * <template #actions=\"{ object }\">\n * <NcActions><NcActionButton @click=\"edit(object)\">Edit</NcActionButton></NcActions>\n * </template>\n * </CnObjectCard>\n * ```\n */\nexport default {\n\tname: 'CnObjectCard',\n\n\tcomponents: {\n\t\tNcCheckboxRadioSwitch,\n\t\tCnCellRenderer,\n\t\tCnLockIndicator,\n\t},\n\n\tinject: {\n\t\t/**\n\t\t * Consumer translation function, provided by CnAppRoot as\n\t\t * `cnTranslate: this.translate` (bound to the host app's id). Metadata\n\t\t * labels come from schema property titles, authored in English as the\n\t\t * canonical source; the visible label is resolved through this function\n\t\t * so it follows the user's language. Defaults to identity when used\n\t\t * standalone (no CnAppRoot ancestor).\n\t\t */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/** The object data */\n\t\tobject: {\n\t\t\ttype: Object,\n\t\t\trequired: true,\n\t\t},\n\n\t\t/** Schema definition with properties and configuration */\n\t\tschema: {\n\t\t\ttype: Object,\n\t\t\trequired: true,\n\t\t},\n\n\t\t/** Whether this card is selected */\n\t\tselected: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Whether to show selection checkbox */\n\t\tselectable: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * When true, a body click on a SELECTABLE card emits `click`\n\t\t * (navigation) instead of toggling selection — selection then happens\n\t\t * via the checkbox only. The card counterpart of CnIndexPage's\n\t\t * `rowClickToView` (a table row click already navigates in that mode).\n\t\t *\n\t\t * @type {boolean}\n\t\t */\n\t\tclickToView: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Maximum number of metadata fields to show */\n\t\tmaxMetadata: {\n\t\t\ttype: Number,\n\t\t\tdefault: 4,\n\t\t},\n\t},\n\n\temits: ['click', 'select'],\n\n\tsetup() {\n\t\t// Tell a deliberate card click apart from a text-selection drag.\n\t\treturn useClickDragGuard()\n\t},\n\n\tcomputed: {\n\t\tconfig() {\n\t\t\treturn this.schema?.configuration || {}\n\t\t},\n\n\t\ttitle() {\n\t\t\tconst field = this.config.objectNameField\n\t\t\tif (field && this.object[field]) {\n\t\t\t\treturn String(this.object[field])\n\t\t\t}\n\t\t\treturn this.object.title || this.object.name || this.object.id || '—'\n\t\t},\n\n\t\tdescription() {\n\t\t\tconst field = this.config.objectDescriptionField\n\t\t\tif (field && this.object[field]) {\n\t\t\t\treturn String(this.object[field])\n\t\t\t}\n\t\t\treturn null\n\t\t},\n\n\t\ttruncatedDescription() {\n\t\t\tif (!this.description) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tif (this.description.length > 120) {\n\t\t\t\treturn this.description.substring(0, 120) + '...'\n\t\t\t}\n\t\t\treturn this.description\n\t\t},\n\n\t\timageUrl() {\n\t\t\tconst field = this.config.objectImageField\n\t\t\tif (field && this.object[field]) {\n\t\t\t\treturn resolveImageUrl(this.object[field])\n\t\t\t}\n\t\t\treturn null\n\t\t},\n\n\t\t/** Fields excluded from metadata (already shown as title/desc/image) */\n\t\tconfigFields() {\n\t\t\treturn [\n\t\t\t\tthis.config.objectNameField,\n\t\t\t\tthis.config.objectDescriptionField,\n\t\t\t\tthis.config.objectSummaryField,\n\t\t\t\tthis.config.objectImageField,\n\t\t\t].filter(Boolean)\n\t\t},\n\n\t\t/** Remaining visible properties for the metadata section */\n\t\tmetadataFields() {\n\t\t\tif (!this.schema?.properties) {\n\t\t\t\treturn []\n\t\t\t}\n\n\t\t\treturn Object.entries(this.schema.properties)\n\t\t\t\t.filter(([key, prop]) => {\n\t\t\t\t\tif (this.configFields.includes(key)) {\n\t\t\t\t\t\treturn false\n\t\t\t\t\t}\n\t\t\t\t\tif (prop.visible === false) {\n\t\t\t\t\t\treturn false\n\t\t\t\t\t}\n\t\t\t\t\tif (prop.type === 'object') {\n\t\t\t\t\t\treturn false\n\t\t\t\t\t}\n\t\t\t\t\tif (prop.format === 'markdown') {\n\t\t\t\t\t\treturn false\n\t\t\t\t\t}\n\t\t\t\t\treturn true\n\t\t\t\t})\n\t\t\t\t.sort(([, a], [, b]) => {\n\t\t\t\t\tconst orderA = typeof a.order === 'number' ? a.order : Infinity\n\t\t\t\t\tconst orderB = typeof b.order === 'number' ? b.order : Infinity\n\t\t\t\t\treturn orderA - orderB\n\t\t\t\t})\n\t\t\t\t.slice(0, this.maxMetadata)\n\t\t\t\t.map(([key, prop]) => ({\n\t\t\t\t\tkey,\n\t\t\t\t\tlabel: this.cnTranslate(prop.title || key),\n\t\t\t\t\tvalue: this.object[key],\n\t\t\t\t\tproperty: prop,\n\t\t\t\t}))\n\t\t},\n\t},\n\n\tmethods: {\n\t\tformatValue,\n\n\t\t/**\n\t\t * Card-body click: emits `select` when `selectable` (ignoring drags),\n\t\t * otherwise emits `click` for navigation. With `clickToView`, a\n\t\t * selectable card's body click navigates too — the checkbox is the\n\t\t * only selection surface, mirroring the table's rowClickToView split.\n\t\t *\n\t\t * @param {MouseEvent} [event] The originating click event.\n\t\t */\n\t\tonCardClick(event) {\n\t\t\tif (this.selectable && !this.clickToView) {\n\t\t\t\tif (this.wasDrag(event)) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\t/**\n\t\t\t\t * @event select Emitted when the card toggles selection (clicking the body of a selectable card, or its checkbox).\n\t\t\t\t * @type {object} The card's object.\n\t\t\t\t */\n\t\t\t\tthis.$emit('select', this.object)\n\t\t\t\t// Deprecation: selectable cards used to emit `click`. Keep emitting it\n\t\t\t\t// for listeners that still rely on it, but warn them to migrate to `select`.\n\t\t\t\t// `$.vnode.props`, not `$attrs`: `click` is a declared emit, and\n\t\t\t\t// Vue keeps declared emits out of `$attrs`.\n\t\t\t\tif (this.$.vnode.props?.onClick) {\n\t\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\t\tconsole.warn('[CnObjectCard] @click on selectable cards is deprecated; use @select instead.')\n\t\t\t\t\tthis.$emit('click', this.object)\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\t/**\n\t\t\t * @event click Emitted when a non-selectable (or `clickToView`) card is clicked or middle-clicked. Payload: `(object, event)` — the card's object and the native click/auxclick event, for opening it in a new tab on a ctrl/cmd/shift or middle click. Selectable cards emit `select`; they also emit `click` (deprecated) when a `click` listener is present, so migrate selectable consumers to `@select`.\n\t\t\t * @type {object} The card's object.\n\t\t\t */\n\t\t\tthis.$emit('click', this.object, event)\n\t\t},\n\n\t\t/**\n\t\t * Card-body middle click: emits `click` like a navigating click.\n\t\t * Ignored on a select-on-click card, on nested controls and on drags.\n\t\t *\n\t\t * @param {MouseEvent} event The auxclick event.\n\t\t */\n\t\tonCardAuxClick(event) {\n\t\t\tif (!isRowMiddleClick(event) || this.wasDrag(event)) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (this.selectable && !this.clickToView) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.$emit('click', this.object, event)\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-object-card {\n\tdisplay: flex;\n\tgap: 12px;\n\tpadding: 16px;\n\tbackground: var(--color-main-background);\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius-large, 10px);\n\tcursor: pointer;\n\ttransition: box-shadow 0.2s ease, border-color 0.2s ease;\n}\n\n.cn-object-card:hover {\n\tborder-color: var(--color-primary-element);\n\tbox-shadow: 0 2px 8px var(--color-box-shadow);\n}\n\n.cn-object-card--selected {\n\tborder-color: var(--color-primary-element);\n\tbackground: var(--color-primary-element-light);\n}\n\n.cn-object-card__checkbox {\n\tflex-shrink: 0;\n\tpadding-top: 2px;\n}\n\n.cn-object-card__content {\n\tflex: 1;\n\tmin-width: 0;\n}\n\n.cn-object-card__header {\n\tdisplay: flex;\n\tgap: 12px;\n\talign-items: flex-start;\n}\n\n.cn-object-card__image {\n\twidth: 48px;\n\theight: 48px;\n\tborder-radius: var(--border-radius);\n\tobject-fit: cover;\n\tflex-shrink: 0;\n}\n\n.cn-object-card__title-area {\n\tflex: 1;\n\tmin-width: 0;\n}\n\n.cn-object-card__title {\n\tmargin: 0;\n\tfont-size: 16px;\n\tfont-weight: 600;\n\tline-height: 1.3;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-object-card__description {\n\tmargin: 4px 0 0;\n\tfont-size: 13px;\n\tcolor: var(--color-text-maxcontrast);\n\tline-height: 1.4;\n}\n\n.cn-object-card__badges {\n\tmargin-top: 8px;\n}\n\n.cn-object-card__metadata {\n\tdisplay: grid;\n\tgrid-template-columns: repeat(auto-fill, minmax(160px, 1fr));\n\tgap: 8px;\n\tmargin-top: 12px;\n\tpadding-top: 12px;\n\tborder-top: 1px solid var(--color-border);\n}\n\n.cn-object-card__meta-item {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n}\n\n.cn-object-card__meta-label {\n\tfont-size: 11px;\n\tfont-weight: 500;\n\tcolor: var(--color-text-maxcontrast);\n\ttext-transform: uppercase;\n\tletter-spacing: 0.3px;\n}\n\n.cn-object-card__actions {\n\tflex-shrink: 0;\n}\n</style>\n"],"names":["_createElementBlock","_normalizeClass","_createVNode","_createElementVNode","_createTextVNode","_toDisplayString","_openBlock","_renderSlot","_Fragment","_renderList"],"mappings":"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"}
1
+ {"version":3,"file":"CnObjectCard.vue3.js","sources":["../../../../src/components/CnObjectCard/CnObjectCard.vue?vue&type=template&id=6f672c8f&lang.js"],"sourcesContent":["<template>\n\t<div\n\t\tclass=\"cn-object-card\"\n\t\t:class=\"{ 'cn-object-card--selected': selected }\"\n\t\t@mousedown=\"onCardMouseDown\"\n\t\t@click=\"onCardClick($event)\"\n\t\t@auxclick=\"onCardAuxClick($event)\">\n\t\t<!-- Selection checkbox -->\n\t\t<div v-if=\"selectable\"\n\t\t\tclass=\"cn-object-card__checkbox\"\n\t\t\t@click.stop\n\t\t\t@auxclick.stop>\n\t\t\t<NcCheckboxRadioSwitch\n\t\t\t\t:modelValue=\"selected\"\n\t\t\t\t@update:modelValue=\"$emit('select', object)\" />\n\t\t</div>\n\n\t\t<!-- Card content -->\n\t\t<div class=\"cn-object-card__content\">\n\t\t\t<!-- Header: image + title -->\n\t\t\t<div class=\"cn-object-card__header\">\n\t\t\t\t<img\n\t\t\t\t\tv-if=\"imageUrl\"\n\t\t\t\t\t:src=\"imageUrl\"\n\t\t\t\t\t:alt=\"title\"\n\t\t\t\t\tclass=\"cn-object-card__image\">\n\n\t\t\t\t<div class=\"cn-object-card__title-area\">\n\t\t\t\t\t<h3 class=\"cn-object-card__title\">\n\t\t\t\t\t\t<!-- Beside the title, not in the badges row: the badges\n\t\t\t\t\t\t slot is the consumer's, and a lock is the library's\n\t\t\t\t\t\t to report. A card whose consumer passes no badges\n\t\t\t\t\t\t would otherwise show no padlock at all. -->\n\t\t\t\t\t\t<CnLockIndicator :object=\"object\" :size=\"16\" />\n\t\t\t\t\t\t{{ title }}\n\t\t\t\t\t</h3>\n\t\t\t\t\t<p v-if=\"description\" class=\"cn-object-card__description\">\n\t\t\t\t\t\t{{ truncatedDescription }}\n\t\t\t\t\t</p>\n\t\t\t\t</div>\n\t\t\t</div>\n\n\t\t\t<!-- Badges slot -->\n\t\t\t<div v-if=\"$slots.badges\" class=\"cn-object-card__badges\">\n\t\t\t\t<slot name=\"badges\" :object=\"object\" />\n\t\t\t</div>\n\n\t\t\t<!-- Metadata: visible properties as label:value pairs -->\n\t\t\t<div v-if=\"metadataFields.length > 0\" class=\"cn-object-card__metadata\">\n\t\t\t\t<slot name=\"metadata\" :object=\"object\" :fields=\"metadataFields\">\n\t\t\t\t\t<div\n\t\t\t\t\t\tv-for=\"field in metadataFields\"\n\t\t\t\t\t\t:key=\"field.key\"\n\t\t\t\t\t\tclass=\"cn-object-card__meta-item\">\n\t\t\t\t\t\t<span class=\"cn-object-card__meta-label\">{{ field.label }}</span>\n\t\t\t\t\t\t<CnCellRenderer\n\t\t\t\t\t\t\t:value=\"field.value\"\n\t\t\t\t\t\t\t:property=\"field.property\"\n\t\t\t\t\t\t\t:truncate=\"60\" />\n\t\t\t\t\t</div>\n\t\t\t\t</slot>\n\t\t\t</div>\n\t\t</div>\n\n\t\t<!-- Actions slot -->\n\t\t<div v-if=\"$slots.actions\"\n\t\t\tclass=\"cn-object-card__actions\"\n\t\t\t@click.stop\n\t\t\t@auxclick.stop>\n\t\t\t<slot name=\"actions\" :object=\"object\" />\n\t\t</div>\n\t</div>\n</template>\n\n<script>\nimport { NcCheckboxRadioSwitch } from '@nextcloud/vue'\nimport { useClickDragGuard } from '../../composables/useClickDragGuard.js'\nimport { resolveImageUrl } from '../../utils/resolveImageUrl.js'\nimport { isRowMiddleClick, preventMiddleClickAutoscroll } from '../../utils/rowAuxClick.js'\nimport { formatValue } from '../../utils/schema.js'\nimport { CnCellRenderer } from '../CnCellRenderer/index.js'\nimport { CnLockIndicator } from '../CnLockIndicator/index.js'\n\n/**\n * CnObjectCard — Schema-configuration-driven card for object display.\n *\n * Uses `schema.configuration` to determine which fields map to the card title,\n * description, and image. Remaining visible properties are shown as metadata.\n *\n * ```vue\n * <CnObjectCard :object=\"publication\" :schema=\"pubSchema\">\n * <template #actions=\"{ object }\">\n * <NcActions><NcActionButton @click=\"edit(object)\">Edit</NcActionButton></NcActions>\n * </template>\n * </CnObjectCard>\n * ```\n */\nexport default {\n\tname: 'CnObjectCard',\n\n\tcomponents: {\n\t\tNcCheckboxRadioSwitch,\n\t\tCnCellRenderer,\n\t\tCnLockIndicator,\n\t},\n\n\tinject: {\n\t\t/**\n\t\t * Consumer translation function, provided by CnAppRoot as\n\t\t * `cnTranslate: this.translate` (bound to the host app's id). Metadata\n\t\t * labels come from schema property titles, authored in English as the\n\t\t * canonical source; the visible label is resolved through this function\n\t\t * so it follows the user's language. Defaults to identity when used\n\t\t * standalone (no CnAppRoot ancestor).\n\t\t */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/** The object data */\n\t\tobject: {\n\t\t\ttype: Object,\n\t\t\trequired: true,\n\t\t},\n\n\t\t/** Schema definition with properties and configuration */\n\t\tschema: {\n\t\t\ttype: Object,\n\t\t\trequired: true,\n\t\t},\n\n\t\t/** Whether this card is selected */\n\t\tselected: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Whether to show selection checkbox */\n\t\tselectable: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * When true, a body click on a SELECTABLE card emits `click`\n\t\t * (navigation) instead of toggling selection — selection then happens\n\t\t * via the checkbox only. The card counterpart of CnIndexPage's\n\t\t * `rowClickToView` (a table row click already navigates in that mode).\n\t\t *\n\t\t * @type {boolean}\n\t\t */\n\t\tclickToView: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Maximum number of metadata fields to show */\n\t\tmaxMetadata: {\n\t\t\ttype: Number,\n\t\t\tdefault: 4,\n\t\t},\n\t},\n\n\temits: ['click', 'select', 'aux-click'],\n\n\tsetup() {\n\t\t// Tell a deliberate card click apart from a text-selection drag.\n\t\treturn useClickDragGuard()\n\t},\n\n\tcomputed: {\n\t\tconfig() {\n\t\t\treturn this.schema?.configuration || {}\n\t\t},\n\n\t\ttitle() {\n\t\t\tconst field = this.config.objectNameField\n\t\t\tif (field && this.object[field]) {\n\t\t\t\treturn String(this.object[field])\n\t\t\t}\n\t\t\treturn this.object.title || this.object.name || this.object.id || '—'\n\t\t},\n\n\t\tdescription() {\n\t\t\tconst field = this.config.objectDescriptionField\n\t\t\tif (field && this.object[field]) {\n\t\t\t\treturn String(this.object[field])\n\t\t\t}\n\t\t\treturn null\n\t\t},\n\n\t\ttruncatedDescription() {\n\t\t\tif (!this.description) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tif (this.description.length > 120) {\n\t\t\t\treturn this.description.substring(0, 120) + '...'\n\t\t\t}\n\t\t\treturn this.description\n\t\t},\n\n\t\timageUrl() {\n\t\t\tconst field = this.config.objectImageField\n\t\t\tif (field && this.object[field]) {\n\t\t\t\treturn resolveImageUrl(this.object[field])\n\t\t\t}\n\t\t\treturn null\n\t\t},\n\n\t\t/** Fields excluded from metadata (already shown as title/desc/image) */\n\t\tconfigFields() {\n\t\t\treturn [\n\t\t\t\tthis.config.objectNameField,\n\t\t\t\tthis.config.objectDescriptionField,\n\t\t\t\tthis.config.objectSummaryField,\n\t\t\t\tthis.config.objectImageField,\n\t\t\t].filter(Boolean)\n\t\t},\n\n\t\t/** Remaining visible properties for the metadata section */\n\t\tmetadataFields() {\n\t\t\tif (!this.schema?.properties) {\n\t\t\t\treturn []\n\t\t\t}\n\n\t\t\treturn Object.entries(this.schema.properties)\n\t\t\t\t.filter(([key, prop]) => {\n\t\t\t\t\tif (this.configFields.includes(key)) {\n\t\t\t\t\t\treturn false\n\t\t\t\t\t}\n\t\t\t\t\tif (prop.visible === false) {\n\t\t\t\t\t\treturn false\n\t\t\t\t\t}\n\t\t\t\t\tif (prop.type === 'object') {\n\t\t\t\t\t\treturn false\n\t\t\t\t\t}\n\t\t\t\t\tif (prop.format === 'markdown') {\n\t\t\t\t\t\treturn false\n\t\t\t\t\t}\n\t\t\t\t\treturn true\n\t\t\t\t})\n\t\t\t\t.sort(([, a], [, b]) => {\n\t\t\t\t\tconst orderA = typeof a.order === 'number' ? a.order : Infinity\n\t\t\t\t\tconst orderB = typeof b.order === 'number' ? b.order : Infinity\n\t\t\t\t\treturn orderA - orderB\n\t\t\t\t})\n\t\t\t\t.slice(0, this.maxMetadata)\n\t\t\t\t.map(([key, prop]) => ({\n\t\t\t\t\tkey,\n\t\t\t\t\tlabel: this.cnTranslate(prop.title || key),\n\t\t\t\t\tvalue: this.object[key],\n\t\t\t\t\tproperty: prop,\n\t\t\t\t}))\n\t\t},\n\t},\n\n\tmethods: {\n\t\tformatValue,\n\n\t\t/**\n\t\t * Card-body click: emits `select` when `selectable` (ignoring drags),\n\t\t * otherwise emits `click` for navigation. With `clickToView`, a\n\t\t * selectable card's body click navigates too — the checkbox is the\n\t\t * only selection surface, mirroring the table's rowClickToView split.\n\t\t *\n\t\t * @param {MouseEvent} [event] The originating click event.\n\t\t */\n\t\tonCardClick(event) {\n\t\t\tif (this.selectable && !this.clickToView) {\n\t\t\t\tif (this.wasDrag(event)) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\t/**\n\t\t\t\t * @event select Emitted when the card toggles selection (clicking the body of a selectable card, or its checkbox).\n\t\t\t\t * @type {object} The card's object.\n\t\t\t\t */\n\t\t\t\tthis.$emit('select', this.object)\n\t\t\t\t// Deprecation: selectable cards used to emit `click`. Keep emitting it\n\t\t\t\t// for listeners that still rely on it, but warn them to migrate to `select`.\n\t\t\t\t// `$.vnode.props`, not `$attrs`: `click` is a declared emit, and\n\t\t\t\t// Vue keeps declared emits out of `$attrs`.\n\t\t\t\tif (this.$.vnode.props?.onClick) {\n\t\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\t\tconsole.warn('[CnObjectCard] @click on selectable cards is deprecated; use @select instead.')\n\t\t\t\t\tthis.$emit('click', this.object)\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\t/**\n\t\t\t * @event click Emitted when a non-selectable (or `clickToView`) card is clicked. Payload: `(object, event)` — the card's object and the native click event, for opening it in a new tab on a ctrl/cmd/shift click. A middle click emits `aux-click` instead. Selectable cards emit `select`; they also emit `click` (deprecated) when a `click` listener is present, so migrate selectable consumers to `@select`.\n\t\t\t * @type {object} The card's object.\n\t\t\t */\n\t\t\tthis.$emit('click', this.object, event)\n\t\t},\n\n\t\t/**\n\t\t * Card mousedown: record the press for the drag guard, and on a card a\n\t\t * middle click opens, keep the browser from starting autoscroll.\n\t\t *\n\t\t * @param {MouseEvent} event The mousedown event.\n\t\t */\n\t\tonCardMouseDown(event) {\n\t\t\tthis.onPointerDown(event)\n\t\t\tif (!this.selectable || this.clickToView) {\n\t\t\t\tpreventMiddleClickAutoscroll(event)\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Card-body middle click: emits `aux-click`, not `click`, so an existing\n\t\t * `click` listener that navigates never moves the current tab away.\n\t\t * Ignored on a select-on-click card, on nested controls and on drags.\n\t\t *\n\t\t * @param {MouseEvent} event The auxclick event.\n\t\t */\n\t\tonCardAuxClick(event) {\n\t\t\tif (!isRowMiddleClick(event) || this.wasDrag(event)) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (this.selectable && !this.clickToView) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\t/**\n\t\t\t * @event aux-click Emitted when a non-selectable (or `clickToView`) card is middle-clicked, for opening it in a new tab (see `openRowTarget`). Payload: `(object, event)` — the card's object and the native auxclick event.\n\t\t\t * @type {object} The card's object.\n\t\t\t */\n\t\t\tthis.$emit('aux-click', this.object, event)\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-object-card {\n\tdisplay: flex;\n\tgap: 12px;\n\tpadding: 16px;\n\tbackground: var(--color-main-background);\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius-large, 10px);\n\tcursor: pointer;\n\ttransition: box-shadow 0.2s ease, border-color 0.2s ease;\n}\n\n.cn-object-card:hover {\n\tborder-color: var(--color-primary-element);\n\tbox-shadow: 0 2px 8px var(--color-box-shadow);\n}\n\n.cn-object-card--selected {\n\tborder-color: var(--color-primary-element);\n\tbackground: var(--color-primary-element-light);\n}\n\n.cn-object-card__checkbox {\n\tflex-shrink: 0;\n\tpadding-top: 2px;\n}\n\n.cn-object-card__content {\n\tflex: 1;\n\tmin-width: 0;\n}\n\n.cn-object-card__header {\n\tdisplay: flex;\n\tgap: 12px;\n\talign-items: flex-start;\n}\n\n.cn-object-card__image {\n\twidth: 48px;\n\theight: 48px;\n\tborder-radius: var(--border-radius);\n\tobject-fit: cover;\n\tflex-shrink: 0;\n}\n\n.cn-object-card__title-area {\n\tflex: 1;\n\tmin-width: 0;\n}\n\n.cn-object-card__title {\n\tmargin: 0;\n\tfont-size: 16px;\n\tfont-weight: 600;\n\tline-height: 1.3;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-object-card__description {\n\tmargin: 4px 0 0;\n\tfont-size: 13px;\n\tcolor: var(--color-text-maxcontrast);\n\tline-height: 1.4;\n}\n\n.cn-object-card__badges {\n\tmargin-top: 8px;\n}\n\n.cn-object-card__metadata {\n\tdisplay: grid;\n\tgrid-template-columns: repeat(auto-fill, minmax(160px, 1fr));\n\tgap: 8px;\n\tmargin-top: 12px;\n\tpadding-top: 12px;\n\tborder-top: 1px solid var(--color-border);\n}\n\n.cn-object-card__meta-item {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n}\n\n.cn-object-card__meta-label {\n\tfont-size: 11px;\n\tfont-weight: 500;\n\tcolor: var(--color-text-maxcontrast);\n\ttext-transform: uppercase;\n\tletter-spacing: 0.3px;\n}\n\n.cn-object-card__actions {\n\tflex-shrink: 0;\n}\n</style>\n"],"names":["_createElementBlock","_normalizeClass","_createVNode","_createElementVNode","_createTextVNode","_toDisplayString","_openBlock","_renderSlot","_Fragment","_renderList"],"mappings":"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"}
@@ -97,7 +97,7 @@ var script = {
97
97
  },
98
98
  },
99
99
 
100
- emits: ['click', 'select'],
100
+ emits: ['click', 'select', 'aux-click'],
101
101
 
102
102
  computed: {
103
103
  /**
@@ -128,6 +128,7 @@ var script = {
128
128
  const listeners = { select: () => this.toggleSelect(object) };
129
129
  if (!this.selectable) {
130
130
  listeners.click = (_row, event) => this.emitClick(object, event);
131
+ listeners['aux-click'] = (_row, event) => this.emitAuxClick(object, event);
131
132
  }
132
133
  return listeners
133
134
  },
@@ -136,16 +137,31 @@ var script = {
136
137
  * Emit `click` for a non-selectable row (navigation).
137
138
  *
138
139
  * @param {object} object The clicked row's object.
139
- * @param {MouseEvent} [event] The native click/auxclick event.
140
+ * @param {MouseEvent} [event] The native click event.
140
141
  */
141
142
  emitClick(object, event) {
142
143
  /**
143
- * @event click Emitted when a non-selectable row is clicked or middle-clicked (navigation). Payload: `(object, event)` — the row's object and the native click/auxclick event, for opening it in a new tab on a ctrl/cmd/shift or middle click.
144
+ * @event click Emitted when a non-selectable row is clicked (navigation). Payload: `(object, event)` — the row's object and the native click event, for opening it in a new tab on a ctrl/cmd/shift click. A middle click emits `aux-click` instead.
144
145
  * @type {object} The clicked row's object.
145
146
  */
146
147
  this.$emit('click', object, event);
147
148
  },
148
149
 
150
+ /**
151
+ * Emit `aux-click` for a middle click on a non-selectable row, kept off
152
+ * `click` so a navigating listener never moves the current tab away.
153
+ *
154
+ * @param {object} object The clicked row's object.
155
+ * @param {MouseEvent} event The native auxclick event.
156
+ */
157
+ emitAuxClick(object, event) {
158
+ /**
159
+ * @event aux-click Emitted when a non-selectable row is middle-clicked, for opening it in a new tab (see `openRowTarget`). Payload: `(object, event)` — the row's object and the native auxclick event.
160
+ * @type {object} The clicked row's object.
161
+ */
162
+ this.$emit('aux-click', object, event);
163
+ },
164
+
149
165
  toggleSelect(object) {
150
166
  const id = object[this.rowKey];
151
167
  const newIds = this.isSelected(object)