@bendyline/docblocks-react 2.0.0 → 2.1.1

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 (310) hide show
  1. package/README.md +18 -3
  2. package/dist/ExportDialog-VYATD5UC.js +9 -0
  3. package/dist/{ExportToolbarControls-D2YDWOXW.js → ExportToolbarControls-5HUE3LSX.js} +206 -42
  4. package/dist/{GitToolbarControl-LMIR4R7I.js → GitToolbarControl-FW3BZFXV.js} +62 -44
  5. package/dist/{GitUI-KGF5MSNV.js → GitUI-GMH3PAYL.js} +225 -141
  6. package/dist/{LazyEditorShell-EF2BWRXU.js → LazyEditorShell-RF54B5UD.js} +0 -1
  7. package/dist/ShareDialog-PAMOQQFB.js +212 -0
  8. package/dist/chunk-3CZ27FOA.js +359 -0
  9. package/dist/chunk-3QSZY74C.js +255 -0
  10. package/{src/Export/export-destination.ts → dist/chunk-BI7NVU6T.js} +16 -15
  11. package/dist/{chunk-VHDP4IPL.js → chunk-BPGEBGAN.js} +0 -1
  12. package/dist/{chunk-DD4VFJQI.js → chunk-EBWYGTN7.js} +2 -1
  13. package/dist/{chunk-ZI56BNB7.js → chunk-GHQ4X7KM.js} +27 -7
  14. package/dist/{chunk-5A4KEWKT.js → chunk-HFYPTTCH.js} +0 -1
  15. package/dist/chunk-LG6HAWCK.js +109 -0
  16. package/dist/chunk-TFMO7EVO.js +88 -0
  17. package/dist/{chunk-4XXSWN3T.js → chunk-YBEYTVU2.js} +0 -1
  18. package/dist/editor.d.ts +5 -4
  19. package/dist/editor.js +8 -70
  20. package/dist/export/index.d.ts +24 -8
  21. package/dist/export/index.js +10 -6
  22. package/dist/index.d.ts +355 -21
  23. package/dist/index.js +6419 -21
  24. package/dist/run-export-Z5BVFLIR.js +9 -0
  25. package/dist/settings/index.d.ts +9 -1
  26. package/dist/settings/index.js +6 -5
  27. package/package.json +18 -13
  28. package/src/styles/docblocks.css +985 -35
  29. package/dist/AppMenu/AppMenu.d.ts +0 -53
  30. package/dist/AppMenu/AppMenu.d.ts.map +0 -1
  31. package/dist/AppMenu/AppMenu.js +0 -67
  32. package/dist/AppMenu/AppMenu.js.map +0 -1
  33. package/dist/AppMenu/index.d.ts +0 -3
  34. package/dist/AppMenu/index.d.ts.map +0 -1
  35. package/dist/AppMenu/index.js +0 -2
  36. package/dist/AppMenu/index.js.map +0 -1
  37. package/dist/DocBlocksShell/DocBlocksShell.d.ts +0 -49
  38. package/dist/DocBlocksShell/DocBlocksShell.d.ts.map +0 -1
  39. package/dist/DocBlocksShell/DocBlocksShell.js +0 -2730
  40. package/dist/DocBlocksShell/DocBlocksShell.js.map +0 -1
  41. package/dist/DocBlocksShell/LazyEditorShell.d.ts +0 -3
  42. package/dist/DocBlocksShell/LazyEditorShell.d.ts.map +0 -1
  43. package/dist/DocBlocksShell/LazyEditorShell.js +0 -3
  44. package/dist/DocBlocksShell/LazyEditorShell.js.map +0 -1
  45. package/dist/DocBlocksShell/UpdateAvailableNotice.d.ts +0 -13
  46. package/dist/DocBlocksShell/UpdateAvailableNotice.d.ts.map +0 -1
  47. package/dist/DocBlocksShell/UpdateAvailableNotice.js +0 -16
  48. package/dist/DocBlocksShell/UpdateAvailableNotice.js.map +0 -1
  49. package/dist/DocBlocksShell/document-title.d.ts +0 -3
  50. package/dist/DocBlocksShell/document-title.d.ts.map +0 -1
  51. package/dist/DocBlocksShell/document-title.js +0 -17
  52. package/dist/DocBlocksShell/document-title.js.map +0 -1
  53. package/dist/DocBlocksShell/index.d.ts +0 -3
  54. package/dist/DocBlocksShell/index.d.ts.map +0 -1
  55. package/dist/DocBlocksShell/index.js +0 -2
  56. package/dist/DocBlocksShell/index.js.map +0 -1
  57. package/dist/DocBlocksShell/workspace-authority-barrier.d.ts +0 -14
  58. package/dist/DocBlocksShell/workspace-authority-barrier.d.ts.map +0 -1
  59. package/dist/DocBlocksShell/workspace-authority-barrier.js +0 -23
  60. package/dist/DocBlocksShell/workspace-authority-barrier.js.map +0 -1
  61. package/dist/Export/DeferredExportToolbarControls.d.ts +0 -5
  62. package/dist/Export/DeferredExportToolbarControls.d.ts.map +0 -1
  63. package/dist/Export/DeferredExportToolbarControls.js +0 -10
  64. package/dist/Export/DeferredExportToolbarControls.js.map +0 -1
  65. package/dist/Export/ExportDialog.d.ts +0 -25
  66. package/dist/Export/ExportDialog.d.ts.map +0 -1
  67. package/dist/Export/ExportDialog.js +0 -160
  68. package/dist/Export/ExportDialog.js.map +0 -1
  69. package/dist/Export/ExportToolbarControls.d.ts +0 -37
  70. package/dist/Export/ExportToolbarControls.d.ts.map +0 -1
  71. package/dist/Export/ExportToolbarControls.js +0 -285
  72. package/dist/Export/ExportToolbarControls.js.map +0 -1
  73. package/dist/Export/export-destination.d.ts +0 -3
  74. package/dist/Export/export-destination.d.ts.map +0 -1
  75. package/dist/Export/export-destination.js +0 -32
  76. package/dist/Export/export-destination.js.map +0 -1
  77. package/dist/Export/export-options.d.ts +0 -53
  78. package/dist/Export/export-options.d.ts.map +0 -1
  79. package/dist/Export/export-options.js +0 -49
  80. package/dist/Export/export-options.js.map +0 -1
  81. package/dist/Export/index.d.ts +0 -10
  82. package/dist/Export/index.d.ts.map +0 -1
  83. package/dist/Export/index.js +0 -6
  84. package/dist/Export/index.js.map +0 -1
  85. package/dist/Export/public-api.d.ts +0 -12
  86. package/dist/Export/public-api.d.ts.map +0 -1
  87. package/dist/Export/public-api.js +0 -9
  88. package/dist/Export/public-api.js.map +0 -1
  89. package/dist/Export/run-export.d.ts +0 -10
  90. package/dist/Export/run-export.d.ts.map +0 -1
  91. package/dist/Export/run-export.js +0 -313
  92. package/dist/Export/run-export.js.map +0 -1
  93. package/dist/Export/transform-summaries.d.ts +0 -7
  94. package/dist/Export/transform-summaries.d.ts.map +0 -1
  95. package/dist/Export/transform-summaries.js +0 -6
  96. package/dist/Export/transform-summaries.js.map +0 -1
  97. package/dist/ExportDialog-MEUISWZX.js +0 -9
  98. package/dist/ExportDialog-MEUISWZX.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/LazyEditorShell-EF2BWRXU.js.map +0 -1
  187. package/dist/Settings/Settings.d.ts +0 -23
  188. package/dist/Settings/Settings.d.ts.map +0 -1
  189. package/dist/Settings/Settings.js +0 -22
  190. package/dist/Settings/Settings.js.map +0 -1
  191. package/dist/Settings/public-api.d.ts +0 -3
  192. package/dist/Settings/public-api.d.ts.map +0 -1
  193. package/dist/Settings/public-api.js +0 -2
  194. package/dist/Settings/public-api.js.map +0 -1
  195. package/dist/WorkspacePicker/WorkspacePicker.d.ts +0 -22
  196. package/dist/WorkspacePicker/WorkspacePicker.d.ts.map +0 -1
  197. package/dist/WorkspacePicker/WorkspacePicker.js +0 -74
  198. package/dist/WorkspacePicker/WorkspacePicker.js.map +0 -1
  199. package/dist/WorkspacePicker/WorkspaceSettingsButton.d.ts +0 -11
  200. package/dist/WorkspacePicker/WorkspaceSettingsButton.d.ts.map +0 -1
  201. package/dist/WorkspacePicker/WorkspaceSettingsButton.js +0 -27
  202. package/dist/WorkspacePicker/WorkspaceSettingsButton.js.map +0 -1
  203. package/dist/WorkspacePicker/WorkspaceSettingsDialog.d.ts +0 -20
  204. package/dist/WorkspacePicker/WorkspaceSettingsDialog.d.ts.map +0 -1
  205. package/dist/WorkspacePicker/WorkspaceSettingsDialog.js +0 -36
  206. package/dist/WorkspacePicker/WorkspaceSettingsDialog.js.map +0 -1
  207. package/dist/WorkspacePicker/index.d.ts +0 -3
  208. package/dist/WorkspacePicker/index.d.ts.map +0 -1
  209. package/dist/WorkspacePicker/index.js +0 -2
  210. package/dist/WorkspacePicker/index.js.map +0 -1
  211. package/dist/chunk-3SAXDU2U.js +0 -157
  212. package/dist/chunk-3SAXDU2U.js.map +0 -1
  213. package/dist/chunk-4XXSWN3T.js.map +0 -1
  214. package/dist/chunk-5A4KEWKT.js.map +0 -1
  215. package/dist/chunk-AOVTHZU7.js +0 -321
  216. package/dist/chunk-AOVTHZU7.js.map +0 -1
  217. package/dist/chunk-DD4VFJQI.js.map +0 -1
  218. package/dist/chunk-GSNZSCIK.js +0 -55
  219. package/dist/chunk-GSNZSCIK.js.map +0 -1
  220. package/dist/chunk-VHDP4IPL.js.map +0 -1
  221. package/dist/chunk-ZI56BNB7.js.map +0 -1
  222. package/dist/components/Dialog.d.ts +0 -22
  223. package/dist/components/Dialog.d.ts.map +0 -1
  224. package/dist/components/Dialog.js +0 -33
  225. package/dist/components/Dialog.js.map +0 -1
  226. package/dist/editor.d.ts.map +0 -1
  227. package/dist/editor.js.map +0 -1
  228. package/dist/export/index.js.map +0 -1
  229. package/dist/hooks/index.d.ts +0 -3
  230. package/dist/hooks/index.d.ts.map +0 -1
  231. package/dist/hooks/index.js +0 -2
  232. package/dist/hooks/index.js.map +0 -1
  233. package/dist/hooks/useDocumentSession.d.ts +0 -16
  234. package/dist/hooks/useDocumentSession.d.ts.map +0 -1
  235. package/dist/hooks/useDocumentSession.js +0 -18
  236. package/dist/hooks/useDocumentSession.js.map +0 -1
  237. package/dist/icons.d.ts +0 -12
  238. package/dist/icons.d.ts.map +0 -1
  239. package/dist/icons.js +0 -28
  240. package/dist/icons.js.map +0 -1
  241. package/dist/index.d.ts.map +0 -1
  242. package/dist/index.js.map +0 -1
  243. package/dist/preferences/theme.d.ts +0 -18
  244. package/dist/preferences/theme.d.ts.map +0 -1
  245. package/dist/preferences/theme.js +0 -63
  246. package/dist/preferences/theme.js.map +0 -1
  247. package/dist/preferences/versioning.d.ts +0 -27
  248. package/dist/preferences/versioning.d.ts.map +0 -1
  249. package/dist/preferences/versioning.js +0 -62
  250. package/dist/preferences/versioning.js.map +0 -1
  251. package/dist/provider-lease.d.ts +0 -10
  252. package/dist/provider-lease.d.ts.map +0 -1
  253. package/dist/provider-lease.js +0 -42
  254. package/dist/provider-lease.js.map +0 -1
  255. package/dist/run-export-ICTZITXC.js +0 -10
  256. package/dist/run-export-ICTZITXC.js.map +0 -1
  257. package/dist/settings/index.js.map +0 -1
  258. package/src/AppMenu/AppMenu.tsx +0 -334
  259. package/src/AppMenu/index.ts +0 -2
  260. package/src/DocBlocksShell/DocBlocksShell.tsx +0 -3398
  261. package/src/DocBlocksShell/LazyEditorShell.ts +0 -2
  262. package/src/DocBlocksShell/UpdateAvailableNotice.tsx +0 -60
  263. package/src/DocBlocksShell/document-title.ts +0 -19
  264. package/src/DocBlocksShell/index.ts +0 -2
  265. package/src/DocBlocksShell/workspace-authority-barrier.ts +0 -26
  266. package/src/Export/DeferredExportToolbarControls.tsx +0 -23
  267. package/src/Export/ExportDialog.tsx +0 -437
  268. package/src/Export/ExportToolbarControls.tsx +0 -511
  269. package/src/Export/export-options.ts +0 -99
  270. package/src/Export/index.ts +0 -19
  271. package/src/Export/public-api.ts +0 -17
  272. package/src/Export/run-export.ts +0 -374
  273. package/src/Export/transform-summaries.ts +0 -14
  274. package/src/FileExplorer/FileExplorer.tsx +0 -528
  275. package/src/FileExplorer/FileTreeNode.tsx +0 -306
  276. package/src/FileExplorer/index.ts +0 -5
  277. package/src/FileExplorer/useFileTree.ts +0 -352
  278. package/src/Git/GitAuthGuidanceDialog.tsx +0 -84
  279. package/src/Git/GitBranchesDialog.tsx +0 -144
  280. package/src/Git/GitCloneDialog.tsx +0 -200
  281. package/src/Git/GitCommitDialog.tsx +0 -119
  282. package/src/Git/GitConflictsDialog.tsx +0 -62
  283. package/src/Git/GitContext.tsx +0 -88
  284. package/src/Git/GitDiffDialog.tsx +0 -147
  285. package/src/Git/GitDiffView.tsx +0 -105
  286. package/src/Git/GitHistoryDialog.tsx +0 -195
  287. package/src/Git/GitStatusBar.tsx +0 -157
  288. package/src/Git/GitToolbarControl.tsx +0 -123
  289. package/src/Git/GitUI.tsx +0 -68
  290. package/src/Git/commit-selection.ts +0 -63
  291. package/src/Git/git-clone-utils.ts +0 -23
  292. package/src/Git/git-status.ts +0 -113
  293. package/src/Git/useGit.ts +0 -279
  294. package/src/Git/useGitStatus.ts +0 -78
  295. package/src/Settings/Settings.tsx +0 -118
  296. package/src/Settings/public-api.ts +0 -8
  297. package/src/WorkspacePicker/WorkspacePicker.tsx +0 -191
  298. package/src/WorkspacePicker/WorkspaceSettingsButton.tsx +0 -88
  299. package/src/WorkspacePicker/WorkspaceSettingsDialog.tsx +0 -121
  300. package/src/WorkspacePicker/index.ts +0 -2
  301. package/src/components/Dialog.tsx +0 -81
  302. package/src/editor.ts +0 -72
  303. package/src/hooks/index.ts +0 -2
  304. package/src/hooks/useDocumentSession.ts +0 -37
  305. package/src/icons.tsx +0 -48
  306. package/src/index.ts +0 -61
  307. package/src/preferences/theme.ts +0 -67
  308. package/src/preferences/versioning.ts +0 -69
  309. package/src/provider-lease.ts +0 -52
  310. package/src/types/pwa-dom.d.ts +0 -31
@@ -1,528 +0,0 @@
1
- /**
2
- * FileExplorer — tree component rendering FileSystemProvider contents.
3
- *
4
- * Shows a file/folder tree with expand/collapse, selection, and a toolbar
5
- * for creating new files/folders.
6
- */
7
-
8
- import { useCallback, useEffect, useRef, useState } from 'react';
9
- import {
10
- isFileSystemMoveStateError,
11
- parseWorkspacePath,
12
- type FileSystemProvider,
13
- type FileSystemEntry,
14
- } from '@bendyline/docblocks/filesystem';
15
- import { useFileTree } from './useFileTree.js';
16
- import {
17
- FileTreeNode,
18
- type FileTreeNodeBadge,
19
- type FileTreeNodeGitActions,
20
- } from './FileTreeNode.js';
21
- import { NewFileIcon, NewFolderIcon } from '../icons.js';
22
- import { useGitContext } from '../Git/GitContext.js';
23
- import { BADGE_GLYPHS, BADGE_LABELS, isFileDirty } from '../Git/git-status.js';
24
-
25
- const SUPPORTED_EXTENSIONS = new Set(['.txt', '.md', '.docx', '.pdf', '.dbk', '.zip']);
26
- const INTERNAL_DRAG_TYPE = 'application/x-docblocks-entry';
27
-
28
- export type FileTreeChange =
29
- | { type: 'create'; path: string; kind?: 'file' | 'directory' }
30
- | { type: 'delete'; path: string; kind: 'file' | 'directory' }
31
- | {
32
- type: 'move';
33
- oldPath: string;
34
- newPath: string;
35
- kind: 'file' | 'directory';
36
- };
37
-
38
- export type FileTreeMutationHandler = (
39
- change: FileTreeChange,
40
- mutate: () => Promise<void>,
41
- ) => Promise<void>;
42
-
43
- function normalisePath(path: string): string {
44
- return parseWorkspacePath(path);
45
- }
46
-
47
- function hasEquivalentPath(paths: ReadonlySet<string>, path: string): boolean {
48
- const canonical = normalisePath(path);
49
- return (
50
- paths.has(path) || paths.has(canonical) || (canonical !== '' && paths.has(`/${canonical}`))
51
- );
52
- }
53
-
54
- function getEquivalentPathValue<T>(values: ReadonlyMap<string, T>, path: string): T | undefined {
55
- const canonical = normalisePath(path);
56
- return values.get(path) ?? values.get(canonical) ?? values.get(`/${canonical}`);
57
- }
58
-
59
- function parentPath(path: string): string {
60
- const clean = normalisePath(path);
61
- const slash = clean.lastIndexOf('/');
62
- return slash < 0 ? '' : clean.slice(0, slash);
63
- }
64
-
65
- function pathInDirectory(entry: FileSystemEntry, directoryPath: string): string {
66
- const prefix = directoryPath.replace(/\/+$/, '');
67
- if (prefix) return `${prefix}/${entry.name}`;
68
- return entry.path.startsWith('/') ? `/${entry.name}` : entry.name;
69
- }
70
-
71
- function canMoveTo(entry: FileSystemEntry, directoryPath: string): boolean {
72
- const source = normalisePath(entry.path);
73
- const target = normalisePath(directoryPath);
74
- if (parentPath(source) === target) return false;
75
- if (entry.kind === 'directory' && (target === source || target.startsWith(`${source}/`))) {
76
- return false;
77
- }
78
- return true;
79
- }
80
-
81
- function isInternalDrag(dataTransfer: DataTransfer): boolean {
82
- return Array.from(dataTransfer.types).includes(INTERNAL_DRAG_TYPE);
83
- }
84
-
85
- /**
86
- * Hide dot-entries (`.git`, `.gitignore`, `.DS_Store`, …) — never user
87
- * documents — and auto-generated `<basename>_files/` companion folders,
88
- * which hold per-document images and version snapshots (useful to the
89
- * editor, noisy in the user-facing file list). Everything still exists on
90
- * disk; we just don't surface it in the explorer.
91
- */
92
- function isHiddenEntry(entry: FileSystemEntry): boolean {
93
- const name = entry.path.replace(/^\/+/, '').split('/').pop() ?? '';
94
- if (name.startsWith('.')) return true;
95
- return entry.kind === 'directory' && name.endsWith('_files');
96
- }
97
-
98
- function filterVisible(entries: FileSystemEntry[]): FileSystemEntry[] {
99
- return entries.filter((e) => !isHiddenEntry(e));
100
- }
101
-
102
- export interface FileExplorerProps {
103
- /** The filesystem to display. */
104
- provider: FileSystemProvider | null;
105
- /** Active document to select and reveal by expanding all ancestor folders. */
106
- activeFilePath?: string | null;
107
- /** Called when any entry is selected (file or directory). */
108
- onSelect?: (path: string, kind: 'file' | 'directory') => void;
109
- /**
110
- * Wraps destructive tree mutations so the active document session can
111
- * flush, cancel, or retarget itself before storage changes.
112
- */
113
- onTreeMutation?: FileTreeMutationHandler;
114
- /** Called after any mutation, with move details when paths change. */
115
- onTreeChange?: (change?: FileTreeChange) => void;
116
- /** Called when supported files are dropped onto the explorer. */
117
- onImportFiles?: (files: File[]) => void;
118
- /** Optional className for the root element. */
119
- className?: string;
120
- }
121
-
122
- export function FileExplorer({
123
- provider,
124
- activeFilePath,
125
- onSelect,
126
- onTreeMutation,
127
- onTreeChange,
128
- onImportFiles,
129
- className,
130
- }: FileExplorerProps) {
131
- const tree = useFileTree(provider);
132
- const { childEntries } = tree as typeof tree & {
133
- childEntries: Map<string, FileSystemEntry[]>;
134
- };
135
- const { reveal } = tree;
136
- // Null on surfaces without git (site, tests) — everything degrades.
137
- const git = useGitContext();
138
-
139
- const [newItemName, setNewItemName] = useState('');
140
- const [newItemType, setNewItemType] = useState<'file' | 'directory' | null>(null);
141
- const [dragOver, setDragOver] = useState(false);
142
- const [draggedEntry, setDraggedEntry] = useState<FileSystemEntry | null>(null);
143
- const [dropTarget, setDropTarget] = useState<string | null>(null);
144
- const [moveError, setMoveError] = useState<string | null>(null);
145
- const dragCounter = useRef(0);
146
-
147
- useEffect(() => {
148
- if (activeFilePath) reveal(activeFilePath, 'file');
149
- }, [activeFilePath, provider, reveal]);
150
-
151
- const hasSupported = useCallback((dt: DataTransfer): boolean => {
152
- for (const item of Array.from(dt.items)) {
153
- if (item.kind !== 'file') continue;
154
- const name = (item as DataTransferItem & { getAsFile(): File | null }).getAsFile?.()?.name;
155
- // During dragover the filename may not be available, so accept broadly
156
- if (!name) return true;
157
- const ext = name.slice(name.lastIndexOf('.')).toLowerCase();
158
- if (SUPPORTED_EXTENSIONS.has(ext)) return true;
159
- }
160
- return dt.items.length > 0;
161
- }, []);
162
-
163
- const handleDragEnter = useCallback(
164
- (e: React.DragEvent) => {
165
- if (isInternalDrag(e.dataTransfer)) return;
166
- e.preventDefault();
167
- dragCounter.current += 1;
168
- if (hasSupported(e.dataTransfer)) setDragOver(true);
169
- },
170
- [hasSupported, dragCounter],
171
- );
172
-
173
- const handleDragOver = useCallback((e: React.DragEvent) => {
174
- if (isInternalDrag(e.dataTransfer)) return;
175
- e.preventDefault();
176
- e.dataTransfer.dropEffect = 'copy';
177
- }, []);
178
-
179
- const handleDragLeave = useCallback(
180
- (e: React.DragEvent) => {
181
- if (isInternalDrag(e.dataTransfer)) return;
182
- e.preventDefault();
183
- dragCounter.current -= 1;
184
- if (dragCounter.current <= 0) {
185
- dragCounter.current = 0;
186
- setDragOver(false);
187
- }
188
- },
189
- [dragCounter],
190
- );
191
-
192
- const handleDrop = useCallback(
193
- (e: React.DragEvent) => {
194
- if (isInternalDrag(e.dataTransfer)) return;
195
- e.preventDefault();
196
- dragCounter.current = 0;
197
- setDragOver(false);
198
- const supported = Array.from(e.dataTransfer.files).filter((f) => {
199
- const ext = f.name.slice(f.name.lastIndexOf('.')).toLowerCase();
200
- return SUPPORTED_EXTENSIONS.has(ext);
201
- });
202
- if (supported.length > 0) onImportFiles?.(supported);
203
- },
204
- [onImportFiles],
205
- );
206
-
207
- const handleSelect = useCallback(
208
- (path: string) => {
209
- // Determine kind from entries (root + children)
210
- const findKind = (p: string): 'file' | 'directory' => {
211
- for (const e of tree.entries) {
212
- if (e.path === p) return e.kind;
213
- }
214
- for (const children of childEntries.values()) {
215
- for (const e of children) {
216
- if (e.path === p) return e.kind;
217
- }
218
- }
219
- return 'file';
220
- };
221
- const kind = findKind(path);
222
- tree.select(path, kind);
223
- onSelect?.(path, kind);
224
- },
225
- [tree, onSelect, childEntries],
226
- );
227
-
228
- const handleNewItemSubmit = useCallback(async () => {
229
- if (!newItemName.trim()) {
230
- setNewItemType(null);
231
- return;
232
- }
233
- const name = newItemName.trim();
234
- // Scope to currently selected folder (or root)
235
- const prefix =
236
- tree.selectedKind === 'directory' && tree.selectedPath ? `${tree.selectedPath}/` : '';
237
- let createdPath = `${prefix}${name}`;
238
- if (newItemType === 'file') {
239
- const filename = name.endsWith('.md') ? name : `${name}.md`;
240
- createdPath = `${prefix}${filename}`;
241
- await tree.createFile(createdPath, '');
242
- } else if (newItemType === 'directory') {
243
- await tree.createDirectory(createdPath);
244
- }
245
- setNewItemName('');
246
- setNewItemType(null);
247
- onTreeChange?.({ type: 'create', path: createdPath });
248
- }, [newItemName, newItemType, tree, onTreeChange]);
249
-
250
- const runTreeMutation = useCallback(
251
- async (change: FileTreeChange, mutate: () => Promise<void>) => {
252
- if (onTreeMutation) await onTreeMutation(change, mutate);
253
- else await mutate();
254
- onTreeChange?.(change);
255
- },
256
- [onTreeMutation, onTreeChange],
257
- );
258
-
259
- const handleDelete = useCallback(
260
- async (path: string, kind: 'file' | 'directory') => {
261
- const change: FileTreeChange = { type: 'delete', path, kind };
262
- await runTreeMutation(change, () => tree.deleteEntry(path));
263
- },
264
- [tree, runTreeMutation],
265
- );
266
-
267
- const handleRename = useCallback(
268
- async (oldPath: string, newPath: string, kind: 'file' | 'directory') => {
269
- setMoveError(null);
270
- const change: FileTreeChange = { type: 'move', oldPath, newPath, kind };
271
- try {
272
- await runTreeMutation(change, () => tree.renameEntry(oldPath, newPath, kind));
273
- } catch (error: unknown) {
274
- await tree.refresh();
275
- if (isFileSystemMoveStateError(error) && error.documentLocation === 'destination') {
276
- onTreeChange?.(change);
277
- }
278
- setMoveError(error instanceof Error ? error.message : 'Unable to move this entry.');
279
- }
280
- },
281
- [tree, runTreeMutation, onTreeChange],
282
- );
283
-
284
- const handleMove = useCallback(
285
- async (entry: FileSystemEntry, directoryPath: string) => {
286
- if (!canMoveTo(entry, directoryPath)) return;
287
- const newPath = pathInDirectory(entry, directoryPath);
288
- await handleRename(entry.path, newPath, entry.kind);
289
- if (directoryPath && !tree.expanded.has(directoryPath)) {
290
- tree.toggleExpand(directoryPath);
291
- }
292
- },
293
- [handleRename, tree],
294
- );
295
-
296
- const handleInternalDragStart = useCallback((e: React.DragEvent, entry: FileSystemEntry) => {
297
- e.dataTransfer.effectAllowed = 'move';
298
- e.dataTransfer.setData(INTERNAL_DRAG_TYPE, entry.path);
299
- e.dataTransfer.setData('text/plain', entry.path);
300
- setDraggedEntry(entry);
301
- setMoveError(null);
302
- }, []);
303
-
304
- const handleInternalDragEnd = useCallback(() => {
305
- setDraggedEntry(null);
306
- setDropTarget(null);
307
- }, []);
308
-
309
- const handleEntryDragOver = useCallback(
310
- (e: React.DragEvent, entry: FileSystemEntry) => {
311
- if (!isInternalDrag(e.dataTransfer)) return;
312
- e.stopPropagation();
313
- const canDrop =
314
- entry.kind === 'directory' && draggedEntry && canMoveTo(draggedEntry, entry.path);
315
- e.dataTransfer.dropEffect = canDrop ? 'move' : 'none';
316
- if (canDrop) {
317
- e.preventDefault();
318
- setDropTarget(entry.path);
319
- } else {
320
- setDropTarget(null);
321
- }
322
- },
323
- [draggedEntry],
324
- );
325
-
326
- const handleEntryDrop = useCallback(
327
- (e: React.DragEvent, entry: FileSystemEntry) => {
328
- if (!isInternalDrag(e.dataTransfer)) return;
329
- e.preventDefault();
330
- e.stopPropagation();
331
- setDropTarget(null);
332
- if (entry.kind === 'directory' && draggedEntry) {
333
- void handleMove(draggedEntry, entry.path);
334
- }
335
- },
336
- [draggedEntry, handleMove],
337
- );
338
-
339
- const handleRootDragOver = useCallback(
340
- (e: React.DragEvent) => {
341
- if (!isInternalDrag(e.dataTransfer) || !draggedEntry) return;
342
- e.preventDefault();
343
- e.stopPropagation();
344
- const canDrop = canMoveTo(draggedEntry, '');
345
- e.dataTransfer.dropEffect = canDrop ? 'move' : 'none';
346
- setDropTarget(canDrop ? '' : null);
347
- },
348
- [draggedEntry],
349
- );
350
-
351
- const handleRootDrop = useCallback(
352
- (e: React.DragEvent) => {
353
- if (!isInternalDrag(e.dataTransfer)) return;
354
- e.preventDefault();
355
- e.stopPropagation();
356
- setDropTarget(null);
357
- if (draggedEntry) void handleMove(draggedEntry, '');
358
- },
359
- [draggedEntry, handleMove],
360
- );
361
-
362
- const badgeFor = useCallback(
363
- (entry: FileSystemEntry): FileTreeNodeBadge | undefined => {
364
- if (!git?.repo) return undefined;
365
- const key = entry.path.startsWith('/') ? entry.path : `/${entry.path}`;
366
- const kind = git.badges.get(key);
367
- if (!kind) return undefined;
368
- return { kind, glyph: BADGE_GLYPHS[kind], label: BADGE_LABELS[kind] };
369
- },
370
- [git],
371
- );
372
-
373
- const gitActionsFor = useCallback(
374
- (entry: FileSystemEntry): FileTreeNodeGitActions | undefined => {
375
- if (!git?.repo || entry.kind !== 'file') return undefined;
376
- const path = entry.path.startsWith('/') ? entry.path : `/${entry.path}`;
377
- return {
378
- viewChanges: isFileDirty(git.status, path)
379
- ? () => git.openDialog({ kind: 'diff', path })
380
- : undefined,
381
- fileHistory: () => git.openDialog({ kind: 'history', path }),
382
- openOnRemote: git.remoteWeb ? () => git.openOnRemote(path) : undefined,
383
- };
384
- },
385
- [git],
386
- );
387
-
388
- const renderEntries = useCallback(
389
- (entries: FileSystemEntry[], depth: number): React.ReactNode => {
390
- return filterVisible(entries).map((entry) => (
391
- <FileTreeNode
392
- key={entry.path}
393
- entry={entry}
394
- depth={depth}
395
- expanded={hasEquivalentPath(tree.expanded, entry.path)}
396
- selected={
397
- tree.selectedPath !== null &&
398
- normalisePath(tree.selectedPath) === normalisePath(entry.path)
399
- }
400
- badge={badgeFor(entry)}
401
- gitActions={gitActionsFor(entry)}
402
- onToggle={tree.toggleExpand}
403
- onSelect={handleSelect}
404
- onDelete={handleDelete}
405
- onRename={handleRename}
406
- draggable
407
- dragging={draggedEntry?.path === entry.path}
408
- dropTarget={dropTarget === entry.path}
409
- onDragStart={handleInternalDragStart}
410
- onDragEnd={handleInternalDragEnd}
411
- onDragOverEntry={handleEntryDragOver}
412
- onDropEntry={handleEntryDrop}
413
- renderChildren={(dirPath: string) => {
414
- const children = getEquivalentPathValue(childEntries, dirPath) ?? [];
415
- return renderEntries(children, depth + 1);
416
- }}
417
- />
418
- ));
419
- },
420
- [
421
- tree,
422
- handleSelect,
423
- handleDelete,
424
- handleRename,
425
- childEntries,
426
- badgeFor,
427
- gitActionsFor,
428
- draggedEntry,
429
- dropTarget,
430
- handleInternalDragStart,
431
- handleInternalDragEnd,
432
- handleEntryDragOver,
433
- handleEntryDrop,
434
- ],
435
- );
436
-
437
- return (
438
- <div
439
- className={`db-file-explorer ${dragOver ? 'db-file-explorer--drop-active' : ''} ${className ?? ''}`}
440
- onDragEnter={handleDragEnter}
441
- onDragOver={handleDragOver}
442
- onDragLeave={handleDragLeave}
443
- onDrop={handleDrop}
444
- >
445
- {/* Toolbar */}
446
- <div className="db-explorer-toolbar">
447
- <span className="db-explorer-title">Files</span>
448
- <div className="db-explorer-actions">
449
- <button
450
- className="db-explorer-btn"
451
- onClick={() => setNewItemType('file')}
452
- title="New File"
453
- aria-label="New File"
454
- >
455
- <NewFileIcon />
456
- </button>
457
- <button
458
- className="db-explorer-btn"
459
- onClick={() => setNewItemType('directory')}
460
- title="New Folder"
461
- aria-label="New Folder"
462
- >
463
- <NewFolderIcon />
464
- </button>
465
- </div>
466
- </div>
467
-
468
- {/* New item input */}
469
- {newItemType && (
470
- <div className="db-new-item">
471
- <form
472
- className="db-new-item-row"
473
- onSubmit={(e) => {
474
- e.preventDefault();
475
- handleNewItemSubmit();
476
- }}
477
- >
478
- <input
479
- className="db-new-item-input"
480
- placeholder={newItemType === 'file' ? 'filename' : 'folder-name'}
481
- value={newItemName}
482
- onChange={(e) => setNewItemName(e.target.value)}
483
- onKeyDown={(e) => {
484
- if (e.key === 'Escape') {
485
- setNewItemType(null);
486
- setNewItemName('');
487
- }
488
- }}
489
- autoFocus
490
- />
491
- {newItemType === 'file' && <span className="db-new-item-suffix">.md</span>}
492
- <button type="submit" className="db-new-item-add">
493
- Add
494
- </button>
495
- </form>
496
- </div>
497
- )}
498
-
499
- {moveError && (
500
- <div className="db-tree-error" role="alert">
501
- {moveError}
502
- </div>
503
- )}
504
-
505
- {/* Tree — role="tree" only when there are real treeitem children; the
506
- loading/empty states get a status role so axe doesn't flag the
507
- tree as missing required children. */}
508
- {tree.loading ? (
509
- <div className="db-tree" role="status" aria-live="polite">
510
- <div className="db-tree-loading">Loading...</div>
511
- </div>
512
- ) : filterVisible(tree.entries).length === 0 ? (
513
- <div className="db-tree">
514
- <div className="db-tree-empty">No files yet</div>
515
- </div>
516
- ) : (
517
- <div
518
- className={`db-tree ${dropTarget === '' ? 'db-tree--drop-target' : ''}`}
519
- role="tree"
520
- onDragOver={handleRootDragOver}
521
- onDrop={handleRootDrop}
522
- >
523
- {renderEntries(tree.entries, 0)}
524
- </div>
525
- )}
526
- </div>
527
- );
528
- }