@blocknote/core
Version:
A "Notion-style" block-based extensible text editor built on top of Prosemirror and Tiptap.
87 lines (70 loc) • 3.02 kB
text/typescript
import { Plugin, PluginKey } from "prosemirror-state";
import { getBlockInfo } from "../../../api/getBlockInfoFromPos.js";
// ProseMirror Plugin which automatically assigns indices to ordered list items per nesting level.
const PLUGIN_KEY = new PluginKey(`numbered-list-indexing`);
export const NumberedListIndexingPlugin = () => {
return new Plugin({
key: PLUGIN_KEY,
appendTransaction: (_transactions, _oldState, newState) => {
const tr = newState.tr;
tr.setMeta("numberedListIndexing", true);
let modified = false;
// Traverses each node the doc using DFS, so blocks which are on the same nesting level will be traversed in the
// same order they appear. This means the index of each list item block can be calculated by incrementing the
// index of the previous list item block.
newState.doc.descendants((node, pos) => {
if (
node.type.name === "blockContainer" &&
node.firstChild!.type.name === "numberedListItem"
) {
let newIndex = `${node.firstChild!.attrs["start"] || 1}`;
const blockInfo = getBlockInfo({
posBeforeNode: pos,
node,
});
if (!blockInfo.isBlockContainer) {
throw new Error("impossible");
}
// Checks if this block is the start of a new ordered list, i.e. if it's the first block in the document, the
// first block in its nesting level, or the previous block is not an ordered list item.
const prevBlock = tr.doc.resolve(
blockInfo.bnBlock.beforePos,
).nodeBefore;
if (prevBlock) {
const prevBlockInfo = getBlockInfo({
posBeforeNode: blockInfo.bnBlock.beforePos - prevBlock.nodeSize,
node: prevBlock,
});
const isPrevBlockOrderedListItem =
prevBlockInfo.blockNoteType === "numberedListItem";
if (isPrevBlockOrderedListItem) {
if (!prevBlockInfo.isBlockContainer) {
throw new Error("impossible");
}
const prevBlockIndex =
prevBlockInfo.blockContent.node.attrs["index"];
newIndex = (parseInt(prevBlockIndex) + 1).toString();
}
}
const contentNode = blockInfo.blockContent.node;
const index = contentNode.attrs["index"];
const isFirst =
prevBlock?.firstChild?.type.name !== "numberedListItem";
if (index !== newIndex || (contentNode.attrs.start && !isFirst)) {
modified = true;
const { start, ...attrs } = contentNode.attrs;
tr.setNodeMarkup(blockInfo.blockContent.beforePos, undefined, {
...attrs,
index: newIndex,
...(typeof start === "number" &&
isFirst && {
start,
}),
});
}
}
});
return modified ? tr : null;
},
});
};