mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-08-18 21:21:21 +02:00
Change signature of Node.normalize to be recursive
This commit is contained in:
@@ -53,11 +53,7 @@ class Block extends new Record(DEFAULTS) {
|
|||||||
properties.isVoid = !!properties.isVoid
|
properties.isVoid = !!properties.isVoid
|
||||||
properties.nodes = Block.createList(properties.nodes)
|
properties.nodes = Block.createList(properties.nodes)
|
||||||
|
|
||||||
// if (properties.nodes.size == 0) {
|
return new Block(properties)
|
||||||
// properties.nodes = properties.nodes.push(Text.create())
|
|
||||||
// }
|
|
||||||
|
|
||||||
return new Block(properties).normalize()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -43,7 +43,7 @@ class Document extends new Record(DEFAULTS) {
|
|||||||
properties.key = properties.key || uid(4)
|
properties.key = properties.key || uid(4)
|
||||||
properties.nodes = Block.createList(properties.nodes)
|
properties.nodes = Block.createList(properties.nodes)
|
||||||
|
|
||||||
return new Document(properties).normalize()
|
return new Document(properties)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -53,7 +53,7 @@ class Inline extends new Record(DEFAULTS) {
|
|||||||
properties.isVoid = !!properties.isVoid
|
properties.isVoid = !!properties.isVoid
|
||||||
properties.nodes = Inline.createList(properties.nodes)
|
properties.nodes = Inline.createList(properties.nodes)
|
||||||
|
|
||||||
return new Inline(properties).normalize()
|
return new Inline(properties)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -14,6 +14,8 @@ import includes from 'lodash/includes'
|
|||||||
import memoize from '../utils/memoize'
|
import memoize from '../utils/memoize'
|
||||||
import uid from '../utils/uid'
|
import uid from '../utils/uid'
|
||||||
import { List, Map, OrderedSet, Set } from 'immutable'
|
import { List, Map, OrderedSet, Set } from 'immutable'
|
||||||
|
import { default as defaultSchema } from '../plugins/schema'
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Node.
|
* Node.
|
||||||
@@ -1039,122 +1041,6 @@ const Node = {
|
|||||||
return this.merge({ nodes })
|
return this.merge({ nodes })
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* Normalize the node by joining any two adjacent text child nodes.
|
|
||||||
*
|
|
||||||
* @return {Node} node
|
|
||||||
*/
|
|
||||||
|
|
||||||
normalize() {
|
|
||||||
let node = this
|
|
||||||
let keys = new Set()
|
|
||||||
let removals = new Set()
|
|
||||||
|
|
||||||
// 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 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 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 })
|
|
||||||
}
|
|
||||||
|
|
||||||
// ...that no inline node is empty.
|
|
||||||
if (desc.kind == 'inline' && desc.text == '') {
|
|
||||||
removals = removals.add(desc.key)
|
|
||||||
}
|
|
||||||
|
|
||||||
return desc
|
|
||||||
})
|
|
||||||
|
|
||||||
// Remove any nodes marked for removal.
|
|
||||||
removals.forEach((key) => {
|
|
||||||
node = node.removeDescendant(key)
|
|
||||||
})
|
|
||||||
|
|
||||||
removals = removals.clear()
|
|
||||||
|
|
||||||
// And, ensuring...
|
|
||||||
node = node.mapDescendants((desc) => {
|
|
||||||
if (desc.kind == 'text') {
|
|
||||||
let next = node.getNextSibling(desc)
|
|
||||||
|
|
||||||
// ...that there are no adjacent text nodes.
|
|
||||||
if (next && next.kind == 'text') {
|
|
||||||
while (next && next.kind == 'text') {
|
|
||||||
const characters = desc.characters.concat(next.characters)
|
|
||||||
desc = desc.merge({ characters })
|
|
||||||
removals = removals.add(next.key)
|
|
||||||
next = node.getNextSibling(next)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ...that there are no extra empty text nodes.
|
|
||||||
else if (desc.length == 0) {
|
|
||||||
const parent = node.getParent(desc)
|
|
||||||
if (!removals.has(parent.key) && parent.nodes.size > 1) {
|
|
||||||
removals = removals.add(desc.key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return desc
|
|
||||||
})
|
|
||||||
|
|
||||||
// Remove any nodes marked for removal.
|
|
||||||
removals.forEach((key) => {
|
|
||||||
node = node.removeDescendant(key)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Ensure that void nodes are surrounded by text nodes
|
|
||||||
node = node.mapDescendants((desc) => {
|
|
||||||
if (desc.kind == 'text') {
|
|
||||||
return desc
|
|
||||||
}
|
|
||||||
|
|
||||||
const nodes = desc.nodes.reduce((accu, child, i) => {
|
|
||||||
// We wrap only inline void nodes
|
|
||||||
if (!child.isVoid || child.kind === 'block') {
|
|
||||||
return accu.push(child)
|
|
||||||
}
|
|
||||||
|
|
||||||
const prev = accu.last()
|
|
||||||
const next = desc.nodes.get(i + 1)
|
|
||||||
|
|
||||||
if (!prev || prev.kind !== 'text') {
|
|
||||||
accu = accu.push(Text.create())
|
|
||||||
}
|
|
||||||
|
|
||||||
accu = accu.push(child)
|
|
||||||
|
|
||||||
if (!next || next.kind !== 'text') {
|
|
||||||
accu = accu.push(Text.create())
|
|
||||||
}
|
|
||||||
|
|
||||||
return accu
|
|
||||||
}, List())
|
|
||||||
|
|
||||||
return desc.merge({ nodes })
|
|
||||||
})
|
|
||||||
|
|
||||||
return node
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove a `node` from the children node map.
|
* Remove a `node` from the children node map.
|
||||||
*
|
*
|
||||||
@@ -1227,8 +1113,8 @@ const Node = {
|
|||||||
const { nodes } = child
|
const { nodes } = child
|
||||||
const oneNodes = nodes.takeUntil(n => n.key == one.key).push(one)
|
const oneNodes = nodes.takeUntil(n => n.key == one.key).push(one)
|
||||||
const twoNodes = nodes.skipUntil(n => n.key == one.key).rest().unshift(two)
|
const twoNodes = nodes.skipUntil(n => n.key == one.key).rest().unshift(two)
|
||||||
one = child.merge({ nodes: oneNodes }).normalize()
|
one = child.merge({ nodes: oneNodes })
|
||||||
two = child.merge({ nodes: twoNodes, key: uid() }).normalize()
|
two = child.merge({ nodes: twoNodes, key: uid() })
|
||||||
}
|
}
|
||||||
|
|
||||||
child = base.getParent(child)
|
child = base.getParent(child)
|
||||||
@@ -1266,7 +1152,6 @@ const Node = {
|
|||||||
|
|
||||||
const path = base.getPath(node.key)
|
const path = base.getPath(node.key)
|
||||||
return this.splitNode(path, offset)
|
return this.splitNode(path, offset)
|
||||||
.normalize()
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1290,8 +1175,38 @@ const Node = {
|
|||||||
|
|
||||||
validate(schema) {
|
validate(schema) {
|
||||||
return schema.__validate(this)
|
return schema.__validate(this)
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalize the node using a schema, by pushing operations to a transform.
|
||||||
|
* "prevNode" can be used to prevent iterating over all children.
|
||||||
|
*
|
||||||
|
* @param {Transform} transform
|
||||||
|
* @param {Schema} schema
|
||||||
|
* @param {Node} prevNode?
|
||||||
|
* @return {Transform}
|
||||||
|
*/
|
||||||
|
|
||||||
|
normalize(transform, schema, prevNode) {
|
||||||
|
if (prevNode === this) {
|
||||||
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Normalize children
|
||||||
|
this.nodes.forEach(child => {
|
||||||
|
const prevChild = prevNode ? prevNode.getChild(child.key) : null
|
||||||
|
child.normalize(transform, schema, prevChild)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Normalize the node itself
|
||||||
|
let failure
|
||||||
|
if (failure = this.validate(schema)) {
|
||||||
|
const { value, rule } = failure
|
||||||
|
rule.normalize(transform, this, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return transform
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -425,19 +425,8 @@ class State extends new Record(DEFAULTS) {
|
|||||||
const state = this
|
const state = this
|
||||||
const { document, selection } = this
|
const { document, selection } = this
|
||||||
let transform = this.transform()
|
let transform = this.transform()
|
||||||
let failure
|
|
||||||
|
|
||||||
document.filterDescendantsDeep((node) => {
|
transform = document.normalize(transform, schema, null)
|
||||||
if (failure = node.validate(schema)) {
|
|
||||||
const { value, rule } = failure
|
|
||||||
rule.normalize(transform, node, value)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if (failure = document.validate(schema)) {
|
|
||||||
const { value, rule } = failure
|
|
||||||
rule.normalize(transform, document, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
return transform.apply({ save: false })
|
return transform.apply({ save: false })
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,10 @@
|
|||||||
import Schema from '../models/schema'
|
import Schema from '../models/schema'
|
||||||
|
|
||||||
|
/*
|
||||||
|
This module contains the default schema to normalize documents
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A default schema rule to only allow block nodes in documents.
|
* A default schema rule to only allow block nodes in documents.
|
||||||
*
|
*
|
||||||
|
@@ -1,112 +1,6 @@
|
|||||||
|
|
||||||
import Schema from '../models/schema'
|
import Schema from '../models/schema'
|
||||||
|
|
||||||
/**
|
|
||||||
* Only allow block nodes in documents.
|
|
||||||
*
|
|
||||||
* @type {Object}
|
|
||||||
*/
|
|
||||||
|
|
||||||
const DOCUMENT_CHILDREN_RULE = {
|
|
||||||
match: (node) => {
|
|
||||||
return node.kind == 'document'
|
|
||||||
},
|
|
||||||
validate: (document) => {
|
|
||||||
const { nodes } = document
|
|
||||||
const invalids = nodes.filter(n => n.kind != 'block')
|
|
||||||
return invalids.size ? invalids : null
|
|
||||||
},
|
|
||||||
normalize: (transform, document, invalids) => {
|
|
||||||
return invalids.reduce((t, n) => t.removeNodeByKey(n.key), transform)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Only allow block, inline and text nodes in blocks.
|
|
||||||
*
|
|
||||||
* @type {Object}
|
|
||||||
*/
|
|
||||||
|
|
||||||
const BLOCK_CHILDREN_RULE = {
|
|
||||||
match: (node) => {
|
|
||||||
return node.kind == 'block'
|
|
||||||
},
|
|
||||||
validate: (block) => {
|
|
||||||
const { nodes } = block
|
|
||||||
const invalids = nodes.filter(n => n.kind != 'block' && n.kind != 'inline' && n.kind != 'text')
|
|
||||||
return invalids.size ? invalids : null
|
|
||||||
},
|
|
||||||
normalize: (transform, block, invalids) => {
|
|
||||||
return invalids.reduce((t, n) => t.removeNodeByKey(n.key), transform)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Only allow inline and text nodes in inlines.
|
|
||||||
*
|
|
||||||
* @type {Object}
|
|
||||||
*/
|
|
||||||
|
|
||||||
const INLINE_CHILDREN_RULE = {
|
|
||||||
match: (object) => {
|
|
||||||
return object.kind == 'inline'
|
|
||||||
},
|
|
||||||
validate: (inline) => {
|
|
||||||
const { nodes } = inline
|
|
||||||
const invalids = nodes.filter(n => n.kind != 'inline' && n.kind != 'text')
|
|
||||||
return invalids.size ? invalids : null
|
|
||||||
},
|
|
||||||
normalize: (transform, inline, invalids) => {
|
|
||||||
return invalids.reduce((t, n) => t.removeNodeByKey(n.key), transform)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Join adjacent text nodes.
|
|
||||||
*
|
|
||||||
* @type {Object}
|
|
||||||
*/
|
|
||||||
|
|
||||||
const NO_ADJACENT_TEXT_RULE = {
|
|
||||||
match: (object) => {
|
|
||||||
return object.kind == 'block' || object.kind == 'inline'
|
|
||||||
},
|
|
||||||
validate: (node) => {
|
|
||||||
const { nodes } = node
|
|
||||||
const invalids = nodes
|
|
||||||
.filter((n, i) => {
|
|
||||||
const next = nodes.get(i + 1)
|
|
||||||
return n.kind == 'text' && next && next.kind == 'text'
|
|
||||||
})
|
|
||||||
.map((n, i) => {
|
|
||||||
const next = nodes.get(i + 1)
|
|
||||||
return [n, next]
|
|
||||||
})
|
|
||||||
|
|
||||||
return invalids.size ? invalids : null
|
|
||||||
},
|
|
||||||
normalize: (transform, node, pairs) => {
|
|
||||||
return pairs.reduce((t, pair) => {
|
|
||||||
const [ first, second ] = pair
|
|
||||||
return t.joinNodeByKey(first.key, second.key)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The internal normalizing schema.
|
|
||||||
*
|
|
||||||
* @type {Schema}
|
|
||||||
*/
|
|
||||||
|
|
||||||
const SCHEMA = Schema.create({
|
|
||||||
rules: [
|
|
||||||
DOCUMENT_CHILDREN_RULE,
|
|
||||||
BLOCK_CHILDREN_RULE,
|
|
||||||
INLINE_CHILDREN_RULE,
|
|
||||||
NO_ADJACENT_TEXT_RULE,
|
|
||||||
]
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Normalize the state.
|
* Normalize the state.
|
||||||
|
Reference in New Issue
Block a user