2021-09-21 10:18:27 +00:00
|
|
|
/**
|
|
|
|
* External dependencies
|
|
|
|
*/
|
2021-10-15 09:48:57 +00:00
|
|
|
import {
|
|
|
|
useLayoutEffect,
|
|
|
|
useRef,
|
|
|
|
useCallback,
|
|
|
|
useMemo,
|
|
|
|
} from '@wordpress/element';
|
2021-09-21 10:18:27 +00:00
|
|
|
import { useSelect, useDispatch } from '@wordpress/data';
|
|
|
|
import {
|
|
|
|
createBlock,
|
|
|
|
getBlockType,
|
|
|
|
Block,
|
|
|
|
AttributeSource,
|
2021-10-15 09:48:57 +00:00
|
|
|
synchronizeBlocksWithTemplate,
|
|
|
|
TemplateArray,
|
2021-09-21 10:18:27 +00:00
|
|
|
} from '@wordpress/blocks';
|
2021-10-15 09:48:57 +00:00
|
|
|
import { isEqual } from 'lodash';
|
2021-09-21 10:18:27 +00:00
|
|
|
|
|
|
|
const isBlockLocked = ( {
|
|
|
|
attributes,
|
|
|
|
}: {
|
|
|
|
attributes: Record< string, AttributeSource.Attribute >;
|
|
|
|
} ) => Boolean( attributes.lock?.remove || attributes.lock?.default?.remove );
|
|
|
|
|
2021-10-15 09:48:57 +00:00
|
|
|
/**
|
|
|
|
* useForcedLayout hook
|
|
|
|
*
|
|
|
|
* Responsible for ensuring FORCED blocks exist in the inner block layout. Forced blocks cannot be removed.
|
|
|
|
*/
|
2021-09-21 10:18:27 +00:00
|
|
|
export const useForcedLayout = ( {
|
|
|
|
clientId,
|
2021-10-15 09:48:57 +00:00
|
|
|
registeredBlocks,
|
|
|
|
defaultTemplate = [],
|
2021-09-21 10:18:27 +00:00
|
|
|
}: {
|
2021-10-15 09:48:57 +00:00
|
|
|
// Client ID of the parent block.
|
2021-09-21 10:18:27 +00:00
|
|
|
clientId: string;
|
2021-10-15 09:48:57 +00:00
|
|
|
// An array of registered blocks that may be forced in this particular layout.
|
|
|
|
registeredBlocks: Array< string >;
|
|
|
|
// The default template for the inner blocks in this layout.
|
|
|
|
defaultTemplate: TemplateArray;
|
2021-09-21 10:18:27 +00:00
|
|
|
} ): void => {
|
2021-10-15 09:48:57 +00:00
|
|
|
const currentRegisteredBlocks = useRef( registeredBlocks );
|
|
|
|
const currentDefaultTemplate = useRef( defaultTemplate );
|
|
|
|
|
|
|
|
const { insertBlock, replaceInnerBlocks } = useDispatch(
|
|
|
|
'core/block-editor'
|
|
|
|
);
|
|
|
|
|
|
|
|
const { innerBlocks, registeredBlockTypes } = useSelect(
|
2021-09-21 10:18:27 +00:00
|
|
|
( select ) => {
|
|
|
|
return {
|
|
|
|
innerBlocks: select( 'core/block-editor' ).getBlocks(
|
|
|
|
clientId
|
|
|
|
),
|
2021-10-15 09:48:57 +00:00
|
|
|
registeredBlockTypes: currentRegisteredBlocks.current.map(
|
|
|
|
( blockName ) => getBlockType( blockName )
|
2021-09-21 10:18:27 +00:00
|
|
|
),
|
|
|
|
};
|
|
|
|
},
|
2021-10-15 09:48:57 +00:00
|
|
|
[ clientId, currentRegisteredBlocks.current ]
|
|
|
|
);
|
|
|
|
|
|
|
|
const appendBlock = useCallback(
|
|
|
|
( block, position ) => {
|
|
|
|
const newBlock = createBlock( block.name );
|
|
|
|
insertBlock( newBlock, position, clientId, false );
|
|
|
|
},
|
|
|
|
[ clientId, insertBlock ]
|
2021-09-21 10:18:27 +00:00
|
|
|
);
|
2021-10-15 09:48:57 +00:00
|
|
|
|
|
|
|
const lockedBlockTypes = useMemo(
|
|
|
|
() =>
|
|
|
|
registeredBlockTypes.filter(
|
|
|
|
( block: Block | undefined ) => block && isBlockLocked( block )
|
|
|
|
),
|
|
|
|
[ registeredBlockTypes ]
|
|
|
|
) as Block[];
|
|
|
|
|
2021-09-21 10:18:27 +00:00
|
|
|
/**
|
|
|
|
* If the current inner blocks differ from the registered blocks, push the differences.
|
|
|
|
*/
|
|
|
|
useLayoutEffect( () => {
|
|
|
|
if ( ! clientId ) {
|
|
|
|
return;
|
|
|
|
}
|
2021-10-15 09:48:57 +00:00
|
|
|
|
|
|
|
// If there are NO inner blocks, sync with the given template.
|
|
|
|
if (
|
|
|
|
innerBlocks.length === 0 &&
|
|
|
|
currentDefaultTemplate.current.length > 0
|
|
|
|
) {
|
|
|
|
const nextBlocks = synchronizeBlocksWithTemplate(
|
|
|
|
innerBlocks,
|
|
|
|
currentDefaultTemplate.current
|
|
|
|
);
|
|
|
|
if ( ! isEqual( nextBlocks, innerBlocks ) ) {
|
|
|
|
replaceInnerBlocks( clientId, nextBlocks );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Find registered locked blocks missing from Inner Blocks and append them.
|
|
|
|
lockedBlockTypes.forEach( ( block ) => {
|
|
|
|
// If the locked block type is already in the layout, we can skip this one.
|
2021-09-21 10:18:27 +00:00
|
|
|
if (
|
2021-10-15 09:48:57 +00:00
|
|
|
innerBlocks.find(
|
2021-09-21 10:18:27 +00:00
|
|
|
( { name }: { name: string } ) => name === block.name
|
|
|
|
)
|
|
|
|
) {
|
2021-10-15 09:48:57 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Is the forced block part of the default template, find it's original position.
|
|
|
|
const defaultTemplatePosition = currentDefaultTemplate.current.findIndex(
|
|
|
|
( [ blockName ] ) => blockName === block.name
|
|
|
|
);
|
|
|
|
|
|
|
|
switch ( defaultTemplatePosition ) {
|
|
|
|
case -1:
|
|
|
|
// The block is not part of the default template so we append it to the current layout.
|
|
|
|
appendBlock( block, innerBlocks.length );
|
|
|
|
break;
|
|
|
|
case 0:
|
|
|
|
// The block was the first block in the default layout, so prepend it to the current layout.
|
|
|
|
appendBlock( block, 0 );
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
// The new layout may have extra blocks compared to the default template, so rather than insert
|
|
|
|
// at the default position, we should append it after another default block.
|
|
|
|
const adjacentBlock =
|
|
|
|
currentDefaultTemplate.current[
|
|
|
|
defaultTemplatePosition - 1
|
|
|
|
];
|
|
|
|
const position = innerBlocks.findIndex(
|
|
|
|
( { name: blockName } ) =>
|
|
|
|
blockName === adjacentBlock[ 0 ]
|
|
|
|
);
|
|
|
|
appendBlock(
|
|
|
|
block,
|
|
|
|
position === -1 ? defaultTemplatePosition : position + 1
|
|
|
|
);
|
|
|
|
break;
|
2021-09-21 10:18:27 +00:00
|
|
|
}
|
|
|
|
} );
|
2021-10-15 09:48:57 +00:00
|
|
|
}, [
|
|
|
|
clientId,
|
|
|
|
innerBlocks,
|
|
|
|
lockedBlockTypes,
|
|
|
|
replaceInnerBlocks,
|
|
|
|
appendBlock,
|
|
|
|
] );
|
2021-09-21 10:18:27 +00:00
|
|
|
};
|