1
0
mirror of https://github.com/ianstormtaylor/slate.git synced 2025-08-21 14:41:23 +02:00

Improve normalize and validation to be recursive

This commit is contained in:
Samy Pesse
2016-10-19 11:52:55 +02:00
parent d2d7122ffc
commit a51061f094
5 changed files with 110 additions and 52 deletions

View File

@@ -7,7 +7,7 @@ import React from 'react'
import String from '../utils/string' import String from '../utils/string'
import getWindow from 'get-window' import getWindow from 'get-window'
import { IS_MAC } from '../constants/environment' import { IS_MAC } from '../constants/environment'
import { rules } from './schema' import { default as defaultSchema } from './schema'
/** /**
* Debug. * Debug.
@@ -741,7 +741,7 @@ function Plugin(options = {}) {
rules: [ rules: [
BLOCK_RENDER_RULE, BLOCK_RENDER_RULE,
INLINE_RENDER_RULE, INLINE_RENDER_RULE,
...rules ...defaultSchema.rules
] ]
} }

View File

@@ -1,4 +1,5 @@
import Schema from '../models/schema' import Schema from '../models/schema'
import Text from '../models/text'
/* /*
This module contains the default schema to normalize documents This module contains the default schema to normalize documents
@@ -84,6 +85,45 @@ const INLINE_CHILDREN_RULE = {
} }
} }
/**
* A default schema rule to ensure that inline void nodes are surrounded with text nodes
*
* @type {Object}
*/
const INLINE_VOID_TEXT_RULE = {
match: (object) => {
return object.kind == 'block'
},
validate: (block) => {
const invalids = block.nodes.reduce((accu, child, index) => {
if (child.kind === 'block' || !child.isVoid) {
return accu
}
const prevNode = index > 0 ? block.nodes.get(index - 1) : null
const nextNode = block.nodes.get(index + 1)
const prev = (!prevNode || prevNode.kind !== 'text')
const next = (!nextNode || nextNode.kind !== 'text')
if (next || prev) {
accu.push({ next, prev, index })
}
return accu
}, [])
return invalids.length ? invalids : null
},
normalize: (transform, block, invalids) => {
return invalids.reduce((t, { index, next, prev }) => {
if (prev) t = transform.insertNodeByKey(block.key, index, Text.create())
if (next) t = transform.insertNodeByKey(block.key, index + 1, Text.create())
return t
}, transform)
}
}
/** /**
* Join adjacent text nodes. * Join adjacent text nodes.
@@ -111,10 +151,13 @@ const NO_ADJACENT_TEXT_RULE = {
return invalids.size ? invalids : null return invalids.size ? invalids : null
}, },
normalize: (transform, node, pairs) => { normalize: (transform, node, pairs) => {
return pairs.reduce((t, pair) => { return pairs
const [ first, second ] = pair // We reverse the list since we want to handle 3 consecutive text nodes
return t.joinNodeByKey(second.key, first.key) .reverse()
}, transform) .reduce((t, pair) => {
const [ first, second ] = pair
return t.joinNodeByKey(second.key, first.key)
}, transform)
} }
} }
@@ -130,6 +173,7 @@ const schema = Schema.create({
BLOCK_CHILDREN_RULE, BLOCK_CHILDREN_RULE,
MIN_TEXT_RULE, MIN_TEXT_RULE,
INLINE_CHILDREN_RULE, INLINE_CHILDREN_RULE,
INLINE_VOID_TEXT_RULE,
NO_ADJACENT_TEXT_RULE NO_ADJACENT_TEXT_RULE
] ]
}) })

View File

@@ -1,6 +1,7 @@
import Debug from 'debug' import Debug from 'debug'
import uid from '../utils/uid' import uid from '../utils/uid'
import { default as defaultSchema } from '../plugins/schema'
/** /**
* Debug. * Debug.
@@ -33,6 +34,8 @@ const OPERATIONS = {
split_node: splitNode, split_node: splitNode,
// Selection operations. // Selection operations.
set_selection: setSelection, set_selection: setSelection,
// Normalize
normalize
} }
/** /**
@@ -48,7 +51,6 @@ export function applyOperation(transform, operation) {
const { type } = operation const { type } = operation
const fn = OPERATIONS[type] const fn = OPERATIONS[type]
console.log('apply op', type, operation);
if (!fn) { if (!fn) {
throw new Error(`Unknown operation type: "${type}".`) throw new Error(`Unknown operation type: "${type}".`)
} }
@@ -57,9 +59,21 @@ export function applyOperation(transform, operation) {
transform.state = fn(state, operation) transform.state = fn(state, operation)
transform.operations = operations.concat([operation]) transform.operations = operations.concat([operation])
return transform return transform
} }
/**
* Normalize the state with core rules
*
* @param {State} state
* @return {State}
*/
function normalize(state) {
return state.normalize(defaultSchema)
}
/** /**
* Add mark to text at `offset` and `length` in node by `path`. * Add mark to text at `offset` and `length` in node by `path`.
* *

View File

@@ -146,8 +146,7 @@ import {
import { import {
normalize, normalize,
normalizeWith, normalizeWith
normalizeDocument,
} from './normalize' } from './normalize'
/** /**
@@ -290,7 +289,6 @@ export default {
*/ */
normalize, normalize,
normalizeWith, normalizeWith
normalizeDocument,
} }

View File

@@ -1,52 +1,54 @@
import Schema from '../models/schema' import Schema from '../models/schema'
import { default as defaultSchema } from '../plugins/schema' import { default as defaultSchema } from '../plugins/schema'
/** /**
* Normalize a node using a schema, by pushing operations to a transform. * Normalize a node using a schema.
* "prevNode" can be used to prevent iterating over all children.
*
* @param {Transform} transform
* @param {Node} node
* @param {Node} prevNode?
* @return {Transform}
*/
function normalizeNode(transform, schema, node, prevNode) {
if (prevNode === node) {
return transform
}
// Normalize children
if (node.nodes) {
transform = node.nodes.reduce((t, child) => {
const prevChild = prevNode ? prevNode.getChild(child.key) : null
return normalizeNode(transform, schema, child, prevChild)
}, transform)
}
// Normalize the node itself
let failure
if (failure = schema.__validate(node)) {
const { value, rule } = failure
transform = rule.normalize(transform, node, value)
}
return transform
}
/**
* Normalize the state with a schema.
* *
* @param {Transform} transform * @param {Transform} transform
* @param {Schema} schema * @param {Node} node
* @param {Node} prevNode
* @return {Transform} * @return {Transform}
*/ */
export function normalizeWith(transform, schema) { export function normalizeWith(transform, schema, node, prevNode) {
const { state } = transform let { state } = transform
const { document } = state
return normalizeNode(transform, schema, document, null) // If no node specific, normalize the whole document
node = node || state.document
if (node === prevNode) {
return transform
}
const failure = schema.__validate(node)
if (failure) {
const { value, rule } = failure
// Normalize and get the new state
transform = rule.normalize(transform, node, value)
const newState = transform.state
// Search for the updated node in the new state
node = newState.document.getDescendant(node.key)
// Node no longer exist, exit
if (!node) {
return transform
}
return transform.normalizeWith(schema, node, prevNode)
}
// No child, stop here
if (!node.nodes) {
return transform
}
return node.nodes.reduce((t, child) => {
const prevChild = prevNode ? prevNode.getChild(child.key) : null
return t.normalizeWith(schema, child, prevChild)
}, transform)
} }
/** /**
@@ -58,7 +60,7 @@ export function normalizeWith(transform, schema) {
*/ */
export function normalize(transform) { export function normalize(transform) {
return transform.normalizeWith(defaultSchema) return transform.normalizeWith(defaultSchema)
} }
/** /**