1
0
mirror of https://github.com/ianstormtaylor/slate.git synced 2025-04-21 13:51:59 +02:00

add split inline with tests

This commit is contained in:
Ian Storm Taylor 2016-06-23 09:15:51 -07:00
parent 24dd5ba34c
commit 1c68ab5d5c
22 changed files with 528 additions and 142 deletions

View File

@ -237,10 +237,7 @@ const Node = {
getBlocksAtRange(range) {
range = range.normalize(this)
const texts = this.getTextsAtRange(range)
const blocks = texts.map((text) => {
return this.getClosest(text, p => p.kind == 'block')
})
const blocks = texts.map(text => this.getClosestBlock(text))
return blocks
},
@ -266,23 +263,80 @@ const Node = {
},
/**
* Get closest parent of `node` that matches `iterator`.
* Get closest parent of node by `key` that matches `iterator`.
*
* @param {Node} node
* @param {String or Node} key
* @param {Function} iterator
* @return {Node or Null} node
* @return {Node or Null} parent
*/
getClosest(node, iterator) {
while (node) {
getClosest(key, iterator) {
key = normalizeKey(key)
this.assertHasDeep(key)
let node = this.getDeep(key)
while (node = this.getParent(node)) {
if (node == this) return null
if (iterator(node)) return node
node = this.getParent(node)
}
return null
},
/**
* Get the closest block parent of a `node`.
*
* @param {String or Node} key
* @return {Node or Null} parent
*/
getClosestBlock(key) {
key = normalizeKey(key)
this.assertHasDeep(key)
const match = this.getClosest(key, parent => parent.kind == 'block')
return match
},
/**
* Get the closest inline parent of a `node`.
*
* @param {String or Node} key
* @return {Node or Null} parent
*/
getClosestInline(key) {
key = normalizeKey(key)
this.assertHasDeep(key)
const match = this.getClosest(key, parent => parent.kind == 'inline')
return match
},
/**
* Get the furthest inline parent of a node by `key`.
*
* @param {String or Node} key
* @return {Node or Null} parent
*/
getFurthestInline(key) {
key = normalizeKey(key)
this.assertHasDeep(key)
let child = this.getDeep(key)
let furthest = null
let next
while (next = this.getClosestInline(child)) {
furthest = next
child = next
}
return furthest
},
/**
* Get a child node by `key`.
*
@ -306,6 +360,8 @@ const Node = {
getDepth(key, startAt = 1) {
key = normalizeKey(key)
this.assertHasDeep(key)
if (this.nodes.has(key)) return startAt
const child = this.nodes.find(node => {
@ -407,6 +463,7 @@ const Node = {
getNextSibling(key) {
key = normalizeKey(key)
this.assertHasDeep(key)
if (this.nodes.has(key)) {
return this.nodes
@ -430,6 +487,8 @@ const Node = {
getNextText(key) {
key = normalizeKey(key)
this.assertHasDeep(key)
return this.getTextNodes()
.skipUntil(text => text.key == key)
.take(2)
@ -444,7 +503,9 @@ const Node = {
*/
getOffset(key) {
key = normalizeKey(key)
this.assertHasDeep(key)
const match = this.getDeep(key)
// Find the shallow matching child.
@ -479,8 +540,9 @@ const Node = {
getParent(key) {
key = normalizeKey(key)
// this.assertHasDeep(key)
if (this.nodes.get(key)) return this
if (this.nodes.has(key)) return this
let node = null
this.nodes.forEach((child) => {
@ -501,6 +563,7 @@ const Node = {
getPreviousSibling(key) {
key = normalizeKey(key)
this.assertHasDeep(key)
if (this.nodes.has(key)) {
return this.nodes
@ -523,6 +586,8 @@ const Node = {
getPreviousText(key) {
key = normalizeKey(key)
this.assertHasDeep(key)
return this.getTextNodes()
.takeUntil(text => text.key == key)
.last()
@ -596,6 +661,7 @@ const Node = {
hasDeep(key) {
key = normalizeKey(key)
return !! this.nodes.find((node) => {
return node.kind == 'text'
? node.key == key
@ -762,6 +828,7 @@ const Node = {
removeDeep(key) {
key = normalizeKey(key)
this.assertHasDeep(key)
let nodes = this.nodes.remove(key)
return this.merge({ nodes })
@ -854,52 +921,134 @@ const Node = {
range = range.moveToStart()
}
const { startKey, startOffset } = range
const startNode = node.getDeep(startKey)
// Split the inline nodes at the range.
node = node.splitInlineAtRange(range)
// Split the text node's characters.
const { characters, length } = startNode
const firstCharacters = characters.take(startOffset)
const secondCharacters = characters.takeLast(length - startOffset)
// Find the highest inline elements that were split.
const { startKey } = range
const firstText = node.getDeep(startKey)
const firstChild = node.getFurthestInline(firstText) || firstText
const secondText = node.getNextText(startKey)
const secondChild = node.getFurthestInline(secondText) || secondText
// Create a new first element with only the first set of characters.
const parent = node.getParent(startNode)
const firstText = startNode.set('characters', firstCharacters)
const firstElement = parent.updateDeep(firstText)
// Remove the second inline child from the first block.
let firstBlock = node.getBlocksAtRange(range).first()
firstBlock = firstBlock.removeDeep(secondChild)
// Create a brand new second element with the second set of characters.
let secondText = Text.create({})
let secondElement = Block.create({
type: firstElement.type,
data: firstElement.data
// Create a new block with the second inline child in it.
const secondBlock = Block.create({
type: firstBlock.type,
data: firstBlock.data,
nodes: Block.createMap([secondChild])
})
secondText = secondText.set('characters', secondCharacters)
secondElement = secondElement.merge({
nodes: secondElement.nodes.set(secondText.key, secondText)
});
// Replace the block in the parent with the two new blocks.
let parent = node.getParent(firstBlock)
const nodes = parent.nodes.takeUntil(n => n.key == firstBlock.key)
.set(firstBlock.key, firstBlock)
.set(secondBlock.key, secondBlock)
.concat(parent.nodes.skipUntil(n => n.key == firstBlock.key).rest())
// Replace the old parent node in the grandparent with the two new ones.
let grandparent = node.getParent(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) {
// If the node is the parent, just merge, otherwise deep merge.
if (parent == node) {
node = node.merge({ nodes })
} else {
grandparent = grandparent.merge({ nodes })
node = node.updateDeep(grandparent)
parent = parent.merge({ nodes })
node = node.updateDeep(parent)
}
// Normalize the node.
return node.normalize()
},
splitInlineAtRange(range) {
debugger
range = range.normalize(this)
const Inline = require('./inline').default
let node = this
// If the range is expanded, remove it first.
if (range.isExpanded) {
node = node.deleteAtRange(range)
range = range.moveToStart()
}
// First split the text nodes.
node = node.splitTextAtRange(range)
let firstChild = node.getDeep(range.startKey)
let secondChild = node.getNextText(firstChild)
let parent
// While the parent is an inline parent, split the inline nodes.
while (parent = node.getClosestInline(firstChild)) {
debugger
const firstNodes = Inline.createMap([firstChild])
const secondNodes = Inline.createMap([secondChild])
firstChild = parent.merge({ nodes: firstNodes })
secondChild = Inline.create({
nodes: secondNodes,
type: parent.type,
data: parent.data
})
// Split the children.
const isGrandparent = node.nodes.has(parent.key)
const grandparent = isGrandparent ? node : node.getParent(parent)
const nodes = grandparent.nodes
.takeUntil(c => c.key == firstChild.key)
.set(firstChild.key, firstChild)
.set(secondChild.key, secondChild)
.concat(grandparent.nodes.skipUntil(n => n.key == firstChild.key).rest())
// Update the grandparent.
node = isGrandparent
? node.merge({ nodes })
: node.updateDeep(grandparent.merge({ nodes }))
}
return node
},
/**
* Split the text nodes at a `range`.
*
* @param {Selection} range
* @return {Node} node
*/
splitTextAtRange(range) {
range = range.normalize(this)
let node = this
// If the range is expanded, remove it first.
if (range.isExpanded) {
node = node.deleteAtRange(range)
range = range.moveToStart()
}
// Split the text node's characters.
const { startKey, startOffset } = range
const text = node.getDeep(startKey)
const { characters } = text
const firstChars = characters.take(startOffset)
const secondChars = characters.skip(startOffset)
let firstChild = text.merge({ characters: firstChars })
let secondChild = Text.create({ characters: secondChars })
// Split the text nodes.
let parent = node.getParent(text)
const nodes = parent.nodes
.takeUntil(c => c.key == firstChild.key)
.set(firstChild.key, firstChild)
.set(secondChild.key, secondChild)
.concat(parent.nodes.skipUntil(n => n.key == firstChild.key).rest())
// Update the nodes.
parent = parent.merge({ nodes })
node = node.updateDeep(parent)
return node
},
/**
* Remove an existing `mark` to the characters at `range`.
*
@ -957,6 +1106,8 @@ const Node = {
key = normalizeKey(key)
}
// this.assertHasDeep(key)
if (this.nodes.get(key)) {
const nodes = this.nodes.set(key, node)
return this.set('nodes', nodes)
@ -1091,105 +1242,6 @@ const Node = {
getClosestInline(child) {
return this.getClosest(child, p => p.kind == 'inline')
},
getFurthestInline(child) {
let furthest = null
let next
while (next = this.getClosestInline(child)) {
furthest = next
child = next
}
return furthest
},
splitTextAtRange(range) {
range = range.normalize(this)
let node = this
// If the range is expanded, remove it first.
if (range.isExpanded) {
node = node.deleteAtRange(range)
range = range.moveToStart()
}
// Split the text node's characters.
const { startKey, startOffset } = range
const text = node.getDeep(startKey)
const { characters } = text
const firstChars = characters.take(startOffset)
const secondChars = characters.skip(startOffset)
let firstChild = text.merge({ characters: firstChars })
let secondChild = Text.create({ characters: secondChars })
// Split the text nodes.
let parent = node.getParent(text)
const nodes = parent.nodes
.takeUntil(c => c.key == firstChild.key)
.set(firstChild.key, firstChild)
.set(secondChild.key, secondChild)
.concat(parent.nodes.skipUntil(n => n.key == firstChild.key).rest())
// Update the nodes.
parent = parent.merge({ nodes })
node = node.updateDeep(parent)
return node
},
splitInlineAtRange(range) {
range = range.normalize(this)
const Inline = require('./inline').default
let node = this
// If the range is expanded, remove it first.
if (range.isExpanded) {
node = node.deleteAtRange(range)
range = range.moveToStart()
}
// First split the text nodes.
node = node.splitTextAtRange(range)
let firstChild = node.getDeep(range.startKey)
let secondChild = node.getNextText(firstChild)
let parent
// While the parent is an inline parent, split the inline nodes.
while (parent = node.getClosestInline(firstChild)) {
const firstNodes = Inline.createMap([firstChild])
const secondNodes = Inline.createMap([secondChild])
firstChild = parent.merge({ nodes: firstNodes })
secondChild = Inline.create({
nodes: secondNodes,
type: parent.type,
data: parent.data
})
// Split the children.
const isGrandparent = node.nodes.has(parent.key)
const grandparent = isGrandparent ? node : node.getParent(parent)
const nodes = grandparent.nodes
.takeUntil(c => c.key == firstChild.key)
.set(firstChild.key, firstChild)
.set(secondChild.key, secondChild)
.concat(parent.nodes.skipUntil(n => n.key == firstChild.key).rest())
// Update the parent.
node = isGrandparent
? node.merge({ nodes })
: node.updateDeep(parent.merge({ nodes }))
}
return node
},
wrapInlineAtRange(range, type, data = new Map()) {
range = range.normalize(this)

View File

@ -0,0 +1,17 @@
export default function (state) {
const { document, selection } = state
const texts = document.getTextNodes()
const first = texts.first()
const range = selection.merge({
anchorKey: first.key,
anchorOffset: 2,
focusKey: first.key,
focusOffset: 2
})
return state
.transform()
.splitBlockAtRange(range)
.apply()
}

View File

@ -0,0 +1,11 @@
nodes:
- kind: block
type: paragraph
nodes:
- kind: inline
type: link
nodes:
- kind: text
ranges:
- text: word

View File

@ -0,0 +1,20 @@
nodes:
- kind: block
type: paragraph
nodes:
- kind: inline
type: link
nodes:
- kind: text
ranges:
- text: wo
- kind: block
type: paragraph
nodes:
- kind: inline
type: link
nodes:
- kind: text
ranges:
- text: rd

View File

@ -0,0 +1,17 @@
export default function (state) {
const { document, selection } = state
const texts = document.getTextNodes()
const first = texts.first()
const range = selection.merge({
anchorKey: first.key,
anchorOffset: first.length,
focusKey: first.key,
focusOffset: first.length
})
return state
.transform()
.splitInlineAtRange(range)
.apply()
}

View File

@ -0,0 +1,11 @@
nodes:
- kind: block
type: paragraph
nodes:
- kind: inline
type: link
nodes:
- kind: text
ranges:
- text: word

View File

@ -0,0 +1,17 @@
nodes:
- kind: block
type: paragraph
nodes:
- kind: inline
type: link
nodes:
- kind: text
ranges:
- text: word
- kind: inline
type: link
nodes:
- kind: text
ranges:
- text: ""

View File

@ -0,0 +1,17 @@
export default function (state) {
const { document, selection } = state
const texts = document.getTextNodes()
const first = texts.first()
const range = selection.merge({
anchorKey: first.key,
anchorOffset: 2,
focusKey: first.key,
focusOffset: 2
})
return state
.transform()
.splitInlineAtRange(range)
.apply()
}

View File

@ -0,0 +1,11 @@
nodes:
- kind: block
type: paragraph
nodes:
- kind: inline
type: link
nodes:
- kind: text
ranges:
- text: word

View File

@ -0,0 +1,17 @@
nodes:
- kind: block
type: paragraph
nodes:
- kind: inline
type: link
nodes:
- kind: text
ranges:
- text: wo
- kind: inline
type: link
nodes:
- kind: text
ranges:
- text: rd

View File

@ -0,0 +1,17 @@
export default function (state) {
const { document, selection } = state
const texts = document.getTextNodes()
const first = texts.first()
const range = selection.merge({
anchorKey: first.key,
anchorOffset: 0,
focusKey: first.key,
focusOffset: 0
})
return state
.transform()
.splitInlineAtRange(range)
.apply()
}

View File

@ -0,0 +1,11 @@
nodes:
- kind: block
type: paragraph
nodes:
- kind: inline
type: link
nodes:
- kind: text
ranges:
- text: word

View File

@ -0,0 +1,17 @@
nodes:
- kind: block
type: paragraph
nodes:
- kind: inline
type: link
nodes:
- kind: text
ranges:
- text: ""
- kind: inline
type: link
nodes:
- kind: text
ranges:
- text: word

View File

@ -0,0 +1,18 @@
export default function (state) {
const { document, selection } = state
const texts = document.getTextNodes()
const first = texts.first()
const second = texts.last()
const range = selection.merge({
anchorKey: first.key,
anchorOffset: 2,
focusKey: second.key,
focusOffset: 2
})
return state
.transform()
.splitInlineAtRange(range)
.apply()
}

View File

@ -0,0 +1,20 @@
nodes:
- kind: block
type: paragraph
nodes:
- kind: inline
type: link
nodes:
- kind: text
ranges:
- text: word
- kind: block
type: paragraph
nodes:
- kind: inline
type: link
nodes:
- kind: text
ranges:
- text: another

View File

@ -0,0 +1,17 @@
nodes:
- kind: block
type: paragraph
nodes:
- kind: inline
type: link
nodes:
- kind: text
ranges:
- text: wo
- kind: inline
type: link
nodes:
- kind: text
ranges:
- text: other

View File

@ -0,0 +1,17 @@
export default function (state) {
const { document, selection } = state
const texts = document.getTextNodes()
const first = texts.first()
const range = selection.merge({
anchorKey: first.key,
anchorOffset: 1,
focusKey: first.key,
focusOffset: 3
})
return state
.transform()
.splitInlineAtRange(range)
.apply()
}

View File

@ -0,0 +1,11 @@
nodes:
- kind: block
type: paragraph
nodes:
- kind: inline
type: link
nodes:
- kind: text
ranges:
- text: word

View File

@ -0,0 +1,17 @@
nodes:
- kind: block
type: paragraph
nodes:
- kind: inline
type: link
nodes:
- kind: text
ranges:
- text: w
- kind: inline
type: link
nodes:
- kind: text
ranges:
- text: d

View File

@ -0,0 +1,17 @@
export default function (state) {
const { document, selection } = state
const texts = document.getTextNodes()
const first = texts.first()
const range = selection.merge({
anchorKey: first.key,
anchorOffset: 2,
focusKey: first.key,
focusOffset: 2
})
return state
.transform()
.splitInlineAtRange(range)
.apply()
}

View File

@ -0,0 +1,13 @@
nodes:
- kind: block
type: paragraph
nodes:
- kind: inline
type: link
nodes:
- kind: text
ranges:
- text: word
marks:
- type: bold

View File

@ -0,0 +1,21 @@
nodes:
- kind: block
type: paragraph
nodes:
- kind: inline
type: link
nodes:
- kind: text
ranges:
- text: wo
marks:
- type: bold
- kind: inline
type: link
nodes:
- kind: text
ranges:
- text: rd
marks:
- type: bold