项目

tiptap 命令

引言

编辑器提供了大量的命令,可以通过编程方式添加或更改内容,或者改变选择。如果你想要构建自己的编辑器,了解它们至关重要。

执行命令

所有可用的命令都可以通过编辑器实例访问。假设你想在用户点击按钮时使选中的文本加粗,以下是实现方式:

editor.commands.setBold();

虽然这确实会将选中的文本设置为粗体,但通常你可能希望一次组合多个命令。让我们看看它是如何工作的。

组合命令

大多数命令可以合并到一个调用中。在大多数情况下,这比单独的函数调用更简洁。这里有一个示例,使选中的文本加粗:

editor.chain().focus().toggleBold().run();

.chain() 用于开始一个新的命令链,而 .run() 是执行链中所有命令所必需的。

在上面的例子中,两个不同的命令同时执行。当用户点击内容区域之外的按钮时,编辑器不再聚焦。因此,你可能想在大多数命令中添加一个 .focus() 调用,以便用户可以继续输入。

所有的链式命令类似排队等待执行。它们组合成一个单一的事务。这意味着内容只更新一次,而且 update 事件也只会触发一次。

警告
默认情况下,Prosemirror **不支持命令链**,这意味着你需要使用 事务映射 在命令链之间更新位置。

例如,如果你想在一个链中组合删除插入命令,你需要在命令中跟踪位置。下面是一个例子:

// 在这里,我们添加了两个自定义命令来演示两个事务步骤之间的事务映射
addCommands() {
  return {
    delete: () => ({ tr }) => {
      const { $from, $to } = tr.selection

      // 使用tr.mapping.map在事务步骤之间映射位置
      const from = tr.mapping.map($from.pos)
      const to = tr.mapping.map($to.pos)

      tr.delete(from, to)

      return true
    },
    insert: (content: string) => ({ tr }) => {
      const { $from } = tr.selection

      // 使用tr.mapping.map在事务步骤之间映射位置
      const pos = tr.mapping.map($from.pos)

      tr.insertText(content, pos)

      return true
    },
  }
}

现在,你可以这样做而不必让 insert 命令将内容插入错误的位置:

editor.chain().delete().insert("foo").run();

在自定义命令中嵌套命令

当在命令中嵌套命令时,事务会被挂起。如果你想在自定义命令中嵌套命令,你需要使用这个事务并添加到其中。这是如何操作的:

addCommands() {
  return {
    customCommand: attributes => ({ chain }) => {
      // 这不工作:
      // return editor.chain() ...

      // 这个可行:
      return chain()
        .insertContent('foo!')
        .insertContent('bar!')
        .run()
    },
  }
}

内置命令

在某些情况下,将一些逻辑放入命令中是有帮助的。这就是为什么你可以在命令中执行命令。我知道这听起来很疯狂,但让我们看一个例子:

editor
  .chain()
  .focus()
  .command(({ tr }) => {
    // 操作事务
    tr.insertText("嘿,真酷!");

    return true;
  })
  .run();

干运行命令

有时,你不想实际运行命令,而是想知道是否可以运行,比如在菜单中显示或隐藏按钮。这就是 .can() 的作用。之后的所有内容都将被执行,但不会对文档应用更改:

editor.can().toggleBold();

你也可以将其与 .chain() 一起使用。这是一个示例,检查是否可以应用所有命令:

editor.can().chain().toggleBold().toggleItalic().run();

如果所有命令都能执行,这两个调用都会返回 true,否则返回 false

为了使 .can() 与你的自定义命令一起工作,别忘了返回 truefalse

对于一些你自己的命令,你可能希望与原始的 事务 一起工作。为了使其与 .can() 一起工作,你应该检查事务是否应该被分发。以下是创建一个简单的 .insertText() 命令的方式:

export default (value) =>
  ({ tr, dispatch }) => {
    if (dispatch) {
      tr.insertText(value);
    }

    return true;
  };

如果你只是包裹另一个 Tiptap 命令,我们会在内部处理是否需要检查。

addCommands() {
  return {
    bold: () => ({ commands }) => {
      return commands.toggleMark('bold')
    },
  }
}

如果你只是包裹一个普通的 ProseMirror 命令,无论如何都需要传递 dispatch。在这种情况下,就没有必要检查它:

import { exitCode } from "@tiptap/pm/commands";

export default () =>
  ({ state, dispatch }) => {
    return exitCode(state, dispatch);
  };

尝试命令

如果你想运行一系列命令,但只想应用第一个成功的命令,可以使用 .first() 方法。此方法会依次运行每个命令,直到找到第一个返回 true 的命令。

例如,退格键首先尝试撤销输入规则。如果成功,它就停止。如果没有应用过输入规则,无法撤销,它就会运行下一个命令,并删除(如果有)选区。这里是简化后的示例:

editor.first(({ commands }) => [
  () => commands.undoInputRule(),
  () => commands.deleteSelection(),
  // ...
]);

在命令中也可以做同样的事情:

export default () =>
  ({ commands }) => {
    return commands.first([
      () => commands.undoInputRule(),
      () => commands.deleteSelection(),
      // ...
    ]);
  };

命令列表

查看下面列出的所有核心命令。它们应该能给你一个初步的印象,了解什么是可能的。

内容

命令 描述 链接
clearContent() 清除整个文档。 更多
insertContent() 在当前位置插入节点或 HTML 字符串。 更多
insertContentAt() 在特定位置插入节点或 HTML 字符串。 更多
setContent() 用新内容替换整个文档。 更多

节点与标记

命令 描述 链接
clearNodes() 将节点规范化为简单的段落。 更多
createParagraphNear() 在附近创建一个段落。 更多
deleteNode() 删除节点。 更多
extendMarkRange() 将文本选择扩展到当前标记。 更多
exitCode() 退出代码块。 更多
joinBackward() 后向连接两个节点。 更多
joinForward() 前向连接两个节点。 更多
lift() 移除现有的包裹。 更多
liftEmptyBlock() 如果块为空,则提升块。 更多
newlineInCode() 在代码中添加换行符。 更多
resetAttributes() 将节点或标记的一些属性重置为默认值。 更多
setMark() 添加具有新属性的标记。 更多
setNode() 替换给定范围内的节点。 更多
splitBlock() 从现有节点中分叉一个新的节点。 更多
toggleMark() 开关标记。 更多
toggleNode() 用另一个节点切换节点。 更多
toggleWrap() 将节点包裹在另一个节点中,或移除现有的包裹。 更多
undoInputRule() 撤销输入规则。 更多
unsetAllMarks() 移除当前选区中的所有标记。 更多
unsetMark() 移除当前选区中的一个标记。 更多
updateAttributes() 更新节点或标记的属性。 更多

列表

命令 描述 链接
liftListItem() 将列表项提升到包裹列表中。 更多
sinkListItem() 将列表项下沉到内部列表中。 更多
splitListItem() 将一个列表项分割成两个列表项。 更多
toggleList() 在不同列表类型之间切换。 更多
wrapInList() 将节点包裹在列表中。 更多

选区

命令 描述 链接
blur() 从编辑器中移除焦点。 更多
deleteRange() 删除指定范围。 更多
deleteSelection() 删除选区(如果有)。 更多
enter() 触发 enter。 更多
focus() 在给定位置聚焦编辑器。 更多
keyboardShortcut() 触发键盘快捷键。 更多
scrollIntoView() 将选区滚动到视图中。 更多
selectAll() 选择整个文档。 更多
selectNodeBackward() 向后选择节点。 更多
selectNodeForward() 向前选择节点。 更多
selectParentNode() 选择父节点。 更多
setNodeSelection() 创建 NodeSelection。 更多
setTextSelection() 创建 TextSelection。 更多

自定义命令

所有扩展都可以添加额外的命令(大多数都这样做),查看提供的 节点标记扩展 的具体文档来了解更多信息。当然,你也可以通过 自定义扩展 添加带有自定义命令的功能。

但如何编写这些命令呢?这里有一些需要学习的知识。

注意,这还在进行中
一份完善的文档需要细致的编写,对项目有深入的理解,也需要投入时间去撰写。
尽管 Tiptap 被全球数千名开发者使用,但它对我们来说仍然是一个副业项目。让我们改变这一点,让开源成为我们的全职工作!目前我们已经获得了近 300 位赞助商的支持,已经走了一半的路。
加入他们,成为我们的赞助者!让我们有更多时间投入到开源中,我们会为你更新这个页面并保持它的及时性。
在 GitHub 上成为赞助者 →

在本文档中