@bendyline/docblocks-react 1.1.1 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +50 -15
- 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 +17 -4
- package/dist/DocBlocksShell/DocBlocksShell.d.ts.map +1 -1
- package/dist/DocBlocksShell/DocBlocksShell.js +1710 -299
- 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 +39 -20
- 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 +161 -26
- 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 +58 -25
- package/dist/Export/run-export.js.map +1 -1
- package/dist/Export/transform-summaries.d.ts +7 -0
- package/dist/Export/transform-summaries.d.ts.map +1 -0
- package/dist/Export/transform-summaries.js +6 -0
- package/dist/Export/transform-summaries.js.map +1 -0
- 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 +27 -10
- package/src/AppMenu/AppMenu.tsx +169 -99
- package/src/AppMenu/index.ts +1 -1
- package/src/DocBlocksShell/DocBlocksShell.tsx +2183 -516
- 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 +102 -27
- package/src/Export/ExportToolbarControls.tsx +343 -57
- 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 +66 -29
- package/src/Export/transform-summaries.ts +14 -0
- 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 +2247 -77
- 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/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
|
@@ -4,7 +4,54 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { useState, useCallback, useEffect, useRef } from 'react';
|
|
7
|
-
import
|
|
7
|
+
import {
|
|
8
|
+
getFileSystemProviderV2,
|
|
9
|
+
moveFileSystemEntry,
|
|
10
|
+
parseWorkspacePath,
|
|
11
|
+
type FileSystemProvider,
|
|
12
|
+
type FileSystemEntry,
|
|
13
|
+
} from '@bendyline/docblocks/filesystem';
|
|
14
|
+
|
|
15
|
+
function normalisePath(path: string): string {
|
|
16
|
+
return parseWorkspacePath(path);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function relocatePath(path: string, oldPath: string, newPath: string): string {
|
|
20
|
+
const current = normalisePath(path);
|
|
21
|
+
const oldNormalised = normalisePath(oldPath);
|
|
22
|
+
if (current !== oldNormalised && !current.startsWith(`${oldNormalised}/`)) return path;
|
|
23
|
+
const suffix = current.slice(oldNormalised.length);
|
|
24
|
+
return `${newPath.replace(/\/+$/, '')}${suffix}`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function ancestorPaths(path: string): string[] {
|
|
28
|
+
const canonical = normalisePath(path);
|
|
29
|
+
if (!canonical) return [];
|
|
30
|
+
const segments = canonical.split('/');
|
|
31
|
+
const prefix = /^[\\/]/u.test(path) ? '/' : '';
|
|
32
|
+
return segments
|
|
33
|
+
.slice(0, -1)
|
|
34
|
+
.map((_, index) => `${prefix}${segments.slice(0, index + 1).join('/')}`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function equivalentPathKeys(path: string): string[] {
|
|
38
|
+
const canonical = normalisePath(path);
|
|
39
|
+
return canonical ? [path, canonical, `/${canonical}`] : [path, canonical];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function hasEquivalentPath(paths: ReadonlySet<string>, path: string): boolean {
|
|
43
|
+
return equivalentPathKeys(path).some((candidate) => paths.has(candidate));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function readProviderDirectory(
|
|
47
|
+
provider: FileSystemProvider,
|
|
48
|
+
path: string,
|
|
49
|
+
): Promise<FileSystemEntry[]> {
|
|
50
|
+
const providerV2 = getFileSystemProviderV2(provider);
|
|
51
|
+
if (!providerV2) return provider.readDirectory(path);
|
|
52
|
+
const entries = await providerV2.readDirectory(parseWorkspacePath(path));
|
|
53
|
+
return entries.map((entry) => ({ kind: entry.kind, name: entry.name, path: entry.path }));
|
|
54
|
+
}
|
|
8
55
|
|
|
9
56
|
export interface FileTreeState {
|
|
10
57
|
/** Flat list of entries for the current directory view. */
|
|
@@ -24,14 +71,16 @@ export interface FileTreeActions {
|
|
|
24
71
|
toggleExpand: (path: string) => void;
|
|
25
72
|
/** Select a file or directory. */
|
|
26
73
|
select: (path: string, kind: 'file' | 'directory') => void;
|
|
74
|
+
/** Select an entry and expand each directory needed to reveal it. */
|
|
75
|
+
reveal: (path: string, kind: 'file' | 'directory') => void;
|
|
27
76
|
/** Create a new file with optional initial content. */
|
|
28
77
|
createFile: (path: string, content?: string) => Promise<void>;
|
|
29
78
|
/** Create a new directory. */
|
|
30
79
|
createDirectory: (path: string) => Promise<void>;
|
|
31
80
|
/** Delete a file or directory. */
|
|
32
81
|
deleteEntry: (path: string) => Promise<void>;
|
|
33
|
-
/** Rename a file or directory. */
|
|
34
|
-
renameEntry: (oldPath: string, newPath: string) => Promise<void>;
|
|
82
|
+
/** Rename or move a file or directory, including a markdown companion folder. */
|
|
83
|
+
renameEntry: (oldPath: string, newPath: string, kind?: 'file' | 'directory') => Promise<void>;
|
|
35
84
|
/** Force refresh the tree. */
|
|
36
85
|
refresh: () => Promise<void>;
|
|
37
86
|
}
|
|
@@ -56,7 +105,7 @@ export function useFileTree(provider: FileSystemProvider | null): FileTreeState
|
|
|
56
105
|
}
|
|
57
106
|
setLoading(true);
|
|
58
107
|
try {
|
|
59
|
-
const root = await providerRef.current
|
|
108
|
+
const root = await readProviderDirectory(providerRef.current, '');
|
|
60
109
|
setEntries(root);
|
|
61
110
|
} finally {
|
|
62
111
|
setLoading(false);
|
|
@@ -65,7 +114,7 @@ export function useFileTree(provider: FileSystemProvider | null): FileTreeState
|
|
|
65
114
|
|
|
66
115
|
const loadChildren = useCallback(async (dirPath: string) => {
|
|
67
116
|
if (!providerRef.current) return;
|
|
68
|
-
const children = await providerRef.current
|
|
117
|
+
const children = await readProviderDirectory(providerRef.current, dirPath);
|
|
69
118
|
setChildEntries((prev) => {
|
|
70
119
|
const next = new Map(prev);
|
|
71
120
|
next.set(dirPath, children);
|
|
@@ -86,8 +135,8 @@ export function useFileTree(provider: FileSystemProvider | null): FileTreeState
|
|
|
86
135
|
(path: string) => {
|
|
87
136
|
setExpanded((prev) => {
|
|
88
137
|
const next = new Set(prev);
|
|
89
|
-
if (next
|
|
90
|
-
next.delete(
|
|
138
|
+
if (hasEquivalentPath(next, path)) {
|
|
139
|
+
for (const candidate of equivalentPathKeys(path)) next.delete(candidate);
|
|
91
140
|
} else {
|
|
92
141
|
next.add(path);
|
|
93
142
|
// Load children when expanding
|
|
@@ -104,6 +153,25 @@ export function useFileTree(provider: FileSystemProvider | null): FileTreeState
|
|
|
104
153
|
setSelectedKind(kind);
|
|
105
154
|
}, []);
|
|
106
155
|
|
|
156
|
+
const reveal = useCallback(
|
|
157
|
+
(path: string, kind: 'file' | 'directory') => {
|
|
158
|
+
const ancestors = ancestorPaths(path);
|
|
159
|
+
setSelectedPath(path);
|
|
160
|
+
setSelectedKind(kind);
|
|
161
|
+
setExpanded((prev) => {
|
|
162
|
+
const next = new Set(prev);
|
|
163
|
+
for (const ancestor of ancestors) {
|
|
164
|
+
if (!hasEquivalentPath(next, ancestor)) next.add(ancestor);
|
|
165
|
+
}
|
|
166
|
+
return next;
|
|
167
|
+
});
|
|
168
|
+
for (const ancestor of ancestors) {
|
|
169
|
+
void loadChildren(ancestor);
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
[loadChildren],
|
|
173
|
+
);
|
|
174
|
+
|
|
107
175
|
const refresh = useCallback(async () => {
|
|
108
176
|
await loadRoot();
|
|
109
177
|
// Reload all expanded directories
|
|
@@ -113,10 +181,82 @@ export function useFileTree(provider: FileSystemProvider | null): FileTreeState
|
|
|
113
181
|
}
|
|
114
182
|
}, [loadRoot, loadChildren, expanded]);
|
|
115
183
|
|
|
184
|
+
const refreshRef = useRef(refresh);
|
|
185
|
+
refreshRef.current = refresh;
|
|
186
|
+
|
|
187
|
+
// Keep the explorer synchronized with provider changes. Watch-capable
|
|
188
|
+
// providers (including Electron's chokidar-backed provider) update in real
|
|
189
|
+
// time. Calls are serialized and coalesced so event bursts cannot publish
|
|
190
|
+
// directory reads out of order.
|
|
191
|
+
useEffect(() => {
|
|
192
|
+
if (!provider) return;
|
|
193
|
+
const providerV2 = getFileSystemProviderV2(provider);
|
|
194
|
+
if (!providerV2?.capabilities.watch) return;
|
|
195
|
+
|
|
196
|
+
let disposed = false;
|
|
197
|
+
let refreshing = false;
|
|
198
|
+
let refreshAgain = false;
|
|
199
|
+
|
|
200
|
+
const requestRefresh = () => {
|
|
201
|
+
if (disposed) return;
|
|
202
|
+
if (refreshing) {
|
|
203
|
+
refreshAgain = true;
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
refreshing = true;
|
|
207
|
+
void (async () => {
|
|
208
|
+
try {
|
|
209
|
+
do {
|
|
210
|
+
refreshAgain = false;
|
|
211
|
+
await refreshRef.current();
|
|
212
|
+
} while (refreshAgain && !disposed);
|
|
213
|
+
} catch {
|
|
214
|
+
// A later watcher event or window resume will retry the read.
|
|
215
|
+
} finally {
|
|
216
|
+
refreshing = false;
|
|
217
|
+
if (refreshAgain && !disposed) requestRefresh();
|
|
218
|
+
}
|
|
219
|
+
})();
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
const subscription = providerV2.watch(requestRefresh, { onError: requestRefresh });
|
|
223
|
+
void subscription.ready.catch(() => undefined);
|
|
224
|
+
return () => {
|
|
225
|
+
disposed = true;
|
|
226
|
+
void subscription.dispose();
|
|
227
|
+
};
|
|
228
|
+
}, [provider]);
|
|
229
|
+
|
|
230
|
+
// File System Access and IndexedDB do not expose a dependable external
|
|
231
|
+
// watcher. Refreshing on resume catches changes made while this surface was
|
|
232
|
+
// in the background without continuously polling the filesystem.
|
|
233
|
+
useEffect(() => {
|
|
234
|
+
if (!provider) return;
|
|
235
|
+
const refreshOnFocus = () => {
|
|
236
|
+
void refreshRef.current().catch(() => undefined);
|
|
237
|
+
};
|
|
238
|
+
const refreshOnVisibility = () => {
|
|
239
|
+
if (document.visibilityState === 'visible') refreshOnFocus();
|
|
240
|
+
};
|
|
241
|
+
window.addEventListener('focus', refreshOnFocus);
|
|
242
|
+
document.addEventListener('visibilitychange', refreshOnVisibility);
|
|
243
|
+
return () => {
|
|
244
|
+
window.removeEventListener('focus', refreshOnFocus);
|
|
245
|
+
document.removeEventListener('visibilitychange', refreshOnVisibility);
|
|
246
|
+
};
|
|
247
|
+
}, [provider]);
|
|
248
|
+
|
|
116
249
|
const createFile = useCallback(
|
|
117
250
|
async (path: string, content = '') => {
|
|
118
251
|
if (!providerRef.current) return;
|
|
119
|
-
|
|
252
|
+
const providerV2 = getFileSystemProviderV2(providerRef.current);
|
|
253
|
+
if (providerV2) {
|
|
254
|
+
await providerV2.writeFile(parseWorkspacePath(path), new TextEncoder().encode(content), {
|
|
255
|
+
mode: 'create',
|
|
256
|
+
});
|
|
257
|
+
} else {
|
|
258
|
+
await providerRef.current.writeFile(path, content);
|
|
259
|
+
}
|
|
120
260
|
await refresh();
|
|
121
261
|
},
|
|
122
262
|
[refresh],
|
|
@@ -125,7 +265,12 @@ export function useFileTree(provider: FileSystemProvider | null): FileTreeState
|
|
|
125
265
|
const createDirectory = useCallback(
|
|
126
266
|
async (path: string) => {
|
|
127
267
|
if (!providerRef.current) return;
|
|
128
|
-
|
|
268
|
+
const providerV2 = getFileSystemProviderV2(providerRef.current);
|
|
269
|
+
if (providerV2) {
|
|
270
|
+
await providerV2.createDirectory(parseWorkspacePath(path), { mode: 'create' });
|
|
271
|
+
} else {
|
|
272
|
+
await providerRef.current.createDirectory(path);
|
|
273
|
+
}
|
|
129
274
|
await refresh();
|
|
130
275
|
},
|
|
131
276
|
[refresh],
|
|
@@ -134,7 +279,20 @@ export function useFileTree(provider: FileSystemProvider | null): FileTreeState
|
|
|
134
279
|
const deleteEntry = useCallback(
|
|
135
280
|
async (path: string) => {
|
|
136
281
|
if (!providerRef.current) return;
|
|
137
|
-
|
|
282
|
+
const providerV2 = getFileSystemProviderV2(providerRef.current);
|
|
283
|
+
if (providerV2) {
|
|
284
|
+
const canonical = parseWorkspacePath(path);
|
|
285
|
+
const entry = await providerV2.stat(canonical);
|
|
286
|
+
if (entry) {
|
|
287
|
+
await providerV2.remove(canonical, {
|
|
288
|
+
recursive: entry.kind === 'directory',
|
|
289
|
+
missing: 'error',
|
|
290
|
+
expectedVersion: entry.version,
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
} else {
|
|
294
|
+
await providerRef.current.delete(path);
|
|
295
|
+
}
|
|
138
296
|
if (selectedPath === path) {
|
|
139
297
|
setSelectedPath(null);
|
|
140
298
|
setSelectedKind(null);
|
|
@@ -145,15 +303,30 @@ export function useFileTree(provider: FileSystemProvider | null): FileTreeState
|
|
|
145
303
|
);
|
|
146
304
|
|
|
147
305
|
const renameEntry = useCallback(
|
|
148
|
-
async (oldPath: string, newPath: string) => {
|
|
306
|
+
async (oldPath: string, newPath: string, kind?: 'file' | 'directory') => {
|
|
149
307
|
if (!providerRef.current) return;
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
308
|
+
const oldNormalised = normalisePath(oldPath);
|
|
309
|
+
const knownEntry = [...entries, ...childEntries.values()]
|
|
310
|
+
.flat()
|
|
311
|
+
.find((entry) => normalisePath(entry.path) === oldNormalised);
|
|
312
|
+
const entryKind = kind ?? knownEntry?.kind ?? 'file';
|
|
313
|
+
await moveFileSystemEntry(providerRef.current, oldPath, newPath, entryKind);
|
|
314
|
+
|
|
315
|
+
if (selectedPath) {
|
|
316
|
+
setSelectedPath(relocatePath(selectedPath, oldPath, newPath));
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const nextExpanded = new Set(
|
|
320
|
+
[...expanded].map((path) => relocatePath(path, oldPath, newPath)),
|
|
321
|
+
);
|
|
322
|
+
setExpanded(nextExpanded);
|
|
323
|
+
setChildEntries(new Map());
|
|
324
|
+
await loadRoot();
|
|
325
|
+
for (const dirPath of nextExpanded) {
|
|
326
|
+
await loadChildren(dirPath);
|
|
153
327
|
}
|
|
154
|
-
await refresh();
|
|
155
328
|
},
|
|
156
|
-
[
|
|
329
|
+
[selectedPath, entries, childEntries, expanded, loadRoot, loadChildren],
|
|
157
330
|
);
|
|
158
331
|
|
|
159
332
|
// Merge root entries with child entries for a flat tree representation
|
|
@@ -167,6 +340,7 @@ export function useFileTree(provider: FileSystemProvider | null): FileTreeState
|
|
|
167
340
|
loading,
|
|
168
341
|
toggleExpand,
|
|
169
342
|
select,
|
|
343
|
+
reveal,
|
|
170
344
|
createFile,
|
|
171
345
|
createDirectory,
|
|
172
346
|
deleteEntry,
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitAuthGuidanceDialog — shown when a git operation fails to authenticate.
|
|
3
|
+
*
|
|
4
|
+
* DocBlocks deliberately never collects or stores credentials, so this
|
|
5
|
+
* dialog only explains the three supported sign-in routes (credential
|
|
6
|
+
* helper, GitHub CLI, SSH key). The copy is exported as a standalone
|
|
7
|
+
* component so the clone dialog can embed it after an auth failure.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { maybeGetDocBlocksHost } from '@bendyline/docblocks/host';
|
|
11
|
+
import { Dialog } from '../components/Dialog.js';
|
|
12
|
+
|
|
13
|
+
const LEARN_MORE_URL =
|
|
14
|
+
'https://docs.github.com/en/get-started/getting-started-with-git/caching-your-github-credentials-in-git';
|
|
15
|
+
|
|
16
|
+
const OPERATION_LABELS: Record<'push' | 'pull' | 'fetch' | 'clone' | 'pr', string> = {
|
|
17
|
+
push: 'push',
|
|
18
|
+
pull: 'pull',
|
|
19
|
+
fetch: 'fetch',
|
|
20
|
+
clone: 'clone',
|
|
21
|
+
pr: 'create the pull request',
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/** The credential guidance copy, reusable outside this dialog (clone flow). */
|
|
25
|
+
export function GitCredentialGuidance({ detail }: { detail?: string }) {
|
|
26
|
+
return (
|
|
27
|
+
<>
|
|
28
|
+
<p className="db-settings-hint">
|
|
29
|
+
DocBlocks never stores Git credentials. Sign in with one of these, then try again:
|
|
30
|
+
</p>
|
|
31
|
+
<ul className="db-git-hint">
|
|
32
|
+
<li>
|
|
33
|
+
A git credential helper (<code>git config credential.helper</code>)
|
|
34
|
+
</li>
|
|
35
|
+
<li>
|
|
36
|
+
The GitHub CLI (<code>gh auth login</code>)
|
|
37
|
+
</li>
|
|
38
|
+
<li>An SSH key registered with your remote</li>
|
|
39
|
+
</ul>
|
|
40
|
+
{detail && (
|
|
41
|
+
<details>
|
|
42
|
+
<summary>Details</summary>
|
|
43
|
+
<p className="db-git-hint">{detail}</p>
|
|
44
|
+
</details>
|
|
45
|
+
)}
|
|
46
|
+
</>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function GitAuthGuidanceDialog({
|
|
51
|
+
operation,
|
|
52
|
+
detail,
|
|
53
|
+
onClose,
|
|
54
|
+
}: {
|
|
55
|
+
operation: 'push' | 'pull' | 'fetch' | 'clone' | 'pr';
|
|
56
|
+
detail?: string;
|
|
57
|
+
onClose: () => void;
|
|
58
|
+
}) {
|
|
59
|
+
return (
|
|
60
|
+
<Dialog
|
|
61
|
+
title="Git needs credentials"
|
|
62
|
+
onClose={onClose}
|
|
63
|
+
footer={
|
|
64
|
+
<>
|
|
65
|
+
<button
|
|
66
|
+
type="button"
|
|
67
|
+
className="db-git-secondary-btn"
|
|
68
|
+
onClick={() => void maybeGetDocBlocksHost()?.shell.openExternal(LEARN_MORE_URL)}
|
|
69
|
+
>
|
|
70
|
+
Learn more
|
|
71
|
+
</button>
|
|
72
|
+
<button type="button" className="db-git-primary-btn" onClick={onClose}>
|
|
73
|
+
Close
|
|
74
|
+
</button>
|
|
75
|
+
</>
|
|
76
|
+
}
|
|
77
|
+
>
|
|
78
|
+
<p className="db-settings-hint">
|
|
79
|
+
Git could not authenticate with the remote while trying to {OPERATION_LABELS[operation]}.
|
|
80
|
+
</p>
|
|
81
|
+
<GitCredentialGuidance detail={detail} />
|
|
82
|
+
</Dialog>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitBranchesDialog — switch between local branches or create a new one.
|
|
3
|
+
*
|
|
4
|
+
* Loads the branch list once from the host git API on mount. Remote
|
|
5
|
+
* branches are display-only in v1 (a collapsed disclosure); switching and
|
|
6
|
+
* creating go through the GitValue actions so errors (e.g. uncommitted
|
|
7
|
+
* changes blocking a checkout) surface inline via lastResult.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { useEffect, useRef, useState } from 'react';
|
|
11
|
+
import type { GitBranchInfo } from '@bendyline/docblocks/host';
|
|
12
|
+
import { Dialog } from '../components/Dialog.js';
|
|
13
|
+
import { useGitContext } from './GitContext.js';
|
|
14
|
+
|
|
15
|
+
export function GitBranchesDialog({
|
|
16
|
+
createFocus,
|
|
17
|
+
onClose,
|
|
18
|
+
}: {
|
|
19
|
+
createFocus?: boolean;
|
|
20
|
+
onClose: () => void;
|
|
21
|
+
}) {
|
|
22
|
+
const git = useGitContext();
|
|
23
|
+
const gitApi = git?.gitApi ?? null;
|
|
24
|
+
const repositoryId = git?.repositoryId ?? null;
|
|
25
|
+
|
|
26
|
+
const [branches, setBranches] = useState<GitBranchInfo[] | null>(null);
|
|
27
|
+
const [loadError, setLoadError] = useState<string | null>(null);
|
|
28
|
+
const [newName, setNewName] = useState('');
|
|
29
|
+
const createInputRef = useRef<HTMLInputElement>(null);
|
|
30
|
+
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
if (!gitApi || !repositoryId) return;
|
|
33
|
+
let cancelled = false;
|
|
34
|
+
void gitApi.listBranches(repositoryId).then((result) => {
|
|
35
|
+
if (cancelled) return;
|
|
36
|
+
if (result.ok) setBranches(result.value);
|
|
37
|
+
else setLoadError(result.error.message || 'Could not list branches');
|
|
38
|
+
});
|
|
39
|
+
return () => {
|
|
40
|
+
cancelled = true;
|
|
41
|
+
};
|
|
42
|
+
}, [gitApi, repositoryId]);
|
|
43
|
+
|
|
44
|
+
if (!git) return null;
|
|
45
|
+
|
|
46
|
+
const locals = branches?.filter((b) => b.kind === 'local') ?? [];
|
|
47
|
+
const remotes = branches?.filter((b) => b.kind === 'remote') ?? [];
|
|
48
|
+
const canCreate = newName.trim().length > 0 && git.busy === null;
|
|
49
|
+
|
|
50
|
+
const handleSwitch = async (name: string) => {
|
|
51
|
+
const ok = await git.switchBranch(name);
|
|
52
|
+
if (ok) onClose();
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const handleCreate = async () => {
|
|
56
|
+
if (!canCreate) return;
|
|
57
|
+
const ok = await git.createBranch(newName.trim());
|
|
58
|
+
if (ok) onClose();
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<Dialog
|
|
63
|
+
title="Branches"
|
|
64
|
+
onClose={onClose}
|
|
65
|
+
initialFocusRef={createFocus ? createInputRef : undefined}
|
|
66
|
+
>
|
|
67
|
+
{branches === null && loadError === null && <p className="db-git-hint">Loading branches…</p>}
|
|
68
|
+
{loadError !== null && (
|
|
69
|
+
<p className="db-git-form-error" role="alert">
|
|
70
|
+
{loadError}
|
|
71
|
+
</p>
|
|
72
|
+
)}
|
|
73
|
+
{branches !== null && (
|
|
74
|
+
<div className="db-git-branch-list">
|
|
75
|
+
{locals.map((branch) =>
|
|
76
|
+
branch.current ? (
|
|
77
|
+
<span
|
|
78
|
+
key={branch.name}
|
|
79
|
+
className="db-git-branch-row db-git-branch-row--current"
|
|
80
|
+
aria-current="true"
|
|
81
|
+
>
|
|
82
|
+
{branch.name}
|
|
83
|
+
</span>
|
|
84
|
+
) : (
|
|
85
|
+
<button
|
|
86
|
+
key={branch.name}
|
|
87
|
+
type="button"
|
|
88
|
+
className="db-git-branch-row"
|
|
89
|
+
disabled={git.busy !== null}
|
|
90
|
+
onClick={() => void handleSwitch(branch.name)}
|
|
91
|
+
>
|
|
92
|
+
{branch.name}
|
|
93
|
+
</button>
|
|
94
|
+
),
|
|
95
|
+
)}
|
|
96
|
+
</div>
|
|
97
|
+
)}
|
|
98
|
+
{git.lastResult?.tone === 'error' && (
|
|
99
|
+
<p className="db-git-form-error" role="alert">
|
|
100
|
+
{git.lastResult.message}
|
|
101
|
+
</p>
|
|
102
|
+
)}
|
|
103
|
+
<hr />
|
|
104
|
+
<div className="db-git-form-row">
|
|
105
|
+
<label className="db-git-form-label" htmlFor="db-git-new-branch-name">
|
|
106
|
+
New branch
|
|
107
|
+
</label>
|
|
108
|
+
<input
|
|
109
|
+
id="db-git-new-branch-name"
|
|
110
|
+
ref={createInputRef}
|
|
111
|
+
className="db-git-form-input"
|
|
112
|
+
type="text"
|
|
113
|
+
value={newName}
|
|
114
|
+
onChange={(e) => setNewName(e.target.value)}
|
|
115
|
+
onKeyDown={(e) => {
|
|
116
|
+
if (e.key === 'Enter') void handleCreate();
|
|
117
|
+
}}
|
|
118
|
+
placeholder="feature/my-branch"
|
|
119
|
+
spellCheck={false}
|
|
120
|
+
/>
|
|
121
|
+
<button
|
|
122
|
+
type="button"
|
|
123
|
+
className="db-git-primary-btn"
|
|
124
|
+
disabled={!canCreate}
|
|
125
|
+
onClick={() => void handleCreate()}
|
|
126
|
+
>
|
|
127
|
+
Create
|
|
128
|
+
</button>
|
|
129
|
+
</div>
|
|
130
|
+
{remotes.length > 0 && (
|
|
131
|
+
<details>
|
|
132
|
+
<summary>Remote branches ({remotes.length})</summary>
|
|
133
|
+
<div className="db-git-branch-list">
|
|
134
|
+
{remotes.map((branch) => (
|
|
135
|
+
<span key={branch.name} className="db-git-branch-row">
|
|
136
|
+
{branch.name}
|
|
137
|
+
</span>
|
|
138
|
+
))}
|
|
139
|
+
</div>
|
|
140
|
+
</details>
|
|
141
|
+
)}
|
|
142
|
+
</Dialog>
|
|
143
|
+
);
|
|
144
|
+
}
|