refactor(react/type_widget): separate Trilium-specific implementation

This commit is contained in:
Elian Doran 2025-09-20 11:22:48 +03:00
parent 43dcdf8925
commit f496caa92c
No known key found for this signature in database
2 changed files with 54 additions and 50 deletions

View File

@ -1,15 +1,16 @@
import { useEffect, useRef, useState } from "preact/hooks"; import { useEffect, useRef, useState } from "preact/hooks";
import { default as VanillaCodeMirror } from "@triliumnext/codemirror"; import { getThemeById, default as VanillaCodeMirror } from "@triliumnext/codemirror";
import { TypeWidgetProps } from "../type_widget"; import { TypeWidgetProps } from "../type_widget";
import "./code.css"; import "./code.css";
import CodeMirror, { CodeMirrorProps } from "./CodeMirror"; import CodeMirror, { CodeMirrorProps } from "./CodeMirror";
import utils from "../../../services/utils"; import utils from "../../../services/utils";
import { useEditorSpacedUpdate, useNoteBlob, useSyncedRef, useTriliumOptionBool } from "../../react/hooks"; import { useEditorSpacedUpdate, useNoteBlob, useSyncedRef, useTriliumEvent, useTriliumOption, useTriliumOptionBool } from "../../react/hooks";
import { t } from "../../../services/i18n"; import { t } from "../../../services/i18n";
import appContext from "../../../components/app_context"; import appContext from "../../../components/app_context";
import TouchBar, { TouchBarButton } from "../../react/TouchBar"; import TouchBar, { TouchBarButton } from "../../react/TouchBar";
import keyboard_actions from "../../../services/keyboard_actions"; import keyboard_actions from "../../../services/keyboard_actions";
import { refToJQuerySelector } from "../../react/react_utils"; import { refToJQuerySelector } from "../../react/react_utils";
import { CODE_THEME_DEFAULT_PREFIX as DEFAULT_PREFIX } from "../constants";
export function ReadOnlyCode({ note, viewScope, ntxId, parentComponent }: TypeWidgetProps) { export function ReadOnlyCode({ note, viewScope, ntxId, parentComponent }: TypeWidgetProps) {
const [ content, setContent ] = useState(""); const [ content, setContent ] = useState("");
@ -24,12 +25,11 @@ export function ReadOnlyCode({ note, viewScope, ntxId, parentComponent }: TypeWi
return ( return (
<div className="note-detail-readonly-code note-detail-printable"> <div className="note-detail-readonly-code note-detail-printable">
<CodeEditor <CodeEditor
note={note} parentComponent={parentComponent} ntxId={ntxId} note={note} parentComponent={parentComponent}
className="note-detail-readonly-code-content" className="note-detail-readonly-code-content"
content={content} content={content}
mime={note.mime} mime={note.mime}
readOnly readOnly
ntxId={ntxId}
/> />
</div> </div>
) )
@ -63,10 +63,9 @@ export function EditableCode({ note, ntxId, debounceUpdate, parentComponent }: T
return ( return (
<div className="note-detail-code note-detail-printable"> <div className="note-detail-code note-detail-printable">
<CodeEditor <CodeEditor
note={note} parentComponent={parentComponent} ntxId={ntxId} note={note} parentComponent={parentComponent}
editorRef={editorRef} containerRef={containerRef} editorRef={editorRef} containerRef={containerRef}
className="note-detail-code-editor" className="note-detail-code-editor"
ntxId={ntxId}
placeholder={t("editable_code.placeholder")} placeholder={t("editable_code.placeholder")}
vimKeybindings={vimKeymapEnabled} vimKeybindings={vimKeymapEnabled}
tabIndex={300} tabIndex={300}
@ -87,8 +86,12 @@ export function EditableCode({ note, ntxId, debounceUpdate, parentComponent }: T
) )
} }
function CodeEditor({ note, parentComponent, containerRef: externalContainerRef, ...editorProps }: Omit<CodeMirrorProps, "onThemeChange"> & Pick<TypeWidgetProps, "note" | "parentComponent">) { function CodeEditor({ note, parentComponent, ntxId, containerRef: externalContainerRef, editorRef: externalEditorRef, ...editorProps }: Omit<CodeMirrorProps, "onThemeChange" | "lineWrapping"> & Pick<TypeWidgetProps, "note" | "parentComponent" | "ntxId">) {
const codeEditorRef = useRef<VanillaCodeMirror>(null);
const containerRef = useSyncedRef(externalContainerRef); const containerRef = useSyncedRef(externalContainerRef);
const initialized = useRef($.Deferred());
const [ codeLineWrapEnabled ] = useTriliumOptionBool("codeLineWrapEnabled");
const [ codeNoteTheme ] = useTriliumOption("codeNoteTheme");
// React to background color. // React to background color.
const [ backgroundColor, setBackgroundColor ] = useState<string>(); const [ backgroundColor, setBackgroundColor ] = useState<string>();
@ -100,14 +103,47 @@ function CodeEditor({ note, parentComponent, containerRef: externalContainerRef,
}; };
}, [ backgroundColor ]); }, [ backgroundColor ]);
// React to theme changes.
useEffect(() => {
if (codeEditorRef.current && codeNoteTheme.startsWith(DEFAULT_PREFIX)) {
const theme = getThemeById(codeNoteTheme.substring(DEFAULT_PREFIX.length));
if (theme) {
codeEditorRef.current.setTheme(theme).then(() => {
if (note?.mime === "text/x-sqlite;schema=trilium") return;
const editor = containerRef.current?.querySelector(".cm-editor");
if (!editor) return;
const style = window.getComputedStyle(editor);
setBackgroundColor(style.backgroundColor);
});
}
}
}, [ codeEditorRef, codeNoteTheme ]);
useTriliumEvent("executeWithCodeEditor", async ({ resolve, ntxId: eventNtxId }) => {
if (eventNtxId !== ntxId) return;
await initialized.current.promise();
resolve(codeEditorRef.current!);
});
useTriliumEvent("executeWithContentElement", async ({ resolve, ntxId: eventNtxId}) => {
if (eventNtxId !== ntxId) return;
await initialized.current.promise();
resolve(refToJQuerySelector(containerRef));
});
return <CodeMirror return <CodeMirror
{...editorProps} {...editorProps}
editorRef={codeEditorRef}
containerRef={containerRef} containerRef={containerRef}
onThemeChange={note?.mime !== "text/x-sqlite;schema=trilium" ? () => { lineWrapping={codeLineWrapEnabled}
const editor = containerRef.current?.querySelector(".cm-editor"); onInitialized={() => {
if (!editor) return; if (externalContainerRef && containerRef.current) {
const style = window.getComputedStyle(editor); externalContainerRef.current = containerRef.current;
setBackgroundColor(style.backgroundColor); }
} : undefined} if (externalEditorRef && codeEditorRef.current) {
externalEditorRef.current = codeEditorRef.current;
}
initialized.current.resolve();
}}
/> />
} }

View File

@ -1,39 +1,20 @@
import { useEffect, useRef } from "preact/hooks"; import { useEffect, useRef } from "preact/hooks";
import { EditorConfig, getThemeById, default as VanillaCodeMirror } from "@triliumnext/codemirror"; import { EditorConfig, default as VanillaCodeMirror } from "@triliumnext/codemirror";
import { useSyncedRef, useTriliumEvent, useTriliumOption, useTriliumOptionBool } from "../../react/hooks"; import { useSyncedRef } from "../../react/hooks";
import { refToJQuerySelector } from "../../react/react_utils";
import { RefObject } from "preact"; import { RefObject } from "preact";
import { CODE_THEME_DEFAULT_PREFIX as DEFAULT_PREFIX } from "../constants";
export interface CodeMirrorProps extends Omit<EditorConfig, "parent"> { export interface CodeMirrorProps extends Omit<EditorConfig, "parent"> {
content: string; content: string;
mime: string; mime: string;
className?: string; className?: string;
ntxId: string | null | undefined;
editorRef?: RefObject<VanillaCodeMirror>; editorRef?: RefObject<VanillaCodeMirror>;
containerRef?: RefObject<HTMLPreElement>; containerRef?: RefObject<HTMLPreElement>;
onThemeChange?: () => void; onInitialized?: () => void;
} }
export default function CodeMirror({ className, content, mime, ntxId, editorRef: externalEditorRef, containerRef: externalContainerRef, onThemeChange, ...extraOpts }: CodeMirrorProps) { export default function CodeMirror({ className, content, mime, editorRef: externalEditorRef, containerRef: externalContainerRef, onInitialized, ...extraOpts }: CodeMirrorProps) {
const parentRef = useSyncedRef(externalContainerRef); const parentRef = useSyncedRef(externalContainerRef);
const codeEditorRef = useRef<VanillaCodeMirror>(); const codeEditorRef = useRef<VanillaCodeMirror>();
const [ codeLineWrapEnabled ] = useTriliumOptionBool("codeLineWrapEnabled");
const [ codeNoteTheme ] = useTriliumOption("codeNoteTheme");
const initialized = $.Deferred();
// Integration within Trilium's event system.
useTriliumEvent("executeWithCodeEditor", async ({ resolve, ntxId: eventNtxId }) => {
if (eventNtxId !== ntxId) return;
await initialized.promise();
resolve(codeEditorRef.current!);
});
useTriliumEvent("executeWithContentElement", async ({ resolve, ntxId: eventNtxId}) => {
if (eventNtxId !== ntxId) return;
await initialized.promise();
resolve(refToJQuerySelector(parentRef));
});
// Create CodeMirror instance. // Create CodeMirror instance.
useEffect(() => { useEffect(() => {
@ -41,30 +22,17 @@ export default function CodeMirror({ className, content, mime, ntxId, editorRef:
const codeEditor = new VanillaCodeMirror({ const codeEditor = new VanillaCodeMirror({
parent: parentRef.current, parent: parentRef.current,
lineWrapping: codeLineWrapEnabled,
...extraOpts ...extraOpts
}); });
codeEditorRef.current = codeEditor; codeEditorRef.current = codeEditor;
if (externalEditorRef) { if (externalEditorRef) {
externalEditorRef.current = codeEditor; externalEditorRef.current = codeEditor;
} }
initialized.resolve(); onInitialized?.();
return () => codeEditor.destroy(); return () => codeEditor.destroy();
}, []); }, []);
// React to theme changes.
useEffect(() => {
if (codeEditorRef.current && codeNoteTheme.startsWith(DEFAULT_PREFIX)) {
const theme = getThemeById(codeNoteTheme.substring(DEFAULT_PREFIX.length));
if (theme) {
codeEditorRef.current.setTheme(theme).then(() => {
onThemeChange?.();
});
}
}
}, [ codeEditorRef, codeNoteTheme ]);
// React to text changes. // React to text changes.
useEffect(() => { useEffect(() => {
const codeEditor = codeEditorRef.current; const codeEditor = codeEditorRef.current;