1
0
mirror of https://github.com/ianstormtaylor/slate.git synced 2025-08-30 18:39:51 +02:00

Node methods optimizations (#364)

* Node.getParent exits earlier

* Add Node.getAncestors method

* Remove numerous getParent in Node.getClosest

* Remove use of assertDescendant in getPath

Still throws when not finding the descendant though

* Remove assertDescendant from Node.updateDescendant

* Remove assertDescendant from Node.removeDescendant

* Fix Node.findDescendant, which always returned first level descendants

* Add Node.findDescendantDeep

* Memoize Node.getAncestors

* Implement and use Node.get{First|Last}Text

* Add jsdom devDepencency

Required as peer dependency by mocha-jsdom
This commit is contained in:
Nicolas Gaborit
2016-10-26 21:46:40 +02:00
committed by Ian Storm Taylor
parent dca60c42ce
commit 8851855b5b
6 changed files with 164 additions and 58 deletions

View File

@@ -47,6 +47,7 @@
"http-server": "^0.9.0", "http-server": "^0.9.0",
"is-image": "^1.0.1", "is-image": "^1.0.1",
"is-url": "^1.2.2", "is-url": "^1.2.2",
"jsdom": "9.8.0",
"jsdom": "9.6.0", "jsdom": "9.6.0",
"jsdom-global": "2.1.0", "jsdom-global": "2.1.0",
"microtime": "2.1.1", "microtime": "2.1.1",

View File

@@ -130,7 +130,7 @@ class Void extends React.Component {
renderLeaf = () => { renderLeaf = () => {
const { node, schema, state } = this.props const { node, schema, state } = this.props
const child = node.getTexts().first() const child = node.getFirstText()
const ranges = child.getRanges() const ranges = child.getRanges()
const text = '' const text = ''
const marks = Mark.createSet() const marks = Mark.createSet()

View File

@@ -105,23 +105,53 @@ const Node = {
}, },
/** /**
* Recursively find all ancestor nodes by `iterator`. * Recursively find all descendant nodes by `iterator`. Breadth first.
* *
* @param {Function} iterator * @param {Function} iterator
* @return {Node} node * @return {Node or Null} node
*/ */
findDescendant(iterator) { findDescendant(iterator) {
return ( const found = this.nodes.find(iterator)
this.nodes.find(iterator) || if (found) return found
this.nodes
.map(node => node.kind == 'text' ? null : node.findDescendant(iterator)) let descendantFound = null
.find(exists => exists) this.nodes.find(node => {
) if (node.kind != 'text') {
descendantFound = node.findDescendant(iterator)
return descendantFound
} else {
return false
}
})
return descendantFound
}, },
/** /**
* Recursively filter all ancestor nodes with `iterator`. * Recursively find all descendant nodes by `iterator`. Depth first.
*
* @param {Function} iterator
* @return {Node or Null} node
*/
findDescendantDeep(iterator) {
let descendantFound = null
const found = this.nodes.find(node => {
if (node.kind != 'text') {
descendantFound = node.findDescendantDeep(iterator)
return descendantFound || iterator(node)
}
return iterator(node) ? node : null
})
return descendantFound || found
},
/**
* Recursively filter all descendant nodes with `iterator`.
* *
* @param {Function} iterator * @param {Function} iterator
* @return {List} nodes * @return {List} nodes
@@ -136,7 +166,7 @@ const Node = {
}, },
/** /**
* Recursively filter all ancestor nodes with `iterator`, depth-first. * Recursively filter all descendant nodes with `iterator`, depth-first.
* *
* @param {Function} iterator * @param {Function} iterator
* @return {List} nodes * @return {List} nodes
@@ -286,14 +316,13 @@ const Node = {
*/ */
getClosest(key, iterator) { getClosest(key, iterator) {
let node = this.assertDescendant(key) let ancestors = this.getAncestors(key)
if (!ancestors) {
while (node = this.getParent(node)) { throw new Error(`Could not find a descendant node with key "${key}".`)
if (node == this) return null
if (iterator(node)) return node
} }
return null // Exclude this node itself
return ancestors.rest().findLast(iterator)
}, },
/** /**
@@ -410,16 +439,7 @@ const Node = {
getDescendant(key) { getDescendant(key) {
key = Normalize.key(key) key = Normalize.key(key)
let child = this.getChild(key) return this.findDescendantDeep(node => node.key == key)
if (child) return child
this.nodes.find((node) => {
if (node.kind == 'text') return false
child = node.getDescendant(key)
return child
})
return child
}, },
/** /**
@@ -654,10 +674,10 @@ const Node = {
let last let last
if (child.kind == 'block') { if (child.kind == 'block') {
last = child.getTexts().last() last = child.getLastText()
} else { } else {
const block = this.getClosestBlock(key) const block = this.getClosestBlock(key)
last = block.getTexts().last() last = block.getLastText()
} }
const next = this.getNextText(last) const next = this.getNextText(last)
@@ -748,10 +768,13 @@ const Node = {
let node = null let node = null
this.nodes.forEach((child) => { this.nodes.find((child) => {
if (child.kind == 'text') return if (child.kind == 'text') {
const match = child.getParent(key) return false
if (match) node = match } else {
node = child.getParent(key)
return node
}
}) })
return node return node
@@ -760,7 +783,7 @@ const Node = {
/** /**
* Get the path of a descendant node by `key`. * Get the path of a descendant node by `key`.
* *
* @param {String || Node} node * @param {String || Node} key
* @return {Array} * @return {Array}
*/ */
@@ -769,17 +792,50 @@ const Node = {
if (key == this.key) return [] if (key == this.key) return []
let child = this.assertDescendant(key)
let path = [] let path = []
let childKey = key
let parent let parent
while (parent = this.getParent(child)) { // Efficient with getParent memoization
const index = parent.nodes.indexOf(child) while (parent = this.getParent(childKey)) {
const index = parent.nodes.findIndex(n => n.key === childKey)
path.unshift(index) path.unshift(index)
child = parent childKey = parent.key
} }
return path if (childKey === key) {
// Did not loop once, meaning we could not find the child
throw new Error(`Could not find a descendant node with key "${key}".`)
} else {
return path
}
},
/**
* Get the path of ancestors of a descendant node by `key`.
*
* @param {String || Node} node
* @return {List<Node> or Null}
*/
getAncestors(key) {
key = Normalize.key(key)
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)
} else {
return null
}
}, },
/** /**
@@ -824,10 +880,10 @@ const Node = {
let first let first
if (child.kind == 'block') { if (child.kind == 'block') {
first = child.getTexts().first() first = child.getFirstText()
} else { } else {
const block = this.getClosestBlock(key) const block = this.getClosestBlock(key)
first = block.getTexts().first() first = block.getFirstText()
} }
const previous = this.getPreviousText(first) const previous = this.getPreviousText(first)
@@ -881,6 +937,34 @@ const Node = {
}, Block.createList()) }, Block.createList())
}, },
/**
* Get the first child text node.
*
* @return {Node || Null} node
*/
getFirstText() {
return this.findDescendantDeep(node => node.kind == 'text')
},
/**
* Get the last child text node.
*
* @return {Node} node
*/
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 text nodes in a `range`. * Get all of the text nodes in a `range`.
* *
@@ -1163,10 +1247,13 @@ const Node = {
*/ */
removeDescendant(key) { removeDescendant(key) {
key = Normalize.key(key)
let node = this let node = this
const desc = node.assertDescendant(key) let parent = node.getParent(key)
let parent = node.getParent(desc) if (!parent) throw new Error(`Could not find a descendant node with key "${key}".`)
const index = parent.nodes.indexOf(desc)
const index = parent.nodes.findIndex(n => n.key === key)
const isParent = node == parent const isParent = node == parent
const nodes = parent.nodes.splice(index, 1) const nodes = parent.nodes.splice(index, 1)
@@ -1277,8 +1364,22 @@ const Node = {
*/ */
updateDescendant(node) { updateDescendant(node) {
this.assertDescendant(node) let found = false
return this.mapDescendants(d => d.key == node.key ? node : d)
const result = this.mapDescendants(d => {
if (d.key == node.key) {
found = true
return node
} else {
return d
}
})
if (!found) {
throw new Error(`Could not update descendant node with key "${node.key}".`)
} else {
return result
}
}, },
/** /**
@@ -1304,6 +1405,8 @@ memoize(Node, [
'filterDescendants', 'filterDescendants',
'filterDescendantsDeep', 'filterDescendantsDeep',
'findDescendant', 'findDescendant',
'findDescendantDeep',
'getAncestors',
'getBlocks', 'getBlocks',
'getBlocksAtRange', 'getBlocksAtRange',
'getCharactersAtRange', 'getCharactersAtRange',
@@ -1322,6 +1425,7 @@ memoize(Node, [
'getDepth', 'getDepth',
'getDescendant', 'getDescendant',
'getDescendantDecorators', 'getDescendantDecorators',
'getFirstText',
'getFragmentAtRange', 'getFragmentAtRange',
'getFurthest', 'getFurthest',
'getFurthestBlock', 'getFurthestBlock',
@@ -1329,6 +1433,7 @@ memoize(Node, [
'getHighestChild', 'getHighestChild',
'getHighestOnlyChildParent', 'getHighestOnlyChildParent',
'getInlinesAtRange', 'getInlinesAtRange',
'getLastText',
'getMarksAtRange', 'getMarksAtRange',
'getNextBlock', 'getNextBlock',
'getNextSibling', 'getNextSibling',

View File

@@ -148,7 +148,7 @@ class Selection extends new Record(DEFAULTS) {
hasAnchorAtStartOf(node) { hasAnchorAtStartOf(node) {
if (this.anchorOffset != 0) return false if (this.anchorOffset != 0) return false
const first = node.kind == 'text' ? node : node.getTexts().first() const first = node.kind == 'text' ? node : node.getFirstText()
return this.anchorKey == first.key return this.anchorKey == first.key
} }
@@ -160,7 +160,7 @@ class Selection extends new Record(DEFAULTS) {
*/ */
hasAnchorAtEndOf(node) { hasAnchorAtEndOf(node) {
const last = node.kind == 'text' ? node : node.getTexts().last() const last = node.kind == 'text' ? node : node.getLastText()
return this.anchorKey == last.key && this.anchorOffset == last.length return this.anchorKey == last.key && this.anchorOffset == last.length
} }
@@ -202,7 +202,7 @@ class Selection extends new Record(DEFAULTS) {
*/ */
hasFocusAtEndOf(node) { hasFocusAtEndOf(node) {
const last = node.kind == 'text' ? node : node.getTexts().last() const last = node.kind == 'text' ? node : node.getLastText()
return this.focusKey == last.key && this.focusOffset == last.length return this.focusKey == last.key && this.focusOffset == last.length
} }
@@ -215,7 +215,7 @@ class Selection extends new Record(DEFAULTS) {
hasFocusAtStartOf(node) { hasFocusAtStartOf(node) {
if (this.focusOffset != 0) return false if (this.focusOffset != 0) return false
const first = node.kind == 'text' ? node : node.getTexts().first() const first = node.kind == 'text' ? node : node.getFirstText()
return this.focusKey == first.key return this.focusKey == first.key
} }
@@ -260,7 +260,7 @@ class Selection extends new Record(DEFAULTS) {
const { isExpanded, startKey, startOffset } = this const { isExpanded, startKey, startOffset } = this
if (isExpanded) return false if (isExpanded) return false
if (startOffset != 0) return false if (startOffset != 0) return false
const first = node.kind == 'text' ? node : node.getTexts().first() const first = node.kind == 'text' ? node : node.getFirstText()
return startKey == first.key return startKey == first.key
} }
@@ -274,7 +274,7 @@ class Selection extends new Record(DEFAULTS) {
isAtEndOf(node) { isAtEndOf(node) {
const { endKey, endOffset, isExpanded } = this const { endKey, endOffset, isExpanded } = this
if (isExpanded) return false if (isExpanded) return false
const last = node.kind == 'text' ? node : node.getTexts().last() const last = node.kind == 'text' ? node : node.getLastText()
return endKey == last.key && endOffset == last.length return endKey == last.key && endOffset == last.length
} }

View File

@@ -47,7 +47,7 @@ class State extends new Record(DEFAULTS) {
let selection = Selection.create(properties.selection) let selection = Selection.create(properties.selection)
if (selection.isUnset) { if (selection.isUnset) {
const text = document.getTexts().first() const text = document.getFirstText()
selection = selection.collapseToStartOf(text) selection = selection.collapseToStartOf(text)
} }

View File

@@ -70,7 +70,7 @@ export function _delete(transform) {
after = selection.collapseToEndOf(previous) after = selection.collapseToEndOf(previous)
} }
} else { } else {
const last = previous.getTexts().last() const last = previous.getLastText()
after = selection.collapseToEndOf(last) after = selection.collapseToEndOf(last)
} }
} }
@@ -143,7 +143,7 @@ export function deleteBackward(transform, n = 1) {
after = selection.collapseToEndOf(previous) after = selection.collapseToEndOf(previous)
} }
} else { } else {
const last = previous.getTexts().last() const last = previous.getLastText()
after = selection.collapseToEndOf(last) after = selection.collapseToEndOf(last)
} }
} else { } else {
@@ -206,7 +206,7 @@ export function deleteForward(transform, n = 1) {
after = selection.collapseToEndOf(previous) after = selection.collapseToEndOf(previous)
} }
} else { } else {
const last = previous.getTexts().last() const last = previous.getLastText()
after = selection.collapseToEndOf(last) after = selection.collapseToEndOf(last)
} }
} }
@@ -259,7 +259,7 @@ export function insertFragment(transform, fragment) {
if (!fragment.length) return transform if (!fragment.length) return transform
const lastText = fragment.getTexts().last() const lastText = fragment.getLastText()
const lastInline = fragment.getClosestInline(lastText) const lastInline = fragment.getClosestInline(lastText)
const beforeTexts = document.getTexts() const beforeTexts = document.getTexts()
const appending = selection.hasEdgeAtEndOf(document.getDescendant(selection.endKey)) const appending = selection.hasEdgeAtEndOf(document.getDescendant(selection.endKey))
@@ -575,7 +575,7 @@ export function wrapInline(transform, properties) {
} }
else if (selection.startOffset == 0) { else if (selection.startOffset == 0) {
const text = previous ? document.getNextText(previous) : document.getTexts().first() const text = previous ? document.getNextText(previous) : document.getFirstText()
after = selection.moveToRangeOf(text) after = selection.moveToRangeOf(text)
} }