From 9df223bce933680eabb118601ccfd95ca994ca14 Mon Sep 17 00:00:00 2001 From: Ian Storm Taylor Date: Fri, 23 Sep 2016 11:33:29 -0700 Subject: [PATCH 01/12] handle surrounding inline void nodes on insert --- src/models/block.js | 6 +++--- src/transforms/by-key.js | 20 +++++++++++++++++++- test/transforms/index.js | 1 - 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/src/models/block.js b/src/models/block.js index 5cbb914bf..0a75134c3 100644 --- a/src/models/block.js +++ b/src/models/block.js @@ -53,9 +53,9 @@ class Block extends new Record(DEFAULTS) { properties.isVoid = !!properties.isVoid properties.nodes = Block.createList(properties.nodes) - // if (properties.nodes.size == 0) { - // properties.nodes = properties.nodes.push(Text.create()) - // } + if (properties.nodes.size == 0) { + properties.nodes = properties.nodes.push(Text.create()) + } return new Block(properties).normalize() } diff --git a/src/transforms/by-key.js b/src/transforms/by-key.js index 90e346ad9..32f6e6122 100644 --- a/src/transforms/by-key.js +++ b/src/transforms/by-key.js @@ -37,7 +37,25 @@ export function insertNodeByKey(transform, key, index, node) { const { document } = state const path = document.getPath(key) const newPath = path.slice().push(index) - return transform.insertNodeOperation(path, index, node) + transform.insertNodeOperation(path, index, node) + + // If the node is an inline void, the parent is a block, and the node will be + // inserted at the block's edge, we need to add surrounding text nodes. + if (node.kind == 'inline' && node.isVoid) { + const parent = document.assertDescendant(key) + + if (index == 0) { + const text = Text.create() + transform.insertNodeByKey(key, index, text) + } + + if (index == parent.nodes.size) { + const text = Text.create() + transform.insertNodeByKey(key, index + 1, text) + } + } + + return transform } /** diff --git a/test/transforms/index.js b/test/transforms/index.js index a6dfcbc80..3a65e4092 100644 --- a/test/transforms/index.js +++ b/test/transforms/index.js @@ -18,7 +18,6 @@ describe('transforms', () => { for (const transform of transforms) { if (transform[0] == '.') continue - if (transform == 'insert-node-by-key') continue describe(`${toCamel(transform)}()`, () => { const transformDir = resolve(__dirname, './fixtures/by-key', transform) From eea034abf6b6b8600483488794934cb0ad7239cb Mon Sep 17 00:00:00 2001 From: Ian Storm Taylor Date: Fri, 23 Sep 2016 11:35:09 -0700 Subject: [PATCH 02/12] remove extra normalize method --- src/transforms/normalize.js | 109 ------------------------------------ 1 file changed, 109 deletions(-) diff --git a/src/transforms/normalize.js b/src/transforms/normalize.js index 978e1ad33..4547dbdba 100644 --- a/src/transforms/normalize.js +++ b/src/transforms/normalize.js @@ -1,113 +1,4 @@ -/** - * Only allow block nodes in documents. - * - * @type {Object} - */ - -const DOCUMENT_CHILDREN_RULE = { - match: (node) => { - return node.kind == 'document' - }, - validate: (document) => { - const { nodes } = document - const invalids = nodes.filter(n => n.kind != 'block') - return invalids.size ? invalids : null - }, - normalize: (transform, document, invalids) => { - return invalids.reduce((t, n) => t.removeNodeByKey(n.key), transform) - } -} - -/** - * Only allow block, inline and text nodes in blocks. - * - * @type {Object} - */ - -const BLOCK_CHILDREN_RULE = { - match: (node) => { - return node.kind == 'block' - }, - validate: (block) => { - const { nodes } = block - const invalids = nodes.filter(n => n.kind != 'block' && n.kind != 'inline' && n.kind != 'text') - return invalids.size ? invalids : null - }, - normalize: (transform, block, invalids) => { - return invalids.reduce((t, n) => t.removeNodeByKey(n.key), transform) - } -} - -/** - * Only allow inline and text nodes in inlines. - * - * @type {Object} - */ - -const INLINE_CHILDREN_RULE = { - match: (object) => { - return object.kind == 'inline' - }, - validate: (inline) => { - const { nodes } = inline - const invalids = nodes.filter(n => n.kind != 'inline' && n.kind != 'text') - return invalids.size ? invalids : null - }, - normalize: (transform, inline, invalids) => { - return invalids.reduce((t, n) => t.removeNodeByKey(n.key), transform) - } -} - -/** - * The default schema. - * - * @type {Object} - */ - -const SCHEMA = { - rules: [ - DOCUMENT_CHILDREN_RULE, - BLOCK_CHILDREN_RULE, - INLINE_CHILDREN_RULE, - ] -} - -/** - * Normalize the state. - * - * @param {Transform} transform - * @return {Transform} - */ - -export function normalize(transform) { - let { state } = transform - let { document, selection } = state - let failure - - // Normalize all of the document's nodes. - document.filterDescendantsDeep((node) => { - if (failure = node.validate(SCHEMA)) { - const { value, rule } = failure - rule.normalize(transform, node, value) - } - }) - - // Normalize the document itself. - if (failure = document.validate(SCHEMA)) { - const { value, rule } = failure - rule.normalize(transform, document, value) - } - - // Normalize the selection. - // TODO: turn this into schema rules. - state = transform.state - document = state.document - let nextSelection = selection.normalize(document) - if (!selection.equals(nextSelection)) transform.setSelection(selection) - return transform -} - /** * Normalize the document. * From 105fba0456ec46ac5f60405309db7a8c29365442 Mon Sep 17 00:00:00 2001 From: Ian Storm Taylor Date: Thu, 6 Oct 2016 11:11:10 -0700 Subject: [PATCH 03/12] handle regenerating keys on insert --- src/models/node.js | 28 +++++++++++++++++++ src/models/text.js | 16 +++++++++-- src/transforms/apply-operation.js | 3 +- src/transforms/at-range.js | 2 ++ src/transforms/by-key.js | 1 + .../insert-node-by-key/duplicate/index.js | 22 +++++++++++++++ .../insert-node-by-key/duplicate/input.yaml | 7 +++++ .../insert-node-by-key/duplicate/output.yaml | 12 ++++++++ 8 files changed, 86 insertions(+), 5 deletions(-) create mode 100644 test/transforms/fixtures/by-key/insert-node-by-key/duplicate/index.js create mode 100644 test/transforms/fixtures/by-key/insert-node-by-key/duplicate/input.yaml create mode 100644 test/transforms/fixtures/by-key/insert-node-by-key/duplicate/output.yaml diff --git a/src/models/node.js b/src/models/node.js index 428b70260..8a441e558 100644 --- a/src/models/node.js +++ b/src/models/node.js @@ -941,6 +941,24 @@ const Node = { */ insertNode(index, node) { + let keys = new Set([ this.key ]) + + this.findDescendant((desc) => { + keys = keys.add(desc.key) + }) + + if (keys.contains(node.key)) { + node = node.regenerateKey() + } + + if (node.kind != 'text') { + node = node.mapDescendants((desc) => { + return keys.contains(desc.key) + ? desc.regenerateKey() + : desc + }) + } + const nodes = this.nodes.splice(index, 0, node) return this.merge({ nodes }) }, @@ -1155,6 +1173,16 @@ const Node = { return node }, + /** + * Regenerate the node's key. + * + * @return {Node} node + */ + + regenerateKey() { + return this.merge({ key: uid() }) + }, + /** * Remove a `node` from the children node map. * diff --git a/src/models/text.js b/src/models/text.js index 738ecd35f..ae9592a15 100644 --- a/src/models/text.js +++ b/src/models/text.js @@ -34,7 +34,7 @@ class Text extends new Record(DEFAULTS) { * Create a new `Text` with `properties`. * * @param {Object} properties - * @return {Text} text + * @return {Text} */ static create(properties = {}) { @@ -223,7 +223,7 @@ class Text extends new Record(DEFAULTS) { * @param {Numbder} index * @param {String} text * @param {String} marks (optional) - * @return {Text} text + * @return {Text} */ insertText(index, text, marks) { @@ -238,6 +238,16 @@ class Text extends new Record(DEFAULTS) { return this.merge({ characters }) } + /** + * Regenerate the node's key. + * + * @return {Text} + */ + + regenerateKey() { + return this.merge({ key: uid() }) + } + /** * Remove a `mark` at `index` and `length`. * @@ -265,7 +275,7 @@ class Text extends new Record(DEFAULTS) { * * @param {Number} index * @param {Number} length - * @return {Text} text + * @return {Text} */ removeText(index, length) { diff --git a/src/transforms/apply-operation.js b/src/transforms/apply-operation.js index 9a9fd1f86..1dccee47e 100644 --- a/src/transforms/apply-operation.js +++ b/src/transforms/apply-operation.js @@ -90,8 +90,7 @@ function insertNode(state, operation) { let { document } = state let parent = document.assertPath(path) const isParent = document == parent - const nodes = parent.nodes.splice(index, 0, node) - parent = parent.merge({ nodes }) + parent = parent.insertNode(index, node) document = isParent ? parent : document.updateDescendant(parent) state = state.merge({ document }) return state diff --git a/src/transforms/at-range.js b/src/transforms/at-range.js index 3fec4ecfb..367a0d63b 100644 --- a/src/transforms/at-range.js +++ b/src/transforms/at-range.js @@ -729,6 +729,7 @@ export function unwrapInlineAtRange(transform, range, properties) { export function wrapBlockAtRange(transform, range, block) { block = Normalize.block(block) + block = block.merge({ nodes: block.nodes.clear() }) const { state } = transform const { document } = state @@ -799,6 +800,7 @@ export function wrapInlineAtRange(transform, range, inline) { if (range.isCollapsed) return transform inline = Normalize.inline(inline) + inline = inline.merge({ nodes: inline.nodes.clear() }) const { startKey, startOffset, endKey, endOffset } = range let { state } = transform diff --git a/src/transforms/by-key.js b/src/transforms/by-key.js index 32f6e6122..caa80189d 100644 --- a/src/transforms/by-key.js +++ b/src/transforms/by-key.js @@ -37,6 +37,7 @@ export function insertNodeByKey(transform, key, index, node) { const { document } = state const path = document.getPath(key) const newPath = path.slice().push(index) + transform.insertNodeOperation(path, index, node) // If the node is an inline void, the parent is a block, and the node will be diff --git a/test/transforms/fixtures/by-key/insert-node-by-key/duplicate/index.js b/test/transforms/fixtures/by-key/insert-node-by-key/duplicate/index.js new file mode 100644 index 000000000..1abd5e32b --- /dev/null +++ b/test/transforms/fixtures/by-key/insert-node-by-key/duplicate/index.js @@ -0,0 +1,22 @@ + +import assert from 'assert' +import { Block } from '../../../../../..' + +export default function (state) { + const { document, selection } = state + const first = document.getBlocks().first() + + debugger + const next = state + .transform() + .insertNodeByKey(document.key, 0, first) + .apply() + + const one = next.document.getBlocks().first() + const two = next.document.getBlocks().last() + + assert.equal(one.type, two.type) + assert.notEqual(one.key, two.key) + + return next +} diff --git a/test/transforms/fixtures/by-key/insert-node-by-key/duplicate/input.yaml b/test/transforms/fixtures/by-key/insert-node-by-key/duplicate/input.yaml new file mode 100644 index 000000000..3c2724d28 --- /dev/null +++ b/test/transforms/fixtures/by-key/insert-node-by-key/duplicate/input.yaml @@ -0,0 +1,7 @@ + +nodes: + - kind: block + type: paragraph + nodes: + - kind: text + text: one diff --git a/test/transforms/fixtures/by-key/insert-node-by-key/duplicate/output.yaml b/test/transforms/fixtures/by-key/insert-node-by-key/duplicate/output.yaml new file mode 100644 index 000000000..65b99a54a --- /dev/null +++ b/test/transforms/fixtures/by-key/insert-node-by-key/duplicate/output.yaml @@ -0,0 +1,12 @@ + +nodes: + - kind: block + type: paragraph + nodes: + - kind: text + text: one + - kind: block + type: paragraph + nodes: + - kind: text + text: one From e86add851fce50eef1ca82da441416e040dbd76f Mon Sep 17 00:00:00 2001 From: Ian Storm Taylor Date: Thu, 6 Oct 2016 11:29:12 -0700 Subject: [PATCH 04/12] handle removing text nodes when removing all characters --- src/transforms/apply-operation.js | 1 - src/transforms/by-key.js | 27 +++++++++++++++++-- .../adjacent-non-void-inlines/index.js | 10 +++++++ .../adjacent-non-void-inlines/input.yaml | 17 ++++++++++++ .../adjacent-non-void-inlines/output.yaml | 15 +++++++++++ .../remove-text-by-key/inline-void/index.js | 10 +++++++ .../remove-text-by-key/inline-void/input.yaml | 12 +++++++++ .../inline-void/output.yaml | 12 +++++++++ .../by-key/remove-text-by-key/inline/index.js | 10 +++++++ .../remove-text-by-key/inline/input.yaml | 10 +++++++ .../remove-text-by-key/inline/output.yaml | 10 +++++++ .../next-void-inline/index.js | 10 +++++++ .../next-void-inline/input.yaml | 17 ++++++++++++ .../next-void-inline/output.yaml | 15 +++++++++++ .../previous-void-inline/index.js | 10 +++++++ .../previous-void-inline/input.yaml | 17 ++++++++++++ .../previous-void-inline/output.yaml | 15 +++++++++++ .../by-key/remove-text-by-key/text/index.js | 10 +++++++ .../by-key/remove-text-by-key/text/input.yaml | 7 +++++ .../remove-text-by-key/text/output.yaml | 7 +++++ 20 files changed, 239 insertions(+), 3 deletions(-) create mode 100644 test/transforms/fixtures/by-key/remove-text-by-key/adjacent-non-void-inlines/index.js create mode 100644 test/transforms/fixtures/by-key/remove-text-by-key/adjacent-non-void-inlines/input.yaml create mode 100644 test/transforms/fixtures/by-key/remove-text-by-key/adjacent-non-void-inlines/output.yaml create mode 100644 test/transforms/fixtures/by-key/remove-text-by-key/inline-void/index.js create mode 100644 test/transforms/fixtures/by-key/remove-text-by-key/inline-void/input.yaml create mode 100644 test/transforms/fixtures/by-key/remove-text-by-key/inline-void/output.yaml create mode 100644 test/transforms/fixtures/by-key/remove-text-by-key/inline/index.js create mode 100644 test/transforms/fixtures/by-key/remove-text-by-key/inline/input.yaml create mode 100644 test/transforms/fixtures/by-key/remove-text-by-key/inline/output.yaml create mode 100644 test/transforms/fixtures/by-key/remove-text-by-key/next-void-inline/index.js create mode 100644 test/transforms/fixtures/by-key/remove-text-by-key/next-void-inline/input.yaml create mode 100644 test/transforms/fixtures/by-key/remove-text-by-key/next-void-inline/output.yaml create mode 100644 test/transforms/fixtures/by-key/remove-text-by-key/previous-void-inline/index.js create mode 100644 test/transforms/fixtures/by-key/remove-text-by-key/previous-void-inline/input.yaml create mode 100644 test/transforms/fixtures/by-key/remove-text-by-key/previous-void-inline/output.yaml create mode 100644 test/transforms/fixtures/by-key/remove-text-by-key/text/index.js create mode 100644 test/transforms/fixtures/by-key/remove-text-by-key/text/input.yaml create mode 100644 test/transforms/fixtures/by-key/remove-text-by-key/text/output.yaml diff --git a/src/transforms/apply-operation.js b/src/transforms/apply-operation.js index 1dccee47e..5410913a5 100644 --- a/src/transforms/apply-operation.js +++ b/src/transforms/apply-operation.js @@ -211,7 +211,6 @@ function removeText(state, operation) { let node = document.assertPath(path) node = node.removeText(offset, length) document = document.updateDescendant(node) - document = document.normalize() state = state.merge({ document }) return state } diff --git a/src/transforms/by-key.js b/src/transforms/by-key.js index caa80189d..b3397d083 100644 --- a/src/transforms/by-key.js +++ b/src/transforms/by-key.js @@ -154,9 +154,32 @@ export function removeNodeByKey(transform, key) { export function removeTextByKey(transform, key, offset, length) { const { state } = transform - const { document } = state + let { document } = state const path = document.getPath(key) - return transform.removeTextOperation(path, offset, length) + transform.removeTextOperation(path, offset, length) + + // If the text node is now empty, and not needed in the tree, remove it. + document = transform.state.document + const node = document.getDescendant(key) + const parent = document.getParent(key) + + if (node.text != '') { + return transform + } + + // If the text node is now empty, and not needed in the tree, remove it. + const previous = document.getPreviousSibling(key) + const next = document.getNextSibling(key) + + if ( + (parent.nodes.size == 1) || + (previous && previous.isVoid == false) || + (next && next.isVoid == false) + ) { + transform.removeNodeByKey(key) + } + + return transform } /** diff --git a/test/transforms/fixtures/by-key/remove-text-by-key/adjacent-non-void-inlines/index.js b/test/transforms/fixtures/by-key/remove-text-by-key/adjacent-non-void-inlines/index.js new file mode 100644 index 000000000..c5deeb445 --- /dev/null +++ b/test/transforms/fixtures/by-key/remove-text-by-key/adjacent-non-void-inlines/index.js @@ -0,0 +1,10 @@ + +export default function (state) { + const { document, selection } = state + const second = document.getTexts().get(1) + + return state + .transform() + .removeTextByKey(second.key, 0, 1) + .apply() +} diff --git a/test/transforms/fixtures/by-key/remove-text-by-key/adjacent-non-void-inlines/input.yaml b/test/transforms/fixtures/by-key/remove-text-by-key/adjacent-non-void-inlines/input.yaml new file mode 100644 index 000000000..a78b31f17 --- /dev/null +++ b/test/transforms/fixtures/by-key/remove-text-by-key/adjacent-non-void-inlines/input.yaml @@ -0,0 +1,17 @@ + +nodes: + - kind: block + type: paragraph + nodes: + - kind: inline + type: link + nodes: + - kind: text + text: one + - kind: text + text: a + - kind: inline + type: link + nodes: + - kind: text + text: two diff --git a/test/transforms/fixtures/by-key/remove-text-by-key/adjacent-non-void-inlines/output.yaml b/test/transforms/fixtures/by-key/remove-text-by-key/adjacent-non-void-inlines/output.yaml new file mode 100644 index 000000000..496aeb02a --- /dev/null +++ b/test/transforms/fixtures/by-key/remove-text-by-key/adjacent-non-void-inlines/output.yaml @@ -0,0 +1,15 @@ + +nodes: + - kind: block + type: paragraph + nodes: + - kind: inline + type: link + nodes: + - kind: text + text: one + - kind: inline + type: link + nodes: + - kind: text + text: two diff --git a/test/transforms/fixtures/by-key/remove-text-by-key/inline-void/index.js b/test/transforms/fixtures/by-key/remove-text-by-key/inline-void/index.js new file mode 100644 index 000000000..c38e1667f --- /dev/null +++ b/test/transforms/fixtures/by-key/remove-text-by-key/inline-void/index.js @@ -0,0 +1,10 @@ + +export default function (state) { + const { document, selection } = state + const last = document.getTexts().last() + + return state + .transform() + .removeTextByKey(last.key, 0, 4) + .apply() +} diff --git a/test/transforms/fixtures/by-key/remove-text-by-key/inline-void/input.yaml b/test/transforms/fixtures/by-key/remove-text-by-key/inline-void/input.yaml new file mode 100644 index 000000000..fbfc98018 --- /dev/null +++ b/test/transforms/fixtures/by-key/remove-text-by-key/inline-void/input.yaml @@ -0,0 +1,12 @@ + +nodes: + - kind: block + type: paragraph + nodes: + - kind: text + text: "" + - kind: inline + type: image + isVoid: true + - kind: text + text: word diff --git a/test/transforms/fixtures/by-key/remove-text-by-key/inline-void/output.yaml b/test/transforms/fixtures/by-key/remove-text-by-key/inline-void/output.yaml new file mode 100644 index 000000000..e19de93d6 --- /dev/null +++ b/test/transforms/fixtures/by-key/remove-text-by-key/inline-void/output.yaml @@ -0,0 +1,12 @@ + +nodes: + - kind: block + type: paragraph + nodes: + - kind: text + text: "" + - kind: inline + type: image + isVoid: true + - kind: text + text: "" diff --git a/test/transforms/fixtures/by-key/remove-text-by-key/inline/index.js b/test/transforms/fixtures/by-key/remove-text-by-key/inline/index.js new file mode 100644 index 000000000..b99ee839d --- /dev/null +++ b/test/transforms/fixtures/by-key/remove-text-by-key/inline/index.js @@ -0,0 +1,10 @@ + +export default function (state) { + const { document, selection } = state + const first = document.getTexts().first() + + return state + .transform() + .removeTextByKey(first.key, 3, 1) + .apply() +} diff --git a/test/transforms/fixtures/by-key/remove-text-by-key/inline/input.yaml b/test/transforms/fixtures/by-key/remove-text-by-key/inline/input.yaml new file mode 100644 index 000000000..f752cee89 --- /dev/null +++ b/test/transforms/fixtures/by-key/remove-text-by-key/inline/input.yaml @@ -0,0 +1,10 @@ + +nodes: + - kind: block + type: paragraph + nodes: + - kind: inline + type: link + nodes: + - kind: text + text: word diff --git a/test/transforms/fixtures/by-key/remove-text-by-key/inline/output.yaml b/test/transforms/fixtures/by-key/remove-text-by-key/inline/output.yaml new file mode 100644 index 000000000..e0e015d73 --- /dev/null +++ b/test/transforms/fixtures/by-key/remove-text-by-key/inline/output.yaml @@ -0,0 +1,10 @@ + +nodes: + - kind: block + type: paragraph + nodes: + - kind: inline + type: link + nodes: + - kind: text + text: wor diff --git a/test/transforms/fixtures/by-key/remove-text-by-key/next-void-inline/index.js b/test/transforms/fixtures/by-key/remove-text-by-key/next-void-inline/index.js new file mode 100644 index 000000000..c5deeb445 --- /dev/null +++ b/test/transforms/fixtures/by-key/remove-text-by-key/next-void-inline/index.js @@ -0,0 +1,10 @@ + +export default function (state) { + const { document, selection } = state + const second = document.getTexts().get(1) + + return state + .transform() + .removeTextByKey(second.key, 0, 1) + .apply() +} diff --git a/test/transforms/fixtures/by-key/remove-text-by-key/next-void-inline/input.yaml b/test/transforms/fixtures/by-key/remove-text-by-key/next-void-inline/input.yaml new file mode 100644 index 000000000..739e5c59f --- /dev/null +++ b/test/transforms/fixtures/by-key/remove-text-by-key/next-void-inline/input.yaml @@ -0,0 +1,17 @@ + +nodes: + - kind: block + type: paragraph + nodes: + - kind: inline + type: link + nodes: + - kind: text + text: one + - kind: text + text: a + - kind: inline + type: image + isVoid: true + - kind: text + text: "" diff --git a/test/transforms/fixtures/by-key/remove-text-by-key/next-void-inline/output.yaml b/test/transforms/fixtures/by-key/remove-text-by-key/next-void-inline/output.yaml new file mode 100644 index 000000000..ebb1fd4d6 --- /dev/null +++ b/test/transforms/fixtures/by-key/remove-text-by-key/next-void-inline/output.yaml @@ -0,0 +1,15 @@ + +nodes: + - kind: block + type: paragraph + nodes: + - kind: inline + type: link + nodes: + - kind: text + text: one + - kind: inline + type: image + isVoid: true + - kind: text + text: "" diff --git a/test/transforms/fixtures/by-key/remove-text-by-key/previous-void-inline/index.js b/test/transforms/fixtures/by-key/remove-text-by-key/previous-void-inline/index.js new file mode 100644 index 000000000..af62d3422 --- /dev/null +++ b/test/transforms/fixtures/by-key/remove-text-by-key/previous-void-inline/index.js @@ -0,0 +1,10 @@ + +export default function (state) { + const { document, selection } = state + const third = document.getTexts().get(2) + + return state + .transform() + .removeTextByKey(third.key, 0, 1) + .apply() +} diff --git a/test/transforms/fixtures/by-key/remove-text-by-key/previous-void-inline/input.yaml b/test/transforms/fixtures/by-key/remove-text-by-key/previous-void-inline/input.yaml new file mode 100644 index 000000000..d468b8878 --- /dev/null +++ b/test/transforms/fixtures/by-key/remove-text-by-key/previous-void-inline/input.yaml @@ -0,0 +1,17 @@ + +nodes: + - kind: block + type: paragraph + nodes: + - kind: text + text: "" + - kind: inline + type: image + isVoid: true + - kind: text + text: a + - kind: inline + type: link + nodes: + - kind: text + text: two diff --git a/test/transforms/fixtures/by-key/remove-text-by-key/previous-void-inline/output.yaml b/test/transforms/fixtures/by-key/remove-text-by-key/previous-void-inline/output.yaml new file mode 100644 index 000000000..2a4137d88 --- /dev/null +++ b/test/transforms/fixtures/by-key/remove-text-by-key/previous-void-inline/output.yaml @@ -0,0 +1,15 @@ + +nodes: + - kind: block + type: paragraph + nodes: + - kind: text + text: "" + - kind: inline + type: image + isVoid: true + - kind: inline + type: link + nodes: + - kind: text + text: two diff --git a/test/transforms/fixtures/by-key/remove-text-by-key/text/index.js b/test/transforms/fixtures/by-key/remove-text-by-key/text/index.js new file mode 100644 index 000000000..b99ee839d --- /dev/null +++ b/test/transforms/fixtures/by-key/remove-text-by-key/text/index.js @@ -0,0 +1,10 @@ + +export default function (state) { + const { document, selection } = state + const first = document.getTexts().first() + + return state + .transform() + .removeTextByKey(first.key, 3, 1) + .apply() +} diff --git a/test/transforms/fixtures/by-key/remove-text-by-key/text/input.yaml b/test/transforms/fixtures/by-key/remove-text-by-key/text/input.yaml new file mode 100644 index 000000000..27f668fe2 --- /dev/null +++ b/test/transforms/fixtures/by-key/remove-text-by-key/text/input.yaml @@ -0,0 +1,7 @@ + +nodes: + - kind: block + type: paragraph + nodes: + - kind: text + text: word diff --git a/test/transforms/fixtures/by-key/remove-text-by-key/text/output.yaml b/test/transforms/fixtures/by-key/remove-text-by-key/text/output.yaml new file mode 100644 index 000000000..2ee7c9598 --- /dev/null +++ b/test/transforms/fixtures/by-key/remove-text-by-key/text/output.yaml @@ -0,0 +1,7 @@ + +nodes: + - kind: block + type: paragraph + nodes: + - kind: text + text: wor From db3f00db6dcf732a381020f365984c0db501ecc5 Mon Sep 17 00:00:00 2001 From: Ian Storm Taylor Date: Thu, 6 Oct 2016 18:19:31 -0700 Subject: [PATCH 05/12] handle joining text nodes when removing inlines between them --- src/transforms/by-key.js | 66 ++++++++++++++----- src/transforms/index.js | 4 ++ .../remove-node-by-key/middle-inline/index.js | 10 +++ .../middle-inline/input.yaml | 14 ++++ .../middle-inline/output.yaml | 7 ++ .../inline-last-character/index.js | 10 +++ .../inline-last-character/input.yaml | 10 +++ .../inline-last-character/output.yaml | 7 ++ 8 files changed, 112 insertions(+), 16 deletions(-) create mode 100644 test/transforms/fixtures/by-key/remove-node-by-key/middle-inline/index.js create mode 100644 test/transforms/fixtures/by-key/remove-node-by-key/middle-inline/input.yaml create mode 100644 test/transforms/fixtures/by-key/remove-node-by-key/middle-inline/output.yaml create mode 100644 test/transforms/fixtures/by-key/remove-text-by-key/inline-last-character/index.js create mode 100644 test/transforms/fixtures/by-key/remove-text-by-key/inline-last-character/input.yaml create mode 100644 test/transforms/fixtures/by-key/remove-text-by-key/inline-last-character/output.yaml diff --git a/src/transforms/by-key.js b/src/transforms/by-key.js index b3397d083..b041b04ca 100644 --- a/src/transforms/by-key.js +++ b/src/transforms/by-key.js @@ -77,6 +77,23 @@ export function insertTextByKey(transform, key, offset, text, marks) { return transform.insertTextOperation(path, offset, text, marks) } +/** + * Join a node by `key` with a node `withKey`. + * + * @param {Transform} transform + * @param {String} key + * @param {String} withKey + * @return {Transform} + */ + +export function joinNodeByKey(transform, key, withKey) { + const { state } = transform + const { document } = state + const path = document.getPath(key) + const withPath = document.getPath(withKey) + return transform.joinNodeOperation(path, withPath) +} + /** * Move a node by `key` to a new parent by `key` and `index`. * @@ -124,21 +141,29 @@ export function removeMarkByKey(transform, key, offset, length, mark) { export function removeNodeByKey(transform, key) { const { state } = transform - const { document } = state + let { document } = state const node = document.assertDescendant(key) - const parent = document.getParent(key) - const index = parent.nodes.indexOf(node) const path = document.getPath(key) + const parent = document.getParent(key) + const previous = document.getPreviousSibling(key) + const next = document.getNextSibling(key) transform.removeNodeOperation(path) - // If the node isn't a text node, or it isn't the last node in its parent, - // then we have nothing else to do. - if (node.kind != 'text' || parent.nodes.size > 1) return transform + // If there are no more remaining nodes in the parent, re-add an empty text + // node so that we guarantee to always have text nodes as the tree's leaves. + if (parent.nodes.size == 1) { + const text = Text.create() + transform.insertNodeByKey(parent.key, 0, text) + } + + // If the previous and next siblings are both text nodes, join them. + if ( + (previous && previous.kind == 'text') && + (next && next.kind == 'text') + ) { + transform.joinNodeByKey(next.key, previous.key) + } - // Otherwise, re-add an empty text node into the parent so that we guarantee - // to always have text nodes as the leaves of the node tree. - const text = Text.create() - transform.insertNodeByKey(parent.key, index, text) return transform } @@ -158,21 +183,30 @@ export function removeTextByKey(transform, key, offset, length) { const path = document.getPath(key) transform.removeTextOperation(path, offset, length) - // If the text node is now empty, and not needed in the tree, remove it. + // If the text node is now empty, we might need to remove more nodes. document = transform.state.document const node = document.getDescendant(key) const parent = document.getParent(key) + const previous = document.getPreviousSibling(key) + const next = document.getNextSibling(key) + // If the text node isn't empty, don't do anything more. if (node.text != '') { return transform } - // If the text node is now empty, and not needed in the tree, remove it. - const previous = document.getPreviousSibling(key) - const next = document.getNextSibling(key) - + // If the empty text node is the only node remaining in a non-void inline, + // remove the inline completely. if ( - (parent.nodes.size == 1) || + parent.kind == 'inline' && + parent.isVoid == false && + parent.nodes.size == 1 + ) { + transform.removeNodeByKey(parent.key) + } + + // Otherwise, if the text node is not needed in the tree any more, remove it. + else if ( (previous && previous.isVoid == false) || (next && next.isVoid == false) ) { diff --git a/src/transforms/index.js b/src/transforms/index.js index 8d750bd1e..ddddad45e 100644 --- a/src/transforms/index.js +++ b/src/transforms/index.js @@ -15,6 +15,7 @@ import { addMarkOperation, insertNodeOperation, insertTextOperation, + joinNodeOperation, moveNodeOperation, removeMarkOperation, removeNodeOperation, @@ -85,6 +86,7 @@ import { addMarkByKey, insertNodeByKey, insertTextByKey, + joinNodeByKey, moveNodeByKey, removeMarkByKey, removeNodeByKey, @@ -167,6 +169,7 @@ export default { addMarkOperation, insertNodeOperation, insertTextOperation, + joinNodeOperation, moveNodeOperation, removeMarkOperation, removeNodeOperation, @@ -231,6 +234,7 @@ export default { addMarkByKey, insertNodeByKey, insertTextByKey, + joinNodeByKey, moveNodeByKey, removeMarkByKey, removeNodeByKey, diff --git a/test/transforms/fixtures/by-key/remove-node-by-key/middle-inline/index.js b/test/transforms/fixtures/by-key/remove-node-by-key/middle-inline/index.js new file mode 100644 index 000000000..1a88ca23a --- /dev/null +++ b/test/transforms/fixtures/by-key/remove-node-by-key/middle-inline/index.js @@ -0,0 +1,10 @@ + +export default function (state) { + const { document, selection } = state + const first = document.getInlines().first() + + return state + .transform() + .removeNodeByKey(first.key) + .apply() +} diff --git a/test/transforms/fixtures/by-key/remove-node-by-key/middle-inline/input.yaml b/test/transforms/fixtures/by-key/remove-node-by-key/middle-inline/input.yaml new file mode 100644 index 000000000..0fa3c627c --- /dev/null +++ b/test/transforms/fixtures/by-key/remove-node-by-key/middle-inline/input.yaml @@ -0,0 +1,14 @@ + +nodes: + - kind: block + type: paragraph + nodes: + - kind: text + text: one + - kind: inline + type: link + nodes: + - kind: text + text: two + - kind: text + text: three diff --git a/test/transforms/fixtures/by-key/remove-node-by-key/middle-inline/output.yaml b/test/transforms/fixtures/by-key/remove-node-by-key/middle-inline/output.yaml new file mode 100644 index 000000000..ab9dcade9 --- /dev/null +++ b/test/transforms/fixtures/by-key/remove-node-by-key/middle-inline/output.yaml @@ -0,0 +1,7 @@ + +nodes: + - kind: block + type: paragraph + nodes: + - kind: text + text: onethree diff --git a/test/transforms/fixtures/by-key/remove-text-by-key/inline-last-character/index.js b/test/transforms/fixtures/by-key/remove-text-by-key/inline-last-character/index.js new file mode 100644 index 000000000..955d83d1b --- /dev/null +++ b/test/transforms/fixtures/by-key/remove-text-by-key/inline-last-character/index.js @@ -0,0 +1,10 @@ + +export default function (state) { + const { document, selection } = state + const first = document.getTexts().first() + + return state + .transform() + .removeTextByKey(first.key, 0, 1) + .apply() +} diff --git a/test/transforms/fixtures/by-key/remove-text-by-key/inline-last-character/input.yaml b/test/transforms/fixtures/by-key/remove-text-by-key/inline-last-character/input.yaml new file mode 100644 index 000000000..8001e3ad1 --- /dev/null +++ b/test/transforms/fixtures/by-key/remove-text-by-key/inline-last-character/input.yaml @@ -0,0 +1,10 @@ + +nodes: + - kind: block + type: paragraph + nodes: + - kind: inline + type: link + nodes: + - kind: text + text: a diff --git a/test/transforms/fixtures/by-key/remove-text-by-key/inline-last-character/output.yaml b/test/transforms/fixtures/by-key/remove-text-by-key/inline-last-character/output.yaml new file mode 100644 index 000000000..f6de4d08a --- /dev/null +++ b/test/transforms/fixtures/by-key/remove-text-by-key/inline-last-character/output.yaml @@ -0,0 +1,7 @@ + +nodes: + - kind: block + type: paragraph + nodes: + - kind: text + text: "" From 5d27344fb715a70570b2a94aa817201a4178505e Mon Sep 17 00:00:00 2001 From: Ian Storm Taylor Date: Thu, 6 Oct 2016 18:39:54 -0700 Subject: [PATCH 06/12] handle text cases for setNodeByKey --- src/transforms/apply-operation.js | 1 - src/transforms/by-key.js | 52 ++++++++++++++++++++++++++++++- 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/src/transforms/apply-operation.js b/src/transforms/apply-operation.js index 5410913a5..5cc8dd70d 100644 --- a/src/transforms/apply-operation.js +++ b/src/transforms/apply-operation.js @@ -247,7 +247,6 @@ function setNode(state, operation) { let node = document.assertPath(path) node = node.merge(properties) document = document.updateDescendant(node) - document = document.normalize() state = state.merge({ document }) return state } diff --git a/src/transforms/by-key.js b/src/transforms/by-key.js index b041b04ca..1be0b1c7b 100644 --- a/src/transforms/by-key.js +++ b/src/transforms/by-key.js @@ -250,8 +250,58 @@ export function setNodeByKey(transform, key, properties) { const { state } = transform const { document } = state const node = document.assertDescendant(key) + const parent = document.getParent(key) + const index = parent.nodes.indexOf(node) const path = document.getPath(key) - return transform.setNodeOperation(path, properties) + const previous = document.getPreviousSibling(key) + const next = document.getNextSibling(key) + transform.setNodeOperation(path, properties) + + // If the `isVoid` property is being changed to true, remove all of the node's + // children, and add additional text nodes around it if necessary. + if (properties.isVoid == true && node.isVoid == false) { + node.nodes.forEach((child) => { + transform.removeNodeByKey(child.key) + }) + + if (node.kind == 'inline') { + if (!next) { + const text = Text.create() + transform.insertNodeByKey(parent.key, index + 1, text) + } + + if (!previous) { + const text = Text.create() + transform.insertNodeByKey(parent.key, index, text) + } + } + } + + // If the `isVoid` property is being changed to `false` and the node is an + // inline node, remove any additional unnecessary text it. + if ( + properties.isVoid == false && + node.isVoid == true && + node.kind == 'inline' + ) { + if ( + previous && + previous.kind == 'text' && + previous.text == '' + ) { + transform.removeNodeByKey(previous.key) + } + + if ( + next && + next.kind == 'text' && + next.text == '' + ) { + transform.removeNodeByKey(next.key) + } + } + + return transform } /** From 70ee777f377c97b6fd08d1d63729a85de93a4d00 Mon Sep 17 00:00:00 2001 From: Ian Storm Taylor Date: Fri, 7 Oct 2016 16:02:18 -0700 Subject: [PATCH 07/12] handle joining and inserting text nodes for moveNodeByKey --- src/transforms/at-range.js | 4 --- src/transforms/by-key.js | 33 ++++++++++++++++++- .../block/index.js | 2 +- .../block/input.yaml | 4 +-- .../by-key/move-node-by-key/block/output.yaml | 12 +++++++ .../inline}/index.js | 3 +- .../inline/input.yaml | 4 +-- .../inline/output.yaml} | 9 ++--- .../by-key/move-node-by-key/text/index.js | 11 +++++++ .../by-key/move-node-by-key/text/input.yaml | 12 +++++++ .../text/output.yaml | 5 +++ .../remove-node-by-key/block/output.yaml | 7 ---- .../by-key/remove-node-by-key/inline/index.js | 10 ------ .../remove-node-by-key/inline/output.yaml | 10 ------ .../middle-inline/output.yaml | 7 ---- .../by-key/remove-node-by-key/text/index.js | 10 ------ .../by-key/remove-node-by-key/text/input.yaml | 7 ---- 17 files changed, 84 insertions(+), 66 deletions(-) rename test/transforms/fixtures/by-key/{remove-node-by-key => move-node-by-key}/block/index.js (78%) rename test/transforms/fixtures/by-key/{remove-node-by-key => move-node-by-key}/block/input.yaml (77%) create mode 100644 test/transforms/fixtures/by-key/move-node-by-key/block/output.yaml rename test/transforms/fixtures/by-key/{remove-node-by-key/middle-inline => move-node-by-key/inline}/index.js (65%) rename test/transforms/fixtures/by-key/{remove-node-by-key => move-node-by-key}/inline/input.yaml (81%) rename test/transforms/fixtures/by-key/{remove-node-by-key/middle-inline/input.yaml => move-node-by-key/inline/output.yaml} (60%) create mode 100644 test/transforms/fixtures/by-key/move-node-by-key/text/index.js create mode 100644 test/transforms/fixtures/by-key/move-node-by-key/text/input.yaml rename test/transforms/fixtures/by-key/{remove-node-by-key => move-node-by-key}/text/output.yaml (51%) delete mode 100644 test/transforms/fixtures/by-key/remove-node-by-key/block/output.yaml delete mode 100644 test/transforms/fixtures/by-key/remove-node-by-key/inline/index.js delete mode 100644 test/transforms/fixtures/by-key/remove-node-by-key/inline/output.yaml delete mode 100644 test/transforms/fixtures/by-key/remove-node-by-key/middle-inline/output.yaml delete mode 100644 test/transforms/fixtures/by-key/remove-node-by-key/text/index.js delete mode 100644 test/transforms/fixtures/by-key/remove-node-by-key/text/input.yaml diff --git a/src/transforms/at-range.js b/src/transforms/at-range.js index 367a0d63b..ce1612ce2 100644 --- a/src/transforms/at-range.js +++ b/src/transforms/at-range.js @@ -94,7 +94,6 @@ export function deleteAtRange(transform, range) { const lonely = document.getFurthest(endBlock, p => p.nodes.size == 1) || endBlock transform.removeNodeByKey(lonely.key) - transform.normalizeDocument() return transform } @@ -523,7 +522,6 @@ export function splitBlockAtRange(transform, range, height = 1) { } transform.splitNodeByKey(node.key, offset) - transform.normalizeDocument() return transform } @@ -672,7 +670,6 @@ export function unwrapBlockAtRange(transform, range, properties) { } }) - transform.normalizeDocument() return transform } @@ -714,7 +711,6 @@ export function unwrapInlineAtRange(transform, range, properties) { }) }) - transform.normalizeDocument() return transform } diff --git a/src/transforms/by-key.js b/src/transforms/by-key.js index 1be0b1c7b..b38146ea3 100644 --- a/src/transforms/by-key.js +++ b/src/transforms/by-key.js @@ -107,9 +107,39 @@ export function joinNodeByKey(transform, key, withKey) { export function moveNodeByKey(transform, key, newKey, newIndex) { const { state } = transform const { document } = state + const node = document.assertDescendant(key) + const prevParent = document.getParent(key) const path = document.getPath(key) const newPath = document.getPath(newKey) - return transform.moveNodeOperation(path, newPath, newIndex) + const parent = document.key == newKey ? document : document.assertDescendant(newKey) + const previous = newIndex == 0 ? null : parent.nodes.get(newIndex - 1) + const next = parent.nodes.get(newIndex) + transform.moveNodeOperation(path, newPath, newIndex) + + // If the node to move is a text node, and it will be moved adjacent to + // another text node, join them together. + if (node.kind == 'text') { + if (next && next.kind == 'text') { + transform.joinNodeByKey(next.key, node.key) + } + + if (previous && previous.kind == 'text') { + transform.joinNodeByKey(node.key, previous.key) + } + } + + // If the node to be moved is the last child of its parent, then create a new + // empty text node in its place. + if (prevParent.nodes.size == 1) { + if (prevParent.kind == 'block') { + const text = Text.create() + transform.insertNodeByKey(prevParent.key, 0, text) + } else { + transform.removeNodeByKey(prevParent.key) + } + } + + return transform } /** @@ -161,6 +191,7 @@ export function removeNodeByKey(transform, key) { (previous && previous.kind == 'text') && (next && next.kind == 'text') ) { + debugger transform.joinNodeByKey(next.key, previous.key) } diff --git a/test/transforms/fixtures/by-key/remove-node-by-key/block/index.js b/test/transforms/fixtures/by-key/move-node-by-key/block/index.js similarity index 78% rename from test/transforms/fixtures/by-key/remove-node-by-key/block/index.js rename to test/transforms/fixtures/by-key/move-node-by-key/block/index.js index 92ee63209..59bfe59e5 100644 --- a/test/transforms/fixtures/by-key/remove-node-by-key/block/index.js +++ b/test/transforms/fixtures/by-key/move-node-by-key/block/index.js @@ -5,6 +5,6 @@ export default function (state) { return state .transform() - .removeNodeByKey(first.key) + .moveNodeByKey(first.key, document.key, 1) .apply() } diff --git a/test/transforms/fixtures/by-key/remove-node-by-key/block/input.yaml b/test/transforms/fixtures/by-key/move-node-by-key/block/input.yaml similarity index 77% rename from test/transforms/fixtures/by-key/remove-node-by-key/block/input.yaml rename to test/transforms/fixtures/by-key/move-node-by-key/block/input.yaml index 881deb1d9..ee05966e8 100644 --- a/test/transforms/fixtures/by-key/remove-node-by-key/block/input.yaml +++ b/test/transforms/fixtures/by-key/move-node-by-key/block/input.yaml @@ -4,9 +4,9 @@ nodes: type: paragraph nodes: - kind: text - text: word + text: one - kind: block type: paragraph nodes: - kind: text - text: another + text: two diff --git a/test/transforms/fixtures/by-key/move-node-by-key/block/output.yaml b/test/transforms/fixtures/by-key/move-node-by-key/block/output.yaml new file mode 100644 index 000000000..0be0b6c01 --- /dev/null +++ b/test/transforms/fixtures/by-key/move-node-by-key/block/output.yaml @@ -0,0 +1,12 @@ + +nodes: + - kind: block + type: paragraph + nodes: + - kind: text + text: two + - kind: block + type: paragraph + nodes: + - kind: text + text: one diff --git a/test/transforms/fixtures/by-key/remove-node-by-key/middle-inline/index.js b/test/transforms/fixtures/by-key/move-node-by-key/inline/index.js similarity index 65% rename from test/transforms/fixtures/by-key/remove-node-by-key/middle-inline/index.js rename to test/transforms/fixtures/by-key/move-node-by-key/inline/index.js index 1a88ca23a..5227152ee 100644 --- a/test/transforms/fixtures/by-key/remove-node-by-key/middle-inline/index.js +++ b/test/transforms/fixtures/by-key/move-node-by-key/inline/index.js @@ -1,10 +1,11 @@ export default function (state) { const { document, selection } = state + const block = document.getBlocks().first() const first = document.getInlines().first() return state .transform() - .removeNodeByKey(first.key) + .moveNodeByKey(first.key, block.key, 1) .apply() } diff --git a/test/transforms/fixtures/by-key/remove-node-by-key/inline/input.yaml b/test/transforms/fixtures/by-key/move-node-by-key/inline/input.yaml similarity index 81% rename from test/transforms/fixtures/by-key/remove-node-by-key/inline/input.yaml rename to test/transforms/fixtures/by-key/move-node-by-key/inline/input.yaml index c17ccbcf2..496aeb02a 100644 --- a/test/transforms/fixtures/by-key/remove-node-by-key/inline/input.yaml +++ b/test/transforms/fixtures/by-key/move-node-by-key/inline/input.yaml @@ -7,9 +7,9 @@ nodes: type: link nodes: - kind: text - text: word + text: one - kind: inline type: link nodes: - kind: text - text: another + text: two diff --git a/test/transforms/fixtures/by-key/remove-node-by-key/middle-inline/input.yaml b/test/transforms/fixtures/by-key/move-node-by-key/inline/output.yaml similarity index 60% rename from test/transforms/fixtures/by-key/remove-node-by-key/middle-inline/input.yaml rename to test/transforms/fixtures/by-key/move-node-by-key/inline/output.yaml index 0fa3c627c..6f46511d3 100644 --- a/test/transforms/fixtures/by-key/remove-node-by-key/middle-inline/input.yaml +++ b/test/transforms/fixtures/by-key/move-node-by-key/inline/output.yaml @@ -3,12 +3,13 @@ nodes: - kind: block type: paragraph nodes: - - kind: text - text: one - kind: inline type: link nodes: - kind: text text: two - - kind: text - text: three + - kind: inline + type: link + nodes: + - kind: text + text: one diff --git a/test/transforms/fixtures/by-key/move-node-by-key/text/index.js b/test/transforms/fixtures/by-key/move-node-by-key/text/index.js new file mode 100644 index 000000000..c08e67853 --- /dev/null +++ b/test/transforms/fixtures/by-key/move-node-by-key/text/index.js @@ -0,0 +1,11 @@ + +export default function (state) { + const { document, selection } = state + const text = document.getTexts().last() + const block = document.getBlocks().first() + + return state + .transform() + .moveNodeByKey(text.key, block.key, 1) + .apply() +} diff --git a/test/transforms/fixtures/by-key/move-node-by-key/text/input.yaml b/test/transforms/fixtures/by-key/move-node-by-key/text/input.yaml new file mode 100644 index 000000000..ee05966e8 --- /dev/null +++ b/test/transforms/fixtures/by-key/move-node-by-key/text/input.yaml @@ -0,0 +1,12 @@ + +nodes: + - kind: block + type: paragraph + nodes: + - kind: text + text: one + - kind: block + type: paragraph + nodes: + - kind: text + text: two diff --git a/test/transforms/fixtures/by-key/remove-node-by-key/text/output.yaml b/test/transforms/fixtures/by-key/move-node-by-key/text/output.yaml similarity index 51% rename from test/transforms/fixtures/by-key/remove-node-by-key/text/output.yaml rename to test/transforms/fixtures/by-key/move-node-by-key/text/output.yaml index f6de4d08a..a4cad435b 100644 --- a/test/transforms/fixtures/by-key/remove-node-by-key/text/output.yaml +++ b/test/transforms/fixtures/by-key/move-node-by-key/text/output.yaml @@ -1,5 +1,10 @@ nodes: + - kind: block + type: paragraph + nodes: + - kind: text + text: onetwo - kind: block type: paragraph nodes: diff --git a/test/transforms/fixtures/by-key/remove-node-by-key/block/output.yaml b/test/transforms/fixtures/by-key/remove-node-by-key/block/output.yaml deleted file mode 100644 index 6c07a315f..000000000 --- a/test/transforms/fixtures/by-key/remove-node-by-key/block/output.yaml +++ /dev/null @@ -1,7 +0,0 @@ - -nodes: - - kind: block - type: paragraph - nodes: - - kind: text - text: another diff --git a/test/transforms/fixtures/by-key/remove-node-by-key/inline/index.js b/test/transforms/fixtures/by-key/remove-node-by-key/inline/index.js deleted file mode 100644 index 1a88ca23a..000000000 --- a/test/transforms/fixtures/by-key/remove-node-by-key/inline/index.js +++ /dev/null @@ -1,10 +0,0 @@ - -export default function (state) { - const { document, selection } = state - const first = document.getInlines().first() - - return state - .transform() - .removeNodeByKey(first.key) - .apply() -} diff --git a/test/transforms/fixtures/by-key/remove-node-by-key/inline/output.yaml b/test/transforms/fixtures/by-key/remove-node-by-key/inline/output.yaml deleted file mode 100644 index df7991b4d..000000000 --- a/test/transforms/fixtures/by-key/remove-node-by-key/inline/output.yaml +++ /dev/null @@ -1,10 +0,0 @@ - -nodes: - - kind: block - type: paragraph - nodes: - - kind: inline - type: link - nodes: - - kind: text - text: another diff --git a/test/transforms/fixtures/by-key/remove-node-by-key/middle-inline/output.yaml b/test/transforms/fixtures/by-key/remove-node-by-key/middle-inline/output.yaml deleted file mode 100644 index ab9dcade9..000000000 --- a/test/transforms/fixtures/by-key/remove-node-by-key/middle-inline/output.yaml +++ /dev/null @@ -1,7 +0,0 @@ - -nodes: - - kind: block - type: paragraph - nodes: - - kind: text - text: onethree diff --git a/test/transforms/fixtures/by-key/remove-node-by-key/text/index.js b/test/transforms/fixtures/by-key/remove-node-by-key/text/index.js deleted file mode 100644 index 809d05121..000000000 --- a/test/transforms/fixtures/by-key/remove-node-by-key/text/index.js +++ /dev/null @@ -1,10 +0,0 @@ - -export default function (state) { - const { document, selection } = state - const first = document.getTexts().first() - - return state - .transform() - .removeNodeByKey(first.key) - .apply() -} diff --git a/test/transforms/fixtures/by-key/remove-node-by-key/text/input.yaml b/test/transforms/fixtures/by-key/remove-node-by-key/text/input.yaml deleted file mode 100644 index 27f668fe2..000000000 --- a/test/transforms/fixtures/by-key/remove-node-by-key/text/input.yaml +++ /dev/null @@ -1,7 +0,0 @@ - -nodes: - - kind: block - type: paragraph - nodes: - - kind: text - text: word From dfe52446e7e7c9325e3972746b270c15e6a574fd Mon Sep 17 00:00:00 2001 From: Ian Storm Taylor Date: Fri, 7 Oct 2016 16:02:39 -0700 Subject: [PATCH 08/12] remove debugger call --- src/transforms/by-key.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/transforms/by-key.js b/src/transforms/by-key.js index b38146ea3..810ca0e6c 100644 --- a/src/transforms/by-key.js +++ b/src/transforms/by-key.js @@ -191,7 +191,6 @@ export function removeNodeByKey(transform, key) { (previous && previous.kind == 'text') && (next && next.kind == 'text') ) { - debugger transform.joinNodeByKey(next.key, previous.key) } From fa5d59c063dd99a969e465d2730bcfad6591db3b Mon Sep 17 00:00:00 2001 From: Ian Storm Taylor Date: Fri, 14 Oct 2016 16:40:45 -0700 Subject: [PATCH 09/12] 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: From 8cf0a05926ea756be4ba8d7fce4894a8aaa47e74 Mon Sep 17 00:00:00 2001 From: Ian Storm Taylor Date: Fri, 14 Oct 2016 16:46:12 -0700 Subject: [PATCH 10/12] handle splitting for fragments --- src/transforms/at-range.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/transforms/at-range.js b/src/transforms/at-range.js index f4c3818b9..fc2b1b289 100644 --- a/src/transforms/at-range.js +++ b/src/transforms/at-range.js @@ -318,7 +318,9 @@ export function insertFragmentAtRange(transform, range, fragment) { }) } - transform.splitNodeByKey(startChild.key, offset) + if (startOffset != 0) { + transform.splitNodeByKey(startChild.key, offset) + } state = transform.state document = state.document @@ -345,7 +347,8 @@ export function insertFragmentAtRange(transform, range, fragment) { const inlineIndex = startBlock.nodes.indexOf(inlineChild) firstBlock.nodes.forEach((inline, i) => { - const newIndex = inlineIndex + i + 1 + const offset = startOffset == 0 ? 0 : 1 + const newIndex = inlineIndex + i + offset transform.insertNodeByKey(startBlock.key, newIndex, inline) }) } From 1443968b834428db853f39d76ebb3b0cdf3b8e7d Mon Sep 17 00:00:00 2001 From: Ian Storm Taylor Date: Fri, 14 Oct 2016 17:22:45 -0700 Subject: [PATCH 11/12] handle joining text nodes with insertFragment --- src/transforms/at-range.js | 5 ++--- src/transforms/by-key.js | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/transforms/at-range.js b/src/transforms/at-range.js index fc2b1b289..deccb472a 100644 --- a/src/transforms/at-range.js +++ b/src/transforms/at-range.js @@ -347,13 +347,12 @@ export function insertFragmentAtRange(transform, range, fragment) { const inlineIndex = startBlock.nodes.indexOf(inlineChild) firstBlock.nodes.forEach((inline, i) => { - const offset = startOffset == 0 ? 0 : 1 - const newIndex = inlineIndex + i + offset + const o = startOffset == 0 ? 0 : 1 + const newIndex = inlineIndex + i + o transform.insertNodeByKey(startBlock.key, newIndex, inline) }) } - transform.normalizeDocument() return transform } diff --git a/src/transforms/by-key.js b/src/transforms/by-key.js index f1d1ac724..5c2964872 100644 --- a/src/transforms/by-key.js +++ b/src/transforms/by-key.js @@ -56,6 +56,22 @@ export function insertNodeByKey(transform, key, index, node) { } } + // If the node is a text node, and it is insert next to a text node, it should + // be joined with it. + if (node.kind == 'text') { + const parent = document.assertDescendant(key) + const previous = index == 0 ? null : parent.nodes.get(index - 1) + const next = parent.nodes.get(index) + + if (next && next.kind == 'text') { + transform.joinNodeByKey(next.key, node.key) + } + + if (previous && previous.kind == 'text') { + transform.joinNodeByKey(node.key, previous.key) + } + } + return transform } From 8213bb089ff9ea5914ef73e6a698f611aed18554 Mon Sep 17 00:00:00 2001 From: Ian Storm Taylor Date: Fri, 14 Oct 2016 17:24:03 -0700 Subject: [PATCH 12/12] remove normalizeDocument transform --- src/transforms/index.js | 2 -- src/transforms/normalize.js | 16 ---------------- 2 files changed, 18 deletions(-) diff --git a/src/transforms/index.js b/src/transforms/index.js index ddddad45e..636b792d1 100644 --- a/src/transforms/index.js +++ b/src/transforms/index.js @@ -144,7 +144,6 @@ import { */ import { - normalizeDocument, normalizeSelection, } from './normalize' @@ -286,7 +285,6 @@ export default { * Normalize. */ - normalizeDocument, normalizeSelection, } diff --git a/src/transforms/normalize.js b/src/transforms/normalize.js index 4547dbdba..2e241daa2 100644 --- a/src/transforms/normalize.js +++ b/src/transforms/normalize.js @@ -1,20 +1,4 @@ -/** - * Normalize the document. - * - * @param {Transform} transform - * @return {Transform} - */ - -export function normalizeDocument(transform) { - let { state } = transform - let { document } = state - document = document.normalize() - state = state.merge({ document }) - transform.state = state - return transform -} - /** * Normalize the selection. *