1
0
mirror of https://github.com/ianstormtaylor/slate.git synced 2025-08-21 14:41:23 +02:00

refactor transforms

This commit is contained in:
Ian Storm Taylor
2016-08-17 15:59:03 -07:00
parent 8434850890
commit 4e24f2666a
4 changed files with 89 additions and 174 deletions

View File

@@ -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) {

View File

@@ -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
}

View File

@@ -27,7 +27,7 @@ export default function (state) {
.apply()
const updated = next.document.getTexts().get(1)
debugger
assert.deepEqual(
next.selection.toJS(),
range.merge({

View File

@@ -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(),