1
0
mirror of https://github.com/ianstormtaylor/slate.git synced 2025-08-19 13:41:19 +02:00

update walkthroughs

This commit is contained in:
Ian Storm Taylor
2019-12-06 12:06:53 -05:00
parent 624c03339b
commit be8b7222ee
6 changed files with 370 additions and 99 deletions

View File

@@ -16,8 +16,6 @@ _Note, if you'd rather use a pre-bundled version of Slate, you can `yarn add sla
Once you've installed Slate, 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 are the `Editor` class and the `<Editable>` component.
```js
// Import the Slate editor factory.
import { createEditor } from 'slate'
@@ -47,19 +45,44 @@ const App = () => {
Of course we haven't rendered anything, so you won't see any changes.
Next up is to render a `<Slate>` context provider.
The provider component keeps track of your Slate editor, its plugins, its default value, and any changes that occur. It **must** be rendered above any `<Editable>` components. But it can also provide the editor state to other components like toolbars, menus, etc. using the `useSlate` hook.
Next we want to create state for `value` and `selection`:
```jsx
const App = () => {
const editor = useMemo(() => withReact(createEditor()), [])
// Render the Slate editor context.
return <Slate editor={editor} />
// Keep track of state for the value and selection of the editor.
const [value, setValue] = useState([])
const [selection, setSelection] = useState(null)
return null
}
```
You can think of the `<Slate>` component as provided an "un-controlled" editor context to every component underneath it.
Next up is to render a `<Slate>` context provider.
The provider component keeps track of your Slate editor, its plugins, its value, its selection, and any changes that occur. It **must** be rendered above any `<Editable>` components. But it can also provide the editor state to other components like toolbars, menus, etc. using the `useSlate` hook.
```jsx
const App = () => {
const editor = useMemo(() => withReact(createEditor()), [])
const [value, setValue] = useState([])
const [selection, setSelection] = useState(null)
// Render the Slate context.
return (
<Slate
editor={editor}
value={value}
selection={selection}
onChange={(value, selection) => {
setValue(value)
setSelection(selection)
}}
/>
)
}
```
You can think of the `<Slate>` component as providing a "controlled" context to every component underneath it.
This is a slightly different mental model than things like `<input>` or `<textarea>`, because richtext documents are more complex. You'll often want to include toolbars, or live previews, or other complex components next to your editable content.
@@ -70,9 +93,19 @@ Okay, so the next step is to render the `<Editable>` component itself:
```jsx
const App = () => {
const editor = useMemo(() => withReact(createEditor()), [])
const [value, setValue] = useState([])
const [selection, setSelection] = useState(null)
return (
// Add the editable component inside the context.
<Slate editor={editor}>
<Slate
editor={editor}
value={value}
selection={selection}
onChange={(value, selection) => {
setValue(value)
setSelection(selection)
}}
>
<Editable />
</Slate>
)
@@ -81,28 +114,32 @@ const App = () => {
The `<Editable>` component acts like `contenteditable`. Anywhere you render it will render an editable richtext document for the nearest editor context.
There's only one last step. So far we haven't defined what the default value of the editor is, so it's empty. Let's fix that by defining an initial value.
There's only one last step. So far we've been using an empty `[]` array as the initial value of the editor, so it has no content. Let's fix that by defining an initial value.
The value is just plain JSON. Here's one containing a single paragraph block with some text in it:
```js
// Create our default value...
const defaultValue = [
{
type: 'paragraph',
children: [
{
text: 'A line of text in a paragraph.',
},
],
},
]
const App = () => {
const editor = useMemo(() => withReact(createEditor()), [])
const [selection, setSelection] = useState(null)
// Add the initial value when setting up our state.
const [value, setValue] = useState([
{
type: 'paragraph',
children: [{ text: 'A line of text in a paragraph.' }],
},
])
return (
// Add the default value as a prop to the editor context.
<Slate editor={editor} defaultValue={defaultValue}>
<Slate
editor={editor}
value={value}
selection={selection}
onChange={(value, selection) => {
setValue(value)
setSelection(selection)
}}
>
<Editable />
</Slate>
)
@@ -112,5 +149,3 @@ const App = () => {
There you have 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!
You'll notice that there is no `onChange` handler defined. That's because the `<Slate>` context acts like an **un-controlled** component, with the changes automatically being propagated to any context consumers. However, just like with un-controlled components you can attach an `onChange` prop to listen for changes. We'll cover that later.

View File

@@ -11,8 +11,24 @@ Here's our app from earlier:
```js
const App = () => {
const editor = useMemo(() => withReact(createEditor()), [])
const [selection, setSelection] = useState(null)
const [value, setValue] = useState([
{
type: 'paragraph',
children: [{ text: 'A line of text in a paragraph.' }],
},
])
return (
<Slate editor={editor} defaultValue={defaultValue}>
<Slate
editor={editor}
value={value}
selection={selection}
onChange={(value, selection) => {
setValue(value)
setSelection(selection)
}}
>
<Editable />
</Slate>
)
@@ -24,8 +40,24 @@ Now we add an `onKeyDown` handler:
```js
const App = () => {
const editor = useMemo(() => withReact(createEditor()), [])
const [selection, setSelection] = useState(null)
const [value, setValue] = useState([
{
type: 'paragraph',
children: [{ text: 'A line of text in a paragraph.' }],
},
])
return (
<Slate editor={editor} defaultValue={defaultValue}>
<Slate
editor={editor}
value={value}
selection={selection}
onChange={(value, selection) => {
setValue(value)
setSelection(selection)
}}
>
<Editable
// Define a new handler which prints the key that was pressed.
onKeyDown={event => {
@@ -46,8 +78,24 @@ Our `onKeyDown` handler might look like this:
```js
const App = () => {
const editor = useMemo(() => withReact(createEditor()), [])
const [selection, setSelection] = useState(null)
const [value, setValue] = useState([
{
type: 'paragraph',
children: [{ text: 'A line of text in a paragraph.' }],
},
])
return (
<Slate editor={editor} defaultValue={defaultValue}>
<Slate
editor={editor}
value={value}
selection={selection}
onChange={(value, selection) => {
setValue(value)
setSelection(selection)
}}
>
<Editable
onKeyDown={event => {
if (event.key === '&') {

View File

@@ -9,8 +9,24 @@ We'll show you how. Let's start with our app from earlier:
```js
const App = () => {
const editor = useMemo(() => withReact(createEditor()), [])
const [selection, setSelection] = useState(null)
const [value, setValue] = useState([
{
type: 'paragraph',
children: [{ text: 'A line of text in a paragraph.' }],
},
])
return (
<Slate editor={editor} defaultValue={defaultValue}>
<Slate
editor={editor}
value={value}
selection={selection}
onChange={(value, selection) => {
setValue(value)
setSelection(selection)
}}
>
<Editable
onKeyDown={event => {
if (event.key === '&') {
@@ -60,6 +76,13 @@ Now, let's add that renderer to our `Editor`:
```js
const App = () => {
const editor = useMemo(() => withReact(createEditor()), [])
const [selection, setSelection] = useState(null)
const [value, setValue] = useState([
{
type: 'paragraph',
children: [{ text: 'A line of text in a paragraph.' }],
},
])
// Define a rendering function based on the element passed to `props`. We use
// `useCallback` here to memoize the function for subsequent renders.
@@ -73,7 +96,15 @@ const App = () => {
}, [])
return (
<Slate editor={editor} defaultValue={defaultValue}>
<Slate
editor={editor}
value={value}
selection={selection}
onChange={(value, selection) => {
setValue(value)
setSelection(selection)
}}
>
<Editable
// Pass in the `renderElement` function.
renderElement={renderElement}
@@ -109,6 +140,14 @@ import { Editor } from 'slate'
const App = () => {
const editor = useMemo(() => withReact(createEditor()), [])
const [selection, setSelection] = useState(null)
const [value, setValue] = useState([
{
type: 'paragraph',
children: [{ text: 'A line of text in a paragraph.' }],
},
])
const renderElement = useCallback(props => {
switch (props.element.type) {
case 'code':
@@ -119,7 +158,15 @@ const App = () => {
}, [])
return (
<Slate editor={editor} defaultValue={defaultValue}>
<Slate
editor={editor}
value={value}
selection={selection}
onChange={(value, selection) => {
setValue(value)
setSelection(selection)
}}
>
<Editable
renderElement={renderElement}
onKeyDown={event => {
@@ -154,8 +201,15 @@ But we forgot one thing. When you hit `` Ctrl-` `` again, it should change the c
```js
const App = () => {
const [value, setValue] = useState(initialValue)
const editor = useMemo(() => withReact(createEditor()), [])
const [selection, setSelection] = useState(null)
const [value, setValue] = useState([
{
type: 'paragraph',
children: [{ text: 'A line of text in a paragraph.' }],
},
])
const renderElement = useCallback(props => {
switch (props.element.type) {
case 'code':
@@ -166,20 +220,26 @@ const App = () => {
}, [])
return (
<Slate editor={editor} defaultValue={defaultValue}>
<Slate
editor={editor}
value={value}
selection={selection}
onChange={(value, selection) => {
setValue(value)
setSelection(selection)
}}
>
<Editable
renderElement={renderElement}
onKeyDown={event => {
if (event.key === '`' && event.ctrlKey) {
event.preventDefault()
// Determine whether any of the currently selected blocks are code blocks.
const [node] = Editor.nodes(editor, { match: { type: 'code' } })
const isCodeActive = !!node
// Toggle the block type depending on `isCode`.
const [match] = Editor.nodes(editor, { match: { type: 'code' } })
// Toggle the block type depending on whether there's already a match.
Editor.setNodes(
editor,
{ type: isCodeActive ? 'paragraph' : 'code' },
{ type: match ? 'paragraph' : 'code' },
{ match: 'block' }
)
}

View File

@@ -9,6 +9,14 @@ So we start with our app from earlier:
```js
const App = () => {
const editor = useMemo(() => withReact(createEditor()), [])
const [selection, setSelection] = useState(null)
const [value, setValue] = useState([
{
type: 'paragraph',
children: [{ text: 'A line of text in a paragraph.' }],
},
])
const renderElement = useCallback(props => {
switch (props.element.type) {
case 'code':
@@ -19,18 +27,25 @@ const App = () => {
}, [])
return (
<Slate editor={editor} defaultValue={defaultValue}>
<Slate
editor={editor}
value={value}
selection={selection}
onChange={(value, selection) => {
setValue(value)
setSelection(selection)
}}
>
<Editable
renderElement={renderElement}
onKeyDown={event => {
if (event.key === '`' && event.ctrlKey) {
event.preventDefault()
const { selection } = editor
const [node] = Editor.nodes(editor, { match: { type: 'code' } })
const isCodeActive = !!node
const [match] = Editor.nodes(editor, { match: { type: 'code' } })
Editor.setNodes(
editor,
{ type: isCodeActive ? 'paragraph' : 'code' },
{ type: match ? 'paragraph' : 'code' },
{ match: 'block' }
)
}
@@ -46,6 +61,14 @@ And now, we'll edit the `onKeyDown` handler to make it so that when you press `c
```js
const App = () => {
const editor = useMemo(() => withReact(createEditor()), [])
const [selection, setSelection] = useState(null)
const [value, setValue] = useState([
{
type: 'paragraph',
children: [{ text: 'A line of text in a paragraph.' }],
},
])
const renderElement = useCallback(props => {
switch (prop.element.type) {
case 'code':
@@ -56,7 +79,15 @@ const App = () => {
}, [])
return (
<Slate editor={editor} defaultValue={defaultValue}>
<Slate
editor={editor}
value={value}
selection={selection}
onChange={(value, selection) => {
setValue(value)
setSelection(selection)
}}
>
<Editable
renderElement={renderElement}
onKeyDown={event => {
@@ -68,11 +99,10 @@ const App = () => {
// When "`" is pressed, keep our existing code block logic.
case '`': {
event.preventDefault()
const [node] = Editor.nodes(editor, { match: { type: 'code' } })
const isCodeActive = !!node
const [match] = Editor.nodes(editor, { match: { type: 'code' } })
Editor.setNodes(
editor,
{ type: isCodeActive ? null : 'code' },
{ type: match ? 'paragraph' : 'code' },
{ match: 'block' }
)
break
@@ -123,6 +153,14 @@ And now, let's tell Slate about that leaf. To do that, we'll pass in the `render
```js
const App = () => {
const editor = useMemo(() => withReact(createEditor()), [])
const [selection, setSelection] = useState(null)
const [value, setValue] = useState([
{
type: 'paragraph',
children: [{ text: 'A line of text in a paragraph.' }],
},
])
const renderElement = useCallback(props => {
switch (props.element.type) {
case 'code':
@@ -138,7 +176,15 @@ const App = () => {
}, [])
return (
<Slate editor={editor} defaultValue={defaultValue}>
<Slate
editor={editor}
value={value}
selection={selection}
onChange={(value, selection) => {
setValue(value)
setSelection(selection)
}}
>
<Editable
renderElement={renderElement}
// Pass in the `renderLeaf` function.
@@ -151,11 +197,10 @@ const App = () => {
switch (event.key) {
case '`': {
event.preventDefault()
const [node] = Editor.nodes(editor, { match: { type: 'code' } })
const isCodeActive = !!node
const [match] = Editor.nodes(editor, { match: { type: 'code' } })
Editor.setNodes(
editor,
{ type: isCodeActive ? null : 'code' },
{ type: match ? null : 'code' },
{ match: 'block' }
)
break

View File

@@ -13,6 +13,14 @@ We'll start with our app from earlier:
```js
const App = () => {
const editor = useMemo(() => withReact(createEditor()), [])
const [selection, setSelection] = useState(null)
const [value, setValue] = useState([
{
type: 'paragraph',
children: [{ text: 'A line of text in a paragraph.' }],
},
])
const renderElement = useCallback(props => {
switch (props.element.type) {
case 'code':
@@ -27,7 +35,15 @@ const App = () => {
}, [])
return (
<Slate editor={editor} defaultValue={defaultValue}>
<Slate
editor={editor}
value={value}
selection={selection}
onChange={(value, selection) => {
setValue(value)
setSelection(selection)
}}
>
<Editable
renderElement={renderElement}
renderLeaf={renderLeaf}
@@ -39,11 +55,10 @@ const App = () => {
switch (event.key) {
case '`': {
event.preventDefault()
const [node] = Editor.nodes(editor, { match: { type: 'code' } })
const isCodeActive = !!node
const [match] = Editor.nodes(editor, { match: { type: 'code' } })
Editor.setNodes(
editor,
{ type: isCodeActive ? null : 'code' },
{ type: match ? null : 'code' },
{ match: 'block' }
)
break
@@ -79,6 +94,14 @@ const withCustom = editor => {
const App = () => {
// Wrap the editor with our new `withCustom` plugin.
const editor = useMemo(() => withCustom(withReact(createEditor())), [])
const [selection, setSelection] = useState(null)
const [value, setValue] = useState([
{
type: 'paragraph',
children: [{ text: 'A line of text in a paragraph.' }],
},
])
const renderElement = useCallback(props => {
switch (props.element.type) {
case 'code':
@@ -93,7 +116,15 @@ const App = () => {
}, [])
return (
<Slate editor={editor} defaultValue={defaultValue}>
<Slate
editor={editor}
value={value}
selection={selection}
onChange={(value, selection) => {
setValue(value)
setSelection(selection)
}}
>
<Editable
renderElement={renderElement}
renderLeaf={renderLeaf}
@@ -193,6 +224,14 @@ const CustomEditor = {
const App = () => {
const editor = useMemo(() => withCustom(withReact(createEditor())), [])
const [selection, setSelection] = useState(null)
const [value, setValue] = useState([
{
type: 'paragraph',
children: [{ text: 'A line of text in a paragraph.' }],
},
])
const renderElement = useCallback(props => {
switch (props.element.type) {
case 'code':
@@ -207,16 +246,24 @@ const App = () => {
}, [])
return (
<Slate editor={editor} defaultValue={defaultValue}>
<Slate
editor={editor}
value={value}
selection={selection}
onChange={(value, selection) => {
setValue(value)
setSelection(selection)
}}
>
<Editable
renderElement={renderElement}
renderLeaf={renderLeaf}
// Replace the `onKeyDown` logic with our new commands.
onKeyDown={event => {
if (!event.ctrlKey) {
return
}
// Replace the `onKeyDown` logic with our new commands.
switch (event.key) {
case '`': {
event.preventDefault()
@@ -242,6 +289,14 @@ Now our commands are clearly defined and you can invoke them from anywhere we ha
```js
const App = () => {
const editor = useMemo(() => withCustom(withReact(createEditor())), [])
const [selection, setSelection] = useState(null)
const [value, setValue] = useState([
{
type: 'paragraph',
children: [{ text: 'A line of text in a paragraph.' }],
},
])
const renderElement = useCallback(props => {
switch (props.element.type) {
case 'code':
@@ -256,8 +311,16 @@ const App = () => {
}, [])
return (
// Add a toolbar with buttons that call the same methods.
<Slate editor={editor} defaultValue={defaultValue}>
// Add a toolbar with buttons that call the same methods.
<Slate
editor={editor}
value={value}
selection={selection}
onChange={(value, selection) => {
setValue(value)
setSelection(selection)
}}
>
<div>
<button
onMouseDown={event => {

View File

@@ -7,24 +7,26 @@ In this guide, we'll show you how to add logic to save your Slate content to a d
Let's start with a basic editor:
```js
import React, { useMemo } from 'react'
import { createEditor } from 'slate'
import { Slate, Editable, withReact } from 'slate-react'
const defaultValue = [
{
children: [
{
text: 'A line of text in a paragraph.',
},
],
},
]
const App = () => {
const editor = useMemo(() => withReact(createEditor()), [])
const [selection, setSelection] = useState(null)
const [value, setValue] = useState([
{
type: 'paragraph',
children: [{ text: 'A line of text in a paragraph.' }],
},
])
return (
<Slate editor={editor} defaultValue={defaultValue}>
<Slate
editor={editor}
value={value}
selection={selection}
onChange={(value, selection) => {
setValue(value)
setSelection(selection)
}}
>
<Editable />
</Slate>
)
@@ -50,11 +52,23 @@ const defaultValue = [
const App = () => {
const editor = useMemo(() => withReact(createEditor()), [])
const [selection, setSelection] = useState(null)
const [value, setValue] = useState([
{
type: 'paragraph',
children: [{ text: 'A line of text in a paragraph.' }],
},
])
return (
<Slate
editor={editor}
defaultValue={defaultValue}
onChange={value => {
value={value}
selection={selection}
onChange={(value, selection) => {
setValue(value)
setSelection(selection)
// Save the value to Local Storage.
const content = JSON.stringify(value)
localStorage.setItem('content', content)
@@ -71,25 +85,27 @@ Now whenever you edit the page, if you look in Local Storage, you should see the
But... if you refresh the page, everything is still reset. That's because we need to make sure the initial value is pulled from that same Local Storage location, like so:
```js
// Update the initial content to be pulled from Local Storage if it exists.
const existingValue = JSON.parse(localStorage.getItem('content'))
const defaultValue = existingValue || [
{
children: [
{
text: 'A line of text in a paragraph.',
},
],
},
]
const App = () => {
const editor = useMemo(() => withReact(createEditor()), [])
const [selection, setSelection] = useState(null)
// Update the initial content to be pulled from Local Storage if it exists.
const [value, setValue] = useState(
JSON.parse(localStorage.getItem('content')) || [
{
type: 'paragraph',
children: [{ text: 'A line of text in a paragraph.' }],
},
]
)
return (
<Slate
editor={editor}
defaultValue={defaultValue}
onChange={value => {
value={value}
selection={selection}
onChange={(value, selection) => {
setValue(value)
setSelection(selection)
const content = JSON.stringify(value)
localStorage.setItem('content', content)
}}
@@ -131,20 +147,24 @@ const deserialize = string => {
})
}
// Use our deserializing function to read the data from Local Storage.
const existingValue = localStorage.getItem('content')
const initialValue = deserialize(existingValue || '')
const App = () => {
const editor = useMemo(() => withReact(createEditor()), [])
const [selection, setSelection] = useState(null)
// Use our deserializing function to read the data from Local Storage.
const [value, setValue] = useState(
deserialize(localStorage.getItem('content')) || ''
)
return (
<Slate
editor={editor}
defaultValue={defaultValue}
onChange={value => {
value={value}
selection={selection}
onChange={(value, selection) => {
setValue(value)
setSelection(selection)
// Serialize the value and save the string value to Local Storage.
const content = serialize(value)
localStorage.setItem('content', content)
localStorage.setItem('content', serialize(value))
}}
>
<Editable />