From 4d36e98b5f7d757ab72038cf03a0f4c310362185 Mon Sep 17 00:00:00 2001 From: Andrew Duthie Date: Thu, 20 Apr 2017 11:25:22 -0400 Subject: [PATCH 1/5] Add children query matcher using HtmlToReact --- blocks/api/index.js | 2 +- blocks/api/query.js | 13 +++++++++++++ blocks/api/test/query.js | 28 ++++++++++++++++++++++++++++ package.json | 1 + 4 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 blocks/api/query.js create mode 100644 blocks/api/test/query.js diff --git a/blocks/api/index.js b/blocks/api/index.js index be8351e5ca643a..5d010d41932d7e 100644 --- a/blocks/api/index.js +++ b/blocks/api/index.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import * as query from 'hpq'; +import * as query from './query'; export { query }; export { createBlock, switchToBlockType } from './factory'; diff --git a/blocks/api/query.js b/blocks/api/query.js new file mode 100644 index 00000000000000..cc058dbd447389 --- /dev/null +++ b/blocks/api/query.js @@ -0,0 +1,13 @@ +/** + * External dependencies + */ +import { flow } from 'lodash'; +import { html } from 'hpq'; +import { Parser } from 'html-to-react'; + +export * from 'hpq'; + +const parser = new Parser(); +export function children( selector ) { + return flow( html( selector ), parser.parse ); +} diff --git a/blocks/api/test/query.js b/blocks/api/test/query.js new file mode 100644 index 00000000000000..4f1fa45472af62 --- /dev/null +++ b/blocks/api/test/query.js @@ -0,0 +1,28 @@ +/** + * External dependencies + */ +import { expect } from 'chai'; + +/** + * Internal dependencies + */ +import { parse, children } from '../query'; + +describe( 'query', () => { + describe( 'children()', () => { + it( 'should return a matcher function', () => { + const matcher = children(); + + expect( matcher ).to.be.a( 'function' ); + } ); + + it( 'should return HTML equivalent WPElement of matched element', () => { + // Assumption here is that we can cleanly convert back and forth + // between a string and WPElement representation + const html = '

A delicious sundae dessert

'; + const match = parse( html, children() ); + + expect( wp.element.renderToString( match ) ).to.equal( html ); + } ); + } ); +} ); diff --git a/package.json b/package.json index 4e8c7c33df4859..1cbc592e0ebf84 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,7 @@ "dependencies": { "classnames": "^2.2.5", "hpq": "^1.2.0", + "html-to-react": "^1.2.7", "jed": "^1.1.1", "lodash": "^4.17.4", "react": "^15.5.4", From 479d01dc3c3f14ac031cc75eb7e94ed9a5758e1d Mon Sep 17 00:00:00 2001 From: Andrew Duthie Date: Thu, 20 Apr 2017 11:36:11 -0400 Subject: [PATCH 2/5] Enhance renderToString to handle more incoming value types --- element/index.js | 41 +++++++++++++++++++++++++++++++++-------- element/test/index.js | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 8 deletions(-) create mode 100644 element/test/index.js diff --git a/element/index.js b/element/index.js index 88399cb74c390e..6459c1c124423b 100644 --- a/element/index.js +++ b/element/index.js @@ -1,9 +1,9 @@ /** * External dependencies */ -import { createElement, Component } from 'react'; +import { createElement, Component, cloneElement } from 'react'; import { render } from 'react-dom'; -import ReactDOMServer from 'react-dom/server'; +import { renderToStaticMarkup } from 'react-dom/server'; /** * Returns a new element of given type. Type can be either a string tag name or @@ -13,16 +13,16 @@ import ReactDOMServer from 'react-dom/server'; * @param {Object} props Element properties, either attribute * set to apply to DOM node or values to * pass through to element creator - * @param {...wp.Element} children Descendant elements - * @return {wp.Element} Element + * @param {...WPElement} children Descendant elements + * @return {WPElement} Element */ export { createElement }; /** * Renders a given element into the target DOM node. * - * @param {wp.Element} element Element to render - * @param {Element} target DOM node into which element should be rendered + * @param {WPElement} element Element to render + * @param {Element} target DOM node into which element should be rendered */ export { render }; @@ -31,10 +31,35 @@ export { render }; */ export { Component }; +/** + * Creates a copy of an element with extended props. + * + * @param {WPElement} element Element + * @param {?Object} props Props to apply to cloned element + * @return {WPElement} Cloned element + */ +export { cloneElement }; + /** * Renders a given element into a string * - * @param {wp.Element} element Element to render + * @param {WPElement} element Element to render * @return {String} HTML */ -export const renderToString = ReactDOMServer.renderToStaticMarkup; +export function renderToString( element ) { + if ( ! element ) { + return ''; + } + + if ( 'string' === typeof element ) { + return element; + } + + if ( Array.isArray( element ) ) { + return renderToStaticMarkup( + createElement( 'div', null, ...element ) + ).slice( 5, -6 ); + } + + return renderToStaticMarkup( element ); +} diff --git a/element/test/index.js b/element/test/index.js new file mode 100644 index 00000000000000..333acd3ffb9ee6 --- /dev/null +++ b/element/test/index.js @@ -0,0 +1,38 @@ +/** + * External dependencies + */ +import { expect } from 'chai'; + +/** + * Internal dependencies + */ +import { createElement, renderToString } from '../'; + +describe( 'element', () => { + describe( 'renderToString', () => { + it( 'should return an empty string for a falsey value', () => { + expect( renderToString() ).to.equal( '' ); + expect( renderToString( false ) ).to.equal( '' ); + expect( renderToString( null ) ).to.equal( '' ); + expect( renderToString( 0 ) ).to.equal( '' ); + } ); + + it( 'should return a string verbatim', () => { + expect( renderToString( 'Zucchini' ) ).to.equal( 'Zucchini' ); + } ); + + it( 'should return a string from an array', () => { + expect( renderToString( [ + 'Zucchini ', + createElement( 'em', null, 'is a' ), + ' summer squash' + ] ) ).to.equal( 'Zucchini is a summer squash' ); + } ); + + it( 'should return a string from an element', () => { + expect( renderToString( + createElement( 'strong', null, 'Courgette' ) + ) ).to.equal( 'Courgette' ); + } ); + } ); +} ); From fbae3767409e8fd95364b731d777fd381ea951bd Mon Sep 17 00:00:00 2001 From: Andrew Duthie Date: Thu, 20 Apr 2017 11:36:46 -0400 Subject: [PATCH 3/5] Manage Editable value as React element --- blocks/components/editable/index.js | 39 +++++++++++++---- blocks/library/embed/index.js | 6 +-- blocks/library/heading/index.js | 10 ++--- blocks/library/image/index.js | 6 +-- blocks/library/list/index.js | 22 ++++++---- blocks/library/quote/index.js | 35 ++++++--------- blocks/library/text/index.js | 68 +++++++++++++++++------------ languages/gutenberg.pot | 12 ++--- post-content.js | 7 +-- 9 files changed, 119 insertions(+), 86 deletions(-) diff --git a/blocks/components/editable/index.js b/blocks/components/editable/index.js index ee315966e6e301..f293232a7caa58 100644 --- a/blocks/components/editable/index.js +++ b/blocks/components/editable/index.js @@ -3,12 +3,15 @@ */ import classnames from 'classnames'; import { last } from 'lodash'; +import { Parser as HtmlToReactParser } from 'html-to-react'; /** * Internal dependencies */ import './style.scss'; +const htmlToReactParser = new HtmlToReactParser(); + export default class Editable extends wp.element.Component { constructor() { super( ...arguments ); @@ -50,8 +53,7 @@ export default class Editable extends wp.element.Component { } onInit() { - const { value = '' } = this.props; - this.editor.setContent( value ); + this.setContent( this.props.value ); this.focus(); } @@ -68,9 +70,9 @@ export default class Editable extends wp.element.Component { if ( ! this.editor.isDirty() ) { return; } - const value = this.editor.getContent(); + this.editor.save(); - this.props.onChange( value ); + this.props.onChange( this.getContent() ); } onNewBlock() { @@ -100,15 +102,21 @@ export default class Editable extends wp.element.Component { return; } const before = getHtml( beforeNodes.slice( 0, beforeNodes.length - 1 ) ); - const after = getHtml( childNodes.slice( splitIndex ) ); // Splitting into two blocks - this.editor.setContent( this.props.value || '' ); + this.setContent( this.props.value ); const hasAfter = !! childNodes.slice( splitIndex ) .reduce( ( memo, node ) => memo + node.textContent, '' ); + const after = hasAfter ? getHtml( childNodes.slice( splitIndex ) ) : ''; + // The setTimeout fixes the focus jump to the original block - setTimeout( () => this.props.onSplit( before, hasAfter ? after : '' ) ); + setTimeout( () => { + this.props.onSplit( + htmlToReactParser.parse( before ), + htmlToReactParser.parse( after ) + ); + } ); } bindNode( ref ) { @@ -117,13 +125,28 @@ export default class Editable extends wp.element.Component { updateContent() { const bookmark = this.editor.selection.getBookmark( 2, true ); - this.editor.setContent( this.props.value ); + this.setContent( this.props.value ); this.editor.selection.moveToBookmark( bookmark ); // Saving the editor on updates avoid unecessary onChanges calls // These calls can make the focus jump this.editor.save(); } + setContent( content ) { + if ( ! content ) { + content = ''; + } + + content = wp.element.renderToString( content ); + this.editor.setContent( content ); + } + + getContent() { + const content = this.editor.getContent(); + + return htmlToReactParser.parse( content ); + } + focus() { if ( this.props.focus ) { this.editor.focus(); diff --git a/blocks/library/embed/index.js b/blocks/library/embed/index.js index 1401b7ba542507..39473174323ecd 100644 --- a/blocks/library/embed/index.js +++ b/blocks/library/embed/index.js @@ -4,7 +4,7 @@ import { registerBlock, query } from 'api'; import Editable from 'components/editable'; -const { attr, html } = query; +const { attr, children } = query; registerBlock( 'core/embed', { title: wp.i18n.__( 'Embed' ), @@ -16,7 +16,7 @@ registerBlock( 'core/embed', { attributes: { url: attr( 'iframe', 'src' ), title: attr( 'iframe', 'title' ), - caption: html( 'figcaption' ) + caption: children( 'figcaption' ) }, edit( { attributes, isSelected, setAttributes } ) { @@ -47,7 +47,7 @@ registerBlock( 'core/embed', { return (
{ iframe } -
+
{ caption }
); } diff --git a/blocks/library/heading/index.js b/blocks/library/heading/index.js index 13ecc92c391893..b7b804cd093b4a 100644 --- a/blocks/library/heading/index.js +++ b/blocks/library/heading/index.js @@ -4,7 +4,7 @@ import { registerBlock, query } from 'api'; import Editable from 'components/editable'; -const { html, prop } = query; +const { children, prop } = query; registerBlock( 'core/heading', { title: wp.i18n.__( 'Heading' ), @@ -14,7 +14,7 @@ registerBlock( 'core/heading', { category: 'common', attributes: { - content: html( 'h1,h2,h3,h4,h5,h6' ), + content: children( 'h1,h2,h3,h4,h5,h6' ), nodeName: prop( 'h1,h2,h3,h4,h5,h6', 'nodeName' ), align: prop( 'h1,h2,h3,h4,h5,h6', 'style.textAlign' ) }, @@ -51,9 +51,9 @@ registerBlock( 'core/heading', { const Tag = nodeName.toLowerCase(); return ( - + + { content } + ); }, diff --git a/blocks/library/image/index.js b/blocks/library/image/index.js index 6b93a95e70f0bd..73a80c5ebd0e01 100644 --- a/blocks/library/image/index.js +++ b/blocks/library/image/index.js @@ -4,7 +4,7 @@ import { registerBlock, query } from 'api'; import Editable from 'components/editable'; -const { attr, html } = query; +const { attr, children } = query; registerBlock( 'core/image', { title: wp.i18n.__( 'Image' ), @@ -16,7 +16,7 @@ registerBlock( 'core/image', { attributes: { url: attr( 'img', 'src' ), alt: attr( 'img', 'alt' ), - caption: html( 'figcaption' ) + caption: children( 'figcaption' ) }, edit( { attributes, setAttributes, focus, setFocus } ) { @@ -49,7 +49,7 @@ registerBlock( 'core/image', { return (
{ img } -
+
{ caption }
); } diff --git a/blocks/library/list/index.js b/blocks/library/list/index.js index 5280974ca39c64..35ce57edcb7df6 100644 --- a/blocks/library/list/index.js +++ b/blocks/library/list/index.js @@ -5,7 +5,7 @@ import './style.scss'; import { registerBlock, query as hpq } from 'api'; import Editable from 'components/editable'; -const { html, prop, query } = hpq; +const { children, prop, query } = hpq; registerBlock( 'core/list', { title: wp.i18n.__( 'List' ), @@ -15,7 +15,7 @@ registerBlock( 'core/list', { attributes: { nodeName: prop( 'ol,ul', 'nodeName' ), items: query( 'li', { - value: html() + value: children() } ) }, @@ -56,9 +56,9 @@ registerBlock( 'core/list', { edit( { attributes, focus, setFocus } ) { const { nodeName = 'OL', items = [], align } = attributes; - const content = items.map( item => { - return `
  • ${ item.value }
  • `; - } ).join( '' ); + const content = items.map( ( item, i ) => { + return
  • { item.value }
  • ; + } ); return ( ( -
  • - ) ); - return wp.element.createElement( nodeName.toLowerCase(), null, children ); + + return wp.element.createElement( + listType.toLowerCase(), + null, + items.map( ( item, index ) => ( +
  • { item.value }
  • + ) ) + ); } } ); diff --git a/blocks/library/quote/index.js b/blocks/library/quote/index.js index 354d5e8ce59b56..b45ba97f73416b 100644 --- a/blocks/library/quote/index.js +++ b/blocks/library/quote/index.js @@ -5,10 +5,7 @@ import './style.scss'; import { registerBlock, query as hpq } from 'api'; import Editable from 'components/editable'; -const { parse, html, query, attr } = hpq; - -const fromValueToParagraphs = ( value ) => value ? value.map( ( paragraph ) => `

    ${ paragraph }

    ` ).join( '' ) : ''; -const fromParagraphsToValue = ( paragraphs ) => parse( paragraphs, query( 'p', html() ) ); +const { children, query, attr } = hpq; registerBlock( 'core/quote', { title: wp.i18n.__( 'Quote' ), @@ -16,13 +13,13 @@ registerBlock( 'core/quote', { category: 'common', attributes: { - value: query( 'blockquote > p', html() ), - citation: html( 'footer' ), + value: query( 'blockquote > p', children() ), + citation: children( 'footer' ), style: node => { const value = attr( 'blockquote', 'class' )( node ); const match = value.match( /\bblocks-quote-style-(\d+)\b/ ); return match ? +match[ 1 ] : null; - }, + } }, controls: [ 1, 2 ].map( ( variation ) => ( { @@ -42,29 +39,29 @@ registerBlock( 'core/quote', { return (
    setAttributes( { - value: fromParagraphsToValue( paragraphs ) + ( nextValue ) => setAttributes( { + value: nextValue } ) } focus={ focus && focus.editable === 'value' ? focus : null } onFocus={ () => setFocus( { editable: 'value' } ) } /> - { ( citation || !! focus ) && + { ( citation || !! focus ) && (
    setAttributes( { - citation: newValue + ( nextCitation ) => setAttributes( { + citation: nextCitation } ) } focus={ focus && focus.editable === 'citation' ? focus : null } onFocus={ () => setFocus( { editable: 'citation' } ) } />
    - } + ) }
    ); }, @@ -76,15 +73,9 @@ registerBlock( 'core/quote', { return (
    { value && value.map( ( paragraph, i ) => ( -

    +

    { paragraph }

    ) ) } -
    +
    { citation }
    ); } diff --git a/blocks/library/text/index.js b/blocks/library/text/index.js index 007e7f4a099fe8..ff505f1109bb8d 100644 --- a/blocks/library/text/index.js +++ b/blocks/library/text/index.js @@ -1,13 +1,10 @@ /** * Internal dependencies */ -import { registerBlock, query as hpq } from 'api'; +import { registerBlock, query } from 'api'; import Editable from 'components/editable'; -const { html, parse, query } = hpq; - -const fromValueToParagraphs = ( value ) => value ? value.map( ( paragraph ) => `

    ${ paragraph }

    ` ).join( '' ) : ''; -const fromParagraphsToValue = ( paragraphs ) => parse( paragraphs, query( 'p', html() ) ); +const { children } = query; registerBlock( 'core/text', { title: wp.i18n.__( 'Text' ), @@ -17,7 +14,7 @@ registerBlock( 'core/text', { category: 'common', attributes: { - content: query( 'p', html() ), + content: children(), }, controls: [ @@ -48,21 +45,23 @@ registerBlock( 'core/text', { ], edit( { attributes, setAttributes, insertBlockAfter, focus, setFocus } ) { - const { content, align } = attributes; + const { content =

    , align } = attributes; return ( setAttributes( { - content: fromParagraphsToValue( paragraphs ) - } ) } + value={ content } + onChange={ ( nextContent ) => { + setAttributes( { + content: nextContent + } ); + } } focus={ focus } onFocus={ setFocus } style={ align ? { textAlign: align } : null } onSplit={ ( before, after ) => { - setAttributes( { content: fromParagraphsToValue( before ) } ); + setAttributes( { content: before } ); insertBlockAfter( wp.blocks.createBlock( 'core/text', { - content: fromParagraphsToValue( after ) + content: after } ) ); } } /> @@ -70,20 +69,35 @@ registerBlock( 'core/text', { }, save( { attributes } ) { - const { align, content } = attributes; + // An empty block will have an undefined content field. Return early + // as an empty string. + let { content } = attributes; + if ( ! content ) { + return ''; + } - // Todo: Remove the temporary

    wrapper once the serializer supports returning an array - return ( -
    - { content && content.map( ( paragraph, i ) => ( -

    - ) ) } -

    - ); + // Content can be in the following shapes, so we normalize to an array: + // - Single paragraph: Object + // - Multiple paragraph: Array of objects + if ( ! Array.isArray( content ) ) { + content = [ content ]; + } + + // We only need to transform content if we need to apply the alignment + // style. Otherwise we can return unmodified. + const { align } = attributes; + if ( ! align ) { + return content; + } + + return content.map( ( paragraph ) => { + if ( 'string' === typeof paragraph ) { + return null; + } + + return wp.element.cloneElement( paragraph, { + style: { textAlign: align } + } ); + } ); } } ); diff --git a/languages/gutenberg.pot b/languages/gutenberg.pot index 25d0c46ff77165..9976a7358a837d 100644 --- a/languages/gutenberg.pot +++ b/languages/gutenberg.pot @@ -33,17 +33,17 @@ msgid "List" msgstr "" #: blocks/library/list/index.js:25 -#: blocks/library/text/index.js:26 +#: blocks/library/text/index.js:23 msgid "Align left" msgstr "" #: blocks/library/list/index.js:33 -#: blocks/library/text/index.js:34 +#: blocks/library/text/index.js:31 msgid "Align center" msgstr "" #: blocks/library/list/index.js:41 -#: blocks/library/text/index.js:42 +#: blocks/library/text/index.js:39 msgid "Align right" msgstr "" @@ -51,15 +51,15 @@ msgstr "" msgid "Justify" msgstr "" -#: blocks/library/quote/index.js:14 +#: blocks/library/quote/index.js:11 msgid "Quote" msgstr "" -#: blocks/library/quote/index.js:30 +#: blocks/library/quote/index.js:27 msgid "Quote style %d" msgstr "" -#: blocks/library/text/index.js:13 +#: blocks/library/text/index.js:10 msgid "Text" msgstr "" diff --git a/post-content.js b/post-content.js index 4a1b3cbd6bc628..e67322d3f1eb1c 100644 --- a/post-content.js +++ b/post-content.js @@ -9,7 +9,7 @@ window._wpGutenbergPost = { '', '', - '

    I imagine prior to the launch of the iPod, or the iPhone, there were teams saying the same thing: the copy + paste guys are so close to being ready and we know Walt Mossberg is going to ding us for this so let\'s just not ship to the manufacturers in China for just a few more weeks… The Apple teams were probably embarrassed. But if you\'re not embarrassed when you ship your first version you waited too long.

    ', + '

    I imagine prior to the launch of the iPod, or the iPhone, there were teams saying the same thing: the copy + paste guys are so close to being ready and we know Walt Mossberg is going to ding us for this so let\'s just not ship to the manufacturers in China for just a few more weeks… The Apple teams were probably embarrassed. But if you\'re not embarrassed when you ship your first version you waited too long.

    ', '', '', @@ -17,7 +17,8 @@ window._wpGutenbergPost = { '', '', - '

    A beautiful thing about Apple is how quickly they obsolete their own products. I imagine this also makes the discipline of getting things out there easier. Like I mentioned before, the longer it’s been since the last release the more pressure there is, but if you know that if your bit of code doesn’t make this version but there’s the +0.1 coming out in 6 weeks, then it’s not that bad. It’s like flights from San Francisco to LA, if you miss one you know there’s another one an hour later so it’s not a big deal. Amazon has done a fantastic job of this with the Kindle as well, with a new model every year.

    ', + '

    A beautiful thing about Apple is how quickly they obsolete their own products. I imagine this also makes the discipline of getting things out there easier. Like I mentioned before, the longer it’s been since the last release the more pressure there is, but if you know that if your bit of code doesn’t make this version but there’s the +0.1 coming out in 6 weeks, then it’s not that bad.

    ', + '

    It’s like flights from San Francisco to LA, if you miss one you know there’s another one an hour later so it’s not a big deal. Amazon has done a fantastic job of this with the Kindle as well, with a new model every year.

    ', '', '', @@ -29,7 +30,7 @@ window._wpGutenbergPost = { '', '', - '

    By shipping early and often you have the unique competitive advantage of hearing from real people what they think of your work, which in best case helps you anticipate market direction, and in worst case gives you a few people rooting for you that you can email when your team pivots to a new idea. Nothing can recreate the crucible of real usage.

    ', + '

    By shipping early and often you have the unique competitive advantage of hearing from real people what they think of your work, which in best case helps you anticipate market direction, and in worst case gives you a few people rooting for you that you can email when your team pivots to a new idea. Nothing can recreate the crucible of real usage.

    ', '', '', From 2c40c99eb59f570a54e45be948c815c83e9134ce Mon Sep 17 00:00:00 2001 From: Andrew Duthie Date: Thu, 20 Apr 2017 15:31:17 -0400 Subject: [PATCH 4/5] Parse Editable content as raw MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Default formatting is slower because it cleans the content. We don’t particularly care for this behavior since the React parser and subsequent renderToString will achieve the desired output --- blocks/components/editable/index.js | 2 +- blocks/library/list/index.js | 2 +- blocks/library/text/index.js | 12 ++++-------- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/blocks/components/editable/index.js b/blocks/components/editable/index.js index f293232a7caa58..e3e37a39e03912 100644 --- a/blocks/components/editable/index.js +++ b/blocks/components/editable/index.js @@ -142,7 +142,7 @@ export default class Editable extends wp.element.Component { } getContent() { - const content = this.editor.getContent(); + const content = this.editor.getContent( { format: 'raw' } ); return htmlToReactParser.parse( content ); } diff --git a/blocks/library/list/index.js b/blocks/library/list/index.js index 35ce57edcb7df6..c197b6a5bff542 100644 --- a/blocks/library/list/index.js +++ b/blocks/library/list/index.js @@ -75,7 +75,7 @@ registerBlock( 'core/list', { const { nodeName = 'OL', items = [] } = attributes; return wp.element.createElement( - listType.toLowerCase(), + nodeName.toLowerCase(), null, items.map( ( item, index ) => (
  • { item.value }
  • diff --git a/blocks/library/text/index.js b/blocks/library/text/index.js index ff505f1109bb8d..3cdcdd2f9d277b 100644 --- a/blocks/library/text/index.js +++ b/blocks/library/text/index.js @@ -90,14 +90,10 @@ registerBlock( 'core/text', { return content; } - return content.map( ( paragraph ) => { - if ( 'string' === typeof paragraph ) { - return null; - } - - return wp.element.cloneElement( paragraph, { + return content.map( ( paragraph ) => ( + wp.element.cloneElement( paragraph, { style: { textAlign: align } - } ); - } ); + } ) + ) ); } } ); From 82feb249cb1dad23bc93088d48f5457a4762283d Mon Sep 17 00:00:00 2001 From: Andrew Duthie Date: Fri, 21 Apr 2017 11:18:01 -0400 Subject: [PATCH 5/5] Use React.Children to map content Designed for this purpose of handling opaque structure of children https://facebook.github.io/react/docs/react-api.html#react.children --- blocks/library/text/index.js | 11 ++--------- element/index.js | 4 +++- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/blocks/library/text/index.js b/blocks/library/text/index.js index 3cdcdd2f9d277b..75c60a6c9d79a1 100644 --- a/blocks/library/text/index.js +++ b/blocks/library/text/index.js @@ -71,18 +71,11 @@ registerBlock( 'core/text', { save( { attributes } ) { // An empty block will have an undefined content field. Return early // as an empty string. - let { content } = attributes; + const { content } = attributes; if ( ! content ) { return ''; } - // Content can be in the following shapes, so we normalize to an array: - // - Single paragraph: Object - // - Multiple paragraph: Array of objects - if ( ! Array.isArray( content ) ) { - content = [ content ]; - } - // We only need to transform content if we need to apply the alignment // style. Otherwise we can return unmodified. const { align } = attributes; @@ -90,7 +83,7 @@ registerBlock( 'core/text', { return content; } - return content.map( ( paragraph ) => ( + return wp.element.Children.map( content, ( paragraph ) => ( wp.element.cloneElement( paragraph, { style: { textAlign: align } } ) diff --git a/element/index.js b/element/index.js index 6459c1c124423b..846c51b98be24f 100644 --- a/element/index.js +++ b/element/index.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { createElement, Component, cloneElement } from 'react'; +import { createElement, Component, cloneElement, Children } from 'react'; import { render } from 'react-dom'; import { renderToStaticMarkup } from 'react-dom/server'; @@ -40,6 +40,8 @@ export { Component }; */ export { cloneElement }; +export { Children }; + /** * Renders a given element into a string *