@rightkit/document-workspace 0.0.0-stage → 0.1.1
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/CHANGELOG.md +11 -0
- package/LICENSE-APACHE +201 -0
- package/LICENSE-MIT +21 -0
- package/README.md +74 -2
- package/dist/app/App.d.ts +1 -0
- package/dist/app/App.js +6 -0
- package/dist/app/AppDialogs.d.ts +52 -0
- package/dist/app/AppDialogs.js +23 -0
- package/dist/app/AppDialogsHost.d.ts +31 -0
- package/dist/app/AppDialogsHost.js +30 -0
- package/dist/app/AppLifecycle.d.ts +19 -0
- package/dist/app/AppLifecycle.js +87 -0
- package/dist/app/AppProvider.d.ts +317 -0
- package/dist/app/AppProvider.js +217 -0
- package/dist/app/AppRoot.d.ts +8 -0
- package/dist/app/AppRoot.js +159 -0
- package/dist/app/AppShell.d.ts +2 -0
- package/dist/app/AppShell.js +81 -0
- package/dist/app/CommandPalette.d.ts +13 -0
- package/dist/app/CommandPalette.js +47 -0
- package/dist/app/DocActionBar.d.ts +36 -0
- package/dist/app/DocActionBar.js +300 -0
- package/dist/app/DocumentContextMenu.d.ts +29 -0
- package/dist/app/DocumentContextMenu.js +122 -0
- package/dist/app/DocumentPane.d.ts +52 -0
- package/dist/app/DocumentPane.js +30 -0
- package/dist/app/DocumentRouterTable.d.ts +58 -0
- package/dist/app/DocumentRouterTable.js +143 -0
- package/dist/app/ErrorBoundary.d.ts +12 -0
- package/dist/app/ErrorBoundary.js +16 -0
- package/dist/app/FindBar.d.ts +12 -0
- package/dist/app/FindBar.js +199 -0
- package/dist/app/LiveTitle.d.ts +14 -0
- package/dist/app/LiveTitle.js +14 -0
- package/dist/app/OpeningProgress.d.ts +13 -0
- package/dist/app/OpeningProgress.js +21 -0
- package/dist/app/PrintPreviewWindow.d.ts +2 -0
- package/dist/app/PrintPreviewWindow.js +57 -0
- package/dist/app/ShortcutHelp.d.ts +3 -0
- package/dist/app/ShortcutHelp.js +99 -0
- package/dist/app/Sidebar.d.ts +36 -0
- package/dist/app/Sidebar.js +121 -0
- package/dist/app/ToastViewport.d.ts +1 -0
- package/dist/app/ToastViewport.js +31 -0
- package/dist/app/Toolbar.d.ts +26 -0
- package/dist/app/Toolbar.js +63 -0
- package/dist/app/activeFormatSavers.d.ts +7 -0
- package/dist/app/activeFormatSavers.js +4 -0
- package/dist/app/clipboardPaths.d.ts +1 -0
- package/dist/app/clipboardPaths.js +31 -0
- package/dist/app/documentDrafts.d.ts +17 -0
- package/dist/app/documentDrafts.js +104 -0
- package/dist/app/draftResolution.d.ts +5 -0
- package/dist/app/draftResolution.js +25 -0
- package/dist/app/markdownPaletteActions.d.ts +8 -0
- package/dist/app/markdownPaletteActions.js +107 -0
- package/dist/app/resourceCensusDev.d.ts +41 -0
- package/dist/app/resourceCensusDev.js +518 -0
- package/dist/app/setupRecovery.d.ts +34 -0
- package/dist/app/setupRecovery.js +70 -0
- package/dist/app/shell/DocStatsPill.d.ts +18 -0
- package/dist/app/shell/DocStatsPill.js +32 -0
- package/dist/app/shell/DocumentTools.d.ts +9 -0
- package/dist/app/shell/DocumentTools.js +73 -0
- package/dist/app/shell/InfoInspector.d.ts +9 -0
- package/dist/app/shell/InfoInspector.js +67 -0
- package/dist/app/shell/StartPage.d.ts +9 -0
- package/dist/app/shell/StartPage.js +49 -0
- package/dist/app/shell/readEdit.d.ts +6 -0
- package/dist/app/shell/readEdit.js +6 -0
- package/dist/app/sidebar/SidebarDocumentMenu.d.ts +20 -0
- package/dist/app/sidebar/SidebarDocumentMenu.js +56 -0
- package/dist/app/sidebar/SidebarExpandedPanel.d.ts +9 -0
- package/dist/app/sidebar/SidebarExpandedPanel.js +78 -0
- package/dist/app/sidebar/SidebarIcons.d.ts +3 -0
- package/dist/app/sidebar/SidebarIcons.js +18 -0
- package/dist/app/sidebar/SidebarMenus.d.ts +19 -0
- package/dist/app/sidebar/SidebarMenus.js +64 -0
- package/dist/app/sidebar/SidebarPathUtils.d.ts +10 -0
- package/dist/app/sidebar/SidebarPathUtils.js +55 -0
- package/dist/app/sidebar/SidebarPrimitives.d.ts +12 -0
- package/dist/app/sidebar/SidebarPrimitives.js +45 -0
- package/dist/app/sidebar/SidebarRail.d.ts +5 -0
- package/dist/app/sidebar/SidebarRail.js +37 -0
- package/dist/app/sidebar/explorer/ExplorerRowMenu.d.ts +21 -0
- package/dist/app/sidebar/explorer/ExplorerRowMenu.js +37 -0
- package/dist/app/sidebar/explorer/ExplorerSections.d.ts +16 -0
- package/dist/app/sidebar/explorer/ExplorerSections.js +259 -0
- package/dist/app/sidebar/explorer/ExplorerTree.d.ts +46 -0
- package/dist/app/sidebar/explorer/ExplorerTree.js +387 -0
- package/dist/app/sidebar/explorer/explorerState.d.ts +37 -0
- package/dist/app/sidebar/explorer/explorerState.js +135 -0
- package/dist/app/tabMountPolicy.d.ts +45 -0
- package/dist/app/tabMountPolicy.js +104 -0
- package/dist/app/tabWarmOrder.d.ts +7 -0
- package/dist/app/tabWarmOrder.js +48 -0
- package/dist/app/tabs/TabFuseCurves.d.ts +3 -0
- package/dist/app/tabs/TabFuseCurves.js +15 -0
- package/dist/app/tabs/TabOverview.d.ts +24 -0
- package/dist/app/tabs/TabOverview.js +121 -0
- package/dist/app/tabs/TabStrip.d.ts +42 -0
- package/dist/app/tabs/TabStrip.js +380 -0
- package/dist/app/tabs/tabIds.d.ts +5 -0
- package/dist/app/tabs/tabIds.js +7 -0
- package/dist/app/tabs/useTabDirty.d.ts +3 -0
- package/dist/app/tabs/useTabDirty.js +11 -0
- package/dist/app/useAppChromeState.d.ts +21 -0
- package/dist/app/useAppChromeState.js +48 -0
- package/dist/app/useAppCommands.d.ts +66 -0
- package/dist/app/useAppCommands.js +71 -0
- package/dist/app/useAppDocumentCore.d.ts +210 -0
- package/dist/app/useAppDocumentCore.js +40 -0
- package/dist/app/useAppEffects.d.ts +18 -0
- package/dist/app/useAppEffects.js +52 -0
- package/dist/app/useAppPaneSearch.d.ts +37 -0
- package/dist/app/useAppPaneSearch.js +74 -0
- package/dist/app/useAppShellSetup.d.ts +40 -0
- package/dist/app/useAppShellSetup.js +52 -0
- package/dist/app/useAppSlots.d.ts +28 -0
- package/dist/app/useAppSlots.js +40 -0
- package/dist/app/useAppTabChrome.d.ts +51 -0
- package/dist/app/useAppTabChrome.js +66 -0
- package/dist/app/useAutoSave.d.ts +34 -0
- package/dist/app/useAutoSave.js +116 -0
- package/dist/app/useCloseAll.d.ts +52 -0
- package/dist/app/useCloseAll.js +295 -0
- package/dist/app/useClosedTabEviction.d.ts +10 -0
- package/dist/app/useClosedTabEviction.js +38 -0
- package/dist/app/useCommandPalette.d.ts +44 -0
- package/dist/app/useCommandPalette.js +254 -0
- package/dist/app/useCrossDocSearch.d.ts +7 -0
- package/dist/app/useCrossDocSearch.js +94 -0
- package/dist/app/useDistractionFree.d.ts +21 -0
- package/dist/app/useDistractionFree.js +73 -0
- package/dist/app/useDocumentCreation.d.ts +18 -0
- package/dist/app/useDocumentCreation.js +63 -0
- package/dist/app/useDocumentDirty.d.ts +31 -0
- package/dist/app/useDocumentDirty.js +46 -0
- package/dist/app/useDocumentLoader.d.ts +55 -0
- package/dist/app/useDocumentLoader.js +267 -0
- package/dist/app/useDocumentOutputActions.d.ts +6 -0
- package/dist/app/useDocumentOutputActions.js +45 -0
- package/dist/app/useDocumentRuntimeEffects.d.ts +36 -0
- package/dist/app/useDocumentRuntimeEffects.js +458 -0
- package/dist/app/useDocumentSaver.d.ts +58 -0
- package/dist/app/useDocumentSaver.js +511 -0
- package/dist/app/useDocumentTabs.d.ts +41 -0
- package/dist/app/useDocumentTabs.js +207 -0
- package/dist/app/useDocumentWatcher.d.ts +83 -0
- package/dist/app/useDocumentWatcher.js +393 -0
- package/dist/app/useDocumentZoom.d.ts +24 -0
- package/dist/app/useDocumentZoom.js +104 -0
- package/dist/app/useDocuments.d.ts +169 -0
- package/dist/app/useDocuments.js +455 -0
- package/dist/app/useDraftConfirmGate.d.ts +27 -0
- package/dist/app/useDraftConfirmGate.js +47 -0
- package/dist/app/useFileDragOverlay.d.ts +12 -0
- package/dist/app/useFileDragOverlay.js +49 -0
- package/dist/app/useFormatApis.d.ts +13 -0
- package/dist/app/useFormatApis.js +62 -0
- package/dist/app/useFormatDirtyState.d.ts +29 -0
- package/dist/app/useFormatDirtyState.js +81 -0
- package/dist/app/useGlobalShortcuts.d.ts +47 -0
- package/dist/app/useGlobalShortcuts.js +359 -0
- package/dist/app/useHtmlSession.d.ts +68 -0
- package/dist/app/useHtmlSession.js +463 -0
- package/dist/app/useHtmlSessionProps.d.ts +33 -0
- package/dist/app/useHtmlSessionProps.js +57 -0
- package/dist/app/useLibrary.d.ts +21 -0
- package/dist/app/useLibrary.js +81 -0
- package/dist/app/useOpenFromClipboard.d.ts +7 -0
- package/dist/app/useOpenFromClipboard.js +44 -0
- package/dist/app/usePaneLiveness.d.ts +29 -0
- package/dist/app/usePaneLiveness.js +199 -0
- package/dist/app/usePendingSavePaths.d.ts +19 -0
- package/dist/app/usePendingSavePaths.js +67 -0
- package/dist/app/useReadyShow.d.ts +12 -0
- package/dist/app/useReadyShow.js +142 -0
- package/dist/app/useSearchController.d.ts +22 -0
- package/dist/app/useSearchController.js +114 -0
- package/dist/app/useSetupRecovery.d.ts +41 -0
- package/dist/app/useSetupRecovery.js +153 -0
- package/dist/app/useTabChrome.d.ts +40 -0
- package/dist/app/useTabChrome.js +115 -0
- package/dist/app/useViewState.d.ts +27 -0
- package/dist/app/useViewState.js +160 -0
- package/dist/appName.d.ts +2 -0
- package/dist/appName.js +8 -0
- package/dist/documents/DocumentRouter.d.ts +102 -0
- package/dist/documents/DocumentRouter.js +153 -0
- package/dist/documents/FileTypeIcon.d.ts +6 -0
- package/dist/documents/FileTypeIcon.js +93 -0
- package/dist/documents/ImageDocument.d.ts +14 -0
- package/dist/documents/ImageDocument.js +61 -0
- package/dist/documents/capabilities.d.ts +17 -0
- package/dist/documents/capabilities.js +18 -0
- package/dist/documents/html/HtmlDocument.d.ts +15 -0
- package/dist/documents/html/HtmlDocument.js +584 -0
- package/dist/documents/html/HtmlDocumentLayout.d.ts +87 -0
- package/dist/documents/html/HtmlDocumentLayout.js +25 -0
- package/dist/documents/html/HtmlDocumentProps.d.ts +60 -0
- package/dist/documents/html/HtmlDocumentProps.js +1 -0
- package/dist/documents/html/HtmlDocumentSource.d.ts +34 -0
- package/dist/documents/html/HtmlDocumentSource.js +28 -0
- package/dist/documents/markdown/MarkdownSplitPane.d.ts +115 -0
- package/dist/documents/markdown/MarkdownSplitPane.js +352 -0
- package/dist/documents/mdx/MdxSplitPane.d.ts +32 -0
- package/dist/documents/mdx/MdxSplitPane.js +60 -0
- package/dist/documents/txt/LinkDialog.d.ts +8 -0
- package/dist/documents/txt/LinkDialog.js +34 -0
- package/dist/documents/txt/MarkdownSourceToolbar.d.ts +8 -0
- package/dist/documents/txt/MarkdownSourceToolbar.js +25 -0
- package/dist/documents/txt/SourceEditorToolbar.d.ts +12 -0
- package/dist/documents/txt/SourceEditorToolbar.js +4 -0
- package/dist/documents/txt/TxtEditor.d.ts +39 -0
- package/dist/documents/txt/TxtEditor.js +186 -0
- package/dist/documents/txt/sourceEditorTypes.d.ts +49 -0
- package/dist/documents/txt/sourceEditorTypes.js +1 -0
- package/dist/documents/txt/sourceExtensions.d.ts +38 -0
- package/dist/documents/txt/sourceExtensions.js +444 -0
- package/dist/documents/txt/sourceFormatting.d.ts +18 -0
- package/dist/documents/txt/sourceFormatting.js +183 -0
- package/dist/documents/txt/sourceTheme.d.ts +5 -0
- package/dist/documents/txt/sourceTheme.js +220 -0
- package/dist/documents/txt/useCodeMirrorSourceEditor.d.ts +69 -0
- package/dist/documents/txt/useCodeMirrorSourceEditor.js +546 -0
- package/dist/documents/txt/useSourceSearch.d.ts +39 -0
- package/dist/documents/txt/useSourceSearch.js +122 -0
- package/dist/documents/txt/useTextEditorCommands.d.ts +26 -0
- package/dist/documents/txt/useTextEditorCommands.js +151 -0
- package/dist/documents/web/LivePreviewPane.d.ts +11 -0
- package/dist/documents/web/LivePreviewPane.js +58 -0
- package/dist/env.d.ts +66 -0
- package/dist/index.d.ts +121 -0
- package/dist/index.js +123 -0
- package/dist/settings/AppearanceMenu.d.ts +9 -0
- package/dist/settings/AppearanceMenu.js +54 -0
- package/dist/settings/SettingsPanel.d.ts +7 -0
- package/dist/settings/SettingsPanel.js +104 -0
- package/dist/settings/publishedSections.d.ts +22 -0
- package/dist/settings/publishedSections.js +79 -0
- package/dist/settings/sections/AboutSection.d.ts +1 -0
- package/dist/settings/sections/AboutSection.js +13 -0
- package/dist/settings/sections/DiagnosticsSection.d.ts +1 -0
- package/dist/settings/sections/DiagnosticsSection.js +42 -0
- package/dist/settings/sections/PrivacySection.d.ts +3 -0
- package/dist/settings/sections/PrivacySection.js +51 -0
- package/dist/settings/sections/UpdateLicenseBlock.d.ts +10 -0
- package/dist/settings/sections/UpdateLicenseBlock.js +158 -0
- package/dist/state/explorer.d.ts +32 -0
- package/dist/state/explorer.js +111 -0
- package/dist/state/legal.d.ts +51 -0
- package/dist/state/legal.js +130 -0
- package/dist/state/library.d.ts +33 -0
- package/dist/state/library.js +110 -0
- package/dist/state/updateLicense.d.ts +31 -0
- package/dist/state/updateLicense.js +340 -0
- package/dist/styles/explorer.css +219 -0
- package/dist/styles/index.css +1477 -0
- package/dist/styles/tabs.css +467 -0
- package/dist/styles.css +6 -0
- package/package.json +88 -3
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef, } from "react";
|
|
2
|
+
import { isUntitled } from "@rightkit/document-core/documents/types";
|
|
3
|
+
import { isWebTabKey } from "@rightkit/document-core/documents/web/webUrl";
|
|
4
|
+
import { fileFingerprint, readDocument, revokePath } from "@rightkit/document-core/state/tauriBridge";
|
|
5
|
+
import { clearDraft, clearFileDraft } from "./documentDrafts.js";
|
|
6
|
+
import { toast } from "@rightkit/platform-ui/headless";
|
|
7
|
+
import { resolveDocumentDrafts } from "./draftResolution.js";
|
|
8
|
+
const CLOSED_STACK_MAX = 10;
|
|
9
|
+
const EMPTY_PENDING_SAVE_PATHS = new Set();
|
|
10
|
+
function hasSynchronousPendingPath(path, primary, mirrors) {
|
|
11
|
+
if (primary?.current.has(path))
|
|
12
|
+
return true;
|
|
13
|
+
for (const ref of mirrors ?? [])
|
|
14
|
+
if (ref.current.has(path))
|
|
15
|
+
return true;
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
// After closing the active tab, which tab gets focus. Prefer the most-recently-used still-open tab
|
|
19
|
+
// (MRU — matches VS Code's `focusRecentEditorAfterClose`); if there's no usable history, fall back to
|
|
20
|
+
// the spatial neighbour (the tab to the right, then to the left); return null only when nothing remains.
|
|
21
|
+
export function pickNextActive(closedPath, prevDocs, remaining, activationOrder) {
|
|
22
|
+
if (remaining.length === 0)
|
|
23
|
+
return null;
|
|
24
|
+
const remainingSet = new Set(remaining.map((d) => d.path));
|
|
25
|
+
for (let i = activationOrder.length - 1; i >= 0; i--) {
|
|
26
|
+
const p = activationOrder[i];
|
|
27
|
+
if (p !== closedPath && remainingSet.has(p))
|
|
28
|
+
return p;
|
|
29
|
+
}
|
|
30
|
+
const idx = prevDocs.findIndex((d) => d.path === closedPath);
|
|
31
|
+
const neighbour = prevDocs[idx + 1] ?? prevDocs[idx - 1];
|
|
32
|
+
return neighbour ? neighbour.path : remaining[0].path;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Tab lifecycle (close/teardown/reopen) and external-change detection (fingerprint-checked
|
|
36
|
+
* reload). Extracted verbatim from useDocuments — bodies, fingerprint suppression, and the reopen
|
|
37
|
+
* stack's bounded-eviction grant revocation are unchanged. Owns `closedTabs` (the Ctrl+Shift+T stack);
|
|
38
|
+
* `fingerprints` and `selfSaved` are owned by sibling hooks and threaded in so all three read/write the
|
|
39
|
+
* same single source of truth. `confirmClose`/`conflict` STATE stays with the orchestrator (it's part
|
|
40
|
+
* of the public return shape and `requestClose` needs to set it before this hook can read it back), so
|
|
41
|
+
* this hook only receives the current value + setter.
|
|
42
|
+
*/
|
|
43
|
+
export function useDocumentWatcher({ openDocs, openDocsRef, activationOrderRef, setOpenDocs, setActivePath, activePath, writeEdits, setDocxDirty, setPdfDirty, htmlPagesRef, writeHtmlPages, setHtmlActivePage, mdxGrantsRef, clearImageDirty, isDirty, openLiveUrl, activate, open, fingerprints, selfSaved, pendingSavePaths = EMPTY_PENDING_SAVE_PATHS, synchronousPendingSavePathsRef, synchronousPendingSavePathsRefs, reloadHtmlSubpage, setHtmlConfirm, setConfirmClose, confirmClose, setConflict, onSave, onDocxSave, docxSaver, docxSaversRef, onDocxSaveAs, withTabKey, blockDrafts, dropViewState, }) {
|
|
44
|
+
// Recently-closed file/web tabs for Ctrl+Shift+T reopen (most-recent last). Bounded; evicting an
|
|
45
|
+
// entry revokes its retained broker grant (see doClose).
|
|
46
|
+
const closedTabs = useRef([]);
|
|
47
|
+
const pendingSavePathsStateRef = useRef(pendingSavePaths);
|
|
48
|
+
pendingSavePathsStateRef.current = pendingSavePaths;
|
|
49
|
+
const deferredReloadsRef = useRef(new Set());
|
|
50
|
+
// Per-doc teardown shared by single close (doClose) and bulk close (closeAll): drop drafts, retain
|
|
51
|
+
// the reopen-stack grant, revoke HTML sub-page grants, and clear docx/image/html session state. It
|
|
52
|
+
// deliberately does NOT touch openDocs/activePath — the caller owns the tab-list mutation, so a bulk
|
|
53
|
+
// close can drop many tabs in one functional setState (a doClose loop would stale-close only one).
|
|
54
|
+
const teardownDoc = useCallback((path, closingDoc) => {
|
|
55
|
+
deferredReloadsRef.current.delete(path);
|
|
56
|
+
// Closing an untitled buffer discards it (no backing file) — drop its cached draft.
|
|
57
|
+
if (isUntitled(path)) {
|
|
58
|
+
clearDraft(path);
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
// File-backed txt draft: clear on explicit close (Don't Save / after Save).
|
|
62
|
+
clearFileDraft(path);
|
|
63
|
+
// Reopen-closed-tab (Ctrl+Shift+T): remember the closed file/web tab and KEEP its broker grant
|
|
64
|
+
// alive so it can reopen without a fresh OS-level grant. The authorized set still shrinks —
|
|
65
|
+
// just lazily: a grant is revoked when its entry is evicted past the bounded stack depth.
|
|
66
|
+
if (!isWebTabKey(path)) {
|
|
67
|
+
closedTabs.current = closedTabs.current.filter((e) => e.path !== path);
|
|
68
|
+
closedTabs.current.push({ path });
|
|
69
|
+
while (closedTabs.current.length > CLOSED_STACK_MAX) {
|
|
70
|
+
const evicted = closedTabs.current.shift();
|
|
71
|
+
if (evicted)
|
|
72
|
+
void revokePath(evicted.path);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
else if (closingDoc?.web?.url) {
|
|
76
|
+
closedTabs.current = closedTabs.current.filter((e) => e.path !== path);
|
|
77
|
+
closedTabs.current.push({ path, url: closingDoc.web.url });
|
|
78
|
+
if (closedTabs.current.length > CLOSED_STACK_MAX)
|
|
79
|
+
closedTabs.current.shift();
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
fingerprints.current.delete(path);
|
|
83
|
+
// Prune this path's view state (mode + scroll/page/zoom meta) alongside the rest of the
|
|
84
|
+
// per-close teardown — otherwise views/viewsRef/viewMetaRef only ever grow for the app's
|
|
85
|
+
// lifetime as tabs are opened and closed.
|
|
86
|
+
dropViewState?.(path);
|
|
87
|
+
// Drop the in-memory unsaved text so reopening a closed-without-saving file shows disk content
|
|
88
|
+
// (not the discarded edits), and closed tabs don't accumulate in editsRef across the session.
|
|
89
|
+
writeEdits((e) => {
|
|
90
|
+
if (!(path in e))
|
|
91
|
+
return e;
|
|
92
|
+
const n = { ...e };
|
|
93
|
+
delete n[path];
|
|
94
|
+
return n;
|
|
95
|
+
});
|
|
96
|
+
setDocxDirty((s) => {
|
|
97
|
+
if (!s.has(path))
|
|
98
|
+
return s;
|
|
99
|
+
const n = new Set(s);
|
|
100
|
+
n.delete(path);
|
|
101
|
+
return n;
|
|
102
|
+
});
|
|
103
|
+
setPdfDirty((paths) => {
|
|
104
|
+
if (!paths.has(path))
|
|
105
|
+
return paths;
|
|
106
|
+
const next = new Set(paths);
|
|
107
|
+
next.delete(path);
|
|
108
|
+
return next;
|
|
109
|
+
});
|
|
110
|
+
clearImageDirty(path);
|
|
111
|
+
// Tear down any HTML site session this tab owned: revoke every sub-page grant minted during
|
|
112
|
+
// in-tab navigation (shrinks the authorized set), drop the buffers, and clear its view state.
|
|
113
|
+
const session = htmlPagesRef.current[path];
|
|
114
|
+
if (session) {
|
|
115
|
+
for (const sub of Object.keys(session))
|
|
116
|
+
if (sub !== path)
|
|
117
|
+
void revokePath(sub);
|
|
118
|
+
writeHtmlPages((p) => {
|
|
119
|
+
if (!(path in p))
|
|
120
|
+
return p;
|
|
121
|
+
const n = { ...p };
|
|
122
|
+
delete n[path];
|
|
123
|
+
return n;
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
// Tear down any MDX component-import grants this tab minted (compile-time relative imports +
|
|
127
|
+
// in-preview link clicks) — mirrors the HTML sub-page revocation above so an MDX grant doesn't
|
|
128
|
+
// outlive its document until app exit.
|
|
129
|
+
const mdxGrants = mdxGrantsRef.current[path];
|
|
130
|
+
if (mdxGrants) {
|
|
131
|
+
for (const sub of mdxGrants)
|
|
132
|
+
void revokePath(sub);
|
|
133
|
+
delete mdxGrantsRef.current[path];
|
|
134
|
+
}
|
|
135
|
+
setHtmlActivePage((s) => {
|
|
136
|
+
if (!(path in s))
|
|
137
|
+
return s;
|
|
138
|
+
const n = { ...s };
|
|
139
|
+
delete n[path];
|
|
140
|
+
return n;
|
|
141
|
+
});
|
|
142
|
+
// Teardown discards any live block drafts for this document so nothing can commit after close.
|
|
143
|
+
if (blockDrafts) {
|
|
144
|
+
for (const d of blockDrafts.snapshotAll(path))
|
|
145
|
+
blockDrafts.discard(d.identity);
|
|
146
|
+
}
|
|
147
|
+
}, [
|
|
148
|
+
writeEdits,
|
|
149
|
+
setDocxDirty,
|
|
150
|
+
setPdfDirty,
|
|
151
|
+
clearImageDirty,
|
|
152
|
+
htmlPagesRef,
|
|
153
|
+
writeHtmlPages,
|
|
154
|
+
setHtmlActivePage,
|
|
155
|
+
mdxGrantsRef,
|
|
156
|
+
fingerprints,
|
|
157
|
+
dropViewState,
|
|
158
|
+
blockDrafts,
|
|
159
|
+
]);
|
|
160
|
+
const doClose = useCallback((path) => {
|
|
161
|
+
const prevDocs = openDocsRef.current;
|
|
162
|
+
const closingDoc = prevDocs.find((d) => d.path === path);
|
|
163
|
+
const remaining = prevDocs.filter((d) => d.path !== path);
|
|
164
|
+
const nextActive = pickNextActive(path, prevDocs, remaining, activationOrderRef.current);
|
|
165
|
+
const at = activationOrderRef.current.indexOf(path);
|
|
166
|
+
if (at !== -1)
|
|
167
|
+
activationOrderRef.current.splice(at, 1);
|
|
168
|
+
setOpenDocs((prev) => prev.filter((d) => d.path !== path));
|
|
169
|
+
setActivePath((current) => (current === path ? nextActive : current));
|
|
170
|
+
teardownDoc(path, closingDoc);
|
|
171
|
+
}, [setOpenDocs, setActivePath, teardownDoc, openDocsRef, activationOrderRef]);
|
|
172
|
+
// Ctrl+Shift+T: reopen the most-recently-closed file/web tab. The retained grant (see doClose) lets
|
|
173
|
+
// a file reopen without a fresh OS grant; a web tab re-validates its URL through openLiveUrl.
|
|
174
|
+
const reopenLastClosed = useCallback(() => {
|
|
175
|
+
const entry = closedTabs.current.pop();
|
|
176
|
+
if (!entry)
|
|
177
|
+
return;
|
|
178
|
+
if (entry.url) {
|
|
179
|
+
void openLiveUrl(entry.url);
|
|
180
|
+
}
|
|
181
|
+
else if (openDocs.some((d) => d.path === entry.path)) {
|
|
182
|
+
void activate(entry.path);
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
void open(entry.path).catch(() => {
|
|
186
|
+
toast("Couldn't reopen — the file may have been removed", "info");
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
}, [activate, open, openLiveUrl, openDocs]);
|
|
190
|
+
// Closing a tab with unsaved changes prompts Save / Don't Save / Cancel (Notepad/Word-style).
|
|
191
|
+
const requestClose = useCallback((path) => {
|
|
192
|
+
const doc = openDocs.find((d) => d.path === path);
|
|
193
|
+
if (doc && isDirty(doc)) {
|
|
194
|
+
// A DOCX's live bytes exist only in its mounted editor — activate it so the dialog's Save works.
|
|
195
|
+
if (doc.format === "docx" && path !== activePath)
|
|
196
|
+
setActivePath(path);
|
|
197
|
+
// HTML sessions can have several dirty pages — use the page-aware Save Current / Save All /
|
|
198
|
+
// Discard All / Cancel dialog instead of the single-doc Save / Don't Save prompt.
|
|
199
|
+
if (doc.format === "html")
|
|
200
|
+
setHtmlConfirm({ entryPath: path, mode: "close" });
|
|
201
|
+
else
|
|
202
|
+
setConfirmClose(path);
|
|
203
|
+
}
|
|
204
|
+
else
|
|
205
|
+
doClose(path);
|
|
206
|
+
}, [openDocs, isDirty, doClose, activePath, setActivePath, setHtmlConfirm, setConfirmClose]);
|
|
207
|
+
// Close dialog: Save → persist then close; Don't Save → discard + close; Cancel → keep open.
|
|
208
|
+
const confirmSave = useCallback(async () => {
|
|
209
|
+
const path = confirmClose;
|
|
210
|
+
if (!path)
|
|
211
|
+
return;
|
|
212
|
+
const doc = openDocs.find((d) => d.path === path);
|
|
213
|
+
if (!doc) {
|
|
214
|
+
setConfirmClose(null);
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
// Save means "Done then persist": resolve this document's block drafts first; a stale/failed
|
|
218
|
+
// draft leaves the dialog + editor open with the draft error visible.
|
|
219
|
+
if (blockDrafts) {
|
|
220
|
+
const outcome = await resolveDocumentDrafts(blockDrafts, path, "close", "done");
|
|
221
|
+
if (outcome !== "resolved")
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
if (doc.format === "docx") {
|
|
225
|
+
if (isUntitled(doc.path)) {
|
|
226
|
+
const savedPath = await onDocxSaveAs(doc);
|
|
227
|
+
if (savedPath)
|
|
228
|
+
doClose(path);
|
|
229
|
+
setConfirmClose(null);
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
const ab = await (docxSaversRef?.current.get(path) ?? docxSaver.current)?.();
|
|
233
|
+
if (ab) {
|
|
234
|
+
await onDocxSave(path, ab);
|
|
235
|
+
doClose(path);
|
|
236
|
+
}
|
|
237
|
+
setConfirmClose(null);
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
if (doc.format === "image") {
|
|
241
|
+
const saved = await onSave(doc);
|
|
242
|
+
if (saved)
|
|
243
|
+
doClose(saved);
|
|
244
|
+
setConfirmClose(null);
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
const saved = await onSave(doc); // resulting path, or null if the save dialog was cancelled
|
|
248
|
+
if (saved)
|
|
249
|
+
doClose(saved);
|
|
250
|
+
setConfirmClose(null);
|
|
251
|
+
}, [
|
|
252
|
+
confirmClose,
|
|
253
|
+
openDocs,
|
|
254
|
+
onSave,
|
|
255
|
+
onDocxSave,
|
|
256
|
+
doClose,
|
|
257
|
+
setConfirmClose,
|
|
258
|
+
docxSaver,
|
|
259
|
+
docxSaversRef,
|
|
260
|
+
onDocxSaveAs,
|
|
261
|
+
blockDrafts,
|
|
262
|
+
]);
|
|
263
|
+
const confirmDontSave = useCallback(async () => {
|
|
264
|
+
// Don't Save discards the document's block drafts before the tab closes.
|
|
265
|
+
if (blockDrafts && confirmClose)
|
|
266
|
+
await resolveDocumentDrafts(blockDrafts, confirmClose, "close", "discard");
|
|
267
|
+
if (confirmClose)
|
|
268
|
+
doClose(confirmClose);
|
|
269
|
+
setConfirmClose(null);
|
|
270
|
+
}, [confirmClose, doClose, setConfirmClose, blockDrafts]);
|
|
271
|
+
const applyReload = useCallback((path) => {
|
|
272
|
+
if (isWebTabKey(path))
|
|
273
|
+
return;
|
|
274
|
+
void readDocument(path)
|
|
275
|
+
.then((loaded) => {
|
|
276
|
+
if (loaded.fingerprint)
|
|
277
|
+
fingerprints.current.set(path, loaded.fingerprint);
|
|
278
|
+
setOpenDocs((prev) => prev.map((d) => (d.path === path ? withTabKey(loaded, d.tabKey) : d)));
|
|
279
|
+
// Drop any cached edits for this path so the editor shows the on-disk content (clean).
|
|
280
|
+
writeEdits((e) => {
|
|
281
|
+
if (!(path in e))
|
|
282
|
+
return e;
|
|
283
|
+
const n = { ...e };
|
|
284
|
+
delete n[path];
|
|
285
|
+
return n;
|
|
286
|
+
});
|
|
287
|
+
// Mark any open block draft for this document/page stale — external content must never be
|
|
288
|
+
// overwritten by a Done that started from older bytes.
|
|
289
|
+
if (blockDrafts)
|
|
290
|
+
for (const draft of blockDrafts.snapshotModified())
|
|
291
|
+
if (draft.identity.documentPath === path || draft.identity.pagePath === path)
|
|
292
|
+
blockDrafts.markStale(draft.identity, "Source changed on disk while this block was open.");
|
|
293
|
+
})
|
|
294
|
+
.catch(() => {
|
|
295
|
+
toast("Couldn't reload the changed file on disk", "info");
|
|
296
|
+
});
|
|
297
|
+
}, [writeEdits, setOpenDocs, fingerprints, withTabKey, blockDrafts]);
|
|
298
|
+
const reload = useCallback((path, options = {}) => {
|
|
299
|
+
if (isWebTabKey(path))
|
|
300
|
+
return;
|
|
301
|
+
if (!options.force &&
|
|
302
|
+
(pendingSavePathsStateRef.current.has(path) ||
|
|
303
|
+
hasSynchronousPendingPath(path, synchronousPendingSavePathsRef, synchronousPendingSavePathsRefs))) {
|
|
304
|
+
// Preserve a real external event that races an in-flight own write; replay after save
|
|
305
|
+
// completion, when selfSaved/fingerprint can distinguish own bytes from external bytes.
|
|
306
|
+
deferredReloadsRef.current.add(path);
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
const selfSavedAtStart = selfSaved.current.has(path);
|
|
310
|
+
const applyReloadForPath = (p) => {
|
|
311
|
+
const doc = openDocs.find((d) => d.path === p);
|
|
312
|
+
if (doc) {
|
|
313
|
+
// External change while a block draft is open must keep draft text, mark stale,
|
|
314
|
+
// and block Done — even when the document is already dirty. Do that before
|
|
315
|
+
// deciding between conflict reload vs clean apply.
|
|
316
|
+
if (blockDrafts) {
|
|
317
|
+
for (const draft of blockDrafts.snapshotModified())
|
|
318
|
+
if (draft.identity.documentPath === p || draft.identity.pagePath === p)
|
|
319
|
+
blockDrafts.markStale(draft.identity, "Source changed on disk while this block was open.");
|
|
320
|
+
}
|
|
321
|
+
if (isDirty(doc))
|
|
322
|
+
setConflict(p);
|
|
323
|
+
else
|
|
324
|
+
applyReload(p);
|
|
325
|
+
}
|
|
326
|
+
else {
|
|
327
|
+
// Sub-page path: also mark any draft for that page stale before the session decides
|
|
328
|
+
// between conflict vs silent refresh (reloadHtmlSubpage handles the buffer but not drafts).
|
|
329
|
+
if (blockDrafts) {
|
|
330
|
+
for (const draft of blockDrafts.snapshotModified())
|
|
331
|
+
if (draft.identity.pagePath === p)
|
|
332
|
+
blockDrafts.markStale(draft.identity, "Source changed on disk while this block was open.");
|
|
333
|
+
}
|
|
334
|
+
reloadHtmlSubpage(p);
|
|
335
|
+
}
|
|
336
|
+
};
|
|
337
|
+
// Fingerprint check: if the stored post-save/post-read fingerprint matches the live one,
|
|
338
|
+
// the change was ours — suppress even if the watcher echo arrived after the selfSaved TTL.
|
|
339
|
+
const prev = fingerprints.current.get(path);
|
|
340
|
+
if (!options.force && prev) {
|
|
341
|
+
void fileFingerprint(path)
|
|
342
|
+
.then((cur) => {
|
|
343
|
+
if (cur && cur.mtimeMs === prev.mtimeMs && cur.size === prev.size)
|
|
344
|
+
return;
|
|
345
|
+
// The stored fingerprint is the post-write one (the saver drops it when it could not
|
|
346
|
+
// refresh), so a differing live fingerprint is an external write even inside the
|
|
347
|
+
// self-save echo window; only the no-fingerprint fast path below trusts the window.
|
|
348
|
+
applyReloadForPath(path);
|
|
349
|
+
})
|
|
350
|
+
.catch(() => applyReloadForPath(path));
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
if (!options.force && selfSavedAtStart)
|
|
354
|
+
return; // watcher fast path: ignore our own write
|
|
355
|
+
applyReloadForPath(path);
|
|
356
|
+
}, [
|
|
357
|
+
openDocs,
|
|
358
|
+
isDirty,
|
|
359
|
+
applyReload,
|
|
360
|
+
reloadHtmlSubpage,
|
|
361
|
+
selfSaved,
|
|
362
|
+
fingerprints,
|
|
363
|
+
setConflict,
|
|
364
|
+
blockDrafts,
|
|
365
|
+
synchronousPendingSavePathsRef,
|
|
366
|
+
synchronousPendingSavePathsRefs,
|
|
367
|
+
]);
|
|
368
|
+
// Stable identity: the `file-changed` listener resubscribes whenever `reload` changes, and the
|
|
369
|
+
// async unlisten/listen gap drops watcher events (one emit per external write, never replayed).
|
|
370
|
+
// `reload`'s deps churn (openDocs, an inline mirror array), so consumers get a forwarding wrapper.
|
|
371
|
+
const reloadRef = useRef(reload);
|
|
372
|
+
reloadRef.current = reload;
|
|
373
|
+
const stableReload = useCallback((path, options) => reloadRef.current(path, options), []);
|
|
374
|
+
useEffect(() => {
|
|
375
|
+
for (const path of deferredReloadsRef.current) {
|
|
376
|
+
if (pendingSavePaths.has(path) ||
|
|
377
|
+
hasSynchronousPendingPath(path, synchronousPendingSavePathsRef, synchronousPendingSavePathsRefs))
|
|
378
|
+
continue;
|
|
379
|
+
deferredReloadsRef.current.delete(path);
|
|
380
|
+
reload(path, { replayDeferred: true });
|
|
381
|
+
}
|
|
382
|
+
}, [pendingSavePaths, reload, synchronousPendingSavePathsRef, synchronousPendingSavePathsRefs]);
|
|
383
|
+
return {
|
|
384
|
+
teardownDoc,
|
|
385
|
+
doClose,
|
|
386
|
+
reopenLastClosed,
|
|
387
|
+
requestClose,
|
|
388
|
+
confirmSave,
|
|
389
|
+
confirmDontSave,
|
|
390
|
+
applyReload,
|
|
391
|
+
reload: stableReload,
|
|
392
|
+
};
|
|
393
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type WheelEvent as ReactWheelEvent } from "react";
|
|
2
|
+
import type { LoadedDocument, ViewMode } from "@rightkit/document-core/documents/types";
|
|
3
|
+
import type { Settings } from "@rightkit/document-core/settings/settings";
|
|
4
|
+
interface DocumentZoomDeps {
|
|
5
|
+
active: LoadedDocument | null;
|
|
6
|
+
view: ViewMode;
|
|
7
|
+
settings: Settings;
|
|
8
|
+
update: (patch: Partial<Settings>) => void;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Ctrl/Cmd+wheel document zoom + the zoom HUD/reset pill. Refs mirror the latest active/view/
|
|
12
|
+
* settings so `onDocumentWheel` stays IDENTITY-STABLE (needed so DocumentRouterTable's memo isn't
|
|
13
|
+
* defeated by a fresh handler every render); wheel ticks accumulate a step count and a single rAF
|
|
14
|
+
* flush applies at most one settings update per frame. Zoom math/keys/HUD behavior unchanged —
|
|
15
|
+
* extracted verbatim from App.tsx.
|
|
16
|
+
*/
|
|
17
|
+
export declare function useDocumentZoom({ active, view, settings, update }: DocumentZoomDeps): {
|
|
18
|
+
zoomHud: string | null;
|
|
19
|
+
zoomedAwayFromDefault: boolean;
|
|
20
|
+
zoomPercentLabel: string;
|
|
21
|
+
resetDocumentZoom: () => void;
|
|
22
|
+
onDocumentWheel: (e: ReactWheelEvent<HTMLDivElement>) => void;
|
|
23
|
+
};
|
|
24
|
+
export {};
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { useCallback, useEffect, useMemo, useRef, useState, } from "react";
|
|
2
|
+
import { isMarkdownFormat } from "../documents/capabilities.js";
|
|
3
|
+
/**
|
|
4
|
+
* Ctrl/Cmd+wheel document zoom + the zoom HUD/reset pill. Refs mirror the latest active/view/
|
|
5
|
+
* settings so `onDocumentWheel` stays IDENTITY-STABLE (needed so DocumentRouterTable's memo isn't
|
|
6
|
+
* defeated by a fresh handler every render); wheel ticks accumulate a step count and a single rAF
|
|
7
|
+
* flush applies at most one settings update per frame. Zoom math/keys/HUD behavior unchanged —
|
|
8
|
+
* extracted verbatim from App.tsx.
|
|
9
|
+
*/
|
|
10
|
+
export function useDocumentZoom({ active, view, settings, update }) {
|
|
11
|
+
const [zoomHud, setZoomHud] = useState(null);
|
|
12
|
+
const zoomHudTimer = useRef(null);
|
|
13
|
+
// T2-2: Ctrl+wheel zoom used to call settings' update() on every wheel tick — a fast trackpad/mouse
|
|
14
|
+
// burst fires many ticks per animation frame, each one a full settings-context re-render. Refs mirror
|
|
15
|
+
// the latest active/view/settings so this handler can stay IDENTITY-STABLE (useCallback with no
|
|
16
|
+
// per-render deps besides `update`/`flushZoom`) — needed so DocumentRouterTable's memo (T2-3) isn't
|
|
17
|
+
// defeated by a fresh onDocumentWheel every render. Wheel events accumulate a step count; a single
|
|
18
|
+
// rAF flush applies at most one settings update per frame. Zoom math/keys/HUD behavior unchanged.
|
|
19
|
+
const activeRef = useRef(active);
|
|
20
|
+
activeRef.current = active;
|
|
21
|
+
const viewRef = useRef(view);
|
|
22
|
+
viewRef.current = view;
|
|
23
|
+
const settingsRef = useRef(settings);
|
|
24
|
+
settingsRef.current = settings;
|
|
25
|
+
const zoomStepsRef = useRef(0);
|
|
26
|
+
const zoomCtxRef = useRef(null);
|
|
27
|
+
const zoomRafRef = useRef(null);
|
|
28
|
+
useEffect(() => () => {
|
|
29
|
+
if (zoomHudTimer.current != null)
|
|
30
|
+
window.clearTimeout(zoomHudTimer.current);
|
|
31
|
+
if (zoomRafRef.current != null)
|
|
32
|
+
window.cancelAnimationFrame(zoomRafRef.current);
|
|
33
|
+
}, []);
|
|
34
|
+
const flushZoom = useCallback(() => {
|
|
35
|
+
zoomRafRef.current = null;
|
|
36
|
+
const ctx = zoomCtxRef.current;
|
|
37
|
+
const steps = zoomStepsRef.current;
|
|
38
|
+
zoomStepsRef.current = 0;
|
|
39
|
+
zoomCtxRef.current = null;
|
|
40
|
+
if (!ctx || steps === 0)
|
|
41
|
+
return;
|
|
42
|
+
const { key, base, min, max } = ctx;
|
|
43
|
+
const current = settingsRef.current[key];
|
|
44
|
+
const next = Math.max(min, Math.min(max, current + steps));
|
|
45
|
+
if (next === current)
|
|
46
|
+
return;
|
|
47
|
+
update({ [key]: next });
|
|
48
|
+
setZoomHud(`${Math.round((next / base) * 100)}%`);
|
|
49
|
+
if (zoomHudTimer.current != null)
|
|
50
|
+
window.clearTimeout(zoomHudTimer.current);
|
|
51
|
+
zoomHudTimer.current = window.setTimeout(() => setZoomHud(null), 900);
|
|
52
|
+
}, [update]);
|
|
53
|
+
// Which zoom setting the visible surface is actually using, mirroring the source-vs-reading split
|
|
54
|
+
// in onDocumentWheel below. The pill must report and reset the SAME setting the wheel changes, or
|
|
55
|
+
// it would offer to reset a value the user never touched.
|
|
56
|
+
const zoomSurface = useMemo(() => {
|
|
57
|
+
const format = active?.format;
|
|
58
|
+
// Only the formats whose ON-SCREEN size this setting actually governs. PDF owns its own zoom
|
|
59
|
+
// toolbar; DOCX and images are not font-scaled; and HTML previews zoom via their own iframe
|
|
60
|
+
// `style.zoom` (HtmlViewer's safe-preview handler and the interactive bridge's pageZoom), which
|
|
61
|
+
// this setting does not touch. Including any of them would render a pill that reports a number
|
|
62
|
+
// the user is not looking at and a Reset that visibly does nothing.
|
|
63
|
+
const zoomable = format === "txt" || isMarkdownFormat(format);
|
|
64
|
+
const sourceZoomable = isMarkdownFormat(format) && view === "source";
|
|
65
|
+
return {
|
|
66
|
+
zoomable,
|
|
67
|
+
...(sourceZoomable
|
|
68
|
+
? { key: "sourceFontSizePx", base: 12 }
|
|
69
|
+
: { key: "fontSizePx", base: 16 }),
|
|
70
|
+
};
|
|
71
|
+
}, [active?.format, view]);
|
|
72
|
+
const zoomPercent = Math.round((settings[zoomSurface.key] / zoomSurface.base) * 100);
|
|
73
|
+
const zoomedAwayFromDefault = zoomSurface.zoomable && settings[zoomSurface.key] !== zoomSurface.base;
|
|
74
|
+
const zoomPercentLabel = `${zoomPercent}%`;
|
|
75
|
+
const resetDocumentZoom = useCallback(() => update({ [zoomSurface.key]: zoomSurface.base }), [update, zoomSurface]);
|
|
76
|
+
const onDocumentWheel = useCallback((e) => {
|
|
77
|
+
const activeDoc = activeRef.current;
|
|
78
|
+
if (!(e.ctrlKey || e.metaKey) || !activeDoc)
|
|
79
|
+
return;
|
|
80
|
+
const currentView = viewRef.current;
|
|
81
|
+
const target = e.target;
|
|
82
|
+
const sourceZoomable = isMarkdownFormat(activeDoc.format) && currentView === "source"
|
|
83
|
+
? true
|
|
84
|
+
: activeDoc.format === "html" && !!target?.closest?.(".cm-editor");
|
|
85
|
+
const readingZoomable = activeDoc.format === "txt" ||
|
|
86
|
+
(isMarkdownFormat(activeDoc.format) && currentView !== "source");
|
|
87
|
+
if (!sourceZoomable && !readingZoomable)
|
|
88
|
+
return;
|
|
89
|
+
e.preventDefault();
|
|
90
|
+
e.stopPropagation();
|
|
91
|
+
const key = sourceZoomable
|
|
92
|
+
? "sourceFontSizePx"
|
|
93
|
+
: "fontSizePx";
|
|
94
|
+
const base = sourceZoomable ? 12 : 16;
|
|
95
|
+
const min = sourceZoomable ? 10 : 12;
|
|
96
|
+
const max = sourceZoomable ? 24 : 28;
|
|
97
|
+
zoomCtxRef.current = { key, base, min, max };
|
|
98
|
+
zoomStepsRef.current += e.deltaY < 0 ? 1 : -1;
|
|
99
|
+
if (zoomRafRef.current == null) {
|
|
100
|
+
zoomRafRef.current = window.requestAnimationFrame(flushZoom);
|
|
101
|
+
}
|
|
102
|
+
}, [flushZoom]);
|
|
103
|
+
return { zoomHud, zoomedAwayFromDefault, zoomPercentLabel, resetDocumentZoom, onDocumentWheel };
|
|
104
|
+
}
|