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

Implement pasting external content with plain and styled text #13841

Merged
merged 12 commits into from
Feb 19, 2019

Conversation

mkevins
Copy link
Contributor

@mkevins mkevins commented Feb 12, 2019

Description

This PR aims to implement paste handling (targets 1 and 2) for the Mobile Gutenberg project. It is part of the work to address #238. The goal is to respond to a paste event from ReactNative code and transform the data for use with Gutenberg's existing paste handling implementations.

How has this been tested?

This PR is a work in progress, and so far testing has been manual (some manual tests done for Android and known issues are described here). When the implementation is more complete / stable, mock events will be created, as well as tests for the implementations.

A local test environment has been set up using the ./bin/setup-local-env.sh script.

DOM implementation on mobile

In order to implement these changes, it was necessary to modify several areas of the codebase. This mostly involved changes to *.native.js (Mobile Gutenberg only) files, but some shared code as well. In particular, a few extra safety checks have been added for situations where properties or methods are null or undefined in the ReactNative environment due to an incomplete implementation of DOM classes.

The codepaths utilizing some DOM methods can be hard to follow at times. As an example, when the pasteHandler function calls deepFilterHTML, it uses the specialCommentConverter filter. The raw-handling/special-comment-converter.js file has a dependency on @wordpress/dom, and makes use of the remove and replace functions from there.

There is a comment in the rawHandler function describing the use of the specialCommentConverter filter there: // Needed to create more and nextpage blocks. As pasteHandler uses the same filter and calls through to the same function (deepFilterHTML) I believe this comment's description applies to the filter's use in pasteHandler as well.

Deeper into this codepath, where deepFilterNodeList makes use of the filters (the forEach loop), is where the need arises for the contains method (which had to be patched into jsdom-jscore). In the forEach loop, item is actually the filter function.

Screenshots

Pasting styled text:

Pasting styled text with image:

Types of changes

  • For more flexibility in code-sharing, I refactored pasteHandler from raw-handling/index.js into its own file.
  • I added safety checks for jsdom methods/properties.
  • I refactored splitContent for use with onEnter and onPaste, and implemented createRecord for converting native content to the RichText format used in several other methods.
  • I implemented onPaste for plain and styled text.
  • I mirrored the implementation of onReplace from edit.js in edit.native.js, and pasted image blocks are now functional in the demo app.

Notes

Originally, I had planned on approaching this task in stages as described in the original issue. However, I realized upon implementing plain text paste, that styled text was implemented with very little additional effort.

Edit: Similarly, images are now working with the above approach, after implementing onReplace.

Related PRs

@daniloercoli daniloercoli requested review from hypest and daniloercoli and removed request for youknowriad, oandregal, noisysocks, gziolo and aduth February 12, 2019 18:40
@gziolo gziolo added the Mobile App - i.e. Android or iOS Native mobile impl of the block editor. (Note: used in scripts, ping mobile folks to change) label Feb 12, 2019
@mkevins mkevins force-pushed the try/pasting-external-content branch from 4b0b0bf to 304978b Compare February 13, 2019 11:38
@hypest hypest requested review from diegoreymendez and removed request for hypest February 14, 2019 09:36
@diegoreymendez
Copy link
Contributor

@mkevins - I'll be testing these changes through gutenberg-mobile, and will let you know if there's anything.

One suggestion I wanted to make for future PRs, is to avoid moving large blocks of code between files, unless the PR is specifically about that.

The reason behind this suggestion is that during the review the diff becomes a mix of code-changes and code-movements that becomes hard to differentiate, and as such, hard to validate.

@mkevins
Copy link
Contributor Author

mkevins commented Feb 14, 2019

@mkevins - I'll be testing these changes through gutenberg-mobile, and will let you know if there's anything.

Thank you for testing it out!

One suggestion I wanted to make for future PRs, is to avoid moving large blocks of code between files, unless the PR is specifically about that.

The reason behind this suggestion is that during the review the diff becomes a mix of code-changes and code-movements that becomes hard to differentiate, and as such, hard to validate.

Thanks for the suggestion. I was torn on this, as it wasn't clear which would be more noisy, splitting on PRs with multiple related PRs across projects, or reducing the number of PRs and clarifying the changes in the commit message. I refactored (the big code movements) in the first commit and isolated those changes by commit. I'll keep this in mind in future PRs, and try to keep them smaller.

If you have any questions, or want clarification on any part of the code, I'll be happy to help.

@mkevins
Copy link
Contributor Author

mkevins commented Feb 15, 2019

I added a commit to refactor rawHandler back into raw-handling/index.js (rather than its own file), as it is not required on the native side for this implementation. Hopefully, this will make reviewing the PR easier.

@hypest hypest requested a review from ellatrix February 15, 2019 12:44
@hypest
Copy link
Contributor

hypest commented Feb 15, 2019

👋 @iseulde , can you make a pass on this PR? It touches some files used from the web too so, just want to make sure we don't break anything there. Thanks!

@@ -4,7 +4,9 @@
import { wrap, replaceTag } from '@wordpress/dom';

export default function( node, doc ) {
if ( node.nodeName === 'SPAN' ) {
// In jsdom-jscore, 'node.style' can be null.
Copy link
Contributor

Choose a reason for hiding this comment

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

Kinda a nit but, perhaps it's not too clear what jsdom-jscore means in this context. Of course, jsdom-jscore it's the DOM parser we're using in the native mobile (React Native) version of Gutenberg, but I think I'd suggest add that bit to the comment to, to help out web-side friends read the code more easily.

So, my suggestion is to refer with In native mobile (jsdom-jscore), ... instead of the current. What do you think? Same for a couple other similar references in this same PR.

Copy link
Contributor

@hypest hypest Feb 15, 2019

Choose a reason for hiding this comment

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

On a less of a nit thing, I wonder about the (emphasis mine on the "can"):

'node.style' *can* be null.

Does that mean that sometimes node.style is undefined and some other it's not?

In the cases where it's undefined, is it OK to just ignore it like we're doing currently in the PR? I wonder if being undefined denotes an unrecoverable error and we should let the user know about it, in some way.

Should we at least log an error/warning?

There are a few places in the PR to ask the same question, but let's discuss it here and then we apply accordingly.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, good suggestion! I'll edit those, and keep that in mind for future comments.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Does that mean that sometimes node.style is undefined and some other it's not?

With the change of wording in the comment to: In native mobile (jsdom-jscore), ..., it's probably more accurate to say that node.style is undefined.

Should we at least log an error/warning?

In the codepath where this is used, node is actually an HTMLElement, as the condition is satisfied that the element's nodeName is SPAN. HTMLElement nodes are supposed to have a style property which returns a read-only CSSStyleDeclaration object, which can be used to retrieve the inline styles applied to the element via the style attribute. This is then used to wrap the node in appropriate tag(s) (strong, em, etc.). In the case where the DOM implementation does not support the style property on a node, I believe it is safe to ignore, but that we may lose style information that was associated with the content via inline styles on a span's attribute.

The property seems to have had wide support for a long time, but I wonder if there are any browsers that don't support this property, or that leave it undefined or null when no style attribute was present on the element. My reason for wording the comment somewhat "vaguely" is that I tend to assume that there could be such a case in the wild, and thus the safety check could be useful even in the browser implementation. I checked the browser compatibility for it on MDN, and it lists a few question marks, but that just means unconfirmed AFAIK.

TL;DR:

The check will not lead to an unrecoverable error, but will gracefully degrade the feature that normalizes some inline styles on span tags. I don’t have a strong opinion on whether that warrants logging a warning; I suppose it depends on the verbosity desired (e.g. web: “please use a better browser :)”, mobile: "mobile currently lacks support for this feature").

Copy link
Contributor

Choose a reason for hiding this comment

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

mobile: "mobile currently lacks support for this feature"

I think that's a good case to actually log since, these things are useful while offering support to users.

While at it, since this is a known limitation, let's add a "Known Limitations" section to wordpress-mobile/gutenberg-mobile#553 and mention this there. WDYT?

@@ -92,35 +98,32 @@ export class RichText extends Component {
* handler.
*
*/
splitContent( htmlText, start, end ) {
splitContent( currentRecord, blocks = [], isPasted = false ) {
const { onSplit } = this.props;

if ( ! onSplit ) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Not strictly related to this PR but, it seems that the onSplit prop is already marked as deprecated on the web side so, since we're touching the splitContent method, what do you think about mirroring this (https://github.com/WordPress/gutenberg/blob/master/packages/editor/src/components/rich-text/index.js#L84-L93) here too?

I'd suggest a follow up PR with that since it's not a blocker for this PR and adding more diff to this is not desirable :)

@@ -92,35 +98,32 @@ export class RichText extends Component {
* handler.
*
*/
splitContent( htmlText, start, end ) {
splitContent( currentRecord, blocks = [], isPasted = false ) {
Copy link
Contributor

@hypest hypest Feb 15, 2019

Choose a reason for hiding this comment

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

isPasted = false

I understand that we just need this flag here but, may I suggest copying what the web side is providing the corresponding method with? The idea is to stay as close as possible to the web code, wishing we can refactor and re-use the same code eventually.

I this case, let's pass a context object with a paste (boolean) field. WDYT?

(By the way, only reason I'm not just recommending extracting this method into a common file with the web is that there's this lastEventCount trigger in this one, and extraction would make the code complex.)

Copy link
Contributor

@hypest hypest left a comment

Choose a reason for hiding this comment

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

👋 @mkevins , great work here, works really nicely!

I left a few comments which I think could use some minor work. Good thing is, I don't see any blockers for this PR in any of those! I'd suggest we tackle them all in follow up PR(s) and go ahead and merge this one. Let me know if that's OK with you too.

If OK with you, then please open a ticket with a checklist of all the known issues and additional work needed so we can more easily work against them.

Copy link
Contributor

@hypest hypest left a comment

Choose a reason for hiding this comment

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

Looks good to me! Nice work @mkevins, thanks!

I'll wait for the restarted Travis job to get green and I will merge this PR.

When you get the chance, please open a ticket in the Gutenberg-mobile repo with a checklist of all the known issues and additional work needed so we can more easily work against them.

@mkevins
Copy link
Contributor Author

mkevins commented Feb 20, 2019

When you get the chance, please open a ticket in the Gutenberg-mobile repo with a checklist of all the known issues and additional work needed so we can more easily work against them.

I've added this issue to track the related work:
wordpress-mobile/gutenberg-mobile#624

@youknowriad youknowriad added this to the 5.2 (Gutenberg) milestone Mar 4, 2019
youknowriad pushed a commit that referenced this pull request Mar 6, 2019
* Refactor raw-handler and paste-handler into their own files

* Add undefined and null checks for jsdom-jscore node methods/properties

* Implement createRecord method for converting native content to RichText

* Refactor splitContent and onEnter for common interface with onPaste

* Begin implementing onPaste for plain and styled text

* Fix some lint errors and some invalid references.

* Add undefined check for jsdom-jscore node in phrasing-content-reducer

* Add onReplace method to ParagraphEdit

* Refactor raw-handler back into index.js

* Remove unused parameters from RichText create method
youknowriad pushed a commit that referenced this pull request Mar 6, 2019
* Refactor raw-handler and paste-handler into their own files

* Add undefined and null checks for jsdom-jscore node methods/properties

* Implement createRecord method for converting native content to RichText

* Refactor splitContent and onEnter for common interface with onPaste

* Begin implementing onPaste for plain and styled text

* Fix some lint errors and some invalid references.

* Add undefined check for jsdom-jscore node in phrasing-content-reducer

* Add onReplace method to ParagraphEdit

* Refactor raw-handler back into index.js

* Remove unused parameters from RichText create method
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Mobile App - i.e. Android or iOS Native mobile impl of the block editor. (Note: used in scripts, ping mobile folks to change)
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants