mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-07-31 04:20:26 +02:00
update docs
This commit is contained in:
@@ -1099,13 +1099,13 @@ This is just an attempt to make dealing with normalization errors slightly more
|
||||
|
||||
###### NEW
|
||||
|
||||
**The `state.activeMarks` returns the intersection of marks in the selection.** Previously there was only `state.marks` which returns marks that appeared on _any_ character in the selection. But `state.activeMarks` returns marks that appear on _every_ character in the selection, which is often more useful for implementing standard rich-text editor behaviors.
|
||||
**The `state.activeMarks` returns the intersection of marks in the selection.** Previously there was only `state.marks` which returns marks that appeared on _any_ character in the selection. But `state.activeMarks` returns marks that appear on _every_ character in the selection, which is often more useful for implementing standard richtext editor behaviors.
|
||||
|
||||
###### BREAKING
|
||||
|
||||
**The `Plain` serializer now adds line breaks between blocks.** Previously between blocks the text would be joined without any space whatsoever, but this wasn't really that useful or what you'd expect.
|
||||
|
||||
**The `toggleMark` transform now checks the intersection of marks.** Previously, toggling would remove the mark from the range if any of the characters in a range didn't have it. However, this wasn't what all other rich-text editors did, so the behavior has changed to mimic the standard behavior. Now, if any characters in the selection have the mark applied, it will first be added when toggling.
|
||||
**The `toggleMark` transform now checks the intersection of marks.** Previously, toggling would remove the mark from the range if any of the characters in a range didn't have it. However, this wasn't what all other richtext editors did, so the behavior has changed to mimic the standard behavior. Now, if any characters in the selection have the mark applied, it will first be added when toggling.
|
||||
|
||||
**The `.length` property of nodes has been removed.** This property caused issues with code like in Lodash that checked for "array-likeness" by simply looking for a `.length` property that was a number.
|
||||
|
||||
|
@@ -103,8 +103,8 @@ Check out the [**live demo**](http://slatejs.org) of all of the examples!
|
||||
|
||||
To get a sense for how you might use Slate, check out a few of the examples:
|
||||
|
||||
- [**Plain text**](https://github.com/ianstormtaylor/slate/tree/master/site/examples/plain-text.js) — showing the most basic case: a glorified `<textarea>`.
|
||||
- [**Rich text**](https://github.com/ianstormtaylor/slate/tree/master/site/examples/rich-text.js) — showing the features you'd expect from a basic editor.
|
||||
- [**Plain text**](https://github.com/ianstormtaylor/slate/tree/master/site/examples/plaintext.js) — showing the most basic case: a glorified `<textarea>`.
|
||||
- [**Rich text**](https://github.com/ianstormtaylor/slate/tree/master/site/examples/richtext.js) — showing the features you'd expect from a basic editor.
|
||||
- [**Markdown preview**](https://github.com/ianstormtaylor/slate/tree/master/site/examples/markdown-preview.js) — showing how to add key handlers for Markdown-like shortcuts.
|
||||
- [**Links**](https://github.com/ianstormtaylor/slate/tree/master/site/examples/links.js) — showing how wrap text in inline nodes with associated data.
|
||||
- [**Images**](https://github.com/ianstormtaylor/slate/tree/master/site/examples/images.js) — showing how to use void (text-less) nodes to add images.
|
||||
|
@@ -60,8 +60,8 @@ Check out the [**live demo**](http://slatejs.org) of all of the examples!
|
||||
|
||||
To get a sense for how you might use Slate, check out a few of the examples:
|
||||
|
||||
- [**Plain text**](https://github.com/ianstormtaylor/slate/tree/master/examples/plain-text) — showing the most basic case: a glorified `<textarea>`.
|
||||
- [**Rich text**](https://github.com/ianstormtaylor/slate/tree/master/examples/rich-text) — showing the features you'd expect from a basic editor.
|
||||
- [**Plain text**](https://github.com/ianstormtaylor/slate/tree/master/examples/plaintext) — showing the most basic case: a glorified `<textarea>`.
|
||||
- [**Rich text**](https://github.com/ianstormtaylor/slate/tree/master/examples/richtext) — showing the features you'd expect from a basic editor.
|
||||
- [**Markdown preview**](https://github.com/ianstormtaylor/slate/tree/master/examples/markdown-preview) — showing how to add key handlers for Markdown-like shortcuts.
|
||||
- [**Links**](https://github.com/ianstormtaylor/slate/tree/master/examples/links) — showing how wrap text in inline nodes with associated data.
|
||||
- [**Images**](https://github.com/ianstormtaylor/slate/tree/master/examples/images) — showing how to use void (text-less) nodes to add images.
|
||||
|
@@ -13,7 +13,7 @@ Which means it must have a `text` property with a string of content.
|
||||
|
||||
But **any** other custom properties are also allowed, and completely up to you. This lets you tailor your data to your specific domain and use case, adding whatever formatting logic you'd like, 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.
|
||||
This interface-based approach separates Slate from most other richtext 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
|
||||
|
||||
|
@@ -6,7 +6,7 @@ The most important type are the `Node` objects:
|
||||
- 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:
|
||||
These three interfaces are combined together to form a tree—just like the DOM. For example, here's a simple plaintext value:
|
||||
|
||||
```js
|
||||
const editor = {
|
||||
@@ -24,7 +24,7 @@ const editor = {
|
||||
}
|
||||
```
|
||||
|
||||
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.
|
||||
Mirroring the DOM as much as possible is one of Slate's principles. People use the DOM to represent documents with richtext-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:
|
||||
>
|
||||
@@ -37,7 +37,7 @@ A Slate document is a nested and recursive structure. In a document, elements ca
|
||||
|
||||
## `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:
|
||||
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
|
||||
interface Editor {
|
||||
@@ -50,7 +50,7 @@ We'll cover its functionality later, but the important part as far as nodes are
|
||||
|
||||
## `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:
|
||||
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
|
||||
interface Element {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# Commands
|
||||
|
||||
While editing rich-text content, your users will be doing things like inserting text, deleteing text, splitting paragraphs, adding formatting, etc. These edits are expressed using two concepts: commands and operations.
|
||||
While editing richtext content, your users will be doing things like inserting text, deleteing text, splitting paragraphs, adding formatting, 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:
|
||||
|
||||
@@ -11,7 +11,7 @@ interface Command {
|
||||
}
|
||||
```
|
||||
|
||||
Slate defines and recognizes a handful of core commands out of the box for common rich-text behaviors, like:
|
||||
Slate defines and recognizes a handful of core commands out of the box for common richtext behaviors, like:
|
||||
|
||||
```js
|
||||
editor.exec({
|
||||
|
@@ -2,7 +2,7 @@
|
||||
|
||||
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:
|
||||
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
|
||||
editor.apply({
|
||||
|
@@ -2,7 +2,7 @@
|
||||
|
||||
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 nodes and properties in your rich-text domain.
|
||||
To that end, Slate gives you control over the rendering behavior of your custom nodes and properties in your richtext domain.
|
||||
|
||||
You can define these behaviors by passing "render props" to the top-level `<Editable>` component.
|
||||
|
||||
|
239
docs/concepts/09-serializing.md
Normal file
239
docs/concepts/09-serializing.md
Normal file
@@ -0,0 +1,239 @@
|
||||
# Serializing
|
||||
|
||||
Slate's data model has been built with serialization in mind. Specifically, its text nodes are defined in a way that makes them easier to read at a glance, but also easy to serialize to common formats like HTML and Markdown.
|
||||
|
||||
And, because Slate uses plain JSON for its data, you can write serialization logic very easily.
|
||||
|
||||
## Plaintext
|
||||
|
||||
For example, taking the value of an editor and returning plaintext:
|
||||
|
||||
```js
|
||||
import { Node } from 'slate'
|
||||
|
||||
const serialize = nodes => {
|
||||
return nodes.map(n => Node.text(n)).join('\n')
|
||||
}
|
||||
```
|
||||
|
||||
Here we're taking the children nodes of an `Editor` as a `nodes` argument, and returning a plaintext representation where each top-level node is separated by a single `\n` new line character.
|
||||
|
||||
For an input of:
|
||||
|
||||
```js
|
||||
const nodes = [
|
||||
{
|
||||
type: 'paragraph',
|
||||
children: [{ text: 'An opening paragraph...' }],
|
||||
},
|
||||
{
|
||||
type: 'quote',
|
||||
children: [{ text: 'A wise quote.' }],
|
||||
},
|
||||
{
|
||||
type: 'paragraph',
|
||||
children: [{ text: 'A closing paragraph!' }],
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
You'd end up with:
|
||||
|
||||
```txt
|
||||
An opening paragraph...
|
||||
A wise quote.
|
||||
A closing paragraph!
|
||||
```
|
||||
|
||||
Notice how the quote block isn't distinguishable in any way, that's because we're talking about plaintext. But you can serialize the data to anything you want—it's just JSON after all.
|
||||
|
||||
## HTML
|
||||
|
||||
For example, here's a similar `serialize` function for HTML:
|
||||
|
||||
```js
|
||||
import escapeHtml from 'escape-html'
|
||||
import { Node, Text } from 'slate'
|
||||
|
||||
const serialize = node => {
|
||||
if (Text.isText(node)) {
|
||||
return escapeHtml(node.text)
|
||||
}
|
||||
|
||||
const children = node.children.map(n => serialize(n)).join('')
|
||||
|
||||
switch (node.type) {
|
||||
case 'quote':
|
||||
return `<blockquote><p>${children}</p></blockquote>`
|
||||
case 'paragraph':
|
||||
return `<p>${children}</p>`
|
||||
case 'link':
|
||||
return `<a href="${escapeHtml(node.url)}">${children}</a>`
|
||||
default:
|
||||
return children
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This one is a bit more aware than the plaintext serializer above. It's actually _recursive_ so that it can keep iterating deeper through a node's children until it gets to the leaf text nodes. And for each node it receives, it converts it to an HTML string.
|
||||
|
||||
It also takes a single node as input instead of an array, so if you passed in an editor like:
|
||||
|
||||
```js
|
||||
const editor = {
|
||||
children: [
|
||||
{
|
||||
type: 'paragraph',
|
||||
children: [
|
||||
{ text: 'An opening paragraph with a ' },
|
||||
{
|
||||
type: 'link',
|
||||
url: 'https://example.com',
|
||||
children: [{ text: 'link' }],
|
||||
},
|
||||
{ text: ' in it.' },
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'quote',
|
||||
children: [{ text: 'A wise quote.' }],
|
||||
},
|
||||
{
|
||||
type: 'paragraph',
|
||||
children: [{ text: 'A closing paragraph!' }],
|
||||
},
|
||||
],
|
||||
// `Editor` objects also have other properties that are omitted here...
|
||||
}
|
||||
```
|
||||
|
||||
You'd receive back (line breaks added for legibility):
|
||||
|
||||
```html
|
||||
<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>
|
||||
```
|
||||
|
||||
It's really that easy!
|
||||
|
||||
## Deserializing
|
||||
|
||||
Another common use case in Slate is doing the reverse—deserializing. This is when you have some arbitrary input and want to convert it into a Slate-compabitable JSON structure. For example, when someone pastes HTML into your editor and you want to ensure it gets parsed with the proper formatting for your editor.
|
||||
|
||||
Slate has a built-in helper for this: the `slate-hyperscript` package.
|
||||
|
||||
The most common way to use `slate-hyperscript` is for writing JSX documents, for example when writing tests. You might use it like so:
|
||||
|
||||
```jsx
|
||||
/** @jsx jsx */
|
||||
import { jsx } from 'slate-hyperscript'
|
||||
|
||||
const input = (
|
||||
<fragment>
|
||||
<element type="paragraph">A line of text.</element>
|
||||
</fragment>
|
||||
)
|
||||
```
|
||||
|
||||
And the JSX feature of your compiler (Babel, TypeScript, etc.) would turn that `input` variable into:
|
||||
|
||||
```js
|
||||
const input = [
|
||||
{
|
||||
type: 'paragraph',
|
||||
children: [{ text: 'A line of text.' }],
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
This is great for test cases, or places where you want to be able to write a lot of Slate objects in a very readable form.
|
||||
|
||||
However! This doesn't help with deserialization.
|
||||
|
||||
But `slate-hyperscript` isn't only for JSX. It's just a way to build _trees of Slate content_. Which happens to be exactly what you want to do when you're deserializing something like HTML.
|
||||
|
||||
For example, here's a `deserialize` function for HTML:
|
||||
|
||||
```js
|
||||
import { jsx } from 'slate-hyperscript'
|
||||
|
||||
const deserialize = el => {
|
||||
if (el.nodeType === 3) {
|
||||
return el.textContent
|
||||
} else if (el.nodeType !== 1) {
|
||||
return null
|
||||
}
|
||||
|
||||
const children = Array.from(el.childNodes).map(deserialize)
|
||||
|
||||
switch (el.nodeName) {
|
||||
case 'BODY':
|
||||
return jsx('fragment', {}, children)
|
||||
case 'BR':
|
||||
return '\n'
|
||||
case 'BLOCKQUOTE':
|
||||
return jsx('element', { type: 'quote' }, children)
|
||||
case 'P':
|
||||
return jsx('element', { type: 'paragraph' }, children)
|
||||
case 'A':
|
||||
return jsx(
|
||||
'element',
|
||||
{ type: 'link', url: el.getAttribute('href') },
|
||||
children
|
||||
)
|
||||
default:
|
||||
return el.textContent
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
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
|
||||
const html = '...'
|
||||
const document = new DOMParser().parseFromString(html, 'text/html')
|
||||
deserialize(document.body)
|
||||
```
|
||||
|
||||
With this input:
|
||||
|
||||
```js
|
||||
<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>
|
||||
```
|
||||
|
||||
You'd end up with this output:
|
||||
|
||||
```js
|
||||
const fragment = [
|
||||
{
|
||||
type: 'paragraph',
|
||||
children: [
|
||||
{ text: 'An opening paragraph with a ' },
|
||||
{
|
||||
type: 'link',
|
||||
url: 'https://example.com',
|
||||
children: [{ text: 'link' }],
|
||||
},
|
||||
{ text: ' in it.' },
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'quote',
|
||||
children: [
|
||||
{
|
||||
type: 'paragraph',
|
||||
children: [{ text: 'A wise quote.' }],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'paragraph',
|
||||
children: [{ text: 'A closing paragraph!' }],
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
And just like the serializing function, you can extend it to fit your exact domain model's needs.
|
@@ -50,7 +50,7 @@ Block-ness and inline-ness is now a runtime choice. Previously it was baked into
|
||||
|
||||
### 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.
|
||||
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 richtext aspects, and leaves the rendering and event handling aspects to React.
|
||||
|
||||
### Context
|
||||
|
||||
|
@@ -2,7 +2,7 @@
|
||||
|
||||
A series of common questions people have about Slate:
|
||||
|
||||
- [Why is content pasted as plain text?](#why-is-content-is-pasted-as-plain-text)
|
||||
- [Why is content pasted as plain text?](#why-is-content-is-pasted-as-plaintext)
|
||||
- [What can a `Block` node have as its children?](#what-can-a-block-node-have-as-its-children)
|
||||
- [What browsers and devices does Slate support?](#what-browsers-and-devices-does-slate-support)
|
||||
|
||||
|
@@ -61,7 +61,7 @@ const App = () => {
|
||||
|
||||
You can think of the `<Slate>` component as provided an "un-controlled" editor context to every component underneath it.
|
||||
|
||||
This is a slightly different mental model than things like `<input>` or `<textarea>`, because rich-text documents are more complex. You'll often want to include toolbars, or live previews, or other complex components next to your editable content.
|
||||
This is a slightly different mental model than things like `<input>` or `<textarea>`, because richtext documents are more complex. You'll often want to include toolbars, or live previews, or other complex components next to your editable content.
|
||||
|
||||
By having a shared context, those other components can execute commands, query the editor's state, etc.
|
||||
|
||||
@@ -79,7 +79,7 @@ const App = () => {
|
||||
}
|
||||
```
|
||||
|
||||
The `<Editable>` component acts like `contenteditable`. Anywhere you render it will render an editable rich-text document for the nearest editor context.
|
||||
The `<Editable>` component acts like `contenteditable`. Anywhere you render it will render an editable richtext document for the nearest editor context.
|
||||
|
||||
There's only one last step. So far we haven't defined what the default value of the editor is, so it's empty. Let's fix that by defining an initial value.
|
||||
|
||||
|
@@ -2,8 +2,8 @@
|
||||
|
||||
This directory contains a set of examples that give you an idea for how you might use Slate to implement your own editor. Take a look around!
|
||||
|
||||
- [**Plain text**](./plain-text.js) — showing the most basic case: a glorified `<textarea>`.
|
||||
- [**Rich text**](./rich-text.js) — showing the features you'd expect from a basic editor.
|
||||
- [**Plain text**](./plaintext.js) — showing the most basic case: a glorified `<textarea>`.
|
||||
- [**Rich text**](./richtext.js) — showing the features you'd expect from a basic editor.
|
||||
- [**Forced Layout**](./forced-layout.js) - showing how to use schema rules to enforce document structure
|
||||
- [**Markdown Shortcuts**](./markdown-shortcuts.js) — showing how to add key handlers for Markdown-like shortcuts.
|
||||
- [**Links**](./links.js) — showing how wrap text in inline nodes with associated data.
|
||||
|
@@ -19,9 +19,9 @@ import MarkdownPreview from '../../examples/markdown-preview'
|
||||
import MarkdownShortcuts from '../../examples/markdown-shortcuts'
|
||||
import Mentions from '../../examples/mentions'
|
||||
import PasteHtml from '../../examples/paste-html'
|
||||
import PlainText from '../../examples/plain-text'
|
||||
import PlainText from '../../examples/plaintext'
|
||||
import ReadOnly from '../../examples/read-only'
|
||||
import RichText from '../../examples/rich-text'
|
||||
import RichText from '../../examples/richtext'
|
||||
import SearchHighlighting from '../../examples/search-highlighting'
|
||||
import Tables from '../../examples/tables'
|
||||
|
||||
@@ -37,9 +37,9 @@ const EXAMPLES = [
|
||||
['Markdown Shortcuts', MarkdownShortcuts, 'markdown-shortcuts'],
|
||||
['Mentions', Mentions, 'mentions'],
|
||||
['Paste HTML', PasteHtml, 'paste-html'],
|
||||
['Plain Text', PlainText, 'plain-text'],
|
||||
['Plain Text', PlainText, 'plaintext'],
|
||||
['Read-only', ReadOnly, 'read-only'],
|
||||
['Rich Text', RichText, 'rich-text'],
|
||||
['Rich Text', RichText, 'richtext'],
|
||||
['Search Highlighting', SearchHighlighting, 'search-highlighting'],
|
||||
['Tables', Tables, 'tables'],
|
||||
]
|
||||
|
@@ -5,7 +5,7 @@ const Example = () => {
|
||||
const router = useRouter()
|
||||
|
||||
useEffect(() => {
|
||||
router.replace(`/examples/rich-text`)
|
||||
router.replace(`/examples/richtext`)
|
||||
})
|
||||
|
||||
return null
|
||||
|
Reference in New Issue
Block a user