@wordpress/block-library
Version:
Block library for the WordPress editor.
615 lines (610 loc) • 22.4 kB
JavaScript
/**
* External dependencies
*/
import clsx from 'clsx';
/**
* WordPress dependencies
*/
import { Placeholder, QueryControls, RadioControl, RangeControl, Spinner, ToggleControl, ToolbarGroup, __experimentalToggleGroupControl as ToggleGroupControl, __experimentalToggleGroupControlOptionIcon as ToggleGroupControlOptionIcon, __experimentalToolsPanel as ToolsPanel, __experimentalToolsPanelItem as ToolsPanelItem } from '@wordpress/components';
import { __, _x, sprintf } from '@wordpress/i18n';
import { dateI18n, format, getSettings } from '@wordpress/date';
import { InspectorControls, BlockControls, __experimentalImageSizeControl as ImageSizeControl, useBlockProps, store as blockEditorStore } from '@wordpress/block-editor';
import { useSelect, useDispatch } from '@wordpress/data';
import { pin, list, grid, alignNone, positionLeft, positionCenter, positionRight } from '@wordpress/icons';
import { store as coreStore } from '@wordpress/core-data';
import { store as noticeStore } from '@wordpress/notices';
import { useInstanceId } from '@wordpress/compose';
import { createInterpolateElement } from '@wordpress/element';
/**
* Internal dependencies
*/
import { MIN_EXCERPT_LENGTH, MAX_EXCERPT_LENGTH, MAX_POSTS_COLUMNS, DEFAULT_EXCERPT_LENGTH } from './constants';
import { useToolsPanelDropdownMenuProps } from '../utils/hooks';
/**
* Module Constants
*/
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
const CATEGORIES_LIST_QUERY = {
per_page: -1,
_fields: 'id,name',
context: 'view'
};
const USERS_LIST_QUERY = {
per_page: -1,
has_published_posts: ['post'],
context: 'view'
};
const imageAlignmentOptions = [{
value: 'none',
icon: alignNone,
label: __('None')
}, {
value: 'left',
icon: positionLeft,
label: __('Left')
}, {
value: 'center',
icon: positionCenter,
label: __('Center')
}, {
value: 'right',
icon: positionRight,
label: __('Right')
}];
function getFeaturedImageDetails(post, size) {
var _image$media_details$;
const image = post._embedded?.['wp:featuredmedia']?.['0'];
return {
url: (_image$media_details$ = image?.media_details?.sizes?.[size]?.source_url) !== null && _image$media_details$ !== void 0 ? _image$media_details$ : image?.source_url,
alt: image?.alt_text
};
}
function getCurrentAuthor(post) {
return post._embedded?.author?.[0];
}
function Controls({
attributes,
setAttributes,
postCount
}) {
var _categoriesList$reduc;
const {
postsToShow,
order,
orderBy,
categories,
selectedAuthor,
displayFeaturedImage,
displayPostContentRadio,
displayPostContent,
displayPostDate,
displayAuthor,
postLayout,
columns,
excerptLength,
featuredImageAlign,
featuredImageSizeSlug,
featuredImageSizeWidth,
featuredImageSizeHeight,
addLinkToFeaturedImage
} = attributes;
const {
imageSizes,
defaultImageWidth,
defaultImageHeight,
categoriesList,
authorList
} = useSelect(select => {
var _settings$imageDimens, _settings$imageDimens2;
const {
getEntityRecords,
getUsers
} = select(coreStore);
const settings = select(blockEditorStore).getSettings();
return {
defaultImageWidth: (_settings$imageDimens = settings.imageDimensions?.[featuredImageSizeSlug]?.width) !== null && _settings$imageDimens !== void 0 ? _settings$imageDimens : 0,
defaultImageHeight: (_settings$imageDimens2 = settings.imageDimensions?.[featuredImageSizeSlug]?.height) !== null && _settings$imageDimens2 !== void 0 ? _settings$imageDimens2 : 0,
imageSizes: settings.imageSizes,
categoriesList: getEntityRecords('taxonomy', 'category', CATEGORIES_LIST_QUERY),
authorList: getUsers(USERS_LIST_QUERY)
};
}, [featuredImageSizeSlug]);
const dropdownMenuProps = useToolsPanelDropdownMenuProps();
const imageSizeOptions = imageSizes.filter(({
slug
}) => slug !== 'full').map(({
name,
slug
}) => ({
value: slug,
label: name
}));
const categorySuggestions = (_categoriesList$reduc = categoriesList?.reduce((accumulator, category) => ({
...accumulator,
[category.name]: category
}), {})) !== null && _categoriesList$reduc !== void 0 ? _categoriesList$reduc : {};
const selectCategories = tokens => {
const hasNoSuggestion = tokens.some(token => typeof token === 'string' && !categorySuggestions[token]);
if (hasNoSuggestion) {
return;
}
// Categories that are already will be objects, while new additions will be strings (the name).
// allCategories nomalizes the array so that they are all objects.
const allCategories = tokens.map(token => {
return typeof token === 'string' ? categorySuggestions[token] : token;
});
// We do nothing if the category is not selected
// from suggestions.
if (allCategories.includes(null)) {
return false;
}
setAttributes({
categories: allCategories
});
};
return /*#__PURE__*/_jsxs(_Fragment, {
children: [/*#__PURE__*/_jsxs(ToolsPanel, {
label: __('Post content'),
resetAll: () => setAttributes({
displayPostContent: false,
displayPostContentRadio: 'excerpt',
excerptLength: DEFAULT_EXCERPT_LENGTH
}),
dropdownMenuProps: dropdownMenuProps,
children: [/*#__PURE__*/_jsx(ToolsPanelItem, {
hasValue: () => !!displayPostContent,
label: __('Display post content'),
onDeselect: () => setAttributes({
displayPostContent: false
}),
isShownByDefault: true,
children: /*#__PURE__*/_jsx(ToggleControl, {
__nextHasNoMarginBottom: true,
label: __('Display post content'),
checked: displayPostContent,
onChange: value => setAttributes({
displayPostContent: value
})
})
}), displayPostContent && /*#__PURE__*/_jsx(ToolsPanelItem, {
hasValue: () => displayPostContentRadio !== 'excerpt',
label: __('Content length'),
onDeselect: () => setAttributes({
displayPostContentRadio: 'excerpt'
}),
isShownByDefault: true,
children: /*#__PURE__*/_jsx(RadioControl, {
label: __('Content length'),
selected: displayPostContentRadio,
options: [{
label: __('Excerpt'),
value: 'excerpt'
}, {
label: __('Full post'),
value: 'full_post'
}],
onChange: value => setAttributes({
displayPostContentRadio: value
})
})
}), displayPostContent && displayPostContentRadio === 'excerpt' && /*#__PURE__*/_jsx(ToolsPanelItem, {
hasValue: () => excerptLength !== DEFAULT_EXCERPT_LENGTH,
label: __('Max number of words'),
onDeselect: () => setAttributes({
excerptLength: DEFAULT_EXCERPT_LENGTH
}),
isShownByDefault: true,
children: /*#__PURE__*/_jsx(RangeControl, {
__nextHasNoMarginBottom: true,
__next40pxDefaultSize: true,
label: __('Max number of words'),
value: excerptLength,
onChange: value => setAttributes({
excerptLength: value
}),
min: MIN_EXCERPT_LENGTH,
max: MAX_EXCERPT_LENGTH
})
})]
}), /*#__PURE__*/_jsxs(ToolsPanel, {
label: __('Post meta'),
resetAll: () => setAttributes({
displayAuthor: false,
displayPostDate: false
}),
dropdownMenuProps: dropdownMenuProps,
children: [/*#__PURE__*/_jsx(ToolsPanelItem, {
hasValue: () => !!displayAuthor,
label: __('Display author name'),
onDeselect: () => setAttributes({
displayAuthor: false
}),
isShownByDefault: true,
children: /*#__PURE__*/_jsx(ToggleControl, {
__nextHasNoMarginBottom: true,
label: __('Display author name'),
checked: displayAuthor,
onChange: value => setAttributes({
displayAuthor: value
})
})
}), /*#__PURE__*/_jsx(ToolsPanelItem, {
hasValue: () => !!displayPostDate,
label: __('Display post date'),
onDeselect: () => setAttributes({
displayPostDate: false
}),
isShownByDefault: true,
children: /*#__PURE__*/_jsx(ToggleControl, {
__nextHasNoMarginBottom: true,
label: __('Display post date'),
checked: displayPostDate,
onChange: value => setAttributes({
displayPostDate: value
})
})
})]
}), /*#__PURE__*/_jsxs(ToolsPanel, {
label: __('Featured image'),
resetAll: () => setAttributes({
displayFeaturedImage: false,
featuredImageAlign: undefined,
featuredImageSizeSlug: 'thumbnail',
featuredImageSizeWidth: null,
featuredImageSizeHeight: null,
addLinkToFeaturedImage: false
}),
dropdownMenuProps: dropdownMenuProps,
children: [/*#__PURE__*/_jsx(ToolsPanelItem, {
hasValue: () => !!displayFeaturedImage,
label: __('Display featured image'),
onDeselect: () => setAttributes({
displayFeaturedImage: false
}),
isShownByDefault: true,
children: /*#__PURE__*/_jsx(ToggleControl, {
__nextHasNoMarginBottom: true,
label: __('Display featured image'),
checked: displayFeaturedImage,
onChange: value => setAttributes({
displayFeaturedImage: value
})
})
}), displayFeaturedImage && /*#__PURE__*/_jsxs(_Fragment, {
children: [/*#__PURE__*/_jsx(ToolsPanelItem, {
hasValue: () => featuredImageSizeSlug !== 'thumbnail' || featuredImageSizeWidth !== null || featuredImageSizeHeight !== null,
label: __('Image size'),
onDeselect: () => setAttributes({
featuredImageSizeSlug: 'thumbnail',
featuredImageSizeWidth: null,
featuredImageSizeHeight: null
}),
isShownByDefault: true,
children: /*#__PURE__*/_jsx(ImageSizeControl, {
onChange: value => {
const newAttrs = {};
if (value.hasOwnProperty('width')) {
newAttrs.featuredImageSizeWidth = value.width;
}
if (value.hasOwnProperty('height')) {
newAttrs.featuredImageSizeHeight = value.height;
}
setAttributes(newAttrs);
},
slug: featuredImageSizeSlug,
width: featuredImageSizeWidth,
height: featuredImageSizeHeight,
imageWidth: defaultImageWidth,
imageHeight: defaultImageHeight,
imageSizeOptions: imageSizeOptions,
imageSizeHelp: __('Select the size of the source image.'),
onChangeImage: value => setAttributes({
featuredImageSizeSlug: value,
featuredImageSizeWidth: undefined,
featuredImageSizeHeight: undefined
})
})
}), /*#__PURE__*/_jsx(ToolsPanelItem, {
hasValue: () => !!featuredImageAlign,
label: __('Image alignment'),
onDeselect: () => setAttributes({
featuredImageAlign: undefined
}),
isShownByDefault: true,
children: /*#__PURE__*/_jsx(ToggleGroupControl, {
className: "editor-latest-posts-image-alignment-control",
__nextHasNoMarginBottom: true,
__next40pxDefaultSize: true,
label: __('Image alignment'),
value: featuredImageAlign || 'none',
onChange: value => setAttributes({
featuredImageAlign: value !== 'none' ? value : undefined
}),
children: imageAlignmentOptions.map(({
value,
icon,
label
}) => {
return /*#__PURE__*/_jsx(ToggleGroupControlOptionIcon, {
value: value,
icon: icon,
label: label
}, value);
})
})
}), /*#__PURE__*/_jsx(ToolsPanelItem, {
hasValue: () => !!addLinkToFeaturedImage,
label: __('Add link to featured image'),
onDeselect: () => setAttributes({
addLinkToFeaturedImage: false
}),
isShownByDefault: true,
children: /*#__PURE__*/_jsx(ToggleControl, {
__nextHasNoMarginBottom: true,
label: __('Add link to featured image'),
checked: addLinkToFeaturedImage,
onChange: value => setAttributes({
addLinkToFeaturedImage: value
})
})
})]
})]
}), /*#__PURE__*/_jsxs(ToolsPanel, {
label: __('Sorting and filtering'),
resetAll: () => setAttributes({
order: 'desc',
orderBy: 'date',
postsToShow: 5,
categories: undefined,
selectedAuthor: undefined,
columns: 3
}),
dropdownMenuProps: dropdownMenuProps,
children: [/*#__PURE__*/_jsx(ToolsPanelItem, {
hasValue: () => order !== 'desc' || orderBy !== 'date' || postsToShow !== 5 || categories?.length > 0 || !!selectedAuthor,
label: __('Sort and filter'),
onDeselect: () => setAttributes({
order: 'desc',
orderBy: 'date',
postsToShow: 5,
categories: undefined,
selectedAuthor: undefined
}),
isShownByDefault: true,
children: /*#__PURE__*/_jsx(QueryControls, {
order,
orderBy,
numberOfItems: postsToShow,
onOrderChange: value => setAttributes({
order: value
}),
onOrderByChange: value => setAttributes({
orderBy: value
}),
onNumberOfItemsChange: value => setAttributes({
postsToShow: value
}),
categorySuggestions: categorySuggestions,
onCategoryChange: selectCategories,
selectedCategories: categories,
onAuthorChange: value => setAttributes({
selectedAuthor: '' !== value ? Number(value) : undefined
}),
authorList: authorList !== null && authorList !== void 0 ? authorList : [],
selectedAuthorId: selectedAuthor
})
}), postLayout === 'grid' && /*#__PURE__*/_jsx(ToolsPanelItem, {
hasValue: () => columns !== 3,
label: __('Columns'),
onDeselect: () => setAttributes({
columns: 3
}),
isShownByDefault: true,
children: /*#__PURE__*/_jsx(RangeControl, {
__nextHasNoMarginBottom: true,
__next40pxDefaultSize: true,
label: __('Columns'),
value: columns,
onChange: value => setAttributes({
columns: value
}),
min: 2,
max: !postCount ? MAX_POSTS_COLUMNS : Math.min(MAX_POSTS_COLUMNS, postCount),
required: true
})
})]
})]
});
}
export default function LatestPostsEdit({
attributes,
setAttributes
}) {
var _latestPosts$length;
const instanceId = useInstanceId(LatestPostsEdit);
const {
postsToShow,
order,
orderBy,
categories,
selectedAuthor,
displayFeaturedImage,
displayPostContentRadio,
displayPostContent,
displayPostDate,
displayAuthor,
postLayout,
columns,
excerptLength,
featuredImageAlign,
featuredImageSizeSlug,
featuredImageSizeWidth,
featuredImageSizeHeight,
addLinkToFeaturedImage
} = attributes;
const {
latestPosts
} = useSelect(select => {
const {
getEntityRecords
} = select(coreStore);
const catIds = categories && categories.length > 0 ? categories.map(cat => cat.id) : [];
const latestPostsQuery = Object.fromEntries(Object.entries({
categories: catIds,
author: selectedAuthor,
order,
orderby: orderBy,
per_page: postsToShow,
_embed: 'author,wp:featuredmedia',
ignore_sticky: true
}).filter(([, value]) => typeof value !== 'undefined'));
return {
latestPosts: getEntityRecords('postType', 'post', latestPostsQuery)
};
}, [postsToShow, order, orderBy, categories, selectedAuthor]);
// If a user clicks to a link prevent redirection and show a warning.
const {
createWarningNotice
} = useDispatch(noticeStore);
const showRedirectionPreventedNotice = event => {
event.preventDefault();
createWarningNotice(__('Links are disabled in the editor.'), {
id: `block-library/core/latest-posts/redirection-prevented/${instanceId}`,
type: 'snackbar'
});
};
const hasPosts = !!latestPosts?.length;
const inspectorControls = /*#__PURE__*/_jsx(InspectorControls, {
children: /*#__PURE__*/_jsx(Controls, {
attributes: attributes,
setAttributes: setAttributes,
postCount: (_latestPosts$length = latestPosts?.length) !== null && _latestPosts$length !== void 0 ? _latestPosts$length : 0
})
});
const blockProps = useBlockProps({
className: clsx({
'wp-block-latest-posts__list': true,
'is-grid': postLayout === 'grid',
'has-dates': displayPostDate,
'has-author': displayAuthor,
[`columns-${columns}`]: postLayout === 'grid'
})
});
if (!hasPosts) {
return /*#__PURE__*/_jsxs("div", {
...blockProps,
children: [inspectorControls, /*#__PURE__*/_jsx(Placeholder, {
icon: pin,
label: __('Latest Posts'),
children: !Array.isArray(latestPosts) ? /*#__PURE__*/_jsx(Spinner, {}) : __('No posts found.')
})]
});
}
// Removing posts from display should be instant.
const displayPosts = latestPosts.length > postsToShow ? latestPosts.slice(0, postsToShow) : latestPosts;
const layoutControls = [{
icon: list,
title: _x('List view', 'Latest posts block display setting'),
onClick: () => setAttributes({
postLayout: 'list'
}),
isActive: postLayout === 'list'
}, {
icon: grid,
title: _x('Grid view', 'Latest posts block display setting'),
onClick: () => setAttributes({
postLayout: 'grid'
}),
isActive: postLayout === 'grid'
}];
const dateFormat = getSettings().formats.date;
return /*#__PURE__*/_jsxs(_Fragment, {
children: [inspectorControls, /*#__PURE__*/_jsx(BlockControls, {
children: /*#__PURE__*/_jsx(ToolbarGroup, {
controls: layoutControls
})
}), /*#__PURE__*/_jsx("ul", {
...blockProps,
children: displayPosts.map(post => {
const titleTrimmed = post.title.rendered.trim();
let excerpt = post.excerpt.rendered;
const currentAuthor = getCurrentAuthor(post);
const excerptElement = document.createElement('div');
excerptElement.innerHTML = excerpt;
excerpt = excerptElement.textContent || excerptElement.innerText || '';
const {
url: imageSourceUrl,
alt: featuredImageAlt
} = getFeaturedImageDetails(post, featuredImageSizeSlug);
const imageClasses = clsx({
'wp-block-latest-posts__featured-image': true,
[`align${featuredImageAlign}`]: !!featuredImageAlign
});
const renderFeaturedImage = displayFeaturedImage && imageSourceUrl;
const featuredImage = renderFeaturedImage && /*#__PURE__*/_jsx("img", {
src: imageSourceUrl,
alt: featuredImageAlt,
style: {
maxWidth: featuredImageSizeWidth,
maxHeight: featuredImageSizeHeight
}
});
const needsReadMore = excerptLength < excerpt.trim().split(' ').length && post.excerpt.raw === '';
const postExcerpt = needsReadMore ? /*#__PURE__*/_jsxs(_Fragment, {
children: [excerpt.trim().split(' ', excerptLength).join(' '), createInterpolateElement(sprintf(/* translators: 1: Hidden accessibility text: Post title */
__('… <a>Read more<span>: %1$s</span></a>'), titleTrimmed || __('(no title)')), {
a:
/*#__PURE__*/
// eslint-disable-next-line jsx-a11y/anchor-has-content
_jsx("a", {
className: "wp-block-latest-posts__read-more",
href: post.link,
rel: "noopener noreferrer",
onClick: showRedirectionPreventedNotice
}),
span: /*#__PURE__*/_jsx("span", {
className: "screen-reader-text"
})
})]
}) : excerpt;
return /*#__PURE__*/_jsxs("li", {
children: [renderFeaturedImage && /*#__PURE__*/_jsx("div", {
className: imageClasses,
children: addLinkToFeaturedImage ? /*#__PURE__*/_jsx("a", {
href: post.link,
rel: "noreferrer noopener",
onClick: showRedirectionPreventedNotice,
children: featuredImage
}) : featuredImage
}), /*#__PURE__*/_jsx("a", {
className: "wp-block-latest-posts__post-title",
href: post.link,
rel: "noreferrer noopener",
dangerouslySetInnerHTML: !!titleTrimmed ? {
__html: titleTrimmed
} : undefined,
onClick: showRedirectionPreventedNotice,
children: !titleTrimmed ? __('(no title)') : null
}), displayAuthor && currentAuthor && /*#__PURE__*/_jsx("div", {
className: "wp-block-latest-posts__post-author",
children: sprintf(/* translators: byline. %s: author. */
__('by %s'), currentAuthor.name)
}), displayPostDate && post.date_gmt && /*#__PURE__*/_jsx("time", {
dateTime: format('c', post.date_gmt),
className: "wp-block-latest-posts__post-date",
children: dateI18n(dateFormat, post.date_gmt)
}), displayPostContent && displayPostContentRadio === 'excerpt' && /*#__PURE__*/_jsx("div", {
className: "wp-block-latest-posts__post-excerpt",
children: postExcerpt
}), displayPostContent && displayPostContentRadio === 'full_post' && /*#__PURE__*/_jsx("div", {
className: "wp-block-latest-posts__post-full-content",
dangerouslySetInnerHTML: {
__html: post.content.raw.trim()
}
})]
}, post.id);
})
})]
});
}
//# sourceMappingURL=edit.js.map