WordPress ブロックエディタのブロックを作ってみる

ワードプレス 5系グーテンベルクで導入されたブロックエディタ。標準仕様でも色々なブロックがあり、とても多機能。さらにカスタムブロックを作ることで、従来のショートコードを超える利便性!(タブンネ)

とはいえ、カスタムブロックは必須パラメータのみの最小構成だと画面表示すらできない。これは、投稿画面と公開画面で共用の関数が使われているから。そこで、とりあえず画面表示できるまでを最小構成とし、最終的にインスタグラムの画像を独自仕様で貼り付けるブロックをプラグインとして作成してみる。

ワードプレス標準の埋め込みブロックでインスタを表示すると↓なかんじ。色々うるさい。

画像だけ表示したいが、引用なのでその辺の最低限のものを入れた状態。これを作る。インスタの埋め込み仕様は、こちら

ブロックの最小構成

block.php, block.js それぞれのファイルが入った insta-block フォルダをzip圧縮。プラグイン > 新規追加。

insta-block
┣ block.php
┗ block.js

block.php

ワードプレスにカスタムブロックを登録するための設定を記す。

<?php
/*
Plugin Name: Insta embed block
*/
function register_insta_block() {
    register_block_type( 'insta-block/image-embed', array(
        'editor_script' => 'image-embed',
    ) );
    wp_register_script(
        'image-embed',
        plugins_url( 'block.js', __FILE__ ), array(
          'wp-blocks',
          'wp-element',
//          'wp-editor'
        )
    );
}
add_action( 'enqueue_block_editor_assets', 'register_insta_block' );

register_block_type()

ブロックタイプを登録する。必須パラメータは、名前空間を含んだブロックタイプ名のみ。変更するブロックタイプの引数があれば、配列で第2パラメータに。

ここでは、第二パラに'editor_script' => 'image-embed'を指定。editor_を頭につけることで、'image-embed'を投稿画面のみのスクリプトとする。

wp_register_script()

スクリプトを登録する。必須パラメータは、ユニークなスクリプト名とソースファイルへのフルURLかワードプレスのルートへの相対パス。今回は、プラグインでテストしてるので plugins_url() で指定。第3パラメータの配列は、依存する他のスクリプトのハンドルを任意で。

ここでは、ブロックの登録関連関数を含む'wp-blocks'とブロック内の構造を指定するための 'wp-element'を指定。なお、リッチテキストな入力欄を作るには'wp-editor'も指定する。

wp- は、ワードプレスのスクリプトパッケージを指す。shortcode を呼び出すスクリプトなんかもある。

element は、React の抽象化レイヤー。ブロックの中身は、ほぼ React の作法で記す。よって、公式サイトを一読することをオススメする。状態に対応するシンプルな View が理解できてないと java script 単体でページを作る時との違いで混乱します。というか、しました。単純な event とも違うんだよね。。そして当ページでは、初期設定がいらないES5 つまり、従来型の java script で記述してるので React の ESNext + JSX とは表記が異なります。

block.js

ブロックを登録し、編集と保存に関するスクリプトを記す。

// リッチテキストエディタを使う場合は適宜コメントを外す
( function( blocks,/* editor,*/ element ) {

  blocks.registerBlockType( 'insta-block/image-embed', {
    title: 'テスト: とりまブロック',
    icon: 'universal-access-alt',
    category: 'layout',
    
    edit: function( props ) {
//      function onChangeText( value ){
//        props.setAttributes( { text: value } );
//			}
      
      return (
//        element.createElement( editor.RichText, {
//          value: props.attributes.text,
//          onChange: onChangeText
//        }),
        element.createElement(
          'p', null, '投稿画面で表示。'
        )
      );
    },
    
    save: function( props ) {
      return (
//        element.createElement( 'p', null,
//                              editor.RichText.value ),
        element.createElement( 'p', null,
                              '公開画面で表示。')
      );
        },
    } );
}(
    window.wp.blocks,
//    window.wp.editor,
    window.wp.element
) );

java script なので、ワードプレスの記事投稿画面でブラウザの ウェブ開発ツール > コンソール を開き、テストするのがオススメ。この時、unregisterBlockType()でブロックを一旦取り消すと何かと楽♪

( function( blocks,/* editor,*/ element ) {
// ↓この一行を入れてコンソールで実行
  blocks.unregisterBlockType( 'insta-block/image-embed');
  blocks.registerBlockType( 'insta-block/image-embed', {
// 以下省略

カプセル化のために即時関数

( function ( /*para*/ ){
  /*処理*/
}( /* パラメータ*/  ));

registerBlockType()

ブロックタイプを登録する。必須パラメータは、名前空間付きのユニークなブロック名とブロック設定をオブジェクト { key: value } で記したもの。ブロック設定の必須項目は、 title, category

title は、「簡潔に一行で名付けよ。」って Block Design ではなってるけどテストなんで。。Category も Core の中からテキトウ。。 icon も Dashicons の中から適当なのをクリックすると大きく表示されるので、その右肩に表示される名前からdashicons-を除いたものを使う。例:dashicons-universal-access-alt → icon: 'universal-access-alt'

edit, save

React の render() に当たる部分。受け取った入力データを元に、表示する内容を返すためのコードを記す。上記「テスト:とりまブロック」のように固定表示なら、JS で HTML を書き出すのとホボ同じ。コメントアウトした RichText のように入力欄がある場合は、状態に対応した(onChange) view を作るため、React の state に当たる attributes を props にセットし結果を返す。

edit は、編集画面での表示を記す。

save は、公開画面での表示用に保存する内容を記す。但し、基本的に文字列と数字しか保存できない。そして、ここで保存された内容をもとに編集画面が再構築されるので、registerBlockType() の attributes 設定が重要になる。

attributes は、save された HTML のどこにそれが使われているかを指定する。edit の描画に使っただけのものは、省略可。

attributes: {
      name: { // 好きな名称
        type: 'string', // 指定可能な type は、公式doc参照
        source: 'text', // <a href='https:// ..'>text はココの事</a>
        selector: 'a', // HTMLの a タグ
      },
      url: {
        type: 'string',
        source: 'attribute', // a タグの属性値がソースだから
        attribute: 'href', // 属性 href の意
        selector: 'a',
      },
    },

block.js まとめ

下記コードで、投稿画面でのプレビューと公開画面での表示が可能。もし投稿画面を保存後リロードするとヴァリデーションエラーとなる場合は、 attributes の設定を確認。r が抜けた attibutes だったり、s が抜けた attribute だったりしてもコンソールのエラーにそれは指摘されない。save された内容と save function でマークアップしたものが違うと指摘されるだけ。ちなみに↓なエラーが表示される。

Block validation: Expected end of content, instead saw  
Object { type: "StartTag", tagName: "blockquote", attributes: (1) […], selfClosing: false }
 . blocks.min.js:2:100144

今回は、無闇矢鱈にインスタへお伺いを立てないよう preview ボタンを押すと編集画面でプレビューできる仕様。そのためリロードすると、’image is here.’ に戻ります。

( function( blocks, element ) {
  function getImage( props ){
    return (
      element.createElement('blockquote', null,
        element.createElement( 'img', {
          src: props.attributes.url + 'media',
          alt: ""
          },
        ),
        element.createElement('div', {
          style: { textAlign: 'right',fontSize: 'smaller' }
          },
          'Photo by ',
          element.createElement( 'a', {
            href: props.attributes.url,
            },
            props.attributes.name
          )
        )
      )
    );
  }

//blocks.unregisterBlockType( 'insta-block/image-embed');
  blocks.registerBlockType( 'insta-block/image-embed', {
    title: 'テスト: とりまインスタ',
    icon: 'instagram',
    category: 'embed',
    attributes: {
      name: {
        type: 'string',
        source: 'text',
        selector: 'a',
      },
      url: {
        type: 'string',
        source: 'attribute',
        attribute: 'href',
        selector: 'a',
      },
    },
    
    edit: function( props ) {
      let child = 'image is here.';
      
      function onChangeUrl( event ){
        event.preventDefault();
        props.setAttributes( { url: event.target.value } );
			}
      function onChangeName( event ){
        event.preventDefault();
        props.setAttributes( { name: event.target.value } );
			}
      function pushBtn( event ){
        event.preventDefault();
        props.setAttributes( { content: getImage( props ) } );
      }
      
      if ( props.attributes.content ){
        child = props.attributes.content;
      }
      return (
        element.createElement( 'form', { onSubmit: pushBtn },
          element.createElement( 'input', {
            type: 'url',
            value: props.attributes.url,
            onChange: onChangeUrl,
            placeholder: 'url here',
            required: true
          }),
          element.createElement('input', {
            type: 'text',
            value: props.attributes.name,
            onChange: onChangeName,
            placeholder: 'author name',
            required: true
          }),
          element.createElement( 'input', {
            type: 'submit',
            value: 'preview'
          }),
            child
        )
		  );
    },
    
    save: function( props ) {
      return getImage( props );
    },  
  } );
}(
    window.wp.blocks,
    window.wp.element
) );

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください