From d7c9bb7f8aac5eabafcf7f7331867f82b9944bd7 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Mon, 27 Mar 2017 11:36:13 +0100 Subject: [PATCH 1/8] TinyMCE: V1 of the Editable Component initializing TinyMCE --- .eslintrc.json | 3 +- blocks/components/editable/index.js | 95 ++++++++++++++++++++++++++++- editor/blocks/text-block/index.js | 4 +- element/index.js | 7 ++- index.php | 3 +- 5 files changed, 105 insertions(+), 7 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 902f6cb6acbbb..2cb6635e3c133 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -18,7 +18,8 @@ } }, "globals": { - "wp": true + "wp": true, + "tinymce": true }, "plugins": [ "react", diff --git a/blocks/components/editable/index.js b/blocks/components/editable/index.js index 59fe33dcba1d7..cfd7b04471e00 100644 --- a/blocks/components/editable/index.js +++ b/blocks/components/editable/index.js @@ -1,3 +1,94 @@ -export default function Editable( { value } ) { - return

{ value }

; +let editorInstance = 1; + +export default class Editable extends wp.element.Component { + constructor( ...args ) { + super( ...args ); + this.onInit = this.onInit.bind( this ); + this.onSetup = this.onSetup.bind( this ); + this.onChange = this.onChange.bind( this ); + this.onFocusIn = this.onFocusIn.bind( this ); + this.onFocusOut = this.onFocusOut.bind( this ); + this.id = `tinymce-instance-${ editorInstance }`; + editorInstance++; + } + + componentDidMount() { + this.initialize(); + if ( this.props.focusConfig ) { + this.focus(); + } + } + + initialize() { + const config = { + mode: 'exact', + elements: this.id, + theme: false, + inline: true, + toolbar: false, + entity_encoding: 'raw', + setup: this.onSetup, + formats: { + strikethrough: { inline: 'del' } + } + }; + + tinymce.init( config ); + } + + onSetup( editor ) { + this.editor = editor; + editor.on( 'init', this.onInit ); + editor.on( 'focusIn', this.onFocusIn ); + editor.on( 'focusout', this.onFocusOut ); + } + + onInit() { + this.editor.setContent( this.props.value ); + } + + onChange() { + const value = this.editor.getContent(); + if ( value === this.props.value ) { + return; + } + + this.props.onChange( value ); + } + + onFocusIn() { + this.isFocused = true; + } + + onFocusOut() { + this.isFocused = false; + this.onChange(); + } + + updateContent() { + let bookmark; + if ( this.isFocused ) { + bookmark = this.editor.selection.getBookmark( 2, true ); + } + this.editor.setContent( this.props.value ); + if ( this.isFocused ) { + this.editor.selection.moveToBookmark( bookmark ); + } + } + + componentWillUnmount() { + if ( this.editor ) { + this.editor.destroy(); + } + } + + componentDidUpdate( prevProps ) { + if ( this.props.value !== prevProps.value ) { + this.updateContent( !! prevProps.focusConfig ); + } + } + + render() { + return
; + } } diff --git a/editor/blocks/text-block/index.js b/editor/blocks/text-block/index.js index 45f5ee45d722f..812f67ec73f9a 100644 --- a/editor/blocks/text-block/index.js +++ b/editor/blocks/text-block/index.js @@ -1,4 +1,4 @@ -const { query, html } = wp.blocks.query; +const { html } = wp.blocks.query; const Editable = wp.blocks.Editable; wp.blocks.registerBlock( 'core/text', { @@ -6,7 +6,7 @@ wp.blocks.registerBlock( 'core/text', { icon: 'text', attributes: { - value: query( 'p', html() ) + value: html() }, edit( attributes, onChange ) { diff --git a/element/index.js b/element/index.js index 3be0186c4c297..c6734e11eb2f1 100644 --- a/element/index.js +++ b/element/index.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { createElement as reactCreateElement } from 'react'; +import { createElement as reactCreateElement, Component as ReactComponent } from 'react'; import { render as reactRender } from 'react-dom'; /** @@ -28,3 +28,8 @@ export function createElement( type, props, ...children ) { export function render( element, target ) { reactRender( element, target ); } + +/** + * A base class to create WordPress Components (Refs, state and lifecycle hooks) + */ +export const Component = ReactComponent; diff --git a/index.php b/index.php index 8e62cf3f3db0f..e8565ee2050df 100644 --- a/index.php +++ b/index.php @@ -40,8 +40,9 @@ function gutenberg_register_scripts() { wp_register_script( 'react-dom', 'https://unpkg.com/react-dom@15/dist/react-dom' . $suffix . '.js', array( 'react' ) ); // Editor + wp_register_script( 'tinymce_js', includes_url( 'js/tinymce/' ) . 'wp-tinymce.php', array( 'jquery' ) ); wp_register_script( 'wp-element', plugins_url( 'element/build/index.js', __FILE__ ), array( 'react', 'react-dom' ) ); - wp_register_script( 'wp-blocks', plugins_url( 'blocks/build/index.js', __FILE__ ), array( 'wp-element' ) ); + wp_register_script( 'wp-blocks', plugins_url( 'blocks/build/index.js', __FILE__ ), array( 'wp-element', 'tinymce_js' ) ); } add_action( 'init', 'gutenberg_register_scripts' ); From 4b7a51a9ff49223dc27cc02b97aacafb3660e040 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Tue, 28 Mar 2017 09:54:44 +0100 Subject: [PATCH 2/8] Changes per review --- blocks/components/editable/index.js | 29 +++++++++++++---------------- element/index.js | 14 +++++--------- index.php | 2 +- 3 files changed, 19 insertions(+), 26 deletions(-) diff --git a/blocks/components/editable/index.js b/blocks/components/editable/index.js index cfd7b04471e00..1cf8d7d0fc3ea 100644 --- a/blocks/components/editable/index.js +++ b/blocks/components/editable/index.js @@ -1,28 +1,21 @@ -let editorInstance = 1; - export default class Editable extends wp.element.Component { - constructor( ...args ) { - super( ...args ); + constructor() { + super( ...arguments ); this.onInit = this.onInit.bind( this ); this.onSetup = this.onSetup.bind( this ); this.onChange = this.onChange.bind( this ); this.onFocusIn = this.onFocusIn.bind( this ); this.onFocusOut = this.onFocusOut.bind( this ); - this.id = `tinymce-instance-${ editorInstance }`; - editorInstance++; + this.bindNode = this.bindNode.bind( this ); } componentDidMount() { this.initialize(); - if ( this.props.focusConfig ) { - this.focus(); - } } initialize() { const config = { - mode: 'exact', - elements: this.id, + target: this.node, theme: false, inline: true, toolbar: false, @@ -48,11 +41,11 @@ export default class Editable extends wp.element.Component { } onChange() { - const value = this.editor.getContent(); - if ( value === this.props.value ) { + if ( ! this.editor.isDirty() ) { return; } - + const value = this.editor.getContent(); + this.editor.save(); this.props.onChange( value ); } @@ -65,6 +58,10 @@ export default class Editable extends wp.element.Component { this.onChange(); } + bindNode( ref ) { + this.node = ref; + } + updateContent() { let bookmark; if ( this.isFocused ) { @@ -84,11 +81,11 @@ export default class Editable extends wp.element.Component { componentDidUpdate( prevProps ) { if ( this.props.value !== prevProps.value ) { - this.updateContent( !! prevProps.focusConfig ); + this.updateContent(); } } render() { - return
; + return
; } } diff --git a/element/index.js b/element/index.js index c6734e11eb2f1..e302593d909b7 100644 --- a/element/index.js +++ b/element/index.js @@ -1,8 +1,8 @@ /** * External dependencies */ -import { createElement as reactCreateElement, Component as ReactComponent } from 'react'; -import { render as reactRender } from 'react-dom'; +import { createElement, Component } from 'react'; +import { render } from 'react-dom'; /** * Returns a new element of given type. Type can be either a string tag name or @@ -15,9 +15,7 @@ import { render as reactRender } from 'react-dom'; * @param {...wp.Element} children Descendant elements * @return {wp.Element} Element */ -export function createElement( type, props, ...children ) { - return reactCreateElement( type, props, ...children ); -} +export { createElement }; /** * Renders a given element into the target DOM node. @@ -25,11 +23,9 @@ export function createElement( type, props, ...children ) { * @param {wp.Element} element Element to render * @param {Element} target DOM node into which element should be rendered */ -export function render( element, target ) { - reactRender( element, target ); -} +export { render }; /** * A base class to create WordPress Components (Refs, state and lifecycle hooks) */ -export const Component = ReactComponent; +export { Component }; diff --git a/index.php b/index.php index e8565ee2050df..160ed62127658 100644 --- a/index.php +++ b/index.php @@ -40,7 +40,7 @@ function gutenberg_register_scripts() { wp_register_script( 'react-dom', 'https://unpkg.com/react-dom@15/dist/react-dom' . $suffix . '.js', array( 'react' ) ); // Editor - wp_register_script( 'tinymce_js', includes_url( 'js/tinymce/' ) . 'wp-tinymce.php', array( 'jquery' ) ); + wp_register_script( 'tinymce_js', 'https://fiddle.azurewebsites.net/tinymce/nightly/tinymce.min.js' ); wp_register_script( 'wp-element', plugins_url( 'element/build/index.js', __FILE__ ), array( 'react', 'react-dom' ) ); wp_register_script( 'wp-blocks', plugins_url( 'blocks/build/index.js', __FILE__ ), array( 'wp-element', 'tinymce_js' ) ); } From a6bfbcf444af3969cfa788e994a55dd266efbcc9 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Tue, 28 Mar 2017 11:54:05 +0100 Subject: [PATCH 3/8] Mock Component to fix unit tests --- bootstrap-test.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/bootstrap-test.js b/bootstrap-test.js index 76c25f0d125ab..57933689bae03 100644 --- a/bootstrap-test.js +++ b/bootstrap-test.js @@ -13,3 +13,8 @@ global.document = require( 'jsdom' ).jsdom( '', { } ); global.window = document.defaultView; global.navigator = window.navigator; +global.wp = { + element: { + Component: function() {} + } +}; From cf04a330fc1206e45ec8aca271511d0c5de25eb6 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Tue, 28 Mar 2017 14:49:57 +0100 Subject: [PATCH 4/8] Drop the global contenteditable --- editor/editor/editor.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/editor/editor/editor.js b/editor/editor/editor.js index d3f7cd34ac15c..6d2e6f315d9bd 100644 --- a/editor/editor/editor.js +++ b/editor/editor/editor.js @@ -6,7 +6,7 @@ import InserterButton from '../inserter/button'; const Editor = ( { state: { blocks, inserter }, toggleInserter } ) => { return (
-
+
{ blocks.map( ( block, index ) =>
{ wp.blocks.getBlockSettings( block.blockType ).edit( block.attributes ) } From f80239f19138aee4802dfc64a967930f9f95b48a Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Tue, 28 Mar 2017 14:53:56 +0100 Subject: [PATCH 5/8] Remove the `isFocused` check --- blocks/components/editable/index.js | 23 +++-------------------- 1 file changed, 3 insertions(+), 20 deletions(-) diff --git a/blocks/components/editable/index.js b/blocks/components/editable/index.js index 1cf8d7d0fc3ea..ba5614f5e649e 100644 --- a/blocks/components/editable/index.js +++ b/blocks/components/editable/index.js @@ -4,8 +4,6 @@ export default class Editable extends wp.element.Component { this.onInit = this.onInit.bind( this ); this.onSetup = this.onSetup.bind( this ); this.onChange = this.onChange.bind( this ); - this.onFocusIn = this.onFocusIn.bind( this ); - this.onFocusOut = this.onFocusOut.bind( this ); this.bindNode = this.bindNode.bind( this ); } @@ -32,8 +30,7 @@ export default class Editable extends wp.element.Component { onSetup( editor ) { this.editor = editor; editor.on( 'init', this.onInit ); - editor.on( 'focusIn', this.onFocusIn ); - editor.on( 'focusout', this.onFocusOut ); + editor.on( 'focusout', this.onChange ); } onInit() { @@ -49,28 +46,14 @@ export default class Editable extends wp.element.Component { this.props.onChange( value ); } - onFocusIn() { - this.isFocused = true; - } - - onFocusOut() { - this.isFocused = false; - this.onChange(); - } - bindNode( ref ) { this.node = ref; } updateContent() { - let bookmark; - if ( this.isFocused ) { - bookmark = this.editor.selection.getBookmark( 2, true ); - } + const bookmark = this.editor.selection.getBookmark( 2, true ); this.editor.setContent( this.props.value ); - if ( this.isFocused ) { - this.editor.selection.moveToBookmark( bookmark ); - } + this.editor.selection.moveToBookmark( bookmark ); } componentWillUnmount() { From 97ba14365a07f23b3f556a9ddbfbc910e0cacadd Mon Sep 17 00:00:00 2001 From: Andrew Duthie Date: Tue, 28 Mar 2017 10:05:57 -0400 Subject: [PATCH 6/8] Expose package globals in test build --- bootstrap-test.js | 5 ----- package.json | 3 +++ webpack.config.js | 14 +++++++++++++- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/bootstrap-test.js b/bootstrap-test.js index 57933689bae03..76c25f0d125ab 100644 --- a/bootstrap-test.js +++ b/bootstrap-test.js @@ -13,8 +13,3 @@ global.document = require( 'jsdom' ).jsdom( '', { } ); global.window = document.defaultView; global.navigator = window.navigator; -global.wp = { - element: { - Component: function() {} - } -}; diff --git a/package.json b/package.json index 1ed09b08fc354..747e81b666536 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "eslint-config-wordpress": "^1.1.0", "eslint-plugin-jsx-a11y": "^4.0.0", "eslint-plugin-react": "^6.10.3", + "expose-loader": "^0.7.3", "extract-text-webpack-plugin": "^2.1.0", "glob": "^7.1.1", "jsdom": "^9.12.0", @@ -42,6 +43,8 @@ "pegjs-loader": "^0.5.1", "postcss-loader": "^1.3.3", "raw-loader": "^0.5.1", + "react": "^15.4.2", + "react-dom": "^15.4.2", "sass-loader": "^6.0.3", "sinon": "^2.1.0", "sinon-chai": "^2.9.0", diff --git a/webpack.config.js b/webpack.config.js index 481729d2554d6..3f35ff8bcd836 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -74,7 +74,19 @@ switch ( process.env.NODE_ENV ) { case 'test': config.target = 'node'; - config.entry = glob.sync( `./{${ Object.keys( config.entry ).join() }}/test/*.js` ); + config.module.rules = [ + ...config.module.rules, + ...[ 'element', 'blocks', 'editor' ].map( ( entry ) => ( { + test: require.resolve( './' + entry + '/index.js' ), + use: 'expose-loader?wp.' + entry + } ) ) + ]; + config.entry = [ + './element/index.js', + './blocks/index.js', + './editor/index.js', + ...glob.sync( `./{${ Object.keys( config.entry ).join() }}/test/*.js` ) + ]; config.externals = [ require( 'webpack-node-externals' )() ]; config.output = { filename: 'build/test.js', From 84f31beccd03bc010196bf2892252f50a51dab4d Mon Sep 17 00:00:00 2001 From: Andrew Duthie Date: Tue, 28 Mar 2017 10:17:50 -0400 Subject: [PATCH 7/8] Rename TinyMCE dependency to clarify nightly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit More importantly to avoid potential naming clash with existing plugins. “Nightly” might be more accurately a version description, but this is all temporary anyways. --- index.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.php b/index.php index 160ed62127658..06bd1181b1a84 100644 --- a/index.php +++ b/index.php @@ -40,9 +40,9 @@ function gutenberg_register_scripts() { wp_register_script( 'react-dom', 'https://unpkg.com/react-dom@15/dist/react-dom' . $suffix . '.js', array( 'react' ) ); // Editor - wp_register_script( 'tinymce_js', 'https://fiddle.azurewebsites.net/tinymce/nightly/tinymce.min.js' ); + wp_register_script( 'tinymce-nightly', 'https://fiddle.azurewebsites.net/tinymce/nightly/tinymce.min.js' ); wp_register_script( 'wp-element', plugins_url( 'element/build/index.js', __FILE__ ), array( 'react', 'react-dom' ) ); - wp_register_script( 'wp-blocks', plugins_url( 'blocks/build/index.js', __FILE__ ), array( 'wp-element', 'tinymce_js' ) ); + wp_register_script( 'wp-blocks', plugins_url( 'blocks/build/index.js', __FILE__ ), array( 'wp-element', 'tinymce-nightly' ) ); } add_action( 'init', 'gutenberg_register_scripts' ); From b33ed3a6da1d9ba53bc715d9491c9c63bf45fada Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Tue, 28 Mar 2017 15:28:40 +0100 Subject: [PATCH 8/8] Adding native browser spellcheck --- blocks/components/editable/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/blocks/components/editable/index.js b/blocks/components/editable/index.js index ba5614f5e649e..9a1638c4b6be3 100644 --- a/blocks/components/editable/index.js +++ b/blocks/components/editable/index.js @@ -17,6 +17,7 @@ export default class Editable extends wp.element.Component { theme: false, inline: true, toolbar: false, + browser_spellcheck: true, entity_encoding: 'raw', setup: this.onSetup, formats: {