Skip to content

Commit 20da270

Browse files
committed
Represent Editable value as array tree
1 parent 600cb2a commit 20da270

16 files changed

+508
-323
lines changed

blocks/api/index.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,4 @@ export {
2121
hasBlockSupport,
2222
isReusableBlock,
2323
} from './registration';
24-
24+
export { nodeListToTree } from './matchers';

blocks/api/matchers.js

+55-8
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,61 @@
1-
/**
2-
* WordPress dependencies
3-
*/
4-
import { createElement } from '@wordpress/element';
5-
61
/**
72
* External dependencies
83
*/
9-
import { nodeListToReact, nodeToReact } from 'dom-react';
104
export { attr, prop, html, text, query } from 'hpq';
115

6+
export function buildTree( type, attributes, ...children ) {
7+
children = children.map( ( child ) => {
8+
if ( 'boolean' === typeof child ) {
9+
child = null;
10+
}
11+
12+
if ( null === child || undefined === child ) {
13+
child = '';
14+
} else if ( 'number' === typeof child ) {
15+
child = String( child );
16+
}
17+
18+
if ( 'string' === typeof child ) {
19+
return child;
20+
}
21+
22+
return buildTree( child );
23+
} );
24+
25+
return [ type, attributes, children ];
26+
}
27+
28+
export function nodeListToTree( nodeList, createElement ) {
29+
return [ ...nodeList ].map( ( node ) => nodeToTree( node, createElement ) );
30+
}
31+
32+
export function elementAsArray( type, attributes, children ) {
33+
return [ type, attributes, children ];
34+
}
35+
36+
export function nodeToTree( node, createElement = elementAsArray ) {
37+
if ( ! node ) {
38+
return null;
39+
}
40+
41+
if ( node.nodeType === 3 ) {
42+
return node.nodeValue;
43+
}
44+
45+
if ( node.nodeType !== 1 ) {
46+
return null;
47+
}
48+
49+
const type = node.nodeName.toLowerCase();
50+
const attributes = [ ...node.attributes ].reduce( ( result, { name, value } ) => {
51+
result[ name ] = value;
52+
return result;
53+
}, {} );
54+
const children = nodeListToTree( node.childNodes );
55+
56+
return createElement( type, attributes, children );
57+
}
58+
1259
export const children = ( selector ) => {
1360
return ( domNode ) => {
1461
let match = domNode;
@@ -18,7 +65,7 @@ export const children = ( selector ) => {
1865
}
1966

2067
if ( match ) {
21-
return nodeListToReact( match.childNodes || [], createElement );
68+
return nodeListToTree( match.childNodes );
2269
}
2370

2471
return [];
@@ -33,6 +80,6 @@ export const node = ( selector ) => {
3380
match = domNode.querySelector( selector );
3481
}
3582

36-
return nodeToReact( match, createElement );
83+
return nodeToTree( match );
3784
};
3885
};

blocks/editable/index.js

+29-13
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import {
1414
defer,
1515
noop,
1616
} from 'lodash';
17-
import { nodeListToReact } from 'dom-react';
1817
import 'element-closest';
1918

2019
/**
@@ -28,7 +27,7 @@ import { Slot, Fill } from '@wordpress/components';
2827
* Internal dependencies
2928
*/
3029
import './style.scss';
31-
import { rawHandler } from '../api';
30+
import { rawHandler, nodeListToTree } from '../api';
3231
import FormatToolbar from './format-toolbar';
3332
import TinyMCE from './tinymce';
3433
import { pickAriaProps } from './aria';
@@ -37,6 +36,23 @@ import { EVENTS } from './constants';
3736

3837
const { BACKSPACE, DELETE, ENTER } = keycodes;
3938

39+
function toElement( value ) {
40+
if ( ! value ) {
41+
return null;
42+
}
43+
44+
if ( ! Array.isArray( value ) ) {
45+
return value;
46+
}
47+
48+
const [ type, attributes, children ] = value;
49+
if ( ! attributes || attributes.constructor !== Object ) {
50+
return value.map( toElement );
51+
}
52+
53+
return createElement( type, attributes, children.map( toElement ) );
54+
}
55+
4056
function createTinyMCEElement( type, props, ...children ) {
4157
if ( props[ 'data-mce-bogus' ] === 'all' ) {
4258
return null;
@@ -46,11 +62,11 @@ function createTinyMCEElement( type, props, ...children ) {
4662
return children;
4763
}
4864

49-
return createElement(
65+
return [
5066
type,
5167
omitBy( props, ( value, key ) => key.indexOf( 'data-mce-' ) === 0 ),
52-
...children
53-
);
68+
children,
69+
];
5470
}
5571

5672
function isLinkBoundary( fragment ) {
@@ -559,8 +575,8 @@ export default class Editable extends Component {
559575
const index = dom.nodeIndex( selectedNode );
560576
const beforeNodes = childNodes.slice( 0, index );
561577
const afterNodes = childNodes.slice( index + 1 );
562-
const beforeElement = nodeListToReact( beforeNodes, createTinyMCEElement );
563-
const afterElement = nodeListToReact( afterNodes, createTinyMCEElement );
578+
const beforeElement = nodeListToTree( beforeNodes, createTinyMCEElement );
579+
const afterElement = nodeListToTree( afterNodes, createTinyMCEElement );
564580

565581
this.setContent( beforeElement );
566582
this.props.onSplit( beforeElement, afterElement );
@@ -614,8 +630,8 @@ export default class Editable extends Component {
614630
const beforeFragment = beforeRange.extractContents();
615631
const afterFragment = afterRange.extractContents();
616632

617-
const beforeElement = nodeListToReact( beforeFragment.childNodes, createTinyMCEElement );
618-
const afterElement = isLinkBoundary( afterFragment ) ? [] : nodeListToReact( afterFragment.childNodes, createTinyMCEElement );
633+
const beforeElement = nodeListToTree( beforeFragment.childNodes, createTinyMCEElement );
634+
const afterElement = isLinkBoundary( afterFragment ) ? [] : nodeListToTree( afterFragment.childNodes, createTinyMCEElement );
619635

620636
this.setContent( beforeElement );
621637
this.props.onSplit( beforeElement, afterElement, ...blocks );
@@ -668,8 +684,8 @@ export default class Editable extends Component {
668684
this.setContent( this.props.value );
669685

670686
this.props.onSplit(
671-
nodeListToReact( before, createTinyMCEElement ),
672-
nodeListToReact( after, createTinyMCEElement )
687+
nodeListToTree( before, createTinyMCEElement ),
688+
nodeListToTree( after, createTinyMCEElement )
673689
);
674690
}
675691

@@ -709,7 +725,7 @@ export default class Editable extends Component {
709725
}
710726

711727
getContent() {
712-
return nodeListToReact( this.editor.getBody().childNodes || [], createTinyMCEElement );
728+
return nodeListToTree( this.editor.getBody().childNodes || [], createTinyMCEElement );
713729
}
714730

715731
updateFocus() {
@@ -862,7 +878,7 @@ export default class Editable extends Component {
862878
getSettings={ this.getSettings }
863879
onSetup={ this.onSetup }
864880
style={ style }
865-
defaultValue={ value }
881+
defaultValue={ toElement( value ) }
866882
isPlaceholderVisible={ isPlaceholderVisible }
867883
aria-label={ placeholder }
868884
{ ...ariaProps }

blocks/library/pullquote/index.js

+2-6
Original file line numberDiff line numberDiff line change
@@ -120,9 +120,7 @@ registerBlockType( 'core/pullquote', {
120120

121121
return (
122122
<blockquote className={ `align${ align }` }>
123-
{ value && value.map( ( paragraph, i ) =>
124-
<p key={ i }>{ paragraph.children && paragraph.children.props.children }</p>
125-
) }
123+
{ value.map( ( paragraph ) => paragraph.children ) }
126124
{ citation && citation.length > 0 && (
127125
<cite>{ citation }</cite>
128126
) }
@@ -145,9 +143,7 @@ registerBlockType( 'core/pullquote', {
145143

146144
return (
147145
<blockquote className={ `align${ align }` }>
148-
{ value && value.map( ( paragraph, i ) =>
149-
<p key={ i }>{ paragraph.children && paragraph.children.props.children }</p>
150-
) }
146+
{ value.map( ( paragraph ) => paragraph.children ) }
151147
{ citation && citation.length > 0 && (
152148
<footer>{ citation }</footer>
153149
) }

blocks/library/quote/index.js

+2-6
Original file line numberDiff line numberDiff line change
@@ -238,9 +238,7 @@ registerBlockType( 'core/quote', {
238238
className={ style === 2 ? 'is-large' : '' }
239239
style={ { textAlign: align ? align : null } }
240240
>
241-
{ value.map( ( paragraph, i ) => (
242-
<p key={ i }>{ paragraph.children && paragraph.children.props.children }</p>
243-
) ) }
241+
{ value.map( ( paragraph ) => paragraph.children ) }
244242
{ citation && citation.length > 0 && (
245243
<cite>{ citation }</cite>
246244
) }
@@ -267,9 +265,7 @@ registerBlockType( 'core/quote', {
267265
className={ `blocks-quote-style-${ style }` }
268266
style={ { textAlign: align ? align : null } }
269267
>
270-
{ value.map( ( paragraph, i ) => (
271-
<p key={ i }>{ paragraph.children && paragraph.children.props.children }</p>
272-
) ) }
268+
{ value.map( ( paragraph ) => paragraph.children ) }
273269
{ citation && citation.length > 0 && (
274270
<footer>{ citation }</footer>
275271
) }

blocks/test/fixtures/core__heading__h2-em.json

+7-4
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,13 @@
66
"attributes": {
77
"content": [
88
"The ",
9-
{
10-
"type": "em",
11-
"children": "Inserter"
12-
},
9+
[
10+
"em",
11+
{},
12+
[
13+
"Inserter"
14+
]
15+
],
1316
" Tool"
1417
],
1518
"nodeName": "H2"

blocks/test/fixtures/core__list__ul.json

+47-28
Original file line numberDiff line numberDiff line change
@@ -6,37 +6,56 @@
66
"attributes": {
77
"nodeName": "UL",
88
"values": [
9-
{
10-
"type": "li",
11-
"children": "Text & Headings"
12-
},
13-
{
14-
"type": "li",
15-
"children": "Images & Videos"
16-
},
17-
{
18-
"type": "li",
19-
"children": "Galleries"
20-
},
21-
{
22-
"type": "li",
23-
"children": "Embeds, like YouTube, Tweets, or other WordPress posts."
24-
},
25-
{
26-
"type": "li",
27-
"children": "Layout blocks, like Buttons, Hero Images, Separators, etc."
28-
},
29-
{
30-
"type": "li",
31-
"children": [
9+
[
10+
"li",
11+
{},
12+
[
13+
"Text & Headings"
14+
]
15+
],
16+
[
17+
"li",
18+
{},
19+
[
20+
"Images & Videos"
21+
]
22+
],
23+
[
24+
"li",
25+
{},
26+
[
27+
"Galleries"
28+
]
29+
],
30+
[
31+
"li",
32+
{},
33+
[
34+
"Embeds, like YouTube, Tweets, or other WordPress posts."
35+
]
36+
],
37+
[
38+
"li",
39+
{},
40+
[
41+
"Layout blocks, like Buttons, Hero Images, Separators, etc."
42+
]
43+
],
44+
[
45+
"li",
46+
{},
47+
[
3248
"And ",
33-
{
34-
"type": "em",
35-
"children": "Lists"
36-
},
49+
[
50+
"em",
51+
{},
52+
[
53+
"Lists"
54+
]
55+
],
3756
" like this one of course :)"
3857
]
39-
}
58+
]
4059
]
4160
},
4261
"originalContent": "<ul><li>Text & Headings</li><li>Images & Videos</li><li>Galleries</li><li>Embeds, like YouTube, Tweets, or other WordPress posts.</li><li>Layout blocks, like Buttons, Hero Images, Separators, etc.</li><li>And <em>Lists</em> like this one of course :)</li></ul>"

blocks/test/fixtures/core__preformatted.json

+12-7
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,19 @@
66
"attributes": {
77
"content": [
88
"Some ",
9-
{
10-
"type": "em",
11-
"children": "preformatted"
12-
},
9+
[
10+
"em",
11+
{},
12+
[
13+
"preformatted"
14+
]
15+
],
1316
" text...",
14-
{
15-
"type": "br"
16-
},
17+
[
18+
"br",
19+
{},
20+
[]
21+
],
1722
"And more!"
1823
]
1924
},

blocks/test/fixtures/core__pullquote.json

+7-10
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,13 @@
66
"attributes": {
77
"value": [
88
{
9-
"children": {
10-
"type": "p",
11-
"key": null,
12-
"ref": null,
13-
"props": {
14-
"children": "Testing pullquote block..."
15-
},
16-
"_owner": null,
17-
"_store": {}
18-
}
9+
"children": [
10+
"p",
11+
{},
12+
[
13+
"Testing pullquote block..."
14+
]
15+
]
1916
}
2017
],
2118
"citation": [

0 commit comments

Comments
 (0)