From b3a4b69f401bce93efe2f8df0c9e4b875498b747 Mon Sep 17 00:00:00 2001 From: ntsekouras Date: Thu, 20 Jul 2023 15:22:12 +0300 Subject: [PATCH 1/9] [Site Editor - Page Inspector]: Add ability to switch templates --- .../data/data-core-edit-site.md | 2 +- .../core-data/src/hooks/use-entity-record.ts | 4 +- packages/core-data/src/resolvers.js | 7 +- .../page-panels/edit-template.js | 77 +++++---- .../sidebar-edit-mode/page-panels/hooks.js | 68 ++++++++ .../sidebar-edit-mode/page-panels/index.js | 4 - .../page-panels/page-summary.js | 2 + .../page-panels/reset-default-template.js | 50 ++++++ .../sidebar-edit-mode/page-panels/style.scss | 36 ++++- .../page-panels/swap-template-button.js | 152 ++++++++++++++++++ packages/edit-site/src/store/actions.js | 3 +- 11 files changed, 354 insertions(+), 51 deletions(-) create mode 100644 packages/edit-site/src/components/sidebar-edit-mode/page-panels/hooks.js create mode 100644 packages/edit-site/src/components/sidebar-edit-mode/page-panels/reset-default-template.js create mode 100644 packages/edit-site/src/components/sidebar-edit-mode/page-panels/swap-template-button.js diff --git a/docs/reference-guides/data/data-core-edit-site.md b/docs/reference-guides/data/data-core-edit-site.md index 0cac2268b2ab2..6dea8e9b77d1b 100644 --- a/docs/reference-guides/data/data-core-edit-site.md +++ b/docs/reference-guides/data/data-core-edit-site.md @@ -291,7 +291,7 @@ _Parameters_ _Returns_ -- `number`: The resolved template ID for the page route. +- `Object`: Action object. ### setHasPageContentFocus diff --git a/packages/core-data/src/hooks/use-entity-record.ts b/packages/core-data/src/hooks/use-entity-record.ts index 01eee562644cf..7f3630b1dd4ce 100644 --- a/packages/core-data/src/hooks/use-entity-record.ts +++ b/packages/core-data/src/hooks/use-entity-record.ts @@ -155,8 +155,8 @@ export default function useEntityRecord< RecordType >( const mutations = useMemo( () => ( { - edit: ( record ) => - editEntityRecord( kind, name, recordId, record ), + edit: ( record, editOptions: any = {} ) => + editEntityRecord( kind, name, recordId, record, editOptions ), save: ( saveOptions: any = {} ) => saveEditedEntityRecord( kind, name, recordId, { throwOnError: true, diff --git a/packages/core-data/src/resolvers.js b/packages/core-data/src/resolvers.js index a9bd6adfcdbff..85fb871fa607d 100644 --- a/packages/core-data/src/resolvers.js +++ b/packages/core-data/src/resolvers.js @@ -497,7 +497,12 @@ __experimentalGetTemplateForLink.shouldInvalidate = ( action ) => { ( action.type === 'RECEIVE_ITEMS' || action.type === 'REMOVE_ITEMS' ) && action.invalidateCache && action.kind === 'postType' && - action.name === 'wp_template' + ( action.name === 'wp_template' || + // Invalidate when a template is updated. + ( [ 'page', 'post' ].includes( action.name ) && + Object.keys( action.persistedEdits || {} ).includes( + 'template' + ) ) ) ); }; diff --git a/packages/edit-site/src/components/sidebar-edit-mode/page-panels/edit-template.js b/packages/edit-site/src/components/sidebar-edit-mode/page-panels/edit-template.js index b49e8ac459e3f..f77edb795fb5d 100644 --- a/packages/edit-site/src/components/sidebar-edit-mode/page-panels/edit-template.js +++ b/packages/edit-site/src/components/sidebar-edit-mode/page-panels/edit-template.js @@ -2,21 +2,31 @@ * WordPress dependencies */ import { useSelect, useDispatch } from '@wordpress/data'; -import { useMemo } from '@wordpress/element'; import { decodeEntities } from '@wordpress/html-entities'; -import { BlockContextProvider, BlockPreview } from '@wordpress/block-editor'; -import { Button, __experimentalVStack as VStack } from '@wordpress/components'; +import { + DropdownMenu, + MenuGroup, + MenuItem, + __experimentalHStack as HStack, + __experimentalText as Text, +} from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { store as coreStore } from '@wordpress/core-data'; -import { parse } from '@wordpress/blocks'; /** * Internal dependencies */ import { store as editSiteStore } from '../../../store'; +import SwapTemplateButton from './swap-template-button'; +import ResetDefaultTemplate from './reset-default-template'; + +const POPOVER_PROPS = { + className: 'edit-site-page-panels-edit-template__dropdown', + placement: 'bottom-start', +}; export default function EditTemplate() { - const { context, hasResolved, template } = useSelect( ( select ) => { + const { hasResolved, template } = useSelect( ( select ) => { const { getEditedPostContext, getEditedPostType, getEditedPostId } = select( editSiteStore ); const { getEditedEntityRecord, hasFinishedResolution } = @@ -39,39 +49,40 @@ export default function EditTemplate() { const { setHasPageContentFocus } = useDispatch( editSiteStore ); - const blockContext = useMemo( - () => ( { ...context, postType: null, postId: null } ), - [ context ] - ); - - const blocks = useMemo( - () => - template.blocks ?? - ( template.content && typeof template.content !== 'function' - ? parse( template.content ) - : [] ), - [ template.blocks, template.content ] - ); - if ( ! hasResolved ) { return null; } return ( - -
{ decodeEntities( template.title ) }
-
- - - -
- -
+ { ( { onClose } ) => ( + <> + + { + setHasPageContentFocus( false ); + onClose(); + } } + > + { __( 'Edit template' ) } + + + + + + ) } + + ); } diff --git a/packages/edit-site/src/components/sidebar-edit-mode/page-panels/hooks.js b/packages/edit-site/src/components/sidebar-edit-mode/page-panels/hooks.js new file mode 100644 index 0000000000000..15144e3c1c919 --- /dev/null +++ b/packages/edit-site/src/components/sidebar-edit-mode/page-panels/hooks.js @@ -0,0 +1,68 @@ +/** + * WordPress dependencies + */ +import { useSelect } from '@wordpress/data'; +import { useMemo } from '@wordpress/element'; +import { store as coreStore } from '@wordpress/core-data'; + +/** + * Internal dependencies + */ +import { store as editSiteStore } from '../../../store'; + +export function useEditedPostContext() { + return useSelect( + ( select ) => select( editSiteStore ).getEditedPostContext(), + [] + ); +} + +export function useIsPostsPage() { + const { postId } = useEditedPostContext(); + return useSelect( + ( select ) => + +postId === + select( coreStore ).getEntityRecord( 'root', 'site' ) + ?.page_for_posts, + [ postId ] + ); +} + +export function useAvailableTemplates() { + const currentTemplateSlug = useCurrentTemplateSlug(); + const isPostsPage = useIsPostsPage(); + const templates = useSelect( + ( select ) => + select( coreStore ).getEntityRecords( 'postType', 'wp_template', { + per_page: -1, + } ), + [] + ); + return useMemo( + () => + // The posts page template cannot be changed. + ! isPostsPage && + templates?.filter( + ( template ) => + template.is_custom && + template.slug !== currentTemplateSlug && + !! template.content.raw // Skip empty templates. + ), + [ templates, currentTemplateSlug, isPostsPage ] + ); +} + +export function useCurrentTemplateSlug() { + const { postType, postId } = useEditedPostContext(); + return useSelect( + ( select ) => { + const post = select( coreStore ).getEntityRecord( + 'postType', + postType, + postId + ); + return post?.template; + }, + [ postType, postId ] + ); +} diff --git a/packages/edit-site/src/components/sidebar-edit-mode/page-panels/index.js b/packages/edit-site/src/components/sidebar-edit-mode/page-panels/index.js index 69971d1ad413a..df59dffe66be6 100644 --- a/packages/edit-site/src/components/sidebar-edit-mode/page-panels/index.js +++ b/packages/edit-site/src/components/sidebar-edit-mode/page-panels/index.js @@ -20,7 +20,6 @@ import { store as editSiteStore } from '../../../store'; import SidebarCard from '../sidebar-card'; import PageContent from './page-content'; import PageSummary from './page-summary'; -import EditTemplate from './edit-template'; export default function PagePanels() { const { id, type, hasResolved, status, date, password, title, modified } = @@ -81,9 +80,6 @@ export default function PagePanels() { - - - ); } diff --git a/packages/edit-site/src/components/sidebar-edit-mode/page-panels/page-summary.js b/packages/edit-site/src/components/sidebar-edit-mode/page-panels/page-summary.js index 3dce743b298d4..c4dafeab6cb37 100644 --- a/packages/edit-site/src/components/sidebar-edit-mode/page-panels/page-summary.js +++ b/packages/edit-site/src/components/sidebar-edit-mode/page-panels/page-summary.js @@ -7,6 +7,7 @@ import { __experimentalVStack as VStack } from '@wordpress/components'; */ import PageStatus from './page-status'; import PublishDate from './publish-date'; +import EditTemplate from './edit-template'; export default function PageSummary( { status, @@ -30,6 +31,7 @@ export default function PageSummary( { postId={ postId } postType={ postType } /> + ); } diff --git a/packages/edit-site/src/components/sidebar-edit-mode/page-panels/reset-default-template.js b/packages/edit-site/src/components/sidebar-edit-mode/page-panels/reset-default-template.js new file mode 100644 index 0000000000000..aa5120135b695 --- /dev/null +++ b/packages/edit-site/src/components/sidebar-edit-mode/page-panels/reset-default-template.js @@ -0,0 +1,50 @@ +/** + * WordPress dependencies + */ +import { useDispatch } from '@wordpress/data'; +import { MenuGroup, MenuItem } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; +import { store as noticesStore } from '@wordpress/notices'; +import { useEntityRecord } from '@wordpress/core-data'; + +/** + * Internal dependencies + */ +import { + useCurrentTemplateSlug, + useEditedPostContext, + useIsPostsPage, +} from './hooks'; +import { store as editSiteStore } from '../../../store'; + +export default function ResetDefaultTemplate( { onClick } ) { + const currentTemplateSlug = useCurrentTemplateSlug(); + const isPostsPage = useIsPostsPage(); + const { postType, postId } = useEditedPostContext(); + const entitiy = useEntityRecord( 'postType', postType, postId ); + const { setPage } = useDispatch( editSiteStore ); + const { createSuccessNotice } = useDispatch( noticesStore ); + // The default template in a post is indicated by an empty string. + if ( ! currentTemplateSlug || isPostsPage ) { + return null; + } + return ( + + { + entitiy.edit( { template: '' }, { undoIgnore: true } ); + await entitiy.save(); + onClick(); + await setPage( { + context: { postType, postId }, + } ); + createSuccessNotice( __( 'Default template applied.' ), { + type: 'snackbar', + } ); + } } + > + { __( 'Reset' ) } + + + ); +} diff --git a/packages/edit-site/src/components/sidebar-edit-mode/page-panels/style.scss b/packages/edit-site/src/components/sidebar-edit-mode/page-panels/style.scss index 8c10b32085612..2061b36be5f6b 100644 --- a/packages/edit-site/src/components/sidebar-edit-mode/page-panels/style.scss +++ b/packages/edit-site/src/components/sidebar-edit-mode/page-panels/style.scss @@ -1,12 +1,33 @@ -.edit-site-page-panels__edit-template-preview { - border: 1px solid $gray-200; - height: 200px; - max-height: 200px; - overflow: hidden; +.edit-site-page-panels__swap-template__confirm-modal__actions { + margin-top: $grid-unit-30; } -.edit-site-page-panels__edit-template-button { - justify-content: center; +.edit-site-page-panels__swap-template__modal-content .block-editor-block-patterns-list { + column-count: 2; + column-gap: $grid-unit-30; + + // Small top padding required to avoid cutting off the visible outline when hovering items + padding-top: $border-width-focus-fallback; + + @include break-medium() { + column-count: 3; + } + + @include break-wide() { + column-count: 4; + } + + .block-editor-block-patterns-list__list-item { + break-inside: avoid-column; + } + + .block-editor-block-patterns-list__item { + // Avoid to override the BlockPatternList component + // default hover and focus styles. + &:not(:focus):not(:hover) .block-editor-block-preview__container { + box-shadow: 0 0 0 1px $gray-300; + } + } } .edit-site-change-status__content { @@ -47,4 +68,3 @@ width: 30%; } } - diff --git a/packages/edit-site/src/components/sidebar-edit-mode/page-panels/swap-template-button.js b/packages/edit-site/src/components/sidebar-edit-mode/page-panels/swap-template-button.js new file mode 100644 index 0000000000000..b644d208b5d34 --- /dev/null +++ b/packages/edit-site/src/components/sidebar-edit-mode/page-panels/swap-template-button.js @@ -0,0 +1,152 @@ +/** + * WordPress dependencies + */ +import { useDispatch } from '@wordpress/data'; +import { useMemo, useState, useCallback } from '@wordpress/element'; +import { decodeEntities } from '@wordpress/html-entities'; +import { __experimentalBlockPatternsList as BlockPatternsList } from '@wordpress/block-editor'; +import { MenuItem, Modal, Button, Flex, FlexItem } from '@wordpress/components'; +import { __, sprintf } from '@wordpress/i18n'; +import { useEntityRecord } from '@wordpress/core-data'; +import { store as noticesStore } from '@wordpress/notices'; +import { parse } from '@wordpress/blocks'; +import { useAsyncList } from '@wordpress/compose'; + +/** + * Internal dependencies + */ +import { store as editSiteStore } from '../../../store'; +import { useAvailableTemplates, useEditedPostContext } from './hooks'; + +const modalContentMap = { + templatesList: 1, + confirmSwap: 2, +}; + +export default function SwapTemplateButton() { + const [ selectedTemplate, setSelectedTemplate ] = useState(); + const [ showModal, setShowModal ] = useState( false ); + const [ modalContent, setModalContent ] = useState( + modalContentMap.templatesList + ); + const availableTemplates = useAvailableTemplates(); + const onClose = useCallback( () => { + setShowModal( false ); + setModalContent( modalContentMap.templatesList ); + }, [] ); + const { postType, postId } = useEditedPostContext(); + const entitiy = useEntityRecord( 'postType', postType, postId ); + const { setPage } = useDispatch( editSiteStore ); + const { createSuccessNotice } = useDispatch( noticesStore ); + if ( ! availableTemplates?.length ) { + return null; + } + const modalTitle = + modalContent === modalContentMap.templatesList + ? __( 'Choose a template' ) + : sprintf( + /* translators: The page's title. */ + __( 'Save "%s"?' ), + decodeEntities( entitiy.record.title.rendered ) + ); + const onConfirmSwap = async ( template ) => { + entitiy.edit( { template: template.name }, { undoIgnore: true } ); + await entitiy.save(); + onClose(); + await setPage( { + context: { postType, postId }, + } ); + createSuccessNotice( + sprintf( + /* translators: The page's title. */ + __( '"%s" applied.' ), + decodeEntities( template.title ) + ), + { type: 'snackbar' } + ); + }; + return ( + <> + setShowModal( true ) }> + { __( 'Swap template' ) } + + { showModal && ( + + { modalContent === modalContentMap.templatesList && ( +
+ { + setModalContent( + modalContentMap.confirmSwap + ); + setSelectedTemplate( template ); + } } + /> +
+ ) } + { modalContent === modalContentMap.confirmSwap && ( + <> + { __( + 'Template swaps are published immediately.' + ) } + + + + + + + + + + ) } +
+ ) } + + ); +} + +function TemplatesList( { onSelect } ) { + const availableTemplates = useAvailableTemplates(); + const templatesAsPatterns = useMemo( + () => + availableTemplates.map( ( template ) => ( { + name: template.slug, + blocks: parse( template.content.raw ), + title: decodeEntities( template.title.rendered ), + id: template.id, + } ) ), + [ availableTemplates ] + ); + const shownTemplates = useAsyncList( templatesAsPatterns ); + return ( + + ); +} diff --git a/packages/edit-site/src/store/actions.js b/packages/edit-site/src/store/actions.js index 0ad521a9c9a54..27c0c21da2ea8 100644 --- a/packages/edit-site/src/store/actions.js +++ b/packages/edit-site/src/store/actions.js @@ -233,7 +233,7 @@ export function setHomeTemplateId() { * * @param {Object} context The context object. * - * @return {number} The resolved template ID for the page route. + * @return {Object} Action object. */ export function setEditedPostContext( context ) { return { @@ -268,7 +268,6 @@ export const setPage = // If the entity is undefined for some reason, path will resolve to "/" page.path = getPathAndQueryString( entity?.link ); } - const template = await registry .resolveSelect( coreStore ) .__experimentalGetTemplateForLink( page.path ); From b07f0f7421b7f026c869c66ff19df8ff51e7ec9c Mon Sep 17 00:00:00 2001 From: ntsekouras Date: Fri, 21 Jul 2023 19:42:17 +0300 Subject: [PATCH 2/9] try setting the page/template from edited entity record --- packages/core-data/src/resolvers.js | 7 +- .../sidebar-edit-mode/page-panels/hooks.js | 26 ++++-- .../page-panels/reset-default-template.js | 6 -- .../page-panels/swap-template-button.js | 91 ++----------------- packages/edit-site/src/store/actions.js | 49 +++++++--- 5 files changed, 69 insertions(+), 110 deletions(-) diff --git a/packages/core-data/src/resolvers.js b/packages/core-data/src/resolvers.js index 85fb871fa607d..a9bd6adfcdbff 100644 --- a/packages/core-data/src/resolvers.js +++ b/packages/core-data/src/resolvers.js @@ -497,12 +497,7 @@ __experimentalGetTemplateForLink.shouldInvalidate = ( action ) => { ( action.type === 'RECEIVE_ITEMS' || action.type === 'REMOVE_ITEMS' ) && action.invalidateCache && action.kind === 'postType' && - ( action.name === 'wp_template' || - // Invalidate when a template is updated. - ( [ 'page', 'post' ].includes( action.name ) && - Object.keys( action.persistedEdits || {} ).includes( - 'template' - ) ) ) + action.name === 'wp_template' ); }; diff --git a/packages/edit-site/src/components/sidebar-edit-mode/page-panels/hooks.js b/packages/edit-site/src/components/sidebar-edit-mode/page-panels/hooks.js index 15144e3c1c919..fe28e19eda2b0 100644 --- a/packages/edit-site/src/components/sidebar-edit-mode/page-panels/hooks.js +++ b/packages/edit-site/src/components/sidebar-edit-mode/page-panels/hooks.js @@ -28,16 +28,20 @@ export function useIsPostsPage() { ); } -export function useAvailableTemplates() { - const currentTemplateSlug = useCurrentTemplateSlug(); - const isPostsPage = useIsPostsPage(); - const templates = useSelect( +function useTemplates() { + return useSelect( ( select ) => select( coreStore ).getEntityRecords( 'postType', 'wp_template', { per_page: -1, } ), [] ); +} + +export function useAvailableTemplates() { + const currentTemplateSlug = useCurrentTemplateSlug(); + const isPostsPage = useIsPostsPage(); + const templates = useTemplates(); return useMemo( () => // The posts page template cannot be changed. @@ -54,9 +58,10 @@ export function useAvailableTemplates() { export function useCurrentTemplateSlug() { const { postType, postId } = useEditedPostContext(); - return useSelect( + const templates = useTemplates(); + const entityTemplate = useSelect( ( select ) => { - const post = select( coreStore ).getEntityRecord( + const post = select( coreStore ).getEditedEntityRecord( 'postType', postType, postId @@ -65,4 +70,13 @@ export function useCurrentTemplateSlug() { }, [ postType, postId ] ); + + if ( ! entityTemplate ) { + return; + } + // If a page has a `template` set and is not included in the list + // of the theme's templates, do not return it, in order to resolve + // to the current theme's default template. + return templates?.find( ( template ) => template.slug === entityTemplate ) + ?.slug; } diff --git a/packages/edit-site/src/components/sidebar-edit-mode/page-panels/reset-default-template.js b/packages/edit-site/src/components/sidebar-edit-mode/page-panels/reset-default-template.js index aa5120135b695..b821cede53c13 100644 --- a/packages/edit-site/src/components/sidebar-edit-mode/page-panels/reset-default-template.js +++ b/packages/edit-site/src/components/sidebar-edit-mode/page-panels/reset-default-template.js @@ -4,7 +4,6 @@ import { useDispatch } from '@wordpress/data'; import { MenuGroup, MenuItem } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; -import { store as noticesStore } from '@wordpress/notices'; import { useEntityRecord } from '@wordpress/core-data'; /** @@ -23,7 +22,6 @@ export default function ResetDefaultTemplate( { onClick } ) { const { postType, postId } = useEditedPostContext(); const entitiy = useEntityRecord( 'postType', postType, postId ); const { setPage } = useDispatch( editSiteStore ); - const { createSuccessNotice } = useDispatch( noticesStore ); // The default template in a post is indicated by an empty string. if ( ! currentTemplateSlug || isPostsPage ) { return null; @@ -33,14 +31,10 @@ export default function ResetDefaultTemplate( { onClick } ) { { entitiy.edit( { template: '' }, { undoIgnore: true } ); - await entitiy.save(); onClick(); await setPage( { context: { postType, postId }, } ); - createSuccessNotice( __( 'Default template applied.' ), { - type: 'snackbar', - } ); } } > { __( 'Reset' ) } diff --git a/packages/edit-site/src/components/sidebar-edit-mode/page-panels/swap-template-button.js b/packages/edit-site/src/components/sidebar-edit-mode/page-panels/swap-template-button.js index b644d208b5d34..1bbd3faa45ddd 100644 --- a/packages/edit-site/src/components/sidebar-edit-mode/page-panels/swap-template-button.js +++ b/packages/edit-site/src/components/sidebar-edit-mode/page-panels/swap-template-button.js @@ -5,10 +5,9 @@ import { useDispatch } from '@wordpress/data'; import { useMemo, useState, useCallback } from '@wordpress/element'; import { decodeEntities } from '@wordpress/html-entities'; import { __experimentalBlockPatternsList as BlockPatternsList } from '@wordpress/block-editor'; -import { MenuItem, Modal, Button, Flex, FlexItem } from '@wordpress/components'; -import { __, sprintf } from '@wordpress/i18n'; +import { MenuItem, Modal } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; import { useEntityRecord } from '@wordpress/core-data'; -import { store as noticesStore } from '@wordpress/notices'; import { parse } from '@wordpress/blocks'; import { useAsyncList } from '@wordpress/compose'; @@ -18,52 +17,25 @@ import { useAsyncList } from '@wordpress/compose'; import { store as editSiteStore } from '../../../store'; import { useAvailableTemplates, useEditedPostContext } from './hooks'; -const modalContentMap = { - templatesList: 1, - confirmSwap: 2, -}; - -export default function SwapTemplateButton() { - const [ selectedTemplate, setSelectedTemplate ] = useState(); +export default function SwapTemplateButton( { onClick } ) { const [ showModal, setShowModal ] = useState( false ); - const [ modalContent, setModalContent ] = useState( - modalContentMap.templatesList - ); const availableTemplates = useAvailableTemplates(); const onClose = useCallback( () => { setShowModal( false ); - setModalContent( modalContentMap.templatesList ); }, [] ); const { postType, postId } = useEditedPostContext(); const entitiy = useEntityRecord( 'postType', postType, postId ); const { setPage } = useDispatch( editSiteStore ); - const { createSuccessNotice } = useDispatch( noticesStore ); if ( ! availableTemplates?.length ) { return null; } - const modalTitle = - modalContent === modalContentMap.templatesList - ? __( 'Choose a template' ) - : sprintf( - /* translators: The page's title. */ - __( 'Save "%s"?' ), - decodeEntities( entitiy.record.title.rendered ) - ); - const onConfirmSwap = async ( template ) => { + const onTemplateSelect = async ( template ) => { entitiy.edit( { template: template.name }, { undoIgnore: true } ); - await entitiy.save(); onClose(); + onClick(); await setPage( { context: { postType, postId }, } ); - createSuccessNotice( - sprintf( - /* translators: The page's title. */ - __( '"%s" applied.' ), - decodeEntities( template.title ) - ), - { type: 'snackbar' } - ); }; return ( <> @@ -72,56 +44,13 @@ export default function SwapTemplateButton() { { showModal && ( - { modalContent === modalContentMap.templatesList && ( -
- { - setModalContent( - modalContentMap.confirmSwap - ); - setSelectedTemplate( template ); - } } - /> -
- ) } - { modalContent === modalContentMap.confirmSwap && ( - <> - { __( - 'Template swaps are published immediately.' - ) } - - - - - - - - - - ) } +
+ +
) } diff --git a/packages/edit-site/src/store/actions.js b/packages/edit-site/src/store/actions.js index 27c0c21da2ea8..f690075c31148 100644 --- a/packages/edit-site/src/store/actions.js +++ b/packages/edit-site/src/store/actions.js @@ -4,7 +4,7 @@ import apiFetch from '@wordpress/api-fetch'; import { parse, __unstableSerializeAndClean } from '@wordpress/blocks'; import deprecated from '@wordpress/deprecated'; -import { addQueryArgs, getPathAndQueryString } from '@wordpress/url'; +import { addQueryArgs } from '@wordpress/url'; import { __, sprintf } from '@wordpress/i18n'; import { store as noticesStore } from '@wordpress/notices'; import { store as coreStore } from '@wordpress/core-data'; @@ -257,20 +257,47 @@ export function setEditedPostContext( context ) { export const setPage = ( page ) => async ( { dispatch, registry } ) => { - if ( ! page.path && page.context?.postId ) { - const entity = await registry + let template; + const getDefaultTemplate = async ( slug ) => + apiFetch( { + path: addQueryArgs( '/wp/v2/templates/lookup', { + slug: `page-${ slug }`, + } ), + } ); + + if ( page.path ) { + template = await registry + .resolveSelect( coreStore ) + .__experimentalGetTemplateForLink( page.path ); + } else { + const editedEntity = await registry .resolveSelect( coreStore ) - .getEntityRecord( + .getEditedEntityRecord( 'postType', - page.context.postType || 'post', - page.context.postId + page.context?.postType || 'post', + page.context?.postId ); - // If the entity is undefined for some reason, path will resolve to "/" - page.path = getPathAndQueryString( entity?.link ); + const currentTemplateSlug = editedEntity?.template; + if ( currentTemplateSlug ) { + const currentTemplate = ( + await registry + .resolveSelect( coreStore ) + .getEntityRecords( 'postType', 'wp_template', { + per_page: -1, + } ) + )?.find( ( { slug } ) => slug === currentTemplateSlug ); + if ( currentTemplate ) { + template = currentTemplate; + } else { + // If a page has a `template` set and is not included in the list + // of the current theme's templates, query for current theme's default template. + template = await getDefaultTemplate( editedEntity?.link ); + } + } else { + // Page's `template` is empty, that indicates we need to use the default template for the page. + template = await getDefaultTemplate( editedEntity?.link ); + } } - const template = await registry - .resolveSelect( coreStore ) - .__experimentalGetTemplateForLink( page.path ); if ( ! template ) { return; From 9fb10fdd322cca6a3c6de2674528e74a8c340b1e Mon Sep 17 00:00:00 2001 From: James Koster Date: Mon, 24 Jul 2023 14:38:11 +0100 Subject: [PATCH 3/9] Update label --- .../components/sidebar-edit-mode/page-panels/edit-template.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/edit-site/src/components/sidebar-edit-mode/page-panels/edit-template.js b/packages/edit-site/src/components/sidebar-edit-mode/page-panels/edit-template.js index f77edb795fb5d..67256c808c816 100644 --- a/packages/edit-site/src/components/sidebar-edit-mode/page-panels/edit-template.js +++ b/packages/edit-site/src/components/sidebar-edit-mode/page-panels/edit-template.js @@ -62,7 +62,7 @@ export default function EditTemplate() { popoverProps={ POPOVER_PROPS } focusOnMount toggleProps={ { variant: 'tertiary' } } - label={ decodeEntities( template.title ) } + label={ __( 'Template options' ) } text={ decodeEntities( template.title ) } icon={ null } > From 54ebe82cbb6fff48d8b7e35a37c0d4f650bdb877 Mon Sep 17 00:00:00 2001 From: James Koster Date: Mon, 24 Jul 2023 14:50:58 +0100 Subject: [PATCH 4/9] Truncate --- .../sidebar-edit-mode/page-panels/edit-template.js | 5 ++++- .../sidebar-edit-mode/page-panels/style.scss | 11 +++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/packages/edit-site/src/components/sidebar-edit-mode/page-panels/edit-template.js b/packages/edit-site/src/components/sidebar-edit-mode/page-panels/edit-template.js index 67256c808c816..2295ee12f4504 100644 --- a/packages/edit-site/src/components/sidebar-edit-mode/page-panels/edit-template.js +++ b/packages/edit-site/src/components/sidebar-edit-mode/page-panels/edit-template.js @@ -61,7 +61,10 @@ export default function EditTemplate() { Date: Mon, 11 Sep 2023 19:35:31 +0300 Subject: [PATCH 5/9] update z-index of templates swap modal --- packages/base-styles/_z-index.scss | 1 + .../src/components/sidebar-edit-mode/page-panels/style.scss | 4 ++++ .../sidebar-edit-mode/page-panels/swap-template-button.js | 1 + 3 files changed, 6 insertions(+) diff --git a/packages/base-styles/_z-index.scss b/packages/base-styles/_z-index.scss index 4f8bbab5a1609..12443a30a9665 100644 --- a/packages/base-styles/_z-index.scss +++ b/packages/base-styles/_z-index.scss @@ -127,6 +127,7 @@ $z-layers: ( ".block-editor-template-part__selection-modal": 1000001, ".block-editor-block-rename-modal": 1000001, ".edit-site-list__rename-modal": 1000001, + ".edit-site-swap-template-modal": 1000001, // Note: The ConfirmDialog component's z-index is being set to 1000001 in packages/components/src/confirm-dialog/styles.ts // because it uses emotion and not sass. We need it to render on top its parent popover. diff --git a/packages/edit-site/src/components/sidebar-edit-mode/page-panels/style.scss b/packages/edit-site/src/components/sidebar-edit-mode/page-panels/style.scss index b5e2423657bd7..aedcf5e46ca9e 100644 --- a/packages/edit-site/src/components/sidebar-edit-mode/page-panels/style.scss +++ b/packages/edit-site/src/components/sidebar-edit-mode/page-panels/style.scss @@ -1,3 +1,7 @@ +.edit-site-swap-template-modal { + z-index: z-index(".edit-site-swap-template-modal"); +} + .edit-site-page-panels__swap-template__confirm-modal__actions { margin-top: $grid-unit-30; } diff --git a/packages/edit-site/src/components/sidebar-edit-mode/page-panels/swap-template-button.js b/packages/edit-site/src/components/sidebar-edit-mode/page-panels/swap-template-button.js index 1bbd3faa45ddd..c241eb43d8cb4 100644 --- a/packages/edit-site/src/components/sidebar-edit-mode/page-panels/swap-template-button.js +++ b/packages/edit-site/src/components/sidebar-edit-mode/page-panels/swap-template-button.js @@ -46,6 +46,7 @@ export default function SwapTemplateButton( { onClick } ) {
From 32259b5a2df45b8972c3996ca498382668c036a5 Mon Sep 17 00:00:00 2001 From: ntsekouras Date: Mon, 11 Sep 2023 19:38:01 +0300 Subject: [PATCH 6/9] fix typo --- .../sidebar-edit-mode/page-panels/reset-default-template.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/edit-site/src/components/sidebar-edit-mode/page-panels/reset-default-template.js b/packages/edit-site/src/components/sidebar-edit-mode/page-panels/reset-default-template.js index b821cede53c13..bc61b82a8d005 100644 --- a/packages/edit-site/src/components/sidebar-edit-mode/page-panels/reset-default-template.js +++ b/packages/edit-site/src/components/sidebar-edit-mode/page-panels/reset-default-template.js @@ -20,7 +20,7 @@ export default function ResetDefaultTemplate( { onClick } ) { const currentTemplateSlug = useCurrentTemplateSlug(); const isPostsPage = useIsPostsPage(); const { postType, postId } = useEditedPostContext(); - const entitiy = useEntityRecord( 'postType', postType, postId ); + const entity = useEntityRecord( 'postType', postType, postId ); const { setPage } = useDispatch( editSiteStore ); // The default template in a post is indicated by an empty string. if ( ! currentTemplateSlug || isPostsPage ) { @@ -30,7 +30,7 @@ export default function ResetDefaultTemplate( { onClick } ) { { - entitiy.edit( { template: '' }, { undoIgnore: true } ); + entity.edit( { template: '' }, { undoIgnore: true } ); onClick(); await setPage( { context: { postType, postId }, From 21e5769eec9652c81a70007d89dfccfb5fe74a69 Mon Sep 17 00:00:00 2001 From: ntsekouras Date: Tue, 12 Sep 2023 10:10:58 +0300 Subject: [PATCH 7/9] add + update tests --- test/e2e/specs/site-editor/pages.spec.js | 109 ++++++++++++++++++----- 1 file changed, 89 insertions(+), 20 deletions(-) diff --git a/test/e2e/specs/site-editor/pages.spec.js b/test/e2e/specs/site-editor/pages.spec.js index 1c0cb8c4b6968..926ff4606f8a2 100644 --- a/test/e2e/specs/site-editor/pages.spec.js +++ b/test/e2e/specs/site-editor/pages.spec.js @@ -3,33 +3,48 @@ */ const { test, expect } = require( '@wordpress/e2e-test-utils-playwright' ); +async function draftNewPage( page ) { + await page.getByRole( 'button', { name: 'Pages' } ).click(); + await page.getByRole( 'button', { name: 'Draft a new page' } ).click(); + await page + .locator( 'role=dialog[name="Draft a new page"i]' ) + .locator( 'role=textbox[name="Page title"i]' ) + .fill( 'Test Page' ); + await page.keyboard.press( 'Enter' ); + await expect( + page.locator( + `role=button[name="Dismiss this notice"i] >> text='"Test Page" successfully created.'` + ) + ).toBeVisible(); +} + test.describe( 'Pages', () => { test.beforeAll( async ( { requestUtils } ) => { await requestUtils.activateTheme( 'emptytheme' ); + await Promise.all( [ + requestUtils.deleteAllTemplates( 'wp_template' ), + requestUtils.deleteAllPages(), + ] ); } ); test.afterAll( async ( { requestUtils } ) => { await requestUtils.activateTheme( 'twentytwentyone' ); + await Promise.all( [ + requestUtils.deleteAllTemplates( 'wp_template' ), + requestUtils.deleteAllPages(), + ] ); } ); - test.beforeEach( async ( { admin } ) => { + test.beforeEach( async ( { requestUtils, admin } ) => { + await Promise.all( [ + requestUtils.deleteAllTemplates( 'wp_template' ), + requestUtils.deleteAllPages(), + ] ); await admin.visitSiteEditor(); } ); test( 'create a new page', async ( { page, editor } ) => { - // Draft a new page. - await page.getByRole( 'button', { name: 'Pages' } ).click(); - await page.getByRole( 'button', { name: 'Draft a new page' } ).click(); - await page - .locator( 'role=dialog[name="Draft a new page"i]' ) - .locator( 'role=textbox[name="Page title"i]' ) - .fill( 'Test Page' ); - await page.keyboard.press( 'Enter' ); - await expect( - page.locator( - `role=button[name="Dismiss this notice"i] >> text='"Test Page" successfully created.'` - ) - ).toBeVisible(); + await draftNewPage( page ); // Insert into Page Content using default block. await editor.canvas @@ -79,15 +94,11 @@ test.describe( 'Pages', () => { // Switch to template editing focus. await editor.openDocumentSettingsSidebar(); - await expect( - page.locator( - '.edit-site-page-panels__edit-template-preview iframe' - ) - ).toBeVisible(); await page .getByRole( 'region', { name: 'Editor settings' } ) - .getByRole( 'button', { name: 'Edit template' } ) + .getByRole( 'button', { name: 'Template options' } ) .click(); + await page.getByRole( 'button', { name: 'Edit template' } ).click(); await expect( editor.canvas.getByRole( 'document', { name: 'Block: Content', @@ -125,4 +136,62 @@ test.describe( 'Pages', () => { ) ).toBeVisible(); } ); + test( 'swap template and reset to default', async ( { + admin, + page, + editor, + } ) => { + // Create a custom template first. + const templateName = 'demo'; + await page.getByRole( 'button', { name: 'Templates' } ).click(); + await page.getByRole( 'button', { name: 'Add New Template' } ).click(); + await page + .getByRole( 'button', { + name: 'A custom template can be manually applied to any post or page.', + } ) + .click(); + // Fill the template title and submit. + const newTemplateDialog = page.locator( + 'role=dialog[name="Create custom template"i]' + ); + const templateNameInput = newTemplateDialog.locator( + 'role=textbox[name="Name"i]' + ); + await templateNameInput.fill( templateName ); + await page.keyboard.press( 'Enter' ); + await page + .locator( '.block-editor-block-patterns-list__list-item' ) + .click(); + await editor.saveSiteEditorEntities(); + await admin.visitSiteEditor(); + + // Create new page that has the default template so as to swap it. + await draftNewPage( page ); + await editor.openDocumentSettingsSidebar(); + const templateOptionsButton = page + .getByRole( 'region', { name: 'Editor settings' } ) + .getByRole( 'button', { name: 'Template options' } ); + await expect( templateOptionsButton ).toHaveText( 'Single Entries' ); + await templateOptionsButton.click(); + await page + .getByRole( 'menu', { name: 'Template options' } ) + .getByText( 'Swap template' ) + .click(); + await page + .locator( '.block-editor-block-patterns-list__item-title', { + name: 'demo', + } ) + .click(); + await expect( templateOptionsButton ).toHaveText( 'demo' ); + await editor.saveSiteEditorEntities(); + + // Now reset, and apply the default template back. + await templateOptionsButton.click(); + const resetButton = page + .getByRole( 'menu', { name: 'Template options' } ) + .getByText( 'Reset' ); + await expect( resetButton ).toBeVisible(); + await resetButton.click(); + await expect( templateOptionsButton ).toHaveText( 'Single Entries' ); + } ); } ); From 6beefed89839f7aceacc6d85ddcf20074b1e70f7 Mon Sep 17 00:00:00 2001 From: ntsekouras Date: Tue, 12 Sep 2023 12:18:45 +0300 Subject: [PATCH 8/9] fix suggestions per template's `postTypes` and test --- .../sidebar-edit-mode/page-panels/hooks.js | 1 + test/e2e/specs/site-editor/pages.spec.js | 29 +++++++++++++++---- .../emptytheme/templates/custom-template.html | 3 ++ test/emptytheme/theme.json | 7 +++++ 4 files changed, 35 insertions(+), 5 deletions(-) create mode 100644 test/emptytheme/templates/custom-template.html diff --git a/packages/edit-site/src/components/sidebar-edit-mode/page-panels/hooks.js b/packages/edit-site/src/components/sidebar-edit-mode/page-panels/hooks.js index fe28e19eda2b0..3000d21ab1366 100644 --- a/packages/edit-site/src/components/sidebar-edit-mode/page-panels/hooks.js +++ b/packages/edit-site/src/components/sidebar-edit-mode/page-panels/hooks.js @@ -33,6 +33,7 @@ function useTemplates() { ( select ) => select( coreStore ).getEntityRecords( 'postType', 'wp_template', { per_page: -1, + post_type: 'page', } ), [] ); diff --git a/test/e2e/specs/site-editor/pages.spec.js b/test/e2e/specs/site-editor/pages.spec.js index 926ff4606f8a2..e6a0b7f266cbf 100644 --- a/test/e2e/specs/site-editor/pages.spec.js +++ b/test/e2e/specs/site-editor/pages.spec.js @@ -177,11 +177,12 @@ test.describe( 'Pages', () => { .getByRole( 'menu', { name: 'Template options' } ) .getByText( 'Swap template' ) .click(); - await page - .locator( '.block-editor-block-patterns-list__item-title', { - name: 'demo', - } ) - .click(); + const templateItem = page.locator( + '.block-editor-block-patterns-list__item-title' + ); + // Empty theme's custom template with `postTypes: ['post']`, should not be suggested. + await expect( templateItem ).toHaveCount( 1 ); + await templateItem.click(); await expect( templateOptionsButton ).toHaveText( 'demo' ); await editor.saveSiteEditorEntities(); @@ -194,4 +195,22 @@ test.describe( 'Pages', () => { await resetButton.click(); await expect( templateOptionsButton ).toHaveText( 'Single Entries' ); } ); + test( 'swap template options should respect the declared `postTypes`', async ( { + page, + editor, + } ) => { + await draftNewPage( page ); + await editor.openDocumentSettingsSidebar(); + const templateOptionsButton = page + .getByRole( 'region', { name: 'Editor settings' } ) + .getByRole( 'button', { name: 'Template options' } ); + await templateOptionsButton.click(); + // Empty theme has only one custom template with `postTypes: ['post']`, + // so it should not be suggested. + await expect( + page + .getByRole( 'menu', { name: 'Template options' } ) + .getByText( 'Swap template' ) + ).toHaveCount( 0 ); + } ); } ); diff --git a/test/emptytheme/templates/custom-template.html b/test/emptytheme/templates/custom-template.html new file mode 100644 index 0000000000000..e4e8c11a39ef6 --- /dev/null +++ b/test/emptytheme/templates/custom-template.html @@ -0,0 +1,3 @@ + +

Custom template for Posts

+ diff --git a/test/emptytheme/theme.json b/test/emptytheme/theme.json index b28e6c9f274b2..d95ed844e6b1c 100644 --- a/test/emptytheme/theme.json +++ b/test/emptytheme/theme.json @@ -8,5 +8,12 @@ "wideSize": "1100px" } }, + "customTemplates": [ + { + "name": "custom-template", + "title": "Custom", + "postTypes": [ "post" ] + } + ], "patterns": [ "short-text-surrounded-by-round-images", "partner-logos" ] } From 13cfba09ad6b7064d927b5f22901cee18c636c97 Mon Sep 17 00:00:00 2001 From: ntsekouras Date: Tue, 12 Sep 2023 13:05:10 +0300 Subject: [PATCH 9/9] change order of calling in `onTemplateSelect` --- .../sidebar-edit-mode/page-panels/swap-template-button.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/edit-site/src/components/sidebar-edit-mode/page-panels/swap-template-button.js b/packages/edit-site/src/components/sidebar-edit-mode/page-panels/swap-template-button.js index c241eb43d8cb4..fee4f22a3ae2b 100644 --- a/packages/edit-site/src/components/sidebar-edit-mode/page-panels/swap-template-button.js +++ b/packages/edit-site/src/components/sidebar-edit-mode/page-panels/swap-template-button.js @@ -31,11 +31,11 @@ export default function SwapTemplateButton( { onClick } ) { } const onTemplateSelect = async ( template ) => { entitiy.edit( { template: template.name }, { undoIgnore: true } ); - onClose(); - onClick(); await setPage( { context: { postType, postId }, } ); + onClose(); // Close the template suggestions modal first. + onClick(); }; return ( <>