mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-08-30 10:29:48 +02:00
change unwrapBlock to operate only on the siblings in a range
This commit is contained in:
10
History.md
10
History.md
@@ -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_
|
||||
|
||||
|
@@ -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 })
|
||||
}
|
||||
|
@@ -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 }))
|
||||
|
@@ -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()
|
||||
}
|
@@ -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
|
@@ -0,0 +1,11 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: quote
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: wordanother
|
@@ -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()
|
||||
}
|
@@ -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
|
@@ -0,0 +1,11 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: quote
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: wordanother
|
@@ -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()
|
||||
}
|
@@ -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
|
@@ -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
|
@@ -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()
|
||||
}
|
@@ -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
|
@@ -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
|
@@ -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()
|
||||
}
|
@@ -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
|
@@ -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
|
Reference in New Issue
Block a user