社区新闻

如何向编辑器预览下拉菜单添加自定义条目

自 WordPress 6.7 起,插件开发者可以使用自定义菜单项扩展编辑器中的预览下拉菜单。这可以通过 @wordpress/editor 包中的 <a href="https://developer.wordpress.org/block-editor/reference-guides/packages/packages-editor/#pluginpreviewmenuitem">PluginPreviewMenuItem</a> 组件实现。本文将指导构建一个添加“社交卡片预览”选项的小插件——展示文章在 X 上分享时的外观。

预览菜单截图

什么是 PluginPreviewMenuItem?

预览下拉菜单位于编辑器顶部工具栏,允许用户以不同方式预览其内容。在 WordPress 6.7 之前,只有核心功能可以添加项目。通过 PluginPreviewMenuItem 组件,插件可以在该下拉菜单中注册自己的条目。它使用与 PluginMoreMenuItemPluginSidebar 等其他扩展点相同的 Slot/Fill 模式

该组件接受用于按钮行为的 onClick 处理函数或用于链接行为的 href。您还可以传递可选的 icon 属性。放置在 <PluginPreviewMenuItem> 开始和结束标签之间的文本将成为菜单中显示的标签。

社交卡片预览示例

此插件向预览下拉菜单添加一个社交卡片预览项。点击时,它会打开一个模态框,显示当前文章作为 X 卡片时的模拟预览。该模态框直接从编辑器存储中读取文章标题、摘要和特色图片,因此始终反映最新的未保存编辑。您可以查看 GitHub 上的示例插件

模态框中显示的预览截图

插件设置

首先从文件结构开始。插件需要一个主 PHP 文件供 WordPress 识别,一个用于构建工具的 package.json,以及位于 src/ 文件夹中的源文件。

custom-preview/
├── custom-preview.php
├── package.json
└── src/
    ├── index.js
    ├── social-card-preview.js
    └── style.css

PHP 引导程序

主插件文件 custom-preview.phpenqueue_block_editor_assets 钩子上注册脚本和样式表。代码使用 @wordpress/scripts 在构建期间生成的资产文件——它包含依赖项列表和版本哈希,以便 WordPress 正确处理缓存。以下代码放在 PHP 插件头部之后。

function social_card_preview_enqueue_editor_assets() {
	$asset_file = plugin_dir_path( __FILE__ ) . 'build/index.asset.php';

	if ( ! file_exists( $asset_file ) ) {
		return;
	}

	$asset = include $asset_file;

	wp_enqueue_script(
		'social-card-preview-editor',
		plugin_dir_url( __FILE__ ) . 'build/index.js',
		$asset['dependencies'],
		$asset['version'],
		true
	);

	wp_enqueue_style(
		'social-card-preview-editor',
		plugin_dir_url( __FILE__ ) . 'build/style-index.css',
		array(),
		$asset['version']
	);
}
add_action( 'enqueue_block_editor_assets', 'social_card_preview_enqueue_editor_assets' );

这是 WordPress 块编辑器插件开发中的常见模式。enqueue_block_editor_assets 钩子确保脚本和样式仅在编辑器内部加载,而不是在前端。

构建工具

对于 JavaScript 构建,@wordpress/scripts 处理工具。package.json 非常简洁:

{
	"name": "social-card-preview",
	"version": "1.0.0",
	"scripts": {
		"build": "wp-scripts build",
		"start": "wp-scripts start"
	},
	"devDependencies": {
		"@wordpress/scripts": "^30.0.0"
	}
}

运行 npm install 获取依赖项,然后运行 npm run build 进行编译。在开发过程中,npm run start 会监视更改并自动重新构建。

注册预览菜单项

入口点 src/index.js 是注册插件并将菜单项添加到预览下拉菜单的地方。其背后的思路是:渲染一个 PluginPreviewMenuItem,点击时打开一个模态框。

import { __ } from '@wordpress/i18n';
import { registerPlugin } from '@wordpress/plugins';
import { PluginPreviewMenuItem } from '@wordpress/editor';
import { useState } from '@wordpress/element';

import SocialCardPreview from './social-card-preview';
import './style.css';

const SocialCardPreviewMenuItem = () => {
	const [ isOpen, setIsOpen ] = useState( false );

	return (
		<>
			<PluginPreviewMenuItem
				onClick={ () => setIsOpen( true ) }
			>
				{ __( 'Social Card Preview', 'social-card-preview' ) }
			</PluginPreviewMenuItem>
			{ isOpen && (
				<SocialCardPreview
					onClose={ () => setIsOpen( false ) }
				/>
			) }
		</>
	);
};

registerPlugin( 'social-card-preview', {
	render: SocialCardPreviewMenuItem,
} );

这里需要注意几点:

  • 代码使用 <a href="https://developer.wordpress.org/block-editor/reference-guides/packages/packages-plugins/">@wordpress/plugins</a> 中的 registerPlugin 来注册组件。
  • PluginPreviewMenuItem 的工作方式与其他菜单项类似——给它一个 onClick 和一些子元素作为标签。
  • 模态框的可见性通过基本的 useState 切换来管理。

构建社交卡片模态框

SocialCardPreview 组件从编辑器存储中读取数据,并在 Modal 内渲染一个模拟的 X 卡片。

import { __ } from '@wordpress/i18n';
import { Modal } from '@wordpress/components';
import { useSelect } from '@wordpress/data';
import { store as editorStore } from '@wordpress/editor';
import { store as coreStore } from '@wordpress/core-data';

const SocialCardPreview = ( { onClose } ) => {
	const { title, excerpt, imageUrl, siteUrl } = useSelect( ( select ) => {
		const { getEditedPostAttribute } = select( editorStore );
		const featuredMediaId = getEditedPostAttribute( 'featured_media' );

		let featuredImageUrl = '';
		if ( featuredMediaId ) {
			const media = select( coreStore ).getMedia( featuredMediaId );
			featuredImageUrl =
				media?.media_details?.sizes?.large?.source_url ||
				media?.source_url ||
				'';
		}

		return {
			title: getEditedPostAttribute( 'title' ) || '',
			excerpt: getEditedPostAttribute( 'excerpt' ) || '',
			imageUrl: featuredImageUrl,
			siteUrl: select( coreStore ).getSite()?.url || '',
					};
	}, [] );

	const domain = siteUrl ? new URL( siteUrl ).hostname : '';
	const truncatedExcerpt =
		excerpt.length > 200
			? excerpt.substring( 0, 200 ) + '…'
			: excerpt;

	return (
		<Modal title={ __( 'X Preview', 'social-card-preview' ) } 
                       onRequestClose={ onClose } 
                       size="medium">
			{ /* Card preview markup see below */ }
		</Modal>
	);
};

以下是详细说明。useSelect 钩子从两个存储中提取所需数据。从 editor 存储中,它检索文章标题、摘要和特色媒体 ID。从 core-data 存储中,它将媒体 ID 解析为实际的图片 URL,并获取站点 URL 以在卡片上显示域名。

对于特色图片,代码首先尝试 large 尺寸,回退到完整的 source_url。摘要被截断为 200 个字符,因为这大约是 X 显示的长度。

由于 useSelect 是响应式的,预览会在您编辑文章时实时更新。更改标题,卡片会立即更新——无需先保存。

卡片预览标记

卡片标记模仿了 X 渲染链接预览的方式。特色图片位于顶部,后面是包含域名、标题和截断摘要的内容区域。由于条件检查 imageUrl &&,仅当存在特色图片时才渲染图片。

每段文本都使用 <span> 而不是标题或段落,因为这是一个视觉模拟——不是语义文档内容。像 social-card-preview__title 这样的类名遵循 BEM(块元素修饰符)命名约定——social-card-preview 是块,__title__domain__description 是其中的元素。这可以保持样式的作用域,并避免与编辑器样式冲突。

以下文本放在上面代码示例中的 <Modal></Modal> 标签之间。完整的 <a href="https://github.com/bph/dev-blog-custom-preview/blob/main/src/social-card-preview.js">social-card-preview.js</a> 也可以在 GitHub 上找到:

<div className="social-card-preview">
    <div className="social-card-preview__card social-card-preview__card--twitter">
	{ imageUrl && (
		<img className="social-card-preview__image"
			src={ imageUrl }
			alt=""
		/>
	) }
      <div className="social-card-preview__content">
	  <span className="social-card-preview__domain">
		{ domain }
	  </span>
          <span className="social-card-preview__title">
	       { title }
         </span>
         <span className="social-card-preview__description">
	         { truncatedExcerpt }
         </span>
     </div>
  </div>
</div>

卡片样式

CSS 近似模拟了 X 渲染链接卡片的方式,并遵循上述命名决策。

.social-card-preview__card {
	border: 1px solid #dadce0;
	border-radius: 16px;
	overflow: hidden;
}

.social-card-preview__title {
	display: -webkit-box;
	-webkit-line-clamp: 2;
	-webkit-box-orient: vertical;
	overflow: hidden;
}

卡片使用 16px 的边框半径以匹配 X 的圆角样式。-webkit-line-clamp 规则处理标题和描述的文本截断。

使用 WordPress Playground 测试

您不需要完整的 WordPress 安装来测试此功能。Playground CLI 可以在几秒钟内为您提供一个一次性的 WordPress 实例。确保您有 Node.js 20.18 或更高版本,然后从插件目录运行:

npm install
npm run build
npx @wp-playground/cli@latest server --auto-mount --login

Playground 会检测到插件,自动挂载并登录管理员用户。在浏览器中打开 http://127.0.0.1:9400,创建或编辑一篇文章,您应该会在预览下拉菜单中看到“社交卡片预览”。

进一步探索

社交卡片示例只是使用 PluginPreviewMenuItem 的一种方式。该插槽开启了一系列以前仅 WordPress 核心可用的预览体验。以下是一些其他可以考虑的用例:

  • 无障碍检查器——打开一个模态框,扫描文章内容中常见的无障碍问题,例如缺少替代文本或低对比度标题,然后再发布。
  • 阅读水平预览——显示可读性分数和内容的简化版本,对于面向广泛或非专业受众的发布者很有用。
  • 外部预览服务——使用 href 属性而不是 onClick,将作者直接发送到预填了文章 URL 的第三方暂存或 QA 环境。
  • 电子邮件通讯预览——对于将文章同步到电子邮件列表的插件,显示内容在电子邮件客户端中的渲染预览,包括插件应用的任何模板包装。

每当您的插件需要为作者提供在发布前查看或检查其内容的方式时,PluginPreviewMenuItem 就是添加它的正确位置。