From b83f0f983d8da9a6709c4237281889b07158da4b Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Fri, 10 May 2024 15:10:01 +0100 Subject: [PATCH] Post Type Actions: Unify the list of available actions (#61520) Co-authored-by: youknowriad Co-authored-by: jorgefilipecosta --- .../src/components/dataviews-actions/index.js | 38 ++++ .../src/components/page-pages/index.js | 46 +---- .../dataviews-pattern-actions.js | 89 -------- .../src/components/page-patterns/index.js | 40 ++-- .../src/components/page-templates/index.js | 28 +-- .../src/components/post-actions/actions.js | 194 +++++++++--------- .../src/components/post-actions/index.js | 42 +--- 7 files changed, 161 insertions(+), 316 deletions(-) create mode 100644 packages/edit-site/src/components/dataviews-actions/index.js diff --git a/packages/edit-site/src/components/dataviews-actions/index.js b/packages/edit-site/src/components/dataviews-actions/index.js new file mode 100644 index 0000000000000..ed6522995d3b7 --- /dev/null +++ b/packages/edit-site/src/components/dataviews-actions/index.js @@ -0,0 +1,38 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { edit } from '@wordpress/icons'; +import { useMemo } from '@wordpress/element'; +import { privateApis as routerPrivateApis } from '@wordpress/router'; + +/** + * Internal dependencies + */ +import { unlock } from '../../lock-unlock'; + +const { useHistory } = unlock( routerPrivateApis ); + +export const useEditPostAction = () => { + const history = useHistory(); + return useMemo( + () => ( { + id: 'edit-post', + label: __( 'Edit' ), + isPrimary: true, + icon: edit, + isEligible( { status } ) { + return status !== 'trash'; + }, + callback( items ) { + const post = items[ 0 ]; + history.push( { + postId: post.id, + postType: post.type, + canvas: 'edit', + } ); + }, + } ), + [ history ] + ); +}; diff --git a/packages/edit-site/src/components/page-pages/index.js b/packages/edit-site/src/components/page-pages/index.js index 8ac080c0ac97f..1215320f4df9f 100644 --- a/packages/edit-site/src/components/page-pages/index.js +++ b/packages/edit-site/src/components/page-pages/index.js @@ -32,11 +32,10 @@ import { import AddNewPageModal from '../add-new-page'; import Media from '../media'; import { unlock } from '../../lock-unlock'; +import { useEditPostAction } from '../dataviews-actions'; const { usePostActions } = unlock( editorPrivateApis ); - const { useLocation, useHistory } = unlock( routerPrivateApis ); - const EMPTY_ARRAY = []; function useView( postType ) { @@ -188,29 +187,6 @@ function FeaturedImage( { item, viewType } ) { ); } -let PAGE_ACTIONS = [ - 'edit-post', - 'view-post', - 'restore', - 'permanently-delete', - 'view-post-revisions', - 'rename-post', - 'move-to-trash', -]; - -if ( process.env.IS_GUTENBERG_PLUGIN ) { - PAGE_ACTIONS = [ - 'edit-post', - 'view-post', - 'restore', - 'permanently-delete', - 'view-post-revisions', - 'duplicate-post', - 'rename-post', - 'move-to-trash', - ]; -} - export default function PagePages() { const postType = 'page'; const [ view, setView ] = useView( postType ); @@ -360,20 +336,14 @@ export default function PagePages() { ], [ authors, view.type ] ); - const onActionPerformed = useCallback( - ( actionId, items ) => { - if ( actionId === 'edit-post' ) { - const post = items[ 0 ]; - history.push( { - postId: post.id, - postType: post.type, - canvas: 'edit', - } ); - } - }, - [ history ] + + const postTypeActions = usePostActions( 'page' ); + const editAction = useEditPostAction(); + const actions = useMemo( + () => [ editAction, ...postTypeActions ], + [ postTypeActions, editAction ] ); - const actions = usePostActions( onActionPerformed, PAGE_ACTIONS ); + const onChangeView = useCallback( ( newView ) => { if ( newView.type !== view.type ) { diff --git a/packages/edit-site/src/components/page-patterns/dataviews-pattern-actions.js b/packages/edit-site/src/components/page-patterns/dataviews-pattern-actions.js index 12a2b99e7dbcb..afa69e9752c5b 100644 --- a/packages/edit-site/src/components/page-patterns/dataviews-pattern-actions.js +++ b/packages/edit-site/src/components/page-patterns/dataviews-pattern-actions.js @@ -11,14 +11,11 @@ import { downloadBlob } from '@wordpress/blob'; import { __, _x, sprintf } from '@wordpress/i18n'; import { Button, - TextControl, __experimentalHStack as HStack, __experimentalVStack as VStack, __experimentalText as Text, } from '@wordpress/components'; -import { store as coreStore } from '@wordpress/core-data'; import { useDispatch } from '@wordpress/data'; -import { useState } from '@wordpress/element'; import { store as noticesStore } from '@wordpress/notices'; import { decodeEntities } from '@wordpress/html-entities'; import { store as reusableBlocksStore } from '@wordpress/reusable-blocks'; @@ -91,92 +88,6 @@ export const exportJSONaction = { }, }; -export const renameAction = { - id: 'rename-pattern', - label: __( 'Rename' ), - isEligible: ( item ) => { - const isTemplatePart = item.type === TEMPLATE_PART_POST_TYPE; - const isUserPattern = item.type === PATTERN_TYPES.user; - const isCustomPattern = - isUserPattern || ( isTemplatePart && item.isCustom ); - const hasThemeFile = isTemplatePart && item.templatePart.has_theme_file; - return isCustomPattern && ! hasThemeFile; - }, - RenderModal: ( { items, closeModal } ) => { - const [ item ] = items; - const [ title, setTitle ] = useState( () => item.title ); - const { editEntityRecord, saveEditedEntityRecord } = - useDispatch( coreStore ); - const { createSuccessNotice, createErrorNotice } = - useDispatch( noticesStore ); - async function onRename( event ) { - event.preventDefault(); - try { - await editEntityRecord( 'postType', item.type, item.id, { - title, - } ); - // Update state before saving rerenders the list. - setTitle( '' ); - closeModal(); - // Persist edited entity. - await saveEditedEntityRecord( 'postType', item.type, item.id, { - throwOnError: true, - } ); - createSuccessNotice( - item.type === TEMPLATE_PART_POST_TYPE - ? __( 'Template part renamed.' ) - : __( 'Pattern renamed.' ), - { type: 'snackbar' } - ); - } catch ( error ) { - const fallbackErrorMessage = - item.type === TEMPLATE_PART_POST_TYPE - ? __( - 'An error occurred while renaming the template part.' - ) - : __( 'An error occurred while renaming the pattern.' ); - const errorMessage = - error.message && error.code !== 'unknown_error' - ? error.message - : fallbackErrorMessage; - createErrorNotice( errorMessage, { type: 'snackbar' } ); - } - } - return ( -
- - - - - - - -
- ); - }, -}; - const canDeleteOrReset = ( item ) => { const isTemplatePart = item.type === TEMPLATE_PART_POST_TYPE; const isUserPattern = item.type === PATTERN_TYPES.user; diff --git a/packages/edit-site/src/components/page-patterns/index.js b/packages/edit-site/src/components/page-patterns/index.js index bf3419ad768bc..724f60ba39103 100644 --- a/packages/edit-site/src/components/page-patterns/index.js +++ b/packages/edit-site/src/components/page-patterns/index.js @@ -48,7 +48,6 @@ import { } from '../../utils/constants'; import { exportJSONaction, - renameAction, resetAction, deleteAction, duplicatePatternAction, @@ -60,12 +59,13 @@ import usePatterns from './use-patterns'; import PatternsHeader from './header'; import { useLink } from '../routes/link'; import { useAddedBy } from '../page-templates/hooks'; +import { useEditPostAction } from '../dataviews-actions'; const { ExperimentalBlockEditorProvider, useGlobalStyle } = unlock( blockEditorPrivateApis ); const { usePostActions } = unlock( editorPrivateApis ); -const { useHistory, useLocation } = unlock( routerPrivateApis ); +const { useLocation } = unlock( routerPrivateApis ); const EMPTY_ARRAY = []; const defaultConfigPerViewType = { @@ -375,45 +375,29 @@ export default function DataviewsPatterns() { return filterSortAndPaginate( patterns, viewWithoutFilters, fields ); }, [ patterns, view, fields, type ] ); - const history = useHistory(); - const onActionPerformed = useCallback( - ( actionId, items ) => { - if ( actionId === 'edit-post' ) { - const post = items[ 0 ]; - history.push( { - postId: post.id, - postType: post.type, - categoryId, - categoryType: type, - canvas: 'edit', - } ); - } - }, - [ history, categoryId, type ] - ); - const [ editAction, viewRevisionsAction ] = usePostActions( - onActionPerformed, - [ 'edit-post', 'view-post-revisions' ] - ); + const templatePartActions = usePostActions( TEMPLATE_PART_POST_TYPE ); + const patternActions = usePostActions( PATTERN_TYPES.user ); + const editAction = useEditPostAction(); + const actions = useMemo( () => { if ( type === TEMPLATE_PART_POST_TYPE ) { return [ editAction, - renameAction, + ...templatePartActions, duplicateTemplatePartAction, - viewRevisionsAction, resetAction, deleteAction, - ]; + ].filter( Boolean ); } return [ - renameAction, + editAction, + ...patternActions, duplicatePatternAction, exportJSONaction, resetAction, deleteAction, - ]; - }, [ type, editAction, viewRevisionsAction ] ); + ].filter( Boolean ); + }, [ editAction, type, templatePartActions, patternActions ] ); const onChangeView = useCallback( ( newView ) => { if ( newView.type !== view.type ) { diff --git a/packages/edit-site/src/components/page-templates/index.js b/packages/edit-site/src/components/page-templates/index.js index c0646981b3933..fc9326a219105 100644 --- a/packages/edit-site/src/components/page-templates/index.js +++ b/packages/edit-site/src/components/page-templates/index.js @@ -43,6 +43,7 @@ import { import usePatternSettings from '../page-patterns/use-pattern-settings'; import { unlock } from '../../lock-unlock'; +import { useEditPostAction } from '../dataviews-actions'; const { usePostActions } = unlock( editorPrivateApis ); @@ -183,14 +184,6 @@ function Preview( { item, viewType } ) { ); } -const TEMPLATE_ACTIONS = [ - 'edit-post', - 'reset-template', - 'rename-template', - 'view-post-revisions', - 'delete-template', -]; - export default function PageTemplates() { const { params } = useLocation(); const { activeView = 'all', layout } = params; @@ -330,22 +323,13 @@ export default function PageTemplates() { return filterSortAndPaginate( records, view, fields ); }, [ records, view, fields ] ); - const onActionPerformed = useCallback( - ( actionId, items ) => { - if ( actionId === 'edit-post' ) { - const post = items[ 0 ]; - history.push( { - postId: post.id, - postType: post.type, - canvas: 'edit', - } ); - } - }, - [ history ] + const postTypeActions = usePostActions( TEMPLATE_POST_TYPE ); + const editAction = useEditPostAction(); + const actions = useMemo( + () => [ editAction, ...postTypeActions ], + [ postTypeActions, editAction ] ); - const actions = usePostActions( onActionPerformed, TEMPLATE_ACTIONS ); - const onChangeView = useCallback( ( newView ) => { if ( newView.type !== view.type ) { diff --git a/packages/editor/src/components/post-actions/actions.js b/packages/editor/src/components/post-actions/actions.js index f60d75435bdb3..4656e34dd6728 100644 --- a/packages/editor/src/components/post-actions/actions.js +++ b/packages/editor/src/components/post-actions/actions.js @@ -1,9 +1,9 @@ /** * WordPress dependencies */ -import { external, trash, edit, backup } from '@wordpress/icons'; +import { external, trash, backup } from '@wordpress/icons'; import { addQueryArgs } from '@wordpress/url'; -import { useDispatch } from '@wordpress/data'; +import { useDispatch, useSelect } from '@wordpress/data'; import { decodeEntities } from '@wordpress/html-entities'; import { store as coreStore } from '@wordpress/core-data'; import { __, _n, sprintf, _x } from '@wordpress/i18n'; @@ -21,7 +21,12 @@ import { /** * Internal dependencies */ -import { TEMPLATE_ORIGINS, TEMPLATE_POST_TYPE } from '../../store/constants'; +import { + TEMPLATE_ORIGINS, + TEMPLATE_PART_POST_TYPE, + TEMPLATE_POST_TYPE, + PATTERN_POST_TYPE, +} from '../../store/constants'; import { store as editorStore } from '../../store'; import { unlock } from '../../lock-unlock'; import isTemplateRevertable from '../../store/utils/is-template-revertable'; @@ -457,20 +462,6 @@ const viewPostAction = { }, }; -const editPostAction = { - id: 'edit-post', - label: __( 'Edit' ), - isPrimary: true, - icon: edit, - isEligible( { status } ) { - return status !== 'trash'; - }, - callback( posts, onActionPerformed ) { - if ( onActionPerformed ) { - onActionPerformed( posts ); - } - }, -}; const postRevisionsAction = { id: 'view-post-revisions', label: __( 'View revisions' ), @@ -997,95 +988,96 @@ const renameTemplateAction = { }, }; -export function usePostActions( onActionPerformed, actionIds = null ) { +export function usePostActions( postType, onActionPerformed ) { + const { postTypeObject } = useSelect( + ( select ) => { + const { getPostType } = select( coreStore ); + return { + postTypeObject: getPostType( postType ), + }; + }, + [ postType ] + ); + const permanentlyDeletePostAction = usePermanentlyDeletePostAction(); const restorePostAction = useRestorePostAction(); - return useMemo( - () => { - // By default, return all actions... - const defaultActions = [ - editPostAction, - resetTemplateAction, - viewPostAction, - restorePostAction, - deleteTemplateAction, - permanentlyDeletePostAction, - postRevisionsAction, - duplicatePostAction, - renamePostAction, - renameTemplateAction, - trashPostAction, - ]; + const isTemplateOrTemplatePart = [ + TEMPLATE_POST_TYPE, + TEMPLATE_PART_POST_TYPE, + ].includes( postType ); + const isPattern = postType === PATTERN_POST_TYPE; + const isLoaded = !! postTypeObject; + return useMemo( () => { + if ( ! isLoaded ) { + return []; + } - // ... unless `actionIds` was specified, in which case we find the - // actions matching the given IDs. - const actions = actionIds - ? actionIds.map( ( actionId ) => - defaultActions.find( ( { id } ) => actionId === id ) - ) - : defaultActions; + const actions = [ + isTemplateOrTemplatePart && resetTemplateAction, + postTypeObject?.viewable && viewPostAction, + ! isTemplateOrTemplatePart && restorePostAction, + isTemplateOrTemplatePart && deleteTemplateAction, + ! isTemplateOrTemplatePart && permanentlyDeletePostAction, + postRevisionsAction, + process.env.IS_GUTENBERG_PLUGIN + ? ! isTemplateOrTemplatePart && + ! isPattern && + duplicatePostAction + : false, + ! isTemplateOrTemplatePart && renamePostAction, + isTemplateOrTemplatePart && renameTemplateAction, + ! isTemplateOrTemplatePart && trashPostAction, + ].filter( Boolean ); - if ( onActionPerformed ) { - for ( let i = 0; i < actions.length; ++i ) { - if ( actions[ i ].callback ) { - const existingCallback = actions[ i ].callback; - actions[ i ] = { - ...actions[ i ], - callback: ( items, _onActionPerformed ) => { - existingCallback( items, ( _items ) => { - if ( _onActionPerformed ) { - _onActionPerformed( _items ); - } - onActionPerformed( - actions[ i ].id, - _items - ); - } ); - }, - }; - } - if ( actions[ i ].RenderModal ) { - const ExistingRenderModal = actions[ i ].RenderModal; - actions[ i ] = { - ...actions[ i ], - RenderModal: ( props ) => { - return ( - { - if ( props.onActionPerformed ) { - props.onActionPerformed( - _items - ); - } - onActionPerformed( - actions[ i ].id, - _items - ); - } } - /> - ); - }, - }; - } + if ( onActionPerformed ) { + for ( let i = 0; i < actions.length; ++i ) { + if ( actions[ i ].callback ) { + const existingCallback = actions[ i ].callback; + actions[ i ] = { + ...actions[ i ], + callback: ( items, _onActionPerformed ) => { + existingCallback( items, ( _items ) => { + if ( _onActionPerformed ) { + _onActionPerformed( _items ); + } + onActionPerformed( actions[ i ].id, _items ); + } ); + }, + }; + } + if ( actions[ i ].RenderModal ) { + const ExistingRenderModal = actions[ i ].RenderModal; + actions[ i ] = { + ...actions[ i ], + RenderModal: ( props ) => { + return ( + { + if ( props.onActionPerformed ) { + props.onActionPerformed( _items ); + } + onActionPerformed( + actions[ i ].id, + _items + ); + } } + /> + ); + }, + }; } } - return actions; - }, + } - // Disable reason: if provided, `actionIds` is a shallow array of - // strings, and the strings themselves should be part of the useMemo - // dependencies. Two different disable statements are needed, as the - // first flags what it thinks are missing dependencies, and the second - // flags the array spread operation. - // - // eslint-disable-next-line react-hooks/exhaustive-deps - [ - // eslint-disable-next-line react-hooks/exhaustive-deps - ...( actionIds || [] ), - permanentlyDeletePostAction, - restorePostAction, - onActionPerformed, - ] - ); + return actions; + }, [ + isTemplateOrTemplatePart, + isPattern, + postTypeObject?.viewable, + permanentlyDeletePostAction, + restorePostAction, + onActionPerformed, + isLoaded, + ] ); } diff --git a/packages/editor/src/components/post-actions/index.js b/packages/editor/src/components/post-actions/index.js index 24d74c3f0d97f..14914ccd8f320 100644 --- a/packages/editor/src/components/post-actions/index.js +++ b/packages/editor/src/components/post-actions/index.js @@ -17,11 +17,6 @@ import { moreVertical } from '@wordpress/icons'; import { unlock } from '../../lock-unlock'; import { usePostActions } from './actions'; import { store as editorStore } from '../../store'; -import { - TEMPLATE_POST_TYPE, - TEMPLATE_PART_POST_TYPE, - PATTERN_POST_TYPE, -} from '../../store/constants'; const { DropdownMenuV2: DropdownMenu, @@ -31,36 +26,16 @@ const { kebabCase, } = unlock( componentsPrivateApis ); -let POST_ACTIONS_WHILE_EDITING = [ - 'view-post', - 'view-post-revisions', - 'rename-post', - 'move-to-trash', -]; - -if ( process.env.IS_GUTENBERG_PLUGIN ) { - POST_ACTIONS_WHILE_EDITING = [ - 'view-post', - 'view-post-revisions', - 'duplicate-post', - 'rename-post', - 'move-to-trash', - ]; -} - export default function PostActions( { onActionPerformed, buttonProps } ) { const [ isActionsMenuOpen, setIsActionsMenuOpen ] = useState( false ); - const { postType, item } = useSelect( ( select ) => { + const { item, postType } = useSelect( ( select ) => { const { getCurrentPostType, getCurrentPost } = select( editorStore ); return { - postType: getCurrentPostType(), item: getCurrentPost(), + postType: getCurrentPostType(), }; - } ); - const allActions = usePostActions( - onActionPerformed, - POST_ACTIONS_WHILE_EDITING - ); + }, [] ); + const allActions = usePostActions( postType, onActionPerformed ); const actions = useMemo( () => { return allActions.filter( ( action ) => { @@ -68,15 +43,6 @@ export default function PostActions( { onActionPerformed, buttonProps } ) { } ); }, [ allActions, item ] ); - if ( - [ - TEMPLATE_POST_TYPE, - TEMPLATE_PART_POST_TYPE, - PATTERN_POST_TYPE, - ].includes( postType ) - ) { - return null; - } return (