1
0
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:
Ian Storm Taylor
2017-10-25 17:32:29 -07:00
committed by GitHub
parent 6298d5442d
commit 509d3d50fc
102 changed files with 2977 additions and 2175 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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
}

View File

@@ -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.

View File

@@ -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}

View File

@@ -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,
}
}

View File

@@ -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,

View File

@@ -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>

View File

@@ -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>

View File

@@ -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)
}
}
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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>

View File

@@ -2,7 +2,7 @@
import h from '../../helpers/h'
export const schema = {}
export const props = {}
export const state = (
<state>

View File

@@ -2,7 +2,7 @@
import h from '../../helpers/h'
export const schema = {}
export const props = {}
export const state = (
<state>

View File

@@ -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>

View File

@@ -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>

View File

@@ -2,7 +2,7 @@
import h from '../../helpers/h'
export const schema = {}
export const props = {}
export const state = (
<state>

View File

@@ -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 = (

View File

@@ -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 = (

View File

@@ -2,7 +2,7 @@
import h from '../../helpers/h'
export const schema = {}
export const props = {}
export const state = (
<state>

View File

@@ -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 || {}),
}