mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-02-25 01:33:37 +01:00
Change signature of Node.normalize to be recursive
This commit is contained in:
parent
8164bb8ce2
commit
8f6a534bc9
@ -53,11 +53,7 @@ class Block extends new Record(DEFAULTS) {
|
||||
properties.isVoid = !!properties.isVoid
|
||||
properties.nodes = Block.createList(properties.nodes)
|
||||
|
||||
// if (properties.nodes.size == 0) {
|
||||
// properties.nodes = properties.nodes.push(Text.create())
|
||||
// }
|
||||
|
||||
return new Block(properties).normalize()
|
||||
return new Block(properties)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -43,7 +43,7 @@ class Document extends new Record(DEFAULTS) {
|
||||
properties.key = properties.key || uid(4)
|
||||
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.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 uid from '../utils/uid'
|
||||
import { List, Map, OrderedSet, Set } from 'immutable'
|
||||
import { default as defaultSchema } from '../plugins/schema'
|
||||
|
||||
|
||||
/**
|
||||
* Node.
|
||||
@ -1039,122 +1041,6 @@ const Node = {
|
||||
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.
|
||||
*
|
||||
@ -1227,8 +1113,8 @@ const Node = {
|
||||
const { nodes } = child
|
||||
const oneNodes = nodes.takeUntil(n => n.key == one.key).push(one)
|
||||
const twoNodes = nodes.skipUntil(n => n.key == one.key).rest().unshift(two)
|
||||
one = child.merge({ nodes: oneNodes }).normalize()
|
||||
two = child.merge({ nodes: twoNodes, key: uid() }).normalize()
|
||||
one = child.merge({ nodes: oneNodes })
|
||||
two = child.merge({ nodes: twoNodes, key: uid() })
|
||||
}
|
||||
|
||||
child = base.getParent(child)
|
||||
@ -1266,7 +1152,6 @@ const Node = {
|
||||
|
||||
const path = base.getPath(node.key)
|
||||
return this.splitNode(path, offset)
|
||||
.normalize()
|
||||
},
|
||||
|
||||
/**
|
||||
@ -1290,8 +1175,38 @@ const Node = {
|
||||
|
||||
validate(schema) {
|
||||
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 { document, selection } = this
|
||||
let transform = this.transform()
|
||||
let failure
|
||||
|
||||
document.filterDescendantsDeep((node) => {
|
||||
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)
|
||||
}
|
||||
transform = document.normalize(transform, schema, null)
|
||||
|
||||
return transform.apply({ save: false })
|
||||
}
|
||||
|
@ -1,5 +1,10 @@
|
||||
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.
|
||||
*
|
||||
|
@ -1,112 +1,6 @@
|
||||
|
||||
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.
|
||||
|
Loading…
x
Reference in New Issue
Block a user