diff --git a/lib/models/text.js b/lib/models/text.js index 1b2185d7b..80582529d 100644 --- a/lib/models/text.js +++ b/lib/models/text.js @@ -110,7 +110,7 @@ class Text extends new Record(DEFAULTS) { addMark(index, length, mark) { const characters = this.characters.map((char, i) => { if (i < index) return char - if (i > index + length) return char + if (i >= index + length) return char let { marks } = char marks = marks.add(mark) char = char.merge({ marks }) diff --git a/lib/transforms/at-range.js b/lib/transforms/at-range.js index cd86507cf..ba35e06bb 100644 --- a/lib/transforms/at-range.js +++ b/lib/transforms/at-range.js @@ -13,44 +13,30 @@ import { Set } from 'immutable' * * @param {Transform} transform * @param {Selection} range - * @param {Mark || String || Object} mark + * @param {Mixed} mark * @return {Transform} */ export function addMarkAtRange(transform, range, mark) { - let { state } = transform - let { document } = state - - // Normalize the mark. - mark = Normalize.mark(mark) - - // When the range is collapsed, do nothing. if (range.isCollapsed) return transform - // Otherwise, find each of the text nodes within the range. + const { state } = transform + const { document } = state const { startKey, startOffset, endKey, endOffset } = range - let texts = document.getTextsAtRange(range) + const texts = document.getTextsAtRange(range) - // Apply the mark to each of the text nodes's matching characters. - texts = texts.map((text) => { - let characters = text.characters.map((char, i) => { - if (!isInRange(i, text, range)) return char - let { marks } = char - marks = marks.add(mark) - return char.merge({ marks }) - }) - - return text.merge({ characters }) - }) - - // Update each of the text nodes. texts.forEach((text) => { - document = document.updateDescendant(text) + const { key } = text + let index = 0 + let length = text.length + + if (key == startKey) index = startOffset + if (key == endKey) length = endOffset + if (key == startKey && key == endKey) length = endOffset - startOffset + + transform.addMarkByKey(key, index, length, mark) }) - // Update the state. - state = state.merge({ document }) - transform.state = state return transform } @@ -522,40 +508,30 @@ export function insertInlineAtRange(transform, range, inline) { } /** - * Insert text `string` at a `range`, with optional `marks`. + * Insert `text` at a `range`, with optional `marks`. * * @param {Transform} transform * @param {Selection} range - * @param {String} string + * @param {String} text * @param {Set} marks (optional) * @return {Transform} */ -export function insertTextAtRange(transform, range, string, marks) { - let { state } = transform - let { document } = state +export function insertTextAtRange(transform, range, text, marks) { + const { state } = transform + const { document } = state const { startKey, startOffset } = range const isVoid = document.hasVoidParent(startKey) - // If inside a void node, do nothing. - if (isVoid) return transform - - // Is the range is expanded, delete it first. - if (range.isExpanded) { - transform = deleteAtRange(transform, range) - state = transform.state - document = state.document - range = range.collapseToStart() + if (isVoid) { + return transform } - // Insert text at the range's offset. - let text = document.getDescendant(startKey) - text = text.insertText(startOffset, string, marks) - document = document.updateDescendant(text) + if (range.isExpanded) { + transform.deleteAtRange(range) + } - // Return the updated selection. - state = state.merge({ document }) - transform.state = state + transform.insertTextByKey(startKey, startOffset, text, marks) return transform } @@ -608,11 +584,11 @@ export function removeMarkAtRange(transform, range, mark) { * * @param {Transform} transform * @param {Selection} range - * @param {Object or String} properties + * @param {Object || String} properties * @return {Transform} */ -export function setBlockAtRange(transform, range, properties = {}) { +export function setBlockAtRange(transform, range, properties) { const { state } = transform const { document } = state const blocks = document.getBlocksAtRange(range) @@ -629,24 +605,19 @@ export function setBlockAtRange(transform, range, properties = {}) { * * @param {Transform} transform * @param {Selection} range - * @param {Object or String} properties + * @param {Object || String} properties * @return {Transform} */ -export function setInlineAtRange(transform, range, properties = {}) { - let { state } = transform - properties = Normalize.nodeProperties(properties) - let { document } = state +export function setInlineAtRange(transform, range, properties) { + const { state } = transform + const { document } = state const inlines = document.getInlinesAtRange(range) inlines.forEach((inline) => { - inline = inline.merge(properties) - document = document.updateDescendant(inline) + transform.setNodeByKey(inline.key, properties) }) - document = document.normalize() - state = state.merge({ document }) - transform.state = state return transform } diff --git a/lib/transforms/by-key.js b/lib/transforms/by-key.js index 592e2caad..8a57558f7 100644 --- a/lib/transforms/by-key.js +++ b/lib/transforms/by-key.js @@ -1,6 +1,73 @@ import Normalize from '../utils/normalize' +/** + * Add mark to text at `index` and `length` in node by `key`. + * + * @param {Transform} transform + * @param {String} key + * @param {Number} index + * @param {Number} length + * @param {Mixed} mark + * @return {Transform} + */ + +export function addMarkByKey(transform, key, index, length, mark) { + mark = Normalize.mark(mark) + let { state } = transform + let { document } = state + let node = document.assertDescendant(key) + const path = document.getPath(node) + + node = node.addMark(index, length, mark) + document = document.updateDescendant(node) + state = state.merge({ document }) + + transform.state = state + transform.operations.push({ + type: 'add-mark', + index, + length, + mark, + path, + }) + + return transform +} + +/** + * Insert a `node` at `index` in a node by `key`. + * + * @param {Transform} transform + * @param {String} key + * @param {Number} index + * @param {Node} node + * @return {Transform} + */ + +export function insertNodeByKey(transform, key, index, node) { + let { state } = transform + let { document } = state + let parent = document.assertDescendant(key) + const path = document.getPath(parent) + const nodes = parent.nodes.splice(index + 1, 0, node) + + parent = parent.merge({ nodes }) + document = document.updateDescendant(parent) + document = document.normalize() + state = state.merge({ document }) + + transform.state = state + transform.operations.push({ + type: 'insert-node', + index, + node, + path, + }) + + return transform +} + /** * Insert `text` at `index` in node by `key`. * @@ -34,234 +101,6 @@ export function insertTextByKey(transform, key, index, text, marks) { return transform } -/** - * Remove text at `index` and `length` in node by `key`. - * - * @param {Transform} transform - * @param {String} key - * @param {Number} index - * @param {Number} length - * @return {Transform} - */ - -export function removeTextByKey(transform, key, index, length) { - let { state } = transform - let { document } = state - let node = document.assertDescendant(key) - const path = document.getPath(node) - - node = node.removeText(index, length) - document = document.updateDescendant(node) - document = document.normalize() - state = state.merge({ document }) - - transform.state = state - transform.operations.push({ - type: 'remove-text', - index, - length, - path, - }) - - return transform -} - -/** - * Add mark to text at `index` and `length` in node by `key`. - * - * @param {Transform} transform - * @param {String} key - * @param {Number} index - * @param {Number} length - * @param {Mark} mark - * @return {Transform} - */ - -export function addMarkByKey(transform, key, index, length, mark) { - let { state } = transform - let { document } = state - let node = document.assertDescendant(key) - const path = document.getPath(node) - - node = node.addMark(index, length, mark) - document = document.updateDescendant(node) - state = state.merge({ document }) - - transform.state = state - transform.operations.push({ - type: 'add-mark', - index, - length, - mark, - path, - }) - - return transform -} - -/** - * Remove mark from text at `index` and `length` in node by `key`. - * - * @param {Transform} transform - * @param {String} key - * @param {Number} index - * @param {Number} length - * @param {Mark} mark - * @return {Transform} - */ - -export function removeMarkByKey(transform, key, index, length, mark) { - let { state } = transform - let { document } = state - let node = document.assertDescendant(key) - const path = document.getPath(node) - - node = node.removeMark(index, length, mark) - document = document.updateDescendant(node) - state = state.merge({ document }) - - transform.state = state - transform.operations.push({ - type: 'remove-mark', - index, - length, - mark, - path, - }) - - return transform -} - -/** - * Set `properties` on mark on text at `index` and `length` in node by `key`. - * - * @param {Transform} transform - * @param {String} key - * @param {Number} index - * @param {Number} length - * @param {Mark} mark - * @return {Transform} - */ - -export function setMarkByKey(transform, key, index, length, mark, properties) { - properties = Normalize.markProperties(properties) - let { state } = transform - let { document } = state - let node = document.assertDescendant(key) - const path = document.getPath(node) - - node = node.updateMark(index, length, mark, properties) - document = document.updateDescendant(node) - state = state.merge({ document }) - - transform.state = state - transform.operations.push({ - type: 'set-mark', - index, - length, - mark, - path, - properties, - }) - - return transform -} - -/** - * Insert a `node` at `index` in a node by `key`. - * - * @param {Transform} transform - * @param {String} key - * @param {Number} index - * @param {Node} node - * @return {Transform} - */ - -export function insertNodeAfterNodeByKey(transform, key, index, node) { - let { state } = transform - let { document } = state - let parent = document.assertDescendant(key) - const path = document.getPath(parent) - const nodes = parent.nodes.splice(index + 1, 0, node) - - parent = parent.merge({ nodes }) - document = document.updateDescendant(parent) - document = document.normalize() - state = state.merge({ document }) - - transform.state = state - transform.operations.push({ - type: 'insert-node', - index, - node, - path, - }) - - return transform -} - -/** - * Remove a node by `key`. - * - * @param {Transform} transform - * @param {String} key - * @return {Transform} - */ - -export function removeNodeByKey(transform, key) { - let { state } = transform - let { document } = state - const node = document.assertDescendant(key) - const path = document.getPath(node) - let parent = document.getParent(node) - const index = parent.nodes.indexOf(node) - const isParent = document == parent - - parent = parent.removeNode(index) - document = isParent ? parent : document.updateDescendant(parent) - document = document.normalize() - state = state.merge({ document }) - - transform.state = state - transform.operations.push({ - type: 'remove-node', - path, - }) - - return transform -} - -/** - * Set `properties` on a node by `key`. - * - * @param {Transform} transform - * @param {String} key - * @param {Object || String} properties - * @return {Transform} - */ - -export function setNodeByKey(transform, key, properties) { - properties = Normalize.nodeProperties(properties) - let { state } = transform - let { document } = state - let node = document.assertDescendant(key) - const path = document.getPath(node) - - node = node.merge(properties) - document = document.updateDescendant(node) - document = document.normalize() - state = state.merge({ document }) - - transform.state = state - transform.operations.push({ - type: 'set-node', - path, - properties, - }) - - return transform -} - /** * Move a node by `key` to a new parent by `key` and `index`. * @@ -303,3 +142,165 @@ export function moveNodeByKey(transform, key, newKey, newIndex) { return transform } + +/** + * Remove mark from text at `index` and `length` in node by `key`. + * + * @param {Transform} transform + * @param {String} key + * @param {Number} index + * @param {Number} length + * @param {Mark} mark + * @return {Transform} + */ + +export function removeMarkByKey(transform, key, index, length, mark) { + let { state } = transform + let { document } = state + let node = document.assertDescendant(key) + const path = document.getPath(node) + + node = node.removeMark(index, length, mark) + document = document.updateDescendant(node) + state = state.merge({ document }) + + transform.state = state + transform.operations.push({ + type: 'remove-mark', + index, + length, + mark, + path, + }) + + return transform +} + +/** + * Remove a node by `key`. + * + * @param {Transform} transform + * @param {String} key + * @return {Transform} + */ + +export function removeNodeByKey(transform, key) { + let { state } = transform + let { document } = state + const node = document.assertDescendant(key) + const path = document.getPath(node) + let parent = document.getParent(node) + const index = parent.nodes.indexOf(node) + const isParent = document == parent + + parent = parent.removeNode(index) + document = isParent ? parent : document.updateDescendant(parent) + document = document.normalize() + state = state.merge({ document }) + + transform.state = state + transform.operations.push({ + type: 'remove-node', + path, + }) + + return transform +} + +/** + * Remove text at `index` and `length` in node by `key`. + * + * @param {Transform} transform + * @param {String} key + * @param {Number} index + * @param {Number} length + * @return {Transform} + */ + +export function removeTextByKey(transform, key, index, length) { + let { state } = transform + let { document } = state + let node = document.assertDescendant(key) + const path = document.getPath(node) + + node = node.removeText(index, length) + document = document.updateDescendant(node) + document = document.normalize() + state = state.merge({ document }) + + transform.state = state + transform.operations.push({ + type: 'remove-text', + index, + length, + path, + }) + + return transform +} + +/** + * Set `properties` on mark on text at `index` and `length` in node by `key`. + * + * @param {Transform} transform + * @param {String} key + * @param {Number} index + * @param {Number} length + * @param {Mark} mark + * @return {Transform} + */ + +export function setMarkByKey(transform, key, index, length, mark, properties) { + properties = Normalize.markProperties(properties) + let { state } = transform + let { document } = state + let node = document.assertDescendant(key) + const path = document.getPath(node) + + node = node.updateMark(index, length, mark, properties) + document = document.updateDescendant(node) + state = state.merge({ document }) + + transform.state = state + transform.operations.push({ + type: 'set-mark', + index, + length, + mark, + path, + properties, + }) + + return transform +} + +/** + * Set `properties` on a node by `key`. + * + * @param {Transform} transform + * @param {String} key + * @param {Object || String} properties + * @return {Transform} + */ + +export function setNodeByKey(transform, key, properties) { + properties = Normalize.nodeProperties(properties) + let { state } = transform + let { document } = state + let node = document.assertDescendant(key) + const path = document.getPath(node) + + node = node.merge(properties) + document = document.updateDescendant(node) + document = document.normalize() + state = state.merge({ document }) + + transform.state = state + transform.operations.push({ + type: 'set-node', + path, + properties, + }) + + return transform +} diff --git a/lib/transforms/index.js b/lib/transforms/index.js index 18e6af703..7ccacf729 100644 --- a/lib/transforms/index.js +++ b/lib/transforms/index.js @@ -56,8 +56,15 @@ import { */ import { + insertTextByKey, + removeTextByKey, + addMarkByKey, + removeMarkByKey, + setMarkByKey, + insertNodeAfterNodeByKey, removeNodeByKey, setNodeByKey, + moveNodeByKey, } from './by-key' /** @@ -152,8 +159,15 @@ export default { * By key. */ + insertTextByKey, + removeTextByKey, + addMarkByKey, + removeMarkByKey, + setMarkByKey, + insertNodeAfterNodeByKey, removeNodeByKey, setNodeByKey, + moveNodeByKey, /** * On selection.