@wordpress/editor
Version:
Enhanced block editor for WordPress posts.
245 lines (232 loc) • 6.56 kB
JavaScript
/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
import {
PanelBody,
__experimentalText as Text,
Dropdown,
Button,
__experimentalVStack as VStack,
} from '@wordpress/components';
import { useDispatch, useSelect } from '@wordpress/data';
import { useMemo, useState } from '@wordpress/element';
import { __experimentalInspectorPopoverHeader as InspectorPopoverHeader } from '@wordpress/block-editor';
import { store as coreStore } from '@wordpress/core-data';
import { decodeEntities } from '@wordpress/html-entities';
/**
* Internal dependencies
*/
import PostExcerptForm from './index';
import PostExcerptCheck from './check';
import PluginPostExcerpt from './plugin';
import { TEMPLATE_ORIGINS } from '../../store/constants';
import { store as editorStore } from '../../store';
import { getTemplateInfo } from '../../utils/get-template-info';
/**
* Module Constants
*/
const PANEL_NAME = 'post-excerpt';
function ExcerptPanel() {
const { isOpened, isEnabled, postType } = useSelect( ( select ) => {
const {
isEditorPanelOpened,
isEditorPanelEnabled,
getCurrentPostType,
} = select( editorStore );
return {
isOpened: isEditorPanelOpened( PANEL_NAME ),
isEnabled: isEditorPanelEnabled( PANEL_NAME ),
postType: getCurrentPostType(),
};
}, [] );
const { toggleEditorPanelOpened } = useDispatch( editorStore );
const toggleExcerptPanel = () => toggleEditorPanelOpened( PANEL_NAME );
if ( ! isEnabled ) {
return null;
}
// There are special cases where we want to label the excerpt as a description.
const shouldUseDescriptionLabel = [
'wp_template',
'wp_template_part',
'wp_block',
].includes( postType );
return (
<PanelBody
title={
shouldUseDescriptionLabel
? __( 'Description' )
: __( 'Excerpt' )
}
opened={ isOpened }
onToggle={ toggleExcerptPanel }
>
<PluginPostExcerpt.Slot>
{ ( fills ) => (
<>
<PostExcerptForm />
{ fills }
</>
) }
</PluginPostExcerpt.Slot>
</PanelBody>
);
}
/**
* Is rendered if the post type supports excerpts and allows editing the excerpt.
*
* @return {React.ReactNode} The rendered PostExcerptPanel component.
*/
export default function PostExcerptPanel() {
return (
<PostExcerptCheck>
<ExcerptPanel />
</PostExcerptCheck>
);
}
export function PrivatePostExcerptPanel() {
return (
<PostExcerptCheck>
<PrivateExcerpt />
</PostExcerptCheck>
);
}
function PrivateExcerpt() {
const { shouldRender, excerpt, shouldBeUsedAsDescription, allowEditing } =
useSelect( ( select ) => {
const {
getCurrentPostType,
getCurrentPostId,
getEditedPostAttribute,
isEditorPanelEnabled,
} = select( editorStore );
const postType = getCurrentPostType();
const isTemplateOrTemplatePart = [
'wp_template',
'wp_template_part',
].includes( postType );
const isPattern = postType === 'wp_block';
// These post types use the `excerpt` field as a description semantically, so we need to
// handle proper labeling and some flows where we should always render them as text.
const _shouldBeUsedAsDescription =
isTemplateOrTemplatePart || isPattern;
const _usedAttribute = isTemplateOrTemplatePart
? 'description'
: 'excerpt';
const _excerpt = getEditedPostAttribute( _usedAttribute );
// We need to fetch the entity in this case to check if we'll allow editing.
const template =
isTemplateOrTemplatePart &&
select( coreStore ).getEntityRecord(
'postType',
postType,
getCurrentPostId()
);
const fallback =
! _excerpt && isTemplateOrTemplatePart
? getTemplateInfo( {
template,
templateTypes:
select( coreStore ).getCurrentTheme()
?.default_template_types,
} )?.description
: undefined;
// For post types that use excerpt as description, we do not abide
// by the `isEnabled` panel flag in order to render them as text.
const _shouldRender =
isEditorPanelEnabled( PANEL_NAME ) ||
_shouldBeUsedAsDescription;
return {
excerpt: _excerpt ?? fallback,
shouldRender: _shouldRender,
shouldBeUsedAsDescription: _shouldBeUsedAsDescription,
// If we should render, allow editing for all post types that are not used as description.
// For the rest allow editing only for user generated entities.
allowEditing:
_shouldRender &&
( ! _shouldBeUsedAsDescription ||
isPattern ||
( template &&
template.source === TEMPLATE_ORIGINS.custom &&
! template.has_theme_file &&
template.is_custom ) ),
};
}, [] );
const [ popoverAnchor, setPopoverAnchor ] = useState( null );
const label = shouldBeUsedAsDescription
? __( 'Description' )
: __( 'Excerpt' );
// Memoize popoverProps to avoid returning a new object every time.
const popoverProps = useMemo(
() => ( {
// Anchor the popover to the middle of the entire row so that it doesn't
// move around when the label changes.
anchor: popoverAnchor,
'aria-label': label,
headerTitle: label,
placement: 'left-start',
offset: 36,
shift: true,
} ),
[ popoverAnchor, label ]
);
if ( ! shouldRender ) {
return false;
}
const excerptText = !! excerpt && (
<Text align="left" numberOfLines={ 4 } truncate={ allowEditing }>
{ decodeEntities( excerpt ) }
</Text>
);
if ( ! allowEditing ) {
return excerptText;
}
const excerptPlaceholder = shouldBeUsedAsDescription
? __( 'Add a description…' )
: __( 'Add an excerpt…' );
const triggerEditLabel = shouldBeUsedAsDescription
? __( 'Edit description' )
: __( 'Edit excerpt' );
return (
<VStack>
{ excerptText }
<Dropdown
className="editor-post-excerpt__dropdown"
contentClassName="editor-post-excerpt__dropdown__content"
popoverProps={ popoverProps }
focusOnMount
ref={ setPopoverAnchor }
renderToggle={ ( { onToggle } ) => (
<Button
__next40pxDefaultSize
onClick={ onToggle }
variant="link"
>
{ excerptText ? triggerEditLabel : excerptPlaceholder }
</Button>
) }
renderContent={ ( { onClose } ) => (
<>
<InspectorPopoverHeader
title={ label }
onClose={ onClose }
/>
<VStack spacing={ 4 }>
<PluginPostExcerpt.Slot>
{ ( fills ) => (
<>
<PostExcerptForm
hideLabelFromVision
updateOnBlur
/>
{ fills }
</>
) }
</PluginPostExcerpt.Slot>
</VStack>
</>
) }
/>
</VStack>
);
}