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 && (
-
- - { __( 'Post Meta.' ) }
-
+ { postMetaChanges.length > 0 && (
+
+
Post Meta
+
+ { postMetaChanges.map( ( postMeta ) => (
+ - { postMeta }
+ ) ) }
+
+
) }
>
);
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;
}