diff --git a/lib/transforms/at-current-range.js b/lib/transforms/at-current-range.js index 86f28f212..47aa7118c 100644 --- a/lib/transforms/at-current-range.js +++ b/lib/transforms/at-current-range.js @@ -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) { diff --git a/lib/transforms/at-range.js b/lib/transforms/at-range.js index 25569455a..2f0aeb569 100644 --- a/lib/transforms/at-range.js +++ b/lib/transforms/at-range.js @@ -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 } diff --git a/test/transforms/fixtures/insert-fragment/middle-inline-fragment-inline/index.js b/test/transforms/fixtures/insert-fragment/middle-inline-fragment-inline/index.js index d9d042c7f..32ebe38ed 100644 --- a/test/transforms/fixtures/insert-fragment/middle-inline-fragment-inline/index.js +++ b/test/transforms/fixtures/insert-fragment/middle-inline-fragment-inline/index.js @@ -27,7 +27,7 @@ export default function (state) { .apply() const updated = next.document.getTexts().get(1) - +debugger assert.deepEqual( next.selection.toJS(), range.merge({ diff --git a/test/transforms/fixtures/insert-fragment/middle-inline/index.js b/test/transforms/fixtures/insert-fragment/middle-inline/index.js index d58efb45b..d9d042c7f 100644 --- a/test/transforms/fixtures/insert-fragment/middle-inline/index.js +++ b/test/transforms/fixtures/insert-fragment/middle-inline/index.js @@ -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(),