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:
committed by
Ian Storm Taylor
parent
f2f97e502e
commit
e6d611d726
@@ -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`
|
||||
|
@@ -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',
|
||||
|
@@ -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']
|
@@ -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']
|
@@ -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']
|
@@ -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']
|
@@ -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']
|
@@ -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']
|
@@ -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']
|
@@ -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']
|
@@ -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']
|
@@ -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']
|
@@ -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']
|
@@ -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']
|
Reference in New Issue
Block a user