diff --git a/client/codemirror/editor_state.ts b/client/codemirror/editor_state.ts index 08cd7275..60bde1ef 100644 --- a/client/codemirror/editor_state.ts +++ b/client/codemirror/editor_state.ts @@ -1,9 +1,5 @@ import customMarkdownStyle from "../style.ts"; -import { - history, - insertNewlineAndIndent, - isolateHistory, -} from "@codemirror/commands"; +import { history, insertNewlineAndIndent } from "@codemirror/commands"; import { autocompletion, closeBrackets, @@ -20,6 +16,7 @@ import { unfoldEffect, } from "@codemirror/language"; import { + Annotation, Compartment, EditorState, type Extension, @@ -57,6 +54,11 @@ import { codeCopyPlugin } from "../codemirror/code_copy.ts"; import { disableSpellcheck } from "../codemirror/spell_checking.ts"; import type { ClickEvent } from "@silverbulletmd/silverbullet/type/client"; +// Annotation marking a transaction whose changes came from outside the +// editor's edit stream (e.g. a page re-fetch from storage), so the +// save-on-change handler can skip it and avoid an immediate re-save loop. +export const externalUpdate = Annotation.define(); + export function createEditorState( client: Client, pageName: string, @@ -292,9 +294,9 @@ export function createEditorState( } } if (update.docChanged) { - // Find if there's a history isolate in the transaction, if so it came from a local reload and we don't do anything + // Skip saving if the change came from outside the editor (e.g. storage reload) if ( - update.transactions.some((t) => t.annotation(isolateHistory)) + update.transactions.some((t) => t.annotation(externalUpdate)) ) { return; } diff --git a/client/content_manager.ts b/client/content_manager.ts index ca944bba..6bb725ef 100644 --- a/client/content_manager.ts +++ b/client/content_manager.ts @@ -18,7 +18,10 @@ import { notFoundError, offlineError, } from "@silverbulletmd/silverbullet/constants"; -import { createEditorState } from "./codemirror/editor_state.ts"; +import { + createEditorState, + externalUpdate, +} from "./codemirror/editor_state.ts"; import { diffAndPrepareChanges } from "./codemirror/cm_util.ts"; import { DocumentEditor } from "./document_editor.ts"; import { fsEndpoint } from "./spaces/constants.ts"; @@ -382,7 +385,7 @@ export class ContentManager { this.client.editorView.setState(editorState); } else { // Just apply minimal patches so that the cursor is preserved - this.setEditorText(doc.text, true); + this.applyExternalPatches(doc.text); } this.client.space.watchFile(path); @@ -492,6 +495,18 @@ export class ContentManager { }); } + // Like setEditorText, but marks the transaction as an external update (e.g. + // a page re-fetch from storage) so the save-on-change handler skips it and + // we avoid an immediate re-save loop. + private applyExternalPatches(newText: string) { + const currentText = this.client.editorView.state.sliceDoc(); + const allChanges = diffAndPrepareChanges(currentText, newText); + this.client.editorView.dispatch({ + changes: allChanges, + annotations: [isolateHistory.of("full"), externalUpdate.of(true)], + }); + } + private navigateWithinPage(pageState: LocationState) { if (!isMarkdownPath(pageState.path)) return;