@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
@@ -0,0 +1,200 @@
1
+ /**
2
+ * GitCloneDialog — clone a remote repository into a new local workspace.
3
+ *
4
+ * The renderer only collects the URL; the main process shows the native
5
+ * destination picker, streams progress back, and registers the clone as a
6
+ * workspace. An auth failure swaps the body to the shared credential
7
+ * guidance copy (no credentials are ever collected here).
8
+ */
9
+
10
+ import { useEffect, useRef, useState } from 'react';
11
+ import type {
12
+ ElectronWorkspaceInfo,
13
+ GitCloneHandle,
14
+ GitCloneProgress,
15
+ } from '@bendyline/docblocks/host';
16
+ import { Dialog } from '../components/Dialog.js';
17
+ import { useGitContext } from './GitContext.js';
18
+ import { deriveCloneFolderName, isLikelyGitUrl } from './git-clone-utils.js';
19
+ import { GitCredentialGuidance } from './GitAuthGuidanceDialog.js';
20
+
21
+ export function GitCloneDialog({
22
+ onClose,
23
+ onCloned,
24
+ }: {
25
+ onClose: () => void;
26
+ onCloned: (info: ElectronWorkspaceInfo) => void;
27
+ }) {
28
+ const git = useGitContext();
29
+ const gitApi = git?.gitApi ?? null;
30
+
31
+ const [url, setUrl] = useState('');
32
+ const [cloning, setCloning] = useState(false);
33
+ const [progress, setProgress] = useState<GitCloneProgress | null>(null);
34
+ const [error, setError] = useState<string | null>(null);
35
+ /** Non-null → show embedded credential guidance instead of the form. */
36
+ const [authDetail, setAuthDetail] = useState<{ detail?: string } | null>(null);
37
+ const handleRef = useRef<GitCloneHandle | null>(null);
38
+ const urlInputRef = useRef<HTMLInputElement>(null);
39
+
40
+ // Closing the dialog mid-clone abandons it — kill git and clean up.
41
+ useEffect(
42
+ () => () => {
43
+ handleRef.current?.cancel();
44
+ },
45
+ [],
46
+ );
47
+
48
+ if (!gitApi || typeof gitApi.clone !== 'function') return null;
49
+
50
+ const canClone = isLikelyGitUrl(url) && !cloning;
51
+
52
+ const handleClone = async () => {
53
+ if (!canClone) return;
54
+ setCloning(true);
55
+ setError(null);
56
+ setProgress(null);
57
+ const handle = gitApi.clone(url.trim(), (p) => setProgress(p));
58
+ handleRef.current = handle;
59
+ const result = await handle.result;
60
+ handleRef.current = null;
61
+ setCloning(false);
62
+ setProgress(null);
63
+ if (!result.ok) {
64
+ switch (result.error.code) {
65
+ case 'invalid-url':
66
+ setError('That doesn’t look like a valid repository URL.');
67
+ break;
68
+ case 'destination-exists':
69
+ setError('A folder with that name already exists there. Pick a different location.');
70
+ break;
71
+ case 'auth-failed':
72
+ setAuthDetail({ detail: result.error.message });
73
+ break;
74
+ case 'cancelled':
75
+ break; // user cancelled — just return to the form
76
+ default:
77
+ setError(result.error.message);
78
+ }
79
+ return;
80
+ }
81
+ if (result.value === null) return; // user cancelled the destination picker
82
+ onCloned(result.value);
83
+ };
84
+
85
+ const progressPercent = progress?.percent ?? null;
86
+ const progressText =
87
+ progress !== null
88
+ ? `${progress.phase}…${progressPercent !== null ? ` ${Math.round(progressPercent)}%` : ''}`
89
+ : 'Starting clone…';
90
+
91
+ if (authDetail !== null) {
92
+ return (
93
+ <Dialog
94
+ title="Clone repository"
95
+ onClose={onClose}
96
+ footer={
97
+ <>
98
+ <button
99
+ type="button"
100
+ className="db-git-secondary-btn"
101
+ onClick={() => setAuthDetail(null)}
102
+ >
103
+ Back
104
+ </button>
105
+ <button type="button" className="db-git-primary-btn" onClick={onClose}>
106
+ Close
107
+ </button>
108
+ </>
109
+ }
110
+ >
111
+ <p className="db-settings-hint">Git could not authenticate with that remote.</p>
112
+ <GitCredentialGuidance detail={authDetail.detail} />
113
+ </Dialog>
114
+ );
115
+ }
116
+
117
+ return (
118
+ <Dialog
119
+ title="Clone repository"
120
+ onClose={onClose}
121
+ closeOnBackdrop={!cloning}
122
+ initialFocusRef={urlInputRef}
123
+ footer={
124
+ <>
125
+ <button
126
+ type="button"
127
+ className="db-git-secondary-btn"
128
+ onClick={onClose}
129
+ disabled={cloning}
130
+ >
131
+ Cancel
132
+ </button>
133
+ <button
134
+ type="button"
135
+ className="db-git-primary-btn"
136
+ disabled={!canClone}
137
+ onClick={() => void handleClone()}
138
+ >
139
+ Clone
140
+ </button>
141
+ </>
142
+ }
143
+ >
144
+ <div className="db-git-form-row">
145
+ <label className="db-git-form-label" htmlFor="db-git-clone-url">
146
+ Repository URL
147
+ </label>
148
+ <input
149
+ id="db-git-clone-url"
150
+ ref={urlInputRef}
151
+ className="db-git-form-input"
152
+ type="text"
153
+ value={url}
154
+ onChange={(e) => setUrl(e.target.value)}
155
+ onKeyDown={(e) => {
156
+ if (e.key === 'Enter') void handleClone();
157
+ }}
158
+ placeholder="https://github.com/owner/repo.git"
159
+ disabled={cloning}
160
+ spellCheck={false}
161
+ />
162
+ </div>
163
+ {isLikelyGitUrl(url) && (
164
+ <p className="db-git-hint">
165
+ Will clone into: <b>{deriveCloneFolderName(url)}</b> — you&rsquo;ll pick where next
166
+ </p>
167
+ )}
168
+ <p className="db-git-hint">
169
+ HTTPS and SSH URLs both work. You&rsquo;ll choose the destination folder in the next step.
170
+ </p>
171
+ {error !== null && (
172
+ <p className="db-git-form-error" role="alert">
173
+ {error}
174
+ </p>
175
+ )}
176
+ {cloning && (
177
+ <div className="db-git-progress">
178
+ {progressPercent !== null && (
179
+ <div
180
+ className="db-git-progress-bar"
181
+ role="progressbar"
182
+ aria-valuemin={0}
183
+ aria-valuemax={100}
184
+ aria-valuenow={Math.round(progressPercent)}
185
+ style={{ width: `${progressPercent}%` }}
186
+ />
187
+ )}
188
+ <p className="db-git-progress-text">{progressText}</p>
189
+ <button
190
+ type="button"
191
+ className="db-git-secondary-btn"
192
+ onClick={() => handleRef.current?.cancel()}
193
+ >
194
+ Cancel
195
+ </button>
196
+ </div>
197
+ )}
198
+ </Dialog>
199
+ );
200
+ }
@@ -0,0 +1,119 @@
1
+ /**
2
+ * GitCommitDialog — message + per-file selection for committing changes.
3
+ *
4
+ * All changed files are included by default; while a merge is being
5
+ * concluded, conflicted files are force-included (locked) because a commit
6
+ * that omits them would not resolve the merge. Selection logic lives in
7
+ * commit-selection.ts so it stays unit-testable.
8
+ */
9
+
10
+ import { useRef, useState } from 'react';
11
+ import { Dialog } from '../components/Dialog.js';
12
+ import { useGitContext } from './GitContext.js';
13
+ import { BADGE_LABELS, badgeKindFor } from './git-status.js';
14
+ import {
15
+ canCommit,
16
+ initSelection,
17
+ isMergeCommit,
18
+ selectedPaths,
19
+ setAllSelected,
20
+ toggleSelection,
21
+ type CommitSelection,
22
+ } from './commit-selection.js';
23
+
24
+ export function GitCommitDialog({ onClose }: { onClose: () => void }) {
25
+ const git = useGitContext();
26
+ const status = git?.status ?? null;
27
+ const merging = isMergeCommit(status);
28
+
29
+ const [message, setMessage] = useState('');
30
+ const [selection, setSelection] = useState<CommitSelection>(() =>
31
+ initSelection(status?.changes ?? [], merging),
32
+ );
33
+ const messageRef = useRef<HTMLTextAreaElement>(null);
34
+
35
+ if (!git || !status) return null;
36
+
37
+ const allOn = [...selection.included.values()].every(Boolean);
38
+ const commitDisabled = !canCommit(message, selection) || git.busy !== null;
39
+
40
+ const handleCommit = async () => {
41
+ const ok = await git.commit(message, selectedPaths(selection));
42
+ if (ok) onClose();
43
+ };
44
+
45
+ return (
46
+ <Dialog
47
+ title={merging ? 'Commit merge' : 'Commit changes'}
48
+ onClose={onClose}
49
+ size="wide"
50
+ closeOnBackdrop={false}
51
+ initialFocusRef={messageRef}
52
+ footer={
53
+ <>
54
+ <button type="button" className="db-git-secondary-btn" onClick={onClose}>
55
+ Cancel
56
+ </button>
57
+ <button
58
+ type="button"
59
+ className="db-git-primary-btn"
60
+ disabled={commitDisabled}
61
+ onClick={() => void handleCommit()}
62
+ >
63
+ Commit
64
+ </button>
65
+ </>
66
+ }
67
+ >
68
+ {merging && (
69
+ <p className="db-settings-hint">
70
+ You&rsquo;re completing a merge. Committing will conclude it.
71
+ </p>
72
+ )}
73
+ <textarea
74
+ ref={messageRef}
75
+ className="db-git-commit-message"
76
+ value={message}
77
+ onChange={(e) => setMessage(e.target.value)}
78
+ placeholder="Commit message"
79
+ aria-label="Commit message"
80
+ rows={3}
81
+ />
82
+ <div className="db-git-form-row">
83
+ <button
84
+ type="button"
85
+ className="db-git-secondary-btn"
86
+ onClick={() => setSelection((s) => setAllSelected(s, !allOn))}
87
+ >
88
+ {allOn ? 'Select none' : 'Select all'}
89
+ </button>
90
+ </div>
91
+ <ul className="db-git-file-list">
92
+ {status.changes.map((change) => {
93
+ const kind = badgeKindFor(change);
94
+ const locked = selection.locked.has(change.path);
95
+ const checked = locked || (selection.included.get(change.path) ?? true);
96
+ return (
97
+ <li key={change.path} className="db-git-file-row">
98
+ <label>
99
+ <input
100
+ type="checkbox"
101
+ checked={checked}
102
+ disabled={locked}
103
+ onChange={() => setSelection((s) => toggleSelection(s, change.path))}
104
+ />
105
+ <span>{change.path.replace(/^\/+/, '')}</span>
106
+ </label>
107
+ <span className="db-git-file-kind">{BADGE_LABELS[kind]}</span>
108
+ </li>
109
+ );
110
+ })}
111
+ </ul>
112
+ {git.lastResult?.tone === 'error' && (
113
+ <p className="db-git-form-error" role="alert">
114
+ {git.lastResult.message}
115
+ </p>
116
+ )}
117
+ </Dialog>
118
+ );
119
+ }
@@ -0,0 +1,62 @@
1
+ /**
2
+ * GitConflictsDialog — lists conflicted files after a pull hits a merge
3
+ * conflict. Each row opens the file in the editor so the user can resolve
4
+ * the markers; the primary action hands off to the commit dialog, which
5
+ * concludes the merge.
6
+ */
7
+
8
+ import { Dialog } from '../components/Dialog.js';
9
+ import { useGitContext } from './GitContext.js';
10
+
11
+ export function GitConflictsDialog({
12
+ paths,
13
+ onOpenFile,
14
+ onClose,
15
+ }: {
16
+ paths: string[];
17
+ onOpenFile: (path: string) => void;
18
+ onClose: () => void;
19
+ }) {
20
+ const git = useGitContext();
21
+
22
+ return (
23
+ <Dialog
24
+ title="Merge conflicts"
25
+ onClose={onClose}
26
+ footer={
27
+ <>
28
+ <button type="button" className="db-git-secondary-btn" onClick={onClose}>
29
+ Close
30
+ </button>
31
+ <button
32
+ type="button"
33
+ className="db-git-primary-btn"
34
+ onClick={() => git?.openDialog({ kind: 'commit' })}
35
+ >
36
+ Open commit dialog
37
+ </button>
38
+ </>
39
+ }
40
+ >
41
+ <p className="db-settings-hint">
42
+ These files contain conflict markers. Edit each one to resolve the conflicts, then commit to
43
+ complete the merge.
44
+ </p>
45
+ <ul className="db-git-file-list">
46
+ {paths.map((path) => (
47
+ <li key={path} className="db-git-file-row">
48
+ <button
49
+ type="button"
50
+ onClick={() => {
51
+ onOpenFile(path);
52
+ onClose();
53
+ }}
54
+ >
55
+ {path.replace(/^\/+/, '')}
56
+ </button>
57
+ </li>
58
+ ))}
59
+ </ul>
60
+ </Dialog>
61
+ );
62
+ }
@@ -0,0 +1,88 @@
1
+ /**
2
+ * GitContext — the seam between DocBlocksShell and every git UI surface.
3
+ *
4
+ * The shell calls useGit() once and provides the value here; the status
5
+ * bar, file-tree badges, toolbar control, and dialogs all consume it.
6
+ * Consumers must tolerate a null context (FileExplorer renders standalone
7
+ * in tests and on surfaces with no git).
8
+ */
9
+
10
+ import { createContext, useContext } from 'react';
11
+ import type {
12
+ DocBlocksHostGitAPI,
13
+ GitCapabilities,
14
+ GitRepoDetection,
15
+ GitStatus,
16
+ } from '@bendyline/docblocks/host';
17
+ import type { FileSystemProvider } from '@bendyline/docblocks/filesystem';
18
+ import type { GitBadgeKind } from './git-status.js';
19
+
20
+ export type GitDialogState =
21
+ | { kind: 'none' }
22
+ | { kind: 'commit' }
23
+ | { kind: 'branches'; createFocus?: boolean }
24
+ | { kind: 'history'; path?: string }
25
+ /** `revision` absent → working tree vs HEAD; present → that commit vs its parent. */
26
+ | { kind: 'diff'; path: string; revision?: { sha: string } }
27
+ | { kind: 'clone' }
28
+ | {
29
+ kind: 'auth-guidance';
30
+ operation: 'push' | 'pull' | 'fetch' | 'clone' | 'pr';
31
+ detail?: string;
32
+ }
33
+ | { kind: 'conflicts'; paths: string[] };
34
+
35
+ export type GitBusyKind = 'push' | 'pull' | 'fetch' | 'commit' | 'branch' | 'pr' | null;
36
+
37
+ export interface GitInlineResult {
38
+ tone: 'info' | 'error';
39
+ message: string;
40
+ }
41
+
42
+ export interface GitValue {
43
+ /** Tier 1 — Electron host with a working git. Gates the clone entry. */
44
+ available: boolean;
45
+ capabilities: GitCapabilities | null;
46
+ /** Tier 2 — the active workspace root is a repository. Gates everything else. */
47
+ repo: GitRepoDetection | null;
48
+ /** Main-owned repository capability; never a native path. */
49
+ repositoryId: string | null;
50
+ /** Parsed web location of the first remote, when one exists and parses. */
51
+ remoteWeb: { host: string; owner: string; repo: string; webUrl: string } | null;
52
+ /** Raw host API for read-heavy dialogs (log, branches, file-at-revision). */
53
+ gitApi: DocBlocksHostGitAPI | null;
54
+ provider: FileSystemProvider | null;
55
+ theme: 'light' | 'dark';
56
+
57
+ status: GitStatus | null;
58
+ badges: ReadonlyMap<string, GitBadgeKind>;
59
+ busy: GitBusyKind;
60
+ /** Latest operation outcome, surfaced in an aria-live region. */
61
+ lastResult: GitInlineResult | null;
62
+
63
+ refresh: () => void;
64
+ /** Debounced refresh — used after autosave writes. */
65
+ scheduleRefresh: () => void;
66
+
67
+ /** Actions route their typed errors to dialogs/lastResult internally. */
68
+ commit: (message: string, paths: string[]) => Promise<boolean>;
69
+ push: () => Promise<void>;
70
+ pull: () => Promise<void>;
71
+ fetchRemote: () => Promise<void>;
72
+ createBranch: (name: string) => Promise<boolean>;
73
+ switchBranch: (name: string) => Promise<boolean>;
74
+ /** Open the repo (or a file within it) on the remote's web host. */
75
+ openOnRemote: (filePath?: string) => void;
76
+ createPullRequest: () => Promise<void>;
77
+
78
+ dialog: GitDialogState;
79
+ openDialog: (dialog: GitDialogState) => void;
80
+ closeDialog: () => void;
81
+ }
82
+
83
+ export const GitContext = createContext<GitValue | null>(null);
84
+
85
+ /** Null when no provider is mounted — callers must degrade gracefully. */
86
+ export function useGitContext(): GitValue | null {
87
+ return useContext(GitContext);
88
+ }
@@ -0,0 +1,147 @@
1
+ /**
2
+ * GitDiffDialog — full-size modal showing a file diff.
3
+ *
4
+ * Without a revision: working tree vs HEAD (HEAD side via the git host API,
5
+ * working side via the workspace FileSystemProvider). With a revision: that
6
+ * commit vs its parent, both sides via readFileAtRevision. Absent sides
7
+ * render as empty text with an "(absent)" label; binary files short-circuit
8
+ * to a hint since there is no text diff to show.
9
+ */
10
+
11
+ import { useEffect, useState } from 'react';
12
+ import { getFileSystemProviderV2, parseWorkspacePath } from '@bendyline/docblocks/filesystem';
13
+ import { Dialog } from '../components/Dialog.js';
14
+ import { useGitContext } from './GitContext.js';
15
+ import { GitDiffView } from './GitDiffView.js';
16
+
17
+ export interface GitDiffDialogProps {
18
+ /** Slash-prefixed workspace-relative path. */
19
+ path: string;
20
+ /** Absent → working tree vs HEAD; present → that commit vs its parent. */
21
+ revision?: { sha: string };
22
+ onClose: () => void;
23
+ }
24
+
25
+ type DiffLoadState =
26
+ | { phase: 'loading' }
27
+ | { phase: 'error'; message: string }
28
+ | { phase: 'binary' }
29
+ | {
30
+ phase: 'ready';
31
+ original: string;
32
+ modified: string;
33
+ labels: { original: string; modified: string };
34
+ };
35
+
36
+ export function GitDiffDialog({ path, revision, onClose }: GitDiffDialogProps) {
37
+ const git = useGitContext();
38
+ const gitApi = git?.gitApi ?? null;
39
+ const provider = git?.provider ?? null;
40
+ const repositoryId = git?.repositoryId ?? null;
41
+ const sha = revision?.sha ?? null;
42
+
43
+ const [state, setState] = useState<DiffLoadState>({ phase: 'loading' });
44
+
45
+ useEffect(() => {
46
+ if (!gitApi || !repositoryId) {
47
+ setState({ phase: 'error', message: 'Git is not available for this workspace.' });
48
+ return;
49
+ }
50
+ let cancelled = false;
51
+ setState({ phase: 'loading' });
52
+
53
+ const load = async (): Promise<DiffLoadState> => {
54
+ if (sha) {
55
+ const short = sha.slice(0, 7);
56
+ const [parent, at] = await Promise.all([
57
+ gitApi.readFileAtRevision(repositoryId, path, { kind: 'parent-of', sha }),
58
+ gitApi.readFileAtRevision(repositoryId, path, { kind: 'commit', sha }),
59
+ ]);
60
+ if (!parent.ok) return { phase: 'error', message: parent.error.message };
61
+ if (!at.ok) return { phase: 'error', message: at.error.message };
62
+ if (
63
+ (parent.value.content === null && parent.value.binary) ||
64
+ (at.value.content === null && at.value.binary)
65
+ ) {
66
+ return { phase: 'binary' };
67
+ }
68
+ return {
69
+ phase: 'ready',
70
+ original: parent.value.content ?? '',
71
+ modified: at.value.content ?? '',
72
+ labels: {
73
+ original: parent.value.content === null ? `${short}^ (absent)` : `${short}^`,
74
+ modified: at.value.content === null ? `${short} (absent)` : short,
75
+ },
76
+ };
77
+ }
78
+
79
+ if (!provider) {
80
+ return { phase: 'error', message: 'Workspace files are not available.' };
81
+ }
82
+ const providerV2 = getFileSystemProviderV2(provider);
83
+ const [head, working] = await Promise.all([
84
+ gitApi.readFileAtRevision(repositoryId, path, { kind: 'head' }),
85
+ providerV2
86
+ ? providerV2
87
+ .readFile(parseWorkspacePath(path))
88
+ .then((file) => (file ? new TextDecoder().decode(file.data) : null))
89
+ : provider.readFile(path),
90
+ ]);
91
+ if (!head.ok) return { phase: 'error', message: head.error.message };
92
+ if (head.value.content === null && head.value.binary) return { phase: 'binary' };
93
+ return {
94
+ phase: 'ready',
95
+ original: head.value.content ?? '',
96
+ modified: working ?? '',
97
+ labels: {
98
+ original: head.value.content === null ? 'HEAD (absent)' : 'HEAD',
99
+ modified: working === null ? 'Working tree (absent)' : 'Working tree',
100
+ },
101
+ };
102
+ };
103
+
104
+ load()
105
+ .catch(
106
+ (err: unknown): DiffLoadState => ({
107
+ phase: 'error',
108
+ message: err instanceof Error ? err.message : 'Failed to load the diff',
109
+ }),
110
+ )
111
+ .then((next) => {
112
+ if (!cancelled) setState(next);
113
+ });
114
+ return () => {
115
+ cancelled = true;
116
+ };
117
+ }, [gitApi, provider, repositoryId, path, sha]);
118
+
119
+ if (!git) return null;
120
+
121
+ const fullPath = path.replace(/^\/+/, '');
122
+ const baseName = fullPath.split('/').pop() || fullPath;
123
+
124
+ return (
125
+ <Dialog title={baseName} size="full" onClose={onClose}>
126
+ <p className="db-settings-hint">{fullPath}</p>
127
+ {state.phase === 'loading' && <p className="db-git-hint">Loading changes…</p>}
128
+ {state.phase === 'error' && (
129
+ <p className="db-git-form-error" role="alert">
130
+ {state.message}
131
+ </p>
132
+ )}
133
+ {state.phase === 'binary' && (
134
+ <p className="db-git-hint">Binary file — no text diff available.</p>
135
+ )}
136
+ {state.phase === 'ready' && (
137
+ <GitDiffView
138
+ original={state.original}
139
+ modified={state.modified}
140
+ fileName={baseName}
141
+ theme={git.theme}
142
+ labels={state.labels}
143
+ />
144
+ )}
145
+ </Dialog>
146
+ );
147
+ }