mirror of
https://github.com/zadam/trilium.git
synced 2025-10-29 17:26:38 +01:00
chore(react/type_widget): basic editable code
This commit is contained in:
@@ -2,7 +2,7 @@ import { NoteType } from "@triliumnext/commons";
|
||||
import { useNoteContext } from "./react/hooks"
|
||||
import FNote from "../entities/fnote";
|
||||
import protected_session_holder from "../services/protected_session_holder";
|
||||
import { useEffect, useState } from "preact/hooks";
|
||||
import { useEffect, useMemo, useState } from "preact/hooks";
|
||||
import NoteContext from "../components/note_context";
|
||||
import Empty from "./type_widgets/Empty";
|
||||
import { VNode } from "preact";
|
||||
@@ -15,7 +15,9 @@ 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";
|
||||
import { ReadOnlyCode, EditableCode } from "./type_widgets/code/Code";
|
||||
import SpacedUpdate from "../services/spaced_update";
|
||||
import server from "../services/server";
|
||||
|
||||
/**
|
||||
* 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,
|
||||
@@ -74,6 +76,7 @@ function getCorrespondingWidget(noteType: ExtendedNoteType | undefined, props: T
|
||||
case "file": return <File {...props} />
|
||||
case "image": return <Image {...props} />
|
||||
case "readOnlyCode": return <ReadOnlyCode {...props} />
|
||||
case "editableCode": return <EditableCode {...props} />
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,32 +74,6 @@ export default class NoteDetailWidget extends NoteContextAwareWidget {
|
||||
|
||||
this.typeWidgets = {};
|
||||
|
||||
this.spacedUpdate = new SpacedUpdate(async () => {
|
||||
if (!this.noteContext) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { note } = this.noteContext;
|
||||
if (!note) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { noteId } = note;
|
||||
|
||||
const data = await this.getTypeWidget().getData();
|
||||
|
||||
// for read only notes
|
||||
if (data === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
protectedSessionHolder.touchProtectedSessionIfNecessary(note);
|
||||
|
||||
await server.put(`notes/${noteId}/data`, data, this.componentId);
|
||||
|
||||
this.getTypeWidget().dataSaved();
|
||||
});
|
||||
|
||||
appContext.addBeforeUnloadListener(this);
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,8 @@ import Mark from "mark.js";
|
||||
import { DragData } from "../note_tree";
|
||||
import Component from "../../components/component";
|
||||
import toast, { ToastOptions } from "../../services/toast";
|
||||
import protected_session_holder from "../../services/protected_session_holder";
|
||||
import server from "../../services/server";
|
||||
|
||||
export function useTriliumEvent<T extends EventNames>(eventName: T, handler: (data: EventData<T>) => void) {
|
||||
const parentComponent = useContext(ParentComponent);
|
||||
@@ -73,6 +75,29 @@ export function useSpacedUpdate(callback: () => void | Promise<void>, interval =
|
||||
return spacedUpdateRef.current;
|
||||
}
|
||||
|
||||
export function useEditorSpacedUpdate({ note, getData, dataSaved }: {
|
||||
note: FNote,
|
||||
getData: () => Promise<object | undefined> | object | undefined,
|
||||
dataSaved?: () => void
|
||||
}) {
|
||||
const parentComponent = useContext(ParentComponent);
|
||||
const callback = useMemo(() => {
|
||||
return async () => {
|
||||
const data = await getData();
|
||||
|
||||
// for read only notes
|
||||
if (data === undefined) return;
|
||||
|
||||
protected_session_holder.touchProtectedSessionIfNecessary(note);
|
||||
await server.put(`notes/${note.noteId}/data`, data, parentComponent?.componentId);
|
||||
|
||||
dataSaved?.();
|
||||
}
|
||||
}, [ note, getData, dataSaved ])
|
||||
const spacedUpdate = useSpacedUpdate(callback);
|
||||
return spacedUpdate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows a React component to read and write a Trilium option, while also watching for external changes.
|
||||
*
|
||||
|
||||
62
apps/client/src/widgets/type_widgets/code/Code.tsx
Normal file
62
apps/client/src/widgets/type_widgets/code/Code.tsx
Normal file
@@ -0,0 +1,62 @@
|
||||
import { useCallback, useEffect, useRef, useState } from "preact/hooks";
|
||||
import { default as VanillaCodeMirror } from "@triliumnext/codemirror";
|
||||
import { TypeWidgetProps } from "../type_widget";
|
||||
import "./code.css";
|
||||
import CodeMirror from "./CodeMirror";
|
||||
import utils from "../../../services/utils";
|
||||
import { useEditorSpacedUpdate, useNoteBlob } from "../../react/hooks";
|
||||
|
||||
export function ReadOnlyCode({ note, viewScope, ntxId }: 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 (
|
||||
<div className="note-detail-readonly-code note-detail-printable">
|
||||
<CodeMirror
|
||||
className="note-detail-readonly-code-content"
|
||||
content={content}
|
||||
mime={note.mime}
|
||||
readOnly
|
||||
ntxId={ntxId}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function EditableCode({ note, ntxId }: TypeWidgetProps) {
|
||||
const editorRef = useRef<VanillaCodeMirror>(null);
|
||||
const blob = useNoteBlob(note);
|
||||
const spacedUpdate = useEditorSpacedUpdate({
|
||||
note,
|
||||
getData: () => ({ content: editorRef.current?.getText() })
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
spacedUpdate.allowUpdateWithoutChange(() => {
|
||||
const codeEditor = editorRef.current;
|
||||
if (!codeEditor) return;
|
||||
codeEditor.setText(blob?.content ?? "");
|
||||
codeEditor.setMimeType(note.mime);
|
||||
codeEditor.clearHistory();
|
||||
});
|
||||
}, [ blob ]);
|
||||
|
||||
return (
|
||||
<div className="note-detail-code note-detail-printable">
|
||||
<CodeMirror
|
||||
editorRef={editorRef}
|
||||
className="note-detail-code-editor"
|
||||
ntxId={ntxId}
|
||||
onContentChanged={() => {
|
||||
spacedUpdate.scheduleUpdate();
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,18 +1,20 @@
|
||||
import { useEffect, useRef } from "preact/hooks";
|
||||
import { EditorConfig, default as VanillaCodeMirror } from "@triliumnext/codemirror";
|
||||
import { useTriliumEvent, useTriliumOptionBool } from "../../react/hooks";
|
||||
import { useSyncedRef, useTriliumEvent, useTriliumOptionBool } from "../../react/hooks";
|
||||
import { refToJQuerySelector } from "../../react/react_utils";
|
||||
import { RefObject } from "preact";
|
||||
|
||||
interface CodeMirrorProps extends Omit<EditorConfig, "parent"> {
|
||||
content: string;
|
||||
mime: string;
|
||||
className?: string;
|
||||
ntxId: string | null | undefined;
|
||||
editorRef?: RefObject<VanillaCodeMirror>;
|
||||
}
|
||||
|
||||
export default function CodeMirror({ className, content, mime, ntxId, ...extraOpts }: CodeMirrorProps) {
|
||||
export default function CodeMirror({ className, content, mime, ntxId, editorRef: externalEditorRef, ...extraOpts }: CodeMirrorProps) {
|
||||
const parentRef = useRef<HTMLPreElement>(null);
|
||||
const codeEditorRef = useRef<VanillaCodeMirror>(null);
|
||||
const codeEditorRef = useRef<VanillaCodeMirror>();
|
||||
const [ codeLineWrapEnabled ] = useTriliumOptionBool("codeLineWrapEnabled");
|
||||
const initialized = $.Deferred();
|
||||
|
||||
@@ -39,6 +41,9 @@ export default function CodeMirror({ className, content, mime, ntxId, ...extraOp
|
||||
...extraOpts
|
||||
});
|
||||
codeEditorRef.current = codeEditor;
|
||||
if (externalEditorRef) {
|
||||
externalEditorRef.current = codeEditor;
|
||||
}
|
||||
initialized.resolve();
|
||||
|
||||
return () => codeEditor.destroy();
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
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, ntxId }: 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 (
|
||||
<div class="note-detail-readonly-code note-detail-printable">
|
||||
<CodeMirror
|
||||
className="note-detail-readonly-code-content"
|
||||
content={content}
|
||||
mime={note.mime}
|
||||
readOnly
|
||||
ntxId={ntxId}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,4 +1,18 @@
|
||||
/* #region Read-only code */
|
||||
.note-detail-readonly-code {
|
||||
min-height: 50px;
|
||||
position: relative;
|
||||
}
|
||||
/* #endregion */
|
||||
|
||||
/* #region Editable code */
|
||||
.note-detail-code {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.note-detail-code-editor {
|
||||
min-height: 50px;
|
||||
height: 100%;
|
||||
}
|
||||
/* #endregion */
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import FNote from "../../entities/fnote";
|
||||
import { ViewScope } from "../../services/link";
|
||||
import SpacedUpdate from "../../services/spaced_update";
|
||||
|
||||
export interface TypeWidgetProps {
|
||||
note: FNote;
|
||||
|
||||
@@ -10,21 +10,7 @@ import { hasTouchBar } from "../../services/utils.js";
|
||||
import type { EditorConfig } from "@triliumnext/codemirror";
|
||||
|
||||
const TPL = /*html*/`
|
||||
<div class="note-detail-code note-detail-printable">
|
||||
<style>
|
||||
.note-detail-code {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.note-detail-code-editor {
|
||||
min-height: 50px;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="note-detail-code-editor"></div>
|
||||
</div>`;
|
||||
`;
|
||||
|
||||
export default class EditableCodeTypeWidget extends AbstractCodeTypeWidget {
|
||||
|
||||
@@ -60,8 +46,6 @@ export default class EditableCodeTypeWidget extends AbstractCodeTypeWidget {
|
||||
if (this.debounceUpdate) {
|
||||
this.spacedUpdate.resetUpdateTimer();
|
||||
}
|
||||
|
||||
this.spacedUpdate.scheduleUpdate();
|
||||
},
|
||||
tabIndex: 300
|
||||
}
|
||||
@@ -81,12 +65,6 @@ export default class EditableCodeTypeWidget extends AbstractCodeTypeWidget {
|
||||
}
|
||||
}
|
||||
|
||||
getData() {
|
||||
return {
|
||||
content: this.codeEditor.getText()
|
||||
};
|
||||
}
|
||||
|
||||
buildTouchBarCommand({ TouchBar, buildIcon }: CommandListenerData<"buildTouchBar">) {
|
||||
const items: TouchBarItem[] = [];
|
||||
const note = this.note;
|
||||
|
||||
Reference in New Issue
Block a user