diff --git a/docs/reference-guides/data/data-core-block-editor.md b/docs/reference-guides/data/data-core-block-editor.md
index a3d6fa25e97c89..41c9771765c10c 100644
--- a/docs/reference-guides/data/data-core-block-editor.md
+++ b/docs/reference-guides/data/data-core-block-editor.md
@@ -1000,6 +1000,23 @@ _Returns_
- `boolean`: Whether the caret is within formatted text.
+### isContentLockedBlock
+
+Returns whether or not the given block is _content locked_.
+
+A block is _content locked_ if it is nested within a block that has a `templateLock` attribute set to `'contentOnly'` (a _content locking_ block), or if the editor has a `templateLock` of `'contentOnly'`.
+
+If the block is nested within a content block type (see `settings.contentBlockTypes`) then it is not _content locked_.
+
+_Parameters_
+
+- _state_ `Object`: Global application state.
+- _clientId_ `string`: The client ID of the block to check.
+
+_Returns_
+
+- `boolean`: Whether or not the block is content locked.
+
### isDraggingBlocks
Returns true if the user is dragging blocks, or false otherwise.
@@ -1025,6 +1042,21 @@ _Returns_
- `boolean`: Whether block is first in multi-selection.
+### isInsertionLocked
+
+Determines if the editor or a given container is locked and does not allow block insertion.
+
+Only the `templateLock` settings of the editor or container block are checked. For more rigorous checking that checks the `allowedBlockTypes` attribute, use `canInsertBlockType()`.
+
+_Parameters_
+
+- _state_ `Object`: Editor state.
+- _rootClientId_ `?string`: Container block's client ID, or `null` to check the editor.
+
+_Returns_
+
+- `boolean`: Whether block insertion is locked.
+
### isLastBlockChangePersistent
Returns true if the most recent block change is be considered persistent, or false otherwise. A persistent change is one committed by BlockEditorProvider via its `onChange` callback, in addition to `onInput`.
diff --git a/packages/block-editor/src/components/block-inspector/index.js b/packages/block-editor/src/components/block-inspector/index.js
index 199f4f4628b4bf..e3af8b176d532a 100644
--- a/packages/block-editor/src/components/block-inspector/index.js
+++ b/packages/block-editor/src/components/block-inspector/index.js
@@ -8,16 +8,8 @@ import {
hasBlockSupport,
store as blocksStore,
} from '@wordpress/blocks';
-import {
- FlexItem,
- PanelBody,
- __experimentalHStack as HStack,
- __experimentalVStack as VStack,
- Button,
- __unstableMotion as motion,
-} from '@wordpress/components';
-import { useSelect, useDispatch } from '@wordpress/data';
-import { useMemo, useCallback } from '@wordpress/element';
+import { PanelBody, __unstableMotion as motion } from '@wordpress/components';
+import { useSelect } from '@wordpress/data';
/**
* Internal dependencies
@@ -28,7 +20,6 @@ import MultiSelectionInspector from '../multi-selection-inspector';
import BlockVariationTransforms from '../block-variation-transforms';
import useBlockDisplayInformation from '../use-block-display-information';
import { store as blockEditorStore } from '../../store';
-import BlockIcon from '../block-icon';
import BlockStyles from '../block-styles';
import DefaultStylePicker from '../default-style-picker';
import { default as InspectorControls } from '../inspector-controls';
@@ -38,102 +29,34 @@ import AdvancedControls from '../inspector-controls-tabs/advanced-controls-panel
import PositionControls from '../inspector-controls-tabs/position-controls-panel';
import useBlockInspectorAnimationSettings from './useBlockInspectorAnimationSettings';
import BlockInfo from '../block-info-slot-fill';
+import ContentBlocksList from '../content-blocks-list';
+import { unlock } from '../../lock-unlock';
-function useContentBlocks( blockTypes, block ) {
- const contentBlocksObjectAux = useMemo( () => {
- return blockTypes.reduce( ( result, blockType ) => {
- if (
- blockType.name !== 'core/list-item' &&
- Object.entries( blockType.attributes ).some(
- ( [ , { __experimentalRole } ] ) =>
- __experimentalRole === 'content'
- )
- ) {
- result[ blockType.name ] = true;
- }
- return result;
- }, {} );
- }, [ blockTypes ] );
- const isContentBlock = useCallback(
- ( blockName ) => {
- return !! contentBlocksObjectAux[ blockName ];
- },
- [ contentBlocksObjectAux ]
- );
- return useMemo( () => {
- return getContentBlocks( [ block ], isContentBlock );
- }, [ block, isContentBlock ] );
-}
-
-function getContentBlocks( blocks, isContentBlock ) {
- const result = [];
- for ( const block of blocks ) {
- if ( isContentBlock( block.name ) ) {
- result.push( block );
- }
- result.push( ...getContentBlocks( block.innerBlocks, isContentBlock ) );
- }
- return result;
-}
-
-function BlockNavigationButton( { blockTypes, block, selectedBlock } ) {
- const { selectBlock } = useDispatch( blockEditorStore );
- const blockType = blockTypes.find( ( { name } ) => name === block.name );
- const isSelected =
- selectedBlock && selectedBlock.clientId === block.clientId;
- return (
-
+function BlockInspectorLockedBlocks( { contentLockingBlock } ) {
+ const selectedBlock = useSelect(
+ ( select ) => select( blockEditorStore ).getSelectedBlockClientId(),
+ []
);
-}
-
-function BlockInspectorLockedBlocks( { topLevelLockedBlock } ) {
- const { blockTypes, block, selectedBlock } = useSelect(
- ( select ) => {
- return {
- blockTypes: select( blocksStore ).getBlockTypes(),
- block: select( blockEditorStore ).getBlock(
- topLevelLockedBlock
- ),
- selectedBlock: select( blockEditorStore ).getSelectedBlock(),
- };
- },
- [ topLevelLockedBlock ]
+ const blockInformation = useBlockDisplayInformation(
+ contentLockingBlock ?? selectedBlock
);
- const blockInformation = useBlockDisplayInformation( topLevelLockedBlock );
- const contentBlocks = useContentBlocks( blockTypes, block );
return (
{ ( { ref, tabIndex, onFocus } ) => (
@@ -268,7 +250,7 @@ function ListViewBlock( {
currentlyEditingBlockInCanvas ? 0 : tabIndex
}
onFocus={ onFocus }
- isExpanded={ canExpand ? isExpanded : undefined }
+ isExpanded={ canEdit ? isExpanded : undefined }
selectedClientIds={ selectedClientIds }
ariaLabel={ blockAriaLabel }
ariaDescribedBy={ descriptionId }
@@ -317,7 +299,7 @@ function ListViewBlock( {
{ showBlockActions && BlockSettingsMenu && (
{ ( { ref, tabIndex, onFocus } ) => (
{
- if ( ! parentId ) {
- return true;
+ const {
+ getTemplateLock,
+ isContentLockingBlock,
+ getContentClientIdsTree,
+ canEditBlock,
+ } = unlock( select( blockEditorStore ) );
+
+ const isContentLocking = parentId
+ ? isContentLockingBlock( parentId )
+ : getTemplateLock() === 'contentOnly';
+
+ if ( isContentLocking ) {
+ return getContentClientIdsTree( parentId );
}
- const isContentLocked =
- select( blockEditorStore ).getTemplateLock( parentId ) ===
- 'contentOnly';
- const canEdit = select( blockEditorStore ).canEditBlock( parentId );
+ if ( ! parentId || canEditBlock( parentId ) ) {
+ return blocks.filter( Boolean );
+ }
- return isContentLocked ? false : canEdit;
+ return [];
},
- [ parentId ]
+ [ parentId, blocks ]
);
- const { expandedState, draggedClientIds } = useListViewContext();
+ const parentBlockInformation = useBlockDisplayInformation( parentId );
+ const syncedBranch = isSyncedBranch || !! parentBlockInformation?.isSynced;
- if ( ! canParentExpand ) {
- return null;
- }
+ const { expandedState, draggedClientIds } = useListViewContext();
// Only show the appender at the first level.
const showAppender = showAppenderProp && level === 1;
- const filteredBlocks = blocks.filter( Boolean );
- const blockCount = filteredBlocks.length;
+ const blockCount = branchBlocks.length;
// The appender means an extra row in List View, so add 1 to the row count.
const rowCount = showAppender ? blockCount + 1 : blockCount;
let nextPosition = listPosition;
return (
<>
- { filteredBlocks.map( ( block, index ) => {
+ { branchBlocks.map( ( block, index ) => {
const { clientId, innerBlocks } = block;
if ( index > 0 ) {
nextPosition += countBlocks(
- filteredBlocks[ index - 1 ],
+ branchBlocks[ index - 1 ],
expandedState,
draggedClientIds,
isExpanded
diff --git a/packages/block-editor/src/hooks/align.js b/packages/block-editor/src/hooks/align.js
index 8d5c7f850e89b5..28f004d779821a 100644
--- a/packages/block-editor/src/hooks/align.js
+++ b/packages/block-editor/src/hooks/align.js
@@ -134,11 +134,10 @@ export const withToolbarControls = createHigherOrderComponent(
blockAllowedAlignments
).map( ( { name } ) => name );
const isContentLocked = useSelect(
- ( select ) => {
- return select(
- blockEditorStore
- ).__unstableGetContentLockingParent( props.clientId );
- },
+ ( select ) =>
+ select( blockEditorStore ).isContentLockedBlock(
+ props.clientId
+ ),
[ props.clientId ]
);
if ( ! validAlignments.length || isContentLocked ) {
diff --git a/packages/block-editor/src/hooks/content-lock-ui.js b/packages/block-editor/src/hooks/content-lock-ui.js
index 568da7974925a1..712eb74dc9bb5e 100644
--- a/packages/block-editor/src/hooks/content-lock-ui.js
+++ b/packages/block-editor/src/hooks/content-lock-ui.js
@@ -1,3 +1,8 @@
+/**
+ * External dependencies
+ */
+import classnames from 'classnames';
+
/**
* WordPress dependencies
*/
@@ -13,10 +18,7 @@ import { useEffect, useRef, useCallback } from '@wordpress/element';
*/
import { store as blockEditorStore } from '../store';
import { BlockControls, BlockSettingsMenuControls } from '../components';
-/**
- * External dependencies
- */
-import classnames from 'classnames';
+import { unlock } from '../lock-unlock';
function StopEditingAsBlocksOnOutsideSelect( {
clientId,
@@ -49,18 +51,15 @@ export const withBlockControls = createHigherOrderComponent(
const { templateLock, isLockedByParent, isEditingAsBlocks } = useSelect(
( select ) => {
const {
- __unstableGetContentLockingParent,
+ isContentLockedBlock,
getTemplateLock,
- __unstableGetTemporarilyEditingAsBlocks,
- } = select( blockEditorStore );
+ getTemporarilyUnlockedBlock,
+ } = unlock( select( blockEditorStore ) );
return {
templateLock: getTemplateLock( props.clientId ),
- isLockedByParent: !! __unstableGetContentLockingParent(
- props.clientId
- ),
+ isLockedByParent: isContentLockedBlock( props.clientId ),
isEditingAsBlocks:
- __unstableGetTemporarilyEditingAsBlocks() ===
- props.clientId,
+ getTemporarilyUnlockedBlock() === props.clientId,
};
},
[ props.clientId ]
diff --git a/packages/block-editor/src/hooks/duotone.js b/packages/block-editor/src/hooks/duotone.js
index c308cfb58a5b43..e3ebf0d704138b 100644
--- a/packages/block-editor/src/hooks/duotone.js
+++ b/packages/block-editor/src/hooks/duotone.js
@@ -226,11 +226,10 @@ const withDuotoneControls = createHigherOrderComponent(
);
const isContentLocked = useSelect(
- ( select ) => {
- return select(
- blockEditorStore
- ).__unstableGetContentLockingParent( props.clientId );
- },
+ ( select ) =>
+ select( blockEditorStore ).isContentLockedBlock(
+ props.clientId
+ ),
[ props.clientId ]
);
diff --git a/packages/block-editor/src/hooks/layout.js b/packages/block-editor/src/hooks/layout.js
index b05068ffbbce5c..efec9216612ef4 100644
--- a/packages/block-editor/src/hooks/layout.js
+++ b/packages/block-editor/src/hooks/layout.js
@@ -141,11 +141,11 @@ function LayoutPanel( {
const defaultThemeLayout = useSetting( 'layout' );
const { themeSupportsLayout, isContentLocked } = useSelect(
( select ) => {
- const { getSettings, __unstableGetContentLockingParent } =
+ const { getSettings, isContentLockedBlock } =
select( blockEditorStore );
return {
themeSupportsLayout: getSettings().supportsLayout,
- isContentLocked: __unstableGetContentLockingParent( clientId ),
+ isContentLocked: isContentLockedBlock( clientId ),
};
},
[ clientId ]
diff --git a/packages/block-editor/src/private-apis.js b/packages/block-editor/src/private-apis.js
index 0b7e8b2511aa6b..ee6de4937fa2f8 100644
--- a/packages/block-editor/src/private-apis.js
+++ b/packages/block-editor/src/private-apis.js
@@ -12,6 +12,7 @@ import { PrivateListView } from './components/list-view';
import BlockInfo from './components/block-info-slot-fill';
import { useShouldContextualToolbarShow } from './utils/use-should-contextual-toolbar-show';
import { cleanEmptyObject } from './hooks/utils';
+import ContentBlocksList from './components/content-blocks-list';
/**
* Private @wordpress/block-editor APIs.
@@ -28,4 +29,5 @@ lock( privateApis, {
BlockInfo,
useShouldContextualToolbarShow,
cleanEmptyObject,
+ ContentBlocksList,
} );
diff --git a/packages/block-editor/src/store/actions.js b/packages/block-editor/src/store/actions.js
index 27ff57e74919cb..e84b645defe1ff 100644
--- a/packages/block-editor/src/store/actions.js
+++ b/packages/block-editor/src/store/actions.js
@@ -25,7 +25,10 @@ import {
retrieveSelectedAttribute,
START_OF_SELECTED_AREA,
} from '../utils/selection';
-import { __experimentalUpdateSettings } from './private-actions';
+import {
+ __experimentalUpdateSettings,
+ setTemporarilyUnlockedBlock,
+} from './private-actions';
/** @typedef {import('../components/use-on-block-drop/types').WPDropOperation} WPDropOperation */
@@ -1630,7 +1633,7 @@ export const insertBeforeBlock =
return;
}
const rootClientId = select.getBlockRootClientId( clientId );
- const isLocked = select.getTemplateLock( rootClientId );
+ const isLocked = select.isInsertionLocked( rootClientId );
if ( isLocked ) {
return;
}
@@ -1655,7 +1658,7 @@ export const insertAfterBlock =
return;
}
const rootClientId = select.getBlockRootClientId( clientId );
- const isLocked = select.getTemplateLock( rootClientId );
+ const isLocked = select.isInsertionLocked( rootClientId );
if ( isLocked ) {
return;
}
@@ -1724,20 +1727,12 @@ export function setBlockVisibility( updates ) {
};
}
-/**
- * Action that sets whether a block is being temporaritly edited as blocks.
- *
- * DO-NOT-USE in production.
- * This action is created for internal/experimental only usage and may be
- * removed anytime without any warning, causing breakage on any plugin or theme invoking it.
- *
- * @param {?string} temporarilyEditingAsBlocks The block's clientId being temporaritly edited as blocks.
- */
export function __unstableSetTemporarilyEditingAsBlocks(
temporarilyEditingAsBlocks
) {
- return {
- type: 'SET_TEMPORARILY_EDITING_AS_BLOCKS',
- temporarilyEditingAsBlocks,
- };
+ deprecated( '__unstableSetTemporarilyEditingAsBlocks', {
+ since: '6.3',
+ version: '6.4',
+ } );
+ return setTemporarilyUnlockedBlock( temporarilyEditingAsBlocks );
}
diff --git a/packages/block-editor/src/store/defaults.js b/packages/block-editor/src/store/defaults.js
index 7c33350d12dd01..7b7684306e70b4 100644
--- a/packages/block-editor/src/store/defaults.js
+++ b/packages/block-editor/src/store/defaults.js
@@ -269,4 +269,6 @@ export const SETTINGS_DEFAULTS = {
],
__unstableResolvedAssets: { styles: [], scripts: [] },
+
+ contentBlockTypes: null,
};
diff --git a/packages/block-editor/src/store/private-actions.js b/packages/block-editor/src/store/private-actions.js
index 2d33ea82cb9b63..fa85e5c684d7b7 100644
--- a/packages/block-editor/src/store/private-actions.js
+++ b/packages/block-editor/src/store/private-actions.js
@@ -66,3 +66,17 @@ export function showBlockInterface() {
type: 'SHOW_BLOCK_INTERFACE',
};
}
+
+/**
+ * Marks the given block as temporarily unlocked.
+ *
+ * @param {string} clientId The client ID of the block to unlock.
+ *
+ * @return {Object} Action object.
+ */
+export function setTemporarilyUnlockedBlock( clientId ) {
+ return {
+ type: 'SET_TEMPORARILY_UNLOCKED_BLOCK',
+ clientId,
+ };
+}
diff --git a/packages/block-editor/src/store/private-selectors.js b/packages/block-editor/src/store/private-selectors.js
index 60712e6b8eb6e0..cdfb42b2a0462a 100644
--- a/packages/block-editor/src/store/private-selectors.js
+++ b/packages/block-editor/src/store/private-selectors.js
@@ -1,3 +1,25 @@
+/**
+ * External dependencies
+ */
+import createSelector from 'rememo';
+
+/**
+ * WordPress dependencies
+ */
+import { createRegistrySelector } from '@wordpress/data';
+import { store as blocksStore } from '@wordpress/blocks';
+
+/**
+ * Internal dependencies
+ */
+import {
+ getBlockParents,
+ getTemplateLock,
+ getBlockName,
+ __unstableGetClientIdWithClientIdsTree,
+ getBlockOrder,
+} from './selectors';
+
/**
* Returns true if the block interface is hidden, or false otherwise.
*
@@ -18,3 +40,111 @@ export function isBlockInterfaceHidden( state ) {
export function getLastInsertedBlocksClientIds( state ) {
return state?.lastBlockInserted?.clientIds;
}
+
+/**
+ * Returns whether or not the given block is a _content locking_ block.
+ *
+ * A block is _content locking_ if it is the top-most block that has a
+ * `templateLock` attribute set to `'contentOnly'`.
+ *
+ * @param {Object} state Global application state.
+ * @param {string} clientId The client ID of the block to check.
+ *
+ * @return {boolean} Whether or not the block is a _content locking_ block.
+ */
+export const isContentLockingBlock = ( state, clientId ) =>
+ getContentLockingBlock( state, clientId ) === clientId;
+
+/**
+ * Returns the client ID of the _content locking_ block that contains the given
+ * block, or `undefined` if the block is not nested within a _content locking_
+ * block.
+ *
+ * A block is _content locking_ if it is the top-most block that has a
+ * `templateLock` attribute set to `'contentOnly'`.
+ *
+ * @param {Object} state Global application state.
+ * @param {string} clientId The client ID of the block to check.
+ *
+ * @return {string|undefined} The client ID of the _content locking_ block that
+ * contains the given block, if it exists.
+ */
+export const getContentLockingBlock = createSelector(
+ ( state, clientId ) => {
+ if ( getTemplateLock( state ) === 'contentOnly' ) {
+ return;
+ }
+
+ return [ ...getBlockParents( state, clientId ), clientId ].find(
+ ( candidateClientId ) =>
+ getTemplateLock( state, candidateClientId ) === 'contentOnly'
+ );
+ },
+ ( state ) => [
+ state.settings.templateLock,
+ state.blocks.parents,
+ state.blockListSettings,
+ ]
+);
+
+/**
+ * Returns whether or not the given block is a _content block_.
+ *
+ * A block is _content block_ if its block type is in
+ * `settings.contentBlockTypes` or if it has an attribute with the `'content'`
+ * role.
+ *
+ * @param {Object} state Global application state.
+ * @param {string} clientId The client ID of the block to check.
+ *
+ * @return {boolean} Whether or not the block is a _content block_.
+ */
+export const isContentBlock = createRegistrySelector(
+ ( select ) => ( state, clientId ) => {
+ const blockName = getBlockName( state, clientId );
+ return state.settings.contentBlockTypes
+ ? state.settings.contentBlockTypes.includes( blockName )
+ : select( blocksStore ).__experimentalHasContentRoleAttribute(
+ blockName
+ );
+ }
+);
+
+/**
+ * Returns all of the _content blocks_. If a `rootClientId` is provided, only
+ * the _content blocks_ that are descendants of that block are returned.
+ *
+ * The resultant array is a tree of block objects containing only the `clientId`
+ * and `innerBlocks` properties.
+ *
+ * @see isContentBlock
+ *
+ * @param {Object} state Global application state.
+ * @param {string} rootClientId Optional root client ID of block list.
+ *
+ * @return {Object[]} Array of block obejcts containing `clientId` and `innerBlocks`.
+ */
+export const getContentClientIdsTree = createSelector(
+ ( state, rootClientId = null ) => {
+ return getBlockOrder( state, rootClientId ).flatMap( ( clientId ) =>
+ isContentBlock( state, clientId )
+ ? [ __unstableGetClientIdWithClientIdsTree( state, clientId ) ]
+ : getContentClientIdsTree( state, clientId )
+ );
+ },
+ ( state ) => [ state.blocks.order ]
+);
+
+/**
+ * Returns the client ID of the block that is temporarily unlocked, or null if
+ * no block is temporarily unlocked.
+ *
+ * Used to allow the user to temporarily edit a _content locked_ block.
+ *
+ * @param {Object} state Global application state.
+ *
+ * @return {string|null} Client ID of the temporarily unlocked block, or null.
+ */
+export function getTemporarilyUnlockedBlock( state ) {
+ return state.temporarilyUnlockedBlock;
+}
diff --git a/packages/block-editor/src/store/reducer.js b/packages/block-editor/src/store/reducer.js
index 4239cb1aba8489..5437d8938e78d3 100644
--- a/packages/block-editor/src/store/reducer.js
+++ b/packages/block-editor/src/store/reducer.js
@@ -1820,16 +1820,16 @@ export function lastBlockInserted( state = {}, action ) {
}
/**
- * Reducer returning the block that is eding temporarily edited as blocks.
+ * Reducer returning the block client ID that is temporarily unlocked.
*
- * @param {Object} state Current state.
- * @param {Object} action Dispatched action.
+ * @param {string|null} state Current state.
+ * @param {Object} action Dispatched action.
*
* @return {Object} Updated state.
*/
-export function temporarilyEditingAsBlocks( state = '', action ) {
- if ( action.type === 'SET_TEMPORARILY_EDITING_AS_BLOCKS' ) {
- return action.temporarilyEditingAsBlocks;
+export function temporarilyUnlockedBlock( state = null, action ) {
+ if ( action.type === 'SET_TEMPORARILY_UNLOCKED_BLOCK' ) {
+ return action.clientId;
}
return state;
}
@@ -1854,7 +1854,7 @@ const combinedReducers = combineReducers( {
hasBlockMovingClientId,
highlightedBlock,
lastBlockInserted,
- temporarilyEditingAsBlocks,
+ temporarilyUnlockedBlock,
blockVisibility,
} );
diff --git a/packages/block-editor/src/store/selectors.js b/packages/block-editor/src/store/selectors.js
index e61e6063a33e65..49b5ec8bb9e977 100644
--- a/packages/block-editor/src/store/selectors.js
+++ b/packages/block-editor/src/store/selectors.js
@@ -27,6 +27,11 @@ import deprecated from '@wordpress/deprecated';
*/
import { mapRichTextSettings } from './utils';
import { orderBy } from '../utils/sorting';
+import {
+ getContentLockingBlock,
+ getTemporarilyUnlockedBlock,
+ isContentBlock,
+} from './private-selectors';
/**
* A block selection object.
@@ -1535,8 +1540,7 @@ const canInsertBlockTypeUnmemoized = (
return false;
}
- const isLocked = !! getTemplateLock( state, rootClientId );
- if ( isLocked ) {
+ if ( isInsertionLocked( state, rootClientId ) ) {
return false;
}
@@ -1653,6 +1657,46 @@ export function canInsertBlocks( state, clientIds, rootClientId = null ) {
);
}
+/**
+ * Determines if the editor or a given container is locked and does not allow
+ * block insertion.
+ *
+ * Only the `templateLock` settings of the editor or container block are
+ * checked. For more rigorous checking that checks the `allowedBlockTypes`
+ * attribute, use `canInsertBlockType()`.
+ *
+ * @param {Object} state Editor state.
+ * @param {?string} rootClientId Container block's client ID, or `null` to check
+ * the editor.
+ *
+ * @return {boolean} Whether block insertion is locked.
+ */
+export const isInsertionLocked = createSelector(
+ ( state, rootClientId = null ) => {
+ const templateLock = getTemplateLock( state, rootClientId );
+ if ( rootClientId && templateLock === 'contentOnly' ) {
+ // Lock insertion into a non content block.
+ const isWithinContentBlock =
+ isContentBlock( state, rootClientId ) ||
+ getBlockParents( state, rootClientId ).some(
+ ( parentClientId ) =>
+ isContentBlock( state, parentClientId )
+ );
+ return ! isWithinContentBlock;
+ }
+ // Otherwise lock insertion when there is a lock.
+ return !! templateLock;
+ },
+ ( state, rootClientId ) =>
+ rootClientId
+ ? [
+ state.blockListSettings[ rootClientId ],
+ state.blocks.parents,
+ state.settings.contentBlockTypes,
+ ]
+ : [ state.settings.templateLock ]
+);
+
/**
* Determines if the given block is allowed to be deleted.
*
@@ -1670,15 +1714,27 @@ export function canRemoveBlock( state, clientId, rootClientId = null ) {
return true;
}
+ // If the block has a lock defined on it, we use that.
const { lock } = attributes;
- const parentIsLocked = !! getTemplateLock( state, rootClientId );
- // If we don't have a lock on the blockType level, we defer to the parent templateLock.
- if ( lock === undefined || lock?.remove === undefined ) {
- return ! parentIsLocked;
+ if ( lock !== undefined && lock?.remove !== undefined ) {
+ // When remove is true, it means we cannot remove it.
+ return ! lock?.remove;
+ }
+
+ const templateLock = getTemplateLock( state, rootClientId );
+ if ( templateLock === 'contentOnly' ) {
+ // Permit removal when inside a content block.
+ const isWithinContentBlock = getBlockParents( state, clientId ).some(
+ ( parentClientId ) => isContentBlock( state, parentClientId )
+ );
+ return isWithinContentBlock;
+ } else if ( templateLock ) {
+ // Prevent removal when template lock exists.
+ return false;
}
- // When remove is true, it means we cannot remove it.
- return ! lock?.remove;
+ // Permit removing by default.
+ return true;
}
/**
@@ -2767,40 +2823,6 @@ export const __unstableGetVisibleBlocks = createSelector(
( state ) => [ state.blockVisibility ]
);
-/**
- * DO-NOT-USE in production.
- * This selector is created for internal/experimental only usage and may be
- * removed anytime without any warning, causing breakage on any plugin or theme invoking it.
- */
-export const __unstableGetContentLockingParent = createSelector(
- ( state, clientId ) => {
- let current = clientId;
- let result;
- while ( state.blocks.parents.has( current ) ) {
- current = state.blocks.parents.get( current );
- if (
- current &&
- getTemplateLock( state, current ) === 'contentOnly'
- ) {
- result = current;
- }
- }
- return result;
- },
- ( state ) => [ state.blocks.parents, state.blockListSettings ]
-);
-
-/**
- * DO-NOT-USE in production.
- * This selector is created for internal/experimental only usage and may be
- * removed anytime without any warning, causing breakage on any plugin or theme invoking it.
- *
- * @param {Object} state Global application state.
- */
-export function __unstableGetTemporarilyEditingAsBlocks( state ) {
- return state.temporarilyEditingAsBlocks;
-}
-
export function __unstableHasActiveBlockOverlayActive( state, clientId ) {
// If the block editing is locked, the block overlay is always active.
if ( ! canEditBlock( state, clientId ) ) {
@@ -2841,6 +2863,65 @@ export function __unstableHasActiveBlockOverlayActive( state, clientId ) {
);
}
+export function __unstableGetContentLockingParent( state, clientId ) {
+ deprecated( '__unstableGetContentLockingParent', {
+ since: '6.3',
+ version: '6.4',
+ alternative: 'isContentLockedBlock',
+ } );
+ return getContentLockingBlock( state, clientId );
+}
+
+export function __unstableGetTemporarilyEditingAsBlocks( state ) {
+ deprecated( '__unstableGetTemporarilyEditingAsBlocks', {
+ since: '6.3',
+ version: '6.4',
+ } );
+ return getTemporarilyUnlockedBlock( state );
+}
+
+/**
+ * Returns whether or not the given block is _content locked_.
+ *
+ * A block is _content locked_ if it is nested within a block that has a
+ * `templateLock` attribute set to `'contentOnly'` (a _content locking_ block),
+ * or if the editor has a `templateLock` of `'contentOnly'`.
+ *
+ * If the block is nested within a content block type (see
+ * `settings.contentBlockTypes`) then it is not _content locked_.
+ *
+ * @param {Object} state Global application state.
+ * @param {string} clientId The client ID of the block to check.
+ *
+ * @return {boolean} Whether or not the block is content locked.
+ */
+export const isContentLockedBlock = createSelector(
+ ( state, clientId ) => {
+ const isWithinContentBlock = getBlockParents( state, clientId ).some(
+ ( parentClientId ) => isContentBlock( state, parentClientId )
+ );
+ if ( isWithinContentBlock ) {
+ return false;
+ }
+
+ if ( getTemplateLock( state ) === 'contentOnly' ) {
+ return true;
+ }
+
+ const isWithinContentLockingBlock = !! getContentLockingBlock(
+ state,
+ clientId
+ );
+ return isWithinContentLockingBlock;
+ },
+ ( state ) => [
+ state.blocks.parents,
+ state.settings.contentBlockTypes,
+ state.settings.templateLock,
+ state.blockListSettings,
+ ]
+);
+
export function __unstableIsWithinBlockOverlay( state, clientId ) {
let parent = state.blocks.parents.get( clientId );
while ( !! parent ) {
diff --git a/packages/block-editor/src/store/test/private-actions.js b/packages/block-editor/src/store/test/private-actions.js
index c4453547f6ce6a..b8b37be35e1bed 100644
--- a/packages/block-editor/src/store/test/private-actions.js
+++ b/packages/block-editor/src/store/test/private-actions.js
@@ -1,7 +1,11 @@
/**
* Internal dependencies
*/
-import { hideBlockInterface, showBlockInterface } from '../private-actions';
+import {
+ hideBlockInterface,
+ showBlockInterface,
+ setTemporarilyUnlockedBlock,
+} from '../private-actions';
describe( 'private actions', () => {
describe( 'hideBlockInterface', () => {
@@ -19,4 +23,17 @@ describe( 'private actions', () => {
} );
} );
} );
+
+ describe( 'setTemporarilyUnlockedBlock', () => {
+ it( 'should return the SET_TEMPORARILY_UNLOCKED_BLOCK action', () => {
+ expect(
+ setTemporarilyUnlockedBlock(
+ 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1'
+ )
+ ).toEqual( {
+ type: 'SET_TEMPORARILY_UNLOCKED_BLOCK',
+ clientId: 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1',
+ } );
+ } );
+ } );
} );
diff --git a/packages/block-editor/src/store/test/private-selectors.js b/packages/block-editor/src/store/test/private-selectors.js
index c5df265f75db35..d64f24f55d9034 100644
--- a/packages/block-editor/src/store/test/private-selectors.js
+++ b/packages/block-editor/src/store/test/private-selectors.js
@@ -4,6 +4,11 @@
import {
isBlockInterfaceHidden,
getLastInsertedBlocksClientIds,
+ isContentLockingBlock,
+ getContentLockingBlock,
+ isContentBlock,
+ getContentClientIdsTree,
+ getTemporarilyUnlockedBlock,
} from '../private-selectors';
describe( 'private selectors', () => {
@@ -31,9 +36,7 @@ describe( 'private selectors', () => {
lastBlockInserted: {},
};
- expect( getLastInsertedBlocksClientIds( state ) ).toEqual(
- undefined
- );
+ expect( getLastInsertedBlocksClientIds( state ) ).toBeUndefined();
} );
it( 'should return clientIds if blocks have been inserted', () => {
@@ -49,4 +52,330 @@ describe( 'private selectors', () => {
] );
} );
} );
+
+ describe( 'content locking selectors', () => {
+ const __experimentalHasContentRoleAttribute = jest.fn( () => false );
+ isContentBlock.registry = {
+ select: jest.fn( () => ( {
+ __experimentalHasContentRoleAttribute,
+ } ) ),
+ };
+
+ const baseState = {
+ settings: {},
+ blocks: {
+ byClientId: new Map(
+ Object.entries( {
+ '6926a815-c923-4daa-bc3f-7da2133b388d': {
+ clientId: '6926a815-c923-4daa-bc3f-7da2133b388d',
+ name: 'core/group',
+ },
+ '9f88f941-9984-419f-8ae7-e427c5b57513': {
+ clientId: '9f88f941-9984-419f-8ae7-e427c5b57513',
+ name: 'core/post-content',
+ },
+ 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1': {
+ clientId: 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1',
+ name: 'core/paragraph',
+ },
+ } )
+ ),
+ attributes: new Map(
+ Object.entries( {
+ '6926a815-c923-4daa-bc3f-7da2133b388d': {},
+ '9f88f941-9984-419f-8ae7-e427c5b57513': {},
+ 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1': {},
+ } )
+ ),
+ order: new Map(
+ Object.entries( {
+ '': [ '6926a815-c923-4daa-bc3f-7da2133b388d' ],
+ '6926a815-c923-4daa-bc3f-7da2133b388d': [
+ '9f88f941-9984-419f-8ae7-e427c5b57513',
+ ],
+ '9f88f941-9984-419f-8ae7-e427c5b57513': [
+ 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1',
+ ],
+ 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1': [],
+ } )
+ ),
+ parents: new Map(
+ Object.entries( {
+ '6926a815-c923-4daa-bc3f-7da2133b388d': '',
+ '9f88f941-9984-419f-8ae7-e427c5b57513':
+ '6926a815-c923-4daa-bc3f-7da2133b388d',
+ 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1':
+ '9f88f941-9984-419f-8ae7-e427c5b57513',
+ } )
+ ),
+ },
+ blockListSettings: {
+ '6926a815-c923-4daa-bc3f-7da2133b388d': {},
+ '9f88f941-9984-419f-8ae7-e427c5b57513': {},
+ 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1': {},
+ },
+ };
+
+ describe( 'isContentLockingBlock', () => {
+ it( 'should return true if the block is the content locking block', () => {
+ const state = {
+ ...baseState,
+ blockListSettings: {
+ '6926a815-c923-4daa-bc3f-7da2133b388d': {
+ templateLock: 'contentOnly',
+ },
+ '9f88f941-9984-419f-8ae7-e427c5b57513': {
+ templateLock: 'contentOnly',
+ },
+ 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1': {
+ templateLock: 'contentOnly',
+ },
+ },
+ };
+ expect(
+ isContentLockingBlock(
+ state,
+ '6926a815-c923-4daa-bc3f-7da2133b388d'
+ )
+ ).toBe( true );
+ } );
+
+ it( 'should return false if the block is not the content locking block', () => {
+ const state = {
+ ...baseState,
+ blockListSettings: {
+ '6926a815-c923-4daa-bc3f-7da2133b388d': {
+ templateLock: 'contentOnly',
+ },
+ '9f88f941-9984-419f-8ae7-e427c5b57513': {
+ templateLock: 'contentOnly',
+ },
+ 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1': {
+ templateLock: 'contentOnly',
+ },
+ },
+ };
+ expect(
+ isContentLockingBlock(
+ state,
+ 'a0d1cb17-2c08-4e7a-91be-007ba7ddc3a1'
+ )
+ ).toBe( false );
+ } );
+ } );
+
+ describe( 'getContentLockingBlock', () => {
+ it( 'should return undefined if there is no content locking block', () => {
+ const state = {
+ ...baseState,
+ };
+ expect(
+ getContentLockingBlock(
+ state,
+ 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1'
+ )
+ ).toBeUndefined();
+ } );
+
+ it( 'should return the topmost content locking block', () => {
+ const state = {
+ ...baseState,
+ blockListSettings: {
+ '6926a815-c923-4daa-bc3f-7da2133b388d': {
+ templateLock: 'contentOnly',
+ },
+ '9f88f941-9984-419f-8ae7-e427c5b57513': {
+ templateLock: 'contentOnly',
+ },
+ 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1': {
+ templateLock: 'contentOnly',
+ },
+ },
+ };
+ expect(
+ getContentLockingBlock(
+ state,
+ 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1'
+ )
+ ).toBe( '6926a815-c923-4daa-bc3f-7da2133b388d' );
+ } );
+
+ it( 'should return the given block if it is the sole content locking block', () => {
+ const state = {
+ ...baseState,
+ blockListSettings: {
+ '6926a815-c923-4daa-bc3f-7da2133b388d': {},
+ '9f88f941-9984-419f-8ae7-e427c5b57513': {},
+ 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1': {
+ templateLock: 'contentOnly',
+ },
+ },
+ };
+ expect(
+ getContentLockingBlock(
+ state,
+ 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1'
+ )
+ ).toBe( 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1' );
+ } );
+
+ it( 'should return undefined if editor is content locked', () => {
+ const state = {
+ ...baseState,
+ settings: {
+ templateLock: 'contentOnly',
+ },
+ blockListSettings: {
+ '6926a815-c923-4daa-bc3f-7da2133b388d': {
+ templateLock: 'contentOnly',
+ },
+ '9f88f941-9984-419f-8ae7-e427c5b57513': {
+ templateLock: 'contentOnly',
+ },
+ 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1': {
+ templateLock: 'contentOnly',
+ },
+ },
+ };
+ expect(
+ getContentLockingBlock(
+ state,
+ 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1'
+ )
+ ).toBeUndefined();
+ } );
+ } );
+
+ describe( 'isContentBlock', () => {
+ it( 'should return false by default', () => {
+ const state = {
+ ...baseState,
+ };
+ expect(
+ isContentBlock(
+ state,
+ 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1'
+ )
+ ).toBe( false );
+ } );
+
+ it( 'should return true if the block type is in settings.contentBlockTypes', () => {
+ const state = {
+ ...baseState,
+ settings: {
+ contentBlockTypes: [ 'core/paragraph' ],
+ },
+ };
+ expect(
+ isContentBlock(
+ state,
+ 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1'
+ )
+ ).toBe( true );
+ } );
+
+ it( 'should return true if the block has a content attribute', () => {
+ __experimentalHasContentRoleAttribute.mockReturnValue( true );
+ const state = {
+ ...baseState,
+ };
+ expect(
+ isContentBlock(
+ state,
+ 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1'
+ )
+ ).toBe( true );
+ __experimentalHasContentRoleAttribute.mockReturnValue( false );
+ } );
+ } );
+
+ describe( 'getContentClientIdsTree', () => {
+ it( 'should return an empty array if there are no content blocks', () => {
+ const state = {
+ ...baseState,
+ };
+ expect( getContentClientIdsTree( state ) ).toEqual( [] );
+ getContentClientIdsTree.clear();
+ } );
+
+ it( 'should return all content blocks', () => {
+ const state = {
+ ...baseState,
+ settings: {
+ contentBlockTypes: [ 'core/paragraph' ],
+ },
+ };
+ expect( getContentClientIdsTree( state ) ).toEqual( [
+ {
+ clientId: 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1',
+ innerBlocks: [],
+ },
+ ] );
+ getContentClientIdsTree.clear();
+ } );
+
+ it( 'should return all children of content blocks', () => {
+ const state = {
+ ...baseState,
+ settings: {
+ contentBlockTypes: [ 'core/post-content' ],
+ },
+ };
+ expect( getContentClientIdsTree( state ) ).toEqual( [
+ {
+ clientId: '9f88f941-9984-419f-8ae7-e427c5b57513',
+ innerBlocks: [
+ {
+ clientId:
+ 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1',
+ innerBlocks: [],
+ },
+ ],
+ },
+ ] );
+ getContentClientIdsTree.clear();
+ } );
+
+ it( 'should return content blocks nested within a given root', () => {
+ const state = {
+ ...baseState,
+ settings: {
+ contentBlockTypes: [
+ 'core/post-content',
+ 'core/paragraph',
+ ],
+ },
+ };
+ expect(
+ getContentClientIdsTree(
+ state,
+ '9f88f941-9984-419f-8ae7-e427c5b57513'
+ )
+ ).toEqual( [
+ {
+ clientId: 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1',
+ innerBlocks: [],
+ },
+ ] );
+ getContentClientIdsTree.clear();
+ } );
+ } );
+
+ describe( 'getTemporarilyUnlockedBlock', () => {
+ it( 'should return undefined if there are no temporarily unlocked blocks', () => {
+ const state = {};
+ expect( getTemporarilyUnlockedBlock( state ) ).toBeUndefined();
+ } );
+
+ it( 'should return the clientId of the temporarily unlocked block', () => {
+ const state = {
+ temporarilyUnlockedBlock:
+ 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1',
+ };
+ expect( getTemporarilyUnlockedBlock( state ) ).toBe(
+ 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1'
+ );
+ } );
+ } );
+ } );
} );
diff --git a/packages/block-editor/src/store/test/reducer.js b/packages/block-editor/src/store/test/reducer.js
index 609cbb59c6e54b..3d715830edd9fc 100644
--- a/packages/block-editor/src/store/test/reducer.js
+++ b/packages/block-editor/src/store/test/reducer.js
@@ -32,6 +32,7 @@ import {
blockListSettings,
lastBlockAttributesChange,
lastBlockInserted,
+ temporarilyUnlockedBlock,
} from '../reducer';
const noop = () => {};
@@ -3367,4 +3368,19 @@ describe( 'state', () => {
expect( state ).toEqual( expectedState );
} );
} );
+
+ describe( 'temporarilyUnlockedBlock', () => {
+ it( 'defaults to null', () => {
+ const state = temporarilyUnlockedBlock( undefined, {} );
+ expect( state ).toBeNull();
+ } );
+
+ it( 'is set when SET_TEMPORARILY_UNLOCKED_BLOCK is dispatched', () => {
+ const state = temporarilyUnlockedBlock( null, {
+ type: 'SET_TEMPORARILY_UNLOCKED_BLOCK',
+ clientId: 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1',
+ } );
+ expect( state ).toBe( 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1' );
+ } );
+ } );
} );
diff --git a/packages/block-editor/src/store/test/selectors.js b/packages/block-editor/src/store/test/selectors.js
index 60d90d80b9d41e..6a14eb77a93f0d 100644
--- a/packages/block-editor/src/store/test/selectors.js
+++ b/packages/block-editor/src/store/test/selectors.js
@@ -12,6 +12,7 @@ import { RawHTML } from '@wordpress/element';
* Internal dependencies
*/
import * as selectors from '../selectors';
+import { isContentBlock } from '../private-selectors';
const {
getBlockName,
@@ -72,6 +73,7 @@ const {
__experimentalGetPatternTransformItems,
wasBlockJustInserted,
__experimentalGetGlobalBlocksByName,
+ isContentLockedBlock,
} = selectors;
describe( 'selectors', () => {
@@ -4687,3 +4689,151 @@ describe( '__unstableGetClientIdsTree', () => {
] );
} );
} );
+
+describe( 'isContentLockedBlock', () => {
+ isContentBlock.registry = {
+ select: jest.fn( () => ( {
+ __experimentalHasContentRoleAttribute: jest.fn( () => false ),
+ } ) ),
+ };
+
+ const baseState = {
+ settings: {},
+ blocks: {
+ byClientId: new Map(
+ Object.entries( {
+ '6926a815-c923-4daa-bc3f-7da2133b388d': {
+ clientId: '6926a815-c923-4daa-bc3f-7da2133b388d',
+ name: 'core/group',
+ },
+ '9f88f941-9984-419f-8ae7-e427c5b57513': {
+ clientId: '9f88f941-9984-419f-8ae7-e427c5b57513',
+ name: 'core/post-content',
+ },
+ 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1': {
+ clientId: 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1',
+ name: 'core/paragraph',
+ },
+ } )
+ ),
+ attributes: new Map(
+ Object.entries( {
+ '6926a815-c923-4daa-bc3f-7da2133b388d': {},
+ '9f88f941-9984-419f-8ae7-e427c5b57513': {},
+ 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1': {},
+ } )
+ ),
+ order: new Map(
+ Object.entries( {
+ '': [ '6926a815-c923-4daa-bc3f-7da2133b388d' ],
+ '6926a815-c923-4daa-bc3f-7da2133b388d': [
+ '9f88f941-9984-419f-8ae7-e427c5b57513',
+ ],
+ '9f88f941-9984-419f-8ae7-e427c5b57513': [
+ 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1',
+ ],
+ 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1': [],
+ } )
+ ),
+ parents: new Map(
+ Object.entries( {
+ '6926a815-c923-4daa-bc3f-7da2133b388d': '',
+ '9f88f941-9984-419f-8ae7-e427c5b57513':
+ '6926a815-c923-4daa-bc3f-7da2133b388d',
+ 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1':
+ '9f88f941-9984-419f-8ae7-e427c5b57513',
+ } )
+ ),
+ },
+ blockListSettings: {
+ '6926a815-c923-4daa-bc3f-7da2133b388d': {},
+ '9f88f941-9984-419f-8ae7-e427c5b57513': {},
+ 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1': {},
+ },
+ };
+
+ it( 'should return false by default', () => {
+ const state = {
+ ...baseState,
+ };
+ expect(
+ isContentLockedBlock(
+ state,
+ 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1'
+ )
+ ).toBe( false );
+ } );
+
+ it( 'should return true if editor is content locked', () => {
+ const state = {
+ ...baseState,
+ settings: {
+ templateLock: 'contentOnly',
+ },
+ };
+ expect(
+ isContentLockedBlock(
+ state,
+ 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1'
+ )
+ ).toBe( true );
+ } );
+
+ it( 'should return true if block is content locked', () => {
+ const state = {
+ ...baseState,
+ blockListSettings: {
+ ...baseState.blockListSettings,
+ 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1': {
+ templateLock: 'contentOnly',
+ },
+ },
+ };
+ expect(
+ isContentLockedBlock(
+ state,
+ 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1'
+ )
+ ).toBe( true );
+ } );
+
+ it( 'should return true if block is nested within a content locked block', () => {
+ const state = {
+ ...baseState,
+ blockListSettings: {
+ ...baseState.blockListSettings,
+ '6926a815-c923-4daa-bc3f-7da2133b388d': {
+ templateLock: 'contentOnly',
+ },
+ },
+ };
+ expect(
+ isContentLockedBlock(
+ state,
+ 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1'
+ )
+ ).toBe( true );
+ } );
+
+ it( 'should return false if block is nested within a content block', () => {
+ const state = {
+ ...baseState,
+ settings: {
+ ...baseState.settings,
+ contentBlockTypes: [ 'core/post-content' ],
+ },
+ blockListSettings: {
+ ...baseState.blockListSettings,
+ '6926a815-c923-4daa-bc3f-7da2133b388d': {
+ templateLock: 'contentOnly',
+ },
+ },
+ };
+ expect(
+ isContentLockedBlock(
+ state,
+ 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1'
+ )
+ ).toBe( false );
+ } );
+} );
diff --git a/packages/block-library/src/image/edit.js b/packages/block-library/src/image/edit.js
index 19e8196dfc7a51..5f5d8760c0aa5e 100644
--- a/packages/block-library/src/image/edit.js
+++ b/packages/block-library/src/image/edit.js
@@ -127,17 +127,16 @@ export function ImageEdit( {
const ref = useRef();
const { imageDefaultSize, mediaUpload, isContentLocked } = useSelect(
( select ) => {
- const { getSettings, __unstableGetContentLockingParent } =
+ const { getSettings, isContentLockedBlock } =
select( blockEditorStore );
const settings = getSettings();
return {
imageDefaultSize: settings.imageDefaultSize,
mediaUpload: settings.mediaUpload,
- isContentLocked:
- !! __unstableGetContentLockingParent( clientId ),
+ isContentLocked: isContentLockedBlock( clientId ),
};
},
- []
+ [ clientId ]
);
const { createErrorNotice } = useDispatch( noticesStore );
diff --git a/packages/block-library/src/media-text/edit.js b/packages/block-library/src/media-text/edit.js
index f2848a584d0d8a..1e39528c3e7719 100644
--- a/packages/block-library/src/media-text/edit.js
+++ b/packages/block-library/src/media-text/edit.js
@@ -149,11 +149,10 @@ function MediaTextEdit( { attributes, isSelected, setAttributes, clientId } ) {
const { imageSizes, image, isContentLocked } = useSelect(
( select ) => {
- const { __unstableGetContentLockingParent, getSettings } =
+ const { isContentLockedBlock, getSettings } =
select( blockEditorStore );
return {
- isContentLocked:
- !! __unstableGetContentLockingParent( clientId ),
+ isContentLocked: isContentLockedBlock( clientId ),
image:
mediaId && isSelected
? select( coreStore ).getMedia( mediaId, {
diff --git a/packages/data/src/redux-store/index.js b/packages/data/src/redux-store/index.js
index c4dc12643673b6..50a0ade0d551c9 100644
--- a/packages/data/src/redux-store/index.js
+++ b/packages/data/src/redux-store/index.js
@@ -221,12 +221,14 @@ export default function createReduxStore( key, options ) {
get: ( target, prop ) => {
return (
mapSelectors(
- mapValues(
- privateSelectors,
- ( selector ) =>
- ( state, ...args ) =>
- selector( state.root, ...args )
- ),
+ mapValues( privateSelectors, ( selector ) => {
+ if ( selector.isRegistrySelector ) {
+ selector.registry = registry;
+ }
+
+ return ( state, ...args ) =>
+ selector( state.root, ...args );
+ } ),
store
)[ prop ] || selectors[ prop ]
);