diff --git a/blocks/api/index.js b/blocks/api/index.js index 7f0653a133c5b3..ffec8b529ea87c 100644 --- a/blocks/api/index.js +++ b/blocks/api/index.js @@ -21,4 +21,4 @@ export { hasBlockSupport, isReusableBlock, } from './registration'; - +export { nodeListToTree } from './matchers'; diff --git a/blocks/api/matchers.js b/blocks/api/matchers.js index 0ccf4cf8d8832b..59da9bbbee1035 100644 --- a/blocks/api/matchers.js +++ b/blocks/api/matchers.js @@ -1,14 +1,61 @@ -/** - * WordPress dependencies - */ -import { createElement } from '@wordpress/element'; - /** * External dependencies */ -import { nodeListToReact, nodeToReact } from 'dom-react'; export { attr, prop, html, text, query } from 'hpq'; +export function buildTree( type, attributes, ...children ) { + children = children.map( ( child ) => { + if ( 'boolean' === typeof child ) { + child = null; + } + + if ( null === child || undefined === child ) { + child = ''; + } else if ( 'number' === typeof child ) { + child = String( child ); + } + + if ( 'string' === typeof child ) { + return child; + } + + return buildTree( child ); + } ); + + return [ type, attributes, children ]; +} + +export function nodeListToTree( nodeList, createElement ) { + return [ ...nodeList ].map( ( node ) => nodeToTree( node, createElement ) ); +} + +export function elementAsArray( type, attributes, children ) { + return [ type, attributes, children ]; +} + +export function nodeToTree( node, createElement = elementAsArray ) { + if ( ! node ) { + return null; + } + + if ( node.nodeType === 3 ) { + return node.nodeValue; + } + + if ( node.nodeType !== 1 ) { + return null; + } + + const type = node.nodeName.toLowerCase(); + const attributes = [ ...node.attributes ].reduce( ( result, { name, value } ) => { + result[ name ] = value; + return result; + }, {} ); + const children = nodeListToTree( node.childNodes ); + + return createElement( type, attributes, children ); +} + export const children = ( selector ) => { return ( domNode ) => { let match = domNode; @@ -18,7 +65,7 @@ export const children = ( selector ) => { } if ( match ) { - return nodeListToReact( match.childNodes || [], createElement ); + return nodeListToTree( match.childNodes ); } return []; @@ -33,6 +80,6 @@ export const node = ( selector ) => { match = domNode.querySelector( selector ); } - return nodeToReact( match, createElement ); + return nodeToTree( match ); }; }; diff --git a/blocks/editable/index.js b/blocks/editable/index.js index 13bd6af1d6776e..40f07e090ef3c7 100644 --- a/blocks/editable/index.js +++ b/blocks/editable/index.js @@ -14,7 +14,6 @@ import { defer, noop, } from 'lodash'; -import { nodeListToReact } from 'dom-react'; import 'element-closest'; /** @@ -28,7 +27,7 @@ import { Slot, Fill } from '@wordpress/components'; * Internal dependencies */ import './style.scss'; -import { rawHandler } from '../api'; +import { rawHandler, nodeListToTree } from '../api'; import FormatToolbar from './format-toolbar'; import TinyMCE from './tinymce'; import { pickAriaProps } from './aria'; @@ -37,6 +36,23 @@ import { EVENTS } from './constants'; const { BACKSPACE, DELETE, ENTER } = keycodes; +function toElement( value ) { + if ( ! value ) { + return null; + } + + if ( ! Array.isArray( value ) ) { + return value; + } + + const [ type, attributes, children ] = value; + if ( ! attributes || attributes.constructor !== Object ) { + return value.map( toElement ); + } + + return createElement( type, attributes, children.map( toElement ) ); +} + function createTinyMCEElement( type, props, ...children ) { if ( props[ 'data-mce-bogus' ] === 'all' ) { return null; @@ -46,11 +62,11 @@ function createTinyMCEElement( type, props, ...children ) { return children; } - return createElement( + return [ type, omitBy( props, ( value, key ) => key.indexOf( 'data-mce-' ) === 0 ), - ...children - ); + children, + ]; } function isLinkBoundary( fragment ) { @@ -559,8 +575,8 @@ export default class Editable extends Component { const index = dom.nodeIndex( selectedNode ); const beforeNodes = childNodes.slice( 0, index ); const afterNodes = childNodes.slice( index + 1 ); - const beforeElement = nodeListToReact( beforeNodes, createTinyMCEElement ); - const afterElement = nodeListToReact( afterNodes, createTinyMCEElement ); + const beforeElement = nodeListToTree( beforeNodes, createTinyMCEElement ); + const afterElement = nodeListToTree( afterNodes, createTinyMCEElement ); this.setContent( beforeElement ); this.props.onSplit( beforeElement, afterElement ); @@ -614,8 +630,8 @@ export default class Editable extends Component { const beforeFragment = beforeRange.extractContents(); const afterFragment = afterRange.extractContents(); - const beforeElement = nodeListToReact( beforeFragment.childNodes, createTinyMCEElement ); - const afterElement = isLinkBoundary( afterFragment ) ? [] : nodeListToReact( afterFragment.childNodes, createTinyMCEElement ); + const beforeElement = nodeListToTree( beforeFragment.childNodes, createTinyMCEElement ); + const afterElement = isLinkBoundary( afterFragment ) ? [] : nodeListToTree( afterFragment.childNodes, createTinyMCEElement ); this.setContent( beforeElement ); this.props.onSplit( beforeElement, afterElement, ...blocks ); @@ -668,8 +684,8 @@ export default class Editable extends Component { this.setContent( this.props.value ); this.props.onSplit( - nodeListToReact( before, createTinyMCEElement ), - nodeListToReact( after, createTinyMCEElement ) + nodeListToTree( before, createTinyMCEElement ), + nodeListToTree( after, createTinyMCEElement ) ); } @@ -709,7 +725,7 @@ export default class Editable extends Component { } getContent() { - return nodeListToReact( this.editor.getBody().childNodes || [], createTinyMCEElement ); + return nodeListToTree( this.editor.getBody().childNodes || [], createTinyMCEElement ); } updateFocus() { @@ -862,7 +878,7 @@ export default class Editable extends Component { getSettings={ this.getSettings } onSetup={ this.onSetup } style={ style } - defaultValue={ value } + defaultValue={ toElement( value ) } isPlaceholderVisible={ isPlaceholderVisible } aria-label={ placeholder } { ...ariaProps } diff --git a/blocks/library/pullquote/index.js b/blocks/library/pullquote/index.js index 0487c215a11d86..c8882ea73042ba 100644 --- a/blocks/library/pullquote/index.js +++ b/blocks/library/pullquote/index.js @@ -120,9 +120,7 @@ registerBlockType( 'core/pullquote', { return (
- { value && value.map( ( paragraph, i ) => -{ paragraph.children && paragraph.children.props.children }
- ) } + { value.map( ( paragraph ) => paragraph.children ) } { citation && citation.length > 0 && ( { citation } ) } @@ -145,9 +143,7 @@ registerBlockType( 'core/pullquote', { return (- { value && value.map( ( paragraph, i ) => -{ paragraph.children && paragraph.children.props.children }
- ) } + { value.map( ( paragraph ) => paragraph.children ) } { citation && citation.length > 0 && ( ) } diff --git a/blocks/library/quote/index.js b/blocks/library/quote/index.js index cda2381e080d0d..575dd23b590186 100644 --- a/blocks/library/quote/index.js +++ b/blocks/library/quote/index.js @@ -238,9 +238,7 @@ registerBlockType( 'core/quote', { className={ style === 2 ? 'is-large' : '' } style={ { textAlign: align ? align : null } } > - { value.map( ( paragraph, i ) => ( -{ paragraph.children && paragraph.children.props.children }
- ) ) } + { value.map( ( paragraph ) => paragraph.children ) } { citation && citation.length > 0 && ( { citation } ) } @@ -267,9 +265,7 @@ registerBlockType( 'core/quote', { className={ `blocks-quote-style-${ style }` } style={ { textAlign: align ? align : null } } > - { value.map( ( paragraph, i ) => ( -{ paragraph.children && paragraph.children.props.children }
- ) ) } + { value.map( ( paragraph ) => paragraph.children ) } { citation && citation.length > 0 && ( ) } diff --git a/blocks/test/fixtures/core__heading__h2-em.json b/blocks/test/fixtures/core__heading__h2-em.json index 39674931b4429a..5ffe34cf2699dc 100644 --- a/blocks/test/fixtures/core__heading__h2-em.json +++ b/blocks/test/fixtures/core__heading__h2-em.json @@ -6,10 +6,13 @@ "attributes": { "content": [ "The ", - { - "type": "em", - "children": "Inserter" - }, + [ + "em", + {}, + [ + "Inserter" + ] + ], " Tool" ], "nodeName": "H2" diff --git a/blocks/test/fixtures/core__list__ul.json b/blocks/test/fixtures/core__list__ul.json index 15bc029cf8de16..17f38c9afe3255 100644 --- a/blocks/test/fixtures/core__list__ul.json +++ b/blocks/test/fixtures/core__list__ul.json @@ -6,37 +6,56 @@ "attributes": { "nodeName": "UL", "values": [ - { - "type": "li", - "children": "Text & Headings" - }, - { - "type": "li", - "children": "Images & Videos" - }, - { - "type": "li", - "children": "Galleries" - }, - { - "type": "li", - "children": "Embeds, like YouTube, Tweets, or other WordPress posts." - }, - { - "type": "li", - "children": "Layout blocks, like Buttons, Hero Images, Separators, etc." - }, - { - "type": "li", - "children": [ + [ + "li", + {}, + [ + "Text & Headings" + ] + ], + [ + "li", + {}, + [ + "Images & Videos" + ] + ], + [ + "li", + {}, + [ + "Galleries" + ] + ], + [ + "li", + {}, + [ + "Embeds, like YouTube, Tweets, or other WordPress posts." + ] + ], + [ + "li", + {}, + [ + "Layout blocks, like Buttons, Hero Images, Separators, etc." + ] + ], + [ + "li", + {}, + [ "And ", - { - "type": "em", - "children": "Lists" - }, + [ + "em", + {}, + [ + "Lists" + ] + ], " like this one of course :)" ] - } + ] ] }, "originalContent": "" diff --git a/blocks/test/fixtures/core__preformatted.json b/blocks/test/fixtures/core__preformatted.json index 37ca66e8c95ad5..95b164635e13cd 100644 --- a/blocks/test/fixtures/core__preformatted.json +++ b/blocks/test/fixtures/core__preformatted.json @@ -6,14 +6,19 @@ "attributes": { "content": [ "Some ", - { - "type": "em", - "children": "preformatted" - }, + [ + "em", + {}, + [ + "preformatted" + ] + ], " text...", - { - "type": "br" - }, + [ + "br", + {}, + [] + ], "And more!" ] }, diff --git a/blocks/test/fixtures/core__pullquote.json b/blocks/test/fixtures/core__pullquote.json index 5ebe0b5564fb25..d3ba09843be9c6 100644 --- a/blocks/test/fixtures/core__pullquote.json +++ b/blocks/test/fixtures/core__pullquote.json @@ -6,16 +6,13 @@ "attributes": { "value": [ { - "children": { - "type": "p", - "key": null, - "ref": null, - "props": { - "children": "Testing pullquote block..." - }, - "_owner": null, - "_store": {} - } + "children": [ + "p", + {}, + [ + "Testing pullquote block..." + ] + ] } ], "citation": [ diff --git a/blocks/test/fixtures/core__pullquote__multi-paragraph.json b/blocks/test/fixtures/core__pullquote__multi-paragraph.json index 29eed74d38b3b5..e53b3dd77c8af9 100644 --- a/blocks/test/fixtures/core__pullquote__multi-paragraph.json +++ b/blocks/test/fixtures/core__pullquote__multi-paragraph.json @@ -6,40 +6,29 @@ "attributes": { "value": [ { - "children": { - "type": "p", - "key": null, - "ref": null, - "props": { - "children": [ - "Paragraph ", - { - "type": "strong", - "key": "_domReact68", - "ref": null, - "props": { - "children": "one" - }, - "_owner": null, - "_store": {} - } + "children": [ + "p", + {}, + [ + "Paragraph ", + [ + "strong", + {}, + [ + "one" + ] ] - }, - "_owner": null, - "_store": {} - } + ] + ] }, { - "children": { - "type": "p", - "key": null, - "ref": null, - "props": { - "children": "Paragraph two" - }, - "_owner": null, - "_store": {} - } + "children": [ + "p", + {}, + [ + "Paragraph two" + ] + ] } ], "citation": [ diff --git a/blocks/test/fixtures/core__quote__style-1.json b/blocks/test/fixtures/core__quote__style-1.json index bea152f89f32c6..43d3e20cf4a5b0 100644 --- a/blocks/test/fixtures/core__quote__style-1.json +++ b/blocks/test/fixtures/core__quote__style-1.json @@ -6,16 +6,13 @@ "attributes": { "value": [ { - "children": { - "type": "p", - "key": null, - "ref": null, - "props": { - "children": "The editor will endeavour to create a new page and post building experience that makes writing rich posts effortless, and has “blocks” to make it easy what today might take shortcodes, custom HTML, or “mystery meat” embed discovery." - }, - "_owner": null, - "_store": {} - } + "children": [ + "p", + {}, + [ + "The editor will endeavour to create a new page and post building experience that makes writing rich posts effortless, and has “blocks” to make it easy what today might take shortcodes, custom HTML, or “mystery meat” embed discovery." + ] + ] } ], "citation": [ diff --git a/blocks/test/fixtures/core__quote__style-2.json b/blocks/test/fixtures/core__quote__style-2.json index ea48f03aef42fe..60a4a916435f8e 100644 --- a/blocks/test/fixtures/core__quote__style-2.json +++ b/blocks/test/fixtures/core__quote__style-2.json @@ -6,16 +6,13 @@ "attributes": { "value": [ { - "children": { - "type": "p", - "key": null, - "ref": null, - "props": { - "children": "There is no greater agony than bearing an untold story inside you." - }, - "_owner": null, - "_store": {} - } + "children": [ + "p", + {}, + [ + "There is no greater agony than bearing an untold story inside you." + ] + ] } ], "citation": [ diff --git a/blocks/test/fixtures/core__table.json b/blocks/test/fixtures/core__table.json index 2ecdee94dd749c..2b4f96c3b890c4 100644 --- a/blocks/test/fixtures/core__table.json +++ b/blocks/test/fixtures/core__table.json @@ -5,195 +5,291 @@ "isValid": true, "attributes": { "content": [ - { - "type": "thead", - "children": { - "type": "tr", - "children": [ - { - "type": "th", - "children": "Version" - }, - { - "type": "th", - "children": "Musician" - }, - { - "type": "th", - "children": "Date" - } + [ + "thead", + {}, + [ + [ + "tr", + {}, + [ + [ + "th", + {}, + [ + "Version" + ] + ], + [ + "th", + {}, + [ + "Musician" + ] + ], + [ + "th", + {}, + [ + "Date" + ] + ] + ] ] - } - }, - { - "type": "tbody", - "children": [ - { - "type": "tr", - "children": [ - { - "type": "td", - "children": { - "type": "a", - "attributes": { - "href": "https://wordpress.org/news/2003/05/wordpress-now-available/" - }, - "children": ".70" - } - }, - { - "type": "td", - "children": "No musician chosen." - }, - { - "type": "td", - "children": "May 27, 2003" - } + ] + ], + [ + "tbody", + {}, + [ + [ + "tr", + {}, + [ + [ + "td", + {}, + [ + [ + "a", + { + "href": "https://wordpress.org/news/2003/05/wordpress-now-available/" + }, + [ + ".70" + ] + ] + ] + ], + [ + "td", + {}, + [ + "No musician chosen." + ] + ], + [ + "td", + {}, + [ + "May 27, 2003" + ] + ] ] - }, - { - "type": "tr", - "children": [ - { - "type": "td", - "children": { - "type": "a", - "attributes": { - "href": "https://wordpress.org/news/2004/01/wordpress-10/" - }, - "children": "1.0" - } - }, - { - "type": "td", - "children": "Miles Davis" - }, - { - "type": "td", - "children": "January 3, 2004" - } + ], + [ + "tr", + {}, + [ + [ + "td", + {}, + [ + [ + "a", + { + "href": "https://wordpress.org/news/2004/01/wordpress-10/" + }, + [ + "1.0" + ] + ] + ] + ], + [ + "td", + {}, + [ + "Miles Davis" + ] + ], + [ + "td", + {}, + [ + "January 3, 2004" + ] + ] ] - }, - { - "type": "tr", - "children": [ - { - "type": "td", - "children": [ + ], + [ + "tr", + {}, + [ + [ + "td", + {}, + [ "Lots of versions skipped, see ", - { - "type": "a", - "attributes": { + [ + "a", + { "href": "https://codex.wordpress.org/WordPress_Versions" }, - "children": "the full list" - } - ] - }, - { - "type": "td", - "children": "…" - }, - { - "type": "td", - "children": "…" - } + [ + "the full list" + ] + ] + ] + ], + [ + "td", + {}, + [ + "…" + ] + ], + [ + "td", + {}, + [ + "…" + ] + ] ] - }, - { - "type": "tr", - "children": [ - { - "type": "td", - "children": { - "type": "a", - "attributes": { - "href": "https://wordpress.org/news/2015/12/clifford/" - }, - "children": "4.4" - } - }, - { - "type": "td", - "children": "Clifford Brown" - }, - { - "type": "td", - "children": "December 8, 2015" - } + ], + [ + "tr", + {}, + [ + [ + "td", + {}, + [ + [ + "a", + { + "href": "https://wordpress.org/news/2015/12/clifford/" + }, + [ + "4.4" + ] + ] + ] + ], + [ + "td", + {}, + [ + "Clifford Brown" + ] + ], + [ + "td", + {}, + [ + "December 8, 2015" + ] + ] ] - }, - { - "type": "tr", - "children": [ - { - "type": "td", - "children": { - "type": "a", - "attributes": { - "href": "https://wordpress.org/news/2016/04/coleman/" - }, - "children": "4.5" - } - }, - { - "type": "td", - "children": "Coleman Hawkins" - }, - { - "type": "td", - "children": "April 12, 2016" - } + ], + [ + "tr", + {}, + [ + [ + "td", + {}, + [ + [ + "a", + { + "href": "https://wordpress.org/news/2016/04/coleman/" + }, + [ + "4.5" + ] + ] + ] + ], + [ + "td", + {}, + [ + "Coleman Hawkins" + ] + ], + [ + "td", + {}, + [ + "April 12, 2016" + ] + ] ] - }, - { - "type": "tr", - "children": [ - { - "type": "td", - "children": { - "type": "a", - "attributes": { - "href": "https://wordpress.org/news/2016/08/pepper/" - }, - "children": "4.6" - } - }, - { - "type": "td", - "children": "Pepper Adams" - }, - { - "type": "td", - "children": "August 16, 2016" - } + ], + [ + "tr", + {}, + [ + [ + "td", + {}, + [ + [ + "a", + { + "href": "https://wordpress.org/news/2016/08/pepper/" + }, + [ + "4.6" + ] + ] + ] + ], + [ + "td", + {}, + [ + "Pepper Adams" + ] + ], + [ + "td", + {}, + [ + "August 16, 2016" + ] + ] ] - }, - { - "type": "tr", - "children": [ - { - "type": "td", - "children": { - "type": "a", - "attributes": { - "href": "https://wordpress.org/news/2016/12/vaughan/" - }, - "children": "4.7" - } - }, - { - "type": "td", - "children": "Sarah Vaughan" - }, - { - "type": "td", - "children": "December 6, 2016" - } + ], + [ + "tr", + {}, + [ + [ + "td", + {}, + [ + [ + "a", + { + "href": "https://wordpress.org/news/2016/12/vaughan/" + }, + [ + "4.7" + ] + ] + ] + ], + [ + "td", + {}, + [ + "Sarah Vaughan" + ] + ], + [ + "td", + {}, + [ + "December 6, 2016" + ] + ] ] - } + ] ] - } + ] ] }, "originalContent": "
- Text & Headings
- Images & Videos
- Galleries
- Embeds, like YouTube, Tweets, or other WordPress posts.
- Layout blocks, like Buttons, Hero Images, Separators, etc.
- And Lists like this one of course :)
" diff --git a/blocks/test/fixtures/core__text__converts-to-paragraph.json b/blocks/test/fixtures/core__text__converts-to-paragraph.json index 0efed063a93be2..05f79a87cf87dc 100644 --- a/blocks/test/fixtures/core__text__converts-to-paragraph.json +++ b/blocks/test/fixtures/core__text__converts-to-paragraph.json @@ -6,10 +6,13 @@ "attributes": { "content": [ "This is an old-style text block. Changed to ", - { - "type": "code", - "children": "paragraph" - }, + [ + "code", + {}, + [ + "paragraph" + ] + ], " in #2135." ], "dropCap": false diff --git a/blocks/test/fixtures/core__verse.json b/blocks/test/fixtures/core__verse.json index 0baa254be043a9..2aa3d5919f727a 100644 --- a/blocks/test/fixtures/core__verse.json +++ b/blocks/test/fixtures/core__verse.json @@ -6,14 +6,19 @@ "attributes": { "content": [ "A ", - { - "type": "em", - "children": "verse" - }, + [ + "em", + {}, + [ + "verse" + ] + ], "…", - { - "type": "br" - }, + [ + "br", + {}, + [] + ], "And more!" ] }, diff --git a/element/serialize.js b/element/serialize.js index 66f00c86225e62..8530b52952a292 100644 --- a/element/serialize.js +++ b/element/serialize.js @@ -171,6 +171,16 @@ function hasPrefixes( string, prefixes ) { return prefixes.some( ( prefix ) => string.indexOf( prefix ) === 0 ); } +/** + * Returns true if the given argument can be inferred to be a props object. + * + * @param {*} object Object to test + * @return {Boolean} Whether object is props + */ +function isPropsLike( object ) { + return object && object.constructor === Object && ! ( '$$typeof' in object ); +} + /** * Serializes a React element to string. * @@ -183,6 +193,11 @@ function renderElement( element ) { } if ( Array.isArray( element ) ) { + if ( isPropsLike( element[ 1 ] ) ) { + const [ type, props, children ] = element; + return renderElement( { type, props: { ...props, children } } ); + } + return element.map( renderElement ).join( '' ); } @@ -271,7 +286,11 @@ function renderChildren( children ) { if ( typeof child === 'string' ) { str += child; } else if ( Array.isArray( child ) ) { - str += renderChildren( child ); + if ( isPropsLike( child[ 1 ] ) ) { + str += renderElement( child ); + } else { + str += renderChildren( child ); + } } else if ( typeof child === 'object' && child ) { str += renderElement( child ); } else if ( typeof child === 'number' ) {
Version Musician Date .70 No musician chosen. May 27, 2003 1.0 Miles Davis January 3, 2004 Lots of versions skipped, see the full list … … 4.4 Clifford Brown December 8, 2015 4.5 Coleman Hawkins April 12, 2016 4.6 Pepper Adams August 16, 2016 4.7 Sarah Vaughan December 6, 2016