1
0
mirror of https://github.com/ianstormtaylor/slate.git synced 2025-09-01 11:12:42 +02:00

more refactoring transforms

This commit is contained in:
Ian Storm Taylor
2016-08-17 20:36:32 -07:00
parent 4e24f2666a
commit 84ba78582f
3 changed files with 167 additions and 196 deletions

View File

@@ -6,7 +6,7 @@ import Selection from '../models/selection'
import Text from '../models/text'
import isInRange from '../utils/is-in-range'
import uid from '../utils/uid'
import { Set } from 'immutable'
import { List, Set } from 'immutable'
/**
* Add a new `mark` to the characters at `range`.
@@ -586,90 +586,80 @@ export function toggleMarkAtRange(transform, range, mark) {
*/
export function unwrapBlockAtRange(transform, range, properties) {
let { state } = transform
properties = Normalize.nodeProperties(properties)
let { state } = transform
let { document } = state
// Get the deepest blocks in the range.
const blocks = document.getBlocksAtRange(range)
const wrappers = blocks
.map((block) => {
return document.getClosest(block, (parent) => {
if (parent.kind != 'block') return false
if (properties.type != null && parent.type != properties.type) return false
if (properties.isVoid != null && parent.isVoid != properties.isVoid) return false
if (properties.data != null && !parent.data.isSuperset(properties.data)) return false
return true
})
})
.filter(exists => exists)
.toSet()
.toList()
// Get the matching wrapper blocks.
const wrappers = blocks.reduce((memo, text) => {
const match = document.getClosest(text, (parent) => {
if (parent.kind != 'block') return false
if (properties.type && parent.type != properties.type) return false
if (properties.data && !parent.data.isSuperset(properties.data)) return false
return true
wrappers.forEach((block) => {
const first = block.nodes.first()
const last = block.nodes.last()
const parent = document.getParent(block)
const index = parent.nodes.indexOf(block)
const children = block.nodes.filter((child) => {
return blocks.some(b => child == b || child.hasDescendant(b))
})
if (match) memo = memo.add(match)
return memo
}, new Set())
// For each of the wrapper blocks...
wrappers.forEach((wrapper) => {
const first = wrapper.nodes.first()
const last = wrapper.nodes.last()
const parent = document.getParent(wrapper)
// Get the wrapped direct children.
const children = wrapper.nodes.filter((child) => {
return blocks.some(block => child == block || child.hasDescendant(block))
})
// Determine what the new nodes should be...
const firstMatch = children.first()
const lastMatch = children.last()
let nodes
let lastMatch = children.last()
// If the first and last both match, remove the wrapper completely.
if (first == firstMatch && last == lastMatch) {
nodes = parent.nodes.takeUntil(n => n == wrapper)
.concat(wrapper.nodes)
.concat(parent.nodes.skipUntil(n => n == wrapper).rest())
block.nodes.forEach((child, i) => {
transform.moveNodeByKey(child.key, parent.key, index + i)
})
transform.removeNodeByKey(block.key)
}
// If only the last child matches, move the last nodes.
else if (last == lastMatch) {
const remain = wrapper.nodes.takeUntil(n => n == firstMatch)
const updated = wrapper.merge({ nodes: remain })
nodes = parent.nodes.takeUntil(n => n == wrapper)
.push(updated)
.concat(children)
.concat(parent.nodes.skipUntil(n => n == wrapper).rest())
block.nodes
.skipUntil(n => n == firstMatch)
.forEach((child, i) => {
transform.moveNodeByKey(child.key, parent.key, index + 1 + i)
})
}
// If only the first child matches, move the first ones.
else if (first == firstMatch) {
const remain = wrapper.nodes.skipUntil(n => n == lastMatch).rest()
const updated = wrapper.merge({ nodes: remain })
nodes = parent.nodes.takeUntil(n => n == wrapper)
.concat(children)
.push(updated)
.concat(parent.nodes.skipUntil(n => n == wrapper).rest())
block.nodes
.takeUntil(n => n == lastMatch)
.push(lastMatch)
.forEach((child, i) => {
transform.moveNodeByKey(child.key, parent.key, index + i)
})
}
// Otherwise, move the middle ones.
else {
const firsts = wrapper.nodes.takeUntil(n => n == firstMatch)
const lasts = wrapper.nodes.skipUntil(n => n == lastMatch).rest()
const updatedFirst = wrapper.merge({ nodes: firsts })
const updatedLast = wrapper.merge({ nodes: lasts })
nodes = parent.nodes.takeUntil(n => n == wrapper)
.push(updatedFirst)
.concat(children)
.push(updatedLast)
.concat(parent.nodes.skipUntil(n => n == wrapper).rest())
}
const offset = block.getOffset(firstMatch)
document = parent == document
? document.merge({ nodes })
: document.updateDescendant(parent.merge({ nodes }))
transform.splitNodeByKey(block.key, offset)
state = transform.state
document = state.document
const extra = document.getPreviousSibling(firstMatch)
children.forEach((child, i) => {
transform.moveNodeByKey(child.key, parent.key, index + 1 + i)
})
transform.removeNodeByKey(extra.key)
}
})
document = document.normalize()
state = state.merge({ document })
transform.state = state
transform.normalizeDocument()
return transform
}
@@ -683,183 +673,163 @@ export function unwrapBlockAtRange(transform, range, properties) {
*/
export function unwrapInlineAtRange(transform, range, properties) {
let { state } = transform
properties = Normalize.nodeProperties(properties)
let { document } = state
let blocks = document.getInlinesAtRange(range)
// Find the closest matching inline wrappers of each text node.
const { state } = transform
const { document } = state
const texts = document.getTexts()
const wrappers = texts.reduce((memo, text) => {
const match = document.getClosest(text, (parent) => {
if (parent.kind != 'inline') return false
if (properties.type && parent.type != properties.type) return false
if (properties.data && !parent.data.isSuperset(properties.data)) return false
return true
const inlines = texts
.map((text) => {
return document.getClosest(text, (parent) => {
if (parent.kind != 'inline') return false
if (properties.type != null && parent.type != properties.type) return false
if (properties.isVoid != null && parent.isVoid != properties.isVoid) return false
if (properties.data != null && !parent.data.isSuperset(properties.data)) return false
return true
})
})
.filter(exists => exists)
.toSet()
.toList()
if (match) memo = memo.add(match)
return memo
}, new Set())
inlines.forEach((inline) => {
const parent = document.getParent(inline)
const index = parent.nodes.indexOf(inline)
// Replace each of the wrappers with their child nodes.
wrappers.forEach((wrapper) => {
const parent = document.getParent(wrapper)
// Replace the wrapper in the parent's nodes with the block.
const nodes = parent.nodes.takeUntil(n => n == wrapper)
.concat(wrapper.nodes)
.concat(parent.nodes.skipUntil(n => n == wrapper).rest())
// Update the parent.
document = parent == document
? document.merge({ nodes })
: document.updateDescendant(parent.merge({ nodes }))
inline.nodes.forEach((child, i) => {
transform.moveNodeByKey(child.key, parent.key, index + i)
})
})
document = document.normalize()
state = state.merge({ document })
transform.state = state
transform.normalizeDocument()
return transform
}
/**
* Wrap all of the blocks in a `range` in a new block with `properties`.
* Wrap all of the blocks in a `range` in a new `block`.
*
* @param {Transform} transform
* @param {Selection} range
* @param {String or Object} properties
* @param {Block || Object || String} block
* @return {Transform}
*/
export function wrapBlockAtRange(transform, range, properties) {
let { state } = transform
properties = Normalize.nodeProperties(properties)
let { document } = state
export function wrapBlockAtRange(transform, range, block) {
block = Normalize.block(block)
// Get the block nodes, sorted by depth.
const { state } = transform
const { document } = state
const blocks = document.getBlocksAtRange(range)
const sorted = blocks.sort((a, b) => {
const da = document.getDepth(a)
const db = document.getDepth(b)
if (da == db) return 0
else if (da > db) return -1
else return 1
})
const depth = blocks.map(n => document.getDepth(n)).min()
// Get the lowest common siblings, relative to the highest block.
const highest = sorted.first()
const depth = document.getDepth(highest)
const siblings = blocks.reduce((memo, block) => {
const sibling = document.getDepth(block) == depth
? block
: document.getClosest(block, (p) => document.getDepth(p) == depth)
memo = memo.push(sibling)
return memo
}, Block.createList())
const siblings = blocks
.map((node) => {
const d = document.getDepth(node)
if (d == depth) return node
return document.getClosest(node, p => document.getDepth(p) == depth)
})
.toSet()
.toList()
// Wrap the siblings in a new block.
const wrapper = Block.create({
nodes: siblings,
type: properties.type,
data: properties.data
})
// Replace the siblings with the wrapper.
const first = siblings.first()
const last = siblings.last()
const parent = document.getParent(highest)
const nodes = parent.nodes
.takeUntil(n => n == first)
.push(wrapper)
.concat(parent.nodes.skipUntil(n => n == last).rest())
const parent = document.getParent(first)
const index = parent.nodes.indexOf(first)
// Update the parent.
document = parent == document
? document.merge({ nodes })
: document.updateDescendant(parent.merge({ nodes }))
transform.insertNodeByKey(parent.key, index + 1, block)
siblings.forEach((node, i) => {
transform.moveNodeByKey(node.key, block.key, i)
})
state = state.merge({ document })
transform.state = state
return transform
}
/**
* Wrap the text and inlines in a `range` in a new inline with `properties`.
* Wrap the text and inlines in a `range` in a new `inline`.
*
* @param {Transform} transform
* @param {Selection} range
* @param {String or Object} properties
* @param {Inline || Object || String} inline
* @return {Transform}
*/
export function wrapInlineAtRange(transform, range, properties) {
let { state } = transform
properties = Normalize.nodeProperties(properties)
let { document } = state
// If collapsed, there's nothing to wrap.
export function wrapInlineAtRange(transform, range, inline) {
if (range.isCollapsed) return transform
// Split at the start of the range.
const start = range.collapseToStart()
transform = splitInlineAtRange(transform, start)
state = transform.state
document = state.document
inline = Normalize.inline(inline)
// Determine the new end of the range, and split there.
const { startKey, startOffset, endKey, endOffset } = range
const firstNode = document.getDescendant(startKey)
const nextNode = document.getNextText(startKey)
const end = startKey != endKey
? range.collapseToEnd()
: Selection.create({
anchorKey: nextNode.key,
anchorOffset: endOffset - startOffset,
focusKey: nextNode.key,
focusOffset: endOffset - startOffset
})
let { state } = transform
let { document } = state
const blocks = document.getBlocksAtRange(range)
let startBlock = document.getClosestBlock(startKey)
let endBlock = document.getClosestBlock(endKey)
let startChild = startBlock.getHighestChild(startKey)
let endChild = endBlock.getHighestChild(endKey)
const startIndex = startBlock.nodes.indexOf(startChild)
const endIndex = endBlock.nodes.indexOf(endChild)
transform = splitInlineAtRange(transform, end)
state = transform.state
document = state.document
const startOff = startChild.key == startKey
? startOffset
: startChild.getOffset(startKey)
// Calculate the new range to wrap around.
const endNode = document.getDescendant(end.anchorKey)
range = Selection.create({
anchorKey: nextNode.key,
anchorOffset: 0,
focusKey: endNode.key,
focusOffset: endNode.length
})
const endOff = endChild.key == endKey
? endOffset
: endChild.getOffset(endKey)
// Get the furthest inline nodes in the range.
const texts = document.getTextsAtRange(range)
const children = texts.map(text => document.getFurthestInline(text) || text)
if (startBlock == endBlock) {
transform.splitNodeByKey(endChild.key, endOff)
transform.splitNodeByKey(startChild.key, startOff)
// Iterate each of the child nodes, wrapping them.
children.forEach((child) => {
const wrapper = Inline.create({
nodes: [child],
type: properties.type,
data: properties.data
state = transform.state
document = state.document
startBlock = document.getClosestBlock(startKey)
const startInner = document.getNextText(startKey)
const innerParent = document.getParent(startInner)
const startInnerIndex = innerParent.nodes.indexOf(startInner)
const endInner = startKey == endKey ? startInner : document.getDescendant(endKey)
const inlines = startBlock.nodes
.skipUntil(n => n == startInner)
.takeUntil(n => n == endInner)
.push(endInner)
const node = inline.merge({
key: uid(),
nodes: inlines
})
// Replace the child in it's parent with the wrapper.
const parent = document.getParent(child)
const nodes = parent.nodes.takeUntil(n => n == child)
.push(wrapper)
.concat(parent.nodes.skipUntil(n => n == child).rest())
debugger
// Update the parent.
document = parent == document
? document.merge({ nodes })
: document.updateDescendant(parent.merge({ nodes }))
})
transform.insertNodeByKey(startBlock.key, startInnerIndex, node)
document = document.normalize()
state = state.merge({ document })
transform.state = state
inlines.forEach((child, i) => {
transform.removeNodeByKey(child.key, node.key, i)
})
}
else {
transform.splitNodeByKey(startChild.key, startOff)
transform.splitNodeByKey(endChild.key, endOff)
state = transform.state
document = state.document
startBlock = document.getDescendant(startBlock.key)
endBlock = document.getDescendant(endBlock.key)
// startChild = startBlock.
// const startInlines = startBlock.skip(startIndex)
blocks.rest().butLast().forEach((block) => {
const node = inline.merge({ key: uid() })
transform.insertNodeByKey(block.key, 0, node)
block.nodes.forEach((child, i) => {
transform.moveNodeByKey(child.key, node.key, i)
})
})
}
transform.normalizeDocument()
return transform
}

View File

@@ -108,6 +108,7 @@ export function insertTextByKey(transform, key, offset, text, marks) {
export function moveNodeByKey(transform, key, newKey, newIndex) {
let { state } = transform
let { document } = state
debugger
const node = document.assertDescendant(key)
const path = document.getPath(node)
const newPath = document.getPath(newKey)
@@ -118,7 +119,7 @@ export function moveNodeByKey(transform, key, newKey, newIndex) {
parent = parent.removeNode(index)
document = isParent ? parent : document.updateDescendant(parent)
let target = document.assertDescendant(newKey)
let target = document.key == newKey ? document : document.assertDescendant(newKey)
const isTarget = document == target
target = target.insertNode(newIndex, node)

View File

@@ -27,7 +27,7 @@ export default function (state) {
.apply()
const updated = next.document.getTexts().get(1)
debugger
assert.deepEqual(
next.selection.toJS(),
range.merge({