トップへ戻る
BLOGS

WordPress コードをシンタックスハイライトさせるブロック作成

WordPress コードをシンタックスハイライトさせるブロック作成

ついに、、
WordPressにおけるカスタムブロックの作成に乗り出したいと思います

WordPressにおけるブロック作成に関しての議事録及び備忘録として本記事を書いていきますが、いかんせんWordPressブロック作成の技術的難易度が高いために、完成コードというよりかは、ただの日記になりそうですがあしからず、、、、

それでは書いていきましょう

※本記事はMacを使用したWordPressのローカル環境であり、Node.jsのインストール環境での開発を想定した記事です

WordPressのカスタムブロック概要

カスタムブロックの作成概要になります

WordPressのブロック作成は

  • ブロックはJavaScript(React)で書く
  • PHPでブロックで使用するJS,スタイルシート,エディタースタイルを登録する
  • ブロックのエディターとフロントエンド側の設定を定義する
  • WordPress公式が開発用のパッケージを用意してくれているため、それがデフォ

今回は私が普段書いているようなプログラミングコードをシンタックスハイライトさせ視認性を良くするブロックを追加するプラグイン形式のコンテンツを追加したいと思います

JSXを書くための環境構築

JSXとは???

WordPressのブロックはReactというJavaScriptのフレームワークで作られています

Reactはコンポーネントと言ってそれぞれの部品を組み合わせて
DOMを構成するという仕組みでまるでHTMLを記述しているかのように記述することが可能でコードの視認性も抜群です
それに対しWordPressの用意しているwordpress/scripts(wp-scripts)のコンポーネントを組み合わせて使用することでブロックを作成していきます

wp-scriptsの内容までしっかり網羅したいところですが、今回はブロック作成をメインに書いていくため、またしっかり調べてまとめたいですね
最近こんな内容ばかりで中々調べが追いついていませんが、、「後でやる」はあんまりいい癖ではないので本当にしっかり調べます

そして私自身Reactに触ること自体初めてであり、間違った認識になっているかもしれませんが、
自分自身のために記録として残します

ターミナルを起動してファイルを作成する

ターミナルでプラグインのディレクトリ(wp-content/plugins/)に移動して
ブロックのディレクトリを作成します。この例では code-block というディレクトリを作成します

ターミナル

$ cd /Applications/MAMP/htdocs/blocks/wp-content/plugins return
$ mkdir oja-code-gutenberg  return
$ cd oja-code-gutenberg  return

そして cd コマンドで作成したディレクトリに移動しておきます

ターミナル

$ npm init -y return //package.json を生成

まずはnpm init -y コマンドを実行して package.json というファイルを生成します

npm init コマンドに -y オプションを指定するとデフォルトの設定値で package.jsonを生成するという意味です

package.jsonとは WordPress 固有のものではなく、インストールされたパッケージに関する設定情報などが記述されたファイルでこのファイルの設定を変更することで、作業ファイルをビルドする詳細などを変更することができます

それでは wp-scripts をインストールしていきます

ターミナル

$ npm install --save-dev --save-exact @wordpress/scripts  return //インストールを実行

インストールが完了するとnode_modules ディレクトリが生成されその中に関連パッケージがコピーされます
これらのパッケージモジュールは開発でビルドする際に依存するライブラリになるためこれらのファイルを編集することは基本的にはありません

その代わり出力設定などは package.json を編集していくことになります

まずは下記を追加します

package.json

"scripts": {
    "start": "wp-scripts start",
    "build": "wp-scripts build",
    "format:js": "wp-scripts format-js",
    "lint:css": "wp-scripts lint-style",
    "lint:js": "wp-scripts lint-js",
    "packages-update": "wp-scripts packages-update"
  },

これらは本番用のビルド(production ビルド)や開発モードをコマンドラインから実行できるようにしたりするものです

作業フォルダの作成

ということで開発中に編集するディレクトリを作成します
「 src 」としてディレクトリを作成しその中で下記のファイルを作ります

  • index.js
  • edit.js
  • style.scss
  • editor.scss

これらが作業ディレクトリになります

今回作成するプラグインでは edit.js を作成し Edit コンポーネントとして別ファイル(src/index.js)からインポートします。

ブロックをフロントエンド側で表示するsave プロパティは 今回の例ではPHP でレンダリングするので null を返すようにする予定です

ブロックの登録

それでは作成したファイルにブロック登録のスクリプトを書いていきます

まずはメインファイルの index.js から

index.js

import { registerBlockType } from '@wordpress/blocks';
import Edit from './edit';
import './style.scss';  
 
registerBlockType( 'oja/code-gutenberg', {
 title: "Oja Code Gutenberg",
  description: 'Code Prettify Block (Syntax Highlighter) ', //説明(オプション)
  icon: 'smiley', //アイコン(オプション)
  category: 'common',
  edit: Edit,
  save: () => { return null },
});

文の先頭では import で必要なファイルを読み込んでいます

edit.js ではとりあえずテスト用の文字列を埋め込んで確認できるだけの記述を書いていきます

edit.js

import './editor.scss';
 
export default function Edit( props ) {
  const { className } = props;
  return (
    <div className={ className }>
      Hello From Edit.
    </div>
  );
}

edit.jsで書いた内容は index.js に import され Editメソッドで管理画面上の表示として登録されます

プラグインとして登録と読み込み

今まで作成したブロックファイルをPHP側で受け取り
プラグインとして読み込めるよう登録します
プラグインディレクトリの直下にPHPファイルを作成しプラグインの決り文句とブロックの登録関数を定義します

oja-code-gutenberg.php

<?php
/**
 * Plugin Name:     Oja Code Gutenberg
 * Description:     プログラミング言語をシンタックスハイライトします。   詳細な設定をお好みで追加することでサイトのデザインにマッチします
 * Version:         0.1.0
 * Author:          おジャコ丸
 *
 * @package         oja
 */

// ▼ブロックスクリプトの登録
function oja_code_gutenberg() {
  $dir = dirname( __FILE__ );

  //依存スクリプトの配列とバージョンが記述されたアセットファイルの読み込み
  $script_asset = require( "$dir/build/index.asset.php" );

  //ブロック用のスクリプトの登録
  wp_register_script(
    //スクリプトのハンドル名
    'oja_code_gutenberg-script',
    //スクリプトの URL
    plugins_url( 'build/index.js', __FILE__ ),
    //依存するスクリプト
    $script_asset['dependencies'],
    //スクリプトのバージョン
    $script_asset['version']
  );

  //エディタ用のスタイルの登録
  wp_register_style(
    'oja_code_gutenberg-editor-style',
    plugins_url( 'build/index.css', __FILE__ ),
    array(),
    filemtime( "$dir/build/index.css" )
  );

  //フロントエンド及びエディタ用のスタイルの登録
  wp_register_style(
    'oja-code-gutenberg-style',
    plugins_url( 'build/style-index.css', __FILE__ ),
    array(),
    filemtime( "$dir/build/style-index.css" )
  );
  
  //ブロックを登録
  register_block_type(
    //名前空間/ブロック名
    'oja/code-gutenberg',
    //スクリプトやスタイルをブロックに関連付け
    array(
      'editor_script' => 'oja_code_gutenberg-script',
      'editor_style'  => 'oja_code_gutenberg-editor-style',
      'style'         => 'oja-code-gutenberg-style',
      //PHP でレンダリングするコールバック関数を指定
      'render_callback' => 'oja_code_render',
    )
  );
}
add_action( 'init', 'oja_code_gutenberg' );

ここまで用意できたらコマンドを入力してJavaScriptをビルド(コンパイル)して
今用意したPHPファイルで読み込める様にしてあげましょう

ターミナル

$ npm start  return //監視スタート

これで「build」ディレクトリが作成されてその中にコンパイルされたSCSSとJSファイルが生成されました

そして管理画面からプラグインを有効化することができるようになりました
有効化してWordPressからも確認できるようにしましょう

「 npm start 」コマンドを実行しておきます
これで自動的にファイルの監視が始まります、また自動監視を停止する際は
「 control + c 」で停止できます

Code-Prettifyの設置

Code-PrettifyはGoogleが提供しているフリーソフトで
軽量かつ使いやすいコードのシンタックスハイライト用ライブラリです

下記からダウンロードして使用します

https://github.com/google/code-prettify/raw/master/distrib/prettify-small.zip

Code-Prettifyの概要としてまとめられている記事です

Google Code-Prettify の基本的な使い方

使用するファイル

ZIPファイルを解凍したら必要なファイルをプラグインディレクトリ直下に新たにディレクトリを作成し配置します
今回の例ではフォルダ名を code-prettify として作成し以下のファイルを直下に配置します

  • prettify.css
  • prettify.js
  • init_prettify.js //これから作成します
  • lang-css.js(CSS 用言語ハンドラー)

必須なのは上記のリスト2つですがCSSをハイライトするのに lang-css.js がいるようで他にも使用したい言語ハンドラーがあればこのディレクトリに配置します

必要な関数を登録

code-prettifyをダウンロードして使用する際は prettify.js を読み込んで PR.prettyPrint() という JavaScript の関数を実行する必要があります
以下のような JavaScript ファイル(init-prettify.js)を作成して code-prettify フォルダ直下に保存します

init-prettify.js

window.addEventListener("load", function() {
  PR.prettyPrint();
});  

ファイルの読み込み

それぞれのファイルをWordPressの関数 wp_enqueue_script() で読み込むようにします
そのさい init-prettify.js は管理画面で読み込む必要はないため !is_admin() で判定しています

oja-code-gutenberg.php

// ▼Code-Prettifyのスクリプトを登録(エンキュー)
function add_code_prettify_scripts_and_styles() {
  $dir = dirname( __FILE__ );

  //Code-Prettify の JavaScript ファイルの読み込み(エンキュー)
  wp_enqueue_script(
    'code-prettify',
    plugins_url( '/code-prettify/prettify.js', __FILE__ ),
    array(),
    filemtime( "$dir/code-prettify/prettify.js" ),
    true
  );

  //言語ハンドラーの JavaScript ファイルの読み込み(エンキュー)
  wp_enqueue_script(
    'code-prettify-css-lang',
    plugins_url( '/code-prettify/lang-css.js', __FILE__ ),
    array('code-prettify'),
    filemtime( "$dir/code-prettify/lang-css.js" ),
    true
  );
  wp_enqueue_script(
    'code-prettify-sql-lang',
    plugins_url( '/code-prettify/lang-sql.js', __FILE__ ),
    array('code-prettify'),
    filemtime( "$dir/code-prettify/lang-sql.js" ),
    true
  );

  //管理画面以外(フロントエンド側でのみ読み込む)
  if(! is_admin()) {
    //PR.prettyPrint() を実行する JavaScript ファイルの読み込み(エンキュー)
    wp_enqueue_script(
      'code-prettify-init',
      plugins_url( '/code-prettify/init-prettify.js', __FILE__ ),
      array('code-prettify'),
      filemtime( "$dir/code-prettify/init-prettify.js" ),
      true
    );
  }

  //Code-Prettify の基本スタイルの読み込み(エンキュー)
  wp_enqueue_style(
    'code-prettify-style',
    plugins_url( '/code-prettify/prettify.css', __FILE__ ),
    array(),
    filemtime( "$dir/code-prettify/prettify.css" )
  );
}
add_action('enqueue_block_assets', 'add_code_prettify_scripts_and_styles');

さてここまでできたら入力した値(コード)を早速ハイライトしてみたいものです
まずはテキストを入力できるようにコンポーネントを活用して
Edit: と 出力するコールバック関数を編集していきます

attributes を設定する

これから管理画面上に入力エリアを作成します


この記事では PHP でレンダリングして表示するので、
入力された値は属性としてPHP側で保存します 
属性は ブロックを登録したregister_block_type 関数に追記して設定します。

oja-code-gutenberg.php の register_block_type 関数に attributes プロパティを追加し、属性 codeArea を設定します

oja-code-gutenberg

//属性を追加
    'attributes' => [
      //属性 codeArea を設定
      'codeArea' => [
        'type' => 'string', 
        'default' => '' 
      ],
    ],

attributes はブロックのデータ(及びその取得方法)を定義するためのオブジェクトで
JSのメソッドなのでドットつなぎでアクセスすることが可能です
今回入力される値は文字列なので type を string に、default は空文字列に設定します

TextareaControl コンポーネント

TextareaControl コンポーネントはエディター画面上でユーザーがテキストを入力できるようにするコンポーネントです

まずはコードを

edit.js

import { TextareaControl } from '@wordpress/components';
import './editor.scss';
 
export default function Edit( props ) {
  const { className, attributes, setAttributes } = props;
  
  //テキストエリア(TextareaControl)の行数
  let codeAreaRows = attributes.codeArea.split(/\r|\r\n|\n/).length > 3 ? attributes.codeArea.split(/\r|\r\n|\n/).length : 3;
 
  return (   
    <div className={ className }>
      <TextareaControl 
        label="Code:" 
        value={ attributes.codeArea }
        onChange={ (code) => setAttributes({ codeArea: code }) }
        rows={ codeAreaRows }
      />
    </div>
  );
}


一つづつ説明していきます

import { TextareaControl } from '@wordpress/components';

この文で wp-scripts の components から TextareaControl コンポーネントを取得し使用できるようにしています

import './editor.scss';

エディター用のCSSをインポートすることで「 build 」ディレクトリにSCSSからCSSとしてコンパイルされるようになります
したがってindex.js などでも同様にフロント用として記述してあります

export default function Edit( props ){ //管理画面設定 }

この関数内で定義した内容が管理画面で出力される内容になります
引数の props はReactの機能でプロパティのリストの様なものでここから先に定義した属性値や、WordPress固有のコンポーネント(部品)にアクセスすることが可能です

 const { className, attributes, setAttributes } = props;

そのコンポーネントを分割代入でそれぞれ変数に格納します

className , (WordPressが自動で付与するクラス名)
ちなみにクラス名は「 wp-block-名前空間−ブロック名 」となります

attributes, (PHP側で定義した属性値、ドット繋で固有のプロパティにアクセス可能)

setAttributes, (受け取った属性値を変更するメソッド)

これらのプロパティを使用してreturn()メソッドで返す事で管理画面上で表示することができます

TextareaControl のプロパティで詳細を変数展開しながら渡します
下記の様な書き方です

<TextareaControl 
  label="Code:"  //ラベル
  value={ attributes.codeArea } //値
  onChange={ (code) => setAttributes({ codeArea: code }) } //イベントハンドラ
  rows={ codeAreaRows } //行数設定
 />

さてこれで管理画面はできましたが今度はフロント側でレンダリング内容を変更します

PHPでフロントエンドをレンダリング

Code-Prettify でシンタックスハイライトして表示するには、シンタックスハイライトで表示したい部分を prettyprint クラスを指定した <pre> タグで囲みます

また行数をデフォルトでは5行間隔で行数が表示されますが、
全行表示するためには「 linenums 」クラスを付与したり更にコロンでつないで行数表示開始行を指定したりできます

function oja_code_block_render($attributes) {
  
  //属性 codeArea が空なら何も表示しない
  if (empty($attributes['codeArea'])) {
    return '';
  }
  
  //入力された値を esc_html() でエスケープ処理して出力
  return '<div class="wp-block-wdl-oja-code-gutenberg"><pre class="prettyprint linenums">'.esc_html($attributes['codeArea']).'</pre></div>';
}

PHPの register_block_type() で ‘render_callback’ を指定した場合は引数に属性値を受け取れます、
そこから連想配列で$attributes[‘codeArea’]のように保存した値にアクセスできるので
Code-Pattifyのクラスを付与したpreタグで出力します
受け取った値(コード)はWordPressのエスケープ関数esc_html()で内部上無害なタグに変換できます

ここまでの表示確認

ようやくこれで管理画面からプログラミングコードを入力して、そのコードを保存でき、公開画面でシンタックスハイライトした表示を出力することができました。

ここからはさらに難易度があがりますが、ここまで作成したプラグインに更に機能を追加していこうと思います

インスペクターでコードをカスタム

インスペクターとは?

インスペクターとはブロックを選択、編集中にサイドバーに現れる詳細設定のことです
デフォルトでは「高度な設定:追加CSSクラス」のみですが、ここにオリジナルの設定を加えていきその値によって Code−Pattify をカスタマイズしたいと思います

InspectorControls コンポーネント

インスペクターをカスタマイズするにはWordPressが提供する block-editor パッケージにある InspectorControls コンポーネントを使用します
edit 関数の return() の中で InspectorControls コンポーネントで囲んだ内容はサイドバーに表示されます

簡単にですがその中に記述するコンポーネントの概要です

<InspectorControls> //InspectorControls で囲んだ内容はサイドバーに表示される
  <PanelBody   // 1つのパネル全体を囲む
    title="サンプルインスペクター"
    initialOpen={true}
  >
    <PanelRow>   //各コンテンツを囲むコンテナ
      <ToggleControl 
        label="トグルボタン"
        help="何らかのオン・オフ"
        checked={attributes.myToggle}
        onChange={(val) => setAttributes({ myToggle: val })}
      />
    </PanelRow>
    <PanelRow>   //各コンテンツを囲むコンテナ 
      <SelectControl  
        label="果物"
        help="好きな果物"
        value={attributes.myFruit}
        options={[
          {label: "りんご", value: 'apple'},
          {label: "ぶどう", value: 'grape'},
          {label: "いちご", value: 'strawberry'},
        ]}
        onChange={(val) => setAttributes({ myFruit: val })}
      />
    </PanelRow>
    
    ・・・中略・・・
    
  </PanelBody>  
</InspectorControls>

上記は一例ですが
PanelBody の中にはパネル内のコンテナである PanelRow でコンポーネントなどのコンテンツを囲んで配置します
これらが一つ一つの項目になり「〜〜Control」のコンポーネントで項目の内容を定義します

各コントロールのコンポーネントではラベルやヘルプテキスト、値などをプロパティとして設定でき、変更された場合は onChange のコールバック関数の定義で setAttributes を使って値を更新できます

この setAttributes プロパティで設定した変数とPHP側で定義しておいた属性値を紐付けることで、値として保存することができます

またPHPでレンダリングする際にも属性値として受け取ってアクセスできます

returnメソッドの中でコールすることもできるようですが、視認性が悪くなることから今回は関数として定義して呼び出す形にします

edit.js でインスペクターを定義

ここでのメインは「どんな設定を行いたいか」を定義していくイメージです

edit.js

//▼インスペクターを表示する関数
  const ojaInspector = () => {
    return (
      <InspectorControls>
        <PanelBody title="シンタックスハイライト設定" initialOpen={true}>
          <PanelRow>
            <ToggleControl
              label={attributes.linenums ? "行番号(表示)" : "行番号(非表示)"}
              checked={attributes.linenums}
              onChange={(val) => setAttributes({ linenums: val })}
            />
          </PanelRow>
          {attributes.linenums && (
            <PanelRow>
              <TextControl
                label="開始する行番号"
                type="number"
                value={attributes.linenumStart}
                onChange={(val) =>
                  setAttributes({ linenumStart: parseInt(val) })
                }
              />
            </PanelRow>
          )}
          <PanelRow>
            <SelectControl
              label="ブロックの配置"
              value={attributes.align}
              options={[
                { label: "なし", value: "" },
                { label: "左寄せ", value: "left" },
                { label: "中央寄せ", value: "center" },
                { label: "右寄せ", value: "right" },
                { label: "幅広", value: "wide" },
              ]}
              onChange={(val) => setAttributes({ align: val })}
            />
          </PanelRow>
          <PanelRow>
            <CheckboxControl
              label="最大幅を指定"
              checked={attributes.maxWidthEnable}
              onChange={(val) => setAttributes({ maxWidthEnable: val })}
              help="※ インラインスタイルで設定します"
            />
          </PanelRow>
          {attributes.maxWidthEnable && (
            <PanelRow>
              <RangeControl
                label="最大幅設定"
                value={attributes.maxWidth}
                onChange={(val) => setAttributes({ maxWidth: parseInt(val) })}
                min={300}
                max={1800}
                step={10}
                help="最大幅を px で指定します"
              />
            </PanelRow>
          )}
          <PanelRow>
            <SelectControl
              label="言語選択"
              value={attributes.lang}
              options={[
                { label: "Default", value: "" },
                { label: "CSS", value: "css" },
                { label: "SQL", value: "sql" },
                { label: "PHP", value: "php" },
                { label: "JavaScript", value: "js" },
                { label: "SASS", value: "scss" },
                { label: "Bash", value: "bash" },
                { label: "Java", value: "java" },
                { label: "C++", value: "c++" },
              ]}
              help="※ 適切な言語を設定することで綺麗にハイライトされます"
              onChange={(val) => setAttributes({ lang: val })}
            />
          </PanelRow>
          <PanelRow>
            <SelectControl
              label="書式設定"
              value={attributes.fontFamily}
              options={[
                { label: "デフォルト", value: "default" },
                { label: "MS P明朝", value: "MS P明朝" },
                { label: "Menlo", value: "Menlo" },
                { label: "monospace", value: "monospace" },
                { label: "Courier", value: "Courier" },
                { label: "Ricty Diminished", value: "Ricty Diminished" },
              ]}
              onChange={(val) => setAttributes({ fontFamily: val })}
            />
          </PanelRow>
          <PanelRow>
            <SelectControl
              label="デザインスキン"
              value={attributes.skin}
              options={[
                { label: "Basic", value: "" },
                { label: "Desert", value: "desert" },
                { label: "Doxy", value: "doxy" },
                { label: "Sons-of-obsidian", value: "sons-of-obsidian" },
                { label: "Sunburst", value: "sunburst" },
              ]}
              onChange={(val) => setAttributes({ skin: val })}
            />
          </PanelRow>
        </PanelBody>
      </InspectorControls>
    );

return (   
  //配列を指定
  [
    getInspectorControls(),  //インスペクター
    <div className={ className }>
      <TextareaControl 
        label="Code:" 
        value={ attributes.codeArea }
        onChange={ (code) => setAttributes({ codeArea: code }) }
        rows={ codeAreaRows }
      />
    </div>
  ]
);
  };

注意点としては最後にreturn()メソッドでインスペクターを返すのですが、
配列で指定して、インスペクターと入力コードを返しています

それぞれのコンポーネントで定義、設定した値をPHPに渡してレンダリングします

PHPでインスペクター属性値を定義

edit.js で定義したインスペクターをPHPで保存するには属性値を設定して紐付けるのでしたね

ここでやりたいことは「値の紐付けと型の指定です」

code_gutenberg.php のregister_block_type()関数の Attributesを追記します

oja-code-gutenberg

'attributes' => [
        //属性 codeArea を設定
        'codeArea' => [
          'type'    => 'string',
          'default' => ''
        ],
        //行番号トグル
        'linenums' => [
          'type'    => 'boolean',
          'default' => true
        ],
        //行番号開始ナンバー
        'linenumStart' => [
          'type' => 'number',
          'default' => 1
        ],
        //ブロックの配置
        'align' => [
          'type'    => 'string',
          'default' => ''
        ],
        //最大幅を指定するか
        'maxWidthEnable' => [
          'type'    => 'boolean',
          'default' => false
        ],
        //最大幅設定
        'maxWidth' => [
          'type'    => 'number',
          'default' => 0
        ],
        //言語指定
        'lang' => [
          'type'   => 'string',
          'default'=> ''
        ],
        //書式設定
        'fontFamily' => [
          'type'   => 'string',
          'default'=> ''
        ],
        //デザインスキン
        'skin' => [
          'type' => 'string',
          'default' => 'desert'
        ]
      ],

これでエディター上で保存が効くようになりました
最後にここで定義した属性値を使用してレンダリング関数を変更します

PHPで設定値をレンダリング

フロントエンドに表示する関数であるため
考え方としては「設定した値をどう表現するか」であります

oja-code-gutenberg

// render_callback 関数の定義
function oja_code_render($attributes) {
  //属性 codeArea が空なら何も表示しない
  if (empty($attributes['codeArea'])) {
    return '';
  }

  //ブロックスタイル用クラス
  $add_block_class  = '';
  $add_inline_style = '';
  $add_font_family  = '';
  //配置
  if ($attributes['align']) {
    $add_block_class = ' align'. $attributes['align'];
  }

  //デザインスキン
  if ($attributes['skin']) {
    $add_block_class .= ' '.$attributes['skin'];
  }

  // maxWidthEnable が true なら max-width をインラインスタイルで設定
  if ($attributes['maxWidthEnable']) {
    $add_inline_style = ' style="max-width: '. $attributes['maxWidth']. 'px"';
  }

  // ブロックの div 要素に追加のクラスとスタイルを指定
  $output = '<div class="wp-block-oja-code-gutenberg'. $add_block_class. '"'. $add_inline_style. '>';

  //preタグに追加するクラス
  $add_pre_class = '';
  // linenums がtrueならクラスを追加
  if($attributes['linenums']) {
    $add_pre_class = ' linenums';
    // 行の開始番号が指定されていればその値をクラスに追加
    if($attributes['linenumStart'] !== 1 ) {
      $add_pre_class .= ':' . $attributes['linenumStart'];
    }
  }

  // 言語が指定されていればそのクラス(lang-xxxx)を設定
  if ($attributes['lang']) {
    $add_pre_class .= ' lang-' . ($attributes['lang']);
  }

  //書式設定があればインラインスタイルで指定
  if($attributes['fontFamily'] !== 'default') {
    $add_font_family = ' style="font-family: '. $attributes['fontFamily']. '"';
    $output .= '<pre class="prettyprint'  . $add_pre_class . '"' . $add_font_family . '>';
  } else {
    $output .= '<pre class="prettyprint' . $add_pre_class . '">';
  }
  //入力された値を esc_html() でエスケープ処理して出力
  $output .= esc_html($attributes['codeArea']).'</pre></div>';

  return $output;
}

言語指定についてついて補足など

code-prettify は記述されている言語を推測してキーワードや構文に対して自動的に色分けをしてくれますが、

自動色分けも完全無欠ではなく、うまく色分けできていない場合もあるようです
それに対しては、言語を示唆するクラス(lang-xxxx)を追加することで言語を明示的に指定してより適切に表示することができます

スタイル指定のskinについて

このブロックではオプションでスキンを変更できるようにしています、

基本的にはprettify.cssだけで問題なく表示できるのですがダウンロードしたファイル群の中に「 skin 」 ディレクトリには好みに応じてそれぞれ別のテーマがあるのですが、これらをすべて一つのSCSSにまとめてしまって、
クラスセレクタを使って skin フォルダの CSS ファイルの内容をコピーしています

スキンのスタイルは code-prettify/styles/ に圧縮されていないファイルがあるので、それを元にカスタマイズすると楽ちんです

すべてコピーしてセレクター指定ができたらラップしているタグにクラスを付与します

$add_block_class .= ' '.$attributes['skin'];

まだ調整は必要ですがこれですべてのテーマの切り替えを行うことができます

管理画面にプレビュー画面を追加

ここに来て最終関門です
管理画面のツールバーにプレビュー用のボタンを作成し、そのボタンによって「プレビューモード」と「編集モード」を切り替えを可能にします

概要は

  • フラグを立てれる真偽値の属性値を追加
  • ツールバーにプレビュー用のボタンを追加
  • 必要なコンポーネントをインポート
  • フラグを元に真偽値を切り替える関数を作成
  • Edit: でブロックをレンダリング ※関数化しておきます
  • code-prettify実行関数をフックで登録する
  • return();メソッド内で真偽値で分岐させる事で管理画面で表示する

では順番に制作します

フラグを立てる真偽値の属性追加

まずは属性から定義していきます
PHP側で attributes 属性の追加ですね

oja-code-gutenberg

'isEditMode' => [
  'type' => 'boolean', 
  'default' => true
],

この真偽値を判定してレンダリング関数を出し分ける用に書いていきます

それぞれのコンポーネントをインポート

次に「ボタン」と「ツールバー」そして条件分岐するための「 Fragment 」code-prettifyを管理画面上で実行するためのフック「 useEffect 」をインポートします

edit.js

//BlockControls を追加でインポート
import { InspectorControls, BlockControls } from '@wordpress/block-editor';
//Button,Toolbar を追加でインポート
import { TextareaControl, PanelBody, PanelRow, ToggleControl, SelectControl, TextControl, RangeControl, CheckboxControl, Button, Toolbar } from '@wordpress/components';
//Fragment, useEffect を追加でインポート
import { Fragment, useEffect } from '@wordpress/element';

フラグ判定の関数を定義

edit.js

// ▼プレビューボタンの判定関数
  const getMode = () => {
    return (
      <BlockControls>
        <Toolbar>
          <Button
            //属性 isEditMode の値により表示するラベルを切り替え
            label={attributes.isEditMode ? "Preview" : "Edit"}
            //属性 isEditMode の値により表示するアイコンを切り替え
            icon={attributes.isEditMode ? "format-image" : "edit"}
            className="preview-button"
            //setAttributes を使って属性の値を更新(真偽値を反転)
            onClick={() =>
              setAttributes({ isEditMode: !attributes.isEditMode })
            }
          />
        </Toolbar>
      </BlockControls>
    );
  };

ボタンの制作ができました
ここで作成した関数は後にreturn();する際に表示を出し分けます

useEffect(フック)を登録

管理画面上でPR.prettyPrint() で実行する必要があります
useEffect フックはレンダーの完了時に毎回実行されるReactのフックという概念でWordPressのフックと同じく実行するタイミングを、第2引数を指定することによって設定できます

第2引数に attributes.isEditMode を指定することでボタンが切り替わるタイミングで毎回実行してくれます

edit.js

useEffect(() => {
  PR.prettyPrint();
}, [attributes.isEditMode]);

Edit: 上でコードをレンダリング

最後にJavaScript(JSX)でシンタックスハイライトをレンダリングします

PHPでのレンダリングと内容を同じにしますが、インラインスタイルはJSのオブジェクトで記述する必要がありますしJSではハイフンが使えなくなるため、プロパティはキャメルケースの maxWidthやfontFamily とする必要があります。

ここまで長く「 edit.js 」を編集しているため最後に全文を載せます

edit.js

//BlockControls を追加でインポート
import { InspectorControls, BlockControls } from "@wordpress/block-editor";
//Button,Toolbar を追加でインポート
import {
  TextareaControl,
  PanelBody,
  PanelRow,
  ToggleControl,
  SelectControl,
  TextControl,
  RangeControl,
  CheckboxControl,
  Button,
  Toolbar,
} from "@wordpress/components";
//Fragment, useEffect を追加でインポート
import { Fragment, useEffect } from "@wordpress/element";
import "./editor.scss";

export default function Edit(props) {
  const { className, attributes, setAttributes } = props;

  //▼インスペクターを表示する関数
  const ojaInspector = () => {
    return (
      <InspectorControls>
        <PanelBody title="シンタックスハイライト設定" initialOpen={true}>
          <PanelRow>
            <ToggleControl
              label={attributes.linenums ? "行番号(表示)" : "行番号(非表示)"}
              checked={attributes.linenums}
              onChange={(val) => setAttributes({ linenums: val })}
            />
          </PanelRow>
          {attributes.linenums && (
            <PanelRow>
              <TextControl
                label="開始する行番号"
                type="number"
                value={attributes.linenumStart}
                onChange={(val) =>
                  setAttributes({ linenumStart: parseInt(val) })
                }
              />
            </PanelRow>
          )}
          <PanelRow>
            <SelectControl
              label="ブロックの配置"
              value={attributes.align}
              options={[
                { label: "なし", value: "" },
                { label: "左寄せ", value: "left" },
                { label: "中央寄せ", value: "center" },
                { label: "右寄せ", value: "right" },
                { label: "幅広", value: "wide" },
              ]}
              onChange={(val) => setAttributes({ align: val })}
            />
          </PanelRow>
          <PanelRow>
            <CheckboxControl
              label="最大幅を指定"
              checked={attributes.maxWidthEnable}
              onChange={(val) => setAttributes({ maxWidthEnable: val })}
              help="※ インラインスタイルで設定します"
            />
          </PanelRow>
          {attributes.maxWidthEnable && (
            <PanelRow>
              <RangeControl
                label="最大幅設定"
                value={attributes.maxWidth}
                onChange={(val) => setAttributes({ maxWidth: parseInt(val) })}
                min={300}
                max={1800}
                step={10}
                help="最大幅を px で指定します"
              />
            </PanelRow>
          )}
          <PanelRow>
            <SelectControl
              label="言語選択"
              value={attributes.lang}
              options={[
                { label: "Default", value: "" },
                { label: "CSS", value: "css" },
                { label: "SQL", value: "sql" },
                { label: "PHP", value: "php" },
                { label: "JavaScript", value: "js" },
                { label: "SASS", value: "scss" },
                { label: "Bash", value: "bash" },
                { label: "Java", value: "java" },
                { label: "C++", value: "c++" },
              ]}
              help="※ 適切な言語を設定することで綺麗にハイライトされます"
              onChange={(val) => setAttributes({ lang: val })}
            />
          </PanelRow>
          <PanelRow>
            <SelectControl
              label="書式設定"
              value={attributes.fontFamily}
              options={[
                { label: "デフォルト", value: "default" },
                { label: "MS P明朝", value: "MS P明朝" },
                { label: "Menlo", value: "Menlo" },
                { label: "monospace", value: "monospace" },
                { label: "Courier", value: "Courier" },
                { label: "Ricty Diminished", value: "Ricty Diminished" },
              ]}
              onChange={(val) => setAttributes({ fontFamily: val })}
            />
          </PanelRow>
          <PanelRow>
            <SelectControl
              label="デザインスキン"
              value={attributes.skin}
              options={[
                { label: "Basic", value: "" },
                { label: "Desert", value: "desert" },
                { label: "Doxy", value: "doxy" },
                { label: "Sons-of-obsidian", value: "sons-of-obsidian" },
                { label: "Sunburst", value: "sunburst" },
              ]}
              onChange={(val) => setAttributes({ skin: val })}
            />
          </PanelRow>
        </PanelBody>
      </InspectorControls>
    );
  };

  // ▼プレビューボタンの判定関数
  const getMode = () => {
    return (
      <BlockControls>
        <Toolbar>
          <Button
            //属性 isEditMode の値により表示するラベルを切り替え
            label={attributes.isEditMode ? "Preview" : "Edit"}
            //属性 isEditMode の値により表示するアイコンを切り替え
            icon={attributes.isEditMode ? "format-image" : "edit"}
            className="preview-button"
            //setAttributes を使って属性の値を更新(真偽値を反転)
            onClick={() =>
              setAttributes({ isEditMode: !attributes.isEditMode })
            }
          />
        </Toolbar>
      </BlockControls>
    );
  };

  //プレビューをレンダリングする関数
  const getPreview = () => {
    // コードが入力されていない時
    if (attributes.codeArea === "") {
      return null;
    }

    // ブロックスタイル用クラス
    let addBlockClass = "",
      addInlineStyle = {},
      addPreClass = "",
      addFontFamily = {};
    //配置
    if (attributes.align) {
      addBlockClass = " align" + attributes.align;
    }

    //デザインスキン
    if (attributes.skin) {
      addBlockClass += " " + attributes.skin;
    }

    //ファイル名が指定されていれば filename_wrapper クラスを追加
    if (attributes.fileName) {
      addBlockClass += " filename_wrapper";
    }

    // maxWidthEnable が true なら max-width をインラインスタイルで設定
    if (attributes.maxWidthEnable) {
      addInlineStyle = { maxWidth: attributes.maxWidth };
    }

    // linenums がtrueならクラスを追加
    if (attributes.linenums) {
      addPreClass = " linenums";
      // 行の開始番号が指定されていればその値をクラスに追加
      if (attributes.linenumStart !== 1) {
        addPreClass += ":" + attributes.linenumStart;
      }
    }

    //言語が指定されていればそのクラスを指定
    if (attributes.lang) {
      addPreClass += " lang-" + attributes.lang;
    }

    //書式設定があればインラインスタイルで指定
    if (attributes.fontFamily !== 'default') {
      addFontFamily = {fontFamily: attributes.fontFamily};
    }

    //最終的なマークアップをレンダリング
    return(
      <div
        className={className + addBlockClass}
        style={addInlineStyle}
      >
        {attributes.fileName && (
          <p className="file_name">{attributes.fileName}</p>
        )}
        <pre
          className={"prettyprint" + addPreClass}
          style={addFontFamily}
          >
          {attributes.codeArea}
        </pre>
      </div>
    );
  };

  // ▼管理画面上でcode−prettifyを実行する関数を登録するフック
  useEffect(() => {
    PR.prettyPrint();
  }, [attributes.isEditMode]);

  //▼テキストエリア(TextareaControl)の行数
  let codeAreaRows =
    attributes.codeArea.split(/\r|\r\n|\n/).length > 3
      ? attributes.codeArea.split(/\r|\r\n|\n/).length
      : 3;

  // ▼編集画面の最終的なビューを返す
  return (
    //配列を指定
    [
      getMode(),
      ojaInspector(),
      <Fragment>
        {attributes.isEditMode && (
          <div className={className}>
            <TextControl
              label="File Name"
              type="string"
              className="filename"
              value={attributes.fileName}
              onChange={(val) => setAttributes({ fileName: val })}
            />
            <TextareaControl
              label="Code"
              value={attributes.codeArea}
              onChange={(code) => setAttributes({ codeArea: code })}
              rows={codeAreaRows}
            />
          </div>
        )}
        {!attributes.isEditMode && [
          <Button
            onClick={() => setAttributes({ isEditMode: true })}
            isLink
            icon="edit"
          >
            編集モード
          </Button>,
          getPreview(),
        ]}
      </Fragment>,
    ]
  );
} //export default function Edit(props)

ブロック制作まとめ

中々記事も膨大になってきましたし、ブロック制作の概要として伝えたいことは書けたので今回はここまでにします
やることとしては、、

  • 開発環境の構築
  • ブロックとスクリプトの登録
  • Edit: をコンポーネントで構築
  • 属性値の定義を登録
  • レンダリング処理を構築
  • スタイルを整える
  • リファクタリング

環境構築は個人でやるには難易度も高いと感じましたがこれからもっとプログラムを触っていく上で、こういった操作はデフォであると感じたため
普段からなるべくなら「ターミナル」操作を癖付けておこうと思います

お疲れさまでした

index.php

wp_register_script(
    //スクリプトのハンドル名
    'oja_code_gutenberg-script',
    //スクリプトの URL
    plugins_url( 'build/index.js', __FILE__ ),
    //依存するスクリプト
    $script_asset['dependencies'],
    //スクリプトのバージョン
    $script_asset['version']
  );

コメントをお待ちしております

お気軽にコメントをどうぞ。

CAPTCHA