@bendyline/docblocks-react 1.1.2 → 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.
- package/README.md +37 -5
- package/dist/ExportDialog-7LDIXJZ3.js +10 -0
- package/dist/ExportDialog-7LDIXJZ3.js.map +1 -0
- package/dist/ExportToolbarControls-GHAOJMH6.js +508 -0
- package/dist/ExportToolbarControls-GHAOJMH6.js.map +1 -0
- package/dist/GitToolbarControl-GNMRVKWQ.js +136 -0
- package/dist/GitToolbarControl-GNMRVKWQ.js.map +1 -0
- package/dist/GitUI-VHOBQY43.js +1133 -0
- package/dist/GitUI-VHOBQY43.js.map +1 -0
- package/dist/LazyEditorShell-EF2BWRXU.js +6 -0
- package/dist/LazyEditorShell-EF2BWRXU.js.map +1 -0
- package/dist/ShareDialog-63ENBEDZ.js +213 -0
- package/dist/ShareDialog-63ENBEDZ.js.map +1 -0
- package/dist/chunk-4XXSWN3T.js +13 -0
- package/dist/chunk-4XXSWN3T.js.map +1 -0
- package/dist/chunk-57ZJJOC5.js +89 -0
- package/dist/chunk-57ZJJOC5.js.map +1 -0
- package/dist/chunk-5A4KEWKT.js +97 -0
- package/dist/chunk-5A4KEWKT.js.map +1 -0
- package/dist/chunk-DD4VFJQI.js +51 -0
- package/dist/chunk-DD4VFJQI.js.map +1 -0
- package/dist/chunk-FEWQUQML.js +256 -0
- package/dist/chunk-FEWQUQML.js.map +1 -0
- package/dist/chunk-FMGTHSTX.js +110 -0
- package/dist/chunk-FMGTHSTX.js.map +1 -0
- package/dist/chunk-UNYZQXWR.js +324 -0
- package/dist/chunk-UNYZQXWR.js.map +1 -0
- package/dist/chunk-VHDP4IPL.js +70 -0
- package/dist/chunk-VHDP4IPL.js.map +1 -0
- package/dist/chunk-ZI56BNB7.js +270 -0
- package/dist/chunk-ZI56BNB7.js.map +1 -0
- package/dist/editor.d.ts +11 -0
- package/dist/editor.js +9 -0
- package/dist/editor.js.map +1 -0
- package/dist/export/index.d.ts +93 -0
- package/dist/export/index.js +27 -0
- package/dist/export/index.js.map +1 -0
- package/dist/index.d.ts +346 -18
- package/dist/index.js +6169 -18
- package/dist/index.js.map +1 -1
- package/dist/run-export-ICTZITXC.js +10 -0
- package/dist/run-export-ICTZITXC.js.map +1 -0
- package/dist/settings/index.d.ts +36 -0
- package/dist/settings/index.js +14 -0
- package/dist/settings/index.js.map +1 -0
- package/package.json +33 -13
- package/src/styles/docblocks.css +2738 -132
- package/dist/AppMenu/AppMenu.d.ts +0 -26
- package/dist/AppMenu/AppMenu.d.ts.map +0 -1
- package/dist/AppMenu/AppMenu.js +0 -28
- package/dist/AppMenu/AppMenu.js.map +0 -1
- package/dist/AppMenu/index.d.ts +0 -3
- package/dist/AppMenu/index.d.ts.map +0 -1
- package/dist/AppMenu/index.js +0 -2
- package/dist/AppMenu/index.js.map +0 -1
- package/dist/DocBlocksShell/DocBlocksShell.d.ts +0 -36
- package/dist/DocBlocksShell/DocBlocksShell.d.ts.map +0 -1
- package/dist/DocBlocksShell/DocBlocksShell.js +0 -1351
- package/dist/DocBlocksShell/DocBlocksShell.js.map +0 -1
- package/dist/DocBlocksShell/index.d.ts +0 -3
- package/dist/DocBlocksShell/index.d.ts.map +0 -1
- package/dist/DocBlocksShell/index.js +0 -2
- package/dist/DocBlocksShell/index.js.map +0 -1
- package/dist/Export/ExportDialog.d.ts +0 -16
- package/dist/Export/ExportDialog.d.ts.map +0 -1
- package/dist/Export/ExportDialog.js +0 -158
- package/dist/Export/ExportDialog.js.map +0 -1
- package/dist/Export/ExportToolbarControls.d.ts +0 -17
- package/dist/Export/ExportToolbarControls.d.ts.map +0 -1
- package/dist/Export/ExportToolbarControls.js +0 -200
- package/dist/Export/ExportToolbarControls.js.map +0 -1
- package/dist/Export/export-options.d.ts +0 -53
- package/dist/Export/export-options.d.ts.map +0 -1
- package/dist/Export/export-options.js +0 -49
- package/dist/Export/export-options.js.map +0 -1
- package/dist/Export/index.d.ts +0 -6
- package/dist/Export/index.d.ts.map +0 -1
- package/dist/Export/index.js +0 -3
- package/dist/Export/index.js.map +0 -1
- package/dist/Export/run-export.d.ts +0 -8
- package/dist/Export/run-export.d.ts.map +0 -1
- package/dist/Export/run-export.js +0 -296
- package/dist/Export/run-export.js.map +0 -1
- package/dist/Export/transform-summaries.d.ts +0 -7
- package/dist/Export/transform-summaries.d.ts.map +0 -1
- package/dist/Export/transform-summaries.js +0 -6
- package/dist/Export/transform-summaries.js.map +0 -1
- package/dist/FileExplorer/FileExplorer.d.ts +0 -21
- package/dist/FileExplorer/FileExplorer.d.ts.map +0 -1
- package/dist/FileExplorer/FileExplorer.js +0 -140
- package/dist/FileExplorer/FileExplorer.js.map +0 -1
- package/dist/FileExplorer/FileTreeNode.d.ts +0 -18
- package/dist/FileExplorer/FileTreeNode.d.ts.map +0 -1
- package/dist/FileExplorer/FileTreeNode.js +0 -112
- package/dist/FileExplorer/FileTreeNode.js.map +0 -1
- package/dist/FileExplorer/index.d.ts +0 -6
- package/dist/FileExplorer/index.d.ts.map +0 -1
- package/dist/FileExplorer/index.js +0 -4
- package/dist/FileExplorer/index.js.map +0 -1
- package/dist/FileExplorer/useFileTree.d.ts +0 -35
- package/dist/FileExplorer/useFileTree.d.ts.map +0 -1
- package/dist/FileExplorer/useFileTree.js +0 -124
- package/dist/FileExplorer/useFileTree.js.map +0 -1
- package/dist/WorkspacePicker/WorkspacePicker.d.ts +0 -17
- package/dist/WorkspacePicker/WorkspacePicker.d.ts.map +0 -1
- package/dist/WorkspacePicker/WorkspacePicker.js +0 -63
- package/dist/WorkspacePicker/WorkspacePicker.js.map +0 -1
- package/dist/WorkspacePicker/WorkspaceSettingsButton.d.ts +0 -11
- package/dist/WorkspacePicker/WorkspaceSettingsButton.d.ts.map +0 -1
- package/dist/WorkspacePicker/WorkspaceSettingsButton.js +0 -27
- package/dist/WorkspacePicker/WorkspaceSettingsButton.js.map +0 -1
- package/dist/WorkspacePicker/WorkspaceSettingsDialog.d.ts +0 -20
- package/dist/WorkspacePicker/WorkspaceSettingsDialog.d.ts.map +0 -1
- package/dist/WorkspacePicker/WorkspaceSettingsDialog.js +0 -36
- package/dist/WorkspacePicker/WorkspaceSettingsDialog.js.map +0 -1
- package/dist/WorkspacePicker/index.d.ts +0 -3
- package/dist/WorkspacePicker/index.d.ts.map +0 -1
- package/dist/WorkspacePicker/index.js +0 -2
- package/dist/WorkspacePicker/index.js.map +0 -1
- package/dist/hooks/index.d.ts +0 -2
- package/dist/hooks/index.d.ts.map +0 -1
- package/dist/hooks/index.js +0 -2
- package/dist/hooks/index.js.map +0 -1
- package/dist/hooks/useAutoSave.d.ts +0 -15
- package/dist/hooks/useAutoSave.d.ts.map +0 -1
- package/dist/hooks/useAutoSave.js +0 -83
- package/dist/hooks/useAutoSave.js.map +0 -1
- package/dist/icons.d.ts +0 -7
- package/dist/icons.d.ts.map +0 -1
- package/dist/icons.js +0 -27
- package/dist/icons.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/monaco-slim.d.ts +0 -19
- package/dist/monaco-slim.d.ts.map +0 -1
- package/dist/monaco-slim.js +0 -19
- package/dist/monaco-slim.js.map +0 -1
- package/dist/preferences/versioning.d.ts +0 -27
- package/dist/preferences/versioning.d.ts.map +0 -1
- package/dist/preferences/versioning.js +0 -62
- package/dist/preferences/versioning.js.map +0 -1
- package/src/AppMenu/AppMenu.tsx +0 -264
- package/src/AppMenu/index.ts +0 -2
- package/src/DocBlocksShell/DocBlocksShell.tsx +0 -1778
- package/src/DocBlocksShell/index.ts +0 -2
- package/src/Export/ExportDialog.tsx +0 -383
- package/src/Export/ExportToolbarControls.tsx +0 -313
- package/src/Export/export-options.ts +0 -99
- package/src/Export/index.ts +0 -5
- package/src/Export/run-export.ts +0 -349
- package/src/Export/transform-summaries.ts +0 -14
- package/src/FileExplorer/FileExplorer.tsx +0 -282
- package/src/FileExplorer/FileTreeNode.tsx +0 -214
- package/src/FileExplorer/index.ts +0 -5
- package/src/FileExplorer/useFileTree.ts +0 -178
- package/src/WorkspacePicker/WorkspacePicker.tsx +0 -137
- package/src/WorkspacePicker/WorkspaceSettingsButton.tsx +0 -88
- package/src/WorkspacePicker/WorkspaceSettingsDialog.tsx +0 -121
- package/src/WorkspacePicker/index.ts +0 -2
- package/src/fonts/fonts.css +0 -362
- package/src/fonts/licenses/LICENSE-cormorant-garamond.txt +0 -93
- package/src/fonts/licenses/LICENSE-crimson-text.txt +0 -93
- package/src/fonts/licenses/LICENSE-dm-sans.txt +0 -93
- package/src/fonts/licenses/LICENSE-dm-serif-display.txt +0 -93
- package/src/fonts/licenses/LICENSE-hanken-grotesk.txt +0 -94
- package/src/fonts/licenses/LICENSE-ibm-plex-sans.txt +0 -93
- package/src/fonts/licenses/LICENSE-inter.txt +0 -93
- package/src/fonts/licenses/LICENSE-jetbrains-mono.txt +0 -93
- package/src/fonts/licenses/LICENSE-lora.txt +0 -93
- package/src/fonts/licenses/LICENSE-merriweather.txt +0 -93
- package/src/fonts/licenses/LICENSE-oswald.txt +0 -93
- package/src/fonts/licenses/LICENSE-playfair-display.txt +0 -93
- package/src/fonts/licenses/LICENSE-pt-serif.txt +0 -93
- package/src/fonts/licenses/LICENSE-roboto.txt +0 -93
- package/src/fonts/licenses/LICENSE-source-serif-4.txt +0 -93
- package/src/fonts/woff2/cormorant-garamond-400.woff2 +0 -0
- package/src/fonts/woff2/cormorant-garamond-700.woff2 +0 -0
- package/src/fonts/woff2/crimson-text-400.woff2 +0 -0
- package/src/fonts/woff2/crimson-text-400i.woff2 +0 -0
- package/src/fonts/woff2/crimson-text-700.woff2 +0 -0
- package/src/fonts/woff2/dm-sans-400.woff2 +0 -0
- package/src/fonts/woff2/dm-sans-400i.woff2 +0 -0
- package/src/fonts/woff2/dm-sans-500.woff2 +0 -0
- package/src/fonts/woff2/dm-sans-700.woff2 +0 -0
- package/src/fonts/woff2/dm-serif-display-400.woff2 +0 -0
- package/src/fonts/woff2/hanken-grotesk-400.woff2 +0 -0
- package/src/fonts/woff2/hanken-grotesk-400i.woff2 +0 -0
- package/src/fonts/woff2/hanken-grotesk-500.woff2 +0 -0
- package/src/fonts/woff2/hanken-grotesk-600.woff2 +0 -0
- package/src/fonts/woff2/hanken-grotesk-700.woff2 +0 -0
- package/src/fonts/woff2/ibm-plex-sans-400.woff2 +0 -0
- package/src/fonts/woff2/ibm-plex-sans-400i.woff2 +0 -0
- package/src/fonts/woff2/ibm-plex-sans-500.woff2 +0 -0
- package/src/fonts/woff2/ibm-plex-sans-600.woff2 +0 -0
- package/src/fonts/woff2/ibm-plex-sans-700.woff2 +0 -0
- package/src/fonts/woff2/inter-400.woff2 +0 -0
- package/src/fonts/woff2/inter-400i.woff2 +0 -0
- package/src/fonts/woff2/inter-500.woff2 +0 -0
- package/src/fonts/woff2/inter-600.woff2 +0 -0
- package/src/fonts/woff2/inter-700.woff2 +0 -0
- package/src/fonts/woff2/jetbrains-mono-400.woff2 +0 -0
- package/src/fonts/woff2/jetbrains-mono-400i.woff2 +0 -0
- package/src/fonts/woff2/jetbrains-mono-700.woff2 +0 -0
- package/src/fonts/woff2/lora-400.woff2 +0 -0
- package/src/fonts/woff2/lora-400i.woff2 +0 -0
- package/src/fonts/woff2/lora-700.woff2 +0 -0
- package/src/fonts/woff2/merriweather-400.woff2 +0 -0
- package/src/fonts/woff2/merriweather-700.woff2 +0 -0
- package/src/fonts/woff2/oswald-400.woff2 +0 -0
- package/src/fonts/woff2/oswald-700.woff2 +0 -0
- package/src/fonts/woff2/playfair-display-400.woff2 +0 -0
- package/src/fonts/woff2/playfair-display-700.woff2 +0 -0
- package/src/fonts/woff2/pt-serif-400.woff2 +0 -0
- package/src/fonts/woff2/pt-serif-700.woff2 +0 -0
- package/src/fonts/woff2/roboto-400.woff2 +0 -0
- package/src/fonts/woff2/roboto-400i.woff2 +0 -0
- package/src/fonts/woff2/roboto-700.woff2 +0 -0
- package/src/fonts/woff2/source-serif-4-400.woff2 +0 -0
- package/src/fonts/woff2/source-serif-4-400i.woff2 +0 -0
- package/src/fonts/woff2/source-serif-4-700.woff2 +0 -0
- package/src/hooks/index.ts +0 -1
- package/src/hooks/useAutoSave.ts +0 -116
- package/src/icons.tsx +0 -70
- package/src/index.ts +0 -31
- package/src/monaco-slim.ts +0 -20
- package/src/preferences/versioning.ts +0 -69
|
@@ -1,282 +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, useState } from 'react';
|
|
9
|
-
import type { FileSystemProvider, FileSystemEntry } from '@bendyline/docblocks/filesystem';
|
|
10
|
-
import { useFileTree } from './useFileTree.js';
|
|
11
|
-
import { FileTreeNode } from './FileTreeNode.js';
|
|
12
|
-
import { NewFileIcon, NewFolderIcon, RefreshIcon } from '../icons.js';
|
|
13
|
-
|
|
14
|
-
const SUPPORTED_EXTENSIONS = new Set(['.txt', '.md', '.docx', '.pdf', '.dbk', '.zip']);
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Hide auto-generated `<basename>_files/` companion folders from the
|
|
18
|
-
* tree. They hold per-document images and version snapshots — useful
|
|
19
|
-
* to the editor, noisy in the user-facing file list. The folders still
|
|
20
|
-
* exist on disk; we just don't surface them in the explorer.
|
|
21
|
-
*/
|
|
22
|
-
function isHiddenEntry(entry: FileSystemEntry): boolean {
|
|
23
|
-
if (entry.kind !== 'directory') return false;
|
|
24
|
-
const name = entry.path.replace(/^\/+/, '').split('/').pop() ?? '';
|
|
25
|
-
return name.endsWith('_files');
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function filterVisible(entries: FileSystemEntry[]): FileSystemEntry[] {
|
|
29
|
-
return entries.filter((e) => !isHiddenEntry(e));
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export interface FileExplorerProps {
|
|
33
|
-
/** The filesystem to display. */
|
|
34
|
-
provider: FileSystemProvider | null;
|
|
35
|
-
/** Called when any entry is selected (file or directory). */
|
|
36
|
-
onSelect?: (path: string, kind: 'file' | 'directory') => void;
|
|
37
|
-
/** Called after any mutation (create, delete, rename). */
|
|
38
|
-
onTreeChange?: () => void;
|
|
39
|
-
/** Called when supported files are dropped onto the explorer. */
|
|
40
|
-
onImportFiles?: (files: File[]) => void;
|
|
41
|
-
/** Optional className for the root element. */
|
|
42
|
-
className?: string;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export function FileExplorer({
|
|
46
|
-
provider,
|
|
47
|
-
onSelect,
|
|
48
|
-
onTreeChange,
|
|
49
|
-
onImportFiles,
|
|
50
|
-
className,
|
|
51
|
-
}: FileExplorerProps) {
|
|
52
|
-
const tree = useFileTree(provider);
|
|
53
|
-
const { childEntries } = tree as typeof tree & {
|
|
54
|
-
childEntries: Map<string, FileSystemEntry[]>;
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
const [newItemName, setNewItemName] = useState('');
|
|
58
|
-
const [newItemType, setNewItemType] = useState<'file' | 'directory' | null>(null);
|
|
59
|
-
const [dragOver, setDragOver] = useState(false);
|
|
60
|
-
const dragCounter = useState({ current: 0 })[0];
|
|
61
|
-
|
|
62
|
-
const hasSupported = useCallback((dt: DataTransfer): boolean => {
|
|
63
|
-
for (const item of Array.from(dt.items)) {
|
|
64
|
-
if (item.kind !== 'file') continue;
|
|
65
|
-
const name = (item as DataTransferItem & { getAsFile(): File | null }).getAsFile?.()?.name;
|
|
66
|
-
// During dragover the filename may not be available, so accept broadly
|
|
67
|
-
if (!name) return true;
|
|
68
|
-
const ext = name.slice(name.lastIndexOf('.')).toLowerCase();
|
|
69
|
-
if (SUPPORTED_EXTENSIONS.has(ext)) return true;
|
|
70
|
-
}
|
|
71
|
-
return dt.items.length > 0;
|
|
72
|
-
}, []);
|
|
73
|
-
|
|
74
|
-
const handleDragEnter = useCallback(
|
|
75
|
-
(e: React.DragEvent) => {
|
|
76
|
-
e.preventDefault();
|
|
77
|
-
dragCounter.current += 1;
|
|
78
|
-
if (hasSupported(e.dataTransfer)) setDragOver(true);
|
|
79
|
-
},
|
|
80
|
-
[hasSupported, dragCounter],
|
|
81
|
-
);
|
|
82
|
-
|
|
83
|
-
const handleDragOver = useCallback((e: React.DragEvent) => {
|
|
84
|
-
e.preventDefault();
|
|
85
|
-
e.dataTransfer.dropEffect = 'copy';
|
|
86
|
-
}, []);
|
|
87
|
-
|
|
88
|
-
const handleDragLeave = useCallback(
|
|
89
|
-
(e: React.DragEvent) => {
|
|
90
|
-
e.preventDefault();
|
|
91
|
-
dragCounter.current -= 1;
|
|
92
|
-
if (dragCounter.current <= 0) {
|
|
93
|
-
dragCounter.current = 0;
|
|
94
|
-
setDragOver(false);
|
|
95
|
-
}
|
|
96
|
-
},
|
|
97
|
-
[dragCounter],
|
|
98
|
-
);
|
|
99
|
-
|
|
100
|
-
const handleDrop = useCallback(
|
|
101
|
-
(e: React.DragEvent) => {
|
|
102
|
-
e.preventDefault();
|
|
103
|
-
dragCounter.current = 0;
|
|
104
|
-
setDragOver(false);
|
|
105
|
-
const supported = Array.from(e.dataTransfer.files).filter((f) => {
|
|
106
|
-
const ext = f.name.slice(f.name.lastIndexOf('.')).toLowerCase();
|
|
107
|
-
return SUPPORTED_EXTENSIONS.has(ext);
|
|
108
|
-
});
|
|
109
|
-
if (supported.length > 0) onImportFiles?.(supported);
|
|
110
|
-
},
|
|
111
|
-
[onImportFiles, dragCounter],
|
|
112
|
-
);
|
|
113
|
-
|
|
114
|
-
const handleSelect = useCallback(
|
|
115
|
-
(path: string) => {
|
|
116
|
-
// Determine kind from entries (root + children)
|
|
117
|
-
const findKind = (p: string): 'file' | 'directory' => {
|
|
118
|
-
for (const e of tree.entries) {
|
|
119
|
-
if (e.path === p) return e.kind;
|
|
120
|
-
}
|
|
121
|
-
for (const children of childEntries.values()) {
|
|
122
|
-
for (const e of children) {
|
|
123
|
-
if (e.path === p) return e.kind;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
return 'file';
|
|
127
|
-
};
|
|
128
|
-
const kind = findKind(path);
|
|
129
|
-
tree.select(path, kind);
|
|
130
|
-
onSelect?.(path, kind);
|
|
131
|
-
},
|
|
132
|
-
[tree, onSelect, childEntries],
|
|
133
|
-
);
|
|
134
|
-
|
|
135
|
-
const handleNewItemSubmit = useCallback(async () => {
|
|
136
|
-
if (!newItemName.trim()) {
|
|
137
|
-
setNewItemType(null);
|
|
138
|
-
return;
|
|
139
|
-
}
|
|
140
|
-
const name = newItemName.trim();
|
|
141
|
-
// Scope to currently selected folder (or root)
|
|
142
|
-
const prefix =
|
|
143
|
-
tree.selectedKind === 'directory' && tree.selectedPath ? `${tree.selectedPath}/` : '';
|
|
144
|
-
if (newItemType === 'file') {
|
|
145
|
-
const filename = name.endsWith('.md') ? name : `${name}.md`;
|
|
146
|
-
await tree.createFile(`${prefix}${filename}`, '');
|
|
147
|
-
} else if (newItemType === 'directory') {
|
|
148
|
-
await tree.createDirectory(`${prefix}${name}`);
|
|
149
|
-
}
|
|
150
|
-
setNewItemName('');
|
|
151
|
-
setNewItemType(null);
|
|
152
|
-
onTreeChange?.();
|
|
153
|
-
}, [newItemName, newItemType, tree, onTreeChange]);
|
|
154
|
-
|
|
155
|
-
const handleDelete = useCallback(
|
|
156
|
-
async (path: string) => {
|
|
157
|
-
await tree.deleteEntry(path);
|
|
158
|
-
onTreeChange?.();
|
|
159
|
-
},
|
|
160
|
-
[tree, onTreeChange],
|
|
161
|
-
);
|
|
162
|
-
|
|
163
|
-
const handleRename = useCallback(
|
|
164
|
-
async (oldPath: string, newPath: string) => {
|
|
165
|
-
await tree.renameEntry(oldPath, newPath);
|
|
166
|
-
onTreeChange?.();
|
|
167
|
-
},
|
|
168
|
-
[tree, onTreeChange],
|
|
169
|
-
);
|
|
170
|
-
|
|
171
|
-
const renderEntries = useCallback(
|
|
172
|
-
(entries: FileSystemEntry[], depth: number): React.ReactNode => {
|
|
173
|
-
return filterVisible(entries).map((entry) => (
|
|
174
|
-
<FileTreeNode
|
|
175
|
-
key={entry.path}
|
|
176
|
-
entry={entry}
|
|
177
|
-
depth={depth}
|
|
178
|
-
expanded={tree.expanded.has(entry.path)}
|
|
179
|
-
selected={tree.selectedPath === entry.path}
|
|
180
|
-
onToggle={tree.toggleExpand}
|
|
181
|
-
onSelect={handleSelect}
|
|
182
|
-
onDelete={handleDelete}
|
|
183
|
-
onRename={handleRename}
|
|
184
|
-
renderChildren={(dirPath: string) => {
|
|
185
|
-
const children = childEntries.get(dirPath) ?? [];
|
|
186
|
-
return renderEntries(children, depth + 1);
|
|
187
|
-
}}
|
|
188
|
-
/>
|
|
189
|
-
));
|
|
190
|
-
},
|
|
191
|
-
[tree, handleSelect, handleDelete, handleRename, childEntries],
|
|
192
|
-
);
|
|
193
|
-
|
|
194
|
-
return (
|
|
195
|
-
<div
|
|
196
|
-
className={`db-file-explorer ${dragOver ? 'db-file-explorer--drop-active' : ''} ${className ?? ''}`}
|
|
197
|
-
onDragEnter={handleDragEnter}
|
|
198
|
-
onDragOver={handleDragOver}
|
|
199
|
-
onDragLeave={handleDragLeave}
|
|
200
|
-
onDrop={handleDrop}
|
|
201
|
-
>
|
|
202
|
-
{/* Toolbar */}
|
|
203
|
-
<div className="db-explorer-toolbar">
|
|
204
|
-
<span className="db-explorer-title">Files</span>
|
|
205
|
-
<div className="db-explorer-actions">
|
|
206
|
-
<button
|
|
207
|
-
className="db-explorer-btn"
|
|
208
|
-
onClick={() => setNewItemType('file')}
|
|
209
|
-
title="New File"
|
|
210
|
-
aria-label="New File"
|
|
211
|
-
>
|
|
212
|
-
<NewFileIcon />
|
|
213
|
-
</button>
|
|
214
|
-
<button
|
|
215
|
-
className="db-explorer-btn"
|
|
216
|
-
onClick={() => setNewItemType('directory')}
|
|
217
|
-
title="New Folder"
|
|
218
|
-
aria-label="New Folder"
|
|
219
|
-
>
|
|
220
|
-
<NewFolderIcon />
|
|
221
|
-
</button>
|
|
222
|
-
<button
|
|
223
|
-
className="db-explorer-btn"
|
|
224
|
-
onClick={() => tree.refresh()}
|
|
225
|
-
title="Refresh file list"
|
|
226
|
-
aria-label="Refresh file list"
|
|
227
|
-
>
|
|
228
|
-
<RefreshIcon />
|
|
229
|
-
</button>
|
|
230
|
-
</div>
|
|
231
|
-
</div>
|
|
232
|
-
|
|
233
|
-
{/* New item input */}
|
|
234
|
-
{newItemType && (
|
|
235
|
-
<div className="db-new-item">
|
|
236
|
-
<form
|
|
237
|
-
className="db-new-item-row"
|
|
238
|
-
onSubmit={(e) => {
|
|
239
|
-
e.preventDefault();
|
|
240
|
-
handleNewItemSubmit();
|
|
241
|
-
}}
|
|
242
|
-
>
|
|
243
|
-
<input
|
|
244
|
-
className="db-new-item-input"
|
|
245
|
-
placeholder={newItemType === 'file' ? 'filename' : 'folder-name'}
|
|
246
|
-
value={newItemName}
|
|
247
|
-
onChange={(e) => setNewItemName(e.target.value)}
|
|
248
|
-
onKeyDown={(e) => {
|
|
249
|
-
if (e.key === 'Escape') {
|
|
250
|
-
setNewItemType(null);
|
|
251
|
-
setNewItemName('');
|
|
252
|
-
}
|
|
253
|
-
}}
|
|
254
|
-
autoFocus
|
|
255
|
-
/>
|
|
256
|
-
{newItemType === 'file' && <span className="db-new-item-suffix">.md</span>}
|
|
257
|
-
<button type="submit" className="db-new-item-add">
|
|
258
|
-
Add
|
|
259
|
-
</button>
|
|
260
|
-
</form>
|
|
261
|
-
</div>
|
|
262
|
-
)}
|
|
263
|
-
|
|
264
|
-
{/* Tree — role="tree" only when there are real treeitem children; the
|
|
265
|
-
loading/empty states get a status role so axe doesn't flag the
|
|
266
|
-
tree as missing required children. */}
|
|
267
|
-
{tree.loading ? (
|
|
268
|
-
<div className="db-tree" role="status" aria-live="polite">
|
|
269
|
-
<div className="db-tree-loading">Loading...</div>
|
|
270
|
-
</div>
|
|
271
|
-
) : filterVisible(tree.entries).length === 0 ? (
|
|
272
|
-
<div className="db-tree">
|
|
273
|
-
<div className="db-tree-empty">No files yet</div>
|
|
274
|
-
</div>
|
|
275
|
-
) : (
|
|
276
|
-
<div className="db-tree" role="tree">
|
|
277
|
-
{renderEntries(tree.entries, 0)}
|
|
278
|
-
</div>
|
|
279
|
-
)}
|
|
280
|
-
</div>
|
|
281
|
-
);
|
|
282
|
-
}
|
|
@@ -1,214 +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
|
-
export interface FileTreeNodeProps {
|
|
11
|
-
entry: FileSystemEntry;
|
|
12
|
-
depth: number;
|
|
13
|
-
expanded: boolean;
|
|
14
|
-
selected: boolean;
|
|
15
|
-
children?: FileSystemEntry[];
|
|
16
|
-
onToggle: (path: string) => void;
|
|
17
|
-
onSelect: (path: string) => void;
|
|
18
|
-
onDelete: (path: string) => Promise<void>;
|
|
19
|
-
onRename: (oldPath: string, newPath: string) => Promise<void>;
|
|
20
|
-
renderChildren?: (dirPath: string) => React.ReactNode;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function FileTreeNode({
|
|
24
|
-
entry,
|
|
25
|
-
depth,
|
|
26
|
-
expanded,
|
|
27
|
-
selected,
|
|
28
|
-
onToggle,
|
|
29
|
-
onSelect,
|
|
30
|
-
onDelete,
|
|
31
|
-
onRename,
|
|
32
|
-
renderChildren,
|
|
33
|
-
}: FileTreeNodeProps) {
|
|
34
|
-
const [renaming, setRenaming] = useState(false);
|
|
35
|
-
const [renameValue, setRenameValue] = useState(entry.name);
|
|
36
|
-
const [showContext, setShowContext] = useState(false);
|
|
37
|
-
const [contextPos, setContextPos] = useState({ x: 0, y: 0 });
|
|
38
|
-
const inputRef = useRef<HTMLInputElement>(null);
|
|
39
|
-
const contextRef = useRef<HTMLDivElement>(null);
|
|
40
|
-
const nodeRef = useRef<HTMLDivElement>(null);
|
|
41
|
-
|
|
42
|
-
const isDir = entry.kind === 'directory';
|
|
43
|
-
const icon = isDir ? (expanded ? '\u25BE' : '\u25B8') : '\u00A0\u00A0';
|
|
44
|
-
|
|
45
|
-
const handleClick = useCallback(() => {
|
|
46
|
-
if (isDir) {
|
|
47
|
-
onToggle(entry.path);
|
|
48
|
-
}
|
|
49
|
-
onSelect(entry.path);
|
|
50
|
-
}, [isDir, entry.path, onToggle, onSelect]);
|
|
51
|
-
|
|
52
|
-
const handleContextMenu = useCallback((e: React.MouseEvent) => {
|
|
53
|
-
e.preventDefault();
|
|
54
|
-
e.stopPropagation();
|
|
55
|
-
setContextPos({ x: e.clientX, y: e.clientY });
|
|
56
|
-
setShowContext(true);
|
|
57
|
-
}, []);
|
|
58
|
-
|
|
59
|
-
const handleMoreClick = useCallback((e: React.MouseEvent) => {
|
|
60
|
-
e.preventDefault();
|
|
61
|
-
e.stopPropagation();
|
|
62
|
-
const rect = (e.currentTarget as HTMLElement).getBoundingClientRect();
|
|
63
|
-
setContextPos({ x: rect.right, y: rect.bottom });
|
|
64
|
-
setShowContext(true);
|
|
65
|
-
}, []);
|
|
66
|
-
|
|
67
|
-
const handleRenameStart = useCallback(() => {
|
|
68
|
-
setRenameValue(entry.name);
|
|
69
|
-
setRenaming(true);
|
|
70
|
-
setShowContext(false);
|
|
71
|
-
}, [entry.name]);
|
|
72
|
-
|
|
73
|
-
const handleRenameSubmit = useCallback(async () => {
|
|
74
|
-
if (renameValue && renameValue !== entry.name) {
|
|
75
|
-
const parentPath = entry.path.includes('/')
|
|
76
|
-
? entry.path.slice(0, entry.path.lastIndexOf('/'))
|
|
77
|
-
: '';
|
|
78
|
-
const newPath = parentPath ? `${parentPath}/${renameValue}` : renameValue;
|
|
79
|
-
await onRename(entry.path, newPath);
|
|
80
|
-
}
|
|
81
|
-
setRenaming(false);
|
|
82
|
-
}, [renameValue, entry.name, entry.path, onRename]);
|
|
83
|
-
|
|
84
|
-
const handleDeleteClick = useCallback(async () => {
|
|
85
|
-
setShowContext(false);
|
|
86
|
-
await onDelete(entry.path);
|
|
87
|
-
}, [entry.path, onDelete]);
|
|
88
|
-
|
|
89
|
-
// Close context menu on outside click or scroll
|
|
90
|
-
useEffect(() => {
|
|
91
|
-
if (!showContext) return;
|
|
92
|
-
function handleClose(e: Event) {
|
|
93
|
-
if (contextRef.current && !contextRef.current.contains(e.target as Node)) {
|
|
94
|
-
setShowContext(false);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
// Use click (not mousedown) so menu button clicks register first
|
|
98
|
-
document.addEventListener('click', handleClose, true);
|
|
99
|
-
document.addEventListener('contextmenu', handleClose, true);
|
|
100
|
-
document.addEventListener('scroll', () => setShowContext(false), { capture: true, once: true });
|
|
101
|
-
return () => {
|
|
102
|
-
document.removeEventListener('click', handleClose, true);
|
|
103
|
-
document.removeEventListener('contextmenu', handleClose, true);
|
|
104
|
-
};
|
|
105
|
-
}, [showContext]);
|
|
106
|
-
|
|
107
|
-
// Focus input when renaming
|
|
108
|
-
useEffect(() => {
|
|
109
|
-
if (renaming && inputRef.current) {
|
|
110
|
-
inputRef.current.focus();
|
|
111
|
-
inputRef.current.select();
|
|
112
|
-
}
|
|
113
|
-
}, [renaming]);
|
|
114
|
-
|
|
115
|
-
// Clamp menu inside viewport after it renders
|
|
116
|
-
useLayoutEffect(() => {
|
|
117
|
-
if (!showContext || !contextRef.current) return;
|
|
118
|
-
const menu = contextRef.current;
|
|
119
|
-
const rect = menu.getBoundingClientRect();
|
|
120
|
-
const margin = 4;
|
|
121
|
-
let left = contextPos.x;
|
|
122
|
-
let top = contextPos.y;
|
|
123
|
-
if (rect.right > window.innerWidth - margin) {
|
|
124
|
-
left = Math.max(margin, window.innerWidth - rect.width - margin);
|
|
125
|
-
}
|
|
126
|
-
if (rect.bottom > window.innerHeight - margin) {
|
|
127
|
-
top = Math.max(margin, window.innerHeight - rect.height - margin);
|
|
128
|
-
}
|
|
129
|
-
if (left !== contextPos.x || top !== contextPos.y) {
|
|
130
|
-
menu.style.left = `${left}px`;
|
|
131
|
-
menu.style.top = `${top}px`;
|
|
132
|
-
}
|
|
133
|
-
}, [showContext, contextPos]);
|
|
134
|
-
|
|
135
|
-
return (
|
|
136
|
-
<div className="db-tree-node" ref={nodeRef}>
|
|
137
|
-
<div
|
|
138
|
-
className={`db-tree-row ${selected ? 'db-tree-row--selected' : ''}`}
|
|
139
|
-
style={{ paddingLeft: depth * 16 + 4 }}
|
|
140
|
-
onClick={handleClick}
|
|
141
|
-
onContextMenu={handleContextMenu}
|
|
142
|
-
role="treeitem"
|
|
143
|
-
aria-expanded={isDir ? expanded : undefined}
|
|
144
|
-
aria-selected={selected}
|
|
145
|
-
tabIndex={0}
|
|
146
|
-
onKeyDown={(e) => {
|
|
147
|
-
if (e.key === 'Enter') handleClick();
|
|
148
|
-
}}
|
|
149
|
-
>
|
|
150
|
-
<span className="db-tree-icon">{icon}</span>
|
|
151
|
-
{renaming ? (
|
|
152
|
-
<input
|
|
153
|
-
ref={inputRef}
|
|
154
|
-
className="db-tree-rename-input"
|
|
155
|
-
value={renameValue}
|
|
156
|
-
onChange={(e) => setRenameValue(e.target.value)}
|
|
157
|
-
onBlur={handleRenameSubmit}
|
|
158
|
-
onKeyDown={(e) => {
|
|
159
|
-
if (e.key === 'Enter') handleRenameSubmit();
|
|
160
|
-
if (e.key === 'Escape') setRenaming(false);
|
|
161
|
-
}}
|
|
162
|
-
onClick={(e) => e.stopPropagation()}
|
|
163
|
-
/>
|
|
164
|
-
) : (
|
|
165
|
-
<>
|
|
166
|
-
<span className="db-tree-label">
|
|
167
|
-
{entry.name.endsWith('.md') ? entry.name.slice(0, -3) : entry.name}
|
|
168
|
-
</span>
|
|
169
|
-
<button
|
|
170
|
-
type="button"
|
|
171
|
-
className={`db-tree-more${showContext ? ' db-tree-more--active' : ''}`}
|
|
172
|
-
onClick={handleMoreClick}
|
|
173
|
-
onContextMenu={handleMoreClick}
|
|
174
|
-
aria-label="More actions"
|
|
175
|
-
aria-haspopup="menu"
|
|
176
|
-
aria-expanded={showContext}
|
|
177
|
-
tabIndex={-1}
|
|
178
|
-
>
|
|
179
|
-
<MoreIcon width={14} height={14} />
|
|
180
|
-
</button>
|
|
181
|
-
</>
|
|
182
|
-
)}
|
|
183
|
-
</div>
|
|
184
|
-
|
|
185
|
-
{/* Context menu — portaled so ancestor overflow:hidden doesn't clip it */}
|
|
186
|
-
{showContext &&
|
|
187
|
-
createPortal(
|
|
188
|
-
<div
|
|
189
|
-
ref={contextRef}
|
|
190
|
-
className="db-tree-context"
|
|
191
|
-
style={{ left: contextPos.x, top: contextPos.y }}
|
|
192
|
-
>
|
|
193
|
-
<button className="db-tree-context-item" onClick={handleRenameStart}>
|
|
194
|
-
Rename
|
|
195
|
-
</button>
|
|
196
|
-
<button
|
|
197
|
-
className="db-tree-context-item db-tree-context-item--danger"
|
|
198
|
-
onClick={handleDeleteClick}
|
|
199
|
-
>
|
|
200
|
-
Delete
|
|
201
|
-
</button>
|
|
202
|
-
</div>,
|
|
203
|
-
document.body,
|
|
204
|
-
)}
|
|
205
|
-
|
|
206
|
-
{/* Render children if directory is expanded */}
|
|
207
|
-
{isDir && expanded && renderChildren && (
|
|
208
|
-
<div className="db-tree-children" role="group">
|
|
209
|
-
{renderChildren(entry.path)}
|
|
210
|
-
</div>
|
|
211
|
-
)}
|
|
212
|
-
</div>
|
|
213
|
-
);
|
|
214
|
-
}
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
export { FileExplorer } from './FileExplorer.js';
|
|
2
|
-
export type { FileExplorerProps } from './FileExplorer.js';
|
|
3
|
-
export { FileTreeNode } from './FileTreeNode.js';
|
|
4
|
-
export type { FileTreeNodeProps } from './FileTreeNode.js';
|
|
5
|
-
export { useFileTree } from './useFileTree.js';
|
|
@@ -1,178 +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 type { FileSystemProvider, FileSystemEntry } from '@bendyline/docblocks/filesystem';
|
|
8
|
-
|
|
9
|
-
export interface FileTreeState {
|
|
10
|
-
/** Flat list of entries for the current directory view. */
|
|
11
|
-
entries: FileSystemEntry[];
|
|
12
|
-
/** Set of expanded directory paths. */
|
|
13
|
-
expanded: Set<string>;
|
|
14
|
-
/** Currently selected path — file or directory (null if none). */
|
|
15
|
-
selectedPath: string | null;
|
|
16
|
-
/** Kind of the selected entry. */
|
|
17
|
-
selectedKind: 'file' | 'directory' | null;
|
|
18
|
-
/** Whether the tree is loading. */
|
|
19
|
-
loading: boolean;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface FileTreeActions {
|
|
23
|
-
/** Toggle a directory's expanded state. */
|
|
24
|
-
toggleExpand: (path: string) => void;
|
|
25
|
-
/** Select a file or directory. */
|
|
26
|
-
select: (path: string, kind: 'file' | 'directory') => void;
|
|
27
|
-
/** Create a new file with optional initial content. */
|
|
28
|
-
createFile: (path: string, content?: string) => Promise<void>;
|
|
29
|
-
/** Create a new directory. */
|
|
30
|
-
createDirectory: (path: string) => Promise<void>;
|
|
31
|
-
/** Delete a file or directory. */
|
|
32
|
-
deleteEntry: (path: string) => Promise<void>;
|
|
33
|
-
/** Rename a file or directory. */
|
|
34
|
-
renameEntry: (oldPath: string, newPath: string) => Promise<void>;
|
|
35
|
-
/** Force refresh the tree. */
|
|
36
|
-
refresh: () => Promise<void>;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export function useFileTree(provider: FileSystemProvider | null): FileTreeState & FileTreeActions {
|
|
40
|
-
const [entries, setEntries] = useState<FileSystemEntry[]>([]);
|
|
41
|
-
const [expanded, setExpanded] = useState<Set<string>>(new Set());
|
|
42
|
-
const [selectedPath, setSelectedPath] = useState<string | null>(null);
|
|
43
|
-
const [selectedKind, setSelectedKind] = useState<'file' | 'directory' | null>(null);
|
|
44
|
-
const [loading, setLoading] = useState(false);
|
|
45
|
-
|
|
46
|
-
// Track child entries for expanded directories
|
|
47
|
-
const [childEntries, setChildEntries] = useState<Map<string, FileSystemEntry[]>>(new Map());
|
|
48
|
-
|
|
49
|
-
const providerRef = useRef(provider);
|
|
50
|
-
providerRef.current = provider;
|
|
51
|
-
|
|
52
|
-
const loadRoot = useCallback(async () => {
|
|
53
|
-
if (!providerRef.current) {
|
|
54
|
-
setEntries([]);
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
setLoading(true);
|
|
58
|
-
try {
|
|
59
|
-
const root = await providerRef.current.readDirectory('');
|
|
60
|
-
setEntries(root);
|
|
61
|
-
} finally {
|
|
62
|
-
setLoading(false);
|
|
63
|
-
}
|
|
64
|
-
}, []);
|
|
65
|
-
|
|
66
|
-
const loadChildren = useCallback(async (dirPath: string) => {
|
|
67
|
-
if (!providerRef.current) return;
|
|
68
|
-
const children = await providerRef.current.readDirectory(dirPath);
|
|
69
|
-
setChildEntries((prev) => {
|
|
70
|
-
const next = new Map(prev);
|
|
71
|
-
next.set(dirPath, children);
|
|
72
|
-
return next;
|
|
73
|
-
});
|
|
74
|
-
}, []);
|
|
75
|
-
|
|
76
|
-
// Load root on provider change
|
|
77
|
-
useEffect(() => {
|
|
78
|
-
setExpanded(new Set());
|
|
79
|
-
setChildEntries(new Map());
|
|
80
|
-
setSelectedPath(null);
|
|
81
|
-
setSelectedKind(null);
|
|
82
|
-
loadRoot();
|
|
83
|
-
}, [provider, loadRoot]);
|
|
84
|
-
|
|
85
|
-
const toggleExpand = useCallback(
|
|
86
|
-
(path: string) => {
|
|
87
|
-
setExpanded((prev) => {
|
|
88
|
-
const next = new Set(prev);
|
|
89
|
-
if (next.has(path)) {
|
|
90
|
-
next.delete(path);
|
|
91
|
-
} else {
|
|
92
|
-
next.add(path);
|
|
93
|
-
// Load children when expanding
|
|
94
|
-
loadChildren(path);
|
|
95
|
-
}
|
|
96
|
-
return next;
|
|
97
|
-
});
|
|
98
|
-
},
|
|
99
|
-
[loadChildren],
|
|
100
|
-
);
|
|
101
|
-
|
|
102
|
-
const select = useCallback((path: string, kind: 'file' | 'directory') => {
|
|
103
|
-
setSelectedPath(path);
|
|
104
|
-
setSelectedKind(kind);
|
|
105
|
-
}, []);
|
|
106
|
-
|
|
107
|
-
const refresh = useCallback(async () => {
|
|
108
|
-
await loadRoot();
|
|
109
|
-
// Reload all expanded directories
|
|
110
|
-
const expandedPaths = [...expanded];
|
|
111
|
-
for (const dirPath of expandedPaths) {
|
|
112
|
-
await loadChildren(dirPath);
|
|
113
|
-
}
|
|
114
|
-
}, [loadRoot, loadChildren, expanded]);
|
|
115
|
-
|
|
116
|
-
const createFile = useCallback(
|
|
117
|
-
async (path: string, content = '') => {
|
|
118
|
-
if (!providerRef.current) return;
|
|
119
|
-
await providerRef.current.writeFile(path, content);
|
|
120
|
-
await refresh();
|
|
121
|
-
},
|
|
122
|
-
[refresh],
|
|
123
|
-
);
|
|
124
|
-
|
|
125
|
-
const createDirectory = useCallback(
|
|
126
|
-
async (path: string) => {
|
|
127
|
-
if (!providerRef.current) return;
|
|
128
|
-
await providerRef.current.createDirectory(path);
|
|
129
|
-
await refresh();
|
|
130
|
-
},
|
|
131
|
-
[refresh],
|
|
132
|
-
);
|
|
133
|
-
|
|
134
|
-
const deleteEntry = useCallback(
|
|
135
|
-
async (path: string) => {
|
|
136
|
-
if (!providerRef.current) return;
|
|
137
|
-
await providerRef.current.delete(path);
|
|
138
|
-
if (selectedPath === path) {
|
|
139
|
-
setSelectedPath(null);
|
|
140
|
-
setSelectedKind(null);
|
|
141
|
-
}
|
|
142
|
-
await refresh();
|
|
143
|
-
},
|
|
144
|
-
[refresh, selectedPath],
|
|
145
|
-
);
|
|
146
|
-
|
|
147
|
-
const renameEntry = useCallback(
|
|
148
|
-
async (oldPath: string, newPath: string) => {
|
|
149
|
-
if (!providerRef.current) return;
|
|
150
|
-
await providerRef.current.rename(oldPath, newPath);
|
|
151
|
-
if (selectedPath === oldPath) {
|
|
152
|
-
setSelectedPath(newPath);
|
|
153
|
-
}
|
|
154
|
-
await refresh();
|
|
155
|
-
},
|
|
156
|
-
[refresh, selectedPath],
|
|
157
|
-
);
|
|
158
|
-
|
|
159
|
-
// Merge root entries with child entries for a flat tree representation
|
|
160
|
-
// (consumers use `expanded` and `childEntries` to render recursively)
|
|
161
|
-
|
|
162
|
-
return {
|
|
163
|
-
entries,
|
|
164
|
-
expanded,
|
|
165
|
-
selectedPath,
|
|
166
|
-
selectedKind,
|
|
167
|
-
loading,
|
|
168
|
-
toggleExpand,
|
|
169
|
-
select,
|
|
170
|
-
createFile,
|
|
171
|
-
createDirectory,
|
|
172
|
-
deleteEntry,
|
|
173
|
-
renameEntry,
|
|
174
|
-
refresh,
|
|
175
|
-
// Expose child entries for rendering
|
|
176
|
-
...{ childEntries },
|
|
177
|
-
} as FileTreeState & FileTreeActions & { childEntries: Map<string, FileSystemEntry[]> };
|
|
178
|
-
}
|