如何让你的插件兼容 Gutenberg:第二部分(侧边栏 API)

如果你正试图让你的插件兼容 Gutenberg,根据插件的功能以及它如何向用户交付功能,你可以/应该采取几条路径。

在这个迷你系列的第一部分中,我们探索了 Gutenberg 的区块 API —— 这是大多数插件在兼容 Gutenberg 方面所需要的。这一次,我们将探索 Gutenberg 拼图中的另一块 —— Gutenberg 的侧边栏 API

make plugin compatible with gutenberg sidebar api

为了确保我们的理解一致,简单解释一下:

  • Gutenberg 的区块 API 非常广泛,允许你将几乎所有内容构建为区块,但有时这还不够。
  • 另一方面,侧边栏 API 允许插件注册一个侧边栏,使其功能超越区块。该侧边栏类似于区块检查器。

例如,正如 Yoast SEO 所演示的那样,Gutenberg 侧边栏 API 的实际应用:

Gutenberg Sidebar API

你可以在哪里使用侧边栏 API?

我们最近致力于让我们的插件 —— WP Product Review —— 为 Gutenberg 做好准备。WP Product Review 使用自定义字段将评论数据添加到文章中,由于各种向后兼容性原因,我们无法将其转换为区块。因此,我们使用了侧边栏 API。

当用户通过侧边栏开关指示该文章是一篇评论时,他们将能够从侧边栏选项中配置有关其评论框的所有内容。

WP Product Review 是侧边栏 API 可以发挥作用的示例之一。另一个很棒的例子是 Drop It 插件,它允许用户使用侧边栏从 Unsplash 和 Giphy 插入库存图片。

在这篇文章中,我将引导您完成实现类似功能的过程,并通过侧边栏 API 使您的插件兼容 Gutenberg。

自定义栏目框 默认情况下某种程度上已经准备好支持 Gutenberg,但并非完全如此

让我们从自定义栏目框开始。假设有一个插件,它使用一个简单的自定义栏目框在编辑器屏幕上向用户交付某些功能。

这在经典编辑器中看起来可能像下面这样:

Classic Editor Meta Field

你可以使用以下代码来创建这样一个自定义栏目框 —— 该代码也可以在 Hello Gutenberg 仓库中找到:

/**
 * Register Hello Gutenbert Meta Box
 */
function hello_gutenberg_add_meta_box() {
  add_meta_box( 'hello_gutenberg_meta_box', __( 'Hello Gutenberg Meta Box', 'hello-gutenberg' ), 'hello_gutenberg_metabox_callback', 'post' );
}
add_action( 'add_meta_boxes', 'hello_gutenberg_add_meta_box' );

/**
 * Hello Gutenberg Metabox Callback
 */
function hello_gutenberg_metabox_callback( $post ) {
  $value = get_post_meta( $post->ID, '_hello_gutenberg_field', true );
  ?>
  <label for="hello_gutenberg_field"><?php _e( 'What\'s your name?', 'hello-gutenberg' ) ?></label>
  <input type="text" name="hello_gutenberg_field" value="<?php echo $value ?>" />
  <?php
}
/**
 * Save Hello Gutenberg Metabox
 */
function hello_gutenberg_save_postdata( $post_id ) {
  if ( array_key_exists( 'hello_gutenberg_field', $_POST ) ) {
    update_post_meta( $post_id, '_hello_gutenberg_field', $_POST['hello_gutenberg_field'] );
  }
}
add_action( 'save_post', 'hello_gutenberg_save_postdata' );

所以这里要问的问题是:

我们首先需要让它与 Gutenberg 兼容吗?

在这一点上,我们应该考虑一下什么是与 Gutenberg 兼容。例如,如果你在 Gutenberg 中使用这个元数据框而不做任何修改,它仍然可以工作。

然而,当我们谈论 Gutenberg 兼容性时,我们不仅仅是指插件在 Gutenberg 环境中工作,而是指插件工作得 像 Gutenberg 一样。

“为什么?”——你会问。很简单,那些在 Gutenberg 成为默认编辑器之后开始使用 WordPress 的用户会觉得旧的方式反直觉。此外,他们会期望编辑器中的所有内容都具有更 Gutenberg 风格的样式。

如果你不为用户提供原生般的感觉,他们肯定会寻找能与 Gutenberg 更好配合的替代品。

最后,让我们动手写一些代码吧!

开始使用 Sidebar API

我们将开始在 Gutenberg 中编写我们的侧边栏代码以包含我们的元字段。你可以继续使用 Gutenberg Boilerplate 仓库作为起点。

让你的元数据框为 Gutenberg 做好准备

在开始之前,我们首先需要告诉 Gutenberg 我们不会在 Gutenberg 中使用我们的元数据框。你可以通过将 __back_compat_meta_box 参数传递给 add_meta_box 函数来做到这一点,如下所示:

/**
 * Register Hello Gutenberg Metabox
 */
function hello_gutenberg_add_meta_box() {
  add_meta_box( 'hello_gutenberg_meta_box', __( 'Hello Gutenberg Meta Box', 'hello-gutenberg' ), 'hello_gutenberg_metabox_callback', 'post',  array(
    '__back_compat_meta_box' => false,
  ) );
}
add_action( 'add_meta_boxes', 'hello_gutenberg_add_meta_box' );

Gutenberg 手册有更多关于如何处理将 PHP 元数据框移植到 Gutenberg 的详细信息(点击这里)。

这将确保我们的旧 PHP 元数据框不再出现在 Gutenberg 中。现在,我们还需要通过将元字段添加到 WordPress REST API 来使其为 Gutenberg 做好准备。你可以添加以下代码以向 REST API 注册元字段:

/**
 * Register Hello Gutenberg Meta Field to Rest API
 */
function hello_gutenberg_register_meta() {
  register_meta(
    'post', '_hello_gutenberg_field', array(
      'type'    => 'string',
      'single'  => true,
      'show_in_rest'  => true,
    )
  );
}
add_action( 'init', 'hello_gutenberg_register_meta' );

这会将 _hello_gutenberg_field 添加到 REST API 中的 meta 对象。

此函数仅完成在 REST API 中显示我们值的工作。现在我们还需要添加一种使用 REST API 更新元字段的方法。

以下代码将把我们的自定义路由添加到 WordPress REST API,该路由将是 /hello-gutenberg/v1/update-meta/

/**
 * Register Hello Gutenberg Metabox to Rest API
 */
function hello_gutenberg_api_posts_meta_field() {
  register_rest_route(
    'hello-gutenberg/v1', '/update-meta', array(
      'methods'  => 'POST',
      'callback' => 'hello_gutenberg_update_callback',
      'args'   => array(
        'id' => array(
          'sanitize_callback' => 'absint',
        ),
      ),
    )
  );
}
add_action( 'rest_api_init', 'hello_gutenberg_api_posts_meta_field' );

/**
 * Hello Gutenberg REST API Callback for Gutenberg
 */
function hello_gutenberg_update_callback( $data ) {
  return update_post_meta( $data['id'], $data['key'], $data['value'] );
}

此 REST API 路由将用于从 Gutenberg 侧边栏修改我们的元字段。您可以从这里了解有关 WordPress REST API 以及如何注册自定义路由的更多信息。

同样,如果您想了解什么是 WordPress REST API 以及如何入门,可以阅读我们的博客文章:WordPress REST API:它是什么以及如何开始使用它

在 Gutenberg 中添加侧边栏区块

让我们从 Gutenberg Boilerplate 的侧边栏代码开始:

/**
 * Internal block libraries
 */
const { __ } = wp.i18n;

const { Fragment } = wp.element;

const {
  PluginSidebar,
  PluginSidebarMoreMenuItem,
} = wp.editPost;

const { registerPlugin } = wp.plugins;

const Component = () => (
  <Fragment>
    <PluginSidebarMoreMenuItem
      target="gutenberg-boilerplate-sidebar"
    >
      { __( 'Gutenberg Boilerplate' ) }
    </PluginSidebarMoreMenuItem>
    <PluginSidebar
      name="gutenberg-boilerplate-sidebar"
      title={ __( 'Gutenberg Boilerplate' ) }
    >
      <h2>{ __( 'Hello World!' ) }</h2>
    </PluginSidebar>
  </Fragment>
);

registerPlugin( 'gutenberg-boilerplate', {
  icon: 'admin-site',
  render: Component,
} );

这将在 Gutenberg 编辑器中插入一个非常简单的侧边栏。我们将以此作为起点,在此基础上构建我们的项目。

示例代码来自我们的 Gutenberg Boilerplate 代码仓库,因此,我们需要将所有出现的 “Gutenberg Boilerplate” 替换为 “Hello Gutenberg”。

使用的组件

虽然我们的侧边栏已经导入了许多组件,但我们还需要更多。您可以将代码的顶部部分替换为以下内容:

/**
 * Internal block libraries
 */

const { __ } = wp.i18n;

const {
  PluginSidebar,
  PluginSidebarMoreMenuItem
} = wp.editPost;

const {
  PanelBody,
  TextControl
} = wp.components;

const {
  Component,
  Fragment
} = wp.element;

const { withSelect } = wp.data;

const { registerPlugin } = wp.plugins;

让我们快速了解一下我们导入的所有组件。

  • Fragment 组件用于对子元素列表进行分组,而不向 DOM 添加额外的节点。我们需要在代码中使用 Fragment 组件,因为 JSX 不允许我们有多个父节点。
  1. 最后,我们导入 registerPlugin,它类似于我们在上一篇文章中使用的 registerBlockType。registerPlugin 不是注册区块,而是注册您的插件。

添加控件

到目前为止,我们的侧边栏只是一个 Component 函数,但由于我们将使用 React 的 生命周期方法,我们将把它变成一个 React 组件,如下所示:

class Hello_Gutenberg extends Component {
  render() {
    return (
      <Fragment>
        <PluginSidebarMoreMenuItem
          target="hello-gutenberg-sidebar"
        >
          { __( 'Hello Gutenberg' ) }
        </PluginSidebarMoreMenuItem>
        <PluginSidebar
          name="hello-gutenberg-sidebar"
          title={ __( 'Hello Gutenberg' ) }
        >
          <h2>{ __( 'Hello World!' ) }</h2>
        </PluginSidebar>
      </Fragment>
    )
  }
}

registerPlugin( 'hello-gutenberg', {
  icon: 'admin-site',
  render: Hello_Gutenberg,
} );

这应该使您的侧边栏能够显示纯文本。

现在让我们将字段添加到侧边栏。这应该使我们的组件看起来像这样:

class Hello_Gutenberg extends Component {
  render() {
    return (
      <Fragment>
      <PluginSidebarMoreMenuItem
        target="hello-gutenberg-sidebar"
      >
        { __( 'Hello Gutenberg' ) }
      </PluginSidebarMoreMenuItem>
      <PluginSidebar
        name="hello-gutenberg-sidebar"
        title={ __( 'Hello Gutenberg' ) }
      >
        <PanelBody>
          <TextControl
            label={ __( 'What\'s your name?' ) }
            // value={}
            // onChange={}
          />
        </PanelBody>
      </PluginSidebar>
    </Fragment>
    )
  }
}

这将在侧边栏中添加一个简单的输入字段,此时它实际上不会执行任何操作。

现在我们还剩下两个任务:

  • 显示我们元字段的值。
  • 允许从侧边栏更新我们元字段的值。
Hello Gutenberg

显示元值

为了获取元数据,我们将使用 wp.apiFetch。apiFetch 是一个允许我们发出 REST API 请求的工具库。

我们将在 React 组件的构造函数中使用 apiFetch,如下所示:

class Hello_Gutenberg extends Component {
  constructor() {
    super( ...arguments );

    this.state = {
      key: '_hello_gutenberg_field',
      value: '',
    }

    wp.apiFetch( { path: `/wp/v2/posts/${this.props.postId}`, method: 'GET' } ).then(
      ( data ) => {
        this.setState( {
          value: data.meta._hello_gutenberg_field
        } );
        return data;
      },
      ( err ) => {
        return err;
      }
    );
  }

  render() {
    return (
      <Fragment>
      <PluginSidebarMoreMenuItem
        target="hello-gutenberg-sidebar"
      >
        { __( 'Hello Gutenberg' ) }
      </PluginSidebarMoreMenuItem>
      <PluginSidebar
        name="hello-gutenberg-sidebar"
        title={ __( 'Hello Gutenberg' ) }
      >
        <PanelBody>
          <TextControl
            label={ __( 'What\'s your name?' ) }
            value={ this.state.value }
            // onChange={}
          />
        </PanelBody>
      </PluginSidebar>
    </Fragment>
    )
  }
}

首先,我们定义了一个初始状态,基本上就是我们元数据字段的键和值。之后,我们使用 apiFetch 从当前文章中获取数据。

我们通过 ${this.props.postId} 变量传递当前文章的 ID。稍后我们会回到这一点。

一旦获取了数据,我们就使用 REST API 返回的值来更新状态。

现在,让我们回到 postId 变量。我们目前不知道当前文章的 ID,为此我们使用 withSelect 高阶组件,如下所示:

const HOC = withSelect( ( select ) => {
  const { getCurrentPostId } = select( 'core/editor' );
  return {
    postId: getCurrentPostId(),
  };
} )( Hello_Gutenberg );

registerPlugin( 'hello-gutenberg', {
  icon: 'admin-site',
  render: HOC,
} );

这将把我们当前文章的 ID 作为 postId 变量传递。现在你可以打开一篇旧文章,我们的 Gutenberg 侧边栏将显示你的元数据字段的值。

更新元数据值

现在我们需要允许侧边栏也能更新元数据值。与获取详情类似,我们将使用 wp.apiRequest 工具。

每当我们的状态值发生变化时,我们都会通过 REST API 更新它。为此,我们首先需要为 TextControl 添加一个 onChange 事件,如下所示:

<TextControl
  label={ __( 'What\'s your name?' ) }
  value={ this.state.value }
  onChange={ ( value ) => {
    this.setState( {
      value
    } );
  } }
/>

然后我们将使用 getDerivedStateFromProps 生命周期方法来发送我们的 REST 请求。

你可以在构造函数下方添加以下代码:

static getDerivedStateFromProps( nextProps, state ) {
  wp.apiRequest( { path: `/hello-gutenberg/v1/update-meta?id=${nextProps.postId}`, method: 'POST', data: state } ).then(
    ( data ) => {
      return data;
    },
    ( err ) => {
      return err;
    }
  );
}

每次我们从字段更改元数据时,这都会更新我们的元数据字段。现在你应该看到了这种方法存在的问题。

每次你更改元数据值时都会更新元数据,这种方法有两个问题:

  • 即使你决定不更新文章,你的数据也会被保存。
  • 它会过于频繁地进行 HTTP 调用,这并不理想。

数据应该仅在你保存或发布文章时保存。为此,我们可以利用我们的高阶组件来查明文章何时正在被保存。

你可以用以下代码替换我们的 HOC 函数:

const HOC = withSelect( ( select, { forceIsSaving } ) => {
  const {
    getCurrentPostId,
    isSavingPost,
    isPublishingPost,
    isAutosavingPost,
  } = select( 'core/editor' );
  return {
    postId: getCurrentPostId(),
    isSaving: forceIsSaving || isSavingPost(),
    isAutoSaving: isAutosavingPost(),
    isPublishing: isPublishingPost(),
  };
} )( Hello_Gutenberg );

这将为我们提供一些变量,用来检查我们的文章是否正在被保存或发布。

我们需要将此条件添加到我们的文章请求函数中:

static getDerivedStateFromProps( nextProps, state ) {
  if ( ( nextProps.isPublishing || nextProps.isSaving ) && !nextProps.isAutoSaving ) {
    wp.apiRequest( { path: `/hello-gutenberg/v1/update-meta?id=${nextProps.postId}`, method: 'POST', data: state } ).then(
      ( data ) => {
        return data;
      },
      ( err ) => {
        return err;
      }
    );
  }
}

现在,元数据只会在我们保存、更新或发布文章时更新。

就是这样!你的侧边栏现在已经完成了,而且你刚刚学会了如何让你的插件与 Gutenberg 兼容!

你可以测试你的区块以确保它能正常工作。如果不能正常工作,请在下方的评论区域留言,我会尽力帮助你。

完成 Sidebar API 后下一步做什么?

你可以在 Hello Gutenberg 代码仓库中找到完整的示例。如果你发现任何问题,随时可以 提交 issue

再次强调,这只是 Gutenberg 侧边栏 API 功能的一小部分示例。使用 Gutenberg 及其 API,你可以创造出许多令人惊叹的东西。

延伸阅读:

你正试图用 Gutenberg 实现什么?请告诉我们。

分享你的喜爱

留下评论

您的邮箱地址不会被公开。 必填项已用 * 标注