From feb293aaa2c02fe3ad319bd021e66908ee770a6e Mon Sep 17 00:00:00 2001 From: Andrew Herron Date: Thu, 6 May 2021 09:39:37 +1000 Subject: [PATCH] Apply no-children normalization fix before normalization (#4208) * Test cases for failure condition * Before normalizing ensure all elements have at least one text child. This is a normalization requirement that some normalization fixes require, so it must be done as an initial dedicated pass. --- .changeset/silver-snakes-perform.md | 5 ++ packages/slate/src/interfaces/editor.ts | 22 +++++ .../transforms/normalization/move_node.tsx | 21 +++++ .../split_node-and-insert_node.tsx | 84 +++++++++++++++++++ 4 files changed, 132 insertions(+) create mode 100644 .changeset/silver-snakes-perform.md create mode 100644 packages/slate/test/transforms/normalization/move_node.tsx create mode 100644 packages/slate/test/transforms/normalization/split_node-and-insert_node.tsx diff --git a/.changeset/silver-snakes-perform.md b/.changeset/silver-snakes-perform.md new file mode 100644 index 000000000..1143ace00 --- /dev/null +++ b/.changeset/silver-snakes-perform.md @@ -0,0 +1,5 @@ +--- +'slate': patch +--- + +Fix `Error: Cannot get the start point in the node at path [...] because it has no start text node` caused by normalizing a document where some elements have no children diff --git a/packages/slate/src/interfaces/editor.ts b/packages/slate/src/interfaces/editor.ts index d6b4eb440..cbc3cd85b 100644 --- a/packages/slate/src/interfaces/editor.ts +++ b/packages/slate/src/interfaces/editor.ts @@ -16,6 +16,7 @@ import { RangeRef, Span, Text, + Transforms, } from '..' import { DIRTY_PATHS, @@ -988,6 +989,27 @@ export const Editor: EditorInterface = { } Editor.withoutNormalizing(editor, () => { + /* + Fix dirty elements with no children. + editor.normalizeNode() does fix this, but some normalization fixes also require it to work. + Running an initial pass avoids the catch-22 race condition. + */ + for (const dirtyPath of getDirtyPaths(editor)) { + if (Node.has(editor, dirtyPath)) { + const [node, _] = Editor.node(editor, dirtyPath) + + // Add a text child to elements with no children. + // This is safe to do in any order, by definition it can't cause other paths to change. + if (Element.isElement(node) && node.children.length === 0) { + const child = { text: '' } + Transforms.insertNodes(editor, child, { + at: dirtyPath.concat(0), + voids: true, + }) + } + } + } + const max = getDirtyPaths(editor).length * 42 // HACK: better way? let m = 0 diff --git a/packages/slate/test/transforms/normalization/move_node.tsx b/packages/slate/test/transforms/normalization/move_node.tsx new file mode 100644 index 000000000..9a21a805d --- /dev/null +++ b/packages/slate/test/transforms/normalization/move_node.tsx @@ -0,0 +1,21 @@ +/** @jsx jsx */ +import { Transforms } from 'slate' +import { jsx } from '../..' + +export const input = ( + + one + two + +) +export const run = editor => { + Transforms.moveNodes(editor, { at: [0, 0], to: [1, 0] }) +} +export const output = ( + + + + + onetwo + +) diff --git a/packages/slate/test/transforms/normalization/split_node-and-insert_node.tsx b/packages/slate/test/transforms/normalization/split_node-and-insert_node.tsx new file mode 100644 index 000000000..21db92713 --- /dev/null +++ b/packages/slate/test/transforms/normalization/split_node-and-insert_node.tsx @@ -0,0 +1,84 @@ +/** @jsx jsx */ +import { Editor, Transforms } from 'slate' +import { jsx } from '../..' + +export const input = ( + + + + one + + + + + two + + + +) +export const run = editor => { + Editor.withoutNormalizing(editor, () => { + const operations = [ + { + type: 'split_node', + path: [0, 1], + position: 0, + properties: { inline: true }, + }, + { + type: 'split_node', + path: [0], + position: 1, + properties: {}, + }, + { + type: 'split_node', + path: [2, 1, 0], + position: 0, + properties: {}, + }, + { + type: 'split_node', + path: [2, 1], + position: 0, + properties: { inline: true }, + }, + { + type: 'split_node', + path: [2], + position: 1, + properties: {}, + }, + { type: 'insert_node', path: [2, 1], node: { text: '' } }, + ] + operations.forEach(editor.apply) + }) +} +export const output = ( + + + + + + + + + + + one + + + + + + + + + + + + two + + + +)