diff --git a/blocks/components/editable/index.js b/blocks/components/editable/index.js index 1018bfac28975..91aec065bfa2c 100644 --- a/blocks/components/editable/index.js +++ b/blocks/components/editable/index.js @@ -12,6 +12,7 @@ import 'element-closest'; */ import './style.scss'; import FormatToolbar from './format-toolbar'; +import TinyMCE from './tinymce'; // 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'; @@ -65,7 +66,6 @@ export default class Editable extends wp.element.Component { this.onSetup = this.onSetup.bind( this ); this.onChange = this.onChange.bind( this ); this.onNewBlock = this.onNewBlock.bind( this ); - this.bindEditorNode = this.bindEditorNode.bind( this ); this.onFocus = this.onFocus.bind( this ); this.onNodeChange = this.onNodeChange.bind( this ); this.onKeyDown = this.onKeyDown.bind( this ); @@ -77,28 +77,6 @@ export default class Editable extends wp.element.Component { }; } - componentDidMount() { - this.initialize(); - } - - initialize() { - const config = { - target: this.editorNode, - theme: false, - inline: true, - toolbar: false, - browser_spellcheck: true, - entity_encoding: 'raw', - convert_urls: false, - setup: this.onSetup, - formats: { - strikethrough: { inline: 'del' } - } - }; - - tinymce.init( config ); - } - onSetup( editor ) { this.editor = editor; editor.on( 'init', this.onInit ); @@ -110,7 +88,6 @@ export default class Editable extends wp.element.Component { } onInit() { - this.setContent( this.props.value ); this.focus(); } @@ -134,7 +111,7 @@ export default class Editable extends wp.element.Component { } getRelativePosition( node ) { - const editorPosition = this.editorNode.closest( '.editor-visual-editor__block' ).getBoundingClientRect(); + const editorPosition = this.editor.getBody().closest( '.editor-visual-editor__block' ).getBoundingClientRect(); const position = node.getBoundingClientRect(); return { top: position.top - editorPosition.top + 40 + ( position.height ), @@ -211,13 +188,10 @@ export default class Editable extends wp.element.Component { // Splitting into two blocks this.setContent( this.props.value ); - // The setTimeout fixes the focus jump to the original block - setTimeout( () => { - this.props.onSplit( - nodeListToReact( before, createElement ), - nodeListToReact( after, createElement ) - ); - } ); + this.props.onSplit( + nodeListToReact( before, createElement ), + nodeListToReact( after, createElement ) + ); } onNodeChange( { element, parents } ) { @@ -242,10 +216,6 @@ export default class Editable extends wp.element.Component { this.setState( { alignment, bookmark, formats, focusPosition } ); } - bindEditorNode( ref ) { - this.editorNode = ref; - } - updateContent() { const bookmark = this.editor.selection.getBookmark( 2, true ); this.savedContent = this.props.value; @@ -267,7 +237,7 @@ export default class Editable extends wp.element.Component { } getContent() { - return nodeListToReact( this.editorNode.childNodes || [], createElement ); + return nodeListToReact( this.editor.getBody().childNodes || [], createElement ); } focus() { @@ -366,14 +336,16 @@ export default class Editable extends wp.element.Component { } render() { - const { tagName: Tag = 'div', style, focus, className, showAlignments = false, formattingControls } = this.props; + const { tagName, style, value, focus, className, showAlignments = false, formattingControls } = this.props; const classes = classnames( 'blocks-editable', className ); let element = ( - ); diff --git a/blocks/components/editable/tinymce.js b/blocks/components/editable/tinymce.js new file mode 100644 index 0000000000000..b20039428cb04 --- /dev/null +++ b/blocks/components/editable/tinymce.js @@ -0,0 +1,49 @@ +export default class TinyMCE extends wp.element.Component { + componentDidMount() { + tinymce.init( { + target: this.editorNode, + theme: false, + inline: true, + toolbar: false, + browser_spellcheck: true, + entity_encoding: 'raw', + convert_urls: false, + setup: this.props.onSetup, + formats: { + strikethrough: { inline: 'del' } + } + } ); + + if ( this.props.focus ) { + this.editorNode.focus(); + } + } + + shouldComponentUpdate() { + // We must prevent rerenders because TinyMCE will modify the DOM, thus + // breaking React's ability to reconcile changes. + // + // See: https://github.com/facebook/react/issues/6802 + return false; + } + + render() { + const { tagName = 'div', style, className, defaultValue } = this.props; + + // If a default value is provided, render it into the DOM even before + // TinyMCE finishes initializing. This avoids a short delay by allowing + // us to show and focus the content before it's truly ready to edit. + let children; + if ( defaultValue ) { + children = wp.element.Children.toArray( defaultValue ); + } + + return wp.element.createElement( tagName, { + ref: ( node ) => this.editorNode = node, + contentEditable: true, + suppressContentEditableWarning: true, + style, + className + }, children ); + } +} diff --git a/editor/modes/visual-editor/block.js b/editor/modes/visual-editor/block.js index ba97c8ef68d0e..d3cc4f52b0405 100644 --- a/editor/modes/visual-editor/block.js +++ b/editor/modes/visual-editor/block.js @@ -20,6 +20,7 @@ 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.maybeStartTyping = this.maybeStartTyping.bind( this ); this.mergeWithPrevious = this.mergeWithPrevious.bind( this ); this.previousOffset = null; } @@ -63,6 +64,18 @@ class VisualEditorBlock extends wp.element.Component { } } + maybeStartTyping() { + // We do not want to dispatch start typing if... + // - State value already reflects that we're typing (dispatch noise) + // - The current block is not selected (e.g. after a split occurs, + // we'll still receive the keyDown event, but the focus has since + // shifted to the newly created block) + const { isTyping, isSelected, onStartTyping } = this.props; + if ( ! isTyping && isSelected ) { + onStartTyping(); + } + } + mergeWithPrevious() { const { block, previousBlock, onFocus, replaceBlocks } = this.props; @@ -137,7 +150,7 @@ class VisualEditorBlock extends wp.element.Component { 'is-hovered': isHovered } ); - const { onSelect, onStartTyping, onHover, onMouseLeave, onFocus, onInsertAfter } = this.props; + const { onSelect, onHover, onMouseLeave, onFocus, onInsertAfter } = this.props; // Determine whether the block has props to apply to the wrapper let wrapperProps; @@ -177,7 +190,7 @@ class VisualEditorBlock extends wp.element.Component { } -
+