From 2a5d3ee55611853e9a8e7339eabc84513ed53e3e Mon Sep 17 00:00:00 2001 From: Ian Storm Taylor Date: Thu, 2 Mar 2017 18:19:39 -0800 Subject: [PATCH] rename plugin.render to renderPortal, and add plugin.render --- History.md | 11 +++++ docs/reference/plugins/core.md | 4 ++ docs/reference/plugins/plugin.md | 14 +++++- src/components/editor.js | 46 ++---------------- src/models/stack.js | 82 ++++++++++++++++++-------------- src/plugins/core.js | 39 +++++++++++++++ 6 files changed, 119 insertions(+), 77 deletions(-) diff --git a/History.md b/History.md index 10e4aaa01..7cc13d349 100644 --- a/History.md +++ b/History.md @@ -4,6 +4,17 @@ This document maintains a list of changes to Slate with each new version. Until --- + +### `0.18.0` — March 2, 2017 + +###### BREAKING CHANGES + +- **The `plugin.render` property is now called `plugin.renderPortal`.** This is to make way for the new `plugin.render` property that offers HOC-like behavior, so that plugins can augment the editor however they choose. + + +--- + + ### `0.17.0` — February 27, 2017 ###### DEPRECATION CHANGES diff --git a/docs/reference/plugins/core.md b/docs/reference/plugins/core.md index 9edb2a757..1dcd2f55e 100644 --- a/docs/reference/plugins/core.md +++ b/docs/reference/plugins/core.md @@ -43,6 +43,10 @@ When the user pastes content into the editor, the core plugin handles all pastes When the user makes a new selection in the DOM, the core plugin updates that selection in Slate's internal data model, re-rendering if it needs to. +### `render` + +Renders all of the default contents of the editor! + ### `schema` The core plugin defines a schema that enforces a few constraints on the content and defines default block and inline node renderer components—wrapping in a `
` and ``, respectively. Each of these components contains `shouldComponentUpdate` logic that prevents unnecessary re-renders. diff --git a/docs/reference/plugins/plugin.md b/docs/reference/plugins/plugin.md index 2035e3377..b48ca3247 100644 --- a/docs/reference/plugins/plugin.md +++ b/docs/reference/plugins/plugin.md @@ -20,6 +20,8 @@ When the editor needs to resolve a plugin-related handler, it will loop through - [Other Properties](#other-properties) - [`onChange`](#onchange) - [`onBeforeChange`](#onbeforechange) + - [`render`](#render) + - [`renderPortal`](#renderportal) - [`schema`](#schema) @@ -181,7 +183,7 @@ The `data` object is a convenience object created to standardize the paste metad If no other plugin handles this event, it will be handled by the [Core plugin](./core.md). ### `onSelect` -`Function onSelect(event: Event, data: Object, state: State, editor: Editor => State || Void` +`Function onSelect(event: Event, data: Object, state: State, editor: Editor) => State || Void` This handler is called whenever the native DOM selection changes. @@ -216,6 +218,16 @@ The `onBeforeChange` handler isn't a native browser event handler. Instead, it i Like `onChange`, `onBeforeChange` is cummulative. +### `render` +`Function render(props: Object, state: State, editor: Editor) => Object || Void` + +The `render` property allows you to define higher-order-component-like behavior. It is passed all of the properties of the editor, including `props.children`. You can then choose to wrap the existing `children` in any custom elements or proxy the properties however you choose. This can be useful for rendering toolbars, styling the editor, rendering validation, etc. + +### `renderPortal` +`Function renderPortal(state: State, editor: Editor) => Object || Void` + +The `renderPortal` property allows you to define extra elements that will render outside of the editor, in a separate [portal](). This is useful for rendering hovering menus, or other cases where you don't need to render inside the editor, but want to add elements to the DOM. + ### `schema` `Object` diff --git a/src/components/editor.js b/src/components/editor.js index a13126a35..7e90f18a2 100644 --- a/src/components/editor.js +++ b/src/components/editor.js @@ -1,5 +1,4 @@ -import Content from './content' import Debug from 'debug' import Portal from 'react-portal' import React from 'react' @@ -47,23 +46,6 @@ const PLUGINS_PROPS = [ 'schema', ] -/** - * Pass-through properties of the editor. - * - * @type {Array} - */ - -const PASS_THROUGH_PROPS = [ - 'autoCorrect', - 'autoFocus', - 'className', - 'readOnly', - 'role', - 'spellCheck', - 'style', - 'tabIndex', -] - /** * Editor. * @@ -258,32 +240,14 @@ class Editor extends React.Component { render = () => { const { props, state } = this const { stack } = state - const handlers = {} - const passes = {} - const children = stack.render(state.state, this) - - for (const property of EVENT_HANDLERS) { - handlers[property] = this[property] - } - - for (const property of PASS_THROUGH_PROPS) { - passes[property] = this.props[property] - } + const children = stack + .renderPortal(state.state, this) + .map((child, i) => {child}) debug('render', { props, state }) - return ( - - {children.map((child, i) => {child})} - - ) + const tree = stack.render(state.state, this, { ...props, children }) + return tree } } diff --git a/src/models/stack.js b/src/models/stack.js index 015786bd6..aaab0660f 100644 --- a/src/models/stack.js +++ b/src/models/stack.js @@ -41,16 +41,6 @@ const STATE_ACCUMULATOR_METHODS = [ 'onChange', ] -/** - * Methods that accumulate an array. - * - * @type {Array} - */ - -const ARRAY_ACCUMULATOR_METHODS = [ - 'render' -] - /** * Default properties. * @@ -95,6 +85,53 @@ class Stack extends new Record(DEFAULTS) { return 'stack' } + /** + * Invoke `render` on all of the plugins in reverse, building up a tree of + * higher-order components. + * + * @param {State} state + * @param {Editor} editor + * @param {Object} children + * @param {Object} props + * @return {Component} + */ + + render = (state, editor, props) => { + debug('render') + const plugins = this.plugins.slice().reverse() + let children + + for (const plugin of plugins) { + if (!plugin.render) continue + children = plugin.render(props, state, editor) + props.children = children + } + + return children + } + + /** + * Invoke `renderPortal` on all of the plugins, building a list of portals. + * + * @param {State} state + * @param {Editor} editor + * @return {Array} + */ + + renderPortal = (state, editor) => { + debug('renderPortal') + const portals = [] + + for (const plugin of this.plugins) { + if (!plugin.renderPortal) continue + const portal = plugin.renderPortal(state, editor) + if (portal == null) continue + portals.push(portal) + } + + return portals + } + } /** @@ -151,31 +188,6 @@ for (const method of STATE_ACCUMULATOR_METHODS) { } } -/** - * 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. * diff --git a/src/plugins/core.js b/src/plugins/core.js index 7a9c11b7b..c5214723e 100644 --- a/src/plugins/core.js +++ b/src/plugins/core.js @@ -1,5 +1,6 @@ import Base64 from '../serializers/base-64' +import Content from '../components/content' import Character from '../models/character' import Debug from 'debug' import Placeholder from '../components/placeholder' @@ -814,6 +815,43 @@ function Plugin(options = {}) { .apply() } + /** + * Render. + * + * @param {Object} props + * @param {State} state + * @param {Editor} editor + * @return {Object} + */ + + function render(props, state, editor) { + return ( + + ) + } + /** * A default schema rule to render block nodes. * @@ -892,6 +930,7 @@ function Plugin(options = {}) { onKeyDown, onPaste, onSelect, + render, schema, } }