1
0
mirror of https://github.com/ianstormtaylor/slate.git synced 2025-03-09 16:07:53 +01:00
slate/docs/walkthroughs/using-plugins.md

272 lines
7.4 KiB
Markdown
Raw Normal View History

2016-07-08 14:40:45 -07:00
<br/>
<p align="center"><strong>Previous:</strong><br/><a href="./applying-custom-formatting.md">Applying Custom Formatting</a></p>
<br/>
2016-07-12 21:34:31 -07:00
# Using Plugins
2016-07-08 14:40:45 -07:00
Up to now, everything we've learned has been about how to write one-off logic for your specific Slate editor. But one of the most beautiful things about Slate is actually its plugin system, and how it lets you write less one-off code.
2016-07-08 14:54:52 -07:00
In the previous guide, we actually wrote some pretty useful code for adding **bold** formatting to ranges of text when a key is pressed. But most of that code wasn't really specific to **bold** text; it could just as easily have applied to _italic_ text or `code` text if we switched a few variables.
2016-07-08 14:40:45 -07:00
2016-07-08 14:54:52 -07:00
So let's break that logic out into it's a reusable plugin that can toggle _any_ mark on _any_ key press.
2016-07-08 14:40:45 -07:00
Starting with our app from earlier:
```js
class App extends React.Component {
2016-08-14 18:25:12 -07:00
state = {
state: initialState,
schema: {
marks: {
bold: props => <strong>{props.children}</strong>
}
2016-07-08 14:40:45 -07:00
}
}
onChange = ({ state }) => {
this.setState({ state })
2016-07-08 14:40:45 -07:00
}
onKeyDown = (event, data, change) => {
2016-07-08 14:40:45 -07:00
if (!event.metaKey || event.which != 66) return
2017-02-16 10:16:19 -08:00
event.preventDefault()
change.toggleMark('bold')
return true
2016-07-08 14:40:45 -07:00
}
2017-08-02 18:36:33 +02:00
render() {
return (
<Editor
schema={this.state.schema}
state={this.state.state}
onChange={this.onChange}
onKeyDown={this.onKeyDown}
/>
)
}
2016-07-08 14:40:45 -07:00
}
```
Let's write a new function, that takes a set of options: the mark `type` to toggle and the key `code` to press.
```js
function MarkHotkey(options) {
// Grab our options from the ones passed in.
const { type, code, isAltKey = false } = options
2016-07-08 14:40:45 -07:00
}
```
2016-07-08 14:54:52 -07:00
Okay, that was easy. But it doesn't do anything.
2016-07-08 14:40:45 -07:00
2016-07-08 14:54:52 -07:00
To fix that, we need our plugin function to return a "plugin object" that Slate recognizes. Slate's plugin objects are just plain objects that have properties that map to the same handler on the `Editor`.
2016-07-08 14:40:45 -07:00
2016-07-08 14:54:52 -07:00
In this case our plugin object will have one property: a `onKeyDown` handler, with its logic copied right from our current app's code:
2016-07-08 14:40:45 -07:00
```js
function MarkHotkey(options) {
2017-05-30 22:15:11 +02:00
const { type, code, isAltKey = false } = options
2016-07-08 14:40:45 -07:00
// Return our "plugin" object, containing the `onKeyDown` handler.
return {
onKeyDown(event, data, change) {
2016-07-08 14:40:45 -07:00
// Check that the key pressed matches our `code` option.
if (!event.metaKey || event.which != code || event.altKey != isAltKey) return
2016-07-08 14:40:45 -07:00
2017-02-16 10:16:19 -08:00
// Prevent the default characters from being inserted.
event.preventDefault()
2016-08-14 18:25:12 -07:00
// Toggle the mark `type`.
change.toggleMark(type)
return true
2016-07-08 14:40:45 -07:00
}
}
}
```
Boom! Now we're getting somewhere. That code is reusable for any type of mark.
2016-07-08 14:54:52 -07:00
Now that we have our plugin, let's remove the hard-coded logic from our app, and replace it with our brand new `MarkHotkey` plugin instead, passing in the same options that will keep our **bold** functionality intact:
2016-07-08 14:40:45 -07:00
```js
// Initialize our bold-mark-adding plugin.
const boldPlugin = MarkHotkey({
type: 'bold',
2016-07-08 14:40:45 -07:00
code: 66
})
// Create an array of plugins.
const plugins = [
boldPlugin
]
class App extends React.Component {
2016-08-14 18:25:12 -07:00
state = {
state: initialState,
schema: {
marks: {
bold: props => <strong>{props.children}</strong>
}
2016-07-08 14:40:45 -07:00
}
}
onChange = ({ state }) => {
this.setState({ state })
}
2016-07-08 14:40:45 -07:00
2017-08-02 18:36:33 +02:00
render() {
2016-07-08 14:40:45 -07:00
return (
2016-08-14 18:25:12 -07:00
// Add the `plugins` property to the editor, and remove `onKeyDown`.
2016-07-08 14:40:45 -07:00
<Editor
plugins={plugins}
2016-08-14 18:25:12 -07:00
schema={this.state.schema}
state={this.state.state}
onChange={this.onChange}
2016-07-08 14:40:45 -07:00
/>
)
}
}
```
Awesome. If you test out the editor now, you'll notice that everything still works just as it did before. But the beauty of the logic being encapsulated in a plugin is that we can add more mark types _extremely_ easily now!
Let's add _italic_, `code`, ~~strikethrough~~ and underline marks:
```js
2016-08-14 18:25:12 -07:00
// Initialize a plugin for each mark...
2016-07-08 14:40:45 -07:00
const plugins = [
MarkHotkey({ code: 66, type: 'bold' }),
MarkHotkey({ code: 67, type: 'code', isAltKey: true }),
2016-07-08 14:40:45 -07:00
MarkHotkey({ code: 73, type: 'italic' }),
MarkHotkey({ code: 68, type: 'strikethrough' }),
MarkHotkey({ code: 85, type: 'underline' })
]
class App extends React.Component {
2016-08-14 18:25:12 -07:00
state = {
state: initialState,
schema: {
marks: {
bold: props => <strong>{props.children}</strong>,
// Add our new mark renderers...
code: props => <code>{props.children}</code>,
italic: props => <em>{props.children}</em>,
strikethrough: props => <del>{props.children}</del>,
underline: props => <u>{props.children}</u>,
}
2016-07-08 14:40:45 -07:00
}
}
onChange = ({ state }) => {
this.setState({ state })
}
2016-07-08 14:40:45 -07:00
2017-08-02 18:36:33 +02:00
render() {
2016-07-08 14:40:45 -07:00
return (
<Editor
plugins={plugins}
2016-08-14 18:25:12 -07:00
schema={this.state.schema}
state={this.state.state}
onChange={this.onChange}
2016-07-08 14:40:45 -07:00
/>
)
}
}
```
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 mark hotkey logic tested and isolated in a single place, making maintaining the code easier.
Of course... now that it's reusable, we could actually make our `MarkHotkey` plugin a little easier to use. What if instead of a `code` argument it took the text of the `key` itself? That would make the calling code a lot clearer, since key codes are really obtuse.
2016-07-08 14:54:52 -07:00
In fact, unless you have weirdly good keycode knowledge, you probably have no idea what our current hotkeys actually are.
2016-07-08 14:40:45 -07:00
Let's fix that.
Using the `keycode` module in npm makes this dead simple.
First install it:
```
npm install keycode
```
And then we can add it our plugin:
```js
// Import the keycode module.
import keycode from `keycode`
function MarkHotkey(options) {
// Change the options to take a `key`.
2017-05-30 22:15:11 +02:00
const { type, key, isAltKey = false } = options
2016-07-08 14:40:45 -07:00
return {
onKeyDown(event, data, change) {
2016-07-08 14:40:45 -07:00
// Change the comparison to use the key name.
2017-05-30 22:15:11 +02:00
if (!event.metaKey || keycode(event.which) != key || event.altKey != isAltKey) return
2017-02-16 10:16:19 -08:00
event.preventDefault()
change.toggleMark(type)
return true
2016-07-08 14:40:45 -07:00
}
}
}
```
2016-07-08 14:54:52 -07:00
And now we can make our app code much clearer for the next person who reads it:
2016-07-08 14:40:45 -07:00
```js
// Use the much clearer key names instead of key codes!
const plugins = [
MarkHotkey({ key: 'b', type: 'bold' }),
MarkHotkey({ key: 'c', type: 'code', isAltKey: true }),
2016-07-08 14:40:45 -07:00
MarkHotkey({ key: 'i', type: 'italic' }),
MarkHotkey({ key: 'd', type: 'strikethrough' }),
MarkHotkey({ key: 'u', type: 'underline' })
]
class App extends React.Component {
2016-08-14 18:25:12 -07:00
state = {
state: initialState,
schema: {
marks: {
bold: props => <strong>{props.children}</strong>,
code: props => <code>{props.children}</code>,
italic: props => <em>{props.children}</em>,
strikethrough: props => <del>{props.children}</del>,
underline: props => <u>{props.children}</u>,
}
2016-07-08 14:40:45 -07:00
}
}
onChange = ({ state }) => {
this.setState({ state })
}
2016-07-08 14:40:45 -07:00
2017-08-02 18:36:33 +02:00
render() {
2016-07-08 14:40:45 -07:00
return (
<Editor
plugins={plugins}
2016-08-14 18:25:12 -07:00
schema={this.state.schema}
state={this.state.state}
onChange={this.onChange}
2016-07-08 14:40:45 -07:00
/>
)
}
}
```
2016-07-08 14:54:52 -07:00
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!
2016-07-18 12:55:04 -07:00
<br/>
<p align="center"><strong>Next:</strong><br/><a href="./saving-to-a-database.md">Saving to a Database</a></p>
<br/>