@bendyline/docblocks-react 1.1.2 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -5
- package/dist/AppMenu/AppMenu.d.ts +29 -2
- package/dist/AppMenu/AppMenu.d.ts.map +1 -1
- package/dist/AppMenu/AppMenu.js +41 -2
- package/dist/AppMenu/AppMenu.js.map +1 -1
- package/dist/AppMenu/index.d.ts +1 -1
- package/dist/AppMenu/index.d.ts.map +1 -1
- package/dist/DocBlocksShell/DocBlocksShell.d.ts +15 -2
- package/dist/DocBlocksShell/DocBlocksShell.d.ts.map +1 -1
- package/dist/DocBlocksShell/DocBlocksShell.js +1677 -298
- package/dist/DocBlocksShell/DocBlocksShell.js.map +1 -1
- package/dist/DocBlocksShell/LazyEditorShell.d.ts +3 -0
- package/dist/DocBlocksShell/LazyEditorShell.d.ts.map +1 -0
- package/dist/DocBlocksShell/LazyEditorShell.js +3 -0
- package/dist/DocBlocksShell/LazyEditorShell.js.map +1 -0
- package/dist/DocBlocksShell/UpdateAvailableNotice.d.ts +13 -0
- package/dist/DocBlocksShell/UpdateAvailableNotice.d.ts.map +1 -0
- package/dist/DocBlocksShell/UpdateAvailableNotice.js +16 -0
- package/dist/DocBlocksShell/UpdateAvailableNotice.js.map +1 -0
- package/dist/DocBlocksShell/document-title.d.ts +3 -0
- package/dist/DocBlocksShell/document-title.d.ts.map +1 -0
- package/dist/DocBlocksShell/document-title.js +17 -0
- package/dist/DocBlocksShell/document-title.js.map +1 -0
- package/dist/DocBlocksShell/workspace-authority-barrier.d.ts +14 -0
- package/dist/DocBlocksShell/workspace-authority-barrier.d.ts.map +1 -0
- package/dist/DocBlocksShell/workspace-authority-barrier.js +23 -0
- package/dist/DocBlocksShell/workspace-authority-barrier.js.map +1 -0
- package/dist/Export/DeferredExportToolbarControls.d.ts +5 -0
- package/dist/Export/DeferredExportToolbarControls.d.ts.map +1 -0
- package/dist/Export/DeferredExportToolbarControls.js +10 -0
- package/dist/Export/DeferredExportToolbarControls.js.map +1 -0
- package/dist/Export/ExportDialog.d.ts +10 -1
- package/dist/Export/ExportDialog.d.ts.map +1 -1
- package/dist/Export/ExportDialog.js +20 -18
- package/dist/Export/ExportDialog.js.map +1 -1
- package/dist/Export/ExportToolbarControls.d.ts +21 -1
- package/dist/Export/ExportToolbarControls.d.ts.map +1 -1
- package/dist/Export/ExportToolbarControls.js +96 -11
- package/dist/Export/ExportToolbarControls.js.map +1 -1
- package/dist/Export/export-destination.d.ts +3 -0
- package/dist/Export/export-destination.d.ts.map +1 -0
- package/dist/Export/export-destination.js +32 -0
- package/dist/Export/export-destination.js.map +1 -0
- package/dist/Export/index.d.ts +8 -4
- package/dist/Export/index.d.ts.map +1 -1
- package/dist/Export/index.js +4 -1
- package/dist/Export/index.js.map +1 -1
- package/dist/Export/public-api.d.ts +12 -0
- package/dist/Export/public-api.d.ts.map +1 -0
- package/dist/Export/public-api.js +9 -0
- package/dist/Export/public-api.js.map +1 -0
- package/dist/Export/run-export.d.ts +3 -1
- package/dist/Export/run-export.d.ts.map +1 -1
- package/dist/Export/run-export.js +30 -13
- package/dist/Export/run-export.js.map +1 -1
- package/dist/ExportDialog-MEUISWZX.js +9 -0
- package/dist/ExportDialog-MEUISWZX.js.map +1 -0
- package/dist/ExportToolbarControls-D2YDWOXW.js +373 -0
- package/dist/ExportToolbarControls-D2YDWOXW.js.map +1 -0
- package/dist/FileExplorer/FileExplorer.d.ts +26 -4
- package/dist/FileExplorer/FileExplorer.d.ts.map +1 -1
- package/dist/FileExplorer/FileExplorer.js +204 -29
- package/dist/FileExplorer/FileExplorer.js.map +1 -1
- package/dist/FileExplorer/FileTreeNode.d.ts +24 -3
- package/dist/FileExplorer/FileTreeNode.d.ts.map +1 -1
- package/dist/FileExplorer/FileTreeNode.js +27 -8
- package/dist/FileExplorer/FileTreeNode.js.map +1 -1
- package/dist/FileExplorer/index.d.ts +1 -1
- package/dist/FileExplorer/index.d.ts.map +1 -1
- package/dist/FileExplorer/useFileTree.d.ts +5 -3
- package/dist/FileExplorer/useFileTree.d.ts.map +1 -1
- package/dist/FileExplorer/useFileTree.js +173 -13
- package/dist/FileExplorer/useFileTree.js.map +1 -1
- package/dist/Git/GitAuthGuidanceDialog.d.ts +18 -0
- package/dist/Git/GitAuthGuidanceDialog.d.ts.map +1 -0
- package/dist/Git/GitAuthGuidanceDialog.js +27 -0
- package/dist/Git/GitAuthGuidanceDialog.js.map +1 -0
- package/dist/Git/GitBranchesDialog.d.ts +13 -0
- package/dist/Git/GitBranchesDialog.d.ts.map +1 -0
- package/dist/Git/GitBranchesDialog.js +59 -0
- package/dist/Git/GitBranchesDialog.js.map +1 -0
- package/dist/Git/GitCloneDialog.d.ts +14 -0
- package/dist/Git/GitCloneDialog.d.ts.map +1 -0
- package/dist/Git/GitCloneDialog.js +79 -0
- package/dist/Git/GitCloneDialog.js.map +1 -0
- package/dist/Git/GitCommitDialog.d.ts +12 -0
- package/dist/Git/GitCommitDialog.d.ts.map +1 -0
- package/dist/Git/GitCommitDialog.js +38 -0
- package/dist/Git/GitCommitDialog.js.map +1 -0
- package/dist/Git/GitConflictsDialog.d.ts +12 -0
- package/dist/Git/GitConflictsDialog.d.ts.map +1 -0
- package/dist/Git/GitConflictsDialog.js +17 -0
- package/dist/Git/GitConflictsDialog.js.map +1 -0
- package/dist/Git/GitContext.d.ts +89 -0
- package/dist/Git/GitContext.d.ts.map +1 -0
- package/dist/Git/GitContext.js +15 -0
- package/dist/Git/GitContext.js.map +1 -0
- package/dist/Git/GitDiffDialog.d.ts +20 -0
- package/dist/Git/GitDiffDialog.d.ts.map +1 -0
- package/dist/Git/GitDiffDialog.js +100 -0
- package/dist/Git/GitDiffDialog.js.map +1 -0
- package/dist/Git/GitDiffView.d.ts +23 -0
- package/dist/Git/GitDiffView.d.ts.map +1 -0
- package/dist/Git/GitDiffView.js +77 -0
- package/dist/Git/GitDiffView.js.map +1 -0
- package/dist/Git/GitHistoryDialog.d.ts +15 -0
- package/dist/Git/GitHistoryDialog.d.ts.map +1 -0
- package/dist/Git/GitHistoryDialog.js +107 -0
- package/dist/Git/GitHistoryDialog.js.map +1 -0
- package/dist/Git/GitStatusBar.d.ts +8 -0
- package/dist/Git/GitStatusBar.d.ts.map +1 -0
- package/dist/Git/GitStatusBar.js +49 -0
- package/dist/Git/GitStatusBar.js.map +1 -0
- package/dist/Git/GitToolbarControl.d.ts +15 -0
- package/dist/Git/GitToolbarControl.d.ts.map +1 -0
- package/dist/Git/GitToolbarControl.js +51 -0
- package/dist/Git/GitToolbarControl.js.map +1 -0
- package/dist/Git/GitUI.d.ts +14 -0
- package/dist/Git/GitUI.d.ts.map +1 -0
- package/dist/Git/GitUI.js +21 -0
- package/dist/Git/GitUI.js.map +1 -0
- package/dist/Git/commit-selection.d.ts +22 -0
- package/dist/Git/commit-selection.d.ts.map +1 -0
- package/dist/Git/commit-selection.js +47 -0
- package/dist/Git/commit-selection.js.map +1 -0
- package/dist/Git/git-clone-utils.d.ts +8 -0
- package/dist/Git/git-clone-utils.d.ts.map +1 -0
- package/dist/Git/git-clone-utils.js +26 -0
- package/dist/Git/git-clone-utils.js.map +1 -0
- package/dist/Git/git-status.d.ts +24 -0
- package/dist/Git/git-status.d.ts.map +1 -0
- package/dist/Git/git-status.js +105 -0
- package/dist/Git/git-status.js.map +1 -0
- package/dist/Git/useGit.d.ts +15 -0
- package/dist/Git/useGit.d.ts.map +1 -0
- package/dist/Git/useGit.js +206 -0
- package/dist/Git/useGit.js.map +1 -0
- package/dist/Git/useGitStatus.d.ts +17 -0
- package/dist/Git/useGitStatus.d.ts.map +1 -0
- package/dist/Git/useGitStatus.js +63 -0
- package/dist/Git/useGitStatus.js.map +1 -0
- package/dist/GitToolbarControl-LMIR4R7I.js +117 -0
- package/dist/GitToolbarControl-LMIR4R7I.js.map +1 -0
- package/dist/GitUI-KGF5MSNV.js +1048 -0
- package/dist/GitUI-KGF5MSNV.js.map +1 -0
- package/dist/LazyEditorShell-EF2BWRXU.js +6 -0
- package/dist/LazyEditorShell-EF2BWRXU.js.map +1 -0
- package/dist/Settings/Settings.d.ts +23 -0
- package/dist/Settings/Settings.d.ts.map +1 -0
- package/dist/Settings/Settings.js +22 -0
- package/dist/Settings/Settings.js.map +1 -0
- package/dist/Settings/public-api.d.ts +3 -0
- package/dist/Settings/public-api.d.ts.map +1 -0
- package/dist/Settings/public-api.js +2 -0
- package/dist/Settings/public-api.js.map +1 -0
- package/dist/WorkspacePicker/WorkspacePicker.d.ts +6 -1
- package/dist/WorkspacePicker/WorkspacePicker.d.ts.map +1 -1
- package/dist/WorkspacePicker/WorkspacePicker.js +16 -5
- package/dist/WorkspacePicker/WorkspacePicker.js.map +1 -1
- package/dist/chunk-3SAXDU2U.js +157 -0
- package/dist/chunk-3SAXDU2U.js.map +1 -0
- package/dist/chunk-4XXSWN3T.js +13 -0
- package/dist/chunk-4XXSWN3T.js.map +1 -0
- package/dist/chunk-5A4KEWKT.js +97 -0
- package/dist/chunk-5A4KEWKT.js.map +1 -0
- package/dist/chunk-AOVTHZU7.js +321 -0
- package/dist/chunk-AOVTHZU7.js.map +1 -0
- package/dist/chunk-DD4VFJQI.js +51 -0
- package/dist/chunk-DD4VFJQI.js.map +1 -0
- package/dist/chunk-GSNZSCIK.js +55 -0
- package/dist/chunk-GSNZSCIK.js.map +1 -0
- package/dist/chunk-VHDP4IPL.js +70 -0
- package/dist/chunk-VHDP4IPL.js.map +1 -0
- package/dist/chunk-ZI56BNB7.js +270 -0
- package/dist/chunk-ZI56BNB7.js.map +1 -0
- package/dist/components/Dialog.d.ts +22 -0
- package/dist/components/Dialog.d.ts.map +1 -0
- package/dist/components/Dialog.js +33 -0
- package/dist/components/Dialog.js.map +1 -0
- package/dist/editor.d.ts +10 -0
- package/dist/editor.d.ts.map +1 -0
- package/dist/editor.js +70 -0
- package/dist/editor.js.map +1 -0
- package/dist/export/index.d.ts +87 -0
- package/dist/export/index.js +26 -0
- package/dist/export/index.js.map +1 -0
- package/dist/hooks/index.d.ts +2 -1
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +1 -1
- package/dist/hooks/index.js.map +1 -1
- package/dist/hooks/useDocumentSession.d.ts +16 -0
- package/dist/hooks/useDocumentSession.d.ts.map +1 -0
- package/dist/hooks/useDocumentSession.js +18 -0
- package/dist/hooks/useDocumentSession.js.map +1 -0
- package/dist/icons.d.ts +11 -6
- package/dist/icons.d.ts.map +1 -1
- package/dist/icons.js +21 -20
- package/dist/icons.js.map +1 -1
- package/dist/index.d.ts +8 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/preferences/theme.d.ts +18 -0
- package/dist/preferences/theme.d.ts.map +1 -0
- package/dist/preferences/theme.js +63 -0
- package/dist/preferences/theme.js.map +1 -0
- package/dist/provider-lease.d.ts +10 -0
- package/dist/provider-lease.d.ts.map +1 -0
- package/dist/provider-lease.js +42 -0
- package/dist/provider-lease.js.map +1 -0
- package/dist/run-export-ICTZITXC.js +10 -0
- package/dist/run-export-ICTZITXC.js.map +1 -0
- package/dist/settings/index.d.ts +28 -0
- package/dist/settings/index.js +12 -0
- package/dist/settings/index.js.map +1 -0
- package/package.json +24 -9
- package/src/AppMenu/AppMenu.tsx +169 -99
- package/src/AppMenu/index.ts +1 -1
- package/src/DocBlocksShell/DocBlocksShell.tsx +2157 -537
- package/src/DocBlocksShell/LazyEditorShell.ts +2 -0
- package/src/DocBlocksShell/UpdateAvailableNotice.tsx +60 -0
- package/src/DocBlocksShell/document-title.ts +19 -0
- package/src/DocBlocksShell/workspace-authority-barrier.ts +26 -0
- package/src/Export/DeferredExportToolbarControls.tsx +23 -0
- package/src/Export/ExportDialog.tsx +74 -20
- package/src/Export/ExportToolbarControls.tsx +238 -40
- package/src/Export/export-destination.ts +30 -0
- package/src/Export/index.ts +18 -4
- package/src/Export/public-api.ts +17 -0
- package/src/Export/run-export.ts +36 -11
- package/src/FileExplorer/FileExplorer.tsx +284 -38
- package/src/FileExplorer/FileTreeNode.tsx +100 -8
- package/src/FileExplorer/index.ts +1 -1
- package/src/FileExplorer/useFileTree.ts +190 -16
- package/src/Git/GitAuthGuidanceDialog.tsx +84 -0
- package/src/Git/GitBranchesDialog.tsx +144 -0
- package/src/Git/GitCloneDialog.tsx +200 -0
- package/src/Git/GitCommitDialog.tsx +119 -0
- package/src/Git/GitConflictsDialog.tsx +62 -0
- package/src/Git/GitContext.tsx +88 -0
- package/src/Git/GitDiffDialog.tsx +147 -0
- package/src/Git/GitDiffView.tsx +105 -0
- package/src/Git/GitHistoryDialog.tsx +195 -0
- package/src/Git/GitStatusBar.tsx +157 -0
- package/src/Git/GitToolbarControl.tsx +123 -0
- package/src/Git/GitUI.tsx +68 -0
- package/src/Git/commit-selection.ts +63 -0
- package/src/Git/git-clone-utils.ts +23 -0
- package/src/Git/git-status.ts +113 -0
- package/src/Git/useGit.ts +279 -0
- package/src/Git/useGitStatus.ts +78 -0
- package/src/Settings/Settings.tsx +118 -0
- package/src/Settings/public-api.ts +8 -0
- package/src/WorkspacePicker/WorkspacePicker.tsx +62 -8
- package/src/components/Dialog.tsx +81 -0
- package/src/editor.ts +72 -0
- package/src/hooks/index.ts +2 -1
- package/src/hooks/useDocumentSession.ts +37 -0
- package/src/icons.tsx +33 -55
- package/src/index.ts +35 -5
- package/src/preferences/theme.ts +67 -0
- package/src/provider-lease.ts +52 -0
- package/src/styles/docblocks.css +2174 -79
- package/src/types/pwa-dom.d.ts +31 -0
- package/dist/hooks/useAutoSave.d.ts +0 -15
- package/dist/hooks/useAutoSave.d.ts.map +0 -1
- package/dist/hooks/useAutoSave.js +0 -83
- package/dist/hooks/useAutoSave.js.map +0 -1
- package/dist/monaco-slim.d.ts +0 -19
- package/dist/monaco-slim.d.ts.map +0 -1
- package/dist/monaco-slim.js +0 -19
- package/dist/monaco-slim.js.map +0 -1
- package/src/fonts/fonts.css +0 -362
- package/src/fonts/licenses/LICENSE-cormorant-garamond.txt +0 -93
- package/src/fonts/licenses/LICENSE-crimson-text.txt +0 -93
- package/src/fonts/licenses/LICENSE-dm-sans.txt +0 -93
- package/src/fonts/licenses/LICENSE-dm-serif-display.txt +0 -93
- package/src/fonts/licenses/LICENSE-hanken-grotesk.txt +0 -94
- package/src/fonts/licenses/LICENSE-ibm-plex-sans.txt +0 -93
- package/src/fonts/licenses/LICENSE-inter.txt +0 -93
- package/src/fonts/licenses/LICENSE-jetbrains-mono.txt +0 -93
- package/src/fonts/licenses/LICENSE-lora.txt +0 -93
- package/src/fonts/licenses/LICENSE-merriweather.txt +0 -93
- package/src/fonts/licenses/LICENSE-oswald.txt +0 -93
- package/src/fonts/licenses/LICENSE-playfair-display.txt +0 -93
- package/src/fonts/licenses/LICENSE-pt-serif.txt +0 -93
- package/src/fonts/licenses/LICENSE-roboto.txt +0 -93
- package/src/fonts/licenses/LICENSE-source-serif-4.txt +0 -93
- package/src/fonts/woff2/cormorant-garamond-400.woff2 +0 -0
- package/src/fonts/woff2/cormorant-garamond-700.woff2 +0 -0
- package/src/fonts/woff2/crimson-text-400.woff2 +0 -0
- package/src/fonts/woff2/crimson-text-400i.woff2 +0 -0
- package/src/fonts/woff2/crimson-text-700.woff2 +0 -0
- package/src/fonts/woff2/dm-sans-400.woff2 +0 -0
- package/src/fonts/woff2/dm-sans-400i.woff2 +0 -0
- package/src/fonts/woff2/dm-sans-500.woff2 +0 -0
- package/src/fonts/woff2/dm-sans-700.woff2 +0 -0
- package/src/fonts/woff2/dm-serif-display-400.woff2 +0 -0
- package/src/fonts/woff2/hanken-grotesk-400.woff2 +0 -0
- package/src/fonts/woff2/hanken-grotesk-400i.woff2 +0 -0
- package/src/fonts/woff2/hanken-grotesk-500.woff2 +0 -0
- package/src/fonts/woff2/hanken-grotesk-600.woff2 +0 -0
- package/src/fonts/woff2/hanken-grotesk-700.woff2 +0 -0
- package/src/fonts/woff2/ibm-plex-sans-400.woff2 +0 -0
- package/src/fonts/woff2/ibm-plex-sans-400i.woff2 +0 -0
- package/src/fonts/woff2/ibm-plex-sans-500.woff2 +0 -0
- package/src/fonts/woff2/ibm-plex-sans-600.woff2 +0 -0
- package/src/fonts/woff2/ibm-plex-sans-700.woff2 +0 -0
- package/src/fonts/woff2/inter-400.woff2 +0 -0
- package/src/fonts/woff2/inter-400i.woff2 +0 -0
- package/src/fonts/woff2/inter-500.woff2 +0 -0
- package/src/fonts/woff2/inter-600.woff2 +0 -0
- package/src/fonts/woff2/inter-700.woff2 +0 -0
- package/src/fonts/woff2/jetbrains-mono-400.woff2 +0 -0
- package/src/fonts/woff2/jetbrains-mono-400i.woff2 +0 -0
- package/src/fonts/woff2/jetbrains-mono-700.woff2 +0 -0
- package/src/fonts/woff2/lora-400.woff2 +0 -0
- package/src/fonts/woff2/lora-400i.woff2 +0 -0
- package/src/fonts/woff2/lora-700.woff2 +0 -0
- package/src/fonts/woff2/merriweather-400.woff2 +0 -0
- package/src/fonts/woff2/merriweather-700.woff2 +0 -0
- package/src/fonts/woff2/oswald-400.woff2 +0 -0
- package/src/fonts/woff2/oswald-700.woff2 +0 -0
- package/src/fonts/woff2/playfair-display-400.woff2 +0 -0
- package/src/fonts/woff2/playfair-display-700.woff2 +0 -0
- package/src/fonts/woff2/pt-serif-400.woff2 +0 -0
- package/src/fonts/woff2/pt-serif-700.woff2 +0 -0
- package/src/fonts/woff2/roboto-400.woff2 +0 -0
- package/src/fonts/woff2/roboto-400i.woff2 +0 -0
- package/src/fonts/woff2/roboto-700.woff2 +0 -0
- package/src/fonts/woff2/source-serif-4-400.woff2 +0 -0
- package/src/fonts/woff2/source-serif-4-400i.woff2 +0 -0
- package/src/fonts/woff2/source-serif-4-700.woff2 +0 -0
- package/src/hooks/useAutoSave.ts +0 -116
- package/src/monaco-slim.ts +0 -20
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
|
|
3
|
+
const UPDATE_PROMPT_ID = 'db-update-available-prompt';
|
|
4
|
+
|
|
5
|
+
export interface UpdateAvailableNoticeProps {
|
|
6
|
+
available: boolean;
|
|
7
|
+
onApplyUpdate?: () => void;
|
|
8
|
+
blocked?: boolean;
|
|
9
|
+
statusBarVisible: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Keeps PWA updates unobtrusive until the user asks to review one. The
|
|
14
|
+
* notice is positioned over the editor status bar by DocBlocksShell CSS;
|
|
15
|
+
* the existing Reload/Later prompt remains a fixed shell-level card.
|
|
16
|
+
*/
|
|
17
|
+
export function UpdateAvailableNotice({
|
|
18
|
+
available,
|
|
19
|
+
onApplyUpdate,
|
|
20
|
+
blocked = false,
|
|
21
|
+
statusBarVisible,
|
|
22
|
+
}: UpdateAvailableNoticeProps) {
|
|
23
|
+
const [promptOpen, setPromptOpen] = useState(false);
|
|
24
|
+
|
|
25
|
+
if (!available || !onApplyUpdate) return null;
|
|
26
|
+
|
|
27
|
+
const promptVisible = promptOpen && !blocked;
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<>
|
|
31
|
+
<button
|
|
32
|
+
type="button"
|
|
33
|
+
className={`db-update-status-notice${
|
|
34
|
+
statusBarVisible ? '' : ' db-update-status-notice--floating'
|
|
35
|
+
}`}
|
|
36
|
+
onClick={() => setPromptOpen(true)}
|
|
37
|
+
disabled={blocked}
|
|
38
|
+
aria-controls={UPDATE_PROMPT_ID}
|
|
39
|
+
aria-expanded={promptVisible}
|
|
40
|
+
aria-label="A new version of DocBlocks is available. Show update options."
|
|
41
|
+
title="A new version of DocBlocks is available"
|
|
42
|
+
>
|
|
43
|
+
Update available
|
|
44
|
+
</button>
|
|
45
|
+
{promptVisible && (
|
|
46
|
+
<div id={UPDATE_PROMPT_ID} className="db-update-banner" role="alert">
|
|
47
|
+
<span>A new version of DocBlocks is available.</span>
|
|
48
|
+
<div className="db-update-banner-actions">
|
|
49
|
+
<button type="button" onClick={onApplyUpdate}>
|
|
50
|
+
Reload
|
|
51
|
+
</button>
|
|
52
|
+
<button type="button" onClick={() => setPromptOpen(false)}>
|
|
53
|
+
Later
|
|
54
|
+
</button>
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
)}
|
|
58
|
+
</>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
|
+
|
|
3
|
+
const APP_TITLE = 'DocBlocks';
|
|
4
|
+
|
|
5
|
+
export function titleForSelectedFile(selectedFile: string | null): string {
|
|
6
|
+
if (!selectedFile) return APP_TITLE;
|
|
7
|
+
|
|
8
|
+
const filename = selectedFile.replace(/^\/+/, '').split('/').pop();
|
|
9
|
+
const name = filename?.replace(/\.[^.]+$/, '');
|
|
10
|
+
return name ? `${name} - ${APP_TITLE}` : APP_TITLE;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function useDocumentTitle(selectedFile: string | null): void {
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
if (typeof document !== 'undefined') {
|
|
16
|
+
document.title = titleForSelectedFile(selectedFile);
|
|
17
|
+
}
|
|
18
|
+
}, [selectedFile]);
|
|
19
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Renderer-side barrier for main-authoritative Electron workspace identity.
|
|
3
|
+
* OS requests may arrive before IndexedDB descriptors have been reconciled;
|
|
4
|
+
* they must retain navigation priority while waiting for that reconciliation.
|
|
5
|
+
*/
|
|
6
|
+
export class WorkspaceAuthorityBarrier {
|
|
7
|
+
private ready = false;
|
|
8
|
+
private readonly readyPromise: Promise<void>;
|
|
9
|
+
private resolveReady!: () => void;
|
|
10
|
+
|
|
11
|
+
public constructor() {
|
|
12
|
+
this.readyPromise = new Promise<void>((resolve) => {
|
|
13
|
+
this.resolveReady = resolve;
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
public markReady(): void {
|
|
18
|
+
if (this.ready) return;
|
|
19
|
+
this.ready = true;
|
|
20
|
+
this.resolveReady();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
public wait(): Promise<void> {
|
|
24
|
+
return this.readyPromise;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { lazy, Suspense } from 'react';
|
|
2
|
+
import type {
|
|
3
|
+
ExportDestinationAdapter,
|
|
4
|
+
ExportDestinationTarget,
|
|
5
|
+
ExportToolbarControlsProps,
|
|
6
|
+
} from './ExportToolbarControls.js';
|
|
7
|
+
|
|
8
|
+
const ExportToolbarControlsImplementation = lazy(() =>
|
|
9
|
+
import('./ExportToolbarControls.js').then((module) => ({
|
|
10
|
+
default: module.ExportToolbarControls,
|
|
11
|
+
})),
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
/** Public toolbar control with its editor-coupled implementation deferred. */
|
|
15
|
+
export function ExportToolbarControls(props: ExportToolbarControlsProps) {
|
|
16
|
+
return (
|
|
17
|
+
<Suspense fallback={null}>
|
|
18
|
+
<ExportToolbarControlsImplementation {...props} />
|
|
19
|
+
</Suspense>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export type { ExportDestinationAdapter, ExportDestinationTarget, ExportToolbarControlsProps };
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* ExportDialog — modal for choosing export format and options.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { useState, useCallback, useEffect, useRef } from 'react';
|
|
5
|
+
import { useState, useCallback, useEffect, useMemo, useRef } from 'react';
|
|
6
6
|
import { getThemeSummaries } from '@bendyline/squisq/schemas';
|
|
7
7
|
import type { ExportFormat, ExportOptions, HtmlBundle, HtmlStyle } from './export-options.js';
|
|
8
8
|
import { FORMAT_LABELS, saveExportOptions } from './export-options.js';
|
|
@@ -13,12 +13,22 @@ export interface ExportDialogProps {
|
|
|
13
13
|
initial: ExportOptions;
|
|
14
14
|
/** Whether the export is currently running. */
|
|
15
15
|
exporting: boolean;
|
|
16
|
+
/** Optional host-provided destination path control. */
|
|
17
|
+
destination?: ExportDestinationControl;
|
|
16
18
|
/** Called when the user confirms the export. */
|
|
17
19
|
onExport: (options: ExportOptions) => void;
|
|
20
|
+
/** Called whenever the currently selected options change. */
|
|
21
|
+
onOptionsChange?: (options: ExportOptions) => void;
|
|
18
22
|
/** Called when the dialog is dismissed. */
|
|
19
23
|
onClose: () => void;
|
|
20
24
|
}
|
|
21
25
|
|
|
26
|
+
export interface ExportDestinationControl {
|
|
27
|
+
value: string;
|
|
28
|
+
onPick: (options: ExportOptions) => void | Promise<void>;
|
|
29
|
+
hint?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
22
32
|
const FORMATS: ExportFormat[] = ['pdf', 'docx', 'pptx', 'html', 'md'];
|
|
23
33
|
|
|
24
34
|
/** Short labels for the Format chip row. The fuller `FORMAT_LABELS` list
|
|
@@ -96,7 +106,14 @@ function ChipRadioGroup<T extends string>({
|
|
|
96
106
|
);
|
|
97
107
|
}
|
|
98
108
|
|
|
99
|
-
export function ExportDialog({
|
|
109
|
+
export function ExportDialog({
|
|
110
|
+
initial,
|
|
111
|
+
exporting,
|
|
112
|
+
destination,
|
|
113
|
+
onExport,
|
|
114
|
+
onOptionsChange,
|
|
115
|
+
onClose,
|
|
116
|
+
}: ExportDialogProps) {
|
|
100
117
|
const [format, setFormat] = useState<ExportFormat>(initial.format);
|
|
101
118
|
const [themeId, setThemeId] = useState(initial.themeId);
|
|
102
119
|
const [transformStyle, setTransformStyle] = useState(initial.transformStyle);
|
|
@@ -156,8 +173,8 @@ export function ExportDialog({ initial, exporting, onExport, onClose }: ExportDi
|
|
|
156
173
|
};
|
|
157
174
|
}, [showTransform]);
|
|
158
175
|
|
|
159
|
-
const
|
|
160
|
-
|
|
176
|
+
const currentOptions = useMemo<ExportOptions>(
|
|
177
|
+
() => ({
|
|
161
178
|
format,
|
|
162
179
|
themeId,
|
|
163
180
|
transformStyle,
|
|
@@ -166,22 +183,29 @@ export function ExportDialog({ initial, exporting, onExport, onClose }: ExportDi
|
|
|
166
183
|
htmlBundle,
|
|
167
184
|
includeLinkedDocs: showIncludeLinked && includeLinkedDocs,
|
|
168
185
|
entryAsIndex: showEntryAsIndex && entryAsIndex,
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
186
|
+
}),
|
|
187
|
+
[
|
|
188
|
+
format,
|
|
189
|
+
themeId,
|
|
190
|
+
transformStyle,
|
|
191
|
+
pageSize,
|
|
192
|
+
htmlStyle,
|
|
193
|
+
htmlBundle,
|
|
194
|
+
includeLinkedDocs,
|
|
195
|
+
entryAsIndex,
|
|
196
|
+
showIncludeLinked,
|
|
197
|
+
showEntryAsIndex,
|
|
198
|
+
],
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
useEffect(() => {
|
|
202
|
+
onOptionsChange?.(currentOptions);
|
|
203
|
+
}, [currentOptions, onOptionsChange]);
|
|
204
|
+
|
|
205
|
+
const handleExport = useCallback(() => {
|
|
206
|
+
saveExportOptions(currentOptions);
|
|
207
|
+
onExport(currentOptions);
|
|
208
|
+
}, [currentOptions, onExport]);
|
|
185
209
|
|
|
186
210
|
// Close on Escape
|
|
187
211
|
useEffect(() => {
|
|
@@ -226,6 +250,36 @@ export function ExportDialog({ initial, exporting, onExport, onClose }: ExportDi
|
|
|
226
250
|
/>
|
|
227
251
|
</div>
|
|
228
252
|
|
|
253
|
+
{destination && (
|
|
254
|
+
<div className="db-export-field">
|
|
255
|
+
<label className="db-export-label" htmlFor="db-export-path">
|
|
256
|
+
Export to
|
|
257
|
+
</label>
|
|
258
|
+
<div className="db-export-path-row">
|
|
259
|
+
<input
|
|
260
|
+
id="db-export-path"
|
|
261
|
+
className="db-export-path-input"
|
|
262
|
+
value={destination.value}
|
|
263
|
+
readOnly
|
|
264
|
+
disabled={exporting}
|
|
265
|
+
spellCheck={false}
|
|
266
|
+
autoComplete="off"
|
|
267
|
+
/>
|
|
268
|
+
<button
|
|
269
|
+
type="button"
|
|
270
|
+
className="db-export-path-pick"
|
|
271
|
+
onClick={() => void destination.onPick(currentOptions)}
|
|
272
|
+
disabled={exporting}
|
|
273
|
+
aria-label="Choose export location"
|
|
274
|
+
title="Choose export location"
|
|
275
|
+
>
|
|
276
|
+
...
|
|
277
|
+
</button>
|
|
278
|
+
</div>
|
|
279
|
+
{destination.hint && <span className="db-export-hint">{destination.hint}</span>}
|
|
280
|
+
</div>
|
|
281
|
+
)}
|
|
282
|
+
|
|
229
283
|
{/* HTML style + bundle (HTML only) — also chip rows */}
|
|
230
284
|
{showHtmlOptions && (
|
|
231
285
|
<>
|
|
@@ -7,7 +7,16 @@
|
|
|
7
7
|
* Must be rendered inside <EditorProvider> so useEditorContext() works.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
lazy,
|
|
12
|
+
Suspense,
|
|
13
|
+
useState,
|
|
14
|
+
useCallback,
|
|
15
|
+
useEffect,
|
|
16
|
+
useRef,
|
|
17
|
+
useMemo,
|
|
18
|
+
type ComponentType,
|
|
19
|
+
} from 'react';
|
|
11
20
|
import { useEditorContext } from '@bendyline/squisq-editor-react';
|
|
12
21
|
import { getThemeSummaries } from '@bendyline/squisq/schemas';
|
|
13
22
|
import { parseMarkdown } from '@bendyline/squisq/markdown';
|
|
@@ -20,15 +29,53 @@ import {
|
|
|
20
29
|
loadLastExportOptions,
|
|
21
30
|
saveExportOptions,
|
|
22
31
|
} from './export-options.js';
|
|
23
|
-
import {
|
|
24
|
-
import { runExport } from './run-export.js';
|
|
32
|
+
import type { ExportBlobSaver } from './run-export.js';
|
|
25
33
|
import { loadTransformStyleSummaries, type ExportSummaryOption } from './transform-summaries.js';
|
|
26
34
|
|
|
35
|
+
const ExportDialog = lazy(() =>
|
|
36
|
+
import('./ExportDialog.js').then((module) => ({ default: module.ExportDialog })),
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
let runExportModulePromise: Promise<typeof import('./run-export.js')> | null = null;
|
|
40
|
+
|
|
41
|
+
function loadRunExportModule(): Promise<typeof import('./run-export.js')> {
|
|
42
|
+
runExportModulePromise ??= import('./run-export.js');
|
|
43
|
+
return runExportModulePromise;
|
|
44
|
+
}
|
|
45
|
+
|
|
27
46
|
export interface ExportToolbarControlsProps {
|
|
28
47
|
/** Currently selected file path — used to derive the download filename. */
|
|
29
48
|
selectedFile: string | null;
|
|
30
49
|
/** Media container for resolving images during export. */
|
|
31
50
|
mediaContainer?: ContentContainer | null;
|
|
51
|
+
/** Override the default browser download behavior for host-provided save flows. */
|
|
52
|
+
saveBlob?: ExportBlobSaver;
|
|
53
|
+
/** Optional host adapter for displaying, picking, and saving to a native target path. */
|
|
54
|
+
destinationAdapter?: ExportDestinationAdapter;
|
|
55
|
+
/** Render the trigger as a direct Export button instead of the overflow menu. */
|
|
56
|
+
trigger?: 'menu' | 'button';
|
|
57
|
+
/** Whether to show video export in the overflow menu. */
|
|
58
|
+
showVideoExport?: boolean;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Host-specific operations behind the shared export destination control. */
|
|
62
|
+
export interface ExportDestinationTarget {
|
|
63
|
+
grantId: string | null;
|
|
64
|
+
displayPath: string;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface ExportDestinationAdapter {
|
|
68
|
+
resolveTarget: (filename: string) => Promise<ExportDestinationTarget>;
|
|
69
|
+
pickTarget: (
|
|
70
|
+
filename: string,
|
|
71
|
+
currentTarget?: ExportDestinationTarget | null,
|
|
72
|
+
) => Promise<ExportDestinationTarget | null>;
|
|
73
|
+
saveBlob: (
|
|
74
|
+
blob: Blob,
|
|
75
|
+
filename: string,
|
|
76
|
+
target?: ExportDestinationTarget | null,
|
|
77
|
+
) => Promise<ExportDestinationTarget | null>;
|
|
78
|
+
hint?: string;
|
|
32
79
|
}
|
|
33
80
|
|
|
34
81
|
type ParsedMarkdown = ReturnType<typeof parseMarkdown>;
|
|
@@ -92,6 +139,10 @@ function quickLabel(opts: ExportOptions, transformSummaries: ExportSummaryOption
|
|
|
92
139
|
export function ExportToolbarControls({
|
|
93
140
|
selectedFile,
|
|
94
141
|
mediaContainer,
|
|
142
|
+
saveBlob,
|
|
143
|
+
destinationAdapter,
|
|
144
|
+
trigger = 'menu',
|
|
145
|
+
showVideoExport = true,
|
|
95
146
|
}: ExportToolbarControlsProps) {
|
|
96
147
|
const { markdownSource, markdownDoc } = useEditorContext();
|
|
97
148
|
const [menuOpen, setMenuOpen] = useState(false);
|
|
@@ -103,6 +154,8 @@ export function ExportToolbarControls({
|
|
|
103
154
|
const [videoDoc, setVideoDoc] = useState<VideoExportModalProps['doc'] | null>(null);
|
|
104
155
|
const [transformSummaries, setTransformSummaries] = useState<ExportSummaryOption[]>([]);
|
|
105
156
|
const [exporting, setExporting] = useState(false);
|
|
157
|
+
const [destinationTarget, setDestinationTarget] = useState<ExportDestinationTarget | null>(null);
|
|
158
|
+
const destinationRequestRef = useRef(0);
|
|
106
159
|
const menuRef = useRef<HTMLDivElement>(null);
|
|
107
160
|
|
|
108
161
|
const lastOptions = loadLastExportOptions();
|
|
@@ -174,10 +227,30 @@ export function ExportToolbarControls({
|
|
|
174
227
|
setMenuOpen((prev) => !prev);
|
|
175
228
|
}, []);
|
|
176
229
|
|
|
230
|
+
const refreshDestination = useCallback(
|
|
231
|
+
async (options: ExportOptions) => {
|
|
232
|
+
if (!destinationAdapter) return;
|
|
233
|
+
|
|
234
|
+
const requestId = destinationRequestRef.current + 1;
|
|
235
|
+
destinationRequestRef.current = requestId;
|
|
236
|
+
|
|
237
|
+
try {
|
|
238
|
+
const { buildExportFilename } = await loadRunExportModule();
|
|
239
|
+
const filename = buildExportFilename(selectedFile, options);
|
|
240
|
+
const target = await destinationAdapter.resolveTarget(filename);
|
|
241
|
+
if (destinationRequestRef.current === requestId) setDestinationTarget(target);
|
|
242
|
+
} catch {
|
|
243
|
+
if (destinationRequestRef.current === requestId) setDestinationTarget(null);
|
|
244
|
+
}
|
|
245
|
+
},
|
|
246
|
+
[destinationAdapter, selectedFile],
|
|
247
|
+
);
|
|
248
|
+
|
|
177
249
|
const handleOpenDialog = useCallback(() => {
|
|
178
250
|
setMenuOpen(false);
|
|
179
251
|
setDialogOpen(true);
|
|
180
|
-
|
|
252
|
+
void refreshDestination(dialogInitial);
|
|
253
|
+
}, [dialogInitial, refreshDestination]);
|
|
181
254
|
|
|
182
255
|
const handleCloseDialog = useCallback(() => {
|
|
183
256
|
setDialogOpen(false);
|
|
@@ -206,17 +279,70 @@ export function ExportToolbarControls({
|
|
|
206
279
|
setVideoLoadError(null);
|
|
207
280
|
}, []);
|
|
208
281
|
|
|
282
|
+
const handleOptionsChange = useCallback(
|
|
283
|
+
(options: ExportOptions) => {
|
|
284
|
+
void refreshDestination(options);
|
|
285
|
+
},
|
|
286
|
+
[refreshDestination],
|
|
287
|
+
);
|
|
288
|
+
|
|
289
|
+
const handlePickDestination = useCallback(
|
|
290
|
+
async (options: ExportOptions) => {
|
|
291
|
+
if (!destinationAdapter) return;
|
|
292
|
+
try {
|
|
293
|
+
const { buildExportFilename } = await loadRunExportModule();
|
|
294
|
+
const filename = buildExportFilename(selectedFile, options);
|
|
295
|
+
const pickedTarget = await destinationAdapter.pickTarget(filename, destinationTarget);
|
|
296
|
+
if (pickedTarget === null) return;
|
|
297
|
+
setDestinationTarget(pickedTarget);
|
|
298
|
+
} catch {
|
|
299
|
+
// Native hosts surface picker failures through their own UI channel.
|
|
300
|
+
}
|
|
301
|
+
},
|
|
302
|
+
[destinationAdapter, destinationTarget, selectedFile],
|
|
303
|
+
);
|
|
304
|
+
|
|
305
|
+
const saveToDestination = useCallback(
|
|
306
|
+
async (blob: Blob, filename: string, target: ExportDestinationTarget | null) => {
|
|
307
|
+
if (!destinationAdapter) return;
|
|
308
|
+
const savedTarget = await destinationAdapter.saveBlob(blob, filename, target);
|
|
309
|
+
if (savedTarget) setDestinationTarget(savedTarget);
|
|
310
|
+
},
|
|
311
|
+
[destinationAdapter],
|
|
312
|
+
);
|
|
313
|
+
|
|
314
|
+
const handleDestinationSaveBlob = useCallback(
|
|
315
|
+
async (blob: Blob, filename: string) => {
|
|
316
|
+
await saveToDestination(blob, filename, destinationTarget);
|
|
317
|
+
},
|
|
318
|
+
[destinationTarget, saveToDestination],
|
|
319
|
+
);
|
|
320
|
+
|
|
209
321
|
const handleExport = useCallback(
|
|
210
322
|
async (opts: ExportOptions) => {
|
|
211
323
|
setExporting(true);
|
|
212
324
|
try {
|
|
213
|
-
|
|
325
|
+
const { runExport } = await loadRunExportModule();
|
|
326
|
+
await runExport(
|
|
327
|
+
markdownSource,
|
|
328
|
+
selectedFile,
|
|
329
|
+
opts,
|
|
330
|
+
mediaContainer,
|
|
331
|
+
destinationAdapter ? handleDestinationSaveBlob : saveBlob,
|
|
332
|
+
);
|
|
214
333
|
} finally {
|
|
215
334
|
setExporting(false);
|
|
216
335
|
setDialogOpen(false);
|
|
217
336
|
}
|
|
218
337
|
},
|
|
219
|
-
[
|
|
338
|
+
[
|
|
339
|
+
markdownSource,
|
|
340
|
+
selectedFile,
|
|
341
|
+
mediaContainer,
|
|
342
|
+
destinationAdapter,
|
|
343
|
+
handleDestinationSaveBlob,
|
|
344
|
+
saveBlob,
|
|
345
|
+
],
|
|
220
346
|
);
|
|
221
347
|
|
|
222
348
|
const handleQuickExport = useCallback(async () => {
|
|
@@ -225,55 +351,108 @@ export function ExportToolbarControls({
|
|
|
225
351
|
setExporting(true);
|
|
226
352
|
try {
|
|
227
353
|
saveExportOptions(lastOptions);
|
|
228
|
-
|
|
354
|
+
const { runExport } = await loadRunExportModule();
|
|
355
|
+
let quickTarget: ExportDestinationTarget | null = null;
|
|
356
|
+
if (destinationAdapter) {
|
|
357
|
+
const { buildExportFilename } = await loadRunExportModule();
|
|
358
|
+
quickTarget = await destinationAdapter.resolveTarget(
|
|
359
|
+
buildExportFilename(selectedFile, lastOptions),
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
await runExport(
|
|
363
|
+
markdownSource,
|
|
364
|
+
selectedFile,
|
|
365
|
+
lastOptions,
|
|
366
|
+
mediaContainer,
|
|
367
|
+
destinationAdapter
|
|
368
|
+
? async (blob, filename) => saveToDestination(blob, filename, quickTarget)
|
|
369
|
+
: saveBlob,
|
|
370
|
+
);
|
|
229
371
|
} finally {
|
|
230
372
|
setExporting(false);
|
|
231
373
|
}
|
|
232
|
-
}, [
|
|
374
|
+
}, [
|
|
375
|
+
destinationAdapter,
|
|
376
|
+
lastOptions,
|
|
377
|
+
markdownSource,
|
|
378
|
+
mediaContainer,
|
|
379
|
+
saveBlob,
|
|
380
|
+
saveToDestination,
|
|
381
|
+
selectedFile,
|
|
382
|
+
]);
|
|
233
383
|
|
|
234
384
|
const LoadedVideoExportModal = videoModules?.Modal;
|
|
235
385
|
|
|
236
386
|
return (
|
|
237
387
|
<>
|
|
238
|
-
|
|
388
|
+
{trigger === 'button' ? (
|
|
239
389
|
<button
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
390
|
+
type="button"
|
|
391
|
+
className="squisq-toolbar-button db-toolbar-export-trigger"
|
|
392
|
+
onClick={handleOpenDialog}
|
|
393
|
+
disabled={exporting}
|
|
394
|
+
aria-label="Export document"
|
|
395
|
+
data-tooltip={exporting ? 'Exporting...' : 'Export document'}
|
|
244
396
|
>
|
|
245
|
-
|
|
397
|
+
<ExportGlyph />
|
|
246
398
|
</button>
|
|
399
|
+
) : (
|
|
400
|
+
<div className="db-toolbar-menu" ref={menuRef}>
|
|
401
|
+
<button
|
|
402
|
+
className="db-toolbar-menu-trigger"
|
|
403
|
+
onClick={handleToggleMenu}
|
|
404
|
+
aria-label="More actions"
|
|
405
|
+
title="More actions"
|
|
406
|
+
>
|
|
407
|
+
···
|
|
408
|
+
</button>
|
|
247
409
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
410
|
+
{menuOpen && (
|
|
411
|
+
<div className="db-toolbar-menu-dropdown">
|
|
412
|
+
{lastOptions && (
|
|
413
|
+
<button
|
|
414
|
+
className="db-toolbar-menu-item"
|
|
415
|
+
onClick={handleQuickExport}
|
|
416
|
+
disabled={exporting}
|
|
417
|
+
>
|
|
418
|
+
{quickLabel(lastOptions, transformSummaries)}
|
|
419
|
+
</button>
|
|
420
|
+
)}
|
|
421
|
+
<button className="db-toolbar-menu-item" onClick={handleOpenDialog}>
|
|
422
|
+
Export...
|
|
257
423
|
</button>
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
424
|
+
{showVideoExport && (
|
|
425
|
+
<>
|
|
426
|
+
<div className="db-toolbar-menu-divider" />
|
|
427
|
+
<button className="db-toolbar-menu-item" onClick={handleOpenVideoModal}>
|
|
428
|
+
Export Video...
|
|
429
|
+
</button>
|
|
430
|
+
</>
|
|
431
|
+
)}
|
|
432
|
+
</div>
|
|
433
|
+
)}
|
|
434
|
+
</div>
|
|
435
|
+
)}
|
|
269
436
|
|
|
270
437
|
{dialogOpen && (
|
|
271
|
-
<
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
438
|
+
<Suspense fallback={null}>
|
|
439
|
+
<ExportDialog
|
|
440
|
+
initial={dialogInitial}
|
|
441
|
+
exporting={exporting}
|
|
442
|
+
destination={
|
|
443
|
+
destinationAdapter
|
|
444
|
+
? {
|
|
445
|
+
value: destinationTarget?.displayPath ?? '',
|
|
446
|
+
onPick: handlePickDestination,
|
|
447
|
+
hint: destinationAdapter.hint,
|
|
448
|
+
}
|
|
449
|
+
: undefined
|
|
450
|
+
}
|
|
451
|
+
onExport={handleExport}
|
|
452
|
+
onOptionsChange={destinationAdapter ? handleOptionsChange : undefined}
|
|
453
|
+
onClose={handleCloseDialog}
|
|
454
|
+
/>
|
|
455
|
+
</Suspense>
|
|
277
456
|
)}
|
|
278
457
|
|
|
279
458
|
{videoModalOpen && (videoLoading || videoLoadError) && (
|
|
@@ -311,3 +490,22 @@ export function ExportToolbarControls({
|
|
|
311
490
|
</>
|
|
312
491
|
);
|
|
313
492
|
}
|
|
493
|
+
|
|
494
|
+
function ExportGlyph() {
|
|
495
|
+
return (
|
|
496
|
+
<svg
|
|
497
|
+
viewBox="0 0 16 16"
|
|
498
|
+
fill="none"
|
|
499
|
+
stroke="currentColor"
|
|
500
|
+
strokeWidth="1.5"
|
|
501
|
+
strokeLinecap="round"
|
|
502
|
+
strokeLinejoin="round"
|
|
503
|
+
aria-hidden="true"
|
|
504
|
+
>
|
|
505
|
+
<path d="M4.5 1.75h4.25L12 5v7.25a1.5 1.5 0 0 1-1.5 1.5h-6A1.5 1.5 0 0 1 3 12.25v-9a1.5 1.5 0 0 1 1.5-1.5z" />
|
|
506
|
+
<path d="M8.75 1.75V5h3.25" />
|
|
507
|
+
<path d="M6 8.75h4.5" />
|
|
508
|
+
<path d="M8.75 7 10.5 8.75 8.75 10.5" />
|
|
509
|
+
</svg>
|
|
510
|
+
);
|
|
511
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/** Keep a user-edited target name while switching the selected export format. */
|
|
2
|
+
export function updateExportTargetExtension(targetPath: string, suggestedFilename: string): string {
|
|
3
|
+
const nextExtension = extensionOf(suggestedFilename);
|
|
4
|
+
if (!nextExtension) return targetPath;
|
|
5
|
+
if (!targetPath.trim()) return suggestedFilename;
|
|
6
|
+
|
|
7
|
+
const suffixIndex = firstSuffixIndex(targetPath);
|
|
8
|
+
const pathPart = suffixIndex === -1 ? targetPath : targetPath.slice(0, suffixIndex);
|
|
9
|
+
const suffix = suffixIndex === -1 ? '' : targetPath.slice(suffixIndex);
|
|
10
|
+
const separatorIndex = Math.max(pathPart.lastIndexOf('/'), pathPart.lastIndexOf('\\'));
|
|
11
|
+
const dotIndex = pathPart.lastIndexOf('.');
|
|
12
|
+
const stem = dotIndex > separatorIndex ? pathPart.slice(0, dotIndex) : pathPart;
|
|
13
|
+
return `${stem}${nextExtension}${suffix}`;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function extensionOf(value: string): string {
|
|
17
|
+
const suffixIndex = firstSuffixIndex(value);
|
|
18
|
+
const pathPart = suffixIndex === -1 ? value : value.slice(0, suffixIndex);
|
|
19
|
+
const separatorIndex = Math.max(pathPart.lastIndexOf('/'), pathPart.lastIndexOf('\\'));
|
|
20
|
+
const dotIndex = pathPart.lastIndexOf('.');
|
|
21
|
+
return dotIndex > separatorIndex ? pathPart.slice(dotIndex) : '';
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function firstSuffixIndex(value: string): number {
|
|
25
|
+
const query = value.indexOf('?');
|
|
26
|
+
const fragment = value.indexOf('#');
|
|
27
|
+
if (query === -1) return fragment;
|
|
28
|
+
if (fragment === -1) return query;
|
|
29
|
+
return Math.min(query, fragment);
|
|
30
|
+
}
|