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;