From 1d4c6db4d428d1da76500b351ceb800e82e76f69 Mon Sep 17 00:00:00 2001 From: Ian Storm Taylor Date: Wed, 17 Aug 2016 01:01:54 -0700 Subject: [PATCH] refactor more transforms --- lib/transforms/at-range.js | 215 +++++++++++++++++-------------------- lib/transforms/by-key.js | 114 +++++++++++++++----- lib/transforms/index.js | 26 ++--- 3 files changed, 200 insertions(+), 155 deletions(-) diff --git a/lib/transforms/at-range.js b/lib/transforms/at-range.js index febb53184..62ee84011 100644 --- a/lib/transforms/at-range.js +++ b/lib/transforms/at-range.js @@ -49,6 +49,20 @@ export function addMarkAtRange(transform, range, mark) { */ export function deleteAtRange(transform, range) { + // if (range.isCollapsed) return transform + + // const { state } = transform + // const { document } = state + // const { startKey, startOffset, endKey, endOffset } = range + // const startText = document.assertDescendant(startKey) + // const endText = document.assertDescendant(endKey) + + // if (startKey == endKey) { + // const index = startOffset + // const length = endOffset - startOffset + // return transform.removeTextByKey(startKey, index, length) + // } + let { state } = transform let { document } = state @@ -147,65 +161,56 @@ export function deleteAtRange(transform, range) { */ export function deleteBackwardAtRange(transform, range, n = 1) { - let { state } = transform - let { document } = state - const { startKey, startOffset } = range - - // When the range is still expanded, just do a regular delete. - if (range.isExpanded) return deleteAtRange(transform, range) - - // When collapsed at the start of the node, there's nothing to do. - if (range.isAtStartOf(document)) return transform - - // When collapsed in a void node, remove that node. + const { state } = transform + const { document } = state + const { startKey, focusOffset } = range + const text = document.getDescendant(startKey) const block = document.getClosestBlock(startKey) - if (block && block.isVoid) { - document = document.removeDescendant(block) - state = state.merge({ document }) - transform.state = state - return transform - } - const inline = document.getClosestInline(startKey) + + if (range.isExpanded) { + return transform.deleteAtRange(range) + } + + if (block && block.isVoid) { + return transform.removeNodeByKey(block.key) + } + if (inline && inline.isVoid) { - document = document.removeDescendant(inline) - state = state.merge({ document }) - transform.state = state + return transform.removeNodeByKey(inline.key) + } + + if (range.isAtStartOf(document)) { return transform } - // When at start of a text node, merge forwards into the next text node. - const startNode = document.getDescendant(startKey) + if (range.isAtStartOf(text)) { + const prev = document.getPreviousText(text) + const prevBlock = document.getClosestBlock(prev) + const prevInline = document.getClosestInline(prev) - if (range.isAtStartOf(startNode)) { - const previous = document.getPreviousText(startNode) - - // If the previous descendant is void, remove it. - const prevBlock = document.getClosestBlock(previous) if (prevBlock && prevBlock.isVoid) { - document = document.removeDescendant(prevBlock) - state = state.merge({ document }) - transform.state = state - return transform + return transform.removeNodeByKey(prevBlock.key) } - const prevInline = document.getClosestInline(previous) if (prevInline && prevInline.isVoid) { - document = document.removeDescendant(prevInline) - state = state.merge({ document }) - transform.state = state - return transform + return transform.removeNodeByKey(prevInline.key) } - range = range.extendToEndOf(previous) - range = range.normalize(document) - return deleteAtRange(transform, range) + range = range.merge({ + anchorKey: prev.key, + anchorOffset: prev.length, + }) + + return transform.deleteAtRange(range) } - // Otherwise, remove `n` characters behind of the cursor. - range = range.extendBackward(n) - range = range.normalize(document) - return deleteAtRange(transform, range) + range = range.merge({ + focusOffset: focusOffset - 1, + isBackward: true, + }) + + return transform.deleteAtRange(range) } /** @@ -218,46 +223,45 @@ export function deleteBackwardAtRange(transform, range, n = 1) { */ export function deleteForwardAtRange(transform, range, n = 1) { - let { state } = transform - let { document } = state - const { startKey } = range - - // When the range is still expanded, just do a regular delete. - if (range.isExpanded) return deleteAtRange(transform, range) - - // When collapsed at the end of the node, there's nothing to do. - if (range.isAtEndOf(document)) return transform - - // When collapsed in a void node, remove that node. - const block = document.getClosestBlock(startKey) - if (block && block.isVoid) { - document = document.removeDescendant(block) - state = state.merge({ document }) - transform.state = state - return transform - } - + const { state } = transform + const { document } = state + const { startKey, focusOffset } = range + const text = document.getDescendant(startKey) const inline = document.getClosestInline(startKey) + const block = document.getClosestBlock(startKey) + + if (range.isExpanded) { + return transform.deleteAtRange(range) + } + + if (block && block.isVoid) { + return transform.removeNodeByKey(block.key) + } + if (inline && inline.isVoid) { - document = document.removeDescendant(inline) - state = state.merge({ document }) - transform.state = state + return transform.removeNodeByKey(inline.key) + } + + if (range.isAtEndOf(document)) { return transform } - // When at end of a text node, merge forwards into the next text node. - const startNode = document.getDescendant(startKey) - if (range.isAtEndOf(startNode)) { - const next = document.getNextText(startNode) - range = range.extendToStartOf(next) - range = range.normalize(document) - return deleteAtRange(transform, range) + if (range.isAtEndOf(text)) { + const next = document.getNextText(text) + + range = range.merge({ + focusKey: next.key, + focusOffset: 0 + }) + + return transform.deleteAtRange(range) } - // Otherwise, remove `n` characters ahead of the cursor. - range = range.extendForward(n) - range = range.normalize(document) - return deleteAtRange(transform, range) + range = range.merge({ + focusOffset: focusOffset + 1 + }) + + return transform.deleteAtRange(range) } /** @@ -689,58 +693,33 @@ export function splitBlockAtRange(transform, range, depth = 1) { */ export function splitInlineAtRange(transform, range, depth = Infinity) { - let { state } = transform - let { document } = state - - // If the range is expanded, remove it first. if (range.isExpanded) { - transform = deleteAtRange(transform, range) - state = transform.state - document = state.document + transform.deleteAtRange(range) range = range.collapseToStart() } - // First split the text nodes. - transform = splitTextAtRange(transform, range) - state = transform.state - document = state.document - - // Find the children that were split. - const { startKey } = range - let firstChild = document.getDescendant(startKey) - let secondChild = document.getNextText(firstChild) - let parent = document.getClosestInline(firstChild) + const { startKey, startOffset } = range + const { state } = transform + const { document } = state + let node = document.assertDescendant(startKey) + let parent = document.getClosestInline(node) + let offset = startOffset let d = 0 - // While the parent is an inline parent, split the inline nodes. - while (parent && d < depth) { - firstChild = parent.merge({ nodes: Inline.createList([firstChild]) }) - secondChild = Inline.create({ - nodes: [secondChild], - type: parent.type, - data: parent.data - }) + debugger - // Split the children. - const grandparent = document.getParent(parent) - const nodes = grandparent.nodes - .takeUntil(n => n.key == firstChild.key) - .push(firstChild) - .push(secondChild) - .concat(grandparent.nodes.skipUntil(n => n.key == firstChild.key).rest()) - - // Update the grandparent. - document = grandparent == document - ? document.merge({ nodes }) - : document.updateDescendant(grandparent.merge({ nodes })) + while (parent && parent.kind == 'inline' && d < depth) { + const index = parent.nodes.indexOf(node) + const befores = parent.nodes.take(index) + const length = befores.reduce((l, n) => n.length, 0) + offset += length + node = parent + parent = document.getClosestInline(parent) d++ - parent = document.getClosestInline(firstChild) } - state = state.merge({ document }) - transform.state = state - return transform + return transform.splitNodeByKey(node.key, offset) } /** diff --git a/lib/transforms/by-key.js b/lib/transforms/by-key.js index bc262c762..436418918 100644 --- a/lib/transforms/by-key.js +++ b/lib/transforms/by-key.js @@ -1,18 +1,19 @@ import Normalize from '../utils/normalize' +import uid from '../utils/uid' /** - * Add mark to text at `index` and `length` in node by `key`. + * Add mark to text at `offset` and `length` in node by `key`. * * @param {Transform} transform * @param {String} key - * @param {Number} index + * @param {Number} offset * @param {Number} length * @param {Mixed} mark * @return {Transform} */ -export function addMarkByKey(transform, key, index, length, mark) { +export function addMarkByKey(transform, key, offset, length, mark) { mark = Normalize.mark(mark) let { state } = transform @@ -20,14 +21,14 @@ export function addMarkByKey(transform, key, index, length, mark) { let node = document.assertDescendant(key) const path = document.getPath(node) - node = node.addMark(index, length, mark) + node = node.addMark(offset, length, mark) document = document.updateDescendant(node) state = state.merge({ document }) transform.state = state transform.operations.push({ type: 'add-mark', - index, + offset, length, mark, path, @@ -70,30 +71,30 @@ export function insertNodeByKey(transform, key, index, node) { } /** - * Insert `text` at `index` in node by `key`. + * Insert `text` at `offset` in node by `key`. * * @param {Transform} transform * @param {String} key - * @param {Number} index + * @param {Number} offset * @param {String} text * @param {Set} marks (optional) * @return {Transform} */ -export function insertTextByKey(transform, key, index, text, marks) { +export function insertTextByKey(transform, key, offset, text, marks) { let { state } = transform let { document } = state let node = document.assertDescendant(key) const path = document.getPath(node) - node = node.insertText(index, text, marks) + node = node.insertText(offset, text, marks) document = document.updateDescendant(node) state = state.merge({ document }) transform.state = state transform.operations.push({ type: 'insert-text', - index, + offset, marks, path, text, @@ -145,17 +146,17 @@ export function moveNodeByKey(transform, key, newKey, newIndex) { } /** - * Remove mark from text at `index` and `length` in node by `key`. + * Remove mark from text at `offset` and `length` in node by `key`. * * @param {Transform} transform * @param {String} key - * @param {Number} index + * @param {Number} offset * @param {Number} length * @param {Mark} mark * @return {Transform} */ -export function removeMarkByKey(transform, key, index, length, mark) { +export function removeMarkByKey(transform, key, offset, length, mark) { mark = Normalize.mark(mark) let { state } = transform @@ -163,14 +164,14 @@ export function removeMarkByKey(transform, key, index, length, mark) { let node = document.assertDescendant(key) const path = document.getPath(node) - node = node.removeMark(index, length, mark) + node = node.removeMark(offset, length, mark) document = document.updateDescendant(node) state = state.merge({ document }) transform.state = state transform.operations.push({ type: 'remove-mark', - index, + offset, length, mark, path, @@ -211,22 +212,22 @@ export function removeNodeByKey(transform, key) { } /** - * Remove text at `index` and `length` in node by `key`. + * Remove text at `offset` and `length` in node by `key`. * * @param {Transform} transform * @param {String} key - * @param {Number} index + * @param {Number} offset * @param {Number} length * @return {Transform} */ -export function removeTextByKey(transform, key, index, length) { +export function removeTextByKey(transform, key, offset, length) { let { state } = transform let { document } = state let node = document.assertDescendant(key) const path = document.getPath(node) - node = node.removeText(index, length) + node = node.removeText(offset, length) document = document.updateDescendant(node) document = document.normalize() state = state.merge({ document }) @@ -234,7 +235,7 @@ export function removeTextByKey(transform, key, index, length) { transform.state = state transform.operations.push({ type: 'remove-text', - index, + offset, length, path, }) @@ -243,17 +244,17 @@ export function removeTextByKey(transform, key, index, length) { } /** - * Set `properties` on mark on text at `index` and `length` in node by `key`. + * Set `properties` on mark on text at `offset` and `length` in node by `key`. * * @param {Transform} transform * @param {String} key - * @param {Number} index + * @param {Number} offset * @param {Number} length * @param {Mark} mark * @return {Transform} */ -export function setMarkByKey(transform, key, index, length, mark, properties) { +export function setMarkByKey(transform, key, offset, length, mark, properties) { mark = Normalize.mark(mark) properties = Normalize.markProperties(properties) @@ -262,14 +263,14 @@ export function setMarkByKey(transform, key, index, length, mark, properties) { let node = document.assertDescendant(key) const path = document.getPath(node) - node = node.updateMark(index, length, mark, properties) + node = node.updateMark(offset, length, mark, properties) document = document.updateDescendant(node) state = state.merge({ document }) transform.state = state transform.operations.push({ type: 'set-mark', - index, + offset, length, mark, path, @@ -310,3 +311,66 @@ export function setNodeByKey(transform, key, properties) { return transform } + +/** + * Split a node by `key` at `offset`. + * + * @param {Transform} transform + * @param {String} key + * @param {Number} offset + * @return {Transform} + */ + +export function splitNodeByKey(transform, key, offset) { + let { state } = transform + let { document } = state + let node = document.assertDescendant(key) + const path = document.getPath(node) + let parent = document.getParent(node) + const isParent = document == parent + const index = parent.nodes.indexOf(node) + + let child = node + let one + let two + + if (node.kind != 'text') { + child = node.getTextAtOffset(offset) + } + + while (child && child != parent) { + if (child.kind == 'text') { + const { characters } = child + const oneChars = characters.take(offset) + const twoChars = characters.skip(offset) + one = child.merge({ characters: oneChars }) + two = child.merge({ characters: twoChars, key: uid() }) + } + + else { + const { nodes } = child + const oneNodes = nodes.takeUntil(n => n.key == one.key).push(one) + const twoNodes = nodes.skipUntil(n => n.key == one.key).rest().unshift(two) + one = child.merge({ nodes: oneNodes }) + two = child.merge({ nodes: twoNodes, key: uid() }) + } + + child = document.getParent(child) + } + + parent = parent.removeNode(index) + parent = parent.insertNode(index, two) + parent = parent.insertNode(index, one) + document = isParent ? parent : document.updateDescendant(parent) + state = state.merge({ document }) + + transform.state = state + transform.operations.push({ + type: 'split-node', + offset, + path, + }) + + return transform +} + diff --git a/lib/transforms/index.js b/lib/transforms/index.js index 7ccacf729..c9781bd87 100644 --- a/lib/transforms/index.js +++ b/lib/transforms/index.js @@ -56,15 +56,16 @@ import { */ import { - insertTextByKey, - removeTextByKey, addMarkByKey, - removeMarkByKey, - setMarkByKey, insertNodeAfterNodeByKey, - removeNodeByKey, - setNodeByKey, + insertTextByKey, moveNodeByKey, + removeMarkByKey, + removeNodeByKey, + removeTextByKey, + setMarkByKey, + setNodeByKey, + splitNodeByKey, } from './by-key' /** @@ -159,15 +160,16 @@ export default { * By key. */ - insertTextByKey, - removeTextByKey, addMarkByKey, - removeMarkByKey, - setMarkByKey, insertNodeAfterNodeByKey, - removeNodeByKey, - setNodeByKey, + insertTextByKey, moveNodeByKey, + removeMarkByKey, + removeNodeByKey, + removeTextByKey, + setMarkByKey, + setNodeByKey, + splitNodeByKey, /** * On selection.