diff --git a/lib/models/node.js b/lib/models/node.js index 5828efbde..71ee7629b 100644 --- a/lib/models/node.js +++ b/lib/models/node.js @@ -922,12 +922,52 @@ const Node = { }) return node.normalize() - } + }, /** - * Unwrap the node + * Unwrap all of the block nodes in a `range` from a parent node of `type.` + * + * @param {Selection} range + * @param {String} type + * @return {Node} node */ + unwrapAtRange(range, type) { + range = range.normalize(this) + let node = this + let blocks = node.getBlockNodesAtRange(range) + + // Iterate each of the block nodes, unwrapping them. + blocks.forEach((block) => { + let wrapper = node.getParentNode(block) + while (wrapper && wrapper.type != type) { + wrapper = node.getParentNode(wrapper) + } + + // If no wrapper was found, do skip this block. + if (!wrapper) return + + // Take the child nodes of the wrapper, and move them to the block. + const isDirectChild = node.nodes.has(wrapper.key) + let parent = isDirectChild ? node : node.getParentNode(wrapper) + + // Replace the wrapper in the parent's nodes with the block. + const nodes = parent.nodes.takeUntil(node => node == wrapper) + .set(block.key, block) + .concat(parent.nodes.skipUntil(node => node == wrapper).rest()) + + // Update the parent. + if (isDirectChild) { + node = node.merge({ nodes }) + } else { + parent = parent.merge({ nodes }) + node = node.updateNode(parent) + } + }) + + return node.normalize() + } + } /** diff --git a/lib/models/state.js b/lib/models/state.js index c30ed3549..6324230b9 100644 --- a/lib/models/state.js +++ b/lib/models/state.js @@ -353,6 +353,22 @@ class State extends Record(DEFAULTS) { return state } + /** + * Unwrap the block nodes in the current selection from a parent of `type`. + * + * @param {String} type + * @return {State} state + */ + + unwrap(type) { + let state = this + let { document, selection } = state + selection = selection.normalize(document) + document = document.unwrapAtRange(selection, type) + state = state.merge({ document, selection }) + return state + } + } /**