diff --git a/packages/block-editor/src/components/block-actions/index.js b/packages/block-editor/src/components/block-actions/index.js index 2b3c883dfc367..b34f8eea93615 100644 --- a/packages/block-editor/src/components/block-actions/index.js +++ b/packages/block-editor/src/components/block-actions/index.js @@ -31,10 +31,12 @@ export default function BlockActions( { getDirectInsertBlock, canMoveBlocks, canRemoveBlocks, + getBlockEditingMode, } = select( blockEditorStore ); const blocks = getBlocksByClientId( clientIds ); const rootClientId = getBlockRootClientId( clientIds[ 0 ] ); + const rootBlockEditingMode = getBlockEditingMode( rootClientId ); const canInsertDefaultBlock = canInsertBlockType( getDefaultBlockName(), rootClientId @@ -46,7 +48,9 @@ export default function BlockActions( { return { canMove: canMoveBlocks( clientIds, rootClientId ), canRemove: canRemoveBlocks( clientIds, rootClientId ), - canInsertBlock: canInsertDefaultBlock || !! directInsertBlock, + canInsertBlock: + ( canInsertDefaultBlock || !! directInsertBlock ) && + rootBlockEditingMode === 'default', canCopyStyles: blocks.every( ( block ) => { return ( !! block && diff --git a/packages/block-editor/src/components/block-settings-menu-controls/index.js b/packages/block-editor/src/components/block-settings-menu-controls/index.js index 8b43827605c53..1edf02eb36894 100644 --- a/packages/block-editor/src/components/block-settings-menu-controls/index.js +++ b/packages/block-editor/src/components/block-settings-menu-controls/index.js @@ -27,15 +27,20 @@ import { BlockRenameControl, useBlockRename } from '../block-rename'; const { Fill, Slot } = createSlotFill( 'BlockSettingsMenuControls' ); const BlockSettingsMenuControlsSlot = ( { fillProps, clientIds = null } ) => { - const { selectedBlocks, selectedClientIds } = useSelect( + const { selectedBlocks, selectedClientIds, isContentOnly } = useSelect( ( select ) => { - const { getBlockNamesByClientId, getSelectedBlockClientIds } = - select( blockEditorStore ); + const { + getBlockNamesByClientId, + getSelectedBlockClientIds, + getBlockEditingMode, + } = select( blockEditorStore ); const ids = clientIds !== null ? clientIds : getSelectedBlockClientIds(); return { selectedBlocks: getBlockNamesByClientId( ids ), selectedClientIds: ids, + isContentOnly: + getBlockEditingMode( ids[ 0 ] ) === 'contentOnly', }; }, [ clientIds ] @@ -43,8 +48,10 @@ const BlockSettingsMenuControlsSlot = ( { fillProps, clientIds = null } ) => { const { canLock } = useBlockLock( selectedClientIds[ 0 ] ); const { canRename } = useBlockRename( selectedBlocks[ 0 ] ); - const showLockButton = selectedClientIds.length === 1 && canLock; - const showRenameButton = selectedClientIds.length === 1 && canRename; + const showLockButton = + selectedClientIds.length === 1 && canLock && ! isContentOnly; + const showRenameButton = + selectedClientIds.length === 1 && canRename && ! isContentOnly; // Check if current selection of blocks is Groupable or Ungroupable // and pass this props down to ConvertToGroupButton. @@ -89,17 +96,19 @@ const BlockSettingsMenuControlsSlot = ( { fillProps, clientIds = null } ) => { /> ) } { fills } - { fillProps?.canMove && ! fillProps?.onlyBlock && ( - - { __( 'Move to' ) } - - ) } - { fillProps?.count === 1 && ( + { fillProps?.canMove && + ! fillProps?.onlyBlock && + ! isContentOnly && ( + + { __( 'Move to' ) } + + ) } + { fillProps?.count === 1 && ! isContentOnly && ( { const { @@ -73,6 +74,7 @@ export function BlockSettingsDropdown( { getSelectedBlockClientIds, getBlockAttributes, getOpenedBlockSettingsMenu, + getBlockEditingMode, } = unlock( select( blockEditorStore ) ); const { getActiveBlockVariation } = select( blocksStore ); @@ -96,6 +98,8 @@ export function BlockSettingsDropdown( { getPreviousBlockClientId( firstBlockClientId ), selectedBlockClientIds: getSelectedBlockClientIds(), openedBlockSettingsMenu: getOpenedBlockSettingsMenu(), + isContentOnly: + getBlockEditingMode( firstBlockClientId ) === 'contentOnly', }; }, [ firstBlockClientId ] @@ -231,11 +235,15 @@ export function BlockSettingsDropdown( { clientId={ firstBlockClientId } /> ) } - + { ! isContentOnly && ( + + ) } { canDuplicate && ( ) } - { canCopyStyles && ( + { canCopyStyles && ! isContentOnly && ( ( { + isContentOnly: + select( blockEditorStore ).getBlockEditingMode( clientId ) === + 'contentOnly', + } ), + [ clientId ] + ); + const shouldShowLockIcon = isLocked && ! isContentOnly; const isSticky = blockInformation?.positionType === 'sticky'; const images = useListViewImages( { clientId, isExpanded } ); @@ -147,7 +158,7 @@ function ListViewBlockSelectButton( ) ) } ) : null } - { isLocked && ( + { shouldShowLockIcon && ( diff --git a/packages/block-editor/src/components/list-view/block.js b/packages/block-editor/src/components/list-view/block.js index cdbc5939e6a2a..c3ba9afe8cc16 100644 --- a/packages/block-editor/src/components/list-view/block.js +++ b/packages/block-editor/src/components/list-view/block.js @@ -104,34 +104,26 @@ function ListViewBlock( { const blockInformation = useBlockDisplayInformation( clientId ); - const { block, blockName, blockEditingMode, allowRightClickOverrides } = - useSelect( - ( select ) => { - const { - getBlock, - getBlockName, - getBlockEditingMode, - getSettings, - } = select( blockEditorStore ); - - return { - block: getBlock( clientId ), - blockName: getBlockName( clientId ), - blockEditingMode: getBlockEditingMode( clientId ), - allowRightClickOverrides: - getSettings().allowRightClickOverrides, - }; - }, - [ clientId ] - ); + const { block, blockName, allowRightClickOverrides } = useSelect( + ( select ) => { + const { getBlock, getBlockName, getSettings } = + select( blockEditorStore ); + + return { + block: getBlock( clientId ), + blockName: getBlockName( clientId ), + allowRightClickOverrides: + getSettings().allowRightClickOverrides, + }; + }, + [ clientId ] + ); const showBlockActions = // When a block hides its toolbar it also hides the block settings menu, // since that menu is part of the toolbar in the editor canvas. // List View respects this by also hiding the block settings menu. - hasBlockSupport( blockName, '__experimentalToolbar', true ) && - // Don't show the settings menu if block is disabled or content only. - blockEditingMode === 'default'; + hasBlockSupport( blockName, '__experimentalToolbar', true ); const instanceId = useInstanceId( ListViewBlock ); const descriptionId = `list-view-block-select-button__description-${ instanceId }`; diff --git a/packages/block-editor/src/hooks/content-lock-ui.js b/packages/block-editor/src/hooks/content-lock-ui.js index eff6cc9a2d8af..7cca4b325b09d 100644 --- a/packages/block-editor/src/hooks/content-lock-ui.js +++ b/packages/block-editor/src/hooks/content-lock-ui.js @@ -20,7 +20,6 @@ import { unlock } from '../lock-unlock'; // also includes artifacts on the store (actions, reducers, and selector). function ContentLockControlsPure( { clientId, isSelected } ) { - const { getBlockListSettings, getSettings } = useSelect( blockEditorStore ); const { templateLock, isLockedByParent, isEditingAsBlocks } = useSelect( ( select ) => { const { @@ -37,16 +36,11 @@ function ContentLockControlsPure( { clientId, isSelected } ) { [ clientId ] ); - const { - updateSettings, - updateBlockListSettings, - __unstableSetTemporarilyEditingAsBlocks, - } = useDispatch( blockEditorStore ); - const { stopEditingAsBlocks } = unlock( useDispatch( blockEditorStore ) ); + const { stopEditingAsBlocks, modifyContentLockBlock } = unlock( + useDispatch( blockEditorStore ) + ); const isContentLocked = ! isLockedByParent && templateLock === 'contentOnly'; - const { __unstableMarkNextChangeAsNotPersistent, updateBlockAttributes } = - useDispatch( blockEditorStore ); const stopEditingAsBlockCallback = useCallback( () => { stopEditingAsBlocks( clientId ); @@ -73,30 +67,19 @@ function ContentLockControlsPure( { clientId, isSelected } ) { ) } { showStartEditingAsBlocks && ( - { ( { onClose } ) => ( - { - __unstableMarkNextChangeAsNotPersistent(); - updateBlockAttributes( clientId, { - templateLock: undefined, - } ); - updateBlockListSettings( clientId, { - ...getBlockListSettings( clientId ), - templateLock: false, - } ); - const focusModeToRevert = - getSettings().focusMode; - updateSettings( { focusMode: true } ); - __unstableSetTemporarilyEditingAsBlocks( - clientId, - focusModeToRevert - ); - onClose(); - } } - > - { __( 'Modify' ) } - - ) } + { ( { selectedClientIds, onClose } ) => + selectedClientIds.length === 1 && + selectedClientIds[ 0 ] === clientId && ( + { + modifyContentLockBlock( clientId ); + onClose(); + } } + > + { __( 'Modify' ) } + + ) + } ) } diff --git a/packages/block-editor/src/store/private-actions.js b/packages/block-editor/src/store/private-actions.js index 6cfccb17287ff..28a7b1da98f73 100644 --- a/packages/block-editor/src/store/private-actions.js +++ b/packages/block-editor/src/store/private-actions.js @@ -391,3 +391,27 @@ export function expandBlock( clientId ) { clientId, }; } + +/** + * Temporarily modify/unlock the content-only block for editions. + * + * @param {string} clientId The client id of the block. + */ +export const modifyContentLockBlock = + ( clientId ) => + ( { select, dispatch } ) => { + dispatch.__unstableMarkNextChangeAsNotPersistent(); + dispatch.updateBlockAttributes( clientId, { + templateLock: undefined, + } ); + dispatch.updateBlockListSettings( clientId, { + ...select.getBlockListSettings( clientId ), + templateLock: false, + } ); + const focusModeToRevert = select.getSettings().focusMode; + dispatch.updateSettings( { focusMode: true } ); + dispatch.__unstableSetTemporarilyEditingAsBlocks( + clientId, + focusModeToRevert + ); + }; diff --git a/packages/block-editor/src/store/selectors.js b/packages/block-editor/src/store/selectors.js index 4ce85afb8cfd1..176c15557c838 100644 --- a/packages/block-editor/src/store/selectors.js +++ b/packages/block-editor/src/store/selectors.js @@ -1759,14 +1759,16 @@ export function canMoveBlock( state, clientId, rootClientId = null ) { if ( attributes === null ) { return true; } + if ( getBlockEditingMode( state, rootClientId ) !== 'default' ) { + return false; + } if ( attributes.lock?.move !== undefined ) { return ! attributes.lock.move; } if ( getTemplateLock( state, rootClientId ) === 'all' ) { return false; } - - return getBlockEditingMode( state, rootClientId ) !== 'disabled'; + return true; } /** diff --git a/packages/edit-site/src/components/template-part-converter/index.js b/packages/edit-site/src/components/template-part-converter/index.js index 7694735cbb302..de47eb6ae26f4 100644 --- a/packages/edit-site/src/components/template-part-converter/index.js +++ b/packages/edit-site/src/components/template-part-converter/index.js @@ -27,12 +27,25 @@ export default function TemplatePartConverter() { } function TemplatePartConverterMenuItem( { clientIds, onClose } ) { - const blocks = useSelect( - ( select ) => - select( blockEditorStore ).getBlocksByClientId( clientIds ), + const { isContentOnly, blocks } = useSelect( + ( select ) => { + const { getBlocksByClientId, getBlockEditingMode } = + select( blockEditorStore ); + return { + blocks: getBlocksByClientId( clientIds ), + isContentOnly: + clientIds.length === 1 && + getBlockEditingMode( clientIds[ 0 ] ) === 'contentOnly', + }; + }, [ clientIds ] ); + // Do not show the convert button if the block is in content-only mode. + if ( isContentOnly ) { + return null; + } + // Allow converting a single template part to standard blocks. if ( blocks.length === 1 && blocks[ 0 ]?.name === 'core/template-part' ) { return ( diff --git a/packages/editor/src/components/block-settings-menu/content-only-settings-menu.js b/packages/editor/src/components/block-settings-menu/content-only-settings-menu.js new file mode 100644 index 0000000000000..4683dd38593a5 --- /dev/null +++ b/packages/editor/src/components/block-settings-menu/content-only-settings-menu.js @@ -0,0 +1,175 @@ +/** + * WordPress dependencies + */ +import { + BlockSettingsMenuControls, + __unstableBlockSettingsMenuFirstItem as BlockSettingsMenuFirstItem, + store as blockEditorStore, + useBlockDisplayInformation, +} from '@wordpress/block-editor'; +import { store as coreStore } from '@wordpress/core-data'; +import { __experimentalText as Text, MenuItem } from '@wordpress/components'; +import { useSelect, useDispatch } from '@wordpress/data'; +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import { store as editorStore } from '../../store'; +import { unlock } from '../../lock-unlock'; + +function ContentOnlySettingsMenuItems( { clientId, onClose } ) { + const { entity, onNavigateToEntityRecord } = useSelect( + ( select ) => { + const { + getBlockEditingMode, + getBlockParentsByBlockName, + getSettings, + getBlockAttributes, + } = select( blockEditorStore ); + const contentOnly = + getBlockEditingMode( clientId ) === 'contentOnly'; + if ( ! contentOnly ) { + return {}; + } + const patternParent = getBlockParentsByBlockName( + clientId, + 'core/block', + true + )[ 0 ]; + + let record; + if ( patternParent ) { + record = select( coreStore ).getEntityRecord( + 'postType', + 'wp_block', + getBlockAttributes( patternParent ).ref + ); + } else { + const { getCurrentPostType, getCurrentTemplateId } = + select( editorStore ); + const currentPostType = getCurrentPostType(); + const templateId = getCurrentTemplateId(); + if ( currentPostType === 'page' && templateId ) { + record = select( coreStore ).getEntityRecord( + 'postType', + 'wp_template', + templateId + ); + } + } + return { + entity: record, + onNavigateToEntityRecord: + getSettings().onNavigateToEntityRecord, + }; + }, + [ clientId ] + ); + + if ( ! entity ) { + return ( + + ); + } + + const isPattern = entity.type === 'wp_block'; + + return ( + <> + + { + onNavigateToEntityRecord( { + postId: entity.id, + postType: entity.type, + } ); + } } + > + { isPattern ? __( 'Edit pattern' ) : __( 'Edit template' ) } + + + + { isPattern + ? __( + 'Edit the pattern to move, delete, or make further changes to this block.' + ) + : __( + 'Edit the template to move, delete, or make further changes to this block.' + ) } + + + ); +} + +function TemplateLockContentOnlyMenuItems( { clientId, onClose } ) { + const { contentLockingParent } = useSelect( + ( select ) => { + const { getContentLockingParent } = unlock( + select( blockEditorStore ) + ); + return { + contentLockingParent: getContentLockingParent( clientId ), + }; + }, + [ clientId ] + ); + const blockDisplayInformation = + useBlockDisplayInformation( contentLockingParent ); + // Disable reason: We're using a hook here so it has to be on top-level. + // eslint-disable-next-line @wordpress/no-unused-vars-before-return + const { modifyContentLockBlock, selectBlock } = unlock( + useDispatch( blockEditorStore ) + ); + + if ( ! blockDisplayInformation?.title ) { + return null; + } + + return ( + <> + + { + selectBlock( contentLockingParent ); + modifyContentLockBlock( contentLockingParent ); + onClose(); + } } + > + { __( 'Unlock' ) } + + + + { __( + 'Temporarily unlock the parent block to edit, delete or make further changes to this block.' + ) } + + + ); +} + +export default function ContentOnlySettingsMenu() { + return ( + + { ( { selectedClientIds, onClose } ) => + selectedClientIds.length === 1 && ( + + ) + } + + ); +} diff --git a/packages/editor/src/components/block-settings-menu/content-only-settings-menu.native.js b/packages/editor/src/components/block-settings-menu/content-only-settings-menu.native.js new file mode 100644 index 0000000000000..11cebce87bbba --- /dev/null +++ b/packages/editor/src/components/block-settings-menu/content-only-settings-menu.native.js @@ -0,0 +1,4 @@ +// Render nothing in native for now. +export default function ContentOnlySettingsMenu() { + return null; +} diff --git a/packages/editor/src/components/block-settings-menu/style.scss b/packages/editor/src/components/block-settings-menu/style.scss new file mode 100644 index 0000000000000..53fa391d28ef0 --- /dev/null +++ b/packages/editor/src/components/block-settings-menu/style.scss @@ -0,0 +1,4 @@ +.editor-content-only-settings-menu__description { + padding: $grid-unit; + min-width: 235px; +} diff --git a/packages/editor/src/components/provider/index.js b/packages/editor/src/components/provider/index.js index df0fb488c69dc..4f359104ea02d 100644 --- a/packages/editor/src/components/provider/index.js +++ b/packages/editor/src/components/provider/index.js @@ -28,6 +28,7 @@ import useCommands from '../commands'; import BlockRemovalWarnings from '../block-removal-warnings'; import StartPageOptions from '../start-page-options'; import KeyboardShortcutHelpModal from '../keyboard-shortcut-help-modal'; +import ContentOnlySettingsMenu from '../block-settings-menu/content-only-settings-menu'; const { ExperimentalBlockEditorProvider } = unlock( blockEditorPrivateApis ); const { PatternsMenuItems } = unlock( editPatternsPrivateApis ); @@ -264,6 +265,7 @@ export const ExperimentalEditorProvider = withRegistryProvider( { ! settings.__unstableIsPreviewMode && ( <> + { mode === 'template-locked' && ( ) } diff --git a/packages/editor/src/style.scss b/packages/editor/src/style.scss index b382fd82c583a..dcc693c08b80a 100644 --- a/packages/editor/src/style.scss +++ b/packages/editor/src/style.scss @@ -3,6 +3,7 @@ @import "./components/autocompleters/style.scss"; @import "./components/block-manager/style.scss"; @import "./components/collapsible-block-toolbar/style.scss"; +@import "./components/block-settings-menu/style.scss"; @import "./components/document-bar/style.scss"; @import "./components/document-outline/style.scss"; @import "./components/document-tools/style.scss";