1
0
mirror of https://github.com/ianstormtaylor/slate.git synced 2025-08-30 02:19:52 +02:00

change unwrapBlock to operate only on the siblings in a range

This commit is contained in:
Ian Storm Taylor
2016-07-20 14:13:29 -07:00
parent 46480d60d6
commit 9499b9188b
18 changed files with 486 additions and 27 deletions

View File

@@ -2,6 +2,16 @@
This document maintains a list of changes to Slate with each new version. Until `1.0.0` is released, breaking changes will be added as minor version bumps, and non-breaking changes won't be accounted for since the library is moving quickly.
## `0.3.0`
_July 20, 2016_
###### BREAKING CHANGES
- Changed `unwrapBlock` to unwrap selectively. Previously, calling `unwrapBlock` with a range representing a middle sibling would unwrap _all_ of the siblings, removing the wrapping block entirely. Now, calling it with those same arguments will only move the middle sibling up a layer in the hierarchy, preserving the nesting on any of its siblings.
This changes makes it much simpler to implement functionality like unwrapping a single list item, which previously would unwrap the entire list.
## `0.2.0`
_July 18, 2016_

View File

@@ -99,6 +99,7 @@ class RichText extends React.Component {
onChange = (state) => {
this.setState({ state })
console.log(state.document.toJS())
}
/**
@@ -169,19 +170,32 @@ class RichText extends React.Component {
onClickBlock = (e, type) => {
e.preventDefault()
const isActive = this.hasBlock(type)
let { state } = this.state
let transform = state.transform()
const { document } = state
let transform = state
.transform()
.setBlock(isActive ? 'paragraph' : type)
// Handle everything but list buttons.
if (type != 'bulleted-list' && type != 'numbered-list') {
const isActive = this.hasBlock(type)
transform = transform.setBlock(isActive ? DEFAULT_NODE : type)
}
// Handle the extra wrapping required for list buttons.
if (type == 'bulleted-list' || type == 'numbered-list') {
if (this.hasBlock('list-item')) {
else {
const isList = this.hasBlock('list-item')
const isType = state.blocks.some((block) => {
return !!document.getClosest(block, parent => parent.type == type)
})
if (isList && isType) {
transform = transform
.setBlock(DEFAULT_NODE)
.unwrapBlock(type)
.unwrapBlock('bulleted-list')
.unwrapBlock('numbered-list')
} else if (isList) {
transform = transform
.unwrapBlock(type == 'bulleted-list' ? 'numbered-list' : 'bulleted-list')
.wrapBlock(type)
} else {
transform = transform
.setBlock('list-item')
@@ -189,11 +203,6 @@ class RichText extends React.Component {
}
}
// Handle everything but list buttons.
else {
transform = transform.setBlock(isActive ? DEFAULT_NODE : type)
}
state = transform.apply()
this.setState({ state })
}

View File

@@ -66,7 +66,7 @@ const Transforms = {
node = node.merge({ nodes })
// Take the end edge's split text and move it to the start edge.
let startBlock = node.getFurthestBlock(startText)
let startBlock = node.getClosestBlock(startText)
let endChild = node.getFurthestInline(endText) || endText
const startNodes = startBlock.nodes.push(endChild)
@@ -426,12 +426,14 @@ const Transforms = {
let firstChild = node.getFurthestInline(firstText) || firstText
let secondChild = node.getFurthestInline(secondText) || secondText
let parent = node.getClosestBlock(firstChild)
let firstChildren = parent.nodes.takeUntil(n => n == firstChild).push(firstChild)
let secondChildren = parent.nodes.skipUntil(n => n == secondChild)
let firstChildren
let secondChildren
let d = 0
// While the parent is a block, split the block nodes.
while (parent && d < depth) {
firstChildren = parent.nodes.takeUntil(n => n == firstChild).push(firstChild)
secondChildren = parent.nodes.skipUntil(n => n == secondChild)
firstChild = parent.merge({ nodes: firstChildren })
secondChild = Block.create({
nodes: secondChildren,
@@ -439,9 +441,6 @@ const Transforms = {
data: parent.data
})
firstChildren = Block.createList([firstChild])
secondChildren = Block.createList([secondChild])
// Add the new children.
const grandparent = node.getParent(parent)
const nodes = grandparent.nodes
@@ -622,9 +621,11 @@ const Transforms = {
// Ensure that data is immutable.
if (data) data = Data.create(data)
// Find the closest wrapping blocks of each text node.
const texts = node.getBlocksAtRange(range)
const wrappers = texts.reduce((memo, text) => {
// Get the deepest blocks in the range.
const blocks = node.getBlocksAtRange(range)
// Get the matching wrapper blocks.
const wrappers = blocks.reduce((memo, text) => {
const match = node.getClosest(text, (parent) => {
if (parent.kind != 'block') return false
if (type && parent.type != type) return false
@@ -636,16 +637,62 @@ const Transforms = {
return memo
}, new Set())
// Replace each of the wrappers with their child nodes.
// For each of the wrapper blocks...
wrappers.forEach((wrapper) => {
const first = wrapper.nodes.first()
const last = wrapper.nodes.last()
const parent = node.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())
// 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
// 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())
}
// 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())
}
// 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())
}
// 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())
}
// Update the parent.
node = parent == node
? node.merge({ nodes })
: node.updateDescendant(parent.merge({ nodes }))

View File

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

View File

@@ -0,0 +1,17 @@
nodes:
- kind: block
type: quote
nodes:
- kind: block
type: paragraph
nodes:
- kind: text
ranges:
- text: word
- kind: block
type: paragraph
nodes:
- kind: text
ranges:
- text: another

View File

@@ -0,0 +1,11 @@
nodes:
- kind: block
type: quote
nodes:
- kind: block
type: paragraph
nodes:
- kind: text
ranges:
- text: wordanother

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()
.deleteForwardAtRange(range)
.apply()
}

View File

@@ -0,0 +1,17 @@
nodes:
- kind: block
type: quote
nodes:
- kind: block
type: paragraph
nodes:
- kind: text
ranges:
- text: word
- kind: block
type: paragraph
nodes:
- kind: text
ranges:
- text: another

View File

@@ -0,0 +1,11 @@
nodes:
- kind: block
type: quote
nodes:
- kind: block
type: paragraph
nodes:
- kind: text
ranges:
- text: wordanother

View File

@@ -0,0 +1,18 @@
export default function (state) {
const { document, selection } = state
const texts = document.getTextNodes()
const fifth = texts.get(4)
const sixth = texts.get(5)
const range = selection.merge({
anchorKey: fifth.key,
anchorOffset: 0,
focusKey: sixth.key,
focusOffset: 0
})
return state
.transform()
.unwrapBlockAtRange(range, 'quote')
.apply()
}

View File

@@ -0,0 +1,41 @@
nodes:
- kind: block
type: quote
nodes:
- kind: block
type: paragraph
nodes:
- kind: text
ranges:
- text: one
- kind: block
type: paragraph
nodes:
- kind: text
ranges:
- text: two
- kind: block
type: paragraph
nodes:
- kind: text
ranges:
- text: three
- kind: block
type: paragraph
nodes:
- kind: text
ranges:
- text: four
- kind: block
type: paragraph
nodes:
- kind: text
ranges:
- text: five
- kind: block
type: paragraph
nodes:
- kind: text
ranges:
- text: six

View File

@@ -0,0 +1,41 @@
nodes:
- kind: block
type: quote
nodes:
- kind: block
type: paragraph
nodes:
- kind: text
ranges:
- text: one
- kind: block
type: paragraph
nodes:
- kind: text
ranges:
- text: two
- kind: block
type: paragraph
nodes:
- kind: text
ranges:
- text: three
- kind: block
type: paragraph
nodes:
- kind: text
ranges:
- text: four
- kind: block
type: paragraph
nodes:
- kind: text
ranges:
- text: five
- kind: block
type: paragraph
nodes:
- kind: text
ranges:
- text: six

View File

@@ -0,0 +1,18 @@
export default function (state) {
const { document, selection } = state
const texts = document.getTextNodes()
const third = texts.get(2)
const fourth = texts.get(3)
const range = selection.merge({
anchorKey: third.key,
anchorOffset: 0,
focusKey: fourth.key,
focusOffset: 0
})
return state
.transform()
.unwrapBlockAtRange(range, 'quote')
.apply()
}

View File

@@ -0,0 +1,41 @@
nodes:
- kind: block
type: quote
nodes:
- kind: block
type: paragraph
nodes:
- kind: text
ranges:
- text: one
- kind: block
type: paragraph
nodes:
- kind: text
ranges:
- text: two
- kind: block
type: paragraph
nodes:
- kind: text
ranges:
- text: three
- kind: block
type: paragraph
nodes:
- kind: text
ranges:
- text: four
- kind: block
type: paragraph
nodes:
- kind: text
ranges:
- text: five
- kind: block
type: paragraph
nodes:
- kind: text
ranges:
- text: six

View File

@@ -0,0 +1,44 @@
nodes:
- kind: block
type: quote
nodes:
- kind: block
type: paragraph
nodes:
- kind: text
ranges:
- text: one
- kind: block
type: paragraph
nodes:
- kind: text
ranges:
- text: two
- kind: block
type: paragraph
nodes:
- kind: text
ranges:
- text: three
- kind: block
type: paragraph
nodes:
- kind: text
ranges:
- text: four
- kind: block
type: quote
nodes:
- kind: block
type: paragraph
nodes:
- kind: text
ranges:
- text: five
- kind: block
type: paragraph
nodes:
- kind: text
ranges:
- text: six

View File

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

View File

@@ -0,0 +1,41 @@
nodes:
- kind: block
type: quote
nodes:
- kind: block
type: paragraph
nodes:
- kind: text
ranges:
- text: one
- kind: block
type: paragraph
nodes:
- kind: text
ranges:
- text: two
- kind: block
type: paragraph
nodes:
- kind: text
ranges:
- text: three
- kind: block
type: paragraph
nodes:
- kind: text
ranges:
- text: four
- kind: block
type: paragraph
nodes:
- kind: text
ranges:
- text: five
- kind: block
type: paragraph
nodes:
- kind: text
ranges:
- text: six

View File

@@ -0,0 +1,41 @@
nodes:
- kind: block
type: paragraph
nodes:
- kind: text
ranges:
- text: one
- kind: block
type: paragraph
nodes:
- kind: text
ranges:
- text: two
- kind: block
type: quote
nodes:
- kind: block
type: paragraph
nodes:
- kind: text
ranges:
- text: three
- kind: block
type: paragraph
nodes:
- kind: text
ranges:
- text: four
- kind: block
type: paragraph
nodes:
- kind: text
ranges:
- text: five
- kind: block
type: paragraph
nodes:
- kind: text
ranges:
- text: six