UNPKG

@blocknote/core

Version:

A "Notion-style" block-based extensible text editor built on top of Prosemirror and Tiptap.

172 lines (155 loc) 5.15 kB
import { InputRule } from "@tiptap/core"; import { updateBlockCommand } from "../../../api/blockManipulation/commands/updateBlock/updateBlock.js"; import { getBlockInfoFromSelection } from "../../../api/getBlockInfoFromPos.js"; import { PropSchema, createBlockSpecFromStronglyTypedTiptapNode, createStronglyTypedTiptapNode, propsToAttributes, } from "../../../schema/index.js"; import { createDefaultBlockDOMOutputSpec } from "../../defaultBlockHelpers.js"; import { defaultProps } from "../../defaultProps.js"; import { getListItemContent } from "../getListItemContent.js"; import { handleEnter } from "../ListItemKeyboardShortcuts.js"; import { NumberedListIndexingPlugin } from "./NumberedListIndexingPlugin.js"; export const numberedListItemPropSchema = { ...defaultProps, start: { default: undefined, type: "number" }, } satisfies PropSchema; const NumberedListItemBlockContent = createStronglyTypedTiptapNode({ name: "numberedListItem", content: "inline*", group: "blockContent", priority: 90, addAttributes() { return { ...propsToAttributes(numberedListItemPropSchema), // the index attribute is only used internally (it's not part of the blocknote schema) // that's why it's defined explicitly here, and not part of the prop schema index: { default: null, parseHTML: (element) => element.getAttribute("data-index"), renderHTML: (attributes) => { return { "data-index": attributes.index, }; }, }, }; }, addInputRules() { return [ // Creates an ordered list when starting with "1.". new InputRule({ find: new RegExp(`^(\\d+)\\.\\s$`), handler: ({ state, chain, range, match }) => { const blockInfo = getBlockInfoFromSelection(state); if ( !blockInfo.isBlockContainer || blockInfo.blockContent.node.type.spec.content !== "inline*" || blockInfo.blockNoteType === "numberedListItem" ) { return; } const startIndex = parseInt(match[1]); chain() .command( updateBlockCommand(blockInfo.bnBlock.beforePos, { type: "numberedListItem", props: (startIndex === 1 && {}) || ({ start: startIndex, } as any), }), ) // Removes the "1." characters used to set the list. .deleteRange({ from: range.from, to: range.to }); }, }), ]; }, addKeyboardShortcuts() { return { Enter: () => handleEnter(this.options.editor), "Mod-Shift-7": () => { const blockInfo = getBlockInfoFromSelection(this.editor.state); if ( !blockInfo.isBlockContainer || blockInfo.blockContent.node.type.spec.content !== "inline*" ) { return true; } return this.editor.commands.command( updateBlockCommand(blockInfo.bnBlock.beforePos, { type: "numberedListItem", props: {}, }), ); }, }; }, addProseMirrorPlugins() { return [NumberedListIndexingPlugin()]; }, parseHTML() { return [ // Parse from internal HTML. { tag: "div[data-content-type=" + this.name + "]", contentElement: ".bn-inline-content", }, // Parse from external HTML. { tag: "li", getAttrs: (element) => { if (typeof element === "string") { return false; } const parent = element.parentElement; if (parent === null) { return false; } if ( parent.tagName === "OL" || (parent.tagName === "DIV" && parent.parentElement?.tagName === "OL") ) { const startIndex = parseInt(parent.getAttribute("start") || "1") || 1; if (element.previousSibling || startIndex === 1) { return {}; } return { start: startIndex, }; } return false; }, // As `li` elements can contain multiple paragraphs, we need to merge their contents // into a single one so that ProseMirror can parse everything correctly. getContent: (node, schema) => getListItemContent(node, schema, this.name), priority: 300, node: "numberedListItem", }, ]; }, renderHTML({ HTMLAttributes }) { return createDefaultBlockDOMOutputSpec( this.name, // We use a <p> tag, because for <li> tags we'd need an <ol> element to // put them in to be semantically correct, which we can't have due to the // schema. "p", { ...(this.options.domAttributes?.blockContent || {}), ...HTMLAttributes, }, this.options.domAttributes?.inlineContent || {}, ); }, }); export const NumberedListItem = createBlockSpecFromStronglyTypedTiptapNode( NumberedListItemBlockContent, numberedListItemPropSchema, );