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: ""