From e1fe60261f06956c70a67ec3449852bb7a76c39c Mon Sep 17 00:00:00 2001 From: Andrew Duthie Date: Fri, 15 Dec 2017 17:00:48 -0500 Subject: [PATCH] Represent Editable value as array tree --- blocks/api/index.js | 2 +- blocks/api/matchers.js | 63 +++++++++++++++++++++++++++---- blocks/editable/index.js | 42 ++++++++++++++------- blocks/library/pullquote/index.js | 8 +--- blocks/library/quote/index.js | 8 +--- element/serialize.js | 11 +++++- 6 files changed, 99 insertions(+), 35 deletions(-) diff --git a/blocks/api/index.js b/blocks/api/index.js index c4fd64bcf4c76d..a2ff3f5e205145 100644 --- a/blocks/api/index.js +++ b/blocks/api/index.js @@ -16,4 +16,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 2307cf992b7bb0..c71f3d8b121548 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 ) { @@ -558,8 +574,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 ); @@ -613,8 +629,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 ); @@ -667,8 +683,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 ) ); } @@ -708,7 +724,7 @@ export default class Editable extends Component { } getContent() { - return nodeListToReact( this.editor.getBody().childNodes || [], createTinyMCEElement ); + return nodeListToTree( this.editor.getBody().childNodes || [], createTinyMCEElement ); } updateFocus() { @@ -861,7 +877,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 && (
{ citation }
) } 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/element/serialize.js b/element/serialize.js index 66f00c86225e62..2886033363aa17 100644 --- a/element/serialize.js +++ b/element/serialize.js @@ -183,6 +183,11 @@ function renderElement( element ) { } if ( Array.isArray( element ) ) { + if ( element[ 1 ] && element[ 1 ].constructor === Object ) { + const [ type, props, children ] = element; + return renderElement( { type, props: { ...props, children } } ); + } + return element.map( renderElement ).join( '' ); } @@ -271,7 +276,11 @@ function renderChildren( children ) { if ( typeof child === 'string' ) { str += child; } else if ( Array.isArray( child ) ) { - str += renderChildren( child ); + if ( child[ 1 ] && child[ 1 ].constructor === Object ) { + str += renderElement( child ); + } else { + str += renderChildren( child ); + } } else if ( typeof child === 'object' && child ) { str += renderElement( child ); } else if ( typeof child === 'number' ) {