diff --git a/examples/app.js b/examples/app.js index bc0f08cad..fb329271f 100644 --- a/examples/app.js +++ b/examples/app.js @@ -27,6 +27,7 @@ import PlainText from './plain-text' import Plugins from './plugins' import RTL from './rtl' import ReadOnly from './read-only' +import RestoreDOM from './restore-dom' import RichText from './rich-text' import SearchHighlighting from './search-highlighting' import Composition from './composition' @@ -63,6 +64,7 @@ const EXAMPLES = [ ['Plain Text', PlainText, '/plain-text'], ['Plugins', Plugins, '/plugins'], ['Read-only', ReadOnly, '/read-only'], + ['Restore DOM', RestoreDOM, '/restore-dom'], ['Rich Text', RichText, '/rich-text'], ['RTL', RTL, '/rtl'], ['Search Highlighting', SearchHighlighting, '/search-highlighting'], diff --git a/examples/restore-dom/index.js b/examples/restore-dom/index.js new file mode 100644 index 000000000..645939904 --- /dev/null +++ b/examples/restore-dom/index.js @@ -0,0 +1,146 @@ +import { Editor } from 'slate-react' +import { Value } from 'slate' + +import React from 'react' +import initialValue from './value.json' +import { Button, Icon, Toolbar } from '../components' + +/** + * The Restore DOM example. + * + * This shows the usage of the `restoreDOM` command to rebuild the editor from + * scratch causing all the nodes to force render even if there are no changes + * to the DOM. + * + * The `onClickHighlight` method changes the internal state but normally the + * change is not rendered because there is no change to Slate's internal + * `value`. + * + * RestoreDOM also blows away the old render which makes it safe if the DOM + * has been altered outside of React. + * + * @type {Component} + */ + +class RestoreDOMExample extends React.Component { + /** + * Deserialize the initial editor value and set an initial highlight color. + * + * @type {Object} + */ + + state = { + value: Value.fromJSON(initialValue), + bgcolor: '#ffffff', + } + + /** + * Store a reference to the `editor`. + * + * @param {Editor} editor + */ + + ref = editor => { + this.editor = editor + } + + /** + * Render. + * + * @return {Element} + */ + + render() { + return ( +
+ + {this.renderHighlightButton('#ffffff')} + {this.renderHighlightButton('#ffeecc')} + {this.renderHighlightButton('#ffffcc')} + {this.renderHighlightButton('#ccffcc')} + {this.renderHighlightButton('#ccffff')} + + +
+ ) + } + + /** + * Render a highlight button + * + * @param {String} bgcolor + * @return {Element} + */ + + renderHighlightButton = bgcolor => { + const isActive = this.state.bgcolor === bgcolor + return ( + + ) + } + + /** + * Highlight every block with a given background color + * + * @param {String} bgcolor + */ + + onClickHighlight = bgcolor => { + const { editor } = this + this.setState({ bgcolor }) + editor.restoreDOM() + } + + /** + * Render a Slate block. + * + * @param {Object} props + * @return {Element} + */ + + renderBlock = (props, editor, next) => { + const { attributes, children, node } = props + const style = { backgroundColor: this.state.bgcolor } + + switch (node.type) { + case 'paragraph': + return ( +

+ {children} +

+ ) + default: + return next() + } + } + + /** + * On change, save the new `value`. + * + * @param {Editor} editor + */ + + onChange = ({ value }) => { + this.setState({ value }) + } +} + +/** + * Export. + */ + +export default RestoreDOMExample diff --git a/examples/restore-dom/value.json b/examples/restore-dom/value.json new file mode 100644 index 000000000..5011dacdd --- /dev/null +++ b/examples/restore-dom/value.json @@ -0,0 +1,38 @@ +{ + "object": "value", + "document": { + "object": "document", + "nodes": [ + { + "object": "block", + "type": "paragraph", + "nodes": [ + { + "object": "text", + "text": "First block of text" + } + ] + }, + { + "object": "block", + "type": "paragraph", + "nodes": [ + { + "object": "text", + "text": "Second block of text" + } + ] + }, + { + "object": "block", + "type": "paragraph", + "nodes": [ + { + "object": "text", + "text": "Third block of text" + } + ] + } + ] + } +} diff --git a/packages/slate-react/src/components/content.js b/packages/slate-react/src/components/content.js index c4ffe5d60..7c0cb8098 100644 --- a/packages/slate-react/src/components/content.js +++ b/packages/slate-react/src/components/content.js @@ -53,6 +53,7 @@ class Content extends React.Component { static propTypes = { autoCorrect: Types.bool.isRequired, className: Types.string, + contentKey: Types.number, editor: Types.object.isRequired, id: Types.string, readOnly: Types.bool.isRequired, @@ -486,6 +487,7 @@ class Content extends React.Component { return ( this.run(handler, event)} diff --git a/packages/slate-react/src/plugins/react/index.js b/packages/slate-react/src/plugins/react/index.js index 4fa44e970..24bc7eb79 100644 --- a/packages/slate-react/src/plugins/react/index.js +++ b/packages/slate-react/src/plugins/react/index.js @@ -4,6 +4,7 @@ import EditorPropsPlugin from './editor-props' import RenderingPlugin from './rendering' import QueriesPlugin from './queries' import DOMPlugin from '../dom' +import RestoreDOMPlugin from './restore-dom' /** * A plugin that adds the React-specific rendering logic to the editor. @@ -20,7 +21,7 @@ function ReactPlugin(options = {}) { const domPlugin = DOMPlugin({ plugins: [editorPropsPlugin, ...plugins], }) - + const restoreDomPlugin = RestoreDOMPlugin() const placeholderPlugin = PlaceholderPlugin({ placeholder, when: (editor, node) => @@ -30,7 +31,13 @@ function ReactPlugin(options = {}) { Array.from(node.texts()).length === 1, }) - return [domPlugin, placeholderPlugin, renderingPlugin, queriesPlugin] + return [ + domPlugin, + restoreDomPlugin, + placeholderPlugin, + renderingPlugin, + queriesPlugin, + ] } /** diff --git a/packages/slate-react/src/plugins/react/restore-dom.js b/packages/slate-react/src/plugins/react/restore-dom.js new file mode 100644 index 000000000..c40a9c521 --- /dev/null +++ b/packages/slate-react/src/plugins/react/restore-dom.js @@ -0,0 +1,21 @@ +function RestoreDOMPlugin() { + /** + * Makes sure that on the next Content `render` the DOM is restored. + * This gets us around issues where the DOM is in a different state than + * React's virtual DOM and would crash. + * + * @param {Editor} editor + */ + + function restoreDOM(editor) { + editor.setState({ contentKey: editor.state.contentKey + 1 }) + } + + return { + commands: { + restoreDOM, + }, + } +} + +export default RestoreDOMPlugin