Skip to content

Commit

Permalink
Pages: Add "Set as posts page" action (#67650)
Browse files Browse the repository at this point in the history
* Move getItemTitle to its own file

* Add unset homepage action

* Add unset as posts page action

* Add set as posts page action

* Update homepage action tests

* Rename unset options to reset

* Reword posts page reset notice

* Ensure Move to trash is always at end of list

* Update packages/editor/src/components/post-actions/actions.js

Co-authored-by: Aki Hamano <54422211+t-hamano@users.noreply.github.com>

* Update packages/editor/src/components/post-actions/reset-homepage.js

Co-authored-by: Aki Hamano <54422211+t-hamano@users.noreply.github.com>

* Remove getItemTitle from utils index.js

* Remove reset actions

* Slight refactor to modal warning in set as posts page action

* Remove use of saveEditedEntityRecord

* Check for currentPostsPage before setting modalwarning

* Add full stop to action success notices

---------

Co-authored-by: Aki Hamano <54422211+t-hamano@users.noreply.github.com>
Co-authored-by: mikachan <mikachan@git.wordpress.org>
Co-authored-by: t-hamano <wildworks@git.wordpress.org>
Co-authored-by: oandregal <oandregal@git.wordpress.org>
Co-authored-by: jameskoster <jameskoster@git.wordpress.org>
Co-authored-by: jasmussen <joen@git.wordpress.org>
Co-authored-by: paaljoachim <paaljoachim@git.wordpress.org>
Co-authored-by: youknowriad <youknowriad@git.wordpress.org>
  • Loading branch information
9 people authored Dec 13, 2024
1 parent 24d5f78 commit 9bdbada
Show file tree
Hide file tree
Showing 5 changed files with 251 additions and 42 deletions.
16 changes: 12 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,7 @@ 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 { useSetAsPostsPageAction } from './set-as-posts-page';

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

const setAsHomepageAction = useSetAsHomepageAction();
const shouldShowSetAsHomepageAction =
const setAsPostsPageAction = useSetAsPostsPageAction();
const shouldShowHomepageActions =
canManageOptions && ! hasFrontPageTemplate;

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

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

// Ensure "Move to trash" is always the last action.
actions = actions.sort( ( a, b ) =>
b.id === 'move-to-trash' ? -1 : 0
);

// 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 @@ -123,6 +130,7 @@ export function usePostActions( { postType, onActionPerformed, context } ) {
defaultActions,
onActionPerformed,
setAsHomepageAction,
shouldShowSetAsHomepageAction,
setAsPostsPageAction,
shouldShowHomepageActions,
] );
}
38 changes: 9 additions & 29 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/get-item-title';

const SetAsHomepageModal = ( { items, closeModal } ) => {
const [ item ] = items;
Expand All @@ -48,38 +39,27 @@ const SetAsHomepageModal = ( { items, closeModal } ) => {
}
);

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

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

try {
// Save new home page settings.
await saveEditedEntityRecord( 'root', 'site', undefined, {
page_on_front: item.id,
show_on_front: 'page',
} );

// 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: item.id,
show_on_front: 'page',
} );

createSuccessNotice( __( 'Homepage updated' ), {
createSuccessNotice( __( 'Homepage updated.' ), {
type: 'snackbar',
} );
} catch ( error ) {
const typedError = error;
const errorMessage =
typedError.message && typedError.code !== 'unknown_error'
? typedError.message
: __( 'An error occurred while setting the homepage' );
error.message && error.code !== 'unknown_error'
? error.message
: __( 'An error occurred while setting the homepage.' );
createErrorNotice( errorMessage, { type: 'snackbar' } );
} finally {
closeModal?.();
Expand Down
158 changes: 158 additions & 0 deletions packages/editor/src/components/post-actions/set-as-posts-page.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
/**
* 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/get-item-title';

const SetAsPostsPageModal = ( { items, closeModal } ) => {
const [ item ] = items;
const pageTitle = getItemTitle( item );
const { currentPostsPage, isPageForPostsSet, isSaving } = useSelect(
( select ) => {
const { getEntityRecord, isSavingEntityRecord } =
select( coreStore );
const siteSettings = getEntityRecord( 'root', 'site' );
const currentPostsPageItem = getEntityRecord(
'postType',
'page',
siteSettings?.page_for_posts
);
return {
currentPostsPage: currentPostsPageItem,
isPageForPostsSet: siteSettings?.page_for_posts !== 0,
isSaving: isSavingEntityRecord( 'root', 'site' ),
};
}
);

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

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

try {
await saveEntityRecord( 'root', 'site', {
page_for_posts: item.id,
show_on_front: 'page',
} );

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

const modalWarning =
isPageForPostsSet && currentPostsPage
? sprintf(
// translators: %s: title of the current posts page.
__( 'This will replace the current posts page: "%s"' ),
getItemTitle( currentPostsPage )
)
: __( 'This page will show the latest posts.' );

const modalText = sprintf(
// translators: %1$s: title of the page to be set as the posts page, %2$s: posts page replacement warning message.
__( 'Set "%1$s" as the posts page? %2$s' ),
pageTitle,
modalWarning
);

// translators: Button label to confirm setting the specified page as the posts page.
const modalButtonLabel = __( 'Set posts page' );

return (
<form onSubmit={ onSetPageAsPostsPage }>
<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 useSetAsPostsPageAction = () => {
const { pageOnFront, pageForPosts } = useSelect( ( select ) => {
const { getEntityRecord } = select( coreStore );
const siteSettings = getEntityRecord( 'root', 'site' );
return {
pageOnFront: siteSettings?.page_on_front,
pageForPosts: siteSettings?.page_for_posts,
};
} );

return useMemo(
() => ( {
id: 'set-as-posts-page',
label: __( 'Set as posts page' ),
isEligible( post ) {
if ( post.status !== 'publish' ) {
return false;
}

if ( post.type !== 'page' ) {
return false;
}

// Don't show the action if the page is already set as the homepage.
if ( pageOnFront === post.id ) {
return false;
}

// Don't show the action if the page is already set as the page for posts.
if ( pageForPosts === post.id ) {
return false;
}

return true;
},
RenderModal: SetAsPostsPageModal,
} ),
[ pageForPosts, pageOnFront ]
);
};
25 changes: 25 additions & 0 deletions packages/editor/src/utils/get-item-title.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**
* WordPress dependencies
*/
import { decodeEntities } from '@wordpress/html-entities';

/**
* Helper function to get the title of a post item.
* This is duplicated from the `@wordpress/fields` package.
* `packages/fields/src/actions/utils.ts`
*
* @param {Object} item The post item.
* @return {string} The title of the item, or an empty string if the title is not found.
*/
export function 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 '';
}
56 changes: 47 additions & 9 deletions test/e2e/specs/site-editor/homepage-settings.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@ test.describe( 'Homepage Settings via Editor', () => {
title: 'Homepage',
status: 'publish',
} );
await requestUtils.createPage( {
title: 'Sample page',
status: 'publish',
} );
await requestUtils.createPage( {
title: 'Draft page',
status: 'draft',
} );
} );

test.beforeEach( async ( { admin, page } ) => {
Expand All @@ -28,27 +36,30 @@ test.describe( 'Homepage Settings via Editor', () => {
] );
} );

test( 'should show "Set as homepage" action on pages with `publish` status', async ( {
test( 'should not show "Set as homepage" and "Set as posts page" action on pages with `draft` status', async ( {
page,
} ) => {
const samplePage = page
const draftPage = page
.getByRole( 'gridcell' )
.getByLabel( 'Homepage' );
const samplePageRow = page
.getByLabel( 'Draft page' );
const draftPageRow = page
.getByRole( 'row' )
.filter( { has: samplePage } );
await samplePageRow.hover();
await samplePageRow
.filter( { has: draftPage } );
await draftPageRow.hover();
await draftPageRow
.getByRole( 'button', {
name: 'Actions',
} )
.click();
await expect(
page.getByRole( 'menuitem', { name: 'Set as homepage' } )
).toBeVisible();
).toBeHidden();
await expect(
page.getByRole( 'menuitem', { name: 'Set as posts page' } )
).toBeHidden();
} );

test( 'should not show "Set as homepage" action on current homepage', async ( {
test( 'should show correct homepage actions based on current homepage or posts page', async ( {
page,
} ) => {
const samplePage = page
Expand All @@ -68,5 +79,32 @@ test.describe( 'Homepage Settings via Editor', () => {
await expect(
page.getByRole( 'menuitem', { name: 'Set as homepage' } )
).toBeHidden();
await expect(
page.getByRole( 'menuitem', { name: 'Set as posts page' } )
).toBeHidden();

const samplePageTwo = page
.getByRole( 'gridcell' )
.getByLabel( 'Sample page' );
const samplePageTwoRow = page
.getByRole( 'row' )
.filter( { has: samplePageTwo } );
// eslint-disable-next-line playwright/no-force-option
await samplePageTwoRow.click( { force: true } );
await samplePageTwoRow
.getByRole( 'button', {
name: 'Actions',
} )
.click();
await page
.getByRole( 'menuitem', { name: 'Set as posts page' } )
.click();
await page.getByRole( 'button', { name: 'Set posts page' } ).click();
await expect(
page.getByRole( 'menuitem', { name: 'Set as homepage' } )
).toBeHidden();
await expect(
page.getByRole( 'menuitem', { name: 'Set as posts page' } )
).toBeHidden();
} );
} );

0 comments on commit 9bdbada

Please sign in to comment.