Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Experimental: Remove children matcher in favor of HTML #1907

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 1 addition & 7 deletions blocks/api/paste.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,8 @@
/**
* External dependencies
*/
import { nodeListToReact } from 'dom-react';
import { find, get } from 'lodash';

/**
* WordPress dependencies
*/
import { createElement } from 'element';

/**
* Internal dependencies
*/
Expand Down Expand Up @@ -100,7 +94,7 @@ export default function( nodes ) {
}

return createBlock( getUnknownTypeHandler(), {
content: nodeListToReact( [ node ], createElement ),
content: node.outerHTML,
} );
} );
}
17 changes: 1 addition & 16 deletions blocks/api/query.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { createElement } from 'element';
/**
* External dependencies
*/
import { nodeListToReact, nodeToReact } from 'dom-react';
import { nodeToReact } from 'dom-react';
import { flow } from 'lodash';
import {
attr as originalAttr,
Expand Down Expand Up @@ -35,21 +35,6 @@ export const prop = withKnownMatcherFlag( originalProp );
export const html = withKnownMatcherFlag( originalHtml );
export const text = withKnownMatcherFlag( originalText );
export const query = withKnownMatcherFlag( originalQuery );
export const children = withKnownMatcherFlag( ( selector ) => {
return ( domNode ) => {
let match = domNode;

if ( selector ) {
match = domNode.querySelector( selector );
}

if ( match ) {
return nodeListToReact( match.childNodes || [], createElement );
}

return [];
};
} );
export const node = withKnownMatcherFlag( ( selector ) => {
return ( domNode ) => {
let match = domNode;
Expand Down
59 changes: 38 additions & 21 deletions blocks/api/serializer.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
*/
import { isEmpty, reduce, isObject } from 'lodash';
import { html as beautifyHtml } from 'js-beautify';
import traverse from 'react-traverse';
import classnames from 'classnames';

/**
Expand Down Expand Up @@ -37,37 +38,53 @@ export function getBlockDefaultClassname( blockName ) {
*/
export function getSaveContent( blockType, attributes ) {
const { save, className = getBlockDefaultClassname( blockType.name ) } = blockType;
let rawContent;

let content;
if ( save.prototype instanceof Component ) {
rawContent = createElement( save, { attributes } );
content = createElement( save, { attributes } );
} else {
rawContent = save( { attributes } );
content = save( { attributes } );

// Special-case function render implementation to allow raw HTML return
if ( 'string' === typeof rawContent ) {
return rawContent;
if ( 'string' === typeof content ) {
return content;
}
}

// Adding a generic classname
const addClassnameToElement = ( element ) => {
if ( ! element || ! isObject( element ) || ! className ) {
return element;
}

const updatedClassName = classnames(
className,
element.props.className,
attributes.className
);

return cloneElement( element, { className: updatedClassName } );
};
const contentWithClassname = Children.map( rawContent, addClassnameToElement );
content = traverse( content, {
DOMElement( { node, traverseChildren } ) {
if ( 'string' === typeof node.props.children ) {
return cloneElement( node, {
...node.props,
dangerouslySetInnerHTML: {
__html: node.props.children,
},
children: undefined,
} );
}

return cloneElement( node, node.props, ...traverseChildren() );
},
} );

if ( false !== className ) {
content = Children.map( content, ( element ) => {
if ( ! element || ! isObject( element ) ) {
return element;
}

return cloneElement( element, {
className: classnames(
className,
element.props.className,
attributes.className
),
} );
} );
}

// Otherwise, infer as element
return renderToString( contentWithClassname );
return renderToString( content );
}

/**
Expand Down
22 changes: 0 additions & 22 deletions blocks/api/test/query.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,6 @@
*/
import { parse } from 'hpq';

/**
* WordPress dependencies
*/
import { renderToString } from 'element';

/**
* Internal dependencies
*/
Expand All @@ -20,23 +15,6 @@ describe( 'query', () => {
}
} );

describe( 'children()', () => {
it( 'should return a matcher function', () => {
const matcher = query.children();

expect( typeof matcher ).toBe( '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 = '<blockquote><p>A delicious sundae dessert</p></blockquote>';
const match = parse( html, query.children() );

expect( renderToString( match ) ).toBe( html );
} );
} );

describe( 'node()', () => {
it( 'should return a matcher function', () => {
const matcher = query.node();
Expand Down
46 changes: 20 additions & 26 deletions blocks/editable/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,14 @@
*/
import tinymce from 'tinymce';
import classnames from 'classnames';
import { last, isEqual, omitBy, forEach, merge, identity, find } from 'lodash';
import { nodeListToReact } from 'dom-react';
import { last, isEqual, forEach, merge, identity, find } from 'lodash';
import { Fill } from 'react-slot-fill';
import 'element-closest';

/**
* WordPress dependencies
*/
import { createElement, Component, renderToString } from 'element';
import { Component } from 'element';
import { parse, pasteHandler } from '../api';
import { BACKSPACE, DELETE, ENTER } from 'utils/keycodes';

Expand All @@ -22,20 +21,16 @@ import './style.scss';
import FormatToolbar from './format-toolbar';
import TinyMCE from './tinymce';

function createTinyMCEElement( type, props, ...children ) {
if ( props[ 'data-mce-bogus' ] === 'all' ) {
return null;
}

if ( props.hasOwnProperty( 'data-mce-bogus' ) ) {
return children;
}
function getNodesHTML( nodes ) {
const tempParent = document.createElement( 'div' );
nodes.forEach( ( node ) => tempParent.appendChild( node ) );
return tempParent.innerHTML;
}

return createElement(
type,
omitBy( props, ( value, key ) => key.indexOf( 'data-mce-' ) === 0 ),
...children
);
function getFragmentHTML( fragment ) {
const tempParent = document.createElement( 'div' );
tempParent.appendChild( fragment );
return tempParent.innerHTML;
}

export default class Editable extends Component {
Expand Down Expand Up @@ -278,11 +273,11 @@ export default class Editable extends Component {
const beforeFragment = beforeRange.extractContents();
const afterFragment = afterRange.extractContents();

const beforeElement = nodeListToReact( beforeFragment.childNodes, createTinyMCEElement );
const afterElement = nodeListToReact( afterFragment.childNodes, createTinyMCEElement );
const beforeContent = getFragmentHTML( beforeFragment );
const afterContent = getFragmentHTML( afterFragment );

this.setContent( beforeElement );
this.props.onSplit( beforeElement, afterElement, ...blocks );
this.setContent( beforeContent );
this.props.onSplit( beforeContent, afterContent, ...blocks );
}

onNewBlock() {
Expand Down Expand Up @@ -328,8 +323,8 @@ export default class Editable extends Component {
this.setContent( this.props.value );

this.props.onSplit(
nodeListToReact( before, createTinyMCEElement ),
nodeListToReact( after, createTinyMCEElement )
getNodesHTML( before ),
getNodesHTML( after )
);
}

Expand Down Expand Up @@ -363,12 +358,11 @@ export default class Editable extends Component {
content = '';
}

content = renderToString( content );
this.editor.setContent( content, { format: 'raw' } );
}

getContent() {
return nodeListToReact( this.editor.getBody().childNodes || [], createTinyMCEElement );
return this.editor.getContent( { format: 'raw' } );
}

updateFocus() {
Expand Down Expand Up @@ -404,8 +398,8 @@ export default class Editable extends Component {
this.props.tagName === prevProps.tagName &&
this.props.value !== prevProps.value &&
this.props.value !== this.savedContent &&
! isEqual( this.props.value, prevProps.value ) &&
! isEqual( this.props.value, this.savedContent )
this.props.value !== prevProps.value &&
this.props.value !== this.savedContent
) {
this.updateContent();
}
Expand Down
13 changes: 3 additions & 10 deletions blocks/editable/tinymce.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import classnames from 'classnames';
/**
* WordPress dependencies
*/
import { Component, Children, createElement } from 'element';
import { Component, createElement } from 'element';

export default class TinyMCE extends Component {
componentDidMount() {
Expand Down Expand Up @@ -80,21 +80,14 @@ export default class TinyMCE extends Component {
render() {
const { tagName = 'div', style, defaultValue, label, className } = this.props;

// If a default value is provided, render it into the DOM even before
// TinyMCE finishes initializing. This avoids a short delay by allowing
// us to show and focus the content before it's truly ready to edit.
let children;
if ( defaultValue ) {
children = Children.toArray( defaultValue );
}

return createElement( tagName, {
ref: ( node ) => this.editorNode = node,
contentEditable: true,
suppressContentEditableWarning: true,
className: classnames( className, 'blocks-editable__tinymce' ),
style,
'aria-label': label,
}, children );
dangerouslySetInnerHTML: { __html: defaultValue },
} );
}
}
4 changes: 2 additions & 2 deletions blocks/library/button/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import UrlInput from '../../url-input';
import BlockControls from '../../block-controls';
import BlockAlignmentToolbar from '../../block-alignment-toolbar';

const { attr, children } = query;
const { attr, html } = query;

registerBlockType( 'core/button', {
title: __( 'Button' ),
Expand All @@ -27,7 +27,7 @@ registerBlockType( 'core/button', {
attributes: {
url: attr( 'a', 'href' ),
title: attr( 'a', 'title' ),
text: children( 'a' ),
text: html( 'a' ),
},

getEditWrapperProps( attributes ) {
Expand Down
25 changes: 15 additions & 10 deletions blocks/library/embed/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import Editable from '../../editable';
import BlockControls from '../../block-controls';
import BlockAlignmentToolbar from '../../block-alignment-toolbar';

const { attr, children } = query;
const { attr, html } = query;

// These embeds do not work in sandboxes
const HOSTS_NO_PREVIEWS = [ 'facebook.com' ];
Expand All @@ -35,7 +35,7 @@ function getEmbedBlockSettings( { title, icon, category = 'embed' } ) {

attributes: {
title: attr( 'iframe', 'title' ),
caption: children( 'figcaption' ),
caption: html( 'figcaption' ),
},

getEditWrapperProps( attributes ) {
Expand Down Expand Up @@ -95,11 +95,16 @@ function getEmbedBlockSettings( { title, icon, category = 'embed' } ) {
return;
}
response.json().then( ( obj ) => {
const { html, type } = obj;
if ( html ) {
this.setState( { html, type } );
} else if ( 'photo' === type ) {
this.setState( { html: this.getPhotoHtml( obj ), type } );
if ( obj.html ) {
this.setState( {
html: obj.html,
type: obj.type,
} );
} else if ( 'photo' === obj.type ) {
this.setState( {
html: this.getPhotoHtml( obj ),
type: obj.type,
} );
} else {
this.setState( { error: true } );
}
Expand All @@ -110,7 +115,7 @@ function getEmbedBlockSettings( { title, icon, category = 'embed' } ) {
}

render() {
const { html, type, error, fetching } = this.state;
const { type, error, fetching } = this.state;
const { align, url, caption } = this.props.attributes;
const { setAttributes, focus, setFocus } = this.props;
const updateAlignment = ( nextAlign ) => setAttributes( { align: nextAlign } );
Expand All @@ -137,7 +142,7 @@ function getEmbedBlockSettings( { title, icon, category = 'embed' } ) {
];
}

if ( ! html ) {
if ( ! this.state.html ) {
const label = sprintf( __( '%s URL' ), title );

return [
Expand Down Expand Up @@ -180,7 +185,7 @@ function getEmbedBlockSettings( { title, icon, category = 'embed' } ) {
</Placeholder>
) : (
<div className="wp-block-embed__wrapper">
<SandBox html={ html } title={ iframeTitle } type={ type } />
<SandBox html={ this.state.html } title={ iframeTitle } type={ type } />
</div>
) }
{ ( caption && caption.length > 0 ) || !! focus ? (
Expand Down
Loading