文档
操作块

操作块

下面,我们介绍 editor 上的方法,你可以使用它们从编辑器读取块,以及如何创建 / 删除 / 更新块:

常用类型

在深入方法之前,先讨论一些参数中常用的类型:

块标识符

访问、插入、更新、删除或替换块的方法,可能需要一个 BlockIdentifier 作为文档中已有块的引用。 这可以是表示块 ID 的字符串,或者是包含该 ID 的 Block 对象:

type BlockIdentifier = string | Block;

部分块(Partial Blocks)

当从编辑器获取块时,始终返回完整的 Block 对象。 对于更新或创建块,你不需要传递所有属性,可以使用 PartialBlock 类型:

type PartialBlock = {
  id?: string;
  type?: string;
  props?: Partial<Record<string, any>>; // 精确类型依赖于 "type"
  content?: string | InlineContent[] | TableContent;
  children?: PartialBlock[];
};

PartialBlock 对象与普通 Block 对象非常相似,但所有成员都是可选的,且 props 是部分的。这样可以简化更新或创建简单块的操作。我们后面会看到示例。

访问块

有几种不同的方法可以从编辑器中检索块:

获取文档

使用以下调用获取编辑器中文档的快照(所有顶层、非嵌套块):

document: Block[];
 
// 用法
const blocks = editor.document;

returns: 文档;编辑器中所有顶层(非嵌套)块的快照。

我们曾在 Document JSON 示例中使用过它。

获取特定块

单个特定块

使用 getBlock 获取编辑器中特定块的快照:

getBlock(blockIdentifier: BlockIdentifier): Block | undefined;
 
// 用法
const block = editor.getBlock(blockIdentifier);

blockIdentifier: 要检索的现有块的标识符

returns: 具有相应标识符的块,如果未找到匹配块则返回 undefined

上一个块

使用 getPrevBlock 获取指定块的前一个同级块的快照:

getPrevBlock(blockIdentifier: BlockIdentifier): Block | undefined;
 
// 用法
const prevBlock = editor.getPrevBlock(blockIdentifier);

blockIdentifier: 要获取前一个同级块的现有块的标识符

returns: 该块的前一个同级块,若未找到匹配块,或者该块是文档中的第一个块或父块的第一个子块,则返回 undefined

下一个块

使用 getNextBlock 获取指定块的下一个同级块的快照:

getNextBlock(blockIdentifier: BlockIdentifier): Block | undefined;
 
// 用法
const nextBlock = editor.getNextBlock(blockIdentifier);

blockIdentifier: 要获取下一个同级块的现有块的标识符

returns: 该块的下一个同级块,若未找到匹配块,或者该块是文档中的最后一个块或父块的最后一个子块,则返回 undefined

父块

使用 getParentBlock 获取指定块的父块快照:

getParentBlock(blockIdentifier: BlockIdentifier): Block | undefined;
 
// 用法
const parentBlock = editor.getParentBlock(blockIdentifier);

blockIdentifier: 要获取父块的现有块的标识符

returns: 该块的父块,若未找到匹配块,或者该块不嵌套在任何父块中,则返回 undefined

遍历所有块

使用 forEachBlock 深度优先遍历编辑器中的所有块,并对每个块执行回调:

forEachBlock(
  callback: (block: Block) => boolean | undefined,
  reverse: boolean = false
): void;
 
// 用法
editor.forEachBlock((block) => {...});

callback: 对每个块执行的回调函数。返回 false 可以停止遍历。

reverse: 是否反向遍历块。

获取鼠标悬停 / 选中的块

查看 光标与选择 以了解如何获取用户正在交互的块。

插入新块

使用 insertBlocks 将新块插入文档:

insertBlocks(
  blocksToInsert: PartialBlock[],
  referenceBlock: BlockIdentifier,
  placement: "before" | "after" = "before"
): void;
 
// 用法
editor.insertBlocks([{type: "paragraph", content: "Hello World"}], referenceBlock, placement)

blocksToInsert: 要插入的一组部分块

referenceBlock: 用作插入位置参考的现有块的标识符

placement: 新块是插入在 referenceBlock 之前还是之后。

如果块的 id 未定义,BlockNote 会自动生成一个。

如果找不到参考块,该方法会抛出错误。

更新块

使用 updateBlock 更新现有块:

updateBlock(
  blockToUpdate: BlockIdentifier,
  update: PartialBlock
): void;
 
// 示例:将块类型改为段落
editor.updateBlock(blockToUpdate, { type: "paragraph" });

blockToUpdate: 要更新的现有块的标识符

update: 一个定义了如何更改现有块的部分块

因为 blockToUpdatePartialBlock 对象,某些字段可能未定义,这些未定义字段会保持现有块的值。

如果未找到要更新的块,会抛出错误。

删除块

使用 removeBlocks 删除文档中的现有块:

removeBlocks(
  blocksToRemove: BlockIdentifier[],
): void;
 
// 用法
editor.removeBlocks(blocksToRemove)

blocksToRemove: 要删除的现有块的标识符数组。

如果找不到任一需要删除的块,方法会抛出错误。

替换块

使用 replaceBlocks 用新块替换编辑器中的现有块:

replaceBlocks(
  blocksToRemove: BlockIdentifier[],
  blocksToInsert: PartialBlock[],
): void;
 
// 用法
editor.replaceBlocks(blocksToRemove, blocksToInsert)

blocksToRemove: 要替换的现有块的标识符数组。

blocksToInsert: 替换用的新块数组,元素是部分块

若要删除的块不相邻或处于不同嵌套层级,blocksToInsert 会插入到 blocksToRemove 中第一个块的位置。

如果找不到任一要删除的块,会抛出错误。

向上 / 向下移动块

向上移动

使用 moveBlocksUp 向上移动选中的块:

moveBlocksUp(): void;
 
// 用法
editor.moveBlocksUp();

向下移动

使用 moveBlocksDown 向下移动选中的块:

moveBlocksDown(): void;
 
// 用法
editor.moveBlocksDown();

嵌套与取消嵌套块

BlockNote 还提供了函数,可以对包含文本光标的块进行嵌套和取消嵌套。

嵌套块

使用 canNestBlock 检查包含文本光标的块是否可以嵌套 (即其上方在相同嵌套层级存在一个块):

canNestBlock(): boolean;
 
// 用法
const canNestBlock = editor.canNestBlock();

然后,使用 nestBlock 实际对该块进行嵌套(缩进):

nestBlock(): void;
 
// 用法
editor.nestBlock();

取消嵌套块

使用 canUnnestBlock 检查包含文本光标的块是否可以取消嵌套(即是否嵌套在另一个块中):

canUnnestBlock(): boolean;
 
// 用法
const canUnnestBlock = editor.canUnnestBlock();

然后,使用 unnestBlock 对该块取消嵌套:

unnestBlock(): void;
 
// 用法
editor.unnestBlock();

低阶 API

BlockNote 事务

transact 方法用于对编辑器执行一系列更改。它可以将多个更改合并为单个撤销/重做操作。它也提供了读取状态和执行编辑器更改的低级 API。

transact<T>(callback: (tr: Transaction) => T): T;

callback: 一个接收 Transaction 对象并返回值的函数。

多个编辑器操作

transact 方法可以将多个编辑器操作分组为单个撤销/重做操作。

editor.transact(() => {
  // 这两个更改被合并为一个撤销/重做操作
  editor.insertBlocks([{ type: "paragraph", content: "Hello World" }], "root");
  editor.updateBlock("root", { type: "heading" });
});

读取状态

transact 方法也可以用来读取编辑器状态。

const isSelectionEmpty = editor.transact((tr) => {
  // 这里返回的值将作为 transact 的返回值
  return tr.selection.empty
});

执行更改

transact 方法还可以用来对编辑器执行更改。

editor.transact((tr) => {
  // 当 transact 方法返回时,这个 tr 会自动应用到编辑器
  tr.insertText("Hello World");
});

执行 ProseMirror 命令

exec 方法可用于执行一个 ProseMirror 命令。这主要是为了与 ProseMirror 命令的向后兼容。

尽可能应优先使用 transact 方法,因为它会自动处理事务的分发,并能配合 BlockNote 事务工作。此外 exec 方法不能在 transact 调用内部使用,因为两者会相互冲突。

exec(command: (state: EditorState, dispatch?: (tr: Transaction) => void, view?: EditorView) => boolean): boolean;

command: 一个接收当前编辑器状态和可选分发函数的命令函数。

返回 true 表示命令被执行,false 表示未执行。

editor.exec((state, dispatch, view) => {
  const tr = state.tr;
  if (dispatch) {
    tr.insertText("Hello World");
    dispatch(tr);
  }
  return true;
});

这将在光标当前位置插入文本 "Hello World"。

检查 ProseMirror 命令是否可执行

canExec 方法用来检查 ProseMirror 命令是否可以执行。

同样,尽可能应优先使用 transact 方法,因为它自动处理事务分发,且支持 BlockNote 事务。exec 不能在 transact 内部使用,避免冲突。

canExec(command: (state: EditorState, dispatch?: (tr: Transaction) => void, view?: EditorView) => boolean): boolean;

command: 接收当前编辑器状态和可选分发函数的命令函数。

返回 true 表示命令可以执行,false 表示不能执行。

const canReplaceSelection = editor.canExec((state, dispatch, view) => {
  if (state.selection.from === state.selection.to){
    return false;
  }
 
  if (dispatch) {
    dispatch(state.tr.insertText("Hello World"));
  }
  return true;
});

仅当光标不为空时,这将在编辑器中插入文本 "Hello World"。