1
0
mirror of https://github.com/ianstormtaylor/slate.git synced 2025-08-09 08:46:35 +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) { getBlocksAtRange(range) {
range = range.normalize(this) range = range.normalize(this)
const texts = this.getTextsAtRange(range) const texts = this.getTextsAtRange(range)
const blocks = texts.map((text) => { const blocks = texts.map(text => this.getClosestBlock(text))
return this.getClosest(text, p => p.kind == 'block')
})
return blocks 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 * @param {Function} iterator
* @return {Node or Null} node * @return {Node or Null} parent
*/ */
getClosest(node, iterator) { getClosest(key, iterator) {
while (node) { key = normalizeKey(key)
this.assertHasDeep(key)
let node = this.getDeep(key)
while (node = this.getParent(node)) {
if (node == this) return null if (node == this) return null
if (iterator(node)) return node if (iterator(node)) return node
node = this.getParent(node)
} }
return null 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`. * Get a child node by `key`.
* *
@@ -306,6 +360,8 @@ const Node = {
getDepth(key, startAt = 1) { getDepth(key, startAt = 1) {
key = normalizeKey(key) key = normalizeKey(key)
this.assertHasDeep(key)
if (this.nodes.has(key)) return startAt if (this.nodes.has(key)) return startAt
const child = this.nodes.find(node => { const child = this.nodes.find(node => {
@@ -407,6 +463,7 @@ const Node = {
getNextSibling(key) { getNextSibling(key) {
key = normalizeKey(key) key = normalizeKey(key)
this.assertHasDeep(key)
if (this.nodes.has(key)) { if (this.nodes.has(key)) {
return this.nodes return this.nodes
@@ -430,6 +487,8 @@ const Node = {
getNextText(key) { getNextText(key) {
key = normalizeKey(key) key = normalizeKey(key)
this.assertHasDeep(key)
return this.getTextNodes() return this.getTextNodes()
.skipUntil(text => text.key == key) .skipUntil(text => text.key == key)
.take(2) .take(2)
@@ -444,7 +503,9 @@ const Node = {
*/ */
getOffset(key) { getOffset(key) {
key = normalizeKey(key)
this.assertHasDeep(key) this.assertHasDeep(key)
const match = this.getDeep(key) const match = this.getDeep(key)
// Find the shallow matching child. // Find the shallow matching child.
@@ -479,8 +540,9 @@ const Node = {
getParent(key) { getParent(key) {
key = normalizeKey(key) key = normalizeKey(key)
// this.assertHasDeep(key)
if (this.nodes.get(key)) return this if (this.nodes.has(key)) return this
let node = null let node = null
this.nodes.forEach((child) => { this.nodes.forEach((child) => {
@@ -501,6 +563,7 @@ const Node = {
getPreviousSibling(key) { getPreviousSibling(key) {
key = normalizeKey(key) key = normalizeKey(key)
this.assertHasDeep(key)
if (this.nodes.has(key)) { if (this.nodes.has(key)) {
return this.nodes return this.nodes
@@ -523,6 +586,8 @@ const Node = {
getPreviousText(key) { getPreviousText(key) {
key = normalizeKey(key) key = normalizeKey(key)
this.assertHasDeep(key)
return this.getTextNodes() return this.getTextNodes()
.takeUntil(text => text.key == key) .takeUntil(text => text.key == key)
.last() .last()
@@ -596,6 +661,7 @@ const Node = {
hasDeep(key) { hasDeep(key) {
key = normalizeKey(key) key = normalizeKey(key)
return !! this.nodes.find((node) => { return !! this.nodes.find((node) => {
return node.kind == 'text' return node.kind == 'text'
? node.key == key ? node.key == key
@@ -762,6 +828,7 @@ const Node = {
removeDeep(key) { removeDeep(key) {
key = normalizeKey(key) key = normalizeKey(key)
this.assertHasDeep(key)
let nodes = this.nodes.remove(key) let nodes = this.nodes.remove(key)
return this.merge({ nodes }) return this.merge({ nodes })
@@ -854,52 +921,134 @@ const Node = {
range = range.moveToStart() range = range.moveToStart()
} }
const { startKey, startOffset } = range // Split the inline nodes at the range.
const startNode = node.getDeep(startKey) node = node.splitInlineAtRange(range)
// Split the text node's characters. // Find the highest inline elements that were split.
const { characters, length } = startNode const { startKey } = range
const firstCharacters = characters.take(startOffset) const firstText = node.getDeep(startKey)
const secondCharacters = characters.takeLast(length - startOffset) 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. // Remove the second inline child from the first block.
const parent = node.getParent(startNode) let firstBlock = node.getBlocksAtRange(range).first()
const firstText = startNode.set('characters', firstCharacters) firstBlock = firstBlock.removeDeep(secondChild)
const firstElement = parent.updateDeep(firstText)
// Create a brand new second element with the second set of characters. // Create a new block with the second inline child in it.
let secondText = Text.create({}) const secondBlock = Block.create({
let secondElement = Block.create({ type: firstBlock.type,
type: firstElement.type, data: firstBlock.data,
data: firstElement.data nodes: Block.createMap([secondChild])
}) })
secondText = secondText.set('characters', secondCharacters) // Replace the block in the parent with the two new blocks.
secondElement = secondElement.merge({ let parent = node.getParent(firstBlock)
nodes: secondElement.nodes.set(secondText.key, secondText) 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. // If the node is the parent, just merge, otherwise deep merge.
let grandparent = node.getParent(parent) if (parent == node) {
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 }) node = node.merge({ nodes })
} else { } else {
grandparent = grandparent.merge({ nodes }) parent = parent.merge({ nodes })
node = node.updateDeep(grandparent) node = node.updateDeep(parent)
} }
// Normalize the node. // Normalize the node.
return node.normalize() 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`. * Remove an existing `mark` to the characters at `range`.
* *
@@ -957,6 +1106,8 @@ const Node = {
key = normalizeKey(key) key = normalizeKey(key)
} }
// this.assertHasDeep(key)
if (this.nodes.get(key)) { if (this.nodes.get(key)) {
const nodes = this.nodes.set(key, node) const nodes = this.nodes.set(key, node)
return this.set('nodes', nodes) 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()) { wrapInlineAtRange(range, type, data = new Map()) {
range = range.normalize(this) 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