2016-06-15 12:07:12 -07:00
|
|
|
|
2016-06-21 16:44:11 -07:00
|
|
|
import Block from './block'
|
2016-06-19 12:12:23 -07:00
|
|
|
import Character from './character'
|
2016-06-22 18:42:49 -07:00
|
|
|
import Data from './data'
|
2016-06-20 13:58:04 -07:00
|
|
|
import Mark from './mark'
|
2016-06-19 12:12:23 -07:00
|
|
|
import Selection from './selection'
|
|
|
|
import Text from './text'
|
2016-06-23 11:32:24 -07:00
|
|
|
import { List, Map, Set } from 'immutable'
|
2016-06-15 12:07:12 -07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Node.
|
2016-06-19 11:51:50 -07:00
|
|
|
*
|
2016-06-21 16:44:11 -07:00
|
|
|
* And interface that `Document`, `Block` and `Inline` all implement, to make
|
|
|
|
* working with the recursive node tree easier.
|
2016-06-15 12:07:12 -07:00
|
|
|
*/
|
|
|
|
|
2016-06-19 11:51:50 -07:00
|
|
|
const Node = {
|
2016-06-16 12:12:50 -07:00
|
|
|
|
2016-06-20 12:57:31 -07:00
|
|
|
/**
|
|
|
|
* Assert that the node has a child by `key`.
|
|
|
|
*
|
|
|
|
* @param {String or Node} key
|
|
|
|
*/
|
|
|
|
|
2016-06-23 12:34:47 -07:00
|
|
|
assertHasChild(key) {
|
|
|
|
key = normalizeKey(key)
|
|
|
|
if (!this.hasChild(key)) {
|
|
|
|
throw new Error(`Could not find a child node with key "${key}".`)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Assert that the node has a descendant by `key`.
|
|
|
|
*
|
|
|
|
* @param {String or Node} key
|
|
|
|
*/
|
|
|
|
|
|
|
|
assertHasDescendant(key) {
|
|
|
|
key = normalizeKey(key)
|
|
|
|
if (!this.hasDescendant(key)) {
|
|
|
|
throw new Error(`Could not find a descendant node with key "${key}".`)
|
|
|
|
}
|
2016-06-20 12:57:31 -07:00
|
|
|
},
|
|
|
|
|
2016-06-19 12:12:23 -07:00
|
|
|
/**
|
|
|
|
* Delete everything in a `range`.
|
|
|
|
*
|
|
|
|
* @param {Selection} range
|
|
|
|
* @return {Node} node
|
|
|
|
*/
|
|
|
|
|
|
|
|
deleteAtRange(range) {
|
|
|
|
let node = this
|
2016-06-21 14:49:08 -07:00
|
|
|
range = range.normalize(node)
|
2016-06-19 12:12:23 -07:00
|
|
|
|
|
|
|
// If the range is collapsed, there's nothing to do.
|
|
|
|
if (range.isCollapsed) return node
|
|
|
|
|
|
|
|
// Make sure the children exist.
|
|
|
|
const { startKey, startOffset, endKey, endOffset } = range
|
2016-06-23 12:34:47 -07:00
|
|
|
node.assertHasDescendant(startKey)
|
|
|
|
node.assertHasDescendant(endKey)
|
2016-06-19 12:12:23 -07:00
|
|
|
|
2016-06-23 12:34:47 -07:00
|
|
|
let startNode = node.getDescendant(startKey)
|
2016-06-19 12:12:23 -07:00
|
|
|
|
|
|
|
// If the start and end nodes are the same, remove the matching characters.
|
|
|
|
if (startKey == endKey) {
|
2016-06-23 11:32:24 -07:00
|
|
|
const characters = startNode.characters.filterNot((char, i) => {
|
2016-06-19 12:12:23 -07:00
|
|
|
return startOffset <= i && i < endOffset
|
|
|
|
})
|
|
|
|
|
|
|
|
startNode = startNode.merge({ characters })
|
2016-06-22 18:42:49 -07:00
|
|
|
node = node.updateDeep(startNode)
|
2016-06-19 12:12:23 -07:00
|
|
|
return node
|
|
|
|
}
|
|
|
|
|
|
|
|
// Otherwise, remove the text from the first and last nodes...
|
|
|
|
const startRange = Selection.create({
|
|
|
|
anchorKey: startKey,
|
|
|
|
anchorOffset: startOffset,
|
|
|
|
focusKey: startKey,
|
|
|
|
focusOffset: startNode.length
|
|
|
|
})
|
|
|
|
|
|
|
|
const endRange = Selection.create({
|
|
|
|
anchorKey: endKey,
|
|
|
|
anchorOffset: 0,
|
|
|
|
focusKey: endKey,
|
|
|
|
focusOffset: endOffset
|
|
|
|
})
|
|
|
|
|
|
|
|
node = node.deleteAtRange(startRange)
|
|
|
|
node = node.deleteAtRange(endRange)
|
|
|
|
|
|
|
|
// Then remove any nodes in between the top-most start and end nodes...
|
2016-06-22 18:42:49 -07:00
|
|
|
let startParent = node.getParent(startKey)
|
|
|
|
let endParent = node.getParent(endKey)
|
2016-06-19 12:12:23 -07:00
|
|
|
|
|
|
|
const startGrandestParent = node.nodes.find((child) => {
|
2016-06-23 12:34:47 -07:00
|
|
|
return child == startParent || child.hasDescendant(startParent)
|
2016-06-19 12:12:23 -07:00
|
|
|
})
|
|
|
|
|
|
|
|
const endGrandestParent = node.nodes.find((child) => {
|
2016-06-23 12:34:47 -07:00
|
|
|
return child == endParent || child.hasDescendant(endParent)
|
2016-06-19 12:12:23 -07:00
|
|
|
})
|
|
|
|
|
|
|
|
const nodes = node.nodes
|
|
|
|
.takeUntil(child => child == startGrandestParent)
|
2016-06-23 11:32:24 -07:00
|
|
|
.push(startGrandestParent)
|
2016-06-19 12:12:23 -07:00
|
|
|
.concat(node.nodes.skipUntil(child => child == endGrandestParent))
|
|
|
|
|
|
|
|
node = node.merge({ nodes })
|
|
|
|
|
|
|
|
// Then add the end parent's nodes to the start parent node.
|
|
|
|
const newNodes = startParent.nodes.concat(endParent.nodes)
|
|
|
|
startParent = startParent.merge({ nodes: newNodes })
|
2016-06-22 18:42:49 -07:00
|
|
|
node = node.updateDeep(startParent)
|
2016-06-19 12:12:23 -07:00
|
|
|
|
|
|
|
// Then remove the end parent.
|
2016-06-22 18:42:49 -07:00
|
|
|
let endGrandparent = node.getParent(endParent)
|
2016-06-19 12:12:23 -07:00
|
|
|
if (endGrandparent == node) {
|
2016-06-23 12:34:47 -07:00
|
|
|
node = node.removeDescendant(endParent)
|
2016-06-19 12:12:23 -07:00
|
|
|
} else {
|
2016-06-23 12:34:47 -07:00
|
|
|
endGrandparent = endGrandparent.removeDescendant(endParent)
|
2016-06-22 18:42:49 -07:00
|
|
|
node = node.updateDeep(endGrandparent)
|
2016-06-19 12:12:23 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// Normalize the node.
|
|
|
|
return node.normalize()
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Delete backward `n` characters at a `range`.
|
|
|
|
*
|
|
|
|
* @param {Selection} range
|
|
|
|
* @param {Number} n (optional)
|
|
|
|
* @return {Node} node
|
|
|
|
*/
|
|
|
|
|
|
|
|
deleteBackwardAtRange(range, n = 1) {
|
|
|
|
let node = this
|
2016-06-21 14:49:08 -07:00
|
|
|
range = range.normalize(node)
|
2016-06-19 12:12:23 -07:00
|
|
|
|
|
|
|
// When collapsed at the start of the node, there's nothing to do.
|
|
|
|
if (range.isCollapsed && range.isAtStartOf(node)) return node
|
|
|
|
|
|
|
|
// When the range is still expanded, just do a regular delete.
|
|
|
|
if (range.isExpanded) return node.deleteAtRange(range)
|
|
|
|
|
|
|
|
// When at start of a text node, merge forwards into the next text node.
|
|
|
|
const { startKey } = range
|
2016-06-23 12:34:47 -07:00
|
|
|
const startNode = node.getDescendant(startKey)
|
2016-06-19 12:12:23 -07:00
|
|
|
|
|
|
|
if (range.isAtStartOf(startNode)) {
|
2016-06-22 18:42:49 -07:00
|
|
|
const previous = node.getPreviousText(startNode)
|
2016-06-23 15:39:44 -07:00
|
|
|
range = range.extendToEndOf(previous)
|
|
|
|
range = range.normalize(node)
|
2016-06-19 12:12:23 -07:00
|
|
|
node = node.deleteAtRange(range)
|
|
|
|
return node
|
|
|
|
}
|
|
|
|
|
|
|
|
// Otherwise, remove `n` characters behind of the cursor.
|
|
|
|
range = range.extendBackward(n)
|
|
|
|
node = node.deleteAtRange(range)
|
|
|
|
|
|
|
|
// Normalize the node.
|
|
|
|
return node.normalize()
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Delete forward `n` characters at a `range`.
|
|
|
|
*
|
|
|
|
* @param {Selection} range
|
|
|
|
* @param {Number} n (optional)
|
|
|
|
* @return {Node} node
|
|
|
|
*/
|
|
|
|
|
|
|
|
deleteForwardAtRange(range, n = 1) {
|
|
|
|
let node = this
|
2016-06-21 14:49:08 -07:00
|
|
|
range = range.normalize(node)
|
2016-06-19 12:12:23 -07:00
|
|
|
|
|
|
|
// When collapsed at the end of the node, there's nothing to do.
|
|
|
|
if (range.isCollapsed && range.isAtEndOf(node)) return node
|
|
|
|
|
|
|
|
// When the range is still expanded, just do a regular delete.
|
|
|
|
if (range.isExpanded) return node.deleteAtRange(range)
|
|
|
|
|
|
|
|
// When at end of a text node, merge forwards into the next text node.
|
|
|
|
const { startKey } = range
|
2016-06-23 12:34:47 -07:00
|
|
|
const startNode = node.getDescendant(startKey)
|
2016-06-19 12:12:23 -07:00
|
|
|
|
|
|
|
if (range.isAtEndOf(startNode)) {
|
2016-06-22 18:42:49 -07:00
|
|
|
const next = node.getNextText(startNode)
|
2016-06-23 15:39:44 -07:00
|
|
|
range = range.extendToStartOf(next)
|
|
|
|
range = range.normalize(node)
|
2016-06-19 12:12:23 -07:00
|
|
|
node = node.deleteAtRange(range)
|
|
|
|
return node
|
|
|
|
}
|
|
|
|
|
|
|
|
// Otherwise, remove `n` characters ahead of the cursor.
|
|
|
|
range = range.extendForward(n)
|
|
|
|
node = node.deleteAtRange(range)
|
|
|
|
|
|
|
|
// Normalize the node.
|
|
|
|
return node.normalize()
|
|
|
|
},
|
|
|
|
|
2016-06-16 12:12:50 -07:00
|
|
|
/**
|
2016-06-23 12:34:47 -07:00
|
|
|
* Recursively find all ancestor nodes by `iterator`.
|
2016-06-16 12:12:50 -07:00
|
|
|
*
|
|
|
|
* @param {Function} iterator
|
|
|
|
* @return {Node} node
|
|
|
|
*/
|
|
|
|
|
2016-06-23 12:34:47 -07:00
|
|
|
findDescendant(iterator) {
|
|
|
|
return (
|
|
|
|
this.nodes.find(iterator) ||
|
|
|
|
this.nodes
|
|
|
|
.map(node => node.kind == 'text' ? null : node.findDescendant(iterator))
|
|
|
|
.find(exists => exists)
|
|
|
|
)
|
2016-06-19 11:51:50 -07:00
|
|
|
},
|
2016-06-16 12:12:50 -07:00
|
|
|
|
2016-06-15 19:46:53 -07:00
|
|
|
/**
|
2016-06-23 12:34:47 -07:00
|
|
|
* Recursively filter all ancestor nodes with `iterator`.
|
2016-06-15 19:46:53 -07:00
|
|
|
*
|
|
|
|
* @param {Function} iterator
|
2016-06-23 12:34:47 -07:00
|
|
|
* @return {List} nodes
|
2016-06-15 19:46:53 -07:00
|
|
|
*/
|
|
|
|
|
2016-06-23 12:34:47 -07:00
|
|
|
filterDescendants(iterator) {
|
2016-06-23 11:32:24 -07:00
|
|
|
return this.nodes.reduce((matches, child, i, nodes) => {
|
|
|
|
if (iterator(child, i, nodes)) matches = matches.push(child)
|
2016-06-23 12:34:47 -07:00
|
|
|
if (child.kind != 'text') matches = matches.concat(child.filterDescendants(iterator))
|
2016-06-22 18:42:49 -07:00
|
|
|
return matches
|
2016-06-23 11:32:24 -07:00
|
|
|
}, Block.createList())
|
2016-06-22 18:42:49 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the closest block nodes for each text node in a `range`.
|
|
|
|
*
|
|
|
|
* @param {Selection} range
|
2016-06-23 12:34:47 -07:00
|
|
|
* @return {List} nodes
|
2016-06-22 18:42:49 -07:00
|
|
|
*/
|
|
|
|
|
|
|
|
getBlocksAtRange(range) {
|
|
|
|
range = range.normalize(this)
|
2016-06-23 12:34:47 -07:00
|
|
|
return this
|
|
|
|
.getTextsAtRange(range)
|
|
|
|
.map(text => this.getClosestBlock(text))
|
2016-06-19 11:51:50 -07:00
|
|
|
},
|
2016-06-15 19:46:53 -07:00
|
|
|
|
2016-06-20 12:57:31 -07:00
|
|
|
/**
|
|
|
|
* Get a list of the characters in a `range`.
|
|
|
|
*
|
|
|
|
* @param {Selection} range
|
|
|
|
* @return {List} characters
|
|
|
|
*/
|
|
|
|
|
|
|
|
getCharactersAtRange(range) {
|
2016-06-21 14:49:08 -07:00
|
|
|
range = range.normalize(this)
|
2016-06-23 12:34:47 -07:00
|
|
|
return this
|
|
|
|
.getTextsAtRange(range)
|
|
|
|
.reduce((characters, text) => {
|
|
|
|
const chars = text.characters.filter((char, i) => isInRange(i, text, range))
|
|
|
|
return characters.concat(chars)
|
|
|
|
}, Character.createList())
|
2016-06-20 12:57:31 -07:00
|
|
|
},
|
|
|
|
|
2016-06-22 18:42:49 -07:00
|
|
|
/**
|
2016-06-23 09:15:51 -07:00
|
|
|
* Get closest parent of node by `key` that matches `iterator`.
|
2016-06-22 18:42:49 -07:00
|
|
|
*
|
2016-06-23 09:15:51 -07:00
|
|
|
* @param {String or Node} key
|
2016-06-22 18:42:49 -07:00
|
|
|
* @param {Function} iterator
|
2016-06-23 12:34:47 -07:00
|
|
|
* @return {Node or Null} node
|
2016-06-22 18:42:49 -07:00
|
|
|
*/
|
|
|
|
|
2016-06-23 09:15:51 -07:00
|
|
|
getClosest(key, iterator) {
|
2016-06-23 12:34:47 -07:00
|
|
|
let node = this.getDescendant(key)
|
2016-06-23 09:15:51 -07:00
|
|
|
|
|
|
|
while (node = this.getParent(node)) {
|
2016-06-22 18:42:49 -07:00
|
|
|
if (node == this) return null
|
|
|
|
if (iterator(node)) return node
|
|
|
|
}
|
|
|
|
|
|
|
|
return null
|
|
|
|
},
|
|
|
|
|
2016-06-23 09:15:51 -07:00
|
|
|
/**
|
|
|
|
* Get the closest block parent of a `node`.
|
|
|
|
*
|
|
|
|
* @param {String or Node} key
|
2016-06-23 12:34:47 -07:00
|
|
|
* @return {Node or Null} node
|
2016-06-23 09:15:51 -07:00
|
|
|
*/
|
|
|
|
|
|
|
|
getClosestBlock(key) {
|
2016-06-23 12:34:47 -07:00
|
|
|
return this.getClosest(key, parent => parent.kind == 'block')
|
2016-06-23 09:15:51 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the closest inline parent of a `node`.
|
|
|
|
*
|
|
|
|
* @param {String or Node} key
|
2016-06-23 12:34:47 -07:00
|
|
|
* @return {Node or Null} node
|
2016-06-23 09:15:51 -07:00
|
|
|
*/
|
|
|
|
|
|
|
|
getClosestInline(key) {
|
2016-06-23 12:34:47 -07:00
|
|
|
return this.getClosest(key, parent => parent.kind == 'inline')
|
2016-06-23 09:15:51 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
2016-06-23 12:34:47 -07:00
|
|
|
* Get a child node by `key`.
|
2016-06-23 09:15:51 -07:00
|
|
|
*
|
2016-06-23 12:34:47 -07:00
|
|
|
* @param {String} key
|
|
|
|
* @return {Node or Null} node
|
2016-06-23 09:15:51 -07:00
|
|
|
*/
|
|
|
|
|
2016-06-23 12:34:47 -07:00
|
|
|
getChild(key) {
|
2016-06-23 09:15:51 -07:00
|
|
|
key = normalizeKey(key)
|
2016-06-23 12:34:47 -07:00
|
|
|
return this.nodes.find(node => node.key == key)
|
2016-06-23 09:15:51 -07:00
|
|
|
},
|
|
|
|
|
2016-06-22 18:42:49 -07:00
|
|
|
/**
|
2016-06-23 12:34:47 -07:00
|
|
|
* Get a descendant node by `key`.
|
2016-06-22 18:42:49 -07:00
|
|
|
*
|
|
|
|
* @param {String} key
|
2016-06-23 12:34:47 -07:00
|
|
|
* @return {Node or Null} node
|
2016-06-22 18:42:49 -07:00
|
|
|
*/
|
|
|
|
|
2016-06-23 12:34:47 -07:00
|
|
|
getDescendant(key) {
|
2016-06-22 18:42:49 -07:00
|
|
|
key = normalizeKey(key)
|
2016-06-23 12:34:47 -07:00
|
|
|
return this.findDescendant(node => node.key == key)
|
2016-06-22 18:42:49 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the depth of a child node by `key`, with optional `startAt`.
|
|
|
|
*
|
|
|
|
* @param {String or Node} key
|
|
|
|
* @param {Number} startAt (optional)
|
|
|
|
* @return {Number} depth
|
|
|
|
*/
|
|
|
|
|
|
|
|
getDepth(key, startAt = 1) {
|
|
|
|
key = normalizeKey(key)
|
2016-06-23 12:34:47 -07:00
|
|
|
this.assertHasDescendant(key)
|
2016-06-23 09:15:51 -07:00
|
|
|
|
2016-06-23 11:32:24 -07:00
|
|
|
const shallow = this.nodes.find(node => node.key == key)
|
|
|
|
if (shallow) return startAt
|
2016-06-22 18:42:49 -07:00
|
|
|
|
|
|
|
const child = this.nodes.find(node => {
|
|
|
|
return node.kind == 'text'
|
|
|
|
? null
|
2016-06-23 12:34:47 -07:00
|
|
|
: node.hasDescendant(key)
|
2016-06-22 18:42:49 -07:00
|
|
|
})
|
|
|
|
|
|
|
|
return child
|
|
|
|
? child.getDepth(key, startAt + 1)
|
|
|
|
: null
|
|
|
|
},
|
|
|
|
|
2016-06-20 13:21:24 -07:00
|
|
|
/**
|
2016-06-23 12:34:47 -07:00
|
|
|
* Get the furthest block parent of a node by `key`.
|
2016-06-20 13:21:24 -07:00
|
|
|
*
|
2016-06-23 12:34:47 -07:00
|
|
|
* @param {String or Node} key
|
|
|
|
* @return {Node or Null} node
|
2016-06-20 13:21:24 -07:00
|
|
|
*/
|
|
|
|
|
2016-06-23 12:34:47 -07:00
|
|
|
getFurthestBlock(key) {
|
|
|
|
let node = this.getDescendant(key)
|
|
|
|
let furthest = null
|
|
|
|
|
|
|
|
while (node = this.getClosestBlock(node)) {
|
|
|
|
furthest = node
|
|
|
|
}
|
|
|
|
|
|
|
|
return furthest
|
2016-06-22 18:42:49 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
2016-06-23 12:34:47 -07:00
|
|
|
* Get the furthest inline parent of a node by `key`.
|
2016-06-22 18:42:49 -07:00
|
|
|
*
|
2016-06-23 12:34:47 -07:00
|
|
|
* @param {String or Node} key
|
|
|
|
* @return {Node or Null} node
|
2016-06-22 18:42:49 -07:00
|
|
|
*/
|
|
|
|
|
2016-06-23 12:34:47 -07:00
|
|
|
getFurthestInline(key) {
|
|
|
|
let node = this.getDescendant(key)
|
|
|
|
let furthest = null
|
|
|
|
|
|
|
|
while (node = this.getClosestInline(node)) {
|
|
|
|
furthest = node
|
|
|
|
}
|
2016-06-22 18:42:49 -07:00
|
|
|
|
2016-06-23 12:34:47 -07:00
|
|
|
return furthest
|
2016-06-20 13:21:24 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
2016-06-23 12:34:47 -07:00
|
|
|
* Get the closest inline nodes for each text node in a `range`.
|
2016-06-20 13:21:24 -07:00
|
|
|
*
|
2016-06-23 12:34:47 -07:00
|
|
|
* @param {Selection} range
|
|
|
|
* @return {List} nodes
|
2016-06-20 13:21:24 -07:00
|
|
|
*/
|
|
|
|
|
2016-06-23 12:34:47 -07:00
|
|
|
getInlinesAtRange(range) {
|
|
|
|
range = range.normalize(this)
|
|
|
|
|
|
|
|
// If the range isn't set, return an empty list.
|
|
|
|
if (range.isUnset) return Inline.createList()
|
|
|
|
|
|
|
|
return this
|
|
|
|
.getTextsAtRange(range)
|
|
|
|
.map(text => this.getClosestInline(text))
|
|
|
|
.filter(exists => exists)
|
2016-06-20 13:21:24 -07:00
|
|
|
},
|
|
|
|
|
2016-06-20 12:57:31 -07:00
|
|
|
/**
|
|
|
|
* Get a set of the marks in a `range`.
|
|
|
|
*
|
|
|
|
* @param {Selection} range
|
|
|
|
* @return {Set} marks
|
|
|
|
*/
|
|
|
|
|
|
|
|
getMarksAtRange(range) {
|
2016-06-21 14:49:08 -07:00
|
|
|
range = range.normalize(this)
|
2016-06-23 12:34:47 -07:00
|
|
|
const { startKey, startOffset } = range
|
|
|
|
const marks = Mark.createSet()
|
2016-06-20 12:57:31 -07:00
|
|
|
|
2016-06-23 12:34:47 -07:00
|
|
|
// If the range isn't set, return an empty set.
|
|
|
|
if (range.isUnset) return marks
|
2016-06-20 12:57:31 -07:00
|
|
|
|
2016-06-23 12:34:47 -07:00
|
|
|
// If the range is collapsed at the start of the node, check the previous.
|
2016-06-20 12:57:31 -07:00
|
|
|
if (range.isCollapsed && startOffset == 0) {
|
2016-06-22 18:42:49 -07:00
|
|
|
const previous = this.getPreviousText(startKey)
|
2016-06-23 12:34:47 -07:00
|
|
|
if (!previous) return marks
|
2016-06-20 12:57:31 -07:00
|
|
|
const char = text.characters.get(previous.length - 1)
|
|
|
|
return char.marks
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the range is collapsed, check the character before the start.
|
|
|
|
if (range.isCollapsed) {
|
2016-06-23 12:34:47 -07:00
|
|
|
const text = this.getDescendant(startKey)
|
2016-06-20 12:57:31 -07:00
|
|
|
const char = text.characters.get(range.startOffset - 1)
|
|
|
|
return char.marks
|
|
|
|
}
|
|
|
|
|
|
|
|
// Otherwise, get a set of the marks for each character in the range.
|
2016-06-23 12:34:47 -07:00
|
|
|
this
|
|
|
|
.getCharactersAtRange(range)
|
|
|
|
.reduce((marks, char) => {
|
|
|
|
return marks.union(char.marks)
|
|
|
|
}, marks)
|
2016-06-20 12:57:31 -07:00
|
|
|
},
|
|
|
|
|
2016-06-16 16:43:02 -07:00
|
|
|
/**
|
2016-06-23 12:34:47 -07:00
|
|
|
* Get the node after a descendant by `key`.
|
2016-06-16 16:43:02 -07:00
|
|
|
*
|
2016-06-22 18:42:49 -07:00
|
|
|
* @param {String or Node} key
|
2016-06-23 12:34:47 -07:00
|
|
|
* @return {Node or Null} node
|
2016-06-16 16:43:02 -07:00
|
|
|
*/
|
|
|
|
|
2016-06-22 18:42:49 -07:00
|
|
|
getNextSibling(key) {
|
2016-06-23 12:34:47 -07:00
|
|
|
const node = this.getDescendant(key)
|
|
|
|
if (!node) return null
|
|
|
|
return this
|
|
|
|
.getParent(node)
|
|
|
|
.nodes
|
|
|
|
.skipUntil(child => child == node)
|
|
|
|
.get(1)
|
2016-06-22 18:42:49 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
2016-06-23 12:34:47 -07:00
|
|
|
* Get the text node after a descendant text node by `key`.
|
2016-06-22 18:42:49 -07:00
|
|
|
*
|
|
|
|
* @param {String or Node} key
|
|
|
|
* @return {Node or Null} node
|
|
|
|
*/
|
|
|
|
|
|
|
|
getNextText(key) {
|
|
|
|
key = normalizeKey(key)
|
|
|
|
return this.getTextNodes()
|
|
|
|
.skipUntil(text => text.key == key)
|
2016-06-23 12:34:47 -07:00
|
|
|
.get(1)
|
2016-06-19 11:51:50 -07:00
|
|
|
},
|
2016-06-16 16:43:02 -07:00
|
|
|
|
2016-06-20 12:57:31 -07:00
|
|
|
/**
|
2016-06-23 12:34:47 -07:00
|
|
|
* Get the offset for a descendant text node by `key`.
|
2016-06-20 12:57:31 -07:00
|
|
|
*
|
2016-06-20 17:38:56 -07:00
|
|
|
* @param {String or Node} key
|
|
|
|
* @return {Number} offset
|
2016-06-20 12:57:31 -07:00
|
|
|
*/
|
|
|
|
|
2016-06-22 18:42:49 -07:00
|
|
|
getOffset(key) {
|
2016-06-23 09:15:51 -07:00
|
|
|
key = normalizeKey(key)
|
2016-06-23 12:34:47 -07:00
|
|
|
this.assertHasDescendant(key)
|
2016-06-23 09:15:51 -07:00
|
|
|
|
2016-06-22 18:51:30 -07:00
|
|
|
// Find the shallow matching child.
|
2016-06-23 15:39:44 -07:00
|
|
|
const isChild = this.hasChild(key)
|
|
|
|
const child = isChild
|
|
|
|
? this.getChild(key)
|
|
|
|
: this.nodes.find(node => node.hasDescendant && node.hasDescendant(key))
|
|
|
|
|
|
|
|
// Calculate the offset of the nodes before the child.
|
|
|
|
const offset = this.nodes
|
|
|
|
.takeUntil(node => node == child)
|
|
|
|
.reduce((offset, child) => offset + child.length, 0)
|
|
|
|
|
|
|
|
// Recurse if need be.
|
|
|
|
return isChild
|
2016-06-20 12:57:31 -07:00
|
|
|
? offset
|
2016-06-22 18:42:49 -07:00
|
|
|
: offset + child.getOffset(key)
|
2016-06-20 12:57:31 -07:00
|
|
|
},
|
|
|
|
|
2016-06-16 16:43:02 -07:00
|
|
|
/**
|
2016-06-22 18:42:49 -07:00
|
|
|
* Get the parent of a child node by `key`.
|
2016-06-16 16:43:02 -07:00
|
|
|
*
|
2016-06-17 00:09:54 -07:00
|
|
|
* @param {String or Node} key
|
2016-06-23 12:34:47 -07:00
|
|
|
* @return {Node or Null} node
|
2016-06-16 16:43:02 -07:00
|
|
|
*/
|
|
|
|
|
2016-06-22 18:42:49 -07:00
|
|
|
getParent(key) {
|
2016-06-20 12:57:31 -07:00
|
|
|
key = normalizeKey(key)
|
2016-06-23 12:34:47 -07:00
|
|
|
if (this.hasChild(key)) return this
|
2016-06-16 16:43:02 -07:00
|
|
|
|
2016-06-22 18:51:30 -07:00
|
|
|
let node = null
|
2016-06-23 12:34:47 -07:00
|
|
|
|
2016-06-22 18:42:49 -07:00
|
|
|
this.nodes.forEach((child) => {
|
|
|
|
if (child.kind == 'text') return
|
|
|
|
const match = child.getParent(key)
|
|
|
|
if (match) node = match
|
|
|
|
})
|
2016-06-16 16:43:02 -07:00
|
|
|
|
2016-06-22 18:42:49 -07:00
|
|
|
return node
|
2016-06-19 11:51:50 -07:00
|
|
|
},
|
2016-06-16 16:43:02 -07:00
|
|
|
|
2016-06-17 00:09:54 -07:00
|
|
|
/**
|
2016-06-23 12:34:47 -07:00
|
|
|
* Get the node before a descendant node by `key`.
|
2016-06-17 00:09:54 -07:00
|
|
|
*
|
|
|
|
* @param {String or Node} key
|
2016-06-23 12:34:47 -07:00
|
|
|
* @return {Node or Null} node
|
2016-06-17 00:09:54 -07:00
|
|
|
*/
|
|
|
|
|
2016-06-22 18:42:49 -07:00
|
|
|
getPreviousSibling(key) {
|
2016-06-23 12:34:47 -07:00
|
|
|
const node = this.getDescendant(key)
|
|
|
|
if (!node) return null
|
|
|
|
return this
|
|
|
|
.getParent(node)
|
|
|
|
.nodes
|
|
|
|
.takeUntil(child => child == node)
|
|
|
|
.last()
|
2016-06-19 11:51:50 -07:00
|
|
|
},
|
2016-06-17 00:09:54 -07:00
|
|
|
|
2016-06-20 12:57:31 -07:00
|
|
|
/**
|
2016-06-23 12:34:47 -07:00
|
|
|
* Get the text node before a descendant text node by `key`.
|
2016-06-17 00:09:54 -07:00
|
|
|
*
|
|
|
|
* @param {String or Node} key
|
2016-06-22 18:42:49 -07:00
|
|
|
* @return {Node or Null} node
|
2016-06-17 00:09:54 -07:00
|
|
|
*/
|
|
|
|
|
2016-06-22 18:42:49 -07:00
|
|
|
getPreviousText(key) {
|
2016-06-20 12:57:31 -07:00
|
|
|
key = normalizeKey(key)
|
2016-06-22 18:42:49 -07:00
|
|
|
return this.getTextNodes()
|
|
|
|
.takeUntil(text => text.key == key)
|
|
|
|
.last()
|
2016-06-19 11:51:50 -07:00
|
|
|
},
|
2016-06-17 00:09:54 -07:00
|
|
|
|
2016-06-16 16:43:02 -07:00
|
|
|
/**
|
2016-06-23 12:34:47 -07:00
|
|
|
* Get the descendent text node at an `offset`.
|
2016-06-16 16:43:02 -07:00
|
|
|
*
|
|
|
|
* @param {String} offset
|
2016-06-23 12:34:47 -07:00
|
|
|
* @return {Node or Null} node
|
2016-06-16 16:43:02 -07:00
|
|
|
*/
|
|
|
|
|
2016-06-22 18:42:49 -07:00
|
|
|
getTextAtOffset(offset) {
|
2016-06-21 16:44:11 -07:00
|
|
|
let length = 0
|
2016-06-23 12:34:47 -07:00
|
|
|
return this
|
|
|
|
.getTextNodes()
|
|
|
|
.find((text) => {
|
|
|
|
length += text.length
|
|
|
|
return length >= offset
|
|
|
|
})
|
2016-06-19 11:51:50 -07:00
|
|
|
},
|
2016-06-16 16:43:02 -07:00
|
|
|
|
2016-06-22 18:42:49 -07:00
|
|
|
/**
|
|
|
|
* Recursively get all of the child text nodes in order of appearance.
|
|
|
|
*
|
2016-06-23 12:34:47 -07:00
|
|
|
* @return {List} nodes
|
2016-06-22 18:42:49 -07:00
|
|
|
*/
|
|
|
|
|
|
|
|
getTextNodes() {
|
|
|
|
return this.nodes.reduce((texts, node) => {
|
|
|
|
return node.kind == 'text'
|
2016-06-23 11:32:24 -07:00
|
|
|
? texts.push(node)
|
2016-06-22 18:42:49 -07:00
|
|
|
: texts.concat(node.getTextNodes())
|
2016-06-23 11:32:24 -07:00
|
|
|
}, Block.createList())
|
2016-06-22 18:42:49 -07:00
|
|
|
},
|
|
|
|
|
2016-06-20 12:57:31 -07:00
|
|
|
/**
|
|
|
|
* Get all of the text nodes in a `range`.
|
|
|
|
*
|
|
|
|
* @param {Selection} range
|
2016-06-23 12:34:47 -07:00
|
|
|
* @return {List} nodes
|
2016-06-20 12:57:31 -07:00
|
|
|
*/
|
|
|
|
|
2016-06-22 18:59:19 -07:00
|
|
|
getTextsAtRange(range) {
|
2016-06-21 14:49:08 -07:00
|
|
|
range = range.normalize(this)
|
2016-06-20 12:57:31 -07:00
|
|
|
|
2016-06-23 12:34:47 -07:00
|
|
|
// If the selection is unset, return an empty list.
|
|
|
|
if (range.isUnset) return Block.createList()
|
2016-06-20 12:57:31 -07:00
|
|
|
|
2016-06-23 12:34:47 -07:00
|
|
|
const { startKey, endKey } = range
|
2016-06-22 18:42:49 -07:00
|
|
|
const texts = this.getTextNodes()
|
2016-06-23 12:34:47 -07:00
|
|
|
const startText = this.getDescendant(startKey)
|
|
|
|
const endText = this.getDescendant(endKey)
|
|
|
|
const start = texts.indexOf(startText)
|
|
|
|
const end = texts.indexOf(endText)
|
|
|
|
return texts.slice(start, end + 1)
|
2016-06-20 17:38:56 -07:00
|
|
|
},
|
2016-06-20 12:57:31 -07:00
|
|
|
|
2016-06-16 16:43:02 -07:00
|
|
|
/**
|
2016-06-23 12:34:47 -07:00
|
|
|
* Check if a child node exists by `key`.
|
2016-06-16 16:43:02 -07:00
|
|
|
*
|
2016-06-17 00:09:54 -07:00
|
|
|
* @param {String or Node} key
|
2016-06-23 12:34:47 -07:00
|
|
|
* @return {Boolean} exists
|
2016-06-16 16:43:02 -07:00
|
|
|
*/
|
|
|
|
|
2016-06-23 12:34:47 -07:00
|
|
|
hasChild(key) {
|
2016-06-20 12:57:31 -07:00
|
|
|
key = normalizeKey(key)
|
2016-06-23 12:34:47 -07:00
|
|
|
return !! this.nodes.find(node => node.key == key)
|
|
|
|
},
|
2016-06-23 09:15:51 -07:00
|
|
|
|
2016-06-23 12:34:47 -07:00
|
|
|
/**
|
|
|
|
* Recursively check if a child node exists by `key`.
|
|
|
|
*
|
|
|
|
* @param {String or Node} key
|
|
|
|
* @return {Boolean} exists
|
|
|
|
*/
|
|
|
|
|
|
|
|
hasDescendant(key) {
|
|
|
|
key = normalizeKey(key)
|
2016-06-21 17:08:15 -07:00
|
|
|
return !! this.nodes.find((node) => {
|
|
|
|
return node.kind == 'text'
|
|
|
|
? node.key == key
|
2016-06-23 12:34:47 -07:00
|
|
|
: node.key == key || node.hasDescendant(key)
|
2016-06-21 17:08:15 -07:00
|
|
|
})
|
2016-06-19 11:51:50 -07:00
|
|
|
},
|
2016-06-16 16:43:02 -07:00
|
|
|
|
2016-06-19 12:12:23 -07:00
|
|
|
/**
|
|
|
|
* Insert `text` at a `range`.
|
|
|
|
*
|
|
|
|
* @param {Selection} range
|
|
|
|
* @param {String} text
|
2016-06-20 12:57:31 -07:00
|
|
|
* @return {Node} node
|
2016-06-19 12:12:23 -07:00
|
|
|
*/
|
|
|
|
|
|
|
|
insertTextAtRange(range, text) {
|
|
|
|
let node = this
|
2016-06-21 14:49:08 -07:00
|
|
|
range = range.normalize(node)
|
2016-06-19 12:12:23 -07:00
|
|
|
|
|
|
|
// When still expanded, remove the current range first.
|
|
|
|
if (range.isExpanded) {
|
|
|
|
node = node.deleteAtRange(range)
|
|
|
|
range = range.moveToStart()
|
|
|
|
}
|
|
|
|
|
|
|
|
let { startKey, startOffset } = range
|
2016-06-23 12:34:47 -07:00
|
|
|
let startNode = node.getDescendant(startKey)
|
2016-06-19 12:12:23 -07:00
|
|
|
let { characters } = startNode
|
|
|
|
|
2016-06-20 12:57:31 -07:00
|
|
|
// Create a list of the new characters, with the marks from the previous
|
|
|
|
// character if one exists.
|
2016-06-22 18:42:49 -07:00
|
|
|
const prev = characters.get(startOffset - 1)
|
|
|
|
const marks = prev ? prev.marks : null
|
2016-06-23 10:46:31 -07:00
|
|
|
const newChars = Character.createList(text.split('').map((char) => {
|
|
|
|
const obj = { text: char }
|
|
|
|
if (marks) obj.marks = marks
|
|
|
|
return obj
|
|
|
|
}))
|
2016-06-19 12:12:23 -07:00
|
|
|
|
|
|
|
// Splice in the new characters.
|
|
|
|
characters = characters.slice(0, startOffset)
|
2016-06-22 18:42:49 -07:00
|
|
|
.concat(newChars)
|
|
|
|
.concat(characters.slice(startOffset))
|
2016-06-19 12:12:23 -07:00
|
|
|
|
|
|
|
// Update the existing text node.
|
|
|
|
startNode = startNode.merge({ characters })
|
2016-06-22 18:42:49 -07:00
|
|
|
node = node.updateDeep(startNode)
|
2016-06-19 12:12:23 -07:00
|
|
|
|
|
|
|
// Normalize the node.
|
|
|
|
return node.normalize()
|
|
|
|
},
|
|
|
|
|
2016-06-20 12:57:31 -07:00
|
|
|
/**
|
|
|
|
* Add a new `mark` to the characters at `range`.
|
|
|
|
*
|
|
|
|
* @param {Selection} range
|
2016-06-20 13:58:04 -07:00
|
|
|
* @param {Mark or String} mark
|
2016-06-20 12:57:31 -07:00
|
|
|
* @return {Node} node
|
|
|
|
*/
|
|
|
|
|
|
|
|
markAtRange(range, mark) {
|
|
|
|
let node = this
|
2016-06-21 14:49:08 -07:00
|
|
|
range = range.normalize(node)
|
2016-06-20 12:57:31 -07:00
|
|
|
|
2016-06-20 13:58:04 -07:00
|
|
|
// Allow for just passing a type for convenience.
|
|
|
|
if (typeof mark == 'string') {
|
|
|
|
mark = new Mark({ type: mark })
|
|
|
|
}
|
|
|
|
|
2016-06-20 12:57:31 -07:00
|
|
|
// When the range is collapsed, do nothing.
|
|
|
|
if (range.isCollapsed) return node
|
|
|
|
|
|
|
|
// Otherwise, find each of the text nodes within the range.
|
|
|
|
const { startKey, startOffset, endKey, endOffset } = range
|
2016-06-22 18:59:19 -07:00
|
|
|
let texts = node.getTextsAtRange(range)
|
2016-06-20 12:57:31 -07:00
|
|
|
|
|
|
|
// Apply the mark to each of the text nodes's matching characters.
|
|
|
|
texts = texts.map((text) => {
|
|
|
|
let characters = text.characters.map((char, i) => {
|
|
|
|
if (!isInRange(i, text, range)) return char
|
|
|
|
let { marks } = char
|
|
|
|
marks = marks.add(mark)
|
|
|
|
return char.merge({ marks })
|
|
|
|
})
|
|
|
|
|
|
|
|
return text.merge({ characters })
|
|
|
|
})
|
|
|
|
|
|
|
|
// Update each of the text nodes.
|
|
|
|
texts.forEach((text) => {
|
2016-06-22 18:42:49 -07:00
|
|
|
node = node.updateDeep(text)
|
2016-06-20 12:57:31 -07:00
|
|
|
})
|
|
|
|
|
|
|
|
return node
|
|
|
|
},
|
|
|
|
|
2016-06-19 12:12:23 -07:00
|
|
|
/**
|
2016-06-21 16:44:11 -07:00
|
|
|
* Normalize the node by joining any two adjacent text child nodes.
|
2016-06-19 12:12:23 -07:00
|
|
|
*
|
|
|
|
* @return {Node} node
|
|
|
|
*/
|
|
|
|
|
|
|
|
normalize() {
|
|
|
|
let node = this
|
2016-06-21 16:44:11 -07:00
|
|
|
|
|
|
|
// See if there are any adjacent text nodes.
|
2016-06-23 12:34:47 -07:00
|
|
|
let firstAdjacent = node.findDescendant((child) => {
|
2016-06-21 16:44:11 -07:00
|
|
|
if (child.kind != 'text') return
|
2016-06-22 18:42:49 -07:00
|
|
|
const parent = node.getParent(child)
|
|
|
|
const next = parent.getNextSibling(child)
|
2016-06-21 16:44:11 -07:00
|
|
|
return next && next.kind == 'text'
|
2016-06-19 12:12:23 -07:00
|
|
|
})
|
|
|
|
|
2016-06-21 16:44:11 -07:00
|
|
|
// If no text nodes are adjacent, abort.
|
|
|
|
if (!firstAdjacent) return node
|
2016-06-19 12:12:23 -07:00
|
|
|
|
2016-06-21 16:44:11 -07:00
|
|
|
// Fix an adjacent text node if one exists.
|
2016-06-22 18:42:49 -07:00
|
|
|
let parent = node.getParent(firstAdjacent)
|
|
|
|
const second = parent.getNextSibling(firstAdjacent)
|
2016-06-21 16:44:11 -07:00
|
|
|
const characters = firstAdjacent.characters.concat(second.characters)
|
|
|
|
firstAdjacent = firstAdjacent.merge({ characters })
|
2016-06-22 18:42:49 -07:00
|
|
|
parent = parent.updateDeep(firstAdjacent)
|
2016-06-19 12:12:23 -07:00
|
|
|
|
|
|
|
// Then remove the second node.
|
2016-06-23 12:34:47 -07:00
|
|
|
parent = parent.removeDescendant(second)
|
2016-06-19 12:12:23 -07:00
|
|
|
|
|
|
|
// If the parent isn't this node, it needs to be updated.
|
|
|
|
if (parent != node) {
|
2016-06-22 18:42:49 -07:00
|
|
|
node = node.updateDeep(parent)
|
2016-06-19 12:12:23 -07:00
|
|
|
} else {
|
|
|
|
node = parent
|
|
|
|
}
|
|
|
|
|
2016-06-21 16:44:11 -07:00
|
|
|
// Recurse by normalizing again.
|
2016-06-19 12:12:23 -07:00
|
|
|
return node.normalize()
|
|
|
|
},
|
|
|
|
|
2016-06-17 00:09:54 -07:00
|
|
|
/**
|
|
|
|
* Remove a `node` from the children node map.
|
|
|
|
*
|
|
|
|
* @param {String or Node} key
|
|
|
|
* @return {Node} node
|
|
|
|
*/
|
|
|
|
|
2016-06-23 12:34:47 -07:00
|
|
|
removeDescendant(key) {
|
2016-06-20 12:57:31 -07:00
|
|
|
key = normalizeKey(key)
|
2016-06-23 12:34:47 -07:00
|
|
|
this.assertHasDescendant(key)
|
2016-06-23 11:32:24 -07:00
|
|
|
const nodes = this.nodes.filterNot(node => node.key == key)
|
2016-06-16 16:43:02 -07:00
|
|
|
return this.merge({ nodes })
|
2016-06-19 11:51:50 -07:00
|
|
|
},
|
2016-06-16 16:43:02 -07:00
|
|
|
|
2016-06-20 17:38:56 -07:00
|
|
|
/**
|
2016-06-22 18:42:49 -07:00
|
|
|
* Set the block nodes in a range to `type`, with optional `data`.
|
2016-06-20 17:38:56 -07:00
|
|
|
*
|
|
|
|
* @param {Selection} range
|
2016-06-22 18:42:49 -07:00
|
|
|
* @param {String} type
|
|
|
|
* @param {Data} data (optional)
|
2016-06-20 17:38:56 -07:00
|
|
|
* @return {Node} node
|
|
|
|
*/
|
|
|
|
|
2016-06-22 18:42:49 -07:00
|
|
|
setBlockAtRange(range, type, data) {
|
2016-06-20 17:38:56 -07:00
|
|
|
let node = this
|
2016-06-21 14:49:08 -07:00
|
|
|
range = range.normalize(node)
|
|
|
|
|
2016-06-22 18:42:49 -07:00
|
|
|
// Allow for passing data only.
|
|
|
|
if (typeof type == 'object') {
|
|
|
|
data = type
|
|
|
|
type = null
|
|
|
|
}
|
2016-06-20 17:38:56 -07:00
|
|
|
|
2016-06-22 18:42:49 -07:00
|
|
|
// If data is passed, ensure it's immutable.
|
|
|
|
if (data) data = Data.create(data)
|
|
|
|
|
|
|
|
// Update each of the blocks.
|
|
|
|
const blocks = node.getBlocksAtRange(range)
|
|
|
|
blocks.forEach((block) => {
|
|
|
|
const obj = {}
|
|
|
|
if (type) obj.type = type
|
|
|
|
if (data) obj.data = data
|
|
|
|
block = block.merge(obj)
|
|
|
|
node = node.updateDeep(block)
|
2016-06-20 17:38:56 -07:00
|
|
|
})
|
|
|
|
|
2016-06-22 18:42:49 -07:00
|
|
|
return node
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set the inline nodes in a range to `type`, with optional `data`.
|
|
|
|
*
|
|
|
|
* @param {Selection} range
|
|
|
|
* @param {String} type
|
|
|
|
* @param {Data} data (optional)
|
|
|
|
* @return {Node} node
|
|
|
|
*/
|
|
|
|
|
|
|
|
setInlineAtRange(range, type, data) {
|
|
|
|
let node = this
|
|
|
|
range = range.normalize(node)
|
|
|
|
|
|
|
|
// Allow for passing data only.
|
|
|
|
if (typeof type == 'object') {
|
|
|
|
data = type
|
|
|
|
type = null
|
|
|
|
}
|
|
|
|
|
|
|
|
// If data is passed, ensure it's immutable.
|
|
|
|
if (data) data = Data.create(data)
|
|
|
|
|
|
|
|
// Update each of the inlines.
|
|
|
|
const inlines = node.getInlinesAtRange(range)
|
|
|
|
inlines.forEach((inline) => {
|
|
|
|
const obj = {}
|
|
|
|
if (type) obj.type = type
|
|
|
|
if (data) obj.data = data
|
|
|
|
inline = inline.merge(obj)
|
|
|
|
node = node.updateDeep(inline)
|
2016-06-20 17:38:56 -07:00
|
|
|
})
|
|
|
|
|
|
|
|
return node
|
|
|
|
},
|
|
|
|
|
2016-06-19 12:12:23 -07:00
|
|
|
/**
|
2016-06-22 18:42:49 -07:00
|
|
|
* Split the block nodes at a `range`.
|
2016-06-19 12:12:23 -07:00
|
|
|
*
|
|
|
|
* @param {Selection} range
|
|
|
|
* @return {Node} node
|
|
|
|
*/
|
|
|
|
|
2016-06-22 18:42:49 -07:00
|
|
|
splitBlockAtRange(range) {
|
2016-06-19 12:12:23 -07:00
|
|
|
let node = this
|
2016-06-21 14:49:08 -07:00
|
|
|
range = range.normalize(node)
|
2016-06-19 12:12:23 -07:00
|
|
|
|
|
|
|
// If the range is expanded, remove it first.
|
|
|
|
if (range.isExpanded) {
|
|
|
|
node = node.deleteAtRange(range)
|
|
|
|
range = range.moveToStart()
|
|
|
|
}
|
|
|
|
|
2016-06-23 09:15:51 -07:00
|
|
|
// Split the inline nodes at the range.
|
|
|
|
node = node.splitInlineAtRange(range)
|
2016-06-19 12:12:23 -07:00
|
|
|
|
2016-06-23 09:15:51 -07:00
|
|
|
// Find the highest inline elements that were split.
|
|
|
|
const { startKey } = range
|
2016-06-23 12:34:47 -07:00
|
|
|
const firstText = node.getDescendant(startKey)
|
2016-06-23 09:15:51 -07:00
|
|
|
const firstChild = node.getFurthestInline(firstText) || firstText
|
|
|
|
const secondText = node.getNextText(startKey)
|
|
|
|
const secondChild = node.getFurthestInline(secondText) || secondText
|
|
|
|
|
|
|
|
// Remove the second inline child from the first block.
|
|
|
|
let firstBlock = node.getBlocksAtRange(range).first()
|
2016-06-23 12:34:47 -07:00
|
|
|
firstBlock = firstBlock.removeDescendant(secondChild)
|
2016-06-23 09:15:51 -07:00
|
|
|
|
|
|
|
// Create a new block with the second inline child in it.
|
|
|
|
const secondBlock = Block.create({
|
|
|
|
type: firstBlock.type,
|
|
|
|
data: firstBlock.data,
|
2016-06-23 10:46:31 -07:00
|
|
|
nodes: [secondChild]
|
2016-06-19 12:12:23 -07:00
|
|
|
})
|
|
|
|
|
2016-06-23 09:15:51 -07:00
|
|
|
// Replace the block in the parent with the two new blocks.
|
|
|
|
let parent = node.getParent(firstBlock)
|
|
|
|
const nodes = parent.nodes.takeUntil(n => n.key == firstBlock.key)
|
2016-06-23 11:32:24 -07:00
|
|
|
.push(firstBlock)
|
|
|
|
.push(secondBlock)
|
2016-06-23 09:15:51 -07:00
|
|
|
.concat(parent.nodes.skipUntil(n => n.key == firstBlock.key).rest())
|
|
|
|
|
|
|
|
// If the node is the parent, just merge, otherwise deep merge.
|
|
|
|
if (parent == node) {
|
2016-06-19 12:12:23 -07:00
|
|
|
node = node.merge({ nodes })
|
|
|
|
} else {
|
2016-06-23 09:15:51 -07:00
|
|
|
parent = parent.merge({ nodes })
|
|
|
|
node = node.updateDeep(parent)
|
2016-06-19 12:12:23 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// Normalize the node.
|
|
|
|
return node.normalize()
|
|
|
|
},
|
|
|
|
|
2016-06-23 09:15:51 -07:00
|
|
|
splitInlineAtRange(range) {
|
|
|
|
range = range.normalize(this)
|
|
|
|
const Inline = require('./inline').default
|
|
|
|
let node = this
|
|
|
|
|
|
|
|
// If the range is expanded, remove it first.
|
|
|
|
if (range.isExpanded) {
|
|
|
|
node = node.deleteAtRange(range)
|
|
|
|
range = range.moveToStart()
|
|
|
|
}
|
|
|
|
|
|
|
|
// First split the text nodes.
|
|
|
|
node = node.splitTextAtRange(range)
|
2016-06-23 12:34:47 -07:00
|
|
|
let firstChild = node.getDescendant(range.startKey)
|
2016-06-23 09:15:51 -07:00
|
|
|
let secondChild = node.getNextText(firstChild)
|
|
|
|
let parent
|
|
|
|
|
|
|
|
// While the parent is an inline parent, split the inline nodes.
|
|
|
|
while (parent = node.getClosestInline(firstChild)) {
|
2016-06-23 11:32:24 -07:00
|
|
|
firstChild = parent.merge({ nodes: Inline.createList([firstChild]) })
|
2016-06-23 09:15:51 -07:00
|
|
|
secondChild = Inline.create({
|
2016-06-23 10:46:31 -07:00
|
|
|
nodes: [secondChild],
|
2016-06-23 09:15:51 -07:00
|
|
|
type: parent.type,
|
|
|
|
data: parent.data
|
|
|
|
})
|
|
|
|
|
|
|
|
// Split the children.
|
2016-06-23 11:32:24 -07:00
|
|
|
const grandparent = node.getParent(parent)
|
2016-06-23 09:15:51 -07:00
|
|
|
const nodes = grandparent.nodes
|
|
|
|
.takeUntil(c => c.key == firstChild.key)
|
2016-06-23 11:32:24 -07:00
|
|
|
.push(firstChild)
|
|
|
|
.push(secondChild)
|
2016-06-23 09:15:51 -07:00
|
|
|
.concat(grandparent.nodes.skipUntil(n => n.key == firstChild.key).rest())
|
|
|
|
|
|
|
|
// Update the grandparent.
|
2016-06-23 11:32:24 -07:00
|
|
|
node = grandparent == node
|
2016-06-23 09:15:51 -07:00
|
|
|
? node.merge({ nodes })
|
|
|
|
: node.updateDeep(grandparent.merge({ nodes }))
|
|
|
|
}
|
|
|
|
|
|
|
|
return node
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Split the text nodes at a `range`.
|
|
|
|
*
|
|
|
|
* @param {Selection} range
|
|
|
|
* @return {Node} node
|
|
|
|
*/
|
|
|
|
|
|
|
|
splitTextAtRange(range) {
|
|
|
|
range = range.normalize(this)
|
|
|
|
let node = this
|
|
|
|
|
|
|
|
// If the range is expanded, remove it first.
|
|
|
|
if (range.isExpanded) {
|
|
|
|
node = node.deleteAtRange(range)
|
|
|
|
range = range.moveToStart()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Split the text node's characters.
|
|
|
|
const { startKey, startOffset } = range
|
2016-06-23 12:34:47 -07:00
|
|
|
const text = node.getDescendant(startKey)
|
2016-06-23 09:15:51 -07:00
|
|
|
const { characters } = text
|
|
|
|
const firstChars = characters.take(startOffset)
|
|
|
|
const secondChars = characters.skip(startOffset)
|
|
|
|
let firstChild = text.merge({ characters: firstChars })
|
|
|
|
let secondChild = Text.create({ characters: secondChars })
|
|
|
|
|
|
|
|
// Split the text nodes.
|
|
|
|
let parent = node.getParent(text)
|
|
|
|
const nodes = parent.nodes
|
|
|
|
.takeUntil(c => c.key == firstChild.key)
|
2016-06-23 11:32:24 -07:00
|
|
|
.push(firstChild)
|
|
|
|
.push(secondChild)
|
2016-06-23 09:15:51 -07:00
|
|
|
.concat(parent.nodes.skipUntil(n => n.key == firstChild.key).rest())
|
|
|
|
|
|
|
|
// Update the nodes.
|
|
|
|
parent = parent.merge({ nodes })
|
|
|
|
node = node.updateDeep(parent)
|
|
|
|
return node
|
|
|
|
},
|
|
|
|
|
2016-06-20 12:57:31 -07:00
|
|
|
/**
|
|
|
|
* Remove an existing `mark` to the characters at `range`.
|
|
|
|
*
|
|
|
|
* @param {Selection} range
|
2016-06-20 13:58:04 -07:00
|
|
|
* @param {Mark or String} mark
|
2016-06-20 12:57:31 -07:00
|
|
|
* @return {Node} node
|
|
|
|
*/
|
|
|
|
|
|
|
|
unmarkAtRange(range, mark) {
|
|
|
|
let node = this
|
2016-06-21 14:49:08 -07:00
|
|
|
range = range.normalize(node)
|
2016-06-20 12:57:31 -07:00
|
|
|
|
2016-06-20 13:58:04 -07:00
|
|
|
// Allow for just passing a type for convenience.
|
|
|
|
if (typeof mark == 'string') {
|
|
|
|
mark = new Mark({ type: mark })
|
|
|
|
}
|
|
|
|
|
2016-06-20 12:57:31 -07:00
|
|
|
// When the range is collapsed, do nothing.
|
|
|
|
if (range.isCollapsed) return node
|
|
|
|
|
|
|
|
// Otherwise, find each of the text nodes within the range.
|
2016-06-22 18:59:19 -07:00
|
|
|
let texts = node.getTextsAtRange(range)
|
2016-06-20 12:57:31 -07:00
|
|
|
|
|
|
|
// Apply the mark to each of the text nodes's matching characters.
|
|
|
|
texts = texts.map((text) => {
|
|
|
|
let characters = text.characters.map((char, i) => {
|
|
|
|
if (!isInRange(i, text, range)) return char
|
|
|
|
let { marks } = char
|
|
|
|
marks = marks.remove(mark)
|
|
|
|
return char.merge({ marks })
|
|
|
|
})
|
|
|
|
|
|
|
|
return text.merge({ characters })
|
|
|
|
})
|
|
|
|
|
|
|
|
// Update each of the text nodes.
|
|
|
|
texts.forEach((text) => {
|
2016-06-22 18:42:49 -07:00
|
|
|
node = node.updateDeep(text)
|
2016-06-20 12:57:31 -07:00
|
|
|
})
|
|
|
|
|
|
|
|
return node
|
|
|
|
},
|
|
|
|
|
2016-06-16 16:43:02 -07:00
|
|
|
/**
|
|
|
|
* Set a new value for a child node by `key`.
|
|
|
|
*
|
2016-06-23 11:32:24 -07:00
|
|
|
* @param {Node} node
|
2016-06-16 16:43:02 -07:00
|
|
|
* @return {Node} node
|
|
|
|
*/
|
|
|
|
|
2016-06-23 11:32:24 -07:00
|
|
|
updateDeep(node) {
|
2016-06-23 12:34:47 -07:00
|
|
|
// this.assertHasDescendant(key)
|
2016-06-23 09:15:51 -07:00
|
|
|
|
2016-06-23 11:32:24 -07:00
|
|
|
const shallow = this.nodes.find(child => child.key == node.key)
|
|
|
|
if (shallow) {
|
|
|
|
const nodes = this.nodes.map(child => child.key == node.key ? node : child)
|
|
|
|
return this.merge({ nodes })
|
2016-06-16 16:43:02 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
const nodes = this.nodes.map((child) => {
|
2016-06-23 11:32:24 -07:00
|
|
|
return child.kind == 'text' ? child : child.updateDeep(node)
|
2016-06-16 16:43:02 -07:00
|
|
|
})
|
|
|
|
|
|
|
|
return this.merge({ nodes })
|
2016-06-20 17:38:56 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
2016-06-22 18:42:49 -07:00
|
|
|
* Wrap all of the blocks in a `range` in a new block node of `type`.
|
2016-06-20 17:38:56 -07:00
|
|
|
*
|
|
|
|
* @param {Selection} range
|
2016-06-21 18:00:18 -07:00
|
|
|
* @param {String} type
|
2016-06-22 18:42:49 -07:00
|
|
|
* @param {Data} data (optional)
|
2016-06-20 17:38:56 -07:00
|
|
|
* @return {Node} node
|
|
|
|
*/
|
|
|
|
|
2016-06-22 18:42:49 -07:00
|
|
|
wrapBlockAtRange(range, type, data) {
|
2016-06-21 18:00:18 -07:00
|
|
|
range = range.normalize(this)
|
2016-06-22 18:42:49 -07:00
|
|
|
data = Data.create(data)
|
2016-06-21 14:49:08 -07:00
|
|
|
let node = this
|
2016-06-20 17:38:56 -07:00
|
|
|
|
2016-06-22 18:42:49 -07:00
|
|
|
// Get the block nodes, sorted by depth.
|
|
|
|
const blocks = node.getBlocksAtRange(range)
|
|
|
|
const sorted = blocks.sort((a, b) => {
|
|
|
|
const da = node.getDepth(a)
|
|
|
|
const db = node.getDepth(b)
|
|
|
|
if (da == db) return 0
|
|
|
|
if (da > db) return -1
|
|
|
|
if (da < db) return 1
|
|
|
|
})
|
2016-06-20 17:38:56 -07:00
|
|
|
|
2016-06-22 18:42:49 -07:00
|
|
|
// Get the lowest common siblings, relative to the highest block.
|
|
|
|
const highest = sorted.first()
|
|
|
|
const depth = node.getDepth(highest)
|
|
|
|
const siblings = blocks.reduce((siblings, block) => {
|
|
|
|
const sibling = node.getDepth(block) == depth
|
|
|
|
? block
|
|
|
|
: node.getClosest(block, (p) => node.getDepth(p) == depth)
|
2016-06-23 11:32:24 -07:00
|
|
|
siblings = siblings.push(sibling)
|
2016-06-22 18:42:49 -07:00
|
|
|
return siblings
|
2016-06-23 11:32:24 -07:00
|
|
|
}, Block.createList())
|
2016-06-22 18:42:49 -07:00
|
|
|
|
|
|
|
// Wrap the siblings in a new block.
|
|
|
|
const wrapper = Block.create({
|
|
|
|
nodes: siblings,
|
|
|
|
type,
|
|
|
|
data
|
|
|
|
})
|
2016-06-20 17:38:56 -07:00
|
|
|
|
2016-06-22 18:42:49 -07:00
|
|
|
// Replace the siblings with the wrapper.
|
2016-06-23 11:32:24 -07:00
|
|
|
const parent = node.getParent(highest)
|
2016-06-22 18:42:49 -07:00
|
|
|
const nodes = parent.nodes
|
|
|
|
.takeUntil(node => node == highest)
|
2016-06-23 11:32:24 -07:00
|
|
|
.push(wrapper)
|
2016-06-22 18:42:49 -07:00
|
|
|
.concat(parent.nodes.skipUntil(node => node == highest).rest())
|
|
|
|
|
|
|
|
// Update the parent.
|
2016-06-23 11:32:24 -07:00
|
|
|
node = parent == node
|
|
|
|
? node.merge({ nodes })
|
|
|
|
: node.updateDeep(parent.merge({ nodes }))
|
2016-06-22 18:42:49 -07:00
|
|
|
|
|
|
|
return node
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Unwrap all of the block nodes in a `range` from a block node of `type.`
|
|
|
|
*
|
|
|
|
* @param {Selection} range
|
|
|
|
* @param {String} type (optional)
|
|
|
|
* @param {Data or Object} data (optional)
|
|
|
|
* @return {Node} node
|
|
|
|
*/
|
|
|
|
|
|
|
|
unwrapBlockAtRange(range, type, data) {
|
|
|
|
range = range.normalize(this)
|
|
|
|
let node = this
|
|
|
|
|
|
|
|
// Allow for only data.
|
|
|
|
if (typeof type == 'object') {
|
|
|
|
data = type
|
|
|
|
type = null
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ensure that data is immutable.
|
|
|
|
if (data) data = Data.create(data)
|
|
|
|
|
2016-06-23 10:04:49 -07:00
|
|
|
// Find the closest wrapping blocks of each text node.
|
|
|
|
const texts = node.getBlocksAtRange(range)
|
|
|
|
const wrappers = texts.reduce((wrappers, text) => {
|
|
|
|
const match = node.getClosest(text, (parent) => {
|
|
|
|
if (parent.kind != 'block') return false
|
2016-06-22 18:42:49 -07:00
|
|
|
if (type && parent.type != type) return false
|
|
|
|
if (data && !parent.data.isSuperset(data)) return false
|
|
|
|
return true
|
|
|
|
})
|
|
|
|
|
2016-06-23 11:32:24 -07:00
|
|
|
if (match) wrappers = wrappers.add(match)
|
2016-06-22 18:42:49 -07:00
|
|
|
return wrappers
|
2016-06-23 11:32:24 -07:00
|
|
|
}, new Set())
|
2016-06-22 18:42:49 -07:00
|
|
|
|
|
|
|
// Replace each of the wrappers with their child nodes.
|
|
|
|
wrappers.forEach((wrapper) => {
|
2016-06-23 11:32:24 -07:00
|
|
|
const parent = node.getParent(wrapper)
|
2016-06-22 18:42:49 -07:00
|
|
|
|
|
|
|
// Replace the wrapper in the parent's nodes with the block.
|
|
|
|
const nodes = parent.nodes.takeUntil(n => n == wrapper)
|
|
|
|
.concat(wrapper.nodes)
|
|
|
|
.concat(parent.nodes.skipUntil(n => n == wrapper).rest())
|
2016-06-20 17:38:56 -07:00
|
|
|
|
2016-06-21 18:00:18 -07:00
|
|
|
// Update the parent.
|
2016-06-23 11:32:24 -07:00
|
|
|
node = parent == node
|
|
|
|
? node.merge({ nodes })
|
|
|
|
: node.updateDeep(parent.merge({ nodes }))
|
2016-06-21 18:00:18 -07:00
|
|
|
})
|
2016-06-20 17:38:56 -07:00
|
|
|
|
2016-06-21 18:00:18 -07:00
|
|
|
return node.normalize()
|
2016-06-21 19:02:39 -07:00
|
|
|
},
|
2016-06-16 16:43:02 -07:00
|
|
|
|
2016-06-23 10:04:49 -07:00
|
|
|
/**
|
|
|
|
* Wrap the text and inline nodes in a `range` with a new inline node.
|
|
|
|
*
|
|
|
|
* @param {Selection} range
|
|
|
|
* @param {String} type
|
|
|
|
* @param {Data} data (optional)
|
|
|
|
* @return {Node} node
|
|
|
|
*/
|
2016-06-22 18:42:49 -07:00
|
|
|
|
2016-06-23 10:04:49 -07:00
|
|
|
wrapInlineAtRange(range, type, data) {
|
2016-06-22 18:42:49 -07:00
|
|
|
range = range.normalize(this)
|
|
|
|
|
|
|
|
const Inline = require('./inline').default
|
|
|
|
let node = this
|
2016-06-23 10:04:49 -07:00
|
|
|
|
|
|
|
// Ensure that data is immutable.
|
|
|
|
if (data) data = Data.create(data)
|
2016-06-22 18:42:49 -07:00
|
|
|
|
|
|
|
// If collapsed or unset, there's nothing to wrap.
|
|
|
|
if (range.isCollapsed || range.isUnset) return node
|
|
|
|
|
|
|
|
// Split at the start of the range.
|
|
|
|
const start = range.moveToStart()
|
|
|
|
node = node.splitInlineAtRange(start)
|
|
|
|
|
2016-06-23 10:04:49 -07:00
|
|
|
// Determine the new end of the range, and split there.
|
|
|
|
const { startKey, startOffset, endKey, endOffset } = range
|
2016-06-23 12:34:47 -07:00
|
|
|
const firstNode = node.getDescendant(startKey)
|
2016-06-23 10:04:49 -07:00
|
|
|
const nextNode = node.getNextText(startKey)
|
|
|
|
const end = startKey != endKey
|
|
|
|
? range.moveToEnd()
|
|
|
|
: Selection.create({
|
|
|
|
anchorKey: nextNode.key,
|
|
|
|
anchorOffset: endOffset - startOffset,
|
|
|
|
focusKey: nextNode.key,
|
|
|
|
focusOffset: endOffset - startOffset
|
|
|
|
})
|
2016-06-22 18:42:49 -07:00
|
|
|
|
|
|
|
node = node.splitInlineAtRange(end)
|
|
|
|
|
2016-06-23 10:04:49 -07:00
|
|
|
// Calculate the new range to wrap around.
|
2016-06-23 12:34:47 -07:00
|
|
|
const endNode = node.getDescendant(end.anchorKey)
|
2016-06-22 18:42:49 -07:00
|
|
|
range = Selection.create({
|
2016-06-23 10:04:49 -07:00
|
|
|
anchorKey: nextNode.key,
|
|
|
|
anchorOffset: 0,
|
|
|
|
focusKey: endNode.key,
|
|
|
|
focusOffset: endNode.length
|
2016-06-22 18:42:49 -07:00
|
|
|
})
|
|
|
|
|
|
|
|
// Get the furthest inline nodes in the range.
|
2016-06-22 18:59:19 -07:00
|
|
|
const texts = node.getTextsAtRange(range)
|
2016-06-22 18:42:49 -07:00
|
|
|
const children = texts.map(text => node.getFurthestInline(text) || text)
|
|
|
|
|
|
|
|
// Iterate each of the child nodes, wrapping them.
|
|
|
|
children.forEach((child) => {
|
2016-06-23 10:04:49 -07:00
|
|
|
const obj = {}
|
2016-06-23 10:46:31 -07:00
|
|
|
obj.nodes = [child]
|
2016-06-23 10:04:49 -07:00
|
|
|
obj.type = type
|
|
|
|
if (data) obj.data = data
|
|
|
|
const wrapper = Inline.create(obj)
|
2016-06-22 18:42:49 -07:00
|
|
|
|
|
|
|
// Replace the child in it's parent with the wrapper.
|
2016-06-23 10:04:49 -07:00
|
|
|
const parent = node.getParent(child)
|
|
|
|
const nodes = parent.nodes.takeUntil(n => n == child)
|
2016-06-23 11:32:24 -07:00
|
|
|
.push(wrapper)
|
2016-06-22 18:42:49 -07:00
|
|
|
.concat(parent.nodes.skipUntil(n => n == child).rest())
|
|
|
|
|
|
|
|
// Update the parent.
|
2016-06-23 10:04:49 -07:00
|
|
|
node = parent == node
|
2016-06-22 18:42:49 -07:00
|
|
|
? node.merge({ nodes })
|
|
|
|
: node.updateDeep(parent.merge({ nodes }))
|
|
|
|
})
|
|
|
|
|
|
|
|
return node
|
|
|
|
},
|
|
|
|
|
2016-06-20 17:38:56 -07:00
|
|
|
/**
|
2016-06-23 10:04:49 -07:00
|
|
|
* Unwrap the inline nodes in a `range` from an parent inline with `type`.
|
2016-06-21 19:02:39 -07:00
|
|
|
*
|
|
|
|
* @param {Selection} range
|
2016-06-23 10:04:49 -07:00
|
|
|
* @param {String} type (optional)
|
|
|
|
* @param {Data} data (optional)
|
2016-06-21 19:02:39 -07:00
|
|
|
* @return {Node} node
|
2016-06-20 17:38:56 -07:00
|
|
|
*/
|
|
|
|
|
2016-06-23 10:04:49 -07:00
|
|
|
unwrapInlineAtRange(range, type, data) {
|
2016-06-21 19:02:39 -07:00
|
|
|
range = range.normalize(this)
|
|
|
|
let node = this
|
2016-06-22 18:42:49 -07:00
|
|
|
let blocks = node.getInlinesAtRange(range)
|
2016-06-21 19:02:39 -07:00
|
|
|
|
2016-06-23 10:04:49 -07:00
|
|
|
// Allow for no type.
|
|
|
|
if (typeof type == 'object') {
|
|
|
|
data = type
|
|
|
|
type = null
|
|
|
|
}
|
2016-06-21 19:02:39 -07:00
|
|
|
|
2016-06-23 10:04:49 -07:00
|
|
|
// Ensure that data is immutable.
|
|
|
|
if (data) data = Data.create(data)
|
2016-06-21 19:02:39 -07:00
|
|
|
|
2016-06-23 10:04:49 -07:00
|
|
|
// Find the closest matching inline wrappers of each text node.
|
|
|
|
const texts = this.getTextNodes()
|
|
|
|
const wrappers = texts.reduce((wrappers, text) => {
|
|
|
|
const match = node.getClosest(text, (parent) => {
|
|
|
|
if (parent.kind != 'inline') return false
|
|
|
|
if (type && parent.type != type) return false
|
|
|
|
if (data && !parent.data.isSuperset(data)) return false
|
|
|
|
return true
|
|
|
|
})
|
|
|
|
|
2016-06-23 11:32:24 -07:00
|
|
|
if (match) wrappers = wrappers.add(match)
|
2016-06-23 10:04:49 -07:00
|
|
|
return wrappers
|
2016-06-23 11:32:24 -07:00
|
|
|
}, new Set())
|
2016-06-23 10:04:49 -07:00
|
|
|
|
|
|
|
// Replace each of the wrappers with their child nodes.
|
|
|
|
wrappers.forEach((wrapper) => {
|
|
|
|
const parent = node.getParent(wrapper)
|
2016-06-21 19:02:39 -07:00
|
|
|
|
|
|
|
// Replace the wrapper in the parent's nodes with the block.
|
2016-06-23 10:04:49 -07:00
|
|
|
const nodes = parent.nodes.takeUntil(n => n == wrapper)
|
|
|
|
.concat(wrapper.nodes)
|
|
|
|
.concat(parent.nodes.skipUntil(n => n == wrapper).rest())
|
2016-06-21 19:02:39 -07:00
|
|
|
|
|
|
|
// Update the parent.
|
2016-06-23 11:32:24 -07:00
|
|
|
node = parent == node
|
2016-06-23 10:04:49 -07:00
|
|
|
? node.merge({ nodes })
|
|
|
|
: node.updateDeep(parent.merge({ nodes }))
|
2016-06-21 19:02:39 -07:00
|
|
|
})
|
|
|
|
|
|
|
|
return node.normalize()
|
|
|
|
}
|
|
|
|
|
2016-06-15 12:07:12 -07:00
|
|
|
}
|
|
|
|
|
2016-06-20 12:57:31 -07:00
|
|
|
/**
|
|
|
|
* Normalize a `key`, from a key string or a node.
|
|
|
|
*
|
|
|
|
* @param {String or Node} key
|
|
|
|
* @return {String} key
|
|
|
|
*/
|
|
|
|
|
|
|
|
function normalizeKey(key) {
|
|
|
|
if (typeof key == 'string') return key
|
|
|
|
return key.key
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check if an `index` of a `text` node is in a `range`.
|
|
|
|
*
|
|
|
|
* @param {Number} index
|
|
|
|
* @param {Text} text
|
|
|
|
* @param {Selection} range
|
|
|
|
* @return {Set} characters
|
|
|
|
*/
|
|
|
|
|
|
|
|
function isInRange(index, text, range) {
|
|
|
|
const { startKey, startOffset, endKey, endOffset } = range
|
|
|
|
let matcher
|
|
|
|
|
|
|
|
if (text.key == startKey && text.key == endKey) {
|
|
|
|
return startOffset <= index && index < endOffset
|
|
|
|
} else if (text.key == startKey) {
|
|
|
|
return startOffset <= index
|
|
|
|
} else if (text.key == endKey) {
|
|
|
|
return index < endOffset
|
|
|
|
} else {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-06-15 12:07:12 -07:00
|
|
|
/**
|
|
|
|
* Export.
|
|
|
|
*/
|
|
|
|
|
|
|
|
export default Node
|