From ca4111c23b184f4852e2c9be12430df8b5c5ee72 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Tue, 28 Mar 2017 15:38:33 +0100 Subject: [PATCH] Editable: implement TinyMCE on the core Editable component (#330) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * TinyMCE: V1 of the Editable Component initializing TinyMCE * Drop the global contenteditable * Expose package globals in test build * Rename TinyMCE dependency to clarify nightly More importantly to avoid potential naming clash with existing plugins. “Nightly” might be more accurately a version description, but this is all temporary anyways. * Adding native browser spellcheck --- .eslintrc.json | 3 +- blocks/components/editable/index.js | 76 ++++++++++++++++++++++++++++- editor/blocks/text-block/index.js | 4 +- editor/editor/editor.js | 2 +- element/index.js | 17 ++++--- index.php | 3 +- package.json | 3 ++ webpack.config.js | 14 +++++- 8 files changed, 106 insertions(+), 16 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 902f6cb6acbbb2..2cb6635e3c1330 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 59fe33dcba1d7d..9a1638c4b6be31 100644 --- a/blocks/components/editable/index.js +++ b/blocks/components/editable/index.js @@ -1,3 +1,75 @@ -export default function Editable( { value } ) { - return

{ value }

; +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.bindNode = this.bindNode.bind( this ); + } + + componentDidMount() { + this.initialize(); + } + + initialize() { + const config = { + target: this.node, + theme: false, + inline: true, + toolbar: false, + browser_spellcheck: true, + 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( 'focusout', this.onChange ); + } + + onInit() { + this.editor.setContent( this.props.value ); + } + + onChange() { + if ( ! this.editor.isDirty() ) { + return; + } + const value = this.editor.getContent(); + this.editor.save(); + this.props.onChange( value ); + } + + bindNode( ref ) { + this.node = ref; + } + + updateContent() { + const bookmark = this.editor.selection.getBookmark( 2, true ); + this.editor.setContent( this.props.value ); + this.editor.selection.moveToBookmark( bookmark ); + } + + componentWillUnmount() { + if ( this.editor ) { + this.editor.destroy(); + } + } + + componentDidUpdate( prevProps ) { + if ( this.props.value !== prevProps.value ) { + this.updateContent(); + } + } + + render() { + return
; + } } diff --git a/editor/blocks/text-block/index.js b/editor/blocks/text-block/index.js index 45f5ee45d722f7..812f67ec73f9ad 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/editor/editor/editor.js b/editor/editor/editor.js index d3f7cd34ac15ca..6d2e6f315d9bd6 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 ) } diff --git a/element/index.js b/element/index.js index 3be0186c4c297f..e302593d909b7c 100644 --- a/element/index.js +++ b/element/index.js @@ -1,8 +1,8 @@ /** * External dependencies */ -import { createElement as reactCreateElement } 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,6 +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 { Component }; diff --git a/index.php b/index.php index 8e62cf3f3db0fe..06bd1181b1a849 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-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' ) ); + wp_register_script( 'wp-blocks', plugins_url( 'blocks/build/index.js', __FILE__ ), array( 'wp-element', 'tinymce-nightly' ) ); } add_action( 'init', 'gutenberg_register_scripts' ); diff --git a/package.json b/package.json index 1ed09b08fc354a..747e81b6665368 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 481729d2554d6e..3f35ff8bcd8363 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',