@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
@@ -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';
@@ -4,7 +4,54 @@
4
4
  */
5
5
 
6
6
  import { useState, useCallback, useEffect, useRef } from 'react';
7
- import type { FileSystemProvider, FileSystemEntry } from '@bendyline/docblocks/filesystem';
7
+ import {
8
+ getFileSystemProviderV2,
9
+ moveFileSystemEntry,
10
+ parseWorkspacePath,
11
+ type FileSystemProvider,
12
+ type FileSystemEntry,
13
+ } from '@bendyline/docblocks/filesystem';
14
+
15
+ function normalisePath(path: string): string {
16
+ return parseWorkspacePath(path);
17
+ }
18
+
19
+ function relocatePath(path: string, oldPath: string, newPath: string): string {
20
+ const current = normalisePath(path);
21
+ const oldNormalised = normalisePath(oldPath);
22
+ if (current !== oldNormalised && !current.startsWith(`${oldNormalised}/`)) return path;
23
+ const suffix = current.slice(oldNormalised.length);
24
+ return `${newPath.replace(/\/+$/, '')}${suffix}`;
25
+ }
26
+
27
+ function ancestorPaths(path: string): string[] {
28
+ const canonical = normalisePath(path);
29
+ if (!canonical) return [];
30
+ const segments = canonical.split('/');
31
+ const prefix = /^[\\/]/u.test(path) ? '/' : '';
32
+ return segments
33
+ .slice(0, -1)
34
+ .map((_, index) => `${prefix}${segments.slice(0, index + 1).join('/')}`);
35
+ }
36
+
37
+ function equivalentPathKeys(path: string): string[] {
38
+ const canonical = normalisePath(path);
39
+ return canonical ? [path, canonical, `/${canonical}`] : [path, canonical];
40
+ }
41
+
42
+ function hasEquivalentPath(paths: ReadonlySet<string>, path: string): boolean {
43
+ return equivalentPathKeys(path).some((candidate) => paths.has(candidate));
44
+ }
45
+
46
+ async function readProviderDirectory(
47
+ provider: FileSystemProvider,
48
+ path: string,
49
+ ): Promise<FileSystemEntry[]> {
50
+ const providerV2 = getFileSystemProviderV2(provider);
51
+ if (!providerV2) return provider.readDirectory(path);
52
+ const entries = await providerV2.readDirectory(parseWorkspacePath(path));
53
+ return entries.map((entry) => ({ kind: entry.kind, name: entry.name, path: entry.path }));
54
+ }
8
55
 
9
56
  export interface FileTreeState {
10
57
  /** Flat list of entries for the current directory view. */
@@ -24,14 +71,16 @@ export interface FileTreeActions {
24
71
  toggleExpand: (path: string) => void;
25
72
  /** Select a file or directory. */
26
73
  select: (path: string, kind: 'file' | 'directory') => void;
74
+ /** Select an entry and expand each directory needed to reveal it. */
75
+ reveal: (path: string, kind: 'file' | 'directory') => void;
27
76
  /** Create a new file with optional initial content. */
28
77
  createFile: (path: string, content?: string) => Promise<void>;
29
78
  /** Create a new directory. */
30
79
  createDirectory: (path: string) => Promise<void>;
31
80
  /** Delete a file or directory. */
32
81
  deleteEntry: (path: string) => Promise<void>;
33
- /** Rename a file or directory. */
34
- renameEntry: (oldPath: string, newPath: string) => Promise<void>;
82
+ /** Rename or move a file or directory, including a markdown companion folder. */
83
+ renameEntry: (oldPath: string, newPath: string, kind?: 'file' | 'directory') => Promise<void>;
35
84
  /** Force refresh the tree. */
36
85
  refresh: () => Promise<void>;
37
86
  }
@@ -56,7 +105,7 @@ export function useFileTree(provider: FileSystemProvider | null): FileTreeState
56
105
  }
57
106
  setLoading(true);
58
107
  try {
59
- const root = await providerRef.current.readDirectory('');
108
+ const root = await readProviderDirectory(providerRef.current, '');
60
109
  setEntries(root);
61
110
  } finally {
62
111
  setLoading(false);
@@ -65,7 +114,7 @@ export function useFileTree(provider: FileSystemProvider | null): FileTreeState
65
114
 
66
115
  const loadChildren = useCallback(async (dirPath: string) => {
67
116
  if (!providerRef.current) return;
68
- const children = await providerRef.current.readDirectory(dirPath);
117
+ const children = await readProviderDirectory(providerRef.current, dirPath);
69
118
  setChildEntries((prev) => {
70
119
  const next = new Map(prev);
71
120
  next.set(dirPath, children);
@@ -86,8 +135,8 @@ export function useFileTree(provider: FileSystemProvider | null): FileTreeState
86
135
  (path: string) => {
87
136
  setExpanded((prev) => {
88
137
  const next = new Set(prev);
89
- if (next.has(path)) {
90
- next.delete(path);
138
+ if (hasEquivalentPath(next, path)) {
139
+ for (const candidate of equivalentPathKeys(path)) next.delete(candidate);
91
140
  } else {
92
141
  next.add(path);
93
142
  // Load children when expanding
@@ -104,6 +153,25 @@ export function useFileTree(provider: FileSystemProvider | null): FileTreeState
104
153
  setSelectedKind(kind);
105
154
  }, []);
106
155
 
156
+ const reveal = useCallback(
157
+ (path: string, kind: 'file' | 'directory') => {
158
+ const ancestors = ancestorPaths(path);
159
+ setSelectedPath(path);
160
+ setSelectedKind(kind);
161
+ setExpanded((prev) => {
162
+ const next = new Set(prev);
163
+ for (const ancestor of ancestors) {
164
+ if (!hasEquivalentPath(next, ancestor)) next.add(ancestor);
165
+ }
166
+ return next;
167
+ });
168
+ for (const ancestor of ancestors) {
169
+ void loadChildren(ancestor);
170
+ }
171
+ },
172
+ [loadChildren],
173
+ );
174
+
107
175
  const refresh = useCallback(async () => {
108
176
  await loadRoot();
109
177
  // Reload all expanded directories
@@ -113,10 +181,82 @@ export function useFileTree(provider: FileSystemProvider | null): FileTreeState
113
181
  }
114
182
  }, [loadRoot, loadChildren, expanded]);
115
183
 
184
+ const refreshRef = useRef(refresh);
185
+ refreshRef.current = refresh;
186
+
187
+ // Keep the explorer synchronized with provider changes. Watch-capable
188
+ // providers (including Electron's chokidar-backed provider) update in real
189
+ // time. Calls are serialized and coalesced so event bursts cannot publish
190
+ // directory reads out of order.
191
+ useEffect(() => {
192
+ if (!provider) return;
193
+ const providerV2 = getFileSystemProviderV2(provider);
194
+ if (!providerV2?.capabilities.watch) return;
195
+
196
+ let disposed = false;
197
+ let refreshing = false;
198
+ let refreshAgain = false;
199
+
200
+ const requestRefresh = () => {
201
+ if (disposed) return;
202
+ if (refreshing) {
203
+ refreshAgain = true;
204
+ return;
205
+ }
206
+ refreshing = true;
207
+ void (async () => {
208
+ try {
209
+ do {
210
+ refreshAgain = false;
211
+ await refreshRef.current();
212
+ } while (refreshAgain && !disposed);
213
+ } catch {
214
+ // A later watcher event or window resume will retry the read.
215
+ } finally {
216
+ refreshing = false;
217
+ if (refreshAgain && !disposed) requestRefresh();
218
+ }
219
+ })();
220
+ };
221
+
222
+ const subscription = providerV2.watch(requestRefresh, { onError: requestRefresh });
223
+ void subscription.ready.catch(() => undefined);
224
+ return () => {
225
+ disposed = true;
226
+ void subscription.dispose();
227
+ };
228
+ }, [provider]);
229
+
230
+ // File System Access and IndexedDB do not expose a dependable external
231
+ // watcher. Refreshing on resume catches changes made while this surface was
232
+ // in the background without continuously polling the filesystem.
233
+ useEffect(() => {
234
+ if (!provider) return;
235
+ const refreshOnFocus = () => {
236
+ void refreshRef.current().catch(() => undefined);
237
+ };
238
+ const refreshOnVisibility = () => {
239
+ if (document.visibilityState === 'visible') refreshOnFocus();
240
+ };
241
+ window.addEventListener('focus', refreshOnFocus);
242
+ document.addEventListener('visibilitychange', refreshOnVisibility);
243
+ return () => {
244
+ window.removeEventListener('focus', refreshOnFocus);
245
+ document.removeEventListener('visibilitychange', refreshOnVisibility);
246
+ };
247
+ }, [provider]);
248
+
116
249
  const createFile = useCallback(
117
250
  async (path: string, content = '') => {
118
251
  if (!providerRef.current) return;
119
- await providerRef.current.writeFile(path, content);
252
+ const providerV2 = getFileSystemProviderV2(providerRef.current);
253
+ if (providerV2) {
254
+ await providerV2.writeFile(parseWorkspacePath(path), new TextEncoder().encode(content), {
255
+ mode: 'create',
256
+ });
257
+ } else {
258
+ await providerRef.current.writeFile(path, content);
259
+ }
120
260
  await refresh();
121
261
  },
122
262
  [refresh],
@@ -125,7 +265,12 @@ export function useFileTree(provider: FileSystemProvider | null): FileTreeState
125
265
  const createDirectory = useCallback(
126
266
  async (path: string) => {
127
267
  if (!providerRef.current) return;
128
- await providerRef.current.createDirectory(path);
268
+ const providerV2 = getFileSystemProviderV2(providerRef.current);
269
+ if (providerV2) {
270
+ await providerV2.createDirectory(parseWorkspacePath(path), { mode: 'create' });
271
+ } else {
272
+ await providerRef.current.createDirectory(path);
273
+ }
129
274
  await refresh();
130
275
  },
131
276
  [refresh],
@@ -134,7 +279,20 @@ export function useFileTree(provider: FileSystemProvider | null): FileTreeState
134
279
  const deleteEntry = useCallback(
135
280
  async (path: string) => {
136
281
  if (!providerRef.current) return;
137
- await providerRef.current.delete(path);
282
+ const providerV2 = getFileSystemProviderV2(providerRef.current);
283
+ if (providerV2) {
284
+ const canonical = parseWorkspacePath(path);
285
+ const entry = await providerV2.stat(canonical);
286
+ if (entry) {
287
+ await providerV2.remove(canonical, {
288
+ recursive: entry.kind === 'directory',
289
+ missing: 'error',
290
+ expectedVersion: entry.version,
291
+ });
292
+ }
293
+ } else {
294
+ await providerRef.current.delete(path);
295
+ }
138
296
  if (selectedPath === path) {
139
297
  setSelectedPath(null);
140
298
  setSelectedKind(null);
@@ -145,15 +303,30 @@ export function useFileTree(provider: FileSystemProvider | null): FileTreeState
145
303
  );
146
304
 
147
305
  const renameEntry = useCallback(
148
- async (oldPath: string, newPath: string) => {
306
+ async (oldPath: string, newPath: string, kind?: 'file' | 'directory') => {
149
307
  if (!providerRef.current) return;
150
- await providerRef.current.rename(oldPath, newPath);
151
- if (selectedPath === oldPath) {
152
- setSelectedPath(newPath);
308
+ const oldNormalised = normalisePath(oldPath);
309
+ const knownEntry = [...entries, ...childEntries.values()]
310
+ .flat()
311
+ .find((entry) => normalisePath(entry.path) === oldNormalised);
312
+ const entryKind = kind ?? knownEntry?.kind ?? 'file';
313
+ await moveFileSystemEntry(providerRef.current, oldPath, newPath, entryKind);
314
+
315
+ if (selectedPath) {
316
+ setSelectedPath(relocatePath(selectedPath, oldPath, newPath));
317
+ }
318
+
319
+ const nextExpanded = new Set(
320
+ [...expanded].map((path) => relocatePath(path, oldPath, newPath)),
321
+ );
322
+ setExpanded(nextExpanded);
323
+ setChildEntries(new Map());
324
+ await loadRoot();
325
+ for (const dirPath of nextExpanded) {
326
+ await loadChildren(dirPath);
153
327
  }
154
- await refresh();
155
328
  },
156
- [refresh, selectedPath],
329
+ [selectedPath, entries, childEntries, expanded, loadRoot, loadChildren],
157
330
  );
158
331
 
159
332
  // Merge root entries with child entries for a flat tree representation
@@ -167,6 +340,7 @@ export function useFileTree(provider: FileSystemProvider | null): FileTreeState
167
340
  loading,
168
341
  toggleExpand,
169
342
  select,
343
+ reveal,
170
344
  createFile,
171
345
  createDirectory,
172
346
  deleteEntry,
@@ -0,0 +1,84 @@
1
+ /**
2
+ * GitAuthGuidanceDialog — shown when a git operation fails to authenticate.
3
+ *
4
+ * DocBlocks deliberately never collects or stores credentials, so this
5
+ * dialog only explains the three supported sign-in routes (credential
6
+ * helper, GitHub CLI, SSH key). The copy is exported as a standalone
7
+ * component so the clone dialog can embed it after an auth failure.
8
+ */
9
+
10
+ import { maybeGetDocBlocksHost } from '@bendyline/docblocks/host';
11
+ import { Dialog } from '../components/Dialog.js';
12
+
13
+ const LEARN_MORE_URL =
14
+ 'https://docs.github.com/en/get-started/getting-started-with-git/caching-your-github-credentials-in-git';
15
+
16
+ const OPERATION_LABELS: Record<'push' | 'pull' | 'fetch' | 'clone' | 'pr', string> = {
17
+ push: 'push',
18
+ pull: 'pull',
19
+ fetch: 'fetch',
20
+ clone: 'clone',
21
+ pr: 'create the pull request',
22
+ };
23
+
24
+ /** The credential guidance copy, reusable outside this dialog (clone flow). */
25
+ export function GitCredentialGuidance({ detail }: { detail?: string }) {
26
+ return (
27
+ <>
28
+ <p className="db-settings-hint">
29
+ DocBlocks never stores Git credentials. Sign in with one of these, then try again:
30
+ </p>
31
+ <ul className="db-git-hint">
32
+ <li>
33
+ A git credential helper (<code>git config credential.helper</code>)
34
+ </li>
35
+ <li>
36
+ The GitHub CLI (<code>gh auth login</code>)
37
+ </li>
38
+ <li>An SSH key registered with your remote</li>
39
+ </ul>
40
+ {detail && (
41
+ <details>
42
+ <summary>Details</summary>
43
+ <p className="db-git-hint">{detail}</p>
44
+ </details>
45
+ )}
46
+ </>
47
+ );
48
+ }
49
+
50
+ export function GitAuthGuidanceDialog({
51
+ operation,
52
+ detail,
53
+ onClose,
54
+ }: {
55
+ operation: 'push' | 'pull' | 'fetch' | 'clone' | 'pr';
56
+ detail?: string;
57
+ onClose: () => void;
58
+ }) {
59
+ return (
60
+ <Dialog
61
+ title="Git needs credentials"
62
+ onClose={onClose}
63
+ footer={
64
+ <>
65
+ <button
66
+ type="button"
67
+ className="db-git-secondary-btn"
68
+ onClick={() => void maybeGetDocBlocksHost()?.shell.openExternal(LEARN_MORE_URL)}
69
+ >
70
+ Learn more
71
+ </button>
72
+ <button type="button" className="db-git-primary-btn" onClick={onClose}>
73
+ Close
74
+ </button>
75
+ </>
76
+ }
77
+ >
78
+ <p className="db-settings-hint">
79
+ Git could not authenticate with the remote while trying to {OPERATION_LABELS[operation]}.
80
+ </p>
81
+ <GitCredentialGuidance detail={detail} />
82
+ </Dialog>
83
+ );
84
+ }