# 插件侧边栏

## 概述

如何为您的插件添加侧边栏。侧边栏是编辑器最右侧的区域。您的插件可以在 InspectorControls（齿轮图标）旁添加一个额外的图标，该图标可展开显示。

![侧边栏示例](https://raw.githubusercontent.com/WordPress/gutenberg/HEAD/docs/assets/sidebar-up-and-running.png)

_注意：本教程介绍自定义侧边栏，如需在侧边栏添加控件，请参阅[区块工具栏与设置侧边栏](/docs/getting-started/fundamentals/block-in-the-editor)_

## 开始之前

本教程假设您已具备插件设置基础，并准备添加 PHP 和 JavaScript 代码。如需了解 WordPress 插件基础及如何通过 JavaScript 扩展区块编辑器，请参阅 [JavaScript 入门指南](/docs/getting-started/fundamentals/javascript-in-the-block-editor)。

## 逐步指南

### 步骤 1：创建并运行侧边栏

第一步是告诉编辑器有一个新的插件，该插件将拥有自己的侧边栏。分别使用 `@wordpress/plugins`、`@wordpress/editor` 和 `react` 包提供的 [registerPlugin](/packages/plugins/README)、[PluginSidebar](/packages/editor/README#pluginsidebar) 和 [createElement](/packages/element/README) 工具。

将以下代码添加到名为 `plugin-sidebar.js` 的 JavaScript 文件中，并将其保存在你的插件目录中：

```js
( function ( wp, React ) {
	var el = React.createElement;
	var registerPlugin = wp.plugins.registerPlugin;
	var PluginSidebar = wp.editor.PluginSidebar;

	registerPlugin( 'my-plugin-sidebar', {
		render: function () {
			return el(
				PluginSidebar,
				{
					name: 'my-plugin-sidebar',
					icon: 'admin-post',
					title: 'My plugin sidebar',
				},
				'Meta field'
			);
		},
	} );
} )( window.wp, window.React );
```

为了使此代码正常工作，这些工具需要在浏览器中可用，因此你必须将 `wp-plugins`、`wp-editor` 和 `react` 指定为脚本的依赖项。

以下是注册脚本并指定依赖项的 PHP 代码：

```php
<?php

/*
Plugin Name: Sidebar plugin
*/

function sidebar_plugin_register() {
	wp_register_script(
		'plugin-sidebar-js',
		plugins_url( 'plugin-sidebar.js', __FILE__ ),
		array( 'wp-plugins', 'wp-editor', 'react' )
	);
}
add_action( 'init', 'sidebar_plugin_register' );

function sidebar_plugin_script_enqueue() {
	wp_enqueue_script( 'plugin-sidebar-js' );
}
add_action( 'enqueue_block_editor_assets', 'sidebar_plugin_script_enqueue' );
```

安装并激活此插件后，编辑器右上角会出现一个类似图钉的新图标。点击它，插件的侧边栏将会打开：

![侧边栏已启动并运行](https://raw.githubusercontent.com/WordPress/gutenberg/HEAD/docs/assets/sidebar-up-and-running.png)

### 步骤 2：调整侧边栏样式并添加控件

侧边栏启动并运行后，下一步是填充必要的组件和基本样式。

为了可视化和编辑元字段值，您将使用一个输入组件。`@wordpress/components` 包包含许多可供您重用的组件，特别是 [TextControl](/packages/components/src/text-control/README) 组件，旨在创建输入字段：

```js
( function ( wp ) {
	var el = React.createElement;
	var registerPlugin = wp.plugins.registerPlugin;
	var PluginSidebar = wp.editor.PluginSidebar;
	var TextControl = wp.components.TextControl;

	registerPlugin( 'my-plugin-sidebar', {
		render: function () {
			return el(
				PluginSidebar,
				{
					name: 'my-plugin-sidebar',
					icon: 'admin-post',
					title: 'My plugin sidebar',
				},
				el(
					'div',
					{ className: 'plugin-sidebar-content' },
					el( TextControl, {
						label: 'Meta Block Field',
						value: 'Initial value',
						onChange: function ( content ) {
							console.log( 'content changed to ', content );
						},
					} )
				)
			);
		},
	} );
} )( window.wp );
```

使用此新代码更新 `plugin-sidebar.js`。请注意，它使用了来自 `@wordpress/components` 包的一个名为 `wp.components` 的新工具。请确保在 PHP 文件中的 `wp_register_script` 函数的依赖项中添加 `wp-components`。

此代码引入了：

-   一个 CSS 类 `plugin-sidebar-content` 到 `div` 元素，以便应用样式，
-   一个 `TextControl` 组件，而不是纯文本 `'Meta field'`。

有了可用的新 CSS 类，您可以添加一些样式。在您的插件目录中创建一个名为 `plugin-sidebar.css` 的新文件，并添加以下内容以提供一些内边距：

```css
.plugin-sidebar-content {
	padding: 16px;
}
```

注册该脚本并将其与 JavaScript 文件一起通过 `enqueue_block_editor_assets` 加载。

完成这些更改后，PHP 代码将如下所示：

```php
<?php

/*
Plugin Name: Sidebar example
*/

function sidebar_plugin_register() {
	wp_register_script(
		'plugin-sidebar-js',
		plugins_url( 'plugin-sidebar.js', __FILE__ ),
		array(
			'react',
			'wp-plugins',
			'wp-editor',
			'wp-components'
		)
	);
	wp_register_style(
		'plugin-sidebar-css',
		plugins_url( 'plugin-sidebar.css', __FILE__ )
	);
}
add_action( 'init', 'sidebar_plugin_register' );

function sidebar_plugin_script_enqueue() {
	wp_enqueue_script( 'plugin-sidebar-js' );
	wp_enqueue_style( 'plugin-sidebar-css' );
}
add_action( 'enqueue_block_editor_assets', 'sidebar_plugin_script_enqueue' );
```

重新加载编辑器并打开侧边栏：

![带样式和控件的侧边栏](https://raw.githubusercontent.com/WordPress/gutenberg/HEAD/docs/assets/sidebar-style-and-controls.png)

此代码尚不允许用户存储或检索数据，因此接下来的步骤将重点介绍如何将其连接到元块字段。

### 步骤 3：注册元字段

要使用 `post_meta` 表中的字段，请使用 [register_post_meta](https://developer.wordpress.org/reference/functions/register_post_meta/) 函数创建一个名为 `sidebar_plugin_meta_block_field` 的新字段。

注意：此字段需要能被 REST API 访问，因为区块编辑器是通过这种方式访问数据的。

将以下 PHP 代码添加到你的插件 `init` 回调函数中：

```php
register_post_meta( 'post', 'sidebar_plugin_meta_block_field', array(
	'show_in_rest' => true,
	'single' => true,
	'type' => 'string',
) );
```

为了确认，可以查询区块编辑器存储以查看字段是否已加载。实现后，重新加载编辑器页面并打开浏览器的开发者控制台。在控制台中使用以下 JavaScript 代码片段进行确认：

```js
wp.data.select( 'core/editor' ).getCurrentPost().meta;
```

该函数将返回一个包含你注册的元字段的对象。

如果代码返回 `undefined`，请确保你的文章类型支持 `custom-fields`。可以在[注册文章类型](https://developer.wordpress.org/reference/functions/register_post_type/#supports)时或使用 [add_post_type_support 函数](https://developer.wordpress.org/reference/functions/add_post_type_support/)时进行设置。

### 步骤 4：初始化输入控件

现在编辑器存储中已有该字段，可以将其显示到用户界面。我们将输入控件提取到一个函数中，以便在添加功能时保持代码整洁。

```js
( function ( wp ) {
	var el = React.createElement;
	var registerPlugin = wp.plugins.registerPlugin;
	var PluginSidebar = wp.editor.PluginSidebar;
	var TextControl = wp.components.TextControl;

	var MetaBlockField = function () {
		return el( TextControl, {
			label: 'Meta Block Field',
			value: 'Initial value',
			onChange: function ( content ) {
				console.log( 'content changed to ', content );
			},
		} );
	};

	registerPlugin( 'my-plugin-sidebar', {
		render: function () {
			return el(
				PluginSidebar,
				{
					name: 'my-plugin-sidebar',
					icon: 'admin-post',
					title: 'My plugin sidebar',
				},
				el(
					'div',
					{ className: 'plugin-sidebar-content' },
					el( MetaBlockField )
				)
			);
		},
	} );
} )( window.wp );
```

我们希望用 `sidebar_plugin_meta_block_field` 的值来初始化 `MetaBlockField` 组件，并在该值发生变化时保持更新。

`useSelect` 函数用于在组件加载时获取数据，并在数据变化时更新。以下是使用 `useSelect` 的代码更新：

```js
( function ( wp ) {
	var el = React.createElement;
	var registerPlugin = wp.plugins.registerPlugin;
	var PluginSidebar = wp.editor.PluginSidebar;
	var Text = wp.components.TextControl;
	var useSelect = wp.data.useSelect;

	var MetaBlockField = function () {
		var metaFieldValue = useSelect( function ( select ) {
			return select( 'core/editor' ).getEditedPostAttribute(
				'meta'
			)[ 'sidebar_plugin_meta_block_field' ];
		}, [] );

		return el( Text, {
			label: 'Meta Block Field',
			value: metaFieldValue,
			onChange: function ( content ) {
				console.log( 'content has changed to ', content );
			},
		} );
	};

	registerPlugin( 'my-plugin-sidebar', {
		render: function () {
			return el(
				PluginSidebar,
				{
					name: 'my-plugin-sidebar',
					icon: 'admin-post',
					title: 'My plugin sidebar',
				},
				el(
					'div',
					{ className: 'plugin-sidebar-content' },
					el( MetaBlockField )
				)
			);
		},
	} );
} )( window.wp );
```

`wp.data.useSelect` 函数来自 `@wordpress/data` 包，因此需要在 PHP 的 `wp_register_script` 函数中将 `wp-data` 添加为依赖项。

注意：`getEditedPostAttribute` 调用用于检索文章的最新值，包括用户尚未保存的编辑内容。

通过更新代码、重新加载并打开侧边栏来确认其是否正常工作。输入框的内容不再是 `Initial value`，而是一个空字符串。用户尚无法输入值，但您可以检查如果存储中的值发生变化，组件是否会更新。打开浏览器的控制台，执行：

```js
wp.data
	.dispatch( 'core/editor' )
	.editPost( { meta: { sidebar_plugin_meta_block_field: 'hello world!' } } );
```

您可以在输入组件中观察到内容的变化。

### 步骤 5：在输入内容变化时更新元字段

最后一步是在输入内容变化时更新元字段。
`useDispatch` 函数接收一个存储名称作为其唯一参数，并返回可用于更新存储的方法，在本例中我们将使用 `editPost`

```js
( function ( wp ) {
	var el = React.createElement;
	var registerPlugin = wp.plugins.registerPlugin;
	var PluginSidebar = wp.editor.PluginSidebar;
	var TextControl = wp.components.TextControl;
	var useSelect = wp.data.useSelect;
	var useDispatch = wp.data.useDispatch;

	var MetaBlockField = function ( props ) {
		var metaFieldValue = useSelect( function ( select ) {
			return select( 'core/editor' ).getEditedPostAttribute(
				'meta'
			)[ 'sidebar_plugin_meta_block_field' ];
		}, [] );

		var editPost = useDispatch( 'core/editor' ).editPost;

		return el( TextControl, {
			label: 'Meta Block Field',
			value: metaFieldValue,
			onChange: function ( content ) {
				editPost( {
					meta: { sidebar_plugin_meta_block_field: content },
				} );
			},
		} );
	};

	registerPlugin( 'my-plugin-sidebar', {
		render: function () {
			return el(
				PluginSidebar,
				{
					name: 'my-plugin-sidebar',
					icon: 'admin-post',
					title: 'My plugin sidebar',
				},
				el(
					'div',
					{ className: 'plugin-sidebar-content' },
					el( MetaBlockField )
				)
			);
		},
	} );
} )( window.wp );
```

更新后，当用户输入时，输入控件会调用 `editPost` 并在每次按键时更新编辑器存储。

更新 JavaScript，加载侧边栏，并在输入字段中键入内容。您可以通过在输入控件中输入内容并在浏览器的开发控制台中执行以下 JavaScript 代码片段来确认其已保存：

```js
wp.data.select( 'core/editor' ).getEditedPostAttribute( 'meta' )[
	'sidebar_plugin_meta_block_field'
];
```

显示的消息应该是您在输入框中键入的内容。

保存文章时，您可以通过在保存后重新加载并确认输入控件已初始化为您最后键入的值，来确认其已正确存储在数据库中。

## 额外资源

有关使用 [@wordpress/data 包](/packages/data/README) 的文档。

本指南中使用的函数：

-   [useSelect](/packages/data/README#useselect)
-   [getEditedPostAttribute](/docs/reference-guides/data/data-core-editor#geteditedpostattribute)
-   [useDispatch](/packages/data/README#usedispatch)

## 结论

现在您已拥有一个可用于更新 `post_meta` 内容的自定义侧边栏。

完整示例已提供，请从 [block-development-examples](https://github.com/WordPress/block-development-examples) 仓库下载 [plugin-sidebar 示例](https://github.com/WordPress/block-development-examples/tree/trunk/plugins/plugin-sidebar-9ee4a6)。

### 注意

如果您已在编辑器“偏好设置”的“面板”页面（通过右上角三个点）启用了自定义字段，那么一个与 TextControl 同名的字段（本例中为 `sidebar_plugin_meta_block_field`）也会出现在编辑器窗口底部的自定义字段面板中。这两个字段访问的是相同的元属性。

![文本控件与自定义字段](https://raw.githubusercontent.com/WordPress/gutenberg/HEAD/docs/assets/plugin-sidebar-text-control-custom-field.png)

保存文章时，TextControl 中的值会先保存，自定义字段中的值会后保存，因此最终持久化到数据库的是后者。所以，即使您更改了 TextControl 中的值，最终保存的仍是自定义字段中的值。

如果未启用自定义字段，则不存在此问题。

如果您需要同时启用自定义字段并在侧边栏中使用文章元数据，有两种可能的解决方案：

1. 在元字段名称前添加下划线，因此上例中的名称应为 `_sidebar_plugin_meta_block_field`。这表示该文章元数据应被视为私有，不会在文章的自定义字段部分显示。采用此方案时，保存文章会生成错误，除非您在传递给 `register_post_meta` 的 `args` 数组中添加 `auth_callback` 属性，并提供一个最终返回 `true` 的函数。更多信息请参阅 [post_meta](https://developer.wordpress.org/reference/functions/register_meta/#parameters) 页面的 `args` 文档。
2. 在 TextControl 的 `onChange` 函数中，定位值字段的文本区域，并将其值设置为与 TextControl 元字段的值相同。这样两个位置的值将保持一致，从而确保即使更改了 TextControl 中的值，它仍会被保存到数据库。

```js
return el( TextControl, {
  label: 'Meta Block Field',
  value: metaFieldValue,
  onChange: function ( content ) {
    editPost( {
      meta: { sidebar_plugin_meta_block_field: content }
    })
    document.querySelector( {the-value-textarea} ).innerHTML = content;
  },
} );
```