-
Notifications
You must be signed in to change notification settings - Fork 4.1k
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
Add block list change detection #7325
Add block list change detection #7325
Conversation
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.
As you mentioned I think this could use unit tests but I also think there's a fair amount of code cleanup required here. I've left some comments asking for refactoring and clarification.
/** | ||
* A compare helper for lodash's difference by | ||
* | ||
* @param {Component} block A block object. |
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.
Nitpick, but I think these should line up (see the docblock below, it looks nicer 😄)
import { subscribe, select } from '@wordpress/data'; | ||
|
||
/** | ||
* A compare helper for lodash's difference by |
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 would write differenceBy function
instead of difference by
... as-is it looks like a half-finished sentence so caught me off-guard.
For clarity and grammar maybe:
/**
* A comparison helper for use with lodash's differenceBy function
[...]
* not just additions/removals. Therefore we actually compare the array with a | ||
* previous state and look for changes in length or uid. | ||
* | ||
* @param {function} selector Selector. |
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 think this parameter could be expanded; what is an example of an argument you'd pass here?
* previous state and look for changes in length or uid. | ||
* | ||
* @param {function} selector Selector. | ||
* @param {function} listener Listener. |
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 think this parameter could be expanded; what is an example of an argument you'd pass here?
// For performance reasons we first check the cheap length change, | ||
// before we actuallly do a deep object comparison | ||
if ( selectedBlocks.length !== previousBlocks.length ) { | ||
// The block list length has changed, so there is obviously a change event happening |
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 this comment is needed if the above is refactored.
if ( selectedBlocks.length !== previousBlocks.length ) { | ||
// The block list length has changed, so there is obviously a change event happening | ||
listener( selectedBlocks, previousBlocks ); | ||
previousBlocks = selectedBlocks; |
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.
Both of these variables are assigned to selector()
... why would they change?
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.
One is the old state (previousBlock) and the new state (selectedBlocks). Once a block in the editor is added the block list is getting updated and the selectedBlocks object changes.
/** | ||
* Subscribe to block data | ||
* | ||
* This function subscribes to block data, compares old and new states upon |
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.
What block data is this subscribing to? I'm sorry but I'm finding the comments here a bit vague.
* This function subscribes to block data, compares old and new states upon | ||
* change and fires actions accordingly. | ||
*/ | ||
subscribe( onBlocksChangeListener( select( 'core/editor' ).getBlocks, ( blocks, oldBlocks, difference = null ) => { |
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 find difference
to be a vague variable name, could you clarify?
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.
Should i add it to the documentation or change the variable name? Technically it is an intersection between blocks & oldBlocks, verbally it is the difference between these two arrays. (To signify that there is a difference, even though the length is equal.)
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.
What kind of value would it have? I don't understand what it means to be "the difference between these two arrays"... does that mean it's all the elements in either array not shared or just a boolean that signifies "yes these arrays are different"?
Docs around it would be great but if it's a boolean that is true
when the arrays are different and false
when not, something like blocksHaveBeenChanged
or something might better? 🤷♂️
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, I see the point. Currently difference
is an array of blocks that have been added. That signals that when the length of the blocklist stays equal, but there is a block in difference, that there was a transformation event.
I have changed the var to a boolean value hasChanged
to signal that.
// When the length is equal, but a change hapened, we have a transformation | ||
if ( oldBlocks.length === blocks.length && difference ) { | ||
// A block has been deleted | ||
for ( const i in deletedBlocks ) { |
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 we do:
deletedBlocks.each( ( block ) => {
const actionName = `blocks.transformed.from.${ camelCase( block.name ) }`;
doAction( actionName, block );
} )
instead?
} | ||
|
||
// A block has been added | ||
for ( const i in addedBlocks ) { |
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 seems like this logic of looping through blocks and calling doAction
is common; you could probably make a function that takes the list of blocks and the prefix rather than duplicating the action each time (eg it would look like doActionForBlocks( 'blocks.transformed.from', deletedBlocks )
)
Can you provide an example how this implementation could help to solve the original issue raised in #5626? |
Noted concern of trying to address this problem from the client in #5626 (comment) . |
Given a hypothetical block named 'gb/opener', that saves it's state in a meta key called
Other usecases would be to notify a plugin about the presence of a certain block. Or do some things on a block transformation. Or trigger some NUX, when a block is being added for the first time by this user. |
After thinking about this, I think if we do this API, we open a pandora box to provide such APIs for a plethora of events. I'm leaning towards keeping this plugin territory for now. |
I tend to agree with @youknowriad. @aduth what's your take on this since you commented on the parent issue. |
Per my comment on #5626, if this were addressed from within core (handling the deletion of a meta associated with a removed block) during save, regardless where the save came from (since this problem exists for e.g. anything interfacing with the REST API to save post content), then the primary need for this pull request goes away anyways. |
I see what you mean. Inded, it makes a lot of sense. Even if we would land this change, this issue would still be present if someone uses REST API to remove one of the blocks. Given that mobile team is working on Gutenberg integration for the mobile app this issue will become eve more prominent. On the technical side I see that this PR is using subscribe( onBlocksChangeListener( select( 'core/editor' ).getBlocks... It might be hard to implement the feature proposed which is performant enough given what we learned in the recent months. I'm inclined to close this PR and seek for PHP based solution. I'm wondering if the root issue shouldn't be moved to core now as the logic for saving blocks is there. @Luehrsen many thanks for all the time you spend working on this PR. It helped a lot to better understand what is required to fix this issue. |
Description
This PR is a result of issue #5626 to be able to react to the deletion of blocks. The added actions can be used to hook into the process of adding or deleting blocks to react accordingly.
This is a fixed PR of #7310.
How has this been tested?
Tested on local machine. Unit tests should probably be written, but I would need assistance with that.
Types of changes
Added a set of new actions for the post editor to hook into the post removal/addition/transformation process.
Checklist: