diff --git a/backport-changelog/6.9/8063.md b/backport-changelog/6.9/8063.md
index 9b83fa2b4a46ad..8b400119ad0f5e 100644
--- a/backport-changelog/6.9/8063.md
+++ b/backport-changelog/6.9/8063.md
@@ -9,3 +9,4 @@ https://github.com/WordPress/wordpress-develop/pull/8063
* https://github.com/WordPress/gutenberg/pull/72011
* https://github.com/WordPress/gutenberg/pull/72141
* https://github.com/WordPress/gutenberg/pull/72223
+* https://github.com/WordPress/gutenberg/pull/72285
diff --git a/lib/compat/wordpress-6.9/class-gutenberg-rest-templates-controller.php b/lib/compat/wordpress-6.9/class-gutenberg-rest-templates-controller.php
deleted file mode 100644
index 53dbb6ca50c39c..00000000000000
--- a/lib/compat/wordpress-6.9/class-gutenberg-rest-templates-controller.php
+++ /dev/null
@@ -1,16 +0,0 @@
-
return {
isBlockBasedTheme:
select( coreStore ).getCurrentTheme()?.is_block_theme,
- canCreateTemplate: select( coreStore ).canUser( 'read', {
+ canCreateTemplate: select( coreStore ).canUser( 'create', {
kind: 'postType',
name: templateType,
} ),
@@ -470,10 +469,6 @@ export function useSiteEditorNavigationCommands() {
name: 'core/edit-site/navigate-templates',
hook: getNavigationCommandLoaderPerTemplate( 'wp_template' ),
} );
- useCommandLoader( {
- name: 'core/edit-site/navigate-templates',
- hook: getNavigationCommandLoaderPerTemplate( 'wp_registered_template' ),
- } );
useCommandLoader( {
name: 'core/edit-site/navigate-template-parts',
hook: getNavigationCommandLoaderPerTemplate( 'wp_template_part' ),
diff --git a/packages/core-data/src/actions.js b/packages/core-data/src/actions.js
index 583f9632ac4a17..6faad5041ec858 100644
--- a/packages/core-data/src/actions.js
+++ b/packages/core-data/src/actions.js
@@ -93,16 +93,6 @@ export function receiveEntityRecords(
edits,
meta
) {
- // If we receive an auto-draft template, pretend it's already published.
- if ( kind === 'postType' && name === 'wp_template' ) {
- records = ( Array.isArray( records ) ? records : [ records ] ).map(
- ( record ) =>
- record.status === 'auto-draft'
- ? { ...record, status: 'publish' }
- : record
- );
- }
-
// Auto drafts should not have titles, but some plugins rely on them so we can't filter this
// on the server.
if ( kind === 'postType' ) {
@@ -709,11 +699,6 @@ export const saveEntityRecord =
),
};
}
- // Unless there is no persisted record, set the status to
- // publish.
- if ( name === 'wp_template' && persistedRecord ) {
- edits.status = 'publish';
- }
updatedRecord = await __unstableFetch( {
path,
method: recordId ? 'PUT' : 'POST',
diff --git a/packages/core-data/src/private-actions.js b/packages/core-data/src/private-actions.js
index 0ed6e9748b552a..29af65ba137bf6 100644
--- a/packages/core-data/src/private-actions.js
+++ b/packages/core-data/src/private-actions.js
@@ -132,7 +132,3 @@ export const editMediaEntity =
dispatch.__unstableReleaseStoreLock( lock );
}
};
-
-export function receiveTemplateAutoDraftId( target, id ) {
- return { type: 'RECEIVE_TEMPLATE_AUTO_DRAFT_ID', target, id };
-}
diff --git a/packages/core-data/src/private-selectors.ts b/packages/core-data/src/private-selectors.ts
index d9bc2214c860c0..eb121b170e03a1 100644
--- a/packages/core-data/src/private-selectors.ts
+++ b/packages/core-data/src/private-selectors.ts
@@ -293,10 +293,3 @@ export const getTemplateId = createRegistrySelector(
} );
}
);
-
-export function getTemplateAutoDraftId(
- state: State,
- staticTemplateId: string
-) {
- return state.templateAutoDraftId[ staticTemplateId ];
-}
diff --git a/packages/core-data/src/reducer.js b/packages/core-data/src/reducer.js
index 051dc884a3f079..2d53bf5762702b 100644
--- a/packages/core-data/src/reducer.js
+++ b/packages/core-data/src/reducer.js
@@ -628,12 +628,6 @@ export function registeredPostMeta( state = {}, action ) {
return state;
}
-export function templateAutoDraftId( state = {}, action ) {
- return action.type === 'RECEIVE_TEMPLATE_AUTO_DRAFT_ID'
- ? { ...state, [ action.target ]: action.id }
- : state;
-}
-
export default combineReducers( {
users,
currentTheme,
@@ -654,5 +648,4 @@ export default combineReducers( {
navigationFallbackId,
defaultTemplates,
registeredPostMeta,
- templateAutoDraftId,
} );
diff --git a/packages/core-data/src/resolvers.js b/packages/core-data/src/resolvers.js
index fd8a9c76b95f5f..53ff8520ce4c0d 100644
--- a/packages/core-data/src/resolvers.js
+++ b/packages/core-data/src/resolvers.js
@@ -248,30 +248,6 @@ getEntityRecord.shouldInvalidate = ( action, kind, name ) => {
);
};
-export const getTemplateAutoDraftId =
- ( staticTemplateId ) =>
- async ( { resolveSelect, dispatch } ) => {
- const record = await resolveSelect.getEntityRecord(
- 'postType',
- 'wp_registered_template',
- staticTemplateId
- );
- const autoDraft = await dispatch.saveEntityRecord(
- 'postType',
- 'wp_template',
- {
- ...record,
- id: undefined,
- type: 'wp_template',
- status: 'auto-draft',
- }
- );
- await dispatch.receiveTemplateAutoDraftId(
- staticTemplateId,
- autoDraft.id
- );
- };
-
/**
* Requests an entity's record from the REST API.
*/
diff --git a/packages/core-data/src/selectors.ts b/packages/core-data/src/selectors.ts
index c618a72afadf24..5844a2d21cb9fb 100644
--- a/packages/core-data/src/selectors.ts
+++ b/packages/core-data/src/selectors.ts
@@ -49,7 +49,6 @@ export interface State {
userPatternCategories: Array< UserPatternCategory >;
defaultTemplates: Record< string, string >;
registeredPostMeta: Record< string, Object >;
- templateAutoDraftId: Record< string, number | null >;
}
type EntityRecordKey = string | number;
diff --git a/packages/edit-post/src/components/layout/index.js b/packages/edit-post/src/components/layout/index.js
index 34a5d36e6fa10f..28707f3bcd9636 100644
--- a/packages/edit-post/src/components/layout/index.js
+++ b/packages/edit-post/src/components/layout/index.js
@@ -519,17 +519,6 @@ function Layout( {
useMetaBoxInitialization( hasActiveMetaboxes && hasResolvedMode );
- const editableResolvedTemplateId = useSelect(
- ( select ) => {
- if ( typeof templateId !== 'string' ) {
- return templateId;
- }
- return unlock( select( coreStore ) ).getTemplateAutoDraftId(
- templateId
- );
- },
- [ templateId ]
- );
const [ paddingAppenderRef, paddingStyle ] = usePaddingAppender(
enablePaddingAppender
);
@@ -653,7 +642,7 @@ function Layout( {
initialEdits={ initialEdits }
postType={ currentPostType }
postId={ currentPostId }
- templateId={ editableResolvedTemplateId }
+ templateId={ templateId }
className={ className }
styles={ styles }
forceIsDirty={ hasActiveMetaboxes }
diff --git a/packages/edit-site/src/components/editor/use-resolve-edited-entity.js b/packages/edit-site/src/components/editor/use-resolve-edited-entity.js
index 93ac9f19406e01..e0b89825ab4ff0 100644
--- a/packages/edit-site/src/components/editor/use-resolve-edited-entity.js
+++ b/packages/edit-site/src/components/editor/use-resolve-edited-entity.js
@@ -25,7 +25,6 @@ const postTypesWithoutParentTemplate = [
TEMPLATE_PART_POST_TYPE,
NAVIGATION_POST_TYPE,
PATTERN_TYPES.user,
- 'wp_registered_template',
];
const authorizedPostTypes = [ 'page', 'post' ];
@@ -42,8 +41,6 @@ function getPostType( name ) {
postType = TEMPLATE_POST_TYPE;
} else if ( name === 'template-item' ) {
postType = TEMPLATE_POST_TYPE;
- } else if ( name === 'static-template-item' ) {
- postType = 'wp_registered_template';
} else if ( name === 'page-item' || name === 'pages' ) {
postType = 'page';
} else if ( name === 'post-item' || name === 'posts' ) {
@@ -55,29 +52,14 @@ function getPostType( name ) {
export function useResolveEditedEntity() {
const { name, params = {}, query } = useLocation();
- const { postId: _postId = query?.postId } = params; // Fallback to query param for postId for list view routes.
- const _postType = getPostType( name, _postId ) ?? query?.postType;
+ const { postId = query?.postId } = params; // Fallback to query param for postId for list view routes.
+ const postType = getPostType( name, postId ) ?? query?.postType;
const homePage = useSelect( ( select ) => {
const { getHomePage } = unlock( select( coreDataStore ) );
return getHomePage();
}, [] );
- const [ postType, postId ] = useSelect(
- ( select ) => {
- if ( _postType !== 'wp_registered_template' ) {
- return [ _postType, _postId ];
- }
- return [
- TEMPLATE_POST_TYPE,
- unlock( select( coreDataStore ) ).getTemplateAutoDraftId(
- _postId
- ),
- ];
- },
- [ _postType, _postId ]
- );
-
/**
* This is a hook that recreates the logic to resolve a template for a given WordPress postID postTypeId
* in order to match the frontend as closely as possible in the site editor.
diff --git a/packages/edit-site/src/components/page-templates/index.js b/packages/edit-site/src/components/page-templates/index.js
index f3f7854724c478..7847c1a9901478 100644
--- a/packages/edit-site/src/components/page-templates/index.js
+++ b/packages/edit-site/src/components/page-templates/index.js
@@ -2,7 +2,8 @@
* WordPress dependencies
*/
import { Page } from '@wordpress/admin-ui';
-import { __ } from '@wordpress/i18n';
+import { __, sprintf } from '@wordpress/i18n';
+import { decodeEntities } from '@wordpress/html-entities';
import { useState, useMemo, useCallback } from '@wordpress/element';
import {
privateApis as corePrivateApis,
@@ -12,10 +13,11 @@ import { DataViews, filterSortAndPaginate } from '@wordpress/dataviews';
import { privateApis as routerPrivateApis } from '@wordpress/router';
import { privateApis as editorPrivateApis } from '@wordpress/editor';
import { addQueryArgs } from '@wordpress/url';
-import { useSelect } from '@wordpress/data';
+import { useSelect, useDispatch } from '@wordpress/data';
import { useEvent } from '@wordpress/compose';
import { useView } from '@wordpress/views';
-import { Button } from '@wordpress/components';
+import { Button, Modal } from '@wordpress/components';
+import { store as noticesStore } from '@wordpress/notices';
/**
* Internal dependencies
@@ -45,6 +47,8 @@ export default function PageTemplates() {
const { path, query } = useLocation();
const { activeView = 'active', postId } = query;
const [ selection, setSelection ] = useState( [ postId ] );
+ const [ selectedRegisteredTemplate, setSelectedRegisteredTemplate ] =
+ useState( false );
const defaultView = useMemo( () => {
return getDefaultView( activeView );
}, [ activeView ] );
@@ -207,15 +211,54 @@ export default function PageTemplates() {
elements,
} );
return _fields;
- }, [ users, activeView ] );
+ }, [ users, activeView, themeField ] );
const { data, paginationInfo } = useMemo( () => {
return filterSortAndPaginate( records, view, fields );
}, [ records, view, fields ] );
+ const { createSuccessNotice } = useDispatch( noticesStore );
+ const onActionPerformed = useCallback(
+ ( actionId, items ) => {
+ switch ( actionId ) {
+ case 'duplicate-post':
+ {
+ const newItem = items[ 0 ];
+ const _title =
+ typeof newItem.title === 'string'
+ ? newItem.title
+ : newItem.title?.rendered;
+ createSuccessNotice(
+ sprintf(
+ // translators: %s: Title of the created post or template, e.g: "Hello world".
+ __( '"%s" successfully created.' ),
+ decodeEntities( _title ) || __( '(no title)' )
+ ),
+ {
+ type: 'snackbar',
+ id: 'duplicate-post-action',
+ actions: [
+ {
+ label: __( 'Edit' ),
+ onClick: () => {
+ history.navigate(
+ `/${ newItem.type }/${ newItem.id }?canvas=edit`
+ );
+ },
+ },
+ ],
+ }
+ );
+ }
+ break;
+ }
+ },
+ [ history, createSuccessNotice ]
+ );
const postTypeActions = usePostActions( {
postType: TEMPLATE_POST_TYPE,
context: 'list',
+ onActionPerformed,
} );
const editAction = useEditPostAction();
const setActiveTemplateAction = useSetActiveTemplateAction();
@@ -235,6 +278,10 @@ export default function PageTemplates() {
updateView( newView );
} );
+ const duplicateAction = actions.find(
+ ( action ) => action.id === 'duplicate-post'
+ );
+
return (