• 赚钱入口【需求资源】限时招募流量主、渠道主,站长合作;【合作模式】CPS长期分成,一次推广永久有收益。主动打款,不扣量;

向WordPress块编辑器添加自定义欢迎指南

CSS/HTML cps12345 9个月前 (06-16) 108次浏览 0个评论

WordPress块编辑器添加自定义欢迎指南

我正在创建一个WordPress插件,在使用它方面有一些学习过程。我想为用户提供有关如何使用该插件的入门知识,但是我想避免使用户转向该插件网站上的文档,因为这会使他们失去经验。

很棒的是,用户在安装插件后立即开始使用该插件,但是在他们积极使用它的同时可以访问有用的提示。WordPress中没有此类功能的本机功能,但我们可以做一些事情,因为WordPress超级灵活。

所以这就是想法。我们将直接将文档烘烤到插件中,并使其在块编辑器中易于访问。这样,用户可以立即使用该插件,同时直接在工作地点获得常见问题的答案。

我的插件通过几种自定义帖子类型(CPT)运行。实际上,我们要构建的是用户进入这些CPT时会得到的弹出模式。

WordPress块编辑器内置于React中,它利用了可以针对不同情况进行定制和重用的组件。我们正在做的事情就是这种情况(我们称它为<Guide>组件),其行为类似于模式,但由用户可以分页的几个页面组成。

WordPress本身具有一个<Guide> 组件,该组件在首次打开块编辑器时会显示欢迎指南:

屏幕截图显示了WordPress块编辑器顶部的模式,欢迎用户首次使用该编辑器。
当用户首次加载编辑器时,WordPress会显示一个模式,其中包含有关使用块编辑器的说明。

该指南是一个容器,里面装有分解成各个页面的内容。换句话说,这几乎就是我们想要的。这意味着我们不必为此项目重新发明轮子。我们可以将相同的概念重新用于我们自己的插件。

让我们做到这一点。

我们想要实现的目标

在找到解决方案之前,让我们谈谈最终目标。

该设计满足插件的要求,该插件是WordPress的GraphQL服务器。该插件提供了各种CPT,这些CPT可通过自定义模块进行编辑,而自定义模块则可通过template进行定义。总共有两个模块:一个称为“ GraphiQL客户端”,用于输入GraphQL查询,另一个称为“ Persisted query options”,用于自定义执行行为。

由于为GraphQL创建查询并非易事,因此我决定将指南组件添加到该CPT的编辑器屏幕中。在“文档设置”中,可以使用名为“欢迎使用指南”的面板。

屏幕截图显示了WordPress编辑器,并在右栏中打开了“文档设置”面板。 设置中突出显示了欢迎指南选项卡。

打开该面板,用户即可获得链接。该链接将触发模式。

将打开“欢迎使用指南”选项卡的特写屏幕快照,其中显示了一个链接,显示“打开指南:创建持久查询”。

对于模式本身,我决定在第一页上显示有关使用CPT的教程视频,然后在随后的页面上详细介绍CPT中可用的所有选项。

屏幕截图显示了在块编辑器中打开的自定义模式,并包含有关如何使用插件的嵌入式视频。

我相信这种布局是向用户显示文档的有效方法。它不碍事,但仍很方便地接近动作。当然,我们可以使用不同的设计,甚至可以使用不同的组件将模式触发器放置在其他位置,而不用重复使用<Guide>,但这是非常好的。

规划实施

该实现包括以下步骤:

  1. 搭建新脚本以注册自定义侧边栏面板
  2. 仅在“自定义帖子类型”的编辑器上显示自定义侧栏面板
  3. 创建指南
  4. 向指南添加内容

开始吧!

第1步:搭建脚本

从WordPress 5.4开始,我们可以使用一个名为的组件在编辑器的Document设置上<PluginDocumentSettingPanel>添加一个面板,如下所示:

const { registerPlugin } = wp.plugins;
const { PluginDocumentSettingPanel } = wp.editPost;
 
const PluginDocumentSettingPanelDemo = () => (
  <PluginDocumentSettingPanel
    name="custom-panel"
    title="Custom Panel"
    className="custom-panel"
  >
    Custom Panel Contents
  </PluginDocumentSettingPanel>
);
registerPlugin( 'plugin-document-setting-panel-demo', {
  render: PluginDocumentSettingPanelDemo,
  icon: 'palmtree',
} );

如果您有块编辑器的经验,并且已经知道如何执行此代码,则可以跳过。我已经使用块编辑器进行了不到三个月的编码,使用React / npm / webpack对我来说是一个新世界-这个插件是我使用它们的第一个项目!我发现古腾堡仓库中的文档并不总是适合像我这样的初学者,有时文档完全丢失,因此我不得不深入研究源代码以找到答案。

当该组件的文档指示使用上面的代码时,我不知道下一步该怎么做,因为<PluginDocumentSettingPanel>它不是一个块,因此我无法在新的支架上脚手架或在其中添加代码。另外,我们正在使用JSX,这意味着我们需要有一个JavaScript构建步骤来编译代码。

但是,我确实找到了等效的ES5代码

var el = wp.element.createElement;
var __ = wp.i18n.__;
var registerPlugin = wp.plugins.registerPlugin;
var PluginDocumentSettingPanel = wp.editPost.PluginDocumentSettingPanel;function MyDocumentSettingPlugin() {
  return el(
    PluginDocumentSettingPanel,
    {
      className: 'my-document-setting-plugin',
      title: 'My Panel',
    },
    __( 'My Document Setting Panel' )
  );
}registerPlugin( 'my-document-setting-plugin', {
  render: MyDocumentSettingPlugin
} );

ES5代码不需要编译,因此我们可以像WordPress中的其他任何脚本一样加载它。但我不想使用它。我想要ESNext和JSX的完整,现代的体验。

所以我的想法是这样的:我不能使用块状脚手架工具,因为它不是块状的,而且我也不知道如何编译脚本(我当然不会自己设置webpack)。那意味着我被困住了。

可是等等!块和常规脚本之间的唯一区别只是它们在WordPress中的注册方式。这样注册一个块:

wp_register_script($blockScriptName, $blockScriptURL, $dependencies, $version);
register_block_type('my-namespace/my-block', [
  'editor_script' => $blockScriptName,
]);

并注册了一个常规脚本,如下所示:

wp_register_script($scriptName, $scriptURL, $dependencies, $version);
wp_enqueue_script($scriptName);

我们可以使用任何块脚手架工具进行修改,然后注册常规脚本而不是块,这使我们能够访问webpack配置以编译ESNext代码。这些可用的工具是:

我选择使用@ wordpress / create-block软件包,因为它是由开发Gutenberg的团队维护的。

要搭建该块,我们在命令行中执行以下步骤:

npm init @wordpress/block

完成所有提示信息后(包括块的名称,标题和描述),该工具将生成一个单块插件,其PHP文件条目包含类似于以下代码的代码:

/**
 * Registers all block assets so that they can be enqueued through the block editor
 * in the corresponding context.
 *
 * @see https://developer.wordpress.org/block-editor/tutorials/block-tutorial/applying-styles-with-stylesheets/
 */
function my_namespace_my_block_block_init() {
  $dir = dirname( __FILE__ );


  $script_asset_path = "$dir/build/index.asset.php";
  if ( ! file_exists( $script_asset_path ) ) {
    throw new Error(
      'You need to run `npm start` or `npm run build` for the "my-namespace/my-block" block first.'
    );
  }
  $index_js     = 'build/index.js';
  $script_asset = require( $script_asset_path );
  wp_register_script(
    'my-namespace-my-block-block-editor',
    plugins_url( $index_js, __FILE__ ),
    $script_asset['dependencies'],
    $script_asset['version']
  );


  $editor_css = 'editor.css';
  wp_register_style(
    'my-namespace-my-block-block-editor',
    plugins_url( $editor_css, __FILE__ ),
    array(),
    filemtime( "$dir/$editor_css" )
  );


  $style_css = 'style.css';
  wp_register_style(
    'my-namespace-my-block-block',
    plugins_url( $style_css, __FILE__ ),
    array(),
    filemtime( "$dir/$style_css" )
  );register_block_type( 'my-namespace/my-block', array(
    'editor_script' => 'my-namespace-my-block-block-editor',
    'editor_style'  => 'my-namespace-my-block-block-editor',
    'style'         => 'my-namespace-my-block-block',
  ) );
}
add_action( 'init', 'my_namespace_my_block_block_init' );

我们可以将此代码复制到插件中,并进行适当的修改,然后将代码块转换为常规脚本。(请注意,我还将同时删除CSS文件,但可以根据需要保留它们。)

function my_script_init() {
  $dir = dirname( __FILE__ );


  $script_asset_path = "$dir/build/index.asset.php";
  if ( ! file_exists( $script_asset_path ) ) {
    throw new Error(
      'You need to run `npm start` or `npm run build` for the "my-script" script first.'
    );
  }
  $index_js     = 'build/index.js';
  $script_asset = require( $script_asset_path );
  wp_register_script(
    'my-script',
    plugins_url( $index_js, __FILE__ ),
    $script_asset['dependencies'],
    $script_asset['version']
  );
  wp_enqueue_script(
    'my-script'
  );
}
add_action( 'init', 'my_script_init' );

让我们将package.json文件复制过来:

{
  "name": "my-block",
  "version": "0.1.0",
  "description": "This is my block",
  "author": "The WordPress Contributors",
  "license": "GPL-2.0-or-later",
  "main": "build/index.js",
  "scripts": {
    "build": "wp-scripts build",
    "format:js": "wp-scripts format-js",
    "lint:css": "wp-scripts lint-style",
    "lint:js": "wp-scripts lint-js",
    "start": "wp-scripts start",
    "packages-update": "wp-scripts packages-update"
  },
  "devDependencies": {
    "@wordpress/scripts": "^9.1.0"
  }
}

现在,我们可以src/index.js从上面用ESNext代码替换文件的内容,以注册<PluginDocumentSettingPanel>组件。运行npm start(或npm run build用于生产)后,代码将被编译为build/index.js

还有一个要解决的问题:该<PluginDocumentSettingPanel>组件不是静态导入的,而是从中获取的wp.editPost,并且由于wp是WordPress在运行时加载的全局变量,因此不存在此依赖项index.asset.php(在构建过程中会自动生成)。wp-edit-post注册脚本时,我们必须手动向脚本添加依赖项,以确保它在我们之前加载:

$dependencies = array_merge(
  $script_asset['dependencies'],
  [
    'wp-edit-post',
  ]
);
wp_register_script(
  'my-script',
  plugins_url( $index_js, __FILE__ ),
  $dependencies,
  $script_asset['version']
);

现在,脚本设置已准备就绪!

该插件可以通过Gutenberg的不懈开发周期进行更新。运行npm run packages-update以将npm依赖项(因此,将package上定义的webpack配置"@wordpress/scripts")更新为其最新的受支持版本。

此时,您可能想知道我如何知道在"wp-edit-post"脚本之前添加对脚本的依赖关系。好吧,我不得不深入研究古腾堡的源代码。的文档<PluginDocumentSettingPanel>有些不完整,这是在某些地方缺少古腾堡文档的一个很好的例子。

在研究代码和浏览文档时,我发现了一些启发性的东西。例如,有两种方法来编写脚本代码:使用ES5或ESNext语法。ES5不需要构建过程,它可以从运行时环境中引用代码实例,这很可能是通过全局wp变量来引用的。例如,创建图标的代码如下所示:

var moreIcon = wp.element.createElement( 'svg' );

ESNext依靠webpack解决所有依赖关系,这使我们能够导入静态组件。例如,创建图标的代码为:

import { more } from '@wordpress/icons';

这几乎适用于所有地方。但是,<PluginDocumentSettingPanel>组件并非如此,该组件引用ESNext的运行时环境

const { PluginDocumentSettingPanel } = wp.editPost;

这就是为什么我们必须在“ wp-edit-post”脚本中添加依赖项。在那里定义了wp.editPost变量。

如果<PluginDocumentSettingPanel>可以直接导入,则块编辑器将通过“ 依赖关系提取Webpack插件 ”自动处理对“ wp-edit-post”的依赖关系。该插件通过创建一个index.asset.php包含运行时环境脚本的所有依赖关系的文件来建立从静态到运行时的桥梁,该文件是通过用替换"@wordpress/"包名称而获得的"wp-"。因此,"@wordpress/edit-post"程序包成为"wp-edit-post"运行时脚本。这就是我找出添加依赖项的脚本的方式。

步骤2:将所有其他CPT上的自定义侧边栏面板列入黑名单 

该面板将显示特定CPT的文档,因此必须仅将其注册到该CPT。这意味着我们需要将其列入其他任何帖子类型的黑名单。

注册面板的Ryan Welcher(创建<PluginDocumentSettingPanel>组件的人)描述了此过程

const { registerPlugin } = wp.plugins;
const { PluginDocumentSettingPanel } = wp.editPost
const { withSelect } = wp.data;const MyCustomSideBarPanel = ( { postType } ) => {if ( 'post-type-name' !== postType ) {
    return null;
  }return(
    <PluginDocumentSettingPanel
      name="my-custom-panel"
      title="My Custom Panel"
    >
      Hello, World!
    </PluginDocumentSettingPanel>
  );
}const CustomSideBarPanelwithSelect = withSelect( select => {
  return {
    postType: select( 'core/editor' ).getCurrentPostType(),
  };
} )( MyCustomSideBarPanel);




registerPlugin( 'my-custom-panel', { render: CustomSideBarPanelwithSelect } );

他还建议了替代解决方案,使用useSelect代替withSelect

就是说,我对这种解决方案并不完全信服,因为即使不需要,仍必须加载JavaScript文件,这迫使网站遭受了性能损失。注册JavaScript文件比运行JavaScript只是为了禁用JavaScript 更有意义吗?

我创建了一个PHP解决方案。我承认这感觉有些古怪,但效果很好。首先,我们找出哪种帖子类型与正在创建或编辑的对象有关:

function get_editing_post_type(): ?string
{
  if (!is_admin()) {
    return null;
  }


  global $pagenow;
  $typenow = '';
  if ( 'post-new.php' === $pagenow ) {
    if ( isset( $_REQUEST['post_type'] ) && post_type_exists( $_REQUEST['post_type'] ) ) {
      $typenow = $_REQUEST['post_type'];
    };
  } elseif ( 'post.php' === $pagenow ) {
    if ( isset( $_GET['post'] ) && isset( $_POST['post_ID'] ) && (int) $_GET['post'] !== (int) $_POST['post_ID'] ) {
      // Do nothing
    } elseif ( isset( $_GET['post'] ) ) {
      $post_id = (int) $_GET['post'];
    } elseif ( isset( $_POST['post_ID'] ) ) {
      $post_id = (int) $_POST['post_ID'];
    }
    if ( $post_id ) {
      $post = get_post( $post_id );
      $typenow = $post->post_type;
    }
  }
  return $typenow;
}

然后,仅当脚本与CPT匹配时,我们才注册该脚本:

add_action('init', 'maybe_register_script');
function maybe_register_script()
{
  // Check if this is the intended custom post type
  if (get_editing_post_type() != 'my-custom-post-type') {
    return;
  }// Only then register the block
  wp_register_script(...);
  wp_enqueue_script(...);
}

查看这篇文章,深入了解其工作原理。

步骤3:建立自订指南

我为基于WordPress <Guide>组件的插件指南设计了功能。一开始我没有意识到自己会这样做,所以这就是我能够弄清楚的方式。

  1. 搜索源代码以了解它是如何完成的。
  2. Gutenberg的Storybook中浏览所有可用组件的目录。

首先,我从块编辑器模式中复制了内容,然后进行了基本搜索。结果将我指向该文件。从那里,我发现该组件被调用,<Guide>并且可以简单地将其代码复制并粘贴到我的插件中,作为我自己指南的基础。

然后,我寻找了组件的文档。我浏览了@ wordpress / components包(您可能已经猜到了,该包是在其中实现组件的),并找到了该组件的README文件。这给了我实现自己的自定义指南组件所需的所有信息。

我还浏览了古腾堡故事书中所有可用组件的目录(实际上表明这些组件可以在WordPress上下文之外使用)。点击所有这些,我终于发现了<Guide>。故事书提供了几个示例(或故事)的源代码。它是了解如何通过道具自定义组件的便捷资源。

至此,我知道<Guide>将为我的组件打下坚实的基础。不过,这里缺少一个要素:如何触发点击指南。我不得不为此绞尽脑汁!

这是带有侦听器的按钮,可在单击时打开模式:

import { useState } from '@wordpress/element';
import { Button } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import MyGuide from './guide';const MyGuideWithButton = ( props ) => {
  const [ isOpen, setOpen ] = useState( false );
  return (
    <>
      <Button onClick={ () => setOpen( true ) }>
        { __('Open Guide: “Creating Persisted Queries”') }
      </Button>
      { isOpen && (
        <MyGuide 
          { ...props }
          onFinish={ () => setOpen( false ) }
        />
      ) }
    </>
  );
};
export default MyGuideWithButton;

即使块编辑器试图隐藏它,我们也在React中运行。到目前为止,我们一直在处理JSX和组件。但是现在我们需要useState钩子,它是React特有的。

我想说,如果您想掌握WordPress块编辑器,则需要掌握React的知识。没有其他办法了。

步骤4:向指南添加内容

我们快到了!让我们创建一个<Guide>组件,其中包含<GuidePage>每个内容页面的组件。

内容可以使用HTML,包括其他组件等等。在这种情况下,我<GuidePage>仅使用HTML就为CPT 添加了三个实例。第一页包含视频教程,后两页包含详细说明。

import { Guide, GuidePage } from '@wordpress/components';
import { __ } from '@wordpress/i18n';const MyGuide = ( props ) => {
  return (
    <Guide { ...props } >
      <GuidePage>
        <video width="640" height="400" controls>
          <source src="https://d1c2lqfn9an7pb.cloudfront.net/presentations/graphql-api/videos/graphql-api-creating-persisted-query.mov" type="video/mp4" />
          { __('Your browser does not support the video tag.') }
        </video>
        // etc.
      </GuidePage>
      <GuidePage>
        // ...
      </GuidePage>
      <GuidePage>
        // ...
      </GuidePage>
    </Guide>
  )
}
export default MyGuide;
显示图像的gif,显示鼠标光标单击块编辑器的文档设置中的“打开指南”链接,这将打开包含视频的自定义欢迎指南,该视频包含指向模式中其他页面的链接。
嘿,我们现在有自己的指南!

不错!但是有一些问题:

  • 我无法将视频嵌入其中,<Guide>因为单击播放按钮会关闭该指南。我认为那是因为<iframe>落在指南范围之外。我结束了将视频文件上传到S3并使用的情况<video>
  • 指南中的页面过渡不是很顺利。块编辑器的模态看起来不错,因为所有页面的高度都相似,但是这一页面的过渡非常突然。
  • 鼠标悬停效果可以改善。希望古腾堡(Gutenberg)团队需要为自己的目的解决此问题,因为我的CSS不在那了。不是我的技能很差;不是我的技能很差。它们不存在。

但是我可以忍受这些问题。在功能方面,我已经实现了我需要做的指导。

奖励:独立打开文档

对于我们的<Guide>,我们<GuidePage>直接使用HTML 创建了每个组件的内容。但是,如果通过自主组件添加此HTML代码,则可以将其重新用于其他用户交互。

例如,该组件<CacheControlDescription>显示有关HTTP缓存的描述:

const CacheControlDescription = () => {
  return (
    <p>The Cache-Control header will contain the minimum max-age value from all fields/directives involved in the request, or "no-store" if the max-age is 0</p>
  )
}
export default CacheControlDescription;

可以<GuidePage>像以前一样在a内添加此组件,也可以在a内添加<Modal>

import { useState } from '@wordpress/element';
import { Button } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import CacheControlDescription from './cache-control-desc';const CacheControlModalWithButton = ( props ) => {
  const [ isOpen, setOpen ] = useState( false );
  return (
    <>
      <Button 
        icon="editor-help"
        onClick={ () => setOpen( true ) }
      />
      { isOpen && (
        <Modal 
          { ...props }
          onRequestClose={ () => setOpen( false ) }
        >
          <CacheControlDescription />
        </Modal>
      ) }
    </>
  );
};
export default CacheControlModalWithButton;

为了提供良好的用户体验,我们可以提供仅在用户与模块交互时显示文档。为此,我们根据的值显示或隐藏按钮isSelected

import { __ } from '@wordpress/i18n';
import CacheControlModalWithButton from './modal-with-btn';const CacheControlHeader = ( props ) => {
  const { isSelected } = props;
  return (
    <>
      { __('Cache-Control max-age') }
      { isSelected && (
        <CacheControlModalWithButton />
      ) }
    </>
  );
}
export default CacheControlHeader;

最后,将该<CacheControlHeader>组件添加到适当的控件中。

喜欢 (0)

您必须 登录 才能发表评论!