mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-01-19 06:18:16 +01:00
change schema plugin to be returned from function (#3252)
* change schema plugin to be returned from function * fix forced-layout example
This commit is contained in:
parent
be1ce2e099
commit
90d40fd764
150
packages/slate-schema/src/define-schema.ts
Normal file
150
packages/slate-schema/src/define-schema.ts
Normal file
@ -0,0 +1,150 @@
|
||||
import { Editor, Text, NodeEntry } from 'slate'
|
||||
|
||||
import { NodeRule, SchemaRule } from './rules'
|
||||
import { NodeError } from './errors'
|
||||
import { checkNode, checkAncestor } from './checkers'
|
||||
|
||||
/**
|
||||
* The schema plugin augments an editor to ensure that its content is normalized
|
||||
* to always obey a schema after operations are applied.
|
||||
*/
|
||||
|
||||
export const defineSchema = (
|
||||
rules: SchemaRule[] = []
|
||||
): ((editor: Editor) => Editor) => {
|
||||
const nodeRules: NodeRule[] = rules
|
||||
const parentRules = rules.filter(rule => {
|
||||
return (
|
||||
'parent' in rule.validate ||
|
||||
'next' in rule.validate ||
|
||||
'previous' in rule.validate
|
||||
)
|
||||
})
|
||||
|
||||
return (editor: Editor): Editor => {
|
||||
const { normalizeNode } = editor
|
||||
|
||||
editor.normalizeNode = (entry: NodeEntry) => {
|
||||
const [n, p] = entry
|
||||
let error: NodeError | undefined
|
||||
let rule: NodeRule | undefined
|
||||
|
||||
for (const r of nodeRules) {
|
||||
error = checkNode(editor, [n, p], r, nodeRules)
|
||||
|
||||
if (error) {
|
||||
rule = r
|
||||
break
|
||||
}
|
||||
|
||||
if (!Text.isText(n)) {
|
||||
const failure = checkAncestor(editor, [n, p], r, parentRules)
|
||||
|
||||
if (failure) {
|
||||
rule = failure[0]
|
||||
error = failure[1]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (error == null) {
|
||||
return normalizeNode(entry)
|
||||
}
|
||||
|
||||
const prevLength = editor.operations.length
|
||||
|
||||
// First run the user-provided `normalize` function if one exists...
|
||||
if (rule != null && rule.normalize) {
|
||||
rule.normalize(editor, error)
|
||||
}
|
||||
|
||||
// If the `normalize` function did add any operations to the editor,
|
||||
// we assume that it fully handled the normalization and exit.
|
||||
if (editor.operations.length > prevLength) {
|
||||
return
|
||||
}
|
||||
|
||||
switch (error.code) {
|
||||
case 'first_child_invalid':
|
||||
case 'last_child_invalid': {
|
||||
const { path } = error
|
||||
const [parent, parentPath] = Editor.parent(editor, path)
|
||||
|
||||
if (parent.children.length > 1) {
|
||||
Editor.removeNodes(editor, { at: path })
|
||||
} else if (parentPath.length === 0) {
|
||||
const range = Editor.range(editor, parentPath)
|
||||
Editor.removeNodes(editor, {
|
||||
at: range,
|
||||
match: ([, p]) => p.length === 1,
|
||||
})
|
||||
} else {
|
||||
Editor.removeNodes(editor, { at: parentPath })
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
case 'child_max_invalid': {
|
||||
const { path } = error
|
||||
const [parent, parentPath] = Editor.parent(editor, path)
|
||||
|
||||
if (parent.children.length === 1 && parentPath.length !== 0) {
|
||||
Editor.removeNodes(editor, { at: parentPath })
|
||||
} else {
|
||||
Editor.removeNodes(editor, { at: path })
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
case 'child_min_invalid': {
|
||||
const { path } = error
|
||||
const [, parentPath] = Editor.parent(editor, path)
|
||||
|
||||
if (parentPath.length === 0) {
|
||||
const range = Editor.range(editor, parentPath)
|
||||
Editor.removeNodes(editor, {
|
||||
at: range,
|
||||
match: ([, p]) => p.length === 1,
|
||||
})
|
||||
} else {
|
||||
Editor.removeNodes(editor, { at: parentPath })
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
case 'child_invalid':
|
||||
case 'next_sibling_invalid':
|
||||
case 'node_leaf_invalid':
|
||||
case 'node_property_invalid':
|
||||
case 'node_text_invalid':
|
||||
case 'previous_sibling_invalid': {
|
||||
const { path } = error
|
||||
Editor.removeNodes(editor, { at: path })
|
||||
break
|
||||
}
|
||||
|
||||
case 'parent_invalid': {
|
||||
const { path, index } = error
|
||||
const childPath = path.concat(index)
|
||||
Editor.removeNodes(editor, { at: childPath })
|
||||
break
|
||||
}
|
||||
|
||||
default: {
|
||||
const _: never = error
|
||||
throw new Error(
|
||||
`Cannot normalize unknown validation error: "${JSON.stringify(
|
||||
error
|
||||
)}"`
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return editor
|
||||
}
|
||||
}
|
@ -1,3 +1,3 @@
|
||||
export * from './errors'
|
||||
export * from './with-schema'
|
||||
export * from './define-schema'
|
||||
export * from './rules'
|
||||
|
@ -1,152 +0,0 @@
|
||||
import { Editor, Text, NodeEntry } from 'slate'
|
||||
|
||||
import { NodeRule, SchemaRule } from './rules'
|
||||
import { NodeError } from './errors'
|
||||
import { checkNode, checkAncestor } from './checkers'
|
||||
|
||||
/**
|
||||
* The `withSchema` plugin augments an editor to ensure that its content is
|
||||
* normalized to always obey a schema after operations are applied.
|
||||
*/
|
||||
|
||||
export const withSchema = (
|
||||
editor: Editor,
|
||||
rules: SchemaRule[] = []
|
||||
): Editor => {
|
||||
const { normalizeNode } = editor
|
||||
const nodeRules: NodeRule[] = rules
|
||||
const parentRules: NodeRule[] = []
|
||||
|
||||
for (const rule of rules) {
|
||||
if (
|
||||
'parent' in rule.validate ||
|
||||
'next' in rule.validate ||
|
||||
'previous' in rule.validate
|
||||
) {
|
||||
parentRules.push(rule)
|
||||
}
|
||||
}
|
||||
|
||||
editor.normalizeNode = (entry: NodeEntry) => {
|
||||
const [n, p] = entry
|
||||
let error: NodeError | undefined
|
||||
let rule: NodeRule | undefined
|
||||
|
||||
for (const r of nodeRules) {
|
||||
error = checkNode(editor, [n, p], r, nodeRules)
|
||||
|
||||
if (error) {
|
||||
rule = r
|
||||
break
|
||||
}
|
||||
|
||||
if (!Text.isText(n)) {
|
||||
const failure = checkAncestor(editor, [n, p], r, parentRules)
|
||||
|
||||
if (failure) {
|
||||
rule = failure[0]
|
||||
error = failure[1]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (error == null) {
|
||||
return normalizeNode(entry)
|
||||
}
|
||||
|
||||
const prevLength = editor.operations.length
|
||||
|
||||
// First run the user-provided `normalize` function if one exists...
|
||||
if (rule != null && rule.normalize) {
|
||||
rule.normalize(editor, error)
|
||||
}
|
||||
|
||||
// If the `normalize` function did add any operations to the editor,
|
||||
// we assume that it fully handled the normalization and exit.
|
||||
if (editor.operations.length > prevLength) {
|
||||
return
|
||||
}
|
||||
|
||||
switch (error.code) {
|
||||
case 'first_child_invalid':
|
||||
case 'last_child_invalid': {
|
||||
const { path } = error
|
||||
const [parent, parentPath] = Editor.parent(editor, path)
|
||||
|
||||
if (parent.children.length > 1) {
|
||||
Editor.removeNodes(editor, { at: path })
|
||||
} else if (parentPath.length === 0) {
|
||||
const range = Editor.range(editor, parentPath)
|
||||
Editor.removeNodes(editor, {
|
||||
at: range,
|
||||
match: ([, p]) => p.length === 1,
|
||||
})
|
||||
} else {
|
||||
Editor.removeNodes(editor, { at: parentPath })
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
case 'child_max_invalid': {
|
||||
const { path } = error
|
||||
const [parent, parentPath] = Editor.parent(editor, path)
|
||||
|
||||
if (parent.children.length === 1 && parentPath.length !== 0) {
|
||||
Editor.removeNodes(editor, { at: parentPath })
|
||||
} else {
|
||||
Editor.removeNodes(editor, { at: path })
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
case 'child_min_invalid': {
|
||||
const { path } = error
|
||||
const [, parentPath] = Editor.parent(editor, path)
|
||||
|
||||
if (parentPath.length === 0) {
|
||||
const range = Editor.range(editor, parentPath)
|
||||
Editor.removeNodes(editor, {
|
||||
at: range,
|
||||
match: ([, p]) => p.length === 1,
|
||||
})
|
||||
} else {
|
||||
Editor.removeNodes(editor, { at: parentPath })
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
case 'child_invalid':
|
||||
case 'next_sibling_invalid':
|
||||
case 'node_leaf_invalid':
|
||||
case 'node_property_invalid':
|
||||
case 'node_text_invalid':
|
||||
case 'previous_sibling_invalid': {
|
||||
const { path } = error
|
||||
Editor.removeNodes(editor, { at: path })
|
||||
break
|
||||
}
|
||||
|
||||
case 'parent_invalid': {
|
||||
const { path, index } = error
|
||||
const childPath = path.concat(index)
|
||||
Editor.removeNodes(editor, { at: childPath })
|
||||
break
|
||||
}
|
||||
|
||||
default: {
|
||||
const _: never = error
|
||||
throw new Error(
|
||||
`Cannot normalize unknown validation error: "${JSON.stringify(
|
||||
error
|
||||
)}"`
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return editor
|
||||
}
|
@ -1,12 +1,13 @@
|
||||
import assert from 'assert'
|
||||
import { fixtures } from '../../../support/fixtures'
|
||||
import { Editor } from 'slate'
|
||||
import { withSchema } from '..'
|
||||
import { defineSchema } from '..'
|
||||
|
||||
describe('slate-schema', () => {
|
||||
fixtures(__dirname, 'validations', ({ module }) => {
|
||||
const { input, schema, output } = module
|
||||
const editor = withSchema(input, schema)
|
||||
const withSchema = defineSchema(schema)
|
||||
const editor = withSchema(input)
|
||||
Editor.normalize(editor, { force: true })
|
||||
assert.deepEqual(editor.children, output.children)
|
||||
})
|
||||
|
@ -2,9 +2,9 @@ import React, { useState, useCallback, useMemo } from 'react'
|
||||
import { Slate, Editable, withReact } from 'slate-react'
|
||||
import { Editor, createEditor } from 'slate'
|
||||
import { withHistory } from 'slate-history'
|
||||
import { withSchema } from 'slate-schema'
|
||||
import { defineSchema } from 'slate-schema'
|
||||
|
||||
const schema = [
|
||||
const withSchema = defineSchema([
|
||||
{
|
||||
for: 'node',
|
||||
match: 'editor',
|
||||
@ -36,14 +36,14 @@ const schema = [
|
||||
}
|
||||
},
|
||||
},
|
||||
]
|
||||
])
|
||||
|
||||
const ForcedLayoutExample = () => {
|
||||
const [value, setValue] = useState(initialValue)
|
||||
const [selection, setSelection] = useState(null)
|
||||
const renderElement = useCallback(props => <Element {...props} />, [])
|
||||
const editor = useMemo(
|
||||
() => withSchema(withHistory(withReact(createEditor())), schema),
|
||||
() => withSchema(withHistory(withReact(createEditor()))),
|
||||
[]
|
||||
)
|
||||
return (
|
||||
|
Loading…
x
Reference in New Issue
Block a user