1
0
mirror of https://github.com/ianstormtaylor/slate.git synced 2025-04-21 05:42:00 +02:00

GitBook: [main] 58 pages and 7 assets modified

This commit is contained in:
Ian Storm Taylor 2021-04-01 23:20:52 +00:00 committed by gitbook-bot
parent 15966086d4
commit a38957b849
No known key found for this signature in database
GPG Key ID: 07D2180C7B12D0FF
51 changed files with 855 additions and 991 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 233 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 233 KiB

View File

@ -1,4 +1,4 @@
# Slate
# Introduction
[Slate](http://slatejs.org) is a _completely_ customizable framework for building rich text editors.
@ -6,27 +6,21 @@ Slate lets you build rich, intuitive editors like those in [Medium](https://medi
It can do this because all of its logic is implemented with a series of plugins, so you aren't ever constrained by what _is_ or _isn't_ in "core". You can think of it like a pluggable implementation of `contenteditable` built on top of [React](https://facebook.github.io/react/). It was inspired by libraries like [Draft.js](https://facebook.github.io/draft-js/), [Prosemirror](http://prosemirror.net/) and [Quill](http://quilljs.com/).
> 🤖 **Slate is currently in beta**. Its core API is usable now, but you might need to pull request fixes for advanced use cases. Some of its APIs are not "finalized" and will (breaking) change over time as we find better solutions.
> 🤖 **Slate is currently in beta**. Its core API is usable now, but you might need to pull request fixes for advanced use cases. Some of its APIs are not "finalized" and will \(breaking\) change over time as we find better solutions.
## Why?
Why create Slate? Well... _(Beware: this section has a few of [my](https://github.com/ianstormtaylor) opinions!)_
Why create Slate? Well... _\(Beware: this section has a few of_ [_my_](https://github.com/ianstormtaylor) _opinions!\)_
Before creating Slate, I tried a lot of the other rich text libraries out there—[**Draft.js**](https://facebook.github.io/draft-js/), [**Prosemirror**](http://prosemirror.net/), [**Quill**](http://quilljs.com/), etc. What I found was that while getting simple examples to work was easy enough, once you started trying to build something like [Medium](https://medium.com/), [Dropbox Paper](https://www.dropbox.com/paper) or [Google Docs](https://www.google.com/docs/about/), you ran into deeper issues...
- **The editor's "schema" was hardcoded and hard to customize.** Things like bold and italic were supported out of the box, but what about comments, or embeds, or even more domain-specific needs?
- **Transforming the documents programmatically was very convoluted.** Writing as a user may have worked, but making programmatic changes, which is critical for building advanced behaviors, was needlessly complex.
- **Serializing to HTML, Markdown, etc. seemed like an afterthought.** Simple things like transforming a document to HTML or Markdown involved writing lots of boilerplate code, for what seemed like very common use cases.
- **Re-inventing the view layer seemed inefficient and limiting.** Most editors rolled their own views, instead of using existing technologies like React, so you had to learn a whole new system with new "gotchas".
- **Collaborative editing wasn't designed for in advance.** Often the editor's internal representation of data made it impossible to use to for a realtime, collaborative editing use case without basically rewriting the editor.
- **The repositories were monolithic, not small and reusable.** The code bases for many of the editors often didn't expose the internal tooling that could have been re-used by developers, leading to having to reinvent the wheel.
- **Building complex, nested documents was impossible.** Many editors were designed around simplistic "flat" documents, making things like tables, embeds and captions difficult to reason about and sometimes impossible.
* **The editor's "schema" was hardcoded and hard to customize.** Things like bold and italic were supported out of the box, but what about comments, or embeds, or even more domain-specific needs?
* **Transforming the documents programmatically was very convoluted.** Writing as a user may have worked, but making programmatic changes, which is critical for building advanced behaviors, was needlessly complex.
* **Serializing to HTML, Markdown, etc. seemed like an afterthought.** Simple things like transforming a document to HTML or Markdown involved writing lots of boilerplate code, for what seemed like very common use cases.
* **Re-inventing the view layer seemed inefficient and limiting.** Most editors rolled their own views, instead of using existing technologies like React, so you had to learn a whole new system with new "gotchas".
* **Collaborative editing wasn't designed for in advance.** Often the editor's internal representation of data made it impossible to use to for a realtime, collaborative editing use case without basically rewriting the editor.
* **The repositories were monolithic, not small and reusable.** The code bases for many of the editors often didn't expose the internal tooling that could have been re-used by developers, leading to having to reinvent the wheel.
* **Building complex, nested documents was impossible.** Many editors were designed around simplistic "flat" documents, making things like tables, embeds and captions difficult to reason about and sometimes impossible.
Of course not every editor exhibits all of these issues, but if you've tried using another editor you might have run into similar problems. To get around the limitations of their APIs and achieve the user experience you're after, you have to resort to very hacky things. And some experiences are just plain impossible to achieve.
@ -36,20 +30,14 @@ Which brings me to how Slate solves all of that...
## Principles
Slate tries to solve the question of "[Why?](#why)" with a few principles:
Slate tries to solve the question of "[Why?](Introduction.md#why)" with a few principles:
1. **First-class plugins.** The most important part of Slate is that plugins are first-class entities. That means you can _completely_ customize the editing experience, to build complex editors like Medium's or Dropbox's, without having to fight against the library's assumptions.
2. **Schema-less core.** Slate's core logic assumes very little about the schema of the data you'll be editing, which means that there are no assumptions baked into the library that'll trip you up when you need to go beyond the most basic use cases.
3. **Nested document model.** The document model used for Slate is a nested, recursive tree, just like the DOM itself. This means that creating complex components like tables or nested block quotes are possible for advanced use cases. But it's also easy to keep it simple by only using a single level of hierarchy.
4. **Parallel to the DOM.** Slate's data model is based on the DOM—the document is a nested tree, it uses selections and ranges, and it exposes all the standard event handlers. This means that advanced behaviors like tables or nested block quotes are possible. Pretty much anything you can do in the DOM, you can do in Slate.
5. **Intuitive commands.** Slate documents are edited using "commands", that are designed to be high-level and extremely intuitive to write and read, so that custom functionality is as expressive as possible. This greatly increases your ability to reason about your code.
6. **Collaboration-ready data model.** The data model Slate uses—specifically how operations are applied to the document—has been designed to allow for collaborative editing to be layered on top, so you won't need to rethink everything if you decide to make your editor collaborative.
7. **Clear "core" boundaries.** With a plugin-first architecture, and a schema-less core, it becomes a lot clearer where the boundary is between "core" and "custom", which means that the core experience doesn't get bogged down in edge cases.
## Demo
@ -60,17 +48,17 @@ 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://www.slatejs.org/examples/plaintext) — showing the most basic case: a glorified `<textarea>`.
- [**Rich text**](https://www.slatejs.org/examples/richtext) — showing the features you'd expect from a basic editor.
- [**Markdown preview**](https://www.slatejs.org/examples/markdown-preview) — showing how to add key handlers for Markdown-like shortcuts.
- [**Links**](https://www.slatejs.org/examples/links) — showing how to wrap text in inline nodes with associated data.
- [**Images**](https://www.slatejs.org/examples/images) — showing how to use void (text-less) nodes to add images.
- [**Hovering toolbar**](https://www.slatejs.org/examples/hovering-toolbar) — showing how a contextual hovering menu can be implemented.
- [**Tables**](https://www.slatejs.org/examples/tables) — showing how to nest blocks to render more advanced components.
- [**Paste HTML**](https://www.slatejs.org/examples/paste-html) — showing how to use an HTML serializer to handle pasted HTML.
- [**Mentions**](https://www.slatejs.org/examples/mentions) — showing how to use inline void nodes for simple @-mentions.
* [**Plain text**](https://www.slatejs.org/examples/plaintext) — showing the most basic case: a glorified `<textarea>`.
* [**Rich text**](https://www.slatejs.org/examples/richtext) — showing the features you'd expect from a basic editor.
* [**Markdown preview**](https://www.slatejs.org/examples/markdown-preview) — showing how to add key handlers for Markdown-like shortcuts.
* [**Links**](https://www.slatejs.org/examples/links) — showing how to wrap text in inline nodes with associated data.
* [**Images**](https://www.slatejs.org/examples/images) — showing how to use void \(text-less\) nodes to add images.
* [**Hovering toolbar**](https://www.slatejs.org/examples/hovering-toolbar) — showing how a contextual hovering menu can be implemented.
* [**Tables**](https://www.slatejs.org/examples/tables) — showing how to nest blocks to render more advanced components.
* [**Paste HTML**](https://www.slatejs.org/examples/paste-html) — showing how to use an HTML serializer to handle pasted HTML.
* [**Mentions**](https://www.slatejs.org/examples/mentions) — showing how to use inline void nodes for simple @-mentions.
Each example includes a **View Source** link to the code that implements it. And we have [other examples](https://github.com/ianstormtaylor/slate/tree/main/site/examples) too.
Each example includes a **View Source** link to the code that implements it. And we have [other examples](https://github.com/ianstormtaylor/slate/tree/master/site/examples) too.
If you have an idea for an example that shows a common use case, pull request it!
@ -78,21 +66,22 @@ If you have an idea for an example that shows a common use case, pull request it
If you're using Slate for the first time, check out the [Getting Started](http://docs.slatejs.org/walkthroughs/01-installing-slate) walkthroughs and the [Concepts](http://docs.slatejs.org/concepts) to familiarize yourself with Slate's architecture and mental models.
- [**Walkthroughs**](http://docs.slatejs.org/walkthroughs)
- [**Concepts**](http://docs.slatejs.org/concepts)
- [**FAQ**](http://docs.slatejs.org/general/faq)
- [**Resources**](http://docs.slatejs.org/general/resources)
* [**Walkthroughs**](http://docs.slatejs.org/walkthroughs)
* [**Concepts**](http://docs.slatejs.org/concepts)
* [**FAQ**](http://docs.slatejs.org/general/faq)
* [**Resources**](http://docs.slatejs.org/general/resources)
If even that's not enough, you can always [read the source itself](https://github.com/ianstormtaylor/slate/tree/main/packages), which is heavily commented.
If even that's not enough, you can always [read the source itself](https://github.com/ianstormtaylor/slate/tree/master/packages), which is heavily commented.
There are also translations of the documentation into other languages:
- [中文](https://doodlewind.github.io/slate-doc-cn/)
* [中文](https://doodlewind.github.io/slate-doc-cn/)
If you're maintaining a translation, feel free to pull request it here!
## Contributing!
All contributions are super welcome! Check out the [Contributing instructions](../Contributing.md) for more info!
All contributions are super welcome! Check out the [Contributing instructions](general/contributing.md) for more info!
Slate is [MIT-licensed](https://github.com/ianstormtaylor/slate/tree/f6bfe034d707693488c38da77537fd36cb8856cf/License.md).
Slate is [MIT-licensed](../License.md).

View File

@ -1,56 +1,59 @@
- [Introduction](./Introduction.md)
# Table of contents
* [Introduction](Introduction.md)
## Walkthroughs
- [Installing Slate](./walkthroughs/01-installing-slate.md)
- [Adding Event Handlers](./walkthroughs/02-adding-event-handlers.md)
- [Defining Custom Elements](./walkthroughs/03-defining-custom-elements.md)
- [Applying Custom Formatting](./walkthroughs/04-applying-custom-formatting.md)
- [Executing Commands](./walkthroughs/05-executing-commands.md)
- [Saving to a Database](./walkthroughs/06-saving-to-a-database.md)
- [Using the Bundled Source](./walkthroughs/XX-using-the-bundled-source.md)
* [Installing Slate](walkthroughs/01-installing-slate.md)
* [Adding Event Handlers](walkthroughs/02-adding-event-handlers.md)
* [Defining Custom Elements](walkthroughs/03-defining-custom-elements.md)
* [Applying Custom Formatting](walkthroughs/04-applying-custom-formatting.md)
* [Executing Commands](walkthroughs/05-executing-commands.md)
* [Saving to a Database](walkthroughs/06-saving-to-a-database.md)
* [Using the Bundled Source](walkthroughs/xx-using-the-bundled-source.md)
## Concepts
- [Interfaces](./concepts/01-interfaces.md)
- [Nodes](./concepts/02-nodes.md)
- [Locations](./concepts/03-locations.md)
- [Commands](./concepts/04-commands.md)
- [Operations](./concepts/05-operations.md)
- [Editor](./concepts/06-editor.md)
- [Plugins](./concepts/07-plugins.md)
- [Rendering](./concepts/08-rendering.md)
- [Serializing](./concepts/09-serializing.md)
- [Normalizing](./concepts/10-normalizing.md)
- [TypeScript](./concepts/11-typescript.md)
- [Migrating](./concepts/XX-migrating.md)
* [Interfaces](concepts/01-interfaces.md)
* [Nodes](concepts/02-nodes.md)
* [Locations](concepts/03-locations.md)
* [Commands](concepts/04-commands.md)
* [Operations](concepts/05-operations.md)
* [Editor](concepts/06-editor.md)
* [Plugins](concepts/07-plugins.md)
* [Rendering](concepts/08-rendering.md)
* [Serializing](concepts/09-serializing.md)
* [Normalizing](concepts/10-normalizing.md)
* [TypeScript](concepts/11-typescript.md)
* [Migrating](concepts/xx-migrating.md)
## API
- [Transforms](./api/transforms.md)
- [Node Types](./api/nodes.md)
- [Editor](./api/editor.md)
- [Element](./api/element.md)
- [Node](./api/node.md)
- [Text](./api/text.md)
- [Location Types](./api/locations.md)
- [Location](./api/location.md)
- [Path](./api/path.md)
- [Point](./api/point.md)
- [PointRef](./api/point-ref.md)
- [Range](./api/range.md)
- [RangeRef](./api/range-ref.md)
- [Operation](./api/operation.md)
* [Transforms](api/transforms.md)
* [Node Types](api/nodes/README.md)
* [Editor](api/nodes/editor.md)
* [Element](api/nodes/element.md)
* [Node](api/nodes/node.md)
* [Text](api/nodes/text.md)
* [Location Types](api/locations/README.md)
* [Location](api/locations/location.md)
* [Path](api/locations/path.md)
* [Point](api/locations/point.md)
* [PointRef](api/locations/point-ref.md)
* [Range](api/locations/range.md)
* [RangeRef](api/locations/range-ref.md)
* [Operation](api/operation.md)
## Libraries
- [Slate React](./libraries/slate-react.md)
- [Slate History](./libraries/slate-history.md)
- [Slate Hyperscript](./libraries/slate-hyperscript.md)
* [Slate React](libraries/slate-react.md)
* [Slate History](libraries/slate-history.md)
* [Slate Hyperscript](libraries/slate-hyperscript.md)
## General
- [Resources](./general/resources.md)
- [Contributing](../Contributing.md)
- [Changelog](../Changelog.md)
- [FAQ](./general/faq.md)
* [Resources](general/resources.md)
* [Contributing](general/contributing.md)
* [Changelog](general/changelog.md)
* [FAQ](general/faq.md)

View File

@ -1,12 +0,0 @@
# Location Types
The `Location` interface is a union of the ways to refer to a specific location in a Slate document: paths, points or ranges. Methods will often accept a `Location` instead of requiring only a `Path`, `Point` or `Range`.
```typescript
type Location = Path | Point | Range
```
- [Location](./api/location.md)
- [Path](./api/path.md)
- [Point](./api/point.md)
- [Range](./api/range.md)

View File

@ -0,0 +1,13 @@
# Location Types
The `Location` interface is a union of the ways to refer to a specific location in a Slate document: paths, points or ranges. Methods will often accept a `Location` instead of requiring only a `Path`, `Point` or `Range`.
```typescript
type Location = Path | Point | Range
```
* [Location](https://github.com/ianstormtaylor/slate/tree/5b6b29d33ddcdb9f7f3601477f1ae93c7d5fe45b/docs/api/api/location.md)
* [Path](https://github.com/ianstormtaylor/slate/tree/5b6b29d33ddcdb9f7f3601477f1ae93c7d5fe45b/docs/api/api/path.md)
* [Point](https://github.com/ianstormtaylor/slate/tree/5b6b29d33ddcdb9f7f3601477f1ae93c7d5fe45b/docs/api/api/point.md)
* [Range](https://github.com/ianstormtaylor/slate/tree/5b6b29d33ddcdb9f7f3601477f1ae93c7d5fe45b/docs/api/api/range.md)

View File

@ -6,13 +6,14 @@ The Location interface is a union of the ways to refer to a specific location in
type Location = Path | Point | Range
```
- [Static methods](#static-methods)
- [Check methods](#check-methods)
* [Static methods](location.md#static-methods)
* [Check methods](location.md#check-methods)
## Static methods
### Check methods
###### `Location.isLocation(value: any): value is Location`
#### `Location.isLocation(value: any): value is Location`
Check if a value implements the `Location` interface.

View File

@ -6,57 +6,52 @@
type Path = number[]
```
- [Static methods](#static-methods)
- [Retrieval methods](#retrieval-methods)
- [Check methods](#check-methods)
- [Transform method](#transform-method)
* [Static methods](path.md#static-methods)
* [Retrieval methods](path.md#retrieval-methods)
* [Check methods](path.md#check-methods)
* [Transform method](path.md#transform-method)
## Static methods
### Retrieval methods
###### `Path.ancestors(path: Path, options: { reverse?: boolean } = {}): Path[]`
#### `Path.ancestors(path: Path, options: { reverse?: boolean } = {}): Path[]`
Get a list of ancestor paths for a given path.
The paths are sorted from deepest to shallowest ancestor. However, if the
`reverse: true` option is passed, they are reversed.
The paths are sorted from deepest to shallowest ancestor. However, if the `reverse: true` option is passed, they are reversed.
###### `Path.common(path: Path, another: Path): Path`
#### `Path.common(path: Path, another: Path): Path`
Get the common ancestor path of two paths.
###### `Path.compare(path: Path, another: Path): -1 | 0 | 1`
#### `Path.compare(path: Path, another: Path): -1 | 0 | 1`
Compare a path to another, returning an integer indicating whether the path
was before, at, or after the other.
Compare a path to another, returning an integer indicating whether the path was before, at, or after the other.
Note: Two paths of unequal length can still receive a `0` result if one is
directly above or below the other. If you want exact matching, use
[[Path.equals]] instead.
Note: Two paths of unequal length can still receive a `0` result if one is directly above or below the other. If you want exact matching, use \[\[Path.equals\]\] instead.
###### `Path.levels(path: Path, options?): Path[]`
#### `Path.levels(path: Path, options?): Path[]`
Get a list of paths at every level down to a path. Note: this is the same
as `Path.ancestors`, but including the path itself.
Get a list of paths at every level down to a path. Note: this is the same as `Path.ancestors`, but including the path itself.
The paths are sorted from shallowest to deepest. However, if the `reverse: true` option is passed, they are reversed.
Options: `{reverse?: boolean}`
###### `Path.next(path: Path): Path`
#### `Path.next(path: Path): Path`
Given a path, get the path to the next sibling node.
###### `Path.parent(path: Path): Path`
#### `Path.parent(path: Path): Path`
Given a path, return a new path referring to the parent node above it.
###### `Path.previous(path: Path): Path`
#### `Path.previous(path: Path): Path`
Given a path, get the path to the previous sibling node.
###### `Path.relative(path: Path, ancestor: Path): Path`
#### `Path.relative(path: Path, ancestor: Path): Path`
Get a path relative to an ancestor.
@ -64,66 +59,67 @@ Get a path relative to an ancestor.
Check some attribute of a path. Always returns a boolean.
###### `Path.endsAfter(path: Path, another: Path): boolean`
#### `Path.endsAfter(path: Path, another: Path): boolean`
Check if a path ends after one of the indexes in another.
###### `Path.endsAt(path: Path, another: Path): boolean`
#### `Path.endsAt(path: Path, another: Path): boolean`
Check if a path ends at one of the indexes in another.
###### `Path.endsBefore(path: Path, another: Path): boolean`
#### `Path.endsBefore(path: Path, another: Path): boolean`
Check if a path ends before one of the indexes in another.
###### `Path.equals(path: Path, another: Path): boolean`
#### `Path.equals(path: Path, another: Path): boolean`
Check if a path is exactly equal to another.
###### `Path.hasPrevious(path: Path): boolean`
#### `Path.hasPrevious(path: Path): boolean`
Check if the path of previous sibling node exists
###### `Path.isAfter(path: Path, another: Path): boolean`
#### `Path.isAfter(path: Path, another: Path): boolean`
Check if a path is after another.
###### `Path.isAncestor(path: Path, another: Path): boolean`
#### `Path.isAncestor(path: Path, another: Path): boolean`
Check if a path is an ancestor of another.
###### `Path.isBefore(path: Path, another: Path): boolean`
#### `Path.isBefore(path: Path, another: Path): boolean`
Check if a path is before another.
###### `Path.isChild(path: Path, another: Path): boolean`
#### `Path.isChild(path: Path, another: Path): boolean`
Check if a path is a child of another.
###### `Path.isCommon(path: Path, another: Path): boolean`
#### `Path.isCommon(path: Path, another: Path): boolean`
Check if a path is equal to or an ancestor of another.
###### `Path.isDescendant(path: Path, another: Path): boolean`
#### `Path.isDescendant(path: Path, another: Path): boolean`
Check if a path is a descendant of another.
###### `Path.isParent(path: Path, another: Path): boolean`
#### `Path.isParent(path: Path, another: Path): boolean`
Check if a path is the parent of another.
###### `Path.isPath(value: any): value is Path`
#### `Path.isPath(value: any): value is Path`
Check is a value implements the `Path` interface.
###### `Path.isSibling(path: Path, another: Path): boolean`
#### `Path.isSibling(path: Path, another: Path): boolean`
Check if a path is a sibling of another.
### Transform method
###### `Path.transform(path: Path, operation: Operation, options?): Path | null`
#### `Path.transform(path: Path, operation: Operation, options?): Path | null`
Transform a path by an operation.
Options: `{ affinity?: 'forward' | 'backward' | null }`

View File

@ -10,13 +10,14 @@ interface PointRef {
}
```
- [Static methods](#static-methods)
- [Transform methods](#trasnform-methods)
* [Static methods](point-ref.md#static-methods)
* [Transform methods](point-ref.md#trasnform-methods)
## Static methods
### Transform methods
###### `PointRef.transform(ref: PointRef, op: Operation)`
#### `PointRef.transform(ref: PointRef, op: Operation)`
Transform the point refs current value by an `op`.

View File

@ -6,45 +6,45 @@
interface Point {
path: Path
offset: number
[key: string]: unknown
}
```
- [Static methods](#static-methods)
- [Retrieval methods](#retrieval-methods)
- [Check methods](#check-methods)
- [Transform methods](#transform-methods)
* [Static methods](point.md#static-methods)
* [Retrieval methods](point.md#retrieval-methods)
* [Check methods](point.md#check-methods)
* [Transform methods](point.md#transform-methods)
## Static methods
### Retrieval methods
###### `Point.compare(point: Point, another: Point): -1 | 0 | 1`
#### `Point.compare(point: Point, another: Point): -1 | 0 | 1`
Compare a `point` to `another`, returning an integer indicating whether the point was before, at or after the other.
### Check methods
###### `Point.isAfter(point: Point, another: Point): boolean`
#### `Point.isAfter(point: Point, another: Point): boolean`
Check if a `point` is after `another`.
###### `Point.isBefore(point: Point, another: Point): boolean`
#### `Point.isBefore(point: Point, another: Point): boolean`
Check if a `point` is before `another`.
###### `Point.equals(point: Point, another: Point): boolean`
#### `Point.equals(point: Point, another: Point): boolean`
Check if a `point` is exactly equal to `another`.
###### `Point.isPoint(value: any): value is Point`
#### `Point.isPoint(value: any): value is Point`
Check if a `value` implements the `Point` interface.
### Transform methods
###### `Point.transform(point: Point, op: Operation, options?): Point | null`
#### `Point.transform(point: Point, op: Operation, options?): Point | null`
Transform a `point` by an `op`.
Options: `{affinity?: 'forward' | 'backward' | null}`

View File

@ -10,13 +10,14 @@ interface RangeRef {
}
```
- [Static methods](#static-methods)
- [Transform methods](#transform-methods)
* [Static methods](range-ref.md#static-methods)
* [Transform methods](range-ref.md#transform-methods)
## Static methods
### Transform methods
###### `RangeRef.transform(ref: RangeRef, op: Operation)`
#### `RangeRef.transform(ref: RangeRef, op: Operation)`
Transform the range refs current value by an `op`.

View File

@ -6,38 +6,37 @@
interface Range {
anchor: Point
focus: Point
[key: string]: unknown
}
```
- [Static methods](#static-methods)
- [Retrieval methods](#retrieval-methods)
- [Check methods](#check-methods)
- [Transform methods](#transform-methods)
* [Static methods](range.md#static-methods)
* [Retrieval methods](range.md#retrieval-methods)
* [Check methods](range.md#check-methods)
* [Transform methods](range.md#transform-methods)
## Static methods
### Retrieval methods
###### `Range.edges(range: Range, options?): [Point, Point]`
#### `Range.edges(range: Range, options?): [Point, Point]`
Get the start and end points of a `range`, in the order in which they appear in the document.
Options: `{reverse?: boolean}`
###### `Range.end(range: Range): Point`
#### `Range.end(range: Range): Point`
Get the end point of a `range`.
###### `Range.intersection(range: Range, another: Range): Range | null`
#### `Range.intersection(range: Range, another: Range): Range | null`
Get the intersection of one `range` with `another`.
###### `Range.points(range: Range): Generator<PointEntry>`
#### `Range.points(range: Range): Generator<PointEntry>`
Iterate through all the point entries in a `range`.
###### `Range.start(range: Range): Point`
#### `Range.start(range: Range): Point`
Get the start point of a `range`
@ -45,38 +44,39 @@ Get the start point of a `range`
Check some attribute of a Range. Always returns a boolean.
###### `Range.equals(range: Range, another: Range): boolean`
#### `Range.equals(range: Range, another: Range): boolean`
Check if a `range` is exactly equal to `another`.
###### `Range.includes(range: Range, target: Path | Point | Range): boolean`
#### `Range.includes(range: Range, target: Path | Point | Range): boolean`
Check if a `range` includes a path, a point, or part of another range.
###### `Range.isBackward(range: Range): boolean`
#### `Range.isBackward(range: Range): boolean`
Check if a `range` is backward, meaning that its anchor point appears _after_ its focus point in the document.
###### `Range.isCollapsed(range: Range): boolean`
#### `Range.isCollapsed(range: Range): boolean`
Check if a `range` is collapsed, meaning that both its anchor and focus points refer to the exact same position in the document.
###### `Range.isExpanded(range: Range): boolean`
#### `Range.isExpanded(range: Range): boolean`
Check if a `range` is expanded. This is the opposite of `Range.isCollapsed` and is provided for legibility.
###### `Range.isForward(range: Range): boolean`
#### `Range.isForward(range: Range): boolean`
Check if a `range` is forward. This is the opposite of `Range.isBackward` and is provided for legibility.
###### `Range.isRange(value: any): value is Range`
#### `Range.isRange(value: any): value is Range`
Check if a `value` implements the `Range` interface.
### Transform methods
###### `Range.transform(range: Range, op: Operation, options): Range | null`
#### `Range.transform(range: Range, op: Operation, options): Range | null`
Transform a `range` by an `op`.
Options: `{affinity: 'forward' | 'backward' | 'outward' | 'inward' | null}`

View File

@ -1,15 +0,0 @@
# Node Types
The `Node` union type represents all of the different types of nodes that occur in a Slate document tree.
```typescript
type Node = Editor | Element | Text
type Descendant = Element | Text
type Ancestor = Editor | Element
```
- [Node](./api/node.md)
- [Editor](./api/editor.md)
- [Element](./api/element.md)
- [Text](./api/text.md)

16
docs/api/nodes/README.md Normal file
View File

@ -0,0 +1,16 @@
# Node Types
The `Node` union type represents all of the different types of nodes that occur in a Slate document tree.
```typescript
type Node = Editor | Element | Text
type Descendant = Element | Text
type Ancestor = Editor | Element
```
* [Node](https://github.com/ianstormtaylor/slate/tree/5b6b29d33ddcdb9f7f3601477f1ae93c7d5fe45b/docs/api/api/node.md)
* [Editor](https://github.com/ianstormtaylor/slate/tree/5b6b29d33ddcdb9f7f3601477f1ae93c7d5fe45b/docs/api/api/editor.md)
* [Element](https://github.com/ianstormtaylor/slate/tree/5b6b29d33ddcdb9f7f3601477f1ae93c7d5fe45b/docs/api/api/element.md)
* [Text](https://github.com/ianstormtaylor/slate/tree/5b6b29d33ddcdb9f7f3601477f1ae93c7d5fe45b/docs/api/api/text.md)

View File

@ -8,8 +8,6 @@ interface Editor {
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
@ -30,19 +28,19 @@ interface Editor {
}
```
- [Instantiation methods](#instantiation-methods)
- [Static methods](#static-methods)
- [Retrieval methods](#retrieval-methods)
- [Manipulation methods](#manipulation-methods)
- [Check methods](#check-methods)
- [Normalization methods](#normalization-methods)
- [Instance methods](#instance-methods)
- [Schema-specific methods to override](#schema-specific-methods-to-override)
- [Core actions](#core-actions)
* [Instantiation methods](editor.md#instantiation-methods)
* [Static methods](editor.md#static-methods)
* [Retrieval methods](editor.md#retrieval-methods)
* [Manipulation methods](editor.md#manipulation-methods)
* [Check methods](editor.md#check-methods)
* [Normalization methods](editor.md#normalization-methods)
* [Instance methods](editor.md#instance-methods)
* [Schema-specific methods to override](editor.md#schema-specific-methods-to-override)
* [Core actions](editor.md#core-actions)
## Instantiation methods
###### `createEditor(): Editor`
#### `createEditor(): Editor`
Note: This method is imported directly from Slate and is not part of the Editor object.
@ -52,164 +50,155 @@ Creates a new, empty `Editor` object.
### Retrieval methods
###### `Editor.above<T extends Ancestor>(editor: Editor, options?): NodeEntry | undefined`
#### `Editor.above<T extends Ancestor>(editor: Editor, options?): NodeEntry | undefined`
Get the ancestor above a location in the document.
Options: `{at?: Location, match?: NodeMatch, mode?: 'highest' | 'lowest', voids?: boolean}`
###### `Editor.after(editor: Editor, at: Location, options?): Point | undefined`
#### `Editor.after(editor: Editor, at: Location, options?): Point | undefined`
Get the point after a location.
Options: `{distance?: number, unit?: 'offset' | 'character' | 'word' | 'line' | 'block', voids?: boolean}`
###### `Editor.before(editor: Editor, at: Location, options?): Point | undefined`
#### `Editor.before(editor: Editor, at: Location, options?): Point | undefined`
Get the point before a location.
Options: `{distance?: number, unit?: 'offset' | 'character' | 'word' | 'line' | 'block', voids?: boolean}`
###### `Editor.edges(editor: Editor, at: Location): [Point, Point]`
#### `Editor.edges(editor: Editor, at: Location): [Point, Point]`
Get the start and end points of a location.
###### `Editor.end(editor: Editor, at: Location): Point`
#### `Editor.end(editor: Editor, at: Location): Point`
Get the end point of a location.
###### `Editor.first(editor: Editor, at: Location): NodeEntry`
#### `Editor.first(editor: Editor, at: Location): NodeEntry`
Get the first node at a location.
###### `Editor.fragment(editor: Editor, at: Location): Descendant[]`
#### `Editor.fragment(editor: Editor, at: Location): Descendant[]`
Get the fragment at a location.
###### `Editor.last(editor: Editor, at: Location): NodeEntry`
#### `Editor.last(editor: Editor, at: Location): NodeEntry`
Get the last node at a location.
###### `Editor.leaf(editor: Editor, at: Location, options?): NodeEntry`
#### `Editor.leaf(editor: Editor, at: Location, options?): NodeEntry`
Get the leaf text node at a location.
Options: `{depth?: number, edge?: 'start' | 'end'}`
###### `Editor.levels<T extends Node>(editor: Editor, options?): Generator<NodeEntry, void, undefined>`
#### `Editor.levels<T extends Node>(editor: Editor, options?): Generator<NodeEntry, void, undefined>`
Iterate through all of the levels at a location.
Options: `{at?: Location, match?: NodeMatch, reverse?: boolean, voids?: boolean}`
###### `Editor.next<T extends Descendant>(editor: Editor, options?): NodeEntry<T> | undefined`
#### `Editor.next<T extends Descendant>(editor: Editor, options?): NodeEntry<T> | undefined`
Get the matching node in the branch of the document after a location.
Options: `{at?: Location, match?: NodeMatch, mode?: 'all' | 'highest' | 'lowest', voids?: boolean}`
###### `Editor.node(editor: Editor, at: Location, options?): NodeEntry`
#### `Editor.node(editor: Editor, at: Location, options?): NodeEntry`
Get the node at a location.
Options: `depth?: number, edge?: 'start' | 'end'`
###### `Editor.nodes(editor: Editor, options?): Generator<NodeEntry<T>, void, undefined>`
#### `Editor.nodes(editor: Editor, options?): Generator<NodeEntry<T>, void, undefined>`
Iterate through all of the nodes in the Editor.
Options: `{at?: Location | Span, match?: NodeMatch, mode?: 'all' | 'highest' | 'lowest', universal?: boolean, reverse?: boolean, voids?: boolean}`
###### `Editor.parent(editor: Editor, at: Location, options?): NodeEntry<Ancestor>`
#### `Editor.parent(editor: Editor, at: Location, options?): NodeEntry<Ancestor>`
Get the parent node of a location.
Options: `{depth?: number, edge?: 'start' | 'end'}`
###### `Editor.path(editor: Editor, at: Location, options?): Path`
#### `Editor.path(editor: Editor, at: Location, options?): Path`
Get the path of a location.
Options: `{depth?: number, edge?: 'start' | 'end'}`
###### `Editor.pathRef(editor: Editor, path: Path, options?): PathRef`
#### `Editor.pathRef(editor: Editor, path: Path, options?): PathRef`
Create a mutable ref for a `Path` object, which will stay in sync as new
operations are applied to the editor.
Create a mutable ref for a `Path` object, which will stay in sync as new operations are applied to the editor.
Options: `{affinity?: 'backward' | 'forward' | null}`
###### `Editor.pathRefs(editor: Editor): Set<PathRef>`
#### `Editor.pathRefs(editor: Editor): Set<PathRef>`
Get the set of currently tracked path refs of the editor.
###### `Editor.point(editor: Editor, at: Location, options?): Point`
#### `Editor.point(editor: Editor, at: Location, options?): Point`
Get the start or end point of a location.
Options: `{edge?: 'start' | 'end'}`
###### `Editor.pointRef(editor: Editor, point: Point, options?): PointRef`
#### `Editor.pointRef(editor: Editor, point: Point, options?): PointRef`
Create a mutable ref for a `Point` object, which will stay in sync as new
operations are applied to the editor.
Create a mutable ref for a `Point` object, which will stay in sync as new operations are applied to the editor.
Options: `{affinity?: 'backward' | 'forward' | null}`
###### `Editor.pointRefs(editor: Editor): Set<PointRef>`
#### `Editor.pointRefs(editor: Editor): Set<PointRef>`
Get the set of currently tracked point refs of the editor.
###### `Editor.positions(editor: Editor, options?): Generator<Point, void, undefined>`
#### `Editor.positions(editor: Editor, options?): Generator<Point, void, undefined>`
Iterate through all of the positions in the document where a `Point` can be
placed.
Iterate through all of the positions in the document where a `Point` can be placed.
By default it will move forward by individual offsets at a time, but you
can pass the `unit: 'character'` option to moved forward one character, word,
or line at at time.
By default it will move forward by individual offsets at a time, but you can pass the `unit: 'character'` option to moved forward one character, word, or line at at time.
Note: By default void nodes are treated as a single point and iteration
will not happen inside their content unless you pass in true for the
voids option, then iteration will occur.
Note: By default void nodes are treated as a single point and iteration will not happen inside their content unless you pass in true for the voids option, then iteration will occur.
Options: `{at?: Location, unit?: 'offset' | 'character' | 'word' | 'line' | 'block', reverse?: boolean, voids?: boolean}`
###### `Editor.previous(editor: Editor, options?): NodeEntry<T> | undefined`
#### `Editor.previous(editor: Editor, options?): NodeEntry<T> | undefined`
Get the matching node in the branch of the document before a location.
Options: `{at?: Location, match?: NodeMatch, mode?: 'all' | 'highest' | 'lowest', voids?: boolean}`
###### `Editor.range(editor: Editor, at: Location, to?: Location): Range`
#### `Editor.range(editor: Editor, at: Location, to?: Location): Range`
Get a range of a location.
###### `Editor.rangeRef(editor: Editor, range: Range, options?): RangeRef`
#### `Editor.rangeRef(editor: Editor, range: Range, options?): RangeRef`
Create a mutable ref for a `Range` object, which will stay in sync as new
operations are applied to the editor.
Create a mutable ref for a `Range` object, which will stay in sync as new operations are applied to the editor.
Options: `{affinity?: 'backward' | 'forward' | 'outward' | 'inward' | null}`
###### `Editor.rangeRefs(editor: Editor): Set<RangeRef>`
#### `Editor.rangeRefs(editor: Editor): Set<RangeRef>`
Get the set of currently tracked range refs of the editor.
###### `Editor.start(editor: Editor, at: Location): Point`
#### `Editor.start(editor: Editor, at: Location): Point`
Get the start point of a location.
###### `Editor.string(editor: Editor, at: Location, options?): string`
#### `Editor.string(editor: Editor, at: Location, options?): string`
Get the text string content of a location.
Note: by default the text of void nodes is considered to be an empty
string, regardless of content, unless you pass in true for the voids option
Note: by default the text of void nodes is considered to be an empty string, regardless of content, unless you pass in true for the voids option
Options: : `{voids?: boolean}`
###### `Editor.void(editor: Editor, options?): NodeEntry<Element> | undefined`
#### `Editor.void(editor: Editor, options?): NodeEntry<Element> | undefined`
Match a void node in the current branch of the editor.
@ -217,60 +206,57 @@ Options: `{at?: Location, mode?: 'highest' | 'lowest', voids?: boolean}`
### Manipulation methods
###### `Editor.addMark(editor: Editor, key: string, value: any): void`
#### `Editor.addMark(editor: Editor, key: string, value: any): void`
Add a custom property to the leaf text nodes in the current selection.
If the selection is currently collapsed, the marks will be added to the
`editor.marks` property instead, and applied when text is inserted next.
If the selection is currently collapsed, the marks will be added to the `editor.marks` property instead, and applied when text is inserted next.
###### `Editor.deleteBackward(editor: Editor, options?): void`
#### `Editor.deleteBackward(editor: Editor, options?): void`
Delete content in the editor backward from the current selection.
Options: `{unit?: 'character' | 'word' | 'line' | 'block'}`
###### `Editor.deleteForward(editor: Editor, options?): void`
#### `Editor.deleteForward(editor: Editor, options?): void`
Delete content in the editor forward from the current selection.
Options: `{unit?: 'character' | 'word' | 'line' | 'block'}`
###### `Editor.deleteFragment(editor: Editor): void`
#### `Editor.deleteFragment(editor: Editor): void`
Delete the content in the current selection.
###### `Editor.insertBreak(editor: Editor): void`
#### `Editor.insertBreak(editor: Editor): void`
Insert a block break at the current selection.
###### `Editor.insertFragment(editor: Editor, fragment: Node[]): void`
#### `Editor.insertFragment(editor: Editor, fragment: Node[]): void`
Insert a fragment at the current selection.
If the selection is currently expanded, it will be deleted first.
###### `Editor.insertNode(editor: Editor, node: Node): void`
#### `Editor.insertNode(editor: Editor, node: Node): void`
Insert a node at the current selection.
If the selection is currently expanded, it will be deleted first.
###### `Editor.insertText(editor: Editor, text: string): void`
#### `Editor.insertText(editor: Editor, text: string): void`
Insert text at the current selection.
If the selection is currently expanded, it will be deleted first.
###### `Editor.removeMark(editor: Editor, key: string): void`
#### `Editor.removeMark(editor: Editor, key: string): void`
Remove a custom property from all of the leaf text nodes in the current
selection.
Remove a custom property from all of the leaf text nodes in the current selection.
If the selection is currently collapsed, the removal will be stored on
`editor.marks` and applied to the text inserted next.
If the selection is currently collapsed, the removal will be stored on `editor.marks` and applied to the text inserted next.
###### `Editor.unhangRange(editor: Editor, range: Range, options?): Range`
#### `Editor.unhangRange(editor: Editor, range: Range, options?): Range`
Convert a range into a non-hanging one.
@ -278,63 +264,63 @@ Options: `{voids?: boolean}`
### Check methods
###### `Editor.hasBlocks(editor: Editor, element: Element): boolean`
#### `Editor.hasBlocks(editor: Editor, element: Element): boolean`
Check if a node has block children.
###### `Editor.hasInlines(editor: Editor, element: Element): boolean`
#### `Editor.hasInlines(editor: Editor, element: Element): boolean`
Check if a node has inline and text children.
###### `Editor.hasTexts(editor: Editor, element: Element): boolean`
#### `Editor.hasTexts(editor: Editor, element: Element): boolean`
Check if a node has text children.
###### `Editor.isBlock(editor: Editor, value: any): value is Element`
#### `Editor.isBlock(editor: Editor, value: any): value is Element`
Check if a value is a block `Element` object.
###### `Editor.isEditor(value: any): value is Editor`
#### `Editor.isEditor(value: any): value is Editor`
Check if a value is an `Editor` object.
###### `Editor.isEnd(editor: Editor, point: Point, at: Location): boolean`
#### `Editor.isEnd(editor: Editor, point: Point, at: Location): boolean`
Check if a point is the end point of a location.
###### `Editor.isEdge(editor: Editor, point: Point, at: Location): boolean`
#### `Editor.isEdge(editor: Editor, point: Point, at: Location): boolean`
Check if a point is an edge of a location.
###### `Editor.isEmpty(editor: Editor, element: Element): boolean`
#### `Editor.isEmpty(editor: Editor, element: Element): boolean`
Check if an element is empty, accounting for void nodes.
###### `Editor.isInline(editor: Editor, value: any): value is Element`
#### `Editor.isInline(editor: Editor, value: any): value is Element`
Check if a value is an inline `Element` object.
###### `Editor.isNormalizing(editor: Editor): boolean`
#### `Editor.isNormalizing(editor: Editor): boolean`
Check if the editor is currently normalizing after each operation.
###### `Editor.isStart(editor: Editor, point: Point, at: Location): boolean`
#### `Editor.isStart(editor: Editor, point: Point, at: Location): boolean`
Check if a point is the start point of a location.
###### `Editor.isVoid(editor: Editor, value: any): value is Element`
#### `Editor.isVoid(editor: Editor, value: any): value is Element`
Check if a value is a void `Element` object.
### Normalization methods
###### `Editor.normalize(editor: Editor, options?): void`
#### `Editor.normalize(editor: Editor, options?): void`
Normalize any dirty objects in the editor.
Options: `{force?: boolean}`
###### `Editor.withoutNormalizing(editor: Editor, fn: () => void): void`
#### `Editor.withoutNormalizing(editor: Editor, fn: () => void): void`
Call a function, deferring normalization until after it completes.
@ -342,9 +328,9 @@ Call a function, deferring normalization until after it completes.
### Schema-specific methods to override
Replace these methods to modify the original behavior of the editor when building [Plugins](/concepts/07-plugins). When modifying behavior, call the original method when appropriate. For example, a plugin that marks image nodes as "void":
Replace these methods to modify the original behavior of the editor when building [Plugins](https://github.com/ianstormtaylor/slate/tree/a02787539a460fb70730085e26df13cca959fabd/concepts/07-plugins/README.md). When modifying behavior, call the original method when appropriate. For example, a plugin that marks image nodes as "void":
```js
```javascript
const withImages = editor => {
const { isVoid } = editor
@ -356,60 +342,61 @@ const withImages = editor => {
}
```
###### `isInline(element: Element)`
#### `isInline(element: Element)`
Check if a value is an inline `Element` object.
###### `isVoid(element: Element)`
#### `isVoid(element: Element)`
Check if a value is a void `Element` object.
###### `normalizeNode(entry: NodeEntry)`
#### `normalizeNode(entry: NodeEntry)`
Normalize a Node according to the schema.
###### `onChange()`
#### `onChange()`
Called when there is a change in the editor.
### Core actions
###### `addMark(key: string, value: any)`
#### `addMark(key: string, value: any)`
Add a custom property to the leaf text nodes in the current selection. If the selection is currently collapsed, the marks will be added to the `editor.marks` property instead, and applied when text is inserted next.
###### `removeMark(key: string)`
#### `removeMark(key: string)`
Remove a custom property from the leaf text nodes in the current selection.
###### `deleteBackward(options?: {unit?: 'character' | 'word' | 'line' | 'block'})`
#### `deleteBackward(options?: {unit?: 'character' | 'word' | 'line' | 'block'})`
Delete content in the editor backward from the current selection.
###### `deleteForward(options?: {unit?: 'character' | 'word' | 'line' | 'block'})`
#### `deleteForward(options?: {unit?: 'character' | 'word' | 'line' | 'block'})`
Delete content in the editor forward from the current selection.
###### `insertFragment(fragment: Node[])`
#### `insertFragment(fragment: Node[])`
Insert a fragment at the current selection. If the selection is currently expanded, delete it first.
###### `deleteFragment()`
#### `deleteFragment()`
Delete the content of the current selection.
###### `insertBreak()`
#### `insertBreak()`
Insert a block break at the current selection. If the selection is currently expanded, delete it first.
###### `insertNode(node: Node)`
#### `insertNode(node: Node)`
Insert a node at the current selection. If the selection is currently expanded, delete it first.
###### `insertText(text: string)`
#### `insertText(text: string)`
Insert text at the current selection. If the selection is currently expanded, delete it first.
###### `apply(operation: Operation)`
#### `apply(operation: Operation)`
Apply an operation in the editor.

View File

@ -5,28 +5,28 @@
```typescript
interface Element {
children: Node[]
[key: string]: unknown
}
```
- [Static methods](#static-methods)
- [Retrieval methods](#retrieval-methods)
- [Check methods](#check-methods)
* [Static methods](element.md#static-methods)
* [Retrieval methods](element.md#retrieval-methods)
* [Check methods](element.md#check-methods)
## Static methods
### Retrieval methods
###### `Element.matches(element: Element, props: Partial<Element>): boolean`
#### `Element.matches(element: Element, props: Partial<Element>): boolean`
Check if an element matches a set of `props`. Note: This checks custom properties, but it does not ensure that any children are equivalent.
### Check methods
###### `Element.isElement(value: any): value is Element`
#### `Element.isElement(value: any): value is Element`
Check if a `value` implements the `Element` interface.
###### `Element.isElementList(value: any): value is Element[]`
#### `Element.isElementList(value: any): value is Element[]`
Check if a `value` is an array of `Element` objects.

View File

@ -1,91 +1,91 @@
# Node
- [Static methods](#static-methods)
- [Retrieval methods](#retrieval-methods)
- [Text methods](#text-methods)
- [Check methods](#check-methods)
* [Static methods](node.md#static-methods)
* [Retrieval methods](node.md#retrieval-methods)
* [Text methods](node.md#text-methods)
* [Check methods](node.md#check-methods)
## Static methods
### Retrieval methods
###### `Node.ancestor(root: Node, path: Path): Ancestor`
#### `Node.ancestor(root: Node, path: Path): Ancestor`
Get the node at a specific `path`, asserting that it is an ancestor node. If the specified node is not an ancestor node, throw an error.
###### `Node.ancestors(root: Node, path: Path, options?): Generator<NodeEntry<Ancestor>>`
#### `Node.ancestors(root: Node, path: Path, options?): Generator<NodeEntry<Ancestor>>`
Return a generator of all the ancestor nodes above a specific path. By default, the order is bottom-up, from lowest to highest ancestor in the tree, but you can pass the `reverse: true` option to go top-down.
Options: `{reverse?: boolean}`
###### `Node.child(root: Node, index: number): Descendant`
#### `Node.child(root: Node, index: number): Descendant`
Get the child of a node at the specified `index`.
###### `Node.children(root: Node, path: Path, options?): Generator<NodeEntry<Descendant>>`
#### `Node.children(root: Node, path: Path, options?): Generator<NodeEntry<Descendant>>`
Iterate over the children of a node at a specific path.
Options: `{reverse?: boolean}`
###### `Node.common(root: Node, path: Path, another: Path): NodeEntry`
#### `Node.common(root: Node, path: Path, another: Path): NodeEntry`
Get an entry for the common ancestor node of two paths.
###### `Node.descendant(root: Node, path: Path): Descendant`
#### `Node.descendant(root: Node, path: Path): Descendant`
Get the node at a specific path, asserting that it's a descendant node.
###### `Node.descendants(root: Node, options?): Generator<NodeEntry<Descendant>>`
#### `Node.descendants(root: Node, options?): Generator<NodeEntry<Descendant>>`
Return a generator of all the descendant node entries inside a root node. Each iteration will return a `NodeEntry` tuple consisting of `[Node, Path]`.
Options: `{from?: Path, to?: Path, reverse?: boolean, pass?: (node: NodeEntry => boolean)}`
###### `Node.elements(root: Node, options?): Generator<ElementEntry>`
#### `Node.elements(root: Node, options?): Generator<ElementEntry>`
Return a generator of all the element nodes inside a root node. Each iteration will return an `ElementEntry` tuple consisting of `[Element, Path]`. If the root node is an element, it will be included in the iteration as well.
Options: `{from?: Path, to?: Path, reverse?: boolean, pass?: (node: NodeEntry => boolean)}`
###### `Node.first(root: Node, path: Path): NodeEntry`
#### `Node.first(root: Node, path: Path): NodeEntry`
Get the first node entry in a root node from a `path`.
###### `Node.fragment(root: Node, range: Range): Descendant[]`
#### `Node.fragment(root: Node, range: Range): Descendant[]`
Get the sliced fragment represented by the `range`.
###### `Node.get(root: Node, path: Path): Node`
#### `Node.get(root: Node, path: Path): Node`
Get the descendant node referred to by a specific `path`. If the path is an empty array, get the root node itself.
###### `Node.last(root: Node, path: Path): NodeEntry`
#### `Node.last(root: Node, path: Path): NodeEntry`
Get the last node entry in a root node at a specific `path`.
###### `Node.leaf(root: Node, path: Path): Text`
#### `Node.leaf(root: Node, path: Path): Text`
Get the node at a specific `path`, ensuring it's a leaf text node. If the node is not a leaf text node, throw an error.
###### `Node.levels(root: Node, path: Path, options?): Generator<NodeEntry>`
#### `Node.levels(root: Node, path: Path, options?): Generator<NodeEntry>`
Return a generator of the nodes in a branch of the tree, from a specific `path`. By default, the order is top-down, from the lowest to the highest node in the tree, but you can pass the `reverse: true` option to go bottom-up.
Options: `{reverse?: boolean}`
###### `Node.matches(root: Node, props: Partial<Node>): boolean`
#### `Node.matches(root: Node, props: Partial<Node>): boolean`
Check if a node matches a set of `props`.
###### `Node.nodes(root: Node, options?): Generator<NodeEntry>`
#### `Node.nodes(root: Node, options?): Generator<NodeEntry>`
Return a generator of all the node entries of a root node. Each entry is returned as a `[Node, Path]` tuple, with the path referring to the node's position inside the root node.
Options: `{from?: Path, to?: Path, reverse?: boolean, pass?: (node: NodeEntry => boolean)}`
###### `Node.parent(root: Node, path: Path): Ancestor`
#### `Node.parent(root: Node, path: Path): Ancestor`
Get the parent of a node at a specific `path`.
@ -93,11 +93,11 @@ Get the parent of a node at a specific `path`.
Methods related to Text.
###### `Node.string(root: Node): string`
#### `Node.string(root: Node): string`
Get the concatenated text string of a node's content. Note that this will not include spaces or line breaks between block nodes. This is not intended as a user-facing string, but as a string for performing offset-related computations for a node.
###### `Node.texts(root: Node, options?): Generator<NodeEntry<Text>>`
#### `Node.texts(root: Node, options?): Generator<NodeEntry<Text>>`
Return a generator of all leaf text nodes in a root node.
@ -107,14 +107,15 @@ Options: `{from?: Path, to?: Path, reverse?: boolean, pass?: (node: NodeEntry =>
Methods used to check some attribute of a Node.
###### `Node.has(root: Node, path: Path): boolean`
#### `Node.has(root: Node, path: Path): boolean`
Check if a descendant node exists at a specific `path`.
###### `Node.isNode(value: any): value is Node`
#### `Node.isNode(value: any): value is Node`
Check if a `value` implements the `Node` interface.
###### `Node.isNodeList(value: any): value is Node[]`
#### `Node.isNodeList(value: any): value is Node[]`
Check if a `value` is a list of `Node` objects.

View File

@ -5,34 +5,34 @@
```typescript
interface Text {
text: string
[key: string]: unknown
}
```
- [Static methods](#static-methods)
- [Retrieval methods](#retrieval-methods)
- [Check methods](#check-methods)
* [Static methods](text.md#static-methods)
* [Retrieval methods](text.md#retrieval-methods)
* [Check methods](text.md#check-methods)
## Static methods
### Retrieval methods
###### `Text.matches(text: Text, props: Partial<Text>): boolean`
#### `Text.matches(text: Text, props: Partial<Text>): boolean`
Check if a `text` matches a set of `props`.
###### `Text.decorations(node: Text, decorations: Range[]): Text[]`
#### `Text.decorations(node: Text, decorations: Range[]): Text[]`
Get the leaves for a text node, given `decorations`.
### Check methods
###### `Text.equals(text: Text, another: Text, options?): boolean`
#### `Text.equals(text: Text, another: Text, options?): boolean`
Check if two text nodes are equal.
Options: `{loose?: boolean}`
###### `Text.isText(value: any): value is Text`
#### `Text.isText(value: any): value is Text`
Check if a `value` implements the `Text` interface.

View File

@ -3,3 +3,4 @@
`Operation` objects define the low-level instructions that Slate editors use to apply changes to their internal state. Representing all changes as operations is what allows Slate editors to easily implement history, collaboration, and other features.
_The `Operation` API reference needs to be added._

View File

@ -2,12 +2,12 @@
Transforms are helper functions operating on the document. They can be used in defining your own commands.
- [Node options](#node-options)
- [Static methods](#static-methods)
- [Node transforms](#node-transforms)
- [Selection transforms](#selection-transforms)
- [Text transforms](#text-transforms)
- [Editor transforms](#editor-transforms)
* [Node options](transforms.md#node-options)
* [Static methods](transforms.md#static-methods)
* [Node transforms](transforms.md#node-transforms)
* [Selection transforms](transforms.md#selection-transforms)
* [Text transforms](transforms.md#text-transforms)
* [Editor transforms](transforms.md#editor-transforms)
## Node options
@ -28,67 +28,67 @@ interface NodeOptions {
Transforms that operate on nodes.
###### `Transforms.insertFragment(editor: Editor, fragment: Node[], options?)`
#### `Transforms.insertFragment(editor: Editor, fragment: Node[], options?)`
Insert of fragment of nodes at the specified location in the document. If no location is specified, insert at the current selection.
Options: `{at?: Location, hanging?: boolean, voids?: boolean}`
###### `Transforms.insertNodes(editor: Editor, nodes: Node | Node[], options?)`
#### `Transforms.insertNodes(editor: Editor, nodes: Node | Node[], options?)`
Insert `nodes` at the specified location in the document. If no location is specified, insert at the current selection. If there is no selection, insert at the end of the document.
Options supported: `NodeOptions & {hanging?: boolean, select?: boolean}`.
###### `Transforms.removeNodes(editor: Editor, options?)`
#### `Transforms.removeNodes(editor: Editor, options?)`
Remove nodes at the specified location in the document. If no location is specified, remove the nodes in the selection.
Options supported: `NodeOptions & {hanging?: boolean}`
###### `Transforms.mergeNodes(editor: Editor, options?)`
#### `Transforms.mergeNodes(editor: Editor, options?)`
Merge a node at the specified location with the previous node at the same depth. If no location is specified, use the selection. Resulting empty container nodes are removed.
Options supported: `NodeOptions & {hanging?: boolean}`
###### `Transforms.splitNodes(editor: Editor, options?)`
#### `Transforms.splitNodes(editor: Editor, options?)`
Split nodes at the specified location. If no location is specified, split the selection.
Options supported: `NodeOptions & {height?: number, always?: boolean}`
###### `Transforms.wrapNodes(editor: Editor, element: Element, options?)`
#### `Transforms.wrapNodes(editor: Editor, element: Element, options?)`
Wrap nodes at the specified location in the `element` container. If no location is specified, wrap the selection.
Options supported: `NodeOptions & {split?: boolean}`. For `options.mode`, `'all'` is also supported.
###### `Transforms.unwrapNodes(editor: Editor, options?)`
#### `Transforms.unwrapNodes(editor: Editor, options?)`
Unwrap nodes at the specified location. If necessary, the parent node is split. If no location is specified, use the selection.
Options supported: `NodeOptions & {split?: boolean}`. For `options.mode`, `'all'` is also supported.
###### `Transforms.setNodes(editor: Editor, props: Partial<Node>, options?)`
#### `Transforms.setNodes(editor: Editor, props: Partial<Node>, options?)`
Set properties of nodes at the specified location. If no location is specified, use the selection.
Options supported: `NodeOptions & {hanging?: boolean, split?: boolean}`. For `options.mode`, `'all'` is also supported.
###### `Transforms.unsetNodes(editor: Editor, props: string | string[], options?)`
#### `Transforms.unsetNodes(editor: Editor, props: string | string[], options?)`
Unset properties of nodes at the specified location. If no location is specified, use the selection.
Options supported: `NodeOptions & {split?: boolean}`. For `options.mode`, `'all'` is also supported.
###### `Transforms.liftNodes(editor: Editor, options?)`
#### `Transforms.liftNodes(editor: Editor, options?)`
Lift nodes at the specified location upwards in the document tree. If necessary, the parent node is split. If no location is specified, use the selection.
Options supported: `NodeOptions`. For `options.mode`, `'all'` is also supported.
###### `Transforms.moveNodes(editor: Editor, options)`
#### `Transforms.moveNodes(editor: Editor, options)`
Move the nodes from an origin to a destination. A destination must be specified in the `options`. If no origin is specified, move the selection.
@ -98,33 +98,33 @@ Options supported: `NodeOptions & {to: Path}`. For `options.mode`, `'all'` is al
Transforms that operate on the document's selection.
###### `Transforms.collapse(editor: Editor, options?)`
#### `Transforms.collapse(editor: Editor, options?)`
Collapse the selection to a single point.
Options: `{edge?: 'anchor' | 'focus' | 'start' | 'end'}`
###### `Transforms.select(editor: Editor, target: Location)`
#### `Transforms.select(editor: Editor, target: Location)`
Set the selection to a new value specified by `target`.
###### `Transforms.deselect(editor: Editor)`
#### `Transforms.deselect(editor: Editor)`
Unset the selection.
###### `Transforms.move(editor: Editor, options?)`
#### `Transforms.move(editor: Editor, options?)`
Move the selection's point forward or backward.
Options: `{distance?: number, unit?: 'offset' | 'character' | 'word' | 'line', reverse?: boolean, edge?: 'anchor' | 'focus' | 'start' | 'end'}`
###### `Transforms.setPoint(editor: Editor, props: Partial<Point>, options?)`
#### `Transforms.setPoint(editor: Editor, props: Partial<Point>, options?)`
Set new properties on one of the selection's points.
Options: `{edge?: 'anchor' | 'focus' | 'start' | 'end'}`
###### `Transforms.setSelection(editor: Editor, props: Partial<Range>)`
#### `Transforms.setSelection(editor: Editor, props: Partial<Range>)`
Set new properties on the selection.
@ -132,13 +132,13 @@ Set new properties on the selection.
Transforms that operate on text.
###### `Transforms.delete(editor: Editor, options?)`
#### `Transforms.delete(editor: Editor, options?)`
Delete text in the document.
Options: `{at?: Location, distance?: number, unit?: 'character' | 'word' | 'line' | 'block', reverse?: boolean, hanging?: boolean, voids?: boolean}`
###### `Transforms.insertText(editor: Editor, text: string, options?)`
#### `Transforms.insertText(editor: Editor, text: string, options?)`
Insert a string of text at the specified location in the document. If no location is specified, insert at the current selection.
@ -146,6 +146,7 @@ Options: `{at?: Location, voids?: boolean}`
### Editor transforms
###### `Transforms.transform(editor: Editor, transform: Transform)`
#### `Transforms.transform(editor: Editor, transform: Transform)`
Transform the `editor` by an `operation`.

View File

@ -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.
@ -110,3 +108,4 @@ export const MyElement = {
```
This makes it easy to reuse domain-specific logic alongside the built-in Slate helpers.

View File

@ -1,14 +1,14 @@
# Nodes: Editor, Elements and Texts
# Nodes
The most important types are the `Node` objects:
- A root-level `Editor` node that contains the 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.
* A root-level `Editor` node that contains the 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 plaintext value:
```js
```javascript
const editor = {
children: [
{
@ -28,10 +28,10 @@ Mirroring the DOM as much as possible is one of Slate's principles. People use t
> 🤖 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)
> * [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 of 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.
@ -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,
@ -140,3 +138,4 @@ const text = {
```
Text nodes too can contain any custom properties you want, and that's how you implement custom formatting like **bold**, _italic_, `code`, etc.

View File

@ -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 },
@ -116,3 +113,4 @@ const editor = {
> 🤖 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

@ -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,
@ -72,3 +72,4 @@ Transforms.insertText(editor, 'A new string of text.', { at: path })
```
The transform helpers are designed to be composed together. So you might use a handful of them for each command.

View File

@ -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],
@ -34,3 +34,4 @@ editor.apply({
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 even undo-able!

View File

@ -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 })) {
// ...
@ -113,3 +111,4 @@ for (const point of Editor.positions(editor)) {
// ...
}
```

View File

@ -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 = {
@ -52,3 +52,4 @@ const MyElement = {
```
Then you can use `MyEditor` and `MyElement` everywhere and have access to all your helpers in one place.

View File

@ -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
```
@ -94,14 +94,14 @@ If you happened to add another overlapping section of `<strike>` to that text, y
Of course, this leaf stuff sounds pretty complex. But, you do not have to think about it much, as long as you use text-level formatting and element-level formatting for their intended purposes:
- Text properties are for **non-contiguous**, character-level formatting.
- Element properties are for **contiguous**, semantic elements in the document.
* Text properties are for **non-contiguous**, character-level formatting.
* Element properties are for **contiguous**, semantic elements in the document.
## Decorations
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.
@ -136,3 +136,4 @@ const Toolbar = () => {
```
Because the `<Toolbar>` uses the `useSlate` hook to retrieve the context, it will re-render whenever the editor changes, so that the active state of the buttons stays in sync.

View File

@ -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',
@ -245,3 +245,4 @@ const fragment = [
```
And just like the serializing function, you can extend it to fit your exact domain model's needs.

View File

@ -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
@ -152,3 +148,4 @@ const withLinks = editor => {
This fix is incorrectly written. It wants to ensure that all `link` elements have a `url` property string. But to fix invalid links it sets the `url` to `null`, which is still not a string!
In this case you'd either want to unwrap the link, removing it entirely. _Or_ expand your validation to accept an "empty" `url == null` as well.

View File

@ -1,24 +1,14 @@
# 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
```typescript
// This example is for an Editor with `ReactEditor` and `HistoryEditor`
import { BaseEditor } from 'slate'
import { ReactEditor } from 'slate-react'
@ -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`.
@ -101,12 +91,13 @@ One workaround for supporting multiple document models is to create each editor
Currently there is also support for extending other types but these haven't been tested as thoroughly as the ones documented above:
- `Selection`
- `Range`
- `Point`
* `Selection`
* `Range`
* `Point`
Feel free to extend these types but extending these types should be considered experimental.
## TypeScript Examples
For some examples of how to use types, see `packages/slate-react/src/custom-types.ts` in the slate repository.

View File

@ -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%)
@ -102,3 +102,4 @@ total 13,807 -> 6,468 (47%)
```
It's quite a big difference! And that doesn't even include the dependencies that were shed in the process too.

File diff suppressed because it is too large Load Diff

View File

@ -2,19 +2,19 @@
Want to contribute to Slate? That would be awesome!
- [Contributing](#contributing)
- [Reporting Bugs](#reporting-bugs)
- [Asking Questions](#asking-questions)
- [Submitting Pull Requests](#submitting-pull-requests)
- [Repository Setup](#repository-setup)
- [Running Examples](#running-examples)
- [Running Tests](#running-tests)
- [Testing Input Methods](#testing-input-methods)
- [Publishing Releases](#publishing-releases)
- [Publishing Normal `@latest` Release](#publishing-normal-latest-release)
- [Publishing `@next` Release](#publishing-next-release)
- [Publishing `@experimental` Release](#publishing-experimental-release)
- [Running Prerelease Script](#running-prerelease-script)
* [Contributing](contributing.md#contributing)
* [Reporting Bugs](contributing.md#reporting-bugs)
* [Asking Questions](contributing.md#asking-questions)
* [Submitting Pull Requests](contributing.md#submitting-pull-requests)
* [Repository Setup](contributing.md#repository-setup)
* [Running Examples](contributing.md#running-examples)
* [Running Tests](contributing.md#running-tests)
* [Testing Input Methods](contributing.md#testing-input-methods)
* [Publishing Releases](contributing.md#publishing-releases)
* [Publishing Normal `@latest` Release](contributing.md#publishing-normal-latest-release)
* [Publishing `@next` Release](contributing.md#publishing-next-release)
* [Publishing `@experimental` Release](contributing.md#publishing-experimental-release)
* [Running Prerelease Script](contributing.md#running-prerelease-script)
## Reporting Bugs
@ -22,19 +22,19 @@ If you run into any weird behavior while using Slate, feel free to open a new is
Any issue you open must include:
- A [JSFiddle](https://jsfiddle.net/01pLxfzu/) that reproduces the bug with a minimal setup.
- A GIF showing the issue in action. (Using something like [RecordIt](http://recordit.co/).)
- A clear explanation of what the issue is.
* A [JSFiddle](https://jsfiddle.net/01pLxfzu/) that reproduces the bug with a minimal setup.
* A GIF showing the issue in action. \(Using something like [RecordIt](http://recordit.co/).\)
* A clear explanation of what the issue is.
Here's a [JSFiddle template for Slate](https://jsfiddle.net/01pLxfzu/) to get you started:
[![](./docs/images/jsfiddle.png)](https://jsfiddle.net/01pLxfzu/)
[![](../.gitbook/assets/jsfiddle.png)](https://jsfiddle.net/01pLxfzu/)
## Asking Questions
We've also got a [Slate Slack team](https://slate-slack.herokuapp.com) where you can ask questions and get answers from other people using Slate:
[![](./docs/images/slack.png)](https://slate-slack.herokuapp.com)
[![](../.gitbook/assets/slack.png)](https://slate-slack.herokuapp.com)
Please use the Slack instead of asking questions in issues, since we want to reserve issues for keeping track of bugs and features. We close questions in issues so that maintaining the project isn't overwhelming.
@ -50,38 +50,38 @@ The slate repository is a monorepo that is managed with [lerna](https://github.c
To run the build, you need to have the Slate repository cloned to your computer. After that, you need to `cd` into the directory where you cloned it, and install the dependencies with `yarn` and build the monorepo:
```shell
```text
yarn install
yarn build
```
## Running Examples
To run the examples, start by building the monorepo as described in the [Repository Setup](#repository-setup) section.
To run the examples, start by building the monorepo as described in the [Repository Setup](contributing.md#repository-setup) section.
Then you can start the examples server with:
```shell
```text
yarn start
```
## Running Tests
To run the tests, start by building the monorepo as described in the [Repository Setup](#repository-setup) section.
To run the tests, start by building the monorepo as described in the [Repository Setup](contributing.md#repository-setup) section.
Then you can rerun the tests with:
```shell
```text
yarn test
```
If you need to debug something, you can add a `debugger` line to the source, and then run `yarn test:inspect`.
If you only want to run a specific test or tests, you can run `yarn test --fgrep="slate-react rendering"` flag which will filter the tests being run by grepping for the string in each test. (This is a Mocha flag that gets passed through.)
If you only want to run a specific test or tests, you can run `yarn test --fgrep="slate-react rendering"` flag which will filter the tests being run by grepping for the string in each test. \(This is a Mocha flag that gets passed through.\)
In addition to tests you should also run the linter:
```shell
```text
yarn lint
```
@ -99,7 +99,7 @@ This will catch TypeScript, Prettier, and Eslint errors.
Since we use [Lerna](https://lerna.js.org) to manage the Slate packages this is fairly easy, just run:
```shell
```text
yarn release:latest
```
@ -111,7 +111,7 @@ Note that this will automatically run the prelease script first that will build,
If we are unsure as to the stability of a release because there are significant changes and/or particularly complex changes, release with the `@next` tag.
```shell
```text
yarn release:next
```
@ -121,7 +121,7 @@ And follow the prompts Lerna gives you.
If you need to create an experimental release to see how a published package will behave during an actual publish, release with the `@experimental` tag. End users should have no expectation that an `@experimental` release will be usable.
```shell
```text
yarn release:experimental
```
@ -129,8 +129,9 @@ yarn release:experimental
If we want to make sure that Slate code follows the preparations for a release but without actually publishing, run:
```shell
```text
yarn prerelease
```
Which will build, test and lint Slate code.

View File

@ -2,22 +2,23 @@
A series of common questions people have about Slate:
- [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)
* [Why is content pasted as plain text?](faq.md#why-is-content-is-pasted-as-plaintext)
* [What can a `Block` node have as its children?](faq.md#what-can-a-block-node-have-as-its-children)
* [What browsers and devices does Slate support?](faq.md#what-browsers-and-devices-does-slate-support)
### Why is content pasted as plain text?
## Why is content pasted as plain text?
One of Slate's core principles is that, unlike most other editors, it does **not** prescribe a specific "schema" to the content you are editing. This means that Slate's core has no concept of "block quotes" or "bold formatting".
For the most part, this leads to increased flexbility without many downsides, but there are certain cases where you have to do a bit more work. Pasting is one of those cases.
Since Slate knows nothing about your domain, it can't know how to parse pasted HTML content (or other content). So, by default whenever a user pastes content into a Slate editor, it will parse it as plain text. If you want it to be smarter about pasted content, you need to override the `insert_data` command and deserialize the `DataTransfer` object's `text/html` data as you wish.
Since Slate knows nothing about your domain, it can't know how to parse pasted HTML content \(or other content\). So, by default whenever a user pastes content into a Slate editor, it will parse it as plain text. If you want it to be smarter about pasted content, you need to override the `insert_data` command and deserialize the `DataTransfer` object's `text/html` data as you wish.
### What browsers and devices does Slate support?
## What browsers and devices does Slate support?
Slate's goal is to support all the modern browsers on both desktop and mobile devices.
However, right now Slate is in beta and is community-driven, so its support is not as robust as it could be. It's currently tested against the latest few versions of Chrome, Edge, Firefox and Safari on desktops. And it does not work in Internet Explorer. On mobile, iOS devices are supported but not regularly tested. Chrome on Android is supported on Slate 0.47 but is not currently supported in Slate 0.50+ though there is currently work being done on one (https://github.com/ianstormtaylor/slate/issues/3786). If you want to add more browser or device support, we'd love for you to submit a pull request! Or in the case of incompatible browsers, build a plugin.
However, right now Slate is in beta and is community-driven, so its support is not as robust as it could be. It's currently tested against the latest few versions of Chrome, Edge, Firefox and Safari on desktops. And it does not work in Internet Explorer. On mobile, iOS devices are supported but not regularly tested. Chrome on Android is supported on Slate 0.47 but is not currently supported in Slate 0.50+ though there is currently work being done on one \([https://github.com/ianstormtaylor/slate/issues/3786](https://github.com/ianstormtaylor/slate/issues/3786)\). If you want to add more browser or device support, we'd love for you to submit a pull request! Or in the case of incompatible browsers, build a plugin.
For older browsers, such as IE11, a lot of the now standard native APIs aren't available. Slate's position on this is that it is up to the user to bring polyfills \(like [https://polyfill.io](https://polyfill.io)\) when needed for things like `el.closest`, etc. Otherwise we'd have to bundle and maintain lots of polyfills that others may not even need in the first place. For clarity, Slate makes no guarantees that it will work with older browsers, even with polyfills and at present, there are still unresolved issues with IE11.
For older browsers, such as IE11, a lot of the now standard native APIs aren't available. Slate's position on this is that it is up to the user to bring polyfills (like https://polyfill.io) when needed for things like `el.closest`, etc. Otherwise we'd have to bundle and maintain lots of polyfills that others may not even need in the first place. For clarity, Slate makes no guarantees that it will work with older browsers, even with polyfills and at present, there are still unresolved issues with IE11.

View File

@ -6,43 +6,44 @@ A few resources that are helpful for building with Slate.
These libraries are helpful when developing with Slate:
- [`is-hotkey`](https://github.com/ianstormtaylor/is-hotkey) is a simple way to check whether an `onKeyDown` handler should fire for a given hotkey, handling cross-platform concerns like <kbd>cmd</kbd> vs. <kbd>ctrl</kbd> keys for you automatically.
* [`is-hotkey`](https://github.com/ianstormtaylor/is-hotkey) is a simple way to check whether an `onKeyDown` handler should fire for a given hotkey, handling cross-platform concerns like cmd vs. ctrl keys for you automatically.
## Products
These products use Slate, and can give you an idea of what's possible:
- [Archbee](https://archbee.io)
- [Cake](https://www.cake.co/)
- [Chatterbug](https://chatterbug.com)
- [Clause](https://clause.io)
- [GitBook](https://www.gitbook.com/)
- [Discord](https://discord.com/)
- [Grafana](https://grafana.com/)
- [GraphCMS](https://graphcms.com)
- [Guilded](https://www.guilded.gg)
- [Guru](https://www.getguru.com/)
- [Kitemaker](https://kitemaker.co)
- [Netlify CMS](https://www.netlifycms.org)
- [Prezly](https://www.prezly.com/)
- [Sanity.io](https://www.sanity.io)
- [Slite](https://slite.com)
- [Taskade](https://www.taskade.com/)
- [TRPG Engine](https://trpg.moonrailgun.com)
- [Yuque](https://www.yuque.com/)
- [Thoughts](https://thoughts.teambition.com)
* [Archbee](https://archbee.io)
* [Cake](https://www.cake.co/)
* [Chatterbug](https://chatterbug.com)
* [Clause](https://clause.io)
* [GitBook](https://www.gitbook.com/)
* [Discord](https://discord.com/)
* [Grafana](https://grafana.com/)
* [GraphCMS](https://graphcms.com)
* [Guilded](https://www.guilded.gg)
* [Guru](https://www.getguru.com/)
* [Kitemaker](https://kitemaker.co)
* [Netlify CMS](https://www.netlifycms.org)
* [Prezly](https://www.prezly.com/)
* [Sanity.io](https://www.sanity.io)
* [Slite](https://slite.com)
* [Taskade](https://www.taskade.com/)
* [TRPG Engine](https://trpg.moonrailgun.com)
* [Yuque](https://www.yuque.com/)
* [Thoughts](https://thoughts.teambition.com)
## Editors
These pre-packaged editors are built on top of Slate, and can be helpful to see how you might structure your code:
- [Accord Project Markdown Editor](https://github.com/accordproject/web-components) is a WYSIWYG editor for [CommonMark](https://commonmark.org/).
- [Canner Editor](https://github.com/Canner/canner-slate-editor) is a rich text editor.
- [Chatterslate](https://github.com/chatterbugapp/chatterslate) helps teach language grammar and more at [Chatterbug](https://chatterbug.com).
- [French Press Editor](https://github.com/roast-cms/french-press-editor) is a customizeable editor with offline support.
- [Nossas Editor](http://slate-editor.bonde.org/) is a drop-in WYSIWYG editor.
- [ORY Editor](https://editor.ory.am/) is a self-contained, inline WYSIWYG editor library.
- [React Force Slate Editor](https://github.com/nareshbhatia/react-force/tree/master/packages/slate-editor) is a light-weight medium-style editor with no editor chrome.
- [Slate Plugins Next](https://github.com/zbeyens/slate-plugins-next) provides an editor with configurable and extendable plugins.
* [Accord Project Markdown Editor](https://github.com/accordproject/web-components) is a WYSIWYG editor for [CommonMark](https://commonmark.org/).
* [Canner Editor](https://github.com/Canner/canner-slate-editor) is a rich text editor.
* [Chatterslate](https://github.com/chatterbugapp/chatterslate) helps teach language grammar and more at [Chatterbug](https://chatterbug.com).
* [French Press Editor](https://github.com/roast-cms/french-press-editor) is a customizeable editor with offline support.
* [Nossas Editor](http://slate-editor.bonde.org/) is a drop-in WYSIWYG editor.
* [ORY Editor](https://editor.ory.am/) is a self-contained, inline WYSIWYG editor library.
* [React Force Slate Editor](https://github.com/nareshbhatia/react-force/tree/master/packages/slate-editor) is a light-weight medium-style editor with no editor chrome.
* [Slate Plugins Next](https://github.com/zbeyens/slate-plugins-next) provides an editor with configurable and extendable plugins.
\(Or, if you have their exact use case, can be a drop-in editor for you.\)
(Or, if you have their exact use case, can be a drop-in editor for you.)

View File

@ -13,3 +13,4 @@ This sub-library tracks changes to the Slate value state over time, and enables
## `withHistory`
The `withHistory` plugin keeps track of the operation history of a Slate editor as operations are applied to it, using undo and redo stacks.

View File

@ -1,3 +1,4 @@
# Slate Hyperscript
This package contains a hyperscript helper for creating Slate documents with JSX!

View File

@ -6,27 +6,27 @@ This sub-library contains the React-specific logic for Slate.
React components for rendering Slate editors
###### `RenderElementProps`
### `RenderElementProps`
`RenderElementProps` are passed to the `renderElement` handler.
###### `RenderLeafProps`
### `RenderLeafProps`
`RenderLeafProps` are passed to the `renderLeaf` handler.
###### `Editable`
### `Editable`
The main Slate editor.
###### `DefaultElement(props: RenderElementProps)`
### `DefaultElement(props: RenderElementProps)`
The default element renderer.
###### `DefaultLeaf(props: RenderLeafProps)`
### `DefaultLeaf(props: RenderLeafProps)`
The default custom leaf renderer.
###### `Slate(editor: ReactEditor, value: Node[], children: React.ReactNode, onChange: (value: Node[]) => void, [key: string]: any)`
### `Slate(editor: ReactEditor, value: Node[], children: React.ReactNode, onChange: (value: Node[]) => void, [key: string]: any)`
A wrapper around the provider to handle `onChange` events, because the editor is a mutable singleton so it won't ever register as "changed" otherwise.
@ -34,23 +34,23 @@ A wrapper around the provider to handle `onChange` events, because the editor is
React hooks for Slate editors
###### `useFocused`
### `useFocused`
Get the current `focused` state of the editor.
###### `useReadOnly`
### `useReadOnly`
Get the current `readOnly` state of the editor.
###### `useSelected`
### `useSelected`
Get the current `selected` state of an element.
###### `useSlate`
### `useSlate`
Get the current editor object from the React context. Re-renders the context whenever changes occur in the editor.
###### `useSlateStatic`
### `useSlateStatic`
Get the current editor object from the React context. A version of useSlate that does not re-render the context. Previously called `useEditor`.
@ -58,71 +58,71 @@ Get the current editor object from the React context. A version of useSlate that
A React and DOM-specific version of the `Editor` interface. All about translating between the DOM and Slate.
###### `findKey(editor: ReactEditor, node: Node)`
### `findKey(editor: ReactEditor, node: Node)`
Find a key for a Slate node.
###### `findPath(editor: ReactEditor, node: Node)`
### `findPath(editor: ReactEditor, node: Node)`
Find the path of Slate node.
###### `isFocused(editor: ReactEditor)`
### `isFocused(editor: ReactEditor)`
Check if the editor is focused.
###### `isReadOnly(editor: ReactEditor)`
### `isReadOnly(editor: ReactEditor)`
Check if the editor is in read-only mode.
###### `blur(editor: ReactEditor)`
### `blur(editor: ReactEditor)`
Blur the editor.
###### `focus(editor: ReactEditor)`
### `focus(editor: ReactEditor)`
Focus the editor.
###### `deselect(editor: ReactEditor)`
### `deselect(editor: ReactEditor)`
Deselect the editor.
###### `hasDOMNode(editor: ReactEditor, target: DOMNode, options: { editable?: boolean } = {})`
### `hasDOMNode(editor: ReactEditor, target: DOMNode, options: { editable?: boolean } = {})`
Check if a DOM node is within the editor.
###### `insertData(editor: ReactEditor, data: DataTransfer)`
### `insertData(editor: ReactEditor, data: DataTransfer)`
Insert data from a `DataTransfer` into the editor.
###### `setFragmentData(editor: ReactEditor, data: DataTransfer)`
### `setFragmentData(editor: ReactEditor, data: DataTransfer)`
Sets data from the currently selected fragment on a `DataTransfer`.
###### `toDOMNode(editor: ReactEditor, node: Node)`
### `toDOMNode(editor: ReactEditor, node: Node)`
Find the native DOM element from a Slate node.
###### `toDOMPoint(editor: ReactEditor, point: Point)`
### `toDOMPoint(editor: ReactEditor, point: Point)`
Find a native DOM selection point from a Slate point.
###### `toDOMRange(editor: ReactEditor, range: Range)`
### `toDOMRange(editor: ReactEditor, range: Range)`
Find a native DOM range from a Slate `range`.
###### `toSlateNode(editor: ReactEditor, domNode: DOMNode)`
### `toSlateNode(editor: ReactEditor, domNode: DOMNode)`
Find a Slate node from a native DOM `element`.
###### `findEventRange(editor: ReactEditor, event: any)`
### `findEventRange(editor: ReactEditor, event: any)`
Get the target range from a DOM `event`.
###### `toSlatePoint(editor: ReactEditor, domPoint: DOMPoint)`
### `toSlatePoint(editor: ReactEditor, domPoint: DOMPoint)`
Find a Slate point from a DOM selection's `domNode` and `domOffset`.
###### `toSlateRange(editor: ReactEditor, domRange: DOMRange | DOMStaticRange | DOMSelection)`
### `toSlateRange(editor: ReactEditor, domRange: DOMRange | DOMStaticRange | DOMSelection)`
Find a Slate range from a DOM range or selection.
@ -130,10 +130,11 @@ Find a Slate range from a DOM range or selection.
React-specific plugins for Slate editors
###### `withReact(editor: Editor)`
### `withReact(editor: Editor)`
Adds React and DOM specific behaviors to the editor.
## Utils
Private convenience modules

View File

@ -2,17 +2,17 @@
Slate is a monorepo divided up into multiple npm packages, so to install it you do:
```
```text
yarn add slate slate-react
```
You'll also need to be sure to install Slate's peer dependencies:
```
```text
yarn add react react-dom
```
_Note, if you'd rather use a pre-bundled version of Slate, you can `yarn add slate` and retrieve the bundled `dist/slate.js` file! Check out the [Using the Bundled Source](./XX-using-the-bundled-source.md) guide for more information._
_Note, if you'd rather use a pre-bundled version of Slate, you can `yarn add slate` and retrieve the bundled `dist/slate.js` file! Check out the_ [_Using the Bundled Source_](xx-using-the-bundled-source.md) _guide for more information._
Once you've installed Slate, you'll need to import it.
@ -47,9 +47,9 @@ const App = () => {
Of course we haven't rendered anything, so you won't see any changes.
> If you are using TypeScript, you will also need to extend the `Editor` with `ReactEditor` as per the documentation on [TypeScript](/concepts/11-typescript). The example below also includes the custom types required for the rest of this example.
> If you are using TypeScript, you will also need to extend the `Editor` with `ReactEditor` as per the documentation on [TypeScript](https://github.com/ianstormtaylor/slate/tree/4b92b752ef234dae57149c713a260b7bacb3f810/concepts/11-typescript/README.md). The example below also includes the custom types required for the rest of this example.
```ts
```typescript
// TypeScript Users only add this code
import { BaseEditor } from 'slate'
import { ReactEditor } from 'slate-react'
@ -154,3 +154,4 @@ const App = () => {
There you have it!
That's the most basic example of Slate. If you render that onto the page, you should see a paragraph with the text `A line of text in a paragraph.` And when you type, you should see the text change!

View File

@ -87,3 +87,4 @@ const App = () => {
With that added, try typing `&`, and you should see it suddenly become `and` instead!
This offers a sense of what can be done with Slate's event handlers. Each one will be called with the `event` object, and you can use your `editor` to perform commands in response. Simple!

View File

@ -1,4 +1,4 @@
# Defining Custom Block Nodes
# Defining Custom Elements
In our previous example, we started with a paragraph, but we never actually told Slate anything about the `paragraph` block type. We just let it use its internal default renderer, which uses a plain old `<div>`.
@ -114,7 +114,7 @@ const DefaultElement = props => {
}
```
Okay, but now we'll need a way for the user to actually turn a block into a code block. So let's change our `onKeyDown` function to add a `` Ctrl-` `` shortcut that does just that:
Okay, but now we'll need a way for the user to actually turn a block into a code block. So let's change our `onKeyDown` function to add a ``Ctrl-``` shortcut that does just that:
```jsx
// Import the `Editor` and `Transforms` helpers from Slate.
@ -172,9 +172,9 @@ const DefaultElement = props => {
}
```
Now, if you press `` Ctrl-` `` the block your cursor is in should turn into a code block! Magic!
Now, if you press ``Ctrl-``` the block your cursor is in should turn into a code block! Magic!
But we forgot one thing. When you hit `` Ctrl-` `` again, it should change the code block back into a paragraph. To do that, we'll need to add a bit of logic to change the type we set based on whether any of the currently selected blocks are already a code block:
But we forgot one thing. When you hit ``Ctrl-``` again, it should change the code block back into a paragraph. To do that, we'll need to add a bit of logic to change the type we set based on whether any of the currently selected blocks are already a code block:
```jsx
const App = () => {
@ -220,4 +220,5 @@ const App = () => {
}
```
And there you have it! If you press `` Ctrl-` `` while inside a code block, it should turn back into a paragraph!
And there you have it! If you press ``Ctrl-``` while inside a code block, it should turn back into a paragraph!

View File

@ -214,3 +214,4 @@ const Leaf = props => {
```
Now, if you try selecting a piece of text and hitting `Ctrl-B` you should see it turn bold! Magic!

View File

@ -1,4 +1,4 @@
# Using Commands
# Executing Commands
Up until now, everything we've learned has been about how to write one-off logic for your specific Slate editor. But one of the most powerful things about Slate is that it lets you model your specific rich text "domain" however you'd like, and write less one-off code.
@ -248,3 +248,4 @@ const App = () => {
That's the benefit of extracting the logic.
And there you have it! We just added a ton of functionality to the editor with very little work. And we can keep all of our command logic tested and isolated in a single place, making the code easier to maintain.

View File

@ -150,3 +150,4 @@ That works! Now you're working with plain text.
You can emulate this strategy for any format you like. You can serialize to HTML, to Markdown, or even to your own custom JSON format that is tailored to your use case.
> 🤖 Note that even though you _can_ serialize your content however you like, there are tradeoffs. The serialization process has a cost itself, and certain formats may be harder to work with than others. In general we recommend writing your own format only if your use case has a specific need for it. Otherwise, you're often better leaving the data in the format Slate uses.

View File

@ -1,18 +1,18 @@
# Using the Bundled Source
For most folks, you'll want to install Slate via `npm`, in which case you can follow the regular [Installing Slate](./01-installing-slate.md) guide.
For most folks, you'll want to install Slate via `npm`, in which case you can follow the regular [Installing Slate](01-installing-slate.md) guide.
But, if you'd rather install Slate by simply adding a `<script>` tag to your application, this guide will help you. To make the "bundled" use case simpler, each version of Slate ships with a bundled source file called `slate.js`.
To get a copy of `slate.js`, download the version of slate you want from npm:
```
```text
npm install slate@latest
```
And then look in the `node_modules` folder for the bundled `slate.js` file:
```
```text
node_modules/
slate/
dist/
@ -24,7 +24,7 @@ A minified version called `slate.min.js` is also included for convenience.
Before you can add `slate.js` to your page, you need to bring your own copy of `react`, `react-dom` and `react-dom-server`, like so:
```html
```markup
<script src="./vendor/react.js"></script>
<script src="./vendor/react-dom.js"></script>
<script src="./vendor/react-dom-server.js"></script>
@ -34,13 +34,13 @@ This ensures that Slate isn't bundling its own copy of React, which would greatl
Then you can add `slate.js` after those includes:
```html
```markup
<script src="./vendor/slate.js"></script>
```
To make things easier, for quick prototyping, you can also use the [`unpkg.com`](https://unpkg.com/#/) delivery network that makes working with bundled npm modules easier. In that case, your includes would look like:
```html
```markup
<script src="https://unpkg.com/react/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/react-dom/umd/react-dom-server.browser.production.min.js"></script>
@ -49,3 +49,4 @@ To make things easier, for quick prototyping, you can also use the [`unpkg.com`]
```
That's it, you're ready to go!