1
0
mirror of https://github.com/ianstormtaylor/slate.git synced 2025-08-07 07:46:32 +02:00

add isVoid property to nodes

This commit is contained in:
Ian Storm Taylor
2016-06-30 11:13:56 -07:00
parent aba40a2aaf
commit 1069fee13a
38 changed files with 329 additions and 94 deletions

View File

@@ -34,40 +34,25 @@ class Images extends React.Component {
} }
const { anchorBlock, selection } = state const { anchorBlock, selection } = state
let transform = state.transform()
if (anchorBlock.text == '') { if (anchorBlock.text != '') {
state = state if (selection.isAtEndOf(anchorBlock)) {
.transform() transform = transform.splitBlock()
.setBlock('image', { src }) } else if (selection.isAtStartOf(anchorBlock)) {
.apply() transform = transform.splitBlock().moveToStartOfPreviousBlock()
} else {
transform = transform.splitBlock().splitBlock().moveToStartOfPreviousBlock()
}
} }
else if (selection.isAtEndOf(anchorBlock)) { state = transform
state = state .setBlock({
.transform() type: 'image',
.splitBlock() isVoid: true,
.setBlock('image', { src }) data: { src }
})
.apply() .apply()
}
else if (selection.isAtStartOf(anchorBlock)) {
state = state
.transform()
.splitBlock()
.moveToStartOfPreviousBlock()
.setBlock('image', { src })
.apply()
}
else {
state = state
.transform()
.splitBlock()
.splitBlock()
.moveToStartOfPreviousBlock()
.setBlock('image', { src })
.apply()
}
this.setState({ state }) this.setState({ state })
} }

View File

@@ -17,8 +17,8 @@
{ {
"kind": "block", "kind": "block",
"type": "image", "type": "image",
"data": {
"isVoid": true, "isVoid": true,
"data": {
"src": "https://img.washingtonpost.com/wp-apps/imrs.php?src=https://img.washingtonpost.com/news/speaking-of-science/wp-content/uploads/sites/36/2015/10/as12-49-7278-1024x1024.jpg&w=1484" "src": "https://img.washingtonpost.com/wp-apps/imrs.php?src=https://img.washingtonpost.com/news/speaking-of-science/wp-content/uploads/sites/36/2015/10/as12-49-7278-1024x1024.jpg&w=1484"
}, },
"nodes": [ "nodes": [

View File

@@ -371,7 +371,7 @@ class Content extends React.Component {
</Component> </Component>
) )
return node.data.get('isVoid') return node.isVoid
? this.renderVoid(element, node) ? this.renderVoid(element, node)
: element : element
} }

View File

@@ -23,6 +23,7 @@ import Immutable, { Map, List, Record } from 'immutable'
const DEFAULTS = { const DEFAULTS = {
data: new Map(), data: new Map(),
isVoid: false,
key: null, key: null,
nodes: new List(), nodes: new List(),
type: null type: null
@@ -49,6 +50,7 @@ class Block extends Record(DEFAULTS) {
properties.key = uid(4) properties.key = uid(4)
properties.data = Data.create(properties.data) properties.data = Data.create(properties.data)
properties.isVoid = !! properties.isVoid
properties.nodes = Block.createList(properties.nodes) properties.nodes = Block.createList(properties.nodes)
return new Block(properties).normalize() return new Block(properties).normalize()

View File

@@ -23,6 +23,7 @@ import { List, Map, Record } from 'immutable'
const DEFAULTS = { const DEFAULTS = {
data: new Map(), data: new Map(),
isVoid: false,
key: null, key: null,
nodes: new List(), nodes: new List(),
type: null type: null
@@ -49,6 +50,7 @@ class Inline extends Record(DEFAULTS) {
properties.key = uid(4) properties.key = uid(4)
properties.data = Data.create(properties.data) properties.data = Data.create(properties.data)
properties.isVoid = !! properties.isVoid
properties.nodes = Inline.createList(properties.nodes) properties.nodes = Inline.createList(properties.nodes)
let inline = new Inline(properties) let inline = new Inline(properties)

View File

@@ -806,19 +806,30 @@ const Node = {
normalize() { normalize() {
let node = this let node = this
// If this node has no children, add a text node. // Map this node's descendants, ensuring... ensuring there are no duplicate keys.
if (node.nodes.size == 0) { const keys = []
node = node.mapDescendants((desc) => {
// That there are no duplicate keys.
if (keys.includes(desc.key)) desc = desc.set('key', uid())
keys.push(desc.key)
// That void nodes contain no text.
if (desc.isVoid && desc.length) {
const text = Text.create() const text = Text.create()
const nodes = node.nodes.push(text) const nodes = desc.nodes.clear().push(text)
return node.merge({ nodes }) desc = desc.merge({ nodes })
} }
// Map this node's descendants, ensuring there are no duplicate keys. // That no block or inline node is empty.
const keys = [] if (desc.kind != 'text' && desc.nodes.size == 0) {
node = node.mapDescendants((node) => { const text = Text.create()
if (keys.includes(node.key)) node = node.set('key', uid()) const nodes = desc.nodes.push(text)
keys.push(node.key) desc = desc.merge({ nodes })
return node }
return desc
}) })
// See if there are any adjacent text nodes. // See if there are any adjacent text nodes.

View File

@@ -562,31 +562,31 @@ class State extends Record(DEFAULTS) {
} }
/** /**
* Set the block nodes in the current selection to `type`. * Set `properties` of the block nodes in the current selection.
* *
* @param {String} type * @param {Object} properties
* @return {State} state * @return {State} state
*/ */
setBlock(type, data) { setBlock(properties) {
let state = this let state = this
let { document, selection } = state let { document, selection } = state
document = document.setBlockAtRange(selection, type, data) document = document.setBlockAtRange(selection, properties)
state = state.merge({ document }) state = state.merge({ document })
return state return state
} }
/** /**
* Set the inline nodes in the current selection to `type`. * Set `properties` of the inline nodes in the current selection.
* *
* @param {String} type * @param {Object} properties
* @return {State} state * @return {State} state
*/ */
setInline(type, data) { setInline(properties) {
let state = this let state = this
let { document, selection } = state let { document, selection } = state
document = document.setInlineAtRange(selection, type, data) document = document.setInlineAtRange(selection, properties)
state = state.merge({ document }) state = state.merge({ document })
return state return state
} }

View File

@@ -324,65 +324,57 @@ const Transforms = {
}, },
/** /**
* Set the block nodes in a range to `type`, with optional `data`. * Set the `properties` of block nodes in a `range`.
* *
* @param {Selection} range * @param {Selection} range
* @param {String} type (optional) * @param {Object or String} properties
* @param {Data} data (optional)
* @return {Node} node * @return {Node} node
*/ */
setBlockAtRange(range, type, data) { setBlockAtRange(range, properties = {}) {
let node = this let node = this
// Allow for passing data only. // Allow for properties to be a string `type` for convenience.
if (typeof type == 'object') { if (typeof properties == 'string') {
data = type properties = { type: properties }
type = null
} }
// Update each of the blocks. // Update each of the blocks.
const blocks = node.getBlocksAtRange(range) const blocks = node.getBlocksAtRange(range)
blocks.forEach((block) => { blocks.forEach((block) => {
const obj = {} if (properties.data) properties.data = Data.create(properties.data)
if (type) obj.type = type block = block.merge(properties)
if (data) obj.data = Data.create(data)
block = block.merge(obj)
node = node.updateDescendant(block) node = node.updateDescendant(block)
}) })
return node return node.normalize()
}, },
/** /**
* Set the inline nodes in a range to `type`, with optional `data`. * Set the `properties` of inline nodes in a `range`.
* *
* @param {Selection} range * @param {Selection} range
* @param {String} type (optional) * @param {Object or String} properties
* @param {Data} data (optional)
* @return {Node} node * @return {Node} node
*/ */
setInlineAtRange(range, type, data) { setInlineAtRange(range, properties = {}) {
let node = this let node = this
// Allow for passing data only. // Allow for properties to be a string `type` for convenience.
if (typeof type == 'object') { if (typeof properties == 'string') {
data = type properties = { type: properties }
type = null
} }
// Update each of the inlines. // Update each of the inlines.
const inlines = node.getInlinesAtRange(range) const inlines = node.getInlinesAtRange(range)
inlines.forEach((inline) => { inlines.forEach((inline) => {
const obj = {} if (properties.data) properties.data = Data.create(properties.data)
if (type) obj.type = type inline = inline.merge(properties)
if (data) obj.data = Data.create(data)
inline = inline.merge(obj)
node = node.updateDescendant(inline) node = node.updateDescendant(inline)
}) })
return node return node.normalize()
}, },
/** /**

View File

@@ -46,6 +46,7 @@ function serializeNode(node) {
obj.kind = node.kind obj.kind = node.kind
obj.type = node.type obj.type = node.type
obj.nodes = node.nodes.toArray().map(node => serializeNode(node)) obj.nodes = node.nodes.toArray().map(node => serializeNode(node))
if (node.isVoid) obj.isVoid = node.isVoid
if (node.data.size) obj.data = node.data.toJSON() if (node.data.size) obj.data = node.data.toJSON()
return obj return obj
} }
@@ -112,6 +113,7 @@ function deserializeNode(object) {
return Block.create({ return Block.create({
type: object.type, type: object.type,
data: object.data, data: object.data,
isVoid: object.isVoid,
nodes: Block.createList(object.nodes.map(deserializeNode)) nodes: Block.createList(object.nodes.map(deserializeNode))
}) })
} }
@@ -119,6 +121,7 @@ function deserializeNode(object) {
return Inline.create({ return Inline.create({
type: object.type, type: object.type,
data: object.data, data: object.data,
isVoid: object.isVoid,
nodes: Inline.createList(object.nodes.map(deserializeNode)) nodes: Inline.createList(object.nodes.map(deserializeNode))
}) })
} }

View File

@@ -13,6 +13,6 @@ export default function (state) {
return state return state
.transform() .transform()
.setBlockAtRange(range, 'code') .setBlockAtRange(range, { type: 'code' })
.apply() .apply()
} }

View File

@@ -13,6 +13,6 @@ export default function (state) {
return state return state
.transform() .transform()
.setBlockAtRange(range, 'code') .setBlockAtRange(range, { type: 'code' })
.apply() .apply()
} }

View File

@@ -1,5 +1,5 @@
import { Map } from 'immutable' import { Data } from '../../../../..'
export default function (state) { export default function (state) {
const { document, selection } = state const { document, selection } = state
@@ -14,6 +14,6 @@ export default function (state) {
return state return state
.transform() .transform()
.setBlockAtRange(range, new Map({ key: 'value' })) .setBlockAtRange(range, { data: Data.create({ key: 'value' }) })
.apply() .apply()
} }

View File

@@ -12,6 +12,6 @@ export default function (state) {
return state return state
.transform() .transform()
.setBlockAtRange(range, 'code') .setBlockAtRange(range, { type: 'code' })
.apply() .apply()
} }

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()
.setBlockAtRange(range, 'code')
.apply()
}

View File

@@ -0,0 +1,8 @@
nodes:
- kind: block
type: paragraph
nodes:
- kind: text
ranges:
- text: word

View File

@@ -0,0 +1,8 @@
nodes:
- kind: block
type: code
nodes:
- kind: text
ranges:
- text: word

View File

@@ -12,6 +12,6 @@ export default function (state) {
return state return state
.transform() .transform()
.setBlockAtRange(range, 'code') .setBlockAtRange(range, { type: 'code' })
.apply() .apply()
} }

View File

@@ -0,0 +1,20 @@
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()
.setBlockAtRange(range, {
type: 'code',
data: { key: 'value' }
})
.apply()
}

View File

@@ -0,0 +1,8 @@
nodes:
- kind: block
type: paragraph
nodes:
- kind: text
ranges:
- text: word

View File

@@ -0,0 +1,10 @@
nodes:
- kind: block
type: code
data:
key: value
nodes:
- kind: text
ranges:
- text: word

View File

@@ -1,5 +1,5 @@
import { Map } from 'immutable' import { Data } from '../../../../..'
export default function (state) { export default function (state) {
const { document, selection } = state const { document, selection } = state
@@ -14,6 +14,9 @@ export default function (state) {
return state return state
.transform() .transform()
.setBlockAtRange(range, 'code', new Map({ key: 'value' })) .setBlockAtRange(range, {
type: 'code',
data: Data.create({ key: 'value' })
})
.apply() .apply()
} }

View File

@@ -0,0 +1,20 @@
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()
.setBlockAtRange(range, {
type: 'image',
isVoid: true
})
.apply()
}

View File

@@ -0,0 +1,8 @@
nodes:
- kind: block
type: paragraph
nodes:
- kind: text
ranges:
- text: word

View File

@@ -0,0 +1,9 @@
nodes:
- kind: block
type: image
isVoid: true
nodes:
- kind: text
ranges:
- text: ""

View File

@@ -13,6 +13,6 @@ export default function (state) {
return state return state
.transform() .transform()
.setInlineAtRange(range, 'code') .setInlineAtRange(range, { type: 'code' })
.apply() .apply()
} }

View File

@@ -1,5 +1,5 @@
import { Map } from 'immutable' import { Data } from '../../../../..'
export default function (state) { export default function (state) {
const { document, selection } = state const { document, selection } = state
@@ -14,6 +14,6 @@ export default function (state) {
return state return state
.transform() .transform()
.setInlineAtRange(range, new Map({ key: 'value' })) .setInlineAtRange(range, { data: Data.create({ key: 'value' }) })
.apply() .apply()
} }

View File

@@ -12,6 +12,6 @@ export default function (state) {
return state return state
.transform() .transform()
.setInlineAtRange(range, 'code') .setInlineAtRange(range, { type: 'code' })
.apply() .apply()
} }

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()
.setInlineAtRange(range, 'code')
.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,11 @@
nodes:
- kind: block
type: paragraph
nodes:
- kind: inline
type: code
nodes:
- kind: text
ranges:
- text: word

View File

@@ -12,6 +12,6 @@ export default function (state) {
return state return state
.transform() .transform()
.setInlineAtRange(range, 'code') .setInlineAtRange(range, { type: 'code' })
.apply() .apply()
} }

View File

@@ -0,0 +1,20 @@
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()
.setInlineAtRange(range, {
type: 'code',
data: { key: 'value' }
})
.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,13 @@
nodes:
- kind: block
type: paragraph
nodes:
- kind: inline
type: code
data:
key: value
nodes:
- kind: text
ranges:
- text: word

View File

@@ -1,5 +1,5 @@
import { Map } from 'immutable' import { Data } from '../../../../..'
export default function (state) { export default function (state) {
const { document, selection } = state const { document, selection } = state
@@ -14,6 +14,9 @@ export default function (state) {
return state return state
.transform() .transform()
.setInlineAtRange(range, 'code', new Map({ key: 'value' })) .setInlineAtRange(range, {
type: 'code',
data: Data.create({ key: 'value' })
})
.apply() .apply()
} }

View File

@@ -0,0 +1,20 @@
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()
.setInlineAtRange(range, {
type: 'emoji',
isVoid: true
})
.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,12 @@
nodes:
- kind: block
type: paragraph
nodes:
- kind: inline
type: emoji
isVoid: true
nodes:
- kind: text
ranges:
- text: ""