diff --git a/lib/components/leaf.js b/lib/components/leaf.js
index e5cf4d24cc..83f0790a27 100644
--- a/lib/components/leaf.js
+++ b/lib/components/leaf.js
@@ -158,9 +158,16 @@ class Leaf extends React.Component {
}
renderText() {
- const { text } = this.props
- if (!text) return
+ const { text, parent } = this.props
+
+ // If the text is empty, we need to render a
to get the block to have
+ // the proper height.
+ if (text == '') return
+
+ // 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
}
diff --git a/lib/components/node.js b/lib/components/node.js
index 9369dd03d7..6458c3d41e 100644
--- a/lib/components/node.js
+++ b/lib/components/node.js
@@ -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 (
{
+ 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)
+ }
}
}
diff --git a/lib/models/text.js b/lib/models/text.js
index cddbc9668c..5c47e60e8c 100644
--- a/lib/models/text.js
+++ b/lib/models/text.js
@@ -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 == ''
}
/**
diff --git a/lib/models/transforms.js b/lib/models/transforms.js
index 26a4c35878..e957377192 100644
--- a/lib/models/transforms.js
+++ b/lib/models/transforms.js
@@ -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)
diff --git a/package.json b/package.json
index 29e10b94e5..3be71b126d 100644
--- a/package.json
+++ b/package.json
@@ -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",
diff --git a/test/serializers/fixtures/html/deserialize/block-with-is-void/output.yaml b/test/serializers/fixtures/html/deserialize/block-with-is-void/output.yaml
index c18f3f47e7..e67844d37e 100644
--- a/test/serializers/fixtures/html/deserialize/block-with-is-void/output.yaml
+++ b/test/serializers/fixtures/html/deserialize/block-with-is-void/output.yaml
@@ -4,4 +4,6 @@ nodes:
isVoid: true
data: {}
nodes:
- - characters: []
+ - characters:
+ - text: " "
+ marks: []
diff --git a/test/serializers/fixtures/html/deserialize/inline-with-is-void/output.yaml b/test/serializers/fixtures/html/deserialize/inline-with-is-void/output.yaml
index d43f535146..96e7ad63d4 100644
--- a/test/serializers/fixtures/html/deserialize/inline-with-is-void/output.yaml
+++ b/test/serializers/fixtures/html/deserialize/inline-with-is-void/output.yaml
@@ -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: []
diff --git a/test/serializers/fixtures/plain/serialize/block-with-is-void/output.txt b/test/serializers/fixtures/plain/serialize/block-with-is-void/output.txt
index e69de29bb2..8d1c8b69c3 100644
--- a/test/serializers/fixtures/plain/serialize/block-with-is-void/output.txt
+++ b/test/serializers/fixtures/plain/serialize/block-with-is-void/output.txt
@@ -0,0 +1 @@
+
diff --git a/test/serializers/fixtures/plain/serialize/inline-with-is-void/output.txt b/test/serializers/fixtures/plain/serialize/inline-with-is-void/output.txt
index e69de29bb2..8d1c8b69c3 100644
--- a/test/serializers/fixtures/plain/serialize/inline-with-is-void/output.txt
+++ b/test/serializers/fixtures/plain/serialize/inline-with-is-void/output.txt
@@ -0,0 +1 @@
+
diff --git a/test/serializers/fixtures/raw/deserialize-terse/block-with-is-void/output.yaml b/test/serializers/fixtures/raw/deserialize-terse/block-with-is-void/output.yaml
index c18f3f47e7..e67844d37e 100644
--- a/test/serializers/fixtures/raw/deserialize-terse/block-with-is-void/output.yaml
+++ b/test/serializers/fixtures/raw/deserialize-terse/block-with-is-void/output.yaml
@@ -4,4 +4,6 @@ nodes:
isVoid: true
data: {}
nodes:
- - characters: []
+ - characters:
+ - text: " "
+ marks: []
diff --git a/test/serializers/fixtures/raw/deserialize-terse/inline-with-is-void/output.yaml b/test/serializers/fixtures/raw/deserialize-terse/inline-with-is-void/output.yaml
index d43f535146..b06ff377bc 100644
--- a/test/serializers/fixtures/raw/deserialize-terse/inline-with-is-void/output.yaml
+++ b/test/serializers/fixtures/raw/deserialize-terse/inline-with-is-void/output.yaml
@@ -8,4 +8,6 @@ nodes:
isVoid: true
data: {}
nodes:
- - characters: []
+ - characters:
+ - text: " "
+ marks: []
diff --git a/test/serializers/fixtures/raw/deserialize/block-with-is-void/output.yaml b/test/serializers/fixtures/raw/deserialize/block-with-is-void/output.yaml
index c18f3f47e7..e67844d37e 100644
--- a/test/serializers/fixtures/raw/deserialize/block-with-is-void/output.yaml
+++ b/test/serializers/fixtures/raw/deserialize/block-with-is-void/output.yaml
@@ -4,4 +4,6 @@ nodes:
isVoid: true
data: {}
nodes:
- - characters: []
+ - characters:
+ - text: " "
+ marks: []
diff --git a/test/serializers/fixtures/raw/deserialize/inline-with-is-void/output.yaml b/test/serializers/fixtures/raw/deserialize/inline-with-is-void/output.yaml
index d43f535146..96e7ad63d4 100644
--- a/test/serializers/fixtures/raw/deserialize/inline-with-is-void/output.yaml
+++ b/test/serializers/fixtures/raw/deserialize/inline-with-is-void/output.yaml
@@ -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: []
diff --git a/test/serializers/fixtures/raw/serialize/block-with-is-void/output.yaml b/test/serializers/fixtures/raw/serialize/block-with-is-void/output.yaml
index 5cf46203ff..e015b47460 100644
--- a/test/serializers/fixtures/raw/serialize/block-with-is-void/output.yaml
+++ b/test/serializers/fixtures/raw/serialize/block-with-is-void/output.yaml
@@ -11,5 +11,5 @@ document:
- kind: text
ranges:
- kind: range
- text: ""
+ text: " "
marks: []
diff --git a/test/serializers/fixtures/raw/serialize/inline-with-is-void/output.yaml b/test/serializers/fixtures/raw/serialize/inline-with-is-void/output.yaml
index 7419d2ae20..d1dd09f0f2 100644
--- a/test/serializers/fixtures/raw/serialize/inline-with-is-void/output.yaml
+++ b/test/serializers/fixtures/raw/serialize/inline-with-is-void/output.yaml
@@ -16,5 +16,5 @@ document:
- kind: text
ranges:
- kind: range
- text: ""
+ text: " "
marks: []
diff --git a/test/serializers/index.js b/test/serializers/index.js
index b083fbacd4..8aefcda774 100644
--- a/test/serializers/index.js
+++ b/test/serializers/index.js
@@ -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, ''))
})
}
})
diff --git a/test/transforms/fixtures/insert-inline-at-range/block-end/index.js b/test/transforms/fixtures/insert-inline-at-range/block-end/index.js
index 93eca0c8cd..3e55f0b039 100644
--- a/test/transforms/fixtures/insert-inline-at-range/block-end/index.js
+++ b/test/transforms/fixtures/insert-inline-at-range/block-end/index.js
@@ -12,6 +12,9 @@ export default function (state) {
return state
.transform()
- .insertInlineAtRange(range, 'hashtag')
+ .insertInlineAtRange(range, {
+ type: 'hashtag',
+ isVoid: true
+ })
.apply()
}
diff --git a/test/transforms/fixtures/insert-inline-at-range/block-end/output.yaml b/test/transforms/fixtures/insert-inline-at-range/block-end/output.yaml
index 762f23589b..f333e4656d 100644
--- a/test/transforms/fixtures/insert-inline-at-range/block-end/output.yaml
+++ b/test/transforms/fixtures/insert-inline-at-range/block-end/output.yaml
@@ -7,6 +7,4 @@ nodes:
text: word
- kind: inline
type: hashtag
- nodes:
- - kind: text
- text: ""
+ isVoid: true
diff --git a/test/transforms/fixtures/insert-inline-at-range/block-middle/index.js b/test/transforms/fixtures/insert-inline-at-range/block-middle/index.js
index 3c3880f33a..3274a85169 100644
--- a/test/transforms/fixtures/insert-inline-at-range/block-middle/index.js
+++ b/test/transforms/fixtures/insert-inline-at-range/block-middle/index.js
@@ -12,6 +12,9 @@ export default function (state) {
return state
.transform()
- .insertBlockAtRange(range, 'image')
+ .insertInlineAtRange(range, {
+ type: 'hashtag',
+ isVoid: true
+ })
.apply()
}
diff --git a/test/transforms/fixtures/insert-inline-at-range/block-middle/output.yaml b/test/transforms/fixtures/insert-inline-at-range/block-middle/output.yaml
index 4d76e4866d..09ad0bf688 100644
--- a/test/transforms/fixtures/insert-inline-at-range/block-middle/output.yaml
+++ b/test/transforms/fixtures/insert-inline-at-range/block-middle/output.yaml
@@ -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
diff --git a/test/transforms/fixtures/insert-inline-at-range/block-start/index.js b/test/transforms/fixtures/insert-inline-at-range/block-start/index.js
index 31dd01775a..7a1c40d306 100644
--- a/test/transforms/fixtures/insert-inline-at-range/block-start/index.js
+++ b/test/transforms/fixtures/insert-inline-at-range/block-start/index.js
@@ -12,6 +12,9 @@ export default function (state) {
return state
.transform()
- .insertBlockAtRange(range, 'image')
+ .insertInlineAtRange(range, {
+ type: 'hashtag',
+ isVoid: true
+ })
.apply()
}
diff --git a/test/transforms/fixtures/insert-inline-at-range/block-start/output.yaml b/test/transforms/fixtures/insert-inline-at-range/block-start/output.yaml
index bf2ea138a6..b1b784956f 100644
--- a/test/transforms/fixtures/insert-inline-at-range/block-start/output.yaml
+++ b/test/transforms/fixtures/insert-inline-at-range/block-start/output.yaml
@@ -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
diff --git a/test/transforms/fixtures/insert-inline-at-range/is-empty/index.js b/test/transforms/fixtures/insert-inline-at-range/is-empty/index.js
index 31dd01775a..7a1c40d306 100644
--- a/test/transforms/fixtures/insert-inline-at-range/is-empty/index.js
+++ b/test/transforms/fixtures/insert-inline-at-range/is-empty/index.js
@@ -12,6 +12,9 @@ export default function (state) {
return state
.transform()
- .insertBlockAtRange(range, 'image')
+ .insertInlineAtRange(range, {
+ type: 'hashtag',
+ isVoid: true
+ })
.apply()
}
diff --git a/test/transforms/fixtures/insert-inline-at-range/is-empty/output.yaml b/test/transforms/fixtures/insert-inline-at-range/is-empty/output.yaml
index 68da1740ef..936e672db9 100644
--- a/test/transforms/fixtures/insert-inline-at-range/is-empty/output.yaml
+++ b/test/transforms/fixtures/insert-inline-at-range/is-empty/output.yaml
@@ -1,7 +1,8 @@
nodes:
- kind: block
- type: image
+ type: paragraph
nodes:
- - kind: text
- text: ""
+ - kind: inline
+ type: hashtag
+ isVoid: true
diff --git a/test/transforms/fixtures/insert-inline-at-range/is-void/index.js b/test/transforms/fixtures/insert-inline-at-range/is-void/index.js
index 31dd01775a..7a1c40d306 100644
--- a/test/transforms/fixtures/insert-inline-at-range/is-void/index.js
+++ b/test/transforms/fixtures/insert-inline-at-range/is-void/index.js
@@ -12,6 +12,9 @@ export default function (state) {
return state
.transform()
- .insertBlockAtRange(range, 'image')
+ .insertInlineAtRange(range, {
+ type: 'hashtag',
+ isVoid: true
+ })
.apply()
}
diff --git a/test/transforms/fixtures/insert-inline-at-range/is-void/input.yaml b/test/transforms/fixtures/insert-inline-at-range/is-void/input.yaml
index 1700caf757..8246cfbed4 100644
--- a/test/transforms/fixtures/insert-inline-at-range/is-void/input.yaml
+++ b/test/transforms/fixtures/insert-inline-at-range/is-void/input.yaml
@@ -3,6 +3,3 @@ nodes:
- kind: block
type: image
isVoid: true
- nodes:
- - kind: text
- text: ""
diff --git a/test/transforms/fixtures/insert-inline-at-range/is-void/output.yaml b/test/transforms/fixtures/insert-inline-at-range/is-void/output.yaml
index 8d64053753..8246cfbed4 100644
--- a/test/transforms/fixtures/insert-inline-at-range/is-void/output.yaml
+++ b/test/transforms/fixtures/insert-inline-at-range/is-void/output.yaml
@@ -3,8 +3,3 @@ nodes:
- kind: block
type: image
isVoid: true
- - kind: block
- type: image
- nodes:
- - kind: text
- text: ""
diff --git a/test/transforms/fixtures/insert-inline-at-range/with-block/output.yaml b/test/transforms/fixtures/insert-inline-at-range/with-block/output.yaml
deleted file mode 100644
index bf2ea138a6..0000000000
--- a/test/transforms/fixtures/insert-inline-at-range/with-block/output.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-
-nodes:
- - kind: block
- type: image
- nodes:
- - kind: text
- text: ""
- - kind: block
- type: paragraph
- nodes:
- - kind: text
- text: word
diff --git a/test/transforms/fixtures/insert-inline-at-range/with-block/index.js b/test/transforms/fixtures/insert-inline-at-range/with-inline/index.js
similarity index 70%
rename from test/transforms/fixtures/insert-inline-at-range/with-block/index.js
rename to test/transforms/fixtures/insert-inline-at-range/with-inline/index.js
index 27f0a28fc9..5a29040163 100644
--- a/test/transforms/fixtures/insert-inline-at-range/with-block/index.js
+++ b/test/transforms/fixtures/insert-inline-at-range/with-inline/index.js
@@ -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()
}
diff --git a/test/transforms/fixtures/insert-inline-at-range/with-block/input.yaml b/test/transforms/fixtures/insert-inline-at-range/with-inline/input.yaml
similarity index 100%
rename from test/transforms/fixtures/insert-inline-at-range/with-block/input.yaml
rename to test/transforms/fixtures/insert-inline-at-range/with-inline/input.yaml
diff --git a/test/transforms/fixtures/insert-inline-at-range/with-object/input.yaml b/test/transforms/fixtures/insert-inline-at-range/with-inline/output.yaml
similarity index 60%
rename from test/transforms/fixtures/insert-inline-at-range/with-object/input.yaml
rename to test/transforms/fixtures/insert-inline-at-range/with-inline/output.yaml
index 27f668fe22..3e75f2f14e 100644
--- a/test/transforms/fixtures/insert-inline-at-range/with-object/input.yaml
+++ b/test/transforms/fixtures/insert-inline-at-range/with-inline/output.yaml
@@ -3,5 +3,8 @@ nodes:
- kind: block
type: paragraph
nodes:
+ - kind: inline
+ type: image
+ isVoid: true
- kind: text
text: word
diff --git a/test/transforms/fixtures/insert-inline-at-range/with-object/index.js b/test/transforms/fixtures/insert-inline-at-range/with-object/index.js
deleted file mode 100644
index 4420b1d05a..0000000000
--- a/test/transforms/fixtures/insert-inline-at-range/with-object/index.js
+++ /dev/null
@@ -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()
-}
diff --git a/test/transforms/fixtures/insert-inline-at-range/with-object/output.yaml b/test/transforms/fixtures/insert-inline-at-range/with-object/output.yaml
deleted file mode 100644
index bf2ea138a6..0000000000
--- a/test/transforms/fixtures/insert-inline-at-range/with-object/output.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-
-nodes:
- - kind: block
- type: image
- nodes:
- - kind: text
- text: ""
- - kind: block
- type: paragraph
- nodes:
- - kind: text
- text: word
diff --git a/test/transforms/fixtures/split-block-at-range/with-delete-across-blocks-and-inlines/output.yaml b/test/transforms/fixtures/split-block-at-range/with-delete-across-blocks-and-inlines/output.yaml
index ee19749e50..b5fb12019f 100644
--- a/test/transforms/fixtures/split-block-at-range/with-delete-across-blocks-and-inlines/output.yaml
+++ b/test/transforms/fixtures/split-block-at-range/with-delete-across-blocks-and-inlines/output.yaml
@@ -11,11 +11,6 @@ nodes:
- kind: block
type: paragraph
nodes:
- - kind: inline
- type: one
- nodes:
- - kind: text
- text: ""
- kind: inline
type: two
nodes:
diff --git a/test/transforms/fixtures/split-block/with-delete-across-blocks-and-inlines/output.yaml b/test/transforms/fixtures/split-block/with-delete-across-blocks-and-inlines/output.yaml
index ee19749e50..b5fb12019f 100644
--- a/test/transforms/fixtures/split-block/with-delete-across-blocks-and-inlines/output.yaml
+++ b/test/transforms/fixtures/split-block/with-delete-across-blocks-and-inlines/output.yaml
@@ -11,11 +11,6 @@ nodes:
- kind: block
type: paragraph
nodes:
- - kind: inline
- type: one
- nodes:
- - kind: text
- text: ""
- kind: inline
type: two
nodes: