#### Is this adding or improving a _feature_ or fixing a _bug_? Improvement / debt. #### What's the new behavior? This pull request removes the `Change` object as we know it, and folds all of its behaviors into the new `Editor` controller instead, simplifying a lot of the confusion around what is a "change vs. editor" and when to use which. It makes the standard API a **lot** nicer to use I think. --- ###### NEW **The `editor.command` and `editor.query` methods can take functions.** Previously they only accepted a `type` string and would look up the command or query by type. Now, they also accept a custom function. This is helpful for plugin authors, who want to accept a "command option", since it gives users more flexibility to write one-off commands or queries. For example a plugin could be passed either: ```js Hotkey({ hotkey: 'cmd+b', command: 'addBoldMark', }) ``` Or a custom command function: ```js Hotkey({ hotkey: 'cmd+b', command: editor => editor.addBoldMark().moveToEnd() }) ``` ###### BREAKING **The `Change` object has been removed.** The `Change` object as we know it previously has been removed, and all of its behaviors have been folded into the `Editor` controller. This includes the top-level commands and queries methods, as well as methods like `applyOperation` and `normalize`. _All places that used to receive `change` now receive `editor`, which is API equivalent._ **Changes are now flushed to `onChange` asynchronously.** Previously this was done synchronously, which resulted in some strange race conditions in React environments. Now they will always be flushed asynchronously, just like `setState`. **The `render*` and `decorate*` middleware signatures have changed!** Previously the `render*` and `decorate*` middleware was passed `(props, next)`. However now, for consistency with the other middleware they are all passed `(props, editor, next)`. This way, all middleware always receive `editor` and `next` as their final two arguments. **The `normalize*` and `validate*` middleware signatures have changed!** Previously the `normalize*` and `validate*` middleware was passed `(node, next)`. However now, for consistency with the other middleware they are all passed `(node, editor, next)`. This way, all middleware always receive `editor` and `next` as their final two arguments. **The `editor.event` method has been removed.** Previously this is what you'd use when writing tests to simulate events being fired—which were slightly different to other running other middleware. With the simplification to the editor and to the newly-consistent middleware signatures, you can now use `editor.run` directly to simulate events: ```js editor.run('onKeyDown', { key: 'Tab', ... }) ``` ###### DEPRECATED **The `editor.change` method is deprecated.** With the removal of the `Change` object, there's no need anymore to create the small closures with `editor.change()`. Instead you can directly invoke commands on the editor in series, and all of the changes will be emitted asynchronously on the next tick. ```js editor .insertText('word') .moveFocusForward(10) .addMark('bold') ``` **The `applyOperations` method is deprecated.** Instead you can loop a set of operations and apply each one using `applyOperation`. This is to reduce the number of methods exposed on the `Editor` to keep it simpler. **The `change.call` method is deprecated.** Previously this was used to call a one-off function as a change method. Now this behavior is equivalent to calling `editor.command(fn)` instead. --- Fixes: https://github.com/ianstormtaylor/slate/issues/2334 Fixes: https://github.com/ianstormtaylor/slate/issues/2282
5.6 KiB
Previous:
Applying Custom Formatting
Using Plugins
Up until now, everything we've learned has been about how to write one-off logic for your specific Slate editor. But one of the most beautiful things about Slate is actually its plugin system and how it lets you write less one-off code.
In the previous guide, we actually wrote some pretty useful code for adding bold formatting to ranges of text when a key is pressed. But most of that code wasn't really specific to bold text; it could just as easily have applied to italic text or code
text if we switched a few variables.
So let's break that logic out into a reusable plugin that can toggle any mark on any key press.
Starting with our app from earlier:
class App extends React.Component {
state = {
value: initialValue,
}
onChange = ({ value }) => {
this.setState({ value })
}
onKeyDown = (event, editor, next) => {
if (event.key != 'b' || !event.ctrlKey) return next()
event.preventDefault()
editor.toggleMark('bold')
}
render() {
return (
<Editor
value={this.state.value}
onChange={this.onChange}
onKeyDown={this.onKeyDown}
renderMark={this.renderMark}
/>
)
}
renderMark = (props, editor, next) => {
switch (props.mark.type) {
case 'bold':
return <strong {...props.attributes}>{props.children}</strong>
default:
return next()
}
}
}
Let's write a new function that takes a set of options: the mark type
to toggle and the key
to press.
function MarkHotkey(options) {
// Grab our options from the ones passed in.
const { type, key } = options
}
Okay, that was easy. But it doesn't do anything.
To fix that, we need our plugin function to return a "plugin object" that Slate recognizes. Slate's plugin objects are just plain JavaScript objects whose properties map to the same handlers on the Editor
.
In this case, our plugin object will have one property, an onKeyDown
handler, with its logic copied right from our current app's code:
function MarkHotkey(options) {
const { type, key } = options
// Return our "plugin" object, containing the `onKeyDown` handler.
return {
onKeyDown(event, editor, next) {
// If it doesn't match our `key`, let other plugins handle it.
if (!event.ctrlKey || event.key != key) return next()
// Prevent the default characters from being inserted.
event.preventDefault()
// Toggle the mark `type`.
editor.toggleMark(type)
},
}
}
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:
// Initialize our bold-mark-adding plugin.
const boldPlugin = MarkHotkey({
type: 'bold',
key: 'b',
})
// Create an array of plugins.
const plugins = [boldPlugin]
class App extends React.Component {
state = {
value: initialValue,
}
onChange = ({ value }) => {
this.setState({ value })
}
render() {
return (
// Add the `plugins` property to the editor, and remove `onKeyDown`.
<Editor
plugins={plugins}
value={this.state.value}
onChange={this.onChange}
renderMark={this.renderMark}
/>
)
}
renderMark = (props, editor, next) => {
switch (props.mark.type) {
case 'bold':
return <strong>{props.children}</strong>
default:
return next()
}
}
}
Awesome. If you test out the editor now, you'll notice that everything still works just as it did before. But the beauty of the logic being encapsulated in a plugin is that we can add more mark types extremely easily now!
Let's add italic, code
, strikethrough and underline marks:
// Initialize a plugin for each mark...
const plugins = [
MarkHotkey({ key: 'b', type: 'bold' }),
MarkHotkey({ key: '`', type: 'code' }),
MarkHotkey({ key: 'i', type: 'italic' }),
MarkHotkey({ key: '~', type: 'strikethrough' }),
MarkHotkey({ key: 'u', type: 'underline' }),
]
class App extends React.Component {
state = {
value: initialValue,
}
onChange = ({ value }) => {
this.setState({ value })
}
render() {
return (
<Editor
plugins={plugins}
value={this.state.value}
onChange={this.onChange}
renderMark={this.renderMark}
/>
)
}
renderMark = (props, editor, next) => {
switch (props.mark.type) {
case 'bold':
return <strong>{props.children}</strong>
// Add our new mark renderers...
case 'code':
return <code>{props.children}</code>
case 'italic':
return <em>{props.children}</em>
case 'strikethrough':
return <del>{props.children}</del>
case 'underline':
return <u>{props.children}</u>
default:
return next()
}
}
}
And there you have it! We just added a ton of functionality to the editor with very little work. And we can keep all of our mark hotkey logic tested and isolated in a single place, making the code easier to maintain.
That's why plugins are awesome. They let you get really expressive while also making your codebase easier to manage. And since Slate is built with plugins as a primary consideration, using them is dead simple!
Next:
Saving to a Database