1
0
mirror of https://github.com/ianstormtaylor/slate.git synced 2025-08-20 22:21:20 +02:00

refactor more transforms

This commit is contained in:
Ian Storm Taylor
2016-08-17 01:01:54 -07:00
parent 229cfd3e4e
commit 1d4c6db4d4
3 changed files with 200 additions and 155 deletions

View File

@@ -49,6 +49,20 @@ export function addMarkAtRange(transform, range, mark) {
*/ */
export function deleteAtRange(transform, range) { 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 { state } = transform
let { document } = state let { document } = state
@@ -147,65 +161,56 @@ export function deleteAtRange(transform, range) {
*/ */
export function deleteBackwardAtRange(transform, range, n = 1) { export function deleteBackwardAtRange(transform, range, n = 1) {
let { state } = transform const { state } = transform
let { document } = state const { document } = state
const { startKey, startOffset } = range const { startKey, focusOffset } = range
const text = document.getDescendant(startKey)
// 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 block = document.getClosestBlock(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) 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) { if (inline && inline.isVoid) {
document = document.removeDescendant(inline) return transform.removeNodeByKey(inline.key)
state = state.merge({ document }) }
transform.state = state
if (range.isAtStartOf(document)) {
return transform return transform
} }
// When at start of a text node, merge forwards into the next text node. if (range.isAtStartOf(text)) {
const startNode = document.getDescendant(startKey) 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) { if (prevBlock && prevBlock.isVoid) {
document = document.removeDescendant(prevBlock) return transform.removeNodeByKey(prevBlock.key)
state = state.merge({ document })
transform.state = state
return transform
} }
const prevInline = document.getClosestInline(previous)
if (prevInline && prevInline.isVoid) { if (prevInline && prevInline.isVoid) {
document = document.removeDescendant(prevInline) return transform.removeNodeByKey(prevInline.key)
state = state.merge({ document })
transform.state = state
return transform
} }
range = range.extendToEndOf(previous) range = range.merge({
range = range.normalize(document) anchorKey: prev.key,
return deleteAtRange(transform, range) anchorOffset: prev.length,
})
return transform.deleteAtRange(range)
} }
// Otherwise, remove `n` characters behind of the cursor. range = range.merge({
range = range.extendBackward(n) focusOffset: focusOffset - 1,
range = range.normalize(document) isBackward: true,
return deleteAtRange(transform, range) })
return transform.deleteAtRange(range)
} }
/** /**
@@ -218,46 +223,45 @@ export function deleteBackwardAtRange(transform, range, n = 1) {
*/ */
export function deleteForwardAtRange(transform, range, n = 1) { export function deleteForwardAtRange(transform, range, n = 1) {
let { state } = transform const { state } = transform
let { document } = state const { document } = state
const { startKey } = range const { startKey, focusOffset } = range
const text = document.getDescendant(startKey)
// 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 inline = document.getClosestInline(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) { if (inline && inline.isVoid) {
document = document.removeDescendant(inline) return transform.removeNodeByKey(inline.key)
state = state.merge({ document }) }
transform.state = state
if (range.isAtEndOf(document)) {
return transform return transform
} }
// When at end of a text node, merge forwards into the next text node. if (range.isAtEndOf(text)) {
const startNode = document.getDescendant(startKey) const next = document.getNextText(text)
if (range.isAtEndOf(startNode)) {
const next = document.getNextText(startNode) range = range.merge({
range = range.extendToStartOf(next) focusKey: next.key,
range = range.normalize(document) focusOffset: 0
return deleteAtRange(transform, range) })
return transform.deleteAtRange(range)
} }
// Otherwise, remove `n` characters ahead of the cursor. range = range.merge({
range = range.extendForward(n) focusOffset: focusOffset + 1
range = range.normalize(document) })
return deleteAtRange(transform, range)
return transform.deleteAtRange(range)
} }
/** /**
@@ -689,58 +693,33 @@ export function splitBlockAtRange(transform, range, depth = 1) {
*/ */
export function splitInlineAtRange(transform, range, depth = Infinity) { export function splitInlineAtRange(transform, range, depth = Infinity) {
let { state } = transform
let { document } = state
// If the range is expanded, remove it first.
if (range.isExpanded) { if (range.isExpanded) {
transform = deleteAtRange(transform, range) transform.deleteAtRange(range)
state = transform.state
document = state.document
range = range.collapseToStart() range = range.collapseToStart()
} }
// First split the text nodes. const { startKey, startOffset } = range
transform = splitTextAtRange(transform, range) const { state } = transform
state = transform.state const { document } = state
document = state.document let node = document.assertDescendant(startKey)
let parent = document.getClosestInline(node)
// Find the children that were split. let offset = startOffset
const { startKey } = range
let firstChild = document.getDescendant(startKey)
let secondChild = document.getNextText(firstChild)
let parent = document.getClosestInline(firstChild)
let d = 0 let d = 0
// While the parent is an inline parent, split the inline nodes. debugger
while (parent && d < depth) {
firstChild = parent.merge({ nodes: Inline.createList([firstChild]) })
secondChild = Inline.create({
nodes: [secondChild],
type: parent.type,
data: parent.data
})
// Split the children. while (parent && parent.kind == 'inline' && d < depth) {
const grandparent = document.getParent(parent) const index = parent.nodes.indexOf(node)
const nodes = grandparent.nodes const befores = parent.nodes.take(index)
.takeUntil(n => n.key == firstChild.key) const length = befores.reduce((l, n) => n.length, 0)
.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 }))
offset += length
node = parent
parent = document.getClosestInline(parent)
d++ d++
parent = document.getClosestInline(firstChild)
} }
state = state.merge({ document }) return transform.splitNodeByKey(node.key, offset)
transform.state = state
return transform
} }
/** /**

View File

@@ -1,18 +1,19 @@
import Normalize from '../utils/normalize' 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 {Transform} transform
* @param {String} key * @param {String} key
* @param {Number} index * @param {Number} offset
* @param {Number} length * @param {Number} length
* @param {Mixed} mark * @param {Mixed} mark
* @return {Transform} * @return {Transform}
*/ */
export function addMarkByKey(transform, key, index, length, mark) { export function addMarkByKey(transform, key, offset, length, mark) {
mark = Normalize.mark(mark) mark = Normalize.mark(mark)
let { state } = transform let { state } = transform
@@ -20,14 +21,14 @@ export function addMarkByKey(transform, key, index, length, mark) {
let node = document.assertDescendant(key) let node = document.assertDescendant(key)
const path = document.getPath(node) const path = document.getPath(node)
node = node.addMark(index, length, mark) node = node.addMark(offset, length, mark)
document = document.updateDescendant(node) document = document.updateDescendant(node)
state = state.merge({ document }) state = state.merge({ document })
transform.state = state transform.state = state
transform.operations.push({ transform.operations.push({
type: 'add-mark', type: 'add-mark',
index, offset,
length, length,
mark, mark,
path, 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 {Transform} transform
* @param {String} key * @param {String} key
* @param {Number} index * @param {Number} offset
* @param {String} text * @param {String} text
* @param {Set} marks (optional) * @param {Set} marks (optional)
* @return {Transform} * @return {Transform}
*/ */
export function insertTextByKey(transform, key, index, text, marks) { export function insertTextByKey(transform, key, offset, text, marks) {
let { state } = transform let { state } = transform
let { document } = state let { document } = state
let node = document.assertDescendant(key) let node = document.assertDescendant(key)
const path = document.getPath(node) const path = document.getPath(node)
node = node.insertText(index, text, marks) node = node.insertText(offset, text, marks)
document = document.updateDescendant(node) document = document.updateDescendant(node)
state = state.merge({ document }) state = state.merge({ document })
transform.state = state transform.state = state
transform.operations.push({ transform.operations.push({
type: 'insert-text', type: 'insert-text',
index, offset,
marks, marks,
path, path,
text, 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 {Transform} transform
* @param {String} key * @param {String} key
* @param {Number} index * @param {Number} offset
* @param {Number} length * @param {Number} length
* @param {Mark} mark * @param {Mark} mark
* @return {Transform} * @return {Transform}
*/ */
export function removeMarkByKey(transform, key, index, length, mark) { export function removeMarkByKey(transform, key, offset, length, mark) {
mark = Normalize.mark(mark) mark = Normalize.mark(mark)
let { state } = transform let { state } = transform
@@ -163,14 +164,14 @@ export function removeMarkByKey(transform, key, index, length, mark) {
let node = document.assertDescendant(key) let node = document.assertDescendant(key)
const path = document.getPath(node) const path = document.getPath(node)
node = node.removeMark(index, length, mark) node = node.removeMark(offset, length, mark)
document = document.updateDescendant(node) document = document.updateDescendant(node)
state = state.merge({ document }) state = state.merge({ document })
transform.state = state transform.state = state
transform.operations.push({ transform.operations.push({
type: 'remove-mark', type: 'remove-mark',
index, offset,
length, length,
mark, mark,
path, 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 {Transform} transform
* @param {String} key * @param {String} key
* @param {Number} index * @param {Number} offset
* @param {Number} length * @param {Number} length
* @return {Transform} * @return {Transform}
*/ */
export function removeTextByKey(transform, key, index, length) { export function removeTextByKey(transform, key, offset, length) {
let { state } = transform let { state } = transform
let { document } = state let { document } = state
let node = document.assertDescendant(key) let node = document.assertDescendant(key)
const path = document.getPath(node) const path = document.getPath(node)
node = node.removeText(index, length) node = node.removeText(offset, length)
document = document.updateDescendant(node) document = document.updateDescendant(node)
document = document.normalize() document = document.normalize()
state = state.merge({ document }) state = state.merge({ document })
@@ -234,7 +235,7 @@ export function removeTextByKey(transform, key, index, length) {
transform.state = state transform.state = state
transform.operations.push({ transform.operations.push({
type: 'remove-text', type: 'remove-text',
index, offset,
length, length,
path, 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 {Transform} transform
* @param {String} key * @param {String} key
* @param {Number} index * @param {Number} offset
* @param {Number} length * @param {Number} length
* @param {Mark} mark * @param {Mark} mark
* @return {Transform} * @return {Transform}
*/ */
export function setMarkByKey(transform, key, index, length, mark, properties) { export function setMarkByKey(transform, key, offset, length, mark, properties) {
mark = Normalize.mark(mark) mark = Normalize.mark(mark)
properties = Normalize.markProperties(properties) properties = Normalize.markProperties(properties)
@@ -262,14 +263,14 @@ export function setMarkByKey(transform, key, index, length, mark, properties) {
let node = document.assertDescendant(key) let node = document.assertDescendant(key)
const path = document.getPath(node) const path = document.getPath(node)
node = node.updateMark(index, length, mark, properties) node = node.updateMark(offset, length, mark, properties)
document = document.updateDescendant(node) document = document.updateDescendant(node)
state = state.merge({ document }) state = state.merge({ document })
transform.state = state transform.state = state
transform.operations.push({ transform.operations.push({
type: 'set-mark', type: 'set-mark',
index, offset,
length, length,
mark, mark,
path, path,
@@ -310,3 +311,66 @@ export function setNodeByKey(transform, key, properties) {
return transform 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
}

View File

@@ -56,15 +56,16 @@ import {
*/ */
import { import {
insertTextByKey,
removeTextByKey,
addMarkByKey, addMarkByKey,
removeMarkByKey,
setMarkByKey,
insertNodeAfterNodeByKey, insertNodeAfterNodeByKey,
removeNodeByKey, insertTextByKey,
setNodeByKey,
moveNodeByKey, moveNodeByKey,
removeMarkByKey,
removeNodeByKey,
removeTextByKey,
setMarkByKey,
setNodeByKey,
splitNodeByKey,
} from './by-key' } from './by-key'
/** /**
@@ -159,15 +160,16 @@ export default {
* By key. * By key.
*/ */
insertTextByKey,
removeTextByKey,
addMarkByKey, addMarkByKey,
removeMarkByKey,
setMarkByKey,
insertNodeAfterNodeByKey, insertNodeAfterNodeByKey,
removeNodeByKey, insertTextByKey,
setNodeByKey,
moveNodeByKey, moveNodeByKey,
removeMarkByKey,
removeNodeByKey,
removeTextByKey,
setMarkByKey,
setNodeByKey,
splitNodeByKey,
/** /**
* On selection. * On selection.