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

228 lines
5.9 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 = {
value: initialValue,
2016-07-08 14:10:06 -07:00
}
onChange = ({ value }) => {
this.setState({ value })
2016-07-08 14:10:06 -07:00
}
onKeyDown = (event, 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
value={this.state.value}
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) {
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 = {
value: initialValue,
2016-07-08 14:10:06 -07:00
}
onChange = ({ value }) => {
this.setState({ value })
2016-07-08 14:10:06 -07:00
}
onKeyDown = (event, 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 `renderNode` prop...
<Editor
value={this.state.value}
onChange={this.onChange}
onKeyDown={this.onKeyDown}
renderNode={this.renderNode}
/>
)
}
// Add a `renderNode` method to render a `CodeNode` for code blocks.
renderNode = props => {
switch (props.node.type) {
case 'code':
return <CodeNode {...props} />
}
}
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 `control-\`` 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 = {
value: initialValue,
2016-07-08 14:10:06 -07:00
}
onChange = ({ value }) => {
this.setState({ value })
2016-07-08 14:10:06 -07:00
}
onKeyDown = (event, change) => {
// Return with no changes if it's not the "`" key with ctrl pressed.
if (event.key != '`' || !event.ctrlKey) 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.setBlocks('code')
return true
}
2017-08-02 18:36:33 +02:00
render() {
return (
<Editor
value={this.state.value}
onChange={this.onChange}
onKeyDown={this.onKeyDown}
renderNode={this.renderNode}
/>
)
2016-07-08 14:10:06 -07:00
}
renderNode = props => {
switch (props.node.type) {
case 'code':
return <CodeNode {...props} />
}
}
2016-07-08 14:10:06 -07:00
}
```
Now, if you press `control-\`` the block your cursor is in should turn into a code block! Magic!
2016-07-08 14:10:06 -07:00
_Note: The Edge browser does not currently support `control-...` key events (see [issue](https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/742263/)), so this example won't work on it._
But we forgot one thing. When you hit `control-\`` 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 = {
value: initialValue,
2016-07-08 14:10:06 -07:00
}
onChange = ({ value }) => {
this.setState({ value })
2016-07-08 14:10:06 -07:00
}
onKeyDown = (event, change) => {
if (event.key != '`' || !event.ctrlKey) 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.value.blocks.some(block => block.type == 'code')
2016-07-08 14:10:06 -07:00
// Toggle the block type depending on `isCode`.
change.setBlocks(isCode ? 'paragraph' : 'code')
return true
2016-07-08 14:10:06 -07:00
}
2017-08-02 18:36:33 +02:00
render() {
return (
<Editor
value={this.state.value}
onChange={this.onChange}
onKeyDown={this.onKeyDown}
renderNode={this.renderNode}
/>
)
}
renderNode = props => {
switch (props.node.type) {
case 'code':
return <CodeNode {...props} />
}
}
2016-07-08 14:10:06 -07:00
}
```
And there you have it! If you press `control-\`` 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/>