diff --git a/package.json b/package.json index 1eab2930d..a4745796f 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "immutable": "^3.8.1", "is-empty": "^1.0.0", "keycode": "^2.1.2", + "react-portal": "^3.0.0", "type-of": "^2.0.1" }, "peerDependencies": { @@ -59,7 +60,6 @@ "react-dom": "^15.4.2", "react-frame-aware-selection-plugin": "^1.0.0", "react-frame-component": "^0.6.2", - "react-portal": "^3.0.0", "react-router": "^2.5.1", "read-metadata": "^1.0.0", "read-yaml-promise": "^1.0.2", diff --git a/src/components/content.js b/src/components/content.js index 0b41de657..e0762889a 100644 --- a/src/components/content.js +++ b/src/components/content.js @@ -36,6 +36,7 @@ class Content extends React.Component { static propTypes = { autoCorrect: React.PropTypes.bool.isRequired, + children: React.PropTypes.object.isRequired, className: React.PropTypes.string, editor: React.PropTypes.object.isRequired, onBeforeInput: React.PropTypes.func.isRequired, @@ -755,6 +756,7 @@ class Content extends React.Component { tabIndex={tabIndex} > {children} + {this.props.children} ) } diff --git a/src/components/editor.js b/src/components/editor.js index 2867f59a3..407908c90 100644 --- a/src/components/editor.js +++ b/src/components/editor.js @@ -1,6 +1,7 @@ import Content from './content' import Debug from 'debug' +import Portal from 'react-portal' import React from 'react' import Stack from '../models/stack' import State from '../models/state' @@ -238,7 +239,9 @@ class Editor extends React.Component { render = () => { const { props, state } = this + const { stack } = state const handlers = {} + const children = stack.render(state.state, this) for (const property of EVENT_HANDLERS) { handlers[property] = this[property] @@ -260,7 +263,9 @@ class Editor extends React.Component { style={props.style} tabIndex={props.tabIndex} role={props.role} - /> + > + {children.map((child, i) => {child})} + ) } diff --git a/src/models/stack.js b/src/models/stack.js index c2123f12a..015786bd6 100644 --- a/src/models/stack.js +++ b/src/models/stack.js @@ -19,7 +19,7 @@ const debug = Debug('slate:stack') * @type {Array} */ -const EVENT_METHODS = [ +const EVENT_HANDLER_METHODS = [ 'onBeforeInput', 'onBlur', 'onCopy', @@ -36,20 +36,20 @@ const EVENT_METHODS = [ * @type {Array} */ -const ACCUMULATOR_METHODS = [ +const STATE_ACCUMULATOR_METHODS = [ 'onBeforeChange', 'onChange', ] /** - * All the runnable methods. + * Methods that accumulate an array. * * @type {Array} */ -const RUNNABLE_METHODS = [] - .concat(EVENT_METHODS) - .concat(ACCUMULATOR_METHODS) +const ARRAY_ACCUMULATOR_METHODS = [ + 'render' +] /** * Default properties. @@ -95,17 +95,19 @@ class Stack extends new Record(DEFAULTS) { return 'stack' } - /** - * Run a `method` in the stack with `state`. - * - * @param {String} method - * @param {State} state - * @param {Editor} editor - * @param {Mixed} ...args - * @return {State} - */ +} - run(method, state, editor, ...args) { +/** + * Mix in the event handler methods. + * + * @param {State} state + * @param {Editor} editor + * @param {Mixed} ...args + * @return {State|Null} + */ + +for (const method of EVENT_HANDLER_METHODS) { + Stack.prototype[method] = function (state, editor, ...args) { debug(method) if (method == 'onChange') { @@ -115,32 +117,76 @@ class Stack extends new Record(DEFAULTS) { for (const plugin of this.plugins) { if (!plugin[method]) continue const next = plugin[method](...args, state, editor) - - if (next == null) { - continue - } else if (next instanceof State) { - state = next - if (!ACCUMULATOR_METHODS.includes(method)) break - } else { - throw new Error(`A plugin returned an unexpected state value: ${next}`) - } + if (next == null) continue + assertState(next) + return next } return state } - } /** - * Mix in the runnable methods. + * Mix in the state accumulator methods. + * + * @param {State} state + * @param {Editor} editor + * @param {Mixed} ...args + * @return {State|Null} */ -for (const method of RUNNABLE_METHODS) { - Stack.prototype[method] = function (...args) { - return this.run(method, ...args) +for (const method of STATE_ACCUMULATOR_METHODS) { + Stack.prototype[method] = function (state, editor, ...args) { + debug(method) + + for (const plugin of this.plugins) { + if (!plugin[method]) continue + const next = plugin[method](...args, state, editor) + if (next == null) continue + assertState(next) + state = next + } + + return state } } +/** + * Mix in the array accumulator methods. + * + * @param {State} state + * @param {Editor} editor + * @param {Mixed} ...args + * @return {Array} + */ + +for (const method of ARRAY_ACCUMULATOR_METHODS) { + Stack.prototype[method] = function (state, editor, ...args) { + debug(method) + const array = [] + + for (const plugin of this.plugins) { + if (!plugin[method]) continue + const next = plugin[method](...args, state, editor) + if (next == null) continue + array.push(next) + } + + return array + } +} + +/** + * Assert that a `value` is a state object. + * + * @param {Mixed} value + */ + +function assertState(value) { + if (value instanceof State) return + throw new Error(`A plugin returned an unexpected state value: ${value}`) +} + /** * Resolve a schema from a set of `plugins`. *