UNPKG

@blocknote/core

Version:

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

252 lines (228 loc) 8.59 kB
import { insertBlocks } from "../../api/blockManipulation/commands/insertBlocks/insertBlocks.js"; import { moveBlocksDown, moveBlocksUp, } from "../../api/blockManipulation/commands/moveBlocks/moveBlocks.js"; import { canNestBlock, canUnnestBlock, nestBlock, unnestBlock, } from "../../api/blockManipulation/commands/nestBlock/nestBlock.js"; import { removeAndInsertBlocks } from "../../api/blockManipulation/commands/replaceBlocks/replaceBlocks.js"; import { updateBlock } from "../../api/blockManipulation/commands/updateBlock/updateBlock.js"; import { getBlock, getNextBlock, getParentBlock, getPrevBlock, } from "../../api/blockManipulation/getBlock/getBlock.js"; import { docToBlocks } from "../../api/nodeConversions/nodeToBlock.js"; import { Block, DefaultBlockSchema, DefaultInlineContentSchema, DefaultStyleSchema, PartialBlock, } from "../../blocks/defaultBlocks.js"; import { BlockIdentifier, BlockSchema, InlineContentSchema, StyleSchema, } from "../../schema/index.js"; import { BlockNoteEditor } from "../BlockNoteEditor.js"; export class BlockManager< BSchema extends BlockSchema = DefaultBlockSchema, ISchema extends InlineContentSchema = DefaultInlineContentSchema, SSchema extends StyleSchema = DefaultStyleSchema, > { constructor(private editor: BlockNoteEditor<BSchema, ISchema, SSchema>) {} /** * Gets a snapshot of all top-level (non-nested) blocks in the editor. * @returns A snapshot of all top-level (non-nested) blocks in the editor. */ public get document(): Block<BSchema, ISchema, SSchema>[] { return this.editor.transact((tr) => { return docToBlocks(tr.doc, this.editor.pmSchema); }); } /** * Gets a snapshot of an existing block from the editor. * @param blockIdentifier The identifier of an existing block that should be * retrieved. * @returns The block that matches the identifier, or `undefined` if no * matching block was found. */ public getBlock( blockIdentifier: BlockIdentifier, ): Block<BSchema, ISchema, SSchema> | undefined { return this.editor.transact((tr) => getBlock(tr.doc, blockIdentifier)); } /** * Gets a snapshot of the previous sibling of an existing block from the * editor. * @param blockIdentifier The identifier of an existing block for which the * previous sibling should be retrieved. * @returns The previous sibling of the block that matches the identifier. * `undefined` if no matching block was found, or it's the first child/block * in the document. */ public getPrevBlock( blockIdentifier: BlockIdentifier, ): Block<BSchema, ISchema, SSchema> | undefined { return this.editor.transact((tr) => getPrevBlock(tr.doc, blockIdentifier)); } /** * Gets a snapshot of the next sibling of an existing block from the editor. * @param blockIdentifier The identifier of an existing block for which the * next sibling should be retrieved. * @returns The next sibling of the block that matches the identifier. * `undefined` if no matching block was found, or it's the last child/block in * the document. */ public getNextBlock( blockIdentifier: BlockIdentifier, ): Block<BSchema, ISchema, SSchema> | undefined { return this.editor.transact((tr) => getNextBlock(tr.doc, blockIdentifier)); } /** * Gets a snapshot of the parent of an existing block from the editor. * @param blockIdentifier The identifier of an existing block for which the * parent should be retrieved. * @returns The parent of the block that matches the identifier. `undefined` * if no matching block was found, or the block isn't nested. */ public getParentBlock( blockIdentifier: BlockIdentifier, ): Block<BSchema, ISchema, SSchema> | undefined { return this.editor.transact((tr) => getParentBlock(tr.doc, blockIdentifier), ); } /** * Traverses all blocks in the editor depth-first, and executes a callback for each. * @param callback The callback to execute for each block. Returning `false` stops the traversal. * @param reverse Whether the blocks should be traversed in reverse order. */ public forEachBlock( callback: (block: Block<BSchema, ISchema, SSchema>) => boolean, reverse = false, ): void { const blocks = this.document.slice(); if (reverse) { blocks.reverse(); } function traverseBlockArray( blockArray: Block<BSchema, ISchema, SSchema>[], ): boolean { for (const block of blockArray) { if (callback(block) === false) { return false; } const children = reverse ? block.children.slice().reverse() : block.children; if (!traverseBlockArray(children)) { return false; } } return true; } traverseBlockArray(blocks); } /** * Inserts new blocks into the editor. If a block's `id` is undefined, BlockNote generates one automatically. Throws an * error if the reference block could not be found. * @param blocksToInsert An array of partial blocks that should be inserted. * @param referenceBlock An identifier for an existing block, at which the new blocks should be inserted. * @param placement Whether the blocks should be inserted just before, just after, or nested inside the * `referenceBlock`. */ public insertBlocks( blocksToInsert: PartialBlock<BSchema, ISchema, SSchema>[], referenceBlock: BlockIdentifier, placement: "before" | "after" = "before", ) { return this.editor.transact((tr) => insertBlocks(tr, blocksToInsert, referenceBlock, placement), ); } /** * Updates an existing block in the editor. Since updatedBlock is a PartialBlock object, some fields might not be * defined. These undefined fields are kept as-is from the existing block. Throws an error if the block to update could * not be found. * @param blockToUpdate The block that should be updated. * @param update A partial block which defines how the existing block should be changed. */ public updateBlock( blockToUpdate: BlockIdentifier, update: PartialBlock<BSchema, ISchema, SSchema>, ) { return this.editor.transact((tr) => updateBlock(tr, blockToUpdate, update)); } /** * Removes existing blocks from the editor. Throws an error if any of the blocks could not be found. * @param blocksToRemove An array of identifiers for existing blocks that should be removed. */ public removeBlocks(blocksToRemove: BlockIdentifier[]) { return this.editor.transact( (tr) => removeAndInsertBlocks(tr, blocksToRemove, []).removedBlocks, ); } /** * Replaces existing blocks in the editor with new blocks. If the blocks that should be removed are not adjacent or * are at different nesting levels, `blocksToInsert` will be inserted at the position of the first block in * `blocksToRemove`. Throws an error if any of the blocks to remove could not be found. * @param blocksToRemove An array of blocks that should be replaced. * @param blocksToInsert An array of partial blocks to replace the old ones with. */ public replaceBlocks( blocksToRemove: BlockIdentifier[], blocksToInsert: PartialBlock<BSchema, ISchema, SSchema>[], ) { return this.editor.transact((tr) => removeAndInsertBlocks(tr, blocksToRemove, blocksToInsert), ); } /** * Checks if the block containing the text cursor can be nested. */ public canNestBlock() { return canNestBlock(this.editor); } /** * Nests the block containing the text cursor into the block above it. */ public nestBlock() { nestBlock(this.editor); } /** * Checks if the block containing the text cursor is nested. */ public canUnnestBlock() { return canUnnestBlock(this.editor); } /** * Lifts the block containing the text cursor out of its parent. */ public unnestBlock() { unnestBlock(this.editor); } /** * Moves the selected blocks up. If the previous block has children, moves * them to the end of its children. If there is no previous block, but the * current blocks share a common parent, moves them out of & before it. */ public moveBlocksUp() { return moveBlocksUp(this.editor); } /** * Moves the selected blocks down. If the next block has children, moves * them to the start of its children. If there is no next block, but the * current blocks share a common parent, moves them out of & after it. */ public moveBlocksDown() { return moveBlocksDown(this.editor); } }