mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-08-29 01:50:06 +02:00
Add schema first/last definitions (#1360)
* add `first` and `last` validations to schema * update docs * update schema usage in images example * fix forced-layout example
This commit is contained in:
@@ -69,7 +69,9 @@ A dictionary of inlines by type, each with its own set of validation rules.
|
||||
```js
|
||||
{
|
||||
data: Object,
|
||||
first: Object,
|
||||
isVoid: Boolean,
|
||||
last: Object,
|
||||
nodes: Array,
|
||||
normalize: Function,
|
||||
parent: Object,
|
||||
@@ -90,7 +92,18 @@ Slate schemas are built up of a set of validation rules. Each of the properties
|
||||
}
|
||||
```
|
||||
|
||||
A dictionary of
|
||||
A dictionary of data attributes and their corresponding validation functions. The functions should return a boolean indicating whether the data value is valid or not.
|
||||
|
||||
### `first`
|
||||
`Object`
|
||||
|
||||
```js
|
||||
{
|
||||
first: { types: ['quote', 'paragraph'] },
|
||||
}
|
||||
```
|
||||
|
||||
Will validate the first child node. The `first` definition can declare `kinds` and `types` properties.
|
||||
|
||||
### `isVoid`
|
||||
`Boolean`
|
||||
@@ -103,6 +116,17 @@ A dictionary of
|
||||
|
||||
Will validate a node's `isVoid` property.
|
||||
|
||||
### `last`
|
||||
`Object`
|
||||
|
||||
```js
|
||||
{
|
||||
last: { types: ['quote', 'paragraph'] },
|
||||
}
|
||||
```
|
||||
|
||||
Will validate the last child node. The `last` definition can declare `kinds` and `types` properties.
|
||||
|
||||
### `nodes`
|
||||
`Array`
|
||||
|
||||
@@ -115,7 +139,9 @@ Will validate a node's `isVoid` property.
|
||||
}
|
||||
```
|
||||
|
||||
Will validate a node's children. The node definitions can declare the `kinds`, `types`, `min` and `max` properties.
|
||||
Will validate a node's children. The `nodes` definitions can declare the `kinds`, `types`, `min` and `max` properties.
|
||||
|
||||
> 🤖 The `nodes` array is order-sensitive! The example above will require that the first node be either an `image` or `video`, and that it be followed by one or more `paragraph` nodes.
|
||||
|
||||
### `normalize`
|
||||
`normalize(change: Change, reason: String, context: Object) => Void`
|
||||
@@ -233,6 +259,46 @@ When supplying your own `normalize` property for a schema rule, it will be calle
|
||||
}
|
||||
```
|
||||
|
||||
### `first_child_kind_invalid`
|
||||
|
||||
```js
|
||||
{
|
||||
child: Node,
|
||||
node: Node,
|
||||
rule: Object,
|
||||
}
|
||||
```
|
||||
|
||||
### `first_child_type_invalid`
|
||||
|
||||
```js
|
||||
{
|
||||
child: Node,
|
||||
node: Node,
|
||||
rule: Object,
|
||||
}
|
||||
```
|
||||
|
||||
### `last_child_kind_invalid`
|
||||
|
||||
```js
|
||||
{
|
||||
child: Node,
|
||||
node: Node,
|
||||
rule: Object,
|
||||
}
|
||||
```
|
||||
|
||||
### `last_child_type_invalid`
|
||||
|
||||
```js
|
||||
{
|
||||
child: Node,
|
||||
node: Node,
|
||||
rule: Object,
|
||||
}
|
||||
```
|
||||
|
||||
### `node_data_invalid`
|
||||
|
||||
```js
|
||||
|
@@ -74,7 +74,6 @@ class ForcedLayout extends React.Component {
|
||||
schema={schema}
|
||||
onChange={this.onChange}
|
||||
renderNode={this.renderNode}
|
||||
validateNode={this.validateNode}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
@@ -27,6 +27,26 @@ function insertImage(change, src, target) {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* A schema to enforce that there's always a paragraph as the last block.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
|
||||
const schema = {
|
||||
document: {
|
||||
last: { types: ['paragraph'] },
|
||||
normalize: (change, reason, { node, child }) => {
|
||||
switch (reason) {
|
||||
case 'last_child_type_invalid': {
|
||||
const paragraph = Block.create('paragraph')
|
||||
return change.insertNodeByKey(node.key, node.nodes.size, paragraph)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The images example.
|
||||
*
|
||||
@@ -88,11 +108,11 @@ class Images extends React.Component {
|
||||
<Editor
|
||||
placeholder="Enter some text..."
|
||||
value={this.state.value}
|
||||
schema={schema}
|
||||
onChange={this.onChange}
|
||||
onDrop={this.onDrop}
|
||||
onPaste={this.onPaste}
|
||||
renderNode={this.renderNode}
|
||||
validateNode={this.validateNode}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
@@ -119,24 +139,6 @@ class Images extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform node validation on the document.
|
||||
*
|
||||
* @param {Node} node
|
||||
* @return {Function|Void}
|
||||
*/
|
||||
|
||||
validateNode = (node) => {
|
||||
if (node.kind != 'document') return
|
||||
const last = node.nodes.last()
|
||||
|
||||
if (!last || last.type != 'paragraph') {
|
||||
const index = node.nodes.size
|
||||
const paragraph = Block.create('paragraph')
|
||||
return change => change.insertNodeByKey(node.key, index, paragraph)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* On change.
|
||||
*
|
||||
|
@@ -19,6 +19,10 @@ const CHILD_KIND_INVALID = 'child_kind_invalid'
|
||||
const CHILD_REQUIRED = 'child_required'
|
||||
const CHILD_TYPE_INVALID = 'child_type_invalid'
|
||||
const CHILD_UNKNOWN = 'child_unknown'
|
||||
const FIRST_CHILD_KIND_INVALID = 'first_child_kind_invalid'
|
||||
const FIRST_CHILD_TYPE_INVALID = 'first_child_type_invalid'
|
||||
const LAST_CHILD_KIND_INVALID = 'last_child_kind_invalid'
|
||||
const LAST_CHILD_TYPE_INVALID = 'last_child_type_invalid'
|
||||
const NODE_DATA_INVALID = 'node_data_invalid'
|
||||
const NODE_IS_VOID_INVALID = 'node_is_void_invalid'
|
||||
const NODE_MARK_INVALID = 'node_mark_invalid'
|
||||
@@ -204,7 +208,11 @@ class Schema extends Record(DEFAULTS) {
|
||||
switch (reason) {
|
||||
case CHILD_KIND_INVALID:
|
||||
case CHILD_TYPE_INVALID:
|
||||
case CHILD_UNKNOWN: {
|
||||
case CHILD_UNKNOWN:
|
||||
case FIRST_CHILD_KIND_INVALID:
|
||||
case FIRST_CHILD_TYPE_INVALID:
|
||||
case LAST_CHILD_KIND_INVALID:
|
||||
case LAST_CHILD_TYPE_INVALID: {
|
||||
const { child, node } = context
|
||||
return child.kind == 'text' && node.kind == 'block' && node.nodes.size == 1
|
||||
? change.removeNodeByKey(node.key)
|
||||
@@ -295,6 +303,30 @@ class Schema extends Record(DEFAULTS) {
|
||||
}
|
||||
}
|
||||
|
||||
if (rule.first != null) {
|
||||
const first = node.nodes.first()
|
||||
|
||||
if (rule.first.kinds != null && !rule.first.kinds.includes(first.kind)) {
|
||||
return this.fail(FIRST_CHILD_KIND_INVALID, { ...ctx, child: first })
|
||||
}
|
||||
|
||||
if (rule.first.types != null && !rule.first.types.includes(first.type)) {
|
||||
return this.fail(FIRST_CHILD_TYPE_INVALID, { ...ctx, child: first })
|
||||
}
|
||||
}
|
||||
|
||||
if (rule.last != null) {
|
||||
const last = node.nodes.last()
|
||||
|
||||
if (rule.last.kinds != null && !rule.last.kinds.includes(last.kind)) {
|
||||
return this.fail(LAST_CHILD_KIND_INVALID, { ...ctx, child: last })
|
||||
}
|
||||
|
||||
if (rule.last.types != null && !rule.last.types.includes(last.type)) {
|
||||
return this.fail(LAST_CHILD_TYPE_INVALID, { ...ctx, child: last })
|
||||
}
|
||||
}
|
||||
|
||||
if (rule.nodes != null || parents != null) {
|
||||
const children = node.nodes.toArray()
|
||||
const defs = rule.nodes != null ? rule.nodes.slice() : []
|
||||
@@ -470,6 +502,8 @@ function resolveNodeRule(kind, type, obj) {
|
||||
data: {},
|
||||
isVoid: null,
|
||||
nodes: null,
|
||||
first: null,
|
||||
last: null,
|
||||
parent: null,
|
||||
text: null,
|
||||
...obj,
|
||||
|
@@ -0,0 +1,39 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../../helpers/h'
|
||||
|
||||
export const schema = {
|
||||
blocks: {
|
||||
paragraph: {},
|
||||
quote: {
|
||||
first: { kinds: ['block'] },
|
||||
normalize: (change, reason, { child }) => {
|
||||
if (reason == 'first_child_kind_invalid') {
|
||||
change.wrapBlockByKey(child.key, 'paragraph')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const input = (
|
||||
<value>
|
||||
<document>
|
||||
<quote>
|
||||
text
|
||||
</quote>
|
||||
</document>
|
||||
</value>
|
||||
)
|
||||
|
||||
export const output = (
|
||||
<value>
|
||||
<document>
|
||||
<quote>
|
||||
<paragraph>
|
||||
text
|
||||
</paragraph>
|
||||
</quote>
|
||||
</document>
|
||||
</value>
|
||||
)
|
@@ -0,0 +1,30 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../../helpers/h'
|
||||
|
||||
export const schema = {
|
||||
blocks: {
|
||||
paragraph: {},
|
||||
quote: {
|
||||
first: { kinds: ['text'] },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const input = (
|
||||
<value>
|
||||
<document>
|
||||
<quote>
|
||||
<paragraph />
|
||||
</quote>
|
||||
</document>
|
||||
</value>
|
||||
)
|
||||
|
||||
export const output = (
|
||||
<value>
|
||||
<document>
|
||||
<quote />
|
||||
</document>
|
||||
</value>
|
||||
)
|
@@ -0,0 +1,43 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../../helpers/h'
|
||||
|
||||
export const schema = {
|
||||
blocks: {
|
||||
paragraph: {},
|
||||
quote: {
|
||||
first: { types: ['paragraph'] },
|
||||
normalize: (change, reason, { child }) => {
|
||||
if (reason == 'first_child_type_invalid') {
|
||||
change.wrapBlockByKey(child.key, 'paragraph')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const input = (
|
||||
<value>
|
||||
<document>
|
||||
<quote>
|
||||
<image />
|
||||
<paragraph />
|
||||
<paragraph />
|
||||
</quote>
|
||||
</document>
|
||||
</value>
|
||||
)
|
||||
|
||||
export const output = (
|
||||
<value>
|
||||
<document>
|
||||
<quote>
|
||||
<paragraph>
|
||||
<image />
|
||||
</paragraph>
|
||||
<paragraph />
|
||||
<paragraph />
|
||||
</quote>
|
||||
</document>
|
||||
</value>
|
||||
)
|
@@ -0,0 +1,35 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../../helpers/h'
|
||||
|
||||
export const schema = {
|
||||
blocks: {
|
||||
paragraph: {},
|
||||
quote: {
|
||||
first: { types: ['paragraph'] }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const input = (
|
||||
<value>
|
||||
<document>
|
||||
<quote>
|
||||
<image />
|
||||
<paragraph />
|
||||
<paragraph />
|
||||
</quote>
|
||||
</document>
|
||||
</value>
|
||||
)
|
||||
|
||||
export const output = (
|
||||
<value>
|
||||
<document>
|
||||
<quote>
|
||||
<paragraph />
|
||||
<paragraph />
|
||||
</quote>
|
||||
</document>
|
||||
</value>
|
||||
)
|
@@ -0,0 +1,39 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../../helpers/h'
|
||||
|
||||
export const schema = {
|
||||
blocks: {
|
||||
paragraph: {},
|
||||
quote: {
|
||||
last: { kinds: ['block'] },
|
||||
normalize: (change, reason, { child }) => {
|
||||
if (reason == 'last_child_kind_invalid') {
|
||||
change.wrapBlockByKey(child.key, 'paragraph')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const input = (
|
||||
<value>
|
||||
<document>
|
||||
<quote>
|
||||
text
|
||||
</quote>
|
||||
</document>
|
||||
</value>
|
||||
)
|
||||
|
||||
export const output = (
|
||||
<value>
|
||||
<document>
|
||||
<quote>
|
||||
<paragraph>
|
||||
text
|
||||
</paragraph>
|
||||
</quote>
|
||||
</document>
|
||||
</value>
|
||||
)
|
@@ -0,0 +1,30 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../../helpers/h'
|
||||
|
||||
export const schema = {
|
||||
blocks: {
|
||||
paragraph: {},
|
||||
quote: {
|
||||
last: { kinds: ['text'] },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const input = (
|
||||
<value>
|
||||
<document>
|
||||
<quote>
|
||||
<paragraph />
|
||||
</quote>
|
||||
</document>
|
||||
</value>
|
||||
)
|
||||
|
||||
export const output = (
|
||||
<value>
|
||||
<document>
|
||||
<quote />
|
||||
</document>
|
||||
</value>
|
||||
)
|
@@ -0,0 +1,43 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../../helpers/h'
|
||||
|
||||
export const schema = {
|
||||
blocks: {
|
||||
paragraph: {},
|
||||
quote: {
|
||||
last: { types: ['paragraph'] },
|
||||
normalize: (change, reason, { child }) => {
|
||||
if (reason == 'last_child_type_invalid') {
|
||||
change.wrapBlockByKey(child.key, 'paragraph')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const input = (
|
||||
<value>
|
||||
<document>
|
||||
<quote>
|
||||
<paragraph />
|
||||
<paragraph />
|
||||
<image />
|
||||
</quote>
|
||||
</document>
|
||||
</value>
|
||||
)
|
||||
|
||||
export const output = (
|
||||
<value>
|
||||
<document>
|
||||
<quote>
|
||||
<paragraph />
|
||||
<paragraph />
|
||||
<paragraph>
|
||||
<image />
|
||||
</paragraph>
|
||||
</quote>
|
||||
</document>
|
||||
</value>
|
||||
)
|
@@ -0,0 +1,35 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../../helpers/h'
|
||||
|
||||
export const schema = {
|
||||
blocks: {
|
||||
paragraph: {},
|
||||
quote: {
|
||||
last: { types: ['paragraph'] }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const input = (
|
||||
<value>
|
||||
<document>
|
||||
<quote>
|
||||
<paragraph />
|
||||
<paragraph />
|
||||
<image />
|
||||
</quote>
|
||||
</document>
|
||||
</value>
|
||||
)
|
||||
|
||||
export const output = (
|
||||
<value>
|
||||
<document>
|
||||
<quote>
|
||||
<paragraph />
|
||||
<paragraph />
|
||||
</quote>
|
||||
</document>
|
||||
</value>
|
||||
)
|
Reference in New Issue
Block a user