mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-08-13 18:53:59 +02:00
add paths to ranges (#1997)
#### Is this adding or improving a _feature_ or fixing a _bug_? Feature. #### What's the new behavior? This pull request adds paths to `Range` objects, including the selection. The paths and keys are kept in sync automatically, so that you can use whichever is ideal for your use case. This should allow us to use paths for lots of the internal logic, which are much quicker to work with than keys since they avoid having to lookup the key in the document and can just traverse right to the node in question. #### How does this change work? `Range` objects have two new properties: ```js range.anchorPath range.focusPath ``` (Eventually these will be `range.anchor.path` and `range.focus.path` when points are introduced.) When operations occur and whenever ranges are created/normalized, the paths are updated and kept in sync with the keys. #### Have you checked that...? <!-- Please run through this checklist for your pull request: --> * [x] The new code matches the existing patterns and styles. * [x] The tests pass with `yarn test`. * [x] The linter passes with `yarn lint`. (Fix errors with `yarn prettier`.) * [x] The relevant examples still work. (Run examples with `yarn watch`.) #### Does this fix any issues or need any specific reviewers? Fixes: https://github.com/ianstormtaylor/slate/issues/1408 Fixes: https://github.com/ianstormtaylor/slate/issues/1567
This commit is contained in:
@@ -67,7 +67,9 @@ These are changes like `blur()`, `collapseToStart()`, `moveToRangeOf()`, etc. th
|
||||
|
||||
### On a Specific Node
|
||||
|
||||
These are changes like `removeNodeByKey()`, `setNodeByKey()`, `removeMarkByKey()`, etc. that take a `key` string referring to a specific node, and then change that node in different ways. These are often what you use when making programmatic changes from inside your custom node components, where you already have a reference to `props.node.key`.
|
||||
There are two types of changes referring to specific nodes, either by `path` or by `key`. These are often what you use when making programmatic changes from inside your custom node components, where you already have a reference to `props.node.key`.
|
||||
|
||||
Path-based changes are ones like `removeNodeByPath()`, `insertNodeByPath()`, etc. that take a `path` pinpointing the node in the document. And key-based changes are ones like `removeNodeByKey()`, `setNodeByKey()`, `removeMarkByKey()`, etc. that take a `key` string referring to a specific node, and then change that node in different ways.
|
||||
|
||||
### On the Top-level Value
|
||||
|
||||
|
@@ -94,7 +94,7 @@ That all sounds pretty complex, but you don't have to think about it much, as lo
|
||||
|
||||
Just like in the DOM, you can reference a part of the document using a `Range`. And there's one special range that Slate keeps track of that refers to the user's current cursor selection, called the "selection".
|
||||
|
||||
Ranges are defined by an "anchor" and "focus" point. The anchor is where the range starts, and the focus is where it ends. And each point is a combination of a "key" referencing a specific node, and an "offset". This ends up looking like this:
|
||||
Ranges are defined by an "anchor" and "focus" point. The anchor is where the range starts, and the focus is where it ends. And each point is a combination of a "path" or "key" referencing a specific node, and an "offset". This ends up looking like this:
|
||||
|
||||
```js
|
||||
const range = Range.create({
|
||||
@@ -104,9 +104,17 @@ const range = Range.create({
|
||||
focusOffset: 4,
|
||||
isBackward: false,
|
||||
})
|
||||
|
||||
const range = Range.create({
|
||||
anchorPath: [0, 2, 1],
|
||||
anchorOffset: 0,
|
||||
focusPath: [0, 3, 2],
|
||||
focusOffset: 4,
|
||||
isBackward: false,
|
||||
})
|
||||
```
|
||||
|
||||
The more readable `node-a` name is just pseudocode, because Slate uses auto-incrementing numerical strings by default—`'1', '2', '3', ...` But the important part is that every node has a unique `key` property, and a range references nodes by their keys.
|
||||
The more readable `node-a` name is just pseudocode, because Slate uses auto-incrementing numerical strings by default—`'1', '2', '3', ...` But the important part is that every node has a unique `key` property, and a range can reference nodes by their keys.
|
||||
|
||||
The terms "anchor" and "focus" are borrowed from the DOM, where they mean the same thing. The anchor point isn't always _before_ the focus point in the document. Just like in the DOM, it depends on whether the range is backwards or forwards.
|
||||
|
||||
@@ -115,7 +123,7 @@ Here's how MDN explains it:
|
||||
> A user may make a selection from left to right (in document order) or right to left (reverse of document order). The anchor is where the user began the selection and the focus is where the user ends the selection. If you make a selection with a desktop mouse, the anchor is placed where you pressed the mouse button and the focus is placed where you released the mouse button. Anchor and focus should not be confused with the start and end positions of a selection, since anchor can be placed before the focus or vice versa, depending on the direction you made your selection.
|
||||
> — [`Selection`, MDN](https://developer.mozilla.org/en-US/docs/Web/API/Selection)
|
||||
|
||||
To make dealing with ranges easier though, they also provide "start" and "end" properties that take whether the range is forward or backward into account. The `startKey` and `startOffset` will always be before the `endKey` and `endOffset` in the document.
|
||||
To make dealing with ranges easier though, they also provide "start" and "end" properties that take whether the range is forward or backward into account. The `startKey` and `startPath` will always be before the `endKey` and `endPath` in the document.
|
||||
|
||||
One important thing to note is that the anchor and focus points of ranges **always reference the "leaf-most" text nodes**. They never reference blocks or inlines, always their child text nodes. This makes dealing with ranges a _lot_ easier.
|
||||
|
||||
|
@@ -44,21 +44,40 @@ Deeply filter the descendant nodes of a node by `iterator`.
|
||||
|
||||
### `findDescendant`
|
||||
|
||||
`findDescendant(iterator: Function) => Node || Void`
|
||||
`findDescendant(iterator: Function) => Node|Void`
|
||||
|
||||
Deeply find a descendant node by `iterator`.
|
||||
|
||||
### `getAncestors`
|
||||
|
||||
`getAncestors(path: List|Array) => List|Void`
|
||||
`getAncestors(key: String) => List|Void`
|
||||
|
||||
Get the ancestors of a descendant by `path` or `key`.
|
||||
|
||||
### `getBlocks`
|
||||
|
||||
`getBlocks() => List`
|
||||
|
||||
Get all of the bottom-most [`Block`](./block.md) node descendants.
|
||||
|
||||
### `getBlocksAtRange`
|
||||
|
||||
`getBlocksAtRange(range: Range) => List`
|
||||
|
||||
Get all of the bottom-most [`Block`](./block.md) nodes in a `range`.
|
||||
|
||||
### `getBlocks`
|
||||
### `getBlocksByType`
|
||||
|
||||
`getBlocks() => List`
|
||||
`getBlocksByType(type: String) => List`
|
||||
|
||||
Get all of the bottom-most [`Block`](./block.md) node descendants.
|
||||
Get all of the bottom-most [`Block`](./block.md) nodes by `type`.
|
||||
|
||||
### `getCharacters`
|
||||
|
||||
`getCharacters() => List`
|
||||
|
||||
Get a list of all of the [`Characters`](./character.md) in the node.
|
||||
|
||||
### `getCharactersAtRange`
|
||||
|
||||
@@ -68,43 +87,63 @@ Get a list of all of the [`Characters`](./character.md) in a `range`.
|
||||
|
||||
### `getChild`
|
||||
|
||||
`getChild(key: String || Node) => Node || Void`
|
||||
`getChild(path: List|Array) => Node|Void`
|
||||
`getChild(key: String) => Node|Void`
|
||||
|
||||
Get a child by `key`.
|
||||
|
||||
### `getClosestBlock`
|
||||
|
||||
`getClosestBlock(key: String || Node) => Node || Void`
|
||||
|
||||
Get the closest [`Block`](./block.md) node to a descendant node by `key`.
|
||||
|
||||
### `getClosestInline`
|
||||
|
||||
`getClosestInline(key: String || Node) => Node || Void`
|
||||
|
||||
Get the closest [`Inline`](./inline.md) node to a descendant node by `key`.
|
||||
Get a child by `path` or `key`.
|
||||
|
||||
### `getClosest`
|
||||
|
||||
`getClosest(key: String || Node, match: Function) => Node || Void`
|
||||
`getClosest(path: List|Array, match: Function) => Node|Void`
|
||||
`getClosest(key: String, match: Function) => Node|Void`
|
||||
|
||||
Get the closest parent node of a descendant node by `key` that matches a `match` function.
|
||||
Get the closest parent node of a descendant node by `path` or `key` that matches a `match` function.
|
||||
|
||||
### `getClosestBlock`
|
||||
|
||||
`getClosestBlock(path: List|Array) => Node|Void`
|
||||
`getClosestBlock(key: String) => Node|Void`
|
||||
|
||||
Get the closest [`Block`](./block.md) node to a descendant node by `path` or `key`.
|
||||
|
||||
### `getClosestInline`
|
||||
|
||||
`getClosestInline(path: List|Array) => Node|Void`
|
||||
`getClosestInline(key: String) => Node|Void`
|
||||
|
||||
Get the closest [`Inline`](./inline.md) node to a descendant node by `path` or `key`.
|
||||
|
||||
### `getClosestVoid`
|
||||
|
||||
`getClosestVoid(path: List|Array) => Node|Void`
|
||||
`getClosestVoid(key: String) => Node|Void`
|
||||
|
||||
Get the closest void parent of a descendant node by `path` or `key`.
|
||||
|
||||
### `getCommonAncestor`
|
||||
|
||||
`getCommonAncestor(path: List|Array) => Number`
|
||||
`getCommonAncestor(key: String) => Number`
|
||||
|
||||
Get the lowest common ancestor of a descendant node by `path` or `key`.
|
||||
|
||||
### `getDepth`
|
||||
|
||||
`getDepth(key: String || Node) => Number`
|
||||
`getDepth(path: List|Array) => Number`
|
||||
`getDepth(key: String) => Number`
|
||||
|
||||
Get the depth of a descendant node by `key`.
|
||||
Get the depth of a descendant node by `path` or `key`.
|
||||
|
||||
### `getDescendant`
|
||||
|
||||
`getDescendant(key: String || Node) => Node || Void`
|
||||
`getDescendant(path: List|Array) => Node|Void`
|
||||
`getDescendant(key: String) => Node|Void`
|
||||
|
||||
Get a descendant node by `key`.
|
||||
Get a descendant node by `path` or `key`.
|
||||
|
||||
### `getFirstText`
|
||||
|
||||
`getFirstText() => Node || Void`
|
||||
`getFirstText() => Text|Void`
|
||||
|
||||
Get the first child text node inside a node.
|
||||
|
||||
@@ -116,33 +155,44 @@ Get a document fragment of the nodes in a `range`.
|
||||
|
||||
### `getFurthest`
|
||||
|
||||
`getFurthest(key: String, iterator: Function) => Node || Null`
|
||||
`getFurthest(path: List|Array, iterator: Function) => Node|Null`
|
||||
`getFurthest(key: String, iterator: Function) => Node|Null`
|
||||
|
||||
Get the furthest parent of a node by `key` that matches an `iterator`.
|
||||
Get the furthest parent of a node by `path` or `key` that matches an `iterator`.
|
||||
|
||||
### `getFurthestAncestor`
|
||||
|
||||
`getFurthestAncestor(key: String) => Node || Null`
|
||||
`getFurthestAncestor(path: List|Array) => Node|Null`
|
||||
`getFurthestAncestor(key: String) => Node|Null`
|
||||
|
||||
Get the furthest ancestor of a node by `key`.
|
||||
Get the furthest ancestor of a node by `path` or `key`.
|
||||
|
||||
### `getFurthestBlock`
|
||||
|
||||
`getFurthestBlock(key: String) => Node || Null`
|
||||
`getFurthestBlock(path: List|Array) => Node|Null`
|
||||
`getFurthestBlock(key: String) => Node|Null`
|
||||
|
||||
Get the furthest block parent of a node by `key`.
|
||||
Get the furthest block parent of a node by `path` or `key`.
|
||||
|
||||
### `getFurthestInline`
|
||||
|
||||
`getFurthestInline(key: String) => Node || Null`
|
||||
`getFurthestInline(path: List|Array) => Node|Null`
|
||||
`getFurthestInline(key: String) => Node|Null`
|
||||
|
||||
Get the furthest inline parent of a node by `key`.
|
||||
Get the furthest inline parent of a node by `path` or `key`.
|
||||
|
||||
### `getFurthestOnlyChildAncestor`
|
||||
|
||||
`getFurthestOnlyChildAncestor(key: String) => Node || Null`
|
||||
`getFurthestOnlyChildAncestor(path: List|Array) => Node|Null`
|
||||
`getFurthestOnlyChildAncestor(key: String) => Node|Null`
|
||||
|
||||
Get the furthest ancestor of a node by `key` that has only one child.
|
||||
Get the furthest ancestor of a node by `path` or `key` that has only one child.
|
||||
|
||||
### `getInlines`
|
||||
|
||||
`getInlines() => List`
|
||||
|
||||
Get all of the top-most [`Inline`](./inline.md) nodes in a node.
|
||||
|
||||
### `getInlinesAtRange`
|
||||
|
||||
@@ -150,59 +200,119 @@ Get the furthest ancestor of a node by `key` that has only one child.
|
||||
|
||||
Get all of the top-most [`Inline`](./inline.md) nodes in a `range`.
|
||||
|
||||
### `getInlinesByType`
|
||||
|
||||
`getInlinesByType(type: string) => List`
|
||||
|
||||
Get all of the top-most [`Inline`](./inline.md) nodes by `type`.
|
||||
|
||||
### `getLastText`
|
||||
|
||||
`getLastText() => Node || Void`
|
||||
`getLastText() => Node|Void`
|
||||
|
||||
Get the last child text node inside a node.
|
||||
|
||||
### `getMarks`
|
||||
|
||||
`getMarks(range: Range) => Set`
|
||||
|
||||
Get a set of all of the marks in a node.
|
||||
|
||||
### `getMarksAtRange`
|
||||
|
||||
`getMarksAtRange(range: Range) => Set`
|
||||
|
||||
Get a set of all of the marks in a `range`.
|
||||
|
||||
### `getMarksByType`
|
||||
|
||||
`getMarksByType(type: String) => Set`
|
||||
|
||||
Get a set of all of the marks by `type`.
|
||||
|
||||
### `getNextBlock`
|
||||
|
||||
`getNextBlock(key: String || Node) => Node || Void`
|
||||
`getNextBlock(path: List|Array) => Node|Void`
|
||||
`getNextBlock(key: String) => Node|Void`
|
||||
|
||||
Get the next, bottom-most [`Block`](./block.md) node after a descendant by `key`.
|
||||
Get the next, bottom-most [`Block`](./block.md) node after a descendant by `path` or `key`.
|
||||
|
||||
### `getNextNode`
|
||||
|
||||
`getNextNode(path: List|Array) => Node|Void`
|
||||
`getNextNode(key: String) => Node|Void`
|
||||
|
||||
Get the next node in the tree of a descendant by `path` or `key`. This will not only check for siblings but instead move up the tree returning the next ancestor if no sibling is found.
|
||||
|
||||
### `getNextSibling`
|
||||
|
||||
`getNextSibling(key: String || Node) => Node || Void`
|
||||
`getNextSibling(path: List|Array) => Node|Void`
|
||||
`getNextSibling(key: String) => Node|Void`
|
||||
|
||||
Get the next sibling of a descendant by `key`.
|
||||
Get the next sibling of a descendant by `path` or `key`.
|
||||
|
||||
### `getNextText`
|
||||
|
||||
`getNextText(key: String || Node) => Node || Void`
|
||||
`getNextText(path: List|Array) => Node|Void`
|
||||
`getNextText(key: String) => Node|Void`
|
||||
|
||||
Get the next [`Text`](./text.md) node after a descendant by `key`.
|
||||
Get the next [`Text`](./text.md) node after a descendant by `path` or `key`.
|
||||
|
||||
### `getNode`
|
||||
|
||||
`getNode(path: List|Array) => Node|Void`
|
||||
`getNode(key: String) => Node|Void`
|
||||
|
||||
Get a node in the tree by `path` or `key`.
|
||||
|
||||
### `getOffset`
|
||||
|
||||
`getOffset(path: List|Array) => Number`
|
||||
`getOffset(key: String) => Number`
|
||||
|
||||
Get the text offset of a descendant in the tree by `path` or `key`.
|
||||
|
||||
### `getParent`
|
||||
|
||||
`getParent(key: String || Node) => Node || Void`
|
||||
`getParent(path: List|Array) => Node|Void`
|
||||
`getParent(key: String) => Node|Void`
|
||||
|
||||
Get the parent node of a descendant by `key`.
|
||||
Get the parent node of a descendant by `path` or `key`.
|
||||
|
||||
### `getPath`
|
||||
|
||||
`getPath(path: List|Array) => Node|Void`
|
||||
`getPath(key: String) => Node|Void`
|
||||
|
||||
Get the path to a descendant by `path` or `key`.
|
||||
|
||||
### `getPreviousBlock`
|
||||
|
||||
`getPreviousBlock(key: String || Node) => Node || Void`
|
||||
`getPreviousBlock(path: List|Array) => Node|Void`
|
||||
`getPreviousBlock(key: String) => Node|Void`
|
||||
|
||||
Get the previous, bottom-most [`Block`](./block.md) node before a descendant by `key`.
|
||||
Get the previous, bottom-most [`Block`](./block.md) node before a descendant by `path` or `key`.
|
||||
|
||||
### `getPreviousNode`
|
||||
|
||||
`getPreviousNode(path: List|Array) => Node|Void`
|
||||
`getPreviousNode(key: String) => Node|Void`
|
||||
|
||||
Get the previous node in the tree of a descendant by `path` or `key`. This will not only check for siblings but instead move up the tree returning the previous ancestor if no sibling is found.
|
||||
|
||||
### `getPreviousSibling`
|
||||
|
||||
`getPreviousSibling(key: String || Node) => Node || Void`
|
||||
`getPreviousSibling(path: List|Array) => Node|Void`
|
||||
`getPreviousSibling(key: String) => Node|Void`
|
||||
|
||||
Get the previous sibling of a descendant by `key`.
|
||||
Get the previous sibling of a descendant by `path` or `key`.
|
||||
|
||||
### `getPreviousText`
|
||||
|
||||
`getPreviousText(key: String || Node) => Node || Void`
|
||||
`getPreviousText(path: List|Array) => Node|Void`
|
||||
`getPreviousText(key: String) => Node|Void`
|
||||
|
||||
Get the previous [`Text`](./text.md) node before a descendant by `key`.
|
||||
Get the previous [`Text`](./text.md) node before a descendant by `path` or `key`.
|
||||
|
||||
### `getTextAtOffset`
|
||||
|
||||
@@ -210,6 +320,18 @@ Get the previous [`Text`](./text.md) node before a descendant by `key`.
|
||||
|
||||
Get the [`Text`](./text.md) node at an `offset`.
|
||||
|
||||
### `getTextDirection`
|
||||
|
||||
`getTextDirection() => String`
|
||||
|
||||
Get the direction of the text content in the node.
|
||||
|
||||
### `getTexts`
|
||||
|
||||
`getTexts(range: Range) => List`
|
||||
|
||||
Get all of the [`Text`](./text.md) nodes in a node.
|
||||
|
||||
### `getTextsAtRange`
|
||||
|
||||
`getTextsAtRange(range: Range) => List`
|
||||
@@ -218,12 +340,21 @@ Get all of the [`Text`](./text.md) nodes in a `range`.
|
||||
|
||||
### `hasChild`
|
||||
|
||||
`hasChild(key: String || Node) => Boolean`
|
||||
`hasChild(path: List|Array) => Boolean`
|
||||
`hasChild(key: String) => Boolean`
|
||||
|
||||
Check whether the node has a child node by `key`.
|
||||
Check whether the node has a child node by `path` or `key`.
|
||||
|
||||
### `hasDescendant`
|
||||
|
||||
`hasDescendant(key: String || Node) => Boolean`
|
||||
`hasDescendant(path: List|Array) => Boolean`
|
||||
`hasDescendant(key: String) => Boolean`
|
||||
|
||||
Check whether the node has a descendant node by `key`.
|
||||
Check whether the node has a descendant node by `path` or `key`.
|
||||
|
||||
### `hasNode`
|
||||
|
||||
`hasNode(path: List|Array) => Boolean`
|
||||
`hasNode(key: String) => Boolean`
|
||||
|
||||
Check whether a node exists in the tree by `path` or `key`.
|
||||
|
@@ -15,8 +15,10 @@ Often times, you don't need to specifically know which point is the "anchor" and
|
||||
```js
|
||||
Range({
|
||||
anchorKey: String,
|
||||
anchorPath: List,
|
||||
anchorOffset: Number,
|
||||
focusKey: String,
|
||||
focusPath: List,
|
||||
focusOffset: Number,
|
||||
isFocused: Boolean,
|
||||
isBackward: Boolean,
|
||||
@@ -29,6 +31,12 @@ Range({
|
||||
|
||||
The key of the text node at the range's anchor point.
|
||||
|
||||
### `anchorPath`
|
||||
|
||||
`List`
|
||||
|
||||
The path to the text node at the range's anchor point.
|
||||
|
||||
### `anchorOffset`
|
||||
|
||||
`Number`
|
||||
@@ -41,6 +49,12 @@ The number of characters from the start of the text node at the range's anchor p
|
||||
|
||||
The key of the text node at the range's focus point.
|
||||
|
||||
### `focusPath`
|
||||
|
||||
`List`
|
||||
|
||||
The path to the text node at the range's focus point.
|
||||
|
||||
### `focusOffset`
|
||||
|
||||
`Number`
|
||||
@@ -95,10 +109,14 @@ The opposite of `isBackward`, for convenience.
|
||||
|
||||
### `startKey`
|
||||
|
||||
### `startPath`
|
||||
|
||||
### `startOffset`
|
||||
|
||||
### `endKey`
|
||||
|
||||
### `endPath`
|
||||
|
||||
### `endOffset`
|
||||
|
||||
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.
|
||||
|
@@ -4,7 +4,7 @@ import { Value } from 'slate'
|
||||
import React from 'react'
|
||||
import initialValue from './value.json'
|
||||
import styled from 'react-emotion'
|
||||
import { Toolbar } from '../components'
|
||||
import { Icon, Toolbar } from '../components'
|
||||
|
||||
/**
|
||||
* Some styled components for the search box.
|
||||
@@ -16,7 +16,7 @@ const SearchWrapper = styled('div')`
|
||||
position: relative;
|
||||
`
|
||||
|
||||
const SearchIcon = styled('icon')`
|
||||
const SearchIcon = styled(Icon)`
|
||||
position: absolute;
|
||||
top: 0.5em;
|
||||
left: 0.5em;
|
||||
@@ -130,7 +130,7 @@ class SearchHighlighting extends React.Component {
|
||||
focusKey: key,
|
||||
focusOffset: offset,
|
||||
marks: [{ type: 'highlight' }],
|
||||
atomic: true,
|
||||
isAtomic: true,
|
||||
})
|
||||
}
|
||||
|
||||
|
@@ -20,9 +20,9 @@
|
||||
"copy-webpack-plugin": "^4.4.1",
|
||||
"cross-env": "^5.1.3",
|
||||
"css-loader": "^0.28.9",
|
||||
"emojis": "^1.0.10",
|
||||
"emotion": "^9.2.4",
|
||||
"eslint": "^4.19.1",
|
||||
"emojis": "^1.0.10",
|
||||
"eslint-config-prettier": "^2.9.0",
|
||||
"eslint-plugin-import": "^2.8.0",
|
||||
"eslint-plugin-prettier": "^2.5.0",
|
||||
@@ -71,6 +71,7 @@
|
||||
"source-map-support": "^0.4.0",
|
||||
"style-loader": "^0.20.2",
|
||||
"to-camel-case": "^1.0.0",
|
||||
"to-snake-case": "^1.0.0",
|
||||
"uglifyjs-webpack-plugin": "^1.1.8",
|
||||
"webpack": "^3.11.0",
|
||||
"webpack-dev-server": "^2.11.1"
|
||||
|
@@ -8,7 +8,7 @@ This document maintains a list of changes to the `slate-base64-serializer` packa
|
||||
|
||||
###### BREAKING
|
||||
|
||||
* **Updated to work with `slate@0.29.0`.** This is required because `slate-base64-serializer` needs access to the new `Value` model.
|
||||
**Updated to work with `slate@0.29.0`.** This is required because `slate-base64-serializer` needs access to the new `Value` model.
|
||||
|
||||
---
|
||||
|
||||
|
@@ -7,6 +7,6 @@
|
||||
*/
|
||||
|
||||
describe('slate-dev-benchmark', () => {
|
||||
require('./tries/')
|
||||
require('./time/')
|
||||
// require('./tries/')
|
||||
// require('./time/')
|
||||
})
|
||||
|
@@ -1,9 +0,0 @@
|
||||
# Changelog
|
||||
|
||||
This document maintains a list of changes to the `slate-dev-environment` package with each new version. Until `1.0.0` is released, breaking changes will be added as minor version bumps, and smaller changes won't be accounted for since the library is moving quickly.
|
||||
|
||||
---
|
||||
|
||||
### `0.1.0` — April 5, 2018
|
||||
|
||||
:tada:
|
@@ -1,9 +0,0 @@
|
||||
# Changelog
|
||||
|
||||
This document maintains a list of changes to the `slate-dev-logger` package with each new version. Until `1.0.0` is released, breaking changes will be added as minor version bumps, and smaller changes won't be accounted for since the library is moving quickly.
|
||||
|
||||
---
|
||||
|
||||
### `0.1.0` — September 17, 2017
|
||||
|
||||
:tada:
|
@@ -8,7 +8,7 @@ This document maintains a list of changes to the `slate-html-serializer` package
|
||||
|
||||
###### BREAKING
|
||||
|
||||
* **Returning `null` now ignores the node.** Previously it would be treated the same as `undefined`, which will move on to the next rule in the stack. Now it ignores the node and moves onto the next node instead.
|
||||
**Returning `null` now ignores the node.** Previously it would be treated the same as `undefined`, which will move on to the next rule in the stack. Now it ignores the node and moves onto the next node instead.
|
||||
|
||||
---
|
||||
|
||||
@@ -16,9 +16,9 @@ This document maintains a list of changes to the `slate-html-serializer` package
|
||||
|
||||
###### BREAKING
|
||||
|
||||
* **The `kind` property of Slate objects has been renamed to `object`.** This is to reduce the confusion over the difference between "kind" and "type" which are practically synonyms. The "object" name was chosen to match the Stripe API, since it seems like a sensible choice and reads much more nicely when looking through JSON.
|
||||
**The `kind` property of Slate objects has been renamed to `object`.** This is to reduce the confusion over the difference between "kind" and "type" which are practically synonyms. The "object" name was chosen to match the Stripe API, since it seems like a sensible choice and reads much more nicely when looking through JSON.
|
||||
|
||||
* **Serializing with `parse5` is no longer possible.** The codebase previously made concessions to allow this, but it was never a good idea because `parse5` does not match the `DOMParser` behavior exactly. Instead, you should use `jsdom` to get a matching behavior, otherwise your serialization rules need to account for two slightly different syntax trees.
|
||||
**Serializing with `parse5` is no longer possible.** The codebase previously made concessions to allow this, but it was never a good idea because `parse5` does not match the `DOMParser` behavior exactly. Instead, you should use `jsdom` to get a matching behavior, otherwise your serialization rules need to account for two slightly different syntax trees.
|
||||
|
||||
---
|
||||
|
||||
@@ -26,7 +26,7 @@ This document maintains a list of changes to the `slate-html-serializer` package
|
||||
|
||||
###### BREAKING
|
||||
|
||||
* **Remove all previously deprecated code paths.** This helps to reduce some of the complexity in Slate by not having to handle these code paths anymore. And it helps to reduce file size. When upgrading, it's _highly_ recommended that you upgrade to the previous version first and ensure there are no deprecation warnings being logged, then upgrade to this version.
|
||||
**Remove all previously deprecated code paths.** This helps to reduce some of the complexity in Slate by not having to handle these code paths anymore. And it helps to reduce file size. When upgrading, it's _highly_ recommended that you upgrade to the previous version first and ensure there are no deprecation warnings being logged, then upgrade to this version.
|
||||
|
||||
---
|
||||
|
||||
@@ -34,7 +34,7 @@ This document maintains a list of changes to the `slate-html-serializer` package
|
||||
|
||||
###### BREAKING
|
||||
|
||||
* **Updated to work with `slate@0.29.0`.** This is required because `slate-html-serializer` needs access to the new `Value` model.
|
||||
**Updated to work with `slate@0.29.0`.** This is required because `slate-html-serializer` needs access to the new `Value` model.
|
||||
|
||||
---
|
||||
|
||||
@@ -42,7 +42,7 @@ This document maintains a list of changes to the `slate-html-serializer` package
|
||||
|
||||
###### BREAKING
|
||||
|
||||
* **Updated work with `slate@0.27.0`.** The new version of Slate renames the old `Range` model to `Leaf`, and the old `Selection` model to `Range`.
|
||||
**Updated work with `slate@0.27.0`.** The new version of Slate renames the old `Range` model to `Leaf`, and the old `Selection` model to `Range`.
|
||||
|
||||
---
|
||||
|
||||
|
@@ -6,7 +6,7 @@ import Html from '..'
|
||||
import assert from 'assert'
|
||||
import fs from 'fs'
|
||||
import { JSDOM } from 'jsdom' // eslint-disable-line import/no-extraneous-dependencies
|
||||
import { Value, resetKeyGenerator } from 'slate'
|
||||
import { Value, KeyUtils } from 'slate'
|
||||
import { basename, extname, resolve } from 'path'
|
||||
|
||||
/**
|
||||
@@ -14,7 +14,7 @@ import { basename, extname, resolve } from 'path'
|
||||
*/
|
||||
|
||||
beforeEach(() => {
|
||||
resetKeyGenerator()
|
||||
KeyUtils.resetGenerator()
|
||||
})
|
||||
|
||||
/**
|
||||
|
@@ -4,11 +4,19 @@ This document maintains a list of changes to the `slate-hyperscript` package wit
|
||||
|
||||
---
|
||||
|
||||
### `0.6.0` — July 27, 2018
|
||||
|
||||
###### NEW
|
||||
|
||||
**Updated to work with the `slate@0.35.0` with paths.** The original logic for selections and decorations didn't account for paths properly. This isn't a breaking change, but to use this library with the latest Slate you'll need to upgrade.
|
||||
|
||||
---
|
||||
|
||||
### `0.5.0` — January 4, 2018
|
||||
|
||||
###### BREAKING
|
||||
|
||||
* **The `kind` property of Slate objects has been renamed to `object`.** This is to reduce the confusion over the difference between "kind" and "type" which are practically synonyms. The "object" name was chosen to match the Stripe API, since it seems like a sensible choice and reads much more nicely when looking through JSON.
|
||||
**The `kind` property of Slate objects has been renamed to `object`.** This is to reduce the confusion over the difference between "kind" and "type" which are practically synonyms. The "object" name was chosen to match the Stripe API, since it seems like a sensible choice and reads much more nicely when looking through JSON.
|
||||
|
||||
---
|
||||
|
||||
@@ -16,7 +24,7 @@ This document maintains a list of changes to the `slate-hyperscript` package wit
|
||||
|
||||
###### BREAKING
|
||||
|
||||
* **Remove all previously deprecated code paths.** This helps to reduce some of the complexity in Slate by not having to handle these code paths anymore. And it helps to reduce file size. When upgrading, it's _highly_ recommended that you upgrade to the previous version first and ensure there are no deprecation warnings being logged, then upgrade to this version.
|
||||
**Remove all previously deprecated code paths.** This helps to reduce some of the complexity in Slate by not having to handle these code paths anymore. And it helps to reduce file size. When upgrading, it's _highly_ recommended that you upgrade to the previous version first and ensure there are no deprecation warnings being logged, then upgrade to this version.
|
||||
|
||||
---
|
||||
|
||||
@@ -24,11 +32,11 @@ This document maintains a list of changes to the `slate-hyperscript` package wit
|
||||
|
||||
###### BREAKING
|
||||
|
||||
* **Updated to work with `slate@0.29.0`.** This is required because `slate-hyperscript` needs access to the new `Value` model.
|
||||
**Updated to work with `slate@0.29.0`.** This is required because `slate-hyperscript` needs access to the new `Value` model.
|
||||
|
||||
###### DEPRECATED
|
||||
|
||||
* **The `<state>` tag has been renamed to `<value>`.** This is to stay in line with the newest version of Slate where the `State` object was renamed to `Value`.
|
||||
**The `<state>` tag has been renamed to `<value>`.** This is to stay in line with the newest version of Slate where the `State` object was renamed to `Value`.
|
||||
|
||||
---
|
||||
|
||||
@@ -36,7 +44,7 @@ This document maintains a list of changes to the `slate-hyperscript` package wit
|
||||
|
||||
###### BREAKING
|
||||
|
||||
* **Updated work with `slate@0.27.0`.** The new version of Slate renames the old `Range` model to `Leaf`, and the old `Selection` model to `Range`.
|
||||
**Updated work with `slate@0.27.0`.** The new version of Slate renames the old `Range` model to `Leaf`, and the old `Selection` model to `Range`.
|
||||
|
||||
---
|
||||
|
||||
|
@@ -18,7 +18,7 @@
|
||||
"slate-dev-logger": "^0.1.39"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"slate": ">=0.32.0"
|
||||
"slate": ">=0.35.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"mocha": "^2.5.3",
|
||||
|
@@ -201,18 +201,17 @@ const CREATORS = {
|
||||
)
|
||||
}
|
||||
|
||||
if (!isEmpty(props)) {
|
||||
selection = selection.merge(props).normalize(document)
|
||||
}
|
||||
|
||||
let value = Value.fromJSON({ data, document, selection }, { normalize })
|
||||
|
||||
// apply any decorations built
|
||||
if (!isEmpty(props)) {
|
||||
selection = selection.merge(props).normalize(value.document)
|
||||
value = value.set('selection', selection)
|
||||
}
|
||||
|
||||
if (decorations.length > 0) {
|
||||
value = value
|
||||
.change()
|
||||
.setValue({ decorations: decorations.map(d => d.normalize(document)) })
|
||||
.value
|
||||
decorations = decorations.map(d => d.normalize(value.document))
|
||||
decorations = Range.createList(decorations)
|
||||
value = value.set('decorations', decorations)
|
||||
}
|
||||
|
||||
return value
|
||||
|
@@ -4,7 +4,7 @@ import h from '../..'
|
||||
|
||||
export const input = (
|
||||
<document>
|
||||
<block type="paragraph">Single block</block>
|
||||
<block type="paragraph">word</block>
|
||||
</document>
|
||||
)
|
||||
|
||||
@@ -23,7 +23,7 @@ export const output = {
|
||||
leaves: [
|
||||
{
|
||||
object: 'leaf',
|
||||
text: 'Single block',
|
||||
text: 'word',
|
||||
marks: [],
|
||||
},
|
||||
],
|
@@ -6,42 +6,38 @@ export const input = (
|
||||
<value>
|
||||
<document>
|
||||
<block type="paragraph">
|
||||
<text>
|
||||
This is{' '}
|
||||
<text key="100">
|
||||
a paragraph with a cursor position (closed selection).
|
||||
</text>
|
||||
</text>
|
||||
w<anchor />or<focus />d
|
||||
</block>
|
||||
</document>
|
||||
<selection
|
||||
anchorKey="100"
|
||||
anchorOffset={30}
|
||||
focusKey="100"
|
||||
focusOffset={30}
|
||||
/>
|
||||
</value>
|
||||
)
|
||||
|
||||
export const options = {
|
||||
preserveSelection: true,
|
||||
preserveKeys: true,
|
||||
}
|
||||
|
||||
export const output = {
|
||||
object: 'value',
|
||||
document: {
|
||||
object: 'document',
|
||||
key: '3',
|
||||
data: {},
|
||||
nodes: [
|
||||
{
|
||||
object: 'block',
|
||||
key: '1',
|
||||
type: 'paragraph',
|
||||
isVoid: false,
|
||||
data: {},
|
||||
nodes: [
|
||||
{
|
||||
object: 'text',
|
||||
key: '0',
|
||||
leaves: [
|
||||
{
|
||||
object: 'leaf',
|
||||
text:
|
||||
'This is a paragraph with a cursor position (closed selection).',
|
||||
text: 'word',
|
||||
marks: [],
|
||||
},
|
||||
],
|
||||
@@ -50,12 +46,17 @@ export const output = {
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
export const expectSelection = {
|
||||
isCollapsed: true,
|
||||
anchorOffset: 30,
|
||||
focusOffset: 30,
|
||||
anchorKey: input.texts.get(0).key,
|
||||
focusKey: input.texts.get(0).key,
|
||||
selection: {
|
||||
object: 'range',
|
||||
anchorKey: '0',
|
||||
anchorPath: [0, 0],
|
||||
anchorOffset: 1,
|
||||
focusKey: '0',
|
||||
focusPath: [0, 0],
|
||||
focusOffset: 3,
|
||||
isBackward: false,
|
||||
isFocused: true,
|
||||
isAtomic: false,
|
||||
marks: null,
|
||||
},
|
||||
}
|
151
packages/slate-hyperscript/test/fixtures/cursor-across-blocks-and-inlines.js
vendored
Normal file
151
packages/slate-hyperscript/test/fixtures/cursor-across-blocks-and-inlines.js
vendored
Normal file
@@ -0,0 +1,151 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../..'
|
||||
|
||||
export const input = (
|
||||
<value>
|
||||
<document>
|
||||
<block type="paragraph">
|
||||
<inline type="link">
|
||||
on<anchor />e
|
||||
</inline>
|
||||
</block>
|
||||
<block type="paragraph">
|
||||
<inline type="link">
|
||||
t<focus />wo
|
||||
</inline>
|
||||
</block>
|
||||
</document>
|
||||
</value>
|
||||
)
|
||||
|
||||
export const options = {
|
||||
preserveSelection: true,
|
||||
preserveKeys: true,
|
||||
}
|
||||
|
||||
export const output = {
|
||||
object: 'value',
|
||||
document: {
|
||||
object: 'document',
|
||||
key: '10',
|
||||
data: {},
|
||||
nodes: [
|
||||
{
|
||||
object: 'block',
|
||||
key: '3',
|
||||
type: 'paragraph',
|
||||
isVoid: false,
|
||||
data: {},
|
||||
nodes: [
|
||||
{
|
||||
object: 'text',
|
||||
key: '11',
|
||||
leaves: [
|
||||
{
|
||||
object: 'leaf',
|
||||
text: '',
|
||||
marks: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
object: 'inline',
|
||||
key: '1',
|
||||
type: 'link',
|
||||
isVoid: false,
|
||||
data: {},
|
||||
nodes: [
|
||||
{
|
||||
object: 'text',
|
||||
key: '0',
|
||||
leaves: [
|
||||
{
|
||||
object: 'leaf',
|
||||
text: 'one',
|
||||
marks: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
object: 'text',
|
||||
key: '12',
|
||||
leaves: [
|
||||
{
|
||||
object: 'leaf',
|
||||
text: '',
|
||||
marks: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
object: 'block',
|
||||
key: '7',
|
||||
type: 'paragraph',
|
||||
isVoid: false,
|
||||
data: {},
|
||||
nodes: [
|
||||
{
|
||||
object: 'text',
|
||||
key: '13',
|
||||
leaves: [
|
||||
{
|
||||
object: 'leaf',
|
||||
text: '',
|
||||
marks: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
object: 'inline',
|
||||
key: '5',
|
||||
type: 'link',
|
||||
isVoid: false,
|
||||
data: {},
|
||||
nodes: [
|
||||
{
|
||||
object: 'text',
|
||||
key: '4',
|
||||
leaves: [
|
||||
{
|
||||
object: 'leaf',
|
||||
text: 'two',
|
||||
marks: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
object: 'text',
|
||||
key: '14',
|
||||
leaves: [
|
||||
{
|
||||
object: 'leaf',
|
||||
text: '',
|
||||
marks: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
selection: {
|
||||
object: 'range',
|
||||
anchorKey: '0',
|
||||
anchorPath: [0, 1, 0],
|
||||
anchorOffset: 2,
|
||||
focusKey: '4',
|
||||
focusPath: [1, 1, 0],
|
||||
focusOffset: 1,
|
||||
isBackward: false,
|
||||
isFocused: true,
|
||||
isAtomic: false,
|
||||
marks: null,
|
||||
},
|
||||
}
|
@@ -6,33 +6,41 @@ export const input = (
|
||||
<value>
|
||||
<document>
|
||||
<block type="paragraph">
|
||||
This is one <anchor />block.
|
||||
one<anchor />
|
||||
</block>
|
||||
<block type="paragraph">
|
||||
This is block<focus /> two.
|
||||
two<focus />
|
||||
</block>
|
||||
</document>
|
||||
</value>
|
||||
)
|
||||
|
||||
export const options = {
|
||||
preserveSelection: true,
|
||||
preserveKeys: true,
|
||||
}
|
||||
|
||||
export const output = {
|
||||
object: 'value',
|
||||
document: {
|
||||
object: 'document',
|
||||
key: '6',
|
||||
data: {},
|
||||
nodes: [
|
||||
{
|
||||
object: 'block',
|
||||
key: '1',
|
||||
type: 'paragraph',
|
||||
isVoid: false,
|
||||
data: {},
|
||||
nodes: [
|
||||
{
|
||||
object: 'text',
|
||||
key: '0',
|
||||
leaves: [
|
||||
{
|
||||
object: 'leaf',
|
||||
text: 'This is one block.',
|
||||
text: 'one',
|
||||
marks: [],
|
||||
},
|
||||
],
|
||||
@@ -41,16 +49,18 @@ export const output = {
|
||||
},
|
||||
{
|
||||
object: 'block',
|
||||
key: '3',
|
||||
type: 'paragraph',
|
||||
isVoid: false,
|
||||
data: {},
|
||||
nodes: [
|
||||
{
|
||||
object: 'text',
|
||||
key: '2',
|
||||
leaves: [
|
||||
{
|
||||
object: 'leaf',
|
||||
text: 'This is block two.',
|
||||
text: 'two',
|
||||
marks: [],
|
||||
},
|
||||
],
|
||||
@@ -59,12 +69,17 @@ export const output = {
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
export const expectSelection = {
|
||||
isCollapsed: false,
|
||||
anchorOffset: 12,
|
||||
focusOffset: 13,
|
||||
anchorKey: input.texts.get(0).key,
|
||||
focusKey: input.texts.get(1).key,
|
||||
selection: {
|
||||
object: 'range',
|
||||
anchorKey: '0',
|
||||
anchorPath: [0, 0],
|
||||
anchorOffset: 3,
|
||||
focusKey: '2',
|
||||
focusPath: [1, 0],
|
||||
focusOffset: 3,
|
||||
isBackward: false,
|
||||
isFocused: true,
|
||||
isAtomic: false,
|
||||
marks: null,
|
||||
},
|
||||
}
|
85
packages/slate-hyperscript/test/fixtures/cursor-across-blocks-middle.js
vendored
Normal file
85
packages/slate-hyperscript/test/fixtures/cursor-across-blocks-middle.js
vendored
Normal file
@@ -0,0 +1,85 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../..'
|
||||
|
||||
export const input = (
|
||||
<value>
|
||||
<document>
|
||||
<block type="paragraph">
|
||||
on<anchor />e
|
||||
</block>
|
||||
<block type="paragraph">
|
||||
t<focus />wo
|
||||
</block>
|
||||
</document>
|
||||
</value>
|
||||
)
|
||||
|
||||
export const options = {
|
||||
preserveSelection: true,
|
||||
preserveKeys: true,
|
||||
}
|
||||
|
||||
export const output = {
|
||||
object: 'value',
|
||||
document: {
|
||||
object: 'document',
|
||||
key: '6',
|
||||
data: {},
|
||||
nodes: [
|
||||
{
|
||||
object: 'block',
|
||||
key: '1',
|
||||
type: 'paragraph',
|
||||
isVoid: false,
|
||||
data: {},
|
||||
nodes: [
|
||||
{
|
||||
object: 'text',
|
||||
key: '0',
|
||||
leaves: [
|
||||
{
|
||||
object: 'leaf',
|
||||
text: 'one',
|
||||
marks: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
object: 'block',
|
||||
key: '3',
|
||||
type: 'paragraph',
|
||||
isVoid: false,
|
||||
data: {},
|
||||
nodes: [
|
||||
{
|
||||
object: 'text',
|
||||
key: '2',
|
||||
leaves: [
|
||||
{
|
||||
object: 'leaf',
|
||||
text: 'two',
|
||||
marks: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
selection: {
|
||||
object: 'range',
|
||||
anchorKey: '0',
|
||||
anchorPath: [0, 0],
|
||||
anchorOffset: 2,
|
||||
focusKey: '2',
|
||||
focusPath: [1, 0],
|
||||
focusOffset: 1,
|
||||
isBackward: false,
|
||||
isFocused: true,
|
||||
isAtomic: false,
|
||||
marks: null,
|
||||
},
|
||||
}
|
85
packages/slate-hyperscript/test/fixtures/cursor-across-blocks-start.js
vendored
Normal file
85
packages/slate-hyperscript/test/fixtures/cursor-across-blocks-start.js
vendored
Normal file
@@ -0,0 +1,85 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../..'
|
||||
|
||||
export const input = (
|
||||
<value>
|
||||
<document>
|
||||
<block type="paragraph">
|
||||
<anchor />one
|
||||
</block>
|
||||
<block type="paragraph">
|
||||
<focus />two
|
||||
</block>
|
||||
</document>
|
||||
</value>
|
||||
)
|
||||
|
||||
export const options = {
|
||||
preserveSelection: true,
|
||||
preserveKeys: true,
|
||||
}
|
||||
|
||||
export const output = {
|
||||
object: 'value',
|
||||
document: {
|
||||
object: 'document',
|
||||
key: '6',
|
||||
data: {},
|
||||
nodes: [
|
||||
{
|
||||
object: 'block',
|
||||
key: '1',
|
||||
type: 'paragraph',
|
||||
isVoid: false,
|
||||
data: {},
|
||||
nodes: [
|
||||
{
|
||||
object: 'text',
|
||||
key: '0',
|
||||
leaves: [
|
||||
{
|
||||
object: 'leaf',
|
||||
text: 'one',
|
||||
marks: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
object: 'block',
|
||||
key: '3',
|
||||
type: 'paragraph',
|
||||
isVoid: false,
|
||||
data: {},
|
||||
nodes: [
|
||||
{
|
||||
object: 'text',
|
||||
key: '2',
|
||||
leaves: [
|
||||
{
|
||||
object: 'leaf',
|
||||
text: 'two',
|
||||
marks: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
selection: {
|
||||
object: 'range',
|
||||
anchorKey: '0',
|
||||
anchorPath: [0, 0],
|
||||
anchorOffset: 0,
|
||||
focusKey: '2',
|
||||
focusPath: [1, 0],
|
||||
focusOffset: 0,
|
||||
isBackward: false,
|
||||
isFocused: true,
|
||||
isAtomic: false,
|
||||
marks: null,
|
||||
},
|
||||
}
|
106
packages/slate-hyperscript/test/fixtures/cursor-across-multiple-blocks-end.js
vendored
Normal file
106
packages/slate-hyperscript/test/fixtures/cursor-across-multiple-blocks-end.js
vendored
Normal file
@@ -0,0 +1,106 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../..'
|
||||
|
||||
export const input = (
|
||||
<value>
|
||||
<document>
|
||||
<block type="paragraph">
|
||||
one<anchor />
|
||||
</block>
|
||||
<block type="paragraph">two</block>
|
||||
<block type="paragraph">
|
||||
three<focus />
|
||||
</block>
|
||||
</document>
|
||||
</value>
|
||||
)
|
||||
|
||||
export const options = {
|
||||
preserveSelection: true,
|
||||
preserveKeys: true,
|
||||
}
|
||||
|
||||
export const output = {
|
||||
object: 'value',
|
||||
document: {
|
||||
object: 'document',
|
||||
key: '9',
|
||||
data: {},
|
||||
nodes: [
|
||||
{
|
||||
object: 'block',
|
||||
key: '1',
|
||||
type: 'paragraph',
|
||||
isVoid: false,
|
||||
data: {},
|
||||
nodes: [
|
||||
{
|
||||
object: 'text',
|
||||
key: '0',
|
||||
leaves: [
|
||||
{
|
||||
object: 'leaf',
|
||||
text: 'one',
|
||||
marks: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
object: 'block',
|
||||
key: '3',
|
||||
type: 'paragraph',
|
||||
isVoid: false,
|
||||
data: {},
|
||||
nodes: [
|
||||
{
|
||||
object: 'text',
|
||||
key: '2',
|
||||
leaves: [
|
||||
{
|
||||
object: 'leaf',
|
||||
text: 'two',
|
||||
marks: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
object: 'block',
|
||||
key: '5',
|
||||
type: 'paragraph',
|
||||
isVoid: false,
|
||||
data: {},
|
||||
nodes: [
|
||||
{
|
||||
object: 'text',
|
||||
key: '4',
|
||||
leaves: [
|
||||
{
|
||||
object: 'leaf',
|
||||
text: 'three',
|
||||
marks: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
selection: {
|
||||
object: 'range',
|
||||
anchorKey: '0',
|
||||
anchorPath: [0, 0],
|
||||
anchorOffset: 3,
|
||||
focusKey: '4',
|
||||
focusPath: [2, 0],
|
||||
focusOffset: 5,
|
||||
isBackward: false,
|
||||
isFocused: true,
|
||||
isAtomic: false,
|
||||
marks: null,
|
||||
},
|
||||
}
|
106
packages/slate-hyperscript/test/fixtures/cursor-across-multiple-blocks-middle.js
vendored
Normal file
106
packages/slate-hyperscript/test/fixtures/cursor-across-multiple-blocks-middle.js
vendored
Normal file
@@ -0,0 +1,106 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../..'
|
||||
|
||||
export const input = (
|
||||
<value>
|
||||
<document>
|
||||
<block type="paragraph">
|
||||
on<anchor />e
|
||||
</block>
|
||||
<block type="paragraph">two</block>
|
||||
<block type="paragraph">
|
||||
t<focus />hree
|
||||
</block>
|
||||
</document>
|
||||
</value>
|
||||
)
|
||||
|
||||
export const options = {
|
||||
preserveSelection: true,
|
||||
preserveKeys: true,
|
||||
}
|
||||
|
||||
export const output = {
|
||||
object: 'value',
|
||||
document: {
|
||||
object: 'document',
|
||||
key: '9',
|
||||
data: {},
|
||||
nodes: [
|
||||
{
|
||||
object: 'block',
|
||||
key: '1',
|
||||
type: 'paragraph',
|
||||
isVoid: false,
|
||||
data: {},
|
||||
nodes: [
|
||||
{
|
||||
object: 'text',
|
||||
key: '0',
|
||||
leaves: [
|
||||
{
|
||||
object: 'leaf',
|
||||
text: 'one',
|
||||
marks: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
object: 'block',
|
||||
key: '3',
|
||||
type: 'paragraph',
|
||||
isVoid: false,
|
||||
data: {},
|
||||
nodes: [
|
||||
{
|
||||
object: 'text',
|
||||
key: '2',
|
||||
leaves: [
|
||||
{
|
||||
object: 'leaf',
|
||||
text: 'two',
|
||||
marks: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
object: 'block',
|
||||
key: '5',
|
||||
type: 'paragraph',
|
||||
isVoid: false,
|
||||
data: {},
|
||||
nodes: [
|
||||
{
|
||||
object: 'text',
|
||||
key: '4',
|
||||
leaves: [
|
||||
{
|
||||
object: 'leaf',
|
||||
text: 'three',
|
||||
marks: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
selection: {
|
||||
object: 'range',
|
||||
anchorKey: '0',
|
||||
anchorPath: [0, 0],
|
||||
anchorOffset: 2,
|
||||
focusKey: '4',
|
||||
focusPath: [2, 0],
|
||||
focusOffset: 1,
|
||||
isBackward: false,
|
||||
isFocused: true,
|
||||
isAtomic: false,
|
||||
marks: null,
|
||||
},
|
||||
}
|
106
packages/slate-hyperscript/test/fixtures/cursor-across-multiple-blocks-start.js
vendored
Normal file
106
packages/slate-hyperscript/test/fixtures/cursor-across-multiple-blocks-start.js
vendored
Normal file
@@ -0,0 +1,106 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../..'
|
||||
|
||||
export const input = (
|
||||
<value>
|
||||
<document>
|
||||
<block type="paragraph">
|
||||
<anchor />one
|
||||
</block>
|
||||
<block type="paragraph">two</block>
|
||||
<block type="paragraph">
|
||||
<focus />three
|
||||
</block>
|
||||
</document>
|
||||
</value>
|
||||
)
|
||||
|
||||
export const options = {
|
||||
preserveSelection: true,
|
||||
preserveKeys: true,
|
||||
}
|
||||
|
||||
export const output = {
|
||||
object: 'value',
|
||||
document: {
|
||||
object: 'document',
|
||||
key: '9',
|
||||
data: {},
|
||||
nodes: [
|
||||
{
|
||||
object: 'block',
|
||||
key: '1',
|
||||
type: 'paragraph',
|
||||
isVoid: false,
|
||||
data: {},
|
||||
nodes: [
|
||||
{
|
||||
object: 'text',
|
||||
key: '0',
|
||||
leaves: [
|
||||
{
|
||||
object: 'leaf',
|
||||
text: 'one',
|
||||
marks: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
object: 'block',
|
||||
key: '3',
|
||||
type: 'paragraph',
|
||||
isVoid: false,
|
||||
data: {},
|
||||
nodes: [
|
||||
{
|
||||
object: 'text',
|
||||
key: '2',
|
||||
leaves: [
|
||||
{
|
||||
object: 'leaf',
|
||||
text: 'two',
|
||||
marks: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
object: 'block',
|
||||
key: '5',
|
||||
type: 'paragraph',
|
||||
isVoid: false,
|
||||
data: {},
|
||||
nodes: [
|
||||
{
|
||||
object: 'text',
|
||||
key: '4',
|
||||
leaves: [
|
||||
{
|
||||
object: 'leaf',
|
||||
text: 'three',
|
||||
marks: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
selection: {
|
||||
object: 'range',
|
||||
anchorKey: '0',
|
||||
anchorPath: [0, 0],
|
||||
anchorOffset: 0,
|
||||
focusKey: '4',
|
||||
focusPath: [2, 0],
|
||||
focusOffset: 0,
|
||||
isBackward: false,
|
||||
isFocused: true,
|
||||
isAtomic: false,
|
||||
marks: null,
|
||||
},
|
||||
}
|
@@ -6,31 +6,38 @@ export const input = (
|
||||
<value>
|
||||
<document>
|
||||
<block type="paragraph">
|
||||
This is a paragraph with a cursor position <cursor />(closed selection).
|
||||
one<cursor />
|
||||
</block>
|
||||
</document>
|
||||
</value>
|
||||
)
|
||||
|
||||
export const options = {
|
||||
preserveSelection: true,
|
||||
preserveKeys: true,
|
||||
}
|
||||
|
||||
export const output = {
|
||||
object: 'value',
|
||||
document: {
|
||||
object: 'document',
|
||||
key: '3',
|
||||
data: {},
|
||||
nodes: [
|
||||
{
|
||||
object: 'block',
|
||||
key: '1',
|
||||
type: 'paragraph',
|
||||
isVoid: false,
|
||||
data: {},
|
||||
nodes: [
|
||||
{
|
||||
object: 'text',
|
||||
key: '0',
|
||||
leaves: [
|
||||
{
|
||||
object: 'leaf',
|
||||
text:
|
||||
'This is a paragraph with a cursor position (closed selection).',
|
||||
text: 'one',
|
||||
marks: [],
|
||||
},
|
||||
],
|
||||
@@ -39,12 +46,17 @@ export const output = {
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
export const expectSelection = {
|
||||
isCollapsed: true,
|
||||
anchorOffset: 43,
|
||||
focusOffset: 43,
|
||||
anchorKey: input.texts.get(0).key,
|
||||
focusKey: input.texts.get(0).key,
|
||||
selection: {
|
||||
object: 'range',
|
||||
anchorKey: '0',
|
||||
anchorPath: [0, 0],
|
||||
anchorOffset: 3,
|
||||
focusKey: '0',
|
||||
focusPath: [0, 0],
|
||||
focusOffset: 3,
|
||||
isBackward: false,
|
||||
isFocused: true,
|
||||
isAtomic: false,
|
||||
marks: null,
|
||||
},
|
||||
}
|
62
packages/slate-hyperscript/test/fixtures/cursor-block-middle.js
vendored
Normal file
62
packages/slate-hyperscript/test/fixtures/cursor-block-middle.js
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../..'
|
||||
|
||||
export const input = (
|
||||
<value>
|
||||
<document>
|
||||
<block type="paragraph">
|
||||
o<cursor />ne
|
||||
</block>
|
||||
</document>
|
||||
</value>
|
||||
)
|
||||
|
||||
export const options = {
|
||||
preserveSelection: true,
|
||||
preserveKeys: true,
|
||||
}
|
||||
|
||||
export const output = {
|
||||
object: 'value',
|
||||
document: {
|
||||
object: 'document',
|
||||
key: '3',
|
||||
data: {},
|
||||
nodes: [
|
||||
{
|
||||
object: 'block',
|
||||
key: '1',
|
||||
type: 'paragraph',
|
||||
isVoid: false,
|
||||
data: {},
|
||||
nodes: [
|
||||
{
|
||||
object: 'text',
|
||||
key: '0',
|
||||
leaves: [
|
||||
{
|
||||
object: 'leaf',
|
||||
text: 'one',
|
||||
marks: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
selection: {
|
||||
object: 'range',
|
||||
anchorKey: '0',
|
||||
anchorPath: [0, 0],
|
||||
anchorOffset: 1,
|
||||
focusKey: '0',
|
||||
focusPath: [0, 0],
|
||||
focusOffset: 1,
|
||||
isBackward: false,
|
||||
isFocused: true,
|
||||
isAtomic: false,
|
||||
marks: null,
|
||||
},
|
||||
}
|
62
packages/slate-hyperscript/test/fixtures/cursor-block-start.js
vendored
Normal file
62
packages/slate-hyperscript/test/fixtures/cursor-block-start.js
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../..'
|
||||
|
||||
export const input = (
|
||||
<value>
|
||||
<document>
|
||||
<block type="paragraph">
|
||||
<cursor />one
|
||||
</block>
|
||||
</document>
|
||||
</value>
|
||||
)
|
||||
|
||||
export const options = {
|
||||
preserveSelection: true,
|
||||
preserveKeys: true,
|
||||
}
|
||||
|
||||
export const output = {
|
||||
object: 'value',
|
||||
document: {
|
||||
object: 'document',
|
||||
key: '3',
|
||||
data: {},
|
||||
nodes: [
|
||||
{
|
||||
object: 'block',
|
||||
key: '1',
|
||||
type: 'paragraph',
|
||||
isVoid: false,
|
||||
data: {},
|
||||
nodes: [
|
||||
{
|
||||
object: 'text',
|
||||
key: '0',
|
||||
leaves: [
|
||||
{
|
||||
object: 'leaf',
|
||||
text: 'one',
|
||||
marks: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
selection: {
|
||||
object: 'range',
|
||||
anchorKey: '0',
|
||||
anchorPath: [0, 0],
|
||||
anchorOffset: 0,
|
||||
focusKey: '0',
|
||||
focusPath: [0, 0],
|
||||
focusOffset: 0,
|
||||
isBackward: false,
|
||||
isFocused: true,
|
||||
isAtomic: false,
|
||||
marks: null,
|
||||
},
|
||||
}
|
68
packages/slate-hyperscript/test/fixtures/cursor-custom-block-middle.js
vendored
Normal file
68
packages/slate-hyperscript/test/fixtures/cursor-custom-block-middle.js
vendored
Normal file
@@ -0,0 +1,68 @@
|
||||
/** @jsx h */
|
||||
|
||||
import { createHyperscript } from '../..'
|
||||
|
||||
const h = createHyperscript({
|
||||
blocks: {
|
||||
paragraph: 'paragraph',
|
||||
},
|
||||
})
|
||||
|
||||
export const input = (
|
||||
<value>
|
||||
<document>
|
||||
<paragraph>
|
||||
o<cursor />ne
|
||||
</paragraph>
|
||||
</document>
|
||||
</value>
|
||||
)
|
||||
|
||||
export const options = {
|
||||
preserveSelection: true,
|
||||
preserveKeys: true,
|
||||
}
|
||||
|
||||
export const output = {
|
||||
object: 'value',
|
||||
document: {
|
||||
object: 'document',
|
||||
key: '3',
|
||||
data: {},
|
||||
nodes: [
|
||||
{
|
||||
object: 'block',
|
||||
key: '1',
|
||||
type: 'paragraph',
|
||||
isVoid: false,
|
||||
data: {},
|
||||
nodes: [
|
||||
{
|
||||
object: 'text',
|
||||
key: '0',
|
||||
leaves: [
|
||||
{
|
||||
object: 'leaf',
|
||||
text: 'one',
|
||||
marks: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
selection: {
|
||||
object: 'range',
|
||||
anchorKey: '0',
|
||||
anchorPath: [0, 0],
|
||||
anchorOffset: 1,
|
||||
focusKey: '0',
|
||||
focusPath: [0, 0],
|
||||
focusOffset: 1,
|
||||
isBackward: false,
|
||||
isFocused: true,
|
||||
isAtomic: false,
|
||||
marks: null,
|
||||
},
|
||||
}
|
97
packages/slate-hyperscript/test/fixtures/cursor-inline.js
vendored
Normal file
97
packages/slate-hyperscript/test/fixtures/cursor-inline.js
vendored
Normal file
@@ -0,0 +1,97 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../..'
|
||||
|
||||
export const input = (
|
||||
<value>
|
||||
<document>
|
||||
<block type="paragraph">
|
||||
one
|
||||
<inline type="link">
|
||||
t<cursor />wo
|
||||
</inline>
|
||||
three
|
||||
</block>
|
||||
</document>
|
||||
</value>
|
||||
)
|
||||
|
||||
export const options = {
|
||||
preserveSelection: true,
|
||||
preserveKeys: true,
|
||||
}
|
||||
|
||||
export const output = {
|
||||
object: 'value',
|
||||
document: {
|
||||
object: 'document',
|
||||
data: {},
|
||||
key: '6',
|
||||
nodes: [
|
||||
{
|
||||
object: 'block',
|
||||
key: '4',
|
||||
type: 'paragraph',
|
||||
isVoid: false,
|
||||
data: {},
|
||||
nodes: [
|
||||
{
|
||||
object: 'text',
|
||||
key: '2',
|
||||
leaves: [
|
||||
{
|
||||
object: 'leaf',
|
||||
text: 'one',
|
||||
marks: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
object: 'inline',
|
||||
key: '1',
|
||||
type: 'link',
|
||||
isVoid: false,
|
||||
data: {},
|
||||
nodes: [
|
||||
{
|
||||
object: 'text',
|
||||
key: '0',
|
||||
leaves: [
|
||||
{
|
||||
object: 'leaf',
|
||||
text: 'two',
|
||||
marks: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
object: 'text',
|
||||
key: '3',
|
||||
leaves: [
|
||||
{
|
||||
object: 'leaf',
|
||||
text: 'three',
|
||||
marks: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
selection: {
|
||||
object: 'range',
|
||||
anchorKey: '0',
|
||||
anchorPath: [0, 1, 0],
|
||||
anchorOffset: 1,
|
||||
focusKey: '0',
|
||||
focusPath: [0, 1, 0],
|
||||
focusOffset: 1,
|
||||
isBackward: false,
|
||||
isFocused: true,
|
||||
isAtomic: false,
|
||||
marks: null,
|
||||
},
|
||||
}
|
@@ -15,32 +15,38 @@ export const input = (
|
||||
<value>
|
||||
<document>
|
||||
<block type="paragraph">
|
||||
This is a <highlight>paragraph with</highlight> a cursor position{' '}
|
||||
<cursor />(closed selection).
|
||||
one<highlight>two</highlight>three
|
||||
</block>
|
||||
</document>
|
||||
</value>
|
||||
)
|
||||
|
||||
export const options = {
|
||||
preserveDecorations: true,
|
||||
preserveKeys: true,
|
||||
}
|
||||
|
||||
export const output = {
|
||||
object: 'value',
|
||||
document: {
|
||||
object: 'document',
|
||||
key: '3',
|
||||
data: {},
|
||||
nodes: [
|
||||
{
|
||||
object: 'block',
|
||||
key: '1',
|
||||
type: 'paragraph',
|
||||
isVoid: false,
|
||||
data: {},
|
||||
nodes: [
|
||||
{
|
||||
object: 'text',
|
||||
key: '0',
|
||||
leaves: [
|
||||
{
|
||||
object: 'leaf',
|
||||
text:
|
||||
'This is a paragraph with a cursor position (closed selection).',
|
||||
text: 'onetwothree',
|
||||
marks: [],
|
||||
},
|
||||
],
|
||||
@@ -49,14 +55,18 @@ export const output = {
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
export const expectDecorations = [
|
||||
decorations: [
|
||||
{
|
||||
anchorOffset: 10,
|
||||
focusOffset: 24,
|
||||
anchorKey: input.texts.get(0).key,
|
||||
focusKey: input.texts.get(0).key,
|
||||
object: 'range',
|
||||
anchorKey: '0',
|
||||
anchorPath: [0, 0],
|
||||
anchorOffset: 3,
|
||||
focusKey: '0',
|
||||
focusPath: [0, 0],
|
||||
focusOffset: 6,
|
||||
isBackward: false,
|
||||
isFocused: false,
|
||||
isAtomic: false,
|
||||
marks: [
|
||||
{
|
||||
object: 'mark',
|
||||
@@ -65,4 +75,5 @@ export const expectDecorations = [
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
],
|
||||
}
|
@@ -5,9 +5,8 @@ import h from '../..'
|
||||
export const input = (
|
||||
<value>
|
||||
<document>
|
||||
<block type="paragraph">
|
||||
This is a <anchor />paragraph<focus /> with an open selection.
|
||||
</block>
|
||||
<block type="paragraph">word</block>
|
||||
<text>invalid</text>
|
||||
</document>
|
||||
</value>
|
||||
)
|
||||
@@ -29,7 +28,7 @@ export const output = {
|
||||
leaves: [
|
||||
{
|
||||
object: 'leaf',
|
||||
text: 'This is a paragraph with an open selection.',
|
||||
text: 'word',
|
||||
marks: [],
|
||||
},
|
||||
],
|
||||
@@ -39,11 +38,3 @@ export const output = {
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
export const expectSelection = {
|
||||
isCollapsed: false,
|
||||
anchorOffset: 10,
|
||||
focusOffset: 19,
|
||||
anchorKey: input.texts.get(0).key,
|
||||
focusKey: input.texts.get(0).key,
|
||||
}
|
50
packages/slate-hyperscript/test/fixtures/normalize-disabled.js
vendored
Normal file
50
packages/slate-hyperscript/test/fixtures/normalize-disabled.js
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../..'
|
||||
|
||||
export const input = (
|
||||
<value normalize={false}>
|
||||
<document>
|
||||
<block type="paragraph">word</block>
|
||||
<text>invalid</text>
|
||||
</document>
|
||||
</value>
|
||||
)
|
||||
|
||||
export const output = {
|
||||
object: 'value',
|
||||
document: {
|
||||
object: 'document',
|
||||
data: {},
|
||||
nodes: [
|
||||
{
|
||||
object: 'block',
|
||||
type: 'paragraph',
|
||||
isVoid: false,
|
||||
data: {},
|
||||
nodes: [
|
||||
{
|
||||
object: 'text',
|
||||
leaves: [
|
||||
{
|
||||
object: 'leaf',
|
||||
text: 'word',
|
||||
marks: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
object: 'text',
|
||||
leaves: [
|
||||
{
|
||||
object: 'leaf',
|
||||
text: 'invalid',
|
||||
marks: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
63
packages/slate-hyperscript/test/fixtures/selection.js
vendored
Normal file
63
packages/slate-hyperscript/test/fixtures/selection.js
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../..'
|
||||
|
||||
export const input = (
|
||||
<value>
|
||||
<document>
|
||||
<block type="paragraph">
|
||||
one<text key="a">two</text>three
|
||||
</block>
|
||||
</document>
|
||||
<selection anchorKey="a" anchorOffset={1} focusKey="a" focusOffset={2} />
|
||||
</value>
|
||||
)
|
||||
|
||||
export const options = {
|
||||
preserveSelection: true,
|
||||
preserveKeys: true,
|
||||
}
|
||||
|
||||
export const output = {
|
||||
object: 'value',
|
||||
document: {
|
||||
object: 'document',
|
||||
key: '2',
|
||||
data: {},
|
||||
nodes: [
|
||||
{
|
||||
object: 'block',
|
||||
key: '0',
|
||||
type: 'paragraph',
|
||||
isVoid: false,
|
||||
data: {},
|
||||
nodes: [
|
||||
{
|
||||
object: 'text',
|
||||
key: 'a',
|
||||
leaves: [
|
||||
{
|
||||
object: 'leaf',
|
||||
text: 'onetwothree',
|
||||
marks: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
selection: {
|
||||
object: 'range',
|
||||
anchorKey: 'a',
|
||||
anchorPath: [0, 0],
|
||||
anchorOffset: 1,
|
||||
focusKey: 'a',
|
||||
focusPath: [0, 0],
|
||||
focusOffset: 2,
|
||||
isBackward: false,
|
||||
isFocused: false,
|
||||
isAtomic: false,
|
||||
marks: null,
|
||||
},
|
||||
}
|
@@ -1,7 +1,5 @@
|
||||
/** @jsx h */
|
||||
|
||||
import assert from 'assert'
|
||||
|
||||
import h from '../..'
|
||||
|
||||
export const input = (
|
||||
@@ -13,24 +11,25 @@ export const input = (
|
||||
</document>
|
||||
)
|
||||
|
||||
export function test() {
|
||||
const block = input.nodes.first()
|
||||
assert.notEqual(block.nodes.first().key, 'a')
|
||||
assert.equal(block.nodes.last().key, 'a')
|
||||
export const options = {
|
||||
preserveKeys: true,
|
||||
}
|
||||
|
||||
export const output = {
|
||||
object: 'document',
|
||||
key: '6',
|
||||
data: {},
|
||||
nodes: [
|
||||
{
|
||||
object: 'block',
|
||||
key: '4',
|
||||
type: 'paragraph',
|
||||
isVoid: false,
|
||||
data: {},
|
||||
nodes: [
|
||||
{
|
||||
object: 'text',
|
||||
key: '2',
|
||||
leaves: [
|
||||
{
|
||||
object: 'leaf',
|
||||
@@ -41,12 +40,14 @@ export const output = {
|
||||
},
|
||||
{
|
||||
object: 'inline',
|
||||
key: '1',
|
||||
type: 'link',
|
||||
data: {},
|
||||
isVoid: false,
|
||||
nodes: [
|
||||
{
|
||||
object: 'text',
|
||||
key: '0',
|
||||
leaves: [
|
||||
{
|
||||
object: 'leaf',
|
||||
@@ -59,6 +60,7 @@ export const output = {
|
||||
},
|
||||
{
|
||||
object: 'text',
|
||||
key: 'a',
|
||||
leaves: [
|
||||
{
|
||||
object: 'leaf',
|
@@ -1,19 +1,12 @@
|
||||
/**
|
||||
* Dependencies.
|
||||
*/
|
||||
|
||||
import assert from 'assert'
|
||||
import fs from 'fs'
|
||||
import { Value } from 'slate'
|
||||
import { Value, KeyUtils } from 'slate'
|
||||
import { basename, extname, resolve } from 'path'
|
||||
|
||||
/**
|
||||
* Tests.
|
||||
*/
|
||||
beforeEach(KeyUtils.resetGenerator)
|
||||
|
||||
describe('slate-hyperscript', () => {
|
||||
describe('default settings', () => {
|
||||
const dir = resolve(__dirname, './default')
|
||||
const dir = resolve(__dirname, './fixtures')
|
||||
const tests = fs
|
||||
.readdirSync(dir)
|
||||
.filter(t => t[0] != '.')
|
||||
@@ -22,70 +15,23 @@ describe('slate-hyperscript', () => {
|
||||
for (const test of tests) {
|
||||
it(test, async () => {
|
||||
const module = require(resolve(dir, test))
|
||||
const { input, output } = module
|
||||
|
||||
const actual = input.toJSON()
|
||||
const expected = Value.isValue(output) ? output.toJSON() : output
|
||||
assert.deepEqual(actual, expected)
|
||||
if (module.test) module.test()
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
describe('custom tags', () => {
|
||||
const dir = resolve(__dirname, './custom')
|
||||
const tests = fs
|
||||
.readdirSync(dir)
|
||||
.filter(t => t[0] != '.')
|
||||
.map(t => basename(t, extname(t)))
|
||||
|
||||
for (const test of tests) {
|
||||
it(test, async () => {
|
||||
const module = require(resolve(dir, test))
|
||||
const { input, output } = module
|
||||
|
||||
const actual = input.toJSON()
|
||||
const { input, output, options } = module
|
||||
const actual = input.toJSON(options)
|
||||
const expected = Value.isValue(output) ? output.toJSON() : output
|
||||
assert.deepEqual(actual, expected)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
describe('selections', () => {
|
||||
const dir = resolve(__dirname, './selections')
|
||||
const tests = fs
|
||||
.readdirSync(dir)
|
||||
describe.skip('decorations', () => {
|
||||
const decDir = resolve(__dirname, './decorations')
|
||||
const decTests = fs
|
||||
.readdirSync(decDir)
|
||||
.filter(t => t[0] != '.')
|
||||
.map(t => basename(t, extname(t)))
|
||||
|
||||
for (const test of tests) {
|
||||
for (const test of decTests) {
|
||||
it(test, async () => {
|
||||
const module = require(resolve(dir, test))
|
||||
const { input, output, expectSelection } = module
|
||||
|
||||
// ensure deserialization was okay
|
||||
const actual = input.toJSON()
|
||||
const expected = Value.isValue(output) ? output.toJSON() : output
|
||||
assert.deepEqual(actual, expected)
|
||||
|
||||
// ensure expected properties of selection match
|
||||
Object.keys(expectSelection).forEach(prop => {
|
||||
assert.equal(input.selection[prop], expectSelection[prop])
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
describe('decorations', () => {
|
||||
const dir = resolve(__dirname, './decorations')
|
||||
const tests = fs
|
||||
.readdirSync(dir)
|
||||
.filter(t => t[0] != '.')
|
||||
.map(t => basename(t, extname(t)))
|
||||
|
||||
for (const test of tests) {
|
||||
it(test, async () => {
|
||||
const module = require(resolve(dir, test))
|
||||
const module = require(resolve(decDir, test))
|
||||
const { input, output, expectDecorations } = module
|
||||
|
||||
// ensure deserialization was okay
|
||||
@@ -107,23 +53,4 @@ describe('slate-hyperscript', () => {
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
describe('normalize', () => {
|
||||
const dir = resolve(__dirname, './normalize')
|
||||
const tests = fs
|
||||
.readdirSync(dir)
|
||||
.filter(t => t[0] != '.')
|
||||
.map(t => basename(t, extname(t)))
|
||||
|
||||
for (const test of tests) {
|
||||
it(test, async () => {
|
||||
const module = require(resolve(dir, test))
|
||||
const { input, output } = module
|
||||
|
||||
const actual = Value.isValue(input) ? input.toJSON() : input
|
||||
const expected = Value.isValue(output) ? output.toJSON() : output
|
||||
assert.deepEqual(actual, expected)
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
|
@@ -1,24 +0,0 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../..'
|
||||
import { Value, Document, Block, Text } from 'slate'
|
||||
|
||||
export const input = (
|
||||
<value>
|
||||
<document>
|
||||
<block type="paragraph">Valid block</block>
|
||||
<text>Invalid text</text>
|
||||
</document>
|
||||
</value>
|
||||
)
|
||||
|
||||
export const output = Value.create({
|
||||
document: Document.create({
|
||||
nodes: [
|
||||
Block.create({
|
||||
type: 'paragraph',
|
||||
nodes: [Text.create('Valid block')],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
})
|
@@ -1,28 +0,0 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../..'
|
||||
import { Value, Document, Block, Text } from 'slate'
|
||||
|
||||
export const input = (
|
||||
<value normalize={false}>
|
||||
<document>
|
||||
<block type="paragraph">Valid block</block>
|
||||
<text>Invalid text</text>
|
||||
</document>
|
||||
</value>
|
||||
)
|
||||
|
||||
export const output = Value.fromJSON(
|
||||
{
|
||||
document: Document.create({
|
||||
nodes: [
|
||||
Block.create({
|
||||
type: 'paragraph',
|
||||
nodes: [Text.create('Valid block')],
|
||||
}),
|
||||
Text.create('Invalid text'),
|
||||
],
|
||||
}),
|
||||
},
|
||||
{ normalize: false }
|
||||
)
|
@@ -1,97 +0,0 @@
|
||||
/** @jsx h */
|
||||
|
||||
import { createHyperscript } from '../..'
|
||||
|
||||
const h = createHyperscript({
|
||||
blocks: {
|
||||
paragraph: 'paragraph',
|
||||
},
|
||||
marks: {
|
||||
b: 'bold',
|
||||
},
|
||||
})
|
||||
|
||||
export const input = (
|
||||
<value>
|
||||
<document>
|
||||
<paragraph>First paragraph</paragraph>
|
||||
<paragraph>
|
||||
This is a paragraph with a cursor{' '}
|
||||
<b>
|
||||
positi<cursor />on
|
||||
</b>{' '}
|
||||
within a mark.
|
||||
</paragraph>
|
||||
</document>
|
||||
</value>
|
||||
)
|
||||
|
||||
export const output = {
|
||||
object: 'value',
|
||||
document: {
|
||||
object: 'document',
|
||||
data: {},
|
||||
nodes: [
|
||||
{
|
||||
object: 'block',
|
||||
type: 'paragraph',
|
||||
isVoid: false,
|
||||
data: {},
|
||||
nodes: [
|
||||
{
|
||||
object: 'text',
|
||||
leaves: [
|
||||
{
|
||||
object: 'leaf',
|
||||
text: 'First paragraph',
|
||||
marks: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
object: 'block',
|
||||
type: 'paragraph',
|
||||
isVoid: false,
|
||||
data: {},
|
||||
nodes: [
|
||||
{
|
||||
object: 'text',
|
||||
leaves: [
|
||||
{
|
||||
object: 'leaf',
|
||||
text: 'This is a paragraph with a cursor ',
|
||||
marks: [],
|
||||
},
|
||||
{
|
||||
object: 'leaf',
|
||||
text: 'position',
|
||||
marks: [
|
||||
{
|
||||
object: 'mark',
|
||||
type: 'bold',
|
||||
data: {},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
object: 'leaf',
|
||||
text: ' within a mark.',
|
||||
marks: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
export const expectSelection = {
|
||||
isCollapsed: true,
|
||||
anchorOffset: 40,
|
||||
focusOffset: 40,
|
||||
anchorKey: input.texts.get(0).key,
|
||||
focusKey: input.texts.get(0).key,
|
||||
}
|
@@ -8,7 +8,7 @@ This document maintains a list of changes to the `slate-plain-serializer` packag
|
||||
|
||||
###### BREAKING
|
||||
|
||||
* **The `kind` property of Slate objects has been renamed to `object`.** This is to reduce the confusion over the difference between "kind" and "type" which are practically synonyms. The "object" name was chosen to match the Stripe API, since it seems like a sensible choice and reads much more nicely when looking through JSON.
|
||||
**The `kind` property of Slate objects has been renamed to `object`.** This is to reduce the confusion over the difference between "kind" and "type" which are practically synonyms. The "object" name was chosen to match the Stripe API, since it seems like a sensible choice and reads much more nicely when looking through JSON.
|
||||
|
||||
---
|
||||
|
||||
@@ -16,7 +16,7 @@ This document maintains a list of changes to the `slate-plain-serializer` packag
|
||||
|
||||
###### BREAKING
|
||||
|
||||
* **Remove all previously deprecated code paths.** This helps to reduce some of the complexity in Slate by not having to handle these code paths anymore. And it helps to reduce file size. When upgrading, it's _highly_ recommended that you upgrade to the previous version first and ensure there are no deprecation warnings being logged, then upgrade to this version.
|
||||
**Remove all previously deprecated code paths.** This helps to reduce some of the complexity in Slate by not having to handle these code paths anymore. And it helps to reduce file size. When upgrading, it's _highly_ recommended that you upgrade to the previous version first and ensure there are no deprecation warnings being logged, then upgrade to this version.
|
||||
|
||||
---
|
||||
|
||||
@@ -24,7 +24,7 @@ This document maintains a list of changes to the `slate-plain-serializer` packag
|
||||
|
||||
###### BREAKING
|
||||
|
||||
* **Updated to work with `slate@0.29.0`.** This is required because `slate-plain-serializer` needs access to the new `Value` model.
|
||||
**Updated to work with `slate@0.29.0`.** This is required because `slate-plain-serializer` needs access to the new `Value` model.
|
||||
|
||||
---
|
||||
|
||||
@@ -32,7 +32,7 @@ This document maintains a list of changes to the `slate-plain-serializer` packag
|
||||
|
||||
###### BREAKING
|
||||
|
||||
* **Updated work with `slate@0.27.0`.** The new version of Slate renames the old `Range` model to `Leaf`, and the old `Selection` model to `Range`.
|
||||
**Updated work with `slate@0.27.0`.** The new version of Slate renames the old `Range` model to `Leaf`, and the old `Selection` model to `Range`.
|
||||
|
||||
---
|
||||
|
||||
|
@@ -1,24 +1,10 @@
|
||||
/**
|
||||
* Dependencies.
|
||||
*/
|
||||
|
||||
import Plain from '..'
|
||||
import assert from 'assert'
|
||||
import fs from 'fs'
|
||||
import { Value, resetKeyGenerator } from 'slate'
|
||||
import { Value, KeyUtils } from 'slate'
|
||||
import { basename, extname, resolve } from 'path'
|
||||
|
||||
/**
|
||||
* Reset Slate's internal key generator state before each text.
|
||||
*/
|
||||
|
||||
beforeEach(() => {
|
||||
resetKeyGenerator()
|
||||
})
|
||||
|
||||
/**
|
||||
* Tests.
|
||||
*/
|
||||
beforeEach(KeyUtils.resetGenerator)
|
||||
|
||||
describe('slate-plain-serializer', () => {
|
||||
describe('deserialize()', () => {
|
||||
|
@@ -8,7 +8,7 @@ This document maintains a list of changes to the `slate-prop-types` package with
|
||||
|
||||
###### BREAKING
|
||||
|
||||
* **Remove all previously deprecated code paths.** This helps to reduce some of the complexity in Slate by not having to handle these code paths anymore. And it helps to reduce file size. When upgrading, it's _highly_ recommended that you upgrade to the previous version first and ensure there are no deprecation warnings being logged, then upgrade to this version.
|
||||
**Remove all previously deprecated code paths.** This helps to reduce some of the complexity in Slate by not having to handle these code paths anymore. And it helps to reduce file size. When upgrading, it's _highly_ recommended that you upgrade to the previous version first and ensure there are no deprecation warnings being logged, then upgrade to this version.
|
||||
|
||||
---
|
||||
|
||||
@@ -16,11 +16,11 @@ This document maintains a list of changes to the `slate-prop-types` package with
|
||||
|
||||
###### BREAKING
|
||||
|
||||
* **Updated to work with `slate@0.29.0`.** This is required because `slate-prop-types` needs access to the new `Value` model.
|
||||
**Updated to work with `slate@0.29.0`.** This is required because `slate-prop-types` needs access to the new `Value` model.
|
||||
|
||||
###### DEPRECATED
|
||||
|
||||
* **The `state` prop type has been renamed to `value`.** This is to stay in line with `slate-react@0.29.0` where the `State` object was renamed.
|
||||
**The `state` prop type has been renamed to `value`.** This is to stay in line with `slate-react@0.29.0` where the `State` object was renamed.
|
||||
|
||||
---
|
||||
|
||||
@@ -28,7 +28,7 @@ This document maintains a list of changes to the `slate-prop-types` package with
|
||||
|
||||
###### BREAKING
|
||||
|
||||
* **Updated work with `slate@0.27.0`.** The new version of Slate renames the old `Range` model to `Leaf`, and the old `Selection` model to `Range`.
|
||||
**Updated work with `slate@0.27.0`.** The new version of Slate renames the old `Range` model to `Leaf`, and the old `Selection` model to `Range`.
|
||||
|
||||
---
|
||||
|
||||
|
@@ -4,11 +4,19 @@ This document maintains a list of changes to the `slate-react` package with each
|
||||
|
||||
---
|
||||
|
||||
### `0.14.0` — July 27, 2018
|
||||
|
||||
###### NEW
|
||||
|
||||
**Updated to work with the `slate@0.35.0` with paths.** It now uses the `PathUtils` export in the latest `slate` internally to work with paths. This isn't a breaking change, but to use this library with the latest Slate you'll need to upgrade.
|
||||
|
||||
---
|
||||
|
||||
### `0.13.0` — July 3, 2018
|
||||
|
||||
###### BREAKING
|
||||
|
||||
* **The `isSelected` prop of nodes has changed.** Previously it was only `true` when the node was selected and the editor was focused. Now it is true even when the editor is not focused, and a new `isFocused` property has been added for the old behavior.
|
||||
**The `isSelected` prop of nodes has changed.** Previously it was only `true` when the node was selected and the editor was focused. Now it is true even when the editor is not focused, and a new `isFocused` property has been added for the old behavior.
|
||||
|
||||
---
|
||||
|
||||
@@ -16,7 +24,7 @@ This document maintains a list of changes to the `slate-react` package with each
|
||||
|
||||
###### BREAKING
|
||||
|
||||
* **Update to use `slate@0.33.0`.** This is to match the changes to void node behavior where their content is no longer restricted.
|
||||
**Update to use `slate@0.33.0`.** This is to match the changes to void node behavior where their content is no longer restricted.
|
||||
|
||||
---
|
||||
|
||||
@@ -24,7 +32,7 @@ This document maintains a list of changes to the `slate-react` package with each
|
||||
|
||||
###### BREAKING
|
||||
|
||||
* **The `kind` property of Slate objects has been renamed to `object`.** This is to reduce the confusion over the difference between "kind" and "type" which are practically synonyms. The "object" name was chosen to match the Stripe API, since it seems like a sensible choice and reads much more nicely when looking through JSON.
|
||||
**The `kind` property of Slate objects has been renamed to `object`.** This is to reduce the confusion over the difference between "kind" and "type" which are practically synonyms. The "object" name was chosen to match the Stripe API, since it seems like a sensible choice and reads much more nicely when looking through JSON.
|
||||
|
||||
---
|
||||
|
||||
@@ -32,7 +40,7 @@ This document maintains a list of changes to the `slate-react` package with each
|
||||
|
||||
###### BREAKING
|
||||
|
||||
* **Remove all previously deprecated code paths.** This helps to reduce some of the complexity in Slate by not having to handle these code paths anymore. And it helps to reduce file size. When upgrading, it's _highly_ recommended that you upgrade to the previous version first and ensure there are no deprecation warnings being logged, then upgrade to this version.
|
||||
**Remove all previously deprecated code paths.** This helps to reduce some of the complexity in Slate by not having to handle these code paths anymore. And it helps to reduce file size. When upgrading, it's _highly_ recommended that you upgrade to the previous version first and ensure there are no deprecation warnings being logged, then upgrade to this version.
|
||||
|
||||
---
|
||||
|
||||
@@ -40,23 +48,23 @@ This document maintains a list of changes to the `slate-react` package with each
|
||||
|
||||
###### BREAKING
|
||||
|
||||
* **Updated to use `slate@0.29.0`.** This is to gain access to the new `Value` model introduced in the newest version of Slate.
|
||||
**Updated to use `slate@0.29.0`.** This is to gain access to the new `Value` model introduced in the newest version of Slate.
|
||||
|
||||
* **Custom components no longer receive `props.state` or `props.schema`.** These are now exposed directly on the `props.editor` instance itself as `editor.value` and `editor.schema`. This helps eliminate a common issue where because of `shouldComponentUpdate` returning `false`, the `props.state` value was actually outdated, and transforming from it would cause incorrect behaviors.
|
||||
**Custom components no longer receive `props.state` or `props.schema`.** These are now exposed directly on the `props.editor` instance itself as `editor.value` and `editor.schema`. This helps eliminate a common issue where because of `shouldComponentUpdate` returning `false`, the `props.state` value was actually outdated, and transforming from it would cause incorrect behaviors.
|
||||
|
||||
* **The `plugin.renderEditor` function's signature has changed.** Previously it received `(props, state, editor)` but it now receives just `(props, editor)`. If you need access to the editor's current value, use the new `editor.value` property. This is simply to clean up the API, since the value is already accessible on `editor`.
|
||||
**The `plugin.renderEditor` function's signature has changed.** Previously it received `(props, state, editor)` but it now receives just `(props, editor)`. If you need access to the editor's current value, use the new `editor.value` property. This is simply to clean up the API, since the value is already accessible on `editor`.
|
||||
|
||||
###### DEPRECATED
|
||||
|
||||
* **The "state" has been renamed to "value" everywhere.** All of the current references are maintained as deprecations, so you should be able to upgrade and see warnings logged instead of being greeted with a broken editor. This is to reduce the confusion between React's "state" and Slate's editor value, and in an effort to further mimic the native DOM APIs.
|
||||
**The "state" has been renamed to "value" everywhere.** All of the current references are maintained as deprecations, so you should be able to upgrade and see warnings logged instead of being greeted with a broken editor. This is to reduce the confusion between React's "state" and Slate's editor value, and in an effort to further mimic the native DOM APIs.
|
||||
|
||||
* **The editor `getSchema()`, `getStack()` and `getState()` methods are deprecated.** These have been replaced by property getters on the editor instance itself—`editor.schema`, `editor.stack` and `editor.value`, respectively. This is to reduce confusion with React's own `setState`, and to make accessing these commonly used properties more convenient.
|
||||
**The editor `getSchema()`, `getStack()` and `getState()` methods are deprecated.** These have been replaced by property getters on the editor instance itself—`editor.schema`, `editor.stack` and `editor.value`, respectively. This is to reduce confusion with React's own `setState`, and to make accessing these commonly used properties more convenient.
|
||||
|
||||
###### NEW
|
||||
|
||||
* **Added a new `editor.value` getter property.** This now mimics the DOM for things like `input.value` and `textarea.value`, and is the new way to access the editor's current value.
|
||||
**Added a new `editor.value` getter property.** This now mimics the DOM for things like `input.value` and `textarea.value`, and is the new way to access the editor's current value.
|
||||
|
||||
* **Added new `editor.schema` and `editor.stack` getters.** Similarly to the new `value` getter, these two new getters give you access to the editor's current schema and stack.
|
||||
**Added new `editor.schema` and `editor.stack` getters.** Similarly to the new `value` getter, these two new getters give you access to the editor's current schema and stack.
|
||||
|
||||
---
|
||||
|
||||
@@ -64,23 +72,23 @@ This document maintains a list of changes to the `slate-react` package with each
|
||||
|
||||
###### BREAKING
|
||||
|
||||
* **The `Schema` objects in Slate have changed!** Previously, they used to be where you could define normalization rules, define rendering rules, and define decoration rules. This was overloaded, and made other improvements hard. Now, rendering and decorating is done via the newly added plugin functions (`renderNode`, `renderMark`, `decorateNode`). And validation is done either via the lower-level `validateNode` plugin function, or via the new `schema` objects.
|
||||
**The `Schema` objects in Slate have changed!** Previously, they used to be where you could define normalization rules, define rendering rules, and define decoration rules. This was overloaded, and made other improvements hard. Now, rendering and decorating is done via the newly added plugin functions (`renderNode`, `renderMark`, `decorateNode`). And validation is done either via the lower-level `validateNode` plugin function, or via the new `schema` objects.
|
||||
|
||||
* **The `plugin.onBeforeChange` function was removed.** Previously there was both an `onBeforeChange` handler and an `onChange` handler. Now there is just an `onChange` handler, and the core plugin adds it's own logic before others.
|
||||
**The `plugin.onBeforeChange` function was removed.** Previously there was both an `onBeforeChange` handler and an `onChange` handler. Now there is just an `onChange` handler, and the core plugin adds it's own logic before others.
|
||||
|
||||
* **The `plugin.render` function was renamed to `plugin.renderEditor`.** It performs the same function, but has been renamed to disambiguate between all of the other new rendering functions available to plugins.
|
||||
**The `plugin.render` function was renamed to `plugin.renderEditor`.** It performs the same function, but has been renamed to disambiguate between all of the other new rendering functions available to plugins.
|
||||
|
||||
###### NEW
|
||||
|
||||
* **`State` objects now have an embedded `state.schema` property.** This new schema property is used to automatically normalize the state as it changes, according to the editor's current schema. This makes normalization much easier.
|
||||
**`State` objects now have an embedded `state.schema` property.** This new schema property is used to automatically normalize the state as it changes, according to the editor's current schema. This makes normalization much easier.
|
||||
|
||||
* **A new `renderNode` plugin function was added.** This is the new way to render nodes, instead of using the schema. Any plugin can define a `renderNode(props)` function which is passed the props to render the custom node component with. This is similar to `react-router`'s `render={...}` prop if you are familiar with that.
|
||||
**A new `renderNode` plugin function was added.** This is the new way to render nodes, instead of using the schema. Any plugin can define a `renderNode(props)` function which is passed the props to render the custom node component with. This is similar to `react-router`'s `render={...}` prop if you are familiar with that.
|
||||
|
||||
* **A new `renderPlaceholder` plugin function was added.** This is similar to the `renderNode` helper, except for rendering placeholders.
|
||||
**A new `renderPlaceholder` plugin function was added.** This is similar to the `renderNode` helper, except for rendering placeholders.
|
||||
|
||||
* **A new `decorateNode` plugin function was added.** This is similar to the old `rule.decorate` function from schemas. Any plugin can define a `decorateNode(node)` function and that can return extra decoration ranges of marks to apply to the document.
|
||||
**A new `decorateNode` plugin function was added.** This is similar to the old `rule.decorate` function from schemas. Any plugin can define a `decorateNode(node)` function and that can return extra decoration ranges of marks to apply to the document.
|
||||
|
||||
* **A new `validateNode` plugin function was added.** This is the new way to do specific, custom validations. (There's also the new schema, which is the easier way to do most common validations.) Any plugin can define a `validateNode(node)` function that will be called to ensure nodes are valid. If they are valid, the function should return nothing. Otherwise, it should return a change function that normalizes the node to make it valid again.
|
||||
**A new `validateNode` plugin function was added.** This is the new way to do specific, custom validations. (There's also the new schema, which is the easier way to do most common validations.) Any plugin can define a `validateNode(node)` function that will be called to ensure nodes are valid. If they are valid, the function should return nothing. Otherwise, it should return a change function that normalizes the node to make it valid again.
|
||||
|
||||
---
|
||||
|
||||
@@ -88,7 +96,7 @@ This document maintains a list of changes to the `slate-react` package with each
|
||||
|
||||
###### BREAKING
|
||||
|
||||
* **The `<Placeholder>` component no longer exists!** Previously there was a `Placeholder` component exported from `slate-react`, but it had lots of problems and a confusing API. Instead, placeholder logic can now be defined via the `schema` by providing a `placeholder` component to render what a node is matched.
|
||||
**The `<Placeholder>` component no longer exists!** Previously there was a `Placeholder` component exported from `slate-react`, but it had lots of problems and a confusing API. Instead, placeholder logic can now be defined via the `schema` by providing a `placeholder` component to render what a node is matched.
|
||||
|
||||
---
|
||||
|
||||
@@ -96,13 +104,13 @@ This document maintains a list of changes to the `slate-react` package with each
|
||||
|
||||
###### BREAKING
|
||||
|
||||
* **The `data` argument to event handlers has been removed.** Previously event handlers had a signature of `(event, data, change, editor)`, but now they have a signature of just `(event, change, editor)`. This leads to simpler internal Slate logic, and less complex relationship dependencies between plugins. All of the information inside the old `data` argument can be accessed via the similar properties on the `event` argument, or via the `getEventRange`, `getEventTransfer` and `setEventTransfer` helpers.
|
||||
**The `data` argument to event handlers has been removed.** Previously event handlers had a signature of `(event, data, change, editor)`, but now they have a signature of just `(event, change, editor)`. This leads to simpler internal Slate logic, and less complex relationship dependencies between plugins. All of the information inside the old `data` argument can be accessed via the similar properties on the `event` argument, or via the `getEventRange`, `getEventTransfer` and `setEventTransfer` helpers.
|
||||
|
||||
###### NEW
|
||||
|
||||
* **Added a new `setEventTransfer` helper.** This is useful if you're working with `onDrop` or `onPaste` event and you want to set custom data in the event, to retrieve later or for others to consume. It takes a data `type` and a `value` to set the type do.
|
||||
**Added a new `setEventTransfer` helper.** This is useful if you're working with `onDrop` or `onPaste` event and you want to set custom data in the event, to retrieve later or for others to consume. It takes a data `type` and a `value` to set the type do.
|
||||
|
||||
* **Event handlers now have access to new events.** The `onClick`, `onCompositionEnd`, `onCompositionStart`, `onDragEnd`, `onDragEnter`, `onDragExit`, `onDragLeave`, `onDragOver`, `onDragStart`, and `onInput` events are all now newly exposed. Your plugin logic can use them to solve some more advanced use cases, and even override the internal Slate logic when necessary. 99% of use cases won't require them still, but they can be useful to have when needed.
|
||||
**Event handlers now have access to new events.** The `onClick`, `onCompositionEnd`, `onCompositionStart`, `onDragEnd`, `onDragEnter`, `onDragExit`, `onDragLeave`, `onDragOver`, `onDragStart`, and `onInput` events are all now newly exposed. Your plugin logic can use them to solve some more advanced use cases, and even override the internal Slate logic when necessary. 99% of use cases won't require them still, but they can be useful to have when needed.
|
||||
|
||||
---
|
||||
|
||||
@@ -110,13 +118,13 @@ This document maintains a list of changes to the `slate-react` package with each
|
||||
|
||||
###### DEPRECATED
|
||||
|
||||
* **The `data` objects in event handlers have been deprecated.** There were a few different issues with these "helpers": `data.key` didn't account for international keyboards, many properties awkwardly duplicated information that was available on `event.*`, but not completely, and many properties were confusing as to when they applied. If you were using these, you'll now need to use the native `event.*` properties instead. There's also a helpful [`is-hotkey`](https://github.com/ianstormtaylor/is-hotkey) package for more complex hotkey matching.
|
||||
**The `data` objects in event handlers have been deprecated.** There were a few different issues with these "helpers": `data.key` didn't account for international keyboards, many properties awkwardly duplicated information that was available on `event.*`, but not completely, and many properties were confusing as to when they applied. If you were using these, you'll now need to use the native `event.*` properties instead. There's also a helpful [`is-hotkey`](https://github.com/ianstormtaylor/is-hotkey) package for more complex hotkey matching.
|
||||
|
||||
###### NEW
|
||||
|
||||
* **Added a new `getEventRange` helper.** This gets the affected `Range` of Slate document given a DOM `event`. This is useful in the `onDrop` or `onPaste` handlers to retrieve the range in the document where the drop or paste will occur.
|
||||
**Added a new `getEventRange` helper.** This gets the affected `Range` of Slate document given a DOM `event`. This is useful in the `onDrop` or `onPaste` handlers to retrieve the range in the document where the drop or paste will occur.
|
||||
|
||||
* **Added a new `getEventTransfer` helper.** This gets any Slate-related data from an `event`. It is modelled after the DOM's [`DataTransfer`](https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer) API, and is useful for retrieve the data being dropped or pasted in `onDrop` or `onPaste` events.
|
||||
**Added a new `getEventTransfer` helper.** This gets any Slate-related data from an `event`. It is modelled after the DOM's [`DataTransfer`](https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer) API, and is useful for retrieve the data being dropped or pasted in `onDrop` or `onPaste` events.
|
||||
|
||||
---
|
||||
|
||||
@@ -124,15 +132,15 @@ This document maintains a list of changes to the `slate-react` package with each
|
||||
|
||||
###### BREAKING
|
||||
|
||||
* **Updated work with `slate@0.27.0`.** The new version of Slate renames the old `Range` model to `Leaf`, and the old `Selection` model to `Range`.
|
||||
**Updated work with `slate@0.27.0`.** The new version of Slate renames the old `Range` model to `Leaf`, and the old `Selection` model to `Range`.
|
||||
|
||||
###### NEW
|
||||
|
||||
* **Added a new `findDOMRange` helper.** Give a Slate `Range` object, it will return a DOM `Range` object with the correct start and end points, making it easier to work with lower-level DOM selections.
|
||||
**Added a new `findDOMRange` helper.** Give a Slate `Range` object, it will return a DOM `Range` object with the correct start and end points, making it easier to work with lower-level DOM selections.
|
||||
|
||||
* **Added a new `findRange` helper.** Given either a DOM `Selection` or DOM `Range` object and a Slate `State`, it will return a Slate `Range` representing the same part of the document, making it easier to work with DOM selection changes.
|
||||
**Added a new `findRange` helper.** Given either a DOM `Selection` or DOM `Range` object and a Slate `State`, it will return a Slate `Range` representing the same part of the document, making it easier to work with DOM selection changes.
|
||||
|
||||
* **Added a new `findNode` helper.** Given a DOM `Element`, it will find the closest Slate `Node` that it represents, making
|
||||
**Added a new `findNode` helper.** Given a DOM `Element`, it will find the closest Slate `Node` that it represents, making
|
||||
|
||||
---
|
||||
|
||||
@@ -140,7 +148,7 @@ This document maintains a list of changes to the `slate-react` package with each
|
||||
|
||||
###### BREAKING
|
||||
|
||||
* **The decoration logic has been updated to use `slate@0.26.0`.** This allows for more complex decoration logic, and even decorations based on external information.
|
||||
**The decoration logic has been updated to use `slate@0.26.0`.** This allows for more complex decoration logic, and even decorations based on external information.
|
||||
|
||||
---
|
||||
|
||||
@@ -148,7 +156,7 @@ This document maintains a list of changes to the `slate-react` package with each
|
||||
|
||||
###### BREAKING
|
||||
|
||||
* **`onBeforeChange` is now called automatically again in `<Editor>`.** This was removed before, in attempt to decrease the "magic" that the editor was performing, since it normalizes when new props are passed to it, creating instant changes. But we discovered that it is actually necessary for now, so it has been added again.
|
||||
**`onBeforeChange` is now called automatically again in `<Editor>`.** This was removed before, in attempt to decrease the "magic" that the editor was performing, since it normalizes when new props are passed to it, creating instant changes. But we discovered that it is actually necessary for now, so it has been added again.
|
||||
|
||||
---
|
||||
|
||||
|
@@ -33,7 +33,7 @@
|
||||
"immutable": ">=3.8.1",
|
||||
"react": ">=0.14.0",
|
||||
"react-dom": ">=0.14.0",
|
||||
"slate": ">=0.32.0"
|
||||
"slate": ">=0.35.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"immutable": "^3.8.1",
|
||||
|
@@ -3,6 +3,7 @@ import ImmutableTypes from 'react-immutable-proptypes'
|
||||
import React from 'react'
|
||||
import SlateTypes from 'slate-prop-types'
|
||||
import Types from 'prop-types'
|
||||
import { PathUtils } from 'slate'
|
||||
|
||||
import Leaf from './leaf'
|
||||
|
||||
@@ -108,13 +109,23 @@ class Text extends React.Component {
|
||||
const { key } = node
|
||||
|
||||
const decs = decorations.filter(d => {
|
||||
const { startKey, endKey } = d
|
||||
if (startKey == key || endKey == key) return true
|
||||
const { startKey, endKey, startPath, endPath } = d
|
||||
|
||||
// If either of the decoration's keys match, include it.
|
||||
if (startKey === key || endKey === key) return true
|
||||
|
||||
// Otherwise, if the decoration is in a single node, it's not ours.
|
||||
if (startKey === endKey) return false
|
||||
const startsBefore = document.areDescendantsSorted(startKey, key)
|
||||
if (!startsBefore) return false
|
||||
const endsAfter = document.areDescendantsSorted(key, endKey)
|
||||
return endsAfter
|
||||
|
||||
// If the node's path is before the start path, ignore it.
|
||||
const path = document.assertPathByKey(key)
|
||||
if (PathUtils.compare(path, startPath) === -1) return false
|
||||
|
||||
// If the node's path is after the end path, ignore it.
|
||||
if (PathUtils.compare(path, endPath) === 1) return false
|
||||
|
||||
// Otherwise, include it.
|
||||
return true
|
||||
})
|
||||
|
||||
// PERF: Take advantage of cache by avoiding arguments
|
||||
|
@@ -1,23 +1,9 @@
|
||||
/**
|
||||
* Dependencies.
|
||||
*/
|
||||
import { KeyUtils } from 'slate'
|
||||
|
||||
import { resetKeyGenerator } from 'slate'
|
||||
|
||||
/**
|
||||
* Tests.
|
||||
*/
|
||||
beforeEach(KeyUtils.resetGenerator)
|
||||
|
||||
describe('slate-react', () => {
|
||||
require('./plugins')
|
||||
require('./rendering')
|
||||
require('./utils')
|
||||
})
|
||||
|
||||
/**
|
||||
* Reset Slate's internal state before each text.
|
||||
*/
|
||||
|
||||
beforeEach(() => {
|
||||
resetKeyGenerator()
|
||||
})
|
||||
|
@@ -1,21 +1,7 @@
|
||||
/**
|
||||
* Dependencies.
|
||||
*/
|
||||
import { KeyUtils } from 'slate'
|
||||
|
||||
import { resetKeyGenerator } from 'slate'
|
||||
|
||||
/**
|
||||
* Tests.
|
||||
*/
|
||||
beforeEach(KeyUtils.resetGenerator)
|
||||
|
||||
describe('utils', () => {
|
||||
require('./get-children-decorations')
|
||||
})
|
||||
|
||||
/**
|
||||
* Reset Slate's internal state before each text.
|
||||
*/
|
||||
|
||||
beforeEach(() => {
|
||||
resetKeyGenerator()
|
||||
})
|
||||
|
@@ -8,7 +8,7 @@ This document maintains a list of changes to the `slate-simulator` package with
|
||||
|
||||
###### BREAKING
|
||||
|
||||
* **Remove all previously deprecated code paths.** This helps to reduce some of the complexity in Slate by not having to handle these code paths anymore. And it helps to reduce file size. When upgrading, it's _highly_ recommended that you upgrade to the previous version first and ensure there are no deprecation warnings being logged, then upgrade to this version.
|
||||
**Remove all previously deprecated code paths.** This helps to reduce some of the complexity in Slate by not having to handle these code paths anymore. And it helps to reduce file size. When upgrading, it's _highly_ recommended that you upgrade to the previous version first and ensure there are no deprecation warnings being logged, then upgrade to this version.
|
||||
|
||||
---
|
||||
|
||||
@@ -16,9 +16,9 @@ This document maintains a list of changes to the `slate-simulator` package with
|
||||
|
||||
###### DEPRECATED
|
||||
|
||||
* **The `props.state` prop has been renamed to `props.value`.** This is to stay in line with `slate-react@0.9.0` where the same change was made to the `<Editor>`.
|
||||
**The `props.state` prop has been renamed to `props.value`.** This is to stay in line with `slate-react@0.9.0` where the same change was made to the `<Editor>`.
|
||||
|
||||
* **The `simulator.state` property is now `simulator.value`** This is to stay in line with `slate@0.29.0` where the same change as made to the `Change` objects.
|
||||
**The `simulator.state` property is now `simulator.value`** This is to stay in line with `slate@0.29.0` where the same change as made to the `Change` objects.
|
||||
|
||||
---
|
||||
|
||||
@@ -26,7 +26,7 @@ This document maintains a list of changes to the `slate-simulator` package with
|
||||
|
||||
###### BREAKING
|
||||
|
||||
* **Updated to work with `slate@0.28.0`.** Along with the new Schema, the `Stack` which is used internally by the simulator has changed slightly.
|
||||
**Updated to work with `slate@0.28.0`.** Along with the new Schema, the `Stack` which is used internally by the simulator has changed slightly.
|
||||
|
||||
---
|
||||
|
||||
|
@@ -4,19 +4,47 @@ This document maintains a list of changes to the `slate` package with each new v
|
||||
|
||||
---
|
||||
|
||||
### `0.35.0` — July 27, 2018
|
||||
|
||||
###### BREAKING
|
||||
|
||||
**Internal-yet-public `Node` methods have been changed.** There were a handful of internal methods that shouldn't be used in 99% of Slate implementations that updated or removed. This was done in the process of streamlining many of the `Node` methods to make them more consistent and easier to use. For a list of those affected:
|
||||
|
||||
* `Node.assertPath` was changed. It was previously confusingly named because the equivalent `Node.getPath` did something completely different. You should now use `Node.assertNode(path)` if you need this behavior.
|
||||
* `Node.removeDescendant` was removed. There's no reason you should have been using this, since it was an undocumented and unused method that was left over from a previous version.
|
||||
* `Node.updateNode`, `Node.insertNode`, `Node.removeNode`, `Node.splitNode` and `Node.mergeNode` mutating methods were changed. All of your changes should be done with operations, so you likely weren't using these internal methods. They have been changed internally to use paths.
|
||||
|
||||
###### DEPRECATED
|
||||
|
||||
**The `setKeyGenerator` and `resetKeyGenerator` helpers are deprecated.** These were previously used to change the default key generation logic. Now you can use the equivalent `KeyUtils.setGenerator` and `KeyUtils.resetGenerator` helpers instead. This follows the new pattern of grouping related utilities into single namespaces, as is the case with the new `PathUtils` and `TextUtils`.
|
||||
|
||||
**Internal-yet-public `Node` methods have been deprecated.** There were a handful of internal methods that shouldn't be used in 99% of Slate implementations that were deprecated. For a list of those affected:
|
||||
|
||||
* `Node.getKeys` and `Node.getKeysAsArray` were deprecated. If you really need to check the presence of a key, use the new `Node.getKeysToPathsObject` instead.
|
||||
* `Node.areDescendantsSorted` and `Node.isInRange` were deprecated. These were used to check whether a node was in a range, but this can be done more performantly and more easily with paths now.
|
||||
* `Node.getNodeAtPath` and `Node.getDescendantAtPath` were deprecated. These were probably not in use by anyone, but if you were using them you can use the existing `Node.getNode` and `Node.getDescendant` methods instead which now take either paths or keys.
|
||||
|
||||
###### NEW
|
||||
|
||||
**`Range` objects now keep track of paths, in addition to keys.** Previously ranges only stored their points as keys. Now both paths and keys are used, which allows you to choose which one is the most convenient or most performant for your use case. They are kept in sync my Slate under the covers.
|
||||
|
||||
**A new set of `*ByPath` change methods have been added.** All of the changes you could previously do with a `*ByKey` change are now also supported with a `*ByPath` change of the same name. The path-based changes are often more performant than the key-based ones.
|
||||
|
||||
---
|
||||
|
||||
### `0.34.0` — June 14, 2018
|
||||
|
||||
###### BREAKING
|
||||
|
||||
* **Text nodes now represent their content as "leaves".** Previously their immutable representation used individual `Character` instance for each character. Now they have changed to group characters into `Leaf` models, which more closely resembles how they are used, and results in a _lot_ fewer immutable object instances floating around. _For most people this shouldn't cause any issues, since this is a low-level aspect of Slate._
|
||||
**Text nodes now represent their content as "leaves".** Previously their immutable representation used individual `Character` instance for each character. Now they have changed to group characters into `Leaf` models, which more closely resembles how they are used, and results in a _lot_ fewer immutable object instances floating around. _For most people this shouldn't cause any issues, since this is a low-level aspect of Slate._
|
||||
|
||||
###### DEPRECATED
|
||||
|
||||
* **The `Character` model is deprecated.** Although the character concept is still in the repository for now, it is deprecated and will be removed in a future release. Everything it solves can be solved with leaves instead.
|
||||
**The `Character` model is deprecated.** Although the character concept is still in the repository for now, it is deprecated and will be removed in a future release. Everything it solves can be solved with leaves instead.
|
||||
|
||||
###### NEW
|
||||
|
||||
* **Decorations can now be "atomic".** If you set a decoration as atomic, it will be removed when changed, preventing it from entering a "partial" state, which can be useful for some use cases.
|
||||
**Decorations can now be "atomic".** If you set a decoration as atomic, it will be removed when changed, preventing it from entering a "partial" state, which can be useful for some use cases.
|
||||
|
||||
---
|
||||
|
||||
@@ -24,13 +52,13 @@ This document maintains a list of changes to the `slate` package with each new v
|
||||
|
||||
###### BREAKING
|
||||
|
||||
* **Void nodes no longer prescribe their text content.** Previously void nodes would automatically normalize their text content to be a single text node containing `' '` an empty string of content. This restriction was removed, so that void nodes can have arbitrary content. You can use this to store information in void nodes in a way that is more consistent with non-void nodes.
|
||||
**Void nodes no longer prescribe their text content.** Previously void nodes would automatically normalize their text content to be a single text node containing `' '` an empty string of content. This restriction was removed, so that void nodes can have arbitrary content. You can use this to store information in void nodes in a way that is more consistent with non-void nodes.
|
||||
|
||||
###### DEPRECATED
|
||||
|
||||
* **The `setBlock` method has been renamed to `setBlocks`.** This is to make it more clear that it operates on any of the current blocks in the selection, not just a single blocks.
|
||||
**The `setBlock` method has been renamed to `setBlocks`.** This is to make it more clear that it operates on any of the current blocks in the selection, not just a single blocks.
|
||||
|
||||
* **The `setInline` method has been renamed to `setInlines`.** For the same reason as `setBlocks`, to be clear and stay consistent.
|
||||
**The `setInline` method has been renamed to `setInlines`.** For the same reason as `setBlocks`, to be clear and stay consistent.
|
||||
|
||||
---
|
||||
|
||||
@@ -38,9 +66,9 @@ This document maintains a list of changes to the `slate` package with each new v
|
||||
|
||||
###### BREAKING
|
||||
|
||||
* **The `kind` property of Slate objects has been renamed to `object`.** This is to reduce the confusion over the difference between "kind" and "type" which are practically synonyms. The "object" name was chosen to match the Stripe API, since it seems like a sensible choice and reads much more nicely when looking through JSON.
|
||||
**The `kind` property of Slate objects has been renamed to `object`.** This is to reduce the confusion over the difference between "kind" and "type" which are practically synonyms. The "object" name was chosen to match the Stripe API, since it seems like a sensible choice and reads much more nicely when looking through JSON.
|
||||
|
||||
* **All normalization reasons containing `kind` have been renamed too.** Previously there were normalization reason strings like `child_kind_invalid`. These types of strings have been renamed to `child_object_invalid` to stay consistent.
|
||||
**All normalization reasons containing `kind` have been renamed too.** Previously there were normalization reason strings like `child_kind_invalid`. These types of strings have been renamed to `child_object_invalid` to stay consistent.
|
||||
|
||||
---
|
||||
|
||||
@@ -48,13 +76,13 @@ This document maintains a list of changes to the `slate` package with each new v
|
||||
|
||||
###### BREAKING
|
||||
|
||||
* **Operation objects in Slate are now immutable records.** Previously they were native, mutable Javascript objects. Now, there's a new immutable `Operation` model in Slate, ensuring that all of the data inside `Value` objects are immutable. And it allows for easy serialization of operations using `operation.toJSON()` for when sending them between editors. This should not affect most users, unless you are relying on changing the values of the low-level Slate operations (simply reading them is fine).
|
||||
**Operation objects in Slate are now immutable records.** Previously they were native, mutable Javascript objects. Now, there's a new immutable `Operation` model in Slate, ensuring that all of the data inside `Value` objects are immutable. And it allows for easy serialization of operations using `operation.toJSON()` for when sending them between editors. This should not affect most users, unless you are relying on changing the values of the low-level Slate operations (simply reading them is fine).
|
||||
|
||||
* **Operation lists in Slate are now immutable lists.** Previously they were native, mutable Javascript arrays. Now, to keep consistent with other immutable uses, they are immutable lists. This should not affect most users.
|
||||
**Operation lists in Slate are now immutable lists.** Previously they were native, mutable Javascript arrays. Now, to keep consistent with other immutable uses, they are immutable lists. This should not affect most users.
|
||||
|
||||
###### NEW
|
||||
|
||||
* **Added a new `Operation` model.** This model is used to store operations for the history stack, and (de)serializes them in a consistent way for collaborative editing use cases.
|
||||
**Added a new `Operation` model.** This model is used to store operations for the history stack, and (de)serializes them in a consistent way for collaborative editing use cases.
|
||||
|
||||
---
|
||||
|
||||
@@ -62,7 +90,7 @@ This document maintains a list of changes to the `slate` package with each new v
|
||||
|
||||
###### BREAKING
|
||||
|
||||
* **Remove all previously deprecated code paths.** This helps to reduce some of the complexity in Slate by not having to handle these code paths anymore. And it helps to reduce file size. When upgrading, it's _highly_ recommended that you upgrade to the previous version first and ensure there are no deprecation warnings being logged, then upgrade to this version.
|
||||
**Remove all previously deprecated code paths.** This helps to reduce some of the complexity in Slate by not having to handle these code paths anymore. And it helps to reduce file size. When upgrading, it's _highly_ recommended that you upgrade to the previous version first and ensure there are no deprecation warnings being logged, then upgrade to this version.
|
||||
|
||||
---
|
||||
|
||||
@@ -70,15 +98,15 @@ This document maintains a list of changes to the `slate` package with each new v
|
||||
|
||||
###### BREAKING
|
||||
|
||||
* **The `set_state` operation has been renamed `set_value`**. This shouldn't affect almost anyone, but in the event that you were relying on the low-level operation types you'll need to update this.
|
||||
**The `set_state` operation has been renamed `set_value`**. This shouldn't affect almost anyone, but in the event that you were relying on the low-level operation types you'll need to update this.
|
||||
|
||||
###### DEPRECATED
|
||||
|
||||
* **The "state" has been renamed to "value" everywhere.** All of the current references are maintained as deprecations, so you should be able to upgrade and see warnings logged instead of being greeted with a broken editor. This is to reduce the confusion between React's "state" and Slate's editor value, and in an effort to further mimic the native DOM APIs.
|
||||
**The "state" has been renamed to "value" everywhere.** All of the current references are maintained as deprecations, so you should be able to upgrade and see warnings logged instead of being greeted with a broken editor. This is to reduce the confusion between React's "state" and Slate's editor value, and in an effort to further mimic the native DOM APIs.
|
||||
|
||||
###### NEW
|
||||
|
||||
* **Added the new `Value` model to replace `State`.** The new model is exactly the same, but with a new name. There is also a shimmed `State` model exported that warns when used, to ease migration.
|
||||
**Added the new `Value` model to replace `State`.** The new model is exactly the same, but with a new name. There is also a shimmed `State` model exported that warns when used, to ease migration.
|
||||
|
||||
---
|
||||
|
||||
@@ -86,13 +114,13 @@ This document maintains a list of changes to the `slate` package with each new v
|
||||
|
||||
###### BREAKING
|
||||
|
||||
* **The `Schema` objects in Slate have changed!** Previously, they used to be where you could define normalization rules, define rendering rules, and define decoration rules. This was overloaded, and made other improvements hard. Now, rendering and decorating is done via the newly added plugin functions (`renderNode`, `renderMark`, `decorateNode`). And validation is done either via the lower-level `validateNode` plugin function, or via the new `schema` objects.
|
||||
**The `Schema` objects in Slate have changed!** Previously, they used to be where you could define normalization rules, define rendering rules, and define decoration rules. This was overloaded, and made other improvements hard. Now, rendering and decorating is done via the newly added plugin functions (`renderNode`, `renderMark`, `decorateNode`). And validation is done either via the lower-level `validateNode` plugin function, or via the new `schema` objects.
|
||||
|
||||
* **The `normalize*` change methods no longer take a `schema` argument.** Previously you had to maintain a reference to your schema, and pass it into the normalize methods when you called them. Since `State` objects now have an embedded `state.schema` property, this is no longer needed.
|
||||
**The `normalize*` change methods no longer take a `schema` argument.** Previously you had to maintain a reference to your schema, and pass it into the normalize methods when you called them. Since `State` objects now have an embedded `state.schema` property, this is no longer needed.
|
||||
|
||||
###### NEW
|
||||
|
||||
* **`State` objects now have an embedded `state.schema` property.** This new schema property is used to automatically normalize the state as it changes, according to the editor's current schema. This makes normalization much easier.
|
||||
**`State` objects now have an embedded `state.schema` property.** This new schema property is used to automatically normalize the state as it changes, according to the editor's current schema. This makes normalization much easier.
|
||||
|
||||
---
|
||||
|
||||
@@ -100,15 +128,15 @@ This document maintains a list of changes to the `slate` package with each new v
|
||||
|
||||
###### BREAKING
|
||||
|
||||
* **The `Range` model is now called `Leaf`.** This is to disambiguate with the concept of "ranges" that is used throughout the codebase to be synonymous to selections. For example in methods like `getBlocksAtRange(selection)`.
|
||||
**The `Range` model is now called `Leaf`.** This is to disambiguate with the concept of "ranges" that is used throughout the codebase to be synonymous to selections. For example in methods like `getBlocksAtRange(selection)`.
|
||||
|
||||
* **The `text.ranges` property in the JSON representation is now `text.leaves`.** When passing in JSON with `text.ranges` you'll now receive a deprecation warning in the console in development.
|
||||
**The `text.ranges` property in the JSON representation is now `text.leaves`.** When passing in JSON with `text.ranges` you'll now receive a deprecation warning in the console in development.
|
||||
|
||||
###### DEPRECATED
|
||||
|
||||
* **The `Selection` model is now called `Range`.** This is to make it more clear what a "selection" really is, to make many of the other methods that act on "ranges" make sense, and to more closely parallel the native DOM API for selections and ranges. A mock `Selection` object is still exported with deprecated `static` methods, to make the transition to the new API easier.
|
||||
**The `Selection` model is now called `Range`.** This is to make it more clear what a "selection" really is, to make many of the other methods that act on "ranges" make sense, and to more closely parallel the native DOM API for selections and ranges. A mock `Selection` object is still exported with deprecated `static` methods, to make the transition to the new API easier.
|
||||
|
||||
* **The `Text.getRanges()` method is now `Text.getLeaves()`.** It will still work, and it will return a list of leaves, but you will see a deprecation warning in the console in development.
|
||||
**The `Text.getRanges()` method is now `Text.getLeaves()`.** It will still work, and it will return a list of leaves, but you will see a deprecation warning in the console in development.
|
||||
|
||||
---
|
||||
|
||||
@@ -116,19 +144,19 @@ This document maintains a list of changes to the `slate` package with each new v
|
||||
|
||||
###### BREAKING
|
||||
|
||||
* **The `decorate` function of schema rules has changed.** Previously, in `decorate` you would receive a text node and the matched node, and you'd need to manually add any marks you wanted to the text node's characters. Now, "decorations" have changed to just be `Selection` objects with marks in the `selection.marks` property. Instead of applying the marks yourself, you simply return selection ranges with the marks to be applied, and Slate will apply them internally. This makes it possible to write much more complex decoration behaviors. Check out the revamped [`code-highlighting`](https://github.com/ianstormtaylor/slate/blob/master/examples/code-highlighting/index.js) example and the new [`search-highlighting`](https://github.com/ianstormtaylor/slate/blob/master/examples/search-highlighting/index.js) example to see this in action.
|
||||
**The `decorate` function of schema rules has changed.** Previously, in `decorate` you would receive a text node and the matched node, and you'd need to manually add any marks you wanted to the text node's characters. Now, "decorations" have changed to just be `Selection` objects with marks in the `selection.marks` property. Instead of applying the marks yourself, you simply return selection ranges with the marks to be applied, and Slate will apply them internally. This makes it possible to write much more complex decoration behaviors. Check out the revamped [`code-highlighting`](https://github.com/ianstormtaylor/slate/blob/master/examples/code-highlighting/index.js) example and the new [`search-highlighting`](https://github.com/ianstormtaylor/slate/blob/master/examples/search-highlighting/index.js) example to see this in action.
|
||||
|
||||
* **The `set_data` operation type has been replaced by `set_state`.** With the new `state.decorations` property, it doesn't make sense to have a new operation type for every property of `State` objects. Instead, the new `set_state` operation more closely mimics the existing `set_mark` and `set_node` operations.
|
||||
**The `set_data` operation type has been replaced by `set_state`.** With the new `state.decorations` property, it doesn't make sense to have a new operation type for every property of `State` objects. Instead, the new `set_state` operation more closely mimics the existing `set_mark` and `set_node` operations.
|
||||
|
||||
###### DEPRECATED
|
||||
|
||||
* **The `setData` change method has been replaced by `setState`.** Previously you would call `change.setData(data)`. But as new `State` properties are introduced it doesn't make sense to need to add new change methods each time. Instead, the new `change.setState(properties)` more closesely mimics the existing `setMarkByKey` and `setNodeByKey`. To achieve the old behavior, you can do `change.setState({ data })`.
|
||||
**The `setData` change method has been replaced by `setState`.** Previously you would call `change.setData(data)`. But as new `State` properties are introduced it doesn't make sense to need to add new change methods each time. Instead, the new `change.setState(properties)` more closesely mimics the existing `setMarkByKey` and `setNodeByKey`. To achieve the old behavior, you can do `change.setState({ data })`.
|
||||
|
||||
* **The `preserveStateData` option of `state.toJSON` has changed.** The same behavior is now called `preserveData` instead. This makes it consistent with all of the existing options, and the new `preserveDecorations` option as well.
|
||||
**The `preserveStateData` option of `state.toJSON` has changed.** The same behavior is now called `preserveData` instead. This makes it consistent with all of the existing options, and the new `preserveDecorations` option as well.
|
||||
|
||||
###### NEW
|
||||
|
||||
* **You can now set decorations based on external information.** Previously, the "decoration" logic in Slate was always based off of the text of a node, and would only re-render when that text changed. Now, there is a new `state.decorations` property that you can set via `change.setState({ decorations })`. You can use this to add presentation-only marks to arbitrary ranges of text in the document. Check out the new [`search-highlighting`](https://github.com/ianstormtaylor/slate/blob/master/examples/search-highlighting/index.js) example to see this in action.
|
||||
**You can now set decorations based on external information.** Previously, the "decoration" logic in Slate was always based off of the text of a node, and would only re-render when that text changed. Now, there is a new `state.decorations` property that you can set via `change.setState({ decorations })`. You can use this to add presentation-only marks to arbitrary ranges of text in the document. Check out the new [`search-highlighting`](https://github.com/ianstormtaylor/slate/blob/master/examples/search-highlighting/index.js) example to see this in action.
|
||||
|
||||
---
|
||||
|
||||
@@ -136,9 +164,9 @@ This document maintains a list of changes to the `slate` package with each new v
|
||||
|
||||
###### BREAKING
|
||||
|
||||
* **The `insertBlock` change method no longer replaces empty blocks.** Previously if you used `insertBlock` and the selection was in an empty block, it would replace it. Now you'll need to perform that check yourself and use the new `replaceNodeByKey` method instead.
|
||||
**The `insertBlock` change method no longer replaces empty blocks.** Previously if you used `insertBlock` and the selection was in an empty block, it would replace it. Now you'll need to perform that check yourself and use the new `replaceNodeByKey` method instead.
|
||||
|
||||
* **The `Block.create` and `Inline.create` methods no longer normalize.** Previously if you used one of them to create a block or inline with zero nodes in it, they would automatically add a single empty text node as the only child. This was unexpected in certain situations, and if you were relying on this you'll need to handle it manually instead now.
|
||||
**The `Block.create` and `Inline.create` methods no longer normalize.** Previously if you used one of them to create a block or inline with zero nodes in it, they would automatically add a single empty text node as the only child. This was unexpected in certain situations, and if you were relying on this you'll need to handle it manually instead now.
|
||||
|
||||
---
|
||||
|
||||
@@ -146,21 +174,21 @@ This document maintains a list of changes to the `slate` package with each new v
|
||||
|
||||
###### BREAKING
|
||||
|
||||
* **`immutable` is now a _peer_ dependency of Slate.** Previously it was a regular dependency, but this prevented you from bringing your own version, or you'd have duplication. You'll need to ensure you install it!
|
||||
**`immutable` is now a _peer_ dependency of Slate.** Previously it was a regular dependency, but this prevented you from bringing your own version, or you'd have duplication. You'll need to ensure you install it!
|
||||
|
||||
* **The `Html`, `Plain` and `Raw` serializers are broken into new packages.** Previously you'd import them from `slate`. But now you'll import them from `slate-html-serializer` and `slate-plain-serializer`. And the `Raw` serializer that was deprecated is now removed.
|
||||
**The `Html`, `Plain` and `Raw` serializers are broken into new packages.** Previously you'd import them from `slate`. But now you'll import them from `slate-html-serializer` and `slate-plain-serializer`. And the `Raw` serializer that was deprecated is now removed.
|
||||
|
||||
* **The `Editor` and `Placeholder` components are broken into a new React-specific package.** Previously you'd import them from `slate`. But now you `import { Editor } from 'slate-react'` instead.
|
||||
**The `Editor` and `Placeholder` components are broken into a new React-specific package.** Previously you'd import them from `slate`. But now you `import { Editor } from 'slate-react'` instead.
|
||||
|
||||
###### NEW
|
||||
|
||||
* **Slate is now a "monorepo".** Instead of a single package, Slate has been divided up into individual packages so that you can only require what you need, cutting down on file size. In the process, some helpful modules that used to be internal-only are now exposed.
|
||||
**Slate is now a "monorepo".** Instead of a single package, Slate has been divided up into individual packages so that you can only require what you need, cutting down on file size. In the process, some helpful modules that used to be internal-only are now exposed.
|
||||
|
||||
* **There's a new `slate-hyperscript` helper.** This was possible thanks to the work on [`slate-sugar`](https://github.com/GitbookIO/slate-sugar), which paved the way.
|
||||
**There's a new `slate-hyperscript` helper.** This was possible thanks to the work on [`slate-sugar`](https://github.com/GitbookIO/slate-sugar), which paved the way.
|
||||
|
||||
* **The `slate-prop-types` package is now exposed.** Previously this was an internal module, but now you can use it for adding prop types to any components or plugins you create.
|
||||
**The `slate-prop-types` package is now exposed.** Previously this was an internal module, but now you can use it for adding prop types to any components or plugins you create.
|
||||
|
||||
* **The `slate-simulator` package is now exposed.** Previously this was an internal testing utility, but now you can use it in your own tests as well. It's currently pretty bare bones, but we can add to it over time.
|
||||
**The `slate-simulator` package is now exposed.** Previously this was an internal testing utility, but now you can use it in your own tests as well. It's currently pretty bare bones, but we can add to it over time.
|
||||
|
||||
---
|
||||
|
||||
@@ -168,25 +196,25 @@ This document maintains a list of changes to the `slate` package with each new v
|
||||
|
||||
###### BREAKING
|
||||
|
||||
* **The `isNative` property of `State` has been removed.** Previously this was used for performance reasons to avoid re-rendering, but it is no longer needed. This shouldn't really affect most people because it's rare that you'd be relying on this property to exist.
|
||||
**The `isNative` property of `State` has been removed.** Previously this was used for performance reasons to avoid re-rendering, but it is no longer needed. This shouldn't really affect most people because it's rare that you'd be relying on this property to exist.
|
||||
|
||||
###### DEPRECATED
|
||||
|
||||
* **The `Raw` serializer is now deprecated.** The entire "raw" concept is being removed, in favor of allowing all models to be able to serialize and deserialize to JSON themselves. Instead of using the `Raw` serializer, you can now use the `fromJSON` and `toJSON` on the models directly.
|
||||
**The `Raw` serializer is now deprecated.** The entire "raw" concept is being removed, in favor of allowing all models to be able to serialize and deserialize to JSON themselves. Instead of using the `Raw` serializer, you can now use the `fromJSON` and `toJSON` on the models directly.
|
||||
|
||||
* **The `toRaw` options for the `Plain` and `Html` serializers are now called `toJSON`.** This is to stay symmetrical with the removal of the "raw" concept everywhere.
|
||||
**The `toRaw` options for the `Plain` and `Html` serializers are now called `toJSON`.** This is to stay symmetrical with the removal of the "raw" concept everywhere.
|
||||
|
||||
* **The `terse` option for JSON serialization has been deprecated!** This option causes lots of abstraction leakiness because it means there is no one canonical JSON representation of objects. You had to work with either terse or not terse data.
|
||||
**The `terse` option for JSON serialization has been deprecated!** This option causes lots of abstraction leakiness because it means there is no one canonical JSON representation of objects. You had to work with either terse or not terse data.
|
||||
|
||||
* **The `Html` serializer no longer uses the `terse` representation.** This shouldn't actually be an issue for anyone because the main manifestation of this has a deprecation notice with a patch in place for now.
|
||||
**The `Html` serializer no longer uses the `terse` representation.** This shouldn't actually be an issue for anyone because the main manifestation of this has a deprecation notice with a patch in place for now.
|
||||
|
||||
* **The `defaultBlockType` of the `Html` serializer is now called `defaultBlock`.** This is just to make it more clear that it supports not only setting the default `type` but also `data` and `isVoid`.
|
||||
**The `defaultBlockType` of the `Html` serializer is now called `defaultBlock`.** This is just to make it more clear that it supports not only setting the default `type` but also `data` and `isVoid`.
|
||||
|
||||
###### NEW
|
||||
|
||||
* **Slate models now have `Model.fromJSON(object)` and `model.toJSON()` methods.** These methods operate with the canonical JSON form (which used to be called "raw"). This way you don't need to `import` a serializer to retrieve JSON, if you have the model you can serialize/deserialize.
|
||||
**Slate models now have `Model.fromJSON(object)` and `model.toJSON()` methods.** These methods operate with the canonical JSON form (which used to be called "raw"). This way you don't need to `import` a serializer to retrieve JSON, if you have the model you can serialize/deserialize.
|
||||
|
||||
* **Models also have `toJS` and `fromJS` aliases.** This is just to match Immutable.js objects, which have both methods. For Slate though, the methods are equivalent.
|
||||
**Models also have `toJS` and `fromJS` aliases.** This is just to match Immutable.js objects, which have both methods. For Slate though, the methods are equivalent.
|
||||
|
||||
---
|
||||
|
||||
@@ -194,13 +222,13 @@ This document maintains a list of changes to the `slate` package with each new v
|
||||
|
||||
###### BREAKING
|
||||
|
||||
* **The `Plain` serializer now adds line breaks between blocks.** Previously between blocks the text would be joined without any space whatsoever, but this wasn't really that useful or what you'd expect.
|
||||
**The `Plain` serializer now adds line breaks between blocks.** Previously between blocks the text would be joined without any space whatsoever, but this wasn't really that useful or what you'd expect.
|
||||
|
||||
* **The `toggleMark` transform now checks the intersection of marks.** Previously, toggling would remove the mark from the range if any of the characters in a range didn't have it. However, this wasn't what all other rich-text editors did, so the behavior has changed to mimic the standard behavior. Now, if any characters in the selection have the mark applied, it will first be added when toggling.
|
||||
**The `toggleMark` transform now checks the intersection of marks.** Previously, toggling would remove the mark from the range if any of the characters in a range didn't have it. However, this wasn't what all other rich-text editors did, so the behavior has changed to mimic the standard behavior. Now, if any characters in the selection have the mark applied, it will first be added when toggling.
|
||||
|
||||
* **The `.length` property of nodes has been removed.** This property caused issues with code like in Lodash that checked for "array-likeness" by simply looking for a `.length` property that was a number.
|
||||
**The `.length` property of nodes has been removed.** This property caused issues with code like in Lodash that checked for "array-likeness" by simply looking for a `.length` property that was a number.
|
||||
|
||||
* **`onChange` now receives a `Change` object (previously named `Transform`) instead of a `State`.** This is needed because it enforces that all changes are represented by a single set of operations. Otherwise right now it's possible to do things like `state.transform()....apply({ save: false }).transform()....apply()` and result in losing the operation information in the history. With OT, we need all transforms that may happen to be exposed and emitted by the editor. The new syntax looks like:
|
||||
**`onChange` now receives a `Change` object (previously named `Transform`) instead of a `State`.** This is needed because it enforces that all changes are represented by a single set of operations. Otherwise right now it's possible to do things like `state.transform()....apply({ save: false }).transform()....apply()` and result in losing the operation information in the history. With OT, we need all transforms that may happen to be exposed and emitted by the editor. The new syntax looks like:
|
||||
|
||||
```js
|
||||
onChange(change) {
|
||||
@@ -212,7 +240,7 @@ onChange({ state }) {
|
||||
}
|
||||
```
|
||||
|
||||
* **Similarly, handlers now receive `e, data, change` instead of `e, data, state`.** Instead of doing `return state.transform()....apply()` the plugins can now act on the change object directly. Plugins can still `return change...` if they want to break the stack from continuing on to other plugins. (Any `!= null` value will break out.) But they can also now not return anything, and the stack will apply their changes and continue onwards. This was previously impossible. The new syntax looks like:
|
||||
**Similarly, handlers now receive `e, data, change` instead of `e, data, state`.** Instead of doing `return state.transform()....apply()` the plugins can now act on the change object directly. Plugins can still `return change...` if they want to break the stack from continuing on to other plugins. (Any `!= null` value will break out.) But they can also now not return anything, and the stack will apply their changes and continue onwards. This was previously impossible. The new syntax looks like:
|
||||
|
||||
```js
|
||||
function onKeyDown(e, data, change) {
|
||||
@@ -222,29 +250,29 @@ function onKeyDown(e, data, change) {
|
||||
}
|
||||
```
|
||||
|
||||
* **The `onChange` and `on[Before]Change` handlers now receive `Change` objects.** Previously they would also receive a `state` object, but now they receive `change` objects like the rest of the plugin API.
|
||||
**The `onChange` and `on[Before]Change` handlers now receive `Change` objects.** Previously they would also receive a `state` object, but now they receive `change` objects like the rest of the plugin API.
|
||||
|
||||
* **The `.apply({ save })` option is now `state.change({ save })` instead.** This is the easiest way to use it, but requires that you know whether to save or not up front. If you want to use it inline after already saving some changes, you can use the `change.setOperationFlag('save', true)` flag instead. This shouldn't be necessary for 99% of use cases though.
|
||||
**The `.apply({ save })` option is now `state.change({ save })` instead.** This is the easiest way to use it, but requires that you know whether to save or not up front. If you want to use it inline after already saving some changes, you can use the `change.setOperationFlag('save', true)` flag instead. This shouldn't be necessary for 99% of use cases though.
|
||||
|
||||
* **The `.undo()` and `.redo()` transforms don't save by default.** Previously you had to specifically tell these transforms not to save into the history, which was awkward. Now they won't save the operations they're undoing/redoing by default.
|
||||
**The `.undo()` and `.redo()` transforms don't save by default.** Previously you had to specifically tell these transforms not to save into the history, which was awkward. Now they won't save the operations they're undoing/redoing by default.
|
||||
|
||||
* **`onBeforeChange` is no longer called from `componentWillReceiveProps`,** when a new `state` is passed in as props to the `<Editor>` component. This caused lots of state-management issues and was weird in the first place because passing in props would result in changes firing. **It is now the parent component's responsibility to not pass in improperly formatted `State` objects**.
|
||||
**`onBeforeChange` is no longer called from `componentWillReceiveProps`,** when a new `state` is passed in as props to the `<Editor>` component. This caused lots of state-management issues and was weird in the first place because passing in props would result in changes firing. **It is now the parent component's responsibility to not pass in improperly formatted `State` objects**.
|
||||
|
||||
* **The `splitNodeByKey` change method has changed to be shallow.** Previously, it would deeply split to an offset. But now it is shallow and another `splitDescendantsByKey` change method has been added (with a different signature) for the deep splitting behavior. This is needed because splitting and joining operations have been changed to all be shallow, which is required so that operational transforms can be written against them.
|
||||
**The `splitNodeByKey` change method has changed to be shallow.** Previously, it would deeply split to an offset. But now it is shallow and another `splitDescendantsByKey` change method has been added (with a different signature) for the deep splitting behavior. This is needed because splitting and joining operations have been changed to all be shallow, which is required so that operational transforms can be written against them.
|
||||
|
||||
* **Blocks cannot have mixed "inline" and "block" children anymore.** Blocks were implicitly expected to either contain "text" and "inline" nodes only, or to contain "block" nodes only. Invalid case are now normalized by the core schema.
|
||||
**Blocks cannot have mixed "inline" and "block" children anymore.** Blocks were implicitly expected to either contain "text" and "inline" nodes only, or to contain "block" nodes only. Invalid case are now normalized by the core schema.
|
||||
|
||||
* **The shape of many operations has changed.** This was needed to make operations completely invertible without any extra context. The operations were never really exposed in a consumable way, so I won't detail all of the changes here, but feel free to look at the source to see the details.
|
||||
**The shape of many operations has changed.** This was needed to make operations completely invertible without any extra context. The operations were never really exposed in a consumable way, so I won't detail all of the changes here, but feel free to look at the source to see the details.
|
||||
|
||||
* **All references to "joining" nodes is now called "merging".** This is to be slightly clearer, since merging can only happen with adjacent nodes already, and to have a nicer parallel with "splitting", as in cells. The operation is now called `merge_node`, and the transforms are now `merge*`.
|
||||
**All references to "joining" nodes is now called "merging".** This is to be slightly clearer, since merging can only happen with adjacent nodes already, and to have a nicer parallel with "splitting", as in cells. The operation is now called `merge_node`, and the transforms are now `merge*`.
|
||||
|
||||
###### DEPRECATED
|
||||
|
||||
* **The `transform.apply()` method is deprecated.** Previously this is where the saving into the history would happen, but it created an awkward convention that wasn't necessary. Now operations are saved into the history as they are created with change methods, instead of waiting until the end. You can access the new `State` of a change at any time via `change.state`.
|
||||
**The `transform.apply()` method is deprecated.** Previously this is where the saving into the history would happen, but it created an awkward convention that wasn't necessary. Now operations are saved into the history as they are created with change methods, instead of waiting until the end. You can access the new `State` of a change at any time via `change.state`.
|
||||
|
||||
###### NEW
|
||||
|
||||
* **The `state.activeMarks` returns the intersection of marks in the selection.** Previously there was only `state.marks` which returns marks that appeared on _any_ character in the selection. But `state.activeMarks` returns marks that appear on _every_ character in the selection, which is often more useful for implementing standard rich-text editor behaviors.
|
||||
**The `state.activeMarks` returns the intersection of marks in the selection.** Previously there was only `state.marks` which returns marks that appeared on _any_ character in the selection. But `state.activeMarks` returns marks that appear on _every_ character in the selection, which is often more useful for implementing standard rich-text editor behaviors.
|
||||
|
||||
---
|
||||
|
||||
@@ -252,7 +280,7 @@ function onKeyDown(e, data, change) {
|
||||
|
||||
###### BREAKING
|
||||
|
||||
* **The `Html` serializer now uses `DOMParser` instead of `cheerio`.** Previously, the `Html` serializer used the `cheerio` library for representing elements in the serialization rule logic, but cheerio was a very large dependency. It has been removed, and the native browser `DOMParser` is now used instead. All HTML serialization rules will need to be updated. If you are working with Slate on the server, you can now pass in a custom serializer to the `Html` constructor, using the `parse5` library.
|
||||
**The `Html` serializer now uses `DOMParser` instead of `cheerio`.** Previously, the `Html` serializer used the `cheerio` library for representing elements in the serialization rule logic, but cheerio was a very large dependency. It has been removed, and the native browser `DOMParser` is now used instead. All HTML serialization rules will need to be updated. If you are working with Slate on the server, you can now pass in a custom serializer to the `Html` constructor, using the `parse5` library.
|
||||
|
||||
---
|
||||
|
||||
@@ -260,7 +288,7 @@ function onKeyDown(e, data, change) {
|
||||
|
||||
###### BREAKING
|
||||
|
||||
* **Returning `null` from the `Html` serializer skips the element.** Previously, `null` and `undefined` had the same behavior of skipping the rule and trying the rest of the rules. Now if you explicitly return `null` it will skip the element itself.
|
||||
**Returning `null` from the `Html` serializer skips the element.** Previously, `null` and `undefined` had the same behavior of skipping the rule and trying the rest of the rules. Now if you explicitly return `null` it will skip the element itself.
|
||||
|
||||
---
|
||||
|
||||
@@ -268,21 +296,22 @@ function onKeyDown(e, data, change) {
|
||||
|
||||
###### BREAKING
|
||||
|
||||
* **The `filterDescendants` and `findDescendants` methods are now depth-first.** This shouldn't affect almost anyone, since they are usually not the best things to be using for performance reasons. If you happen to have a very specific use case that needs breadth-first, (or even likely something better), you'll need to implement it yourself.
|
||||
**The `filterDescendants` and `findDescendants` methods are now depth-first.** This shouldn't affect almost anyone, since they are usually not the best things to be using for performance reasons. If you happen to have a very specific use case that needs breadth-first, (or even likely something better), you'll need to implement it yourself.
|
||||
|
||||
###### DEPRECATED
|
||||
|
||||
* **Some `Node` methods have been deprecated!** There were a few methods that had been added over time that were either poorly named that have been deprecated and renamed, and a handful of methods that are no longer useful for the core library that have been deprecated. Here's a full list:
|
||||
* `areDescendantSorted` -> `areDescendantsSorted`
|
||||
* `getHighestChild` -> `getFurthestAncestor`
|
||||
* `getHighestOnlyChildParent` -> `getFurthestOnlyChildAncestor`
|
||||
* `concatChildren`
|
||||
* `decorateTexts`
|
||||
* `filterDescendantsDeep`
|
||||
* `findDescendantDeep`
|
||||
* `getChildrenBetween`
|
||||
* `getChildrenBetweenIncluding`
|
||||
* `isInlineSplitAtRange`
|
||||
**Some `Node` methods have been deprecated!** There were a few methods that had been added over time that were either poorly named that have been deprecated and renamed, and a handful of methods that are no longer useful for the core library that have been deprecated. Here's a full list:
|
||||
|
||||
* `areDescendantSorted` -> `areDescendantsSorted`
|
||||
* `getHighestChild` -> `getFurthestAncestor`
|
||||
* `getHighestOnlyChildParent` -> `getFurthestOnlyChildAncestor`
|
||||
* `concatChildren`
|
||||
* `decorateTexts`
|
||||
* `filterDescendantsDeep`
|
||||
* `findDescendantDeep`
|
||||
* `getChildrenBetween`
|
||||
* `getChildrenBetweenIncluding`
|
||||
* `isInlineSplitAtRange`
|
||||
|
||||
---
|
||||
|
||||
@@ -290,7 +319,7 @@ function onKeyDown(e, data, change) {
|
||||
|
||||
###### BREAKING
|
||||
|
||||
* **The `plugin.render` property is now called `plugin.renderPortal`.** This is to make way for the new `plugin.render` property that offers HOC-like behavior, so that plugins can augment the editor however they choose.
|
||||
**The `plugin.render` property is now called `plugin.renderPortal`.** This is to make way for the new `plugin.render` property that offers HOC-like behavior, so that plugins can augment the editor however they choose.
|
||||
|
||||
---
|
||||
|
||||
@@ -298,31 +327,32 @@ function onKeyDown(e, data, change) {
|
||||
|
||||
###### DEPRECATED
|
||||
|
||||
* **Some `Selection` methods have been deprecated!** Previously there were many inconsistencies in the naming and handling of selection changes. This has all been cleaned up, but in the process some methods have been deprecated. Here is a full list of the deprecated methods and their new alternatives:
|
||||
**Some `Selection` methods have been deprecated!** Previously there were many inconsistencies in the naming and handling of selection changes. This has all been cleaned up, but in the process some methods have been deprecated. Here is a full list of the deprecated methods and their new alternatives:
|
||||
|
||||
* `moveToOffsets` -> `moveOffsetsTo`
|
||||
* `moveForward` -> `move`
|
||||
* `moveBackward` -> `move`
|
||||
* `moveAnchorOffset` -> `moveAnchor`
|
||||
* `moveFocusOffset` -> `moveFocus`
|
||||
* `moveStartOffset` -> `moveStart`
|
||||
* `moveEndOffset` -> `moveEnd`
|
||||
* `extendForward` -> `extend`
|
||||
* `extendBackward` -> `extend`
|
||||
* `unset` -> `deselect`
|
||||
* `moveToOffsets` -> `moveOffsetsTo`
|
||||
* `moveForward` -> `move`
|
||||
* `moveBackward` -> `move`
|
||||
* `moveAnchorOffset` -> `moveAnchor`
|
||||
* `moveFocusOffset` -> `moveFocus`
|
||||
* `moveStartOffset` -> `moveStart`
|
||||
* `moveEndOffset` -> `moveEnd`
|
||||
* `extendForward` -> `extend`
|
||||
* `extendBackward` -> `extend`
|
||||
* `unset` -> `deselect`
|
||||
|
||||
* **Some selection transforms have been deprecated!** Along with the methods, the selection-based transforms have also been refactored, resulting in deprecations. Here is a full list of the deprecated transforms and their new alternatives:
|
||||
* `moveTo` -> `select`
|
||||
* `moveToOffsets` -> `moveOffsetsTo`
|
||||
* `moveForward` -> `move`
|
||||
* `moveBackward` -> `move`
|
||||
* `moveStartOffset` -> `moveStart`
|
||||
* `moveEndOffset` -> `moveEnd`
|
||||
* `extendForward` -> `extend`
|
||||
* `extendBackward` -> `extend`
|
||||
* `flipSelection` -> `flip`
|
||||
* `unsetSelection` -> `deselect`
|
||||
* `unsetMarks`
|
||||
**Some selection transforms have been deprecated!** Along with the methods, the selection-based transforms have also been refactored, resulting in deprecations. Here is a full list of the deprecated transforms and their new alternatives:
|
||||
|
||||
* `moveTo` -> `select`
|
||||
* `moveToOffsets` -> `moveOffsetsTo`
|
||||
* `moveForward` -> `move`
|
||||
* `moveBackward` -> `move`
|
||||
* `moveStartOffset` -> `moveStart`
|
||||
* `moveEndOffset` -> `moveEnd`
|
||||
* `extendForward` -> `extend`
|
||||
* `extendBackward` -> `extend`
|
||||
* `flipSelection` -> `flip`
|
||||
* `unsetSelection` -> `deselect`
|
||||
* `unsetMarks`
|
||||
|
||||
---
|
||||
|
||||
@@ -330,7 +360,7 @@ function onKeyDown(e, data, change) {
|
||||
|
||||
###### BREAKING
|
||||
|
||||
* **Inline nodes are now always surrounded by text nodes.** Previously this behavior only occured for inline nodes with `isVoid: true`. Now, all inline nodes will always be surrounded by text nodes. If text nodes don't exist, empty ones will be created. This allows for more consistent behavior across Slate, and parity with other editing experiences.
|
||||
**Inline nodes are now always surrounded by text nodes.** Previously this behavior only occured for inline nodes with `isVoid: true`. Now, all inline nodes will always be surrounded by text nodes. If text nodes don't exist, empty ones will be created. This allows for more consistent behavior across Slate, and parity with other editing experiences.
|
||||
|
||||
---
|
||||
|
||||
@@ -338,15 +368,15 @@ function onKeyDown(e, data, change) {
|
||||
|
||||
###### BREAKING
|
||||
|
||||
* **The unique `key` generated values have changed.** Previously, Slate generated unique keys that looked like `'9dk3'`. But they were not very conflict-resistant. Now the keys are simple string of auto-incrementing numbers, like `'0'`, `'1'`, `'2'`. This makes more clear that keys are simply a convenient way to uniquely reference nodes in the **short-term** lifespan of a single in-memory instance of Slate. They are not designed to be used for long-term uniqueness. A new `setKeyGenerator` function has been exported that allows you to pass in your own key generating mechanism if you want to ensure uniqueness.
|
||||
**The unique `key` generated values have changed.** Previously, Slate generated unique keys that looked like `'9dk3'`. But they were not very conflict-resistant. Now the keys are simple string of auto-incrementing numbers, like `'0'`, `'1'`, `'2'`. This makes more clear that keys are simply a convenient way to uniquely reference nodes in the **short-term** lifespan of a single in-memory instance of Slate. They are not designed to be used for long-term uniqueness. A new `setKeyGenerator` function has been exported that allows you to pass in your own key generating mechanism if you want to ensure uniqueness.
|
||||
|
||||
* **The `Raw` serializer doesn't preserve keys by default.** Previously, the `Raw` serializer would omit keys when passed the `terse: true` option, but preserve them without it. Now it will always omit keys, unless you pass the new `preserveKeys: true` option. This better reflects that keys are temporary, in-memory IDs.
|
||||
**The `Raw` serializer doesn't preserve keys by default.** Previously, the `Raw` serializer would omit keys when passed the `terse: true` option, but preserve them without it. Now it will always omit keys, unless you pass the new `preserveKeys: true` option. This better reflects that keys are temporary, in-memory IDs.
|
||||
|
||||
* **Operations on the document now update the selection when needed.** This won't affect you unless you were doing some very specific things with transforms and updating selections. Overall, this makes it much easier to write transforms, since in most cases, the underlying operations will update the selection as you would expect without you doing anything.
|
||||
**Operations on the document now update the selection when needed.** This won't affect you unless you were doing some very specific things with transforms and updating selections. Overall, this makes it much easier to write transforms, since in most cases, the underlying operations will update the selection as you would expect without you doing anything.
|
||||
|
||||
###### DEPRECATED
|
||||
|
||||
* **Node accessor methods no longer accept being passed another node!** Previously, node accessor methods like `node.getParent` could be passed either a `key` string or a `node` object. For performance reasons, passing in a `node` object is being deprecated. So if you have any calls that look like: `node.getParent(descendant)`, they will now need to be written as `node.getParent(descendant.key)`. They will throw a warning for now, and will throw an error in a later version of Slate.
|
||||
**Node accessor methods no longer accept being passed another node!** Previously, node accessor methods like `node.getParent` could be passed either a `key` string or a `node` object. For performance reasons, passing in a `node` object is being deprecated. So if you have any calls that look like: `node.getParent(descendant)`, they will now need to be written as `node.getParent(descendant.key)`. They will throw a warning for now, and will throw an error in a later version of Slate.
|
||||
|
||||
---
|
||||
|
||||
@@ -354,13 +384,13 @@ function onKeyDown(e, data, change) {
|
||||
|
||||
###### BREAKING
|
||||
|
||||
* **The `undo` and `redo` transforms need to be applied!** Previously, `undo` and `redo` were special cased such that they did not require an `.apply()` call, and instead would return a new `State` directly. Now this is no longer the case, and they are just like every other transform.
|
||||
**The `undo` and `redo` transforms need to be applied!** Previously, `undo` and `redo` were special cased such that they did not require an `.apply()` call, and instead would return a new `State` directly. Now this is no longer the case, and they are just like every other transform.
|
||||
|
||||
* **Transforms are no longer exposed on `State` or `Node`.** The transforms API has been completely refactored to be built up of "operations" for collaborative editing support. As part of this refactor, the transforms are now only available via the `state.transform()` API, and aren't exposed on the `State` or `Node` objects as they were before.
|
||||
**Transforms are no longer exposed on `State` or `Node`.** The transforms API has been completely refactored to be built up of "operations" for collaborative editing support. As part of this refactor, the transforms are now only available via the `state.transform()` API, and aren't exposed on the `State` or `Node` objects as they were before.
|
||||
|
||||
* **`Transform` objects are now mutable.** Previously `Transform` was an Immutable.js `Record`, but now it is a simple constructor. This is because transforms are inherently mutating their representation of a state, but this decision is [up for discussion](https://github.com/ianstormtaylor/slate/issues/328).
|
||||
**`Transform` objects are now mutable.** Previously `Transform` was an Immutable.js `Record`, but now it is a simple constructor. This is because transforms are inherently mutating their representation of a state, but this decision is [up for discussion](https://github.com/ianstormtaylor/slate/issues/328).
|
||||
|
||||
* **The selection can now be "unset".** Previously, a selection could never be in an "unset" state where the `anchorKey` or `focusKey` was null. This is no longer technically true, although this shouldn't really affect anyone in practice.
|
||||
**The selection can now be "unset".** Previously, a selection could never be in an "unset" state where the `anchorKey` or `focusKey` was null. This is no longer technically true, although this shouldn't really affect anyone in practice.
|
||||
|
||||
---
|
||||
|
||||
@@ -368,9 +398,9 @@ function onKeyDown(e, data, change) {
|
||||
|
||||
###### BREAKING
|
||||
|
||||
* **The `renderNode` and `renderMark` properties are gone!** Previously, rendering nodes and marks happened via these two properties of the `<Editor>`, but this has been replaced by the new `schema` property. Check out the updated examples to see how to define a schema! There's a good chance this eliminates extra code for most use cases! :smile:
|
||||
**The `renderNode` and `renderMark` properties are gone!** Previously, rendering nodes and marks happened via these two properties of the `<Editor>`, but this has been replaced by the new `schema` property. Check out the updated examples to see how to define a schema! There's a good chance this eliminates extra code for most use cases! :smile:
|
||||
|
||||
* **The `renderDecorations` property is gone!** Decoration rendering has also been replaced by the new `schema` property of the `<Editor>`.
|
||||
**The `renderDecorations` property is gone!** Decoration rendering has also been replaced by the new `schema` property of the `<Editor>`.
|
||||
|
||||
---
|
||||
|
||||
@@ -378,7 +408,7 @@ function onKeyDown(e, data, change) {
|
||||
|
||||
###### BREAKING
|
||||
|
||||
* **The `data.files` property is now an `Array`.** Previously it was a native `FileList` object, but needed to be changed to add full support for pasting an dropping files in all browsers. This shouldn't affect you unless you were specifically depending on it being array-like instead of a true `Array`.
|
||||
**The `data.files` property is now an `Array`.** Previously it was a native `FileList` object, but needed to be changed to add full support for pasting an dropping files in all browsers. This shouldn't affect you unless you were specifically depending on it being array-like instead of a true `Array`.
|
||||
|
||||
---
|
||||
|
||||
@@ -386,7 +416,7 @@ function onKeyDown(e, data, change) {
|
||||
|
||||
###### BREAKING
|
||||
|
||||
* **Void nodes are renderered implicitly again!** Previously Slate had required that you wrap void node renderers yourself with the exposed `<Void>` wrapping component. This was to allow for selection styling, but a change was made to make selection styling able to handled in Javascript. Now the `<Void>` wrapper will be implicitly rendered by Slate, so you do not need to worry about it, and "voidness" only needs to toggled in one place, the `isVoid: true` property of a node.
|
||||
**Void nodes are renderered implicitly again!** Previously Slate had required that you wrap void node renderers yourself with the exposed `<Void>` wrapping component. This was to allow for selection styling, but a change was made to make selection styling able to handled in Javascript. Now the `<Void>` wrapper will be implicitly rendered by Slate, so you do not need to worry about it, and "voidness" only needs to toggled in one place, the `isVoid: true` property of a node.
|
||||
|
||||
---
|
||||
|
||||
@@ -394,7 +424,7 @@ function onKeyDown(e, data, change) {
|
||||
|
||||
###### BREAKING
|
||||
|
||||
* **Marks are now renderable as components.** Previously the only supported way to render marks was by returning a `style` object. Now you can return a style object, a class name string, or a full React component. Because of this, the DOM will be renderered slightly differently than before, resulting in an extra `<span>` when rendering non-component marks. This won't affect you unless you were depending on the DOM output by Slate for some reason.
|
||||
**Marks are now renderable as components.** Previously the only supported way to render marks was by returning a `style` object. Now you can return a style object, a class name string, or a full React component. Because of this, the DOM will be renderered slightly differently than before, resulting in an extra `<span>` when rendering non-component marks. This won't affect you unless you were depending on the DOM output by Slate for some reason.
|
||||
|
||||
---
|
||||
|
||||
@@ -402,7 +432,7 @@ function onKeyDown(e, data, change) {
|
||||
|
||||
###### BREAKING
|
||||
|
||||
* **The `wrap` and `unwrap` method signatures have changed!** Previously, you would pass `type` and `data` as separate parameters, for example: `wrapBlock('code', { src: true })`. This was inconsistent with other transforms, and has been updated such that a single argument of `properties` is passed instead. So that example could now be: `wrapBlock({ type: 'code', { data: { src: true }})`. You can still pass a `type` string as shorthand, which will be the most frequent use case, for example: `wrapBlock('code')`.
|
||||
**The `wrap` and `unwrap` method signatures have changed!** Previously, you would pass `type` and `data` as separate parameters, for example: `wrapBlock('code', { src: true })`. This was inconsistent with other transforms, and has been updated such that a single argument of `properties` is passed instead. So that example could now be: `wrapBlock({ type: 'code', { data: { src: true }})`. You can still pass a `type` string as shorthand, which will be the most frequent use case, for example: `wrapBlock('code')`.
|
||||
|
||||
---
|
||||
|
||||
@@ -410,13 +440,13 @@ function onKeyDown(e, data, change) {
|
||||
|
||||
###### BREAKING
|
||||
|
||||
* **The `onKeyDown` and `onBeforeInput` handlers signatures have changed!** Previously, some Slate handlers had a signature of `(e, state, editor)` and others had a signature of `(e, data, state, editor)`. Now all handlers will be passed a `data` object—which contains Slate-specific data related to the event—even if it is empty. This is helpful for future compatibility where we might need to add data to a handler that previously didn't have any, and is nicer for consistency. The `onKeyDown` handler's new `data` object contains the `key` name, `code` and a series of `is*` properties to make working with hotkeys easier. The `onBeforeInput` handler's new `data` object is empty.
|
||||
**The `onKeyDown` and `onBeforeInput` handlers signatures have changed!** Previously, some Slate handlers had a signature of `(e, state, editor)` and others had a signature of `(e, data, state, editor)`. Now all handlers will be passed a `data` object—which contains Slate-specific data related to the event—even if it is empty. This is helpful for future compatibility where we might need to add data to a handler that previously didn't have any, and is nicer for consistency. The `onKeyDown` handler's new `data` object contains the `key` name, `code` and a series of `is*` properties to make working with hotkeys easier. The `onBeforeInput` handler's new `data` object is empty.
|
||||
|
||||
* **The `Utils` export has been removed.** Previously, a `Key` utility and the `findDOMNode` utility were exposed under the `Utils` object. The `Key` has been removed in favor of the `data` object passed to `onKeyDown`. And then `findDOMNode` utility has been upgraded to a top-level named export, so you'll now need to access it via `import { findDOMNode } from 'slate'`.
|
||||
**The `Utils` export has been removed.** Previously, a `Key` utility and the `findDOMNode` utility were exposed under the `Utils` object. The `Key` has been removed in favor of the `data` object passed to `onKeyDown`. And then `findDOMNode` utility has been upgraded to a top-level named export, so you'll now need to access it via `import { findDOMNode } from 'slate'`.
|
||||
|
||||
* **Void nodes now permanently have `" "` as content.** Previously, they contained an empty string, but this isn't technically correct, since they have content and shouldn't be considered "empty". Now they will have a single space of content. This shouldn't really affect anyone, unless you happened to be accessing that string for serialization.
|
||||
**Void nodes now permanently have `" "` as content.** Previously, they contained an empty string, but this isn't technically correct, since they have content and shouldn't be considered "empty". Now they will have a single space of content. This shouldn't really affect anyone, unless you happened to be accessing that string for serialization.
|
||||
|
||||
* **Empty inline nodes are now impossible.** This is to stay consistent with native `contenteditable` behavior, where although technically the elements can exist, they have odd behavior and can never be selected.
|
||||
**Empty inline nodes are now impossible.** This is to stay consistent with native `contenteditable` behavior, where although technically the elements can exist, they have odd behavior and can never be selected.
|
||||
|
||||
---
|
||||
|
||||
@@ -424,7 +454,7 @@ function onKeyDown(e, data, change) {
|
||||
|
||||
###### BREAKING
|
||||
|
||||
* **The `Raw` serializer is no longer terse by default!** Previously, the `Raw` serializer would return a "terse" representation of the document, omitting information that wasn't _strictly_ necessary to deserialize later, like the `key` of nodes. By default this no longer happens. You have to opt-in to the behavior by passing `{ terse: true }` as the second `options` argument of the `deserialize` and `serialize` methods.
|
||||
**The `Raw` serializer is no longer terse by default!** Previously, the `Raw` serializer would return a "terse" representation of the document, omitting information that wasn't _strictly_ necessary to deserialize later, like the `key` of nodes. By default this no longer happens. You have to opt-in to the behavior by passing `{ terse: true }` as the second `options` argument of the `deserialize` and `serialize` methods.
|
||||
|
||||
---
|
||||
|
||||
@@ -432,9 +462,9 @@ function onKeyDown(e, data, change) {
|
||||
|
||||
###### BREAKING
|
||||
|
||||
* **Void components are no longer rendered implicity!** Previously, Slate would automatically wrap any node with `isVoid: true` in a `<Void>` component. But doing this prevented you from customizing the wrapper, like adding a `className` or `style` property. So you **must now render the wrapper yourself**, and it has been exported as `Slate.Void`. This, combined with a small change to the `<Void>` component's structure allows the "selected" state of void nodes to be rendered purely with CSS based on the `:focus` property of a `<Void>` element, which previously [had to be handled in Javascript](https://github.com/ianstormtaylor/slate/commit/31782cb11a272466b6b9f1e4d6cc0c698504d97f). This allows us to streamline selection-handling logic, improving performance and reducing complexity.
|
||||
**Void components are no longer rendered implicity!** Previously, Slate would automatically wrap any node with `isVoid: true` in a `<Void>` component. But doing this prevented you from customizing the wrapper, like adding a `className` or `style` property. So you **must now render the wrapper yourself**, and it has been exported as `Slate.Void`. This, combined with a small change to the `<Void>` component's structure allows the "selected" state of void nodes to be rendered purely with CSS based on the `:focus` property of a `<Void>` element, which previously [had to be handled in Javascript](https://github.com/ianstormtaylor/slate/commit/31782cb11a272466b6b9f1e4d6cc0c698504d97f). This allows us to streamline selection-handling logic, improving performance and reducing complexity.
|
||||
|
||||
* **`data-offset-key` is now `<key>-<index>` instead of `<key>:<start>-<end>`.** This shouldn't actually affect anyone, unless you were specifically relying on that attribute in the DOM. This change greatly reduces the number of re-renders needed, since previously any additional characters would cause a cascading change in the `<start>` and `<end>` offsets of latter text ranges.
|
||||
**`data-offset-key` is now `<key>-<index>` instead of `<key>:<start>-<end>`.** This shouldn't actually affect anyone, unless you were specifically relying on that attribute in the DOM. This change greatly reduces the number of re-renders needed, since previously any additional characters would cause a cascading change in the `<start>` and `<end>` offsets of latter text ranges.
|
||||
|
||||
---
|
||||
|
||||
@@ -442,9 +472,9 @@ function onKeyDown(e, data, change) {
|
||||
|
||||
###### BREAKING
|
||||
|
||||
* **`node.getTextNodes()` is now `node.getTexts()`.** This is just for consistency with the other existing `Node` methods like `getBlocks()`, `getInlines()`, etc. And it's nicely shorter. :wink:
|
||||
**`node.getTextNodes()` is now `node.getTexts()`.** This is just for consistency with the other existing `Node` methods like `getBlocks()`, `getInlines()`, etc. And it's nicely shorter. :wink:
|
||||
|
||||
* **`Node` methods now `throw` earlier during unexpected states.** This shouldn't break anything for most folks, unless a strange edge-case was going undetected previously.
|
||||
**`Node` methods now `throw` earlier during unexpected states.** This shouldn't break anything for most folks, unless a strange edge-case was going undetected previously.
|
||||
|
||||
---
|
||||
|
||||
@@ -452,7 +482,7 @@ function onKeyDown(e, data, change) {
|
||||
|
||||
###### BREAKING
|
||||
|
||||
* **`renderMark(mark, state, editor)` is now `renderMark(mark, marks, state, editor)`.** This change allows you to render marks based on multiple `marks` presence at once on a given range of text, for example using a custom `BoldItalic.otf` font when text has both `bold` and `italic` marks.
|
||||
**`renderMark(mark, state, editor)` is now `renderMark(mark, marks, state, editor)`.** This change allows you to render marks based on multiple `marks` presence at once on a given range of text, for example using a custom `BoldItalic.otf` font when text has both `bold` and `italic` marks.
|
||||
|
||||
---
|
||||
|
||||
@@ -460,7 +490,7 @@ function onKeyDown(e, data, change) {
|
||||
|
||||
###### BREAKING
|
||||
|
||||
* **`transform.unwrapBlock()` now unwraps selectively.** Previously, calling `unwrapBlock` with a range representing a middle sibling would unwrap _all_ of the siblings, removing the wrapping block entirely. Now, calling it with those same arguments will only move the middle sibling up a layer in the hierarchy, preserving the nesting on any of its siblings. This changes makes it much simpler to implement functionality like unwrapping a single list item, which previously would unwrap the entire list.
|
||||
**`transform.unwrapBlock()` now unwraps selectively.** Previously, calling `unwrapBlock` with a range representing a middle sibling would unwrap _all_ of the siblings, removing the wrapping block entirely. Now, calling it with those same arguments will only move the middle sibling up a layer in the hierarchy, preserving the nesting on any of its siblings. This changes makes it much simpler to implement functionality like unwrapping a single list item, which previously would unwrap the entire list.
|
||||
|
||||
---
|
||||
|
||||
@@ -468,7 +498,7 @@ function onKeyDown(e, data, change) {
|
||||
|
||||
###### BREAKING
|
||||
|
||||
* **`transform.mark()` is now `transform.addMark()` and `transform.unmark()` is now `transform.removeMark()`.** The new names make it clearer that the transforms are actions being performed, and it paves the way for adding a `toggleMark` convenience as well.
|
||||
**`transform.mark()` is now `transform.addMark()` and `transform.unmark()` is now `transform.removeMark()`.** The new names make it clearer that the transforms are actions being performed, and it paves the way for adding a `toggleMark` convenience as well.
|
||||
|
||||
---
|
||||
|
||||
|
@@ -162,7 +162,7 @@ Changes.insertFragment = (change, fragment) => {
|
||||
selection.hasEdgeAtEndOf(endText)
|
||||
|
||||
const isInserting =
|
||||
fragment.hasBlocks(firstChild.key) || fragment.hasBlocks(lastChild.key)
|
||||
firstChild.hasBlockChildren() || lastChild.hasBlockChildren()
|
||||
|
||||
change.insertFragmentAtRange(selection, fragment)
|
||||
value = change.value
|
||||
|
@@ -4,7 +4,7 @@ import Block from '../models/block'
|
||||
import Inline from '../models/inline'
|
||||
import Mark from '../models/mark'
|
||||
import Node from '../models/node'
|
||||
import String from '../utils/string'
|
||||
import TextUtils from '../utils/text-utils'
|
||||
|
||||
/**
|
||||
* Changes.
|
||||
@@ -278,7 +278,7 @@ Changes.deleteCharBackwardAtRange = (change, range, options) => {
|
||||
const offset = startBlock.getOffset(startKey)
|
||||
const o = offset + startOffset
|
||||
const { text } = startBlock
|
||||
const n = String.getCharOffsetBackward(text, o)
|
||||
const n = TextUtils.getCharOffsetBackward(text, o)
|
||||
change.deleteBackwardAtRange(range, n, options)
|
||||
}
|
||||
|
||||
@@ -318,7 +318,7 @@ Changes.deleteWordBackwardAtRange = (change, range, options) => {
|
||||
const offset = startBlock.getOffset(startKey)
|
||||
const o = offset + startOffset
|
||||
const { text } = startBlock
|
||||
const n = String.getWordOffsetBackward(text, o)
|
||||
const n = TextUtils.getWordOffsetBackward(text, o)
|
||||
change.deleteBackwardAtRange(range, n, options)
|
||||
}
|
||||
|
||||
@@ -449,7 +449,7 @@ Changes.deleteCharForwardAtRange = (change, range, options) => {
|
||||
const offset = startBlock.getOffset(startKey)
|
||||
const o = offset + startOffset
|
||||
const { text } = startBlock
|
||||
const n = String.getCharOffsetForward(text, o)
|
||||
const n = TextUtils.getCharOffsetForward(text, o)
|
||||
change.deleteForwardAtRange(range, n, options)
|
||||
}
|
||||
|
||||
@@ -489,7 +489,7 @@ Changes.deleteWordForwardAtRange = (change, range, options) => {
|
||||
const offset = startBlock.getOffset(startKey)
|
||||
const o = offset + startOffset
|
||||
const { text } = startBlock
|
||||
const n = String.getWordOffsetForward(text, o)
|
||||
const n = TextUtils.getWordOffsetForward(text, o)
|
||||
change.deleteForwardAtRange(range, n, options)
|
||||
}
|
||||
|
||||
@@ -599,13 +599,6 @@ Changes.deleteForwardAtRange = (change, range, n = 1, options = {}) => {
|
||||
}
|
||||
}
|
||||
|
||||
// If the focus node is inside a void, go up until right before it.
|
||||
if (document.hasVoidParent(node.key)) {
|
||||
const parent = document.getClosestVoid(node.key)
|
||||
node = document.getPreviousText(parent.key)
|
||||
offset = node.text.length
|
||||
}
|
||||
|
||||
range = range.merge({
|
||||
focusKey: node.key,
|
||||
focusOffset: offset,
|
||||
@@ -734,7 +727,7 @@ Changes.insertFragmentAtRange = (change, range, fragment, options = {}) => {
|
||||
|
||||
// If the fragment starts or ends with single nested block, (e.g., table),
|
||||
// do not merge this fragment with existing blocks.
|
||||
if (fragment.hasBlocks(firstChild.key) || fragment.hasBlocks(lastChild.key)) {
|
||||
if (firstChild.hasBlockChildren() || lastChild.hasBlockChildren()) {
|
||||
fragment.nodes.reverse().forEach(node => {
|
||||
change.insertBlockAtRange(range, node, options)
|
||||
})
|
||||
@@ -750,7 +743,7 @@ Changes.insertFragmentAtRange = (change, range, fragment, options = {}) => {
|
||||
)
|
||||
const lonelyChild = lonelyParent || firstBlock
|
||||
const startIndex = parent.nodes.indexOf(startBlock)
|
||||
fragment = fragment.removeDescendant(lonelyChild.key)
|
||||
fragment = fragment.removeNode(lonelyChild.key)
|
||||
|
||||
fragment.nodes.forEach((node, i) => {
|
||||
const newIndex = startIndex + i + 1
|
||||
|
@@ -2,6 +2,7 @@ import Block from '../models/block'
|
||||
import Inline from '../models/inline'
|
||||
import Mark from '../models/mark'
|
||||
import Node from '../models/node'
|
||||
import PathUtils from '../utils/path-utils'
|
||||
import Range from '../models/range'
|
||||
|
||||
/**
|
||||
@@ -13,24 +14,21 @@ import Range from '../models/range'
|
||||
const Changes = {}
|
||||
|
||||
/**
|
||||
* Add mark to text at `offset` and `length` in node by `key`.
|
||||
* Add mark to text at `offset` and `length` in node by `path`.
|
||||
*
|
||||
* @param {Change} change
|
||||
* @param {String} key
|
||||
* @param {Array} path
|
||||
* @param {Number} offset
|
||||
* @param {Number} length
|
||||
* @param {Mixed} mark
|
||||
* @param {Object} options
|
||||
* @property {Boolean} normalize
|
||||
*/
|
||||
|
||||
Changes.addMarkByKey = (change, key, offset, length, mark, options = {}) => {
|
||||
Changes.addMarkByPath = (change, path, offset, length, mark, options) => {
|
||||
mark = Mark.create(mark)
|
||||
const normalize = change.getFlag('normalize', options)
|
||||
const { value } = change
|
||||
const { document } = value
|
||||
const path = document.getPath(key)
|
||||
const node = document.getNode(key)
|
||||
const node = document.assertNode(path)
|
||||
const leaves = node.getLeaves()
|
||||
|
||||
const operations = []
|
||||
@@ -65,84 +63,65 @@ Changes.addMarkByKey = (change, key, offset, length, mark, options = {}) => {
|
||||
})
|
||||
|
||||
change.applyOperations(operations)
|
||||
|
||||
if (normalize) {
|
||||
const parent = document.getParent(key)
|
||||
change.normalizeNodeByKey(parent.key)
|
||||
}
|
||||
change.normalizeParentByPath(path, options)
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert a `fragment` at `index` in a node by `key`.
|
||||
* Insert a `fragment` at `index` in a node by `path`.
|
||||
*
|
||||
* @param {Change} change
|
||||
* @param {String} key
|
||||
* @param {Array} path
|
||||
* @param {Number} index
|
||||
* @param {Fragment} fragment
|
||||
* @param {Object} options
|
||||
* @property {Boolean} normalize
|
||||
*/
|
||||
|
||||
Changes.insertFragmentByKey = (change, key, index, fragment, options = {}) => {
|
||||
const normalize = change.getFlag('normalize', options)
|
||||
|
||||
Changes.insertFragmentByPath = (change, path, index, fragment, options) => {
|
||||
fragment.nodes.forEach((node, i) => {
|
||||
change.insertNodeByKey(key, index + i, node)
|
||||
change.insertNodeByPath(path, index + i, node)
|
||||
})
|
||||
|
||||
if (normalize) {
|
||||
change.normalizeNodeByKey(key)
|
||||
}
|
||||
change.normalizeNodeByPath(path, options)
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert a `node` at `index` in a node by `key`.
|
||||
* Insert a `node` at `index` in a node by `path`.
|
||||
*
|
||||
* @param {Change} change
|
||||
* @param {String} key
|
||||
* @param {Array} path
|
||||
* @param {Number} index
|
||||
* @param {Node} node
|
||||
* @param {Object} options
|
||||
* @property {Boolean} normalize
|
||||
*/
|
||||
|
||||
Changes.insertNodeByKey = (change, key, index, node, options = {}) => {
|
||||
const normalize = change.getFlag('normalize', options)
|
||||
Changes.insertNodeByPath = (change, path, index, node, options) => {
|
||||
const { value } = change
|
||||
const { document } = value
|
||||
const path = document.getPath(key)
|
||||
|
||||
change.applyOperation({
|
||||
type: 'insert_node',
|
||||
value,
|
||||
path: [...path, index],
|
||||
path: path.concat(index),
|
||||
node,
|
||||
})
|
||||
|
||||
if (normalize) {
|
||||
change.normalizeNodeByKey(key)
|
||||
}
|
||||
change.normalizeNodeByPath(path, options)
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert `text` at `offset` in node by `key`.
|
||||
* Insert `text` at `offset` in node by `path`.
|
||||
*
|
||||
* @param {Change} change
|
||||
* @param {String} key
|
||||
* @param {Array} path
|
||||
* @param {Number} offset
|
||||
* @param {String} text
|
||||
* @param {Set<Mark>} marks (optional)
|
||||
* @param {Object} options
|
||||
* @property {Boolean} normalize
|
||||
*/
|
||||
|
||||
Changes.insertTextByKey = (change, key, offset, text, marks, options = {}) => {
|
||||
const normalize = change.getFlag('normalize', options)
|
||||
|
||||
Changes.insertTextByPath = (change, path, offset, text, marks, options) => {
|
||||
const { value } = change
|
||||
const { document } = value
|
||||
const path = document.getPath(key)
|
||||
const node = document.getNode(key)
|
||||
const node = document.assertNode(path)
|
||||
marks = marks || node.getMarksAtIndex(offset)
|
||||
|
||||
change.applyOperation({
|
||||
@@ -154,31 +133,27 @@ Changes.insertTextByKey = (change, key, offset, text, marks, options = {}) => {
|
||||
marks,
|
||||
})
|
||||
|
||||
if (normalize) {
|
||||
const parent = document.getParent(key)
|
||||
change.normalizeNodeByKey(parent.key)
|
||||
}
|
||||
change.normalizeParentByPath(path, options)
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge a node by `key` with the previous node.
|
||||
* Merge a node by `path` with the previous node.
|
||||
*
|
||||
* @param {Change} change
|
||||
* @param {String} key
|
||||
* @param {Array} path
|
||||
* @param {Object} options
|
||||
* @property {Boolean} normalize
|
||||
*/
|
||||
|
||||
Changes.mergeNodeByKey = (change, key, options = {}) => {
|
||||
const normalize = change.getFlag('normalize', options)
|
||||
Changes.mergeNodeByPath = (change, path, options) => {
|
||||
const { value } = change
|
||||
const { document } = value
|
||||
const path = document.getPath(key)
|
||||
const original = document.getDescendant(key)
|
||||
const previous = document.getPreviousSibling(key)
|
||||
const original = document.getDescendant(path)
|
||||
const previous = document.getPreviousSibling(path)
|
||||
|
||||
if (!previous) {
|
||||
throw new Error(`Unable to merge node with key "${key}", no previous key.`)
|
||||
throw new Error(
|
||||
`Unable to merge node with path "${path}", because it has no previous sibling.`
|
||||
)
|
||||
}
|
||||
|
||||
const position =
|
||||
@@ -198,63 +173,49 @@ Changes.mergeNodeByKey = (change, key, options = {}) => {
|
||||
target: null,
|
||||
})
|
||||
|
||||
if (normalize) {
|
||||
const parent = document.getParent(key)
|
||||
change.normalizeNodeByKey(parent.key)
|
||||
}
|
||||
change.normalizeParentByPath(path, options)
|
||||
}
|
||||
|
||||
/**
|
||||
* Move a node by `key` to a new parent by `newKey` and `index`.
|
||||
* `newKey` is the key of the container (it can be the document itself)
|
||||
* Move a node by `path` to a new parent by `newPath` and `index`.
|
||||
*
|
||||
* @param {Change} change
|
||||
* @param {String} key
|
||||
* @param {String} newKey
|
||||
* @param {Array} path
|
||||
* @param {String} newPath
|
||||
* @param {Number} index
|
||||
* @param {Object} options
|
||||
* @property {Boolean} normalize
|
||||
*/
|
||||
|
||||
Changes.moveNodeByKey = (change, key, newKey, newIndex, options = {}) => {
|
||||
const normalize = change.getFlag('normalize', options)
|
||||
Changes.moveNodeByPath = (change, path, newPath, newIndex, options) => {
|
||||
const { value } = change
|
||||
const { document } = value
|
||||
const path = document.getPath(key)
|
||||
const newPath = document.getPath(newKey)
|
||||
|
||||
change.applyOperation({
|
||||
type: 'move_node',
|
||||
value,
|
||||
path,
|
||||
newPath: [...newPath, newIndex],
|
||||
newPath: newPath.concat(newIndex),
|
||||
})
|
||||
|
||||
if (normalize) {
|
||||
const parent = document.getCommonAncestor(key, newKey)
|
||||
change.normalizeNodeByKey(parent.key)
|
||||
}
|
||||
const ancestorPath = PathUtils.relate(path, newPath)
|
||||
change.normalizeNodeByPath(ancestorPath, options)
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove mark from text at `offset` and `length` in node by `key`.
|
||||
* Remove mark from text at `offset` and `length` in node by `path`.
|
||||
*
|
||||
* @param {Change} change
|
||||
* @param {String} key
|
||||
* @param {Array} path
|
||||
* @param {Number} offset
|
||||
* @param {Number} length
|
||||
* @param {Mark} mark
|
||||
* @param {Object} options
|
||||
* @property {Boolean} normalize
|
||||
*/
|
||||
|
||||
Changes.removeMarkByKey = (change, key, offset, length, mark, options = {}) => {
|
||||
Changes.removeMarkByPath = (change, path, offset, length, mark, options) => {
|
||||
mark = Mark.create(mark)
|
||||
const normalize = change.getFlag('normalize', options)
|
||||
const { value } = change
|
||||
const { document } = value
|
||||
const path = document.getPath(key)
|
||||
const node = document.getNode(key)
|
||||
const node = document.assertNode(path)
|
||||
const leaves = node.getLeaves()
|
||||
|
||||
const operations = []
|
||||
@@ -289,26 +250,21 @@ Changes.removeMarkByKey = (change, key, offset, length, mark, options = {}) => {
|
||||
})
|
||||
|
||||
change.applyOperations(operations)
|
||||
|
||||
if (normalize) {
|
||||
const parent = document.getParent(key)
|
||||
change.normalizeNodeByKey(parent.key)
|
||||
}
|
||||
change.normalizeParentByPath(path, options)
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all `marks` from node by `key`.
|
||||
* Remove all `marks` from node by `path`.
|
||||
*
|
||||
* @param {Change} change
|
||||
* @param {String} key
|
||||
* @param {Array} path
|
||||
* @param {Object} options
|
||||
* @property {Boolean} normalize
|
||||
*/
|
||||
|
||||
Changes.removeAllMarksByKey = (change, key, options = {}) => {
|
||||
Changes.removeAllMarksByPath = (change, path, options) => {
|
||||
const { state } = change
|
||||
const { document } = state
|
||||
const node = document.getNode(key)
|
||||
const node = document.assertNode(path)
|
||||
const texts = node.object === 'text' ? [node] : node.getTextsAsArray()
|
||||
|
||||
texts.forEach(text => {
|
||||
@@ -319,20 +275,17 @@ Changes.removeAllMarksByKey = (change, key, options = {}) => {
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a node by `key`.
|
||||
* Remove a node by `path`.
|
||||
*
|
||||
* @param {Change} change
|
||||
* @param {String} key
|
||||
* @param {Array} path
|
||||
* @param {Object} options
|
||||
* @property {Boolean} normalize
|
||||
*/
|
||||
|
||||
Changes.removeNodeByKey = (change, key, options = {}) => {
|
||||
const normalize = change.getFlag('normalize', options)
|
||||
Changes.removeNodeByPath = (change, path, options) => {
|
||||
const { value } = change
|
||||
const { document } = value
|
||||
const path = document.getPath(key)
|
||||
const node = document.getNode(key)
|
||||
const node = document.assertNode(path)
|
||||
|
||||
change.applyOperation({
|
||||
type: 'remove_node',
|
||||
@@ -341,26 +294,25 @@ Changes.removeNodeByKey = (change, key, options = {}) => {
|
||||
node,
|
||||
})
|
||||
|
||||
if (normalize) {
|
||||
const parent = document.getParent(key)
|
||||
change.normalizeNodeByKey(parent.key)
|
||||
}
|
||||
change.normalizeParentByPath(path, options)
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert `text` at `offset` in node by `key`.
|
||||
* Insert `text` at `offset` in node by `path`.
|
||||
*
|
||||
* @param {Change} change
|
||||
* @param {String} key
|
||||
* @param {Array} path
|
||||
* @param {String} text
|
||||
* @param {Set<Mark>} marks (optional)
|
||||
* @param {Object} options
|
||||
* @property {Boolean} normalize
|
||||
*/
|
||||
|
||||
Changes.setTextByKey = (change, key, text, marks, options = {}) => {
|
||||
const textNode = change.value.document.getDescendant(key)
|
||||
change.replaceTextByKey(key, 0, textNode.text.length, text, marks, options)
|
||||
Changes.setTextByPath = (change, path, text, marks, options) => {
|
||||
const { value } = change
|
||||
const { document } = value
|
||||
const node = document.assertNode(path)
|
||||
const end = node.text.length
|
||||
change.replaceTextByPath(path, 0, end, text, marks, options)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -372,13 +324,12 @@ Changes.setTextByKey = (change, key, text, marks, options = {}) => {
|
||||
* @param {string} text
|
||||
* @param {Set<Mark>} marks (optional)
|
||||
* @param {Object} options
|
||||
* @property {Boolean} normalize
|
||||
*
|
||||
*/
|
||||
|
||||
Changes.replaceTextByKey = (
|
||||
Changes.replaceTextByPath = (
|
||||
change,
|
||||
key,
|
||||
path,
|
||||
offset,
|
||||
length,
|
||||
text,
|
||||
@@ -386,21 +337,22 @@ Changes.replaceTextByKey = (
|
||||
options
|
||||
) => {
|
||||
const { document } = change.value
|
||||
const textNode = document.getDescendant(key)
|
||||
const node = document.assertNode(path)
|
||||
|
||||
if (length + offset > textNode.text.length) {
|
||||
length = textNode.text.length - offset
|
||||
if (length + offset > node.text.length) {
|
||||
length = node.text.length - offset
|
||||
}
|
||||
|
||||
const range = Range.create({
|
||||
anchorKey: key,
|
||||
focusKey: key,
|
||||
anchorPath: path,
|
||||
focusPath: path,
|
||||
anchorOffset: offset,
|
||||
focusOffset: offset + length,
|
||||
})
|
||||
}).normalize(document)
|
||||
|
||||
let activeMarks = document.getActiveMarksAtRange(range)
|
||||
|
||||
change.removeTextByKey(key, offset, length, { normalize: false })
|
||||
change.removeTextByPath(path, offset, length, { normalize: false })
|
||||
|
||||
if (!marks) {
|
||||
// Do not use mark at index when marks and activeMarks are both empty
|
||||
@@ -414,26 +366,23 @@ Changes.replaceTextByKey = (
|
||||
marks = activeMarks.merge(marks)
|
||||
}
|
||||
|
||||
change.insertTextByKey(key, offset, text, marks, options)
|
||||
change.insertTextByPath(path, offset, text, marks, options)
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove text at `offset` and `length` in node by `key`.
|
||||
* Remove text at `offset` and `length` in node by `path`.
|
||||
*
|
||||
* @param {Change} change
|
||||
* @param {String} key
|
||||
* @param {Array} path
|
||||
* @param {Number} offset
|
||||
* @param {Number} length
|
||||
* @param {Object} options
|
||||
* @property {Boolean} normalize
|
||||
*/
|
||||
|
||||
Changes.removeTextByKey = (change, key, offset, length, options = {}) => {
|
||||
const normalize = change.getFlag('normalize', options)
|
||||
Changes.removeTextByPath = (change, path, offset, length, options) => {
|
||||
const { value } = change
|
||||
const { document } = value
|
||||
const path = document.getPath(key)
|
||||
const node = document.getNode(key)
|
||||
const node = document.assertNode(path)
|
||||
const leaves = node.getLeaves()
|
||||
const { text } = node
|
||||
|
||||
@@ -469,65 +418,51 @@ Changes.removeTextByKey = (change, key, offset, length, options = {}) => {
|
||||
// Apply in reverse order, so subsequent removals don't impact previous ones.
|
||||
change.applyOperations(removals.reverse())
|
||||
|
||||
if (normalize) {
|
||||
const block = document.getClosestBlock(key)
|
||||
change.normalizeNodeByKey(block.key)
|
||||
}
|
||||
const block = document.getClosestBlock(node.key)
|
||||
change.normalizeNodeByKey(block.key, options)
|
||||
}
|
||||
|
||||
/**
|
||||
`* Replace a `node` with another `node`
|
||||
*
|
||||
* @param {Change} change
|
||||
* @param {String} key
|
||||
* @param {Array} path
|
||||
* @param {Object|Node} node
|
||||
* @param {Object} options
|
||||
* @property {Boolean} normalize
|
||||
*/
|
||||
|
||||
Changes.replaceNodeByKey = (change, key, newNode, options = {}) => {
|
||||
Changes.replaceNodeByPath = (change, path, newNode, options) => {
|
||||
newNode = Node.create(newNode)
|
||||
const normalize = change.getFlag('normalize', options)
|
||||
const { value } = change
|
||||
const { document } = value
|
||||
const node = document.getNode(key)
|
||||
const parent = document.getParent(key)
|
||||
const index = parent.nodes.indexOf(node)
|
||||
change.removeNodeByKey(key, { normalize: false })
|
||||
change.insertNodeByKey(parent.key, index, newNode, { normalize: false })
|
||||
|
||||
if (normalize) {
|
||||
change.normalizeNodeByKey(parent.key)
|
||||
}
|
||||
const index = path.last()
|
||||
const parentPath = PathUtils.lift(path)
|
||||
change.removeNodeByPath(path, { normalize: false })
|
||||
change.insertNodeByPath(parentPath, index, newNode, { normalize: false })
|
||||
change.normalizeParentByPath(path, options)
|
||||
}
|
||||
|
||||
/**
|
||||
* Set `properties` on mark on text at `offset` and `length` in node by `key`.
|
||||
* Set `properties` on mark on text at `offset` and `length` in node by `path`.
|
||||
*
|
||||
* @param {Change} change
|
||||
* @param {String} key
|
||||
* @param {Array} path
|
||||
* @param {Number} offset
|
||||
* @param {Number} length
|
||||
* @param {Mark} mark
|
||||
* @param {Object} options
|
||||
* @property {Boolean} normalize
|
||||
*/
|
||||
|
||||
Changes.setMarkByKey = (
|
||||
Changes.setMarkByPath = (
|
||||
change,
|
||||
key,
|
||||
path,
|
||||
offset,
|
||||
length,
|
||||
mark,
|
||||
properties,
|
||||
options = {}
|
||||
options
|
||||
) => {
|
||||
mark = Mark.create(mark)
|
||||
properties = Mark.createProperties(properties)
|
||||
const normalize = change.getFlag('normalize', options)
|
||||
const { value } = change
|
||||
const { document } = value
|
||||
const path = document.getPath(key)
|
||||
|
||||
change.applyOperation({
|
||||
type: 'set_mark',
|
||||
@@ -539,29 +474,23 @@ Changes.setMarkByKey = (
|
||||
properties,
|
||||
})
|
||||
|
||||
if (normalize) {
|
||||
const parent = document.getParent(key)
|
||||
change.normalizeNodeByKey(parent.key)
|
||||
}
|
||||
change.normalizeParentByPath(path, options)
|
||||
}
|
||||
|
||||
/**
|
||||
* Set `properties` on a node by `key`.
|
||||
* Set `properties` on a node by `path`.
|
||||
*
|
||||
* @param {Change} change
|
||||
* @param {String} key
|
||||
* @param {Array} path
|
||||
* @param {Object|String} properties
|
||||
* @param {Object} options
|
||||
* @property {Boolean} normalize
|
||||
*/
|
||||
|
||||
Changes.setNodeByKey = (change, key, properties, options = {}) => {
|
||||
Changes.setNodeByPath = (change, path, properties, options) => {
|
||||
properties = Node.createProperties(properties)
|
||||
const normalize = change.getFlag('normalize', options)
|
||||
const { value } = change
|
||||
const { document } = value
|
||||
const path = document.getPath(key)
|
||||
const node = document.getNode(key)
|
||||
const node = document.assertNode(path)
|
||||
|
||||
change.applyOperation({
|
||||
type: 'set_node',
|
||||
@@ -571,27 +500,23 @@ Changes.setNodeByKey = (change, key, properties, options = {}) => {
|
||||
properties,
|
||||
})
|
||||
|
||||
if (normalize) {
|
||||
change.normalizeNodeByKey(node.key)
|
||||
}
|
||||
change.normalizeNodeByPath(path, options)
|
||||
}
|
||||
|
||||
/**
|
||||
* Split a node by `key` at `position`.
|
||||
* Split a node by `path` at `position`.
|
||||
*
|
||||
* @param {Change} change
|
||||
* @param {String} key
|
||||
* @param {Array} path
|
||||
* @param {Number} position
|
||||
* @param {Object} options
|
||||
* @property {Boolean} normalize
|
||||
*/
|
||||
|
||||
Changes.splitNodeByKey = (change, key, position, options = {}) => {
|
||||
const { normalize = true, target = null } = options
|
||||
Changes.splitNodeByPath = (change, path, position, options = {}) => {
|
||||
const { target = null } = options
|
||||
const { value } = change
|
||||
const { document } = value
|
||||
const path = document.getPath(key)
|
||||
const node = document.getDescendantAtPath(path)
|
||||
const node = document.getDescendant(path)
|
||||
|
||||
change.applyOperation({
|
||||
type: 'split_node',
|
||||
@@ -605,78 +530,71 @@ Changes.splitNodeByKey = (change, key, position, options = {}) => {
|
||||
target,
|
||||
})
|
||||
|
||||
if (normalize) {
|
||||
const parent = document.getParent(key)
|
||||
change.normalizeNodeByKey(parent.key)
|
||||
}
|
||||
change.normalizeParentByPath(path, options)
|
||||
}
|
||||
|
||||
/**
|
||||
* Split a node deeply down the tree by `key`, `textKey` and `textOffset`.
|
||||
* Split a node deeply down the tree by `path`, `textPath` and `textOffset`.
|
||||
*
|
||||
* @param {Change} change
|
||||
* @param {String} key
|
||||
* @param {Number} position
|
||||
* @param {Array} path
|
||||
* @param {Array} textPath
|
||||
* @param {Number} textOffset
|
||||
* @param {Object} options
|
||||
* @property {Boolean} normalize
|
||||
*/
|
||||
|
||||
Changes.splitDescendantsByKey = (
|
||||
Changes.splitDescendantsByPath = (
|
||||
change,
|
||||
key,
|
||||
textKey,
|
||||
path,
|
||||
textPath,
|
||||
textOffset,
|
||||
options = {}
|
||||
options
|
||||
) => {
|
||||
if (key == textKey) {
|
||||
change.splitNodeByKey(textKey, textOffset, options)
|
||||
if (path.equals(textPath)) {
|
||||
change.splitNodeByPath(textPath, textOffset, options)
|
||||
return
|
||||
}
|
||||
|
||||
const normalize = change.getFlag('normalize', options)
|
||||
const { value } = change
|
||||
const { document } = value
|
||||
|
||||
const text = document.getNode(textKey)
|
||||
const ancestors = document.getAncestors(textKey)
|
||||
const node = document.assertNode(path)
|
||||
const text = document.assertNode(textPath)
|
||||
const ancestors = document.getAncestors(textPath)
|
||||
const nodes = ancestors
|
||||
.skipUntil(a => a.key == key)
|
||||
.skipUntil(a => a.key == node.key)
|
||||
.reverse()
|
||||
.unshift(text)
|
||||
|
||||
let previous
|
||||
let index
|
||||
|
||||
nodes.forEach(node => {
|
||||
nodes.forEach(n => {
|
||||
const prevIndex = index == null ? null : index
|
||||
index = previous ? node.nodes.indexOf(previous) + 1 : textOffset
|
||||
previous = node
|
||||
index = previous ? n.nodes.indexOf(previous) + 1 : textOffset
|
||||
previous = n
|
||||
|
||||
change.splitNodeByKey(node.key, index, {
|
||||
change.splitNodeByKey(n.key, index, {
|
||||
normalize: false,
|
||||
target: prevIndex,
|
||||
})
|
||||
})
|
||||
|
||||
if (normalize) {
|
||||
const parent = document.getParent(key)
|
||||
change.normalizeNodeByKey(parent.key)
|
||||
}
|
||||
change.normalizeParentByPath(path, options)
|
||||
}
|
||||
|
||||
/**
|
||||
* Unwrap content from an inline parent with `properties`.
|
||||
*
|
||||
* @param {Change} change
|
||||
* @param {String} key
|
||||
* @param {Array} path
|
||||
* @param {Object|String} properties
|
||||
* @param {Object} options
|
||||
* @property {Boolean} normalize
|
||||
*/
|
||||
|
||||
Changes.unwrapInlineByKey = (change, key, properties, options) => {
|
||||
Changes.unwrapInlineByPath = (change, path, properties, options) => {
|
||||
const { value } = change
|
||||
const { document, selection } = value
|
||||
const node = document.assertDescendant(key)
|
||||
const node = document.assertNode(path)
|
||||
const first = node.getFirstText()
|
||||
const last = node.getLastText()
|
||||
const range = selection.moveToRangeOf(first, last)
|
||||
@@ -687,16 +605,15 @@ Changes.unwrapInlineByKey = (change, key, properties, options) => {
|
||||
* Unwrap content from a block parent with `properties`.
|
||||
*
|
||||
* @param {Change} change
|
||||
* @param {String} key
|
||||
* @param {Array} path
|
||||
* @param {Object|String} properties
|
||||
* @param {Object} options
|
||||
* @property {Boolean} normalize
|
||||
*/
|
||||
|
||||
Changes.unwrapBlockByKey = (change, key, properties, options) => {
|
||||
Changes.unwrapBlockByPath = (change, path, properties, options) => {
|
||||
const { value } = change
|
||||
const { document, selection } = value
|
||||
const node = document.assertDescendant(key)
|
||||
const node = document.assertNode(path)
|
||||
const first = node.getFirstText()
|
||||
const last = node.getLastText()
|
||||
const range = selection.moveToRangeOf(first, last)
|
||||
@@ -711,49 +628,44 @@ Changes.unwrapBlockByKey = (change, key, properties, options) => {
|
||||
* simply replaced by the node itself. Cannot unwrap a root node.
|
||||
*
|
||||
* @param {Change} change
|
||||
* @param {String} key
|
||||
* @param {Array} path
|
||||
* @param {Object} options
|
||||
* @property {Boolean} normalize
|
||||
*/
|
||||
|
||||
Changes.unwrapNodeByKey = (change, key, options = {}) => {
|
||||
const normalize = change.getFlag('normalize', options)
|
||||
Changes.unwrapNodeByPath = (change, path, options) => {
|
||||
const { value } = change
|
||||
const { document } = value
|
||||
const parent = document.getParent(key)
|
||||
const node = parent.getChild(key)
|
||||
document.assertNode(path)
|
||||
|
||||
const index = parent.nodes.indexOf(node)
|
||||
const parentPath = PathUtils.lift(path)
|
||||
const parent = document.assertNode(parentPath)
|
||||
const index = path.last()
|
||||
const parentIndex = parentPath.last()
|
||||
const grandPath = PathUtils.lift(parentPath)
|
||||
const isFirst = index === 0
|
||||
const isLast = index === parent.nodes.size - 1
|
||||
|
||||
const parentParent = document.getParent(parent.key)
|
||||
const parentIndex = parentParent.nodes.indexOf(parent)
|
||||
|
||||
if (parent.nodes.size === 1) {
|
||||
change.moveNodeByKey(key, parentParent.key, parentIndex, {
|
||||
change.moveNodeByPath(path, grandPath, parentIndex + 1, {
|
||||
normalize: false,
|
||||
})
|
||||
|
||||
change.removeNodeByKey(parent.key, options)
|
||||
change.removeNodeByPath(parentPath, options)
|
||||
} else if (isFirst) {
|
||||
// Just move the node before its parent.
|
||||
change.moveNodeByKey(key, parentParent.key, parentIndex, options)
|
||||
change.moveNodeByPath(path, grandPath, parentIndex, options)
|
||||
} else if (isLast) {
|
||||
// Just move the node after its parent.
|
||||
change.moveNodeByKey(key, parentParent.key, parentIndex + 1, options)
|
||||
change.moveNodeByPath(path, grandPath, parentIndex + 1, options)
|
||||
} else {
|
||||
// Split the parent.
|
||||
change.splitNodeByKey(parent.key, index, { normalize: false })
|
||||
change.splitNodeByPath(parentPath, index, { normalize: false })
|
||||
|
||||
// Extract the node in between the splitted parent.
|
||||
change.moveNodeByKey(key, parentParent.key, parentIndex + 1, {
|
||||
let updatedPath = PathUtils.increment(path, 1, parentPath.size - 1)
|
||||
updatedPath = updatedPath.set(updatedPath.size - 1, 0)
|
||||
|
||||
change.moveNodeByPath(updatedPath, grandPath, parentIndex + 1, {
|
||||
normalize: false,
|
||||
})
|
||||
|
||||
if (normalize) {
|
||||
change.normalizeNodeByKey(parentParent.key)
|
||||
}
|
||||
change.normalizeNodeByPath(grandPath, options)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -761,72 +673,118 @@ Changes.unwrapNodeByKey = (change, key, options = {}) => {
|
||||
* Wrap a node in a block with `properties`.
|
||||
*
|
||||
* @param {Change} change
|
||||
* @param {String} key The node to wrap
|
||||
* @param {Block|Object|String} block The wrapping block (its children are discarded)
|
||||
* @param {Array} path
|
||||
* @param {Block|Object|String} block
|
||||
* @param {Object} options
|
||||
* @property {Boolean} normalize
|
||||
*/
|
||||
|
||||
Changes.wrapBlockByKey = (change, key, block, options) => {
|
||||
Changes.wrapBlockByPath = (change, path, block, options) => {
|
||||
block = Block.create(block)
|
||||
block = block.set('nodes', block.nodes.clear())
|
||||
|
||||
const { document } = change.value
|
||||
const node = document.assertDescendant(key)
|
||||
const parent = document.getParent(node.key)
|
||||
const index = parent.nodes.indexOf(node)
|
||||
|
||||
change.insertNodeByKey(parent.key, index, block, { normalize: false })
|
||||
change.moveNodeByKey(node.key, block.key, 0, options)
|
||||
const parentPath = PathUtils.lift(path)
|
||||
const index = path.last()
|
||||
const newPath = PathUtils.increment(path)
|
||||
change.insertNodeByPath(parentPath, index, block, { normalize: false })
|
||||
change.moveNodeByPath(newPath, path, 0, options)
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap a node in an inline with `properties`.
|
||||
*
|
||||
* @param {Change} change
|
||||
* @param {String} key The node to wrap
|
||||
* @param {Block|Object|String} inline The wrapping inline (its children are discarded)
|
||||
* @param {Array} path
|
||||
* @param {Block|Object|String} inline
|
||||
* @param {Object} options
|
||||
* @property {Boolean} normalize
|
||||
*/
|
||||
|
||||
Changes.wrapInlineByKey = (change, key, inline, options) => {
|
||||
Changes.wrapInlineByPath = (change, path, inline, options) => {
|
||||
inline = Inline.create(inline)
|
||||
inline = inline.set('nodes', inline.nodes.clear())
|
||||
|
||||
const { document } = change.value
|
||||
const node = document.assertDescendant(key)
|
||||
const parent = document.getParent(node.key)
|
||||
const index = parent.nodes.indexOf(node)
|
||||
|
||||
change.insertNodeByKey(parent.key, index, inline, { normalize: false })
|
||||
change.moveNodeByKey(node.key, inline.key, 0, options)
|
||||
const parentPath = PathUtils.lift(path)
|
||||
const index = path.last()
|
||||
const newPath = PathUtils.increment(path)
|
||||
change.insertNodeByPath(parentPath, index, inline, { normalize: false })
|
||||
change.moveNodeByPath(newPath, path, 0, options)
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap a node by `key` with `parent`.
|
||||
* Wrap a node by `path` with `node`.
|
||||
*
|
||||
* @param {Change} change
|
||||
* @param {String} key
|
||||
* @param {Node|Object} parent
|
||||
* @param {Array} path
|
||||
* @param {Node|Object} node
|
||||
* @param {Object} options
|
||||
*/
|
||||
|
||||
Changes.wrapNodeByKey = (change, key, parent) => {
|
||||
parent = Node.create(parent)
|
||||
parent = parent.set('nodes', parent.nodes.clear())
|
||||
Changes.wrapNodeByPath = (change, path, node) => {
|
||||
node = Node.create(node)
|
||||
|
||||
if (parent.object == 'block') {
|
||||
change.wrapBlockByKey(key, parent)
|
||||
if (node.object == 'block') {
|
||||
change.wrapBlockByPath(path, node)
|
||||
return
|
||||
}
|
||||
|
||||
if (parent.object == 'inline') {
|
||||
change.wrapInlineByKey(key, parent)
|
||||
if (node.object == 'inline') {
|
||||
change.wrapInlineByPath(path, node)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mix in `*ByKey` variants.
|
||||
*/
|
||||
|
||||
const CHANGES = [
|
||||
'addMark',
|
||||
'insertFragment',
|
||||
'insertNode',
|
||||
'insertText',
|
||||
'mergeNode',
|
||||
'removeMark',
|
||||
'removeAllMarks',
|
||||
'removeNode',
|
||||
'setText',
|
||||
'replaceText',
|
||||
'removeText',
|
||||
'replaceNode',
|
||||
'setMark',
|
||||
'setNode',
|
||||
'splitNode',
|
||||
'unwrapInline',
|
||||
'unwrapBlock',
|
||||
'unwrapNode',
|
||||
'wrapBlock',
|
||||
'wrapInline',
|
||||
'wrapNode',
|
||||
]
|
||||
|
||||
for (const method of CHANGES) {
|
||||
Changes[`${method}ByKey`] = (change, key, ...args) => {
|
||||
const { value } = change
|
||||
const { document } = value
|
||||
const path = document.assertPath(key)
|
||||
change[`${method}ByPath`](path, ...args)
|
||||
}
|
||||
}
|
||||
|
||||
// Moving nodes takes two keys, so it's slightly different.
|
||||
Changes.moveNodeByKey = (change, key, newKey, ...args) => {
|
||||
const { value } = change
|
||||
const { document } = value
|
||||
const path = document.assertPath(key)
|
||||
const newPath = document.assertPath(newKey)
|
||||
change.moveNodeByPath(path, newPath, ...args)
|
||||
}
|
||||
|
||||
// Splitting descendants takes two keys, so it's slightly different.
|
||||
Changes.splitDescendantsByKey = (change, key, textKey, ...args) => {
|
||||
const { value } = change
|
||||
const { document } = value
|
||||
const path = document.assertPath(key)
|
||||
const textPath = document.assertPath(textKey)
|
||||
change.splitDescendantsByPath(path, textPath, ...args)
|
||||
}
|
||||
|
||||
/**
|
||||
* Export.
|
||||
*
|
@@ -1,6 +1,6 @@
|
||||
import AtCurrentRange from './at-current-range'
|
||||
import AtRange from './at-range'
|
||||
import ByKey from './by-key'
|
||||
import ByPath from './by-path'
|
||||
import OnHistory from './on-history'
|
||||
import OnSelection from './on-selection'
|
||||
import OnValue from './on-value'
|
||||
@@ -15,7 +15,7 @@ import WithSchema from './with-schema'
|
||||
export default {
|
||||
...AtCurrentRange,
|
||||
...AtRange,
|
||||
...ByKey,
|
||||
...ByPath,
|
||||
...OnHistory,
|
||||
...OnSelection,
|
||||
...OnValue,
|
||||
|
@@ -1,3 +1,5 @@
|
||||
import PathUtils from '../utils/path-utils'
|
||||
|
||||
/**
|
||||
* Changes.
|
||||
*
|
||||
@@ -12,8 +14,8 @@ const Changes = {}
|
||||
* @param {Change} change
|
||||
*/
|
||||
|
||||
Changes.normalize = change => {
|
||||
change.normalizeDocument()
|
||||
Changes.normalize = (change, options) => {
|
||||
change.normalizeDocument(options)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -22,10 +24,10 @@ Changes.normalize = change => {
|
||||
* @param {Change} change
|
||||
*/
|
||||
|
||||
Changes.normalizeDocument = change => {
|
||||
Changes.normalizeDocument = (change, options) => {
|
||||
const { value } = change
|
||||
const { document } = value
|
||||
change.normalizeNodeByKey(document.key)
|
||||
change.normalizeNodeByKey(document.key, options)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -35,7 +37,10 @@ Changes.normalizeDocument = change => {
|
||||
* @param {Node|String} key
|
||||
*/
|
||||
|
||||
Changes.normalizeNodeByKey = (change, key) => {
|
||||
Changes.normalizeNodeByKey = (change, key, options = {}) => {
|
||||
const normalize = change.getFlag('normalize', options)
|
||||
if (!normalize) return
|
||||
|
||||
const { value } = change
|
||||
let { document, schema } = value
|
||||
const node = document.assertNode(key)
|
||||
@@ -53,6 +58,46 @@ Changes.normalizeNodeByKey = (change, key) => {
|
||||
})
|
||||
}
|
||||
|
||||
Changes.normalizeParentByKey = (change, key, options) => {
|
||||
const { value } = change
|
||||
const { document } = value
|
||||
const parent = document.getParent(key)
|
||||
change.normalizeNodeByKey(parent.key, options)
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize a `node` and its children with the value's schema.
|
||||
*
|
||||
* @param {Change} change
|
||||
* @param {Array} path
|
||||
*/
|
||||
|
||||
Changes.normalizeNodeByPath = (change, path, options = {}) => {
|
||||
const normalize = change.getFlag('normalize', options)
|
||||
if (!normalize) return
|
||||
|
||||
const { value } = change
|
||||
let { document, schema } = value
|
||||
const node = document.assertNode(path)
|
||||
|
||||
normalizeNodeAndChildren(change, node, schema)
|
||||
|
||||
document = change.value.document
|
||||
const ancestors = document.getAncestors(path)
|
||||
if (!ancestors) return
|
||||
|
||||
ancestors.forEach(ancestor => {
|
||||
if (change.value.document.getDescendant(ancestor.key)) {
|
||||
normalizeNode(change, ancestor, schema)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Changes.normalizeParentByPath = (change, path, options) => {
|
||||
const p = PathUtils.lift(path)
|
||||
change.normalizeNodeByPath(p, options)
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize a `node` and its children with a `schema`.
|
||||
*
|
||||
|
@@ -6,15 +6,18 @@ import Data from './models/data'
|
||||
import Document from './models/document'
|
||||
import History from './models/history'
|
||||
import Inline from './models/inline'
|
||||
import KeyUtils from './utils/key-utils'
|
||||
import Leaf from './models/leaf'
|
||||
import Mark from './models/mark'
|
||||
import Node from './models/node'
|
||||
import Operation from './models/operation'
|
||||
import Operations from './operations'
|
||||
import PathUtils from './utils/path-utils'
|
||||
import Range from './models/range'
|
||||
import Schema from './models/schema'
|
||||
import Stack from './models/stack'
|
||||
import Text from './models/text'
|
||||
import TextUtils from './utils/text-utils'
|
||||
import Value from './models/value'
|
||||
import { resetKeyGenerator, setKeyGenerator } from './utils/generate-key'
|
||||
import { resetMemoization, useMemoization } from './utils/memoize'
|
||||
@@ -34,20 +37,23 @@ export {
|
||||
Document,
|
||||
History,
|
||||
Inline,
|
||||
KeyUtils,
|
||||
Leaf,
|
||||
Mark,
|
||||
Node,
|
||||
Operation,
|
||||
Operations,
|
||||
PathUtils,
|
||||
Range,
|
||||
resetKeyGenerator,
|
||||
resetMemoization,
|
||||
Schema,
|
||||
setKeyGenerator,
|
||||
Stack,
|
||||
Text,
|
||||
Value,
|
||||
resetKeyGenerator,
|
||||
setKeyGenerator,
|
||||
resetMemoization,
|
||||
TextUtils,
|
||||
useMemoization,
|
||||
Value,
|
||||
}
|
||||
|
||||
export default {
|
||||
@@ -58,18 +64,21 @@ export default {
|
||||
Document,
|
||||
History,
|
||||
Inline,
|
||||
KeyUtils,
|
||||
Leaf,
|
||||
Mark,
|
||||
Node,
|
||||
Operation,
|
||||
Operations,
|
||||
PathUtils,
|
||||
Range,
|
||||
resetKeyGenerator,
|
||||
resetMemoization,
|
||||
Schema,
|
||||
setKeyGenerator,
|
||||
Stack,
|
||||
Text,
|
||||
Value,
|
||||
resetKeyGenerator,
|
||||
setKeyGenerator,
|
||||
resetMemoization,
|
||||
TextUtils,
|
||||
useMemoization,
|
||||
Value,
|
||||
}
|
||||
|
@@ -7,7 +7,7 @@ import logger from 'slate-dev-logger'
|
||||
import { List, Map, Record } from 'immutable'
|
||||
|
||||
import MODEL_TYPES, { isType } from '../constants/model-types'
|
||||
import generateKey from '../utils/generate-key'
|
||||
import KeyUtils from '../utils/key-utils'
|
||||
|
||||
/**
|
||||
* Default properties.
|
||||
@@ -88,7 +88,7 @@ class Block extends Record(DEFAULTS) {
|
||||
const {
|
||||
data = {},
|
||||
isVoid = false,
|
||||
key = generateKey(),
|
||||
key = KeyUtils.create(),
|
||||
nodes = [],
|
||||
type,
|
||||
} = object
|
||||
|
@@ -144,24 +144,18 @@ class Change {
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies a series of change mutations and defers normalization until the end.
|
||||
* Applies a series of change mutations, deferring normalization to the end.
|
||||
*
|
||||
* @param {Function} customChange - function that accepts a change object and executes change operations
|
||||
* @param {Function} fn
|
||||
* @return {Change}
|
||||
*/
|
||||
|
||||
withoutNormalization(customChange) {
|
||||
withoutNormalization(fn) {
|
||||
const original = this.flags.normalize
|
||||
this.setOperationFlag('normalize', false)
|
||||
|
||||
try {
|
||||
customChange(this)
|
||||
// if the change function worked then run normalization
|
||||
this.normalizeDocument()
|
||||
} finally {
|
||||
// restore the flag to whatever it was
|
||||
fn(this)
|
||||
this.setOperationFlag('normalize', original)
|
||||
}
|
||||
this.normalizeDocument()
|
||||
return this
|
||||
}
|
||||
|
||||
|
@@ -7,7 +7,7 @@ import logger from 'slate-dev-logger'
|
||||
import { List, Map, Record } from 'immutable'
|
||||
|
||||
import MODEL_TYPES, { isType } from '../constants/model-types'
|
||||
import generateKey from '../utils/generate-key'
|
||||
import KeyUtils from '../utils/key-utils'
|
||||
|
||||
/**
|
||||
* Default properties.
|
||||
@@ -65,7 +65,7 @@ class Document extends Record(DEFAULTS) {
|
||||
return object
|
||||
}
|
||||
|
||||
const { data = {}, key = generateKey(), nodes = [] } = object
|
||||
const { data = {}, key = KeyUtils.create(), nodes = [] } = object
|
||||
|
||||
const document = new Document({
|
||||
key,
|
||||
|
@@ -7,7 +7,7 @@ import logger from 'slate-dev-logger'
|
||||
import { List, Map, Record } from 'immutable'
|
||||
|
||||
import MODEL_TYPES, { isType } from '../constants/model-types'
|
||||
import generateKey from '../utils/generate-key'
|
||||
import KeyUtils from '../utils/key-utils'
|
||||
|
||||
/**
|
||||
* Default properties.
|
||||
@@ -88,7 +88,7 @@ class Inline extends Record(DEFAULTS) {
|
||||
const {
|
||||
data = {},
|
||||
isVoid = false,
|
||||
key = generateKey(),
|
||||
key = KeyUtils.create(),
|
||||
nodes = [],
|
||||
type,
|
||||
} = object
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -6,6 +6,7 @@ import MODEL_TYPES from '../constants/model-types'
|
||||
import OPERATION_ATTRIBUTES from '../constants/operation-attributes'
|
||||
import Mark from './mark'
|
||||
import Node from './node'
|
||||
import PathUtils from '../utils/path-utils'
|
||||
import Range from './range'
|
||||
import Value from './value'
|
||||
|
||||
@@ -90,7 +91,7 @@ class Operation extends Record(DEFAULTS) {
|
||||
return object
|
||||
}
|
||||
|
||||
const { type, value } = object
|
||||
const { type } = object
|
||||
const ATTRIBUTES = OPERATION_ATTRIBUTES[type]
|
||||
const attrs = { type }
|
||||
|
||||
@@ -116,58 +117,51 @@ class Operation extends Record(DEFAULTS) {
|
||||
)
|
||||
}
|
||||
|
||||
if (key == 'mark') {
|
||||
if (key === 'path' || key === 'newPath') {
|
||||
v = PathUtils.create(v)
|
||||
}
|
||||
|
||||
if (key === 'mark') {
|
||||
v = Mark.create(v)
|
||||
}
|
||||
|
||||
if (key == 'marks' && v != null) {
|
||||
if (key === 'marks' && v != null) {
|
||||
v = Mark.createSet(v)
|
||||
}
|
||||
|
||||
if (key == 'node') {
|
||||
if (key === 'node') {
|
||||
v = Node.create(v)
|
||||
}
|
||||
|
||||
if (key == 'selection') {
|
||||
if (key === 'selection') {
|
||||
v = Range.create(v)
|
||||
}
|
||||
|
||||
if (key == 'value') {
|
||||
if (key === 'value') {
|
||||
v = Value.create(v)
|
||||
}
|
||||
|
||||
if (key == 'properties' && type == 'merge_node') {
|
||||
if (key === 'properties' && type === 'merge_node') {
|
||||
v = Node.createProperties(v)
|
||||
}
|
||||
|
||||
if (key == 'properties' && type == 'set_mark') {
|
||||
if (key === 'properties' && type === 'set_mark') {
|
||||
v = Mark.createProperties(v)
|
||||
}
|
||||
|
||||
if (key == 'properties' && type == 'set_node') {
|
||||
if (key === 'properties' && type === 'set_node') {
|
||||
v = Node.createProperties(v)
|
||||
}
|
||||
|
||||
if (key == 'properties' && type == 'set_selection') {
|
||||
const { anchorKey, focusKey, ...rest } = v
|
||||
v = Range.createProperties(rest)
|
||||
|
||||
if (anchorKey !== undefined) {
|
||||
v.anchorPath =
|
||||
anchorKey === null ? null : value.document.getPath(anchorKey)
|
||||
if (key === 'properties' && type === 'set_selection') {
|
||||
v = Range.createProperties(v)
|
||||
}
|
||||
|
||||
if (focusKey !== undefined) {
|
||||
v.focusPath =
|
||||
focusKey === null ? null : value.document.getPath(focusKey)
|
||||
}
|
||||
}
|
||||
|
||||
if (key == 'properties' && type == 'set_value') {
|
||||
if (key === 'properties' && type === 'set_value') {
|
||||
v = Value.createProperties(v)
|
||||
}
|
||||
|
||||
if (key == 'properties' && type == 'split_node') {
|
||||
if (key === 'properties' && type === 'split_node') {
|
||||
v = Node.createProperties(v)
|
||||
}
|
||||
|
||||
@@ -275,13 +269,14 @@ class Operation extends Record(DEFAULTS) {
|
||||
if (key == 'properties' && type == 'set_selection') {
|
||||
const v = {}
|
||||
if ('anchorOffset' in value) v.anchorOffset = value.anchorOffset
|
||||
if ('anchorPath' in value) v.anchorPath = value.anchorPath
|
||||
if ('anchorPath' in value)
|
||||
v.anchorPath = value.anchorPath && value.anchorPath.toJSON()
|
||||
if ('focusOffset' in value) v.focusOffset = value.focusOffset
|
||||
if ('focusPath' in value) v.focusPath = value.focusPath
|
||||
if ('focusPath' in value)
|
||||
v.focusPath = value.focusPath && value.focusPath.toJSON()
|
||||
if ('isBackward' in value) v.isBackward = value.isBackward
|
||||
if ('isFocused' in value) v.isFocused = value.isFocused
|
||||
if ('marks' in value)
|
||||
v.marks = value.marks == null ? null : value.marks.toJSON()
|
||||
if ('marks' in value) v.marks = value.marks && value.marks.toJSON()
|
||||
value = v
|
||||
}
|
||||
|
||||
|
@@ -2,6 +2,7 @@ import isPlainObject from 'is-plain-object'
|
||||
import logger from 'slate-dev-logger'
|
||||
import { List, Record, Set } from 'immutable'
|
||||
|
||||
import PathUtils from '../utils/path-utils'
|
||||
import MODEL_TYPES from '../constants/model-types'
|
||||
import Mark from './mark'
|
||||
|
||||
@@ -14,12 +15,14 @@ import Mark from './mark'
|
||||
const DEFAULTS = {
|
||||
anchorKey: null,
|
||||
anchorOffset: 0,
|
||||
anchorPath: null,
|
||||
focusKey: null,
|
||||
focusOffset: 0,
|
||||
focusPath: null,
|
||||
isAtomic: false,
|
||||
isBackward: null,
|
||||
isFocused: false,
|
||||
marks: null,
|
||||
isAtomic: false,
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -75,38 +78,49 @@ class Range extends Record(DEFAULTS) {
|
||||
* @return {Object}
|
||||
*/
|
||||
|
||||
static createProperties(attrs = {}) {
|
||||
if (Range.isRange(attrs)) {
|
||||
static createProperties(a = {}) {
|
||||
if (Range.isRange(a)) {
|
||||
return {
|
||||
anchorKey: attrs.anchorKey,
|
||||
anchorOffset: attrs.anchorOffset,
|
||||
focusKey: attrs.focusKey,
|
||||
focusOffset: attrs.focusOffset,
|
||||
isBackward: attrs.isBackward,
|
||||
isFocused: attrs.isFocused,
|
||||
marks: attrs.marks,
|
||||
isAtomic: attrs.isAtomic,
|
||||
anchorKey: a.anchorKey,
|
||||
anchorOffset: a.anchorOffset,
|
||||
anchorPath: a.anchorPath,
|
||||
focusKey: a.focusKey,
|
||||
focusOffset: a.focusOffset,
|
||||
focusPath: a.focusPath,
|
||||
isAtomic: a.isAtomic,
|
||||
isBackward: a.isBackward,
|
||||
isFocused: a.isFocused,
|
||||
marks: a.marks,
|
||||
}
|
||||
}
|
||||
|
||||
if (isPlainObject(attrs)) {
|
||||
const props = {}
|
||||
if ('anchorKey' in attrs) props.anchorKey = attrs.anchorKey
|
||||
if ('anchorOffset' in attrs) props.anchorOffset = attrs.anchorOffset
|
||||
if ('anchorPath' in attrs) props.anchorPath = attrs.anchorPath
|
||||
if ('focusKey' in attrs) props.focusKey = attrs.focusKey
|
||||
if ('focusOffset' in attrs) props.focusOffset = attrs.focusOffset
|
||||
if ('focusPath' in attrs) props.focusPath = attrs.focusPath
|
||||
if ('isBackward' in attrs) props.isBackward = attrs.isBackward
|
||||
if ('isFocused' in attrs) props.isFocused = attrs.isFocused
|
||||
if ('marks' in attrs)
|
||||
props.marks = attrs.marks == null ? null : Mark.createSet(attrs.marks)
|
||||
if ('isAtomic' in attrs) props.isAtomic = attrs.isAtomic
|
||||
return props
|
||||
if (isPlainObject(a)) {
|
||||
const p = {}
|
||||
if ('anchorKey' in a) p.anchorKey = a.anchorKey
|
||||
if ('anchorOffset' in a) p.anchorOffset = a.anchorOffset
|
||||
if ('anchorPath' in a) p.anchorPath = PathUtils.create(a.anchorPath)
|
||||
if ('focusKey' in a) p.focusKey = a.focusKey
|
||||
if ('focusOffset' in a) p.focusOffset = a.focusOffset
|
||||
if ('focusPath' in a) p.focusPath = PathUtils.create(a.focusPath)
|
||||
if ('isAtomic' in a) p.isAtomic = a.isAtomic
|
||||
if ('isBackward' in a) p.isBackward = a.isBackward
|
||||
if ('isFocused' in a) p.isFocused = a.isFocused
|
||||
if ('marks' in a)
|
||||
p.marks = a.marks == null ? null : Mark.createSet(a.marks)
|
||||
|
||||
// If only a path is set, or only a key is set, ensure that the other is
|
||||
// set to null so that it can be normalized back to the right value.
|
||||
// Otherwise we won't realize that the path and key don't match anymore.
|
||||
if ('anchorPath' in a && !('anchorKey' in a)) p.anchorKey = null
|
||||
if ('anchorKey' in a && !('anchorPath' in a)) p.anchorPath = null
|
||||
if ('focusPath' in a && !('focusKey' in a)) p.focusKey = null
|
||||
if ('focusKey' in a && !('focusPath' in a)) p.focusPath = null
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`\`Range.createProperties\` only accepts objects or ranges, but you passed it: ${attrs}`
|
||||
`\`Range.createProperties\` only accepts objects or ranges, but you passed it: ${a}`
|
||||
)
|
||||
}
|
||||
|
||||
@@ -121,23 +135,27 @@ class Range extends Record(DEFAULTS) {
|
||||
const {
|
||||
anchorKey = null,
|
||||
anchorOffset = 0,
|
||||
anchorPath = null,
|
||||
focusKey = null,
|
||||
focusOffset = 0,
|
||||
focusPath = null,
|
||||
isAtomic = false,
|
||||
isBackward = null,
|
||||
isFocused = false,
|
||||
marks = null,
|
||||
isAtomic = false,
|
||||
} = object
|
||||
|
||||
const range = new Range({
|
||||
anchorKey,
|
||||
anchorOffset,
|
||||
anchorPath: PathUtils.create(anchorPath),
|
||||
focusKey,
|
||||
focusOffset,
|
||||
focusPath: PathUtils.create(focusPath),
|
||||
isAtomic,
|
||||
isBackward,
|
||||
isFocused,
|
||||
marks: marks == null ? null : new Set(marks.map(Mark.fromJSON)),
|
||||
isAtomic,
|
||||
})
|
||||
|
||||
return range
|
||||
@@ -227,7 +245,10 @@ class Range extends Record(DEFAULTS) {
|
||||
*/
|
||||
|
||||
get isSet() {
|
||||
return this.anchorKey != null && this.focusKey != null
|
||||
return (
|
||||
(this.anchorKey != null && this.focusKey != null) ||
|
||||
(this.anchorPath != null && this.focusPath != null)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -260,6 +281,16 @@ class Range extends Record(DEFAULTS) {
|
||||
return this.isBackward ? this.focusOffset : this.anchorOffset
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the start path.
|
||||
*
|
||||
* @return {String}
|
||||
*/
|
||||
|
||||
get startPath() {
|
||||
return this.isBackward ? this.focusPath : this.anchorPath
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the end key.
|
||||
*
|
||||
@@ -280,6 +311,16 @@ class Range extends Record(DEFAULTS) {
|
||||
return this.isBackward ? this.anchorOffset : this.focusOffset
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the end path.
|
||||
*
|
||||
* @return {String}
|
||||
*/
|
||||
|
||||
get endPath() {
|
||||
return this.isBackward ? this.anchorPath : this.focusPath
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether anchor point of the range is at the start of a `node`.
|
||||
*
|
||||
@@ -290,7 +331,7 @@ class Range extends Record(DEFAULTS) {
|
||||
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)
|
||||
const first = getFirstText(node)
|
||||
return this.anchorKey == first.key
|
||||
}
|
||||
|
||||
@@ -302,7 +343,7 @@ class Range extends Record(DEFAULTS) {
|
||||
*/
|
||||
|
||||
hasAnchorAtEndOf(node) {
|
||||
const last = getLast(node)
|
||||
const last = getLastText(node)
|
||||
return this.anchorKey == last.key && this.anchorOffset == last.text.length
|
||||
}
|
||||
|
||||
@@ -345,7 +386,7 @@ class Range extends Record(DEFAULTS) {
|
||||
*/
|
||||
|
||||
hasFocusAtEndOf(node) {
|
||||
const last = getLast(node)
|
||||
const last = getLastText(node)
|
||||
return this.focusKey == last.key && this.focusOffset == last.text.length
|
||||
}
|
||||
|
||||
@@ -358,7 +399,7 @@ class Range extends Record(DEFAULTS) {
|
||||
|
||||
hasFocusAtStartOf(node) {
|
||||
if (this.focusOffset != 0) return false
|
||||
const first = getFirst(node)
|
||||
const first = getFirstText(node)
|
||||
return this.focusKey == first.key
|
||||
}
|
||||
|
||||
@@ -449,8 +490,10 @@ class Range extends Record(DEFAULTS) {
|
||||
return this.merge({
|
||||
anchorKey: null,
|
||||
anchorOffset: 0,
|
||||
anchorPath: null,
|
||||
focusKey: null,
|
||||
focusOffset: 0,
|
||||
focusPath: null,
|
||||
isFocused: false,
|
||||
isBackward: false,
|
||||
})
|
||||
@@ -466,8 +509,10 @@ class Range extends Record(DEFAULTS) {
|
||||
return this.merge({
|
||||
anchorKey: this.focusKey,
|
||||
anchorOffset: this.focusOffset,
|
||||
anchorPath: this.focusPath,
|
||||
focusKey: this.anchorKey,
|
||||
focusOffset: this.anchorOffset,
|
||||
focusPath: this.anchorPath,
|
||||
isBackward: this.isBackward == null ? null : !this.isBackward,
|
||||
})
|
||||
}
|
||||
@@ -507,43 +552,91 @@ class Range extends Record(DEFAULTS) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the range's anchor point to a `key` and `offset`.
|
||||
* Move the range's anchor point to a new `key` or `path` and `offset`.
|
||||
*
|
||||
* @param {String} key
|
||||
* @param {String|List} key or path
|
||||
* @param {Number} offset
|
||||
* @return {Range}
|
||||
*/
|
||||
|
||||
moveAnchorTo(key, offset) {
|
||||
const { anchorKey, focusKey, focusOffset, isBackward } = this
|
||||
const {
|
||||
anchorKey,
|
||||
focusKey,
|
||||
focusOffset,
|
||||
anchorPath,
|
||||
focusPath,
|
||||
isBackward,
|
||||
} = this
|
||||
|
||||
if (typeof key === 'string') {
|
||||
const isAnchor = key === anchorKey
|
||||
const isFocus = key === focusKey
|
||||
return this.merge({
|
||||
anchorKey: key,
|
||||
anchorPath: isFocus ? focusPath : isAnchor ? anchorPath : null,
|
||||
anchorOffset: offset,
|
||||
isBackward:
|
||||
key == focusKey
|
||||
isBackward: isFocus
|
||||
? offset > focusOffset
|
||||
: key == anchorKey ? isBackward : null,
|
||||
: isAnchor ? isBackward : null,
|
||||
})
|
||||
} else {
|
||||
const path = key
|
||||
const isAnchor = path && path.equals(anchorPath)
|
||||
const isFocus = path && path.equals(focusPath)
|
||||
return this.merge({
|
||||
anchorPath: path,
|
||||
anchorKey: isAnchor ? anchorKey : isFocus ? focusKey : null,
|
||||
anchorOffset: offset,
|
||||
isBackward: isFocus
|
||||
? offset > focusOffset
|
||||
: isAnchor ? isBackward : null,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the range's focus point to a `key` and `offset`.
|
||||
* Move the range's focus point to a new `key` or `path` and `offset`.
|
||||
*
|
||||
* @param {String} key
|
||||
* @param {String|List} key or path
|
||||
* @param {Number} offset
|
||||
* @return {Range}
|
||||
*/
|
||||
|
||||
moveFocusTo(key, offset) {
|
||||
const { focusKey, anchorKey, anchorOffset, isBackward } = this
|
||||
const {
|
||||
focusKey,
|
||||
anchorKey,
|
||||
anchorOffset,
|
||||
anchorPath,
|
||||
focusPath,
|
||||
isBackward,
|
||||
} = this
|
||||
|
||||
if (typeof key === 'string') {
|
||||
const isAnchor = key === anchorKey
|
||||
const isFocus = key === focusKey
|
||||
return this.merge({
|
||||
focusKey: key,
|
||||
focusPath: isAnchor ? anchorPath : isFocus ? focusPath : null,
|
||||
focusOffset: offset,
|
||||
isBackward:
|
||||
key == anchorKey
|
||||
? anchorOffset > offset
|
||||
: key == focusKey ? isBackward : null,
|
||||
isBackward: isAnchor
|
||||
? offset < anchorOffset
|
||||
: isFocus ? isBackward : null,
|
||||
})
|
||||
} else {
|
||||
const path = key
|
||||
const isAnchor = path && path.equals(anchorPath)
|
||||
const isFocus = path && path.equals(focusPath)
|
||||
return this.merge({
|
||||
focusPath: path,
|
||||
focusKey: isFocus ? focusKey : isAnchor ? anchorKey : null,
|
||||
focusOffset: offset,
|
||||
isBackward: isAnchor
|
||||
? offset < anchorOffset
|
||||
: isFocus ? isBackward : null,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -620,7 +713,7 @@ class Range extends Record(DEFAULTS) {
|
||||
*/
|
||||
|
||||
moveAnchorToStartOf(node) {
|
||||
node = getFirst(node)
|
||||
node = getFirstText(node)
|
||||
return this.moveAnchorTo(node.key, 0)
|
||||
}
|
||||
|
||||
@@ -632,7 +725,7 @@ class Range extends Record(DEFAULTS) {
|
||||
*/
|
||||
|
||||
moveAnchorToEndOf(node) {
|
||||
node = getLast(node)
|
||||
node = getLastText(node)
|
||||
return this.moveAnchorTo(node.key, node.text.length)
|
||||
}
|
||||
|
||||
@@ -644,7 +737,7 @@ class Range extends Record(DEFAULTS) {
|
||||
*/
|
||||
|
||||
moveFocusToStartOf(node) {
|
||||
node = getFirst(node)
|
||||
node = getFirstText(node)
|
||||
return this.moveFocusTo(node.key, 0)
|
||||
}
|
||||
|
||||
@@ -656,7 +749,7 @@ class Range extends Record(DEFAULTS) {
|
||||
*/
|
||||
|
||||
moveFocusToEndOf(node) {
|
||||
node = getLast(node)
|
||||
node = getLastText(node)
|
||||
return this.moveFocusTo(node.key, node.text.length)
|
||||
}
|
||||
|
||||
@@ -683,7 +776,15 @@ class Range extends Record(DEFAULTS) {
|
||||
|
||||
normalize(node) {
|
||||
const range = this
|
||||
let { anchorKey, anchorOffset, focusKey, focusOffset, isBackward } = range
|
||||
let {
|
||||
anchorKey,
|
||||
anchorOffset,
|
||||
anchorPath,
|
||||
focusKey,
|
||||
focusOffset,
|
||||
focusPath,
|
||||
isBackward,
|
||||
} = range
|
||||
|
||||
const anchorOffsetType = typeof anchorOffset
|
||||
const focusOffsetType = typeof focusOffset
|
||||
@@ -694,20 +795,25 @@ class Range extends Record(DEFAULTS) {
|
||||
)
|
||||
}
|
||||
|
||||
// If the range is unset, make sure it is properly zeroed out.
|
||||
if (anchorKey == null || focusKey == null) {
|
||||
// If either point in the range is unset, make sure it is fully unset.
|
||||
if (
|
||||
(anchorKey == null && anchorPath == null) ||
|
||||
(focusKey == null && focusPath == null)
|
||||
) {
|
||||
return range.merge({
|
||||
anchorKey: null,
|
||||
anchorOffset: 0,
|
||||
anchorPath: null,
|
||||
focusKey: null,
|
||||
focusOffset: 0,
|
||||
focusPath: null,
|
||||
isBackward: false,
|
||||
})
|
||||
}
|
||||
|
||||
// Get the anchor and focus nodes.
|
||||
let anchorNode = node.getDescendant(anchorKey)
|
||||
let focusNode = node.getDescendant(focusKey)
|
||||
let anchorNode = node.getNode(anchorKey || anchorPath)
|
||||
let focusNode = node.getNode(focusKey || focusPath)
|
||||
|
||||
// If the range is malformed, warn and zero it out.
|
||||
if (!anchorNode || !focusNode) {
|
||||
@@ -717,11 +823,14 @@ class Range extends Record(DEFAULTS) {
|
||||
)
|
||||
|
||||
const first = node.getFirstText()
|
||||
const path = first && node.getPath(first.key)
|
||||
return range.merge({
|
||||
anchorKey: first ? first.key : null,
|
||||
anchorOffset: 0,
|
||||
anchorPath: first ? path : null,
|
||||
focusKey: first ? first.key : null,
|
||||
focusOffset: 0,
|
||||
focusPath: first ? path : null,
|
||||
isBackward: false,
|
||||
})
|
||||
}
|
||||
@@ -752,21 +861,25 @@ class Range extends Record(DEFAULTS) {
|
||||
focusNode = focusText
|
||||
}
|
||||
|
||||
anchorKey = anchorNode.key
|
||||
focusKey = focusNode.key
|
||||
anchorPath = node.getPath(anchorKey)
|
||||
focusPath = node.getPath(focusKey)
|
||||
|
||||
// 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)
|
||||
}
|
||||
const result = PathUtils.compare(anchorPath, focusPath)
|
||||
isBackward = result === 0 ? anchorOffset > focusOffset : result === 1
|
||||
}
|
||||
|
||||
// Merge in any updated properties.
|
||||
return range.merge({
|
||||
anchorKey: anchorNode.key,
|
||||
anchorKey,
|
||||
anchorOffset,
|
||||
focusKey: focusNode.key,
|
||||
anchorPath,
|
||||
focusKey,
|
||||
focusOffset,
|
||||
focusPath,
|
||||
isBackward,
|
||||
})
|
||||
}
|
||||
@@ -774,21 +887,29 @@ class Range extends Record(DEFAULTS) {
|
||||
/**
|
||||
* Return a JSON representation of the range.
|
||||
*
|
||||
* @param {Object} options
|
||||
* @return {Object}
|
||||
*/
|
||||
|
||||
toJSON() {
|
||||
toJSON(options = {}) {
|
||||
const object = {
|
||||
object: this.object,
|
||||
anchorKey: this.anchorKey,
|
||||
anchorOffset: this.anchorOffset,
|
||||
anchorPath: this.anchorPath && this.anchorPath.toArray(),
|
||||
focusKey: this.focusKey,
|
||||
focusOffset: this.focusOffset,
|
||||
focusPath: this.focusPath && this.focusPath.toArray(),
|
||||
isAtomic: this.isAtomic,
|
||||
isBackward: this.isBackward,
|
||||
isFocused: this.isFocused,
|
||||
marks:
|
||||
this.marks == null ? null : this.marks.toArray().map(m => m.toJSON()),
|
||||
isAtomic: this.isAtomic,
|
||||
}
|
||||
|
||||
if (!options.preserveKeys) {
|
||||
delete object.anchorKey
|
||||
delete object.focusKey
|
||||
}
|
||||
|
||||
return object
|
||||
@@ -892,7 +1013,7 @@ ALIAS_METHODS.forEach(([alias, method]) => {
|
||||
* @return {Text}
|
||||
*/
|
||||
|
||||
function getFirst(node) {
|
||||
function getFirstText(node) {
|
||||
return node.object == 'text' ? node : node.getFirstText()
|
||||
}
|
||||
|
||||
@@ -903,7 +1024,7 @@ function getFirst(node) {
|
||||
* @return {Text}
|
||||
*/
|
||||
|
||||
function getLast(node) {
|
||||
function getLastText(node) {
|
||||
return node.object == 'text' ? node : node.getLastText()
|
||||
}
|
||||
|
||||
|
@@ -4,7 +4,7 @@ import { List, OrderedSet, Record, Set } from 'immutable'
|
||||
|
||||
import Leaf from './leaf'
|
||||
import MODEL_TYPES, { isType } from '../constants/model-types'
|
||||
import generateKey from '../utils/generate-key'
|
||||
import KeyUtils from '../utils/key-utils'
|
||||
import memoize from '../utils/memoize'
|
||||
|
||||
/**
|
||||
@@ -85,7 +85,7 @@ class Text extends Record(DEFAULTS) {
|
||||
return object
|
||||
}
|
||||
|
||||
const { key = generateKey() } = object
|
||||
const { key = KeyUtils.create() } = object
|
||||
let { leaves = List() } = object
|
||||
|
||||
if (Array.isArray(leaves)) {
|
||||
@@ -387,6 +387,14 @@ class Text extends Record(DEFAULTS) {
|
||||
})
|
||||
}
|
||||
|
||||
getFirstText() {
|
||||
return this
|
||||
}
|
||||
|
||||
getLastText() {
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all of the marks on between two offsets
|
||||
* Corner Cases:
|
||||
@@ -545,7 +553,7 @@ class Text extends Record(DEFAULTS) {
|
||||
*/
|
||||
|
||||
regenerateKey() {
|
||||
const key = generateKey()
|
||||
const key = KeyUtils.create()
|
||||
return this.set('key', key)
|
||||
}
|
||||
|
||||
|
@@ -3,6 +3,7 @@ import logger from 'slate-dev-logger'
|
||||
import { Record, Set, List, Map } from 'immutable'
|
||||
|
||||
import MODEL_TYPES from '../constants/model-types'
|
||||
import PathUtils from '../utils/path-utils'
|
||||
import Change from './change'
|
||||
import Data from './data'
|
||||
import Document from './document'
|
||||
@@ -61,26 +62,25 @@ class Value extends Record(DEFAULTS) {
|
||||
* @return {Object}
|
||||
*/
|
||||
|
||||
static createProperties(attrs = {}) {
|
||||
if (Value.isValue(attrs)) {
|
||||
static createProperties(a = {}) {
|
||||
if (Value.isValue(a)) {
|
||||
return {
|
||||
data: attrs.data,
|
||||
decorations: attrs.decorations,
|
||||
schema: attrs.schema,
|
||||
data: a.data,
|
||||
decorations: a.decorations,
|
||||
schema: a.schema,
|
||||
}
|
||||
}
|
||||
|
||||
if (isPlainObject(attrs)) {
|
||||
const props = {}
|
||||
if ('data' in attrs) props.data = Data.create(attrs.data)
|
||||
if ('decorations' in attrs)
|
||||
props.decorations = Range.createList(attrs.decorations)
|
||||
if ('schema' in attrs) props.schema = Schema.create(attrs.schema)
|
||||
return props
|
||||
if (isPlainObject(a)) {
|
||||
const p = {}
|
||||
if ('data' in a) p.data = Data.create(a.data)
|
||||
if ('decorations' in a) p.decorations = Range.createList(a.decorations)
|
||||
if ('schema' in a) p.schema = Schema.create(a.schema)
|
||||
return p
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`\`Value.createProperties\` only accepts objects or values, but you passed it: ${attrs}`
|
||||
`\`Value.createProperties\` only accepts objects or values, but you passed it: ${a}`
|
||||
)
|
||||
}
|
||||
|
||||
@@ -96,22 +96,8 @@ class Value extends Record(DEFAULTS) {
|
||||
|
||||
static fromJSON(object, options = {}) {
|
||||
let { document = {}, selection = {}, schema = {}, history = {} } = object
|
||||
|
||||
let data = new Map()
|
||||
|
||||
document = Document.fromJSON(document)
|
||||
|
||||
// rebuild selection from anchorPath and focusPath if keys were dropped
|
||||
const { anchorPath, focusPath, anchorKey, focusKey } = selection
|
||||
|
||||
if (anchorPath !== undefined && anchorKey === undefined) {
|
||||
selection.anchorKey = document.assertPath(anchorPath).key
|
||||
}
|
||||
|
||||
if (focusPath !== undefined && focusKey === undefined) {
|
||||
selection.focusKey = document.assertPath(focusPath).key
|
||||
}
|
||||
|
||||
selection = Range.fromJSON(selection)
|
||||
schema = Schema.fromJSON(schema)
|
||||
history = History.fromJSON(history)
|
||||
@@ -133,6 +119,8 @@ class Value extends Record(DEFAULTS) {
|
||||
if (text) selection = selection.collapseToStartOf(text)
|
||||
}
|
||||
|
||||
selection = selection.normalize(document)
|
||||
|
||||
let value = new Value({
|
||||
data,
|
||||
document,
|
||||
@@ -283,6 +271,26 @@ class Value extends Record(DEFAULTS) {
|
||||
return this.selection.endKey
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current start path.
|
||||
*
|
||||
* @return {String}
|
||||
*/
|
||||
|
||||
get startPath() {
|
||||
return this.selection.startPath
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current end path.
|
||||
*
|
||||
* @return {String}
|
||||
*/
|
||||
|
||||
get endPath() {
|
||||
return this.selection.endPath
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current start offset.
|
||||
*
|
||||
@@ -323,6 +331,26 @@ class Value extends Record(DEFAULTS) {
|
||||
return this.selection.focusKey
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current anchor path.
|
||||
*
|
||||
* @return {String}
|
||||
*/
|
||||
|
||||
get anchorPath() {
|
||||
return this.selection.anchorPath
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current focus path.
|
||||
*
|
||||
* @return {String}
|
||||
*/
|
||||
|
||||
get focusPath() {
|
||||
return this.selection.focusPath
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current anchor offset.
|
||||
*
|
||||
@@ -642,6 +670,422 @@ class Value extends Record(DEFAULTS) {
|
||||
return new Change({ ...attrs, value: this })
|
||||
}
|
||||
|
||||
/**
|
||||
* Add mark to text at `offset` and `length` in node by `path`.
|
||||
*
|
||||
* @param {List|String} path
|
||||
* @param {Number} offset
|
||||
* @param {Number} length
|
||||
* @param {Mark} mark
|
||||
* @return {Value}
|
||||
*/
|
||||
|
||||
addMark(path, offset, length, mark) {
|
||||
let value = this
|
||||
let { document } = value
|
||||
document = document.addMark(path, offset, length, mark)
|
||||
value = this.set('document', document)
|
||||
return value
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert a `node`.
|
||||
*
|
||||
* @param {List|String} path
|
||||
* @param {Node} node
|
||||
* @return {Value}
|
||||
*/
|
||||
|
||||
insertNode(path, node) {
|
||||
let value = this
|
||||
let { document } = value
|
||||
document = document.insertNode(path, node)
|
||||
value = value.set('document', document)
|
||||
|
||||
value = value.mapRanges(range => {
|
||||
return range.merge({ anchorPath: null, focusPath: null })
|
||||
})
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert `text` at `offset` in node by `path`.
|
||||
*
|
||||
* @param {List|String} path
|
||||
* @param {Number} offset
|
||||
* @param {String} text
|
||||
* @param {Set} marks
|
||||
* @return {Value}
|
||||
*/
|
||||
|
||||
insertText(path, offset, text, marks) {
|
||||
let value = this
|
||||
let { document } = value
|
||||
document = document.insertText(path, offset, text, marks)
|
||||
value = value.set('document', document)
|
||||
|
||||
// Update any ranges that were affected.
|
||||
const node = document.assertNode(path)
|
||||
value = value.clearAtomicRanges(node.key, offset)
|
||||
|
||||
value = value.mapRanges(range => {
|
||||
const { anchorKey, anchorOffset, isBackward, isAtomic } = range
|
||||
|
||||
if (
|
||||
anchorKey === node.key &&
|
||||
(anchorOffset > offset ||
|
||||
(anchorOffset === offset && (!isAtomic || !isBackward)))
|
||||
) {
|
||||
return range.moveAnchor(text.length)
|
||||
}
|
||||
|
||||
return range
|
||||
})
|
||||
|
||||
value = value.mapRanges(range => {
|
||||
const { focusKey, focusOffset, isBackward, isAtomic } = range
|
||||
|
||||
if (
|
||||
focusKey === node.key &&
|
||||
(focusOffset > offset ||
|
||||
(focusOffset == offset && (!isAtomic || isBackward)))
|
||||
) {
|
||||
return range.moveFocus(text.length)
|
||||
}
|
||||
|
||||
return range
|
||||
})
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge a node backwards its previous sibling.
|
||||
*
|
||||
* @param {List|Key} path
|
||||
* @return {Value}
|
||||
*/
|
||||
|
||||
mergeNode(path) {
|
||||
let value = this
|
||||
const { document } = value
|
||||
const newDocument = document.mergeNode(path)
|
||||
path = document.resolvePath(path)
|
||||
const withPath = PathUtils.decrement(path)
|
||||
const one = document.getNode(withPath)
|
||||
const two = document.getNode(path)
|
||||
value = value.set('document', newDocument)
|
||||
|
||||
value = value.mapRanges(range => {
|
||||
if (two.object === 'text') {
|
||||
const max = one.text.length
|
||||
|
||||
if (range.anchorKey === two.key) {
|
||||
range = range.moveAnchorTo(one.key, max + range.anchorOffset)
|
||||
}
|
||||
|
||||
if (range.focusKey === two.key) {
|
||||
range = range.moveFocusTo(one.key, max + range.focusOffset)
|
||||
}
|
||||
}
|
||||
|
||||
range = range.merge({ anchorPath: null, focusPath: null })
|
||||
return range
|
||||
})
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
/**
|
||||
* Move a node by `path` to `newPath`.
|
||||
*
|
||||
* A `newIndex` can be provided when move nodes by `key`, to account for not
|
||||
* being able to have a key for a location in the tree that doesn't exist yet.
|
||||
*
|
||||
* @param {List|Key} path
|
||||
* @param {List|Key} newPath
|
||||
* @param {Number} newIndex
|
||||
* @return {Value}
|
||||
*/
|
||||
|
||||
moveNode(path, newPath, newIndex = 0) {
|
||||
let value = this
|
||||
let { document } = value
|
||||
document = document.moveNode(path, newPath, newIndex)
|
||||
value = value.set('document', document)
|
||||
|
||||
value = value.mapRanges(range => {
|
||||
return range.merge({ anchorPath: null, focusPath: null })
|
||||
})
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove mark from text at `offset` and `length` in node.
|
||||
*
|
||||
* @param {List|String} path
|
||||
* @param {Number} offset
|
||||
* @param {Number} length
|
||||
* @param {Mark} mark
|
||||
* @return {Value}
|
||||
*/
|
||||
|
||||
removeMark(path, offset, length, mark) {
|
||||
let value = this
|
||||
let { document } = value
|
||||
document = document.removeMark(path, offset, length, mark)
|
||||
value = this.set('document', document)
|
||||
return value
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a node by `path`.
|
||||
*
|
||||
* @param {List|String} path
|
||||
* @return {Value}
|
||||
*/
|
||||
|
||||
removeNode(path) {
|
||||
let value = this
|
||||
let { document } = value
|
||||
const node = document.assertNode(path)
|
||||
const first = node.object == 'text' ? node : node.getFirstText() || node
|
||||
const last = node.object == 'text' ? node : node.getLastText() || node
|
||||
const prev = document.getPreviousText(first.key)
|
||||
const next = document.getNextText(last.key)
|
||||
|
||||
document = document.removeNode(path)
|
||||
value = value.set('document', document)
|
||||
|
||||
value = value.mapRanges(range => {
|
||||
const { startKey, endKey } = range
|
||||
|
||||
if (node.hasNode(startKey)) {
|
||||
range = prev
|
||||
? range.moveStartTo(prev.key, prev.text.length)
|
||||
: next ? range.moveStartTo(next.key, 0) : range.deselect()
|
||||
}
|
||||
|
||||
if (node.hasNode(endKey)) {
|
||||
range = prev
|
||||
? range.moveEndTo(prev.key, prev.text.length)
|
||||
: next ? range.moveEndTo(next.key, 0) : range.deselect()
|
||||
}
|
||||
|
||||
range = range.merge({ anchorPath: null, focusPath: null })
|
||||
return range
|
||||
})
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove `text` at `offset` in node by `path`.
|
||||
*
|
||||
* @param {List|Key} path
|
||||
* @param {Number} offset
|
||||
* @param {String} text
|
||||
* @return {Value}
|
||||
*/
|
||||
|
||||
removeText(path, offset, text) {
|
||||
let value = this
|
||||
let { document } = value
|
||||
document = document.removeText(path, offset, text)
|
||||
value = value.set('document', document)
|
||||
|
||||
const node = document.assertNode(path)
|
||||
const { length } = text
|
||||
const rangeOffset = offset + length
|
||||
value = value.clearAtomicRanges(node.key, offset, offset + length)
|
||||
|
||||
value = value.mapRanges(range => {
|
||||
const { anchorKey } = range
|
||||
|
||||
if (anchorKey === node.key) {
|
||||
return range.anchorOffset >= rangeOffset
|
||||
? range.moveAnchor(-length)
|
||||
: range.anchorOffset > offset
|
||||
? range.moveAnchorTo(range.anchorKey, offset)
|
||||
: range
|
||||
}
|
||||
|
||||
return range
|
||||
})
|
||||
|
||||
value = value.mapRanges(range => {
|
||||
const { focusKey } = range
|
||||
|
||||
if (focusKey === node.key) {
|
||||
return range.focusOffset >= rangeOffset
|
||||
? range.moveFocus(-length)
|
||||
: range.focusOffset > offset
|
||||
? range.moveFocusTo(range.focusKey, offset)
|
||||
: range
|
||||
}
|
||||
|
||||
return range
|
||||
})
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
/**
|
||||
* Set `properties` on a node.
|
||||
*
|
||||
* @param {List|String} path
|
||||
* @param {Object} properties
|
||||
* @return {Value}
|
||||
*/
|
||||
|
||||
setNode(path, properties) {
|
||||
let value = this
|
||||
let { document } = value
|
||||
document = document.setNode(path, properties)
|
||||
value = value.set('document', document)
|
||||
return value
|
||||
}
|
||||
|
||||
/**
|
||||
* Set `properties` on `mark` on text at `offset` and `length` in node.
|
||||
*
|
||||
* @param {List|String} path
|
||||
* @param {Number} offset
|
||||
* @param {Number} length
|
||||
* @param {Mark} mark
|
||||
* @param {Object} properties
|
||||
* @return {Value}
|
||||
*/
|
||||
|
||||
setMark(path, offset, length, mark, properties) {
|
||||
let value = this
|
||||
let { document } = value
|
||||
document = document.setMark(path, offset, length, mark, properties)
|
||||
value = value.set('document', document)
|
||||
return value
|
||||
}
|
||||
|
||||
/**
|
||||
* Set `properties` on the selection.
|
||||
*
|
||||
* @param {Value} value
|
||||
* @param {Operation} operation
|
||||
* @return {Value}
|
||||
*/
|
||||
|
||||
setSelection(properties) {
|
||||
let value = this
|
||||
let { document, selection } = value
|
||||
selection = selection.merge(properties)
|
||||
selection = selection.normalize(document)
|
||||
value = value.set('selection', selection)
|
||||
return value
|
||||
}
|
||||
|
||||
/**
|
||||
* Split a node by `path` at `position` with optional `properties` to apply
|
||||
* to the newly split node.
|
||||
*
|
||||
* @param {List|String} path
|
||||
* @param {Number} position
|
||||
* @param {Object} properties
|
||||
* @return {Value}
|
||||
*/
|
||||
|
||||
splitNode(path, position, properties) {
|
||||
let value = this
|
||||
const { document } = value
|
||||
const newDocument = document.splitNode(path, position, properties)
|
||||
const node = document.assertNode(path)
|
||||
value = value.set('document', newDocument)
|
||||
|
||||
value = value.mapRanges(range => {
|
||||
const next = newDocument.getNextText(node.key)
|
||||
const { startKey, startOffset, endKey, endOffset } = range
|
||||
|
||||
// If the start was after the split, move it to the next node.
|
||||
if (node.key === startKey && position <= startOffset) {
|
||||
range = range.moveStartTo(next.key, startOffset - position)
|
||||
}
|
||||
|
||||
// If the end was after the split, move it to the next node.
|
||||
if (node.key === endKey && position <= endOffset) {
|
||||
range = range.moveEndTo(next.key, endOffset - position)
|
||||
}
|
||||
|
||||
range = range.merge({ anchorPath: null, focusPath: null })
|
||||
return range
|
||||
})
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
/**
|
||||
* Map all range objects to apply adjustments with an `iterator`.
|
||||
*
|
||||
* @param {Function} iterator
|
||||
* @return {Value}
|
||||
*/
|
||||
|
||||
mapRanges(iterator) {
|
||||
let value = this
|
||||
const { document, selection, decorations } = value
|
||||
|
||||
if (selection) {
|
||||
let next = selection.isSet ? iterator(selection) : selection
|
||||
if (!next) next = selection.deselect()
|
||||
if (next !== selection) next = next.normalize(document)
|
||||
value = value.set('selection', next)
|
||||
}
|
||||
|
||||
if (decorations) {
|
||||
let next = decorations.map(decoration => {
|
||||
let n = decoration.isSet ? iterator(decoration) : decoration
|
||||
if (n && n !== decoration) n = n.normalize(document)
|
||||
return n
|
||||
})
|
||||
|
||||
next = next.filter(decoration => !!decoration)
|
||||
next = next.size ? next : null
|
||||
value = value.set('decorations', next)
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove any atomic ranges inside a `key`, `offset` and `length`.
|
||||
*
|
||||
* @param {String} key
|
||||
* @param {Number} start
|
||||
* @param {Number?} end
|
||||
* @return {Value}
|
||||
*/
|
||||
|
||||
clearAtomicRanges(key, start, end = null) {
|
||||
return this.mapRanges(range => {
|
||||
const { isAtomic, startKey, startOffset, endKey, endOffset } = range
|
||||
if (!isAtomic) return range
|
||||
if (startKey !== key) return range
|
||||
|
||||
if (startOffset < start && (endKey !== key || endOffset > start)) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (
|
||||
end != null &&
|
||||
startOffset < end &&
|
||||
(endKey !== key || endOffset > end)
|
||||
) {
|
||||
return null
|
||||
}
|
||||
|
||||
return range
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a JSON representation of the value.
|
||||
*
|
||||
@@ -656,59 +1100,25 @@ class Value extends Record(DEFAULTS) {
|
||||
}
|
||||
|
||||
if (options.preserveData) {
|
||||
object.data = this.data.toJSON()
|
||||
object.data = this.data.toJSON(options)
|
||||
}
|
||||
|
||||
if (options.preserveDecorations) {
|
||||
object.decorations = this.decorations
|
||||
? this.decorations.toArray().map(d => d.toJSON())
|
||||
? this.decorations.toArray().map(d => d.toJSON(options))
|
||||
: null
|
||||
}
|
||||
|
||||
if (options.preserveHistory) {
|
||||
object.history = this.history.toJSON()
|
||||
object.history = this.history.toJSON(options)
|
||||
}
|
||||
|
||||
if (options.preserveSelection) {
|
||||
object.selection = this.selection.toJSON()
|
||||
object.selection = this.selection.toJSON(options)
|
||||
}
|
||||
|
||||
if (options.preserveSchema) {
|
||||
object.schema = this.schema.toJSON()
|
||||
}
|
||||
|
||||
if (options.preserveSelection && !options.preserveKeys) {
|
||||
const { document, selection } = this
|
||||
|
||||
object.selection.anchorPath = selection.isSet
|
||||
? document.getPath(selection.anchorKey)
|
||||
: null
|
||||
|
||||
object.selection.focusPath = selection.isSet
|
||||
? document.getPath(selection.focusKey)
|
||||
: null
|
||||
|
||||
delete object.selection.anchorKey
|
||||
delete object.selection.focusKey
|
||||
}
|
||||
|
||||
if (
|
||||
options.preserveDecorations &&
|
||||
object.decorations &&
|
||||
!options.preserveKeys
|
||||
) {
|
||||
const { document } = this
|
||||
|
||||
object.decorations = object.decorations.map(decoration => {
|
||||
const withPath = {
|
||||
...decoration,
|
||||
anchorPath: document.getPath(decoration.anchorKey),
|
||||
focusPath: document.getPath(decoration.focusKey),
|
||||
}
|
||||
delete withPath.anchorKey
|
||||
delete withPath.focusKey
|
||||
return withPath
|
||||
})
|
||||
object.schema = this.schema.toJSON(options)
|
||||
}
|
||||
|
||||
return object
|
||||
|
@@ -11,552 +11,101 @@ import Operation from '../models/operation'
|
||||
const debug = Debug('slate:operation:apply')
|
||||
|
||||
/**
|
||||
* Apply adjustments to affected ranges (selections, decorations);
|
||||
* accepts (value, checking function(range) -> bool, applying function(range) -> range)
|
||||
* returns value with affected ranges updated
|
||||
* Apply an `op` to a `value`.
|
||||
*
|
||||
* @param {Value} value
|
||||
* @param {Function} checkAffected
|
||||
* @param {Function} adjustRange
|
||||
* @return {Value}
|
||||
*/
|
||||
|
||||
function applyRangeAdjustments(value, checkAffected, adjustRange) {
|
||||
// check selection, apply adjustment if affected
|
||||
if (value.selection && checkAffected(value.selection)) {
|
||||
value = value.set('selection', adjustRange(value.selection))
|
||||
}
|
||||
|
||||
if (!value.decorations) return value
|
||||
|
||||
// check all ranges, apply adjustment if affected
|
||||
const decorations = value.decorations
|
||||
.map(
|
||||
decoration =>
|
||||
checkAffected(decoration) ? adjustRange(decoration) : decoration
|
||||
)
|
||||
.filter(decoration => decoration.anchorKey !== null)
|
||||
return value.set('decorations', decorations)
|
||||
}
|
||||
|
||||
/**
|
||||
* clear any atomic ranges (in decorations) if they contain the point (key, offset, offset-end?)
|
||||
* specified
|
||||
*
|
||||
* @param {Value} value
|
||||
* @param {String} key
|
||||
* @param {Number} offset
|
||||
* @param {Number?} offsetEnd
|
||||
* @return {Value}
|
||||
*/
|
||||
|
||||
function clearAtomicRangesIfContains(value, key, offset, offsetEnd = null) {
|
||||
return applyRangeAdjustments(
|
||||
value,
|
||||
range => {
|
||||
if (!range.isAtomic) return false
|
||||
const { startKey, startOffset, endKey, endOffset } = range
|
||||
return (
|
||||
(startKey == key &&
|
||||
startOffset < offset &&
|
||||
(endKey != key || endOffset > offset)) ||
|
||||
(offsetEnd &&
|
||||
startKey == key &&
|
||||
startOffset < offsetEnd &&
|
||||
(endKey != key || endOffset > offsetEnd))
|
||||
)
|
||||
},
|
||||
range => range.deselect()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Applying functions.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
|
||||
const APPLIERS = {
|
||||
/**
|
||||
* Add mark to text at `offset` and `length` in node by `path`.
|
||||
*
|
||||
* @param {Value} value
|
||||
* @param {Operation} operation
|
||||
* @return {Value}
|
||||
*/
|
||||
|
||||
add_mark(value, operation) {
|
||||
const { path, offset, length, mark } = operation
|
||||
let { document } = value
|
||||
let node = document.assertPath(path)
|
||||
node = node.addMark(offset, length, mark)
|
||||
document = document.updateNode(node)
|
||||
value = value.set('document', document)
|
||||
return value
|
||||
},
|
||||
|
||||
/**
|
||||
* Insert a `node` at `index` in a node by `path`.
|
||||
*
|
||||
* @param {Value} value
|
||||
* @param {Operation} operation
|
||||
* @return {Value}
|
||||
*/
|
||||
|
||||
insert_node(value, operation) {
|
||||
const { path, node } = operation
|
||||
const index = path[path.length - 1]
|
||||
const rest = path.slice(0, -1)
|
||||
let { document } = value
|
||||
let parent = document.assertPath(rest)
|
||||
parent = parent.insertNode(index, node)
|
||||
document = document.updateNode(parent)
|
||||
value = value.set('document', document)
|
||||
return value
|
||||
},
|
||||
|
||||
/**
|
||||
* Insert `text` at `offset` in node by `path`.
|
||||
*
|
||||
* @param {Value} value
|
||||
* @param {Operation} operation
|
||||
* @return {Value}
|
||||
*/
|
||||
|
||||
insert_text(value, operation) {
|
||||
const { path, offset, text, marks } = operation
|
||||
let { document } = value
|
||||
let node = document.assertPath(path)
|
||||
|
||||
// Update the document
|
||||
node = node.insertText(offset, text, marks)
|
||||
document = document.updateNode(node)
|
||||
|
||||
value = value.set('document', document)
|
||||
|
||||
// if insert happens within atomic ranges, clear
|
||||
value = clearAtomicRangesIfContains(value, node.key, offset)
|
||||
|
||||
// Update the selection, decorations
|
||||
value = applyRangeAdjustments(
|
||||
value,
|
||||
({ anchorKey, anchorOffset, isBackward, isAtomic }) =>
|
||||
anchorKey == node.key &&
|
||||
(anchorOffset > offset ||
|
||||
(anchorOffset == offset && (!isAtomic || !isBackward))),
|
||||
range => range.moveAnchor(text.length)
|
||||
)
|
||||
|
||||
value = applyRangeAdjustments(
|
||||
value,
|
||||
({ focusKey, focusOffset, isBackward, isAtomic }) =>
|
||||
focusKey == node.key &&
|
||||
(focusOffset > offset ||
|
||||
(focusOffset == offset && (!isAtomic || isBackward))),
|
||||
range => range.moveFocus(text.length)
|
||||
)
|
||||
|
||||
return value
|
||||
},
|
||||
|
||||
/**
|
||||
* Merge a node at `path` with the previous node.
|
||||
*
|
||||
* @param {Value} value
|
||||
* @param {Operation} operation
|
||||
* @return {Value}
|
||||
*/
|
||||
|
||||
merge_node(value, operation) {
|
||||
const { path } = operation
|
||||
const withPath = path
|
||||
.slice(0, path.length - 1)
|
||||
.concat([path[path.length - 1] - 1])
|
||||
let { document } = value
|
||||
const one = document.assertPath(withPath)
|
||||
const two = document.assertPath(path)
|
||||
let parent = document.getParent(one.key)
|
||||
const oneIndex = parent.nodes.indexOf(one)
|
||||
const twoIndex = parent.nodes.indexOf(two)
|
||||
|
||||
// Perform the merge in the document.
|
||||
parent = parent.mergeNode(oneIndex, twoIndex)
|
||||
document = document.updateNode(parent)
|
||||
value = value.set('document', document)
|
||||
|
||||
if (one.object == 'text') {
|
||||
value = applyRangeAdjustments(
|
||||
value,
|
||||
// If the nodes are text nodes and the range is inside the second node:
|
||||
({ anchorKey, focusKey }) =>
|
||||
anchorKey == two.key || focusKey == two.key,
|
||||
// update it to refer to the first node instead:
|
||||
range => {
|
||||
if (range.anchorKey == two.key)
|
||||
range = range.moveAnchorTo(
|
||||
one.key,
|
||||
one.text.length + range.anchorOffset
|
||||
)
|
||||
if (range.focusKey == two.key)
|
||||
range = range.moveFocusTo(
|
||||
one.key,
|
||||
one.text.length + range.focusOffset
|
||||
)
|
||||
return range.normalize(document)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
return value
|
||||
},
|
||||
|
||||
/**
|
||||
* Move a node by `path` to `newPath`.
|
||||
*
|
||||
* @param {Value} value
|
||||
* @param {Operation} operation
|
||||
* @return {Value}
|
||||
*/
|
||||
|
||||
move_node(value, operation) {
|
||||
const { path, newPath } = operation
|
||||
const newIndex = newPath[newPath.length - 1]
|
||||
const newParentPath = newPath.slice(0, -1)
|
||||
const oldParentPath = path.slice(0, -1)
|
||||
const oldIndex = path[path.length - 1]
|
||||
let { document } = value
|
||||
const node = document.assertPath(path)
|
||||
|
||||
// Remove the node from its current parent.
|
||||
let parent = document.getParent(node.key)
|
||||
parent = parent.removeNode(oldIndex)
|
||||
document = document.updateNode(parent)
|
||||
|
||||
// Find the new target...
|
||||
let target
|
||||
|
||||
// If the old path and the rest of the new path are the same, then the new
|
||||
// target is the old parent.
|
||||
if (
|
||||
oldParentPath.every((x, i) => x === newParentPath[i]) &&
|
||||
oldParentPath.length === newParentPath.length
|
||||
) {
|
||||
target = parent
|
||||
} else if (
|
||||
oldParentPath.every((x, i) => x === newParentPath[i]) &&
|
||||
oldIndex < newParentPath[oldParentPath.length]
|
||||
) {
|
||||
// Otherwise, if the old path removal resulted in the new path being no longer
|
||||
// correct, we need to decrement the new path at the old path's last index.
|
||||
newParentPath[oldParentPath.length]--
|
||||
target = document.assertPath(newParentPath)
|
||||
} else {
|
||||
// Otherwise, we can just grab the target normally...
|
||||
target = document.assertPath(newParentPath)
|
||||
}
|
||||
|
||||
// Insert the new node to its new parent.
|
||||
target = target.insertNode(newIndex, node)
|
||||
document = document.updateNode(target)
|
||||
value = value.set('document', document)
|
||||
return value
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove mark from text at `offset` and `length` in node by `path`.
|
||||
*
|
||||
* @param {Value} value
|
||||
* @param {Operation} operation
|
||||
* @return {Value}
|
||||
*/
|
||||
|
||||
remove_mark(value, operation) {
|
||||
const { path, offset, length, mark } = operation
|
||||
let { document } = value
|
||||
let node = document.assertPath(path)
|
||||
node = node.removeMark(offset, length, mark)
|
||||
document = document.updateNode(node)
|
||||
value = value.set('document', document)
|
||||
return value
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove a node by `path`.
|
||||
*
|
||||
* @param {Value} value
|
||||
* @param {Operation} operation
|
||||
* @return {Value}
|
||||
*/
|
||||
|
||||
remove_node(value, operation) {
|
||||
const { path } = operation
|
||||
let { document, selection } = value
|
||||
const node = document.assertPath(path)
|
||||
|
||||
if (selection.isSet || value.decorations !== null) {
|
||||
const first = node.object == 'text' ? node : node.getFirstText() || node
|
||||
const last = node.object == 'text' ? node : node.getLastText() || node
|
||||
const prev = document.getPreviousText(first.key)
|
||||
const next = document.getNextText(last.key)
|
||||
|
||||
value = applyRangeAdjustments(
|
||||
value,
|
||||
// If the start or end point was in this node
|
||||
({ startKey, endKey }) =>
|
||||
node.hasNode(startKey) || node.hasNode(endKey),
|
||||
// update it to be just before/after
|
||||
range => {
|
||||
const { startKey, endKey } = range
|
||||
|
||||
if (node.hasNode(startKey)) {
|
||||
range = prev
|
||||
? range.moveStartTo(prev.key, prev.text.length)
|
||||
: next ? range.moveStartTo(next.key, 0) : range.deselect()
|
||||
}
|
||||
|
||||
if (node.hasNode(endKey)) {
|
||||
range = prev
|
||||
? range.moveEndTo(prev.key, prev.text.length)
|
||||
: next ? range.moveEndTo(next.key, 0) : range.deselect()
|
||||
}
|
||||
|
||||
// If the range wasn't deselected, normalize it.
|
||||
if (range.isSet) return range.normalize(document)
|
||||
return range
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// Remove the node from the document.
|
||||
let parent = document.getParent(node.key)
|
||||
const index = parent.nodes.indexOf(node)
|
||||
parent = parent.removeNode(index)
|
||||
document = document.updateNode(parent)
|
||||
|
||||
// Update the document and range.
|
||||
value = value.set('document', document)
|
||||
return value
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove `text` at `offset` in node by `path`.
|
||||
*
|
||||
* @param {Value} value
|
||||
* @param {Operation} operation
|
||||
* @return {Value}
|
||||
*/
|
||||
|
||||
remove_text(value, operation) {
|
||||
const { path, offset, text } = operation
|
||||
const { length } = text
|
||||
const rangeOffset = offset + length
|
||||
let { document } = value
|
||||
|
||||
let node = document.assertPath(path)
|
||||
|
||||
// if insert happens within atomic ranges, clear
|
||||
value = clearAtomicRangesIfContains(
|
||||
value,
|
||||
node.key,
|
||||
offset,
|
||||
offset + length
|
||||
)
|
||||
|
||||
value = applyRangeAdjustments(
|
||||
value,
|
||||
// if anchor of range is here
|
||||
({ anchorKey }) => anchorKey == node.key,
|
||||
// adjust if it is in or past the removal range
|
||||
range =>
|
||||
range.anchorOffset >= rangeOffset
|
||||
? range.moveAnchor(-length)
|
||||
: range.anchorOffset > offset
|
||||
? range.moveAnchorTo(range.anchorKey, offset)
|
||||
: range
|
||||
)
|
||||
|
||||
value = applyRangeAdjustments(
|
||||
value,
|
||||
// if focus of range is here
|
||||
({ focusKey }) => focusKey == node.key,
|
||||
// adjust if it is in or past the removal range
|
||||
range =>
|
||||
range.focusOffset >= rangeOffset
|
||||
? range.moveFocus(-length)
|
||||
: range.focusOffset > offset
|
||||
? range.moveFocusTo(range.focusKey, offset)
|
||||
: range
|
||||
)
|
||||
|
||||
node = node.removeText(offset, length)
|
||||
document = document.updateNode(node)
|
||||
value = value.set('document', document)
|
||||
return value
|
||||
},
|
||||
|
||||
/**
|
||||
* Set `properties` on mark on text at `offset` and `length` in node by `path`.
|
||||
*
|
||||
* @param {Value} value
|
||||
* @param {Operation} operation
|
||||
* @return {Value}
|
||||
*/
|
||||
|
||||
set_mark(value, operation) {
|
||||
const { path, offset, length, mark, properties } = operation
|
||||
let { document } = value
|
||||
let node = document.assertPath(path)
|
||||
node = node.updateMark(offset, length, mark, properties)
|
||||
document = document.updateNode(node)
|
||||
value = value.set('document', document)
|
||||
return value
|
||||
},
|
||||
|
||||
/**
|
||||
* Set `properties` on a node by `path`.
|
||||
*
|
||||
* @param {Value} value
|
||||
* @param {Operation} operation
|
||||
* @return {Value}
|
||||
*/
|
||||
|
||||
set_node(value, operation) {
|
||||
const { path, properties } = operation
|
||||
let { document } = value
|
||||
let node = document.assertPath(path)
|
||||
node = node.merge(properties)
|
||||
document = document.updateNode(node)
|
||||
value = value.set('document', document)
|
||||
return value
|
||||
},
|
||||
|
||||
/**
|
||||
* Set `properties` on the selection.
|
||||
*
|
||||
* @param {Value} value
|
||||
* @param {Operation} operation
|
||||
* @return {Value}
|
||||
*/
|
||||
|
||||
set_selection(value, operation) {
|
||||
const { properties } = operation
|
||||
const { anchorPath, focusPath, ...props } = properties
|
||||
let { document, selection } = value
|
||||
|
||||
if (anchorPath !== undefined) {
|
||||
props.anchorKey =
|
||||
anchorPath === null ? null : document.assertPath(anchorPath).key
|
||||
}
|
||||
|
||||
if (focusPath !== undefined) {
|
||||
props.focusKey =
|
||||
focusPath === null ? null : document.assertPath(focusPath).key
|
||||
}
|
||||
|
||||
selection = selection.merge(props)
|
||||
selection = selection.normalize(document)
|
||||
value = value.set('selection', selection)
|
||||
return value
|
||||
},
|
||||
|
||||
/**
|
||||
* Set `properties` on `value`.
|
||||
*
|
||||
* @param {Value} value
|
||||
* @param {Operation} operation
|
||||
* @return {Value}
|
||||
*/
|
||||
|
||||
set_value(value, operation) {
|
||||
const { properties } = operation
|
||||
value = value.merge(properties)
|
||||
return value
|
||||
},
|
||||
|
||||
/**
|
||||
* Split a node by `path` at `offset`.
|
||||
*
|
||||
* @param {Value} value
|
||||
* @param {Operation} operation
|
||||
* @return {Value}
|
||||
*/
|
||||
|
||||
split_node(value, operation) {
|
||||
const { path, position, properties } = operation
|
||||
let { document } = value
|
||||
|
||||
// Calculate a few things...
|
||||
const node = document.assertPath(path)
|
||||
let parent = document.getParent(node.key)
|
||||
const index = parent.nodes.indexOf(node)
|
||||
|
||||
// Split the node by its parent.
|
||||
parent = parent.splitNode(index, position)
|
||||
|
||||
if (properties) {
|
||||
const splitNode = parent.nodes.get(index + 1)
|
||||
|
||||
if (splitNode.object !== 'text') {
|
||||
parent = parent.updateNode(splitNode.merge(properties))
|
||||
}
|
||||
}
|
||||
|
||||
document = document.updateNode(parent)
|
||||
const next = document.getNextText(node.key)
|
||||
|
||||
value = applyRangeAdjustments(
|
||||
value,
|
||||
// check if range is affected
|
||||
({ startKey, startOffset, endKey, endOffset }) =>
|
||||
(node.key == startKey && position <= startOffset) ||
|
||||
(node.key == endKey && position <= endOffset),
|
||||
// update its start / end as needed
|
||||
range => {
|
||||
const { startKey, startOffset, endKey, endOffset } = range
|
||||
let normalize = false
|
||||
|
||||
if (node.key == startKey && position <= startOffset) {
|
||||
range = range.moveStartTo(next.key, startOffset - position)
|
||||
normalize = true
|
||||
}
|
||||
|
||||
if (node.key == endKey && position <= endOffset) {
|
||||
range = range.moveEndTo(next.key, endOffset - position)
|
||||
normalize = true
|
||||
}
|
||||
|
||||
// Normalize the selection if we changed it
|
||||
if (normalize) return range.normalize(document)
|
||||
return range
|
||||
}
|
||||
)
|
||||
|
||||
// Return the updated value.
|
||||
value = value.set('document', document)
|
||||
return value
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply an `operation` to a `value`.
|
||||
*
|
||||
* @param {Value} value
|
||||
* @param {Object|Operation} operation
|
||||
* @param {Object|Operation} op
|
||||
* @return {Value} value
|
||||
*/
|
||||
|
||||
function applyOperation(value, operation) {
|
||||
operation = Operation.create(operation)
|
||||
const { type } = operation
|
||||
const apply = APPLIERS[type]
|
||||
function applyOperation(value, op) {
|
||||
op = Operation.create(op)
|
||||
const { type } = op
|
||||
debug(type, op)
|
||||
|
||||
if (!apply) {
|
||||
throw new Error(`Unknown operation type: "${type}".`)
|
||||
switch (type) {
|
||||
case 'add_mark': {
|
||||
const { path, offset, length, mark } = op
|
||||
const next = value.addMark(path, offset, length, mark)
|
||||
return next
|
||||
}
|
||||
|
||||
debug(type, operation)
|
||||
value = apply(value, operation)
|
||||
return value
|
||||
case 'insert_node': {
|
||||
const { path, node } = op
|
||||
const next = value.insertNode(path, node)
|
||||
return next
|
||||
}
|
||||
|
||||
case 'insert_text': {
|
||||
const { path, offset, text, marks } = op
|
||||
const next = value.insertText(path, offset, text, marks)
|
||||
return next
|
||||
}
|
||||
|
||||
case 'merge_node': {
|
||||
const { path } = op
|
||||
const next = value.mergeNode(path)
|
||||
return next
|
||||
}
|
||||
|
||||
case 'move_node': {
|
||||
const { path, newPath } = op
|
||||
const next = value.moveNode(path, newPath)
|
||||
return next
|
||||
}
|
||||
|
||||
case 'remove_mark': {
|
||||
const { path, offset, length, mark } = op
|
||||
const next = value.removeMark(path, offset, length, mark)
|
||||
return next
|
||||
}
|
||||
|
||||
case 'remove_node': {
|
||||
const { path } = op
|
||||
const next = value.removeNode(path)
|
||||
return next
|
||||
}
|
||||
|
||||
case 'remove_text': {
|
||||
const { path, offset, text } = op
|
||||
const next = value.removeText(path, offset, text)
|
||||
return next
|
||||
}
|
||||
|
||||
case 'set_mark': {
|
||||
const { path, offset, length, mark, properties } = op
|
||||
const next = value.setMark(path, offset, length, mark, properties)
|
||||
return next
|
||||
}
|
||||
|
||||
case 'set_node': {
|
||||
const { path, properties } = op
|
||||
const next = value.setNode(path, properties)
|
||||
return next
|
||||
}
|
||||
|
||||
case 'set_selection': {
|
||||
const { properties } = op
|
||||
const next = value.setSelection(properties)
|
||||
return next
|
||||
}
|
||||
|
||||
case 'set_value': {
|
||||
const { properties } = op
|
||||
const next = value.merge(properties)
|
||||
return next
|
||||
}
|
||||
|
||||
case 'split_node': {
|
||||
const { path, position, properties } = op
|
||||
const next = value.splitNode(path, position, properties)
|
||||
return next
|
||||
}
|
||||
|
||||
default: {
|
||||
throw new Error(`Unknown operation type: "${type}".`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -2,6 +2,7 @@ import Debug from 'debug'
|
||||
import pick from 'lodash/pick'
|
||||
|
||||
import Operation from '../models/operation'
|
||||
import PathUtils from '../utils/path-utils'
|
||||
|
||||
/**
|
||||
* Debug.
|
||||
@@ -23,98 +24,73 @@ function invertOperation(op) {
|
||||
const { type } = op
|
||||
debug(type, op)
|
||||
|
||||
/**
|
||||
* Insert node.
|
||||
*/
|
||||
|
||||
if (type == 'insert_node') {
|
||||
switch (type) {
|
||||
case 'insert_node': {
|
||||
const inverse = op.set('type', 'remove_node')
|
||||
return inverse
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove node.
|
||||
*/
|
||||
|
||||
if (type == 'remove_node') {
|
||||
case 'remove_node': {
|
||||
const inverse = op.set('type', 'insert_node')
|
||||
return inverse
|
||||
}
|
||||
|
||||
/**
|
||||
* Move node.
|
||||
*/
|
||||
|
||||
if (type == 'move_node') {
|
||||
case 'move_node': {
|
||||
const { newPath, path } = op
|
||||
let inversePath = newPath
|
||||
let inverseNewPath = path
|
||||
|
||||
const pathLast = path.length - 1
|
||||
const newPathLast = newPath.length - 1
|
||||
const pathLast = path.size - 1
|
||||
const newPathLast = newPath.size - 1
|
||||
|
||||
// If the node's old position was a left sibling of an ancestor of
|
||||
// its new position, we need to adjust part of the path by -1.
|
||||
if (
|
||||
path.length < inversePath.length &&
|
||||
path.slice(0, pathLast).every((e, i) => e == inversePath[i]) &&
|
||||
path[pathLast] < inversePath[pathLast]
|
||||
path.size < inversePath.size &&
|
||||
path.slice(0, pathLast).every((e, i) => e == inversePath.get(i)) &&
|
||||
path.last() < inversePath.get(pathLast)
|
||||
) {
|
||||
inversePath = inversePath
|
||||
.slice(0, pathLast)
|
||||
.concat([inversePath[pathLast] - 1])
|
||||
.concat(inversePath.slice(pathLast + 1, inversePath.length))
|
||||
.concat(inversePath.get(pathLast) - 1)
|
||||
.concat(inversePath.slice(pathLast + 1, inversePath.size))
|
||||
}
|
||||
|
||||
// If the node's new position is an ancestor of the old position,
|
||||
// or a left sibling of an ancestor of its old position, we need
|
||||
// to adjust part of the path by 1.
|
||||
if (
|
||||
newPath.length < inverseNewPath.length &&
|
||||
newPath.slice(0, newPathLast).every((e, i) => e == inverseNewPath[i]) &&
|
||||
newPath[newPathLast] <= inverseNewPath[newPathLast]
|
||||
newPath.size < inverseNewPath.size &&
|
||||
newPath
|
||||
.slice(0, newPathLast)
|
||||
.every((e, i) => e == inverseNewPath.get(i)) &&
|
||||
newPath.last() <= inverseNewPath.get(newPathLast)
|
||||
) {
|
||||
inverseNewPath = inverseNewPath
|
||||
.slice(0, newPathLast)
|
||||
.concat([inverseNewPath[newPathLast] + 1])
|
||||
.concat(inverseNewPath.slice(newPathLast + 1, inverseNewPath.length))
|
||||
.concat(inverseNewPath.get(newPathLast) + 1)
|
||||
.concat(inverseNewPath.slice(newPathLast + 1, inverseNewPath.size))
|
||||
}
|
||||
|
||||
const inverse = op.set('path', inversePath).set('newPath', inverseNewPath)
|
||||
return inverse
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge node.
|
||||
*/
|
||||
|
||||
if (type == 'merge_node') {
|
||||
case 'merge_node': {
|
||||
const { path } = op
|
||||
const { length } = path
|
||||
const last = length - 1
|
||||
const inversePath = path.slice(0, last).concat([path[last] - 1])
|
||||
const inversePath = PathUtils.decrement(path)
|
||||
const inverse = op.set('type', 'split_node').set('path', inversePath)
|
||||
return inverse
|
||||
}
|
||||
|
||||
/**
|
||||
* Split node.
|
||||
*/
|
||||
|
||||
if (type == 'split_node') {
|
||||
case 'split_node': {
|
||||
const { path } = op
|
||||
const { length } = path
|
||||
const last = length - 1
|
||||
const inversePath = path.slice(0, last).concat([path[last] + 1])
|
||||
const inversePath = PathUtils.increment(path)
|
||||
const inverse = op.set('type', 'merge_node').set('path', inversePath)
|
||||
return inverse
|
||||
}
|
||||
|
||||
/**
|
||||
* Set node.
|
||||
*/
|
||||
|
||||
if (type == 'set_node') {
|
||||
case 'set_node': {
|
||||
const { properties, node } = op
|
||||
const inverseNode = node.merge(properties)
|
||||
const inverseProperties = pick(node, Object.keys(properties))
|
||||
@@ -124,47 +100,27 @@ function invertOperation(op) {
|
||||
return inverse
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert text.
|
||||
*/
|
||||
|
||||
if (type == 'insert_text') {
|
||||
case 'insert_text': {
|
||||
const inverse = op.set('type', 'remove_text')
|
||||
return inverse
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove text.
|
||||
*/
|
||||
|
||||
if (type == 'remove_text') {
|
||||
case 'remove_text': {
|
||||
const inverse = op.set('type', 'insert_text')
|
||||
return inverse
|
||||
}
|
||||
|
||||
/**
|
||||
* Add mark.
|
||||
*/
|
||||
|
||||
if (type == 'add_mark') {
|
||||
case 'add_mark': {
|
||||
const inverse = op.set('type', 'remove_mark')
|
||||
return inverse
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove mark.
|
||||
*/
|
||||
|
||||
if (type == 'remove_mark') {
|
||||
case 'remove_mark': {
|
||||
const inverse = op.set('type', 'add_mark')
|
||||
return inverse
|
||||
}
|
||||
|
||||
/**
|
||||
* Set mark.
|
||||
*/
|
||||
|
||||
if (type == 'set_mark') {
|
||||
case 'set_mark': {
|
||||
const { properties, mark } = op
|
||||
const inverseMark = mark.merge(properties)
|
||||
const inverseProperties = pick(mark, Object.keys(properties))
|
||||
@@ -174,57 +130,17 @@ function invertOperation(op) {
|
||||
return inverse
|
||||
}
|
||||
|
||||
/**
|
||||
* Set selection.
|
||||
*/
|
||||
|
||||
if (type == 'set_selection') {
|
||||
const { properties, selection, value } = op
|
||||
const { anchorPath, focusPath, ...props } = properties
|
||||
const { document } = value
|
||||
|
||||
if (anchorPath !== undefined) {
|
||||
props.anchorKey =
|
||||
anchorPath === null ? null : document.assertPath(anchorPath).key
|
||||
}
|
||||
|
||||
if (focusPath !== undefined) {
|
||||
props.focusKey =
|
||||
focusPath === null ? null : document.assertPath(focusPath).key
|
||||
}
|
||||
|
||||
const inverseSelection = selection.merge(props)
|
||||
const inverseProps = pick(selection, Object.keys(props))
|
||||
|
||||
if (anchorPath !== undefined) {
|
||||
inverseProps.anchorPath =
|
||||
inverseProps.anchorKey === null
|
||||
? null
|
||||
: document.getPath(inverseProps.anchorKey)
|
||||
|
||||
delete inverseProps.anchorKey
|
||||
}
|
||||
|
||||
if (focusPath !== undefined) {
|
||||
inverseProps.focusPath =
|
||||
inverseProps.focusKey === null
|
||||
? null
|
||||
: document.getPath(inverseProps.focusKey)
|
||||
|
||||
delete inverseProps.focusKey
|
||||
}
|
||||
|
||||
case 'set_selection': {
|
||||
const { properties, selection } = op
|
||||
const inverseSelection = selection.merge(properties)
|
||||
const inverseProps = pick(selection, Object.keys(properties))
|
||||
const inverse = op
|
||||
.set('selection', inverseSelection)
|
||||
.set('properties', inverseProps)
|
||||
return inverse
|
||||
}
|
||||
|
||||
/**
|
||||
* Set value.
|
||||
*/
|
||||
|
||||
if (type == 'set_value') {
|
||||
case 'set_value': {
|
||||
const { properties, value } = op
|
||||
const inverseValue = value.merge(properties)
|
||||
const inverseProperties = pick(value, Object.keys(properties))
|
||||
@@ -233,6 +149,11 @@ function invertOperation(op) {
|
||||
.set('properties', inverseProperties)
|
||||
return inverse
|
||||
}
|
||||
|
||||
default: {
|
||||
throw new Error(`Unknown operation type: "${type}".`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -1,59 +1,32 @@
|
||||
/**
|
||||
* An auto-incrementing index for generating keys.
|
||||
*
|
||||
* @type {Number}
|
||||
*/
|
||||
|
||||
let n
|
||||
|
||||
/**
|
||||
* The global key generating function.
|
||||
*
|
||||
* @type {Function}
|
||||
*/
|
||||
|
||||
let generate
|
||||
|
||||
/**
|
||||
* Generate a key.
|
||||
*
|
||||
* @return {String}
|
||||
*/
|
||||
import KeyUtils from './key-utils'
|
||||
import logger from 'slate-dev-logger'
|
||||
|
||||
function generateKey() {
|
||||
return generate()
|
||||
logger.deprecate(
|
||||
`0.35.0`,
|
||||
'The `generateKey()` util is deprecrated. Use the `KeyUtils.create()` helper instead.'
|
||||
)
|
||||
|
||||
return KeyUtils.create()
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a different unique ID generating `function`.
|
||||
*
|
||||
* @param {Function} func
|
||||
*/
|
||||
function setKeyGenerator(fn) {
|
||||
logger.deprecate(
|
||||
`0.35.0`,
|
||||
'The `setKeyGenerator()` util is deprecrated. Use the `KeyUtils.setGenerator()` helper instead.'
|
||||
)
|
||||
|
||||
function setKeyGenerator(func) {
|
||||
generate = func
|
||||
return KeyUtils.setGenerator(fn)
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the key generating function to its initial state.
|
||||
*/
|
||||
|
||||
function resetKeyGenerator() {
|
||||
n = 0
|
||||
generate = () => `${n++}`
|
||||
logger.deprecate(
|
||||
`0.35.0`,
|
||||
'The `resetKeyGenerator()` util is deprecrated. Use the `KeyUtils.resetGenerator()` helper instead.'
|
||||
)
|
||||
|
||||
return KeyUtils.resetGenerator()
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the initial state.
|
||||
*/
|
||||
|
||||
resetKeyGenerator()
|
||||
|
||||
/**
|
||||
* Export.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
|
||||
export default generateKey
|
||||
export { setKeyGenerator, resetKeyGenerator }
|
||||
|
@@ -1,18 +0,0 @@
|
||||
/**
|
||||
* Check if an `object` is a React component.
|
||||
*
|
||||
* @param {Object} object
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
function isReactComponent(object) {
|
||||
return object && object.prototype && object.prototype.isReactComponent
|
||||
}
|
||||
|
||||
/**
|
||||
* Export.
|
||||
*
|
||||
* @type {Function}
|
||||
*/
|
||||
|
||||
export default isReactComponent
|
71
packages/slate/src/utils/key-utils.js
Normal file
71
packages/slate/src/utils/key-utils.js
Normal file
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* An auto-incrementing index for generating keys.
|
||||
*
|
||||
* @type {Number}
|
||||
*/
|
||||
|
||||
let n
|
||||
|
||||
/**
|
||||
* The global key generating function.
|
||||
*
|
||||
* @type {Function}
|
||||
*/
|
||||
|
||||
let generate
|
||||
|
||||
/**
|
||||
* Create a key, using a provided key if available.
|
||||
*
|
||||
* @param {String|Void} key
|
||||
* @return {String}
|
||||
*/
|
||||
|
||||
function create(key) {
|
||||
if (key == null) {
|
||||
return generate()
|
||||
}
|
||||
|
||||
if (typeof key === 'string') {
|
||||
return key
|
||||
}
|
||||
|
||||
throw new Error(`Keys must be strings, but you passed: ${key}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a different unique ID generating `function`.
|
||||
*
|
||||
* @param {Function} func
|
||||
*/
|
||||
|
||||
function setGenerator(func) {
|
||||
generate = func
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the key generating function to its initial state.
|
||||
*/
|
||||
|
||||
function resetGenerator() {
|
||||
n = 0
|
||||
generate = () => `${n++}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the initial state.
|
||||
*/
|
||||
|
||||
resetGenerator()
|
||||
|
||||
/**
|
||||
* Export.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
|
||||
export default {
|
||||
create,
|
||||
setGenerator,
|
||||
resetGenerator,
|
||||
}
|
217
packages/slate/src/utils/path-utils.js
Normal file
217
packages/slate/src/utils/path-utils.js
Normal file
@@ -0,0 +1,217 @@
|
||||
import { List } from 'immutable'
|
||||
|
||||
/**
|
||||
* Compare paths `a` and `b` to see which is before or after.
|
||||
*
|
||||
* @param {List} a
|
||||
* @param {List} b
|
||||
* @return {Number|Null}
|
||||
*/
|
||||
|
||||
function compare(a, b) {
|
||||
// PERF: if the paths are the same we can exit early.
|
||||
if (a.size !== b.size) return null
|
||||
|
||||
for (let i = 0; i < a.size; i++) {
|
||||
const av = a.get(i)
|
||||
const bv = b.get(i)
|
||||
|
||||
// If a's value is ever less than b's, it's before.
|
||||
if (av < bv) return -1
|
||||
|
||||
// If b's value is ever less than a's, it's after.
|
||||
if (av > bv) return 1
|
||||
}
|
||||
|
||||
// Otherwise they were equal the whole way, it's the same.
|
||||
return 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a path from `attrs`.
|
||||
*
|
||||
* @param {Array|List} attrs
|
||||
* @return {List}
|
||||
*/
|
||||
|
||||
function create(attrs) {
|
||||
if (attrs == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (List.isList(attrs)) {
|
||||
return attrs
|
||||
}
|
||||
|
||||
if (Array.isArray(attrs)) {
|
||||
return List(attrs)
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`Paths can only be created from arrays or lists, but you passed: ${attrs}`
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Crop paths `a` and `b` to an equal size, defaulting to the shortest.
|
||||
*
|
||||
* @param {List} a
|
||||
* @param {List} b
|
||||
*/
|
||||
|
||||
function crop(a, b, size = min(a, b)) {
|
||||
const ca = a.slice(0, size)
|
||||
const cb = b.slice(0, size)
|
||||
return [ca, cb]
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrement a `path` by `n` at `index`, defaulting to the last index.
|
||||
*
|
||||
* @param {List} path
|
||||
* @param {Number} n
|
||||
* @param {Number} index
|
||||
*/
|
||||
|
||||
function decrement(path, n = 1, index = path.size - 1) {
|
||||
return increment(path, 0 - n, index)
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment a `path` by `n` at `index`, defaulting to the last index.
|
||||
*
|
||||
* @param {List} path
|
||||
* @param {Number} n
|
||||
* @param {Number} index
|
||||
*/
|
||||
|
||||
function increment(path, n = 1, index = path.size - 1) {
|
||||
const value = path.get(index)
|
||||
const newValue = value + n
|
||||
const newPath = path.set(index, newValue)
|
||||
return newPath
|
||||
}
|
||||
|
||||
/**
|
||||
* Is a `path` above another `target` path?
|
||||
*
|
||||
* @param {List} path
|
||||
* @param {List} target
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
function isAbove(path, target) {
|
||||
const [p, t] = crop(path, target)
|
||||
return path.size < target.size && compare(p, t) === 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Is a `path` after another `target` path in a document?
|
||||
*
|
||||
* @param {List} path
|
||||
* @param {List} target
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
function isAfter(path, target) {
|
||||
const [p, t] = crop(path, target)
|
||||
return compare(p, t) === 1
|
||||
}
|
||||
|
||||
/**
|
||||
* Is a `path` before another `target` path in a document?
|
||||
*
|
||||
* @param {List} path
|
||||
* @param {List} target
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
function isBefore(path, target) {
|
||||
const [p, t] = crop(path, target)
|
||||
return compare(p, t) === -1
|
||||
}
|
||||
|
||||
/**
|
||||
* Lift a `path` to refer to its parent.
|
||||
*
|
||||
* @param {List} path
|
||||
* @return {Array}
|
||||
*/
|
||||
|
||||
function lift(path) {
|
||||
const parent = path.slice(0, -1)
|
||||
return parent
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the maximum length of paths `a` and `b`.
|
||||
*
|
||||
* @param {List} path
|
||||
* @param {List} path
|
||||
* @return {Number}
|
||||
*/
|
||||
|
||||
function max(a, b) {
|
||||
const n = Math.max(a.size, b.size)
|
||||
return n
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the minimum length of paths `a` and `b`.
|
||||
*
|
||||
* @param {List} path
|
||||
* @param {List} path
|
||||
* @return {Number}
|
||||
*/
|
||||
|
||||
function min(a, b) {
|
||||
const n = Math.min(a.size, b.size)
|
||||
return n
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the common ancestor path of path `a` and path `b`.
|
||||
*
|
||||
* @param {List} a
|
||||
* @param {List} b
|
||||
* @return {List}
|
||||
*/
|
||||
|
||||
function relate(a, b) {
|
||||
const array = []
|
||||
|
||||
for (let i = 0; i < a.size && i < b.size; i++) {
|
||||
const av = a.get(i)
|
||||
const bv = b.get(i)
|
||||
|
||||
// If the values aren't equal, they've diverged and don't share an ancestor.
|
||||
if (av !== bv) break
|
||||
|
||||
// Otherwise, the current value is still a common ancestor.
|
||||
array.push(av)
|
||||
}
|
||||
|
||||
const path = create(array)
|
||||
return path
|
||||
}
|
||||
|
||||
/**
|
||||
* Export.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
|
||||
export default {
|
||||
compare,
|
||||
create,
|
||||
crop,
|
||||
decrement,
|
||||
increment,
|
||||
isAbove,
|
||||
isAfter,
|
||||
isBefore,
|
||||
lift,
|
||||
max,
|
||||
min,
|
||||
relate,
|
||||
}
|
@@ -188,8 +188,13 @@ function getWordOffsetForward(text, offset) {
|
||||
*/
|
||||
|
||||
export default {
|
||||
getCharOffsetForward,
|
||||
getCharLength,
|
||||
getCharOffset,
|
||||
getCharOffsetBackward,
|
||||
getCharOffsetForward,
|
||||
getWordOffset,
|
||||
getWordOffsetBackward,
|
||||
getWordOffsetForward,
|
||||
isSurrogate,
|
||||
isWord,
|
||||
}
|
@@ -2,7 +2,7 @@
|
||||
* Dependencies.
|
||||
*/
|
||||
|
||||
import { resetKeyGenerator } from '..'
|
||||
import { KeyUtils } from '..'
|
||||
|
||||
/**
|
||||
* Tests.
|
||||
@@ -22,5 +22,5 @@ describe('slate', () => {
|
||||
*/
|
||||
|
||||
beforeEach(() => {
|
||||
resetKeyGenerator()
|
||||
KeyUtils.resetGenerator()
|
||||
})
|
||||
|
@@ -38,7 +38,9 @@ export const output = (
|
||||
<value>
|
||||
<document>
|
||||
<list>
|
||||
<item />
|
||||
<item>
|
||||
<paragraph />
|
||||
</item>
|
||||
</list>
|
||||
</document>
|
||||
</value>
|
||||
|
@@ -6,8 +6,8 @@ export default [
|
||||
{
|
||||
type: 'remove_text',
|
||||
path: [0, 0],
|
||||
offset: 2,
|
||||
text: 'is is some text inside ',
|
||||
offset: 1,
|
||||
text: 'or',
|
||||
marks: [],
|
||||
},
|
||||
]
|
||||
@@ -16,7 +16,7 @@ export const input = (
|
||||
<value>
|
||||
<document>
|
||||
<paragraph>
|
||||
This <cursor />is some text inside a paragraph.
|
||||
wor<anchor />d<focus />
|
||||
</paragraph>
|
||||
</document>
|
||||
</value>
|
||||
@@ -26,7 +26,7 @@ export const output = (
|
||||
<value>
|
||||
<document>
|
||||
<paragraph>
|
||||
Th<cursor />a paragraph.
|
||||
w<anchor />d<focus />
|
||||
</paragraph>
|
||||
</document>
|
||||
</value>
|
@@ -6,8 +6,8 @@ export default [
|
||||
{
|
||||
type: 'remove_text',
|
||||
path: [0, 0],
|
||||
offset: 2,
|
||||
text: ' there',
|
||||
offset: 1,
|
||||
text: 'or',
|
||||
marks: [],
|
||||
},
|
||||
]
|
||||
@@ -16,7 +16,7 @@ export const input = (
|
||||
<value>
|
||||
<document>
|
||||
<paragraph>
|
||||
Hi<cursor /> there <highlight>you</highlight> person
|
||||
w<anchor />ord<focus />
|
||||
</paragraph>
|
||||
</document>
|
||||
</value>
|
||||
@@ -26,7 +26,7 @@ export const output = (
|
||||
<value>
|
||||
<document>
|
||||
<paragraph>
|
||||
Hi<cursor /> <highlight>you</highlight> person
|
||||
w<anchor />d<focus />
|
||||
</paragraph>
|
||||
</document>
|
||||
</value>
|
@@ -0,0 +1,33 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../../../helpers/h'
|
||||
|
||||
export default [
|
||||
{
|
||||
type: 'remove_text',
|
||||
path: [0, 0],
|
||||
offset: 1,
|
||||
text: 'or',
|
||||
marks: [],
|
||||
},
|
||||
]
|
||||
|
||||
export const input = (
|
||||
<value>
|
||||
<document>
|
||||
<paragraph>
|
||||
wo<anchor />rd<focus />
|
||||
</paragraph>
|
||||
</document>
|
||||
</value>
|
||||
)
|
||||
|
||||
export const output = (
|
||||
<value>
|
||||
<document>
|
||||
<paragraph>
|
||||
w<anchor />d<focus />
|
||||
</paragraph>
|
||||
</document>
|
||||
</value>
|
||||
)
|
@@ -0,0 +1,33 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../../../helpers/h'
|
||||
|
||||
export default [
|
||||
{
|
||||
type: 'remove_text',
|
||||
path: [0, 0],
|
||||
offset: 1,
|
||||
text: 'or',
|
||||
marks: [],
|
||||
},
|
||||
]
|
||||
|
||||
export const input = (
|
||||
<value>
|
||||
<document>
|
||||
<paragraph>
|
||||
wor<cursor />d
|
||||
</paragraph>
|
||||
</document>
|
||||
</value>
|
||||
)
|
||||
|
||||
export const output = (
|
||||
<value>
|
||||
<document>
|
||||
<paragraph>
|
||||
w<cursor />d
|
||||
</paragraph>
|
||||
</document>
|
||||
</value>
|
||||
)
|
@@ -0,0 +1,33 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../../../helpers/h'
|
||||
|
||||
export default [
|
||||
{
|
||||
type: 'remove_text',
|
||||
path: [0, 0],
|
||||
offset: 1,
|
||||
text: 'or',
|
||||
marks: [],
|
||||
},
|
||||
]
|
||||
|
||||
export const input = (
|
||||
<value>
|
||||
<document>
|
||||
<paragraph>
|
||||
w<cursor />ord
|
||||
</paragraph>
|
||||
</document>
|
||||
</value>
|
||||
)
|
||||
|
||||
export const output = (
|
||||
<value>
|
||||
<document>
|
||||
<paragraph>
|
||||
w<cursor />d
|
||||
</paragraph>
|
||||
</document>
|
||||
</value>
|
||||
)
|
@@ -0,0 +1,33 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../../../helpers/h'
|
||||
|
||||
export default [
|
||||
{
|
||||
type: 'remove_text',
|
||||
path: [0, 0],
|
||||
offset: 1,
|
||||
text: 'or',
|
||||
marks: [],
|
||||
},
|
||||
]
|
||||
|
||||
export const input = (
|
||||
<value>
|
||||
<document>
|
||||
<paragraph>
|
||||
wo<cursor />rd
|
||||
</paragraph>
|
||||
</document>
|
||||
</value>
|
||||
)
|
||||
|
||||
export const output = (
|
||||
<value>
|
||||
<document>
|
||||
<paragraph>
|
||||
w<cursor />d
|
||||
</paragraph>
|
||||
</document>
|
||||
</value>
|
||||
)
|
@@ -0,0 +1,33 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../../../helpers/h'
|
||||
|
||||
export default [
|
||||
{
|
||||
type: 'remove_text',
|
||||
path: [0, 0],
|
||||
offset: 1,
|
||||
text: 'or',
|
||||
marks: [],
|
||||
},
|
||||
]
|
||||
|
||||
export const input = (
|
||||
<value>
|
||||
<document>
|
||||
<paragraph>
|
||||
wor<highlight>d</highlight>
|
||||
</paragraph>
|
||||
</document>
|
||||
</value>
|
||||
)
|
||||
|
||||
export const output = (
|
||||
<value>
|
||||
<document>
|
||||
<paragraph>
|
||||
w<highlight>d</highlight>
|
||||
</paragraph>
|
||||
</document>
|
||||
</value>
|
||||
)
|
@@ -0,0 +1,33 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../../../helpers/h'
|
||||
|
||||
export default [
|
||||
{
|
||||
type: 'remove_text',
|
||||
path: [0, 0],
|
||||
offset: 1,
|
||||
text: 'or',
|
||||
marks: [],
|
||||
},
|
||||
]
|
||||
|
||||
export const input = (
|
||||
<value>
|
||||
<document>
|
||||
<paragraph>
|
||||
wor<highlight atomic>d</highlight>
|
||||
</paragraph>
|
||||
</document>
|
||||
</value>
|
||||
)
|
||||
|
||||
export const output = (
|
||||
<value>
|
||||
<document>
|
||||
<paragraph>
|
||||
w<highlight atomic>d</highlight>
|
||||
</paragraph>
|
||||
</document>
|
||||
</value>
|
||||
)
|
@@ -0,0 +1,33 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../../../helpers/h'
|
||||
|
||||
export default [
|
||||
{
|
||||
type: 'remove_text',
|
||||
path: [0, 0],
|
||||
offset: 1,
|
||||
text: 'or',
|
||||
marks: [],
|
||||
},
|
||||
]
|
||||
|
||||
export const input = (
|
||||
<value>
|
||||
<document>
|
||||
<paragraph>
|
||||
<highlight atomic>w</highlight>ord
|
||||
</paragraph>
|
||||
</document>
|
||||
</value>
|
||||
)
|
||||
|
||||
export const output = (
|
||||
<value>
|
||||
<document>
|
||||
<paragraph>
|
||||
<highlight atomic>w</highlight>d
|
||||
</paragraph>
|
||||
</document>
|
||||
</value>
|
||||
)
|
@@ -0,0 +1,31 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../../../helpers/h'
|
||||
|
||||
export default [
|
||||
{
|
||||
type: 'remove_text',
|
||||
path: [0, 0],
|
||||
offset: 1,
|
||||
text: 'o',
|
||||
marks: [],
|
||||
},
|
||||
]
|
||||
|
||||
export const input = (
|
||||
<value>
|
||||
<document>
|
||||
<paragraph>
|
||||
w<highlight atomic>or</highlight>d
|
||||
</paragraph>
|
||||
</document>
|
||||
</value>
|
||||
)
|
||||
|
||||
export const output = (
|
||||
<value>
|
||||
<document>
|
||||
<paragraph>wrd</paragraph>
|
||||
</document>
|
||||
</value>
|
||||
)
|
@@ -0,0 +1,33 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../../../helpers/h'
|
||||
|
||||
export default [
|
||||
{
|
||||
type: 'remove_text',
|
||||
path: [0, 0],
|
||||
offset: 1,
|
||||
text: 'or',
|
||||
marks: [],
|
||||
},
|
||||
]
|
||||
|
||||
export const input = (
|
||||
<value>
|
||||
<document>
|
||||
<paragraph>
|
||||
<highlight>w</highlight>ord
|
||||
</paragraph>
|
||||
</document>
|
||||
</value>
|
||||
)
|
||||
|
||||
export const output = (
|
||||
<value>
|
||||
<document>
|
||||
<paragraph>
|
||||
<highlight>w</highlight>d
|
||||
</paragraph>
|
||||
</document>
|
||||
</value>
|
||||
)
|
@@ -0,0 +1,33 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../../../helpers/h'
|
||||
|
||||
export default [
|
||||
{
|
||||
type: 'remove_text',
|
||||
path: [0, 0],
|
||||
offset: 1,
|
||||
text: 'o',
|
||||
marks: [],
|
||||
},
|
||||
]
|
||||
|
||||
export const input = (
|
||||
<value>
|
||||
<document>
|
||||
<paragraph>
|
||||
w<highlight>or</highlight>d
|
||||
</paragraph>
|
||||
</document>
|
||||
</value>
|
||||
)
|
||||
|
||||
export const output = (
|
||||
<value>
|
||||
<document>
|
||||
<paragraph>
|
||||
w<highlight>r</highlight>d
|
||||
</paragraph>
|
||||
</document>
|
||||
</value>
|
||||
)
|
@@ -0,0 +1,33 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../../../helpers/h'
|
||||
|
||||
export default [
|
||||
{
|
||||
type: 'remove_text',
|
||||
path: [0, 0],
|
||||
offset: 1,
|
||||
text: 'or',
|
||||
marks: [],
|
||||
},
|
||||
]
|
||||
|
||||
export const input = (
|
||||
<value>
|
||||
<document>
|
||||
<paragraph>
|
||||
<anchor />wor<focus />d
|
||||
</paragraph>
|
||||
</document>
|
||||
</value>
|
||||
)
|
||||
|
||||
export const output = (
|
||||
<value>
|
||||
<document>
|
||||
<paragraph>
|
||||
<anchor />w<focus />d
|
||||
</paragraph>
|
||||
</document>
|
||||
</value>
|
||||
)
|
@@ -0,0 +1,33 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../../../helpers/h'
|
||||
|
||||
export default [
|
||||
{
|
||||
type: 'remove_text',
|
||||
path: [0, 0],
|
||||
offset: 1,
|
||||
text: 'or',
|
||||
marks: [],
|
||||
},
|
||||
]
|
||||
|
||||
export const input = (
|
||||
<value>
|
||||
<document>
|
||||
<paragraph>
|
||||
<anchor />w<focus />ord
|
||||
</paragraph>
|
||||
</document>
|
||||
</value>
|
||||
)
|
||||
|
||||
export const output = (
|
||||
<value>
|
||||
<document>
|
||||
<paragraph>
|
||||
<anchor />w<focus />d
|
||||
</paragraph>
|
||||
</document>
|
||||
</value>
|
||||
)
|
@@ -0,0 +1,33 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../../../helpers/h'
|
||||
|
||||
export default [
|
||||
{
|
||||
type: 'remove_text',
|
||||
path: [0, 0],
|
||||
offset: 1,
|
||||
text: 'or',
|
||||
marks: [],
|
||||
},
|
||||
]
|
||||
|
||||
export const input = (
|
||||
<value>
|
||||
<document>
|
||||
<paragraph>
|
||||
<anchor />wo<focus />rd
|
||||
</paragraph>
|
||||
</document>
|
||||
</value>
|
||||
)
|
||||
|
||||
export const output = (
|
||||
<value>
|
||||
<document>
|
||||
<paragraph>
|
||||
<anchor />w<focus />d
|
||||
</paragraph>
|
||||
</document>
|
||||
</value>
|
||||
)
|
@@ -1,61 +0,0 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../../../helpers/h'
|
||||
|
||||
export default [
|
||||
{
|
||||
type: 'remove_text',
|
||||
path: [0, 0],
|
||||
offset: 13,
|
||||
text: 'on',
|
||||
marks: [],
|
||||
},
|
||||
{
|
||||
type: 'remove_text',
|
||||
path: [1, 0],
|
||||
offset: 0,
|
||||
text: 'This ',
|
||||
marks: [],
|
||||
},
|
||||
{
|
||||
type: 'remove_text',
|
||||
path: [2, 0],
|
||||
offset: 10,
|
||||
text: 'ation',
|
||||
marks: [],
|
||||
},
|
||||
]
|
||||
|
||||
export const input = (
|
||||
<value>
|
||||
<document>
|
||||
<paragraph>
|
||||
This <highlight atomic>decoration</highlight> should be invalid,{' '}
|
||||
<highlight atomic>this</highlight> one shouldn't.
|
||||
</paragraph>
|
||||
<paragraph>
|
||||
This <highlight atomic>decoration</highlight> will be fine.
|
||||
</paragraph>
|
||||
<paragraph>
|
||||
This <highlight>decoration</highlight> can be altered, since non-atomic.
|
||||
</paragraph>
|
||||
</document>
|
||||
</value>
|
||||
)
|
||||
|
||||
export const output = (
|
||||
<value>
|
||||
<document>
|
||||
<paragraph>
|
||||
This decorati should be invalid, <highlight atomic>this</highlight> one
|
||||
shouldn't.
|
||||
</paragraph>
|
||||
<paragraph>
|
||||
<highlight atomic>decoration</highlight> will be fine.
|
||||
</paragraph>
|
||||
<paragraph>
|
||||
This <highlight>decor</highlight> can be altered, since non-atomic.
|
||||
</paragraph>
|
||||
</document>
|
||||
</value>
|
||||
)
|
@@ -1,61 +0,0 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../../../helpers/h'
|
||||
|
||||
export default [
|
||||
{
|
||||
type: 'insert_text',
|
||||
path: [0, 0],
|
||||
offset: 6,
|
||||
text: 'x',
|
||||
marks: [],
|
||||
},
|
||||
{
|
||||
type: 'insert_text',
|
||||
path: [1, 0],
|
||||
offset: 5,
|
||||
text: 'small ',
|
||||
marks: [],
|
||||
},
|
||||
{
|
||||
type: 'remove_text',
|
||||
path: [2, 0],
|
||||
offset: 10,
|
||||
text: 'ation',
|
||||
marks: [],
|
||||
},
|
||||
]
|
||||
|
||||
export const input = (
|
||||
<value>
|
||||
<document>
|
||||
<paragraph>
|
||||
This <highlight atomic>decoration</highlight> should be invalid,{' '}
|
||||
<highlight atomic>this</highlight> one shouldn't.
|
||||
</paragraph>
|
||||
<paragraph>
|
||||
This <highlight atomic>decoration</highlight> will be fine.
|
||||
</paragraph>
|
||||
<paragraph>
|
||||
This <highlight>decoration</highlight> can be altered, since non-atomic.
|
||||
</paragraph>
|
||||
</document>
|
||||
</value>
|
||||
)
|
||||
|
||||
export const output = (
|
||||
<value>
|
||||
<document>
|
||||
<paragraph>
|
||||
This dxecoration should be invalid, <highlight atomic>this</highlight>{' '}
|
||||
one shouldn't.
|
||||
</paragraph>
|
||||
<paragraph>
|
||||
This small <highlight atomic>decoration</highlight> will be fine.
|
||||
</paragraph>
|
||||
<paragraph>
|
||||
This <highlight>decor</highlight> can be altered, since non-atomic.
|
||||
</paragraph>
|
||||
</document>
|
||||
</value>
|
||||
)
|
@@ -1,6 +1,6 @@
|
||||
import assert from 'assert'
|
||||
import fs from 'fs-promise' // eslint-disable-line import/no-extraneous-dependencies
|
||||
import toCamel from 'to-camel-case' // eslint-disable-line import/no-extraneous-dependencies
|
||||
import toSnake from 'to-snake-case' // eslint-disable-line import/no-extraneous-dependencies
|
||||
import { basename, extname, resolve } from 'path'
|
||||
|
||||
/**
|
||||
@@ -19,7 +19,7 @@ describe('operations', async () => {
|
||||
const methods = fs.readdirSync(categoryDir).filter(c => c[0] != '.')
|
||||
|
||||
for (const method of methods) {
|
||||
describe(toCamel(method), () => {
|
||||
describe(toSnake(method), () => {
|
||||
const testDir = resolve(categoryDir, method)
|
||||
const tests = fs
|
||||
.readdirSync(testDir)
|
||||
@@ -40,7 +40,6 @@ describe('operations', async () => {
|
||||
}
|
||||
const actual = change.value.toJSON(opts)
|
||||
const expected = output.toJSON(opts)
|
||||
|
||||
assert.deepEqual(actual, expected)
|
||||
})
|
||||
}
|
||||
|
@@ -42,8 +42,10 @@ export const output = {
|
||||
selection: {
|
||||
object: 'range',
|
||||
anchorKey: '0',
|
||||
anchorPath: [0, 0],
|
||||
anchorOffset: 0,
|
||||
focusKey: '0',
|
||||
focusPath: [0, 0],
|
||||
focusOffset: 0,
|
||||
isBackward: false,
|
||||
isFocused: false,
|
||||
|
@@ -8007,6 +8007,12 @@ to-regex@^3.0.1:
|
||||
extend-shallow "^2.0.1"
|
||||
regex-not "^1.0.0"
|
||||
|
||||
to-snake-case@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/to-snake-case/-/to-snake-case-1.0.0.tgz#ce746913897946019a87e62edfaeaea4c608ab8c"
|
||||
dependencies:
|
||||
to-space-case "^1.0.0"
|
||||
|
||||
to-space-case@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/to-space-case/-/to-space-case-1.0.0.tgz#b052daafb1b2b29dc770cea0163e5ec0ebc9fc17"
|
||||
|
Reference in New Issue
Block a user