mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-09-01 11:12:42 +02:00
refactor core schema rules
This commit is contained in:
@@ -12,12 +12,20 @@ import { List } from 'immutable'
|
||||
const OPTS = { normalize: false }
|
||||
|
||||
/**
|
||||
* Define the core schema rules, order-sensitive.
|
||||
*
|
||||
* @type {Array}
|
||||
*/
|
||||
|
||||
const rules = [
|
||||
|
||||
/**
|
||||
* Only allow block nodes in documents.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
|
||||
const DOCUMENT_CHILDREN_RULE = {
|
||||
{
|
||||
match: (node) => {
|
||||
return node.kind == 'document'
|
||||
},
|
||||
@@ -30,15 +38,15 @@ const DOCUMENT_CHILDREN_RULE = {
|
||||
transform.removeNodeByKey(node.key, OPTS)
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
/**
|
||||
* Only allow block, inline and text nodes in blocks.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
|
||||
const BLOCK_CHILDREN_RULE = {
|
||||
{
|
||||
match: (node) => {
|
||||
return node.kind == 'block'
|
||||
},
|
||||
@@ -54,34 +62,15 @@ const BLOCK_CHILDREN_RULE = {
|
||||
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.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
|
||||
const INLINE_CHILDREN_RULE = {
|
||||
{
|
||||
match: (object) => {
|
||||
return object.kind == 'inline'
|
||||
},
|
||||
@@ -94,48 +83,34 @@ const INLINE_CHILDREN_RULE = {
|
||||
transform.removeNodeByKey(node.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.
|
||||
/**
|
||||
* Ensure that block and inline nodes have at least one text child.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
|
||||
const INLINE_NO_EMPTY = {
|
||||
{
|
||||
match: (object) => {
|
||||
return object.kind == 'block'
|
||||
return object.kind == 'block' || object.kind == 'inline'
|
||||
},
|
||||
validate: (block) => {
|
||||
const invalids = block.nodes.filter(n => n.kind == 'inline' && n.text == '')
|
||||
return invalids.size ? invalids : null
|
||||
validate: (node) => {
|
||||
return node.nodes.size == 0
|
||||
},
|
||||
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) {
|
||||
normalize: (transform, node) => {
|
||||
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.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
|
||||
const VOID_TEXT_RULE = {
|
||||
{
|
||||
match: (object) => {
|
||||
return (
|
||||
(object.kind == 'inline' || object.kind == 'block') &&
|
||||
@@ -155,26 +130,59 @@ const VOID_TEXT_RULE = {
|
||||
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
|
||||
* blank text nodes if necessary.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
|
||||
const INLINE_VOID_TEXTS_AROUND_RULE = {
|
||||
{
|
||||
match: (object) => {
|
||||
return object.kind == 'block' || object.kind == 'inline'
|
||||
},
|
||||
validate: (block) => {
|
||||
const invalids = block.nodes.reduce((list, child, index) => {
|
||||
validate: (node) => {
|
||||
const invalids = node.nodes.reduce((list, child, index) => {
|
||||
if (child.kind == 'block') return list
|
||||
if (!child.isVoid) return list
|
||||
|
||||
const prev = index > 0 ? block.nodes.get(index - 1) : null
|
||||
const next = block.nodes.get(index + 1)
|
||||
const prev = index > 0 ? node.nodes.get(index - 1) : null
|
||||
const next = node.nodes.get(index + 1)
|
||||
const insertBefore = !prev
|
||||
const insertAfter = !next || isInlineVoid(next)
|
||||
|
||||
@@ -203,15 +211,15 @@ const INLINE_VOID_TEXTS_AROUND_RULE = {
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
/**
|
||||
* Join adjacent text nodes.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
|
||||
const NO_ADJACENT_TEXT_RULE = {
|
||||
{
|
||||
match: (object) => {
|
||||
return object.kind == 'block' || object.kind == 'inline'
|
||||
},
|
||||
@@ -235,15 +243,15 @@ const NO_ADJACENT_TEXT_RULE = {
|
||||
return transform.joinNodeByKey(second.key, first.key, OPTS)
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
/**
|
||||
* Prevent extra empty text nodes, except when adjacent to inline void nodes.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
|
||||
const NO_EMPTY_TEXT_RULE = {
|
||||
{
|
||||
match: (object) => {
|
||||
return object.kind == 'block' || object.kind == 'inline'
|
||||
},
|
||||
@@ -278,7 +286,9 @@ const NO_EMPTY_TEXT_RULE = {
|
||||
transform.removeNodeByKey(text.key, OPTS)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
]
|
||||
|
||||
/**
|
||||
* 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}
|
||||
*/
|
||||
|
||||
const SCHEMA = Schema.create({
|
||||
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
|
||||
]
|
||||
})
|
||||
const SCHEMA = Schema.create({ rules })
|
||||
|
||||
/**
|
||||
* Export.
|
||||
|
@@ -7,5 +7,3 @@ nodes:
|
||||
- kind: text
|
||||
key: 'anchor'
|
||||
text: Thetext
|
||||
# Selection
|
||||
# Th[ete]xt
|
Reference in New Issue
Block a user