@wordpress/block-library
Version:
Block library for the WordPress editor.
246 lines (224 loc) • 6.13 kB
JavaScript
/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
import {
InspectorControls,
useBlockProps,
useInnerBlocksProps,
RecursionProvider,
useHasRecursion,
Warning,
privateApis as blockEditorPrivateApis,
__experimentalUseBlockPreview as useBlockPreview,
} from '@wordpress/block-editor';
import { parse } from '@wordpress/blocks';
import {
useEntityProp,
useEntityBlockEditor,
store as coreStore,
} from '@wordpress/core-data';
import { useSelect } from '@wordpress/data';
import { useMemo } from '@wordpress/element';
/**
* Internal dependencies
*/
import { useCanEditEntity } from '../utils/hooks';
import { unlock } from '../lock-unlock';
const { HTMLElementControl } = unlock( blockEditorPrivateApis );
function ReadOnlyContent( {
parentLayout,
layoutClassNames,
userCanEdit,
postType,
postId,
tagName: TagName = 'div',
} ) {
const [ , , content ] = useEntityProp(
'postType',
postType,
'content',
postId
);
const blockProps = useBlockProps( { className: layoutClassNames } );
const blocks = useMemo( () => {
return content?.raw ? parse( content.raw ) : [];
}, [ content?.raw ] );
const blockPreviewProps = useBlockPreview( {
blocks,
props: blockProps,
layout: parentLayout,
} );
if ( userCanEdit ) {
/*
* Rendering the block preview using the raw content blocks allows for
* block support styles to be generated and applied by the editor.
*
* The preview using the raw blocks can only be presented to users with
* edit permissions for the post to prevent potential exposure of private
* block content.
*/
return <div { ...blockPreviewProps }></div>;
}
return content?.protected ? (
<TagName { ...blockProps }>
<Warning>{ __( 'This content is password protected.' ) }</Warning>
</TagName>
) : (
<TagName
{ ...blockProps }
dangerouslySetInnerHTML={ { __html: content?.rendered } }
></TagName>
);
}
function EditableContent( { context = {}, tagName: TagName = 'div' } ) {
const { postType, postId } = context;
const [ blocks, onInput, onChange ] = useEntityBlockEditor(
'postType',
postType,
{ id: postId }
);
const entityRecord = useSelect(
( select ) => {
return select( coreStore ).getEntityRecord(
'postType',
postType,
postId
);
},
[ postType, postId ]
);
const hasInnerBlocks = !! entityRecord?.content?.raw || blocks?.length;
const initialInnerBlocks = [ [ 'core/paragraph' ] ];
const props = useInnerBlocksProps(
useBlockProps( { className: 'entry-content' } ),
{
value: blocks,
onInput,
onChange,
template: ! hasInnerBlocks ? initialInnerBlocks : undefined,
}
);
return <TagName { ...props } />;
}
function Content( props ) {
const {
context: { queryId, postType, postId } = {},
layoutClassNames,
tagName,
} = props;
const userCanEdit = useCanEditEntity( 'postType', postType, postId );
if ( userCanEdit === undefined ) {
return null;
}
const isDescendentOfQueryLoop = Number.isFinite( queryId );
const isEditable = userCanEdit && ! isDescendentOfQueryLoop;
return isEditable ? (
<EditableContent { ...props } />
) : (
<ReadOnlyContent
parentLayout={ props.parentLayout }
layoutClassNames={ layoutClassNames }
userCanEdit={ userCanEdit }
postType={ postType }
postId={ postId }
tagName={ tagName }
/>
);
}
function Placeholder( { layoutClassNames } ) {
const blockProps = useBlockProps( { className: layoutClassNames } );
return (
<div { ...blockProps }>
<p>
{ __(
'This is the Content block, it will display all the blocks in any single post or page.'
) }
</p>
<p>
{ __(
'That might be a simple arrangement like consecutive paragraphs in a blog post, or a more elaborate composition that includes image galleries, videos, tables, columns, and any other block types.'
) }
</p>
<p>
{ __(
'If there are any Custom Post Types registered at your site, the Content block can display the contents of those entries as well.'
) }
</p>
</div>
);
}
function RecursionError() {
const blockProps = useBlockProps();
return (
<div { ...blockProps }>
<Warning>
{ __( 'Block cannot be rendered inside itself.' ) }
</Warning>
</div>
);
}
/**
* Render inspector controls for the PostContent block.
*
* @param {Object} props Component props.
* @param {string} props.tagName The HTML tag name.
* @param {Function} props.onSelectTagName onChange function for the SelectControl.
* @param {string} props.clientId The client ID of the current block.
*
* @return {JSX.Element} The control group.
*/
function PostContentEditControls( { tagName, onSelectTagName, clientId } ) {
return (
<InspectorControls group="advanced">
<HTMLElementControl
tagName={ tagName }
onChange={ onSelectTagName }
clientId={ clientId }
options={ [
{ label: __( 'Default (<div>)' ), value: 'div' },
{ label: '<main>', value: 'main' },
{ label: '<section>', value: 'section' },
{ label: '<article>', value: 'article' },
] }
/>
</InspectorControls>
);
}
export default function PostContentEdit( {
context,
attributes: { tagName = 'div' },
setAttributes,
clientId,
__unstableLayoutClassNames: layoutClassNames,
__unstableParentLayout: parentLayout,
} ) {
const { postId: contextPostId, postType: contextPostType } = context;
const hasAlreadyRendered = useHasRecursion( contextPostId );
if ( contextPostId && contextPostType && hasAlreadyRendered ) {
return <RecursionError />;
}
const handleSelectTagName = ( value ) => {
setAttributes( { tagName: value } );
};
return (
<>
<PostContentEditControls
tagName={ tagName }
onSelectTagName={ handleSelectTagName }
clientId={ clientId }
/>
<RecursionProvider uniqueId={ contextPostId }>
{ contextPostId && contextPostType ? (
<Content
context={ context }
parentLayout={ parentLayout }
layoutClassNames={ layoutClassNames }
/>
) : (
<Placeholder layoutClassNames={ layoutClassNames } />
) }
</RecursionProvider>
</>
);
}