From efd884b4b1e8730625b154716d2ffcb1196ac691 Mon Sep 17 00:00:00 2001 From: Ian Storm Taylor Date: Thu, 1 Dec 2016 14:26:45 -0800 Subject: [PATCH] fix offset calculations and splitting near inlines --- src/models/node.js | 9 +- src/transforms/apply-operation.js | 101 +++++++----------- src/transforms/at-range.js | 9 +- .../split-block/after-inline/index.js | 29 +++++ .../split-block/after-inline/input.yaml | 16 +++ .../split-block/after-inline/output.yaml | 19 ++++ .../unwrap-block/middle-child-blocks/index.js | 4 +- .../after-inline-void/index.js | 17 +++ .../after-inline-void/input.yaml | 12 +++ .../after-inline-void/output.yaml | 17 +++ 10 files changed, 168 insertions(+), 65 deletions(-) create mode 100644 test/transforms/fixtures/at-current-range/split-block/after-inline/index.js create mode 100644 test/transforms/fixtures/at-current-range/split-block/after-inline/input.yaml create mode 100644 test/transforms/fixtures/at-current-range/split-block/after-inline/output.yaml create mode 100644 test/transforms/fixtures/at-range/split-block-at-range/after-inline-void/index.js create mode 100644 test/transforms/fixtures/at-range/split-block-at-range/after-inline-void/input.yaml create mode 100644 test/transforms/fixtures/at-range/split-block-at-range/after-inline-void/output.yaml diff --git a/src/models/node.js b/src/models/node.js index 60b695a99..267a6a611 100644 --- a/src/models/node.js +++ b/src/models/node.js @@ -987,7 +987,13 @@ const Node = { */ getTextAtOffset(offset) { + // PERF: Add a few shortcuts for the obvious cases. + if (offset == 0) return this.getFirstText() + if (offset == this.length) return this.getLastText() + if (offset < 0 || offset > this.length) return null + let length = 0 + return this .getTexts() .find((text, i, texts) => { @@ -998,7 +1004,7 @@ const Node = { // the furthest text node at the offset, and it will also match. if (next && next.length == 0) return false - return length >= offset + return length > offset }) }, @@ -1332,6 +1338,7 @@ const Node = { let one let two + if (node.kind != 'text') { child = node.getTextAtOffset(offset) } diff --git a/src/transforms/apply-operation.js b/src/transforms/apply-operation.js index a4e50479e..c40f8f07a 100644 --- a/src/transforms/apply-operation.js +++ b/src/transforms/apply-operation.js @@ -399,67 +399,48 @@ function setSelection(state, operation) { function splitNode(state, operation) { const { path, offset, count } = operation - const { document } = state + let { document, selection } = state - if (offset === undefined) { - return state.merge({ - document: document.splitNodeAfter(path, count) - // No need to update selection - }) - } - - else { - // Update document - let newDocument = document.splitNode(path, offset) - - // Update selection - let { selection } = state - const { anchorKey, anchorOffset, focusKey, focusOffset } = selection - - const node = document.assertPath(path) - // The text node that was split - const splittedText = node.kind == 'text' - ? node - : node.getTextAtOffset(offset) - const textOffset = node.kind == 'text' - ? offset - : offset - node.getOffset(splittedText.key) - - // Should we update the selection ? - const shouldUpdateAnchor = splittedText.key == anchorKey && textOffset <= anchorOffset - const shouldUpdateFocus = splittedText.key == focusKey && textOffset <= focusOffset - if (shouldUpdateFocus || shouldUpdateAnchor) { - // The node next to `node`, resulting from the split - const secondNode = newDocument.getNextSibling(node.key) - let secondText, newOffset - - if (shouldUpdateAnchor) { - newOffset = anchorOffset - textOffset - secondText = secondNode.kind == 'text' - ? secondNode - : secondNode.getTextAtOffset(newOffset) - selection = selection.merge({ - anchorKey: secondText.key, - anchorOffset: newOffset - }) - } - - if (shouldUpdateFocus) { - newOffset = focusOffset - textOffset - secondText = secondNode.kind == 'text' - ? secondNode - : secondNode.getTextAtOffset(newOffset) - selection = selection.merge({ - focusKey: secondText.key, - focusOffset: newOffset - }) - } - } - - state = state.merge({ - document: newDocument, - selection - }) + // If there's no offset, it's using the `count` instead. + if (offset == null) { + document = document.splitNodeAfter(path, count) + state = state.merge({ document }) return state } + + // Otherwise, split using the `offset`, but calculate a few things first. + const node = document.assertPath(path) + const text = node.kind == 'text' ? node : node.getTextAtOffset(offset) + const textOffset = node.kind == 'text' ? offset : offset - node.getOffset(text.key) + const { anchorKey, anchorOffset, focusKey, focusOffset } = selection + + document = document.splitNode(path, offset) + + // Determine whether we need to update the selection. + const splitAnchor = text.key == anchorKey && textOffset <= anchorOffset + const splitFocus = text.key == focusKey && textOffset <= focusOffset + + debugger + + // If either the anchor of focus was after the split, we need to update them. + if (splitFocus || splitAnchor) { + const nextText = document.getNextText(text.key) + + if (splitAnchor) { + selection = selection.merge({ + anchorKey: nextText.key, + anchorOffset: anchorOffset - textOffset + }) + } + + if (splitFocus) { + selection = selection.merge({ + focusKey: nextText.key, + focusOffset: focusOffset - textOffset + }) + } + } + + state = state.merge({ document, selection }) + return state } diff --git a/src/transforms/at-range.js b/src/transforms/at-range.js index 001834a10..a8079a9fb 100644 --- a/src/transforms/at-range.js +++ b/src/transforms/at-range.js @@ -842,13 +842,16 @@ export function unwrapBlockAtRange(transform, range, properties, options = {}) { transform.splitNodeByKey(block.key, offset, OPTS) state = transform.state document = state.document - const extra = document.getPreviousSibling(firstMatch.key) children.forEach((child, i) => { + if (i == 0) { + const extra = child + child = document.getNextBlock(child.key) + transform.removeNodeByKey(extra.key, OPTS) + } + transform.moveNodeByKey(child.key, parent.key, index + 1 + i, OPTS) }) - - transform.removeNodeByKey(extra.key, OPTS) } }) diff --git a/test/transforms/fixtures/at-current-range/split-block/after-inline/index.js b/test/transforms/fixtures/at-current-range/split-block/after-inline/index.js new file mode 100644 index 000000000..8a9f6606b --- /dev/null +++ b/test/transforms/fixtures/at-current-range/split-block/after-inline/index.js @@ -0,0 +1,29 @@ + +import assert from 'assert' + +export default function (state) { + const { document, selection } = state + const texts = document.getTexts() + const third = texts.get(2) + const range = selection.merge({ + anchorKey: third.key, + anchorOffset: 0, + focusKey: third.key, + focusOffset: 0 + }) + + const next = state + .transform() + .moveTo(range) + .splitBlock() + .apply() + + const updated = next.document.getTexts().get(2) + + assert.deepEqual( + next.selection.toJS(), + range.collapseToStartOf(updated).toJS() + ) + + return next +} diff --git a/test/transforms/fixtures/at-current-range/split-block/after-inline/input.yaml b/test/transforms/fixtures/at-current-range/split-block/after-inline/input.yaml new file mode 100644 index 000000000..cd77b2980 --- /dev/null +++ b/test/transforms/fixtures/at-current-range/split-block/after-inline/input.yaml @@ -0,0 +1,16 @@ + +nodes: + - kind: block + type: paragraph + nodes: + - kind: text + text: word + - kind: inline + type: link + data: + href: 'website.com' + nodes: + - kind: text + text: hyperlink + - kind: text + text: word diff --git a/test/transforms/fixtures/at-current-range/split-block/after-inline/output.yaml b/test/transforms/fixtures/at-current-range/split-block/after-inline/output.yaml new file mode 100644 index 000000000..af6ff21b8 --- /dev/null +++ b/test/transforms/fixtures/at-current-range/split-block/after-inline/output.yaml @@ -0,0 +1,19 @@ + +nodes: + - kind: block + type: paragraph + nodes: + - kind: text + text: word + - kind: inline + type: link + data: + href: 'website.com' + nodes: + - kind: text + text: hyperlink + - kind: block + type: paragraph + nodes: + - kind: text + text: word diff --git a/test/transforms/fixtures/at-current-range/unwrap-block/middle-child-blocks/index.js b/test/transforms/fixtures/at-current-range/unwrap-block/middle-child-blocks/index.js index 60034ea90..f498a20ea 100644 --- a/test/transforms/fixtures/at-current-range/unwrap-block/middle-child-blocks/index.js +++ b/test/transforms/fixtures/at-current-range/unwrap-block/middle-child-blocks/index.js @@ -19,9 +19,11 @@ export default function (state) { .unwrapBlock('quote') .apply() + const updated = next.document.getTexts().get(2) + assert.deepEqual( next.selection.toJS(), - range.toJS() + range.merge({ anchorKey: updated.key }).toJS() ) return next diff --git a/test/transforms/fixtures/at-range/split-block-at-range/after-inline-void/index.js b/test/transforms/fixtures/at-range/split-block-at-range/after-inline-void/index.js new file mode 100644 index 000000000..b5361880c --- /dev/null +++ b/test/transforms/fixtures/at-range/split-block-at-range/after-inline-void/index.js @@ -0,0 +1,17 @@ + +export default function (state) { + const { document, selection } = state + const texts = document.getTexts() + const third = texts.get(2) + const range = selection.merge({ + anchorKey: third.key, + anchorOffset: 0, + focusKey: third.key, + focusOffset: 0 + }) + + return state + .transform() + .splitBlockAtRange(range) + .apply() +} diff --git a/test/transforms/fixtures/at-range/split-block-at-range/after-inline-void/input.yaml b/test/transforms/fixtures/at-range/split-block-at-range/after-inline-void/input.yaml new file mode 100644 index 000000000..0567f8b2b --- /dev/null +++ b/test/transforms/fixtures/at-range/split-block-at-range/after-inline-void/input.yaml @@ -0,0 +1,12 @@ + +nodes: + - kind: block + type: paragraph + nodes: + - kind: text + text: word + - kind: inline + type: emoji + isVoid: true + - kind: text + text: word diff --git a/test/transforms/fixtures/at-range/split-block-at-range/after-inline-void/output.yaml b/test/transforms/fixtures/at-range/split-block-at-range/after-inline-void/output.yaml new file mode 100644 index 000000000..61824c0ef --- /dev/null +++ b/test/transforms/fixtures/at-range/split-block-at-range/after-inline-void/output.yaml @@ -0,0 +1,17 @@ + +nodes: + - kind: block + type: paragraph + nodes: + - kind: text + text: word + - kind: inline + type: emoji + isVoid: true + - kind: text + text: "" + - kind: block + type: paragraph + nodes: + - kind: text + text: word