1
0
mirror of https://github.com/ianstormtaylor/slate.git synced 2025-02-08 09:01:01 +01:00
slate/docs/walkthroughs/defining-custom-block-nodes.md

213 lines
5.4 KiB
Markdown
Raw Normal View History

2016-07-08 14:10:06 -07:00
<br/>
<p align="center"><strong>Previous:</strong><br/><a href="./adding-event-handlers.md">Adding Event Handlers</a></p>
<br/>
2016-07-12 21:34:31 -07:00
# Defining Custom Block Nodes
2016-07-08 14:10:06 -07:00
2016-08-14 18:25:12 -07:00
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, which uses a plain old `<div>`.
2016-07-12 21:34:31 -07:00
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.
2016-07-08 14:10:06 -07:00
We'll show you how. Let's start with our app from earlier:
```js
class App extends React.Component {
2016-08-14 18:25:12 -07:00
state = {
state: initialState
2016-07-08 14:10:06 -07:00
}
onChange = ({ state }) => {
this.setState({ state })
2016-07-08 14:10:06 -07:00
}
onKeyDown = (event, data, change) => {
if (event.key != '&') return
2017-02-16 10:16:19 -08:00
event.preventDefault()
change.insertText('and');
return true
2016-07-08 14:10:06 -07:00
}
2017-08-02 18:36:33 +02:00
render() {
return (
<Editor
state={this.state.state}
onChange={this.onChange}
onKeyDown={this.onKeyDown}
/>
)
}
2016-07-08 14:10:06 -07:00
}
```
Now let's add "code blocks" to our editor.
2017-10-15 16:22:00 -05:00
The problem is, code blocks won't just be rendered as a plain paragraph, they'll need to be rendered differently. To make that happen, we need to define a "renderer" for `code` nodes.
2016-07-12 21:34:31 -07:00
Node renderers are just simple React components, like so:
2016-07-08 14:10:06 -07:00
```js
// Define a React component renderer for our code blocks.
2016-08-14 18:25:12 -07:00
function CodeNode(props) {
2016-07-12 21:34:31 -07:00
return <pre {...props.attributes}><code>{props.children}</code></pre>
2016-07-08 14:10:06 -07:00
}
```
Pretty simple.
2016-07-08 14:10:06 -07:00
2016-07-12 21:34:31 -07:00
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.
2016-07-08 14:10:06 -07:00
Now, let's add that renderer to our `Editor`:
```js
2016-08-14 18:25:12 -07:00
function CodeNode(props) {
return <pre {...props.attributes}><code>{props.children}</code></pre>
2016-07-08 14:10:06 -07:00
}
class App extends React.Component {
2016-08-14 18:25:12 -07:00
state = {
state: initialState,
// Add a "schema" to our app's state that we can pass to the Editor.
schema: {
nodes: {
code: CodeNode
}
2016-07-08 14:10:06 -07:00
}
}
onChange = ({ state }) => {
this.setState({ state })
2016-07-08 14:10:06 -07:00
}
onKeyDown = (event, data, change) => {
if (event.key != '&') return
2017-02-16 10:16:19 -08:00
event.preventDefault()
change.insertText('and')
return true
2016-07-08 14:10:06 -07:00
}
2017-08-02 18:36:33 +02:00
render() {
return (
// Pass in the `schema` property...
<Editor
schema={this.state.schema}
state={this.state.state}
onChange={this.onChange}
onKeyDown={this.onKeyDown}
/>
)
}
2016-07-08 14:10:06 -07:00
}
```
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:
2016-07-08 14:10:06 -07:00
```js
2016-08-14 18:25:12 -07:00
function CodeNode(props) {
return <pre {...props.attributes}><code>{props.children}</code></pre>
2016-07-08 14:10:06 -07:00
}
class App extends React.Component {
2016-08-14 18:25:12 -07:00
state = {
state: initialState,
schema: {
nodes: {
code: CodeNode
}
2016-07-08 14:10:06 -07:00
}
}
onChange = ({ state }) => {
this.setState({ state })
2016-07-08 14:10:06 -07:00
}
onKeyDown = (event, data, change) => {
2016-07-08 14:10:06 -07:00
// Return with no changes if it's not the "`" key with cmd/ctrl pressed.
if (event.key != '`' || !event.metaKey) return
2017-02-16 10:16:19 -08:00
// Prevent the "`" from being inserted by default.
event.preventDefault()
2016-07-08 14:10:06 -07:00
// Otherwise, set the currently selected blocks type to "code".
change.setBlock('code')
return true
}
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:10:06 -07:00
}
}
```
Now, if you press `⌘-\`` the block your cursor is in should turn into a code block! Magic!
2016-07-08 14:10:06 -07:00
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:
2016-07-08 14:10:06 -07:00
```js
2016-08-14 18:25:12 -07:00
function CodeNode(props) {
return <pre {...props.attributes}><code>{props.children}</code></pre>
2016-07-08 14:10:06 -07:00
}
class App extends React.Component {
2016-08-14 18:25:12 -07:00
state = {
state: initialState,
schema: {
nodes: {
code: CodeNode
}
2016-07-08 14:10:06 -07:00
}
}
onChange = ({ state }) => {
this.setState({ state })
2016-07-08 14:10:06 -07:00
}
onKeyDown = (event, data, change) => {
if (event.key != '`' || !event.metaKey) return
2017-02-16 10:16:19 -08:00
event.preventDefault()
2016-07-08 14:10:06 -07:00
// Determine whether any of the currently selected blocks are code blocks.
const isCode = change.state.blocks.some(block => block.type == 'code')
2016-07-08 14:10:06 -07:00
// Toggle the block type depending on `isCode`.
change.setBlock(isCode ? 'paragraph' : 'code')
return true
2016-07-08 14:10:06 -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:10:06 -07:00
}
```
And there you have it! If you press `⌘-\`` while inside a code block, it should turn back into a paragraph!
2016-07-08 14:10:06 -07:00
<br/>
2016-07-08 14:54:52 -07:00
<p align="center"><strong>Next:</strong><br/><a href="./applying-custom-formatting.md">Applying Custom Formatting</a></p>
2016-07-08 14:10:06 -07:00
<br/>