mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-08-21 14:41:23 +02:00
refactor transforms
This commit is contained in:
@@ -300,11 +300,11 @@ export function insertFragment(transform, fragment) {
|
||||
|
||||
// Determine what the selection should be after inserting.
|
||||
const keys = beforeTexts.map(text => text.key)
|
||||
const text = document.getTexts().findLast(n => !keys.includes(n.key))
|
||||
const previousText = text ? document.getPreviousText(text) : null
|
||||
const news = document.getTexts().filter(n => !keys.includes(n.key))
|
||||
const text = news.size ? news.takeLast(2).first() : null
|
||||
|
||||
if (text && lastInline && previousText) {
|
||||
after = selection.collapseToEndOf(previousText)
|
||||
if (text && lastInline) {
|
||||
after = selection.collapseToEndOf(text)
|
||||
}
|
||||
|
||||
else if (text && lastInline) {
|
||||
|
@@ -49,105 +49,51 @@ export function addMarkAtRange(transform, range, mark) {
|
||||
*/
|
||||
|
||||
export function deleteAtRange(transform, range) {
|
||||
// if (range.isCollapsed) return transform
|
||||
if (range.isCollapsed) return transform
|
||||
|
||||
// const { state } = transform
|
||||
// const { document } = state
|
||||
// const { startKey, startOffset, endKey, endOffset } = range
|
||||
// const startText = document.assertDescendant(startKey)
|
||||
// const endText = document.assertDescendant(endKey)
|
||||
const { startKey, startOffset, endKey, endOffset } = range
|
||||
|
||||
// if (startKey == endKey) {
|
||||
// const index = startOffset
|
||||
// const length = endOffset - startOffset
|
||||
// return transform.removeTextByKey(startKey, index, length)
|
||||
// }
|
||||
if (startKey == endKey) {
|
||||
const index = startOffset
|
||||
const length = endOffset - startOffset
|
||||
return transform.removeTextByKey(startKey, index, length)
|
||||
}
|
||||
|
||||
let { state } = transform
|
||||
let { document } = state
|
||||
|
||||
// If the range is collapsed, there's nothing to delete.
|
||||
if (range.isCollapsed) return transform
|
||||
|
||||
// Make sure the children exist.
|
||||
const { startKey, startOffset, endKey, endOffset } = range
|
||||
document.assertDescendant(startKey)
|
||||
document.assertDescendant(endKey)
|
||||
|
||||
// If the start and end nodes are the same, just remove characters.
|
||||
if (startKey == endKey) {
|
||||
let text = document.getDescendant(startKey)
|
||||
text = text.removeText(startOffset, endOffset - startOffset)
|
||||
document = document.updateDescendant(text)
|
||||
document = document.normalize()
|
||||
state = state.merge({ document })
|
||||
transform.state = state
|
||||
return transform
|
||||
}
|
||||
|
||||
// Split the blocks and determine the edge boundaries.
|
||||
const start = range.collapseToStart()
|
||||
const end = range.collapseToEnd()
|
||||
let startBlock = document.getClosestBlock(startKey)
|
||||
let endBlock = document.getClosestBlock(endKey)
|
||||
const startDepth = document.getDepth(startBlock)
|
||||
const endDepth = document.getDepth(endBlock)
|
||||
|
||||
let ancestor = document.getCommonAncestor(startKey, endKey)
|
||||
let isAncestor = ancestor == document
|
||||
const ancestorDepth = isAncestor ? 0 : document.getDepth(ancestor)
|
||||
const startChild = ancestor.getHighestChild(startKey)
|
||||
const endChild = ancestor.getHighestChild(endKey)
|
||||
const startOff = startChild.getOffset(startKey) + startOffset
|
||||
const endOff = endChild.getOffset(endKey) + endOffset
|
||||
|
||||
transform.splitNodeByKey(startChild.key, startOff)
|
||||
transform.splitNodeByKey(endChild.key, endOff)
|
||||
|
||||
transform = splitBlockAtRange(transform, start, startDepth - ancestorDepth)
|
||||
transform = splitBlockAtRange(transform, end, endDepth - ancestorDepth)
|
||||
state = transform.state
|
||||
document = state.document
|
||||
ancestor = document.getCommonAncestor(startKey, endKey)
|
||||
|
||||
const startText = ancestor.getDescendant(startKey)
|
||||
const startEdgeText = ancestor.getNextText(startKey)
|
||||
const startBlock = document.getClosestBlock(startKey)
|
||||
const endBlock = document.getClosestBlock(document.getNextText(endKey))
|
||||
const startIndex = ancestor.nodes.indexOf(startBlock)
|
||||
const endIndex = ancestor.nodes.indexOf(endBlock)
|
||||
const endLonelyParent = ancestor.getHighestChild(endBlock, (parent) => {
|
||||
return parent.nodes.size == 1
|
||||
})
|
||||
|
||||
const endText = ancestor.getNextText(endKey)
|
||||
const endEdgeText = ancestor.getDescendant(endKey)
|
||||
ancestor.nodes.slice(startIndex + 1, endIndex).forEach((child) => {
|
||||
transform.removeNodeByKey(child.key)
|
||||
})
|
||||
|
||||
startBlock = document.getClosestBlock(startText)
|
||||
endBlock = document.getClosestBlock(endText)
|
||||
endBlock.nodes.forEach((child, i) => {
|
||||
const newKey = startBlock.key
|
||||
const newIndex = startBlock.nodes.size + i
|
||||
transform.moveNodeByKey(child.key, newKey, newIndex)
|
||||
})
|
||||
|
||||
// Remove the new blocks inside the edges.
|
||||
const startEdgeBlock = ancestor.getFurthestBlock(startEdgeText)
|
||||
const endEdgeBlock = ancestor.getFurthestBlock(endEdgeText)
|
||||
|
||||
const nodes = ancestor.nodes
|
||||
.takeUntil(n => n == startEdgeBlock)
|
||||
.concat(ancestor.nodes.skipUntil(n => n == endEdgeBlock).rest())
|
||||
|
||||
ancestor = ancestor.merge({ nodes })
|
||||
|
||||
// Take the end edge's inline nodes and move them to the start edge.
|
||||
const startNodes = startBlock.nodes.concat(endBlock.nodes)
|
||||
startBlock = startBlock.merge({ nodes: startNodes })
|
||||
ancestor = ancestor.updateDescendant(startBlock)
|
||||
|
||||
// While the end child is an only child, remove the block it's in.
|
||||
let endParent = ancestor.getClosestBlock(endBlock)
|
||||
|
||||
while (endParent && endParent.nodes.size == 1) {
|
||||
endBlock = endParent
|
||||
endParent = ancestor.getClosestBlock(endParent)
|
||||
}
|
||||
|
||||
ancestor = ancestor.removeDescendant(endBlock)
|
||||
|
||||
// Update the document.
|
||||
document = isAncestor
|
||||
? ancestor
|
||||
: document.updateDescendant(ancestor)
|
||||
|
||||
// Normalize the adjacent text nodes.
|
||||
document = document.normalize()
|
||||
|
||||
// Update the state.
|
||||
state = state.merge({ document })
|
||||
transform.state = state
|
||||
transform.removeNodeByKey(endLonelyParent.key)
|
||||
transform.normalizeDocument()
|
||||
return transform
|
||||
}
|
||||
|
||||
@@ -325,107 +271,76 @@ export function insertBlockAtRange(transform, range, block) {
|
||||
*/
|
||||
|
||||
export function insertFragmentAtRange(transform, range, fragment) {
|
||||
let { state } = transform
|
||||
let { document } = state
|
||||
|
||||
// Ensure that the selection is normalized.
|
||||
range = range.normalize(document)
|
||||
|
||||
// If the range is expanded, delete first.
|
||||
if (range.isExpanded) {
|
||||
transform = deleteAtRange(transform, range)
|
||||
state = transform.state
|
||||
document = state.document
|
||||
transform.deleteAtRange(range)
|
||||
range = range.collapseToStart()
|
||||
}
|
||||
|
||||
// If the fragment is empty, do nothing.
|
||||
if (!fragment.length) return transform
|
||||
|
||||
// Make sure each node in the fragment has a unique key.
|
||||
fragment = fragment.mapDescendants(child => child.set('key', uid()))
|
||||
|
||||
// Split the inlines if need be.
|
||||
if (!document.isInlineSplitAtRange(range)) {
|
||||
transform = splitInlineAtRange(transform, range)
|
||||
state = transform.state
|
||||
document = state.document
|
||||
}
|
||||
const { startKey, startOffset } = range
|
||||
let { state } = transform
|
||||
let { document } = state
|
||||
let startText = document.getDescendant(startKey)
|
||||
let startBlock = document.getClosestBlock(startText)
|
||||
let startChild = startBlock.getHighestChild(startText)
|
||||
const parent = document.getParent(startBlock)
|
||||
const index = parent.nodes.indexOf(startBlock)
|
||||
const offset = startChild == startText
|
||||
? startOffset
|
||||
: startChild.getOffset(startText) + startOffset
|
||||
|
||||
// Determine the start and next children to insert into.
|
||||
const { startKey, endKey } = range
|
||||
let block = document.getClosestBlock(startKey)
|
||||
let start = document.getDescendant(startKey)
|
||||
let startChild
|
||||
let nextChild
|
||||
|
||||
if (range.isAtStartOf(document)) {
|
||||
nextChild = document.getClosestBlock(document.getTexts().first())
|
||||
}
|
||||
|
||||
if (range.isAtStartOf(block)) {
|
||||
nextChild = block.getHighestChild(block.getTexts().first())
|
||||
}
|
||||
|
||||
else if (range.isAtStartOf(start)) {
|
||||
startChild = block.getHighestChild(block.getPreviousText(start))
|
||||
nextChild = block.getNextSibling(startChild)
|
||||
}
|
||||
|
||||
else {
|
||||
startChild = block.getHighestChild(start)
|
||||
nextChild = block.getNextSibling(startChild)
|
||||
}
|
||||
|
||||
// Get the first and last block of the fragment.
|
||||
const blocks = fragment.getBlocks()
|
||||
const firstBlock = blocks.first()
|
||||
let lastBlock = blocks.last()
|
||||
const lastBlock = blocks.last()
|
||||
|
||||
// If the block is empty, merge in the first block's type and data.
|
||||
if (block.length == 0) {
|
||||
block = block.merge({
|
||||
type: firstBlock.type,
|
||||
data: firstBlock.data
|
||||
if (firstBlock != lastBlock) {
|
||||
const lonelyParent = fragment.getFurthest(firstBlock, p => p.nodes.size == 1)
|
||||
const lonelyChild = lonelyParent || firstBlock
|
||||
const startIndex = parent.nodes.indexOf(startBlock)
|
||||
fragment = fragment.removeDescendant(lonelyChild)
|
||||
|
||||
fragment.nodes.forEach((node, i) => {
|
||||
const newIndex = startIndex + i + 2
|
||||
transform.insertNodeByKey(parent.key, newIndex, node)
|
||||
})
|
||||
}
|
||||
|
||||
// Insert the first blocks nodes into the starting block.
|
||||
if (startChild) {
|
||||
block = block.insertChildrenAfter(startChild, firstBlock.nodes)
|
||||
transform.splitNodeByKey(startChild.key, offset)
|
||||
|
||||
state = transform.state
|
||||
document = state.document
|
||||
startText = document.getDescendant(startKey)
|
||||
startBlock = document.getClosestBlock(startKey)
|
||||
startChild = startBlock.getHighestChild(startText)
|
||||
|
||||
if (firstBlock != lastBlock) {
|
||||
const nextChild = startBlock.getNextSibling(startChild)
|
||||
const nextNodes = startBlock.nodes.skipUntil(n => n == nextChild)
|
||||
const lastIndex = lastBlock.nodes.size
|
||||
|
||||
nextNodes.forEach((node, i) => {
|
||||
const newIndex = lastIndex + i + 1
|
||||
transform.moveNodeByKey(node.key, lastBlock.key, newIndex)
|
||||
})
|
||||
}
|
||||
|
||||
if (startBlock.isEmpty) {
|
||||
transform.removeNodeByKey(startBlock.key)
|
||||
transform.insertNodeByKey(parent.key, index, firstBlock)
|
||||
} else {
|
||||
block = block.insertChildrenBefore(nextChild, firstBlock.nodes)
|
||||
const inlineChild = startBlock.getHighestChild(startText)
|
||||
const inlineIndex = startBlock.nodes.indexOf(inlineChild)
|
||||
|
||||
firstBlock.nodes.forEach((inline, i) => {
|
||||
const newIndex = inlineIndex + i + 1
|
||||
transform.insertNodeByKey(startBlock.key, newIndex, inline)
|
||||
})
|
||||
}
|
||||
|
||||
document = document.updateDescendant(block)
|
||||
|
||||
// If there are no other siblings, that's it.
|
||||
if (firstBlock == lastBlock) {
|
||||
document = document.normalize()
|
||||
state = state.merge({ document })
|
||||
transform.state = state
|
||||
return transform
|
||||
}
|
||||
|
||||
// Otherwise, remove the fragment's first block's highest solo parent...
|
||||
let highestParent = fragment.getHighestOnlyChildParent(firstBlock)
|
||||
fragment = fragment.removeDescendant(highestParent || firstBlock)
|
||||
|
||||
// Then, add the inlines after the cursor from the current block to the
|
||||
// start of the last block in the fragment.
|
||||
if (nextChild) {
|
||||
lastBlock = lastBlock.concatChildren(block.getChildrenAfterIncluding(nextChild))
|
||||
fragment = fragment.updateDescendant(lastBlock)
|
||||
|
||||
block = block.removeChildrenAfterIncluding(nextChild)
|
||||
document = document.updateDescendant(block)
|
||||
}
|
||||
|
||||
// Finally, add the fragment's children after the block.
|
||||
document = document.insertChildrenAfter(block, fragment.nodes)
|
||||
document = document.normalize()
|
||||
state = state.merge({ document })
|
||||
transform.state = state
|
||||
transform.normalizeDocument()
|
||||
return transform
|
||||
}
|
||||
|
||||
|
@@ -27,7 +27,7 @@ export default function (state) {
|
||||
.apply()
|
||||
|
||||
const updated = next.document.getTexts().get(1)
|
||||
|
||||
debugger
|
||||
assert.deepEqual(
|
||||
next.selection.toJS(),
|
||||
range.merge({
|
||||
|
@@ -26,7 +26,7 @@ export default function (state) {
|
||||
.insertFragment(fragment)
|
||||
.apply()
|
||||
|
||||
const updated = next.document.getTexts().last()
|
||||
const updated = next.document.getTexts().get(1)
|
||||
|
||||
assert.deepEqual(
|
||||
next.selection.toJS(),
|
||||
|
Reference in New Issue
Block a user