diff --git a/apps/client/src/widgets/NoteDetail.tsx b/apps/client/src/widgets/NoteDetail.tsx index 06270a59a..533ee2f9b 100644 --- a/apps/client/src/widgets/NoteDetail.tsx +++ b/apps/client/src/widgets/NoteDetail.tsx @@ -15,6 +15,7 @@ import WebView from "./type_widgets/WebView"; import "./NoteDetail.css"; import File from "./type_widgets/File"; import Image from "./type_widgets/Image"; +import ReadOnlyCode from "./type_widgets/code/ReadOnlyCode"; /** * A `NoteType` altered by the note detail widget, taking into consideration whether the note is editable or not and adding special note types such as an empty one, @@ -72,6 +73,7 @@ function getCorrespondingWidget(noteType: ExtendedNoteType | undefined, props: T case "webView": return case "file": return case "image": return + case "readOnlyCode": return default: break; } } diff --git a/apps/client/src/widgets/type_widgets/code/CodeMirror.tsx b/apps/client/src/widgets/type_widgets/code/CodeMirror.tsx new file mode 100644 index 000000000..cf2d88b0f --- /dev/null +++ b/apps/client/src/widgets/type_widgets/code/CodeMirror.tsx @@ -0,0 +1,39 @@ +import { useEffect, useRef } from "preact/hooks"; +import { EditorConfig, default as VanillaCodeMirror } from "@triliumnext/codemirror"; +import { useTriliumOptionBool } from "../../react/hooks"; + +interface CodeMirrorProps extends Omit { + content: string; + mime: string; + className?: string; +} + +export default function CodeMirror({ className, content, mime, ...extraOpts }: CodeMirrorProps) { + const parentRef = useRef(null); + const codeEditorRef = useRef(null); + const [ codeLineWrapEnabled ] = useTriliumOptionBool("codeLineWrapEnabled"); + + useEffect(() => { + if (!parentRef.current) return; + + const codeEditor = new VanillaCodeMirror({ + parent: parentRef.current, + lineWrapping: codeLineWrapEnabled, + ...extraOpts + }); + codeEditorRef.current = codeEditor; + + return () => codeEditor.destroy(); + }, []); + + useEffect(() => { + const codeEditor = codeEditorRef.current; + codeEditor?.setText(content ?? ""); + codeEditor?.setMimeType(mime); + codeEditor?.clearHistory(); + }, [content]); + + return ( +
+    )
+}
diff --git a/apps/client/src/widgets/type_widgets/code/ReadOnlyCode.tsx b/apps/client/src/widgets/type_widgets/code/ReadOnlyCode.tsx
new file mode 100644
index 000000000..be9fc1ba0
--- /dev/null
+++ b/apps/client/src/widgets/type_widgets/code/ReadOnlyCode.tsx
@@ -0,0 +1,28 @@
+import { useEffect, useState } from "preact/hooks";
+import { TypeWidgetProps } from "../type_widget";
+import "./code.css";
+import CodeMirror from "./CodeMirror";
+import utils from "../../../services/utils";
+import { useNoteBlob } from "../../react/hooks";
+
+export default function ReadOnlyCode({ note, viewScope }: TypeWidgetProps) {
+    const [ content, setContent ] = useState("");
+    const blob = useNoteBlob(note);
+
+    useEffect(() => {
+        if (!blob) return;
+        const isFormattable = note.type === "text" && viewScope?.viewMode === "source";
+        setContent(isFormattable ? utils.formatHtml(blob.content) : blob.content);
+    }, [ blob ]);
+
+    return (
+        
+ +
+ ) +} diff --git a/apps/client/src/widgets/type_widgets/code/code.css b/apps/client/src/widgets/type_widgets/code/code.css new file mode 100644 index 000000000..1af75176a --- /dev/null +++ b/apps/client/src/widgets/type_widgets/code/code.css @@ -0,0 +1,4 @@ +.note-detail-readonly-code { + min-height: 50px; + position: relative; +} \ No newline at end of file diff --git a/apps/client/src/widgets/type_widgets_old/abstract_code_type_widget.ts b/apps/client/src/widgets/type_widgets_old/abstract_code_type_widget.ts index 821926afb..b2fd8c8b6 100644 --- a/apps/client/src/widgets/type_widgets_old/abstract_code_type_widget.ts +++ b/apps/client/src/widgets/type_widgets_old/abstract_code_type_widget.ts @@ -29,12 +29,6 @@ export default class AbstractCodeTypeWidget extends TypeWidget { } async #initEditor() { - this.codeEditor = new CodeMirror({ - parent: this.$editor[0], - lineWrapping: options.is("codeLineWrapEnabled"), - ...this.getExtraOpts() - }); - // Load the theme. const themeId = options.get("codeNoteTheme"); if (themeId?.startsWith(DEFAULT_PREFIX)) { @@ -65,18 +59,6 @@ export default class AbstractCodeTypeWidget extends TypeWidget { // Do nothing by default. } - /** - * Must be called by the derived classes in `#doRefresh(note)` in order to react to changes. - * - * @param the note that was changed. - * @param new content of the note. - */ - _update(note: { mime: string }, content: string) { - this.codeEditor.setText(content); - this.codeEditor.setMimeType(note.mime); - this.codeEditor.clearHistory(); - } - show() { this.$widget.show(); this.updateBackgroundColor(); diff --git a/apps/client/src/widgets/type_widgets_old/read_only_code.ts b/apps/client/src/widgets/type_widgets_old/read_only_code.ts index cdae4565e..6c9aca73a 100644 --- a/apps/client/src/widgets/type_widgets_old/read_only_code.ts +++ b/apps/client/src/widgets/type_widgets_old/read_only_code.ts @@ -4,48 +4,18 @@ import AbstractCodeTypeWidget from "./abstract_code_type_widget.js"; import utils from "../../services/utils.js"; const TPL = /*html*/` -
- - -

-
`; +`; export default class ReadOnlyCodeTypeWidget extends AbstractCodeTypeWidget { - static getType() { - return "readOnlyCode"; - } - - doRender() { - this.$widget = $(TPL); - this.contentSized(); - this.$editor = this.$widget.find(".note-detail-readonly-code-content"); - - super.doRender(); - } - async doRefresh(note: FNote) { const blob = await this.note?.getBlob(); if (!blob) return; - const isFormattable = note.type === "text" && this.noteContext?.viewScope?.viewMode === "source"; - const content = isFormattable ? utils.formatHtml(blob.content) : blob.content; - this._update(note, content); this.show(); } - getExtraOpts() { - return { - readOnly: true - }; - } - async executeWithContentElementEvent({ resolve, ntxId }: EventData<"executeWithContentElement">) { if (!this.isNoteContext(ntxId)) { return;