From 10ee0513a6532add84f89c3396e187738cfcac7f Mon Sep 17 00:00:00 2001 From: Jorge Costa Date: Tue, 16 Apr 2024 16:03:12 +0100 Subject: [PATCH] Update: Move pattern actions to the editor package. --- .../dataviews-pattern-actions.js | 374 ----------------- .../src/components/page-patterns/index.js | 89 +++-- packages/editor/package.json | 2 + .../src/components/post-actions/actions.js | 375 +++++++++++++++++- 4 files changed, 434 insertions(+), 406 deletions(-) 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 50baed5658e92..69e300cc2ffcf 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 @@ -1,397 +1,23 @@ -/** - * External dependencies - */ -import { paramCase as kebabCase } from 'change-case'; -import { downloadZip } from 'client-zip'; - /** * WordPress dependencies */ import { getQueryArgs } from '@wordpress/url'; -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'; -import { store as editorStore } from '@wordpress/editor'; import { privateApis as routerPrivateApis } from '@wordpress/router'; -import { privateApis as patternsPrivateApis } from '@wordpress/patterns'; /** * Internal dependencies */ import { unlock } from '../../lock-unlock'; -import { store as editSiteStore } from '../../store'; import { - PATTERN_TYPES, TEMPLATE_PART_POST_TYPE, PATTERN_DEFAULT_CATEGORY, } from '../../utils/constants'; import { CreateTemplatePartModalContents } from '../create-template-part-modal'; const { useHistory } = unlock( routerPrivateApis ); -const { CreatePatternModalContents, useDuplicatePatternProps } = - unlock( patternsPrivateApis ); - -function getJsonFromItem( item ) { - return JSON.stringify( - { - __file: item.type, - title: item.title || item.name, - content: item.patternPost.content.raw, - syncStatus: item.patternPost.wp_pattern_sync_status, - }, - null, - 2 - ); -} - -export const exportJSONaction = { - id: 'export-pattern', - label: __( 'Export as JSON' ), - supportsBulk: true, - isEligible: ( item ) => item.type === PATTERN_TYPES.user, - callback: async ( items ) => { - if ( items.length === 1 ) { - return downloadBlob( - `${ kebabCase( items[ 0 ].title || items[ 0 ].name ) }.json`, - getJsonFromItem( items[ 0 ] ), - 'application/json' - ); - } - const nameCount = {}; - const filesToZip = items.map( ( item ) => { - const name = kebabCase( item.title || item.name ); - nameCount[ name ] = ( nameCount[ name ] || 0 ) + 1; - return { - name: `${ - name + - ( nameCount[ name ] > 1 - ? '-' + ( nameCount[ name ] - 1 ) - : '' ) - }.json`, - lastModified: new Date(), - input: getJsonFromItem( item ), - }; - } ); - return downloadBlob( - __( 'patterns-export' ) + '.zip', - await downloadZip( filesToZip ).blob(), - 'application/zip' - ); - }, -}; - -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; - return isUserPattern || ( isTemplatePart && item.isCustom ); -}; - -export const deleteAction = { - id: 'delete-pattern', - label: __( 'Delete' ), - isEligible: ( item ) => { - const isTemplatePart = item.type === TEMPLATE_PART_POST_TYPE; - const hasThemeFile = isTemplatePart && item.templatePart.has_theme_file; - return canDeleteOrReset( item ) && ! hasThemeFile; - }, - hideModalHeader: true, - supportsBulk: true, - RenderModal: ( { items, closeModal, onPerform } ) => { - const { __experimentalDeleteReusableBlock } = - useDispatch( reusableBlocksStore ); - const { createErrorNotice, createSuccessNotice } = - useDispatch( noticesStore ); - const { removeTemplates } = unlock( useDispatch( editorStore ) ); - - const deletePattern = async () => { - const promiseResult = await Promise.allSettled( - items.map( ( item ) => { - return __experimentalDeleteReusableBlock( item.id ); - } ) - ); - // If all the promises were fulfilled with success. - if ( - promiseResult.every( ( { status } ) => status === 'fulfilled' ) - ) { - let successMessage; - if ( promiseResult.length === 1 ) { - successMessage = sprintf( - /* translators: The posts's title. */ - __( '"%s" deleted.' ), - items[ 0 ].title - ); - } else { - successMessage = __( 'The patterns were deleted.' ); - } - createSuccessNotice( successMessage, { - type: 'snackbar', - id: 'edit-site-page-trashed', - } ); - } else { - // If there was at lease one failure. - let errorMessage; - // If we were trying to delete a single pattern. - if ( promiseResult.length === 1 ) { - if ( promiseResult[ 0 ].reason?.message ) { - errorMessage = promiseResult[ 0 ].reason.message; - } else { - errorMessage = __( - 'An error occurred while deleting the pattern.' - ); - } - // If we were trying to delete multiple patterns. - } else { - const errorMessages = new Set(); - const failedPromises = promiseResult.filter( - ( { status } ) => status === 'rejected' - ); - for ( const failedPromise of failedPromises ) { - if ( failedPromise.reason?.message ) { - errorMessages.add( failedPromise.reason.message ); - } - } - if ( errorMessages.size === 0 ) { - errorMessage = __( - 'An error occurred while deleting the patterns.' - ); - } else if ( errorMessages.size === 1 ) { - errorMessage = sprintf( - /* translators: %s: an error message */ - __( - 'An error occurred while deleting the patterns: %s' - ), - [ ...errorMessages ][ 0 ] - ); - } else { - errorMessage = sprintf( - /* translators: %s: a list of comma separated error messages */ - __( - 'Some errors occurred while deleting the patterns: %s' - ), - [ ...errorMessages ].join( ',' ) - ); - } - createErrorNotice( errorMessage, { - type: 'snackbar', - } ); - } - } - }; - const deleteItem = () => { - if ( items[ 0 ].type === TEMPLATE_PART_POST_TYPE ) { - removeTemplates( items ); - } else { - deletePattern(); - } - if ( onPerform ) { - onPerform(); - } - closeModal(); - }; - let questionMessage; - if ( items.length === 1 ) { - questionMessage = sprintf( - // translators: %s: The page's title. - __( 'Are you sure you want to delete "%s"?' ), - decodeEntities( items[ 0 ].title || items[ 0 ].name ) - ); - } else if ( - items.length > 1 && - items[ 0 ].type === TEMPLATE_PART_POST_TYPE - ) { - questionMessage = sprintf( - // translators: %d: The number of template parts (2 or more). - __( 'Are you sure you want to delete %d template parts?' ), - items.length - ); - } else { - questionMessage = sprintf( - // translators: %d: The number of patterns (2 or more). - __( 'Are you sure you want to delete %d patterns?' ), - items.length - ); - } - return ( - - { questionMessage } - - - - - - ); - }, -}; - -export const resetAction = { - id: 'reset-action', - label: __( 'Reset' ), - isEligible: ( item ) => { - const isTemplatePart = item.type === TEMPLATE_PART_POST_TYPE; - const hasThemeFile = isTemplatePart && item.templatePart.has_theme_file; - return canDeleteOrReset( item ) && hasThemeFile; - }, - hideModalHeader: true, - RenderModal: ( { items, closeModal } ) => { - const [ item ] = items; - const { removeTemplate } = useDispatch( editSiteStore ); - return ( - - - { __( 'Reset to default and clear all customizations?' ) } - - - - - - - ); - }, -}; - -export const duplicatePatternAction = { - id: 'duplicate-pattern', - label: _x( 'Duplicate', 'action label' ), - isEligible: ( item ) => item.type !== TEMPLATE_PART_POST_TYPE, - modalHeader: _x( 'Duplicate pattern', 'action label' ), - RenderModal: ( { items, closeModal } ) => { - const [ item ] = items; - const { categoryId = PATTERN_DEFAULT_CATEGORY } = getQueryArgs( - window.location.href - ); - const isThemePattern = item.type === PATTERN_TYPES.theme; - const history = useHistory(); - function onPatternSuccess( { pattern } ) { - history.push( { - categoryType: PATTERN_TYPES.theme, - categoryId, - postType: PATTERN_TYPES.user, - postId: pattern.id, - } ); - closeModal(); - } - const duplicatedProps = useDuplicatePatternProps( { - pattern: isThemePattern ? item : item.patternPost, - onSuccess: onPatternSuccess, - } ); - return ( - - ); - }, -}; export const duplicateTemplatePartAction = { id: 'duplicate-template-part', diff --git a/packages/edit-site/src/components/page-patterns/index.js b/packages/edit-site/src/components/page-patterns/index.js index 6fa84df7a0171..a643e45c429ad 100644 --- a/packages/edit-site/src/components/page-patterns/index.js +++ b/packages/edit-site/src/components/page-patterns/index.js @@ -55,14 +55,7 @@ import { ENUMERATION_TYPE, OPERATOR_IS, } from '../../utils/constants'; -import { - exportJSONaction, - renameAction, - resetAction, - deleteAction, - duplicatePatternAction, - duplicateTemplatePartAction, -} from './dataviews-pattern-actions'; +import { duplicateTemplatePartAction } from './dataviews-pattern-actions'; import usePatternSettings from './use-pattern-settings'; import { unlock } from '../../lock-unlock'; import usePatterns from './use-patterns'; @@ -440,42 +433,78 @@ export default function DataviewsPatterns() { 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', - } ); + switch ( actionId ) { + case 'edit-post': + { + const post = items[ 0 ]; + history.push( { + postId: post.id, + postType: post.type, + categoryId, + categoryType: type, + canvas: 'edit', + } ); + } + break; + case 'duplicate-pattern': + { + const pattern = items[ 0 ]; + history.push( { + categoryType: PATTERN_TYPES.theme, + categoryId, + postType: PATTERN_TYPES.user, + postId: pattern.id, + } ); + } + break; } }, - [ history ] - ); - const [ editAction, viewRevisionsAction ] = usePostActions( - onActionPerformed, - [ 'edit-post', 'view-post-revisions' ] + [ categoryId, history, type ] ); + const [ + editAction, + renamePatternAction, + duplicatePatternAction, + exportPatternAsJSONAction, + viewRevisionsAction, + resetTemplatePartAction, + deletePatternAction, + ] = usePostActions( onActionPerformed, [ + 'edit-post', + 'rename-pattern', + 'duplicate-pattern', + 'export-pattern', + 'view-post-revisions', + 'reset-template-part', + 'delete-pattern', + ] ); const actions = useMemo( () => { if ( type === TEMPLATE_PART_POST_TYPE ) { return [ editAction, - renameAction, + renamePatternAction, duplicateTemplatePartAction, viewRevisionsAction, - resetAction, - deleteAction, + resetTemplatePartAction, + deletePatternAction, ]; } return [ - renameAction, + renamePatternAction, duplicatePatternAction, - exportJSONaction, - resetAction, - deleteAction, + exportPatternAsJSONAction, + deletePatternAction, ]; - }, [ type, editAction, viewRevisionsAction ] ); + }, [ + type, + renamePatternAction, + duplicatePatternAction, + exportPatternAsJSONAction, + deletePatternAction, + editAction, + viewRevisionsAction, + resetTemplatePartAction, + ] ); const onChangeView = useCallback( ( newView ) => { if ( newView.type !== view.type ) { diff --git a/packages/editor/package.json b/packages/editor/package.json index 9b4068d2cd12c..5fa18d7b98eab 100644 --- a/packages/editor/package.json +++ b/packages/editor/package.json @@ -64,7 +64,9 @@ "@wordpress/url": "file:../url", "@wordpress/warning": "file:../warning", "@wordpress/wordcount": "file:../wordcount", + "change-case": "^4.1.2", "classnames": "^2.3.1", + "client-zip": "^2.4.4", "date-fns": "^3.6.0", "memize": "^2.1.0", "react-autosize-textarea": "^7.1.0", diff --git a/packages/editor/src/components/post-actions/actions.js b/packages/editor/src/components/post-actions/actions.js index a6ff1f77486c9..96eedff7a7945 100644 --- a/packages/editor/src/components/post-actions/actions.js +++ b/packages/editor/src/components/post-actions/actions.js @@ -1,3 +1,9 @@ +/** + * External dependencies + */ +import { paramCase as kebabCase } from 'change-case'; +import { downloadZip } from 'client-zip'; + /** * WordPress dependencies */ @@ -6,9 +12,11 @@ import { addQueryArgs } from '@wordpress/url'; import { useDispatch } from '@wordpress/data'; import { decodeEntities } from '@wordpress/html-entities'; import { store as coreStore } from '@wordpress/core-data'; -import { __, _n, sprintf } from '@wordpress/i18n'; +import { __, _n, sprintf, _x } from '@wordpress/i18n'; import { store as noticesStore } from '@wordpress/notices'; import { useMemo, useState } from '@wordpress/element'; +import { downloadBlob } from '@wordpress/blob'; +import { store as reusableBlocksStore } from '@wordpress/reusable-blocks'; import { Button, @@ -17,15 +25,31 @@ import { __experimentalHStack as HStack, __experimentalVStack as VStack, } from '@wordpress/components'; +import { privateApis as patternPrivateApis } from '@wordpress/patterns'; /** * Internal dependencies */ -import { TEMPLATE_ORIGINS, TEMPLATE_POST_TYPE } from '../../store/constants'; +import { + TEMPLATE_ORIGINS, + TEMPLATE_POST_TYPE, + TEMPLATE_PART_POST_TYPE, +} from '../../store/constants'; import { store as editorStore } from '../../store'; import { unlock } from '../../lock-unlock'; import isTemplateRevertable from '../../store/utils/is-template-revertable'; +// Patterns. +export const { + PATTERN_TYPES, + PATTERN_DEFAULT_CATEGORY, + PATTERN_USER_CATEGORY, + EXCLUDED_PATTERN_SOURCES, + PATTERN_SYNC_TYPES, + CreatePatternModalContents, + useDuplicatePatternProps, +} = unlock( patternPrivateApis ); + function getItemTitle( item ) { if ( typeof item.title === 'string' ) { return decodeEntities( item.title ); @@ -768,6 +792,348 @@ const renameTemplateAction = { }, }; +function getJsonFromItem( item ) { + return JSON.stringify( + { + __file: item.type, + title: item.title || item.name, + content: item.patternPost.content.raw, + syncStatus: item.patternPost.wp_pattern_sync_status, + }, + null, + 2 + ); +} + +const exportPatternAsJSONAction = { + id: 'export-pattern', + label: __( 'Export as JSON' ), + supportsBulk: true, + isEligible: ( item ) => item.type === PATTERN_TYPES.user, + callback: async ( items, onActionPerformed ) => { + if ( items.length === 1 ) { + return downloadBlob( + `${ kebabCase( items[ 0 ].title || items[ 0 ].name ) }.json`, + getJsonFromItem( items[ 0 ] ), + 'application/json' + ); + } + const nameCount = {}; + const filesToZip = items.map( ( item ) => { + const name = kebabCase( item.title || item.name ); + nameCount[ name ] = ( nameCount[ name ] || 0 ) + 1; + return { + name: `${ + name + + ( nameCount[ name ] > 1 + ? '-' + ( nameCount[ name ] - 1 ) + : '' ) + }.json`, + lastModified: new Date(), + input: getJsonFromItem( item ), + }; + } ); + downloadBlob( + __( 'patterns-export' ) + '.zip', + await downloadZip( filesToZip ).blob(), + 'application/zip' + ); + onActionPerformed?.( items ); + }, +}; + +const renamePatternAction = { + 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, onActionPerformed } ) => { + 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' } + ); + onActionPerformed?.( items ); + } 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; + return isUserPattern || ( isTemplatePart && item.isCustom ); +}; + +const deletePatternAction = { + id: 'delete-pattern', + label: __( 'Delete' ), + isEligible: ( item ) => { + const isTemplatePart = item.type === TEMPLATE_PART_POST_TYPE; + const hasThemeFile = isTemplatePart && item.templatePart.has_theme_file; + return canDeleteOrReset( item ) && ! hasThemeFile; + }, + hideModalHeader: true, + supportsBulk: true, + RenderModal: ( { items, closeModal, onActionPerformed } ) => { + const { __experimentalDeleteReusableBlock } = + useDispatch( reusableBlocksStore ); + const { createErrorNotice, createSuccessNotice } = + useDispatch( noticesStore ); + const { removeTemplates } = unlock( useDispatch( editorStore ) ); + + const deletePattern = async () => { + const promiseResult = await Promise.allSettled( + items.map( ( item ) => { + return __experimentalDeleteReusableBlock( item.id ); + } ) + ); + // If all the promises were fulfilled with success. + if ( + promiseResult.every( ( { status } ) => status === 'fulfilled' ) + ) { + let successMessage; + if ( promiseResult.length === 1 ) { + successMessage = sprintf( + /* translators: The posts's title. */ + __( '"%s" deleted.' ), + items[ 0 ].title + ); + } else { + successMessage = __( 'The patterns were deleted.' ); + } + createSuccessNotice( successMessage, { + type: 'snackbar', + id: 'edit-site-page-trashed', + } ); + } else { + // If there was at lease one failure. + let errorMessage; + // If we were trying to delete a single pattern. + if ( promiseResult.length === 1 ) { + if ( promiseResult[ 0 ].reason?.message ) { + errorMessage = promiseResult[ 0 ].reason.message; + } else { + errorMessage = __( + 'An error occurred while deleting the pattern.' + ); + } + // If we were trying to delete multiple patterns. + } else { + const errorMessages = new Set(); + const failedPromises = promiseResult.filter( + ( { status } ) => status === 'rejected' + ); + for ( const failedPromise of failedPromises ) { + if ( failedPromise.reason?.message ) { + errorMessages.add( failedPromise.reason.message ); + } + } + if ( errorMessages.size === 0 ) { + errorMessage = __( + 'An error occurred while deleting the patterns.' + ); + } else if ( errorMessages.size === 1 ) { + errorMessage = sprintf( + /* translators: %s: an error message */ + __( + 'An error occurred while deleting the patterns: %s' + ), + [ ...errorMessages ][ 0 ] + ); + } else { + errorMessage = sprintf( + /* translators: %s: a list of comma separated error messages */ + __( + 'Some errors occurred while deleting the patterns: %s' + ), + [ ...errorMessages ].join( ',' ) + ); + } + createErrorNotice( errorMessage, { + type: 'snackbar', + } ); + } + } + }; + const deleteItem = () => { + if ( items[ 0 ].type === TEMPLATE_PART_POST_TYPE ) { + removeTemplates( items ); + } else { + deletePattern(); + } + if ( onActionPerformed ) { + onActionPerformed( items ); + } + closeModal(); + }; + let questionMessage; + if ( items.length === 1 ) { + questionMessage = sprintf( + // translators: %s: The page's title. + __( 'Are you sure you want to delete "%s"?' ), + decodeEntities( items[ 0 ].title || items[ 0 ].name ) + ); + } else if ( + items.length > 1 && + items[ 0 ].type === TEMPLATE_PART_POST_TYPE + ) { + questionMessage = sprintf( + // translators: %d: The number of template parts (2 or more). + __( 'Are you sure you want to delete %d template parts?' ), + items.length + ); + } else { + questionMessage = sprintf( + // translators: %d: The number of patterns (2 or more). + __( 'Are you sure you want to delete %d patterns?' ), + items.length + ); + } + return ( + + { questionMessage } + + + + + + ); + }, +}; + +const resetTemplatePartAction = { + id: 'reset-template-part', + label: __( 'Reset' ), + isEligible: ( item ) => { + const isTemplatePart = item.type === TEMPLATE_PART_POST_TYPE; + const hasThemeFile = isTemplatePart && item.templatePart.has_theme_file; + return canDeleteOrReset( item ) && hasThemeFile; + }, + hideModalHeader: true, + RenderModal: ( { items, closeModal } ) => { + const { removeTemplates } = unlock( useDispatch( editorStore ) ); + return ( + + + { __( 'Reset to default and clear all customizations?' ) } + + + + + + + ); + }, +}; + +const duplicatePatternAction = { + id: 'duplicate-pattern', + label: _x( 'Duplicate', 'action label' ), + isEligible: ( item ) => item.type !== TEMPLATE_PART_POST_TYPE, + modalHeader: _x( 'Duplicate pattern', 'action label' ), + RenderModal: ( { items, closeModal, onActionPerformed } ) => { + const [ item ] = items; + const isThemePattern = item.type === PATTERN_TYPES.theme; + function onPatternSuccess( { pattern } ) { + onActionPerformed?.( [ pattern ] ); + closeModal(); + } + const duplicatedProps = useDuplicatePatternProps( { + pattern: isThemePattern ? item : item.patternPost, + onSuccess: onPatternSuccess, + } ); + return ( + + ); + }, +}; + export function usePostActions( onActionPerformed, actionIds = null ) { const permanentlyDeletePostAction = usePermanentlyDeletePostAction(); const restorePostAction = useRestorePostAction(); @@ -777,13 +1143,18 @@ export function usePostActions( onActionPerformed, actionIds = null ) { const defaultActions = [ editPostAction, resetTemplateAction, + resetTemplatePartAction, viewPostAction, + duplicatePatternAction, restorePostAction, + exportPatternAsJSONAction, deleteTemplateAction, + deletePatternAction, permanentlyDeletePostAction, postRevisionsAction, renamePostAction, renameTemplateAction, + renamePatternAction, trashPostAction, ];