1
0
mirror of https://github.com/ianstormtaylor/slate.git synced 2025-08-30 02:19:52 +02:00
* remove some key usage from core, refactor Operations.apply

* undeprecate some methods

* convert more key usage to paths

* update deprecations

* convert selection commands to use all paths

* refactor word boundary selection logic

* convert many at-range commands to use paths

* convert wrapBlock and wrapInline to not use keys

* cleanup

* remove chainability from editor

* simplify commands, queries and middleware

* convert deleteAtRange

* remove key usage from schema, deprecate *ByKey methods

* migrate *ByKey tests, remove index from *ByPath signatures

* rename at-current-range tests

* deprecate mode key usage, migrate more tests away from keys

* deprecate range and point methods which rely on keys to work

* refactor insertBlock, without fixing warnings

* add pathRef/pointRef, fix insertBlock/Inline deprecations, work on insertFragment

* refactor insertFragment

* get rich-text example rendering

* fix lint

* refactor query files, fix more tests

* remove unused queries, refactor others

* deprecate splitDescendantsByPath

* merge master

* add typescript, convert slate, slate-hyperscript, slate-plain-serializer

* add Point, Path, Range, Annotation tests

* add Annotation, Change, Element, Fragment, Mark, Range, Selection, Value interfaces tests

* add Operation and Text tests

* add Node tests

* get operations and normalization tests working for slate

* get *AtPath command tests passing

* rename *AtPath command tests

* rename

* get *AtPoint tests working

* rename

* rename

* add value queries tests

* add element, mark and path queries tests

* convert most on-selection tests

* convert on-selection commands

* rename

* get addMarks and delete commands working

* rename

* rename

* rename

* refactor value.positions(), work on delete tests

* progress on delete tests

* more delete work

* finish delete tests

* start converting to at-based commands

* restructure query tests

* restructure operations tests

* more work converting to multi-purpose commands

* lots of progress on converting to at-based commands

* add unwrapNodes

* remove setValue

* more progress

* refactor node commands to use consistent matching logic

* cleanup, get non-fragment commands passing

* remove annotations and isAtomic

* rename surround/pluck to cover/uncover

* add location concept, change at-path to from-path for iterables

* refactor batches

* add location-based queries

* refactor hanging logic

* more location query work

* renaming

* use getMatch more

* add split to wrap/unwrap

* flip levels/ancestors ordering

* switch splitNodes to use levels

* change split to always:false by default

* fix tests

* add more queries tests

* fixing more delete logic

* add more splitNodes tests

* get rest of delete tests passing

* fix location-based logic in some commands

* cleanup

* get previous packages tests passing again

* add slate-history package

* start slate-schema work

* start of react working

* rendering fixes

* get rich and plain text examples working

* get image example working with hooks and dropping

* refactor onDrop to be internal

* inline more event handlers

* refactor lots of event-related logic

* change rendering to use render props

* delete unused stuff

* cleanup dom utils

* remove unused deps

* remove unnecessary packages, add placeholder

* remove slate-react-placeholder package

* remove unused dep

* remove unnecessary tests, fix readonly example

* convert checklists example

* switch to next from webpack

* get link example working

* convert more examples

* preserve keys, memoized leafs/texts, fix node lookup

* fix to always useLayoutEffect for ordering

* fix annotations to be maps, memoize elements

* remove Change interface

* remove String interface

* rename Node.entries to Node.nodes

* remove unnecessary value queries

* default to selection when iterating, cleanup

* remove unused files

* update scroll into view logic

* fix undoing, remove constructor types

* dont sync selection while composing

* add workflows

* remove unused deps

* convert mentions example

* tweaks

* convert remaining examples

* rename h to jsx, update schema

* fix schema tests

* fix slate-schema logic and tests

* really fix slate-schema and forced-layout example

* get start of insertFragment tests working

* remove Fragment interface

* remove debugger

* get all non-skipped tests passing

* cleanup deps

* run prettier

* configure eslint for typescript

* more eslint fixes...

* more passing

* update some docs

* fix examples

* port windows undo hotkey change

* fix deps, add basic firefox support

* add event overriding, update walkthroughs

* add commands, remove classes, cleanup examples

* cleanup rollup config

* update tests

* rename queries tests

* update other tests

* update walkthroughs

* cleanup interface exports

* cleanup, change mark transforms to require location

* undo mark transform change

* more

* fix tests

* fix example

* update walkthroughs

* update docs

* update docs

* remove annotations

* remove value, move selection and children to editor

* add migrating doc

* fix lint

* fix tests

* fix DOM types aliasing

* add next export

* update deps, fix prod build

* fix prod build

* update scripts

* update docs and changelogs

* update workflow and pull request template
This commit is contained in:
Ian Storm Taylor
2019-11-27 20:54:42 -05:00
committed by GitHub
parent 02b87d5968
commit 4ff6972096
2367 changed files with 45706 additions and 80698 deletions

View File

@@ -0,0 +1,113 @@
# Interfaces
Slate works with pure JSON objects. All it requires is that those JSON objects conform to certain interfaces. For example, a text node in Slate must obey the `Text` interface:
```ts
interface Text {
text: string
marks: Mark[]
[key: string]: any
}
```
Which means it must have a `text` property with a string of content, and a `marks` property with an array of formatting marks (or an empty array).
But other custom properties are also allowed, and completely up to you. This lets you tailor your data to your specific domain and use case, without Slate getting in the way.
This interface-based approach separates Slate from most other rich-text editors which require you to work with their hand-rolled "model" classes, and makes it much easier to reason about. It also means that it avoids startup time penalties related to "initializing" the data model.
## Custom Properties
To take another example, the `Element` node interface in Slate is:
```ts
interface Element {
children: Node[]
[key: string]: any
}
```
This is a very permissive interface. All it requires is that the `children` property be defined containing the element's child nodes.
But you can extend elements (or any other interface) with your own custom properties that are specific to your domain. For example, you might have "paragraph" and "link" elements:
```js
const paragraph = {
type: 'paragraph',
children: [...],
}
const link = {
type: 'link',
url: 'https://example.com',
children: [...]
}
```
The `type` and `url` properties there are your own custom API. Slate sees that they exist, but it doesn't ever use them for anything. However, when it goes to render an link element you'll receive an object with the custom properties attached, so that you can render it as:
```jsx
<a href={element.url}>{element.children}</a>
```
When getting started with Slate, it's important to understand all of the interfaces it defines. There are a handful of them that are discussed in each of the guides.
## Helper Functions
In addition to the typing information, each interface in Slate also exposes a series of helper functions that make them easier to work with.
For example, when working with nodes:
```js
import { Node } from 'slate'
// Get the text content of an element node.
const text = Node.text(element)
// Get the node at a specific path inside a root node.
const descendant = Node.get(value, path)
```
Or, when working with ranges:
```js
import { Range } from 'slate'
// Get the start and end points of a range in order.
const [start, end] = Range.edges(range)
// Check if a range is collapsed to a single point.
if (Range.isCollapsed(range)) {
// ...
}
```
There are lots of helper functions available for all of the common use cases when working with the different interfaces. When getting started it pays to read through them all, because you can often simplify complex logic into just a handful of lines of code with them.
## Custom Helpers
In addition to the built-in helper functions, you might want to define your own custom helper functions and expose them on your own custom namespaces.
For example, if your editor supports images, you might want a helper that determines if an element is an image element:
```js
const isImageElement = element => {
return element.type === 'image' && typeof element.url === 'string'
}
```
You can define these as one-off functions easily. But you might also bundle them up into namespaces, just like the core interfaces do, and use them instead. For example:
```js
import { Element } from 'slate'
// You can use `MyElement` everywhere to have access to your extensions.
export const MyElement = {
...Element,
isImageElement,
isParagraphElement,
isQuoteElement,
}
```
This makes it easy to reuse domain-specific logic alongside the built-in Slate helpers.

144
docs/concepts/02-nodes.md Normal file
View File

@@ -0,0 +1,144 @@
# Nodes: Editor, Elements and Texts
The most important type are the `Node` objects:
- A root-level `Editor` node that contains their entire document's content.
- Container `Element` nodes which have semantic meaning in your domain.
- And leaf-level `Text` nodes which contain the document's text.
These three interfaces are combined together to form a tree—just like the DOM. For example, here's a simple plain-text value:
```js
const editor = {
children: [
{
type: 'paragraph',
children: [
{
text: 'A line of text!',
marks: [],
},
],
},
],
// ...the editor has other properties too.
}
```
Mirroring the DOM as much as possible is one of Slate's principles. People use the DOM to represent documents with rich-text-like structures all the time. Mirroring the DOM helps make the library familiar for new users, and it lets us reuse battle-tested patterns without having to reinvent them ourselves.
> 🤖 The following content on Mozilla's Developer Network may help you learn more about the corresponding DOM concepts:
>
> - [Document](https://developer.mozilla.org/en-US/docs/Web/API/Document)
> - [Block Elements](https://developer.mozilla.org/en-US/docs/Web/HTML/Block-level_elements)
> - [Inline elements](https://developer.mozilla.org/en-US/docs/Web/HTML/Inline_elements)
> - [Text elements](https://developer.mozilla.org/en-US/docs/Web/API/Text)
A Slate document is a nested and recursive structure. In a document, elements can have children nodes—all which may have children nodes without limit. The nested and recursive structure enables you to model simple behaviors such as user mentions and hashtags or complex behaviors such as tables and figures with captions.
## `Editor`
The top-level node in a Slate document is the `Editor` itself. It encapsulates all of the rich-text "content" of the document. Its interface is:
```ts
interface Editor {
children: Node[]
...
}
```
We'll cover its functionality later, but the important part as far as nodes are concerned is its `children` property which contains a tree of `Node` objects.
## `Element`
Elements make up the middle layers of a rich-text document. They are the nodes that are custom to your own domain. Their interface is:
```ts
interface Element {
children: Node[]
[key: string]: any
}
```
You can define custom elements for any type of content you want. For example you might have paragraphs and quotes in your data model which are differentiated by a `type` property:
```js
const paragraph = {
type: 'paragraph',
children: [...],
}
const quote = {
type: 'quote',
children: [...],
}
```
It's important to note that you can use _any_ custom properties you want. The `type` property in that example isn't something Slate knows or cares about. If you were defining your own "link" nodes, you might have a `url` property:
```js
const link = {
type: 'link',
url: 'https://example.com',
children: [...],
}
```
Or maybe you want to give all of your nodes ID an property:
```js
const paragraph = {
id: 1,
type: 'paragraph',
children: [...],
}
```
All that matters is that elements always have a `children` property.
## Blocks vs. Inlines
Depending on your use case, you might want to define another behavior for `Element` nodes which determines their editing "flow".
All elements default to being "block" elements. They each appear separated by vertical space, and they never run into each other.
But in certain cases, like for links, you might want to make as "inline" flowing elements instead. That way they live at the same level as text nodes, and flow.
> 🤖 This is a concept borrowed from the DOM's behavior, see [Block Elements](https://developer.mozilla.org/en-US/docs/Web/HTML/Block-level_elements) and [Inline Elements](https://developer.mozilla.org/en-US/docs/Web/HTML/Inline_elements).
You can define which nodes are treated as inline nodes by overriding the `editor.isInline` function. (By default it always returns `false`.)
Elements can either contain block elements as children. Or they can contain inline elements intermingled with text nodes as children. But elements **cannot** contain some children that are blocks and some that are inlines.
## Voids
Similarly to blocks and inlines, there is another element-specific behavior you can define depending on your use case: their "void"-ness.
Elements default to being non-void, meaning that their children are fully editable as text. But in some cases, like for images, you want to ensure that Slate doesn't treat their content as editable text, but instead as a black box.
> 🤖 This is a concept borrowed from the HTML spec, see [Void Elements](https://www.w3.org/TR/2011/WD-html-markup-20110405/syntax.html#void-element).
You can do define which elements are treated as void by overriding the `editor.isVoid` function. (By default it always returns `false`.)
## `Text`
Text nodes are the lowest-level nodes in the tree, containing the text content of the document, along with any formatting. Their interface is:
```ts
interface Text {
text: string
marks: Mark[]
[key: string]: any
}
```
We'll cover `Mark` objects shortly, but for now you can get an idea for them:
```js
const text = {
text: 'A string of bold text',
marks: [{ type: 'bold' }],
}
```
Text nodes too can contain any custom properties you want, although it is rarer to need to do that.

View File

@@ -0,0 +1,120 @@
# Locations: Paths, Points, Ranges and Selections
Locations are how you refer to specific places in the document when inserting, deleting, or doing anything else with a Slate editor. There are a few different kinds of location interfaces, each for different use cases.
## `Path`
Paths are the lowest-level way to refer to a location. Each path is a simple array of numbers that refers to a node in the document tree by its indexes in each of its ancestors nodes down the tree:
```ts
type Path = number[]
```
For example, in this document:
```js
const editor = {
children: [
{
type: 'paragraph',
children: [
{
text: 'A line of text!',
marks: [],
},
],
},
],
}
```
The leaf text node would have a path of: `[0, 0]`.
## `Point`
Points are slightly more specific than paths, and contain an `offset` into a specific text node. Their interface is:
```ts
interface Point {
path: Path
offset: number
[key: string]: any
}
```
For example, with that same document, if you wanted to refer to the very first place you could put your cursor it would be:
```js
const start = {
path: [0, 0],
offset: 0,
}
```
Or, if you wanted to refer to the end of the sentence:
```js
const end = {
path: [0, 0],
offset: 15,
}
```
It can be helpful to think of points as being "cursors" (or "carets") of a selection.
> 🤖 Points _always_ refer to text nodes! Since they are the only ones with strings that can have cursors.
## `Range`
Ranges are a way to refer not just to a single point in the document, but to a wider span of content between two points. (An example of a range is when you make a selection.) Their interface is:
```ts
interface Range {
anchor: Point
focus: Point
[key: string]: any
}
```
> 🤖 The terms "anchor" and "focus" are borrowed from the DOM, see [Anchor](https://developer.mozilla.org/en-US/docs/Web/API/Selection/anchorNode) and [Focus](https://developer.mozilla.org/en-US/docs/Web/API/Selection/focusNode).
An anchor and focus are established by a user interaction. The anchor point isn't always _before_ the focus point in the document. Just like in the DOM, the ordering of an anchor and selection point depend on whether the range is backwards or forwards.
Here's how Mozilla Developer Network explains it:
> A user may make a selection from left to right (in document order) or right to left (reverse of document order). The anchor is where the user began the selection and the focus is where the user ends the selection. If you make a selection with a desktop mouse, the anchor is placed where you pressed the mouse button and the focus is placed where you released the mouse button. Anchor and focus should not be confused with the start and end positions of a selection, since anchor can be placed before the focus or vice versa, depending on the direction you made your selection.
> — [`Selection`, MDN](https://developer.mozilla.org/en-US/docs/Web/API/Selection)
One important distinction is that the anchor and focus points of ranges **always reference the leaf-level text nodes** in a document and never reference elements. This behavior is different than the DOM, but it simplifies working with ranges as there are fewer edge cases for you to handle.
> 🤖 For more info, check out the [`Range` reference](../reference/slate/range.md).
## Selection
Ranges are used in many places in Slate's API when you need to refer to a span of content between two points. One of the most common though is the user's current "selection".
The selection is a special range that is property of the top-level `Editor`. For example, say someone has the whole sentence currently selected:
```js
const editor = {
selection: {
anchor: { path: [0, 0], offset: 0 },
focus: { path: [0, 0], offset: 15 },
},
children: [
{
type: 'paragraph',
children: [
{
text: 'A line of text!',
marks: [],
},
],
},
],
}
```
> 🤖 The selection concept is also borrowed from the DOM, see [`Selection`, MDN](https://developer.mozilla.org/en-US/docs/Web/API/Selection), although in a greatly-simplified form because Slate doesn't allow for multiple ranges inside a single selection, which makes things a lot easier to work with.
There isn't a special `Selection` interface, it's just an object that happens to respect the more general-purpose `Range` interface instead.

View File

@@ -0,0 +1,49 @@
# Formatting: Marks and Decorations
We've already seen how `Element` objects can be extended with custom properties to add semantic meaning to your rich-text documents. But there are other kinds of formatting too.
## `Mark`
Marks are the lowest level of formatting that is applied directly to the text nodes of your document, for things like **bold**, _italic_ and `code`. Their interface is:
```ts
interface Mark {
[key: string]: any
}
```
Which means that they are entirely composed of your domain-specific custom properties. For simple cases, it can suffice to use a `type` string:
```js
const bold = { type: 'bold' }
const italic = { type: 'italic' }
```
There are multiple techniques you might choose to format or style text. You can implement styling based on inlines or marks. Unlike inlines, marks do not affect the structure of the nodes in the document. Marks simply attach themselves to the characters.
Marks may be easier to reason about and manipulate because marks do not affect the structure of the document and are associated to the characters. Marks can be applied to characters no matter how the characters are nested in the document. If you can express it as a `Range`, you can add marks to it. Working with marks instead of inlines does not require you to edit the document's structure, split existing nodes, determine where nodes are in the hierarchy, or other more complex interactions.
When marks are rendered, the characters are grouped into "leaves" of text that each contain the same set of marks applied to them. One disadvantage of marks is that you cannot guarantee how a set of marks will be ordered.
This limitation with respect to the ordering of marks is similar to the DOM, where this is invalid:
```html
<em>t<strong>e</em>x</strong>t
```
Because the elements in the above example do not properly close themselves they are invalid. Instead, you would write the above HTML as follows:
```html
<em>t</em><strong><em>e</em>x</strong>t
```
If you happened to add another overlapping section of `<strike>` to that text, you might have to rearrange the closing tags yet again. Rendering marks in Slate is similar—you can't guarantee that even though a word has one mark applied that that mark will be contiguous, because it depends on how it overlaps with other marks.
Of course, this mark ordering stuff sounds pretty complex. But, you do not have to think about it much, as long as you use marks and inlines for their intended purposes:
- Marks represent **unordered**, character-level formatting.
- Inlines represent **contiguous**, semantic elements in the document.
## Decorations
Decorations are similar to marks, with each one applying to a range of content. However, they are computed at render-time based on the content itself. This is helpful for dynamic formatting like syntax highlighting or search keywords, where changes to the content (or some external data) has the potential to change the formatting.

View File

@@ -0,0 +1,74 @@
# Commands
While editing rich-text content, your users will be doing things like inserting text, deleteing text, splitting paragraphs, adding marks, etc. These edits are expressed using two concepts: commands and operations.
Commands are the high-level actions that represent a specific intent of the user. Their interface is simply:
```ts
interface Command {
type: string
[key: string]: any
}
```
Slate defines and recognizes a handful of core commands out of the box for common rich-text behaviors, like:
```js
editor.exec({
type: 'insert_text',
text: 'A new string of text to be inserted.',
})
editor.exec({
type: 'delete_backward',
unit: 'character',
})
editor.exec({
type: 'add_mark',
mark: { type: 'bold' },
})
```
But you can (and will!) also define your own custom commands that model your domain. For example, you might want to define a `wrap_quote` command, or a `insert_image` command depending on what types of content you allow.
Commands always describe an action to be taken as if the user themselves was performing the action. For that reason, they never need to define a location to perform the command, because they always act on the user's current selection.
> 🤖 The concept of commands are loosely based on the DOM's built-in [`execCommand`](https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand) APIs. However Slate defines its own simpler (and extendable!) version of the API, because the DOM's version is overly complex and has known versatility issues.
Under the covers, Slate takes care of converting each command into a set of low-level "operations" 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.
## Custom Commands
When defining custom commands, you'll always trigger them by calling the `editor.exec` function:
```js
// Call your custom "insert_image" command.
editor.exec({
type: 'insert_image',
url: 'https://unsplash.com/photos/m0By_H6ofeE',
})
```
And then you define what their behaviors are by overriding the default `editor.exec` command:
```js
const withImages = editor => {
const { exec } = editor
editor.exec = command => {
if (command.type === 'insert_image') {
// Define your behaviors here...
} else {
// Call the existing behavior to handle any other commands.
exec(command)
}
}
return editor
}
```
This makes it easy for you to define a handful of commands specific to your problem domain.
But as you can see with the `withImages` function above, custom logic can be extracted into simple plugins that can be reused and shared with others. This is one of the powerful aspects of Slate's architecture.

View File

@@ -0,0 +1,37 @@
# Operations
Operations are the granular, low-level actions that occur while invoking commands. A single high-level command could result in many low-level operations being applied to the editor.
Unlike commands, operations aren't extendable. Slate's core defines all of the possible operations that can occur on a rich-text document. For example:
```js
editor.apply({
type: 'insert_text',
path: [0, 0],
offset: 15,
text: 'A new string of text to be inserted.',
})
editor.apply({
type: 'remove_node',
path: [0, 0],
node: {
text: 'A line of text!',
marks: [],
},
})
editor.apply({
type: 'set_selection',
properties: {
anchor: { path: [0, 0], offset: 0 },
},
newProperties: {
anchor: { path: [0, 0], offset: 15 },
},
})
```
Under the covers Slate converts complex commands into the low-level operations and applies them to the editor automatically, so you rarely have to think about them.
> 🤖 Slate's editing behaviors being defined as operations is what makes things like collaborative editing possible, because each change is easily define-able, apply-able, compose-able and event undo-able!

117
docs/concepts/07-editor.md Normal file
View File

@@ -0,0 +1,117 @@
# Editor
All of the behaviors, content and state of a Slate editor is rollup up into a single, top-level `Editor` object. It has an interface of:
```ts
interface Editor {
apply: (operation: Operation) => void
exec: (command: Command) => void
isInline: (element: Element) => boolean
isVoid: (element: Element) => boolean
normalizeNode: (entry: NodeEntry) => void
onChange: (children: Node[], operations: Operation[]) => void
children: Node[]
operations: Operation[]
selection: Range | null
[key: string]: any
}
```
Slightly more complex than the others, because it contains all of the top-level functions that define your custom, domain-specific behaviors.
The `children` property contains the document tree of nodes that make up the editor's content.
The `selection` property contains the user's current selection, if any.
And the `operations` property contains all of the operations that have been applied since the last "change" was flushed. (Since Slate batches operations up into ticks of the event loop.)
## Overriding Behaviors
In previous guides we've already hinted at this, but you can overriding any of the behaviors of an editor by overriding it's function properties.
For example, if you want define link elements that are inline nodes:
```js
const { isInline } = editor
editor.isInline = element => {
return element.type === 'link' ? true : isInline(element)
}
```
Or maybe you want to define a custom command:
```js
const { exec } = editor
editor.exec = command => {
if (command.type === 'insert_link') {
const { url } = command
// ...
} else {
exec(command)
}
}
```
Or you can even define custom "normalizations" that take place to ensure that links obey certain constraints:
```js
const { normalizeNode } = editor
editor.normalizeNode = entry => {
const [node, path] = entry
if (Element.isElement(node) && node.type === 'link') {
// ...
}
normalizeNode(entry)
}
```
Whenever you override behaviors, be sure to call in to the existing functions as a fallback mechanism for the default behavior. Unless you really do want to completely remove the default behaviors (which is rarely a good idea).
## Helper Functions
The `Editor` interface, like all Slate interfaces, exposes helper functions that are useful when implementing certain behaviors. There are many, many editor-related helpers. For example:
```js
// Get the start point of a specific node at path.
const point = Editor.start(editor, [0, 0])
// Check whether an element matches a set of properties.
const isMatch = Editor.isMatch(editor, element, { type: 'quote' })
// Get the fragment (a slice of the document) at a range.
const fragment = Editor.fragment(editor, range)
```
There are also many iterator-based helpers, for example:
```js
// Iterate over every element in a range.
for (const [element, path] of Editor.elements(editor, { at: range })) {
// ...
}
// Iterate over every mark in every text node in the current selection.
for (const [mark, index, text, path] of Editor.marks(editor)) {
// ...
}
```
Another special group of helper functions exposed on the `Editor` interface are the "transform" helpers. They are the lower-level functions that commands use to define their behaviors. For example:
```js
// Insert an element node at a specific path.
Editor.insertNodes(editor, [element], { at: path })
// Split the nodes in half at a specific point.
Editor.splitNodes(editor, { at: point })
// Add a mark to all the text in a range.
Editor.addMarks(editor, [mark], { at: range })
```
The editor-specific helpers are the ones you'll use most often when working with Slate editors, so it pays to become very familiar with them.

View File

@@ -0,0 +1,72 @@
# Plugins
You've already seen how the behaviors of Slate editors can be overriden. These overrides can also be packaged up into "plugins" to be reused, tested and shared. This is one of the most powerful aspects of Slate's architecture.
A plugin is simply a function that takes an `Editor` object and returns it after it has augmented it in some way.
For example, a plugin that handles images:
```js
const withImages = editor => {
const { exec, isVoid } = editor
editor.exec = command => {
if (command.type === 'insert_image') {
const { url } = command
const text = { text: '', marks: [] }
const element = { type: 'image', url, children: [text] }
Editor.insertNodes(editor)
} else {
exec(command)
}
}
editor.isVoid = element => {
return element.type === 'image' ? true : isVoid(editor)
}
return editor
}
```
And then to use the plugin, simply:
```js
import { createEditor } from 'slate'
const editor = withImages(createEditor())
// Later, when you want to insert an image...
editor.exec({
type: 'insert_image',
url: 'https://unsplash.com/photos/m0By_H6ofeE',
})
```
This plugin composition model makes Slate extremely easy to extend!
## Helpers Functions
In addition to the plugin functions, you might want to expose helper functions that are used alongside your plugins. For example:
```js
const ImageElement = {
isImageElement(value) {
return Element.isElement(element) && element.type === 'image'
},
}
```
That way you can reuse your helpers. Or even mix them with the core Slate helpers to create your own bundle, like:
```js
import { Element } from 'slate'
import { ImageElement } from './images'
export const MyElement = {
...Element,
...ImageElement,
}
```
Then you can use `MySelect` everywhere and have access to all your helpers in one place.

View File

@@ -0,0 +1,102 @@
# Rendering
One of the best parts of Slate is that it's built with React, so it fits right into your existing application. It doesn't re-invent its own view layer that you have to learn. It tries to keep everything as React-y as possible.
To that end, Slate gives you control over the rendering behavior of your custom elements and custom marks in your rich-text domain.
You can define these behaviors by passing "render props" to the top-level `<Editable>` component.
For example if you wanted to render custom element components, you'd pass in the `renderElement` prop:
```jsx
import { createEditor } from 'slate'
import { Slate, Editable, withReact } from 'slate-react'
const MyEditor = () => {
const editor = useMemo(() => withReact(createEditor()), [])
const renderElement = useCallback(({ attributes, children, element }) => {
switch (element.type) {
case 'quote':
return <blockquote {...attributes}>{children}</blockquote>
case 'link':
return (
<a {...attributes} href={element.url}>
{children}
</a>
)
default:
return <p {...attributes}>{children}</p>
}
}, [])
return (
<Slate editor={editor}>
<Editable renderElement={renderElement} />
</Slate>
)
}
```
> 🤖 Be sure to mix in `props.attributes` and render `props.children` in your custom components! The attributes must be added to the top-level DOM element inside the component, as they are required for Slate's DOM helper functions to work. And the children are the actual text content of your document which Slate manages for you automatically.
You don't have to use simple HTML elements, you can use your own custom React components too:
```js
const renderElement = useCallback(props => {
switch (props.element.type) {
case 'quote':
return <QuoteElement {...props} />
case 'link':
return <LinkElement {...props} />
default:
return <DefaultElement {...props} />
}
}, [])
```
The same thing goes for a custom `renderMark` prop:
```jsx
const renderMark = useCallback(({ attributes, children, mark }) => {
switch (mark.type) {
case 'bold':
return <strong {...attributes}>{children}</strong>
case 'italic':
return <em {...attributes}>{children}</em>
}
}, [])
```
> 🤖 Be aware though that marks aren't guaranteed to be "contiguous". Which means even though a **word** is bolded, it's not guaranteed to render as a single `<strong>` element. If some of its characters are also italic, it might be split up into multiple elements—one `<strong>wo</strong>` and one `<em><strong>rd</strong></em>`.
## Toolbars, Menus, Overlays, and more!
In addition to controlling the rendering of elements and marks inside Slate, you can also retrieve the current editor context from inside other components using the `useSlate` hook.
That way other components can execute commands, query the editor state, or anything else.
A common use case for this is rendering a toolbar with formatting buttons that are highlighted based on the current selection:
```jsx
const MyEditor = () => {
const editor = useMemo(() => withReact(createEditor()), [])
return (
<Slate editor={editor}>
<Toolbar />
<Editable />
</Slate>
)
}
const Toolbar = () => {
const editor = useSlate()
return (
<div>
<Button active={isBoldActive(editor)}>B</Button>
<Button active={isItalicActive(editor)}>B</Button>
</div>
)
}
```
Because the `<Toolbar>` uses the `useSlate` hook to retrieve the context, it will will re-render whenever the editor changes, so that the active state of the buttons stays in sync.

View File

@@ -0,0 +1,101 @@
# Migrating
Migrating from earlier versions of Slate to the `0.50.x` versions is not a simple task. The entire framework was re-considered from the ground up. This has resulted in a **much** better set of abstractions, which will result in you writing less code. But the migration process is not simple.
It's highly recommended that after reading this guide you read through the original [Walkthroughs](../walkthroughs) and the other [Guides](./) to see how all of the new concepts get applied.
## Major Differences
Here's an overview of the _major_ differences in the `0.50.x` version of Slate from an architectural point of view.
### JSON!
The data model is now comprised of simple JSON objects. Previously, it used [Immutable.js](https://immutable-js.github.io/immutable-js/) data structures. This is a huge change, and one that unlocks many other things. Hopefully it will also increase the average performance when using Slate. It also makes it much easier to get started for newcomers. This will be a large change to migrate from, but it will be worth it.
### Interfaces
The data model is interface-based. Previously each model was an instance of a class. Now, not only is the data plain objects, but Slate only expects that the objects implement an interface. So custom properties that used to live in `node.data` can now live at the top-level of the nodes.
### Namespaces
A lot of helper functions are exposed as a collection of helper functions on a namespace. For example, `Node.get(root, path)` or `Range.isCollapsed(range)`. This ends up making code much clearer because you can always quickly see what interface you're working with.
### TypeScript
The codebase now uses TypeScript. Working with pure JSON as a data model, and using an interface-based API are two things that have been made easier by migrating to TypeScript. You don't need to use it yourself, but if you do you'll get a lot more security when using the APIs. (And if you use VS Code you'll get nice autocompletion regardless!)
### Fewer Concepts
The number of interfaces and commands has been reduced. Previously `Selection`, `Annotation`, `Decoration` used to all be separate classes. Now they are simply objects that implement the `Range` interface. Previously `Block` and `Inline` were separate, now they are objects that implement the `Element` interface. Previously there was a `Document` and `Value`, but now the top-level `Editor` contains the children nodes of the document itself.
The number of commands has been reduced too. Previously we had commands for every type of input, like `insertText`, `insertTextAtRange`, `insertTextAtPath`. These have been merged into a smaller set of more customizable commands, eg. `insertText` which can take `at: Path | Range | Point`.
### Fewer Packages
In attempt to decrease the maintenance burden, and because the new abstraction and APIs in Slate's core packages make things much easier, the total number of packages has been reduced. Things like `slate-plain-serializer`, `slate-base64-serializer`, etc. have been removed and can be implemented in userland easily if needed. Even the `slate-html-deserializer` can now be implemented in userland (in ~10 LOC leveraging `slate-hyperscript`). And internal packages like `slate-dev-environment`, `slate-dev-test-utils`, etc. are no longer exposed because they are implementation details.
### Commands
A new `Command` concept has been introduced. (The old "commands" are now called "transforms".) This new concept expresses the semantic intent of a user editing the document. And they allow for the right abstraction to tap into user behaviors—for example to change what happens when a user presses enter, or backspace, etc. Instead of using `keydown` events you should likely override command behaviors instead.
Commands are triggered by calling the `editor.exec` function. And they travel through a middleware-like stack, but built from composed functions. Any plugin can override the `exec` behaviors to augment an editor.
### Plugins
Plugins are now plain functions that augment the `Editor` object they receive and return it again. For example can augment the command execution by composing the `editor.exec` function. Or listen to operations by composing `editor.apply`. Previously they relied on a custom middleware stack, and they were just bags of handlers that got merged onto an editor. Now we're using plain old function composition (aka wrapping) instead.
### Elements
Block-ness and inline-ness is now a runtime choice. Previously it was baked into the data model with the `object: 'block'` or `object: 'inline'` attributes. Now, it checks whether an "element" is inline or not at runtime. For example, you might check to see that `element.type === 'link'` and treat it as inline.
### More React-ish
Rendering and event-handling is no longer a plugin's concern. Previously plugins had full control over the rendering logic, and event-handling logic in the editor. This creates a bad incentive to start putting **all** rendering logic in plugins, which puts Slate in a position of being a wrapper around all of React, which is very hard to do well. Instead, the new architecture has plugins focused purely on the rich-text aspects, and leaves the rendering and event handling aspects to React.
### Context
Previously the `<Editor>` component was doing double duty as a sort of "controller" object and also the `contenteditable` DOM element. This led to a lot of awkwardness in how other components worked with Slate. In the new version, there is a new `<Slate>` context provider and a simpler `<Editable>` `contenteditable`-like component. By putting the `<Slate>` provider higher up in your component tree, you can share the editor directly with toolbars, buttons, etc. using the `useSlate` hook.
### Hooks
In addition to the `useSlate` hook, there are a handful of other hooks. For example the `useSelected` and `useFocused` hooks help with knowing when to render selected states (often for void nodes). And since the use React's Content API they will automatically re-render when their state changes.
### `beforeinput`
We now use the `beforeinput` event almost exclusively. Instead of having relying on a series of shims and the quirks of React synthetic events, we're now using the standardized `beforeinput` event as our baseline. It is fully supported in Safari and Chrome, will soon be supported in the new Chromium-based Edge, and is currently being worked on in Firefox. In the meantime there are a few patches to make Firefox work. Internet Explorer is no longer supported in core out of the box.
### History-less
The core history logic has now finally been extracted into a standalone plugin. This makes it much easier for people to implement their own custom history behaviors. And it ensures that plugins have enough control to augment the editor in complex ways, because the history requires it.
### Annotation-less
Similarly, annotations have been removed from Slate's core. They can be fully implemented now in userland by defining custom operations and rendering annotated ranges using decorations. But most cases should be using plain marks or plain decorations anyways. There were not that many use cases that benefitted from annotations.
## Reductions
One of the goals was to dramatically simplify a lot of the logic in Slate to make it easier to maintain and iterate on. This was done by refactoring to better base abstractions that can be built on, by leveraging modern DOM APIs, and by migrating to simpler React patterns.
To give you a sense for the change in total lines of code:
```
slate 8,436 -> 4,038 (48%)
slate-react 3,905 -> 715 (18%)
slate-base64-serializer 38 -> 0
slate-dev-benchmark 340 -> 0
slate-dev-environment 102 -> 0
slate-dev-test-utils 44 -> 0
slate-history 0 -> 201
slate-hotkeys 62 -> 0
slate-html-serializer 253 -> 0
slate-hyperscript 447 -> 410
slate-plain-serializer 56 -> 0
slate-prop-types 62 -> 0
slate-react-placeholder 62 -> 0
slate-schema 0 -> 504
total 13,807 -> 5,868 (43%)
```
It's quite a big difference! And that doesn't even include the dependencies that were shed in the process too.