@bendyline/docblocks-react 1.1.2 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -5
- package/dist/AppMenu/AppMenu.d.ts +29 -2
- package/dist/AppMenu/AppMenu.d.ts.map +1 -1
- package/dist/AppMenu/AppMenu.js +41 -2
- package/dist/AppMenu/AppMenu.js.map +1 -1
- package/dist/AppMenu/index.d.ts +1 -1
- package/dist/AppMenu/index.d.ts.map +1 -1
- package/dist/DocBlocksShell/DocBlocksShell.d.ts +15 -2
- package/dist/DocBlocksShell/DocBlocksShell.d.ts.map +1 -1
- package/dist/DocBlocksShell/DocBlocksShell.js +1677 -298
- package/dist/DocBlocksShell/DocBlocksShell.js.map +1 -1
- package/dist/DocBlocksShell/LazyEditorShell.d.ts +3 -0
- package/dist/DocBlocksShell/LazyEditorShell.d.ts.map +1 -0
- package/dist/DocBlocksShell/LazyEditorShell.js +3 -0
- package/dist/DocBlocksShell/LazyEditorShell.js.map +1 -0
- package/dist/DocBlocksShell/UpdateAvailableNotice.d.ts +13 -0
- package/dist/DocBlocksShell/UpdateAvailableNotice.d.ts.map +1 -0
- package/dist/DocBlocksShell/UpdateAvailableNotice.js +16 -0
- package/dist/DocBlocksShell/UpdateAvailableNotice.js.map +1 -0
- package/dist/DocBlocksShell/document-title.d.ts +3 -0
- package/dist/DocBlocksShell/document-title.d.ts.map +1 -0
- package/dist/DocBlocksShell/document-title.js +17 -0
- package/dist/DocBlocksShell/document-title.js.map +1 -0
- package/dist/DocBlocksShell/workspace-authority-barrier.d.ts +14 -0
- package/dist/DocBlocksShell/workspace-authority-barrier.d.ts.map +1 -0
- package/dist/DocBlocksShell/workspace-authority-barrier.js +23 -0
- package/dist/DocBlocksShell/workspace-authority-barrier.js.map +1 -0
- package/dist/Export/DeferredExportToolbarControls.d.ts +5 -0
- package/dist/Export/DeferredExportToolbarControls.d.ts.map +1 -0
- package/dist/Export/DeferredExportToolbarControls.js +10 -0
- package/dist/Export/DeferredExportToolbarControls.js.map +1 -0
- package/dist/Export/ExportDialog.d.ts +10 -1
- package/dist/Export/ExportDialog.d.ts.map +1 -1
- package/dist/Export/ExportDialog.js +20 -18
- package/dist/Export/ExportDialog.js.map +1 -1
- package/dist/Export/ExportToolbarControls.d.ts +21 -1
- package/dist/Export/ExportToolbarControls.d.ts.map +1 -1
- package/dist/Export/ExportToolbarControls.js +96 -11
- package/dist/Export/ExportToolbarControls.js.map +1 -1
- package/dist/Export/export-destination.d.ts +3 -0
- package/dist/Export/export-destination.d.ts.map +1 -0
- package/dist/Export/export-destination.js +32 -0
- package/dist/Export/export-destination.js.map +1 -0
- package/dist/Export/index.d.ts +8 -4
- package/dist/Export/index.d.ts.map +1 -1
- package/dist/Export/index.js +4 -1
- package/dist/Export/index.js.map +1 -1
- package/dist/Export/public-api.d.ts +12 -0
- package/dist/Export/public-api.d.ts.map +1 -0
- package/dist/Export/public-api.js +9 -0
- package/dist/Export/public-api.js.map +1 -0
- package/dist/Export/run-export.d.ts +3 -1
- package/dist/Export/run-export.d.ts.map +1 -1
- package/dist/Export/run-export.js +30 -13
- package/dist/Export/run-export.js.map +1 -1
- package/dist/ExportDialog-MEUISWZX.js +9 -0
- package/dist/ExportDialog-MEUISWZX.js.map +1 -0
- package/dist/ExportToolbarControls-D2YDWOXW.js +373 -0
- package/dist/ExportToolbarControls-D2YDWOXW.js.map +1 -0
- package/dist/FileExplorer/FileExplorer.d.ts +26 -4
- package/dist/FileExplorer/FileExplorer.d.ts.map +1 -1
- package/dist/FileExplorer/FileExplorer.js +204 -29
- package/dist/FileExplorer/FileExplorer.js.map +1 -1
- package/dist/FileExplorer/FileTreeNode.d.ts +24 -3
- package/dist/FileExplorer/FileTreeNode.d.ts.map +1 -1
- package/dist/FileExplorer/FileTreeNode.js +27 -8
- package/dist/FileExplorer/FileTreeNode.js.map +1 -1
- package/dist/FileExplorer/index.d.ts +1 -1
- package/dist/FileExplorer/index.d.ts.map +1 -1
- package/dist/FileExplorer/useFileTree.d.ts +5 -3
- package/dist/FileExplorer/useFileTree.d.ts.map +1 -1
- package/dist/FileExplorer/useFileTree.js +173 -13
- package/dist/FileExplorer/useFileTree.js.map +1 -1
- package/dist/Git/GitAuthGuidanceDialog.d.ts +18 -0
- package/dist/Git/GitAuthGuidanceDialog.d.ts.map +1 -0
- package/dist/Git/GitAuthGuidanceDialog.js +27 -0
- package/dist/Git/GitAuthGuidanceDialog.js.map +1 -0
- package/dist/Git/GitBranchesDialog.d.ts +13 -0
- package/dist/Git/GitBranchesDialog.d.ts.map +1 -0
- package/dist/Git/GitBranchesDialog.js +59 -0
- package/dist/Git/GitBranchesDialog.js.map +1 -0
- package/dist/Git/GitCloneDialog.d.ts +14 -0
- package/dist/Git/GitCloneDialog.d.ts.map +1 -0
- package/dist/Git/GitCloneDialog.js +79 -0
- package/dist/Git/GitCloneDialog.js.map +1 -0
- package/dist/Git/GitCommitDialog.d.ts +12 -0
- package/dist/Git/GitCommitDialog.d.ts.map +1 -0
- package/dist/Git/GitCommitDialog.js +38 -0
- package/dist/Git/GitCommitDialog.js.map +1 -0
- package/dist/Git/GitConflictsDialog.d.ts +12 -0
- package/dist/Git/GitConflictsDialog.d.ts.map +1 -0
- package/dist/Git/GitConflictsDialog.js +17 -0
- package/dist/Git/GitConflictsDialog.js.map +1 -0
- package/dist/Git/GitContext.d.ts +89 -0
- package/dist/Git/GitContext.d.ts.map +1 -0
- package/dist/Git/GitContext.js +15 -0
- package/dist/Git/GitContext.js.map +1 -0
- package/dist/Git/GitDiffDialog.d.ts +20 -0
- package/dist/Git/GitDiffDialog.d.ts.map +1 -0
- package/dist/Git/GitDiffDialog.js +100 -0
- package/dist/Git/GitDiffDialog.js.map +1 -0
- package/dist/Git/GitDiffView.d.ts +23 -0
- package/dist/Git/GitDiffView.d.ts.map +1 -0
- package/dist/Git/GitDiffView.js +77 -0
- package/dist/Git/GitDiffView.js.map +1 -0
- package/dist/Git/GitHistoryDialog.d.ts +15 -0
- package/dist/Git/GitHistoryDialog.d.ts.map +1 -0
- package/dist/Git/GitHistoryDialog.js +107 -0
- package/dist/Git/GitHistoryDialog.js.map +1 -0
- package/dist/Git/GitStatusBar.d.ts +8 -0
- package/dist/Git/GitStatusBar.d.ts.map +1 -0
- package/dist/Git/GitStatusBar.js +49 -0
- package/dist/Git/GitStatusBar.js.map +1 -0
- package/dist/Git/GitToolbarControl.d.ts +15 -0
- package/dist/Git/GitToolbarControl.d.ts.map +1 -0
- package/dist/Git/GitToolbarControl.js +51 -0
- package/dist/Git/GitToolbarControl.js.map +1 -0
- package/dist/Git/GitUI.d.ts +14 -0
- package/dist/Git/GitUI.d.ts.map +1 -0
- package/dist/Git/GitUI.js +21 -0
- package/dist/Git/GitUI.js.map +1 -0
- package/dist/Git/commit-selection.d.ts +22 -0
- package/dist/Git/commit-selection.d.ts.map +1 -0
- package/dist/Git/commit-selection.js +47 -0
- package/dist/Git/commit-selection.js.map +1 -0
- package/dist/Git/git-clone-utils.d.ts +8 -0
- package/dist/Git/git-clone-utils.d.ts.map +1 -0
- package/dist/Git/git-clone-utils.js +26 -0
- package/dist/Git/git-clone-utils.js.map +1 -0
- package/dist/Git/git-status.d.ts +24 -0
- package/dist/Git/git-status.d.ts.map +1 -0
- package/dist/Git/git-status.js +105 -0
- package/dist/Git/git-status.js.map +1 -0
- package/dist/Git/useGit.d.ts +15 -0
- package/dist/Git/useGit.d.ts.map +1 -0
- package/dist/Git/useGit.js +206 -0
- package/dist/Git/useGit.js.map +1 -0
- package/dist/Git/useGitStatus.d.ts +17 -0
- package/dist/Git/useGitStatus.d.ts.map +1 -0
- package/dist/Git/useGitStatus.js +63 -0
- package/dist/Git/useGitStatus.js.map +1 -0
- package/dist/GitToolbarControl-LMIR4R7I.js +117 -0
- package/dist/GitToolbarControl-LMIR4R7I.js.map +1 -0
- package/dist/GitUI-KGF5MSNV.js +1048 -0
- package/dist/GitUI-KGF5MSNV.js.map +1 -0
- package/dist/LazyEditorShell-EF2BWRXU.js +6 -0
- package/dist/LazyEditorShell-EF2BWRXU.js.map +1 -0
- package/dist/Settings/Settings.d.ts +23 -0
- package/dist/Settings/Settings.d.ts.map +1 -0
- package/dist/Settings/Settings.js +22 -0
- package/dist/Settings/Settings.js.map +1 -0
- package/dist/Settings/public-api.d.ts +3 -0
- package/dist/Settings/public-api.d.ts.map +1 -0
- package/dist/Settings/public-api.js +2 -0
- package/dist/Settings/public-api.js.map +1 -0
- package/dist/WorkspacePicker/WorkspacePicker.d.ts +6 -1
- package/dist/WorkspacePicker/WorkspacePicker.d.ts.map +1 -1
- package/dist/WorkspacePicker/WorkspacePicker.js +16 -5
- package/dist/WorkspacePicker/WorkspacePicker.js.map +1 -1
- package/dist/chunk-3SAXDU2U.js +157 -0
- package/dist/chunk-3SAXDU2U.js.map +1 -0
- package/dist/chunk-4XXSWN3T.js +13 -0
- package/dist/chunk-4XXSWN3T.js.map +1 -0
- package/dist/chunk-5A4KEWKT.js +97 -0
- package/dist/chunk-5A4KEWKT.js.map +1 -0
- package/dist/chunk-AOVTHZU7.js +321 -0
- package/dist/chunk-AOVTHZU7.js.map +1 -0
- package/dist/chunk-DD4VFJQI.js +51 -0
- package/dist/chunk-DD4VFJQI.js.map +1 -0
- package/dist/chunk-GSNZSCIK.js +55 -0
- package/dist/chunk-GSNZSCIK.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/components/Dialog.d.ts +22 -0
- package/dist/components/Dialog.d.ts.map +1 -0
- package/dist/components/Dialog.js +33 -0
- package/dist/components/Dialog.js.map +1 -0
- package/dist/editor.d.ts +10 -0
- package/dist/editor.d.ts.map +1 -0
- package/dist/editor.js +70 -0
- package/dist/editor.js.map +1 -0
- package/dist/export/index.d.ts +87 -0
- package/dist/export/index.js +26 -0
- package/dist/export/index.js.map +1 -0
- package/dist/hooks/index.d.ts +2 -1
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +1 -1
- package/dist/hooks/index.js.map +1 -1
- package/dist/hooks/useDocumentSession.d.ts +16 -0
- package/dist/hooks/useDocumentSession.d.ts.map +1 -0
- package/dist/hooks/useDocumentSession.js +18 -0
- package/dist/hooks/useDocumentSession.js.map +1 -0
- package/dist/icons.d.ts +11 -6
- package/dist/icons.d.ts.map +1 -1
- package/dist/icons.js +21 -20
- package/dist/icons.js.map +1 -1
- package/dist/index.d.ts +8 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/preferences/theme.d.ts +18 -0
- package/dist/preferences/theme.d.ts.map +1 -0
- package/dist/preferences/theme.js +63 -0
- package/dist/preferences/theme.js.map +1 -0
- package/dist/provider-lease.d.ts +10 -0
- package/dist/provider-lease.d.ts.map +1 -0
- package/dist/provider-lease.js +42 -0
- package/dist/provider-lease.js.map +1 -0
- package/dist/run-export-ICTZITXC.js +10 -0
- package/dist/run-export-ICTZITXC.js.map +1 -0
- package/dist/settings/index.d.ts +28 -0
- package/dist/settings/index.js +12 -0
- package/dist/settings/index.js.map +1 -0
- package/package.json +24 -9
- package/src/AppMenu/AppMenu.tsx +169 -99
- package/src/AppMenu/index.ts +1 -1
- package/src/DocBlocksShell/DocBlocksShell.tsx +2157 -537
- package/src/DocBlocksShell/LazyEditorShell.ts +2 -0
- package/src/DocBlocksShell/UpdateAvailableNotice.tsx +60 -0
- package/src/DocBlocksShell/document-title.ts +19 -0
- package/src/DocBlocksShell/workspace-authority-barrier.ts +26 -0
- package/src/Export/DeferredExportToolbarControls.tsx +23 -0
- package/src/Export/ExportDialog.tsx +74 -20
- package/src/Export/ExportToolbarControls.tsx +238 -40
- package/src/Export/export-destination.ts +30 -0
- package/src/Export/index.ts +18 -4
- package/src/Export/public-api.ts +17 -0
- package/src/Export/run-export.ts +36 -11
- package/src/FileExplorer/FileExplorer.tsx +284 -38
- package/src/FileExplorer/FileTreeNode.tsx +100 -8
- package/src/FileExplorer/index.ts +1 -1
- package/src/FileExplorer/useFileTree.ts +190 -16
- package/src/Git/GitAuthGuidanceDialog.tsx +84 -0
- package/src/Git/GitBranchesDialog.tsx +144 -0
- package/src/Git/GitCloneDialog.tsx +200 -0
- package/src/Git/GitCommitDialog.tsx +119 -0
- package/src/Git/GitConflictsDialog.tsx +62 -0
- package/src/Git/GitContext.tsx +88 -0
- package/src/Git/GitDiffDialog.tsx +147 -0
- package/src/Git/GitDiffView.tsx +105 -0
- package/src/Git/GitHistoryDialog.tsx +195 -0
- package/src/Git/GitStatusBar.tsx +157 -0
- package/src/Git/GitToolbarControl.tsx +123 -0
- package/src/Git/GitUI.tsx +68 -0
- package/src/Git/commit-selection.ts +63 -0
- package/src/Git/git-clone-utils.ts +23 -0
- package/src/Git/git-status.ts +113 -0
- package/src/Git/useGit.ts +279 -0
- package/src/Git/useGitStatus.ts +78 -0
- package/src/Settings/Settings.tsx +118 -0
- package/src/Settings/public-api.ts +8 -0
- package/src/WorkspacePicker/WorkspacePicker.tsx +62 -8
- package/src/components/Dialog.tsx +81 -0
- package/src/editor.ts +72 -0
- package/src/hooks/index.ts +2 -1
- package/src/hooks/useDocumentSession.ts +37 -0
- package/src/icons.tsx +33 -55
- package/src/index.ts +35 -5
- package/src/preferences/theme.ts +67 -0
- package/src/provider-lease.ts +52 -0
- package/src/styles/docblocks.css +2174 -79
- package/src/types/pwa-dom.d.ts +31 -0
- 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/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/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/useAutoSave.ts +0 -116
- package/src/monaco-slim.ts +0 -20
package/src/Export/index.ts
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
|
-
export { ExportToolbarControls } from './
|
|
2
|
-
export type {
|
|
1
|
+
export { ExportToolbarControls } from './DeferredExportToolbarControls.js';
|
|
2
|
+
export type {
|
|
3
|
+
ExportDestinationAdapter,
|
|
4
|
+
ExportDestinationTarget,
|
|
5
|
+
ExportToolbarControlsProps,
|
|
6
|
+
} from './DeferredExportToolbarControls.js';
|
|
3
7
|
export { ExportDialog } from './ExportDialog.js';
|
|
4
|
-
export type { ExportDialogProps } from './ExportDialog.js';
|
|
5
|
-
export
|
|
8
|
+
export type { ExportDestinationControl, ExportDialogProps } from './ExportDialog.js';
|
|
9
|
+
export {
|
|
10
|
+
DEFAULT_OPTIONS,
|
|
11
|
+
FORMAT_EXTENSIONS,
|
|
12
|
+
FORMAT_LABELS,
|
|
13
|
+
loadLastExportOptions,
|
|
14
|
+
saveExportOptions,
|
|
15
|
+
} from './export-options.js';
|
|
16
|
+
export type { ExportFormat, ExportOptions, HtmlBundle, HtmlStyle } from './export-options.js';
|
|
17
|
+
export { buildExportFilename, runExport } from './run-export.js';
|
|
18
|
+
export { updateExportTargetExtension } from './export-destination.js';
|
|
19
|
+
export type { ExportBlobSaver } from './run-export.js';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Narrow public export pipeline for hosts that provide their own editor shell.
|
|
3
|
+
* Keep toolbar/video controls out of this entry so consumers such as the VS
|
|
4
|
+
* Code webview do not eagerly transform the video worker graph.
|
|
5
|
+
*/
|
|
6
|
+
export { ExportDialog } from './ExportDialog.js';
|
|
7
|
+
export type { ExportDestinationControl, ExportDialogProps } from './ExportDialog.js';
|
|
8
|
+
export {
|
|
9
|
+
DEFAULT_OPTIONS,
|
|
10
|
+
FORMAT_EXTENSIONS,
|
|
11
|
+
FORMAT_LABELS,
|
|
12
|
+
loadLastExportOptions,
|
|
13
|
+
saveExportOptions,
|
|
14
|
+
} from './export-options.js';
|
|
15
|
+
export type { ExportFormat, ExportOptions, HtmlBundle, HtmlStyle } from './export-options.js';
|
|
16
|
+
export { buildExportFilename, runExport } from './run-export.js';
|
|
17
|
+
export type { ExportBlobSaver } from './run-export.js';
|
package/src/Export/run-export.ts
CHANGED
|
@@ -13,6 +13,8 @@ import type { ContentContainer } from '@bendyline/squisq/storage';
|
|
|
13
13
|
import type { ExportOptions, ExportFormat } from './export-options.js';
|
|
14
14
|
import { FORMAT_EXTENSIONS } from './export-options.js';
|
|
15
15
|
|
|
16
|
+
export type ExportBlobSaver = (blob: Blob, filename: string) => Promise<void> | void;
|
|
17
|
+
|
|
16
18
|
const MIME_TYPES: Record<ExportFormat, string> = {
|
|
17
19
|
docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
18
20
|
pdf: 'application/pdf',
|
|
@@ -31,29 +33,51 @@ function downloadBlob(blob: Blob, filename: string): void {
|
|
|
31
33
|
URL.revokeObjectURL(url);
|
|
32
34
|
}
|
|
33
35
|
|
|
36
|
+
async function saveExportBlob(
|
|
37
|
+
blob: Blob,
|
|
38
|
+
filename: string,
|
|
39
|
+
saveBlob?: ExportBlobSaver,
|
|
40
|
+
): Promise<void> {
|
|
41
|
+
if (saveBlob) {
|
|
42
|
+
await saveBlob(blob, filename);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
downloadBlob(blob, filename);
|
|
46
|
+
}
|
|
47
|
+
|
|
34
48
|
/** Derive a filename from the selected file path and export format. */
|
|
35
49
|
function buildFilename(selectedFile: string | null, format: ExportFormat): string {
|
|
36
50
|
const base = selectedFile ? selectedFile.replace(/^\//, '').replace(/\.[^.]+$/, '') : 'document';
|
|
37
51
|
return base + FORMAT_EXTENSIONS[format];
|
|
38
52
|
}
|
|
39
53
|
|
|
54
|
+
export function buildExportFilename(selectedFile: string | null, options: ExportOptions): string {
|
|
55
|
+
const base = selectedFile ? selectedFile.replace(/^\//, '').replace(/\.[^.]+$/, '') : 'document';
|
|
56
|
+
if (options.format === 'html') {
|
|
57
|
+
if (options.includeLinkedDocs || options.htmlBundle === 'zip') return `${base}.zip`;
|
|
58
|
+
if (options.entryAsIndex) return 'index.html';
|
|
59
|
+
}
|
|
60
|
+
return base + FORMAT_EXTENSIONS[options.format];
|
|
61
|
+
}
|
|
62
|
+
|
|
40
63
|
/** Run the export and trigger a download. */
|
|
41
64
|
export async function runExport(
|
|
42
65
|
markdown: string,
|
|
43
66
|
selectedFile: string | null,
|
|
44
67
|
options: ExportOptions,
|
|
45
68
|
mediaContainer?: ContentContainer | null,
|
|
69
|
+
saveBlob?: ExportBlobSaver,
|
|
46
70
|
): Promise<void> {
|
|
47
71
|
const filename = buildFilename(selectedFile, options.format);
|
|
48
72
|
const themeId = options.themeId !== 'standard' ? options.themeId : undefined;
|
|
49
73
|
|
|
50
74
|
if (options.format === 'md') {
|
|
51
|
-
|
|
75
|
+
await saveExportBlob(new Blob([markdown], { type: MIME_TYPES.md }), filename, saveBlob);
|
|
52
76
|
return;
|
|
53
77
|
}
|
|
54
78
|
|
|
55
79
|
if (options.format === 'html') {
|
|
56
|
-
await runHtmlExport(markdown, filename, options, mediaContainer, selectedFile);
|
|
80
|
+
await runHtmlExport(markdown, filename, options, mediaContainer, selectedFile, saveBlob);
|
|
57
81
|
return;
|
|
58
82
|
}
|
|
59
83
|
|
|
@@ -63,7 +87,7 @@ export async function runExport(
|
|
|
63
87
|
const { markdownDocToDocx } = await import('@bendyline/squisq-formats/docx');
|
|
64
88
|
const images = mediaContainer ? await resolveImages(doc, mediaContainer) : undefined;
|
|
65
89
|
const buf = await markdownDocToDocx(doc, { themeId, images });
|
|
66
|
-
|
|
90
|
+
await saveExportBlob(new Blob([buf], { type: MIME_TYPES.docx }), filename, saveBlob);
|
|
67
91
|
return;
|
|
68
92
|
}
|
|
69
93
|
|
|
@@ -73,7 +97,7 @@ export async function runExport(
|
|
|
73
97
|
themeId,
|
|
74
98
|
pageSize: options.pageSize,
|
|
75
99
|
});
|
|
76
|
-
|
|
100
|
+
await saveExportBlob(new Blob([buf], { type: MIME_TYPES.pdf }), filename, saveBlob);
|
|
77
101
|
return;
|
|
78
102
|
}
|
|
79
103
|
|
|
@@ -91,7 +115,7 @@ export async function runExport(
|
|
|
91
115
|
enrichedDoc.themeId = themeId;
|
|
92
116
|
}
|
|
93
117
|
const buf = await docToPptx(enrichedDoc, { themeId });
|
|
94
|
-
|
|
118
|
+
await saveExportBlob(new Blob([buf], { type: MIME_TYPES.pptx }), filename, saveBlob);
|
|
95
119
|
return;
|
|
96
120
|
}
|
|
97
121
|
}
|
|
@@ -103,6 +127,7 @@ async function runHtmlExport(
|
|
|
103
127
|
options: ExportOptions,
|
|
104
128
|
mediaContainer?: ContentContainer | null,
|
|
105
129
|
selectedFile?: string | null,
|
|
130
|
+
saveBlob?: ExportBlobSaver,
|
|
106
131
|
): Promise<void> {
|
|
107
132
|
const baseName = htmlFilename.replace(/\.html$/, '');
|
|
108
133
|
const zipName = `${baseName}.zip`;
|
|
@@ -141,7 +166,7 @@ async function runHtmlExport(
|
|
|
141
166
|
mode: 'static',
|
|
142
167
|
entryAsIndex: options.entryAsIndex,
|
|
143
168
|
});
|
|
144
|
-
|
|
169
|
+
await saveExportBlob(blob, zipName, saveBlob);
|
|
145
170
|
return;
|
|
146
171
|
}
|
|
147
172
|
const { markdownDocsToPlainHtmlBundle } = await import('@bendyline/squisq-formats/html');
|
|
@@ -153,7 +178,7 @@ async function runHtmlExport(
|
|
|
153
178
|
themeId,
|
|
154
179
|
entryAsIndex: options.entryAsIndex,
|
|
155
180
|
});
|
|
156
|
-
|
|
181
|
+
await saveExportBlob(blob, zipName, saveBlob);
|
|
157
182
|
return;
|
|
158
183
|
}
|
|
159
184
|
|
|
@@ -179,7 +204,7 @@ async function runHtmlExport(
|
|
|
179
204
|
mode: 'static',
|
|
180
205
|
title: baseName,
|
|
181
206
|
});
|
|
182
|
-
|
|
207
|
+
await saveExportBlob(blob, zipName, saveBlob);
|
|
183
208
|
return;
|
|
184
209
|
}
|
|
185
210
|
|
|
@@ -189,7 +214,7 @@ async function runHtmlExport(
|
|
|
189
214
|
mode: 'static',
|
|
190
215
|
title: baseName,
|
|
191
216
|
});
|
|
192
|
-
|
|
217
|
+
await saveExportBlob(new Blob([html], { type: MIME_TYPES.html }), singleHtmlFilename, saveBlob);
|
|
193
218
|
return;
|
|
194
219
|
}
|
|
195
220
|
|
|
@@ -216,7 +241,7 @@ async function runHtmlExport(
|
|
|
216
241
|
await container.writeFile(cleanPath, new Uint8Array(data));
|
|
217
242
|
}
|
|
218
243
|
const blob = await containerToZip(container);
|
|
219
|
-
|
|
244
|
+
await saveExportBlob(blob, zipName, saveBlob);
|
|
220
245
|
return;
|
|
221
246
|
}
|
|
222
247
|
|
|
@@ -235,7 +260,7 @@ async function runHtmlExport(
|
|
|
235
260
|
images: inlineMap.size > 0 ? inlineMap : undefined,
|
|
236
261
|
themeId,
|
|
237
262
|
});
|
|
238
|
-
|
|
263
|
+
await saveExportBlob(new Blob([html], { type: MIME_TYPES.html }), singleHtmlFilename, saveBlob);
|
|
239
264
|
}
|
|
240
265
|
|
|
241
266
|
const IMAGE_MIME: Record<string, string> = {
|
|
@@ -5,24 +5,94 @@
|
|
|
5
5
|
* for creating new files/folders.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { useCallback, useState } from 'react';
|
|
9
|
-
import
|
|
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';
|
|
10
15
|
import { useFileTree } from './useFileTree.js';
|
|
11
|
-
import {
|
|
12
|
-
|
|
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';
|
|
13
24
|
|
|
14
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
|
+
}
|
|
15
84
|
|
|
16
85
|
/**
|
|
17
|
-
* Hide
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
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.
|
|
21
91
|
*/
|
|
22
92
|
function isHiddenEntry(entry: FileSystemEntry): boolean {
|
|
23
|
-
if (entry.kind !== 'directory') return false;
|
|
24
93
|
const name = entry.path.replace(/^\/+/, '').split('/').pop() ?? '';
|
|
25
|
-
|
|
94
|
+
if (name.startsWith('.')) return true;
|
|
95
|
+
return entry.kind === 'directory' && name.endsWith('_files');
|
|
26
96
|
}
|
|
27
97
|
|
|
28
98
|
function filterVisible(entries: FileSystemEntry[]): FileSystemEntry[] {
|
|
@@ -32,10 +102,17 @@ function filterVisible(entries: FileSystemEntry[]): FileSystemEntry[] {
|
|
|
32
102
|
export interface FileExplorerProps {
|
|
33
103
|
/** The filesystem to display. */
|
|
34
104
|
provider: FileSystemProvider | null;
|
|
105
|
+
/** Active document to select and reveal by expanding all ancestor folders. */
|
|
106
|
+
activeFilePath?: string | null;
|
|
35
107
|
/** Called when any entry is selected (file or directory). */
|
|
36
108
|
onSelect?: (path: string, kind: 'file' | 'directory') => void;
|
|
37
|
-
/**
|
|
38
|
-
|
|
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;
|
|
39
116
|
/** Called when supported files are dropped onto the explorer. */
|
|
40
117
|
onImportFiles?: (files: File[]) => void;
|
|
41
118
|
/** Optional className for the root element. */
|
|
@@ -44,7 +121,9 @@ export interface FileExplorerProps {
|
|
|
44
121
|
|
|
45
122
|
export function FileExplorer({
|
|
46
123
|
provider,
|
|
124
|
+
activeFilePath,
|
|
47
125
|
onSelect,
|
|
126
|
+
onTreeMutation,
|
|
48
127
|
onTreeChange,
|
|
49
128
|
onImportFiles,
|
|
50
129
|
className,
|
|
@@ -53,11 +132,21 @@ export function FileExplorer({
|
|
|
53
132
|
const { childEntries } = tree as typeof tree & {
|
|
54
133
|
childEntries: Map<string, FileSystemEntry[]>;
|
|
55
134
|
};
|
|
135
|
+
const { reveal } = tree;
|
|
136
|
+
// Null on surfaces without git (site, tests) — everything degrades.
|
|
137
|
+
const git = useGitContext();
|
|
56
138
|
|
|
57
139
|
const [newItemName, setNewItemName] = useState('');
|
|
58
140
|
const [newItemType, setNewItemType] = useState<'file' | 'directory' | null>(null);
|
|
59
141
|
const [dragOver, setDragOver] = useState(false);
|
|
60
|
-
const
|
|
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]);
|
|
61
150
|
|
|
62
151
|
const hasSupported = useCallback((dt: DataTransfer): boolean => {
|
|
63
152
|
for (const item of Array.from(dt.items)) {
|
|
@@ -73,6 +162,7 @@ export function FileExplorer({
|
|
|
73
162
|
|
|
74
163
|
const handleDragEnter = useCallback(
|
|
75
164
|
(e: React.DragEvent) => {
|
|
165
|
+
if (isInternalDrag(e.dataTransfer)) return;
|
|
76
166
|
e.preventDefault();
|
|
77
167
|
dragCounter.current += 1;
|
|
78
168
|
if (hasSupported(e.dataTransfer)) setDragOver(true);
|
|
@@ -81,12 +171,14 @@ export function FileExplorer({
|
|
|
81
171
|
);
|
|
82
172
|
|
|
83
173
|
const handleDragOver = useCallback((e: React.DragEvent) => {
|
|
174
|
+
if (isInternalDrag(e.dataTransfer)) return;
|
|
84
175
|
e.preventDefault();
|
|
85
176
|
e.dataTransfer.dropEffect = 'copy';
|
|
86
177
|
}, []);
|
|
87
178
|
|
|
88
179
|
const handleDragLeave = useCallback(
|
|
89
180
|
(e: React.DragEvent) => {
|
|
181
|
+
if (isInternalDrag(e.dataTransfer)) return;
|
|
90
182
|
e.preventDefault();
|
|
91
183
|
dragCounter.current -= 1;
|
|
92
184
|
if (dragCounter.current <= 0) {
|
|
@@ -99,6 +191,7 @@ export function FileExplorer({
|
|
|
99
191
|
|
|
100
192
|
const handleDrop = useCallback(
|
|
101
193
|
(e: React.DragEvent) => {
|
|
194
|
+
if (isInternalDrag(e.dataTransfer)) return;
|
|
102
195
|
e.preventDefault();
|
|
103
196
|
dragCounter.current = 0;
|
|
104
197
|
setDragOver(false);
|
|
@@ -108,7 +201,7 @@ export function FileExplorer({
|
|
|
108
201
|
});
|
|
109
202
|
if (supported.length > 0) onImportFiles?.(supported);
|
|
110
203
|
},
|
|
111
|
-
[onImportFiles
|
|
204
|
+
[onImportFiles],
|
|
112
205
|
);
|
|
113
206
|
|
|
114
207
|
const handleSelect = useCallback(
|
|
@@ -141,31 +234,155 @@ export function FileExplorer({
|
|
|
141
234
|
// Scope to currently selected folder (or root)
|
|
142
235
|
const prefix =
|
|
143
236
|
tree.selectedKind === 'directory' && tree.selectedPath ? `${tree.selectedPath}/` : '';
|
|
237
|
+
let createdPath = `${prefix}${name}`;
|
|
144
238
|
if (newItemType === 'file') {
|
|
145
239
|
const filename = name.endsWith('.md') ? name : `${name}.md`;
|
|
146
|
-
|
|
240
|
+
createdPath = `${prefix}${filename}`;
|
|
241
|
+
await tree.createFile(createdPath, '');
|
|
147
242
|
} else if (newItemType === 'directory') {
|
|
148
|
-
await tree.createDirectory(
|
|
243
|
+
await tree.createDirectory(createdPath);
|
|
149
244
|
}
|
|
150
245
|
setNewItemName('');
|
|
151
246
|
setNewItemType(null);
|
|
152
|
-
onTreeChange?.();
|
|
247
|
+
onTreeChange?.({ type: 'create', path: createdPath });
|
|
153
248
|
}, [newItemName, newItemType, tree, onTreeChange]);
|
|
154
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
|
+
|
|
155
259
|
const handleDelete = useCallback(
|
|
156
|
-
async (path: string) => {
|
|
157
|
-
|
|
158
|
-
|
|
260
|
+
async (path: string, kind: 'file' | 'directory') => {
|
|
261
|
+
const change: FileTreeChange = { type: 'delete', path, kind };
|
|
262
|
+
await runTreeMutation(change, () => tree.deleteEntry(path));
|
|
159
263
|
},
|
|
160
|
-
[tree,
|
|
264
|
+
[tree, runTreeMutation],
|
|
161
265
|
);
|
|
162
266
|
|
|
163
267
|
const handleRename = useCallback(
|
|
164
|
-
async (oldPath: string, newPath: string) => {
|
|
165
|
-
|
|
166
|
-
|
|
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
|
+
}
|
|
167
322
|
},
|
|
168
|
-
[
|
|
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],
|
|
169
386
|
);
|
|
170
387
|
|
|
171
388
|
const renderEntries = useCallback(
|
|
@@ -175,20 +392,46 @@ export function FileExplorer({
|
|
|
175
392
|
key={entry.path}
|
|
176
393
|
entry={entry}
|
|
177
394
|
depth={depth}
|
|
178
|
-
expanded={tree.expanded
|
|
179
|
-
selected={
|
|
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)}
|
|
180
402
|
onToggle={tree.toggleExpand}
|
|
181
403
|
onSelect={handleSelect}
|
|
182
404
|
onDelete={handleDelete}
|
|
183
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}
|
|
184
413
|
renderChildren={(dirPath: string) => {
|
|
185
|
-
const children = childEntries
|
|
414
|
+
const children = getEquivalentPathValue(childEntries, dirPath) ?? [];
|
|
186
415
|
return renderEntries(children, depth + 1);
|
|
187
416
|
}}
|
|
188
417
|
/>
|
|
189
418
|
));
|
|
190
419
|
},
|
|
191
|
-
[
|
|
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
|
+
],
|
|
192
435
|
);
|
|
193
436
|
|
|
194
437
|
return (
|
|
@@ -219,14 +462,6 @@ export function FileExplorer({
|
|
|
219
462
|
>
|
|
220
463
|
<NewFolderIcon />
|
|
221
464
|
</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
465
|
</div>
|
|
231
466
|
</div>
|
|
232
467
|
|
|
@@ -261,6 +496,12 @@ export function FileExplorer({
|
|
|
261
496
|
</div>
|
|
262
497
|
)}
|
|
263
498
|
|
|
499
|
+
{moveError && (
|
|
500
|
+
<div className="db-tree-error" role="alert">
|
|
501
|
+
{moveError}
|
|
502
|
+
</div>
|
|
503
|
+
)}
|
|
504
|
+
|
|
264
505
|
{/* Tree — role="tree" only when there are real treeitem children; the
|
|
265
506
|
loading/empty states get a status role so axe doesn't flag the
|
|
266
507
|
tree as missing required children. */}
|
|
@@ -273,7 +514,12 @@ export function FileExplorer({
|
|
|
273
514
|
<div className="db-tree-empty">No files yet</div>
|
|
274
515
|
</div>
|
|
275
516
|
) : (
|
|
276
|
-
<div
|
|
517
|
+
<div
|
|
518
|
+
className={`db-tree ${dropTarget === '' ? 'db-tree--drop-target' : ''}`}
|
|
519
|
+
role="tree"
|
|
520
|
+
onDragOver={handleRootDragOver}
|
|
521
|
+
onDrop={handleRootDrop}
|
|
522
|
+
>
|
|
277
523
|
{renderEntries(tree.entries, 0)}
|
|
278
524
|
</div>
|
|
279
525
|
)}
|