@wordpress/block-library
Version:
Block library for the WordPress editor.
163 lines (152 loc) • 4.65 kB
JavaScript
import { __ } from '@wordpress/i18n';
import { useEffect, useState } from '@wordpress/element';
import {
BlockControls,
BlockIcon,
InspectorControls,
useBlockProps,
store as blockEditorStore,
privateApis as blockEditorPrivateApis,
} from '@wordpress/block-editor';
import { parse, serialize, getBlockContent } from '@wordpress/blocks';
import { useSelect, useDispatch, useRegistry } from '@wordpress/data';
import deprecated from '@wordpress/deprecated';
import {
ToolbarButton,
ToolbarGroup,
Placeholder,
Button,
__experimentalVStack as VStack,
} from '@wordpress/components';
import { code } from '@wordpress/icons';
import { unlock } from '../lock-unlock';
import HTMLEditModal from './modal';
const { InnerContent } = unlock( blockEditorPrivateApis );
export default function HTMLEdit( { clientId, attributes } ) {
const [ isModalOpen, setIsModalOpen ] = useState( false );
const registry = useRegistry();
const { updateBlock, replaceInnerBlocks } = useDispatch( blockEditorStore );
const content = useSelect(
( select ) => {
const block = select( blockEditorStore ).getBlock( clientId );
return block ? getBlockContent( block ) : '';
},
[ clientId ]
);
const blockProps = useBlockProps( {
className: 'block-library-html__edit',
} );
// Re-parse the edited content: static HTML becomes the block's
// `innerContent` fragments and `<!-- wp:* -->` delimited segments become
// inner blocks mounted at their positions within the static markup.
const onUpdate = ( nextContent ) => {
if ( nextContent === content ) {
return;
}
const [ parsedBlock ] = parse(
`<!-- wp:html -->\n${ nextContent }\n<!-- /wp:html -->`
);
const nextInnerBlocks = parsedBlock?.innerBlocks ?? [];
const prevInnerBlocks = registry
.select( blockEditorStore )
.getBlocks( clientId );
// Keep the existing inner blocks, and thereby their client IDs and
// selection, when their markup is unchanged — e.g. when only the
// surrounding static HTML was edited.
const innerBlocksUnchanged =
prevInnerBlocks.length === nextInnerBlocks.length &&
prevInnerBlocks.every(
( block, index ) =>
serialize( block ) === serialize( nextInnerBlocks[ index ] )
);
registry.batch( () => {
updateBlock( clientId, {
innerContent:
parsedBlock?.innerContent ??
( nextContent ? [ nextContent ] : [] ),
} );
if ( ! innerBlocksUnchanged ) {
replaceInnerBlocks( clientId, nextInnerBlocks, false );
}
} );
};
// Migrate the deprecated `content` attribute. The block's markup now lives
// in its inner content, but a block created via `createBlock( 'core/html',
// { content } )` still arrives with a `content` attribute. As soon as it
// loads, move that markup into the block's inner content and clear the
// attribute.
useEffect( () => {
if ( ! attributes.content ) {
return;
}
deprecated( 'The content attribute on the Custom HTML block', {
since: '7.1',
alternative: 'inner content',
} );
updateBlock( clientId, {
attributes: { content: undefined },
innerContent: [ attributes.content ],
} );
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [ attributes.content ] );
// Show placeholder when content is empty
if ( ! content?.trim() ) {
return (
<div { ...blockProps }>
<Placeholder
icon={ <BlockIcon icon={ code } /> }
label={ __( 'Custom HTML' ) }
instructions={ __(
'Add custom HTML code and preview how it looks.'
) }
>
<Button
__next40pxDefaultSize
variant="primary"
onClick={ () => setIsModalOpen( true ) }
>
{ __( 'Edit HTML' ) }
</Button>
</Placeholder>
{ isModalOpen && (
<HTMLEditModal
onRequestClose={ () => setIsModalOpen( false ) }
content={ content }
onUpdate={ onUpdate }
/>
) }
</div>
);
}
return (
<div { ...blockProps }>
<BlockControls>
<ToolbarGroup>
<ToolbarButton onClick={ () => setIsModalOpen( true ) }>
{ __( 'Edit code' ) }
</ToolbarButton>
</ToolbarGroup>
</BlockControls>
<InspectorControls>
<VStack className="block-library-html__edit-code" expanded>
<Button
className="block-library-html__edit-code-button"
__next40pxDefaultSize
variant="secondary"
onClick={ () => setIsModalOpen( true ) }
>
{ __( 'Edit code' ) }
</Button>
</VStack>
</InspectorControls>
<InnerContent clientId={ clientId } />
{ isModalOpen && (
<HTMLEditModal
onRequestClose={ () => setIsModalOpen( false ) }
content={ content }
onUpdate={ onUpdate }
/>
) }
</div>
);
}