diff --git a/.eslintrc b/.eslintrc index e500b31b1..e01221b3f 100644 --- a/.eslintrc +++ b/.eslintrc @@ -21,7 +21,7 @@ "arrow-parens": ["error", "as-needed", { "requireForBlockBody": true }], "arrow-spacing": "error", "block-spacing": "error", - "capitalized-comments": ["error", "always", { "ignoreConsecutiveComments": true }], + "capitalized-comments": ["error", "always", { "ignoreConsecutiveComments": true, "ignoreInlineComments": true }], "comma-dangle": ["error", "only-multiline"], "comma-spacing": ["error", { "before": false, "after": true }], "comma-style": ["error", "last"], diff --git a/History.md b/History.md index 7cc13d349..6fba742b6 100644 --- a/History.md +++ b/History.md @@ -2,6 +2,29 @@ This document maintains a list of changes to Slate with each new version. Until `1.0.0` is released, breaking changes will be added as minor version bumps, and non-breaking changes won't be accounted for since the library is moving quickly. +--- + +### `0.19.0` — March 3, 2017 + +###### BREAKING CHANGES + +- **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. + +###### DEPRECATION CHANGES + +- **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` + + --- @@ -19,7 +42,7 @@ This document maintains a list of changes to Slate with each new version. Until ###### DEPRECATION CHANGES -- **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` diff --git a/src/models/node.js b/src/models/node.js index 8ec177ef3..c3f7099ad 100644 --- a/src/models/node.js +++ b/src/models/node.js @@ -1,13 +1,11 @@ -import Block from './block' -import Character from './character' import Document from './document' -import Mark from './mark' import Normalize from '../utils/normalize' import direction from 'direction' +import generateKey from '../utils/generate-key' import isInRange from '../utils/is-in-range' import memoize from '../utils/memoize' -import generateKey from '../utils/generate-key' +import warn from '../utils/warn' import { List, Set } from 'immutable' /** @@ -22,31 +20,31 @@ import { List, Set } from 'immutable' const Node = { /** - * Return a set of all keys in the node. + * True if the node has both descendants in that order, false otherwise. The + * order is depth-first, post-order. * - * @return {Set} + * @param {String} first + * @param {String} second + * @return {Boolean} */ - getKeys() { - const keys = [] + areDescendantsSorted(first, second) { + first = Normalize.key(first) + second = Normalize.key(second) - this.forEachDescendant((desc) => { - keys.push(desc.key) + let sorted + + this.forEachDescendant((n) => { + if (n.key === first) { + sorted = true + return false + } else if (n.key === second) { + sorted = false + return false + } }) - return Set(keys) - }, - - /** - * Get the concatenated text `string` of all child nodes. - * - * @return {String} - */ - - getText() { - return this.nodes.reduce((result, node) => { - return result + node.text - }, '') + return sorted }, /** @@ -121,69 +119,34 @@ const Node = { }, /** - * Concat children `nodes` on to the end of the node. + * Recursively filter all descendant nodes with `iterator`. * - * @param {List} nodes - * @return {Node} + * @param {Function} iterator + * @return {List} */ - concatChildren(nodes) { - nodes = this.nodes.concat(nodes) - return this.merge({ nodes }) - }, + filterDescendants(iterator) { + const matches = [] - /** - * Decorate all of the text nodes with a `decorator` function. - * - * @param {Function} decorator - * @return {Node} - */ - - decorateTexts(decorator) { - return this.mapDescendants((child) => { - return child.kind == 'text' - ? child.decorateCharacters(decorator) - : child + this.forEachDescendant((node, i, nodes) => { + if (iterator(node, i, nodes)) matches.push(node) }) + + return List(matches) }, /** - * Recursively find all descendant nodes by `iterator`. Breadth first. + * Recursively find all descendant nodes by `iterator`. * * @param {Function} iterator * @return {Node|Null} */ findDescendant(iterator) { - const childFound = this.nodes.find(iterator) - if (childFound) return childFound - - let descendantFound = null - - this.nodes.find((node) => { - if (node.kind != 'text') { - descendantFound = node.findDescendant(iterator) - return descendantFound - } else { - return false - } - }) - - return descendantFound - }, - - /** - * Recursively find all descendant nodes by `iterator`. Depth first. - * - * @param {Function} iterator - * @return {Node|Null} - */ - - findDescendantDeep(iterator) { let found - this.forEachDescendant((node) => { - if (iterator(node)) { + this.forEachDescendant((node, i, nodes) => { + if (iterator(node, i, nodes)) { found = node return false } @@ -193,13 +156,13 @@ const Node = { }, /** - * Recursively iterate over all descendant nodes with `iterator`. + * Recursively iterate over all descendant nodes with `iterator`. If the + * iterator returns false it will break the loop. * * @param {Function} iterator */ forEachDescendant(iterator) { - // If the iterator returns false it will break the loop. let ret this.nodes.forEach((child, i, nodes) => { @@ -218,36 +181,30 @@ const Node = { }, /** - * Recursively filter all descendant nodes with `iterator`. + * Get the path of ancestors of a descendant node by `key`. * - * @param {Function} iterator - * @return {List} + * @param {String|Node} key + * @return {List|Null} */ - filterDescendants(iterator) { - const matches = [] + getAncestors(key) { + key = Normalize.key(key) - this.forEachDescendant((child, i, nodes) => { - if (iterator(child, i, nodes)) matches.push(child) + if (key == this.key) return List() + if (this.hasChild(key)) return List([this]) + + let ancestors + this.nodes.find((node) => { + if (node.kind == 'text') return false + ancestors = node.getAncestors(key) + return ancestors }) - return List(matches) - }, - - /** - * Recursively filter all descendant nodes with `iterator`, depth-first. - * It is different from `filterDescendants` in regard of the order of results. - * - * @param {Function} iterator - * @return {List} - */ - - filterDescendantsDeep(iterator) { - return this.nodes.reduce((matches, child, i, nodes) => { - if (child.kind != 'text') matches = matches.concat(child.filterDescendantsDeep(iterator)) - if (iterator(child, i, nodes)) matches = matches.push(child) - return matches - }, Block.createList()) + if (ancestors) { + return ancestors.unshift(this) + } else { + return null + } }, /** @@ -260,7 +217,7 @@ const Node = { return this .getTexts() .map(text => this.getClosestBlock(text.key)) - // Eliminate duplicates + // Eliminate duplicates by converting to a `Set` first. .toOrderedSet() .toList() }, @@ -276,16 +233,28 @@ const Node = { return this .getTextsAtRange(range) .map(text => this.getClosestBlock(text.key)) - // Eliminate duplicates + // Eliminate duplicates by converting to a `Set` first. .toOrderedSet() .toList() }, + /** + * Get all of the characters for every text node. + * + * @return {List} characters + */ + + getCharacters() { + return this + .getTexts() + .reduce((chars, text) => chars.concat(text.characters), new List()) + }, + /** * Get a list of the characters in a `range`. * * @param {Selection} range - * @return {List} characters + * @return {List} characters */ getCharactersAtRange(range) { @@ -294,39 +263,19 @@ const Node = { .reduce((characters, text) => { const chars = text.characters.filter((char, i) => isInRange(i, text, range)) return characters.concat(chars) - }, Character.createList()) + }, new List()) }, /** - * Get children between two child keys. + * Get a child node by `key`. * - * @param {String} start - * @param {String} end - * @return {Node} + * @param {String} key + * @return {Node|Null} */ - getChildrenBetween(start, end) { - start = this.assertChild(start) - start = this.nodes.indexOf(start) - end = this.assertChild(end) - end = this.nodes.indexOf(end) - return this.nodes.slice(start + 1, end) - }, - - /** - * Get children between two child keys, including the two children. - * - * @param {String} start - * @param {String} end - * @return {Node} - */ - - getChildrenBetweenIncluding(start, end) { - start = this.assertChild(start) - start = this.nodes.indexOf(start) - end = this.assertChild(end) - end = this.nodes.indexOf(end) - return this.nodes.slice(start, end + 1) + getChild(key) { + key = Normalize.key(key) + return this.nodes.find(node => node.key == key) }, /** @@ -344,7 +293,7 @@ const Node = { throw new Error(`Could not find a descendant node with key "${key}".`) } - // Exclude this node itself + // Exclude this node itself. return ancestors.rest().findLast(iterator) }, @@ -371,15 +320,14 @@ const Node = { }, /** - * Get a child node by `key`. + * Get the closest void parent of a `node`. * * @param {String} key * @return {Node|Null} */ - getChild(key) { - key = Normalize.key(key) - return this.nodes.find(node => node.key == key) + getClosestVoid(key) { + return this.getClosest(key, parent => parent.isVoid) }, /** @@ -437,29 +385,20 @@ const Node = { }, /** - * Get the decorations for a descendant by `key` given a `schema`. + * Get the depth of a child node by `key`, with optional `startAt`. * * @param {String} key - * @param {Schema} schema - * @return {Array} + * @param {Number} startAt (optional) + * @return {Number} depth */ - getDescendantDecorators(key, schema) { - if (!schema.hasDecorators) { - return [] - } - - const descendant = this.assertDescendant(key) - let child = this.getHighestChild(key) - let decorators = [] - - while (child != descendant) { - decorators = decorators.concat(child.getDecorators(schema)) - child = child.getHighestChild(key) - } - - decorators = decorators.concat(descendant.getDecorators(schema)) - return decorators + getDepth(key, startAt = 1) { + this.assertDescendant(key) + return this.hasChild(key) + ? startAt + : this + .getFurthestAncestor(key) + .getDepth(key, startAt + 1) }, /** @@ -471,18 +410,13 @@ const Node = { getDescendant(key) { key = Normalize.key(key) - return this._getDescendant(key) - }, - - // This one is memoized - _getDescendant(key) { let descendantFound = null const found = this.nodes.find((node) => { if (node.key === key) { return node } else if (node.kind !== 'text') { - descendantFound = node._getDescendant(key) + descendantFound = node.getDescendant(key) return descendantFound } else { return false @@ -512,48 +446,47 @@ const Node = { }, /** - * True if the node has both descendants in that order, false - * otherwise. The order is depth-first, post-order. + * Get the decorators for a descendant by `key` given a `schema`. * - * @param {String} key1 - * @param {String} key2 - * @return {Boolean} True if nodes are found in this order + * @param {String} key + * @param {Schema} schema + * @return {Array} */ - areDescendantSorted(key1, key2) { - key1 = Normalize.key(key1) - key2 = Normalize.key(key2) + getDescendantDecorators(key, schema) { + if (!schema.hasDecorators) { + return [] + } - let sorted + const descendant = this.assertDescendant(key) + let child = this.getFurthestAncestor(key) + let decorators = [] - this.forEachDescendant((n) => { - if (n.key === key1) { - sorted = true - return false - } else if (n.key === key2) { - sorted = false - return false - } - }) + while (child != descendant) { + decorators = decorators.concat(child.getDecorators(schema)) + child = child.getFurthestAncestor(key) + } - return sorted + decorators = decorators.concat(descendant.getDecorators(schema)) + return decorators }, /** - * Get the depth of a child node by `key`, with optional `startAt`. + * Get the first child text node. * - * @param {String} key - * @param {Number} startAt (optional) - * @return {Number} depth + * @return {Node|Null} */ - getDepth(key, startAt = 1) { - this.assertDescendant(key) - return this.hasChild(key) - ? startAt - : this - .getHighestChild(key) - .getDepth(key, startAt + 1) + getFirstText() { + let descendantFound = null + + const found = this.nodes.find((node) => { + if (node.kind == 'text') return true + descendantFound = node.getFirstText() + return descendantFound + }) + + return descendantFound || found }, /** @@ -565,7 +498,7 @@ const Node = { getFragmentAtRange(range) { let node = this - let nodes = Block.createList() + let nodes = new List() // If the range is collapsed, there's nothing to do. if (range.isCollapsed) return Document.create({ nodes }) @@ -586,10 +519,10 @@ const Node = { node = node.splitBlockAtRange(end, Infinity) // Get the start and end nodes. - const startNode = node.getNextSibling(node.getHighestChild(startKey).key) + const startNode = node.getNextSibling(node.getFurthestAncestor(startKey).key) const endNode = startKey == endKey - ? node.getHighestChild(next.key) - : node.getHighestChild(endKey) + ? node.getFurthestAncestor(next.key) + : node.getFurthestAncestor(endKey) nodes = node.getChildrenBetweenIncluding(startNode.key, endNode.key) @@ -639,13 +572,13 @@ const Node = { }, /** - * Get the highest child ancestor of a node by `key`. + * Get the furthest ancestor of a node by `key`. * * @param {String} key * @return {Node|Null} */ - getHighestChild(key) { + getFurthestAncestor(key) { key = Normalize.key(key) return this.nodes.find((node) => { if (node.key == key) return true @@ -655,13 +588,13 @@ const Node = { }, /** - * Get the highest parent of a node by `key` which has an only child. + * Get the furthest ancestor of a node by `key` that has only one child. * * @param {String} key * @return {Node|Null} */ - getHighestOnlyChildParent(key) { + getFurthestOnlyChildAncestor(key) { const ancestors = this.getAncestors(key) if (!ancestors) { @@ -670,11 +603,11 @@ const Node = { } return ancestors - // Skip this node + // Skip this node... .skipLast() - // Take parents until there are more than one child + // Take parents until there are more than one child... .reverse().takeUntil(p => p.nodes.size > 1) - // Pick the highest + // And pick the highest. .last() }, @@ -709,6 +642,52 @@ const Node = { .toList() }, + /** + * Return a set of all keys in the node. + * + * @return {Set} + */ + + getKeys() { + const keys = [] + + this.forEachDescendant((desc) => { + keys.push(desc.key) + }) + + return Set(keys) + }, + + /** + * Get the last child text node. + * + * @return {Node|Null} + */ + + getLastText() { + let descendantFound = null + + const found = this.nodes.findLast((node) => { + if (node.kind == 'text') return true + descendantFound = node.getLastText() + return descendantFound + }) + + return descendantFound || found + }, + + /** + * Get all of the marks for all of the characters of every text node. + * + * @return {Set} + */ + + getMarks() { + return this + .getCharacters() + .reduce((marks, char) => marks.union(char.marks), new Set()) + }, + /** * Get a set of the marks in a `range`. * @@ -719,12 +698,11 @@ const Node = { getMarksAtRange(range) { range = range.normalize(this) const { startKey, startOffset } = range - const marks = Mark.createSet() // If the range is collapsed at the start of the node, check the previous. if (range.isCollapsed && startOffset == 0) { const previous = this.getPreviousText(startKey) - if (!previous || !previous.length) return marks + if (!previous || !previous.length) return new Set() const char = previous.characters.get(previous.length - 1) return char.marks } @@ -739,9 +717,7 @@ const Node = { // Otherwise, get a set of the marks for each character in the range. return this .getCharactersAtRange(range) - .reduce((memo, char) => { - return memo.union(char.marks) - }, new Set()) + .reduce((memo, char) => memo.union(char.marks), new Set()) }, /** @@ -825,7 +801,7 @@ const Node = { this.assertDescendant(key) // Calculate the offset of the nodes before the highest child. - const child = this.getHighestChild(key) + const child = this.getFurthestAncestor(key) const offset = this.nodes .takeUntil(n => n == child) .reduce((memo, n) => memo + n.length, 0) @@ -910,30 +886,27 @@ const Node = { }, /** - * Get the path of ancestors of a descendant node by `key`. + * Get the block node before a descendant text node by `key`. * - * @param {String|Node} key - * @return {List|Null} + * @param {String} key + * @return {Node|Null} */ - getAncestors(key) { - key = Normalize.key(key) + getPreviousBlock(key) { + const child = this.assertDescendant(key) + let first - if (key == this.key) return List() - if (this.hasChild(key)) return List([this]) - - let ancestors - this.nodes.find((node) => { - if (node.kind == 'text') return false - ancestors = node.getAncestors(key) - return ancestors - }) - - if (ancestors) { - return ancestors.unshift(this) + if (child.kind == 'block') { + first = child.getFirstText() } else { - return null + const block = this.getClosestBlock(key) + first = block.getFirstText() } + + const previous = this.getPreviousText(first.key) + if (!previous) return null + + return this.getClosestBlock(previous.key) }, /** @@ -971,27 +944,15 @@ const Node = { }, /** - * Get the block node before a descendant text node by `key`. + * Get the concatenated text `string` of all child nodes. * - * @param {String} key - * @return {Node|Null} + * @return {String} */ - getPreviousBlock(key) { - const child = this.assertDescendant(key) - let first - - if (child.kind == 'block') { - first = child.getFirstText() - } else { - const block = this.getClosestBlock(key) - first = block.getFirstText() - } - - const previous = this.getPreviousText(first.key) - if (!previous) return null - - return this.getClosestBlock(previous.key) + getText() { + return this.nodes.reduce((result, node) => { + return result + node.text + }, '') }, /** @@ -1035,55 +996,11 @@ const Node = { */ getTexts() { - return List(this._getTexts()) - }, - - // This one is memoized for performance. - _getTexts() { return this.nodes.reduce((texts, node) => { - if (node.kind == 'text') { - texts.push(node) - return texts - } else { - return texts.concat(node._getTexts()) - } - }, []) - }, - - /** - * Get the first child text node. - * - * @return {Node|Null} - */ - - getFirstText() { - let descendantFound = null - - const found = this.nodes.find((node) => { - if (node.kind == 'text') return true - descendantFound = node.getFirstText() - return descendantFound - }) - - return descendantFound || found - }, - - /** - * Get the last child text node. - * - * @return {Node|Null} - */ - - getLastText() { - let descendantFound = null - - const found = this.nodes.findLast((node) => { - if (node.kind == 'text') return true - descendantFound = node.getLastText() - return descendantFound - }) - - return descendantFound || found + return node.kind == 'text' + ? texts.push(node) + : texts.concat(node.getTexts()) + }, new List()) }, /** @@ -1175,22 +1092,6 @@ const Node = { return this.merge({ nodes }) }, - /** - * Check if the inline nodes are split at a `range`. - * - * @param {Selection} range - * @return {Boolean} - */ - - isInlineSplitAtRange(range) { - range = range.normalize(this) - if (range.isExpanded) throw new Error() - - const { startKey } = range - const start = this.getFurthestInline(startKey) || this.getDescendant(startKey) - return range.isAtStartOf(start) || range.isAtEndOf(start) - }, - /** * Join a children node `first` with another children node `second`. * `first` and `second` will be concatenated in that order. @@ -1325,6 +1226,33 @@ const Node = { return this.merge({ nodes }) }, + /** + * Split the block nodes at a `range`, to optional `height`. + * + * @param {Selection} range + * @param {Number} height (optional) + * @return {Node} + */ + + splitBlockAtRange(range, height = 1) { + const { startKey, startOffset } = range + const base = this + let node = base.assertDescendant(startKey) + let parent = base.getClosestBlock(node.key) + let offset = startOffset + let h = 0 + + while (parent && parent.kind == 'block' && h < height) { + offset += parent.getOffset(node.key) + node = parent + parent = base.getClosestBlock(parent.key) + h++ + } + + const path = base.getPath(node.key) + return this.splitNode(path, offset) + }, + /** * Split a node by `path` at `offset`. * @@ -1416,33 +1344,6 @@ const Node = { return base }, - /** - * Split the block nodes at a `range`, to optional `height`. - * - * @param {Selection} range - * @param {Number} height (optional) - * @return {Node} - */ - - splitBlockAtRange(range, height = 1) { - const { startKey, startOffset } = range - const base = this - let node = base.assertDescendant(startKey) - let parent = base.getClosestBlock(node.key) - let offset = startOffset - let h = 0 - - while (parent && parent.kind == 'block' && h < height) { - offset += parent.getOffset(node.key) - node = parent - parent = base.getClosestBlock(parent.key) - h++ - } - - const path = base.getPath(node.key) - return this.splitNode(path, offset) - }, - /** * Set a new value for a child node by `key`. * @@ -1478,7 +1379,163 @@ const Node = { validate(schema) { return schema.__validate(this) - } + }, + + /** + * True if the node has both descendants in that order, false otherwise. The + * order is depth-first, post-order. + * + * @param {String} first + * @param {String} second + * @return {Boolean} + */ + + areDescendantSorted(first, second) { + warn('The Node.areDescendantSorted(first, second) method is deprecated, please use `Node.areDescendantsSorted(first, second) instead.') + return this.areDescendantsSorted(first, second) + }, + + /** + * Concat children `nodes` on to the end of the node. + * + * @param {List} nodes + * @return {Node} + */ + + concatChildren(nodes) { + warn('The `Node.concatChildren(nodes)` method is deprecated.') + nodes = this.nodes.concat(nodes) + return this.merge({ nodes }) + }, + + /** + * Decorate all of the text nodes with a `decorator` function. + * + * @param {Function} decorator + * @return {Node} + */ + + decorateTexts(decorator) { + warn('The `Node.decorateTexts(decorator) method is deprecated.') + return this.mapDescendants((child) => { + return child.kind == 'text' + ? child.decorateCharacters(decorator) + : child + }) + }, + + /** + * Recursively filter all descendant nodes with `iterator`, depth-first. + * It is different from `filterDescendants` in regard of the order of results. + * + * @param {Function} iterator + * @return {List} + */ + + filterDescendantsDeep(iterator) { + warn('The Node.filterDescendantsDeep(iterator) method is deprecated.') + return this.nodes.reduce((matches, child, i, nodes) => { + if (child.kind != 'text') matches = matches.concat(child.filterDescendantsDeep(iterator)) + if (iterator(child, i, nodes)) matches = matches.push(child) + return matches + }, new List()) + }, + + /** + * Recursively find all descendant nodes by `iterator`. Depth first. + * + * @param {Function} iterator + * @return {Node|Null} + */ + + findDescendantDeep(iterator) { + warn('The Node.findDescendantDeep(iterator) method is deprecated.') + let found + + this.forEachDescendant((node) => { + if (iterator(node)) { + found = node + return false + } + }) + + return found + }, + + /** + * Get children between two child keys. + * + * @param {String} start + * @param {String} end + * @return {Node} + */ + + getChildrenBetween(start, end) { + warn('The `Node.getChildrenBetween(start, end)` method is deprecated.') + start = this.assertChild(start) + start = this.nodes.indexOf(start) + end = this.assertChild(end) + end = this.nodes.indexOf(end) + return this.nodes.slice(start + 1, end) + }, + + /** + * Get children between two child keys, including the two children. + * + * @param {String} start + * @param {String} end + * @return {Node} + */ + + getChildrenBetweenIncluding(start, end) { + warn('The `Node.getChildrenBetweenIncluding(start, end)` method is deprecated.') + start = this.assertChild(start) + start = this.nodes.indexOf(start) + end = this.assertChild(end) + end = this.nodes.indexOf(end) + return this.nodes.slice(start, end + 1) + }, + + /** + * Get the highest child ancestor of a node by `key`. + * + * @param {String} key + * @return {Node|Null} + */ + + getHighestChild(key) { + warn('The `Node.getHighestChild(key) method is deprecated, please use `Node.getFurthestAncestor(key) instead.') + return this.getFurthestAncestor(key) + }, + + /** + * Get the highest parent of a node by `key` which has an only child. + * + * @param {String} key + * @return {Node|Null} + */ + + getHighestOnlyChildParent(key) { + warn('The `Node.getHighestOnlyChildParent(key)` method is deprecated, please use `Node.getFurthestOnlyChildAncestor` instead.') + return this.getFurthestOnlyChildAncestor(key) + }, + + /** + * Check if the inline nodes are split at a `range`. + * + * @param {Selection} range + * @return {Boolean} + */ + + isInlineSplitAtRange(range) { + warn('The `Node.isInlineSplitAtRange(range)` method is deprecated.') + range = range.normalize(this) + if (range.isExpanded) throw new Error() + + const { startKey } = range + const start = this.getFurthestInline(startKey) || this.getDescendant(startKey) + return range.isAtStartOf(start) || range.isAtEndOf(start) + }, } @@ -1487,28 +1544,37 @@ const Node = { */ memoize(Node, [ - 'getText', + 'areDescendantsSorted', 'getAncestors', 'getBlocks', 'getBlocksAtRange', + 'getCharacters', 'getCharactersAtRange', 'getChild', + 'getChildrenBetween', + 'getChildrenBetweenIncluding', 'getClosestBlock', 'getClosestInline', + 'getClosestVoid', + 'getCommonAncestor', 'getComponent', 'getDecorators', 'getDepth', - '_getDescendant', + 'getDescendant', + 'getDescendant', 'getDescendantAtPath', 'getDescendantDecorators', 'getFirstText', 'getFragmentAtRange', 'getFurthestBlock', 'getFurthestInline', - 'getHighestChild', - 'getHighestOnlyChildParent', + 'getFurthestAncestor', + 'getFurthestOnlyChildAncestor', + 'getInlines', 'getInlinesAtRange', + 'getKeys', 'getLastText', + 'getMarks', 'getMarksAtRange', 'getNextBlock', 'getNextSibling', @@ -1517,16 +1583,21 @@ memoize(Node, [ 'getOffset', 'getOffsetAtRange', 'getParent', + 'getPath', 'getPreviousBlock', 'getPreviousSibling', 'getPreviousText', + 'getText', 'getTextAtOffset', 'getTextDirection', - '_getTexts', + 'getTexts', 'getTextsAtRange', + 'hasChild', + 'hasDescendant', + 'hasNode', 'hasVoidParent', 'isInlineSplitAtRange', - 'validate' + 'validate', ]) /** diff --git a/src/models/selection.js b/src/models/selection.js index 77fa591d1..7ee9fe030 100644 --- a/src/models/selection.js +++ b/src/models/selection.js @@ -601,7 +601,7 @@ class Selection extends new Record(DEFAULTS) { if (anchorNode.key === focusNode.key) { isBackward = anchorOffset > focusOffset } else { - isBackward = !node.areDescendantSorted(anchorNode.key, focusNode.key) + isBackward = !node.areDescendantsSorted(anchorNode.key, focusNode.key) } } diff --git a/src/models/text.js b/src/models/text.js index ca119d588..2477cc0ba 100644 --- a/src/models/text.js +++ b/src/models/text.js @@ -378,6 +378,7 @@ class Text extends new Record(DEFAULTS) { memoize(Text.prototype, [ 'getDecorations', 'getDecorators', + 'getMarksAtIndex', 'getRanges', 'validate' ]) diff --git a/src/transforms/at-range.js b/src/transforms/at-range.js index 0a5f89d23..c04ac855e 100644 --- a/src/transforms/at-range.js +++ b/src/transforms/at-range.js @@ -82,8 +82,8 @@ Transforms.deleteAtRange = (transform, range, options = {}) => { let { state } = transform let { document } = state let ancestor = document.getCommonAncestor(startKey, endKey) - let startChild = ancestor.getHighestChild(startKey) - let endChild = ancestor.getHighestChild(endKey) + let startChild = ancestor.getFurthestAncestor(startKey) + let endChild = ancestor.getFurthestAncestor(endKey) const startOff = (startChild.kind == 'text' ? 0 : startChild.getOffset(startKey)) + startOffset const endOff = (endChild.kind == 'text' ? 0 : endChild.getOffset(endKey)) + endOffset @@ -94,8 +94,8 @@ Transforms.deleteAtRange = (transform, range, options = {}) => { state = transform.state document = state.document ancestor = document.getCommonAncestor(startKey, endKey) - startChild = ancestor.getHighestChild(startKey) - endChild = ancestor.getHighestChild(endKey) + startChild = ancestor.getFurthestAncestor(startKey) + endChild = ancestor.getFurthestAncestor(endKey) const startIndex = ancestor.nodes.indexOf(startChild) const endIndex = ancestor.nodes.indexOf(endChild) const middles = ancestor.nodes.slice(startIndex + 1, endIndex + 1) @@ -120,7 +120,7 @@ Transforms.deleteAtRange = (transform, range, options = {}) => { }) // Remove parents of endBlock as long as they have a single child - const lonely = document.getHighestOnlyChildParent(endBlock.key) || endBlock + const lonely = document.getFurthestOnlyChildAncestor(endBlock.key) || endBlock transform.removeNodeByKey(lonely.key, OPTS) } @@ -299,7 +299,7 @@ Transforms.deleteBackwardAtRange = (transform, range, n = 1, options = {}) => { // If the focus node is inside a void, go up until right after it. if (document.hasVoidParent(node.key)) { - const parent = document.getClosest(node.key, p => p.isVoid) + const parent = document.getClosestVoid(node.key) node = document.getNextText(parent.key) offset = 0 } @@ -483,7 +483,7 @@ Transforms.deleteForwardAtRange = (transform, 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.getClosest(node.key, p => p.isVoid) + const parent = document.getClosestVoid(node.key) node = document.getPreviousText(parent.key) offset = node.length } @@ -585,7 +585,7 @@ Transforms.insertFragmentAtRange = (transform, range, fragment, options = {}) => let { document } = state let startText = document.getDescendant(startKey) let startBlock = document.getClosestBlock(startText.key) - let startChild = startBlock.getHighestChild(startText.key) + let startChild = startBlock.getFurthestAncestor(startText.key) const isAtStart = range.isAtStartOf(startBlock) const parent = document.getParent(startBlock.key) const index = parent.nodes.indexOf(startBlock) @@ -621,7 +621,7 @@ Transforms.insertFragmentAtRange = (transform, range, fragment, options = {}) => document = state.document startText = document.getDescendant(startKey) startBlock = document.getClosestBlock(startKey) - startChild = startBlock.getHighestChild(startText.key) + startChild = startBlock.getFurthestAncestor(startText.key) // If the first and last block aren't the same, we need to move any of the // starting block's children after the split into the last block of the @@ -647,7 +647,7 @@ Transforms.insertFragmentAtRange = (transform, range, fragment, options = {}) => // Otherwise, we maintain the starting block, and insert all of the first // block's inline nodes into it at the split point. else { - const inlineChild = startBlock.getHighestChild(startText.key) + const inlineChild = startBlock.getFurthestAncestor(startText.key) const inlineIndex = startBlock.nodes.indexOf(inlineChild) firstBlock.nodes.forEach((inline, i) => { @@ -1149,8 +1149,8 @@ Transforms.wrapInlineAtRange = (transform, range, inline, options = {}) => { const blocks = document.getBlocksAtRange(range) let startBlock = document.getClosestBlock(startKey) let endBlock = document.getClosestBlock(endKey) - let startChild = startBlock.getHighestChild(startKey) - const endChild = endBlock.getHighestChild(endKey) + let startChild = startBlock.getFurthestAncestor(startKey) + const endChild = endBlock.getFurthestAncestor(endKey) const startIndex = startBlock.nodes.indexOf(startChild) const endIndex = endBlock.nodes.indexOf(endChild) @@ -1174,7 +1174,7 @@ Transforms.wrapInlineAtRange = (transform, range, inline, options = {}) => { state = transform.state document = state.document startBlock = document.getClosestBlock(startKey) - startChild = startBlock.getHighestChild(startKey) + startChild = startBlock.getFurthestAncestor(startKey) const startInner = startOff == 0 ? startChild @@ -1182,7 +1182,7 @@ Transforms.wrapInlineAtRange = (transform, range, inline, options = {}) => { const startInnerIndex = startBlock.nodes.indexOf(startInner) - const endInner = startKey == endKey ? startInner : startBlock.getHighestChild(endKey) + const endInner = startKey == endKey ? startInner : startBlock.getFurthestAncestor(endKey) const inlines = startBlock.nodes .skipUntil(n => n == startInner) .takeUntil(n => n == endInner)