Skip to content
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 indicator for metadata changes to Save Panel when reviewing modified entities #61811

Merged
merged 25 commits into from
May 31, 2024
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
941fd19
Add rough prototype showing meta in entities panel
artemiomorales May 21, 2024
a13c172
Simplify detection of metadata changes
artemiomorales May 24, 2024
58c06bd
Remove obsolete code
artemiomorales May 24, 2024
6d1c37b
Add indicator for bindings to save entities panel
artemiomorales May 26, 2024
3582092
Modify message to read 'Post Meta'
artemiomorales May 27, 2024
439c0da
Add store function to check if meta has changed
artemiomorales May 27, 2024
97bdf0e
Remove obsolete check
artemiomorales May 27, 2024
dbd7989
Simplify logic to check if meta has changed
artemiomorales May 27, 2024
059b7d4
Update tests
artemiomorales May 29, 2024
c0217d1
Make hasMetaChanges selector private
artemiomorales May 29, 2024
63cef47
Suggestion: Move logic to `hasPostMetaChanges` selector
SantosGuillamot May 30, 2024
ce4ade3
Change test formatting
SantosGuillamot May 30, 2024
cfd0f26
Don't show save panel in pre-publish
SantosGuillamot May 30, 2024
804761c
Get `hasPostMetaChanges` from the proper place
SantosGuillamot May 30, 2024
83abcd7
Add end-to-end test
artemiomorales May 30, 2024
8f1df20
Update class name
artemiomorales May 30, 2024
f681800
Clarify naming
artemiomorales May 30, 2024
baf5ff7
Show Post Meta in relevant post
SantosGuillamot May 31, 2024
cd0ceeb
Remove extra change
SantosGuillamot May 31, 2024
4172d9f
Move test metadata test util
artemiomorales May 31, 2024
c1a756a
Update comments
artemiomorales May 31, 2024
23d9429
Prevent save panel from appearing when just footnotes are modified
artemiomorales May 31, 2024
2b509ce
Update package-lock.json
artemiomorales May 31, 2024
2d8fb99
Revert "Update package-lock.json"
artemiomorales May 31, 2024
1511a08
Update package-lock programatically
SantosGuillamot May 31, 2024
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/e2e-test-utils-playwright/src/editor/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { clickBlockToolbarButton } from './click-block-toolbar-button';
import { getBlocks } from './get-blocks';
import { getEditedPostContent } from './get-edited-post-content';
import { insertBlock } from './insert-block';
import { modifyPostMetadata } from './modify-post-metadata';
import { openDocumentSettingsSidebar } from './open-document-settings-sidebar';
import { openPreviewPage } from './preview';
import { publishPost } from './publish-post';
Expand Down Expand Up @@ -61,6 +62,9 @@ export class Editor {
getEditedPostContent.bind( this );
/** @borrows insertBlock as this.insertBlock */
insertBlock: typeof insertBlock = insertBlock.bind( this );
/** @borrows modifyPostMetadata as this.modifyPostMetadata */
modifyPostMetadata: typeof modifyPostMetadata =
modifyPostMetadata.bind( this );
/** @borrows openDocumentSettingsSidebar as this.openDocumentSettingsSidebar */
openDocumentSettingsSidebar: typeof openDocumentSettingsSidebar =
openDocumentSettingsSidebar.bind( this );
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/**
* Internal dependencies
*/
import type { Editor } from './index';

/**
* Clicks on the button in the header which opens Document Settings sidebar when
Copy link
Member

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 description is correct.

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm wondering if it is okay to create a new global util or start with something specific for this tests. Something like the UndoUtils, for example. (There are many other tests with their own utils)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I updated the description and moved the test 👍

* it is closed.
*
* @param this
* @param postType
* @param postId
* @param metaKey
* @param metaValue
*/
export async function modifyPostMetadata(
this: Editor,
postType: string,
postId: string,
metaKey: string,
metaValue: string
) {
await this.page.waitForFunction( () => window?.wp?.data );

const parameters = {
postType,
postId,
metaKey,
metaValue,
};

await this.page.evaluate( ( _parameters ) => {
window.wp.data
.dispatch( 'core' )
.editEntityRecord(
'postType',
_parameters.postType,
_parameters.postId,
{
meta: { [ _parameters.metaKey ]: _parameters.metaValue },
}
);
}, parameters );
}
Original file line number Diff line number Diff line change
@@ -1,49 +1,78 @@
/**
* WordPress dependencies
*/
import { CheckboxControl, PanelRow } from '@wordpress/components';
import { Icon, CheckboxControl, Flex, PanelRow } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import { useSelect } from '@wordpress/data';
import { store as coreStore } from '@wordpress/core-data';
import { decodeEntities } from '@wordpress/html-entities';
import { connection } from '@wordpress/icons';

/**
* Internal dependencies
*/
import { store as editorStore } from '../../store';
import { unlock } from '../../lock-unlock';

export default function EntityRecordItem( { record, checked, onChange } ) {
const { name, kind, title, key } = record;

// Handle templates that might use default descriptive titles.
const entityRecordTitle = useSelect(
const { entityRecordTitle, hasPostMetaChanges } = useSelect(
( select ) => {
if ( 'postType' !== kind || 'wp_template' !== name ) {
return title;
return {
entityRecordTitle: title,
hasPostMetaChanges: unlock(
select( editorStore )
).hasPostMetaChanges(),
};
}

const template = select( coreStore ).getEditedEntityRecord(
kind,
name,
key
);
return select( editorStore ).__experimentalGetTemplateInfo(
template
).title;
return {
entityRecordTitle:
select( editorStore ).__experimentalGetTemplateInfo(
template
).title,
hasPostMetaChanges: unlock(
select( editorStore )
).hasPostMetaChanges(),
};
},
[ name, kind, title, key ]
);

return (
<PanelRow>
<CheckboxControl
__nextHasNoMarginBottom
label={
decodeEntities( entityRecordTitle ) || __( 'Untitled' )
}
checked={ checked }
onChange={ onChange }
/>
</PanelRow>
<>
<PanelRow>
<CheckboxControl
__nextHasNoMarginBottom
label={
decodeEntities( entityRecordTitle ) || __( 'Untitled' )
}
checked={ checked }
onChange={ onChange }
/>
</PanelRow>
{ hasPostMetaChanges && (
<PanelRow>
<Flex className="entities-saved-states__post-meta">
<Icon
className="entities-saved-states__connections-icon"
icon={ connection }
size={ 24 }
/>
<span className="entities-saved-states__bindings-text">
{ __( 'Post Meta.' ) }
</span>
</Flex>
</PanelRow>
) }
</>
);
}
13 changes: 13 additions & 0 deletions packages/editor/src/components/entities-saved-states/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,16 @@
height: $header-height + $border-width;
}
}

.entities-saved-states__post-meta {
margin-left: $grid-unit-30;
align-items: center;
}

.entities-saved-states__connections-icon {
flex-grow: 0;
}

.entities-saved-states__bindings-text {
flex-grow: 1;
}
21 changes: 17 additions & 4 deletions packages/editor/src/components/post-publish-button/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { compose } from '@wordpress/compose';
*/
import PublishButtonLabel from './label';
import { store as editorStore } from '../../store';
import { unlock } from '../../lock-unlock';

const noop = () => {};

Expand Down Expand Up @@ -45,14 +46,24 @@ export class PostPublishButton extends Component {

createOnClick( callback ) {
return ( ...args ) => {
const { hasNonPostEntityChanges, setEntitiesSavedStatesCallback } =
this.props;
const {
hasNonPostEntityChanges,
hasPostMetaChanges,
setEntitiesSavedStatesCallback,
isPublished,
} = this.props;
// If a post with non-post entities is published, but the user
// elects to not save changes to the non-post entities, those
// entities will still be dirty when the Publish button is clicked.
// We also need to check that the `setEntitiesSavedStatesCallback`
// prop was passed. See https://github.com/WordPress/gutenberg/pull/37383
if ( hasNonPostEntityChanges && setEntitiesSavedStatesCallback ) {
//
// TODO: Explore how to manage `hasPostMetaChanges` and pre-publish workflow properly.
if (
( hasNonPostEntityChanges ||
( hasPostMetaChanges && isPublished ) ) &&
setEntitiesSavedStatesCallback
) {
// The modal for multiple entity saving will open,
// hold the callback for saving/publishing the post
// so that we can call it if the post entity is checked.
Expand Down Expand Up @@ -212,7 +223,8 @@ export default compose( [
isSavingNonPostEntityChanges,
getEditedPostAttribute,
getPostEdits,
} = select( editorStore );
hasPostMetaChanges,
} = unlock( select( editorStore ) );
return {
isSaving: isSavingPost(),
isAutoSaving: isAutosavingPost(),
Expand All @@ -229,6 +241,7 @@ export default compose( [
postStatus: getEditedPostAttribute( 'status' ),
postStatusHasChanged: getPostEdits()?.status,
hasNonPostEntityChanges: hasNonPostEntityChanges(),
hasPostMetaChanges: hasPostMetaChanges(),
isSavingNonPostEntityChanges: isSavingNonPostEntityChanges(),
};
} ),
Expand Down
12 changes: 10 additions & 2 deletions packages/editor/src/components/save-publish-panels/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import PostPublishPanel from '../post-publish-panel';
import PluginPrePublishPanel from '../plugin-pre-publish-panel';
import PluginPostPublishPanel from '../plugin-post-publish-panel';
import { store as editorStore } from '../../store';
import { unlock } from '../../lock-unlock';

const { Fill, Slot } = createSlotFill( 'ActionsPanel' );

Expand All @@ -27,12 +28,19 @@ export default function SavePublishPanels( {
} ) {
const { closePublishSidebar, togglePublishSidebar } =
useDispatch( editorStore );
const { publishSidebarOpened, hasNonPostEntityChanges } = useSelect(
const {
publishSidebarOpened,
hasNonPostEntityChanges,
hasPostMetaChanges,
} = useSelect(
( select ) => ( {
publishSidebarOpened:
select( editorStore ).isPublishSidebarOpened(),
hasNonPostEntityChanges:
select( editorStore ).hasNonPostEntityChanges(),
hasPostMetaChanges: unlock(
select( editorStore )
).hasPostMetaChanges(),
Comment on lines +41 to +43
Copy link
Contributor

Choose a reason for hiding this comment

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

I assume we should get hasPostMetaChanges from here and not in the component props, right?

Copy link
Contributor

Choose a reason for hiding this comment

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

I just changed it. Feel free to revert it if you consider it necessary.

} ),
[]
);
Expand All @@ -54,7 +62,7 @@ export default function SavePublishPanels( {
PostPublishExtension={ PluginPostPublishPanel.Slot }
/>
);
} else if ( hasNonPostEntityChanges ) {
} else if ( hasNonPostEntityChanges || hasPostMetaChanges ) {
Copy link
Contributor

Choose a reason for hiding this comment

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

To be honest, I don't fully understand what this part of the code does and if it is necessary or not.

Copy link
Member

Choose a reason for hiding this comment

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

As far as I remember, this alternative flow triggers when you edit the template of the post. Let me record a screencast to better illustrate it.

Copy link
Member

Choose a reason for hiding this comment

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

save-flow.mov
Copy link
Member

@gziolo gziolo May 31, 2024

Choose a reason for hiding this comment

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

In effect, what I think we should achieve with this PR would be:

  • Regular post – show only Publish
  • Regular post with post meta from the same post – show only Publish (covers Footnotes)
    • Follow-enhancement could be highlighting post meta in the Pre-publish screen
  • Regular post with any changes to external post - Save + Publish flow
Copy link
Member

@gziolo gziolo May 31, 2024

Choose a reason for hiding this comment

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

To support my line of thinking for the second case, updating the post meta of the currently edited post, isn't a site change. What I see currently after adding footnotes to the published post:

Screenshot 2024-05-31 at 09 55 38
Copy link
Contributor

Choose a reason for hiding this comment

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

If I am not mistaken, that is caused because the selector gets the type and id using getCurrentPost: link. Maybe we need to pass the type and id as an argument. I can give that a try.

Copy link
Member

Choose a reason for hiding this comment

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

In my opinion, it should use the selector from the core store directly so the post id and post type isn't connected with the currently edited entity - post, page, template, etc.

Copy link
Contributor

Choose a reason for hiding this comment

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

Do you mean using getEntityRecordNonTransientEdits directly and removing the hasPostMetaChanges one we are creating?

Copy link
Contributor

Choose a reason for hiding this comment

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

I pushed this commit to illustrate what I mean, but I am happy to iterate once I understand better your suggestion.

If I am not mistaken, that change fixes the problems you mention.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I tested @SantosGuillamot's fix for to address the scenario wherein the indicator for post meta changes didn't appear when modifying posts in the query loop. It appears to work:

Screenshot 2024-05-31 at 12 45 10 PM
unmountableContent = (
<div className="editor-layout__toggle-entities-saved-states-panel">
<Button
Expand Down
21 changes: 21 additions & 0 deletions packages/editor/src/store/private-selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { store as coreStore } from '@wordpress/core-data';
*/
import {
getRenderingMode,
getCurrentPost,
__experimentalGetDefaultTemplatePartAreas,
} from './selectors';
import { TEMPLATE_PART_POST_TYPE } from './constants';
Expand Down Expand Up @@ -135,3 +136,23 @@ export const getCurrentTemplateTemplateParts = createRegistrySelector(
return getFilteredTemplatePartBlocks( blocks, templateParts );
}
);

/**
* Returns true if there are unsaved changes to the
* post's meta fields, and false otherwise.
*
* @param {Object} state Global application state.
*
* @return {boolean} Whether there are edits or not in the meta fields.
*/
export const hasPostMetaChanges = createRegistrySelector(
( select ) => ( state ) => {
const { type, id } = getCurrentPost( state );
const edits = select( coreStore ).getEntityRecordNonTransientEdits(
'postType',
type,
id
);
return !! edits?.meta;
}
);
55 changes: 55 additions & 0 deletions test/e2e/specs/editor/various/publish-panel.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,4 +113,59 @@ test.describe( 'Post publish panel', () => {
} )
).toBeFocused();
} );

test( 'should show panel and indicator when metadata has been modified', async ( {
admin,
editor,
page,
} ) => {
await admin.createNewPost( {
title: 'Test metadata changes with save panel',
} );
await editor.insertBlock( {
name: 'core/paragraph',
attributes: {
content: 'paragraph default content',
metadata: {
bindings: {
content: {
source: 'core/post-meta',
args: { key: 'text_custom_field' },
},
},
},
},
} );
const postId = await editor.publishPost();
await editor.modifyPostMetadata(
'post',
postId,
'text_custom_field',
'test value'
);
const editorTopBar = page.getByRole( 'region', {
name: 'Editor top bar',
} );

const saveButton = editorTopBar.getByRole( 'button', {
name: 'Save',
exact: true,
} );

await expect( saveButton ).toBeVisible();

await saveButton.click();

const publishPanel = page.getByRole( 'region', {
name: 'Editor publish',
} );

await expect( publishPanel ).toBeVisible();

const postMetaPanel = publishPanel.locator(
'.entities-saved-states__post-meta'
);

await expect( postMetaPanel ).toBeVisible();
} );
} );
Loading