🚀 BlockNote AI is here! Access the early preview.
BlockNote Docs/Features/自定义模式/自定义区块

自定义区块类型

除了 BlockNote 提供的默认区块类型外,您还可以使用 React 组件自行创建自定义区块。请看下面的演示示例,我们在 BlockNote 编辑器中添加了一个自定义警告区块,同时添加了一个自定义的 斜杠菜单项 用于插入该区块。

创建自定义区块类型

使用 createReactBlockSpec 函数来创建自定义区块类型。该函数接受三个参数:

function createReactBlockSpec(
  blockConfig: CustomBlockConfig,
  blockImplementation: ReactCustomBlockImplementation,
  extensions?: BlockNoteExtension[],
): (options? BlockOptions) => BlockSpec;

它返回一个函数,您可以调用该函数来创建自定义区块的实例,或者创建一个 BlockSpec。该 BlockSpec 随后会传入您的 BlockNote schema,以将区块添加到编辑器中。此函数也可以接受任意选项,您可以在下方了解更多相关内容。

让我们来看演示中的自定义警告区块,并逐一说明各字段的作用:

const createAlert = createReactBlockSpec(
  {
    type: "alert",
    propSchema: {
      textAlignment: defaultProps.textAlignment,
      textColor: defaultProps.textColor,
      type: {
        default: "warning",
        values: ["warning", "error", "info", "success"],
      },
    },
    content: "inline",
  },
  {
    render: (props) => {
      ...
    },
  }
);

区块配置 (CustomBlockConfig)

区块配置描述了自定义区块的结构。用它来指定区块的类型、属性(props)以及内容支持:

type BlockConfig = {
  type: string;
  content: "inline" | "none";
  readonly propSchema: PropSchema;
};

type: 定义自定义区块的标识符。

content: 如果您的自定义区块应支持富文本内容,则设置为 inline,否则设置为 none

在警告示例中,我们希望用户可以在警告区块中输入文本,因此将 content 设为 "inline"

propSchema: PropSchema 指定区块支持的属性。区块属性(props)是存储在文档中的数据,可用于自定义其外观或行为。

type PropSchema<PrimitiveType extends "boolean" | "number" | "string"> = Record<
  string,
  | {
      default: PrimitiveType;
      values?: PrimitiveType[];
    }
  | {
      default: undefined;
      type: PrimitiveType;
      values?: PrimitiveType[];
    }
>;

[key: string] 是属性的名称。如果您希望它有默认值,应定义为包含以下属性的对象:

  • default: 指定属性的默认值,由此推断出 PrimitiveType

  • values?: 指定属性可取的值数组,例如用于限制值为预定义字符串列表。如果未定义 values,BlockNote 会假设该属性可以为任意 PrimitiveType 的值。

如果您不希望该属性有默认值,可以定义为包含以下属性的对象:

  • default: 置为 undefined,表示无默认值。

  • type: 指定该属性允许的 PrimitiveType,因为默认值为 undefined 无法推断类型。

  • values?: 指定该属性可取的值数组,用于限制为预定义字符串列表。如果未定义 values,BlockNote 会假设该属性可以为任意 PrimitiveType 的值。

在警告示例中,我们添加了名为 type 的属性,用于指定警告类型(警告 / 错误 / 信息 / 成功),还添加了从默认区块属性继承的文本对齐和文本颜色属性,以支持基本样式选项。

isSelectable?: 可以设置为 false,使区块不可被鼠标和键盘选中。这也有助于能选中区块中不可编辑的内容。仅当 contentnone 时才应设置为 false,默认为 true。

hardBreakShortcut?: 定义用于在区块的内联内容中插入硬换行的键盘快捷键。默认为 "shift+enter"

文件区块配置

区块实现 (ReactCustomBlockImplementation)

区块实现定义了区块在编辑器中的渲染方式,以及如何从 HTML 解析和转换为 HTML。

type ReactCustomBlockImplementation = {
  render: React.FC<{
    block: Block;
    editor: BlockNoteEditor;
    contentRef?: (node: HTMLElement | null) => void;
  }>;
  toExternalHTML?: React.FC<{
    block: Block;
    editor: BlockNoteEditor;
    contentRef?: (node: HTMLElement | null) => void;
  }>;
  parse?: (element: HTMLElement) => PartialBlock["props"] | undefined;
  runsBefore?: string[];
  meta?: {
    hardBreakShortcut?: "shift+enter" | "enter" | "none";
    selectable?: boolean;
    fileBlockAccept?: string[];
    code?: boolean;
    defining?: boolean;
    isolating?: boolean;
  };
};

render: 这是您的 React 组件,定义了自定义区块在编辑器中的渲染方式,接收三个 React props:

  • block: 要渲染的区块。其类型和属性将与区块配置中定义的类型和 PropSchema 匹配。

  • editor: 区块所在的 BlockNote 编辑器实例。

  • contentRef: 一个 React ref,可用来标记区块中哪些元素可编辑,仅当区块配置包含 content: "inline" 时可用。

toExternalHTML?: 当区块导出到 HTML(例如复制到剪贴板)时使用的组件。如果未定义,BlockNote 会使用 render 进行 HTML 转换。接收与 render 相同的 props。

请注意,传入 toExternalHTML 的组件会在一个独立的 React 根实例中渲染和序列化,因此无法使用依赖于 React 上下文的 Hooks。

parse?: parse 函数定义了如何将 HTML 内容解析为您的区块,例如从剪贴板粘贴内容时调用。如果元素应解析为您的自定义区块,则返回该区块应有的 props;否则返回 undefined。接收一个参数:

  • element: 正在解析的 HTML 元素。

runsBefore?: 如果该区块的解析或扩展需要优先于其他区块,可在此传入它们的 type 数组。

meta?: 用于设置区块的各种通用属性的对象。

  • hardBreakShortcut?: 定义用于在区块的内联内容中插入硬换行的键盘快捷键。默认为 "shift+enter"

  • selectable?: 可以设置为 false,使区块不可被鼠标和键盘选中。这也有助于能选中区块中不可编辑的内容。仅当 contentnone 时才应设置为 false,默认为 true。

  • fileBlockAccept?: 针对自定义的文件区块,指定上传文件时接受的 MIME 类型。所有文件区块都应指定该属性,并在 render 函数中使用 FileBlockWrapper / ResizableFileBlockWrapper 组件(详见下一小节)。

  • code?: 该区块是否包含 代码

  • defining?: 该区块是否为 defining

  • isolating?: 该区块是否为 isolating

区块扩展

尽管本页示例未使用,createReactBlockSpec 接受第三个可选参数 extensions。用于添加特定于该区块的编辑器 extensions,您可以在 此处 了解更多。

区块扩展通常用于添加键盘快捷键,以便将当前区块类型切换为自定义区块。例如,一个目录区块的扩展可能会添加一个 ProseMirror 插件,用以扫描标题并生成目录。

区块配置选项

在某些情况下,您可能希望区块配置可自定义。例如,您可能希望代码区块支持不同的语法高亮(如 web 或嵌入式代码),或标题区块支持灵活的标题级别数。您可以对该用例使用相同的 API,做一些小改动:

// 区块可接受的任意选项,例如标题级别数或可用的代码高亮语言。
type CustomBlockConfigOptions = {
  ...
}

const createCustomBlock = createReactBlockSpec(
  createBlockConfig((options: CustomBlockConfigOptions) => ({
    type: "customBlock"
    propSchema: ...,
    content: ...,
  })),
  (options: CustomBlockConfigOptions) => ({
    render: ...,
    ...
  })
)

const options: CustomBlockConfigOptions = {
  ...
};

const schema = BlockNoteSchema.create().extend({
  blockSpecs: {
    // 创建定制区块的实例,并添加进 schema。
    customBlock: createCustomBlock(options),
  },
});

你可以看到,我们不再为配置和实现传入普通对象,而是传入函数。这些函数接收区块选项作为参数,并分别返回配置和实现对象。另外,用于创建配置的函数被 createBlockConfig 包裹。

另外请注意,本页示例中,我们通过调用 createAlert()(无参数)创建了新的 Alert 区块实例。而对自定义区块若有选项,则可按上述方式传入。

要查看使用区块选项的完整示例,请参考 内置标题区块

将自定义区块添加到编辑器

最后,使用您定义的自定义区块创建一个 BlockNoteSchema:

const schema = BlockNoteSchema.create({
  blockSpecs: {
    // 如需启用默认区块,请使用展开语法
    ...defaultBlockSpecs,

    // 添加您自己的自定义区块:
    alert: createAlert(),
  },
});

然后您可以按照自定义 Schema页面中说明的,使用此自定义 Schema 实例化您的编辑器。

改善用户体验

现在您已经知道如何创建自定义区块并将其添加到编辑器,但用户尚无方式往文档中创建该区块实例。

为了解决这个问题,建议实现一个命令以在斜杠菜单中插入您的自定义区块,以及在区块类型选择器中为其添加一个条目