mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-08-29 18:09:49 +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
|
```js
|
||||||
{
|
{
|
||||||
data: Object,
|
data: Object,
|
||||||
|
first: Object,
|
||||||
isVoid: Boolean,
|
isVoid: Boolean,
|
||||||
|
last: Object,
|
||||||
nodes: Array,
|
nodes: Array,
|
||||||
normalize: Function,
|
normalize: Function,
|
||||||
parent: Object,
|
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`
|
### `isVoid`
|
||||||
`Boolean`
|
`Boolean`
|
||||||
@@ -103,6 +116,17 @@ A dictionary of
|
|||||||
|
|
||||||
Will validate a node's `isVoid` property.
|
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`
|
### `nodes`
|
||||||
`Array`
|
`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`
|
||||||
`normalize(change: Change, reason: String, context: Object) => Void`
|
`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`
|
### `node_data_invalid`
|
||||||
|
|
||||||
```js
|
```js
|
||||||
|
@@ -74,7 +74,6 @@ class ForcedLayout extends React.Component {
|
|||||||
schema={schema}
|
schema={schema}
|
||||||
onChange={this.onChange}
|
onChange={this.onChange}
|
||||||
renderNode={this.renderNode}
|
renderNode={this.renderNode}
|
||||||
validateNode={this.validateNode}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</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.
|
* The images example.
|
||||||
*
|
*
|
||||||
@@ -88,11 +108,11 @@ class Images extends React.Component {
|
|||||||
<Editor
|
<Editor
|
||||||
placeholder="Enter some text..."
|
placeholder="Enter some text..."
|
||||||
value={this.state.value}
|
value={this.state.value}
|
||||||
|
schema={schema}
|
||||||
onChange={this.onChange}
|
onChange={this.onChange}
|
||||||
onDrop={this.onDrop}
|
onDrop={this.onDrop}
|
||||||
onPaste={this.onPaste}
|
onPaste={this.onPaste}
|
||||||
renderNode={this.renderNode}
|
renderNode={this.renderNode}
|
||||||
validateNode={this.validateNode}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</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.
|
* On change.
|
||||||
*
|
*
|
||||||
|
@@ -19,6 +19,10 @@ const CHILD_KIND_INVALID = 'child_kind_invalid'
|
|||||||
const CHILD_REQUIRED = 'child_required'
|
const CHILD_REQUIRED = 'child_required'
|
||||||
const CHILD_TYPE_INVALID = 'child_type_invalid'
|
const CHILD_TYPE_INVALID = 'child_type_invalid'
|
||||||
const CHILD_UNKNOWN = 'child_unknown'
|
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_DATA_INVALID = 'node_data_invalid'
|
||||||
const NODE_IS_VOID_INVALID = 'node_is_void_invalid'
|
const NODE_IS_VOID_INVALID = 'node_is_void_invalid'
|
||||||
const NODE_MARK_INVALID = 'node_mark_invalid'
|
const NODE_MARK_INVALID = 'node_mark_invalid'
|
||||||
@@ -204,7 +208,11 @@ class Schema extends Record(DEFAULTS) {
|
|||||||
switch (reason) {
|
switch (reason) {
|
||||||
case CHILD_KIND_INVALID:
|
case CHILD_KIND_INVALID:
|
||||||
case CHILD_TYPE_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
|
const { child, node } = context
|
||||||
return child.kind == 'text' && node.kind == 'block' && node.nodes.size == 1
|
return child.kind == 'text' && node.kind == 'block' && node.nodes.size == 1
|
||||||
? change.removeNodeByKey(node.key)
|
? 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) {
|
if (rule.nodes != null || parents != null) {
|
||||||
const children = node.nodes.toArray()
|
const children = node.nodes.toArray()
|
||||||
const defs = rule.nodes != null ? rule.nodes.slice() : []
|
const defs = rule.nodes != null ? rule.nodes.slice() : []
|
||||||
@@ -470,6 +502,8 @@ function resolveNodeRule(kind, type, obj) {
|
|||||||
data: {},
|
data: {},
|
||||||
isVoid: null,
|
isVoid: null,
|
||||||
nodes: null,
|
nodes: null,
|
||||||
|
first: null,
|
||||||
|
last: null,
|
||||||
parent: null,
|
parent: null,
|
||||||
text: null,
|
text: null,
|
||||||
...obj,
|
...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