From e9d6403919a3e10c1db55490c9969670fac3a275 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Tue, 4 Jul 2023 14:10:10 +1000 Subject: [PATCH 01/18] Add renaming and deletion of custom patterns and template parts --- .../src/components/page-patterns/grid-item.js | 22 +++- .../page-patterns/rename-menu-item.js | 115 ++++++++++++++++++ .../components/page-patterns/use-patterns.js | 2 + 3 files changed, 135 insertions(+), 4 deletions(-) create mode 100644 packages/edit-site/src/components/page-patterns/rename-menu-item.js 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 8795e41eedd4f..dd2eae6b1f28d 100644 --- a/packages/edit-site/src/components/page-patterns/grid-item.js +++ b/packages/edit-site/src/components/page-patterns/grid-item.js @@ -35,7 +35,9 @@ import { DELETE, BACKSPACE } from '@wordpress/keycodes'; /** * Internal dependencies */ -import { PATTERNS, USER_PATTERNS } from './utils'; +import RenameMenuItem from './rename-menu-item'; +import { PATTERNS, TEMPLATE_PARTS, USER_PATTERNS } from './utils'; +import { store as editSiteStore } from '../../store'; import { useLink } from '../routes/link'; const THEME_PATTERN_TOOLTIP = __( 'Theme patterns cannot be edited.' ); @@ -44,6 +46,7 @@ export default function GridItem( { categoryId, composite, icon, item } ) { const descriptionId = useId(); const [ isDeleteDialogOpen, setIsDeleteDialogOpen ] = useState( false ); + const { removeTemplate } = useDispatch( editSiteStore ); const { __experimentalDeleteReusableBlock } = useDispatch( reusableBlocksStore ); const { createErrorNotice, createSuccessNotice } = @@ -85,6 +88,9 @@ export default function GridItem( { categoryId, composite, icon, item } ) { } }; + const deleteItem = () => + item.type === TEMPLATE_PARTS ? removeTemplate( item ) : deletePattern(); + const isUserPattern = item.type === USER_PATTERNS; const ariaDescriptions = []; if ( isUserPattern ) { @@ -108,6 +114,10 @@ export default function GridItem( { categoryId, composite, icon, item } ) { itemIcon = symbolFilled; } + const isCustomPattern = + item.type === USER_PATTERNS || + ( item.type === TEMPLATE_PARTS && item.isCustom ); + return ( <>
@@ -179,7 +189,7 @@ export default function GridItem( { categoryId, composite, icon, item } ) { ) } - { item.type === USER_PATTERNS && ( + { isCustomPattern && ( - { () => ( + { ( { onClose } ) => ( + setIsDeleteDialogOpen( true ) @@ -217,7 +231,7 @@ export default function GridItem( { categoryId, composite, icon, item } ) {
{ isDeleteDialogOpen && ( setIsDeleteDialogOpen( false ) } > { __( 'Are you sure you want to delete this pattern?' ) } 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 new file mode 100644 index 0000000000000..938023a62cefd --- /dev/null +++ b/packages/edit-site/src/components/page-patterns/rename-menu-item.js @@ -0,0 +1,115 @@ +/** + * 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_PARTS } from './utils'; + +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_PARTS && ! 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( __( 'Entity renamed.' ), { + type: 'snackbar', + } ); + } catch ( error ) { + const errorMessage = + error.message && error.code !== 'unknown_error' + ? error.message + : __( 'An error occurred while renaming the entity.' ); + + createErrorNotice( errorMessage, { type: 'snackbar' } ); + } + } + + return ( + <> + { + setIsModalOpen( true ); + setTitle( item.title ); + } } + > + { __( 'Rename' ) } + + { isModalOpen && ( + { + setIsModalOpen( false ); + onClose(); + } } + overlayClassName="edit-site-list__rename_modal" + > +
+ + + + + + + + + +
+
+ ) } + + ); +} diff --git a/packages/edit-site/src/components/page-patterns/use-patterns.js b/packages/edit-site/src/components/page-patterns/use-patterns.js index a394aabf572c4..295d1eee8e410 100644 --- a/packages/edit-site/src/components/page-patterns/use-patterns.js +++ b/packages/edit-site/src/components/page-patterns/use-patterns.js @@ -31,7 +31,9 @@ const templatePartToPattern = ( templatePart ) => ( { blocks: parse( templatePart.content.raw ), categories: [ templatePart.area ], description: templatePart.description || '', + isCustom: templatePart.source === 'custom', keywords: templatePart.keywords || [], + id: createTemplatePartId( templatePart.theme, templatePart.slug ), name: createTemplatePartId( templatePart.theme, templatePart.slug ), title: templatePart.title.rendered, type: templatePart.type, From 35a429d38539026050e7722aba7de71969c5f18b Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Tue, 4 Jul 2023 14:59:11 +1000 Subject: [PATCH 02/18] Add duplication for custom patterns and template parts --- .../components/create-pattern-modal/index.js | 7 +- .../page-patterns/duplicate-menu-item.js | 77 +++++++++++++++++++ .../src/components/page-patterns/grid-item.js | 5 ++ 3 files changed, 87 insertions(+), 2 deletions(-) create mode 100644 packages/edit-site/src/components/page-patterns/duplicate-menu-item.js diff --git a/packages/edit-site/src/components/create-pattern-modal/index.js b/packages/edit-site/src/components/create-pattern-modal/index.js index 46d734b86fdd1..753dccfb961dd 100644 --- a/packages/edit-site/src/components/create-pattern-modal/index.js +++ b/packages/edit-site/src/components/create-pattern-modal/index.js @@ -14,6 +14,7 @@ import { __ } from '@wordpress/i18n'; import { useState } from '@wordpress/element'; import { store as noticesStore } from '@wordpress/notices'; import { useDispatch } from '@wordpress/data'; +import { serialize } from '@wordpress/blocks'; /** * Internal dependencies @@ -21,9 +22,11 @@ import { useDispatch } from '@wordpress/data'; import { SYNC_TYPES, USER_PATTERN_CATEGORY } from '../page-patterns/utils'; export default function CreatePatternModal( { + blocks = [], closeModal, onCreate, onError, + title, } ) { const [ name, setName ] = useState( '' ); const [ syncType, setSyncType ] = useState( SYNC_TYPES.unsynced ); @@ -52,7 +55,7 @@ export default function CreatePatternModal( { 'wp_block', { title: name || __( 'Untitled Pattern' ), - content: '', + content: blocks?.length ? serialize( blocks ) : '', status: 'publish', meta: syncType === SYNC_TYPES.unsynced @@ -76,7 +79,7 @@ export default function CreatePatternModal( { return ( diff --git a/packages/edit-site/src/components/page-patterns/duplicate-menu-item.js b/packages/edit-site/src/components/page-patterns/duplicate-menu-item.js new file mode 100644 index 0000000000000..c38ad609df77e --- /dev/null +++ b/packages/edit-site/src/components/page-patterns/duplicate-menu-item.js @@ -0,0 +1,77 @@ +/** + * WordPress dependencies + */ +import { MenuItem } from '@wordpress/components'; +import { useState } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; +import { privateApis as routerPrivateApis } from '@wordpress/router'; + +/** + * Internal dependencies + */ +import CreatePatternModal from '../create-pattern-modal'; +import CreateTemplatePartModal from '../create-template-part-modal'; +import { TEMPLATE_PARTS, USER_PATTERNS } from './utils'; +import { unlock } from '../../lock-unlock'; + +const { useHistory } = unlock( routerPrivateApis ); + +export default function DuplicateMenuItem( { item, onClose } ) { + const history = useHistory(); + const [ isModalOpen, setIsModalOpen ] = useState( false ); + + function handleCreatePattern( { pattern, categoryId } ) { + setIsModalOpen( false ); + onClose(); + + history.push( { + postId: pattern.id, + postType: 'wp_block', + categoryType: 'wp_block', + categoryId, + canvas: 'edit', + } ); + } + + function handleCreateTemplatePart( templatePart ) { + setIsModalOpen( false ); + onClose(); + + // Navigate to the created template part editor. + history.push( { + postId: templatePart.id, + postType: 'wp_template_part', + canvas: 'edit', + } ); + } + + function handleError() { + setIsModalOpen( false ); + onClose(); + } + + return ( + <> + setIsModalOpen( true ) }> + { __( 'Duplicate' ) } + + { isModalOpen && item.type === USER_PATTERNS && ( + setIsModalOpen( false ) } + onCreate={ handleCreatePattern } + onError={ handleError } + blocks={ item.blocks || [] } + title={ __( 'Duplicate pattern' ) } + /> + ) } + { isModalOpen && item.type === TEMPLATE_PARTS && ( + setIsModalOpen( false ) } + blocks={ item.blocks || [] } + onCreate={ handleCreateTemplatePart } + onError={ handleError } + /> + ) } + + ); +} 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 dd2eae6b1f28d..25a7a6c7ed3d9 100644 --- a/packages/edit-site/src/components/page-patterns/grid-item.js +++ b/packages/edit-site/src/components/page-patterns/grid-item.js @@ -36,6 +36,7 @@ import { DELETE, BACKSPACE } from '@wordpress/keycodes'; * Internal dependencies */ import RenameMenuItem from './rename-menu-item'; +import DuplicateMenuItem from './duplicate-menu-item'; import { PATTERNS, TEMPLATE_PARTS, USER_PATTERNS } from './utils'; import { store as editSiteStore } from '../../store'; import { useLink } from '../routes/link'; @@ -216,6 +217,10 @@ export default function GridItem( { categoryId, composite, icon, item } ) { item={ item } onClose={ onClose } /> + setIsDeleteDialogOpen( true ) From c6f654b12b7fadc9943f7956b3b20889cc9e2f7b Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Tue, 4 Jul 2023 15:13:14 +1000 Subject: [PATCH 03/18] Allow duplication of theme patterns and non-custom template parts --- .../page-patterns/duplicate-menu-item.js | 4 +- .../src/components/page-patterns/grid-item.js | 65 ++++++++++--------- 2 files changed, 36 insertions(+), 33 deletions(-) diff --git a/packages/edit-site/src/components/page-patterns/duplicate-menu-item.js b/packages/edit-site/src/components/page-patterns/duplicate-menu-item.js index c38ad609df77e..882838a5a8f08 100644 --- a/packages/edit-site/src/components/page-patterns/duplicate-menu-item.js +++ b/packages/edit-site/src/components/page-patterns/duplicate-menu-item.js @@ -11,7 +11,7 @@ import { privateApis as routerPrivateApis } from '@wordpress/router'; */ import CreatePatternModal from '../create-pattern-modal'; import CreateTemplatePartModal from '../create-template-part-modal'; -import { TEMPLATE_PARTS, USER_PATTERNS } from './utils'; +import { TEMPLATE_PARTS } from './utils'; import { unlock } from '../../lock-unlock'; const { useHistory } = unlock( routerPrivateApis ); @@ -55,7 +55,7 @@ export default function DuplicateMenuItem( { item, onClose } ) { setIsModalOpen( true ) }> { __( 'Duplicate' ) } - { isModalOpen && item.type === USER_PATTERNS && ( + { isModalOpen && item.type !== TEMPLATE_PARTS && ( setIsModalOpen( false ) } onCreate={ handleCreatePattern } 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 25a7a6c7ed3d9..d17af49482fd9 100644 --- a/packages/edit-site/src/components/page-patterns/grid-item.js +++ b/packages/edit-site/src/components/page-patterns/grid-item.js @@ -115,6 +115,7 @@ export default function GridItem( { categoryId, composite, icon, item } ) { itemIcon = symbolFilled; } + // Only custom patterns or custom template parts can be renamed or deleted. const isCustomPattern = item.type === USER_PATTERNS || ( item.type === TEMPLATE_PARTS && item.isCustom ); @@ -190,37 +191,39 @@ export default function GridItem( { categoryId, composite, icon, item } ) { ) } - { isCustomPattern && ( - - { ( { onClose } ) => ( - + + { ( { onClose } ) => ( + + { isCustomPattern && ( - + ) } + + { isCustomPattern && ( setIsDeleteDialogOpen( true ) @@ -228,10 +231,10 @@ export default function GridItem( { categoryId, composite, icon, item } ) { > { __( 'Delete' ) } - - ) } - - ) } + ) } + + ) } + { isDeleteDialogOpen && ( From 653bd7ea6643b4f2912da3ecb6316ca9fdb95610 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Tue, 4 Jul 2023 15:23:19 +1000 Subject: [PATCH 04/18] Remove inline styles for pattern icon --- .../edit-site/src/components/page-patterns/grid-item.js | 6 +----- packages/edit-site/src/components/page-patterns/style.scss | 4 ++++ 2 files changed, 5 insertions(+), 5 deletions(-) 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 d17af49482fd9..738a96c12036e 100644 --- a/packages/edit-site/src/components/page-patterns/grid-item.js +++ b/packages/edit-site/src/components/page-patterns/grid-item.js @@ -181,11 +181,7 @@ export default function GridItem( { categoryId, composite, icon, item } ) { ) } > - + ) } diff --git a/packages/edit-site/src/components/page-patterns/style.scss b/packages/edit-site/src/components/page-patterns/style.scss index 9326a96612319..7a7bf026b9c62 100644 --- a/packages/edit-site/src/components/page-patterns/style.scss +++ b/packages/edit-site/src/components/page-patterns/style.scss @@ -101,6 +101,10 @@ .edit-site-patterns__pattern-lock-icon { display: inline-flex; + + svg { + fill: currentcolor; + } } } From 6e5a30c39ff5b33199d826f4342984000e4486c9 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Tue, 4 Jul 2023 18:58:12 +1000 Subject: [PATCH 05/18] A little code clean up As per feedback: https://github.com/WordPress/gutenberg/pull/52263#issuecomment-1619778107 --- .../src/components/page-patterns/grid-item.js | 36 +++++++++---------- 1 file changed, 17 insertions(+), 19 deletions(-) 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 738a96c12036e..b98399664824c 100644 --- a/packages/edit-site/src/components/page-patterns/grid-item.js +++ b/packages/edit-site/src/components/page-patterns/grid-item.js @@ -24,7 +24,7 @@ import { Icon, header, footer, - symbolFilled, + symbolFilled as uncategorized, moreHorizontal, lockSmall, } from '@wordpress/icons'; @@ -41,7 +41,7 @@ import { PATTERNS, TEMPLATE_PARTS, USER_PATTERNS } from './utils'; import { store as editSiteStore } from '../../store'; import { useLink } from '../routes/link'; -const THEME_PATTERN_TOOLTIP = __( 'Theme patterns cannot be edited.' ); +const templatePartIcons = { header, footer, uncategorized }; export default function GridItem( { categoryId, composite, icon, item } ) { const descriptionId = useId(); @@ -53,9 +53,13 @@ export default function GridItem( { categoryId, composite, icon, item } ) { const { createErrorNotice, createSuccessNotice } = useDispatch( noticesStore ); + const isUserPattern = item.type === USER_PATTERNS; + const isNonUserPattern = item.type === PATTERNS; + const isTemplatePart = item.type === TEMPLATE_PARTS; + const { onClick } = useLink( { postType: item.type, - postId: item.type === USER_PATTERNS ? item.id : item.name, + postId: isUserPattern ? item.id : item.name, categoryId, categoryType: item.type, } ); @@ -71,7 +75,7 @@ export default function GridItem( { categoryId, composite, icon, item } ) { 'is-placeholder': isEmpty, } ); const previewClassNames = classnames( 'edit-site-patterns__preview', { - 'is-inactive': item.type === PATTERNS, + 'is-inactive': isNonUserPattern, } ); const deletePattern = async () => { @@ -88,12 +92,11 @@ export default function GridItem( { categoryId, composite, icon, item } ) { createErrorNotice( errorMessage, { type: 'snackbar' } ); } }; - const deleteItem = () => - item.type === TEMPLATE_PARTS ? removeTemplate( item ) : deletePattern(); + isTemplatePart ? removeTemplate( item ) : deletePattern(); - const isUserPattern = item.type === USER_PATTERNS; const ariaDescriptions = []; + if ( isUserPattern ) { // User patterns don't have descriptions, but can be edited and deleted, so include some help text. ariaDescriptions.push( @@ -102,23 +105,18 @@ export default function GridItem( { categoryId, composite, icon, item } ) { } else if ( item.description ) { ariaDescriptions.push( item.description ); } - if ( item.type === PATTERNS ) { - ariaDescriptions.push( THEME_PATTERN_TOOLTIP ); - } - let itemIcon = icon; - if ( categoryId === 'header' ) { - itemIcon = header; - } else if ( categoryId === 'footer' ) { - itemIcon = footer; - } else if ( categoryId === 'uncategorized' ) { - itemIcon = symbolFilled; + if ( isNonUserPattern ) { + ariaDescriptions.push( __( 'Theme patterns cannot be edited.' ) ); } + const itemIcon = templatePartIcons[ categoryId ] + ? templatePartIcons[ categoryId ] + : icon; + // Only custom patterns or custom template parts can be renamed or deleted. const isCustomPattern = - item.type === USER_PATTERNS || - ( item.type === TEMPLATE_PARTS && item.isCustom ); + isUserPattern || ( isTemplatePart && item.isCustom ); return ( <> From 533d774cb6a11051092b3e896af4a5f098ddf2b6 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Wed, 5 Jul 2023 16:30:17 +1000 Subject: [PATCH 06/18] Fix keyboard deletion of customized template parts --- .../src/components/page-patterns/grid-item.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) 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 b98399664824c..9a542ab917d53 100644 --- a/packages/edit-site/src/components/page-patterns/grid-item.js +++ b/packages/edit-site/src/components/page-patterns/grid-item.js @@ -95,9 +95,12 @@ export default function GridItem( { categoryId, composite, icon, item } ) { const deleteItem = () => isTemplatePart ? removeTemplate( item ) : deletePattern(); + // Only custom patterns or custom template parts can be renamed or deleted. + const isCustomPattern = + isUserPattern || ( isTemplatePart && item.isCustom ); const ariaDescriptions = []; - if ( isUserPattern ) { + if ( isCustomPattern ) { // User patterns don't have descriptions, but can be edited and deleted, so include some help text. ariaDescriptions.push( __( 'Press Enter to edit, or Delete to delete the pattern.' ) @@ -114,10 +117,6 @@ export default function GridItem( { categoryId, composite, icon, item } ) { ? templatePartIcons[ categoryId ] : icon; - // Only custom patterns or custom template parts can be renamed or deleted. - const isCustomPattern = - isUserPattern || ( isTemplatePart && item.isCustom ); - return ( <>
@@ -127,7 +126,7 @@ export default function GridItem( { categoryId, composite, icon, item } ) { as="div" { ...composite } onClick={ item.type !== PATTERNS ? onClick : undefined } - onKeyDown={ isUserPattern ? onKeyDown : undefined } + onKeyDown={ isCustomPattern ? onKeyDown : undefined } aria-label={ item.title } aria-describedby={ ariaDescriptions.length From 55df947fb49e3bd7ae3460e295483282db9b1520 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Wed, 5 Jul 2023 16:30:48 +1000 Subject: [PATCH 07/18] Rename delete confirmation button to match other flows --- packages/edit-site/src/components/page-patterns/grid-item.js | 1 + 1 file changed, 1 insertion(+) 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 9a542ab917d53..2a131c7e15a45 100644 --- a/packages/edit-site/src/components/page-patterns/grid-item.js +++ b/packages/edit-site/src/components/page-patterns/grid-item.js @@ -232,6 +232,7 @@ export default function GridItem( { categoryId, composite, icon, item } ) {
{ isDeleteDialogOpen && ( setIsDeleteDialogOpen( false ) } > From 1ea6fc389fec5408b2c22b3d9c32be98618bf544 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Wed, 5 Jul 2023 16:36:36 +1000 Subject: [PATCH 08/18] Make theme pattern duplicate label more reflective of action --- .../src/components/page-patterns/duplicate-menu-item.js | 8 ++++++-- .../edit-site/src/components/page-patterns/grid-item.js | 5 +++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/packages/edit-site/src/components/page-patterns/duplicate-menu-item.js b/packages/edit-site/src/components/page-patterns/duplicate-menu-item.js index 882838a5a8f08..19180ffe53edc 100644 --- a/packages/edit-site/src/components/page-patterns/duplicate-menu-item.js +++ b/packages/edit-site/src/components/page-patterns/duplicate-menu-item.js @@ -16,7 +16,11 @@ import { unlock } from '../../lock-unlock'; const { useHistory } = unlock( routerPrivateApis ); -export default function DuplicateMenuItem( { item, onClose } ) { +export default function DuplicateMenuItem( { + item, + label = __( 'Duplicate' ), + onClose, +} ) { const history = useHistory(); const [ isModalOpen, setIsModalOpen ] = useState( false ); @@ -53,7 +57,7 @@ export default function DuplicateMenuItem( { item, onClose } ) { return ( <> setIsModalOpen( true ) }> - { __( 'Duplicate' ) } + { label } { isModalOpen && item.type !== TEMPLATE_PARTS && ( { isCustomPattern && ( Date: Wed, 5 Jul 2023 16:46:22 +1000 Subject: [PATCH 09/18] Stay on current page after duplicating pattern or part --- .../page-patterns/duplicate-menu-item.js | 64 +++++-------------- 1 file changed, 17 insertions(+), 47 deletions(-) diff --git a/packages/edit-site/src/components/page-patterns/duplicate-menu-item.js b/packages/edit-site/src/components/page-patterns/duplicate-menu-item.js index 19180ffe53edc..abd7bf784edef 100644 --- a/packages/edit-site/src/components/page-patterns/duplicate-menu-item.js +++ b/packages/edit-site/src/components/page-patterns/duplicate-menu-item.js @@ -4,7 +4,6 @@ import { MenuItem } from '@wordpress/components'; import { useState } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; -import { privateApis as routerPrivateApis } from '@wordpress/router'; /** * Internal dependencies @@ -12,70 +11,41 @@ import { privateApis as routerPrivateApis } from '@wordpress/router'; import CreatePatternModal from '../create-pattern-modal'; import CreateTemplatePartModal from '../create-template-part-modal'; import { TEMPLATE_PARTS } from './utils'; -import { unlock } from '../../lock-unlock'; - -const { useHistory } = unlock( routerPrivateApis ); export default function DuplicateMenuItem( { item, label = __( 'Duplicate' ), onClose, } ) { - const history = useHistory(); const [ isModalOpen, setIsModalOpen ] = useState( false ); - function handleCreatePattern( { pattern, categoryId } ) { - setIsModalOpen( false ); - onClose(); - - history.push( { - postId: pattern.id, - postType: 'wp_block', - categoryType: 'wp_block', - categoryId, - canvas: 'edit', - } ); - } - - function handleCreateTemplatePart( templatePart ) { + function handleClose() { setIsModalOpen( false ); onClose(); - - // Navigate to the created template part editor. - history.push( { - postId: templatePart.id, - postType: 'wp_template_part', - canvas: 'edit', - } ); } - function handleError() { - setIsModalOpen( false ); - onClose(); - } + const CreateModal = + item.type === TEMPLATE_PARTS + ? CreateTemplatePartModal + : CreatePatternModal; + + const modalProps = { + blocks: item.blocks || [], + closeModal: handleClose, + onCreate: handleClose, + onError: handleClose, + title: + item.type !== TEMPLATE_PARTS + ? __( 'Duplicate pattern' ) + : undefined, + }; return ( <> setIsModalOpen( true ) }> { label } - { isModalOpen && item.type !== TEMPLATE_PARTS && ( - setIsModalOpen( false ) } - onCreate={ handleCreatePattern } - onError={ handleError } - blocks={ item.blocks || [] } - title={ __( 'Duplicate pattern' ) } - /> - ) } - { isModalOpen && item.type === TEMPLATE_PARTS && ( - setIsModalOpen( false ) } - blocks={ item.blocks || [] } - onCreate={ handleCreateTemplatePart } - onError={ handleError } - /> - ) } + { isModalOpen && } ); } From 706a763f526cb447f12812596eb505fe45fb685c Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Wed, 5 Jul 2023 17:41:49 +1000 Subject: [PATCH 10/18] Directly create new parts or patterns when duplicating --- .../page-patterns/duplicate-menu-item.js | 140 +++++++++++++----- 1 file changed, 107 insertions(+), 33 deletions(-) diff --git a/packages/edit-site/src/components/page-patterns/duplicate-menu-item.js b/packages/edit-site/src/components/page-patterns/duplicate-menu-item.js index abd7bf784edef..ac66213143231 100644 --- a/packages/edit-site/src/components/page-patterns/duplicate-menu-item.js +++ b/packages/edit-site/src/components/page-patterns/duplicate-menu-item.js @@ -2,50 +2,124 @@ * WordPress dependencies */ import { MenuItem } from '@wordpress/components'; -import { useState } from '@wordpress/element'; -import { __ } from '@wordpress/i18n'; +import { store as coreStore } from '@wordpress/core-data'; +import { useDispatch } from '@wordpress/data'; +import { __, sprintf } from '@wordpress/i18n'; +import { store as noticesStore } from '@wordpress/notices'; +import { privateApis as routerPrivateApis } from '@wordpress/router'; /** * Internal dependencies */ -import CreatePatternModal from '../create-pattern-modal'; -import CreateTemplatePartModal from '../create-template-part-modal'; -import { TEMPLATE_PARTS } from './utils'; +import { + TEMPLATE_PARTS, + PATTERNS, + SYNC_TYPES, + USER_PATTERNS, + USER_PATTERN_CATEGORY, +} from './utils'; +import { + useExistingTemplateParts, + getUniqueTemplatePartTitle, + getCleanTemplatePartSlug, +} from '../../utils/template-part-create'; +import { unlock } from '../../lock-unlock'; + +const { useHistory } = unlock( routerPrivateApis ); export default function DuplicateMenuItem( { item, label = __( 'Duplicate' ), onClose, } ) { - const [ isModalOpen, setIsModalOpen ] = useState( false ); + const { createErrorNotice } = useDispatch( noticesStore ); + const { saveEntityRecord } = useDispatch( coreStore ); + + const history = useHistory(); + const existingTemplateParts = useExistingTemplateParts(); + + async function createTemplatePart() { + try { + const copiedTitle = sprintf( + /* translators: %s: Existing template part title */ + __( '%s (Copy)' ), + item.title + ); + const title = getUniqueTemplatePartTitle( + copiedTitle, + existingTemplateParts + ); + const slug = getCleanTemplatePartSlug( title ); + const { area, content } = item.templatePart; + + await saveEntityRecord( + 'postType', + 'wp_template_part', + { slug, title, content, area }, + { throwOnError: true } + ); + + onClose(); + } catch ( error ) { + const errorMessage = + error.message && error.code !== 'unknown_error' + ? error.message + : __( + 'An error occurred while creating the template part.' + ); + + createErrorNotice( errorMessage, { type: 'snackbar' } ); + onClose(); + } + } + + async function createPattern() { + try { + const isThemePattern = item.type === PATTERNS; + const title = sprintf( + /* translators: %s: Existing pattern title */ + __( '%s (Copy)' ), + item.title + ); + + await saveEntityRecord( + 'postType', + 'wp_block', + { + content: isThemePattern + ? item.content + : item.reusableBlock.content, + meta: isThemePattern + ? { sync_status: SYNC_TYPES.unsynced } + : item.reusableBlock.meta, + status: 'publish', + title, + }, + { throwOnError: true } + ); - function handleClose() { - setIsModalOpen( false ); - onClose(); + onClose(); + + // If this was a theme pattern, we're "copying to my patterns", so + // we need to navigate to that category to display the new pattern. + history.push( { + categoryType: USER_PATTERNS, + categoryId: USER_PATTERN_CATEGORY, + path: '/patterns', + } ); + } catch ( error ) { + const errorMessage = + error.message && error.code !== 'unknown_error' + ? error.message + : __( 'An error occurred while creating the pattern.' ); + + createErrorNotice( errorMessage, { type: 'snackbar' } ); + onClose(); + } } - const CreateModal = - item.type === TEMPLATE_PARTS - ? CreateTemplatePartModal - : CreatePatternModal; - - const modalProps = { - blocks: item.blocks || [], - closeModal: handleClose, - onCreate: handleClose, - onError: handleClose, - title: - item.type !== TEMPLATE_PARTS - ? __( 'Duplicate pattern' ) - : undefined, - }; - - return ( - <> - setIsModalOpen( true ) }> - { label } - - { isModalOpen && } - - ); + const createItem = + item.type === TEMPLATE_PARTS ? createTemplatePart : createPattern; + + return { label }; } From b9cc1ba893f29037bccf5df12a21d38a792ce7d9 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Wed, 5 Jul 2023 18:17:24 +1000 Subject: [PATCH 11/18] Update actions menu for customized template parts --- .../edit-site/src/components/page-patterns/grid-item.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) 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 bf25ac5cc6e72..38036743f8a5b 100644 --- a/packages/edit-site/src/components/page-patterns/grid-item.js +++ b/packages/edit-site/src/components/page-patterns/grid-item.js @@ -98,6 +98,7 @@ export default function GridItem( { categoryId, composite, icon, item } ) { // 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 ) { @@ -206,7 +207,7 @@ export default function GridItem( { categoryId, composite, icon, item } ) { > { ( { onClose } ) => ( - { isCustomPattern && ( + { isCustomPattern && ! hasThemeFile && ( - { __( 'Delete' ) } + { hasThemeFile + ? __( 'Clear customizations' ) + : __( 'Delete' ) } ) } From 5f6514bc186e73294e2e066eee9a8735b477fb06 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Wed, 5 Jul 2023 18:27:58 +1000 Subject: [PATCH 12/18] Update confirmation dialog for customized template parts --- .../edit-site/src/components/page-patterns/grid-item.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) 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 38036743f8a5b..71fa563b48c89 100644 --- a/packages/edit-site/src/components/page-patterns/grid-item.js +++ b/packages/edit-site/src/components/page-patterns/grid-item.js @@ -118,6 +118,11 @@ export default function GridItem( { categoryId, composite, icon, item } ) { ? templatePartIcons[ categoryId ] : icon; + const confirmButtonText = hasThemeFile ? __( 'Clear' ) : __( 'Delete' ); + const confirmPrompt = hasThemeFile + ? __( 'Are you sure you want to clear these customizations?' ) + : __( 'Are you sure you want to delete this pattern?' ); + return ( <>
@@ -240,11 +245,11 @@ export default function GridItem( { categoryId, composite, icon, item } ) {
{ isDeleteDialogOpen && ( setIsDeleteDialogOpen( false ) } > - { __( 'Are you sure you want to delete this pattern?' ) } + { confirmPrompt } ) } From 49506ee171ed4e1826db294685a4fc6f31c146c0 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Thu, 6 Jul 2023 18:26:13 +1000 Subject: [PATCH 13/18] Improve notices, prompts, and redirection --- .../page-patterns/duplicate-menu-item.js | 85 ++++++++++++++++--- .../src/components/page-patterns/grid-item.js | 23 +++-- 2 files changed, 90 insertions(+), 18 deletions(-) diff --git a/packages/edit-site/src/components/page-patterns/duplicate-menu-item.js b/packages/edit-site/src/components/page-patterns/duplicate-menu-item.js index ac66213143231..653b627db24ab 100644 --- a/packages/edit-site/src/components/page-patterns/duplicate-menu-item.js +++ b/packages/edit-site/src/components/page-patterns/duplicate-menu-item.js @@ -28,12 +28,14 @@ import { unlock } from '../../lock-unlock'; const { useHistory } = unlock( routerPrivateApis ); export default function DuplicateMenuItem( { + categoryId, item, label = __( 'Duplicate' ), onClose, } ) { - const { createErrorNotice } = useDispatch( noticesStore ); const { saveEntityRecord } = useDispatch( coreStore ); + const { createErrorNotice, createSuccessNotice } = + useDispatch( noticesStore ); const history = useHistory(); const existingTemplateParts = useExistingTemplateParts(); @@ -52,13 +54,37 @@ export default function DuplicateMenuItem( { const slug = getCleanTemplatePartSlug( title ); const { area, content } = item.templatePart; - await saveEntityRecord( + const result = await saveEntityRecord( 'postType', 'wp_template_part', { slug, title, content, area }, { throwOnError: true } ); + createSuccessNotice( + sprintf( + // translators: %s: The new template part's title e.g. 'Call to action (copy)'. + __( "'%s' created." ), + title + ), + { + type: 'snackbar', + id: 'edit-site-patterns-success', + actions: [ + { + label: __( 'Edit template part' ), + onClick: () => + history.push( { + postType: TEMPLATE_PARTS, + postId: result?.id, + categoryType: TEMPLATE_PARTS, + categoryId, + } ), + }, + ], + } + ); + onClose(); } catch ( error ) { const errorMessage = @@ -68,7 +94,10 @@ export default function DuplicateMenuItem( { 'An error occurred while creating the template part.' ); - createErrorNotice( errorMessage, { type: 'snackbar' } ); + createErrorNotice( errorMessage, { + type: 'snackbar', + id: 'edit-site-patterns-error', + } ); onClose(); } } @@ -82,7 +111,7 @@ export default function DuplicateMenuItem( { item.title ); - await saveEntityRecord( + const result = await saveEntityRecord( 'postType', 'wp_block', { @@ -98,22 +127,52 @@ export default function DuplicateMenuItem( { { throwOnError: true } ); - onClose(); + const actionLabel = isThemePattern + ? __( 'View my patterns' ) + : __( 'Edit pattern' ); - // If this was a theme pattern, we're "copying to my patterns", so - // we need to navigate to that category to display the new pattern. - history.push( { - categoryType: USER_PATTERNS, - categoryId: USER_PATTERN_CATEGORY, - path: '/patterns', - } ); + const newLocation = isThemePattern + ? { + categoryType: USER_PATTERNS, + categoryId: USER_PATTERN_CATEGORY, + path: '/patterns', + } + : { + categoryType: USER_PATTERNS, + categoryId: USER_PATTERN_CATEGORY, + postType: USER_PATTERNS, + postId: result?.id, + }; + + createSuccessNotice( + sprintf( + // translators: %s: The new pattern's title e.g. 'Call to action (copy)'. + __( "'%s' added to my patterns." ), + title + ), + { + type: 'snackbar', + id: 'edit-site-patterns-success', + actions: [ + { + label: actionLabel, + onClick: () => history.push( newLocation ), + }, + ], + } + ); + + onClose(); } catch ( error ) { const errorMessage = error.message && error.code !== 'unknown_error' ? error.message : __( 'An error occurred while creating the pattern.' ); - createErrorNotice( errorMessage, { type: 'snackbar' } ); + createErrorNotice( errorMessage, { + type: 'snackbar', + id: 'edit-site-patterns-error', + } ); onClose(); } } 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 71fa563b48c89..8cb431ae26a7b 100644 --- a/packages/edit-site/src/components/page-patterns/grid-item.js +++ b/packages/edit-site/src/components/page-patterns/grid-item.js @@ -81,15 +81,23 @@ export default function GridItem( { categoryId, composite, icon, item } ) { const deletePattern = async () => { try { await __experimentalDeleteReusableBlock( item.id ); - createSuccessNotice( __( 'Pattern successfully deleted.' ), { - type: 'snackbar', - } ); + 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' } ); + createErrorNotice( errorMessage, { + type: 'snackbar', + id: 'edit-site-patterns-error', + } ); } }; const deleteItem = () => @@ -121,7 +129,11 @@ export default function GridItem( { categoryId, composite, icon, item } ) { const confirmButtonText = hasThemeFile ? __( 'Clear' ) : __( 'Delete' ); const confirmPrompt = hasThemeFile ? __( 'Are you sure you want to clear these customizations?' ) - : __( 'Are you sure you want to delete this pattern?' ); + : 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 + ); return ( <> @@ -219,6 +231,7 @@ export default function GridItem( { categoryId, composite, icon, item } ) { /> ) } Date: Fri, 7 Jul 2023 13:53:08 +1000 Subject: [PATCH 14/18] Further tweaks to snackbar notices and actions --- .../src/components/page-patterns/duplicate-menu-item.js | 8 ++++---- .../edit-site/src/components/page-patterns/grid-item.js | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/edit-site/src/components/page-patterns/duplicate-menu-item.js b/packages/edit-site/src/components/page-patterns/duplicate-menu-item.js index 653b627db24ab..9e635444a0d1f 100644 --- a/packages/edit-site/src/components/page-patterns/duplicate-menu-item.js +++ b/packages/edit-site/src/components/page-patterns/duplicate-menu-item.js @@ -64,7 +64,7 @@ export default function DuplicateMenuItem( { createSuccessNotice( sprintf( // translators: %s: The new template part's title e.g. 'Call to action (copy)'. - __( "'%s' created." ), + __( '"%s" created.' ), title ), { @@ -72,7 +72,7 @@ export default function DuplicateMenuItem( { id: 'edit-site-patterns-success', actions: [ { - label: __( 'Edit template part' ), + label: __( 'Edit' ), onClick: () => history.push( { postType: TEMPLATE_PARTS, @@ -129,7 +129,7 @@ export default function DuplicateMenuItem( { const actionLabel = isThemePattern ? __( 'View my patterns' ) - : __( 'Edit pattern' ); + : __( 'Edit' ); const newLocation = isThemePattern ? { @@ -147,7 +147,7 @@ export default function DuplicateMenuItem( { createSuccessNotice( sprintf( // translators: %s: The new pattern's title e.g. 'Call to action (copy)'. - __( "'%s' added to my patterns." ), + __( '"%s" added to my patterns.' ), title ), { 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 8cb431ae26a7b..7f40fbce9035c 100644 --- a/packages/edit-site/src/components/page-patterns/grid-item.js +++ b/packages/edit-site/src/components/page-patterns/grid-item.js @@ -84,7 +84,7 @@ export default function GridItem( { categoryId, composite, icon, item } ) { createSuccessNotice( sprintf( // translators: %s: The pattern's title e.g. 'Call to action'. - __( "'%s' deleted." ), + __( '"%s" deleted.' ), item.title ), { type: 'snackbar', id: 'edit-site-patterns-success' } @@ -131,7 +131,7 @@ export default function GridItem( { categoryId, composite, icon, item } ) { ? __( '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'?" ), + __( 'Are you sure you want to delete "%s"?' ), item.title ); From 83284968dfd3f14473285fc4df9da0fa2042ab2d Mon Sep 17 00:00:00 2001 From: Glen Davies Date: Fri, 7 Jul 2023 17:01:20 +1200 Subject: [PATCH 15/18] Fix sync_status --- .../src/components/page-patterns/duplicate-menu-item.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/edit-site/src/components/page-patterns/duplicate-menu-item.js b/packages/edit-site/src/components/page-patterns/duplicate-menu-item.js index 9e635444a0d1f..66dae13fcfd30 100644 --- a/packages/edit-site/src/components/page-patterns/duplicate-menu-item.js +++ b/packages/edit-site/src/components/page-patterns/duplicate-menu-item.js @@ -119,8 +119,11 @@ export default function DuplicateMenuItem( { ? item.content : item.reusableBlock.content, meta: isThemePattern - ? { sync_status: SYNC_TYPES.unsynced } - : item.reusableBlock.meta, + ? { wp_pattern_sync_status: SYNC_TYPES.unsynced } + : { + wp_pattern_sync_status: + item.reusableBlock.wp_pattern_sync_status, + }, status: 'publish', title, }, From 1145a938298ebbf457a10b96640182cb4ed4557e Mon Sep 17 00:00:00 2001 From: Glen Davies Date: Fri, 7 Jul 2023 17:06:52 +1200 Subject: [PATCH 16/18] Also copy any other meta fields --- .../src/components/page-patterns/duplicate-menu-item.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/edit-site/src/components/page-patterns/duplicate-menu-item.js b/packages/edit-site/src/components/page-patterns/duplicate-menu-item.js index 66dae13fcfd30..dc337304ed6c3 100644 --- a/packages/edit-site/src/components/page-patterns/duplicate-menu-item.js +++ b/packages/edit-site/src/components/page-patterns/duplicate-menu-item.js @@ -121,6 +121,7 @@ export default function DuplicateMenuItem( { meta: isThemePattern ? { wp_pattern_sync_status: SYNC_TYPES.unsynced } : { + ...item.reusableBlock.meta, wp_pattern_sync_status: item.reusableBlock.wp_pattern_sync_status, }, From 9362aefe315f9766c751d6fba82d1b5d5eef778f Mon Sep 17 00:00:00 2001 From: Glen Davies Date: Fri, 7 Jul 2023 17:15:52 +1200 Subject: [PATCH 17/18] fix bug with duplicating synced --- .../src/components/page-patterns/duplicate-menu-item.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/edit-site/src/components/page-patterns/duplicate-menu-item.js b/packages/edit-site/src/components/page-patterns/duplicate-menu-item.js index dc337304ed6c3..9f7b1a7c9feb7 100644 --- a/packages/edit-site/src/components/page-patterns/duplicate-menu-item.js +++ b/packages/edit-site/src/components/page-patterns/duplicate-menu-item.js @@ -123,7 +123,12 @@ export default function DuplicateMenuItem( { : { ...item.reusableBlock.meta, wp_pattern_sync_status: - item.reusableBlock.wp_pattern_sync_status, + item.reusableBlock + .wp_pattern_sync_status === + SYNC_TYPES.unsynced + ? item.reusableBlock + .wp_pattern_sync_status + : undefined, }, status: 'publish', title, From fee8340d163cded2a384e111bfd0519fc25369b1 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Fri, 7 Jul 2023 15:31:40 +1000 Subject: [PATCH 18/18] Tweak to improve readability --- .../page-patterns/duplicate-menu-item.js | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/packages/edit-site/src/components/page-patterns/duplicate-menu-item.js b/packages/edit-site/src/components/page-patterns/duplicate-menu-item.js index 9f7b1a7c9feb7..d2c14d15f341b 100644 --- a/packages/edit-site/src/components/page-patterns/duplicate-menu-item.js +++ b/packages/edit-site/src/components/page-patterns/duplicate-menu-item.js @@ -27,6 +27,20 @@ import { unlock } from '../../lock-unlock'; const { useHistory } = unlock( routerPrivateApis ); +function getPatternMeta( item ) { + if ( item.type === PATTERNS ) { + return { wp_pattern_sync_status: SYNC_TYPES.unsynced }; + } + + const syncStatus = item.reusableBlock.wp_pattern_sync_status; + const isUnsynced = syncStatus === SYNC_TYPES.unsynced; + + return { + ...item.reusableBlock.meta, + wp_pattern_sync_status: isUnsynced ? syncStatus : undefined, + }; +} + export default function DuplicateMenuItem( { categoryId, item, @@ -118,18 +132,7 @@ export default function DuplicateMenuItem( { content: isThemePattern ? item.content : item.reusableBlock.content, - meta: isThemePattern - ? { wp_pattern_sync_status: SYNC_TYPES.unsynced } - : { - ...item.reusableBlock.meta, - wp_pattern_sync_status: - item.reusableBlock - .wp_pattern_sync_status === - SYNC_TYPES.unsynced - ? item.reusableBlock - .wp_pattern_sync_status - : undefined, - }, + meta: getPatternMeta( item ), status: 'publish', title, },