@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,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitDiffView — read-only Monaco side-by-side diff.
|
|
3
|
+
*
|
|
4
|
+
* Monaco is never statically imported: squisq's useMonacoLoader() lazily
|
|
5
|
+
* imports the editor bundle once per page and hands back the namespace.
|
|
6
|
+
* The diff editor and both text models are created imperatively into a
|
|
7
|
+
* host div and disposed on unmount (the classic Monaco leak). Content and
|
|
8
|
+
* theme changes update the existing editor without recreating it.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { useEffect, useRef } from 'react';
|
|
12
|
+
import {
|
|
13
|
+
detectLanguageFromFileName,
|
|
14
|
+
useMonacoLoader,
|
|
15
|
+
type UseMonacoLoaderResult,
|
|
16
|
+
} from '@bendyline/squisq-editor-react';
|
|
17
|
+
|
|
18
|
+
type MonacoModule = NonNullable<UseMonacoLoaderResult['monaco']>;
|
|
19
|
+
type DiffEditorInstance = ReturnType<MonacoModule['editor']['createDiffEditor']>;
|
|
20
|
+
type TextModel = ReturnType<MonacoModule['editor']['createModel']>;
|
|
21
|
+
|
|
22
|
+
export interface GitDiffViewProps {
|
|
23
|
+
original: string;
|
|
24
|
+
modified: string;
|
|
25
|
+
/** Used for language detection (markdown for .md, plain text otherwise). */
|
|
26
|
+
fileName: string;
|
|
27
|
+
theme: 'light' | 'dark';
|
|
28
|
+
/** Optional column headers rendered above the two panes. */
|
|
29
|
+
labels?: { original: string; modified: string };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function languageFor(fileName: string): string {
|
|
33
|
+
const detected = detectLanguageFromFileName(fileName);
|
|
34
|
+
if (detected) return detected;
|
|
35
|
+
return fileName.toLowerCase().endsWith('.md') ? 'markdown' : 'plaintext';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function GitDiffView({ original, modified, fileName, theme, labels }: GitDiffViewProps) {
|
|
39
|
+
const { monaco, ready } = useMonacoLoader();
|
|
40
|
+
const hostRef = useRef<HTMLDivElement | null>(null);
|
|
41
|
+
const editorRef = useRef<DiffEditorInstance | null>(null);
|
|
42
|
+
const modelsRef = useRef<{ original: TextModel; modified: TextModel } | null>(null);
|
|
43
|
+
|
|
44
|
+
const language = languageFor(fileName);
|
|
45
|
+
|
|
46
|
+
// Create the editor once monaco settles; dispose editor AND models on unmount.
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
const host = hostRef.current;
|
|
49
|
+
if (!monaco || !host) return;
|
|
50
|
+
const originalModel = monaco.editor.createModel('', 'plaintext');
|
|
51
|
+
const modifiedModel = monaco.editor.createModel('', 'plaintext');
|
|
52
|
+
const editor = monaco.editor.createDiffEditor(host, {
|
|
53
|
+
readOnly: true,
|
|
54
|
+
renderSideBySide: true,
|
|
55
|
+
automaticLayout: true,
|
|
56
|
+
minimap: { enabled: false },
|
|
57
|
+
wordWrap: 'on',
|
|
58
|
+
scrollBeyondLastLine: false,
|
|
59
|
+
renderOverviewRuler: false,
|
|
60
|
+
});
|
|
61
|
+
editor.setModel({ original: originalModel, modified: modifiedModel });
|
|
62
|
+
editorRef.current = editor;
|
|
63
|
+
modelsRef.current = { original: originalModel, modified: modifiedModel };
|
|
64
|
+
return () => {
|
|
65
|
+
editorRef.current = null;
|
|
66
|
+
modelsRef.current = null;
|
|
67
|
+
editor.dispose();
|
|
68
|
+
originalModel.dispose();
|
|
69
|
+
modifiedModel.dispose();
|
|
70
|
+
};
|
|
71
|
+
}, [monaco]);
|
|
72
|
+
|
|
73
|
+
// Sync content + language into the existing models — never recreate the editor.
|
|
74
|
+
useEffect(() => {
|
|
75
|
+
if (!monaco) return;
|
|
76
|
+
const models = modelsRef.current;
|
|
77
|
+
if (!models) return;
|
|
78
|
+
if (models.original.getValue() !== original) models.original.setValue(original);
|
|
79
|
+
if (models.modified.getValue() !== modified) models.modified.setValue(modified);
|
|
80
|
+
monaco.editor.setModelLanguage(models.original, language);
|
|
81
|
+
monaco.editor.setModelLanguage(models.modified, language);
|
|
82
|
+
}, [monaco, original, modified, language]);
|
|
83
|
+
|
|
84
|
+
// React to theme prop changes.
|
|
85
|
+
useEffect(() => {
|
|
86
|
+
if (!monaco) return;
|
|
87
|
+
monaco.editor.setTheme(theme === 'dark' ? 'vs-dark' : 'vs');
|
|
88
|
+
}, [monaco, theme]);
|
|
89
|
+
|
|
90
|
+
if (!ready) {
|
|
91
|
+
return <div className="db-git-diff-loading">Loading diff…</div>;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
<>
|
|
96
|
+
{labels && (
|
|
97
|
+
<div className="db-git-diff-labels">
|
|
98
|
+
<span>{labels.original}</span>
|
|
99
|
+
<span>{labels.modified}</span>
|
|
100
|
+
</div>
|
|
101
|
+
)}
|
|
102
|
+
<div ref={hostRef} className="db-git-diff-host" />
|
|
103
|
+
</>
|
|
104
|
+
);
|
|
105
|
+
}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHistoryDialog — paged commit log for the repository or a single file.
|
|
3
|
+
*
|
|
4
|
+
* Pages `git log` 50 commits at a time; a row expands (one at a time) to
|
|
5
|
+
* show the full message body and the files changed in that commit — each
|
|
6
|
+
* file opens the commit-vs-parent diff dialog (which replaces this one;
|
|
7
|
+
* acceptable for v1).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
11
|
+
import type { GitFileChange, GitLogEntry } from '@bendyline/docblocks/host';
|
|
12
|
+
import { Dialog } from '../components/Dialog.js';
|
|
13
|
+
import { useGitContext } from './GitContext.js';
|
|
14
|
+
import { BADGE_GLYPHS, BADGE_LABELS, badgeKindFor } from './git-status.js';
|
|
15
|
+
|
|
16
|
+
export interface GitHistoryDialogProps {
|
|
17
|
+
/** Slash-prefixed workspace-relative path; absent → whole-repo history. */
|
|
18
|
+
path?: string;
|
|
19
|
+
onClose: () => void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const PAGE_SIZE = 50;
|
|
23
|
+
|
|
24
|
+
type CommitDetail = { body: string; files: GitFileChange[] } | { error: string };
|
|
25
|
+
|
|
26
|
+
/** Message body beyond the subject line, or '' when it adds nothing. */
|
|
27
|
+
function extraBody(body: string, subject: string): string {
|
|
28
|
+
const trimmedBody = body.trim();
|
|
29
|
+
const trimmedSubject = subject.trim();
|
|
30
|
+
if (!trimmedBody || trimmedBody === trimmedSubject) return '';
|
|
31
|
+
if (trimmedBody.startsWith(trimmedSubject)) {
|
|
32
|
+
return trimmedBody.slice(trimmedSubject.length).trim();
|
|
33
|
+
}
|
|
34
|
+
return trimmedBody;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function formatDate(iso: string): string {
|
|
38
|
+
const date = new Date(iso);
|
|
39
|
+
return `${date.toLocaleDateString()} ${date.toLocaleTimeString()}`;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function GitHistoryDialog({ path, onClose }: GitHistoryDialogProps) {
|
|
43
|
+
const git = useGitContext();
|
|
44
|
+
const gitApi = git?.gitApi ?? null;
|
|
45
|
+
const repositoryId = git?.repositoryId ?? null;
|
|
46
|
+
|
|
47
|
+
const [entries, setEntries] = useState<GitLogEntry[]>([]);
|
|
48
|
+
const [loading, setLoading] = useState(false);
|
|
49
|
+
const [error, setError] = useState<string | null>(null);
|
|
50
|
+
const [hasMore, setHasMore] = useState(false);
|
|
51
|
+
const [expandedSha, setExpandedSha] = useState<string | null>(null);
|
|
52
|
+
const [details, setDetails] = useState<ReadonlyMap<string, CommitDetail>>(new Map());
|
|
53
|
+
|
|
54
|
+
const loadPage = useCallback(
|
|
55
|
+
async (skip: number) => {
|
|
56
|
+
if (!gitApi || !repositoryId) return;
|
|
57
|
+
setLoading(true);
|
|
58
|
+
setError(null);
|
|
59
|
+
try {
|
|
60
|
+
const result = await gitApi.log(repositoryId, { path, maxCount: PAGE_SIZE, skip });
|
|
61
|
+
if (!result.ok) {
|
|
62
|
+
setError(result.error.message);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
setEntries((prev) => (skip === 0 ? result.value : [...prev, ...result.value]));
|
|
66
|
+
setHasMore(result.value.length === PAGE_SIZE);
|
|
67
|
+
} finally {
|
|
68
|
+
setLoading(false);
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
[gitApi, repositoryId, path],
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
useEffect(() => {
|
|
75
|
+
setEntries([]);
|
|
76
|
+
setExpandedSha(null);
|
|
77
|
+
setDetails(new Map());
|
|
78
|
+
setHasMore(false);
|
|
79
|
+
void loadPage(0);
|
|
80
|
+
}, [loadPage]);
|
|
81
|
+
|
|
82
|
+
const handleRowClick = (sha: string) => {
|
|
83
|
+
if (expandedSha === sha) {
|
|
84
|
+
setExpandedSha(null);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
setExpandedSha(sha);
|
|
88
|
+
if (!gitApi || !repositoryId || details.has(sha)) return;
|
|
89
|
+
void gitApi.commitFiles(repositoryId, sha).then((result) => {
|
|
90
|
+
setDetails((prev) => {
|
|
91
|
+
const next = new Map(prev);
|
|
92
|
+
next.set(sha, result.ok ? result.value : { error: result.error.message });
|
|
93
|
+
return next;
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
if (!git) return null;
|
|
99
|
+
|
|
100
|
+
const baseName = path ? path.replace(/^\/+/, '').split('/').pop() : null;
|
|
101
|
+
const title = baseName ? `History — ${baseName}` : 'Commit history';
|
|
102
|
+
|
|
103
|
+
return (
|
|
104
|
+
<Dialog title={title} size="wide" onClose={onClose}>
|
|
105
|
+
{error && (
|
|
106
|
+
<p className="db-git-form-error" role="alert">
|
|
107
|
+
{error}
|
|
108
|
+
</p>
|
|
109
|
+
)}
|
|
110
|
+
{!error && entries.length === 0 && loading && <p className="db-git-hint">Loading history…</p>}
|
|
111
|
+
{!error && entries.length === 0 && !loading && <p className="db-git-hint">No commits yet.</p>}
|
|
112
|
+
{entries.length > 0 && (
|
|
113
|
+
<ul className="db-git-history-list">
|
|
114
|
+
{entries.map((entry) => {
|
|
115
|
+
const expanded = expandedSha === entry.sha;
|
|
116
|
+
const detail = details.get(entry.sha);
|
|
117
|
+
const meta = [
|
|
118
|
+
entry.authorName,
|
|
119
|
+
entry.sha.slice(0, 7),
|
|
120
|
+
formatDate(entry.authorDate),
|
|
121
|
+
entry.refs.join(', '),
|
|
122
|
+
]
|
|
123
|
+
.filter(Boolean)
|
|
124
|
+
.join(' · ');
|
|
125
|
+
const body =
|
|
126
|
+
detail && !('error' in detail) ? extraBody(detail.body, entry.subject) : '';
|
|
127
|
+
return (
|
|
128
|
+
<li key={entry.sha} className="db-git-history-item">
|
|
129
|
+
<button
|
|
130
|
+
type="button"
|
|
131
|
+
className="db-git-history-toggle"
|
|
132
|
+
aria-expanded={expanded}
|
|
133
|
+
onClick={() => handleRowClick(entry.sha)}
|
|
134
|
+
>
|
|
135
|
+
<span className="db-git-history-subject">{entry.subject}</span>
|
|
136
|
+
<span className="db-git-history-meta">{meta}</span>
|
|
137
|
+
</button>
|
|
138
|
+
{expanded && !detail && <p className="db-git-hint">Loading…</p>}
|
|
139
|
+
{expanded && detail && 'error' in detail && (
|
|
140
|
+
<p className="db-git-form-error" role="alert">
|
|
141
|
+
{detail.error}
|
|
142
|
+
</p>
|
|
143
|
+
)}
|
|
144
|
+
{expanded && detail && !('error' in detail) && (
|
|
145
|
+
<>
|
|
146
|
+
{body && <pre className="db-git-history-body">{body}</pre>}
|
|
147
|
+
<ul className="db-git-history-files">
|
|
148
|
+
{detail.files.map((file) => {
|
|
149
|
+
const kind = badgeKindFor(file);
|
|
150
|
+
return (
|
|
151
|
+
<li key={file.path}>
|
|
152
|
+
<button
|
|
153
|
+
type="button"
|
|
154
|
+
className="db-git-history-file"
|
|
155
|
+
onClick={() =>
|
|
156
|
+
git.openDialog({
|
|
157
|
+
kind: 'diff',
|
|
158
|
+
path: file.path,
|
|
159
|
+
revision: { sha: entry.sha },
|
|
160
|
+
})
|
|
161
|
+
}
|
|
162
|
+
>
|
|
163
|
+
<span
|
|
164
|
+
className="db-git-file-kind"
|
|
165
|
+
aria-label={BADGE_LABELS[kind]}
|
|
166
|
+
title={BADGE_LABELS[kind]}
|
|
167
|
+
>
|
|
168
|
+
{BADGE_GLYPHS[kind]}
|
|
169
|
+
</span>
|
|
170
|
+
{file.path.replace(/^\/+/, '')}
|
|
171
|
+
</button>
|
|
172
|
+
</li>
|
|
173
|
+
);
|
|
174
|
+
})}
|
|
175
|
+
</ul>
|
|
176
|
+
</>
|
|
177
|
+
)}
|
|
178
|
+
</li>
|
|
179
|
+
);
|
|
180
|
+
})}
|
|
181
|
+
</ul>
|
|
182
|
+
)}
|
|
183
|
+
{hasMore && (
|
|
184
|
+
<button
|
|
185
|
+
type="button"
|
|
186
|
+
className="db-git-secondary-btn"
|
|
187
|
+
disabled={loading}
|
|
188
|
+
onClick={() => void loadPage(entries.length)}
|
|
189
|
+
>
|
|
190
|
+
Show older commits
|
|
191
|
+
</button>
|
|
192
|
+
)}
|
|
193
|
+
</Dialog>
|
|
194
|
+
);
|
|
195
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitStatusBar — slim sidebar row showing branch, ahead/behind, and dirty
|
|
3
|
+
* count, with the repo actions menu (commit, sync, branches, history,
|
|
4
|
+
* remote conveniences). Renders nothing unless the active workspace is a
|
|
5
|
+
* git repository.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
9
|
+
import { useGitContext } from './GitContext.js';
|
|
10
|
+
import { formatAheadBehind, summarizeDirty } from './git-status.js';
|
|
11
|
+
|
|
12
|
+
export function GitStatusBar() {
|
|
13
|
+
const git = useGitContext();
|
|
14
|
+
const [menuOpen, setMenuOpen] = useState(false);
|
|
15
|
+
const rootRef = useRef<HTMLDivElement>(null);
|
|
16
|
+
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
if (!menuOpen) return;
|
|
19
|
+
const onPointerDown = (e: PointerEvent) => {
|
|
20
|
+
if (rootRef.current && !rootRef.current.contains(e.target as Node)) {
|
|
21
|
+
setMenuOpen(false);
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
document.addEventListener('pointerdown', onPointerDown);
|
|
25
|
+
return () => document.removeEventListener('pointerdown', onPointerDown);
|
|
26
|
+
}, [menuOpen]);
|
|
27
|
+
|
|
28
|
+
const runItem = useCallback((action: () => void) => {
|
|
29
|
+
setMenuOpen(false);
|
|
30
|
+
action();
|
|
31
|
+
}, []);
|
|
32
|
+
|
|
33
|
+
if (!git || !git.repo || !git.status) return null;
|
|
34
|
+
|
|
35
|
+
const { status } = git;
|
|
36
|
+
const branchLabel = status.detached ? 'detached HEAD' : (status.branch ?? 'no branch');
|
|
37
|
+
const aheadBehind = formatAheadBehind(status);
|
|
38
|
+
const dirty = summarizeDirty(status);
|
|
39
|
+
const merging = status.operation === 'merge';
|
|
40
|
+
const chipLabelParts = [`Git: branch ${branchLabel}`];
|
|
41
|
+
if (status.ahead > 0) chipLabelParts.push(`${status.ahead} ahead`);
|
|
42
|
+
if (status.behind > 0) chipLabelParts.push(`${status.behind} behind`);
|
|
43
|
+
if (dirty) chipLabelParts.push(dirty);
|
|
44
|
+
if (merging) chipLabelParts.push('merge in progress');
|
|
45
|
+
|
|
46
|
+
const canCreatePr =
|
|
47
|
+
git.capabilities?.ghAvailable === true && git.remoteWeb?.host === 'github.com';
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<div ref={rootRef} className="db-git-statusbar">
|
|
51
|
+
<button
|
|
52
|
+
type="button"
|
|
53
|
+
className="db-git-statusbar-chip"
|
|
54
|
+
onClick={() => setMenuOpen((open) => !open)}
|
|
55
|
+
aria-haspopup="menu"
|
|
56
|
+
aria-expanded={menuOpen}
|
|
57
|
+
aria-label={chipLabelParts.join(', ')}
|
|
58
|
+
title={chipLabelParts.join(', ')}
|
|
59
|
+
>
|
|
60
|
+
<span className="db-git-branch-glyph" aria-hidden="true">
|
|
61
|
+
{'⎇'}
|
|
62
|
+
</span>
|
|
63
|
+
<span className="db-git-statusbar-branch">{branchLabel}</span>
|
|
64
|
+
{aheadBehind && <span className="db-git-statusbar-counts">{aheadBehind}</span>}
|
|
65
|
+
{dirty && <span className="db-git-statusbar-dirty">{dirty}</span>}
|
|
66
|
+
{merging && <span className="db-git-statusbar-merge">merging</span>}
|
|
67
|
+
{git.busy && (
|
|
68
|
+
<span className="db-git-statusbar-busy" aria-hidden="true">
|
|
69
|
+
{'…'}
|
|
70
|
+
</span>
|
|
71
|
+
)}
|
|
72
|
+
</button>
|
|
73
|
+
|
|
74
|
+
{menuOpen && (
|
|
75
|
+
<div className="db-git-menu" role="menu" aria-label="Git actions">
|
|
76
|
+
<button
|
|
77
|
+
type="button"
|
|
78
|
+
role="menuitem"
|
|
79
|
+
className="db-git-menu-item"
|
|
80
|
+
disabled={status.changes.length === 0 && !merging}
|
|
81
|
+
onClick={() => runItem(() => git.openDialog({ kind: 'commit' }))}
|
|
82
|
+
>
|
|
83
|
+
{merging ? 'Commit merge…' : 'Commit…'}
|
|
84
|
+
</button>
|
|
85
|
+
<button
|
|
86
|
+
type="button"
|
|
87
|
+
role="menuitem"
|
|
88
|
+
className="db-git-menu-item"
|
|
89
|
+
disabled={git.busy !== null}
|
|
90
|
+
onClick={() => runItem(() => void git.push())}
|
|
91
|
+
>
|
|
92
|
+
{status.upstream === null && !status.unborn ? 'Publish branch' : 'Push'}
|
|
93
|
+
</button>
|
|
94
|
+
<button
|
|
95
|
+
type="button"
|
|
96
|
+
role="menuitem"
|
|
97
|
+
className="db-git-menu-item"
|
|
98
|
+
disabled={git.busy !== null}
|
|
99
|
+
onClick={() => runItem(() => void git.pull())}
|
|
100
|
+
>
|
|
101
|
+
Pull
|
|
102
|
+
</button>
|
|
103
|
+
<button
|
|
104
|
+
type="button"
|
|
105
|
+
role="menuitem"
|
|
106
|
+
className="db-git-menu-item"
|
|
107
|
+
disabled={git.busy !== null}
|
|
108
|
+
onClick={() => runItem(() => void git.fetchRemote())}
|
|
109
|
+
>
|
|
110
|
+
Fetch
|
|
111
|
+
</button>
|
|
112
|
+
<div className="db-git-menu-divider" role="separator" />
|
|
113
|
+
<button
|
|
114
|
+
type="button"
|
|
115
|
+
role="menuitem"
|
|
116
|
+
className="db-git-menu-item"
|
|
117
|
+
onClick={() => runItem(() => git.openDialog({ kind: 'branches' }))}
|
|
118
|
+
>
|
|
119
|
+
Branches…
|
|
120
|
+
</button>
|
|
121
|
+
<button
|
|
122
|
+
type="button"
|
|
123
|
+
role="menuitem"
|
|
124
|
+
className="db-git-menu-item"
|
|
125
|
+
onClick={() => runItem(() => git.openDialog({ kind: 'history' }))}
|
|
126
|
+
>
|
|
127
|
+
Commit history…
|
|
128
|
+
</button>
|
|
129
|
+
{(git.remoteWeb || canCreatePr) && (
|
|
130
|
+
<div className="db-git-menu-divider" role="separator" />
|
|
131
|
+
)}
|
|
132
|
+
{git.remoteWeb && (
|
|
133
|
+
<button
|
|
134
|
+
type="button"
|
|
135
|
+
role="menuitem"
|
|
136
|
+
className="db-git-menu-item"
|
|
137
|
+
onClick={() => runItem(() => git.openOnRemote())}
|
|
138
|
+
>
|
|
139
|
+
Open on {git.remoteWeb.host}
|
|
140
|
+
</button>
|
|
141
|
+
)}
|
|
142
|
+
{canCreatePr && (
|
|
143
|
+
<button
|
|
144
|
+
type="button"
|
|
145
|
+
role="menuitem"
|
|
146
|
+
className="db-git-menu-item"
|
|
147
|
+
disabled={git.busy !== null}
|
|
148
|
+
onClick={() => runItem(() => void git.createPullRequest())}
|
|
149
|
+
>
|
|
150
|
+
Create pull request
|
|
151
|
+
</button>
|
|
152
|
+
)}
|
|
153
|
+
</div>
|
|
154
|
+
)}
|
|
155
|
+
</div>
|
|
156
|
+
);
|
|
157
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitToolbarControl — per-file git actions in the editor toolbar.
|
|
3
|
+
*
|
|
4
|
+
* A single branch-glyph button (styled like the export trigger) opening a
|
|
5
|
+
* small dropdown: view working-tree changes, per-file history, and "Open
|
|
6
|
+
* on <host>" when a web remote is known. A dot overlay marks the selected
|
|
7
|
+
* file as modified. Null-renders without a git context, a repository, or a
|
|
8
|
+
* selected file. Deliberately has no editor dependency.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { useEffect, useRef, useState } from 'react';
|
|
12
|
+
import { useGitContext } from './GitContext.js';
|
|
13
|
+
import { isFileDirty } from './git-status.js';
|
|
14
|
+
|
|
15
|
+
export interface GitToolbarControlProps {
|
|
16
|
+
/** Currently selected file path (slash-prefixed), or null. */
|
|
17
|
+
selectedFile: string | null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function GitToolbarControl({ selectedFile }: GitToolbarControlProps) {
|
|
21
|
+
const git = useGitContext();
|
|
22
|
+
const [menuOpen, setMenuOpen] = useState(false);
|
|
23
|
+
const menuRef = useRef<HTMLDivElement>(null);
|
|
24
|
+
|
|
25
|
+
// Close on outside click (same pointerdown pattern as ExportToolbarControls).
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
if (!menuOpen) return;
|
|
28
|
+
const onPointerDown = (e: PointerEvent) => {
|
|
29
|
+
if (menuRef.current && !menuRef.current.contains(e.target as Node)) {
|
|
30
|
+
setMenuOpen(false);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
document.addEventListener('pointerdown', onPointerDown);
|
|
34
|
+
return () => document.removeEventListener('pointerdown', onPointerDown);
|
|
35
|
+
}, [menuOpen]);
|
|
36
|
+
|
|
37
|
+
if (!git || !git.repo || !selectedFile) return null;
|
|
38
|
+
|
|
39
|
+
const dirty = isFileDirty(git.status, selectedFile);
|
|
40
|
+
const label = dirty ? 'Git actions (file modified)' : 'Git actions';
|
|
41
|
+
|
|
42
|
+
const openDiff = () => {
|
|
43
|
+
setMenuOpen(false);
|
|
44
|
+
git.openDialog({ kind: 'diff', path: selectedFile });
|
|
45
|
+
};
|
|
46
|
+
const openHistory = () => {
|
|
47
|
+
setMenuOpen(false);
|
|
48
|
+
git.openDialog({ kind: 'history', path: selectedFile });
|
|
49
|
+
};
|
|
50
|
+
const openOnRemote = () => {
|
|
51
|
+
setMenuOpen(false);
|
|
52
|
+
git.openOnRemote(selectedFile);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<div className="db-toolbar-menu" ref={menuRef}>
|
|
57
|
+
<button
|
|
58
|
+
type="button"
|
|
59
|
+
className="squisq-toolbar-button db-git-toolbar-trigger"
|
|
60
|
+
onClick={() => setMenuOpen((prev) => !prev)}
|
|
61
|
+
aria-label={label}
|
|
62
|
+
aria-haspopup="menu"
|
|
63
|
+
aria-expanded={menuOpen}
|
|
64
|
+
data-tooltip={label}
|
|
65
|
+
>
|
|
66
|
+
<BranchGlyph />
|
|
67
|
+
{dirty && <span className="db-git-toolbar-dot" aria-hidden="true" />}
|
|
68
|
+
</button>
|
|
69
|
+
|
|
70
|
+
{menuOpen && (
|
|
71
|
+
<div className="db-toolbar-menu-dropdown" role="menu">
|
|
72
|
+
<button
|
|
73
|
+
type="button"
|
|
74
|
+
role="menuitem"
|
|
75
|
+
className="db-toolbar-menu-item"
|
|
76
|
+
disabled={!dirty}
|
|
77
|
+
onClick={openDiff}
|
|
78
|
+
>
|
|
79
|
+
View changes
|
|
80
|
+
</button>
|
|
81
|
+
<button
|
|
82
|
+
type="button"
|
|
83
|
+
role="menuitem"
|
|
84
|
+
className="db-toolbar-menu-item"
|
|
85
|
+
onClick={openHistory}
|
|
86
|
+
>
|
|
87
|
+
File history…
|
|
88
|
+
</button>
|
|
89
|
+
{git.remoteWeb && (
|
|
90
|
+
<button
|
|
91
|
+
type="button"
|
|
92
|
+
role="menuitem"
|
|
93
|
+
className="db-toolbar-menu-item"
|
|
94
|
+
onClick={openOnRemote}
|
|
95
|
+
>
|
|
96
|
+
Open on {git.remoteWeb.host}
|
|
97
|
+
</button>
|
|
98
|
+
)}
|
|
99
|
+
</div>
|
|
100
|
+
)}
|
|
101
|
+
</div>
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function BranchGlyph() {
|
|
106
|
+
return (
|
|
107
|
+
<svg
|
|
108
|
+
viewBox="0 0 16 16"
|
|
109
|
+
width="16"
|
|
110
|
+
height="16"
|
|
111
|
+
fill="none"
|
|
112
|
+
stroke="currentColor"
|
|
113
|
+
strokeWidth="1.5"
|
|
114
|
+
strokeLinecap="round"
|
|
115
|
+
strokeLinejoin="round"
|
|
116
|
+
aria-hidden="true"
|
|
117
|
+
>
|
|
118
|
+
<circle cx="4.5" cy="12" r="2" />
|
|
119
|
+
<circle cx="11.5" cy="4" r="2" />
|
|
120
|
+
<path d="M4.5 10V7.5A2.5 2.5 0 0 1 7 5h2.5" />
|
|
121
|
+
</svg>
|
|
122
|
+
);
|
|
123
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitUI — single mount point for the git status bar, the aria-live
|
|
3
|
+
* operation result line, and every git dialog. Dialog visibility lives in
|
|
4
|
+
* GitContext so the file tree, toolbar, and native menu can all open them.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { ElectronWorkspaceInfo } from '@bendyline/docblocks/host';
|
|
8
|
+
import { useGitContext } from './GitContext.js';
|
|
9
|
+
import { GitStatusBar } from './GitStatusBar.js';
|
|
10
|
+
import { GitCommitDialog } from './GitCommitDialog.js';
|
|
11
|
+
import { GitBranchesDialog } from './GitBranchesDialog.js';
|
|
12
|
+
import { GitHistoryDialog } from './GitHistoryDialog.js';
|
|
13
|
+
import { GitDiffDialog } from './GitDiffDialog.js';
|
|
14
|
+
import { GitCloneDialog } from './GitCloneDialog.js';
|
|
15
|
+
import { GitAuthGuidanceDialog } from './GitAuthGuidanceDialog.js';
|
|
16
|
+
import { GitConflictsDialog } from './GitConflictsDialog.js';
|
|
17
|
+
|
|
18
|
+
export interface GitUIProps {
|
|
19
|
+
/** Open a workspace file in the editor (from conflicts/history dialogs). */
|
|
20
|
+
onOpenFile: (path: string) => void;
|
|
21
|
+
/** A clone finished and its folder is registered — open the workspace. */
|
|
22
|
+
onWorkspaceCloned?: (info: ElectronWorkspaceInfo) => void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function GitUI({ onOpenFile, onWorkspaceCloned }: GitUIProps) {
|
|
26
|
+
const git = useGitContext();
|
|
27
|
+
if (!git || !git.available) return null;
|
|
28
|
+
|
|
29
|
+
const { dialog, closeDialog } = git;
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<>
|
|
33
|
+
<GitStatusBar />
|
|
34
|
+
{git.repo && (
|
|
35
|
+
<span className="db-git-inline-status" role="status" aria-live="polite">
|
|
36
|
+
{git.lastResult?.message ?? ''}
|
|
37
|
+
</span>
|
|
38
|
+
)}
|
|
39
|
+
{dialog.kind === 'commit' && <GitCommitDialog onClose={closeDialog} />}
|
|
40
|
+
{dialog.kind === 'branches' && (
|
|
41
|
+
<GitBranchesDialog createFocus={dialog.createFocus} onClose={closeDialog} />
|
|
42
|
+
)}
|
|
43
|
+
{dialog.kind === 'history' && <GitHistoryDialog path={dialog.path} onClose={closeDialog} />}
|
|
44
|
+
{dialog.kind === 'diff' && (
|
|
45
|
+
<GitDiffDialog path={dialog.path} revision={dialog.revision} onClose={closeDialog} />
|
|
46
|
+
)}
|
|
47
|
+
{dialog.kind === 'clone' && (
|
|
48
|
+
<GitCloneDialog
|
|
49
|
+
onClose={closeDialog}
|
|
50
|
+
onCloned={(info) => {
|
|
51
|
+
closeDialog();
|
|
52
|
+
onWorkspaceCloned?.(info);
|
|
53
|
+
}}
|
|
54
|
+
/>
|
|
55
|
+
)}
|
|
56
|
+
{dialog.kind === 'auth-guidance' && (
|
|
57
|
+
<GitAuthGuidanceDialog
|
|
58
|
+
operation={dialog.operation}
|
|
59
|
+
detail={dialog.detail}
|
|
60
|
+
onClose={closeDialog}
|
|
61
|
+
/>
|
|
62
|
+
)}
|
|
63
|
+
{dialog.kind === 'conflicts' && (
|
|
64
|
+
<GitConflictsDialog paths={dialog.paths} onOpenFile={onOpenFile} onClose={closeDialog} />
|
|
65
|
+
)}
|
|
66
|
+
</>
|
|
67
|
+
);
|
|
68
|
+
}
|