@wordpress/block-library
Version:
Block library for the WordPress editor.
710 lines (709 loc) • 24.1 kB
JavaScript
// packages/block-library/src/latest-posts/edit.js
import clsx from "clsx";
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";
import {
MIN_EXCERPT_LENGTH,
MAX_EXCERPT_LENGTH,
MAX_POSTS_COLUMNS,
DEFAULT_EXCERPT_LENGTH
} from "./constants";
import { useToolsPanelDropdownMenuProps } from "../utils/hooks";
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
var CATEGORIES_LIST_QUERY = {
per_page: -1,
_fields: "id,name",
context: "view"
};
var USERS_LIST_QUERY = {
per_page: -1,
has_published_posts: ["post"],
context: "view"
};
var 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) {
const image = post._embedded?.["wp:featuredmedia"]?.["0"];
return {
url: image?.media_details?.sizes?.[size]?.source_url ?? image?.source_url,
alt: image?.alt_text
};
}
function getCurrentAuthor(post) {
return post._embedded?.author?.[0];
}
function Controls({ attributes, setAttributes, postCount }) {
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) => {
const { getEntityRecords, getUsers } = select(coreStore);
const settings = select(blockEditorStore).getSettings();
return {
defaultImageWidth: settings.imageDimensions?.[featuredImageSizeSlug]?.width ?? 0,
defaultImageHeight: settings.imageDimensions?.[featuredImageSizeSlug]?.height ?? 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?.reduce(
(accumulator, category) => ({
...accumulator,
[category.name]: category
}),
{}
) ?? {};
const selectCategories = (tokens) => {
const hasNoSuggestion = tokens.some(
(token) => typeof token === "string" && !categorySuggestions[token]
);
if (hasNoSuggestion) {
return;
}
const allCategories = tokens.map((token) => {
return typeof token === "string" ? categorySuggestions[token] : token;
});
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,
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,
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: void 0,
featuredImageSizeSlug: "thumbnail",
featuredImageSizeWidth: null,
featuredImageSizeHeight: null,
addLinkToFeaturedImage: false
}),
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,
imageSizeHelp: __(
"Select the size of the source image."
),
onChangeImage: (value) => setAttributes({
featuredImageSizeSlug: value,
featuredImageSizeWidth: void 0,
featuredImageSizeHeight: void 0
})
}
)
}
),
/* @__PURE__ */ jsx(
ToolsPanelItem,
{
hasValue: () => !!featuredImageAlign,
label: __("Image alignment"),
onDeselect: () => setAttributes({
featuredImageAlign: void 0
}),
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 : void 0
}),
children: imageAlignmentOptions.map(
({ value, icon, label }) => {
return /* @__PURE__ */ jsx(
ToggleGroupControlOptionIcon,
{
value,
icon,
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: void 0,
selectedAuthor: void 0,
columns: 3
}),
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: void 0,
selectedAuthor: void 0
}),
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,
onCategoryChange: selectCategories,
selectedCategories: categories,
onAuthorChange: (value) => setAttributes({
selectedAuthor: "" !== value ? Number(value) : void 0
}),
authorList: 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
}
)
}
)
]
}
)
] });
}
function LatestPostsEdit({ attributes, setAttributes }) {
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]
);
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,
setAttributes,
postCount: 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.") })
] });
}
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 */
__(
"\u2026 <a>Read more<span>: %1$s</span></a>"
),
titleTrimmed || __("(no title)")
),
{
a: (
// eslint-disable-next-line jsx-a11y/anchor-has-content
/* @__PURE__ */ 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,
onClick: showRedirectionPreventedNotice,
children: featuredImage
}
) : featuredImage }),
/* @__PURE__ */ jsx(
"a",
{
className: "wp-block-latest-posts__post-title",
href: post.link,
dangerouslySetInnerHTML: !!titleTrimmed ? {
__html: titleTrimmed
} : void 0,
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);
}) })
] });
}
export {
LatestPostsEdit as default
};
//# sourceMappingURL=edit.js.map