-
Notifications
You must be signed in to change notification settings - Fork 4.2k
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
Block comparison: show difference between invalid block conversion options #7995
Conversation
5ffb94b
to
27ed264
Compare
5c9e4b3
to
0ec836a
Compare
06c5849
to
f181dce
Compare
b7196b4
to
8079b91
Compare
Noting that the comparison currently uses code from While Gutenberg does have it's own HTML parsing, and also HTML comparison (via An alternative is to re-implement Open to suggestions. |
8079b91
to
2d4e20b
Compare
Also noting that the style is a best attempt at matching that proposed in #7604, but needs some improving. @jasmussen I'd very much appreciate any suggestions/help you can give. In particular I assumed that we would want to show deleted characters in the right panel, and I've included these in red. We can not show those at all, if it makes more sense. |
Sorry I completely missed this. Feel free to assign me as a reviewer next time and I'll be sure to get to it. I'll try and take a look today, maybe push code. |
@johngodley This is impressive work. I think it's super duper solid, and it's VERY much good enough to get into master so we can iterate. I pushed a small fix so it only scrolls when necessary, and I made the menu option titlecase. Can you rebase this? Question: does this use the modal API? Assuming yes as it really seems to be, which is great, just verifying. |
8d870fc
to
58e8bab
Compare
If you mean Rebased. There's a codecov failure which I don't understand as the unit test coverage has increased. I've noticed some problems with a few of the more esoteric blocks so I'll look at fixing those. |
Yes, awesome! I'm extending the review range and adding a milestone so we can get this in. Amazing work! This is a KILLER feature. |
Shows two blocks side by side with the difference in HTML highlighted
I made a some small tweaks to the CSS regarding line height. If you had a deleted section wrapping over lines it could overlap: This has now been fixed. I noticed two other problems:
|
🏅 Yeah this is a separate issue that would be sweet to fix.
You rule the world. |
The vertical spacing would become strange if the converted preview was shorter than the original preview
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@johngodley - This is really cool. In fact, I think it's so useful I was wondering if we would want to make it more prominent?
I added some comments, I think the main one that needs to be fixed is the max-height: 70%
issue which is visible to the user.
Will probably also need someone more knowledgeable to comment on the library inclusion. Not sure who would be best.
const difference = differ.diff( originalContent, newContent ); | ||
|
||
return difference.map( ( item, pos ) => { | ||
if ( item.added ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could use the classnames
utility here to remove the need for the if and shorten to something like:
const className = classnames( {
'editor-block-compare__added': item.added,
'editor-block-compare__removed': item.removed,
} );
return <span className={ className } key={ pos }>{ item.value }</span>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Excellent idea!
|
||
return { | ||
content: block.originalContent, | ||
render: getSaveElement( blockType, block.attributes ), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would be good to make these property names consistent with the props on the BlockView, either changing them here or there. I think the BlockView names are quite nice.
render() { | ||
const { block, onKeep, onConvert, convertor, convertButtonText } = this.props; | ||
const original = this.getOriginalContent( block ); | ||
const converted = this.getConvertedContent( convertor( block ), original ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking at the definition of getConvertedContent, it seems like the second arg isn't needed.
|
||
getConvertedContent( block ) { | ||
// The convertor may return an array of items or a single item | ||
const newBlocks = Array.isArray( block ) ? block : [ block ]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I found out recently that castArray
is another succinct way to do this:
https://lodash.com/docs/4.17.10#castArray
|
||
// Ensure the modal fits the content, otherwise it could be too big | ||
.editor-block-compare { | ||
max-height: 70%; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At low screen width breakpoints the modal becomes full screen, but still only takes up 70% of screen height, so I think we need a rule to set it back to 100%
|
||
.editor-block-compare__wrapper { | ||
display: flex; | ||
justify-content: space-between; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure space-between
is needed as the content seems to take up 100% of the width.
} | ||
} | ||
|
||
.editor-block-compare__converted { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we have any thoughts on nesting element selectors inside the block selector @jasmussen? It might introduce unnecessary specificity, but then I'm not sure if we'd expect to have other styles try to override these, so probably ok.
One option is to do &__converted
and in the resulting css the parent selector won't be included, but that only works one level deep.
padding-bottom: $panel-padding; | ||
|
||
> div { | ||
display: flex; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think flex is needed here, could just rely on normal layout.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's being used to push the buttons to the same position at the bottom of the modal, otherwise they become vertically misaligned.
|
||
if ( compare ) { | ||
return ( | ||
<Modal |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just some food for thought - I think I would have approached this differently and made BlockCompareModal a component that manages its own open/closed state. I'm not sure there's a right or wrong.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, it's a good point. My thinking is that the comparison component could (potentially, and currently without any need) be used elsewhere that might be outside of a modal or an on/off situation and so I made the component do it's thing, and left it up to the parent to decide when to show it.
It’s not necessary for anything and makes the modal too small on mobile
Thanks for the review @talldan. I do wonder if it might make more sense if the 'convert to blocks' button just defaults to showing the comparison component directly, and we remove the option from the dropdown menu? The effect of 'convert to blocks' is unknown to the user, and sometimes quite arbitrary and destructive. At the point the user is being presented with this warning I don't think it's excessive to add an additional click in the process. It saves having to undo the action and being annoyed at Gutenberg if the conversion isn't as expected. |
Without this the button doesnt stick to the bottom
/** | ||
* External dependencies | ||
*/ | ||
import classname from 'classnames'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a typo here, the imported function should be classnames
instead of classname
@johngodley This is looking good. I've spotted one additional thing here: I think the only thing we need now is to confirm that the script/dependency inclusion is ok. (edit: worth noting that at least if we use an npm package, the licence is checked by the licence-checker script: #8808). On the user experience, I feel like it could definitely be iterated on this so that it's part of the workflow when editing a block HTML:
There are other times when the user might end up with invalid html (e.g. using the classic editor), so we might present that slightly differently to what I've proposed above. Would be good to get additional input on this though. |
Thanks for that link, and it's pushed me towards just bundling the module normally |
@@ -0,0 +1,28 @@ | |||
/** |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm a bit hesitant about including a component BlockCompare
in the components
package, since it's not as general-purpose as intended by the package, but rather domain-specific to the idea of WordPress editors. Was editors
considered instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure what you mean by editors - can you point me in the direction?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah ok. I'm not entirely clear on the difference between editor and WordPress editors components. Are you suggesting it work like the keyboard shortcuts modal and isModalActive
?
If the concern is the general purposeness of the component then could it be made local to the invalid warning component? I'm not sure if modals are designed to be used locally like this as the only other one I can find is the keyboard shortcut modal, which is 'global'.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again I can't but make educated guesses — I think it's something about the packages getting published on NPM but not sure.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll wait for Andrew to clarify, but the component is already in the editor
package, not in the components
package, so maybe it was a misread. Or maybe he did mean edit-post
, but I'm not sure we should be implementing components from edit-post
in editor
like this would be.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oof, I'm sorry for the trouble, it was definitely a misread on my part.
I had mistaken it as being part of packages/components
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is really close, I just spotted one thing in further testing. Will also wait for @aduth to clarify his comment.
|
||
return { | ||
rawContent: block.originalContent, | ||
renderedContent: getSaveElement( blockType, block.attributes ), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've just given this another test, and I'm not sure the original content's preview displays quite what I was expecting. My steps:
- Add some simple text to a paragraph block
- Edit the html and add an inline style
style="font-size:25px;"
- View the comparison
- Note that the original content preview doesn't show the font size at 25px, which is what I'd get if I selected 'Keep as HTML'.
To make the preview display correctly I tried using RawHTML like the HTML block does for its preview, which seemed to do the trick:
getOriginalContent( block ) {
return {
rawContent: block.originalContent,
renderedContent: (
<RawHTML>{ block.originalContent }</RawHTML>
),
};
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting!
RawHTML uses dangerouslySetInnerHTML
, which opens up things like <p onclick="alert(1)">fdsfsdfds fd sfds</p>
. I'm not sure if this is a problem in this context and what the behaviour is supposed to be so I'll have a poke around.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The 'preview' mode of the HTML block uses the <Sandbox />
component to display arbitrary HTML, which neutralises my above example, and seems to be the way to go. The styling is different so I'll see if it can be made to fit in.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After some playing around it seems <Sandbox />
is an iframe and while it does prevent the JS above, it is also unstyled.
Although I think the scope for mischief is small I'm not sure we should introduce it.
With that in mind we can either accept the unstyled appearance (looks ok for paragraphs, doesnt look ok for images), or keep the current not-quite-full preview (it's displaying the block without the invalid bits, but with the valid changes). There may be another way I'm unaware of, too
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, yep, thanks for looking into it. Lets go with what we've got and then maybe look into this on a separate issue.
Thanks for the feedback and suggestions! |
Amazing work! |
return ( | ||
<div className={ className }> | ||
<div className="editor-block-compare__content"> | ||
<h1 className="components-modal__header-heading">{ title }</h1> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not the modal component, so should not be stealing / inheriting styles. Components should be insulated from one another to avoid maintenance issues with cascading consequences of future changes.
</div> | ||
|
||
<div className="editor-block-compare__action"> | ||
<Button isLarge tabindex="0" onClick={ action }>{ actionText }</Button> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm guessing a console warning is being logged on this line?
We should pass tabIndex
as the DOM property tabIndex={ 0 }
, not the attribute name.
Note that you should still use the canonical React naming for known attributes:
// Yes, please <div tabIndex="-1" /> // Warning: Invalid DOM property `tabindex`. Did you mean `tabIndex`? <div tabindex="-1" />
In other words, the way you use DOM components in React hasn’t changed, but now you have some new capabilities.
https://reactjs.org/blog/2017/09/08/dom-attributes-in-react-16.html
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oddly it doesn't log a warning, but both very valid points and I'll get those fixed in another PR
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Description
When you have an invalid block the result of the available actions can be unknown.
This PR adds a 'compare conversion' option that shows the actual HTML difference when 'convert to blocks' is applied.
The difference appears in a modal and the user can select to keep the current contents or apply the conversion, with full knowledge of the result of their action.
The modal adjusts to the height of the block:
Overflowing, if too tall, and scrollable:
And adjusts accordingly on smaller screens (using overflow scroll if too tall):
Note: this is based on the discussion in #7604
How has this been tested?
Types of changes
This adds a new feature.
Checklist: