mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-09-03 04:02:33 +02:00
update docs
This commit is contained in:
@@ -55,9 +55,9 @@ If you're using Slate for the first time, check out the [Getting Started](./docs
|
|||||||
- [**Guides**](docs/guides/installing-slate.md)
|
- [**Guides**](docs/guides/installing-slate.md)
|
||||||
- [Installing Slate](docs/guides/installing-slate.md)
|
- [Installing Slate](docs/guides/installing-slate.md)
|
||||||
- [Adding Event Handlers](docs/guides/adding-event-handlers.md)
|
- [Adding Event Handlers](docs/guides/adding-event-handlers.md)
|
||||||
- [Adding Custom Formatting](docs/guides/adding-custom-formatting.md)
|
- [Defining Custom Block Nodes](docs/guides/defining-custom-block-nodes.md)
|
||||||
- [Adding Custom Block Types](docs/guides/adding-custom-block-types.md)
|
- [Applying Custom Formatting](docs/guides/applying-custom-formatting.md)
|
||||||
- Adding Plugins
|
- [Using Plugins](docs/guides/using-plugins.md)
|
||||||
|
|
||||||
- [**Concepts**](docs/concepts.md)
|
- [**Concepts**](docs/concepts.md)
|
||||||
- Statelessness & Immutability
|
- Statelessness & Immutability
|
||||||
|
@@ -3,8 +3,8 @@ These guides introduce you to the different parts of Slate in a step-by-step way
|
|||||||
|
|
||||||
- [Installing Slate](./installing-slate.md)
|
- [Installing Slate](./installing-slate.md)
|
||||||
- [Adding Event Handlers](./adding-event-handlers.md)
|
- [Adding Event Handlers](./adding-event-handlers.md)
|
||||||
- [Adding Custom Formatting](./adding-custom-formatting.md)
|
- [Defining Custom Block Nodes](./defining-custom-block-nodes.md)
|
||||||
- [Adding Custom Block Types](./adding-custom-block-types.md)
|
- [Applying Custom Formatting](./applying-custom-formatting.md)
|
||||||
- Adding Plugins
|
- [Using Plugins](./using-plugins.md)
|
||||||
|
|
||||||
_If you have an idea for a guide, or notice something that isn't clear, submit a pull request!_
|
_If you have an idea for a guide, or notice something that isn't clear, submit a pull request!_
|
||||||
|
@@ -253,7 +253,7 @@ class App extends React.Component {
|
|||||||
const isBold = state.marks.some(mark => mark.type == 'bold')
|
const isBold = state.marks.some(mark => mark.type == 'bold')
|
||||||
return state
|
return state
|
||||||
.transform()
|
.transform()
|
||||||
[isBold ? 'mark' : 'unmark']('bold')
|
[isBold ? 'unmark' : 'mark']('bold')
|
||||||
.apply()
|
.apply()
|
||||||
}
|
}
|
||||||
case 192: {
|
case 192: {
|
||||||
|
338
docs/guides/using-plugins.md
Normal file
338
docs/guides/using-plugins.md
Normal file
@@ -0,0 +1,338 @@
|
|||||||
|
|
||||||
|
<br/>
|
||||||
|
<p align="center"><strong>Previous:</strong><br/><a href="./applying-custom-formatting.md">Applying Custom Formatting</a></p>
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
### Using Formatting
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
So let's look at how you'd break that code out into it's own reusable plugin that can toggle _any_ formatting mark on _any_ key press.
|
||||||
|
|
||||||
|
Starting with our app from earlier:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const BOLD_MARK = {
|
||||||
|
fontWeight: 'bold'
|
||||||
|
}
|
||||||
|
|
||||||
|
class App extends React.Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props)
|
||||||
|
this.state = {
|
||||||
|
state: initialState
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<Editor
|
||||||
|
state={this.state.state}
|
||||||
|
renderMark={mark => this.renderMark(mark)}
|
||||||
|
renderNode={node => this.renderNode(node)}
|
||||||
|
onChange={state => this.onChange(state)}
|
||||||
|
onKeyDown={(e, state) => this.onKeyDown(e, state)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
renderMark(mark) {
|
||||||
|
if (mark.type == 'bold') return BOLD_MARK
|
||||||
|
}
|
||||||
|
|
||||||
|
renderNode(node) {
|
||||||
|
if (node.type == 'paragraph') return ParagraphNode
|
||||||
|
}
|
||||||
|
|
||||||
|
onChange(state) {
|
||||||
|
this.setState({ state })
|
||||||
|
}
|
||||||
|
|
||||||
|
onKeyDown(event, state) {
|
||||||
|
if (!event.metaKey || event.which != 66) return
|
||||||
|
|
||||||
|
const isBold = state.marks.some(mark => mark.type == 'bold')
|
||||||
|
return state
|
||||||
|
.transform()
|
||||||
|
[isBold ? 'unmark' : 'mark']('bold')
|
||||||
|
.apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
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 } = options
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Okay, that was easy.
|
||||||
|
|
||||||
|
Now we want to have it return a dictionary of handlers, in this case specifically containing a `onKeyDown` handler with logic pulled from our app:
|
||||||
|
|
||||||
|
|
||||||
|
```js
|
||||||
|
function MarkHotkey(options) {
|
||||||
|
const { type, code } = options
|
||||||
|
|
||||||
|
// Return our "plugin" object, containing the `onKeyDown` handler.
|
||||||
|
return {
|
||||||
|
onKeyDown(event, state) {
|
||||||
|
// Check that the key pressed matches our `code` option.
|
||||||
|
if (!event.metaKey || event.which != code) return
|
||||||
|
|
||||||
|
// Determine whether our `type` option mark is currently active.
|
||||||
|
const isActive = state.marks.some(mark => mark.type == type)
|
||||||
|
|
||||||
|
// Toggle the mark `type` based on whether it is active.
|
||||||
|
return state
|
||||||
|
.transform()
|
||||||
|
[isActive ? 'unmark' : 'mark'](type)
|
||||||
|
.apply()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Boom! Now we're getting somewhere. That code is reusable for any type of mark.
|
||||||
|
|
||||||
|
So now, let's remove the old code from our app, and pass the editor our brand new `MarkHotkey` plugin instead, giving it the same options that will keep our bold functionality intact:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const BOLD_MARK = {
|
||||||
|
fontWeight: 'bold'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize our bold-mark-adding plugin.
|
||||||
|
const boldPlugin = MarkHotkey({
|
||||||
|
type: 'bold',
|
||||||
|
code: 66
|
||||||
|
})
|
||||||
|
|
||||||
|
// Create an array of plugins.
|
||||||
|
const plugins = [
|
||||||
|
boldPlugin
|
||||||
|
]
|
||||||
|
|
||||||
|
class App extends React.Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props)
|
||||||
|
this.state = {
|
||||||
|
state: initialState
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the `plugins` property to the editor, and remove `onKeyDown`.
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<Editor
|
||||||
|
state={this.state.state}
|
||||||
|
plugins={plugins}
|
||||||
|
renderMark={mark => this.renderMark(mark)}
|
||||||
|
renderNode={node => this.renderNode(node)}
|
||||||
|
onChange={state => this.onChange(state)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
renderMark(mark) {
|
||||||
|
if (mark.type == 'bold') return BOLD_MARK
|
||||||
|
}
|
||||||
|
|
||||||
|
renderNode(node) {
|
||||||
|
if (node.type == 'paragraph') return ParagraphNode
|
||||||
|
}
|
||||||
|
|
||||||
|
onChange(state) {
|
||||||
|
this.setState({ state })
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
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
|
||||||
|
// Add our new mark renderers...
|
||||||
|
const MARKS = {
|
||||||
|
bold: {
|
||||||
|
fontWeight: 'bold'
|
||||||
|
},
|
||||||
|
code: {
|
||||||
|
fontFamily: 'monospace'
|
||||||
|
},
|
||||||
|
italic: {
|
||||||
|
fontStyle: 'italic'
|
||||||
|
},
|
||||||
|
strikethrough: {
|
||||||
|
textDecoration: 'strikethrough'
|
||||||
|
},
|
||||||
|
underline: {
|
||||||
|
textDecoration: 'underline'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize our plugins...
|
||||||
|
const plugins = [
|
||||||
|
MarkHotkey({ code: 66, type: 'bold' }),
|
||||||
|
MarkHotkey({ code: 192, type: 'code' }),
|
||||||
|
MarkHotkey({ code: 73, type: 'italic' }),
|
||||||
|
MarkHotkey({ code: 68, type: 'strikethrough' }),
|
||||||
|
MarkHotkey({ code: 85, type: 'underline' })
|
||||||
|
]
|
||||||
|
|
||||||
|
class App extends React.Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props)
|
||||||
|
this.state = {
|
||||||
|
state: initialState
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<Editor
|
||||||
|
state={this.state.state}
|
||||||
|
plugins={plugins}
|
||||||
|
renderMark={mark => this.renderMark(mark)}
|
||||||
|
renderNode={node => this.renderNode(node)}
|
||||||
|
onChange={state => this.onChange(state)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update our render function to handle all the new marks...
|
||||||
|
renderMark(mark) {
|
||||||
|
return MARKS[mark.type]
|
||||||
|
}
|
||||||
|
|
||||||
|
renderNode(node) {
|
||||||
|
if (node.type == 'paragraph') return ParagraphNode
|
||||||
|
}
|
||||||
|
|
||||||
|
onChange(state) {
|
||||||
|
this.setState({ state })
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
In fact, unless you have weirdly good keycode guessing, there's a good chance you had no idea what our current hotkeys bindings actually mapped to.
|
||||||
|
|
||||||
|
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`.
|
||||||
|
const { type, key } = options
|
||||||
|
|
||||||
|
return {
|
||||||
|
onKeyDown(event, state) {
|
||||||
|
// Change the comparison to use the key name.
|
||||||
|
if (!event.metaKey || keycode(event.which) != key) return
|
||||||
|
|
||||||
|
const isActive = state.marks.some(mark => mark.type == type)
|
||||||
|
return state
|
||||||
|
.transform()
|
||||||
|
[isActive ? 'unmark' : 'mark'](type)
|
||||||
|
.apply()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
And finally, we can make our app code clearer:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const MARKS = {
|
||||||
|
bold: {
|
||||||
|
fontWeight: 'bold'
|
||||||
|
},
|
||||||
|
code: {
|
||||||
|
fontFamily: 'monospace'
|
||||||
|
},
|
||||||
|
italic: {
|
||||||
|
fontStyle: 'italic'
|
||||||
|
},
|
||||||
|
strikethrough: {
|
||||||
|
textDecoration: 'strikethrough'
|
||||||
|
},
|
||||||
|
underline: {
|
||||||
|
textDecoration: 'underline'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the much clearer key names instead of key codes!
|
||||||
|
const plugins = [
|
||||||
|
MarkHotkey({ key: 'b', type: 'bold' }),
|
||||||
|
MarkHotkey({ key: '`', type: 'code' }),
|
||||||
|
MarkHotkey({ key: 'i', type: 'italic' }),
|
||||||
|
MarkHotkey({ key: 'd', type: 'strikethrough' }),
|
||||||
|
MarkHotkey({ key: 'u', type: 'underline' })
|
||||||
|
]
|
||||||
|
|
||||||
|
class App extends React.Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props)
|
||||||
|
this.state = {
|
||||||
|
state: initialState
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<Editor
|
||||||
|
state={this.state.state}
|
||||||
|
plugins={plugins}
|
||||||
|
renderMark={mark => this.renderMark(mark)}
|
||||||
|
renderNode={node => this.renderNode(node)}
|
||||||
|
onChange={state => this.onChange(state)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
renderMark(mark) {
|
||||||
|
return MARKS[mark.type]
|
||||||
|
}
|
||||||
|
|
||||||
|
renderNode(node) {
|
||||||
|
if (node.type == 'paragraph') return ParagraphNode
|
||||||
|
}
|
||||||
|
|
||||||
|
onChange(state) {
|
||||||
|
this.setState({ state })
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
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