mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-08-12 18:24:03 +02:00
change void nodes to have a single space, prevent text-less inlines
This commit is contained in:
@@ -158,9 +158,16 @@ class Leaf extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderText() {
|
renderText() {
|
||||||
const { text } = this.props
|
const { text, parent } = this.props
|
||||||
if (!text) return <br />
|
|
||||||
|
// If the text is empty, we need to render a <br/> to get the block to have
|
||||||
|
// the proper height.
|
||||||
|
if (text == '') return <br />
|
||||||
|
|
||||||
|
// COMPAT: Browsers will collapse trailing new lines, so we need to add an
|
||||||
|
// extra trailing new lines to prevent that.
|
||||||
if (text.charAt(text.length - 1) == '\n') return `${text}\n`
|
if (text.charAt(text.length - 1) == '\n') return `${text}\n`
|
||||||
|
|
||||||
return text
|
return text
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -59,18 +59,18 @@ class Node extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render a `node`.
|
* Render a `child` node.
|
||||||
*
|
*
|
||||||
* @param {Node} node
|
* @param {Node} child
|
||||||
* @return {Element} element
|
* @return {Element} element
|
||||||
*/
|
*/
|
||||||
|
|
||||||
renderNode = (node) => {
|
renderNode = (child) => {
|
||||||
const { editor, renderDecorations, renderMark, renderNode, state } = this.props
|
const { editor, renderDecorations, renderMark, renderNode, state } = this.props
|
||||||
return (
|
return (
|
||||||
<Node
|
<Node
|
||||||
key={node.key}
|
key={child.key}
|
||||||
node={node}
|
node={child}
|
||||||
state={state}
|
state={state}
|
||||||
editor={editor}
|
editor={editor}
|
||||||
renderDecorations={renderDecorations}
|
renderDecorations={renderDecorations}
|
||||||
|
@@ -141,7 +141,6 @@ class Void extends React.Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Leaf
|
<Leaf
|
||||||
ref={this.renderLeafRefs}
|
|
||||||
renderMark={this.renderLeafMark}
|
renderMark={this.renderLeafMark}
|
||||||
key={offsetKey}
|
key={offsetKey}
|
||||||
state={state}
|
state={state}
|
||||||
|
@@ -85,11 +85,7 @@ class Block extends new Record(DEFAULTS) {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
get isEmpty() {
|
get isEmpty() {
|
||||||
return (
|
return this.text == ''
|
||||||
this.nodes.size == 1 &&
|
|
||||||
this.nodes.first().kind == 'text' &&
|
|
||||||
this.length == 0
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -52,7 +52,17 @@ class Document extends new Record(DEFAULTS) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the length of the concatenated text of the node.
|
* Is the document empty?
|
||||||
|
*
|
||||||
|
* @return {Boolean} isEmpty
|
||||||
|
*/
|
||||||
|
|
||||||
|
get isEmpty() {
|
||||||
|
return this.text == ''
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the length of the concatenated text of the document.
|
||||||
*
|
*
|
||||||
* @return {Number} length
|
* @return {Number} length
|
||||||
*/
|
*/
|
||||||
|
@@ -85,11 +85,7 @@ class Inline extends new Record(DEFAULTS) {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
get isEmpty() {
|
get isEmpty() {
|
||||||
return (
|
return this.text == ''
|
||||||
this.nodes.size == 1 &&
|
|
||||||
this.nodes.first().kind == 'text' &&
|
|
||||||
this.length == 0
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -882,28 +882,34 @@ const Node = {
|
|||||||
|
|
||||||
// Map this node's descendants, ensuring...
|
// Map this node's descendants, ensuring...
|
||||||
node = node.mapDescendants((desc) => {
|
node = node.mapDescendants((desc) => {
|
||||||
|
if (removals.has(desc.key)) return desc
|
||||||
|
|
||||||
// ...that there are no duplicate keys.
|
// ...that there are no duplicate keys.
|
||||||
if (keys.has(desc.key)) desc = desc.set('key', uid())
|
if (keys.has(desc.key)) desc = desc.set('key', uid())
|
||||||
keys = keys.add(desc.key)
|
keys = keys.add(desc.key)
|
||||||
|
|
||||||
// ...that void nodes contain no text.
|
// ...that void nodes contain a single space of content.
|
||||||
if (desc.isVoid && desc.length) {
|
if (desc.isVoid && desc.text != ' ') {
|
||||||
let text = desc.getTexts().first()
|
desc = desc.merge({
|
||||||
let characters = text.characters.clear()
|
nodes: Text.createList([{
|
||||||
text = text.merge({ characters })
|
characters: Character.createList([{ text: ' ' }])
|
||||||
const nodes = desc.nodes.clear().push(text)
|
}])
|
||||||
desc = desc.merge({ nodes })
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// ...that no block or inline node is empty.
|
// ...that no block or inline has no text node inside it.
|
||||||
if (desc.kind != 'text' && desc.nodes.size == 0) {
|
if (desc.kind != 'text' && desc.nodes.size == 0) {
|
||||||
const text = Text.create()
|
const text = Text.create()
|
||||||
const nodes = desc.nodes.push(text)
|
const nodes = desc.nodes.push(text)
|
||||||
desc = desc.merge({ nodes })
|
desc = desc.merge({ nodes })
|
||||||
}
|
}
|
||||||
|
|
||||||
if (desc.kind == 'text' && !removals.has(desc.key)) {
|
// ...that no inline node is empty.
|
||||||
|
if (desc.kind == 'inline' && desc.text == '') {
|
||||||
|
removals = removals.add(desc.key)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (desc.kind == 'text') {
|
||||||
let next = node.getNextSibling(desc)
|
let next = node.getNextSibling(desc)
|
||||||
|
|
||||||
// ...that there are no adjacent text nodes.
|
// ...that there are no adjacent text nodes.
|
||||||
@@ -919,7 +925,9 @@ const Node = {
|
|||||||
// ...that there are no extra empty text nodes.
|
// ...that there are no extra empty text nodes.
|
||||||
else if (desc.length == 0) {
|
else if (desc.length == 0) {
|
||||||
const parent = node.getParent(desc)
|
const parent = node.getParent(desc)
|
||||||
if (parent.nodes.size > 1) removals = removals.add(desc.key)
|
if (!removals.has(parent.key) && parent.nodes.size > 1) {
|
||||||
|
removals = removals.add(desc.key)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -21,9 +21,7 @@ const Range = new Record({
|
|||||||
|
|
||||||
const DEFAULTS = {
|
const DEFAULTS = {
|
||||||
characters: new List(),
|
characters: new List(),
|
||||||
decorations: null,
|
key: null
|
||||||
key: null,
|
|
||||||
cache: null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -43,8 +41,6 @@ class Text extends new Record(DEFAULTS) {
|
|||||||
if (properties instanceof Text) return properties
|
if (properties instanceof Text) return properties
|
||||||
properties.key = properties.key || uid(4)
|
properties.key = properties.key || uid(4)
|
||||||
properties.characters = Character.createList(properties.characters)
|
properties.characters = Character.createList(properties.characters)
|
||||||
properties.decorations = null
|
|
||||||
properties.cache = null
|
|
||||||
return new Text(properties)
|
return new Text(properties)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,7 +73,7 @@ class Text extends new Record(DEFAULTS) {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
get isEmpty() {
|
get isEmpty() {
|
||||||
return this.length == 0
|
return this.text == ''
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -401,6 +401,15 @@ const Transforms = {
|
|||||||
range = range.collapseToStart()
|
range = range.collapseToStart()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { startKey, endKey, startOffset, endOffset } = range
|
||||||
|
|
||||||
|
// If the range is inside a void, abort.
|
||||||
|
const block = doc.getClosestBlock(startKey)
|
||||||
|
if (block && block.isVoid) return doc
|
||||||
|
|
||||||
|
const inline = doc.getClosestInline(startKey)
|
||||||
|
if (inline && inline.isVoid) return doc
|
||||||
|
|
||||||
// Allow for passing a type string.
|
// Allow for passing a type string.
|
||||||
if (typeof node == 'string') node = { type: node }
|
if (typeof node == 'string') node = { type: node }
|
||||||
|
|
||||||
@@ -411,7 +420,6 @@ const Transforms = {
|
|||||||
doc = doc.splitTextAtRange(range)
|
doc = doc.splitTextAtRange(range)
|
||||||
|
|
||||||
// Insert the node between the split text nodes.
|
// Insert the node between the split text nodes.
|
||||||
const { startKey, endKey, startOffset, endOffset } = range
|
|
||||||
const startText = doc.getDescendant(startKey)
|
const startText = doc.getDescendant(startKey)
|
||||||
let parent = doc.getParent(startKey)
|
let parent = doc.getParent(startKey)
|
||||||
const nodes = parent.nodes.takeUntil(n => n == startText)
|
const nodes = parent.nodes.takeUntil(n => n == startText)
|
||||||
|
@@ -72,7 +72,7 @@
|
|||||||
"lint": "eslint --ignore-pattern 'build.js' '{examples,lib}/**/*.js'",
|
"lint": "eslint --ignore-pattern 'build.js' '{examples,lib}/**/*.js'",
|
||||||
"prepublish": "npm run dist",
|
"prepublish": "npm run dist",
|
||||||
"start": "http-server ./examples",
|
"start": "http-server ./examples",
|
||||||
"test": "mocha --compilers js:babel-core/register --require source-map-support/register --reporter spec ./test/server.js"
|
"test": "mocha --compilers js:babel-core/register --reporter spec ./test/server.js"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"canvas",
|
"canvas",
|
||||||
|
@@ -4,4 +4,6 @@ nodes:
|
|||||||
isVoid: true
|
isVoid: true
|
||||||
data: {}
|
data: {}
|
||||||
nodes:
|
nodes:
|
||||||
- characters: []
|
- characters:
|
||||||
|
- text: " "
|
||||||
|
marks: []
|
||||||
|
@@ -4,8 +4,10 @@ nodes:
|
|||||||
isVoid: false
|
isVoid: false
|
||||||
data: {}
|
data: {}
|
||||||
nodes:
|
nodes:
|
||||||
- type: link
|
- type: link
|
||||||
isVoid: true
|
isVoid: true
|
||||||
data: {}
|
data: {}
|
||||||
nodes:
|
nodes:
|
||||||
- characters: []
|
- characters:
|
||||||
|
- text: " "
|
||||||
|
marks: []
|
||||||
|
@@ -4,4 +4,6 @@ nodes:
|
|||||||
isVoid: true
|
isVoid: true
|
||||||
data: {}
|
data: {}
|
||||||
nodes:
|
nodes:
|
||||||
- characters: []
|
- characters:
|
||||||
|
- text: " "
|
||||||
|
marks: []
|
||||||
|
@@ -8,4 +8,6 @@ nodes:
|
|||||||
isVoid: true
|
isVoid: true
|
||||||
data: {}
|
data: {}
|
||||||
nodes:
|
nodes:
|
||||||
- characters: []
|
- characters:
|
||||||
|
- text: " "
|
||||||
|
marks: []
|
||||||
|
@@ -4,4 +4,6 @@ nodes:
|
|||||||
isVoid: true
|
isVoid: true
|
||||||
data: {}
|
data: {}
|
||||||
nodes:
|
nodes:
|
||||||
- characters: []
|
- characters:
|
||||||
|
- text: " "
|
||||||
|
marks: []
|
||||||
|
@@ -4,8 +4,10 @@ nodes:
|
|||||||
isVoid: false
|
isVoid: false
|
||||||
data: {}
|
data: {}
|
||||||
nodes:
|
nodes:
|
||||||
- type: link
|
- type: link
|
||||||
isVoid: true
|
isVoid: true
|
||||||
data: {}
|
data: {}
|
||||||
nodes:
|
nodes:
|
||||||
- characters: []
|
- characters:
|
||||||
|
- text: " "
|
||||||
|
marks: []
|
||||||
|
@@ -11,5 +11,5 @@ document:
|
|||||||
- kind: text
|
- kind: text
|
||||||
ranges:
|
ranges:
|
||||||
- kind: range
|
- kind: range
|
||||||
text: ""
|
text: " "
|
||||||
marks: []
|
marks: []
|
||||||
|
@@ -16,5 +16,5 @@ document:
|
|||||||
- kind: text
|
- kind: text
|
||||||
ranges:
|
ranges:
|
||||||
- kind: range
|
- kind: range
|
||||||
text: ""
|
text: " "
|
||||||
marks: []
|
marks: []
|
||||||
|
@@ -60,7 +60,7 @@ describe('serializers', () => {
|
|||||||
const innerDir = resolve(dir, test)
|
const innerDir = resolve(dir, test)
|
||||||
const expected = readMetadata.sync(resolve(innerDir, 'output.yaml'))
|
const expected = readMetadata.sync(resolve(innerDir, 'output.yaml'))
|
||||||
const input = fs.readFileSync(resolve(innerDir, 'input.txt'), 'utf8')
|
const input = fs.readFileSync(resolve(innerDir, 'input.txt'), 'utf8')
|
||||||
const state = Plain.deserialize(input.trim())
|
const state = Plain.deserialize(input.replace(/\n$/m, ''))
|
||||||
const json = state.document.toJS()
|
const json = state.document.toJS()
|
||||||
strictEqual(strip(json), expected)
|
strictEqual(strip(json), expected)
|
||||||
})
|
})
|
||||||
@@ -77,7 +77,7 @@ describe('serializers', () => {
|
|||||||
const input = require(resolve(innerDir, 'input.js')).default
|
const input = require(resolve(innerDir, 'input.js')).default
|
||||||
const expected = fs.readFileSync(resolve(innerDir, 'output.txt'), 'utf8')
|
const expected = fs.readFileSync(resolve(innerDir, 'output.txt'), 'utf8')
|
||||||
const serialized = Plain.serialize(input)
|
const serialized = Plain.serialize(input)
|
||||||
strictEqual(serialized, expected.trim())
|
strictEqual(serialized, expected.replace(/\n$/m, ''))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@@ -12,6 +12,9 @@ export default function (state) {
|
|||||||
|
|
||||||
return state
|
return state
|
||||||
.transform()
|
.transform()
|
||||||
.insertInlineAtRange(range, 'hashtag')
|
.insertInlineAtRange(range, {
|
||||||
|
type: 'hashtag',
|
||||||
|
isVoid: true
|
||||||
|
})
|
||||||
.apply()
|
.apply()
|
||||||
}
|
}
|
||||||
|
@@ -7,6 +7,4 @@ nodes:
|
|||||||
text: word
|
text: word
|
||||||
- kind: inline
|
- kind: inline
|
||||||
type: hashtag
|
type: hashtag
|
||||||
nodes:
|
isVoid: true
|
||||||
- kind: text
|
|
||||||
text: ""
|
|
||||||
|
@@ -12,6 +12,9 @@ export default function (state) {
|
|||||||
|
|
||||||
return state
|
return state
|
||||||
.transform()
|
.transform()
|
||||||
.insertBlockAtRange(range, 'image')
|
.insertInlineAtRange(range, {
|
||||||
|
type: 'hashtag',
|
||||||
|
isVoid: true
|
||||||
|
})
|
||||||
.apply()
|
.apply()
|
||||||
}
|
}
|
||||||
|
@@ -5,13 +5,8 @@ nodes:
|
|||||||
nodes:
|
nodes:
|
||||||
- kind: text
|
- kind: text
|
||||||
text: wo
|
text: wo
|
||||||
- kind: block
|
- kind: inline
|
||||||
type: image
|
type: hashtag
|
||||||
nodes:
|
isVoid: true
|
||||||
- kind: text
|
|
||||||
text: ""
|
|
||||||
- kind: block
|
|
||||||
type: paragraph
|
|
||||||
nodes:
|
|
||||||
- kind: text
|
- kind: text
|
||||||
text: rd
|
text: rd
|
||||||
|
@@ -12,6 +12,9 @@ export default function (state) {
|
|||||||
|
|
||||||
return state
|
return state
|
||||||
.transform()
|
.transform()
|
||||||
.insertBlockAtRange(range, 'image')
|
.insertInlineAtRange(range, {
|
||||||
|
type: 'hashtag',
|
||||||
|
isVoid: true
|
||||||
|
})
|
||||||
.apply()
|
.apply()
|
||||||
}
|
}
|
||||||
|
@@ -1,12 +1,10 @@
|
|||||||
|
|
||||||
nodes:
|
nodes:
|
||||||
- kind: block
|
|
||||||
type: image
|
|
||||||
nodes:
|
|
||||||
- kind: text
|
|
||||||
text: ""
|
|
||||||
- kind: block
|
- kind: block
|
||||||
type: paragraph
|
type: paragraph
|
||||||
nodes:
|
nodes:
|
||||||
|
- kind: inline
|
||||||
|
type: hashtag
|
||||||
|
isVoid: true
|
||||||
- kind: text
|
- kind: text
|
||||||
text: word
|
text: word
|
||||||
|
@@ -12,6 +12,9 @@ export default function (state) {
|
|||||||
|
|
||||||
return state
|
return state
|
||||||
.transform()
|
.transform()
|
||||||
.insertBlockAtRange(range, 'image')
|
.insertInlineAtRange(range, {
|
||||||
|
type: 'hashtag',
|
||||||
|
isVoid: true
|
||||||
|
})
|
||||||
.apply()
|
.apply()
|
||||||
}
|
}
|
||||||
|
@@ -1,7 +1,8 @@
|
|||||||
|
|
||||||
nodes:
|
nodes:
|
||||||
- kind: block
|
- kind: block
|
||||||
type: image
|
type: paragraph
|
||||||
nodes:
|
nodes:
|
||||||
- kind: text
|
- kind: inline
|
||||||
text: ""
|
type: hashtag
|
||||||
|
isVoid: true
|
||||||
|
@@ -12,6 +12,9 @@ export default function (state) {
|
|||||||
|
|
||||||
return state
|
return state
|
||||||
.transform()
|
.transform()
|
||||||
.insertBlockAtRange(range, 'image')
|
.insertInlineAtRange(range, {
|
||||||
|
type: 'hashtag',
|
||||||
|
isVoid: true
|
||||||
|
})
|
||||||
.apply()
|
.apply()
|
||||||
}
|
}
|
||||||
|
@@ -3,6 +3,3 @@ nodes:
|
|||||||
- kind: block
|
- kind: block
|
||||||
type: image
|
type: image
|
||||||
isVoid: true
|
isVoid: true
|
||||||
nodes:
|
|
||||||
- kind: text
|
|
||||||
text: ""
|
|
||||||
|
@@ -3,8 +3,3 @@ nodes:
|
|||||||
- kind: block
|
- kind: block
|
||||||
type: image
|
type: image
|
||||||
isVoid: true
|
isVoid: true
|
||||||
- kind: block
|
|
||||||
type: image
|
|
||||||
nodes:
|
|
||||||
- kind: text
|
|
||||||
text: ""
|
|
||||||
|
@@ -1,12 +0,0 @@
|
|||||||
|
|
||||||
nodes:
|
|
||||||
- kind: block
|
|
||||||
type: image
|
|
||||||
nodes:
|
|
||||||
- kind: text
|
|
||||||
text: ""
|
|
||||||
- kind: block
|
|
||||||
type: paragraph
|
|
||||||
nodes:
|
|
||||||
- kind: text
|
|
||||||
text: word
|
|
@@ -1,5 +1,5 @@
|
|||||||
|
|
||||||
import { Block } from '../../../../..'
|
import { Inline } 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()
|
||||||
.insertBlockAtRange(range, Block.create({ type: 'image' }))
|
.insertInlineAtRange(range, Inline.create({
|
||||||
|
type: 'image',
|
||||||
|
isVoid: true
|
||||||
|
}))
|
||||||
.apply()
|
.apply()
|
||||||
}
|
}
|
@@ -3,5 +3,8 @@ nodes:
|
|||||||
- kind: block
|
- kind: block
|
||||||
type: paragraph
|
type: paragraph
|
||||||
nodes:
|
nodes:
|
||||||
|
- kind: inline
|
||||||
|
type: image
|
||||||
|
isVoid: true
|
||||||
- kind: text
|
- kind: text
|
||||||
text: word
|
text: word
|
@@ -1,17 +0,0 @@
|
|||||||
|
|
||||||
export default function (state) {
|
|
||||||
const { document, selection } = state
|
|
||||||
const texts = document.getTexts()
|
|
||||||
const first = texts.first()
|
|
||||||
const range = selection.merge({
|
|
||||||
anchorKey: first.key,
|
|
||||||
anchorOffset: 0,
|
|
||||||
focusKey: first.key,
|
|
||||||
focusOffset: 0
|
|
||||||
})
|
|
||||||
|
|
||||||
return state
|
|
||||||
.transform()
|
|
||||||
.insertBlockAtRange(range, { type: 'image' })
|
|
||||||
.apply()
|
|
||||||
}
|
|
@@ -1,12 +0,0 @@
|
|||||||
|
|
||||||
nodes:
|
|
||||||
- kind: block
|
|
||||||
type: image
|
|
||||||
nodes:
|
|
||||||
- kind: text
|
|
||||||
text: ""
|
|
||||||
- kind: block
|
|
||||||
type: paragraph
|
|
||||||
nodes:
|
|
||||||
- kind: text
|
|
||||||
text: word
|
|
@@ -11,11 +11,6 @@ nodes:
|
|||||||
- kind: block
|
- kind: block
|
||||||
type: paragraph
|
type: paragraph
|
||||||
nodes:
|
nodes:
|
||||||
- kind: inline
|
|
||||||
type: one
|
|
||||||
nodes:
|
|
||||||
- kind: text
|
|
||||||
text: ""
|
|
||||||
- kind: inline
|
- kind: inline
|
||||||
type: two
|
type: two
|
||||||
nodes:
|
nodes:
|
||||||
|
@@ -11,11 +11,6 @@ nodes:
|
|||||||
- kind: block
|
- kind: block
|
||||||
type: paragraph
|
type: paragraph
|
||||||
nodes:
|
nodes:
|
||||||
- kind: inline
|
|
||||||
type: one
|
|
||||||
nodes:
|
|
||||||
- kind: text
|
|
||||||
text: ""
|
|
||||||
- kind: inline
|
- kind: inline
|
||||||
type: two
|
type: two
|
||||||
nodes:
|
nodes:
|
||||||
|
Reference in New Issue
Block a user