diff --git a/src/extras/editorScript.ts b/src/extras/editorScript.ts index 30c538c..6fbdec3 100644 --- a/src/extras/editorScript.ts +++ b/src/extras/editorScript.ts @@ -1,6 +1,14 @@ // The prosemirror imports are only for type hint -import { Node, NodeType } from "prosemirror-model"; -import { Mark, MarkType, ResolvedPos, Attrs } from "prosemirror-model"; +import { + Node, + NodeType, + Mark, + MarkType, + ResolvedPos, + Attrs, + DOMParser, + Schema, +} from "prosemirror-model"; import { EditorState, TextSelection } from "prosemirror-state"; import { EditorView } from "prosemirror-view"; @@ -8,6 +16,38 @@ declare const _currentEditorInstance: { _editorCore: EditorCore; }; +function fromHTML(schema: Schema, html: string, slice?: boolean) { + let domNode = document.createElement("div"); + domNode.innerHTML = html; + let fragment = document.createDocumentFragment(); + while (domNode.firstChild) { + fragment.appendChild(domNode.firstChild); + } + if (slice) { + return DOMParser.fromSchema(schema).parseSlice(fragment); + } else { + return DOMParser.fromSchema(schema).parse(fragment); + } +} + +function getSliceFromHTML(state: EditorState, html: string) { + return fromHTML(state.schema, html, true); +} + +function getNodeFromHTML(state: EditorState, html: string) { + return fromHTML(state.schema, html); +} + +function setSelection(anchor: number, head?: number | undefined) { + return (state: EditorState, dispatch?: EditorView["dispatch"]) => { + const { tr, selection } = state; + const _TextSelection = + selection.constructor as unknown as typeof TextSelection; + tr.setSelection(_TextSelection.create(tr.doc, anchor, head)); + dispatch && dispatch(tr); + }; +} + // Code from https://github.com/ueberdosis/tiptap/tree/main/packages/core/src/helpers function objectIncludes(object1: any, object2: any) { @@ -148,7 +188,8 @@ function replaceRangeNode( nodeType: NodeType, nodeAttrs: Attrs | string, markType?: MarkType, - markAttrs?: Attrs | string + markAttrs?: Attrs | string, + select?: boolean ) { return (state: EditorState, dispatch: EditorView["dispatch"]) => { const { tr } = state; @@ -166,6 +207,9 @@ function replaceRangeNode( ); console.log("Replace Node", from, to, node); tr.replaceWith(from, to, node); + if (select) { + setSelection(from + node.nodeSize, from)(state); + } dispatch(tr); }; } @@ -333,6 +377,9 @@ export const BetterNotesEditorAPI = { updateImageDimensions, getHeadingLevelInRange, updateHeadingsInRange, + getSliceFromHTML, + getNodeFromHTML, + setSelection, }; // @ts-ignore diff --git a/src/utils/editor.ts b/src/utils/editor.ts index 7862515..bdf7a3a 100644 --- a/src/utils/editor.ts +++ b/src/utils/editor.ts @@ -14,6 +14,7 @@ export { getRangeAtCursor, getLineAtCursor, getPositionAtLine, + getPositionAtCursor, getURLAtCursor, updateImageDimensionsAtCursor, updateURLAtCursor, @@ -24,9 +25,11 @@ export { function insert( editor: Zotero.EditorInstance, content: string = "", - position: number | "end" | "start" | "cursor" = "cursor" + position: number | "end" | "start" | "cursor" = "cursor", + select?: boolean ) { const core = getEditorCore(editor); + const EditorAPI = getEditorAPI(editor); if (position === "cursor") { position = getPositionAtCursor(editor); } else if (position === "end") { @@ -36,6 +39,13 @@ function insert( } position = Math.max(0, Math.min(position, core.view.state.doc.content.size)); (core as any).insertHTML(position, content); + if (select) { + const slice = EditorAPI.getSliceFromHTML(core.view.state, content); + EditorAPI.setSelection(position + slice.content.size, position)( + core.view.state, + core.view.dispatch + ); + } } function del(editor: Zotero.EditorInstance, from: number, to: number) { @@ -92,7 +102,8 @@ function replace( | "backgroundColor" | "link" | "code", - markAttrs: Record + markAttrs: Record, + select?: boolean ) { const core = getEditorCore(editor); const EditorAPI = getEditorAPI(editor); @@ -104,7 +115,8 @@ function replace( schema.nodes[nodeTypeName], JSON.stringify(nodeAttrs), schema.marks[markTypeName], - JSON.stringify(markAttrs) + JSON.stringify(markAttrs), + select )(core.view.state, core.view.dispatch); }