@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.
Files changed (262) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/LICENSE-APACHE +201 -0
  3. package/LICENSE-MIT +21 -0
  4. package/README.md +74 -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,32 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // Document status row (App's bottom status bar): words/chars/reading time (click cycles),
3
+ // save state and autosave, docked to the bottom edge of the page outside the scrolling content.
4
+ // Subscribes to the doc's live edit buffer so keystrokes update the count without re-rendering
5
+ // the App shell. Only text formats (md, mdx, txt, html, unknown) show counts.
6
+ import { memo, useState } from "react";
7
+ import { Check } from "@phosphor-icons/react/dist/icons/Check";
8
+ import { shortcutLabel } from "@rightkit/document-core/platform";
9
+ import { useTabDirty } from "../tabs/useTabDirty.js";
10
+ import { useEditText } from "@rightkit/document-core/app/useEditText";
11
+ const STATS_FORMATS = new Set(["md", "mdx", "txt", "html", "unknown"]);
12
+ const NEXT_MODE = { words: "chars", chars: "read", read: "words" };
13
+ export const DocStatsPill = memo(DocStatsPillImpl);
14
+ function DocStatsPillImpl({ doc, subscribeEdits, getEditSnapshot, status, }) {
15
+ const liveText = useEditText(subscribeEdits, getEditSnapshot, doc.path) ?? doc.content;
16
+ const [mode, setMode] = useState("words");
17
+ const dirty = useTabDirty(doc, status?.dirtyHint ?? false, status?.isDirty ?? NEVER_DIRTY, status?.subscribeDirty ?? NO_SUBSCRIBE);
18
+ const showStats = STATS_FORMATS.has(doc.format);
19
+ if (!showStats && !status)
20
+ return null;
21
+ const trimmed = liveText.trim();
22
+ const words = trimmed ? trimmed.split(/\s+/).length : 0;
23
+ const mins = words ? Math.max(1, Math.round(words / 230)) : 0;
24
+ const label = mode === "words"
25
+ ? `${words.toLocaleString()} words`
26
+ : mode === "chars"
27
+ ? `${liveText.length.toLocaleString()} chars`
28
+ : `~${mins} min read`;
29
+ return (_jsxs("div", { className: "flex min-w-0 items-center gap-3 whitespace-nowrap font-sans", "data-vr-doc-status": true, children: [showStats && (_jsx("button", { type: "button", onClick: () => setMode((m) => NEXT_MODE[m]), title: "Document statistics \u2014 click to cycle words, characters, reading time", "aria-label": `${label}. Click to cycle document statistics.`, "data-vr-stats": true, className: "vr-focus h-6 rounded px-1 tabular-nums hover:text-paper", children: label })), status && (_jsxs(_Fragment, { children: [dirty ? (_jsxs("button", { type: "button", onClick: status.onSave, title: `Save (${shortcutLabel("mod+s")})`, "aria-label": "Unsaved changes. Save", className: "vr-focus flex h-6 items-center gap-1.5 rounded px-1.5 text-paper hover:bg-ink/30", children: [_jsx("span", { "aria-hidden": "true", className: "h-1.5 w-1.5 rounded-full bg-copper" }), "Save"] })) : (_jsxs("span", { className: "flex items-center gap-1.5 px-1 text-paper/70", children: [_jsx(Check, { size: 11, weight: "bold", "aria-hidden": true, className: "text-accent2" }), "Saved"] })), _jsxs("button", { type: "button", role: "switch", "aria-checked": status.autoSave, onClick: status.onToggleAutoSave, title: "Save automatically after you pause", className: "vr-focus flex h-6 items-center gap-1.5 rounded px-1 hover:text-paper", children: ["Autosave", _jsx("span", { "aria-hidden": "true", className: `relative h-3.5 w-6 rounded-full transition-colors ${status.autoSave ? "bg-copper" : "bg-line"}`, children: _jsx("span", { className: `absolute left-0 top-0.5 h-2.5 w-2.5 rounded-full shadow-sm transition-transform ${status.autoSave ? "translate-x-3 bg-panel" : "translate-x-0.5 bg-muted"}` }) })] })] }))] }));
30
+ }
31
+ const NEVER_DIRTY = () => false;
32
+ const NO_SUBSCRIBE = () => () => { };
@@ -0,0 +1,9 @@
1
+ import type { LoadedDocument, ViewMode } from "@rightkit/document-core/documents/types";
2
+ export declare const DocumentTools: import("react").MemoExoticComponent<typeof DocumentToolsImpl>;
3
+ declare function DocumentToolsImpl({ doc, view, inspectorOpen, onToggleInspector, }: {
4
+ doc: LoadedDocument;
5
+ view: ViewMode;
6
+ inspectorOpen: boolean;
7
+ onToggleInspector: () => void;
8
+ }): import("react").JSX.Element;
9
+ export {};
@@ -0,0 +1,73 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // Wave 3 right-hand toolbar capsule: the "Aa" text-size popover (text formats) and the Info
3
+ // inspector toggle. Sits in the title bar's right pill area next to the per-format mode controls.
4
+ // The Aa popover is document-only: it steps the same per-surface zoom setting the Ctrl/Cmd+wheel
5
+ // handler in useDocumentZoom drives. App-wide look (theme, interface font) lives in the sidebar
6
+ // footer's Appearance menu and Settings → Theme — not here.
7
+ import { isMarkdownFormat } from "../../documents/capabilities.js";
8
+ import { memo, useCallback, useLayoutEffect, useRef, useState } from "react";
9
+ import { SidebarSimple } from "@phosphor-icons/react/dist/icons/SidebarSimple";
10
+ import { TextAa } from "@phosphor-icons/react/dist/icons/TextAa";
11
+ import { BUNDLED_FONTS } from "@rightkit/document-core/settings/fonts";
12
+ import { useSettings } from "@rightkit/document-core/settings/settings";
13
+ import { useDismiss, useModalDismiss } from "@rightkit/platform-ui/headless";
14
+ // Formats whose reading/source text the shell's zoom settings actually size. HTML's interactive
15
+ // preview owns its own iframe zoom (Ctrl+wheel in-page), so its popover stepper follows the source
16
+ // drawer's zoom setting — see the comment in AppearancePopover.
17
+ const AA_FORMATS = new Set(["md", "mdx", "txt", "html"]);
18
+ export const DocumentTools = memo(DocumentToolsImpl);
19
+ function DocumentToolsImpl({ doc, view, inspectorOpen, onToggleInspector, }) {
20
+ return (_jsxs("div", { className: "flex items-center gap-0.5", children: [AA_FORMATS.has(doc.format) ? _jsx(TextAppearanceButton, { doc: doc, view: view }) : null, _jsx("button", { id: "vr-inspector-toggle", type: "button", "aria-pressed": inspectorOpen, "aria-controls": "vr-info-inspector", "aria-label": inspectorOpen ? "Hide document info" : "Show document info", title: inspectorOpen ? "Hide info" : "Show info", onClick: onToggleInspector, className: `vr-press vr-focus flex h-7 w-7 items-center justify-center rounded-full ${inspectorOpen
21
+ ? "bg-copper/[0.14] text-copper-hi"
22
+ : "text-muted-2 hover:bg-ink/40 hover:text-paper"}`, children: _jsx(SidebarSimple, { size: 17, weight: "regular", "aria-hidden": true, className: "-scale-x-100" }) })] }));
23
+ }
24
+ const capsuleBtn = "vr-press vr-focus flex h-7 w-7 items-center justify-center rounded-full";
25
+ function TextAppearanceButton({ doc, view }) {
26
+ const [open, setOpen] = useState(false);
27
+ const buttonRef = useRef(null);
28
+ // The popover takes focus on open (so Escape lands inside it); every close path returns focus
29
+ // to the trigger button.
30
+ const close = useCallback(() => {
31
+ setOpen(false);
32
+ buttonRef.current?.focus();
33
+ }, []);
34
+ return (_jsxs("span", { className: "relative", children: [_jsx("button", { ref: buttonRef, type: "button", "aria-haspopup": "dialog", "aria-expanded": open, "aria-controls": open ? "vr-aa-popover" : undefined, "aria-label": "Text size", title: "Text size", onClick: () => (open ? close() : setOpen(true)), className: `${capsuleBtn} ${open ? "bg-copper/[0.14] text-copper-hi" : "text-muted-2 hover:bg-ink/40 hover:text-paper"}`, children: _jsx(TextAa, { size: 17, weight: "regular", "aria-hidden": true }) }), open ? _jsx(AppearancePopover, { doc: doc, view: view, onClose: close }) : null] }));
35
+ }
36
+ function AppearancePopover({ doc, view, onClose, }) {
37
+ const { settings, update } = useSettings();
38
+ const panelRef = useRef(null);
39
+ // Escape closes (window capture, stopPropagation only); a click on the scrim closes it.
40
+ const { closing, close } = useModalDismiss(onClose, { escape: true, preventDefault: false });
41
+ const scrimRef = useRef(null);
42
+ useDismiss(() => close(), {
43
+ escape: false,
44
+ outside: "click-target",
45
+ container: panelRef,
46
+ backdrop: scrimRef,
47
+ imeGuard: false,
48
+ });
49
+ // Focus the dialog on open so Escape is scoped to it and screen readers announce it.
50
+ useLayoutEffect(() => {
51
+ panelRef.current?.focus();
52
+ }, []);
53
+ // Same key/base/min/max the Ctrl/Cmd+wheel zoom in useDocumentZoom uses: reading surfaces take
54
+ // fontSizePx (base 16, 12–28), source surfaces take sourceFontSizePx (base 12, 10–24). HTML's
55
+ // preview zoom lives inside its opaque-origin iframe, so its shell-owned text size is the
56
+ // source drawer's.
57
+ const sourceSurface = doc.format === "html" || (isMarkdownFormat(doc.format) && view === "source");
58
+ const zoomKey = sourceSurface ? "sourceFontSizePx" : "fontSizePx";
59
+ const base = sourceSurface ? 12 : 16;
60
+ const min = sourceSurface ? 10 : 12;
61
+ const max = sourceSurface ? 24 : 28;
62
+ const current = settings[zoomKey];
63
+ const percent = Math.round((current / base) * 100);
64
+ const step = (delta) => update({ [zoomKey]: Math.max(min, Math.min(max, current + delta)) });
65
+ return (_jsxs(_Fragment, { children: [_jsx("div", { ref: scrimRef, className: "no-print fixed inset-0 z-[55]", "aria-hidden": true }), _jsxs("div", { ref: panelRef, id: "vr-aa-popover", role: "dialog", "aria-label": "Text", tabIndex: -1, className: `vr-pop vr-pop-menu-tr${closing ? " vr-pop-exit" : ""} no-print vr-glass-panel absolute top-[calc(100%+8px)] right-0 z-[60] w-[220px] max-h-[calc(100vh-8rem)] overflow-y-auto rounded-[18px] p-3.5 outline-none`, children: [_jsx("h4", { className: "mb-1.5 font-sans text-chrome-xs font-semibold text-muted-2", children: "Text size" }), _jsxs("div", { className: "flex h-10 overflow-hidden rounded-xl bg-paper/5", children: [_jsx("button", { type: "button", "aria-label": "Decrease text size", title: "Decrease text size", disabled: current <= min, onClick: () => step(-1), className: "vr-press vr-focus flex flex-1 items-center justify-center font-serif text-chrome-md text-paper hover:bg-paper/10 disabled:opacity-40", children: "A" }), _jsx("button", { type: "button", "aria-label": "Increase text size", title: "Increase text size", disabled: current >= max, onClick: () => step(1), className: "vr-press vr-focus flex flex-1 items-center justify-center border-l border-line font-serif text-[20px] leading-none text-paper hover:bg-paper/10 disabled:opacity-40", children: "A" })] }), _jsxs("div", { className: "mt-1.5 flex items-center justify-between px-0.5", children: [_jsxs("span", { className: "font-mono text-chrome-xs tabular-nums text-muted-2", children: [percent, "%"] }), _jsx("button", { type: "button", disabled: current === base, onClick: () => update({ [zoomKey]: base }), title: "Reset text size", className: "vr-press vr-focus rounded-md px-2 py-1 font-sans text-chrome-xs text-muted hover:bg-paper/5 hover:text-paper disabled:opacity-40", children: "Reset" })] }), !sourceSurface && doc.format === "md" && (_jsxs(_Fragment, { children: [_jsx("h4", { id: "vr-aa-width-label", className: "mb-1.5 mt-3 font-sans text-chrome-xs font-semibold text-muted-2", children: "Content width" }), _jsxs("div", { role: "group", "aria-labelledby": "vr-aa-width-label", children: [_jsx("div", { className: "flex h-9 overflow-hidden rounded-xl bg-paper/5", children: ["full", "column"].map((mode) => (_jsx("button", { type: "button", "aria-pressed": settings.readingWidthMode === mode, onClick: () => update({ readingWidthMode: mode }), className: `vr-press vr-focus flex-1 whitespace-nowrap px-2 font-sans text-chrome-xs ${settings.readingWidthMode === mode
66
+ ? "bg-copper/[0.14] text-copper-hi"
67
+ : "text-muted hover:bg-paper/10 hover:text-paper"}`, children: mode === "full" ? "Full width" : "Reading column" }, mode))) }), settings.readingWidthMode === "column" && (_jsxs("div", { className: "mt-2", children: [_jsx("input", { type: "range", "aria-label": "Reading column width", min: 560, max: 960, step: 20, value: settings.readingWidthPx, onChange: (e) => update({ readingWidthPx: Number(e.target.value) }), className: "w-full accent-[var(--copper)]" }), _jsxs("div", { className: "mt-1 flex items-center justify-between px-0.5", children: [_jsxs("span", { className: "font-mono text-chrome-xs tabular-nums text-muted-2", children: [settings.readingWidthPx, "px"] }), _jsx("button", { type: "button", disabled: settings.readingWidthPx === 700, onClick: () => update({ readingWidthPx: 700 }), title: "Reset column width", className: "vr-press vr-focus rounded-md px-2 py-1 font-sans text-chrome-xs text-muted hover:bg-paper/5 hover:text-paper disabled:opacity-40", children: "Reset" })] })] }))] })] })), !sourceSurface && (_jsxs(_Fragment, { children: [_jsx("h4", { id: "vr-aa-font-label", className: "mb-1.5 mt-3 font-sans text-chrome-xs font-semibold text-muted-2", children: "Reading font" }), _jsx("div", { role: "radiogroup", "aria-labelledby": "vr-aa-font-label", className: "flex flex-col gap-0.5", children: READING_FONTS.map((f) => {
68
+ const checked = settings.fontFamily === f.id;
69
+ return (_jsxs("button", { type: "button", role: "radio", "aria-checked": checked, onClick: () => update({ fontFamily: f.id }), className: `vr-press vr-focus flex h-8 items-center justify-between rounded-lg px-2.5 text-left text-[14px] text-paper hover:bg-paper/10${checked ? " bg-paper/10" : ""}`, children: [_jsx("span", { style: { fontFamily: f.stack }, children: f.name }), checked && (_jsx("span", { "aria-hidden": "true", className: "text-chrome-sm text-muted", children: "\u2713" }))] }, f.id));
70
+ }) })] }))] })] }));
71
+ }
72
+ /** Reading fonts offered for Markdown and text (monospace faces stay for code and source). */
73
+ const READING_FONTS = BUNDLED_FONTS.filter((f) => f.kind !== "mono");
@@ -0,0 +1,9 @@
1
+ import type { LoadedDocument } from "@rightkit/document-core/documents/types";
2
+ export declare const InfoInspector: import("react").MemoExoticComponent<typeof InfoInspectorImpl>;
3
+ declare function InfoInspectorImpl({ doc, onClose, subscribeEdits, getEditSnapshot, }: {
4
+ doc: LoadedDocument;
5
+ onClose: () => void;
6
+ subscribeEdits: (path: string, cb: () => void) => () => void;
7
+ getEditSnapshot: (path: string) => string | undefined;
8
+ }): import("react").JSX.Element;
9
+ export {};
@@ -0,0 +1,67 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // Wave 3 Info inspector — a 280px panel docked at the right edge of the content plane. It is a
3
+ // flex SIBLING of the plane (the plane shrinks), so it never overlaps the floating format bar or
4
+ // find bar. Shows only data already on LoadedDocument (path, encoding, fingerprint) or reachable
5
+ // through the live edit subscription the DocStatsPill uses — no backend calls, no invented fields.
6
+ // One global toggle owns it (App.tsx); Escape inside the panel closes it via onClose, which returns
7
+ // focus to the toolbar toggle button.
8
+ import { memo, useRef } from "react";
9
+ import { useDismiss } from "@rightkit/platform-ui/headless";
10
+ import { Copy } from "@phosphor-icons/react/dist/icons/Copy";
11
+ import { LockSimple } from "@phosphor-icons/react/dist/icons/LockSimple";
12
+ import { X } from "@phosphor-icons/react/dist/icons/X";
13
+ import { KIND_LABEL } from "../Toolbar.js";
14
+ import { copyText, isLocalFileDoc } from "../sidebar/SidebarPathUtils.js";
15
+ import { useEditText } from "@rightkit/document-core/app/useEditText";
16
+ // Same formats the DocStatsPill counts — the only surfaces with a live text buffer.
17
+ const COUNT_FORMATS = new Set(["md", "mdx", "txt", "html", "unknown"]);
18
+ export const InfoInspector = memo(InfoInspectorImpl);
19
+ function InfoInspectorImpl({ doc, onClose, subscribeEdits, getEditSnapshot, }) {
20
+ // Escape inside the panel closes it (bubble phase on the panel; stops propagation, as before).
21
+ const panelRef = useRef(null);
22
+ useDismiss(onClose, {
23
+ escapeTarget: panelRef,
24
+ outside: false,
25
+ preventDefault: false,
26
+ imeGuard: false,
27
+ });
28
+ // "Location" means the real filesystem path for file docs and the URL for web previews; untitled
29
+ // drafts have only an internal pseudo-path, so the row is omitted for them.
30
+ const location = doc.format === "web" ? doc.web?.url : isLocalFileDoc(doc) ? doc.path : null;
31
+ const fingerprint = doc.fingerprint;
32
+ const size = fingerprint?.size ? formatBytes(fingerprint.size) : null;
33
+ const modified = fingerprint?.mtimeMs && fingerprint.mtimeMs > 0
34
+ ? new Date(fingerprint.mtimeMs).toLocaleString(undefined, {
35
+ dateStyle: "medium",
36
+ timeStyle: "short",
37
+ })
38
+ : null;
39
+ return (_jsxs("aside", { ref: panelRef, id: "vr-info-inspector", "aria-label": "Document info", className: "no-print vr-glass-panel flex w-[280px] shrink-0 flex-col", children: [_jsxs("div", { className: "flex h-11 shrink-0 items-center justify-between pl-4 pr-2", children: [_jsx("h2", { className: "font-sans text-chrome-xs font-semibold uppercase tracking-[0.14em] text-muted-2", children: "Info" }), _jsx("button", { type: "button", "aria-label": "Close document info", title: "Close", onClick: onClose, className: "vr-press vr-focus flex h-7 w-7 items-center justify-center rounded-full text-muted hover:bg-paper/10 hover:text-paper", children: _jsx(X, { size: 14, weight: "regular", "aria-hidden": true }) })] }), _jsxs("div", { className: "min-h-0 flex-1 overflow-y-auto px-4 pb-4", children: [_jsx("h3", { className: "mb-2 font-sans text-chrome-sm font-semibold text-paper", children: "Document" }), _jsxs("dl", { className: "grid grid-cols-[auto_minmax(0,1fr)] gap-x-3 gap-y-2", children: [_jsx(InfoRow, { label: "Kind", children: KIND_LABEL[doc.format] ?? "Document" }), _jsx(InfoRow, { label: "Name", children: doc.displayName }), location ? _jsx(LocationRow, { location: location, web: doc.format === "web" }) : null, COUNT_FORMATS.has(doc.format) ? (_jsx(LiveCounts, { doc: doc, subscribeEdits: subscribeEdits, getEditSnapshot: getEditSnapshot })) : null, doc.encoding ? (_jsx(InfoRow, { label: "Encoding", mono: true, children: doc.encoding })) : null, size ? _jsx(InfoRow, { label: "Size", children: size }) : null, modified ? _jsx(InfoRow, { label: "Modified", children: modified }) : null] }), doc.format !== "web" && (_jsxs("div", { className: "mt-4 flex gap-2.5 rounded-xl bg-paper/5 px-3 py-2.5 font-sans text-chrome-sm leading-relaxed text-muted", children: [_jsx(LockSimple, { size: 15, weight: "regular", "aria-hidden": true, className: "mt-0.5 shrink-0 text-copper-hi" }), _jsx("p", { className: "m-0", children: "Opened from this computer. Nothing is uploaded." })] }))] })] }));
40
+ }
41
+ function InfoRow({ label, mono = false, children, }) {
42
+ return (_jsxs(_Fragment, { children: [_jsx("dt", { className: "whitespace-nowrap pt-px text-right font-sans text-chrome-sm leading-5 text-muted-2", children: label }), _jsx("dd", { title: typeof children === "string" ? children : undefined, className: `min-w-0 truncate leading-5 text-paper ${mono ? "font-mono text-chrome-sm" : "font-sans text-chrome-sm"}`, children: children })] }));
43
+ }
44
+ // Full path/URL in the mono accent face, truncated with a title tooltip, plus the existing
45
+ // sidebar clipboard helper for the copy affordance.
46
+ function LocationRow({ location, web }) {
47
+ const copyLabel = web ? "Copy URL" : "Copy path";
48
+ return (_jsxs(_Fragment, { children: [_jsx("dt", { className: "whitespace-nowrap pt-px text-right font-sans text-chrome-sm leading-5 text-muted-2", children: "Location" }), _jsxs("dd", { className: "flex min-w-0 items-center gap-1", children: [_jsx("span", { title: location, className: "min-w-0 flex-1 truncate font-mono text-chrome-xs leading-5 text-paper", children: location }), _jsx("button", { type: "button", "aria-label": copyLabel, title: copyLabel, onClick: () => void copyText(location), className: "vr-press vr-focus flex h-6 w-6 shrink-0 items-center justify-center rounded text-muted-2 hover:bg-paper/10 hover:text-paper", children: _jsx(Copy, { size: 12, weight: "regular", "aria-hidden": true }) })] })] }));
49
+ }
50
+ // Live word/character counts — the same subscription DocStatsPill uses, so the inspector updates
51
+ // per keystroke without re-rendering the App shell.
52
+ function LiveCounts({ doc, subscribeEdits, getEditSnapshot, }) {
53
+ const liveText = useEditText(subscribeEdits, getEditSnapshot, doc.path) ?? doc.content;
54
+ const trimmed = liveText.trim();
55
+ const words = trimmed ? trimmed.split(/\s+/).length : 0;
56
+ const mins = words ? Math.max(1, Math.round(words / 230)) : 0;
57
+ return (_jsxs(_Fragment, { children: [_jsx(InfoRow, { label: "Words", children: words.toLocaleString() }), _jsx(InfoRow, { label: "Characters", children: liveText.length.toLocaleString() }), _jsx(InfoRow, { label: "Reading", children: words ? `~${mins} min` : "—" })] }));
58
+ }
59
+ function formatBytes(size) {
60
+ if (size < 1024)
61
+ return `${size} B`;
62
+ const kb = size / 1024;
63
+ if (kb < 1024)
64
+ return `${kb >= 100 ? Math.round(kb) : kb.toFixed(1)} KB`;
65
+ const mb = kb / 1024;
66
+ return `${mb >= 100 ? Math.round(mb) : mb.toFixed(1)} MB`;
67
+ }
@@ -0,0 +1,9 @@
1
+ import type { FileEntry } from "../../state/library.js";
2
+ export declare function StartPage({ onNew, onOpenDialog, onOpenUrl, onOpenClipboard, recents, onOpenRecent, }: {
3
+ onNew: () => void;
4
+ onOpenDialog: () => void;
5
+ onOpenUrl: () => void;
6
+ onOpenClipboard: () => void;
7
+ recents?: FileEntry[];
8
+ onOpenRecent?: (entry: FileEntry) => void;
9
+ }): import("react").JSX.Element;
@@ -0,0 +1,49 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { relativeTimeFrom } from "@rightkit/platform-ui";
3
+ import { ClipboardText } from "@phosphor-icons/react/dist/icons/ClipboardText";
4
+ import { FolderOpen } from "@phosphor-icons/react/dist/icons/FolderOpen";
5
+ import { GlobeSimple } from "@phosphor-icons/react/dist/icons/GlobeSimple";
6
+ import { NotePencil } from "@phosphor-icons/react/dist/icons/NotePencil";
7
+ import { FileTypeIcon } from "../../documents/FileTypeIcon.js";
8
+ import { isMac, shortcutLabel } from "@rightkit/document-core/platform";
9
+ export function StartPage({ onNew, onOpenDialog, onOpenUrl, onOpenClipboard, recents, onOpenRecent, }) {
10
+ return (_jsxs("div", { className: "flex min-h-full w-full min-w-0 max-w-full flex-col items-center justify-center gap-9 px-4 py-10 text-center sm:px-6", children: [_jsxs("section", { className: "vr-rise flex w-full min-w-0 max-w-full flex-col items-center", children: [_jsxs("h1", { className: "[font-family:var(--font-wordmark)] text-[42px] leading-none tracking-[0.01em] text-paper", children: ["View", _jsx("span", { className: "text-copper", children: "Right" })] }), _jsx("p", { className: "mt-3 max-w-[440px] text-balance font-sans text-sm leading-relaxed text-muted", children: "Open Markdown, text, HTML, Word documents, images and PDFs. Files stay on this computer." }), _jsxs("div", { className: "mt-6 flex w-full min-w-0 max-w-full flex-wrap items-center justify-center gap-2.5", children: [_jsx(StartAction, { primary: true, label: "Open\u2026", hint: "Ctrl+O", onClick: onOpenDialog, icon: _jsx(FolderOpen, { size: 15, weight: "regular", "aria-hidden": true }) }), _jsx(StartAction, { label: "New document", hint: "Ctrl+N",
11
+ // Bare call — passing the click event through would land it in the format arg.
12
+ onClick: () => onNew(), icon: _jsx(NotePencil, { size: 15, weight: "regular", "aria-hidden": true }) }), _jsx(StartAction, { label: "Paste", hint: "Ctrl+Shift+V", onClick: onOpenClipboard, icon: _jsx(ClipboardText, { size: 15, weight: "regular", "aria-hidden": true }) }), _jsx(StartAction, { label: "Open URL\u2026", onClick: onOpenUrl, icon: _jsx(GlobeSimple, { size: 15, weight: "regular", "aria-hidden": true }) })] })] }), _jsxs("section", { className: "vr-rise w-full min-w-0 max-w-[640px] rounded-2xl border border-dashed border-line p-4 sm:p-5", style: { "--rise-delay": "90ms" }, children: [_jsxs("div", { className: "mb-3 flex min-w-0 flex-wrap items-baseline justify-between gap-x-3 gap-y-1 px-1 text-left", children: [_jsx("h2", { className: "font-sans text-chrome-md font-semibold text-paper", children: "Recent" }), _jsx("span", { className: "font-sans text-xs text-muted-2", children: "Drop files anywhere to open them" })] }), recents && recents.length > 0 && onOpenRecent ? (_jsx("ul", { className: "flex flex-col gap-0.5", children: recents.slice(0, 8).map((entry) => (_jsx("li", { children: _jsx(RecentRow, { entry: entry, onOpen: () => onOpenRecent(entry) }) }, entry.token))) })) : (_jsx("p", { className: "py-4 text-center font-sans text-xs text-muted-2", children: "Recently opened files appear here." }))] })] }));
13
+ }
14
+ // aria-keyshortcuts wants UI Events key names — "Ctrl" isn't one, and the actual modifier on
15
+ // macOS is Meta (the same mapping shortcutLabel renders as ⌘).
16
+ // Hints are authored in Windows vocabulary ("Ctrl+Shift+V"); convert to a canonical chord.
17
+ function hintChord(hint) {
18
+ return hint.toLowerCase().replace(/\bctrl\b/g, "mod");
19
+ }
20
+ function keyShortcutsAttr(hint) {
21
+ return hint
22
+ .split("+")
23
+ .map((part) => {
24
+ const key = part.trim();
25
+ if (key === "Ctrl" || key === "Control")
26
+ return isMac() ? "Meta" : "Control";
27
+ return key;
28
+ })
29
+ .join("+");
30
+ }
31
+ function StartAction({ icon, label, hint, onClick, primary = false, }) {
32
+ return (_jsxs("button", { type: "button", onClick: onClick, title: hint ? `${label} (${shortcutLabel(hintChord(hint))})` : label, "aria-keyshortcuts": hint ? keyShortcutsAttr(hint) : undefined, className: `vr-press vr-focus flex h-[34px] items-center gap-1.5 rounded-full border px-4 font-sans text-chrome-md font-medium ${primary
33
+ ? "border-copper/40 bg-copper/15 text-copper-hi hover:bg-copper/25"
34
+ : "border-line bg-panel text-paper hover:border-muted-2 hover:bg-panel-2"}`, children: [icon, label, hint ? (_jsx("kbd", { "aria-hidden": true, className: "ml-1 font-mono text-chrome-xs opacity-60", children: shortcutLabel(hintChord(hint)) })) : null] }));
35
+ }
36
+ /** Recents row time: the owner's relative text for the first week, then a short date. */
37
+ function recentTimeLabel(openedAt) {
38
+ if (!openedAt)
39
+ return null;
40
+ if (Date.now() - openedAt >= 7 * 86_400_000) {
41
+ return new Date(openedAt).toLocaleDateString(undefined, { month: "short", day: "numeric" });
42
+ }
43
+ return relativeTimeFrom(openedAt);
44
+ }
45
+ // Recent row: format icon, file name, the full path (mono, truncated), last-opened time.
46
+ function RecentRow({ entry, onOpen }) {
47
+ const time = recentTimeLabel(entry.openedAt);
48
+ return (_jsxs("button", { type: "button", onClick: onOpen, title: entry.token, "aria-label": `Open ${entry.displayName}`, className: "vr-press vr-focus flex h-9 w-full items-center gap-2.5 rounded-lg px-2 text-left hover:bg-panel-2", children: [_jsx("span", { "aria-hidden": true, className: "flex w-5 shrink-0 justify-center text-muted-2", children: _jsx(FileTypeIcon, { format: entry.format, path: entry.token, size: 16 }) }), _jsx("span", { className: "max-w-[38%] shrink-0 truncate font-sans text-chrome-sm font-medium text-paper", children: entry.displayName }), _jsx("span", { className: "min-w-0 flex-1 truncate text-left font-mono text-chrome-xs text-muted-2", children: entry.token }), time && (_jsx("span", { className: "shrink-0 font-mono text-chrome-xs tabular-nums text-muted-2", children: time }))] }));
49
+ }
@@ -0,0 +1,6 @@
1
+ import type { DocFormat, ReadEditMode } from "@rightkit/document-core/documents/types";
2
+ /** Every editable format opens editable with its tools showing — the host app edits inline with no
3
+ * extra step. Formats that cannot edit (web previews) stay read-only. */
4
+ export declare function effectiveReadEdit(doc: {
5
+ format: DocFormat;
6
+ }): ReadEditMode;
@@ -0,0 +1,6 @@
1
+ import { capabilitiesFor } from "../../documents/capabilities.js";
2
+ /** Every editable format opens editable with its tools showing — the host app edits inline with no
3
+ * extra step. Formats that cannot edit (web previews) stay read-only. */
4
+ export function effectiveReadEdit(doc) {
5
+ return capabilitiesFor(doc.format).canEdit ? "edit" : "read";
6
+ }
@@ -0,0 +1,20 @@
1
+ import type { LoadedDocument } from "@rightkit/document-core/documents/types";
2
+ export declare function DocumentMenu({ menu, openLocalPaths, pinned, onClose, onActivate, onCloseDoc, onCloseOthers, onCloseRight, onCloseAll, onTogglePin, titleFor, }: {
3
+ menu: {
4
+ doc: LoadedDocument;
5
+ x: number;
6
+ y: number;
7
+ };
8
+ openLocalPaths: string[];
9
+ pinned: boolean;
10
+ onClose: () => void;
11
+ onActivate: (path: string) => void;
12
+ onCloseDoc: (path: string) => void;
13
+ /** Rendered only when provided (tab-strip lane): close every other tab. */
14
+ onCloseOthers?: () => void;
15
+ /** Rendered only when provided (tab-strip lane): close tabs to the right of this one. */
16
+ onCloseRight?: () => void;
17
+ onCloseAll: () => void;
18
+ onTogglePin: (path: string) => void;
19
+ titleFor: (doc: LoadedDocument) => string;
20
+ }): import("react").JSX.Element;
@@ -0,0 +1,56 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ // Right-click / "..." context menu for a single document tab (Open, Close, Close all, Pin, Copy
3
+ // Path variants, Reveal in Finder/File Explorer). Rendered from both the collapsed rail and the
4
+ // expanded tab list in Sidebar.tsx. The shell's ContextMenu owns keyboard, dismissal (scrim press,
5
+ // context-menu gesture), focus return and viewport fit; the host app supplies the rows and the look.
6
+ import { useMemo, useRef } from "react";
7
+ import { ContextMenu } from "@rightkit/react-shell/react";
8
+ import { revealInFileExplorer } from "@rightkit/document-core/state/tauriBridge";
9
+ import { isMac } from "@rightkit/document-core/platform";
10
+ import { compactMenuItemClass, menuPanelClass } from "./SidebarPrimitives.js";
11
+ import { breadcrumbPath, copyText, isLocalFileDoc, pathBase, pathFolder, relativePath, } from "./SidebarPathUtils.js";
12
+ export function DocumentMenu({ menu, openLocalPaths, pinned, onClose, onActivate, onCloseDoc, onCloseOthers, onCloseRight, onCloseAll, onTogglePin, titleFor, }) {
13
+ const { doc, x, y } = menu;
14
+ const local = isLocalFileDoc(doc);
15
+ const pathText = doc.web?.url ?? doc.path;
16
+ // Callbacks change identity every render; items must not (exitMs treats a new items identity as
17
+ // a re-open), so run() reads the latest props through a ref.
18
+ const cb = useRef({ onActivate, onCloseDoc, onCloseOthers, onCloseRight, onCloseAll, onTogglePin });
19
+ cb.current = { onActivate, onCloseDoc, onCloseOthers, onCloseRight, onCloseAll, onTogglePin };
20
+ const hasOthers = !!onCloseOthers;
21
+ const hasRight = !!onCloseRight;
22
+ const docPath = doc.path;
23
+ const isWeb = doc.format === "web";
24
+ const items = useMemo(() => [
25
+ { id: "open", label: "Open", run: () => cb.current.onActivate(docPath) },
26
+ { id: "close", label: "Close", run: () => cb.current.onCloseDoc(docPath) },
27
+ ...(hasOthers ? [{ id: "close-others", label: "Close other tabs", run: () => cb.current.onCloseOthers?.() }] : []),
28
+ ...(hasRight ? [{ id: "close-right", label: "Close tabs to the right", run: () => cb.current.onCloseRight?.() }] : []),
29
+ { id: "close-all", label: "Close all", run: () => cb.current.onCloseAll() },
30
+ ...(local ? [{ id: "pin", label: pinned ? "Unpin" : "Pin", run: () => cb.current.onTogglePin(docPath) }] : []),
31
+ {
32
+ id: "copy-path",
33
+ label: isWeb ? "Copy URL" : "Copy Path",
34
+ separatorBefore: true,
35
+ run: () => copyText(pathText),
36
+ },
37
+ ...(local
38
+ ? [
39
+ { id: "copy-relative", label: "Copy Relative Path", run: () => copyText(relativePath(docPath, openLocalPaths)) },
40
+ { id: "copy-name", label: "Copy File Name", run: () => copyText(pathBase(docPath)) },
41
+ { id: "copy-folder", label: "Copy Folder Path", run: () => copyText(pathFolder(docPath)) },
42
+ { id: "copy-breadcrumbs", label: "Copy Breadcrumbs Path", run: () => copyText(breadcrumbPath(docPath)) },
43
+ {
44
+ id: "reveal",
45
+ label: isMac() ? "Reveal in Finder" : "Reveal in File Explorer",
46
+ separatorBefore: true,
47
+ run: () => revealInFileExplorer(docPath),
48
+ },
49
+ ]
50
+ : []),
51
+ ], [hasOthers, hasRight, local, pinned, isWeb, pathText, docPath, openLocalPaths]);
52
+ return (_jsx(ContextMenu, { x: x, y: y, items: items, onClose: onClose, label: `${titleFor(doc)} tab options`, width: 224, itemAs: "button", showLead: false, renderItem: (item) => item.label, className: `${menuPanelClass} overflow-y-auto py-0.5`, scrimClassName: "no-print", exitMs: 80, exitClassName: "vr-pop-exit", slots: {
53
+ item: `${compactMenuItemClass} [&.is-active]:bg-ink/50 [&.is-active]:text-paper`,
54
+ separator: "my-1 h-px bg-line",
55
+ } }));
56
+ }
@@ -0,0 +1,9 @@
1
+ import { type PointerEvent as ReactPointerEvent, type ReactNode } from "react";
2
+ import type { SidebarProps } from "../Sidebar.js";
3
+ export declare function SidebarExpandedPanel({ activePath, openPaths, recents, pinnedPaths, pinnedEntries, onActivate, onOpenRecent, onOpenPath, onTogglePin, onTogglePinDir, onClearRecents, onOpenDialog, onOpenUrl, onOpenClipboard, onNew, onOpenSettings, titlebarSlot, sidebarWidth, startWidthResize, setOpen, SIDEBAR_MIN_WIDTH, SIDEBAR_MAX_WIDTH, }: SidebarProps & {
4
+ sidebarWidth: number;
5
+ startWidthResize: (event: ReactPointerEvent<HTMLDivElement>) => void;
6
+ setOpen: (open: boolean) => void;
7
+ SIDEBAR_MIN_WIDTH: number;
8
+ SIDEBAR_MAX_WIDTH: number;
9
+ }): ReactNode;
@@ -0,0 +1,78 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // The expanded sidebar shell: titlebar row (drag region + collapse button), the slim New/Open/
3
+ // Search action row, the explorer sections (Pinned / Recent / Locations lazy tree), the footer
4
+ // (wordmark + settings + theme), and the width drag handle. Open documents live in the titlebar
5
+ // tab strip now — this panel only shows files on disk.
6
+ import { useEffect, useRef, useState, } from "react";
7
+ import { X } from "@phosphor-icons/react/dist/icons/X";
8
+ import { Gear } from "@phosphor-icons/react/dist/icons/Gear";
9
+ import { MagnifyingGlass } from "@phosphor-icons/react/dist/icons/MagnifyingGlass";
10
+ import { Palette } from "@phosphor-icons/react/dist/icons/Palette";
11
+ import { FolderOpen } from "@phosphor-icons/react/dist/icons/FolderOpen";
12
+ import { IconButton } from "@rightkit/react-shell/react";
13
+ import { useDismiss } from "@rightkit/platform-ui/headless";
14
+ import { AppearanceMenu } from "../../settings/AppearanceMenu.js";
15
+ import { PanelIcon, PlusIcon } from "./SidebarIcons.js";
16
+ import { NewOpenMenu } from "./SidebarMenus.js";
17
+ import { ExplorerSections } from "./explorer/ExplorerSections.js";
18
+ const HEADER_BTN = "vr-press vr-focus flex h-7 min-w-7 items-center justify-center rounded px-1 font-sans text-xs text-muted hover:bg-ink/40 hover:text-paper";
19
+ export function SidebarExpandedPanel({ activePath, openPaths, recents, pinnedPaths, pinnedEntries, onActivate, onOpenRecent, onOpenPath, onTogglePin, onTogglePinDir, onClearRecents, onOpenDialog, onOpenUrl, onOpenClipboard, onNew, onOpenSettings, titlebarSlot, sidebarWidth, startWidthResize, setOpen, SIDEBAR_MIN_WIDTH, SIDEBAR_MAX_WIDTH, }) {
20
+ const [searchOpen, setSearchOpen] = useState(false);
21
+ const [searchQuery, setSearchQuery] = useState("");
22
+ const searchInputRef = useRef(null);
23
+ const searchButtonRef = useRef(null);
24
+ useEffect(() => {
25
+ if (searchOpen)
26
+ searchInputRef.current?.focus();
27
+ }, [searchOpen]);
28
+ const closeSearch = () => {
29
+ setSearchOpen(false);
30
+ setSearchQuery("");
31
+ searchButtonRef.current?.focus();
32
+ };
33
+ // Escape in the search field closes it (bubble phase on the input; stops propagation as before).
34
+ useDismiss(closeSearch, {
35
+ enabled: searchOpen,
36
+ escapeTarget: searchInputRef,
37
+ outside: false,
38
+ imeGuard: false,
39
+ });
40
+ const plusButtonRef = useRef(null);
41
+ const themeButtonRef = useRef(null);
42
+ const [plusOpen, setPlusOpen] = useState(false);
43
+ const [appearanceOpen, setAppearanceOpen] = useState(false);
44
+ return (_jsxs("aside", { "data-vr-sidebar": "expanded", className: "no-print vr-chrome vr-sidebar-shell relative flex shrink-0 flex-col bg-panel", style: { width: sidebarWidth }, children: [_jsxs("div", { "data-vr-sidebar-titlebar": true, "data-tauri-drag-region": true, className: "relative flex h-[52px] items-center justify-end gap-1 px-3", children: [titlebarSlot && _jsx("div", { className: "mr-auto flex min-w-0 items-center", children: titlebarSlot }), _jsx(IconButton, { label: "Collapse sidebar", onClick: () => setOpen(false), className: HEADER_BTN, children: _jsx(PanelIcon, {}) })] }), _jsxs("div", { className: "vr-x-actions", children: [_jsxs("button", { type: "button", ref: plusButtonRef, className: "vr-x-new vr-press vr-focus", "aria-haspopup": "menu", "aria-expanded": plusOpen, title: "New document", onClick: () => setPlusOpen((o) => !o), children: [_jsx(PlusIcon, {}), " New"] }), _jsx("span", { className: "flex-1" }), _jsx(IconButton, { ref: searchButtonRef, label: "Search documents", "aria-expanded": searchOpen, "aria-controls": "vr-sidebar-document-search", className: "vr-press vr-focus flex h-7 min-w-7 items-center justify-center rounded px-1 text-muted hover:bg-ink/40 hover:text-paper", onClick: () => (searchOpen ? closeSearch() : setSearchOpen(true)), children: _jsx(MagnifyingGlass, { size: 14, weight: "regular" }) }), _jsx(IconButton, { label: "Open file\u2026", onClick: onOpenDialog, className: HEADER_BTN, children: _jsx(FolderOpen, { size: 14, weight: "regular" }) })] }), plusOpen && (_jsx(NewOpenMenu, { anchor: plusButtonRef.current ?? undefined, onClose: () => setPlusOpen(() => false), onNew: onNew, onOpenDialog: onOpenDialog, onOpenUrl: onOpenUrl, onOpenClipboard: onOpenClipboard })), searchOpen && (_jsxs("div", { id: "vr-sidebar-document-search", role: "search", "aria-label": "Search sidebar documents", className: "mx-3 mb-2 flex shrink-0 items-center gap-1 rounded border border-line bg-panel-2 px-2 focus-within:border-copper", children: [_jsx("input", { ref: searchInputRef, "aria-label": "Document name", placeholder: "Search documents\u2026", value: searchQuery, onChange: (event) => setSearchQuery(event.target.value), onKeyDown: (event) => {
45
+ if (event.key === "ArrowDown" || event.key === "Enter") {
46
+ const first = event.currentTarget
47
+ .closest("aside")
48
+ ?.querySelector("[data-vr-document-search-results] button");
49
+ if (first) {
50
+ event.preventDefault();
51
+ if (event.key === "Enter")
52
+ first.click();
53
+ else
54
+ first.focus();
55
+ }
56
+ }
57
+ }, className: "min-w-0 flex-1 bg-transparent py-1.5 font-sans text-xs text-paper outline-none placeholder:text-muted" }), _jsx(IconButton, { label: "Close document search", onClick: closeSearch, className: "vr-focus flex h-6 w-6 shrink-0 items-center justify-center rounded text-muted hover:text-paper", children: _jsx(X, { size: 12 }) })] })), _jsx(ExplorerSections, { searchQuery: searchQuery, activePath: activePath, openPaths: openPaths, recents: recents, pinnedPaths: pinnedPaths, pinnedEntries: pinnedEntries, onActivate: onActivate, onOpenRecent: onOpenRecent, onOpenPath: onOpenPath, onTogglePin: onTogglePin, onTogglePinDir: onTogglePinDir, onClearRecents: onClearRecents }), _jsxs("div", { className: "relative flex items-center justify-between border-t border-line px-3 py-2", children: [_jsxs("span", { "data-vr-sidebar-footer-brand": true, className: "min-w-0 truncate px-1 [font-family:var(--font-wordmark)] text-xl leading-none tracking-[0.01em] text-paper", children: ["View", _jsx("span", { className: "text-copper", children: "Right" })] }), _jsxs("div", { className: "flex items-center gap-1", children: [_jsx(IconButton, { label: "Settings", onClick: onOpenSettings, "data-vr-tooltip": "Settings", "data-vr-tooltip-side": "top", className: "vr-icon-tooltip vr-press vr-focus flex h-8 w-8 items-center justify-center rounded text-muted hover:bg-ink/40 hover:text-paper", children: _jsx(Gear, { size: 14, weight: "regular" }) }), _jsx(IconButton, { ref: themeButtonRef, label: "Theme", onClick: () => setAppearanceOpen((o) => !o), "aria-haspopup": "dialog", "aria-expanded": appearanceOpen, "data-vr-tooltip": "Theme", "data-vr-tooltip-side": "top", className: "vr-icon-tooltip vr-press vr-focus flex h-8 w-8 items-center justify-center rounded text-muted hover:bg-ink/40 hover:text-paper", children: _jsx(Palette, { size: 14, weight: "regular" }) })] }), appearanceOpen && (_jsx(AppearanceMenu, { anchor: themeButtonRef.current ?? undefined, onClose: () => setAppearanceOpen(() => false), onMore: onOpenSettings }))] }), _jsx("div", { role: "separator", tabIndex: 0, "aria-label": "Resize sidebar", "aria-orientation": "vertical", "aria-valuemin": SIDEBAR_MIN_WIDTH, "aria-valuemax": SIDEBAR_MAX_WIDTH, "aria-valuenow": Math.round(sidebarWidth), "aria-valuetext": `${Math.round(sidebarWidth)} pixels wide`, onPointerDown: startWidthResize, onKeyDown: (event) => {
58
+ // Arrow keys resize through the SAME drag state machine the pointer path uses:
59
+ // a plain-object pointerdown (its currentTarget lacks setPointerCapture, so the
60
+ // capture call is skipped), then real pointermove/pointerup events so the window
61
+ // listeners apply, clamp, and persist the delta exactly as a mouse drag would.
62
+ if (event.key !== "ArrowLeft" && event.key !== "ArrowRight")
63
+ return;
64
+ if (typeof PointerEvent === "undefined")
65
+ return;
66
+ event.preventDefault();
67
+ const step = event.key === "ArrowRight" ? 16 : -16;
68
+ const x = event.currentTarget.getBoundingClientRect().left;
69
+ startWidthResize({
70
+ preventDefault() { },
71
+ currentTarget: {},
72
+ pointerId: -1,
73
+ clientX: x,
74
+ });
75
+ window.dispatchEvent(new PointerEvent("pointermove", { pointerId: -1, clientX: x + step }));
76
+ window.dispatchEvent(new PointerEvent("pointerup", { pointerId: -1 }));
77
+ }, className: "absolute bottom-0 right-0 top-0 z-30 w-2 translate-x-1 cursor-col-resize", title: "Resize sidebar" })] }));
78
+ }
@@ -0,0 +1,3 @@
1
+ export declare function ClockIcon(): import("react").JSX.Element;
2
+ export declare function PlusIcon(): import("react").JSX.Element;
3
+ export declare function PanelIcon(): import("react").JSX.Element;
@@ -0,0 +1,18 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ // Small icons used across the sidebar shell (header/footer + tab strip).
3
+ // Now backed by Phosphor React icons (see docs/agent-rules.md chrome modernization) instead of
4
+ // hand-rolled inline SVG — kept as named wrapper exports so call sites (SidebarExpandedPanel,
5
+ // SidebarRail) don't need to change.
6
+ import { Clock } from "@phosphor-icons/react/dist/icons/Clock";
7
+ import { Plus } from "@phosphor-icons/react/dist/icons/Plus";
8
+ import { SidebarSimple } from "@phosphor-icons/react/dist/icons/SidebarSimple";
9
+ export function ClockIcon() {
10
+ return _jsx(Clock, { size: 14, weight: "regular" });
11
+ }
12
+ export function PlusIcon() {
13
+ return _jsx(Plus, { size: 14, weight: "regular" });
14
+ }
15
+ // Standard "toggle sidebar" glyph — used for both open and collapse.
16
+ export function PanelIcon() {
17
+ return _jsx(SidebarSimple, { size: 14, weight: "regular" });
18
+ }
@@ -0,0 +1,19 @@
1
+ import { type AnchorRect } from "@rightkit/react-shell/react";
2
+ import type { FileEntry } from "../../state/library.js";
3
+ type Anchor = Element | AnchorRect;
4
+ export declare function NewOpenMenu({ anchor, onClose, onNew, onOpenDialog, onOpenUrl, onOpenClipboard, }: {
5
+ anchor?: Anchor;
6
+ onClose: () => void;
7
+ onNew: (format?: "txt" | "md" | "docx") => void;
8
+ onOpenDialog: () => void;
9
+ onOpenUrl: () => void;
10
+ onOpenClipboard: () => void;
11
+ }): import("react").JSX.Element;
12
+ export declare function RecentsMenu({ anchor, onClose, recents, onOpenRecent, onClearRecents, }: {
13
+ anchor?: Anchor;
14
+ onClose: () => void;
15
+ recents: FileEntry[];
16
+ onOpenRecent: (entry: FileEntry) => void;
17
+ onClearRecents: () => void;
18
+ }): import("react").JSX.Element;
19
+ export {};
@@ -0,0 +1,64 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // New/Open and Recents popovers. Render identically in the collapsed rail and the expanded header —
3
+ // only their anchor button differs. Shared here so the two render sites stay in sync. Both are the
4
+ // shell's ContextMenu opened from an anchor (keyboard, dismissal, focus return, viewport fit); the host app supplies the
5
+ // glass panel classes and the row look.
6
+ import { useMemo, useRef } from "react";
7
+ import { ContextMenu } from "@rightkit/react-shell/react";
8
+ import { FileTypeIcon } from "../../documents/FileTypeIcon.js";
9
+ import { menuItemClass, menuPanelClass } from "./SidebarPrimitives.js";
10
+ const MENU_SLOTS = {
11
+ item: `${menuItemClass} [&.is-active]:bg-ink/50 [&.is-active]:text-paper [&.is-disabled]:cursor-default [&.is-disabled]:hover:bg-transparent`,
12
+ separator: "my-1 h-px bg-line",
13
+ };
14
+ /**
15
+ * The button a menu hangs off. Call sites pass `anchor` (the clicked button); without it the menu
16
+ * finds its trigger by the label the rail / expanded header give it, which is already in the DOM
17
+ * when the menu mounts.
18
+ */
19
+ const TRIGGERS = {
20
+ new: '[data-vr-sidebar] button[aria-label="New document"], [data-vr-sidebar="expanded"] .vr-x-new',
21
+ recents: '[data-vr-sidebar] button[aria-label="Recent files"]',
22
+ };
23
+ function triggerAnchor(kind) {
24
+ return (document.querySelector(TRIGGERS[kind]) ?? { left: 0, top: 52, right: 0, bottom: 52 });
25
+ }
26
+ export function NewOpenMenu({ anchor, onClose, onNew, onOpenDialog, onOpenUrl, onOpenClipboard, }) {
27
+ // Props change identity per render; items must not (exitMs treats a new items identity as a
28
+ // re-open), so run() reads the latest through a ref.
29
+ const cb = useRef({ onNew, onOpenDialog, onOpenUrl, onOpenClipboard });
30
+ cb.current = { onNew, onOpenDialog, onOpenUrl, onOpenClipboard };
31
+ const items = useMemo(() => [
32
+ { id: "new-txt", label: "New text", run: () => cb.current.onNew("txt") },
33
+ { id: "new-md", label: "New Markdown", run: () => cb.current.onNew("md") },
34
+ { id: "new-docx", label: "New DOCX", run: () => cb.current.onNew("docx") },
35
+ { id: "open", label: "Open file…", run: () => cb.current.onOpenDialog() },
36
+ { id: "open-url", label: "Open URL…", run: () => cb.current.onOpenUrl() },
37
+ { id: "clipboard", label: "From clipboard", run: () => cb.current.onOpenClipboard() },
38
+ ], []);
39
+ return (_jsx(ContextMenu, { anchor: anchor ?? triggerAnchor("new"), placement: "bottom-start", label: "New document", width: 176, items: items, onClose: onClose, itemAs: "button", showLead: false, renderItem: (item) => item.label, className: `${menuPanelClass} py-1`, scrimClassName: "no-print", exitMs: 80, exitClassName: "vr-pop-exit", slots: MENU_SLOTS }));
40
+ }
41
+ export function RecentsMenu({ anchor, onClose, recents, onOpenRecent, onClearRecents, }) {
42
+ const cb = useRef({ onOpenRecent, onClearRecents });
43
+ cb.current = { onOpenRecent, onClearRecents };
44
+ const shown = useMemo(() => recents.slice(0, 12), [recents]);
45
+ const items = useMemo(() => [
46
+ { id: "head", label: "Recent", disabled: true },
47
+ ...(shown.length === 0 ? [{ id: "empty", label: "Nothing yet.", disabled: true }] : []),
48
+ ...shown.map((r) => ({ id: `recent:${r.token}`, label: r.displayName, run: () => cb.current.onOpenRecent(r) })),
49
+ ...(shown.length > 0
50
+ ? [{ id: "clear", label: "Clear recent files", separatorBefore: true, run: () => cb.current.onClearRecents() }]
51
+ : []),
52
+ ], [shown]);
53
+ const byId = useMemo(() => new Map(shown.map((r) => [`recent:${r.token}`, r])), [shown]);
54
+ return (_jsx(ContextMenu, { anchor: anchor ?? triggerAnchor("recents"), placement: "bottom-start", label: "Recent files", width: 256, items: items, onClose: onClose, itemAs: "button", showLead: false, renderItem: (item) => {
55
+ const entry = byId.get(item.id);
56
+ if (item.id === "head")
57
+ return _jsx("span", { className: "font-sans text-chrome-xs font-semibold text-muted-2", children: item.label });
58
+ if (item.id === "empty")
59
+ return _jsx("span", { className: "font-sans text-xs text-muted-2", children: item.label });
60
+ if (!entry)
61
+ return _jsx("span", { className: "text-muted", children: item.label });
62
+ return (_jsxs(_Fragment, { children: [_jsx("span", { className: "mr-2 shrink-0", children: _jsx(FileTypeIcon, { format: entry.format, path: entry.token, size: 21 }) }), _jsx("span", { className: "truncate", title: entry.displayName, children: entry.displayName })] }));
63
+ }, className: `${menuPanelClass} py-1`, scrimClassName: "no-print", exitMs: 80, exitClassName: "vr-pop-exit", slots: MENU_SLOTS }));
64
+ }
@@ -0,0 +1,10 @@
1
+ import type { LoadedDocument } from "@rightkit/document-core/documents/types";
2
+ export declare function isLocalFileDoc(doc: LoadedDocument): boolean;
3
+ export declare function pathFolder(path: string): string;
4
+ export declare function pathBase(path: string): string;
5
+ export declare function pathSeparator(path: string): "\\" | "/";
6
+ export declare function splitPath(path: string): string[];
7
+ export declare function commonFolder(paths: string[]): string;
8
+ export declare function relativePath(path: string, openLocalPaths: string[]): string;
9
+ export declare function breadcrumbPath(path: string): string;
10
+ export declare function copyText(text: string): Promise<void>;