diff --git a/packages/edit-post/src/components/layout/index.js b/packages/edit-post/src/components/layout/index.js index 3f9f71b4f4de8a..a3deb4a1ba79d2 100644 --- a/packages/edit-post/src/components/layout/index.js +++ b/packages/edit-post/src/components/layout/index.js @@ -379,6 +379,7 @@ function Layout( { const { mode, isFullscreenActive, + hasResolvedMode, hasActiveMetaboxes, hasBlockSelected, showIconLabels, @@ -390,7 +391,7 @@ function Layout( { } = useSelect( ( select ) => { const { get } = select( preferencesStore ); - const { isFeatureActive } = select( editPostStore ); + const { isFeatureActive, hasMetaBoxes } = select( editPostStore ); const { canUser, getPostType, getTemplateId } = unlock( select( coreStore ) ); @@ -402,22 +403,29 @@ function Layout( { kind: 'postType', name: 'wp_template', } ); - const { isZoomOut } = unlock( select( blockEditorStore ) ); - const { getEditorMode, getRenderingMode } = select( editorStore ); + const { getBlockSelectionStart, isZoomOut } = unlock( + select( blockEditorStore ) + ); + const { getEditorMode, getRenderingMode, getDefaultRenderingMode } = + unlock( select( editorStore ) ); const isRenderingPostOnly = getRenderingMode() === 'post-only'; const isNotDesignPostType = ! DESIGN_POST_TYPES.includes( currentPostType ); const isDirectlyEditingPattern = currentPostType === 'wp_block' && ! onNavigateToPreviousEntityRecord; + const _templateId = getTemplateId( currentPostType, currentPostId ); + const defaultMode = getDefaultRenderingMode( currentPostType ); return { mode: getEditorMode(), - isFullscreenActive: - select( editPostStore ).isFeatureActive( 'fullscreenMode' ), - hasActiveMetaboxes: select( editPostStore ).hasMetaBoxes(), - hasBlockSelected: - !! select( blockEditorStore ).getBlockSelectionStart(), + isFullscreenActive: isFeatureActive( 'fullscreenMode' ), + hasActiveMetaboxes: hasMetaBoxes(), + hasResolvedMode: + defaultMode === 'template-locked' + ? !! _templateId + : defaultMode !== undefined, + hasBlockSelected: !! getBlockSelectionStart(), showIconLabels: get( 'core', 'showIconLabels' ), isDistractionFree: get( 'core', 'distractionFree' ), showMetaBoxes: @@ -429,7 +437,7 @@ function Layout( { isViewable && canViewTemplate && ! isEditingTemplate - ? getTemplateId( currentPostType, currentPostId ) + ? _templateId : null, enablePaddingAppender: ! isZoomOut() && isRenderingPostOnly && isNotDesignPostType, @@ -443,7 +451,8 @@ function Layout( { onNavigateToPreviousEntityRecord, ] ); - useMetaBoxInitialization( hasActiveMetaboxes ); + + useMetaBoxInitialization( hasActiveMetaboxes && hasResolvedMode ); const [ paddingAppenderRef, paddingStyle ] = usePaddingAppender( enablePaddingAppender diff --git a/packages/editor/src/components/post-template/block-theme.js b/packages/editor/src/components/post-template/block-theme.js index 6d7f7f787b32f8..8089e187233e04 100644 --- a/packages/editor/src/components/post-template/block-theme.js +++ b/packages/editor/src/components/post-template/block-theme.js @@ -53,7 +53,9 @@ export default function BlockThemeControl( { id } ) { id ); const { createSuccessNotice } = useDispatch( noticesStore ); - const { setRenderingMode } = useDispatch( editorStore ); + const { setRenderingMode, setDefaultRenderingMode } = unlock( + useDispatch( editorStore ) + ); const canCreateTemplate = useSelect( ( select ) => @@ -149,11 +151,11 @@ export default function BlockThemeControl( { id } ) { isSelected={ ! isTemplateHidden } role="menuitemcheckbox" onClick={ () => { - setRenderingMode( - isTemplateHidden - ? 'template-locked' - : 'post-only' - ); + const newRenderingMode = isTemplateHidden + ? 'template-locked' + : 'post-only'; + setRenderingMode( newRenderingMode ); + setDefaultRenderingMode( newRenderingMode ); } } > { __( 'Show template' ) } diff --git a/packages/editor/src/components/preview-dropdown/index.js b/packages/editor/src/components/preview-dropdown/index.js index a081564e48ea8d..3fa1e211bb7d35 100644 --- a/packages/editor/src/components/preview-dropdown/index.js +++ b/packages/editor/src/components/preview-dropdown/index.js @@ -56,7 +56,9 @@ export default function PreviewDropdown( { forceIsAutosaveable, disabled } ) { templateId: getCurrentTemplateId(), }; }, [] ); - const { setDeviceType, setRenderingMode } = useDispatch( editorStore ); + const { setDeviceType, setRenderingMode, setDefaultRenderingMode } = unlock( + useDispatch( editorStore ) + ); const { resetZoomLevel } = unlock( useDispatch( blockEditorStore ) ); const handleDevicePreviewChange = ( newDeviceType ) => { @@ -160,11 +162,11 @@ export default function PreviewDropdown( { forceIsAutosaveable, disabled } ) { isSelected={ ! isTemplateHidden } role="menuitemcheckbox" onClick={ () => { - setRenderingMode( - isTemplateHidden - ? 'template-locked' - : 'post-only' - ); + const newRenderingMode = isTemplateHidden + ? 'template-locked' + : 'post-only'; + setRenderingMode( newRenderingMode ); + setDefaultRenderingMode( newRenderingMode ); } } > { __( 'Show template' ) } diff --git a/packages/editor/src/components/provider/index.js b/packages/editor/src/components/provider/index.js index 0e653228fee628..3e9afe3363b1a5 100644 --- a/packages/editor/src/components/provider/index.js +++ b/packages/editor/src/components/provider/index.js @@ -56,11 +56,6 @@ const NON_CONTEXTUAL_POST_TYPES = [ 'wp_template_part', ]; -/** - * These are rendering modes that the editor supports. - */ -const RENDERING_MODES = [ 'post-only', 'template-locked' ]; - /** * Depending on the post, template and template mode, * returns the appropriate blocks and change handlers for the block editor provider. @@ -183,37 +178,28 @@ export const ExperimentalEditorProvider = withRegistryProvider( getEditorSelection, getRenderingMode, __unstableIsEditorReady, - } = select( editorStore ); - const { - getEntitiesConfig, - getPostType, - hasFinishedResolution, - } = select( coreStore ); - - const postTypeSupports = getPostType( post.type )?.supports; - const hasLoadedPostObject = hasFinishedResolution( - 'getPostType', - [ post.type ] - ); - - const _defaultMode = Array.isArray( postTypeSupports?.editor ) - ? postTypeSupports.editor.find( - ( features ) => 'default-mode' in features - )?.[ 'default-mode' ] - : undefined; - const hasDefaultMode = RENDERING_MODES.includes( _defaultMode ); - - // Wait for template resolution when rendering in a `template-locked` mode. + getDefaultRenderingMode, + } = unlock( select( editorStore ) ); + const { getEntitiesConfig } = select( coreStore ); + + const _defaultMode = getDefaultRenderingMode( post.type ); + /** + * To avoid content "flash", wait until rendering mode has been resolved. + * This is important for the initial render of the editor. + * + * - Wait for template to be resolved if the default mode is 'template-locked'. + * - Wait for default mode to be resolved otherwise. + */ const hasResolvedMode = - hasLoadedPostObject && _defaultMode === 'template-locked' + _defaultMode === 'template-locked' ? hasTemplate - : true; + : _defaultMode !== undefined; return { editorSettings: getEditorSettings(), isReady: __unstableIsEditorReady() && hasResolvedMode, mode: getRenderingMode(), - defaultMode: hasDefaultMode ? _defaultMode : 'post-only', + defaultMode: _defaultMode, selection: getEditorSelection(), postTypeEntities: post.type === 'wp_template' @@ -224,7 +210,7 @@ export const ExperimentalEditorProvider = withRegistryProvider( [ post.type, hasTemplate ] ); - const shouldRenderTemplate = !! template && mode !== 'post-only'; + const shouldRenderTemplate = hasTemplate && mode !== 'post-only'; const rootLevelPost = shouldRenderTemplate ? template : post; const defaultBlockContext = useMemo( () => { const postContext = {}; @@ -341,7 +327,9 @@ export const ExperimentalEditorProvider = withRegistryProvider( // Sets the right rendering mode when loading the editor. useEffect( () => { - setRenderingMode( defaultMode ); + if ( defaultMode ) { + setRenderingMode( defaultMode ); + } }, [ defaultMode, setRenderingMode ] ); useHideBlocksFromInserter( post.type, mode ); diff --git a/packages/editor/src/store/private-actions.js b/packages/editor/src/store/private-actions.js index 6a83b3ca0b4032..f1aa458fdab98c 100644 --- a/packages/editor/src/store/private-actions.js +++ b/packages/editor/src/store/private-actions.js @@ -492,3 +492,36 @@ export const removeTemplates = .createErrorNotice( errorMessage, { type: 'snackbar' } ); } }; + +/** + * Set the default rendering mode preference for the current post type. + * + * @param {string} mode The rendering mode to set as default. + */ +export const setDefaultRenderingMode = + ( mode ) => + ( { select, registry } ) => { + const postType = select.getCurrentPostType(); + const theme = registry + .select( coreStore ) + .getCurrentTheme()?.stylesheet; + const renderingModes = + registry + .select( preferencesStore ) + .get( 'core', 'renderingModes' )?.[ theme ] ?? {}; + + if ( renderingModes[ postType ] === mode ) { + return; + } + + const newModes = { + [ theme ]: { + ...renderingModes, + [ postType ]: mode, + }, + }; + + registry + .dispatch( preferencesStore ) + .set( 'core', 'renderingModes', newModes ); + }; diff --git a/packages/editor/src/store/private-selectors.js b/packages/editor/src/store/private-selectors.js index 73bf19f3d99937..d381d4caa7492b 100644 --- a/packages/editor/src/store/private-selectors.js +++ b/packages/editor/src/store/private-selectors.js @@ -16,6 +16,7 @@ import { verse, } from '@wordpress/icons'; import { store as coreStore } from '@wordpress/core-data'; +import { store as preferencesStore } from '@wordpress/preferences'; /** * Internal dependencies @@ -34,6 +35,11 @@ const EMPTY_INSERTION_POINT = { filterValue: undefined, }; +/** + * These are rendering modes that the editor supports. + */ +const RENDERING_MODES = [ 'post-only', 'template-locked' ]; + /** * Get the inserter. * @@ -215,3 +221,54 @@ export const getPostBlocksByName = createRegistrySelector( ( select ) => () => [ select( blockEditorStore ).getBlocks() ] ) ); + +/** + * Returns the default rendering mode for a post type by user preference or post type configuration. + * + * @param {Object} state Global application state. + * @param {string} postType The post type. + * + * @return {string} The default rendering mode. Returns `undefined` while resolving value. + */ +export const getDefaultRenderingMode = createRegistrySelector( + ( select ) => ( state, postType ) => { + const { getPostType, getCurrentTheme, hasFinishedResolution } = + select( coreStore ); + + // This needs to be called before `hasFinishedResolution`. + // eslint-disable-next-line @wordpress/no-unused-vars-before-return + const currentTheme = getCurrentTheme(); + // eslint-disable-next-line @wordpress/no-unused-vars-before-return + const postTypeEntity = getPostType( postType ); + + // Wait for the post type and theme resolution. + if ( + ! hasFinishedResolution( 'getPostType', [ postType ] ) || + ! hasFinishedResolution( 'getCurrentTheme' ) + ) { + return undefined; + } + + const theme = currentTheme?.stylesheet; + const defaultModePreference = select( preferencesStore ).get( + 'core', + 'renderingModes' + )?.[ theme ]?.[ postType ]; + const postTypeDefaultMode = Array.isArray( + postTypeEntity?.supports?.editor + ) + ? postTypeEntity.supports.editor.find( + ( features ) => 'default-mode' in features + )?.[ 'default-mode' ] + : undefined; + + const defaultMode = defaultModePreference || postTypeDefaultMode; + + // Fallback gracefully to 'post-only' when rendering mode is not supported. + if ( ! RENDERING_MODES.includes( defaultMode ) ) { + return 'post-only'; + } + + return defaultMode; + } +); diff --git a/test/e2e/specs/site-editor/preload.spec.js b/test/e2e/specs/site-editor/preload.spec.js index e618d70ca20b90..b6f9f49aedeb7c 100644 --- a/test/e2e/specs/site-editor/preload.spec.js +++ b/test/e2e/specs/site-editor/preload.spec.js @@ -46,6 +46,9 @@ test.describe( 'Preload', () => { expect( requests ).toEqual( [ // Seems to be coming from `enableComplementaryArea`. '/wp/v2/users/me', + // There are two separate settings OPTIONS requests. We should fix + // so the one for canUser and getEntityRecord are reused. + '/wp/v2/settings', ] ); } ); } ); diff --git a/test/native/integration-test-helpers/initialize-editor.js b/test/native/integration-test-helpers/initialize-editor.js index 3b89da979aee3a..ffaea764a90ec7 100644 --- a/test/native/integration-test-helpers/initialize-editor.js +++ b/test/native/integration-test-helpers/initialize-editor.js @@ -38,7 +38,7 @@ export async function initializeEditor( props, { component } = {} ) { resolutionSpy.mockImplementation( ( selectorName, args ) => { // The mobile editor only supports the `post-only` rendering mode, so we // presume a resolved `getPostType` selector to unblock editor rendering. - if ( 'getPostType' === selectorName ) { + if ( [ 'getPostType', 'getCurrentTheme' ].includes( selectorName ) ) { return true; }