WordPress如何创建自定义区块
在 wordpress 中有多种方式创建自定义区块,如:使用 Wordpress 插件创建自定义区块,使用 Node.js、NPM 包的形式创建区块。
在本教程中,我们将介绍使用创建区块工具的基本步骤,让你无需使用插件就能创建完全自定义的区块。
Info警告:您需要了解 PHP、JavaScript、HTML 和 CSS,本教程使用的前端框架是 React。
设置 Node.js、NPM、和本地 WordPress 安装
在创建新的 block 之前,我们需要正确的设置和访问权限。
在此之前,需要:
- 本地 WordPress 安装
- 本地安装 Node.js
您可能还想使用 nvm(Node 版本管理器)来安装和管理您的 NodeJs 版本。
有了这些设置,我们将可以使用 npm 命令,使用npx可以直接从云中的托管目录执行 create-block 软件包。
创建区块软件包
在终端进入 WordPress 插件目录/wp-content/plugins,然后使用 npx 运行 create-block 软件包。
npx @wordpress/create-block plugin-name运行该命令后,会自动下载并完成block scaffolding的设置过程,它将注册所有相关元素,生成正确的文件和目录结构,并为整个程序生成可编辑和可用的默认代码。
激活插件
新创建的区块将以插件的形式出现
创建区块完成后,需要你跳转到 WP 的管理页面,进入插件页面,激活新创建的插件,插件名称与在你之前使用 npx 创建区块时的名称相同。
自定义区块代码
你将在插件目录中的**/src**文件夹中编写你的业务代码。
你将在index.js中注册程序中编写主要输出代码。
从 WordPress 的区块 API 中导入 registerBlockType 函数,这个函数用于注册一个新的区块类型,使其能够出现在区块编辑器中。
import { registerBlockType } from "@wordpress/blocks";
import Editor from "./Editor";
import Save from "./Save";
import metadata from "./block.json";
registerBlockType(metadata.name, {
edit: Editor,
save: Save,
});Editor 是用户在后台编辑文章时,区块的显示和交互部分。它负责渲染区块的编辑器界面,比如表单、文本框、图片上传等。
import { __ } from "@wordpress/i18n";
import clsx from "clsx";
import { InspectorControls, useBlockProps } from "@wordpress/block-editor";
import {
PanelBody,
ComboboxControl,
TextControl,
Spinner,
Notice,
} from "@wordpress/components";
import { useSelect } from "@wordpress/data";
import View from "./View";
import useGetPostOptions from "../hooks/useGetPostOptions";
import { debounce } from "../utils";
const modeOptions = [
{ label: "Compact", value: "concise" },
{ label: "Compact + Description", value: "summary" },
{ label: "Insight Series", value: "general" },
];
const sourceOptions = [
{ label: "Insights", value: "insight" },
{ label: "Services", value: "service" },
{ label: "Solutions", value: "solution" },
{ label: "Work", value: "work" },
{ label: "Pages", value: "page" },
{ label: "Downloads", value: "download" },
{ label: "Series", value: "series" },
];
export default function Editor({ attributes, setAttributes }) {
const { postId, ctaText, labelText, mode, source } = attributes;
const blockProps = useBlockProps({ className: "tmo-featured-post-card" });
const parentPostId = useSelect((select) =>
select("core/editor").getCurrentPostId(),
);
const selectedPost = useSelect(
(select) => {
if (!postId) return null;
return select("core").getEntityRecord(
"postType",
(["insight", "series"].includes(source) ? "post" : source) || "post",
postId,
);
},
[postId, source],
);
const { category, options, isLoading, searchPosts, getSourcePosts } =
useGetPostOptions({
source,
parentPostId,
title: selectedPost?.title?.rendered,
});
const handleChangeQuery = (val) => {
if (["insight", "download"].includes(source) && val?.length >= 2) {
searchPosts(val, source);
}
};
const featuredMedia = useSelect(
(select) => {
if (!selectedPost?.featured_media) return null;
return select("core").getMedia(selectedPost.featured_media);
},
[selectedPost?.featured_media],
);
const handleModeChange = async (val) => {
setAttributes({ mode: val, ctaText: "Read more" });
};
const handleSourceChange = (val) => {
getSourcePosts(val);
setAttributes({ source: val, postId: null });
};
const handlePostChange = (val) => {
setAttributes({
postId: Number(val) || 0,
ctaText: source === "series" ? category?.name : "Read more",
});
};
return (
<>
<InspectorControls>
<PanelBody title={__("Content", "tmo")} initialOpen>
<ComboboxControl
label={__("Blog Card Type", "tmo")}
options={modeOptions}
value={mode}
onChange={handleModeChange}
/>
<ComboboxControl
label={__("Pick a Source", "tmo")}
help={__("选择博客数据来源", "tmo")}
value={source}
options={sourceOptions}
onChange={handleSourceChange}
/>
<ComboboxControl
label={__("Pick a post", "tmo")}
help={__("输入2个字以上搜索文章", "tmo")}
value={postId || ""}
onChange={handlePostChange}
onFilterValueChange={debounce(handleChangeQuery)}
options={options}
/>
{isLoading && <Spinner />}
{mode === "general" && (
<TextControl
label={__("Label text", "tmo")}
value={labelText}
onChange={(v) => setAttributes({ labelText: v })}
/>
)}
<TextControl
label={__("CTA text", "tmo")}
value={ctaText}
onChange={(v) => setAttributes({ ctaText: v })}
/>
</PanelBody>
</InspectorControls>
<div
{...blockProps}
className={clsx(blockProps.className, {
related: mode === "related",
})}
>
{!postId && (
<Notice status="info" isDismissible={false}>
{__("请选择一篇文章(右侧面板搜索/选择)", "tmo")}
</Notice>
)}
{selectedPost && (
<View
sourceUrl={featuredMedia?.source_url}
post={selectedPost}
ctaText={ctaText}
mode={mode}
labelText={labelText}
/>
)}
</div>
</>
);
}Save 组件负责输入区块在前端的 HTML,通常它会根据用户在编辑器时的选择,生成并保存对应的内容。
import { useBlockProps } from "@wordpress/block-editor";
export default function Save({ attributes }) {
return <p {...useBlockProps.save()}>{attributes.content}</p>;
}Info这里没有采用这种方式将所选择的内容展示在前端,这里采用的是利用 render 来对所选择的内容进行展示,后面会做详细的说明。
import ArrowRight from "./ArrowRight";
import "../style.scss";
export default function View({
post = {},
ctaText,
mode,
labelText,
sourceUrl,
}) {
return (
<>
<div className="tmo-card-post">
{sourceUrl && <img className="tmo-post-thumb" src={sourceUrl} alt="" />}
<div className="tmo-post-content">
<div
className="tmo-post-title"
dangerouslySetInnerHTML={{
__html: post.title?.rendered || "",
}}
/>
{post.excerpt?.rendered && mode !== "concise" && (
<div
className="tmo-post-excerpt"
dangerouslySetInnerHTML={{
__html: post.excerpt?.rendered || "",
}}
/>
)}
{mode !== "general" && ctaText && (
<div class="tmo-post-series">
<a class="tmo-series-link">
{ctaText}
<span className="tmo-series-link-icon">
<ArrowRight />
</span>
</a>
</div>
)}
</div>
</div>
{mode === "general" && (
<div class="tmo-post-series">
{labelText && <span class="tmo-series-kicker">{labelText}</span>}
{ctaText && (
<a class="tmo-series-link">
{ctaText}
<span className="tmo-series-link-icon">
<ArrowRight />
</span>
</a>
)}
</div>
)}
</>
);
}