@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
@@ -1 +1 @@
1
- {"version":3,"file":"CnFilesWidget.vue2.js","sources":["../../../../src/components/CnFilesWidget/CnFilesWidget.vue"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<template>\n\t<div\n\t\tclass=\"cn-files-widget\"\n\t\t:class=\"{ 'cn-files-widget--dragging': isDragging }\"\n\t\t@dragenter=\"onDragEnter\"\n\t\t@dragover=\"onDragOver\"\n\t\t@dragleave=\"onDragLeave\"\n\t\t@drop=\"onDrop\">\n\t\t<div class=\"cn-files-widget__toolbar\">\n\t\t\t<nav\n\t\t\t\tv-if=\"!objectBound\"\n\t\t\t\tclass=\"cn-files-widget__breadcrumb\"\n\t\t\t\t:aria-label=\"t('nextcloud-vue', 'Folder breadcrumb')\">\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"cn-files-widget__crumb cn-files-widget__crumb--root\"\n\t\t\t\t\t:aria-current=\"currentSubPath === '/' ? 'page' : null\"\n\t\t\t\t\t@click=\"navigateTo('/')\">\n\t\t\t\t\t{{ t('nextcloud-vue', 'Root') }}\n\t\t\t\t</button>\n\t\t\t\t<template v-for=\"(segment, index) in pathSegments\" :key=\"index\">\n\t\t\t\t\t<span\n\t\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\t\tclass=\"cn-files-widget__separator\">/</span>\n\t\t\t\t\t<button\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tclass=\"cn-files-widget__crumb\"\n\t\t\t\t\t\t:aria-current=\"index === pathSegments.length - 1 ? 'page' : null\"\n\t\t\t\t\t\t@click=\"navigateTo(segmentPathTo(index))\">\n\t\t\t\t\t\t{{ segment }}\n\t\t\t\t\t</button>\n\t\t\t\t</template>\n\t\t\t</nav>\n\n\t\t\t<input\n\t\t\t\tv-model=\"searchQuery\"\n\t\t\t\ttype=\"search\"\n\t\t\t\tclass=\"cn-files-widget__search\"\n\t\t\t\t:aria-label=\"t('nextcloud-vue', 'Search this folder')\"\n\t\t\t\t:placeholder=\"t('nextcloud-vue', 'Search this folder…')\">\n\n\t\t\t<button\n\t\t\t\tv-if=\"canShowUpload\"\n\t\t\t\ttype=\"button\"\n\t\t\t\tclass=\"cn-files-widget__upload\"\n\t\t\t\t@click=\"triggerUpload\">\n\t\t\t\t{{ t('nextcloud-vue', 'Upload File') }}\n\t\t\t</button>\n\t\t\t<!--\n\t\t\t\t`:ref`, not `ref`. Note `:aria-hidden=\"true\"` does NOT save this:\n\t\t\t\ta binding to a LITERAL CONSTANT is constant-folded, so the vnode\n\t\t\t\tstays fully static and the compiler hoists it to module scope.\n\t\t\t\tA hoisted vnode's ref has no owner instance, and `setRef()`'s\n\t\t\t\tnull-owner guard — along with its early `return` — is compiled\n\t\t\t\tout of production builds, so it dereferences null and throws\n\t\t\t\t`Cannot read properties of null (reading 'refs')`, aborting the\n\t\t\t\trender of the page that mounted this widget.\n\n\t\t\t\tConfirmed by instrumenting Vue's own warning to print the ref\n\t\t\t\tname: `[DEBUG ref=\"fileInput\" vnodeType=input]`. This widget is\n\t\t\t\tthe `files` integration leaf, which is how it reaches a detail\n\t\t\t\tpage. See CnFilesTab for the full account of the mechanism.\n\t\t\t-->\n\t\t\t<input\n\t\t\t\t:ref=\"fileInputRef\"\n\t\t\t\ttype=\"file\"\n\t\t\t\tmultiple\n\t\t\t\tclass=\"cn-files-widget__file-input\"\n\t\t\t\t:aria-hidden=\"true\"\n\t\t\t\ttabindex=\"-1\"\n\t\t\t\t@change=\"onFileInputChange\">\n\t\t</div>\n\n\t\t<div\n\t\t\tv-if=\"isDragging && canShowUpload\"\n\t\t\tclass=\"cn-files-widget__dropzone\"\n\t\t\taria-hidden=\"true\">\n\t\t\t{{ t('nextcloud-vue', 'Drop files to upload') }}\n\t\t</div>\n\n\t\t<div v-if=\"loading\" class=\"cn-files-widget__state\">\n\t\t\t{{ t('nextcloud-vue', 'Loading folder…') }}\n\t\t</div>\n\n\t\t<div\n\t\t\tv-else-if=\"unavailable\"\n\t\t\tclass=\"cn-files-widget__state cn-files-widget__state--unavailable\">\n\t\t\t{{ t('nextcloud-vue', 'Files is not available') }}\n\t\t</div>\n\n\t\t<div\n\t\t\tv-else-if=\"errorCode === 'no_access'\"\n\t\t\tclass=\"cn-files-widget__state cn-files-widget__state--no-access\">\n\t\t\t{{ t('nextcloud-vue', 'You don\\'t have access to this folder.') }}\n\t\t</div>\n\n\t\t<div\n\t\t\tv-else-if=\"errorCode === 'folder_not_found'\"\n\t\t\tclass=\"cn-files-widget__state cn-files-widget__state--not-found\">\n\t\t\t{{ t('nextcloud-vue', 'Folder no longer exists.') }}\n\t\t</div>\n\n\t\t<div\n\t\t\tv-else-if=\"errorCode\"\n\t\t\tclass=\"cn-files-widget__state cn-files-widget__state--error\">\n\t\t\t{{ t('nextcloud-vue', 'Failed to load folder contents.') }}\n\t\t\t<button\n\t\t\t\ttype=\"button\"\n\t\t\t\tclass=\"cn-files-widget__retry\"\n\t\t\t\t@click=\"fetchContents\">\n\t\t\t\t{{ t('nextcloud-vue', 'Retry') }}\n\t\t\t</button>\n\t\t</div>\n\n\t\t<div\n\t\t\tv-else-if=\"filteredItems.length === 0 && !searchQuery\"\n\t\t\tclass=\"cn-files-widget__state cn-files-widget__state--empty\">\n\t\t\t{{ t('nextcloud-vue', 'This folder is empty.') }}\n\t\t</div>\n\n\t\t<div\n\t\t\tv-else-if=\"filteredItems.length === 0 && searchQuery\"\n\t\t\tclass=\"cn-files-widget__state cn-files-widget__state--empty\">\n\t\t\t{{ noSearchResultsLabel }}\n\t\t</div>\n\n\t\t<ul\n\t\t\tv-else\n\t\t\tclass=\"cn-files-widget__list\"\n\t\t\t:class=\"{ 'cn-files-widget__list--grid': viewMode === 'grid' }\">\n\t\t\t<li\n\t\t\t\tv-for=\"item in filteredItems\"\n\t\t\t\t:key=\"item.fileId\"\n\t\t\t\tclass=\"cn-files-widget__row\"\n\t\t\t\t:class=\"{ 'cn-files-widget__row--folder': item.isFolder }\">\n\t\t\t\t<!-- Files are real links (new tab, copyable); folders browse in-widget. -->\n\t\t\t\t<component\n\t\t\t\t\t:is=\"item.isFolder ? 'button' : 'a'\"\n\t\t\t\t\t:type=\"item.isFolder ? 'button' : undefined\"\n\t\t\t\t\t:href=\"item.isFolder ? undefined : (fileUrl(item.fileId) || undefined)\"\n\t\t\t\t\t:target=\"item.isFolder ? undefined : '_blank'\"\n\t\t\t\t\t:rel=\"item.isFolder ? undefined : 'noopener noreferrer'\"\n\t\t\t\t\tclass=\"cn-files-widget__row-name\"\n\t\t\t\t\t@click=\"item.isFolder && onItemClick(item)\">\n\t\t\t\t\t<span aria-hidden=\"true\" class=\"cn-files-widget__row-icon\">\n\t\t\t\t\t\t<img\n\t\t\t\t\t\t\tv-if=\"showThumbnails && !item.isFolder && item.thumbnailUrl && !failedThumbs[item.fileId]\"\n\t\t\t\t\t\t\t:src=\"item.thumbnailUrl\"\n\t\t\t\t\t\t\talt=\"\"\n\t\t\t\t\t\t\tclass=\"cn-files-widget__row-thumb\"\n\t\t\t\t\t\t\tloading=\"lazy\"\n\t\t\t\t\t\t\t@error=\"onThumbError(item)\">\n\t\t\t\t\t\t<template v-else>{{ item.isFolder ? '📁' : '📄' }}</template>\n\t\t\t\t\t</span>\n\t\t\t\t\t<span class=\"cn-files-widget__row-label\">{{ item.name }}</span>\n\t\t\t\t</component>\n\t\t\t\t<span class=\"cn-files-widget__row-modified\">{{ item.modifiedAt }}</span>\n\t\t\t\t<span class=\"cn-files-widget__row-size\">{{ formatSize(item.size, item.isFolder) }}</span>\n\t\t\t\t<button\n\t\t\t\t\tv-if=\"canDeleteItem(item)\"\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"cn-files-widget__row-delete\"\n\t\t\t\t\t:aria-label=\"t('nextcloud-vue', 'Delete {name}', { name: item.name })\"\n\t\t\t\t\t@click=\"confirmDelete(item)\">\n\t\t\t\t\t{{ t('nextcloud-vue', 'Delete') }}\n\t\t\t\t</button>\n\t\t\t</li>\n\t\t</ul>\n\n\t\t<div v-if=\"nextCursor\" class=\"cn-files-widget__pagination\">\n\t\t\t<button\n\t\t\t\ttype=\"button\"\n\t\t\t\tclass=\"cn-files-widget__more\"\n\t\t\t\t@click=\"loadMore\">\n\t\t\t\t{{ t('nextcloud-vue', 'Load more') }}\n\t\t\t</button>\n\t\t</div>\n\n\t\t<CnFilesWidgetDeleteDialog\n\t\t\t:open=\"confirmTarget !== null\"\n\t\t\t:fileName=\"confirmTarget ? confirmTarget.name : ''\"\n\t\t\t@update:open=\"onDeleteDialogToggle\"\n\t\t\t@confirm=\"performDelete\" />\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { generateUrl } from '@nextcloud/router'\nimport CnFilesWidgetDeleteDialog from '../../dialogs/CnFilesWidgetDeleteDialog.vue'\n\n/**\n * CnFilesWidget — an inline Nextcloud Files browser rendered as a dashboard\n * widget (catalog type `files`, migrated from the launchpad `files` widget).\n *\n * NC-INTEGRATION DEPENDENCY: this widget reads folder contents from a host\n * application's `/api/widgets/files/{placementId}/...` endpoints (the same\n * contract the launchpad/mydash backend served) and deep-links file rows into\n * the Nextcloud **Files** app via the canonical `/f/{fileid}` permalink. The\n * `@nextcloud/axios` + `@nextcloud/router` helpers are imported LAZILY at call\n * time so the widget code never hard-couples to a network stack at module load\n * (keeps the no-op css transform path clean and lets a host without the backing\n * endpoint mount the component without side-effects). When the backing endpoint\n * is absent the widget degrades to a localised disabled / empty state rather\n * than throwing — see the `unavailable` data flag and the `folder_not_found` /\n * `no_access` error branches.\n *\n * Every placement instance owns its own navigation stack: the sub-path resets\n * to `/` whenever the `placement` prop changes, so re-configuring the widget\n * root never leaves the user on a stale path.\n *\n * @spec openspec/changes/cn-widget-library/specs/cn-widget-library/spec.md\n */\nexport default {\n\tname: 'CnFilesWidget',\n\n\tcomponents: {\n\t\tCnFilesWidgetDeleteDialog,\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * Persisted content blob configuring the folder + toolbar — see\n\t\t * `CnFilesWidgetForm` for the full shape (`folderPath`, `fileId`,\n\t\t * `viewMode`, `allowUpload`, `allowDelete`, …).\n\t\t */\n\t\tcontent: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/** Placement entity; its `id` scopes the host Files endpoint. */\n\t\tplacement: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/** Whether the dashboard shell is in admin mode. */\n\t\tisAdmin: { // eslint-disable-line vue/no-unused-properties -- the dashboard shell passes this to every widget; declared so it does not fall through to $attrs.\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Whether the dashboard shell is in edit mode. */\n\t\tcanEdit: { // eslint-disable-line vue/no-unused-properties -- the dashboard shell passes this to every widget; declared so it does not fall through to $attrs.\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * App base for the host's files-widget endpoints\n\t\t * (`{apiBase}/api/widgets/files/{placementId}/...`). Lets a consuming\n\t\t * app point the widget at its own backend (e.g. `/apps/launchpad`).\n\t\t * Defaults to `/apps/files`.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tapiBase: {\n\t\t\ttype: String,\n\t\t\tdefault: '/apps/files',\n\t\t},\n\n\t\t/**\n\t\t * Object-bound mode: the OpenRegister object id this widget attaches\n\t\t * files to. When `objectId` + `register` + `schema` are all set (they\n\t\t * are auto-supplied by `CnWidgetGrid`'s detail-page context merge), the\n\t\t * widget lists/uploads/deletes against the object's Nextcloud folder via\n\t\t * the OpenRegister object endpoints instead of the dashboard\n\t\t * `/api/widgets/files/...` contract. Empty (default) keeps the dashboard\n\t\t * placement behaviour unchanged.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tobjectId: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Object-bound mode: the OpenRegister register slug of the bound object.\n\t\t * See the `objectId` prop.\n\t\t *\n\t\t * @type {string}\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 * Object-bound mode: the OpenRegister schema of the bound object. Accepts\n\t\t * either a slug string or the resolved schema object (as merged by\n\t\t * `CnWidgetGrid`); the slug is derived by the `schemaSlug` computed. See\n\t\t * the `objectId` prop.\n\t\t *\n\t\t * @type {string|object}\n\t\t */\n\t\tschema: {\n\t\t\ttype: [String, Object],\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * App base for the OpenRegister object-file endpoints used in\n\t\t * object-bound mode (`{objectApiBase}/objects/{register}/{schema}/{id}/...`).\n\t\t * Defaults to `/apps/openregister/api`.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tobjectApiBase: {\n\t\t\ttype: String,\n\t\t\tdefault: '/apps/openregister/api',\n\t\t},\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\t/**\n\t\t\t * The hidden file input, set by the template's function ref (kept off `$refs` so the ref stays dynamic — see the template).\n\t\t\t *\n\t\t\t * @type {HTMLInputElement|null}\n\t\t\t */\n\t\t\tfileInputEl: null,\n\t\t\titems: [],\n\t\t\tcurrentSubPath: '/',\n\t\t\tloading: false,\n\t\t\terrorCode: null,\n\t\t\tunavailable: false,\n\t\t\tnextCursor: null,\n\t\t\tsearchQuery: '',\n\t\t\tconfirmTarget: null,\n\t\t\t// fileId → true for thumbnails whose <img> failed to load, so the\n\t\t\t// row falls back to the generic icon instead of a broken image.\n\t\t\tfailedThumbs: {},\n\t\t\t// Whether a file drag is currently hovering the widget (drop overlay).\n\t\t\tisDragging: false,\n\t\t\t// Depth counter so nested dragenter/dragleave don't flicker the overlay.\n\t\t\tdragDepth: 0,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Numeric placement id used to scope the host Files endpoint.\n\t\t *\n\t\t * @return {number} the placement id, or `0` when no placement is bound.\n\t\t */\n\t\tplacementId() {\n\t\t\treturn Number(this.placement?.id || 0)\n\t\t},\n\n\t\t/**\n\t\t * The schema slug in object-bound mode. Accepts either a slug string or\n\t\t * the resolved schema object merged by `CnWidgetGrid` (in which case the\n\t\t * slug is read from `slug`/`id`/`uuid`).\n\t\t *\n\t\t * @return {string} the schema slug, or `''` when unavailable.\n\t\t */\n\t\tschemaSlug() {\n\t\t\tconst s = this.schema\n\t\t\tif (typeof s === 'string') {\n\t\t\t\treturn s\n\t\t\t}\n\t\t\treturn (s && (s.slug || s.id || s.uuid)) || ''\n\t\t},\n\n\t\t/**\n\t\t * Whether the widget runs in object-bound mode — i.e. it attaches files\n\t\t * to a specific OpenRegister object rather than a dashboard placement.\n\t\t *\n\t\t * @return {boolean} `true` when `objectId`, `register` and `schemaSlug` are all set.\n\t\t */\n\t\tobjectBound() {\n\t\t\treturn !!(this.objectId && this.register && this.schemaSlug)\n\t\t},\n\n\t\t/**\n\t\t * Stable identity key for the bound object, or `''` in dashboard mode.\n\t\t * Watched to (re)fetch when the object context first resolves (the schema\n\t\t * object arrives after the ids) or when the widget rebinds to another\n\t\t * object.\n\t\t *\n\t\t * @return {string} `register/schema/objectId`, or `''` when not object-bound.\n\t\t */\n\t\tobjectKey() {\n\t\t\treturn this.objectBound\n\t\t\t\t? `${this.register}/${this.schemaSlug}/${this.objectId}`\n\t\t\t\t: ''\n\t\t},\n\n\t\t/**\n\t\t * Whether the upload affordance is enabled by configuration. Defaults to\n\t\t * enabled in BOTH modes — a Files widget is an add-files surface — and is\n\t\t * only suppressed when a placement explicitly sets `allowUpload: false`.\n\t\t * (Object-bound mode already defaulted on, matching the object sidebar's\n\t\t * Files tab; beta extended the same default to dashboard placements.)\n\t\t *\n\t\t * @return {boolean} `true` unless `content.allowUpload` is explicitly false.\n\t\t */\n\t\tallowUpload() {\n\t\t\treturn this.content?.allowUpload !== false\n\t\t},\n\n\t\t/**\n\t\t * Whether per-row delete affordances are enabled by configuration. On by\n\t\t * default in object-bound mode (unless `content.allowDelete` is explicitly\n\t\t * false); opt-in (`content.allowDelete === true`) in dashboard mode.\n\t\t *\n\t\t * @return {boolean} `true` when deletes are allowed.\n\t\t */\n\t\tallowDelete() {\n\t\t\tif (this.objectBound) {\n\t\t\t\treturn this.content?.allowDelete !== false\n\t\t\t}\n\t\t\treturn this.content?.allowDelete === true\n\t\t},\n\n\t\t/**\n\t\t * Layout for the file listing, from `content.viewMode`. `'grid'` renders\n\t\t * the items as a tile grid; any other value (`'list'`, `'tree'`, unset)\n\t\t * renders the default list. Tree navigation is not implemented, so\n\t\t * `'tree'` falls back to the list layout.\n\t\t *\n\t\t * @return {string} `'grid'` or `'list'`.\n\t\t */\n\t\tviewMode() {\n\t\t\treturn this.content?.viewMode === 'grid' ? 'grid' : 'list'\n\t\t},\n\n\t\t/**\n\t\t * Whether file rows show a preview thumbnail (from `content.showThumbnails`).\n\t\t * Defaults to on; only an explicit `false` disables it. Files without a\n\t\t * backend-provided `thumbnailUrl` still fall back to the generic icon.\n\t\t *\n\t\t * @return {boolean} `true` unless `content.showThumbnails` is explicitly false.\n\t\t */\n\t\tshowThumbnails() {\n\t\t\treturn this.content?.showThumbnails !== false\n\t\t},\n\n\t\t/**\n\t\t * Heuristic write-permission check: an empty folder optimistically\n\t\t * surfaces the upload button (the backend re-validates), otherwise at\n\t\t * least one editable child is required.\n\t\t *\n\t\t * @return {boolean} `true` when the viewer is treated as having write access.\n\t\t */\n\t\tviewerCanWrite() {\n\t\t\tif (this.items.length === 0) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\treturn this.items.some((item) => item.canEdit === true)\n\t\t},\n\n\t\t/**\n\t\t * Whether the upload button should render.\n\t\t *\n\t\t * @return {boolean} `true` when upload is allowed, writable, and not errored.\n\t\t */\n\t\tcanShowUpload() {\n\t\t\treturn this.allowUpload && this.viewerCanWrite && !this.errorCode && !this.unavailable\n\t\t},\n\n\t\t/**\n\t\t * The current sub-path split into breadcrumb segments.\n\t\t *\n\t\t * @return {string[]} the path segments (empty at root).\n\t\t */\n\t\tpathSegments() {\n\t\t\tif (!this.currentSubPath || this.currentSubPath === '/') {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\treturn this.currentSubPath\n\t\t\t\t.split('/')\n\t\t\t\t.map((segment) => segment.trim())\n\t\t\t\t.filter((segment) => segment !== '')\n\t\t},\n\n\t\t/**\n\t\t * The items filtered by the in-widget search query.\n\t\t *\n\t\t * @return {Array<object>} the matching items (all items when the query is empty).\n\t\t */\n\t\tfilteredItems() {\n\t\t\tconst query = this.searchQuery.trim().toLowerCase()\n\t\t\tif (query === '') {\n\t\t\t\treturn this.items\n\t\t\t}\n\t\t\treturn this.items.filter((item) => {\n\t\t\t\tconst name = String(item.name || '').toLowerCase()\n\t\t\t\treturn name.includes(query)\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * The localised \"no results\" label for the active search query.\n\t\t *\n\t\t * @return {string} the no-results message.\n\t\t */\n\t\tnoSearchResultsLabel() {\n\t\t\treturn t(\n\t\t\t\t'nextcloud-vue',\n\t\t\t\t'No files matching \\'{query}\\'',\n\t\t\t\t{ query: this.searchQuery },\n\t\t\t)\n\t\t},\n\t},\n\n\twatch: {\n\t\tplacement: {\n\t\t\timmediate: true,\n\t\t\t/**\n\t\t\t * Reset navigation state and refetch whenever the placement changes.\n\t\t\t * Dashboard mode only — object-bound mode is driven by `objectKey`.\n\t\t\t *\n\t\t\t * @return {void}\n\t\t\t */\n\t\t\thandler() {\n\t\t\t\tif (this.objectBound) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tthis.resetListing()\n\t\t\t\tthis.fetchContents()\n\t\t\t},\n\t\t},\n\n\t\tobjectKey: {\n\t\t\timmediate: true,\n\t\t\t/**\n\t\t\t * Reset and refetch when the bound object first resolves or changes.\n\t\t\t *\n\t\t\t * @param {string} key the new object key (`''` in dashboard mode).\n\t\t\t * @return {void}\n\t\t\t */\n\t\t\thandler(key) {\n\t\t\t\tif (!key) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tthis.resetListing()\n\t\t\t\tthis.fetchContents()\n\t\t\t},\n\t\t},\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/**\n\t\t * Function ref for the hidden file input; a method so the binding is\n\t\t * stable across renders and the vnode stays dynamic.\n\t\t *\n\t\t * @param {HTMLInputElement|null} el The element, or null on unmount.\n\t\t * @return {void}\n\t\t */\n\t\tfileInputRef(el) {\n\t\t\tthis.fileInputEl = el || null\n\t\t},\n\n\t\t/**\n\t\t * Reset the listing/navigation state before a (re)fetch.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tresetListing() {\n\t\t\tthis.currentSubPath = '/'\n\t\t\tthis.items = []\n\t\t\tthis.nextCursor = null\n\t\t\tthis.errorCode = null\n\t\t\tthis.unavailable = false\n\t\t},\n\n\t\t/**\n\t\t * Fetch (or append) one page of folder contents from the host Files\n\t\t * endpoint. Degrades gracefully: a missing endpoint flips `unavailable`,\n\t\t * a 404/403 maps to a typed `errorCode`, and no exception escapes.\n\t\t *\n\t\t * @param {boolean} [append] whether to append to the existing list (pagination).\n\t\t * @return {Promise<void>} resolves when the fetch settles.\n\t\t */\n\t\tasync fetchContents(append = false) {\n\t\t\tif (this.objectBound) {\n\t\t\t\treturn this.fetchObjectFiles()\n\t\t\t}\n\t\t\tif (this.placementId === 0) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tthis.loading = !append\n\t\t\tthis.errorCode = null\n\t\t\tthis.unavailable = false\n\n\t\t\ttry {\n\t\t\t\tconst { default: axios } = await import('@nextcloud/axios')\n\n\t\t\t\tconst url = generateUrl(\n\t\t\t\t\t`${this.apiBase}/api/widgets/files/{placementId}/contents`,\n\t\t\t\t\t{ placementId: this.placementId },\n\t\t\t\t)\n\t\t\t\tconst params = {\n\t\t\t\t\tcurrentPath: this.currentSubPath,\n\t\t\t\t\tlimit: 50,\n\t\t\t\t}\n\t\t\t\tif (append && this.nextCursor) {\n\t\t\t\t\tparams.cursor = this.nextCursor\n\t\t\t\t}\n\n\t\t\t\tconst response = await axios.get(url, { params })\n\t\t\t\tconst data = response?.data || {}\n\t\t\t\tconst nextItems = Array.isArray(data.items) ? data.items : []\n\t\t\t\tthis.items = append ? this.items.concat(nextItems) : nextItems\n\t\t\t\tthis.nextCursor = data.nextCursor || null\n\t\t\t} catch (err) {\n\t\t\t\tconst status = err?.response?.status\n\t\t\t\tconst code = err?.response?.data?.error\n\t\t\t\tif (status === 404 || code === 'folder_not_found') {\n\t\t\t\t\tthis.errorCode = 'folder_not_found'\n\t\t\t\t} else if (status === 403 || code === 'no_access') {\n\t\t\t\t\tthis.errorCode = 'no_access'\n\t\t\t\t} else if (status === undefined) {\n\t\t\t\t\t// No HTTP response at all — the backing Files endpoint is\n\t\t\t\t\t// not installed/reachable; degrade to the disabled state.\n\t\t\t\t\tthis.unavailable = true\n\t\t\t\t} else {\n\t\t\t\t\tthis.errorCode = 'unknown_error'\n\t\t\t\t}\n\t\t\t\tif (!append) {\n\t\t\t\t\tthis.items = []\n\t\t\t\t\tthis.nextCursor = null\n\t\t\t\t}\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Object-bound listing: fetch the bound OpenRegister object's files from\n\t\t * `{objectApiBase}/objects/{register}/{schema}/{id}/files` and map them\n\t\t * onto the widget's flat item shape (no folder navigation). Degrades the\n\t\t * same way as the dashboard fetch: 404/403 → typed `errorCode`, no HTTP\n\t\t * response → `unavailable`.\n\t\t *\n\t\t * @return {Promise<void>} resolves when the fetch settles.\n\t\t */\n\t\tasync fetchObjectFiles() {\n\t\t\tthis.loading = true\n\t\t\tthis.errorCode = null\n\t\t\tthis.unavailable = false\n\n\t\t\ttry {\n\t\t\t\tconst { default: axios } = await import('@nextcloud/axios')\n\n\t\t\t\tconst url = generateUrl(\n\t\t\t\t\t`${this.objectApiBase}/objects/{register}/{schema}/{objectId}/files`,\n\t\t\t\t\t{ register: this.register, schema: this.schemaSlug, objectId: this.objectId },\n\t\t\t\t)\n\t\t\t\tconst response = await axios.get(url)\n\t\t\t\tconst data = response?.data || {}\n\t\t\t\tconst results = Array.isArray(data)\n\t\t\t\t\t? data\n\t\t\t\t\t: (Array.isArray(data.results) ? data.results : [])\n\t\t\t\tthis.items = results.map((file) => {\n\t\t\t\t\tconst mime = file.mimeType || file.mimetype || file.type || ''\n\t\t\t\t\t// SVG: Nextcloud's rasterised /core/preview is usually\n\t\t\t\t\t// unavailable for SVG (no rsvg/imagick), so stream the raw file\n\t\t\t\t\t// from OpenRegister's authenticated per-file download endpoint\n\t\t\t\t\t// (works for the owner's un-shared uploads via the session\n\t\t\t\t\t// cookie; the widget's other calls use the same objectApiBase).\n\t\t\t\t\t// Rendered through the list's <img>, the SVG runs in the\n\t\t\t\t\t// browser's restricted image mode — inline <script> / event\n\t\t\t\t\t// handlers never execute and external refs don't load — so\n\t\t\t\t\t// untrusted SVG is shown safely without a bespoke sandbox.\n\t\t\t\t\t// Other images use the rasterised server preview; anything else\n\t\t\t\t\t// gets the generic icon (a missing preview also falls back via\n\t\t\t\t\t// the <img> @error handler).\n\t\t\t\t\tlet thumbnailUrl = null\n\t\t\t\t\tif (file.id && /^image\\/svg\\+xml$/i.test(mime)) {\n\t\t\t\t\t\tthumbnailUrl = generateUrl(`${this.objectApiBase}/files/{fileId}/download`, { fileId: file.id })\n\t\t\t\t\t} else if (file.id && /^image\\//.test(mime)) {\n\t\t\t\t\t\tthumbnailUrl = generateUrl('/core/preview?fileId={fileId}&x=256&y=256&a=1', { fileId: file.id })\n\t\t\t\t\t}\n\t\t\t\t\treturn {\n\t\t\t\t\t\tname: file.name || file.title || '',\n\t\t\t\t\t\tfileId: file.id,\n\t\t\t\t\t\tisFolder: false,\n\t\t\t\t\t\tsize: file.size,\n\t\t\t\t\t\tmodifiedAt: file.modified || file.updated || '',\n\t\t\t\t\t\tthumbnailUrl,\n\t\t\t\t\t\tcanEdit: true,\n\t\t\t\t\t\tcanDelete: this.allowDelete,\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\tthis.nextCursor = null\n\t\t\t} catch (err) {\n\t\t\t\tconst status = err?.response?.status\n\t\t\t\tif (status === 404) {\n\t\t\t\t\tthis.errorCode = 'folder_not_found'\n\t\t\t\t} else if (status === 403) {\n\t\t\t\t\tthis.errorCode = 'no_access'\n\t\t\t\t} else if (status === undefined) {\n\t\t\t\t\tthis.unavailable = true\n\t\t\t\t} else {\n\t\t\t\t\tthis.errorCode = 'unknown_error'\n\t\t\t\t}\n\t\t\t\tthis.items = []\n\t\t\t\tthis.nextCursor = null\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Load the next page when a cursor is present.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tloadMore() {\n\t\t\tif (this.nextCursor) {\n\t\t\t\tthis.fetchContents(true)\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Handle a row click — descend into folders, open files in the Files app.\n\t\t *\n\t\t * @param {object} item the clicked item.\n\t\t * @return {void}\n\t\t */\n\t\tonItemClick(item) {\n\t\t\tif (item.isFolder) {\n\t\t\t\tconst next = this.joinPath(this.currentSubPath, item.name)\n\t\t\t\tthis.navigateTo(next)\n\t\t\t} else {\n\t\t\t\tthis.openFileInFilesApp(item.fileId)\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Navigate to a sub-path, clearing search + pagination and refetching.\n\t\t *\n\t\t * @param {string} path the target sub-path.\n\t\t * @return {void}\n\t\t */\n\t\tnavigateTo(path) {\n\t\t\tthis.currentSubPath = path || '/'\n\t\t\tthis.searchQuery = ''\n\t\t\tthis.nextCursor = null\n\t\t\tthis.fetchContents()\n\t\t},\n\n\t\t/**\n\t\t * Build the sub-path up to (and including) a breadcrumb segment index.\n\t\t *\n\t\t * @param {number} index the segment index.\n\t\t * @return {string} the assembled sub-path.\n\t\t */\n\t\tsegmentPathTo(index) {\n\t\t\tconst segments = this.pathSegments.slice(0, index + 1)\n\t\t\treturn '/' + segments.join('/')\n\t\t},\n\n\t\t/**\n\t\t * Join a base path and a child name into a normalised sub-path.\n\t\t *\n\t\t * @param {string} base the base path.\n\t\t * @param {string} name the child name.\n\t\t * @return {string} the joined path.\n\t\t */\n\t\tjoinPath(base, name) {\n\t\t\tconst trimmedBase = String(base || '/').replace(/\\/+$/, '')\n\t\t\tconst trimmedName = String(name || '').replace(/^\\/+/, '')\n\t\t\tif (trimmedBase === '' || trimmedBase === '/') {\n\t\t\t\treturn '/' + trimmedName\n\t\t\t}\n\t\t\treturn `${trimmedBase}/${trimmedName}`\n\t\t},\n\n\t\t/**\n\t\t * Deep-link a file into the Nextcloud Files app in a new tab.\n\t\t *\n\t\t * @param {number|string} fileId the Nextcloud file id.\n\t\t * @return {void}\n\t\t */\n\t\topenFileInFilesApp(fileId) {\n\t\t\tconst url = this.fileUrl(fileId)\n\t\t\tif (url) {\n\t\t\t\twindow.open(url, '_blank', 'noopener,noreferrer')\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * The Files-app permalink for a file.\n\t\t *\n\t\t * @param {number|string} fileId the Nextcloud file id.\n\t\t * @return {string} the URL, or '' without a file id.\n\t\t */\n\t\tfileUrl(fileId) {\n\t\t\tif (!fileId) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\t// Canonical permalink: `/f/{fileid}` resolves the id to its folder\n\t\t\t// and opens the file; generateUrl adds `index.php` where needed.\n\t\t\treturn generateUrl('/f/{fileid}', { fileid: fileId })\n\t\t},\n\n\t\t/**\n\t\t * Whether a per-row delete button should render for an item.\n\t\t *\n\t\t * @param {object} item the item.\n\t\t * @return {boolean} `true` when delete is allowed and the item is deletable.\n\t\t */\n\t\tcanDeleteItem(item) {\n\t\t\treturn this.allowDelete === true && item.canDelete === true\n\t\t},\n\n\t\t/**\n\t\t * Mark an item's thumbnail as failed so the row falls back to the\n\t\t * generic icon. Fires when the preview `<img>` cannot load (e.g. the\n\t\t * preview was not generated in time).\n\t\t *\n\t\t * @param {object} item the item whose thumbnail failed.\n\t\t * @return {void}\n\t\t */\n\t\tonThumbError(item) {\n\t\t\tthis.failedThumbs[item.fileId] = true\n\t\t},\n\n\t\t/**\n\t\t * Open the delete-confirmation modal for an item.\n\t\t *\n\t\t * @param {object} item the item to delete.\n\t\t * @return {void}\n\t\t */\n\t\tconfirmDelete(item) {\n\t\t\tthis.confirmTarget = item\n\t\t},\n\n\t\t/**\n\t\t * React to the delete dialog's open-state changes. Closing it (Cancel,\n\t\t * Esc, click-outside, or the header X) clears the pending target.\n\t\t *\n\t\t * @param {boolean} open the dialog's new open state.\n\t\t * @return {void}\n\t\t */\n\t\tonDeleteDialogToggle(open) {\n\t\t\tif (!open) {\n\t\t\t\tthis.confirmTarget = null\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Delete the confirmed target via the host Files endpoint, refreshing\n\t\t * the listing on failure so the UI stays truthful.\n\t\t *\n\t\t * @return {Promise<void>} resolves when the delete settles.\n\t\t */\n\t\tasync performDelete() {\n\t\t\tconst target = this.confirmTarget\n\t\t\tif (!target) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst { default: axios } = await import('@nextcloud/axios')\n\n\t\t\t\tconst url = this.objectBound\n\t\t\t\t\t? generateUrl(\n\t\t\t\t\t\t\t`${this.objectApiBase}/objects/{register}/{schema}/{objectId}/files/{fileId}`,\n\t\t\t\t\t\t\t{ register: this.register, schema: this.schemaSlug, objectId: this.objectId, fileId: target.fileId },\n\t\t\t\t\t\t)\n\t\t\t\t\t: generateUrl(\n\t\t\t\t\t\t\t`${this.apiBase}/api/widgets/files/{placementId}/files/{fileId}`,\n\t\t\t\t\t\t\t{ placementId: this.placementId, fileId: target.fileId },\n\t\t\t\t\t\t)\n\t\t\t\tawait axios.delete(url)\n\t\t\t\tthis.items = this.items.filter((item) => item.fileId !== target.fileId)\n\t\t\t} catch {\n\t\t\t\tthis.fetchContents()\n\t\t\t} finally {\n\t\t\t\tthis.confirmTarget = null\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Trigger the hidden file input from the upload button.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\ttriggerUpload() {\n\t\t\tif (this.fileInputEl) {\n\t\t\t\tthis.fileInputEl.click()\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Upload the chosen files to the host Files endpoint and refresh.\n\t\t *\n\t\t * @param {Event} event the file-input change event.\n\t\t * @return {Promise<void>} resolves when the upload settles.\n\t\t */\n\t\tasync onFileInputChange(event) {\n\t\t\tconst fileList = event?.target?.files\n\t\t\tawait this.uploadFiles(fileList)\n\t\t\tif (this.fileInputEl) {\n\t\t\t\tthis.fileInputEl.value = ''\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * POST a FileList (from the picker or a drag-and-drop) to the host Files\n\t\t * endpoint, then refresh the listing. No-op when there is nothing to upload\n\t\t * or upload is not permitted; never throws.\n\t\t *\n\t\t * @param {FileList|File[]|null} fileList the files to upload.\n\t\t * @return {Promise<void>} resolves when the upload settles.\n\t\t */\n\t\tasync uploadFiles(fileList) {\n\t\t\tif (!fileList || fileList.length === 0 || !this.canShowUpload) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tconst formData = new FormData()\n\t\t\tfor (let i = 0; i < fileList.length; i++) {\n\t\t\t\tformData.append('files[]', fileList[i])\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tconst { default: axios } = await import('@nextcloud/axios')\n\n\t\t\t\tif (this.objectBound) {\n\t\t\t\t\tconst objectUrl = generateUrl(\n\t\t\t\t\t\t`${this.objectApiBase}/objects/{register}/{schema}/{objectId}/filesMultipart`,\n\t\t\t\t\t\t{ register: this.register, schema: this.schemaSlug, objectId: this.objectId },\n\t\t\t\t\t)\n\t\t\t\t\tawait axios.post(objectUrl, formData)\n\t\t\t\t} else {\n\t\t\t\t\tconst url = generateUrl(\n\t\t\t\t\t\t`${this.apiBase}/api/widgets/files/{placementId}/upload`,\n\t\t\t\t\t\t{ placementId: this.placementId },\n\t\t\t\t\t)\n\t\t\t\t\tawait axios.post(url, formData, {\n\t\t\t\t\t\tparams: { currentPath: this.currentSubPath },\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t\tthis.fetchContents()\n\t\t\t} catch {\n\t\t\t\tthis.fetchContents()\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Track a drag entering the widget (depth-counted so nested elements don't\n\t\t * flicker the drop overlay).\n\t\t *\n\t\t * @param {DragEvent} event the dragenter event.\n\t\t * @return {void}\n\t\t */\n\t\tonDragEnter(event) {\n\t\t\tif (!this.canShowUpload || !this.hasFiles(event)) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tevent.preventDefault()\n\t\t\tthis.dragDepth++\n\t\t\tthis.isDragging = true\n\t\t},\n\n\t\t/**\n\t\t * Keep the browser from opening the dragged file and mark the drop as a copy.\n\t\t *\n\t\t * @param {DragEvent} event the dragover event.\n\t\t * @return {void}\n\t\t */\n\t\tonDragOver(event) {\n\t\t\tif (!this.canShowUpload || !this.hasFiles(event)) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tevent.preventDefault()\n\t\t\tif (event.dataTransfer) {\n\t\t\t\tevent.dataTransfer.dropEffect = 'copy'\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Track a drag leaving the widget, clearing the overlay at depth zero.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tonDragLeave() {\n\t\t\tif (this.dragDepth > 0) {\n\t\t\t\tthis.dragDepth--\n\t\t\t}\n\t\t\tif (this.dragDepth === 0) {\n\t\t\t\tthis.isDragging = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Handle a file drop — upload the dropped files into the current folder.\n\t\t *\n\t\t * @param {DragEvent} event the drop event.\n\t\t * @return {Promise<void>} resolves when the upload settles.\n\t\t */\n\t\tasync onDrop(event) {\n\t\t\tthis.dragDepth = 0\n\t\t\tthis.isDragging = false\n\t\t\tif (!this.canShowUpload || !this.hasFiles(event)) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tevent.preventDefault()\n\t\t\tawait this.uploadFiles(event.dataTransfer?.files)\n\t\t},\n\n\t\t/**\n\t\t * Whether a drag event is carrying files (vs. text / an internal drag).\n\t\t *\n\t\t * @param {DragEvent} event the drag event.\n\t\t * @return {boolean} `true` when the payload includes files.\n\t\t */\n\t\thasFiles(event) {\n\t\t\tconst types = event?.dataTransfer?.types\n\t\t\tif (!types) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treturn Array.from(types).includes('Files')\n\t\t},\n\n\t\t/**\n\t\t * Human-format a byte count (folders render an empty size).\n\t\t *\n\t\t * @param {number} bytes the byte count.\n\t\t * @param {boolean} isFolder whether the item is a folder.\n\t\t * @return {string} the formatted size.\n\t\t */\n\t\tformatSize(bytes, isFolder) {\n\t\t\tif (isFolder) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst value = Number(bytes || 0)\n\t\t\tif (value === 0) {\n\t\t\t\treturn '0 B'\n\t\t\t}\n\t\t\tconst units = ['B', 'KB', 'MB', 'GB', 'TB']\n\t\t\tlet unitIndex = 0\n\t\t\tlet display = value\n\t\t\twhile (display >= 1024 && unitIndex < units.length - 1) {\n\t\t\t\tdisplay = display / 1024\n\t\t\t\tunitIndex++\n\t\t\t}\n\t\t\treturn `${display.toFixed(unitIndex === 0 ? 0 : 1)} ${units[unitIndex]}`\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-files-widget {\n\tposition: relative;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 8px;\n\theight: 100%;\n\toverflow: hidden;\n}\n\n.cn-files-widget--dragging {\n\toutline: 2px dashed var(--color-primary-element);\n\toutline-offset: -2px;\n\tborder-radius: var(--border-radius);\n}\n\n.cn-files-widget__dropzone {\n\tposition: absolute;\n\tinset: 0;\n\tz-index: 5;\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\tpointer-events: none;\n\tbackground: var(--color-primary-element-light, rgba(0, 130, 201, 0.1));\n\tcolor: var(--color-primary-element);\n\tfont-weight: 600;\n\tborder-radius: var(--border-radius);\n}\n\n.cn-files-widget__toolbar {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\tflex-wrap: wrap;\n}\n\n.cn-files-widget__breadcrumb {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tgap: 4px;\n\tflex: 1 1 auto;\n\tmin-width: 0;\n}\n\n.cn-files-widget__crumb {\n\tbackground: transparent;\n\tborder: none;\n\tpadding: 4px 6px;\n\tcursor: pointer;\n\tcolor: var(--color-primary-element);\n\tfont: inherit;\n}\n\n.cn-files-widget__crumb[aria-current='page'] {\n\tcolor: var(--color-main-text);\n\tcursor: default;\n}\n\n.cn-files-widget__separator {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-files-widget__search {\n\tflex: 0 1 200px;\n\tpadding: 4px 8px;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius);\n\tbackground: var(--color-main-background);\n\tcolor: var(--color-main-text);\n}\n\n.cn-files-widget__upload {\n\tpadding: 4px 12px;\n\tborder: 1px solid var(--color-primary-element);\n\tbackground: var(--color-primary-element);\n\tcolor: var(--color-primary-element-text);\n\tborder-radius: var(--border-radius);\n\tcursor: pointer;\n}\n\n.cn-files-widget__file-input {\n\tposition: absolute;\n\twidth: 1px;\n\theight: 1px;\n\tpadding: 0;\n\tmargin: -1px;\n\toverflow: hidden;\n\tclip-path: inset(50%);\n\tborder: 0;\n}\n\n.cn-files-widget__state {\n\tpadding: 16px;\n\tcolor: var(--color-text-maxcontrast);\n\ttext-align: center;\n}\n\n.cn-files-widget__retry {\n\tdisplay: block;\n\tmargin: 8px auto 0;\n\tpadding: 4px 12px;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius);\n\tbackground: var(--color-background-hover);\n\tcursor: pointer;\n}\n\n.cn-files-widget__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n\toverflow: auto;\n\tflex: 1 1 auto;\n}\n\n.cn-files-widget__row {\n\tdisplay: grid;\n\tgrid-template-columns: 1fr 140px 80px auto;\n\talign-items: center;\n\tgap: 8px;\n\tpadding: 4px 6px;\n\tborder-bottom: 1px solid var(--color-border);\n}\n\n.cn-files-widget__row-name {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\tbackground: transparent;\n\tborder: none;\n\tpadding: 0;\n\tcursor: pointer;\n\ttext-align: left;\n\tcolor: var(--color-main-text);\n\tfont: inherit;\n\ttext-decoration: none;\n\tmin-width: 0;\n}\n\n.cn-files-widget__row-icon {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tjustify-content: center;\n\tflex: 0 0 auto;\n\twidth: 24px;\n\theight: 24px;\n}\n\n.cn-files-widget__row-thumb {\n\twidth: 100%;\n\theight: 100%;\n\tobject-fit: cover;\n\tborder-radius: var(--border-radius);\n\tbackground-color: var(--color-background-dark);\n}\n\n.cn-files-widget__row-label {\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-files-widget__row-modified,\n.cn-files-widget__row-size {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 12px;\n}\n\n.cn-files-widget__row-delete {\n\tbackground: transparent;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius);\n\tpadding: 2px 8px;\n\tcursor: pointer;\n\tfont-size: 12px;\n\tcolor: var(--color-error);\n}\n\n/* Grid view (content.viewMode === 'grid'): lay the rows out as tiles. */\n.cn-files-widget__list--grid {\n\tdisplay: grid;\n\tgrid-template-columns: repeat(auto-fill, minmax(120px, 1fr));\n\tgap: 8px;\n\tpadding: 4px;\n\talign-content: start;\n}\n\n.cn-files-widget__list--grid .cn-files-widget__row {\n\tgrid-template-columns: 1fr;\n\tjustify-items: center;\n\ttext-align: center;\n\tgap: 4px;\n\tpadding: 10px 6px;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius);\n}\n\n.cn-files-widget__list--grid .cn-files-widget__row-name {\n\tflex-direction: column;\n\talign-items: center;\n\tmax-width: 100%;\n}\n\n.cn-files-widget__list--grid .cn-files-widget__row-label {\n\tmax-width: 100%;\n}\n\n.cn-files-widget__list--grid .cn-files-widget__row-icon {\n\tfont-size: 32px;\n\twidth: 56px;\n\theight: 56px;\n}\n\n/* Keep tiles compact — the modified date is list-view detail. */\n.cn-files-widget__list--grid .cn-files-widget__row-modified {\n\tdisplay: none;\n}\n\n.cn-files-widget__pagination {\n\ttext-align: center;\n\tpadding: 8px 0;\n}\n\n.cn-files-widget__more {\n\tpadding: 4px 12px;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius);\n\tbackground: var(--color-background-hover);\n\tcursor: pointer;\n}\n\n</style>\n"],"names":["CnFilesWidgetDeleteDialog","t"],"mappings":"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"}
1
+ {"version":3,"file":"CnFilesWidget.vue2.js","sources":["../../../../src/components/CnFilesWidget/CnFilesWidget.vue"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<template>\n\t<div\n\t\tclass=\"cn-files-widget\"\n\t\t:class=\"{ 'cn-files-widget--dragging': isDragging }\"\n\t\t@dragenter=\"onDragEnter\"\n\t\t@dragover=\"onDragOver\"\n\t\t@dragleave=\"onDragLeave\"\n\t\t@drop=\"onDrop\">\n\t\t<div class=\"cn-files-widget__toolbar\">\n\t\t\t<nav\n\t\t\t\tv-if=\"!objectBound\"\n\t\t\t\tclass=\"cn-files-widget__breadcrumb\"\n\t\t\t\t:aria-label=\"t('nextcloud-vue', 'Folder breadcrumb')\">\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"cn-files-widget__crumb cn-files-widget__crumb--root\"\n\t\t\t\t\t:aria-current=\"currentSubPath === '/' ? 'page' : null\"\n\t\t\t\t\t@click=\"navigateTo('/')\">\n\t\t\t\t\t{{ t('nextcloud-vue', 'Root') }}\n\t\t\t\t</button>\n\t\t\t\t<template v-for=\"(segment, index) in pathSegments\" :key=\"index\">\n\t\t\t\t\t<span\n\t\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\t\tclass=\"cn-files-widget__separator\">/</span>\n\t\t\t\t\t<button\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tclass=\"cn-files-widget__crumb\"\n\t\t\t\t\t\t:aria-current=\"index === pathSegments.length - 1 ? 'page' : null\"\n\t\t\t\t\t\t@click=\"navigateTo(segmentPathTo(index))\">\n\t\t\t\t\t\t{{ segment }}\n\t\t\t\t\t</button>\n\t\t\t\t</template>\n\t\t\t</nav>\n\n\t\t\t<input\n\t\t\t\tv-model=\"searchQuery\"\n\t\t\t\ttype=\"search\"\n\t\t\t\tclass=\"cn-files-widget__search\"\n\t\t\t\t:aria-label=\"t('nextcloud-vue', 'Search this folder')\"\n\t\t\t\t:placeholder=\"t('nextcloud-vue', 'Search this folder…')\">\n\n\t\t\t<button\n\t\t\t\tv-if=\"canShowUpload\"\n\t\t\t\ttype=\"button\"\n\t\t\t\tclass=\"cn-files-widget__upload\"\n\t\t\t\t@click=\"triggerUpload\">\n\t\t\t\t{{ t('nextcloud-vue', 'Upload File') }}\n\t\t\t</button>\n\t\t\t<!--\n\t\t\t\t`:ref`, not `ref`. Note `:aria-hidden=\"true\"` does NOT save this:\n\t\t\t\ta binding to a LITERAL CONSTANT is constant-folded, so the vnode\n\t\t\t\tstays fully static and the compiler hoists it to module scope.\n\t\t\t\tA hoisted vnode's ref has no owner instance, and `setRef()`'s\n\t\t\t\tnull-owner guard — along with its early `return` — is compiled\n\t\t\t\tout of production builds, so it dereferences null and throws\n\t\t\t\t`Cannot read properties of null (reading 'refs')`, aborting the\n\t\t\t\trender of the page that mounted this widget.\n\n\t\t\t\tConfirmed by instrumenting Vue's own warning to print the ref\n\t\t\t\tname: `[DEBUG ref=\"fileInput\" vnodeType=input]`. This widget is\n\t\t\t\tthe `files` integration leaf, which is how it reaches a detail\n\t\t\t\tpage. See CnFilesTab for the full account of the mechanism.\n\t\t\t-->\n\t\t\t<input\n\t\t\t\t:ref=\"fileInputRef\"\n\t\t\t\ttype=\"file\"\n\t\t\t\tmultiple\n\t\t\t\tclass=\"cn-files-widget__file-input\"\n\t\t\t\t:aria-hidden=\"true\"\n\t\t\t\ttabindex=\"-1\"\n\t\t\t\t@change=\"onFileInputChange\">\n\t\t</div>\n\n\t\t<div\n\t\t\tv-if=\"isDragging && canShowUpload\"\n\t\t\tclass=\"cn-files-widget__dropzone\"\n\t\t\taria-hidden=\"true\">\n\t\t\t{{ t('nextcloud-vue', 'Drop files to upload') }}\n\t\t</div>\n\n\t\t<div v-if=\"loading\" class=\"cn-files-widget__state\">\n\t\t\t{{ t('nextcloud-vue', 'Loading folder…') }}\n\t\t</div>\n\n\t\t<div\n\t\t\tv-else-if=\"unavailable\"\n\t\t\tclass=\"cn-files-widget__state cn-files-widget__state--unavailable\">\n\t\t\t{{ t('nextcloud-vue', 'Files is not available') }}\n\t\t</div>\n\n\t\t<div\n\t\t\tv-else-if=\"errorCode === 'no_access'\"\n\t\t\tclass=\"cn-files-widget__state cn-files-widget__state--no-access\">\n\t\t\t{{ t('nextcloud-vue', 'You don\\'t have access to this folder.') }}\n\t\t</div>\n\n\t\t<div\n\t\t\tv-else-if=\"errorCode === 'folder_not_found'\"\n\t\t\tclass=\"cn-files-widget__state cn-files-widget__state--not-found\">\n\t\t\t{{ t('nextcloud-vue', 'Folder no longer exists.') }}\n\t\t</div>\n\n\t\t<div\n\t\t\tv-else-if=\"errorCode\"\n\t\t\tclass=\"cn-files-widget__state cn-files-widget__state--error\">\n\t\t\t{{ t('nextcloud-vue', 'Failed to load folder contents.') }}\n\t\t\t<button\n\t\t\t\ttype=\"button\"\n\t\t\t\tclass=\"cn-files-widget__retry\"\n\t\t\t\t@click=\"fetchContents\">\n\t\t\t\t{{ t('nextcloud-vue', 'Retry') }}\n\t\t\t</button>\n\t\t</div>\n\n\t\t<div\n\t\t\tv-else-if=\"filteredItems.length === 0 && !searchQuery\"\n\t\t\tclass=\"cn-files-widget__state cn-files-widget__state--empty\">\n\t\t\t{{ t('nextcloud-vue', 'This folder is empty.') }}\n\t\t</div>\n\n\t\t<div\n\t\t\tv-else-if=\"filteredItems.length === 0 && searchQuery\"\n\t\t\tclass=\"cn-files-widget__state cn-files-widget__state--empty\">\n\t\t\t{{ noSearchResultsLabel }}\n\t\t</div>\n\n\t\t<ul\n\t\t\tv-else\n\t\t\tclass=\"cn-files-widget__list\"\n\t\t\t:class=\"{ 'cn-files-widget__list--grid': viewMode === 'grid' }\">\n\t\t\t<li\n\t\t\t\tv-for=\"item in filteredItems\"\n\t\t\t\t:key=\"item.fileId\"\n\t\t\t\tclass=\"cn-files-widget__row\"\n\t\t\t\t:class=\"{ 'cn-files-widget__row--folder': item.isFolder }\">\n\t\t\t\t<!-- Files are real links (new tab, copyable); folders browse in-widget. -->\n\t\t\t\t<component\n\t\t\t\t\t:is=\"item.isFolder ? 'button' : 'a'\"\n\t\t\t\t\t:type=\"item.isFolder ? 'button' : undefined\"\n\t\t\t\t\t:href=\"item.isFolder ? undefined : (fileUrl(item.fileId) || undefined)\"\n\t\t\t\t\t:target=\"item.isFolder ? undefined : '_blank'\"\n\t\t\t\t\t:rel=\"item.isFolder ? undefined : 'noopener noreferrer'\"\n\t\t\t\t\tclass=\"cn-files-widget__row-name\"\n\t\t\t\t\t@click=\"item.isFolder && onItemClick(item)\">\n\t\t\t\t\t<span aria-hidden=\"true\" class=\"cn-files-widget__row-icon\">\n\t\t\t\t\t\t<img\n\t\t\t\t\t\t\tv-if=\"showThumbnails && !item.isFolder && item.thumbnailUrl && !failedThumbs[item.fileId]\"\n\t\t\t\t\t\t\t:src=\"item.thumbnailUrl\"\n\t\t\t\t\t\t\talt=\"\"\n\t\t\t\t\t\t\tclass=\"cn-files-widget__row-thumb\"\n\t\t\t\t\t\t\tloading=\"lazy\"\n\t\t\t\t\t\t\t@error=\"onThumbError(item)\">\n\t\t\t\t\t\t<template v-else>{{ item.isFolder ? '📁' : '📄' }}</template>\n\t\t\t\t\t</span>\n\t\t\t\t\t<span class=\"cn-files-widget__row-label\">{{ item.name }}</span>\n\t\t\t\t</component>\n\t\t\t\t<span class=\"cn-files-widget__row-modified\">{{ item.modifiedAt }}</span>\n\t\t\t\t<span class=\"cn-files-widget__row-size\">{{ formatSize(item.size, item.isFolder) }}</span>\n\t\t\t\t<button\n\t\t\t\t\tv-if=\"canDeleteItem(item)\"\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"cn-files-widget__row-delete\"\n\t\t\t\t\t:aria-label=\"t('nextcloud-vue', 'Delete {name}', { name: item.name })\"\n\t\t\t\t\t@click=\"confirmDelete(item)\">\n\t\t\t\t\t{{ t('nextcloud-vue', 'Delete') }}\n\t\t\t\t</button>\n\t\t\t</li>\n\t\t</ul>\n\n\t\t<div v-if=\"nextCursor\" class=\"cn-files-widget__pagination\">\n\t\t\t<button\n\t\t\t\ttype=\"button\"\n\t\t\t\tclass=\"cn-files-widget__more\"\n\t\t\t\t@click=\"loadMore\">\n\t\t\t\t{{ t('nextcloud-vue', 'Load more') }}\n\t\t\t</button>\n\t\t</div>\n\n\t\t<CnFilesWidgetDeleteDialog\n\t\t\t:open=\"confirmTarget !== null\"\n\t\t\t:fileName=\"confirmTarget ? confirmTarget.name : ''\"\n\t\t\t@update:open=\"onDeleteDialogToggle\"\n\t\t\t@confirm=\"performDelete\" />\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { generateUrl } from '@nextcloud/router'\nimport CnFilesWidgetDeleteDialog from '../../dialogs/CnFilesWidgetDeleteDialog.vue'\n\n/**\n * CnFilesWidget — an inline Nextcloud Files browser rendered as a dashboard\n * widget (catalog type `files`, migrated from the launchpad `files` widget).\n *\n * NC-INTEGRATION DEPENDENCY: this widget reads folder contents from a host\n * application's `/api/widgets/files/{placementId}/...` endpoints (the same\n * contract the launchpad/mydash backend served) and deep-links file rows into\n * the Nextcloud **Files** app via the canonical `/f/{fileid}` permalink. The\n * `@nextcloud/axios` + `@nextcloud/router` helpers are imported LAZILY at call\n * time so the widget code never hard-couples to a network stack at module load\n * (keeps the no-op css transform path clean and lets a host without the backing\n * endpoint mount the component without side-effects). When the backing endpoint\n * is absent the widget degrades to a localised disabled / empty state rather\n * than throwing — see the `unavailable` data flag and the `folder_not_found` /\n * `no_access` error branches.\n *\n * Every placement instance owns its own navigation stack: the sub-path resets\n * to `/` whenever the `placement` prop changes, so re-configuring the widget\n * root never leaves the user on a stale path.\n *\n * @spec openspec/changes/cn-widget-library/specs/cn-widget-library/spec.md\n */\nexport default {\n\tname: 'CnFilesWidget',\n\n\tcomponents: {\n\t\tCnFilesWidgetDeleteDialog,\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * Persisted content blob configuring the folder + toolbar — see\n\t\t * `CnFilesWidgetForm` for the full shape (`folderPath`, `fileId`,\n\t\t * `viewMode`, `allowUpload`, `allowDelete`, …).\n\t\t */\n\t\tcontent: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/** Placement entity; its `id` scopes the host Files endpoint. */\n\t\tplacement: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/** Whether the dashboard shell is in admin mode. */\n\t\tisAdmin: { // eslint-disable-line vue/no-unused-properties -- the dashboard shell passes this to every widget; declared so it does not fall through to $attrs.\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Whether the dashboard shell is in edit mode. */\n\t\tcanEdit: { // eslint-disable-line vue/no-unused-properties -- the dashboard shell passes this to every widget; declared so it does not fall through to $attrs.\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * App base for the host's files-widget endpoints\n\t\t * (`{apiBase}/api/widgets/files/{placementId}/...`). Lets a consuming\n\t\t * app point the widget at its own backend (e.g. `/apps/launchpad`).\n\t\t * Defaults to `/apps/files`.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tapiBase: {\n\t\t\ttype: String,\n\t\t\tdefault: '/apps/files',\n\t\t},\n\n\t\t/**\n\t\t * Object-bound mode: the OpenRegister object id this widget attaches\n\t\t * files to. When `objectId` + `register` + `schema` are all set (they\n\t\t * are auto-supplied by `CnWidgetGrid`'s detail-page context merge), the\n\t\t * widget lists/uploads/deletes against the object's Nextcloud folder via\n\t\t * the OpenRegister object endpoints instead of the dashboard\n\t\t * `/api/widgets/files/...` contract. Empty (default) keeps the dashboard\n\t\t * placement behaviour unchanged.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tobjectId: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Object-bound mode: the OpenRegister register slug of the bound object.\n\t\t * See the `objectId` prop.\n\t\t *\n\t\t * @type {string}\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 * Object-bound mode: the OpenRegister schema of the bound object. Accepts\n\t\t * either a slug string or the resolved schema object (as merged by\n\t\t * `CnWidgetGrid`); the slug is derived by the `schemaSlug` computed. See\n\t\t * the `objectId` prop.\n\t\t *\n\t\t * @type {string|object}\n\t\t */\n\t\tschema: {\n\t\t\ttype: [String, Object],\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * App base for the OpenRegister object-file endpoints used in\n\t\t * object-bound mode (`{objectApiBase}/objects/{register}/{schema}/{id}/...`).\n\t\t * Defaults to `/apps/openregister/api`.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tobjectApiBase: {\n\t\t\ttype: String,\n\t\t\tdefault: '/apps/openregister/api',\n\t\t},\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\t/**\n\t\t\t * The hidden file input, set by the template's function ref (kept off `$refs` so the ref stays dynamic — see the template).\n\t\t\t *\n\t\t\t * @type {HTMLInputElement|null}\n\t\t\t */\n\t\t\tfileInputEl: null,\n\t\t\titems: [],\n\t\t\tcurrentSubPath: '/',\n\t\t\tloading: false,\n\t\t\terrorCode: null,\n\t\t\tunavailable: false,\n\t\t\tnextCursor: null,\n\t\t\tsearchQuery: '',\n\t\t\tconfirmTarget: null,\n\t\t\t// fileId → true for thumbnails whose <img> failed to load, so the\n\t\t\t// row falls back to the generic icon instead of a broken image.\n\t\t\tfailedThumbs: {},\n\t\t\t// Whether a file drag is currently hovering the widget (drop overlay).\n\t\t\tisDragging: false,\n\t\t\t// Depth counter so nested dragenter/dragleave don't flicker the overlay.\n\t\t\tdragDepth: 0,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Numeric placement id used to scope the host Files endpoint.\n\t\t *\n\t\t * @return {number} the placement id, or `0` when no placement is bound.\n\t\t */\n\t\tplacementId() {\n\t\t\treturn Number(this.placement?.id || 0)\n\t\t},\n\n\t\t/**\n\t\t * The schema slug in object-bound mode. Accepts either a slug string or\n\t\t * the resolved schema object merged by `CnWidgetGrid` (in which case the\n\t\t * slug is read from `slug`/`id`/`uuid`).\n\t\t *\n\t\t * @return {string} the schema slug, or `''` when unavailable.\n\t\t */\n\t\tschemaSlug() {\n\t\t\tconst s = this.schema\n\t\t\tif (typeof s === 'string') {\n\t\t\t\treturn s\n\t\t\t}\n\t\t\treturn (s && (s.slug || s.id || s.uuid)) || ''\n\t\t},\n\n\t\t/**\n\t\t * Whether the widget runs in object-bound mode — i.e. it attaches files\n\t\t * to a specific OpenRegister object rather than a dashboard placement.\n\t\t *\n\t\t * @return {boolean} `true` when `objectId`, `register` and `schemaSlug` are all set.\n\t\t */\n\t\tobjectBound() {\n\t\t\treturn !!(this.objectId && this.register && this.schemaSlug)\n\t\t},\n\n\t\t/**\n\t\t * Stable identity key for the bound object, or `''` in dashboard mode.\n\t\t * Watched to (re)fetch when the object context first resolves (the schema\n\t\t * object arrives after the ids) or when the widget rebinds to another\n\t\t * object.\n\t\t *\n\t\t * @return {string} `register/schema/objectId`, or `''` when not object-bound.\n\t\t */\n\t\tobjectKey() {\n\t\t\treturn this.objectBound\n\t\t\t\t? `${this.register}/${this.schemaSlug}/${this.objectId}`\n\t\t\t\t: ''\n\t\t},\n\n\t\t/**\n\t\t * Whether the upload affordance is enabled by configuration. Defaults to\n\t\t * enabled in BOTH modes — a Files widget is an add-files surface — and is\n\t\t * only suppressed when a placement explicitly sets `allowUpload: false`.\n\t\t * (Object-bound mode already defaulted on, matching the object sidebar's\n\t\t * Files tab; beta extended the same default to dashboard placements.)\n\t\t *\n\t\t * @return {boolean} `true` unless `content.allowUpload` is explicitly false.\n\t\t */\n\t\tallowUpload() {\n\t\t\treturn this.content?.allowUpload !== false\n\t\t},\n\n\t\t/**\n\t\t * Whether per-row delete affordances are enabled by configuration. On by\n\t\t * default in object-bound mode (unless `content.allowDelete` is explicitly\n\t\t * false); opt-in (`content.allowDelete === true`) in dashboard mode.\n\t\t *\n\t\t * @return {boolean} `true` when deletes are allowed.\n\t\t */\n\t\tallowDelete() {\n\t\t\tif (this.objectBound) {\n\t\t\t\treturn this.content?.allowDelete !== false\n\t\t\t}\n\t\t\treturn this.content?.allowDelete === true\n\t\t},\n\n\t\t/**\n\t\t * Layout for the file listing, from `content.viewMode`. `'grid'` renders\n\t\t * the items as a tile grid; any other value (`'list'`, `'tree'`, unset)\n\t\t * renders the default list. Tree navigation is not implemented, so\n\t\t * `'tree'` falls back to the list layout.\n\t\t *\n\t\t * @return {string} `'grid'` or `'list'`.\n\t\t */\n\t\tviewMode() {\n\t\t\treturn this.content?.viewMode === 'grid' ? 'grid' : 'list'\n\t\t},\n\n\t\t/**\n\t\t * Whether file rows show a preview thumbnail (from `content.showThumbnails`).\n\t\t * Defaults to on; only an explicit `false` disables it. Files without a\n\t\t * backend-provided `thumbnailUrl` still fall back to the generic icon.\n\t\t *\n\t\t * @return {boolean} `true` unless `content.showThumbnails` is explicitly false.\n\t\t */\n\t\tshowThumbnails() {\n\t\t\treturn this.content?.showThumbnails !== false\n\t\t},\n\n\t\t/**\n\t\t * Heuristic write-permission check: an empty folder optimistically\n\t\t * surfaces the upload button (the backend re-validates), otherwise at\n\t\t * least one editable child is required.\n\t\t *\n\t\t * @return {boolean} `true` when the viewer is treated as having write access.\n\t\t */\n\t\tviewerCanWrite() {\n\t\t\tif (this.items.length === 0) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\treturn this.items.some((item) => item.canEdit === true)\n\t\t},\n\n\t\t/**\n\t\t * Whether the upload button should render.\n\t\t *\n\t\t * @return {boolean} `true` when upload is allowed, writable, and not errored.\n\t\t */\n\t\tcanShowUpload() {\n\t\t\treturn this.allowUpload && this.viewerCanWrite && !this.errorCode && !this.unavailable\n\t\t},\n\n\t\t/**\n\t\t * The current sub-path split into breadcrumb segments.\n\t\t *\n\t\t * @return {string[]} the path segments (empty at root).\n\t\t */\n\t\tpathSegments() {\n\t\t\tif (!this.currentSubPath || this.currentSubPath === '/') {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\treturn this.currentSubPath\n\t\t\t\t.split('/')\n\t\t\t\t.map((segment) => segment.trim())\n\t\t\t\t.filter((segment) => segment !== '')\n\t\t},\n\n\t\t/**\n\t\t * The items filtered by the in-widget search query.\n\t\t *\n\t\t * @return {Array<object>} the matching items (all items when the query is empty).\n\t\t */\n\t\tfilteredItems() {\n\t\t\tconst query = this.searchQuery.trim().toLowerCase()\n\t\t\tif (query === '') {\n\t\t\t\treturn this.items\n\t\t\t}\n\t\t\treturn this.items.filter((item) => {\n\t\t\t\tconst name = String(item.name || '').toLowerCase()\n\t\t\t\treturn name.includes(query)\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * The localised \"no results\" label for the active search query.\n\t\t *\n\t\t * @return {string} the no-results message.\n\t\t */\n\t\tnoSearchResultsLabel() {\n\t\t\treturn t(\n\t\t\t\t'nextcloud-vue',\n\t\t\t\t'No files matching \\'{query}\\'',\n\t\t\t\t{ query: this.searchQuery },\n\t\t\t)\n\t\t},\n\t},\n\n\twatch: {\n\t\tplacement: {\n\t\t\timmediate: true,\n\t\t\t/**\n\t\t\t * Reset navigation state and refetch whenever the placement changes.\n\t\t\t * Dashboard mode only — object-bound mode is driven by `objectKey`.\n\t\t\t *\n\t\t\t * @return {void}\n\t\t\t */\n\t\t\thandler() {\n\t\t\t\tif (this.objectBound) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tthis.resetListing()\n\t\t\t\tthis.fetchContents()\n\t\t\t},\n\t\t},\n\n\t\tobjectKey: {\n\t\t\timmediate: true,\n\t\t\t/**\n\t\t\t * Reset and refetch when the bound object first resolves or changes.\n\t\t\t *\n\t\t\t * @param {string} key the new object key (`''` in dashboard mode).\n\t\t\t * @return {void}\n\t\t\t */\n\t\t\thandler(key) {\n\t\t\t\tif (!key) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tthis.resetListing()\n\t\t\t\tthis.fetchContents()\n\t\t\t},\n\t\t},\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/**\n\t\t * Function ref for the hidden file input; a method so the binding is\n\t\t * stable across renders and the vnode stays dynamic.\n\t\t *\n\t\t * @param {HTMLInputElement|null} el The element, or null on unmount.\n\t\t * @return {void}\n\t\t */\n\t\tfileInputRef(el) {\n\t\t\tthis.fileInputEl = el || null\n\t\t},\n\n\t\t/**\n\t\t * Reset the listing/navigation state before a (re)fetch.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tresetListing() {\n\t\t\tthis.currentSubPath = '/'\n\t\t\tthis.items = []\n\t\t\tthis.nextCursor = null\n\t\t\tthis.errorCode = null\n\t\t\tthis.unavailable = false\n\t\t},\n\n\t\t/**\n\t\t * Fetch (or append) one page of folder contents from the host Files\n\t\t * endpoint. Degrades gracefully: a missing endpoint flips `unavailable`,\n\t\t * a 404/403 maps to a typed `errorCode`, and no exception escapes.\n\t\t *\n\t\t * @param {boolean} [append] whether to append to the existing list (pagination).\n\t\t * @return {Promise<void>} resolves when the fetch settles.\n\t\t */\n\t\tasync fetchContents(append = false) {\n\t\t\tif (this.objectBound) {\n\t\t\t\treturn this.fetchObjectFiles()\n\t\t\t}\n\t\t\tif (this.placementId === 0) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tthis.loading = !append\n\t\t\tthis.errorCode = null\n\t\t\tthis.unavailable = false\n\n\t\t\ttry {\n\t\t\t\tconst { default: axios } = await import('@nextcloud/axios')\n\n\t\t\t\tconst url = generateUrl(\n\t\t\t\t\t`${this.apiBase}/api/widgets/files/{placementId}/contents`,\n\t\t\t\t\t{ placementId: this.placementId },\n\t\t\t\t)\n\t\t\t\tconst params = {\n\t\t\t\t\tcurrentPath: this.currentSubPath,\n\t\t\t\t\tlimit: 50,\n\t\t\t\t}\n\t\t\t\tif (append && this.nextCursor) {\n\t\t\t\t\tparams.cursor = this.nextCursor\n\t\t\t\t}\n\n\t\t\t\tconst response = await axios.get(url, { params })\n\t\t\t\tconst data = response?.data || {}\n\t\t\t\tconst nextItems = Array.isArray(data.items) ? data.items : []\n\t\t\t\tthis.items = append ? this.items.concat(nextItems) : nextItems\n\t\t\t\tthis.nextCursor = data.nextCursor || null\n\t\t\t} catch (err) {\n\t\t\t\tconst status = err?.response?.status\n\t\t\t\tconst code = err?.response?.data?.error\n\t\t\t\tif (status === 404 || code === 'folder_not_found') {\n\t\t\t\t\tthis.errorCode = 'folder_not_found'\n\t\t\t\t} else if (status === 403 || code === 'no_access') {\n\t\t\t\t\tthis.errorCode = 'no_access'\n\t\t\t\t} else if (status === undefined) {\n\t\t\t\t\t// No HTTP response at all — the backing Files endpoint is\n\t\t\t\t\t// not installed/reachable; degrade to the disabled state.\n\t\t\t\t\tthis.unavailable = true\n\t\t\t\t} else {\n\t\t\t\t\tthis.errorCode = 'unknown_error'\n\t\t\t\t}\n\t\t\t\tif (!append) {\n\t\t\t\t\tthis.items = []\n\t\t\t\t\tthis.nextCursor = null\n\t\t\t\t}\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Object-bound listing: fetch the bound OpenRegister object's files from\n\t\t * `{objectApiBase}/objects/{register}/{schema}/{id}/files` and map them\n\t\t * onto the widget's flat item shape (no folder navigation). Degrades the\n\t\t * same way as the dashboard fetch: 404/403 → typed `errorCode`, no HTTP\n\t\t * response → `unavailable`.\n\t\t *\n\t\t * @return {Promise<void>} resolves when the fetch settles.\n\t\t */\n\t\tasync fetchObjectFiles() {\n\t\t\tthis.loading = true\n\t\t\tthis.errorCode = null\n\t\t\tthis.unavailable = false\n\n\t\t\ttry {\n\t\t\t\tconst { default: axios } = await import('@nextcloud/axios')\n\n\t\t\t\tconst url = generateUrl(\n\t\t\t\t\t`${this.objectApiBase}/objects/{register}/{schema}/{objectId}/files`,\n\t\t\t\t\t{ register: this.register, schema: this.schemaSlug, objectId: this.objectId },\n\t\t\t\t)\n\t\t\t\tconst response = await axios.get(url)\n\t\t\t\tconst data = response?.data || {}\n\t\t\t\tconst results = Array.isArray(data)\n\t\t\t\t\t? data\n\t\t\t\t\t: (Array.isArray(data.results) ? data.results : [])\n\t\t\t\tthis.items = results.map((file) => {\n\t\t\t\t\tconst mime = file.mimeType || file.mimetype || file.type || ''\n\t\t\t\t\t// SVG: Nextcloud's rasterised /core/preview is usually\n\t\t\t\t\t// unavailable for SVG (no rsvg/imagick), so stream the raw file\n\t\t\t\t\t// from OpenRegister's authenticated per-file download endpoint\n\t\t\t\t\t// (works for the owner's un-shared uploads via the session\n\t\t\t\t\t// cookie; the widget's other calls use the same objectApiBase).\n\t\t\t\t\t// Rendered through the list's <img>, the SVG runs in the\n\t\t\t\t\t// browser's restricted image mode — inline <script> / event\n\t\t\t\t\t// handlers never execute and external refs don't load — so\n\t\t\t\t\t// untrusted SVG is shown safely without a bespoke sandbox.\n\t\t\t\t\t// Other images use the rasterised server preview; anything else\n\t\t\t\t\t// gets the generic icon (a missing preview also falls back via\n\t\t\t\t\t// the <img> @error handler).\n\t\t\t\t\tlet thumbnailUrl = null\n\t\t\t\t\tif (file.id && /^image\\/svg\\+xml$/i.test(mime)) {\n\t\t\t\t\t\tthumbnailUrl = generateUrl(`${this.objectApiBase}/files/{fileId}/download`, { fileId: file.id })\n\t\t\t\t\t} else if (file.id && /^image\\//.test(mime)) {\n\t\t\t\t\t\tthumbnailUrl = generateUrl('/core/preview?fileId={fileId}&x=256&y=256&a=1', { fileId: file.id })\n\t\t\t\t\t}\n\t\t\t\t\treturn {\n\t\t\t\t\t\tname: file.name || file.title || '',\n\t\t\t\t\t\tfileId: file.id,\n\t\t\t\t\t\tisFolder: false,\n\t\t\t\t\t\tsize: file.size,\n\t\t\t\t\t\tmodifiedAt: file.modified || file.updated || '',\n\t\t\t\t\t\tthumbnailUrl,\n\t\t\t\t\t\tcanEdit: true,\n\t\t\t\t\t\tcanDelete: this.allowDelete,\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\tthis.nextCursor = null\n\t\t\t} catch (err) {\n\t\t\t\tconst status = err?.response?.status\n\t\t\t\tif (status === 404) {\n\t\t\t\t\tthis.errorCode = 'folder_not_found'\n\t\t\t\t} else if (status === 403) {\n\t\t\t\t\tthis.errorCode = 'no_access'\n\t\t\t\t} else if (status === undefined) {\n\t\t\t\t\tthis.unavailable = true\n\t\t\t\t} else {\n\t\t\t\t\tthis.errorCode = 'unknown_error'\n\t\t\t\t}\n\t\t\t\tthis.items = []\n\t\t\t\tthis.nextCursor = null\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Load the next page when a cursor is present.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tloadMore() {\n\t\t\tif (this.nextCursor) {\n\t\t\t\tthis.fetchContents(true)\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Handle a folder row click by descending into it. A file row is a\n\t\t * real link to the Files app and never reaches this.\n\t\t *\n\t\t * @param {object} item the clicked folder item.\n\t\t * @return {void}\n\t\t */\n\t\tonItemClick(item) {\n\t\t\tthis.navigateTo(this.joinPath(this.currentSubPath, item.name))\n\t\t},\n\n\t\t/**\n\t\t * Navigate to a sub-path, clearing search + pagination and refetching.\n\t\t *\n\t\t * @param {string} path the target sub-path.\n\t\t * @return {void}\n\t\t */\n\t\tnavigateTo(path) {\n\t\t\tthis.currentSubPath = path || '/'\n\t\t\tthis.searchQuery = ''\n\t\t\tthis.nextCursor = null\n\t\t\tthis.fetchContents()\n\t\t},\n\n\t\t/**\n\t\t * Build the sub-path up to (and including) a breadcrumb segment index.\n\t\t *\n\t\t * @param {number} index the segment index.\n\t\t * @return {string} the assembled sub-path.\n\t\t */\n\t\tsegmentPathTo(index) {\n\t\t\tconst segments = this.pathSegments.slice(0, index + 1)\n\t\t\treturn '/' + segments.join('/')\n\t\t},\n\n\t\t/**\n\t\t * Join a base path and a child name into a normalised sub-path.\n\t\t *\n\t\t * @param {string} base the base path.\n\t\t * @param {string} name the child name.\n\t\t * @return {string} the joined path.\n\t\t */\n\t\tjoinPath(base, name) {\n\t\t\tconst trimmedBase = String(base || '/').replace(/\\/+$/, '')\n\t\t\tconst trimmedName = String(name || '').replace(/^\\/+/, '')\n\t\t\tif (trimmedBase === '' || trimmedBase === '/') {\n\t\t\t\treturn '/' + trimmedName\n\t\t\t}\n\t\t\treturn `${trimmedBase}/${trimmedName}`\n\t\t},\n\n\t\t/**\n\t\t * The Files-app permalink for a file.\n\t\t *\n\t\t * @param {number|string} fileId the Nextcloud file id.\n\t\t * @return {string} the URL, or '' without a file id.\n\t\t */\n\t\tfileUrl(fileId) {\n\t\t\tif (!fileId) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\t// Canonical permalink: `/f/{fileid}` resolves the id to its folder\n\t\t\t// and opens the file; generateUrl adds `index.php` where needed.\n\t\t\treturn generateUrl('/f/{fileid}', { fileid: fileId })\n\t\t},\n\n\t\t/**\n\t\t * Whether a per-row delete button should render for an item.\n\t\t *\n\t\t * @param {object} item the item.\n\t\t * @return {boolean} `true` when delete is allowed and the item is deletable.\n\t\t */\n\t\tcanDeleteItem(item) {\n\t\t\treturn this.allowDelete === true && item.canDelete === true\n\t\t},\n\n\t\t/**\n\t\t * Mark an item's thumbnail as failed so the row falls back to the\n\t\t * generic icon. Fires when the preview `<img>` cannot load (e.g. the\n\t\t * preview was not generated in time).\n\t\t *\n\t\t * @param {object} item the item whose thumbnail failed.\n\t\t * @return {void}\n\t\t */\n\t\tonThumbError(item) {\n\t\t\tthis.failedThumbs[item.fileId] = true\n\t\t},\n\n\t\t/**\n\t\t * Open the delete-confirmation modal for an item.\n\t\t *\n\t\t * @param {object} item the item to delete.\n\t\t * @return {void}\n\t\t */\n\t\tconfirmDelete(item) {\n\t\t\tthis.confirmTarget = item\n\t\t},\n\n\t\t/**\n\t\t * React to the delete dialog's open-state changes. Closing it (Cancel,\n\t\t * Esc, click-outside, or the header X) clears the pending target.\n\t\t *\n\t\t * @param {boolean} open the dialog's new open state.\n\t\t * @return {void}\n\t\t */\n\t\tonDeleteDialogToggle(open) {\n\t\t\tif (!open) {\n\t\t\t\tthis.confirmTarget = null\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Delete the confirmed target via the host Files endpoint, refreshing\n\t\t * the listing on failure so the UI stays truthful.\n\t\t *\n\t\t * @return {Promise<void>} resolves when the delete settles.\n\t\t */\n\t\tasync performDelete() {\n\t\t\tconst target = this.confirmTarget\n\t\t\tif (!target) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst { default: axios } = await import('@nextcloud/axios')\n\n\t\t\t\tconst url = this.objectBound\n\t\t\t\t\t? generateUrl(\n\t\t\t\t\t\t\t`${this.objectApiBase}/objects/{register}/{schema}/{objectId}/files/{fileId}`,\n\t\t\t\t\t\t\t{ register: this.register, schema: this.schemaSlug, objectId: this.objectId, fileId: target.fileId },\n\t\t\t\t\t\t)\n\t\t\t\t\t: generateUrl(\n\t\t\t\t\t\t\t`${this.apiBase}/api/widgets/files/{placementId}/files/{fileId}`,\n\t\t\t\t\t\t\t{ placementId: this.placementId, fileId: target.fileId },\n\t\t\t\t\t\t)\n\t\t\t\tawait axios.delete(url)\n\t\t\t\tthis.items = this.items.filter((item) => item.fileId !== target.fileId)\n\t\t\t} catch {\n\t\t\t\tthis.fetchContents()\n\t\t\t} finally {\n\t\t\t\tthis.confirmTarget = null\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Trigger the hidden file input from the upload button.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\ttriggerUpload() {\n\t\t\tif (this.fileInputEl) {\n\t\t\t\tthis.fileInputEl.click()\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Upload the chosen files to the host Files endpoint and refresh.\n\t\t *\n\t\t * @param {Event} event the file-input change event.\n\t\t * @return {Promise<void>} resolves when the upload settles.\n\t\t */\n\t\tasync onFileInputChange(event) {\n\t\t\tconst fileList = event?.target?.files\n\t\t\tawait this.uploadFiles(fileList)\n\t\t\tif (this.fileInputEl) {\n\t\t\t\tthis.fileInputEl.value = ''\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * POST a FileList (from the picker or a drag-and-drop) to the host Files\n\t\t * endpoint, then refresh the listing. No-op when there is nothing to upload\n\t\t * or upload is not permitted; never throws.\n\t\t *\n\t\t * @param {FileList|File[]|null} fileList the files to upload.\n\t\t * @return {Promise<void>} resolves when the upload settles.\n\t\t */\n\t\tasync uploadFiles(fileList) {\n\t\t\tif (!fileList || fileList.length === 0 || !this.canShowUpload) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tconst formData = new FormData()\n\t\t\tfor (let i = 0; i < fileList.length; i++) {\n\t\t\t\tformData.append('files[]', fileList[i])\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tconst { default: axios } = await import('@nextcloud/axios')\n\n\t\t\t\tif (this.objectBound) {\n\t\t\t\t\tconst objectUrl = generateUrl(\n\t\t\t\t\t\t`${this.objectApiBase}/objects/{register}/{schema}/{objectId}/filesMultipart`,\n\t\t\t\t\t\t{ register: this.register, schema: this.schemaSlug, objectId: this.objectId },\n\t\t\t\t\t)\n\t\t\t\t\tawait axios.post(objectUrl, formData)\n\t\t\t\t} else {\n\t\t\t\t\tconst url = generateUrl(\n\t\t\t\t\t\t`${this.apiBase}/api/widgets/files/{placementId}/upload`,\n\t\t\t\t\t\t{ placementId: this.placementId },\n\t\t\t\t\t)\n\t\t\t\t\tawait axios.post(url, formData, {\n\t\t\t\t\t\tparams: { currentPath: this.currentSubPath },\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t\tthis.fetchContents()\n\t\t\t} catch {\n\t\t\t\tthis.fetchContents()\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Track a drag entering the widget (depth-counted so nested elements don't\n\t\t * flicker the drop overlay).\n\t\t *\n\t\t * @param {DragEvent} event the dragenter event.\n\t\t * @return {void}\n\t\t */\n\t\tonDragEnter(event) {\n\t\t\tif (!this.canShowUpload || !this.hasFiles(event)) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tevent.preventDefault()\n\t\t\tthis.dragDepth++\n\t\t\tthis.isDragging = true\n\t\t},\n\n\t\t/**\n\t\t * Keep the browser from opening the dragged file and mark the drop as a copy.\n\t\t *\n\t\t * @param {DragEvent} event the dragover event.\n\t\t * @return {void}\n\t\t */\n\t\tonDragOver(event) {\n\t\t\tif (!this.canShowUpload || !this.hasFiles(event)) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tevent.preventDefault()\n\t\t\tif (event.dataTransfer) {\n\t\t\t\tevent.dataTransfer.dropEffect = 'copy'\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Track a drag leaving the widget, clearing the overlay at depth zero.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tonDragLeave() {\n\t\t\tif (this.dragDepth > 0) {\n\t\t\t\tthis.dragDepth--\n\t\t\t}\n\t\t\tif (this.dragDepth === 0) {\n\t\t\t\tthis.isDragging = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Handle a file drop — upload the dropped files into the current folder.\n\t\t *\n\t\t * @param {DragEvent} event the drop event.\n\t\t * @return {Promise<void>} resolves when the upload settles.\n\t\t */\n\t\tasync onDrop(event) {\n\t\t\tthis.dragDepth = 0\n\t\t\tthis.isDragging = false\n\t\t\tif (!this.canShowUpload || !this.hasFiles(event)) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tevent.preventDefault()\n\t\t\tawait this.uploadFiles(event.dataTransfer?.files)\n\t\t},\n\n\t\t/**\n\t\t * Whether a drag event is carrying files (vs. text / an internal drag).\n\t\t *\n\t\t * @param {DragEvent} event the drag event.\n\t\t * @return {boolean} `true` when the payload includes files.\n\t\t */\n\t\thasFiles(event) {\n\t\t\tconst types = event?.dataTransfer?.types\n\t\t\tif (!types) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treturn Array.from(types).includes('Files')\n\t\t},\n\n\t\t/**\n\t\t * Human-format a byte count (folders render an empty size).\n\t\t *\n\t\t * @param {number} bytes the byte count.\n\t\t * @param {boolean} isFolder whether the item is a folder.\n\t\t * @return {string} the formatted size.\n\t\t */\n\t\tformatSize(bytes, isFolder) {\n\t\t\tif (isFolder) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst value = Number(bytes || 0)\n\t\t\tif (value === 0) {\n\t\t\t\treturn '0 B'\n\t\t\t}\n\t\t\tconst units = ['B', 'KB', 'MB', 'GB', 'TB']\n\t\t\tlet unitIndex = 0\n\t\t\tlet display = value\n\t\t\twhile (display >= 1024 && unitIndex < units.length - 1) {\n\t\t\t\tdisplay = display / 1024\n\t\t\t\tunitIndex++\n\t\t\t}\n\t\t\treturn `${display.toFixed(unitIndex === 0 ? 0 : 1)} ${units[unitIndex]}`\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-files-widget {\n\tposition: relative;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 8px;\n\theight: 100%;\n\toverflow: hidden;\n}\n\n.cn-files-widget--dragging {\n\toutline: 2px dashed var(--color-primary-element);\n\toutline-offset: -2px;\n\tborder-radius: var(--border-radius);\n}\n\n.cn-files-widget__dropzone {\n\tposition: absolute;\n\tinset: 0;\n\tz-index: 5;\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\tpointer-events: none;\n\tbackground: var(--color-primary-element-light, rgba(0, 130, 201, 0.1));\n\tcolor: var(--color-primary-element);\n\tfont-weight: 600;\n\tborder-radius: var(--border-radius);\n}\n\n.cn-files-widget__toolbar {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\tflex-wrap: wrap;\n}\n\n.cn-files-widget__breadcrumb {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tgap: 4px;\n\tflex: 1 1 auto;\n\tmin-width: 0;\n}\n\n.cn-files-widget__crumb {\n\tbackground: transparent;\n\tborder: none;\n\tpadding: 4px 6px;\n\tcursor: pointer;\n\tcolor: var(--color-primary-element);\n\tfont: inherit;\n}\n\n.cn-files-widget__crumb[aria-current='page'] {\n\tcolor: var(--color-main-text);\n\tcursor: default;\n}\n\n.cn-files-widget__separator {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-files-widget__search {\n\tflex: 0 1 200px;\n\tpadding: 4px 8px;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius);\n\tbackground: var(--color-main-background);\n\tcolor: var(--color-main-text);\n}\n\n.cn-files-widget__upload {\n\tpadding: 4px 12px;\n\tborder: 1px solid var(--color-primary-element);\n\tbackground: var(--color-primary-element);\n\tcolor: var(--color-primary-element-text);\n\tborder-radius: var(--border-radius);\n\tcursor: pointer;\n}\n\n.cn-files-widget__file-input {\n\tposition: absolute;\n\twidth: 1px;\n\theight: 1px;\n\tpadding: 0;\n\tmargin: -1px;\n\toverflow: hidden;\n\tclip-path: inset(50%);\n\tborder: 0;\n}\n\n.cn-files-widget__state {\n\tpadding: 16px;\n\tcolor: var(--color-text-maxcontrast);\n\ttext-align: center;\n}\n\n.cn-files-widget__retry {\n\tdisplay: block;\n\tmargin: 8px auto 0;\n\tpadding: 4px 12px;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius);\n\tbackground: var(--color-background-hover);\n\tcursor: pointer;\n}\n\n.cn-files-widget__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n\toverflow: auto;\n\tflex: 1 1 auto;\n}\n\n.cn-files-widget__row {\n\tdisplay: grid;\n\tgrid-template-columns: 1fr 140px 80px auto;\n\talign-items: center;\n\tgap: 8px;\n\tpadding: 4px 6px;\n\tborder-bottom: 1px solid var(--color-border);\n}\n\n.cn-files-widget__row-name {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\tbackground: transparent;\n\tborder: none;\n\tpadding: 0;\n\tcursor: pointer;\n\ttext-align: left;\n\tcolor: var(--color-main-text);\n\tfont: inherit;\n\ttext-decoration: none;\n\tmin-width: 0;\n}\n\n.cn-files-widget__row-icon {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tjustify-content: center;\n\tflex: 0 0 auto;\n\twidth: 24px;\n\theight: 24px;\n}\n\n.cn-files-widget__row-thumb {\n\twidth: 100%;\n\theight: 100%;\n\tobject-fit: cover;\n\tborder-radius: var(--border-radius);\n\tbackground-color: var(--color-background-dark);\n}\n\n.cn-files-widget__row-label {\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-files-widget__row-modified,\n.cn-files-widget__row-size {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 12px;\n}\n\n.cn-files-widget__row-delete {\n\tbackground: transparent;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius);\n\tpadding: 2px 8px;\n\tcursor: pointer;\n\tfont-size: 12px;\n\tcolor: var(--color-error);\n}\n\n/* Grid view (content.viewMode === 'grid'): lay the rows out as tiles. */\n.cn-files-widget__list--grid {\n\tdisplay: grid;\n\tgrid-template-columns: repeat(auto-fill, minmax(120px, 1fr));\n\tgap: 8px;\n\tpadding: 4px;\n\talign-content: start;\n}\n\n.cn-files-widget__list--grid .cn-files-widget__row {\n\tgrid-template-columns: 1fr;\n\tjustify-items: center;\n\ttext-align: center;\n\tgap: 4px;\n\tpadding: 10px 6px;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius);\n}\n\n.cn-files-widget__list--grid .cn-files-widget__row-name {\n\tflex-direction: column;\n\talign-items: center;\n\tmax-width: 100%;\n}\n\n.cn-files-widget__list--grid .cn-files-widget__row-label {\n\tmax-width: 100%;\n}\n\n.cn-files-widget__list--grid .cn-files-widget__row-icon {\n\tfont-size: 32px;\n\twidth: 56px;\n\theight: 56px;\n}\n\n/* Keep tiles compact — the modified date is list-view detail. */\n.cn-files-widget__list--grid .cn-files-widget__row-modified {\n\tdisplay: none;\n}\n\n.cn-files-widget__pagination {\n\ttext-align: center;\n\tpadding: 8px 0;\n}\n\n.cn-files-widget__more {\n\tpadding: 4px 12px;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius);\n\tbackground: var(--color-background-hover);\n\tcursor: pointer;\n}\n\n</style>\n"],"names":["CnFilesWidgetDeleteDialog","t"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"CnFilesWidget.vue3.js","sources":["../../../../src/components/CnFilesWidget/CnFilesWidget.vue?vue&type=template&id=cd260c1e&lang.js"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<template>\n\t<div\n\t\tclass=\"cn-files-widget\"\n\t\t:class=\"{ 'cn-files-widget--dragging': isDragging }\"\n\t\t@dragenter=\"onDragEnter\"\n\t\t@dragover=\"onDragOver\"\n\t\t@dragleave=\"onDragLeave\"\n\t\t@drop=\"onDrop\">\n\t\t<div class=\"cn-files-widget__toolbar\">\n\t\t\t<nav\n\t\t\t\tv-if=\"!objectBound\"\n\t\t\t\tclass=\"cn-files-widget__breadcrumb\"\n\t\t\t\t:aria-label=\"t('nextcloud-vue', 'Folder breadcrumb')\">\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"cn-files-widget__crumb cn-files-widget__crumb--root\"\n\t\t\t\t\t:aria-current=\"currentSubPath === '/' ? 'page' : null\"\n\t\t\t\t\t@click=\"navigateTo('/')\">\n\t\t\t\t\t{{ t('nextcloud-vue', 'Root') }}\n\t\t\t\t</button>\n\t\t\t\t<template v-for=\"(segment, index) in pathSegments\" :key=\"index\">\n\t\t\t\t\t<span\n\t\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\t\tclass=\"cn-files-widget__separator\">/</span>\n\t\t\t\t\t<button\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tclass=\"cn-files-widget__crumb\"\n\t\t\t\t\t\t:aria-current=\"index === pathSegments.length - 1 ? 'page' : null\"\n\t\t\t\t\t\t@click=\"navigateTo(segmentPathTo(index))\">\n\t\t\t\t\t\t{{ segment }}\n\t\t\t\t\t</button>\n\t\t\t\t</template>\n\t\t\t</nav>\n\n\t\t\t<input\n\t\t\t\tv-model=\"searchQuery\"\n\t\t\t\ttype=\"search\"\n\t\t\t\tclass=\"cn-files-widget__search\"\n\t\t\t\t:aria-label=\"t('nextcloud-vue', 'Search this folder')\"\n\t\t\t\t:placeholder=\"t('nextcloud-vue', 'Search this folder…')\">\n\n\t\t\t<button\n\t\t\t\tv-if=\"canShowUpload\"\n\t\t\t\ttype=\"button\"\n\t\t\t\tclass=\"cn-files-widget__upload\"\n\t\t\t\t@click=\"triggerUpload\">\n\t\t\t\t{{ t('nextcloud-vue', 'Upload File') }}\n\t\t\t</button>\n\t\t\t<!--\n\t\t\t\t`:ref`, not `ref`. Note `:aria-hidden=\"true\"` does NOT save this:\n\t\t\t\ta binding to a LITERAL CONSTANT is constant-folded, so the vnode\n\t\t\t\tstays fully static and the compiler hoists it to module scope.\n\t\t\t\tA hoisted vnode's ref has no owner instance, and `setRef()`'s\n\t\t\t\tnull-owner guard — along with its early `return` — is compiled\n\t\t\t\tout of production builds, so it dereferences null and throws\n\t\t\t\t`Cannot read properties of null (reading 'refs')`, aborting the\n\t\t\t\trender of the page that mounted this widget.\n\n\t\t\t\tConfirmed by instrumenting Vue's own warning to print the ref\n\t\t\t\tname: `[DEBUG ref=\"fileInput\" vnodeType=input]`. This widget is\n\t\t\t\tthe `files` integration leaf, which is how it reaches a detail\n\t\t\t\tpage. See CnFilesTab for the full account of the mechanism.\n\t\t\t-->\n\t\t\t<input\n\t\t\t\t:ref=\"fileInputRef\"\n\t\t\t\ttype=\"file\"\n\t\t\t\tmultiple\n\t\t\t\tclass=\"cn-files-widget__file-input\"\n\t\t\t\t:aria-hidden=\"true\"\n\t\t\t\ttabindex=\"-1\"\n\t\t\t\t@change=\"onFileInputChange\">\n\t\t</div>\n\n\t\t<div\n\t\t\tv-if=\"isDragging && canShowUpload\"\n\t\t\tclass=\"cn-files-widget__dropzone\"\n\t\t\taria-hidden=\"true\">\n\t\t\t{{ t('nextcloud-vue', 'Drop files to upload') }}\n\t\t</div>\n\n\t\t<div v-if=\"loading\" class=\"cn-files-widget__state\">\n\t\t\t{{ t('nextcloud-vue', 'Loading folder…') }}\n\t\t</div>\n\n\t\t<div\n\t\t\tv-else-if=\"unavailable\"\n\t\t\tclass=\"cn-files-widget__state cn-files-widget__state--unavailable\">\n\t\t\t{{ t('nextcloud-vue', 'Files is not available') }}\n\t\t</div>\n\n\t\t<div\n\t\t\tv-else-if=\"errorCode === 'no_access'\"\n\t\t\tclass=\"cn-files-widget__state cn-files-widget__state--no-access\">\n\t\t\t{{ t('nextcloud-vue', 'You don\\'t have access to this folder.') }}\n\t\t</div>\n\n\t\t<div\n\t\t\tv-else-if=\"errorCode === 'folder_not_found'\"\n\t\t\tclass=\"cn-files-widget__state cn-files-widget__state--not-found\">\n\t\t\t{{ t('nextcloud-vue', 'Folder no longer exists.') }}\n\t\t</div>\n\n\t\t<div\n\t\t\tv-else-if=\"errorCode\"\n\t\t\tclass=\"cn-files-widget__state cn-files-widget__state--error\">\n\t\t\t{{ t('nextcloud-vue', 'Failed to load folder contents.') }}\n\t\t\t<button\n\t\t\t\ttype=\"button\"\n\t\t\t\tclass=\"cn-files-widget__retry\"\n\t\t\t\t@click=\"fetchContents\">\n\t\t\t\t{{ t('nextcloud-vue', 'Retry') }}\n\t\t\t</button>\n\t\t</div>\n\n\t\t<div\n\t\t\tv-else-if=\"filteredItems.length === 0 && !searchQuery\"\n\t\t\tclass=\"cn-files-widget__state cn-files-widget__state--empty\">\n\t\t\t{{ t('nextcloud-vue', 'This folder is empty.') }}\n\t\t</div>\n\n\t\t<div\n\t\t\tv-else-if=\"filteredItems.length === 0 && searchQuery\"\n\t\t\tclass=\"cn-files-widget__state cn-files-widget__state--empty\">\n\t\t\t{{ noSearchResultsLabel }}\n\t\t</div>\n\n\t\t<ul\n\t\t\tv-else\n\t\t\tclass=\"cn-files-widget__list\"\n\t\t\t:class=\"{ 'cn-files-widget__list--grid': viewMode === 'grid' }\">\n\t\t\t<li\n\t\t\t\tv-for=\"item in filteredItems\"\n\t\t\t\t:key=\"item.fileId\"\n\t\t\t\tclass=\"cn-files-widget__row\"\n\t\t\t\t:class=\"{ 'cn-files-widget__row--folder': item.isFolder }\">\n\t\t\t\t<!-- Files are real links (new tab, copyable); folders browse in-widget. -->\n\t\t\t\t<component\n\t\t\t\t\t:is=\"item.isFolder ? 'button' : 'a'\"\n\t\t\t\t\t:type=\"item.isFolder ? 'button' : undefined\"\n\t\t\t\t\t:href=\"item.isFolder ? undefined : (fileUrl(item.fileId) || undefined)\"\n\t\t\t\t\t:target=\"item.isFolder ? undefined : '_blank'\"\n\t\t\t\t\t:rel=\"item.isFolder ? undefined : 'noopener noreferrer'\"\n\t\t\t\t\tclass=\"cn-files-widget__row-name\"\n\t\t\t\t\t@click=\"item.isFolder && onItemClick(item)\">\n\t\t\t\t\t<span aria-hidden=\"true\" class=\"cn-files-widget__row-icon\">\n\t\t\t\t\t\t<img\n\t\t\t\t\t\t\tv-if=\"showThumbnails && !item.isFolder && item.thumbnailUrl && !failedThumbs[item.fileId]\"\n\t\t\t\t\t\t\t:src=\"item.thumbnailUrl\"\n\t\t\t\t\t\t\talt=\"\"\n\t\t\t\t\t\t\tclass=\"cn-files-widget__row-thumb\"\n\t\t\t\t\t\t\tloading=\"lazy\"\n\t\t\t\t\t\t\t@error=\"onThumbError(item)\">\n\t\t\t\t\t\t<template v-else>{{ item.isFolder ? '📁' : '📄' }}</template>\n\t\t\t\t\t</span>\n\t\t\t\t\t<span class=\"cn-files-widget__row-label\">{{ item.name }}</span>\n\t\t\t\t</component>\n\t\t\t\t<span class=\"cn-files-widget__row-modified\">{{ item.modifiedAt }}</span>\n\t\t\t\t<span class=\"cn-files-widget__row-size\">{{ formatSize(item.size, item.isFolder) }}</span>\n\t\t\t\t<button\n\t\t\t\t\tv-if=\"canDeleteItem(item)\"\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"cn-files-widget__row-delete\"\n\t\t\t\t\t:aria-label=\"t('nextcloud-vue', 'Delete {name}', { name: item.name })\"\n\t\t\t\t\t@click=\"confirmDelete(item)\">\n\t\t\t\t\t{{ t('nextcloud-vue', 'Delete') }}\n\t\t\t\t</button>\n\t\t\t</li>\n\t\t</ul>\n\n\t\t<div v-if=\"nextCursor\" class=\"cn-files-widget__pagination\">\n\t\t\t<button\n\t\t\t\ttype=\"button\"\n\t\t\t\tclass=\"cn-files-widget__more\"\n\t\t\t\t@click=\"loadMore\">\n\t\t\t\t{{ t('nextcloud-vue', 'Load more') }}\n\t\t\t</button>\n\t\t</div>\n\n\t\t<CnFilesWidgetDeleteDialog\n\t\t\t:open=\"confirmTarget !== null\"\n\t\t\t:fileName=\"confirmTarget ? confirmTarget.name : ''\"\n\t\t\t@update:open=\"onDeleteDialogToggle\"\n\t\t\t@confirm=\"performDelete\" />\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { generateUrl } from '@nextcloud/router'\nimport CnFilesWidgetDeleteDialog from '../../dialogs/CnFilesWidgetDeleteDialog.vue'\n\n/**\n * CnFilesWidget — an inline Nextcloud Files browser rendered as a dashboard\n * widget (catalog type `files`, migrated from the launchpad `files` widget).\n *\n * NC-INTEGRATION DEPENDENCY: this widget reads folder contents from a host\n * application's `/api/widgets/files/{placementId}/...` endpoints (the same\n * contract the launchpad/mydash backend served) and deep-links file rows into\n * the Nextcloud **Files** app via the canonical `/f/{fileid}` permalink. The\n * `@nextcloud/axios` + `@nextcloud/router` helpers are imported LAZILY at call\n * time so the widget code never hard-couples to a network stack at module load\n * (keeps the no-op css transform path clean and lets a host without the backing\n * endpoint mount the component without side-effects). When the backing endpoint\n * is absent the widget degrades to a localised disabled / empty state rather\n * than throwing — see the `unavailable` data flag and the `folder_not_found` /\n * `no_access` error branches.\n *\n * Every placement instance owns its own navigation stack: the sub-path resets\n * to `/` whenever the `placement` prop changes, so re-configuring the widget\n * root never leaves the user on a stale path.\n *\n * @spec openspec/changes/cn-widget-library/specs/cn-widget-library/spec.md\n */\nexport default {\n\tname: 'CnFilesWidget',\n\n\tcomponents: {\n\t\tCnFilesWidgetDeleteDialog,\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * Persisted content blob configuring the folder + toolbar — see\n\t\t * `CnFilesWidgetForm` for the full shape (`folderPath`, `fileId`,\n\t\t * `viewMode`, `allowUpload`, `allowDelete`, …).\n\t\t */\n\t\tcontent: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/** Placement entity; its `id` scopes the host Files endpoint. */\n\t\tplacement: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/** Whether the dashboard shell is in admin mode. */\n\t\tisAdmin: { // eslint-disable-line vue/no-unused-properties -- the dashboard shell passes this to every widget; declared so it does not fall through to $attrs.\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Whether the dashboard shell is in edit mode. */\n\t\tcanEdit: { // eslint-disable-line vue/no-unused-properties -- the dashboard shell passes this to every widget; declared so it does not fall through to $attrs.\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * App base for the host's files-widget endpoints\n\t\t * (`{apiBase}/api/widgets/files/{placementId}/...`). Lets a consuming\n\t\t * app point the widget at its own backend (e.g. `/apps/launchpad`).\n\t\t * Defaults to `/apps/files`.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tapiBase: {\n\t\t\ttype: String,\n\t\t\tdefault: '/apps/files',\n\t\t},\n\n\t\t/**\n\t\t * Object-bound mode: the OpenRegister object id this widget attaches\n\t\t * files to. When `objectId` + `register` + `schema` are all set (they\n\t\t * are auto-supplied by `CnWidgetGrid`'s detail-page context merge), the\n\t\t * widget lists/uploads/deletes against the object's Nextcloud folder via\n\t\t * the OpenRegister object endpoints instead of the dashboard\n\t\t * `/api/widgets/files/...` contract. Empty (default) keeps the dashboard\n\t\t * placement behaviour unchanged.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tobjectId: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Object-bound mode: the OpenRegister register slug of the bound object.\n\t\t * See the `objectId` prop.\n\t\t *\n\t\t * @type {string}\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 * Object-bound mode: the OpenRegister schema of the bound object. Accepts\n\t\t * either a slug string or the resolved schema object (as merged by\n\t\t * `CnWidgetGrid`); the slug is derived by the `schemaSlug` computed. See\n\t\t * the `objectId` prop.\n\t\t *\n\t\t * @type {string|object}\n\t\t */\n\t\tschema: {\n\t\t\ttype: [String, Object],\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * App base for the OpenRegister object-file endpoints used in\n\t\t * object-bound mode (`{objectApiBase}/objects/{register}/{schema}/{id}/...`).\n\t\t * Defaults to `/apps/openregister/api`.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tobjectApiBase: {\n\t\t\ttype: String,\n\t\t\tdefault: '/apps/openregister/api',\n\t\t},\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\t/**\n\t\t\t * The hidden file input, set by the template's function ref (kept off `$refs` so the ref stays dynamic — see the template).\n\t\t\t *\n\t\t\t * @type {HTMLInputElement|null}\n\t\t\t */\n\t\t\tfileInputEl: null,\n\t\t\titems: [],\n\t\t\tcurrentSubPath: '/',\n\t\t\tloading: false,\n\t\t\terrorCode: null,\n\t\t\tunavailable: false,\n\t\t\tnextCursor: null,\n\t\t\tsearchQuery: '',\n\t\t\tconfirmTarget: null,\n\t\t\t// fileId → true for thumbnails whose <img> failed to load, so the\n\t\t\t// row falls back to the generic icon instead of a broken image.\n\t\t\tfailedThumbs: {},\n\t\t\t// Whether a file drag is currently hovering the widget (drop overlay).\n\t\t\tisDragging: false,\n\t\t\t// Depth counter so nested dragenter/dragleave don't flicker the overlay.\n\t\t\tdragDepth: 0,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Numeric placement id used to scope the host Files endpoint.\n\t\t *\n\t\t * @return {number} the placement id, or `0` when no placement is bound.\n\t\t */\n\t\tplacementId() {\n\t\t\treturn Number(this.placement?.id || 0)\n\t\t},\n\n\t\t/**\n\t\t * The schema slug in object-bound mode. Accepts either a slug string or\n\t\t * the resolved schema object merged by `CnWidgetGrid` (in which case the\n\t\t * slug is read from `slug`/`id`/`uuid`).\n\t\t *\n\t\t * @return {string} the schema slug, or `''` when unavailable.\n\t\t */\n\t\tschemaSlug() {\n\t\t\tconst s = this.schema\n\t\t\tif (typeof s === 'string') {\n\t\t\t\treturn s\n\t\t\t}\n\t\t\treturn (s && (s.slug || s.id || s.uuid)) || ''\n\t\t},\n\n\t\t/**\n\t\t * Whether the widget runs in object-bound mode — i.e. it attaches files\n\t\t * to a specific OpenRegister object rather than a dashboard placement.\n\t\t *\n\t\t * @return {boolean} `true` when `objectId`, `register` and `schemaSlug` are all set.\n\t\t */\n\t\tobjectBound() {\n\t\t\treturn !!(this.objectId && this.register && this.schemaSlug)\n\t\t},\n\n\t\t/**\n\t\t * Stable identity key for the bound object, or `''` in dashboard mode.\n\t\t * Watched to (re)fetch when the object context first resolves (the schema\n\t\t * object arrives after the ids) or when the widget rebinds to another\n\t\t * object.\n\t\t *\n\t\t * @return {string} `register/schema/objectId`, or `''` when not object-bound.\n\t\t */\n\t\tobjectKey() {\n\t\t\treturn this.objectBound\n\t\t\t\t? `${this.register}/${this.schemaSlug}/${this.objectId}`\n\t\t\t\t: ''\n\t\t},\n\n\t\t/**\n\t\t * Whether the upload affordance is enabled by configuration. Defaults to\n\t\t * enabled in BOTH modes — a Files widget is an add-files surface — and is\n\t\t * only suppressed when a placement explicitly sets `allowUpload: false`.\n\t\t * (Object-bound mode already defaulted on, matching the object sidebar's\n\t\t * Files tab; beta extended the same default to dashboard placements.)\n\t\t *\n\t\t * @return {boolean} `true` unless `content.allowUpload` is explicitly false.\n\t\t */\n\t\tallowUpload() {\n\t\t\treturn this.content?.allowUpload !== false\n\t\t},\n\n\t\t/**\n\t\t * Whether per-row delete affordances are enabled by configuration. On by\n\t\t * default in object-bound mode (unless `content.allowDelete` is explicitly\n\t\t * false); opt-in (`content.allowDelete === true`) in dashboard mode.\n\t\t *\n\t\t * @return {boolean} `true` when deletes are allowed.\n\t\t */\n\t\tallowDelete() {\n\t\t\tif (this.objectBound) {\n\t\t\t\treturn this.content?.allowDelete !== false\n\t\t\t}\n\t\t\treturn this.content?.allowDelete === true\n\t\t},\n\n\t\t/**\n\t\t * Layout for the file listing, from `content.viewMode`. `'grid'` renders\n\t\t * the items as a tile grid; any other value (`'list'`, `'tree'`, unset)\n\t\t * renders the default list. Tree navigation is not implemented, so\n\t\t * `'tree'` falls back to the list layout.\n\t\t *\n\t\t * @return {string} `'grid'` or `'list'`.\n\t\t */\n\t\tviewMode() {\n\t\t\treturn this.content?.viewMode === 'grid' ? 'grid' : 'list'\n\t\t},\n\n\t\t/**\n\t\t * Whether file rows show a preview thumbnail (from `content.showThumbnails`).\n\t\t * Defaults to on; only an explicit `false` disables it. Files without a\n\t\t * backend-provided `thumbnailUrl` still fall back to the generic icon.\n\t\t *\n\t\t * @return {boolean} `true` unless `content.showThumbnails` is explicitly false.\n\t\t */\n\t\tshowThumbnails() {\n\t\t\treturn this.content?.showThumbnails !== false\n\t\t},\n\n\t\t/**\n\t\t * Heuristic write-permission check: an empty folder optimistically\n\t\t * surfaces the upload button (the backend re-validates), otherwise at\n\t\t * least one editable child is required.\n\t\t *\n\t\t * @return {boolean} `true` when the viewer is treated as having write access.\n\t\t */\n\t\tviewerCanWrite() {\n\t\t\tif (this.items.length === 0) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\treturn this.items.some((item) => item.canEdit === true)\n\t\t},\n\n\t\t/**\n\t\t * Whether the upload button should render.\n\t\t *\n\t\t * @return {boolean} `true` when upload is allowed, writable, and not errored.\n\t\t */\n\t\tcanShowUpload() {\n\t\t\treturn this.allowUpload && this.viewerCanWrite && !this.errorCode && !this.unavailable\n\t\t},\n\n\t\t/**\n\t\t * The current sub-path split into breadcrumb segments.\n\t\t *\n\t\t * @return {string[]} the path segments (empty at root).\n\t\t */\n\t\tpathSegments() {\n\t\t\tif (!this.currentSubPath || this.currentSubPath === '/') {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\treturn this.currentSubPath\n\t\t\t\t.split('/')\n\t\t\t\t.map((segment) => segment.trim())\n\t\t\t\t.filter((segment) => segment !== '')\n\t\t},\n\n\t\t/**\n\t\t * The items filtered by the in-widget search query.\n\t\t *\n\t\t * @return {Array<object>} the matching items (all items when the query is empty).\n\t\t */\n\t\tfilteredItems() {\n\t\t\tconst query = this.searchQuery.trim().toLowerCase()\n\t\t\tif (query === '') {\n\t\t\t\treturn this.items\n\t\t\t}\n\t\t\treturn this.items.filter((item) => {\n\t\t\t\tconst name = String(item.name || '').toLowerCase()\n\t\t\t\treturn name.includes(query)\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * The localised \"no results\" label for the active search query.\n\t\t *\n\t\t * @return {string} the no-results message.\n\t\t */\n\t\tnoSearchResultsLabel() {\n\t\t\treturn t(\n\t\t\t\t'nextcloud-vue',\n\t\t\t\t'No files matching \\'{query}\\'',\n\t\t\t\t{ query: this.searchQuery },\n\t\t\t)\n\t\t},\n\t},\n\n\twatch: {\n\t\tplacement: {\n\t\t\timmediate: true,\n\t\t\t/**\n\t\t\t * Reset navigation state and refetch whenever the placement changes.\n\t\t\t * Dashboard mode only — object-bound mode is driven by `objectKey`.\n\t\t\t *\n\t\t\t * @return {void}\n\t\t\t */\n\t\t\thandler() {\n\t\t\t\tif (this.objectBound) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tthis.resetListing()\n\t\t\t\tthis.fetchContents()\n\t\t\t},\n\t\t},\n\n\t\tobjectKey: {\n\t\t\timmediate: true,\n\t\t\t/**\n\t\t\t * Reset and refetch when the bound object first resolves or changes.\n\t\t\t *\n\t\t\t * @param {string} key the new object key (`''` in dashboard mode).\n\t\t\t * @return {void}\n\t\t\t */\n\t\t\thandler(key) {\n\t\t\t\tif (!key) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tthis.resetListing()\n\t\t\t\tthis.fetchContents()\n\t\t\t},\n\t\t},\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/**\n\t\t * Function ref for the hidden file input; a method so the binding is\n\t\t * stable across renders and the vnode stays dynamic.\n\t\t *\n\t\t * @param {HTMLInputElement|null} el The element, or null on unmount.\n\t\t * @return {void}\n\t\t */\n\t\tfileInputRef(el) {\n\t\t\tthis.fileInputEl = el || null\n\t\t},\n\n\t\t/**\n\t\t * Reset the listing/navigation state before a (re)fetch.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tresetListing() {\n\t\t\tthis.currentSubPath = '/'\n\t\t\tthis.items = []\n\t\t\tthis.nextCursor = null\n\t\t\tthis.errorCode = null\n\t\t\tthis.unavailable = false\n\t\t},\n\n\t\t/**\n\t\t * Fetch (or append) one page of folder contents from the host Files\n\t\t * endpoint. Degrades gracefully: a missing endpoint flips `unavailable`,\n\t\t * a 404/403 maps to a typed `errorCode`, and no exception escapes.\n\t\t *\n\t\t * @param {boolean} [append] whether to append to the existing list (pagination).\n\t\t * @return {Promise<void>} resolves when the fetch settles.\n\t\t */\n\t\tasync fetchContents(append = false) {\n\t\t\tif (this.objectBound) {\n\t\t\t\treturn this.fetchObjectFiles()\n\t\t\t}\n\t\t\tif (this.placementId === 0) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tthis.loading = !append\n\t\t\tthis.errorCode = null\n\t\t\tthis.unavailable = false\n\n\t\t\ttry {\n\t\t\t\tconst { default: axios } = await import('@nextcloud/axios')\n\n\t\t\t\tconst url = generateUrl(\n\t\t\t\t\t`${this.apiBase}/api/widgets/files/{placementId}/contents`,\n\t\t\t\t\t{ placementId: this.placementId },\n\t\t\t\t)\n\t\t\t\tconst params = {\n\t\t\t\t\tcurrentPath: this.currentSubPath,\n\t\t\t\t\tlimit: 50,\n\t\t\t\t}\n\t\t\t\tif (append && this.nextCursor) {\n\t\t\t\t\tparams.cursor = this.nextCursor\n\t\t\t\t}\n\n\t\t\t\tconst response = await axios.get(url, { params })\n\t\t\t\tconst data = response?.data || {}\n\t\t\t\tconst nextItems = Array.isArray(data.items) ? data.items : []\n\t\t\t\tthis.items = append ? this.items.concat(nextItems) : nextItems\n\t\t\t\tthis.nextCursor = data.nextCursor || null\n\t\t\t} catch (err) {\n\t\t\t\tconst status = err?.response?.status\n\t\t\t\tconst code = err?.response?.data?.error\n\t\t\t\tif (status === 404 || code === 'folder_not_found') {\n\t\t\t\t\tthis.errorCode = 'folder_not_found'\n\t\t\t\t} else if (status === 403 || code === 'no_access') {\n\t\t\t\t\tthis.errorCode = 'no_access'\n\t\t\t\t} else if (status === undefined) {\n\t\t\t\t\t// No HTTP response at all — the backing Files endpoint is\n\t\t\t\t\t// not installed/reachable; degrade to the disabled state.\n\t\t\t\t\tthis.unavailable = true\n\t\t\t\t} else {\n\t\t\t\t\tthis.errorCode = 'unknown_error'\n\t\t\t\t}\n\t\t\t\tif (!append) {\n\t\t\t\t\tthis.items = []\n\t\t\t\t\tthis.nextCursor = null\n\t\t\t\t}\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Object-bound listing: fetch the bound OpenRegister object's files from\n\t\t * `{objectApiBase}/objects/{register}/{schema}/{id}/files` and map them\n\t\t * onto the widget's flat item shape (no folder navigation). Degrades the\n\t\t * same way as the dashboard fetch: 404/403 → typed `errorCode`, no HTTP\n\t\t * response → `unavailable`.\n\t\t *\n\t\t * @return {Promise<void>} resolves when the fetch settles.\n\t\t */\n\t\tasync fetchObjectFiles() {\n\t\t\tthis.loading = true\n\t\t\tthis.errorCode = null\n\t\t\tthis.unavailable = false\n\n\t\t\ttry {\n\t\t\t\tconst { default: axios } = await import('@nextcloud/axios')\n\n\t\t\t\tconst url = generateUrl(\n\t\t\t\t\t`${this.objectApiBase}/objects/{register}/{schema}/{objectId}/files`,\n\t\t\t\t\t{ register: this.register, schema: this.schemaSlug, objectId: this.objectId },\n\t\t\t\t)\n\t\t\t\tconst response = await axios.get(url)\n\t\t\t\tconst data = response?.data || {}\n\t\t\t\tconst results = Array.isArray(data)\n\t\t\t\t\t? data\n\t\t\t\t\t: (Array.isArray(data.results) ? data.results : [])\n\t\t\t\tthis.items = results.map((file) => {\n\t\t\t\t\tconst mime = file.mimeType || file.mimetype || file.type || ''\n\t\t\t\t\t// SVG: Nextcloud's rasterised /core/preview is usually\n\t\t\t\t\t// unavailable for SVG (no rsvg/imagick), so stream the raw file\n\t\t\t\t\t// from OpenRegister's authenticated per-file download endpoint\n\t\t\t\t\t// (works for the owner's un-shared uploads via the session\n\t\t\t\t\t// cookie; the widget's other calls use the same objectApiBase).\n\t\t\t\t\t// Rendered through the list's <img>, the SVG runs in the\n\t\t\t\t\t// browser's restricted image mode — inline <script> / event\n\t\t\t\t\t// handlers never execute and external refs don't load — so\n\t\t\t\t\t// untrusted SVG is shown safely without a bespoke sandbox.\n\t\t\t\t\t// Other images use the rasterised server preview; anything else\n\t\t\t\t\t// gets the generic icon (a missing preview also falls back via\n\t\t\t\t\t// the <img> @error handler).\n\t\t\t\t\tlet thumbnailUrl = null\n\t\t\t\t\tif (file.id && /^image\\/svg\\+xml$/i.test(mime)) {\n\t\t\t\t\t\tthumbnailUrl = generateUrl(`${this.objectApiBase}/files/{fileId}/download`, { fileId: file.id })\n\t\t\t\t\t} else if (file.id && /^image\\//.test(mime)) {\n\t\t\t\t\t\tthumbnailUrl = generateUrl('/core/preview?fileId={fileId}&x=256&y=256&a=1', { fileId: file.id })\n\t\t\t\t\t}\n\t\t\t\t\treturn {\n\t\t\t\t\t\tname: file.name || file.title || '',\n\t\t\t\t\t\tfileId: file.id,\n\t\t\t\t\t\tisFolder: false,\n\t\t\t\t\t\tsize: file.size,\n\t\t\t\t\t\tmodifiedAt: file.modified || file.updated || '',\n\t\t\t\t\t\tthumbnailUrl,\n\t\t\t\t\t\tcanEdit: true,\n\t\t\t\t\t\tcanDelete: this.allowDelete,\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\tthis.nextCursor = null\n\t\t\t} catch (err) {\n\t\t\t\tconst status = err?.response?.status\n\t\t\t\tif (status === 404) {\n\t\t\t\t\tthis.errorCode = 'folder_not_found'\n\t\t\t\t} else if (status === 403) {\n\t\t\t\t\tthis.errorCode = 'no_access'\n\t\t\t\t} else if (status === undefined) {\n\t\t\t\t\tthis.unavailable = true\n\t\t\t\t} else {\n\t\t\t\t\tthis.errorCode = 'unknown_error'\n\t\t\t\t}\n\t\t\t\tthis.items = []\n\t\t\t\tthis.nextCursor = null\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Load the next page when a cursor is present.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tloadMore() {\n\t\t\tif (this.nextCursor) {\n\t\t\t\tthis.fetchContents(true)\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Handle a row click — descend into folders, open files in the Files app.\n\t\t *\n\t\t * @param {object} item the clicked item.\n\t\t * @return {void}\n\t\t */\n\t\tonItemClick(item) {\n\t\t\tif (item.isFolder) {\n\t\t\t\tconst next = this.joinPath(this.currentSubPath, item.name)\n\t\t\t\tthis.navigateTo(next)\n\t\t\t} else {\n\t\t\t\tthis.openFileInFilesApp(item.fileId)\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Navigate to a sub-path, clearing search + pagination and refetching.\n\t\t *\n\t\t * @param {string} path the target sub-path.\n\t\t * @return {void}\n\t\t */\n\t\tnavigateTo(path) {\n\t\t\tthis.currentSubPath = path || '/'\n\t\t\tthis.searchQuery = ''\n\t\t\tthis.nextCursor = null\n\t\t\tthis.fetchContents()\n\t\t},\n\n\t\t/**\n\t\t * Build the sub-path up to (and including) a breadcrumb segment index.\n\t\t *\n\t\t * @param {number} index the segment index.\n\t\t * @return {string} the assembled sub-path.\n\t\t */\n\t\tsegmentPathTo(index) {\n\t\t\tconst segments = this.pathSegments.slice(0, index + 1)\n\t\t\treturn '/' + segments.join('/')\n\t\t},\n\n\t\t/**\n\t\t * Join a base path and a child name into a normalised sub-path.\n\t\t *\n\t\t * @param {string} base the base path.\n\t\t * @param {string} name the child name.\n\t\t * @return {string} the joined path.\n\t\t */\n\t\tjoinPath(base, name) {\n\t\t\tconst trimmedBase = String(base || '/').replace(/\\/+$/, '')\n\t\t\tconst trimmedName = String(name || '').replace(/^\\/+/, '')\n\t\t\tif (trimmedBase === '' || trimmedBase === '/') {\n\t\t\t\treturn '/' + trimmedName\n\t\t\t}\n\t\t\treturn `${trimmedBase}/${trimmedName}`\n\t\t},\n\n\t\t/**\n\t\t * Deep-link a file into the Nextcloud Files app in a new tab.\n\t\t *\n\t\t * @param {number|string} fileId the Nextcloud file id.\n\t\t * @return {void}\n\t\t */\n\t\topenFileInFilesApp(fileId) {\n\t\t\tconst url = this.fileUrl(fileId)\n\t\t\tif (url) {\n\t\t\t\twindow.open(url, '_blank', 'noopener,noreferrer')\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * The Files-app permalink for a file.\n\t\t *\n\t\t * @param {number|string} fileId the Nextcloud file id.\n\t\t * @return {string} the URL, or '' without a file id.\n\t\t */\n\t\tfileUrl(fileId) {\n\t\t\tif (!fileId) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\t// Canonical permalink: `/f/{fileid}` resolves the id to its folder\n\t\t\t// and opens the file; generateUrl adds `index.php` where needed.\n\t\t\treturn generateUrl('/f/{fileid}', { fileid: fileId })\n\t\t},\n\n\t\t/**\n\t\t * Whether a per-row delete button should render for an item.\n\t\t *\n\t\t * @param {object} item the item.\n\t\t * @return {boolean} `true` when delete is allowed and the item is deletable.\n\t\t */\n\t\tcanDeleteItem(item) {\n\t\t\treturn this.allowDelete === true && item.canDelete === true\n\t\t},\n\n\t\t/**\n\t\t * Mark an item's thumbnail as failed so the row falls back to the\n\t\t * generic icon. Fires when the preview `<img>` cannot load (e.g. the\n\t\t * preview was not generated in time).\n\t\t *\n\t\t * @param {object} item the item whose thumbnail failed.\n\t\t * @return {void}\n\t\t */\n\t\tonThumbError(item) {\n\t\t\tthis.failedThumbs[item.fileId] = true\n\t\t},\n\n\t\t/**\n\t\t * Open the delete-confirmation modal for an item.\n\t\t *\n\t\t * @param {object} item the item to delete.\n\t\t * @return {void}\n\t\t */\n\t\tconfirmDelete(item) {\n\t\t\tthis.confirmTarget = item\n\t\t},\n\n\t\t/**\n\t\t * React to the delete dialog's open-state changes. Closing it (Cancel,\n\t\t * Esc, click-outside, or the header X) clears the pending target.\n\t\t *\n\t\t * @param {boolean} open the dialog's new open state.\n\t\t * @return {void}\n\t\t */\n\t\tonDeleteDialogToggle(open) {\n\t\t\tif (!open) {\n\t\t\t\tthis.confirmTarget = null\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Delete the confirmed target via the host Files endpoint, refreshing\n\t\t * the listing on failure so the UI stays truthful.\n\t\t *\n\t\t * @return {Promise<void>} resolves when the delete settles.\n\t\t */\n\t\tasync performDelete() {\n\t\t\tconst target = this.confirmTarget\n\t\t\tif (!target) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst { default: axios } = await import('@nextcloud/axios')\n\n\t\t\t\tconst url = this.objectBound\n\t\t\t\t\t? generateUrl(\n\t\t\t\t\t\t\t`${this.objectApiBase}/objects/{register}/{schema}/{objectId}/files/{fileId}`,\n\t\t\t\t\t\t\t{ register: this.register, schema: this.schemaSlug, objectId: this.objectId, fileId: target.fileId },\n\t\t\t\t\t\t)\n\t\t\t\t\t: generateUrl(\n\t\t\t\t\t\t\t`${this.apiBase}/api/widgets/files/{placementId}/files/{fileId}`,\n\t\t\t\t\t\t\t{ placementId: this.placementId, fileId: target.fileId },\n\t\t\t\t\t\t)\n\t\t\t\tawait axios.delete(url)\n\t\t\t\tthis.items = this.items.filter((item) => item.fileId !== target.fileId)\n\t\t\t} catch {\n\t\t\t\tthis.fetchContents()\n\t\t\t} finally {\n\t\t\t\tthis.confirmTarget = null\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Trigger the hidden file input from the upload button.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\ttriggerUpload() {\n\t\t\tif (this.fileInputEl) {\n\t\t\t\tthis.fileInputEl.click()\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Upload the chosen files to the host Files endpoint and refresh.\n\t\t *\n\t\t * @param {Event} event the file-input change event.\n\t\t * @return {Promise<void>} resolves when the upload settles.\n\t\t */\n\t\tasync onFileInputChange(event) {\n\t\t\tconst fileList = event?.target?.files\n\t\t\tawait this.uploadFiles(fileList)\n\t\t\tif (this.fileInputEl) {\n\t\t\t\tthis.fileInputEl.value = ''\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * POST a FileList (from the picker or a drag-and-drop) to the host Files\n\t\t * endpoint, then refresh the listing. No-op when there is nothing to upload\n\t\t * or upload is not permitted; never throws.\n\t\t *\n\t\t * @param {FileList|File[]|null} fileList the files to upload.\n\t\t * @return {Promise<void>} resolves when the upload settles.\n\t\t */\n\t\tasync uploadFiles(fileList) {\n\t\t\tif (!fileList || fileList.length === 0 || !this.canShowUpload) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tconst formData = new FormData()\n\t\t\tfor (let i = 0; i < fileList.length; i++) {\n\t\t\t\tformData.append('files[]', fileList[i])\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tconst { default: axios } = await import('@nextcloud/axios')\n\n\t\t\t\tif (this.objectBound) {\n\t\t\t\t\tconst objectUrl = generateUrl(\n\t\t\t\t\t\t`${this.objectApiBase}/objects/{register}/{schema}/{objectId}/filesMultipart`,\n\t\t\t\t\t\t{ register: this.register, schema: this.schemaSlug, objectId: this.objectId },\n\t\t\t\t\t)\n\t\t\t\t\tawait axios.post(objectUrl, formData)\n\t\t\t\t} else {\n\t\t\t\t\tconst url = generateUrl(\n\t\t\t\t\t\t`${this.apiBase}/api/widgets/files/{placementId}/upload`,\n\t\t\t\t\t\t{ placementId: this.placementId },\n\t\t\t\t\t)\n\t\t\t\t\tawait axios.post(url, formData, {\n\t\t\t\t\t\tparams: { currentPath: this.currentSubPath },\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t\tthis.fetchContents()\n\t\t\t} catch {\n\t\t\t\tthis.fetchContents()\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Track a drag entering the widget (depth-counted so nested elements don't\n\t\t * flicker the drop overlay).\n\t\t *\n\t\t * @param {DragEvent} event the dragenter event.\n\t\t * @return {void}\n\t\t */\n\t\tonDragEnter(event) {\n\t\t\tif (!this.canShowUpload || !this.hasFiles(event)) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tevent.preventDefault()\n\t\t\tthis.dragDepth++\n\t\t\tthis.isDragging = true\n\t\t},\n\n\t\t/**\n\t\t * Keep the browser from opening the dragged file and mark the drop as a copy.\n\t\t *\n\t\t * @param {DragEvent} event the dragover event.\n\t\t * @return {void}\n\t\t */\n\t\tonDragOver(event) {\n\t\t\tif (!this.canShowUpload || !this.hasFiles(event)) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tevent.preventDefault()\n\t\t\tif (event.dataTransfer) {\n\t\t\t\tevent.dataTransfer.dropEffect = 'copy'\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Track a drag leaving the widget, clearing the overlay at depth zero.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tonDragLeave() {\n\t\t\tif (this.dragDepth > 0) {\n\t\t\t\tthis.dragDepth--\n\t\t\t}\n\t\t\tif (this.dragDepth === 0) {\n\t\t\t\tthis.isDragging = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Handle a file drop — upload the dropped files into the current folder.\n\t\t *\n\t\t * @param {DragEvent} event the drop event.\n\t\t * @return {Promise<void>} resolves when the upload settles.\n\t\t */\n\t\tasync onDrop(event) {\n\t\t\tthis.dragDepth = 0\n\t\t\tthis.isDragging = false\n\t\t\tif (!this.canShowUpload || !this.hasFiles(event)) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tevent.preventDefault()\n\t\t\tawait this.uploadFiles(event.dataTransfer?.files)\n\t\t},\n\n\t\t/**\n\t\t * Whether a drag event is carrying files (vs. text / an internal drag).\n\t\t *\n\t\t * @param {DragEvent} event the drag event.\n\t\t * @return {boolean} `true` when the payload includes files.\n\t\t */\n\t\thasFiles(event) {\n\t\t\tconst types = event?.dataTransfer?.types\n\t\t\tif (!types) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treturn Array.from(types).includes('Files')\n\t\t},\n\n\t\t/**\n\t\t * Human-format a byte count (folders render an empty size).\n\t\t *\n\t\t * @param {number} bytes the byte count.\n\t\t * @param {boolean} isFolder whether the item is a folder.\n\t\t * @return {string} the formatted size.\n\t\t */\n\t\tformatSize(bytes, isFolder) {\n\t\t\tif (isFolder) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst value = Number(bytes || 0)\n\t\t\tif (value === 0) {\n\t\t\t\treturn '0 B'\n\t\t\t}\n\t\t\tconst units = ['B', 'KB', 'MB', 'GB', 'TB']\n\t\t\tlet unitIndex = 0\n\t\t\tlet display = value\n\t\t\twhile (display >= 1024 && unitIndex < units.length - 1) {\n\t\t\t\tdisplay = display / 1024\n\t\t\t\tunitIndex++\n\t\t\t}\n\t\t\treturn `${display.toFixed(unitIndex === 0 ? 0 : 1)} ${units[unitIndex]}`\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-files-widget {\n\tposition: relative;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 8px;\n\theight: 100%;\n\toverflow: hidden;\n}\n\n.cn-files-widget--dragging {\n\toutline: 2px dashed var(--color-primary-element);\n\toutline-offset: -2px;\n\tborder-radius: var(--border-radius);\n}\n\n.cn-files-widget__dropzone {\n\tposition: absolute;\n\tinset: 0;\n\tz-index: 5;\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\tpointer-events: none;\n\tbackground: var(--color-primary-element-light, rgba(0, 130, 201, 0.1));\n\tcolor: var(--color-primary-element);\n\tfont-weight: 600;\n\tborder-radius: var(--border-radius);\n}\n\n.cn-files-widget__toolbar {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\tflex-wrap: wrap;\n}\n\n.cn-files-widget__breadcrumb {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tgap: 4px;\n\tflex: 1 1 auto;\n\tmin-width: 0;\n}\n\n.cn-files-widget__crumb {\n\tbackground: transparent;\n\tborder: none;\n\tpadding: 4px 6px;\n\tcursor: pointer;\n\tcolor: var(--color-primary-element);\n\tfont: inherit;\n}\n\n.cn-files-widget__crumb[aria-current='page'] {\n\tcolor: var(--color-main-text);\n\tcursor: default;\n}\n\n.cn-files-widget__separator {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-files-widget__search {\n\tflex: 0 1 200px;\n\tpadding: 4px 8px;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius);\n\tbackground: var(--color-main-background);\n\tcolor: var(--color-main-text);\n}\n\n.cn-files-widget__upload {\n\tpadding: 4px 12px;\n\tborder: 1px solid var(--color-primary-element);\n\tbackground: var(--color-primary-element);\n\tcolor: var(--color-primary-element-text);\n\tborder-radius: var(--border-radius);\n\tcursor: pointer;\n}\n\n.cn-files-widget__file-input {\n\tposition: absolute;\n\twidth: 1px;\n\theight: 1px;\n\tpadding: 0;\n\tmargin: -1px;\n\toverflow: hidden;\n\tclip-path: inset(50%);\n\tborder: 0;\n}\n\n.cn-files-widget__state {\n\tpadding: 16px;\n\tcolor: var(--color-text-maxcontrast);\n\ttext-align: center;\n}\n\n.cn-files-widget__retry {\n\tdisplay: block;\n\tmargin: 8px auto 0;\n\tpadding: 4px 12px;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius);\n\tbackground: var(--color-background-hover);\n\tcursor: pointer;\n}\n\n.cn-files-widget__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n\toverflow: auto;\n\tflex: 1 1 auto;\n}\n\n.cn-files-widget__row {\n\tdisplay: grid;\n\tgrid-template-columns: 1fr 140px 80px auto;\n\talign-items: center;\n\tgap: 8px;\n\tpadding: 4px 6px;\n\tborder-bottom: 1px solid var(--color-border);\n}\n\n.cn-files-widget__row-name {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\tbackground: transparent;\n\tborder: none;\n\tpadding: 0;\n\tcursor: pointer;\n\ttext-align: left;\n\tcolor: var(--color-main-text);\n\tfont: inherit;\n\ttext-decoration: none;\n\tmin-width: 0;\n}\n\n.cn-files-widget__row-icon {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tjustify-content: center;\n\tflex: 0 0 auto;\n\twidth: 24px;\n\theight: 24px;\n}\n\n.cn-files-widget__row-thumb {\n\twidth: 100%;\n\theight: 100%;\n\tobject-fit: cover;\n\tborder-radius: var(--border-radius);\n\tbackground-color: var(--color-background-dark);\n}\n\n.cn-files-widget__row-label {\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-files-widget__row-modified,\n.cn-files-widget__row-size {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 12px;\n}\n\n.cn-files-widget__row-delete {\n\tbackground: transparent;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius);\n\tpadding: 2px 8px;\n\tcursor: pointer;\n\tfont-size: 12px;\n\tcolor: var(--color-error);\n}\n\n/* Grid view (content.viewMode === 'grid'): lay the rows out as tiles. */\n.cn-files-widget__list--grid {\n\tdisplay: grid;\n\tgrid-template-columns: repeat(auto-fill, minmax(120px, 1fr));\n\tgap: 8px;\n\tpadding: 4px;\n\talign-content: start;\n}\n\n.cn-files-widget__list--grid .cn-files-widget__row {\n\tgrid-template-columns: 1fr;\n\tjustify-items: center;\n\ttext-align: center;\n\tgap: 4px;\n\tpadding: 10px 6px;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius);\n}\n\n.cn-files-widget__list--grid .cn-files-widget__row-name {\n\tflex-direction: column;\n\talign-items: center;\n\tmax-width: 100%;\n}\n\n.cn-files-widget__list--grid .cn-files-widget__row-label {\n\tmax-width: 100%;\n}\n\n.cn-files-widget__list--grid .cn-files-widget__row-icon {\n\tfont-size: 32px;\n\twidth: 56px;\n\theight: 56px;\n}\n\n/* Keep tiles compact — the modified date is list-view detail. */\n.cn-files-widget__list--grid .cn-files-widget__row-modified {\n\tdisplay: none;\n}\n\n.cn-files-widget__pagination {\n\ttext-align: center;\n\tpadding: 8px 0;\n}\n\n.cn-files-widget__more {\n\tpadding: 4px 12px;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius);\n\tbackground: var(--color-background-hover);\n\tcursor: pointer;\n}\n\n</style>\n"],"names":["_normalizeClass","_createElementVNode","_Fragment","_renderList","_createElementBlock","_toDisplayString","_createTextVNode","_resolveDynamicComponent","_openBlock","_createVNode"],"mappings":"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"}
1
+ {"version":3,"file":"CnFilesWidget.vue3.js","sources":["../../../../src/components/CnFilesWidget/CnFilesWidget.vue?vue&type=template&id=cd260c1e&lang.js"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<template>\n\t<div\n\t\tclass=\"cn-files-widget\"\n\t\t:class=\"{ 'cn-files-widget--dragging': isDragging }\"\n\t\t@dragenter=\"onDragEnter\"\n\t\t@dragover=\"onDragOver\"\n\t\t@dragleave=\"onDragLeave\"\n\t\t@drop=\"onDrop\">\n\t\t<div class=\"cn-files-widget__toolbar\">\n\t\t\t<nav\n\t\t\t\tv-if=\"!objectBound\"\n\t\t\t\tclass=\"cn-files-widget__breadcrumb\"\n\t\t\t\t:aria-label=\"t('nextcloud-vue', 'Folder breadcrumb')\">\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"cn-files-widget__crumb cn-files-widget__crumb--root\"\n\t\t\t\t\t:aria-current=\"currentSubPath === '/' ? 'page' : null\"\n\t\t\t\t\t@click=\"navigateTo('/')\">\n\t\t\t\t\t{{ t('nextcloud-vue', 'Root') }}\n\t\t\t\t</button>\n\t\t\t\t<template v-for=\"(segment, index) in pathSegments\" :key=\"index\">\n\t\t\t\t\t<span\n\t\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\t\tclass=\"cn-files-widget__separator\">/</span>\n\t\t\t\t\t<button\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tclass=\"cn-files-widget__crumb\"\n\t\t\t\t\t\t:aria-current=\"index === pathSegments.length - 1 ? 'page' : null\"\n\t\t\t\t\t\t@click=\"navigateTo(segmentPathTo(index))\">\n\t\t\t\t\t\t{{ segment }}\n\t\t\t\t\t</button>\n\t\t\t\t</template>\n\t\t\t</nav>\n\n\t\t\t<input\n\t\t\t\tv-model=\"searchQuery\"\n\t\t\t\ttype=\"search\"\n\t\t\t\tclass=\"cn-files-widget__search\"\n\t\t\t\t:aria-label=\"t('nextcloud-vue', 'Search this folder')\"\n\t\t\t\t:placeholder=\"t('nextcloud-vue', 'Search this folder…')\">\n\n\t\t\t<button\n\t\t\t\tv-if=\"canShowUpload\"\n\t\t\t\ttype=\"button\"\n\t\t\t\tclass=\"cn-files-widget__upload\"\n\t\t\t\t@click=\"triggerUpload\">\n\t\t\t\t{{ t('nextcloud-vue', 'Upload File') }}\n\t\t\t</button>\n\t\t\t<!--\n\t\t\t\t`:ref`, not `ref`. Note `:aria-hidden=\"true\"` does NOT save this:\n\t\t\t\ta binding to a LITERAL CONSTANT is constant-folded, so the vnode\n\t\t\t\tstays fully static and the compiler hoists it to module scope.\n\t\t\t\tA hoisted vnode's ref has no owner instance, and `setRef()`'s\n\t\t\t\tnull-owner guard — along with its early `return` — is compiled\n\t\t\t\tout of production builds, so it dereferences null and throws\n\t\t\t\t`Cannot read properties of null (reading 'refs')`, aborting the\n\t\t\t\trender of the page that mounted this widget.\n\n\t\t\t\tConfirmed by instrumenting Vue's own warning to print the ref\n\t\t\t\tname: `[DEBUG ref=\"fileInput\" vnodeType=input]`. This widget is\n\t\t\t\tthe `files` integration leaf, which is how it reaches a detail\n\t\t\t\tpage. See CnFilesTab for the full account of the mechanism.\n\t\t\t-->\n\t\t\t<input\n\t\t\t\t:ref=\"fileInputRef\"\n\t\t\t\ttype=\"file\"\n\t\t\t\tmultiple\n\t\t\t\tclass=\"cn-files-widget__file-input\"\n\t\t\t\t:aria-hidden=\"true\"\n\t\t\t\ttabindex=\"-1\"\n\t\t\t\t@change=\"onFileInputChange\">\n\t\t</div>\n\n\t\t<div\n\t\t\tv-if=\"isDragging && canShowUpload\"\n\t\t\tclass=\"cn-files-widget__dropzone\"\n\t\t\taria-hidden=\"true\">\n\t\t\t{{ t('nextcloud-vue', 'Drop files to upload') }}\n\t\t</div>\n\n\t\t<div v-if=\"loading\" class=\"cn-files-widget__state\">\n\t\t\t{{ t('nextcloud-vue', 'Loading folder…') }}\n\t\t</div>\n\n\t\t<div\n\t\t\tv-else-if=\"unavailable\"\n\t\t\tclass=\"cn-files-widget__state cn-files-widget__state--unavailable\">\n\t\t\t{{ t('nextcloud-vue', 'Files is not available') }}\n\t\t</div>\n\n\t\t<div\n\t\t\tv-else-if=\"errorCode === 'no_access'\"\n\t\t\tclass=\"cn-files-widget__state cn-files-widget__state--no-access\">\n\t\t\t{{ t('nextcloud-vue', 'You don\\'t have access to this folder.') }}\n\t\t</div>\n\n\t\t<div\n\t\t\tv-else-if=\"errorCode === 'folder_not_found'\"\n\t\t\tclass=\"cn-files-widget__state cn-files-widget__state--not-found\">\n\t\t\t{{ t('nextcloud-vue', 'Folder no longer exists.') }}\n\t\t</div>\n\n\t\t<div\n\t\t\tv-else-if=\"errorCode\"\n\t\t\tclass=\"cn-files-widget__state cn-files-widget__state--error\">\n\t\t\t{{ t('nextcloud-vue', 'Failed to load folder contents.') }}\n\t\t\t<button\n\t\t\t\ttype=\"button\"\n\t\t\t\tclass=\"cn-files-widget__retry\"\n\t\t\t\t@click=\"fetchContents\">\n\t\t\t\t{{ t('nextcloud-vue', 'Retry') }}\n\t\t\t</button>\n\t\t</div>\n\n\t\t<div\n\t\t\tv-else-if=\"filteredItems.length === 0 && !searchQuery\"\n\t\t\tclass=\"cn-files-widget__state cn-files-widget__state--empty\">\n\t\t\t{{ t('nextcloud-vue', 'This folder is empty.') }}\n\t\t</div>\n\n\t\t<div\n\t\t\tv-else-if=\"filteredItems.length === 0 && searchQuery\"\n\t\t\tclass=\"cn-files-widget__state cn-files-widget__state--empty\">\n\t\t\t{{ noSearchResultsLabel }}\n\t\t</div>\n\n\t\t<ul\n\t\t\tv-else\n\t\t\tclass=\"cn-files-widget__list\"\n\t\t\t:class=\"{ 'cn-files-widget__list--grid': viewMode === 'grid' }\">\n\t\t\t<li\n\t\t\t\tv-for=\"item in filteredItems\"\n\t\t\t\t:key=\"item.fileId\"\n\t\t\t\tclass=\"cn-files-widget__row\"\n\t\t\t\t:class=\"{ 'cn-files-widget__row--folder': item.isFolder }\">\n\t\t\t\t<!-- Files are real links (new tab, copyable); folders browse in-widget. -->\n\t\t\t\t<component\n\t\t\t\t\t:is=\"item.isFolder ? 'button' : 'a'\"\n\t\t\t\t\t:type=\"item.isFolder ? 'button' : undefined\"\n\t\t\t\t\t:href=\"item.isFolder ? undefined : (fileUrl(item.fileId) || undefined)\"\n\t\t\t\t\t:target=\"item.isFolder ? undefined : '_blank'\"\n\t\t\t\t\t:rel=\"item.isFolder ? undefined : 'noopener noreferrer'\"\n\t\t\t\t\tclass=\"cn-files-widget__row-name\"\n\t\t\t\t\t@click=\"item.isFolder && onItemClick(item)\">\n\t\t\t\t\t<span aria-hidden=\"true\" class=\"cn-files-widget__row-icon\">\n\t\t\t\t\t\t<img\n\t\t\t\t\t\t\tv-if=\"showThumbnails && !item.isFolder && item.thumbnailUrl && !failedThumbs[item.fileId]\"\n\t\t\t\t\t\t\t:src=\"item.thumbnailUrl\"\n\t\t\t\t\t\t\talt=\"\"\n\t\t\t\t\t\t\tclass=\"cn-files-widget__row-thumb\"\n\t\t\t\t\t\t\tloading=\"lazy\"\n\t\t\t\t\t\t\t@error=\"onThumbError(item)\">\n\t\t\t\t\t\t<template v-else>{{ item.isFolder ? '📁' : '📄' }}</template>\n\t\t\t\t\t</span>\n\t\t\t\t\t<span class=\"cn-files-widget__row-label\">{{ item.name }}</span>\n\t\t\t\t</component>\n\t\t\t\t<span class=\"cn-files-widget__row-modified\">{{ item.modifiedAt }}</span>\n\t\t\t\t<span class=\"cn-files-widget__row-size\">{{ formatSize(item.size, item.isFolder) }}</span>\n\t\t\t\t<button\n\t\t\t\t\tv-if=\"canDeleteItem(item)\"\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"cn-files-widget__row-delete\"\n\t\t\t\t\t:aria-label=\"t('nextcloud-vue', 'Delete {name}', { name: item.name })\"\n\t\t\t\t\t@click=\"confirmDelete(item)\">\n\t\t\t\t\t{{ t('nextcloud-vue', 'Delete') }}\n\t\t\t\t</button>\n\t\t\t</li>\n\t\t</ul>\n\n\t\t<div v-if=\"nextCursor\" class=\"cn-files-widget__pagination\">\n\t\t\t<button\n\t\t\t\ttype=\"button\"\n\t\t\t\tclass=\"cn-files-widget__more\"\n\t\t\t\t@click=\"loadMore\">\n\t\t\t\t{{ t('nextcloud-vue', 'Load more') }}\n\t\t\t</button>\n\t\t</div>\n\n\t\t<CnFilesWidgetDeleteDialog\n\t\t\t:open=\"confirmTarget !== null\"\n\t\t\t:fileName=\"confirmTarget ? confirmTarget.name : ''\"\n\t\t\t@update:open=\"onDeleteDialogToggle\"\n\t\t\t@confirm=\"performDelete\" />\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { generateUrl } from '@nextcloud/router'\nimport CnFilesWidgetDeleteDialog from '../../dialogs/CnFilesWidgetDeleteDialog.vue'\n\n/**\n * CnFilesWidget — an inline Nextcloud Files browser rendered as a dashboard\n * widget (catalog type `files`, migrated from the launchpad `files` widget).\n *\n * NC-INTEGRATION DEPENDENCY: this widget reads folder contents from a host\n * application's `/api/widgets/files/{placementId}/...` endpoints (the same\n * contract the launchpad/mydash backend served) and deep-links file rows into\n * the Nextcloud **Files** app via the canonical `/f/{fileid}` permalink. The\n * `@nextcloud/axios` + `@nextcloud/router` helpers are imported LAZILY at call\n * time so the widget code never hard-couples to a network stack at module load\n * (keeps the no-op css transform path clean and lets a host without the backing\n * endpoint mount the component without side-effects). When the backing endpoint\n * is absent the widget degrades to a localised disabled / empty state rather\n * than throwing — see the `unavailable` data flag and the `folder_not_found` /\n * `no_access` error branches.\n *\n * Every placement instance owns its own navigation stack: the sub-path resets\n * to `/` whenever the `placement` prop changes, so re-configuring the widget\n * root never leaves the user on a stale path.\n *\n * @spec openspec/changes/cn-widget-library/specs/cn-widget-library/spec.md\n */\nexport default {\n\tname: 'CnFilesWidget',\n\n\tcomponents: {\n\t\tCnFilesWidgetDeleteDialog,\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * Persisted content blob configuring the folder + toolbar — see\n\t\t * `CnFilesWidgetForm` for the full shape (`folderPath`, `fileId`,\n\t\t * `viewMode`, `allowUpload`, `allowDelete`, …).\n\t\t */\n\t\tcontent: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/** Placement entity; its `id` scopes the host Files endpoint. */\n\t\tplacement: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/** Whether the dashboard shell is in admin mode. */\n\t\tisAdmin: { // eslint-disable-line vue/no-unused-properties -- the dashboard shell passes this to every widget; declared so it does not fall through to $attrs.\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Whether the dashboard shell is in edit mode. */\n\t\tcanEdit: { // eslint-disable-line vue/no-unused-properties -- the dashboard shell passes this to every widget; declared so it does not fall through to $attrs.\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * App base for the host's files-widget endpoints\n\t\t * (`{apiBase}/api/widgets/files/{placementId}/...`). Lets a consuming\n\t\t * app point the widget at its own backend (e.g. `/apps/launchpad`).\n\t\t * Defaults to `/apps/files`.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tapiBase: {\n\t\t\ttype: String,\n\t\t\tdefault: '/apps/files',\n\t\t},\n\n\t\t/**\n\t\t * Object-bound mode: the OpenRegister object id this widget attaches\n\t\t * files to. When `objectId` + `register` + `schema` are all set (they\n\t\t * are auto-supplied by `CnWidgetGrid`'s detail-page context merge), the\n\t\t * widget lists/uploads/deletes against the object's Nextcloud folder via\n\t\t * the OpenRegister object endpoints instead of the dashboard\n\t\t * `/api/widgets/files/...` contract. Empty (default) keeps the dashboard\n\t\t * placement behaviour unchanged.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tobjectId: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Object-bound mode: the OpenRegister register slug of the bound object.\n\t\t * See the `objectId` prop.\n\t\t *\n\t\t * @type {string}\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 * Object-bound mode: the OpenRegister schema of the bound object. Accepts\n\t\t * either a slug string or the resolved schema object (as merged by\n\t\t * `CnWidgetGrid`); the slug is derived by the `schemaSlug` computed. See\n\t\t * the `objectId` prop.\n\t\t *\n\t\t * @type {string|object}\n\t\t */\n\t\tschema: {\n\t\t\ttype: [String, Object],\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * App base for the OpenRegister object-file endpoints used in\n\t\t * object-bound mode (`{objectApiBase}/objects/{register}/{schema}/{id}/...`).\n\t\t * Defaults to `/apps/openregister/api`.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tobjectApiBase: {\n\t\t\ttype: String,\n\t\t\tdefault: '/apps/openregister/api',\n\t\t},\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\t/**\n\t\t\t * The hidden file input, set by the template's function ref (kept off `$refs` so the ref stays dynamic — see the template).\n\t\t\t *\n\t\t\t * @type {HTMLInputElement|null}\n\t\t\t */\n\t\t\tfileInputEl: null,\n\t\t\titems: [],\n\t\t\tcurrentSubPath: '/',\n\t\t\tloading: false,\n\t\t\terrorCode: null,\n\t\t\tunavailable: false,\n\t\t\tnextCursor: null,\n\t\t\tsearchQuery: '',\n\t\t\tconfirmTarget: null,\n\t\t\t// fileId → true for thumbnails whose <img> failed to load, so the\n\t\t\t// row falls back to the generic icon instead of a broken image.\n\t\t\tfailedThumbs: {},\n\t\t\t// Whether a file drag is currently hovering the widget (drop overlay).\n\t\t\tisDragging: false,\n\t\t\t// Depth counter so nested dragenter/dragleave don't flicker the overlay.\n\t\t\tdragDepth: 0,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Numeric placement id used to scope the host Files endpoint.\n\t\t *\n\t\t * @return {number} the placement id, or `0` when no placement is bound.\n\t\t */\n\t\tplacementId() {\n\t\t\treturn Number(this.placement?.id || 0)\n\t\t},\n\n\t\t/**\n\t\t * The schema slug in object-bound mode. Accepts either a slug string or\n\t\t * the resolved schema object merged by `CnWidgetGrid` (in which case the\n\t\t * slug is read from `slug`/`id`/`uuid`).\n\t\t *\n\t\t * @return {string} the schema slug, or `''` when unavailable.\n\t\t */\n\t\tschemaSlug() {\n\t\t\tconst s = this.schema\n\t\t\tif (typeof s === 'string') {\n\t\t\t\treturn s\n\t\t\t}\n\t\t\treturn (s && (s.slug || s.id || s.uuid)) || ''\n\t\t},\n\n\t\t/**\n\t\t * Whether the widget runs in object-bound mode — i.e. it attaches files\n\t\t * to a specific OpenRegister object rather than a dashboard placement.\n\t\t *\n\t\t * @return {boolean} `true` when `objectId`, `register` and `schemaSlug` are all set.\n\t\t */\n\t\tobjectBound() {\n\t\t\treturn !!(this.objectId && this.register && this.schemaSlug)\n\t\t},\n\n\t\t/**\n\t\t * Stable identity key for the bound object, or `''` in dashboard mode.\n\t\t * Watched to (re)fetch when the object context first resolves (the schema\n\t\t * object arrives after the ids) or when the widget rebinds to another\n\t\t * object.\n\t\t *\n\t\t * @return {string} `register/schema/objectId`, or `''` when not object-bound.\n\t\t */\n\t\tobjectKey() {\n\t\t\treturn this.objectBound\n\t\t\t\t? `${this.register}/${this.schemaSlug}/${this.objectId}`\n\t\t\t\t: ''\n\t\t},\n\n\t\t/**\n\t\t * Whether the upload affordance is enabled by configuration. Defaults to\n\t\t * enabled in BOTH modes — a Files widget is an add-files surface — and is\n\t\t * only suppressed when a placement explicitly sets `allowUpload: false`.\n\t\t * (Object-bound mode already defaulted on, matching the object sidebar's\n\t\t * Files tab; beta extended the same default to dashboard placements.)\n\t\t *\n\t\t * @return {boolean} `true` unless `content.allowUpload` is explicitly false.\n\t\t */\n\t\tallowUpload() {\n\t\t\treturn this.content?.allowUpload !== false\n\t\t},\n\n\t\t/**\n\t\t * Whether per-row delete affordances are enabled by configuration. On by\n\t\t * default in object-bound mode (unless `content.allowDelete` is explicitly\n\t\t * false); opt-in (`content.allowDelete === true`) in dashboard mode.\n\t\t *\n\t\t * @return {boolean} `true` when deletes are allowed.\n\t\t */\n\t\tallowDelete() {\n\t\t\tif (this.objectBound) {\n\t\t\t\treturn this.content?.allowDelete !== false\n\t\t\t}\n\t\t\treturn this.content?.allowDelete === true\n\t\t},\n\n\t\t/**\n\t\t * Layout for the file listing, from `content.viewMode`. `'grid'` renders\n\t\t * the items as a tile grid; any other value (`'list'`, `'tree'`, unset)\n\t\t * renders the default list. Tree navigation is not implemented, so\n\t\t * `'tree'` falls back to the list layout.\n\t\t *\n\t\t * @return {string} `'grid'` or `'list'`.\n\t\t */\n\t\tviewMode() {\n\t\t\treturn this.content?.viewMode === 'grid' ? 'grid' : 'list'\n\t\t},\n\n\t\t/**\n\t\t * Whether file rows show a preview thumbnail (from `content.showThumbnails`).\n\t\t * Defaults to on; only an explicit `false` disables it. Files without a\n\t\t * backend-provided `thumbnailUrl` still fall back to the generic icon.\n\t\t *\n\t\t * @return {boolean} `true` unless `content.showThumbnails` is explicitly false.\n\t\t */\n\t\tshowThumbnails() {\n\t\t\treturn this.content?.showThumbnails !== false\n\t\t},\n\n\t\t/**\n\t\t * Heuristic write-permission check: an empty folder optimistically\n\t\t * surfaces the upload button (the backend re-validates), otherwise at\n\t\t * least one editable child is required.\n\t\t *\n\t\t * @return {boolean} `true` when the viewer is treated as having write access.\n\t\t */\n\t\tviewerCanWrite() {\n\t\t\tif (this.items.length === 0) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\treturn this.items.some((item) => item.canEdit === true)\n\t\t},\n\n\t\t/**\n\t\t * Whether the upload button should render.\n\t\t *\n\t\t * @return {boolean} `true` when upload is allowed, writable, and not errored.\n\t\t */\n\t\tcanShowUpload() {\n\t\t\treturn this.allowUpload && this.viewerCanWrite && !this.errorCode && !this.unavailable\n\t\t},\n\n\t\t/**\n\t\t * The current sub-path split into breadcrumb segments.\n\t\t *\n\t\t * @return {string[]} the path segments (empty at root).\n\t\t */\n\t\tpathSegments() {\n\t\t\tif (!this.currentSubPath || this.currentSubPath === '/') {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\treturn this.currentSubPath\n\t\t\t\t.split('/')\n\t\t\t\t.map((segment) => segment.trim())\n\t\t\t\t.filter((segment) => segment !== '')\n\t\t},\n\n\t\t/**\n\t\t * The items filtered by the in-widget search query.\n\t\t *\n\t\t * @return {Array<object>} the matching items (all items when the query is empty).\n\t\t */\n\t\tfilteredItems() {\n\t\t\tconst query = this.searchQuery.trim().toLowerCase()\n\t\t\tif (query === '') {\n\t\t\t\treturn this.items\n\t\t\t}\n\t\t\treturn this.items.filter((item) => {\n\t\t\t\tconst name = String(item.name || '').toLowerCase()\n\t\t\t\treturn name.includes(query)\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * The localised \"no results\" label for the active search query.\n\t\t *\n\t\t * @return {string} the no-results message.\n\t\t */\n\t\tnoSearchResultsLabel() {\n\t\t\treturn t(\n\t\t\t\t'nextcloud-vue',\n\t\t\t\t'No files matching \\'{query}\\'',\n\t\t\t\t{ query: this.searchQuery },\n\t\t\t)\n\t\t},\n\t},\n\n\twatch: {\n\t\tplacement: {\n\t\t\timmediate: true,\n\t\t\t/**\n\t\t\t * Reset navigation state and refetch whenever the placement changes.\n\t\t\t * Dashboard mode only — object-bound mode is driven by `objectKey`.\n\t\t\t *\n\t\t\t * @return {void}\n\t\t\t */\n\t\t\thandler() {\n\t\t\t\tif (this.objectBound) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tthis.resetListing()\n\t\t\t\tthis.fetchContents()\n\t\t\t},\n\t\t},\n\n\t\tobjectKey: {\n\t\t\timmediate: true,\n\t\t\t/**\n\t\t\t * Reset and refetch when the bound object first resolves or changes.\n\t\t\t *\n\t\t\t * @param {string} key the new object key (`''` in dashboard mode).\n\t\t\t * @return {void}\n\t\t\t */\n\t\t\thandler(key) {\n\t\t\t\tif (!key) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tthis.resetListing()\n\t\t\t\tthis.fetchContents()\n\t\t\t},\n\t\t},\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/**\n\t\t * Function ref for the hidden file input; a method so the binding is\n\t\t * stable across renders and the vnode stays dynamic.\n\t\t *\n\t\t * @param {HTMLInputElement|null} el The element, or null on unmount.\n\t\t * @return {void}\n\t\t */\n\t\tfileInputRef(el) {\n\t\t\tthis.fileInputEl = el || null\n\t\t},\n\n\t\t/**\n\t\t * Reset the listing/navigation state before a (re)fetch.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tresetListing() {\n\t\t\tthis.currentSubPath = '/'\n\t\t\tthis.items = []\n\t\t\tthis.nextCursor = null\n\t\t\tthis.errorCode = null\n\t\t\tthis.unavailable = false\n\t\t},\n\n\t\t/**\n\t\t * Fetch (or append) one page of folder contents from the host Files\n\t\t * endpoint. Degrades gracefully: a missing endpoint flips `unavailable`,\n\t\t * a 404/403 maps to a typed `errorCode`, and no exception escapes.\n\t\t *\n\t\t * @param {boolean} [append] whether to append to the existing list (pagination).\n\t\t * @return {Promise<void>} resolves when the fetch settles.\n\t\t */\n\t\tasync fetchContents(append = false) {\n\t\t\tif (this.objectBound) {\n\t\t\t\treturn this.fetchObjectFiles()\n\t\t\t}\n\t\t\tif (this.placementId === 0) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tthis.loading = !append\n\t\t\tthis.errorCode = null\n\t\t\tthis.unavailable = false\n\n\t\t\ttry {\n\t\t\t\tconst { default: axios } = await import('@nextcloud/axios')\n\n\t\t\t\tconst url = generateUrl(\n\t\t\t\t\t`${this.apiBase}/api/widgets/files/{placementId}/contents`,\n\t\t\t\t\t{ placementId: this.placementId },\n\t\t\t\t)\n\t\t\t\tconst params = {\n\t\t\t\t\tcurrentPath: this.currentSubPath,\n\t\t\t\t\tlimit: 50,\n\t\t\t\t}\n\t\t\t\tif (append && this.nextCursor) {\n\t\t\t\t\tparams.cursor = this.nextCursor\n\t\t\t\t}\n\n\t\t\t\tconst response = await axios.get(url, { params })\n\t\t\t\tconst data = response?.data || {}\n\t\t\t\tconst nextItems = Array.isArray(data.items) ? data.items : []\n\t\t\t\tthis.items = append ? this.items.concat(nextItems) : nextItems\n\t\t\t\tthis.nextCursor = data.nextCursor || null\n\t\t\t} catch (err) {\n\t\t\t\tconst status = err?.response?.status\n\t\t\t\tconst code = err?.response?.data?.error\n\t\t\t\tif (status === 404 || code === 'folder_not_found') {\n\t\t\t\t\tthis.errorCode = 'folder_not_found'\n\t\t\t\t} else if (status === 403 || code === 'no_access') {\n\t\t\t\t\tthis.errorCode = 'no_access'\n\t\t\t\t} else if (status === undefined) {\n\t\t\t\t\t// No HTTP response at all — the backing Files endpoint is\n\t\t\t\t\t// not installed/reachable; degrade to the disabled state.\n\t\t\t\t\tthis.unavailable = true\n\t\t\t\t} else {\n\t\t\t\t\tthis.errorCode = 'unknown_error'\n\t\t\t\t}\n\t\t\t\tif (!append) {\n\t\t\t\t\tthis.items = []\n\t\t\t\t\tthis.nextCursor = null\n\t\t\t\t}\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Object-bound listing: fetch the bound OpenRegister object's files from\n\t\t * `{objectApiBase}/objects/{register}/{schema}/{id}/files` and map them\n\t\t * onto the widget's flat item shape (no folder navigation). Degrades the\n\t\t * same way as the dashboard fetch: 404/403 → typed `errorCode`, no HTTP\n\t\t * response → `unavailable`.\n\t\t *\n\t\t * @return {Promise<void>} resolves when the fetch settles.\n\t\t */\n\t\tasync fetchObjectFiles() {\n\t\t\tthis.loading = true\n\t\t\tthis.errorCode = null\n\t\t\tthis.unavailable = false\n\n\t\t\ttry {\n\t\t\t\tconst { default: axios } = await import('@nextcloud/axios')\n\n\t\t\t\tconst url = generateUrl(\n\t\t\t\t\t`${this.objectApiBase}/objects/{register}/{schema}/{objectId}/files`,\n\t\t\t\t\t{ register: this.register, schema: this.schemaSlug, objectId: this.objectId },\n\t\t\t\t)\n\t\t\t\tconst response = await axios.get(url)\n\t\t\t\tconst data = response?.data || {}\n\t\t\t\tconst results = Array.isArray(data)\n\t\t\t\t\t? data\n\t\t\t\t\t: (Array.isArray(data.results) ? data.results : [])\n\t\t\t\tthis.items = results.map((file) => {\n\t\t\t\t\tconst mime = file.mimeType || file.mimetype || file.type || ''\n\t\t\t\t\t// SVG: Nextcloud's rasterised /core/preview is usually\n\t\t\t\t\t// unavailable for SVG (no rsvg/imagick), so stream the raw file\n\t\t\t\t\t// from OpenRegister's authenticated per-file download endpoint\n\t\t\t\t\t// (works for the owner's un-shared uploads via the session\n\t\t\t\t\t// cookie; the widget's other calls use the same objectApiBase).\n\t\t\t\t\t// Rendered through the list's <img>, the SVG runs in the\n\t\t\t\t\t// browser's restricted image mode — inline <script> / event\n\t\t\t\t\t// handlers never execute and external refs don't load — so\n\t\t\t\t\t// untrusted SVG is shown safely without a bespoke sandbox.\n\t\t\t\t\t// Other images use the rasterised server preview; anything else\n\t\t\t\t\t// gets the generic icon (a missing preview also falls back via\n\t\t\t\t\t// the <img> @error handler).\n\t\t\t\t\tlet thumbnailUrl = null\n\t\t\t\t\tif (file.id && /^image\\/svg\\+xml$/i.test(mime)) {\n\t\t\t\t\t\tthumbnailUrl = generateUrl(`${this.objectApiBase}/files/{fileId}/download`, { fileId: file.id })\n\t\t\t\t\t} else if (file.id && /^image\\//.test(mime)) {\n\t\t\t\t\t\tthumbnailUrl = generateUrl('/core/preview?fileId={fileId}&x=256&y=256&a=1', { fileId: file.id })\n\t\t\t\t\t}\n\t\t\t\t\treturn {\n\t\t\t\t\t\tname: file.name || file.title || '',\n\t\t\t\t\t\tfileId: file.id,\n\t\t\t\t\t\tisFolder: false,\n\t\t\t\t\t\tsize: file.size,\n\t\t\t\t\t\tmodifiedAt: file.modified || file.updated || '',\n\t\t\t\t\t\tthumbnailUrl,\n\t\t\t\t\t\tcanEdit: true,\n\t\t\t\t\t\tcanDelete: this.allowDelete,\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\tthis.nextCursor = null\n\t\t\t} catch (err) {\n\t\t\t\tconst status = err?.response?.status\n\t\t\t\tif (status === 404) {\n\t\t\t\t\tthis.errorCode = 'folder_not_found'\n\t\t\t\t} else if (status === 403) {\n\t\t\t\t\tthis.errorCode = 'no_access'\n\t\t\t\t} else if (status === undefined) {\n\t\t\t\t\tthis.unavailable = true\n\t\t\t\t} else {\n\t\t\t\t\tthis.errorCode = 'unknown_error'\n\t\t\t\t}\n\t\t\t\tthis.items = []\n\t\t\t\tthis.nextCursor = null\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Load the next page when a cursor is present.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tloadMore() {\n\t\t\tif (this.nextCursor) {\n\t\t\t\tthis.fetchContents(true)\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Handle a folder row click by descending into it. A file row is a\n\t\t * real link to the Files app and never reaches this.\n\t\t *\n\t\t * @param {object} item the clicked folder item.\n\t\t * @return {void}\n\t\t */\n\t\tonItemClick(item) {\n\t\t\tthis.navigateTo(this.joinPath(this.currentSubPath, item.name))\n\t\t},\n\n\t\t/**\n\t\t * Navigate to a sub-path, clearing search + pagination and refetching.\n\t\t *\n\t\t * @param {string} path the target sub-path.\n\t\t * @return {void}\n\t\t */\n\t\tnavigateTo(path) {\n\t\t\tthis.currentSubPath = path || '/'\n\t\t\tthis.searchQuery = ''\n\t\t\tthis.nextCursor = null\n\t\t\tthis.fetchContents()\n\t\t},\n\n\t\t/**\n\t\t * Build the sub-path up to (and including) a breadcrumb segment index.\n\t\t *\n\t\t * @param {number} index the segment index.\n\t\t * @return {string} the assembled sub-path.\n\t\t */\n\t\tsegmentPathTo(index) {\n\t\t\tconst segments = this.pathSegments.slice(0, index + 1)\n\t\t\treturn '/' + segments.join('/')\n\t\t},\n\n\t\t/**\n\t\t * Join a base path and a child name into a normalised sub-path.\n\t\t *\n\t\t * @param {string} base the base path.\n\t\t * @param {string} name the child name.\n\t\t * @return {string} the joined path.\n\t\t */\n\t\tjoinPath(base, name) {\n\t\t\tconst trimmedBase = String(base || '/').replace(/\\/+$/, '')\n\t\t\tconst trimmedName = String(name || '').replace(/^\\/+/, '')\n\t\t\tif (trimmedBase === '' || trimmedBase === '/') {\n\t\t\t\treturn '/' + trimmedName\n\t\t\t}\n\t\t\treturn `${trimmedBase}/${trimmedName}`\n\t\t},\n\n\t\t/**\n\t\t * The Files-app permalink for a file.\n\t\t *\n\t\t * @param {number|string} fileId the Nextcloud file id.\n\t\t * @return {string} the URL, or '' without a file id.\n\t\t */\n\t\tfileUrl(fileId) {\n\t\t\tif (!fileId) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\t// Canonical permalink: `/f/{fileid}` resolves the id to its folder\n\t\t\t// and opens the file; generateUrl adds `index.php` where needed.\n\t\t\treturn generateUrl('/f/{fileid}', { fileid: fileId })\n\t\t},\n\n\t\t/**\n\t\t * Whether a per-row delete button should render for an item.\n\t\t *\n\t\t * @param {object} item the item.\n\t\t * @return {boolean} `true` when delete is allowed and the item is deletable.\n\t\t */\n\t\tcanDeleteItem(item) {\n\t\t\treturn this.allowDelete === true && item.canDelete === true\n\t\t},\n\n\t\t/**\n\t\t * Mark an item's thumbnail as failed so the row falls back to the\n\t\t * generic icon. Fires when the preview `<img>` cannot load (e.g. the\n\t\t * preview was not generated in time).\n\t\t *\n\t\t * @param {object} item the item whose thumbnail failed.\n\t\t * @return {void}\n\t\t */\n\t\tonThumbError(item) {\n\t\t\tthis.failedThumbs[item.fileId] = true\n\t\t},\n\n\t\t/**\n\t\t * Open the delete-confirmation modal for an item.\n\t\t *\n\t\t * @param {object} item the item to delete.\n\t\t * @return {void}\n\t\t */\n\t\tconfirmDelete(item) {\n\t\t\tthis.confirmTarget = item\n\t\t},\n\n\t\t/**\n\t\t * React to the delete dialog's open-state changes. Closing it (Cancel,\n\t\t * Esc, click-outside, or the header X) clears the pending target.\n\t\t *\n\t\t * @param {boolean} open the dialog's new open state.\n\t\t * @return {void}\n\t\t */\n\t\tonDeleteDialogToggle(open) {\n\t\t\tif (!open) {\n\t\t\t\tthis.confirmTarget = null\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Delete the confirmed target via the host Files endpoint, refreshing\n\t\t * the listing on failure so the UI stays truthful.\n\t\t *\n\t\t * @return {Promise<void>} resolves when the delete settles.\n\t\t */\n\t\tasync performDelete() {\n\t\t\tconst target = this.confirmTarget\n\t\t\tif (!target) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst { default: axios } = await import('@nextcloud/axios')\n\n\t\t\t\tconst url = this.objectBound\n\t\t\t\t\t? generateUrl(\n\t\t\t\t\t\t\t`${this.objectApiBase}/objects/{register}/{schema}/{objectId}/files/{fileId}`,\n\t\t\t\t\t\t\t{ register: this.register, schema: this.schemaSlug, objectId: this.objectId, fileId: target.fileId },\n\t\t\t\t\t\t)\n\t\t\t\t\t: generateUrl(\n\t\t\t\t\t\t\t`${this.apiBase}/api/widgets/files/{placementId}/files/{fileId}`,\n\t\t\t\t\t\t\t{ placementId: this.placementId, fileId: target.fileId },\n\t\t\t\t\t\t)\n\t\t\t\tawait axios.delete(url)\n\t\t\t\tthis.items = this.items.filter((item) => item.fileId !== target.fileId)\n\t\t\t} catch {\n\t\t\t\tthis.fetchContents()\n\t\t\t} finally {\n\t\t\t\tthis.confirmTarget = null\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Trigger the hidden file input from the upload button.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\ttriggerUpload() {\n\t\t\tif (this.fileInputEl) {\n\t\t\t\tthis.fileInputEl.click()\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Upload the chosen files to the host Files endpoint and refresh.\n\t\t *\n\t\t * @param {Event} event the file-input change event.\n\t\t * @return {Promise<void>} resolves when the upload settles.\n\t\t */\n\t\tasync onFileInputChange(event) {\n\t\t\tconst fileList = event?.target?.files\n\t\t\tawait this.uploadFiles(fileList)\n\t\t\tif (this.fileInputEl) {\n\t\t\t\tthis.fileInputEl.value = ''\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * POST a FileList (from the picker or a drag-and-drop) to the host Files\n\t\t * endpoint, then refresh the listing. No-op when there is nothing to upload\n\t\t * or upload is not permitted; never throws.\n\t\t *\n\t\t * @param {FileList|File[]|null} fileList the files to upload.\n\t\t * @return {Promise<void>} resolves when the upload settles.\n\t\t */\n\t\tasync uploadFiles(fileList) {\n\t\t\tif (!fileList || fileList.length === 0 || !this.canShowUpload) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tconst formData = new FormData()\n\t\t\tfor (let i = 0; i < fileList.length; i++) {\n\t\t\t\tformData.append('files[]', fileList[i])\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tconst { default: axios } = await import('@nextcloud/axios')\n\n\t\t\t\tif (this.objectBound) {\n\t\t\t\t\tconst objectUrl = generateUrl(\n\t\t\t\t\t\t`${this.objectApiBase}/objects/{register}/{schema}/{objectId}/filesMultipart`,\n\t\t\t\t\t\t{ register: this.register, schema: this.schemaSlug, objectId: this.objectId },\n\t\t\t\t\t)\n\t\t\t\t\tawait axios.post(objectUrl, formData)\n\t\t\t\t} else {\n\t\t\t\t\tconst url = generateUrl(\n\t\t\t\t\t\t`${this.apiBase}/api/widgets/files/{placementId}/upload`,\n\t\t\t\t\t\t{ placementId: this.placementId },\n\t\t\t\t\t)\n\t\t\t\t\tawait axios.post(url, formData, {\n\t\t\t\t\t\tparams: { currentPath: this.currentSubPath },\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t\tthis.fetchContents()\n\t\t\t} catch {\n\t\t\t\tthis.fetchContents()\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Track a drag entering the widget (depth-counted so nested elements don't\n\t\t * flicker the drop overlay).\n\t\t *\n\t\t * @param {DragEvent} event the dragenter event.\n\t\t * @return {void}\n\t\t */\n\t\tonDragEnter(event) {\n\t\t\tif (!this.canShowUpload || !this.hasFiles(event)) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tevent.preventDefault()\n\t\t\tthis.dragDepth++\n\t\t\tthis.isDragging = true\n\t\t},\n\n\t\t/**\n\t\t * Keep the browser from opening the dragged file and mark the drop as a copy.\n\t\t *\n\t\t * @param {DragEvent} event the dragover event.\n\t\t * @return {void}\n\t\t */\n\t\tonDragOver(event) {\n\t\t\tif (!this.canShowUpload || !this.hasFiles(event)) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tevent.preventDefault()\n\t\t\tif (event.dataTransfer) {\n\t\t\t\tevent.dataTransfer.dropEffect = 'copy'\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Track a drag leaving the widget, clearing the overlay at depth zero.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tonDragLeave() {\n\t\t\tif (this.dragDepth > 0) {\n\t\t\t\tthis.dragDepth--\n\t\t\t}\n\t\t\tif (this.dragDepth === 0) {\n\t\t\t\tthis.isDragging = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Handle a file drop — upload the dropped files into the current folder.\n\t\t *\n\t\t * @param {DragEvent} event the drop event.\n\t\t * @return {Promise<void>} resolves when the upload settles.\n\t\t */\n\t\tasync onDrop(event) {\n\t\t\tthis.dragDepth = 0\n\t\t\tthis.isDragging = false\n\t\t\tif (!this.canShowUpload || !this.hasFiles(event)) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tevent.preventDefault()\n\t\t\tawait this.uploadFiles(event.dataTransfer?.files)\n\t\t},\n\n\t\t/**\n\t\t * Whether a drag event is carrying files (vs. text / an internal drag).\n\t\t *\n\t\t * @param {DragEvent} event the drag event.\n\t\t * @return {boolean} `true` when the payload includes files.\n\t\t */\n\t\thasFiles(event) {\n\t\t\tconst types = event?.dataTransfer?.types\n\t\t\tif (!types) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treturn Array.from(types).includes('Files')\n\t\t},\n\n\t\t/**\n\t\t * Human-format a byte count (folders render an empty size).\n\t\t *\n\t\t * @param {number} bytes the byte count.\n\t\t * @param {boolean} isFolder whether the item is a folder.\n\t\t * @return {string} the formatted size.\n\t\t */\n\t\tformatSize(bytes, isFolder) {\n\t\t\tif (isFolder) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst value = Number(bytes || 0)\n\t\t\tif (value === 0) {\n\t\t\t\treturn '0 B'\n\t\t\t}\n\t\t\tconst units = ['B', 'KB', 'MB', 'GB', 'TB']\n\t\t\tlet unitIndex = 0\n\t\t\tlet display = value\n\t\t\twhile (display >= 1024 && unitIndex < units.length - 1) {\n\t\t\t\tdisplay = display / 1024\n\t\t\t\tunitIndex++\n\t\t\t}\n\t\t\treturn `${display.toFixed(unitIndex === 0 ? 0 : 1)} ${units[unitIndex]}`\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-files-widget {\n\tposition: relative;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 8px;\n\theight: 100%;\n\toverflow: hidden;\n}\n\n.cn-files-widget--dragging {\n\toutline: 2px dashed var(--color-primary-element);\n\toutline-offset: -2px;\n\tborder-radius: var(--border-radius);\n}\n\n.cn-files-widget__dropzone {\n\tposition: absolute;\n\tinset: 0;\n\tz-index: 5;\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\tpointer-events: none;\n\tbackground: var(--color-primary-element-light, rgba(0, 130, 201, 0.1));\n\tcolor: var(--color-primary-element);\n\tfont-weight: 600;\n\tborder-radius: var(--border-radius);\n}\n\n.cn-files-widget__toolbar {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\tflex-wrap: wrap;\n}\n\n.cn-files-widget__breadcrumb {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tgap: 4px;\n\tflex: 1 1 auto;\n\tmin-width: 0;\n}\n\n.cn-files-widget__crumb {\n\tbackground: transparent;\n\tborder: none;\n\tpadding: 4px 6px;\n\tcursor: pointer;\n\tcolor: var(--color-primary-element);\n\tfont: inherit;\n}\n\n.cn-files-widget__crumb[aria-current='page'] {\n\tcolor: var(--color-main-text);\n\tcursor: default;\n}\n\n.cn-files-widget__separator {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-files-widget__search {\n\tflex: 0 1 200px;\n\tpadding: 4px 8px;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius);\n\tbackground: var(--color-main-background);\n\tcolor: var(--color-main-text);\n}\n\n.cn-files-widget__upload {\n\tpadding: 4px 12px;\n\tborder: 1px solid var(--color-primary-element);\n\tbackground: var(--color-primary-element);\n\tcolor: var(--color-primary-element-text);\n\tborder-radius: var(--border-radius);\n\tcursor: pointer;\n}\n\n.cn-files-widget__file-input {\n\tposition: absolute;\n\twidth: 1px;\n\theight: 1px;\n\tpadding: 0;\n\tmargin: -1px;\n\toverflow: hidden;\n\tclip-path: inset(50%);\n\tborder: 0;\n}\n\n.cn-files-widget__state {\n\tpadding: 16px;\n\tcolor: var(--color-text-maxcontrast);\n\ttext-align: center;\n}\n\n.cn-files-widget__retry {\n\tdisplay: block;\n\tmargin: 8px auto 0;\n\tpadding: 4px 12px;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius);\n\tbackground: var(--color-background-hover);\n\tcursor: pointer;\n}\n\n.cn-files-widget__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n\toverflow: auto;\n\tflex: 1 1 auto;\n}\n\n.cn-files-widget__row {\n\tdisplay: grid;\n\tgrid-template-columns: 1fr 140px 80px auto;\n\talign-items: center;\n\tgap: 8px;\n\tpadding: 4px 6px;\n\tborder-bottom: 1px solid var(--color-border);\n}\n\n.cn-files-widget__row-name {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\tbackground: transparent;\n\tborder: none;\n\tpadding: 0;\n\tcursor: pointer;\n\ttext-align: left;\n\tcolor: var(--color-main-text);\n\tfont: inherit;\n\ttext-decoration: none;\n\tmin-width: 0;\n}\n\n.cn-files-widget__row-icon {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tjustify-content: center;\n\tflex: 0 0 auto;\n\twidth: 24px;\n\theight: 24px;\n}\n\n.cn-files-widget__row-thumb {\n\twidth: 100%;\n\theight: 100%;\n\tobject-fit: cover;\n\tborder-radius: var(--border-radius);\n\tbackground-color: var(--color-background-dark);\n}\n\n.cn-files-widget__row-label {\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.cn-files-widget__row-modified,\n.cn-files-widget__row-size {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 12px;\n}\n\n.cn-files-widget__row-delete {\n\tbackground: transparent;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius);\n\tpadding: 2px 8px;\n\tcursor: pointer;\n\tfont-size: 12px;\n\tcolor: var(--color-error);\n}\n\n/* Grid view (content.viewMode === 'grid'): lay the rows out as tiles. */\n.cn-files-widget__list--grid {\n\tdisplay: grid;\n\tgrid-template-columns: repeat(auto-fill, minmax(120px, 1fr));\n\tgap: 8px;\n\tpadding: 4px;\n\talign-content: start;\n}\n\n.cn-files-widget__list--grid .cn-files-widget__row {\n\tgrid-template-columns: 1fr;\n\tjustify-items: center;\n\ttext-align: center;\n\tgap: 4px;\n\tpadding: 10px 6px;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius);\n}\n\n.cn-files-widget__list--grid .cn-files-widget__row-name {\n\tflex-direction: column;\n\talign-items: center;\n\tmax-width: 100%;\n}\n\n.cn-files-widget__list--grid .cn-files-widget__row-label {\n\tmax-width: 100%;\n}\n\n.cn-files-widget__list--grid .cn-files-widget__row-icon {\n\tfont-size: 32px;\n\twidth: 56px;\n\theight: 56px;\n}\n\n/* Keep tiles compact — the modified date is list-view detail. */\n.cn-files-widget__list--grid .cn-files-widget__row-modified {\n\tdisplay: none;\n}\n\n.cn-files-widget__pagination {\n\ttext-align: center;\n\tpadding: 8px 0;\n}\n\n.cn-files-widget__more {\n\tpadding: 4px 12px;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius);\n\tbackground: var(--color-background-hover);\n\tcursor: pointer;\n}\n\n</style>\n"],"names":["_normalizeClass","_createElementVNode","_Fragment","_renderList","_createElementBlock","_toDisplayString","_createTextVNode","_resolveDynamicComponent","_openBlock","_createVNode"],"mappings":"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"}
@@ -259,19 +259,6 @@ var script = {
259
259
  },
260
260
 
261
261
  computed: {
262
- /**
263
- * Each run's link href by uuid, resolved once per render.
264
- *
265
- * @return {{[uuid: string]: string}}
266
- */
267
- runHrefs() {
268
- const out = {};
269
- for (const run of [...this.rows, ...this.completedRows]) {
270
- out[run.uuid] = this.runHref(run);
271
- }
272
- return out
273
- },
274
-
275
262
  /**
276
263
  * Effective translate function: the explicit prop, then the injected
277
264
  * host one, then `@nextcloud/l10n` under the library's own app id.