1
0
mirror of https://github.com/ianstormtaylor/slate.git synced 2025-02-24 17:23:07 +01:00
slate/lib/components/editor.js
2016-06-17 19:57:37 -07:00

176 lines
3.7 KiB
JavaScript

import Content from './content'
import React from 'react'
import State from '../models/state'
import corePlugin from '../plugins/core'
/**
* Editor.
*/
class Editor extends React.Component {
static propTypes = {
plugins: React.PropTypes.array,
renderMark: React.PropTypes.func,
renderNode: React.PropTypes.func,
state: React.PropTypes.object,
onChange: React.PropTypes.func.isRequired
};
static defaultProps = {
plugins: [],
state: new State()
};
/**
* When created, compute the plugins from `props`.
*
* @param {Object} props
*/
constructor(props) {
super(props)
this.state = {}
this.state.plugins = this.resolvePlugins(props)
}
/**
* When the `props` are updated, recompute the plugins.
*
* @param {Object} props
*/
componentWillReceiveProps(props) {
const plugins = this.resolvePlugins(props)
this.setState({ plugins })
}
/**
* Get the editor's current `state`.
*
* @return {State} state
*/
getState() {
return this.props.state
}
/**
* When the `state` changes, pass through plugins, then bubble up.
*
* @param {State} state
*/
onChange(state) {
if (state == this.props.state) return
for (const plugin of this.state.plugins) {
if (!plugin.onChange) continue
const newState = plugin.onChange(state, this)
if (newState == null) continue
state = newState
}
this.props.onChange(state)
}
/**
* When an event by `name` fires, pass it through the plugins, and update the
* state if one of them chooses to.
*
* @param {String} name
* @param {Event} e
*/
onEvent(name, e) {
for (const plugin of this.state.plugins) {
if (!plugin[name]) continue
const newState = plugin[name](e, this.props.state, this)
if (!newState) continue
this.props.onChange(newState)
break
}
}
/**
* Render the editor.
*
* @return {Component} component
*/
render() {
return (
<Content
state={this.props.state}
onChange={state => this.onChange(state)}
onKeyDown={e => this.onEvent('onKeyDown', e)}
renderMark={mark => this.renderMark(mark)}
renderNode={node => this.renderNode(node)}
/>
)
}
/**
* Render a `node`, cascading through the plugins.
*
* @param {Node} node
* @return {Component} component
*/
renderNode(node) {
for (const plugin of this.state.plugins) {
if (!plugin.renderNode) continue
const component = plugin.renderNode(node, this.props.state, this)
if (component) return component
throw new Error('No renderer found for node.')
}
}
/**
* Render a `mark`, cascading through the plugins.
*
* @param {Mark} mark
* @return {Object} style
*/
renderMark(mark) {
for (const plugin of this.state.plugins) {
if (!plugin.renderMark) continue
const style = plugin.renderMark(mark, this.props.state, this)
if (style) return style
throw new Error('No renderer found for mark.')
}
}
/**
* Resolve the editor's current plugins from `props` when they change.
*
* Add a plugin made from the editor's own `props` at the beginning of the
* stack. That way, you can add a `onKeyDown` handler to the editor itself,
* and it will override all of the existing plugins.
*
* Also add the "core" functionality plugin that handles the most basic events
* for the editor, like delete characters and such.
*
* @param {Object} props
* @return {Array} plugins
*/
resolvePlugins(props) {
const { onChange, plugins, ...editorPlugin } = props
return [
editorPlugin,
...plugins,
corePlugin
]
}
}
/**
* Export.
*/
export default Editor