From fa5d59c063dd99a969e465d2730bcfad6591db3b Mon Sep 17 00:00:00 2001 From: Ian Storm Taylor Date: Fri, 14 Oct 2016 16:40:45 -0700 Subject: [PATCH] handle lots of splitting cases --- src/models/inline.js | 4 + src/transforms/at-current-range.js | 30 +++++-- src/transforms/at-range.js | 19 ++-- src/transforms/by-key.js | 90 ++++++++++++++++++- .../split-inline/block-end/index.js | 2 +- .../split-inline/block-end/output.yaml | 5 -- .../split-inline/block-start/output.yaml | 5 -- .../block-end/output.yaml | 5 -- .../block-start/output.yaml | 5 -- 9 files changed, 129 insertions(+), 36 deletions(-) diff --git a/src/models/inline.js b/src/models/inline.js index dfb433dfe..58b7f5b53 100644 --- a/src/models/inline.js +++ b/src/models/inline.js @@ -53,6 +53,10 @@ class Inline extends new Record(DEFAULTS) { properties.isVoid = !!properties.isVoid properties.nodes = Inline.createList(properties.nodes) + if (properties.nodes.size == 0) { + properties.nodes = properties.nodes.push(Text.create()) + } + return new Inline(properties).normalize() } diff --git a/src/transforms/at-current-range.js b/src/transforms/at-current-range.js index 767247091..e7b78b43a 100644 --- a/src/transforms/at-current-range.js +++ b/src/transforms/at-current-range.js @@ -436,18 +436,38 @@ export function splitBlock(transform, depth = 1) { export function splitInline(transform, depth = Infinity) { let { state } = transform let { document, selection } = state + + // If the selection is expanded, remove it first. + if (selection.isExpanded) { + transform.delete() + state = transform.state + document = state.document + selection = state.selection + } + let after = selection + const { startKey, startOffset } = selection + let startNode = document.assertDescendant(startKey) + const furthestInline = document.getFurthestInline(startKey) + const offset = furthestInline.getOffset(startNode) + + // If the selection is at the start of end of the furthest inline, there isn't + // anything to split, so abort. + if ( + (offset + startOffset == 0) || + (offset + startNode.length == startOffset) + ) { + return transform + } transform.unsetSelection() transform.splitInlineAtRange(selection, depth) state = transform.state document = state.document + const closestInline = document.getClosestInline(startKey) - const { startKey } = selection - const inlineParent = document.getClosestInline(startKey) - - if (inlineParent) { - const startNode = document.getDescendant(startKey) + if (closestInline) { + startNode = document.getDescendant(startKey) const nextNode = document.getNextText(startNode) after = selection.collapseToStartOf(nextNode) } diff --git a/src/transforms/at-range.js b/src/transforms/at-range.js index ce1612ce2..f4c3818b9 100644 --- a/src/transforms/at-range.js +++ b/src/transforms/at-range.js @@ -268,7 +268,6 @@ export function insertBlockAtRange(transform, range, block) { transform.insertNodeByKey(parent.key, index + 1, block) } - transform.normalizeDocument() return transform } @@ -385,7 +384,6 @@ export function insertInlineAtRange(transform, range, inline) { transform.splitNodeByKey(startKey, startOffset) transform.insertNodeByKey(parent.key, index + 1, inline) - transform.normalizeDocument() return transform } @@ -818,15 +816,25 @@ export function wrapInlineAtRange(transform, range, inline) { : endChild.getOffset(endKey) + endOffset if (startBlock == endBlock) { - transform.splitNodeByKey(endChild.key, endOff) - transform.splitNodeByKey(startChild.key, startOff) + if (endOff != endChild.length) { + transform.splitNodeByKey(endChild.key, endOff) + } + + if (startOff != 0) { + transform.splitNodeByKey(startChild.key, startOff) + } state = transform.state document = state.document startBlock = document.getClosestBlock(startKey) startChild = startBlock.getHighestChild(startKey) - const startInner = document.getNextSibling(startChild) + + const startInner = startOff == 0 + ? startChild + : document.getNextSibling(startChild) + const startInnerIndex = startBlock.nodes.indexOf(startInner) + const endInner = startKey == endKey ? startInner : startBlock.getHighestChild(endKey) const inlines = startBlock.nodes .skipUntil(n => n == startInner) @@ -877,7 +885,6 @@ export function wrapInlineAtRange(transform, range, inline) { }) } - transform.normalizeDocument() return transform } diff --git a/src/transforms/by-key.js b/src/transforms/by-key.js index 810ca0e6c..f1d1ac724 100644 --- a/src/transforms/by-key.js +++ b/src/transforms/by-key.js @@ -308,7 +308,7 @@ export function setNodeByKey(transform, key, properties) { } // If the `isVoid` property is being changed to `false` and the node is an - // inline node, remove any additional unnecessary text it. + // inline node, remove any additional unnecessary text around it. if ( properties.isVoid == false && node.isVoid == true && @@ -344,8 +344,90 @@ export function setNodeByKey(transform, key, properties) { */ export function splitNodeByKey(transform, key, offset) { - const { state } = transform - const { document } = state + let { state } = transform + let { document } = state const path = document.getPath(key) - return transform.splitNodeOperation(path, offset) + transform.splitNodeOperation(path, offset) + + // Traverse the nodes on both sides of the split, ensuring that there are no + // empty inline nodes, or empty text nodes that should be removed. + state = transform.state + document = state.document + const parent = document.getParent(key) + + // Define an iterator that will apply normalization transforms. + parent.filterDescendants((d) => { + + // We don't need to do any normalization for block nodes. + if (d.kind == 'block') { + return + } + + // If an inline void node has no text, add a space character. + if ( + d.kind == 'inline' && + d.text == '' && + d.isVoid == true + ) { + transform.insertTextByKey(d.key, 0, ' ') + } + + // If an non-void inline node has no text now, remove it. + if ( + d.kind == 'inline' && + d.text == '' && + d.isVoid == false + ) { + transform.removeNodeByKey(d.key) + } + + // Check to ensure that extra empty text nodes are preserved around inline + // void nodes. + if ( + d.kind == 'inline' && + d.isVoid == true + ) { + const previous = document.getPreviousSibling(d) + const next = document.getNextSibling(d) + + if ( + (!previous) || + (previous.kind == 'block' || previous.kind == 'inline' && previous.isVoid) + ) { + const p = document.getParent(d) + const index = p.nodes.indexOf(d) + const text = Text.create() + transform.insertNodeByKey(p, index, text) + } + + if ( + (!next) || + (next.kind == 'block' || next.kind == 'inline' && next.isVoid) + ) { + const p = document.getParent(d) + const index = p.nodes.indexOf(d) + const text = Text.create() + transform.insertNodeByKey(p, index + 1, text) + } + } + + // If an empty text node is adjacent to an non-void inline node, remove it. + if ( + d.kind == 'text' && + d.text == '' + ) { + const previous = document.getPreviousSibling(d) + const next = document.getNextSibling(d) + + if ( + (previous && previous.kind == 'inline' && previous.isVoid == false) || + (next && next.kind == 'inline' && next.isVoid == false) + ) { + transform.removeNodeByKey(d.key) + } + } + }) + + // Return the transform. + return transform } diff --git a/test/transforms/fixtures/at-current-range/split-inline/block-end/index.js b/test/transforms/fixtures/at-current-range/split-inline/block-end/index.js index 572bc022d..13594b6a4 100644 --- a/test/transforms/fixtures/at-current-range/split-inline/block-end/index.js +++ b/test/transforms/fixtures/at-current-range/split-inline/block-end/index.js @@ -22,7 +22,7 @@ export default function (state) { assert.deepEqual( next.selection.toJS(), - range.collapseToStartOf(updated).toJS() + range.collapseToEndOf(updated).toJS() ) return next diff --git a/test/transforms/fixtures/at-current-range/split-inline/block-end/output.yaml b/test/transforms/fixtures/at-current-range/split-inline/block-end/output.yaml index a236cf96f..f752cee89 100644 --- a/test/transforms/fixtures/at-current-range/split-inline/block-end/output.yaml +++ b/test/transforms/fixtures/at-current-range/split-inline/block-end/output.yaml @@ -8,8 +8,3 @@ nodes: nodes: - kind: text text: word - - kind: inline - type: link - nodes: - - kind: text - text: "" diff --git a/test/transforms/fixtures/at-current-range/split-inline/block-start/output.yaml b/test/transforms/fixtures/at-current-range/split-inline/block-start/output.yaml index e67e780fb..f752cee89 100644 --- a/test/transforms/fixtures/at-current-range/split-inline/block-start/output.yaml +++ b/test/transforms/fixtures/at-current-range/split-inline/block-start/output.yaml @@ -3,11 +3,6 @@ nodes: - kind: block type: paragraph nodes: - - kind: inline - type: link - nodes: - - kind: text - text: "" - kind: inline type: link nodes: diff --git a/test/transforms/fixtures/at-range/split-inline-at-range/block-end/output.yaml b/test/transforms/fixtures/at-range/split-inline-at-range/block-end/output.yaml index a236cf96f..f752cee89 100644 --- a/test/transforms/fixtures/at-range/split-inline-at-range/block-end/output.yaml +++ b/test/transforms/fixtures/at-range/split-inline-at-range/block-end/output.yaml @@ -8,8 +8,3 @@ nodes: nodes: - kind: text text: word - - kind: inline - type: link - nodes: - - kind: text - text: "" diff --git a/test/transforms/fixtures/at-range/split-inline-at-range/block-start/output.yaml b/test/transforms/fixtures/at-range/split-inline-at-range/block-start/output.yaml index e67e780fb..f752cee89 100644 --- a/test/transforms/fixtures/at-range/split-inline-at-range/block-start/output.yaml +++ b/test/transforms/fixtures/at-range/split-inline-at-range/block-start/output.yaml @@ -3,11 +3,6 @@ nodes: - kind: block type: paragraph nodes: - - kind: inline - type: link - nodes: - - kind: text - text: "" - kind: inline type: link nodes: