1
0
mirror of https://github.com/ianstormtaylor/slate.git synced 2025-08-15 11:44:05 +02:00

Glossary Enhancements (#2663)

* Rephrased to removed idiom and better describe Slate.

* Added links to educate folks about the core DOM concepts

* Renamed headline to `Slate Mirrors the Dom`

* Rephrased immutable js introduction

* Corrected spelling error

* Simplified language introducing how one can change values.

* Simplified statement about collection methods

* Added encouraging language for Immutable JS learning suggestion

* Quoted mozilla links

* Suggestions to improve readability of data-model documentation

* Added serialized example value

* Resolved one prettier complaints

* `yarn run prettier`

* anchor/focus point glossary content

* normalized term identifiers

* added mark

* Added plugin

* Added schema

* `yarn run prettier` and enhancements to collapsed, focus, and value
This commit is contained in:
Aaron Greenlee
2019-03-26 10:27:48 -04:00
committed by Ian Storm Taylor
parent 02f4850930
commit edbafa6fcb
5 changed files with 118 additions and 44 deletions

View File

@@ -4,6 +4,10 @@ A glossary explaining the terms commonly used in Slate:
### Anchor
An _"anchor point"_ is a point where a range starts.
![An animated gif illustrating an anchor point within a selection.](../images/glossary/anchor-point.gif 'Anchor Point')
### Block
### Blur
@@ -12,10 +16,14 @@ A glossary explaining the terms commonly used in Slate:
### Character
A "character" is the smallest element that makes up a text node in Slate.
A _"character"_ is the smallest element that makes up a text node in Slate.
### Collapsed
A selection is _"collapsed"_ when text is deselected. A collapse occurs when a range's start and end points are the same.
![An animated gif illustrating the how a selection is collapsed when text is de-selected.](../images/glossary/collapsed.gif 'Deselection')
### Core
### Data
@@ -24,7 +32,7 @@ A "character" is the smallest element that makes up a text node in Slate.
### Document
The "document" is the top-level ["node"](#node) that contains all other nodes that make up the content of the Slate editor.
The _"document"_ is the top-level ["node"](#node) that contains all other nodes that make up the content of the Slate editor.
### Editor
@@ -32,6 +40,18 @@ The "document" is the top-level ["node"](#node) that contains all other nodes th
### Focus
Focus is defined differently based on your context:
#### Focus Point
A _"focus point"_ is where a range ends. Unlike a anchor point, a focus point can be expanded.
![An animated gif illustrating the focus point as it changes for an expanding selection.](../images/glossary/focus-point.gif 'Focus Point')
#### Focus Block
The editor value provides a reference to the current _"focus block"_ as a convenience. For example, you access the words within the block a user is focused on like so: `const words = editor.value.focusBlock.text.split(' ');`
### Fragment
### History
@@ -40,10 +60,12 @@ The "document" is the top-level ["node"](#node) that contains all other nodes th
### Key
Keys are unique identifiers given to nodes in Slate to be able to reference them uniquely even as the document changes.
A _"keys"_ is a unique identifier assigned to a node in Slate and is used to reference a node uniquely. As as the document changes, new unique keys are issued to avoid collisions within the data model.
### Mark
A _"mark"_ represents formatting data that is attached to characters within text. Standard formatting such as **bold**, _italic_, `code`, or custom formatting for your application can be implemented using marks.
### Merge
### Model
@@ -54,7 +76,7 @@ Keys are unique identifiers given to nodes in Slate to be able to reference them
### Offset
An offset is a distance from the start of a text node, measured in ["characters"](#character).
An _"offset"_ is a distance from the start of a text node, measured in ["characters"](#character).
### Operation
@@ -62,13 +84,15 @@ An offset is a distance from the start of a text node, measured in ["characters"
### Plugin
A _"plugin"_ is a reusable object that implements one or more of Slate's plugin hooks to add specific behavior to your editor. A plugin helps you express your application while keeping it easy to maintain and reason about.
### Point
A point represents a specific location in a document, where a user's cursor could be placed. It is represented by the `key` of the node in the document, and the `offset` of characters into a node.
A _"point"_ represents a specific location in a document, where a user's cursor could be placed. It is represented by the `key` of the node in the document, and the `offset` of characters into a node.
### Range
A range is a way to represent a specific section of a document between two ["points"](#point). It is modelled after the [DOM Range](https://developer.mozilla.org/en-US/docs/Web/API/Range) concept.
A _"range"_ is a way to represent a specific section of a document between two ["points"](#point). It is modelled after the [DOM Range](https://developer.mozilla.org/en-US/docs/Web/API/Range) concept.
### Redo
@@ -76,6 +100,8 @@ A range is a way to represent a specific section of a document between two ["poi
### Schema
A Slate _"schema"_ is a JavaScript object with properties that describe the document, block nodes, and inline nodes in your editor. Every Slate editor has a "schema" associated with it, which contains information about the structure of its content. For the most basic cases, you'll just rely on Slate's default core schema. But for advanced use cases, you can enforce rules about what the content of a Slate document can contain. Read [Schema reference](../reference/slate/schema.md) to learn more.
### Selection
### Serializer
@@ -90,12 +116,14 @@ A range is a way to represent a specific section of a document between two ["poi
### Unwrap
To "unwrap" is the opposite of to ["wrap"](#wrap), removing a surrounding node from a selection.
To _"unwrap"_ is the opposite of to ["wrap"](#wrap), removing a surrounding node from a selection.
### Validate
### Value
A Slate _"value"_ is the top-level object in Slate and is an object encapsulating the entire value of a Slate editor. Read the [Data Model guide](../guides/data-model.md#the-value) to learn more.
### Wrap
To "wrap" is to surround a piece of text or a node in another node. For example, if you select the text `Google` and want to turn it into a link, you'd "wrap" it with an inline link node.
To _"wrap"_ is to surround a piece of text or a node in another node. For example, if you select the text `Google` and want to turn it into a link, you'd "wrap" it with an inline link node.

View File

@@ -2,19 +2,26 @@
Slate is based on an immutable data model that closely resembles the DOM. When you start using Slate, one of the most important things to do is familiarize yourself with this data model. This guide will help you do just that!
## Mirror the DOM
## Slate Mirrors the DOM
One of the main principles of Slate is that it tries to mirror the native DOM API's as much as possible.
If you think about it, this makes sense. Slate is kind of like a nicer implementation of `contenteditable`, which itself is built with the DOM. And 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 is an intentional decision given Slate is a richer implementation of `contenteditable,` which uses the DOM. And 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.
Because it mirrors the DOM, Slate's data model features a [`Document`](../reference/slate/document.md) with [`Block`](../reference/slate/block.md), [`Inline`](../reference/slate/inline.md) and [`Text`](../reference/slate/text.md) nodes. You can reference parts of the document with a [`Range`](../reference/slate/range.md). And there is a special range-like object called a [`Selection`](../reference/slate/selection.md) that represents the user's current cursor selection.
> 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)
## Immutable Objects
Slate's data model is built out of [`Immutable.js`](https://immutable-js.github.io/immutable-js/) objects. This allows us to make rendering much more performant, and it ensures that we don't end up with hard to track down bugs due to accidentally modifying objects in-place.
Slate's data model is implemented using [`Immutable.js`](https://immutable-js.github.io/immutable-js/) objects to allow more performant rendering and ensure objects cannot be accidentally modified (which are especially tricky bugs to track down).
Specifically, Slate's models are [`Immutable.Record`](https://immutable-js.github.io/immutable-js/docs/#/Record) objects, which makes them very similar to JavaScript objects for retrieiving values:
Specifically, Slate's models are [`Immutable.Record`](https://immutable-js.github.io/immutable-js/docs/#/Record) objects, which makes them very similar to JavaScript objects for retrieving values:
```js
const block = Block.create({ type: 'paragraph' })
@@ -23,76 +30,115 @@ block.object // "block"
block.type // "paragraph"
```
But for updating values, you'll need to use the [`Immutable.Record` API](https://immutable-js.github.io/immutable-js/docs/#/Record/set).
Changing values requires you to use the [`Immutable.Record` API](https://immutable-js.github.io/immutable-js/docs/#/Record/set).
Collections of Slate objects are represented as immutable `Lists`, `Sets`, `Stacks`, etc, which means we get nice support for expressive methods like `filter`, `includes`, `take`, `skip`, `rest` and `last`.
Collections of Slate objects are represented as immutable `Lists`, `Sets`, `Stacks`, etc, which means we enjoy expressive methods like `filter`, `includes`, `take`, `skip`, `rest` and `last`.
If you haven't used Immutable.js before, there is definitely a learning curve. Before you dive into Slate, you should check out the [Immutable.js docs](https://immutable-js.github.io/immutable-js/docs/#/). Once you get the hang of it, it won't slow you down at all, but it will take a few days to get used to, and you might write things suboptimally at first.
If you haven't used [`Immutable.js`](https://immutable-js.github.io/immutable-js/) before, there is definitely a learning curve. Before you dive into Slate, you are encouraged to become familiar the [Immutable.js documentation](https://immutable-js.github.io/immutable-js/docs/#/). Once you get the hang of the Immutable JS API you'll be quite productive. It might take a few days to get used to Immutable JS. And, you might write some suboptimal code at first. Don't let this discourage you! Learning Immutable JS is is well worth the investment!
## The "Value"
The top-level object in Slate—the object encapsulating the entire value of an Slate editor—is called a [`Value`](../reference/slate/value.md).
The top-level object in Slate—the object encapsulating the entire value of a Slate editor—is called a [`Value`](../reference/slate/value.md).
It is made up of a document filled with content, and a selection representing the user's current cursor selection. It also has a few other more advanced properties like `decorations` and `data`.
Value consists of the document which contains all content, and a `selection` representing the user's current cursor selection. Value also has a few other advanced properties such as `decorations` and `data`.
> 📋 For more info, check out the [`Value` reference](../reference/slate/value.md).
The following example illustrates a simple Slate value which has been serialized and logged to the console. Continue reading to learn more about the data types represented.
```json
{
"object": "value",
"document": {
"object": "document",
"data": {},
"nodes": [
{
"object": "block",
"type": "paragraph",
"data": {},
"nodes": [
{
"object": "text",
"leaves": [
{
"object": "leaf",
"text": "A line of text in a paragraph.",
"marks": []
}
]
}
]
}
]
}
}
```
## Documents and Nodes
Slate documents are nested and recursive. This means that a document has block nodes, and those block nodes can have child nodes—all the way down. This lets you model more complex nested behaviors like tables and figures with captions.
A Slate document is a nested and recursive structure. In a document block nodes can have child nodes—all which may have child nodes without limit. The nested and recursive structure enable you to model simple behaviors such as user mentions and hashtags or complex behaviors such as tables and figures with captions.
Unlike the DOM though, Slate enforces a few more restrictions on its documents. This reduces the complexity involved in manipulating them and prevents "impossible" situations from arising. These restrictions are:
Unlike the DOM, Slate offers some constraints to prevent "impossible" situations from occurring. Slate's constraints include:
* **Documents can only contain block nodes as direct children.** This restriction mirrors how rich-text editors work, with the top-most elements being blocks that can be split when pressing <kbd>enter</kbd>.
* **Documents must have block nodes as direct children.** This constraint mirrors how rich-text editors work. The top-most elements are blocks that may be split when pressing <kbd>Enter</kbd>.
* **Blocks can only contain either other block nodes, or inlines and text nodes.** This is another "sane" restriction that allows you to avoid lots of boilerplate `if` statements in your code. Blocks either wrap other blocks, or contain actual content.
* **Blocks may contain either other block nodes, or inlines and text nodes.** This constraint helps you avoid boilerplate `if` statements. You can trust blocks either wrap other blocks, or contain actual content.
* **Inlines can only contain inline or text nodes.** This one is also for sanity and avoiding boilerplate. Once you've descended into an "inline" context, you can't have block nodes inside them.
* **Inlines can only contain inline or text nodes.** This constraint helps you avoid boilerplate code. When working within the context of an inline you can trust the contents do not contain blocks.
* **Text nodes can't be adjacent to other text nodes.** Any two adjacent text nodes will automatically be merged into one. This prevents ambiguous cases where a cursor could be at the end of one text node or at the start of the next. However, you can have an inline node surrounded by two texts.
* **Text nodes cannot be adjacent to other text nodes.** Any two adjacent text nodes will automatically be merged into one by Slate which prevents ambiguous cases where a cursor could be at the end of one text node or at the start of the next. However, you may have an inline node surrounded by two texts.
* **Blocks and inlines must always contain at least one text node.** This is to ensure that the user's cursor can always "enter" the nodes and to make sure that ranges can be created referencing them.
* **Blocks and inlines must always contain at least one text node.** This constraint ensures that the user's cursor can always "enter" the nodes and that ranges can be created referencing them.
Slate enforces all of these restrictions for you automatically. Any time you [run commands](./commands-and-queries.md) that manipulate the document, Slate will check if the document is invalid, and if so, it will return it to a "normalized" value.
Slate enforces all of these constraints for you automatically. As you [run commands](./commands-and-queries.md) to manipulate the document, Slate will normalize the value if it determines the document violates any of these constraints.
> 🙃 Fun fact: "normalizing" is actually based on the DOM's [`Node.normalize()`](https://developer.mozilla.org/en-US/docs/Web/API/Node/normalize)!
In addition to documents, blocks and inlines, Slate introduces one other type of markup that the DOM doesn't have natively, the [`Mark`](../reference/slate/mark.md).
In addition to documents, blocks and inlines, Slate introduces one other type of markup that the DOM does not have natively, the [`Mark`](../reference/slate/mark.md) which is used for formatting.
## Marks
Marks are how Slate represents formatting data that is attached to the characters in the text itself—things like **bold**, _italic_, `code`, or even more complex formatting like comments.
Marks are how Slate represents formatting data that is attached to the characters in the text itself. Standard formatting such as **bold**, _italic_, `code`, or custom formatting for your application can be implemented using marks.
Although you can change styling based on either inlines or marks, marks differ from inlines in that they don't affect the structure of the nodes in the document, they simply attach themselves to the characters.
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.
This makes marks easier to reason about and easier to manipulate. Because inlines involve editing the document's structure, you have to worry about things like splitting any existing nodes, what their order in the hierarchy is, etc. Marks on the other hand 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.
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 an 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.
But this also has implications on how marks are rendered. When marks are rendered, the characters are grouped into "leaves" of text that each contain the same set of marks applied to them. But you cannot guarantee how a set of marks will be ordered.
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 is actually similar to the DOM, where this is invalid:
This limitation with respect to the ordering of marks is similar to the DOM, where this is invalid:
<!-- prettier-ignore -->
```html
<em>t<strong>e</em>x</strong>t
```
Because the elements don't properly close themselves. Instead you have to write it like this:
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
```
And if you happened to add another overlapping section of `<strike>` to that text, you might have to rearrange the closing tags 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.
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.
That all sounds pretty complex, but you don't have to think about it much, as long as you use marks and inlines for their intended purposes...
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.
## Ranges, Points and "The Selection"
Just like in the DOM, you can reference a part of the document using a `Range`. And there's one special range that Slate keeps track of that refers to the user's current cursor selection, called the `Selection`.
Just like the DOM, you can reference a part of the document using a `Range`. Slate keeps track of a the user's current cursor selection using a range called the `Selection`.
Ranges are defined by two `Point`s, an "anchor" point and a "focus" point. The anchor is where the range starts, and the focus is where it ends. And each point is a combination of a "path" or "key" referencing a specific node, and an "offset". This ends up looking like this:
Ranges are defined by two `Point`s:
* **Anchor point** is where the range starts
* **Focus point** is where the range ends
> Note: The terms "anchor" and "focus" are borrowed from the DOM API (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)).
Each point is a combination of a "path" or "key" referencing a specific node, and an "offset". This ends up looking like this:
```js
const range = Range.create({
@@ -109,24 +155,24 @@ const range = Range.create({
})
```
The more readable `node-a` name is just pseudocode, because Slate uses auto-incrementing numerical strings by default—`'1', '2', '3', ...` But the important part is that every node has a unique `key` property, and a range can reference nodes by their keys.
> Note: Every node has a unique `key` property. The above psuedocode references `node-a` and `node-b`; however, Slate uses auto-incrementing numerical strings by default—`'1', '2', '3', ...`.
The terms "anchor" and "focus" are borrowed from the DOM API. The anchor point isn't always _before_ the focus point in the document. Just like in the DOM, it depends on whether the range is backwards or forwards.
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 MDN explains it:
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)
To make dealing with ranges easier though, `Range` objects also provide `start` and `end` point properties that take whether the range is forward or backward into account. The `start.key` and `start.path` will always be before the `end.key` and `end.path` in the document.
To make working with ranges easier, `Range` objects also provide both `start` and `end` point properties that consider whether the range is forward or backward into account. The `start.key` and `start.path` will always be before the `end.key` and `end.path` in the document to provide you with intuitive methods of working with ranges.
These `start` and `end` points are what most of your logic will be based on, since you rarely care which side of the selection is "extendable".
Typically, you will utilize `start` and `end` points since you rarely care which side of the selection is "extendable".
One important thing to note is that the anchor and focus points of ranges **always reference the "leaf-most" text nodes** in a document. They never reference blocks or inlines, always their child text nodes. This is different than in the DOM API, but it makes dealing with ranges a _lot_ easier because there are less edge cases to handle.
One important thing to note is that the anchor and focus points of ranges **always reference the "leaf-most" text nodes** in a document and never reference blocks or inlines. Said differently ranges always reference child text nodes. This range behavior is different than the DOM API and 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).
The `Selection` model contains slightly more information than the simple `Range` model, because it needs to keep track of "focus" and "marks" for the user. For example:
The `Selection` model contains slightly more information than the `Range` model because it is responsible for tracking the "focus" and "marks" for the user. Example:
```js
const selection = Selection.create({
@@ -145,7 +191,7 @@ const selection = Selection.create({
})
```
However, keeping the `key` and `path` of ranges or selections in sync yourself is tedious. Instead, you can create selections using either and have the other automatically be inferred by the document. To do that, you use the `createRange` and `createSelection` methods:
However, keeping the `key` and `path` of ranges or selections synchronized yourself is tedious. Instead, you can create selections using either option and have the other automatically be inferred by the document. To do so, you use the `createRange` and `createSelection` methods:
```js
const selection = document.createSelection({

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB