@blocknote/core
Version:
A "Notion-style" block-based extensible text editor built on top of Prosemirror and Tiptap.
86 lines (71 loc) • 2.83 kB
text/typescript
import { Extension } from "@tiptap/core";
import { Plugin, PluginKey } from "prosemirror-state";
// 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
*/
export interface TrailingNodeOptions {
node: string;
}
/**
* Add a trailing node to the document so the user can always click at the bottom of the document and start typing
*/
export const TrailingNode = Extension.create<TrailingNodeOptions>({
name: "trailingNode",
addProseMirrorPlugins() {
const plugin = new PluginKey(this.name);
// const disabledNodes = Object.entries(this.editor.schema.nodes)
// .map(([, value]) => value)
// .filter((node) => this.options.notAfter.includes(node.name));
return [
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*"
);
},
},
}),
];
},
});