@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
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitCloneDialog — clone a remote repository into a new local workspace.
|
|
3
|
+
*
|
|
4
|
+
* The renderer only collects the URL; the main process shows the native
|
|
5
|
+
* destination picker, streams progress back, and registers the clone as a
|
|
6
|
+
* workspace. An auth failure swaps the body to the shared credential
|
|
7
|
+
* guidance copy (no credentials are ever collected here).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { useEffect, useRef, useState } from 'react';
|
|
11
|
+
import type {
|
|
12
|
+
ElectronWorkspaceInfo,
|
|
13
|
+
GitCloneHandle,
|
|
14
|
+
GitCloneProgress,
|
|
15
|
+
} from '@bendyline/docblocks/host';
|
|
16
|
+
import { Dialog } from '../components/Dialog.js';
|
|
17
|
+
import { useGitContext } from './GitContext.js';
|
|
18
|
+
import { deriveCloneFolderName, isLikelyGitUrl } from './git-clone-utils.js';
|
|
19
|
+
import { GitCredentialGuidance } from './GitAuthGuidanceDialog.js';
|
|
20
|
+
|
|
21
|
+
export function GitCloneDialog({
|
|
22
|
+
onClose,
|
|
23
|
+
onCloned,
|
|
24
|
+
}: {
|
|
25
|
+
onClose: () => void;
|
|
26
|
+
onCloned: (info: ElectronWorkspaceInfo) => void;
|
|
27
|
+
}) {
|
|
28
|
+
const git = useGitContext();
|
|
29
|
+
const gitApi = git?.gitApi ?? null;
|
|
30
|
+
|
|
31
|
+
const [url, setUrl] = useState('');
|
|
32
|
+
const [cloning, setCloning] = useState(false);
|
|
33
|
+
const [progress, setProgress] = useState<GitCloneProgress | null>(null);
|
|
34
|
+
const [error, setError] = useState<string | null>(null);
|
|
35
|
+
/** Non-null → show embedded credential guidance instead of the form. */
|
|
36
|
+
const [authDetail, setAuthDetail] = useState<{ detail?: string } | null>(null);
|
|
37
|
+
const handleRef = useRef<GitCloneHandle | null>(null);
|
|
38
|
+
const urlInputRef = useRef<HTMLInputElement>(null);
|
|
39
|
+
|
|
40
|
+
// Closing the dialog mid-clone abandons it — kill git and clean up.
|
|
41
|
+
useEffect(
|
|
42
|
+
() => () => {
|
|
43
|
+
handleRef.current?.cancel();
|
|
44
|
+
},
|
|
45
|
+
[],
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
if (!gitApi || typeof gitApi.clone !== 'function') return null;
|
|
49
|
+
|
|
50
|
+
const canClone = isLikelyGitUrl(url) && !cloning;
|
|
51
|
+
|
|
52
|
+
const handleClone = async () => {
|
|
53
|
+
if (!canClone) return;
|
|
54
|
+
setCloning(true);
|
|
55
|
+
setError(null);
|
|
56
|
+
setProgress(null);
|
|
57
|
+
const handle = gitApi.clone(url.trim(), (p) => setProgress(p));
|
|
58
|
+
handleRef.current = handle;
|
|
59
|
+
const result = await handle.result;
|
|
60
|
+
handleRef.current = null;
|
|
61
|
+
setCloning(false);
|
|
62
|
+
setProgress(null);
|
|
63
|
+
if (!result.ok) {
|
|
64
|
+
switch (result.error.code) {
|
|
65
|
+
case 'invalid-url':
|
|
66
|
+
setError('That doesn’t look like a valid repository URL.');
|
|
67
|
+
break;
|
|
68
|
+
case 'destination-exists':
|
|
69
|
+
setError('A folder with that name already exists there. Pick a different location.');
|
|
70
|
+
break;
|
|
71
|
+
case 'auth-failed':
|
|
72
|
+
setAuthDetail({ detail: result.error.message });
|
|
73
|
+
break;
|
|
74
|
+
case 'cancelled':
|
|
75
|
+
break; // user cancelled — just return to the form
|
|
76
|
+
default:
|
|
77
|
+
setError(result.error.message);
|
|
78
|
+
}
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
if (result.value === null) return; // user cancelled the destination picker
|
|
82
|
+
onCloned(result.value);
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const progressPercent = progress?.percent ?? null;
|
|
86
|
+
const progressText =
|
|
87
|
+
progress !== null
|
|
88
|
+
? `${progress.phase}…${progressPercent !== null ? ` ${Math.round(progressPercent)}%` : ''}`
|
|
89
|
+
: 'Starting clone…';
|
|
90
|
+
|
|
91
|
+
if (authDetail !== null) {
|
|
92
|
+
return (
|
|
93
|
+
<Dialog
|
|
94
|
+
title="Clone repository"
|
|
95
|
+
onClose={onClose}
|
|
96
|
+
footer={
|
|
97
|
+
<>
|
|
98
|
+
<button
|
|
99
|
+
type="button"
|
|
100
|
+
className="db-git-secondary-btn"
|
|
101
|
+
onClick={() => setAuthDetail(null)}
|
|
102
|
+
>
|
|
103
|
+
Back
|
|
104
|
+
</button>
|
|
105
|
+
<button type="button" className="db-git-primary-btn" onClick={onClose}>
|
|
106
|
+
Close
|
|
107
|
+
</button>
|
|
108
|
+
</>
|
|
109
|
+
}
|
|
110
|
+
>
|
|
111
|
+
<p className="db-settings-hint">Git could not authenticate with that remote.</p>
|
|
112
|
+
<GitCredentialGuidance detail={authDetail.detail} />
|
|
113
|
+
</Dialog>
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return (
|
|
118
|
+
<Dialog
|
|
119
|
+
title="Clone repository"
|
|
120
|
+
onClose={onClose}
|
|
121
|
+
closeOnBackdrop={!cloning}
|
|
122
|
+
initialFocusRef={urlInputRef}
|
|
123
|
+
footer={
|
|
124
|
+
<>
|
|
125
|
+
<button
|
|
126
|
+
type="button"
|
|
127
|
+
className="db-git-secondary-btn"
|
|
128
|
+
onClick={onClose}
|
|
129
|
+
disabled={cloning}
|
|
130
|
+
>
|
|
131
|
+
Cancel
|
|
132
|
+
</button>
|
|
133
|
+
<button
|
|
134
|
+
type="button"
|
|
135
|
+
className="db-git-primary-btn"
|
|
136
|
+
disabled={!canClone}
|
|
137
|
+
onClick={() => void handleClone()}
|
|
138
|
+
>
|
|
139
|
+
Clone
|
|
140
|
+
</button>
|
|
141
|
+
</>
|
|
142
|
+
}
|
|
143
|
+
>
|
|
144
|
+
<div className="db-git-form-row">
|
|
145
|
+
<label className="db-git-form-label" htmlFor="db-git-clone-url">
|
|
146
|
+
Repository URL
|
|
147
|
+
</label>
|
|
148
|
+
<input
|
|
149
|
+
id="db-git-clone-url"
|
|
150
|
+
ref={urlInputRef}
|
|
151
|
+
className="db-git-form-input"
|
|
152
|
+
type="text"
|
|
153
|
+
value={url}
|
|
154
|
+
onChange={(e) => setUrl(e.target.value)}
|
|
155
|
+
onKeyDown={(e) => {
|
|
156
|
+
if (e.key === 'Enter') void handleClone();
|
|
157
|
+
}}
|
|
158
|
+
placeholder="https://github.com/owner/repo.git"
|
|
159
|
+
disabled={cloning}
|
|
160
|
+
spellCheck={false}
|
|
161
|
+
/>
|
|
162
|
+
</div>
|
|
163
|
+
{isLikelyGitUrl(url) && (
|
|
164
|
+
<p className="db-git-hint">
|
|
165
|
+
Will clone into: <b>{deriveCloneFolderName(url)}</b> — you’ll pick where next
|
|
166
|
+
</p>
|
|
167
|
+
)}
|
|
168
|
+
<p className="db-git-hint">
|
|
169
|
+
HTTPS and SSH URLs both work. You’ll choose the destination folder in the next step.
|
|
170
|
+
</p>
|
|
171
|
+
{error !== null && (
|
|
172
|
+
<p className="db-git-form-error" role="alert">
|
|
173
|
+
{error}
|
|
174
|
+
</p>
|
|
175
|
+
)}
|
|
176
|
+
{cloning && (
|
|
177
|
+
<div className="db-git-progress">
|
|
178
|
+
{progressPercent !== null && (
|
|
179
|
+
<div
|
|
180
|
+
className="db-git-progress-bar"
|
|
181
|
+
role="progressbar"
|
|
182
|
+
aria-valuemin={0}
|
|
183
|
+
aria-valuemax={100}
|
|
184
|
+
aria-valuenow={Math.round(progressPercent)}
|
|
185
|
+
style={{ width: `${progressPercent}%` }}
|
|
186
|
+
/>
|
|
187
|
+
)}
|
|
188
|
+
<p className="db-git-progress-text">{progressText}</p>
|
|
189
|
+
<button
|
|
190
|
+
type="button"
|
|
191
|
+
className="db-git-secondary-btn"
|
|
192
|
+
onClick={() => handleRef.current?.cancel()}
|
|
193
|
+
>
|
|
194
|
+
Cancel
|
|
195
|
+
</button>
|
|
196
|
+
</div>
|
|
197
|
+
)}
|
|
198
|
+
</Dialog>
|
|
199
|
+
);
|
|
200
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitCommitDialog — message + per-file selection for committing changes.
|
|
3
|
+
*
|
|
4
|
+
* All changed files are included by default; while a merge is being
|
|
5
|
+
* concluded, conflicted files are force-included (locked) because a commit
|
|
6
|
+
* that omits them would not resolve the merge. Selection logic lives in
|
|
7
|
+
* commit-selection.ts so it stays unit-testable.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { useRef, useState } from 'react';
|
|
11
|
+
import { Dialog } from '../components/Dialog.js';
|
|
12
|
+
import { useGitContext } from './GitContext.js';
|
|
13
|
+
import { BADGE_LABELS, badgeKindFor } from './git-status.js';
|
|
14
|
+
import {
|
|
15
|
+
canCommit,
|
|
16
|
+
initSelection,
|
|
17
|
+
isMergeCommit,
|
|
18
|
+
selectedPaths,
|
|
19
|
+
setAllSelected,
|
|
20
|
+
toggleSelection,
|
|
21
|
+
type CommitSelection,
|
|
22
|
+
} from './commit-selection.js';
|
|
23
|
+
|
|
24
|
+
export function GitCommitDialog({ onClose }: { onClose: () => void }) {
|
|
25
|
+
const git = useGitContext();
|
|
26
|
+
const status = git?.status ?? null;
|
|
27
|
+
const merging = isMergeCommit(status);
|
|
28
|
+
|
|
29
|
+
const [message, setMessage] = useState('');
|
|
30
|
+
const [selection, setSelection] = useState<CommitSelection>(() =>
|
|
31
|
+
initSelection(status?.changes ?? [], merging),
|
|
32
|
+
);
|
|
33
|
+
const messageRef = useRef<HTMLTextAreaElement>(null);
|
|
34
|
+
|
|
35
|
+
if (!git || !status) return null;
|
|
36
|
+
|
|
37
|
+
const allOn = [...selection.included.values()].every(Boolean);
|
|
38
|
+
const commitDisabled = !canCommit(message, selection) || git.busy !== null;
|
|
39
|
+
|
|
40
|
+
const handleCommit = async () => {
|
|
41
|
+
const ok = await git.commit(message, selectedPaths(selection));
|
|
42
|
+
if (ok) onClose();
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<Dialog
|
|
47
|
+
title={merging ? 'Commit merge' : 'Commit changes'}
|
|
48
|
+
onClose={onClose}
|
|
49
|
+
size="wide"
|
|
50
|
+
closeOnBackdrop={false}
|
|
51
|
+
initialFocusRef={messageRef}
|
|
52
|
+
footer={
|
|
53
|
+
<>
|
|
54
|
+
<button type="button" className="db-git-secondary-btn" onClick={onClose}>
|
|
55
|
+
Cancel
|
|
56
|
+
</button>
|
|
57
|
+
<button
|
|
58
|
+
type="button"
|
|
59
|
+
className="db-git-primary-btn"
|
|
60
|
+
disabled={commitDisabled}
|
|
61
|
+
onClick={() => void handleCommit()}
|
|
62
|
+
>
|
|
63
|
+
Commit
|
|
64
|
+
</button>
|
|
65
|
+
</>
|
|
66
|
+
}
|
|
67
|
+
>
|
|
68
|
+
{merging && (
|
|
69
|
+
<p className="db-settings-hint">
|
|
70
|
+
You’re completing a merge. Committing will conclude it.
|
|
71
|
+
</p>
|
|
72
|
+
)}
|
|
73
|
+
<textarea
|
|
74
|
+
ref={messageRef}
|
|
75
|
+
className="db-git-commit-message"
|
|
76
|
+
value={message}
|
|
77
|
+
onChange={(e) => setMessage(e.target.value)}
|
|
78
|
+
placeholder="Commit message"
|
|
79
|
+
aria-label="Commit message"
|
|
80
|
+
rows={3}
|
|
81
|
+
/>
|
|
82
|
+
<div className="db-git-form-row">
|
|
83
|
+
<button
|
|
84
|
+
type="button"
|
|
85
|
+
className="db-git-secondary-btn"
|
|
86
|
+
onClick={() => setSelection((s) => setAllSelected(s, !allOn))}
|
|
87
|
+
>
|
|
88
|
+
{allOn ? 'Select none' : 'Select all'}
|
|
89
|
+
</button>
|
|
90
|
+
</div>
|
|
91
|
+
<ul className="db-git-file-list">
|
|
92
|
+
{status.changes.map((change) => {
|
|
93
|
+
const kind = badgeKindFor(change);
|
|
94
|
+
const locked = selection.locked.has(change.path);
|
|
95
|
+
const checked = locked || (selection.included.get(change.path) ?? true);
|
|
96
|
+
return (
|
|
97
|
+
<li key={change.path} className="db-git-file-row">
|
|
98
|
+
<label>
|
|
99
|
+
<input
|
|
100
|
+
type="checkbox"
|
|
101
|
+
checked={checked}
|
|
102
|
+
disabled={locked}
|
|
103
|
+
onChange={() => setSelection((s) => toggleSelection(s, change.path))}
|
|
104
|
+
/>
|
|
105
|
+
<span>{change.path.replace(/^\/+/, '')}</span>
|
|
106
|
+
</label>
|
|
107
|
+
<span className="db-git-file-kind">{BADGE_LABELS[kind]}</span>
|
|
108
|
+
</li>
|
|
109
|
+
);
|
|
110
|
+
})}
|
|
111
|
+
</ul>
|
|
112
|
+
{git.lastResult?.tone === 'error' && (
|
|
113
|
+
<p className="db-git-form-error" role="alert">
|
|
114
|
+
{git.lastResult.message}
|
|
115
|
+
</p>
|
|
116
|
+
)}
|
|
117
|
+
</Dialog>
|
|
118
|
+
);
|
|
119
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitConflictsDialog — lists conflicted files after a pull hits a merge
|
|
3
|
+
* conflict. Each row opens the file in the editor so the user can resolve
|
|
4
|
+
* the markers; the primary action hands off to the commit dialog, which
|
|
5
|
+
* concludes the merge.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { Dialog } from '../components/Dialog.js';
|
|
9
|
+
import { useGitContext } from './GitContext.js';
|
|
10
|
+
|
|
11
|
+
export function GitConflictsDialog({
|
|
12
|
+
paths,
|
|
13
|
+
onOpenFile,
|
|
14
|
+
onClose,
|
|
15
|
+
}: {
|
|
16
|
+
paths: string[];
|
|
17
|
+
onOpenFile: (path: string) => void;
|
|
18
|
+
onClose: () => void;
|
|
19
|
+
}) {
|
|
20
|
+
const git = useGitContext();
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<Dialog
|
|
24
|
+
title="Merge conflicts"
|
|
25
|
+
onClose={onClose}
|
|
26
|
+
footer={
|
|
27
|
+
<>
|
|
28
|
+
<button type="button" className="db-git-secondary-btn" onClick={onClose}>
|
|
29
|
+
Close
|
|
30
|
+
</button>
|
|
31
|
+
<button
|
|
32
|
+
type="button"
|
|
33
|
+
className="db-git-primary-btn"
|
|
34
|
+
onClick={() => git?.openDialog({ kind: 'commit' })}
|
|
35
|
+
>
|
|
36
|
+
Open commit dialog
|
|
37
|
+
</button>
|
|
38
|
+
</>
|
|
39
|
+
}
|
|
40
|
+
>
|
|
41
|
+
<p className="db-settings-hint">
|
|
42
|
+
These files contain conflict markers. Edit each one to resolve the conflicts, then commit to
|
|
43
|
+
complete the merge.
|
|
44
|
+
</p>
|
|
45
|
+
<ul className="db-git-file-list">
|
|
46
|
+
{paths.map((path) => (
|
|
47
|
+
<li key={path} className="db-git-file-row">
|
|
48
|
+
<button
|
|
49
|
+
type="button"
|
|
50
|
+
onClick={() => {
|
|
51
|
+
onOpenFile(path);
|
|
52
|
+
onClose();
|
|
53
|
+
}}
|
|
54
|
+
>
|
|
55
|
+
{path.replace(/^\/+/, '')}
|
|
56
|
+
</button>
|
|
57
|
+
</li>
|
|
58
|
+
))}
|
|
59
|
+
</ul>
|
|
60
|
+
</Dialog>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitContext — the seam between DocBlocksShell and every git UI surface.
|
|
3
|
+
*
|
|
4
|
+
* The shell calls useGit() once and provides the value here; the status
|
|
5
|
+
* bar, file-tree badges, toolbar control, and dialogs all consume it.
|
|
6
|
+
* Consumers must tolerate a null context (FileExplorer renders standalone
|
|
7
|
+
* in tests and on surfaces with no git).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { createContext, useContext } from 'react';
|
|
11
|
+
import type {
|
|
12
|
+
DocBlocksHostGitAPI,
|
|
13
|
+
GitCapabilities,
|
|
14
|
+
GitRepoDetection,
|
|
15
|
+
GitStatus,
|
|
16
|
+
} from '@bendyline/docblocks/host';
|
|
17
|
+
import type { FileSystemProvider } from '@bendyline/docblocks/filesystem';
|
|
18
|
+
import type { GitBadgeKind } from './git-status.js';
|
|
19
|
+
|
|
20
|
+
export type GitDialogState =
|
|
21
|
+
| { kind: 'none' }
|
|
22
|
+
| { kind: 'commit' }
|
|
23
|
+
| { kind: 'branches'; createFocus?: boolean }
|
|
24
|
+
| { kind: 'history'; path?: string }
|
|
25
|
+
/** `revision` absent → working tree vs HEAD; present → that commit vs its parent. */
|
|
26
|
+
| { kind: 'diff'; path: string; revision?: { sha: string } }
|
|
27
|
+
| { kind: 'clone' }
|
|
28
|
+
| {
|
|
29
|
+
kind: 'auth-guidance';
|
|
30
|
+
operation: 'push' | 'pull' | 'fetch' | 'clone' | 'pr';
|
|
31
|
+
detail?: string;
|
|
32
|
+
}
|
|
33
|
+
| { kind: 'conflicts'; paths: string[] };
|
|
34
|
+
|
|
35
|
+
export type GitBusyKind = 'push' | 'pull' | 'fetch' | 'commit' | 'branch' | 'pr' | null;
|
|
36
|
+
|
|
37
|
+
export interface GitInlineResult {
|
|
38
|
+
tone: 'info' | 'error';
|
|
39
|
+
message: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface GitValue {
|
|
43
|
+
/** Tier 1 — Electron host with a working git. Gates the clone entry. */
|
|
44
|
+
available: boolean;
|
|
45
|
+
capabilities: GitCapabilities | null;
|
|
46
|
+
/** Tier 2 — the active workspace root is a repository. Gates everything else. */
|
|
47
|
+
repo: GitRepoDetection | null;
|
|
48
|
+
/** Main-owned repository capability; never a native path. */
|
|
49
|
+
repositoryId: string | null;
|
|
50
|
+
/** Parsed web location of the first remote, when one exists and parses. */
|
|
51
|
+
remoteWeb: { host: string; owner: string; repo: string; webUrl: string } | null;
|
|
52
|
+
/** Raw host API for read-heavy dialogs (log, branches, file-at-revision). */
|
|
53
|
+
gitApi: DocBlocksHostGitAPI | null;
|
|
54
|
+
provider: FileSystemProvider | null;
|
|
55
|
+
theme: 'light' | 'dark';
|
|
56
|
+
|
|
57
|
+
status: GitStatus | null;
|
|
58
|
+
badges: ReadonlyMap<string, GitBadgeKind>;
|
|
59
|
+
busy: GitBusyKind;
|
|
60
|
+
/** Latest operation outcome, surfaced in an aria-live region. */
|
|
61
|
+
lastResult: GitInlineResult | null;
|
|
62
|
+
|
|
63
|
+
refresh: () => void;
|
|
64
|
+
/** Debounced refresh — used after autosave writes. */
|
|
65
|
+
scheduleRefresh: () => void;
|
|
66
|
+
|
|
67
|
+
/** Actions route their typed errors to dialogs/lastResult internally. */
|
|
68
|
+
commit: (message: string, paths: string[]) => Promise<boolean>;
|
|
69
|
+
push: () => Promise<void>;
|
|
70
|
+
pull: () => Promise<void>;
|
|
71
|
+
fetchRemote: () => Promise<void>;
|
|
72
|
+
createBranch: (name: string) => Promise<boolean>;
|
|
73
|
+
switchBranch: (name: string) => Promise<boolean>;
|
|
74
|
+
/** Open the repo (or a file within it) on the remote's web host. */
|
|
75
|
+
openOnRemote: (filePath?: string) => void;
|
|
76
|
+
createPullRequest: () => Promise<void>;
|
|
77
|
+
|
|
78
|
+
dialog: GitDialogState;
|
|
79
|
+
openDialog: (dialog: GitDialogState) => void;
|
|
80
|
+
closeDialog: () => void;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export const GitContext = createContext<GitValue | null>(null);
|
|
84
|
+
|
|
85
|
+
/** Null when no provider is mounted — callers must degrade gracefully. */
|
|
86
|
+
export function useGitContext(): GitValue | null {
|
|
87
|
+
return useContext(GitContext);
|
|
88
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitDiffDialog — full-size modal showing a file diff.
|
|
3
|
+
*
|
|
4
|
+
* Without a revision: working tree vs HEAD (HEAD side via the git host API,
|
|
5
|
+
* working side via the workspace FileSystemProvider). With a revision: that
|
|
6
|
+
* commit vs its parent, both sides via readFileAtRevision. Absent sides
|
|
7
|
+
* render as empty text with an "(absent)" label; binary files short-circuit
|
|
8
|
+
* to a hint since there is no text diff to show.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { useEffect, useState } from 'react';
|
|
12
|
+
import { getFileSystemProviderV2, parseWorkspacePath } from '@bendyline/docblocks/filesystem';
|
|
13
|
+
import { Dialog } from '../components/Dialog.js';
|
|
14
|
+
import { useGitContext } from './GitContext.js';
|
|
15
|
+
import { GitDiffView } from './GitDiffView.js';
|
|
16
|
+
|
|
17
|
+
export interface GitDiffDialogProps {
|
|
18
|
+
/** Slash-prefixed workspace-relative path. */
|
|
19
|
+
path: string;
|
|
20
|
+
/** Absent → working tree vs HEAD; present → that commit vs its parent. */
|
|
21
|
+
revision?: { sha: string };
|
|
22
|
+
onClose: () => void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
type DiffLoadState =
|
|
26
|
+
| { phase: 'loading' }
|
|
27
|
+
| { phase: 'error'; message: string }
|
|
28
|
+
| { phase: 'binary' }
|
|
29
|
+
| {
|
|
30
|
+
phase: 'ready';
|
|
31
|
+
original: string;
|
|
32
|
+
modified: string;
|
|
33
|
+
labels: { original: string; modified: string };
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export function GitDiffDialog({ path, revision, onClose }: GitDiffDialogProps) {
|
|
37
|
+
const git = useGitContext();
|
|
38
|
+
const gitApi = git?.gitApi ?? null;
|
|
39
|
+
const provider = git?.provider ?? null;
|
|
40
|
+
const repositoryId = git?.repositoryId ?? null;
|
|
41
|
+
const sha = revision?.sha ?? null;
|
|
42
|
+
|
|
43
|
+
const [state, setState] = useState<DiffLoadState>({ phase: 'loading' });
|
|
44
|
+
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
if (!gitApi || !repositoryId) {
|
|
47
|
+
setState({ phase: 'error', message: 'Git is not available for this workspace.' });
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
let cancelled = false;
|
|
51
|
+
setState({ phase: 'loading' });
|
|
52
|
+
|
|
53
|
+
const load = async (): Promise<DiffLoadState> => {
|
|
54
|
+
if (sha) {
|
|
55
|
+
const short = sha.slice(0, 7);
|
|
56
|
+
const [parent, at] = await Promise.all([
|
|
57
|
+
gitApi.readFileAtRevision(repositoryId, path, { kind: 'parent-of', sha }),
|
|
58
|
+
gitApi.readFileAtRevision(repositoryId, path, { kind: 'commit', sha }),
|
|
59
|
+
]);
|
|
60
|
+
if (!parent.ok) return { phase: 'error', message: parent.error.message };
|
|
61
|
+
if (!at.ok) return { phase: 'error', message: at.error.message };
|
|
62
|
+
if (
|
|
63
|
+
(parent.value.content === null && parent.value.binary) ||
|
|
64
|
+
(at.value.content === null && at.value.binary)
|
|
65
|
+
) {
|
|
66
|
+
return { phase: 'binary' };
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
phase: 'ready',
|
|
70
|
+
original: parent.value.content ?? '',
|
|
71
|
+
modified: at.value.content ?? '',
|
|
72
|
+
labels: {
|
|
73
|
+
original: parent.value.content === null ? `${short}^ (absent)` : `${short}^`,
|
|
74
|
+
modified: at.value.content === null ? `${short} (absent)` : short,
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (!provider) {
|
|
80
|
+
return { phase: 'error', message: 'Workspace files are not available.' };
|
|
81
|
+
}
|
|
82
|
+
const providerV2 = getFileSystemProviderV2(provider);
|
|
83
|
+
const [head, working] = await Promise.all([
|
|
84
|
+
gitApi.readFileAtRevision(repositoryId, path, { kind: 'head' }),
|
|
85
|
+
providerV2
|
|
86
|
+
? providerV2
|
|
87
|
+
.readFile(parseWorkspacePath(path))
|
|
88
|
+
.then((file) => (file ? new TextDecoder().decode(file.data) : null))
|
|
89
|
+
: provider.readFile(path),
|
|
90
|
+
]);
|
|
91
|
+
if (!head.ok) return { phase: 'error', message: head.error.message };
|
|
92
|
+
if (head.value.content === null && head.value.binary) return { phase: 'binary' };
|
|
93
|
+
return {
|
|
94
|
+
phase: 'ready',
|
|
95
|
+
original: head.value.content ?? '',
|
|
96
|
+
modified: working ?? '',
|
|
97
|
+
labels: {
|
|
98
|
+
original: head.value.content === null ? 'HEAD (absent)' : 'HEAD',
|
|
99
|
+
modified: working === null ? 'Working tree (absent)' : 'Working tree',
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
load()
|
|
105
|
+
.catch(
|
|
106
|
+
(err: unknown): DiffLoadState => ({
|
|
107
|
+
phase: 'error',
|
|
108
|
+
message: err instanceof Error ? err.message : 'Failed to load the diff',
|
|
109
|
+
}),
|
|
110
|
+
)
|
|
111
|
+
.then((next) => {
|
|
112
|
+
if (!cancelled) setState(next);
|
|
113
|
+
});
|
|
114
|
+
return () => {
|
|
115
|
+
cancelled = true;
|
|
116
|
+
};
|
|
117
|
+
}, [gitApi, provider, repositoryId, path, sha]);
|
|
118
|
+
|
|
119
|
+
if (!git) return null;
|
|
120
|
+
|
|
121
|
+
const fullPath = path.replace(/^\/+/, '');
|
|
122
|
+
const baseName = fullPath.split('/').pop() || fullPath;
|
|
123
|
+
|
|
124
|
+
return (
|
|
125
|
+
<Dialog title={baseName} size="full" onClose={onClose}>
|
|
126
|
+
<p className="db-settings-hint">{fullPath}</p>
|
|
127
|
+
{state.phase === 'loading' && <p className="db-git-hint">Loading changes…</p>}
|
|
128
|
+
{state.phase === 'error' && (
|
|
129
|
+
<p className="db-git-form-error" role="alert">
|
|
130
|
+
{state.message}
|
|
131
|
+
</p>
|
|
132
|
+
)}
|
|
133
|
+
{state.phase === 'binary' && (
|
|
134
|
+
<p className="db-git-hint">Binary file — no text diff available.</p>
|
|
135
|
+
)}
|
|
136
|
+
{state.phase === 'ready' && (
|
|
137
|
+
<GitDiffView
|
|
138
|
+
original={state.original}
|
|
139
|
+
modified={state.modified}
|
|
140
|
+
fileName={baseName}
|
|
141
|
+
theme={git.theme}
|
|
142
|
+
labels={state.labels}
|
|
143
|
+
/>
|
|
144
|
+
)}
|
|
145
|
+
</Dialog>
|
|
146
|
+
);
|
|
147
|
+
}
|