mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-08-30 10:29:48 +02:00
simplify schema rules, update docs
This commit is contained in:
20
Readme.md
20
Readme.md
@@ -104,17 +104,17 @@ Slate encourages you to write small, reusable modules. Check out the public ones
|
||||
<br/>
|
||||
### Documentation
|
||||
|
||||
If you're using Slate for the first time, check out the [Getting Started](./docs/guides/installing-slate.md) guides and the [Core Concepts](./docs/concepts) to familiarize yourself with Slate's architecture and mental models. Once you've gotten familiar with those, you'll probably want to check out the full [API Reference](./docs/reference).
|
||||
If you're using Slate for the first time, check out the [Getting Started](./docs/walkthroughs/installing-slate.md) walkthroughs and the [Core Concepts](./docs/concepts) to familiarize yourself with Slate's architecture and mental models. Once you've gotten familiar with those, you'll probably want to check out the full [API Reference](./docs/reference).
|
||||
|
||||
- [**Guides**](./docs/guides)
|
||||
- [Installing Slate](./docs/guides/installing-slate.md)
|
||||
- [Using the Bundled Source](./docs/guides/using-the-bundled-source.md)
|
||||
- [Adding Event Handlers](./docs/guides/adding-event-handlers.md)
|
||||
- [Defining Custom Block Nodes](./docs/guides/defining-custom-block-nodes.md)
|
||||
- [Applying Custom Formatting](./docs/guides/applying-custom-formatting.md)
|
||||
- [Using Plugins](./docs/guides/using-plugins.md)
|
||||
- [Saving to a Database](./docs/guides/saving-to-a-database.md)
|
||||
- [Saving and Loading HTML Content](./docs/guides/saving-and-loading-html-content.md)
|
||||
- [**Walkthroughts**](./docs/walkthroughs)
|
||||
- [Installing Slate](./docs/walkthroughs/installing-slate.md)
|
||||
- [Using the Bundled Source](./docs/walkthroughs/using-the-bundled-source.md)
|
||||
- [Adding Event Handlers](./docs/walkthroughs/adding-event-handlers.md)
|
||||
- [Defining Custom Block Nodes](./docs/walkthroughs/defining-custom-block-nodes.md)
|
||||
- [Applying Custom Formatting](./docs/walkthroughs/applying-custom-formatting.md)
|
||||
- [Using Plugins](./docs/walkthroughs/using-plugins.md)
|
||||
- [Saving to a Database](./docs/walkthroughs/saving-to-a-database.md)
|
||||
- [Saving and Loading HTML Content](./docs/walkthroughs/saving-and-loading-html-content.md)
|
||||
- [**Concepts**](./docs/concepts)
|
||||
- [Statelessness & Immutability](./docs/concepts/statelessness-and-immutability.md)
|
||||
- [The Document Model](./docs/concepts/the-document-model.md)
|
||||
|
@@ -16,8 +16,8 @@ Slate schemas are built up of a set of rules. Every rule has a few properties:
|
||||
```js
|
||||
{
|
||||
match: Function || Object,
|
||||
component: Component || Function || Object || String,
|
||||
decorator: Function,
|
||||
render: Component || Function || Object || String,
|
||||
decorate: Function,
|
||||
validate: Function || Object,
|
||||
transform: Function
|
||||
}
|
||||
|
@@ -1,185 +0,0 @@
|
||||
|
||||
# Schemas
|
||||
|
||||
Every Slate editor has a "schema" associated with it, which contains information about the structure of its content. It lets you specify how to render each different type of node. And for more advanced use cases it lets you enforce rules about what the content of the editor can and cannot be.
|
||||
|
||||
- [Properties](#properties)
|
||||
- [`marks`](#marks)
|
||||
- [`nodes`](#nodes)
|
||||
- [`rules`](#rules)
|
||||
- [Rule Properties](#rule-properties)
|
||||
- [`component`](#component)
|
||||
- [`decorator`](#decorator)
|
||||
- [`match`](#match)
|
||||
- [`transform`](#transform)
|
||||
- [`validate`](#validate)
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
```js
|
||||
{
|
||||
marks: Object,
|
||||
nodes: Object,
|
||||
rules: Array
|
||||
}
|
||||
```
|
||||
|
||||
The top-level properties of a schema all give you a way to define `rules` that the schema enforces. The `nodes` and `marks` properties are just convenient ways to define the most common set of rules.
|
||||
|
||||
### `marks`
|
||||
`Object type: Component || Function || Object || String`
|
||||
|
||||
```js
|
||||
{
|
||||
bold: props => <strong>{props.children}</strong>
|
||||
}
|
||||
```
|
||||
```js
|
||||
{
|
||||
bold: {
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
}
|
||||
```
|
||||
```js
|
||||
{
|
||||
bold: 'my-bold-class-name'
|
||||
}
|
||||
```
|
||||
|
||||
An object that defines the [`Marks`](./mark.md) in the schema by `type`. Each key in the object refers to a mark by its `type`. The value defines how Slate will render the mark, and can either be a React component, an object of styles, or a class name.
|
||||
|
||||
### `nodes`
|
||||
`Object<type, Component || Function>`
|
||||
`Object<type, Rule>`
|
||||
|
||||
```js
|
||||
{
|
||||
quote: props => <blockquote {...props.attributes}>{props.children}</blockquote>
|
||||
}
|
||||
```
|
||||
```js
|
||||
{
|
||||
code: {
|
||||
component: props => <pre {...props.attributes}><code>{props.children}</code></pre>,
|
||||
decorator: myCodeHighlighter
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
An object that defines the [`Block`](./block.md) and [`Inline`](./inline.md) nodes in the schema by `type`. Each key in the object refers to a node by its `type`. The values defines how Slate will render the node, and can optionall define any other property of a schema `Rule`.
|
||||
|
||||
### `rules`
|
||||
`Array<Rule>`
|
||||
|
||||
```js
|
||||
[
|
||||
{
|
||||
match: { kind: 'block', type: 'code' },
|
||||
component: props => <pre {...props.attributes}><code>{props.children}</code></pre>,
|
||||
decorator: myCodeHighlighter
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
An array of rules that define the schema's behavior. Each of the rules are evaluated in order to determine a match.
|
||||
|
||||
Internally, the `marks` and `nodes` properties of a schema are simply converted into `rules`.
|
||||
|
||||
|
||||
## Rule Properties
|
||||
|
||||
```js
|
||||
{
|
||||
match: Function || Object,
|
||||
component: Component || Function || Object || String,
|
||||
decorator: Function,
|
||||
validate: Function || Object,
|
||||
transform: Function
|
||||
}
|
||||
```
|
||||
|
||||
Slate schemas are built up of a set of rules. Each of the properties will add certain functionality to the schema, based on the properties it defines.
|
||||
|
||||
### `match`
|
||||
`Function`
|
||||
`Object`
|
||||
|
||||
```js
|
||||
(node) => node.kind == 'block' && node.type == 'quote'
|
||||
```
|
||||
```js
|
||||
{
|
||||
kind: 'block',
|
||||
type: 'quote'
|
||||
}
|
||||
```
|
||||
|
||||
The `match` property is the only required property of a rule. It determines which nodes are matched when a rule is matched. In the simplest form it is a function which returns a boolean, but it can also be expressed in object form.
|
||||
|
||||
### `component`
|
||||
`ReactComponent` <br/>
|
||||
`Function component(props: Object) => Any` <br/>
|
||||
`Object<cssProperty, cssValue>` <br/>
|
||||
`String`
|
||||
|
||||
```js
|
||||
(props) => <blockquote {...props.attributes}>{props.children}</blockquote>
|
||||
```
|
||||
|
||||
The `component` property determines how Slate will render the node or mark that was matched by the `match` property. In addition to a React component, marks can also be rendered by supplying an object of styles, or a class name string.
|
||||
|
||||
### `decorator`
|
||||
`Function decorate(text: Text, match: Node) => List<Character>`
|
||||
|
||||
The `decorator` property defines a function that can add extra marks to the text inside of a node, for example for code highlighting. It is called with the [`Text`](./text.md) node in question, and the [`Node`](./node.md) matched by the `match` property, and should return a list of [`Characters`](./character.md) with the desired marks applied.
|
||||
|
||||
### `validate`
|
||||
`Function (match: Node) => Any` <br/>
|
||||
`Object`
|
||||
|
||||
```js
|
||||
{
|
||||
nodesAnyOf: [
|
||||
{ kind: 'block' }
|
||||
],
|
||||
nodesNoneOf: [
|
||||
{ type: 'quote' }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
The `validate` property defines a series of constraints that the [`Node`](./node.md) must abide by, for example that its children only be other [`Block`](./block.md) nodes. The full list of validations supported is:
|
||||
|
||||
- `kind` `String` — the
|
||||
- `kinds`
|
||||
- `maxLength`
|
||||
- `maxNodes` `Number` — the maximum number of child nodes a node can have.
|
||||
- `minLength`
|
||||
- `minNodes` `Number` —
|
||||
- `nodesAnyOf` `Array` — an array of `match` objects that the nodes can match.
|
||||
- `nodesExactlyOf` `Array` — an array of `match` objects that the nodes must all match exactly in order.
|
||||
- `nodesNoneOf` `Array` — an array of `match` objects that the nodes must not match.
|
||||
- `text`
|
||||
- `type`
|
||||
- `types`
|
||||
|
||||
|
||||
## Matches
|
||||
|
||||
For any schema rule to be applied, it has to match a node in the editor's content. The most basic way to do this is to match by `kind` and `type`. For example:
|
||||
|
||||
|
||||
|
||||
## Components
|
||||
|
||||
The most basic use of a schema is to define which React components should be rendered for each node in the editor. For example, you might want to
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Match Properties
|
||||
|
||||
## Validate Properties
|
@@ -8,9 +8,9 @@ Every Slate editor has a "schema" associated with it, which contains information
|
||||
- [`nodes`](#nodes)
|
||||
- [`rules`](#rules)
|
||||
- [Rule Properties](#rule-properties)
|
||||
- [`component`](#component)
|
||||
- [`decorator`](#decorator)
|
||||
- [`decorate`](#decorate)
|
||||
- [`match`](#match)
|
||||
- [`render`](#render)
|
||||
- [`transform`](#transform)
|
||||
- [`validate`](#validate)
|
||||
|
||||
@@ -19,8 +19,8 @@ Every Slate editor has a "schema" associated with it, which contains information
|
||||
|
||||
```js
|
||||
{
|
||||
marks: Object,
|
||||
nodes: Object,
|
||||
marks: Object,
|
||||
rules: Array
|
||||
}
|
||||
```
|
||||
@@ -62,8 +62,8 @@ An object that defines the [`Marks`](./mark.md) in the schema by `type`. Each ke
|
||||
```js
|
||||
{
|
||||
code: {
|
||||
component: props => <pre {...props.attributes}><code>{props.children}</code></pre>,
|
||||
decorator: myCodeHighlighter
|
||||
render: props => <pre {...props.attributes}><code>{props.children}</code></pre>,
|
||||
decorate: myCodeHighlighter
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -78,7 +78,7 @@ An object that defines the [`Block`](./block.md) and [`Inline`](./inline.md) nod
|
||||
{
|
||||
match: { kind: 'block', type: 'code' },
|
||||
component: props => <pre {...props.attributes}><code>{props.children}</code></pre>,
|
||||
decorator: myCodeHighlighter
|
||||
decorate: myCodeHighlighter
|
||||
}
|
||||
]
|
||||
```
|
||||
@@ -92,43 +92,88 @@ Internally, the `marks` and `nodes` properties of a schema are simply converted
|
||||
|
||||
```js
|
||||
{
|
||||
match: Function || Object,
|
||||
component: Component || Function || Object || String,
|
||||
decorator: Function,
|
||||
validate: Function || Object,
|
||||
transform: Function
|
||||
match: Function,
|
||||
decorate: Function,
|
||||
render: Component || Function || Object || String,
|
||||
transform: Function,
|
||||
validate: Function
|
||||
}
|
||||
```
|
||||
|
||||
Slate schemas are built up of a set of rules. Each of the properties will add certain functionality to the schema, based on the properties it defines.
|
||||
|
||||
### `match`
|
||||
`Object || Function`
|
||||
`Function match(object: Node || Mark)`
|
||||
|
||||
```js
|
||||
{
|
||||
kind: 'block',
|
||||
type: 'quote'
|
||||
match: (object) => object.kind == 'block' && object.type == 'code'
|
||||
}
|
||||
```
|
||||
|
||||
The `match` property is the only required property of a rule. It determines which nodes are matched when a rule is matched.
|
||||
The `match` property is the only required property of a rule. It determines which objects the rule applies to.
|
||||
|
||||
### `decorate`
|
||||
`Function decorate(text: Node, object: Node) => List<Characters>`
|
||||
|
||||
## Matches
|
||||
```js
|
||||
{
|
||||
decorate: (text, node) => {
|
||||
let { characters } = text
|
||||
let first = characters.get(0)
|
||||
let { marks } = first
|
||||
let mark = Mark.create({ type: 'bold' })
|
||||
marks = marks.add(mark)
|
||||
first = first.merge({ marks })
|
||||
characters = characters.set(0, first)
|
||||
return characters
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
For any schema rule to be applied, it has to match a node in the editor's content. The most basic way to do this is to match by `kind` and `type`. For example:
|
||||
The `decorate` property allows you define a function that will apply extra marks to all of the ranges of text inside a node. It is called with a [`Text`](./text.md) node and the matched node. It should return a list of characters with the desired marks, which will then be added to the text before rendering.
|
||||
|
||||
### `render`
|
||||
`Component` <br/>
|
||||
`Function` <br/>
|
||||
`Object` <br/>
|
||||
`String`
|
||||
|
||||
```js
|
||||
{
|
||||
render: (props) => <pre {...props.attributes}><code>{props.children}</code></pre>
|
||||
}
|
||||
```
|
||||
|
||||
## Components
|
||||
The `render` property determines which React component Slate will use to render a [`Node`](./node.md) or [`Mark`](./mark.md). Mark renderers can also be defined as an object of styles or a class name string for convenience.
|
||||
|
||||
The most basic use of a schema is to define which React components should be rendered for each node in the editor. For example, you might want to
|
||||
### `transform`
|
||||
`Function transform(transform: Transform, object: Node, failure: Any) => Transform`
|
||||
|
||||
```js
|
||||
{
|
||||
transform: (transform, node, invalidChildren) => {
|
||||
invalidChildren.forEach((child) => {
|
||||
transform = transform.removeNodeByKey(child.key)
|
||||
})
|
||||
|
||||
return transform
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The `transform` property is run to recover the editor's state after the `validate` property of a rule has determined that an object is invalid. It is passed a [`Transform`](./transform.md) that it can use to make modifications. It is also passed the return value of the `validate` function, which makes it easy to quickly determine the reason validation failed.
|
||||
|
||||
### `validate`
|
||||
`Function validate(object: Node) => Any || Void`
|
||||
|
||||
## Match Properties
|
||||
```js
|
||||
{
|
||||
validate: (node) => {
|
||||
const invalidChildren = node.nodes.filter(child => child.kind == 'block')
|
||||
return invalidChildren.size ? invalidChildren : null
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Validate Properties
|
||||
The `validate` property allows you to define a constraint that the matching object must abide by. It should return either `Void` if the object is valid, or any non-void value if it is invalid. This makes it easy to return the exact reason that the object is invalid, which makes it simple to recover from the invalid state with the `transform` property.
|
||||
|
@@ -138,7 +138,7 @@ class Schema extends new Record(DEFAULTS) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the component for an `object`.
|
||||
* Return the renderer for an `object`.
|
||||
*
|
||||
* This method is private, because it should always be called on one of the
|
||||
* often-changing immutable objects instead, since it will be memoized for
|
||||
@@ -149,9 +149,9 @@ class Schema extends new Record(DEFAULTS) {
|
||||
*/
|
||||
|
||||
__getComponent(object) {
|
||||
const match = this.rules.find(rule => rule.match(object) && rule.component)
|
||||
const match = this.rules.find(rule => rule.match(object) && rule.render)
|
||||
if (!match) return
|
||||
return match.component
|
||||
return match.render
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -167,17 +167,17 @@ class Schema extends new Record(DEFAULTS) {
|
||||
|
||||
__getDecorators(object) {
|
||||
return this.rules
|
||||
.filter(rule => rule.match(object) && rule.decorator)
|
||||
.filter(rule => rule.match(object) && rule.decorate)
|
||||
.map((rule) => {
|
||||
return (text) => {
|
||||
return rule.decorator(text, object)
|
||||
return rule.decorate(text, object)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate an `object` against the schema, returning the failing rule and
|
||||
* reason if the object is invalid, or void if it's valid.
|
||||
* value if the object is invalid, or void if it's valid.
|
||||
*
|
||||
* This method is private, because it should always be called on one of the
|
||||
* often-changing immutable objects instead, since it will be memoized for
|
||||
@@ -188,20 +188,21 @@ class Schema extends new Record(DEFAULTS) {
|
||||
*/
|
||||
|
||||
__validate(object) {
|
||||
let reason
|
||||
let value
|
||||
|
||||
const match = this.rules.find((rule) => {
|
||||
if (!rule.match(object)) return
|
||||
if (!rule.validate) return
|
||||
reason = rule.validate(object)
|
||||
return reason
|
||||
|
||||
value = rule.validate(object)
|
||||
return value
|
||||
})
|
||||
|
||||
if (!reason) return
|
||||
if (!value) return
|
||||
|
||||
return {
|
||||
rule: match,
|
||||
reason
|
||||
value,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,9 +228,7 @@ function normalizeProperties(properties) {
|
||||
rules = rules.concat(array)
|
||||
}
|
||||
|
||||
return {
|
||||
rules: rules.map(normalizeRule)
|
||||
}
|
||||
return { rules }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -243,23 +242,20 @@ function normalizeNodes(nodes) {
|
||||
const rules = []
|
||||
|
||||
for (const key in nodes) {
|
||||
const value = nodes[key]
|
||||
const match = {
|
||||
kinds: ['block', 'inline'],
|
||||
type: key,
|
||||
let rule = nodes[key]
|
||||
|
||||
if (typeOf(rule) == 'function' || isReactComponent(rule)) {
|
||||
rule = { render: rule }
|
||||
}
|
||||
|
||||
if (value.component || value.decorator || value.validate) {
|
||||
rules.push({
|
||||
match,
|
||||
...value,
|
||||
})
|
||||
} else {
|
||||
rules.push({
|
||||
match,
|
||||
component: value
|
||||
})
|
||||
rule.match = (object) => {
|
||||
return (
|
||||
(object.kind == 'block' || object.kind == 'inline') &&
|
||||
object.type == key
|
||||
)
|
||||
}
|
||||
|
||||
rules.push(rule)
|
||||
}
|
||||
|
||||
return rules
|
||||
@@ -276,146 +272,37 @@ function normalizeMarks(marks) {
|
||||
const rules = []
|
||||
|
||||
for (const key in marks) {
|
||||
const value = marks[key]
|
||||
const match = {
|
||||
kind: 'mark',
|
||||
type: key,
|
||||
let rule = marks[key]
|
||||
|
||||
if (!rule.render && !rule.decorator && !rule.validate) {
|
||||
rule = { render: rule }
|
||||
}
|
||||
|
||||
if (value.component || value.decorator || value.validate) {
|
||||
rules.push({
|
||||
match,
|
||||
...value,
|
||||
component: normalizeMarkComponent(value.component),
|
||||
})
|
||||
} else {
|
||||
rules.push({
|
||||
match,
|
||||
component: normalizeMarkComponent(value)
|
||||
})
|
||||
}
|
||||
rule.render = normalizeMarkComponent(rule.render)
|
||||
rule.match = object => object.kind == 'mark' && object.type == key
|
||||
rules.push(rule)
|
||||
}
|
||||
|
||||
return rules
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize a mark `component`.
|
||||
* Normalize a mark `render` property.
|
||||
*
|
||||
* @param {Component || Function || Object || String} component
|
||||
* @param {Component || Function || Object || String} render
|
||||
* @return {Component}
|
||||
*/
|
||||
|
||||
function normalizeMarkComponent(component) {
|
||||
if (isReactComponent(component)) return component
|
||||
function normalizeMarkComponent(render) {
|
||||
if (isReactComponent(render)) return render
|
||||
|
||||
switch (typeOf(component)) {
|
||||
switch (typeOf(render)) {
|
||||
case 'function':
|
||||
return component
|
||||
return render
|
||||
case 'object':
|
||||
return props => <span style={component}>{props.children}</span>
|
||||
return props => <span style={render}>{props.children}</span>
|
||||
case 'string':
|
||||
return props => <span className={component}>{props.children}</span>
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize a `rule` object.
|
||||
*
|
||||
* @param {Object} rule
|
||||
* @return {Object}
|
||||
*/
|
||||
|
||||
function normalizeRule(rule) {
|
||||
return {
|
||||
...rule,
|
||||
match: normalizeMatch(rule.match),
|
||||
validate: normalizeValidate(rule.validate),
|
||||
transform: normalizeTransform(rule.transform),
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize a `match` spec.
|
||||
*
|
||||
* @param {Function || Object || String} match
|
||||
* @return {Function}
|
||||
*/
|
||||
|
||||
function normalizeMatch(match) {
|
||||
switch (typeOf(match)) {
|
||||
case 'function': return match
|
||||
case 'boolean': return () => match
|
||||
case 'object': return normalizeSpec(match)
|
||||
default: {
|
||||
throw new Error(`Invalid \`match\` spec: "${match}".`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize a `validate` spec.
|
||||
*
|
||||
* @param {Function || Object || String} validate
|
||||
* @return {Function}
|
||||
*/
|
||||
|
||||
function normalizeValidate(validate) {
|
||||
switch (typeOf(validate)) {
|
||||
case 'function': return validate
|
||||
case 'boolean': return () => validate
|
||||
case 'object': return normalizeSpec(validate, true)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize a `transform` spec.
|
||||
*
|
||||
* @param {Function || Object || String} transform
|
||||
* @return {Function}
|
||||
*/
|
||||
|
||||
function normalizeTransform(transform) {
|
||||
switch (typeOf(transform)) {
|
||||
case 'function': return transform
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize a `spec` object.
|
||||
*
|
||||
* @param {Object} obj
|
||||
* @param {Boolean} giveReason
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
function normalizeSpec(obj, giveReason) {
|
||||
const spec = { ...obj }
|
||||
if (spec.exactlyOf) spec.exactlyOf = spec.exactlyOf.map(s => normalizeSpec(s))
|
||||
if (spec.anyOf) spec.anyOf = spec.anyOf.map(s => normalizeSpec(s))
|
||||
if (spec.noneOf) spec.noneOf = spec.noneOf.map(s => normalizeSpec(s))
|
||||
|
||||
return (node) => {
|
||||
for (const key in CHECKS) {
|
||||
const value = spec[key]
|
||||
|
||||
if (value == null) continue
|
||||
|
||||
const fail = CHECKS[key]
|
||||
const failure = fail(node, value)
|
||||
if (failure === undefined) continue
|
||||
|
||||
if (giveReason) {
|
||||
return {
|
||||
type: key,
|
||||
value: failure
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return giveReason ? undefined : true
|
||||
return props => <span className={render}>{props.children}</span>
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -373,14 +373,14 @@ class State extends new Record(DEFAULTS) {
|
||||
|
||||
document.filterDescendantsDeep((node) => {
|
||||
if (failure = node.validate(schema)) {
|
||||
const { reason, rule } = failure
|
||||
transform = rule.transform(transform, node, reason)
|
||||
const { value, rule } = failure
|
||||
transform = rule.transform(transform, node, value)
|
||||
}
|
||||
})
|
||||
|
||||
if (failure = document.validate(schema)) {
|
||||
const { reason, rule } = failure
|
||||
transform = rule.transform(transform, document, reason)
|
||||
const { value, rule } = failure
|
||||
transform = rule.transform(transform, document, value)
|
||||
}
|
||||
|
||||
return transform.steps.size
|
||||
|
@@ -32,108 +32,6 @@ function Plugin(options = {}) {
|
||||
placeholderStyle,
|
||||
} = options
|
||||
|
||||
/**
|
||||
* The default block renderer.
|
||||
*
|
||||
* @param {Object} props
|
||||
* @return {Element}
|
||||
*/
|
||||
|
||||
function DEFAULT_BLOCK(props) {
|
||||
return (
|
||||
<div {...props.attributes} style={{ position: 'relative' }}>
|
||||
{props.children}
|
||||
{placeholder
|
||||
? <Placeholder
|
||||
className={placeholderClassName}
|
||||
node={props.node}
|
||||
parent={props.state.document}
|
||||
state={props.state}
|
||||
style={placeholderStyle}
|
||||
>
|
||||
{placeholder}
|
||||
</Placeholder>
|
||||
: null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* The default inline renderer.
|
||||
*
|
||||
* @param {Object} props
|
||||
* @return {Element}
|
||||
*/
|
||||
|
||||
function DEFAULT_INLINE(props) {
|
||||
return (
|
||||
<span {...props.attributes} style={{ position: 'relative' }}>
|
||||
{props.children}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* The default schema.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
|
||||
const DEFAULT_SCHEMA = {
|
||||
rules: [
|
||||
{
|
||||
match: {
|
||||
kind: 'document'
|
||||
},
|
||||
validate: {
|
||||
anyOf: [
|
||||
{ kind: 'block' }
|
||||
]
|
||||
},
|
||||
transform: (transform, match, reason) => {
|
||||
return reason.value.reduce((tr, node) => {
|
||||
return tr.removeNodeByKey(node.key)
|
||||
}, transform)
|
||||
}
|
||||
},
|
||||
{
|
||||
match: {
|
||||
kind: 'block'
|
||||
},
|
||||
validate: {
|
||||
anyOf: [
|
||||
{ kind: 'block' },
|
||||
{ kind: 'inline' },
|
||||
{ kind: 'text' },
|
||||
]
|
||||
},
|
||||
transform: (transform, match, reason) => {
|
||||
return reason.value.reduce((tr, node) => {
|
||||
return tr.removeNodeByKey(node.key)
|
||||
}, transform)
|
||||
},
|
||||
component: DEFAULT_BLOCK
|
||||
},
|
||||
{
|
||||
match: {
|
||||
kind: 'inline'
|
||||
},
|
||||
validate: {
|
||||
anyOf: [
|
||||
{ kind: 'inline' },
|
||||
{ kind: 'text' },
|
||||
]
|
||||
},
|
||||
transform: (transform, match, reason) => {
|
||||
return reason.value.reduce((tr, node) => {
|
||||
return tr.removeNodeByKey(node.key)
|
||||
}, transform)
|
||||
},
|
||||
component: DEFAULT_INLINE
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* On before change, enforce the editor's schema.
|
||||
*
|
||||
@@ -672,17 +570,128 @@ function Plugin(options = {}) {
|
||||
}
|
||||
|
||||
/**
|
||||
* The core `node` renderer, which uses plain `<div>` or `<span>` depending on
|
||||
* what kind of node it is.
|
||||
* A default schema rule to render block nodes.
|
||||
*
|
||||
* @param {Node} node
|
||||
* @return {Component} component
|
||||
* @type {Object}
|
||||
*/
|
||||
|
||||
function renderNode(node) {
|
||||
return node.kind == 'block'
|
||||
? DEFAULT_BLOCK
|
||||
: DEFAULT_INLINE
|
||||
const BLOCK_RENDER_RULE = {
|
||||
match: (node) => {
|
||||
return node.kind == 'block'
|
||||
},
|
||||
render: (props) => {
|
||||
return (
|
||||
<div {...props.attributes} style={{ position: 'relative' }}>
|
||||
{props.children}
|
||||
{placeholder
|
||||
? <Placeholder
|
||||
className={placeholderClassName}
|
||||
node={props.node}
|
||||
parent={props.state.document}
|
||||
state={props.state}
|
||||
style={placeholderStyle}
|
||||
>
|
||||
{placeholder}
|
||||
</Placeholder>
|
||||
: null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A default schema rule to render inline nodes.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
|
||||
const INLINE_RENDER_RULE = {
|
||||
match: (node) => {
|
||||
return node.kind == 'inline'
|
||||
},
|
||||
render: (props) => {
|
||||
return (
|
||||
<span {...props.attributes} style={{ position: 'relative' }}>
|
||||
{props.children}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A default schema rule to only allow block nodes in documents.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
|
||||
const DOCUMENT_CHILDREN_RULE = {
|
||||
match: (node) => {
|
||||
return node.kind == 'document'
|
||||
},
|
||||
validate: (document) => {
|
||||
const { nodes } = document
|
||||
const invalids = nodes.filter(n => n.kind != 'block')
|
||||
return invalids.size ? invalids : null
|
||||
},
|
||||
transform: (transform, document, invalids) => {
|
||||
return invalids.reduce((t, n) => t.removeNodeByKey(n.key), transform)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A default schema rule to only allow block, inline and text nodes in blocks.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
|
||||
const BLOCK_CHILDREN_RULE = {
|
||||
match: (node) => {
|
||||
return node.kind == 'block'
|
||||
},
|
||||
validate: (block) => {
|
||||
const { nodes } = block
|
||||
const invalids = nodes.filter(n => n.kind != 'block' && n.kind != 'inline' && n.kind != 'text')
|
||||
return invalids.size ? invalids : null
|
||||
},
|
||||
transform: (transform, block, invalids) => {
|
||||
return invalids.reduce((t, n) => t.removeNodeByKey(n.key), transform)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A default schema rule to only allow inline and text nodes in inlines.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
|
||||
const INLINE_CHILDREN_RULE = {
|
||||
match: (object) => {
|
||||
return object.kind == 'inline'
|
||||
},
|
||||
validate: (inline) => {
|
||||
const { nodes } = inline
|
||||
const invalids = nodes.filter(n => n.kind != 'inline' && n.kind != 'text')
|
||||
return invalids.size ? invalids : null
|
||||
},
|
||||
transform: (transform, inline, invalids) => {
|
||||
return invalids.reduce((t, n) => t.removeNodeByKey(n.key), transform)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The default schema.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
|
||||
const schema = {
|
||||
rules: [
|
||||
BLOCK_RENDER_RULE,
|
||||
INLINE_RENDER_RULE,
|
||||
DOCUMENT_CHILDREN_RULE,
|
||||
BLOCK_CHILDREN_RULE,
|
||||
INLINE_CHILDREN_RULE,
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -699,8 +708,7 @@ function Plugin(options = {}) {
|
||||
onKeyDown,
|
||||
onPaste,
|
||||
onSelect,
|
||||
renderNode,
|
||||
schema: DEFAULT_SCHEMA
|
||||
schema,
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -5,7 +5,7 @@ const BOLD = {
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
|
||||
function decorator(text) {
|
||||
function decorate(text, block) {
|
||||
let { characters } = text
|
||||
let second = characters.get(1)
|
||||
let mark = Mark.create({ type: 'bold' })
|
||||
@@ -18,7 +18,7 @@ function decorator(text) {
|
||||
export const schema = {
|
||||
nodes: {
|
||||
default: {
|
||||
decorator
|
||||
decorate
|
||||
}
|
||||
},
|
||||
marks: {
|
||||
|
Reference in New Issue
Block a user