From f42a64ac8fa1a8129c6fdd9758fe67247f063046 Mon Sep 17 00:00:00 2001 From: Ian Storm Taylor Date: Wed, 18 Oct 2017 00:23:39 -0700 Subject: [PATCH] refactor placeholder to use schema (#1253) * refactor placeholder to use schema * update placeholder, remove old export * add maxWidth to prevent overflow * update docs --- docs/Readme.md | 1 - docs/reference/slate-react/placeholder.md | 58 -------- docs/reference/slate/schema.md | 13 ++ examples/check-lists/index.js | 2 +- examples/code-highlighting/index.js | 1 + examples/embeds/index.js | 1 + examples/emojis/index.js | 1 + examples/forced-layout/index.js | 1 + examples/hovering-menu/index.js | 1 + examples/huge-document/index.js | 2 +- examples/images/index.js | 1 + examples/links/index.js | 1 + examples/markdown-preview/index.js | 1 + examples/markdown-shortcuts/index.js | 1 + examples/paste-html/index.js | 1 + examples/plain-text/index.js | 2 +- examples/plugins/index.js | 2 +- examples/read-only/index.js | 2 +- examples/rich-text/index.js | 6 +- examples/rtl/index.js | 2 +- examples/search-highlighting/index.js | 2 +- examples/syncing-operations/index.js | 2 +- examples/tables/index.js | 1 + .../src/components/default-node.js | 51 +++++++ .../src/components/default-placeholder.js | 64 +++++++++ packages/slate-react/src/components/node.js | 28 ++-- .../slate-react/src/components/placeholder.js | 125 ------------------ packages/slate-react/src/index.js | 3 - packages/slate-react/src/plugins/after.js | 73 ++-------- packages/slate/src/models/node.js | 12 ++ packages/slate/src/models/schema.js | 23 +++- 31 files changed, 209 insertions(+), 275 deletions(-) delete mode 100644 docs/reference/slate-react/placeholder.md create mode 100644 packages/slate-react/src/components/default-node.js create mode 100644 packages/slate-react/src/components/default-placeholder.js delete mode 100644 packages/slate-react/src/components/placeholder.js diff --git a/docs/Readme.md b/docs/Readme.md index 2e8d2ca80..d194736f2 100644 --- a/docs/Readme.md +++ b/docs/Readme.md @@ -48,7 +48,6 @@ ## Slate React - [Editor](./reference/slate-react/editor.md) -- [Placeholder](./reference/slate-react/placeholder.md) - [Plugins](./reference/slate-react/plugins.md) - [Custom Nodes](./reference/slate-react/custom-nodes.md) - [Core Plugins](./reference/slate-react/core-plugins.md) diff --git a/docs/reference/slate-react/placeholder.md b/docs/reference/slate-react/placeholder.md deleted file mode 100644 index 521370ccb..000000000 --- a/docs/reference/slate-react/placeholder.md +++ /dev/null @@ -1,58 +0,0 @@ - -# `` - -```js -import { Placeholder } from 'slate-react' -``` - -A simple component that adds a placeholder to a node. It encapsulates all of the Slate-related logic that determines when to render the placeholder, so you don't have to think about it. - - -## Properties - -```js - - {children} - -``` - -### `children` -`Any` - -React child elements to render inside the placeholder `` element. - -### `className` -`String` - -An optional class name string to add to the placeholder `` element. - -### `firstOnly` -`Boolean` - -An optional toggle that allows the Placeholder to render even if it is not the first node of the parent. This is useful for cases where the Placeholder should show up at every empty instance of the node. Defaults to `true`. - -### `node` -`Node` - -The node to render the placeholder element on top of. The placeholder is positioned absolutely, covering the entire node. - -### `parent` -`Node` - -The node to check for non-empty content, to determine whether the placeholder should be shown or not, if `firstOnly` is set to `false`. - -### `state` -`State` - -The current state of the editor. - -### `style` -`Object` - -An optional dictionary of styles to pass to the placeholder `` element. diff --git a/docs/reference/slate/schema.md b/docs/reference/slate/schema.md index 6335d7417..a8f1a2cd2 100644 --- a/docs/reference/slate/schema.md +++ b/docs/reference/slate/schema.md @@ -84,6 +84,7 @@ Internally, the `marks` and `nodes` properties of a schema are simply converted match: Function, decorate: Function, normalize: Function, + placeholder: Component || Function, render: Component || Function || Object || String, validate: Function } @@ -140,6 +141,18 @@ The `decorate` property allows you define a function that will apply extra marks The `normalize` property is a function to run that recovers the editor's state after the `validate` property of a rule has determined that an object is invalid. It is passed a [`Change`](./change.md) that it can use to make modifications. It is also passed the return value of the `validate` function, which makes it easy to quickly determine the failure reason from the validation. +### `placeholder` +`Component`
+`Function` + +```js +{ + placeholder: (props) => {props.editor.props.placeholder} +} +``` + +The `placeholder` property determines which React component Slate will use to render a placeholder for the editor. + ### `render` `Component`
`Function`
diff --git a/examples/check-lists/index.js b/examples/check-lists/index.js index df81d579e..78e04cf6f 100644 --- a/examples/check-lists/index.js +++ b/examples/check-lists/index.js @@ -145,7 +145,7 @@ class CheckLists extends React.Component {
diff --git a/examples/plugins/index.js b/examples/plugins/index.js index c0417f257..ff8158e53 100644 --- a/examples/plugins/index.js +++ b/examples/plugins/index.js @@ -83,7 +83,7 @@ The third is an example of using the plugin.render property to create a higher-o render() { return ( diff --git a/examples/rich-text/index.js b/examples/rich-text/index.js index 86d1e8c27..bb5673a90 100644 --- a/examples/rich-text/index.js +++ b/examples/rich-text/index.js @@ -295,12 +295,12 @@ class RichTextExample extends React.Component { return (
) diff --git a/examples/rtl/index.js b/examples/rtl/index.js index c5df00c71..2a32addfd 100644 --- a/examples/rtl/index.js +++ b/examples/rtl/index.js @@ -69,7 +69,7 @@ class PlainText extends React.Component { render() { return (
diff --git a/examples/syncing-operations/index.js b/examples/syncing-operations/index.js index bc68becf9..e080b7212 100644 --- a/examples/syncing-operations/index.js +++ b/examples/syncing-operations/index.js @@ -209,7 +209,7 @@ class SyncingEditor extends React.Component { onChange={this.onChange} onKeyDown={this.onKeyDown} schema={schema} - placeholder={'Enter some rich text...'} + placeholder="Enter some text..." spellCheck />
diff --git a/examples/tables/index.js b/examples/tables/index.js index 47be1fc65..20601fa43 100644 --- a/examples/tables/index.js +++ b/examples/tables/index.js @@ -134,6 +134,7 @@ class Tables extends React.Component { return (
{children} + } + +} + +/** + * Export. + * + * @type {Component} + */ + +export default DefaultNode diff --git a/packages/slate-react/src/components/default-placeholder.js b/packages/slate-react/src/components/default-placeholder.js new file mode 100644 index 000000000..9ecd8e71a --- /dev/null +++ b/packages/slate-react/src/components/default-placeholder.js @@ -0,0 +1,64 @@ + +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 (state.document.nodes.size > 1) return null + if (!editor.props.placeholder) return null + + const style = { + pointerEvents: 'none', + display: 'inline-block', + width: '0', + maxWidth: '100%', + whiteSpace: 'nowrap', + opacity: '0.333', + } + + return ( + + {editor.props.placeholder} + + ) + } + +} + +/** + * Export. + * + * @type {Component} + */ + +export default DefaultPlaceholder diff --git a/packages/slate-react/src/components/node.js b/packages/slate-react/src/components/node.js index 46748adf2..8469c17a6 100644 --- a/packages/slate-react/src/components/node.js +++ b/packages/slate-react/src/components/node.js @@ -54,6 +54,7 @@ class Node extends React.Component { const { node, schema } = props this.state = {} this.state.Component = node.getComponent(schema) + this.state.Placeholder = node.getPlaceholder(schema) } /** @@ -78,7 +79,8 @@ class Node extends React.Component { componentWillReceiveProps = (props) => { if (props.node == this.props.node) return const Component = props.node.getComponent(props.schema) - this.setState({ Component }) + const Placeholder = props.node.getPlaceholder(props.schema) + this.setState({ Component, Placeholder }) } /** @@ -154,7 +156,7 @@ class Node extends React.Component { this.debug('render', { props }) const { editor, isSelected, node, parent, readOnly, state } = props - const { Component } = this.state + const { Component, Placeholder } = this.state const { selection } = state const indexes = node.getSelectionIndexes(selection, isSelected) const children = node.nodes.toArray().map((child, i) => { @@ -173,17 +175,19 @@ class Node extends React.Component { if (direction == 'rtl') attributes.dir = 'rtl' } + const p = { + editor, + isSelected, + key: node.key, + node, + parent, + readOnly, + state + } + const element = ( - + + {Placeholder && } {children} ) diff --git a/packages/slate-react/src/components/placeholder.js b/packages/slate-react/src/components/placeholder.js deleted file mode 100644 index 096966fdc..000000000 --- a/packages/slate-react/src/components/placeholder.js +++ /dev/null @@ -1,125 +0,0 @@ - -import React from 'react' -import SlateTypes from 'slate-prop-types' -import Types from 'prop-types' - -/** - * Placeholder. - * - * @type {Component} - */ - -class Placeholder extends React.Component { - - /** - * Property types. - * - * @type {Object} - */ - - static propTypes = { - children: Types.any.isRequired, - className: Types.string, - firstOnly: Types.bool, - node: SlateTypes.node.isRequired, - parent: SlateTypes.node, - state: SlateTypes.state.isRequired, - style: Types.object, - } - - /** - * Default properties. - * - * @type {Object} - */ - - static defaultProps = { - firstOnly: true, - } - - /** - * Should the placeholder update? - * - * @param {Object} props - * @param {Object} state - * @return {Boolean} - */ - - shouldComponentUpdate = (props, state) => { - return ( - props.children != this.props.children || - props.className != this.props.className || - props.firstOnly != this.props.firstOnly || - props.parent != this.props.parent || - props.node != this.props.node || - props.style != this.props.style - ) - } - - /** - * Is the placeholder visible? - * - * @return {Boolean} - */ - - isVisible = () => { - const { firstOnly, node, parent } = this.props - if (node.text) return false - - if (firstOnly) { - if (parent.nodes.size > 1) return false - if (parent.nodes.first() === node) return true - return false - } else { - return true - } - } - - /** - * Render. - * - * If the placeholder is a string, and no `className` or `style` has been - * passed, give it a default style of lowered opacity. - * - * @return {Element} - */ - - render() { - const isVisible = this.isVisible() - if (!isVisible) return null - - const { children, className } = this.props - let { style } = this.props - - if (typeof children === 'string' && style == null && className == null) { - style = { opacity: '0.333' } - } else if (style == null) { - style = {} - } - - const styles = { - position: 'absolute', - top: '0px', - right: '0px', - bottom: '0px', - left: '0px', - pointerEvents: 'none', - ...style - } - - return ( - - {children} - - ) - } - -} - -/** - * Export. - * - * @type {Component} - */ - -export default Placeholder diff --git a/packages/slate-react/src/index.js b/packages/slate-react/src/index.js index 9461742b3..ca8b0c0a9 100644 --- a/packages/slate-react/src/index.js +++ b/packages/slate-react/src/index.js @@ -1,6 +1,5 @@ import Editor from './components/editor' -import Placeholder from './components/placeholder' import findDOMNode from './utils/find-dom-node' import findDOMRange from './utils/find-dom-range' import findNode from './utils/find-node' @@ -17,7 +16,6 @@ import setEventTransfer from './utils/set-event-transfer' export { Editor, - Placeholder, findDOMNode, findDOMRange, findNode, @@ -29,7 +27,6 @@ export { export default { Editor, - Placeholder, findDOMNode, findDOMRange, findNode, diff --git a/packages/slate-react/src/plugins/after.js b/packages/slate-react/src/plugins/after.js index bf02e1cd4..39115a405 100644 --- a/packages/slate-react/src/plugins/after.js +++ b/packages/slate-react/src/plugins/after.js @@ -4,12 +4,13 @@ import Debug from 'debug' import Plain from 'slate-plain-serializer' import React from 'react' import getWindow from 'get-window' -import { Block, Inline, coreSchema } from 'slate' +import { Block, Inline, Text, coreSchema } from 'slate' import EVENT_HANDLERS from '../constants/event-handlers' import HOTKEYS from '../constants/hotkeys' import Content from '../components/content' -import Placeholder from '../components/placeholder' +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' @@ -31,19 +32,10 @@ const debug = Debug('slate:core:after') * The after plugin. * * @param {Object} options - * @property {Element} placeholder - * @property {String} placeholderClassName - * @property {Object} placeholderStyle * @return {Object} */ function AfterPlugin(options = {}) { - const { - placeholder, - placeholderClassName, - placeholderStyle, - } = options - let isDraggingInternally = null /** @@ -726,55 +718,6 @@ function AfterPlugin(options = {}) { ) } - /** - * A default schema rule to render block nodes. - * - * @type {Object} - */ - - const BLOCK_RENDER_RULE = { - match: (node) => { - return node.kind == 'block' - }, - render: (props) => { - return ( -
- {props.children} - {placeholder - ? - {placeholder} - - : null} -
- ) - } - } - - /** - * A default schema rule to render inline nodes. - * - * @type {Object} - */ - - const INLINE_RENDER_RULE = { - match: (node) => { - return node.kind == 'inline' - }, - render: (props) => { - return ( - - {props.children} - - ) - } - } - /** * Add default rendering rules to the schema. * @@ -783,8 +726,14 @@ function AfterPlugin(options = {}) { const schema = { rules: [ - BLOCK_RENDER_RULE, - INLINE_RENDER_RULE + { + match: obj => obj.kind == 'block' || obj.kind == 'inline', + render: DefaultNode, + }, + { + match: obj => obj.kind == 'block' && Text.isTextList(obj.nodes) && obj.text == '', + placeholder: DefaultPlaceholder, + }, ] } diff --git a/packages/slate/src/models/node.js b/packages/slate/src/models/node.js index 361731428..43d93d8a5 100644 --- a/packages/slate/src/models/node.js +++ b/packages/slate/src/models/node.js @@ -1328,6 +1328,17 @@ class Node { return path } + /** + * Get the placeholder for the node from a `schema`. + * + * @param {Schema} schema + * @return {Component|Void} + */ + + getPlaceholder(schema) { + return schema.__getPlaceholder(this) + } + /** * Get the block node before a descendant text node by `key`. * @@ -2142,6 +2153,7 @@ memoize(Node.prototype, [ 'getOffsetAtRange', 'getParent', 'getPath', + 'getPlaceholder', 'getPreviousBlock', 'getPreviousSibling', 'getPreviousText', diff --git a/packages/slate/src/models/schema.js b/packages/slate/src/models/schema.js index 78cb6b841..d248e0702 100644 --- a/packages/slate/src/models/schema.js +++ b/packages/slate/src/models/schema.js @@ -110,22 +110,39 @@ class Schema extends Record(DEFAULTS) { } /** - * Return the renderer for an `object`. + * Return the component for an `object`. * * This method is private, because it should always be called on one of the * often-changing immutable objects instead, since it will be memoized for * much better performance. * * @param {Mixed} object - * @return {Component|Void} + * @return {Component|Null} */ __getComponent(object) { const match = find(this.rules, rule => rule.render && rule.match(object)) - if (!match) return + if (!match) return null return match.render } + /** + * Return the placeholder for an `object`. + * + * This method is private, because it should always be called on one of the + * often-changing immutable objects instead, since it will be memoized for + * much better performance. + * + * @param {Mixed} object + * @return {Component|Null} + */ + + __getPlaceholder(object) { + const match = find(this.rules, rule => rule.placeholder && rule.match(object)) + if (!match) return null + return match.placeholder + } + /** * Return the decorations for an `object`. *