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: