1
0
mirror of https://github.com/ianstormtaylor/slate.git synced 2025-08-12 10:14:02 +02:00

update walkthroughs and history

This commit is contained in:
Ian Storm Taylor
2016-08-14 18:25:12 -07:00
parent 973c6ce0bf
commit e3e9afba43
11 changed files with 216 additions and 368 deletions

View File

@@ -5,6 +5,18 @@ This document maintains a list of changes to Slate with each new version. Until
---
### `0.13.0` — August 14, 2016
###### BREAKING CHANGES
- **The `renderNode` and `renderMark` properties are gone!** Previously, rendering nodes and marks happened via these two properties of the `<Editor>`, but this has been replaced by the new `schema` property. Check out the updated examples to see how to define a schema! There's a good chance this eliminates extra code for most use cases! :smile:
- **The `renderDecorations` property is gone!** Decoration rendering has also been replaced by the new `schema` property of the `<Editor>`.
---
### `0.12.0` — August 9, 2016
###### BREAKING CHANGES

View File

@@ -10,8 +10,8 @@ Every Slate editor has a "schema" associated with it, which contains information
- [Rule Properties](#rule-properties)
- [`decorate`](#decorate)
- [`match`](#match)
- [`normalize`](#normalize)
- [`render`](#render)
- [`transform`](#transform)
- [`validate`](#validate)
@@ -94,8 +94,8 @@ Internally, the `marks` and `nodes` properties of a schema are simply converted
{
match: Function,
decorate: Function,
normalize: Function,
render: Component || Function || Object || String,
transform: Function,
validate: Function
}
```
@@ -133,6 +133,23 @@ The `match` property is the only required property of a rule. It determines whic
The `decorate` property allows you define a function that will apply extra marks to all of the ranges of text inside a node. It is called with a [`Text`](./text.md) node and the matched node. It should return a list of characters with the desired marks, which will then be added to the text before rendering.
### `normalize`
`Function normalize(transform: Transform, object: Node, failure: Any) => Transform`
```js
{
normalize: (transform, node, invalidChildren) => {
invalidChildren.forEach((child) => {
transform = transform.removeNodeByKey(child.key)
})
return transform
}
}
```
The `normalize` property is a function to run that recovers the editor's state after the `validate` property of a rule has determined that an object is invalid. It is passed a [`Transform`](./transform.md) that it can use to make modifications. It is also passed the return value of the `validate` function, which makes it easy to quickly determine the failure reason from the validation.
### `render`
`Component` <br/>
`Function` <br/>
@@ -147,23 +164,6 @@ The `decorate` property allows you define a function that will apply extra marks
The `render` property determines which React component Slate will use to render a [`Node`](./node.md) or [`Mark`](./mark.md). Mark renderers can also be defined as an object of styles or a class name string for convenience.
### `transform`
`Function transform(transform: Transform, object: Node, failure: Any) => Transform`
```js
{
transform: (transform, node, invalidChildren) => {
invalidChildren.forEach((child) => {
transform = transform.removeNodeByKey(child.key)
})
return transform
}
}
```
The `transform` property is run to recover the editor's state after the `validate` property of a rule has determined that an object is invalid. It is passed a [`Transform`](./transform.md) that it can use to make modifications. It is also passed the return value of the `validate` function, which makes it easy to quickly determine the reason validation failed.
### `validate`
`Function validate(object: Node) => Any || Void`
@@ -176,4 +176,4 @@ The `transform` property is run to recover the editor's state after the `validat
}
```
The `validate` property allows you to define a constraint that the matching object must abide by. It should return either `Void` if the object is valid, or any non-void value if it is invalid. This makes it easy to return the exact reason that the object is invalid, which makes it simple to recover from the invalid state with the `transform` property.
The `validate` property allows you to define a constraint that the matching object must abide by. It should return either `Void` if the object is valid, or any non-void value if it is invalid. This makes it easy to return the exact reason that the object is invalid, which makes it simple to recover from the invalid state with the `normalize` property.

View File

@@ -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!

View File

@@ -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>

View File

@@ -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>

View File

@@ -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 })
}
}
```

View File

@@ -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)}

View File

@@ -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() {

View File

@@ -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 })
}
}
```

View File

@@ -374,13 +374,13 @@ class State extends new Record(DEFAULTS) {
document.filterDescendantsDeep((node) => {
if (failure = node.validate(schema)) {
const { value, rule } = failure
transform = rule.transform(transform, node, value)
transform = rule.normalize(transform, node, value)
}
})
if (failure = document.validate(schema)) {
const { value, rule } = failure
transform = rule.transform(transform, document, value)
transform = rule.normalize(transform, document, value)
}
return transform.steps.size

View File

@@ -633,7 +633,7 @@ function Plugin(options = {}) {
const invalids = nodes.filter(n => n.kind != 'block')
return invalids.size ? invalids : null
},
transform: (transform, document, invalids) => {
normalize: (transform, document, invalids) => {
return invalids.reduce((t, n) => t.removeNodeByKey(n.key), transform)
}
}
@@ -653,7 +653,7 @@ function Plugin(options = {}) {
const invalids = nodes.filter(n => n.kind != 'block' && n.kind != 'inline' && n.kind != 'text')
return invalids.size ? invalids : null
},
transform: (transform, block, invalids) => {
normalize: (transform, block, invalids) => {
return invalids.reduce((t, n) => t.removeNodeByKey(n.key), transform)
}
}
@@ -673,7 +673,7 @@ function Plugin(options = {}) {
const invalids = nodes.filter(n => n.kind != 'inline' && n.kind != 'text')
return invalids.size ? invalids : null
},
transform: (transform, inline, invalids) => {
normalize: (transform, inline, invalids) => {
return invalids.reduce((t, n) => t.removeNodeByKey(n.key), transform)
}
}