📝

Wordpress ブロックエディタ用プラグインを作る③ 〜実装開始〜

2022/01/25に公開

実際にブロックエディタープラグインを作成します。

https://ja.wordpress.org/team/handbook/block-editor/getting-started/create-block/block-code/

前回でやったこと

  • @wordpress/create-block で雛形を作成
  • ブロックのnamespace、説明文、カテゴリ、アイコンを変更

現在のディレクトリ構成

wp-develop/
 +- study/        # 今から実装するブロック
 +- vendor/       # 既存のプラグイン・テーマを置く
 +- .wp-env.json  # 設定ファイル

この章のゴール

タイトル付きの枠が挿入できるブロックを目標とします。
この章の最後に最終的なスクリプトを掲載しています。

編集画面を作る

ファイル保存時に自動コンパイルするように npm run start します。

shell
$ cd wp-develop/study
$ npm run start

初期状態

下記は編集画面のスクリプト edit.js の雛形による初期状態です。
こちらを編集していきます。

edit.js
import { __ } from '@wordpress/i18n';
import { useBlockProps } from '@wordpress/block-editor';
import './editor.scss';

export default function Edit() {
  return (
    <p {...useBlockProps()}>
      {__('Study – hello from the editor!', 'study')}
    </p>
  );
}

タイトル部分、内容部分を追加

タイトル部分はユーザーによる装飾を付けたくないので、リッチテキストではなく TextControl を使います。本文部分は太字などを使いたいので RichText を使います。

変更前

edit.js
import { __ } from '@wordpress/i18n';
import { useBlockProps } from '@wordpress/block-editor';
import './editor.scss';

変更後

edit.js
import { useBlockProps, RichText } from '@wordpress/block-editor';
import { TextControl } from '@wordpress/components';
import './editor.scss';

タイトル付き枠はセクション扱いにしたいので <p> から <section> に変更します。
不要なサンプルメッセージを削除して <TextControl /><RichText /> を追加しました。

変更前

edit.js
<p {...useBlockProps()}>
  {__('Study – hello from the editor!', 'study')}
</p>

変更後

edit.js
<section {...useBlockProps()}>
  <TextControl />
  <RichText />
</section>

コンパイルエラーが無ければ記事の編集画面に行き、 study ブロックを挿入します。
文字のないテキストボックスと、その下には空白が入っています。

入力内容の保存先を定義する

タイトルを title、内容を content として保存できるように block.json の最後に attributes を追加します。

block.json
  〜略〜
  "style": "file:./build/style-index.css",
  "attributes": {
    "title": {
      "type": "string",
      "source": "text",
      "selector": ".title",
      "default": ""
    },
    "content": {
      "type": "array",
      "source": "children",
      "selector": ".content",
      "default": ""
    }
  }
}

titleTextControl を使うので文字列型の string になっています。
contentRichText の中身を配列で受け取るようにしています。
selector はそれぞれのクラス名です。

実は TextControl の指定は下記のような簡単なものでも問題ないそうですが、一応定義できるところは定義するようにしています。

block.json
    "title": {
      "type": "string",
    },

入力内容を保存できるようにする

edit.js を編集して先ほど追加した TextControlRichText に追記します。

edit.js
export default function Edit({attributes, setAttributes}) {
  return (
    <section {...useBlockProps()}>
      <TextControl
        className="title"
        type="text"
        value={attributes.title}
        onChange={(val)=> setAttributes({title:val})}
      />
      <RichText
        className="content"
        tagName="p"
        onChange={(val)=> setAttributes({content:val})}
        value={attributes.content}
      />
    </section>
  );
}

どちらも onChange で変更を受け取り setAttributes() で内容を保存しています。

htmlの class 属性はJavaScriptの class とぶつかってしまうので、JSXでは className と表記します。他にもJSX独自の表記ルールなどありますので、下記のページがわかりやすくまとまってます。

https://www.webdesignleaves.com/pr/wp/wp_block_basic_jsx.html

props について

edit.js
export default function Edit({attributes, setAttributes}) {

この部分は

edit.js
export default function Edit(props) {

と書いて props.attributesprops.setAttributes のように利用することもできます。
他にも classNamename など様々なパラメーター、メソッドが含まれています。

公開画面を作る

ここからは一般ユーザーに公開される画面用スクリプト save.js を編集します。

save.js
import { useBlockProps, RichText } from '@wordpress/block-editor';

export default function save({attributes}) {
  return (
    <section {...useBlockProps.save()}>
      <span className="title">
        {attributes.title}
      </span>
      <RichText.Content
        className="content"
        tagName="p"
        value={attributes.content}
      />
    </section>
  );
}

試しにテキストを入力して、プレビューしてみました。
ちゃんとテキストが出力されています。

記事編集画面

公開画面

解説

save.js
export default function save({attributes}) {

edit.js と同じくReactの props を分割代入しています。

save.js
    <section {...useBlockProps.save()}>

useBlockProps.save() メソッドを使ってブロック全体のクラス名などを出力します。

save.js
      <RichText.Content
        className="content"
        tagName="p"
        value={attributes.content}
      />

RichText.Content を使って本文部分を出力しています。
実は下記のように RichText.Content を使わなくても表示されます。

save.js
      <p>{attributes.content}</p>

ただし公式サイトによると

<strong> や <em> などのテキストフォーマットの HTML タグがエスケープされてサイトのフロントエンド側に表示される場合は恐らく save 関数の問題です。save 関数のコードが <RichText.Content tagName="h2" value={ heading } /> (JSX の場合) のようになっていることを確認してください。単純な出力 <h2>{ heading }</h2> は誤りです。

とあるので、やはり RichText.Content を使ったほうが良さそうです。

https://ja.wordpress.org/team/handbook/block-editor/reference-guides/richtext/#よくある問題と解決策

タイトルを TextControl から RichText に変更する

TextControl だと編集画面と公開画面が違いすぎるので、やはり RichText を使うことにしました。

編集画面を修正

edit.js を編集します。

RichText を使うと改行が可能ですが、タイトルでは改行をさせたくないので削除する関数を追加します。

edit.js
import './editor.scss';

// 改行を削除する関数
const removeBr = val => {
  return val.split('<br>').join('');
}

export default function Edit({attributes, setAttributes}) {

TextControl から RichText に変更します。

変更前

edit.js
      <TextControl
        className="title"
        type="text"
        value={attributes.title}
        onChange={(val)=> setAttributes({title:val})}
      />

変更後

edit.js
      <RichText
        className="title"
        tagName="span"
        allowedFormats={[]}
        placeholder="タイトルを入力"
        onChange={(val)=> setAttributes({title:removeBr(val)})}
        value={attributes.title}
      />

allowedFormats={[]} は文字装飾とリンクの無効化、placeholder="タイトルを入力" はプレースホルダーを指定しています。

onChange={(val)=> setAttributes({title:removeBr(val)})}

先ほど作った関数 removeBr()<br> を削除し、setAttributes() で保存します。

公開画面を修正

save.js を編集します。
content と同じように RichText.Content を使って表示します。

変更前

save.js
      <span className="title">
        {attributes.title}
      </span>

変更後

save.js
      <RichText.Content
        className="title"
        tagName="span"
        value={attributes.title}
      />

CSSで見た目を整える

CSS的に特別なことは何もやっていないので、CSSがわかる人は読み飛ばして大丈夫です。

編集画面、公開画面の両方に反映したいので style.scss を編集します。
後々デザイン変更することを考慮してCSS変数を使っています。

style.scss
.wp-block-my-plugin-study {
  --study--pading: 2rem;
  --study--border-color: #000;
  --study--border-width: 1px;
  --study--border-style: solid;
  --study--border-radius: 0;
  --study--stage-color: var(--global--color-background);
  --study--title-color: #000;
  --study--title-size: calc(var(--global--font-size-base) * .8);
  --study--title-left: calc(var(--study--pading) - (var(--study--title-size) * .5));
  --study--title-top: -.5em;
  --study--title-padding: 0 .5em;

  position: relative;
  padding: var(--study--pading);
  border-style: var(--study--border-style);
  border-width: var(--study--border-width);
  border-color: var(--study--border-color);
  border-radius: var(--study--border-radius);

  .title{
    position: absolute;
    left: var(--study--title-left);
    top: var(--study--title-top);
    padding: var(--study--title-padding);
    background-color: var(--study--stage-color);
    color: var(--study--title-color);
    line-height: 1;
    font-size: var(--study--title-size);
  }
}

公開用にビルド

最後に公開用にcss、jsのミニファイと難読化を行います。

shell
$ npm run build

これでひとまず完成です。

次章ではバリエーションを作っていきます。

https://zenn.dev/dada_caracol/articles/8bc404e89fa214

全スクリプト

ここまでに編集したものを掲載します。

block.json

block.json
{
  "$schema": "https://json.schemastore.org/block.json",
  "apiVersion": 2,
  "name": "my-plugin/study",
  "version": "0.1.8",
  "title": "Study",
  "category": "text",
  "icon": "smiley",
  "description": "説明文",
  "supports": {
    "html": false
  },
  "textdomain": "heart",
  "editorScript": "file:./build/index.js",
  "editorStyle": "file:./build/index.css",
  "style": "file:./build/style-index.css",
  "attributes": {
    "title": {
      "type": "string",
      "source": "text",
      "selector": ".title",
      "default": ""
    },
    "content": {
      "type": "array",
      "source": "children",
      "selector": ".content",
      "default": ""
    }
  }
}

edit.js

edit.js
import { useBlockProps, RichText } from '@wordpress/block-editor';
import './editor.scss';

const removeBr = val => {
  return val.split('<br>').join('');
}

export default function Edit({attributes, setAttributes}) {
  return (
    <section {...useBlockProps()}>
      <RichText
        className="title"
        tagName="span"
        allowedFormats={[]}
        placeholder="タイトルを入力"
        onChange={(val)=> setAttributes({title:removeBr(val)})}
        value={attributes.title}
      />
      <RichText
        className="content"
        tagName="p"
        placeholder="本文を入力"
        onChange={(val)=> setAttributes({content:val})}
        value={attributes.content}
      />
    </section>
  );
}

save.js

save.js
import { __ } from '@wordpress/i18n';
import { useBlockProps, RichText } from '@wordpress/block-editor';

export default function save({attributes}) {
  return (
    <section {...useBlockProps.save()}>
      <RichText.Content
        className="title"
        tagName="span"
        value={attributes.title}
      />
      <RichText.Content
        className="content"
        tagName="p"
        value={attributes.content}
      />
    </section>
  );
}

style.scss

前の項目と同じなので省略します。

editor.scss

編集画面独自のスタイルは無いので空っぽです。

editor.scss
.wp-block-my-plugin-study {
}

Discussion