mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-08-12 02:03:59 +02:00
merge
This commit is contained in:
@@ -2,10 +2,9 @@
|
||||
|
||||
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
|
||||
```typescript
|
||||
interface Text {
|
||||
text: string
|
||||
[key: string]: unknown
|
||||
}
|
||||
```
|
||||
|
||||
@@ -19,18 +18,17 @@ This interface-based approach separates Slate from most other richtext editors w
|
||||
|
||||
To take another example, the `Element` node interface in Slate is:
|
||||
|
||||
```ts
|
||||
```typescript
|
||||
interface Element {
|
||||
children: Node[]
|
||||
[key: string]: unknown
|
||||
}
|
||||
```
|
||||
|
||||
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:
|
||||
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
|
||||
```javascript
|
||||
const paragraph = {
|
||||
type: 'paragraph',
|
||||
children: [...],
|
||||
@@ -57,7 +55,7 @@ In addition to the typing information, each interface in Slate also exposes a se
|
||||
|
||||
For example, when working with nodes:
|
||||
|
||||
```js
|
||||
```javascript
|
||||
import { Node } from 'slate'
|
||||
|
||||
// Get the string content of an element node.
|
||||
@@ -69,7 +67,7 @@ const descendant = Node.get(value, path)
|
||||
|
||||
Or, when working with ranges:
|
||||
|
||||
```js
|
||||
```javascript
|
||||
import { Range } from 'slate'
|
||||
|
||||
// Get the start and end points of a range in order.
|
||||
@@ -89,7 +87,7 @@ In addition to the built-in helper functions, you might want to define your own
|
||||
|
||||
For example, if your editor supports images, you might want a helper that determines if an element is an image element:
|
||||
|
||||
```js
|
||||
```javascript
|
||||
const isImageElement = element => {
|
||||
return element.type === 'image' && typeof element.url === 'string'
|
||||
}
|
||||
@@ -97,7 +95,7 @@ const isImageElement = element => {
|
||||
|
||||
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
|
||||
```javascript
|
||||
import { Element } from 'slate'
|
||||
|
||||
// You can use `MyElement` everywhere to have access to your extensions.
|
||||
|
@@ -1,4 +1,4 @@
|
||||
# Nodes: Editor, Elements and Texts
|
||||
# Nodes
|
||||
|
||||
The most important types are the `Node` objects:
|
||||
|
||||
@@ -8,7 +8,7 @@ The most important types are the `Node` objects:
|
||||
|
||||
These three interfaces are combined together to form a tree—just like the DOM. For example, here's a simple plaintext value:
|
||||
|
||||
```js
|
||||
```javascript
|
||||
const editor = {
|
||||
children: [
|
||||
{
|
||||
@@ -39,7 +39,7 @@ A Slate document is a nested and recursive structure. In a document, elements ca
|
||||
|
||||
The top-level node in a Slate document is the `Editor` itself. It encapsulates all of the richtext "content" of the document. Its interface is:
|
||||
|
||||
```ts
|
||||
```typescript
|
||||
interface Editor {
|
||||
children: Node[]
|
||||
...
|
||||
@@ -52,16 +52,15 @@ We'll cover its functionality later, but the important part as far as nodes are
|
||||
|
||||
Elements make up the middle layers of a richtext document. They are the nodes that are custom to your own domain. Their interface is:
|
||||
|
||||
```ts
|
||||
```typescript
|
||||
interface Element {
|
||||
children: Node[]
|
||||
[key: string]: unknown
|
||||
}
|
||||
```
|
||||
|
||||
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
|
||||
```javascript
|
||||
const paragraph = {
|
||||
type: 'paragraph',
|
||||
children: [...],
|
||||
@@ -75,7 +74,7 @@ const quote = {
|
||||
|
||||
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
|
||||
```javascript
|
||||
const link = {
|
||||
type: 'link',
|
||||
url: 'https://example.com',
|
||||
@@ -85,7 +84,7 @@ const link = {
|
||||
|
||||
Or maybe you want to give all of your nodes ID an property:
|
||||
|
||||
```js
|
||||
```javascript
|
||||
const paragraph = {
|
||||
id: 1,
|
||||
type: 'paragraph',
|
||||
@@ -105,7 +104,7 @@ But in certain cases, like for links, you might want to make them "inline" flowi
|
||||
|
||||
> 🤖 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`.) Note that inline nodes cannot be the first or last child of a parent block, nor can it be next to another inline node in the children array. Slate will automatically space these with `{ text: '' }` children by default with [`normalizeNode`](https://docs.slatejs.org/concepts/10-normalizing#built-in-constraints).
|
||||
You can define which nodes are treated as inline nodes by overriding the `editor.isInline` function. \(By default it always returns `false`.\) Note that inline nodes cannot be the first or last child of a parent block, nor can it be next to another inline node in the children array. Slate will automatically space these with `{ text: '' }` children by default with [`normalizeNode`](https://docs.slatejs.org/concepts/10-normalizing#built-in-constraints).
|
||||
|
||||
Elements can either contain block elements or inline elements intermingled with text nodes as children. But elements **cannot** contain some children that are blocks and some that are inlines.
|
||||
|
||||
@@ -117,22 +116,21 @@ Elements default to being non-void, meaning that their children are fully editab
|
||||
|
||||
> 🤖 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 define which elements are treated as void by overriding the `editor.isVoid` function. (By default it always returns `false`.)
|
||||
You can 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
|
||||
```typescript
|
||||
interface Text {
|
||||
text: string
|
||||
[key: string]: unknown
|
||||
}
|
||||
```
|
||||
|
||||
For example, a string of bold text:
|
||||
|
||||
```js
|
||||
```javascript
|
||||
const text = {
|
||||
text: 'A string of bold text',
|
||||
bold: true,
|
||||
|
@@ -1,4 +1,4 @@
|
||||
# Locations: Paths, Points, Ranges and Selections
|
||||
# Locations
|
||||
|
||||
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.
|
||||
|
||||
@@ -6,13 +6,13 @@ Locations are how you refer to specific places in the document when inserting, d
|
||||
|
||||
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 ancestor nodes down the tree:
|
||||
|
||||
```ts
|
||||
```typescript
|
||||
type Path = number[]
|
||||
```
|
||||
|
||||
For example, in this document:
|
||||
|
||||
```js
|
||||
```javascript
|
||||
const editor = {
|
||||
children: [
|
||||
{
|
||||
@@ -33,17 +33,16 @@ The leaf text node would have a path of: `[0, 0]`.
|
||||
|
||||
Points are slightly more specific than paths, and contain an `offset` into a specific text node. Their interface is:
|
||||
|
||||
```ts
|
||||
```typescript
|
||||
interface Point {
|
||||
path: Path
|
||||
offset: number
|
||||
[key: string]: unknown
|
||||
}
|
||||
```
|
||||
|
||||
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
|
||||
```javascript
|
||||
const start = {
|
||||
path: [0, 0],
|
||||
offset: 0,
|
||||
@@ -52,26 +51,25 @@ const start = {
|
||||
|
||||
Or, if you wanted to refer to the end of the sentence:
|
||||
|
||||
```js
|
||||
```javascript
|
||||
const end = {
|
||||
path: [0, 0],
|
||||
offset: 15,
|
||||
}
|
||||
```
|
||||
|
||||
It can be helpful to think of points as being "cursors" (or "carets") of a selection.
|
||||
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:
|
||||
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
|
||||
```typescript
|
||||
interface Range {
|
||||
anchor: Point
|
||||
focus: Point
|
||||
[key: string]: unknown
|
||||
}
|
||||
```
|
||||
|
||||
@@ -81,12 +79,11 @@ An anchor and focus are established by a user interaction. The anchor point isn'
|
||||
|
||||
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)
|
||||
> 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).
|
||||
> 🤖 For more info, check out the [`Range` reference](https://github.com/ianstormtaylor/slate/tree/d82ffe49a5253de08adab8f36ac7f07879037977/docs/reference/slate/range.md).
|
||||
|
||||
## Selection
|
||||
|
||||
@@ -94,7 +91,7 @@ Ranges are used in many places in Slate's API when you need to refer to a span o
|
||||
|
||||
The selection is a special range that is a property of the top-level `Editor`. For example, say someone has the whole sentence currently selected:
|
||||
|
||||
```js
|
||||
```javascript
|
||||
const editor = {
|
||||
selection: {
|
||||
anchor: { path: [0, 0], offset: 0 },
|
||||
|
@@ -6,7 +6,7 @@ Commands are the high-level actions that represent a specific intent of the user
|
||||
|
||||
For example, here are some of the built-in commands:
|
||||
|
||||
```js
|
||||
```javascript
|
||||
Editor.insertText(editor, 'A new string of text to be inserted.')
|
||||
|
||||
Editor.deleteBackward(editor, { unit: 'word' })
|
||||
@@ -14,11 +14,11 @@ Editor.deleteBackward(editor, { unit: 'word' })
|
||||
Editor.insertBreak(editor)
|
||||
```
|
||||
|
||||
But you can (and will!) also define your own custom commands that model your domain. For example, you might want to define a `formatQuote` command, or an `insertImage` command, or a `toggleBold` command depending on what types of content you allow.
|
||||
But you can \(and will!\) also define your own custom commands that model your domain. For example, you might want to define a `formatQuote` command, or an `insertImage` command, or a `toggleBold` command depending on what types of content you allow.
|
||||
|
||||
Commands always describe an action to be taken as if the **user** 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 is 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 too opinionated and inconsistent.
|
||||
> 🤖 The concept of commands is 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 too opinionated and inconsistent.
|
||||
|
||||
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.
|
||||
|
||||
@@ -26,7 +26,7 @@ Under the covers, Slate takes care of converting each command into a set of low-
|
||||
|
||||
When defining custom commands, you can create your own namespace:
|
||||
|
||||
```js
|
||||
```javascript
|
||||
const MyEditor = {
|
||||
...Editor,
|
||||
|
||||
@@ -42,7 +42,7 @@ When writing your own commands, you'll often make use of the `Transforms` helper
|
||||
|
||||
Transforms are a specific set of helpers that allow you to perform a wide variety of specific changes to the document, for example:
|
||||
|
||||
```js
|
||||
```javascript
|
||||
// Set a "bold" format on all of the text nodes in a range.
|
||||
Transforms.setNodes(
|
||||
editor,
|
||||
|
@@ -4,7 +4,7 @@ Operations are the granular, low-level actions that occur while invoking command
|
||||
|
||||
Unlike commands, operations aren't extendable. Slate's core defines all of the possible operations that can occur on a richtext document. For example:
|
||||
|
||||
```js
|
||||
```javascript
|
||||
editor.apply({
|
||||
type: 'insert_text',
|
||||
path: [0, 0],
|
||||
|
@@ -2,14 +2,12 @@
|
||||
|
||||
All of the behaviors, content and state of a Slate editor is rolled up into a single, top-level `Editor` object. It has an interface of:
|
||||
|
||||
```ts
|
||||
```typescript
|
||||
interface Editor {
|
||||
children: Node[]
|
||||
selection: Range | null
|
||||
operations: Operation[]
|
||||
marks: Record<string, any> | null
|
||||
[key: string]: unknown
|
||||
|
||||
// Schema-specific node behaviors.
|
||||
isInline: (element: Element) => boolean
|
||||
isVoid: (element: Element) => boolean
|
||||
@@ -36,7 +34,7 @@ The `children` property contains the document tree of nodes that make up the edi
|
||||
|
||||
The `selection` property contains the user's current selection, if any.
|
||||
|
||||
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.)
|
||||
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.\)
|
||||
|
||||
The `marks` property stores formatting to be applied when the editor inserts text. If `marks` is `null`, the formatting will be taken from the current selection.
|
||||
|
||||
@@ -46,7 +44,7 @@ In previous guides we've already hinted at this, but you can override any of the
|
||||
|
||||
For example, if you want to define link elements that are inline nodes:
|
||||
|
||||
```js
|
||||
```javascript
|
||||
const { isInline } = editor
|
||||
|
||||
editor.isInline = element => {
|
||||
@@ -56,7 +54,7 @@ editor.isInline = element => {
|
||||
|
||||
Or maybe you want to override the `insertText` behavior to "linkify" URLs:
|
||||
|
||||
```js
|
||||
```javascript
|
||||
const { insertText } = editor
|
||||
|
||||
editor.insertText = text => {
|
||||
@@ -71,7 +69,7 @@ editor.insertText = text => {
|
||||
|
||||
Or you can even define custom "normalizations" that take place to ensure that links obey certain constraints:
|
||||
|
||||
```js
|
||||
```javascript
|
||||
const { normalizeNode } = editor
|
||||
|
||||
editor.normalizeNode = entry => {
|
||||
@@ -86,13 +84,13 @@ editor.normalizeNode = entry => {
|
||||
}
|
||||
```
|
||||
|
||||
Whenever you override behaviors, be sure to call 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).
|
||||
Whenever you override behaviors, be sure to call 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
|
||||
```javascript
|
||||
// Get the start point of a specific node at path.
|
||||
const point = Editor.start(editor, [0, 0])
|
||||
|
||||
@@ -102,7 +100,7 @@ const fragment = Editor.fragment(editor, range)
|
||||
|
||||
There are also many iterator-based helpers, for example:
|
||||
|
||||
```js
|
||||
```javascript
|
||||
// Iterate over every node in a range.
|
||||
for (const [node, path] of Editor.nodes(editor, { at: range })) {
|
||||
// ...
|
||||
|
@@ -6,7 +6,7 @@ A plugin is simply a function that takes an `Editor` object and returns it after
|
||||
|
||||
For example, a plugin that marks image nodes as "void":
|
||||
|
||||
```js
|
||||
```javascript
|
||||
const withImages = editor => {
|
||||
const { isVoid } = editor
|
||||
|
||||
@@ -20,7 +20,7 @@ const withImages = editor => {
|
||||
|
||||
And then to use the plugin, simply:
|
||||
|
||||
```js
|
||||
```javascript
|
||||
import { createEditor } from 'slate'
|
||||
|
||||
const editor = withImages(createEditor())
|
||||
@@ -32,7 +32,7 @@ This plugin composition model makes Slate extremely easy to extend!
|
||||
|
||||
In addition to the plugin functions, you might want to expose helper functions that are used alongside your plugins. For example:
|
||||
|
||||
```js
|
||||
```javascript
|
||||
import { Editor, Element } from 'slate'
|
||||
|
||||
const MyEditor = {
|
||||
|
@@ -41,7 +41,7 @@ const MyEditor = () => {
|
||||
|
||||
You don't have to use simple HTML elements, you can use your own custom React components too:
|
||||
|
||||
```js
|
||||
```javascript
|
||||
const renderElement = useCallback(props => {
|
||||
switch (props.element.type) {
|
||||
case 'quote':
|
||||
@@ -76,17 +76,17 @@ const renderLeaf = useCallback(({ attributes, children, leaf }) => {
|
||||
}, [])
|
||||
```
|
||||
|
||||
Notice though how we've handled it slightly differently than `renderElement`. Since text formatting tends to be fairly simple, we've opted to ditch the `switch` statement and just toggle on/off a few styles instead. (But there's nothing preventing you from using custom components if you'd like!)
|
||||
Notice though how we've handled it slightly differently than `renderElement`. Since text formatting tends to be fairly simple, we've opted to ditch the `switch` statement and just toggle on/off a few styles instead. \(But there's nothing preventing you from using custom components if you'd like!\)
|
||||
|
||||
One disadvantage of text-level formatting is that you cannot guarantee that any given format is "contiguous"—meaning that it stays as a single leaf. This limitation with respect to leaves is similar to the DOM, where this is invalid:
|
||||
|
||||
```html
|
||||
```markup
|
||||
<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
|
||||
```markup
|
||||
<em>t</em><strong><em>e</em>x</strong>t
|
||||
```
|
||||
|
||||
@@ -101,7 +101,7 @@ Of course, this leaf stuff sounds pretty complex. But, you do not have to think
|
||||
|
||||
Decorations are another type of text-level formatting. They are similar to regular old custom properties, except each one applies to a `Range` of the document instead of being associated with a given text node.
|
||||
|
||||
However, decorations 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.
|
||||
However, decorations 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.
|
||||
|
||||
Decorations are different from Marks in that they are not stored on editor state.
|
||||
|
||||
|
@@ -8,7 +8,7 @@ And, because Slate uses plain JSON for its data, you can write serialization log
|
||||
|
||||
For example, taking the value of an editor and returning plaintext:
|
||||
|
||||
```js
|
||||
```javascript
|
||||
import { Node } from 'slate'
|
||||
|
||||
const serialize = nodes => {
|
||||
@@ -20,7 +20,7 @@ Here we're taking the children nodes of an `Editor` as a `nodes` argument, and r
|
||||
|
||||
For an input of:
|
||||
|
||||
```js
|
||||
```javascript
|
||||
const nodes = [
|
||||
{
|
||||
type: 'paragraph',
|
||||
@@ -39,7 +39,7 @@ const nodes = [
|
||||
|
||||
You'd end up with:
|
||||
|
||||
```txt
|
||||
```text
|
||||
An opening paragraph...
|
||||
A wise quote.
|
||||
A closing paragraph!
|
||||
@@ -51,7 +51,7 @@ Notice how the quote block isn't distinguishable in any way, that's because we'r
|
||||
|
||||
For example, here's a similar `serialize` function for HTML:
|
||||
|
||||
```js
|
||||
```javascript
|
||||
import escapeHtml from 'escape-html'
|
||||
import { Text } from 'slate'
|
||||
|
||||
@@ -83,7 +83,7 @@ This one is a bit more aware than the plaintext serializer above. It's actually
|
||||
|
||||
It also takes a single node as input instead of an array, so if you passed in an editor like:
|
||||
|
||||
```js
|
||||
```javascript
|
||||
const editor = {
|
||||
children: [
|
||||
{
|
||||
@@ -111,9 +111,9 @@ const editor = {
|
||||
}
|
||||
```
|
||||
|
||||
You'd receive back (line breaks added for legibility):
|
||||
You'd receive back \(line breaks added for legibility\):
|
||||
|
||||
```html
|
||||
```markup
|
||||
<p>An opening paragraph with a <a href="https://example.com">link</a> in it.</p>
|
||||
<blockquote><p>A wise quote.</p></blockquote>
|
||||
<p>A closing paragraph!</p>
|
||||
@@ -140,9 +140,9 @@ const input = (
|
||||
)
|
||||
```
|
||||
|
||||
And the JSX feature of your compiler (Babel, TypeScript, etc.) would turn that `input` variable into:
|
||||
And the JSX feature of your compiler \(Babel, TypeScript, etc.\) would turn that `input` variable into:
|
||||
|
||||
```js
|
||||
```javascript
|
||||
const input = [
|
||||
{
|
||||
type: 'paragraph',
|
||||
@@ -159,7 +159,7 @@ But `slate-hyperscript` isn't only for JSX. It's just a way to build _trees of S
|
||||
|
||||
For example, here's a `deserialize` function for HTML:
|
||||
|
||||
```js
|
||||
```javascript
|
||||
import { jsx } from 'slate-hyperscript'
|
||||
|
||||
const deserialize = el => {
|
||||
@@ -198,7 +198,7 @@ const deserialize = el => {
|
||||
|
||||
It takes in an `el` HTML element object and returns a Slate fragment. So if you have an HTML string, you can parse and deserialize it like so:
|
||||
|
||||
```js
|
||||
```javascript
|
||||
const html = '...'
|
||||
const document = new DOMParser().parseFromString(html, 'text/html')
|
||||
deserialize(document.body)
|
||||
@@ -206,7 +206,7 @@ deserialize(document.body)
|
||||
|
||||
With this input:
|
||||
|
||||
```html
|
||||
```markup
|
||||
<p>An opening paragraph with a <a href="https://example.com">link</a> in it.</p>
|
||||
<blockquote><p>A wise quote.</p></blockquote>
|
||||
<p>A closing paragraph!</p>
|
||||
@@ -214,7 +214,7 @@ With this input:
|
||||
|
||||
You'd end up with this output:
|
||||
|
||||
```js
|
||||
```javascript
|
||||
const fragment = [
|
||||
{
|
||||
type: 'paragraph',
|
||||
|
@@ -8,14 +8,10 @@ Slate editors can edit complex, nested data structures. And for the most part th
|
||||
|
||||
Slate editors come with a few built-in constraints out of the box. These constraints are there to make working with content _much_ more predictable than standard `contenteditable`. All of the built-in logic in Slate depends on these constraints, so unfortunately you cannot omit them. They are...
|
||||
|
||||
1. **All `Element` nodes must contain at least one `Text` descendant.** If an element node does not contain any children, an empty text node will be added as its only child. This constraint exists to ensure that the selection's anchor and focus points (which rely on referencing text nodes) can always be placed inside any node. With this, empty elements (or void elements) wouldn't be selectable.
|
||||
|
||||
1. **All `Element` nodes must contain at least one `Text` descendant.** If an element node does not contain any children, an empty text node will be added as its only child. This constraint exists to ensure that the selection's anchor and focus points \(which rely on referencing text nodes\) can always be placed inside any node. With this, empty elements \(or void elements\) wouldn't be selectable.
|
||||
2. **Two adjacent texts with the same custom properties will be merged.** If two adjacent text nodes have the same formatting, they're merged into a single text node with a combined text string of the two. This exists to prevent the text nodes from only ever expanding in count in the document, since both adding and removing formatting results in splitting text nodes.
|
||||
|
||||
3. **Block nodes can only contain other blocks, or inline and text nodes.** For example, a `paragraph` block cannot have another `paragraph` block element _and_ a `link` inline element as children at the same time. The type of children allowed is determined by the first child, and any other non-conforming children are removed. This ensures that common richtext behaviors like "splitting a block in two" function consistently.
|
||||
|
||||
4. **Inline nodes cannot be the first or last child of a parent block, nor can it be next to another inline node in the children array.** If this is the case, an empty text node will be added to correct this to be in complience with the constraint.
|
||||
|
||||
5. **The top-level editor node can only contain block nodes.** If any of the top-level children are inline or text nodes they will be removed. This ensures that there are always block nodes in the editor so that behaviors like "splitting a block in two" work as expected.
|
||||
|
||||
These default constraints are all mandated because they make working with Slate documents _much_ more predictable.
|
||||
@@ -26,11 +22,11 @@ These default constraints are all mandated because they make working with Slate
|
||||
|
||||
The built-in constraints are fairly generic. But you can also add your own constraints on top of the built-in ones that are specific to your domain.
|
||||
|
||||
To do this, you extend the `normalizeNode` function on the editor. The `normalizeNode` function gets called every time an operation is applied that inserts or updates a node (or its descendants), giving you the opportunity to ensure that the changes didn't leave it in an invalid state, and correcting the node if so.
|
||||
To do this, you extend the `normalizeNode` function on the editor. The `normalizeNode` function gets called every time an operation is applied that inserts or updates a node \(or its descendants\), giving you the opportunity to ensure that the changes didn't leave it in an invalid state, and correcting the node if so.
|
||||
|
||||
For example here's a plugin that ensures `paragraph` blocks only have text or inline elements as children:
|
||||
|
||||
```js
|
||||
```javascript
|
||||
import { Transforms, Element, Node } from 'slate'
|
||||
|
||||
const withParagraphs = editor => {
|
||||
@@ -67,7 +63,7 @@ One thing to understand about `normalizeNode` constraints is that they are **mul
|
||||
|
||||
If you check the example above again, you'll notice the `return` statement:
|
||||
|
||||
```js
|
||||
```javascript
|
||||
if (Element.isElement(child) && !editor.isInline(child)) {
|
||||
Transforms.unwrapNodes(editor, { at: childPath })
|
||||
return
|
||||
@@ -96,7 +92,7 @@ To see how this works in practice, let's start with this invalid document:
|
||||
|
||||
The editor starts by running `normalizeNode` on `<paragraph c>`. And it is valid, because it contains only text nodes as children.
|
||||
|
||||
But then, it moves up the tree, and runs `normalizeNode` on `<paragraph b>`. This paragraph is invalid, since it contains a block element (`<paragraph c>`). So that child block gets unwrapped, resulting in a new document of:
|
||||
But then, it moves up the tree, and runs `normalizeNode` on `<paragraph b>`. This paragraph is invalid, since it contains a block element \(`<paragraph c>`\). So that child block gets unwrapped, resulting in a new document of:
|
||||
|
||||
```jsx
|
||||
<editor>
|
||||
@@ -124,7 +120,7 @@ The one pitfall to avoid however is creating an infinite normalization loop. Thi
|
||||
|
||||
For example, consider a normalization that ensured `link` elements have a valid `url` property:
|
||||
|
||||
```js
|
||||
```javascript
|
||||
// WARNING: this is an example of incorrect behavior!
|
||||
const withLinks = editor => {
|
||||
const { normalizeNode } = editor
|
||||
|
@@ -1,25 +1,15 @@
|
||||
# TypeScript
|
||||
|
||||
Slate supports typing of one Slate document model (ie. one set of custom `Editor`, `Element` and `Text` types). If you need to support more than one document model, see the section Multiple Document Models.
|
||||
Slate supports typing of one Slate document model \(ie. one set of custom `Editor`, `Element` and `Text` types\). If you need to support more than one document model, see the section Multiple Document Models.
|
||||
|
||||
**Warning:** You must define `CustomTypes` when using TypeScript or Slate will display typing errors.
|
||||
|
||||
> Migrating from 0.47.x?
|
||||
>
|
||||
> Please read the guide below first to understand CustomTypes.
|
||||
>
|
||||
> If you are having issues, here are common migration gotchas:
|
||||
>
|
||||
> - You get typing errors when referring to `node.type` (Property `type` does not exist on type `Node`). To fix this, you need something like `Element.isElement(node) && node.type === 'paragraph'`. This is because a `Node` can be an `Element` or `Text` and `Text` does not have a `type` property.
|
||||
>
|
||||
> - You may have defined CustomType for `Editor` incorrectly. Make sure to define the CustomType for `Editor` as `BaseEditor & ...`. It should not be `Editor & ...`
|
||||
|
||||
## Defining `Editor`, `Element` and `Text` Types
|
||||
|
||||
To define a custom `Element` or `Text` type, extend the `CustomTypes` interface in the `slate` module like this.
|
||||
|
||||
```ts
|
||||
// This example is for an Editor that uses `ReactEditor` and `HistoryEditor`
|
||||
```typescript
|
||||
// This example is for an Editor with `ReactEditor` and `HistoryEditor`
|
||||
import { BaseEditor } from 'slate'
|
||||
import { ReactEditor } from 'slate-react'
|
||||
import { HistoryEditor } from 'slate-history'
|
||||
@@ -41,7 +31,7 @@ While you can define types directly in the `CustomTypes` interface, best practic
|
||||
|
||||
Using best practices, the custom types might look something like:
|
||||
|
||||
```ts
|
||||
```typescript
|
||||
// This example is for an Editor with `ReactEditor` and `HistoryEditor`
|
||||
import { BaseEditor } from 'slate'
|
||||
import { ReactEditor } from 'slate-react'
|
||||
@@ -81,7 +71,7 @@ In this example, `CustomText` is equal to `FormattedText` but in a real editor,
|
||||
|
||||
Because it gets asked often, this section explains why Slate's type definition is atypical.
|
||||
|
||||
Slate needs to support a feature called type discrimination which is available when using union types (e.g. `ParagraphElement | HeadingElement`). This allows a user to narrow a type. If presented with code like `if (node.type === 'paragraph') { ... }` the inside of the block, will narrow the type of node to `ParagraphElement`.
|
||||
Slate needs to support a feature called type discrimination which is available when using union types \(e.g. `ParagraphElement | HeadingElement`\). This allows a user to narrow a type. If presented with code like `if (node.type === 'paragraph') { ... }` the inside of the block, will narrow the type of node to `ParagraphElement`.
|
||||
|
||||
Slate also needs a way to allow developers to get their custom types into Slate. This is done through declaration merging which is a feature of an `interface`.
|
||||
|
||||
|
@@ -2,7 +2,7 @@
|
||||
|
||||
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/01-installing-slate.md) and the other [Concepts](./01-interfaces.md) to see how all of the new concepts get applied.
|
||||
It's highly recommended that after reading this guide you read through the original [Walkthroughs](../walkthroughs/01-installing-slate.md) and the other [Concepts](01-interfaces.md) to see how all of the new concepts get applied.
|
||||
|
||||
## Major Differences
|
||||
|
||||
@@ -22,7 +22,7 @@ A lot of helper functions are exposed as a collection of helper functions on a n
|
||||
|
||||
### 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!)
|
||||
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
|
||||
|
||||
@@ -32,17 +32,17 @@ The number of commands has been reduced too. Previously we had commands for ever
|
||||
|
||||
### Fewer Packages
|
||||
|
||||
In an 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.
|
||||
In an 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.
|
||||
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.*` core functions. And they travel through a middleware-like stack, but built from composed functions. Any plugin can override the behaviors to augment an editor.
|
||||
|
||||
### Plugins
|
||||
|
||||
Plugins are now plain functions that augment the `Editor` object they receive and return it again. For example, they 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.
|
||||
Plugins are now plain functions that augment the `Editor` object they receive and return it again. For example, they 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
|
||||
|
||||
@@ -58,7 +58,7 @@ Previously the `<Editor>` component was doing double duty as a sort of "controll
|
||||
|
||||
### 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 they use React's Context API they will automatically re-render when their state changes.
|
||||
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 they use React's Context API they will automatically re-render when their state changes.
|
||||
|
||||
### `beforeinput`
|
||||
|
||||
@@ -82,7 +82,7 @@ One of the goals was to dramatically simplify a lot of the logic in Slate to mak
|
||||
|
||||
To give you a sense for the change in total lines of code:
|
||||
|
||||
```
|
||||
```text
|
||||
slate 8,436 -> 3,958 (47%)
|
||||
slate-react 3,905 -> 1,954 (50%)
|
||||
|
Reference in New Issue
Block a user