@wordpress/block-library
Version:
Block library for the WordPress editor.
248 lines (246 loc) • 9.2 kB
JavaScript
/**
* WordPress dependencies
*/
import { serialize } from '@wordpress/blocks';
import { useSelect, useDispatch } from '@wordpress/data';
import { BlockSettingsMenuControls, useBlockProps, Warning, store as blockEditorStore, RecursionProvider, useHasRecursion, InspectorControls, __experimentalBlockPatternsList as BlockPatternsList, BlockControls } from '@wordpress/block-editor';
import { PanelBody, Spinner, Modal, MenuItem, ToolbarButton } from '@wordpress/components';
import { __, sprintf } from '@wordpress/i18n';
import { store as coreStore } from '@wordpress/core-data';
import { useState } from '@wordpress/element';
import { store as noticesStore } from '@wordpress/notices';
/**
* Internal dependencies
*/
import TemplatePartPlaceholder from './placeholder';
import TemplatePartSelectionModal from './selection-modal';
import { TemplatePartAdvancedControls } from './advanced-controls';
import TemplatePartInnerBlocks from './inner-blocks';
import { createTemplatePartId } from './utils/create-template-part-id';
import { useAlternativeBlockPatterns, useAlternativeTemplateParts, useTemplatePartArea } from './utils/hooks';
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
function ReplaceButton({
isEntityAvailable,
area,
templatePartId,
isTemplatePartSelectionOpen,
setIsTemplatePartSelectionOpen
}) {
// This hook fetches patterns, so don't run it unconditionally in the main
// edit function!
const {
templateParts
} = useAlternativeTemplateParts(area, templatePartId);
const hasReplacements = !!templateParts.length;
const canReplace = isEntityAvailable && hasReplacements && (area === 'header' || area === 'footer');
if (!canReplace) {
return null;
}
return /*#__PURE__*/_jsx(MenuItem, {
onClick: () => {
setIsTemplatePartSelectionOpen(true);
},
"aria-expanded": isTemplatePartSelectionOpen,
"aria-haspopup": "dialog",
children: __('Replace')
});
}
function TemplatesList({
area,
clientId,
isEntityAvailable,
onSelect
}) {
// This hook fetches patterns, so don't run it unconditionally in the main
// edit function!
const blockPatterns = useAlternativeBlockPatterns(area, clientId);
const canReplace = isEntityAvailable && !!blockPatterns.length && (area === 'header' || area === 'footer');
if (!canReplace) {
return null;
}
return /*#__PURE__*/_jsx(PanelBody, {
title: __('Design'),
children: /*#__PURE__*/_jsx(BlockPatternsList, {
label: __('Templates'),
blockPatterns: blockPatterns,
onClickPattern: onSelect,
showTitlesAsTooltip: true
})
});
}
export default function TemplatePartEdit({
attributes,
setAttributes,
clientId
}) {
const {
createSuccessNotice
} = useDispatch(noticesStore);
const {
editEntityRecord
} = useDispatch(coreStore);
const currentTheme = useSelect(select => select(coreStore).getCurrentTheme()?.stylesheet, []);
const {
slug,
theme = currentTheme,
tagName,
layout = {}
} = attributes;
const templatePartId = createTemplatePartId(theme, slug);
const hasAlreadyRendered = useHasRecursion(templatePartId);
const [isTemplatePartSelectionOpen, setIsTemplatePartSelectionOpen] = useState(false);
const {
isResolved,
hasInnerBlocks,
isMissing,
area,
onNavigateToEntityRecord,
title,
canUserEdit
} = useSelect(select => {
const {
getEditedEntityRecord,
hasFinishedResolution
} = select(coreStore);
const {
getBlockCount,
getSettings
} = select(blockEditorStore);
const getEntityArgs = ['postType', 'wp_template_part', templatePartId];
const entityRecord = templatePartId ? getEditedEntityRecord(...getEntityArgs) : null;
const _area = entityRecord?.area || attributes.area;
const hasResolvedEntity = templatePartId ? hasFinishedResolution('getEditedEntityRecord', getEntityArgs) : false;
const _canUserEdit = hasResolvedEntity ? select(coreStore).canUser('update', {
kind: 'postType',
name: 'wp_template_part',
id: templatePartId
}) : false;
return {
hasInnerBlocks: getBlockCount(clientId) > 0,
isResolved: hasResolvedEntity,
isMissing: hasResolvedEntity && (!entityRecord || Object.keys(entityRecord).length === 0),
area: _area,
onNavigateToEntityRecord: getSettings().onNavigateToEntityRecord,
title: entityRecord?.title,
canUserEdit: !!_canUserEdit
};
}, [templatePartId, attributes.area, clientId]);
const areaObject = useTemplatePartArea(area);
const blockProps = useBlockProps();
const isPlaceholder = !slug;
const isEntityAvailable = !isPlaceholder && !isMissing && isResolved;
const TagName = tagName || areaObject.tagName;
const onPatternSelect = async pattern => {
await editEntityRecord('postType', 'wp_template_part', templatePartId, {
blocks: pattern.blocks,
content: serialize(pattern.blocks)
});
createSuccessNotice(sprintf(/* translators: %s: template part title. */
__('Template Part "%s" updated.'), title || slug), {
type: 'snackbar'
});
};
// We don't want to render a missing state if we have any inner blocks.
// A new template part is automatically created if we have any inner blocks but no entity.
if (!hasInnerBlocks && (slug && !theme || slug && isMissing)) {
return /*#__PURE__*/_jsx(TagName, {
...blockProps,
children: /*#__PURE__*/_jsx(Warning, {
children: sprintf(/* translators: %s: Template part slug. */
__('Template part has been deleted or is unavailable: %s'), slug)
})
});
}
if (isEntityAvailable && hasAlreadyRendered) {
return /*#__PURE__*/_jsx(TagName, {
...blockProps,
children: /*#__PURE__*/_jsx(Warning, {
children: __('Block cannot be rendered inside itself.')
})
});
}
return /*#__PURE__*/_jsxs(_Fragment, {
children: [/*#__PURE__*/_jsxs(RecursionProvider, {
uniqueId: templatePartId,
children: [isEntityAvailable && onNavigateToEntityRecord && canUserEdit && /*#__PURE__*/_jsx(BlockControls, {
group: "other",
children: /*#__PURE__*/_jsx(ToolbarButton, {
onClick: () => onNavigateToEntityRecord({
postId: templatePartId,
postType: 'wp_template_part'
}),
children: __('Edit')
})
}), canUserEdit && /*#__PURE__*/_jsx(InspectorControls, {
group: "advanced",
children: /*#__PURE__*/_jsx(TemplatePartAdvancedControls, {
tagName: tagName,
setAttributes: setAttributes,
isEntityAvailable: isEntityAvailable,
templatePartId: templatePartId,
defaultWrapper: areaObject.tagName,
hasInnerBlocks: hasInnerBlocks,
clientId: clientId
})
}), isPlaceholder && /*#__PURE__*/_jsx(TagName, {
...blockProps,
children: /*#__PURE__*/_jsx(TemplatePartPlaceholder, {
area: attributes.area,
templatePartId: templatePartId,
clientId: clientId,
setAttributes: setAttributes,
onOpenSelectionModal: () => setIsTemplatePartSelectionOpen(true)
})
}), /*#__PURE__*/_jsx(BlockSettingsMenuControls, {
children: ({
selectedClientIds
}) => {
// Only enable for single selection that matches the current block.
// Ensures menu item doesn't render multiple times.
if (!(selectedClientIds.length === 1 && clientId === selectedClientIds[0])) {
return null;
}
return /*#__PURE__*/_jsx(ReplaceButton, {
isEntityAvailable,
area,
clientId,
templatePartId,
isTemplatePartSelectionOpen,
setIsTemplatePartSelectionOpen
});
}
}), /*#__PURE__*/_jsx(InspectorControls, {
children: /*#__PURE__*/_jsx(TemplatesList, {
area: area,
clientId: clientId,
isEntityAvailable: isEntityAvailable,
onSelect: pattern => onPatternSelect(pattern)
})
}), isEntityAvailable && /*#__PURE__*/_jsx(TemplatePartInnerBlocks, {
tagName: TagName,
blockProps: blockProps,
postId: templatePartId,
hasInnerBlocks: hasInnerBlocks,
layout: layout
}), !isPlaceholder && !isResolved && /*#__PURE__*/_jsx(TagName, {
...blockProps,
children: /*#__PURE__*/_jsx(Spinner, {})
})]
}), isTemplatePartSelectionOpen && /*#__PURE__*/_jsx(Modal, {
overlayClassName: "block-editor-template-part__selection-modal",
title: sprintf(
// Translators: %s as template part area title ("Header", "Footer", etc.).
__('Choose a %s'), areaObject.label.toLowerCase()),
onRequestClose: () => setIsTemplatePartSelectionOpen(false),
isFullScreen: true,
children: /*#__PURE__*/_jsx(TemplatePartSelectionModal, {
templatePartId: templatePartId,
clientId: clientId,
area: area,
setAttributes: setAttributes,
onClose: () => setIsTemplatePartSelectionOpen(false)
})
})]
});
}
//# sourceMappingURL=index.js.map