From 3e9d958fa633b9703d8c9484fa9920bbb7adec9b Mon Sep 17 00:00:00 2001 From: James Johnson Date: Wed, 24 May 2017 16:41:50 +1000 Subject: [PATCH 01/11] Basic freeform block --- blocks/library/freeform/index.js | 131 ++++++++++++++++++++++++++--- blocks/library/freeform/style.scss | 12 +++ 2 files changed, 130 insertions(+), 13 deletions(-) create mode 100644 blocks/library/freeform/style.scss diff --git a/blocks/library/freeform/index.js b/blocks/library/freeform/index.js index ade728732b96f7..aa4b14b126e2a4 100644 --- a/blocks/library/freeform/index.js +++ b/blocks/library/freeform/index.js @@ -1,9 +1,53 @@ +/** + * WordPress dependencies + */ +import { find } from 'lodash'; + /** * Internal dependencies */ -import { registerBlock, query, setUnknownTypeHandler } from '../../api'; +import './style.scss'; +import { registerBlock, query } from '../../api'; +import Editable from '../../editable'; + +const { children } = query; + +function execCommand( command ) { + return ( { editor } ) => { + if ( editor ) { + editor.execCommand( command ); + } + }; +} + +function blockQuoteIsActive() { + return ( { inBlockQuote } ) => { + console.log( 'inQuote:', inBlockQuote ); + return inBlockQuote; + }; +} + +function listIsActive( expectedListType ) { + return ( { listType } ) => { + const inList = expectedListType === listType; + console.log( 'inList:', inList ); + return inList; + }; +} -const { html } = query; +function findParentList( { parents } ) { + const list = find( parents, ( node ) => node.nodeName === 'UL' || node.nodeName === 'OL' ); + const listType = list ? list.nodeName : null; + console.log( 'listType:', listType ); + return listType; +} + +function findParentQuote( { parents } ) { + const quote = find( parents, ( node ) => node.nodeName === 'BLOCKQUOTE' ); + const inBlockQuote = ! ! quote; + console.log( 'inBlockQuote:', inBlockQuote ); + return inBlockQuote; +} registerBlock( 'core/freeform', { title: wp.i18n.__( 'Freeform' ), @@ -13,23 +57,84 @@ registerBlock( 'core/freeform', { category: 'common', attributes: { - html: html(), + content: children(), + }, + + defaultAttributes: { + content:

, + listType: null, + inBlockQuote: false, }, - edit( { attributes } ) { + controls: [ + { + icon: 'editor-quote', + title: wp.i18n.__( 'Quote' ), + isActive: blockQuoteIsActive(), + onClick: execCommand( 'mceBlockQuote' ), + }, + { + icon: 'editor-ul', + title: wp.i18n.__( 'Convert to unordered' ), + isActive: listIsActive( 'UL' ), + onClick: execCommand( 'InsertUnorderedList' ), + }, + { + icon: 'editor-ol', + title: wp.i18n.__( 'Convert to ordered' ), + isActive: listIsActive( 'OL' ), + onClick: execCommand( 'InsertOrderedList' ), + }, + ], + + advControls: [ + { + icon: 'editor-outdent', + title: wp.i18n.__( 'Outdent list item' ), + onClick: execCommand( 'Outdent' ), + }, + { + icon: 'editor-indent', + title: wp.i18n.__( 'Indent list item' ), + onClick: execCommand( 'Indent' ), + }, + ], + + edit( { attributes, setAttributes, focus, setFocus } ) { + const { content } = attributes; + return ( -

- { attributes.html } -
+ ( { + ...settings, + plugins: ( settings.plugins || [] ).concat( 'lists' ), + } ) } + onSetup={ ( editor ) => { + editor.on( 'nodeChange', ( nodeInfo ) => { + console.log( 'nodeChange ', nodeInfo ); + setAttributes( { + listType: findParentList( nodeInfo ), + inBlockQuote: findParentQuote( nodeInfo ), + } ); + } ); + setAttributes( { editor } ); + } } + onChange={ ( nextContent ) => { + setAttributes( { + content: nextContent, + } ); + } } + focus={ focus } + onFocus={ setFocus } + showAlignments + /> ); }, save( { attributes } ) { - return attributes.html; + const { content } = attributes; + return content; }, } ); - -setUnknownTypeHandler( 'core/freeform' ); diff --git a/blocks/library/freeform/style.scss b/blocks/library/freeform/style.scss new file mode 100644 index 00000000000000..2c0a73a1c9ae74 --- /dev/null +++ b/blocks/library/freeform/style.scss @@ -0,0 +1,12 @@ +.blocks-freeform .blocks-editable__tinymce { + ul, ol { + padding-left: 2.5em; + margin-left: 0; + } + blockquote { + margin: 0; + box-shadow: inset 0px 0px 0px 0px $light-gray-500; + border-left: 4px solid $black; + padding-left: 1em; + } +} \ No newline at end of file From 9a893fde01fac71f71438af229a3e9cd6fb9973a Mon Sep 17 00:00:00 2001 From: James Johnson Date: Wed, 24 May 2017 17:03:16 +1000 Subject: [PATCH 02/11] Re-add unknown type handler; remove console logs; cleanup --- blocks/library/freeform/index.js | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/blocks/library/freeform/index.js b/blocks/library/freeform/index.js index aa4b14b126e2a4..96ae6c8a6375df 100644 --- a/blocks/library/freeform/index.js +++ b/blocks/library/freeform/index.js @@ -7,7 +7,7 @@ import { find } from 'lodash'; * Internal dependencies */ import './style.scss'; -import { registerBlock, query } from '../../api'; +import { registerBlock, query, setUnknownTypeHandler } from '../../api'; import Editable from '../../editable'; const { children } = query; @@ -22,31 +22,24 @@ function execCommand( command ) { function blockQuoteIsActive() { return ( { inBlockQuote } ) => { - console.log( 'inQuote:', inBlockQuote ); return inBlockQuote; }; } function listIsActive( expectedListType ) { return ( { listType } ) => { - const inList = expectedListType === listType; - console.log( 'inList:', inList ); - return inList; + return expectedListType === listType; }; } -function findParentList( { parents } ) { +function findListType( { parents } ) { const list = find( parents, ( node ) => node.nodeName === 'UL' || node.nodeName === 'OL' ); - const listType = list ? list.nodeName : null; - console.log( 'listType:', listType ); - return listType; + return list ? list.nodeName : null; } -function findParentQuote( { parents } ) { +function findInBlockQuote( { parents } ) { const quote = find( parents, ( node ) => node.nodeName === 'BLOCKQUOTE' ); - const inBlockQuote = ! ! quote; - console.log( 'inBlockQuote:', inBlockQuote ); - return inBlockQuote; + return ! ! quote; } registerBlock( 'core/freeform', { @@ -113,10 +106,9 @@ registerBlock( 'core/freeform', { } ) } onSetup={ ( editor ) => { editor.on( 'nodeChange', ( nodeInfo ) => { - console.log( 'nodeChange ', nodeInfo ); setAttributes( { - listType: findParentList( nodeInfo ), - inBlockQuote: findParentQuote( nodeInfo ), + listType: findListType( nodeInfo ), + inBlockQuote: findInBlockQuote( nodeInfo ), } ); } ); setAttributes( { editor } ); @@ -138,3 +130,5 @@ registerBlock( 'core/freeform', { return content; }, } ); + +setUnknownTypeHandler( 'core/freeform' ); From d3a6292b9e554f897451c795cfcf62f828965f33 Mon Sep 17 00:00:00 2001 From: James Johnson Date: Thu, 25 May 2017 13:42:11 +1000 Subject: [PATCH 03/11] Replaced spaces with tabs --- blocks/library/freeform/style.scss | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/blocks/library/freeform/style.scss b/blocks/library/freeform/style.scss index 2c0a73a1c9ae74..694c7755451b29 100644 --- a/blocks/library/freeform/style.scss +++ b/blocks/library/freeform/style.scss @@ -1,12 +1,12 @@ .blocks-freeform .blocks-editable__tinymce { - ul, ol { - padding-left: 2.5em; - margin-left: 0; - } - blockquote { - margin: 0; - box-shadow: inset 0px 0px 0px 0px $light-gray-500; - border-left: 4px solid $black; - padding-left: 1em; - } + ul, ol { + padding-left: 2.5em; + margin-left: 0; + } + blockquote { + margin: 0; + box-shadow: inset 0px 0px 0px 0px $light-gray-500; + border-left: 4px solid $black; + padding-left: 1em; + } } \ No newline at end of file From 73984d0b89d3768eeb1eb4ae1d537cd0d39a6046 Mon Sep 17 00:00:00 2001 From: James Johnson Date: Fri, 26 May 2017 14:51:03 +1000 Subject: [PATCH 04/11] Avoid outputting internal values in serialized form --- blocks/library/freeform/index.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/blocks/library/freeform/index.js b/blocks/library/freeform/index.js index 96ae6c8a6375df..d7c81898c77f58 100644 --- a/blocks/library/freeform/index.js +++ b/blocks/library/freeform/index.js @@ -42,6 +42,12 @@ function findInBlockQuote( { parents } ) { return ! ! quote; } +function internalValue( defaultValue ) { + const thunk = () => defaultValue; + thunk._wpBlocksKnownMatcher = true; + return thunk; +} + registerBlock( 'core/freeform', { title: wp.i18n.__( 'Freeform' ), @@ -51,12 +57,16 @@ registerBlock( 'core/freeform', { attributes: { content: children(), + listType: internalValue( null ), + inBlockQuote: internalValue( false ), + editor: internalValue( null ), }, defaultAttributes: { content:

, listType: null, inBlockQuote: false, + editor: null, }, controls: [ From ace3c3c17d7ba44533fff0b45ef79440eaf9fd78 Mon Sep 17 00:00:00 2001 From: James Johnson Date: Fri, 26 May 2017 14:51:47 +1000 Subject: [PATCH 05/11] Update core-freeform serialization test --- blocks/test/fixtures/core-freeform.json | 25 ++++++++++++++++++- .../fixtures/core-freeform.serialized.html | 2 +- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/blocks/test/fixtures/core-freeform.json b/blocks/test/fixtures/core-freeform.json index cf8daa0bf85abf..d4d8f00a3c2e44 100644 --- a/blocks/test/fixtures/core-freeform.json +++ b/blocks/test/fixtures/core-freeform.json @@ -3,7 +3,30 @@ "uid": "_uid_0", "blockType": "core/freeform", "attributes": { - "html": "Testing freeform block with some

HTML content
" + "content": [ + "Testing freeform block with some", + { + "attributes": { + "className": "wp-some-class" + }, + "children": [ + "HTML ", + { + "attributes": { + "style": { + "color": "red" + } + }, + "children": "content", + "type": "span" + } + ], + "type": "div" + } + ], + "editor": null, + "inBlockQuote": false, + "listType": null } } ] diff --git a/blocks/test/fixtures/core-freeform.serialized.html b/blocks/test/fixtures/core-freeform.serialized.html index d93c2e8ceb4c0e..4ce56fd27af8e9 100644 --- a/blocks/test/fixtures/core-freeform.serialized.html +++ b/blocks/test/fixtures/core-freeform.serialized.html @@ -1,5 +1,5 @@ Testing freeform block with some -
HTML content
+
HTML content
From c40f1842e1c9159423681109937bb8118d3e2c59 Mon Sep 17 00:00:00 2001 From: James Johnson Date: Wed, 31 May 2017 15:27:12 +1000 Subject: [PATCH 06/11] Moved internal state out of attributes into react component private state --- blocks/library/freeform/freeform-block.js | 123 ++++++++++++++++++++++ blocks/library/freeform/index.js | 101 +----------------- blocks/test/fixtures/core-freeform.json | 5 +- 3 files changed, 127 insertions(+), 102 deletions(-) create mode 100644 blocks/library/freeform/freeform-block.js diff --git a/blocks/library/freeform/freeform-block.js b/blocks/library/freeform/freeform-block.js new file mode 100644 index 00000000000000..b293f65dda0b75 --- /dev/null +++ b/blocks/library/freeform/freeform-block.js @@ -0,0 +1,123 @@ +/** + * WordPress dependencies + */ +import { find } from 'lodash'; + +/** + * Internal dependencies + */ +import Editable from '../../editable'; +import BlockControls from '../../block-controls'; + +function addPluginToSettings( plugin ) { + return ( settings ) => ( { + ...settings, + plugins: ( settings.plugins || [] ).concat( plugin ), + } ); +} + +function execCommand( command ) { + return ( editor ) => { + if ( editor ) { + editor.execCommand( command ); + } + }; +} + +function blockQuoteIsActive() { + return ( props, state ) => { + const { inBlockQuote } = state; + return inBlockQuote; + }; +} + +function listIsActive( expectedListType ) { + return ( props, state ) => { + const { listType } = state; + return expectedListType === listType; + }; +} + +function findListType( { parents } ) { + const list = find( parents, ( node ) => node.nodeName === 'UL' || node.nodeName === 'OL' ); + return list ? list.nodeName : null; +} + +function findInBlockQuote( { parents } ) { + const quote = find( parents, ( node ) => node.nodeName === 'BLOCKQUOTE' ); + return ! ! quote; +} + +const FREEFORM_CONTROLS = [ + { + icon: 'editor-quote', + title: wp.i18n.__( 'Quote' ), + isActive: blockQuoteIsActive(), + onClick: execCommand( 'mceBlockQuote' ), + }, + { + icon: 'editor-ul', + title: wp.i18n.__( 'Convert to unordered' ), + isActive: listIsActive( 'UL' ), + onClick: execCommand( 'InsertUnorderedList' ), + }, + { + icon: 'editor-ol', + title: wp.i18n.__( 'Convert to ordered' ), + isActive: listIsActive( 'OL' ), + onClick: execCommand( 'InsertOrderedList' ), + }, +]; + +export default class FreeformBlock extends wp.element.Component { + constructor() { + super(); + this.state = { + editor: null, + }; + this.setEditor = this.setEditor.bind( this ); + this.controls = this.controls.bind( this ); + } + + setEditor( editor ) { + editor.on( 'nodeChange', ( nodeInfo ) => { + this.setState( { + listType: findListType( nodeInfo ), + inBlockQuote: findInBlockQuote( nodeInfo ), + } ); + } ); + this.setState( { editor } ); + } + + controls() { + return FREEFORM_CONTROLS.map( ( control ) => ( { + ...control, + onClick: () => control.onClick( this.state.editor ), + isActive: control.isActive( this.props, this.state ), + } ) ); + } + + render() { + const { content, focus, onFocus, onChange } = this.props; + + return [ + focus && ( + + ), + , + ]; + } +} diff --git a/blocks/library/freeform/index.js b/blocks/library/freeform/index.js index d7c81898c77f58..ae908f92059e88 100644 --- a/blocks/library/freeform/index.js +++ b/blocks/library/freeform/index.js @@ -1,53 +1,13 @@ -/** - * WordPress dependencies - */ -import { find } from 'lodash'; /** * Internal dependencies */ import './style.scss'; import { registerBlock, query, setUnknownTypeHandler } from '../../api'; -import Editable from '../../editable'; +import FreeformBlock from './freeform-block'; const { children } = query; -function execCommand( command ) { - return ( { editor } ) => { - if ( editor ) { - editor.execCommand( command ); - } - }; -} - -function blockQuoteIsActive() { - return ( { inBlockQuote } ) => { - return inBlockQuote; - }; -} - -function listIsActive( expectedListType ) { - return ( { listType } ) => { - return expectedListType === listType; - }; -} - -function findListType( { parents } ) { - const list = find( parents, ( node ) => node.nodeName === 'UL' || node.nodeName === 'OL' ); - return list ? list.nodeName : null; -} - -function findInBlockQuote( { parents } ) { - const quote = find( parents, ( node ) => node.nodeName === 'BLOCKQUOTE' ); - return ! ! quote; -} - -function internalValue( defaultValue ) { - const thunk = () => defaultValue; - thunk._wpBlocksKnownMatcher = true; - return thunk; -} - registerBlock( 'core/freeform', { title: wp.i18n.__( 'Freeform' ), @@ -57,72 +17,18 @@ registerBlock( 'core/freeform', { attributes: { content: children(), - listType: internalValue( null ), - inBlockQuote: internalValue( false ), - editor: internalValue( null ), }, defaultAttributes: { content:

, - listType: null, - inBlockQuote: false, - editor: null, }, - controls: [ - { - icon: 'editor-quote', - title: wp.i18n.__( 'Quote' ), - isActive: blockQuoteIsActive(), - onClick: execCommand( 'mceBlockQuote' ), - }, - { - icon: 'editor-ul', - title: wp.i18n.__( 'Convert to unordered' ), - isActive: listIsActive( 'UL' ), - onClick: execCommand( 'InsertUnorderedList' ), - }, - { - icon: 'editor-ol', - title: wp.i18n.__( 'Convert to ordered' ), - isActive: listIsActive( 'OL' ), - onClick: execCommand( 'InsertOrderedList' ), - }, - ], - - advControls: [ - { - icon: 'editor-outdent', - title: wp.i18n.__( 'Outdent list item' ), - onClick: execCommand( 'Outdent' ), - }, - { - icon: 'editor-indent', - title: wp.i18n.__( 'Indent list item' ), - onClick: execCommand( 'Indent' ), - }, - ], - edit( { attributes, setAttributes, focus, setFocus } ) { const { content } = attributes; return ( - ( { - ...settings, - plugins: ( settings.plugins || [] ).concat( 'lists' ), - } ) } - onSetup={ ( editor ) => { - editor.on( 'nodeChange', ( nodeInfo ) => { - setAttributes( { - listType: findListType( nodeInfo ), - inBlockQuote: findInBlockQuote( nodeInfo ), - } ); - } ); - setAttributes( { editor } ); - } } + { setAttributes( { content: nextContent, @@ -130,7 +36,6 @@ registerBlock( 'core/freeform', { } } focus={ focus } onFocus={ setFocus } - showAlignments /> ); }, diff --git a/blocks/test/fixtures/core-freeform.json b/blocks/test/fixtures/core-freeform.json index d4d8f00a3c2e44..4a59d1049c6a2d 100644 --- a/blocks/test/fixtures/core-freeform.json +++ b/blocks/test/fixtures/core-freeform.json @@ -23,10 +23,7 @@ ], "type": "div" } - ], - "editor": null, - "inBlockQuote": false, - "listType": null + ] } } ] From d0ccb06cf8e83fc9ac31f88d742b66cb859158d7 Mon Sep 17 00:00:00 2001 From: James Johnson Date: Wed, 31 May 2017 16:04:53 +1000 Subject: [PATCH 07/11] Initialized some state --- blocks/library/freeform/freeform-block.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/blocks/library/freeform/freeform-block.js b/blocks/library/freeform/freeform-block.js index b293f65dda0b75..0df93f6cd4eee5 100644 --- a/blocks/library/freeform/freeform-block.js +++ b/blocks/library/freeform/freeform-block.js @@ -74,6 +74,8 @@ export default class FreeformBlock extends wp.element.Component { super(); this.state = { editor: null, + listType: null, + inBlockQuote: false, }; this.setEditor = this.setEditor.bind( this ); this.controls = this.controls.bind( this ); From 28fad958eda2dcd22efa2fce2d53f250e110a671 Mon Sep 17 00:00:00 2001 From: James Johnson Date: Fri, 2 Jun 2017 17:29:27 +1000 Subject: [PATCH 08/11] Try to hook into TinyMCE button state --- blocks/library/freeform/freeform-block.js | 274 ++++++++++++++++------ blocks/library/freeform/style.scss | 2 +- 2 files changed, 197 insertions(+), 79 deletions(-) diff --git a/blocks/library/freeform/freeform-block.js b/blocks/library/freeform/freeform-block.js index 0df93f6cd4eee5..d5e0654b119acf 100644 --- a/blocks/library/freeform/freeform-block.js +++ b/blocks/library/freeform/freeform-block.js @@ -1,125 +1,243 @@ /** - * WordPress dependencies + * External dependencies */ -import { find } from 'lodash'; +import { nodeListToReact } from 'dom-react'; +import { isEqual, omitBy } from 'lodash'; /** * Internal dependencies */ -import Editable from '../../editable'; +import TinyMCE from '../../editable/tinymce'; import BlockControls from '../../block-controls'; -function addPluginToSettings( plugin ) { - return ( settings ) => ( { - ...settings, - plugins: ( settings.plugins || [] ).concat( plugin ), - } ); -} - -function execCommand( command ) { - return ( editor ) => { - if ( editor ) { - editor.execCommand( command ); - } - }; -} - -function blockQuoteIsActive() { - return ( props, state ) => { - const { inBlockQuote } = state; - return inBlockQuote; - }; -} - -function listIsActive( expectedListType ) { - return ( props, state ) => { - const { listType } = state; - return expectedListType === listType; - }; -} - -function findListType( { parents } ) { - const list = find( parents, ( node ) => node.nodeName === 'UL' || node.nodeName === 'OL' ); - return list ? list.nodeName : null; -} - -function findInBlockQuote( { parents } ) { - const quote = find( parents, ( node ) => node.nodeName === 'BLOCKQUOTE' ); - return ! ! quote; -} - const FREEFORM_CONTROLS = [ { + id: 'blockquote', icon: 'editor-quote', title: wp.i18n.__( 'Quote' ), - isActive: blockQuoteIsActive(), - onClick: execCommand( 'mceBlockQuote' ), }, { + id: 'bullist', icon: 'editor-ul', title: wp.i18n.__( 'Convert to unordered' ), - isActive: listIsActive( 'UL' ), - onClick: execCommand( 'InsertUnorderedList' ), }, { + id: 'numlist', icon: 'editor-ol', title: wp.i18n.__( 'Convert to ordered' ), - isActive: listIsActive( 'OL' ), - onClick: execCommand( 'InsertOrderedList' ), + }, + { + id: 'bold', + icon: 'editor-bold', + title: wp.i18n.__( 'Bold' ), + }, + { + id: 'italic', + icon: 'editor-italic', + title: wp.i18n.__( 'Italic' ), + }, + { + id: 'strikethrough', + icon: 'editor-strikethrough', + title: wp.i18n.__( 'Strikethrough' ), + }, +]; + +const ALIGNMENT_CONTROLS = [ + { + id: 'alignleft', + icon: 'editor-alignleft', + title: wp.i18n.__( 'Align left' ), + }, + { + id: 'aligncenter', + icon: 'editor-aligncenter', + title: wp.i18n.__( 'Align center' ), + }, + { + id: 'alignright', + icon: 'editor-alignright', + title: wp.i18n.__( 'Align right' ), }, ]; +function createElement( type, props, ...children ) { + if ( props[ 'data-mce-bogus' ] === 'all' ) { + return null; + } + + if ( props.hasOwnProperty( 'data-mce-bogus' ) ) { + return children; + } + + return wp.element.createElement( + type, + omitBy( props, ( value, key ) => key.indexOf( 'data-mce-' ) === 0 ), + ...children + ); +} + export default class FreeformBlock extends wp.element.Component { - constructor() { - super(); + constructor( props ) { + super( ...arguments ); + this.getSettings = this.getSettings.bind( this ); + this.setButtonActive = this.setButtonActive.bind( this ); + this.onSetup = this.onSetup.bind( this ); + this.onInit = this.onInit.bind( this ); + this.onChange = this.onChange.bind( this ); + this.onFocus = this.onFocus.bind( this ); + this.updateFocus = this.updateFocus.bind( this ); + this.updateContent = this.updateContent.bind( this ); + this.setContent = this.setContent.bind( this ); + this.getContent = this.getContent.bind( this ); + this.controls = this.mapControls.bind( this ); + this.editor = null; + this.savedContent = null; this.state = { - editor: null, - listType: null, - inBlockQuote: false, + empty: ! props.value || ! props.value.length, + activeButtons: { }, + }; + } + + getSettings( baseSettings ) { + return { + ...baseSettings, + plugins: ( baseSettings.plugins || [] ).concat( 'lists' ), }; - this.setEditor = this.setEditor.bind( this ); - this.controls = this.controls.bind( this ); } - setEditor( editor ) { - editor.on( 'nodeChange', ( nodeInfo ) => { - this.setState( { - listType: findListType( nodeInfo ), - inBlockQuote: findInBlockQuote( nodeInfo ), - } ); + setButtonActive( id, active ) { + const activeButtons = { + ...this.state.activeButtons, + [ id ]: active, + }; + this.setState( { activeButtons } ); + } + + onSetup( editor ) { + this.editor = editor; + editor.on( 'init', this.onInit ); + editor.on( 'focusout', this.onChange ); + editor.on( 'focusin', this.onFocus ); + } + + onInit() { + FREEFORM_CONTROLS.forEach( ( control ) => { + if ( control.id ) { + const button = this.editor.buttons[ control.id ]; + button.onPostRender.call( { + active: ( isActive ) => this.setButtonActive( control.id, isActive ), + }, this.editor ); + } } ); - this.setState( { editor } ); + this.updateFocus(); + } + + onChange() { + if ( ! this.editor.isDirty() ) { + return; + } + + this.savedContent = this.getContent(); + this.editor.save(); + this.props.onChange( this.savedContent ); + } + + onFocus() { + if ( this.props.onFocus ) { + this.props.onFocus(); + } + } + + updateFocus() { + const { focus } = this.props; + if ( focus ) { + this.editor.focus(); + // Offset = -1 means we should focus the end of the editable + if ( focus.offset === -1 ) { + this.editor.selection.select( this.editor.getBody(), true ); + this.editor.selection.collapse( false ); + } + } else { + this.editor.getBody().blur(); + } + } + + updateContent() { + const bookmark = this.editor.selection.getBookmark( 2, true ); + this.savedContent = this.props.value; + this.setContent( this.savedContent ); + this.editor.selection.moveToBookmark( bookmark ); + + // Saving the editor on updates avoid unecessary onChanges calls + // These calls can make the focus jump + this.editor.save(); } - controls() { - return FREEFORM_CONTROLS.map( ( control ) => ( { + setContent( content ) { + if ( ! content ) { + content = ''; + } + + content = wp.element.renderToString( content ); + this.editor.setContent( content, { format: 'raw' } ); + } + + getContent() { + return nodeListToReact( this.editor.getBody().childNodes || [], createElement ); + } + + mapControls( controls ) { + return controls.map( ( control ) => ( { ...control, - onClick: () => control.onClick( this.state.editor ), - isActive: control.isActive( this.props, this.state ), + onClick: () => this.editor && this.editor.buttons[ control.id ].onclick(), + isActive: this.state.activeButtons[ control.id ], } ) ); } + componentWillUnmount() { + this.onChange(); + } + + componentDidUpdate( prevProps ) { + if ( this.props.focus !== prevProps.focus ) { + this.updateFocus(); + } + + // The `savedContent` var allows us to avoid updating the content right after an `onChange` call + if ( + this.props.content !== prevProps.content && + this.props.content !== this.savedContent && + ! isEqual( this.props.content, prevProps.content ) && + ! isEqual( this.props.content, this.savedContent ) + ) { + this.updateContent(); + } + } + render() { - const { content, focus, onFocus, onChange } = this.props; + const { content, focus } = this.props; return [ + focus && ( + + ), focus && ( ), - , + getSettings={ this.getSettings } + onSetup={ this.onSetup } + defaultValue={ content } + isEmpty={ this.state.empty } + />, ]; } } diff --git a/blocks/library/freeform/style.scss b/blocks/library/freeform/style.scss index 694c7755451b29..547919ab2783eb 100644 --- a/blocks/library/freeform/style.scss +++ b/blocks/library/freeform/style.scss @@ -1,4 +1,4 @@ -.blocks-freeform .blocks-editable__tinymce { +.editor-visual-editor__block[data-type="core/freeform"] .blocks-editable__tinymce { ul, ol { padding-left: 2.5em; margin-left: 0; From c49d9a1af4db17fc85e66c5527087bdfd86bfd3e Mon Sep 17 00:00:00 2001 From: James Johnson Date: Mon, 5 Jun 2017 11:28:01 +1000 Subject: [PATCH 09/11] Initilize alignment controls so the button states can be rendered --- blocks/library/freeform/freeform-block.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/blocks/library/freeform/freeform-block.js b/blocks/library/freeform/freeform-block.js index d5e0654b119acf..c2e1aae1564305 100644 --- a/blocks/library/freeform/freeform-block.js +++ b/blocks/library/freeform/freeform-block.js @@ -2,7 +2,7 @@ * External dependencies */ import { nodeListToReact } from 'dom-react'; -import { isEqual, omitBy } from 'lodash'; +import { concat, isEqual, omitBy } from 'lodash'; /** * Internal dependencies @@ -122,7 +122,7 @@ export default class FreeformBlock extends wp.element.Component { } onInit() { - FREEFORM_CONTROLS.forEach( ( control ) => { + concat( ALIGNMENT_CONTROLS, FREEFORM_CONTROLS ).forEach( ( control ) => { if ( control.id ) { const button = this.editor.buttons[ control.id ]; button.onPostRender.call( { From 58c473519d502ffebcaa114d34bc60e473e4565d Mon Sep 17 00:00:00 2001 From: James Johnson Date: Mon, 5 Jun 2017 11:29:15 +1000 Subject: [PATCH 10/11] Reorganise code --- blocks/library/freeform/freeform-block.js | 36 +++++++++++------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/blocks/library/freeform/freeform-block.js b/blocks/library/freeform/freeform-block.js index c2e1aae1564305..3c9c631aac11ca 100644 --- a/blocks/library/freeform/freeform-block.js +++ b/blocks/library/freeform/freeform-block.js @@ -10,6 +10,24 @@ import { concat, isEqual, omitBy } from 'lodash'; import TinyMCE from '../../editable/tinymce'; import BlockControls from '../../block-controls'; +const ALIGNMENT_CONTROLS = [ + { + id: 'alignleft', + icon: 'editor-alignleft', + title: wp.i18n.__( 'Align left' ), + }, + { + id: 'aligncenter', + icon: 'editor-aligncenter', + title: wp.i18n.__( 'Align center' ), + }, + { + id: 'alignright', + icon: 'editor-alignright', + title: wp.i18n.__( 'Align right' ), + }, +]; + const FREEFORM_CONTROLS = [ { id: 'blockquote', @@ -43,24 +61,6 @@ const FREEFORM_CONTROLS = [ }, ]; -const ALIGNMENT_CONTROLS = [ - { - id: 'alignleft', - icon: 'editor-alignleft', - title: wp.i18n.__( 'Align left' ), - }, - { - id: 'aligncenter', - icon: 'editor-aligncenter', - title: wp.i18n.__( 'Align center' ), - }, - { - id: 'alignright', - icon: 'editor-alignright', - title: wp.i18n.__( 'Align right' ), - }, -]; - function createElement( type, props, ...children ) { if ( props[ 'data-mce-bogus' ] === 'all' ) { return null; From 9d61284bc3d9277f9d7886b6aacd2ba0c89cd924 Mon Sep 17 00:00:00 2001 From: James Johnson Date: Mon, 5 Jun 2017 11:30:01 +1000 Subject: [PATCH 11/11] Set state based on previous state correctly --- blocks/library/freeform/freeform-block.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/blocks/library/freeform/freeform-block.js b/blocks/library/freeform/freeform-block.js index 3c9c631aac11ca..05ad3cae02ab1e 100644 --- a/blocks/library/freeform/freeform-block.js +++ b/blocks/library/freeform/freeform-block.js @@ -107,11 +107,12 @@ export default class FreeformBlock extends wp.element.Component { } setButtonActive( id, active ) { - const activeButtons = { - ...this.state.activeButtons, - [ id ]: active, - }; - this.setState( { activeButtons } ); + this.setState( ( prevState ) => ( { + activeButtons: { + ...prevState.activeButtons, + [ id ]: active, + }, + } ) ); } onSetup( editor ) { @@ -217,7 +218,6 @@ export default class FreeformBlock extends wp.element.Component { render() { const { content, focus } = this.props; - return [ focus && (