diff --git a/package-lock.json b/package-lock.json
index 893097f0ab78d2..586c6b3eec9380 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -54145,6 +54145,7 @@
"@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",
@@ -54240,8 +54241,6 @@
"@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",
@@ -69217,6 +69216,7 @@
"@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,8 +69294,6 @@
"@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 a77fc84a4b3d19..1cf07bec6740df 100644
--- a/packages/edit-site/package.json
+++ b/packages/edit-site/package.json
@@ -69,6 +69,7 @@
"@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 a37ee426709cb7..afa69e9752c5b4 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,10 +1,25 @@
+/**
+ * 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';
@@ -12,6 +27,7 @@ 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,
@@ -23,6 +39,235 @@ 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 f8314d65f34ff5..724f60ba391034 100644
--- a/packages/edit-site/src/components/page-patterns/index.js
+++ b/packages/edit-site/src/components/page-patterns/index.js
@@ -47,6 +47,9 @@ import {
OPERATOR_IS,
} from '../../utils/constants';
import {
+ exportJSONaction,
+ resetAction,
+ deleteAction,
duplicatePatternAction,
duplicateTemplatePartAction,
} from './dataviews-pattern-actions';
@@ -380,13 +383,20 @@ export default function DataviewsPatterns() {
if ( type === TEMPLATE_PART_POST_TYPE ) {
return [
editAction,
- duplicateTemplatePartAction,
...templatePartActions,
+ duplicateTemplatePartAction,
+ resetAction,
+ deleteAction,
].filter( Boolean );
}
- return [ editAction, duplicatePatternAction, ...patternActions ].filter(
- Boolean
- );
+ return [
+ editAction,
+ ...patternActions,
+ duplicatePatternAction,
+ exportJSONaction,
+ resetAction,
+ deleteAction,
+ ].filter( Boolean );
}, [ editAction, type, templatePartActions, patternActions ] );
const onChangeView = useCallback(
( newView ) => {
diff --git a/packages/editor/package.json b/packages/editor/package.json
index d8a54c778b3ae3..65fa7deae1828c 100644
--- a/packages/editor/package.json
+++ b/packages/editor/package.json
@@ -64,8 +64,6 @@
"@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 719ca69ee51fea..4656e34dd67289 100644
--- a/packages/editor/src/components/post-actions/actions.js
+++ b/packages/editor/src/components/post-actions/actions.js
@@ -1,13 +1,6 @@
-/**
- * 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';
@@ -16,8 +9,6 @@ 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,
@@ -40,17 +31,6 @@ 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 );
@@ -700,19 +680,10 @@ 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: ( item ) => {
- return item.type === TEMPLATE_PART_POST_TYPE
- ? isTemplatePartRevertable( item )
- : isTemplateRevertable( item );
- },
+ isEligible: isTemplateRevertable,
icon: backup,
supportsBulk: true,
hideModalHeader: true,
@@ -723,47 +694,40 @@ const resetTemplateAction = {
onActionPerformed,
} ) => {
const [ isBusy, setIsBusy ] = useState( false );
- const { revertTemplate, removeTemplates } = unlock(
- useDispatch( editorStore )
- );
+ const { revertTemplate } = 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,
- } );
- 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',
- }
+ for ( const template of items ) {
+ 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( items[ 0 ].title.rendered )
+ ),
+ {
+ type: 'snackbar',
+ id: 'revert-template-action',
+ }
+ );
} catch ( error ) {
let fallbackErrorMessage;
if ( items[ 0 ].type === TEMPLATE_POST_TYPE ) {
@@ -1024,202 +988,6 @@ 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 ) => {
@@ -1258,8 +1026,6 @@ export function usePostActions( postType, onActionPerformed ) {
: false,
! isTemplateOrTemplatePart && renamePostAction,
isTemplateOrTemplatePart && renameTemplateAction,
- isPattern && exportPatternAsJSONAction,
- isPattern && deletePatternAction,
! isTemplateOrTemplatePart && trashPostAction,
].filter( Boolean );