@rightkit/document-workspace 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/LICENSE-APACHE +201 -0
- package/LICENSE-MIT +21 -0
- package/README.md +74 -2
- package/dist/app/App.d.ts +1 -0
- package/dist/app/App.js +6 -0
- package/dist/app/AppDialogs.d.ts +52 -0
- package/dist/app/AppDialogs.js +23 -0
- package/dist/app/AppDialogsHost.d.ts +31 -0
- package/dist/app/AppDialogsHost.js +30 -0
- package/dist/app/AppLifecycle.d.ts +19 -0
- package/dist/app/AppLifecycle.js +87 -0
- package/dist/app/AppProvider.d.ts +317 -0
- package/dist/app/AppProvider.js +217 -0
- package/dist/app/AppRoot.d.ts +8 -0
- package/dist/app/AppRoot.js +159 -0
- package/dist/app/AppShell.d.ts +2 -0
- package/dist/app/AppShell.js +81 -0
- package/dist/app/CommandPalette.d.ts +13 -0
- package/dist/app/CommandPalette.js +47 -0
- package/dist/app/DocActionBar.d.ts +36 -0
- package/dist/app/DocActionBar.js +300 -0
- package/dist/app/DocumentContextMenu.d.ts +29 -0
- package/dist/app/DocumentContextMenu.js +122 -0
- package/dist/app/DocumentPane.d.ts +52 -0
- package/dist/app/DocumentPane.js +30 -0
- package/dist/app/DocumentRouterTable.d.ts +58 -0
- package/dist/app/DocumentRouterTable.js +143 -0
- package/dist/app/ErrorBoundary.d.ts +12 -0
- package/dist/app/ErrorBoundary.js +16 -0
- package/dist/app/FindBar.d.ts +12 -0
- package/dist/app/FindBar.js +199 -0
- package/dist/app/LiveTitle.d.ts +14 -0
- package/dist/app/LiveTitle.js +14 -0
- package/dist/app/OpeningProgress.d.ts +13 -0
- package/dist/app/OpeningProgress.js +21 -0
- package/dist/app/PrintPreviewWindow.d.ts +2 -0
- package/dist/app/PrintPreviewWindow.js +57 -0
- package/dist/app/ShortcutHelp.d.ts +3 -0
- package/dist/app/ShortcutHelp.js +99 -0
- package/dist/app/Sidebar.d.ts +36 -0
- package/dist/app/Sidebar.js +121 -0
- package/dist/app/ToastViewport.d.ts +1 -0
- package/dist/app/ToastViewport.js +31 -0
- package/dist/app/Toolbar.d.ts +26 -0
- package/dist/app/Toolbar.js +63 -0
- package/dist/app/activeFormatSavers.d.ts +7 -0
- package/dist/app/activeFormatSavers.js +4 -0
- package/dist/app/clipboardPaths.d.ts +1 -0
- package/dist/app/clipboardPaths.js +31 -0
- package/dist/app/documentDrafts.d.ts +17 -0
- package/dist/app/documentDrafts.js +104 -0
- package/dist/app/draftResolution.d.ts +5 -0
- package/dist/app/draftResolution.js +25 -0
- package/dist/app/markdownPaletteActions.d.ts +8 -0
- package/dist/app/markdownPaletteActions.js +107 -0
- package/dist/app/resourceCensusDev.d.ts +41 -0
- package/dist/app/resourceCensusDev.js +518 -0
- package/dist/app/setupRecovery.d.ts +34 -0
- package/dist/app/setupRecovery.js +70 -0
- package/dist/app/shell/DocStatsPill.d.ts +18 -0
- package/dist/app/shell/DocStatsPill.js +32 -0
- package/dist/app/shell/DocumentTools.d.ts +9 -0
- package/dist/app/shell/DocumentTools.js +73 -0
- package/dist/app/shell/InfoInspector.d.ts +9 -0
- package/dist/app/shell/InfoInspector.js +67 -0
- package/dist/app/shell/StartPage.d.ts +9 -0
- package/dist/app/shell/StartPage.js +49 -0
- package/dist/app/shell/readEdit.d.ts +6 -0
- package/dist/app/shell/readEdit.js +6 -0
- package/dist/app/sidebar/SidebarDocumentMenu.d.ts +20 -0
- package/dist/app/sidebar/SidebarDocumentMenu.js +56 -0
- package/dist/app/sidebar/SidebarExpandedPanel.d.ts +9 -0
- package/dist/app/sidebar/SidebarExpandedPanel.js +78 -0
- package/dist/app/sidebar/SidebarIcons.d.ts +3 -0
- package/dist/app/sidebar/SidebarIcons.js +18 -0
- package/dist/app/sidebar/SidebarMenus.d.ts +19 -0
- package/dist/app/sidebar/SidebarMenus.js +64 -0
- package/dist/app/sidebar/SidebarPathUtils.d.ts +10 -0
- package/dist/app/sidebar/SidebarPathUtils.js +55 -0
- package/dist/app/sidebar/SidebarPrimitives.d.ts +12 -0
- package/dist/app/sidebar/SidebarPrimitives.js +45 -0
- package/dist/app/sidebar/SidebarRail.d.ts +5 -0
- package/dist/app/sidebar/SidebarRail.js +37 -0
- package/dist/app/sidebar/explorer/ExplorerRowMenu.d.ts +21 -0
- package/dist/app/sidebar/explorer/ExplorerRowMenu.js +37 -0
- package/dist/app/sidebar/explorer/ExplorerSections.d.ts +16 -0
- package/dist/app/sidebar/explorer/ExplorerSections.js +259 -0
- package/dist/app/sidebar/explorer/ExplorerTree.d.ts +46 -0
- package/dist/app/sidebar/explorer/ExplorerTree.js +387 -0
- package/dist/app/sidebar/explorer/explorerState.d.ts +37 -0
- package/dist/app/sidebar/explorer/explorerState.js +135 -0
- package/dist/app/tabMountPolicy.d.ts +45 -0
- package/dist/app/tabMountPolicy.js +104 -0
- package/dist/app/tabWarmOrder.d.ts +7 -0
- package/dist/app/tabWarmOrder.js +48 -0
- package/dist/app/tabs/TabFuseCurves.d.ts +3 -0
- package/dist/app/tabs/TabFuseCurves.js +15 -0
- package/dist/app/tabs/TabOverview.d.ts +24 -0
- package/dist/app/tabs/TabOverview.js +121 -0
- package/dist/app/tabs/TabStrip.d.ts +42 -0
- package/dist/app/tabs/TabStrip.js +380 -0
- package/dist/app/tabs/tabIds.d.ts +5 -0
- package/dist/app/tabs/tabIds.js +7 -0
- package/dist/app/tabs/useTabDirty.d.ts +3 -0
- package/dist/app/tabs/useTabDirty.js +11 -0
- package/dist/app/useAppChromeState.d.ts +21 -0
- package/dist/app/useAppChromeState.js +48 -0
- package/dist/app/useAppCommands.d.ts +66 -0
- package/dist/app/useAppCommands.js +71 -0
- package/dist/app/useAppDocumentCore.d.ts +210 -0
- package/dist/app/useAppDocumentCore.js +40 -0
- package/dist/app/useAppEffects.d.ts +18 -0
- package/dist/app/useAppEffects.js +52 -0
- package/dist/app/useAppPaneSearch.d.ts +37 -0
- package/dist/app/useAppPaneSearch.js +74 -0
- package/dist/app/useAppShellSetup.d.ts +40 -0
- package/dist/app/useAppShellSetup.js +52 -0
- package/dist/app/useAppSlots.d.ts +28 -0
- package/dist/app/useAppSlots.js +40 -0
- package/dist/app/useAppTabChrome.d.ts +51 -0
- package/dist/app/useAppTabChrome.js +66 -0
- package/dist/app/useAutoSave.d.ts +34 -0
- package/dist/app/useAutoSave.js +116 -0
- package/dist/app/useCloseAll.d.ts +52 -0
- package/dist/app/useCloseAll.js +295 -0
- package/dist/app/useClosedTabEviction.d.ts +10 -0
- package/dist/app/useClosedTabEviction.js +38 -0
- package/dist/app/useCommandPalette.d.ts +44 -0
- package/dist/app/useCommandPalette.js +254 -0
- package/dist/app/useCrossDocSearch.d.ts +7 -0
- package/dist/app/useCrossDocSearch.js +94 -0
- package/dist/app/useDistractionFree.d.ts +21 -0
- package/dist/app/useDistractionFree.js +73 -0
- package/dist/app/useDocumentCreation.d.ts +18 -0
- package/dist/app/useDocumentCreation.js +63 -0
- package/dist/app/useDocumentDirty.d.ts +31 -0
- package/dist/app/useDocumentDirty.js +46 -0
- package/dist/app/useDocumentLoader.d.ts +55 -0
- package/dist/app/useDocumentLoader.js +267 -0
- package/dist/app/useDocumentOutputActions.d.ts +6 -0
- package/dist/app/useDocumentOutputActions.js +45 -0
- package/dist/app/useDocumentRuntimeEffects.d.ts +36 -0
- package/dist/app/useDocumentRuntimeEffects.js +458 -0
- package/dist/app/useDocumentSaver.d.ts +58 -0
- package/dist/app/useDocumentSaver.js +511 -0
- package/dist/app/useDocumentTabs.d.ts +41 -0
- package/dist/app/useDocumentTabs.js +207 -0
- package/dist/app/useDocumentWatcher.d.ts +83 -0
- package/dist/app/useDocumentWatcher.js +393 -0
- package/dist/app/useDocumentZoom.d.ts +24 -0
- package/dist/app/useDocumentZoom.js +104 -0
- package/dist/app/useDocuments.d.ts +169 -0
- package/dist/app/useDocuments.js +455 -0
- package/dist/app/useDraftConfirmGate.d.ts +27 -0
- package/dist/app/useDraftConfirmGate.js +47 -0
- package/dist/app/useFileDragOverlay.d.ts +12 -0
- package/dist/app/useFileDragOverlay.js +49 -0
- package/dist/app/useFormatApis.d.ts +13 -0
- package/dist/app/useFormatApis.js +62 -0
- package/dist/app/useFormatDirtyState.d.ts +29 -0
- package/dist/app/useFormatDirtyState.js +81 -0
- package/dist/app/useGlobalShortcuts.d.ts +47 -0
- package/dist/app/useGlobalShortcuts.js +359 -0
- package/dist/app/useHtmlSession.d.ts +68 -0
- package/dist/app/useHtmlSession.js +463 -0
- package/dist/app/useHtmlSessionProps.d.ts +33 -0
- package/dist/app/useHtmlSessionProps.js +57 -0
- package/dist/app/useLibrary.d.ts +21 -0
- package/dist/app/useLibrary.js +81 -0
- package/dist/app/useOpenFromClipboard.d.ts +7 -0
- package/dist/app/useOpenFromClipboard.js +44 -0
- package/dist/app/usePaneLiveness.d.ts +29 -0
- package/dist/app/usePaneLiveness.js +199 -0
- package/dist/app/usePendingSavePaths.d.ts +19 -0
- package/dist/app/usePendingSavePaths.js +67 -0
- package/dist/app/useReadyShow.d.ts +12 -0
- package/dist/app/useReadyShow.js +142 -0
- package/dist/app/useSearchController.d.ts +22 -0
- package/dist/app/useSearchController.js +114 -0
- package/dist/app/useSetupRecovery.d.ts +41 -0
- package/dist/app/useSetupRecovery.js +153 -0
- package/dist/app/useTabChrome.d.ts +40 -0
- package/dist/app/useTabChrome.js +115 -0
- package/dist/app/useViewState.d.ts +27 -0
- package/dist/app/useViewState.js +160 -0
- package/dist/appName.d.ts +2 -0
- package/dist/appName.js +8 -0
- package/dist/documents/DocumentRouter.d.ts +102 -0
- package/dist/documents/DocumentRouter.js +153 -0
- package/dist/documents/FileTypeIcon.d.ts +6 -0
- package/dist/documents/FileTypeIcon.js +93 -0
- package/dist/documents/ImageDocument.d.ts +14 -0
- package/dist/documents/ImageDocument.js +61 -0
- package/dist/documents/capabilities.d.ts +17 -0
- package/dist/documents/capabilities.js +18 -0
- package/dist/documents/html/HtmlDocument.d.ts +15 -0
- package/dist/documents/html/HtmlDocument.js +584 -0
- package/dist/documents/html/HtmlDocumentLayout.d.ts +87 -0
- package/dist/documents/html/HtmlDocumentLayout.js +25 -0
- package/dist/documents/html/HtmlDocumentProps.d.ts +60 -0
- package/dist/documents/html/HtmlDocumentProps.js +1 -0
- package/dist/documents/html/HtmlDocumentSource.d.ts +34 -0
- package/dist/documents/html/HtmlDocumentSource.js +28 -0
- package/dist/documents/markdown/MarkdownSplitPane.d.ts +115 -0
- package/dist/documents/markdown/MarkdownSplitPane.js +352 -0
- package/dist/documents/mdx/MdxSplitPane.d.ts +32 -0
- package/dist/documents/mdx/MdxSplitPane.js +60 -0
- package/dist/documents/txt/LinkDialog.d.ts +8 -0
- package/dist/documents/txt/LinkDialog.js +34 -0
- package/dist/documents/txt/MarkdownSourceToolbar.d.ts +8 -0
- package/dist/documents/txt/MarkdownSourceToolbar.js +25 -0
- package/dist/documents/txt/SourceEditorToolbar.d.ts +12 -0
- package/dist/documents/txt/SourceEditorToolbar.js +4 -0
- package/dist/documents/txt/TxtEditor.d.ts +39 -0
- package/dist/documents/txt/TxtEditor.js +186 -0
- package/dist/documents/txt/sourceEditorTypes.d.ts +49 -0
- package/dist/documents/txt/sourceEditorTypes.js +1 -0
- package/dist/documents/txt/sourceExtensions.d.ts +38 -0
- package/dist/documents/txt/sourceExtensions.js +444 -0
- package/dist/documents/txt/sourceFormatting.d.ts +18 -0
- package/dist/documents/txt/sourceFormatting.js +183 -0
- package/dist/documents/txt/sourceTheme.d.ts +5 -0
- package/dist/documents/txt/sourceTheme.js +220 -0
- package/dist/documents/txt/useCodeMirrorSourceEditor.d.ts +69 -0
- package/dist/documents/txt/useCodeMirrorSourceEditor.js +546 -0
- package/dist/documents/txt/useSourceSearch.d.ts +39 -0
- package/dist/documents/txt/useSourceSearch.js +122 -0
- package/dist/documents/txt/useTextEditorCommands.d.ts +26 -0
- package/dist/documents/txt/useTextEditorCommands.js +151 -0
- package/dist/documents/web/LivePreviewPane.d.ts +11 -0
- package/dist/documents/web/LivePreviewPane.js +58 -0
- package/dist/env.d.ts +66 -0
- package/dist/index.d.ts +121 -0
- package/dist/index.js +123 -0
- package/dist/settings/AppearanceMenu.d.ts +9 -0
- package/dist/settings/AppearanceMenu.js +54 -0
- package/dist/settings/SettingsPanel.d.ts +7 -0
- package/dist/settings/SettingsPanel.js +104 -0
- package/dist/settings/publishedSections.d.ts +22 -0
- package/dist/settings/publishedSections.js +79 -0
- package/dist/settings/sections/AboutSection.d.ts +1 -0
- package/dist/settings/sections/AboutSection.js +13 -0
- package/dist/settings/sections/DiagnosticsSection.d.ts +1 -0
- package/dist/settings/sections/DiagnosticsSection.js +42 -0
- package/dist/settings/sections/PrivacySection.d.ts +3 -0
- package/dist/settings/sections/PrivacySection.js +51 -0
- package/dist/settings/sections/UpdateLicenseBlock.d.ts +10 -0
- package/dist/settings/sections/UpdateLicenseBlock.js +158 -0
- package/dist/state/explorer.d.ts +32 -0
- package/dist/state/explorer.js +111 -0
- package/dist/state/legal.d.ts +51 -0
- package/dist/state/legal.js +130 -0
- package/dist/state/library.d.ts +33 -0
- package/dist/state/library.js +110 -0
- package/dist/state/updateLicense.d.ts +31 -0
- package/dist/state/updateLicense.js +340 -0
- package/dist/styles/explorer.css +219 -0
- package/dist/styles/index.css +1477 -0
- package/dist/styles/tabs.css +467 -0
- package/dist/styles.css +6 -0
- package/package.json +88 -3
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { HtmlDocumentView } from "@rightkit/html-editor/documents/html/HtmlDocumentView";
|
|
3
|
+
import { HtmlDocumentSource } from "./HtmlDocumentSource.js";
|
|
4
|
+
import { HtmlDocumentEdit } from "@rightkit/html-editor/documents/html/HtmlDocumentEdit";
|
|
5
|
+
import { HtmlAddressRow, HtmlLinkStatus, HtmlRichEditRow, HtmlSiteRootNotice, } from "@rightkit/html-editor/documents/html/HtmlDocumentToolbars";
|
|
6
|
+
import { HtmlSafeBlockEditor } from "@rightkit/html-editor/documents/html/HtmlSafeBlockEditor";
|
|
7
|
+
import { BlockedLinkPrompt } from "@rightkit/html-editor/documents/html/HtmlDocumentChrome";
|
|
8
|
+
/** Pure render tree for the HTML document: toolbars, preview split, source drawer, and overlays. */
|
|
9
|
+
export function HtmlDocumentLayout({ flags, address, rich, preview, source, overlays }) {
|
|
10
|
+
const { readOnly, active, drawer, sourceFullscreen, mode } = flags;
|
|
11
|
+
const { safeTarget, blockSnapshot, blockIdentity, hasChangeView, richEditRef, richFormatting, setRichFormatting, runBlockRichOp, runBlockStructuralOp, canRunRichOp, canRunStructuralOp, propertyAttributes, editRichSource, closeInlineEditor, inframeTarget, setInframeTarget, iframeGeneration, } = rich;
|
|
12
|
+
const { splitRef, hoveredLink, pickingRoot, onChooseSiteRoot, showSiteRootNotice, ...viewProps } = preview;
|
|
13
|
+
const overlay = !readOnly && safeTarget && blockSnapshot && (inframeTarget || !hasChangeView) ? (_jsx(HtmlSafeBlockEditor, { ref: richEditRef, identity: blockIdentity, snapshot: blockSnapshot, visualStyle: safeTarget.style, onClose: closeInlineEditor, onRichOp: (op) => void runBlockRichOp(op), onToolbarStateChange: setRichFormatting, propertyTargetTag: safeTarget.src !== undefined ? "img" : undefined, propertyAttributes: propertyAttributes, onEditSource: editRichSource, iframeTarget: inframeTarget, mountMode: inframeTarget ? "inframe" : undefined, iframeGeneration: iframeGeneration, onIframeTargetChange: setInframeTarget })) : null;
|
|
14
|
+
return (_jsxs("div", { className: "relative flex h-full min-h-0 flex-col bg-ink", children: [!(!readOnly && safeTarget && blockSnapshot) && (_jsx(HtmlAddressRow, { address: address.activePath, dirty: address.activeDirty, subpageStale: address.subpageStale, viewport: address.viewport, canGoBack: address.canGoBack, canGoForward: address.canGoForward, onBack: () => address.goHistory(-1), onForward: () => address.goHistory(1), onViewport: (next) => address.setViewportMode(next === "desktop" ? "auto" : next), onReload: address.onReload, findBar: drawer ? undefined : address.findBar, findMode: drawer ? undefined : address.findMode, onRequestFind: address.onRequestFind, onRequestReplace: drawer ? undefined : address.onRequestReplace, trailing: readOnly
|
|
15
|
+
? undefined
|
|
16
|
+
: {
|
|
17
|
+
label: "source",
|
|
18
|
+
ariaLabel: "Toggle source",
|
|
19
|
+
active: drawer,
|
|
20
|
+
onClick: address.onToggleSource,
|
|
21
|
+
} })), !readOnly && safeTarget && blockSnapshot && (_jsx(HtmlRichEditRow, { handleRef: richEditRef, snapshot: blockSnapshot, anchorHref: safeTarget.href, anchorSrc: safeTarget.src, formatting: richFormatting, onRichOp: (op) => void runBlockRichOp(op), onStructuralOp: (surface, action) => void runBlockStructuralOp(surface, action), canRichOp: canRunRichOp, canStructuralOp: canRunStructuralOp, onEditSource: editRichSource })), _jsxs("div", { ref: splitRef, "data-html-preview-split": true, className: "relative flex min-h-0 min-w-0 flex-1", children: [hoveredLink && _jsx(HtmlLinkStatus, { href: hoveredLink }), !sourceFullscreen && showSiteRootNotice && (_jsx(HtmlSiteRootNotice, { pickingRoot: pickingRoot, onChoose: onChooseSiteRoot })), !sourceFullscreen && (_jsx(HtmlDocumentView, { ...viewProps, mode: mode, drawerOpen: drawer, active: active, overlay: overlay })), drawer && (_jsx(HtmlDocumentSource, { ...source, active: active, sourceFullscreen: sourceFullscreen }))] }), overlays.inspectMenu && (_jsx(HtmlDocumentEdit, { inspectMenu: overlays.inspectMenu, onClose: () => overlays.setInspectMenu(null), onCopyValue: overlays.copyMenuValue, onOpenLinkNewTab: (href) => {
|
|
22
|
+
overlays.setInspectMenu(null);
|
|
23
|
+
void overlays.handleHref(href, true);
|
|
24
|
+
}, onViewSource: overlays.inspectFromMenu })), overlays.blocked && (_jsx(BlockedLinkPrompt, { href: overlays.blocked.href, reason: overlays.blocked.reason, onLocate: () => void overlays.locateBlocked(), onCopy: () => void overlays.copyBlocked(), onDismiss: () => overlays.setBlocked(null) }))] }));
|
|
25
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { ReactNode, RefObject } from "react";
|
|
2
|
+
import type { SearchProvider, ViewMode } from "@rightkit/document-core/documents/types";
|
|
3
|
+
import type { SourceEditorViewState } from "../txt/TxtEditor.js";
|
|
4
|
+
import type { HtmlPageBuffer } from "@rightkit/html-editor/documents/html/sessionTypes";
|
|
5
|
+
export interface HtmlDocumentProps {
|
|
6
|
+
/** The originally-opened file. Its folder is the trusted site root; never changes as you navigate. */
|
|
7
|
+
entryPath: string;
|
|
8
|
+
/** Shared ViewMode; for HTML only interactive (default) / preview (Safe) apply now — Code is gone,
|
|
9
|
+
* replaced by the in-place source drawer below. A stale persisted "source" falls back to interactive.
|
|
10
|
+
* Flipped between interactive (scripts ON) and preview/Safe (scripts OFF) by the scripts switch in
|
|
11
|
+
* the action bar above — HtmlDocument only READS it (drives `mode`); it doesn't own the toggle. */
|
|
12
|
+
view: ViewMode;
|
|
13
|
+
/** Live, editable source of the ENTRY doc (owned by App via the shared edits buffer). */
|
|
14
|
+
text: string;
|
|
15
|
+
onChange: (text: string) => void;
|
|
16
|
+
registerSearchProvider: (provider: SearchProvider | null) => void;
|
|
17
|
+
findBar?: ReactNode;
|
|
18
|
+
findMode?: "find" | "replace";
|
|
19
|
+
onRequestFind?: () => void;
|
|
20
|
+
onRequestReplace?: () => void;
|
|
21
|
+
/** Close the shared find bar. The drawer and the preview search DIFFERENT surfaces, so an open
|
|
22
|
+
* find must not silently survive the switch between them. */
|
|
23
|
+
onCloseFind?: () => void;
|
|
24
|
+
/** Open a linked doc as its own the host app tab. `background` (new-tab gesture) adds it without
|
|
25
|
+
* switching away from the current document. */
|
|
26
|
+
onOpenPath?: (path: string, opts?: {
|
|
27
|
+
background?: boolean;
|
|
28
|
+
}) => void;
|
|
29
|
+
/** Switch Interactive edit requests into Safe before creating a rich-edit draft. */
|
|
30
|
+
onChangeView?: (view: ViewMode) => void;
|
|
31
|
+
/** Inactive keep-alive panes stay mounted but must not own global shortcut events. */
|
|
32
|
+
active?: boolean;
|
|
33
|
+
/** Wave 2 Read mode: the source drawer, block editing, and inspect-to-source all stay closed —
|
|
34
|
+
* Interactive/Safe `view` and every viewing control are unaffected. */
|
|
35
|
+
readOnly?: boolean;
|
|
36
|
+
/** Editable buffers for navigated (non-entry) pages in this session, keyed by absolute path. */
|
|
37
|
+
pageBuffers: Record<string, HtmlPageBuffer>;
|
|
38
|
+
/** Absolute paths of every dirty page in this session (entry + subs) — drives the page dirty marker. */
|
|
39
|
+
dirtyPagePaths: string[];
|
|
40
|
+
/** Seed a sub-page buffer from disk on first visit (no-op for the entry / already-loaded page). */
|
|
41
|
+
onEnsurePage: (entryPath: string, pagePath: string) => void;
|
|
42
|
+
/** Edit a page's buffer (entry routes to onChange; subs to App's per-page buffer). */
|
|
43
|
+
onPageChange: (entryPath: string, pagePath: string, text: string) => void;
|
|
44
|
+
/** Report the in-site page currently shown (App needs it for Ctrl+S targeting). */
|
|
45
|
+
onSetActivePage: (entryPath: string, pagePath: string) => void;
|
|
46
|
+
/** Save one page to disk (entry or sub). */
|
|
47
|
+
onSavePage: (entryPath: string, pagePath: string) => void;
|
|
48
|
+
/** Revert one page's buffer to its on-disk content. */
|
|
49
|
+
onDiscardPage: (entryPath: string, pagePath: string) => void;
|
|
50
|
+
/** Re-read a page from disk (refresh, browser-style). Prompts if the page has unsaved edits. */
|
|
51
|
+
onReloadFromDisk: (path: string, options?: {
|
|
52
|
+
force?: boolean;
|
|
53
|
+
}) => void;
|
|
54
|
+
/** Durable preview scroll: seeded on mount so scroll survives a tab-switch unmount. */
|
|
55
|
+
initialPreviewScrollY?: number;
|
|
56
|
+
/** Report the live preview scroll up to App's per-tab store (so it's restored on return). */
|
|
57
|
+
onPreviewScroll?: (y: number) => void;
|
|
58
|
+
/** App-owned source selection/scroll, retained if this dirty session is shelled. */
|
|
59
|
+
sourceViewStateRef?: RefObject<Record<string, SourceEditorViewState>>;
|
|
60
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { EditorView } from "@codemirror/view";
|
|
2
|
+
import type { RefObject, ReactNode } from "react";
|
|
3
|
+
import { type SourceEditorViewState } from "../txt/TxtEditor.js";
|
|
4
|
+
import type { SearchProvider } from "@rightkit/document-core/documents/types";
|
|
5
|
+
import type { SourceRange } from "@rightkit/html-editor/documents/html/sourceMap";
|
|
6
|
+
/**
|
|
7
|
+
* The in-place HTML source drawer: a resizable pane (splitter + optional fullscreen) hosting the
|
|
8
|
+
* editable CodeMirror source for the active page (entry or a navigated sub-page). Extracted verbatim
|
|
9
|
+
* from HtmlDocument — no behavior change.
|
|
10
|
+
*/
|
|
11
|
+
export declare function HtmlDocumentSource({ active, subLoaded, activePath, activeBuffer, activeName, activeDirty, drawerWidth, sourceFullscreen, onToggleFullscreen, onStartResize, onActiveSourceChange, onActiveSave, onDiscardPage, registerSearchProvider, findBar, findMode, onRequestFind, onRequestReplace, sourceViewStateRef, sourceEditorRef, pendingInspectRangeRef, revealSourceRange, }: {
|
|
12
|
+
active: boolean;
|
|
13
|
+
subLoaded: boolean;
|
|
14
|
+
activePath: string;
|
|
15
|
+
activeBuffer: string;
|
|
16
|
+
activeName: string;
|
|
17
|
+
activeDirty: boolean;
|
|
18
|
+
drawerWidth: number;
|
|
19
|
+
sourceFullscreen: boolean;
|
|
20
|
+
onToggleFullscreen: () => void;
|
|
21
|
+
onStartResize: (e: import("react").PointerEvent<HTMLDivElement>) => void;
|
|
22
|
+
onActiveSourceChange: (next: string) => void;
|
|
23
|
+
onActiveSave: () => void;
|
|
24
|
+
onDiscardPage: () => void;
|
|
25
|
+
registerSearchProvider: (provider: SearchProvider | null) => void;
|
|
26
|
+
findBar?: ReactNode;
|
|
27
|
+
findMode?: "find" | "replace";
|
|
28
|
+
onRequestFind?: () => void;
|
|
29
|
+
onRequestReplace?: () => void;
|
|
30
|
+
sourceViewStateRef: RefObject<Record<string, SourceEditorViewState>>;
|
|
31
|
+
sourceEditorRef: RefObject<EditorView | null>;
|
|
32
|
+
pendingInspectRangeRef: RefObject<SourceRange | null>;
|
|
33
|
+
revealSourceRange: (range: SourceRange) => boolean;
|
|
34
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { FullscreenIcon } from "@rightkit/document-core/documents/FullscreenIcon";
|
|
3
|
+
import { TextEditor } from "../txt/TxtEditor.js";
|
|
4
|
+
/**
|
|
5
|
+
* The in-place HTML source drawer: a resizable pane (splitter + optional fullscreen) hosting the
|
|
6
|
+
* editable CodeMirror source for the active page (entry or a navigated sub-page). Extracted verbatim
|
|
7
|
+
* from HtmlDocument — no behavior change.
|
|
8
|
+
*/
|
|
9
|
+
export function HtmlDocumentSource({ active, subLoaded, activePath, activeBuffer, activeName, activeDirty, drawerWidth, sourceFullscreen, onToggleFullscreen, onStartResize, onActiveSourceChange, onActiveSave, onDiscardPage, registerSearchProvider, findBar, findMode, onRequestFind, onRequestReplace, sourceViewStateRef, sourceEditorRef, pendingInspectRangeRef, revealSourceRange, }) {
|
|
10
|
+
return (_jsxs(_Fragment, { children: [!sourceFullscreen && (_jsx("div", { onPointerDown: onStartResize, role: "separator", tabIndex: 0, "aria-orientation": "vertical", "aria-label": "Resize source drawer", "aria-valuemin": 280, "aria-valuenow": Math.round(drawerWidth), className: "w-1.5 shrink-0 cursor-col-resize bg-line transition-colors hover:bg-copper/60" })), _jsx("div", { className: `flex min-w-0 flex-col bg-panel ${sourceFullscreen ? "flex-1" : "shrink-0"}`, style: sourceFullscreen ? undefined : { width: drawerWidth, maxWidth: "calc(100% - 6px)" }, children: _jsx("div", { className: "min-h-0 flex-1", children: subLoaded ? (_jsx(TextEditor, { dockToolbar: false, active: active, documentPath: activePath, mono: true, language: "html", text: activeBuffer, onChange: onActiveSourceChange, onSave: onActiveSave, registerSearchProvider: registerSearchProvider, findBar: findBar, findMode: findMode, onRequestFind: onRequestFind, onRequestReplace: onRequestReplace, sourceToolbarActions: _jsxs(_Fragment, { children: [_jsx("button", { type: "button", onClick: onToggleFullscreen, className: "vr-press vr-focus flex h-6 w-6 items-center justify-center rounded text-muted hover:bg-panel-2 hover:text-paper", "aria-label": sourceFullscreen ? "Restore HTML split" : "Expand HTML source", title: sourceFullscreen ? "Restore split" : "Expand source", children: _jsx(FullscreenIcon, { restore: sourceFullscreen }) }), _jsx("button", { type: "button", onClick: onActiveSave, title: `Save ${activeName}`, className: `vr-press vr-focus rounded px-2 py-1 ${activeDirty ? "text-copper-hi" : "text-muted hover:bg-panel-2 hover:text-paper"}`, children: "save" }), _jsx("button", { type: "button", onClick: onDiscardPage, disabled: !activeDirty, title: `Discard ${activeName}`, className: "vr-press vr-focus rounded px-2 py-1 text-muted hover:bg-panel-2 hover:text-paper disabled:cursor-not-allowed disabled:opacity-40", children: "discard" })] }), initialViewState: sourceViewStateRef.current[activePath] ?? null, onViewStateChange: (state) => {
|
|
11
|
+
sourceViewStateRef.current[activePath] = state;
|
|
12
|
+
}, onEditorReady: (view) => {
|
|
13
|
+
sourceEditorRef.current = view;
|
|
14
|
+
if (!view)
|
|
15
|
+
return;
|
|
16
|
+
const pending = pendingInspectRangeRef.current;
|
|
17
|
+
if (!pending)
|
|
18
|
+
return;
|
|
19
|
+
// The editor restores its saved scroll in a rAF right after this fires (see
|
|
20
|
+
// TextEditor mount). Reveal on a LATER frame so our scroll-to-match isn't undone —
|
|
21
|
+
// otherwise inspecting from a freshly-opened drawer selects the range off-screen.
|
|
22
|
+
window.requestAnimationFrame(() => window.requestAnimationFrame(() => {
|
|
23
|
+
const range = pendingInspectRangeRef.current;
|
|
24
|
+
if (range && revealSourceRange(range))
|
|
25
|
+
pendingInspectRangeRef.current = null;
|
|
26
|
+
}));
|
|
27
|
+
} }, activePath)) : (_jsx("div", { className: "flex h-full items-center justify-center text-xs text-muted-2", children: "Loading source\u2026" })) }) })] }));
|
|
28
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { type ReactNode, type RefObject, type UIEventHandler } from "react";
|
|
2
|
+
import type { EditorView } from "@codemirror/view";
|
|
3
|
+
import type { SearchProvider, ViewMode } from "@rightkit/document-core/documents/types";
|
|
4
|
+
import type { SourceEditorViewState } from "../txt/TxtEditor.js";
|
|
5
|
+
import type { MarkdownSourceRange } from "@rightkit/markdown-editor/documents/markdown/MarkdownProtectedBlock";
|
|
6
|
+
import { collectMarkdownHeadings, type MarkdownHeading } from "@rightkit/markdown-editor/documents/markdown/markdownTools";
|
|
7
|
+
import type { MarkdownExportApi } from "@rightkit/markdown-editor/documents/markdown/export/markdownExportTypes";
|
|
8
|
+
export declare function MarkdownSourceFallback({ text }: {
|
|
9
|
+
text: string;
|
|
10
|
+
}): import("react").JSX.Element;
|
|
11
|
+
export declare function selectAndScrollSourceLine(view: EditorView, lineNumber: number): void;
|
|
12
|
+
export declare function selectAndScrollSourceRange(view: EditorView, range: MarkdownSourceRange): void;
|
|
13
|
+
interface Props {
|
|
14
|
+
text: string;
|
|
15
|
+
documentPath?: string;
|
|
16
|
+
subscribeEdits?: (path: string, cb: () => void) => () => void;
|
|
17
|
+
getEditSnapshot?: (path: string) => string | undefined;
|
|
18
|
+
devMode?: boolean;
|
|
19
|
+
/** Source drawer open. The preview stays MOUNTED across this toggle (drawer-style, like HTML) — no
|
|
20
|
+
* remount, so opening source no longer flashes a loading skeleton / re-renders / resets scroll. */
|
|
21
|
+
showSource: boolean;
|
|
22
|
+
/** Wave 2 Read mode: hybrid view becomes non-editable and the source drawer stays closed —
|
|
23
|
+
* editing is only ever reachable in Edit. */
|
|
24
|
+
readOnly?: boolean;
|
|
25
|
+
/** Close the shared find bar when the rendered/source surface switches. */
|
|
26
|
+
onCloseFind?: () => void;
|
|
27
|
+
onChange: (text: string) => void;
|
|
28
|
+
onSave: () => void;
|
|
29
|
+
registerSearchProvider: (provider: SearchProvider | null) => void;
|
|
30
|
+
findBar?: ReactNode;
|
|
31
|
+
findMode?: "find" | "replace";
|
|
32
|
+
onRequestFind?: () => void;
|
|
33
|
+
onRequestReplace?: () => void;
|
|
34
|
+
onOpenPath?: (path: string) => void;
|
|
35
|
+
onChangeView?: (view: ViewMode) => void;
|
|
36
|
+
active?: boolean;
|
|
37
|
+
registerMarkdownExport?: (api: MarkdownExportApi | null) => void;
|
|
38
|
+
/** App-owned raw-source selection/scroll, retained across a shelled dirty tab. */
|
|
39
|
+
sourceViewStateRef?: RefObject<Record<string, SourceEditorViewState>>;
|
|
40
|
+
scrollContainerRef?: RefObject<HTMLDivElement | null>;
|
|
41
|
+
onScroll?: UIEventHandler<HTMLDivElement>;
|
|
42
|
+
/**
|
|
43
|
+
* Optional: Markdown document core to wire edits through. When
|
|
44
|
+
* supplied, the source editor applies edits via the core instead
|
|
45
|
+
* of the legacy `subscribeEdits`/`onChange` pipeline. Off by
|
|
46
|
+
* default to preserve existing behavior.
|
|
47
|
+
*/
|
|
48
|
+
markdownCore?: import("@rightkit/markdown-editor/documents/markdown/core/MarkdownDocumentCore").MarkdownDocumentCore;
|
|
49
|
+
}
|
|
50
|
+
export declare function MarkdownSourceDrawer({ documentPath, text, onChange, onSave, registerSourceProvider, findBar, findMode, onRequestFind, onRequestReplace, headings, sourceFocusMode, sourceTypewriterMode, sourceFullscreen, sourceWidth, initialViewState, onJumpLine, onToggleFocus, onToggleTypewriter, onToggleFullscreen, onViewStateChange, onEditorReady, externalHistory, markdownCore, restoreTitle, expandTitle, showFullscreenToggle, className, }: {
|
|
51
|
+
documentPath?: string;
|
|
52
|
+
text: string;
|
|
53
|
+
onChange: (text: string) => void;
|
|
54
|
+
onSave: () => void;
|
|
55
|
+
registerSourceProvider: (provider: SearchProvider | null) => void;
|
|
56
|
+
findBar?: ReactNode;
|
|
57
|
+
findMode?: "find" | "replace";
|
|
58
|
+
onRequestFind?: () => void;
|
|
59
|
+
onRequestReplace?: () => void;
|
|
60
|
+
headings: ReturnType<typeof collectMarkdownHeadings>;
|
|
61
|
+
sourceFocusMode: boolean;
|
|
62
|
+
sourceTypewriterMode: boolean;
|
|
63
|
+
sourceFullscreen: boolean;
|
|
64
|
+
sourceWidth: number;
|
|
65
|
+
initialViewState: SourceEditorViewState | null;
|
|
66
|
+
onJumpLine: (line: number) => void;
|
|
67
|
+
onToggleFocus: () => void;
|
|
68
|
+
onToggleTypewriter: () => void;
|
|
69
|
+
onToggleFullscreen: () => void;
|
|
70
|
+
onViewStateChange: (state: SourceEditorViewState) => void;
|
|
71
|
+
onEditorReady: (view: EditorView | null) => void;
|
|
72
|
+
externalHistory?: boolean;
|
|
73
|
+
markdownCore?: import("@rightkit/markdown-editor/documents/markdown/core/MarkdownDocumentCore").MarkdownDocumentCore;
|
|
74
|
+
restoreTitle?: string;
|
|
75
|
+
expandTitle?: string;
|
|
76
|
+
showFullscreenToggle?: boolean;
|
|
77
|
+
className?: string;
|
|
78
|
+
}): import("react").JSX.Element;
|
|
79
|
+
export declare function useMarkdownSourceDrawer(text: string, onEditorReady?: (view: EditorView | null, jumpSourceToLine: (lineNumber: number) => void, jumpSourceToRange: (range: MarkdownSourceRange) => void) => void, onCaretOffsetChange?: (offset: number) => void, mappedHeadings?: MarkdownHeading[], documentPath?: string, externalSourceViewStateRef?: RefObject<Record<string, SourceEditorViewState>>): {
|
|
80
|
+
sourceWidth: number;
|
|
81
|
+
setSourceWidth: import("react").Dispatch<import("react").SetStateAction<number>>;
|
|
82
|
+
sourceFullscreen: boolean;
|
|
83
|
+
sourceEditorRef: RefObject<EditorView | null>;
|
|
84
|
+
jumpSourceToLine: (lineNumber: number) => void;
|
|
85
|
+
jumpSourceToRange: (range: MarkdownSourceRange) => void;
|
|
86
|
+
drawerProps: {
|
|
87
|
+
headings: MarkdownHeading[];
|
|
88
|
+
sourceFocusMode: boolean;
|
|
89
|
+
sourceTypewriterMode: boolean;
|
|
90
|
+
sourceFullscreen: boolean;
|
|
91
|
+
sourceWidth: number;
|
|
92
|
+
initialViewState: SourceEditorViewState | null;
|
|
93
|
+
onJumpLine: (lineNumber: number) => void;
|
|
94
|
+
onToggleFocus: () => void;
|
|
95
|
+
onToggleTypewriter: () => void;
|
|
96
|
+
onToggleFullscreen: () => void;
|
|
97
|
+
onViewStateChange: (state: SourceEditorViewState) => void;
|
|
98
|
+
onEditorReady: (view: EditorView | null) => void;
|
|
99
|
+
};
|
|
100
|
+
};
|
|
101
|
+
export declare function MarkdownSplitPane({ text, showSource, readOnly, onCloseFind, onChange, onSave, registerSearchProvider, findBar, findMode, onRequestFind, onRequestReplace, documentPath, subscribeEdits, getEditSnapshot, onOpenPath, onChangeView, active, registerMarkdownExport, sourceViewStateRef, scrollContainerRef, onScroll, markdownCore, }: Props): import("react").JSX.Element;
|
|
102
|
+
export declare function MarkdownSourceActions({ headings, focusMode, typewriterMode, sourceFullscreen, onJumpLine, onToggleFocus, onToggleTypewriter, onToggleFullscreen, restoreTitle, expandTitle, showFullscreenToggle, }: {
|
|
103
|
+
headings: ReturnType<typeof collectMarkdownHeadings>;
|
|
104
|
+
focusMode: boolean;
|
|
105
|
+
typewriterMode: boolean;
|
|
106
|
+
sourceFullscreen: boolean;
|
|
107
|
+
onJumpLine: (line: number) => void;
|
|
108
|
+
onToggleFocus: () => void;
|
|
109
|
+
onToggleTypewriter: () => void;
|
|
110
|
+
onToggleFullscreen: () => void;
|
|
111
|
+
restoreTitle?: string;
|
|
112
|
+
expandTitle?: string;
|
|
113
|
+
showFullscreenToggle?: boolean;
|
|
114
|
+
}): import("react").JSX.Element;
|
|
115
|
+
export {};
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useCloseFindOnSurfaceChange } from "@rightkit/document-core/app/useCloseFindOnSurfaceChange";
|
|
3
|
+
import { lazy, Suspense, useCallback, useEffect, useMemo, useRef, useState, } from "react";
|
|
4
|
+
import { useEditText } from "@rightkit/document-core/app/useEditText";
|
|
5
|
+
import { useMirroredEditText } from "@rightkit/document-core/documents/useMirroredEditText";
|
|
6
|
+
import { FullscreenIcon } from "@rightkit/document-core/documents/FullscreenIcon";
|
|
7
|
+
import { TextSelection } from "prosemirror-state";
|
|
8
|
+
import { scrollPosIntoView } from "@rightkit/document-core/documents/txt/cmScroll";
|
|
9
|
+
import { MarkdownEditor } from "@rightkit/markdown-editor/documents/markdown/MarkdownEditor";
|
|
10
|
+
import { collectMarkdownHeadings, collectSourceMapHeadings, } from "@rightkit/markdown-editor/documents/markdown/markdownTools";
|
|
11
|
+
import { Dropdown, Item, Sep, TBtn } from "@rightkit/document-core/documents/toolbarPrimitives";
|
|
12
|
+
import { useMarkdownCoreSubscription } from "@rightkit/markdown-editor/documents/markdown/core/useMarkdownCoreSubscription";
|
|
13
|
+
import { useMarkdownCoreOptional } from "@rightkit/markdown-editor/documents/markdown/MarkdownCoreProvider";
|
|
14
|
+
import { MarkdownWorkspacePanel } from "@rightkit/markdown-editor/documents/markdown/properties/MarkdownWorkspacePanel";
|
|
15
|
+
import { useSettings } from "@rightkit/document-core/settings/settings";
|
|
16
|
+
import { logicalSourceToPmPosition, postPmSelectionToCore } from "@rightkit/markdown-editor/documents/markdown/projection/pmSelectionBridge";
|
|
17
|
+
import { publishMarkdownDocumentActivity } from "@rightkit/markdown-editor/documents/markdown/markdownDocumentActivity";
|
|
18
|
+
// Source editor (CodeMirror) is lazy so md OPEN stays light — only the preview loads. DocumentRouter
|
|
19
|
+
// prefetches this chunk when a md doc opens, so by the time the drawer is opened it's already warm
|
|
20
|
+
// (the drawer shows readable source while the chunk resolves). The preview never unmounts, so the
|
|
21
|
+
// Suspense fallback only ever covers the drawer.
|
|
22
|
+
const TextEditor = lazy(() => import("../txt/TxtEditor.js").then((m) => ({ default: m.TextEditor })));
|
|
23
|
+
const noopSubscribeEdits = (_path, _cb) => () => { };
|
|
24
|
+
const noopEditSnapshot = (_path) => undefined;
|
|
25
|
+
export function MarkdownSourceFallback({ text }) {
|
|
26
|
+
return (_jsx("div", { className: "h-full overflow-auto bg-panel px-8 py-6", children: _jsx("pre", { "data-vr-markdown-source-fallback": "", className: "min-h-full whitespace-pre-wrap rounded-lg px-8 py-7 font-mono text-sm leading-relaxed", style: { background: "var(--reader-surface)", color: "var(--doc-fg)" }, children: text }) }));
|
|
27
|
+
}
|
|
28
|
+
export function selectAndScrollSourceLine(view, lineNumber) {
|
|
29
|
+
const line = view.state.doc.line(Math.max(1, Math.min(lineNumber, view.state.doc.lines)));
|
|
30
|
+
view.dispatch({
|
|
31
|
+
selection: { anchor: line.from, head: line.to },
|
|
32
|
+
scrollIntoView: true,
|
|
33
|
+
userEvent: "select.pointer",
|
|
34
|
+
});
|
|
35
|
+
scrollPosIntoView(view, line.from);
|
|
36
|
+
view.focus();
|
|
37
|
+
}
|
|
38
|
+
export function selectAndScrollSourceRange(view, range) {
|
|
39
|
+
const from = Math.max(0, Math.min(range.from, view.state.doc.length));
|
|
40
|
+
const to = Math.max(from, Math.min(range.to, view.state.doc.length));
|
|
41
|
+
view.dispatch({
|
|
42
|
+
selection: { anchor: from, head: to },
|
|
43
|
+
scrollIntoView: true,
|
|
44
|
+
userEvent: "select.pointer",
|
|
45
|
+
});
|
|
46
|
+
scrollPosIntoView(view, from);
|
|
47
|
+
view.focus();
|
|
48
|
+
}
|
|
49
|
+
export function MarkdownSourceDrawer({ documentPath, text, onChange, onSave, registerSourceProvider, findBar, findMode, onRequestFind, onRequestReplace, headings, sourceFocusMode, sourceTypewriterMode, sourceFullscreen, sourceWidth, initialViewState, onJumpLine, onToggleFocus, onToggleTypewriter, onToggleFullscreen, onViewStateChange, onEditorReady, externalHistory = false, markdownCore, restoreTitle, expandTitle, showFullscreenToggle = true, className = `relative flex min-h-0 min-w-0 flex-col bg-panel ${sourceFullscreen ? "-ml-px flex-1" : "shrink-0 border-l border-line"}`, }) {
|
|
50
|
+
return (_jsx("section", { className: className, style: sourceFullscreen ? undefined : { width: `${sourceWidth}%` }, children: _jsx("div", { className: "min-h-0 flex-1", children: _jsx(Suspense, { fallback: _jsx(MarkdownSourceFallback, { text: text }), children: _jsx(TextEditor, { documentPath: documentPath, mono: true, markdownToolbar: true, text: text, onChange: onChange, onSave: onSave, registerSearchProvider: registerSourceProvider, findBar: findBar, findMode: findMode, onRequestFind: onRequestFind, onRequestReplace: onRequestReplace, sourceToolbarActions: _jsx(MarkdownSourceActions, { headings: headings, focusMode: sourceFocusMode, typewriterMode: sourceTypewriterMode, sourceFullscreen: sourceFullscreen, onJumpLine: onJumpLine, onToggleFocus: onToggleFocus, onToggleTypewriter: onToggleTypewriter, onToggleFullscreen: onToggleFullscreen, restoreTitle: restoreTitle, expandTitle: expandTitle, showFullscreenToggle: showFullscreenToggle }), initialViewState: initialViewState, onViewStateChange: onViewStateChange, onEditorReady: onEditorReady, autoFocus: true, sourceFocusMode: sourceFocusMode, sourceTypewriterMode: sourceTypewriterMode, externalHistory: externalHistory, markdownCore: markdownCore }) }) }) }));
|
|
51
|
+
}
|
|
52
|
+
export function useMarkdownSourceDrawer(text, onEditorReady, onCaretOffsetChange, mappedHeadings, documentPath, externalSourceViewStateRef) {
|
|
53
|
+
const localSourceViewStateRef = useRef(null);
|
|
54
|
+
const sourceEditorRef = useRef(null);
|
|
55
|
+
const [sourceWidth, setSourceWidth] = useState(42); // % — wide enough for Markdown tools while keeping preview visible
|
|
56
|
+
const [sourceFullscreen, setSourceFullscreen] = useState(false);
|
|
57
|
+
const [sourceFocusMode, setSourceFocusMode] = useState(false);
|
|
58
|
+
const [sourceTypewriterMode, setSourceTypewriterMode] = useState(false);
|
|
59
|
+
const headings = useMemo(() => mappedHeadings ?? collectMarkdownHeadings(text), [mappedHeadings, text]);
|
|
60
|
+
const jumpSourceToLine = useCallback((lineNumber) => {
|
|
61
|
+
const view = sourceEditorRef.current;
|
|
62
|
+
if (!view)
|
|
63
|
+
return;
|
|
64
|
+
selectAndScrollSourceLine(view, lineNumber);
|
|
65
|
+
requestAnimationFrame(() => selectAndScrollSourceLine(view, lineNumber));
|
|
66
|
+
window.setTimeout(() => selectAndScrollSourceLine(view, lineNumber), 80);
|
|
67
|
+
window.setTimeout(() => selectAndScrollSourceLine(view, lineNumber), 250);
|
|
68
|
+
}, []);
|
|
69
|
+
const jumpSourceToRange = useCallback((range) => {
|
|
70
|
+
const jump = () => {
|
|
71
|
+
const view = sourceEditorRef.current;
|
|
72
|
+
if (view)
|
|
73
|
+
selectAndScrollSourceRange(view, range);
|
|
74
|
+
};
|
|
75
|
+
jump();
|
|
76
|
+
requestAnimationFrame(jump);
|
|
77
|
+
window.setTimeout(jump, 80);
|
|
78
|
+
window.setTimeout(jump, 250);
|
|
79
|
+
}, []);
|
|
80
|
+
const drawerProps = {
|
|
81
|
+
headings,
|
|
82
|
+
sourceFocusMode,
|
|
83
|
+
sourceTypewriterMode,
|
|
84
|
+
sourceFullscreen,
|
|
85
|
+
sourceWidth,
|
|
86
|
+
initialViewState: (documentPath ? externalSourceViewStateRef?.current[documentPath] : undefined) ??
|
|
87
|
+
localSourceViewStateRef.current,
|
|
88
|
+
onJumpLine: jumpSourceToLine,
|
|
89
|
+
onToggleFocus: () => setSourceFocusMode((value) => !value),
|
|
90
|
+
onToggleTypewriter: () => setSourceTypewriterMode((value) => !value),
|
|
91
|
+
onToggleFullscreen: () => setSourceFullscreen((value) => !value),
|
|
92
|
+
onViewStateChange: (state) => {
|
|
93
|
+
if (documentPath && externalSourceViewStateRef)
|
|
94
|
+
externalSourceViewStateRef.current[documentPath] = state;
|
|
95
|
+
else
|
|
96
|
+
localSourceViewStateRef.current = state;
|
|
97
|
+
onCaretOffsetChange?.(state.head);
|
|
98
|
+
},
|
|
99
|
+
onEditorReady: (view) => {
|
|
100
|
+
sourceEditorRef.current = view;
|
|
101
|
+
onEditorReady?.(view, jumpSourceToLine, jumpSourceToRange);
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
return {
|
|
105
|
+
sourceWidth,
|
|
106
|
+
setSourceWidth,
|
|
107
|
+
sourceFullscreen,
|
|
108
|
+
sourceEditorRef,
|
|
109
|
+
jumpSourceToLine,
|
|
110
|
+
jumpSourceToRange,
|
|
111
|
+
drawerProps,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
export function MarkdownSplitPane({ text, showSource, readOnly = false, onCloseFind, onChange, onSave, registerSearchProvider, findBar, findMode, onRequestFind, onRequestReplace, documentPath, subscribeEdits, getEditSnapshot, onOpenPath, onChangeView, active = true, registerMarkdownExport, sourceViewStateRef, scrollContainerRef, onScroll, markdownCore, }) {
|
|
115
|
+
const { settings } = useSettings();
|
|
116
|
+
// When the core is supplied, mirror its snapshot into local state
|
|
117
|
+
// so the rest of the component works without further changes. The
|
|
118
|
+
// source editor's own updates route back through the core via the
|
|
119
|
+
// legacy `onChange` path; the core is the source of truth.
|
|
120
|
+
const contextCore = useMarkdownCoreOptional();
|
|
121
|
+
const resolvedCore = markdownCore ?? contextCore;
|
|
122
|
+
const coreSnapshot = useMarkdownCoreSubscription(resolvedCore ?? undefined);
|
|
123
|
+
const liveText = useEditText(subscribeEdits ?? noopSubscribeEdits, getEditSnapshot ?? noopEditSnapshot, documentPath ?? "");
|
|
124
|
+
// Core mode: MarkdownCoreProvider owns external ingress (origin-tagged); no mirror into the core.
|
|
125
|
+
const [legacyText, setLegacyText] = useMirroredEditText(liveText, text);
|
|
126
|
+
// Legacy fallback for non-core mode. In core mode the hot path is
|
|
127
|
+
// direct: CodeMirror feeds update.changes and ProseMirror feeds
|
|
128
|
+
// local block patches without whole-string LCS. This callback is
|
|
129
|
+
// retained only for external reconciliation (bounded diff).
|
|
130
|
+
const commitSurfaceChange = useCallback((next, origin) => {
|
|
131
|
+
if (!resolvedCore) {
|
|
132
|
+
setLegacyText(next);
|
|
133
|
+
onChange(next);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const current = resolvedCore.snapshot();
|
|
137
|
+
if (current.logicalSource === next)
|
|
138
|
+
return;
|
|
139
|
+
// Complete-source callbacks are reconciliation-only. Keep this path
|
|
140
|
+
// bounded and deterministic; local core surfaces feed native ranges.
|
|
141
|
+
const edits = [
|
|
142
|
+
{
|
|
143
|
+
from: 0,
|
|
144
|
+
to: current.logicalSource.length,
|
|
145
|
+
insert: next,
|
|
146
|
+
inheritMode: "preserve",
|
|
147
|
+
},
|
|
148
|
+
];
|
|
149
|
+
resolvedCore.applyCommand({
|
|
150
|
+
edits,
|
|
151
|
+
expectedRevision: current.revision,
|
|
152
|
+
origin,
|
|
153
|
+
label: "Typing",
|
|
154
|
+
coalesceKey: `${origin}:typing`,
|
|
155
|
+
});
|
|
156
|
+
}, [onChange, resolvedCore, setLegacyText]);
|
|
157
|
+
const handleHybridChange = useCallback((next) => commitSurfaceChange(next, "hybrid"), [commitSurfaceChange]);
|
|
158
|
+
const handleSourceChange = useCallback((next) => commitSurfaceChange(next, "source"), [commitSurfaceChange]);
|
|
159
|
+
const renderedText = coreSnapshot?.logicalSource ?? legacyText;
|
|
160
|
+
const mappedHeadings = useMemo(() => coreSnapshot
|
|
161
|
+
? collectSourceMapHeadings(coreSnapshot.logicalSource, coreSnapshot.sourceMap)
|
|
162
|
+
: undefined, [coreSnapshot]);
|
|
163
|
+
const [caretOffset, setCaretOffset] = useState(0);
|
|
164
|
+
const [tableContext, setTableContext] = useState(null);
|
|
165
|
+
const [toolbarState, setToolbarState] = useState({
|
|
166
|
+
headingLevel: null,
|
|
167
|
+
bold: false,
|
|
168
|
+
italic: false,
|
|
169
|
+
strike: false,
|
|
170
|
+
code: false,
|
|
171
|
+
highlight: false,
|
|
172
|
+
});
|
|
173
|
+
const [imageDialogRequest, setImageDialogRequest] = useState(0);
|
|
174
|
+
const hybridEditorRef = useRef(null);
|
|
175
|
+
const selectionRangeRef = useRef({ anchor: 0, head: 0 });
|
|
176
|
+
const captureHybridSelection = useCallback(() => {
|
|
177
|
+
const view = hybridEditorRef.current;
|
|
178
|
+
if (!view || !resolvedCore)
|
|
179
|
+
return undefined;
|
|
180
|
+
// Keyboard focus may leave the browser range in this editor before ProseMirror's
|
|
181
|
+
// selection observer catches up. Reconcile that owned range before formatting.
|
|
182
|
+
const domSelection = view.dom.ownerDocument.getSelection();
|
|
183
|
+
if (domSelection?.anchorNode &&
|
|
184
|
+
domSelection.focusNode &&
|
|
185
|
+
view.dom.contains(domSelection.anchorNode) &&
|
|
186
|
+
view.dom.contains(domSelection.focusNode)) {
|
|
187
|
+
try {
|
|
188
|
+
const anchor = view.posAtDOM(domSelection.anchorNode, domSelection.anchorOffset);
|
|
189
|
+
const head = view.posAtDOM(domSelection.focusNode, domSelection.focusOffset);
|
|
190
|
+
const selection = TextSelection.between(view.state.doc.resolve(anchor), view.state.doc.resolve(head));
|
|
191
|
+
if (!selection.eq(view.state.selection))
|
|
192
|
+
view.dispatch(view.state.tr.setSelection(selection));
|
|
193
|
+
}
|
|
194
|
+
catch {
|
|
195
|
+
// Detached ranges use the editor's last owned selection.
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
const snapshot = resolvedCore.snapshot();
|
|
199
|
+
const hint = postPmSelectionToCore(resolvedCore, view.state.doc, view.state.selection, snapshot);
|
|
200
|
+
if (!hint)
|
|
201
|
+
return undefined;
|
|
202
|
+
const range = { anchor: hint.anchor, head: hint.head };
|
|
203
|
+
selectionRangeRef.current = range;
|
|
204
|
+
return range;
|
|
205
|
+
}, [resolvedCore]);
|
|
206
|
+
useEffect(() => {
|
|
207
|
+
if (!active || !resolvedCore || !registerMarkdownExport)
|
|
208
|
+
return;
|
|
209
|
+
const fallbackTitle = (documentPath?.split(/[\\/]/).pop() ?? "Untitled").replace(/\.(md|markdown)$/i, "");
|
|
210
|
+
const api = {
|
|
211
|
+
documentPath,
|
|
212
|
+
getRevision: () => resolvedCore.snapshot().revision,
|
|
213
|
+
// Export pipeline (DOM render, standalone HTML) is loaded on first export, not on open.
|
|
214
|
+
createSnapshot: async (options) => {
|
|
215
|
+
const { renderMarkdownExport } = await import("@rightkit/markdown-editor/documents/markdown/export/renderMarkdownExport");
|
|
216
|
+
return renderMarkdownExport({
|
|
217
|
+
snapshot: resolvedCore.snapshot(),
|
|
218
|
+
documentPath,
|
|
219
|
+
fallbackTitle,
|
|
220
|
+
theme: options?.theme ?? "current",
|
|
221
|
+
remoteImages: options?.remoteImages ?? "preserve",
|
|
222
|
+
safeHtmlEnabled: settings.markdownSafeHtml,
|
|
223
|
+
});
|
|
224
|
+
},
|
|
225
|
+
};
|
|
226
|
+
registerMarkdownExport(api);
|
|
227
|
+
return () => registerMarkdownExport(null);
|
|
228
|
+
}, [active, documentPath, registerMarkdownExport, resolvedCore, settings.markdownSafeHtml]);
|
|
229
|
+
const pendingSourceRangeRef = useRef(null);
|
|
230
|
+
const { sourceEditorRef, jumpSourceToRange, drawerProps } = useMarkdownSourceDrawer(renderedText, (view, _jumpLine, jumpRange) => {
|
|
231
|
+
if (view && pendingSourceRangeRef.current) {
|
|
232
|
+
const range = pendingSourceRangeRef.current;
|
|
233
|
+
pendingSourceRangeRef.current = null;
|
|
234
|
+
jumpRange(range);
|
|
235
|
+
}
|
|
236
|
+
}, setCaretOffset, mappedHeadings, documentPath, sourceViewStateRef);
|
|
237
|
+
// The active surface owns find: Hybrid when rendered, Source while the drawer is open.
|
|
238
|
+
const hybridProviderRef = useRef(null);
|
|
239
|
+
const sourceProviderRef = useRef(null);
|
|
240
|
+
// Read mode keeps the drawer closed even if a persisted view asks for "source" — the drawer is
|
|
241
|
+
// the editing surface, and Read never exposes one.
|
|
242
|
+
const drawer = showSource && !readOnly;
|
|
243
|
+
useCloseFindOnSurfaceChange(drawer, !!findMode, onCloseFind);
|
|
244
|
+
const showSourceRef = useRef(drawer);
|
|
245
|
+
showSourceRef.current = drawer;
|
|
246
|
+
const publish = useCallback(() => {
|
|
247
|
+
registerSearchProvider(showSourceRef.current ? sourceProviderRef.current : hybridProviderRef.current);
|
|
248
|
+
}, [registerSearchProvider]);
|
|
249
|
+
const registerHybridProvider = useCallback((p) => {
|
|
250
|
+
hybridProviderRef.current = p;
|
|
251
|
+
publish();
|
|
252
|
+
}, [publish]);
|
|
253
|
+
const registerSourceProvider = useCallback((p) => {
|
|
254
|
+
sourceProviderRef.current = p;
|
|
255
|
+
publish();
|
|
256
|
+
}, [publish]);
|
|
257
|
+
useEffect(() => {
|
|
258
|
+
publish();
|
|
259
|
+
}, [publish]); // re-expose the active surface when the drawer toggles
|
|
260
|
+
useEffect(() => () => registerSearchProvider(null), [registerSearchProvider]);
|
|
261
|
+
const openSourceAtRange = useCallback((range) => {
|
|
262
|
+
const from = Math.max(0, Math.min(range.from, renderedText.length));
|
|
263
|
+
const to = Math.max(from, Math.min(range.to, renderedText.length));
|
|
264
|
+
pendingSourceRangeRef.current = { from, to };
|
|
265
|
+
if (!showSourceRef.current) {
|
|
266
|
+
onChangeView?.("source");
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
jumpSourceToRange({ from, to });
|
|
270
|
+
pendingSourceRangeRef.current = null;
|
|
271
|
+
}, [jumpSourceToRange, onChangeView, renderedText]);
|
|
272
|
+
// Read-mode outline/issue jump: the source drawer is unavailable, so select the range inside the
|
|
273
|
+
// hybrid view instead — same "jump to the thing" intent, never an edit surface.
|
|
274
|
+
const revealInHybrid = useCallback((range) => {
|
|
275
|
+
const view = hybridEditorRef.current;
|
|
276
|
+
if (!view || view.isDestroyed || !resolvedCore)
|
|
277
|
+
return;
|
|
278
|
+
const pos = logicalSourceToPmPosition(view.state.doc, range.from, resolvedCore.snapshot());
|
|
279
|
+
const clamped = Math.max(0, Math.min(pos, view.state.doc.content.size));
|
|
280
|
+
try {
|
|
281
|
+
const sel = TextSelection.near(view.state.doc.resolve(clamped));
|
|
282
|
+
view.dispatch(view.state.tr.setSelection(sel).scrollIntoView());
|
|
283
|
+
view.focus();
|
|
284
|
+
}
|
|
285
|
+
catch {
|
|
286
|
+
// Positions inside protected/atom blocks have no text cursor — leave the doc as-is.
|
|
287
|
+
}
|
|
288
|
+
}, [resolvedCore]);
|
|
289
|
+
useEffect(() => {
|
|
290
|
+
publishMarkdownDocumentActivity(documentPath, active ?? true);
|
|
291
|
+
return () => publishMarkdownDocumentActivity(documentPath, false);
|
|
292
|
+
}, [active, documentPath]);
|
|
293
|
+
// Read mode: undo/redo mutate the doc — the listeners stay off entirely (the Edit menu that
|
|
294
|
+
// fires these events is hidden too, so nothing is lost).
|
|
295
|
+
useEffect(() => {
|
|
296
|
+
if (!active || !resolvedCore || readOnly)
|
|
297
|
+
return;
|
|
298
|
+
const onUndo = () => {
|
|
299
|
+
if (!drawer)
|
|
300
|
+
captureHybridSelection();
|
|
301
|
+
resolvedCore.undo();
|
|
302
|
+
};
|
|
303
|
+
const onRedo = () => {
|
|
304
|
+
if (!drawer)
|
|
305
|
+
captureHybridSelection();
|
|
306
|
+
resolvedCore.redo();
|
|
307
|
+
};
|
|
308
|
+
window.addEventListener("viewright:undo", onUndo);
|
|
309
|
+
window.addEventListener("viewright:redo", onRedo);
|
|
310
|
+
return () => {
|
|
311
|
+
window.removeEventListener("viewright:undo", onUndo);
|
|
312
|
+
window.removeEventListener("viewright:redo", onRedo);
|
|
313
|
+
};
|
|
314
|
+
}, [active, resolvedCore, readOnly, drawer, captureHybridSelection]);
|
|
315
|
+
useEffect(() => {
|
|
316
|
+
if (!drawer || !sourceEditorRef.current)
|
|
317
|
+
return;
|
|
318
|
+
if (pendingSourceRangeRef.current) {
|
|
319
|
+
const range = pendingSourceRangeRef.current;
|
|
320
|
+
pendingSourceRangeRef.current = null;
|
|
321
|
+
jumpSourceToRange(range);
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
}, [drawer, jumpSourceToRange, sourceEditorRef.current]);
|
|
325
|
+
const getInsertAt = useCallback(() => {
|
|
326
|
+
return (sourceEditorRef.current?.state.selection.main.head ??
|
|
327
|
+
selectionRangeRef.current.head ??
|
|
328
|
+
caretOffset);
|
|
329
|
+
}, [caretOffset, sourceEditorRef]);
|
|
330
|
+
return (_jsxs("div", { className: "flex h-full min-h-0 min-w-0 bg-ink", children: [_jsx("section", { className: drawer ? "hidden" : "min-h-0 min-w-0 flex-1", "aria-hidden": drawer ? true : undefined, children: resolvedCore ? (_jsx(MarkdownWorkspacePanel, { core: resolvedCore, caretOffset: caretOffset, documentPath: documentPath, safeHtml: settings.markdownSafeHtml, onJumpToRange: readOnly ? revealInHybrid : openSourceAtRange, getInsertAt: getInsertAt, selection: selectionRangeRef.current, getEditorView: () => hybridEditorRef.current, tableContext: tableContext, toolbarState: toolbarState, onInsertImage: () => setImageDialogRequest((request) => request + 1), chromeHidden: drawer, readOnly: readOnly, onToggleSource: () => onChangeView?.("source"), captureSelection: captureHybridSelection, scrollContainerRef: scrollContainerRef, onScroll: onScroll, findBar: findBar, findMode: findMode, onRequestFind: onRequestFind, onRequestReplace: onRequestReplace, children: _jsx(MarkdownEditor, { text: renderedText, onChange: handleHybridChange, onSave: onSave, documentPath: documentPath, onOpenPath: onOpenPath, onSourceJump: readOnly ? revealInHybrid : openSourceAtRange, onCaretOffsetChange: setCaretOffset, onSelectionRangeChange: (selection) => {
|
|
331
|
+
selectionRangeRef.current = selection;
|
|
332
|
+
}, onEditorReady: (view) => {
|
|
333
|
+
hybridEditorRef.current = view;
|
|
334
|
+
}, onTableContextChange: setTableContext, onToolbarStateChange: (next) => setToolbarState((current) => current.headingLevel === next.headingLevel &&
|
|
335
|
+
current.bold === next.bold &&
|
|
336
|
+
current.italic === next.italic &&
|
|
337
|
+
current.strike === next.strike &&
|
|
338
|
+
current.code === next.code &&
|
|
339
|
+
current.highlight === next.highlight
|
|
340
|
+
? current
|
|
341
|
+
: next), registerSearchProvider: registerHybridProvider, core: resolvedCore, mappedHeadings: mappedHeadings, requestImageDialog: imageDialogRequest, surfaceHidden: drawer || !active, active: active, readOnly: readOnly }) })) : (_jsx(MarkdownEditor, { text: renderedText, onChange: handleHybridChange, onSave: onSave, documentPath: documentPath, onOpenPath: onOpenPath, onSourceJump: readOnly ? revealInHybrid : openSourceAtRange, onCaretOffsetChange: setCaretOffset, onSelectionRangeChange: (selection) => {
|
|
342
|
+
selectionRangeRef.current = selection;
|
|
343
|
+
}, onEditorReady: (view) => {
|
|
344
|
+
hybridEditorRef.current = view;
|
|
345
|
+
}, registerSearchProvider: registerHybridProvider, core: resolvedCore, surfaceHidden: !active, active: active, readOnly: readOnly })) }), drawer && (_jsx(MarkdownSourceDrawer, { documentPath: documentPath, text: renderedText, onChange: handleSourceChange, externalHistory: Boolean(resolvedCore), onSave: onSave, registerSourceProvider: registerSourceProvider, findBar: findBar, findMode: findMode, onRequestFind: onRequestFind, onRequestReplace: onRequestReplace, ...drawerProps, sourceFullscreen: true, showFullscreenToggle: false, markdownCore: resolvedCore ?? undefined }))] }));
|
|
346
|
+
}
|
|
347
|
+
export function MarkdownSourceActions({ headings, focusMode, typewriterMode, sourceFullscreen, onJumpLine, onToggleFocus, onToggleTypewriter, onToggleFullscreen, restoreTitle = "Restore markdown split", expandTitle = "Expand markdown source", showFullscreenToggle = true, }) {
|
|
348
|
+
return (_jsxs(_Fragment, { children: [headings.length > 0 && (_jsx(Dropdown, { label: "outline", title: "Jump to heading", minWidth: 72, panelWidth: 220, children: (close) => headings.map((heading) => (_jsx(Item, { onRun: () => {
|
|
349
|
+
onJumpLine(heading.line);
|
|
350
|
+
close();
|
|
351
|
+
}, children: _jsx("span", { style: { paddingLeft: (heading.depth - 1) * 10 }, children: heading.text || `Line ${heading.line}` }) }, `${heading.line}-${heading.text}`))) })), _jsx(Sep, {}), _jsx(TBtn, { label: "focus", title: "Focus current source line", active: focusMode, onRun: onToggleFocus, width: 50 }), _jsx(TBtn, { label: "type", title: "Typewriter source mode", active: typewriterMode, onRun: onToggleTypewriter, width: 42 }), showFullscreenToggle && (_jsx(TBtn, { label: _jsx(FullscreenIcon, { restore: sourceFullscreen }), title: sourceFullscreen ? restoreTitle : expandTitle, onRun: onToggleFullscreen }))] }));
|
|
352
|
+
}
|