mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-07-31 12:30:11 +02:00
big refactor and cleanup of transforms
This commit is contained in:
@@ -253,7 +253,7 @@ class Selection extends SelectionRecord {
|
|||||||
/**
|
/**
|
||||||
* Move the selection forward `n` characters.
|
* Move the selection forward `n` characters.
|
||||||
*
|
*
|
||||||
* @param {Number} n
|
* @param {Number} n (optional)
|
||||||
* @return {Selection} selection
|
* @return {Selection} selection
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -271,7 +271,7 @@ class Selection extends SelectionRecord {
|
|||||||
/**
|
/**
|
||||||
* Move the selection backward `n` characters.
|
* Move the selection backward `n` characters.
|
||||||
*
|
*
|
||||||
* @param {Number} n
|
* @param {Number} n (optional)
|
||||||
* @return {Selection} selection
|
* @return {Selection} selection
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -286,6 +286,118 @@ class Selection extends SelectionRecord {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extend the focus point forward `n` characters.
|
||||||
|
*
|
||||||
|
* @param {Number} n (optional)
|
||||||
|
* @return {Selection} selection
|
||||||
|
*/
|
||||||
|
|
||||||
|
extendForward(n = 1) {
|
||||||
|
if (!this.isCollapsed) {
|
||||||
|
throw new Error('The selection must be collapsed before extending.')
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.merge({
|
||||||
|
focusOffset: this.focusOffset + n,
|
||||||
|
isBackward: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extend the focus point backward `n` characters.
|
||||||
|
*
|
||||||
|
* @param {Number} n (optional)
|
||||||
|
* @return {Selection} selection
|
||||||
|
*/
|
||||||
|
|
||||||
|
extendBackward(n = 1) {
|
||||||
|
if (!this.isCollapsed) {
|
||||||
|
throw new Error('The selection must be collapsed before extending.')
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.merge({
|
||||||
|
focusOffset: this.focusOffset - n,
|
||||||
|
isBackward: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extend the focus forward to the start of a `node`.
|
||||||
|
*
|
||||||
|
* @param {Node} node
|
||||||
|
* @return {Selection} selection
|
||||||
|
*/
|
||||||
|
|
||||||
|
extendForwardToStartOf(node) {
|
||||||
|
if (!this.isCollapsed) {
|
||||||
|
throw new Error('The selection must be collapsed before extending.')
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.merge({
|
||||||
|
focusKey: node.key,
|
||||||
|
focusOffset: 0,
|
||||||
|
isBackward: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extend the focus backward to the start of a `node`.
|
||||||
|
*
|
||||||
|
* @param {Node} node
|
||||||
|
* @return {Selection} selection
|
||||||
|
*/
|
||||||
|
|
||||||
|
extendBackwardToStartOf(node) {
|
||||||
|
if (!this.isCollapsed) {
|
||||||
|
throw new Error('The selection must be collapsed before extending.')
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.merge({
|
||||||
|
focusKey: node.key,
|
||||||
|
focusOffset: 0,
|
||||||
|
isBackward: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extend the focus forward to the end of a `node`.
|
||||||
|
*
|
||||||
|
* @param {Node} node
|
||||||
|
* @return {Selection} selection
|
||||||
|
*/
|
||||||
|
|
||||||
|
extendForwardToEndOf(node) {
|
||||||
|
if (!this.isCollapsed) {
|
||||||
|
throw new Error('The selection must be collapsed before extending.')
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.merge({
|
||||||
|
focusKey: node.key,
|
||||||
|
focusOffset: node.length,
|
||||||
|
isBackward: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extend the focus backward to the end of a `node`.
|
||||||
|
*
|
||||||
|
* @param {Node} node
|
||||||
|
* @return {Selection} selection
|
||||||
|
*/
|
||||||
|
|
||||||
|
extendBackwardToEndOf(node) {
|
||||||
|
if (!this.isCollapsed) {
|
||||||
|
throw new Error('The selection must be collapsed before extending.')
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.merge({
|
||||||
|
focusKey: node.key,
|
||||||
|
focusOffset: node.length,
|
||||||
|
isBackward: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -46,7 +46,9 @@ const SELECTION_LIKE_METHODS = [
|
|||||||
'moveToEndOf',
|
'moveToEndOf',
|
||||||
'moveToRangeOf',
|
'moveToRangeOf',
|
||||||
'moveForward',
|
'moveForward',
|
||||||
'moveBackward'
|
'moveBackward',
|
||||||
|
'extendForward',
|
||||||
|
'extendBackward'
|
||||||
]
|
]
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -196,51 +198,6 @@ class State extends StateRecord {
|
|||||||
return this.selection.isAtEndOf(node)
|
return this.selection.isAtEndOf(node)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Backspace.
|
|
||||||
*
|
|
||||||
* @return {State} state
|
|
||||||
*/
|
|
||||||
|
|
||||||
backspace() {
|
|
||||||
let state = this
|
|
||||||
|
|
||||||
// When not collapsed, remove the entire selection.
|
|
||||||
if (!state.isCollapsed) {
|
|
||||||
state = state.removeRange()
|
|
||||||
state = state.moveToStart()
|
|
||||||
return state
|
|
||||||
}
|
|
||||||
|
|
||||||
// When already at the start of the content, there's nothing to do.
|
|
||||||
if (state.isAtStartOf(state)) return state
|
|
||||||
|
|
||||||
// When at start of a node, merge backwards into the previous node.
|
|
||||||
const { startNode } = state
|
|
||||||
if (state.isAtStartOf(startNode)) {
|
|
||||||
const { selection, startOffset } = state
|
|
||||||
const parent = state.getParentNode(startNode)
|
|
||||||
const previous = state.getPreviousNode(parent).nodes.first()
|
|
||||||
const range = selection.merge({
|
|
||||||
anchorKey: previous.key,
|
|
||||||
anchorOffset: previous.length,
|
|
||||||
focusKey: startNode.key,
|
|
||||||
focusOffset: 0,
|
|
||||||
isBackward: false
|
|
||||||
})
|
|
||||||
|
|
||||||
state = state.removeRange(range)
|
|
||||||
return state
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise, remove one character behind of the cursor.
|
|
||||||
const { endOffset } = state
|
|
||||||
const startOffset = endOffset - 1
|
|
||||||
state = state.removeCharacters(startNode.key, startOffset, endOffset)
|
|
||||||
state = state.moveBackward()
|
|
||||||
return state
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete a single character.
|
* Delete a single character.
|
||||||
*
|
*
|
||||||
@@ -250,73 +207,260 @@ class State extends StateRecord {
|
|||||||
delete() {
|
delete() {
|
||||||
let state = this
|
let state = this
|
||||||
|
|
||||||
// When not collapsed, remove the entire selection range.
|
// When collapsed, there's nothing to do.
|
||||||
if (!state.isCollapsed) {
|
if (state.isCollapsed) return state
|
||||||
state = state.removeRange()
|
|
||||||
state = state.moveToStart()
|
|
||||||
return state
|
|
||||||
}
|
|
||||||
|
|
||||||
// When already at the end of the content, there's nothing to do.
|
// Otherwise, delete and update the selection.
|
||||||
if (state.isAtEndOf(state)) return state
|
state = state.deleteAtRange(state.selection)
|
||||||
|
state = state.moveToStart()
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
|
||||||
// When at end of a node, merge forwards into the next node.
|
/**
|
||||||
const { startNode } = state
|
* Delete everything in a `range`.
|
||||||
if (state.isAtEndOf(startNode)) {
|
*
|
||||||
const { selection, startOffset } = state
|
* @param {Selection} range
|
||||||
const parent = state.getParentNode(startNode)
|
* @return {State} state
|
||||||
const next = state.getNextNode(parent).nodes.first()
|
*/
|
||||||
const range = selection.merge({
|
|
||||||
anchorKey: startNode.key,
|
deleteAtRange(range) {
|
||||||
anchorOffset: startNode.length,
|
let state = this
|
||||||
focusKey: next.key,
|
|
||||||
focusOffset: 0,
|
// If the range is collapsed, there's nothing to do.
|
||||||
isBackward: false
|
if (range.isCollapsed) return state
|
||||||
|
|
||||||
|
const { startKey, startOffset, endKey, endOffset } = range
|
||||||
|
let startNode = state.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
|
||||||
})
|
})
|
||||||
|
|
||||||
state = state.removeRange(range)
|
startNode = startNode.merge({ characters })
|
||||||
|
state = state.updateNode(startNode)
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise, remove one character ahead of the cursor.
|
// Otherwise, remove the text from the first and last nodes...
|
||||||
const { startOffset } = state
|
const startRange = Selection.create({
|
||||||
const endOffset = startOffset + 1
|
anchorKey: startKey,
|
||||||
state = state.removeCharacters(startNode.key, startOffset, endOffset)
|
anchorOffset: startOffset,
|
||||||
|
focusKey: startKey,
|
||||||
|
focusOffset: startNode.length
|
||||||
|
})
|
||||||
|
|
||||||
|
const endRange = Selection.create({
|
||||||
|
anchorKey: endKey,
|
||||||
|
anchorOffset: 0,
|
||||||
|
focusKey: endKey,
|
||||||
|
focusOffset: endOffset
|
||||||
|
})
|
||||||
|
|
||||||
|
state = state.deleteAtRange(startRange)
|
||||||
|
state = state.deleteAtRange(endRange)
|
||||||
|
|
||||||
|
// Then remove any nodes in between the top-most start and end nodes...
|
||||||
|
let startParent = state.getParentNode(startKey)
|
||||||
|
let endParent = state.getParentNode(endKey)
|
||||||
|
|
||||||
|
const startGrandestParent = state.nodes.find((node) => {
|
||||||
|
return node == startParent || node.hasNode(startParent)
|
||||||
|
})
|
||||||
|
|
||||||
|
const endGrandestParent = state.nodes.find((node) => {
|
||||||
|
return node == endParent || node.hasNode(endParent)
|
||||||
|
})
|
||||||
|
|
||||||
|
const nodes = state.nodes
|
||||||
|
.takeUntil(node => node == startGrandestParent)
|
||||||
|
.set(startGrandestParent.key, startGrandestParent)
|
||||||
|
.concat(state.nodes.skipUntil(node => node == endGrandestParent))
|
||||||
|
|
||||||
|
state = state.merge({ nodes })
|
||||||
|
|
||||||
|
// Then bring the end text node into the start node.
|
||||||
|
let endText = state.getNode(endKey)
|
||||||
|
startParent = startParent.pushNode(endText)
|
||||||
|
endParent = endParent.removeNode(endText)
|
||||||
|
state = state.updateNode(startParent)
|
||||||
|
state = state.updateNode(endParent)
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete backward `n` characters at the current selection.
|
||||||
|
*
|
||||||
|
* @param {Number} n (optional)
|
||||||
|
* @return {State} state
|
||||||
|
*/
|
||||||
|
|
||||||
|
deleteBackward(n = 1) {
|
||||||
|
let state = this
|
||||||
|
let selection = state.selection
|
||||||
|
|
||||||
|
// Determine what the selection should be after deleting.
|
||||||
|
const startNode = state.startNode
|
||||||
|
|
||||||
|
if (state.isCollapsed && state.isAtStartOf(startNode)) {
|
||||||
|
const parent = state.getParentNode(startNode)
|
||||||
|
const previous = state.getPreviousNode(parent).nodes.first()
|
||||||
|
selection = selection.moveToEndOf(previous)
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (state.isCollapsed && !state.isAtEndOf(state)) {
|
||||||
|
selection = selection.moveBackward(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete backward and then update the selection.
|
||||||
|
state = state.deleteBackwardAtRange(state.selection)
|
||||||
|
state = state.merge({ selection })
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete backward `n` characters at a `range`.
|
||||||
|
*
|
||||||
|
* @param {Selection} range
|
||||||
|
* @param {Number} n (optional)
|
||||||
|
* @return {State} state
|
||||||
|
*/
|
||||||
|
|
||||||
|
deleteBackwardAtRange(range, n = 1) {
|
||||||
|
let state = this
|
||||||
|
|
||||||
|
// When collapsed at the end of the document, there's nothing to do.
|
||||||
|
if (range.isCollapsed && range.isAtEndOf(state)) return state
|
||||||
|
|
||||||
|
// When the range is still expanded, just do a regular delete.
|
||||||
|
if (range.isExpanded) return state.deleteAtRange(range)
|
||||||
|
|
||||||
|
// When at start of a text node, merge forwards into the next text node.
|
||||||
|
const { startKey } = range
|
||||||
|
const startNode = state.getNode(startKey)
|
||||||
|
|
||||||
|
if (range.isAtStartOf(startNode)) {
|
||||||
|
const parent = state.getParentNode(startNode)
|
||||||
|
const previous = state.getPreviousNode(parent).nodes.first()
|
||||||
|
range = range.extendBackwardToEndOf(previous)
|
||||||
|
state = state.deleteAtRange(range)
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, remove `n` characters behind of the cursor.
|
||||||
|
range = range.extendBackward(n)
|
||||||
|
state = state.deleteAtRange(range)
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete forward `n` characters at the current selection.
|
||||||
|
*
|
||||||
|
* @param {Number} n (optional)
|
||||||
|
* @return {State} state
|
||||||
|
*/
|
||||||
|
|
||||||
|
deleteForward(n = 1) {
|
||||||
|
let state = this
|
||||||
|
state = state.deleteForwardAtRange(state.selection)
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete forward `n` characters at a `range`.
|
||||||
|
*
|
||||||
|
* @param {Selection} range
|
||||||
|
* @param {Number} n (optional)
|
||||||
|
* @return {State} state
|
||||||
|
*/
|
||||||
|
|
||||||
|
deleteForwardAtRange(range, n = 1) {
|
||||||
|
let state = this
|
||||||
|
|
||||||
|
// When collapsed at the end of the document, there's nothing to do.
|
||||||
|
if (range.isCollapsed && range.isAtEndOf(state)) return state
|
||||||
|
|
||||||
|
// When the range is still expanded, just do a regular delete.
|
||||||
|
if (range.isExpanded) return state.deleteAtRange(range)
|
||||||
|
|
||||||
|
// When at end of a text node, merge forwards into the next text node.
|
||||||
|
const { startKey } = range
|
||||||
|
const startNode = state.getNode(startKey)
|
||||||
|
|
||||||
|
if (range.isAtEndOf(startNode)) {
|
||||||
|
const parent = state.getParentNode(startNode)
|
||||||
|
const next = state.getNextNode(parent).nodes.first()
|
||||||
|
range = range.extendForwardToStartOf(next)
|
||||||
|
state = state.deleteAtRange(range)
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, remove `n` characters ahead of the cursor.
|
||||||
|
range = range.extendForward(n)
|
||||||
|
state = state.deleteAtRange(range)
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Insert a `text` string at the current cursor position.
|
* Insert a `text` string at the current cursor position.
|
||||||
*
|
*
|
||||||
* @param {String} text
|
* @param {String or Node or OrderedMap} data
|
||||||
* @return {State} state
|
* @return {State} state
|
||||||
*/
|
*/
|
||||||
|
|
||||||
insert(text) {
|
insert(data) {
|
||||||
|
let state = this
|
||||||
|
state = state.insertAtRange(state.selection, data)
|
||||||
|
|
||||||
|
// When the data is a string of characters...
|
||||||
|
if (typeof data == 'string') {
|
||||||
|
state = state.moveForward(data.length)
|
||||||
|
}
|
||||||
|
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Insert `data` at a `range`.
|
||||||
|
*
|
||||||
|
* @param {Selection} range
|
||||||
|
* @param {String or Node or OrderedMap} data
|
||||||
|
* @return {State} state
|
||||||
|
*/
|
||||||
|
|
||||||
|
insertAtRange(range, data) {
|
||||||
let state = this
|
let state = this
|
||||||
|
|
||||||
// When still expanded, remove the current range first.
|
// When still expanded, remove the current range first.
|
||||||
if (state.isExpanded) {
|
if (range.isExpanded) {
|
||||||
state = state.delete()
|
state = state.deleteAtRange(range)
|
||||||
|
range = range.moveToStart()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert text at the current cursor.
|
// When the data is a string of characters...
|
||||||
const ranges = [{ text }]
|
if (typeof data == 'string') {
|
||||||
let { startNode, startOffset } = state
|
|
||||||
let { characters } = startNode
|
|
||||||
let newCharacters = convertRangesToCharacters(ranges)
|
|
||||||
const { size } = newCharacters
|
|
||||||
|
|
||||||
// Splice in the new characters.
|
// Insert text at the current cursor.
|
||||||
characters = characters.slice(0, startOffset)
|
const ranges = [{ text: data }]
|
||||||
.concat(newCharacters)
|
let { startNode, startOffset } = state
|
||||||
.concat(characters.slice(startOffset + size - 1, Infinity))
|
let { characters } = startNode
|
||||||
|
let newCharacters = convertRangesToCharacters(ranges)
|
||||||
|
const { size } = newCharacters
|
||||||
|
|
||||||
|
// Splice in the new characters.
|
||||||
|
characters = characters.slice(0, startOffset)
|
||||||
|
.concat(newCharacters)
|
||||||
|
.concat(characters.slice(startOffset + size - 1, Infinity))
|
||||||
|
|
||||||
|
// Update the existing text node.
|
||||||
|
startNode = startNode.merge({ characters })
|
||||||
|
state = state.updateNode(startNode)
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
|
||||||
// Update the existing text node and the selection.
|
|
||||||
startNode = startNode.merge({ characters })
|
|
||||||
state = state.updateNode(startNode)
|
|
||||||
state = state.moveForward(size)
|
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -360,40 +504,45 @@ class State extends StateRecord {
|
|||||||
|
|
||||||
split() {
|
split() {
|
||||||
let state = this
|
let state = this
|
||||||
state = state.splitRange()
|
state = state.splitAtRange(state.selection)
|
||||||
|
|
||||||
const parent = state.getParentNode(state.startKey)
|
const parent = state.getParentNode(state.startNode)
|
||||||
const next = state.getNextNode(parent)
|
const next = state.getNextNode(parent)
|
||||||
const text = next.nodes.first()
|
const text = next.nodes.first()
|
||||||
state = state.moveToStartOf(text)
|
state = state.moveToStartOf(text)
|
||||||
|
|
||||||
|
// const next = state.getNextTextNode(state.startNode)
|
||||||
|
// state = state.moveToStartOf(next)
|
||||||
|
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Split the nodes at a `selection`.
|
* Split the nodes at a `range`.
|
||||||
*
|
*
|
||||||
* @param {Selection} selection (optional)
|
* @param {Selection} range
|
||||||
* @return {State} state
|
* @return {State} state
|
||||||
*/
|
*/
|
||||||
|
|
||||||
splitRange(selection = this.selection) {
|
splitAtRange(range) {
|
||||||
let state = this
|
let state = this
|
||||||
|
|
||||||
// If there's an existing selection, remove it first.
|
// If the range is expanded, remove it first.
|
||||||
if (!selection.isCollapsed) {
|
if (range.isExpanded) {
|
||||||
state = state.removeRange(selection)
|
state = state.deleteAtRange(range)
|
||||||
selection = selection.moveToStart()
|
range = range.moveToStart()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { startKey, startOffset } = range
|
||||||
|
const startNode = state.getNode(startKey)
|
||||||
|
|
||||||
// Split the text node's characters.
|
// Split the text node's characters.
|
||||||
const { startNode, startOffset } = state
|
const { characters, length } = startNode
|
||||||
const parent = state.getParentNode(startNode)
|
|
||||||
const { characters , length } = startNode
|
|
||||||
const firstCharacters = characters.take(startOffset)
|
const firstCharacters = characters.take(startOffset)
|
||||||
const secondCharacters = characters.takeLast(length - startOffset)
|
const secondCharacters = characters.takeLast(length - startOffset)
|
||||||
|
|
||||||
// Create a new first node with only the first set of characters.
|
// Create a new first node with only the first set of characters.
|
||||||
|
const parent = state.getParentNode(startNode)
|
||||||
const firstText = startNode.set('characters', firstCharacters)
|
const firstText = startNode.set('characters', firstCharacters)
|
||||||
const firstNode = parent.updateNode(firstText)
|
const firstNode = parent.updateNode(firstText)
|
||||||
|
|
||||||
@@ -416,6 +565,7 @@ class State extends StateRecord {
|
|||||||
.set(secondNode.key, secondNode)
|
.set(secondNode.key, secondNode)
|
||||||
.concat(afters)
|
.concat(afters)
|
||||||
|
|
||||||
|
// If the state is the grandparent, just merge, otherwise deep merge.
|
||||||
if (grandparent == state) {
|
if (grandparent == state) {
|
||||||
state = state.merge({ nodes })
|
state = state.merge({ nodes })
|
||||||
} else {
|
} else {
|
||||||
@@ -426,52 +576,6 @@ class State extends StateRecord {
|
|||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Merge the nodes between `selection`.
|
|
||||||
*
|
|
||||||
* @param {Selection} selection (optional)
|
|
||||||
* @return {State} state
|
|
||||||
*/
|
|
||||||
|
|
||||||
removeRange(selection = this.selection) {
|
|
||||||
let state = this
|
|
||||||
|
|
||||||
// If the selection is collapsed, there's nothing to do.
|
|
||||||
if (selection.isCollapsed) return state
|
|
||||||
|
|
||||||
// If the start and end nodes are the same, just remove the matching text.
|
|
||||||
const { startKey, startOffset, endKey, endOffset } = selection
|
|
||||||
if (startKey == endKey) {
|
|
||||||
return state.removeCharacters(startKey, startOffset, endOffset)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise, remove the text from the first and last nodes...
|
|
||||||
let startText = state.getNode(startKey)
|
|
||||||
state = state.removeCharacters(startKey, startOffset, startText.length)
|
|
||||||
state = state.removeCharacters(endKey, 0, endOffset)
|
|
||||||
|
|
||||||
// Then remove any nodes in between the top-most start and end nodes...
|
|
||||||
let startNode = state.getParentNode(startKey)
|
|
||||||
let endNode = state.getParentNode(endKey)
|
|
||||||
const startParent = state.nodes.find(node => node == startNode || node.hasNode(startNode))
|
|
||||||
const endParent = state.nodes.find(node => node == endNode || node.hasNode(endNode))
|
|
||||||
|
|
||||||
const nodes = state.nodes
|
|
||||||
.takeUntil(node => node == startParent)
|
|
||||||
.set(startParent.key, startParent)
|
|
||||||
.concat(state.nodes.skipUntil(node => node == endParent))
|
|
||||||
|
|
||||||
state = state.merge({ nodes })
|
|
||||||
|
|
||||||
// Then bring the end text node into the start node.
|
|
||||||
let endText = state.getNode(endKey)
|
|
||||||
startNode = startNode.pushNode(endText)
|
|
||||||
endNode = endNode.removeNode(endText)
|
|
||||||
state = state.updateNode(startNode)
|
|
||||||
state = state.updateNode(endNode)
|
|
||||||
return state
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -32,7 +32,7 @@ const CORE_PLUGIN = {
|
|||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
return isWord(e)
|
return isWord(e)
|
||||||
? state.backspaceWord()
|
? state.backspaceWord()
|
||||||
: state.backspace()
|
: state.deleteBackward()
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'delete': {
|
case 'delete': {
|
||||||
@@ -41,7 +41,7 @@ const CORE_PLUGIN = {
|
|||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
return isWord(e)
|
return isWord(e)
|
||||||
? state.deleteWord()
|
? state.deleteWord()
|
||||||
: state.delete()
|
: state.deleteForward()
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'y': {
|
case 'y': {
|
||||||
|
Reference in New Issue
Block a user