diff --git a/docs/reference-guides/data/data-core-editor.md b/docs/reference-guides/data/data-core-editor.md
index 4774934651b13..e8cbad2518c2e 100644
--- a/docs/reference-guides/data/data-core-editor.md
+++ b/docs/reference-guides/data/data-core-editor.md
@@ -256,6 +256,18 @@ _Returns_
- `string`: Post type.
+### getCurrentTemplateId
+
+Returns the template ID currently being rendered/edited
+
+_Parameters_
+
+- _state_ `Object`: Global application state.
+
+_Returns_
+
+- `string?`: Template ID.
+
### getEditedPostAttribute
Returns a single attribute of the post being edited, preferring the unsaved edit if one exists, but falling back to the attribute for the last known saved state of the post.
diff --git a/packages/edit-post/src/components/visual-editor/index.js b/packages/edit-post/src/components/visual-editor/index.js
index 25dcf941970ac..5b9290fff5137 100644
--- a/packages/edit-post/src/components/visual-editor/index.js
+++ b/packages/edit-post/src/components/visual-editor/index.js
@@ -6,24 +6,21 @@ import classnames from 'classnames';
/**
* WordPress dependencies
*/
-import { PostTitle, store as editorStore } from '@wordpress/editor';
import {
- BlockList,
+ store as editorStore,
+ privateApis as editorPrivateApis,
+} from '@wordpress/editor';
+import {
BlockTools,
- store as blockEditorStore,
__unstableUseTypewriter as useTypewriter,
- __unstableUseTypingObserver as useTypingObserver,
__experimentalUseResizeCanvas as useResizeCanvas,
- useSettings,
- __experimentalRecursionProvider as RecursionProvider,
privateApis as blockEditorPrivateApis,
} from '@wordpress/block-editor';
-import { useEffect, useRef, useMemo } from '@wordpress/element';
+import { useRef, useMemo, useEffect } from '@wordpress/element';
import { __unstableMotion as motion } from '@wordpress/components';
-import { useSelect } from '@wordpress/data';
+import { useSelect, useDispatch } from '@wordpress/data';
import { useMergeRefs } from '@wordpress/compose';
-import { parse, store as blocksStore } from '@wordpress/blocks';
-import { store as coreStore } from '@wordpress/core-data';
+import { store as blocksStore } from '@wordpress/blocks';
/**
* Internal dependencies
@@ -31,128 +28,46 @@ import { store as coreStore } from '@wordpress/core-data';
import { store as editPostStore } from '../../store';
import { unlock } from '../../lock-unlock';
-const {
- LayoutStyle,
- useLayoutClasses,
- useLayoutStyles,
- ExperimentalBlockCanvas: BlockCanvas,
-} = unlock( blockEditorPrivateApis );
+const { ExperimentalBlockCanvas: BlockCanvas } = unlock(
+ blockEditorPrivateApis
+);
+const { EditorCanvas } = unlock( editorPrivateApis );
const isGutenbergPlugin = process.env.IS_GUTENBERG_PLUGIN ? true : false;
-/**
- * Given an array of nested blocks, find the first Post Content
- * block inside it, recursing through any nesting levels,
- * and return its attributes.
- *
- * @param {Array} blocks A list of blocks.
- *
- * @return {Object | undefined} The Post Content block.
- */
-function getPostContentAttributes( blocks ) {
- for ( let i = 0; i < blocks.length; i++ ) {
- if ( blocks[ i ].name === 'core/post-content' ) {
- return blocks[ i ].attributes;
- }
- if ( blocks[ i ].innerBlocks.length ) {
- const nestedPostContent = getPostContentAttributes(
- blocks[ i ].innerBlocks
- );
-
- if ( nestedPostContent ) {
- return nestedPostContent;
- }
- }
- }
-}
-
-function checkForPostContentAtRootLevel( blocks ) {
- for ( let i = 0; i < blocks.length; i++ ) {
- if ( blocks[ i ].name === 'core/post-content' ) {
- return true;
- }
- }
- return false;
-}
-
export default function VisualEditor( { styles } ) {
const {
deviceType,
isWelcomeGuideVisible,
isTemplateMode,
- postContentAttributes,
- editedPostTemplate = {},
- wrapperBlockName,
- wrapperUniqueId,
isBlockBasedTheme,
hasV3BlocksOnly,
} = useSelect( ( select ) => {
const {
isFeatureActive,
isEditingTemplate,
- getEditedPostTemplate,
__experimentalGetPreviewDeviceType,
} = select( editPostStore );
- const { getCurrentPostId, getCurrentPostType, getEditorSettings } =
- select( editorStore );
+ const { getEditorSettings } = select( editorStore );
const { getBlockTypes } = select( blocksStore );
const _isTemplateMode = isEditingTemplate();
- const postTypeSlug = getCurrentPostType();
- let _wrapperBlockName;
-
- if ( postTypeSlug === 'wp_block' ) {
- _wrapperBlockName = 'core/block';
- } else if ( ! _isTemplateMode ) {
- _wrapperBlockName = 'core/post-content';
- }
-
const editorSettings = getEditorSettings();
- const supportsTemplateMode = editorSettings.supportsTemplateMode;
- const postType = select( coreStore ).getPostType( postTypeSlug );
- const canEditTemplate = select( coreStore ).canUser(
- 'create',
- 'templates'
- );
return {
deviceType: __experimentalGetPreviewDeviceType(),
isWelcomeGuideVisible: isFeatureActive( 'welcomeGuide' ),
isTemplateMode: _isTemplateMode,
- postContentAttributes: getEditorSettings().postContentAttributes,
- // Post template fetch returns a 404 on classic themes, which
- // messes with e2e tests, so check it's a block theme first.
- editedPostTemplate:
- postType?.viewable && supportsTemplateMode && canEditTemplate
- ? getEditedPostTemplate()
- : undefined,
- wrapperBlockName: _wrapperBlockName,
- wrapperUniqueId: getCurrentPostId(),
isBlockBasedTheme: editorSettings.__unstableIsBlockBasedTheme,
hasV3BlocksOnly: getBlockTypes().every( ( type ) => {
return type.apiVersion >= 3;
} ),
};
}, [] );
- const { isCleanNewPost } = useSelect( editorStore );
const hasMetaBoxes = useSelect(
( select ) => select( editPostStore ).hasMetaBoxes(),
[]
);
- const {
- hasRootPaddingAwareAlignments,
- isFocusMode,
- themeHasDisabledLayoutStyles,
- themeSupportsLayout,
- } = useSelect( ( select ) => {
- const _settings = select( blockEditorStore ).getSettings();
- return {
- themeHasDisabledLayoutStyles: _settings.disableLayoutStyles,
- themeSupportsLayout: _settings.supportsLayout,
- isFocusMode: _settings.focusMode,
- hasRootPaddingAwareAlignments:
- _settings.__experimentalFeatures?.useRootPaddingAwareAlignments,
- };
- }, [] );
+ const { setRenderingMode } = useDispatch( editorStore );
const desktopCanvasStyles = {
height: '100%',
width: '100%',
@@ -171,7 +86,6 @@ export default function VisualEditor( { styles } ) {
borderBottom: 0,
};
const resizedCanvasStyles = useResizeCanvas( deviceType, isTemplateMode );
- const [ globalLayoutSettings ] = useSettings( 'layout' );
const previewMode = 'is-' + deviceType.toLowerCase() + '-preview';
let animatedStyles = isTemplateMode
@@ -192,143 +106,19 @@ export default function VisualEditor( { styles } ) {
const ref = useRef();
const contentRef = useMergeRefs( [ ref, useTypewriter() ] );
- // fallbackLayout is used if there is no Post Content,
- // and for Post Title.
- const fallbackLayout = useMemo( () => {
- if ( isTemplateMode ) {
- return { type: 'default' };
- }
-
- if ( themeSupportsLayout ) {
- // We need to ensure support for wide and full alignments,
- // so we add the constrained type.
- return { ...globalLayoutSettings, type: 'constrained' };
- }
- // Set default layout for classic themes so all alignments are supported.
- return { type: 'default' };
- }, [ isTemplateMode, themeSupportsLayout, globalLayoutSettings ] );
-
- const newestPostContentAttributes = useMemo( () => {
- if ( ! editedPostTemplate?.content && ! editedPostTemplate?.blocks ) {
- return postContentAttributes;
- }
- // When in template editing mode, we can access the blocks directly.
- if ( editedPostTemplate?.blocks ) {
- return getPostContentAttributes( editedPostTemplate?.blocks );
- }
- // If there are no blocks, we have to parse the content string.
- // Best double-check it's a string otherwise the parse function gets unhappy.
- const parseableContent =
- typeof editedPostTemplate?.content === 'string'
- ? editedPostTemplate?.content
- : '';
-
- return getPostContentAttributes( parse( parseableContent ) ) || {};
- }, [
- editedPostTemplate?.content,
- editedPostTemplate?.blocks,
- postContentAttributes,
- ] );
-
- const hasPostContentAtRootLevel = useMemo( () => {
- if ( ! editedPostTemplate?.content && ! editedPostTemplate?.blocks ) {
- return false;
- }
- // When in template editing mode, we can access the blocks directly.
- if ( editedPostTemplate?.blocks ) {
- return checkForPostContentAtRootLevel( editedPostTemplate?.blocks );
- }
- // If there are no blocks, we have to parse the content string.
- // Best double-check it's a string otherwise the parse function gets unhappy.
- const parseableContent =
- typeof editedPostTemplate?.content === 'string'
- ? editedPostTemplate?.content
- : '';
-
- return (
- checkForPostContentAtRootLevel( parse( parseableContent ) ) || false
- );
- }, [ editedPostTemplate?.content, editedPostTemplate?.blocks ] );
-
- const { layout = {}, align = '' } = newestPostContentAttributes || {};
-
- const postContentLayoutClasses = useLayoutClasses(
- newestPostContentAttributes,
- 'core/post-content'
- );
-
- const blockListLayoutClass = classnames(
- {
- 'is-layout-flow': ! themeSupportsLayout,
- },
- themeSupportsLayout && postContentLayoutClasses,
- align && `align${ align }`
- );
-
- const postContentLayoutStyles = useLayoutStyles(
- newestPostContentAttributes,
- 'core/post-content',
- '.block-editor-block-list__layout.is-root-container'
- );
-
- // Update type for blocks using legacy layouts.
- const postContentLayout = useMemo( () => {
- return layout &&
- ( layout?.type === 'constrained' ||
- layout?.inherit ||
- layout?.contentSize ||
- layout?.wideSize )
- ? { ...globalLayoutSettings, ...layout, type: 'constrained' }
- : { ...globalLayoutSettings, ...layout, type: 'default' };
- }, [
- layout?.type,
- layout?.inherit,
- layout?.contentSize,
- layout?.wideSize,
- globalLayoutSettings,
- ] );
-
- // If there is a Post Content block we use its layout for the block list;
- // if not, this must be a classic theme, in which case we use the fallback layout.
- const blockListLayout = postContentAttributes
- ? postContentLayout
- : fallbackLayout;
-
- const postEditorLayout =
- blockListLayout?.type === 'default' && ! hasPostContentAtRootLevel
- ? fallbackLayout
- : blockListLayout;
-
- const observeTypingRef = useTypingObserver();
- const titleRef = useRef();
- useEffect( () => {
- if ( isWelcomeGuideVisible || ! isCleanNewPost() ) {
- return;
- }
- titleRef?.current?.focus();
- }, [ isWelcomeGuideVisible, isCleanNewPost ] );
-
styles = useMemo(
() => [
...styles,
{
// We should move this in to future to the body.
- css:
- `.edit-post-visual-editor__post-title-wrapper{margin-top:4rem}` +
- ( paddingBottom
- ? `body{padding-bottom:${ paddingBottom }}`
- : '' ),
+ css: paddingBottom
+ ? `body{padding-bottom:${ paddingBottom }}`
+ : '',
},
],
[ styles ]
);
- // Add some styles for alignwide/alignfull Post Content and its children.
- const alignCSS = `.is-root-container.alignwide { max-width: var(--wp--style--global--wide-size); margin-left: auto; margin-right: auto;}
- .is-root-container.alignwide:where(.is-layout-flow) > :not(.alignleft):not(.alignright) { max-width: var(--wp--style--global--wide-size);}
- .is-root-container.alignfull { max-width: none; margin-left: auto; margin-right: auto;}
- .is-root-container.alignfull:where(.is-layout-flow) > :not(.alignleft):not(.alignright) { max-width: none;}`;
-
const isToBeIframed =
( ( hasV3BlocksOnly || ( isGutenbergPlugin && isBlockBasedTheme ) ) &&
! hasMetaBoxes ) ||
@@ -336,6 +126,14 @@ export default function VisualEditor( { styles } ) {
deviceType === 'Tablet' ||
deviceType === 'Mobile';
+ useEffect( () => {
+ if ( isTemplateMode ) {
+ setRenderingMode( 'all' );
+ } else {
+ setRenderingMode( 'post-only' );
+ }
+ }, [ isTemplateMode, setRenderingMode ] );
+
return (
- { themeSupportsLayout &&
- ! themeHasDisabledLayoutStyles &&
- ! isTemplateMode && (
- <>
-
-
- { align && (
-
- ) }
- { postContentLayoutStyles && (
-
- ) }
- >
- ) }
- { ! isTemplateMode && (
-
- ) }
-
-
-
+
diff --git a/packages/edit-post/src/components/visual-editor/style.scss b/packages/edit-post/src/components/visual-editor/style.scss
index 237bbf25f2c79..46838c97f8799 100644
--- a/packages/edit-post/src/components/visual-editor/style.scss
+++ b/packages/edit-post/src/components/visual-editor/style.scss
@@ -38,21 +38,6 @@
// See also https://www.w3.org/TR/CSS22/visudet.html#the-height-property.
}
-// Ideally this wrapper div is not needed but if we want to match the positioning of blocks
-// .block-editor-block-list__layout and block-editor-block-list__block
-// We need to have two DOM elements.
-.edit-post-visual-editor__post-title-wrapper {
- .editor-post-title {
- // Center.
- margin-left: auto;
- margin-right: auto;
- }
-
- // Add extra margin at the top, to push down the Title area in the post editor.
- margin-top: 4rem;
- margin-bottom: var(--wp--style--block-gap);
-}
-
.edit-post-visual-editor__content-area {
width: 100%;
height: 100%;
diff --git a/packages/edit-post/src/editor.js b/packages/edit-post/src/editor.js
index d1ef111e7dc4c..cff867c3f7a2c 100644
--- a/packages/edit-post/src/editor.js
+++ b/packages/edit-post/src/editor.js
@@ -36,13 +36,11 @@ function Editor( { postId, postType, settings, initialEdits, ...props } ) {
hiddenBlockTypes,
blockTypes,
keepCaretInsideBlock,
- isTemplateMode,
template,
} = useSelect(
( select ) => {
const {
isFeatureActive,
- isEditingTemplate,
getEditedPostTemplate,
getHiddenBlockTypes,
} = select( editPostStore );
@@ -81,7 +79,6 @@ function Editor( { postId, postType, settings, initialEdits, ...props } ) {
hiddenBlockTypes: getHiddenBlockTypes(),
blockTypes: getBlockTypes(),
keepCaretInsideBlock: isFeatureActive( 'keepCaretInsideBlock' ),
- isTemplateMode: isEditingTemplate(),
template:
supportsTemplateMode && isViewable && canEditTemplate
? getEditedPostTemplate()
@@ -156,7 +153,7 @@ function Editor( { postId, postType, settings, initialEdits, ...props } ) {
post={ post }
initialEdits={ initialEdits }
useSubRegistry={ false }
- __unstableTemplate={ isTemplateMode ? template : undefined }
+ __unstableTemplate={ template }
{ ...props }
>
diff --git a/packages/edit-site/src/components/block-editor/editor-canvas.js b/packages/edit-site/src/components/block-editor/editor-canvas.js
index 235eaf6617aa8..15d638aa329e1 100644
--- a/packages/edit-site/src/components/block-editor/editor-canvas.js
+++ b/packages/edit-site/src/components/block-editor/editor-canvas.js
@@ -15,16 +15,22 @@ import { useSelect, useDispatch } from '@wordpress/data';
import { ENTER, SPACE } from '@wordpress/keycodes';
import { useState, useEffect } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
+import { privateApis as editorPrivateApis } from '@wordpress/editor';
/**
* Internal dependencies
*/
import { unlock } from '../../lock-unlock';
import { store as editSiteStore } from '../../store';
+import {
+ FOCUSABLE_ENTITIES,
+ NAVIGATION_POST_TYPE,
+} from '../../utils/constants';
const { ExperimentalBlockCanvas: BlockCanvas } = unlock(
blockEditorPrivateApis
);
+const { EditorCanvas: EditorCanvasRoot } = unlock( editorPrivateApis );
function EditorCanvas( {
enableResizing,
@@ -33,17 +39,32 @@ function EditorCanvas( {
contentRef,
...props
} ) {
- const { canvasMode, deviceType, isZoomOutMode } = useSelect(
- ( select ) => ( {
- deviceType:
- select( editSiteStore ).__experimentalGetPreviewDeviceType(),
- isZoomOutMode:
- select( blockEditorStore ).__unstableGetEditorMode() ===
- 'zoom-out',
- canvasMode: unlock( select( editSiteStore ) ).getCanvasMode(),
- } ),
- []
- );
+ const {
+ hasBlocks,
+ isFocusMode,
+ templateType,
+ canvasMode,
+ deviceType,
+ isZoomOutMode,
+ } = useSelect( ( select ) => {
+ const { getBlockCount, __unstableGetEditorMode } =
+ select( blockEditorStore );
+ const {
+ getEditedPostType,
+ __experimentalGetPreviewDeviceType,
+ getCanvasMode,
+ } = unlock( select( editSiteStore ) );
+ const _templateType = getEditedPostType();
+
+ return {
+ templateType: _templateType,
+ isFocusMode: FOCUSABLE_ENTITIES.includes( _templateType ),
+ deviceType: __experimentalGetPreviewDeviceType(),
+ isZoomOutMode: __unstableGetEditorMode() === 'zoom-out',
+ canvasMode: getCanvasMode(),
+ hasBlocks: !! getBlockCount(),
+ };
+ }, [] );
const { setCanvasMode } = unlock( useDispatch( editSiteStore ) );
const deviceStyles = useResizeCanvas( deviceType );
const [ isFocused, setIsFocused ] = useState( false );
@@ -70,6 +91,15 @@ function EditorCanvas( {
onClick: () => setCanvasMode( 'edit' ),
readonly: true,
};
+ const isTemplateTypeNavigation = templateType === NAVIGATION_POST_TYPE;
+ const isNavigationFocusMode = isTemplateTypeNavigation && isFocusMode;
+ // Hide the appender when:
+ // - In navigation focus mode (should only allow the root Nav block).
+ // - In view mode (i.e. not editing).
+ const showBlockAppender =
+ ( isNavigationFocusMode && hasBlocks ) || canvasMode === 'view'
+ ? false
+ : undefined;
return (
+
{ children }
);
diff --git a/packages/edit-site/src/components/block-editor/site-editor-canvas.js b/packages/edit-site/src/components/block-editor/site-editor-canvas.js
index 0d2d522c8b3e1..bfbb2d3eac43f 100644
--- a/packages/edit-site/src/components/block-editor/site-editor-canvas.js
+++ b/packages/edit-site/src/components/block-editor/site-editor-canvas.js
@@ -7,12 +7,9 @@ import classnames from 'classnames';
*/
import { useSelect, useDispatch } from '@wordpress/data';
import { useRef } from '@wordpress/element';
-import {
- BlockList,
- BlockTools,
- store as blockEditorStore,
-} from '@wordpress/block-editor';
+import { BlockTools, store as blockEditorStore } from '@wordpress/block-editor';
import { useViewportMatch, useResizeObserver } from '@wordpress/compose';
+
/**
* Internal dependencies
*/
@@ -29,12 +26,6 @@ import {
import { unlock } from '../../lock-unlock';
import PageContentFocusNotifications from '../page-content-focus-notifications';
-const LAYOUT = {
- type: 'default',
- // At the root level of the site editor, no alignments should be allowed.
- alignments: [],
-};
-
export default function SiteEditorCanvas() {
const { clearSelectedBlock } = useDispatch( blockEditorStore );
@@ -56,16 +47,6 @@ export default function SiteEditorCanvas() {
const settings = useSiteEditorSettings();
- const { hasBlocks } = useSelect( ( select ) => {
- const { getBlockCount } = select( blockEditorStore );
-
- const blocks = getBlockCount();
-
- return {
- hasBlocks: !! blocks,
- };
- }, [] );
-
const isMobileViewport = useViewportMatch( 'small', '<' );
const enableResizing =
isFocusMode &&
@@ -75,17 +56,7 @@ export default function SiteEditorCanvas() {
const contentRef = useRef();
const isTemplateTypeNavigation = templateType === NAVIGATION_POST_TYPE;
-
const isNavigationFocusMode = isTemplateTypeNavigation && isFocusMode;
-
- // Hide the appender when:
- // - In navigation focus mode (should only allow the root Nav block).
- // - In view mode (i.e. not editing).
- const showBlockAppender =
- ( isNavigationFocusMode && hasBlocks ) || isViewMode
- ? false
- : undefined;
-
const forceFullHeight = isNavigationFocusMode;
return (
@@ -126,23 +97,6 @@ export default function SiteEditorCanvas() {
contentRef={ contentRef }
>
{ resizeObserver }
-
diff --git a/packages/edit-site/src/components/block-editor/style.scss b/packages/edit-site/src/components/block-editor/style.scss
index b110b1c274e77..6f64acdbccfc8 100644
--- a/packages/edit-site/src/components/block-editor/style.scss
+++ b/packages/edit-site/src/components/block-editor/style.scss
@@ -14,7 +14,7 @@
// Navigation focus mode requires padding around the root Navigation block
// for presentational purposes.
-.edit-site-block-editor__block-list.is-navigation-block {
+.edit-site-editor-canvas__block-list.is-navigation-block {
padding: $grid-unit-30;
}
diff --git a/packages/edit-site/src/components/block-editor/use-site-editor-settings.js b/packages/edit-site/src/components/block-editor/use-site-editor-settings.js
index cb3fb3f1cb333..3cca41d67985c 100644
--- a/packages/edit-site/src/components/block-editor/use-site-editor-settings.js
+++ b/packages/edit-site/src/components/block-editor/use-site-editor-settings.js
@@ -138,6 +138,7 @@ export function useSpecificEditorSettings() {
return {
...settings,
+ supportsTemplateMode: true,
__experimentalSetIsInserterOpened: setIsInserterOpened,
focusMode: canvasMode === 'view' && focusMode ? false : focusMode,
isDistractionFree,
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 df59dffe66be6..bbf4b55c05287 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
@@ -12,6 +12,7 @@ import { humanTimeDiff } from '@wordpress/date';
import { useSelect } from '@wordpress/data';
import { store as coreStore } from '@wordpress/core-data';
import { decodeEntities } from '@wordpress/html-entities';
+import { store as editorStore } from '@wordpress/editor';
/**
* Internal dependencies
@@ -22,28 +23,39 @@ import PageContent from './page-content';
import PageSummary from './page-summary';
export default function PagePanels() {
- const { id, type, hasResolved, status, date, password, title, modified } =
- useSelect( ( select ) => {
- const { getEditedPostContext } = select( editSiteStore );
- const { getEditedEntityRecord, hasFinishedResolution } =
- select( coreStore );
- const context = getEditedPostContext();
- const queryArgs = [ 'postType', context.postType, context.postId ];
- const page = getEditedEntityRecord( ...queryArgs );
- return {
- hasResolved: hasFinishedResolution(
- 'getEditedEntityRecord',
- queryArgs
- ),
- title: page?.title,
- id: page?.id,
- type: page?.type,
- status: page?.status,
- date: page?.date,
- password: page?.password,
- modified: page?.modified,
- };
- }, [] );
+ const {
+ id,
+ type,
+ hasResolved,
+ status,
+ date,
+ password,
+ title,
+ modified,
+ renderingMode,
+ } = useSelect( ( select ) => {
+ const { getEditedPostContext } = select( editSiteStore );
+ const { getEditedEntityRecord, hasFinishedResolution } =
+ select( coreStore );
+ const { getRenderingMode } = select( editorStore );
+ const context = getEditedPostContext();
+ const queryArgs = [ 'postType', context.postType, context.postId ];
+ const page = getEditedEntityRecord( ...queryArgs );
+ return {
+ hasResolved: hasFinishedResolution(
+ 'getEditedEntityRecord',
+ queryArgs
+ ),
+ title: page?.title,
+ id: page?.id,
+ type: page?.type,
+ status: page?.status,
+ date: page?.date,
+ password: page?.password,
+ modified: page?.modified,
+ renderingMode: getRenderingMode(),
+ };
+ }, [] );
if ( ! hasResolved ) {
return null;
@@ -77,9 +89,11 @@ export default function PagePanels() {
postType={ type }
/>
-
-
-
+ { renderingMode !== 'post-only' && (
+
+
+
+ ) }
>
);
}
diff --git a/packages/editor/src/components/editor-canvas/index.js b/packages/editor/src/components/editor-canvas/index.js
new file mode 100644
index 0000000000000..906eb6b272fc7
--- /dev/null
+++ b/packages/editor/src/components/editor-canvas/index.js
@@ -0,0 +1,334 @@
+/**
+ * External dependencies
+ */
+import classnames from 'classnames';
+
+/**
+ * WordPress dependencies
+ */
+import {
+ BlockList,
+ store as blockEditorStore,
+ __unstableUseTypingObserver as useTypingObserver,
+ useSettings,
+ __experimentalRecursionProvider as RecursionProvider,
+ privateApis as blockEditorPrivateApis,
+} from '@wordpress/block-editor';
+import { useEffect, useRef, useMemo } from '@wordpress/element';
+import { useSelect } from '@wordpress/data';
+import { parse } from '@wordpress/blocks';
+import { store as coreStore } from '@wordpress/core-data';
+
+/**
+ * Internal dependencies
+ */
+import PostTitle from '../post-title';
+import { store as editorStore } from '../../store';
+import { unlock } from '../../lock-unlock';
+
+const { LayoutStyle, useLayoutClasses, useLayoutStyles } = unlock(
+ blockEditorPrivateApis
+);
+
+/**
+ * Given an array of nested blocks, find the first Post Content
+ * block inside it, recursing through any nesting levels,
+ * and return its attributes.
+ *
+ * @param {Array} blocks A list of blocks.
+ *
+ * @return {Object | undefined} The Post Content block.
+ */
+function getPostContentAttributes( blocks ) {
+ for ( let i = 0; i < blocks.length; i++ ) {
+ if ( blocks[ i ].name === 'core/post-content' ) {
+ return blocks[ i ].attributes;
+ }
+ if ( blocks[ i ].innerBlocks.length ) {
+ const nestedPostContent = getPostContentAttributes(
+ blocks[ i ].innerBlocks
+ );
+
+ if ( nestedPostContent ) {
+ return nestedPostContent;
+ }
+ }
+ }
+}
+
+function checkForPostContentAtRootLevel( blocks ) {
+ for ( let i = 0; i < blocks.length; i++ ) {
+ if ( blocks[ i ].name === 'core/post-content' ) {
+ return true;
+ }
+ }
+ return false;
+}
+
+export default function EditorCanvas( {
+ // Ideally as we unify post and site editors, we won't need these props.
+ autoFocus,
+ dropZoneElement,
+ className,
+ renderAppender,
+} ) {
+ const {
+ renderingMode,
+ postContentAttributes,
+ editedPostTemplate = {},
+ wrapperBlockName,
+ wrapperUniqueId,
+ } = useSelect( ( select ) => {
+ const {
+ getCurrentPostId,
+ getCurrentPostType,
+ getCurrentTemplateId,
+ getEditorSettings,
+ getRenderingMode,
+ } = select( editorStore );
+ const postTypeSlug = getCurrentPostType();
+ const _renderingMode = getRenderingMode();
+ let _wrapperBlockName;
+
+ if ( postTypeSlug === 'wp_block' ) {
+ _wrapperBlockName = 'core/block';
+ } else if ( ! _renderingMode === 'post-only' ) {
+ _wrapperBlockName = 'core/post-content';
+ }
+
+ const editorSettings = getEditorSettings();
+ const supportsTemplateMode = editorSettings.supportsTemplateMode;
+ const postType = select( coreStore ).getPostType( postTypeSlug );
+ const canEditTemplate = select( coreStore ).canUser(
+ 'create',
+ 'templates'
+ );
+ const currentTemplateId = getCurrentTemplateId();
+ const template = currentTemplateId
+ ? select( coreStore ).getEditedEntityRecord(
+ 'postType',
+ 'wp_template',
+ currentTemplateId
+ )
+ : undefined;
+
+ return {
+ renderingMode: _renderingMode,
+ postContentAttributes: getEditorSettings().postContentAttributes,
+ // Post template fetch returns a 404 on classic themes, which
+ // messes with e2e tests, so check it's a block theme first.
+ editedPostTemplate:
+ postType?.viewable && supportsTemplateMode && canEditTemplate
+ ? template
+ : undefined,
+ wrapperBlockName: _wrapperBlockName,
+ wrapperUniqueId: getCurrentPostId(),
+ };
+ }, [] );
+ const { isCleanNewPost } = useSelect( editorStore );
+ const {
+ hasRootPaddingAwareAlignments,
+ themeHasDisabledLayoutStyles,
+ themeSupportsLayout,
+ } = useSelect( ( select ) => {
+ const _settings = select( blockEditorStore ).getSettings();
+ return {
+ themeHasDisabledLayoutStyles: _settings.disableLayoutStyles,
+ themeSupportsLayout: _settings.supportsLayout,
+ hasRootPaddingAwareAlignments:
+ _settings.__experimentalFeatures?.useRootPaddingAwareAlignments,
+ };
+ }, [] );
+
+ const [ globalLayoutSettings ] = useSettings( 'layout' );
+
+ // fallbackLayout is used if there is no Post Content,
+ // and for Post Title.
+ const fallbackLayout = useMemo( () => {
+ if ( renderingMode !== 'post-only' ) {
+ return { type: 'default' };
+ }
+
+ if ( themeSupportsLayout ) {
+ // We need to ensure support for wide and full alignments,
+ // so we add the constrained type.
+ return { ...globalLayoutSettings, type: 'constrained' };
+ }
+ // Set default layout for classic themes so all alignments are supported.
+ return { type: 'default' };
+ }, [ renderingMode, themeSupportsLayout, globalLayoutSettings ] );
+
+ const newestPostContentAttributes = useMemo( () => {
+ if (
+ ! editedPostTemplate?.content &&
+ ! editedPostTemplate?.blocks &&
+ postContentAttributes
+ ) {
+ return postContentAttributes;
+ }
+ // When in template editing mode, we can access the blocks directly.
+ if ( editedPostTemplate?.blocks ) {
+ return getPostContentAttributes( editedPostTemplate?.blocks );
+ }
+ // If there are no blocks, we have to parse the content string.
+ // Best double-check it's a string otherwise the parse function gets unhappy.
+ const parseableContent =
+ typeof editedPostTemplate?.content === 'string'
+ ? editedPostTemplate?.content
+ : '';
+
+ return getPostContentAttributes( parse( parseableContent ) ) || {};
+ }, [
+ editedPostTemplate?.content,
+ editedPostTemplate?.blocks,
+ postContentAttributes,
+ ] );
+
+ const hasPostContentAtRootLevel = useMemo( () => {
+ if ( ! editedPostTemplate?.content && ! editedPostTemplate?.blocks ) {
+ return false;
+ }
+ // When in template editing mode, we can access the blocks directly.
+ if ( editedPostTemplate?.blocks ) {
+ return checkForPostContentAtRootLevel( editedPostTemplate?.blocks );
+ }
+ // If there are no blocks, we have to parse the content string.
+ // Best double-check it's a string otherwise the parse function gets unhappy.
+ const parseableContent =
+ typeof editedPostTemplate?.content === 'string'
+ ? editedPostTemplate?.content
+ : '';
+
+ return (
+ checkForPostContentAtRootLevel( parse( parseableContent ) ) || false
+ );
+ }, [ editedPostTemplate?.content, editedPostTemplate?.blocks ] );
+
+ const { layout = {}, align = '' } = newestPostContentAttributes || {};
+
+ const postContentLayoutClasses = useLayoutClasses(
+ newestPostContentAttributes,
+ 'core/post-content'
+ );
+
+ const blockListLayoutClass = classnames(
+ {
+ 'is-layout-flow': ! themeSupportsLayout,
+ },
+ themeSupportsLayout && postContentLayoutClasses,
+ align && `align${ align }`
+ );
+
+ const postContentLayoutStyles = useLayoutStyles(
+ newestPostContentAttributes,
+ 'core/post-content',
+ '.block-editor-block-list__layout.is-root-container'
+ );
+
+ // Update type for blocks using legacy layouts.
+ const postContentLayout = useMemo( () => {
+ return layout &&
+ ( layout?.type === 'constrained' ||
+ layout?.inherit ||
+ layout?.contentSize ||
+ layout?.wideSize )
+ ? { ...globalLayoutSettings, ...layout, type: 'constrained' }
+ : { ...globalLayoutSettings, ...layout, type: 'default' };
+ }, [
+ layout?.type,
+ layout?.inherit,
+ layout?.contentSize,
+ layout?.wideSize,
+ globalLayoutSettings,
+ ] );
+
+ // If there is a Post Content block we use its layout for the block list;
+ // if not, this must be a classic theme, in which case we use the fallback layout.
+ const blockListLayout = postContentAttributes
+ ? postContentLayout
+ : fallbackLayout;
+
+ const postEditorLayout =
+ blockListLayout?.type === 'default' && ! hasPostContentAtRootLevel
+ ? fallbackLayout
+ : blockListLayout;
+
+ const observeTypingRef = useTypingObserver();
+ const titleRef = useRef();
+ useEffect( () => {
+ if ( ! autoFocus || ! isCleanNewPost() ) {
+ return;
+ }
+ titleRef?.current?.focus();
+ }, [ autoFocus, isCleanNewPost ] );
+
+ // Add some styles for alignwide/alignfull Post Content and its children.
+ const alignCSS = `.is-root-container.alignwide { max-width: var(--wp--style--global--wide-size); margin-left: auto; margin-right: auto;}
+ .is-root-container.alignwide:where(.is-layout-flow) > :not(.alignleft):not(.alignright) { max-width: var(--wp--style--global--wide-size);}
+ .is-root-container.alignfull { max-width: none; margin-left: auto; margin-right: auto;}
+ .is-root-container.alignfull:where(.is-layout-flow) > :not(.alignleft):not(.alignright) { max-width: none;}`;
+
+ return (
+ <>
+ { themeSupportsLayout &&
+ ! themeHasDisabledLayoutStyles &&
+ renderingMode === 'post-only' && (
+ <>
+
+
+ { align && }
+ { postContentLayoutStyles && (
+
+ ) }
+ >
+ ) }
+ { renderingMode === 'post-only' && (
+
+ ) }
+
+
+
+ >
+ );
+}
diff --git a/packages/editor/src/components/provider/index.js b/packages/editor/src/components/provider/index.js
index dda536aec4f73..3bd5860501d4e 100644
--- a/packages/editor/src/components/provider/index.js
+++ b/packages/editor/src/components/provider/index.js
@@ -23,7 +23,6 @@ import { store as editorStore } from '../../store';
import useBlockEditorSettings from './use-block-editor-settings';
import { unlock } from '../../lock-unlock';
import DisableNonPageContentBlocks from './disable-non-page-content-blocks';
-import { PAGE_CONTENT_BLOCK_TYPES } from './constants';
const { ExperimentalBlockEditorProvider } = unlock( blockEditorPrivateApis );
const { PatternsMenuItems } = unlock( editPatternsPrivateApis );
@@ -60,36 +59,6 @@ function useForceFocusModeForNavigation( navigationBlockClientId ) {
] );
}
-/**
- * Helper method to extract the post content block types from a template.
- *
- * @param {Array} blocks Template blocks.
- *
- * @return {Array} Flattened object.
- */
-function extractPageContentBlockTypesFromTemplateBlocks( blocks ) {
- 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 ( blocks[ i ].name === 'core/query' ) {
- continue;
- }
- if ( PAGE_CONTENT_BLOCK_TYPES.includes( blocks[ i ].name ) ) {
- result.push( createBlock( blocks[ i ].name ) );
- }
- if ( blocks[ i ].innerBlocks.length ) {
- result.push(
- ...extractPageContentBlockTypesFromTemplateBlocks(
- blocks[ i ].innerBlocks
- )
- );
- }
- }
-
- return result;
-}
-
/**
* Depending on the post, template and template mode,
* returns the appropriate blocks and change handlers for the block editor provider.
@@ -125,36 +94,6 @@ function useBlockEditorProps( post, template, mode ) {
}
}, [ post.type, post.id ] );
- const maybePostOnlyBlocks = useMemo( () => {
- if ( mode === 'post-only' ) {
- const postContentBlocks =
- extractPageContentBlockTypesFromTemplateBlocks(
- templateBlocks
- );
- return [
- createBlock(
- 'core/group',
- {
- layout: { type: 'constrained' },
- style: {
- spacing: {
- margin: {
- top: '4em', // Mimics the post editor.
- },
- },
- },
- },
- postContentBlocks.length
- ? postContentBlocks
- : [
- createBlock( 'core/post-title' ),
- createBlock( 'core/post-content' ),
- ]
- ),
- ];
- }
- }, [ templateBlocks, mode ] );
-
// It is important that we don't create a new instance of blocks on every change
// We should only create a new instance if the blocks them selves change, not a dependency of them.
const blocks = useMemo( () => {
@@ -162,30 +101,19 @@ function useBlockEditorProps( post, template, mode ) {
return maybeNavigationBlocks;
}
- if ( maybePostOnlyBlocks ) {
- return maybePostOnlyBlocks;
- }
-
if ( rootLevelPost === 'template' ) {
return templateBlocks;
}
return postBlocks;
- }, [
- maybeNavigationBlocks,
- maybePostOnlyBlocks,
- rootLevelPost,
- templateBlocks,
- postBlocks,
- ] );
+ }, [ maybeNavigationBlocks, rootLevelPost, templateBlocks, postBlocks ] );
// Handle fallback to postBlocks outside of the above useMemo, to ensure
// that constructed block templates that call `createBlock` are not generated
// too frequently. This ensures that clientIds are stable.
const disableRootLevelChanges =
( !! template && mode === 'template-locked' ) ||
- post.type === 'wp_navigation' ||
- mode === 'post-only';
+ post.type === 'wp_navigation';
const navigationBlockClientId =
post.type === 'wp_navigation' && blocks && blocks[ 0 ]?.clientId;
useForceFocusModeForNavigation( navigationBlockClientId );
@@ -270,7 +198,8 @@ export const ExperimentalEditorProvider = withRegistryProvider(
setupEditor,
updateEditorSettings,
__experimentalTearDownEditor,
- } = useDispatch( editorStore );
+ setCurrentTemplateId,
+ } = unlock( useDispatch( editorStore ) );
const { createWarningNotice } = useDispatch( noticesStore );
// Initialize and tear down the editor.
@@ -310,6 +239,10 @@ export const ExperimentalEditorProvider = withRegistryProvider(
updateEditorSettings( settings );
}, [ settings, updateEditorSettings ] );
+ useEffect( () => {
+ setCurrentTemplateId( template?.id );
+ }, [ template?.id, setCurrentTemplateId ] );
+
if ( ! isReady ) {
return null;
}
@@ -332,9 +265,9 @@ export const ExperimentalEditorProvider = withRegistryProvider(
>
{ children }
- { [ 'post-only', 'template-locked' ].includes(
- mode
- ) && }
+ { mode === 'template-locked' && (
+
+ ) }
diff --git a/packages/editor/src/private-apis.js b/packages/editor/src/private-apis.js
index a44720eb93ac8..046feee5b9c3f 100644
--- a/packages/editor/src/private-apis.js
+++ b/packages/editor/src/private-apis.js
@@ -1,6 +1,7 @@
/**
* Internal dependencies
*/
+import EditorCanvas from './components/editor-canvas';
import { ExperimentalEditorProvider } from './components/provider';
import { lock } from './lock-unlock';
import { EntitiesSavedStatesExtensible } from './components/entities-saved-states';
@@ -9,6 +10,7 @@ import PostPanelRow from './components/post-panel-row';
export const privateApis = {};
lock( privateApis, {
+ EditorCanvas,
ExperimentalEditorProvider,
EntitiesSavedStatesExtensible,
PostPanelRow,
diff --git a/packages/editor/src/store/actions.js b/packages/editor/src/store/actions.js
index 0c946d4124f49..4c1170b064202 100644
--- a/packages/editor/src/store/actions.js
+++ b/packages/editor/src/store/actions.js
@@ -560,8 +560,12 @@ export function updateEditorSettings( settings ) {
*/
export const setRenderingMode =
( mode ) =>
- ( { dispatch, registry } ) => {
- registry.dispatch( blockEditorStore ).clearSelectedBlock();
+ ( { dispatch, registry, select } ) => {
+ if ( select.__unstableIsEditorReady() ) {
+ // We clear the block selection but we also need to clear the selection from the core store.
+ registry.dispatch( blockEditorStore ).clearSelectedBlock();
+ dispatch.editPost( { selection: undefined }, { undoIgnore: true } );
+ }
dispatch( {
type: 'SET_RENDERING_MODE',
diff --git a/packages/editor/src/store/index.js b/packages/editor/src/store/index.js
index baee4d9197d0c..ebd41354308e7 100644
--- a/packages/editor/src/store/index.js
+++ b/packages/editor/src/store/index.js
@@ -9,7 +9,9 @@ import { createReduxStore, register } from '@wordpress/data';
import reducer from './reducer';
import * as selectors from './selectors';
import * as actions from './actions';
+import * as privateActions from './private-actions';
import { STORE_NAME } from './constants';
+import { unlock } from '../lock-unlock';
/**
* Post editor data store configuration.
@@ -36,3 +38,4 @@ export const store = createReduxStore( STORE_NAME, {
} );
register( store );
+unlock( store ).registerPrivateActions( privateActions );
diff --git a/packages/editor/src/store/private-actions.js b/packages/editor/src/store/private-actions.js
new file mode 100644
index 0000000000000..1af9ff5f6adb9
--- /dev/null
+++ b/packages/editor/src/store/private-actions.js
@@ -0,0 +1,13 @@
+/**
+ * Returns an action object used to set which template is currently being used/edited.
+ *
+ * @param {string} id Template Id.
+ *
+ * @return {Object} Action object.
+ */
+export function setCurrentTemplateId( id ) {
+ return {
+ type: 'SET_CURRENT_TEMPLATE_ID',
+ id,
+ };
+}
diff --git a/packages/editor/src/store/reducer.js b/packages/editor/src/store/reducer.js
index 48356fd8e99e3..7821baf5cdc06 100644
--- a/packages/editor/src/store/reducer.js
+++ b/packages/editor/src/store/reducer.js
@@ -90,6 +90,15 @@ export function postId( state = null, action ) {
return state;
}
+export function templateId( state = null, action ) {
+ switch ( action.type ) {
+ case 'SET_CURRENT_TEMPLATE_ID':
+ return action.id;
+ }
+
+ return state;
+}
+
export function postType( state = null, action ) {
switch ( action.type ) {
case 'SETUP_EDITOR_STATE':
@@ -291,6 +300,7 @@ export function renderingMode( state = 'all', action ) {
export default combineReducers( {
postId,
postType,
+ templateId,
saving,
deleting,
postLock,
diff --git a/packages/editor/src/store/selectors.js b/packages/editor/src/store/selectors.js
index 78944335bd398..554b3d83e5912 100644
--- a/packages/editor/src/store/selectors.js
+++ b/packages/editor/src/store/selectors.js
@@ -205,6 +205,17 @@ export function getCurrentPostId( state ) {
return state.postId;
}
+/**
+ * Returns the template ID currently being rendered/edited
+ *
+ * @param {Object} state Global application state.
+ *
+ * @return {string?} Template ID.
+ */
+export function getCurrentTemplateId( state ) {
+ return state.templateId;
+}
+
/**
* Returns the number of revisions of the post currently being edited.
*
diff --git a/packages/react-native-bridge/common/gutenberg-web-single-block/editor-style-overrides.css b/packages/react-native-bridge/common/gutenberg-web-single-block/editor-style-overrides.css
index f8f2e8fe2b4cd..b0ce8f3dc948d 100644
--- a/packages/react-native-bridge/common/gutenberg-web-single-block/editor-style-overrides.css
+++ b/packages/react-native-bridge/common/gutenberg-web-single-block/editor-style-overrides.css
@@ -8,7 +8,7 @@
display: none;
}
-.edit-post-visual-editor__post-title-wrapper {
+.editor-editor-canvas__post-title-wrapper {
display: none;
}
diff --git a/test/e2e/specs/site-editor/pages.spec.js b/test/e2e/specs/site-editor/pages.spec.js
index af58daeaedbe4..4d40223e0a99c 100644
--- a/test/e2e/specs/site-editor/pages.spec.js
+++ b/test/e2e/specs/site-editor/pages.spec.js
@@ -215,24 +215,13 @@ test.describe( 'Pages', () => {
} )
).toBeHidden();
- // Content blocks are wrapped in a Group block by default.
+ // Ensure post title component to be visible.
await expect(
- editor.canvas
- .getByRole( 'document', {
- name: 'Block: Group',
- } )
- .getByRole( 'document', {
- name: 'Block: Content',
- } )
+ editor.canvas.getByRole( 'textbox', {
+ name: 'Add Title',
+ } )
).toBeVisible();
- // Ensure order is preserved between toggling.
- await page
- .locator(
- '[aria-label="Block: Content"] + [aria-label="Block: Title"]'
- )
- .isVisible();
-
// Remove focus from templateOptionsButton button.
await editor.canvas.locator( 'body' ).click();