fix(note_detail): spaced update sometimes overwrites when note type is changed

This commit is contained in:
Elian Doran 2025-12-24 16:36:34 +02:00
parent fd760951cc
commit db3aedf39d
No known key found for this signature in database
7 changed files with 55 additions and 41 deletions

View File

@ -1,5 +1,5 @@
import { CKTextEditor } from "@triliumnext/ckeditor5";
import { FilterLabelsByType, KeyboardActionNames, OptionNames, RelationNames } from "@triliumnext/commons";
import { FilterLabelsByType, KeyboardActionNames, NoteType, OptionNames, RelationNames } from "@triliumnext/commons";
import { Tooltip } from "bootstrap";
import Mark from "mark.js";
import { RefObject, VNode } from "preact";
@ -94,7 +94,8 @@ export interface SavedData {
}[];
}
export function useEditorSpacedUpdate({ note, noteContext, getData, onContentChange, dataSaved, updateInterval }: {
export function useEditorSpacedUpdate({ note, noteType, noteContext, getData, onContentChange, dataSaved, updateInterval }: {
noteType: NoteType;
note: FNote,
noteContext: NoteContext | null | undefined,
getData: () => Promise<SavedData | undefined> | SavedData | undefined,
@ -110,15 +111,16 @@ export function useEditorSpacedUpdate({ note, noteContext, getData, onContentCha
const data = await getData();
// for read only notes
if (data === undefined) return;
if (data === undefined || note.type !== noteType) return;
protected_session_holder.touchProtectedSessionIfNecessary(note);
await server.put(`notes/${note.noteId}/data`, data, parentComponent?.componentId);
noteSavedDataStore.set(note.noteId, data.content);
dataSaved?.(data);
};
}, [ note, getData, dataSaved ]);
}, [ note, getData, dataSaved, noteType, parentComponent ]);
const spacedUpdate = useSpacedUpdate(callback);
// React to note/blob changes.

View File

@ -1,13 +1,15 @@
import { useEffect, useRef } from "preact/hooks";
import LlmChatPanel from "../llm_chat";
import { useEditorSpacedUpdate, useLegacyWidget } from "../react/hooks";
import { type TypeWidgetProps } from "./type_widget";
import LlmChatPanel from "../llm_chat";
export default function AiChat({ note, noteContext }: TypeWidgetProps) {
const dataRef = useRef<object>();
const spacedUpdate = useEditorSpacedUpdate({
note,
noteContext,
noteType: "aiChat",
getData: async () => ({
content: JSON.stringify(dataRef.current)
}),

View File

@ -1,17 +1,19 @@
import { useCallback, useEffect, useRef } from "preact/hooks";
import { TypeWidgetProps } from "./type_widget";
import { MindElixirData, MindElixirInstance, Operation, Options, default as VanillaMindElixir } from "mind-elixir";
import { HTMLAttributes, RefObject } from "preact";
// allow node-menu plugin css to be bundled by webpack
import nodeMenu from "@mind-elixir/node-menu";
import "mind-elixir/style";
import "@mind-elixir/node-menu/dist/style.css";
import "./MindMap.css";
// allow node-menu plugin css to be bundled by webpack
import nodeMenu from "@mind-elixir/node-menu";
import { DISPLAYABLE_LOCALE_IDS } from "@triliumnext/commons";
import { snapdom } from "@zumer/snapdom";
import { default as VanillaMindElixir,MindElixirData, MindElixirInstance, Operation, Options } from "mind-elixir";
import { HTMLAttributes, RefObject } from "preact";
import { useCallback, useEffect, useRef } from "preact/hooks";
import utils from "../../services/utils";
import { useEditorSpacedUpdate, useNoteLabelBoolean, useSyncedRef, useTriliumEvent, useTriliumEvents, useTriliumOption } from "../react/hooks";
import { refToJQuerySelector } from "../react/react_utils";
import utils from "../../services/utils";
import { DISPLAYABLE_LOCALE_IDS } from "@triliumnext/commons";
import { snapdom, SnapdomOptions } from "@zumer/snapdom";
import { TypeWidgetProps } from "./type_widget";
const NEW_TOPIC_NAME = "";
@ -50,6 +52,7 @@ export default function MindMap({ note, ntxId, noteContext }: TypeWidgetProps) {
const spacedUpdate = useEditorSpacedUpdate({
note,
noteType: "mindMap",
noteContext,
getData: async () => {
if (!apiRef.current) return;
@ -75,7 +78,7 @@ export default function MindMap({ note, ntxId, noteContext }: TypeWidgetProps) {
position: 0
}
]
}
};
},
onContentChange: (content) => {
let newContent: MindElixirData;
@ -87,7 +90,7 @@ export default function MindMap({ note, ntxId, noteContext }: TypeWidgetProps) {
console.debug("Wrong JSON content: ", content);
}
} else {
newContent = VanillaMindElixir.new(NEW_TOPIC_NAME)
newContent = VanillaMindElixir.new(NEW_TOPIC_NAME);
}
apiRef.current?.init(newContent!);
}
@ -138,7 +141,7 @@ export default function MindMap({ note, ntxId, noteContext }: TypeWidgetProps) {
onKeyDown
}}
/>
)
);
}
function MindElixir({ containerRef: externalContainerRef, containerProps, apiRef: externalApiRef, onChange, editable }: MindElixirProps) {
@ -190,7 +193,7 @@ function MindElixir({ containerRef: externalContainerRef, containerProps, apiRef
if (operation.name !== "beginEdit") {
onChange();
}
}
};
bus.addListener("operation", operationListener);
bus.addListener("changeDirection", onChange);
@ -203,5 +206,5 @@ function MindElixir({ containerRef: externalContainerRef, containerProps, apiRef
return (
<div ref={containerRef} {...containerProps} />
)
);
}

View File

@ -1,12 +1,13 @@
import { exportToSvg, getSceneVersion } from "@excalidraw/excalidraw";
import { ExcalidrawElement, NonDeletedExcalidrawElement } from "@excalidraw/excalidraw/element/types";
import { AppState, BinaryFileData, ExcalidrawImperativeAPI, ExcalidrawProps, LibraryItem } from "@excalidraw/excalidraw/types";
import { RefObject } from "preact";
import { useRef } from "preact/hooks";
import NoteContext from "../../../components/note_context";
import FNote from "../../../entities/fnote";
import { AppState, BinaryFileData, ExcalidrawImperativeAPI, ExcalidrawProps, LibraryItem } from "@excalidraw/excalidraw/types";
import { useRef } from "preact/hooks";
import { SavedData, useEditorSpacedUpdate } from "../../react/hooks";
import { ExcalidrawElement, NonDeletedExcalidrawElement } from "@excalidraw/excalidraw/element/types";
import { exportToSvg, getSceneVersion } from "@excalidraw/excalidraw";
import server from "../../../services/server";
import { SavedData, useEditorSpacedUpdate } from "../../react/hooks";
interface AttachmentMetadata {
title: string;
@ -39,6 +40,7 @@ export default function useCanvasPersistence(note: FNote, noteContext: NoteConte
const spacedUpdate = useEditorSpacedUpdate({
note,
noteContext,
noteType: "canvas",
onContentChange(newContent) {
const api = apiRef.current;
if (!api) return;
@ -66,7 +68,7 @@ export default function useCanvasPersistence(note: FNote, noteContext: NoteConte
// load the library state
loadLibrary(note).then(({ libraryItems, metadata }) => {
// Update the library and save to independent variables
api.updateLibrary({ libraryItems: libraryItems, merge: false });
api.updateLibrary({ libraryItems, merge: false });
// save state of library to compare it to the new state later.
libraryCache.current = libraryItems;
@ -158,7 +160,7 @@ export default function useCanvasPersistence(note: FNote, noteContext: NoteConte
spacedUpdate.resetUpdateTimer();
spacedUpdate.scheduleUpdate();
}
}
};
}
async function getData(api: ExcalidrawImperativeAPI) {
@ -202,7 +204,7 @@ async function getData(api: ExcalidrawImperativeAPI) {
return {
content,
svg: svgString
}
};
}
function loadData(api: ExcalidrawImperativeAPI, content: CanvasContent, theme: AppState["theme"]) {

View File

@ -79,6 +79,7 @@ export function EditableCode({ note, ntxId, noteContext, debounceUpdate, parentC
const mime = useNoteProperty(note, "mime");
const spacedUpdate = useEditorSpacedUpdate({
note,
noteType: "code",
noteContext,
getData: () => ({ content: editorRef.current?.getText() ?? "" }),
onContentChange: (content) => {

View File

@ -58,6 +58,7 @@ export default function RelationMap({ note, noteContext, ntxId, parentComponent
const spacedUpdate = useEditorSpacedUpdate({
note,
noteContext,
noteType: "relationMap",
getData() {
return {
content: JSON.stringify(data),

View File

@ -1,23 +1,25 @@
import "./EditableText.css";
import { CKTextEditor, EditorWatchdog, TemplateDefinition } from "@triliumnext/ckeditor5";
import { deferred } from "@triliumnext/commons";
import { RefObject } from "preact";
import { useCallback, useEffect, useRef, useState } from "preact/hooks";
import appContext from "../../../components/app_context";
import { buildSelectedBackgroundColor } from "../../../components/touch_bar";
import dialog from "../../../services/dialog";
import { t } from "../../../services/i18n";
import link, { parseNavigationStateFromUrl } from "../../../services/link";
import note_create from "../../../services/note_create";
import options from "../../../services/options";
import toast from "../../../services/toast";
import utils, { hasTouchBar, isMobile } from "../../../services/utils";
import { useEditorSpacedUpdate, useLegacyImperativeHandlers, useNoteLabel, useTriliumEvent, useTriliumOption, useTriliumOptionBool } from "../../react/hooks";
import TouchBar, { TouchBarButton, TouchBarGroup, TouchBarSegmentedControl } from "../../react/TouchBar";
import { TypeWidgetProps } from "../type_widget";
import CKEditorWithWatchdog, { CKEditorApi } from "./CKEditorWithWatchdog";
import "./EditableText.css";
import { CKTextEditor, EditorWatchdog, TemplateDefinition } from "@triliumnext/ckeditor5";
import options from "../../../services/options";
import { loadIncludedNote, refreshIncludedNote, setupImageOpening } from "./utils";
import getTemplates, { updateTemplateCache } from "./snippets.js";
import appContext from "../../../components/app_context";
import link, { parseNavigationStateFromUrl } from "../../../services/link";
import note_create from "../../../services/note_create";
import TouchBar, { TouchBarButton, TouchBarGroup, TouchBarSegmentedControl } from "../../react/TouchBar";
import { RefObject } from "preact";
import { buildSelectedBackgroundColor } from "../../../components/touch_bar";
import { deferred } from "@triliumnext/commons";
import { t } from "../../../services/i18n";
import { loadIncludedNote, refreshIncludedNote, setupImageOpening } from "./utils";
/**
* The editor can operate into two distinct modes:
@ -39,6 +41,7 @@ export default function EditableText({ note, parentComponent, ntxId, noteContext
const spacedUpdate = useEditorSpacedUpdate({
note,
noteContext,
noteType: "text",
getData() {
const editor = watchdogRef.current?.editor;
if (!editor) {
@ -376,7 +379,7 @@ function EditableTextTouchBar({ watchdogRef, refreshTouchBarRef }: { watchdogRef
const editor = watchdogRef.current?.editor;
switch (selectedIndex) {
case 0:
editor?.execute("paragraph")
editor?.execute("paragraph");
break;
case 1:
editor?.execute("heading", { value: "heading2" });
@ -396,7 +399,7 @@ function EditableTextTouchBar({ watchdogRef, refreshTouchBarRef }: { watchdogRef
<TouchBarCommandButton watchdogRef={watchdogRef} command="underline" icon="NSTouchBarTextUnderlineTemplate" />
</TouchBarGroup>
</TouchBar>
)
);
}
function TouchBarCommandButton({ watchdogRef, icon, command }: { watchdogRef: RefObject<EditorWatchdog | null>, icon: string, command: string }) {