diff --git a/packages/edit-site/src/components/block-editor/block-editor-provider/default-block-editor-provider.js b/packages/edit-site/src/components/block-editor/block-editor-provider/default-block-editor-provider.js
new file mode 100644
index 00000000000000..9ffee1ca687228
--- /dev/null
+++ b/packages/edit-site/src/components/block-editor/block-editor-provider/default-block-editor-provider.js
@@ -0,0 +1,70 @@
+/**
+ * WordPress dependencies
+ */
+import { useEntityBlockEditor } from '@wordpress/core-data';
+import { privateApis as blockEditorPrivateApis } from '@wordpress/block-editor';
+import { useSelect } from '@wordpress/data';
+
+/**
+ * Internal dependencies
+ */
+import { store as editSiteStore } from '../../../store';
+import { unlock } from '../../../lock-unlock';
+import useSiteEditorSettings from '../use-site-editor-settings';
+import usePageContentBlocks from './use-page-content-blocks';
+
+const { ExperimentalBlockEditorProvider } = unlock( blockEditorPrivateApis );
+
+const noop = () => {};
+
+/**
+ * The default block editor provider for the site editor. Typically used when
+ * the post type is `'wp_template_part'` or `'wp_template'` and allows editing
+ * of the template and its nested entities.
+ *
+ * If the page content focus type is `'hideTemplate'`, the provider will provide
+ * a set of page content blocks wrapped in a container that, together,
+ * mimic the look and feel of the post editor and
+ * allow editing of the page content only.
+ *
+ * @param {Object} props
+ * @param {WPElement} props.children
+ */
+export default function DefaultBlockEditorProvider( { children } ) {
+ const settings = useSiteEditorSettings();
+
+ const { templateType, isTemplateHidden } = useSelect( ( select ) => {
+ const { getEditedPostType } = select( editSiteStore );
+ const { getPageContentFocusType, getCanvasMode } = unlock(
+ select( editSiteStore )
+ );
+ return {
+ templateType: getEditedPostType(),
+ isTemplateHidden:
+ getCanvasMode() === 'edit' &&
+ getPageContentFocusType() === 'hideTemplate',
+ canvasMode: unlock( select( editSiteStore ) ).getCanvasMode(),
+ };
+ }, [] );
+
+ const [ blocks, onInput, onChange ] = useEntityBlockEditor(
+ 'postType',
+ templateType
+ );
+ const pageContentBlock = usePageContentBlocks( blocks, isTemplateHidden );
+ return (
+
+ { children }
+
+ );
+}
diff --git a/packages/edit-site/src/components/block-editor/block-editor-provider/index.js b/packages/edit-site/src/components/block-editor/block-editor-provider/index.js
new file mode 100644
index 00000000000000..36c7c72066a88d
--- /dev/null
+++ b/packages/edit-site/src/components/block-editor/block-editor-provider/index.js
@@ -0,0 +1,28 @@
+/**
+ * WordPress dependencies
+ */
+import { useSelect } from '@wordpress/data';
+
+/**
+ * Internal dependencies
+ */
+import { store as editSiteStore } from '../../../store';
+import DefaultBlockEditorProvider from './default-block-editor-provider';
+import NavigationBlockEditorProvider from './navigation-block-editor-provider';
+
+export default function BlockEditorProvider( { children } ) {
+ const entityType = useSelect(
+ ( select ) => select( editSiteStore ).getEditedPostType(),
+ []
+ );
+ if ( entityType === 'wp_navigation' ) {
+ return (
+
+ { children }
+
+ );
+ }
+ return (
+ { children }
+ );
+}
diff --git a/packages/edit-site/src/components/block-editor/providers/navigation-block-editor-provider.js b/packages/edit-site/src/components/block-editor/block-editor-provider/navigation-block-editor-provider.js
similarity index 100%
rename from packages/edit-site/src/components/block-editor/providers/navigation-block-editor-provider.js
rename to packages/edit-site/src/components/block-editor/block-editor-provider/navigation-block-editor-provider.js
diff --git a/packages/edit-site/src/components/block-editor/block-editor-provider/test/use-page-content-blocks.js b/packages/edit-site/src/components/block-editor/block-editor-provider/test/use-page-content-blocks.js
new file mode 100644
index 00000000000000..533ed0f8f3d100
--- /dev/null
+++ b/packages/edit-site/src/components/block-editor/block-editor-provider/test/use-page-content-blocks.js
@@ -0,0 +1,87 @@
+/**
+ * External dependencies
+ */
+import { renderHook } from '@testing-library/react';
+/**
+ * WordPress dependencies
+ */
+import { createBlock } from '@wordpress/blocks';
+
+/**
+ * Internal dependencies
+ */
+import usePageContentBlocks from '../use-page-content-blocks';
+
+jest.mock( '@wordpress/blocks', () => {
+ return {
+ __esModule: true,
+ ...jest.requireActual( '@wordpress/blocks' ),
+ createBlock( name, attributes = {}, innerBlocks = [] ) {
+ return {
+ name,
+ attributes,
+ innerBlocks,
+ };
+ },
+ };
+} );
+
+describe( 'usePageContentBlocks', () => {
+ const blocksList = [
+ createBlock( 'core/group', {}, [
+ createBlock( 'core/post-title' ),
+ createBlock( 'core/post-featured-image' ),
+ createBlock( 'core/query', {}, [
+ createBlock( 'core/post-title' ),
+ createBlock( 'core/post-featured-image' ),
+ createBlock( 'core/post-content' ),
+ ] ),
+ createBlock( 'core/post-content' ),
+ ] ),
+ createBlock( 'core/query' ),
+ createBlock( 'core/paragraph' ),
+ createBlock( 'core/post-content' ),
+ ];
+ it( 'should return empty array if `isPageContentFocused` is `false`', () => {
+ const { result } = renderHook( () =>
+ usePageContentBlocks( blocksList, false )
+ );
+ expect( result.current ).toEqual( [] );
+ } );
+ it( 'should return empty array if `blocks` is undefined', () => {
+ const { result } = renderHook( () =>
+ usePageContentBlocks( undefined, true )
+ );
+ expect( result.current ).toEqual( [] );
+ } );
+ it( 'should return empty array if `blocks` is an empty array', () => {
+ const { result } = renderHook( () => usePageContentBlocks( [], true ) );
+ expect( result.current ).toEqual( [] );
+ } );
+ it( 'should return new block list', () => {
+ const { result } = renderHook( () =>
+ usePageContentBlocks( blocksList, true )
+ );
+ expect( result.current ).toEqual( [
+ {
+ name: 'core/group',
+ attributes: {
+ layout: { type: 'constrained' },
+ style: {
+ spacing: {
+ margin: {
+ top: '4em', // Mimics the post editor.
+ },
+ },
+ },
+ },
+ innerBlocks: [
+ createBlock( 'core/post-title' ),
+ createBlock( 'core/post-featured-image' ),
+ createBlock( 'core/post-content' ),
+ createBlock( 'core/post-content' ),
+ ],
+ },
+ ] );
+ } );
+} );
diff --git a/packages/edit-site/src/components/block-editor/block-editor-provider/use-page-content-blocks.js b/packages/edit-site/src/components/block-editor/block-editor-provider/use-page-content-blocks.js
new file mode 100644
index 00000000000000..17e907113c5155
--- /dev/null
+++ b/packages/edit-site/src/components/block-editor/block-editor-provider/use-page-content-blocks.js
@@ -0,0 +1,77 @@
+/**
+ * WordPress dependencies
+ */
+import { useMemo } from '@wordpress/element';
+import { createBlock } from '@wordpress/blocks';
+
+/**
+ * Internal dependencies
+ */
+import { PAGE_CONTENT_BLOCK_TYPES } from '../../../utils/constants';
+
+/**
+ * Helper method to iterate through all blocks, recursing into allowed inner blocks.
+ * Returns a flattened object of transformed blocks.
+ *
+ * @param {Array} blocks Blocks to flatten.
+ * @param {Function} transform Transforming function to be applied to each block. If transform returns `undefined`, the block is skipped.
+ *
+ * @return {Array} Flattened object.
+ */
+function flattenBlocks( blocks, transform ) {
+ const result = [];
+ for ( let i = 0; i < blocks.length; i++ ) {
+ // Since the Query Block could contain PAGE_CONTENT_BLOCK_TYPES block types,
+ // we skip it because we only want to render stand-alone page content blocks in the block list.
+ if ( [ 'core/query' ].includes( blocks[ i ].name ) ) {
+ continue;
+ }
+ const transformedBlock = transform( blocks[ i ] );
+ if ( transformedBlock ) {
+ result.push( transformedBlock );
+ }
+ result.push( ...flattenBlocks( blocks[ i ].innerBlocks, transform ) );
+ }
+
+ return result;
+}
+
+/**
+ * Returns a memoized array of blocks that contain only page content blocks,
+ * surrounded by a group block to mimic the post editor.
+ *
+ * @param {Array} blocks Block list.
+ * @param {boolean} isPageContentFocused Whether the page content has focus. If `true` return page content blocks. Default `false`.
+ *
+ * @return {Array} Page content blocks.
+ */
+export default function usePageContentBlocks(
+ blocks,
+ isPageContentFocused = false
+) {
+ return useMemo( () => {
+ if ( ! isPageContentFocused || ! blocks || ! blocks.length ) {
+ return [];
+ }
+ return [
+ createBlock(
+ 'core/group',
+ {
+ layout: { type: 'constrained' },
+ style: {
+ spacing: {
+ margin: {
+ top: '4em', // Mimics the post editor.
+ },
+ },
+ },
+ },
+ flattenBlocks( blocks, ( block ) => {
+ if ( PAGE_CONTENT_BLOCK_TYPES[ block.name ] ) {
+ return createBlock( block.name );
+ }
+ } )
+ ),
+ ];
+ }, [ blocks, isPageContentFocused ] );
+}
diff --git a/packages/edit-site/src/components/block-editor/get-block-editor-provider.js b/packages/edit-site/src/components/block-editor/get-block-editor-provider.js
deleted file mode 100644
index df8185605f13aa..00000000000000
--- a/packages/edit-site/src/components/block-editor/get-block-editor-provider.js
+++ /dev/null
@@ -1,29 +0,0 @@
-/**
- * Internal dependencies
- */
-import DefaultBlockEditor from './providers/default-block-editor-provider';
-import NavigationBlockEditor from './providers/navigation-block-editor-provider';
-
-/**
- * Factory to isolate choosing the appropriate block editor
- * component to handle a given entity type.
- *
- * @param {string} entityType the entity type being edited
- * @return {JSX.Element} the block editor component to use.
- */
-export default function getBlockEditorProvider( entityType ) {
- let Provider = null;
-
- switch ( entityType ) {
- case 'wp_navigation':
- Provider = NavigationBlockEditor;
- break;
- case 'wp_template':
- case 'wp_template_part':
- default:
- Provider = DefaultBlockEditor;
- break;
- }
-
- return Provider;
-}
diff --git a/packages/edit-site/src/components/block-editor/index.js b/packages/edit-site/src/components/block-editor/index.js
index 5bfcdb012d1f7e..2c635ff860a5bd 100644
--- a/packages/edit-site/src/components/block-editor/index.js
+++ b/packages/edit-site/src/components/block-editor/index.js
@@ -1,7 +1,6 @@
/**
* WordPress dependencies
*/
-import { useSelect } from '@wordpress/data';
import { BlockInspector } from '@wordpress/block-editor';
import { privateApis as editPatternsPrivateApis } from '@wordpress/patterns';
@@ -10,31 +9,19 @@ import { privateApis as editPatternsPrivateApis } from '@wordpress/patterns';
*/
import TemplatePartConverter from '../template-part-converter';
import { SidebarInspectorFill } from '../sidebar-edit-mode';
-import { store as editSiteStore } from '../../store';
import SiteEditorCanvas from './site-editor-canvas';
-import getBlockEditorProvider from './get-block-editor-provider';
+import BlockEditorProvider from './block-editor-provider';
import { unlock } from '../../lock-unlock';
const { PatternsMenuItems } = unlock( editPatternsPrivateApis );
export default function BlockEditor() {
- const entityType = useSelect(
- ( select ) => select( editSiteStore ).getEditedPostType(),
- []
- );
-
- // Choose the provider based on the entity type currently
- // being edited.
- const BlockEditorProvider = getBlockEditorProvider( entityType );
-
return (
-
-
);
diff --git a/packages/edit-site/src/components/block-editor/providers/default-block-editor-provider.js b/packages/edit-site/src/components/block-editor/providers/default-block-editor-provider.js
deleted file mode 100644
index 2ee0ae467f8d65..00000000000000
--- a/packages/edit-site/src/components/block-editor/providers/default-block-editor-provider.js
+++ /dev/null
@@ -1,44 +0,0 @@
-/**
- * WordPress dependencies
- */
-import { useEntityBlockEditor } from '@wordpress/core-data';
-import { privateApis as blockEditorPrivateApis } from '@wordpress/block-editor';
-import { useSelect } from '@wordpress/data';
-
-/**
- * Internal dependencies
- */
-import { store as editSiteStore } from '../../../store';
-import { unlock } from '../../../lock-unlock';
-import useSiteEditorSettings from '../use-site-editor-settings';
-
-const { ExperimentalBlockEditorProvider } = unlock( blockEditorPrivateApis );
-
-export default function DefaultBlockEditorProvider( { children } ) {
- const settings = useSiteEditorSettings();
-
- const { templateType } = useSelect( ( select ) => {
- const { getEditedPostType } = unlock( select( editSiteStore ) );
-
- return {
- templateType: getEditedPostType(),
- };
- }, [] );
-
- const [ blocks, onInput, onChange ] = useEntityBlockEditor(
- 'postType',
- templateType
- );
-
- return (
-
- { children }
-
- );
-}
diff --git a/packages/edit-site/src/components/page-content-focus-manager/disable-non-page-content-blocks.js b/packages/edit-site/src/components/page-content-focus-manager/disable-non-page-content-blocks.js
index 9d28c01164f29b..f3e021ba885244 100644
--- a/packages/edit-site/src/components/page-content-focus-manager/disable-non-page-content-blocks.js
+++ b/packages/edit-site/src/components/page-content-focus-manager/disable-non-page-content-blocks.js
@@ -9,12 +9,7 @@ import { useEffect } from '@wordpress/element';
/**
* Internal dependencies
*/
-
-const PAGE_CONTENT_BLOCK_TYPES = [
- 'core/post-title',
- 'core/post-featured-image',
- 'core/post-content',
-];
+import { PAGE_CONTENT_BLOCK_TYPES } from '../../utils/constants';
/**
* Component that when rendered, makes it so that the site editor allows only
@@ -48,8 +43,7 @@ const withDisableNonPageContentBlocks = createHigherOrderComponent(
( BlockEdit ) => ( props ) => {
const isDescendentOfQueryLoop = props.context.queryId !== undefined;
const isPageContent =
- PAGE_CONTENT_BLOCK_TYPES.includes( props.name ) &&
- ! isDescendentOfQueryLoop;
+ PAGE_CONTENT_BLOCK_TYPES[ props.name ] && ! isDescendentOfQueryLoop;
const mode = isPageContent ? 'contentOnly' : undefined;
useBlockEditingMode( mode );
return ;
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 2295ee12f45049..9e0b9e37c33ff6 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
@@ -12,6 +12,7 @@ import {
} from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import { store as coreStore } from '@wordpress/core-data';
+import { check } from '@wordpress/icons';
/**
* Internal dependencies
@@ -19,6 +20,7 @@ import { store as coreStore } from '@wordpress/core-data';
import { store as editSiteStore } from '../../../store';
import SwapTemplateButton from './swap-template-button';
import ResetDefaultTemplate from './reset-default-template';
+import { unlock } from '../../../lock-unlock';
const POPOVER_PROPS = {
className: 'edit-site-page-panels-edit-template__dropdown',
@@ -26,28 +28,40 @@ const POPOVER_PROPS = {
};
export default function EditTemplate() {
- const { hasResolved, template } = useSelect( ( select ) => {
- const { getEditedPostContext, getEditedPostType, getEditedPostId } =
- select( editSiteStore );
- const { getEditedEntityRecord, hasFinishedResolution } =
- select( coreStore );
- const _context = getEditedPostContext();
- const queryArgs = [
- 'postType',
- getEditedPostType(),
- getEditedPostId(),
- ];
- return {
- context: _context,
- hasResolved: hasFinishedResolution(
- 'getEditedEntityRecord',
- queryArgs
- ),
- template: getEditedEntityRecord( ...queryArgs ),
- };
- }, [] );
+ const { hasResolved, template, isTemplateHidden } = useSelect(
+ ( select ) => {
+ const { getEditedPostContext, getEditedPostType, getEditedPostId } =
+ select( editSiteStore );
+ const { getCanvasMode, getPageContentFocusType } = unlock(
+ select( editSiteStore )
+ );
+ const { getEditedEntityRecord, hasFinishedResolution } =
+ select( coreStore );
+ const _context = getEditedPostContext();
+ const queryArgs = [
+ 'postType',
+ getEditedPostType(),
+ getEditedPostId(),
+ ];
+ return {
+ context: _context,
+ hasResolved: hasFinishedResolution(
+ 'getEditedEntityRecord',
+ queryArgs
+ ),
+ template: getEditedEntityRecord( ...queryArgs ),
+ isTemplateHidden:
+ getCanvasMode() === 'edit' &&
+ getPageContentFocusType() === 'hideTemplate',
+ };
+ },
+ []
+ );
const { setHasPageContentFocus } = useDispatch( editSiteStore );
+ // Disable reason: `useDispatch` can't be called conditionally.
+ // eslint-disable-next-line @wordpress/no-unused-vars-before-return
+ const { setPageContentFocusType } = unlock( useDispatch( editSiteStore ) );
if ( ! hasResolved ) {
return null;
@@ -83,6 +97,20 @@ export default function EditTemplate() {
+
+
+
>
) }
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 aedcf5e46ca9ea..5501fe49e5876b 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
@@ -79,3 +79,9 @@
width: 30%;
}
}
+
+.edit-site-page-panels-edit-template__dropdown {
+ .components-popover__content {
+ min-width: 240px;
+ }
+}
diff --git a/packages/edit-site/src/store/private-actions.js b/packages/edit-site/src/store/private-actions.js
index f3dd4c10cec43e..3e2bfe2ee47b24 100644
--- a/packages/edit-site/src/store/private-actions.js
+++ b/packages/edit-site/src/store/private-actions.js
@@ -49,3 +49,22 @@ export const setEditorCanvasContainerView =
view,
} );
};
+
+/**
+ * Sets the type of page content focus. Can be one of:
+ *
+ * - `'disableTemplate'`: Disable the blocks belonging to the page's template.
+ * - `'hideTemplate'`: Hide the blocks belonging to the page's template.
+ *
+ * @param {'disableTemplate'|'hideTemplate'} pageContentFocusType The type of page content focus.
+ *
+ * @return {Object} Action object.
+ */
+export const setPageContentFocusType =
+ ( pageContentFocusType ) =>
+ ( { dispatch } ) => {
+ dispatch( {
+ type: 'SET_PAGE_CONTENT_FOCUS_TYPE',
+ pageContentFocusType,
+ } );
+ };
diff --git a/packages/edit-site/src/store/private-selectors.js b/packages/edit-site/src/store/private-selectors.js
index 1f1f6e999fdb29..0d4cf2b3eefdaa 100644
--- a/packages/edit-site/src/store/private-selectors.js
+++ b/packages/edit-site/src/store/private-selectors.js
@@ -1,3 +1,8 @@
+/**
+ * Internal dependencies
+ */
+import { hasPageContentFocus } from './selectors';
+
/**
* Returns the current canvas mode.
*
@@ -19,3 +24,20 @@ export function getCanvasMode( state ) {
export function getEditorCanvasContainerView( state ) {
return state.editorCanvasContainerView;
}
+
+/**
+ * Returns the type of the current page content focus, or null if there is no
+ * page content focus.
+ *
+ * Possible values are:
+ *
+ * - `'disableTemplate'`: Disable the blocks belonging to the page's template.
+ * - `'hideTemplate'`: Hide the blocks belonging to the page's template.
+ *
+ * @param {Object} state Global application state.
+ *
+ * @return {'disableTemplate'|'hideTemplate'|null} Type of the current page content focus.
+ */
+export function getPageContentFocusType( state ) {
+ return hasPageContentFocus( state ) ? state.pageContentFocusType : null;
+}
diff --git a/packages/edit-site/src/store/reducer.js b/packages/edit-site/src/store/reducer.js
index 4b4689e26c561e..e99c6dda1fc1d0 100644
--- a/packages/edit-site/src/store/reducer.js
+++ b/packages/edit-site/src/store/reducer.js
@@ -177,6 +177,23 @@ export function hasPageContentFocus( state = false, action ) {
return state;
}
+/**
+ * Reducer used to track the type of page content focus.
+ *
+ * @param {string} state Current state.
+ * @param {Object} action Dispatched action.
+ *
+ * @return {string} Updated state.
+ */
+export function pageContentFocusType( state = 'disableTemplate', action ) {
+ switch ( action.type ) {
+ case 'SET_PAGE_CONTENT_FOCUS_TYPE':
+ return action.pageContentFocusType;
+ }
+
+ return state;
+}
+
export default combineReducers( {
deviceType,
settings,
@@ -187,4 +204,5 @@ export default combineReducers( {
canvasMode,
editorCanvasContainerView,
hasPageContentFocus,
+ pageContentFocusType,
} );
diff --git a/packages/edit-site/src/store/test/reducer.js b/packages/edit-site/src/store/test/reducer.js
index d3816f6ac0ac2a..a5e47ec5bbbaf3 100644
--- a/packages/edit-site/src/store/test/reducer.js
+++ b/packages/edit-site/src/store/test/reducer.js
@@ -12,6 +12,7 @@ import {
blockInserterPanel,
listViewPanel,
hasPageContentFocus,
+ pageContentFocusType,
} from '../reducer';
import { setIsInserterOpened } from '../actions';
@@ -191,4 +192,21 @@ describe( 'state', () => {
).toBe( false );
} );
} );
+
+ describe( 'pageContentFocusType', () => {
+ it( 'defaults to disableTemplate', () => {
+ expect( pageContentFocusType( undefined, {} ) ).toBe(
+ 'disableTemplate'
+ );
+ } );
+
+ it( 'can be set', () => {
+ expect(
+ pageContentFocusType( 'disableTemplate', {
+ type: 'SET_PAGE_CONTENT_FOCUS_TYPE',
+ pageContentFocusType: 'enableTemplate',
+ } )
+ ).toBe( 'enableTemplate' );
+ } );
+ } );
} );
diff --git a/packages/edit-site/src/utils/constants.js b/packages/edit-site/src/utils/constants.js
index d12c7f84cc356c..1dcbdc0cc6fa3b 100644
--- a/packages/edit-site/src/utils/constants.js
+++ b/packages/edit-site/src/utils/constants.js
@@ -32,6 +32,16 @@ export const FOCUSABLE_ENTITIES = [
PATTERN_TYPES.user,
];
+/**
+ * Block types that are considered to be page content. These are the only blocks
+ * editable when hasPageContentFocus() is true.
+ */
+export const PAGE_CONTENT_BLOCK_TYPES = {
+ 'core/post-title': true,
+ 'core/post-featured-image': true,
+ 'core/post-content': true,
+};
+
export const POST_TYPE_LABELS = {
[ TEMPLATE_POST_TYPE ]: __( 'Template' ),
[ TEMPLATE_PART_POST_TYPE ]: __( 'Template Part' ),