diff --git a/blocks/components/editable/index.js b/blocks/components/editable/index.js index bc524c53205250..072615f44564fc 100644 --- a/blocks/components/editable/index.js +++ b/blocks/components/editable/index.js @@ -2,14 +2,19 @@ * External dependencies */ import classnames from 'classnames'; -import { forEach, last } from 'lodash'; +import { last, isEqual } from 'lodash'; import { Parser as HtmlToReactParser } from 'html-to-react'; +import { Fill } from 'react-slot-fill'; /** * Internal dependencies */ import './style.scss'; + // TODO: We mustn't import by relative path traversing from blocks to editor + // as we're doing here; instead, we should consider a common components path. +import Toolbar from '../../../editor/components/toolbar'; + const htmlToReactParser = new HtmlToReactParser(); const formatMap = { strong: 'bold', @@ -17,17 +22,39 @@ const formatMap = { del: 'strikethrough' }; +const formattingControls = [ + { + icon: 'editor-bold', + title: wp.i18n.__( 'Bold' ), + format: 'bold' + }, + { + icon: 'editor-italic', + title: wp.i18n.__( 'Italic' ), + format: 'italic' + }, + { + icon: 'editor-strikethrough', + title: wp.i18n.__( 'Strikethrough' ), + format: 'strikethrough' + } +]; + export default class Editable extends wp.element.Component { constructor() { super( ...arguments ); + this.onInit = this.onInit.bind( this ); this.onSetup = this.onSetup.bind( this ); this.onChange = this.onChange.bind( this ); this.onNewBlock = this.onNewBlock.bind( this ); - this.bindNode = this.bindNode.bind( this ); + this.bindEditorNode = this.bindEditorNode.bind( this ); this.onFocus = this.onFocus.bind( this ); this.onNodeChange = this.onNodeChange.bind( this ); - this.formats = {}; + + this.state = { + formats: {} + }; } componentDidMount() { @@ -36,7 +63,7 @@ export default class Editable extends wp.element.Component { initialize() { const config = { - target: this.node, + target: this.editorNode, theme: false, inline: true, toolbar: false, @@ -57,10 +84,7 @@ export default class Editable extends wp.element.Component { editor.on( 'focusout', this.onChange ); editor.on( 'NewBlock', this.onNewBlock ); editor.on( 'focusin', this.onFocus ); - - if ( this.props.onFormatChange ) { - editor.on( 'nodechange', this.onNodeChange ); - } + editor.on( 'nodechange', this.onNodeChange ); } onInit() { @@ -131,7 +155,7 @@ export default class Editable extends wp.element.Component { } onNodeChange( { parents } ) { - this.formats = parents.reduce( ( result, node ) => { + const formats = parents.reduce( ( result, node ) => { const tag = node.nodeName.toLowerCase(); if ( formatMap.hasOwnProperty( tag ) ) { @@ -141,11 +165,13 @@ export default class Editable extends wp.element.Component { return result; }, {} ); - this.props.onFormatChange( this.formats ); + if ( ! isEqual( this.state.formats, formats ) ) { + this.setState( { formats } ); + } } - bindNode( ref ) { - this.node = ref; + bindEditorNode( ref ) { + this.editorNode = ref; } updateContent() { @@ -208,31 +234,46 @@ export default class Editable extends wp.element.Component { } } - componentWillReceiveProps( nextProps ) { - forEach( nextProps.formats, ( state, format ) => { - const currentState = this.formats[ format ] || false; + isFormatActive( format ) { + return !! this.state.formats[ format ]; + } - if ( state !== currentState ) { - this.editor.focus(); + toggleFormat( format ) { + this.editor.focus(); - if ( state ) { - this.editor.formatter.apply( format ); - } else { - this.editor.formatter.remove( format ); - } - } - } ); + if ( this.isFormatActive( format ) ) { + this.editor.formatter.remove( format ); + } else { + this.editor.formatter.apply( format ); + } } render() { - const { tagName: Tag = 'div', style, className } = this.props; + const { tagName: Tag = 'div', style, focus, className } = this.props; const classes = classnames( 'blocks-editable', className ); - return ( + let element = ( + className={ classes } + key="editor" /> ); + + if ( focus ) { + element = [ + + ( { + ...control, + onClick: () => this.toggleFormat( control.format ), + isActive: this.isFormatActive( control.format ) + } ) ) } /> + , + element + ]; + } + + return element; } } diff --git a/blocks/library/text/index.js b/blocks/library/text/index.js index 5d1084fd146cbd..75c60a6c9d79a1 100644 --- a/blocks/library/text/index.js +++ b/blocks/library/text/index.js @@ -44,7 +44,7 @@ registerBlock( 'core/text', { } ], - edit( { attributes, setAttributes, insertBlockAfter, focus, setFocus, onFormatChange, formats } ) { + edit( { attributes, setAttributes, insertBlockAfter, focus, setFocus } ) { const { content =

, align } = attributes; return ( @@ -64,8 +64,6 @@ registerBlock( 'core/text', { content: after } ) ); } } - onFormatChange={ onFormatChange } - formats={ formats } /> ); }, diff --git a/editor/index.js b/editor/index.js index 5c9b8ab0d4c4b8..4a003fdf0de398 100644 --- a/editor/index.js +++ b/editor/index.js @@ -1,7 +1,8 @@ /** * External dependencies */ -import { Provider } from 'react-redux'; +import { Provider as ReduxProvider } from 'react-redux'; +import { Provider as SlotFillProvider } from 'react-slot-fill'; /** * Internal dependencies @@ -24,9 +25,11 @@ export function createEditorInstance( id, post ) { } ); wp.element.render( - - - , + + + + + , document.getElementById( id ) ); } diff --git a/editor/modes/visual-editor/block.js b/editor/modes/visual-editor/block.js index 441287f052a0f2..f524930ee38fd1 100644 --- a/editor/modes/visual-editor/block.js +++ b/editor/modes/visual-editor/block.js @@ -3,6 +3,7 @@ */ import { connect } from 'react-redux'; import classnames from 'classnames'; +import { Slot } from 'react-slot-fill'; /** * Internal dependencies @@ -11,24 +12,6 @@ import Toolbar from 'components/toolbar'; import BlockMover from 'components/block-mover'; import BlockSwitcher from 'components/block-switcher'; -const formattingControls = [ - { - icon: 'editor-bold', - title: wp.i18n.__( 'Bold' ), - format: 'bold' - }, - { - icon: 'editor-italic', - title: wp.i18n.__( 'Italic' ), - format: 'italic' - }, - { - icon: 'editor-strikethrough', - title: wp.i18n.__( 'Strikethrough' ), - format: 'strikethrough' - } -]; - class VisualEditorBlock extends wp.element.Component { constructor() { super( ...arguments ); @@ -36,37 +19,13 @@ class VisualEditorBlock extends wp.element.Component { this.setAttributes = this.setAttributes.bind( this ); this.maybeDeselect = this.maybeDeselect.bind( this ); this.maybeHover = this.maybeHover.bind( this ); - this.onFormatChange = this.onFormatChange.bind( this ); - this.toggleFormat = this.toggleFormat.bind( this ); this.previousOffset = null; - this.state = { - formats: {} - }; } bindBlockNode( node ) { this.node = node; } - onFormatChange( formats ) { - if ( ! this.state.hasEditable ) { - this.setState( { hasEditable: true } ); - } - - this.setState( { formats } ); - } - - toggleFormat( format ) { - const { formats } = this.state; - - this.setState( { - formats: { - ...formats, - [ format ]: ! formats[ format ] - } - } ); - } - componentWillReceiveProps( newProps ) { if ( this.props.order !== newProps.order && @@ -169,14 +128,7 @@ class VisualEditorBlock extends wp.element.Component { isActive: control.isActive( block.attributes ) } ) ) } /> ) } - { this.state.hasEditable && ( - ( { - ...control, - onClick: () => this.toggleFormat( control.format ), - isActive: !! this.state.formats[ control.format ] - } ) ) } /> - ) } + } ); diff --git a/languages/gutenberg.pot b/languages/gutenberg.pot index c55d14c19e9142..69d5764cf232ae 100644 --- a/languages/gutenberg.pot +++ b/languages/gutenberg.pot @@ -3,6 +3,18 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "X-Generator: babel-plugin-wp-i18n\n" +#: blocks/components/editable/index.js:28 +msgid "Bold" +msgstr "" + +#: blocks/components/editable/index.js:33 +msgid "Italic" +msgstr "" + +#: blocks/components/editable/index.js:38 +msgid "Strikethrough" +msgstr "" + #: blocks/library/embed/index.js:10 msgid "Embed" msgstr "" @@ -102,18 +114,6 @@ msgstr "" msgid "Publish" msgstr "" -#: editor/modes/visual-editor/block.js:17 -msgid "Bold" -msgstr "" - -#: editor/modes/visual-editor/block.js:22 -msgid "Italic" -msgstr "" - -#: editor/modes/visual-editor/block.js:27 -msgid "Strikethrough" -msgstr "" - #: editor/header/mode-switcher/index.js:24 msgctxt "Name for the Text editor tab (formerly HTML)" msgid "Text" diff --git a/package.json b/package.json index a90facfc9c60c0..48df1ab96db118 100644 --- a/package.json +++ b/package.json @@ -67,6 +67,7 @@ "react-autosize-textarea": "^0.4.2", "react-dom": "^15.5.4", "react-redux": "^5.0.4", + "react-slot-fill": "^1.0.0-alpha.11", "redux": "^3.6.0", "uuid": "^3.0.1" } diff --git a/webpack.config.js b/webpack.config.js index 2907fb75b43b83..352778f3eedfca 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -25,6 +25,13 @@ const config = { 'react-dom': 'ReactDOM', 'react-dom/server': 'ReactDOMServer' }, + resolve: { + alias: { + // There are currently resolution errors on RSF's "mitt" dependency + // when imported as native ES module + 'react-slot-fill': 'react-slot-fill/lib/rsf.js' + } + }, module: { rules: [ {