diff --git a/docs/Introduction.md b/docs/Introduction.md index 3a8fc2621..cc8077a27 100644 --- a/docs/Introduction.md +++ b/docs/Introduction.md @@ -80,11 +80,7 @@ Slate encourages you to write small, reusable modules. Check out the public ones * [`slate-auto-replace`](https://github.com/ianstormtaylor/slate-auto-replace) auto-replaces text as the user types. Useful for "smart" typography! * [`slate-collapse-on-escape`](https://github.com/ianstormtaylor/slate-collapse-on-escape) simply collapses the selection when `escape` is pressed. -* [`slate-edit-code`](https://github.com/GitbookIO/slate-edit-code) adds code editing behavior like tab-to-indent, and enter-to-soft-break. -* [`slate-edit-list`](https://github.com/GitbookIO/slate-edit-list) adds rich, nested list editing behavior. -* [`slate-edit-table`](https://github.com/GitbookIO/slate-edit-table) adds complex table editing behavior! * [`slate-paste-linkify`](https://github.com/ianstormtaylor/slate-paste-linkify) wraps the selected text in a link when a URL is pasted from the clipboard. -* [`slate-prism`](https://github.com/GitbookIO/slate-prism) highlights code blocks with [Prism.js](http://prismjs.com/)! * [`slate-soft-break`](https://github.com/ianstormtaylor/slate-soft-break) adds a soft break when `enter` is pressed. * [`slate-drop-or-paste-images`](https://github.com/ianstormtaylor/slate-drop-or-paste-images) lets users drop or paste images to insert them! * [**View all plugins...**](https://github.com/ianstormtaylor/slate/blob/master/docs/general/plugins.md) diff --git a/docs/Readme.md b/docs/Readme.md index 57ec93a42..81f41b995 100644 --- a/docs/Readme.md +++ b/docs/Readme.md @@ -12,7 +12,7 @@ ## Guides -* [Changes](./guides/changes.md) +* [Commands & Queries](./guides/commands-and-queries.md) * [Data Model](./guides/data-model.md) * [Plugins](./guides/plugins.md) * [Rendering](./guides/rendering.md) @@ -34,33 +34,25 @@ * [Data](./reference/slate/data.md) * [Decoration](./reference/slate/decoration.md) * [Document](./reference/slate/document.md) +* [Editor](./reference/slate/editor.md) * [Inline](./reference/slate/inline.md) * [Mark](./reference//slate/mark.md) * [Node](./reference/slate/node.md) * [Operation](./reference/slate/operation.md) +* [Plugins](./reference/slate/plugins.md) * [Point](./reference/slate/point.md) * [Range](./reference/slate/range.md) -* [Schema](./reference/slate/schema.md) * [Selection](./reference/slate/selection.md) * [Text](./reference/slate/text.md) +* [Utils](./reference/slate/utils.md) * [Value](./reference/slate/value.md) -* [setKeyGenerator](./reference/slate/utils.md) -* [resetKeyGenerator](./reference/slate/utils.md) ## Slate React * [Editor](./reference/slate-react/editor.md) * [Plugins](./reference/slate-react/plugins.md) -* [Custom Nodes](./reference/slate-react/custom-nodes.md) -* [Core Plugins](./reference/slate-react/core-plugins.md) -* [cloneFragment](./reference/slate-react/utils.md) -* [findDOMNode](./reference/slate-react/utils.md) -* [findDOMRange](./reference/slate-react/utils.md) -* [findNode](./reference/slate-react/utils.md) -* [findRange](./reference/slate-react/utils.md) -* [getEventRange](./reference/slate-react/utils.md) -* [getEventTransfer](./reference/slate-react/utils.md) -* [setEventTransfer](./reference/slate-react/utils.md) +* [Rendering](./reference/slate-react/rendering.md) +* [Utils](./reference/slate-react/utils.md) ## Other Packages @@ -68,4 +60,3 @@ * [`slate-hyperscript`](./reference/slate-hyperscript/index.md) * [`slate-plain-serializer`](./reference/slate-plain-serializer/index.md) * [`slate-prop-types`](./reference/slate-prop-types/index.md) -* [`slate-simulator`](./reference/slate-simulator/index.md) diff --git a/docs/general/plugins.md b/docs/general/plugins.md index b1c308d86..155ebec62 100644 --- a/docs/general/plugins.md +++ b/docs/general/plugins.md @@ -11,11 +11,6 @@ Plugins that add specific behaviors to your editor. | [`slate-auto-replace`](https://yarnpkg.com/en/package/slate-auto-replace) | Automatically transform certain input as a user types. | ![](https://img.shields.io/npm/dm/slate-auto-replace.svg?maxAge=3600&label=⬇) | | [`slate-collapse-on-escape`](https://yarnpkg.com/en/package/slate-collapse-on-escape) | Collapse the selection when users hit esc. | ![](https://img.shields.io/npm/dm/slate-collapse-on-escape.svg?maxAge=3600&label=⬇) | | [`slate-drop-or-paste-images`](https://yarnpkg.com/en/package/slate-drop-or-paste-images) | Allows users to insert images by drag-dropping or copy-pasting. | ![](https://img.shields.io/npm/dm/slate-drop-or-paste-images.svg?maxAge=3600&label=⬇) | -| [`slate-edit-blockquote`](https://yarnpkg.com/en/package/slate-edit-blockquote) | Adds blockquote editing behaviors to an editor. | ![](https://img.shields.io/npm/dm/slate-edit-blockquote.svg?maxAge=3600&label=⬇) | -| [`slate-edit-code`](https://yarnpkg.com/en/package/slate-edit-code) | Adds code block editing behaviors to an editor. | ![](https://img.shields.io/npm/dm/slate-edit-code.svg?maxAge=3600&label=⬇) | -| [`slate-edit-footnote`](https://yarnpkg.com/en/package/slate-edit-footnote) | Adds footnote editing behaviors to an editor. | ![](https://img.shields.io/npm/dm/slate-edit-footnote.svg?maxAge=3600&label=⬇) | -| [`slate-edit-list`](https://yarnpkg.com/en/package/slate-edit-list) | Adds list editing behaviors to an editor. | ![](https://img.shields.io/npm/dm/slate-edit-list.svg?maxAge=3600&label=⬇) | -| [`slate-edit-table`](https://yarnpkg.com/en/package/slate-edit-table) | Adds common table editing behaviors to an editor. | ![](https://img.shields.io/npm/dm/slate-edit-table.svg?maxAge=3600&label=⬇) | | [`slate-mark-hotkeys`](https://yarnpkg.com/en/package/slate-mark-hotkeys) | Adds common hotkey formatting utils to an editor. | ![](https://img.shields.io/npm/dm/slate-mark-hotkeys.svg?maxAge=3600&label=⬇) | | [`slate-no-empty`](https://yarnpkg.com/en/package/slate-no-empty) | Prevents documents from being empty. | ![](https://img.shields.io/npm/dm/slate-no-empty.svg?maxAge=3600&label=⬇) | | [`slate-paste-linkify`](https://yarnpkg.com/en/package/slate-paste-linkify) | Automatically linkify URLs when they are pasted. | ![](https://img.shields.io/npm/dm/slate-paste-linkify.svg?maxAge=3600&label=⬇) | diff --git a/docs/general/resources.md b/docs/general/resources.md index ad863368b..cfaa83ba3 100644 --- a/docs/general/resources.md +++ b/docs/general/resources.md @@ -17,9 +17,10 @@ These tools are helpful when developing with Slate: ## Products -These products are built with Slate, and can give you an idea of what's possible: +These products use Slate, and can give you an idea of what's possible: * [Cake](https://www.cake.co/) +* [Chatterbug](https://chatterbug.com) * [GitBook](https://www.gitbook.com/) * [Grafana](https://grafana.com/) * [Guru](https://www.getguru.com/) @@ -36,4 +37,6 @@ These pre-packaged editors are built on top of Slate, and can be helpful to see * [Nossas Editor](http://slate-editor.bonde.org/) is a drop-in WYSIWYG editor. * [ORY Editor](https://editor.ory.am/) is a self-contained, inline WYSIWYG editor library. * [Outline Editor](https://github.com/outline/rich-markdown-editor) is the editor that powers the [Outline](https://www.getoutline.com/) wiki. -* [Chatterslate](https://github.com/chatterbugapp/chatterslate) helps teach language grammar and more at [Chatterbug](https://chatterbug.com) +* [Chatterslate](https://github.com/chatterbugapp/chatterslate) helps teach language grammar and more at [Chatterbug](https://chatterbug.com). + +(Or, if you have their exact use case, can be a drop-in editor for you.) diff --git a/docs/guides/changes.md b/docs/guides/changes.md deleted file mode 100644 index dbff70f7b..000000000 --- a/docs/guides/changes.md +++ /dev/null @@ -1,197 +0,0 @@ -# Changes - -All changes to a Slate editor's value, whether it's the `selection`, `document`, `history`, etc. happen via "changes"—specifically, via the [`Change`](../reference/slate/change.md) model. - -This is important because the `Change` model is responsible for ensuring that every change to a Slate value can be expressed in terms of low-level [operations](../reference/slate/operation.md). But you don't have to worry about that, because it happens automatically. - -You just need to understand changes... - -## Expressiveness is Key - -Changes in Slate are designed to prioritize expressiveness above almost all else. - -If you're building a powerful editor, it's going to be somewhat complex, and you're going to be writing code to perform all different kinds of programmatic changes. You'll be removing nodes, inserting fragments, moving the selection around, etc. - -And if the API for changes was verbose, or if it required lots of in between steps to be continually performed, your code would balloon to be impossible to understand very quickly. - -To solve this, Slate has very expressive, chainable changes. Like this: - -```js -change - .focus() - .moveToRangeOfDocument() - .delete() - .insertText('A bit of rich text, followed by...') - .moveTo(10) - .moveFocusForward(4) - .addMark('bold') - .moveToEndOfBlock() - .insertBlock({ - type: 'image', - data: { - src: 'http://placekitten.com/200/300', - alt: 'Kittens', - className: 'img-responsive', - }, - }) - .insertBlock('paragraph') -``` - -Hopefully from reading that you can discern that those changes result in... the entire document's content being selected and deleted, some text being written, a word being bolded, and finally an image block and a paragraph block being added. - -Of course you're not usually going to chain that much. - -Point is, you can get pretty expressive in just a few lines of code. - -That way, when you're scanning to see what behaviors are being triggered, you can understand your code easily. You don't have to sit there and try to parse out a bunch of interim variables to figure out what you're trying to achieve. - -To that end, Slate defines _lots_ of change methods. - -The change methods are the one place in Slate where overlap and near-duplication isn't stomped out. Because sometimes the exact-right change method is the difference between one line of code and ten. And not just ten once, but ten repeated everywhere throughout your codebase. - -## Change Categories - -There are a handful of different categories of changes that ship with Slate by default, and understanding them may help you understand which methods to reach for when trying to write your editor's logic... - -### At a Specific Range - -These are changes like `deleteAtRange()`, `addMarkAtArange()`, `unwrapBlockAtRange()`, etc. that take in a [`Range`](../reference/slate/range.md) argument and apply a change to the document for all of the content in that range. These aren't used that often, because you'll usually be able to get away with using the next category of changes instead... - -### At the Current Selection - -These are changes like `delete()`, `addMark()`, `insertBlock()`, etc. that are the same as the `*AtRange` equivalents, but don't need to take in a range argument, because they apply their edits based on where the user's current selection is. These are often what you want to use when programmatically editing "like a user". - -### On the Selection - -These are changes like `blur()`, `moveToStart()`, `moveToRangeOfNode()`, etc. that change the `value.selection` model and update the user's cursor without affecting the content of the document. - -### On a Specific Node - -There are two types of changes referring to specific nodes, either by `path` or by `key`. These are often what you use when making programmatic changes from inside your custom node components, where you already have a reference to `props.node.key`. - -Path-based changes are ones like `removeNodeByPath()`, `insertNodeByPath()`, etc. that take a `path` pinpointing the node in the document. And key-based changes are ones like `removeNodeByKey()`, `setNodeByKey()`, `removeMarkByKey()`, etc. that take a `key` string referring to a specific node, and then change that node in different ways. - -### On the Top-level Value - -These are changes like `setData()`, `setDecorations()`, etc. that act on the other top-level properties of the [`Value`](../reference/slate/value.md) object. These are more advanced. - -### On the History - -These are changes like `undo()`, `redo()`, etc. that use the operation history and redo or undo changes that have already happened. You generally don't need to worry about these, because they're already bound to the keyboard shortcuts you'd expect, and the user can use them. - -## Making Changes - -When you decide you want to make a change to the Slate value, you're almost always in one of four places... - -### 1. In Slate Handlers - -The first place, is inside a Slate-controlled event handler, like `onKeyDown` or `onPaste`. These handlers take a signature of `event, change, editor`. That `change` argument is a [`Change`](../reference/slate/change.md) object that you can manipulate. For example... - -```js -function onKeyDown(event, change, editor) { - if (event.key == 'Enter') { - change.splitBlock() - } -} -``` - -Any change methods you call will be applied, and when the event handler stack is finished resolving, the editor will automatically update with those changes. - -### 2. From Custom Node Components - -The second place is inside a custom node component. For example, you might have an `` component and you want to make a change when the image is clicked. - -In that case, you'll need to use the `change()` method on the Slate [``](../reference/slate-react/editor.md) which you have available as `props.editor`. For example... - -```js -class Image extends React.Component { - onClick = event => { - this.props.editor.change(change => { - change.removeNodeByKey(this.props.node.key) - }) - } - - render() { - return - } -} -``` - -The `editor.change()` method will create a new [`Change`](../reference/slate/change.md) object for you, based on the editor's current value. You can then call any change methods you want, and the new value will be applied to the editor. - -### 3. From Schema Rules - -The third place you may perform change operations—for more complex use cases—is from inside a custom normalization rule in your editor's [`Schema`](../references/slate/schema.md). For example... - -```js -{ - blocks: { - list: { - nodes: [{ - match: { type: 'item' } - }], - normalize: (change, error) => { - if (error.code == 'child_type_invalid') { - change.wrapBlockByKey(error.child.key, 'item') - } - } - } - } -} -``` - -When a rule's validation fails, Slate passes a [`Change`](../reference/slate/change.md) object to the `normalize` function of the rule, if one exists. You can use this object to apply the changes necessary to make your document valid on the next normalization pass. - -### 4. From Outside Slate - -This is the fourth place you might want to make changes, and also the most dangerous. You should know that any changes you make outside of the Slate editor might not be seen by your plugins (eg. if they register an `onChange` handler) and may not work with collaborative editing implements. - -That said, if that's okay with you, you can make changes manually by using the `change()` method on a Slate [`Value`](../reference/slate/value.md). For example: - -```js -const change = value - .change() - .moveToRangeOfDocument() - .delete() - -const newValue = change.value -``` - -Note that you'll need to then grab the new value by accessing the `change.value` property directly. - -## Reusing Changes - -In addition to using the built-in changes, if your editor is of any complexity you'll want to write your own reusable changes. That way, you can reuse a single `insertImage` change instead of constantly writing `insertBlock(...args)`. - -To do that, you should define change functions just like Slate's core does—as functions that take `(change, ...args)` arguments. Where `change` is the current mutable change object, and `...args` is anything else you want to accept to perform your change. - -For example, here are two simple block inserting changes... - -```js -function insertParagraph(change) { - change.insertBlock('paragraph') -} - -function insertImage(change, src) { - change.insertBlock({ - type: 'image', - data: { src }, - }) -} -``` - -Notice how rewriting that image inserting logic multiple times without having it encapsulated in a single function would get tedious. Now with those change functions defined, you can reuse them! - -But sadly you can't chain with those functions directly, since `change` objects don't actually know about them. Instead, you use the `.call()` method: - -```js -change.call(insertParagraph).call(insertImage, 'https://google.com/logo') -``` - -Not only can you use them with `.call()`, but if you're making one-off changes to the `editor`, you can use them with `editor.change()` as well. For example: - -```js -editor.change(insertImage, 'https://google.com/logo') -``` - -That's the benefit of standardizing a function signature! diff --git a/docs/guides/commands-and-queries.md b/docs/guides/commands-and-queries.md new file mode 100644 index 000000000..e2332ec94 --- /dev/null +++ b/docs/guides/commands-and-queries.md @@ -0,0 +1,223 @@ +# Commands & Queries + +All commands to a Slate editor's value, whether it's the `selection`, `document`, `history`, etc. happen via "commands" that are applied to a [`Change`]([operations](../reference/slate/change.md). + +Under the covers, Slate takes care of converting each command into a set of low-level [operations](../reference/slate/operation.md) that are applied to produce a new value. This is what makes collaborative editing implementations possible. But you don't have to worry about that, because it happens automatically. + +You just need to understand commands... + +## Expressiveness is Key + +Commands in Slate are designed to prioritize expressiveness above almost all else. + +If you're building a powerful editor, it's going to be somewhat complex, and you're going to be writing code to perform all different kinds of programmatic commands. You'll be removing nodes, inserting fragments, moving the selection around, etc. + +And if the API for commands was verbose, or if it required lots of in between steps to be continually performed, your code would balloon to be impossible to understand very quickly. + +To solve this, Slate has very expressive, chainable commands. Like this: + +```js +change + .focus() + .moveToRangeOfDocument() + .delete() + .insertText('A bit of rich text, followed by...') + .moveTo(10) + .moveFocusForward(4) + .addMark('bold') + .moveToEndOfBlock() + .insertBlock({ + type: 'image', + data: { + src: 'http://placekitten.com/200/300', + alt: 'Kittens', + className: 'img-responsive', + }, + }) + .insertBlock('paragraph') +``` + +Hopefully from reading that you can discern that those commands result in... the entire document's content being selected and deleted, some text being written, a word being bolded, and finally an image block and a paragraph block being added. + +Of course you're not usually going to chain that much. + +Point is, you can get pretty expressive in just a few lines of code. + +That way, when you're scanning to see what behaviors are being triggered, you can understand your code easily. You don't have to sit there and try to parse out a bunch of interim variables to figure out what you're trying to achieve. + +To that end, Slate defines _lots_ of commands. + +The commands are the one place in Slate where overlap and near-duplication isn't stomped out. Because sometimes the exact-right command is the difference between one line of code and ten. And not just ten once, but ten repeated everywhere throughout your codebase. + +## Command Categories + +There are a handful of different categories of commands that ship with Slate by default, and understanding them may help you understand which methods to reach for when trying to write your editor's logic... + +### At a Specific Range + +These are commands like `deleteAtRange()`, `addMarkAtArange()`, `unwrapBlockAtRange()`, etc. that take in a [`Range`](../reference/slate/range.md) argument and apply a change to the document for all of the content in that range. These aren't used that often, because you'll usually be able to get away with using the next category of commands instead... + +### At the Current Selection + +These are commands like `delete()`, `addMark()`, `insertBlock()`, etc. that are the same as the `*AtRange` equivalents, but don't need to take in a range argument, because they apply their edits based on where the user's current selection is. These are often what you want to use when programmatically editing "like a user". + +### On the Selection + +These are commands like `blur()`, `moveToStart()`, `moveToRangeOfNode()`, etc. that change the `value.selection` model and update the user's cursor without affecting the content of the document. + +### On a Specific Node + +There are two types of commands referring to specific nodes, either by `path` or by `key`. These are often what you use when making programmatic commands from inside your custom node components, where you already have a reference to `props.node.key`. + +Path-based commands are ones like `removeNodeByPath()`, `insertNodeByPath()`, etc. that take a `path` pinpointing the node in the document. And key-based commands are ones like `removeNodeByKey()`, `setNodeByKey()`, `removeMarkByKey()`, etc. that take a `key` string referring to a specific node, and then change that node in different ways. + +### On the Top-level Value + +These are commands like `setData()`, `setDecorations()`, etc. that act on the other top-level properties of the [`Value`](../reference/slate/value.md) object. These are more advanced. + +### On the History + +These are commands like `undo()`, `redo()`, etc. that use the operation history and redo or undo commands that have already happened. You generally don't need to worry about these, because they're already bound to the keyboard shortcuts you'd expect, and the user can use them. + +## Running Commands + +When you decide you want to make a change to the Slate value, you're almost always in one of four places... + +### 1. In Slate Handlers + +The first place, is inside a Slate-controlled event handler, like `onKeyDown` or `onPaste`. These handlers take a signature of `event, change, next`. That `change` argument is a [`Change`](../reference/slate/change.md) object that you can manipulate. For example... + +```js +function onKeyDown(event, change, editor) { + if (event.key == 'Enter') { + change.splitBlock() + } +} +``` + +Any commands you call will be applied, and when the event handler stack is finished resolving, the editor will automatically update with those commands. + +### 2. From Custom Node Components + +The second place is inside a custom node component. For example, you might have an `` component and you want to make a change when the image is clicked. + +In that case, you'll need to use the `change()` method on the Slate [``](../reference/slate-react/editor.md) which you have available as `props.editor`. For example... + +```js +class Image extends React.Component { + onClick = event => { + this.props.editor.change(change => { + change.removeNodeByKey(this.props.node.key) + }) + } + + render() { + return + } +} +``` + +The `editor.change()` method will create a new [`Change`](../reference/slate/change.md) object for you, based on the editor's current plugins and value. You can then call any commands you want, and the new value will be applied to the editor. + +### 3. From Schema Rules + +The third place you may perform change operations—for more complex use cases—is from inside a custom normalization rule in your editor's [`Schema`](../references/slate/schema.md). For example... + +```js +{ + blocks: { + list: { + nodes: [{ + match: { type: 'item' } + }], + normalize: (change, error) => { + if (error.code == 'child_type_invalid') { + change.wrapBlockByKey(error.child.key, 'item') + } + } + } + } +} +``` + +When a rule's validation fails, Slate passes a [`Change`](../reference/slate/change.md) object to the `normalize` function of the rule, if one exists. You can use this object to apply the commands necessary to make your document valid on the next normalization pass. + +### 4. From Outside Slate + +The last place is from outside of Slate. Sometimes you'll have components that live next to your +editor in the render tree, and you'll need to explicitly pass them a reference to the Slate `editor` to run changes. In React you do this with the `ref={}` prop... + +```js + +``` + +Which gives you a reference to the Slate editor. And from there you can use the same `editor.change` syntax from above to apply changes. + +## Running Queries + +Queries are similar to commands, but instead of manipulating the current value of the editor, they return information about the current value, or a specific node, etc. + +By default, Slate only defines two queries: `isAtomic` for marks and decorations, and `isVoid` for nodes. You can access them directly on the change object: + +```js +const isVoid = change.isVoid(node) +``` + +But you can also define your own queries that are specific to your schema. For example, you might use a query to determine whether the "bold" mark is active... + +```js +const isBold = change.isBoldActive(value) +``` + +And then use that information to mark the bold button in your editor's toolbar as active or not. + +## Reusing Commands and Queries + +In addition to using the built-in commands, if your editor is of any complexity you'll want to write your own reusable commands. That way, you can reuse a single `insertImage` change instead of constantly writing `insertBlock(...args)`. + +To do that, you can define commands in your own Slate plugin, which will be made available as methods on the `change` object specific to your editor. For example, here are two simple block inserting commands... + +```js +const yourPlugin = { + commands: { + insertParagraph(change) { + change.insertBlock('paragraph') + }, + + insertImage(change, src) { + change.insertBlock({ + type: 'image', + data: { src }, + }) + }, + }, +} +``` + +Notice how rewriting that image inserting logic multiple times without having it encapsulated in a single function would get tedious. Now with those change functions defined, you can reuse them! + +```js +change.insertImage('https://google.com/logo.png') +``` + +And any arguments you pass in are sent to your custom command functions. + +The same thing goes for queries, which can be defined in plugins and re-used across your entire codebase. To do so, define a `queries` object: + +```js +const yourPlugin = { + queries: { + getActiveListItem(value) { + ... + } + } +} +``` + +And then you can use them: + +```js +change.getActiveListItem(change.value) +``` + +This reusability is key to being able to organize your commands and queries, and compose them together to create more advanced behaviors. diff --git a/docs/guides/data-model.md b/docs/guides/data-model.md index b23e7e3a1..e63d7edb5 100644 --- a/docs/guides/data-model.md +++ b/docs/guides/data-model.md @@ -14,7 +14,7 @@ Because it mirrors the DOM, Slate's data model features a [`Document`](../refere Slate's data model is built out of [`Immutable.js`](https://facebook.github.io/immutable-js/) objects. This allows us to make rendering much more performant, and it ensures that we don't end up with hard to track down bugs due to accidentally modifying objects in-place. -Specifically, Slate's models are [`Immutable.Record`](https://facebook.github.io/immutable-js/docs/#/Record) objects, which makes them very similar to Javascript objects for retrieiving values: +Specifically, Slate's models are [`Immutable.Record`](https://facebook.github.io/immutable-js/docs/#/Record) objects, which makes them very similar to JavaScript objects for retrieiving values: ```js const block = Block.create({ type: 'paragraph' }) @@ -33,7 +33,7 @@ If you haven't used Immutable.js before, there is definitely a learning curve. B The top-level object in Slate—the object encapsulating the entire value of an Slate editor—is called a [`Value`](../reference/slate/value.md). -It is made up of a document filled with content, and a selection representing the user's current cursor selection. It also has a history, to keep track of changes, and a few other more advanced properties like `decorations` and `data`. +It is made up of a document filled with content, and a selection representing the user's current cursor selection. It also has a few other more advanced properties like `decorations` and `data`. > 📋 For more info, check out the [`Value` reference](../reference/slate/value.md). @@ -53,7 +53,7 @@ Unlike the DOM though, Slate enforces a few more restrictions on its documents. * **Blocks and inlines must always contain at least one text node.** This is to ensure that the user's cursor can always "enter" the nodes and to make sure that ranges can be created referencing them. -Slate enforces all of these restrictions for you automatically. Any time you [perform changes](./changes.md) to the document, Slate will check if the document is invalid, and if so, it will return it to a "normalized" value. +Slate enforces all of these restrictions for you automatically. Any time you [run commands](./commands-and-queries.md) that manipulate the document, Slate will check if the document is invalid, and if so, it will return it to a "normalized" value. > 🙃 Fun fact: "normalizing" is actually based on the DOM's [`Node.normalize()`](https://developer.mozilla.org/en-US/docs/Web/API/Node/normalize)! diff --git a/docs/guides/plugins.md b/docs/guides/plugins.md index 36396f94b..60cdb5947 100644 --- a/docs/guides/plugins.md +++ b/docs/guides/plugins.md @@ -2,38 +2,46 @@ With Slate, _all_ of your editor's logic is controlled by "plugins". -Plugins have complete control over the schema, the behaviors, and the rendering of the editor—they can add any kind of functionality they want. So much so that even the core logic of Slate is provided via two "core" plugins. +Plugins have complete control over the schema, the behaviors, and the rendering of the editor—they can add any kind of functionality they want. So much so that even the core logic of Slate is defined by its own plugins. Slate encourages you to break up code into small, reusable modules that can be shared with others, and easily reasoned about. ## What Are Plugins? -Slate's plugins are simply a collection of functions that all contribute to a shared behavior—each with a specific name and set of arguments. For a full list of the arguments, check out the [`Plugins` reference](../reference/slate-react/plugins.md). +Slate's plugins are plain JavaScript objects containing a collection of functions that all contribute to a shared behavior—each with a specific name and set of arguments. For a full list of the arguments, check out the [Plugins](../reference/slate/plugins.md) and [React Plugins](../reference/slate-react/plugins.md) references. -Here's a really simple plugin: +When building a plugin module, it should always export a function that takes options. This way even if it doesn't take any options now, it won't be a breaking API change to take more options in the future. + +So a basic plugin might look like this: ```js -{ - onKeyDown(event, change, editor) { - if (event.key == 'Escape') { - change.blur() - } - }, - onClick(event, change, editor) { - if (change.value.selection.isBlurred) { - change.moveToRangeOfDocument().focus() - } - } +export default function MySlatePlugin(options) { + return { + onKeyDown(event, change, next) { + if (event.key == options.key) { + change.blur() + return true + } + }, + onClick(event, change, next) { + if (change.value.selection.isBlurred) { + change.moveToRangeOfDocument().focus() + return true + } + }, + } } ``` -It focuses the editor and selects everything when it is clicked, and it blurs the editor when esc is pressed. +It focuses the editor and selects everything when it is clicked, and it blurs the editor when `options.key` is pressed. -Notice how it's able to define a set of behaviors that work together to form a single "feature" in the editor. That's what makes Slate's plugins a powerful form of encapsulation. +Notice how it's able to define a set of behaviors, reacting to different events, that work together to form a single "feature" in the editor. That's what makes Slate's plugins a powerful form of encapsulation. + +Also notice how it returns `true`. This is a convention that allows other plugins in the stack to know that an event has been "handled" and that they can abort without duplicating logic. ## The Plugins "Stack" -Slate's editor takes a list of plugins as one of its arguments. We refer to this list as the plugins "stack". It is very similar to "middleware" from Express or Koa. +Slate's editor takes a list of plugins as one of its arguments. We refer to this list as the plugins "stack". It is very similar to "middleware" from Express or Koa, except instead of just a single stack of handler functions, there are multiple stacks for each type of request. ```js const plugins = [ @@ -58,9 +66,7 @@ The core plugins doesn't have any assumptions about your schema, or what types o These are behaviors that all rich-text editors exhibit, and that don't make sense for userland to have to re-invent for every new editor. -There are two core plugins: the "before plugin" and the "after plugin". They get their names because one of them is before all other plugins in the stack, and the other is after them. - -For the most part you don't need to worry about the core plugins. The before plugin helps to pave over editing inconsistencies, and the after plugin serves as a fallback, to implement the default behavior in the event that your own plugins choose not to handle a specific event. +For the most part you don't need to worry about the core plugins. _To learn more, check out the [Core Plugin reference](../reference/slate-react/core-plugins.md)._ @@ -80,9 +86,9 @@ const plugins = [ /> ``` -This is nice because it makes simple cases easier, and nicely mimics the native DOM API of `` and `