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.
This commit is contained in:
Jonatan Heyman 2024-07-15 10:45:25 +02:00
parent ca38f9f932
commit ec360f5456
3 changed files with 75 additions and 21 deletions

View File

@ -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() {

35
src/editor/note-format.js Normal file
View File

@ -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
}
}

View File

@ -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()
}
}
}