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

Manage Editable value as React element #466

Merged
merged 5 commits into from
Apr 24, 2017
Merged

Manage Editable value as React element #466

merged 5 commits into from
Apr 24, 2017

Conversation

aduth
Copy link
Member

@aduth aduth commented Apr 20, 2017

Closes #421

This pull request seeks to address issues with treating the Editable value as a string. Specifically, the need to manage a string value counteracts the promise of avoiding manual manipulation of HTML and adds complexity to serialization.

Related discussion at #413 (comment)

Implementation details:

  • Introduces a new children hpq matcher to extract content as a React element, using html-to-react
  • Enhances renderToString to handle more incoming value types (falsey, string, array)

It's admittedly concerning to apply even more parsing in the form of HTML → React transformation, especially in juggling the incoming and outgoing value on the Editable (TinyMCE) component. I think we'll need to address this sooner than later, but the changes introduced here are aimed at shaping expectations of managing attributes of a block which contain elements.

Testing instructions:

Verify that there are no regressions of existing behavior:

  • Confirming serialization of changes are respected (switching to HTML mode)
  • Text blocks can contain multiple paragraphs
  • Text block splitting

Ensure included tests pass:

npm test

@aduth aduth added [Feature] Blocks Overall functionality of blocks Framework Issues related to broader framework topics, especially as it relates to javascript labels Apr 20, 2017
this.editor.selection.moveToBookmark( bookmark );
}

setContent( content ) {
if ( ! content ) {
return '';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we setContent( '' ) instead? What if it's an update?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we setContent( '' ) instead?

Oh, yes, I think I meant for this line to be content = '';

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in rebased d4f5a57c4854c11d405691b37824e63cefb3eab1.

} ) );
} }
/>
);
},

save( { attributes } ) {
const { align, content } = attributes;
let { content } = attributes;
if ( ! Array.isArray( content ) ) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this be something different than an array of undefined ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this be something different than an array of undefined ?

Yes, I've added some clarifying comments and slightly adjusted the behavior to return early on an undefined content in the rebased d4f5a57c4854c11d405691b37824e63cefb3eab1.

Specifically:

// Content can be in the following shapes, so we normalize to an array:
// - Single paragraph: Object
// - Multiple paragraph: Array of objects

}

return content.map( ( paragraph ) => {
if ( 'string' === typeof paragraph ) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When do we have a string here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When do we have a string here?

When splitting a paragraph into two, the default TinyMCE getContent was including a newline between the paragraphs which when parsed by html-to-react resulted in an [ Element, '\n', Element ]. In the rebased 075237ef5d3947a9f784c27580bf6ba9a9193a4e, I changed this to get content as raw formatted with the assumption that clean-up will occur as a result of html-to-react and renderToString (see also extended commit description).

return '';
}

content = wp.element.renderToString( content );
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we be concerned about performance here? renderToString on each render and parse on each onChange ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we be concerned about performance here? renderToString on each render and parse on each onChange ?

Yes 😄

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not the intent of the change, but a trade-off that could help performance slightly is change to retrieve content from editor instance as raw formatted in 075237ef5d3947a9f784c27580bf6ba9a9193a4e.

// - Single paragraph: Object
// - Multiple paragraph: Array of objects
if ( ! Array.isArray( content ) ) {
content = [ content ];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could we use castArray

Copy link
Contributor

@youknowriad youknowriad left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The thing that bothers me with this, is that Plugin authors would need to understand well React Elements.

The fact that it could be sometimes a single element and sometimes an array is a bit confusing I think. Shouldn't we always castArray in the parser and in the Editable?

Anyway, this solves the dangerouslySetInnerHTML issue and is worth a try

* @param {?Object} props Props to apply to cloned element
* @return {WPElement} Cloned element
*/
export { cloneElement };
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Honestly, I don't see much value in abstracting "React"

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Honestly, I don't see much value in abstracting "React"

What are you suggesting here?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we just drop wp.element :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could probably be convinced. Needs to be a decision made with future compatibility in mind, related to some of the "reason to be" outlined in the docs:

https://github.com/WordPress/gutenberg/tree/master/element#element

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To clarify my point, I don't see much value in abstracting React because we're using a lot of React features: Element, Component (refs, state, lifecycle), cloneElement, renderToString, render. Which is close to be the whole React.

Also, and this is probably the main reason for me. If we want to use a library like react-textarea-autosize or react-tooltip or any library dependent on React, we can do so (we're already doing it) technically and it'll work because we're using React but what if we switch our wp.element implementation to a custom React alternative, nothing guarantees that the libraries we're using are only using the React features we whitelist on our implementation. And this IMO should force us to avoid using any React third-party library because "logically" they're not dependent on our abstraction but they're dependent on React (which is a larger library).

@aduth
Copy link
Member Author

aduth commented Apr 21, 2017

The thing that bothers me with this, is that Plugin authors would need to understand well React Elements.

I don't anticipate that they'd need to do any manipulation of the object, and I'm fine with it being slightly opaque in that regard. You're correct there would need to be some awareness of what the value is generally.

The goal here is to eliminate plugin author consideration of markup strings. We could simply pass the rawContent of a parsed block to the implementation and call it a day, but we've decided there's some value in forcing implementers to think of their block in terms of its data shape. That we still maintain HTML strings regresses away from this ideal (and almost encourages doing so).

So the question becomes: if we want to represent the caption, citation, or paragraphs nested elements as data, what shape does it take? I think there are some less complex options if we optimize for the value's shape to be inspected, but I don't think we care. A React tree seemed a natural fit into what we're already building.

Aside: I'm not claiming you disagree with me on any of these points. Just trying to clarify (for myself and others) the rationale for this change.

@aduth
Copy link
Member Author

aduth commented Apr 21, 2017

The fact that it could be sometimes a single element and sometimes an array is a bit confusing I think. Shouldn't we always castArray in the parser and in the Editable?

I'm not totally clear on the ramifications of this change, but I think it could make sense, sure.

@aduth
Copy link
Member Author

aduth commented Apr 21, 2017

Rebased again to resolve conflicts, and account for newly added Embed block.

@youknowriad 0924fe6 is an improved approach to handling differing shapes of the parsed content, leveraging the fact that React.Children exists for this very purpose:

utilities for dealing with the this.props.children opaque data structure

https://facebook.github.io/react/docs/react-api.html#react.children

And yes, I can totally appreciate that including this adds fuel to your argument that we should drop the abstraction 😆 Let's do that... but not here.

@aduth aduth force-pushed the add/query-children branch from 0924fe6 to f67320a Compare April 21, 2017 19:39
aduth added 5 commits April 24, 2017 08:24
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
Designed for this purpose of handling opaque structure of children

https://facebook.github.io/react/docs/react-api.html#react.children
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Feature] Blocks Overall functionality of blocks Framework Issues related to broader framework topics, especially as it relates to javascript
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants