mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-08-13 18:53:59 +02:00
holy shit delete range rabbit hole
This commit is contained in:
@@ -61,61 +61,90 @@ const Node = {
|
|||||||
node.assertHasDescendant(startKey)
|
node.assertHasDescendant(startKey)
|
||||||
node.assertHasDescendant(endKey)
|
node.assertHasDescendant(endKey)
|
||||||
|
|
||||||
let startNode = node.getDescendant(startKey)
|
// If the start and end nodes are the same, just remove characters.
|
||||||
|
|
||||||
// If the start and end nodes are the same, remove the matching characters.
|
|
||||||
if (startKey == endKey) {
|
if (startKey == endKey) {
|
||||||
|
const startNode = node.getDescendant(startKey)
|
||||||
const characters = startNode.characters.filterNot((char, i) => {
|
const characters = startNode.characters.filterNot((char, i) => {
|
||||||
return startOffset <= i && i < endOffset
|
return startOffset <= i && i < endOffset
|
||||||
})
|
})
|
||||||
|
|
||||||
startNode = startNode.merge({ characters })
|
node = node.updateDescendant(startNode.merge({ characters }))
|
||||||
node = node.updateDescendant(startNode)
|
|
||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise, remove the text from the first and last nodes...
|
// Determine the start edge text nodes.
|
||||||
const startRange = Selection.create({
|
const start = range.moveToStart()
|
||||||
anchorKey: startKey,
|
const startFurthest = node.getFurthestBlock(startKey)
|
||||||
anchorOffset: startOffset,
|
let startText
|
||||||
focusKey: startKey,
|
let startEdgeText
|
||||||
focusOffset: startNode.length
|
|
||||||
})
|
|
||||||
|
|
||||||
const endRange = Selection.create({
|
if (range.hasEdgeAtStartOf(startFurthest)) {
|
||||||
anchorKey: endKey,
|
startText = node.getPreviousText(startKey)
|
||||||
anchorOffset: 0,
|
startEdgeText = node.getDescendant(startKey)
|
||||||
focusKey: endKey,
|
}
|
||||||
focusOffset: endOffset
|
|
||||||
})
|
|
||||||
|
|
||||||
node = node.deleteAtRange(startRange)
|
else if (range.hasEdgeAtEndOf(startFurthest)) {
|
||||||
node = node.deleteAtRange(endRange)
|
startText = node.getDescendant(startKey)
|
||||||
|
startEdgeText = node.getNextText(startKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
node = node.splitBlockAtRange(start, Infinity)
|
||||||
|
startText = node.getDescendant(startKey)
|
||||||
|
startEdgeText = node.getNextText(startKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine the end edge text nodes.
|
||||||
|
const end = range.moveToEnd()
|
||||||
|
const endFurthest = node.getFurthestBlock(endKey)
|
||||||
|
let endText
|
||||||
|
let endEdgeText
|
||||||
|
|
||||||
|
if (range.hasEdgeAtStartOf(endFurthest)) {
|
||||||
|
endEdgeText = node.getPreviousText(endKey)
|
||||||
|
endText = node.getDescendant(endKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (range.hasEdgeAtEndOf(endFurthest)) {
|
||||||
|
endEdgeText = node.getDescendant(endKey)
|
||||||
|
endText = node.getNextText(endKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
node = node.splitBlockAtRange(end, Infinity)
|
||||||
|
endEdgeText = node.getDescendant(endKey)
|
||||||
|
endText = node.getNextText(endKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the new blocks inside the edges.
|
||||||
|
const startEdgeBlock = node.getFurthestBlock(startEdgeText)
|
||||||
|
const endEdgeBlock = node.getFurthestBlock(endEdgeText)
|
||||||
|
|
||||||
// Then remove any nodes in between the top-most start and end nodes...
|
|
||||||
let startParent = node.getParent(startKey)
|
|
||||||
let endParent = node.getParent(endKey)
|
|
||||||
const startAncestor = node.getHighestChild(startParent)
|
|
||||||
const endAncestor = node.getHighestChild(endParent)
|
|
||||||
const nodes = node.nodes
|
const nodes = node.nodes
|
||||||
.takeUntil(child => child == startAncestor)
|
.takeUntil(n => n == startEdgeBlock)
|
||||||
.push(startAncestor)
|
.concat(node.nodes.skipUntil(n => n == endEdgeBlock).rest())
|
||||||
.concat(node.nodes.skipUntil(child => child == endAncestor))
|
|
||||||
|
|
||||||
node = node.merge({ nodes })
|
node = node.merge({ nodes })
|
||||||
|
|
||||||
// Then add the end parent's nodes to the start parent node.
|
// Take the end edge's split text and move it to the start edge.
|
||||||
const newNodes = startParent.nodes.concat(endParent.nodes)
|
let startBlock = node.getFurthestBlock(startText)
|
||||||
startParent = startParent.merge({ nodes: newNodes })
|
let endChild = node.getFurthestInline(endText) || endText
|
||||||
node = node.updateDescendant(startParent)
|
|
||||||
|
|
||||||
// Then remove the end parent.
|
const startNodes = startBlock.nodes.push(endChild)
|
||||||
let endGrandparent = node.getParent(endParent)
|
startBlock = startBlock.merge({ nodes: startNodes })
|
||||||
node = endGrandparent == node
|
node = node.updateDescendant(startBlock)
|
||||||
? node.removeDescendant(endParent)
|
|
||||||
: node.updateDescendant(endGrandparent.removeDescendant(endParent))
|
|
||||||
|
|
||||||
// Normalize the node.
|
// While the end child is an only child, remove the block it's in.
|
||||||
|
let endParent = node.getClosestBlock(endChild)
|
||||||
|
|
||||||
|
while (endParent && endParent.nodes.size == 1) {
|
||||||
|
endChild = endParent
|
||||||
|
endParent = node.getClosestBlock(endParent)
|
||||||
|
}
|
||||||
|
|
||||||
|
node = node.removeDescendant(endChild)
|
||||||
|
|
||||||
|
// Normalize the adjacent text nodes.
|
||||||
return node.normalize()
|
return node.normalize()
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -774,7 +803,20 @@ const Node = {
|
|||||||
removeDescendant(key) {
|
removeDescendant(key) {
|
||||||
key = normalizeKey(key)
|
key = normalizeKey(key)
|
||||||
this.assertHasDescendant(key)
|
this.assertHasDescendant(key)
|
||||||
const nodes = this.nodes.filterNot(node => node.key == key)
|
|
||||||
|
const child = this.getChild(key)
|
||||||
|
|
||||||
|
if (child) {
|
||||||
|
const nodes = this.nodes.filterNot(node => node == child)
|
||||||
|
return this.merge({ nodes })
|
||||||
|
}
|
||||||
|
|
||||||
|
const nodes = this.nodes.map((node) => {
|
||||||
|
return node.kind == 'text'
|
||||||
|
? node
|
||||||
|
: node.removeDescendant(key)
|
||||||
|
})
|
||||||
|
|
||||||
return this.merge({ nodes })
|
return this.merge({ nodes })
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -782,7 +824,7 @@ const Node = {
|
|||||||
* Set the block nodes in a range to `type`, with optional `data`.
|
* Set the block nodes in a range to `type`, with optional `data`.
|
||||||
*
|
*
|
||||||
* @param {Selection} range
|
* @param {Selection} range
|
||||||
* @param {String} type
|
* @param {String} type (optional)
|
||||||
* @param {Data} data (optional)
|
* @param {Data} data (optional)
|
||||||
* @return {Node} node
|
* @return {Node} node
|
||||||
*/
|
*/
|
||||||
@@ -797,15 +839,12 @@ const Node = {
|
|||||||
type = null
|
type = null
|
||||||
}
|
}
|
||||||
|
|
||||||
// If data is passed, ensure it's immutable.
|
|
||||||
if (data) data = Data.create(data)
|
|
||||||
|
|
||||||
// Update each of the blocks.
|
// Update each of the blocks.
|
||||||
const blocks = node.getBlocksAtRange(range)
|
const blocks = node.getBlocksAtRange(range)
|
||||||
blocks.forEach((block) => {
|
blocks.forEach((block) => {
|
||||||
const obj = {}
|
const obj = {}
|
||||||
if (type) obj.type = type
|
if (type) obj.type = type
|
||||||
if (data) obj.data = data
|
if (data) obj.data = Data.create(data)
|
||||||
block = block.merge(obj)
|
block = block.merge(obj)
|
||||||
node = node.updateDescendant(block)
|
node = node.updateDescendant(block)
|
||||||
})
|
})
|
||||||
@@ -817,7 +856,7 @@ const Node = {
|
|||||||
* Set the inline nodes in a range to `type`, with optional `data`.
|
* Set the inline nodes in a range to `type`, with optional `data`.
|
||||||
*
|
*
|
||||||
* @param {Selection} range
|
* @param {Selection} range
|
||||||
* @param {String} type
|
* @param {String} type (optional)
|
||||||
* @param {Data} data (optional)
|
* @param {Data} data (optional)
|
||||||
* @return {Node} node
|
* @return {Node} node
|
||||||
*/
|
*/
|
||||||
@@ -832,15 +871,12 @@ const Node = {
|
|||||||
type = null
|
type = null
|
||||||
}
|
}
|
||||||
|
|
||||||
// If data is passed, ensure it's immutable.
|
|
||||||
if (data) data = Data.create(data)
|
|
||||||
|
|
||||||
// Update each of the inlines.
|
// Update each of the inlines.
|
||||||
const inlines = node.getInlinesAtRange(range)
|
const inlines = node.getInlinesAtRange(range)
|
||||||
inlines.forEach((inline) => {
|
inlines.forEach((inline) => {
|
||||||
const obj = {}
|
const obj = {}
|
||||||
if (type) obj.type = type
|
if (type) obj.type = type
|
||||||
if (data) obj.data = data
|
if (data) obj.data = Data.create(data)
|
||||||
inline = inline.merge(obj)
|
inline = inline.merge(obj)
|
||||||
node = node.updateDescendant(inline)
|
node = node.updateDescendant(inline)
|
||||||
})
|
})
|
||||||
@@ -849,13 +885,14 @@ const Node = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Split the block nodes at a `range`.
|
* Split the block nodes at a `range`, to optional `depth`.
|
||||||
*
|
*
|
||||||
* @param {Selection} range
|
* @param {Selection} range
|
||||||
|
* @param {Number} depth (optional)
|
||||||
* @return {Node} node
|
* @return {Node} node
|
||||||
*/
|
*/
|
||||||
|
|
||||||
splitBlockAtRange(range) {
|
splitBlockAtRange(range, depth = 1) {
|
||||||
let node = this
|
let node = this
|
||||||
range = range.normalize(node)
|
range = range.normalize(node)
|
||||||
|
|
||||||
@@ -865,47 +902,68 @@ const Node = {
|
|||||||
range = range.moveToStart()
|
range = range.moveToStart()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Split the inline nodes at the range.
|
// If at the start of end of the node, do nothing.
|
||||||
node = node.splitInlineAtRange(range)
|
if (range.isAtStartOf(node) || range.isAtEndOf(node)) return node
|
||||||
|
|
||||||
// Find the highest inline elements that were split.
|
// If at the start of end of the closest block, do nothing.
|
||||||
const { startKey } = range
|
const { startKey } = range
|
||||||
const firstText = node.getDescendant(startKey)
|
const block = node.getClosestBlock(startKey)
|
||||||
const firstChild = node.getFurthestInline(firstText) || firstText
|
if (range.isAtStartOf(block) || range.isAtEndOf(block)) return node
|
||||||
const secondText = node.getNextText(startKey)
|
|
||||||
const secondChild = node.getFurthestInline(secondText) || secondText
|
|
||||||
|
|
||||||
// Remove the second inline child from the first block.
|
// If not at the edge of the furthest inline, split it.
|
||||||
let firstBlock = node.getBlocksAtRange(range).first()
|
const text = node.getDescendant(startKey)
|
||||||
firstBlock = firstBlock.removeDescendant(secondChild)
|
const furthest = node.getFurthestInline(text) || text
|
||||||
|
|
||||||
// Create a new block with the second inline child in it.
|
if (!range.isAtStartOf(furthest) && !range.isAtEndOf(furthest)) {
|
||||||
const secondBlock = Block.create({
|
node = node.splitInlineAtRange(range)
|
||||||
type: firstBlock.type,
|
|
||||||
data: firstBlock.data,
|
|
||||||
nodes: [secondChild]
|
|
||||||
})
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
.push(firstBlock)
|
|
||||||
.push(secondBlock)
|
|
||||||
.concat(parent.nodes.skipUntil(n => n.key == firstBlock.key).rest())
|
|
||||||
|
|
||||||
// If the node is the parent, just merge, otherwise deep merge.
|
|
||||||
if (parent == node) {
|
|
||||||
node = node.merge({ nodes })
|
|
||||||
} else {
|
|
||||||
parent = parent.merge({ nodes })
|
|
||||||
node = node.updateDescendant(parent)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Normalize the node.
|
// Find the highest inline elements that were split.
|
||||||
return node.normalize()
|
const firstText = node.getDescendant(startKey)
|
||||||
|
const secondText = node.getNextText(startKey)
|
||||||
|
let firstChild = node.getFurthestInline(firstText) || firstText
|
||||||
|
let secondChild = node.getFurthestInline(secondText) || secondText
|
||||||
|
let parent = node.getClosestBlock(firstChild)
|
||||||
|
let d = 0
|
||||||
|
|
||||||
|
// While the parent is a block, split the block nodes.
|
||||||
|
while (parent && d < depth) {
|
||||||
|
firstChild = parent.merge({ nodes: Block.createList([firstChild]) })
|
||||||
|
secondChild = Block.create({
|
||||||
|
nodes: [secondChild],
|
||||||
|
type: parent.type,
|
||||||
|
data: parent.data
|
||||||
|
})
|
||||||
|
|
||||||
|
// Add the new children.
|
||||||
|
const grandparent = node.getParent(parent)
|
||||||
|
const nodes = grandparent.nodes
|
||||||
|
.takeUntil(n => n.key == firstChild.key)
|
||||||
|
.push(firstChild)
|
||||||
|
.push(secondChild)
|
||||||
|
.concat(grandparent.nodes.skipUntil(n => n.key == firstChild.key).rest())
|
||||||
|
|
||||||
|
// Update the grandparent.
|
||||||
|
node = grandparent == node
|
||||||
|
? node.merge({ nodes })
|
||||||
|
: node.updateDescendant(grandparent.merge({ nodes }))
|
||||||
|
|
||||||
|
d++
|
||||||
|
parent = node.getClosestBlock(firstChild)
|
||||||
|
}
|
||||||
|
|
||||||
|
return node
|
||||||
},
|
},
|
||||||
|
|
||||||
splitInlineAtRange(range) {
|
/**
|
||||||
|
* Split the inline nodes at a `range`, to optional `depth`.
|
||||||
|
*
|
||||||
|
* @param {Selection} range
|
||||||
|
* @param {Number} depth (optiona)
|
||||||
|
* @return {Node} node
|
||||||
|
*/
|
||||||
|
|
||||||
|
splitInlineAtRange(range, depth = Infinity) {
|
||||||
range = range.normalize(this)
|
range = range.normalize(this)
|
||||||
const Inline = require('./inline').default
|
const Inline = require('./inline').default
|
||||||
let node = this
|
let node = this
|
||||||
@@ -916,14 +974,41 @@ const Node = {
|
|||||||
range = range.moveToStart()
|
range = range.moveToStart()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If at the start or end of the node, do nothing.
|
||||||
|
if (range.isAtStartOf(node) || range.isAtEndOf(node)) return node
|
||||||
|
|
||||||
|
// If at the start or the end of the furthest inline, do nothing.
|
||||||
|
const { startKey } = range
|
||||||
|
const text = node.getDescendant(startKey)
|
||||||
|
const furthest = node.getFurthestInline(text) || text
|
||||||
|
if (range.isAtStartOf(furthest) || range.isAtEndOf(furthest)) return node
|
||||||
|
|
||||||
|
// Determine the first and next nodes based on the edge.
|
||||||
|
let firstChild
|
||||||
|
let secondChild
|
||||||
|
|
||||||
|
if (range.isAtStartOf(text)) {
|
||||||
|
firstChild = node.getPreviousText(startKey)
|
||||||
|
secondChild = node.getDescendant(startKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (range.isAtEndOf(text)) {
|
||||||
|
firstChild = node.getDescendant(startKey)
|
||||||
|
secondChild = node.getNextText(firstChild)
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
node = node.splitTextAtRange(range)
|
||||||
|
firstChild = node.getDescendant(startKey)
|
||||||
|
secondChild = node.getNextText(firstChild)
|
||||||
|
}
|
||||||
|
|
||||||
// First split the text nodes.
|
// First split the text nodes.
|
||||||
node = node.splitTextAtRange(range)
|
let parent = node.getClosestInline(firstChild)
|
||||||
let firstChild = node.getDescendant(range.startKey)
|
let d = 0
|
||||||
let secondChild = node.getNextText(firstChild)
|
|
||||||
let parent
|
|
||||||
|
|
||||||
// While the parent is an inline parent, split the inline nodes.
|
// While the parent is an inline parent, split the inline nodes.
|
||||||
while (parent = node.getClosestInline(firstChild)) {
|
while (parent && d < depth) {
|
||||||
firstChild = parent.merge({ nodes: Inline.createList([firstChild]) })
|
firstChild = parent.merge({ nodes: Inline.createList([firstChild]) })
|
||||||
secondChild = Inline.create({
|
secondChild = Inline.create({
|
||||||
nodes: [secondChild],
|
nodes: [secondChild],
|
||||||
@@ -934,7 +1019,7 @@ const Node = {
|
|||||||
// Split the children.
|
// Split the children.
|
||||||
const grandparent = node.getParent(parent)
|
const grandparent = node.getParent(parent)
|
||||||
const nodes = grandparent.nodes
|
const nodes = grandparent.nodes
|
||||||
.takeUntil(c => c.key == firstChild.key)
|
.takeUntil(n => n.key == firstChild.key)
|
||||||
.push(firstChild)
|
.push(firstChild)
|
||||||
.push(secondChild)
|
.push(secondChild)
|
||||||
.concat(grandparent.nodes.skipUntil(n => n.key == firstChild.key).rest())
|
.concat(grandparent.nodes.skipUntil(n => n.key == firstChild.key).rest())
|
||||||
@@ -943,6 +1028,9 @@ const Node = {
|
|||||||
node = grandparent == node
|
node = grandparent == node
|
||||||
? node.merge({ nodes })
|
? node.merge({ nodes })
|
||||||
: node.updateDescendant(grandparent.merge({ nodes }))
|
: node.updateDescendant(grandparent.merge({ nodes }))
|
||||||
|
|
||||||
|
d++
|
||||||
|
parent = node.getClosestInline(firstChild)
|
||||||
}
|
}
|
||||||
|
|
||||||
return node
|
return node
|
||||||
@@ -965,9 +1053,12 @@ const Node = {
|
|||||||
range = range.moveToStart()
|
range = range.moveToStart()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Split the text node's characters.
|
// If at the start or the end of the text node, do nothing.
|
||||||
const { startKey, startOffset } = range
|
const { startKey, startOffset } = range
|
||||||
const text = node.getDescendant(startKey)
|
const text = node.getDescendant(startKey)
|
||||||
|
if (range.isAtStartOf(text) || range.isAtEndOf(text)) return node
|
||||||
|
|
||||||
|
// Split the text node's characters.
|
||||||
const { characters } = text
|
const { characters } = text
|
||||||
const firstChars = characters.take(startOffset)
|
const firstChars = characters.take(startOffset)
|
||||||
const secondChars = characters.skip(startOffset)
|
const secondChars = characters.skip(startOffset)
|
||||||
@@ -1031,6 +1122,115 @@ const Node = {
|
|||||||
return node
|
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)
|
||||||
|
|
||||||
|
// 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
|
||||||
|
if (type && parent.type != type) return false
|
||||||
|
if (data && !parent.data.isSuperset(data)) return false
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
if (match) wrappers = wrappers.add(match)
|
||||||
|
return wrappers
|
||||||
|
}, new Set())
|
||||||
|
|
||||||
|
// Replace each of the wrappers with their child nodes.
|
||||||
|
wrappers.forEach((wrapper) => {
|
||||||
|
const parent = node.getParent(wrapper)
|
||||||
|
|
||||||
|
// 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())
|
||||||
|
|
||||||
|
// Update the parent.
|
||||||
|
node = parent == node
|
||||||
|
? node.merge({ nodes })
|
||||||
|
: node.updateDescendant(parent.merge({ nodes }))
|
||||||
|
})
|
||||||
|
|
||||||
|
return node.normalize()
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unwrap the inline nodes in a `range` from an parent inline with `type`.
|
||||||
|
*
|
||||||
|
* @param {Selection} range
|
||||||
|
* @param {String} type (optional)
|
||||||
|
* @param {Data} data (optional)
|
||||||
|
* @return {Node} node
|
||||||
|
*/
|
||||||
|
|
||||||
|
unwrapInlineAtRange(range, type, data) {
|
||||||
|
range = range.normalize(this)
|
||||||
|
let node = this
|
||||||
|
let blocks = node.getInlinesAtRange(range)
|
||||||
|
|
||||||
|
// Allow for no type.
|
||||||
|
if (typeof type == 'object') {
|
||||||
|
data = type
|
||||||
|
type = null
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure that data is immutable.
|
||||||
|
if (data) data = Data.create(data)
|
||||||
|
|
||||||
|
// 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
|
||||||
|
})
|
||||||
|
|
||||||
|
if (match) wrappers = wrappers.add(match)
|
||||||
|
return wrappers
|
||||||
|
}, new Set())
|
||||||
|
|
||||||
|
// Replace each of the wrappers with their child nodes.
|
||||||
|
wrappers.forEach((wrapper) => {
|
||||||
|
const parent = node.getParent(wrapper)
|
||||||
|
|
||||||
|
// 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())
|
||||||
|
|
||||||
|
// Update the parent.
|
||||||
|
node = parent == node
|
||||||
|
? node.merge({ nodes })
|
||||||
|
: node.updateDescendant(parent.merge({ nodes }))
|
||||||
|
})
|
||||||
|
|
||||||
|
return node.normalize()
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set a new value for a child node by `key`.
|
* Set a new value for a child node by `key`.
|
||||||
*
|
*
|
||||||
@@ -1097,11 +1297,13 @@ const Node = {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Replace the siblings with the wrapper.
|
// Replace the siblings with the wrapper.
|
||||||
|
const first = siblings.first()
|
||||||
|
const last = siblings.last()
|
||||||
const parent = node.getParent(highest)
|
const parent = node.getParent(highest)
|
||||||
const nodes = parent.nodes
|
const nodes = parent.nodes
|
||||||
.takeUntil(node => node == highest)
|
.takeUntil(node => node == first)
|
||||||
.push(wrapper)
|
.push(wrapper)
|
||||||
.concat(parent.nodes.skipUntil(node => node == highest).rest())
|
.concat(parent.nodes.skipUntil(node => node == last).rest())
|
||||||
|
|
||||||
// Update the parent.
|
// Update the parent.
|
||||||
node = parent == node
|
node = parent == node
|
||||||
@@ -1111,60 +1313,6 @@ const Node = {
|
|||||||
return node
|
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)
|
|
||||||
|
|
||||||
// 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
|
|
||||||
if (type && parent.type != type) return false
|
|
||||||
if (data && !parent.data.isSuperset(data)) return false
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
|
|
||||||
if (match) wrappers = wrappers.add(match)
|
|
||||||
return wrappers
|
|
||||||
}, new Set())
|
|
||||||
|
|
||||||
// Replace each of the wrappers with their child nodes.
|
|
||||||
wrappers.forEach((wrapper) => {
|
|
||||||
const parent = node.getParent(wrapper)
|
|
||||||
|
|
||||||
// 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())
|
|
||||||
|
|
||||||
// Update the parent.
|
|
||||||
node = parent == node
|
|
||||||
? node.merge({ nodes })
|
|
||||||
: node.updateDescendant(parent.merge({ nodes }))
|
|
||||||
})
|
|
||||||
|
|
||||||
return node.normalize()
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wrap the text and inline nodes in a `range` with a new inline node.
|
* Wrap the text and inline nodes in a `range` with a new inline node.
|
||||||
*
|
*
|
||||||
@@ -1176,13 +1324,11 @@ const Node = {
|
|||||||
|
|
||||||
wrapInlineAtRange(range, type, data) {
|
wrapInlineAtRange(range, type, data) {
|
||||||
range = range.normalize(this)
|
range = range.normalize(this)
|
||||||
|
data = Data.create(data)
|
||||||
|
|
||||||
const Inline = require('./inline').default
|
const Inline = require('./inline').default
|
||||||
let node = this
|
let node = this
|
||||||
|
|
||||||
// Ensure that data is immutable.
|
|
||||||
if (data) data = Data.create(data)
|
|
||||||
|
|
||||||
// If collapsed or unset, there's nothing to wrap.
|
// If collapsed or unset, there's nothing to wrap.
|
||||||
if (range.isCollapsed || range.isUnset) return node
|
if (range.isCollapsed || range.isUnset) return node
|
||||||
|
|
||||||
@@ -1239,61 +1385,6 @@ const Node = {
|
|||||||
})
|
})
|
||||||
|
|
||||||
return node
|
return node
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unwrap the inline nodes in a `range` from an parent inline with `type`.
|
|
||||||
*
|
|
||||||
* @param {Selection} range
|
|
||||||
* @param {String} type (optional)
|
|
||||||
* @param {Data} data (optional)
|
|
||||||
* @return {Node} node
|
|
||||||
*/
|
|
||||||
|
|
||||||
unwrapInlineAtRange(range, type, data) {
|
|
||||||
range = range.normalize(this)
|
|
||||||
let node = this
|
|
||||||
let blocks = node.getInlinesAtRange(range)
|
|
||||||
|
|
||||||
// Allow for no type.
|
|
||||||
if (typeof type == 'object') {
|
|
||||||
data = type
|
|
||||||
type = null
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure that data is immutable.
|
|
||||||
if (data) data = Data.create(data)
|
|
||||||
|
|
||||||
// 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
|
|
||||||
})
|
|
||||||
|
|
||||||
if (match) wrappers = wrappers.add(match)
|
|
||||||
return wrappers
|
|
||||||
}, new Set())
|
|
||||||
|
|
||||||
// Replace each of the wrappers with their child nodes.
|
|
||||||
wrappers.forEach((wrapper) => {
|
|
||||||
const parent = node.getParent(wrapper)
|
|
||||||
|
|
||||||
// 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())
|
|
||||||
|
|
||||||
// Update the parent.
|
|
||||||
node = parent == node
|
|
||||||
? node.merge({ nodes })
|
|
||||||
: node.updateDescendant(parent.merge({ nodes }))
|
|
||||||
})
|
|
||||||
|
|
||||||
return node.normalize()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -131,6 +131,38 @@ class Selection extends Record(DEFAULTS) {
|
|||||||
return endKey == last.key && endOffset == last.length
|
return endKey == last.key && endOffset == last.length
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether the selection has an edge at the start of a `node`.
|
||||||
|
*
|
||||||
|
* @param {Node} node
|
||||||
|
* @return {Boolean} hasEdgeAtStart
|
||||||
|
*/
|
||||||
|
|
||||||
|
hasEdgeAtStartOf(node) {
|
||||||
|
const { startKey, startOffset, endKey, endOffset } = this
|
||||||
|
const first = node.kind == 'text' ? node : node.getTextNodes().first()
|
||||||
|
return (
|
||||||
|
(startKey == first.key && startOffset == 0) ||
|
||||||
|
(endKey == first.key && endOffset == 0)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether the selection has an edge at the end of a `node`.
|
||||||
|
*
|
||||||
|
* @param {Node} node
|
||||||
|
* @return {Boolean} hasEdgeAtEnd
|
||||||
|
*/
|
||||||
|
|
||||||
|
hasEdgeAtEndOf(node) {
|
||||||
|
const { startKey, startOffset, endKey, endOffset } = this
|
||||||
|
const last = node.kind == 'text' ? node : node.getTextNodes().last()
|
||||||
|
return (
|
||||||
|
(startKey == last.key && startOffset == last.length) ||
|
||||||
|
(endKey == last.key && endOffset == last.length)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Normalize the selection, relative to a `node`, ensuring that the anchor
|
* Normalize the selection, relative to a `node`, ensuring that the anchor
|
||||||
* and focus nodes of the selection always refer to leaf text nodes.
|
* and focus nodes of the selection always refer to leaf text nodes.
|
||||||
|
@@ -0,0 +1,18 @@
|
|||||||
|
|
||||||
|
export default function (state) {
|
||||||
|
const { document, selection } = state
|
||||||
|
const texts = document.getTextNodes()
|
||||||
|
const first = texts.first()
|
||||||
|
const last = texts.last()
|
||||||
|
const range = selection.merge({
|
||||||
|
anchorKey: first.key,
|
||||||
|
anchorOffset: 2,
|
||||||
|
focusKey: last.key,
|
||||||
|
focusOffset: 2
|
||||||
|
})
|
||||||
|
|
||||||
|
return state
|
||||||
|
.transform()
|
||||||
|
.deleteAtRange(range, 'code')
|
||||||
|
.apply()
|
||||||
|
}
|
@@ -0,0 +1,20 @@
|
|||||||
|
|
||||||
|
nodes:
|
||||||
|
- kind: block
|
||||||
|
type: paragraph
|
||||||
|
nodes:
|
||||||
|
- kind: inline
|
||||||
|
type: link
|
||||||
|
nodes:
|
||||||
|
- kind: text
|
||||||
|
ranges:
|
||||||
|
- text: word
|
||||||
|
- kind: block
|
||||||
|
type: paragraph
|
||||||
|
nodes:
|
||||||
|
- kind: inline
|
||||||
|
type: link
|
||||||
|
nodes:
|
||||||
|
- kind: text
|
||||||
|
ranges:
|
||||||
|
- text: another
|
@@ -0,0 +1,17 @@
|
|||||||
|
|
||||||
|
nodes:
|
||||||
|
- kind: block
|
||||||
|
type: paragraph
|
||||||
|
nodes:
|
||||||
|
- kind: inline
|
||||||
|
type: link
|
||||||
|
nodes:
|
||||||
|
- kind: text
|
||||||
|
ranges:
|
||||||
|
- text: wo
|
||||||
|
- kind: inline
|
||||||
|
type: link
|
||||||
|
nodes:
|
||||||
|
- kind: text
|
||||||
|
ranges:
|
||||||
|
- text: other
|
@@ -0,0 +1,18 @@
|
|||||||
|
|
||||||
|
export default function (state) {
|
||||||
|
const { document, selection } = state
|
||||||
|
const texts = document.getTextNodes()
|
||||||
|
const first = texts.first()
|
||||||
|
const last = texts.last()
|
||||||
|
const range = selection.merge({
|
||||||
|
anchorKey: first.key,
|
||||||
|
anchorOffset: 2,
|
||||||
|
focusKey: last.key,
|
||||||
|
focusOffset: 2
|
||||||
|
})
|
||||||
|
|
||||||
|
return state
|
||||||
|
.transform()
|
||||||
|
.deleteAtRange(range, 'code')
|
||||||
|
.apply()
|
||||||
|
}
|
@@ -0,0 +1,23 @@
|
|||||||
|
|
||||||
|
nodes:
|
||||||
|
- kind: block
|
||||||
|
type: paragraph
|
||||||
|
nodes:
|
||||||
|
- kind: text
|
||||||
|
ranges:
|
||||||
|
- text: word
|
||||||
|
- kind: block
|
||||||
|
type: paragraph
|
||||||
|
nodes:
|
||||||
|
- kind: block
|
||||||
|
type: paragraph
|
||||||
|
nodes:
|
||||||
|
- kind: text
|
||||||
|
ranges:
|
||||||
|
- text: middle
|
||||||
|
- kind: block
|
||||||
|
type: paragraph
|
||||||
|
nodes:
|
||||||
|
- kind: text
|
||||||
|
ranges:
|
||||||
|
- text: another
|
@@ -0,0 +1,8 @@
|
|||||||
|
|
||||||
|
nodes:
|
||||||
|
- kind: block
|
||||||
|
type: paragraph
|
||||||
|
nodes:
|
||||||
|
- kind: text
|
||||||
|
ranges:
|
||||||
|
- text: woother
|
@@ -6,9 +6,3 @@ nodes:
|
|||||||
- kind: text
|
- kind: text
|
||||||
ranges:
|
ranges:
|
||||||
- text: word
|
- text: word
|
||||||
- kind: block
|
|
||||||
type: paragraph
|
|
||||||
nodes:
|
|
||||||
- kind: text
|
|
||||||
ranges:
|
|
||||||
- text: ""
|
|
||||||
|
@@ -1,11 +1,5 @@
|
|||||||
|
|
||||||
nodes:
|
nodes:
|
||||||
- kind: block
|
|
||||||
type: paragraph
|
|
||||||
nodes:
|
|
||||||
- kind: text
|
|
||||||
ranges:
|
|
||||||
- text: ""
|
|
||||||
- kind: block
|
- kind: block
|
||||||
type: paragraph
|
type: paragraph
|
||||||
nodes:
|
nodes:
|
||||||
|
17
test/transforms/fixtures/split-block-at-range/depth/index.js
Normal file
17
test/transforms/fixtures/split-block-at-range/depth/index.js
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
|
||||||
|
export default function (state) {
|
||||||
|
const { document, selection } = state
|
||||||
|
const texts = document.getTextNodes()
|
||||||
|
const first = texts.first()
|
||||||
|
const range = selection.merge({
|
||||||
|
anchorKey: first.key,
|
||||||
|
anchorOffset: 2,
|
||||||
|
focusKey: first.key,
|
||||||
|
focusOffset: 2
|
||||||
|
})
|
||||||
|
|
||||||
|
return state
|
||||||
|
.transform()
|
||||||
|
.splitBlockAtRange(range, Infinity)
|
||||||
|
.apply()
|
||||||
|
}
|
@@ -0,0 +1,14 @@
|
|||||||
|
|
||||||
|
nodes:
|
||||||
|
- kind: block
|
||||||
|
type: paragraph
|
||||||
|
nodes:
|
||||||
|
- kind: block
|
||||||
|
type: paragraph
|
||||||
|
nodes:
|
||||||
|
- kind: block
|
||||||
|
type: paragraph
|
||||||
|
nodes:
|
||||||
|
- kind: text
|
||||||
|
ranges:
|
||||||
|
- text: word
|
@@ -0,0 +1,26 @@
|
|||||||
|
|
||||||
|
nodes:
|
||||||
|
- kind: block
|
||||||
|
type: paragraph
|
||||||
|
nodes:
|
||||||
|
- kind: block
|
||||||
|
type: paragraph
|
||||||
|
nodes:
|
||||||
|
- kind: block
|
||||||
|
type: paragraph
|
||||||
|
nodes:
|
||||||
|
- kind: text
|
||||||
|
ranges:
|
||||||
|
- text: wo
|
||||||
|
- kind: block
|
||||||
|
type: paragraph
|
||||||
|
nodes:
|
||||||
|
- kind: block
|
||||||
|
type: paragraph
|
||||||
|
nodes:
|
||||||
|
- kind: block
|
||||||
|
type: paragraph
|
||||||
|
nodes:
|
||||||
|
- kind: text
|
||||||
|
ranges:
|
||||||
|
- text: rd
|
@@ -9,9 +9,3 @@ nodes:
|
|||||||
- kind: text
|
- kind: text
|
||||||
ranges:
|
ranges:
|
||||||
- text: word
|
- text: word
|
||||||
- kind: inline
|
|
||||||
type: link
|
|
||||||
nodes:
|
|
||||||
- kind: text
|
|
||||||
ranges:
|
|
||||||
- text: ""
|
|
||||||
|
@@ -3,12 +3,6 @@ nodes:
|
|||||||
- kind: block
|
- kind: block
|
||||||
type: paragraph
|
type: paragraph
|
||||||
nodes:
|
nodes:
|
||||||
- kind: inline
|
|
||||||
type: link
|
|
||||||
nodes:
|
|
||||||
- kind: text
|
|
||||||
ranges:
|
|
||||||
- text: ""
|
|
||||||
- kind: inline
|
- kind: inline
|
||||||
type: link
|
type: link
|
||||||
nodes:
|
nodes:
|
||||||
|
@@ -0,0 +1,17 @@
|
|||||||
|
|
||||||
|
export default function (state) {
|
||||||
|
const { document, selection } = state
|
||||||
|
const texts = document.getTextNodes()
|
||||||
|
const first = texts.first()
|
||||||
|
const range = selection.merge({
|
||||||
|
anchorKey: first.key,
|
||||||
|
anchorOffset: 2,
|
||||||
|
focusKey: first.key,
|
||||||
|
focusOffset: 2
|
||||||
|
})
|
||||||
|
|
||||||
|
return state
|
||||||
|
.transform()
|
||||||
|
.splitInlineAtRange(range, 1)
|
||||||
|
.apply()
|
||||||
|
}
|
@@ -0,0 +1,14 @@
|
|||||||
|
|
||||||
|
nodes:
|
||||||
|
- kind: block
|
||||||
|
type: paragraph
|
||||||
|
nodes:
|
||||||
|
- kind: inline
|
||||||
|
type: link
|
||||||
|
nodes:
|
||||||
|
- kind: inline
|
||||||
|
type: link
|
||||||
|
nodes:
|
||||||
|
- kind: text
|
||||||
|
ranges:
|
||||||
|
- text: word
|
@@ -0,0 +1,20 @@
|
|||||||
|
|
||||||
|
nodes:
|
||||||
|
- kind: block
|
||||||
|
type: paragraph
|
||||||
|
nodes:
|
||||||
|
- kind: inline
|
||||||
|
type: link
|
||||||
|
nodes:
|
||||||
|
- kind: inline
|
||||||
|
type: link
|
||||||
|
nodes:
|
||||||
|
- kind: text
|
||||||
|
ranges:
|
||||||
|
- text: wo
|
||||||
|
- kind: inline
|
||||||
|
type: link
|
||||||
|
nodes:
|
||||||
|
- kind: text
|
||||||
|
ranges:
|
||||||
|
- text: rd
|
@@ -4,7 +4,7 @@ import fs from 'fs'
|
|||||||
import readMetadata from 'read-metadata'
|
import readMetadata from 'read-metadata'
|
||||||
import toCamel from 'to-camel-case'
|
import toCamel from 'to-camel-case'
|
||||||
import { Raw, State } from '../..'
|
import { Raw, State } from '../..'
|
||||||
import { equal } from '../helpers/assert-json'
|
import { equal, strictEqual } from '../helpers/assert-json'
|
||||||
import { resolve } from 'path'
|
import { resolve } from 'path'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -30,7 +30,7 @@ describe('transforms', () => {
|
|||||||
let state = Raw.deserialize(input)
|
let state = Raw.deserialize(input)
|
||||||
state = fn(state)
|
state = fn(state)
|
||||||
const output = Raw.serialize(state)
|
const output = Raw.serialize(state)
|
||||||
equal(output, expected)
|
strictEqual(output, expected)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
Reference in New Issue
Block a user