mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-08-22 15:02:51 +02:00
update walkthroughs and history
This commit is contained in:
@@ -16,31 +16,19 @@ So we start with our app from earlier:
|
||||
```js
|
||||
class App extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
state: initialState
|
||||
}
|
||||
state = {
|
||||
state: initialState
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Editor
|
||||
state={this.state.state}
|
||||
renderNode={node => this.renderNode(node)}
|
||||
onChange={state => this.onChange(state)}
|
||||
onChange={state => this.setState({ state })}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
renderNode(node) {
|
||||
if (node.type == 'paragraph') return ParagraphNode
|
||||
}
|
||||
|
||||
onChange(state) {
|
||||
this.setState({ state })
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
@@ -49,32 +37,20 @@ And now we'll add an `onKeyDown` handler:
|
||||
```js
|
||||
class App extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
state: initialState
|
||||
}
|
||||
state = {
|
||||
state: initialState
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Editor
|
||||
state={this.state.state}
|
||||
renderNode={node => this.renderNode(node)}
|
||||
onChange={state => this.onChange(state)}
|
||||
onChange={state => this.setState({ 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)
|
||||
@@ -85,39 +61,27 @@ class App extends React.Component {
|
||||
|
||||
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.
|
||||
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 <kbd>&</kbd> 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
|
||||
}
|
||||
state = {
|
||||
state: initialState
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Editor
|
||||
state={this.state.state}
|
||||
renderNode={node => this.renderNode(node)}
|
||||
onChange={state => this.onChange(state)}
|
||||
onChange={state => this.setState({ 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
|
||||
@@ -135,7 +99,7 @@ class App extends React.Component {
|
||||
}
|
||||
```
|
||||
|
||||
With that added, try typing `&`, and you should see it automatically become `and` instead!
|
||||
With that added, try typing <kbd>&</kbd>, 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!
|
||||
|
||||
|
@@ -14,10 +14,12 @@ So we start with our app from earlier:
|
||||
```js
|
||||
class App extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
state: initialState
|
||||
state = {
|
||||
state: initialState,
|
||||
schema: {
|
||||
nodes: {
|
||||
code: CodeNode
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,16 +27,13 @@ class App extends React.Component {
|
||||
return (
|
||||
<Editor
|
||||
state={this.state.state}
|
||||
onChange={state => this.onChange(state)}
|
||||
state={this.state.state}
|
||||
onChange={state => this.setState({ 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')
|
||||
@@ -49,32 +48,31 @@ class App extends React.Component {
|
||||
}
|
||||
```
|
||||
|
||||
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:
|
||||
And now, we'll edit the `onKeyDown` handler to make it so that when you press <kbd>⌘-B</kbd>, 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
|
||||
state = {
|
||||
state: initialState,
|
||||
schema: {
|
||||
nodes: {
|
||||
code: CodeNode
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Editor
|
||||
schema={this.state.schema}
|
||||
state={this.state.state}
|
||||
onChange={state => this.onChange(state)}
|
||||
onChange={state => this.setState({ state })}
|
||||
onKeyDown={(e, state) => this.onKeyDown(e, state)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
onChange(state) {
|
||||
this.setState({ state })
|
||||
}
|
||||
|
||||
onKeyDown(event, state) {
|
||||
if (!event.metaKey) return
|
||||
|
||||
@@ -101,34 +99,38 @@ class App extends React.Component {
|
||||
}
|
||||
```
|
||||
|
||||
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.
|
||||
Okay, so we've got the hotkey handler setup... but! If you happen to now try selecting text and hitting <kbd>⌘-B</kbd>, 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:
|
||||
For every mark type you want to add to your schema, you need to give Slate a "renderer" for that mark, just like nodes. So let's define our `bold` mark:
|
||||
|
||||
```js
|
||||
// Define a set of styles that make text bold.
|
||||
const BOLD_MARK = {
|
||||
fontWeight: 'bold'
|
||||
// Define a React component to render bold text with.
|
||||
function BoldMark(props) {
|
||||
return <strong>{props.children}</strong>
|
||||
}
|
||||
```
|
||||
|
||||
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:
|
||||
And now, let's tell Slate about that mark. To do that, we'll add it to the `schema` object under a `marks` property, like so so:
|
||||
|
||||
```js
|
||||
const BOLD_MARK = {
|
||||
fontWeight: 'bold'
|
||||
function BoldMark(props) {
|
||||
return <strong>{props.children}</strong>
|
||||
}
|
||||
|
||||
class App extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
state: initialState
|
||||
state = {
|
||||
state: initialState,
|
||||
schema: {
|
||||
nodes: {
|
||||
code: CodeNode
|
||||
},
|
||||
// Add our "bold" mark to the schema...
|
||||
marks: {
|
||||
bold: BoldMark
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,23 +138,14 @@ class App extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<Editor
|
||||
schema={this.state.schema}
|
||||
state={this.state.state}
|
||||
renderMark={mark => this.renderMark(mark)}
|
||||
onChange={state => this.onChange(state)}
|
||||
onChange={state => this.setState({ 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
|
||||
|
||||
@@ -176,7 +169,7 @@ class App extends React.Component {
|
||||
}
|
||||
```
|
||||
|
||||
Now, if you try selecting a piece of text and hitting **⌘-B** you should see it turn bold! Magic!
|
||||
Now, if you try selecting a piece of text and hitting <kbd>⌘-B</kbd> you should see it turn bold! Magic!
|
||||
|
||||
<br/>
|
||||
<p align="center"><strong>Next:</strong><br/><a href="./using-plugins.md">Using Plugins</a></p>
|
||||
|
@@ -5,7 +5,7 @@
|
||||
|
||||
# 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.
|
||||
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>`.
|
||||
|
||||
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.
|
||||
|
||||
@@ -14,32 +14,20 @@ 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
|
||||
}
|
||||
state = {
|
||||
state: initialState
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Editor
|
||||
state={this.state.state}
|
||||
renderNode={node => this.renderNode(node)}
|
||||
onChange={state => this.onChange(state)}
|
||||
onChange={state => this.setState({ 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
|
||||
|
||||
@@ -62,7 +50,7 @@ Node renderers are just simple React components, like so:
|
||||
|
||||
```js
|
||||
// Define a React component renderer for our code blocks.
|
||||
const CodeNode = (props) => {
|
||||
function CodeNode(props) {
|
||||
return <pre {...props.attributes}><code>{props.children}</code></pre>
|
||||
}
|
||||
```
|
||||
@@ -76,44 +64,34 @@ And see that `props.children` reference? Slate will automatically render all of
|
||||
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>
|
||||
function CodeNode(props) {
|
||||
return <pre {...props.attributes}><code>{props.children}</code></pre>
|
||||
}
|
||||
|
||||
class App extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
state: initialState
|
||||
state = {
|
||||
state: initialState,
|
||||
// Add a "schema" to our app's state that we can pass to the Editor.
|
||||
schema: {
|
||||
nodes: {
|
||||
code: CodeNode
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
// Pass in the `schema` property...
|
||||
<Editor
|
||||
schema={this.state.schema}
|
||||
state={this.state.state}
|
||||
renderNode={node => this.renderNode(node)}
|
||||
onChange={state => this.onChange(state)}
|
||||
onChange={state => this.setState({ 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
|
||||
|
||||
@@ -128,45 +106,35 @@ class App extends React.Component {
|
||||
}
|
||||
```
|
||||
|
||||
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:
|
||||
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 <kbd>⌘-`</kbd> shortcut that does just that:
|
||||
|
||||
```js
|
||||
const CodeNode = (props) => {
|
||||
return <pre><code>{props.children}</code></pre>
|
||||
}
|
||||
|
||||
const ParagraphNode = (props) => {
|
||||
return <p>{props.children}</p>
|
||||
function CodeNode(props) {
|
||||
return <pre {...props.attributes}><code>{props.children}</code></pre>
|
||||
}
|
||||
|
||||
class App extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
state: initialState
|
||||
state = {
|
||||
state: initialState,
|
||||
schema: {
|
||||
nodes: {
|
||||
code: CodeNode
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Editor
|
||||
schema={this.state.schema}
|
||||
state={this.state.state}
|
||||
renderNode={node => this.renderNode(node)}
|
||||
onChange={state => this.onChange(state)}
|
||||
onChange={state => this.setState({ 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
|
||||
@@ -182,47 +150,37 @@ class App extends React.Component {
|
||||
}
|
||||
```
|
||||
|
||||
Now, if you press **⌘-`**, the block your cursor is in should turn into a code block! Magic!
|
||||
Now, if you press <kbd>⌘-`</kbd>, 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:
|
||||
But we forgot one thing. When you hit <kbd>⌘-`</kbd> 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>
|
||||
function CodeNode(props) {
|
||||
return <pre {...props.attributes}><code>{props.children}</code></pre>
|
||||
}
|
||||
|
||||
class App extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
state: initialState
|
||||
state = {
|
||||
state: initialState,
|
||||
schema: {
|
||||
nodes: {
|
||||
code: CodeNode
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Editor
|
||||
schema={this.state.schema}
|
||||
state={this.state.state}
|
||||
renderNode={node => this.renderNode(node)}
|
||||
onChange={state => this.onChange(state)}
|
||||
onChange={state => this.setState({ 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
|
||||
|
||||
@@ -240,7 +198,7 @@ class App extends React.Component {
|
||||
}
|
||||
```
|
||||
|
||||
And there you have it! If you press **⌘-`** while inside a code block, it should turn back into a paragraph!
|
||||
And there you have it! If you press <kbd>⌘-`</kbd> 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>
|
||||
|
@@ -24,7 +24,7 @@ Slate exposes a set of modules that you'll use to build your editor. The most im
|
||||
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.
|
||||
In addition to rendering the editor, you need to give Slate a "initial state" to render as content.
|
||||
|
||||
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:
|
||||
|
||||
@@ -46,9 +46,11 @@ const initialState = Raw.deserialize({
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
}, { terse: true })
|
||||
```
|
||||
|
||||
The `terse: true` option there isn't important for now, but if you're curious about it you can check out the [`Raw` serializer reference](../reference/serializers/raw.md).
|
||||
|
||||
And now that we've our initial state, we define our `App` and pass it into Slate's `Editor` component, like so:
|
||||
|
||||
```js
|
||||
@@ -69,34 +71,26 @@ const initialState = Raw.deserialize({
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
}, { terse: true })
|
||||
|
||||
// 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
|
||||
}
|
||||
// Set the initial state when the app is first constructed.
|
||||
state = {
|
||||
state: initialState
|
||||
}
|
||||
|
||||
// On change, update the app's React state with the new editor state.
|
||||
render() {
|
||||
return (
|
||||
<Editor
|
||||
state={this.state.state}
|
||||
onChange={state => this.onChange(state)}
|
||||
onChange={state => this.setState({ state })}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
onChange(state) {
|
||||
// Update the app's React state with the new editor state.
|
||||
this.setState({ state })
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
|
@@ -16,11 +16,8 @@ import { Editor } from 'slate'
|
||||
|
||||
class App extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
state: Plain.deserialize('')
|
||||
}
|
||||
state = {
|
||||
state: Plain.deserialize('')
|
||||
}
|
||||
|
||||
render() {
|
||||
@@ -213,8 +210,6 @@ 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 = (
|
||||
@@ -224,10 +219,20 @@ const initialState = (
|
||||
|
||||
class App extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
state: html.deserialize(initialState)
|
||||
state = {
|
||||
state: html.deserialize(initialState),
|
||||
// Add a schema with our nodes and marks...
|
||||
schema: {
|
||||
nodes: {
|
||||
code: props => <pre {...props.attributes}>{props.children}</pre>,
|
||||
paragraph: props => <p {...props.attributes}>{props.children}</p>,
|
||||
quote: props => <blockquote {...props.attributes}>{props.children}</blockquote>,
|
||||
},
|
||||
marks: {
|
||||
bold: props => <strong>{props.children}</strong>,
|
||||
italic: props => <em>{props.children}</em>,
|
||||
underline: props => <u>{props.children}</u>,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,6 +240,7 @@ class App extends React.Component {
|
||||
// Add the `onDocumentChange` handler.
|
||||
return (
|
||||
<Editor
|
||||
schema={this.state.schema}
|
||||
state={this.state.state}
|
||||
onChange={state => this.onChange(state)}
|
||||
onDocumentChange={(document, state) => this.onDocumentChange(document, state)}
|
||||
|
@@ -18,11 +18,8 @@ const initialState = Plain.deserialize('The initial state string!')
|
||||
|
||||
class App extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
state: initialState
|
||||
}
|
||||
state = {
|
||||
state: initialState
|
||||
}
|
||||
|
||||
render() {
|
||||
@@ -54,11 +51,8 @@ const initialState = Plain.deserialize('The initial state string!')
|
||||
|
||||
class App extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
state: initialState
|
||||
}
|
||||
state = {
|
||||
state: initialState
|
||||
}
|
||||
|
||||
render() {
|
||||
@@ -94,11 +88,8 @@ const initialState = (
|
||||
|
||||
class App extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
state: initialState
|
||||
}
|
||||
state = {
|
||||
state: initialState
|
||||
}
|
||||
|
||||
render() {
|
||||
@@ -134,11 +125,8 @@ const initialState = (
|
||||
|
||||
class App extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
state: initialState
|
||||
}
|
||||
state = {
|
||||
state: initialState
|
||||
}
|
||||
|
||||
render() {
|
||||
@@ -185,11 +173,8 @@ const initialState = (
|
||||
|
||||
class App extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
state: initialState
|
||||
}
|
||||
state = {
|
||||
state: initialState
|
||||
}
|
||||
|
||||
render() {
|
||||
|
@@ -14,45 +14,33 @@ So let's break that logic out into it's a reusable plugin that can toggle _any_
|
||||
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
|
||||
state = {
|
||||
state: initialState,
|
||||
schema: {
|
||||
marks: {
|
||||
bold: props => <strong>{props.children}</strong>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Editor
|
||||
schema={this.state.schema}
|
||||
state={this.state.state}
|
||||
renderMark={mark => this.renderMark(mark)}
|
||||
onChange={state => this.onChange(state)}
|
||||
onChange={state => this.setState({ 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')
|
||||
.toggleMark('bold')
|
||||
.apply()
|
||||
}
|
||||
|
||||
@@ -84,13 +72,10 @@ function MarkHotkey(options) {
|
||||
// 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.
|
||||
// Toggle the mark `type`.
|
||||
return state
|
||||
.transform()
|
||||
[isActive ? 'removeMark' : 'addMark'](type)
|
||||
.toggleMark(type)
|
||||
.apply()
|
||||
}
|
||||
}
|
||||
@@ -102,8 +87,8 @@ 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'
|
||||
function BoldMark(props) {
|
||||
return <strong>{props.children}</strong>
|
||||
}
|
||||
|
||||
// Initialize our bold-mark-adding plugin.
|
||||
@@ -119,33 +104,27 @@ const plugins = [
|
||||
|
||||
class App extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
state: initialState
|
||||
state = {
|
||||
state: initialState,
|
||||
schema: {
|
||||
marks: {
|
||||
bold: props => <strong>{props.children}</strong>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add the `plugins` property to the editor, and remove `onKeyDown`.
|
||||
render() {
|
||||
return (
|
||||
// Add the `plugins` property to the editor, and remove `onKeyDown`.
|
||||
<Editor
|
||||
state={this.state.state}
|
||||
plugins={plugins}
|
||||
renderMark={mark => this.renderMark(mark)}
|
||||
onChange={state => this.onChange(state)}
|
||||
schema={this.state.schema}
|
||||
state={this.state.state}
|
||||
onChange={state => this.setState({ state })}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
renderMark(mark) {
|
||||
if (mark.type == 'bold') return BOLD_MARK
|
||||
}
|
||||
|
||||
onChange(state) {
|
||||
this.setState({ state })
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
@@ -154,26 +133,7 @@ Awesome. If you test out the editor now, you'll notice that everything still wor
|
||||
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...
|
||||
// Initialize a plugin for each mark...
|
||||
const plugins = [
|
||||
MarkHotkey({ code: 66, type: 'bold' }),
|
||||
MarkHotkey({ code: 192, type: 'code' }),
|
||||
@@ -184,33 +144,31 @@ const plugins = [
|
||||
|
||||
class App extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
state: initialState
|
||||
state = {
|
||||
state: initialState,
|
||||
schema: {
|
||||
marks: {
|
||||
bold: props => <strong>{props.children}</strong>,
|
||||
// Add our new mark renderers...
|
||||
code: props => <code>{props.children}</code>,
|
||||
italic: props => <em>{props.children}</em>,
|
||||
strikethrough: props => <del>{props.children}</del>,
|
||||
underline: props => <u>{props.children}</u>,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Editor
|
||||
state={this.state.state}
|
||||
plugins={plugins}
|
||||
renderMark={mark => this.renderMark(mark)}
|
||||
onChange={state => this.onChange(state)}
|
||||
schema={this.state.schema}
|
||||
state={this.state.state}
|
||||
onChange={state => this.setState({ state })}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
// Update our render function to handle all the new marks...
|
||||
renderMark(mark) {
|
||||
return MARKS[mark.type]
|
||||
}
|
||||
|
||||
onChange(state) {
|
||||
this.setState({ state })
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
@@ -244,11 +202,9 @@ function MarkHotkey(options) {
|
||||
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)
|
||||
.toggleMark(type)
|
||||
.apply()
|
||||
}
|
||||
}
|
||||
@@ -258,24 +214,6 @@ function MarkHotkey(options) {
|
||||
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' }),
|
||||
@@ -287,32 +225,30 @@ const plugins = [
|
||||
|
||||
class App extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
state: initialState
|
||||
state = {
|
||||
state: initialState,
|
||||
schema: {
|
||||
marks: {
|
||||
bold: props => <strong>{props.children}</strong>,
|
||||
code: props => <code>{props.children}</code>,
|
||||
italic: props => <em>{props.children}</em>,
|
||||
strikethrough: props => <del>{props.children}</del>,
|
||||
underline: props => <u>{props.children}</u>,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Editor
|
||||
state={this.state.state}
|
||||
plugins={plugins}
|
||||
renderMark={mark => this.renderMark(mark)}
|
||||
onChange={state => this.onChange(state)}
|
||||
schema={this.state.schema}
|
||||
state={this.state.state}
|
||||
onChange={state => this.setState({ state })}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
renderMark(mark) {
|
||||
return MARKS[mark.type]
|
||||
}
|
||||
|
||||
onChange(state) {
|
||||
this.setState({ state })
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
|
Reference in New Issue
Block a user