diff --git a/package-lock.json b/package-lock.json
index a8dbbedaf11cbf..2f5b912dc53390 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 2e70debcc0edec..a8b12bdd15b61e 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 fe7d2fb5e32d1e..a37ee426709cb7 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, 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();
- }
- 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 724f60ba391034..f8314d65f34ff5 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 c87f49b1f04a64..6b26977fd28b0d 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 194dd338f49e61..49ebc02f6af0a8 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 @@ 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,13 +723,19 @@ 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 {
+ 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,
} );
@@ -710,7 +745,7 @@ const resetTemplateAction = {
template.id
);
}
-
+ }
createSuccessNotice(
items.length > 1
? sprintf(
@@ -721,13 +756,14 @@ const resetTemplateAction = {
: sprintf(
/* translators: The template/part's name. */
__( '"%s" reset.' ),
- decodeEntities( items[ 0 ].title.rendered )
+ decodeEntities( getItemTitle( items[ 0 ] ) )
),
{
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, 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();
+ }
+ 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 );