シンプルなブロックを簡単に作る

ワイ、ブロック作成初心者。
WordPressでpにclassが当たってるだけのリッチテキスト入力化のブロックをサクッと作りたいなって。

そんなの段落ブロックに追加CSSクラスを設定すればいいだろうって?
わかるよ。でもその操作が難しいユーザーもいるのさ。

作るもの

リード文ブロック。

pタグにcontent-leadというクラスが当たっているだけのブロック。リッチテキストを入力できる。

クラスはデフォルトでブロック全体にwp-{ブロック名}というクラスが当たるが、既存のCSSとの兼ね合いでcontent-leadも当たるようにする。

方針

公式のチュートリアルだとJSXをコンパイルできる環境を整えたプラグインの形で作りはじめるが、今回はテーマにJavaScriptで実装していく。

jsファイルを一個書いて、functions.phpでwp_enquque_scriptするだけなので流れがシンプルなので分かりやすい。
実はあまり推奨されたやり方ではない(wp_enquque_scriptじゃなくてregister_block_typeを使った方がいい)のだが、ひとまずブロック作成に慣れる意味で。

ブロックエディターハンドブックを起点にリッチテキストで入力できるシンプルなブロック を作成。

ついでに段落ブロックと相互変換したいので他のブロックへの変換も実装。

コード

functions.php

特に難しいことはない管理画面でのスクリプトの読み込み。

add_action('admin_enqueue_scripts', function ($hook_suffix) {
  $lead_block_js_uri = get_template_directory_uri() . "/js/lead-block.js";
  $lead_block_js_path = get_template_directory() . "/js/lead-block.js";
  wp_enqueue_script("lead-block-js", $lead_block_js_uri, [ 'wp-element', 'wp-editor', ], filemtime($lead_block_js_path));
});

依存するスクリプトとしてwp-elementとwp-editorを読み込んでいる。

filemtimeしてるのはキャッシュ対策。

js/lead-block.js

registerBlockTypeでブロックを実装。

第一引数はブロックの名前で第二引数はブロック構成。

(function (blocks, blockEditor, element) {
  const el = element.createElement;
  const RichText = blockEditor.RichText;
  const useBlockProps = blockEditor.useBlockProps;

  blocks.registerBlockType("brassworks/lead", {
    title: "リード文",
    icon: "text-page",
    category: "text",
    keywords: ["lead", "リード文"],

    transforms: {
      from: [
        {
          type: "block",
          blocks: ["core/paragraph"],
          transform: ({ content }) => {
            return blocks.createBlock("brassworks/lead", {
              content,
            });
          },
        },
      ],
      to: [
        {
          type: "block",
          blocks: ["core/paragraph"],
          transform: ({ content }) => {
            return blocks.createBlock("core/paragraph", {
              content,
            });
          },
        },
      ],
    },

    attributes: {
      content: {
        type: "string",
        source: "html",
        selector: "p",
      },
    },

    example: {
      attributes: {
        content: "リード文です",
      },
    },

    edit: function (props) {
      function onChangeContent(newContent) {
        props.setAttributes({ content: newContent });
      }

      blockProps = useBlockProps({
        key: "richtext",
        tagName: "p",
        className: "content-lead",
        onChange: onChangeContent,
        value: props.attributes.content,
        placeholder: "リード文を入力してください。",
      });

      return el(RichText, blockProps);
    },

    save: function (props) {
      const blockProps = useBlockProps.save({
        tagName: "p",
        className: "content-lead",
        value: props.attributes.content,
      });

      return el(RichText.Content, blockProps);
    },
  });
})(window.wp.blocks, window.wp.blockEditor, window.wp.element);

これで終わり。じゃアレなのでブロック構成の個別の項目について解説していく。

ブロック構成

editとsaveが肝。あとattributesも

リファレンス的内容は公式ドキュメントにゆずるとして、以下は実装例的な内容。

title

title: "リード文",

管理画面に表示されるブロック名。

icon

icon: "text-page",

管理画面に表示されるブロックアイコン。

ダッシュアイコンを見て適当にそれっぽくて紛らわしくないやつを選ぶ。こだわるならカスタムSVGをを自分で用意することもできる。

category

category: "text",

ブロック選択時のカテゴリー分け。コアで提供されるtextを選択。

keywords

keywords: ["lead", "リード文"],

ブロックを検索するときのためのキーワード。

transforms

transforms: {
  from: [
    {
      type: "block",
      blocks: ["core/paragraph"],
      transform: ({ content }) => {
        return blocks.createBlock("brassworks/lead", {
          content,
        });
      },
    },
  ],
  to: [
    {
      type: "block",
      blocks: ["core/paragraph"],
      transform: ({ content }) => {
        return blocks.createBlock("core/paragraph", {
          content,
        });
      },
    },
  ],
},

単純なブロックなので段落との相互変換はできた方がよかろうということで。

fromの方のblocksにはcore/headingを追加したり複数のブロックを指定できる。

toの方は多分複数指定はできない(?)

fromにEnterキーを押した時の挙動とかも書ける。

attributes

attributes: {
  content: {
    type: "string",
    source: "html",
    selector: "p",
  },
},

ブロックの属性。編集して保存したり表示したりできる。属性の個数は任意。

リッチテキストを使うのでtypeはstringでsourceはhtml。

selectorはeditやsaveが入力値の入ったHTMLエレメントを探せるように指定する。
このケースはpが一個しかないのでpだけでOKだが、もう少し複雑なブロックでpタグを複数含むような場合にはp.classやp#idのように入力欄を判別するためのCSSセレクターを指定してやる必要がある。

example

example: {
  attributes: {
    content: "リード文です",
  },
},

ブロックを選択するときにhoverすると出てくるサンプルに使われる内容。

edit

edit: function (props) {
  function onChangeContent(newContent) {
    props.setAttributes({ content: newContent });
  }

  blockProps = useBlockProps({
    key: "richtext",
    tagName: "p",
    className: "content-lead",
    onChange: onChangeContent,
    value: props.attributes.content,
    placeholder: "リード文を入力してください。",
  });

  return el(RichText, blockProps);
},

ブロックエディタ上にブロックのコンテンツを表示して編集する際に使われる関数を定義。

elはelement.createElementのエイリアス。第三引数はchildrenで、タグに挟まれた要素がある場合に指定する。単なる文字列かelementかそれらの配列などを。

const el = element.createElement;

save

save: function (props) {
  const blockProps = useBlockProps.save({
    tagName: "p",
    className: "content-lead",
    value: props.attributes.content,
  });

  return el(RichText.Content, blockProps);
},

保存する際に使われる関数。

returnした内容がブロックのコンテンツとして保存される。RichText.Contentを使うのが肝。

    <!-- wp:brassworks/lead -->
    <p class="wp-block-brassworks-lead content-lead">これもまたリード文なのだ。</p>
    <!-- /wp:brassworks/lead -->

editの出力との内容に齟齬が起きるとブロックは壊れたとみなされエディタ上での表示も編集もできなくなる。
その場合はconsoleに差分が出ているので、それを手がかりに直す。

次なる道

ブロック作成完全に理解した。

次のステップは

  • wp_block_typeとblock.jsonを使ってブロックを登録するようにする
  • JSXで書いてwp-scriptsでビルドするようにする

ってかんじかな。

参考: register_block_type() と wp-scripts を使おう