diff --git a/apps/client/src/widgets/type_widgets/code/Markdown.tsx b/apps/client/src/widgets/type_widgets/code/Markdown.tsx index beba918323..46d0d7f214 100644 --- a/apps/client/src/widgets/type_widgets/code/Markdown.tsx +++ b/apps/client/src/widgets/type_widgets/code/Markdown.tsx @@ -1,9 +1,9 @@ import "./Markdown.css"; +import DOMPurify from "dompurify"; import { Marked } from "marked"; -import { useMemo, useState } from "preact/hooks"; +import { useEffect, useMemo, useRef, useState } from "preact/hooks"; -import { SanitizedHtml } from "../../react/RawHtml"; import SplitEditor from "../helpers/SplitEditor"; import { TypeWidgetProps } from "../type_widget"; @@ -11,14 +11,40 @@ const marked = new Marked({ breaks: true, gfm: true }); export default function Markdown(props: TypeWidgetProps) { const [ content, setContent ] = useState(""); - const html = useMemo(() => marked.parse(content) as string, [ content ]); + const html = useMemo(() => DOMPurify.sanitize(marked.parse(content) as string), [ content ]); + const previewRef = useRef(null); + + useEffect(() => { + const split = previewRef.current?.closest(".note-detail-split"); + const preview = split?.querySelector(".note-detail-split-preview"); + const editor = split?.querySelector(".cm-scroller"); + if (!preview || !editor) return; + + function onScroll() { + if (!preview || !editor) return; + const editorMax = editor.scrollHeight - editor.clientHeight; + const previewMax = preview.scrollHeight - preview.clientHeight; + if (editorMax <= 0 || previewMax <= 0) return; + preview.scrollTop = (editor.scrollTop / editorMax) * previewMax; + } + + editor.addEventListener("scroll", onScroll, { passive: true }); + return () => editor.removeEventListener("scroll", onScroll); + }, [ html ]); return ( } + previewContent={( +
+ )} /> ); }