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

Add node.getNodesAtRange (#2398)

* Add `Node.getNodesAtRange`

* Add tests for `Node.getNodesAtRange`
This commit is contained in:
Dundercover
2018-11-08 20:31:54 +01:00
committed by Ian Storm Taylor
parent f2f97e502e
commit e6d611d726
14 changed files with 478 additions and 1 deletions

View File

@@ -253,6 +253,12 @@ Get the next [`Text`](./text.md) node after a descendant by `path` or `key`.
Get a node in the tree by `path` or `key`.
### `getNodesAtRange`
`getNodesAtRange(range: Range) => List`
Get all of the nodes in a `range`. This includes all of the [`Text`](./text.md) nodes inside the range and all ancestors of those [`Text`](./text.md) nodes up to this node.
### `getOffset`
`getOffset(path: List|Array) => Number`

View File

@@ -1,6 +1,6 @@
import direction from 'direction'
import invariant from 'tiny-invariant'
import { List, OrderedSet, Set } from 'immutable'
import { List, OrderedSet, Set, Stack } from 'immutable'
import mixin from '../utils/mixin'
import Block from '../models/block'
@@ -913,6 +913,75 @@ class ElementInterface {
return text
}
/**
* Get all of the nodes in a `range`. This includes all of the
* text nodes inside the range and all ancestors of those text
* nodes up to this node.
*
* @param {Range} range
* @return {List<Node>}
*/
getNodesAtRange(range) {
range = this.resolveRange(range)
if (range.isUnset) return List()
const { start, end } = range
// Do a depth-first stack-based search for all nodes in the range
// Nodes that are pushed to the stack are inside the range
// Start with the nodes that are on the highest level in the tree
let stack = Stack(
this.nodes
.slice(start.path.get(0), end.path.get(0) + 1)
.map((node, index) => ({
node,
onStartEdge: index === 0,
onEndEdge: index === end.path.get(0) - start.path.get(0),
relativeStartPath: start.path.slice(1),
relativeEndPath: end.path.slice(1),
}))
)
const result = []
while (stack.size > 0) {
const {
node,
onStartEdge,
onEndEdge,
relativeStartPath,
relativeEndPath,
} = stack.peek()
stack = stack.shift()
result.push(node)
if (node.object === 'text') continue
// Modify indexes to exclude children that are outside of the range
const startIndex = onStartEdge ? relativeStartPath.get(0) : 0
const endIndex = onEndEdge ? relativeEndPath.get(0) : node.nodes.size - 1
// Push children that are inside the range to the stack
stack = stack.pushAll(
node.nodes.slice(startIndex, endIndex + 1).map((n, i) => ({
node: n,
onStartEdge: onStartEdge && i === 0,
onEndEdge: onEndEdge && i === endIndex - startIndex,
relativeStartPath:
onStartEdge && i === 0 ? relativeStartPath.slice(1) : null,
relativeEndPath:
onEndEdge && i === endIndex - startIndex
? relativeEndPath.slice(1)
: null,
}))
)
}
return List(result)
}
/**
* Get the offset for a descendant text node by `key`.
*
@@ -1797,6 +1866,7 @@ memoize(ElementInterface.prototype, [
'getInlinesByTypeAsArray',
'getMarksAsArray',
'getMarksAtPosition',
'getNodesAtRange',
'getOrderedMarksBetweenPositions',
'getInsertMarksAtRange',
'getMarksByTypeAsArray',

View File

@@ -0,0 +1,38 @@
/** @jsx h */
import h from '../../../helpers/h'
export const input = (
<value>
<document>
<paragraph key="a">
<text key="b">one</text>
</paragraph>
<paragraph key="c">
<text key="d">
tw<anchor />o
</text>
</paragraph>
<image key="e" src="https://example.com/image2.png">
<text key="f" />
</image>
<paragraph key="g">
<inline type="link" key="h">
<text key="i">three</text>
</inline>
<text key="j">
<focus />four
</text>
</paragraph>
</document>
</value>
)
export default function({ document, selection }) {
return document
.getNodesAtRange(selection)
.map(n => n.key)
.toArray()
}
export const output = ['c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']

View File

@@ -0,0 +1,31 @@
/** @jsx h */
import h from '../../../helpers/h'
export const input = (
<value>
<document>
<quote key="a">
<quote key="b">
<paragraph key="c">
<text key="d">
on<cursor />e
</text>
</paragraph>
</quote>
</quote>
<paragraph key="e">
<text key="f">two</text>
</paragraph>
</document>
</value>
)
export default function({ document, selection }) {
return document
.getNodesAtRange(selection)
.map(n => n.key)
.toArray()
}
export const output = ['a', 'b', 'c', 'd']

View File

@@ -0,0 +1,37 @@
/** @jsx h */
import h from '../../../helpers/h'
export const input = (
<value>
<document>
<quote key="a">
<quote key="b">
<paragraph key="c">
<text key="d">one</text>
</paragraph>
<paragraph key="e">
<text key="f">two</text>
</paragraph>
</quote>
<quote key="g">
<paragraph key="h">
<text key="i">
three
<cursor />
</text>
</paragraph>
</quote>
</quote>
</document>
</value>
)
export default function({ document, selection }) {
return document
.getNodesAtRange(selection)
.map(n => n.key)
.toArray()
}
export const output = ['a', 'g', 'h', 'i']

View File

@@ -0,0 +1,35 @@
/** @jsx h */
import h from '../../../helpers/h'
export const input = (
<value>
<document>
<quote key="a">
<quote key="b">
<paragraph key="c">
<text key="d">one</text>
</paragraph>
<paragraph key="e">
<text key="f">
<cursor />
two
</text>
</paragraph>
</quote>
</quote>
<paragraph key="g">
<text key="h">three</text>
</paragraph>
</document>
</value>
)
export default function({ document, selection }) {
return document
.getNodesAtRange(selection)
.map(n => n.key)
.toArray()
}
export const output = ['a', 'b', 'e', 'f']

View File

@@ -0,0 +1,46 @@
/** @jsx h */
import h from '../../../helpers/h'
export const input = (
<value>
<document>
<paragraph key="a">
<text key="b">
<focus />
one
</text>
</paragraph>
<quote key="c">
<quote key="d">
<paragraph key="e">
<text key="f">two</text>
</paragraph>
</quote>
<quote key="g">
<paragraph key="h">
<text key="i">three</text>
</paragraph>
<paragraph key="j">
<text key="k">
<anchor />
four
</text>
</paragraph>
<paragraph key="l">
<text key="m">five</text>
</paragraph>
</quote>
</quote>
</document>
</value>
)
export default function({ document, selection }) {
return document
.getNodesAtRange(selection)
.map(n => n.key)
.toArray()
}
export const output = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k']

View File

@@ -0,0 +1,44 @@
/** @jsx h */
import h from '../../../helpers/h'
export const input = (
<value>
<document>
<quote key="a">
<quote key="b">
<paragraph key="c">
<text key="d">one</text>
</paragraph>
</quote>
<quote key="e">
<paragraph key="f">
<text key="g">
<anchor />two
</text>
</paragraph>
<paragraph key="h">
<text key="i">three</text>
</paragraph>
<paragraph key="j">
<text key="k">
f<focus />our
</text>
</paragraph>
</quote>
</quote>
<paragraph key="l">
<text key="m">five</text>
</paragraph>
</document>
</value>
)
export default function({ document, selection }) {
return document
.getNodesAtRange(selection)
.map(n => n.key)
.toArray()
}
export const output = ['a', 'e', 'f', 'g', 'h', 'i', 'j', 'k']

View File

@@ -0,0 +1,31 @@
/** @jsx h */
import h from '../../../helpers/h'
export const input = (
<value>
<document>
<quote key="a">
<quote key="b">
<paragraph key="c">
<text key="d">
<anchor />one<focus />
</text>
</paragraph>
</quote>
</quote>
<paragraph key="e">
<text key="f">two</text>
</paragraph>
</document>
</value>
)
export default function({ document, selection }) {
return document
.getNodesAtRange(selection)
.map(n => n.key)
.toArray()
}
export const output = ['a', 'b', 'c', 'd']

View File

@@ -0,0 +1,28 @@
/** @jsx h */
import h from '../../../helpers/h'
export const input = (
<value>
<document>
<paragraph key="a">
<text key="b">one</text>
</paragraph>
<paragraph key="c">
<text key="d">
<cursor />
two
</text>
</paragraph>
</document>
</value>
)
export default function({ document, selection }) {
return document
.getNodesAtRange(selection)
.map(n => n.key)
.toArray()
}
export const output = ['c', 'd']

View File

@@ -0,0 +1,31 @@
/** @jsx h */
import h from '../../../helpers/h'
export const input = (
<value>
<document>
<paragraph key="a">
<text key="b">one</text>
</paragraph>
<paragraph key="c">
<text key="d">
two
<cursor />
</text>
</paragraph>
<paragraph key="e">
<text key="f">three</text>
</paragraph>
</document>
</value>
)
export default function({ document, selection }) {
return document
.getNodesAtRange(selection)
.map(n => n.key)
.toArray()
}
export const output = ['c', 'd']

View File

@@ -0,0 +1,27 @@
/** @jsx h */
import h from '../../../helpers/h'
export const input = (
<value>
<document>
<paragraph key="a">
<text key="b">
on<cursor />e
</text>
</paragraph>
<paragraph key="c">
<text key="d">two</text>
</paragraph>
</document>
</value>
)
export default function({ document, selection }) {
return document
.getNodesAtRange(selection)
.map(n => n.key)
.toArray()
}
export const output = ['a', 'b']

View File

@@ -0,0 +1,27 @@
/** @jsx h */
import h from '../../../helpers/h'
export const input = (
<value>
<document>
<paragraph key="a">
<text key="b">one</text>
<inline type="link" key="c">
<text key="d">
tw<cursor />o
</text>
</inline>
</paragraph>
</document>
</value>
)
export default function({ document, selection }) {
return document
.getNodesAtRange(selection)
.map(n => n.key)
.toArray()
}
export const output = ['a', 'c', 'd']

View File

@@ -0,0 +1,26 @@
/** @jsx h */
import h from '../../../helpers/h'
export const input = (
<value>
<document>
<image key="a" src="https://example.com/image2.png">
<text key="b" />
</image>
</document>
<selection isFocused={false}>
<anchor key="b" offset={0} />
<focus key="b" offset={0} />
</selection>
</value>
)
export default function({ document, selection }) {
return document
.getNodesAtRange(selection)
.map(n => n.key)
.toArray()
}
export const output = ['a', 'b']