1
0
mirror of https://github.com/ianstormtaylor/slate.git synced 2025-08-23 15:32:59 +02:00

add plugins guide

This commit is contained in:
Ian Storm Taylor
2017-10-25 11:04:11 -07:00
parent 68680f6754
commit 0694daf26a
2 changed files with 163 additions and 0 deletions

View File

@@ -16,6 +16,7 @@
## Guides ## Guides
- [Changes](./guides/changes.md) - [Changes](./guides/changes.md)
- [Plugins](./guides/plugins.md)
## General ## General

162
docs/guides/plugins.md Normal file
View File

@@ -0,0 +1,162 @@
# Plugins
With Slate, _all_ of your editor's logic is controlled by "plugins".
Plugins have complete control over the schema, the behaviors, and the rendering of the editor—they can add any kind of functionality they want. So much so that even the core logic of Slate is provided via two "core" plugins.
Slate encourages you to break up code into small, reusable modules that can be shared with others, and easily reasoned about.
## What Are Plugins?
Slate's plugins are simply a collection of functions that all contribute to a shared behavior—each with a specific name and set of arguments. For a full list of the arguments, check out the [`Plugins` reference](../reference/slate-react/plugins).
Here's a really simple plugin:
```js
{
onKeyDown(event, change, editor) {
if (event.key == 'Escape') {
change.blur()
}
},
onClick(event, change, editor) {
if (change.state.isBlurred) {
change.selectAll().focus()
}
}
}
```
It focuses the editor and selects everything when it is clicked, and it blurs the editor what <kbd>esc</kbd> is pressed.
Notice how it's able to define a set of behaviors that work together to form a single "feature" in the editor. That's what makes Slate's plugins a powerful for of encapsulation, so that your codebase doesn't become mired in complexity.
## The Plugins "Stack"
Slate's editor takes a list of plugins as one of its arguments. We refer to this list as the plugins "stack". It is very similar to "middleware" from Express or Koa.
When the editor needs to handle a DOM event, or to decide what to render, it will loop through the plugins stack, invoking each plugin in turn. Plugins can choose to handle the request, in which case the editor will break out of the loop. Or they can ignore it, and they will be skipped as the editor proceeds to the next plugin in the stack.
Because of this looping, plugins are **order-sensitive**! This is very important. The earlier in the stack, the more preference the plugin has, since it can react before the others after it. If two plugins both try to handle the same event, the earlier plugin will "win".
## "Core" Plugins
If you put Slate on the page without adding any of your own plugins, it will still behave like you'd expect a rich-text editor to. That's because it has its own "core" logic. And that core logic is implemented with its own core plugins.
The core plugins define the common editing behaviors like splitting the current block when <kbd>enter</kbd> is pressed, or inserting a string of text when the user pastes from their clipboard. These are behaviors that all rich-text editors exhibit, and that don't make sense for userland to have to re-invent for every new editor.
There are two core plugins: the "before plugin" and the "after plugin". They get their names because one of them is before all other plugins in the stack, and the other is after them.
For the most part you don't need to worry about the core plugins. The before plugin helps to pave over editing inconsistencies, and the after plugin serves as a fallback, to implement the default behavior in the event that your own plugins choose not to handle a specific event.
_To learn more, check out the [Core Plugin reference](../reference/slate-react/core-plugin.md)._
## Helper Plugins vs. Feature Plugins
Plugins _can_ do anything and everything. But that doesn't mean you should build plugins that are thousands of lines long that implement every single feature in your editor—your codebase would become hell to maintain. Instead, just like all modules, you should split them up into pieces with separate concerns.
A distinction that helps with this is to consider two different types of plugins: "helper plugins" and "feature plugins".
This distinction is very similar to any other type of packages. You have things like [`chalk`](https://yarnpkg.com/en/package/chalk), [`is-url`](https://yarnpkg.com/en/package/is-url) and [`isomorphic-fetch`](https://yarnpkg.com/en/package/isomorphic-fetch) that are open-source helpers that you compose together to form larger features in your app.
### Helper Plugins
Helper plugins are usually very small, and just serve to easily encapsulate a specific piece of logic that gets re-used in multiple places—often in multiple "feature" plugins.
For example, you may have a simple `Hotkey` plugin that makes binding behaviors to hotkeys a lot simpler:
```js
function Hotkey(hotkey, fn) {
return {
onKeyDown(event, change, editor) {
if (isHotkey(hotkey, event)) {
change.call(fn)
}
}
}
}
```
That pseudo-code allows you to encapsulate the hotkey-binding logic in one place, and re-use it anywhere else you want, like so:
```js
const plugins = [
...,
Hotkey('cmd+b', (change) => {
change.addMark('bold')
})
]
```
These types of plugins are critical to keeping your code maintainable. And they're good candidates for open-sourcing for others to use. A few examples of plugins like this in the wild are [`slate-auto-replace`](https://github.com/ianstormtaylor/slate-auto-replace), [`slate-prism`](https://github.com/GitbookIO/slate-prism), [`slate-collapse-on-escape`](https://github.com/ianstormtaylor/slate-collapse-on-escape), etc. There's almost no piece of logic too small to abstract out and share, as long as it's reusable.
But hotkey binding logic by itself isn't a "feature". It's just a small helper that makes building more complex features a lot more expressive.
### Feature Plugins
Feature plugins are much larger in scope, and serve to define an entire series of behaviors that make up a single "feature" in your editor. They're not as concrete as util plugins, but they make reasoning about complex editors much simple.
For example, you maybe decide you want to allow **bold** formatting in your editor. To do that, you need a handful of different behaviors.
You could just have a single, long `plugins.js` file that contained all of the plugins for all of the features in your editor. But figuring out what was going on in that file would get confusing very quickly.
Instead, it can help to split up your plugins into features. So you might have a `bold.js`, `italic.js`, `images.js`, etc. Your bold plugin might look like...
```js
function Bold(options) {
return [
Hotkey('cmd+b', addBoldMark),
RenderMark('bold', props => <BoldMark {...props} />),
RenderButton(props => <BoldButton {...props} />),
...
]
}
```
This is just pseudo-code, but you get the point.
You've created a single plugin that defines the entire bold "feature". If you go to your editor and you removed the `Bold` plugin, the entire bold "feature" would be removed. Having it encapsulated like this makes it much easier to maintain.
More often than not you actually want to change the return value to not just be an array of plugins, but actually an object containing other helpful tools that are associated with the feature, like pre-defined change functions. For example:
```js
function Bold(options) {
return {
changes: {
addBoldMark,
removeBoldMark,
toggleBoldMark,
},
helpers: {
hasBoldMark,
},
plugins: [
Hotkey('cmd+b', addBoldMark),
RenderMark('bold', props => <BoldMark {...props} />),
RenderButton(props => <BoldButton {...props} />),
...
]
}
}
```
With things like `changes` and `helpers`, you can define your bold logic in a single place, and allow other parts of your codebase to use the exposed API to keep consistent.
Feature plugins like that are almost always made up of many smaller helper plugins. And they are usually app-specific, so they don't make great open-source candidates.
### Framework Plugins
That said, there might be another type of plugins that kind of straddle the line. Continuining our analogy to the Javascript package landscape, you might call these "framework" plugins.
These are plugins that bundle up a set of logic, similar to how a feature might, but in a way that is re-usable across codebases. Some examples of these would be [`slate-edit-code`](https://github.com/GitbookIO/slate-edit-code), [`slate-edit-list`](https://github.com/GitbookIO/slate-edit-list), [`slate-edit-table`](https://github.com/GitbookIO/slate-edit-table), etc.
Framework plugins will often expose objects with `changes`, `helpers` and `plugins` instead of simple array.
You'll often want to encapsulate framework plugins in your own feature plugins, but they can go a long way in terms of reducing your codebase size.