mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-08-09 08:46:35 +02:00
update docs for removal of commands
This commit is contained in:
@@ -2,72 +2,73 @@
|
|||||||
|
|
||||||
While editing richtext content, your users will be doing things like inserting text, deleteing text, splitting paragraphs, adding formatting, etc. These edits are expressed using two concepts: commands and operations.
|
While editing richtext content, your users will be doing things like inserting text, deleteing text, splitting paragraphs, adding formatting, etc. These edits are expressed using two concepts: commands and operations.
|
||||||
|
|
||||||
Commands are the high-level actions that represent a specific intent of the user. Their interface is simply:
|
Commands are the high-level actions that represent a specific intent of the user. They are represented as helper functions on the `Editor` interface. A handful of helpers are included in core for common richtext behaviors, but you are encouraged to write your own that model your specific domain.
|
||||||
|
|
||||||
```ts
|
For example, here are some of the built-in commands:
|
||||||
interface Command {
|
|
||||||
type: string
|
|
||||||
[key: string]: any
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Slate defines and recognizes a handful of core commands out of the box for common richtext behaviors, like:
|
|
||||||
|
|
||||||
```js
|
```js
|
||||||
editor.exec({
|
Editor.insertText(editor, 'A new string of text to be inserted.')
|
||||||
type: 'insert_text',
|
|
||||||
text: 'A new string of text to be inserted.',
|
|
||||||
})
|
|
||||||
|
|
||||||
editor.exec({
|
Editor.deleteBackward(editor, { unit: 'word' })
|
||||||
type: 'delete_backward',
|
|
||||||
unit: 'character',
|
|
||||||
})
|
|
||||||
|
|
||||||
editor.exec({
|
Editor.insertBreak(editor)
|
||||||
type: 'insert_break',
|
|
||||||
})
|
|
||||||
```
|
```
|
||||||
|
|
||||||
But you can (and will!) also define your own custom commands that model your domain. For example, you might want to define a `wrap_quote` command, or a `insert_image` command, or a `format_bold` command depending on what types of content you allow.
|
But you can (and will!) also define your own custom commands that model your domain. For example, you might want to define a `formatQuote` command, or an `insertImage` command, or a `toggleBold` command depending on what types of content you allow.
|
||||||
|
|
||||||
Commands always describe an action to be taken as if the user themselves was performing the action. For that reason, they never need to define a location to perform the command, because they always act on the user's current selection.
|
Commands always describe an action to be taken as if the **user** themselves was performing the action. For that reason, they never need to define a location to perform the command, because they always act on the user's current selection.
|
||||||
|
|
||||||
> 🤖 The concept of commands are loosely based on the DOM's built-in [`execCommand`](https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand) APIs. However Slate defines its own simpler (and extendable!) version of the API, because the DOM's version is overly complex and has known versatility issues.
|
> 🤖 The concept of commands are loosely based on the DOM's built-in [`execCommand`](https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand) APIs. However Slate defines its own simpler (and extendable!) version of the API, because the DOM's version is too opinionated and inconsistent.
|
||||||
|
|
||||||
Under the covers, Slate takes care of converting each command into a set of low-level "operations" that are applied to produce a new value. This is what makes collaborative editing implementations possible. But you don't have to worry about that, because it happens automatically.
|
Under the covers, Slate takes care of converting each command into a set of low-level "operations" that are applied to produce a new value. This is what makes collaborative editing implementations possible. But you don't have to worry about that, because it happens automatically.
|
||||||
|
|
||||||
## Custom Commands
|
## Custom Commands
|
||||||
|
|
||||||
When defining custom commands, you'll always trigger them by calling the `editor.exec` function:
|
When defining custom commands, you can create your own namespace:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
// Call your custom "insert_image" command.
|
const MyEditor = {
|
||||||
editor.exec({
|
...Editor,
|
||||||
type: 'insert_image',
|
|
||||||
url: 'https://unsplash.com/photos/m0By_H6ofeE',
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
And then you define what their behaviors are by overriding the default `editor.exec` command:
|
insertParagraph(editor) {
|
||||||
|
// ...
|
||||||
```js
|
},
|
||||||
const withImages = editor => {
|
|
||||||
const { exec } = editor
|
|
||||||
|
|
||||||
editor.exec = command => {
|
|
||||||
if (command.type === 'insert_image') {
|
|
||||||
// Define your behaviors here...
|
|
||||||
} else {
|
|
||||||
// Call the existing behavior to handle any other commands.
|
|
||||||
exec(command)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return editor
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
This makes it easy for you to define a handful of commands specific to your problem domain.
|
When writing your own commands, you'll often make use of the `Transforms` helpers that ship with Slate.
|
||||||
|
|
||||||
But as you can see with the `withImages` function above, custom logic can be extracted into simple plugins that can be reused and shared with others. This is one of the powerful aspects of Slate's architecture.
|
## Transforms
|
||||||
|
|
||||||
|
Transforms are a specific set of helpers that allow you to perform a wide variety of specific changes to the document, for example:
|
||||||
|
|
||||||
|
```js
|
||||||
|
// Set a "bold" format on all of the text nodes in a range.
|
||||||
|
Transforms.setNodes(
|
||||||
|
editor,
|
||||||
|
{ bold: true },
|
||||||
|
{
|
||||||
|
at: range,
|
||||||
|
match: node => Text.isText(node),
|
||||||
|
split: true,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Wrap the lowest block at a point in the document in a quote block.
|
||||||
|
Transforms.wrapNodes(
|
||||||
|
editor,
|
||||||
|
{ type: 'quote', children: [] },
|
||||||
|
{
|
||||||
|
at: point,
|
||||||
|
match: node => Editor.isBlock(editor, node),
|
||||||
|
mode: 'lowest',
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Insert new text to replace the text in a node at a specific path.
|
||||||
|
Transforms.insertText(editor, 'A new string of text.', { at: path })
|
||||||
|
|
||||||
|
// ...there are many more transforms!
|
||||||
|
```
|
||||||
|
|
||||||
|
The transform helpers are designed to be composed together. So you might use a handful of them for each command.
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
# Operations
|
# Operations
|
||||||
|
|
||||||
Operations are the granular, low-level actions that occur while invoking commands. A single high-level command could result in many low-level operations being applied to the editor.
|
Operations are the granular, low-level actions that occur while invoking commands and transforms. A single high-level command could result in many low-level operations being applied to the editor.
|
||||||
|
|
||||||
Unlike commands, operations aren't extendable. Slate's core defines all of the possible operations that can occur on a richtext document. For example:
|
Unlike commands, operations aren't extendable. Slate's core defines all of the possible operations that can occur on a richtext document. For example:
|
||||||
|
|
||||||
|
@@ -5,16 +5,28 @@ All of the behaviors, content and state of a Slate editor is rollup up into a si
|
|||||||
```ts
|
```ts
|
||||||
interface Editor {
|
interface Editor {
|
||||||
children: Node[]
|
children: Node[]
|
||||||
operations: Operation[]
|
|
||||||
selection: Range | null
|
selection: Range | null
|
||||||
|
operations: Operation[]
|
||||||
marks: Record<string, any> | null
|
marks: Record<string, any> | null
|
||||||
apply: (operation: Operation) => void
|
[key: string]: any
|
||||||
exec: (command: Command) => void
|
|
||||||
|
// Schema-specific node behaviors.
|
||||||
isInline: (element: Element) => boolean
|
isInline: (element: Element) => boolean
|
||||||
isVoid: (element: Element) => boolean
|
isVoid: (element: Element) => boolean
|
||||||
normalizeNode: (entry: NodeEntry) => void
|
normalizeNode: (entry: NodeEntry) => void
|
||||||
onChange: () => void
|
onChange: () => void
|
||||||
[key: string]: any
|
|
||||||
|
// Overrideable core actions.
|
||||||
|
addMark: (key: string, value: any) => void
|
||||||
|
apply: (operation: Operation) => void
|
||||||
|
deleteBackward: (unit: 'character' | 'word' | 'line' | 'block') => void
|
||||||
|
deleteForward: (unit: 'character' | 'word' | 'line' | 'block') => void
|
||||||
|
deleteFragment: () => void
|
||||||
|
insertBreak: () => void
|
||||||
|
insertFragment: (fragment: Node[]) => void
|
||||||
|
insertNode: (node: Node) => void
|
||||||
|
insertText: (text: string) => void
|
||||||
|
removeMark: (key: string) => void
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -24,7 +36,9 @@ The `children` property contains the document tree of nodes that make up the edi
|
|||||||
|
|
||||||
The `selection` property contains the user's current selection, if any.
|
The `selection` property contains the user's current selection, if any.
|
||||||
|
|
||||||
And the `operations` property contains all of the operations that have been applied since the last "change" was flushed. (Since Slate batches operations up into ticks of the event loop.)
|
The `operations` property contains all of the operations that have been applied since the last "change" was flushed. (Since Slate batches operations up into ticks of the event loop.)
|
||||||
|
|
||||||
|
The `marks` property stores formatting that is attached to the cursor, and that will be applied to the text that is inserted next.
|
||||||
|
|
||||||
## Overriding Behaviors
|
## Overriding Behaviors
|
||||||
|
|
||||||
@@ -40,18 +54,18 @@ editor.isInline = element => {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Or maybe you want to define a custom command:
|
Or maybe you want to override the `insertText` behavior to "linkify" URLs:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
const { exec } = editor
|
const { insertText } = editor
|
||||||
|
|
||||||
editor.exec = command => {
|
editor.insertText = text => {
|
||||||
if (command.type === 'insert_link') {
|
if (isUrl(text) {
|
||||||
const { url } = command
|
|
||||||
// ...
|
// ...
|
||||||
} else {
|
return
|
||||||
exec(command)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
insertText(text)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -65,6 +79,7 @@ editor.normalizeNode = entry => {
|
|||||||
|
|
||||||
if (Element.isElement(node) && node.type === 'link') {
|
if (Element.isElement(node) && node.type === 'link') {
|
||||||
// ...
|
// ...
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
normalizeNode(entry)
|
normalizeNode(entry)
|
||||||
@@ -98,18 +113,3 @@ for (const [point] of Editor.positions(editor)) {
|
|||||||
// ...
|
// ...
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Another special group of helper functions exposed on the `Editor` interface are the "transform" helpers. They are the lower-level functions that commands use to define their behaviors. For example:
|
|
||||||
|
|
||||||
```js
|
|
||||||
// Insert an element node at a specific path.
|
|
||||||
Transforms.insertNodes(editor, [element], { at: path })
|
|
||||||
|
|
||||||
// Split the nodes in half at a specific point.
|
|
||||||
Transforms.splitNodes(editor, { at: point })
|
|
||||||
|
|
||||||
// Add a quote format to all the block nodes in the selection.
|
|
||||||
Transforms.setNodes(editor, { type: 'quote' })
|
|
||||||
```
|
|
||||||
|
|
||||||
The editor-specific helpers are the ones you'll use most often when working with Slate editors, so it pays to become very familiar with them.
|
|
||||||
|
@@ -4,21 +4,11 @@ You've already seen how the behaviors of Slate editors can be overriden. These o
|
|||||||
|
|
||||||
A plugin is simply a function that takes an `Editor` object and returns it after it has augmented it in some way.
|
A plugin is simply a function that takes an `Editor` object and returns it after it has augmented it in some way.
|
||||||
|
|
||||||
For example, a plugin that handles images:
|
For example, a plugin that marks image nodes as "void":
|
||||||
|
|
||||||
```js
|
```js
|
||||||
const withImages = editor => {
|
const withImages = editor => {
|
||||||
const { exec, isVoid } = editor
|
const { isVoid } = editor
|
||||||
|
|
||||||
editor.exec = command => {
|
|
||||||
if (command.type === 'insert_image') {
|
|
||||||
const { url } = command
|
|
||||||
const element = { type: 'image', url, children: [{ text: '' }] }
|
|
||||||
Transforms.insertNodes(editor, element)
|
|
||||||
} else {
|
|
||||||
exec(command)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
editor.isVoid = element => {
|
editor.isVoid = element => {
|
||||||
return element.type === 'image' ? true : isVoid(editor)
|
return element.type === 'image' ? true : isVoid(editor)
|
||||||
@@ -34,38 +24,31 @@ And then to use the plugin, simply:
|
|||||||
import { createEditor } from 'slate'
|
import { createEditor } from 'slate'
|
||||||
|
|
||||||
const editor = withImages(createEditor())
|
const editor = withImages(createEditor())
|
||||||
|
|
||||||
// Later, when you want to insert an image...
|
|
||||||
editor.exec({
|
|
||||||
type: 'insert_image',
|
|
||||||
url: 'https://unsplash.com/photos/m0By_H6ofeE',
|
|
||||||
})
|
|
||||||
```
|
```
|
||||||
|
|
||||||
This plugin composition model makes Slate extremely easy to extend!
|
This plugin composition model makes Slate extremely easy to extend!
|
||||||
|
|
||||||
## Helpers Functions
|
## Helper Functions
|
||||||
|
|
||||||
In addition to the plugin functions, you might want to expose helper functions that are used alongside your plugins. For example:
|
In addition to the plugin functions, you might want to expose helper functions that are used alongside your plugins. For example:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
const ImageElement = {
|
import { Editor, Element } from 'slate'
|
||||||
|
|
||||||
|
const MyEditor = {
|
||||||
|
...Editor,
|
||||||
|
insertImage(editor, url) {
|
||||||
|
const element = { type: 'image', url, children: [{ text: '' }] }
|
||||||
|
Transforms.insertNodes(editor, element)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const MyElement = {
|
||||||
|
...Element,
|
||||||
isImageElement(value) {
|
isImageElement(value) {
|
||||||
return Element.isElement(element) && element.type === 'image'
|
return Element.isElement(element) && element.type === 'image'
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
That way you can reuse your helpers. Or even mix them with the core Slate helpers to create your own bundle, like:
|
Then you can use `MyEditor` and `MyElement` everywhere and have access to all your helpers in one place.
|
||||||
|
|
||||||
```js
|
|
||||||
import { Element } from 'slate'
|
|
||||||
import { ImageElement } from './images'
|
|
||||||
|
|
||||||
export const MyElement = {
|
|
||||||
...Element,
|
|
||||||
...ImageElement,
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Then you can use `MyElement` everywhere and have access to all your helpers in one place.
|
|
||||||
|
@@ -36,9 +36,9 @@ In attempt to decrease the maintenance burden, and because the new abstraction a
|
|||||||
|
|
||||||
### Commands
|
### Commands
|
||||||
|
|
||||||
A new `Command` concept has been introduced. (The old "commands" are now called "transforms".) This new concept expresses the semantic intent of a user editing the document. And they allow for the right abstraction to tap into user behaviors—for example to change what happens when a user presses enter, or backspace, etc. Instead of using `keydown` events you should likely override command behaviors instead.
|
A new "command" concept has been introduced. (The old "commands" are now called "transforms".) This new concept expresses the semantic intent of a user editing the document. And they allow for the right abstraction to tap into user behaviors—for example to change what happens when a user presses enter, or backspace, etc. Instead of using `keydown` events you should likely override command behaviors instead.
|
||||||
|
|
||||||
Commands are triggered by calling the `editor.exec` function. And they travel through a middleware-like stack, but built from composed functions. Any plugin can override the `exec` behaviors to augment an editor.
|
Commands are triggered by calling the `editor.*` core functions. And they travel through a middleware-like stack, but built from composed functions. Any plugin can override the behaviors to augment an editor.
|
||||||
|
|
||||||
### Plugins
|
### Plugins
|
||||||
|
|
||||||
|
@@ -76,7 +76,7 @@ const App = () => {
|
|||||||
|
|
||||||
It has the concept of "code blocks" and "bold formatting". But these things are all defined in one-off cases inside the `onKeyDown` handler. If you wanted to reuse that logic elsewhere you'd need to extract it.
|
It has the concept of "code blocks" and "bold formatting". But these things are all defined in one-off cases inside the `onKeyDown` handler. If you wanted to reuse that logic elsewhere you'd need to extract it.
|
||||||
|
|
||||||
We can instead implement these domain-specific concepts by extending the `editor` object:
|
We can instead implement these domain-specific concepts by creating custom functions:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
// Create a custom editor plugin function that will augment the editor.
|
// Create a custom editor plugin function that will augment the editor.
|
||||||
@@ -154,39 +154,6 @@ Since we haven't yet defined (or overridden) any commands in `withCustom`, nothi
|
|||||||
However, now we can start extract bits of logic into reusable methods:
|
However, now we can start extract bits of logic into reusable methods:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
const withCustom = editor => {
|
|
||||||
const { exec } = editor
|
|
||||||
|
|
||||||
editor.exec = command => {
|
|
||||||
// Define a command to toggle the bold formatting.
|
|
||||||
if (command.type === 'toggle_bold_mark') {
|
|
||||||
const isActive = CustomEditor.isBoldMarkActive(editor)
|
|
||||||
Transforms.setNodes(
|
|
||||||
editor,
|
|
||||||
{ bold: isActive ? null : true },
|
|
||||||
{ match: n => Text.isText(n), split: true }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Define a command to toggle the code block formatting.
|
|
||||||
else if (command.type === 'toggle_code_block') {
|
|
||||||
const isActive = CustomEditor.isCodeBlockActive(editor)
|
|
||||||
Transforms.setNodes(
|
|
||||||
editor,
|
|
||||||
{ type: isActive ? null : 'code' },
|
|
||||||
{ match: n => Editor.isBlock(editor, n) }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise, fall back to the built-in `exec` logic for everything else.
|
|
||||||
else {
|
|
||||||
exec(command)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return editor
|
|
||||||
}
|
|
||||||
|
|
||||||
// Define our own custom set of helpers for active-checking queries.
|
// Define our own custom set of helpers for active-checking queries.
|
||||||
const CustomEditor = {
|
const CustomEditor = {
|
||||||
isBoldMarkActive(editor) {
|
isBoldMarkActive(editor) {
|
||||||
@@ -205,10 +172,28 @@ const CustomEditor = {
|
|||||||
|
|
||||||
return !!match
|
return !!match
|
||||||
},
|
},
|
||||||
|
|
||||||
|
toggleBoldMark(editor) {
|
||||||
|
const isActive = CustomEditor.isBoldMarkActive(editor)
|
||||||
|
Transforms.setNodes(
|
||||||
|
editor,
|
||||||
|
{ bold: isActive ? null : true },
|
||||||
|
{ match: n => Text.isText(n), split: true }
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
toggleCodeBlock(editor) {
|
||||||
|
const isActive = CustomEditor.isCodeBlockActive(editor)
|
||||||
|
Transforms.setNodes(
|
||||||
|
editor,
|
||||||
|
{ type: isActive ? null : 'code' },
|
||||||
|
{ match: n => Editor.isBlock(editor, n) }
|
||||||
|
)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
const editor = useMemo(() => withCustom(withReact(createEditor())), [])
|
const editor = useMemo(() => withReact(createEditor()), [])
|
||||||
const [value, setValue] = useState([
|
const [value, setValue] = useState([
|
||||||
{
|
{
|
||||||
type: 'paragraph',
|
type: 'paragraph',
|
||||||
@@ -243,13 +228,13 @@ const App = () => {
|
|||||||
switch (event.key) {
|
switch (event.key) {
|
||||||
case '`': {
|
case '`': {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
editor.exec({ type: 'toggle_code_block' })
|
CustomEditor.toggleCodeBlock(editor)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'b': {
|
case 'b': {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
editor.exec({ type: 'toggle_bold_mark' })
|
CustomEditor.toggleBoldMark(editor)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -264,7 +249,7 @@ Now our commands are clearly defined and you can invoke them from anywhere we ha
|
|||||||
|
|
||||||
```js
|
```js
|
||||||
const App = () => {
|
const App = () => {
|
||||||
const editor = useMemo(() => withCustom(withReact(createEditor())), [])
|
const editor = useMemo(() => withReact(createEditor()), [])
|
||||||
const [value, setValue] = useState([
|
const [value, setValue] = useState([
|
||||||
{
|
{
|
||||||
type: 'paragraph',
|
type: 'paragraph',
|
||||||
@@ -292,7 +277,7 @@ const App = () => {
|
|||||||
<button
|
<button
|
||||||
onMouseDown={event => {
|
onMouseDown={event => {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
editor.exec({ type: 'toggle_bold_mark' })
|
CustomEditor.toggleBoldMark(editor)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Bold
|
Bold
|
||||||
@@ -300,7 +285,7 @@ const App = () => {
|
|||||||
<button
|
<button
|
||||||
onMouseDown={event => {
|
onMouseDown={event => {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
editor.exec({ type: 'toggle_code_block' })
|
CustomEditor.toggleCodeBlock(editor)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Code Block
|
Code Block
|
||||||
@@ -318,13 +303,13 @@ const App = () => {
|
|||||||
switch (event.key) {
|
switch (event.key) {
|
||||||
case '`': {
|
case '`': {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
editor.exec({ type: 'toggle_code_block' })
|
CustomEditor.toggleCodeBlock(editor)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'b': {
|
case 'b': {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
editor.exec({ type: 'toggle_bold_mark' })
|
CustomEditor.toggleBoldMark(editor)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -337,20 +322,4 @@ const App = () => {
|
|||||||
|
|
||||||
That's the benefit of extracting the logic.
|
That's the benefit of extracting the logic.
|
||||||
|
|
||||||
And you don't necessarily need to define it all in the same plugin. You can use the plugin pattern to add logic and behaviors to an editor from elsewhere.
|
|
||||||
|
|
||||||
For example, you can use the `slate-history` package to add a history stack to your editor, like so:
|
|
||||||
|
|
||||||
```js
|
|
||||||
import { Editor } from 'slate'
|
|
||||||
import { withHistory } from 'slate-history'
|
|
||||||
|
|
||||||
const editor = useMemo(
|
|
||||||
() => withCustom(withHistory(withReact(createEditor()))),
|
|
||||||
[]
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
And there you have it! We just added a ton of functionality to the editor with very little work. And we can keep all of our command logic tested and isolated in a single place, making the code easier to maintain.
|
And there you have it! We just added a ton of functionality to the editor with very little work. And we can keep all of our command logic tested and isolated in a single place, making the code easier to maintain.
|
||||||
|
|
||||||
That's why plugins are awesome. They let you get really expressive while also making your codebase easier to manage. And since Slate is built with plugins as a primary consideration, using them is dead simple!
|
|
||||||
|
Reference in New Issue
Block a user