diff --git a/packages/slate/benchmark/models/from-json-big.js b/packages/slate/benchmark/models/from-json-big.js new file mode 100644 index 000000000..a3d902c8a --- /dev/null +++ b/packages/slate/benchmark/models/from-json-big.js @@ -0,0 +1,66 @@ +/* eslint-disable react/jsx-key */ + +import { Value } from '../..' + +export default function(json) { + Value.fromJSON(json) +} + +export const input = { + document: { + nodes: Array.from(Array(100)).map(() => ({ + type: 'list', + object: 'block', + isVoid: false, + data: {}, + nodes: Array.from(Array(10)).map(() => ({ + type: 'list-item', + object: 'block', + isVoid: false, + data: {}, + nodes: [ + { + leaves: [ + { + object: 'leaf', + marks: [], + text: '', + }, + ], + object: 'text', + }, + { + type: 'link', + object: 'inline', + isVoid: false, + data: { + id: 1, + }, + nodes: [ + { + leaves: [ + { + object: 'leaf', + marks: [], + text: 'Some text for a link', + }, + ], + object: 'text', + }, + ], + }, + { + leaves: [ + { + object: 'leaf', + marks: [], + text: '', + }, + ], + object: 'text', + }, + ], + })), + })), + }, +} diff --git a/packages/slate/src/changes/with-schema.js b/packages/slate/src/changes/with-schema.js index f2486dfed..4e6368742 100644 --- a/packages/slate/src/changes/with-schema.js +++ b/packages/slate/src/changes/with-schema.js @@ -65,32 +65,17 @@ function normalizeNodeAndChildren(change, node, schema) { return } - const normalizedKeys = [] - let child = node.nodes.first() + let child = node.getFirstInvalidDescendant(schema) let path = change.value.document.getPath(node.key) - - // We can't just loop the children and normalize them, because in the process - // of normalizing one child, we might end up creating another. Instead, we - // have to normalize one at a time, and check for new children along the way. while (node && child) { - const lastSize = change.operations.size normalizeNodeAndChildren(change, child, schema) - normalizedKeys.push(child.key) - - // PERF: if size is unchanged, then no operation happens - // Therefore we can simply normalize the next child - if (lastSize === change.operations.size) { - const nextIndex = node.nodes.indexOf(child) + 1 - child = node.nodes.get(nextIndex) + node = change.value.document.refindNode(path, node.key) + if (!node) { + path = [] + child = null } else { - node = change.value.document.refindNode(path, node.key) - if (!node) { - path = [] - child = null - } else { - path = change.value.document.refindPath(path, node.key) - child = node.nodes.find(c => !normalizedKeys.includes(c.key)) - } + path = change.value.document.refindPath(path, node.key) + child = node.getFirstInvalidDescendant(schema) } } diff --git a/packages/slate/src/models/node.js b/packages/slate/src/models/node.js index ddae92e7f..2d1d24e7c 100644 --- a/packages/slate/src/models/node.js +++ b/packages/slate/src/models/node.js @@ -1987,6 +1987,22 @@ class Node { validate(schema) { return schema.validateNode(this) } + + /** + * Get the first invalid descendant + * + * @param {Schema} schema + * @return {Node|Text|Null} + */ + + getFirstInvalidDescendant(schema) { + let result = null + this.nodes.find(n => { + result = n.validate(schema) ? n : n.getFirstInvalidDescendant(schema) + return result + }) + return result + } } /** @@ -2072,6 +2088,7 @@ memoize( 'getTextAtOffset', 'getTextsAtRangeAsArray', 'validate', + 'getFirstInvalidDescendant', ], { takesArguments: true, diff --git a/packages/slate/src/models/text.js b/packages/slate/src/models/text.js index 29c8fc21f..3f3a890b2 100644 --- a/packages/slate/src/models/text.js +++ b/packages/slate/src/models/text.js @@ -495,6 +495,18 @@ class Text extends Record(DEFAULTS) { validate(schema) { return schema.validateNode(this) } + + /** + * Get the first invalid descendant + * PREF: Do not cache this method; because it can cause cycle reference + * + * @param {Schema} schema + * @returns {Text|Null} + */ + + getFirstInvalidDescendant(schema) { + return this.validate(schema) ? this : null + } } /**