From 822f0aa65aaed549b6b834e1e80e4a99f0e72d45 Mon Sep 17 00:00:00 2001 From: Jorge Costa Date: Mon, 13 May 2024 10:59:48 +0100 Subject: [PATCH] Revert "Revert "Update: Move pattern actions to the editor package." (#61611)" This reverts commit f87fd2129d5fdaab480e0866cbbe039e46cf49cf. --- package-lock.json | 6 +- packages/edit-site/package.json | 1 - .../dataviews-pattern-actions.js | 247 +-------------- .../src/components/page-patterns/index.js | 18 +- packages/editor/package.json | 2 + .../src/components/post-actions/actions.js | 290 ++++++++++++++++-- 6 files changed, 273 insertions(+), 291 deletions(-) diff --git a/package-lock.json b/package-lock.json index 586c6b3eec938..893097f0ab78d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -54145,7 +54145,6 @@ "@wordpress/widgets": "file:../widgets", "@wordpress/wordcount": "file:../wordcount", "change-case": "^4.1.2", - "client-zip": "^2.4.4", "clsx": "^2.1.1", "colord": "^2.9.2", "fast-deep-equal": "^3.1.3", @@ -54241,6 +54240,8 @@ "@wordpress/url": "file:../url", "@wordpress/warning": "file:../warning", "@wordpress/wordcount": "file:../wordcount", + "change-case": "^4.1.2", + "client-zip": "^2.4.4", "clsx": "^2.1.1", "date-fns": "^3.6.0", "deepmerge": "^4.3.0", @@ -69216,7 +69217,6 @@ "@wordpress/widgets": "file:../widgets", "@wordpress/wordcount": "file:../wordcount", "change-case": "^4.1.2", - "client-zip": "^2.4.4", "clsx": "^2.1.1", "colord": "^2.9.2", "fast-deep-equal": "^3.1.3", @@ -69294,6 +69294,8 @@ "@wordpress/url": "file:../url", "@wordpress/warning": "file:../warning", "@wordpress/wordcount": "file:../wordcount", + "change-case": "^4.1.2", + "client-zip": "^2.4.4", "clsx": "^2.1.1", "date-fns": "^3.6.0", "deepmerge": "^4.3.0", diff --git a/packages/edit-site/package.json b/packages/edit-site/package.json index 1cf07bec6740d..a77fc84a4b3d1 100644 --- a/packages/edit-site/package.json +++ b/packages/edit-site/package.json @@ -69,7 +69,6 @@ "@wordpress/widgets": "file:../widgets", "@wordpress/wordcount": "file:../wordcount", "change-case": "^4.1.2", - "client-zip": "^2.4.4", "clsx": "^2.1.1", "colord": "^2.9.2", "fast-deep-equal": "^3.1.3", 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 afa69e9752c5b..a37ee426709cb 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,25 +1,10 @@ -/** - * External dependencies - */ -import { paramCase as kebabCase } from 'change-case'; -import { downloadZip } from 'client-zip'; - /** * WordPress dependencies */ -import { downloadBlob } from '@wordpress/blob'; import { __, _x, sprintf } from '@wordpress/i18n'; -import { - Button, - __experimentalHStack as HStack, - __experimentalVStack as VStack, - __experimentalText as Text, -} from '@wordpress/components'; + import { useDispatch } from '@wordpress/data'; 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'; @@ -27,7 +12,6 @@ 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, @@ -39,235 +23,6 @@ const { useHistory, useLocation } = 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' - ); - }, -}; - -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' ), diff --git a/packages/edit-site/src/components/page-patterns/index.js b/packages/edit-site/src/components/page-patterns/index.js index 724f60ba39103..f8314d65f34ff 100644 --- a/packages/edit-site/src/components/page-patterns/index.js +++ b/packages/edit-site/src/components/page-patterns/index.js @@ -47,9 +47,6 @@ import { OPERATOR_IS, } from '../../utils/constants'; import { - exportJSONaction, - resetAction, - deleteAction, duplicatePatternAction, duplicateTemplatePartAction, } from './dataviews-pattern-actions'; @@ -383,20 +380,13 @@ export default function DataviewsPatterns() { if ( type === TEMPLATE_PART_POST_TYPE ) { return [ editAction, - ...templatePartActions, duplicateTemplatePartAction, - resetAction, - deleteAction, + ...templatePartActions, ].filter( Boolean ); } - return [ - editAction, - ...patternActions, - duplicatePatternAction, - exportJSONaction, - resetAction, - deleteAction, - ].filter( Boolean ); + return [ editAction, duplicatePatternAction, ...patternActions ].filter( + Boolean + ); }, [ editAction, type, templatePartActions, patternActions ] ); const onChangeView = useCallback( ( newView ) => { diff --git a/packages/editor/package.json b/packages/editor/package.json index 65fa7deae1828..d8a54c778b3ae 100644 --- a/packages/editor/package.json +++ b/packages/editor/package.json @@ -64,6 +64,8 @@ "@wordpress/url": "file:../url", "@wordpress/warning": "file:../warning", "@wordpress/wordcount": "file:../wordcount", + "change-case": "^4.1.2", + "client-zip": "^2.4.4", "clsx": "^2.1.1", "date-fns": "^3.6.0", "deepmerge": "^4.3.0", diff --git a/packages/editor/src/components/post-actions/actions.js b/packages/editor/src/components/post-actions/actions.js index 4656e34dd6728..719ca69ee51fe 100644 --- a/packages/editor/src/components/post-actions/actions.js +++ b/packages/editor/src/components/post-actions/actions.js @@ -1,6 +1,13 @@ +/** + * External dependencies + */ +import { paramCase as kebabCase } from 'change-case'; +import { downloadZip } from 'client-zip'; + /** * WordPress dependencies */ +import { downloadBlob } from '@wordpress/blob'; import { external, trash, backup } from '@wordpress/icons'; import { addQueryArgs } from '@wordpress/url'; import { useDispatch, useSelect } from '@wordpress/data'; @@ -9,6 +16,8 @@ import { store as coreStore } from '@wordpress/core-data'; import { __, _n, sprintf, _x } from '@wordpress/i18n'; import { store as noticesStore } from '@wordpress/notices'; import { useMemo, useState } from '@wordpress/element'; +import { store as reusableBlocksStore } from '@wordpress/reusable-blocks'; +import { privateApis as patternsPrivateApis } from '@wordpress/patterns'; import { Button, @@ -31,6 +40,17 @@ 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( patternsPrivateApis ); + function getItemTitle( item ) { if ( typeof item.title === 'string' ) { return decodeEntities( item.title ); @@ -680,10 +700,19 @@ export const duplicatePostAction = { }, }; +const isTemplatePartRevertable = ( item ) => { + const hasThemeFile = item.templatePart.has_theme_file; + return canDeleteOrReset( item ) && hasThemeFile; +}; + const resetTemplateAction = { id: 'reset-template', label: __( 'Reset' ), - isEligible: isTemplateRevertable, + isEligible: ( item ) => { + return item.type === TEMPLATE_PART_POST_TYPE + ? isTemplatePartRevertable( item ) + : isTemplateRevertable( item ); + }, icon: backup, supportsBulk: true, hideModalHeader: true, @@ -694,40 +723,47 @@ const resetTemplateAction = { onActionPerformed, } ) => { const [ isBusy, setIsBusy ] = useState( false ); - const { revertTemplate } = unlock( useDispatch( editorStore ) ); + const { revertTemplate, removeTemplates } = unlock( + useDispatch( editorStore ) + ); const { saveEditedEntityRecord } = useDispatch( coreStore ); const { createSuccessNotice, createErrorNotice } = useDispatch( noticesStore ); const onConfirm = async () => { try { - for ( const template of items ) { - await revertTemplate( template, { - allowUndo: false, - } ); - await saveEditedEntityRecord( - 'postType', - template.type, - template.id + if ( items[ 0 ].type === TEMPLATE_PART_POST_TYPE ) { + await removeTemplates( items ); + } else { + for ( const template of items ) { + if ( template.type === TEMPLATE_POST_TYPE ) { + await revertTemplate( template, { + allowUndo: false, + } ); + await saveEditedEntityRecord( + 'postType', + template.type, + template.id + ); + } + } + createSuccessNotice( + items.length > 1 + ? sprintf( + /* translators: The number of items. */ + __( '%s items reset.' ), + items.length + ) + : sprintf( + /* translators: The template/part's name. */ + __( '"%s" reset.' ), + decodeEntities( getItemTitle( items[ 0 ] ) ) + ), + { + type: 'snackbar', + id: 'revert-template-action', + } ); } - - createSuccessNotice( - items.length > 1 - ? sprintf( - /* translators: The number of items. */ - __( '%s items reset.' ), - items.length - ) - : sprintf( - /* translators: The template/part's name. */ - __( '"%s" reset.' ), - decodeEntities( items[ 0 ].title.rendered ) - ), - { - type: 'snackbar', - id: 'revert-template-action', - } - ); } catch ( error ) { let fallbackErrorMessage; if ( items[ 0 ].type === TEMPLATE_POST_TYPE ) { @@ -988,6 +1024,202 @@ 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 + ); +} + +export const exportPatternAsJSONAction = { + 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' + ); + }, +}; + +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 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, 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 function usePostActions( postType, onActionPerformed ) { const { postTypeObject } = useSelect( ( select ) => { @@ -1026,6 +1258,8 @@ export function usePostActions( postType, onActionPerformed ) { : false, ! isTemplateOrTemplatePart && renamePostAction, isTemplateOrTemplatePart && renameTemplateAction, + isPattern && exportPatternAsJSONAction, + isPattern && deletePatternAction, ! isTemplateOrTemplatePart && trashPostAction, ].filter( Boolean );