mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-08-22 06:53:25 +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.
|
// Determine what the selection should be after inserting.
|
||||||
const keys = beforeTexts.map(text => text.key)
|
const keys = beforeTexts.map(text => text.key)
|
||||||
const text = document.getTexts().findLast(n => !keys.includes(n.key))
|
const news = document.getTexts().filter(n => !keys.includes(n.key))
|
||||||
const previousText = text ? document.getPreviousText(text) : null
|
const text = news.size ? news.takeLast(2).first() : null
|
||||||
|
|
||||||
if (text && lastInline && previousText) {
|
if (text && lastInline) {
|
||||||
after = selection.collapseToEndOf(previousText)
|
after = selection.collapseToEndOf(text)
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (text && lastInline) {
|
else if (text && lastInline) {
|
||||||
|
@@ -49,105 +49,51 @@ export function addMarkAtRange(transform, range, mark) {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export function deleteAtRange(transform, range) {
|
export function deleteAtRange(transform, range) {
|
||||||
// if (range.isCollapsed) return transform
|
if (range.isCollapsed) return transform
|
||||||
|
|
||||||
// const { state } = transform
|
const { startKey, startOffset, endKey, endOffset } = range
|
||||||
// const { document } = state
|
|
||||||
// const { startKey, startOffset, endKey, endOffset } = range
|
|
||||||
// const startText = document.assertDescendant(startKey)
|
|
||||||
// const endText = document.assertDescendant(endKey)
|
|
||||||
|
|
||||||
// if (startKey == endKey) {
|
if (startKey == endKey) {
|
||||||
// const index = startOffset
|
const index = startOffset
|
||||||
// const length = endOffset - startOffset
|
const length = endOffset - startOffset
|
||||||
// return transform.removeTextByKey(startKey, index, length)
|
return transform.removeTextByKey(startKey, index, length)
|
||||||
// }
|
}
|
||||||
|
|
||||||
let { state } = transform
|
let { state } = transform
|
||||||
let { document } = state
|
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 ancestor = document.getCommonAncestor(startKey, endKey)
|
||||||
let isAncestor = ancestor == document
|
const startChild = ancestor.getHighestChild(startKey)
|
||||||
const ancestorDepth = isAncestor ? 0 : document.getDepth(ancestor)
|
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
|
state = transform.state
|
||||||
document = state.document
|
document = state.document
|
||||||
ancestor = document.getCommonAncestor(startKey, endKey)
|
ancestor = document.getCommonAncestor(startKey, endKey)
|
||||||
|
|
||||||
const startText = ancestor.getDescendant(startKey)
|
const startBlock = document.getClosestBlock(startKey)
|
||||||
const startEdgeText = ancestor.getNextText(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)
|
ancestor.nodes.slice(startIndex + 1, endIndex).forEach((child) => {
|
||||||
const endEdgeText = ancestor.getDescendant(endKey)
|
transform.removeNodeByKey(child.key)
|
||||||
|
})
|
||||||
|
|
||||||
startBlock = document.getClosestBlock(startText)
|
endBlock.nodes.forEach((child, i) => {
|
||||||
endBlock = document.getClosestBlock(endText)
|
const newKey = startBlock.key
|
||||||
|
const newIndex = startBlock.nodes.size + i
|
||||||
|
transform.moveNodeByKey(child.key, newKey, newIndex)
|
||||||
|
})
|
||||||
|
|
||||||
// Remove the new blocks inside the edges.
|
transform.removeNodeByKey(endLonelyParent.key)
|
||||||
const startEdgeBlock = ancestor.getFurthestBlock(startEdgeText)
|
transform.normalizeDocument()
|
||||||
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
|
|
||||||
return transform
|
return transform
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -325,107 +271,76 @@ export function insertBlockAtRange(transform, range, block) {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export function insertFragmentAtRange(transform, range, fragment) {
|
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) {
|
if (range.isExpanded) {
|
||||||
transform = deleteAtRange(transform, range)
|
transform.deleteAtRange(range)
|
||||||
state = transform.state
|
|
||||||
document = state.document
|
|
||||||
range = range.collapseToStart()
|
range = range.collapseToStart()
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the fragment is empty, do nothing.
|
|
||||||
if (!fragment.length) return transform
|
if (!fragment.length) return transform
|
||||||
|
|
||||||
// Make sure each node in the fragment has a unique key.
|
|
||||||
fragment = fragment.mapDescendants(child => child.set('key', uid()))
|
fragment = fragment.mapDescendants(child => child.set('key', uid()))
|
||||||
|
|
||||||
// Split the inlines if need be.
|
const { startKey, startOffset } = range
|
||||||
if (!document.isInlineSplitAtRange(range)) {
|
let { state } = transform
|
||||||
transform = splitInlineAtRange(transform, range)
|
let { document } = state
|
||||||
state = transform.state
|
let startText = document.getDescendant(startKey)
|
||||||
document = state.document
|
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 blocks = fragment.getBlocks()
|
||||||
const firstBlock = blocks.first()
|
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 (firstBlock != lastBlock) {
|
||||||
if (block.length == 0) {
|
const lonelyParent = fragment.getFurthest(firstBlock, p => p.nodes.size == 1)
|
||||||
block = block.merge({
|
const lonelyChild = lonelyParent || firstBlock
|
||||||
type: firstBlock.type,
|
const startIndex = parent.nodes.indexOf(startBlock)
|
||||||
data: firstBlock.data
|
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.
|
transform.splitNodeByKey(startChild.key, offset)
|
||||||
if (startChild) {
|
|
||||||
block = block.insertChildrenAfter(startChild, firstBlock.nodes)
|
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 {
|
} 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)
|
transform.normalizeDocument()
|
||||||
|
|
||||||
// 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
|
|
||||||
return transform
|
return transform
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -27,7 +27,7 @@ export default function (state) {
|
|||||||
.apply()
|
.apply()
|
||||||
|
|
||||||
const updated = next.document.getTexts().get(1)
|
const updated = next.document.getTexts().get(1)
|
||||||
|
debugger
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
next.selection.toJS(),
|
next.selection.toJS(),
|
||||||
range.merge({
|
range.merge({
|
||||||
|
@@ -26,7 +26,7 @@ export default function (state) {
|
|||||||
.insertFragment(fragment)
|
.insertFragment(fragment)
|
||||||
.apply()
|
.apply()
|
||||||
|
|
||||||
const updated = next.document.getTexts().last()
|
const updated = next.document.getTexts().get(1)
|
||||||
|
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
next.selection.toJS(),
|
next.selection.toJS(),
|
||||||
|
Reference in New Issue
Block a user