From ed0395999c2f3246f00280d1528e8a220fa82e0e Mon Sep 17 00:00:00 2001 From: Nicolas Gaborit Date: Thu, 8 Dec 2016 19:37:34 +0100 Subject: [PATCH] Add unwrapNodeByKey (#509) * Add separate splitNodeOperation in two different op * Add transform unwrapNodeByKey * Add tests for unwrapNodeByKey * Add test for undo of unwrapNodeByKey * Support normalize option * Handle last/first sibling case * Document new unwrapNodeByKey transform --- docs/reference/models/transform.md | 6 ++ src/transforms/by-key.js | 59 ++++++++++++++++++- src/transforms/index.js | 4 ++ src/transforms/operations.js | 36 ++++++++++- .../unwrap-node-by-key/first-sibling/index.js | 7 +++ .../first-sibling/input.yaml | 16 +++++ .../first-sibling/output.yaml | 15 +++++ .../unwrap-node-by-key/last-sibling/index.js | 7 +++ .../last-sibling/input.yaml | 16 +++++ .../last-sibling/output.yaml | 15 +++++ .../unwrap-node-by-key/single-block/index.js | 7 +++ .../single-block/input.yaml | 19 ++++++ .../single-block/output.yaml | 15 +++++ .../unwrap-node-by-key/with-siblings/index.js | 7 +++ .../with-siblings/input.yaml | 21 +++++++ .../with-siblings/output.yaml | 23 ++++++++ .../undo/unwrap-node-by-key/index.js | 11 ++++ .../undo/unwrap-node-by-key/input.yaml | 21 +++++++ .../undo/unwrap-node-by-key/output.yaml | 20 +++++++ 19 files changed, 322 insertions(+), 3 deletions(-) create mode 100644 test/transforms/fixtures/by-key/unwrap-node-by-key/first-sibling/index.js create mode 100644 test/transforms/fixtures/by-key/unwrap-node-by-key/first-sibling/input.yaml create mode 100644 test/transforms/fixtures/by-key/unwrap-node-by-key/first-sibling/output.yaml create mode 100644 test/transforms/fixtures/by-key/unwrap-node-by-key/last-sibling/index.js create mode 100644 test/transforms/fixtures/by-key/unwrap-node-by-key/last-sibling/input.yaml create mode 100644 test/transforms/fixtures/by-key/unwrap-node-by-key/last-sibling/output.yaml create mode 100644 test/transforms/fixtures/by-key/unwrap-node-by-key/single-block/index.js create mode 100644 test/transforms/fixtures/by-key/unwrap-node-by-key/single-block/input.yaml create mode 100644 test/transforms/fixtures/by-key/unwrap-node-by-key/single-block/output.yaml create mode 100644 test/transforms/fixtures/by-key/unwrap-node-by-key/with-siblings/index.js create mode 100644 test/transforms/fixtures/by-key/unwrap-node-by-key/with-siblings/input.yaml create mode 100644 test/transforms/fixtures/by-key/unwrap-node-by-key/with-siblings/output.yaml create mode 100644 test/transforms/fixtures/on-history/undo/unwrap-node-by-key/index.js create mode 100644 test/transforms/fixtures/on-history/undo/unwrap-node-by-key/input.yaml create mode 100644 test/transforms/fixtures/on-history/undo/unwrap-node-by-key/output.yaml diff --git a/docs/reference/models/transform.md b/docs/reference/models/transform.md index 19c5abd67..ef7fe3584 100644 --- a/docs/reference/models/transform.md +++ b/docs/reference/models/transform.md @@ -59,6 +59,7 @@ Transform methods can either operate on the [`Document`](./document.md), the [`S - [`splitNodeByKey`](#splitnodebykey) - [`unwrapInlineByKey`](#unwrapinlinebykey) - [`unwrapBlockByKey`](#unwrapblockbykey) + - [`unwrapNodeByKey`](#unwrapnodebykey) - [`wrapBlockByKey`](#wrapblockbykey) - [`wrapInlineByKey`](#wrapinlinebykey) - [Document Transforms](#document-transforms) @@ -333,6 +334,11 @@ Unwrap all inner content of an [`Inline`](./inline.md) node that match `properti Unwrap all inner content of a [`Block`](./block.md) node that match `properties`. For convenience, you can pass a `type` string or `properties` object. +### `unwrapNodeByKey` +`unwrapNodeByKey(key: String) => Transform` + +Unwrap a single node from its parent. If the node is surrounded with siblings, its parent will be split. If the node is the only child, the parent is removed, and simply replaced by the node itself. Cannot unwrap a root node. + ### `wrapBlockByKey` `wrapBlockByKey(key: String, properties: Object) => Transform`
`wrapBlockByKey(key: String, type: String) => Transform` diff --git a/src/transforms/by-key.js b/src/transforms/by-key.js index 2cd3eccc8..a3298e709 100644 --- a/src/transforms/by-key.js +++ b/src/transforms/by-key.js @@ -276,7 +276,7 @@ export function splitNodeByKey(transform, key, offset, options = {}) { let { document } = state const path = document.getPath(key) - transform.splitNodeOperation(path, offset) + transform.splitNodeAtOffsetOperation(path, offset) if (normalize) { const parent = document.getParent(key) @@ -324,6 +324,63 @@ export function unwrapBlockByKey(transform, key, properties, options) { transform.unwrapBlockAtRange(range, properties, options) } +/** + * Unwrap a single node from its parent. + * + * If the node is surrounded with siblings, its parent will be + * split. If the node is the only child, the parent is removed, and + * simply replaced by the node itself. Cannot unwrap a root node. + * + * @param {Transform} transform + * @param {String} key + * @param {Object} options + * @property {Boolean} normalize + */ + +export function unwrapNodeByKey(transform, key, options = {}) { + const { normalize = true } = options + const { state } = transform + const { document } = state + const parent = document.getParent(key) + const node = parent.getChild(key) + + const index = parent.nodes.indexOf(node) + const isFirst = index === 0 + const isLast = index === parent.nodes.size - 1 + + const parentParent = document.getParent(parent.key) + const parentIndex = parentParent.nodes.indexOf(parent) + + + if (parent.nodes.size === 1) { + // Remove the parent + transform.removeNodeByKey(parent.key, { normalize: false }) + // and replace it by the node itself + transform.insertNodeByKey(parentParent.key, parentIndex, node, options) + } + + else if (isFirst) { + // Just move the node before its parent + transform.moveNodeByKey(key, parentParent.key, parentIndex, options) + } + + else if (isLast) { + // Just move the node after its parent + transform.moveNodeByKey(key, parentParent.key, parentIndex + 1, options) + } + + else { + const parentPath = document.getPath(parent.key) + // Split the parent + transform.splitNodeOperation(parentPath, index) + // Extract the node in between the splitted parent + transform.moveNodeByKey(key, parentParent.key, parentIndex + 1, { normalize: false }) + + if (normalize) { + transform.normalizeNodeByKey(parentParent.key, SCHEMA) + } + } +} /** * Wrap a node in an inline with `properties`. diff --git a/src/transforms/index.js b/src/transforms/index.js index 11c33f9b2..280901a63 100644 --- a/src/transforms/index.js +++ b/src/transforms/index.js @@ -29,6 +29,7 @@ import { setMarkOperation, setNodeOperation, setSelectionOperation, + splitNodeAtOffsetOperation, splitNodeOperation, } from './operations' @@ -114,6 +115,7 @@ import { splitNodeByKey, unwrapInlineByKey, unwrapBlockByKey, + unwrapNodeByKey, wrapBlockByKey, wrapInlineByKey, } from './by-key' @@ -211,6 +213,7 @@ export default { setMarkOperation, setNodeOperation, setSelectionOperation, + splitNodeAtOffsetOperation, splitNodeOperation, /** @@ -290,6 +293,7 @@ export default { splitNodeByKey, unwrapInlineByKey, unwrapBlockByKey, + unwrapNodeByKey, wrapBlockByKey, wrapInlineByKey, diff --git a/src/transforms/operations.js b/src/transforms/operations.js index 923919192..b6121989b 100644 --- a/src/transforms/operations.js +++ b/src/transforms/operations.js @@ -432,7 +432,7 @@ export function setSelectionOperation(transform, properties, options = {}) { * @param {Number} offset */ -export function splitNodeOperation(transform, path, offset) { +export function splitNodeAtOffsetOperation(transform, path, offset) { const inversePath = path.slice() inversePath[path.length - 1] += 1 @@ -440,13 +440,45 @@ export function splitNodeOperation(transform, path, offset) { type: 'join_node', path: inversePath, withPath: path, - deep: true // we need to join nodes recursively + // we will split down to the text nodes, so we must join nodes recursively + deep: true }] const operation = { type: 'split_node', path, offset, + count: null, + inverse, + } + + transform.applyOperation(operation) +} + +/** + * Split a node by `path` after its 'count' child. + * + * @param {Transform} transform + * @param {Array} path + * @param {Number} count + */ + +export function splitNodeOperation(transform, path, count) { + const inversePath = path.slice() + inversePath[path.length - 1] += 1 + + const inverse = [{ + type: 'join_node', + path: inversePath, + withPath: path, + deep: false + }] + + const operation = { + type: 'split_node', + path, + offset: null, + count, inverse, } diff --git a/test/transforms/fixtures/by-key/unwrap-node-by-key/first-sibling/index.js b/test/transforms/fixtures/by-key/unwrap-node-by-key/first-sibling/index.js new file mode 100644 index 000000000..536c233d8 --- /dev/null +++ b/test/transforms/fixtures/by-key/unwrap-node-by-key/first-sibling/index.js @@ -0,0 +1,7 @@ + +export default function (state) { + return state + .transform() + .unwrapNodeByKey('to-unwrap') + .apply() +} diff --git a/test/transforms/fixtures/by-key/unwrap-node-by-key/first-sibling/input.yaml b/test/transforms/fixtures/by-key/unwrap-node-by-key/first-sibling/input.yaml new file mode 100644 index 000000000..76ac4dd3a --- /dev/null +++ b/test/transforms/fixtures/by-key/unwrap-node-by-key/first-sibling/input.yaml @@ -0,0 +1,16 @@ + +nodes: + - kind: block + type: quote + nodes: + - kind: block + type: paragraph + key: 'to-unwrap' + nodes: + - kind: text + text: word1 + - kind: block + type: paragraph + nodes: + - kind: text + text: word2 diff --git a/test/transforms/fixtures/by-key/unwrap-node-by-key/first-sibling/output.yaml b/test/transforms/fixtures/by-key/unwrap-node-by-key/first-sibling/output.yaml new file mode 100644 index 000000000..c6de08c0c --- /dev/null +++ b/test/transforms/fixtures/by-key/unwrap-node-by-key/first-sibling/output.yaml @@ -0,0 +1,15 @@ + +nodes: + - kind: block + type: paragraph + nodes: + - kind: text + text: word1 + - kind: block + type: quote + nodes: + - kind: block + type: paragraph + nodes: + - kind: text + text: word2 diff --git a/test/transforms/fixtures/by-key/unwrap-node-by-key/last-sibling/index.js b/test/transforms/fixtures/by-key/unwrap-node-by-key/last-sibling/index.js new file mode 100644 index 000000000..536c233d8 --- /dev/null +++ b/test/transforms/fixtures/by-key/unwrap-node-by-key/last-sibling/index.js @@ -0,0 +1,7 @@ + +export default function (state) { + return state + .transform() + .unwrapNodeByKey('to-unwrap') + .apply() +} diff --git a/test/transforms/fixtures/by-key/unwrap-node-by-key/last-sibling/input.yaml b/test/transforms/fixtures/by-key/unwrap-node-by-key/last-sibling/input.yaml new file mode 100644 index 000000000..8f064ed90 --- /dev/null +++ b/test/transforms/fixtures/by-key/unwrap-node-by-key/last-sibling/input.yaml @@ -0,0 +1,16 @@ + +nodes: + - kind: block + type: quote + nodes: + - kind: block + type: paragraph + nodes: + - kind: text + text: word1 + - kind: block + type: paragraph + key: 'to-unwrap' + nodes: + - kind: text + text: word2 diff --git a/test/transforms/fixtures/by-key/unwrap-node-by-key/last-sibling/output.yaml b/test/transforms/fixtures/by-key/unwrap-node-by-key/last-sibling/output.yaml new file mode 100644 index 000000000..b5ce4de3d --- /dev/null +++ b/test/transforms/fixtures/by-key/unwrap-node-by-key/last-sibling/output.yaml @@ -0,0 +1,15 @@ + +nodes: + - kind: block + type: quote + nodes: + - kind: block + type: paragraph + nodes: + - kind: text + text: word1 + - kind: block + type: paragraph + nodes: + - kind: text + text: word2 diff --git a/test/transforms/fixtures/by-key/unwrap-node-by-key/single-block/index.js b/test/transforms/fixtures/by-key/unwrap-node-by-key/single-block/index.js new file mode 100644 index 000000000..536c233d8 --- /dev/null +++ b/test/transforms/fixtures/by-key/unwrap-node-by-key/single-block/index.js @@ -0,0 +1,7 @@ + +export default function (state) { + return state + .transform() + .unwrapNodeByKey('to-unwrap') + .apply() +} diff --git a/test/transforms/fixtures/by-key/unwrap-node-by-key/single-block/input.yaml b/test/transforms/fixtures/by-key/unwrap-node-by-key/single-block/input.yaml new file mode 100644 index 000000000..fee4eb30f --- /dev/null +++ b/test/transforms/fixtures/by-key/unwrap-node-by-key/single-block/input.yaml @@ -0,0 +1,19 @@ + +nodes: + - kind: block + type: quote + nodes: + - kind: block + key: 'to-unwrap' + type: paragraph + nodes: + - kind: text + text: word + - kind: block + type: quote + nodes: + - kind: block + type: paragraph + nodes: + - kind: text + text: word diff --git a/test/transforms/fixtures/by-key/unwrap-node-by-key/single-block/output.yaml b/test/transforms/fixtures/by-key/unwrap-node-by-key/single-block/output.yaml new file mode 100644 index 000000000..c5a0f1a30 --- /dev/null +++ b/test/transforms/fixtures/by-key/unwrap-node-by-key/single-block/output.yaml @@ -0,0 +1,15 @@ + +nodes: + - kind: block + type: paragraph + nodes: + - kind: text + text: word + - kind: block + type: quote + nodes: + - kind: block + type: paragraph + nodes: + - kind: text + text: word diff --git a/test/transforms/fixtures/by-key/unwrap-node-by-key/with-siblings/index.js b/test/transforms/fixtures/by-key/unwrap-node-by-key/with-siblings/index.js new file mode 100644 index 000000000..536c233d8 --- /dev/null +++ b/test/transforms/fixtures/by-key/unwrap-node-by-key/with-siblings/index.js @@ -0,0 +1,7 @@ + +export default function (state) { + return state + .transform() + .unwrapNodeByKey('to-unwrap') + .apply() +} diff --git a/test/transforms/fixtures/by-key/unwrap-node-by-key/with-siblings/input.yaml b/test/transforms/fixtures/by-key/unwrap-node-by-key/with-siblings/input.yaml new file mode 100644 index 000000000..eac9dbc1b --- /dev/null +++ b/test/transforms/fixtures/by-key/unwrap-node-by-key/with-siblings/input.yaml @@ -0,0 +1,21 @@ + +nodes: + - kind: block + type: quote + nodes: + - kind: block + type: paragraph + nodes: + - kind: text + text: word1 + - kind: block + type: paragraph + key: 'to-unwrap' + nodes: + - kind: text + text: word2 + - kind: block + type: paragraph + nodes: + - kind: text + text: word3 diff --git a/test/transforms/fixtures/by-key/unwrap-node-by-key/with-siblings/output.yaml b/test/transforms/fixtures/by-key/unwrap-node-by-key/with-siblings/output.yaml new file mode 100644 index 000000000..eed18bd3d --- /dev/null +++ b/test/transforms/fixtures/by-key/unwrap-node-by-key/with-siblings/output.yaml @@ -0,0 +1,23 @@ + +nodes: + - kind: block + type: quote + nodes: + - kind: block + type: paragraph + nodes: + - kind: text + text: word1 + - kind: block + type: paragraph + nodes: + - kind: text + text: word2 + - kind: block + type: quote + nodes: + - kind: block + type: paragraph + nodes: + - kind: text + text: word3 diff --git a/test/transforms/fixtures/on-history/undo/unwrap-node-by-key/index.js b/test/transforms/fixtures/on-history/undo/unwrap-node-by-key/index.js new file mode 100644 index 000000000..911da2ab6 --- /dev/null +++ b/test/transforms/fixtures/on-history/undo/unwrap-node-by-key/index.js @@ -0,0 +1,11 @@ + +export default function (state) { + return state + .transform() + .unwrapNodeByKey('to-unwrap') + .apply() + + .transform() + .undo() + .apply() +} diff --git a/test/transforms/fixtures/on-history/undo/unwrap-node-by-key/input.yaml b/test/transforms/fixtures/on-history/undo/unwrap-node-by-key/input.yaml new file mode 100644 index 000000000..eac9dbc1b --- /dev/null +++ b/test/transforms/fixtures/on-history/undo/unwrap-node-by-key/input.yaml @@ -0,0 +1,21 @@ + +nodes: + - kind: block + type: quote + nodes: + - kind: block + type: paragraph + nodes: + - kind: text + text: word1 + - kind: block + type: paragraph + key: 'to-unwrap' + nodes: + - kind: text + text: word2 + - kind: block + type: paragraph + nodes: + - kind: text + text: word3 diff --git a/test/transforms/fixtures/on-history/undo/unwrap-node-by-key/output.yaml b/test/transforms/fixtures/on-history/undo/unwrap-node-by-key/output.yaml new file mode 100644 index 000000000..a99daffea --- /dev/null +++ b/test/transforms/fixtures/on-history/undo/unwrap-node-by-key/output.yaml @@ -0,0 +1,20 @@ + +nodes: + - kind: block + type: quote + nodes: + - kind: block + type: paragraph + nodes: + - kind: text + text: word1 + - kind: block + type: paragraph + nodes: + - kind: text + text: word2 + - kind: block + type: paragraph + nodes: + - kind: text + text: word3