Skip to content

Inserter: Replace the selected block if empty#4917

Merged
youknowriad merged 4 commits intomasterfrom
add/replace-empty-blocks
Feb 8, 2018
Merged

Inserter: Replace the selected block if empty#4917
youknowriad merged 4 commits intomasterfrom
add/replace-empty-blocks

Conversation

@youknowriad
Copy link
Copy Markdown
Contributor

@youknowriad youknowriad commented Feb 7, 2018

closes #3627

this PR update the inserter to replace the selected block if it's an empty block.

Testing instructions

  • Add an empty paragraph block
  • Select it
  • Add an image block from the inserter
  • The image block should replace the empty block instead of adding a new block

@youknowriad youknowriad self-assigned this Feb 7, 2018
@youknowriad
Copy link
Copy Markdown
Contributor Author

So I managed to avoid the isEmpty block API by checking that the attributes of the block are equal to a newly inserted default block.

  • If we detect that the selected block is a default block and its attributes are untouched, replace it
  • If not, inserter after it

There's a small difference here compared to the original isEmpty implementation because this means if you trigger drop cap or change a color without inserting any text, this block won't be replaced but I guess it makes sense. cc @jasmussen

@jasmussen
Copy link
Copy Markdown
Contributor

Impressive, and thank you for working on this.

When it works, it is glorious.

But I can't get it to work all the time, though:

insert replace

@youknowriad youknowriad force-pushed the add/replace-empty-blocks branch from 228466a to 73c4975 Compare February 7, 2018 12:44
@youknowriad
Copy link
Copy Markdown
Contributor Author

@jasmussen What about now?

@youknowriad
Copy link
Copy Markdown
Contributor Author

Sorry there's an error on the last commit fixing

@youknowriad youknowriad force-pushed the add/replace-empty-blocks branch from 73c4975 to 5581715 Compare February 7, 2018 13:01
@youknowriad
Copy link
Copy Markdown
Contributor Author

youknowriad commented Feb 7, 2018

So there are still some issues where sometimes the content attribute is equal to [""] which is different from the default value [] so we're considering this as a modified block but in really they are equivalent.

Not sure how to fix this, maybe we should rollback to the isEmpty API as it's more controlled :(

@jasmussen
Copy link
Copy Markdown
Contributor

This is solid work for me. I love it. It definitely works for me.

Though CC: @mtias regarding isEmpty. We might need it.

@ellatrix
Copy link
Copy Markdown
Member

ellatrix commented Feb 7, 2018

So there are still some issues where sometimes the content attribute is equal to [""] which is different from the default value [] so we're considering this as a modified block but in really they are equivalent.

@youknowriad Maybe we can change this in TinyMCE to trigger a change with [] if the content is empty? Maybe this is also "safer" to catch edge cases with all the different situations the field is considered empty. We are already doing an isEmpty check on every selection change, we just need to call this.props.onChange( [] ) there as well.

Copy link
Copy Markdown
Member

@aduth aduth left a comment

Choose a reason for hiding this comment

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

I appreciate the effort to avoid expanding the block API 😄

*
* @return {boolean} Whether the block is an untouched default block
*/
export function isUntouchedDefaultBlock( block ) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Minor: What do you think of "Unmodified" instead of "Untouched" ? Not sure "touch" is the best way for describing modifications to a bock.

};

return (
block.name === newDefaultBlock.name &&
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Minor (performance): This could be an early return to avoid Object.assign cloning of keys into attributeKeys:

const newDefaultBlock = createBlock( getDefaultBlockName() );
if ( block.name !== newDefaultBlock.name ) {
	return false;
}

const attributeKeys = {
	...keys( newDefaultBlock.attributes ),
	...keys( block.attributes ),
};

return every( attributeKeys, ( key ) => (
	isEqual( newDefaultBlock.attributes[ key ], block.attributes[ key ] )
) );

*/
export function isUntouchedDefaultBlock( block ) {
const newDefaultBlock = createBlock( getDefaultBlockName() );
const attributeKeys = {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Any reason this ought to be an object, rather than an array?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

No just my brain making fun of me


return (
block.name === newDefaultBlock.name &&
every( attributeKeys, ( key ) => isEqual( newDefaultBlock.attributes[ key ], block.attributes[ key ] ) )
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Do we want the deep equality check of isEqual, or is shallow sufficient? (via is-equal-shallow)

(Might complicate @iseulde's suggestion of onChange( [] ), but maybe the empty value should be represented as undefined ? or null?)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I think for this function to work properly across several block types (the default block could change and we don't control the attributes shape), this should be a deep equality to avoid bugs.

Performance wise, this is only called when inserting a block (not repeatedly), so probably acceptable.

) );
const insertedBlock = createBlock( name, { ...initialAttributes, layout } );
if ( selectedBlock && isUntouchedDefaultBlock( selectedBlock ) ) {
dispatchProps.replaceBlocks( selectedBlock.uid, insertedBlock );
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Random thought: Would this be any less complex if we just allowed the block to be inserted as normal, but had a side-effect to remove the previous untouched default block?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What about this feedback?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Oh sorry about that.

Are you saying I should call another action removeBlock + insertBlocks instead of replaceBlocks: We'd still need the selectedBlock prop to check if it's unmodified or not, so we'd still need mergeProps. I'm thinking this doesn't change anything in terms of complexity

Or maybe you're saying we should use effects? Maybe it just adds unnecessary indirection don't you think? It will have the advantage of being more generic (though I'm not sure we want this to be generic: inserting at a given position, no matter how we triggered this insert will trigger the behavior)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Or maybe you're saying we should use effects?

This is more what I had in mind, that upon inserting a new block, if the block preceding it (a) was previously the last block, (b) is of the default block type, and (c) is empty, then it should be removed.

I guess it could be perceived as indirection. I'd framed it in my mind more as a side effect obviously 😄

@@ -0,0 +1,32 @@
/**
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Function dumping grounds 😩

As to not be purely cynical: Elsewhere we use "utils" naming? Ought we use that here?

Also, some small tweaks to the isUnmodifiedDefaultBlock function
@youknowriad youknowriad force-pushed the add/replace-empty-blocks branch from 815a5ef to fba8389 Compare February 8, 2018 09:33
@youknowriad
Copy link
Copy Markdown
Contributor Author

Update this per @iseulde proposition and it seems to work consistently now. Let me know if you find another edge case.

@youknowriad
Copy link
Copy Markdown
Contributor Author

Feedback addressed, merging

@youknowriad youknowriad merged commit 5a6e4ee into master Feb 8, 2018
@youknowriad youknowriad deleted the add/replace-empty-blocks branch February 8, 2018 12:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

When caret is in an empty text block, inserted blocks should replace it

4 participants