@rightkit/document-workspace 0.0.0-stage → 0.1.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.
Files changed (262) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/LICENSE-APACHE +201 -0
  3. package/LICENSE-MIT +21 -0
  4. package/README.md +51 -2
  5. package/dist/app/App.d.ts +1 -0
  6. package/dist/app/App.js +6 -0
  7. package/dist/app/AppDialogs.d.ts +52 -0
  8. package/dist/app/AppDialogs.js +23 -0
  9. package/dist/app/AppDialogsHost.d.ts +31 -0
  10. package/dist/app/AppDialogsHost.js +30 -0
  11. package/dist/app/AppLifecycle.d.ts +19 -0
  12. package/dist/app/AppLifecycle.js +87 -0
  13. package/dist/app/AppProvider.d.ts +317 -0
  14. package/dist/app/AppProvider.js +217 -0
  15. package/dist/app/AppRoot.d.ts +8 -0
  16. package/dist/app/AppRoot.js +159 -0
  17. package/dist/app/AppShell.d.ts +2 -0
  18. package/dist/app/AppShell.js +81 -0
  19. package/dist/app/CommandPalette.d.ts +13 -0
  20. package/dist/app/CommandPalette.js +47 -0
  21. package/dist/app/DocActionBar.d.ts +36 -0
  22. package/dist/app/DocActionBar.js +300 -0
  23. package/dist/app/DocumentContextMenu.d.ts +29 -0
  24. package/dist/app/DocumentContextMenu.js +122 -0
  25. package/dist/app/DocumentPane.d.ts +52 -0
  26. package/dist/app/DocumentPane.js +30 -0
  27. package/dist/app/DocumentRouterTable.d.ts +58 -0
  28. package/dist/app/DocumentRouterTable.js +143 -0
  29. package/dist/app/ErrorBoundary.d.ts +12 -0
  30. package/dist/app/ErrorBoundary.js +16 -0
  31. package/dist/app/FindBar.d.ts +12 -0
  32. package/dist/app/FindBar.js +199 -0
  33. package/dist/app/LiveTitle.d.ts +14 -0
  34. package/dist/app/LiveTitle.js +14 -0
  35. package/dist/app/OpeningProgress.d.ts +13 -0
  36. package/dist/app/OpeningProgress.js +21 -0
  37. package/dist/app/PrintPreviewWindow.d.ts +2 -0
  38. package/dist/app/PrintPreviewWindow.js +57 -0
  39. package/dist/app/ShortcutHelp.d.ts +3 -0
  40. package/dist/app/ShortcutHelp.js +99 -0
  41. package/dist/app/Sidebar.d.ts +36 -0
  42. package/dist/app/Sidebar.js +121 -0
  43. package/dist/app/ToastViewport.d.ts +1 -0
  44. package/dist/app/ToastViewport.js +31 -0
  45. package/dist/app/Toolbar.d.ts +26 -0
  46. package/dist/app/Toolbar.js +63 -0
  47. package/dist/app/activeFormatSavers.d.ts +7 -0
  48. package/dist/app/activeFormatSavers.js +4 -0
  49. package/dist/app/clipboardPaths.d.ts +1 -0
  50. package/dist/app/clipboardPaths.js +31 -0
  51. package/dist/app/documentDrafts.d.ts +17 -0
  52. package/dist/app/documentDrafts.js +104 -0
  53. package/dist/app/draftResolution.d.ts +5 -0
  54. package/dist/app/draftResolution.js +25 -0
  55. package/dist/app/markdownPaletteActions.d.ts +8 -0
  56. package/dist/app/markdownPaletteActions.js +107 -0
  57. package/dist/app/resourceCensusDev.d.ts +41 -0
  58. package/dist/app/resourceCensusDev.js +518 -0
  59. package/dist/app/setupRecovery.d.ts +34 -0
  60. package/dist/app/setupRecovery.js +70 -0
  61. package/dist/app/shell/DocStatsPill.d.ts +18 -0
  62. package/dist/app/shell/DocStatsPill.js +32 -0
  63. package/dist/app/shell/DocumentTools.d.ts +9 -0
  64. package/dist/app/shell/DocumentTools.js +73 -0
  65. package/dist/app/shell/InfoInspector.d.ts +9 -0
  66. package/dist/app/shell/InfoInspector.js +67 -0
  67. package/dist/app/shell/StartPage.d.ts +9 -0
  68. package/dist/app/shell/StartPage.js +49 -0
  69. package/dist/app/shell/readEdit.d.ts +6 -0
  70. package/dist/app/shell/readEdit.js +6 -0
  71. package/dist/app/sidebar/SidebarDocumentMenu.d.ts +20 -0
  72. package/dist/app/sidebar/SidebarDocumentMenu.js +56 -0
  73. package/dist/app/sidebar/SidebarExpandedPanel.d.ts +9 -0
  74. package/dist/app/sidebar/SidebarExpandedPanel.js +78 -0
  75. package/dist/app/sidebar/SidebarIcons.d.ts +3 -0
  76. package/dist/app/sidebar/SidebarIcons.js +18 -0
  77. package/dist/app/sidebar/SidebarMenus.d.ts +19 -0
  78. package/dist/app/sidebar/SidebarMenus.js +64 -0
  79. package/dist/app/sidebar/SidebarPathUtils.d.ts +10 -0
  80. package/dist/app/sidebar/SidebarPathUtils.js +55 -0
  81. package/dist/app/sidebar/SidebarPrimitives.d.ts +12 -0
  82. package/dist/app/sidebar/SidebarPrimitives.js +45 -0
  83. package/dist/app/sidebar/SidebarRail.d.ts +5 -0
  84. package/dist/app/sidebar/SidebarRail.js +37 -0
  85. package/dist/app/sidebar/explorer/ExplorerRowMenu.d.ts +21 -0
  86. package/dist/app/sidebar/explorer/ExplorerRowMenu.js +37 -0
  87. package/dist/app/sidebar/explorer/ExplorerSections.d.ts +16 -0
  88. package/dist/app/sidebar/explorer/ExplorerSections.js +259 -0
  89. package/dist/app/sidebar/explorer/ExplorerTree.d.ts +46 -0
  90. package/dist/app/sidebar/explorer/ExplorerTree.js +387 -0
  91. package/dist/app/sidebar/explorer/explorerState.d.ts +37 -0
  92. package/dist/app/sidebar/explorer/explorerState.js +135 -0
  93. package/dist/app/tabMountPolicy.d.ts +45 -0
  94. package/dist/app/tabMountPolicy.js +104 -0
  95. package/dist/app/tabWarmOrder.d.ts +7 -0
  96. package/dist/app/tabWarmOrder.js +48 -0
  97. package/dist/app/tabs/TabFuseCurves.d.ts +3 -0
  98. package/dist/app/tabs/TabFuseCurves.js +15 -0
  99. package/dist/app/tabs/TabOverview.d.ts +24 -0
  100. package/dist/app/tabs/TabOverview.js +121 -0
  101. package/dist/app/tabs/TabStrip.d.ts +42 -0
  102. package/dist/app/tabs/TabStrip.js +380 -0
  103. package/dist/app/tabs/tabIds.d.ts +5 -0
  104. package/dist/app/tabs/tabIds.js +7 -0
  105. package/dist/app/tabs/useTabDirty.d.ts +3 -0
  106. package/dist/app/tabs/useTabDirty.js +11 -0
  107. package/dist/app/useAppChromeState.d.ts +21 -0
  108. package/dist/app/useAppChromeState.js +48 -0
  109. package/dist/app/useAppCommands.d.ts +66 -0
  110. package/dist/app/useAppCommands.js +71 -0
  111. package/dist/app/useAppDocumentCore.d.ts +210 -0
  112. package/dist/app/useAppDocumentCore.js +40 -0
  113. package/dist/app/useAppEffects.d.ts +18 -0
  114. package/dist/app/useAppEffects.js +52 -0
  115. package/dist/app/useAppPaneSearch.d.ts +37 -0
  116. package/dist/app/useAppPaneSearch.js +74 -0
  117. package/dist/app/useAppShellSetup.d.ts +40 -0
  118. package/dist/app/useAppShellSetup.js +52 -0
  119. package/dist/app/useAppSlots.d.ts +28 -0
  120. package/dist/app/useAppSlots.js +40 -0
  121. package/dist/app/useAppTabChrome.d.ts +51 -0
  122. package/dist/app/useAppTabChrome.js +66 -0
  123. package/dist/app/useAutoSave.d.ts +34 -0
  124. package/dist/app/useAutoSave.js +116 -0
  125. package/dist/app/useCloseAll.d.ts +52 -0
  126. package/dist/app/useCloseAll.js +295 -0
  127. package/dist/app/useClosedTabEviction.d.ts +10 -0
  128. package/dist/app/useClosedTabEviction.js +38 -0
  129. package/dist/app/useCommandPalette.d.ts +44 -0
  130. package/dist/app/useCommandPalette.js +254 -0
  131. package/dist/app/useCrossDocSearch.d.ts +7 -0
  132. package/dist/app/useCrossDocSearch.js +94 -0
  133. package/dist/app/useDistractionFree.d.ts +21 -0
  134. package/dist/app/useDistractionFree.js +73 -0
  135. package/dist/app/useDocumentCreation.d.ts +18 -0
  136. package/dist/app/useDocumentCreation.js +63 -0
  137. package/dist/app/useDocumentDirty.d.ts +31 -0
  138. package/dist/app/useDocumentDirty.js +46 -0
  139. package/dist/app/useDocumentLoader.d.ts +55 -0
  140. package/dist/app/useDocumentLoader.js +267 -0
  141. package/dist/app/useDocumentOutputActions.d.ts +6 -0
  142. package/dist/app/useDocumentOutputActions.js +45 -0
  143. package/dist/app/useDocumentRuntimeEffects.d.ts +36 -0
  144. package/dist/app/useDocumentRuntimeEffects.js +458 -0
  145. package/dist/app/useDocumentSaver.d.ts +58 -0
  146. package/dist/app/useDocumentSaver.js +511 -0
  147. package/dist/app/useDocumentTabs.d.ts +41 -0
  148. package/dist/app/useDocumentTabs.js +207 -0
  149. package/dist/app/useDocumentWatcher.d.ts +83 -0
  150. package/dist/app/useDocumentWatcher.js +393 -0
  151. package/dist/app/useDocumentZoom.d.ts +24 -0
  152. package/dist/app/useDocumentZoom.js +104 -0
  153. package/dist/app/useDocuments.d.ts +169 -0
  154. package/dist/app/useDocuments.js +455 -0
  155. package/dist/app/useDraftConfirmGate.d.ts +27 -0
  156. package/dist/app/useDraftConfirmGate.js +47 -0
  157. package/dist/app/useFileDragOverlay.d.ts +12 -0
  158. package/dist/app/useFileDragOverlay.js +49 -0
  159. package/dist/app/useFormatApis.d.ts +13 -0
  160. package/dist/app/useFormatApis.js +62 -0
  161. package/dist/app/useFormatDirtyState.d.ts +29 -0
  162. package/dist/app/useFormatDirtyState.js +81 -0
  163. package/dist/app/useGlobalShortcuts.d.ts +47 -0
  164. package/dist/app/useGlobalShortcuts.js +359 -0
  165. package/dist/app/useHtmlSession.d.ts +68 -0
  166. package/dist/app/useHtmlSession.js +463 -0
  167. package/dist/app/useHtmlSessionProps.d.ts +33 -0
  168. package/dist/app/useHtmlSessionProps.js +57 -0
  169. package/dist/app/useLibrary.d.ts +21 -0
  170. package/dist/app/useLibrary.js +81 -0
  171. package/dist/app/useOpenFromClipboard.d.ts +7 -0
  172. package/dist/app/useOpenFromClipboard.js +44 -0
  173. package/dist/app/usePaneLiveness.d.ts +29 -0
  174. package/dist/app/usePaneLiveness.js +199 -0
  175. package/dist/app/usePendingSavePaths.d.ts +19 -0
  176. package/dist/app/usePendingSavePaths.js +67 -0
  177. package/dist/app/useReadyShow.d.ts +12 -0
  178. package/dist/app/useReadyShow.js +142 -0
  179. package/dist/app/useSearchController.d.ts +22 -0
  180. package/dist/app/useSearchController.js +114 -0
  181. package/dist/app/useSetupRecovery.d.ts +41 -0
  182. package/dist/app/useSetupRecovery.js +153 -0
  183. package/dist/app/useTabChrome.d.ts +40 -0
  184. package/dist/app/useTabChrome.js +115 -0
  185. package/dist/app/useViewState.d.ts +27 -0
  186. package/dist/app/useViewState.js +160 -0
  187. package/dist/appName.d.ts +2 -0
  188. package/dist/appName.js +8 -0
  189. package/dist/documents/DocumentRouter.d.ts +102 -0
  190. package/dist/documents/DocumentRouter.js +153 -0
  191. package/dist/documents/FileTypeIcon.d.ts +6 -0
  192. package/dist/documents/FileTypeIcon.js +93 -0
  193. package/dist/documents/ImageDocument.d.ts +14 -0
  194. package/dist/documents/ImageDocument.js +61 -0
  195. package/dist/documents/capabilities.d.ts +17 -0
  196. package/dist/documents/capabilities.js +18 -0
  197. package/dist/documents/html/HtmlDocument.d.ts +15 -0
  198. package/dist/documents/html/HtmlDocument.js +584 -0
  199. package/dist/documents/html/HtmlDocumentLayout.d.ts +87 -0
  200. package/dist/documents/html/HtmlDocumentLayout.js +25 -0
  201. package/dist/documents/html/HtmlDocumentProps.d.ts +60 -0
  202. package/dist/documents/html/HtmlDocumentProps.js +1 -0
  203. package/dist/documents/html/HtmlDocumentSource.d.ts +34 -0
  204. package/dist/documents/html/HtmlDocumentSource.js +28 -0
  205. package/dist/documents/markdown/MarkdownSplitPane.d.ts +115 -0
  206. package/dist/documents/markdown/MarkdownSplitPane.js +352 -0
  207. package/dist/documents/mdx/MdxSplitPane.d.ts +32 -0
  208. package/dist/documents/mdx/MdxSplitPane.js +60 -0
  209. package/dist/documents/txt/LinkDialog.d.ts +8 -0
  210. package/dist/documents/txt/LinkDialog.js +34 -0
  211. package/dist/documents/txt/MarkdownSourceToolbar.d.ts +8 -0
  212. package/dist/documents/txt/MarkdownSourceToolbar.js +25 -0
  213. package/dist/documents/txt/SourceEditorToolbar.d.ts +12 -0
  214. package/dist/documents/txt/SourceEditorToolbar.js +4 -0
  215. package/dist/documents/txt/TxtEditor.d.ts +39 -0
  216. package/dist/documents/txt/TxtEditor.js +186 -0
  217. package/dist/documents/txt/sourceEditorTypes.d.ts +49 -0
  218. package/dist/documents/txt/sourceEditorTypes.js +1 -0
  219. package/dist/documents/txt/sourceExtensions.d.ts +38 -0
  220. package/dist/documents/txt/sourceExtensions.js +444 -0
  221. package/dist/documents/txt/sourceFormatting.d.ts +18 -0
  222. package/dist/documents/txt/sourceFormatting.js +183 -0
  223. package/dist/documents/txt/sourceTheme.d.ts +5 -0
  224. package/dist/documents/txt/sourceTheme.js +220 -0
  225. package/dist/documents/txt/useCodeMirrorSourceEditor.d.ts +69 -0
  226. package/dist/documents/txt/useCodeMirrorSourceEditor.js +546 -0
  227. package/dist/documents/txt/useSourceSearch.d.ts +39 -0
  228. package/dist/documents/txt/useSourceSearch.js +122 -0
  229. package/dist/documents/txt/useTextEditorCommands.d.ts +26 -0
  230. package/dist/documents/txt/useTextEditorCommands.js +151 -0
  231. package/dist/documents/web/LivePreviewPane.d.ts +11 -0
  232. package/dist/documents/web/LivePreviewPane.js +58 -0
  233. package/dist/env.d.ts +66 -0
  234. package/dist/index.d.ts +121 -0
  235. package/dist/index.js +123 -0
  236. package/dist/settings/AppearanceMenu.d.ts +9 -0
  237. package/dist/settings/AppearanceMenu.js +54 -0
  238. package/dist/settings/SettingsPanel.d.ts +7 -0
  239. package/dist/settings/SettingsPanel.js +104 -0
  240. package/dist/settings/publishedSections.d.ts +22 -0
  241. package/dist/settings/publishedSections.js +79 -0
  242. package/dist/settings/sections/AboutSection.d.ts +1 -0
  243. package/dist/settings/sections/AboutSection.js +13 -0
  244. package/dist/settings/sections/DiagnosticsSection.d.ts +1 -0
  245. package/dist/settings/sections/DiagnosticsSection.js +42 -0
  246. package/dist/settings/sections/PrivacySection.d.ts +3 -0
  247. package/dist/settings/sections/PrivacySection.js +51 -0
  248. package/dist/settings/sections/UpdateLicenseBlock.d.ts +10 -0
  249. package/dist/settings/sections/UpdateLicenseBlock.js +158 -0
  250. package/dist/state/explorer.d.ts +32 -0
  251. package/dist/state/explorer.js +111 -0
  252. package/dist/state/legal.d.ts +51 -0
  253. package/dist/state/legal.js +130 -0
  254. package/dist/state/library.d.ts +33 -0
  255. package/dist/state/library.js +110 -0
  256. package/dist/state/updateLicense.d.ts +31 -0
  257. package/dist/state/updateLicense.js +340 -0
  258. package/dist/styles/explorer.css +219 -0
  259. package/dist/styles/index.css +1477 -0
  260. package/dist/styles/tabs.css +467 -0
  261. package/dist/styles.css +6 -0
  262. package/package.json +88 -3
@@ -0,0 +1,66 @@
1
+ import { useCallback, useMemo } from "react";
2
+ import { useTabChrome } from "./useTabChrome.js";
3
+ import { useDocumentZoom } from "./useDocumentZoom.js";
4
+ import { useAppLifecycle } from "./AppLifecycle.js";
5
+ import { useOpenFromClipboard } from "./useOpenFromClipboard.js";
6
+ import { baseName, isUntitled } from "@rightkit/document-core/documents/types";
7
+ /**
8
+ * Titlebar title, tab strip, document zoom, the open-file watch set, the lifecycle effects, and the
9
+ * open-from-clipboard action. Extracted verbatim from useAppState; hook call order is unchanged.
10
+ */
11
+ export function useAppTabChrome({ docs, tabs, activePath, sidebarCollapsed, tabOverviewOpen, setTabOverviewOpen, pinnedPaths, onTogglePin, dirtyPaths, subscribeDirty, active, view, settings, update, autoUpdateCheckRan, }) {
12
+ const { titleFor } = docs;
13
+ // Title bar carries the extension (e.g. "notes.md"); sidebar tabs stay stripped (the badge shows extension).
14
+ // As a function so the Toolbar's LiveTitle can recompute it live while typing an untitled doc.
15
+ const titleBarTitleFor = useCallback((d) => d.format === "web" || isUntitled(d.path) ? titleFor(d) : baseName(d.path), [titleFor]);
16
+ const { tabStripProps, openPathsSet, openExplorerPath, cycleTab, jumpToTab } = useTabChrome({
17
+ tabs,
18
+ docs,
19
+ activePath,
20
+ sidebarCollapsed,
21
+ tabOverviewOpen,
22
+ setTabOverviewOpen,
23
+ pinnedPaths,
24
+ onTogglePin,
25
+ titleBarTitleFor,
26
+ dirtyPaths,
27
+ subscribeDirty,
28
+ });
29
+ // Ctrl/Cmd+wheel document zoom + the zoom HUD/reset pill — extracted to useDocumentZoom.
30
+ const zoom = useDocumentZoom({ active, view, settings, update });
31
+ // Watch EVERY open file-backed tab plus each HTML session's visited sub-pages, in the one watcher.
32
+ // An external edit to ANY of them (active or background) arrives via the `file-changed` event and
33
+ // reloads that tab when it happens — so tab activation is a pure state change (no re-stat /
34
+ // reload-on-switch) and a background tab is already fresh by the time you click it. Keyed on the
35
+ // sorted path set so it re-arms only when a tab opens/closes/navigates, not on every keystroke.
36
+ const watchKey = useMemo(() => {
37
+ const files = tabs.openDocs
38
+ .filter((d) => d.format !== "web" && !isUntitled(d.path))
39
+ .map((d) => d.path);
40
+ const subPages = Object.values(tabs.htmlPages).flatMap((session) => Object.keys(session));
41
+ return [...new Set([...files, ...subPages])].sort().join("|");
42
+ }, [tabs.openDocs, tabs.htmlPages]);
43
+ // Ready-show, file watcher re-arm, background update check, and the "open URL as a tab" wiring.
44
+ useAppLifecycle({
45
+ watchKey,
46
+ autoCheckUpdates: settings.autoCheckUpdates,
47
+ autoUpdateCheckRan,
48
+ openLiveUrl: docs.openLiveUrl,
49
+ });
50
+ const openFromClipboard = useOpenFromClipboard({
51
+ open: docs.open,
52
+ openLiveUrl: docs.openLiveUrl,
53
+ duplicateAsDraft: docs.duplicateAsDraft,
54
+ setError: docs.setError,
55
+ });
56
+ return {
57
+ titleBarTitleFor,
58
+ tabStripProps,
59
+ openPathsSet,
60
+ openExplorerPath,
61
+ cycleTab,
62
+ jumpToTab,
63
+ zoom,
64
+ openFromClipboard,
65
+ };
66
+ }
@@ -0,0 +1,34 @@
1
+ import { type MutableRefObject } from "react";
2
+ import { type LoadedDocument } from "@rightkit/document-core/documents/types";
3
+ interface AutoSaveDeps {
4
+ /** settings.autoSave — file-backed edits persist after a short idle delay. */
5
+ autoSave: boolean;
6
+ openDocs: LoadedDocument[];
7
+ openDocsRef: MutableRefObject<LoadedDocument[]>;
8
+ activePath: string | null;
9
+ activePathRef: MutableRefObject<string | null>;
10
+ isDirty: (doc: LoadedDocument) => boolean;
11
+ docxSaver: MutableRefObject<(() => Promise<ArrayBuffer | null>) | null>;
12
+ onDocxSave: (path: string, buffer: ArrayBuffer) => Promise<void>;
13
+ onSave: (doc: LoadedDocument, options?: {
14
+ silent?: boolean;
15
+ resolveDrafts?: boolean;
16
+ }) => Promise<string | null>;
17
+ saveAllHtmlPages: (entryPath: string, options?: {
18
+ silent?: boolean;
19
+ }) => Promise<boolean>;
20
+ /** Scheduler slot the orchestrator's stable `queueAutoSave` forwarder calls through — assigned
21
+ * here each render (same ref-forward contract as before: the forwarder exists before this hook
22
+ * can be constructed, so the ref bridges the gap). */
23
+ autoSaveSchedulerRef: MutableRefObject<(path: string) => void>;
24
+ }
25
+ /**
26
+ * Idle-delay autosave for file-backed tabs (txt/md via onSave, docx via the active bridge's
27
+ * exporter, image via the payload saver, html via the session's saveAllHtmlPages), plus the T2-1
28
+ * draft-flush guards on unload/hide. Extracted verbatim from useDocuments — bodies unchanged; the
29
+ * only edits are `settings.autoSave`/`htmlSession.saveAllHtmlPages` read through their narrower
30
+ * params. `autoSaveTimers` is owned here; `autoSaveSchedulerRef` stays owned by the orchestrator so
31
+ * `queueAutoSave` (needed earlier, before this hook's deps exist) keeps its stable identity.
32
+ */
33
+ export declare function useAutoSave({ autoSave, openDocs, openDocsRef, activePath, activePathRef, isDirty, docxSaver, onDocxSave, onSave, saveAllHtmlPages, autoSaveSchedulerRef, }: AutoSaveDeps): void;
34
+ export {};
@@ -0,0 +1,116 @@
1
+ import { useCallback, useEffect, useRef } from "react";
2
+ import { isUntitled } from "@rightkit/document-core/documents/types";
3
+ import { editableImageOutputForPath } from "@rightkit/media-ui";
4
+ import { flushDrafts } from "./documentDrafts.js";
5
+ const AUTO_SAVE_DELAY_MS = 1000;
6
+ /**
7
+ * Idle-delay autosave for file-backed tabs (txt/md via onSave, docx via the active bridge's
8
+ * exporter, image via the payload saver, html via the session's saveAllHtmlPages), plus the T2-1
9
+ * draft-flush guards on unload/hide. Extracted verbatim from useDocuments — bodies unchanged; the
10
+ * only edits are `settings.autoSave`/`htmlSession.saveAllHtmlPages` read through their narrower
11
+ * params. `autoSaveTimers` is owned here; `autoSaveSchedulerRef` stays owned by the orchestrator so
12
+ * `queueAutoSave` (needed earlier, before this hook's deps exist) keeps its stable identity.
13
+ */
14
+ export function useAutoSave({ autoSave, openDocs, openDocsRef, activePath, activePathRef, isDirty, docxSaver, onDocxSave, onSave, saveAllHtmlPages, autoSaveSchedulerRef, }) {
15
+ const autoSaveTimers = useRef(new Map());
16
+ const scheduleAutoSave = useCallback((path) => {
17
+ const prior = autoSaveTimers.current.get(path);
18
+ if (prior)
19
+ clearTimeout(prior);
20
+ autoSaveTimers.current.delete(path);
21
+ if (!autoSave || isUntitled(path))
22
+ return;
23
+ const timer = setTimeout(() => {
24
+ autoSaveTimers.current.delete(path);
25
+ const doc = openDocsRef.current.find((candidate) => candidate.path === path);
26
+ if (!doc || !isDirty(doc))
27
+ return;
28
+ if (doc.format === "docx") {
29
+ // The imperative DOCX saver belongs to the active mounted bridge. Never risk using another
30
+ // tab's bridge if the user switched tabs during the idle delay.
31
+ if (activePathRef.current !== path)
32
+ return;
33
+ void docxSaver.current?.().then((buffer) => {
34
+ if (buffer)
35
+ void onDocxSave(path, buffer);
36
+ });
37
+ return;
38
+ }
39
+ if (doc.format === "image") {
40
+ if (activePathRef.current !== path || !editableImageOutputForPath(path))
41
+ return;
42
+ void onSave(doc, { silent: true, resolveDrafts: false });
43
+ return;
44
+ }
45
+ if (doc.format === "html") {
46
+ void saveAllHtmlPages(path, { silent: true });
47
+ return;
48
+ }
49
+ void onSave(doc, { silent: true, resolveDrafts: false });
50
+ }, AUTO_SAVE_DELAY_MS);
51
+ autoSaveTimers.current.set(path, timer);
52
+ }, [
53
+ docxSaver,
54
+ saveAllHtmlPages,
55
+ isDirty,
56
+ onDocxSave,
57
+ onSave,
58
+ autoSave,
59
+ activePathRef,
60
+ openDocsRef,
61
+ ]);
62
+ autoSaveSchedulerRef.current = scheduleAutoSave;
63
+ useEffect(() => {
64
+ if (autoSave) {
65
+ for (const doc of openDocs)
66
+ if (isDirty(doc))
67
+ scheduleAutoSave(doc.path);
68
+ return;
69
+ }
70
+ for (const timer of autoSaveTimers.current.values())
71
+ clearTimeout(timer);
72
+ autoSaveTimers.current.clear();
73
+ }, [isDirty, openDocs, scheduleAutoSave, autoSave]);
74
+ // DOCX and image exporters are registered by their active viewer. If a user switches tabs during
75
+ // the idle delay, their timer intentionally skips that unsafe save; retry when the tab becomes
76
+ // active again so autosave does not remain stranded behind the active-viewer guard.
77
+ useEffect(() => {
78
+ if (!autoSave || !activePath)
79
+ return;
80
+ const active = openDocs.find((doc) => doc.path === activePath);
81
+ if (active && isDirty(active))
82
+ scheduleAutoSave(active.path);
83
+ }, [activePath, isDirty, openDocs, scheduleAutoSave, autoSave]);
84
+ useEffect(() => () => {
85
+ for (const timer of autoSaveTimers.current.values())
86
+ clearTimeout(timer);
87
+ autoSaveTimers.current.clear();
88
+ }, []);
89
+ // T2-1 data-loss guard: onTxtChange's draft writes are debounced (~400ms). If the window/process goes
90
+ // away before a pending write's timer fires, that in-flight edit would be lost from the recovery
91
+ // draft (the live in-memory editsRef is what save/close actually use, so this only protects the
92
+ // startup-restore draft, not save correctness). Flush synchronously on both signals — beforeunload
93
+ // covers window close/reload, visibilitychange-hidden also covers OS-level app switch/minimize/quit
94
+ // paths that don't reliably fire beforeunload in a Tauri webview.
95
+ useEffect(() => {
96
+ const onBeforeUnload = (event) => {
97
+ flushDrafts();
98
+ // Browser close guard: block navigation if any file-backed dirty doc or modified block draft exists.
99
+ const hasUnsavedWork = openDocsRef.current.some((doc) => isDirty(doc));
100
+ if (hasUnsavedWork) {
101
+ event.preventDefault();
102
+ event.returnValue = "";
103
+ }
104
+ };
105
+ const onVisibilityChange = () => {
106
+ if (document.visibilityState === "hidden")
107
+ flushDrafts();
108
+ };
109
+ window.addEventListener("beforeunload", onBeforeUnload);
110
+ document.addEventListener("visibilitychange", onVisibilityChange);
111
+ return () => {
112
+ window.removeEventListener("beforeunload", onBeforeUnload);
113
+ document.removeEventListener("visibilitychange", onVisibilityChange);
114
+ };
115
+ }, [isDirty, openDocsRef]);
116
+ }
@@ -0,0 +1,52 @@
1
+ import { type MutableRefObject } from "react";
2
+ import { type LoadedDocument } from "@rightkit/document-core/documents/types";
3
+ import type { DocumentTabs } from "./useDocumentTabs.js";
4
+ import type { BlockDraftRegistry } from "@rightkit/document-core/app/blockDrafts";
5
+ import type { useHtmlSession } from "./useHtmlSession.js";
6
+ interface CloseAllDeps {
7
+ openDocsRef: MutableRefObject<LoadedDocument[]>;
8
+ isDirty: (doc: LoadedDocument) => boolean;
9
+ setOpenDocs: DocumentTabs["setOpenDocs"];
10
+ setActivePath: DocumentTabs["setActivePath"];
11
+ teardownDoc: (path: string, closingDoc: LoadedDocument | undefined) => void;
12
+ blockDrafts: BlockDraftRegistry;
13
+ /** Whole session object (same as the original deps-array entries): only saveAllHtmlPages is
14
+ * used, but passing the session keeps both call sites' dep expressions identical to inline. */
15
+ htmlSession: ReturnType<typeof useHtmlSession>;
16
+ onSaveFlushed: (doc: LoadedDocument, options?: {
17
+ silent?: boolean;
18
+ }) => Promise<string | null>;
19
+ docxSaver: MutableRefObject<(() => Promise<ArrayBuffer | null>) | null>;
20
+ docxSaversRef: MutableRefObject<Map<string, () => Promise<ArrayBuffer | null>>>;
21
+ onDocxSave: (path: string, buffer: ArrayBuffer) => Promise<void>;
22
+ onDocxSaveAs: (doc: LoadedDocument, buffer?: ArrayBuffer) => Promise<string | null>;
23
+ }
24
+ /**
25
+ * Bulk close + quit flows. Close All snapshots ordered affected paths (sorted dirty tabs) plus the
26
+ * full ordered snapshot so Save All/Don't Save can close clean tabs only after the dirty queue
27
+ * succeeds; the quit flow runs the same Save-All/Discard-All choice before destroying the window.
28
+ * Owns the confirmQuit/confirmCloseAll dialog state (not needed until after requestClose is
29
+ * constructed, so unlike the close/conflict/html dialog state it can live in this hook).
30
+ * Extracted verbatim from useDocuments; bodies are unchanged.
31
+ */
32
+ export declare function useCloseAll({ openDocsRef, isDirty, setOpenDocs, setActivePath, teardownDoc, blockDrafts, htmlSession, onSaveFlushed, docxSaver, docxSaversRef, onDocxSave, onDocxSaveAs, }: CloseAllDeps): {
33
+ confirmQuit: boolean;
34
+ setConfirmQuit: import("react").Dispatch<import("react").SetStateAction<boolean>>;
35
+ confirmCloseAll: {
36
+ orderedPaths: string[];
37
+ affectedPaths: string[];
38
+ } | null;
39
+ setConfirmCloseAll: import("react").Dispatch<import("react").SetStateAction<{
40
+ orderedPaths: string[];
41
+ affectedPaths: string[];
42
+ } | null>>;
43
+ closeAll: () => void;
44
+ closeMany: (paths: string[]) => void;
45
+ confirmCloseAllSaveAll: () => Promise<void>;
46
+ confirmCloseAllDontSave: () => Promise<void>;
47
+ confirmCloseAllCancel: () => void;
48
+ quitNow: () => Promise<void>;
49
+ quitSaveAll: () => Promise<void>;
50
+ quitDiscardAll: () => Promise<void>;
51
+ };
52
+ export {};
@@ -0,0 +1,295 @@
1
+ import { useCallback, useRef, useState } from "react";
2
+ import { getCurrentWindow } from "@tauri-apps/api/window";
3
+ import { isUntitled } from "@rightkit/document-core/documents/types";
4
+ import { toast } from "@rightkit/platform-ui/headless";
5
+ import { resolveDocumentDrafts } from "./draftResolution.js";
6
+ /**
7
+ * Bulk close + quit flows. Close All snapshots ordered affected paths (sorted dirty tabs) plus the
8
+ * full ordered snapshot so Save All/Don't Save can close clean tabs only after the dirty queue
9
+ * succeeds; the quit flow runs the same Save-All/Discard-All choice before destroying the window.
10
+ * Owns the confirmQuit/confirmCloseAll dialog state (not needed until after requestClose is
11
+ * constructed, so unlike the close/conflict/html dialog state it can live in this hook).
12
+ * Extracted verbatim from useDocuments; bodies are unchanged.
13
+ */
14
+ export function useCloseAll({ openDocsRef, isDirty, setOpenDocs, setActivePath, teardownDoc, blockDrafts, htmlSession, onSaveFlushed, docxSaver, docxSaversRef, onDocxSave, onDocxSaveAs, }) {
15
+ const [confirmQuit, setConfirmQuit] = useState(false);
16
+ // Close All bulk chooser: snapshots ordered affected paths (sorted dirty tabs) plus full ordered
17
+ // snapshot so Save All/Don't Save can close clean tabs only after the dirty queue succeeds.
18
+ const [confirmCloseAll, setConfirmCloseAll] = useState(null);
19
+ // Close All: one operation per spec. Snapshot ordered affected paths. If any dirty
20
+ // (isDirty includes modified block drafts), open the bulk Save/Don't Save/Cancel
21
+ // chooser instead of auto-saving. The dialog lists ordered dirty docs; Save All
22
+ // resolves each draft with Done then persists sequentially, stopping on first
23
+ // failure/cancel (no teardown for remaining). Don't Save discards drafts then
24
+ // closes all. Cancel stays. Clean tabs close only via the chooser's Save/Don't
25
+ // Save path — they are not torn down before the user decides.
26
+ const closeAllInProgress = useRef(false);
27
+ /** After `closedPaths` leave the tab list, keep the current active tab if it survives; otherwise
28
+ * activate the first remaining tab (snapshot order). closeAll() gets remaining=[] → null, so the
29
+ * old null-out behavior is preserved. */
30
+ const fixActiveAfterClose = useCallback((closedPaths) => {
31
+ const closed = new Set(closedPaths);
32
+ setActivePath((cur) => {
33
+ if (cur && !closed.has(cur))
34
+ return cur;
35
+ const remaining = openDocsRef.current.filter((d) => !closed.has(d.path));
36
+ return remaining[0]?.path ?? null;
37
+ });
38
+ }, [openDocsRef, setActivePath]);
39
+ /** Close a chosen subset of tabs — the engine behind Close All, Close Others, and Close to the
40
+ * Right. Dirty docs route through the same Save All / Don't Save bulk chooser closeAll uses. */
41
+ const closeMany = useCallback((paths) => {
42
+ if (closeAllInProgress.current)
43
+ return;
44
+ if (confirmCloseAll)
45
+ return; // dialog already open
46
+ const wanted = new Set(paths);
47
+ const ordered = [...openDocsRef.current]
48
+ .filter((d) => wanted.has(d.path))
49
+ .sort((a, b) => a.path.localeCompare(b.path));
50
+ if (ordered.length === 0)
51
+ return;
52
+ const affected = ordered.filter((doc) => isDirty(doc));
53
+ if (affected.length === 0) {
54
+ for (const doc of ordered)
55
+ teardownDoc(doc.path, doc);
56
+ const orderedSet = new Set(ordered.map((d) => d.path));
57
+ setOpenDocs((prev) => prev.filter((d) => !orderedSet.has(d.path)));
58
+ fixActiveAfterClose(ordered.map((d) => d.path));
59
+ return;
60
+ }
61
+ setConfirmCloseAll({
62
+ orderedPaths: ordered.map((d) => d.path),
63
+ affectedPaths: affected.map((d) => d.path),
64
+ });
65
+ }, [isDirty, teardownDoc, setOpenDocs, fixActiveAfterClose, confirmCloseAll, openDocsRef]);
66
+ const closeAll = useCallback(() => {
67
+ closeMany(openDocsRef.current.map((d) => d.path));
68
+ }, [closeMany, openDocsRef]);
69
+ const confirmCloseAllSaveAll = useCallback(async () => {
70
+ const snapshot = confirmCloseAll;
71
+ if (!snapshot)
72
+ return;
73
+ if (closeAllInProgress.current)
74
+ return;
75
+ closeAllInProgress.current = true;
76
+ setConfirmCloseAll(null);
77
+ try {
78
+ const { orderedPaths, affectedPaths } = snapshot;
79
+ // Save each affected doc in snapshot order (already sorted)
80
+ for (const path of affectedPaths) {
81
+ const doc = openDocsRef.current.find((d) => d.path === path);
82
+ if (!doc)
83
+ continue;
84
+ // Save = Done then persist. Resolve draft first; a stale/failed Done blocks the write.
85
+ if (blockDrafts.snapshotModified(path).length > 0) {
86
+ const outcome = await resolveDocumentDrafts(blockDrafts, path, "close-all", "done");
87
+ if (outcome !== "resolved") {
88
+ toast(`Close All cancelled — could not save ${doc.displayName}`, "error");
89
+ return;
90
+ }
91
+ }
92
+ if (doc.format === "html") {
93
+ const ok = await htmlSession.saveAllHtmlPages(path);
94
+ if (!ok) {
95
+ toast(`Close All cancelled — could not save ${doc.displayName}`, "error");
96
+ return;
97
+ }
98
+ }
99
+ else if (doc.format === "docx") {
100
+ if (isUntitled(doc.path)) {
101
+ const savedPath = await onDocxSaveAs(doc);
102
+ if (!savedPath) {
103
+ toast(`Close All cancelled — could not save ${doc.displayName}`, "error");
104
+ return;
105
+ }
106
+ const newDoc = openDocsRef.current.find((d) => d.path === savedPath) ?? {
107
+ ...doc,
108
+ path: savedPath,
109
+ };
110
+ teardownDoc(savedPath, newDoc);
111
+ // Remove both old untitled path (if still present) and new saved path in one update
112
+ setOpenDocs((prev) => prev.filter((d) => d.path !== path && d.path !== savedPath));
113
+ continue;
114
+ }
115
+ const ab = await (docxSaversRef.current.get(path) ?? docxSaver.current)?.();
116
+ if (ab) {
117
+ await onDocxSave(path, ab);
118
+ }
119
+ else {
120
+ toast(`Close All cancelled — could not save ${doc.displayName}`, "error");
121
+ return;
122
+ }
123
+ }
124
+ else if (doc.format === "image") {
125
+ const saved = await onSaveFlushed(doc);
126
+ if (!saved) {
127
+ toast(`Close All cancelled — could not save ${doc.displayName}`, "error");
128
+ return;
129
+ }
130
+ if (isUntitled(doc.path) && typeof saved === "string" && saved !== path) {
131
+ const newDoc = openDocsRef.current.find((d) => d.path === saved) ?? {
132
+ ...doc,
133
+ path: saved,
134
+ };
135
+ teardownDoc(saved, newDoc);
136
+ setOpenDocs((prev) => prev.filter((d) => d.path !== path && d.path !== saved));
137
+ continue;
138
+ }
139
+ }
140
+ else {
141
+ const saved = await onSaveFlushed(doc);
142
+ if (!saved) {
143
+ toast(`Close All cancelled — could not save ${doc.displayName}`, "error");
144
+ return;
145
+ }
146
+ if (isUntitled(doc.path) && typeof saved === "string" && saved !== path) {
147
+ const newDoc = openDocsRef.current.find((d) => d.path === saved) ?? {
148
+ ...doc,
149
+ path: saved,
150
+ };
151
+ teardownDoc(saved, newDoc);
152
+ setOpenDocs((prev) => prev.filter((d) => d.path !== path && d.path !== saved));
153
+ continue;
154
+ }
155
+ }
156
+ teardownDoc(path, doc);
157
+ setOpenDocs((prev) => prev.filter((d) => d.path !== path));
158
+ }
159
+ // All affected saved; now close clean tabs in one batch
160
+ const cleanPaths = orderedPaths.filter((p) => !affectedPaths.includes(p));
161
+ for (const p of cleanPaths) {
162
+ const doc = openDocsRef.current.find((d) => d.path === p);
163
+ if (doc)
164
+ teardownDoc(p, doc);
165
+ }
166
+ if (cleanPaths.length > 0) {
167
+ const cleanSet = new Set(cleanPaths);
168
+ setOpenDocs((prev) => prev.filter((d) => !cleanSet.has(d.path)));
169
+ }
170
+ fixActiveAfterClose(orderedPaths);
171
+ }
172
+ finally {
173
+ closeAllInProgress.current = false;
174
+ }
175
+ }, [
176
+ confirmCloseAll,
177
+ blockDrafts,
178
+ htmlSession,
179
+ onSaveFlushed,
180
+ docxSaver,
181
+ docxSaversRef,
182
+ onDocxSave,
183
+ onDocxSaveAs,
184
+ teardownDoc,
185
+ setOpenDocs,
186
+ fixActiveAfterClose,
187
+ openDocsRef,
188
+ ]);
189
+ const confirmCloseAllDontSave = useCallback(async () => {
190
+ const snapshot = confirmCloseAll;
191
+ if (!snapshot)
192
+ return;
193
+ if (closeAllInProgress.current)
194
+ return;
195
+ closeAllInProgress.current = true;
196
+ setConfirmCloseAll(null);
197
+ try {
198
+ const { orderedPaths, affectedPaths } = snapshot;
199
+ for (const path of affectedPaths) {
200
+ if (blockDrafts.snapshotModified(path).length > 0) {
201
+ await resolveDocumentDrafts(blockDrafts, path, "close-all", "discard");
202
+ }
203
+ }
204
+ for (const p of orderedPaths) {
205
+ const doc = openDocsRef.current.find((d) => d.path === p);
206
+ if (doc)
207
+ teardownDoc(p, doc);
208
+ }
209
+ const orderedSet = new Set(orderedPaths);
210
+ setOpenDocs((prev) => prev.filter((d) => !orderedSet.has(d.path)));
211
+ fixActiveAfterClose(orderedPaths);
212
+ }
213
+ finally {
214
+ closeAllInProgress.current = false;
215
+ }
216
+ }, [confirmCloseAll, blockDrafts, teardownDoc, setOpenDocs, fixActiveAfterClose, openDocsRef]);
217
+ const confirmCloseAllCancel = useCallback(() => {
218
+ setConfirmCloseAll(null);
219
+ }, []);
220
+ const quitNow = useCallback(async () => {
221
+ await getCurrentWindow().destroy();
222
+ }, []);
223
+ // Save All before quit: snapshot ordered affected paths, resolve/save every affected
224
+ // document+page in sequence; quit only after every Done + disk write succeeds. Any failure
225
+ // cancels the quit with the tab still open. Single sequential operation per spec.
226
+ const quitSaveAll = useCallback(async () => {
227
+ const ordered = [...openDocsRef.current]
228
+ .filter((doc) => isDirty(doc))
229
+ .sort((a, b) => a.path.localeCompare(b.path));
230
+ // Ensure block-draft ordering is respected: snapshotModified is already sorted by identity key,
231
+ // but ordering the doc list by path matches that deterministic order for the document-level loop.
232
+ for (const doc of ordered) {
233
+ if (doc.format === "html") {
234
+ const ok = await htmlSession.saveAllHtmlPages(doc.path);
235
+ if (!ok) {
236
+ toast(`Quit cancelled — could not save ${doc.displayName}`, "error");
237
+ return;
238
+ }
239
+ }
240
+ else if (doc.format === "docx") {
241
+ const buffer = await (docxSaversRef.current.get(doc.path) ?? docxSaver.current)?.();
242
+ let saved = null;
243
+ if (isUntitled(doc.path))
244
+ saved = await onDocxSaveAs(doc, buffer ?? undefined);
245
+ else if (buffer) {
246
+ await onDocxSave(doc.path, buffer);
247
+ saved = doc.path;
248
+ }
249
+ if (!saved) {
250
+ toast(`Quit cancelled — could not save ${doc.displayName}`, "error");
251
+ return;
252
+ }
253
+ }
254
+ else {
255
+ const saved = await onSaveFlushed(doc);
256
+ if (!saved) {
257
+ toast(`Quit cancelled — could not save ${doc.displayName}`, "error");
258
+ return;
259
+ }
260
+ }
261
+ }
262
+ await quitNow();
263
+ }, [
264
+ htmlSession.saveAllHtmlPages,
265
+ isDirty,
266
+ onSaveFlushed,
267
+ quitNow,
268
+ docxSaversRef,
269
+ docxSaver,
270
+ onDocxSaveAs,
271
+ onDocxSave,
272
+ openDocsRef,
273
+ ]);
274
+ // Quit without saving: discard every open block draft, then quit (whole-document edits are
275
+ // dropped by the existing dirty-state semantics).
276
+ const quitDiscardAll = useCallback(async () => {
277
+ for (const draft of blockDrafts.snapshotModified())
278
+ await blockDrafts.resolve(draft.identity, "quit", "discard");
279
+ await quitNow();
280
+ }, [blockDrafts, quitNow]);
281
+ return {
282
+ confirmQuit,
283
+ setConfirmQuit,
284
+ confirmCloseAll,
285
+ setConfirmCloseAll,
286
+ closeAll,
287
+ closeMany,
288
+ confirmCloseAllSaveAll,
289
+ confirmCloseAllDontSave,
290
+ confirmCloseAllCancel,
291
+ quitNow,
292
+ quitSaveAll,
293
+ quitDiscardAll,
294
+ };
295
+ }
@@ -0,0 +1,10 @@
1
+ import type { LoadedDocument } from "@rightkit/document-core/documents/types";
2
+ /** Auto-evict per-path caches when tabs close: compare previous openDocs to current and evict removed
3
+ * paths. Extracted verbatim from App.tsx. */
4
+ export declare function useClosedTabEviction({ openDocs, onEvictPath, dropHtmlProps, sourceViewStateRef, pruneClosedPaths, }: {
5
+ openDocs: LoadedDocument[];
6
+ onEvictPath: (path: string) => void;
7
+ dropHtmlProps: (path: string) => void;
8
+ sourceViewStateRef: React.MutableRefObject<Record<string, unknown>>;
9
+ pruneClosedPaths: (paths: string[]) => void;
10
+ }): void;
@@ -0,0 +1,38 @@
1
+ import { useEffect, useRef } from "react";
2
+ import { clearImageEditSnapshot } from "@rightkit/media-ui";
3
+ import { inTauri } from "@rightkit/document-core/state/tauriBridge";
4
+ import { isMac } from "@rightkit/document-core/platform";
5
+ /** Auto-evict per-path caches when tabs close: compare previous openDocs to current and evict removed
6
+ * paths. Extracted verbatim from App.tsx. */
7
+ export function useClosedTabEviction({ openDocs, onEvictPath, dropHtmlProps, sourceViewStateRef, pruneClosedPaths, }) {
8
+ const prevOpenDocsRef = useRef(openDocs);
9
+ useEffect(() => {
10
+ const prev = prevOpenDocsRef.current;
11
+ if (prev !== openDocs) {
12
+ const current = new Set(openDocs.map((d) => d.path));
13
+ const closedPaths = [];
14
+ for (const doc of prev) {
15
+ if (!current.has(doc.path)) {
16
+ onEvictPath(doc.path);
17
+ dropHtmlProps(doc.path);
18
+ clearImageEditSnapshot(doc.path);
19
+ // A PDF pane can unmount merely because its router leaves the live-tab budget.
20
+ // Only removal from openDocs is terminal for its parked native reader.
21
+ if (doc.format === "pdf" && inTauri() && isMac()) {
22
+ void import("@rightkit/document-core/documents/pdf/macPdfNativeBridge")
23
+ .then(({ nativePdfClosePath }) => nativePdfClosePath(doc.path))
24
+ .catch(() => undefined);
25
+ }
26
+ delete sourceViewStateRef.current[doc.path];
27
+ closedPaths.push(doc.path);
28
+ }
29
+ }
30
+ // T5-2: drop closed tabs' rendered/live/recent keep-alive markers too — owned by
31
+ // usePaneLiveness (see the hook for why each set must be pruned).
32
+ if (closedPaths.length) {
33
+ pruneClosedPaths(closedPaths);
34
+ }
35
+ prevOpenDocsRef.current = openDocs;
36
+ }
37
+ }, [openDocs, pruneClosedPaths, dropHtmlProps, onEvictPath, sourceViewStateRef]);
38
+ }
@@ -0,0 +1,44 @@
1
+ import { type LoadedDocument, type ViewMode } from "@rightkit/document-core/documents/types";
2
+ import type { FileEntry } from "../state/library.js";
3
+ import type { Settings } from "@rightkit/document-core/settings/settings";
4
+ import type { DocxMenuApi } from "@rightkit/docx-editor/documents/docx/menuApi";
5
+ import type { PaletteItem } from "./CommandPalette.js";
6
+ interface PaletteDeps {
7
+ openDocs: LoadedDocument[];
8
+ recents: FileEntry[];
9
+ active: LoadedDocument | null;
10
+ activeEditable: boolean;
11
+ titleFor: (doc: LoadedDocument) => string;
12
+ activate: (path: string) => void | Promise<void>;
13
+ openRecent: (entry: FileEntry) => undefined | Promise<unknown>;
14
+ newDocument: () => void;
15
+ onOpenDialog: () => void | Promise<void>;
16
+ onOpenUrl: () => void;
17
+ onSave: (doc: LoadedDocument) => undefined | Promise<unknown>;
18
+ onSaveAs: (doc: LoadedDocument) => undefined | Promise<unknown>;
19
+ onPrint: (doc: LoadedDocument | null) => void;
20
+ docxMenu: DocxMenuApi | null;
21
+ onChangeView: (next: ViewMode) => void | Promise<void>;
22
+ settings: Settings;
23
+ update: (patch: Partial<Settings>) => void;
24
+ openSettings: () => void;
25
+ reopenClosed: () => void;
26
+ showHelp: () => void;
27
+ /** Copy the active document's text (text-like formats only); null/undefined hides the command. */
28
+ copyContents: (() => void) | null;
29
+ /** Export the active Markdown doc to HTML / plain text; null hides the commands. */
30
+ exportMarkdown: ((target: "html" | "txt") => void) | null;
31
+ /** Copy the active Markdown's RENDERED output to the clipboard (rich HTML + text); null hides it. */
32
+ copyRendered: (() => void) | null;
33
+ /** Copy a PDF/DOCX converted to Markdown; null hides it. */
34
+ copyAsMarkdown: (() => void) | null;
35
+ /** Duplicate the active text doc into a new unsaved draft; null hides the command. */
36
+ duplicate: (() => void) | null;
37
+ }
38
+ /**
39
+ * Command-palette entries: open tabs + recents + context-aware commands. Routes through the
40
+ * existing document handlers — the palette is just a uniform, cross-format way to reach them.
41
+ * Format-specific commands (save/print/view) gate on `capabilitiesFor`, not ad-hoc format checks.
42
+ */
43
+ export declare function useCommandPalette(d: PaletteDeps): PaletteItem[];
44
+ export {};