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()
与你的自定义命令一起工作,别忘了返回 true
或 false
。
对于一些你自己的命令,你可能希望与原始的 事务 一起工作。为了使其与 .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 上成为赞助者 →