1
0
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:
Samy Pesse 2016-10-18 20:40:36 +02:00
parent 8164bb8ce2
commit 8f6a534bc9
7 changed files with 44 additions and 245 deletions

View File

@ -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)
}
/**

View File

@ -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)
}
/**

View File

@ -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)
}
/**

View File

@ -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
}
}
/**

View File

@ -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 })
}

View File

@ -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.
*

View File

@ -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.