diff --git a/lib/experimental/editor-settings.php b/lib/experimental/editor-settings.php index 919be2e6e34a45..ed4bed2b2293e4 100644 --- a/lib/experimental/editor-settings.php +++ b/lib/experimental/editor-settings.php @@ -37,6 +37,10 @@ function gutenberg_enable_experiments() { if ( $gutenberg_experiments && array_key_exists( 'gutenberg-zoom-out-experiment', $gutenberg_experiments ) ) { wp_add_inline_script( 'wp-block-editor', 'window.__experimentalEnableZoomOutExperiment = true', 'before' ); } + + if ( $gutenberg_experiments && array_key_exists( 'gutenberg-simple-editing-mode', $gutenberg_experiments ) ) { + wp_add_inline_script( 'wp-block-editor', 'window.__experimentalSimpleEditingMode = true', 'before' ); + } } add_action( 'admin_init', 'gutenberg_enable_experiments' ); diff --git a/lib/experiments-page.php b/lib/experiments-page.php index 5acd5f0f192364..16a930303fdac9 100644 --- a/lib/experiments-page.php +++ b/lib/experiments-page.php @@ -186,6 +186,19 @@ function gutenberg_initialize_experiments_settings() { 'id' => 'gutenberg-zoom-out-experiment', ) ); + + add_settings_field( + 'gutenberg-simple-editing-mode', + __( 'Simple editing mode', 'gutenberg' ), + 'gutenberg_display_experiment_field', + 'gutenberg-experiments', + 'gutenberg_experiments_section', + array( + 'label' => __( 'Add UI that lets you toggle between a simple and an advanced editing mode.', 'gutenberg' ), + 'id' => 'gutenberg-simple-editing-mode', + ) + ); + register_setting( 'gutenberg-experiments', 'gutenberg-experiments' diff --git a/packages/block-editor/src/components/block-inspector/index.js b/packages/block-editor/src/components/block-inspector/index.js index 1986ad8730c121..24c0389bdeaa42 100644 --- a/packages/block-editor/src/components/block-inspector/index.js +++ b/packages/block-editor/src/components/block-inspector/index.js @@ -100,6 +100,8 @@ const BlockInspector = ( { showNoBlockSelectedMessage = true } ) => { getBlockName, getContentLockingParent, getTemplateLock, + getClosestSectionBlock, + getBlockEditingMode, } = unlock( select( blockEditorStore ) ); const _selectedBlockClientId = getSelectedBlockClientId(); const _selectedBlockName = @@ -107,6 +109,15 @@ const BlockInspector = ( { showNoBlockSelectedMessage = true } ) => { const _blockType = _selectedBlockName && getBlockType( _selectedBlockName ); + const closestSectionBlock = getClosestSectionBlock( + _selectedBlockClientId + ); + + const closestContentOnlySectionBlock = + getBlockEditingMode( closestSectionBlock ) === 'contentOnly' + ? closestSectionBlock + : undefined; + return { count: getSelectedBlockCount(), selectedBlockClientId: _selectedBlockClientId, @@ -117,7 +128,8 @@ const BlockInspector = ( { showNoBlockSelectedMessage = true } ) => { ( getTemplateLock( _selectedBlockClientId ) === 'contentOnly' || _selectedBlockName === 'core/block' ? _selectedBlockClientId - : undefined ), + : undefined ) || + closestContentOnlySectionBlock, }; }, [] ); diff --git a/packages/block-editor/src/store/private-selectors.js b/packages/block-editor/src/store/private-selectors.js index 7e323cee6581ae..f69b10cc0541a4 100644 --- a/packages/block-editor/src/store/private-selectors.js +++ b/packages/block-editor/src/store/private-selectors.js @@ -545,6 +545,46 @@ export function isZoomOutMode( state ) { return state.editorMode === 'zoom-out'; } +/** + * Retrieves the client ID for the block that is acting as the element + * which contains the "sections" of a template/post. + * + * @param {Object} state - The current state. + * @return {string|undefined} The root client ID for the section, or undefined if not set. + */ export function getSectionRootClientId( state ) { return state.settings?.[ sectionRootClientIdKey ]; } + +/** + * Retrieves the client IDs for the individual "sections" + * within the current template/post. + * + * @param {Object} state - The current state. + * @return {Array} An array of client IDs for the blocks within the section. + */ +export function getSectionClientIds( state ) { + const sectionRootClientId = getSectionRootClientId( state ); + return getBlockOrder( state, sectionRootClientId ); +} + +/** + * Retrieves the closest "section" block to the given client ID. + * + * @param {Object} state - The current state. + * @param {string} clientId - The client ID to start the search from. + * @return {string|undefined} The client ID of the closest section block, or undefined if not found. + */ +export function getClosestSectionBlock( state, clientId ) { + let current = clientId; + let result; + const sectionClientIds = getSectionClientIds( state ); + while ( current ) { + if ( sectionClientIds.includes( current ) ) { + result = current; + break; + } + current = state.blocks.parents.get( current ); + } + return result; +} diff --git a/packages/editor/src/components/document-tools/index.js b/packages/editor/src/components/document-tools/index.js index 54121652bbf131..8f74ab96d42d23 100644 --- a/packages/editor/src/components/document-tools/index.js +++ b/packages/editor/src/components/document-tools/index.js @@ -27,6 +27,7 @@ import { unlock } from '../../lock-unlock'; import { store as editorStore } from '../../store'; import EditorHistoryRedo from '../editor-history/redo'; import EditorHistoryUndo from '../editor-history/undo'; +import SimpleEditingModeSelector from '../simple-editing-mode-selector'; function DocumentTools( { className, disableBlockTools = false } ) { const { setIsInserterOpened, setIsListViewOpened } = @@ -139,7 +140,11 @@ function DocumentTools( { className, disableBlockTools = false } ) { <> { isLargeViewport && ! hasFixedToolbar && ( { + const { + getClientIdsOfDescendants, + getClientIdsWithDescendants, + getSectionRootClientId, + getSectionClientIds, + } = unlock( select( blockEditorStore ) ); + + const sectionRootClientId = getSectionRootClientId(); + const sectionClientIds = getSectionClientIds(); + const allClientIds = sectionRootClientId + ? getClientIdsOfDescendants( sectionRootClientId ) + : getClientIdsWithDescendants(); + return { + sectionRootClientId, + sectionClientIds, + allClientIds, + }; + }, [] ); + const { sectionClientIds, allClientIds, sectionRootClientId } = selected; + const { getBlockOrder, getBlockName } = useSelect( blockEditorStore ); + const { __experimentalHasContentRoleAttribute } = useSelect( blocksStore ); + + useEffect( () => { + const sectionChildrenClientIds = sectionClientIds.flatMap( + ( clientId ) => getBlockOrder( clientId ) + ); + const contentClientIds = allClientIds.filter( ( clientId ) => + __experimentalHasContentRoleAttribute( getBlockName( clientId ) ) + ); + + registry.batch( () => { + // 1. The section root should hide non-content controls. + setBlockEditingMode( sectionRootClientId, 'contentOnly' ); + + // 2. Each section should hide non-content controls. + for ( const clientId of sectionClientIds ) { + setBlockEditingMode( clientId, 'contentOnly' ); + } + + // 3. The children of the section should be disabled. + for ( const clientId of sectionChildrenClientIds ) { + setBlockEditingMode( clientId, 'disabled' ); + } + + // 4. ...except for the "content" blocks which should be content-only. + for ( const clientId of contentClientIds ) { + setBlockEditingMode( clientId, 'contentOnly' ); + } + } ); + + return () => { + registry.batch( () => { + for ( const clientId of [ + sectionRootClientId, + ...sectionClientIds, + ...sectionChildrenClientIds, + ...contentClientIds, + ] ) { + unsetBlockEditingMode( clientId ); + } + } ); + }; + }, [ + __experimentalHasContentRoleAttribute, + allClientIds, + getBlockName, + getBlockOrder, + registry, + sectionRootClientId, + sectionClientIds, + setBlockEditingMode, + unsetBlockEditingMode, + ] ); + + return null; +} diff --git a/packages/editor/src/components/provider/index.js b/packages/editor/src/components/provider/index.js index aaf25621d3324b..300e68b9c22042 100644 --- a/packages/editor/src/components/provider/index.js +++ b/packages/editor/src/components/provider/index.js @@ -34,6 +34,8 @@ import EditorKeyboardShortcuts from '../global-keyboard-shortcuts'; import PatternRenameModal from '../pattern-rename-modal'; import PatternDuplicateModal from '../pattern-duplicate-modal'; import TemplatePartMenuItems from '../template-part-menu-items'; +import { TEMPLATE_POST_TYPE } from '../../store/constants'; +import ContentOnlyLockSections from './content-only-lock-sections'; const { ExperimentalBlockEditorProvider } = unlock( blockEditorPrivateApis ); const { PatternsMenuItems } = unlock( editPatternsPrivateApis ); @@ -302,9 +304,14 @@ export const ExperimentalEditorProvider = withRegistryProvider( - { mode === 'template-locked' && ( - - ) } + { mode === 'template-locked' && + type === TEMPLATE_POST_TYPE && ( + + ) } + { mode === 'template-locked' && + type !== TEMPLATE_POST_TYPE && ( + + ) } { type === 'wp_navigation' && ( ) } 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 868ff08b3d0709..46642d85d8d7ce 100644 --- a/packages/editor/src/components/provider/use-block-editor-settings.js +++ b/packages/editor/src/components/provider/use-block-editor-settings.js @@ -25,6 +25,7 @@ import { mediaUpload } from '../../utils'; import { store as editorStore } from '../../store'; import { unlock } from '../../lock-unlock'; import { useGlobalStylesContext } from '../global-styles-provider'; +import { TEMPLATE_POST_TYPE } from '../../store/constants'; const EMPTY_BLOCKS_LIST = []; const EMPTY_OBJECT = {}; @@ -142,7 +143,10 @@ function useBlockEditorSettings( settings, postType, postId, renderingMode ) { : undefined; function getSectionRootBlock() { - if ( renderingMode === 'template-locked' ) { + if ( + renderingMode === 'template-locked' && + postType !== TEMPLATE_POST_TYPE + ) { return getBlocksByName( 'core/post-content' )?.[ 0 ] ?? ''; } diff --git a/packages/editor/src/components/simple-editing-mode-selector/index.js b/packages/editor/src/components/simple-editing-mode-selector/index.js new file mode 100644 index 00000000000000..8f718e572c97e1 --- /dev/null +++ b/packages/editor/src/components/simple-editing-mode-selector/index.js @@ -0,0 +1,97 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { + Button, + SVG, + Path, + privateApis as componentsPrivateApis, +} from '@wordpress/components'; +import { useSelect, useDispatch } from '@wordpress/data'; +import { forwardRef } from '@wordpress/element'; +import { edit } from '@wordpress/icons'; + +/** + * Internal dependencies + */ +import { unlock } from '../../lock-unlock'; +import { store as editorStore } from '../../store'; +import { TEMPLATE_POST_TYPE } from '../../store/constants'; + +const { DropdownMenuV2 } = unlock( componentsPrivateApis ); + +const selectIcon = ( + + + +); + +function SimpleEditingModeSelector( props, ref ) { + const { postType, renderingMode } = useSelect( + ( select ) => ( { + postType: select( editorStore ).getCurrentPostType(), + renderingMode: select( editorStore ).getRenderingMode(), + } ), + [] + ); + + const { setRenderingMode } = useDispatch( editorStore ); + + if ( postType !== TEMPLATE_POST_TYPE ) { + return null; + } + + return ( + + } + > + + setRenderingMode( 'template-locked' ) } + > + + { __( 'Edit' ) } + + + { __( 'Focus on content.' ) } + + + setRenderingMode( 'post-only' ) } + > + + { __( 'Design' ) } + + + { __( 'Full control over layout and styling.' ) } + + + + + ); +} + +export default forwardRef( SimpleEditingModeSelector ); diff --git a/packages/editor/src/components/simple-editing-mode-selector/style.scss b/packages/editor/src/components/simple-editing-mode-selector/style.scss new file mode 100644 index 00000000000000..d6adc552406427 --- /dev/null +++ b/packages/editor/src/components/simple-editing-mode-selector/style.scss @@ -0,0 +1,9 @@ +.editor-simple-editing-mode-selector__menu { + .components-menu-item__button { + height: auto; + } + + .components-menu-item__info { + width: max-content; + } +} diff --git a/packages/editor/src/style.scss b/packages/editor/src/style.scss index d1af3095230f34..2dd9ed0fc7fbeb 100644 --- a/packages/editor/src/style.scss +++ b/packages/editor/src/style.scss @@ -52,6 +52,7 @@ @import "./components/start-page-options/style.scss"; @import "./components/start-template-options/style.scss"; @import "./components/sidebar/style.scss"; +@import "./components/simple-editing-mode-selector/style.scss"; @import "./components/site-discussion/style.scss"; @import "./components/table-of-contents/style.scss"; @import "./components/text-editor/style.scss";