1
0
mirror of https://github.com/ianstormtaylor/slate.git synced 2025-09-01 19:22:35 +02:00

refactor core schema rules

This commit is contained in:
Ian Storm Taylor
2016-12-01 12:19:44 -08:00
parent a4db9148a4
commit 623b1f17b3
2 changed files with 270 additions and 274 deletions

View File

@@ -11,13 +11,21 @@ import { List } from 'immutable'
const OPTS = { normalize: false } const OPTS = { normalize: false }
/**
* Define the core schema rules, order-sensitive.
*
* @type {Array}
*/
const rules = [
/** /**
* Only allow block nodes in documents. * Only allow block nodes in documents.
* *
* @type {Object} * @type {Object}
*/ */
const DOCUMENT_CHILDREN_RULE = { {
match: (node) => { match: (node) => {
return node.kind == 'document' return node.kind == 'document'
}, },
@@ -30,7 +38,7 @@ const DOCUMENT_CHILDREN_RULE = {
transform.removeNodeByKey(node.key, OPTS) transform.removeNodeByKey(node.key, OPTS)
}) })
} }
} },
/** /**
* Only allow block, inline and text nodes in blocks. * Only allow block, inline and text nodes in blocks.
@@ -38,7 +46,7 @@ const DOCUMENT_CHILDREN_RULE = {
* @type {Object} * @type {Object}
*/ */
const BLOCK_CHILDREN_RULE = { {
match: (node) => { match: (node) => {
return node.kind == 'block' return node.kind == 'block'
}, },
@@ -54,26 +62,7 @@ const BLOCK_CHILDREN_RULE = {
transform.removeNodeByKey(node.key, OPTS) transform.removeNodeByKey(node.key, OPTS)
}) })
} }
}
/**
* Ensure that block and inline nodes have at least one text child.
*
* @type {Object}
*/
const MIN_TEXT_RULE = {
match: (object) => {
return object.kind == 'block' || object.kind == 'inline'
}, },
validate: (node) => {
return node.nodes.size == 0
},
normalize: (transform, node) => {
const text = Text.create()
transform.insertNodeByKey(node.key, 0, text, OPTS)
}
}
/** /**
* Only allow inline and text nodes in inlines. * Only allow inline and text nodes in inlines.
@@ -81,7 +70,7 @@ const MIN_TEXT_RULE = {
* @type {Object} * @type {Object}
*/ */
const INLINE_CHILDREN_RULE = { {
match: (object) => { match: (object) => {
return object.kind == 'inline' return object.kind == 'inline'
}, },
@@ -94,40 +83,26 @@ const INLINE_CHILDREN_RULE = {
transform.removeNodeByKey(node.key, OPTS) transform.removeNodeByKey(node.key, OPTS)
}) })
} }
} },
/** /**
* Ensure that inline nodes are never empty. * Ensure that block and inline nodes have at least one text child.
*
* This rule is applied to all blocks, because when they contain an empty
* inline, we need to remove the inline from that parent block. If `validate`
* was to be memoized, it should be against the parent node, not the inline
* themselves.
* *
* @type {Object} * @type {Object}
*/ */
const INLINE_NO_EMPTY = { {
match: (object) => { match: (object) => {
return object.kind == 'block' return object.kind == 'block' || object.kind == 'inline'
}, },
validate: (block) => { validate: (node) => {
const invalids = block.nodes.filter(n => n.kind == 'inline' && n.text == '') return node.nodes.size == 0
return invalids.size ? invalids : null
}, },
normalize: (transform, block, invalids) => { normalize: (transform, node) => {
// If all of the block's nodes are invalid, insert an empty text node so
// that the selection will be preserved when they are all removed.
if (block.nodes.size == invalids.size) {
const text = Text.create() const text = Text.create()
transform.insertNodeByKey(block.key, 1, text, OPTS) transform.insertNodeByKey(node.key, 0, text, OPTS)
}
invalids.forEach((node) => {
transform.removeNodeByKey(node.key, OPTS)
})
}
} }
},
/** /**
* Ensure that void nodes contain a text node with a single space of text. * Ensure that void nodes contain a text node with a single space of text.
@@ -135,7 +110,7 @@ const INLINE_NO_EMPTY = {
* @type {Object} * @type {Object}
*/ */
const VOID_TEXT_RULE = { {
match: (object) => { match: (object) => {
return ( return (
(object.kind == 'inline' || object.kind == 'block') && (object.kind == 'inline' || object.kind == 'block') &&
@@ -155,8 +130,41 @@ const VOID_TEXT_RULE = {
transform.removeNodeByKey(child.key, OPTS) transform.removeNodeByKey(child.key, OPTS)
}) })
} }
},
/**
* Ensure that inline nodes are never empty.
*
* This rule is applied to all blocks, because when they contain an empty
* inline, we need to remove the inline from that parent block. If `validate`
* was to be memoized, it should be against the parent node, not the inline
* themselves.
*
* @type {Object}
*/
{
match: (object) => {
return object.kind == 'block'
},
validate: (block) => {
const invalids = block.nodes.filter(n => n.kind == 'inline' && n.text == '')
return invalids.size ? invalids : null
},
normalize: (transform, block, invalids) => {
// If all of the block's nodes are invalid, insert an empty text node so
// that the selection will be preserved when they are all removed.
if (block.nodes.size == invalids.size) {
const text = Text.create()
transform.insertNodeByKey(block.key, 1, text, OPTS)
} }
invalids.forEach((node) => {
transform.removeNodeByKey(node.key, OPTS)
})
}
},
/** /**
* Ensure that inline void nodes are surrounded by text nodes, by adding extra * Ensure that inline void nodes are surrounded by text nodes, by adding extra
* blank text nodes if necessary. * blank text nodes if necessary.
@@ -164,17 +172,17 @@ const VOID_TEXT_RULE = {
* @type {Object} * @type {Object}
*/ */
const INLINE_VOID_TEXTS_AROUND_RULE = { {
match: (object) => { match: (object) => {
return object.kind == 'block' || object.kind == 'inline' return object.kind == 'block' || object.kind == 'inline'
}, },
validate: (block) => { validate: (node) => {
const invalids = block.nodes.reduce((list, child, index) => { const invalids = node.nodes.reduce((list, child, index) => {
if (child.kind == 'block') return list if (child.kind == 'block') return list
if (!child.isVoid) return list if (!child.isVoid) return list
const prev = index > 0 ? block.nodes.get(index - 1) : null const prev = index > 0 ? node.nodes.get(index - 1) : null
const next = block.nodes.get(index + 1) const next = node.nodes.get(index + 1)
const insertBefore = !prev const insertBefore = !prev
const insertAfter = !next || isInlineVoid(next) const insertAfter = !next || isInlineVoid(next)
@@ -203,7 +211,7 @@ const INLINE_VOID_TEXTS_AROUND_RULE = {
} }
}) })
} }
} },
/** /**
* Join adjacent text nodes. * Join adjacent text nodes.
@@ -211,7 +219,7 @@ const INLINE_VOID_TEXTS_AROUND_RULE = {
* @type {Object} * @type {Object}
*/ */
const NO_ADJACENT_TEXT_RULE = { {
match: (object) => { match: (object) => {
return object.kind == 'block' || object.kind == 'inline' return object.kind == 'block' || object.kind == 'inline'
}, },
@@ -235,7 +243,7 @@ const NO_ADJACENT_TEXT_RULE = {
return transform.joinNodeByKey(second.key, first.key, OPTS) return transform.joinNodeByKey(second.key, first.key, OPTS)
}) })
} }
} },
/** /**
* Prevent extra empty text nodes, except when adjacent to inline void nodes. * Prevent extra empty text nodes, except when adjacent to inline void nodes.
@@ -243,7 +251,7 @@ const NO_ADJACENT_TEXT_RULE = {
* @type {Object} * @type {Object}
*/ */
const NO_EMPTY_TEXT_RULE = { {
match: (object) => { match: (object) => {
return object.kind == 'block' || object.kind == 'inline' return object.kind == 'block' || object.kind == 'inline'
}, },
@@ -280,6 +288,8 @@ const NO_EMPTY_TEXT_RULE = {
} }
} }
]
/** /**
* Test if a `node` is an inline void node. * Test if a `node` is an inline void node.
* *
@@ -292,24 +302,12 @@ function isInlineVoid(node) {
} }
/** /**
* The core schema. * Create the core schema.
* *
* @type {Schema} * @type {Schema}
*/ */
const SCHEMA = Schema.create({ const SCHEMA = Schema.create({ rules })
rules: [
DOCUMENT_CHILDREN_RULE,
BLOCK_CHILDREN_RULE,
INLINE_CHILDREN_RULE,
VOID_TEXT_RULE,
MIN_TEXT_RULE,
INLINE_NO_EMPTY,
INLINE_VOID_TEXTS_AROUND_RULE,
NO_ADJACENT_TEXT_RULE,
NO_EMPTY_TEXT_RULE
]
})
/** /**
* Export. * Export.

View File

@@ -7,5 +7,3 @@ nodes:
- kind: text - kind: text
key: 'anchor' key: 'anchor'
text: Thetext text: Thetext
# Selection
# Th[ete]xt