@blocknote/core
Version:
A "Notion-style" block-based extensible text editor built on top of Prosemirror and Tiptap.
77 lines (65 loc) • 2.62 kB
text/typescript
import { Plugin, PluginKey } from "prosemirror-state";
import { createExtension } from "../../editor/BlockNoteExtension.js";
// based on https://github.com/ueberdosis/tiptap/blob/40a9404c94c7fef7900610c195536384781ae101/demos/src/Experiments/TrailingNode/Vue/trailing-node.ts
/**
* Extension based on:
* - https://github.com/ueberdosis/tiptap/blob/v1/packages/tiptap-extensions/src/extensions/TrailingNode.js
* - https://github.com/remirror/remirror/blob/e0f1bec4a1e8073ce8f5500d62193e52321155b9/packages/prosemirror-trailing-node/src/trailing-node-plugin.ts
*/
const plugin = new PluginKey("trailingNode");
/**
* Add a trailing node to the document so the user can always click at the bottom of the document and start typing
*/
export const TrailingNodeExtension = createExtension(() => {
return {
key: "trailingNode",
prosemirrorPlugins: [
new Plugin({
key: plugin,
appendTransaction: (_, __, state) => {
const { doc, tr, schema } = state;
const shouldInsertNodeAtEnd = plugin.getState(state);
const endPosition = doc.content.size - 2;
const type = schema.nodes["blockContainer"];
const contentType = schema.nodes["paragraph"];
if (!shouldInsertNodeAtEnd) {
return;
}
return tr.insert(
endPosition,
type.create(undefined, contentType.create()),
);
},
state: {
init: (_, _state) => {
// (maybe fix): use same logic as apply() here
// so it works when initializing
},
apply: (tr, value) => {
if (!tr.docChanged) {
return value;
}
let lastNode = tr.doc.lastChild;
if (!lastNode || lastNode.type.name !== "blockGroup") {
throw new Error("Expected blockGroup");
}
lastNode = lastNode.lastChild;
if (!lastNode || lastNode.type.name !== "blockContainer") {
return true; // not a blockContainer, but for example Columns. Insert trailing node
}
const lastContentNode = lastNode.firstChild;
if (!lastContentNode) {
throw new Error("Expected blockContent");
}
// If last node is not empty (size > 4) or it doesn't contain
// inline content, we need to add a trailing node.
return (
lastNode.nodeSize > 4 ||
lastContentNode.type.spec.content !== "inline*"
);
},
},
}),
],
} as const;
});