mirror of
https://github.com/zadam/trilium.git
synced 2026-05-06 09:36:37 +02:00
refactor(markdown): get rid of DOM queries
This commit is contained in:
@@ -2,6 +2,7 @@ import "./code.css";
|
||||
|
||||
import { default as VanillaCodeMirror, getThemeById } from "@triliumnext/codemirror";
|
||||
import { NoteType } from "@triliumnext/commons";
|
||||
import { RefObject } from "preact";
|
||||
import { useEffect, useRef, useState } from "preact/hooks";
|
||||
|
||||
import appContext, { CommandListenerData } from "../../../components/app_context";
|
||||
@@ -31,6 +32,8 @@ export interface EditableCodeProps extends TypeWidgetProps, Omit<CodeEditorProps
|
||||
/** Invoked after the content of the note has been uploaded to the server, using a spaced update. */
|
||||
dataSaved?: () => void;
|
||||
placeholder?: string;
|
||||
/** Optional external ref to the underlying CodeMirror `EditorView`. Populated once the editor has initialized. */
|
||||
editorRef?: RefObject<VanillaCodeMirror>;
|
||||
}
|
||||
|
||||
export function ReadOnlyCode({ note, viewScope, ntxId, parentComponent }: TypeWidgetProps) {
|
||||
@@ -81,8 +84,9 @@ function formatViewSource(note: FNote, content: string) {
|
||||
return content;
|
||||
}
|
||||
|
||||
export function EditableCode({ note, ntxId, noteContext, debounceUpdate, parentComponent, updateInterval, noteType = "code", onContentChanged, dataSaved, placeholder, ...editorProps }: EditableCodeProps) {
|
||||
const editorRef = useRef<VanillaCodeMirror>(null);
|
||||
export function EditableCode({ note, ntxId, noteContext, debounceUpdate, parentComponent, updateInterval, noteType = "code", onContentChanged, dataSaved, placeholder, editorRef: externalEditorRef, ...editorProps }: EditableCodeProps) {
|
||||
const internalEditorRef = useRef<VanillaCodeMirror>(null);
|
||||
const editorRef = externalEditorRef ?? internalEditorRef;
|
||||
const containerRef = useRef<HTMLPreElement>(null);
|
||||
const [ vimKeymapEnabled ] = useTriliumOptionBool("vimKeymapEnabled");
|
||||
const [ noteTabWidth ] = useNoteLabelInt(note, "tabWidth");
|
||||
|
||||
@@ -3,12 +3,11 @@
|
||||
padding-block: 0.25em;
|
||||
}
|
||||
|
||||
.note-detail-split-preview {
|
||||
.markdown-preview {
|
||||
box-sizing: border-box;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
|
||||
.markdown-preview {
|
||||
padding: 0.5em;
|
||||
}
|
||||
padding: 0.5em;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -15,13 +15,15 @@ export default function Markdown(props: TypeWidgetProps) {
|
||||
const [ content, setContent ] = useState("");
|
||||
const html = useMemo(() => DOMPurify.sanitize(renderWithSourceLines(content), { ADD_ATTR: [ "data-source-line" ] }), [ content ]);
|
||||
const previewRef = useRef<HTMLDivElement>(null);
|
||||
const editorRef = useRef<VanillaCodeMirror>(null);
|
||||
|
||||
useSyncedScrolling(previewRef);
|
||||
useSyncedScrolling(editorRef, previewRef);
|
||||
|
||||
return (
|
||||
<SplitEditor
|
||||
noteType="code"
|
||||
{...props}
|
||||
editorRef={editorRef}
|
||||
onContentChanged={setContent}
|
||||
previewContent={(
|
||||
<div
|
||||
@@ -42,22 +44,20 @@ export default function Markdown(props: TypeWidgetProps) {
|
||||
* preview so the block tagged with that line is at the top — interpolating to
|
||||
* the next block for smoothness.
|
||||
*/
|
||||
function useSyncedScrolling(previewRef: RefObject<HTMLDivElement>) {
|
||||
function useSyncedScrolling(editorRef: RefObject<VanillaCodeMirror>, previewRef: RefObject<HTMLDivElement>) {
|
||||
useEffect(() => {
|
||||
let rafId = 0;
|
||||
let scroller: HTMLElement | null = null;
|
||||
let cmEditor: HTMLElement | null = null;
|
||||
let preview: HTMLElement | null = null;
|
||||
const view = editorRef.current;
|
||||
const preview = previewRef.current;
|
||||
if (!view || !preview) return;
|
||||
|
||||
const scroller = view.scrollDOM;
|
||||
|
||||
function onScroll() {
|
||||
if (!scroller || !cmEditor || !preview) return;
|
||||
const view = VanillaCodeMirror.findFromDOM(cmEditor);
|
||||
if (!view) return;
|
||||
|
||||
if (!view || !preview) return;
|
||||
const topLine = view.state.doc.lineAt(view.lineBlockAtHeight(scroller.scrollTop).from).number;
|
||||
|
||||
const blocks = previewRef.current?.querySelectorAll<HTMLElement>("[data-source-line]");
|
||||
if (!blocks?.length) return;
|
||||
const blocks = preview.querySelectorAll<HTMLElement>("[data-source-line]");
|
||||
if (!blocks.length) return;
|
||||
|
||||
let before: HTMLElement | null = null;
|
||||
let after: HTMLElement | null = null;
|
||||
@@ -81,24 +81,9 @@ function useSyncedScrolling(previewRef: RefObject<HTMLDivElement>) {
|
||||
preview.scrollTop = beforeOffset + (afterOffset - beforeOffset) * ratio;
|
||||
}
|
||||
|
||||
function tryAttach() {
|
||||
const split = previewRef.current?.closest(".note-detail-split");
|
||||
scroller = split?.querySelector<HTMLElement>(".cm-scroller") ?? null;
|
||||
cmEditor = split?.querySelector<HTMLElement>(".cm-editor") ?? null;
|
||||
preview = split?.querySelector<HTMLElement>(".note-detail-split-preview") ?? null;
|
||||
if (!scroller || !cmEditor || !preview) {
|
||||
rafId = requestAnimationFrame(tryAttach);
|
||||
return;
|
||||
}
|
||||
scroller.addEventListener("scroll", onScroll, { passive: true });
|
||||
}
|
||||
tryAttach();
|
||||
|
||||
return () => {
|
||||
cancelAnimationFrame(rafId);
|
||||
scroller?.removeEventListener("scroll", onScroll);
|
||||
};
|
||||
}, [ previewRef ]);
|
||||
scroller.addEventListener("scroll", onScroll, { passive: true });
|
||||
return () => scroller.removeEventListener("scroll", onScroll);
|
||||
}, [ editorRef, previewRef ]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user