mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-08-23 07:22:55 +02:00
move document transforms to node interface
This commit is contained in:
@@ -1,9 +1,5 @@
|
|||||||
|
|
||||||
import Character from './character'
|
|
||||||
import Element from './element'
|
|
||||||
import Node from './node'
|
import Node from './node'
|
||||||
import Selection from './selection'
|
|
||||||
import Text from './text'
|
|
||||||
import { OrderedMap, Record } from 'immutable'
|
import { OrderedMap, Record } from 'immutable'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -48,7 +44,7 @@ class Document extends Record(DEFAULTS) {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
get text() {
|
get text() {
|
||||||
return this
|
return this.nodes
|
||||||
.map(node => node.text)
|
.map(node => node.text)
|
||||||
.join('')
|
.join('')
|
||||||
}
|
}
|
||||||
@@ -63,303 +59,6 @@ class Document extends Record(DEFAULTS) {
|
|||||||
return 'document'
|
return 'document'
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete everything in a `range`.
|
|
||||||
*
|
|
||||||
* @param {Selection} range
|
|
||||||
* @return {Document} document
|
|
||||||
*/
|
|
||||||
|
|
||||||
deleteAtRange(range) {
|
|
||||||
let document = this
|
|
||||||
|
|
||||||
// If the range is collapsed, there's nothing to do.
|
|
||||||
if (range.isCollapsed) return document
|
|
||||||
|
|
||||||
const { startKey, startOffset, endKey, endOffset } = range
|
|
||||||
let startNode = document.getNode(startKey)
|
|
||||||
|
|
||||||
// If the start and end nodes are the same, remove the matching characters.
|
|
||||||
if (startKey == endKey) {
|
|
||||||
let { characters } = startNode
|
|
||||||
|
|
||||||
characters = characters.filterNot((char, i) => {
|
|
||||||
return startOffset <= i && i < endOffset
|
|
||||||
})
|
|
||||||
|
|
||||||
startNode = startNode.merge({ characters })
|
|
||||||
document = document.updateNode(startNode)
|
|
||||||
return document
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise, remove the text from the first and last nodes...
|
|
||||||
const startRange = Selection.create({
|
|
||||||
anchorKey: startKey,
|
|
||||||
anchorOffset: startOffset,
|
|
||||||
focusKey: startKey,
|
|
||||||
focusOffset: startNode.length
|
|
||||||
})
|
|
||||||
|
|
||||||
const endRange = Selection.create({
|
|
||||||
anchorKey: endKey,
|
|
||||||
anchorOffset: 0,
|
|
||||||
focusKey: endKey,
|
|
||||||
focusOffset: endOffset
|
|
||||||
})
|
|
||||||
|
|
||||||
document = document.deleteAtRange(startRange)
|
|
||||||
document = document.deleteAtRange(endRange)
|
|
||||||
|
|
||||||
// Then remove any nodes in between the top-most start and end nodes...
|
|
||||||
let startParent = document.getParentNode(startKey)
|
|
||||||
let endParent = document.getParentNode(endKey)
|
|
||||||
|
|
||||||
const startGrandestParent = document.nodes.find((node) => {
|
|
||||||
return node == startParent || node.hasNode(startParent)
|
|
||||||
})
|
|
||||||
|
|
||||||
const endGrandestParent = document.nodes.find((node) => {
|
|
||||||
return node == endParent || node.hasNode(endParent)
|
|
||||||
})
|
|
||||||
|
|
||||||
const nodes = document.nodes
|
|
||||||
.takeUntil(node => node == startGrandestParent)
|
|
||||||
.set(startGrandestParent.key, startGrandestParent)
|
|
||||||
.concat(document.nodes.skipUntil(node => node == endGrandestParent))
|
|
||||||
|
|
||||||
document = document.merge({ nodes })
|
|
||||||
|
|
||||||
// Then add the end parent's nodes to the start parent node.
|
|
||||||
const newNodes = startParent.nodes.concat(endParent.nodes)
|
|
||||||
startParent = startParent.merge({ nodes: newNodes })
|
|
||||||
document = document.updateNode(startParent)
|
|
||||||
|
|
||||||
// Then remove the end parent.
|
|
||||||
let endGrandparent = document.getParentNode(endParent)
|
|
||||||
if (endGrandparent == document) {
|
|
||||||
document = document.removeNode(endParent)
|
|
||||||
} else {
|
|
||||||
endGrandparent = endGrandparent.removeNode(endParent)
|
|
||||||
document = document.updateNode(endGrandparent)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Normalize the document.
|
|
||||||
return document.normalize()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete backward `n` characters at a `range`.
|
|
||||||
*
|
|
||||||
* @param {Selection} range
|
|
||||||
* @param {Number} n (optional)
|
|
||||||
* @return {Document} document
|
|
||||||
*/
|
|
||||||
|
|
||||||
deleteBackwardAtRange(range, n = 1) {
|
|
||||||
let document = this
|
|
||||||
|
|
||||||
// When collapsed at the end of the document, there's nothing to do.
|
|
||||||
if (range.isCollapsed && range.isAtEndOf(document)) return document
|
|
||||||
|
|
||||||
// When the range is still expanded, just do a regular delete.
|
|
||||||
if (range.isExpanded) return document.deleteAtRange(range)
|
|
||||||
|
|
||||||
// When at start of a text node, merge forwards into the next text node.
|
|
||||||
const { startKey } = range
|
|
||||||
const startNode = document.getNode(startKey)
|
|
||||||
|
|
||||||
if (range.isAtStartOf(startNode)) {
|
|
||||||
const parent = document.getParentNode(startNode)
|
|
||||||
const previous = document.getPreviousNode(parent).nodes.first()
|
|
||||||
range = range.extendBackwardToEndOf(previous)
|
|
||||||
document = document.deleteAtRange(range)
|
|
||||||
return document
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise, remove `n` characters behind of the cursor.
|
|
||||||
range = range.extendBackward(n)
|
|
||||||
document = document.deleteAtRange(range)
|
|
||||||
|
|
||||||
// Normalize the document.
|
|
||||||
return document.normalize()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete forward `n` characters at a `range`.
|
|
||||||
*
|
|
||||||
* @param {Selection} range
|
|
||||||
* @param {Number} n (optional)
|
|
||||||
* @return {Document} document
|
|
||||||
*/
|
|
||||||
|
|
||||||
deleteForwardAtRange(range, n = 1) {
|
|
||||||
let document = this
|
|
||||||
|
|
||||||
// When collapsed at the end of the document, there's nothing to do.
|
|
||||||
if (range.isCollapsed && range.isAtEndOf(document)) return document
|
|
||||||
|
|
||||||
// When the range is still expanded, just do a regular delete.
|
|
||||||
if (range.isExpanded) return document.deleteAtRange(range)
|
|
||||||
|
|
||||||
// When at end of a text node, merge forwards into the next text node.
|
|
||||||
const { startKey } = range
|
|
||||||
const startNode = document.getNode(startKey)
|
|
||||||
|
|
||||||
if (range.isAtEndOf(startNode)) {
|
|
||||||
const parent = document.getParentNode(startNode)
|
|
||||||
const next = document.getNextNode(parent).nodes.first()
|
|
||||||
range = range.extendForwardToStartOf(next)
|
|
||||||
document = document.deleteAtRange(range)
|
|
||||||
return document
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise, remove `n` characters ahead of the cursor.
|
|
||||||
range = range.extendForward(n)
|
|
||||||
document = document.deleteAtRange(range)
|
|
||||||
|
|
||||||
// Normalize the document.
|
|
||||||
return document.normalize()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Insert `text` at a `range`.
|
|
||||||
*
|
|
||||||
* @param {Selection} range
|
|
||||||
* @param {String} text
|
|
||||||
* @return {Document} document
|
|
||||||
*/
|
|
||||||
|
|
||||||
insertTextAtRange(range, text) {
|
|
||||||
let document = this
|
|
||||||
|
|
||||||
// When still expanded, remove the current range first.
|
|
||||||
if (range.isExpanded) {
|
|
||||||
document = document.deleteAtRange(range)
|
|
||||||
range = range.moveToStart()
|
|
||||||
}
|
|
||||||
|
|
||||||
let { startKey, startOffset } = range
|
|
||||||
let startNode = document.getNode(startKey)
|
|
||||||
let { characters } = startNode
|
|
||||||
|
|
||||||
// Create a list of the new characters, with the right marks.
|
|
||||||
const marks = characters.has(startOffset)
|
|
||||||
? characters.get(startOffset).marks
|
|
||||||
: null
|
|
||||||
|
|
||||||
const newCharacters = text.split('').reduce((list, char) => {
|
|
||||||
const obj = { text }
|
|
||||||
if (marks) obj.marks = marks
|
|
||||||
return list.push(Character.create(obj))
|
|
||||||
}, Character.createList())
|
|
||||||
|
|
||||||
// Splice in the new characters.
|
|
||||||
const resumeOffset = startOffset + text.length - 1
|
|
||||||
characters = characters.slice(0, startOffset)
|
|
||||||
.concat(newCharacters)
|
|
||||||
.concat(characters.slice(resumeOffset, Infinity))
|
|
||||||
|
|
||||||
// Update the existing text node.
|
|
||||||
startNode = startNode.merge({ characters })
|
|
||||||
document = document.updateNode(startNode)
|
|
||||||
|
|
||||||
// Normalize the document.
|
|
||||||
return document.normalize()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Normalize the document, joining any two adjacent text nodes.
|
|
||||||
*
|
|
||||||
* @return {Document} document
|
|
||||||
*/
|
|
||||||
|
|
||||||
normalize() {
|
|
||||||
let document = this
|
|
||||||
let first = document.findNode((node) => {
|
|
||||||
if (node.type != 'text') return
|
|
||||||
const parent = document.getParentNode(node)
|
|
||||||
const next = parent.getNextNode(node)
|
|
||||||
return next && next.type == 'text'
|
|
||||||
})
|
|
||||||
|
|
||||||
// If no text node was followed by another, do nothing.
|
|
||||||
if (!first) return document
|
|
||||||
|
|
||||||
// Otherwise, add the text of the second node to the first...
|
|
||||||
let parent = document.getParentNode(first)
|
|
||||||
const second = parent.getNextNode(first)
|
|
||||||
const characters = first.characters.concat(second.characters)
|
|
||||||
first = first.merge({ characters })
|
|
||||||
parent = parent.updateNode(first)
|
|
||||||
|
|
||||||
// Then remove the second node.
|
|
||||||
parent = parent.removeNode(second)
|
|
||||||
document = document.updateNode(parent)
|
|
||||||
|
|
||||||
// Finally, recurse by normalizing again.
|
|
||||||
return document.normalize()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Split the nodes at a `range`.
|
|
||||||
*
|
|
||||||
* @param {Selection} range
|
|
||||||
* @return {Document} document
|
|
||||||
*/
|
|
||||||
|
|
||||||
splitAtRange(range) {
|
|
||||||
let document = this
|
|
||||||
|
|
||||||
// If the range is expanded, remove it first.
|
|
||||||
if (range.isExpanded) {
|
|
||||||
document = document.deleteAtRange(range)
|
|
||||||
range = range.moveToStart()
|
|
||||||
}
|
|
||||||
|
|
||||||
const { startKey, startOffset } = range
|
|
||||||
const startNode = document.getNode(startKey)
|
|
||||||
|
|
||||||
// Split the text node's characters.
|
|
||||||
const { characters, length } = startNode
|
|
||||||
const firstCharacters = characters.take(startOffset)
|
|
||||||
const secondCharacters = characters.takeLast(length - startOffset)
|
|
||||||
|
|
||||||
// Create a new first element with only the first set of characters.
|
|
||||||
const parent = document.getParentNode(startNode)
|
|
||||||
const firstText = startNode.set('characters', firstCharacters)
|
|
||||||
const firstElement = parent.updateNode(firstText)
|
|
||||||
|
|
||||||
// Create a brand new second element with the second set of characters.
|
|
||||||
let secondText = Text.create({})
|
|
||||||
let secondElement = Element.create({
|
|
||||||
type: firstElement.type,
|
|
||||||
data: firstElement.data
|
|
||||||
})
|
|
||||||
|
|
||||||
secondText = secondText.set('characters', secondCharacters)
|
|
||||||
secondElement = secondElement.pushNode(secondText)
|
|
||||||
|
|
||||||
// Replace the old parent node in the grandparent with the two new ones.
|
|
||||||
let grandparent = document.getParentNode(parent)
|
|
||||||
const befores = grandparent.nodes.takeUntil(node => node.key == parent.key)
|
|
||||||
const afters = grandparent.nodes.skipUntil(node => node.key == parent.key).rest()
|
|
||||||
const nodes = befores
|
|
||||||
.set(firstElement.key, firstElement)
|
|
||||||
.set(secondElement.key, secondElement)
|
|
||||||
.concat(afters)
|
|
||||||
|
|
||||||
// If the document is the grandparent, just merge, otherwise deep merge.
|
|
||||||
if (grandparent == document) {
|
|
||||||
document = document.merge({ nodes })
|
|
||||||
} else {
|
|
||||||
grandparent = grandparent.merge({ nodes })
|
|
||||||
document = document.updateNode(grandparent)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Normalize the document.
|
|
||||||
return document.normalize()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -42,8 +42,7 @@ class Element extends Record(DEFAULTS) {
|
|||||||
|
|
||||||
static createMap(elements = []) {
|
static createMap(elements = []) {
|
||||||
return elements.reduce((map, element) => {
|
return elements.reduce((map, element) => {
|
||||||
map = map.set(element.key, element)
|
return map.set(element.key, element)
|
||||||
return map
|
|
||||||
}, new OrderedMap())
|
}, new OrderedMap())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,7 +63,7 @@ class Element extends Record(DEFAULTS) {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
get text() {
|
get text() {
|
||||||
return this
|
return this.nodes
|
||||||
.map(node => node.text)
|
.map(node => node.text)
|
||||||
.join('')
|
.join('')
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,8 @@
|
|||||||
|
|
||||||
|
import Character from './character'
|
||||||
|
import Element from './element'
|
||||||
|
import Selection from './selection'
|
||||||
|
import Text from './text'
|
||||||
import { OrderedMap } from 'immutable'
|
import { OrderedMap } from 'immutable'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -10,6 +14,168 @@ import { OrderedMap } from 'immutable'
|
|||||||
|
|
||||||
const Node = {
|
const Node = {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete everything in a `range`.
|
||||||
|
*
|
||||||
|
* @param {Selection} range
|
||||||
|
* @return {Node} node
|
||||||
|
*/
|
||||||
|
|
||||||
|
deleteAtRange(range) {
|
||||||
|
let node = this
|
||||||
|
|
||||||
|
// If the range is collapsed, there's nothing to do.
|
||||||
|
if (range.isCollapsed) return node
|
||||||
|
|
||||||
|
// Make sure the children exist.
|
||||||
|
const { startKey, startOffset, endKey, endOffset } = range
|
||||||
|
if (!node.hasNode(startKey)) throw new Error('Could not find that start node.')
|
||||||
|
if (!node.hasNode(endKey)) throw new Error('Could not find that end node.')
|
||||||
|
|
||||||
|
let startNode = node.getNode(startKey)
|
||||||
|
|
||||||
|
// If the start and end nodes are the same, remove the matching characters.
|
||||||
|
if (startKey == endKey) {
|
||||||
|
let { characters } = startNode
|
||||||
|
|
||||||
|
characters = characters.filterNot((char, i) => {
|
||||||
|
return startOffset <= i && i < endOffset
|
||||||
|
})
|
||||||
|
|
||||||
|
startNode = startNode.merge({ characters })
|
||||||
|
node = node.updateNode(startNode)
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, remove the text from the first and last nodes...
|
||||||
|
const startRange = Selection.create({
|
||||||
|
anchorKey: startKey,
|
||||||
|
anchorOffset: startOffset,
|
||||||
|
focusKey: startKey,
|
||||||
|
focusOffset: startNode.length
|
||||||
|
})
|
||||||
|
|
||||||
|
const endRange = Selection.create({
|
||||||
|
anchorKey: endKey,
|
||||||
|
anchorOffset: 0,
|
||||||
|
focusKey: endKey,
|
||||||
|
focusOffset: endOffset
|
||||||
|
})
|
||||||
|
|
||||||
|
node = node.deleteAtRange(startRange)
|
||||||
|
node = node.deleteAtRange(endRange)
|
||||||
|
|
||||||
|
// Then remove any nodes in between the top-most start and end nodes...
|
||||||
|
let startParent = node.getParentNode(startKey)
|
||||||
|
let endParent = node.getParentNode(endKey)
|
||||||
|
|
||||||
|
const startGrandestParent = node.nodes.find((child) => {
|
||||||
|
return child == startParent || child.hasNode(startParent)
|
||||||
|
})
|
||||||
|
|
||||||
|
const endGrandestParent = node.nodes.find((child) => {
|
||||||
|
return child == endParent || child.hasNode(endParent)
|
||||||
|
})
|
||||||
|
|
||||||
|
const nodes = node.nodes
|
||||||
|
.takeUntil(child => child == startGrandestParent)
|
||||||
|
.set(startGrandestParent.key, startGrandestParent)
|
||||||
|
.concat(node.nodes.skipUntil(child => child == endGrandestParent))
|
||||||
|
|
||||||
|
node = node.merge({ nodes })
|
||||||
|
|
||||||
|
// Then add the end parent's nodes to the start parent node.
|
||||||
|
const newNodes = startParent.nodes.concat(endParent.nodes)
|
||||||
|
startParent = startParent.merge({ nodes: newNodes })
|
||||||
|
node = node.updateNode(startParent)
|
||||||
|
|
||||||
|
// Then remove the end parent.
|
||||||
|
let endGrandparent = node.getParentNode(endParent)
|
||||||
|
if (endGrandparent == node) {
|
||||||
|
node = node.removeNode(endParent)
|
||||||
|
} else {
|
||||||
|
endGrandparent = endGrandparent.removeNode(endParent)
|
||||||
|
node = node.updateNode(endGrandparent)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normalize the node.
|
||||||
|
return node.normalize()
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete backward `n` characters at a `range`.
|
||||||
|
*
|
||||||
|
* @param {Selection} range
|
||||||
|
* @param {Number} n (optional)
|
||||||
|
* @return {Node} node
|
||||||
|
*/
|
||||||
|
|
||||||
|
deleteBackwardAtRange(range, n = 1) {
|
||||||
|
let node = this
|
||||||
|
|
||||||
|
// When collapsed at the start of the node, there's nothing to do.
|
||||||
|
if (range.isCollapsed && range.isAtStartOf(node)) return node
|
||||||
|
|
||||||
|
// When the range is still expanded, just do a regular delete.
|
||||||
|
if (range.isExpanded) return node.deleteAtRange(range)
|
||||||
|
|
||||||
|
// When at start of a text node, merge forwards into the next text node.
|
||||||
|
const { startKey } = range
|
||||||
|
const startNode = node.getNode(startKey)
|
||||||
|
|
||||||
|
if (range.isAtStartOf(startNode)) {
|
||||||
|
const parent = node.getParentNode(startNode)
|
||||||
|
const previous = node.getPreviousNode(parent).nodes.first()
|
||||||
|
range = range.extendBackwardToEndOf(previous)
|
||||||
|
node = node.deleteAtRange(range)
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, remove `n` characters behind of the cursor.
|
||||||
|
range = range.extendBackward(n)
|
||||||
|
node = node.deleteAtRange(range)
|
||||||
|
|
||||||
|
// Normalize the node.
|
||||||
|
return node.normalize()
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete forward `n` characters at a `range`.
|
||||||
|
*
|
||||||
|
* @param {Selection} range
|
||||||
|
* @param {Number} n (optional)
|
||||||
|
* @return {Node} node
|
||||||
|
*/
|
||||||
|
|
||||||
|
deleteForwardAtRange(range, n = 1) {
|
||||||
|
let node = this
|
||||||
|
|
||||||
|
// When collapsed at the end of the node, there's nothing to do.
|
||||||
|
if (range.isCollapsed && range.isAtEndOf(node)) return node
|
||||||
|
|
||||||
|
// When the range is still expanded, just do a regular delete.
|
||||||
|
if (range.isExpanded) return node.deleteAtRange(range)
|
||||||
|
|
||||||
|
// When at end of a text node, merge forwards into the next text node.
|
||||||
|
const { startKey } = range
|
||||||
|
const startNode = node.getNode(startKey)
|
||||||
|
|
||||||
|
if (range.isAtEndOf(startNode)) {
|
||||||
|
const parent = node.getParentNode(startNode)
|
||||||
|
const next = node.getNextNode(parent).nodes.first()
|
||||||
|
range = range.extendForwardToStartOf(next)
|
||||||
|
node = node.deleteAtRange(range)
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, remove `n` characters ahead of the cursor.
|
||||||
|
range = range.extendForward(n)
|
||||||
|
node = node.deleteAtRange(range)
|
||||||
|
|
||||||
|
// Normalize the node.
|
||||||
|
return node.normalize()
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Recursively find nodes nodes by `iterator`.
|
* Recursively find nodes nodes by `iterator`.
|
||||||
*
|
*
|
||||||
@@ -175,6 +341,91 @@ const Node = {
|
|||||||
return false
|
return false
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Insert `text` at a `range`.
|
||||||
|
*
|
||||||
|
* @param {Selection} range
|
||||||
|
* @param {String} text
|
||||||
|
* @return {Document} node
|
||||||
|
*/
|
||||||
|
|
||||||
|
insertTextAtRange(range, text) {
|
||||||
|
let node = this
|
||||||
|
|
||||||
|
// When still expanded, remove the current range first.
|
||||||
|
if (range.isExpanded) {
|
||||||
|
node = node.deleteAtRange(range)
|
||||||
|
range = range.moveToStart()
|
||||||
|
}
|
||||||
|
|
||||||
|
let { startKey, startOffset } = range
|
||||||
|
let startNode = node.getNode(startKey)
|
||||||
|
let { characters } = startNode
|
||||||
|
|
||||||
|
// Create a list of the new characters, with the right marks.
|
||||||
|
const marks = characters.has(startOffset)
|
||||||
|
? characters.get(startOffset).marks
|
||||||
|
: null
|
||||||
|
|
||||||
|
const newCharacters = text.split('').reduce((list, char) => {
|
||||||
|
const obj = { text }
|
||||||
|
if (marks) obj.marks = marks
|
||||||
|
return list.push(Character.create(obj))
|
||||||
|
}, Character.createList())
|
||||||
|
|
||||||
|
// Splice in the new characters.
|
||||||
|
const resumeOffset = startOffset + text.length - 1
|
||||||
|
characters = characters.slice(0, startOffset)
|
||||||
|
.concat(newCharacters)
|
||||||
|
.concat(characters.slice(resumeOffset, Infinity))
|
||||||
|
|
||||||
|
// Update the existing text node.
|
||||||
|
startNode = startNode.merge({ characters })
|
||||||
|
node = node.updateNode(startNode)
|
||||||
|
|
||||||
|
// Normalize the node.
|
||||||
|
return node.normalize()
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalize the node, joining any two adjacent text child nodes.
|
||||||
|
*
|
||||||
|
* @return {Node} node
|
||||||
|
*/
|
||||||
|
|
||||||
|
normalize() {
|
||||||
|
let node = this
|
||||||
|
let first = node.findNode((child) => {
|
||||||
|
if (child.type != 'text') return
|
||||||
|
const parent = node.getParentNode(child)
|
||||||
|
const next = parent.getNextNode(child)
|
||||||
|
return next && next.type == 'text'
|
||||||
|
})
|
||||||
|
|
||||||
|
// If no text node was followed by another, do nothing.
|
||||||
|
if (!first) return node
|
||||||
|
|
||||||
|
// Otherwise, add the text of the second node to the first...
|
||||||
|
let parent = node.getParentNode(first)
|
||||||
|
const second = parent.getNextNode(first)
|
||||||
|
const characters = first.characters.concat(second.characters)
|
||||||
|
first = first.merge({ characters })
|
||||||
|
parent = parent.updateNode(first)
|
||||||
|
|
||||||
|
// Then remove the second node.
|
||||||
|
parent = parent.removeNode(second)
|
||||||
|
|
||||||
|
// If the parent isn't this node, it needs to be updated.
|
||||||
|
if (parent != node) {
|
||||||
|
node = node.updateNode(parent)
|
||||||
|
} else {
|
||||||
|
node = parent
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally, recurse by normalizing again.
|
||||||
|
return node.normalize()
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Push a new `node` onto the map of nodes.
|
* Push a new `node` onto the map of nodes.
|
||||||
*
|
*
|
||||||
@@ -209,6 +460,66 @@ const Node = {
|
|||||||
return this.merge({ nodes })
|
return this.merge({ nodes })
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Split the nodes at a `range`.
|
||||||
|
*
|
||||||
|
* @param {Selection} range
|
||||||
|
* @return {Node} node
|
||||||
|
*/
|
||||||
|
|
||||||
|
splitAtRange(range) {
|
||||||
|
let node = this
|
||||||
|
|
||||||
|
// If the range is expanded, remove it first.
|
||||||
|
if (range.isExpanded) {
|
||||||
|
node = node.deleteAtRange(range)
|
||||||
|
range = range.moveToStart()
|
||||||
|
}
|
||||||
|
|
||||||
|
const { startKey, startOffset } = range
|
||||||
|
const startNode = node.getNode(startKey)
|
||||||
|
|
||||||
|
// Split the text node's characters.
|
||||||
|
const { characters, length } = startNode
|
||||||
|
const firstCharacters = characters.take(startOffset)
|
||||||
|
const secondCharacters = characters.takeLast(length - startOffset)
|
||||||
|
|
||||||
|
// Create a new first element with only the first set of characters.
|
||||||
|
const parent = node.getParentNode(startNode)
|
||||||
|
const firstText = startNode.set('characters', firstCharacters)
|
||||||
|
const firstElement = parent.updateNode(firstText)
|
||||||
|
|
||||||
|
// Create a brand new second element with the second set of characters.
|
||||||
|
let secondText = Text.create({})
|
||||||
|
let secondElement = Element.create({
|
||||||
|
type: firstElement.type,
|
||||||
|
data: firstElement.data
|
||||||
|
})
|
||||||
|
|
||||||
|
secondText = secondText.set('characters', secondCharacters)
|
||||||
|
secondElement = secondElement.pushNode(secondText)
|
||||||
|
|
||||||
|
// Replace the old parent node in the grandparent with the two new ones.
|
||||||
|
let grandparent = node.getParentNode(parent)
|
||||||
|
const befores = grandparent.nodes.takeUntil(child => child.key == parent.key)
|
||||||
|
const afters = grandparent.nodes.skipUntil(child => child.key == parent.key).rest()
|
||||||
|
const nodes = befores
|
||||||
|
.set(firstElement.key, firstElement)
|
||||||
|
.set(secondElement.key, secondElement)
|
||||||
|
.concat(afters)
|
||||||
|
|
||||||
|
// If the node is the grandparent, just merge, otherwise deep merge.
|
||||||
|
if (grandparent == node) {
|
||||||
|
node = node.merge({ nodes })
|
||||||
|
} else {
|
||||||
|
grandparent = grandparent.merge({ nodes })
|
||||||
|
node = node.updateNode(grandparent)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normalize the node.
|
||||||
|
return node.normalize()
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set a new value for a child node by `key`.
|
* Set a new value for a child node by `key`.
|
||||||
*
|
*
|
||||||
|
Reference in New Issue
Block a user