diff --git a/packages/edit-site/src/components/page-templates/hooks.js b/packages/edit-site/src/components/page-templates/hooks.js index 3cf2d78a59ee99..3b4e5e445caaa8 100644 --- a/packages/edit-site/src/components/page-templates/hooks.js +++ b/packages/edit-site/src/components/page-templates/hooks.js @@ -98,3 +98,31 @@ export function useAddedBy( postType, postId ) { [ postType, postId ] ); } + +/** + * Returns the template part title. + * + * @param {?string} slug The template part slug. + * @return {?string} The template part title. + */ +export function useTemplatePartTitle( slug ) { + return useSelect( + ( select ) => { + const { getEntityRecord, getCurrentTheme } = select( coreStore ); + const theme = getCurrentTheme()?.stylesheet; + if ( ! theme ) { + return; + } + const templatePart = getEntityRecord( + 'postType', + 'wp_template_part', + `${ theme }//${ slug }` + ); + if ( ! templatePart ) { + return; + } + return templatePart.title?.rendered; + }, + [ slug ] + ); +} diff --git a/packages/edit-site/src/components/page-templates/index.js b/packages/edit-site/src/components/page-templates/index.js index e0e04e0f5da924..971d4466340624 100644 --- a/packages/edit-site/src/components/page-templates/index.js +++ b/packages/edit-site/src/components/page-templates/index.js @@ -1,7 +1,7 @@ /** * WordPress dependencies */ -import { __ } from '@wordpress/i18n'; +import { __, sprintf } from '@wordpress/i18n'; import { useState, useMemo, useCallback, useEffect } from '@wordpress/element'; import { privateApis as corePrivateApis } from '@wordpress/core-data'; import { DataViews, filterSortAndPaginate } from '@wordpress/dataviews'; @@ -27,7 +27,10 @@ import { useEditPostAction } from '../dataviews-actions'; import { authorField, descriptionField, previewField } from './fields'; import { useEvent } from '@wordpress/compose'; -const { usePostActions } = unlock( editorPrivateApis ); +import { useTemplatePartTitle } from './hooks'; + +const { usePostActions, useTemplatesFilteredByTemplatePart } = + unlock( editorPrivateApis ); const { useHistory, useLocation } = unlock( routerPrivateApis ); const { useEntityRecordsWithPermissions } = unlock( corePrivateApis ); @@ -71,7 +74,7 @@ const DEFAULT_VIEW = { export default function PageTemplates() { const { path, query } = useLocation(); - const { activeView = 'all', layout, postId } = query; + const { activeView = 'all', layout, postId, usingTemplatePart } = query; const [ selection, setSelection ] = useState( [ postId ] ); const defaultView = useMemo( () => { @@ -119,10 +122,17 @@ export default function PageTemplates() { } ) ); }, [ setView, activeView ] ); - const { records, isResolving: isLoadingData } = + const { records: unfilteredRecords, isResolving: isLoadingData } = useEntityRecordsWithPermissions( 'postType', TEMPLATE_POST_TYPE, { per_page: -1, } ); + const records = useTemplatesFilteredByTemplatePart( + usingTemplatePart, + unfilteredRecords + ); + + const templatePartTitle = useTemplatePartTitle( usingTemplatePart ); + const history = useHistory(); const onChangeSelection = useCallback( ( items ) => { @@ -189,12 +199,19 @@ export default function PageTemplates() { ); } } ); - + let title = __( 'Templates' ); + if ( usingTemplatePart && templatePartTitle ) { + /* translators: %s: template part title */ + title = sprintf( + 'Templates using %s template part.', + templatePartTitle + ); + } return ( } + title={ title } + actions={ usingTemplatePart ? undefined : } > + { createInterpolateElement( + sprintf( + /* translators: 1: number of templates. */ + _n( + 'Used by %1$s template.', + 'Used by %1$s templates.', + filteredTemplates.length + ), + filteredTemplates.length + ), + { + Link: ( + + ), + } + ) } + + ); +} + +export default function PostUsedByPanel() { + const { postType, slug } = useSelect( ( select ) => { + select( editorStore ).getCurrentPostType(); + return { + postType: select( editorStore ).getCurrentPostType(), + slug: select( editorStore ).getCurrentPostAttribute( 'slug' ), + }; + }, [] ); + if ( postType !== TEMPLATE_PART_POST_TYPE ) { + return null; + } + return ( +
+ +
+ ); +} diff --git a/packages/editor/src/components/sidebar/post-summary.js b/packages/editor/src/components/sidebar/post-summary.js index 3539f7ba964ec7..b1ca20e661e026 100644 --- a/packages/editor/src/components/sidebar/post-summary.js +++ b/packages/editor/src/components/sidebar/post-summary.js @@ -29,6 +29,7 @@ import SiteDiscussion from '../site-discussion'; import { store as editorStore } from '../../store'; import { PrivatePostLastRevision } from '../post-last-revision'; import PostTrash from '../post-trash'; +import PostUsedByPanel from '../post-used-by-panel'; /** * Module Constants @@ -70,6 +71,7 @@ export default function PostSummary( { onActionPerformed } ) { + { ! isRemovedPostStatusPanel && ( diff --git a/packages/editor/src/private-apis.js b/packages/editor/src/private-apis.js index 11083eb6ab8a45..283cc3fd774707 100644 --- a/packages/editor/src/private-apis.js +++ b/packages/editor/src/private-apis.js @@ -26,6 +26,7 @@ import { import { CreateTemplatePartModal } from '@wordpress/fields'; import { registerCoreBlockBindingsSources } from './bindings/api'; import { getTemplateInfo } from './utils/get-template-info'; +import useTemplatesFilteredByTemplatePart from './utils/use-templates-filtered-by-template-part'; const { store: interfaceStore, ...remainingInterfaceApis } = interfaceApis; @@ -51,4 +52,5 @@ lock( privateApis, { // This is a temporary private API while we're updating the site editor to use EditorProvider. interfaceStore, ...remainingInterfaceApis, + useTemplatesFilteredByTemplatePart, } ); diff --git a/packages/editor/src/utils/use-templates-filtered-by-template-part.js b/packages/editor/src/utils/use-templates-filtered-by-template-part.js new file mode 100644 index 00000000000000..0dece943a6761f --- /dev/null +++ b/packages/editor/src/utils/use-templates-filtered-by-template-part.js @@ -0,0 +1,60 @@ +/** + * WordPress dependencies + */ +import { useEffect, useState } from '@wordpress/element'; +import { parse } from '@wordpress/blocks'; + +function findBlock( blocks, isMatch ) { + if ( ! Array.isArray( blocks ) ) { + return; + } + for ( const block of blocks ) { + if ( isMatch( block ) ) { + return block; + } + const childResult = findBlock( block.innerBlocks, isMatch ); + if ( childResult ) { + return childResult; + } + } +} + +export default function useTemplatesFilteredByTemplatePart( + templatePartSlug, + templates +) { + const [ result, setResult ] = useState( [] ); + // We are using a timeout and an effect just to make sure + // the parsing of templates is done asynchronously and does not blocks + // the rendering of the page. + useEffect( () => { + if ( ! templatePartSlug || ! templates ) { + return; + } + const timeoutId = setTimeout( () => { + setResult( + templates.filter( ( template ) => { + if ( template?.content?.raw ) { + const blocks = parse( template.content.raw ); + return !! findBlock( blocks, ( block ) => { + return ( + block.name === 'core/template-part' && + block.attributes.slug === templatePartSlug + ); + } ); + } + return false; + } ) + ); + }, 0 ); + return () => { + if ( timeoutId ) { + clearTimeout( timeoutId ); + } + }; + } ); + if ( ! templatePartSlug || ! templates ) { + return templates; + } + return result; +}