@bendyline/docblocks-react 2.0.0 → 2.1.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 (300) hide show
  1. package/README.md +6 -0
  2. package/dist/{ExportDialog-MEUISWZX.js → ExportDialog-7LDIXJZ3.js} +3 -2
  3. package/dist/{ExportToolbarControls-D2YDWOXW.js → ExportToolbarControls-GHAOJMH6.js} +147 -12
  4. package/dist/ExportToolbarControls-GHAOJMH6.js.map +1 -0
  5. package/dist/{GitToolbarControl-LMIR4R7I.js → GitToolbarControl-GNMRVKWQ.js} +62 -43
  6. package/dist/GitToolbarControl-GNMRVKWQ.js.map +1 -0
  7. package/dist/{GitUI-KGF5MSNV.js → GitUI-VHOBQY43.js} +225 -140
  8. package/dist/GitUI-VHOBQY43.js.map +1 -0
  9. package/dist/ShareDialog-63ENBEDZ.js +213 -0
  10. package/dist/ShareDialog-63ENBEDZ.js.map +1 -0
  11. package/dist/chunk-57ZJJOC5.js +89 -0
  12. package/dist/chunk-57ZJJOC5.js.map +1 -0
  13. package/dist/chunk-FEWQUQML.js +256 -0
  14. package/dist/chunk-FEWQUQML.js.map +1 -0
  15. package/dist/chunk-FMGTHSTX.js +110 -0
  16. package/dist/chunk-FMGTHSTX.js.map +1 -0
  17. package/dist/chunk-UNYZQXWR.js +324 -0
  18. package/dist/chunk-UNYZQXWR.js.map +1 -0
  19. package/dist/editor.d.ts +5 -4
  20. package/dist/editor.js +8 -69
  21. package/dist/editor.js.map +1 -1
  22. package/dist/export/index.d.ts +7 -1
  23. package/dist/export/index.js +2 -1
  24. package/dist/index.d.ts +346 -21
  25. package/dist/index.js +6169 -20
  26. package/dist/index.js.map +1 -1
  27. package/dist/settings/index.d.ts +9 -1
  28. package/dist/settings/index.js +6 -4
  29. package/package.json +18 -13
  30. package/src/styles/docblocks.css +537 -26
  31. package/dist/AppMenu/AppMenu.d.ts +0 -53
  32. package/dist/AppMenu/AppMenu.d.ts.map +0 -1
  33. package/dist/AppMenu/AppMenu.js +0 -67
  34. package/dist/AppMenu/AppMenu.js.map +0 -1
  35. package/dist/AppMenu/index.d.ts +0 -3
  36. package/dist/AppMenu/index.d.ts.map +0 -1
  37. package/dist/AppMenu/index.js +0 -2
  38. package/dist/AppMenu/index.js.map +0 -1
  39. package/dist/DocBlocksShell/DocBlocksShell.d.ts +0 -49
  40. package/dist/DocBlocksShell/DocBlocksShell.d.ts.map +0 -1
  41. package/dist/DocBlocksShell/DocBlocksShell.js +0 -2730
  42. package/dist/DocBlocksShell/DocBlocksShell.js.map +0 -1
  43. package/dist/DocBlocksShell/LazyEditorShell.d.ts +0 -3
  44. package/dist/DocBlocksShell/LazyEditorShell.d.ts.map +0 -1
  45. package/dist/DocBlocksShell/LazyEditorShell.js +0 -3
  46. package/dist/DocBlocksShell/LazyEditorShell.js.map +0 -1
  47. package/dist/DocBlocksShell/UpdateAvailableNotice.d.ts +0 -13
  48. package/dist/DocBlocksShell/UpdateAvailableNotice.d.ts.map +0 -1
  49. package/dist/DocBlocksShell/UpdateAvailableNotice.js +0 -16
  50. package/dist/DocBlocksShell/UpdateAvailableNotice.js.map +0 -1
  51. package/dist/DocBlocksShell/document-title.d.ts +0 -3
  52. package/dist/DocBlocksShell/document-title.d.ts.map +0 -1
  53. package/dist/DocBlocksShell/document-title.js +0 -17
  54. package/dist/DocBlocksShell/document-title.js.map +0 -1
  55. package/dist/DocBlocksShell/index.d.ts +0 -3
  56. package/dist/DocBlocksShell/index.d.ts.map +0 -1
  57. package/dist/DocBlocksShell/index.js +0 -2
  58. package/dist/DocBlocksShell/index.js.map +0 -1
  59. package/dist/DocBlocksShell/workspace-authority-barrier.d.ts +0 -14
  60. package/dist/DocBlocksShell/workspace-authority-barrier.d.ts.map +0 -1
  61. package/dist/DocBlocksShell/workspace-authority-barrier.js +0 -23
  62. package/dist/DocBlocksShell/workspace-authority-barrier.js.map +0 -1
  63. package/dist/Export/DeferredExportToolbarControls.d.ts +0 -5
  64. package/dist/Export/DeferredExportToolbarControls.d.ts.map +0 -1
  65. package/dist/Export/DeferredExportToolbarControls.js +0 -10
  66. package/dist/Export/DeferredExportToolbarControls.js.map +0 -1
  67. package/dist/Export/ExportDialog.d.ts +0 -25
  68. package/dist/Export/ExportDialog.d.ts.map +0 -1
  69. package/dist/Export/ExportDialog.js +0 -160
  70. package/dist/Export/ExportDialog.js.map +0 -1
  71. package/dist/Export/ExportToolbarControls.d.ts +0 -37
  72. package/dist/Export/ExportToolbarControls.d.ts.map +0 -1
  73. package/dist/Export/ExportToolbarControls.js +0 -285
  74. package/dist/Export/ExportToolbarControls.js.map +0 -1
  75. package/dist/Export/export-destination.d.ts +0 -3
  76. package/dist/Export/export-destination.d.ts.map +0 -1
  77. package/dist/Export/export-destination.js +0 -32
  78. package/dist/Export/export-destination.js.map +0 -1
  79. package/dist/Export/export-options.d.ts +0 -53
  80. package/dist/Export/export-options.d.ts.map +0 -1
  81. package/dist/Export/export-options.js +0 -49
  82. package/dist/Export/export-options.js.map +0 -1
  83. package/dist/Export/index.d.ts +0 -10
  84. package/dist/Export/index.d.ts.map +0 -1
  85. package/dist/Export/index.js +0 -6
  86. package/dist/Export/index.js.map +0 -1
  87. package/dist/Export/public-api.d.ts +0 -12
  88. package/dist/Export/public-api.d.ts.map +0 -1
  89. package/dist/Export/public-api.js +0 -9
  90. package/dist/Export/public-api.js.map +0 -1
  91. package/dist/Export/run-export.d.ts +0 -10
  92. package/dist/Export/run-export.d.ts.map +0 -1
  93. package/dist/Export/run-export.js +0 -313
  94. package/dist/Export/run-export.js.map +0 -1
  95. package/dist/Export/transform-summaries.d.ts +0 -7
  96. package/dist/Export/transform-summaries.d.ts.map +0 -1
  97. package/dist/Export/transform-summaries.js +0 -6
  98. package/dist/Export/transform-summaries.js.map +0 -1
  99. package/dist/ExportToolbarControls-D2YDWOXW.js.map +0 -1
  100. package/dist/FileExplorer/FileExplorer.d.ts +0 -43
  101. package/dist/FileExplorer/FileExplorer.d.ts.map +0 -1
  102. package/dist/FileExplorer/FileExplorer.js +0 -315
  103. package/dist/FileExplorer/FileExplorer.js.map +0 -1
  104. package/dist/FileExplorer/FileTreeNode.d.ts +0 -39
  105. package/dist/FileExplorer/FileTreeNode.d.ts.map +0 -1
  106. package/dist/FileExplorer/FileTreeNode.js +0 -131
  107. package/dist/FileExplorer/FileTreeNode.js.map +0 -1
  108. package/dist/FileExplorer/index.d.ts +0 -6
  109. package/dist/FileExplorer/index.d.ts.map +0 -1
  110. package/dist/FileExplorer/index.js +0 -4
  111. package/dist/FileExplorer/index.js.map +0 -1
  112. package/dist/FileExplorer/useFileTree.d.ts +0 -37
  113. package/dist/FileExplorer/useFileTree.d.ts.map +0 -1
  114. package/dist/FileExplorer/useFileTree.js +0 -284
  115. package/dist/FileExplorer/useFileTree.js.map +0 -1
  116. package/dist/Git/GitAuthGuidanceDialog.d.ts +0 -18
  117. package/dist/Git/GitAuthGuidanceDialog.d.ts.map +0 -1
  118. package/dist/Git/GitAuthGuidanceDialog.js +0 -27
  119. package/dist/Git/GitAuthGuidanceDialog.js.map +0 -1
  120. package/dist/Git/GitBranchesDialog.d.ts +0 -13
  121. package/dist/Git/GitBranchesDialog.d.ts.map +0 -1
  122. package/dist/Git/GitBranchesDialog.js +0 -59
  123. package/dist/Git/GitBranchesDialog.js.map +0 -1
  124. package/dist/Git/GitCloneDialog.d.ts +0 -14
  125. package/dist/Git/GitCloneDialog.d.ts.map +0 -1
  126. package/dist/Git/GitCloneDialog.js +0 -79
  127. package/dist/Git/GitCloneDialog.js.map +0 -1
  128. package/dist/Git/GitCommitDialog.d.ts +0 -12
  129. package/dist/Git/GitCommitDialog.d.ts.map +0 -1
  130. package/dist/Git/GitCommitDialog.js +0 -38
  131. package/dist/Git/GitCommitDialog.js.map +0 -1
  132. package/dist/Git/GitConflictsDialog.d.ts +0 -12
  133. package/dist/Git/GitConflictsDialog.d.ts.map +0 -1
  134. package/dist/Git/GitConflictsDialog.js +0 -17
  135. package/dist/Git/GitConflictsDialog.js.map +0 -1
  136. package/dist/Git/GitContext.d.ts +0 -89
  137. package/dist/Git/GitContext.d.ts.map +0 -1
  138. package/dist/Git/GitContext.js +0 -15
  139. package/dist/Git/GitContext.js.map +0 -1
  140. package/dist/Git/GitDiffDialog.d.ts +0 -20
  141. package/dist/Git/GitDiffDialog.d.ts.map +0 -1
  142. package/dist/Git/GitDiffDialog.js +0 -100
  143. package/dist/Git/GitDiffDialog.js.map +0 -1
  144. package/dist/Git/GitDiffView.d.ts +0 -23
  145. package/dist/Git/GitDiffView.d.ts.map +0 -1
  146. package/dist/Git/GitDiffView.js +0 -77
  147. package/dist/Git/GitDiffView.js.map +0 -1
  148. package/dist/Git/GitHistoryDialog.d.ts +0 -15
  149. package/dist/Git/GitHistoryDialog.d.ts.map +0 -1
  150. package/dist/Git/GitHistoryDialog.js +0 -107
  151. package/dist/Git/GitHistoryDialog.js.map +0 -1
  152. package/dist/Git/GitStatusBar.d.ts +0 -8
  153. package/dist/Git/GitStatusBar.d.ts.map +0 -1
  154. package/dist/Git/GitStatusBar.js +0 -49
  155. package/dist/Git/GitStatusBar.js.map +0 -1
  156. package/dist/Git/GitToolbarControl.d.ts +0 -15
  157. package/dist/Git/GitToolbarControl.d.ts.map +0 -1
  158. package/dist/Git/GitToolbarControl.js +0 -51
  159. package/dist/Git/GitToolbarControl.js.map +0 -1
  160. package/dist/Git/GitUI.d.ts +0 -14
  161. package/dist/Git/GitUI.d.ts.map +0 -1
  162. package/dist/Git/GitUI.js +0 -21
  163. package/dist/Git/GitUI.js.map +0 -1
  164. package/dist/Git/commit-selection.d.ts +0 -22
  165. package/dist/Git/commit-selection.d.ts.map +0 -1
  166. package/dist/Git/commit-selection.js +0 -47
  167. package/dist/Git/commit-selection.js.map +0 -1
  168. package/dist/Git/git-clone-utils.d.ts +0 -8
  169. package/dist/Git/git-clone-utils.d.ts.map +0 -1
  170. package/dist/Git/git-clone-utils.js +0 -26
  171. package/dist/Git/git-clone-utils.js.map +0 -1
  172. package/dist/Git/git-status.d.ts +0 -24
  173. package/dist/Git/git-status.d.ts.map +0 -1
  174. package/dist/Git/git-status.js +0 -105
  175. package/dist/Git/git-status.js.map +0 -1
  176. package/dist/Git/useGit.d.ts +0 -15
  177. package/dist/Git/useGit.d.ts.map +0 -1
  178. package/dist/Git/useGit.js +0 -206
  179. package/dist/Git/useGit.js.map +0 -1
  180. package/dist/Git/useGitStatus.d.ts +0 -17
  181. package/dist/Git/useGitStatus.d.ts.map +0 -1
  182. package/dist/Git/useGitStatus.js +0 -63
  183. package/dist/Git/useGitStatus.js.map +0 -1
  184. package/dist/GitToolbarControl-LMIR4R7I.js.map +0 -1
  185. package/dist/GitUI-KGF5MSNV.js.map +0 -1
  186. package/dist/Settings/Settings.d.ts +0 -23
  187. package/dist/Settings/Settings.d.ts.map +0 -1
  188. package/dist/Settings/Settings.js +0 -22
  189. package/dist/Settings/Settings.js.map +0 -1
  190. package/dist/Settings/public-api.d.ts +0 -3
  191. package/dist/Settings/public-api.d.ts.map +0 -1
  192. package/dist/Settings/public-api.js +0 -2
  193. package/dist/Settings/public-api.js.map +0 -1
  194. package/dist/WorkspacePicker/WorkspacePicker.d.ts +0 -22
  195. package/dist/WorkspacePicker/WorkspacePicker.d.ts.map +0 -1
  196. package/dist/WorkspacePicker/WorkspacePicker.js +0 -74
  197. package/dist/WorkspacePicker/WorkspacePicker.js.map +0 -1
  198. package/dist/WorkspacePicker/WorkspaceSettingsButton.d.ts +0 -11
  199. package/dist/WorkspacePicker/WorkspaceSettingsButton.d.ts.map +0 -1
  200. package/dist/WorkspacePicker/WorkspaceSettingsButton.js +0 -27
  201. package/dist/WorkspacePicker/WorkspaceSettingsButton.js.map +0 -1
  202. package/dist/WorkspacePicker/WorkspaceSettingsDialog.d.ts +0 -20
  203. package/dist/WorkspacePicker/WorkspaceSettingsDialog.d.ts.map +0 -1
  204. package/dist/WorkspacePicker/WorkspaceSettingsDialog.js +0 -36
  205. package/dist/WorkspacePicker/WorkspaceSettingsDialog.js.map +0 -1
  206. package/dist/WorkspacePicker/index.d.ts +0 -3
  207. package/dist/WorkspacePicker/index.d.ts.map +0 -1
  208. package/dist/WorkspacePicker/index.js +0 -2
  209. package/dist/WorkspacePicker/index.js.map +0 -1
  210. package/dist/chunk-3SAXDU2U.js +0 -157
  211. package/dist/chunk-3SAXDU2U.js.map +0 -1
  212. package/dist/chunk-AOVTHZU7.js +0 -321
  213. package/dist/chunk-AOVTHZU7.js.map +0 -1
  214. package/dist/chunk-GSNZSCIK.js +0 -55
  215. package/dist/chunk-GSNZSCIK.js.map +0 -1
  216. package/dist/components/Dialog.d.ts +0 -22
  217. package/dist/components/Dialog.d.ts.map +0 -1
  218. package/dist/components/Dialog.js +0 -33
  219. package/dist/components/Dialog.js.map +0 -1
  220. package/dist/editor.d.ts.map +0 -1
  221. package/dist/hooks/index.d.ts +0 -3
  222. package/dist/hooks/index.d.ts.map +0 -1
  223. package/dist/hooks/index.js +0 -2
  224. package/dist/hooks/index.js.map +0 -1
  225. package/dist/hooks/useDocumentSession.d.ts +0 -16
  226. package/dist/hooks/useDocumentSession.d.ts.map +0 -1
  227. package/dist/hooks/useDocumentSession.js +0 -18
  228. package/dist/hooks/useDocumentSession.js.map +0 -1
  229. package/dist/icons.d.ts +0 -12
  230. package/dist/icons.d.ts.map +0 -1
  231. package/dist/icons.js +0 -28
  232. package/dist/icons.js.map +0 -1
  233. package/dist/index.d.ts.map +0 -1
  234. package/dist/preferences/theme.d.ts +0 -18
  235. package/dist/preferences/theme.d.ts.map +0 -1
  236. package/dist/preferences/theme.js +0 -63
  237. package/dist/preferences/theme.js.map +0 -1
  238. package/dist/preferences/versioning.d.ts +0 -27
  239. package/dist/preferences/versioning.d.ts.map +0 -1
  240. package/dist/preferences/versioning.js +0 -62
  241. package/dist/preferences/versioning.js.map +0 -1
  242. package/dist/provider-lease.d.ts +0 -10
  243. package/dist/provider-lease.d.ts.map +0 -1
  244. package/dist/provider-lease.js +0 -42
  245. package/dist/provider-lease.js.map +0 -1
  246. package/src/AppMenu/AppMenu.tsx +0 -334
  247. package/src/AppMenu/index.ts +0 -2
  248. package/src/DocBlocksShell/DocBlocksShell.tsx +0 -3398
  249. package/src/DocBlocksShell/LazyEditorShell.ts +0 -2
  250. package/src/DocBlocksShell/UpdateAvailableNotice.tsx +0 -60
  251. package/src/DocBlocksShell/document-title.ts +0 -19
  252. package/src/DocBlocksShell/index.ts +0 -2
  253. package/src/DocBlocksShell/workspace-authority-barrier.ts +0 -26
  254. package/src/Export/DeferredExportToolbarControls.tsx +0 -23
  255. package/src/Export/ExportDialog.tsx +0 -437
  256. package/src/Export/ExportToolbarControls.tsx +0 -511
  257. package/src/Export/export-destination.ts +0 -30
  258. package/src/Export/export-options.ts +0 -99
  259. package/src/Export/index.ts +0 -19
  260. package/src/Export/public-api.ts +0 -17
  261. package/src/Export/run-export.ts +0 -374
  262. package/src/Export/transform-summaries.ts +0 -14
  263. package/src/FileExplorer/FileExplorer.tsx +0 -528
  264. package/src/FileExplorer/FileTreeNode.tsx +0 -306
  265. package/src/FileExplorer/index.ts +0 -5
  266. package/src/FileExplorer/useFileTree.ts +0 -352
  267. package/src/Git/GitAuthGuidanceDialog.tsx +0 -84
  268. package/src/Git/GitBranchesDialog.tsx +0 -144
  269. package/src/Git/GitCloneDialog.tsx +0 -200
  270. package/src/Git/GitCommitDialog.tsx +0 -119
  271. package/src/Git/GitConflictsDialog.tsx +0 -62
  272. package/src/Git/GitContext.tsx +0 -88
  273. package/src/Git/GitDiffDialog.tsx +0 -147
  274. package/src/Git/GitDiffView.tsx +0 -105
  275. package/src/Git/GitHistoryDialog.tsx +0 -195
  276. package/src/Git/GitStatusBar.tsx +0 -157
  277. package/src/Git/GitToolbarControl.tsx +0 -123
  278. package/src/Git/GitUI.tsx +0 -68
  279. package/src/Git/commit-selection.ts +0 -63
  280. package/src/Git/git-clone-utils.ts +0 -23
  281. package/src/Git/git-status.ts +0 -113
  282. package/src/Git/useGit.ts +0 -279
  283. package/src/Git/useGitStatus.ts +0 -78
  284. package/src/Settings/Settings.tsx +0 -118
  285. package/src/Settings/public-api.ts +0 -8
  286. package/src/WorkspacePicker/WorkspacePicker.tsx +0 -191
  287. package/src/WorkspacePicker/WorkspaceSettingsButton.tsx +0 -88
  288. package/src/WorkspacePicker/WorkspaceSettingsDialog.tsx +0 -121
  289. package/src/WorkspacePicker/index.ts +0 -2
  290. package/src/components/Dialog.tsx +0 -81
  291. package/src/editor.ts +0 -72
  292. package/src/hooks/index.ts +0 -2
  293. package/src/hooks/useDocumentSession.ts +0 -37
  294. package/src/icons.tsx +0 -48
  295. package/src/index.ts +0 -61
  296. package/src/preferences/theme.ts +0 -67
  297. package/src/preferences/versioning.ts +0 -69
  298. package/src/provider-lease.ts +0 -52
  299. package/src/types/pwa-dom.d.ts +0 -31
  300. /package/dist/{ExportDialog-MEUISWZX.js.map → ExportDialog-7LDIXJZ3.js.map} +0 -0
@@ -1,306 +0,0 @@
1
- /**
2
- * FileTreeNode — recursive tree node for the file explorer.
3
- */
4
-
5
- import { useCallback, useState, useRef, useEffect, useLayoutEffect } from 'react';
6
- import { createPortal } from 'react-dom';
7
- import type { FileSystemEntry } from '@bendyline/docblocks/filesystem';
8
- import { MoreIcon } from '../icons.js';
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
-
24
- export interface FileTreeNodeProps {
25
- entry: FileSystemEntry;
26
- depth: number;
27
- expanded: boolean;
28
- selected: boolean;
29
- badge?: FileTreeNodeBadge;
30
- gitActions?: FileTreeNodeGitActions;
31
- children?: FileSystemEntry[];
32
- onToggle: (path: string) => void;
33
- onSelect: (path: string) => 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;
43
- renderChildren?: (dirPath: string) => React.ReactNode;
44
- }
45
-
46
- export function FileTreeNode({
47
- entry,
48
- depth,
49
- expanded,
50
- selected,
51
- badge,
52
- gitActions,
53
- onToggle,
54
- onSelect,
55
- onDelete,
56
- onRename,
57
- draggable = false,
58
- dragging = false,
59
- dropTarget = false,
60
- onDragStart,
61
- onDragEnd,
62
- onDragOverEntry,
63
- onDropEntry,
64
- renderChildren,
65
- }: FileTreeNodeProps) {
66
- const [renaming, setRenaming] = useState(false);
67
- const [renameValue, setRenameValue] = useState(entry.name);
68
- const [showContext, setShowContext] = useState(false);
69
- const [contextPos, setContextPos] = useState({ x: 0, y: 0 });
70
- const inputRef = useRef<HTMLInputElement>(null);
71
- const contextRef = useRef<HTMLDivElement>(null);
72
- const nodeRef = useRef<HTMLDivElement>(null);
73
-
74
- const isDir = entry.kind === 'directory';
75
- const icon = isDir ? (expanded ? '\u25BE' : '\u25B8') : '\u00A0\u00A0';
76
-
77
- const handleClick = useCallback(() => {
78
- if (isDir) {
79
- onToggle(entry.path);
80
- }
81
- onSelect(entry.path);
82
- }, [isDir, entry.path, onToggle, onSelect]);
83
-
84
- const handleContextMenu = useCallback((e: React.MouseEvent) => {
85
- e.preventDefault();
86
- e.stopPropagation();
87
- setContextPos({ x: e.clientX, y: e.clientY });
88
- setShowContext(true);
89
- }, []);
90
-
91
- const handleMoreClick = useCallback((e: React.MouseEvent) => {
92
- e.preventDefault();
93
- e.stopPropagation();
94
- const rect = (e.currentTarget as HTMLElement).getBoundingClientRect();
95
- setContextPos({ x: rect.right, y: rect.bottom });
96
- setShowContext(true);
97
- }, []);
98
-
99
- const handleRenameStart = useCallback(() => {
100
- setRenameValue(entry.name);
101
- setRenaming(true);
102
- setShowContext(false);
103
- }, [entry.name]);
104
-
105
- const handleRenameSubmit = useCallback(async () => {
106
- if (renameValue && renameValue !== entry.name) {
107
- const parentPath = entry.path.includes('/')
108
- ? entry.path.slice(0, entry.path.lastIndexOf('/'))
109
- : '';
110
- const newPath = parentPath ? `${parentPath}/${renameValue}` : renameValue;
111
- await onRename(entry.path, newPath, entry.kind);
112
- }
113
- setRenaming(false);
114
- }, [renameValue, entry.name, entry.path, entry.kind, onRename]);
115
-
116
- const handleDeleteClick = useCallback(async () => {
117
- setShowContext(false);
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]);
124
-
125
- // Close context menu on outside click or scroll
126
- useEffect(() => {
127
- if (!showContext) return;
128
- function handleClose(e: Event) {
129
- if (contextRef.current && !contextRef.current.contains(e.target as Node)) {
130
- setShowContext(false);
131
- }
132
- }
133
- // Use click (not mousedown) so menu button clicks register first
134
- document.addEventListener('click', handleClose, true);
135
- document.addEventListener('contextmenu', handleClose, true);
136
- document.addEventListener('scroll', () => setShowContext(false), { capture: true, once: true });
137
- return () => {
138
- document.removeEventListener('click', handleClose, true);
139
- document.removeEventListener('contextmenu', handleClose, true);
140
- };
141
- }, [showContext]);
142
-
143
- // Focus input when renaming
144
- useEffect(() => {
145
- if (renaming && inputRef.current) {
146
- inputRef.current.focus();
147
- inputRef.current.select();
148
- }
149
- }, [renaming]);
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
-
157
- // Clamp menu inside viewport after it renders
158
- useLayoutEffect(() => {
159
- if (!showContext || !contextRef.current) return;
160
- const menu = contextRef.current;
161
- const rect = menu.getBoundingClientRect();
162
- const margin = 4;
163
- let left = contextPos.x;
164
- let top = contextPos.y;
165
- if (rect.right > window.innerWidth - margin) {
166
- left = Math.max(margin, window.innerWidth - rect.width - margin);
167
- }
168
- if (rect.bottom > window.innerHeight - margin) {
169
- top = Math.max(margin, window.innerHeight - rect.height - margin);
170
- }
171
- if (left !== contextPos.x || top !== contextPos.y) {
172
- menu.style.left = `${left}px`;
173
- menu.style.top = `${top}px`;
174
- }
175
- }, [showContext, contextPos]);
176
-
177
- return (
178
- <div className="db-tree-node" ref={nodeRef}>
179
- <div
180
- className={`db-tree-row ${selected ? 'db-tree-row--selected' : ''} ${dragging ? 'db-tree-row--dragging' : ''} ${dropTarget ? 'db-tree-row--drop-target' : ''}`}
181
- style={{ paddingLeft: depth * 16 + 4 }}
182
- onClick={handleClick}
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)}
189
- role="treeitem"
190
- aria-expanded={isDir ? expanded : undefined}
191
- aria-selected={selected}
192
- aria-label={badge ? `${entry.name}, ${badge.label}` : undefined}
193
- tabIndex={0}
194
- onKeyDown={(e) => {
195
- if (e.key === 'Enter') handleClick();
196
- }}
197
- >
198
- <span className="db-tree-icon">{icon}</span>
199
- {renaming ? (
200
- <input
201
- ref={inputRef}
202
- className="db-tree-rename-input"
203
- value={renameValue}
204
- onChange={(e) => setRenameValue(e.target.value)}
205
- onBlur={handleRenameSubmit}
206
- onKeyDown={(e) => {
207
- if (e.key === 'Enter') handleRenameSubmit();
208
- if (e.key === 'Escape') setRenaming(false);
209
- }}
210
- onClick={(e) => e.stopPropagation()}
211
- />
212
- ) : (
213
- <>
214
- <span className="db-tree-label">
215
- {entry.name.endsWith('.md') ? entry.name.slice(0, -3) : entry.name}
216
- </span>
217
- {badge && (
218
- <span className={`db-git-badge db-git-badge--${badge.kind}`} aria-hidden="true">
219
- {badge.glyph}
220
- </span>
221
- )}
222
- <button
223
- type="button"
224
- className={`db-tree-more${showContext ? ' db-tree-more--active' : ''}`}
225
- onClick={handleMoreClick}
226
- onContextMenu={handleMoreClick}
227
- aria-label="More actions"
228
- aria-haspopup="menu"
229
- aria-expanded={showContext}
230
- tabIndex={-1}
231
- >
232
- <MoreIcon />
233
- </button>
234
- </>
235
- )}
236
- </div>
237
-
238
- {/* Context menu — portaled so ancestor overflow:hidden doesn't clip it */}
239
- {showContext &&
240
- createPortal(
241
- <div
242
- ref={contextRef}
243
- className="db-tree-context"
244
- style={{ left: contextPos.x, top: contextPos.y }}
245
- >
246
- <button className="db-tree-context-item" onClick={handleRenameStart}>
247
- Rename
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
- )}
288
- <button
289
- className="db-tree-context-item db-tree-context-item--danger"
290
- onClick={handleDeleteClick}
291
- >
292
- Delete
293
- </button>
294
- </div>,
295
- document.body,
296
- )}
297
-
298
- {/* Render children if directory is expanded */}
299
- {isDir && expanded && renderChildren && (
300
- <div className="db-tree-children" role="group">
301
- {renderChildren(entry.path)}
302
- </div>
303
- )}
304
- </div>
305
- );
306
- }
@@ -1,5 +0,0 @@
1
- export { FileExplorer } from './FileExplorer.js';
2
- export type { FileExplorerProps, FileTreeChange, FileTreeMutationHandler } from './FileExplorer.js';
3
- export { FileTreeNode } from './FileTreeNode.js';
4
- export type { FileTreeNodeProps } from './FileTreeNode.js';
5
- export { useFileTree } from './useFileTree.js';
@@ -1,352 +0,0 @@
1
- /**
2
- * useFileTree — hook that reads from a FileSystemProvider,
3
- * maintains expanded/collapsed state, and provides CRUD operations.
4
- */
5
-
6
- import { useState, useCallback, useEffect, useRef } from 'react';
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
- }
55
-
56
- export interface FileTreeState {
57
- /** Flat list of entries for the current directory view. */
58
- entries: FileSystemEntry[];
59
- /** Set of expanded directory paths. */
60
- expanded: Set<string>;
61
- /** Currently selected path — file or directory (null if none). */
62
- selectedPath: string | null;
63
- /** Kind of the selected entry. */
64
- selectedKind: 'file' | 'directory' | null;
65
- /** Whether the tree is loading. */
66
- loading: boolean;
67
- }
68
-
69
- export interface FileTreeActions {
70
- /** Toggle a directory's expanded state. */
71
- toggleExpand: (path: string) => void;
72
- /** Select a file or directory. */
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;
76
- /** Create a new file with optional initial content. */
77
- createFile: (path: string, content?: string) => Promise<void>;
78
- /** Create a new directory. */
79
- createDirectory: (path: string) => Promise<void>;
80
- /** Delete a file or directory. */
81
- deleteEntry: (path: 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>;
84
- /** Force refresh the tree. */
85
- refresh: () => Promise<void>;
86
- }
87
-
88
- export function useFileTree(provider: FileSystemProvider | null): FileTreeState & FileTreeActions {
89
- const [entries, setEntries] = useState<FileSystemEntry[]>([]);
90
- const [expanded, setExpanded] = useState<Set<string>>(new Set());
91
- const [selectedPath, setSelectedPath] = useState<string | null>(null);
92
- const [selectedKind, setSelectedKind] = useState<'file' | 'directory' | null>(null);
93
- const [loading, setLoading] = useState(false);
94
-
95
- // Track child entries for expanded directories
96
- const [childEntries, setChildEntries] = useState<Map<string, FileSystemEntry[]>>(new Map());
97
-
98
- const providerRef = useRef(provider);
99
- providerRef.current = provider;
100
-
101
- const loadRoot = useCallback(async () => {
102
- if (!providerRef.current) {
103
- setEntries([]);
104
- return;
105
- }
106
- setLoading(true);
107
- try {
108
- const root = await readProviderDirectory(providerRef.current, '');
109
- setEntries(root);
110
- } finally {
111
- setLoading(false);
112
- }
113
- }, []);
114
-
115
- const loadChildren = useCallback(async (dirPath: string) => {
116
- if (!providerRef.current) return;
117
- const children = await readProviderDirectory(providerRef.current, dirPath);
118
- setChildEntries((prev) => {
119
- const next = new Map(prev);
120
- next.set(dirPath, children);
121
- return next;
122
- });
123
- }, []);
124
-
125
- // Load root on provider change
126
- useEffect(() => {
127
- setExpanded(new Set());
128
- setChildEntries(new Map());
129
- setSelectedPath(null);
130
- setSelectedKind(null);
131
- loadRoot();
132
- }, [provider, loadRoot]);
133
-
134
- const toggleExpand = useCallback(
135
- (path: string) => {
136
- setExpanded((prev) => {
137
- const next = new Set(prev);
138
- if (hasEquivalentPath(next, path)) {
139
- for (const candidate of equivalentPathKeys(path)) next.delete(candidate);
140
- } else {
141
- next.add(path);
142
- // Load children when expanding
143
- loadChildren(path);
144
- }
145
- return next;
146
- });
147
- },
148
- [loadChildren],
149
- );
150
-
151
- const select = useCallback((path: string, kind: 'file' | 'directory') => {
152
- setSelectedPath(path);
153
- setSelectedKind(kind);
154
- }, []);
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
-
175
- const refresh = useCallback(async () => {
176
- await loadRoot();
177
- // Reload all expanded directories
178
- const expandedPaths = [...expanded];
179
- for (const dirPath of expandedPaths) {
180
- await loadChildren(dirPath);
181
- }
182
- }, [loadRoot, loadChildren, expanded]);
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
-
249
- const createFile = useCallback(
250
- async (path: string, content = '') => {
251
- if (!providerRef.current) return;
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
- }
260
- await refresh();
261
- },
262
- [refresh],
263
- );
264
-
265
- const createDirectory = useCallback(
266
- async (path: string) => {
267
- if (!providerRef.current) return;
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
- }
274
- await refresh();
275
- },
276
- [refresh],
277
- );
278
-
279
- const deleteEntry = useCallback(
280
- async (path: string) => {
281
- if (!providerRef.current) return;
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
- }
296
- if (selectedPath === path) {
297
- setSelectedPath(null);
298
- setSelectedKind(null);
299
- }
300
- await refresh();
301
- },
302
- [refresh, selectedPath],
303
- );
304
-
305
- const renameEntry = useCallback(
306
- async (oldPath: string, newPath: string, kind?: 'file' | 'directory') => {
307
- if (!providerRef.current) return;
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);
327
- }
328
- },
329
- [selectedPath, entries, childEntries, expanded, loadRoot, loadChildren],
330
- );
331
-
332
- // Merge root entries with child entries for a flat tree representation
333
- // (consumers use `expanded` and `childEntries` to render recursively)
334
-
335
- return {
336
- entries,
337
- expanded,
338
- selectedPath,
339
- selectedKind,
340
- loading,
341
- toggleExpand,
342
- select,
343
- reveal,
344
- createFile,
345
- createDirectory,
346
- deleteEntry,
347
- renameEntry,
348
- refresh,
349
- // Expose child entries for rendering
350
- ...{ childEntries },
351
- } as FileTreeState & FileTreeActions & { childEntries: Map<string, FileSystemEntry[]> };
352
- }