@bendyline/docblocks-react 1.1.2 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (335) hide show
  1. package/README.md +31 -5
  2. package/dist/AppMenu/AppMenu.d.ts +29 -2
  3. package/dist/AppMenu/AppMenu.d.ts.map +1 -1
  4. package/dist/AppMenu/AppMenu.js +41 -2
  5. package/dist/AppMenu/AppMenu.js.map +1 -1
  6. package/dist/AppMenu/index.d.ts +1 -1
  7. package/dist/AppMenu/index.d.ts.map +1 -1
  8. package/dist/DocBlocksShell/DocBlocksShell.d.ts +15 -2
  9. package/dist/DocBlocksShell/DocBlocksShell.d.ts.map +1 -1
  10. package/dist/DocBlocksShell/DocBlocksShell.js +1677 -298
  11. package/dist/DocBlocksShell/DocBlocksShell.js.map +1 -1
  12. package/dist/DocBlocksShell/LazyEditorShell.d.ts +3 -0
  13. package/dist/DocBlocksShell/LazyEditorShell.d.ts.map +1 -0
  14. package/dist/DocBlocksShell/LazyEditorShell.js +3 -0
  15. package/dist/DocBlocksShell/LazyEditorShell.js.map +1 -0
  16. package/dist/DocBlocksShell/UpdateAvailableNotice.d.ts +13 -0
  17. package/dist/DocBlocksShell/UpdateAvailableNotice.d.ts.map +1 -0
  18. package/dist/DocBlocksShell/UpdateAvailableNotice.js +16 -0
  19. package/dist/DocBlocksShell/UpdateAvailableNotice.js.map +1 -0
  20. package/dist/DocBlocksShell/document-title.d.ts +3 -0
  21. package/dist/DocBlocksShell/document-title.d.ts.map +1 -0
  22. package/dist/DocBlocksShell/document-title.js +17 -0
  23. package/dist/DocBlocksShell/document-title.js.map +1 -0
  24. package/dist/DocBlocksShell/workspace-authority-barrier.d.ts +14 -0
  25. package/dist/DocBlocksShell/workspace-authority-barrier.d.ts.map +1 -0
  26. package/dist/DocBlocksShell/workspace-authority-barrier.js +23 -0
  27. package/dist/DocBlocksShell/workspace-authority-barrier.js.map +1 -0
  28. package/dist/Export/DeferredExportToolbarControls.d.ts +5 -0
  29. package/dist/Export/DeferredExportToolbarControls.d.ts.map +1 -0
  30. package/dist/Export/DeferredExportToolbarControls.js +10 -0
  31. package/dist/Export/DeferredExportToolbarControls.js.map +1 -0
  32. package/dist/Export/ExportDialog.d.ts +10 -1
  33. package/dist/Export/ExportDialog.d.ts.map +1 -1
  34. package/dist/Export/ExportDialog.js +20 -18
  35. package/dist/Export/ExportDialog.js.map +1 -1
  36. package/dist/Export/ExportToolbarControls.d.ts +21 -1
  37. package/dist/Export/ExportToolbarControls.d.ts.map +1 -1
  38. package/dist/Export/ExportToolbarControls.js +96 -11
  39. package/dist/Export/ExportToolbarControls.js.map +1 -1
  40. package/dist/Export/export-destination.d.ts +3 -0
  41. package/dist/Export/export-destination.d.ts.map +1 -0
  42. package/dist/Export/export-destination.js +32 -0
  43. package/dist/Export/export-destination.js.map +1 -0
  44. package/dist/Export/index.d.ts +8 -4
  45. package/dist/Export/index.d.ts.map +1 -1
  46. package/dist/Export/index.js +4 -1
  47. package/dist/Export/index.js.map +1 -1
  48. package/dist/Export/public-api.d.ts +12 -0
  49. package/dist/Export/public-api.d.ts.map +1 -0
  50. package/dist/Export/public-api.js +9 -0
  51. package/dist/Export/public-api.js.map +1 -0
  52. package/dist/Export/run-export.d.ts +3 -1
  53. package/dist/Export/run-export.d.ts.map +1 -1
  54. package/dist/Export/run-export.js +30 -13
  55. package/dist/Export/run-export.js.map +1 -1
  56. package/dist/ExportDialog-MEUISWZX.js +9 -0
  57. package/dist/ExportDialog-MEUISWZX.js.map +1 -0
  58. package/dist/ExportToolbarControls-D2YDWOXW.js +373 -0
  59. package/dist/ExportToolbarControls-D2YDWOXW.js.map +1 -0
  60. package/dist/FileExplorer/FileExplorer.d.ts +26 -4
  61. package/dist/FileExplorer/FileExplorer.d.ts.map +1 -1
  62. package/dist/FileExplorer/FileExplorer.js +204 -29
  63. package/dist/FileExplorer/FileExplorer.js.map +1 -1
  64. package/dist/FileExplorer/FileTreeNode.d.ts +24 -3
  65. package/dist/FileExplorer/FileTreeNode.d.ts.map +1 -1
  66. package/dist/FileExplorer/FileTreeNode.js +27 -8
  67. package/dist/FileExplorer/FileTreeNode.js.map +1 -1
  68. package/dist/FileExplorer/index.d.ts +1 -1
  69. package/dist/FileExplorer/index.d.ts.map +1 -1
  70. package/dist/FileExplorer/useFileTree.d.ts +5 -3
  71. package/dist/FileExplorer/useFileTree.d.ts.map +1 -1
  72. package/dist/FileExplorer/useFileTree.js +173 -13
  73. package/dist/FileExplorer/useFileTree.js.map +1 -1
  74. package/dist/Git/GitAuthGuidanceDialog.d.ts +18 -0
  75. package/dist/Git/GitAuthGuidanceDialog.d.ts.map +1 -0
  76. package/dist/Git/GitAuthGuidanceDialog.js +27 -0
  77. package/dist/Git/GitAuthGuidanceDialog.js.map +1 -0
  78. package/dist/Git/GitBranchesDialog.d.ts +13 -0
  79. package/dist/Git/GitBranchesDialog.d.ts.map +1 -0
  80. package/dist/Git/GitBranchesDialog.js +59 -0
  81. package/dist/Git/GitBranchesDialog.js.map +1 -0
  82. package/dist/Git/GitCloneDialog.d.ts +14 -0
  83. package/dist/Git/GitCloneDialog.d.ts.map +1 -0
  84. package/dist/Git/GitCloneDialog.js +79 -0
  85. package/dist/Git/GitCloneDialog.js.map +1 -0
  86. package/dist/Git/GitCommitDialog.d.ts +12 -0
  87. package/dist/Git/GitCommitDialog.d.ts.map +1 -0
  88. package/dist/Git/GitCommitDialog.js +38 -0
  89. package/dist/Git/GitCommitDialog.js.map +1 -0
  90. package/dist/Git/GitConflictsDialog.d.ts +12 -0
  91. package/dist/Git/GitConflictsDialog.d.ts.map +1 -0
  92. package/dist/Git/GitConflictsDialog.js +17 -0
  93. package/dist/Git/GitConflictsDialog.js.map +1 -0
  94. package/dist/Git/GitContext.d.ts +89 -0
  95. package/dist/Git/GitContext.d.ts.map +1 -0
  96. package/dist/Git/GitContext.js +15 -0
  97. package/dist/Git/GitContext.js.map +1 -0
  98. package/dist/Git/GitDiffDialog.d.ts +20 -0
  99. package/dist/Git/GitDiffDialog.d.ts.map +1 -0
  100. package/dist/Git/GitDiffDialog.js +100 -0
  101. package/dist/Git/GitDiffDialog.js.map +1 -0
  102. package/dist/Git/GitDiffView.d.ts +23 -0
  103. package/dist/Git/GitDiffView.d.ts.map +1 -0
  104. package/dist/Git/GitDiffView.js +77 -0
  105. package/dist/Git/GitDiffView.js.map +1 -0
  106. package/dist/Git/GitHistoryDialog.d.ts +15 -0
  107. package/dist/Git/GitHistoryDialog.d.ts.map +1 -0
  108. package/dist/Git/GitHistoryDialog.js +107 -0
  109. package/dist/Git/GitHistoryDialog.js.map +1 -0
  110. package/dist/Git/GitStatusBar.d.ts +8 -0
  111. package/dist/Git/GitStatusBar.d.ts.map +1 -0
  112. package/dist/Git/GitStatusBar.js +49 -0
  113. package/dist/Git/GitStatusBar.js.map +1 -0
  114. package/dist/Git/GitToolbarControl.d.ts +15 -0
  115. package/dist/Git/GitToolbarControl.d.ts.map +1 -0
  116. package/dist/Git/GitToolbarControl.js +51 -0
  117. package/dist/Git/GitToolbarControl.js.map +1 -0
  118. package/dist/Git/GitUI.d.ts +14 -0
  119. package/dist/Git/GitUI.d.ts.map +1 -0
  120. package/dist/Git/GitUI.js +21 -0
  121. package/dist/Git/GitUI.js.map +1 -0
  122. package/dist/Git/commit-selection.d.ts +22 -0
  123. package/dist/Git/commit-selection.d.ts.map +1 -0
  124. package/dist/Git/commit-selection.js +47 -0
  125. package/dist/Git/commit-selection.js.map +1 -0
  126. package/dist/Git/git-clone-utils.d.ts +8 -0
  127. package/dist/Git/git-clone-utils.d.ts.map +1 -0
  128. package/dist/Git/git-clone-utils.js +26 -0
  129. package/dist/Git/git-clone-utils.js.map +1 -0
  130. package/dist/Git/git-status.d.ts +24 -0
  131. package/dist/Git/git-status.d.ts.map +1 -0
  132. package/dist/Git/git-status.js +105 -0
  133. package/dist/Git/git-status.js.map +1 -0
  134. package/dist/Git/useGit.d.ts +15 -0
  135. package/dist/Git/useGit.d.ts.map +1 -0
  136. package/dist/Git/useGit.js +206 -0
  137. package/dist/Git/useGit.js.map +1 -0
  138. package/dist/Git/useGitStatus.d.ts +17 -0
  139. package/dist/Git/useGitStatus.d.ts.map +1 -0
  140. package/dist/Git/useGitStatus.js +63 -0
  141. package/dist/Git/useGitStatus.js.map +1 -0
  142. package/dist/GitToolbarControl-LMIR4R7I.js +117 -0
  143. package/dist/GitToolbarControl-LMIR4R7I.js.map +1 -0
  144. package/dist/GitUI-KGF5MSNV.js +1048 -0
  145. package/dist/GitUI-KGF5MSNV.js.map +1 -0
  146. package/dist/LazyEditorShell-EF2BWRXU.js +6 -0
  147. package/dist/LazyEditorShell-EF2BWRXU.js.map +1 -0
  148. package/dist/Settings/Settings.d.ts +23 -0
  149. package/dist/Settings/Settings.d.ts.map +1 -0
  150. package/dist/Settings/Settings.js +22 -0
  151. package/dist/Settings/Settings.js.map +1 -0
  152. package/dist/Settings/public-api.d.ts +3 -0
  153. package/dist/Settings/public-api.d.ts.map +1 -0
  154. package/dist/Settings/public-api.js +2 -0
  155. package/dist/Settings/public-api.js.map +1 -0
  156. package/dist/WorkspacePicker/WorkspacePicker.d.ts +6 -1
  157. package/dist/WorkspacePicker/WorkspacePicker.d.ts.map +1 -1
  158. package/dist/WorkspacePicker/WorkspacePicker.js +16 -5
  159. package/dist/WorkspacePicker/WorkspacePicker.js.map +1 -1
  160. package/dist/chunk-3SAXDU2U.js +157 -0
  161. package/dist/chunk-3SAXDU2U.js.map +1 -0
  162. package/dist/chunk-4XXSWN3T.js +13 -0
  163. package/dist/chunk-4XXSWN3T.js.map +1 -0
  164. package/dist/chunk-5A4KEWKT.js +97 -0
  165. package/dist/chunk-5A4KEWKT.js.map +1 -0
  166. package/dist/chunk-AOVTHZU7.js +321 -0
  167. package/dist/chunk-AOVTHZU7.js.map +1 -0
  168. package/dist/chunk-DD4VFJQI.js +51 -0
  169. package/dist/chunk-DD4VFJQI.js.map +1 -0
  170. package/dist/chunk-GSNZSCIK.js +55 -0
  171. package/dist/chunk-GSNZSCIK.js.map +1 -0
  172. package/dist/chunk-VHDP4IPL.js +70 -0
  173. package/dist/chunk-VHDP4IPL.js.map +1 -0
  174. package/dist/chunk-ZI56BNB7.js +270 -0
  175. package/dist/chunk-ZI56BNB7.js.map +1 -0
  176. package/dist/components/Dialog.d.ts +22 -0
  177. package/dist/components/Dialog.d.ts.map +1 -0
  178. package/dist/components/Dialog.js +33 -0
  179. package/dist/components/Dialog.js.map +1 -0
  180. package/dist/editor.d.ts +10 -0
  181. package/dist/editor.d.ts.map +1 -0
  182. package/dist/editor.js +70 -0
  183. package/dist/editor.js.map +1 -0
  184. package/dist/export/index.d.ts +87 -0
  185. package/dist/export/index.js +26 -0
  186. package/dist/export/index.js.map +1 -0
  187. package/dist/hooks/index.d.ts +2 -1
  188. package/dist/hooks/index.d.ts.map +1 -1
  189. package/dist/hooks/index.js +1 -1
  190. package/dist/hooks/index.js.map +1 -1
  191. package/dist/hooks/useDocumentSession.d.ts +16 -0
  192. package/dist/hooks/useDocumentSession.d.ts.map +1 -0
  193. package/dist/hooks/useDocumentSession.js +18 -0
  194. package/dist/hooks/useDocumentSession.js.map +1 -0
  195. package/dist/icons.d.ts +11 -6
  196. package/dist/icons.d.ts.map +1 -1
  197. package/dist/icons.js +21 -20
  198. package/dist/icons.js.map +1 -1
  199. package/dist/index.d.ts +8 -5
  200. package/dist/index.d.ts.map +1 -1
  201. package/dist/index.js +4 -2
  202. package/dist/index.js.map +1 -1
  203. package/dist/preferences/theme.d.ts +18 -0
  204. package/dist/preferences/theme.d.ts.map +1 -0
  205. package/dist/preferences/theme.js +63 -0
  206. package/dist/preferences/theme.js.map +1 -0
  207. package/dist/provider-lease.d.ts +10 -0
  208. package/dist/provider-lease.d.ts.map +1 -0
  209. package/dist/provider-lease.js +42 -0
  210. package/dist/provider-lease.js.map +1 -0
  211. package/dist/run-export-ICTZITXC.js +10 -0
  212. package/dist/run-export-ICTZITXC.js.map +1 -0
  213. package/dist/settings/index.d.ts +28 -0
  214. package/dist/settings/index.js +12 -0
  215. package/dist/settings/index.js.map +1 -0
  216. package/package.json +24 -9
  217. package/src/AppMenu/AppMenu.tsx +169 -99
  218. package/src/AppMenu/index.ts +1 -1
  219. package/src/DocBlocksShell/DocBlocksShell.tsx +2157 -537
  220. package/src/DocBlocksShell/LazyEditorShell.ts +2 -0
  221. package/src/DocBlocksShell/UpdateAvailableNotice.tsx +60 -0
  222. package/src/DocBlocksShell/document-title.ts +19 -0
  223. package/src/DocBlocksShell/workspace-authority-barrier.ts +26 -0
  224. package/src/Export/DeferredExportToolbarControls.tsx +23 -0
  225. package/src/Export/ExportDialog.tsx +74 -20
  226. package/src/Export/ExportToolbarControls.tsx +238 -40
  227. package/src/Export/export-destination.ts +30 -0
  228. package/src/Export/index.ts +18 -4
  229. package/src/Export/public-api.ts +17 -0
  230. package/src/Export/run-export.ts +36 -11
  231. package/src/FileExplorer/FileExplorer.tsx +284 -38
  232. package/src/FileExplorer/FileTreeNode.tsx +100 -8
  233. package/src/FileExplorer/index.ts +1 -1
  234. package/src/FileExplorer/useFileTree.ts +190 -16
  235. package/src/Git/GitAuthGuidanceDialog.tsx +84 -0
  236. package/src/Git/GitBranchesDialog.tsx +144 -0
  237. package/src/Git/GitCloneDialog.tsx +200 -0
  238. package/src/Git/GitCommitDialog.tsx +119 -0
  239. package/src/Git/GitConflictsDialog.tsx +62 -0
  240. package/src/Git/GitContext.tsx +88 -0
  241. package/src/Git/GitDiffDialog.tsx +147 -0
  242. package/src/Git/GitDiffView.tsx +105 -0
  243. package/src/Git/GitHistoryDialog.tsx +195 -0
  244. package/src/Git/GitStatusBar.tsx +157 -0
  245. package/src/Git/GitToolbarControl.tsx +123 -0
  246. package/src/Git/GitUI.tsx +68 -0
  247. package/src/Git/commit-selection.ts +63 -0
  248. package/src/Git/git-clone-utils.ts +23 -0
  249. package/src/Git/git-status.ts +113 -0
  250. package/src/Git/useGit.ts +279 -0
  251. package/src/Git/useGitStatus.ts +78 -0
  252. package/src/Settings/Settings.tsx +118 -0
  253. package/src/Settings/public-api.ts +8 -0
  254. package/src/WorkspacePicker/WorkspacePicker.tsx +62 -8
  255. package/src/components/Dialog.tsx +81 -0
  256. package/src/editor.ts +72 -0
  257. package/src/hooks/index.ts +2 -1
  258. package/src/hooks/useDocumentSession.ts +37 -0
  259. package/src/icons.tsx +33 -55
  260. package/src/index.ts +35 -5
  261. package/src/preferences/theme.ts +67 -0
  262. package/src/provider-lease.ts +52 -0
  263. package/src/styles/docblocks.css +2174 -79
  264. package/src/types/pwa-dom.d.ts +31 -0
  265. package/dist/hooks/useAutoSave.d.ts +0 -15
  266. package/dist/hooks/useAutoSave.d.ts.map +0 -1
  267. package/dist/hooks/useAutoSave.js +0 -83
  268. package/dist/hooks/useAutoSave.js.map +0 -1
  269. package/dist/monaco-slim.d.ts +0 -19
  270. package/dist/monaco-slim.d.ts.map +0 -1
  271. package/dist/monaco-slim.js +0 -19
  272. package/dist/monaco-slim.js.map +0 -1
  273. package/src/fonts/fonts.css +0 -362
  274. package/src/fonts/licenses/LICENSE-cormorant-garamond.txt +0 -93
  275. package/src/fonts/licenses/LICENSE-crimson-text.txt +0 -93
  276. package/src/fonts/licenses/LICENSE-dm-sans.txt +0 -93
  277. package/src/fonts/licenses/LICENSE-dm-serif-display.txt +0 -93
  278. package/src/fonts/licenses/LICENSE-hanken-grotesk.txt +0 -94
  279. package/src/fonts/licenses/LICENSE-ibm-plex-sans.txt +0 -93
  280. package/src/fonts/licenses/LICENSE-inter.txt +0 -93
  281. package/src/fonts/licenses/LICENSE-jetbrains-mono.txt +0 -93
  282. package/src/fonts/licenses/LICENSE-lora.txt +0 -93
  283. package/src/fonts/licenses/LICENSE-merriweather.txt +0 -93
  284. package/src/fonts/licenses/LICENSE-oswald.txt +0 -93
  285. package/src/fonts/licenses/LICENSE-playfair-display.txt +0 -93
  286. package/src/fonts/licenses/LICENSE-pt-serif.txt +0 -93
  287. package/src/fonts/licenses/LICENSE-roboto.txt +0 -93
  288. package/src/fonts/licenses/LICENSE-source-serif-4.txt +0 -93
  289. package/src/fonts/woff2/cormorant-garamond-400.woff2 +0 -0
  290. package/src/fonts/woff2/cormorant-garamond-700.woff2 +0 -0
  291. package/src/fonts/woff2/crimson-text-400.woff2 +0 -0
  292. package/src/fonts/woff2/crimson-text-400i.woff2 +0 -0
  293. package/src/fonts/woff2/crimson-text-700.woff2 +0 -0
  294. package/src/fonts/woff2/dm-sans-400.woff2 +0 -0
  295. package/src/fonts/woff2/dm-sans-400i.woff2 +0 -0
  296. package/src/fonts/woff2/dm-sans-500.woff2 +0 -0
  297. package/src/fonts/woff2/dm-sans-700.woff2 +0 -0
  298. package/src/fonts/woff2/dm-serif-display-400.woff2 +0 -0
  299. package/src/fonts/woff2/hanken-grotesk-400.woff2 +0 -0
  300. package/src/fonts/woff2/hanken-grotesk-400i.woff2 +0 -0
  301. package/src/fonts/woff2/hanken-grotesk-500.woff2 +0 -0
  302. package/src/fonts/woff2/hanken-grotesk-600.woff2 +0 -0
  303. package/src/fonts/woff2/hanken-grotesk-700.woff2 +0 -0
  304. package/src/fonts/woff2/ibm-plex-sans-400.woff2 +0 -0
  305. package/src/fonts/woff2/ibm-plex-sans-400i.woff2 +0 -0
  306. package/src/fonts/woff2/ibm-plex-sans-500.woff2 +0 -0
  307. package/src/fonts/woff2/ibm-plex-sans-600.woff2 +0 -0
  308. package/src/fonts/woff2/ibm-plex-sans-700.woff2 +0 -0
  309. package/src/fonts/woff2/inter-400.woff2 +0 -0
  310. package/src/fonts/woff2/inter-400i.woff2 +0 -0
  311. package/src/fonts/woff2/inter-500.woff2 +0 -0
  312. package/src/fonts/woff2/inter-600.woff2 +0 -0
  313. package/src/fonts/woff2/inter-700.woff2 +0 -0
  314. package/src/fonts/woff2/jetbrains-mono-400.woff2 +0 -0
  315. package/src/fonts/woff2/jetbrains-mono-400i.woff2 +0 -0
  316. package/src/fonts/woff2/jetbrains-mono-700.woff2 +0 -0
  317. package/src/fonts/woff2/lora-400.woff2 +0 -0
  318. package/src/fonts/woff2/lora-400i.woff2 +0 -0
  319. package/src/fonts/woff2/lora-700.woff2 +0 -0
  320. package/src/fonts/woff2/merriweather-400.woff2 +0 -0
  321. package/src/fonts/woff2/merriweather-700.woff2 +0 -0
  322. package/src/fonts/woff2/oswald-400.woff2 +0 -0
  323. package/src/fonts/woff2/oswald-700.woff2 +0 -0
  324. package/src/fonts/woff2/playfair-display-400.woff2 +0 -0
  325. package/src/fonts/woff2/playfair-display-700.woff2 +0 -0
  326. package/src/fonts/woff2/pt-serif-400.woff2 +0 -0
  327. package/src/fonts/woff2/pt-serif-700.woff2 +0 -0
  328. package/src/fonts/woff2/roboto-400.woff2 +0 -0
  329. package/src/fonts/woff2/roboto-400i.woff2 +0 -0
  330. package/src/fonts/woff2/roboto-700.woff2 +0 -0
  331. package/src/fonts/woff2/source-serif-4-400.woff2 +0 -0
  332. package/src/fonts/woff2/source-serif-4-400i.woff2 +0 -0
  333. package/src/fonts/woff2/source-serif-4-700.woff2 +0 -0
  334. package/src/hooks/useAutoSave.ts +0 -116
  335. package/src/monaco-slim.ts +0 -20
@@ -1,5 +1,19 @@
1
- export { ExportToolbarControls } from './ExportToolbarControls.js';
2
- export type { ExportToolbarControlsProps } from './ExportToolbarControls.js';
1
+ export { ExportToolbarControls } from './DeferredExportToolbarControls.js';
2
+ export type {
3
+ ExportDestinationAdapter,
4
+ ExportDestinationTarget,
5
+ ExportToolbarControlsProps,
6
+ } from './DeferredExportToolbarControls.js';
3
7
  export { ExportDialog } from './ExportDialog.js';
4
- export type { ExportDialogProps } from './ExportDialog.js';
5
- export type { ExportFormat, ExportOptions } from './export-options.js';
8
+ export type { ExportDestinationControl, ExportDialogProps } from './ExportDialog.js';
9
+ export {
10
+ DEFAULT_OPTIONS,
11
+ FORMAT_EXTENSIONS,
12
+ FORMAT_LABELS,
13
+ loadLastExportOptions,
14
+ saveExportOptions,
15
+ } from './export-options.js';
16
+ export type { ExportFormat, ExportOptions, HtmlBundle, HtmlStyle } from './export-options.js';
17
+ export { buildExportFilename, runExport } from './run-export.js';
18
+ export { updateExportTargetExtension } from './export-destination.js';
19
+ export type { ExportBlobSaver } from './run-export.js';
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Narrow public export pipeline for hosts that provide their own editor shell.
3
+ * Keep toolbar/video controls out of this entry so consumers such as the VS
4
+ * Code webview do not eagerly transform the video worker graph.
5
+ */
6
+ export { ExportDialog } from './ExportDialog.js';
7
+ export type { ExportDestinationControl, ExportDialogProps } from './ExportDialog.js';
8
+ export {
9
+ DEFAULT_OPTIONS,
10
+ FORMAT_EXTENSIONS,
11
+ FORMAT_LABELS,
12
+ loadLastExportOptions,
13
+ saveExportOptions,
14
+ } from './export-options.js';
15
+ export type { ExportFormat, ExportOptions, HtmlBundle, HtmlStyle } from './export-options.js';
16
+ export { buildExportFilename, runExport } from './run-export.js';
17
+ export type { ExportBlobSaver } from './run-export.js';
@@ -13,6 +13,8 @@ import type { ContentContainer } from '@bendyline/squisq/storage';
13
13
  import type { ExportOptions, ExportFormat } from './export-options.js';
14
14
  import { FORMAT_EXTENSIONS } from './export-options.js';
15
15
 
16
+ export type ExportBlobSaver = (blob: Blob, filename: string) => Promise<void> | void;
17
+
16
18
  const MIME_TYPES: Record<ExportFormat, string> = {
17
19
  docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
18
20
  pdf: 'application/pdf',
@@ -31,29 +33,51 @@ function downloadBlob(blob: Blob, filename: string): void {
31
33
  URL.revokeObjectURL(url);
32
34
  }
33
35
 
36
+ async function saveExportBlob(
37
+ blob: Blob,
38
+ filename: string,
39
+ saveBlob?: ExportBlobSaver,
40
+ ): Promise<void> {
41
+ if (saveBlob) {
42
+ await saveBlob(blob, filename);
43
+ return;
44
+ }
45
+ downloadBlob(blob, filename);
46
+ }
47
+
34
48
  /** Derive a filename from the selected file path and export format. */
35
49
  function buildFilename(selectedFile: string | null, format: ExportFormat): string {
36
50
  const base = selectedFile ? selectedFile.replace(/^\//, '').replace(/\.[^.]+$/, '') : 'document';
37
51
  return base + FORMAT_EXTENSIONS[format];
38
52
  }
39
53
 
54
+ export function buildExportFilename(selectedFile: string | null, options: ExportOptions): string {
55
+ const base = selectedFile ? selectedFile.replace(/^\//, '').replace(/\.[^.]+$/, '') : 'document';
56
+ if (options.format === 'html') {
57
+ if (options.includeLinkedDocs || options.htmlBundle === 'zip') return `${base}.zip`;
58
+ if (options.entryAsIndex) return 'index.html';
59
+ }
60
+ return base + FORMAT_EXTENSIONS[options.format];
61
+ }
62
+
40
63
  /** Run the export and trigger a download. */
41
64
  export async function runExport(
42
65
  markdown: string,
43
66
  selectedFile: string | null,
44
67
  options: ExportOptions,
45
68
  mediaContainer?: ContentContainer | null,
69
+ saveBlob?: ExportBlobSaver,
46
70
  ): Promise<void> {
47
71
  const filename = buildFilename(selectedFile, options.format);
48
72
  const themeId = options.themeId !== 'standard' ? options.themeId : undefined;
49
73
 
50
74
  if (options.format === 'md') {
51
- downloadBlob(new Blob([markdown], { type: MIME_TYPES.md }), filename);
75
+ await saveExportBlob(new Blob([markdown], { type: MIME_TYPES.md }), filename, saveBlob);
52
76
  return;
53
77
  }
54
78
 
55
79
  if (options.format === 'html') {
56
- await runHtmlExport(markdown, filename, options, mediaContainer, selectedFile);
80
+ await runHtmlExport(markdown, filename, options, mediaContainer, selectedFile, saveBlob);
57
81
  return;
58
82
  }
59
83
 
@@ -63,7 +87,7 @@ export async function runExport(
63
87
  const { markdownDocToDocx } = await import('@bendyline/squisq-formats/docx');
64
88
  const images = mediaContainer ? await resolveImages(doc, mediaContainer) : undefined;
65
89
  const buf = await markdownDocToDocx(doc, { themeId, images });
66
- downloadBlob(new Blob([buf], { type: MIME_TYPES.docx }), filename);
90
+ await saveExportBlob(new Blob([buf], { type: MIME_TYPES.docx }), filename, saveBlob);
67
91
  return;
68
92
  }
69
93
 
@@ -73,7 +97,7 @@ export async function runExport(
73
97
  themeId,
74
98
  pageSize: options.pageSize,
75
99
  });
76
- downloadBlob(new Blob([buf], { type: MIME_TYPES.pdf }), filename);
100
+ await saveExportBlob(new Blob([buf], { type: MIME_TYPES.pdf }), filename, saveBlob);
77
101
  return;
78
102
  }
79
103
 
@@ -91,7 +115,7 @@ export async function runExport(
91
115
  enrichedDoc.themeId = themeId;
92
116
  }
93
117
  const buf = await docToPptx(enrichedDoc, { themeId });
94
- downloadBlob(new Blob([buf], { type: MIME_TYPES.pptx }), filename);
118
+ await saveExportBlob(new Blob([buf], { type: MIME_TYPES.pptx }), filename, saveBlob);
95
119
  return;
96
120
  }
97
121
  }
@@ -103,6 +127,7 @@ async function runHtmlExport(
103
127
  options: ExportOptions,
104
128
  mediaContainer?: ContentContainer | null,
105
129
  selectedFile?: string | null,
130
+ saveBlob?: ExportBlobSaver,
106
131
  ): Promise<void> {
107
132
  const baseName = htmlFilename.replace(/\.html$/, '');
108
133
  const zipName = `${baseName}.zip`;
@@ -141,7 +166,7 @@ async function runHtmlExport(
141
166
  mode: 'static',
142
167
  entryAsIndex: options.entryAsIndex,
143
168
  });
144
- downloadBlob(blob, zipName);
169
+ await saveExportBlob(blob, zipName, saveBlob);
145
170
  return;
146
171
  }
147
172
  const { markdownDocsToPlainHtmlBundle } = await import('@bendyline/squisq-formats/html');
@@ -153,7 +178,7 @@ async function runHtmlExport(
153
178
  themeId,
154
179
  entryAsIndex: options.entryAsIndex,
155
180
  });
156
- downloadBlob(blob, zipName);
181
+ await saveExportBlob(blob, zipName, saveBlob);
157
182
  return;
158
183
  }
159
184
 
@@ -179,7 +204,7 @@ async function runHtmlExport(
179
204
  mode: 'static',
180
205
  title: baseName,
181
206
  });
182
- downloadBlob(blob, zipName);
207
+ await saveExportBlob(blob, zipName, saveBlob);
183
208
  return;
184
209
  }
185
210
 
@@ -189,7 +214,7 @@ async function runHtmlExport(
189
214
  mode: 'static',
190
215
  title: baseName,
191
216
  });
192
- downloadBlob(new Blob([html], { type: MIME_TYPES.html }), singleHtmlFilename);
217
+ await saveExportBlob(new Blob([html], { type: MIME_TYPES.html }), singleHtmlFilename, saveBlob);
193
218
  return;
194
219
  }
195
220
 
@@ -216,7 +241,7 @@ async function runHtmlExport(
216
241
  await container.writeFile(cleanPath, new Uint8Array(data));
217
242
  }
218
243
  const blob = await containerToZip(container);
219
- downloadBlob(blob, zipName);
244
+ await saveExportBlob(blob, zipName, saveBlob);
220
245
  return;
221
246
  }
222
247
 
@@ -235,7 +260,7 @@ async function runHtmlExport(
235
260
  images: inlineMap.size > 0 ? inlineMap : undefined,
236
261
  themeId,
237
262
  });
238
- downloadBlob(new Blob([html], { type: MIME_TYPES.html }), singleHtmlFilename);
263
+ await saveExportBlob(new Blob([html], { type: MIME_TYPES.html }), singleHtmlFilename, saveBlob);
239
264
  }
240
265
 
241
266
  const IMAGE_MIME: Record<string, string> = {
@@ -5,24 +5,94 @@
5
5
  * for creating new files/folders.
6
6
  */
7
7
 
8
- import { useCallback, useState } from 'react';
9
- import type { FileSystemProvider, FileSystemEntry } from '@bendyline/docblocks/filesystem';
8
+ import { useCallback, useEffect, useRef, useState } from 'react';
9
+ import {
10
+ isFileSystemMoveStateError,
11
+ parseWorkspacePath,
12
+ type FileSystemProvider,
13
+ type FileSystemEntry,
14
+ } from '@bendyline/docblocks/filesystem';
10
15
  import { useFileTree } from './useFileTree.js';
11
- import { FileTreeNode } from './FileTreeNode.js';
12
- import { NewFileIcon, NewFolderIcon, RefreshIcon } from '../icons.js';
16
+ import {
17
+ FileTreeNode,
18
+ type FileTreeNodeBadge,
19
+ type FileTreeNodeGitActions,
20
+ } from './FileTreeNode.js';
21
+ import { NewFileIcon, NewFolderIcon } from '../icons.js';
22
+ import { useGitContext } from '../Git/GitContext.js';
23
+ import { BADGE_GLYPHS, BADGE_LABELS, isFileDirty } from '../Git/git-status.js';
13
24
 
14
25
  const SUPPORTED_EXTENSIONS = new Set(['.txt', '.md', '.docx', '.pdf', '.dbk', '.zip']);
26
+ const INTERNAL_DRAG_TYPE = 'application/x-docblocks-entry';
27
+
28
+ export type FileTreeChange =
29
+ | { type: 'create'; path: string; kind?: 'file' | 'directory' }
30
+ | { type: 'delete'; path: string; kind: 'file' | 'directory' }
31
+ | {
32
+ type: 'move';
33
+ oldPath: string;
34
+ newPath: string;
35
+ kind: 'file' | 'directory';
36
+ };
37
+
38
+ export type FileTreeMutationHandler = (
39
+ change: FileTreeChange,
40
+ mutate: () => Promise<void>,
41
+ ) => Promise<void>;
42
+
43
+ function normalisePath(path: string): string {
44
+ return parseWorkspacePath(path);
45
+ }
46
+
47
+ function hasEquivalentPath(paths: ReadonlySet<string>, path: string): boolean {
48
+ const canonical = normalisePath(path);
49
+ return (
50
+ paths.has(path) || paths.has(canonical) || (canonical !== '' && paths.has(`/${canonical}`))
51
+ );
52
+ }
53
+
54
+ function getEquivalentPathValue<T>(values: ReadonlyMap<string, T>, path: string): T | undefined {
55
+ const canonical = normalisePath(path);
56
+ return values.get(path) ?? values.get(canonical) ?? values.get(`/${canonical}`);
57
+ }
58
+
59
+ function parentPath(path: string): string {
60
+ const clean = normalisePath(path);
61
+ const slash = clean.lastIndexOf('/');
62
+ return slash < 0 ? '' : clean.slice(0, slash);
63
+ }
64
+
65
+ function pathInDirectory(entry: FileSystemEntry, directoryPath: string): string {
66
+ const prefix = directoryPath.replace(/\/+$/, '');
67
+ if (prefix) return `${prefix}/${entry.name}`;
68
+ return entry.path.startsWith('/') ? `/${entry.name}` : entry.name;
69
+ }
70
+
71
+ function canMoveTo(entry: FileSystemEntry, directoryPath: string): boolean {
72
+ const source = normalisePath(entry.path);
73
+ const target = normalisePath(directoryPath);
74
+ if (parentPath(source) === target) return false;
75
+ if (entry.kind === 'directory' && (target === source || target.startsWith(`${source}/`))) {
76
+ return false;
77
+ }
78
+ return true;
79
+ }
80
+
81
+ function isInternalDrag(dataTransfer: DataTransfer): boolean {
82
+ return Array.from(dataTransfer.types).includes(INTERNAL_DRAG_TYPE);
83
+ }
15
84
 
16
85
  /**
17
- * Hide auto-generated `<basename>_files/` companion folders from the
18
- * tree. They hold per-document images and version snapshots — useful
19
- * to the editor, noisy in the user-facing file list. The folders still
20
- * exist on disk; we just don't surface them in the explorer.
86
+ * Hide dot-entries (`.git`, `.gitignore`, `.DS_Store`, …) — never user
87
+ * documents and auto-generated `<basename>_files/` companion folders,
88
+ * which hold per-document images and version snapshots (useful to the
89
+ * editor, noisy in the user-facing file list). Everything still exists on
90
+ * disk; we just don't surface it in the explorer.
21
91
  */
22
92
  function isHiddenEntry(entry: FileSystemEntry): boolean {
23
- if (entry.kind !== 'directory') return false;
24
93
  const name = entry.path.replace(/^\/+/, '').split('/').pop() ?? '';
25
- return name.endsWith('_files');
94
+ if (name.startsWith('.')) return true;
95
+ return entry.kind === 'directory' && name.endsWith('_files');
26
96
  }
27
97
 
28
98
  function filterVisible(entries: FileSystemEntry[]): FileSystemEntry[] {
@@ -32,10 +102,17 @@ function filterVisible(entries: FileSystemEntry[]): FileSystemEntry[] {
32
102
  export interface FileExplorerProps {
33
103
  /** The filesystem to display. */
34
104
  provider: FileSystemProvider | null;
105
+ /** Active document to select and reveal by expanding all ancestor folders. */
106
+ activeFilePath?: string | null;
35
107
  /** Called when any entry is selected (file or directory). */
36
108
  onSelect?: (path: string, kind: 'file' | 'directory') => void;
37
- /** Called after any mutation (create, delete, rename). */
38
- onTreeChange?: () => void;
109
+ /**
110
+ * Wraps destructive tree mutations so the active document session can
111
+ * flush, cancel, or retarget itself before storage changes.
112
+ */
113
+ onTreeMutation?: FileTreeMutationHandler;
114
+ /** Called after any mutation, with move details when paths change. */
115
+ onTreeChange?: (change?: FileTreeChange) => void;
39
116
  /** Called when supported files are dropped onto the explorer. */
40
117
  onImportFiles?: (files: File[]) => void;
41
118
  /** Optional className for the root element. */
@@ -44,7 +121,9 @@ export interface FileExplorerProps {
44
121
 
45
122
  export function FileExplorer({
46
123
  provider,
124
+ activeFilePath,
47
125
  onSelect,
126
+ onTreeMutation,
48
127
  onTreeChange,
49
128
  onImportFiles,
50
129
  className,
@@ -53,11 +132,21 @@ export function FileExplorer({
53
132
  const { childEntries } = tree as typeof tree & {
54
133
  childEntries: Map<string, FileSystemEntry[]>;
55
134
  };
135
+ const { reveal } = tree;
136
+ // Null on surfaces without git (site, tests) — everything degrades.
137
+ const git = useGitContext();
56
138
 
57
139
  const [newItemName, setNewItemName] = useState('');
58
140
  const [newItemType, setNewItemType] = useState<'file' | 'directory' | null>(null);
59
141
  const [dragOver, setDragOver] = useState(false);
60
- const dragCounter = useState({ current: 0 })[0];
142
+ const [draggedEntry, setDraggedEntry] = useState<FileSystemEntry | null>(null);
143
+ const [dropTarget, setDropTarget] = useState<string | null>(null);
144
+ const [moveError, setMoveError] = useState<string | null>(null);
145
+ const dragCounter = useRef(0);
146
+
147
+ useEffect(() => {
148
+ if (activeFilePath) reveal(activeFilePath, 'file');
149
+ }, [activeFilePath, provider, reveal]);
61
150
 
62
151
  const hasSupported = useCallback((dt: DataTransfer): boolean => {
63
152
  for (const item of Array.from(dt.items)) {
@@ -73,6 +162,7 @@ export function FileExplorer({
73
162
 
74
163
  const handleDragEnter = useCallback(
75
164
  (e: React.DragEvent) => {
165
+ if (isInternalDrag(e.dataTransfer)) return;
76
166
  e.preventDefault();
77
167
  dragCounter.current += 1;
78
168
  if (hasSupported(e.dataTransfer)) setDragOver(true);
@@ -81,12 +171,14 @@ export function FileExplorer({
81
171
  );
82
172
 
83
173
  const handleDragOver = useCallback((e: React.DragEvent) => {
174
+ if (isInternalDrag(e.dataTransfer)) return;
84
175
  e.preventDefault();
85
176
  e.dataTransfer.dropEffect = 'copy';
86
177
  }, []);
87
178
 
88
179
  const handleDragLeave = useCallback(
89
180
  (e: React.DragEvent) => {
181
+ if (isInternalDrag(e.dataTransfer)) return;
90
182
  e.preventDefault();
91
183
  dragCounter.current -= 1;
92
184
  if (dragCounter.current <= 0) {
@@ -99,6 +191,7 @@ export function FileExplorer({
99
191
 
100
192
  const handleDrop = useCallback(
101
193
  (e: React.DragEvent) => {
194
+ if (isInternalDrag(e.dataTransfer)) return;
102
195
  e.preventDefault();
103
196
  dragCounter.current = 0;
104
197
  setDragOver(false);
@@ -108,7 +201,7 @@ export function FileExplorer({
108
201
  });
109
202
  if (supported.length > 0) onImportFiles?.(supported);
110
203
  },
111
- [onImportFiles, dragCounter],
204
+ [onImportFiles],
112
205
  );
113
206
 
114
207
  const handleSelect = useCallback(
@@ -141,31 +234,155 @@ export function FileExplorer({
141
234
  // Scope to currently selected folder (or root)
142
235
  const prefix =
143
236
  tree.selectedKind === 'directory' && tree.selectedPath ? `${tree.selectedPath}/` : '';
237
+ let createdPath = `${prefix}${name}`;
144
238
  if (newItemType === 'file') {
145
239
  const filename = name.endsWith('.md') ? name : `${name}.md`;
146
- await tree.createFile(`${prefix}${filename}`, '');
240
+ createdPath = `${prefix}${filename}`;
241
+ await tree.createFile(createdPath, '');
147
242
  } else if (newItemType === 'directory') {
148
- await tree.createDirectory(`${prefix}${name}`);
243
+ await tree.createDirectory(createdPath);
149
244
  }
150
245
  setNewItemName('');
151
246
  setNewItemType(null);
152
- onTreeChange?.();
247
+ onTreeChange?.({ type: 'create', path: createdPath });
153
248
  }, [newItemName, newItemType, tree, onTreeChange]);
154
249
 
250
+ const runTreeMutation = useCallback(
251
+ async (change: FileTreeChange, mutate: () => Promise<void>) => {
252
+ if (onTreeMutation) await onTreeMutation(change, mutate);
253
+ else await mutate();
254
+ onTreeChange?.(change);
255
+ },
256
+ [onTreeMutation, onTreeChange],
257
+ );
258
+
155
259
  const handleDelete = useCallback(
156
- async (path: string) => {
157
- await tree.deleteEntry(path);
158
- onTreeChange?.();
260
+ async (path: string, kind: 'file' | 'directory') => {
261
+ const change: FileTreeChange = { type: 'delete', path, kind };
262
+ await runTreeMutation(change, () => tree.deleteEntry(path));
159
263
  },
160
- [tree, onTreeChange],
264
+ [tree, runTreeMutation],
161
265
  );
162
266
 
163
267
  const handleRename = useCallback(
164
- async (oldPath: string, newPath: string) => {
165
- await tree.renameEntry(oldPath, newPath);
166
- onTreeChange?.();
268
+ async (oldPath: string, newPath: string, kind: 'file' | 'directory') => {
269
+ setMoveError(null);
270
+ const change: FileTreeChange = { type: 'move', oldPath, newPath, kind };
271
+ try {
272
+ await runTreeMutation(change, () => tree.renameEntry(oldPath, newPath, kind));
273
+ } catch (error: unknown) {
274
+ await tree.refresh();
275
+ if (isFileSystemMoveStateError(error) && error.documentLocation === 'destination') {
276
+ onTreeChange?.(change);
277
+ }
278
+ setMoveError(error instanceof Error ? error.message : 'Unable to move this entry.');
279
+ }
280
+ },
281
+ [tree, runTreeMutation, onTreeChange],
282
+ );
283
+
284
+ const handleMove = useCallback(
285
+ async (entry: FileSystemEntry, directoryPath: string) => {
286
+ if (!canMoveTo(entry, directoryPath)) return;
287
+ const newPath = pathInDirectory(entry, directoryPath);
288
+ await handleRename(entry.path, newPath, entry.kind);
289
+ if (directoryPath && !tree.expanded.has(directoryPath)) {
290
+ tree.toggleExpand(directoryPath);
291
+ }
292
+ },
293
+ [handleRename, tree],
294
+ );
295
+
296
+ const handleInternalDragStart = useCallback((e: React.DragEvent, entry: FileSystemEntry) => {
297
+ e.dataTransfer.effectAllowed = 'move';
298
+ e.dataTransfer.setData(INTERNAL_DRAG_TYPE, entry.path);
299
+ e.dataTransfer.setData('text/plain', entry.path);
300
+ setDraggedEntry(entry);
301
+ setMoveError(null);
302
+ }, []);
303
+
304
+ const handleInternalDragEnd = useCallback(() => {
305
+ setDraggedEntry(null);
306
+ setDropTarget(null);
307
+ }, []);
308
+
309
+ const handleEntryDragOver = useCallback(
310
+ (e: React.DragEvent, entry: FileSystemEntry) => {
311
+ if (!isInternalDrag(e.dataTransfer)) return;
312
+ e.stopPropagation();
313
+ const canDrop =
314
+ entry.kind === 'directory' && draggedEntry && canMoveTo(draggedEntry, entry.path);
315
+ e.dataTransfer.dropEffect = canDrop ? 'move' : 'none';
316
+ if (canDrop) {
317
+ e.preventDefault();
318
+ setDropTarget(entry.path);
319
+ } else {
320
+ setDropTarget(null);
321
+ }
167
322
  },
168
- [tree, onTreeChange],
323
+ [draggedEntry],
324
+ );
325
+
326
+ const handleEntryDrop = useCallback(
327
+ (e: React.DragEvent, entry: FileSystemEntry) => {
328
+ if (!isInternalDrag(e.dataTransfer)) return;
329
+ e.preventDefault();
330
+ e.stopPropagation();
331
+ setDropTarget(null);
332
+ if (entry.kind === 'directory' && draggedEntry) {
333
+ void handleMove(draggedEntry, entry.path);
334
+ }
335
+ },
336
+ [draggedEntry, handleMove],
337
+ );
338
+
339
+ const handleRootDragOver = useCallback(
340
+ (e: React.DragEvent) => {
341
+ if (!isInternalDrag(e.dataTransfer) || !draggedEntry) return;
342
+ e.preventDefault();
343
+ e.stopPropagation();
344
+ const canDrop = canMoveTo(draggedEntry, '');
345
+ e.dataTransfer.dropEffect = canDrop ? 'move' : 'none';
346
+ setDropTarget(canDrop ? '' : null);
347
+ },
348
+ [draggedEntry],
349
+ );
350
+
351
+ const handleRootDrop = useCallback(
352
+ (e: React.DragEvent) => {
353
+ if (!isInternalDrag(e.dataTransfer)) return;
354
+ e.preventDefault();
355
+ e.stopPropagation();
356
+ setDropTarget(null);
357
+ if (draggedEntry) void handleMove(draggedEntry, '');
358
+ },
359
+ [draggedEntry, handleMove],
360
+ );
361
+
362
+ const badgeFor = useCallback(
363
+ (entry: FileSystemEntry): FileTreeNodeBadge | undefined => {
364
+ if (!git?.repo) return undefined;
365
+ const key = entry.path.startsWith('/') ? entry.path : `/${entry.path}`;
366
+ const kind = git.badges.get(key);
367
+ if (!kind) return undefined;
368
+ return { kind, glyph: BADGE_GLYPHS[kind], label: BADGE_LABELS[kind] };
369
+ },
370
+ [git],
371
+ );
372
+
373
+ const gitActionsFor = useCallback(
374
+ (entry: FileSystemEntry): FileTreeNodeGitActions | undefined => {
375
+ if (!git?.repo || entry.kind !== 'file') return undefined;
376
+ const path = entry.path.startsWith('/') ? entry.path : `/${entry.path}`;
377
+ return {
378
+ viewChanges: isFileDirty(git.status, path)
379
+ ? () => git.openDialog({ kind: 'diff', path })
380
+ : undefined,
381
+ fileHistory: () => git.openDialog({ kind: 'history', path }),
382
+ openOnRemote: git.remoteWeb ? () => git.openOnRemote(path) : undefined,
383
+ };
384
+ },
385
+ [git],
169
386
  );
170
387
 
171
388
  const renderEntries = useCallback(
@@ -175,20 +392,46 @@ export function FileExplorer({
175
392
  key={entry.path}
176
393
  entry={entry}
177
394
  depth={depth}
178
- expanded={tree.expanded.has(entry.path)}
179
- selected={tree.selectedPath === entry.path}
395
+ expanded={hasEquivalentPath(tree.expanded, entry.path)}
396
+ selected={
397
+ tree.selectedPath !== null &&
398
+ normalisePath(tree.selectedPath) === normalisePath(entry.path)
399
+ }
400
+ badge={badgeFor(entry)}
401
+ gitActions={gitActionsFor(entry)}
180
402
  onToggle={tree.toggleExpand}
181
403
  onSelect={handleSelect}
182
404
  onDelete={handleDelete}
183
405
  onRename={handleRename}
406
+ draggable
407
+ dragging={draggedEntry?.path === entry.path}
408
+ dropTarget={dropTarget === entry.path}
409
+ onDragStart={handleInternalDragStart}
410
+ onDragEnd={handleInternalDragEnd}
411
+ onDragOverEntry={handleEntryDragOver}
412
+ onDropEntry={handleEntryDrop}
184
413
  renderChildren={(dirPath: string) => {
185
- const children = childEntries.get(dirPath) ?? [];
414
+ const children = getEquivalentPathValue(childEntries, dirPath) ?? [];
186
415
  return renderEntries(children, depth + 1);
187
416
  }}
188
417
  />
189
418
  ));
190
419
  },
191
- [tree, handleSelect, handleDelete, handleRename, childEntries],
420
+ [
421
+ tree,
422
+ handleSelect,
423
+ handleDelete,
424
+ handleRename,
425
+ childEntries,
426
+ badgeFor,
427
+ gitActionsFor,
428
+ draggedEntry,
429
+ dropTarget,
430
+ handleInternalDragStart,
431
+ handleInternalDragEnd,
432
+ handleEntryDragOver,
433
+ handleEntryDrop,
434
+ ],
192
435
  );
193
436
 
194
437
  return (
@@ -219,14 +462,6 @@ export function FileExplorer({
219
462
  >
220
463
  <NewFolderIcon />
221
464
  </button>
222
- <button
223
- className="db-explorer-btn"
224
- onClick={() => tree.refresh()}
225
- title="Refresh file list"
226
- aria-label="Refresh file list"
227
- >
228
- <RefreshIcon />
229
- </button>
230
465
  </div>
231
466
  </div>
232
467
 
@@ -261,6 +496,12 @@ export function FileExplorer({
261
496
  </div>
262
497
  )}
263
498
 
499
+ {moveError && (
500
+ <div className="db-tree-error" role="alert">
501
+ {moveError}
502
+ </div>
503
+ )}
504
+
264
505
  {/* Tree — role="tree" only when there are real treeitem children; the
265
506
  loading/empty states get a status role so axe doesn't flag the
266
507
  tree as missing required children. */}
@@ -273,7 +514,12 @@ export function FileExplorer({
273
514
  <div className="db-tree-empty">No files yet</div>
274
515
  </div>
275
516
  ) : (
276
- <div className="db-tree" role="tree">
517
+ <div
518
+ className={`db-tree ${dropTarget === '' ? 'db-tree--drop-target' : ''}`}
519
+ role="tree"
520
+ onDragOver={handleRootDragOver}
521
+ onDrop={handleRootDrop}
522
+ >
277
523
  {renderEntries(tree.entries, 0)}
278
524
  </div>
279
525
  )}