mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-08-09 08:46:35 +02:00
Rename Range
to Leaf
, and Selection
to Range
(#1231)
* rename Range to Leaf * rename Selection to Range * add findDOMRange, findNode, findRange helpers * refactor to remove findDropPoint util * revert findDOMNode to throwing errors * export new helpers, fix linter * update docs * update examples
This commit is contained in:
@@ -29,8 +29,8 @@
|
||||
- [Inline](./reference/slate/inline.md)
|
||||
- [Mark](./reference//slate/mark.md)
|
||||
- [Node](./reference/slate/node.md)
|
||||
- [Range](./reference/slate/range.md)
|
||||
- [Schema](./reference/slate/schema.md)
|
||||
- [Selection](./reference/slate/selection.md)
|
||||
- [State](./reference/slate/state.md)
|
||||
- [Text](./reference/slate/text.md)
|
||||
- [setKeyGenerator](./reference/slate/utils.md)
|
||||
|
@@ -31,4 +31,4 @@ Note that even though the node is "void", it will still have an empty text node
|
||||
|
||||
One constraint of Slate documents is that the leaf nodes are always `Text` nodes. No `Block` or `Inline` node will ever have no children. It will always have at least an empty text node. (However, you can _render_ text-less nodes, see the [Void Nodes](#void-nodes) section above!)
|
||||
|
||||
This constraint means that [`Selections`](../reference/slate/selection.md) can always refer to text nodes, and many text-node-level operations are always "safe" regardless of the tree's structure.
|
||||
This constraint means that [`Ranges`](../reference/slate/range.md) can always refer to text nodes, and many text-node-level operations are always "safe" regardless of the tree's structure.
|
||||
|
@@ -1,7 +1,7 @@
|
||||
|
||||
# The Selection Model
|
||||
|
||||
Slate keeps track of the user's selection in the editor in an immutable data store called a [`Selection`](../reference/slate/selection.md). By doing this, it lets Slate manipulate the selection with changes, but still update it in the DOM on `render`.
|
||||
Slate keeps track of the user's selection in the editor in an immutable data store called a [`Range`](../reference/slate/range.md). By doing this, it lets Slate manipulate the selection with changes, but still update it in the DOM on `render`.
|
||||
|
||||
|
||||
### Always References Text
|
||||
|
@@ -70,6 +70,14 @@ Ensure that a value is a Slate `Inline`.
|
||||
|
||||
Ensure that a value is an immutable `List` of Slate [`Inline`](../slate/inline.md) objects.
|
||||
|
||||
### `leaf`
|
||||
|
||||
Ensure that a value is a Slate `Leaf`.
|
||||
|
||||
### `leaves`
|
||||
|
||||
Ensure that a value is an immutable `List` of Slate [`Leaf`](../slate/leaf.md) objects.
|
||||
|
||||
### `mark`
|
||||
|
||||
Ensure that a value is a Slate `Mark`.
|
||||
@@ -98,10 +106,6 @@ Ensure that a value is an immutable `List` of Slate [`Range`](../slate/range.md)
|
||||
|
||||
Ensure that a value is a Slate `Schema`.
|
||||
|
||||
### `selection`
|
||||
|
||||
Ensure that a value is a Slate `Selection`.
|
||||
|
||||
### `stack`
|
||||
|
||||
Ensure that a value is a Slate `Stack`.
|
||||
|
@@ -85,25 +85,25 @@ This handler is equivalent to the `onCopy` handler. If no other plugin handles t
|
||||
|
||||
This handler is called when the user drops content into the `contenteditable` element. The event is already prevented by default, so you must define a state change to have any affect occur.
|
||||
|
||||
The `data` object is a convenience object created to standardize the drop metadata across browsers. Every data object has a `type` property, which can be one of `text`, `html` or `files`, and a `target` property which is a [`Selection`](../slate/selection.md) indicating where the drop occurred. Depending on the type, its structure will be:
|
||||
The `data` object is a convenience object created to standardize the drop metadata across browsers. Every data object has a `type` property, which can be one of `text`, `html` or `files`, and a `target` property which is a [`Range`](../slate/range.md) indicating where the drop occurred. Depending on the type, its structure will be:
|
||||
|
||||
```js
|
||||
{
|
||||
type: 'text',
|
||||
target: Selection,
|
||||
target: Range,
|
||||
text: String
|
||||
}
|
||||
|
||||
{
|
||||
type: 'html',
|
||||
target: Selection,
|
||||
target: Range,
|
||||
text: String,
|
||||
html: String
|
||||
}
|
||||
|
||||
{
|
||||
type: 'files',
|
||||
target: Selection,
|
||||
target: Range,
|
||||
files: FileList
|
||||
}
|
||||
```
|
||||
@@ -180,7 +180,7 @@ If no other plugin handles this event, it will be handled by the [Core plugin](.
|
||||
|
||||
This handler is called whenever the native DOM selection changes.
|
||||
|
||||
The `data` object contains a [`Selection`](../slate/selection.md) object representing the new selection.
|
||||
The `data` object contains a [`Range`](../slate/range.md) object representing the new selection.
|
||||
|
||||
If no other plugin handles this event, it will be handled by the [Core plugin](./core.md).
|
||||
|
||||
|
@@ -5,12 +5,10 @@
|
||||
import { Change } from 'slate'
|
||||
```
|
||||
|
||||
A change allows you to define a series of changes you'd like to make to the current [`Document`](./document.md) or [`Selection`](./selection.md) in a [`State`](./state.md).
|
||||
A change allows you to define a series of changes you'd like to make to the current [`State`](./state.md).
|
||||
|
||||
All changes are performed through `Change` objects, so that a history of changes can be preserved for use in undo/redo operations, and to make collaborative editing possible.
|
||||
|
||||
Change methods can either operate on the [`Document`](./document.md), the [`Selection`](./selection.md), or both at once.
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
@@ -221,9 +219,9 @@ Move the current selection's offsets to a new `anchorOffset` and `focusOffset`.
|
||||
Move the current selection's anchor point to the start of a `node` and its focus point to the end of the `node`.
|
||||
|
||||
### `select`
|
||||
`select(properties: Selection || Object) => Change`
|
||||
`select(properties: Range || Object) => Change`
|
||||
|
||||
Set the current selection to a selection with merged `properties`. The `properties` can either be a [`Selection`](./selection.md) object or a plain Javascript object of selection properties.
|
||||
Set the current selection to a range with merged `properties`. The `properties` can either be a [`Range`](./range.md) object or a plain Javascript object of selection properties.
|
||||
|
||||
### `selectAll`
|
||||
`selectAll() => Change`
|
||||
@@ -331,112 +329,112 @@ Wrap the given node in a [`Inline`](./inline.md) node that match `properties`. F
|
||||
## Document Changes
|
||||
|
||||
### `deleteBackwardAtRange`
|
||||
`deleteBackwardAtRange(range: Selection, n: Number) => Change`
|
||||
`deleteBackwardAtRange(range: Range, n: Number) => Change`
|
||||
|
||||
Delete backward `n` characters at a `range`. If the `range` is expanded, this method is equivalent to a regular [`delete()`](#delete). `n` defaults to `1`.
|
||||
|
||||
### `deleteForwardAtRange`
|
||||
`deleteForwardAtRange(range: Selection, n: Number) => Change`
|
||||
`deleteForwardAtRange(range: Range, n: Number) => Change`
|
||||
|
||||
Delete forward `n` characters at a `range`. If the `range` is expanded, this method is equivalent to a regular [`delete()`](#delete). `n` defaults to `1`.
|
||||
|
||||
### `deleteAtRange`
|
||||
`deleteAtRange(range: Selection, ) => Change`
|
||||
`deleteAtRange(range: Range, ) => Change`
|
||||
|
||||
Delete everything in a `range`.
|
||||
|
||||
### `insertBlockAtRange`
|
||||
`insertBlockAtRange(range: Selection, block: Block) => Change` <br/>
|
||||
`insertBlockAtRange(range: Selection, properties: Object) => Change` <br/>
|
||||
`insertBlockAtRange(range: Selection, type: String) => Change`
|
||||
`insertBlockAtRange(range: Range, block: Block) => Change` <br/>
|
||||
`insertBlockAtRange(range: Range, properties: Object) => Change` <br/>
|
||||
`insertBlockAtRange(range: Range, type: String) => Change`
|
||||
|
||||
Insert a new block at the same level as the leaf block at a `range`, splitting the current block to make room if it is non-empty. If the selection is expanded, it will be deleted first.
|
||||
|
||||
### `insertFragmentAtRange`
|
||||
`insertFragmentAtRange(range: Selection, fragment: Document) => Change`
|
||||
`insertFragmentAtRange(range: Range, fragment: Document) => Change`
|
||||
|
||||
Insert a [`fragment`](./document.md) at a `range`. If the selection is expanded, it will be deleted first.
|
||||
|
||||
### `insertInlineAtRange`
|
||||
`insertInlineAtRange(range: Selection, inline: Inline) => Change` <br/>
|
||||
`insertInlineAtRange(range: Selection, properties: Object) => Change`
|
||||
`insertInlineAtRange(range: Range, inline: Inline) => Change` <br/>
|
||||
`insertInlineAtRange(range: Range, properties: Object) => Change`
|
||||
|
||||
Insert a new inline at a `range`, splitting the text to make room if it is non-empty. If the selection is expanded, it will be deleted first.
|
||||
|
||||
### `insertTextAtRange`
|
||||
`insertTextAtRange(range: Selection, text: String) => Change`
|
||||
`insertTextAtRange(range: Range, text: String) => Change`
|
||||
|
||||
Insert a string of `text` at a `range`. If the selection is expanded, it will be deleted first.
|
||||
|
||||
### `addMarkAtRange`
|
||||
`addMarkAtRange(range: Selection, mark: Mark) => Change` <br/>
|
||||
`addMarkAtRange(range: Selection, properties: Object) => Change` <br/>
|
||||
`addMarkAtRange(range: Selection, type: String) => Change`
|
||||
`addMarkAtRange(range: Range, mark: Mark) => Change` <br/>
|
||||
`addMarkAtRange(range: Range, properties: Object) => Change` <br/>
|
||||
`addMarkAtRange(range: Range, type: String) => Change`
|
||||
|
||||
Add a [`mark`](./mark.md) to the characters in a `range`. For convenience, you can pass a `type` string or `properties` object to implicitly create a [`Mark`](./mark.md) of that type.
|
||||
|
||||
### `setBlockAtRange`
|
||||
`setBlockAtRange(range: Selection, properties: Object) => Change` <br/>
|
||||
`setBlock(range: Selection, type: String) => Change`
|
||||
`setBlockAtRange(range: Range, properties: Object) => Change` <br/>
|
||||
`setBlock(range: Range, type: String) => Change`
|
||||
|
||||
Set the `properties` of the [`Block`](./block.md) in a `range`. For convenience, you can pass a `type` string to set the blocks's type only.
|
||||
|
||||
### `setInlineAtRange`
|
||||
`setInlineAtRange(range: Selection, properties: Object) => Change` <br/>
|
||||
`setInline(range: Selection, type: String) => Change`
|
||||
`setInlineAtRange(range: Range, properties: Object) => Change` <br/>
|
||||
`setInline(range: Range, type: String) => Change`
|
||||
|
||||
Set the `properties` of the [`Inline`](./inline.md) nodes in a `range`. For convenience, you can pass a `type` string to set the inline's type only.
|
||||
|
||||
### `splitBlockAtRange`
|
||||
`splitBlockAtRange(range: Selection, depth: Number) => Change`
|
||||
`splitBlockAtRange(range: Range, depth: Number) => Change`
|
||||
|
||||
Split the [`Block`](./block.md) in a `range` by `depth` levels. If the selection is expanded, it will be deleted first. `depth` defaults to `1`.
|
||||
|
||||
### `splitInlineAtRange`
|
||||
`splitInlineAtRange(range: Selection, depth: Number) => Change`
|
||||
`splitInlineAtRange(range: Range, depth: Number) => Change`
|
||||
|
||||
Split the [`Inline`](./inline.md) node in a `range` by `depth` levels. If the selection is expanded, it will be deleted first. `depth` defaults to `Infinity`.
|
||||
|
||||
### `removeMarkAtRange`
|
||||
`removeMarkAtRange(range: Selection, mark: Mark) => Change` <br/>
|
||||
`removeMarkAtRange(range: Selection, properties: Object) => Change` <br/>
|
||||
`removeMarkAtRange(range: Selection, type: String) => Change`
|
||||
`removeMarkAtRange(range: Range, mark: Mark) => Change` <br/>
|
||||
`removeMarkAtRange(range: Range, properties: Object) => Change` <br/>
|
||||
`removeMarkAtRange(range: Range, type: String) => Change`
|
||||
|
||||
Remove a [`mark`](./mark.md) from the characters in a `range`. For convenience, you can pass a `type` string or `properties` object to implicitly create a [`Mark`](./mark.md) of that type.
|
||||
|
||||
### `toggleMarkAtRange`
|
||||
`toggleMarkAtRange(range: Selection, mark: Mark) => Change` <br/>
|
||||
`toggleMarkAtRange(range: Selection, properties: Object) => Change` <br/>
|
||||
`toggleMarkAtRange(range: Selection, type: String) => Change`
|
||||
`toggleMarkAtRange(range: Range, mark: Mark) => Change` <br/>
|
||||
`toggleMarkAtRange(range: Range, properties: Object) => Change` <br/>
|
||||
`toggleMarkAtRange(range: Range, type: String) => Change`
|
||||
|
||||
Add or remove a [`mark`](./mark.md) from the characters in a `range`, depending on whether any of them already have the mark. For convenience, you can pass a `type` string or `properties` object to implicitly create a [`Mark`](./mark.md) of that type.
|
||||
|
||||
### `unwrapBlockAtRange`
|
||||
`unwrapBlockAtRange(range: Selection, properties: Object) => Change` <br/>
|
||||
`unwrapBlockAtRange(range: Selection, type: String) => Change`
|
||||
`unwrapBlockAtRange(range: Range, properties: Object) => Change` <br/>
|
||||
`unwrapBlockAtRange(range: Range, type: String) => Change`
|
||||
|
||||
Unwrap all [`Block`](./block.md) nodes in a `range` that match `properties`. For convenience, you can pass a `type` string or `properties` object.
|
||||
|
||||
### `unwrapInlineAtRange`
|
||||
`unwrapInlineAtRange(range: Selection, properties: Object) => Change` <br/>
|
||||
`unwrapInlineAtRange(range: Selection, type: String) => Change`
|
||||
`unwrapInlineAtRange(range: Range, properties: Object) => Change` <br/>
|
||||
`unwrapInlineAtRange(range: Range, type: String) => Change`
|
||||
|
||||
Unwrap all [`Inline`](./inline.md) nodes in a `range` that match `properties`. For convenience, you can pass a `type` string or `properties` object.
|
||||
|
||||
### `wrapBlockAtRange`
|
||||
`wrapBlockAtRange(range: Selection, properties: Object) => Change` <br/>
|
||||
`wrapBlockAtRange(range: Selection, type: String) => Change`
|
||||
`wrapBlockAtRange(range: Range, properties: Object) => Change` <br/>
|
||||
`wrapBlockAtRange(range: Range, type: String) => Change`
|
||||
|
||||
Wrap the [`Block`](./block.md) nodes in a `range` with a new [`Block`](./block.md) node with `properties`. For convenience, you can pass a `type` string or `properties` object.
|
||||
|
||||
### `wrapInlineAtRange`
|
||||
`wrapInlineAtRange(range: Selection, properties: Object) => Change` <br/>
|
||||
`wrapInlineAtRange(range: Selection, type: String) => Change`
|
||||
`wrapInlineAtRange(range: Range, properties: Object) => Change` <br/>
|
||||
`wrapInlineAtRange(range: Range, type: String) => Change`
|
||||
|
||||
Wrap the [`Inline`](./inline.md) nodes in a `range` with a new [`Inline`](./inline.md) node with `properties`. For convenience, you can pass a `type` string or `properties` object.
|
||||
|
||||
### `wrapTextAtRange`
|
||||
`wrapTextAtRange(range: Selection, prefix: String, [suffix: String]) => Change`
|
||||
`wrapTextAtRange(range: Range, prefix: String, [suffix: String]) => Change`
|
||||
|
||||
Surround the text in a `range` with `prefix` and `suffix` strings. If the `suffix` is ommitted, the `prefix` will be used instead.
|
||||
|
||||
|
@@ -47,7 +47,7 @@ Deeply filter the descendant nodes of a node by `iterator`.
|
||||
Deeply find a descendant node by `iterator`.
|
||||
|
||||
### `getBlocksAtRange`
|
||||
`getBlocksAtRange(range: Selection) => List`
|
||||
`getBlocksAtRange(range: Range) => List`
|
||||
|
||||
Get all of the bottom-most [`Block`](./block.md) nodes in a `range`.
|
||||
|
||||
@@ -57,7 +57,7 @@ Get all of the bottom-most [`Block`](./block.md) nodes in a `range`.
|
||||
Get all of the bottom-most [`Block`](./block.md) node descendants.
|
||||
|
||||
### `getCharactersAtRange`
|
||||
`getCharactersAtRange(range: Selection) => List`
|
||||
`getCharactersAtRange(range: Range) => List`
|
||||
|
||||
Get a list of all of the [`Characters`](./character.md) in a `range`.
|
||||
|
||||
@@ -97,7 +97,7 @@ Get a descendant node by `key`.
|
||||
Get the first child text node inside a node.
|
||||
|
||||
### `getFragmentAtRange`
|
||||
`getFragmentAtRange(range: Selection) => Document`
|
||||
`getFragmentAtRange(range: Range) => Document`
|
||||
|
||||
Get a document fragment of the nodes in a `range`.
|
||||
|
||||
@@ -127,7 +127,7 @@ Get the furthest inline parent of a node by `key`.
|
||||
Get the furthest ancestor of a node by `key` that has only one child.
|
||||
|
||||
### `getInlinesAtRange`
|
||||
`getInlinesAtRange(range: Selection) => List`
|
||||
`getInlinesAtRange(range: Range) => List`
|
||||
|
||||
Get all of the top-most [`Inline`](./inline.md) nodes in a `range`.
|
||||
|
||||
@@ -137,7 +137,7 @@ Get all of the top-most [`Inline`](./inline.md) nodes in a `range`.
|
||||
Get the last child text node inside a node.
|
||||
|
||||
### `getMarksAtRange`
|
||||
`getMarksAtRange(range: Selection) => Set`
|
||||
`getMarksAtRange(range: Range) => Set`
|
||||
|
||||
Get a set of all of the marks in a `range`.
|
||||
|
||||
@@ -182,7 +182,7 @@ Get the previous [`Text`](./text.md) node before a descendant by `key`.
|
||||
Get the [`Text`](./text.md) node at an `offset`.
|
||||
|
||||
### `getTextsAtRange`
|
||||
`getTextsAtRange(range: Selection) => List`
|
||||
`getTextsAtRange(range: Range) => List`
|
||||
|
||||
Get all of the [`Text`](./text.md) nodes in a `range`.
|
||||
|
||||
|
@@ -103,7 +103,7 @@ Slate schemas are built up of a set of rules. Each of the properties will add ce
|
||||
The `match` property is the only required property of a rule. It determines which objects the rule applies to.
|
||||
|
||||
### `decorate`
|
||||
`Function decorate(node: Node) => List<Selection>|Array<Object>`
|
||||
`Function decorate(node: Node) => List<Range>|Array<Object>`
|
||||
|
||||
```js
|
||||
{
|
||||
@@ -121,7 +121,7 @@ The `match` property is the only required property of a rule. It determines whic
|
||||
}
|
||||
```
|
||||
|
||||
The `decorate` property allows you define a function that will apply extra marks to ranges of text inside a node. It is called with a [`Node`](./node.md). It should return a list of [`Selection`](./selection.md) objects with the desired marks, which will then be added to the text before rendering.
|
||||
The `decorate` property allows you define a function that will apply extra marks to ranges of text inside a node. It is called with a [`Node`](./node.md). It should return a list of [`Range`](./range.md) objects with the desired marks, which will then be added to the text before rendering.
|
||||
|
||||
### `normalize`
|
||||
`Function normalize(change: Change, object: Node, failure: Any) => Change`
|
||||
|
@@ -1,13 +1,13 @@
|
||||
|
||||
# `Selection`
|
||||
# `Range`
|
||||
|
||||
```js
|
||||
import { Selection } from 'slate'
|
||||
import { Range } from 'slate'
|
||||
```
|
||||
|
||||
A selection of a Slate [`Document`](./document.md). Selections in Slate are modeled after the native [DOM Selection API](https://developer.mozilla.org/en-US/docs/Web/API/Selection), using terms like "anchor", "focus" and "collapsed".
|
||||
A range of a Slate [`Document`](./document.md). Ranges in Slate are modeled after a combination of the [DOM Selection API](https://developer.mozilla.org/en-US/docs/Web/API/Selection) and the [DOM Range API](https://developer.mozilla.org/en-US/docs/Web/API/Range), using terms like "anchor", "focus" and "collapsed".
|
||||
|
||||
The "anchor" is the fixed point in a selection, and the "focus" is the non-fixed point, which may move when you move the cursor (eg. when pressing `Shift + Right Arrow`).
|
||||
The "anchor" is the fixed point in a range, and the "focus" is the non-fixed point, which may move when you move the cursor (eg. when pressing `Shift + Right Arrow`).
|
||||
|
||||
Often times, you don't need to specifically know which point is the "anchor" and which is the "focus", and you just need to know which comes first and last in the document. For these cases, there are many convenience equivalent properties and methods referring to the "start" and "end" points.
|
||||
|
||||
@@ -15,7 +15,7 @@ Often times, you don't need to specifically know which point is the "anchor" and
|
||||
## Properties
|
||||
|
||||
```js
|
||||
Selection({
|
||||
Range({
|
||||
anchorKey: String,
|
||||
anchorOffset: Number,
|
||||
focusKey: String,
|
||||
@@ -28,37 +28,37 @@ Selection({
|
||||
### `anchorKey`
|
||||
`String`
|
||||
|
||||
The key of the text node at the selection's anchor point.
|
||||
The key of the text node at the range's anchor point.
|
||||
|
||||
### `anchorOffset`
|
||||
`Number`
|
||||
|
||||
The number of characters from the start of the text node at the selection's anchor point.
|
||||
The number of characters from the start of the text node at the range's anchor point.
|
||||
|
||||
### `focusKey`
|
||||
`String`
|
||||
|
||||
The key of the text node at the selection's focus point.
|
||||
The key of the text node at the range's focus point.
|
||||
|
||||
### `focusOffset`
|
||||
`Number`
|
||||
|
||||
The number of characters from the start of the text node at the selection's focus point.
|
||||
The number of characters from the start of the text node at the range's focus point.
|
||||
|
||||
### `isBackward`
|
||||
`Boolean`
|
||||
|
||||
Whether the selection is backward. A selection is considered "backward" when its focus point references a location earlier in the document than its anchor point.
|
||||
Whether the range is backward. A range is considered "backward" when its focus point references a location earlier in the document than its anchor point.
|
||||
|
||||
### `isFocused`
|
||||
`Boolean`
|
||||
|
||||
Whether the selection currently has focus.
|
||||
Whether the range currently has focus.
|
||||
|
||||
|
||||
## Computed Properties
|
||||
|
||||
These properties aren't supplied when creating a selection, but are instead computed based on the real properties.
|
||||
These properties aren't supplied when creating a range, but are instead computed based on the real properties.
|
||||
|
||||
### `isBlurred`
|
||||
`Boolean`
|
||||
@@ -68,7 +68,7 @@ The opposite of `isFocused`, for convenience.
|
||||
### `isCollapsed`
|
||||
`Boolean`
|
||||
|
||||
Whether the selection is collapsed. A selection is considered "collapsed" when the anchor point and focus point of the selection are the same.
|
||||
Whether the range is collapsed. A range is considered "collapsed" when the anchor point and focus point of the range are the same.
|
||||
|
||||
### `isExpanded`
|
||||
`Boolean`
|
||||
@@ -85,25 +85,25 @@ The opposite of `isBackward`, for convenience.
|
||||
### `endKey`
|
||||
### `endOffset`
|
||||
|
||||
A few convenience properties for accessing the first and last point of the selection. When the selection is forward, `start` refers to the `anchor` point and `end` refers to the `focus` point. And when it's backward they are reversed.
|
||||
A few convenience properties for accessing the first and last point of the range. When the range is forward, `start` refers to the `anchor` point and `end` refers to the `focus` point. And when it's backward they are reversed.
|
||||
|
||||
|
||||
## Static Methods
|
||||
|
||||
### `Selection.create`
|
||||
`Selection.create(properties: Object) => Selection`
|
||||
### `Range.create`
|
||||
`Range.create(properties: Object) => Range`
|
||||
|
||||
Create a new `Selection` instance with `properties`.
|
||||
Create a new `Range` instance with `properties`.
|
||||
|
||||
### `Selection.fromJSON`
|
||||
`Selection.fromJSON(object: Object) => Selection`
|
||||
### `Range.fromJSON`
|
||||
`Range.fromJSON(object: Object) => Range`
|
||||
|
||||
Create a selection from a JSON `object`.
|
||||
Create a range from a JSON `object`.
|
||||
|
||||
### `Selection.isSelection`
|
||||
`Selection.isSelection(maybeSelection: Any) => Boolean`
|
||||
### `Range.isRange`
|
||||
`Range.isRange(maybeRange: Any) => Boolean`
|
||||
|
||||
Returns a boolean if the passed in argument is a `Selection`.
|
||||
Returns a boolean if the passed in argument is a `Range`.
|
||||
|
||||
|
||||
## Instance Methods
|
||||
@@ -111,7 +111,7 @@ Returns a boolean if the passed in argument is a `Selection`.
|
||||
### `toJSON`
|
||||
`toJSON() => Object`
|
||||
|
||||
Returns a JSON representation of the selection.
|
||||
Returns a JSON representation of the range.
|
||||
|
||||
|
||||
## Checking Methods
|
||||
@@ -119,29 +119,29 @@ Returns a JSON representation of the selection.
|
||||
### `has{Edge}AtStartOf`
|
||||
`has{Edge}AtStartOf(node: Node) => Boolean`
|
||||
|
||||
Determine whether a selection has an edge at the start of a `node`. Where `{Edge}` can be one of: `Anchor`, `Focus`, `Start`, `End` or `Edge` (referring to either point).
|
||||
Determine whether a range has an edge at the start of a `node`. Where `{Edge}` can be one of: `Anchor`, `Focus`, `Start`, `End` or `Edge` (referring to either point).
|
||||
|
||||
### `has{Edge}AtEndOf`
|
||||
`has{Edge}AtEndOf(node: Node) => Boolean`
|
||||
|
||||
Determine whether a selection has an edge at the end of a `node`. Where `{Edge}` can be one of: `Anchor`, `Focus`, `Start`, `End` or `Edge` (referring to either point).
|
||||
Determine whether a range has an edge at the end of a `node`. Where `{Edge}` can be one of: `Anchor`, `Focus`, `Start`, `End` or `Edge` (referring to either point).
|
||||
|
||||
### `has{Edge}Between`
|
||||
`has{Edge}Between(node: Node, start: Number, end: Number) => Boolean`
|
||||
|
||||
Determine whether a selection has an edge in a `node` between its `start` and `end` offset. Where `{Edge}` can be one of: `Anchor`, `Focus`, `Start`, `End` or `Edge` (referring to either point).
|
||||
Determine whether a range has an edge in a `node` between its `start` and `end` offset. Where `{Edge}` can be one of: `Anchor`, `Focus`, `Start`, `End` or `Edge` (referring to either point).
|
||||
|
||||
### `has{Edge}In`
|
||||
`has{Edge}In(node: Node) => Boolean`
|
||||
|
||||
Determine whether a selection has an edge inside a `node`. Where `{Edge}` can be one of: `Anchor`, `Focus`, `Start`, `End` or `Edge` (referring to either point).
|
||||
Determine whether a range has an edge inside a `node`. Where `{Edge}` can be one of: `Anchor`, `Focus`, `Start`, `End` or `Edge` (referring to either point).
|
||||
|
||||
### `isAtStartOf`
|
||||
`isAtStartOf(node: Node) => Boolean`
|
||||
|
||||
Determine whether the selection is at the start of a `node`.
|
||||
Determine whether the range is at the start of a `node`.
|
||||
|
||||
### `isAtEndOf`
|
||||
`isAtEndOf(node: Node) => Boolean`
|
||||
|
||||
Determine whether the selection is at the end of a `node`.
|
||||
Determine whether the range is at the end of a `node`.
|
||||
|
@@ -5,11 +5,11 @@
|
||||
import { State } from 'slate'
|
||||
```
|
||||
|
||||
A `State` is the top-level representation of data in Slate, containing both a [`Document`](./document.md) and a [`Selection`](./selection.md). It's what you need to paste into the Slate [`<Editor>`](../slate-react/editor.md) to render something onto the page.
|
||||
A `State` is the top-level representation of data in Slate, containing both a [`Document`](./document.md) and a selection [`Range`](./range.md). It's what you need to pass into the Slate [`<Editor>`](../slate-react/editor.md) to render something onto the page.
|
||||
|
||||
All changes to the document and selection are also performed through the state object, so that they can stay in sync, and be propagated to its internal history of undo/redo state.
|
||||
|
||||
For convenience, in addition to changes, many of the [`Selection`](./selection.md) and [`Document`](./document.md) properties are exposed as proxies on the `State` object.
|
||||
For convenience, in addition to changes, many of the selection and document properties are exposed as proxies on the `State` object.
|
||||
|
||||
|
||||
## Properties
|
||||
@@ -17,7 +17,7 @@ For convenience, in addition to changes, many of the [`Selection`](./selection.m
|
||||
```js
|
||||
State({
|
||||
document: Document,
|
||||
selection: Selection
|
||||
selection: Range
|
||||
})
|
||||
```
|
||||
|
||||
@@ -27,7 +27,7 @@ State({
|
||||
The current document of the state.
|
||||
|
||||
### `selection`
|
||||
`Selection`
|
||||
`Range`
|
||||
|
||||
The current selection of the state.
|
||||
|
||||
@@ -92,9 +92,9 @@ Whether there are undoable snapshots to revert to in the history.
|
||||
|
||||
Whether there are redoable snapshots to revert to in the history.
|
||||
|
||||
## Selection-like Properties
|
||||
## Range-like Properties
|
||||
|
||||
These properties are exact proxies of their [`Selection`](./selection) equivalents.
|
||||
These properties are exact proxies of the selection [`Range`](./range.md) equivalents.
|
||||
|
||||
### `{edge}Key`
|
||||
`String`
|
||||
|
@@ -7,7 +7,7 @@
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
"leaves": [
|
||||
{
|
||||
"text": "With Slate you can build complex block types that have their own embedded content and behaviors, like rendering checkboxes inside check list items!"
|
||||
}
|
||||
@@ -24,7 +24,7 @@
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
"leaves": [
|
||||
{
|
||||
"text": "Slide to the left."
|
||||
}
|
||||
@@ -41,7 +41,7 @@
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
"leaves": [
|
||||
{
|
||||
"text": "Slide to the right."
|
||||
}
|
||||
@@ -58,7 +58,7 @@
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
"leaves": [
|
||||
{
|
||||
"text": "Criss-cross."
|
||||
}
|
||||
@@ -75,7 +75,7 @@
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
"leaves": [
|
||||
{
|
||||
"text": "Criss-cross!"
|
||||
}
|
||||
@@ -92,7 +92,7 @@
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
"leaves": [
|
||||
{
|
||||
"text": "Cha cha real smooth…"
|
||||
}
|
||||
@@ -109,7 +109,7 @@
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
"leaves": [
|
||||
{
|
||||
"text": "Let's go to work!"
|
||||
}
|
||||
@@ -123,7 +123,7 @@
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
"leaves": [
|
||||
{
|
||||
"text": "Try it out for yourself!"
|
||||
}
|
||||
|
@@ -7,7 +7,7 @@
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
"leaves": [
|
||||
{
|
||||
"text": "There are certain behaviors that require rendering dynamic marks on string of text, like rendering code highlighting. For example:"
|
||||
}
|
||||
@@ -28,7 +28,7 @@
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
"leaves": [
|
||||
{
|
||||
"text": "// A simple FizzBuzz implementation."
|
||||
}
|
||||
@@ -42,7 +42,7 @@
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
"leaves": [
|
||||
{
|
||||
"text": "for (var i = 1; i <= 100; i++) {"
|
||||
}
|
||||
@@ -56,7 +56,7 @@
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
"leaves": [
|
||||
{
|
||||
"text": " if (i % 15 == 0) {"
|
||||
}
|
||||
@@ -70,7 +70,7 @@
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
"leaves": [
|
||||
{
|
||||
"text": " console.log('Fizz Buzz');"
|
||||
}
|
||||
@@ -84,7 +84,7 @@
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
"leaves": [
|
||||
{
|
||||
"text": " } else if (i % 5 == 0) {"
|
||||
}
|
||||
@@ -98,7 +98,7 @@
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
"leaves": [
|
||||
{
|
||||
"text": " console.log('Buzz');"
|
||||
}
|
||||
@@ -112,7 +112,7 @@
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
"leaves": [
|
||||
{
|
||||
"text": " } else if (i % 3 == 0) {"
|
||||
}
|
||||
@@ -126,7 +126,7 @@
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
"leaves": [
|
||||
{
|
||||
"text": " console.log('Fizz');"
|
||||
}
|
||||
@@ -140,7 +140,7 @@
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
"leaves": [
|
||||
{
|
||||
"text": " } else {"
|
||||
}
|
||||
@@ -154,7 +154,7 @@
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
"leaves": [
|
||||
{
|
||||
"text": " console.log(i);"
|
||||
}
|
||||
@@ -168,7 +168,7 @@
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
"leaves": [
|
||||
{
|
||||
"text": " }"
|
||||
}
|
||||
@@ -182,7 +182,7 @@
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
"leaves": [
|
||||
{
|
||||
"text": "}"
|
||||
}
|
||||
@@ -198,7 +198,7 @@
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
"leaves": [
|
||||
{
|
||||
"text": "Try it out for yourself!"
|
||||
}
|
||||
|
@@ -7,7 +7,7 @@
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
"leaves": [
|
||||
{
|
||||
"text": "In addition to simple image nodes, you can actually create complex embedded nodes. For example, this one contains an input element that lets you change the video being rendered!"
|
||||
}
|
||||
@@ -29,7 +29,7 @@
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
"leaves": [
|
||||
{
|
||||
"text": "Try it out! If you want another good video URL to try, go with: https://www.youtube.com/embed/6Ejga4kJUts"
|
||||
}
|
||||
|
@@ -7,7 +7,7 @@
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
"leaves": [
|
||||
{
|
||||
"text": "In addition to block nodes, you can create inline void nodes, like "
|
||||
}
|
||||
@@ -23,7 +23,7 @@
|
||||
},
|
||||
{
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
"leaves": [
|
||||
{
|
||||
"text": "!"
|
||||
}
|
||||
@@ -51,7 +51,7 @@
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
"leaves": [
|
||||
{
|
||||
"text": "This example shows emojis in action."
|
||||
}
|
||||
|
@@ -7,7 +7,7 @@
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
"leaves": [
|
||||
{
|
||||
"text": "Enforce Your Layout!"
|
||||
}
|
||||
@@ -21,7 +21,7 @@
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
"leaves": [
|
||||
{
|
||||
"text": "This example shows how to enforce your layout with schema-specific rules. This document will always have a title block at the top and at least one paragraph in the body. Try deleting them and see what happens!"
|
||||
}
|
||||
|
@@ -7,7 +7,7 @@
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
"leaves": [
|
||||
{
|
||||
"text": "This example shows how you can make a hovering menu appear above your content, which you can use to make text "
|
||||
},
|
||||
@@ -43,7 +43,7 @@
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
"leaves": [
|
||||
{
|
||||
"text": "Try it out yourself! Just "
|
||||
},
|
||||
|
@@ -78,7 +78,7 @@ const schema = {
|
||||
*
|
||||
* @param {Change} change
|
||||
* @param {String} src
|
||||
* @param {Selection} target
|
||||
* @param {Range} target
|
||||
*/
|
||||
|
||||
function insertImage(change, src, target) {
|
||||
|
@@ -7,7 +7,7 @@
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
"leaves": [
|
||||
{
|
||||
"text": "In addition to nodes that contain editable text, you can also create other types of nodes, like images or videos."
|
||||
}
|
||||
@@ -29,7 +29,7 @@
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
"leaves": [
|
||||
{
|
||||
"text": "This example shows images in action. It features two ways to add images. You can either add an image via the toolbar icon above, or if you want in on a little secret, copy an image URL to your keyboard and paste it anywhere in the editor!"
|
||||
}
|
||||
|
@@ -7,7 +7,7 @@
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
"leaves": [
|
||||
{
|
||||
"text": "In addition to block nodes, you can create inline nodes, like "
|
||||
}
|
||||
@@ -22,7 +22,7 @@
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
"leaves": [
|
||||
{
|
||||
"text": "hyperlinks"
|
||||
}
|
||||
@@ -32,7 +32,7 @@
|
||||
},
|
||||
{
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
"leaves": [
|
||||
{
|
||||
"text": "!"
|
||||
}
|
||||
@@ -46,7 +46,7 @@
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
"leaves": [
|
||||
{
|
||||
"text": "This example shows hyperlinks in action. It features two ways to add links. You can either add a link via the toolbar icon above, or if you want in on a little secret, copy a URL to your keyboard and paste it while a range of text is selected."
|
||||
}
|
||||
|
@@ -7,7 +7,7 @@
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
"leaves": [
|
||||
{
|
||||
"text": "The editor gives you full control over the logic you can add. For example, it's fairly common to want to add markdown-like shortcuts to editors. So that, when you start a line with \"> \" you get a blockquote that looks like this:"
|
||||
}
|
||||
@@ -21,7 +21,7 @@
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
"leaves": [
|
||||
{
|
||||
"text": "A wise quote."
|
||||
}
|
||||
@@ -35,7 +35,7 @@
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
"leaves": [
|
||||
{
|
||||
"text": "Order when you start a line with \"## \" you get a level-two heading, like this:"
|
||||
}
|
||||
@@ -49,7 +49,7 @@
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
"leaves": [
|
||||
{
|
||||
"text": "Try it out!"
|
||||
}
|
||||
@@ -63,7 +63,7 @@
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
"leaves": [
|
||||
{
|
||||
"text": "Try it out for yourself! Try starting a new line with \">\", \"-\", or \"#\"s."
|
||||
}
|
||||
|
@@ -7,7 +7,7 @@
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
"leaves": [
|
||||
{
|
||||
"text": "By default, pasting content into a Slate editor will use the content's plain text representation. This is fine for some use cases, but sometimes you want to actually be able to paste in content and have it parsed into blocks and links and things. To do this, you need to add a parser that triggers on paste. This is an example of doing exactly that!"
|
||||
}
|
||||
@@ -21,7 +21,7 @@
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
"leaves": [
|
||||
{
|
||||
"text": "Try it out for yourself! Copy and paste some HTML content from another site into this editor."
|
||||
}
|
||||
|
@@ -7,7 +7,7 @@
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
"leaves": [
|
||||
{
|
||||
"text": "This is editable "
|
||||
},
|
||||
@@ -54,7 +54,7 @@
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
"leaves": [
|
||||
{
|
||||
"text": "Since it's rich text, you can do things like turn a selection of text "
|
||||
},
|
||||
@@ -78,7 +78,7 @@
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
"leaves": [
|
||||
{
|
||||
"text": "A wise quote."
|
||||
}
|
||||
@@ -92,7 +92,7 @@
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
"leaves": [
|
||||
{
|
||||
"text": "Try it out for yourself!"
|
||||
}
|
||||
|
@@ -7,7 +7,7 @@
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
"leaves": [
|
||||
{
|
||||
"text": "Slate supports both left-to-right text editing (English, French, etc.) and right-to-left text editing (Arabic, Hebrew, etc.) which it automatically detects. Here's an example featuring excerpts from Khalil Gibran:"
|
||||
}
|
||||
@@ -21,7 +21,7 @@
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
"leaves": [
|
||||
{
|
||||
"text": "Et un jeune dit : parle-nous de l'amitié.\nEt il répondit, disant :\nVotre ami est votre besoin qui a trouvé une réponse.\nIl est le champ que vous semez avec amour et moissonnez avec reconnaissance.\nIl est votre table et votre foyer."
|
||||
}
|
||||
@@ -35,7 +35,7 @@
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
"leaves": [
|
||||
{
|
||||
"text": "ثم قال له شاب: هات حدثناعن الصداقة.\nفأجاب و قال:\nإن صديقك هو كفاية حاجاتك.\nهو حقك الذي تزرعه بالمحبة و تحصده بالشكر.\nهو مائدتك و موقدك."
|
||||
}
|
||||
@@ -49,7 +49,7 @@
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
"leaves": [
|
||||
{
|
||||
"text": "And a youth said, \"Speak to us of Friendship.\"\nYour friend is your needs answered.\nHe is your field which you sow with love and reap with thanksgiving.\nAnd he is your board and your fireside."
|
||||
}
|
||||
@@ -63,7 +63,7 @@
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
"leaves": [
|
||||
{
|
||||
"text": "Try it out for yourself!"
|
||||
}
|
||||
|
@@ -7,7 +7,7 @@
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
"leaves": [
|
||||
{
|
||||
"text": "This is editable text that you can search. As you search, it looks for matching strings of text, and adds \"decoration\" marks to them in realtime."
|
||||
}
|
||||
@@ -21,7 +21,7 @@
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
"leaves": [
|
||||
{
|
||||
"text": "Try it out for yourself by typing in the search box above!"
|
||||
}
|
||||
|
@@ -6,7 +6,7 @@
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
"leaves": [
|
||||
{
|
||||
"text": "The editor gives you full control over the logic you can add. For example, it's fairly common to want to add markdown-like shortcuts to editors. So that, when you start a line with \"> \" you get a blockquote that looks like this:"
|
||||
}
|
||||
@@ -20,7 +20,7 @@
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
"leaves": [
|
||||
{
|
||||
"text": "A wise quote."
|
||||
}
|
||||
@@ -34,7 +34,7 @@
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
"leaves": [
|
||||
{
|
||||
"text": "Order when you start a line with \"## \" you get a level-two heading, like this:"
|
||||
}
|
||||
@@ -48,7 +48,7 @@
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
"leaves": [
|
||||
{
|
||||
"text": "Try it out!"
|
||||
}
|
||||
@@ -62,7 +62,7 @@
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
"leaves": [
|
||||
{
|
||||
"text": "Try it out for yourself! Try starting a new line with \">\", \"-\", or \"#\"s."
|
||||
}
|
||||
|
@@ -7,7 +7,7 @@
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
"leaves": [
|
||||
{
|
||||
"text": "These two editors are kept "
|
||||
},
|
||||
@@ -32,7 +32,7 @@
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
"leaves": [
|
||||
{
|
||||
"text": "They achieve this by sending any document-altering operations to each other whenever a change occurs, and then applying them locally with "
|
||||
},
|
||||
@@ -57,7 +57,7 @@
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
"leaves": [
|
||||
{
|
||||
"text": "Note: ",
|
||||
"marks": [
|
||||
|
@@ -7,7 +7,7 @@
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
"leaves": [
|
||||
{
|
||||
"text": "Since the editor is based on a recursive tree model, similar to an HTML document, you can create complex nested structures, like tables:"
|
||||
}
|
||||
@@ -29,7 +29,7 @@
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
"leaves": [
|
||||
{
|
||||
"text": ""
|
||||
}
|
||||
@@ -43,7 +43,7 @@
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
"leaves": [
|
||||
{
|
||||
"text": "Human",
|
||||
"marks": [
|
||||
@@ -62,7 +62,7 @@
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
"leaves": [
|
||||
{
|
||||
"text": "Dog",
|
||||
"marks": [
|
||||
@@ -81,7 +81,7 @@
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
"leaves": [
|
||||
{
|
||||
"text": "Cat",
|
||||
"marks": [
|
||||
@@ -106,7 +106,7 @@
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
"leaves": [
|
||||
{
|
||||
"text": "# of Feet",
|
||||
"marks": [
|
||||
@@ -125,7 +125,7 @@
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
"leaves": [
|
||||
{
|
||||
"text": "2"
|
||||
}
|
||||
@@ -139,7 +139,7 @@
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
"leaves": [
|
||||
{
|
||||
"text": "4"
|
||||
}
|
||||
@@ -153,7 +153,7 @@
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
"leaves": [
|
||||
{
|
||||
"text": "4"
|
||||
}
|
||||
@@ -173,7 +173,7 @@
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
"leaves": [
|
||||
{
|
||||
"text": "# of Lives",
|
||||
"marks": [
|
||||
@@ -192,7 +192,7 @@
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
"leaves": [
|
||||
{
|
||||
"text": "1"
|
||||
}
|
||||
@@ -206,7 +206,7 @@
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
"leaves": [
|
||||
{
|
||||
"text": "1"
|
||||
}
|
||||
@@ -220,7 +220,7 @@
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
"leaves": [
|
||||
{
|
||||
"text": "9"
|
||||
}
|
||||
@@ -238,7 +238,7 @@
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
"leaves": [
|
||||
{
|
||||
"text": "This table is just a basic example of rendering a table, and it doesn\'t have fancy functionality. But you could augment it to add support for navigating with arrow keys, displaying table headers, adding column and rows, or even formulas if you wanted to get really crazy!"
|
||||
}
|
||||
|
@@ -12,6 +12,19 @@ const IS_DEV = (
|
||||
process.env.NODE_ENV !== 'production'
|
||||
)
|
||||
|
||||
/**
|
||||
* Has console?
|
||||
*
|
||||
* @type {Boolean}
|
||||
*/
|
||||
|
||||
const HAS_CONSOLE = (
|
||||
typeof console != 'undefined' &&
|
||||
typeof console.log == 'function' &&
|
||||
typeof console.warn == 'function' &&
|
||||
typeof console.error == 'function'
|
||||
)
|
||||
|
||||
/**
|
||||
* Log a `message` at `level`.
|
||||
*
|
||||
@@ -25,13 +38,27 @@ function log(level, message, ...args) {
|
||||
return
|
||||
}
|
||||
|
||||
if (typeof console != 'undefined' && typeof console[level] == 'function') {
|
||||
if (HAS_CONSOLE) {
|
||||
console[level](message, ...args)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log a development warning `message`.
|
||||
* Log an error `message`.
|
||||
*
|
||||
* @param {String} message
|
||||
* @param {Any} ...args
|
||||
*/
|
||||
|
||||
|
||||
function error(message, ...args) {
|
||||
if (HAS_CONSOLE) {
|
||||
console.error(message, ...args)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log a warning `message` in development only.
|
||||
*
|
||||
* @param {String} message
|
||||
* @param {Any} ...args
|
||||
@@ -42,7 +69,8 @@ function warn(message, ...args) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Log a deprecation warning `message`, with helpful `version` number.
|
||||
* Log a deprecation warning `message`, with helpful `version` number in
|
||||
* development only.
|
||||
*
|
||||
* @param {String} version
|
||||
* @param {String} message
|
||||
@@ -61,5 +89,6 @@ function deprecate(version, message, ...args) {
|
||||
|
||||
export default {
|
||||
deprecate,
|
||||
error,
|
||||
warn,
|
||||
}
|
||||
|
@@ -30,7 +30,10 @@ const TEXT_RULE = {
|
||||
if (el.tagName && el.tagName.toLowerCase() === 'br') {
|
||||
return {
|
||||
kind: 'text',
|
||||
ranges: [{ text: '\n' }],
|
||||
leaves: [{
|
||||
kind: 'leaf',
|
||||
text: '\n'
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +42,10 @@ const TEXT_RULE = {
|
||||
|
||||
return {
|
||||
kind: 'text',
|
||||
ranges: [{ text: el.value || el.nodeValue }],
|
||||
leaves: [{
|
||||
kind: 'leaf',
|
||||
text: el.value || el.nodeValue
|
||||
}]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -168,9 +174,9 @@ class Html {
|
||||
nodes: [
|
||||
{
|
||||
kind: 'text',
|
||||
ranges: [
|
||||
leaves: [
|
||||
{
|
||||
kind: 'range',
|
||||
kind: 'leaf',
|
||||
text: '',
|
||||
marks: [],
|
||||
}
|
||||
@@ -292,10 +298,10 @@ class Html {
|
||||
}
|
||||
|
||||
else if (node.kind == 'text') {
|
||||
node.ranges = node.ranges.map((range) => {
|
||||
range.marks = range.marks || []
|
||||
range.marks.push({ type, data })
|
||||
return range
|
||||
node.leaves = node.leaves.map((leaf) => {
|
||||
leaf.marks = leaf.marks || []
|
||||
leaf.marks.push({ type, data })
|
||||
return leaf
|
||||
})
|
||||
}
|
||||
|
||||
@@ -342,8 +348,8 @@ class Html {
|
||||
|
||||
serializeNode = (node) => {
|
||||
if (node.kind === 'text') {
|
||||
const ranges = node.getRanges()
|
||||
return ranges.map(this.serializeRange)
|
||||
const leaves = node.getLeaves()
|
||||
return leaves.map(this.serializeLeaf)
|
||||
}
|
||||
|
||||
const children = node.nodes.map(this.serializeNode)
|
||||
@@ -359,17 +365,17 @@ class Html {
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize a `range`.
|
||||
* Serialize a `leaf`.
|
||||
*
|
||||
* @param {Range} range
|
||||
* @param {Leaf} leaf
|
||||
* @return {String}
|
||||
*/
|
||||
|
||||
serializeRange = (range) => {
|
||||
const string = new String({ text: range.text })
|
||||
serializeLeaf = (leaf) => {
|
||||
const string = new String({ text: leaf.text })
|
||||
const text = this.serializeString(string)
|
||||
|
||||
return range.marks.reduce((children, mark) => {
|
||||
return leaf.marks.reduce((children, mark) => {
|
||||
for (let i = 0; i < this.rules.length; i++) {
|
||||
const rule = this.rules[i]
|
||||
if (!rule.serialize) continue
|
||||
|
@@ -33,8 +33,9 @@ export const output = {
|
||||
nodes: [
|
||||
{
|
||||
kind: 'text',
|
||||
ranges: [
|
||||
leaves: [
|
||||
{
|
||||
kind: 'leaf',
|
||||
text: 'one',
|
||||
}
|
||||
]
|
||||
|
@@ -8,7 +8,7 @@ import {
|
||||
Inline,
|
||||
Mark,
|
||||
Node,
|
||||
Selection,
|
||||
Range,
|
||||
State,
|
||||
Text
|
||||
} from 'slate'
|
||||
@@ -71,13 +71,13 @@ const CREATORS = {
|
||||
},
|
||||
|
||||
selection(tagName, attributes, children) {
|
||||
return Selection.create(attributes)
|
||||
return Range.create(attributes)
|
||||
},
|
||||
|
||||
state(tagName, attributes, children) {
|
||||
const { data } = attributes
|
||||
const document = children.find(Document.isDocument)
|
||||
let selection = children.find(Selection.isSelection) || Selection.create()
|
||||
let selection = children.find(Range.isRange) || Range.create()
|
||||
const props = {}
|
||||
|
||||
// Search the document's texts to see if any of them have the anchor or
|
||||
@@ -211,11 +211,11 @@ function createChildren(children, options = {}) {
|
||||
setNode(node.set('key', child.key))
|
||||
}
|
||||
|
||||
child.getRanges().forEach((range) => {
|
||||
let { marks } = range
|
||||
child.getLeaves().forEach((leaf) => {
|
||||
let { marks } = leaf
|
||||
if (options.marks) marks = marks.union(options.marks)
|
||||
setNode(node.insertText(i, range.text, marks))
|
||||
i += range.text.length
|
||||
setNode(node.insertText(i, leaf.text, marks))
|
||||
i += leaf.text.length
|
||||
})
|
||||
|
||||
if (__anchor != null) node.__anchor = __anchor + length
|
||||
|
@@ -47,9 +47,9 @@ function deserialize(string, options = {}) {
|
||||
nodes: [
|
||||
{
|
||||
kind: 'text',
|
||||
ranges: [
|
||||
leaves: [
|
||||
{
|
||||
kind: 'range',
|
||||
kind: 'leaf',
|
||||
text: line,
|
||||
marks: defaultMarks,
|
||||
}
|
||||
|
@@ -17,9 +17,9 @@ export const output = {
|
||||
nodes: [
|
||||
{
|
||||
kind: 'text',
|
||||
ranges: [
|
||||
leaves: [
|
||||
{
|
||||
kind: 'range',
|
||||
kind: 'leaf',
|
||||
text: 'one',
|
||||
marks: [],
|
||||
}
|
||||
|
@@ -7,11 +7,11 @@ import {
|
||||
Document,
|
||||
History,
|
||||
Inline,
|
||||
Leaf,
|
||||
Mark,
|
||||
Node,
|
||||
Range,
|
||||
Schema,
|
||||
Selection,
|
||||
Stack,
|
||||
State,
|
||||
Text,
|
||||
@@ -62,6 +62,8 @@ const Types = {
|
||||
history: create('History', v => History.isHistory(v)),
|
||||
inline: create('Inline', v => Inline.isInline(v)),
|
||||
inlines: create('Inline', v => Inline.isInlineList(v)),
|
||||
leaf: create('Leaf', v => Leaf.isLeaf(v)),
|
||||
leaves: create('List<Leaf>', v => Leaf.isLeafList(v)),
|
||||
mark: create('Mark', v => Mark.isMark(v)),
|
||||
marks: create('Set<Mark>', v => Mark.isMarkSet(v)),
|
||||
node: create('Node', v => Node.isNode(v)),
|
||||
@@ -69,7 +71,6 @@ const Types = {
|
||||
range: create('Range', v => Range.isRange(v)),
|
||||
ranges: create('List<Range>', v => Range.isRangeList(v)),
|
||||
schema: create('Schema', v => Schema.isSchema(v)),
|
||||
selection: create('Selection', v => Selection.isSelection(v)),
|
||||
stack: create('Stack', v => Stack.isStack(v)),
|
||||
state: create('State', v => State.isState(v)),
|
||||
text: create('Text', v => Text.isText(v)),
|
||||
|
@@ -39,7 +39,7 @@ A `Placeholder` component is just a convenience for rendering placeholders on to
|
||||
|
||||
#### Text
|
||||
|
||||
A `Text` component is rendered for each [`Text`](../models#text) model in the document tree. This component handles grouping the characters of the text node into ranges that have the same set of [`Marks`](../models#mark), and then delegates rendering each range to...
|
||||
A `Text` component is rendered for each [`Text`](../models#text) model in the document tree. This component handles grouping the characters of the text node into leaves that have the same set of [`Marks`](../models#mark), and then delegates rendering each leaf to...
|
||||
|
||||
|
||||
#### Void
|
||||
|
@@ -6,15 +6,15 @@ import SlateTypes from 'slate-prop-types'
|
||||
import Types from 'prop-types'
|
||||
import getWindow from 'get-window'
|
||||
import keycode from 'keycode'
|
||||
import { Selection } from 'slate'
|
||||
import logger from 'slate-dev-logger'
|
||||
|
||||
import TRANSFER_TYPES from '../constants/transfer-types'
|
||||
import Node from './node'
|
||||
import extendSelection from '../utils/extend-selection'
|
||||
import findClosestNode from '../utils/find-closest-node'
|
||||
import findDropPoint from '../utils/find-drop-point'
|
||||
import findNativePoint from '../utils/find-native-point'
|
||||
import findDOMNode from '../utils/find-dom-node'
|
||||
import findDOMRange from '../utils/find-dom-range'
|
||||
import findPoint from '../utils/find-point'
|
||||
import findRange from '../utils/find-range'
|
||||
import getHtmlFromNativePaste from '../utils/get-html-from-native-paste'
|
||||
import getTransferData from '../utils/get-transfer-data'
|
||||
import scrollToSelection from '../utils/scroll-to-selection'
|
||||
@@ -143,16 +143,20 @@ class Content extends React.Component {
|
||||
if (selection.isUnset) return
|
||||
|
||||
// Otherwise, figure out which DOM nodes should be selected...
|
||||
const { anchorKey, anchorOffset, focusKey, focusOffset, isCollapsed } = selection
|
||||
const anchor = findNativePoint(anchorKey, anchorOffset)
|
||||
const focus = isCollapsed ? anchor : findNativePoint(focusKey, focusOffset)
|
||||
const current = native.getRangeAt(0)
|
||||
const range = findDOMRange(selection)
|
||||
|
||||
// If they are already selected, do nothing.
|
||||
if (!range) {
|
||||
logger.error('Unable to find a native DOM range from the current selection.', { selection })
|
||||
return
|
||||
}
|
||||
|
||||
// If the new range matches the current selection, do nothing.
|
||||
if (
|
||||
anchor.node == native.anchorNode &&
|
||||
anchor.offset == native.anchorOffset &&
|
||||
focus.node == native.focusNode &&
|
||||
focus.offset == native.focusOffset
|
||||
range.startContainer == current.startContainer &&
|
||||
range.startOffset == current.startOffset &&
|
||||
range.endContainer == current.endContainer &&
|
||||
range.endOffset == current.endOffset
|
||||
) {
|
||||
return
|
||||
}
|
||||
@@ -160,11 +164,7 @@ class Content extends React.Component {
|
||||
// Otherwise, set the `isSelecting` flag and update the selection.
|
||||
this.tmp.isSelecting = true
|
||||
native.removeAllRanges()
|
||||
const range = window.document.createRange()
|
||||
range.setStart(anchor.node, anchor.offset)
|
||||
native.addRange(range)
|
||||
if (!isCollapsed) extendSelection(native, focus.node, focus.offset)
|
||||
|
||||
scrollToSelection(native)
|
||||
|
||||
// Then unset the `isSelecting` flag after a delay.
|
||||
@@ -432,19 +432,48 @@ class Content extends React.Component {
|
||||
|
||||
const { state } = this.props
|
||||
const { nativeEvent } = event
|
||||
const { dataTransfer } = nativeEvent
|
||||
const { dataTransfer, x, y } = nativeEvent
|
||||
const data = getTransferData(dataTransfer)
|
||||
const point = findDropPoint(event, state)
|
||||
if (!point) return
|
||||
|
||||
// Resolve a range from the caret position where the drop occured.
|
||||
const window = getWindow(event.target)
|
||||
let range
|
||||
|
||||
// COMPAT: In Firefox, `caretRangeFromPoint` doesn't exist. (2016/07/25)
|
||||
if (window.document.caretRangeFromPoint) {
|
||||
range = window.document.caretRangeFromPoint(x, y)
|
||||
} else {
|
||||
const position = window.document.caretPositionFromPoint(x, y)
|
||||
range = window.document.createRange()
|
||||
range.setStart(position.offsetNode, position.offset)
|
||||
range.setEnd(position.offsetNode, position.offset)
|
||||
}
|
||||
|
||||
// Resolve a Slate range from the DOM range.
|
||||
let selection = findRange(range, state)
|
||||
if (!selection) return
|
||||
|
||||
const { document } = state
|
||||
const node = document.getNode(selection.anchorKey)
|
||||
const parent = document.getParent(node.key)
|
||||
const el = findDOMNode(parent)
|
||||
|
||||
// If the drop target is inside a void node, move it into either the next or
|
||||
// previous node, depending on which side the `x` and `y` coordinates are
|
||||
// closest to.
|
||||
if (parent.isVoid) {
|
||||
const rect = el.getBoundingClientRect()
|
||||
const isPrevious = parent.kind == 'inline'
|
||||
? x - rect.left < rect.left + rect.width - x
|
||||
: y - rect.top < rect.top + rect.height - y
|
||||
|
||||
selection = isPrevious
|
||||
? selection.moveToEndOf(document.getPreviousText(node.key))
|
||||
: selection.moveToStartOf(document.getNextText(node.key))
|
||||
}
|
||||
|
||||
// Add drop-specific information to the data.
|
||||
data.target = Selection.create({
|
||||
anchorKey: point.key,
|
||||
anchorOffset: point.offset,
|
||||
focusKey: point.key,
|
||||
focusOffset: point.offset,
|
||||
isFocused: true
|
||||
})
|
||||
data.target = selection
|
||||
|
||||
// COMPAT: Edge throws "Permission denied" errors when
|
||||
// accessing `dropEffect` or `effectAllowed` (2017/7/12)
|
||||
@@ -484,33 +513,33 @@ class Content extends React.Component {
|
||||
const point = findPoint(anchorNode, anchorOffset, state)
|
||||
if (!point) return
|
||||
|
||||
// Get the text node and range in question.
|
||||
// Get the text node and leaf in question.
|
||||
const { document, selection } = state
|
||||
const node = document.getDescendant(point.key)
|
||||
const ranges = node.getRanges()
|
||||
const leaves = node.getLeaves()
|
||||
let start = 0
|
||||
let end = 0
|
||||
|
||||
const range = ranges.find((r) => {
|
||||
const leaf = leaves.find((r) => {
|
||||
end += r.text.length
|
||||
if (end >= point.offset) return true
|
||||
start = end
|
||||
})
|
||||
|
||||
// Get the text information.
|
||||
const { text } = range
|
||||
const { text } = leaf
|
||||
let { textContent } = anchorNode
|
||||
const block = document.getClosestBlock(node.key)
|
||||
const lastText = block.getLastText()
|
||||
const lastRange = ranges.last()
|
||||
const lastLeaf = leaves.last()
|
||||
const lastChar = textContent.charAt(textContent.length - 1)
|
||||
const isLastText = node == lastText
|
||||
const isLastRange = range == lastRange
|
||||
const isLastLeaf = leaf == lastLeaf
|
||||
|
||||
// COMPAT: If this is the last range, and the DOM text ends in a new line,
|
||||
// COMPAT: If this is the last leaf, and the DOM text ends in a new line,
|
||||
// we will have added another new line in <Leaf>'s render method to account
|
||||
// for browsers collapsing a single trailing new lines, so remove it.
|
||||
if (isLastText && isLastRange && lastChar == '\n') {
|
||||
if (isLastText && isLastLeaf && lastChar == '\n') {
|
||||
textContent = textContent.slice(0, -1)
|
||||
}
|
||||
|
||||
@@ -522,12 +551,12 @@ class Content extends React.Component {
|
||||
const corrected = selection.collapseToEnd().move(delta)
|
||||
const entire = selection.moveAnchorTo(point.key, start).moveFocusTo(point.key, end)
|
||||
|
||||
// Change the current state to have the range's text replaced.
|
||||
// Change the current state to have the leaf's text replaced.
|
||||
editor.change((change) => {
|
||||
change
|
||||
.select(entire)
|
||||
.delete()
|
||||
.insertText(textContent, range.marks)
|
||||
.insertText(textContent, leaf.marks)
|
||||
.select(corrected)
|
||||
})
|
||||
}
|
||||
@@ -687,42 +716,26 @@ class Content extends React.Component {
|
||||
|
||||
// Otherwise, determine the Slate selection from the native one.
|
||||
else {
|
||||
const { anchorNode, anchorOffset, focusNode, focusOffset } = native
|
||||
const anchor = findPoint(anchorNode, anchorOffset, state)
|
||||
const focus = findPoint(focusNode, focusOffset, state)
|
||||
if (!anchor || !focus) return
|
||||
let range = findRange(native, state)
|
||||
if (!range) return
|
||||
|
||||
// There are situations where a select event will fire with a new native
|
||||
// selection that resolves to the same internal position. In those cases
|
||||
// we don't need to trigger any changes, since our internal model is
|
||||
// already up to date, but we do want to update the native selection again
|
||||
// to make sure it is in sync.
|
||||
if (
|
||||
anchor.key == selection.anchorKey &&
|
||||
anchor.offset == selection.anchorOffset &&
|
||||
focus.key == selection.focusKey &&
|
||||
focus.offset == selection.focusOffset &&
|
||||
selection.isFocused
|
||||
) {
|
||||
if (range.equals(selection)) {
|
||||
this.updateSelection()
|
||||
return
|
||||
}
|
||||
|
||||
const properties = {
|
||||
anchorKey: anchor.key,
|
||||
anchorOffset: anchor.offset,
|
||||
focusKey: focus.key,
|
||||
focusOffset: focus.offset,
|
||||
isFocused: true,
|
||||
isBackward: null
|
||||
}
|
||||
|
||||
const anchorText = document.getNode(anchor.key)
|
||||
const focusText = document.getNode(focus.key)
|
||||
const anchorInline = document.getClosestInline(anchor.key)
|
||||
const focusInline = document.getClosestInline(focus.key)
|
||||
const focusBlock = document.getClosestBlock(focus.key)
|
||||
const anchorBlock = document.getClosestBlock(anchor.key)
|
||||
const { anchorKey, anchorOffset, focusKey, focusOffset } = range
|
||||
const anchorText = document.getNode(anchorKey)
|
||||
const focusText = document.getNode(focusKey)
|
||||
const anchorInline = document.getClosestInline(anchorKey)
|
||||
const focusInline = document.getClosestInline(focusKey)
|
||||
const focusBlock = document.getClosestBlock(focusKey)
|
||||
const anchorBlock = document.getClosestBlock(anchorKey)
|
||||
|
||||
// COMPAT: If the anchor point is at the start of a non-void, and the
|
||||
// focus point is inside a void node with an offset that isn't `0`, set
|
||||
@@ -734,12 +747,12 @@ class Content extends React.Component {
|
||||
if (
|
||||
anchorBlock &&
|
||||
!anchorBlock.isVoid &&
|
||||
anchor.offset == 0 &&
|
||||
anchorOffset == 0 &&
|
||||
focusBlock &&
|
||||
focusBlock.isVoid &&
|
||||
focus.offset != 0
|
||||
focusOffset != 0
|
||||
) {
|
||||
properties.focusOffset = 0
|
||||
range = range.set('focusOffset', 0)
|
||||
}
|
||||
|
||||
// COMPAT: If the selection is at the end of a non-void inline node, and
|
||||
@@ -748,32 +761,25 @@ class Content extends React.Component {
|
||||
if (
|
||||
anchorInline &&
|
||||
!anchorInline.isVoid &&
|
||||
anchor.offset == anchorText.text.length
|
||||
anchorOffset == anchorText.text.length
|
||||
) {
|
||||
const block = document.getClosestBlock(anchor.key)
|
||||
const next = block.getNextText(anchor.key)
|
||||
if (next) {
|
||||
properties.anchorKey = next.key
|
||||
properties.anchorOffset = 0
|
||||
}
|
||||
const block = document.getClosestBlock(anchorKey)
|
||||
const next = block.getNextText(anchorKey)
|
||||
if (next) range = range.moveAnchorTo(next.key, 0)
|
||||
}
|
||||
|
||||
if (
|
||||
focusInline &&
|
||||
!focusInline.isVoid &&
|
||||
focus.offset == focusText.text.length
|
||||
focusOffset == focusText.text.length
|
||||
) {
|
||||
const block = document.getClosestBlock(focus.key)
|
||||
const next = block.getNextText(focus.key)
|
||||
if (next) {
|
||||
properties.focusKey = next.key
|
||||
properties.focusOffset = 0
|
||||
}
|
||||
const block = document.getClosestBlock(focusKey)
|
||||
const next = block.getNextText(focusKey)
|
||||
if (next) range = range.moveFocusTo(next.key, 0)
|
||||
}
|
||||
|
||||
data.selection = selection
|
||||
.merge(properties)
|
||||
.normalize(document)
|
||||
range = range.normalize(document)
|
||||
data.selection = range
|
||||
}
|
||||
|
||||
debug('onSelect', { event, data })
|
||||
|
@@ -13,7 +13,7 @@ import { IS_FIREFOX } from '../constants/environment'
|
||||
* @type {Function}
|
||||
*/
|
||||
|
||||
const debug = Debug('slate:leaf')
|
||||
const debug = Debug('slate:leaves')
|
||||
|
||||
/**
|
||||
* Leaf.
|
||||
@@ -33,11 +33,11 @@ class Leaf extends React.Component {
|
||||
block: SlateTypes.block.isRequired,
|
||||
editor: Types.object.isRequired,
|
||||
index: Types.number.isRequired,
|
||||
leaves: SlateTypes.leaves.isRequired,
|
||||
marks: SlateTypes.marks.isRequired,
|
||||
node: SlateTypes.node.isRequired,
|
||||
offset: Types.number.isRequired,
|
||||
parent: SlateTypes.node.isRequired,
|
||||
ranges: SlateTypes.ranges.isRequired,
|
||||
schema: SlateTypes.schema.isRequired,
|
||||
state: SlateTypes.state.isRequired,
|
||||
text: Types.string.isRequired,
|
||||
@@ -139,7 +139,7 @@ class Leaf extends React.Component {
|
||||
*/
|
||||
|
||||
renderText(props) {
|
||||
const { block, node, parent, text, index, ranges } = props
|
||||
const { block, node, parent, text, index, leaves } = props
|
||||
|
||||
// COMPAT: If the text is empty and it's the only child, we need to render a
|
||||
// <br/> to get the block to have the proper height.
|
||||
@@ -160,8 +160,8 @@ class Leaf extends React.Component {
|
||||
const lastText = block.getLastText()
|
||||
const lastChar = text.charAt(text.length - 1)
|
||||
const isLastText = node == lastText
|
||||
const isLastRange = index == ranges.size - 1
|
||||
if (isLastText && isLastRange && lastChar == '\n') return `${text}\n`
|
||||
const isLastLeaf = index == leaves.size - 1
|
||||
if (isLastText && isLastLeaf && lastChar == '\n') return `${text}\n`
|
||||
|
||||
// Otherwise, just return the text.
|
||||
return text
|
||||
|
@@ -119,35 +119,35 @@ class Text extends React.Component {
|
||||
return startsBefore && endsAfter
|
||||
})
|
||||
|
||||
const ranges = node.getRanges(decs)
|
||||
const leaves = node.getLeaves(decs)
|
||||
let offset = 0
|
||||
|
||||
const leaves = ranges.map((range, i) => {
|
||||
const leaf = this.renderLeaf(ranges, range, i, offset)
|
||||
offset += range.text.length
|
||||
return leaf
|
||||
const children = leaves.map((leaf, i) => {
|
||||
const child = this.renderLeaf(leaves, leaf, i, offset)
|
||||
offset += leaf.text.length
|
||||
return child
|
||||
})
|
||||
|
||||
return (
|
||||
<span data-key={key} style={style}>
|
||||
{leaves}
|
||||
{children}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a single leaf node given a `range` and `offset`.
|
||||
* Render a single leaf given a `leaf` and `offset`.
|
||||
*
|
||||
* @param {List<Range>} ranges
|
||||
* @param {Range} range
|
||||
* @param {List<Leaf>} leaves
|
||||
* @param {Leaf} leaf
|
||||
* @param {Number} index
|
||||
* @param {Number} offset
|
||||
* @return {Element} leaf
|
||||
*/
|
||||
|
||||
renderLeaf = (ranges, range, index, offset) => {
|
||||
renderLeaf = (leaves, leaf, index, offset) => {
|
||||
const { block, node, parent, schema, state, editor } = this.props
|
||||
const { text, marks } = range
|
||||
const { text, marks } = leaf
|
||||
|
||||
return (
|
||||
<Leaf
|
||||
@@ -159,7 +159,7 @@ class Text extends React.Component {
|
||||
node={node}
|
||||
offset={offset}
|
||||
parent={parent}
|
||||
ranges={ranges}
|
||||
leaves={leaves}
|
||||
schema={schema}
|
||||
state={state}
|
||||
text={text}
|
||||
|
@@ -1,16 +1,10 @@
|
||||
|
||||
/**
|
||||
* Components.
|
||||
*/
|
||||
|
||||
import Editor from './components/editor'
|
||||
import Placeholder from './components/placeholder'
|
||||
|
||||
/**
|
||||
* Utils.
|
||||
*/
|
||||
|
||||
import findDOMNode from './utils/find-dom-node'
|
||||
import findDOMRange from './utils/find-dom-range'
|
||||
import findNode from './utils/find-node'
|
||||
import findRange from './utils/find-range'
|
||||
|
||||
/**
|
||||
* Export.
|
||||
@@ -22,10 +16,16 @@ export {
|
||||
Editor,
|
||||
Placeholder,
|
||||
findDOMNode,
|
||||
findDOMRange,
|
||||
findNode,
|
||||
findRange,
|
||||
}
|
||||
|
||||
export default {
|
||||
Editor,
|
||||
Placeholder,
|
||||
findDOMNode,
|
||||
findDOMRange,
|
||||
findNode,
|
||||
findRange,
|
||||
}
|
||||
|
@@ -9,7 +9,7 @@ import { Block, Inline, coreSchema } from 'slate'
|
||||
import Content from '../components/content'
|
||||
import Placeholder from '../components/placeholder'
|
||||
import findDOMNode from '../utils/find-dom-node'
|
||||
import findPoint from '../utils/find-point'
|
||||
import findRange from '../utils/find-range'
|
||||
import { IS_CHROME, IS_MAC, IS_SAFARI } from '../constants/environment'
|
||||
|
||||
/**
|
||||
@@ -72,7 +72,6 @@ function Plugin(options = {}) {
|
||||
|
||||
const { state } = change
|
||||
const { selection } = state
|
||||
const { anchorKey, anchorOffset, focusKey, focusOffset } = selection
|
||||
|
||||
// COMPAT: In iOS, when using predictive text suggestions, the native
|
||||
// selection will be changed to span the existing word, so that the word is
|
||||
@@ -81,22 +80,11 @@ function Plugin(options = {}) {
|
||||
// the selection has gotten out of sync, and adjust it if so. (03/18/2017)
|
||||
const window = getWindow(e.target)
|
||||
const native = window.getSelection()
|
||||
const a = findPoint(native.anchorNode, native.anchorOffset, state)
|
||||
const f = findPoint(native.focusNode, native.focusOffset, state)
|
||||
const hasMismatch = a && f && (
|
||||
anchorKey != a.key ||
|
||||
anchorOffset != a.offset ||
|
||||
focusKey != f.key ||
|
||||
focusOffset != f.offset
|
||||
)
|
||||
const range = findRange(native, state)
|
||||
const hasMismatch = range && !range.equals(selection)
|
||||
|
||||
if (hasMismatch) {
|
||||
change.select({
|
||||
anchorKey: a.key,
|
||||
anchorOffset: a.offset,
|
||||
focusKey: f.key,
|
||||
focusOffset: f.offset
|
||||
})
|
||||
change.select(range)
|
||||
}
|
||||
|
||||
change.insertText(e.data)
|
||||
@@ -180,7 +168,8 @@ function Plugin(options = {}) {
|
||||
// the void node's spacer span, to the end of the void node's content.
|
||||
if (isVoid) {
|
||||
const r = range.cloneRange()
|
||||
const node = findDOMNode(isVoidBlock ? endBlock : endInline)
|
||||
const n = isVoidBlock ? endBlock : endInline
|
||||
const node = findDOMNode(n)
|
||||
r.setEndAfter(node)
|
||||
contents = r.cloneContents()
|
||||
attach = contents.childNodes[contents.childNodes.length - 1].firstChild
|
||||
@@ -315,6 +304,7 @@ function Plugin(options = {}) {
|
||||
if (Block.isBlock(node)) {
|
||||
change
|
||||
.select(target)
|
||||
.focus()
|
||||
.insertBlock(node)
|
||||
.removeNodeByKey(node.key)
|
||||
}
|
||||
@@ -322,6 +312,7 @@ function Plugin(options = {}) {
|
||||
if (Inline.isInline(node)) {
|
||||
change
|
||||
.select(target)
|
||||
.focus()
|
||||
.insertInline(node)
|
||||
.removeNodeByKey(node.key)
|
||||
}
|
||||
@@ -360,6 +351,7 @@ function Plugin(options = {}) {
|
||||
|
||||
change
|
||||
.select(target)
|
||||
.focus()
|
||||
.insertFragment(fragment)
|
||||
}
|
||||
|
||||
@@ -379,7 +371,7 @@ function Plugin(options = {}) {
|
||||
const { text, target } = data
|
||||
const { anchorKey } = target
|
||||
|
||||
change.select(target)
|
||||
change.select(target).focus()
|
||||
|
||||
let hasVoidParent = document.hasVoidParent(anchorKey)
|
||||
|
||||
|
@@ -1,53 +0,0 @@
|
||||
|
||||
/**
|
||||
* Extends a DOM `selection` to a given `el` and `offset`.
|
||||
*
|
||||
* COMPAT: In IE11, `selection.extend` doesn't exist natively, so we have to
|
||||
* polyfill it with this. (2017/09/06)
|
||||
*
|
||||
* https://gist.github.com/tyler-johnson/0a3e8818de3f115b2a2dc47468ac0099
|
||||
*
|
||||
* @param {Selection} selection
|
||||
* @param {Element} el
|
||||
* @param {Number} offset
|
||||
* @return {Selection}
|
||||
*/
|
||||
|
||||
function extendSelection(selection, el, offset) {
|
||||
// Use native method whenever possible.
|
||||
if (typeof selection.extend === 'function') {
|
||||
return selection.extend(el, offset)
|
||||
}
|
||||
|
||||
const range = document.createRange()
|
||||
const anchor = document.createRange()
|
||||
const focus = document.createRange()
|
||||
anchor.setStart(selection.anchorNode, selection.anchorOffset)
|
||||
focus.setStart(el, offset)
|
||||
|
||||
const v = focus.compareBoundaryPoints(window.Range.START_TO_START, anchor)
|
||||
|
||||
// If the focus is after the anchor...
|
||||
if (v >= 0) {
|
||||
range.setStart(selection.anchorNode, selection.anchorOffset)
|
||||
range.setEnd(el, offset)
|
||||
}
|
||||
|
||||
// Otherwise, if the anchor if after the focus...
|
||||
else {
|
||||
range.setStart(el, offset)
|
||||
range.setEnd(selection.anchorNode, selection.anchorOffset)
|
||||
}
|
||||
|
||||
selection.removeAllRanges()
|
||||
selection.addRange(range)
|
||||
return selection
|
||||
}
|
||||
|
||||
/**
|
||||
* Export.
|
||||
*
|
||||
* @type {Function}
|
||||
*/
|
||||
|
||||
export default extendSelection
|
@@ -16,7 +16,7 @@ function findDOMNode(key) {
|
||||
const el = window.document.querySelector(`[data-key="${key}"]`)
|
||||
|
||||
if (!el) {
|
||||
throw new Error(`Unable to find a DOM node for "${key}". This is often because of forgetting to add \`props.attributes\` to a component returned from \`renderNode\`.`)
|
||||
throw new Error(`Unable to find a DOM node for "${key}". This is often because of forgetting to add \`props.attributes\` to a custom component.`)
|
||||
}
|
||||
|
||||
return el
|
||||
|
@@ -12,10 +12,8 @@ import findDOMNode from './find-dom-node'
|
||||
* @return {Object}
|
||||
*/
|
||||
|
||||
function findNativePoint(key, offset) {
|
||||
function findDOMPoint(key, offset) {
|
||||
const el = findDOMNode(key)
|
||||
if (!el) return null
|
||||
|
||||
const window = getWindow(el)
|
||||
const iterator = window.document.createNodeIterator(el, NodeFilter.SHOW_TEXT)
|
||||
let start = 0
|
||||
@@ -52,4 +50,4 @@ function findNativePoint(key, offset) {
|
||||
* @type {Function}
|
||||
*/
|
||||
|
||||
export default findNativePoint
|
||||
export default findDOMPoint
|
34
packages/slate-react/src/utils/find-dom-range.js
Normal file
34
packages/slate-react/src/utils/find-dom-range.js
Normal file
@@ -0,0 +1,34 @@
|
||||
|
||||
import getWindow from 'get-window'
|
||||
|
||||
import findDOMPoint from './find-dom-point'
|
||||
|
||||
/**
|
||||
* Find a native DOM range Slate `range`.
|
||||
*
|
||||
* @param {Range} range
|
||||
* @return {Object|Null}
|
||||
*/
|
||||
|
||||
function findDOMRange(range) {
|
||||
const { anchorKey, anchorOffset, focusKey, focusOffset, isBackward, isCollapsed } = range
|
||||
const anchor = findDOMPoint(anchorKey, anchorOffset)
|
||||
const focus = isCollapsed ? anchor : findDOMPoint(focusKey, focusOffset)
|
||||
if (!anchor || !focus) return null
|
||||
|
||||
const window = getWindow(anchor.node)
|
||||
const r = window.document.createRange()
|
||||
const start = isBackward ? focus : anchor
|
||||
const end = isBackward ? anchor : focus
|
||||
r.setStart(start.node, start.offset)
|
||||
r.setEnd(end.node, end.offset)
|
||||
return r
|
||||
}
|
||||
|
||||
/**
|
||||
* Export.
|
||||
*
|
||||
* @type {Function}
|
||||
*/
|
||||
|
||||
export default findDOMRange
|
@@ -1,85 +0,0 @@
|
||||
|
||||
import getWindow from 'get-window'
|
||||
|
||||
import findClosestNode from './find-closest-node'
|
||||
import findPoint from './find-point'
|
||||
|
||||
/**
|
||||
* Find the target point for a drop `event`.
|
||||
*
|
||||
* @param {Event} event
|
||||
* @param {State} state
|
||||
* @return {Object}
|
||||
*/
|
||||
|
||||
function findDropPoint(event, state) {
|
||||
const { document } = state
|
||||
const { nativeEvent, target } = event
|
||||
const { x, y } = nativeEvent
|
||||
|
||||
// Resolve the caret position where the drop occured.
|
||||
const window = getWindow(target)
|
||||
let n, o
|
||||
|
||||
// COMPAT: In Firefox, `caretRangeFromPoint` doesn't exist. (2016/07/25)
|
||||
if (window.document.caretRangeFromPoint) {
|
||||
const range = window.document.caretRangeFromPoint(x, y)
|
||||
n = range.startContainer
|
||||
o = range.startOffset
|
||||
} else {
|
||||
const position = window.document.caretPositionFromPoint(x, y)
|
||||
n = position.offsetNode
|
||||
o = position.offset
|
||||
}
|
||||
|
||||
const nodeEl = findClosestNode(n, '[data-key]')
|
||||
const nodeKey = nodeEl.getAttribute('data-key')
|
||||
const node = document.key === nodeKey ? document : document.getDescendant(nodeKey)
|
||||
|
||||
// If the drop DOM target is inside an inline void node use last position of
|
||||
// the previous sibling text node or first position of the next sibling text
|
||||
// node as Slate target.
|
||||
if (node.isVoid && node.kind === 'inline') {
|
||||
const rect = nodeEl.getBoundingClientRect()
|
||||
const previous = x - rect.left < rect.left + rect.width - x
|
||||
const text = previous ?
|
||||
document.getPreviousSibling(nodeKey) :
|
||||
document.getNextSibling(nodeKey)
|
||||
const key = text.key
|
||||
const offset = previous ? text.characters.size : 0
|
||||
return { key, offset }
|
||||
}
|
||||
|
||||
// If the drop DOM target is inside a block void node use last position of
|
||||
// the previous sibling block node or first position of the next sibling block
|
||||
// node as Slate target.
|
||||
if (node.isVoid) {
|
||||
const rect = nodeEl.getBoundingClientRect()
|
||||
const previous = y - rect.top < rect.top + rect.height - y
|
||||
|
||||
if (previous) {
|
||||
const block = document.getPreviousBlock(nodeKey)
|
||||
const text = block.getLastText()
|
||||
const { key } = text
|
||||
const offset = text.characters.size
|
||||
return { key, offset }
|
||||
}
|
||||
|
||||
const block = document.getNextBlock(nodeKey)
|
||||
const text = block.getLastText()
|
||||
const { key } = text
|
||||
const offset = 0
|
||||
return { key, offset }
|
||||
}
|
||||
|
||||
const point = findPoint(n, o, state)
|
||||
return point
|
||||
}
|
||||
|
||||
/**
|
||||
* Export.
|
||||
*
|
||||
* @type {Function}
|
||||
*/
|
||||
|
||||
export default findDropPoint
|
28
packages/slate-react/src/utils/find-node.js
Normal file
28
packages/slate-react/src/utils/find-node.js
Normal file
@@ -0,0 +1,28 @@
|
||||
|
||||
import findClosestNode from './find-closest-node'
|
||||
|
||||
/**
|
||||
* Find a Slate node from a DOM `element`.
|
||||
*
|
||||
* @param {Element} element
|
||||
* @return {Node|Null}
|
||||
*/
|
||||
|
||||
function findNode(element, state) {
|
||||
const closest = findClosestNode(element, '[data-key]')
|
||||
if (!closest) return null
|
||||
|
||||
const key = closest.getAttribute('data-key')
|
||||
if (!key) return null
|
||||
|
||||
const node = state.document.getNode(key)
|
||||
return node || null
|
||||
}
|
||||
|
||||
/**
|
||||
* Export.
|
||||
*
|
||||
* @type {Function}
|
||||
*/
|
||||
|
||||
export default findNode
|
@@ -2,7 +2,6 @@
|
||||
import getWindow from 'get-window'
|
||||
|
||||
import OffsetKey from './offset-key'
|
||||
import normalizeNodeAndOffset from './normalize-node-and-offset'
|
||||
import findClosestNode from './find-closest-node'
|
||||
|
||||
/**
|
||||
@@ -75,6 +74,87 @@ function findPoint(nativeNode, nativeOffset, state) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* From a DOM selection's `node` and `offset`, normalize so that it always
|
||||
* refers to a text node.
|
||||
*
|
||||
* @param {Element} node
|
||||
* @param {Number} offset
|
||||
* @return {Object}
|
||||
*/
|
||||
|
||||
function normalizeNodeAndOffset(node, offset) {
|
||||
// If it's an element node, its offset refers to the index of its children
|
||||
// including comment nodes, so try to find the right text child node.
|
||||
if (node.nodeType == 1 && node.childNodes.length) {
|
||||
const isLast = offset == node.childNodes.length
|
||||
const direction = isLast ? 'backward' : 'forward'
|
||||
const index = isLast ? offset - 1 : offset
|
||||
node = getEditableChild(node, index, direction)
|
||||
|
||||
// If the node has children, traverse until we have a leaf node. Leaf nodes
|
||||
// can be either text nodes, or other void DOM nodes.
|
||||
while (node.nodeType == 1 && node.childNodes.length) {
|
||||
const i = isLast ? node.childNodes.length - 1 : 0
|
||||
node = getEditableChild(node, i, direction)
|
||||
}
|
||||
|
||||
// Determine the new offset inside the text node.
|
||||
offset = isLast ? node.textContent.length : 0
|
||||
}
|
||||
|
||||
// Return the node and offset.
|
||||
return { node, offset }
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the nearest editable child at `index` in a `parent`, preferring
|
||||
* `direction`.
|
||||
*
|
||||
* @param {Element} parent
|
||||
* @param {Number} index
|
||||
* @param {String} direction ('forward' or 'backward')
|
||||
* @return {Element|Null}
|
||||
*/
|
||||
|
||||
function getEditableChild(parent, index, direction) {
|
||||
const { childNodes } = parent
|
||||
let child = childNodes[index]
|
||||
let i = index
|
||||
let triedForward = false
|
||||
let triedBackward = false
|
||||
|
||||
// While the child is a comment node, or an element node with no children,
|
||||
// keep iterating to find a sibling non-void, non-comment node.
|
||||
while (
|
||||
(child.nodeType == 8) ||
|
||||
(child.nodeType == 1 && child.childNodes.length == 0) ||
|
||||
(child.nodeType == 1 && child.getAttribute('contenteditable') == 'false')
|
||||
) {
|
||||
if (triedForward && triedBackward) break
|
||||
|
||||
if (i >= childNodes.length) {
|
||||
triedForward = true
|
||||
i = index - 1
|
||||
direction = 'backward'
|
||||
continue
|
||||
}
|
||||
|
||||
if (i < 0) {
|
||||
triedBackward = true
|
||||
i = index + 1
|
||||
direction = 'forward'
|
||||
continue
|
||||
}
|
||||
|
||||
child = childNodes[i]
|
||||
if (direction == 'forward') i++
|
||||
if (direction == 'backward') i--
|
||||
}
|
||||
|
||||
return child || null
|
||||
}
|
||||
|
||||
/**
|
||||
* Export.
|
||||
*
|
||||
|
54
packages/slate-react/src/utils/find-range.js
Normal file
54
packages/slate-react/src/utils/find-range.js
Normal file
@@ -0,0 +1,54 @@
|
||||
|
||||
import getWindow from 'get-window'
|
||||
import isBackward from 'selection-is-backward'
|
||||
import { Range } from 'slate'
|
||||
|
||||
import findPoint from './find-point'
|
||||
|
||||
/**
|
||||
* Find a Slate range from a DOM `native` selection.
|
||||
*
|
||||
* @param {Selection} native
|
||||
* @param {State} state
|
||||
* @return {Range}
|
||||
*/
|
||||
|
||||
function findRange(native, state) {
|
||||
const el = native.anchorNode || native.startContainer
|
||||
const window = getWindow(el)
|
||||
|
||||
// If the `native` object is a DOM `Range` object, change it into something
|
||||
// that looks like a DOM `Selection` instead.
|
||||
if (native instanceof window.Range) {
|
||||
native = {
|
||||
anchorNode: native.startContainer,
|
||||
anchorOffset: native.startOffset,
|
||||
focusNode: native.endContainer,
|
||||
focusOffset: native.endOffset,
|
||||
}
|
||||
}
|
||||
|
||||
const { anchorNode, anchorOffset, focusNode, focusOffset, isCollapsed } = native
|
||||
const anchor = findPoint(anchorNode, anchorOffset, state)
|
||||
const focus = isCollapsed ? anchor : findPoint(focusNode, focusOffset, state)
|
||||
if (!anchor || !focus) return null
|
||||
|
||||
const range = Range.create({
|
||||
anchorKey: anchor.key,
|
||||
anchorOffset: anchor.offset,
|
||||
focusKey: focus.key,
|
||||
focusOffset: focus.offset,
|
||||
isBackward: isCollapsed ? false : isBackward(native),
|
||||
isFocused: true,
|
||||
})
|
||||
|
||||
return range
|
||||
}
|
||||
|
||||
/**
|
||||
* Export.
|
||||
*
|
||||
* @type {Function}
|
||||
*/
|
||||
|
||||
export default findRange
|
@@ -13,9 +13,8 @@ import { findDOMNode } from 'react-dom'
|
||||
*/
|
||||
|
||||
function getHtmlFromNativePaste(component, callback) {
|
||||
const el = findDOMNode(component)
|
||||
|
||||
// Create an off-screen clone of the element and give it focus.
|
||||
const el = findDOMNode(component)
|
||||
const clone = el.cloneNode()
|
||||
clone.setAttribute('class', '')
|
||||
clone.setAttribute('style', 'position: fixed; left: -9999px')
|
||||
|
@@ -1,89 +0,0 @@
|
||||
|
||||
/**
|
||||
* From a DOM selection's `node` and `offset`, normalize so that it always
|
||||
* refers to a text node.
|
||||
*
|
||||
* @param {Element} node
|
||||
* @param {Number} offset
|
||||
* @return {Object}
|
||||
*/
|
||||
|
||||
function normalizeNodeAndOffset(node, offset) {
|
||||
// If it's an element node, its offset refers to the index of its children
|
||||
// including comment nodes, so try to find the right text child node.
|
||||
if (node.nodeType == 1 && node.childNodes.length) {
|
||||
const isLast = offset == node.childNodes.length
|
||||
const direction = isLast ? 'backward' : 'forward'
|
||||
const index = isLast ? offset - 1 : offset
|
||||
node = getEditableChild(node, index, direction)
|
||||
|
||||
// If the node has children, traverse until we have a leaf node. Leaf nodes
|
||||
// can be either text nodes, or other void DOM nodes.
|
||||
while (node.nodeType == 1 && node.childNodes.length) {
|
||||
const i = isLast ? node.childNodes.length - 1 : 0
|
||||
node = getEditableChild(node, i, direction)
|
||||
}
|
||||
|
||||
// Determine the new offset inside the text node.
|
||||
offset = isLast ? node.textContent.length : 0
|
||||
}
|
||||
|
||||
// Return the node and offset.
|
||||
return { node, offset }
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the nearest editable child at `index` in a `parent`, preferring
|
||||
* `direction`.
|
||||
*
|
||||
* @param {Element} parent
|
||||
* @param {Number} index
|
||||
* @param {String} direction ('forward' or 'backward')
|
||||
* @return {Element|Null}
|
||||
*/
|
||||
|
||||
function getEditableChild(parent, index, direction) {
|
||||
const { childNodes } = parent
|
||||
let child = childNodes[index]
|
||||
let i = index
|
||||
let triedForward = false
|
||||
let triedBackward = false
|
||||
|
||||
// While the child is a comment node, or an element node with no children,
|
||||
// keep iterating to find a sibling non-void, non-comment node.
|
||||
while (
|
||||
(child.nodeType == 8) ||
|
||||
(child.nodeType == 1 && child.childNodes.length == 0) ||
|
||||
(child.nodeType == 1 && child.getAttribute('contenteditable') == 'false')
|
||||
) {
|
||||
if (triedForward && triedBackward) break
|
||||
|
||||
if (i >= childNodes.length) {
|
||||
triedForward = true
|
||||
i = index - 1
|
||||
direction = 'backward'
|
||||
continue
|
||||
}
|
||||
|
||||
if (i < 0) {
|
||||
triedBackward = true
|
||||
i = index + 1
|
||||
direction = 'forward'
|
||||
continue
|
||||
}
|
||||
|
||||
child = childNodes[i]
|
||||
if (direction == 'forward') i++
|
||||
if (direction == 'backward') i--
|
||||
}
|
||||
|
||||
return child || null
|
||||
}
|
||||
|
||||
/**
|
||||
* Export.
|
||||
*
|
||||
* @type {Function}
|
||||
*/
|
||||
|
||||
export default normalizeNodeAndOffset
|
@@ -1,12 +1,12 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../../../helpers/h'
|
||||
import { Selection } from 'slate'
|
||||
import { Range } from 'slate'
|
||||
|
||||
export default function (simulator) {
|
||||
const { state } = simulator
|
||||
const text = state.document.getTexts().first()
|
||||
const selection = Selection.create().collapseToStartOf(text).move(1).focus()
|
||||
const selection = Range.create().collapseToStartOf(text).move(1).focus()
|
||||
simulator.select(null, { selection })
|
||||
}
|
||||
|
||||
|
@@ -20,7 +20,7 @@ const Changes = {}
|
||||
* Add a new `mark` to the characters at `range`.
|
||||
*
|
||||
* @param {Change} change
|
||||
* @param {Selection} range
|
||||
* @param {Range} range
|
||||
* @param {Mixed} mark
|
||||
* @param {Object} options
|
||||
* @property {Boolean} normalize
|
||||
@@ -52,7 +52,7 @@ Changes.addMarkAtRange = (change, range, mark, options = {}) => {
|
||||
* Delete everything in a `range`.
|
||||
*
|
||||
* @param {Change} change
|
||||
* @param {Selection} range
|
||||
* @param {Range} range
|
||||
* @param {Object} options
|
||||
* @property {Boolean} normalize
|
||||
*/
|
||||
@@ -251,7 +251,7 @@ Changes.deleteAtRange = (change, range, options = {}) => {
|
||||
* Delete backward until the character boundary at a `range`.
|
||||
*
|
||||
* @param {Change} change
|
||||
* @param {Selection} range
|
||||
* @param {Range} range
|
||||
* @param {Object} options
|
||||
* @property {Boolean} normalize
|
||||
*/
|
||||
@@ -272,7 +272,7 @@ Changes.deleteCharBackwardAtRange = (change, range, options) => {
|
||||
* Delete backward until the line boundary at a `range`.
|
||||
*
|
||||
* @param {Change} change
|
||||
* @param {Selection} range
|
||||
* @param {Range} range
|
||||
* @param {Object} options
|
||||
* @property {Boolean} normalize
|
||||
*/
|
||||
@@ -291,7 +291,7 @@ Changes.deleteLineBackwardAtRange = (change, range, options) => {
|
||||
* Delete backward until the word boundary at a `range`.
|
||||
*
|
||||
* @param {Change} change
|
||||
* @param {Selection} range
|
||||
* @param {Range} range
|
||||
* @param {Object} options
|
||||
* @property {Boolean} normalize
|
||||
*/
|
||||
@@ -312,7 +312,7 @@ Changes.deleteWordBackwardAtRange = (change, range, options) => {
|
||||
* Delete backward `n` characters at a `range`.
|
||||
*
|
||||
* @param {Change} change
|
||||
* @param {Selection} range
|
||||
* @param {Range} range
|
||||
* @param {Number} n (optional)
|
||||
* @param {Object} options
|
||||
* @property {Boolean} normalize
|
||||
@@ -437,7 +437,7 @@ Changes.deleteBackwardAtRange = (change, range, n = 1, options = {}) => {
|
||||
* Delete forward until the character boundary at a `range`.
|
||||
*
|
||||
* @param {Change} change
|
||||
* @param {Selection} range
|
||||
* @param {Range} range
|
||||
* @param {Object} options
|
||||
* @property {Boolean} normalize
|
||||
*/
|
||||
@@ -458,7 +458,7 @@ Changes.deleteCharForwardAtRange = (change, range, options) => {
|
||||
* Delete forward until the line boundary at a `range`.
|
||||
*
|
||||
* @param {Change} change
|
||||
* @param {Selection} range
|
||||
* @param {Range} range
|
||||
* @param {Object} options
|
||||
* @property {Boolean} normalize
|
||||
*/
|
||||
@@ -477,7 +477,7 @@ Changes.deleteLineForwardAtRange = (change, range, options) => {
|
||||
* Delete forward until the word boundary at a `range`.
|
||||
*
|
||||
* @param {Change} change
|
||||
* @param {Selection} range
|
||||
* @param {Range} range
|
||||
* @param {Object} options
|
||||
* @property {Boolean} normalize
|
||||
*/
|
||||
@@ -498,7 +498,7 @@ Changes.deleteWordForwardAtRange = (change, range, options) => {
|
||||
* Delete forward `n` characters at a `range`.
|
||||
*
|
||||
* @param {Change} change
|
||||
* @param {Selection} range
|
||||
* @param {Range} range
|
||||
* @param {Number} n (optional)
|
||||
* @param {Object} options
|
||||
* @property {Boolean} normalize
|
||||
@@ -622,7 +622,7 @@ Changes.deleteForwardAtRange = (change, range, n = 1, options = {}) => {
|
||||
* Insert a `block` node at `range`.
|
||||
*
|
||||
* @param {Change} change
|
||||
* @param {Selection} range
|
||||
* @param {Range} range
|
||||
* @param {Block|String|Object} block
|
||||
* @param {Object} options
|
||||
* @property {Boolean} normalize
|
||||
@@ -675,7 +675,7 @@ Changes.insertBlockAtRange = (change, range, block, options = {}) => {
|
||||
* Insert a `fragment` at a `range`.
|
||||
*
|
||||
* @param {Change} change
|
||||
* @param {Selection} range
|
||||
* @param {Range} range
|
||||
* @param {Document} fragment
|
||||
* @param {Object} options
|
||||
* @property {Boolean} normalize
|
||||
@@ -788,7 +788,7 @@ Changes.insertFragmentAtRange = (change, range, fragment, options = {}) => {
|
||||
* Insert an `inline` node at `range`.
|
||||
*
|
||||
* @param {Change} change
|
||||
* @param {Selection} range
|
||||
* @param {Range} range
|
||||
* @param {Inline|String|Object} inline
|
||||
* @param {Object} options
|
||||
* @property {Boolean} normalize
|
||||
@@ -824,7 +824,7 @@ Changes.insertInlineAtRange = (change, range, inline, options = {}) => {
|
||||
* Insert `text` at a `range`, with optional `marks`.
|
||||
*
|
||||
* @param {Change} change
|
||||
* @param {Selection} range
|
||||
* @param {Range} range
|
||||
* @param {String} text
|
||||
* @param {Set<Mark>} marks (optional)
|
||||
* @param {Object} options
|
||||
@@ -856,7 +856,7 @@ Changes.insertTextAtRange = (change, range, text, marks, options = {}) => {
|
||||
* Remove an existing `mark` to the characters at `range`.
|
||||
*
|
||||
* @param {Change} change
|
||||
* @param {Selection} range
|
||||
* @param {Range} range
|
||||
* @param {Mark|String} mark (optional)
|
||||
* @param {Object} options
|
||||
* @property {Boolean} normalize
|
||||
@@ -888,7 +888,7 @@ Changes.removeMarkAtRange = (change, range, mark, options = {}) => {
|
||||
* Set the `properties` of block nodes in a `range`.
|
||||
*
|
||||
* @param {Change} change
|
||||
* @param {Selection} range
|
||||
* @param {Range} range
|
||||
* @param {Object|String} properties
|
||||
* @param {Object} options
|
||||
* @property {Boolean} normalize
|
||||
@@ -909,7 +909,7 @@ Changes.setBlockAtRange = (change, range, properties, options = {}) => {
|
||||
* Set the `properties` of inline nodes in a `range`.
|
||||
*
|
||||
* @param {Change} change
|
||||
* @param {Selection} range
|
||||
* @param {Range} range
|
||||
* @param {Object|String} properties
|
||||
* @param {Object} options
|
||||
* @property {Boolean} normalize
|
||||
@@ -930,7 +930,7 @@ Changes.setInlineAtRange = (change, range, properties, options = {}) => {
|
||||
* Split the block nodes at a `range`, to optional `height`.
|
||||
*
|
||||
* @param {Change} change
|
||||
* @param {Selection} range
|
||||
* @param {Range} range
|
||||
* @param {Number} height (optional)
|
||||
* @param {Object} options
|
||||
* @property {Boolean} normalize
|
||||
@@ -964,7 +964,7 @@ Changes.splitBlockAtRange = (change, range, height = 1, options = {}) => {
|
||||
* Split the inline nodes at a `range`, to optional `height`.
|
||||
*
|
||||
* @param {Change} change
|
||||
* @param {Selection} range
|
||||
* @param {Range} range
|
||||
* @param {Number} height (optional)
|
||||
* @param {Object} options
|
||||
* @property {Boolean} normalize
|
||||
@@ -999,7 +999,7 @@ Changes.splitInlineAtRange = (change, range, height = Infinity, options = {}) =>
|
||||
* it's already there.
|
||||
*
|
||||
* @param {Change} change
|
||||
* @param {Selection} range
|
||||
* @param {Range} range
|
||||
* @param {Mixed} mark
|
||||
* @param {Object} options
|
||||
* @property {Boolean} normalize
|
||||
@@ -1027,7 +1027,7 @@ Changes.toggleMarkAtRange = (change, range, mark, options = {}) => {
|
||||
* Unwrap all of the block nodes in a `range` from a block with `properties`.
|
||||
*
|
||||
* @param {Change} change
|
||||
* @param {Selection} range
|
||||
* @param {Range} range
|
||||
* @param {String|Object} properties
|
||||
* @param {Object} options
|
||||
* @property {Boolean} normalize
|
||||
@@ -1119,7 +1119,7 @@ Changes.unwrapBlockAtRange = (change, range, properties, options = {}) => {
|
||||
* Unwrap the inline nodes in a `range` from an inline with `properties`.
|
||||
*
|
||||
* @param {Change} change
|
||||
* @param {Selection} range
|
||||
* @param {Range} range
|
||||
* @param {String|Object} properties
|
||||
* @param {Object} options
|
||||
* @property {Boolean} normalize
|
||||
@@ -1165,7 +1165,7 @@ Changes.unwrapInlineAtRange = (change, range, properties, options = {}) => {
|
||||
* Wrap all of the blocks in a `range` in a new `block`.
|
||||
*
|
||||
* @param {Change} change
|
||||
* @param {Selection} range
|
||||
* @param {Range} range
|
||||
* @param {Block|Object|String} block
|
||||
* @param {Object} options
|
||||
* @property {Boolean} normalize
|
||||
@@ -1236,7 +1236,7 @@ Changes.wrapBlockAtRange = (change, range, block, options = {}) => {
|
||||
* Wrap the text and inlines in a `range` in a new `inline`.
|
||||
*
|
||||
* @param {Change} change
|
||||
* @param {Selection} range
|
||||
* @param {Range} range
|
||||
* @param {Inline|Object|String} inline
|
||||
* @param {Object} options
|
||||
* @property {Boolean} normalize
|
||||
@@ -1346,7 +1346,7 @@ Changes.wrapInlineAtRange = (change, range, inline, options = {}) => {
|
||||
* Wrap the text in a `range` in a prefix/suffix.
|
||||
*
|
||||
* @param {Change} change
|
||||
* @param {Selection} range
|
||||
* @param {Range} range
|
||||
* @param {String} prefix
|
||||
* @param {String} suffix (optional)
|
||||
* @param {Object} options
|
||||
|
@@ -32,24 +32,24 @@ Changes.addMarkByKey = (change, key, offset, length, mark, options = {}) => {
|
||||
const { document } = state
|
||||
const path = document.getPath(key)
|
||||
const node = document.getNode(key)
|
||||
const ranges = node.getRanges()
|
||||
const leaves = node.getLeaves()
|
||||
|
||||
const operations = []
|
||||
const bx = offset
|
||||
const by = offset + length
|
||||
let o = 0
|
||||
|
||||
ranges.forEach((range) => {
|
||||
leaves.forEach((leaf) => {
|
||||
const ax = o
|
||||
const ay = ax + range.text.length
|
||||
const ay = ax + leaf.text.length
|
||||
|
||||
o += range.text.length
|
||||
o += leaf.text.length
|
||||
|
||||
// If the range doesn't overlap with the operation, continue on.
|
||||
// If the leaf doesn't overlap with the operation, continue on.
|
||||
if (ay < bx || by < ax) return
|
||||
|
||||
// If the range already has the mark, continue on.
|
||||
if (range.marks.has(mark)) return
|
||||
// If the leaf already has the mark, continue on.
|
||||
if (leaf.marks.has(mark)) return
|
||||
|
||||
// Otherwise, determine which offset and characters overlap.
|
||||
const start = Math.max(ax, bx)
|
||||
@@ -241,24 +241,24 @@ Changes.removeMarkByKey = (change, key, offset, length, mark, options = {}) => {
|
||||
const { document } = state
|
||||
const path = document.getPath(key)
|
||||
const node = document.getNode(key)
|
||||
const ranges = node.getRanges()
|
||||
const leaves = node.getLeaves()
|
||||
|
||||
const operations = []
|
||||
const bx = offset
|
||||
const by = offset + length
|
||||
let o = 0
|
||||
|
||||
ranges.forEach((range) => {
|
||||
leaves.forEach((leaf) => {
|
||||
const ax = o
|
||||
const ay = ax + range.text.length
|
||||
const ay = ax + leaf.text.length
|
||||
|
||||
o += range.text.length
|
||||
o += leaf.text.length
|
||||
|
||||
// If the range doesn't overlap with the operation, continue on.
|
||||
// If the leaf doesn't overlap with the operation, continue on.
|
||||
if (ay < bx || by < ax) return
|
||||
|
||||
// If the range already has the mark, continue on.
|
||||
if (!range.marks.has(mark)) return
|
||||
// If the leaf already has the mark, continue on.
|
||||
if (!leaf.marks.has(mark)) return
|
||||
|
||||
// Otherwise, determine which offset and characters overlap.
|
||||
const start = Math.max(ax, bx)
|
||||
@@ -326,7 +326,7 @@ Changes.removeTextByKey = (change, key, offset, length, options = {}) => {
|
||||
const { document } = state
|
||||
const path = document.getPath(key)
|
||||
const node = document.getNode(key)
|
||||
const ranges = node.getRanges()
|
||||
const leaves = node.getLeaves()
|
||||
const { text } = node
|
||||
|
||||
const removals = []
|
||||
@@ -334,13 +334,13 @@ Changes.removeTextByKey = (change, key, offset, length, options = {}) => {
|
||||
const by = offset + length
|
||||
let o = 0
|
||||
|
||||
ranges.forEach((range) => {
|
||||
leaves.forEach((leaf) => {
|
||||
const ax = o
|
||||
const ay = ax + range.text.length
|
||||
const ay = ax + leaf.text.length
|
||||
|
||||
o += range.text.length
|
||||
o += leaf.text.length
|
||||
|
||||
// If the range doesn't overlap with the removal, continue on.
|
||||
// If the leaf doesn't overlap with the removal, continue on.
|
||||
if (ay < bx || by < ax) return
|
||||
|
||||
// Otherwise, determine which offset and characters overlap.
|
||||
@@ -353,7 +353,7 @@ Changes.removeTextByKey = (change, key, offset, length, options = {}) => {
|
||||
path,
|
||||
offset: start,
|
||||
text: string,
|
||||
marks: range.marks,
|
||||
marks: leaf.marks,
|
||||
})
|
||||
})
|
||||
|
||||
|
@@ -3,7 +3,7 @@ import isEmpty from 'is-empty'
|
||||
import logger from 'slate-dev-logger'
|
||||
import pick from 'lodash/pick'
|
||||
|
||||
import Selection from '../models/selection'
|
||||
import Range from '../models/range'
|
||||
|
||||
/**
|
||||
* Changes.
|
||||
@@ -21,7 +21,7 @@ const Changes = {}
|
||||
*/
|
||||
|
||||
Changes.select = (change, properties, options = {}) => {
|
||||
properties = Selection.createProperties(properties)
|
||||
properties = Range.createProperties(properties)
|
||||
|
||||
const { snapshot = false } = options
|
||||
const { state } = change
|
||||
|
@@ -12,10 +12,10 @@ const MODEL_TYPES = {
|
||||
DOCUMENT: '@@__SLATE_DOCUMENT__@@',
|
||||
HISTORY: '@@__SLATE_HISTORY__@@',
|
||||
INLINE: '@@__SLATE_INLINE__@@',
|
||||
LEAF: '@@__SLATE_LEAF__@@',
|
||||
MARK: '@@__SLATE_MARK__@@',
|
||||
RANGE: '@@__SLATE_RANGE__@@',
|
||||
SCHEMA: '@@__SLATE_SCHEMA__@@',
|
||||
SELECTION: '@@__SLATE_SELECTION__@@',
|
||||
STACK: '@@__SLATE_STACK__@@',
|
||||
STATE: '@@__SLATE_STATE__@@',
|
||||
TEXT: '@@__SLATE_TEXT__@@',
|
||||
|
@@ -6,6 +6,7 @@ import Data from './models/data'
|
||||
import Document from './models/document'
|
||||
import History from './models/history'
|
||||
import Inline from './models/inline'
|
||||
import Leaf from './models/leaf'
|
||||
import Mark from './models/mark'
|
||||
import Node from './models/node'
|
||||
import Operations from './operations'
|
||||
@@ -32,6 +33,7 @@ export {
|
||||
Document,
|
||||
History,
|
||||
Inline,
|
||||
Leaf,
|
||||
Mark,
|
||||
Node,
|
||||
Operations,
|
||||
@@ -54,6 +56,7 @@ export default {
|
||||
Document,
|
||||
History,
|
||||
Inline,
|
||||
Leaf,
|
||||
Mark,
|
||||
Node,
|
||||
Operations,
|
||||
|
@@ -9,7 +9,7 @@ This directory contains all of the immutable models that contain the data that p
|
||||
- [Inline](#inline)
|
||||
- [Mark](#mark)
|
||||
- [Node](#node)
|
||||
- [Selection](#selection)
|
||||
- [Range](#range)
|
||||
- [State](#state)
|
||||
- [Text](#text)
|
||||
|
||||
@@ -54,18 +54,18 @@ Marks are the pieces of "formatting" that can be applied to strings of text in t
|
||||
`Node` isn't actually a model that is exposed, but instead it's an interface full of convenience methods that [`Document`](#document), [`Block`](#block), [`Inline`](#inline) all implement.
|
||||
|
||||
|
||||
#### Selection
|
||||
#### Range
|
||||
|
||||
The `Selection` keeps track of where the user's cursor is. It's modeled after the [DOM Selection API](https://developer.mozilla.org/en-US/docs/Web/API/Selection), using terms like "anchor", "focus" and "collapsed".
|
||||
The `Range` represents a fragment of a [`Document`](#document). It's modeled after a combination of the [DOM Selection API](https://developer.mozilla.org/en-US/docs/Web/API/Selection) and the [DOM Range API](https://developer.mozilla.org/en-US/docs/Web/API/Range), using terms like "anchor", "focus" and "collapsed".
|
||||
|
||||
|
||||
#### State
|
||||
|
||||
The `State` is the highest-level model. It is really just a convenient wrapper around a few other models: [`Document`](#document), [`Selection`](#selection), and a `History` which is not publicly exposed.
|
||||
The `State` is the highest-level model. It is really just a convenient wrapper around a few other models: [`Document`](#document), [`Range`](#range), and a `History` which is not publicly exposed.
|
||||
|
||||
Since `State` has knowledge of both the [`Document`](#document) and the [`Selection`](#selection), it provides a handful of convenience methods for updating the both at the same time. For example, when inserting a new content fragment, it inserts the fragment and then moves the selection to the end of the newly inserted content.
|
||||
Since `State` has knowledge of both the [`Document`](#document) and a selection [`Range`](#range), it provides a handful of convenience methods for updating the both at the same time. For example, when inserting a new content fragment, it inserts the fragment and then moves the range to the end of the newly inserted content.
|
||||
|
||||
The `State` is the object that lets you apply "changes" that change the current document or selection. By having them all be applied through the top-level state, it can keep track of changes in the `History`, allowing for undoing and redoing changes.
|
||||
The `State` is the object that lets you apply "changes" that change the current document or range. By having them all be applied through the top-level state, it can keep track of changes in the `History`, allowing for undoing and redoing changes.
|
||||
|
||||
|
||||
#### Text
|
||||
|
184
packages/slate/src/models/leaf.js
Normal file
184
packages/slate/src/models/leaf.js
Normal file
@@ -0,0 +1,184 @@
|
||||
|
||||
import isPlainObject from 'is-plain-object'
|
||||
import { List, Record, Set } from 'immutable'
|
||||
|
||||
import MODEL_TYPES from '../constants/model-types'
|
||||
import Character from './character'
|
||||
import Mark from './mark'
|
||||
|
||||
/**
|
||||
* Default properties.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
|
||||
const DEFAULTS = {
|
||||
marks: new Set(),
|
||||
text: '',
|
||||
}
|
||||
|
||||
/**
|
||||
* Leaf.
|
||||
*
|
||||
* @type {Leaf}
|
||||
*/
|
||||
|
||||
class Leaf extends Record(DEFAULTS) {
|
||||
|
||||
/**
|
||||
* Create a new `Leaf` with `attrs`.
|
||||
*
|
||||
* @param {Object|Leaf} attrs
|
||||
* @return {Leaf}
|
||||
*/
|
||||
|
||||
static create(attrs = {}) {
|
||||
if (Leaf.isLeaf(attrs)) {
|
||||
return attrs
|
||||
}
|
||||
|
||||
if (typeof attrs == 'string') {
|
||||
attrs = { text: attrs }
|
||||
}
|
||||
|
||||
if (isPlainObject(attrs)) {
|
||||
return Leaf.fromJSON(attrs)
|
||||
}
|
||||
|
||||
throw new Error(`\`Leaf.create\` only accepts objects, strings or leaves, but you passed it: ${attrs}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a `Leaf` list from `value`.
|
||||
*
|
||||
* @param {Array<Leaf|Object>|List<Leaf|Object>} value
|
||||
* @return {List<Leaf>}
|
||||
*/
|
||||
|
||||
static createList(value = []) {
|
||||
if (List.isList(value) || Array.isArray(value)) {
|
||||
const list = new List(value.map(Leaf.create))
|
||||
return list
|
||||
}
|
||||
|
||||
throw new Error(`\`Leaf.createList\` only accepts arrays or lists, but you passed it: ${value}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a `Leaf` from a JSON `object`.
|
||||
*
|
||||
* @param {Object} object
|
||||
* @return {Leaf}
|
||||
*/
|
||||
|
||||
static fromJSON(object) {
|
||||
const {
|
||||
text = '',
|
||||
marks = [],
|
||||
} = object
|
||||
|
||||
const leaf = new Leaf({
|
||||
text,
|
||||
marks: new Set(marks.map(Mark.fromJSON)),
|
||||
})
|
||||
|
||||
return leaf
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias `fromJS`.
|
||||
*/
|
||||
|
||||
static fromJS = Leaf.fromJSON
|
||||
|
||||
/**
|
||||
* Check if a `value` is a `Leaf`.
|
||||
*
|
||||
* @param {Any} value
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
static isLeaf(value) {
|
||||
return !!(value && value[MODEL_TYPES.LEAF])
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a `value` is a list of leaves.
|
||||
*
|
||||
* @param {Any} value
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
static isLeafList(value) {
|
||||
return List.isList(value) && value.every(item => Leaf.isLeaf(item))
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the node's kind.
|
||||
*
|
||||
* @return {String}
|
||||
*/
|
||||
|
||||
get kind() {
|
||||
return 'leaf'
|
||||
}
|
||||
|
||||
/**
|
||||
* Return leaf as a list of characters
|
||||
*
|
||||
* @return {List<Character>}
|
||||
*/
|
||||
|
||||
getCharacters() {
|
||||
const { marks } = this
|
||||
const characters = Character.createList(this.text
|
||||
.split('')
|
||||
.map((char) => {
|
||||
return Character.create({
|
||||
text: char,
|
||||
marks
|
||||
})
|
||||
}))
|
||||
|
||||
return characters
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a JSON representation of the leaf.
|
||||
*
|
||||
* @return {Object}
|
||||
*/
|
||||
|
||||
toJSON() {
|
||||
const object = {
|
||||
kind: this.kind,
|
||||
marks: this.marks.toArray().map(m => m.toJSON()),
|
||||
text: this.text,
|
||||
}
|
||||
|
||||
return object
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias `toJS`.
|
||||
*/
|
||||
|
||||
toJS() {
|
||||
return this.toJSON()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach a pseudo-symbol for type checking.
|
||||
*/
|
||||
|
||||
Leaf.prototype[MODEL_TYPES.LEAF] = true
|
||||
|
||||
/**
|
||||
* Export.
|
||||
*
|
||||
* @type {Leaf}
|
||||
*/
|
||||
|
||||
export default Leaf
|
@@ -362,7 +362,7 @@ class Node {
|
||||
/**
|
||||
* Get the leaf block descendants in a `range`.
|
||||
*
|
||||
* @param {Selection} range
|
||||
* @param {Range} range
|
||||
* @return {List<Node>}
|
||||
*/
|
||||
|
||||
@@ -375,7 +375,7 @@ class Node {
|
||||
/**
|
||||
* Get the leaf block descendants in a `range` as an array
|
||||
*
|
||||
* @param {Selection} range
|
||||
* @param {Range} range
|
||||
* @return {Array}
|
||||
*/
|
||||
|
||||
@@ -457,7 +457,7 @@ class Node {
|
||||
/**
|
||||
* Get a list of the characters in a `range`.
|
||||
*
|
||||
* @param {Selection} range
|
||||
* @param {Range} range
|
||||
* @return {List<Character>}
|
||||
*/
|
||||
|
||||
@@ -469,7 +469,7 @@ class Node {
|
||||
/**
|
||||
* Get a list of the characters in a `range` as an array.
|
||||
*
|
||||
* @param {Selection} range
|
||||
* @param {Range} range
|
||||
* @return {Array}
|
||||
*/
|
||||
|
||||
@@ -688,7 +688,7 @@ class Node {
|
||||
/**
|
||||
* Get a fragment of the node at a `range`.
|
||||
*
|
||||
* @param {Selection} range
|
||||
* @param {Range} range
|
||||
* @return {Document}
|
||||
*/
|
||||
|
||||
@@ -865,7 +865,7 @@ class Node {
|
||||
/**
|
||||
* Get the closest inline nodes for each text node in a `range`.
|
||||
*
|
||||
* @param {Selection} range
|
||||
* @param {Range} range
|
||||
* @return {List<Node>}
|
||||
*/
|
||||
|
||||
@@ -878,7 +878,7 @@ class Node {
|
||||
/**
|
||||
* Get the closest inline nodes for each text node in a `range` as an array.
|
||||
*
|
||||
* @param {Selection} range
|
||||
* @param {Range} range
|
||||
* @return {Array}
|
||||
*/
|
||||
|
||||
@@ -1006,7 +1006,7 @@ class Node {
|
||||
/**
|
||||
* Get a set of the marks in a `range`.
|
||||
*
|
||||
* @param {Selection} range
|
||||
* @param {Range} range
|
||||
* @return {Set<Mark>}
|
||||
*/
|
||||
|
||||
@@ -1018,7 +1018,7 @@ class Node {
|
||||
/**
|
||||
* Get a set of the marks in a `range`.
|
||||
*
|
||||
* @param {Selection} range
|
||||
* @param {Range} range
|
||||
* @return {OrderedSet<Mark>}
|
||||
*/
|
||||
|
||||
@@ -1030,7 +1030,7 @@ class Node {
|
||||
/**
|
||||
* Get a set of the active marks in a `range`.
|
||||
*
|
||||
* @param {Selection} range
|
||||
* @param {Range} range
|
||||
* @return {Set<Mark>}
|
||||
*/
|
||||
|
||||
@@ -1042,7 +1042,7 @@ class Node {
|
||||
/**
|
||||
* Get a set of the marks in a `range`, by unioning.
|
||||
*
|
||||
* @param {Selection} range
|
||||
* @param {Range} range
|
||||
* @return {Array}
|
||||
*/
|
||||
|
||||
@@ -1079,7 +1079,7 @@ class Node {
|
||||
/**
|
||||
* Get a set of marks in a `range`, by intersecting.
|
||||
*
|
||||
* @param {Selection} range
|
||||
* @param {Range} range
|
||||
* @return {Array}
|
||||
*/
|
||||
|
||||
@@ -1264,7 +1264,7 @@ class Node {
|
||||
/**
|
||||
* Get the offset from a `range`.
|
||||
*
|
||||
* @param {Selection} range
|
||||
* @param {Range} range
|
||||
* @return {Number}
|
||||
*/
|
||||
|
||||
@@ -1391,7 +1391,7 @@ class Node {
|
||||
* whether the node `isSelected`, to determine whether not finding matches
|
||||
* means everything is selected or nothing is.
|
||||
*
|
||||
* @param {Selection} range
|
||||
* @param {Range} range
|
||||
* @param {Boolean} isSelected
|
||||
* @return {Object|Null}
|
||||
*/
|
||||
@@ -1514,7 +1514,7 @@ class Node {
|
||||
/**
|
||||
* Get all of the text nodes in a `range`.
|
||||
*
|
||||
* @param {Selection} range
|
||||
* @param {Range} range
|
||||
* @return {List<Node>}
|
||||
*/
|
||||
|
||||
@@ -1526,7 +1526,7 @@ class Node {
|
||||
/**
|
||||
* Get all of the text nodes in a `range` as an array.
|
||||
*
|
||||
* @param {Selection} range
|
||||
* @param {Range} range
|
||||
* @return {Array}
|
||||
*/
|
||||
|
||||
@@ -1622,7 +1622,7 @@ class Node {
|
||||
/**
|
||||
* Check whether the node is in a `range`.
|
||||
*
|
||||
* @param {Selection} range
|
||||
* @param {Range} range
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
@@ -2032,7 +2032,7 @@ class Node {
|
||||
/**
|
||||
* Check if the inline nodes are split at a `range`.
|
||||
*
|
||||
* @param {Selection} range
|
||||
* @param {Range} range
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
|
@@ -1,9 +1,9 @@
|
||||
|
||||
import isPlainObject from 'is-plain-object'
|
||||
import logger from 'slate-dev-logger'
|
||||
import { List, Record, Set } from 'immutable'
|
||||
|
||||
import MODEL_TYPES from '../constants/model-types'
|
||||
import Character from './character'
|
||||
import Mark from './mark'
|
||||
|
||||
/**
|
||||
@@ -13,8 +13,13 @@ import Mark from './mark'
|
||||
*/
|
||||
|
||||
const DEFAULTS = {
|
||||
marks: new Set(),
|
||||
text: '',
|
||||
anchorKey: null,
|
||||
anchorOffset: 0,
|
||||
focusKey: null,
|
||||
focusOffset: 0,
|
||||
isBackward: null,
|
||||
isFocused: false,
|
||||
marks: null,
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -37,19 +42,15 @@ class Range extends Record(DEFAULTS) {
|
||||
return attrs
|
||||
}
|
||||
|
||||
if (typeof attrs == 'string') {
|
||||
attrs = { text: attrs }
|
||||
}
|
||||
|
||||
if (isPlainObject(attrs)) {
|
||||
return Range.fromJSON(attrs)
|
||||
}
|
||||
|
||||
throw new Error(`\`Range.create\` only accepts objects, strings or ranges, but you passed it: ${attrs}`)
|
||||
throw new Error(`\`Range.create\` only accepts objects or ranges, but you passed it: ${attrs}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a list of `Ranges` from `value`.
|
||||
* Create a list of `Ranges` from a `value`.
|
||||
*
|
||||
* @param {Array<Range|Object>|List<Range|Object>} value
|
||||
* @return {List<Range>}
|
||||
@@ -64,6 +65,41 @@ class Range extends Record(DEFAULTS) {
|
||||
throw new Error(`\`Range.createList\` only accepts arrays or lists, but you passed it: ${value}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a dictionary of settable range properties from `attrs`.
|
||||
*
|
||||
* @param {Object|String|Range} attrs
|
||||
* @return {Object}
|
||||
*/
|
||||
|
||||
static createProperties(attrs = {}) {
|
||||
if (Range.isRange(attrs)) {
|
||||
return {
|
||||
anchorKey: attrs.anchorKey,
|
||||
anchorOffset: attrs.anchorOffset,
|
||||
focusKey: attrs.focusKey,
|
||||
focusOffset: attrs.focusOffset,
|
||||
isBackward: attrs.isBackward,
|
||||
isFocused: attrs.isFocused,
|
||||
marks: attrs.marks,
|
||||
}
|
||||
}
|
||||
|
||||
if (isPlainObject(attrs)) {
|
||||
const props = {}
|
||||
if ('anchorKey' in attrs) props.anchorKey = attrs.anchorKey
|
||||
if ('anchorOffset' in attrs) props.anchorOffset = attrs.anchorOffset
|
||||
if ('focusKey' in attrs) props.focusKey = attrs.focusKey
|
||||
if ('focusOffset' in attrs) props.focusOffset = attrs.focusOffset
|
||||
if ('isBackward' in attrs) props.isBackward = attrs.isBackward
|
||||
if ('isFocused' in attrs) props.isFocused = attrs.isFocused
|
||||
if ('marks' in attrs) props.marks = attrs.marks
|
||||
return props
|
||||
}
|
||||
|
||||
throw new Error(`\`Range.createProperties\` only accepts objects or ranges, but you passed it: ${attrs}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a `Range` from a JSON `object`.
|
||||
*
|
||||
@@ -73,13 +109,23 @@ class Range extends Record(DEFAULTS) {
|
||||
|
||||
static fromJSON(object) {
|
||||
const {
|
||||
text = '',
|
||||
marks = [],
|
||||
anchorKey = null,
|
||||
anchorOffset = 0,
|
||||
focusKey = null,
|
||||
focusOffset = 0,
|
||||
isBackward = null,
|
||||
isFocused = false,
|
||||
marks = null,
|
||||
} = object
|
||||
|
||||
const range = new Range({
|
||||
text,
|
||||
marks: new Set(marks.map(Mark.fromJSON)),
|
||||
anchorKey,
|
||||
anchorOffset,
|
||||
focusKey,
|
||||
focusOffset,
|
||||
isBackward,
|
||||
isFocused,
|
||||
marks: marks == null ? null : new Set(marks.map(Mark.fromJSON)),
|
||||
})
|
||||
|
||||
return range
|
||||
@@ -103,18 +149,7 @@ class Range extends Record(DEFAULTS) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a `value` is a list of ranges.
|
||||
*
|
||||
* @param {Any} value
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
static isRangeList(value) {
|
||||
return List.isList(value) && value.every(item => Range.isRange(item))
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the node's kind.
|
||||
* Get the kind.
|
||||
*
|
||||
* @return {String}
|
||||
*/
|
||||
@@ -124,23 +159,577 @@ class Range extends Record(DEFAULTS) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return range as a list of characters
|
||||
* Check whether the range is blurred.
|
||||
*
|
||||
* @return {List<Character>}
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
getCharacters() {
|
||||
const { marks } = this
|
||||
const characters = Character.createList(this.text
|
||||
.split('')
|
||||
.map((char) => {
|
||||
return Character.create({
|
||||
text: char,
|
||||
marks
|
||||
})
|
||||
}))
|
||||
get isBlurred() {
|
||||
return !this.isFocused
|
||||
}
|
||||
|
||||
return characters
|
||||
/**
|
||||
* Check whether the range is collapsed.
|
||||
*
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
get isCollapsed() {
|
||||
return (
|
||||
this.anchorKey == this.focusKey &&
|
||||
this.anchorOffset == this.focusOffset
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the range is expanded.
|
||||
*
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
get isExpanded() {
|
||||
return !this.isCollapsed
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the range is forward.
|
||||
*
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
get isForward() {
|
||||
return this.isBackward == null ? null : !this.isBackward
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the range's keys are set.
|
||||
*
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
get isSet() {
|
||||
return this.anchorKey != null && this.focusKey != null
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the range's keys are not set.
|
||||
*
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
get isUnset() {
|
||||
return !this.isSet
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the start key.
|
||||
*
|
||||
* @return {String}
|
||||
*/
|
||||
|
||||
get startKey() {
|
||||
return this.isBackward ? this.focusKey : this.anchorKey
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the start offset.
|
||||
*
|
||||
* @return {String}
|
||||
*/
|
||||
|
||||
get startOffset() {
|
||||
return this.isBackward ? this.focusOffset : this.anchorOffset
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the end key.
|
||||
*
|
||||
* @return {String}
|
||||
*/
|
||||
|
||||
get endKey() {
|
||||
return this.isBackward ? this.anchorKey : this.focusKey
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the end offset.
|
||||
*
|
||||
* @return {String}
|
||||
*/
|
||||
|
||||
get endOffset() {
|
||||
return this.isBackward ? this.anchorOffset : this.focusOffset
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether anchor point of the range is at the start of a `node`.
|
||||
*
|
||||
* @param {Node} node
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
hasAnchorAtStartOf(node) {
|
||||
// PERF: Do a check for a `0` offset first since it's quickest.
|
||||
if (this.anchorOffset != 0) return false
|
||||
const first = getFirst(node)
|
||||
return this.anchorKey == first.key
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether anchor point of the range is at the end of a `node`.
|
||||
*
|
||||
* @param {Node} node
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
hasAnchorAtEndOf(node) {
|
||||
const last = getLast(node)
|
||||
return this.anchorKey == last.key && this.anchorOffset == last.text.length
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the anchor edge of a range is in a `node` and at an
|
||||
* offset between `start` and `end`.
|
||||
*
|
||||
* @param {Node} node
|
||||
* @param {Number} start
|
||||
* @param {Number} end
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
hasAnchorBetween(node, start, end) {
|
||||
return (
|
||||
this.anchorOffset <= end &&
|
||||
start <= this.anchorOffset &&
|
||||
this.hasAnchorIn(node)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the anchor edge of a range is in a `node`.
|
||||
*
|
||||
* @param {Node} node
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
hasAnchorIn(node) {
|
||||
return node.kind == 'text'
|
||||
? node.key == this.anchorKey
|
||||
: this.anchorKey != null && node.hasDescendant(this.anchorKey)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether focus point of the range is at the end of a `node`.
|
||||
*
|
||||
* @param {Node} node
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
hasFocusAtEndOf(node) {
|
||||
const last = getLast(node)
|
||||
return this.focusKey == last.key && this.focusOffset == last.text.length
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether focus point of the range is at the start of a `node`.
|
||||
*
|
||||
* @param {Node} node
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
hasFocusAtStartOf(node) {
|
||||
if (this.focusOffset != 0) return false
|
||||
const first = getFirst(node)
|
||||
return this.focusKey == first.key
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the focus edge of a range is in a `node` and at an
|
||||
* offset between `start` and `end`.
|
||||
*
|
||||
* @param {Node} node
|
||||
* @param {Number} start
|
||||
* @param {Number} end
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
hasFocusBetween(node, start, end) {
|
||||
return (
|
||||
start <= this.focusOffset &&
|
||||
this.focusOffset <= end &&
|
||||
this.hasFocusIn(node)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the focus edge of a range is in a `node`.
|
||||
*
|
||||
* @param {Node} node
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
hasFocusIn(node) {
|
||||
return node.kind == 'text'
|
||||
? node.key == this.focusKey
|
||||
: this.focusKey != null && node.hasDescendant(this.focusKey)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the range is at the start of a `node`.
|
||||
*
|
||||
* @param {Node} node
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
isAtStartOf(node) {
|
||||
return this.isCollapsed && this.hasAnchorAtStartOf(node)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the range is at the end of a `node`.
|
||||
*
|
||||
* @param {Node} node
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
isAtEndOf(node) {
|
||||
return this.isCollapsed && this.hasAnchorAtEndOf(node)
|
||||
}
|
||||
|
||||
/**
|
||||
* Focus the range.
|
||||
*
|
||||
* @return {Range}
|
||||
*/
|
||||
|
||||
focus() {
|
||||
return this.merge({
|
||||
isFocused: true
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Blur the range.
|
||||
*
|
||||
* @return {Range}
|
||||
*/
|
||||
|
||||
blur() {
|
||||
return this.merge({
|
||||
isFocused: false
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Unset the range.
|
||||
*
|
||||
* @return {Range}
|
||||
*/
|
||||
|
||||
deselect() {
|
||||
return this.merge({
|
||||
anchorKey: null,
|
||||
anchorOffset: 0,
|
||||
focusKey: null,
|
||||
focusOffset: 0,
|
||||
isFocused: false,
|
||||
isBackward: false
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Flip the range.
|
||||
*
|
||||
* @return {Range}
|
||||
*/
|
||||
|
||||
flip() {
|
||||
return this.merge({
|
||||
anchorKey: this.focusKey,
|
||||
anchorOffset: this.focusOffset,
|
||||
focusKey: this.anchorKey,
|
||||
focusOffset: this.anchorOffset,
|
||||
isBackward: this.isBackward == null ? null : !this.isBackward,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the anchor offset `n` characters.
|
||||
*
|
||||
* @param {Number} n (optional)
|
||||
* @return {Range}
|
||||
*/
|
||||
|
||||
moveAnchor(n = 1) {
|
||||
const { anchorKey, focusKey, focusOffset, isBackward } = this
|
||||
const anchorOffset = this.anchorOffset + n
|
||||
return this.merge({
|
||||
anchorOffset,
|
||||
isBackward: anchorKey == focusKey
|
||||
? anchorOffset > focusOffset
|
||||
: isBackward
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the anchor offset `n` characters.
|
||||
*
|
||||
* @param {Number} n (optional)
|
||||
* @return {Range}
|
||||
*/
|
||||
|
||||
moveFocus(n = 1) {
|
||||
const { anchorKey, anchorOffset, focusKey, isBackward } = this
|
||||
const focusOffset = this.focusOffset + n
|
||||
return this.merge({
|
||||
focusOffset,
|
||||
isBackward: focusKey == anchorKey
|
||||
? anchorOffset > focusOffset
|
||||
: isBackward
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the range's anchor point to a `key` and `offset`.
|
||||
*
|
||||
* @param {String} key
|
||||
* @param {Number} offset
|
||||
* @return {Range}
|
||||
*/
|
||||
|
||||
moveAnchorTo(key, offset) {
|
||||
const { anchorKey, focusKey, focusOffset, isBackward } = this
|
||||
return this.merge({
|
||||
anchorKey: key,
|
||||
anchorOffset: offset,
|
||||
isBackward: key == focusKey
|
||||
? offset > focusOffset
|
||||
: key == anchorKey ? isBackward : null
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the range's focus point to a `key` and `offset`.
|
||||
*
|
||||
* @param {String} key
|
||||
* @param {Number} offset
|
||||
* @return {Range}
|
||||
*/
|
||||
|
||||
moveFocusTo(key, offset) {
|
||||
const { focusKey, anchorKey, anchorOffset, isBackward } = this
|
||||
return this.merge({
|
||||
focusKey: key,
|
||||
focusOffset: offset,
|
||||
isBackward: key == anchorKey
|
||||
? anchorOffset > offset
|
||||
: key == focusKey ? isBackward : null
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the range to `anchorOffset`.
|
||||
*
|
||||
* @param {Number} anchorOffset
|
||||
* @return {Range}
|
||||
*/
|
||||
|
||||
moveAnchorOffsetTo(anchorOffset) {
|
||||
return this.merge({
|
||||
anchorOffset,
|
||||
isBackward: this.anchorKey == this.focusKey
|
||||
? anchorOffset > this.focusOffset
|
||||
: this.isBackward
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the range to `focusOffset`.
|
||||
*
|
||||
* @param {Number} focusOffset
|
||||
* @return {Range}
|
||||
*/
|
||||
|
||||
moveFocusOffsetTo(focusOffset) {
|
||||
return this.merge({
|
||||
focusOffset,
|
||||
isBackward: this.anchorKey == this.focusKey
|
||||
? this.anchorOffset > focusOffset
|
||||
: this.isBackward
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the range to `anchorOffset` and `focusOffset`.
|
||||
*
|
||||
* @param {Number} anchorOffset
|
||||
* @param {Number} focusOffset (optional)
|
||||
* @return {Range}
|
||||
*/
|
||||
|
||||
moveOffsetsTo(anchorOffset, focusOffset = anchorOffset) {
|
||||
return this
|
||||
.moveAnchorOffsetTo(anchorOffset)
|
||||
.moveFocusOffsetTo(focusOffset)
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the focus point to the anchor point.
|
||||
*
|
||||
* @return {Range}
|
||||
*/
|
||||
|
||||
moveToAnchor() {
|
||||
return this.moveFocusTo(this.anchorKey, this.anchorOffset)
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the anchor point to the focus point.
|
||||
*
|
||||
* @return {Range}
|
||||
*/
|
||||
|
||||
moveToFocus() {
|
||||
return this.moveAnchorTo(this.focusKey, this.focusOffset)
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the range's anchor point to the start of a `node`.
|
||||
*
|
||||
* @param {Node} node
|
||||
* @return {Range}
|
||||
*/
|
||||
|
||||
moveAnchorToStartOf(node) {
|
||||
node = getFirst(node)
|
||||
return this.moveAnchorTo(node.key, 0)
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the range's anchor point to the end of a `node`.
|
||||
*
|
||||
* @param {Node} node
|
||||
* @return {Range}
|
||||
*/
|
||||
|
||||
moveAnchorToEndOf(node) {
|
||||
node = getLast(node)
|
||||
return this.moveAnchorTo(node.key, node.text.length)
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the range's focus point to the start of a `node`.
|
||||
*
|
||||
* @param {Node} node
|
||||
* @return {Range}
|
||||
*/
|
||||
|
||||
moveFocusToStartOf(node) {
|
||||
node = getFirst(node)
|
||||
return this.moveFocusTo(node.key, 0)
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the range's focus point to the end of a `node`.
|
||||
*
|
||||
* @param {Node} node
|
||||
* @return {Range}
|
||||
*/
|
||||
|
||||
moveFocusToEndOf(node) {
|
||||
node = getLast(node)
|
||||
return this.moveFocusTo(node.key, node.text.length)
|
||||
}
|
||||
|
||||
/**
|
||||
* Move to the entire range of `start` and `end` nodes.
|
||||
*
|
||||
* @param {Node} start
|
||||
* @param {Node} end (optional)
|
||||
* @return {Range}
|
||||
*/
|
||||
|
||||
moveToRangeOf(start, end = start) {
|
||||
return this
|
||||
.moveAnchorToStartOf(start)
|
||||
.moveFocusToEndOf(end)
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize the range, relative to a `node`, ensuring that the anchor
|
||||
* and focus nodes of the range always refer to leaf text nodes.
|
||||
*
|
||||
* @param {Node} node
|
||||
* @return {Range}
|
||||
*/
|
||||
|
||||
normalize(node) {
|
||||
const range = this
|
||||
let { anchorKey, anchorOffset, focusKey, focusOffset, isBackward } = range
|
||||
|
||||
// If the range is unset, make sure it is properly zeroed out.
|
||||
if (anchorKey == null || focusKey == null) {
|
||||
return range.merge({
|
||||
anchorKey: null,
|
||||
anchorOffset: 0,
|
||||
focusKey: null,
|
||||
focusOffset: 0,
|
||||
isBackward: false,
|
||||
})
|
||||
}
|
||||
|
||||
// Get the anchor and focus nodes.
|
||||
let anchorNode = node.getDescendant(anchorKey)
|
||||
let focusNode = node.getDescendant(focusKey)
|
||||
|
||||
// If the range is malformed, warn and zero it out.
|
||||
if (!anchorNode || !focusNode) {
|
||||
logger.warn('The range was invalid and was reset. The range in question was:', range)
|
||||
const first = node.getFirstText()
|
||||
return range.merge({
|
||||
anchorKey: first ? first.key : null,
|
||||
anchorOffset: 0,
|
||||
focusKey: first ? first.key : null,
|
||||
focusOffset: 0,
|
||||
isBackward: false,
|
||||
})
|
||||
}
|
||||
|
||||
// If the anchor node isn't a text node, match it to one.
|
||||
if (anchorNode.kind != 'text') {
|
||||
logger.warn('The range anchor was set to a Node that is not a Text node. This should not happen and can degrade performance. The node in question was:', anchorNode)
|
||||
const anchorText = anchorNode.getTextAtOffset(anchorOffset)
|
||||
const offset = anchorNode.getOffset(anchorText.key)
|
||||
anchorOffset = anchorOffset - offset
|
||||
anchorNode = anchorText
|
||||
}
|
||||
|
||||
// If the focus node isn't a text node, match it to one.
|
||||
if (focusNode.kind != 'text') {
|
||||
logger.warn('The range focus was set to a Node that is not a Text node. This should not happen and can degrade performance. The node in question was:', focusNode)
|
||||
const focusText = focusNode.getTextAtOffset(focusOffset)
|
||||
const offset = focusNode.getOffset(focusText.key)
|
||||
focusOffset = focusOffset - offset
|
||||
focusNode = focusText
|
||||
}
|
||||
|
||||
// If `isBackward` is not set, derive it.
|
||||
if (isBackward == null) {
|
||||
if (anchorNode.key === focusNode.key) {
|
||||
isBackward = anchorOffset > focusOffset
|
||||
} else {
|
||||
isBackward = !node.areDescendantsSorted(anchorNode.key, focusNode.key)
|
||||
}
|
||||
}
|
||||
|
||||
// Merge in any updated properties.
|
||||
return range.merge({
|
||||
anchorKey: anchorNode.key,
|
||||
anchorOffset,
|
||||
focusKey: focusNode.key,
|
||||
focusOffset,
|
||||
isBackward
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -151,9 +740,14 @@ class Range extends Record(DEFAULTS) {
|
||||
|
||||
toJSON() {
|
||||
const object = {
|
||||
anchorKey: this.anchorKey,
|
||||
anchorOffset: this.anchorOffset,
|
||||
focusKey: this.focusKey,
|
||||
focusOffset: this.focusOffset,
|
||||
isBackward: this.isBackward,
|
||||
isFocused: this.isFocused,
|
||||
kind: this.kind,
|
||||
marks: this.marks.toArray().map(m => m.toJSON()),
|
||||
text: this.text,
|
||||
marks: this.marks == null ? null : this.marks.toArray().map(m => m.toJSON()),
|
||||
}
|
||||
|
||||
return object
|
||||
@@ -167,6 +761,126 @@ class Range extends Record(DEFAULTS) {
|
||||
return this.toJSON()
|
||||
}
|
||||
|
||||
/**
|
||||
* Unset the range.
|
||||
*
|
||||
* @return {Range}
|
||||
*/
|
||||
|
||||
unset() {
|
||||
logger.deprecate('0.17.0', 'The `Range.unset` method is deprecated, please switch to using `Range.deselect` instead.')
|
||||
return this.deselect()
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the range forward `n` characters.
|
||||
*
|
||||
* @param {Number} n (optional)
|
||||
* @return {Range}
|
||||
*/
|
||||
|
||||
moveForward(n = 1) {
|
||||
logger.deprecate('0.17.0', 'The `Range.moveForward(n)` method is deprecated, please switch to using `Range.move(n)` instead.')
|
||||
return this.move(n)
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the range backward `n` characters.
|
||||
*
|
||||
* @param {Number} n (optional)
|
||||
* @return {Range}
|
||||
*/
|
||||
|
||||
moveBackward(n = 1) {
|
||||
logger.deprecate('0.17.0', 'The `Range.moveBackward(n)` method is deprecated, please switch to using `Range.move(-n)` (with a negative number) instead.')
|
||||
return this.move(0 - n)
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the anchor offset `n` characters.
|
||||
*
|
||||
* @param {Number} n (optional)
|
||||
* @return {Range}
|
||||
*/
|
||||
|
||||
moveAnchorOffset(n = 1) {
|
||||
logger.deprecate('0.17.0', 'The `Range.moveAnchorOffset(n)` method is deprecated, please switch to using `Range.moveAnchor(n)` instead.')
|
||||
return this.moveAnchor(n)
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the focus offset `n` characters.
|
||||
*
|
||||
* @param {Number} n (optional)
|
||||
* @return {Range}
|
||||
*/
|
||||
|
||||
moveFocusOffset(n = 1) {
|
||||
logger.deprecate('0.17.0', 'The `Range.moveFocusOffset(n)` method is deprecated, please switch to using `Range.moveFocus(n)` instead.')
|
||||
return this.moveFocus(n)
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the start offset `n` characters.
|
||||
*
|
||||
* @param {Number} n (optional)
|
||||
* @return {Range}
|
||||
*/
|
||||
|
||||
moveStartOffset(n = 1) {
|
||||
logger.deprecate('0.17.0', 'The `Range.moveStartOffset(n)` method is deprecated, please switch to using `Range.moveStart(n)` instead.')
|
||||
return this.moveStart(n)
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the focus offset `n` characters.
|
||||
*
|
||||
* @param {Number} n (optional)
|
||||
* @return {Range}
|
||||
*/
|
||||
|
||||
moveEndOffset(n = 1) {
|
||||
logger.deprecate('0.17.0', 'The `Range.moveEndOffset(n)` method is deprecated, please switch to using `Range.moveEnd(n)` instead.')
|
||||
return this.moveEnd(n)
|
||||
}
|
||||
|
||||
/**
|
||||
* Extend the focus point forward `n` characters.
|
||||
*
|
||||
* @param {Number} n (optional)
|
||||
* @return {Range}
|
||||
*/
|
||||
|
||||
extendForward(n = 1) {
|
||||
logger.deprecate('0.17.0', 'The `Range.extendForward(n)` method is deprecated, please switch to using `Range.extend(n)` instead.')
|
||||
return this.extend(n)
|
||||
}
|
||||
|
||||
/**
|
||||
* Extend the focus point backward `n` characters.
|
||||
*
|
||||
* @param {Number} n (optional)
|
||||
* @return {Range}
|
||||
*/
|
||||
|
||||
extendBackward(n = 1) {
|
||||
logger.deprecate('0.17.0', 'The `Range.extendBackward(n)` method is deprecated, please switch to using `Range.extend(-n)` (with a negative number) instead.')
|
||||
return this.extend(0 - n)
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the range to `anchorOffset` and `focusOffset`.
|
||||
*
|
||||
* @param {Number} anchorOffset
|
||||
* @param {Number} focusOffset (optional)
|
||||
* @return {Range}
|
||||
*/
|
||||
|
||||
moveToOffsets(anchorOffset, focusOffset = anchorOffset) {
|
||||
logger.deprecate('0.17.0', 'The `Range.moveToOffsets` method is deprecated, please switch to using `Range.moveOffsetsTo` instead.')
|
||||
return this.moveOffsetsTo(anchorOffset, focusOffset)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -175,6 +889,110 @@ class Range extends Record(DEFAULTS) {
|
||||
|
||||
Range.prototype[MODEL_TYPES.RANGE] = true
|
||||
|
||||
/**
|
||||
* Mix in some "move" convenience methods.
|
||||
*/
|
||||
|
||||
const MOVE_METHODS = [
|
||||
['move', ''],
|
||||
['move', 'To'],
|
||||
['move', 'ToStartOf'],
|
||||
['move', 'ToEndOf'],
|
||||
]
|
||||
|
||||
MOVE_METHODS.forEach(([ p, s ]) => {
|
||||
Range.prototype[`${p}${s}`] = function (...args) {
|
||||
return this
|
||||
[`${p}Anchor${s}`](...args)
|
||||
[`${p}Focus${s}`](...args)
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* Mix in the "start", "end" and "edge" convenience methods.
|
||||
*/
|
||||
|
||||
const EDGE_METHODS = [
|
||||
['has', 'AtStartOf', true],
|
||||
['has', 'AtEndOf', true],
|
||||
['has', 'Between', true],
|
||||
['has', 'In', true],
|
||||
['collapseTo', ''],
|
||||
['move', ''],
|
||||
['moveTo', ''],
|
||||
['move', 'To'],
|
||||
['move', 'OffsetTo'],
|
||||
]
|
||||
|
||||
EDGE_METHODS.forEach(([ p, s, hasEdge ]) => {
|
||||
const anchor = `${p}Anchor${s}`
|
||||
const focus = `${p}Focus${s}`
|
||||
|
||||
Range.prototype[`${p}Start${s}`] = function (...args) {
|
||||
return this.isBackward
|
||||
? this[focus](...args)
|
||||
: this[anchor](...args)
|
||||
}
|
||||
|
||||
Range.prototype[`${p}End${s}`] = function (...args) {
|
||||
return this.isBackward
|
||||
? this[anchor](...args)
|
||||
: this[focus](...args)
|
||||
}
|
||||
|
||||
if (hasEdge) {
|
||||
Range.prototype[`${p}Edge${s}`] = function (...args) {
|
||||
return this[anchor](...args) || this[focus](...args)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* Mix in some aliases for convenience / parallelism with the browser APIs.
|
||||
*/
|
||||
|
||||
const ALIAS_METHODS = [
|
||||
['collapseTo', 'moveTo'],
|
||||
['collapseToAnchor', 'moveToAnchor'],
|
||||
['collapseToFocus', 'moveToFocus'],
|
||||
['collapseToStart', 'moveToStart'],
|
||||
['collapseToEnd', 'moveToEnd'],
|
||||
['collapseToStartOf', 'moveToStartOf'],
|
||||
['collapseToEndOf', 'moveToEndOf'],
|
||||
['extend', 'moveFocus'],
|
||||
['extendTo', 'moveFocusTo'],
|
||||
['extendToStartOf', 'moveFocusToStartOf'],
|
||||
['extendToEndOf', 'moveFocusToEndOf'],
|
||||
]
|
||||
|
||||
ALIAS_METHODS.forEach(([ alias, method ]) => {
|
||||
Range.prototype[alias] = function (...args) {
|
||||
return this[method](...args)
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* Get the first text of a `node`.
|
||||
*
|
||||
* @param {Node} node
|
||||
* @return {Text}
|
||||
*/
|
||||
|
||||
function getFirst(node) {
|
||||
return node.kind == 'text' ? node : node.getFirstText()
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the last text of a `node`.
|
||||
*
|
||||
* @param {Node} node
|
||||
* @return {Text}
|
||||
*/
|
||||
|
||||
function getLast(node) {
|
||||
return node.kind == 'text' ? node : node.getLastText()
|
||||
}
|
||||
|
||||
/**
|
||||
* Export.
|
||||
*
|
||||
|
@@ -7,7 +7,7 @@ import typeOf from 'type-of'
|
||||
import { Record } from 'immutable'
|
||||
|
||||
import MODEL_TYPES from '../constants/model-types'
|
||||
import Selection from '../models/selection'
|
||||
import Range from '../models/range'
|
||||
import isReactComponent from '../utils/is-react-component'
|
||||
|
||||
/**
|
||||
@@ -134,7 +134,7 @@ class Schema extends Record(DEFAULTS) {
|
||||
* much better performance.
|
||||
*
|
||||
* @param {Mixed} object
|
||||
* @return {List<Selection>}
|
||||
* @return {List<Range>}
|
||||
*/
|
||||
|
||||
__getDecorations(object) {
|
||||
@@ -152,7 +152,7 @@ class Schema extends Record(DEFAULTS) {
|
||||
})
|
||||
})
|
||||
|
||||
const list = Selection.createList(array)
|
||||
const list = Range.createList(array)
|
||||
return list
|
||||
}
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -8,7 +8,7 @@ import SCHEMA from '../schemas/core'
|
||||
import Data from './data'
|
||||
import Document from './document'
|
||||
import History from './history'
|
||||
import Selection from './selection'
|
||||
import Range from './range'
|
||||
|
||||
/**
|
||||
* Default properties.
|
||||
@@ -18,7 +18,7 @@ import Selection from './selection'
|
||||
|
||||
const DEFAULTS = {
|
||||
document: Document.create(),
|
||||
selection: Selection.create(),
|
||||
selection: Range.create(),
|
||||
history: History.create(),
|
||||
data: new Map(),
|
||||
decorations: null,
|
||||
@@ -70,7 +70,7 @@ class State extends Record(DEFAULTS) {
|
||||
if (isPlainObject(attrs)) {
|
||||
const props = {}
|
||||
if ('data' in attrs) props.data = Data.create(attrs.data)
|
||||
if ('decorations' in attrs) props.decorations = Selection.createList(attrs.decorations)
|
||||
if ('decorations' in attrs) props.decorations = Range.createList(attrs.decorations)
|
||||
return props
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ class State extends Record(DEFAULTS) {
|
||||
let data = new Map()
|
||||
|
||||
document = Document.fromJSON(document)
|
||||
selection = Selection.fromJSON(selection)
|
||||
selection = Range.fromJSON(selection)
|
||||
|
||||
// Allow plugins to set a default value for `data`.
|
||||
if (options.plugins) {
|
||||
|
@@ -5,7 +5,7 @@ import { List, OrderedSet, Record, Set, is } from 'immutable'
|
||||
|
||||
import Character from './character'
|
||||
import Mark from './mark'
|
||||
import Range from './range'
|
||||
import Leaf from './leaf'
|
||||
import MODEL_TYPES from '../constants/model-types'
|
||||
import generateKey from '../utils/generate-key'
|
||||
import memoize from '../utils/memoize'
|
||||
@@ -42,13 +42,13 @@ class Text extends Record(DEFAULTS) {
|
||||
}
|
||||
|
||||
if (typeof attrs == 'string') {
|
||||
attrs = { ranges: [{ text: attrs }] }
|
||||
attrs = { leaves: [{ text: attrs }] }
|
||||
}
|
||||
|
||||
if (isPlainObject(attrs)) {
|
||||
if (attrs.text) {
|
||||
const { text, marks, key } = attrs
|
||||
attrs = { key, ranges: [{ text, marks }] }
|
||||
attrs = { key, leaves: [{ text, marks }] }
|
||||
}
|
||||
|
||||
return Text.fromJSON(attrs)
|
||||
@@ -86,17 +86,22 @@ class Text extends Record(DEFAULTS) {
|
||||
}
|
||||
|
||||
let {
|
||||
ranges = [],
|
||||
leaves = [],
|
||||
key = generateKey(),
|
||||
} = object
|
||||
|
||||
if (object.text) {
|
||||
logger.deprecate('0.23.0', 'Passing `object.text` to `Text.fromJSON` has been deprecated, please use `object.ranges` instead.')
|
||||
ranges = [{ text: object.text }]
|
||||
if (object.ranges) {
|
||||
logger.deprecate('0.27.0', 'Passing `object.ranges` to `Text.fromJSON` has been renamed to `object.leaves`.')
|
||||
leaves = object.ranges
|
||||
}
|
||||
|
||||
const characters = ranges
|
||||
.map(Range.fromJSON)
|
||||
if (object.text) {
|
||||
logger.deprecate('0.23.0', 'Passing `object.text` to `Text.fromJSON` has been deprecated, please use `object.leaves` instead.')
|
||||
leaves = [{ text: object.text }]
|
||||
}
|
||||
|
||||
const characters = leaves
|
||||
.map(Leaf.fromJSON)
|
||||
.reduce((l, r) => l.concat(r.getCharacters()), new List())
|
||||
|
||||
const node = new Text({
|
||||
@@ -256,6 +261,64 @@ class Text extends Record(DEFAULTS) {
|
||||
return schema.__getDecorations(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* Derive the leaves for a list of `characters`.
|
||||
*
|
||||
* @param {Array|Void} decorations (optional)
|
||||
* @return {List<Leaf>}
|
||||
*/
|
||||
|
||||
getLeaves(decorations = []) {
|
||||
const characters = this.getDecoratedCharacters(decorations)
|
||||
let leaves = []
|
||||
|
||||
// PERF: cache previous values for faster lookup.
|
||||
let prevChar
|
||||
let prevLeaf
|
||||
|
||||
// If there are no characters, return one empty range.
|
||||
if (characters.size == 0) {
|
||||
leaves.push({})
|
||||
}
|
||||
|
||||
// Otherwise, loop the characters and build the leaves...
|
||||
else {
|
||||
characters.forEach((char, i) => {
|
||||
const { marks, text } = char
|
||||
|
||||
// The first one can always just be created.
|
||||
if (i == 0) {
|
||||
prevChar = char
|
||||
prevLeaf = { text, marks }
|
||||
leaves.push(prevLeaf)
|
||||
return
|
||||
}
|
||||
|
||||
// Otherwise, compare the current and previous marks.
|
||||
const prevMarks = prevChar.marks
|
||||
const isSame = is(marks, prevMarks)
|
||||
|
||||
// If the marks are the same, add the text to the previous range.
|
||||
if (isSame) {
|
||||
prevChar = char
|
||||
prevLeaf.text += text
|
||||
return
|
||||
}
|
||||
|
||||
// Otherwise, create a new range.
|
||||
prevChar = char
|
||||
prevLeaf = { text, marks }
|
||||
leaves.push(prevLeaf)
|
||||
}, [])
|
||||
}
|
||||
|
||||
// PERF: convert the leaves to immutable objects after iterating.
|
||||
leaves = new List(leaves.map(object => new Leaf(object)))
|
||||
|
||||
// Return the leaves.
|
||||
return leaves
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all of the marks on the text.
|
||||
*
|
||||
@@ -307,64 +370,6 @@ class Text extends Record(DEFAULTS) {
|
||||
: null
|
||||
}
|
||||
|
||||
/**
|
||||
* Derive the ranges for a list of `characters`.
|
||||
*
|
||||
* @param {Array|Void} decorations (optional)
|
||||
* @return {List<Range>}
|
||||
*/
|
||||
|
||||
getRanges(decorations = []) {
|
||||
const characters = this.getDecoratedCharacters(decorations)
|
||||
let ranges = []
|
||||
|
||||
// PERF: cache previous values for faster lookup.
|
||||
let prevChar
|
||||
let prevRange
|
||||
|
||||
// If there are no characters, return one empty range.
|
||||
if (characters.size == 0) {
|
||||
ranges.push({})
|
||||
}
|
||||
|
||||
// Otherwise, loop the characters and build the ranges...
|
||||
else {
|
||||
characters.forEach((char, i) => {
|
||||
const { marks, text } = char
|
||||
|
||||
// The first one can always just be created.
|
||||
if (i == 0) {
|
||||
prevChar = char
|
||||
prevRange = { text, marks }
|
||||
ranges.push(prevRange)
|
||||
return
|
||||
}
|
||||
|
||||
// Otherwise, compare the current and previous marks.
|
||||
const prevMarks = prevChar.marks
|
||||
const isSame = is(marks, prevMarks)
|
||||
|
||||
// If the marks are the same, add the text to the previous range.
|
||||
if (isSame) {
|
||||
prevChar = char
|
||||
prevRange.text += text
|
||||
return
|
||||
}
|
||||
|
||||
// Otherwise, create a new range.
|
||||
prevChar = char
|
||||
prevRange = { text, marks }
|
||||
ranges.push(prevRange)
|
||||
}, [])
|
||||
}
|
||||
|
||||
// PERF: convert the ranges to immutable objects after iterating.
|
||||
ranges = new List(ranges.map(object => new Range(object)))
|
||||
|
||||
// Return the ranges.
|
||||
return ranges
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the node has a node by `key`, to parallel other nodes.
|
||||
*
|
||||
@@ -456,7 +461,7 @@ class Text extends Record(DEFAULTS) {
|
||||
const object = {
|
||||
key: this.key,
|
||||
kind: this.kind,
|
||||
ranges: this.getRanges().toArray().map(r => r.toJSON()),
|
||||
leaves: this.getLeaves().toArray().map(r => r.toJSON()),
|
||||
}
|
||||
|
||||
if (!options.preserveKeys) {
|
||||
@@ -512,6 +517,15 @@ class Text extends Record(DEFAULTS) {
|
||||
return schema.__validate(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* Deprecated.
|
||||
*/
|
||||
|
||||
getRanges(...args) {
|
||||
logger.deprecate('0.27.0', 'The `Text.getRanges()` method was renamed to `Text.getLeaves`.')
|
||||
return this.getLeaves(...args)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -534,8 +548,8 @@ memoize(Text.prototype, [
|
||||
memoize(Text.prototype, [
|
||||
'getDecoratedCharacters',
|
||||
'getDecorations',
|
||||
'getLeaves',
|
||||
'getMarksAtIndex',
|
||||
'getRanges',
|
||||
'validate'
|
||||
], {
|
||||
takesArguments: true,
|
||||
|
@@ -171,15 +171,15 @@ function invertOperation(op) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Set data.
|
||||
* Set state.
|
||||
*/
|
||||
|
||||
if (type == 'set_data') {
|
||||
const { properties, data } = op
|
||||
if (type == 'set_state') {
|
||||
const { properties, state } = op
|
||||
return {
|
||||
...op,
|
||||
data: data.merge(properties),
|
||||
properties: pick(data, Object.keys(properties)),
|
||||
state: state.merge(properties),
|
||||
properties: pick(state, Object.keys(properties)),
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -4,7 +4,7 @@
|
||||
*
|
||||
* @param {Number} index
|
||||
* @param {Text} text
|
||||
* @param {Selection} range
|
||||
* @param {Range} range
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
|
@@ -3,7 +3,7 @@
|
||||
import h from '../../../helpers/h'
|
||||
|
||||
export default function (change) {
|
||||
change.replaceNodeByKey('a', { kind: 'text', ranges: [{ text: 'three' }] })
|
||||
change.replaceNodeByKey('a', { kind: 'text', leaves: [{ text: 'three' }] })
|
||||
}
|
||||
|
||||
export const input = (
|
||||
|
@@ -26,9 +26,9 @@ export const output = {
|
||||
nodes: [
|
||||
{
|
||||
kind: 'text',
|
||||
ranges: [
|
||||
leaves: [
|
||||
{
|
||||
kind: 'range',
|
||||
kind: 'leaf',
|
||||
text: '',
|
||||
marks: [],
|
||||
}
|
||||
|
@@ -31,9 +31,9 @@ export const output = {
|
||||
nodes: [
|
||||
{
|
||||
kind: 'text',
|
||||
ranges: [
|
||||
leaves: [
|
||||
{
|
||||
kind: 'range',
|
||||
kind: 'leaf',
|
||||
text: 'two',
|
||||
marks: [],
|
||||
}
|
||||
|
@@ -29,9 +29,9 @@ export const output = {
|
||||
nodes: [
|
||||
{
|
||||
kind: 'text',
|
||||
ranges: [
|
||||
leaves: [
|
||||
{
|
||||
kind: 'range',
|
||||
kind: 'leaf',
|
||||
text: 'two',
|
||||
marks: [],
|
||||
}
|
||||
|
@@ -33,9 +33,9 @@ export const output = {
|
||||
nodes: [
|
||||
{
|
||||
kind: 'text',
|
||||
ranges: [
|
||||
leaves: [
|
||||
{
|
||||
kind: 'range',
|
||||
kind: 'leaf',
|
||||
text: '',
|
||||
marks: [],
|
||||
}
|
||||
@@ -49,9 +49,9 @@ export const output = {
|
||||
nodes: [
|
||||
{
|
||||
kind: 'text',
|
||||
ranges: [
|
||||
leaves: [
|
||||
{
|
||||
kind: 'range',
|
||||
kind: 'leaf',
|
||||
text: 'two',
|
||||
marks: [],
|
||||
}
|
||||
@@ -61,9 +61,9 @@ export const output = {
|
||||
},
|
||||
{
|
||||
kind: 'text',
|
||||
ranges: [
|
||||
leaves: [
|
||||
{
|
||||
kind: 'range',
|
||||
kind: 'leaf',
|
||||
text: '',
|
||||
marks: [],
|
||||
}
|
||||
|
@@ -35,9 +35,9 @@ export const output = {
|
||||
nodes: [
|
||||
{
|
||||
kind: 'text',
|
||||
ranges: [
|
||||
leaves: [
|
||||
{
|
||||
kind: 'range',
|
||||
kind: 'leaf',
|
||||
text: '',
|
||||
marks: [],
|
||||
}
|
||||
@@ -51,9 +51,9 @@ export const output = {
|
||||
nodes: [
|
||||
{
|
||||
kind: 'text',
|
||||
ranges: [
|
||||
leaves: [
|
||||
{
|
||||
kind: 'range',
|
||||
kind: 'leaf',
|
||||
text: '',
|
||||
marks: [],
|
||||
}
|
||||
@@ -67,9 +67,9 @@ export const output = {
|
||||
nodes: [
|
||||
{
|
||||
kind: 'text',
|
||||
ranges: [
|
||||
leaves: [
|
||||
{
|
||||
kind: 'range',
|
||||
kind: 'leaf',
|
||||
text: 'one',
|
||||
marks: [],
|
||||
}
|
||||
@@ -79,9 +79,9 @@ export const output = {
|
||||
},
|
||||
{
|
||||
kind: 'text',
|
||||
ranges: [
|
||||
leaves: [
|
||||
{
|
||||
kind: 'range',
|
||||
kind: 'leaf',
|
||||
text: '',
|
||||
marks: [],
|
||||
}
|
||||
@@ -95,9 +95,9 @@ export const output = {
|
||||
nodes: [
|
||||
{
|
||||
kind: 'text',
|
||||
ranges: [
|
||||
leaves: [
|
||||
{
|
||||
kind: 'range',
|
||||
kind: 'leaf',
|
||||
text: 'two',
|
||||
marks: [],
|
||||
}
|
||||
@@ -107,9 +107,9 @@ export const output = {
|
||||
},
|
||||
{
|
||||
kind: 'text',
|
||||
ranges: [
|
||||
leaves: [
|
||||
{
|
||||
kind: 'range',
|
||||
kind: 'leaf',
|
||||
text: '',
|
||||
marks: [],
|
||||
}
|
||||
@@ -119,9 +119,9 @@ export const output = {
|
||||
},
|
||||
{
|
||||
kind: 'text',
|
||||
ranges: [
|
||||
leaves: [
|
||||
{
|
||||
kind: 'range',
|
||||
kind: 'leaf',
|
||||
text: '',
|
||||
marks: [],
|
||||
}
|
||||
|
@@ -28,9 +28,9 @@ export const output = {
|
||||
nodes: [
|
||||
{
|
||||
kind: 'text',
|
||||
ranges: [
|
||||
leaves: [
|
||||
{
|
||||
kind: 'range',
|
||||
kind: 'leaf',
|
||||
text: '',
|
||||
marks: [],
|
||||
}
|
||||
|
@@ -22,10 +22,10 @@ export const input = {
|
||||
nodes: [
|
||||
{
|
||||
kind: 'text',
|
||||
ranges: [
|
||||
leaves: [
|
||||
{
|
||||
text: 'one',
|
||||
kind: 'range',
|
||||
kind: 'leaf',
|
||||
marks: []
|
||||
}
|
||||
]
|
||||
|
@@ -18,9 +18,9 @@ export const input = {
|
||||
nodes: [
|
||||
{
|
||||
kind: 'text',
|
||||
ranges: [
|
||||
leaves: [
|
||||
{
|
||||
kind: 'range',
|
||||
kind: 'leaf',
|
||||
text: 'one',
|
||||
marks: []
|
||||
}
|
||||
|
@@ -16,9 +16,9 @@ export const input = {
|
||||
nodes: [
|
||||
{
|
||||
kind: 'text',
|
||||
ranges: [
|
||||
leaves: [
|
||||
{
|
||||
kind: 'range',
|
||||
kind: 'leaf',
|
||||
text: ' ',
|
||||
marks: [],
|
||||
}
|
||||
|
@@ -16,9 +16,9 @@ export const input = {
|
||||
nodes: [
|
||||
{
|
||||
kind: 'text',
|
||||
ranges: [
|
||||
leaves: [
|
||||
{
|
||||
kind: 'range',
|
||||
kind: 'leaf',
|
||||
text: 'one',
|
||||
marks: [],
|
||||
}
|
||||
|
@@ -16,9 +16,9 @@ export const input = {
|
||||
nodes: [
|
||||
{
|
||||
kind: 'text',
|
||||
ranges: [
|
||||
leaves: [
|
||||
{
|
||||
kind: 'range',
|
||||
kind: 'leaf',
|
||||
text: '',
|
||||
marks: [],
|
||||
}
|
||||
@@ -32,9 +32,9 @@ export const input = {
|
||||
nodes: [
|
||||
{
|
||||
kind: 'text',
|
||||
ranges: [
|
||||
leaves: [
|
||||
{
|
||||
kind: 'range',
|
||||
kind: 'leaf',
|
||||
text: '',
|
||||
marks: [],
|
||||
}
|
||||
@@ -48,9 +48,9 @@ export const input = {
|
||||
nodes: [
|
||||
{
|
||||
kind: 'text',
|
||||
ranges: [
|
||||
leaves: [
|
||||
{
|
||||
kind: 'range',
|
||||
kind: 'leaf',
|
||||
text: 'one',
|
||||
marks: [],
|
||||
}
|
||||
@@ -60,9 +60,9 @@ export const input = {
|
||||
},
|
||||
{
|
||||
kind: 'text',
|
||||
ranges: [
|
||||
leaves: [
|
||||
{
|
||||
kind: 'range',
|
||||
kind: 'leaf',
|
||||
text: '',
|
||||
marks: [],
|
||||
}
|
||||
@@ -72,9 +72,9 @@ export const input = {
|
||||
},
|
||||
{
|
||||
kind: 'text',
|
||||
ranges: [
|
||||
leaves: [
|
||||
{
|
||||
kind: 'range',
|
||||
kind: 'leaf',
|
||||
text: '',
|
||||
marks: [],
|
||||
}
|
||||
|
@@ -16,9 +16,9 @@ export const input = {
|
||||
nodes: [
|
||||
{
|
||||
kind: 'text',
|
||||
ranges: [
|
||||
leaves: [
|
||||
{
|
||||
kind: 'range',
|
||||
kind: 'leaf',
|
||||
text: '',
|
||||
marks: [],
|
||||
}
|
||||
@@ -34,9 +34,9 @@ export const input = {
|
||||
nodes: [
|
||||
{
|
||||
kind: 'text',
|
||||
ranges: [
|
||||
leaves: [
|
||||
{
|
||||
kind: 'range',
|
||||
kind: 'leaf',
|
||||
text: 'one',
|
||||
marks: [],
|
||||
}
|
||||
@@ -46,9 +46,9 @@ export const input = {
|
||||
},
|
||||
{
|
||||
kind: 'text',
|
||||
ranges: [
|
||||
leaves: [
|
||||
{
|
||||
kind: 'range',
|
||||
kind: 'leaf',
|
||||
text: '',
|
||||
marks: [],
|
||||
}
|
||||
|
@@ -16,9 +16,9 @@ export const input = {
|
||||
nodes: [
|
||||
{
|
||||
kind: 'text',
|
||||
ranges: [
|
||||
leaves: [
|
||||
{
|
||||
kind: 'range',
|
||||
kind: 'leaf',
|
||||
text: '',
|
||||
marks: [],
|
||||
}
|
||||
@@ -32,9 +32,9 @@ export const input = {
|
||||
nodes: [
|
||||
{
|
||||
kind: 'text',
|
||||
ranges: [
|
||||
leaves: [
|
||||
{
|
||||
kind: 'range',
|
||||
kind: 'leaf',
|
||||
text: ' ',
|
||||
marks: [],
|
||||
}
|
||||
@@ -44,9 +44,9 @@ export const input = {
|
||||
},
|
||||
{
|
||||
kind: 'text',
|
||||
ranges: [
|
||||
leaves: [
|
||||
{
|
||||
kind: 'range',
|
||||
kind: 'leaf',
|
||||
text: '',
|
||||
marks: [],
|
||||
}
|
||||
|
@@ -16,9 +16,9 @@ export const input = {
|
||||
nodes: [
|
||||
{
|
||||
kind: 'text',
|
||||
ranges: [
|
||||
leaves: [
|
||||
{
|
||||
kind: 'range',
|
||||
kind: 'leaf',
|
||||
text: '',
|
||||
marks: [],
|
||||
}
|
||||
@@ -32,9 +32,9 @@ export const input = {
|
||||
nodes: [
|
||||
{
|
||||
kind: 'text',
|
||||
ranges: [
|
||||
leaves: [
|
||||
{
|
||||
kind: 'range',
|
||||
kind: 'leaf',
|
||||
text: 'one',
|
||||
marks: [],
|
||||
}
|
||||
@@ -44,9 +44,9 @@ export const input = {
|
||||
},
|
||||
{
|
||||
kind: 'text',
|
||||
ranges: [
|
||||
leaves: [
|
||||
{
|
||||
kind: 'range',
|
||||
kind: 'leaf',
|
||||
text: '',
|
||||
marks: [],
|
||||
}
|
||||
|
@@ -16,14 +16,14 @@ export const input = {
|
||||
nodes: [
|
||||
{
|
||||
kind: 'text',
|
||||
ranges: [
|
||||
leaves: [
|
||||
{
|
||||
kind: 'range',
|
||||
kind: 'leaves',
|
||||
text: 'o',
|
||||
marks: [],
|
||||
},
|
||||
{
|
||||
kind: 'range',
|
||||
kind: 'leaves',
|
||||
text: 'n',
|
||||
marks: [
|
||||
{
|
||||
@@ -34,7 +34,7 @@ export const input = {
|
||||
]
|
||||
},
|
||||
{
|
||||
kind: 'range',
|
||||
kind: 'leaves',
|
||||
text: 'e',
|
||||
marks: [],
|
||||
}
|
||||
|
@@ -34,10 +34,10 @@ export const output = {
|
||||
nodes: [
|
||||
{
|
||||
kind: 'text',
|
||||
ranges: [
|
||||
leaves: [
|
||||
{
|
||||
text: 'one',
|
||||
kind: 'range',
|
||||
kind: 'leaf',
|
||||
marks: []
|
||||
}
|
||||
]
|
||||
|
@@ -28,9 +28,9 @@ export const output = {
|
||||
nodes: [
|
||||
{
|
||||
kind: 'text',
|
||||
ranges: [
|
||||
leaves: [
|
||||
{
|
||||
kind: 'range',
|
||||
kind: 'leaf',
|
||||
text: 'one',
|
||||
marks: []
|
||||
}
|
||||
|
@@ -24,9 +24,9 @@ export const output = {
|
||||
nodes: [
|
||||
{
|
||||
kind: 'text',
|
||||
ranges: [
|
||||
leaves: [
|
||||
{
|
||||
kind: 'range',
|
||||
kind: 'leaf',
|
||||
text: ' ',
|
||||
marks: [],
|
||||
}
|
||||
|
@@ -26,9 +26,9 @@ export const output = {
|
||||
nodes: [
|
||||
{
|
||||
kind: 'text',
|
||||
ranges: [
|
||||
leaves: [
|
||||
{
|
||||
kind: 'range',
|
||||
kind: 'leaf',
|
||||
text: 'one',
|
||||
marks: [],
|
||||
}
|
||||
|
@@ -30,9 +30,9 @@ export const output = {
|
||||
nodes: [
|
||||
{
|
||||
kind: 'text',
|
||||
ranges: [
|
||||
leaves: [
|
||||
{
|
||||
kind: 'range',
|
||||
kind: 'leaf',
|
||||
text: '',
|
||||
marks: [],
|
||||
}
|
||||
@@ -46,9 +46,9 @@ export const output = {
|
||||
nodes: [
|
||||
{
|
||||
kind: 'text',
|
||||
ranges: [
|
||||
leaves: [
|
||||
{
|
||||
kind: 'range',
|
||||
kind: 'leaf',
|
||||
text: '',
|
||||
marks: [],
|
||||
}
|
||||
@@ -62,9 +62,9 @@ export const output = {
|
||||
nodes: [
|
||||
{
|
||||
kind: 'text',
|
||||
ranges: [
|
||||
leaves: [
|
||||
{
|
||||
kind: 'range',
|
||||
kind: 'leaf',
|
||||
text: 'one',
|
||||
marks: [],
|
||||
}
|
||||
@@ -74,9 +74,9 @@ export const output = {
|
||||
},
|
||||
{
|
||||
kind: 'text',
|
||||
ranges: [
|
||||
leaves: [
|
||||
{
|
||||
kind: 'range',
|
||||
kind: 'leaf',
|
||||
text: '',
|
||||
marks: [],
|
||||
}
|
||||
@@ -86,9 +86,9 @@ export const output = {
|
||||
},
|
||||
{
|
||||
kind: 'text',
|
||||
ranges: [
|
||||
leaves: [
|
||||
{
|
||||
kind: 'range',
|
||||
kind: 'leaf',
|
||||
text: '',
|
||||
marks: [],
|
||||
}
|
||||
|
@@ -28,9 +28,9 @@ export const output = {
|
||||
nodes: [
|
||||
{
|
||||
kind: 'text',
|
||||
ranges: [
|
||||
leaves: [
|
||||
{
|
||||
kind: 'range',
|
||||
kind: 'leaf',
|
||||
text: '',
|
||||
marks: [],
|
||||
}
|
||||
@@ -46,9 +46,9 @@ export const output = {
|
||||
nodes: [
|
||||
{
|
||||
kind: 'text',
|
||||
ranges: [
|
||||
leaves: [
|
||||
{
|
||||
kind: 'range',
|
||||
kind: 'leaf',
|
||||
text: 'one',
|
||||
marks: [],
|
||||
}
|
||||
@@ -58,9 +58,9 @@ export const output = {
|
||||
},
|
||||
{
|
||||
kind: 'text',
|
||||
ranges: [
|
||||
leaves: [
|
||||
{
|
||||
kind: 'range',
|
||||
kind: 'leaf',
|
||||
text: '',
|
||||
marks: [],
|
||||
}
|
||||
|
@@ -26,9 +26,9 @@ export const output = {
|
||||
nodes: [
|
||||
{
|
||||
kind: 'text',
|
||||
ranges: [
|
||||
leaves: [
|
||||
{
|
||||
kind: 'range',
|
||||
kind: 'leaf',
|
||||
text: '',
|
||||
marks: [],
|
||||
}
|
||||
@@ -42,9 +42,9 @@ export const output = {
|
||||
nodes: [
|
||||
{
|
||||
kind: 'text',
|
||||
ranges: [
|
||||
leaves: [
|
||||
{
|
||||
kind: 'range',
|
||||
kind: 'leaf',
|
||||
text: ' ',
|
||||
marks: [],
|
||||
}
|
||||
@@ -54,9 +54,9 @@ export const output = {
|
||||
},
|
||||
{
|
||||
kind: 'text',
|
||||
ranges: [
|
||||
leaves: [
|
||||
{
|
||||
kind: 'range',
|
||||
kind: 'leaf',
|
||||
text: '',
|
||||
marks: [],
|
||||
}
|
||||
|
@@ -28,9 +28,9 @@ export const output = {
|
||||
nodes: [
|
||||
{
|
||||
kind: 'text',
|
||||
ranges: [
|
||||
leaves: [
|
||||
{
|
||||
kind: 'range',
|
||||
kind: 'leaf',
|
||||
text: '',
|
||||
marks: [],
|
||||
}
|
||||
@@ -44,9 +44,9 @@ export const output = {
|
||||
nodes: [
|
||||
{
|
||||
kind: 'text',
|
||||
ranges: [
|
||||
leaves: [
|
||||
{
|
||||
kind: 'range',
|
||||
kind: 'leaf',
|
||||
text: 'one',
|
||||
marks: [],
|
||||
}
|
||||
@@ -56,9 +56,9 @@ export const output = {
|
||||
},
|
||||
{
|
||||
kind: 'text',
|
||||
ranges: [
|
||||
leaves: [
|
||||
{
|
||||
kind: 'range',
|
||||
kind: 'leaf',
|
||||
text: '',
|
||||
marks: [],
|
||||
}
|
||||
|
@@ -27,10 +27,10 @@ export const output = {
|
||||
nodes: [
|
||||
{
|
||||
kind: 'text',
|
||||
ranges: [
|
||||
leaves: [
|
||||
{
|
||||
text: 'one',
|
||||
kind: 'range',
|
||||
kind: 'leaf',
|
||||
marks: []
|
||||
}
|
||||
]
|
@@ -29,10 +29,10 @@ export const output = {
|
||||
{
|
||||
kind: 'text',
|
||||
key: '0',
|
||||
ranges: [
|
||||
leaves: [
|
||||
{
|
||||
text: 'one',
|
||||
kind: 'range',
|
||||
kind: 'leaf',
|
||||
marks: []
|
||||
}
|
||||
]
|
||||
|
@@ -29,10 +29,10 @@ export const output = {
|
||||
{
|
||||
kind: 'text',
|
||||
key: '0',
|
||||
ranges: [
|
||||
leaves: [
|
||||
{
|
||||
text: 'one',
|
||||
kind: 'range',
|
||||
kind: 'leaf',
|
||||
marks: []
|
||||
}
|
||||
]
|
||||
@@ -42,7 +42,7 @@ export const output = {
|
||||
]
|
||||
},
|
||||
selection: {
|
||||
kind: 'selection',
|
||||
kind: 'range',
|
||||
anchorKey: '0',
|
||||
anchorOffset: 0,
|
||||
focusKey: '0',
|
||||
|
@@ -26,10 +26,10 @@ export const output = {
|
||||
nodes: [
|
||||
{
|
||||
kind: 'text',
|
||||
ranges: [
|
||||
leaves: [
|
||||
{
|
||||
text: 'one',
|
||||
kind: 'range',
|
||||
kind: 'leaf',
|
||||
marks: []
|
||||
}
|
||||
]
|
||||
@@ -39,7 +39,7 @@ export const output = {
|
||||
]
|
||||
},
|
||||
selection: {
|
||||
kind: 'selection',
|
||||
kind: 'range',
|
||||
anchorPath: [0, 0],
|
||||
anchorOffset: 0,
|
||||
focusPath: [0, 0],
|
||||
|
@@ -26,14 +26,14 @@ export const output = {
|
||||
nodes: [
|
||||
{
|
||||
kind: 'text',
|
||||
ranges: [
|
||||
leaves: [
|
||||
{
|
||||
kind: 'range',
|
||||
kind: 'leaf',
|
||||
text: 'o',
|
||||
marks: [],
|
||||
},
|
||||
{
|
||||
kind: 'range',
|
||||
kind: 'leaf',
|
||||
text: 'n',
|
||||
marks: [
|
||||
{
|
||||
@@ -44,7 +44,7 @@ export const output = {
|
||||
]
|
||||
},
|
||||
{
|
||||
kind: 'range',
|
||||
kind: 'leaf',
|
||||
text: 'e',
|
||||
marks: [],
|
||||
}
|
||||
|
Reference in New Issue
Block a user