tiptap/docs/api/commands.md

324 lines
14 KiB
Markdown
Raw Normal View History

---
tableOfContents: true
---
2020-04-22 15:23:53 +08:00
# Commands
## Introduction
2021-04-24 23:51:54 +08:00
The editor provides a ton of commands to programmatically add or change content or alter the selection. If you want to build your own editor you definitely want to learn more about them.
2020-10-02 22:56:02 +08:00
## Execute a command
All available commands are accessible through an editor instance. Lets say you want to make text bold when a user clicks on a button. Thats how that would look like:
```js
2020-11-18 23:59:58 +08:00
editor.commands.setBold()
2020-10-02 22:56:02 +08:00
```
2023-05-11 06:50:14 +08:00
While thats perfectly fine and does make the selected bold, youd likely want to chain multiple commands in one run. Lets have a look at how that works.
2020-11-05 23:02:12 +08:00
### Chain commands
2020-11-12 22:31:44 +08:00
Most commands can be combined to one call. Thats shorter than separate function calls in most cases. Here is an example to make the selected text bold:
```js
2020-11-17 22:07:05 +08:00
editor
.chain()
.focus()
2020-11-18 04:47:39 +08:00
.toggleBold()
2020-11-17 22:07:05 +08:00
.run()
```
2020-11-12 22:31:44 +08:00
The `.chain()` is required to start a new chain and the `.run()` is needed to actually execute all the commands in between.
2020-10-02 22:56:02 +08:00
2020-11-12 22:31:44 +08:00
In the example above two different commands are executed at once. When a user clicks on a button outside of the content, the editor isnt in focus anymore. Thats why you probably want to add a `.focus()` call to most of your commands. It brings back the focus to the editor, so the user can continue to type.
2020-10-02 22:56:02 +08:00
All chained commands are kind of queued up. They are combined to one single transaction. That means, the content is only updated once, also the `update` event is only triggered once.
:::warning Important
By default Prosemirror **does not support chaining** which means that you need to update the positions between chained commands via [**Transaction mapping**](https://prosemirror.net/docs/ref/#transform.Mapping).
:::
For example you want to chain a **delete** and **insert** command in one chain, you need to keep track of the position inside your chain commands. Here is an example:
```js
// here we add two custom commands to the editor to demonstrate transaction mapping between two transaction steps
addCommands() {
return {
delete: () => ({ tr }) => {
const { $from, $to } = tr.selection
// here we use tr.mapping.map to map the position between transaction steps
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
// here we use tr.mapping.map to map the position between transaction steps
const pos = tr.mapping.map($from.pos)
tr.insertText(content, pos)
return true
},
}
}
```
Now you can do the following without `insert` inserting the content into the wrong position:
```js
editor.chain().delete().insert('foo').run()
```
2021-04-03 21:21:30 +08:00
#### Chaining inside custom commands
When chaining a command, the transaction is held back. If you want to chain commands inside your custom commands, youll need to use said transaction and add to it. Here is how you would do that:
```js
addCommands() {
return {
2021-04-21 16:22:59 +08:00
customCommand: attributes => ({ chain }) => {
2021-04-03 21:21:30 +08:00
// Doesnt work:
// return editor.chain() …
// Does work:
return chain()
.insertContent('foo!')
.insertContent('bar!')
2021-04-03 21:21:30 +08:00
.run()
},
}
}
```
2020-11-17 22:07:05 +08:00
### Inline commands
In some cases, its helpful to put some more logic in a command. Thats why you can execute commands in commands. I know, that sounds crazy, but lets look at an example:
```js
editor
.chain()
.focus()
2020-11-17 22:15:21 +08:00
.command(({ tr }) => {
// manipulate the transaction
2021-04-19 05:21:52 +08:00
tr.insertText('hey, thats cool!')
2020-11-17 22:15:21 +08:00
return true
2020-11-17 22:07:05 +08:00
})
.run()
```
2020-11-05 23:02:12 +08:00
### Dry run for commands
Sometimes, you dont want to actually run the commands, but only know if it would be possible to run commands, for example to show or hide buttons in a menu. Thats what we added `.can()` for. Everything coming after this method will be executed, without applying the changes to the document:
2020-11-05 21:39:28 +08:00
```js
2020-11-17 22:07:05 +08:00
editor
.can()
2020-11-18 04:47:39 +08:00
.toggleBold()
2020-11-05 21:39:28 +08:00
```
2020-11-05 23:02:12 +08:00
And you can use it together with `.chain()`, too. Here is an example which checks if its possible to apply all the commands:
```js
2020-11-17 22:07:05 +08:00
editor
.can()
.chain()
2020-11-18 04:47:39 +08:00
.toggleBold()
2020-11-18 04:49:23 +08:00
.toggleItalic()
2020-11-17 22:07:05 +08:00
.run()
2020-11-05 23:02:12 +08:00
```
Both calls would return `true` if its possible to apply the commands, and `false` in case its not.
In order to make that work with your custom commands, dont forget to return `true` or `false`.
2021-04-21 21:41:27 +08:00
For some of your own commands, you probably want to work with the raw [transaction](/api/introduction). To make them work with `.can()` you should check if the transaction should be dispatched. Here is how you can create a simple `.insertText()` command:
```js
2021-04-21 16:22:59 +08:00
export default (value) => ({ tr, dispatch }) => {
if (dispatch) {
tr.insertText(value)
}
return true
}
```
2021-10-20 04:30:45 +08:00
If youre just wrapping another Tiptap command, you dont need to check that, well do it for you.
```js
2021-04-21 16:22:59 +08:00
addCommands() {
return {
bold: () => ({ commands }) => {
return commands.toggleMark('bold')
},
}
}
```
2021-04-21 16:22:59 +08:00
If youre just wrapping a plain ProseMirror command, youll need to pass `dispatch` anyway. Then theres also no need to check it:
```js
feat(pm): new prosemirror package for dependency resolving * chore:(core): migrate to tsup * chore: migrate blockquote and bold to tsup * chore: migrated bubble-menu and bullet-list to tsup * chore: migrated more packages to tsup * chore: migrate code and character extensions to tsup * chore: update package.json to simplify build for all packages * chore: move all packages to tsup as a build process * chore: change ci build task * feat(pm): add prosemirror meta package * rfix: resolve issues with build paths & export mappings * docs: update documentation to include notes for @tiptap/pm * chore(pm): update tsconfig * chore(packages): update packages * fix(pm): add package export infos & fix dependencies * chore(general): start moving to pm package as deps * chore: move to tiptap pm package internally * fix(demos): fix demos working with new pm package * fix(tables): fix tables package * fix(tables): fix tables package * chore(demos): pinned typescript version * chore: remove unnecessary tsconfig * chore: fix netlify build * fix(demos): fix package resolving for pm packages * fix(tests): fix package resolving for pm packages * fix(tests): fix package resolving for pm packages * chore(tests): fix tests not running correctly after pm package * chore(pm): add files to files array * chore: update build workflow * chore(tests): increase timeout time back to 12s * chore(docs): update docs * chore(docs): update installation guides & pm information to docs * chore(docs): add link to prosemirror docs * fix(vue-3): add missing build step * chore(docs): comment out cdn link * chore(docs): remove semicolons from docs * chore(docs): remove unnecessary installation note * chore(docs): remove unnecessary installation note
2023-02-03 00:37:33 +08:00
import { exitCode } from '@tiptap/pm/commands'
2021-04-21 16:22:59 +08:00
export default () => ({ state, dispatch }) => {
return exitCode(state, dispatch)
}
```
2020-11-05 23:02:12 +08:00
### Try commands
2020-11-18 23:28:45 +08:00
If you want to run a list of commands, but want only the first successful command to be applied, you can do this with the `.first()` method. This method runs one command after the other and stops at the first which returns `true`.
2020-11-05 23:02:12 +08:00
For example, the backspace key tries to undo an input rule first. If that was successful, it stops there. If no input rule has been applied and thus cant be reverted, it runs the next command and deletes the selection, if there is one. Here is the simplified example:
2020-11-05 21:39:28 +08:00
```js
2020-11-18 23:28:45 +08:00
editor.first(({ commands }) => [
2020-11-05 23:02:12 +08:00
() => commands.undoInputRule(),
() => commands.deleteSelection(),
// …
2020-11-05 21:39:28 +08:00
])
```
Inside of commands you can do the same thing:
2020-11-05 23:02:12 +08:00
```js
2021-04-21 16:22:59 +08:00
export default () => ({ commands }) => {
return commands.first([
() => commands.undoInputRule(),
() => commands.deleteSelection(),
// …
])
}
2020-11-05 23:02:12 +08:00
```
2020-11-05 21:39:28 +08:00
2020-10-02 23:22:38 +08:00
## List of commands
Have a look at all of the core commands listed below. They should give you a good first impression of whats possible.
### Content
2021-05-06 01:42:47 +08:00
| Command | Description | Links |
| ------------------ | -------------------------------------------------------- | --------------------------------------- |
| clearContent() | Clear the whole document. | [More](/api/commands/clear-content) |
| insertContent() | Insert a node or string of HTML at the current position. | [More](/api/commands/insert-content) |
| insertContentAt() | Insert a node or string of HTML at a specific position. | [More](/api/commands/insert-content-at) |
| setContent() | Replace the whole document with new content. | [More](/api/commands/set-content) |
2020-10-02 23:22:38 +08:00
### Nodes & Marks
2021-04-21 17:03:33 +08:00
| Command | Description | Links |
| ----------------------- | --------------------------------------------------------- | ------------------------------------ |
| clearNodes() | Normalize nodes to a simple paragraph. | [More](/api/commands/clear-nodes) |
| createParagraphNear() | Create a paragraph nearby. | [More](/api/commands/create-paragraph-near) |
| deleteNode() | Delete a node. | [More](/api/commands/delete-node) |
| extendMarkRange() | Extends the text selection to the current mark. | [More](/api/commands/extend-mark-range) |
| exitCode() | Exit from a code block. | [More](/api/commands/exit-code) |
| joinBackward() | Join two nodes backward. | [More](/api/commands/join-backward) |
| joinForward() | Join two nodes forward. | [More](/api/commands/join-forward) |
| lift() | Removes an existing wrap. | [More](/api/commands/lift) |
| liftEmptyBlock() | Lift block if empty. | [More](/api/commands/lift-empty-block) |
| newlineInCode() | Add a newline character in code. | [More](/api/commands/newline-in-code) |
| resetAttributes() | Resets some node or mark attributes to the default value. | [More](/api/commands/reset-attributes) |
| setMark() | Add a mark with new attributes. | [More](/api/commands/set-mark) |
| setNode() | Replace a given range with a node. | [More](/api/commands/set-node) |
| splitBlock() | Forks a new node from an existing node. | [More](/api/commands/split-block) |
| toggleMark() | Toggle a mark on and off. | [More](/api/commands/toggle-mark) |
| toggleNode() | Toggle a node with another node. | [More](/api/commands/toggle-node) |
| toggleWrap() | Wraps nodes in another node, or removes an existing wrap. | [More](/api/commands/toggle-wrap) |
| undoInputRule() | Undo an input rule. | [More](/api/commands/undo-input-rule) |
| unsetAllMarks() | Remove all marks in the current selection. | [More](/api/commands/unset-all-marks) |
| unsetMark() | Remove a mark in the current selection. | [More](/api/commands/unset-mark) |
| updateAttributes() | Update attributes of a node or mark. | [More](/api/commands/update-attributes) |
2020-09-26 04:37:44 +08:00
2020-10-02 23:22:38 +08:00
### Lists
2021-04-21 17:03:33 +08:00
| Command | Description | Links |
| ---------------- | ------------------------------------------- | ------------------------------------ |
| liftListItem() | Lift the list item into a wrapping list. | [More](/api/commands/lift-list-item) |
| sinkListItem() | Sink the list item down into an inner list. | [More](/api/commands/sink-list-item) |
| splitListItem() | Splits one list item into two list items. | [More](/api/commands/split-list-item) |
| toggleList() | Toggle between different list types. | [More](/api/commands/toggle-list) |
| wrapInList() | Wrap a node in a list. | [More](/api/commands/wrap-in-list) |
2020-10-02 23:22:38 +08:00
### Selection
2021-04-21 17:03:33 +08:00
| Command | Description | Links |
| --------------------- | --------------------------------------- | ------------------------------------ |
| blur() | Removes focus from the editor. | [More](/api/commands/blur) |
| deleteRange() | Delete a given range. | [More](/api/commands/delete-range) |
| deleteSelection() | Delete the selection, if there is one. | [More](/api/commands/delete-selection) |
| enter() | Trigger enter. | [More](/api/commands/enter) |
| focus() | Focus the editor at the given position. | [More](/api/commands/focus) |
| keyboardShortcut() | Trigger a keyboard shortcut. | [More](/api/commands/keyboard-shortcut) |
| scrollIntoView() | Scroll the selection into view. | [More](/api/commands/scroll-into-view) |
| selectAll() | Select the whole document. | [More](/api/commands/select-all) |
| selectNodeBackward() | Select a node backward. | [More](/api/commands/select-node-backward) |
| selectNodeForward() | Select a node forward. | [More](/api/commands/select-node-forward) |
| selectParentNode() | Select the parent node. | [More](/api/commands/select-parent-node) |
| setNodeSelection() | Creates a NodeSelection. | [More](/api/commands/set-node-selection) |
| setTextSelection() | Creates a TextSelection. | [More](/api/commands/set-text-selection) |
2020-10-02 22:56:02 +08:00
2021-03-25 16:48:47 +08:00
<!-- ## Example use cases
2021-02-12 06:37:41 +08:00
### Quote a text
TODO
Add a blockquote, with a specified text, add a paragraph below, set the cursor there.
```js
// Untested, work in progress, likely to change
this.editor
.chain()
.focus()
.createParagraphNear()
.insertContent(text)
2021-02-12 06:37:41 +08:00
.setBlockquote()
.insertContent('<p></p>')
2021-02-12 06:37:41 +08:00
.createParagraphNear()
.unsetBlockquote()
.createParagraphNear()
.insertContent('<p></p>')
2021-02-12 06:37:41 +08:00
.run()
2021-04-03 21:21:30 +08:00
```
2021-02-12 06:37:41 +08:00
2021-04-03 21:21:30 +08:00
Add a custom command to insert a node.
```js
addCommands() {
return {
insertTimecode: attributes => ({ chain }) => {
return chain()
.focus()
.insertContent({
type: 'heading',
attrs: {
level: 2,
},
content: [
{
type: 'text',
text: 'heading',
},
],
})
2021-04-03 21:21:30 +08:00
.insertText(' ')
.run();
},
}
},
```
-->
2021-05-05 05:59:34 +08:00
## Write your own commands
All extensions can add additional commands (and most do), check out the specific [documentation for the provided nodes](/api/nodes), [marks](/api/marks), and [extensions](/api/extensions) to learn more about those. And of course, you can [add your custom extensions](/guide/custom-extensions) with custom commands aswell.
But how do you write those commands? Theres a little bit to learn about that.
:::pro Oops, this is work in progress
A well-written documentation needs attention to detail, a great understanding of the project and time to write.
2021-10-20 04:30:45 +08:00
Though Tiptap is used by thousands of developers all around the world, its still a side project for us. Lets change that and make open source our full-time job! With nearly 300 sponsors we are half way there already.
2021-05-05 05:59:34 +08:00
Join them and become a sponsor! Enable us to put more time into open source and well fill this page and keep it up to date for you.
[Become a sponsor on GitHub →](https://github.com/sponsors/ueberdosis)
:::
2020-11-05 23:02:12 +08:00
2021-04-03 21:21:30 +08:00