@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
@@ -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
+ }
@@ -0,0 +1,144 @@
1
+ /**
2
+ * GitBranchesDialog — switch between local branches or create a new one.
3
+ *
4
+ * Loads the branch list once from the host git API on mount. Remote
5
+ * branches are display-only in v1 (a collapsed disclosure); switching and
6
+ * creating go through the GitValue actions so errors (e.g. uncommitted
7
+ * changes blocking a checkout) surface inline via lastResult.
8
+ */
9
+
10
+ import { useEffect, useRef, useState } from 'react';
11
+ import type { GitBranchInfo } from '@bendyline/docblocks/host';
12
+ import { Dialog } from '../components/Dialog.js';
13
+ import { useGitContext } from './GitContext.js';
14
+
15
+ export function GitBranchesDialog({
16
+ createFocus,
17
+ onClose,
18
+ }: {
19
+ createFocus?: boolean;
20
+ onClose: () => void;
21
+ }) {
22
+ const git = useGitContext();
23
+ const gitApi = git?.gitApi ?? null;
24
+ const repositoryId = git?.repositoryId ?? null;
25
+
26
+ const [branches, setBranches] = useState<GitBranchInfo[] | null>(null);
27
+ const [loadError, setLoadError] = useState<string | null>(null);
28
+ const [newName, setNewName] = useState('');
29
+ const createInputRef = useRef<HTMLInputElement>(null);
30
+
31
+ useEffect(() => {
32
+ if (!gitApi || !repositoryId) return;
33
+ let cancelled = false;
34
+ void gitApi.listBranches(repositoryId).then((result) => {
35
+ if (cancelled) return;
36
+ if (result.ok) setBranches(result.value);
37
+ else setLoadError(result.error.message || 'Could not list branches');
38
+ });
39
+ return () => {
40
+ cancelled = true;
41
+ };
42
+ }, [gitApi, repositoryId]);
43
+
44
+ if (!git) return null;
45
+
46
+ const locals = branches?.filter((b) => b.kind === 'local') ?? [];
47
+ const remotes = branches?.filter((b) => b.kind === 'remote') ?? [];
48
+ const canCreate = newName.trim().length > 0 && git.busy === null;
49
+
50
+ const handleSwitch = async (name: string) => {
51
+ const ok = await git.switchBranch(name);
52
+ if (ok) onClose();
53
+ };
54
+
55
+ const handleCreate = async () => {
56
+ if (!canCreate) return;
57
+ const ok = await git.createBranch(newName.trim());
58
+ if (ok) onClose();
59
+ };
60
+
61
+ return (
62
+ <Dialog
63
+ title="Branches"
64
+ onClose={onClose}
65
+ initialFocusRef={createFocus ? createInputRef : undefined}
66
+ >
67
+ {branches === null && loadError === null && <p className="db-git-hint">Loading branches…</p>}
68
+ {loadError !== null && (
69
+ <p className="db-git-form-error" role="alert">
70
+ {loadError}
71
+ </p>
72
+ )}
73
+ {branches !== null && (
74
+ <div className="db-git-branch-list">
75
+ {locals.map((branch) =>
76
+ branch.current ? (
77
+ <span
78
+ key={branch.name}
79
+ className="db-git-branch-row db-git-branch-row--current"
80
+ aria-current="true"
81
+ >
82
+ {branch.name}
83
+ </span>
84
+ ) : (
85
+ <button
86
+ key={branch.name}
87
+ type="button"
88
+ className="db-git-branch-row"
89
+ disabled={git.busy !== null}
90
+ onClick={() => void handleSwitch(branch.name)}
91
+ >
92
+ {branch.name}
93
+ </button>
94
+ ),
95
+ )}
96
+ </div>
97
+ )}
98
+ {git.lastResult?.tone === 'error' && (
99
+ <p className="db-git-form-error" role="alert">
100
+ {git.lastResult.message}
101
+ </p>
102
+ )}
103
+ <hr />
104
+ <div className="db-git-form-row">
105
+ <label className="db-git-form-label" htmlFor="db-git-new-branch-name">
106
+ New branch
107
+ </label>
108
+ <input
109
+ id="db-git-new-branch-name"
110
+ ref={createInputRef}
111
+ className="db-git-form-input"
112
+ type="text"
113
+ value={newName}
114
+ onChange={(e) => setNewName(e.target.value)}
115
+ onKeyDown={(e) => {
116
+ if (e.key === 'Enter') void handleCreate();
117
+ }}
118
+ placeholder="feature/my-branch"
119
+ spellCheck={false}
120
+ />
121
+ <button
122
+ type="button"
123
+ className="db-git-primary-btn"
124
+ disabled={!canCreate}
125
+ onClick={() => void handleCreate()}
126
+ >
127
+ Create
128
+ </button>
129
+ </div>
130
+ {remotes.length > 0 && (
131
+ <details>
132
+ <summary>Remote branches ({remotes.length})</summary>
133
+ <div className="db-git-branch-list">
134
+ {remotes.map((branch) => (
135
+ <span key={branch.name} className="db-git-branch-row">
136
+ {branch.name}
137
+ </span>
138
+ ))}
139
+ </div>
140
+ </details>
141
+ )}
142
+ </Dialog>
143
+ );
144
+ }