From 3e621d80a1e15559b55f95df529088c931eca6f6 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Thu, 9 Nov 2023 14:51:46 +0100 Subject: [PATCH 01/14] Site Editor: use EditorProvider instead of custom logic --- .../default-block-editor-provider.js | 75 ------ .../block-editor-provider/index.js | 29 --- .../navigation-block-editor-provider.js | 114 --------- .../src/components/block-editor/index.js | 28 --- .../test/use-page-content-blocks.js | 0 .../use-page-content-blocks.js | 2 +- .../block-editor/use-site-editor-settings.js | 20 +- .../edit-site/src/components/editor/index.js | 229 +++++++++--------- .../page-panels/edit-template.js | 2 +- .../editor/src/components/provider/index.js | 123 +++++++++- .../provider/use-block-editor-settings.js | 8 +- 11 files changed, 253 insertions(+), 377 deletions(-) delete mode 100644 packages/edit-site/src/components/block-editor/block-editor-provider/default-block-editor-provider.js delete mode 100644 packages/edit-site/src/components/block-editor/block-editor-provider/index.js delete mode 100644 packages/edit-site/src/components/block-editor/block-editor-provider/navigation-block-editor-provider.js delete mode 100644 packages/edit-site/src/components/block-editor/index.js rename packages/edit-site/src/components/block-editor/{block-editor-provider => }/test/use-page-content-blocks.js (100%) rename packages/edit-site/src/components/block-editor/{block-editor-provider => }/use-page-content-blocks.js (97%) 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 deleted file mode 100644 index c91c687ce5284..0000000000000 --- a/packages/edit-site/src/components/block-editor/block-editor-provider/default-block-editor-provider.js +++ /dev/null @@ -1,75 +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'; -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 {Element} 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 pageContentBlocks = usePageContentBlocks( { - blocks, - isPageContentFocused: isTemplateHidden, - wrapPageContent: true, - } ); - - 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 deleted file mode 100644 index ab07a3fefc5d3..0000000000000 --- a/packages/edit-site/src/components/block-editor/block-editor-provider/index.js +++ /dev/null @@ -1,29 +0,0 @@ -/** - * 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'; -import { NAVIGATION_POST_TYPE } from '../../../utils/constants'; - -export default function BlockEditorProvider( { children } ) { - const entityType = useSelect( - ( select ) => select( editSiteStore ).getEditedPostType(), - [] - ); - if ( entityType === NAVIGATION_POST_TYPE ) { - return ( - - { children } - - ); - } - return ( - { children } - ); -} diff --git a/packages/edit-site/src/components/block-editor/block-editor-provider/navigation-block-editor-provider.js b/packages/edit-site/src/components/block-editor/block-editor-provider/navigation-block-editor-provider.js deleted file mode 100644 index 9927cbb040c7b..0000000000000 --- a/packages/edit-site/src/components/block-editor/block-editor-provider/navigation-block-editor-provider.js +++ /dev/null @@ -1,114 +0,0 @@ -/** - * WordPress dependencies - */ -import { useSelect, useDispatch } from '@wordpress/data'; -import { useMemo, useEffect } from '@wordpress/element'; -import { useEntityId } from '@wordpress/core-data'; -import { - store as blockEditorStore, - privateApis as blockEditorPrivateApis, -} from '@wordpress/block-editor'; -import { createBlock } from '@wordpress/blocks'; - -/** - * Internal dependencies - */ -import { unlock } from '../../../lock-unlock'; -import useSiteEditorSettings from '../use-site-editor-settings'; -import { store as editSiteStore } from '../../../store'; -import { NAVIGATION_POST_TYPE } from '../../../utils/constants'; - -const { ExperimentalBlockEditorProvider } = unlock( blockEditorPrivateApis ); - -const noop = () => {}; - -/** - * Block editor component for editing navigation menus. - * - * Note: Navigation entities require a wrapping Navigation block to provide - * them with some basic layout and styling. Therefore we create a "ghost" block - * and provide it will a reference to the navigation entity ID being edited. - * - * In this scenario it is the **block** that handles syncing the entity content - * whereas for other entities this is handled by entity block editor. - * - * @param {number} navigationMenuId the navigation menu ID - * @return {[WPBlock[], Function, Function]} The block array and setters. - */ -export default function NavigationBlockEditorProvider( { children } ) { - const defaultSettings = useSiteEditorSettings(); - - const navigationMenuId = useEntityId( 'postType', NAVIGATION_POST_TYPE ); - - const blocks = useMemo( () => { - return [ - createBlock( 'core/navigation', { - ref: navigationMenuId, - // As the parent editor is locked with `templateLock`, the template locking - // must be explicitly "unset" on the block itself to allow the user to modify - // the block's content. - templateLock: false, - } ), - ]; - }, [ navigationMenuId ] ); - - const { isEditMode } = useSelect( ( select ) => { - const { getCanvasMode } = unlock( select( editSiteStore ) ); - - return { - isEditMode: getCanvasMode() === 'edit', - }; - }, [] ); - - const { selectBlock, setBlockEditingMode, unsetBlockEditingMode } = - useDispatch( blockEditorStore ); - - const navigationBlockClientId = blocks && blocks[ 0 ]?.clientId; - - const settings = useMemo( () => { - return { - ...defaultSettings, - // Lock the editor to allow the root ("ghost") Navigation block only. - templateLock: 'insert', - template: [ [ 'core/navigation', {}, [] ] ], - }; - }, [ defaultSettings ] ); - - // Auto-select the Navigation block when entering Navigation focus mode. - useEffect( () => { - if ( navigationBlockClientId && isEditMode ) { - selectBlock( navigationBlockClientId ); - } - }, [ navigationBlockClientId, isEditMode, selectBlock ] ); - - // Set block editing mode to contentOnly when entering Navigation focus mode. - // This ensures that non-content controls on the block will be hidden and thus - // the user can focus on editing the Navigation Menu content only. - useEffect( () => { - if ( ! navigationBlockClientId ) { - return; - } - - setBlockEditingMode( navigationBlockClientId, 'contentOnly' ); - - return () => { - unsetBlockEditingMode( navigationBlockClientId ); - }; - }, [ - navigationBlockClientId, - unsetBlockEditingMode, - setBlockEditingMode, - ] ); - - return ( - - { children } - - ); -} diff --git a/packages/edit-site/src/components/block-editor/index.js b/packages/edit-site/src/components/block-editor/index.js deleted file mode 100644 index 2c635ff860a5b..0000000000000 --- a/packages/edit-site/src/components/block-editor/index.js +++ /dev/null @@ -1,28 +0,0 @@ -/** - * WordPress dependencies - */ -import { BlockInspector } from '@wordpress/block-editor'; -import { privateApis as editPatternsPrivateApis } from '@wordpress/patterns'; - -/** - * Internal dependencies - */ -import TemplatePartConverter from '../template-part-converter'; -import { SidebarInspectorFill } from '../sidebar-edit-mode'; -import SiteEditorCanvas from './site-editor-canvas'; -import BlockEditorProvider from './block-editor-provider'; - -import { unlock } from '../../lock-unlock'; -const { PatternsMenuItems } = unlock( editPatternsPrivateApis ); -export default function BlockEditor() { - return ( - - - - - - - - - ); -} 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/test/use-page-content-blocks.js similarity index 100% rename from packages/edit-site/src/components/block-editor/block-editor-provider/test/use-page-content-blocks.js rename to packages/edit-site/src/components/block-editor/test/use-page-content-blocks.js 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/use-page-content-blocks.js similarity index 97% rename from packages/edit-site/src/components/block-editor/block-editor-provider/use-page-content-blocks.js rename to packages/edit-site/src/components/block-editor/use-page-content-blocks.js index dd05f90a56bbb..9201b7816286e 100644 --- a/packages/edit-site/src/components/block-editor/block-editor-provider/use-page-content-blocks.js +++ b/packages/edit-site/src/components/block-editor/use-page-content-blocks.js @@ -7,7 +7,7 @@ import { createBlock } from '@wordpress/blocks'; /** * Internal dependencies */ -import { PAGE_CONTENT_BLOCK_TYPES } from '../../../utils/constants'; +import { PAGE_CONTENT_BLOCK_TYPES } from '../../utils/constants'; /** * Helper method to iterate through all blocks, recursing into allowed inner blocks. 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 f597abee0726d..1587b8ba4a2d4 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 @@ -87,7 +87,7 @@ function useArchiveLabel( templateSlug ) { ); } -export default function useSiteEditorSettings() { +export function useSpecificEditorSettings() { const { setIsInserterOpened } = useDispatch( editSiteStore ); const { templateSlug, @@ -97,8 +97,6 @@ export default function useSiteEditorSettings() { keepCaretInsideBlock, canvasMode, settings, - postType, - postId, } = useSelect( ( select ) => { const { getEditedPostType, @@ -164,5 +162,21 @@ export default function useSiteEditorSettings() { archiveLabels.archiveNameLabel, ] ); + return defaultEditorSettings; +} + +export default function useSiteEditorSettings() { + const defaultEditorSettings = useSpecificEditorSettings(); + const { postType, postId } = useSelect( ( select ) => { + const { getEditedPostType, getEditedPostId } = unlock( + select( editSiteStore ) + ); + const usedPostType = getEditedPostType(); + const usedPostId = getEditedPostId(); + return { + postType: usedPostType, + postId: usedPostId, + }; + }, [] ); return useBlockEditorSettings( defaultEditorSettings, postType, postId ); } diff --git a/packages/edit-site/src/components/editor/index.js b/packages/edit-site/src/components/editor/index.js index 70d033d14188c..a47ef3c33817c 100644 --- a/packages/edit-site/src/components/editor/index.js +++ b/packages/edit-site/src/components/editor/index.js @@ -6,31 +6,36 @@ import classnames from 'classnames'; /** * WordPress dependencies */ -import { useMemo } from '@wordpress/element'; import { useSelect } from '@wordpress/data'; import { Notice } from '@wordpress/components'; import { useInstanceId } from '@wordpress/compose'; -import { EntityProvider } from '@wordpress/core-data'; import { store as preferencesStore } from '@wordpress/preferences'; import { - BlockContextProvider, BlockBreadcrumb, store as blockEditorStore, privateApis as blockEditorPrivateApis, + BlockInspector, } from '@wordpress/block-editor'; import { InterfaceSkeleton, ComplementaryArea, store as interfaceStore, } from '@wordpress/interface'; -import { EditorNotices, EditorSnackbars } from '@wordpress/editor'; +import { + EditorNotices, + EditorSnackbars, + privateApis as editorPrivateApis, +} from '@wordpress/editor'; import { __, sprintf } from '@wordpress/i18n'; +import { store as coreDataStore } from '@wordpress/core-data'; /** * Internal dependencies */ -import { SidebarComplementaryAreaFills } from '../sidebar-edit-mode'; -import BlockEditor from '../block-editor'; +import { + SidebarComplementaryAreaFills, + SidebarInspectorFill, +} from '../sidebar-edit-mode'; import CodeEditor from '../code-editor'; import KeyboardShortcutsEditMode from '../keyboard-shortcuts/edit-mode'; import InserterSidebar from '../secondary-sidebar/inserter-sidebar'; @@ -46,8 +51,13 @@ import useEditedEntityRecord from '../use-edited-entity-record'; import { SidebarFixedBottomSlot } from '../sidebar-edit-mode/sidebar-fixed-bottom'; import PatternModal from '../pattern-modal'; import { POST_TYPE_LABELS, TEMPLATE_POST_TYPE } from '../../utils/constants'; +import SiteEditorCanvas from '../block-editor/site-editor-canvas'; +import TemplatePartConverter from '../template-part-converter'; +import { useSpecificEditorSettings } from '../block-editor/use-site-editor-settings'; const { BlockRemovalWarningModal } = unlock( blockEditorPrivateApis ); +const { ExperimentalEditorProvider: EditorProvider } = + unlock( editorPrivateApis ); const interfaceLabels = { /* translators: accessibility text for the editor content landmark region. */ @@ -79,10 +89,11 @@ export default function Editor( { listViewToggleElement, isLoading } ) { isLoaded: hasLoadedPost, } = useEditedEntityRecord(); - const { id: editedPostId, type: editedPostType } = editedPost; + const { type: editedPostType } = editedPost; const { context, + contextPost, editorMode, canvasMode, blockEditorMode, @@ -103,11 +114,20 @@ export default function Editor( { listViewToggleElement, isLoading } ) { } = unlock( select( editSiteStore ) ); const { __unstableGetEditorMode } = select( blockEditorStore ); const { getActiveComplementaryArea } = select( interfaceStore ); + const { getEntityRecord } = select( coreDataStore ); + const _context = getEditedPostContext(); // The currently selected entity to display. // Typically template or template part in the site editor. return { - context: getEditedPostContext(), + context: _context, + contextPost: _context?.postId + ? getEntityRecord( + 'postType', + _context.postType, + _context.postId + ) + : undefined, editorMode: getEditorMode(), canvasMode: getCanvasMode(), blockEditorMode: __unstableGetEditorMode(), @@ -141,18 +161,7 @@ export default function Editor( { listViewToggleElement, isLoading } ) { const secondarySidebarLabel = isListViewOpen ? __( 'List View' ) : __( 'Block Library' ); - const blockContext = useMemo( () => { - const { postType, postId, ...nonPostFields } = context ?? {}; - - return { - ...( hasPageContentFocus ? context : nonPostFields ), - // Ideally this context should be removed. However, it is currently used by the Query Loop block. - templateSlug: - editedPostType === TEMPLATE_POST_TYPE - ? editedPost.slug - : undefined, - }; - }, [ editedPost.slug, editedPostType, hasPageContentFocus, context ] ); + const hasTemplate = hasPageContentFocus && context?.postId; let title; if ( hasLoadedPost ) { @@ -180,104 +189,102 @@ export default function Editor( { listViewToggleElement, isLoading } ) { 'aria-describedby': loadingProgressId, } : undefined; + const settings = useSpecificEditorSettings(); return ( <> { isLoading ? : null } { isEditMode && } - - - - - { isEditMode && } - } - content={ + + + { isEditMode && } + } + content={ + <> + + { isEditMode && } + { showVisualEditor && editedPost && ( <> - - { isEditMode && } - { showVisualEditor && editedPost && ( - <> - - - - - ) } - { editorMode === 'text' && - editedPost && - isEditMode && } - { hasLoadedPost && ! editedPost && ( - - { __( - "You attempted to edit an item that doesn't exist. Perhaps it was deleted?" - ) } - - ) } - { isEditMode && ( - - ) } - - } - contentProps={ contentProps } - secondarySidebar={ - isEditMode && - ( ( shouldShowInserter && ( - - ) ) || - ( shouldShowListView && ( - - ) ) ) - } - sidebar={ - isEditMode && - isRightSidebarOpen && ( - <> - - - - ) - } - footer={ - shouldShowBlockBreadcrumbs && ( - + + + + + - ) - } - labels={ { - ...interfaceLabels, - secondarySidebar: secondarySidebarLabel, - } } - /> - - - + + + ) } + { editorMode === 'text' && + editedPost && + isEditMode && } + { hasLoadedPost && ! editedPost && ( + + { __( + "You attempted to edit an item that doesn't exist. Perhaps it was deleted?" + ) } + + ) } + { isEditMode && } + + } + contentProps={ contentProps } + secondarySidebar={ + isEditMode && + ( ( shouldShowInserter && ) || + ( shouldShowListView && ( + + ) ) ) + } + sidebar={ + isEditMode && + isRightSidebarOpen && ( + <> + + + + ) + } + footer={ + shouldShowBlockBreadcrumbs && ( + + ) + } + labels={ { + ...interfaceLabels, + secondarySidebar: secondarySidebarLabel, + } } + /> + ); } 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 fde2b63d6e833..31048106116d2 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 @@ -21,7 +21,7 @@ import { store as editSiteStore } from '../../../store'; import SwapTemplateButton from './swap-template-button'; import ResetDefaultTemplate from './reset-default-template'; import { unlock } from '../../../lock-unlock'; -import usePageContentBlocks from '../../block-editor/block-editor-provider/use-page-content-blocks'; +import usePageContentBlocks from '../../block-editor/use-page-content-blocks'; const POPOVER_PROPS = { className: 'edit-site-page-panels-edit-template__dropdown', diff --git a/packages/editor/src/components/provider/index.js b/packages/editor/src/components/provider/index.js index 6037c6496cc0c..9334ff99de998 100644 --- a/packages/editor/src/components/provider/index.js +++ b/packages/editor/src/components/provider/index.js @@ -1,7 +1,12 @@ /** * WordPress dependencies */ -import { useEffect, useLayoutEffect, useMemo } from '@wordpress/element'; +import { + useEffect, + useLayoutEffect, + useMemo, + useState, +} from '@wordpress/element'; import { useDispatch, useSelect } from '@wordpress/data'; import { __ } from '@wordpress/i18n'; import { EntityProvider, useEntityBlockEditor } from '@wordpress/core-data'; @@ -9,9 +14,11 @@ import { BlockEditorProvider, BlockContextProvider, privateApis as blockEditorPrivateApis, + store as blockEditorStore, } from '@wordpress/block-editor'; import { store as noticesStore } from '@wordpress/notices'; import { privateApis as editPatternsPrivateApis } from '@wordpress/patterns'; +import { createBlock } from '@wordpress/blocks'; /** * Internal dependencies @@ -24,9 +31,86 @@ import { unlock } from '../../lock-unlock'; const { ExperimentalBlockEditorProvider } = unlock( blockEditorPrivateApis ); const { PatternsMenuItems } = unlock( editPatternsPrivateApis ); +const noop = () => {}; + +/** + * For the Navigation block editor, we need to force the block editor to contentOnly for that block. + * + * Set block editing mode to contentOnly when entering Navigation focus mode. + * this ensures that non-content controls on the block will be hidden and thus + * the user can focus on editing the Navigation Menu content only. + * + * @param {string} navigationBlockClientId ClientId. + */ +function useForceFocusModeForNavigation( navigationBlockClientId ) { + const { setBlockEditingMode, unsetBlockEditingMode } = + useDispatch( blockEditorStore ); + + useEffect( () => { + if ( ! navigationBlockClientId ) { + return; + } + + setBlockEditingMode( navigationBlockClientId, 'contentOnly' ); + + return () => { + unsetBlockEditingMode( navigationBlockClientId ); + }; + }, [ + navigationBlockClientId, + unsetBlockEditingMode, + setBlockEditingMode, + ] ); +} + +/** + * Depending on the post, template and template mode, + * returns the appropriate blocks and change handlers for the block editor provider. + * + * @param {Array} post Block list. + * @param {boolean} template Whether the page content has focus (and the surrounding template is inert). If `true` return page content blocks. Default `false`. + * @param {boolean} templateMode Whether to wrap the page content blocks in a group block to mimic the post editor. Default `false`. + * @return {Array} Block editor props. + */ +function useBlockEditorProps( post, template, templateMode ) { + const rootLevelPost = + !! template && templateMode !== 'hidden' ? template : post; + const { type, id } = rootLevelPost; + const [ blocks, onInput, onChange ] = useEntityBlockEditor( + 'postType', + type, + { id } + ); + const actualBlocks = useMemo( () => { + if ( type === 'wp_navigation' ) { + return [ + createBlock( 'core/navigation', { + ref: id, + // As the parent editor is locked with `templateLock`, the template locking + // must be explicitly "unset" on the block itself to allow the user to modify + // the block's content. + templateLock: false, + } ), + ]; + } + }, [ type, id ] ); + const allowRootLevelChanges = + !! template && templateMode !== 'disabled' && type === 'wp_navigation'; + const navigationBlockClientId = + type === 'wp_navigation' && actualBlocks && actualBlocks[ 0 ]?.clientId; + useForceFocusModeForNavigation( navigationBlockClientId ); + + return [ + actualBlocks ?? blocks, + allowRootLevelChanges ? onInput : noop, + allowRootLevelChanges ? onChange : noop, + ]; +} + export const ExperimentalEditorProvider = withRegistryProvider( ( { __unstableTemplate, + templateMode = 'all', post, settings, recovery, @@ -34,12 +118,24 @@ export const ExperimentalEditorProvider = withRegistryProvider( children, BlockEditorProviderComponent = ExperimentalBlockEditorProvider, } ) => { + const rootLevelPost = + !! __unstableTemplate && templateMode !== 'hidden' + ? __unstableTemplate + : post; const defaultBlockContext = useMemo( () => { - if ( post.type === 'wp_template' ) { - return {}; - } - return { postId: post.id, postType: post.type }; - }, [ post.id, post.type ] ); + const postContext = + post.type !== 'wp_template' + ? { postId: post.id, postType: post.type } + : {}; + + return { + ...postContext, + templateSlug: + rootLevelPost.type === 'wp_template' + ? rootLevelPost.slug + : undefined, + }; + }, [ post.id, post.type, rootLevelPost.type, rootLevelPost?.slug ] ); const { editorSettings, selection, isReady } = useSelect( ( select ) => { const { @@ -55,17 +151,18 @@ export const ExperimentalEditorProvider = withRegistryProvider( }, [] ); - const { id, type } = __unstableTemplate ?? post; - const [ blocks, onInput, onChange ] = useEntityBlockEditor( - 'postType', - type, - { id } - ); + const { id, type } = rootLevelPost; const blockEditorSettings = useBlockEditorSettings( editorSettings, type, id ); + const [ blocks, onInput, onChange ] = useBlockEditorProps( + post, + __unstableTemplate, + templateMode + ); + const { updatePostLock, setupEditor, @@ -109,7 +206,7 @@ export const ExperimentalEditorProvider = withRegistryProvider( // Synchronize the editor settings as they change. useEffect( () => { updateEditorSettings( settings ); - }, [ settings ] ); + }, [ settings, updateEditorSettings ] ); if ( ! isReady ) { return null; diff --git a/packages/editor/src/components/provider/use-block-editor-settings.js b/packages/editor/src/components/provider/use-block-editor-settings.js index c0804febab0db..e2cbba7e6a757 100644 --- a/packages/editor/src/components/provider/use-block-editor-settings.js +++ b/packages/editor/src/components/provider/use-block-editor-settings.js @@ -67,8 +67,6 @@ const BLOCK_EDITOR_SETTINGS = [ 'postsPerPage', 'readOnly', 'styles', - 'template', - 'templateLock', 'titlePlaceholder', 'supportsLayout', 'widgetTypesToHideFromLegacyWidgetBlock', @@ -237,6 +235,12 @@ function useBlockEditorSettings( settings, postType, postId ) { pageOnFront, pageForPosts, __experimentalPreferPatternsOnRoot: postType === 'wp_template', + templateLock: + postType === 'wp_navigation' ? 'insert' : settings.templateLock, + template: + postType === 'wp_navigation' + ? [ [ 'core/navigation', {}, [] ] ] + : settings.template, } ), [ settings, From f2039412146286d99249f647ffd9b0f9b45565d0 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Fri, 10 Nov 2023 14:58:36 +0100 Subject: [PATCH 02/14] Only render editor provider when ready --- .../edit-site/src/components/editor/index.js | 182 +++++++++--------- .../components/interface-skeleton/index.js | 2 - 2 files changed, 90 insertions(+), 94 deletions(-) diff --git a/packages/edit-site/src/components/editor/index.js b/packages/edit-site/src/components/editor/index.js index a47ef3c33817c..21e9be06cdade 100644 --- a/packages/edit-site/src/components/editor/index.js +++ b/packages/edit-site/src/components/editor/index.js @@ -183,108 +183,106 @@ export default function Editor( { listViewToggleElement, isLoading } ) { 'edit-site-editor__loading-progress' ); - const contentProps = isLoading - ? { - 'aria-busy': 'true', - 'aria-describedby': loadingProgressId, - } - : undefined; const settings = useSpecificEditorSettings(); + const isReady = + ! isLoading && + ( ( hasTemplate && !! contextPost && !! editedPost ) || + ( ! hasTemplate && !! editedPost ) ); return ( <> - { isLoading ? : null } + { ! isReady ? : null } { isEditMode && } - - - { isEditMode && } - + + { isEditMode && } + } + content={ + <> + + { isEditMode && } + { showVisualEditor && editedPost && ( + <> + + + + + + + + + ) } + { editorMode === 'text' && + editedPost && + isEditMode && } + { hasLoadedPost && ! editedPost && ( + + { __( + "You attempted to edit an item that doesn't exist. Perhaps it was deleted?" + ) } + + ) } + { isEditMode && } + } - ) } - notices={ } - content={ - <> - - { isEditMode && } - { showVisualEditor && editedPost && ( - <> - - - - - - ) || + ( shouldShowListView && ( + - + ) ) ) + } + sidebar={ + isEditMode && + isRightSidebarOpen && ( + <> + + - ) } - { editorMode === 'text' && - editedPost && - isEditMode && } - { hasLoadedPost && ! editedPost && ( - - { __( - "You attempted to edit an item that doesn't exist. Perhaps it was deleted?" - ) } - - ) } - { isEditMode && } - - } - contentProps={ contentProps } - secondarySidebar={ - isEditMode && - ( ( shouldShowInserter && ) || - ( shouldShowListView && ( - - ) ) ) - } - sidebar={ - isEditMode && - isRightSidebarOpen && ( - <> - - - - ) - } - footer={ - shouldShowBlockBreadcrumbs && ( - - ) - } - labels={ { - ...interfaceLabels, - secondarySidebar: secondarySidebarLabel, - } } - /> - + ) + } + labels={ { + ...interfaceLabels, + secondarySidebar: secondarySidebarLabel, + } } + /> + + ) } ); } diff --git a/packages/interface/src/components/interface-skeleton/index.js b/packages/interface/src/components/interface-skeleton/index.js index 58684ebaddd7e..baf98d153ed87 100644 --- a/packages/interface/src/components/interface-skeleton/index.js +++ b/packages/interface/src/components/interface-skeleton/index.js @@ -52,7 +52,6 @@ function InterfaceSkeleton( secondarySidebar, notices, content, - contentProps, actions, labels, className, @@ -151,7 +150,6 @@ function InterfaceSkeleton( { content } From 31a3a63abc71e9fb919c6b43bea0dac85900d83c Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Mon, 13 Nov 2023 08:20:41 +0100 Subject: [PATCH 03/14] Fix linting --- packages/editor/src/components/provider/index.js | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/editor/src/components/provider/index.js b/packages/editor/src/components/provider/index.js index 9334ff99de998..778f471d64c5e 100644 --- a/packages/editor/src/components/provider/index.js +++ b/packages/editor/src/components/provider/index.js @@ -1,12 +1,7 @@ /** * WordPress dependencies */ -import { - useEffect, - useLayoutEffect, - useMemo, - useState, -} from '@wordpress/element'; +import { useEffect, useLayoutEffect, useMemo } from '@wordpress/element'; import { useDispatch, useSelect } from '@wordpress/data'; import { __ } from '@wordpress/i18n'; import { EntityProvider, useEntityBlockEditor } from '@wordpress/core-data'; From 060bada92d71f740471e791f69487bc3ec297314 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Mon, 13 Nov 2023 08:46:26 +0100 Subject: [PATCH 04/14] Fix block editor onchange handler --- packages/editor/src/components/provider/index.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/editor/src/components/provider/index.js b/packages/editor/src/components/provider/index.js index 778f471d64c5e..86aba0ca1e1fa 100644 --- a/packages/editor/src/components/provider/index.js +++ b/packages/editor/src/components/provider/index.js @@ -89,16 +89,17 @@ function useBlockEditorProps( post, template, templateMode ) { ]; } }, [ type, id ] ); - const allowRootLevelChanges = - !! template && templateMode !== 'disabled' && type === 'wp_navigation'; + const disableRootLevelChanges = + ( !! template && templateMode === 'disabled' ) || + type === 'wp_navigation'; const navigationBlockClientId = type === 'wp_navigation' && actualBlocks && actualBlocks[ 0 ]?.clientId; useForceFocusModeForNavigation( navigationBlockClientId ); return [ actualBlocks ?? blocks, - allowRootLevelChanges ? onInput : noop, - allowRootLevelChanges ? onChange : noop, + disableRootLevelChanges ? noop : onInput, + disableRootLevelChanges ? noop : onChange, ]; } From 9552a7edb3226fe83afc5f30052a3bacb373ab45 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Mon, 13 Nov 2023 10:45:25 +0100 Subject: [PATCH 05/14] Move not found entity message outside entity provider --- .../edit-site/src/components/editor/index.js | 23 ++++++++----------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/packages/edit-site/src/components/editor/index.js b/packages/edit-site/src/components/editor/index.js index 21e9be06cdade..0af5478e7be0b 100644 --- a/packages/edit-site/src/components/editor/index.js +++ b/packages/edit-site/src/components/editor/index.js @@ -193,6 +193,13 @@ export default function Editor( { listViewToggleElement, isLoading } ) { <> { ! isReady ? : null } { isEditMode && } + { hasLoadedPost && ! editedPost && ( + + { __( + "You attempted to edit an item that doesn't exist. Perhaps it was deleted?" + ) } + + ) } { isReady && ( { isEditMode && } - { showVisualEditor && editedPost && ( + { showVisualEditor && ( <> @@ -229,18 +236,8 @@ export default function Editor( { listViewToggleElement, isLoading } ) { ) } - { editorMode === 'text' && - editedPost && - isEditMode && } - { hasLoadedPost && ! editedPost && ( - - { __( - "You attempted to edit an item that doesn't exist. Perhaps it was deleted?" - ) } - + { editorMode === 'text' && isEditMode && ( + ) } { isEditMode && } From 3dc1b8f404d1b07525fb8ca1171e2a53e43bb902 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Mon, 13 Nov 2023 12:28:14 +0100 Subject: [PATCH 06/14] Fix focus loss test --- .../src/components/provider/use-block-sync.js | 23 ++----------------- .../specs/site-editor/template-part.spec.js | 16 +++++++++---- 2 files changed, 14 insertions(+), 25 deletions(-) diff --git a/packages/block-editor/src/components/provider/use-block-sync.js b/packages/block-editor/src/components/provider/use-block-sync.js index 58aca847d80de..4f2300f380892 100644 --- a/packages/block-editor/src/components/provider/use-block-sync.js +++ b/packages/block-editor/src/components/provider/use-block-sync.js @@ -76,18 +76,11 @@ export default function useBlockSync( { resetBlocks, resetSelection, replaceInnerBlocks, - selectBlock, setHasControlledInnerBlocks, __unstableMarkNextChangeAsNotPersistent, } = registry.dispatch( blockEditorStore ); - const { - hasSelectedBlock, - getBlockName, - getBlocks, - getSelectionStart, - getSelectionEnd, - getBlock, - } = registry.select( blockEditorStore ); + const { getBlockName, getBlocks, getSelectionStart, getSelectionEnd } = + registry.select( blockEditorStore ); const isControlled = useSelect( ( select ) => { return ( @@ -180,9 +173,6 @@ export default function useBlockSync( { // bound sync, unset the outbound value to avoid considering it in // subsequent renders. pendingChanges.current.outgoing = []; - const hadSelection = hasSelectedBlock(); - const selectionAnchor = getSelectionStart(); - const selectionFocus = getSelectionEnd(); setControlledBlocks(); if ( controlledSelection ) { @@ -191,15 +181,6 @@ export default function useBlockSync( { controlledSelection.selectionEnd, controlledSelection.initialPosition ); - } else { - const selectionStillExists = getBlock( - selectionAnchor.clientId - ); - if ( hadSelection && ! selectionStillExists ) { - selectBlock( clientId ); - } else { - resetSelection( selectionAnchor, selectionFocus ); - } } } }, [ controlledBlocks, clientId ] ); diff --git a/test/e2e/specs/site-editor/template-part.spec.js b/test/e2e/specs/site-editor/template-part.spec.js index ff4610a57ba27..9d4e1385627e7 100644 --- a/test/e2e/specs/site-editor/template-part.spec.js +++ b/test/e2e/specs/site-editor/template-part.spec.js @@ -382,9 +382,19 @@ test.describe( 'Template Part', () => { // Insert a group block with a Site Title block inside. await editor.insertBlock( { name: 'core/group', - innerBlocks: [ { name: 'core/site-title' } ], + innerBlocks: [ + { name: 'core/paragraph', attributes: { content: 'Hello' } }, + { name: 'core/site-title' }, + ], } ); + // Type within a first block. + const paragraph = editor.canvas.getByRole( 'document', { + name: 'Paragraph', + } ); + await editor.selectBlocks( paragraph ); + await page.keyboard.type( 'Modify' ); + // Select the Site Title block inside the group. const siteTitleInGroup = editor.canvas.getByRole( 'document', { name: 'Site title', @@ -401,8 +411,6 @@ test.describe( 'Template Part', () => { // Undo the change. await pageUtils.pressKeys( 'primary+z' ); - await expect( - page.locator( 'role=button[name="Change level"i]' ) - ).toBeFocused(); + await expect( paragraph ).toBeFocused(); } ); } ); From 76a2f2977b3489054af87438558ea73fe5efbc53 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Mon, 13 Nov 2023 14:13:20 +0100 Subject: [PATCH 07/14] Support hidden template mode --- .../test/use-page-content-blocks.js | 116 ------------------ .../block-editor/use-page-content-blocks.js | 90 -------------- .../edit-site/src/components/editor/index.js | 20 +++ .../page-panels/edit-template.js | 28 ++--- .../editor/src/components/provider/index.js | 24 +++- 5 files changed, 55 insertions(+), 223 deletions(-) delete mode 100644 packages/edit-site/src/components/block-editor/test/use-page-content-blocks.js delete mode 100644 packages/edit-site/src/components/block-editor/use-page-content-blocks.js diff --git a/packages/edit-site/src/components/block-editor/test/use-page-content-blocks.js b/packages/edit-site/src/components/block-editor/test/use-page-content-blocks.js deleted file mode 100644 index 775aea0bdb391..0000000000000 --- a/packages/edit-site/src/components/block-editor/test/use-page-content-blocks.js +++ /dev/null @@ -1,116 +0,0 @@ -/** - * 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( { - blocks: blocksList, - isPageContentFocused: false, - } ) - ); - expect( result.current ).toEqual( [] ); - } ); - it( 'should return empty array if `blocks` is undefined', () => { - const { result } = renderHook( () => - usePageContentBlocks( { - blocks: undefined, - isPageContentFocused: true, - } ) - ); - expect( result.current ).toEqual( [] ); - } ); - it( 'should return empty array if `blocks` is an empty array', () => { - const { result } = renderHook( () => - usePageContentBlocks( { - blocks: [], - isPageContentFocused: true, - } ) - ); - expect( result.current ).toEqual( [] ); - } ); - it( 'should return new block list', () => { - const { result } = renderHook( () => - usePageContentBlocks( { - blocks: blocksList, - isPageContentFocused: true, - } ) - ); - expect( result.current ).toEqual( [ - createBlock( 'core/post-title' ), - createBlock( 'core/post-featured-image' ), - createBlock( 'core/post-content' ), - createBlock( 'core/post-content' ), - ] ); - } ); - it( 'should return new block list wrapped in a Group block', () => { - const { result } = renderHook( () => - usePageContentBlocks( { - blocks: blocksList, - isPageContentFocused: true, - wrapPageContent: 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/use-page-content-blocks.js b/packages/edit-site/src/components/block-editor/use-page-content-blocks.js deleted file mode 100644 index 9201b7816286e..0000000000000 --- a/packages/edit-site/src/components/block-editor/use-page-content-blocks.js +++ /dev/null @@ -1,90 +0,0 @@ -/** - * 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 an optional group block to mimic the post editor. - * - * @param {Object} props The argument for the function. - * @param {Array} props.blocks Block list. - * @param {boolean} props.isPageContentFocused Whether the page content has focus (and the surrounding template is inert). If `true` return page content blocks. Default `false`. - * @param {boolean} props.wrapPageContent Whether to wrap the page content blocks in a group block to mimic the post editor. Default `false`. - * @return {Array} Page content blocks. - */ -export default function usePageContentBlocks( { - blocks = [], - isPageContentFocused = false, - wrapPageContent = false, -} ) { - return useMemo( () => { - if ( ! isPageContentFocused || ! blocks?.length ) { - return []; - } - - const innerBlocks = flattenBlocks( blocks, ( block ) => - PAGE_CONTENT_BLOCK_TYPES[ block.name ] - ? createBlock( block.name ) - : undefined - ); - - if ( ! innerBlocks.length ) { - return []; - } - - if ( ! wrapPageContent ) { - return innerBlocks; - } - - return [ - createBlock( - 'core/group', - { - layout: { type: 'constrained' }, - style: { - spacing: { - margin: { - top: '4em', // Mimics the post editor. - }, - }, - }, - }, - innerBlocks - ), - ]; - }, [ blocks, isPageContentFocused ] ); -} diff --git a/packages/edit-site/src/components/editor/index.js b/packages/edit-site/src/components/editor/index.js index 0af5478e7be0b..c6b1bd3e4c0c4 100644 --- a/packages/edit-site/src/components/editor/index.js +++ b/packages/edit-site/src/components/editor/index.js @@ -28,6 +28,7 @@ import { } from '@wordpress/editor'; import { __, sprintf } from '@wordpress/i18n'; import { store as coreDataStore } from '@wordpress/core-data'; +import { useMemo } from '@wordpress/element'; /** * Internal dependencies @@ -103,6 +104,7 @@ export default function Editor( { listViewToggleElement, isLoading } ) { showIconLabels, showBlockBreadcrumbs, hasPageContentFocus, + pageContentFocusType, } = useSelect( ( select ) => { const { getEditedPostContext, @@ -111,6 +113,7 @@ export default function Editor( { listViewToggleElement, isLoading } ) { isInserterOpened, isListViewOpened, hasPageContentFocus: _hasPageContentFocus, + getPageContentFocusType, } = unlock( select( editSiteStore ) ); const { __unstableGetEditorMode } = select( blockEditorStore ); const { getActiveComplementaryArea } = select( interfaceStore ); @@ -145,6 +148,7 @@ export default function Editor( { listViewToggleElement, isLoading } ) { 'showBlockBreadcrumbs' ), hasPageContentFocus: _hasPageContentFocus(), + pageContentFocusType: getPageContentFocusType(), }; }, [] ); @@ -188,6 +192,21 @@ export default function Editor( { listViewToggleElement, isLoading } ) { ! isLoading && ( ( hasTemplate && !! contextPost && !! editedPost ) || ( ! hasTemplate && !! editedPost ) ); + const templateMode = useMemo( () => { + if ( isViewMode ) { + return 'disabled'; + } + + if ( isEditMode && pageContentFocusType === 'hideTemplate' ) { + return 'hidden'; + } + + if ( hasTemplate ) { + return 'disabled'; + } + + return 'all'; + }, [ isViewMode, isEditMode, hasTemplate, pageContentFocusType ] ); return ( <> @@ -206,6 +225,7 @@ export default function Editor( { listViewToggleElement, isLoading } ) { __unstableTemplate={ hasTemplate ? editedPost : undefined } settings={ settings } useSubRegistry={ false } + templateMode={ templateMode } > { isEditMode && } 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 31048106116d2..25a239e12992c 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 @@ -11,8 +11,9 @@ import { __experimentalText as Text, } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; -import { store as coreStore, useEntityBlockEditor } from '@wordpress/core-data'; +import { store as coreStore } from '@wordpress/core-data'; import { check } from '@wordpress/icons'; +import { store as blockEditorStore } from '@wordpress/block-editor'; /** * Internal dependencies @@ -21,7 +22,7 @@ import { store as editSiteStore } from '../../../store'; import SwapTemplateButton from './swap-template-button'; import ResetDefaultTemplate from './reset-default-template'; import { unlock } from '../../../lock-unlock'; -import usePageContentBlocks from '../../block-editor/use-page-content-blocks'; +import { PAGE_CONTENT_BLOCK_TYPES } from '../../../utils/constants'; const POPOVER_PROPS = { className: 'edit-site-page-panels-edit-template__dropdown', @@ -29,8 +30,8 @@ const POPOVER_PROPS = { }; export default function EditTemplate() { - const { hasResolved, template, isTemplateHidden, postType } = useSelect( - ( select ) => { + const { hasPostContentBlocks, hasResolved, template, isTemplateHidden } = + useSelect( ( select ) => { const { getEditedPostContext, getEditedPostType, getEditedPostId } = select( editSiteStore ); const { getCanvasMode, getPageContentFocusType } = unlock( @@ -38,6 +39,8 @@ export default function EditTemplate() { ); const { getEditedEntityRecord, hasFinishedResolution } = select( coreStore ); + const { __experimentalGetGlobalBlocksByName } = + select( blockEditorStore ); const _context = getEditedPostContext(); const _postType = getEditedPostType(); const queryArgs = [ @@ -45,8 +48,10 @@ export default function EditTemplate() { getEditedPostType(), getEditedPostId(), ]; - return { + hasPostContentBlocks: !! __experimentalGetGlobalBlocksByName( + Object.keys( PAGE_CONTENT_BLOCK_TYPES ) + ).length, context: _context, hasResolved: hasFinishedResolution( 'getEditedEntityRecord', @@ -58,21 +63,12 @@ export default function EditTemplate() { getPageContentFocusType() === 'hideTemplate', postType: _postType, }; - }, - [] - ); - - const [ blocks ] = useEntityBlockEditor( 'postType', postType ); + }, [] ); 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 ) ); - // Check if there are any post content block types in the blocks tree. - const pageContentBlocks = usePageContentBlocks( { - blocks, - isPageContentFocused: true, - } ); if ( ! hasResolved ) { return null; @@ -108,7 +104,7 @@ export default function EditTemplate() { - { !! pageContentBlocks?.length && ( + { hasPostContentBlocks && ( Date: Mon, 13 Nov 2023 14:58:02 +0100 Subject: [PATCH 08/14] Update modes --- .../edit-site/src/components/editor/index.js | 24 ++++++---- .../editor/src/components/provider/index.js | 44 ++++++++++--------- 2 files changed, 39 insertions(+), 29 deletions(-) diff --git a/packages/edit-site/src/components/editor/index.js b/packages/edit-site/src/components/editor/index.js index c6b1bd3e4c0c4..7c88d8b294485 100644 --- a/packages/edit-site/src/components/editor/index.js +++ b/packages/edit-site/src/components/editor/index.js @@ -165,7 +165,7 @@ export default function Editor( { listViewToggleElement, isLoading } ) { const secondarySidebarLabel = isListViewOpen ? __( 'List View' ) : __( 'Block Library' ); - const hasTemplate = hasPageContentFocus && context?.postId; + const hasTemplate = context?.postId; let title; if ( hasLoadedPost ) { @@ -192,21 +192,27 @@ export default function Editor( { listViewToggleElement, isLoading } ) { ! isLoading && ( ( hasTemplate && !! contextPost && !! editedPost ) || ( ! hasTemplate && !! editedPost ) ); - const templateMode = useMemo( () => { + const mode = useMemo( () => { if ( isViewMode ) { - return 'disabled'; + return 'locked'; } if ( isEditMode && pageContentFocusType === 'hideTemplate' ) { - return 'hidden'; + return 'post-only'; } - if ( hasTemplate ) { - return 'disabled'; + if ( hasTemplate && hasPageContentFocus ) { + return 'locked'; } - return 'all'; - }, [ isViewMode, isEditMode, hasTemplate, pageContentFocusType ] ); + return 'template-only'; + }, [ + isViewMode, + isEditMode, + hasTemplate, + pageContentFocusType, + hasPageContentFocus, + ] ); return ( <> @@ -225,7 +231,7 @@ export default function Editor( { listViewToggleElement, isLoading } ) { __unstableTemplate={ hasTemplate ? editedPost : undefined } settings={ settings } useSubRegistry={ false } - templateMode={ templateMode } + mode={ mode } > { isEditMode && } diff --git a/packages/editor/src/components/provider/index.js b/packages/editor/src/components/provider/index.js index 778705a0fc1b5..79e70073d4f3a 100644 --- a/packages/editor/src/components/provider/index.js +++ b/packages/editor/src/components/provider/index.js @@ -62,14 +62,13 @@ function useForceFocusModeForNavigation( navigationBlockClientId ) { * Depending on the post, template and template mode, * returns the appropriate blocks and change handlers for the block editor provider. * - * @param {Array} post Block list. - * @param {boolean} template Whether the page content has focus (and the surrounding template is inert). If `true` return page content blocks. Default `false`. - * @param {boolean} templateMode Whether to wrap the page content blocks in a group block to mimic the post editor. Default `false`. + * @param {Array} post Block list. + * @param {boolean} template Whether the page content has focus (and the surrounding template is inert). If `true` return page content blocks. Default `false`. + * @param {boolean} mode Rendering mode. * @return {Array} Block editor props. */ -function useBlockEditorProps( post, template, templateMode ) { - const rootLevelPost = - !! template && templateMode !== 'hidden' ? template : post; +function useBlockEditorProps( post, template, mode ) { + const rootLevelPost = mode === 'post-only' || ! template ? post : template; const { type, id } = rootLevelPost; const [ blocks, onInput, onChange ] = useEntityBlockEditor( 'postType', @@ -89,7 +88,7 @@ function useBlockEditorProps( post, template, templateMode ) { ]; } - if ( !! template && templateMode === 'hidden' ) { + if ( mode === 'post-only' ) { return [ createBlock( 'core/group', @@ -110,10 +109,9 @@ function useBlockEditorProps( post, template, templateMode ) { ), ]; } - }, [ type, id, templateMode, template ] ); + }, [ type, id, mode ] ); const disableRootLevelChanges = - ( !! template && templateMode === 'disabled' ) || - type === 'wp_navigation'; + ( !! template && mode === 'locked' ) || type === 'wp_navigation'; const navigationBlockClientId = type === 'wp_navigation' && actualBlocks && actualBlocks[ 0 ]?.clientId; useForceFocusModeForNavigation( navigationBlockClientId ); @@ -127,22 +125,21 @@ function useBlockEditorProps( post, template, templateMode ) { export const ExperimentalEditorProvider = withRegistryProvider( ( { - __unstableTemplate, - templateMode = 'all', + mode = 'all', post, settings, recovery, initialEdits, children, BlockEditorProviderComponent = ExperimentalBlockEditorProvider, + __unstableTemplate: template, } ) => { - const rootLevelPost = - !! __unstableTemplate && templateMode !== 'hidden' - ? __unstableTemplate - : post; + const shouldRenderTemplate = !! template && mode !== 'post-only'; + const rootLevelPost = shouldRenderTemplate ? template : post; const defaultBlockContext = useMemo( () => { const postContext = - post.type !== 'wp_template' + rootLevelPost.type !== 'wp_template' || + ( shouldRenderTemplate && mode !== 'template-only' ) ? { postId: post.id, postType: post.type } : {}; @@ -153,7 +150,14 @@ export const ExperimentalEditorProvider = withRegistryProvider( ? rootLevelPost.slug : undefined, }; - }, [ post.id, post.type, rootLevelPost.type, rootLevelPost?.slug ] ); + }, [ + mode, + post.id, + post.type, + rootLevelPost.type, + rootLevelPost?.slug, + shouldRenderTemplate, + ] ); const { editorSettings, selection, isReady } = useSelect( ( select ) => { const { @@ -177,8 +181,8 @@ export const ExperimentalEditorProvider = withRegistryProvider( ); const [ blocks, onInput, onChange ] = useBlockEditorProps( post, - __unstableTemplate, - templateMode + template, + mode ); const { From ef3545c28e320a2519880fc9a7b536bb02fd8983 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Tue, 14 Nov 2023 08:52:29 +0100 Subject: [PATCH 09/14] Cleanup --- .../src/components/block-editor/use-site-editor-settings.js | 2 -- 1 file changed, 2 deletions(-) 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 1587b8ba4a2d4..cb3fb3f1cb333 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 @@ -130,8 +130,6 @@ export function useSpecificEditorSettings() { ), canvasMode: getCanvasMode(), settings: getSettings(), - postType: usedPostType, - postId: usedPostId, }; }, [] ); const archiveLabels = useArchiveLabel( templateSlug ); From c34e32287f5ce96980217eabfbf2a7312f50aad8 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Wed, 15 Nov 2023 09:54:19 +0100 Subject: [PATCH 10/14] Restore template preview computed template --- packages/core-data/src/entity-provider.js | 9 +- .../editor/src/components/provider/index.js | 90 +++++++++++++++---- 2 files changed, 80 insertions(+), 19 deletions(-) diff --git a/packages/core-data/src/entity-provider.js b/packages/core-data/src/entity-provider.js index e2274629006ee..4b82b62e318bc 100644 --- a/packages/core-data/src/entity-provider.js +++ b/packages/core-data/src/entity-provider.js @@ -155,6 +155,9 @@ export function useEntityBlockEditor( kind, name, { id: _id } = {} ) { const id = _id ?? providerId; const { content, editedBlocks, meta } = useSelect( ( select ) => { + if ( ! id ) { + return {}; + } const { getEditedEntityRecord } = select( STORE_NAME ); const editedRecord = getEditedEntityRecord( kind, name, id ); return { @@ -169,6 +172,10 @@ export function useEntityBlockEditor( kind, name, { id: _id } = {} ) { useDispatch( STORE_NAME ); const blocks = useMemo( () => { + if ( ! id ) { + return undefined; + } + if ( editedBlocks ) { return editedBlocks; } @@ -176,7 +183,7 @@ export function useEntityBlockEditor( kind, name, { id: _id } = {} ) { return content && typeof content !== 'function' ? parse( content ) : EMPTY_ARRAY; - }, [ editedBlocks, content ] ); + }, [ id, editedBlocks, content ] ); const updateFootnotes = useCallback( ( _blocks ) => updateFootnotesFromMeta( _blocks, meta ), diff --git a/packages/editor/src/components/provider/index.js b/packages/editor/src/components/provider/index.js index 79e70073d4f3a..ee5cf695b3063 100644 --- a/packages/editor/src/components/provider/index.js +++ b/packages/editor/src/components/provider/index.js @@ -27,6 +27,11 @@ const { ExperimentalBlockEditorProvider } = unlock( blockEditorPrivateApis ); const { PatternsMenuItems } = unlock( editPatternsPrivateApis ); const noop = () => {}; +export const PAGE_CONTENT_BLOCK_TYPES = [ + 'core/post-title', + 'core/post-featured-image', + 'core/post-content', +]; /** * For the Navigation block editor, we need to force the block editor to contentOnly for that block. @@ -58,6 +63,34 @@ 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 extractedPageContentBlockTypesFromTemplateBlocks( 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 ( [ 'core/query' ].includes( blocks[ i ].name ) ) { + continue; + } + if ( PAGE_CONTENT_BLOCK_TYPES.includes( blocks[ i ].name ) ) { + result.push( createBlock( blocks[ i ].name ) ); + } + result.push( + ...extractedPageContentBlockTypesFromTemplateBlocks( + 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. @@ -68,18 +101,22 @@ function useForceFocusModeForNavigation( navigationBlockClientId ) { * @return {Array} Block editor props. */ function useBlockEditorProps( post, template, mode ) { - const rootLevelPost = mode === 'post-only' || ! template ? post : template; - const { type, id } = rootLevelPost; - const [ blocks, onInput, onChange ] = useEntityBlockEditor( + const rootLevelPost = + mode === 'post-only' || ! template ? 'post' : 'template'; + const [ postBlocks, onInput, onChange ] = useEntityBlockEditor( 'postType', - type, - { id } + post.type, + { id: post.id } ); - const actualBlocks = useMemo( () => { - if ( type === 'wp_navigation' ) { + const [ templateBlocks, onInputTemplate, onChangeTemplate ] = + useEntityBlockEditor( 'postType', template?.type, { + id: template?.id, + } ); + const blocks = useMemo( () => { + if ( post.type === 'wp_navigation' ) { return [ createBlock( 'core/navigation', { - ref: id, + ref: post.id, // As the parent editor is locked with `templateLock`, the template locking // must be explicitly "unset" on the block itself to allow the user to modify // the block's content. @@ -102,24 +139,41 @@ function useBlockEditorProps( post, template, mode ) { }, }, }, - [ - createBlock( 'core/post-title' ), - createBlock( 'core/post-content' ), - ] + extractedPageContentBlockTypesFromTemplateBlocks( + templateBlocks + ) ), ]; } - }, [ type, id, mode ] ); + + if ( rootLevelPost === 'template' ) { + return templateBlocks; + } + + return postBlocks; + }, [ + templateBlocks, + postBlocks, + rootLevelPost, + post.type, + post.id, + mode, + ] ); const disableRootLevelChanges = - ( !! template && mode === 'locked' ) || type === 'wp_navigation'; + ( !! template && mode === 'locked' ) || + post.type === 'wp_navigation' || + mode === 'post-only'; const navigationBlockClientId = - type === 'wp_navigation' && actualBlocks && actualBlocks[ 0 ]?.clientId; + post.type === 'wp_navigation' && blocks && blocks[ 0 ]?.clientId; useForceFocusModeForNavigation( navigationBlockClientId ); + if ( disableRootLevelChanges ) { + return [ blocks, noop, noop ]; + } return [ - actualBlocks ?? blocks, - disableRootLevelChanges ? noop : onInput, - disableRootLevelChanges ? noop : onChange, + blocks, + rootLevelPost === 'post' ? onInput : onInputTemplate, + rootLevelPost === 'post' ? onChange : onChangeTemplate, ]; } From c1a1873185f64954056c4e39d7f91a4f8eca23cd Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Wed, 15 Nov 2023 10:00:48 +0100 Subject: [PATCH 11/14] Rename locked to template-locked --- .../edit-site/src/components/editor/index.js | 26 ++++++++++++------- .../editor/src/components/provider/index.js | 2 +- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/packages/edit-site/src/components/editor/index.js b/packages/edit-site/src/components/editor/index.js index 7c88d8b294485..110e891cc1858 100644 --- a/packages/edit-site/src/components/editor/index.js +++ b/packages/edit-site/src/components/editor/index.js @@ -165,7 +165,7 @@ export default function Editor( { listViewToggleElement, isLoading } ) { const secondarySidebarLabel = isListViewOpen ? __( 'List View' ) : __( 'Block Library' ); - const hasTemplate = context?.postId; + const postWithTemplate = context?.postId; let title; if ( hasLoadedPost ) { @@ -190,26 +190,30 @@ export default function Editor( { listViewToggleElement, isLoading } ) { const settings = useSpecificEditorSettings(); const isReady = ! isLoading && - ( ( hasTemplate && !! contextPost && !! editedPost ) || - ( ! hasTemplate && !! editedPost ) ); + ( ( postWithTemplate && !! contextPost && !! editedPost ) || + ( ! postWithTemplate && !! editedPost ) ); const mode = useMemo( () => { if ( isViewMode ) { - return 'locked'; + return postWithTemplate ? 'template-locked' : 'all'; } if ( isEditMode && pageContentFocusType === 'hideTemplate' ) { return 'post-only'; } - if ( hasTemplate && hasPageContentFocus ) { - return 'locked'; + if ( postWithTemplate && hasPageContentFocus ) { + return 'template-locked'; } - return 'template-only'; + if ( postWithTemplate && ! hasPageContentFocus ) { + return 'template-only'; + } + + return 'all'; }, [ isViewMode, isEditMode, - hasTemplate, + postWithTemplate, pageContentFocusType, hasPageContentFocus, ] ); @@ -227,8 +231,10 @@ export default function Editor( { listViewToggleElement, isLoading } ) { ) } { isReady && ( Date: Wed, 15 Nov 2023 10:10:51 +0100 Subject: [PATCH 12/14] Document the EditorProvider component --- .../editor/src/components/provider/README.md | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 packages/editor/src/components/provider/README.md diff --git a/packages/editor/src/components/provider/README.md b/packages/editor/src/components/provider/README.md new file mode 100644 index 0000000000000..d6b41118d8e92 --- /dev/null +++ b/packages/editor/src/components/provider/README.md @@ -0,0 +1,50 @@ +# EditorProvider + +EditorProvider is a component which establishes a new post editing context, and serves as the entry point for a new post editor (or post with template editor). + +It supports a big number of post types, including post, page, templates, custom post types, patterns, template parts. + +All modification and changes are performed to the `@wordpress/core-data` store. + +## Props + +### `post` + +- **Type:** `Object` +- **Required** `yes` + +The post object to edit + +### `__unstableTemplate` + +- **Type:** `Object` +- **Required** `no` + +The template object wrapper the edited post. This is optional and can only be used when the post type supports templates (like posts and pages). + +### `mode` + +- **Type:** `String` +- **Required** `no` +- **default** `all` + +This is the rendering mode of the post editor. We support mutiple rendering modes: + +- `all`: This is the default mode. It renders the post editor with all the features available. If a template is provided, it's prefered over the post. +- `template-only`: This mode renders the editor with only the template blocks visible. +- `post-only`: This mode extracts the post blocks from the template and renders only those. The idea is to allow the user to edit the post/page in isolation without the wrapping template. +- `template-locked`: This mode renders both the template and the post blocks but the template blocks are locked and can't be edited. The post blocks are editable. + +### `settings` + +- **Type:** `Object` +- **Required** `no` + +The settings object to use for the editor. This is optional and can be used to override the default settings. + +### `children` + +- **Type:** `Element` +- **Required** `no` + +Children elements for which the BlockEditorProvider context should apply. From e1c1cb0e705a0d79e83a712c5b2b24e567deeda2 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Thu, 16 Nov 2023 08:52:13 +0100 Subject: [PATCH 13/14] Apply suggestions from code review Co-authored-by: Ramon --- packages/editor/src/components/provider/README.md | 4 ++-- packages/editor/src/components/provider/index.js | 12 +++++++----- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/packages/editor/src/components/provider/README.md b/packages/editor/src/components/provider/README.md index d6b41118d8e92..f2b9b697a0908 100644 --- a/packages/editor/src/components/provider/README.md +++ b/packages/editor/src/components/provider/README.md @@ -28,9 +28,9 @@ The template object wrapper the edited post. This is optional and can only be us - **Required** `no` - **default** `all` -This is the rendering mode of the post editor. We support mutiple rendering modes: +This is the rendering mode of the post editor. We support multiple rendering modes: -- `all`: This is the default mode. It renders the post editor with all the features available. If a template is provided, it's prefered over the post. +- `all`: This is the default mode. It renders the post editor with all the features available. If a template is provided, it's preferred over the post. - `template-only`: This mode renders the editor with only the template blocks visible. - `post-only`: This mode extracts the post blocks from the template and renders only those. The idea is to allow the user to edit the post/page in isolation without the wrapping template. - `template-locked`: This mode renders both the template and the post blocks but the template blocks are locked and can't be edited. The post blocks are editable. diff --git a/packages/editor/src/components/provider/index.js b/packages/editor/src/components/provider/index.js index ba66893f04e3f..9717f768695ba 100644 --- a/packages/editor/src/components/provider/index.js +++ b/packages/editor/src/components/provider/index.js @@ -81,11 +81,13 @@ function extractedPageContentBlockTypesFromTemplateBlocks( blocks ) { if ( PAGE_CONTENT_BLOCK_TYPES.includes( blocks[ i ].name ) ) { result.push( createBlock( blocks[ i ].name ) ); } - result.push( - ...extractedPageContentBlockTypesFromTemplateBlocks( - blocks[ i ].innerBlocks - ) - ); + if ( blocks[ i ].innerBlocks.length ) { + result.push( + ...extractedPageContentBlockTypesFromTemplateBlocks( + blocks[ i ].innerBlocks + ) + ); + } } return result; From ea301646ad55b5f35862e647db5adbde3667820b Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Thu, 16 Nov 2023 08:55:47 +0100 Subject: [PATCH 14/14] More changes per review --- .../sidebar-edit-mode/page-panels/edit-template.js | 6 +----- packages/editor/src/components/provider/index.js | 10 +++++----- 2 files changed, 6 insertions(+), 10 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 25a239e12992c..1ee382420a7d9 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 @@ -43,11 +43,7 @@ export default function EditTemplate() { select( blockEditorStore ); const _context = getEditedPostContext(); const _postType = getEditedPostType(); - const queryArgs = [ - 'postType', - getEditedPostType(), - getEditedPostId(), - ]; + const queryArgs = [ 'postType', _postType, getEditedPostId() ]; return { hasPostContentBlocks: !! __experimentalGetGlobalBlocksByName( Object.keys( PAGE_CONTENT_BLOCK_TYPES ) diff --git a/packages/editor/src/components/provider/index.js b/packages/editor/src/components/provider/index.js index 9717f768695ba..5fa79eedef987 100644 --- a/packages/editor/src/components/provider/index.js +++ b/packages/editor/src/components/provider/index.js @@ -70,12 +70,12 @@ function useForceFocusModeForNavigation( navigationBlockClientId ) { * * @return {Array} Flattened object. */ -function extractedPageContentBlockTypesFromTemplateBlocks( blocks ) { +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 ( [ 'core/query' ].includes( blocks[ i ].name ) ) { + if ( blocks[ i ].name === 'core/query' ) { continue; } if ( PAGE_CONTENT_BLOCK_TYPES.includes( blocks[ i ].name ) ) { @@ -83,7 +83,7 @@ function extractedPageContentBlockTypesFromTemplateBlocks( blocks ) { } if ( blocks[ i ].innerBlocks.length ) { result.push( - ...extractedPageContentBlockTypesFromTemplateBlocks( + ...extractPageContentBlockTypesFromTemplateBlocks( blocks[ i ].innerBlocks ) ); @@ -99,7 +99,7 @@ function extractedPageContentBlockTypesFromTemplateBlocks( blocks ) { * * @param {Array} post Block list. * @param {boolean} template Whether the page content has focus (and the surrounding template is inert). If `true` return page content blocks. Default `false`. - * @param {boolean} mode Rendering mode. + * @param {string} mode Rendering mode. * @return {Array} Block editor props. */ function useBlockEditorProps( post, template, mode ) { @@ -141,7 +141,7 @@ function useBlockEditorProps( post, template, mode ) { }, }, }, - extractedPageContentBlockTypesFromTemplateBlocks( + extractPageContentBlockTypesFromTemplateBlocks( templateBlocks ) ),