diff --git a/apps/client/src/widgets/NoteDetail.tsx b/apps/client/src/widgets/NoteDetail.tsx index 65f1abaad..7e501b20b 100644 --- a/apps/client/src/widgets/NoteDetail.tsx +++ b/apps/client/src/widgets/NoteDetail.tsx @@ -1,4 +1,4 @@ -import { useNoteContext, useTriliumEvent } from "./react/hooks" +import { useNoteContext, useTriliumEvent, useTriliumEvents } from "./react/hooks" import FNote from "../entities/fnote"; import protected_session_holder from "../services/protected_session_holder"; import { useEffect, useRef, useState } from "preact/hooks"; @@ -9,6 +9,7 @@ import "./NoteDetail.css"; import attributes from "../services/attributes"; import { ExtendedNoteType, TYPE_MAPPINGS } from "./note_types"; import { dynamicRequire, isMobile } from "../services/utils"; +import { ReactWrappedWidget } from "./basic_widget"; /** * The note detail is in charge of rendering the content of a note, by determining its type (e.g. text, code) and using the appropriate view widget. diff --git a/apps/client/src/widgets/note_detail.ts.bak b/apps/client/src/widgets/note_detail.ts.bak index abbbbeb1e..993181547 100644 --- a/apps/client/src/widgets/note_detail.ts.bak +++ b/apps/client/src/widgets/note_detail.ts.bak @@ -13,18 +13,6 @@ export default class NoteDetailWidget extends NoteContextAwareWidget { appContext.addBeforeUnloadListener(this); } - async beforeNoteSwitchEvent({ noteContext }: EventData<"beforeNoteSwitch">) { - if (this.isNoteContext(noteContext.ntxId)) { - await this.spacedUpdate.updateNowIfNecessary(); - } - } - - async beforeNoteContextRemoveEvent({ ntxIds }: EventData<"beforeNoteContextRemove">) { - if (this.isNoteContext(ntxIds)) { - await this.spacedUpdate.updateNowIfNecessary(); - } - } - async runActiveNoteCommand(params: CommandListenerData<"runActiveNote">) { if (this.isNoteContext(params.ntxId)) { // make sure that script is saved before running it #4028 diff --git a/apps/client/src/widgets/react/hooks.tsx b/apps/client/src/widgets/react/hooks.tsx index 24400a584..daf4c84be 100644 --- a/apps/client/src/widgets/react/hooks.tsx +++ b/apps/client/src/widgets/react/hooks.tsx @@ -23,6 +23,7 @@ import protected_session_holder from "../../services/protected_session_holder"; import server from "../../services/server"; import { removeIndividualBinding } from "../../services/shortcuts"; import { ViewScope } from "../../services/link"; +import { VirtualConsolePrinter } from "happy-dom"; export function useTriliumEvent(eventName: T, handler: (data: EventData) => void) { const parentComponent = useContext(ParentComponent); @@ -77,8 +78,9 @@ export function useSpacedUpdate(callback: () => void | Promise, interval = return spacedUpdateRef.current; } -export function useEditorSpacedUpdate({ note, getData, onContentChange, dataSaved, updateInterval }: { +export function useEditorSpacedUpdate({ note, noteContext, getData, onContentChange, dataSaved, updateInterval }: { note: FNote, + noteContext: NoteContext | null | undefined, getData: () => Promise | object | undefined, onContentChange: (newContent: string) => void, dataSaved?: () => void, @@ -114,6 +116,18 @@ export function useEditorSpacedUpdate({ note, getData, onContentChange, dataSave spacedUpdate.setUpdateInterval(updateInterval); }, [ updateInterval ]); + // Save if needed upon switching tabs. + useTriliumEvent("beforeNoteSwitch", async ({ noteContext: eventNoteContext }) => { + if (eventNoteContext.ntxId !== noteContext?.ntxId) return; + await spacedUpdate.updateNowIfNecessary(); + }); + + // Save if needed upon tab closing. + useTriliumEvent("beforeNoteContextRemove", async ({ ntxIds }) => { + if (!noteContext?.ntxId || !ntxIds.includes(noteContext.ntxId)) return; + await spacedUpdate.updateNowIfNecessary(); + }) + return spacedUpdate; } diff --git a/apps/client/src/widgets/type_widgets/AiChat.tsx b/apps/client/src/widgets/type_widgets/AiChat.tsx index 97e9f9eb5..5c0df6681 100644 --- a/apps/client/src/widgets/type_widgets/AiChat.tsx +++ b/apps/client/src/widgets/type_widgets/AiChat.tsx @@ -7,6 +7,7 @@ export default function AiChat({ note, noteContext }: TypeWidgetProps) { const dataRef = useRef(); const spacedUpdate = useEditorSpacedUpdate({ note, + noteContext, getData: async () => ({ content: JSON.stringify(dataRef.current) }), diff --git a/apps/client/src/widgets/type_widgets/Canvas.tsx b/apps/client/src/widgets/type_widgets/Canvas.tsx index bb3b8ecff..c0808dfbf 100644 --- a/apps/client/src/widgets/type_widgets/Canvas.tsx +++ b/apps/client/src/widgets/type_widgets/Canvas.tsx @@ -11,6 +11,7 @@ import { RefObject } from "preact"; import server from "../../services/server"; import { ExcalidrawElement, NonDeletedExcalidrawElement } from "@excalidraw/excalidraw/element/types"; import { goToLinkExt } from "../../services/link"; +import NoteContext from "../../components/note_context"; // currently required by excalidraw, in order to allows self-hosting fonts locally. // this avoids making excalidraw load the fonts from an external CDN. @@ -27,14 +28,14 @@ interface CanvasContent { appState: Partial; } -export default function Canvas({ note }: TypeWidgetProps) { +export default function Canvas({ note, noteContext }: TypeWidgetProps) { const apiRef = useRef(null); const [ isReadOnly ] = useNoteLabelBoolean(note, "readOnly"); const themeStyle = useMemo(() => { const documentStyle = window.getComputedStyle(document.documentElement); return documentStyle.getPropertyValue("--theme-style")?.trim() as AppState["theme"]; }, []); - const persistence = usePersistence(note, apiRef, themeStyle, isReadOnly); + const persistence = usePersistence(note, noteContext, apiRef, themeStyle, isReadOnly); /** Use excalidraw's native zoom instead of the global zoom. */ const onWheel = useCallback((e: MouseEvent) => { @@ -85,7 +86,7 @@ export default function Canvas({ note }: TypeWidgetProps) { ) } -function usePersistence(note: FNote, apiRef: RefObject, theme: AppState["theme"], isReadOnly: boolean): Partial { +function usePersistence(note: FNote, noteContext: NoteContext, apiRef: RefObject, theme: AppState["theme"], isReadOnly: boolean): Partial { const libraryChanged = useRef(false); /** @@ -104,6 +105,7 @@ function usePersistence(note: FNote, apiRef: RefObject, const spacedUpdate = useEditorSpacedUpdate({ note, + noteContext, onContentChange(newContent) { const api = apiRef.current; if (!api) return; diff --git a/apps/client/src/widgets/type_widgets/MindMap.tsx b/apps/client/src/widgets/type_widgets/MindMap.tsx index 542a5152a..e5f5b97ef 100644 --- a/apps/client/src/widgets/type_widgets/MindMap.tsx +++ b/apps/client/src/widgets/type_widgets/MindMap.tsx @@ -22,13 +22,14 @@ interface MindElixirProps { onChange?: () => void; } -export default function MindMap({ note, ntxId }: TypeWidgetProps) { +export default function MindMap({ note, ntxId, noteContext }: TypeWidgetProps) { const content = VanillaMindElixir.new(NEW_TOPIC_NAME); const apiRef = useRef(null); const containerRef = useRef(null); const [ isReadOnly ] = useNoteLabelBoolean(note, "readOnly"); const spacedUpdate = useEditorSpacedUpdate({ note, + noteContext, getData: async () => { if (!apiRef.current) return; return { diff --git a/apps/client/src/widgets/type_widgets/code/Code.tsx b/apps/client/src/widgets/type_widgets/code/Code.tsx index 8a4f58e02..e65445a44 100644 --- a/apps/client/src/widgets/type_widgets/code/Code.tsx +++ b/apps/client/src/widgets/type_widgets/code/Code.tsx @@ -70,12 +70,13 @@ function formatViewSource(note: FNote, content: string) { return content; } -export function EditableCode({ note, ntxId, debounceUpdate, parentComponent, updateInterval, onContentChanged, dataSaved, ...editorProps }: EditableCodeProps) { +export function EditableCode({ note, ntxId, noteContext, debounceUpdate, parentComponent, updateInterval, onContentChanged, dataSaved, ...editorProps }: EditableCodeProps) { const editorRef = useRef(null); const containerRef = useRef(null); const [ vimKeymapEnabled ] = useTriliumOptionBool("vimKeymapEnabled"); const spacedUpdate = useEditorSpacedUpdate({ note, + noteContext, getData: () => ({ content: editorRef.current?.getText() }), onContentChange: (content) => { const codeEditor = editorRef.current; diff --git a/apps/client/src/widgets/type_widgets/relation_map/RelationMap.tsx b/apps/client/src/widgets/type_widgets/relation_map/RelationMap.tsx index aa7f45095..8ecabacfe 100644 --- a/apps/client/src/widgets/type_widgets/relation_map/RelationMap.tsx +++ b/apps/client/src/widgets/type_widgets/relation_map/RelationMap.tsx @@ -43,7 +43,7 @@ declare module "jsplumb" { } } -export default function RelationMap({ note, ntxId }: TypeWidgetProps) { +export default function RelationMap({ note, noteContext, ntxId }: TypeWidgetProps) { const [ data, setData ] = useState(); const containerRef = useRef(null); const mapApiRef = useRef(null); @@ -51,6 +51,7 @@ export default function RelationMap({ note, ntxId }: TypeWidgetProps) { const spacedUpdate = useEditorSpacedUpdate({ note, + noteContext, getData() { return { content: JSON.stringify(data), diff --git a/apps/client/src/widgets/type_widgets/text/EditableText.tsx b/apps/client/src/widgets/type_widgets/text/EditableText.tsx index a4c1c0087..388da1691 100644 --- a/apps/client/src/widgets/type_widgets/text/EditableText.tsx +++ b/apps/client/src/widgets/type_widgets/text/EditableText.tsx @@ -37,6 +37,7 @@ export default function EditableText({ note, parentComponent, ntxId, noteContext const initialized = useRef(deferred()); const spacedUpdate = useEditorSpacedUpdate({ note, + noteContext, getData() { const editor = watchdogRef.current?.editor; if (!editor) {