diff --git a/packages/editor/src/components/post-actions/actions.js b/packages/editor/src/components/post-actions/actions.js index 64afec2417cd0..490bab2cc2fa4 100644 --- a/packages/editor/src/components/post-actions/actions.js +++ b/packages/editor/src/components/post-actions/actions.js @@ -14,7 +14,6 @@ import { parse } from '@wordpress/blocks'; import { DataForm } from '@wordpress/dataviews'; import { Button, - TextControl, __experimentalHStack as HStack, __experimentalVStack as VStack, } from '@wordpress/components'; @@ -23,7 +22,6 @@ import { * Internal dependencies */ import { - TEMPLATE_ORIGINS, TEMPLATE_PART_POST_TYPE, TEMPLATE_POST_TYPE, PATTERN_POST_TYPE, @@ -34,7 +32,7 @@ import { CreateTemplatePartModalContents } from '../create-template-part-modal'; import { getItemTitle } from '../../dataviews/actions/utils'; // Patterns. -const { PATTERN_TYPES, CreatePatternModalContents, useDuplicatePatternProps } = +const { CreatePatternModalContents, useDuplicatePatternProps } = unlock( patternsPrivateApis ); // TODO: this should be shared with other components (see post-fields in edit-site). @@ -58,25 +56,6 @@ const formDuplicateAction = { fields: [ 'title' ], }; -/** - * Check if a template is removable. - * - * @param {Object} template The template entity to check. - * @return {boolean} Whether the template is removable. - */ -function isTemplateRemovable( template ) { - if ( ! template ) { - return false; - } - // In patterns list page we map the templates parts to a different object - // than the one returned from the endpoint. This is why we need to check for - // two props whether is custom or has a theme file. - return ( - template?.source === TEMPLATE_ORIGINS.custom && - ! template?.has_theme_file - ); -} - const viewPostAction = { id: 'view-post', label: __( 'View' ), @@ -128,112 +107,6 @@ const postRevisionsAction = { }, }; -const renamePostAction = { - id: 'rename-post', - label: __( 'Rename' ), - isEligible( post ) { - if ( post.status === 'trash' ) { - return false; - } - // Templates, template parts and patterns have special checks for renaming. - if ( - ! [ - TEMPLATE_POST_TYPE, - TEMPLATE_PART_POST_TYPE, - ...Object.values( PATTERN_TYPES ), - ].includes( post.type ) - ) { - return post.permissions?.update; - } - // In the case of templates, we can only rename custom templates. - if ( post.type === TEMPLATE_POST_TYPE ) { - return ( - isTemplateRemovable( post ) && - post.is_custom && - post.permissions?.update - ); - } - // Make necessary checks for template parts and patterns. - const isTemplatePart = post.type === TEMPLATE_PART_POST_TYPE; - const isUserPattern = post.type === PATTERN_TYPES.user; - // In patterns list page we map the templates parts to a different object - // than the one returned from the endpoint. This is why we need to check for - // two props whether is custom or has a theme file. - const isCustomPattern = - isUserPattern || - ( isTemplatePart && post.source === TEMPLATE_ORIGINS.custom ); - const hasThemeFile = post?.has_theme_file; - return isCustomPattern && ! hasThemeFile && post.permissions?.update; - }, - RenderModal: ( { items, closeModal, onActionPerformed } ) => { - const [ item ] = items; - const [ title, setTitle ] = useState( () => getItemTitle( item ) ); - 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( __( 'Name updated' ), { - type: 'snackbar', - } ); - onActionPerformed?.( items ); - } catch ( error ) { - const errorMessage = - error.message && error.code !== 'unknown_error' - ? error.message - : __( 'An error occurred while updating the name' ); - createErrorNotice( errorMessage, { type: 'snackbar' } ); - } - } - - return ( -
- - - - - - - -
- ); - }, -}; - const useDuplicatePostAction = ( postType ) => { const userCanCreatePost = useSelect( ( select ) => { @@ -494,7 +367,6 @@ export function usePostActions( { postType, onActionPerformed, context } ) { const isPattern = postType === PATTERN_POST_TYPE; const isLoaded = !! postTypeObject; const supportsRevisions = !! postTypeObject?.supports?.revisions; - const supportsTitle = !! postTypeObject?.supports?.title; return useMemo( () => { if ( ! isLoaded ) { return []; @@ -512,7 +384,6 @@ export function usePostActions( { postType, onActionPerformed, context } ) { userCanCreatePostType && duplicateTemplatePartAction, isPattern && userCanCreatePostType && duplicatePatternAction, - supportsTitle && renamePostAction, ...defaultActions, ].filter( Boolean ); // Filter actions based on provided context. If not provided @@ -586,7 +457,6 @@ export function usePostActions( { postType, onActionPerformed, context } ) { onActionPerformed, isLoaded, supportsRevisions, - supportsTitle, context, ] ); } diff --git a/packages/editor/src/dataviews/actions/rename-post.tsx b/packages/editor/src/dataviews/actions/rename-post.tsx new file mode 100644 index 0000000000000..ef9da271111ea --- /dev/null +++ b/packages/editor/src/dataviews/actions/rename-post.tsx @@ -0,0 +1,146 @@ +/** + * WordPress dependencies + */ +import { useDispatch } from '@wordpress/data'; +import { store as coreStore } from '@wordpress/core-data'; +import { __ } from '@wordpress/i18n'; +import { useState } from '@wordpress/element'; +// @ts-ignore +import { privateApis as patternsPrivateApis } from '@wordpress/patterns'; +import { + Button, + TextControl, + __experimentalHStack as HStack, + __experimentalVStack as VStack, +} from '@wordpress/components'; +import type { Action } from '@wordpress/dataviews'; +import { store as noticesStore } from '@wordpress/notices'; + +/** + * Internal dependencies + */ +import { + TEMPLATE_ORIGINS, + TEMPLATE_PART_POST_TYPE, + TEMPLATE_POST_TYPE, +} from '../../store/constants'; +import { unlock } from '../../lock-unlock'; +import { + getItemTitle, + isTemplateRemovable, + isTemplate, + isTemplatePart, +} from './utils'; +import type { CoreDataError, PostWithPermissions } from '../types'; + +// Patterns. +const { PATTERN_TYPES } = unlock( patternsPrivateApis ); + +const renamePost: Action< PostWithPermissions > = { + id: 'rename-post', + label: __( 'Rename' ), + isEligible( post ) { + if ( post.status === 'trash' ) { + return false; + } + // Templates, template parts and patterns have special checks for renaming. + if ( + ! [ + TEMPLATE_POST_TYPE, + TEMPLATE_PART_POST_TYPE, + ...Object.values( PATTERN_TYPES ), + ].includes( post.type ) + ) { + return post.permissions?.update; + } + + // In the case of templates, we can only rename custom templates. + if ( isTemplate( post ) ) { + return ( + isTemplateRemovable( post ) && + post.is_custom && + post.permissions?.update + ); + } + + if ( isTemplatePart( post ) ) { + return ( + post.source === TEMPLATE_ORIGINS.custom && + ! post?.has_theme_file && + post.permissions?.update + ); + } + + return post.type === PATTERN_TYPES.user && post.permissions?.update; + }, + RenderModal: ( { items, closeModal, onActionPerformed } ) => { + const [ item ] = items; + const [ title, setTitle ] = useState( () => getItemTitle( item ) ); + const { editEntityRecord, saveEditedEntityRecord } = + useDispatch( coreStore ); + const { createSuccessNotice, createErrorNotice } = + useDispatch( noticesStore ); + + async function onRename( event: React.FormEvent ) { + 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( __( 'Name updated' ), { + type: 'snackbar', + } ); + onActionPerformed?.( items ); + } catch ( error ) { + const typedError = error as CoreDataError; + const errorMessage = + typedError.message && typedError.code !== 'unknown_error' + ? typedError.message + : __( 'An error occurred while updating the name' ); + createErrorNotice( errorMessage, { type: 'snackbar' } ); + } + } + + return ( +
+ + + + + + + +
+ ); + }, +}; + +export default renamePost; diff --git a/packages/editor/src/dataviews/actions/utils.ts b/packages/editor/src/dataviews/actions/utils.ts index 56c8c9f54c850..7da1f71728365 100644 --- a/packages/editor/src/dataviews/actions/utils.ts +++ b/packages/editor/src/dataviews/actions/utils.ts @@ -12,11 +12,19 @@ import { TEMPLATE_POST_TYPE, } from '../../store/constants'; -import type { Post, TemplateOrTemplatePart } from '../types'; +import type { Post, TemplatePart, Template } from '../types'; + +export function isTemplate( post: Post ): post is Template { + return post.type === TEMPLATE_POST_TYPE; +} + +export function isTemplatePart( post: Post ): post is TemplatePart { + return post.type === TEMPLATE_PART_POST_TYPE; +} export function isTemplateOrTemplatePart( p: Post -): p is TemplateOrTemplatePart { +): p is Template | TemplatePart { return p.type === TEMPLATE_POST_TYPE || p.type === TEMPLATE_PART_POST_TYPE; } @@ -39,7 +47,7 @@ export function getItemTitle( item: Post ) { * @param template The template entity to check. * @return Whether the template is removable. */ -export function isTemplateRemovable( template: TemplateOrTemplatePart ) { +export function isTemplateRemovable( template: Template | TemplatePart ) { if ( ! template ) { return false; } diff --git a/packages/editor/src/dataviews/store/private-actions.ts b/packages/editor/src/dataviews/store/private-actions.ts index d5e12e298039a..a8b1573a528b5 100644 --- a/packages/editor/src/dataviews/store/private-actions.ts +++ b/packages/editor/src/dataviews/store/private-actions.ts @@ -13,8 +13,9 @@ import exportPattern from '../actions/export-pattern'; import resetPost from '../actions/reset-post'; import trashPost from '../actions/trash-post'; import permanentlyDeletePost from '../actions/permanently-delete-post'; -import restorePost from '../actions/restore-post'; +import renamePost from '../actions/rename-post'; import reorderPage from '../actions/reorder-page'; +import restorePost from '../actions/restore-post'; import type { PostType } from '../types'; import { store as editorStore } from '../../store'; import { unlock } from '../../lock-unlock'; @@ -74,6 +75,7 @@ export const registerPostTypeActions = .getPostType( postType ) ) as PostType; const actions = [ + postTypeConfig.supports?.title ? renamePost : undefined, postTypeConfig?.supports?.[ 'page-attributes' ] ? reorderPage : undefined, diff --git a/packages/editor/src/dataviews/types.ts b/packages/editor/src/dataviews/types.ts index 0c31bef195eb9..80b6f3c5ceb85 100644 --- a/packages/editor/src/dataviews/types.ts +++ b/packages/editor/src/dataviews/types.ts @@ -13,8 +13,17 @@ export interface BasePost { type: string; id: string | number; } -export interface TemplateOrTemplatePart extends BasePost { - type: 'wp_template' | 'wp_template_part'; + +export interface Template extends BasePost { + type: 'wp_template'; + is_custom: boolean; + source: string; + has_theme_file: boolean; + id: string; +} + +export interface TemplatePart extends BasePost { + type: 'wp_template_part'; source: string; has_theme_file: boolean; id: string; @@ -31,7 +40,7 @@ export interface PostWithPageAttributesSupport extends BasePost { menu_order: number; } -export type Post = TemplateOrTemplatePart | Pattern | BasePost; +export type Post = Template | TemplatePart | Pattern | BasePost; export type PostWithPermissions = Post & { permissions: { @@ -44,6 +53,7 @@ export interface PostType { slug: string; supports?: { 'page-attributes'?: boolean; + title?: boolean; }; }