mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-08-18 13:11:17 +02:00
remove rendering from schema & make it expressive (#1262)
* split rendering out of schema * remove default components * first stab at new schema * make default normalizations smarter * revert to forcing defaults to be verbose? * refactor reason constants * split nodes into blocks/inlines * get tests passing * restructure schema tests * add parent test * cleanup * remove defaults from schema * refactor schema rule.nodes validation, update example * embed schema in state objects * fixes * update examples, and fixes * update walkthroughs * update docs * remove old schemas doc page * add more tests * update benchmarks
This commit is contained in:
@@ -435,7 +435,8 @@ class Content extends React.Component {
|
||||
renderNode = (child, isSelected) => {
|
||||
const { editor, readOnly, schema, state } = this.props
|
||||
const { document, decorations } = state
|
||||
let decs = document.getDecorations(schema)
|
||||
const stack = editor.getStack()
|
||||
let decs = document.getDecorations(stack)
|
||||
if (decorations) decs = decorations.concat(decs)
|
||||
return (
|
||||
<Node
|
||||
|
@@ -1,51 +0,0 @@
|
||||
|
||||
import React from 'react'
|
||||
import SlateTypes from 'slate-prop-types'
|
||||
import Types from 'prop-types'
|
||||
|
||||
/**
|
||||
* Default node.
|
||||
*
|
||||
* @type {Component}
|
||||
*/
|
||||
|
||||
class DefaultNode extends React.Component {
|
||||
|
||||
/**
|
||||
* Prop types.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
|
||||
static propTypes = {
|
||||
attributes: Types.object.isRequired,
|
||||
editor: Types.object.isRequired,
|
||||
isSelected: Types.bool.isRequired,
|
||||
node: SlateTypes.node.isRequired,
|
||||
parent: SlateTypes.node.isRequired,
|
||||
readOnly: Types.bool.isRequired,
|
||||
state: SlateTypes.state.isRequired,
|
||||
}
|
||||
|
||||
/**
|
||||
* Render.
|
||||
*
|
||||
* @return {Element}
|
||||
*/
|
||||
|
||||
render() {
|
||||
const { attributes, children, node } = this.props
|
||||
const Tag = node.kind == 'block' ? 'div' : 'span'
|
||||
const style = { position: 'relative' }
|
||||
return <Tag {...attributes} style={style}>{children}</Tag>
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Export.
|
||||
*
|
||||
* @type {Component}
|
||||
*/
|
||||
|
||||
export default DefaultNode
|
@@ -1,64 +0,0 @@
|
||||
|
||||
import React from 'react'
|
||||
import SlateTypes from 'slate-prop-types'
|
||||
import Types from 'prop-types'
|
||||
|
||||
/**
|
||||
* Default placeholder.
|
||||
*
|
||||
* @type {Component}
|
||||
*/
|
||||
|
||||
class DefaultPlaceholder extends React.Component {
|
||||
|
||||
/**
|
||||
* Property types.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
|
||||
static propTypes = {
|
||||
editor: Types.object.isRequired,
|
||||
isSelected: Types.bool.isRequired,
|
||||
node: SlateTypes.node.isRequired,
|
||||
parent: SlateTypes.node.isRequired,
|
||||
readOnly: Types.bool.isRequired,
|
||||
state: SlateTypes.state.isRequired,
|
||||
}
|
||||
|
||||
/**
|
||||
* Render.
|
||||
*
|
||||
* @return {Element}
|
||||
*/
|
||||
|
||||
render() {
|
||||
const { editor, state } = this.props
|
||||
if (!editor.props.placeholder) return null
|
||||
if (state.document.getBlocks().size > 1) return null
|
||||
|
||||
const style = {
|
||||
pointerEvents: 'none',
|
||||
display: 'inline-block',
|
||||
width: '0',
|
||||
maxWidth: '100%',
|
||||
whiteSpace: 'nowrap',
|
||||
opacity: '0.333',
|
||||
}
|
||||
|
||||
return (
|
||||
<span contentEditable={false} style={style}>
|
||||
{editor.props.placeholder}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Export.
|
||||
*
|
||||
* @type {Component}
|
||||
*/
|
||||
|
||||
export default DefaultPlaceholder
|
@@ -5,7 +5,7 @@ import React from 'react'
|
||||
import SlateTypes from 'slate-prop-types'
|
||||
import Types from 'prop-types'
|
||||
import logger from 'slate-dev-logger'
|
||||
import { Stack, State } from 'slate'
|
||||
import { Schema, Stack, State } from 'slate'
|
||||
|
||||
import EVENT_HANDLERS from '../constants/event-handlers'
|
||||
import AfterPlugin from '../plugins/after'
|
||||
@@ -53,7 +53,6 @@ class Editor extends React.Component {
|
||||
autoCorrect: Types.bool,
|
||||
autoFocus: Types.bool,
|
||||
className: Types.string,
|
||||
onBeforeChange: Types.func,
|
||||
onChange: Types.func,
|
||||
placeholder: Types.any,
|
||||
placeholderClassName: Types.string,
|
||||
@@ -85,7 +84,7 @@ class Editor extends React.Component {
|
||||
}
|
||||
|
||||
/**
|
||||
* When constructed, create a new `Stack` and run `onBeforeChange`.
|
||||
* Constructor.
|
||||
*
|
||||
* @param {Object} props
|
||||
*/
|
||||
@@ -99,12 +98,14 @@ class Editor extends React.Component {
|
||||
// special significance on the editor itself.
|
||||
const plugins = resolvePlugins(props)
|
||||
const stack = Stack.create({ plugins })
|
||||
const schema = Schema.create({ plugins })
|
||||
this.state.schema = schema
|
||||
this.state.stack = stack
|
||||
|
||||
// Run `onBeforeChange` on the passed-in state because we need to ensure
|
||||
// that it is normalized, and queue the resulting change.
|
||||
// Run `onChange` on the passed-in state because we need to ensure that it
|
||||
// is normalized, and queue the resulting change.
|
||||
const change = props.state.change()
|
||||
stack.handle('onBeforeChange', change, this)
|
||||
stack.run('onChange', change, this)
|
||||
const { state } = change
|
||||
this.queueChange(change)
|
||||
this.cacheState(state)
|
||||
@@ -128,28 +129,37 @@ class Editor extends React.Component {
|
||||
|
||||
/**
|
||||
* When the `props` are updated, create a new `Stack` if necessary and run
|
||||
* `onBeforeChange` to ensure the state is normalized.
|
||||
* `onChange` to ensure the state is normalized.
|
||||
*
|
||||
* @param {Object} props
|
||||
*/
|
||||
|
||||
componentWillReceiveProps = (props) => {
|
||||
let { stack } = this.state
|
||||
let { state } = props
|
||||
let { schema, stack } = this.state
|
||||
let isNew = false
|
||||
|
||||
// If any plugin-related properties will change, create a new `Stack`.
|
||||
// Check to see if any plugin-related properœties have changed.
|
||||
for (let i = 0; i < PLUGINS_PROPS.length; i++) {
|
||||
const prop = PLUGINS_PROPS[i]
|
||||
if (props[prop] == this.props[prop]) continue
|
||||
const plugins = resolvePlugins(props)
|
||||
stack = Stack.create({ plugins })
|
||||
this.setState({ stack })
|
||||
isNew = true
|
||||
break
|
||||
}
|
||||
|
||||
// Run `onBeforeChange` on the passed-in state because we need to ensure
|
||||
// that it is normalized, and queue the resulting change.
|
||||
const change = props.state.change()
|
||||
stack.handle('onBeforeChange', change, this)
|
||||
const { state } = change
|
||||
// If any plugin-related properties will change, create a new `Stack`.
|
||||
if (isNew) {
|
||||
const plugins = resolvePlugins(props)
|
||||
stack = Stack.create({ plugins })
|
||||
schema = Schema.create({ plugins })
|
||||
this.setState({ schema, stack })
|
||||
}
|
||||
|
||||
// Run `onChange` on the passed-in state because we need to ensure that it
|
||||
// is normalized, and queue the resulting change.
|
||||
const change = state.change()
|
||||
stack.run('onChange', change, this)
|
||||
state = change.state
|
||||
this.queueChange(change)
|
||||
this.cacheState(state)
|
||||
this.setState({ state })
|
||||
@@ -208,8 +218,10 @@ class Editor extends React.Component {
|
||||
|
||||
if (change) {
|
||||
debug('flushChange', { change })
|
||||
this.props.onChange(change)
|
||||
delete this.tmp.change
|
||||
window.requestAnimationFrame(() => {
|
||||
delete this.tmp.change
|
||||
this.props.onChange(change)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,7 +248,17 @@ class Editor extends React.Component {
|
||||
*/
|
||||
|
||||
getSchema = () => {
|
||||
return this.state.stack.schema
|
||||
return this.state.schema
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the editor's current stack.
|
||||
*
|
||||
* @return {Stack}
|
||||
*/
|
||||
|
||||
getStack = () => {
|
||||
return this.state.stack
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -258,7 +280,6 @@ class Editor extends React.Component {
|
||||
change = (...args) => {
|
||||
const { state } = this.state
|
||||
const change = state.change().call(...args)
|
||||
debug('change', { change })
|
||||
this.onChange(change)
|
||||
}
|
||||
|
||||
@@ -266,13 +287,13 @@ class Editor extends React.Component {
|
||||
* On event.
|
||||
*
|
||||
* @param {String} handler
|
||||
* @param {Mixed} ...args
|
||||
* @param {Event} event
|
||||
*/
|
||||
|
||||
onEvent = (handler, ...args) => {
|
||||
onEvent = (handler, event) => {
|
||||
const { stack, state } = this.state
|
||||
const change = state.change()
|
||||
stack.handle(handler, change, this, ...args)
|
||||
stack.run(handler, event, change, this)
|
||||
this.onChange(change)
|
||||
}
|
||||
|
||||
@@ -283,22 +304,19 @@ class Editor extends React.Component {
|
||||
*/
|
||||
|
||||
onChange = (change) => {
|
||||
debug('onChange', { change })
|
||||
|
||||
if (State.isState(change)) {
|
||||
throw new Error('As of slate@0.22.0 the `editor.onChange` method must be passed a `Change` object not a `State` object.')
|
||||
}
|
||||
|
||||
const { stack } = this.state
|
||||
|
||||
stack.handle('onBeforeChange', change, this)
|
||||
stack.handle('onChange', change, this)
|
||||
|
||||
stack.run('onChange', change, this)
|
||||
const { state } = change
|
||||
const { document, selection } = this.tmp
|
||||
const { onChange, onDocumentChange, onSelectionChange } = this.props
|
||||
|
||||
if (state == this.state.state) return
|
||||
|
||||
debug('onChange', { change })
|
||||
onChange(change)
|
||||
if (onDocumentChange && state.document != document) onDocumentChange(state.document, change)
|
||||
if (onSelectionChange && state.selection != selection) onSelectionChange(state.selection, change)
|
||||
@@ -311,19 +329,15 @@ class Editor extends React.Component {
|
||||
*/
|
||||
|
||||
render() {
|
||||
const { props, state } = this
|
||||
const { stack } = state
|
||||
debug('render', this)
|
||||
|
||||
const { stack, state } = this.state
|
||||
const children = stack
|
||||
.renderPortal(state.state, this)
|
||||
.map('renderPortal', state, this)
|
||||
.map((child, i) => <Portal key={i} isOpened>{child}</Portal>)
|
||||
|
||||
debug('render', { props, state })
|
||||
|
||||
const tree = stack.render(state.state, this, {
|
||||
...props,
|
||||
children,
|
||||
})
|
||||
|
||||
const props = { ...this.props, children }
|
||||
const tree = stack.render('renderEditor', props, state, this)
|
||||
return tree
|
||||
}
|
||||
|
||||
|
@@ -84,18 +84,17 @@ class Leaf extends React.Component {
|
||||
*/
|
||||
|
||||
render() {
|
||||
const { props } = this
|
||||
const { node, index } = props
|
||||
this.debug('render', this)
|
||||
|
||||
const { node, index } = this.props
|
||||
const offsetKey = OffsetKey.stringify({
|
||||
key: node.key,
|
||||
index
|
||||
})
|
||||
|
||||
this.debug('render', { props })
|
||||
|
||||
return (
|
||||
<span data-offset-key={offsetKey}>
|
||||
{this.renderMarks(props)}
|
||||
{this.renderMarks()}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
@@ -103,43 +102,29 @@ class Leaf extends React.Component {
|
||||
/**
|
||||
* Render all of the leaf's mark components.
|
||||
*
|
||||
* @param {Object} props
|
||||
* @return {Element}
|
||||
*/
|
||||
|
||||
renderMarks(props) {
|
||||
const { marks, schema, node, offset, text, state, editor } = props
|
||||
const children = this.renderText(props)
|
||||
renderMarks() {
|
||||
const { marks, schema, node, offset, text, state, editor } = this.props
|
||||
const stack = editor.getStack()
|
||||
const leaf = this.renderText()
|
||||
|
||||
return marks.reduce((memo, mark) => {
|
||||
const Component = mark.getComponent(schema)
|
||||
if (!Component) return memo
|
||||
return (
|
||||
<Component
|
||||
editor={editor}
|
||||
mark={mark}
|
||||
marks={marks}
|
||||
node={node}
|
||||
offset={offset}
|
||||
schema={schema}
|
||||
state={state}
|
||||
text={text}
|
||||
>
|
||||
{memo}
|
||||
</Component>
|
||||
)
|
||||
}, children)
|
||||
return marks.reduce((children, mark) => {
|
||||
const props = { editor, mark, marks, node, offset, schema, state, text, children }
|
||||
const element = stack.find('renderMark', props)
|
||||
return element || children
|
||||
}, leaf)
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the text content of the leaf, accounting for browsers.
|
||||
*
|
||||
* @param {Object} props
|
||||
* @return {Element}
|
||||
*/
|
||||
|
||||
renderText(props) {
|
||||
const { block, node, parent, text, index, leaves } = props
|
||||
renderText() {
|
||||
const { block, node, parent, text, index, leaves } = this.props
|
||||
|
||||
// COMPAT: If the text is empty and it's the only child, we need to render a
|
||||
// <br/> to get the block to have the proper height.
|
||||
|
@@ -43,20 +43,6 @@ class Node extends React.Component {
|
||||
state: SlateTypes.state.isRequired,
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param {Object} props
|
||||
*/
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
const { node, schema } = props
|
||||
this.state = {}
|
||||
this.state.Component = node.getComponent(schema)
|
||||
this.state.Placeholder = node.getPlaceholder(schema)
|
||||
}
|
||||
|
||||
/**
|
||||
* Debug.
|
||||
*
|
||||
@@ -70,19 +56,6 @@ class Node extends React.Component {
|
||||
debug(message, `${key} (${type})`, ...args)
|
||||
}
|
||||
|
||||
/**
|
||||
* On receiving new props, update the `Component` renderer.
|
||||
*
|
||||
* @param {Object} props
|
||||
*/
|
||||
|
||||
componentWillReceiveProps = (props) => {
|
||||
if (props.node == this.props.node) return
|
||||
const Component = props.node.getComponent(props.schema)
|
||||
const Placeholder = props.node.getPlaceholder(props.schema)
|
||||
this.setState({ Component, Placeholder })
|
||||
}
|
||||
|
||||
/**
|
||||
* Should the node update?
|
||||
*
|
||||
@@ -93,24 +66,15 @@ class Node extends React.Component {
|
||||
|
||||
shouldComponentUpdate = (nextProps) => {
|
||||
const { props } = this
|
||||
const { Component } = this.state
|
||||
const stack = props.editor.getStack()
|
||||
const shouldUpdate = stack.find('shouldNodeComponentUpdate', props, nextProps)
|
||||
const n = nextProps
|
||||
const p = props
|
||||
|
||||
// If the `Component` has enabled suppression of update checking, always
|
||||
// return true so that it can deal with update checking itself.
|
||||
if (Component && Component.suppressShouldComponentUpdate) {
|
||||
logger.deprecate('2.2.0', 'The `suppressShouldComponentUpdate` property is deprecated because it led to an important performance loss, use `shouldNodeComponentUpdate` instead.')
|
||||
return true
|
||||
}
|
||||
|
||||
// If the `Component` has a custom logic to determine whether the component
|
||||
// needs to be updated or not, return true if it returns true.
|
||||
// If it returns false, we still want to benefit from the
|
||||
// performance gain of the rest of the logic.
|
||||
if (Component && Component.shouldNodeComponentUpdate) {
|
||||
const shouldUpdate = Component.shouldNodeComponentUpdate(p, n)
|
||||
|
||||
// needs to be updated or not, return true if it returns true. If it returns
|
||||
// false, we need to ignore it, because it shouldn't be allowed it.
|
||||
if (shouldUpdate != null) {
|
||||
if (shouldUpdate) {
|
||||
return true
|
||||
}
|
||||
@@ -151,15 +115,13 @@ class Node extends React.Component {
|
||||
*/
|
||||
|
||||
render() {
|
||||
const { props } = this
|
||||
this.debug('render', this)
|
||||
|
||||
this.debug('render', { props })
|
||||
|
||||
const { editor, isSelected, node, parent, readOnly, state } = props
|
||||
const { Component, Placeholder } = this.state
|
||||
const { editor, isSelected, node, parent, readOnly, state } = this.props
|
||||
const { selection } = state
|
||||
const stack = editor.getStack()
|
||||
const indexes = node.getSelectionIndexes(selection, isSelected)
|
||||
const children = node.nodes.toArray().map((child, i) => {
|
||||
let children = node.nodes.toArray().map((child, i) => {
|
||||
const isChildSelected = !!indexes && indexes.start <= i && i < indexes.end
|
||||
return this.renderNode(child, isChildSelected)
|
||||
})
|
||||
@@ -175,22 +137,24 @@ class Node extends React.Component {
|
||||
if (direction == 'rtl') attributes.dir = 'rtl'
|
||||
}
|
||||
|
||||
const p = {
|
||||
const props = {
|
||||
key: node.key,
|
||||
editor,
|
||||
isSelected,
|
||||
key: node.key,
|
||||
node,
|
||||
parent,
|
||||
readOnly,
|
||||
state
|
||||
}
|
||||
|
||||
const element = (
|
||||
<Component {...p} attributes={attributes}>
|
||||
{Placeholder && <Placeholder {...p} />}
|
||||
{children}
|
||||
</Component>
|
||||
)
|
||||
let placeholder = stack.find('renderPlaceholder', props)
|
||||
|
||||
if (placeholder) {
|
||||
placeholder = React.cloneElement(placeholder, { key: `${node.key}-placeholder` })
|
||||
children = [placeholder, ...children]
|
||||
}
|
||||
|
||||
const element = stack.find('renderNode', { ...props, attributes, children })
|
||||
|
||||
return node.isVoid
|
||||
? <Void {...this.props}>{element}</Void>
|
||||
@@ -207,8 +171,9 @@ class Node extends React.Component {
|
||||
|
||||
renderNode = (child, isSelected) => {
|
||||
const { block, decorations, editor, node, readOnly, schema, state } = this.props
|
||||
const Component = child.kind === 'text' ? Text : Node
|
||||
const decs = decorations.concat(node.getDecorations(schema))
|
||||
const Component = child.kind == 'text' ? Text : Node
|
||||
const stack = editor.getStack()
|
||||
const decs = decorations.concat(node.getDecorations(stack))
|
||||
return (
|
||||
<Component
|
||||
block={node.kind == 'block' ? node : block}
|
||||
|
@@ -4,13 +4,11 @@ import Debug from 'debug'
|
||||
import Plain from 'slate-plain-serializer'
|
||||
import React from 'react'
|
||||
import getWindow from 'get-window'
|
||||
import { Block, Inline, Text, coreSchema } from 'slate'
|
||||
import { Block, Inline, Text } from 'slate'
|
||||
|
||||
import EVENT_HANDLERS from '../constants/event-handlers'
|
||||
import HOTKEYS from '../constants/hotkeys'
|
||||
import Content from '../components/content'
|
||||
import DefaultNode from '../components/default-node'
|
||||
import DefaultPlaceholder from '../components/default-placeholder'
|
||||
import findDOMNode from '../utils/find-dom-node'
|
||||
import findNode from '../utils/find-node'
|
||||
import findPoint from '../utils/find-point'
|
||||
@@ -38,28 +36,6 @@ const debug = Debug('slate:core:after')
|
||||
function AfterPlugin(options = {}) {
|
||||
let isDraggingInternally = null
|
||||
|
||||
/**
|
||||
* On before change, enforce the editor's schema.
|
||||
*
|
||||
* @param {Change} change
|
||||
* @param {Editor} editor
|
||||
*/
|
||||
|
||||
function onBeforeChange(change, editor) {
|
||||
const { state } = change
|
||||
const schema = editor.getSchema()
|
||||
const prevState = editor.getState()
|
||||
|
||||
// PERF: Skip normalizing if the document hasn't changed, since schemas only
|
||||
// normalize changes to the document, not selection.
|
||||
if (prevState && state.document == prevState.document) return
|
||||
|
||||
debug('onBeforeChange')
|
||||
|
||||
change.normalize(coreSchema)
|
||||
change.normalize(schema)
|
||||
}
|
||||
|
||||
/**
|
||||
* On before input, correct any browser inconsistencies.
|
||||
*
|
||||
@@ -684,7 +660,7 @@ function AfterPlugin(options = {}) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Render.
|
||||
* Render editor.
|
||||
*
|
||||
* @param {Object} props
|
||||
* @param {State} state
|
||||
@@ -692,7 +668,7 @@ function AfterPlugin(options = {}) {
|
||||
* @return {Object}
|
||||
*/
|
||||
|
||||
function render(props, state, editor) {
|
||||
function renderEditor(props, state, editor) {
|
||||
const handlers = EVENT_HANDLERS.reduce((obj, handler) => {
|
||||
obj[handler] = editor[handler]
|
||||
return obj
|
||||
@@ -719,22 +695,48 @@ function AfterPlugin(options = {}) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Add default rendering rules to the schema.
|
||||
* Render node.
|
||||
*
|
||||
* @type {Object}
|
||||
* @param {Object} props
|
||||
* @return {Element}
|
||||
*/
|
||||
|
||||
const schema = {
|
||||
rules: [
|
||||
{
|
||||
match: obj => obj.kind == 'block' || obj.kind == 'inline',
|
||||
render: DefaultNode,
|
||||
},
|
||||
{
|
||||
match: obj => obj.kind == 'block' && Text.isTextList(obj.nodes) && obj.text == '',
|
||||
placeholder: DefaultPlaceholder,
|
||||
},
|
||||
]
|
||||
function renderNode(props) {
|
||||
const { attributes, children, node } = props
|
||||
if (node.kind != 'block' && node.kind != 'inline') return
|
||||
const Tag = node.kind == 'block' ? 'div' : 'span'
|
||||
const style = { position: 'relative' }
|
||||
return <Tag {...attributes} style={style}>{children}</Tag>
|
||||
}
|
||||
|
||||
/**
|
||||
* Render placeholder.
|
||||
*
|
||||
* @param {Object} props
|
||||
* @return {Element}
|
||||
*/
|
||||
|
||||
function renderPlaceholder(props) {
|
||||
const { editor, node, state } = props
|
||||
if (node.kind != 'block') return
|
||||
if (!Text.isTextList(node.nodes)) return
|
||||
if (node.text != '') return
|
||||
if (state.document.getBlocks().size > 1) return
|
||||
|
||||
const style = {
|
||||
pointerEvents: 'none',
|
||||
display: 'inline-block',
|
||||
width: '0',
|
||||
maxWidth: '100%',
|
||||
whiteSpace: 'nowrap',
|
||||
opacity: '0.333',
|
||||
}
|
||||
|
||||
return (
|
||||
<span contentEditable={false} style={style}>
|
||||
{editor.props.placeholder}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -744,7 +746,6 @@ function AfterPlugin(options = {}) {
|
||||
*/
|
||||
|
||||
return {
|
||||
onBeforeChange,
|
||||
onBeforeInput,
|
||||
onBlur,
|
||||
onClick,
|
||||
@@ -758,8 +759,9 @@ function AfterPlugin(options = {}) {
|
||||
onKeyDown,
|
||||
onPaste,
|
||||
onSelect,
|
||||
render,
|
||||
schema,
|
||||
renderEditor,
|
||||
renderNode,
|
||||
renderPlaceholder,
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -90,6 +90,27 @@ function BeforePlugin() {
|
||||
debug('onBlur', { event })
|
||||
}
|
||||
|
||||
/**
|
||||
* On change.
|
||||
*
|
||||
* @param {Change} change
|
||||
* @param {Editor} editor
|
||||
*/
|
||||
|
||||
function onChange(change, editor) {
|
||||
const { state } = change
|
||||
const schema = editor.getSchema()
|
||||
|
||||
// If the state's schema isn't the editor's schema, update it.
|
||||
if (state.schema != schema) {
|
||||
change
|
||||
.setState({ schema })
|
||||
.normalize()
|
||||
}
|
||||
|
||||
debug('onChange')
|
||||
}
|
||||
|
||||
/**
|
||||
* On composition end.
|
||||
*
|
||||
@@ -377,6 +398,7 @@ function BeforePlugin() {
|
||||
return {
|
||||
onBeforeInput,
|
||||
onBlur,
|
||||
onChange,
|
||||
onCompositionEnd,
|
||||
onCompositionStart,
|
||||
onCopy,
|
||||
|
@@ -3,18 +3,24 @@
|
||||
import React from 'react'
|
||||
import h from '../../helpers/h'
|
||||
|
||||
export const schema = {
|
||||
nodes: {
|
||||
code: (props) => {
|
||||
return (
|
||||
React.createElement('pre', props.attributes,
|
||||
React.createElement('code', {}, props.children)
|
||||
)
|
||||
)
|
||||
}
|
||||
function Code(props) {
|
||||
return (
|
||||
React.createElement('pre', props.attributes,
|
||||
React.createElement('code', {}, props.children)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
function renderNode(props) {
|
||||
switch (props.node.type) {
|
||||
case 'code': return Code(props)
|
||||
}
|
||||
}
|
||||
|
||||
export const props = {
|
||||
renderNode,
|
||||
}
|
||||
|
||||
export const state = (
|
||||
<state>
|
||||
<document>
|
||||
|
@@ -3,16 +3,22 @@
|
||||
import React from 'react'
|
||||
import h from '../../helpers/h'
|
||||
|
||||
export const schema = {
|
||||
nodes: {
|
||||
image: (props) => {
|
||||
return (
|
||||
React.createElement('img', { src: props.node.data.get('src'), ...props.attributes })
|
||||
)
|
||||
}
|
||||
function Image(props) {
|
||||
return (
|
||||
React.createElement('img', { src: props.node.data.get('src'), ...props.attributes })
|
||||
)
|
||||
}
|
||||
|
||||
function renderNode(props) {
|
||||
switch (props.node.type) {
|
||||
case 'image': return Image(props)
|
||||
}
|
||||
}
|
||||
|
||||
export const props = {
|
||||
renderNode,
|
||||
}
|
||||
|
||||
export const state = (
|
||||
<state>
|
||||
<document>
|
||||
|
@@ -3,14 +3,18 @@
|
||||
import React from 'react'
|
||||
import h from '../../helpers/h'
|
||||
|
||||
export const schema = {
|
||||
nodes: {
|
||||
code: (props) => {
|
||||
return (
|
||||
React.createElement('pre', props.attributes,
|
||||
React.createElement('code', {}, props.children)
|
||||
)
|
||||
)
|
||||
function Code(props) {
|
||||
return (
|
||||
React.createElement('pre', props.attributes,
|
||||
React.createElement('code', {}, props.children)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
export const props = {
|
||||
renderNode(p) {
|
||||
switch (p.node.type) {
|
||||
case 'code': return Code(p)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,29 +1,34 @@
|
||||
/** @jsx h */
|
||||
|
||||
import React from 'react'
|
||||
import h from '../../helpers/h'
|
||||
|
||||
export const schema = {
|
||||
nodes: {
|
||||
paragraph: {
|
||||
decorate(block) {
|
||||
const text = block.getFirstText()
|
||||
return [{
|
||||
anchorKey: text.key,
|
||||
anchorOffset: 1,
|
||||
focusKey: text.key,
|
||||
focusOffset: 2,
|
||||
marks: [{ type: 'bold' }]
|
||||
}]
|
||||
}
|
||||
}
|
||||
},
|
||||
marks: {
|
||||
bold: {
|
||||
fontWeight: 'bold',
|
||||
}
|
||||
function decorateNode(block) {
|
||||
const text = block.getFirstText()
|
||||
return [{
|
||||
anchorKey: text.key,
|
||||
anchorOffset: 1,
|
||||
focusKey: text.key,
|
||||
focusOffset: 2,
|
||||
marks: [{ type: 'bold' }]
|
||||
}]
|
||||
}
|
||||
|
||||
function Bold(props) {
|
||||
return React.createElement('strong', null, props.children)
|
||||
}
|
||||
|
||||
function renderMark(props) {
|
||||
switch (props.mark.type) {
|
||||
case 'bold': return Bold(props)
|
||||
}
|
||||
}
|
||||
|
||||
export const props = {
|
||||
decorateNode,
|
||||
renderMark,
|
||||
}
|
||||
|
||||
export const state = (
|
||||
<state>
|
||||
<document>
|
||||
@@ -39,7 +44,7 @@ export const output = `
|
||||
<div style="position:relative">
|
||||
<span>
|
||||
<span>o</span>
|
||||
<span><span style="font-weight:bold">n</span></span>
|
||||
<span><strong>n</strong></span>
|
||||
<span>e</span>
|
||||
</span>
|
||||
</div>
|
||||
|
@@ -3,16 +3,22 @@
|
||||
import React from 'react'
|
||||
import h from '../../helpers/h'
|
||||
|
||||
export const schema = {
|
||||
nodes: {
|
||||
link: (props) => {
|
||||
return (
|
||||
React.createElement('a', { href: props.node.data.get('href'), ...props.attributes }, props.children)
|
||||
)
|
||||
}
|
||||
function Link(props) {
|
||||
return (
|
||||
React.createElement('a', { href: props.node.data.get('href'), ...props.attributes }, props.children)
|
||||
)
|
||||
}
|
||||
|
||||
function renderNode(props) {
|
||||
switch (props.node.type) {
|
||||
case 'link': return Link(props)
|
||||
}
|
||||
}
|
||||
|
||||
export const props = {
|
||||
renderNode,
|
||||
}
|
||||
|
||||
export const state = (
|
||||
<state>
|
||||
<document>
|
||||
|
@@ -3,16 +3,22 @@
|
||||
import React from 'react'
|
||||
import h from '../../helpers/h'
|
||||
|
||||
export const schema = {
|
||||
nodes: {
|
||||
emoji: (props) => {
|
||||
return (
|
||||
React.createElement('img', props.attributes)
|
||||
)
|
||||
}
|
||||
function Emoji(props) {
|
||||
return (
|
||||
React.createElement('img', props.attributes)
|
||||
)
|
||||
}
|
||||
|
||||
function renderNode(props) {
|
||||
switch (props.node.type) {
|
||||
case 'emoji': return Emoji(props)
|
||||
}
|
||||
}
|
||||
|
||||
export const props = {
|
||||
renderNode,
|
||||
}
|
||||
|
||||
export const state = (
|
||||
<state>
|
||||
<document>
|
||||
|
@@ -3,16 +3,22 @@
|
||||
import React from 'react'
|
||||
import h from '../../helpers/h'
|
||||
|
||||
export const schema = {
|
||||
nodes: {
|
||||
link: (props) => {
|
||||
return (
|
||||
React.createElement('a', { href: props.node.data.get('href'), ...props.attributes }, props.children)
|
||||
)
|
||||
}
|
||||
function Link(props) {
|
||||
return (
|
||||
React.createElement('a', { href: props.node.data.get('href'), ...props.attributes }, props.children)
|
||||
)
|
||||
}
|
||||
|
||||
function renderNode(props) {
|
||||
switch (props.node.type) {
|
||||
case 'link': return Link(props)
|
||||
}
|
||||
}
|
||||
|
||||
export const props = {
|
||||
renderNode,
|
||||
}
|
||||
|
||||
export const state = (
|
||||
<state>
|
||||
<document>
|
||||
|
@@ -1,36 +0,0 @@
|
||||
/** @jsx h */
|
||||
|
||||
import React from 'react'
|
||||
import h from '../../helpers/h'
|
||||
|
||||
export const schema = {
|
||||
marks: {
|
||||
bold: (props) => {
|
||||
return (
|
||||
React.createElement('strong', {}, props.children)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const state = (
|
||||
<state>
|
||||
<document>
|
||||
<paragraph>
|
||||
one<b>two</b>three
|
||||
</paragraph>
|
||||
</document>
|
||||
</state>
|
||||
)
|
||||
|
||||
export const output = `
|
||||
<div data-slate-editor="true" contenteditable="true" role="textbox">
|
||||
<div style="position:relative">
|
||||
<span>
|
||||
<span>one</span>
|
||||
<span><strong>two</strong></span>
|
||||
<span>three</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
`.trim()
|
@@ -1,33 +0,0 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../../helpers/h'
|
||||
|
||||
export const schema = {
|
||||
marks: {
|
||||
bold: {
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const state = (
|
||||
<state>
|
||||
<document>
|
||||
<paragraph>
|
||||
one<b>two</b>three
|
||||
</paragraph>
|
||||
</document>
|
||||
</state>
|
||||
)
|
||||
|
||||
export const output = `
|
||||
<div data-slate-editor="true" contenteditable="true" role="textbox">
|
||||
<div style="position:relative">
|
||||
<span>
|
||||
<span>one</span>
|
||||
<span><span style="font-weight:bold">two</span></span>
|
||||
<span>three</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
`.trim()
|
@@ -1,31 +0,0 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../../helpers/h'
|
||||
|
||||
export const schema = {
|
||||
marks: {
|
||||
bold: 'bold',
|
||||
}
|
||||
}
|
||||
|
||||
export const state = (
|
||||
<state>
|
||||
<document>
|
||||
<paragraph>
|
||||
one<b>two</b>three
|
||||
</paragraph>
|
||||
</document>
|
||||
</state>
|
||||
)
|
||||
|
||||
export const output = `
|
||||
<div data-slate-editor="true" contenteditable="true" role="textbox">
|
||||
<div style="position:relative">
|
||||
<span>
|
||||
<span>one</span>
|
||||
<span><span class="bold">two</span></span>
|
||||
<span>three</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
`.trim()
|
@@ -3,22 +3,20 @@
|
||||
import React from 'react'
|
||||
import h from '../../helpers/h'
|
||||
|
||||
class Bold extends React.Component {
|
||||
|
||||
render() {
|
||||
return (
|
||||
React.createElement('strong', {}, this.props.children)
|
||||
)
|
||||
}
|
||||
|
||||
function Bold(props) {
|
||||
return React.createElement('strong', null, props.children)
|
||||
}
|
||||
|
||||
export const schema = {
|
||||
marks: {
|
||||
bold: Bold,
|
||||
function renderMark(props) {
|
||||
switch (props.mark.type) {
|
||||
case 'bold': return Bold(props)
|
||||
}
|
||||
}
|
||||
|
||||
export const props = {
|
||||
renderMark,
|
||||
}
|
||||
|
||||
export const state = (
|
||||
<state>
|
||||
<document>
|
@@ -2,7 +2,7 @@
|
||||
|
||||
import h from '../../helpers/h'
|
||||
|
||||
export const schema = {}
|
||||
export const props = {}
|
||||
|
||||
export const state = (
|
||||
<state>
|
||||
|
@@ -2,7 +2,7 @@
|
||||
|
||||
import h from '../../helpers/h'
|
||||
|
||||
export const schema = {}
|
||||
export const props = {}
|
||||
|
||||
export const state = (
|
||||
<state>
|
||||
|
@@ -2,7 +2,7 @@
|
||||
|
||||
import h from '../../helpers/h'
|
||||
|
||||
export const schema = {}
|
||||
export const props = {}
|
||||
|
||||
export const state = (
|
||||
<state>
|
||||
@@ -17,6 +17,7 @@ export const state = (
|
||||
export const output = `
|
||||
<div data-slate-editor="true" contenteditable="true" role="textbox">
|
||||
<div style="position:relative">
|
||||
<span contenteditable="false" style="pointer-events:none;display:inline-block;width:0;max-width:100%;white-space:nowrap;opacity:0.333"></span>
|
||||
<span>
|
||||
<span><br></span>
|
||||
</span>
|
||||
|
@@ -2,7 +2,7 @@
|
||||
|
||||
import h from '../../helpers/h'
|
||||
|
||||
export const schema = {}
|
||||
export const props = {}
|
||||
|
||||
export const state = (
|
||||
<state>
|
||||
@@ -15,6 +15,7 @@ export const state = (
|
||||
export const output = `
|
||||
<div data-slate-editor="true" contenteditable="true" role="textbox">
|
||||
<div style="position:relative">
|
||||
<span contenteditable="false" style="pointer-events:none;display:inline-block;width:0;max-width:100%;white-space:nowrap;opacity:0.333"></span>
|
||||
<span>
|
||||
<span><br></span>
|
||||
</span>
|
||||
|
@@ -2,7 +2,7 @@
|
||||
|
||||
import h from '../../helpers/h'
|
||||
|
||||
export const schema = {}
|
||||
export const props = {}
|
||||
|
||||
export const state = (
|
||||
<state>
|
||||
|
@@ -3,18 +3,19 @@
|
||||
import React from 'react'
|
||||
import h from '../../helpers/h'
|
||||
|
||||
export const schema = {
|
||||
nodes: {
|
||||
image: (props) => {
|
||||
return (
|
||||
React.createElement('img', { src: props.node.data.get('src'), ...props.attributes })
|
||||
)
|
||||
}
|
||||
function Image(props) {
|
||||
return React.createElement('img', { src: props.node.data.get('src'), ...props.attributes })
|
||||
}
|
||||
|
||||
function renderNode(props) {
|
||||
switch (props.node.type) {
|
||||
case 'image': return Image(props)
|
||||
}
|
||||
}
|
||||
|
||||
export const props = {
|
||||
readOnly: true,
|
||||
renderNode,
|
||||
}
|
||||
|
||||
export const state = (
|
||||
|
@@ -3,18 +3,21 @@
|
||||
import React from 'react'
|
||||
import h from '../../helpers/h'
|
||||
|
||||
export const schema = {
|
||||
nodes: {
|
||||
emoji: (props) => {
|
||||
return (
|
||||
React.createElement('img', props.attributes)
|
||||
)
|
||||
}
|
||||
function Emoji(props) {
|
||||
return (
|
||||
React.createElement('img', props.attributes)
|
||||
)
|
||||
}
|
||||
|
||||
function renderNode(props) {
|
||||
switch (props.node.type) {
|
||||
case 'emoji': return Emoji(props)
|
||||
}
|
||||
}
|
||||
|
||||
export const props = {
|
||||
readOnly: true,
|
||||
renderNode,
|
||||
}
|
||||
|
||||
export const state = (
|
||||
|
@@ -2,7 +2,7 @@
|
||||
|
||||
import h from '../../helpers/h'
|
||||
|
||||
export const schema = {}
|
||||
export const props = {}
|
||||
|
||||
export const state = (
|
||||
<state>
|
||||
|
@@ -19,10 +19,9 @@ describe('rendering', () => {
|
||||
for (const test of tests) {
|
||||
it(test, async () => {
|
||||
const module = require(resolve(dir, test))
|
||||
const { state, schema, output, props } = module
|
||||
const { state, output, props } = module
|
||||
const p = {
|
||||
state,
|
||||
schema,
|
||||
onChange() {},
|
||||
...(props || {}),
|
||||
}
|
||||
|
Reference in New Issue
Block a user