-
Notifications
You must be signed in to change notification settings - Fork 4.3k
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
Simplify meta attribute sources and attempt to prepare a generalization of custom sources #16075
Changes from all commits
c78c848
668cd1a
f66152d
3d68485
dd14ac6
ff515c2
e74ed1d
94327e8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -983,8 +983,35 @@ export const blockListSettings = ( state = {}, action ) => { | |
return state; | ||
}; | ||
|
||
export function lastBlockAttributesChanges( state, action ) { | ||
switch ( action.type ) { | ||
case 'UPDATE_BLOCK': | ||
if ( ! action.updates.attributes ) { | ||
return null; | ||
} | ||
return { | ||
[ action.clientId ]: action.updates.attributes, | ||
}; | ||
|
||
case 'UPDATE_BLOCK_ATTRIBUTES': | ||
return { | ||
[ action.clientId ]: action.attributes, | ||
}; | ||
|
||
case 'RESET_BLOCKS': | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm wondering if we need to account for these actions. It seems that interpreting something like a block replacement to trigger meta updates might not always be expected (though conversely, maybe it is). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well, I don't know. I agree that it's less important in the way we use "RESET_BLOCKS" in Core but there's nothing stopping third-party plugins from using the actions right. |
||
case 'INSERT_BLOCKS': | ||
case 'RECEIVE_BLOCKS': | ||
case 'REPLACE_BLOCKS': | ||
return getFlattenedBlockAttributes( action.blocks ); | ||
} | ||
|
||
return null; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Did you mean to return There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes, I didn't give it too much thoughts but yeah the idea is that we don't care about the value after. I don't think it would harm if we return "state" but I wonder if we'd need to "reset" the value in some actions I might have missed. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Tbh, this "last changes" thing was consider very experimental. If we follow the approach of this PR, I wouldn't mind if we do a full sync on update. Maybe the performance impact is small? we should measure. |
||
} | ||
|
||
export default combineReducers( { | ||
blocks, | ||
// This is ideally embedded in blocks, the issues is that it alterns the isPersistent behavior | ||
lastBlockAttributesChanges, | ||
isTyping, | ||
isCaretWithinFormattedText, | ||
blockSelection, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -64,14 +64,6 @@ const MILLISECONDS_PER_WEEK = 7 * 24 * 3600 * 1000; | |
*/ | ||
const EMPTY_ARRAY = []; | ||
|
||
/** | ||
* Shared reference to an empty object for cases where it is important to avoid | ||
* returning a new object reference on every invocation. | ||
* | ||
* @type {Object} | ||
*/ | ||
const EMPTY_OBJECT = {}; | ||
|
||
/** | ||
* Returns a new reference when the inner blocks of a given block client ID | ||
* change. This is used exclusively as a memoized selector dependant, relying | ||
|
@@ -130,42 +122,14 @@ export function isBlockValid( state, clientId ) { | |
* | ||
* @return {Object?} Block attributes. | ||
*/ | ||
export const getBlockAttributes = createSelector( | ||
( state, clientId ) => { | ||
const block = state.blocks.byClientId[ clientId ]; | ||
if ( ! block ) { | ||
return null; | ||
} | ||
|
||
let attributes = state.blocks.attributes[ clientId ]; | ||
|
||
// Inject custom source attribute values. | ||
// | ||
// TODO: Create generic external sourcing pattern, not explicitly | ||
// targeting meta attributes. | ||
const type = getBlockType( block.name ); | ||
if ( type ) { | ||
attributes = reduce( type.attributes, ( result, value, key ) => { | ||
if ( value.source === 'meta' ) { | ||
if ( result === attributes ) { | ||
result = { ...result }; | ||
} | ||
|
||
result[ key ] = getPostMeta( state, value.meta ); | ||
} | ||
|
||
return result; | ||
}, attributes ); | ||
} | ||
export function getBlockAttributes( state, clientId ) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, this refactor makes me quite happy to see 😄 This should serve as a nice optimization. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was a bit disappointed to notice that it doesn't change a lot in terms for performance impact. (The cache bust is the bottleneck right now) |
||
const block = state.blocks.byClientId[ clientId ]; | ||
if ( ! block ) { | ||
return null; | ||
} | ||
|
||
return attributes; | ||
}, | ||
( state, clientId ) => [ | ||
state.blocks.byClientId[ clientId ], | ||
state.blocks.attributes[ clientId ], | ||
getPostMeta( state ), | ||
] | ||
); | ||
return state.blocks.attributes[ clientId ]; | ||
} | ||
|
||
/** | ||
* Returns a block given its client ID. This is a parsed copy of the block, | ||
|
@@ -192,7 +156,8 @@ export const getBlock = createSelector( | |
}; | ||
}, | ||
( state, clientId ) => [ | ||
...getBlockAttributes.getDependants( state, clientId ), | ||
state.blocks.attributes[ clientId ], | ||
state.blocks.byClientId[ clientId ], | ||
getBlockDependantsCacheBust( state, clientId ), | ||
] | ||
); | ||
|
@@ -211,7 +176,7 @@ export const __unstableGetBlockWithoutInnerBlocks = createSelector( | |
}, | ||
( state, clientId ) => [ | ||
state.blocks.byClientId[ clientId ], | ||
...getBlockAttributes.getDependants( state, clientId ), | ||
state.blocks.attributes[ clientId ], | ||
] | ||
); | ||
|
||
|
@@ -314,7 +279,6 @@ export const getBlocksByClientId = createSelector( | |
( clientId ) => getBlock( state, clientId ) | ||
), | ||
( state ) => [ | ||
getPostMeta( state ), | ||
state.blocks.byClientId, | ||
state.blocks.order, | ||
state.blocks.attributes, | ||
|
@@ -691,7 +655,6 @@ export const getMultiSelectedBlocks = createSelector( | |
state.blocks.byClientId, | ||
state.blocks.order, | ||
state.blocks.attributes, | ||
getPostMeta( state ), | ||
] | ||
); | ||
|
||
|
@@ -1437,6 +1400,17 @@ export function isLastBlockChangePersistent( state ) { | |
return state.blocks.isPersistentChange; | ||
} | ||
|
||
/** | ||
* Returns an object describing the last block attributes changes | ||
* | ||
* @param {Object} state Block editor state. | ||
* | ||
* @return {Object} the clientId and the block attributes changes | ||
*/ | ||
export function getLastBlockAttributesChange( state ) { | ||
return state.lastBlockAttributesChanges; | ||
} | ||
|
||
/** | ||
* Returns true if the most recent block change is be considered ignored, or | ||
* false otherwise. An ignored change is one not to be committed by | ||
|
@@ -1455,22 +1429,6 @@ export function __unstableIsLastBlockChangeIgnored( state ) { | |
return state.blocks.isIgnoredChange; | ||
} | ||
|
||
/** | ||
* Returns the value of a post meta from the editor settings. | ||
* | ||
* @param {Object} state Global application state. | ||
* @param {string} key Meta Key to retrieve | ||
* | ||
* @return {*} Meta value | ||
*/ | ||
function getPostMeta( state, key ) { | ||
if ( key === undefined ) { | ||
return get( state, [ 'settings', '__experimentalMetaSource', 'value' ], EMPTY_OBJECT ); | ||
} | ||
|
||
return get( state, [ 'settings', '__experimentalMetaSource', 'value', key ] ); | ||
} | ||
|
||
/** | ||
* Returns the available reusable blocks | ||
* | ||
|
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 satisfied with this trick (this value being an array) but it's the only way I found right now to fix all the use-cases which are:
resetBlocks
if the new "value" prop received is the same as the one already in the block-editor store (to avoid resetting the "undo" levels). (undo e2e test failing)onChange
calls being performed synchronously which will eventually trigger twocomponentDidUpdate
calls but eventually the firstcomponentDidUpdate
will callresetBlocks
if we only compare against the latest blocks value in the block-editor state causing the editor to reset to the previous blocks value (and not the last one). (columns e2e test failing)The fix here is a bit fragile though, it basically says: don't call "resetBlocks" unless the new prop received is not one of the last persisted blocks value passed to
onChange
.