diff --git a/packages/edit-site/src/components/list/table.js b/packages/edit-site/src/components/list/table.js
index 18198cb600dda..65d88ecb45ece 100644
--- a/packages/edit-site/src/components/list/table.js
+++ b/packages/edit-site/src/components/list/table.js
@@ -13,7 +13,7 @@ import { decodeEntities } from '@wordpress/html-entities';
/**
* Internal dependencies
*/
-import TemplateActions from '../template-actions';
+import PatternActions from '../pattern-actions';
import Link from '../routes/link';
import AddedBy from './added-by';
@@ -126,9 +126,8 @@ export default function Table( { templateType } ) {
) : null }
-
|
diff --git a/packages/edit-site/src/components/page-patterns/grid-item.js b/packages/edit-site/src/components/page-patterns/grid-item.js
index b394ef8eb6e76..0f9d3da598868 100644
--- a/packages/edit-site/src/components/page-patterns/grid-item.js
+++ b/packages/edit-site/src/components/page-patterns/grid-item.js
@@ -2,7 +2,6 @@
* External dependencies
*/
import classnames from 'classnames';
-import { paramCase as kebabCase } from 'change-case';
/**
* WordPress dependencies
@@ -13,60 +12,41 @@ import {
} from '@wordpress/block-editor';
import {
Button,
- __experimentalConfirmDialog as ConfirmDialog,
- DropdownMenu,
- MenuGroup,
- MenuItem,
__experimentalHeading as Heading,
__experimentalHStack as HStack,
Tooltip,
Flex,
} from '@wordpress/components';
-import { useDispatch } from '@wordpress/data';
-import { useState, useId, memo } from '@wordpress/element';
-import { __, sprintf } from '@wordpress/i18n';
+import { useId, memo } from '@wordpress/element';
+import { __ } from '@wordpress/i18n';
import {
Icon,
header,
footer,
symbolFilled as uncategorized,
symbol,
- moreVertical,
lockSmall,
} from '@wordpress/icons';
-import { store as noticesStore } from '@wordpress/notices';
-import { store as reusableBlocksStore } from '@wordpress/reusable-blocks';
-import { downloadBlob } from '@wordpress/blob';
/**
* Internal dependencies
*/
-import RenameMenuItem from './rename-menu-item';
-import DuplicateMenuItem from './duplicate-menu-item';
import {
PATTERN_TYPES,
TEMPLATE_PART_POST_TYPE,
PATTERN_SYNC_TYPES,
} from '../../utils/constants';
-import { store as editSiteStore } from '../../store';
import { useLink } from '../routes/link';
import { unlock } from '../../lock-unlock';
+import PatternActions from '../pattern-actions';
const { useGlobalStyle } = unlock( blockEditorPrivateApis );
-
const templatePartIcons = { header, footer, uncategorized };
function GridItem( { categoryId, item, ...props } ) {
const descriptionId = useId();
- const [ isDeleteDialogOpen, setIsDeleteDialogOpen ] = useState( false );
const [ backgroundColor ] = useGlobalStyle( 'color.background' );
- const { removeTemplate } = useDispatch( editSiteStore );
- const { __experimentalDeleteReusableBlock } =
- useDispatch( reusableBlocksStore );
- const { createErrorNotice, createSuccessNotice } =
- useDispatch( noticesStore );
-
const isUserPattern = item.type === PATTERN_TYPES.user;
const isNonUserPattern = item.type === PATTERN_TYPES.theme;
const isTemplatePart = item.type === TEMPLATE_PART_POST_TYPE;
@@ -86,49 +66,9 @@ function GridItem( { categoryId, item, ...props } ) {
'is-inactive': isNonUserPattern,
} );
- const deletePattern = async () => {
- try {
- await __experimentalDeleteReusableBlock( item.id );
- createSuccessNotice(
- sprintf(
- // translators: %s: The pattern's title e.g. 'Call to action'.
- __( '"%s" deleted.' ),
- item.title
- ),
- { type: 'snackbar', id: 'edit-site-patterns-success' }
- );
- } catch ( error ) {
- const errorMessage =
- error.message && error.code !== 'unknown_error'
- ? error.message
- : __( 'An error occurred while deleting the pattern.' );
- createErrorNotice( errorMessage, {
- type: 'snackbar',
- id: 'edit-site-patterns-error',
- } );
- }
- };
- const deleteItem = () =>
- isTemplatePart ? removeTemplate( item ) : deletePattern();
- const exportAsJSON = () => {
- const json = {
- __file: item.type,
- title: item.title || item.name,
- content: item.patternBlock.content.raw,
- syncStatus: item.patternBlock.wp_pattern_sync_status,
- };
-
- return downloadBlob(
- `${ kebabCase( item.title || item.name ) }.json`,
- JSON.stringify( json, null, 2 ),
- 'application/json'
- );
- };
-
// Only custom patterns or custom template parts can be renamed or deleted.
const isCustomPattern =
isUserPattern || ( isTemplatePart && item.isCustom );
- const hasThemeFile = isTemplatePart && item.templatePart.has_theme_file;
const ariaDescriptions = [];
if ( isCustomPattern ) {
@@ -154,15 +94,6 @@ function GridItem( { categoryId, item, ...props } ) {
item.syncStatus === PATTERN_SYNC_TYPES.full ? symbol : undefined;
}
- const confirmButtonText = hasThemeFile ? __( 'Clear' ) : __( 'Delete' );
- const confirmPrompt = hasThemeFile
- ? __( 'Are you sure you want to clear these customizations?' )
- : sprintf(
- // translators: %s: The pattern or template part's title e.g. 'Call to action'.
- __( 'Are you sure you want to delete "%s"?' ),
- item.title || item.name
- );
-
const additionalStyles = ! backgroundColor
? [ { css: 'body { background: #fff; }' } ]
: undefined;
@@ -265,66 +196,11 @@ function GridItem( { categoryId, item, ...props } ) {
) }
-
- { ( { onClose } ) => (
-
- { isCustomPattern && ! hasThemeFile && (
-
- ) }
-
- { item.type === PATTERN_TYPES.user && (
-
- ) }
-
- { isCustomPattern && (
-
- ) }
-
- ) }
-
+
-
- { isDeleteDialogOpen && (
- setIsDeleteDialogOpen( false ) }
- >
- { confirmPrompt }
-
- ) }
);
}
diff --git a/packages/edit-site/src/components/page-patterns/rename-menu-item.js b/packages/edit-site/src/components/page-patterns/rename-menu-item.js
deleted file mode 100644
index c2b3b960fb667..0000000000000
--- a/packages/edit-site/src/components/page-patterns/rename-menu-item.js
+++ /dev/null
@@ -1,132 +0,0 @@
-/**
- * WordPress dependencies
- */
-import {
- Button,
- MenuItem,
- Modal,
- TextControl,
- __experimentalHStack as HStack,
- __experimentalVStack as VStack,
-} from '@wordpress/components';
-import { store as coreStore } from '@wordpress/core-data';
-import { useDispatch } from '@wordpress/data';
-import { useState } from '@wordpress/element';
-import { __ } from '@wordpress/i18n';
-import { store as noticesStore } from '@wordpress/notices';
-
-/**
- * Internal dependencies
- */
-import { TEMPLATE_PART_POST_TYPE } from '../../utils/constants';
-
-export default function RenameMenuItem( { item, onClose } ) {
- const [ title, setTitle ] = useState( () => item.title );
- const [ isModalOpen, setIsModalOpen ] = useState( false );
-
- const { editEntityRecord, saveEditedEntityRecord } =
- useDispatch( coreStore );
- const { createSuccessNotice, createErrorNotice } =
- useDispatch( noticesStore );
-
- if ( item.type === TEMPLATE_PART_POST_TYPE && ! item.isCustom ) {
- return null;
- }
-
- async function onRename( event ) {
- event.preventDefault();
-
- try {
- await editEntityRecord( 'postType', item.type, item.id, { title } );
-
- // Update state before saving rerenders the list.
- setTitle( '' );
- setIsModalOpen( false );
- onClose();
-
- // 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 (
- <>
-
- { isModalOpen && (
- {
- setIsModalOpen( false );
- onClose();
- } }
- overlayClassName="edit-site-list__rename-modal"
- >
-
-
- ) }
- >
- );
-}
diff --git a/packages/edit-site/src/components/page-template-parts/index.js b/packages/edit-site/src/components/page-template-parts/index.js
index 2a8c41e333ce2..d3f573f3dab0e 100644
--- a/packages/edit-site/src/components/page-template-parts/index.js
+++ b/packages/edit-site/src/components/page-template-parts/index.js
@@ -18,7 +18,7 @@ import Page from '../page';
import Table from '../table';
import Link from '../routes/link';
import AddedBy from '../list/added-by';
-import TemplateActions from '../template-actions';
+import PatternActions from '../pattern-actions';
import AddNewTemplatePart from './add-new-template-part';
import { TEMPLATE_PART_POST_TYPE } from '../../utils/constants';
import { unlock } from '../../lock-unlock';
@@ -77,12 +77,7 @@ export default function PageTemplateParts() {
},
{
header: { __( 'Actions' ) },
- cell: ( templatePart ) => (
-
- ),
+ cell: ( templatePart ) => ,
},
];
diff --git a/packages/edit-site/src/components/page-patterns/duplicate-menu-item.js b/packages/edit-site/src/components/pattern-actions/duplicate-menu-item.js
similarity index 91%
rename from packages/edit-site/src/components/page-patterns/duplicate-menu-item.js
rename to packages/edit-site/src/components/pattern-actions/duplicate-menu-item.js
index 118c954a851f3..d44ff3505ac95 100644
--- a/packages/edit-site/src/components/page-patterns/duplicate-menu-item.js
+++ b/packages/edit-site/src/components/pattern-actions/duplicate-menu-item.js
@@ -8,6 +8,7 @@ import { __, sprintf } from '@wordpress/i18n';
import { store as noticesStore } from '@wordpress/notices';
import { privateApis as patternsPrivateApis } from '@wordpress/patterns';
import { privateApis as routerPrivateApis } from '@wordpress/router';
+import { parse } from '@wordpress/blocks';
/**
* Internal dependencies
@@ -28,18 +29,15 @@ export default function DuplicateMenuItem( {
const { createSuccessNotice } = useDispatch( noticesStore );
const [ isModalOpen, setIsModalOpen ] = useState( false );
const history = useHistory();
-
const closeModal = () => setIsModalOpen( false );
-
const isTemplatePart = item.type === TEMPLATE_PART_POST_TYPE;
- const isThemePattern = item.type === PATTERN_TYPES.theme;
async function onTemplatePartSuccess( templatePart ) {
createSuccessNotice(
sprintf(
// translators: %s: The new template part's title e.g. 'Call to action (copy)'.
__( '"%s" duplicated.' ),
- item.title
+ item.title.rendered
),
{
type: 'snackbar',
@@ -81,19 +79,21 @@ export default function DuplicateMenuItem( {
) }
{ isModalOpen && isTemplatePart && (
{
+ if ( item?.type && item?.id ) {
+ return select( coreStore ).getEntityRecord(
+ 'postType',
+ item?.type,
+ item?.id
+ );
+ }
+ return null;
+ },
+ [ item?.type, item?.id ]
+ );
+ const isBlockBasedTheme = useSelect( ( select ) => {
+ return select( coreStore ).getCurrentTheme()?.is_block_theme;
+ }, [] );
+ const { removeTemplate, revertTemplate } = useDispatch( editSiteStore );
+ const { saveEditedEntityRecord } = useDispatch( coreStore );
+ const { createSuccessNotice, createErrorNotice } =
+ useDispatch( noticesStore );
+ const { __experimentalDeleteReusableBlock } =
+ useDispatch( reusableBlocksStore );
+
+ // Don't show pattern actions for non-block based or hybrid themes.
+ if ( ! isBlockBasedTheme ) {
+ return null;
+ }
+
+ // Only custom patterns or custom template parts can be renamed or deleted.
+ const isUserPattern = record?.type === PATTERN_TYPES.user;
+ const isNonUserPattern = item?.type === PATTERN_TYPES.theme;
+ const isTemplate = record?.type === TEMPLATE_POST_TYPE;
+ const isTemplatePart = record?.type === TEMPLATE_PART_POST_TYPE;
+
+ // Type check to make sure we're dealing with a pattern or template.
+ if (
+ ! isTemplatePart &&
+ ! isTemplate &&
+ ! isUserPattern &&
+ ! isNonUserPattern
+ ) {
+ return null;
+ }
+
+ const isRemovable = isTemplateRemovable( record );
+ const isRevertable = isTemplateRevertable( record );
+ // Only patterns and template parts can be duplicated for now.
+ const isDuplicable = isUserPattern || isNonUserPattern || isTemplatePart;
+
+ // If the pattern is not editable or duplicable, don't show the menu.
+ if ( ! isRemovable && ! isRevertable && ! isDuplicable ) {
+ return null;
+ }
+
+ const isEditable = isUserPattern || isRemovable;
+ const decodedTitle = decodeEntities(
+ record?.title?.rendered || record?.title?.raw
+ );
+
+ const exportAsJSON = ( pattern ) => {
+ const json = {
+ __file: pattern.type,
+ title: pattern?.title?.raw || pattern.slug,
+ content: pattern.content.raw,
+ syncStatus: pattern.wp_pattern_sync_status,
+ };
+
+ return downloadBlob(
+ `${ kebabCase( json.title ) }.json`,
+ JSON.stringify( json, null, 2 ),
+ 'application/json'
+ );
+ };
+
+ const deletePattern = async ( pattern ) => {
+ try {
+ await __experimentalDeleteReusableBlock( pattern.id );
+ createSuccessNotice(
+ sprintf(
+ // translators: %s: The pattern's title e.g. 'Call to action'.
+ __( '"%s" deleted.' ),
+ decodedTitle
+ ),
+ { type: 'snackbar', id: 'edit-site-patterns-success' }
+ );
+ } catch ( error ) {
+ const errorMessage =
+ error.message && error.code !== 'unknown_error'
+ ? error.message
+ : __( 'An error occurred while deleting the pattern.' );
+ createErrorNotice( errorMessage, {
+ type: 'snackbar',
+ id: 'edit-site-patterns-error',
+ } );
+ }
+ };
+
+ const deleteItem = async () => {
+ if ( isTemplateRemovable( record ) ) {
+ removeTemplate( record );
+ } else if ( isUserPattern ) {
+ deletePattern( record );
+ }
+ };
+
+ async function revertAndSaveTemplate() {
+ try {
+ await revertTemplate( record, { allowUndo: false } );
+ await saveEditedEntityRecord( 'postType', record.type, record.id );
+
+ createSuccessNotice(
+ sprintf(
+ // translators: %s: the pattern, template/part's title.
+ __( '"%s" reverted.' ),
+ decodedTitle
+ ),
+ {
+ type: 'snackbar',
+ id: 'edit-site-template-reverted',
+ }
+ );
+ } catch ( error ) {
+ const fallbackErrorMessage = sprintf(
+ // translators: %s: a post type label, e.g., Template, Template Part or Pattern.
+ __( 'An error occurred while reverting the %s.' ),
+ POST_TYPE_LABELS[ record?.type ] ??
+ POST_TYPE_LABELS[ TEMPLATE_POST_TYPE ]
+ );
+ const errorMessage =
+ error.message && error.code !== 'unknown_error'
+ ? error.message
+ : fallbackErrorMessage;
+
+ createErrorNotice( errorMessage, { type: 'snackbar' } );
+ }
+ }
+
+ const { categoryId } = getQueryArgs( window.location.href );
+
+ return (
+
+ { ( { onClose } ) => (
+
+ { isEditable && (
+
+ ) }
+ { isDuplicable && (
+
+ ) }
+ { isUserPattern && (
+
+ ) }
+ { isEditable && (
+ {
+ deleteItem();
+ onRemove?.( record );
+ onClose();
+ } }
+ title={ decodedTitle }
+ />
+ ) }
+ { isRevertable && (
+ {
+ revertAndSaveTemplate();
+ onClose();
+ } }
+ />
+ ) }
+
+ ) }
+
+ );
+}
+
+function DeleteMenuItem( { onRemove, title } ) {
+ const [ isModalOpen, setIsModalOpen ] = useState( false );
+ return (
+ <>
+
+ setIsModalOpen( false ) }
+ confirmButtonText={ __( 'Delete' ) }
+ >
+ { sprintf(
+ // translators: %s: The template or template part's title.
+ __( 'Are you sure you want to delete "%s"?' ),
+ decodeEntities( title )
+ ) }
+
+ >
+ );
+}
+
+function RevertMenuItem( { onRemove } ) {
+ const [ isModalOpen, setIsModalOpen ] = useState( false );
+ return (
+ <>
+
+ setIsModalOpen( false ) }
+ confirmButtonText={ __( 'Clear' ) }
+ >
+ { __( 'Are you sure you want to clear these customizations?' ) }
+
+ >
+ );
+}
diff --git a/packages/edit-site/src/components/template-actions/rename-menu-item.js b/packages/edit-site/src/components/pattern-actions/rename-menu-item.js
similarity index 60%
rename from packages/edit-site/src/components/template-actions/rename-menu-item.js
rename to packages/edit-site/src/components/pattern-actions/rename-menu-item.js
index 730bdba803ab5..db4bfd2be4b94 100644
--- a/packages/edit-site/src/components/template-actions/rename-menu-item.js
+++ b/packages/edit-site/src/components/pattern-actions/rename-menu-item.js
@@ -1,9 +1,9 @@
/**
* WordPress dependencies
*/
-import { __ } from '@wordpress/i18n';
+import { __, sprintf } from '@wordpress/i18n';
import { useState } from '@wordpress/element';
-import { useDispatch } from '@wordpress/data';
+import { useDispatch, useSelect } from '@wordpress/data';
import {
Button,
MenuItem,
@@ -19,13 +19,22 @@ import { decodeEntities } from '@wordpress/html-entities';
/**
* Internal dependencies
*/
-import { TEMPLATE_POST_TYPE } from '../../utils/constants';
+import {
+ TEMPLATE_POST_TYPE,
+ TEMPLATE_PART_POST_TYPE,
+ PATTERN_TYPES,
+ TEMPLATE_ORIGINS,
+ POST_TYPE_LABELS,
+} from '../../utils/constants';
-export default function RenameMenuItem( { template, onClose } ) {
- const title = decodeEntities( template.title.rendered );
- const [ editedTitle, setEditedTitle ] = useState( title );
+export default function RenameMenuItem( { postType, postId, onClose } ) {
+ const record = useSelect(
+ ( select ) =>
+ select( coreStore ).getEntityRecord( 'postType', postType, postId ),
+ [ postType, postId ]
+ );
+ const [ editedTitle, setEditedTitle ] = useState( '' );
const [ isModalOpen, setIsModalOpen ] = useState( false );
-
const {
editEntityRecord,
__experimentalSaveSpecifiedEntityEdits: saveSpecifiedEntityEdits,
@@ -33,15 +42,25 @@ export default function RenameMenuItem( { template, onClose } ) {
const { createSuccessNotice, createErrorNotice } =
useDispatch( noticesStore );
- if ( template.type === TEMPLATE_POST_TYPE && ! template.is_custom ) {
+ const isTemplate = record?.type === TEMPLATE_POST_TYPE;
+ const isTemplatePart = record?.type === TEMPLATE_PART_POST_TYPE;
+ const isUserPattern = record?.type === PATTERN_TYPES.user;
+
+ if (
+ ( isTemplate || isTemplatePart ) &&
+ record?.source !== TEMPLATE_ORIGINS.custom &&
+ ! isUserPattern
+ ) {
return null;
}
- async function onTemplateRename( event ) {
+ async function onRename( event ) {
event.preventDefault();
+ const postTypeLabel =
+ POST_TYPE_LABELS[ postType ] ?? POST_TYPE_LABELS.wp_template;
try {
- await editEntityRecord( 'postType', template.type, template.id, {
+ await editEntityRecord( 'postType', record.type, record.id, {
title: editedTitle,
} );
@@ -53,8 +72,8 @@ export default function RenameMenuItem( { template, onClose } ) {
// Persist edited entity.
await saveSpecifiedEntityEdits(
'postType',
- template.type,
- template.id,
+ postType,
+ postId,
[ 'title' ], // Only save title to avoid persisting other edits.
{
throwOnError: true,
@@ -62,20 +81,22 @@ export default function RenameMenuItem( { template, onClose } ) {
);
createSuccessNotice(
- template.type === TEMPLATE_POST_TYPE
- ? __( 'Template renamed.' )
- : __( 'Template part renamed.' ),
+ sprintf(
+ // translators: %s is a post type label, e.g., Template, Template Part or Pattern.
+ __( '%s renamed.' ),
+ postTypeLabel
+ ),
{
type: 'snackbar',
+ id: 'template-rename-success',
}
);
} catch ( error ) {
- const fallbackErrorMessage =
- template.type === TEMPLATE_POST_TYPE
- ? __( 'An error occurred while renaming the template.' )
- : __(
- 'An error occurred while renaming the template part.'
- );
+ const fallbackErrorMessage = sprintf(
+ // translators: %s is a post type label, e.g., Template, Template Part or Pattern.
+ __( 'An error occurred while renaming the %s.' ),
+ postTypeLabel
+ );
const errorMessage =
error.message && error.code !== 'unknown_error'
? error.message
@@ -90,7 +111,11 @@ export default function RenameMenuItem( { template, onClose } ) {