@blocknote/core
Version:
A "Notion-style" block-based extensible text editor built on top of Prosemirror and Tiptap.
84 lines (70 loc) • 2.4 kB
text/typescript
import { selectionToInsertionEnd } from "@tiptap/core";
import { Node } from "prosemirror-model";
import type { Transaction } from "prosemirror-state";
// similar to tiptap insertContentAt
export function insertContentAt(
tr: Transaction,
position: number | { from: number; to: number },
nodes: Node[],
options: {
updateSelection: boolean;
} = { updateSelection: true },
) {
// don’t dispatch an empty fragment because this can lead to strange errors
// if (content.toString() === "<>") {
// return true;
// }
let { from, to } =
typeof position === "number"
? { from: position, to: position }
: { from: position.from, to: position.to };
let isOnlyTextContent = true;
let isOnlyBlockContent = true;
// const nodes = isFragment(content) ? content : [content];
let text = "";
nodes.forEach((node) => {
// check if added node is valid
node.check();
if (isOnlyTextContent && node.isText && node.marks.length === 0) {
text += node.text;
} else {
isOnlyTextContent = false;
}
isOnlyBlockContent = isOnlyBlockContent ? node.isBlock : false;
});
// check if we can replace the wrapping node by
// the newly inserted content
// example:
// replace an empty paragraph by an inserted image
// instead of inserting the image below the paragraph
if (from === to && isOnlyBlockContent) {
const { parent } = tr.doc.resolve(from);
const isEmptyTextBlock =
parent.isTextblock && !parent.type.spec.code && !parent.childCount;
if (isEmptyTextBlock) {
from -= 1;
to += 1;
}
}
// if there is only plain text we have to use `insertText`
// because this will keep the current marks
if (isOnlyTextContent) {
// if value is string, we can use it directly
// otherwise if it is an array, we have to join it
// if (Array.isArray(value)) {
// tr.insertText(value.map((v) => v.text || "").join(""), from, to);
// } else if (typeof value === "object" && !!value && !!value.text) {
// tr.insertText(value.text, from, to);
// } else {
// tr.insertText(value as string, from, to);
// }
tr.insertText(text, from, to);
} else {
tr.replaceWith(from, to, nodes);
}
// set cursor at end of inserted content
if (options.updateSelection) {
selectionToInsertionEnd(tr, tr.steps.length - 1, -1);
}
return true;
}