mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-08-24 16:02:55 +02:00
start adding docs
This commit is contained in:
15
docs/walkthroughs/Readme.md
Normal file
15
docs/walkthroughs/Readme.md
Normal file
@@ -0,0 +1,15 @@
|
||||
|
||||
# Walkthroughs
|
||||
|
||||
These walkthroughs introduce you to the different parts of Slate in a step-by-step way that build on each other, perfect for getting started. We recommend reading them in order, start to finish!
|
||||
|
||||
- [Installing Slate](./installing-slate.md)
|
||||
- [Using the Bundled Source](./using-the-bundled-source.md)
|
||||
- [Adding Event Handlers](./adding-event-handlers.md)
|
||||
- [Defining Custom Block Nodes](./defining-custom-block-nodes.md)
|
||||
- [Applying Custom Formatting](./applying-custom-formatting.md)
|
||||
- [Using Plugins](./using-plugins.md)
|
||||
- [Saving to a Database](./saving-to-a-database.md)
|
||||
- [Saving and Loading HTML Content](./saving-and-loading-html-content.md)
|
||||
|
||||
_If you have an idea for a walkthrough, or notice something that isn't clear, submit a pull request!_
|
144
docs/walkthroughs/adding-event-handlers.md
Normal file
144
docs/walkthroughs/adding-event-handlers.md
Normal file
@@ -0,0 +1,144 @@
|
||||
|
||||
<br/>
|
||||
<p align="center"><strong>Previous:</strong><br/><a href="./installing-slate.md">Installing Slate</a></p>
|
||||
<br/>
|
||||
|
||||
# Adding Event Handlers
|
||||
|
||||
Okay, so you've got Slate installed and rendered on the page, and when you type in it, you can see the changes reflected. But you want to do more than just type a plaintext string.
|
||||
|
||||
What makes Slate great is how easy it is to customize. Just like other React components you're used it, Slate allows you to pass in handlers that are triggered on certain events. You've already seen on the `onChange` handler can be used to store the changed editor state, but let's try add something more...
|
||||
|
||||
We'll show you how to use the `onKeyDown` handler to change the editor's content when the user presses a button.
|
||||
|
||||
So we start with our app from earlier:
|
||||
|
||||
```js
|
||||
class App extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
state: initialState
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Editor
|
||||
state={this.state.state}
|
||||
renderNode={node => this.renderNode(node)}
|
||||
onChange={state => this.onChange(state)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
renderNode(node) {
|
||||
if (node.type == 'paragraph') return ParagraphNode
|
||||
}
|
||||
|
||||
onChange(state) {
|
||||
this.setState({ state })
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
And now we'll add an `onKeyDown` handler:
|
||||
|
||||
```js
|
||||
class App extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
state: initialState
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Editor
|
||||
state={this.state.state}
|
||||
renderNode={node => this.renderNode(node)}
|
||||
onChange={state => this.onChange(state)}
|
||||
onKeyDown={(e, state) => this.onKeyDown(e, state)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
renderNode(node) {
|
||||
if (node.type == 'paragraph') return ParagraphNode
|
||||
}
|
||||
|
||||
onChange(state) {
|
||||
this.setState({ state })
|
||||
}
|
||||
|
||||
// Define a new handler which prints the key code that was pressed.
|
||||
onKeyDown(event, state) {
|
||||
console.log(event.which)
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
Okay cool, so now when you press a key in the editor, you'll see the key's code printed to the console. Not very useful, but at least we know it's working.
|
||||
|
||||
Now we want to make it actually change the content. For the purposes of our example, let's say we want to make it so that whenever a user types `&` we actually add `and` to the content.
|
||||
|
||||
Our `onKeyDown` handler might look like this:
|
||||
|
||||
```js
|
||||
class App extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
state: initialState
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Editor
|
||||
state={this.state.state}
|
||||
renderNode={node => this.renderNode(node)}
|
||||
onChange={state => this.onChange(state)}
|
||||
onKeyDown={(e, state) => this.onKeyDown(e, state)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
renderNode(node) {
|
||||
if (node.type == 'paragraph') return ParagraphNode
|
||||
}
|
||||
|
||||
onChange(state) {
|
||||
this.setState({ state })
|
||||
}
|
||||
|
||||
onKeyDown(event, state) {
|
||||
// Return with no changes if it's not the "7" key with shift pressed.
|
||||
if (event.which != 55 || !event.shiftKey) return
|
||||
|
||||
// Otherwise, transform the state by insert "and" at the cursor's position.
|
||||
const newState = state
|
||||
.transform()
|
||||
.insertText('and')
|
||||
.apply()
|
||||
|
||||
// Return the new state, which will cause the editor to update it.
|
||||
return newState
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
With that added, try typing `&`, and you should see it automatically become `and` instead!
|
||||
|
||||
That gives you a sense for what you can do with Slate's event handlers. Each one will be called with the `event` object, and the current `state` of the editor. And if you return a new `state`, the editor will be updated. Simple!
|
||||
|
||||
<br/>
|
||||
<p align="center"><strong>Next:</strong><br/><a href="./defining-custom-block-nodes.md">Defining Custom Block Nodes</a></p>
|
||||
<br/>
|
183
docs/walkthroughs/applying-custom-formatting.md
Normal file
183
docs/walkthroughs/applying-custom-formatting.md
Normal file
@@ -0,0 +1,183 @@
|
||||
|
||||
<br/>
|
||||
<p align="center"><strong>Previous:</strong><br/><a href="./defining-custom-block-nodes.md">Defining Custom Block Nodes</a></p>
|
||||
<br/>
|
||||
|
||||
# Applying Custom Formatting
|
||||
|
||||
In the previous guide we learned how to create custom block types that render chunks of text inside different containers. But Slate allows for more than just "blocks".
|
||||
|
||||
In this guide, we'll show you how to add custom formatting options, like **bold**, _italic_, `code` or ~~strikethrough~~.
|
||||
|
||||
So we start with our app from earlier:
|
||||
|
||||
```js
|
||||
class App extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
state: initialState
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Editor
|
||||
state={this.state.state}
|
||||
onChange={state => this.onChange(state)}
|
||||
onKeyDown={e, state => this.onKeyDown(e, state)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
onChange(state) {
|
||||
this.setState({ state })
|
||||
}
|
||||
|
||||
onKeyDown(event, state) {
|
||||
if (event.which != 192 || !event.metaKey) return
|
||||
const isCode = state.blocks.some(block => block.type == 'code')
|
||||
|
||||
return state
|
||||
.transform()
|
||||
.setBlock(isCode ? 'paragraph' : 'code')
|
||||
.apply()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
And now, we'll edit the `onKeyDown` handler to make it so that when you press **⌘-B**, it will add a "bold" mark to the currently selected text:
|
||||
|
||||
```js
|
||||
class App extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
state: initialState
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Editor
|
||||
state={this.state.state}
|
||||
onChange={state => this.onChange(state)}
|
||||
onKeyDown={(e, state) => this.onKeyDown(e, state)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
onChange(state) {
|
||||
this.setState({ state })
|
||||
}
|
||||
|
||||
onKeyDown(event, state) {
|
||||
if (!event.metaKey) return
|
||||
|
||||
// Decide what to do based on the key code...
|
||||
switch (event.which) {
|
||||
// When "B" is pressed, add a "bold" mark to the text.
|
||||
case 66: {
|
||||
return state
|
||||
.transform()
|
||||
.addMark('bold')
|
||||
.apply()
|
||||
}
|
||||
// When "`" is pressed, keep our existing code block logic.
|
||||
case 192: {
|
||||
const isCode = state.blocks.some(block => block.type == 'code')
|
||||
return state
|
||||
.transform()
|
||||
.setBlock(isCode ? 'paragraph' : 'code')
|
||||
.apply()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
Okay, so we've got the hotkey handler setup... but! If you happen to now try selecting text and hitting **⌘-B**, you'll get an error in your console. That's because we haven't told Slate how to render a "bold" mark.
|
||||
|
||||
For every mark type you want to add to your schema, you need to give Slate a "renderer" for that mark, just like nodes. Except unlike nodes, mark renderers are just simple objects of styles that will be passed directly to React's `style=` property.
|
||||
|
||||
So let's define our `bold` mark:
|
||||
|
||||
```js
|
||||
// Define a set of styles that make text bold.
|
||||
const BOLD_MARK = {
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
```
|
||||
|
||||
Pretty simple, right?
|
||||
|
||||
And now, let's tell Slate about that mark. To do that, we'll need to pass in a `renderMark` function to the `Editor`, which it will call with any mark it finds. And when it calls it with a bold mark, we'll return our `BOLD_MARK` styles. Like so:
|
||||
|
||||
```js
|
||||
const BOLD_MARK = {
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
|
||||
class App extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
state: initialState
|
||||
}
|
||||
}
|
||||
|
||||
// Add the `renderMark` handler to the editor.
|
||||
render() {
|
||||
return (
|
||||
<Editor
|
||||
state={this.state.state}
|
||||
renderMark={mark => this.renderMark(mark)}
|
||||
onChange={state => this.onChange(state)}
|
||||
onKeyDown={(e, state) => this.onKeyDown(e, state)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
// Define a mark rendering function that knows about "bold" marks.
|
||||
renderMark(mark) {
|
||||
if (mark.type == 'bold') return BOLD_MARK
|
||||
}
|
||||
|
||||
onChange(state) {
|
||||
this.setState({ state })
|
||||
}
|
||||
|
||||
onKeyDown(event, state) {
|
||||
if (!event.metaKey) return
|
||||
|
||||
switch (event.which) {
|
||||
case 66: {
|
||||
return state
|
||||
.transform()
|
||||
.toggleMark('bold')
|
||||
.apply()
|
||||
}
|
||||
case 192: {
|
||||
const isCode = state.blocks.some(block => block.type == 'code')
|
||||
return state
|
||||
.transform()
|
||||
.setBlock(isCode ? 'paragraph' : 'code')
|
||||
.apply()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
Now, if you try selecting a piece of text and hitting **⌘-B** you should see it turn bold! Magic!
|
||||
|
||||
<br/>
|
||||
<p align="center"><strong>Next:</strong><br/><a href="./using-plugins.md">Using Plugins</a></p>
|
||||
<br/>
|
247
docs/walkthroughs/defining-custom-block-nodes.md
Normal file
247
docs/walkthroughs/defining-custom-block-nodes.md
Normal file
@@ -0,0 +1,247 @@
|
||||
|
||||
<br/>
|
||||
<p align="center"><strong>Previous:</strong><br/><a href="./adding-event-handlers.md">Adding Event Handlers</a></p>
|
||||
<br/>
|
||||
|
||||
# Defining Custom Block Nodes
|
||||
|
||||
In our previous example, we started with a paragraph, but we never actually told Slate anything about the `paragraph` block type. We just let it use its internal default renderer.
|
||||
|
||||
But that's not all you can do. Slate lets you define any type of custom blocks you want, like block quotes, code blocks, list items, etc.
|
||||
|
||||
We'll show you how. Let's start with our app from earlier:
|
||||
|
||||
```js
|
||||
class App extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
state: initialState
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Editor
|
||||
state={this.state.state}
|
||||
renderNode={node => this.renderNode(node)}
|
||||
onChange={state => this.onChange(state)}
|
||||
onKeyDown={(e, state) => this.onKeyDown(e, state)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
renderNode(node) {
|
||||
if (node.type == 'paragraph') return ParagraphNode
|
||||
}
|
||||
|
||||
onChange(state) {
|
||||
this.setState({ state })
|
||||
}
|
||||
|
||||
onKeyDown(event, state) {
|
||||
if (event.which != 55 || !event.shiftKey) return
|
||||
|
||||
const newState = state
|
||||
.transform()
|
||||
.insertText('and')
|
||||
.apply()
|
||||
|
||||
return newState
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
Now let's add "code blocks" to our editor.
|
||||
|
||||
The problem is, code blocks won't just be rendered as a plain paragraph, they'll need to be renderer differently. To make that happen, we need to define a "renderer" for `code` nodes
|
||||
|
||||
Node renderers are just simple React components, like so:
|
||||
|
||||
```js
|
||||
// Define a React component renderer for our code blocks.
|
||||
const CodeNode = (props) => {
|
||||
return <pre {...props.attributes}><code>{props.children}</code></pre>
|
||||
}
|
||||
```
|
||||
|
||||
Pretty simple.
|
||||
|
||||
See the `props.attributes` reference? Slate passes attributes that should be rendered on the top-most element of your blocks, so that you don't have to build them up yourself. You **must** mix the attributes into your component.
|
||||
|
||||
And see that `props.children` reference? Slate will automatically render all of the children of a block for you, and then pass them to you just like any other React component would, via `props.children`. That way you don't have to muck around with rendering the proper text nodes or anything like that. You **must** render the children as the lowest leaf in your component.
|
||||
|
||||
Now, let's add that renderer to our `Editor`:
|
||||
|
||||
```js
|
||||
const CodeNode = (props) => {
|
||||
return <pre><code>{props.children}</code></pre>
|
||||
}
|
||||
|
||||
const ParagraphNode = (props) => {
|
||||
return <p>{props.children}</p>
|
||||
}
|
||||
|
||||
class App extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
state: initialState
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Editor
|
||||
state={this.state.state}
|
||||
renderNode={node => this.renderNode(node)}
|
||||
onChange={state => this.onChange(state)}
|
||||
onKeyDown={e, state => this.onKeyDown(e, state)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
// Render the right component depending on the node `type`.
|
||||
renderNode(node) {
|
||||
if (node.type == 'code') return CodeNode
|
||||
// Any nodes that fall through here will still use the default renderer.
|
||||
}
|
||||
|
||||
onChange(state) {
|
||||
this.setState({ state })
|
||||
}
|
||||
|
||||
onKeyDown(event, state) {
|
||||
if (event.which != 55 || !event.shiftKey) return
|
||||
|
||||
const newState = state
|
||||
.transform()
|
||||
.insertText('and')
|
||||
.apply()
|
||||
|
||||
return newState
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
Okay, but now we'll need a way for the user to actually turn a block into a code block. So let's change our `onKeyDown` function to add a **⌘-`** shortcut that does just that:
|
||||
|
||||
```js
|
||||
const CodeNode = (props) => {
|
||||
return <pre><code>{props.children}</code></pre>
|
||||
}
|
||||
|
||||
const ParagraphNode = (props) => {
|
||||
return <p>{props.children}</p>
|
||||
}
|
||||
|
||||
class App extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
state: initialState
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Editor
|
||||
state={this.state.state}
|
||||
renderNode={node => this.renderNode(node)}
|
||||
onChange={state => this.onChange(state)}
|
||||
onKeyDown={e, state => this.onKeyDown(e, state)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
renderNode(node) {
|
||||
if (node.type == 'code') return CodeNode
|
||||
}
|
||||
|
||||
onChange(state) {
|
||||
this.setState({ state })
|
||||
}
|
||||
|
||||
onKeyDown(event, state) {
|
||||
// Return with no changes if it's not the "`" key with cmd/ctrl pressed.
|
||||
if (event.which != 192 || !event.metaKey) return
|
||||
|
||||
// Otherwise, set the currently selected blocks type to "code".
|
||||
return state
|
||||
.transform()
|
||||
.setBlock('code')
|
||||
.apply()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
Now, if you press **⌘-`**, the block your cursor is in should turn into a code block! Magic!
|
||||
|
||||
But we forgot one thing. When you hit **⌘-`** again, it should change the code block back into a paragraph. To do that, we'll need to add a bit of logic to change the type we set based on whether any of the currently selected blocks are already a code block:
|
||||
|
||||
```js
|
||||
const CodeNode = (props) => {
|
||||
return <pre><code>{props.children}</code></pre>
|
||||
}
|
||||
|
||||
const ParagraphNode = (props) => {
|
||||
return <p>{props.children}</p>
|
||||
}
|
||||
|
||||
class App extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
state: initialState
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Editor
|
||||
state={this.state.state}
|
||||
renderNode={node => this.renderNode(node)}
|
||||
onChange={state => this.onChange(state)}
|
||||
onKeyDown={e, state => this.onKeyDown(e, state)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
renderNode(node) {
|
||||
if (node.type == 'code') return CodeNode
|
||||
}
|
||||
|
||||
onChange(state) {
|
||||
this.setState({ state })
|
||||
}
|
||||
|
||||
onKeyDown(event, state) {
|
||||
if (event.which != 192 || !event.metaKey) return
|
||||
|
||||
// Determine whether any of the currently selected blocks are code blocks.
|
||||
const isCode = state.blocks.some(block => block.type == 'code')
|
||||
|
||||
// Toggle the block type depending on `isCode`.
|
||||
return state
|
||||
.transform()
|
||||
.setBlock(isCode ? 'paragraph' : 'code')
|
||||
.apply()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
And there you have it! If you press **⌘-`** while inside a code block, it should turn back into a paragraph!
|
||||
|
||||
<br/>
|
||||
<p align="center"><strong>Next:</strong><br/><a href="./applying-custom-formatting.md">Applying Custom Formatting</a></p>
|
||||
<br/>
|
131
docs/walkthroughs/installing-slate.md
Normal file
131
docs/walkthroughs/installing-slate.md
Normal file
@@ -0,0 +1,131 @@
|
||||
|
||||
# Installing Slate
|
||||
|
||||
Slate is an npm module, so to install it you do:
|
||||
|
||||
```
|
||||
npm install slate
|
||||
```
|
||||
|
||||
You'll also need to be sure to install Slate's peer dependencies for React:
|
||||
|
||||
```
|
||||
npm install react react-dom
|
||||
```
|
||||
|
||||
_Note, if you'd rather use a pre-bundled version of Slate, you can `npm install slate` and retrieve the bundled `dist/slate.js` file! Check out the [Using the Bundled Source](./using-the-bundled-source.md) guide for more information._
|
||||
|
||||
Once you've install it, you'll need to import it.
|
||||
|
||||
Slate exposes a set of modules that you'll use to build your editor. The most important of which is an `Editor` component.
|
||||
|
||||
```js
|
||||
// Import the Slate editor.
|
||||
import { Editor } from 'slate'
|
||||
```
|
||||
|
||||
In addition to loading the editor, you need to give Slate a "initial state" to work with. Without it, Slate knows nothing about the type of content you want to create, since it has no knowledge of your schema.
|
||||
|
||||
To keep things simple, we'll use the `Raw` serializer that ships with Slate to create a new initial state that just contains a single paragraph block with some text in it:
|
||||
|
||||
```js
|
||||
// Import the "raw" serializer that ships with Slate.
|
||||
import { Editor, Raw } from 'slate'
|
||||
|
||||
// Create our initial state...
|
||||
const initialState = Raw.deserialize({
|
||||
nodes: [
|
||||
{
|
||||
kind: 'block',
|
||||
type: 'paragraph',
|
||||
nodes: [
|
||||
{
|
||||
kind: 'text',
|
||||
text: 'A line of text in a paragraph.'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
```
|
||||
|
||||
And now that we've our initial state, we define our `App` and pass it into Slate's `Editor` component, like so:
|
||||
|
||||
```js
|
||||
// Import React!
|
||||
import React from 'react'
|
||||
import { Editor, Raw } from 'slate'
|
||||
|
||||
const initialState = Raw.deserialize({
|
||||
nodes: [
|
||||
{
|
||||
kind: 'block',
|
||||
type: 'paragraph',
|
||||
nodes: [
|
||||
{
|
||||
kind: 'text',
|
||||
text: 'A line of text in a paragraph.'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
// Define our app...
|
||||
class App extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
// Set the initial state when the app is first constructed.
|
||||
this.state = {
|
||||
state: initialState
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Editor
|
||||
state={this.state.state}
|
||||
onChange={state => this.onChange(state)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
onChange(state) {
|
||||
// Update the app's React state with the new editor state.
|
||||
this.setState({ state })
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
You'll notice that the `onChange` handler passed into the `Editor` component just updates the app's state with the newest editor state. That way, when it re-renders the editor, the new state is reflected with your changes.
|
||||
|
||||
And that's it!
|
||||
|
||||
That's the most basic example of Slate. If you render that onto the page, you should see a paragraph with the text `A line of text in a paragraph.`. And when you type, you should see the text change!
|
||||
|
||||
<br/>
|
||||
<p align="center"><strong>Next:</strong><br/><a href="./adding-event-handlers.md">Adding Event Handlers</a></p>
|
||||
<br/>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
258
docs/walkthroughs/saving-and-loading-html-content.md
Normal file
258
docs/walkthroughs/saving-and-loading-html-content.md
Normal file
@@ -0,0 +1,258 @@
|
||||
|
||||
<br/>
|
||||
<p align="center"><strong>Previous:</strong><br/><a href="./saving-to-a-database.md">Saving to a Database</a></p>
|
||||
<br/>
|
||||
|
||||
# Saving and Loading HTML Content
|
||||
|
||||
In the previous guide, we looked at how to serialize the Slate editor's content and save it for later. But we only covered the [`Plain`](../reference/serializers/plain.md) and [`Raw`](../reference/serializers/raw.md) serialization techniques.
|
||||
|
||||
What if you want to save the content as HTML? It's a slightly more involved process, but this guide will show you how to do it.
|
||||
|
||||
Let's start with a basic editor:
|
||||
|
||||
```js
|
||||
import { Editor } from 'slate'
|
||||
|
||||
class App extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
state: Plain.deserialize('')
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Editor
|
||||
state={this.state.state}
|
||||
onChange={state => this.onChange(state)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
onChange(state) {
|
||||
this.setState({ state })
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
That will render a basic Slate editor on your page.
|
||||
|
||||
Now... we need to add the [`Html`](../reference/serializers/html.md) serializer. And to do that, we need to tell it a bit about the schema we plan on using. For this example, we'll work with a schema that has a few different parts:
|
||||
|
||||
- A `paragraph` block.
|
||||
- A `code` block for code samples.
|
||||
- A `quote` block for quotes...
|
||||
- And `bold`, `italic` and `underline` formatting.
|
||||
|
||||
By default, the `Html` serializer, knows nothing about our schema just like Slate itself. To fix this, we need to pass it a set of `rules`. Each rule defines how to serialize and deserialize a Slate object.
|
||||
|
||||
To start, let's create a new rule with a `deserialize` function for paragraph blocks.
|
||||
|
||||
```js
|
||||
const rules = [
|
||||
// Add our first rule with a deserializing function.
|
||||
{
|
||||
deserialize(el, next) {
|
||||
if (el.tagName == 'p') {
|
||||
return {
|
||||
kind: 'block',
|
||||
type: 'paragraph',
|
||||
nodes: next(el.children)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
If you've worked with the [`Raw`](../reference/serializers/raw.md) serializer before, the return value of the `deserialize` should look familiar! It's just the same raw JSON format.
|
||||
|
||||
The `el` argument that the `deserialize` function receives is just a [`cheerio`](https://github.com/cheeriojs/cheerio) element object. And the `next` argument is a function that will deserialize any `cheerio` element(s) we pass it, which is how you recurse through each nodes children.
|
||||
|
||||
Okay, that's `deserialize`, now let's define the `serialize` property of the paragraph rule as well:
|
||||
|
||||
```js
|
||||
const rules = [
|
||||
{
|
||||
deserialize(el, next) {
|
||||
if (el.tagName == 'p') {
|
||||
return {
|
||||
kind: 'block',
|
||||
type: 'paragraph',
|
||||
nodes: next(el.children)
|
||||
}
|
||||
}
|
||||
},
|
||||
// Add a serializing function property to our rule...
|
||||
serialize(object, children) {
|
||||
if (object.kind == 'block' && object.type == 'paragraph') {
|
||||
return <p>{children}</p>
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
The `serialize` function should also feel familiar. It's just taking [Slate models](../reference/models) and turning them into React elements, which will then be rendered to an HTML string.
|
||||
|
||||
The `object` argument of the `serialize` function will either be a [`Node`](../reference/models/node.md), a [`Mark`](../reference/models/mark.md) or a special immutable [`String`](../reference/serializers/html.md#ruleserialize) object. And the `children` argument is a React element describing the nested children of the object in question, for recursing.
|
||||
|
||||
Okay, so now our serializer can handle `paragraph` nodes.
|
||||
|
||||
Let's add the other types of blocks we want:
|
||||
|
||||
```js
|
||||
// Refactor block tags into a dictionary for cleanliness.
|
||||
const BLOCK_TAGS = {
|
||||
p: 'paragraph',
|
||||
blockquote: 'quote',
|
||||
pre: 'code'
|
||||
}
|
||||
|
||||
const rules = [
|
||||
{
|
||||
// Switch deserialize to handle more blocks...
|
||||
deserialize(el, next) {
|
||||
const type = BLOCK_TAGS[el.tagName]
|
||||
if (!type) return
|
||||
return {
|
||||
kind: 'block',
|
||||
type: type,
|
||||
nodes: next(el.children)
|
||||
}
|
||||
},
|
||||
// Switch serialize to handle more blocks...
|
||||
serialize(object, children) {
|
||||
if (object.kind != 'block') return
|
||||
switch (object.type) {
|
||||
case 'paragraph': return <p>{children}</p>
|
||||
case 'quote': return <blockquote>{children}</blockquote>
|
||||
case 'code': return <pre><code>{children}</code></pre>
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
Now each of our block types is handled.
|
||||
|
||||
You'll notice that even though code blocks are nested in a `<pre>` and a `<code>` element, we don't need to specifically handle that case in our `deserialize` function, because the `Html` serializer will automatically recurse through `el.children` if no matching deserializer is found. This way, unknown tags will just be skipped over in the tree, instead of their contents omitted completely.
|
||||
|
||||
Okay. So now our serializer can handle blocks, but we need to add our marks to it as well. Let's do that with a new rule...
|
||||
|
||||
|
||||
```js
|
||||
const BLOCK_TAGS = {
|
||||
blockquote: 'quote',
|
||||
p: 'paragraph',
|
||||
pre: 'code'
|
||||
}
|
||||
|
||||
// Add a dictionary of mark tags.
|
||||
const MARK_TAGS = {
|
||||
em: 'italic',
|
||||
strong: 'bold',
|
||||
u: 'underline',
|
||||
}
|
||||
|
||||
const rules = [
|
||||
{
|
||||
deserialize(el, next) {
|
||||
const type = BLOCK_TAGS[el.tagName]
|
||||
if (!type) return
|
||||
return {
|
||||
kind: 'block',
|
||||
type: type,
|
||||
nodes: next(el.children)
|
||||
}
|
||||
},
|
||||
serialize(object, children) {
|
||||
if (object.kind != 'block') return
|
||||
switch (object.type) {
|
||||
case 'code': return <pre><code>{children}</code></pre>
|
||||
case 'paragraph': return <p>{children}</p>
|
||||
case 'quote': return <blockquote>{children}</blockquote>
|
||||
}
|
||||
}
|
||||
},
|
||||
// Add a new rule that handles marks...
|
||||
{
|
||||
deserialize(el, next) {
|
||||
const type = MARK_TAGS[el.tagName]
|
||||
if (!type) return
|
||||
return {
|
||||
kind: 'mark',
|
||||
type: type,
|
||||
nodes: next(el.children)
|
||||
}
|
||||
},
|
||||
serialize(object, children) {
|
||||
if (object.kind != 'mark') return
|
||||
switch (object.type) {
|
||||
case 'bold': return <strong>{children}</strong>
|
||||
case 'italic': return <em>{children}</em>
|
||||
case 'underline': return <u>{children}</u>
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
Great, that's all of the rules we need! Now let's create a new `Html` serializer and pass in those rules:
|
||||
|
||||
```js
|
||||
import { Html } from 'slate'
|
||||
|
||||
// Create a new serializer instance with our `rules` from above.
|
||||
const html = new Html({ rules })
|
||||
```
|
||||
|
||||
And finally, now that we have our serializer initialized, we can update our app to use it to save and load content, like so:
|
||||
|
||||
|
||||
|
||||
```js
|
||||
// Load the initial state from Local Storage or a default.
|
||||
const initialState = (
|
||||
localStorage.get('content') ||
|
||||
'<p></p>'
|
||||
)
|
||||
|
||||
class App extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
state: html.deserialize(initialState)
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
// Add the `onDocumentChange` handler.
|
||||
return (
|
||||
<Editor
|
||||
state={this.state.state}
|
||||
onChange={state => this.onChange(state)}
|
||||
onDocumentChange={(document, state) => this.onDocumentChange(document, state)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
onChange(state) {
|
||||
this.setState({ state })
|
||||
}
|
||||
|
||||
// When the document changes, save the serialized HTML to Local Storage.
|
||||
onDocumentChange(document, state) {
|
||||
const string = html.serialize(state)
|
||||
localStorage.set('content', string)
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
And that's it! When you make any changes in your editor, you should see the updated HTML being saved to Local Storage. And when you refresh the page, those changes should be carried over.
|
224
docs/walkthroughs/saving-to-a-database.md
Normal file
224
docs/walkthroughs/saving-to-a-database.md
Normal file
@@ -0,0 +1,224 @@
|
||||
|
||||
<br/>
|
||||
<p align="center"><strong>Previous:</strong><br/><a href="./using-plugins.md">Using Plugins</a></p>
|
||||
<br/>
|
||||
|
||||
# Saving to a Database
|
||||
|
||||
Now that you've learned the basics of how to add functionality to the Slate editor, you might be wondering how you'd go about saving the content you've been editing, such that you can come back to your app later and have it load.
|
||||
|
||||
In this guide, we'll show you how to add logic to save your Slate content to a database for storage and retrieval later.
|
||||
|
||||
Let's start with a basic, plain text rendering editor:
|
||||
|
||||
```js
|
||||
import { Editor, Plain } from 'slate'
|
||||
|
||||
const initialState = Plain.deserialize('The initial state string!')
|
||||
|
||||
class App extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
state: initialState
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Editor
|
||||
state={this.state.state}
|
||||
onChange={state => this.onChange(state)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
onChange(state) {
|
||||
this.setState({ state })
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
That will render a basic Slate editor on your page, and when you type things will change. But if you refresh the page, everything will be reverted back to its original stage.
|
||||
|
||||
What we need to do is save the changes you make somewhere. For this example, we'll just be using [Local Storage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage), but it will give you an idea for where you'd need to add your own database hooks.
|
||||
|
||||
So, in our `onChange` handler, we need to save the `state`. But the `state` argument that `onChange` receives is an immutable object, so we can't just save it as-is. We need to serialize it to a format we understand first.
|
||||
|
||||
In this case, we're already using the [`Plain`](../reference/serializers/plain.md) serializer to create our intial state, so let's use it to serialize our saved state as well, like so:
|
||||
|
||||
```js
|
||||
const initialState = Plain.deserialize('The initial state string!')
|
||||
|
||||
class App extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
state: initialState
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Editor
|
||||
state={this.state.state}
|
||||
onChange={state => this.onChange(state)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
onChange(state) {
|
||||
this.setState({ state })
|
||||
|
||||
// Save the state to Local Storage.
|
||||
const string = Plain.serialize(state)
|
||||
localStorage.setItem('content', string)
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
Now whenever you edit the page, if you look in Local Storage, you should see the content string changing.
|
||||
|
||||
But... if you refresh the page, everything is still reset. That's because we need to make sure the initial state is pulled from that same Local Storage location, like so:
|
||||
|
||||
```js
|
||||
// Update the initial value to be pulled from Local Storage.
|
||||
const initialState = (
|
||||
Plain.deserialize(localStorage.getItem('content')) ||
|
||||
'The initial state string!'
|
||||
)
|
||||
|
||||
class App extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
state: initialState
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Editor
|
||||
state={this.state.state}
|
||||
onChange={state => this.onChange(state)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
onChange(state) {
|
||||
this.setState({ state })
|
||||
|
||||
const string = Plain.serialize(state)
|
||||
localStorage.setItem('content', string)
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
Now you should be able to save changes across refreshes!
|
||||
|
||||
However, if you inspect the change handler, you'll notice that it's actually saving the Local Storage value on _every_ change to the editor, even when only the selection changes! This is because `onChange` is called for _every_ change. For Local Storage this doesn't really matter, but if you're saving things to a database via HTTP request this would result in a lot of unnecessary requests.
|
||||
|
||||
Instead of using `onChange`, Slate's editor also accepts an `onDocumentChange` convenience handler that you can use to isolate saving logic to only happen when the document itself has changed, like so:
|
||||
|
||||
```js
|
||||
const initialState = (
|
||||
Plain.deserialize(localStorage.getItem('content')) ||
|
||||
'The initial state string!'
|
||||
)
|
||||
|
||||
class App extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
state: initialState
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
// Add the `onDocumentChange` handler to the editor.
|
||||
return (
|
||||
<Editor
|
||||
state={this.state.state}
|
||||
onChange={state => this.onChange(state)}
|
||||
onDocumentChange={state => this.onDocumentChange(document, state)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
onChange(state) {
|
||||
this.setState({ state })
|
||||
}
|
||||
|
||||
// Pull the saving logic out into the `onDocumentChange` handler.
|
||||
onDocumentChange(document, state) {
|
||||
const string = Plain.serialize(state)
|
||||
localStorage.setItem('content', string)
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
Now you're content will be saved only when the content itself changes!
|
||||
|
||||
Success. But we're only saving plain text strings here. If you're working with rich text, you'll need to serialize the `state` object differently. The easiest option is to just replace calls to `Plain` with `Raw`:
|
||||
|
||||
|
||||
```js
|
||||
const initialState = (
|
||||
Raw.deserialize(localStorage.getItem('content')) ||
|
||||
{
|
||||
nodes: [
|
||||
{
|
||||
kind: 'block',
|
||||
type: 'paragraph'
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
class App extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
state: initialState
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Editor
|
||||
state={this.state.state}
|
||||
onChange={state => this.onChange(state)}
|
||||
onDocumentChange={state => this.onDocumentChange(document, state)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
onChange(state) {
|
||||
this.setState({ state })
|
||||
}
|
||||
|
||||
onDocumentChange(document, state) {
|
||||
// Switch to using the Raw serializer.
|
||||
localStorage.setItem('content', Raw.serialize(state))
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
That works! Now you can preserve any formatting that the user added.
|
||||
|
||||
However, sometimes you may not want to use the raw JSON representation that Slate understands, and you may want something slightly more standardized, like good old fashioned HTML. In that case, check out the next guide...
|
||||
|
||||
|
||||
<br/>
|
||||
<p align="center"><strong>Next:</strong><br/><a href="./saving-and-loading-html-content.md">Saving and Loading HTML Content</a></p>
|
||||
<br/>
|
324
docs/walkthroughs/using-plugins.md
Normal file
324
docs/walkthroughs/using-plugins.md
Normal file
@@ -0,0 +1,324 @@
|
||||
|
||||
<br/>
|
||||
<p align="center"><strong>Previous:</strong><br/><a href="./applying-custom-formatting.md">Applying Custom Formatting</a></p>
|
||||
<br/>
|
||||
|
||||
# Using Plugins
|
||||
|
||||
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; it could just as easily have applied to _italic_ text or `code` text if we switched a few variables.
|
||||
|
||||
So let's break that logic out into it's a reusable plugin that can toggle _any_ 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)}
|
||||
onChange={state => this.onChange(state)}
|
||||
onKeyDown={(e, state) => this.onKeyDown(e, state)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
renderMark(mark) {
|
||||
if (mark.type == 'bold') return BOLD_MARK
|
||||
}
|
||||
|
||||
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 ? 'removeMark' : 'addMark']('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. But it doesn't do anything.
|
||||
|
||||
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`.
|
||||
|
||||
In this case our plugin object will have one property: a `onKeyDown` handler, with its logic copied right from our current app's code:
|
||||
|
||||
```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 ? 'removeMark' : 'addMark'](type)
|
||||
.apply()
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Boom! Now we're getting somewhere. That code is reusable for any type of mark.
|
||||
|
||||
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:
|
||||
|
||||
```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)}
|
||||
onChange={state => this.onChange(state)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
renderMark(mark) {
|
||||
if (mark.type == 'bold') return BOLD_MARK
|
||||
}
|
||||
|
||||
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)}
|
||||
onChange={state => this.onChange(state)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
// Update our render function to handle all the new marks...
|
||||
renderMark(mark) {
|
||||
return MARKS[mark.type]
|
||||
}
|
||||
|
||||
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 knowledge, you probably have no idea what our current hotkeys actually are.
|
||||
|
||||
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 ? 'removeMark' : 'addMark'](type)
|
||||
.apply()
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
And now we can make our app code much clearer for the next person who reads it:
|
||||
|
||||
```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)}
|
||||
onChange={state => this.onChange(state)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
renderMark(mark) {
|
||||
return MARKS[mark.type]
|
||||
}
|
||||
|
||||
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!
|
||||
|
||||
|
||||
<br/>
|
||||
<p align="center"><strong>Next:</strong><br/><a href="./saving-to-a-database.md">Saving to a Database</a></p>
|
||||
<br/>
|
56
docs/walkthroughs/using-the-bundled-source.md
Normal file
56
docs/walkthroughs/using-the-bundled-source.md
Normal file
@@ -0,0 +1,56 @@
|
||||
|
||||
# Using the Bundled Source
|
||||
|
||||
For most folks, you'll want to install Slate via `npm`, in which case you can follow the regular [Installing Slate](./installing-slate.md) guide.
|
||||
|
||||
But, if you'd rather install Slate by simply adding a `<script>` tag to your application, this guide will help you. To make the "bundled" use case simpler, each version of Slate ships with a bundled source file called `slate.js`.
|
||||
|
||||
To get a copy of `slate.js`, download the version of slate you want from npm:
|
||||
|
||||
```
|
||||
npm install slate @0.11.12
|
||||
```
|
||||
|
||||
And then look in the `node_modules` folder for the bundled `slate.js` file:
|
||||
|
||||
```
|
||||
node_modules/
|
||||
slate/
|
||||
dist/
|
||||
slate.js
|
||||
slate.min.js
|
||||
```
|
||||
|
||||
A minified version called `slate.min.js` is also included for convenience.
|
||||
|
||||
Before you can add `slate.js` to your page, you need to bring your own copy of `immutable`, `react` and `react-dom`, like so:
|
||||
|
||||
```html
|
||||
<script src="./vendor/react.js"></script>
|
||||
<script src="./vendor/react-dom.js"></script>
|
||||
<script src="./vendor/immutable.js"></script>
|
||||
```
|
||||
|
||||
This ensures that Slate isn't bundling its own copy of Immutable and React, which would greatly increase the file size of your application.
|
||||
|
||||
Then you can add `slate.js` after those includes:
|
||||
|
||||
```html
|
||||
<script src="./vendor/slate.js"></script>
|
||||
```
|
||||
|
||||
To make things easier, for quick prototyping, you can also use the [`npmcdn.com`](https://npmcdn.com/#/) delivery network that makes working with bundled npm modules easier. In that case, your includes would look like:
|
||||
|
||||
```html
|
||||
<script src="https://npmcdn.com/react/dist/react.js"></script>
|
||||
<script src="https://npmcdn.com/react-dom/dist/react-dom.js"></script>
|
||||
<script src="https://npmcdn.com/immutable/dist/immutable.js"></script>
|
||||
<script src="https://npmcdn.com/slate/dist/slate.js"></script>
|
||||
```
|
||||
|
||||
That's it, you're ready to go!
|
||||
|
||||
<br/>
|
||||
<p align="center"><strong>Next:</strong><br/><a href="./adding-event-handlers.md">Adding Event Handlers</a></p>
|
||||
<br/>
|
||||
|
Reference in New Issue
Block a user