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

Pages: Add "Set as posts page" action #67650

Merged
merged 18 commits into from
Dec 13, 2024
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions packages/editor/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,18 @@ getDerivedStateFromError is used to render a fallback UI after an error has been

> **Deprecated** since 5.3, use `wp.blockEditor.getFontSizeClass` instead.

### getItemTitle

Helper function to get the title of a post item. This is duplicated from the `@wordpress/fields` package. `packages/fields/src/actions/utils.ts`

_Parameters_

- _item_ `Object`: The post item.

_Returns_

- `string`: The title of the item, or an empty string if the title is not found.

### getTemplatePartIcon

Helper function to retrieve the corresponding icon by name.
Expand Down
30 changes: 26 additions & 4 deletions packages/editor/src/components/post-actions/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ import { store as coreStore } from '@wordpress/core-data';
import { store as editorStore } from '../../store';
import { unlock } from '../../lock-unlock';
import { useSetAsHomepageAction } from './set-as-homepage';
import { useResetHomepageAction } from './reset-homepage';
import { useSetAsPostsPageAction } from './set-as-posts-page';
import { useResetPostsPageAction } from './reset-posts-page';

export function usePostActions( { postType, onActionPerformed, context } ) {
const { defaultActions } = useSelect(
Expand Down Expand Up @@ -43,7 +46,10 @@ export function usePostActions( { postType, onActionPerformed, context } ) {
);

const setAsHomepageAction = useSetAsHomepageAction();
const shouldShowSetAsHomepageAction =
const resetHomepageAction = useResetHomepageAction();
const setAsPostsPageAction = useSetAsPostsPageAction();
const resetPostsPageAction = useResetPostsPageAction();
const shouldShowHomepageActions =
canManageOptions && ! hasFrontPageTemplate;

const { registerPostTypeSchema } = unlock( useDispatch( editorStore ) );
Expand All @@ -53,10 +59,23 @@ export function usePostActions( { postType, onActionPerformed, context } ) {

return useMemo( () => {
let actions = [ ...defaultActions ];
if ( shouldShowSetAsHomepageAction ) {
actions.push( setAsHomepageAction );
if ( shouldShowHomepageActions ) {
actions.push(
setAsHomepageAction,
resetHomepageAction,
setAsPostsPageAction,
resetPostsPageAction
);
}

// Ensure "Move to trash" is always the last action.
actions = actions.sort( ( a, b ) => {
if ( b.id === 'move-to-trash' ) {
return -1;
}
return 0;
} );
mikachan marked this conversation as resolved.
Show resolved Hide resolved

// Filter actions based on provided context. If not provided
// all actions are returned. We'll have a single entry for getting the actions
// and the consumer should provide the context to filter the actions, if needed.
Expand Down Expand Up @@ -122,7 +141,10 @@ export function usePostActions( { postType, onActionPerformed, context } ) {
context,
defaultActions,
onActionPerformed,
resetHomepageAction,
resetPostsPageAction,
setAsHomepageAction,
shouldShowSetAsHomepageAction,
setAsPostsPageAction,
shouldShowHomepageActions,
] );
}
143 changes: 143 additions & 0 deletions packages/editor/src/components/post-actions/reset-homepage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
/**
* WordPress dependencies
*/
import { __, sprintf } from '@wordpress/i18n';
import { useMemo } from '@wordpress/element';
import {
Button,
__experimentalText as Text,
__experimentalHStack as HStack,
__experimentalVStack as VStack,
} from '@wordpress/components';
import { useDispatch, useSelect } from '@wordpress/data';
import { store as coreStore } from '@wordpress/core-data';
import { store as noticesStore } from '@wordpress/notices';

/**
* Internal dependencies
*/
import { getItemTitle } from '../../utils';

const ResetHomepageModal = ( { items, closeModal } ) => {
const [ item ] = items;
const pageTitle = getItemTitle( item );
const { isPageForPostsSet, isSaving } = useSelect( ( select ) => {
const { getEntityRecord, isSavingEntityRecord } = select( coreStore );
const siteSettings = getEntityRecord( 'root', 'site' );

return {
isPageForPostsSet: siteSettings?.page_for_posts !== 0,
isSaving: isSavingEntityRecord( 'root', 'site' ),
};
} );

const { saveEditedEntityRecord, saveEntityRecord } =
useDispatch( coreStore );
const { createSuccessNotice, createErrorNotice } =
useDispatch( noticesStore );

async function onResetHomepage( event ) {
event.preventDefault();

try {
// Save new home page settings.
await saveEditedEntityRecord( 'root', 'site', undefined, {
page_on_front: 0,
show_on_front: isPageForPostsSet ? 'page' : 'posts',
} );

// This second call to a save function is a workaround for a bug in
// `saveEditedEntityRecord`. This forces the root site settings to be updated.
// See https://github.com/WordPress/gutenberg/issues/67161.
await saveEntityRecord( 'root', 'site', {
page_on_front: 0,
show_on_front: isPageForPostsSet ? 'page' : 'posts',
} );

createSuccessNotice( __( 'Homepage reset' ), {
type: 'snackbar',
} );
} catch ( error ) {
const typedError = error;
const errorMessage =
typedError.message && typedError.code !== 'unknown_error'
? typedError.message
: __( 'An error occurred while resetting the homepage' );
createErrorNotice( errorMessage, { type: 'snackbar' } );
} finally {
closeModal?.();
}
}

const modalWarning = ! isPageForPostsSet
? __( 'This will set the homepage to display latest posts.' )
: '';

const modalText = sprintf(
// translators: %1$s: title of the page to be unset as the homepage, %2$s: homepage replacement warning message.
__(
'Reset the site homepage? "%1$s" will no longer be set as the homepage. %2$s'
),
pageTitle,
modalWarning
);

// translators: Button label to confirm resetting the homepage.
const modalButtonLabel = __( 'Reset homepage' );

return (
<form onSubmit={ onResetHomepage }>
<VStack spacing="5">
<Text>{ modalText }</Text>
<HStack justify="right">
<Button
__next40pxDefaultSize
variant="tertiary"
onClick={ () => {
closeModal?.();
} }
disabled={ isSaving }
accessibleWhenDisabled
>
{ __( 'Cancel' ) }
</Button>
<Button
__next40pxDefaultSize
variant="primary"
type="submit"
disabled={ isSaving }
accessibleWhenDisabled
>
{ modalButtonLabel }
</Button>
</HStack>
</VStack>
</form>
);
};

export const useResetHomepageAction = () => {
const { pageOnFront } = useSelect( ( select ) => {
const { getEntityRecord } = select( coreStore );
const siteSettings = getEntityRecord( 'root', 'site' );
return {
pageOnFront: siteSettings?.page_on_front,
};
} );

return useMemo(
() => ( {
id: 'reset-homepage',
label: __( 'Reset homepage' ),
isEligible( post ) {
if ( pageOnFront !== post.id ) {
return false;
}

return true;
},
mikachan marked this conversation as resolved.
Show resolved Hide resolved
RenderModal: ResetHomepageModal,
} ),
[ pageOnFront ]
);
};
142 changes: 142 additions & 0 deletions packages/editor/src/components/post-actions/reset-posts-page.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
/**
* WordPress dependencies
*/
import { __, sprintf } from '@wordpress/i18n';
import { useMemo } from '@wordpress/element';
import {
Button,
__experimentalText as Text,
__experimentalHStack as HStack,
__experimentalVStack as VStack,
} from '@wordpress/components';
import { useDispatch, useSelect } from '@wordpress/data';
import { store as coreStore } from '@wordpress/core-data';
import { store as noticesStore } from '@wordpress/notices';

/**
* Internal dependencies
*/
import { getItemTitle } from '../../utils';

const ResetPostsPageModal = ( { items, closeModal } ) => {
const [ item ] = items;
const pageTitle = getItemTitle( item );
const { isPageOnFrontSet, isSaving } = useSelect( ( select ) => {
const { getEntityRecord, isSavingEntityRecord } = select( coreStore );
const siteSettings = getEntityRecord( 'root', 'site' );
return {
isPageOnFrontSet: siteSettings?.page_on_front !== 0,
isSaving: isSavingEntityRecord( 'root', 'site' ),
};
} );

const { saveEditedEntityRecord, saveEntityRecord } =
useDispatch( coreStore );
const { createSuccessNotice, createErrorNotice } =
useDispatch( noticesStore );

async function onResetPostsPage( event ) {
event.preventDefault();

try {
// Save new posts page settings.
await saveEditedEntityRecord( 'root', 'site', undefined, {
page_for_posts: 0,
show_on_front: isPageOnFrontSet ? 'page' : 'posts',
} );

// This second call to a save function is a workaround for a bug in
// `saveEditedEntityRecord`. This forces the root site settings to be updated.
// See https://github.com/WordPress/gutenberg/issues/67161.
await saveEntityRecord( 'root', 'site', {
page_for_posts: 0,
show_on_front: isPageOnFrontSet ? 'page' : 'posts',
} );
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't know much about this, but wouldn't just saveEntityRecord work correctly?

Copy link
Member Author

Choose a reason for hiding this comment

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

I also don't know much about these functions, but I believe ideally just saveEditedEntityRecord should be required, as that calls saveEntityRecord. However, using saveEntityRecord on its own does seem to work successfully, so maybe we could just use that function in these cases.

Copy link
Member Author

Choose a reason for hiding this comment

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

I looked through the codebase some more at other uses of these functions, and saveEntityRecord is often used on its own (rather than saveEditedEntityRecord as I expected), so I've removed saveEditedEntityRecord from both the "Set as homepage" and "Set as posts page" actions. They still work fine so I think this is a nice refactor!

Commit with these changes is here: 2cfd817.


createSuccessNotice( __( 'Posts page reset' ), {
type: 'snackbar',
} );
} catch ( error ) {
Copy link
Contributor

@t-hamano t-hamano Dec 10, 2024

Choose a reason for hiding this comment

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

Is this exception error actually possible? Because saveEntityRecord doesn't seem to throw any exceptions by default.

https://github.com/WordPress/gutenberg/blob/add/more-homepage-actions/packages/core-data/README.md#saveentityrecord

Copy link
Member Author

Choose a reason for hiding this comment

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

I think it's nice to have the catch block anyway, to catch any other errors (like a network error maybe). But I completely see your point, I don't think saveEntityRecord throws any exceptions.

const typedError = error;
const errorMessage =
typedError.message && typedError.code !== 'unknown_error'
? typedError.message
: __( 'An error occurred while resetting the posts page' );
createErrorNotice( errorMessage, { type: 'snackbar' } );
} finally {
closeModal?.();
}
}

const modalWarning = ! isPageOnFrontSet
? __( 'This will set the homepage to display latest posts.' )
: '';

const modalText = sprintf(
// translators: %1$s: title of the page to be unset as the posts page, %2$s: post pages warning message.
__(
'Reset the posts page? "%1$s" will no longer show the latest posts. %2$s'
),
pageTitle,
modalWarning
);

// translators: Button label to confirm resetting the posts page.
const modalButtonLabel = __( 'Reset posts page' );

return (
<form onSubmit={ onResetPostsPage }>
<VStack spacing="5">
<Text>{ modalText }</Text>
<HStack justify="right">
<Button
__next40pxDefaultSize
variant="tertiary"
onClick={ () => {
closeModal?.();
} }
disabled={ isSaving }
accessibleWhenDisabled
>
{ __( 'Cancel' ) }
</Button>
<Button
__next40pxDefaultSize
variant="primary"
type="submit"
disabled={ isSaving }
accessibleWhenDisabled
>
{ modalButtonLabel }
</Button>
</HStack>
</VStack>
</form>
);
};

export const useResetPostsPageAction = () => {
const { pageForPosts } = useSelect( ( select ) => {
const { getEntityRecord } = select( coreStore );
const siteSettings = getEntityRecord( 'root', 'site' );
return {
pageForPosts: siteSettings?.page_for_posts,
};
} );

return useMemo(
() => ( {
id: 'reset-posts-page',
label: __( 'Reset posts page' ),
isEligible( post ) {
if ( pageForPosts !== post.id ) {
return false;
}

return true;
},
RenderModal: ResetPostsPageModal,
} ),
[ pageForPosts ]
);
};
17 changes: 4 additions & 13 deletions packages/editor/src/components/post-actions/set-as-homepage.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,11 @@ import {
import { useDispatch, useSelect } from '@wordpress/data';
import { store as coreStore } from '@wordpress/core-data';
import { store as noticesStore } from '@wordpress/notices';
import { decodeEntities } from '@wordpress/html-entities';

const getItemTitle = ( item ) => {
if ( typeof item.title === 'string' ) {
return decodeEntities( item.title );
}
if ( item.title && 'rendered' in item.title ) {
return decodeEntities( item.title.rendered );
}
if ( item.title && 'raw' in item.title ) {
return decodeEntities( item.title.raw );
}
return '';
};
/**
* Internal dependencies
*/
import { getItemTitle } from '../../utils';

const SetAsHomepageModal = ( { items, closeModal } ) => {
const [ item ] = items;
Expand Down
Loading
Loading