From 2e262241c97c487e4421af484bd874b9a99fcc3f Mon Sep 17 00:00:00 2001 From: Jonatan Heyman Date: Mon, 15 Jul 2024 10:45:25 +0200 Subject: [PATCH] Add metadata to the beginning of the serialized buffer. Store the cursors' positions in the buffer metadata and restore the cursors when loading the buffer content. --- src/editor/editor.js | 53 ++++++++++++++++++++++++++------------- src/editor/note-format.js | 35 ++++++++++++++++++++++++++ src/editor/save.js | 8 +++--- 3 files changed, 75 insertions(+), 21 deletions(-) create mode 100644 src/editor/note-format.js diff --git a/src/editor/editor.js b/src/editor/editor.js index ecc1f5ba..1c070ac5 100644 --- a/src/editor/editor.js +++ b/src/editor/editor.js @@ -1,4 +1,4 @@ -import { Annotation, EditorState, Compartment, Facet } from "@codemirror/state" +import { Annotation, EditorState, Compartment, Facet, EditorSelection } from "@codemirror/state" import { EditorView, keymap, drawSelection, ViewPlugin, lineNumbers } from "@codemirror/view" import { indentUnit, forceParsing, foldGutter, ensureSyntaxTree } from "@codemirror/language" import { markdown } from "@codemirror/lang-markdown" @@ -21,6 +21,7 @@ import { languageDetection } from "./language-detection/autodetect.js" import { autoSaveContent } from "./save.js" import { todoCheckboxPlugin} from "./todo-checkbox.ts" import { links } from "./links.js" +import { NoteFormat } from "./note-format.js" export const LANGUAGE_SELECTOR_EVENT = "openLanguageSelector" @@ -61,9 +62,10 @@ export class HeynoteEditor { this.fontTheme = new Compartment this.defaultBlockToken = "text" this.defaultBlockAutoDetect = true + this.saveFunction = saveFunction const state = EditorState.create({ - doc: content || "", + doc: "", extensions: [ this.keymapCompartment.of(getKeymapExtensions(this, keymap)), heynoteCopyCut(this), @@ -96,7 +98,7 @@ export class HeynoteEditor { return {class: view.state.facet(EditorView.darkTheme) ? "dark-theme" : "light-theme"} }), - saveFunction ? autoSaveContent(saveFunction, 2000) : [], + this.saveFunction ? autoSaveContent(this, 2000) : [], todoCheckboxPlugin, markdown(), @@ -107,7 +109,7 @@ export class HeynoteEditor { // make sure saveFunction is called when page is unloaded if (saveFunction) { window.addEventListener("beforeunload", () => { - saveFunction(this.getContent()) + this.save() }) } @@ -116,36 +118,53 @@ export class HeynoteEditor { parent: element, }) - // Ensure we have a parsed syntax tree when buffer is loaded. This prevents errors for large buffers - // when moving the cursor to the end of the buffer when the program starts - ensureSyntaxTree(state, state.doc.length, 5000) + this.setContent(content) if (focus) { - this.view.dispatch({ - selection: {anchor: this.view.state.doc.length, head: this.view.state.doc.length}, - scrollIntoView: true, - }) this.view.focus() } } + save() { + this.saveFunction(this.getContent()) + } + getContent() { - return this.view.state.sliceDoc() + this.note.content = this.view.state.sliceDoc() + this.note.cursors = this.view.state.selection.toJSON() + return this.note.serialize() } setContent(content) { + this.note = NoteFormat.load(content) + + // set buffer content this.view.dispatch({ changes: { from: 0, to: this.view.state.doc.length, - insert: content, + insert: this.note.content, }, annotations: [heynoteEvent.of(SET_CONTENT)], }) - this.view.dispatch({ - selection: {anchor: this.view.state.doc.length, head: this.view.state.doc.length}, - scrollIntoView: true, - }) + + // Ensure we have a parsed syntax tree when buffer is loaded. This prevents errors for large buffers + // when moving the cursor to the end of the buffer when the program starts + ensureSyntaxTree(this.view.state, this.view.state.doc.length, 5000) + + // set cursor positions + if (this.note.cursors) { + this.view.dispatch({ + selection: EditorSelection.fromJSON(this.note.cursors), + scrollIntoView: true, + }) + } else { + // if metadata doesn't contain cursor position, we set the cursor to the end of the buffer + this.view.dispatch({ + selection: {anchor: this.view.state.doc.length, head: this.view.state.doc.length}, + scrollIntoView: true, + }) + } } getBlocks() { diff --git a/src/editor/note-format.js b/src/editor/note-format.js new file mode 100644 index 00000000..0d1313b4 --- /dev/null +++ b/src/editor/note-format.js @@ -0,0 +1,35 @@ +export class NoteFormat { + constructor() { + this.content = ''; + this.metadata = {}; + } + + static load(data) { + const note = new NoteFormat(); + + note.content = data + const firstSeparator = data.indexOf("\nāˆžāˆžāˆž") + if (firstSeparator !== -1) { + const metadataContent = data.slice(0, firstSeparator).trim() + if (metadataContent !== "") { + note.metadata = JSON.parse(metadataContent) + } + note.content = data.slice(firstSeparator) + } + + return note + } + + serialize() { + this.metadata.formatVersion = "1.0" + return JSON.stringify(this.metadata) + this.content + } + + set cursors(cursors) { + this.metadata.cursors = cursors + } + + get cursors() { + return this.metadata.cursors + } +} diff --git a/src/editor/save.js b/src/editor/save.js index 81b74220..415a8eec 100644 --- a/src/editor/save.js +++ b/src/editor/save.js @@ -2,17 +2,17 @@ import { ViewPlugin } from "@codemirror/view" import { debounce } from "debounce" -export const autoSaveContent = (saveFunction, interval) => { - const save = debounce((view) => { +export const autoSaveContent = (editor, interval) => { + const save = debounce(() => { //console.log("saving buffer") - saveFunction(view.state.sliceDoc()) + editor.save() }, interval); return ViewPlugin.fromClass( class { update(update) { if (update.docChanged) { - save(update.view) + save() } } }