mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-04-22 14:21:54 +02:00
change void nodes to have a single space, prevent text-less inlines
This commit is contained in:
parent
807d9f93e4
commit
42cbcb7e8d
@ -158,9 +158,16 @@ class Leaf extends React.Component {
|
||||
}
|
||||
|
||||
renderText() {
|
||||
const { text } = this.props
|
||||
if (!text) return <br />
|
||||
const { text, parent } = this.props
|
||||
|
||||
// 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`
|
||||
|
||||
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
|
||||
*/
|
||||
|
||||
renderNode = (node) => {
|
||||
renderNode = (child) => {
|
||||
const { editor, renderDecorations, renderMark, renderNode, state } = this.props
|
||||
return (
|
||||
<Node
|
||||
key={node.key}
|
||||
node={node}
|
||||
key={child.key}
|
||||
node={child}
|
||||
state={state}
|
||||
editor={editor}
|
||||
renderDecorations={renderDecorations}
|
||||
|
@ -141,7 +141,6 @@ class Void extends React.Component {
|
||||
|
||||
return (
|
||||
<Leaf
|
||||
ref={this.renderLeafRefs}
|
||||
renderMark={this.renderLeafMark}
|
||||
key={offsetKey}
|
||||
state={state}
|
||||
|
@ -85,11 +85,7 @@ class Block extends new Record(DEFAULTS) {
|
||||
*/
|
||||
|
||||
get isEmpty() {
|
||||
return (
|
||||
this.nodes.size == 1 &&
|
||||
this.nodes.first().kind == 'text' &&
|
||||
this.length == 0
|
||||
)
|
||||
return this.text == ''
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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
|
||||
*/
|
||||
|
@ -85,11 +85,7 @@ class Inline extends new Record(DEFAULTS) {
|
||||
*/
|
||||
|
||||
get isEmpty() {
|
||||
return (
|
||||
this.nodes.size == 1 &&
|
||||
this.nodes.first().kind == 'text' &&
|
||||
this.length == 0
|
||||
)
|
||||
return this.text == ''
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -882,28 +882,34 @@ const Node = {
|
||||
|
||||
// Map this node's descendants, ensuring...
|
||||
node = node.mapDescendants((desc) => {
|
||||
if (removals.has(desc.key)) return desc
|
||||
|
||||
// ...that there are no duplicate keys.
|
||||
if (keys.has(desc.key)) desc = desc.set('key', uid())
|
||||
keys = keys.add(desc.key)
|
||||
|
||||
// ...that void nodes contain no text.
|
||||
if (desc.isVoid && desc.length) {
|
||||
let text = desc.getTexts().first()
|
||||
let characters = text.characters.clear()
|
||||
text = text.merge({ characters })
|
||||
const nodes = desc.nodes.clear().push(text)
|
||||
desc = desc.merge({ nodes })
|
||||
// ...that void nodes contain a single space of content.
|
||||
if (desc.isVoid && desc.text != ' ') {
|
||||
desc = desc.merge({
|
||||
nodes: Text.createList([{
|
||||
characters: Character.createList([{ text: ' ' }])
|
||||
}])
|
||||
})
|
||||
}
|
||||
|
||||
// ...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) {
|
||||
const text = Text.create()
|
||||
const nodes = desc.nodes.push(text)
|
||||
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)
|
||||
|
||||
// ...that there are no adjacent text nodes.
|
||||
@ -919,7 +925,9 @@ const Node = {
|
||||
// ...that there are no extra empty text nodes.
|
||||
else if (desc.length == 0) {
|
||||
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 = {
|
||||
characters: new List(),
|
||||
decorations: null,
|
||||
key: null,
|
||||
cache: null
|
||||
key: null
|
||||
}
|
||||
|
||||
/**
|
||||
@ -43,8 +41,6 @@ class Text extends new Record(DEFAULTS) {
|
||||
if (properties instanceof Text) return properties
|
||||
properties.key = properties.key || uid(4)
|
||||
properties.characters = Character.createList(properties.characters)
|
||||
properties.decorations = null
|
||||
properties.cache = null
|
||||
return new Text(properties)
|
||||
}
|
||||
|
||||
@ -77,7 +73,7 @@ class Text extends new Record(DEFAULTS) {
|
||||
*/
|
||||
|
||||
get isEmpty() {
|
||||
return this.length == 0
|
||||
return this.text == ''
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -401,6 +401,15 @@ const Transforms = {
|
||||
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.
|
||||
if (typeof node == 'string') node = { type: node }
|
||||
|
||||
@ -411,7 +420,6 @@ const Transforms = {
|
||||
doc = doc.splitTextAtRange(range)
|
||||
|
||||
// Insert the node between the split text nodes.
|
||||
const { startKey, endKey, startOffset, endOffset } = range
|
||||
const startText = doc.getDescendant(startKey)
|
||||
let parent = doc.getParent(startKey)
|
||||
const nodes = parent.nodes.takeUntil(n => n == startText)
|
||||
|
@ -72,7 +72,7 @@
|
||||
"lint": "eslint --ignore-pattern 'build.js' '{examples,lib}/**/*.js'",
|
||||
"prepublish": "npm run dist",
|
||||
"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": [
|
||||
"canvas",
|
||||
|
@ -4,4 +4,6 @@ nodes:
|
||||
isVoid: true
|
||||
data: {}
|
||||
nodes:
|
||||
- characters: []
|
||||
- characters:
|
||||
- text: " "
|
||||
marks: []
|
||||
|
@ -4,8 +4,10 @@ nodes:
|
||||
isVoid: false
|
||||
data: {}
|
||||
nodes:
|
||||
- type: link
|
||||
isVoid: true
|
||||
data: {}
|
||||
nodes:
|
||||
- characters: []
|
||||
- type: link
|
||||
isVoid: true
|
||||
data: {}
|
||||
nodes:
|
||||
- characters:
|
||||
- text: " "
|
||||
marks: []
|
||||
|
@ -4,4 +4,6 @@ nodes:
|
||||
isVoid: true
|
||||
data: {}
|
||||
nodes:
|
||||
- characters: []
|
||||
- characters:
|
||||
- text: " "
|
||||
marks: []
|
||||
|
@ -8,4 +8,6 @@ nodes:
|
||||
isVoid: true
|
||||
data: {}
|
||||
nodes:
|
||||
- characters: []
|
||||
- characters:
|
||||
- text: " "
|
||||
marks: []
|
||||
|
@ -4,4 +4,6 @@ nodes:
|
||||
isVoid: true
|
||||
data: {}
|
||||
nodes:
|
||||
- characters: []
|
||||
- characters:
|
||||
- text: " "
|
||||
marks: []
|
||||
|
@ -4,8 +4,10 @@ nodes:
|
||||
isVoid: false
|
||||
data: {}
|
||||
nodes:
|
||||
- type: link
|
||||
isVoid: true
|
||||
data: {}
|
||||
nodes:
|
||||
- characters: []
|
||||
- type: link
|
||||
isVoid: true
|
||||
data: {}
|
||||
nodes:
|
||||
- characters:
|
||||
- text: " "
|
||||
marks: []
|
||||
|
@ -11,5 +11,5 @@ document:
|
||||
- kind: text
|
||||
ranges:
|
||||
- kind: range
|
||||
text: ""
|
||||
text: " "
|
||||
marks: []
|
||||
|
@ -16,5 +16,5 @@ document:
|
||||
- kind: text
|
||||
ranges:
|
||||
- kind: range
|
||||
text: ""
|
||||
text: " "
|
||||
marks: []
|
||||
|
@ -60,7 +60,7 @@ describe('serializers', () => {
|
||||
const innerDir = resolve(dir, test)
|
||||
const expected = readMetadata.sync(resolve(innerDir, 'output.yaml'))
|
||||
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()
|
||||
strictEqual(strip(json), expected)
|
||||
})
|
||||
@ -77,7 +77,7 @@ describe('serializers', () => {
|
||||
const input = require(resolve(innerDir, 'input.js')).default
|
||||
const expected = fs.readFileSync(resolve(innerDir, 'output.txt'), 'utf8')
|
||||
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
|
||||
.transform()
|
||||
.insertInlineAtRange(range, 'hashtag')
|
||||
.insertInlineAtRange(range, {
|
||||
type: 'hashtag',
|
||||
isVoid: true
|
||||
})
|
||||
.apply()
|
||||
}
|
||||
|
@ -7,6 +7,4 @@ nodes:
|
||||
text: word
|
||||
- kind: inline
|
||||
type: hashtag
|
||||
nodes:
|
||||
- kind: text
|
||||
text: ""
|
||||
isVoid: true
|
||||
|
@ -12,6 +12,9 @@ export default function (state) {
|
||||
|
||||
return state
|
||||
.transform()
|
||||
.insertBlockAtRange(range, 'image')
|
||||
.insertInlineAtRange(range, {
|
||||
type: 'hashtag',
|
||||
isVoid: true
|
||||
})
|
||||
.apply()
|
||||
}
|
||||
|
@ -5,13 +5,8 @@ nodes:
|
||||
nodes:
|
||||
- kind: text
|
||||
text: wo
|
||||
- kind: block
|
||||
type: image
|
||||
nodes:
|
||||
- kind: text
|
||||
text: ""
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: inline
|
||||
type: hashtag
|
||||
isVoid: true
|
||||
- kind: text
|
||||
text: rd
|
||||
|
@ -12,6 +12,9 @@ export default function (state) {
|
||||
|
||||
return state
|
||||
.transform()
|
||||
.insertBlockAtRange(range, 'image')
|
||||
.insertInlineAtRange(range, {
|
||||
type: 'hashtag',
|
||||
isVoid: true
|
||||
})
|
||||
.apply()
|
||||
}
|
||||
|
@ -1,12 +1,10 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: image
|
||||
nodes:
|
||||
- kind: text
|
||||
text: ""
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: inline
|
||||
type: hashtag
|
||||
isVoid: true
|
||||
- kind: text
|
||||
text: word
|
||||
|
@ -12,6 +12,9 @@ export default function (state) {
|
||||
|
||||
return state
|
||||
.transform()
|
||||
.insertBlockAtRange(range, 'image')
|
||||
.insertInlineAtRange(range, {
|
||||
type: 'hashtag',
|
||||
isVoid: true
|
||||
})
|
||||
.apply()
|
||||
}
|
||||
|
@ -1,7 +1,8 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: image
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
text: ""
|
||||
- kind: inline
|
||||
type: hashtag
|
||||
isVoid: true
|
||||
|
@ -12,6 +12,9 @@ export default function (state) {
|
||||
|
||||
return state
|
||||
.transform()
|
||||
.insertBlockAtRange(range, 'image')
|
||||
.insertInlineAtRange(range, {
|
||||
type: 'hashtag',
|
||||
isVoid: true
|
||||
})
|
||||
.apply()
|
||||
}
|
||||
|
@ -3,6 +3,3 @@ nodes:
|
||||
- kind: block
|
||||
type: image
|
||||
isVoid: true
|
||||
nodes:
|
||||
- kind: text
|
||||
text: ""
|
||||
|
@ -3,8 +3,3 @@ nodes:
|
||||
- kind: block
|
||||
type: image
|
||||
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) {
|
||||
const { document, selection } = state
|
||||
@ -14,6 +14,9 @@ export default function (state) {
|
||||
|
||||
return state
|
||||
.transform()
|
||||
.insertBlockAtRange(range, Block.create({ type: 'image' }))
|
||||
.insertInlineAtRange(range, Inline.create({
|
||||
type: 'image',
|
||||
isVoid: true
|
||||
}))
|
||||
.apply()
|
||||
}
|
@ -3,5 +3,8 @@ nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: inline
|
||||
type: image
|
||||
isVoid: true
|
||||
- kind: text
|
||||
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
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: inline
|
||||
type: one
|
||||
nodes:
|
||||
- kind: text
|
||||
text: ""
|
||||
- kind: inline
|
||||
type: two
|
||||
nodes:
|
||||
|
@ -11,11 +11,6 @@ nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: inline
|
||||
type: one
|
||||
nodes:
|
||||
- kind: text
|
||||
text: ""
|
||||
- kind: inline
|
||||
type: two
|
||||
nodes:
|
||||
|
Loading…
x
Reference in New Issue
Block a user