mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-23 09:42:53 +00:00
The file tab stats the file and mounts its panel without reading it. Every panel, built-in or plugin, gets a PluginFile handle: readText, readBytes, url, version-checked writeText/writeBytes (PluginFileConflictError unless force), and watch. useFileText, useFileBytes and useFileUrl read through it, and Loading joins @getpaseo/plugin/client/ui. Panels own their loading state. The host document pipeline, the explorer preview cache and the preview lifecycle are deleted. The daemon classifies a file version from a bounded sniff and writes base64 content as bytes behind fileByteWrites. A deleted file keeps its panel mounted; a read confirms the deletion first, so a remove-and-rewrite is not reported as one. Native Source decodes with builtins and highlights in idle-time slices, the lines around the opening line first, so a large file no longer blocks the JS thread.
76 lines
2.7 KiB
TypeScript
76 lines
2.7 KiB
TypeScript
import type { PluginFilePanelProps } from "@getpaseo/plugin/client";
|
|
import type { EditorView, EditorViewConfig } from "@codemirror/view";
|
|
import { Platform } from "react-native";
|
|
|
|
export interface MarkdownEditorHandle {
|
|
/** Replaces the editor's text without reporting it as an edit. */
|
|
setText(text: string): void;
|
|
destroy(): void;
|
|
}
|
|
|
|
/** DOM code stays here; native never evaluates the editor dependencies. */
|
|
export function mountEditor(
|
|
parent: unknown,
|
|
text: string,
|
|
theme: PluginFilePanelProps["theme"],
|
|
onChange: (text: string) => void,
|
|
): MarkdownEditorHandle | null {
|
|
if (Platform.OS !== "web" || !parent) return null;
|
|
const { EditorState } = require("@codemirror/state") as typeof import("@codemirror/state");
|
|
const { EditorView, keymap, lineNumbers } =
|
|
require("@codemirror/view") as typeof import("@codemirror/view");
|
|
const { defaultKeymap, history, historyKeymap } =
|
|
require("@codemirror/commands") as typeof import("@codemirror/commands");
|
|
const { markdown } =
|
|
require("@codemirror/lang-markdown") as typeof import("@codemirror/lang-markdown");
|
|
const { syntaxHighlighting, defaultHighlightStyle } =
|
|
require("@codemirror/language") as typeof import("@codemirror/language");
|
|
let applyingHostText = false;
|
|
const view: EditorView = new EditorView({
|
|
parent: parent as EditorViewConfig["parent"],
|
|
state: EditorState.create({
|
|
doc: text,
|
|
extensions: [
|
|
markdown(),
|
|
syntaxHighlighting(defaultHighlightStyle),
|
|
lineNumbers(),
|
|
history(),
|
|
keymap.of([...defaultKeymap, ...historyKeymap]),
|
|
EditorView.lineWrapping,
|
|
EditorView.contentAttributes.of({ "aria-label": "Rich Markdown editor" }),
|
|
EditorView.theme({
|
|
"&": {
|
|
height: "100%",
|
|
color: theme.colors.foreground,
|
|
backgroundColor: theme.colors.surface0,
|
|
},
|
|
".cm-scroller": { overflow: "auto" },
|
|
".cm-content": { padding: "16px", caretColor: theme.colors.foreground },
|
|
".cm-gutters": {
|
|
backgroundColor: theme.colors.surface0,
|
|
color: theme.colors.foregroundMuted,
|
|
border: "none",
|
|
},
|
|
}),
|
|
EditorView.updateListener.of((update) => {
|
|
if (update.docChanged && !applyingHostText) onChange(update.state.doc.toString());
|
|
}),
|
|
],
|
|
}),
|
|
});
|
|
return {
|
|
setText(next) {
|
|
if (view.state.doc.toString() === next) return;
|
|
applyingHostText = true;
|
|
try {
|
|
view.dispatch({ changes: { from: 0, to: view.state.doc.length, insert: next } });
|
|
} finally {
|
|
applyingHostText = false;
|
|
}
|
|
},
|
|
destroy() {
|
|
view.destroy();
|
|
},
|
|
};
|
|
}
|