@bendyline/docblocks-react 1.1.1 → 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 +50 -15
  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 +17 -4
  9. package/dist/DocBlocksShell/DocBlocksShell.d.ts.map +1 -1
  10. package/dist/DocBlocksShell/DocBlocksShell.js +1710 -299
  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 +39 -20
  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 +161 -26
  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 +58 -25
  55. package/dist/Export/run-export.js.map +1 -1
  56. package/dist/Export/transform-summaries.d.ts +7 -0
  57. package/dist/Export/transform-summaries.d.ts.map +1 -0
  58. package/dist/Export/transform-summaries.js +6 -0
  59. package/dist/Export/transform-summaries.js.map +1 -0
  60. package/dist/ExportDialog-MEUISWZX.js +9 -0
  61. package/dist/ExportDialog-MEUISWZX.js.map +1 -0
  62. package/dist/ExportToolbarControls-D2YDWOXW.js +373 -0
  63. package/dist/ExportToolbarControls-D2YDWOXW.js.map +1 -0
  64. package/dist/FileExplorer/FileExplorer.d.ts +26 -4
  65. package/dist/FileExplorer/FileExplorer.d.ts.map +1 -1
  66. package/dist/FileExplorer/FileExplorer.js +204 -29
  67. package/dist/FileExplorer/FileExplorer.js.map +1 -1
  68. package/dist/FileExplorer/FileTreeNode.d.ts +24 -3
  69. package/dist/FileExplorer/FileTreeNode.d.ts.map +1 -1
  70. package/dist/FileExplorer/FileTreeNode.js +27 -8
  71. package/dist/FileExplorer/FileTreeNode.js.map +1 -1
  72. package/dist/FileExplorer/index.d.ts +1 -1
  73. package/dist/FileExplorer/index.d.ts.map +1 -1
  74. package/dist/FileExplorer/useFileTree.d.ts +5 -3
  75. package/dist/FileExplorer/useFileTree.d.ts.map +1 -1
  76. package/dist/FileExplorer/useFileTree.js +173 -13
  77. package/dist/FileExplorer/useFileTree.js.map +1 -1
  78. package/dist/Git/GitAuthGuidanceDialog.d.ts +18 -0
  79. package/dist/Git/GitAuthGuidanceDialog.d.ts.map +1 -0
  80. package/dist/Git/GitAuthGuidanceDialog.js +27 -0
  81. package/dist/Git/GitAuthGuidanceDialog.js.map +1 -0
  82. package/dist/Git/GitBranchesDialog.d.ts +13 -0
  83. package/dist/Git/GitBranchesDialog.d.ts.map +1 -0
  84. package/dist/Git/GitBranchesDialog.js +59 -0
  85. package/dist/Git/GitBranchesDialog.js.map +1 -0
  86. package/dist/Git/GitCloneDialog.d.ts +14 -0
  87. package/dist/Git/GitCloneDialog.d.ts.map +1 -0
  88. package/dist/Git/GitCloneDialog.js +79 -0
  89. package/dist/Git/GitCloneDialog.js.map +1 -0
  90. package/dist/Git/GitCommitDialog.d.ts +12 -0
  91. package/dist/Git/GitCommitDialog.d.ts.map +1 -0
  92. package/dist/Git/GitCommitDialog.js +38 -0
  93. package/dist/Git/GitCommitDialog.js.map +1 -0
  94. package/dist/Git/GitConflictsDialog.d.ts +12 -0
  95. package/dist/Git/GitConflictsDialog.d.ts.map +1 -0
  96. package/dist/Git/GitConflictsDialog.js +17 -0
  97. package/dist/Git/GitConflictsDialog.js.map +1 -0
  98. package/dist/Git/GitContext.d.ts +89 -0
  99. package/dist/Git/GitContext.d.ts.map +1 -0
  100. package/dist/Git/GitContext.js +15 -0
  101. package/dist/Git/GitContext.js.map +1 -0
  102. package/dist/Git/GitDiffDialog.d.ts +20 -0
  103. package/dist/Git/GitDiffDialog.d.ts.map +1 -0
  104. package/dist/Git/GitDiffDialog.js +100 -0
  105. package/dist/Git/GitDiffDialog.js.map +1 -0
  106. package/dist/Git/GitDiffView.d.ts +23 -0
  107. package/dist/Git/GitDiffView.d.ts.map +1 -0
  108. package/dist/Git/GitDiffView.js +77 -0
  109. package/dist/Git/GitDiffView.js.map +1 -0
  110. package/dist/Git/GitHistoryDialog.d.ts +15 -0
  111. package/dist/Git/GitHistoryDialog.d.ts.map +1 -0
  112. package/dist/Git/GitHistoryDialog.js +107 -0
  113. package/dist/Git/GitHistoryDialog.js.map +1 -0
  114. package/dist/Git/GitStatusBar.d.ts +8 -0
  115. package/dist/Git/GitStatusBar.d.ts.map +1 -0
  116. package/dist/Git/GitStatusBar.js +49 -0
  117. package/dist/Git/GitStatusBar.js.map +1 -0
  118. package/dist/Git/GitToolbarControl.d.ts +15 -0
  119. package/dist/Git/GitToolbarControl.d.ts.map +1 -0
  120. package/dist/Git/GitToolbarControl.js +51 -0
  121. package/dist/Git/GitToolbarControl.js.map +1 -0
  122. package/dist/Git/GitUI.d.ts +14 -0
  123. package/dist/Git/GitUI.d.ts.map +1 -0
  124. package/dist/Git/GitUI.js +21 -0
  125. package/dist/Git/GitUI.js.map +1 -0
  126. package/dist/Git/commit-selection.d.ts +22 -0
  127. package/dist/Git/commit-selection.d.ts.map +1 -0
  128. package/dist/Git/commit-selection.js +47 -0
  129. package/dist/Git/commit-selection.js.map +1 -0
  130. package/dist/Git/git-clone-utils.d.ts +8 -0
  131. package/dist/Git/git-clone-utils.d.ts.map +1 -0
  132. package/dist/Git/git-clone-utils.js +26 -0
  133. package/dist/Git/git-clone-utils.js.map +1 -0
  134. package/dist/Git/git-status.d.ts +24 -0
  135. package/dist/Git/git-status.d.ts.map +1 -0
  136. package/dist/Git/git-status.js +105 -0
  137. package/dist/Git/git-status.js.map +1 -0
  138. package/dist/Git/useGit.d.ts +15 -0
  139. package/dist/Git/useGit.d.ts.map +1 -0
  140. package/dist/Git/useGit.js +206 -0
  141. package/dist/Git/useGit.js.map +1 -0
  142. package/dist/Git/useGitStatus.d.ts +17 -0
  143. package/dist/Git/useGitStatus.d.ts.map +1 -0
  144. package/dist/Git/useGitStatus.js +63 -0
  145. package/dist/Git/useGitStatus.js.map +1 -0
  146. package/dist/GitToolbarControl-LMIR4R7I.js +117 -0
  147. package/dist/GitToolbarControl-LMIR4R7I.js.map +1 -0
  148. package/dist/GitUI-KGF5MSNV.js +1048 -0
  149. package/dist/GitUI-KGF5MSNV.js.map +1 -0
  150. package/dist/LazyEditorShell-EF2BWRXU.js +6 -0
  151. package/dist/LazyEditorShell-EF2BWRXU.js.map +1 -0
  152. package/dist/Settings/Settings.d.ts +23 -0
  153. package/dist/Settings/Settings.d.ts.map +1 -0
  154. package/dist/Settings/Settings.js +22 -0
  155. package/dist/Settings/Settings.js.map +1 -0
  156. package/dist/Settings/public-api.d.ts +3 -0
  157. package/dist/Settings/public-api.d.ts.map +1 -0
  158. package/dist/Settings/public-api.js +2 -0
  159. package/dist/Settings/public-api.js.map +1 -0
  160. package/dist/WorkspacePicker/WorkspacePicker.d.ts +6 -1
  161. package/dist/WorkspacePicker/WorkspacePicker.d.ts.map +1 -1
  162. package/dist/WorkspacePicker/WorkspacePicker.js +16 -5
  163. package/dist/WorkspacePicker/WorkspacePicker.js.map +1 -1
  164. package/dist/chunk-3SAXDU2U.js +157 -0
  165. package/dist/chunk-3SAXDU2U.js.map +1 -0
  166. package/dist/chunk-4XXSWN3T.js +13 -0
  167. package/dist/chunk-4XXSWN3T.js.map +1 -0
  168. package/dist/chunk-5A4KEWKT.js +97 -0
  169. package/dist/chunk-5A4KEWKT.js.map +1 -0
  170. package/dist/chunk-AOVTHZU7.js +321 -0
  171. package/dist/chunk-AOVTHZU7.js.map +1 -0
  172. package/dist/chunk-DD4VFJQI.js +51 -0
  173. package/dist/chunk-DD4VFJQI.js.map +1 -0
  174. package/dist/chunk-GSNZSCIK.js +55 -0
  175. package/dist/chunk-GSNZSCIK.js.map +1 -0
  176. package/dist/chunk-VHDP4IPL.js +70 -0
  177. package/dist/chunk-VHDP4IPL.js.map +1 -0
  178. package/dist/chunk-ZI56BNB7.js +270 -0
  179. package/dist/chunk-ZI56BNB7.js.map +1 -0
  180. package/dist/components/Dialog.d.ts +22 -0
  181. package/dist/components/Dialog.d.ts.map +1 -0
  182. package/dist/components/Dialog.js +33 -0
  183. package/dist/components/Dialog.js.map +1 -0
  184. package/dist/editor.d.ts +10 -0
  185. package/dist/editor.d.ts.map +1 -0
  186. package/dist/editor.js +70 -0
  187. package/dist/editor.js.map +1 -0
  188. package/dist/export/index.d.ts +87 -0
  189. package/dist/export/index.js +26 -0
  190. package/dist/export/index.js.map +1 -0
  191. package/dist/hooks/index.d.ts +2 -1
  192. package/dist/hooks/index.d.ts.map +1 -1
  193. package/dist/hooks/index.js +1 -1
  194. package/dist/hooks/index.js.map +1 -1
  195. package/dist/hooks/useDocumentSession.d.ts +16 -0
  196. package/dist/hooks/useDocumentSession.d.ts.map +1 -0
  197. package/dist/hooks/useDocumentSession.js +18 -0
  198. package/dist/hooks/useDocumentSession.js.map +1 -0
  199. package/dist/icons.d.ts +11 -6
  200. package/dist/icons.d.ts.map +1 -1
  201. package/dist/icons.js +21 -20
  202. package/dist/icons.js.map +1 -1
  203. package/dist/index.d.ts +8 -5
  204. package/dist/index.d.ts.map +1 -1
  205. package/dist/index.js +4 -2
  206. package/dist/index.js.map +1 -1
  207. package/dist/preferences/theme.d.ts +18 -0
  208. package/dist/preferences/theme.d.ts.map +1 -0
  209. package/dist/preferences/theme.js +63 -0
  210. package/dist/preferences/theme.js.map +1 -0
  211. package/dist/provider-lease.d.ts +10 -0
  212. package/dist/provider-lease.d.ts.map +1 -0
  213. package/dist/provider-lease.js +42 -0
  214. package/dist/provider-lease.js.map +1 -0
  215. package/dist/run-export-ICTZITXC.js +10 -0
  216. package/dist/run-export-ICTZITXC.js.map +1 -0
  217. package/dist/settings/index.d.ts +28 -0
  218. package/dist/settings/index.js +12 -0
  219. package/dist/settings/index.js.map +1 -0
  220. package/package.json +27 -10
  221. package/src/AppMenu/AppMenu.tsx +169 -99
  222. package/src/AppMenu/index.ts +1 -1
  223. package/src/DocBlocksShell/DocBlocksShell.tsx +2183 -516
  224. package/src/DocBlocksShell/LazyEditorShell.ts +2 -0
  225. package/src/DocBlocksShell/UpdateAvailableNotice.tsx +60 -0
  226. package/src/DocBlocksShell/document-title.ts +19 -0
  227. package/src/DocBlocksShell/workspace-authority-barrier.ts +26 -0
  228. package/src/Export/DeferredExportToolbarControls.tsx +23 -0
  229. package/src/Export/ExportDialog.tsx +102 -27
  230. package/src/Export/ExportToolbarControls.tsx +343 -57
  231. package/src/Export/export-destination.ts +30 -0
  232. package/src/Export/index.ts +18 -4
  233. package/src/Export/public-api.ts +17 -0
  234. package/src/Export/run-export.ts +66 -29
  235. package/src/Export/transform-summaries.ts +14 -0
  236. package/src/FileExplorer/FileExplorer.tsx +284 -38
  237. package/src/FileExplorer/FileTreeNode.tsx +100 -8
  238. package/src/FileExplorer/index.ts +1 -1
  239. package/src/FileExplorer/useFileTree.ts +190 -16
  240. package/src/Git/GitAuthGuidanceDialog.tsx +84 -0
  241. package/src/Git/GitBranchesDialog.tsx +144 -0
  242. package/src/Git/GitCloneDialog.tsx +200 -0
  243. package/src/Git/GitCommitDialog.tsx +119 -0
  244. package/src/Git/GitConflictsDialog.tsx +62 -0
  245. package/src/Git/GitContext.tsx +88 -0
  246. package/src/Git/GitDiffDialog.tsx +147 -0
  247. package/src/Git/GitDiffView.tsx +105 -0
  248. package/src/Git/GitHistoryDialog.tsx +195 -0
  249. package/src/Git/GitStatusBar.tsx +157 -0
  250. package/src/Git/GitToolbarControl.tsx +123 -0
  251. package/src/Git/GitUI.tsx +68 -0
  252. package/src/Git/commit-selection.ts +63 -0
  253. package/src/Git/git-clone-utils.ts +23 -0
  254. package/src/Git/git-status.ts +113 -0
  255. package/src/Git/useGit.ts +279 -0
  256. package/src/Git/useGitStatus.ts +78 -0
  257. package/src/Settings/Settings.tsx +118 -0
  258. package/src/Settings/public-api.ts +8 -0
  259. package/src/WorkspacePicker/WorkspacePicker.tsx +62 -8
  260. package/src/components/Dialog.tsx +81 -0
  261. package/src/editor.ts +72 -0
  262. package/src/hooks/index.ts +2 -1
  263. package/src/hooks/useDocumentSession.ts +37 -0
  264. package/src/icons.tsx +33 -55
  265. package/src/index.ts +35 -5
  266. package/src/preferences/theme.ts +67 -0
  267. package/src/provider-lease.ts +52 -0
  268. package/src/styles/docblocks.css +2247 -77
  269. package/src/types/pwa-dom.d.ts +31 -0
  270. package/dist/hooks/useAutoSave.d.ts +0 -15
  271. package/dist/hooks/useAutoSave.d.ts.map +0 -1
  272. package/dist/hooks/useAutoSave.js +0 -83
  273. package/dist/hooks/useAutoSave.js.map +0 -1
  274. package/src/fonts/fonts.css +0 -362
  275. package/src/fonts/licenses/LICENSE-cormorant-garamond.txt +0 -93
  276. package/src/fonts/licenses/LICENSE-crimson-text.txt +0 -93
  277. package/src/fonts/licenses/LICENSE-dm-sans.txt +0 -93
  278. package/src/fonts/licenses/LICENSE-dm-serif-display.txt +0 -93
  279. package/src/fonts/licenses/LICENSE-hanken-grotesk.txt +0 -94
  280. package/src/fonts/licenses/LICENSE-ibm-plex-sans.txt +0 -93
  281. package/src/fonts/licenses/LICENSE-inter.txt +0 -93
  282. package/src/fonts/licenses/LICENSE-jetbrains-mono.txt +0 -93
  283. package/src/fonts/licenses/LICENSE-lora.txt +0 -93
  284. package/src/fonts/licenses/LICENSE-merriweather.txt +0 -93
  285. package/src/fonts/licenses/LICENSE-oswald.txt +0 -93
  286. package/src/fonts/licenses/LICENSE-playfair-display.txt +0 -93
  287. package/src/fonts/licenses/LICENSE-pt-serif.txt +0 -93
  288. package/src/fonts/licenses/LICENSE-roboto.txt +0 -93
  289. package/src/fonts/licenses/LICENSE-source-serif-4.txt +0 -93
  290. package/src/fonts/woff2/cormorant-garamond-400.woff2 +0 -0
  291. package/src/fonts/woff2/cormorant-garamond-700.woff2 +0 -0
  292. package/src/fonts/woff2/crimson-text-400.woff2 +0 -0
  293. package/src/fonts/woff2/crimson-text-400i.woff2 +0 -0
  294. package/src/fonts/woff2/crimson-text-700.woff2 +0 -0
  295. package/src/fonts/woff2/dm-sans-400.woff2 +0 -0
  296. package/src/fonts/woff2/dm-sans-400i.woff2 +0 -0
  297. package/src/fonts/woff2/dm-sans-500.woff2 +0 -0
  298. package/src/fonts/woff2/dm-sans-700.woff2 +0 -0
  299. package/src/fonts/woff2/dm-serif-display-400.woff2 +0 -0
  300. package/src/fonts/woff2/hanken-grotesk-400.woff2 +0 -0
  301. package/src/fonts/woff2/hanken-grotesk-400i.woff2 +0 -0
  302. package/src/fonts/woff2/hanken-grotesk-500.woff2 +0 -0
  303. package/src/fonts/woff2/hanken-grotesk-600.woff2 +0 -0
  304. package/src/fonts/woff2/hanken-grotesk-700.woff2 +0 -0
  305. package/src/fonts/woff2/ibm-plex-sans-400.woff2 +0 -0
  306. package/src/fonts/woff2/ibm-plex-sans-400i.woff2 +0 -0
  307. package/src/fonts/woff2/ibm-plex-sans-500.woff2 +0 -0
  308. package/src/fonts/woff2/ibm-plex-sans-600.woff2 +0 -0
  309. package/src/fonts/woff2/ibm-plex-sans-700.woff2 +0 -0
  310. package/src/fonts/woff2/inter-400.woff2 +0 -0
  311. package/src/fonts/woff2/inter-400i.woff2 +0 -0
  312. package/src/fonts/woff2/inter-500.woff2 +0 -0
  313. package/src/fonts/woff2/inter-600.woff2 +0 -0
  314. package/src/fonts/woff2/inter-700.woff2 +0 -0
  315. package/src/fonts/woff2/jetbrains-mono-400.woff2 +0 -0
  316. package/src/fonts/woff2/jetbrains-mono-400i.woff2 +0 -0
  317. package/src/fonts/woff2/jetbrains-mono-700.woff2 +0 -0
  318. package/src/fonts/woff2/lora-400.woff2 +0 -0
  319. package/src/fonts/woff2/lora-400i.woff2 +0 -0
  320. package/src/fonts/woff2/lora-700.woff2 +0 -0
  321. package/src/fonts/woff2/merriweather-400.woff2 +0 -0
  322. package/src/fonts/woff2/merriweather-700.woff2 +0 -0
  323. package/src/fonts/woff2/oswald-400.woff2 +0 -0
  324. package/src/fonts/woff2/oswald-700.woff2 +0 -0
  325. package/src/fonts/woff2/playfair-display-400.woff2 +0 -0
  326. package/src/fonts/woff2/playfair-display-700.woff2 +0 -0
  327. package/src/fonts/woff2/pt-serif-400.woff2 +0 -0
  328. package/src/fonts/woff2/pt-serif-700.woff2 +0 -0
  329. package/src/fonts/woff2/roboto-400.woff2 +0 -0
  330. package/src/fonts/woff2/roboto-400i.woff2 +0 -0
  331. package/src/fonts/woff2/roboto-700.woff2 +0 -0
  332. package/src/fonts/woff2/source-serif-4-400.woff2 +0 -0
  333. package/src/fonts/woff2/source-serif-4-400i.woff2 +0 -0
  334. package/src/fonts/woff2/source-serif-4-700.woff2 +0 -0
  335. package/src/hooks/useAutoSave.ts +0 -116
@@ -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
  )}
@@ -7,16 +7,39 @@ import { createPortal } from 'react-dom';
7
7
  import type { FileSystemEntry } from '@bendyline/docblocks/filesystem';
8
8
  import { MoreIcon } from '../icons.js';
9
9
 
10
+ /** Git decoration for a row — precomputed by FileExplorer so this node stays context-free. */
11
+ export interface FileTreeNodeBadge {
12
+ kind: string;
13
+ glyph: string;
14
+ label: string;
15
+ }
16
+
17
+ /** Git context-menu actions, prebound to this entry's path. */
18
+ export interface FileTreeNodeGitActions {
19
+ viewChanges?: () => void;
20
+ fileHistory?: () => void;
21
+ openOnRemote?: () => void;
22
+ }
23
+
10
24
  export interface FileTreeNodeProps {
11
25
  entry: FileSystemEntry;
12
26
  depth: number;
13
27
  expanded: boolean;
14
28
  selected: boolean;
29
+ badge?: FileTreeNodeBadge;
30
+ gitActions?: FileTreeNodeGitActions;
15
31
  children?: FileSystemEntry[];
16
32
  onToggle: (path: string) => void;
17
33
  onSelect: (path: string) => void;
18
- onDelete: (path: string) => Promise<void>;
19
- onRename: (oldPath: string, newPath: string) => Promise<void>;
34
+ onDelete: (path: string, kind: 'file' | 'directory') => Promise<void>;
35
+ onRename: (oldPath: string, newPath: string, kind: 'file' | 'directory') => Promise<void>;
36
+ draggable?: boolean;
37
+ dragging?: boolean;
38
+ dropTarget?: boolean;
39
+ onDragStart?: (event: React.DragEvent, entry: FileSystemEntry) => void;
40
+ onDragEnd?: () => void;
41
+ onDragOverEntry?: (event: React.DragEvent, entry: FileSystemEntry) => void;
42
+ onDropEntry?: (event: React.DragEvent, entry: FileSystemEntry) => void;
20
43
  renderChildren?: (dirPath: string) => React.ReactNode;
21
44
  }
22
45
 
@@ -25,10 +48,19 @@ export function FileTreeNode({
25
48
  depth,
26
49
  expanded,
27
50
  selected,
51
+ badge,
52
+ gitActions,
28
53
  onToggle,
29
54
  onSelect,
30
55
  onDelete,
31
56
  onRename,
57
+ draggable = false,
58
+ dragging = false,
59
+ dropTarget = false,
60
+ onDragStart,
61
+ onDragEnd,
62
+ onDragOverEntry,
63
+ onDropEntry,
32
64
  renderChildren,
33
65
  }: FileTreeNodeProps) {
34
66
  const [renaming, setRenaming] = useState(false);
@@ -76,15 +108,19 @@ export function FileTreeNode({
76
108
  ? entry.path.slice(0, entry.path.lastIndexOf('/'))
77
109
  : '';
78
110
  const newPath = parentPath ? `${parentPath}/${renameValue}` : renameValue;
79
- await onRename(entry.path, newPath);
111
+ await onRename(entry.path, newPath, entry.kind);
80
112
  }
81
113
  setRenaming(false);
82
- }, [renameValue, entry.name, entry.path, onRename]);
114
+ }, [renameValue, entry.name, entry.path, entry.kind, onRename]);
83
115
 
84
116
  const handleDeleteClick = useCallback(async () => {
85
117
  setShowContext(false);
86
- await onDelete(entry.path);
87
- }, [entry.path, onDelete]);
118
+ const description = entry.kind === 'directory' ? 'folder and everything inside it' : 'document';
119
+ if (!window.confirm(`Delete the ${description} "${entry.name}"? This cannot be undone.`)) {
120
+ return;
121
+ }
122
+ await onDelete(entry.path, entry.kind);
123
+ }, [entry.name, entry.path, entry.kind, onDelete]);
88
124
 
89
125
  // Close context menu on outside click or scroll
90
126
  useEffect(() => {
@@ -112,6 +148,12 @@ export function FileTreeNode({
112
148
  }
113
149
  }, [renaming]);
114
150
 
151
+ // Active documents can be revealed after asynchronous ancestor loads. Keep
152
+ // the selected row inside the explorer's scroll viewport once it mounts.
153
+ useEffect(() => {
154
+ if (selected) nodeRef.current?.scrollIntoView?.({ block: 'nearest' });
155
+ }, [selected]);
156
+
115
157
  // Clamp menu inside viewport after it renders
116
158
  useLayoutEffect(() => {
117
159
  if (!showContext || !contextRef.current) return;
@@ -135,13 +177,19 @@ export function FileTreeNode({
135
177
  return (
136
178
  <div className="db-tree-node" ref={nodeRef}>
137
179
  <div
138
- className={`db-tree-row ${selected ? 'db-tree-row--selected' : ''}`}
180
+ className={`db-tree-row ${selected ? 'db-tree-row--selected' : ''} ${dragging ? 'db-tree-row--dragging' : ''} ${dropTarget ? 'db-tree-row--drop-target' : ''}`}
139
181
  style={{ paddingLeft: depth * 16 + 4 }}
140
182
  onClick={handleClick}
141
183
  onContextMenu={handleContextMenu}
184
+ draggable={draggable && !renaming}
185
+ onDragStart={(event) => onDragStart?.(event, entry)}
186
+ onDragEnd={onDragEnd}
187
+ onDragOver={(event) => onDragOverEntry?.(event, entry)}
188
+ onDrop={(event) => onDropEntry?.(event, entry)}
142
189
  role="treeitem"
143
190
  aria-expanded={isDir ? expanded : undefined}
144
191
  aria-selected={selected}
192
+ aria-label={badge ? `${entry.name}, ${badge.label}` : undefined}
145
193
  tabIndex={0}
146
194
  onKeyDown={(e) => {
147
195
  if (e.key === 'Enter') handleClick();
@@ -166,6 +214,11 @@ export function FileTreeNode({
166
214
  <span className="db-tree-label">
167
215
  {entry.name.endsWith('.md') ? entry.name.slice(0, -3) : entry.name}
168
216
  </span>
217
+ {badge && (
218
+ <span className={`db-git-badge db-git-badge--${badge.kind}`} aria-hidden="true">
219
+ {badge.glyph}
220
+ </span>
221
+ )}
169
222
  <button
170
223
  type="button"
171
224
  className={`db-tree-more${showContext ? ' db-tree-more--active' : ''}`}
@@ -176,7 +229,7 @@ export function FileTreeNode({
176
229
  aria-expanded={showContext}
177
230
  tabIndex={-1}
178
231
  >
179
- <MoreIcon width={14} height={14} />
232
+ <MoreIcon />
180
233
  </button>
181
234
  </>
182
235
  )}
@@ -193,6 +246,45 @@ export function FileTreeNode({
193
246
  <button className="db-tree-context-item" onClick={handleRenameStart}>
194
247
  Rename
195
248
  </button>
249
+ {gitActions && (gitActions.viewChanges || gitActions.fileHistory) && (
250
+ <>
251
+ <div className="db-tree-context-divider" role="separator" />
252
+ {gitActions.viewChanges && (
253
+ <button
254
+ className="db-tree-context-item"
255
+ onClick={() => {
256
+ setShowContext(false);
257
+ gitActions.viewChanges?.();
258
+ }}
259
+ >
260
+ View changes
261
+ </button>
262
+ )}
263
+ {gitActions.fileHistory && (
264
+ <button
265
+ className="db-tree-context-item"
266
+ onClick={() => {
267
+ setShowContext(false);
268
+ gitActions.fileHistory?.();
269
+ }}
270
+ >
271
+ File history…
272
+ </button>
273
+ )}
274
+ {gitActions.openOnRemote && (
275
+ <button
276
+ className="db-tree-context-item"
277
+ onClick={() => {
278
+ setShowContext(false);
279
+ gitActions.openOnRemote?.();
280
+ }}
281
+ >
282
+ Open on remote
283
+ </button>
284
+ )}
285
+ <div className="db-tree-context-divider" role="separator" />
286
+ </>
287
+ )}
196
288
  <button
197
289
  className="db-tree-context-item db-tree-context-item--danger"
198
290
  onClick={handleDeleteClick}
@@ -1,5 +1,5 @@
1
1
  export { FileExplorer } from './FileExplorer.js';
2
- export type { FileExplorerProps } from './FileExplorer.js';
2
+ export type { FileExplorerProps, FileTreeChange, FileTreeMutationHandler } from './FileExplorer.js';
3
3
  export { FileTreeNode } from './FileTreeNode.js';
4
4
  export type { FileTreeNodeProps } from './FileTreeNode.js';
5
5
  export { useFileTree } from './useFileTree.js';