diff --git a/packages/editor/src/components/entities-saved-states/entity-record-item.js b/packages/editor/src/components/entities-saved-states/entity-record-item.js index ca9fb2e0b169c3..492377ad0455b1 100644 --- a/packages/editor/src/components/entities-saved-states/entity-record-item.js +++ b/packages/editor/src/components/entities-saved-states/entity-record-item.js @@ -17,14 +17,16 @@ export default function EntityRecordItem( { record, checked, onChange } ) { const { name, kind, title, key } = record; // Handle templates that might use default descriptive titles. - const { entityRecordTitle, hasPostMetaChanges } = useSelect( + const { entityRecordTitle, postMetaChanges } = useSelect( ( select ) => { + const _postMetaChanges = unlock( + select( editorStore ) + ).getPostMetaChanges(); + if ( 'postType' !== kind || 'wp_template' !== name ) { return { entityRecordTitle: title, - hasPostMetaChanges: unlock( - select( editorStore ) - ).hasPostMetaChanges( name, key ), + postMetaChanges: _postMetaChanges, }; } @@ -38,9 +40,7 @@ export default function EntityRecordItem( { record, checked, onChange } ) { select( editorStore ).__experimentalGetTemplateInfo( template ).title, - hasPostMetaChanges: unlock( - select( editorStore ) - ).hasPostMetaChanges( name, key ), + postMetaChanges: _postMetaChanges, }; }, [ name, kind, title, key ] @@ -58,10 +58,15 @@ export default function EntityRecordItem( { record, checked, onChange } ) { onChange={ onChange } /> - { hasPostMetaChanges && ( - + { postMetaChanges.length > 0 && ( +
+

Post Meta

+ +
) } ); diff --git a/packages/editor/src/components/entities-saved-states/style.scss b/packages/editor/src/components/entities-saved-states/style.scss index 981a0d92e5ff6b..adee99ae44dbf6 100644 --- a/packages/editor/src/components/entities-saved-states/style.scss +++ b/packages/editor/src/components/entities-saved-states/style.scss @@ -24,9 +24,13 @@ color: $gray-700; font-size: $helptext-font-size; margin: $grid-unit-10 $grid-unit-20 0 $grid-unit-20; - list-style: disc; - li { - margin-bottom: $grid-unit-05; + ul { + list-style: disc; + margin: $grid-unit-10 $grid-unit-30 0 $grid-unit-30; + + li { + margin-bottom: $grid-unit-05; + } } } diff --git a/packages/editor/src/components/post-publish-button/index.js b/packages/editor/src/components/post-publish-button/index.js index e27b4fbd16c3ec..61b2ec4d0d622e 100644 --- a/packages/editor/src/components/post-publish-button/index.js +++ b/packages/editor/src/components/post-publish-button/index.js @@ -48,7 +48,7 @@ export class PostPublishButton extends Component { return ( ...args ) => { const { hasNonPostEntityChanges, - hasPostMetaChanges, + postMetaChanges, setEntitiesSavedStatesCallback, isPublished, } = this.props; @@ -58,10 +58,12 @@ export class PostPublishButton extends Component { // We also need to check that the `setEntitiesSavedStatesCallback` // prop was passed. See https://github.com/WordPress/gutenberg/pull/37383 // - // TODO: Explore how to manage `hasPostMetaChanges` and pre-publish workflow properly. + // TODO: Explore how to manage `getPostMetaChanges` and pre-publish workflow properly. if ( ( hasNonPostEntityChanges || - ( hasPostMetaChanges && isPublished ) ) && + ( postMetaChanges && + postMetaChanges.length > 0 && + isPublished ) ) && setEntitiesSavedStatesCallback ) { // The modal for multiple entity saving will open, @@ -226,7 +228,7 @@ export default compose( [ isSavingNonPostEntityChanges, getEditedPostAttribute, getPostEdits, - hasPostMetaChanges, + getPostMetaChanges, } = unlock( select( editorStore ) ); return { isSaving: isSavingPost(), @@ -244,7 +246,7 @@ export default compose( [ postStatus: getEditedPostAttribute( 'status' ), postStatusHasChanged: getPostEdits()?.status, hasNonPostEntityChanges: hasNonPostEntityChanges(), - hasPostMetaChanges: hasPostMetaChanges(), + postMetaChanges: getPostMetaChanges(), isSavingNonPostEntityChanges: isSavingNonPostEntityChanges(), }; } ), diff --git a/packages/editor/src/components/save-publish-panels/index.js b/packages/editor/src/components/save-publish-panels/index.js index 2b1a54b57d9558..8f7b4eb938f71c 100644 --- a/packages/editor/src/components/save-publish-panels/index.js +++ b/packages/editor/src/components/save-publish-panels/index.js @@ -43,7 +43,7 @@ export default function SavePublishPanels( { } = select( editorStore ); const _hasOtherEntitiesChanges = hasNonPostEntityChanges() || - unlock( select( editorStore ) ).hasPostMetaChanges(); + unlock( select( editorStore ) ).getPostMetaChanges().length > 0; return { publishSidebarOpened: isPublishSidebarOpened(), isPublishable: @@ -52,7 +52,6 @@ export default function SavePublishPanels( { hasOtherEntitiesChanges: _hasOtherEntitiesChanges, }; }, [] ); - const openEntitiesSavedStates = useCallback( () => setEntitiesSavedStatesCallback( true ), [] diff --git a/packages/editor/src/store/private-selectors.js b/packages/editor/src/store/private-selectors.js index f56e1e8c9e81fb..26c5cf80c29df3 100644 --- a/packages/editor/src/store/private-selectors.js +++ b/packages/editor/src/store/private-selectors.js @@ -1,8 +1,3 @@ -/** - * External dependencies - */ -import fastDeepEqual from 'fast-deep-equal'; - /** * WordPress dependencies */ @@ -34,6 +29,49 @@ const EMPTY_INSERTION_POINT = { insertionIndex: undefined, filterValue: undefined, }; +const isObject = ( obj ) => obj !== null && typeof obj === 'object'; + +/** + * A deep comparison of two objects, optimized for comparing metadata changes. + * @param {Object} changedObject The changed object to compare. + * @param {Object} originalObject The original object to compare against. + * @param {string} parentPath A key/value pair object of block names and their rendered titles. + * @return {string[]} An array of paths whose values have changed. + */ +function deepCompare( changedObject, originalObject, parentPath = '' ) { + // We have two non-object values to compare. + if ( ! isObject( changedObject ) && ! isObject( originalObject ) ) { + /* + * Only return a path if the value has changed. + */ + return changedObject !== originalObject + ? parentPath.split( '.' ).join( ' > ' ) + : undefined; + } + + // Enable comparison when an object doesn't have a corresponding property to compare. + changedObject = isObject( changedObject ) ? changedObject : {}; + originalObject = isObject( originalObject ) ? originalObject : {}; + + const allKeys = new Set( [ + ...Object.keys( changedObject ), + ...Object.keys( originalObject ), + ] ); + + let diffs = []; + for ( const key of allKeys ) { + const path = parentPath ? parentPath + '.' + key : key; + const changedPath = deepCompare( + changedObject[ key ], + originalObject[ key ], + path + ); + if ( changedPath ) { + diffs = diffs.concat( changedPath ); + } + } + return diffs; +} /** * Get the insertion point for the inserter. @@ -153,31 +191,28 @@ export const getCurrentTemplateTemplateParts = createRegistrySelector( * * @return {boolean} Whether there are edits or not in the meta fields of the relevant post. */ -export const hasPostMetaChanges = createRegistrySelector( +export const getPostMetaChanges = createRegistrySelector( ( select ) => ( state, postType, postId ) => { const { type: currentPostType, id: currentPostId } = getCurrentPost( state ); // If no postType or postId is passed, use the current post. - const edits = select( coreStore ).getEntityRecordNonTransientEdits( + const original = select( coreStore ).getEntityRecord( 'postType', postType || currentPostType, postId || currentPostId ); - - if ( ! edits?.meta ) { - return false; - } - - // Compare if anything apart from `footnotes` has changed. - const originalPostMeta = select( coreStore ).getEntityRecord( + const edits = select( coreStore ).getEntityRecordNonTransientEdits( 'postType', postType || currentPostType, postId || currentPostId - )?.meta; + ); + + if ( ! original?.meta || ! edits?.meta ) { + return []; + } - return ! fastDeepEqual( - { ...originalPostMeta, footnotes: undefined }, - { ...edits.meta, footnotes: undefined } + return deepCompare( edits.meta, original.meta ).filter( + ( item ) => item !== 'footnotes' ); } ); diff --git a/packages/is-shallow-equal/src/objects.js b/packages/is-shallow-equal/src/objects.js index e2dc6867c7d51b..100600aba5b31f 100644 --- a/packages/is-shallow-equal/src/objects.js +++ b/packages/is-shallow-equal/src/objects.js @@ -1,3 +1,29 @@ +/** + * Internal dependencies + */ +import isShallowEqualArrays from './arrays'; + +/** + * Returns true if the two values are equal, or false otherwise. + * Includes a check for shallow array equality. + * + * @param {any} a + * @param {any} b + * + * @return {boolean} Whether the two values are equal. + */ +function isEqual( a, b ) { + if ( Array.isArray( a ) && Array.isArray( b ) ) { + return isShallowEqualArrays( a, b ); + } + + if ( a === b ) { + return true; + } + + return false; +} + /** * Returns true if the two objects are shallow equal, or false otherwise. * @@ -31,7 +57,7 @@ export default function isShallowEqualObjects( a, b ) { // // Example: isShallowEqualObjects( { a: undefined }, { b: 5 } ) ( aValue === undefined && ! b.hasOwnProperty( key ) ) || - aValue !== b[ key ] + ! isEqual( aValue, b[ key ] ) ) { return false; }