WordPress Logo WordPress Code-Prettify でシンタックスハイライトするブロックの作成

WordPress のブロックエディタ Gutenberg で記述したコードを Code-Prettify を使ってシンタックスハイライトするブロックを作成する方法の覚書です。

更新日:2020年11月03日

作成日:2020年11月02日

ブロックの基本的な作成方法や環境の構築方法については以下を御覧ください。

関連ページ

また、Node.js がインストールされていて、WordPress のローカル環境があることを前提にしています。

概要

TextareaControl コンポーネントを使ってコードを入力するエリアを表示し、入力されたコードをフロントエンドではエスケープ処理して Code-Prettify を使ってシンタックスハイライトで表示するブロックを作成する例です。

ブロックの編集画面では、サイドバーの設定項目(インスペクター)でスキンや行番号の表示・非表示、max-width などをブロックごとに設定できるようにします。

以下は上記入力時のフロントエンド側の表示例です。

環境構築とファイルの作成

以下は wp-scripts を使って JSX をコンパイルする環境を構築する例です。create-block を使うともっと簡単に環境を構築することができます。

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

作成したディレクトリに移動します。

$ cd /Applications/MAMP/htdocs/blocks/wp-content/plugins return
$ mkdir my-code-block  return
$ cd my-code-block  return

npm init -y を実行して package.json というファイルを生成します。

npm init コマンドに -y オプションを指定するとデフォルトの設定値で package.json というファイルが生成されます。

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

//生成される package.json の場所と内容が表示される
Wrote to /Applications/MAMP/htdocs/blocks/wp-content/plugins/my-code-block/package.json:

{
  "name": "my-code-block",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

npm install コマンドで @wordpress/scripts をインストールします。

--save-dev は開発環境で使う(開発時に依存する)パッケージに指定するオプションで、--save-exact は正確なバージョンのみを依存の対象とするオプションです(npm install)。

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

インストールが完了すると、node_modules ディレクトリが作成されその中に関連パッケージがコピーされ、作業用のディレクトリは以下のような構成になります。

my-code-block
  ├── node_modules //関連パッケージ(モジュール)が入っているディレクトリ
  ├── package-lock.json  //自動的に生成されるファイル
  └── package.json //パッケージの設定ファイル

本番用のビルド(production ビルド)や開発モードをコマンドラインから実行できるように package.json の scripts フィールドにコマンド(npm script)を追加します(6〜9行目)。

package.json
{
  "name": "my-code-block",
  "version": "1.0.0",
  "description": "",
  "main": "build/index.js",
  "scripts": {
    "start": "wp-scripts start",
    "build": "wp-scripts build"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@wordpress/scripts": "^12.3.0"
  }
}

必要に応じて description や author を設定できます。また、start や build 以外にも format:js や lint:js、packages-update などのコマンドを追加することができます。

package.json
{
  "name": "my-code-block",
  "version": "0.1.0",
  "description": "My Code Prettify Block",
  "author": "WDL",
  "license": "GPL-2.0-or-later",
  "main": "build/index.js",
  "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"
  },
  "devDependencies": {
    "@wordpress/scripts": "^12.3.0"
  }
}

src フォルダを作成し、以下のファイルを追加します。

my-code-block 
├── node_modules 
├── package-lock.json
├── package.json
└── src  //開発用ディレクトリ(追加)
    ├── edit.js // edit 関数を記述したファイル(追加)
    ├── editor.scss //エディタ用のスタイル(追加)
    ├── index.js  // ブロック用スクリプト(追加)
    └── style.scss //フロントエンド及びエディタ用のスタイル(追加)

index.js では registerBlockType 関数でブロックを登録します。

この例では edit プロパティは Edit コンポーネントとして別ファイル(src/edit.js)に記述してインポートします。save プロパティでは PHP でレンダリングするので null を返します(ダイナミックブロック)。

src/index.js
import { registerBlockType } from '@wordpress/blocks';
import Edit from './edit';
import './style.scss';  

registerBlockType( 'wdl/my-code-block', {
  title: 'My Code Block', 
  description: 'Code Prettify Block (Syntax Highlighter) ', //説明(オプション)
  icon: 'smiley', //アイコン(オプション)
  category: 'common',
  edit: Edit,
  save: () => { return null },
});

edit.js は edit プロパティに指定する Edit コンポーネント(edit 関数)を記述します。

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

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

style.scss と editor.scss はこの時点では取り敢えず、自動的に付与されるクラス名を使って適当なスタイルを指定しておきます。

src/style.scss
.wp-block-wdl-my-code-block {
  color: #999;
  border: 1px solid #ccc;
  background-color: #EFEFEF;
  padding: 10px 20px;
}
src/editor.scss
.wp-block-wdl-my-code-block {
  text-decoration: underline;
}

npm run build を実行してビルドします。

ビルドを実行すると、build ディレクトリが作成されてその中にコンパイルされたファイルが出力されます。また、同時に index.asset.php という依存ファイルとバージョンの情報が記述されたアセットファイルも自動的に生成されます。

my-code-block 
├── block.json  
├── build //ビルドで出力されるファイルのディレクトリ
│   ├── index.asset.php //依存情報とファイルバージョンが記載されるファイル(自動生成)
│   ├── index.css  //editor.scss がビルドで変換された CSS
│   ├── index.js  //ビルドされたブロック用のスクリプト
│   └── style-index.css // style.scss がビルドで変換された CSS
├── node_modules 
├── package-lock.json
├── package.json 
└── src  //開発用ディレクトリ(この中のファイルを編集)
    ├── edit.js
    ├── editor.scss
    ├── index.js  
    └── style.scss

PHP でブロックを登録するファイル my-code-block.php を作成します。

my-code-block 
├── block.json  
├── build 
│   ├── index.asset.php 
│   ├── index.css  
│   ├── index.js  
│   └── style-index.css 
├── node_modules 
├── my-code-block.php  //追加
├── package-lock.json
├── package.json 
└── src  
    ├── edit.js
    ├── editor.scss
    ├── index.js  
    └── style.scss

my-code-block.php にはプラグインヘッダを記述し、register_block_type 関数を使って PHP 側でブロックを登録します。この例ではブロックを PHP 側でレンダリングするので、render_callback を指定して定義します。

my-code-block.php
<?php
/**
 * Plugin Name:     My Code Block
 * Description:     Code Prettify Block (Syntax Highlighter) – build step required.
 * Version:         0.1.0
 * Author:          WebDesignLeaves
 *
 * @package         wdl
 */

function wdl_my_code_block_init() {
  $dir = dirname( __FILE__ );
  
  //依存スクリプトの配列とバージョンが記述されたアセットファイルの読み込み
  $script_asset = require( "$dir/build/index.asset.php" );
  
  //ブロック用のスクリプトの登録
  wp_register_script(
    //スクリプトのハンドル名
    'wdl-my-code-block-editor',
    //スクリプトの URL
    plugins_url( 'build/index.js', __FILE__ ),
    //依存するスクリプト
    $script_asset['dependencies'],
    //スクリプトのバージョン
    $script_asset['version']
  );
  
  //エディタ用のスタイルの登録
  wp_register_style(
    'wdl-my-code-block-editor-style',
    plugins_url( 'build/index.css', __FILE__ ),
    array(),
    filemtime( "$dir/build/index.css" )
  );
  
  //フロントエンド及びエディタ用のスタイルの登録
  wp_register_style(
    'wdl-my-code-block-style',
    plugins_url( 'build/style-index.css', __FILE__ ),
    array(),
    filemtime( "$dir/build/style-index.css" )
  );
  
  //ブロックを登録
  register_block_type( 
    //名前空間/ブロック名
    'wdl/my-code-block', 
    //スクリプトやスタイルをブロックに関連付け
    array(
      'editor_script' => 'wdl-my-code-block-editor',
      'editor_style'  => 'wdl-my-code-block-editor-style',
      'style'         => 'wdl-my-code-block-style',
      //PHP でレンダリングするコールバック関数を指定
      'render_callback' => 'my_code_block_render',
    )
  );
}
add_action( 'init', 'wdl_my_code_block_init' );

// render_callback 関数の定義(取り敢えず div 要素で Hello from ... と出力)
function my_code_block_render($attributes, $content) {
  return '<div class="wp-block-wdl-my-code-block">Hello from PHP for Code Prettify !</div>';
}

プラグインの管理ページを開いて有効化します。

投稿にブロックを挿入すると、エディター画面では以下のように表示されます。

以下はフロントエンド側での表示です。

npm start を実行して開発モードにして作業をします。

開発モードを終了するには control + c を押します。

Code-Prettify

Google Code-Prettify を使うには CDN 経由で読み込みオートローダーを使うのが簡単ですが、このブロックでは Google Code-Prettify をダウンロードして使用します。

関連ページ:Google Code-Prettify の基本的な使い方

ダウンロードのリンク(https://github.com/google/code-prettify/raw/master/distrib/prettify-small.zip)から Code-Prettify をダウンロードして解凍します。

この例のブロックでは prettify.css、prettify.js(run_prettify.js)、lang-css.js(CSS 用言語ハンドラー)を使用します。また、オプションでスキンを指定できるように skin フォルダ内の CSS ファイルに記述されている内容をブロックのスタイルにコピーします(後述)。

解凍したフォルダをブロックのフォルダにコピーします。この例ではフォルダ名を code-prettify としています。

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

※ prettify.js の代わりに run_prettify.js を読み込めば PR.prettyPrint() を実行する必要はありませんが、プレビュー表示で useEffect を使って PR.prettyPrint() を別途実行するためこのようにしています。

init-prettify.js
window.addEventListener("load", function() {
  PR.prettyPrint();
});  

この時点では以下のような構成になっています。その他の言語ハンドラーは不要であれば削除しても問題ありませんが、将来的に特定の言語のハンドラーを追加することを考慮して残しています。

my-code-block 
├── block.json  
├── build 
│   ├── index.asset.php 
│   ├── index.css  
│   ├── index.js  
│   └── style-index.css 
├── code-prettify   //追加
│   ├── init-prettify.js //作成
│   ├── lang-css.js
│ ・・・中略(その他の言語ハンドラー)・・・
│   ├── prettify.css  
│   ├── prettify.js  
│   └── run_prettify.js
├── node_modules 
├── my-code-block.php
├── package-lock.json
├── package.json 
└── src  
    ├── edit.js
    ├── editor.scss
    ├── index.js  
    └── style.scss

スクリプトやスタイルの読み込み

code-prettify フォルダに保存した Code-Prettify のスクリプトとスタイルを PHP でブロックを登録するファイル(my-code-block.php)で wp_enqueue_scriptwp_enqueue_style を使って、enqueue_block_assets アクションで読み込みます。

以下を my-code-block.php に追加します。この例の場合、エディター側ではシンタックスハイライトは表示しないので ! is_admin() で判定してフロントエンド側でのみ読み込むようにします。

my-code-block.php
function add_my_code_block_scripts_and_styles() {
  $dir = dirname( __FILE__ );
  
  //管理画面以外(フロントエンド側でのみ読み込む)
  if(! is_admin()) {   
    //Code-Prettify の JavaScript ファイルの読み込み(エンキュー)
    wp_enqueue_script( 
      'code-prettify', 
      plugins_url( '/code-prettify/prettify.js', __FILE__ ), 
      array(),
      filemtime( "$dir/code-prettify/prettify.js" ),
      true
    );
    
    //CSS 用言語ハンドラーの 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
    );
    
    //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_my_code_block_scripts_and_styles');

スタイルのカスタマイズ

必要に応じてサイトや好みに合わせて Code-Prettify のスタイルをカスタマイズします。

このブロックではオプションでスキンを変更できるようにするため、クラスセレクタを使って skin フォルダの CSS ファイルの内容をコピーしています。スタイルは style.scss に SASS で記述できるのでスキンのスタイルはネストしています。

以下は一例です。指定するスタイルはテーマなどの設定により調整する必要があります。

スキンのスタイルは code-prettify/styles/ にミニファイされていないものがあるので、それを元にカスタマイズして使用することができます。また、ビルドする際に CSS もミニファイされるので、style.scss にはミニファイされていないものをコピーした方が作業しやすいです。

また、この例では基本のスタイル prettify.css の最後の部分(li の背景色)をコメントアウトしています。

prettify.css
.pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.clo,.opn,.pun{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.kwd,.tag,.typ{font-weight:700}.str{color:#060}.kwd{color:#006}.com{color:#600;font-style:italic}.typ{color:#404}.lit{color:#044}.clo,.opn,.pun{color:#440}.tag{color:#006}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none} 
/* コメントアウト li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee} */

基本的な表示

エディター画面では TextareaControl コンポーネントを使ってユーザがテキストエリアにコードのテキストを入力できるようにします。

入力された値は属性に保存します。この例のブロックでは PHP でレンダリングするので、属性は register_block_type 関数に設定します。

attributes を設定

テキストエリアに入力された値を保持するために attributes プロパティを追加し属性を設定します。

my-code-block.php の register_block_type 関数に attributes プロパティを追加し、属性 codeArea を設定します。入力される値は文字列なので type を string に、default は空文字列に設定します。

my-code-block.php
register_block_type( 
  'wdl/my-code-block', 
  array(
    'editor_script' => 'wdl-my-code-block-editor',
    'editor_style'  => 'wdl-my-code-block-editor-style',
    'style'         => 'wdl-my-code-block-style',
    'render_callback' => 'my_code_block_render',
    //属性を追加
    'attributes' => [
      //属性 codeArea を設定
      'codeArea' => [
        'type' => 'string', 
        'default' => '' 
      ],
    ],
  )
);

TextareaControl

TextareaControl はユーザがテキストを入力することができる textarea 要素を使ったコンポーネントです。以下のようなプロパティを設定することができます。

TextareaControl の主なプロパティ
プロパティ 説明
label このプロパティを指定すると、指定された値の文字列を使って label 要素が出力されます。
help このプロパティに文字列を指定するとヘルプテキストを出力します。
rows テキストエリアの行数を指定します。デフォルトは4です。
value この要素の値(表示される文字列)
onChange 入力の値が変更されたら呼び出される関数(イベントハンドラ)

edit 関数(edit.js)では TextareaControl コンポーネントをインポートしてコードを入力できるテキストエリアをレンダリングします。

props からクラス名(className)、属性(attributes)、属性を更新する関数(setAttributes)を分割代入で変数に受け取ります。

テキストエリアに入力される値が変更されると onChange プロパティの setAttributes メソッドで値(value)を更新します。

また、TextareaControl コンポーネントのデフォルトの行数は4なので、テキストエリアに入力されている値(attributes.codeArea)を改行文字で分割し、その数を行数に設定するようにしています。以下の場合、何も入力されていない状態では3行分の高さのテキストエリアを表示します。

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>
  );
}

render_callback を変更

この時点の render_callback 関数では div 要素で「Hello from ...」と出力するようになっていますが、入力された値を出力するように変更します。

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

行番号を表示する場合は、linenums というクラスを指定します。関連項目:Code-Prettify /コードの記述

<pre class="prettyprint">
//コードを記述
</pre>

以下が変更後の render_callback 関数です。

入力された値は属性 codeArea に保存されているので、PHP 側では $attributes['codeArea'] でアクセスできます。

テキストエリアに何も入力されていない場合(属性 codeArea が空文字列)は、何も表示しないので return します。

入力された値は esc_html() でエスケープ処理して出力する必要があります。

また、この例では prettyprint クラスを指定した pre 要素を、自動的に付与されるブロックのクラス wp-block-wdl-my-code-block で囲んでいます。このクラスは edit 関数では props 経由で className プロパティとして取得できますが、PHP 側では自分で記述します。

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

以下はエディター画面でテキストエリアにコードを入力した場合の例です。

以下はフロントエンド側の表示例です。

my-code-block.php のスタイルの読み込みで、例えば prettify.css の代わりに skin ディレクトリの sunburst.css を読み込めば以下のような表示になります。

wp_enqueue_style(
  'code-prettify-style-sunburst',
  plugins_url( '/code-prettify/skins/sunburst.css', __FILE__ ), 
  array(),
  filemtime( "$dir/code-prettify/skins/sunburst.css" )
);

my-code-block.php
<?php
/**
 * Plugin Name:     My Code Block
 * Description:     Code Prettify Block (Syntax Highlighter) – build step required.
 * Version:         0.1.0
 * Author:          WebDesignLeaves
 *
 * @package         wdl
 */

function wdl_my_code_block_init() {
  $dir = dirname( __FILE__ );
  
  //アセットファイルの読み込み
  $script_asset = require( "$dir/build/index.asset.php" );
  
  //ブロック用のスクリプトの登録
  wp_register_script(
    //スクリプトのハンドル名
    'wdl-my-code-block-editor',
    //スクリプトの URL
    plugins_url( 'build/index.js', __FILE__ ),
    //依存するスクリプト
    $script_asset['dependencies'],
    //スクリプトのバージョン
    $script_asset['version']
  );
  
  //エディタ用のスタイルの登録
  wp_register_style(
    'wdl-my-code-block-editor-style',
    plugins_url( 'build/index.css', __FILE__ ),
    array(),
    filemtime( "$dir/build/index.css" )
  );
  
  //フロントエンド及びエディタ用のスタイルの登録
  wp_register_style(
    'wdl-my-code-block-style',
    plugins_url( 'build/style-index.css', __FILE__ ),
    array(),
    filemtime( "$dir/build/style-index.css" )
  );
  
  //ブロックを登録
  register_block_type( 
    //名前空間/ブロック名
    'wdl/my-code-block', 
    //スクリプトやスタイルをブロックに関連付け
    array(
      'editor_script' => 'wdl-my-code-block-editor',
      'editor_style'  => 'wdl-my-code-block-editor-style',
      'style'         => 'wdl-my-code-block-style',
      //PHP でレンダリングするコールバック関数を指定
      'render_callback' => 'my_code_block_render',
      //
      'attributes' => [
        'codeArea' => [
          'type' => 'string', 
          'default' => '' 
        ],
      ],
    )
  );
}
add_action( 'init', 'wdl_my_code_block_init' );

// render_callback 関数の定義 
function my_code_block_render($attributes, $content) {
  
  //属性 codeArea が空なら何も表示しない
  if (empty($attributes['codeArea'])) {
    return '';
  }
  
  return '<div class="wp-block-wdl-my-code-block"><pre class="prettyprint linenums">'.esc_html($attributes['codeArea']).'</pre></div>';
}

//Code-Prettify のスクリプトやスタイルの読み込み
function add_my_code_block_scripts_and_styles() {
  $dir = dirname( __FILE__ );
  
  //管理画面以外(フロントエンド側でのみ読み込む)
  if(! is_admin()) {
    
    //Code-Prettify の JavaScript ファイルの読み込み(エンキュー)
    wp_enqueue_script( 
      'code-prettify', 
      plugins_url( '/code-prettify/prettify.js', __FILE__ ), 
      array(),
      filemtime( "$dir/code-prettify/prettify.js" ),
      true
    );
    
    //CSS 用言語ハンドラーの 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
    );
    
    //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_my_code_block_scripts_and_styles');

インスペクターの追加

行番号の表示・非表示や言語の指定、スキンの指定などをブロックごとにユーザが設定できるようにするためエディター画面にインスペクターを追加します。

属性を追加

インスペクターでユーザが設定した値を保持するために register_block_type で属性を追加します。

my-code-block.php
register_block_type( 
    'wdl/my-code-block', 
    array(
      'editor_script' => 'wdl-my-code-block-editor',
      'editor_style'  => 'wdl-my-code-block-editor-style',
      'style'         => 'wdl-my-code-block-style',
      'render_callback' => 'my_code_block_render',
      'attributes' => [
        'codeArea' => [
          'type' => 'string', 
          'default' => '' 
        ],  
        //以下の属性を追加
        //行番号の表示・非表示
        'linenums' => [
          'type' => 'boolean', 
          'default' => true 
        ],
        //行番号の開始番号
        'linenumsStart' => [
          'type' => 'number', 
          'default' => 1 
        ],
        //言語の指定
        'lang' => [
          'type' => 'string', 
          'default' => '' 
        ],
        //配置の指定
        'align' => [
          'type' => 'string', 
          'default' => '' 
        ],
        //max-width を指定するかどうか
        'maxWidthEnable' => [
          'type' => 'boolean', 
          'default' => false 
        ],
        //max-width の値
        'maxWidth' => [
          'type' => 'number', 
          'default' => 0 
        ],
        //スキン(一番良く使うものをデフォルトに指定)
        'skin' => [
          'type' => 'string', 
          'default' => 'desert' 
        ],
      ],
    )
  );

インスペクターの表示

edit.js ではインスペクターに表示する必要なコンポーネントをインポートします。

関連項目:インスペクター

edit.js
import { InspectorControls } from '@wordpress/block-editor';
import { TextareaControl, PanelBody, PanelRow, ToggleControl, SelectControl, TextControl, RangeControl, CheckboxControl  } from '@wordpress/components';

インスペクターの表示は getInspectorControls という関数を定義して表示するようにします(return ステートメント内に直接記述することもできます)。

開始する行番号を指定する TextControl コンポーネントは行番号を表示する場合(attributes.linenums が true の場合)に表示するようにしています。同様に max-width の値を設定する RangeControl は、attributes.maxWidthEnable が true の場合に表示するようにしています。

また、ブロックの配置は save 関数でレンダリングする場合は、registerBlockType で supports プロパティを指定して簡単にツールバーに表示できますが、この例の場合、PHP でレンダリングするのでインスペクターに項目を追加しています(もっと良い方法があるかも知れません)。

edit.js
const getInspectorControls = () => {
  return (
    <InspectorControls>
      <PanelBody
        title='シンタックスハイライト設定'
        initialOpen={true}
      >
        <PanelRow>   
          <ToggleControl 
            label={ attributes.linenums ? "行番号(表示)" : "行番号(非表示)" }
            checked={attributes.linenums}
            onChange={(val) => setAttributes({ linenums: val })}
          />
        </PanelRow>
        { attributes.linenums &&  //上記が true の場合に表示
          <PanelRow>   
            <TextControl 
              label="開始する行番号"
              type="number"
              value={ attributes.linenumsStart }
              onChange={ (val) => setAttributes({ linenumsStart: 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>
          <SelectControl
            label="lang"
            value={attributes.lang}
            options={[
              {label: "Default", value: ''},
              {label: "CSS", value: 'css'},
            ]}
            onChange={(val) => setAttributes({ lang: val })}
          />
        </PanelRow>
        <PanelRow>
          <CheckboxControl
            label="max-width を指定"
            checked={attributes.maxWidthEnable}
            onChange={(val) => setAttributes({ maxWidthEnable: val })}
            help="※ インラインスタイルで設定します"
          />
        </PanelRow>
        { attributes.maxWidthEnable &&  //上記が true の場合に表示
          <PanelRow>   
            <RangeControl
              label='max-width'
              value={attributes.maxWidth}
              onChange={(val) => setAttributes({ maxWidth: parseInt(val) })}
              min={300}
              max={1800}
              step={10}
              help="max-width を px で指定"
            />
          </PanelRow>
        }
        <PanelRow>
          <SelectControl
            label="skin"
            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>
  );
}
InputControl

開始する行番号の入力には TextControl を使用していますが、入力欄が大きい(横幅が広い)ので別のコンポーネントを探したところ InputControl というコンポーネントがあります。

InputControl コンポーネントはサイズも指定できますが、現時点では「This is an experimental component intended to (in time) merge with or replace TextControl.」とされていて将来的に TextControl に統合される可能性があります。

以下は TextControl の代わりに InspectorControls を使用する場合の例です。但し、experimental なため、インポートも通常のインポートではエラーになるので __experimentalInputControl as を指定する必要があります。

import { __experimentalInputControl as InputControl } from '@wordpress/components';
      
・・・中略・・・

const getInspectorControls = () => {
  return (
    <InspectorControls>
      <PanelBody
        title='シンタックスハイライト設定'
        initialOpen={true}
      >
        <PanelRow>   
          ・・・中略・・・
        </PanelRow>
        { attributes.linenums &&  
          <PanelRow>   
            <InputControl 
              label="開始する行番号"
              type="number"
              value={ attributes.linenumsStart }
              onChange={ (val) => setAttributes({ linenumsStart: parseInt(val) }) }
            />
          </PanelRow>
        }
        
・・・中略・・・
return ステートメント

return ステートメントでは、上記関数 getInspectorControls を使ってインスペクターを出力するので、配列でコンポーネントを指定します。配列内では記述順にレンダリングされ、return ステートメント内で関数を直接呼び出すことができます。

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

render_callback を更新

ユーザがインスペクターで設定した値を反映してブロックをレンダリングするように render_callback 関数を更新します。

my-code-block.php のブロックをレンダリングする関数 my_code_block_render() を以下のように変更します。

属性 align には配置の値が保持されているので、その値を使ってブロックの外側の div 要素にクラスを追加します。例えば、中央揃えが選択されている場合、$attributes['align'] の値は center なので、align をその前に付けて aligncenter というクラスを追加します。WordPress のデフォルトのテーマではこれらのクラスのスタイルが設定されていますが、テーマによっては対応するスタイルを設定する必要があります。

スキンも属性に保持されている値をブロックの外側の div 要素にクラスとして追加します。例えば desert というスキンが選択されていれば、style.scss で設定している .desert が適用されます。

max-width はインラインスタイルで外側の div 要素に指定しています。

行番号の linenums と言語の指定 lang-xxxx は prettyprint クラスが指定されている pre 要素に追加します。この例では言語の指定は CSS のみにしていますが、追加の言語を指定できるようにするには、その言語ハンドラーを別途 my-code-block.php で読み込む必要があります。

例えば配置を中央揃え、スキンを desert を選択し、max-width を 920px に設定し、行番号を7から開始して表示する場合は以下のようなマークアップを出力するようにします。

<div class="wp-block-wdl-my-code-block aligncenter desert" style="max-width:920px">
  <pre class="prettyprint linenums:7">コード</pre>
</div>

以下が変更後の render_callback 関数 my_code_block_render です。

my-code-block.php
function my_code_block_render($attributes, $content) {
  
  //属性 codeArea が空なら何も表示しない
  if (empty($attributes['codeArea'])) {
    return '';
  }
  
  //ブロックに追加するクラス
  $add_block_class = '';
  
  //配置
  if($attributes['align']) {
    $add_block_class .= ' align' . $attributes['align'];
  }
  
  //スキン
  if($attributes['skin']) {
    $add_block_class .= ' '.$attributes['skin'];
  }
  
  //ブロックに指定するインラインスタイル
  $add_style = '';
  
  // maxWidthEnable が true なら max-width をインラインスタイルで設定
  if($attributes['maxWidthEnable']) {
    $add_style = ' style="max-width:' . $attributes['maxWidth'] . 'px" ';
  }
  
  // ブロックの div 要素に追加のクラスとスタイルを指定
  $output = '<div class="wp-block-wdl-my-code-block' . $add_block_class . '" ' . $add_style .  '>';
  
  // pre 要素に追加するクラス
  $add_pre_class = '';
  
  // linenums が true なら行番号を表示するために linenums クラスを追加
  if($attributes['linenums']) {
    $add_pre_class = ' linenums';
    //行の開始番号が指定されていればその値を設定
    if($attributes['linenumsStart'] !== 1) {
      $add_pre_class .= ':' . $attributes['linenumsStart'];
    }
  }
  
  // 言語が指定されていればそのクラス(lang-xxxx)を設定
  if($attributes['lang']) {
    $add_pre_class .= ' lang-' . ($attributes['lang']);
  }
  
  //$output = '<div class="wp-block-wdl-my-code-block"' . $add_style . '>';
  $output .= '<pre class="prettyprint' . $add_pre_class . '">';
  
  //入力された値をエスケープ処理
  $output .=  esc_html($attributes['codeArea']).'</pre></div>';
  
  // 最終的なマークアップを返す
  return $output;
}

以下はここまでの時点での各ファイルです。

src/index.js
import { registerBlockType } from '@wordpress/blocks';
import Edit from './edit';
import './style.scss';  

registerBlockType( 'wdl/my-code-block', {
  title: 'My Code Block', 
  description: 'Code Prettify Block (Syntax Highlighter) ',
  icon: 'smiley',
  category: 'common',
  edit: Edit,
  save: () => { return null },
});
src/edit.js
import { InspectorControls } from '@wordpress/block-editor';
import { TextareaControl, PanelBody, PanelRow, ToggleControl, SelectControl, TextControl, RangeControl, CheckboxControl  } from '@wordpress/components';
import './editor.scss';

export default function Edit( props ) {
  const { className, attributes, setAttributes } = props;
    
  //インスペクターを表示する関数
  const getInspectorControls = () => {
    return (
      <InspectorControls>
        <PanelBody
          title='シンタックスハイライト設定'
          initialOpen={true}
        >
          <PanelRow>   
            <ToggleControl 
              label={ attributes.linenums ? "行番号(表示)" : "行番号(非表示)" }
              checked={attributes.linenums}
              onChange={(val) => setAttributes({ linenums: val })}
            />
          </PanelRow>
          { attributes.linenums &&  //上記が true の場合に表示
            <PanelRow>   
              <TextControl 
                label="開始する行番号"
                type="number"
                value={ attributes.linenumsStart }
                onChange={ (val) => setAttributes({ linenumsStart: 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>
            <SelectControl
              label="lang"
              value={attributes.lang}
              options={[
                {label: "Default", value: ''},
                {label: "CSS", value: 'css'},
              ]}
              onChange={(val) => setAttributes({ lang: val })}
            />
          </PanelRow>
          <PanelRow>
            <CheckboxControl
              label="max-width を指定"
              checked={attributes.maxWidthEnable}
              onChange={(val) => setAttributes({ maxWidthEnable: val })}
              help="※ インラインスタイルで設定します"
            />
          </PanelRow>
          { attributes.maxWidthEnable && //上記が true の場合に表示
            <PanelRow>   
              <RangeControl
                label='max-width'
                value={attributes.maxWidth}
                onChange={(val) => setAttributes({ maxWidth: parseInt(val) })}
                min={300}
                max={1800}
                step={10}
                help="max-width を px で指定"
              />
            </PanelRow>
          }
          <PanelRow>
            <SelectControl
              label="skin"
              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>
    );
  }
                       
  let codeAreaRows = attributes.codeArea.split(/\r|\r\n|\n/).length > 3 ? attributes.codeArea.split(/\r|\r\n|\n/).length : 3;
                
  return (   
    //配列を指定
    [
      getInspectorControls(),  //インスペクター
      <div className={ className }>
        <TextareaControl 
          label="Code:" 
          value={ attributes.codeArea }
          onChange={ (code) => setAttributes({ codeArea: code }) }
          rows={ codeAreaRows }
        />
      </div>
    ]
  );
}
import { InspectorControls } from '@wordpress/block-editor';
import { TextareaControl, PanelBody, PanelRow, ToggleControl, SelectControl, TextControl, RangeControl, CheckboxControl  } from '@wordpress/components';
import './editor.scss';

export default function Edit( props ) {
  const { className, attributes, setAttributes } = props;
    
  //インスペクターを表示する関数
  const getInspectorControls = () => {
    return (
      <InspectorControls>
        <PanelBody
          title='シンタックスハイライト設定'
          initialOpen={true}
        >
          <PanelRow>   
            <ToggleControl 
              label={ attributes.linenums ? "行番号(表示)" : "行番号(非表示)" }
              checked={attributes.linenums}
              onChange={(val) => setAttributes({ linenums: val })}
            />
          </PanelRow>
          { attributes.linenums &&  //上記が true の場合に表示
            <PanelRow>   
              <TextControl 
                label="開始する行番号"
                type="number"
                value={ attributes.linenumsStart }
                onChange={ (val) => setAttributes({ linenumsStart: 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>
            <SelectControl
              label="lang"
              value={attributes.lang}
              options={[
                {label: "Default", value: ''},
                {label: "CSS", value: 'css'},
              ]}
              onChange={(val) => setAttributes({ lang: val })}
            />
          </PanelRow>
          <PanelRow>
            <CheckboxControl
              label="max-width を指定"
              checked={attributes.maxWidthEnable}
              onChange={(val) => setAttributes({ maxWidthEnable: val })}
              help="※ インラインスタイルで設定します"
            />
          </PanelRow>
          { attributes.maxWidthEnable && //上記が true の場合に表示
            <PanelRow>   
              <RangeControl
                label='max-width'
                value={attributes.maxWidth}
                onChange={(val) => setAttributes({ maxWidth: parseInt(val) })}
                min={300}
                max={1800}
                step={10}
                help="max-width を px で指定"
              />
            </PanelRow>
          }
          <PanelRow>
            <SelectControl
              label="skin"
              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>
    );
  }

  let codeAreaRows = attributes.codeArea.split(/\r|\r\n|\n/).length > 3 ? attributes.codeArea.split(/\r|\r\n|\n/).length : 3;

  return (   
    [
      getInspectorControls(),  
      <div className={ className }>
        <TextareaControl 
          label="Code:" 
          value={ attributes.codeArea }
          onChange={ (code) => setAttributes({ codeArea: code }) }
          rows={ codeAreaRows }
        />
      </div>
    ]
  );
}
src/style.scss
.entry-content .wp-block-wdl-my-code-block {
  width: 100%;
}

.wp-block-wdl-my-code-block pre.prettyprint {
  font-family: Monaco, Menlo, Consolas, 'Courier New', Courier, monospace, sans-serif;
  font-size: 16px;
  padding: 10px;
}  
 
.wp-block-wdl-my-code-block .prettyprint ol{
  margin-left: 10px;
  white-space: pre-wrap;  /* 折り返し */
}
 
.wp-block-wdl-my-code-block .prettyprint ol.linenums > li {
  list-style-type: decimal;  /* 行番号を1行ずつ表示 */
  border-left:solid 1px #EBB15E;  /* 行番号との区切り線 */
  padding-left: 1rem; /* 間隔の調整 */
  margin: 0 2rem; /* 間隔の調整 */
}

.wp-block-wdl-my-code-block .prettyprint ol.linenums > li::marker {
  color: #aaa; /* 行番号の色 */
}

.desert {
  /* desert.css をコピー */
  pre .atn,pre .kwd,pre .tag{font-weight:700}pre.prettyprint{display:block;background-color:#333}pre .nocode{background-color:none;color:#000}pre .str{color:#ffa0a0}pre .kwd{color:khaki}pre .com{color:#87ceeb}pre .typ{color:#98fb98}pre .lit{color:#cd5c5c}pre .pln,pre .pun{color:#fff}pre .tag{color:khaki}pre .atn{color:#bdb76b}pre .atv{color:#ffa0a0}pre .dec{color:#98fb98}ol.linenums{margin-top:0;margin-bottom:0;color:#AEAEAE}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}@media print{pre.prettyprint{background-color:none}code .str,pre .str{color:#060}code .kwd,pre .kwd{color:#006;font-weight:700}code .com,pre .com{color:#600;font-style:italic}code .typ,pre .typ{color:#404;font-weight:700}code .lit,pre .lit{color:#044}code .pun,pre .pun{color:#440}code .pln,pre .pln{color:#000}code .tag,pre .tag{color:#006;font-weight:700}code .atn,pre .atn{color:#404}code .atv,pre .atv{color:#060}}
}

.sunburst {
  /* sunburst.css をコピー */
  code .str,pre .str{color:#65B042}code .kwd,pre .kwd{color:#E28964}code .com,pre .com{color:#AEAEAE;font-style:italic}code .typ,pre .typ{color:#89bdff}code .lit,pre .lit{color:#3387CC}code .pln,code .pun,pre .pln,pre .pun{color:#fff}code .tag,pre .tag{color:#89bdff}code .atn,pre .atn{color:#bdb76b}code .atv,pre .atv{color:#65B042}code .dec,pre .dec{color:#3387CC}code.prettyprint,pre.prettyprint{background-color:#000;border-radius:8px}pre.prettyprint{width:95%;margin:1em auto;padding:1em;white-space:pre-wrap}ol.linenums{margin-top:0;margin-bottom:0;color:#AEAEAE}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}@media print{code .str,pre .str{color:#060}code .kwd,pre .kwd{color:#006;font-weight:700}code .com,pre .com{color:#600;font-style:italic}code .typ,pre .typ{color:#404;font-weight:700}code .lit,pre .lit{color:#044}code .pun,pre .pun{color:#440}code .pln,pre .pln{color:#000}code .tag,pre .tag{color:#006;font-weight:700}code .atn,pre .atn{color:#404}code .atv,pre .atv{color:#060}}
}

.doxy {
  /* doxy.css をコピー */
  a,code.prettyprint a,pre.prettyprint a{text-decoration:none}code .str,pre .str{color:#fec243}code .kwd,pre .kwd{color:#8470FF}code .com,pre .com{color:#32cd32;font-style:italic}code .typ,pre .typ{color:#6ecbcc}code .lit,pre .lit{color:#d06}code .pun,pre .pun{color:#8B8970}code .pln,pre .pln{color:#f0f0f0}code .tag,pre .tag{color:#9c9cff}code .htm,pre .htm{color:plum}code .xsl,pre .xsl{color:#d0a0d0}code .atn,pre .atn{color:#46eeee;font-weight:400}code .atv,pre .atv{color:#EEB4B4}code .dec,pre .dec{color:#3387CC}code.prettyprint,pre.prettyprint{font-family:'Droid Sans Mono','CPMono_v07 Bold','Droid Sans';font-weight:700;font-size:16px;background-color:#0f0f0f;-moz-border-radius:8px;-webkit-border-radius:8px;-o-border-radius:8px;-ms-border-radius:8px;-khtml-border-radius:8px;border-radius:8px}pre.prettyprint{width:95%;margin:1em auto;padding:1em;white-space:pre-wrap}ol.linenums{margin-top:0;margin-bottom:0;color:#8B8970}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}@media print{code.prettyprint,pre.prettyprint{background-color:#fff}code .str,pre .str{color:#088}code .kwd,pre .kwd{color:#006;font-weight:700}code .com,pre .com{color:#oc3;font-style:italic}code .typ,pre .typ{color:#404;font-weight:700}code .lit,pre .lit{color:#044}code .pun,pre .pun{color:#440}code .pln,pre .pln{color:#000}code .tag,pre .tag{color:#b66ff7;font-weight:700}code .htm,code .xsl,pre .htm,pre .xsl{color:#606;font-weight:700}code .atn,pre .atn{color:#c71585;font-weight:400}code .atv,pre .atv{color:#088;font-weight:400}}
}

.sons-of-obsidian {
  /* sons-of-obsidian.css をコピー */
  .str{color:#EC7600}.kwd{color:#93C763}.com{color:#66747B}.typ{color:#678CB1}.lit{color:#FACD22}.pln,.pun{color:#F1F2F3}.tag{color:#8AC763}.atn{color:#E0E2E4}.atv{color:#EC7600}.dec{color:purple}pre.prettyprint{border:0 solid #888}ol.linenums{margin-top:0;margin-bottom:0}.prettyprint{background:#000}li.L0,li.L1,li.L2,li.L3,li.L4,li.L5,li.L6,li.L7,li.L8,li.L9{color:#555;list-style-type:decimal}li.L1,li.L3,li.L5,li.L7,li.L9{background:#111}@media print{.kwd,.tag,.typ{font-weight:700}.str{color:#060}.kwd{color:#006}.com{color:#600;font-style:italic}.typ{color:#404}.lit{color:#044}.pun{color:#440}.pln{color:#000}.tag{color:#006}.atn{color:#404}.atv{color:#060}}
}
src/editor.scss
.components-textarea-control__input {
  font-size: 16px;
  padding: 10px;
  background: #efefef;
}

ファイル名を表示

TextControl コンポーネントを使ってファイル名を入力できるようにする例です。

適宜スタイルを設定すれば以下のようにタブのような表示にすることもできます。

register_block_type にファイル名を保存する属性 fileName を追加します。

my-code-block.php
//ファイル名を保存する属性を追加
'fileName' => [
  'type' => 'string', 
  'default' => '' 
],

edit 関数の return ステートメントで TextControl コンポーネントを使ってファイル名を入力する input 要素をレンダリングします。その際にスタイルを適用しやすいようにクラス名(filename)を設定します。

edit.js
return (   
  //配列を指定                   
  [
    getInspectorControls(),  
    <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>
  ]
);

editor.scss でエディター画面での input 要素のスタイル(最大幅や文字サイズなど)を指定します。

editor.scss
.filename input {
  max-width: 300px;
  margin-left: 10px;
  font-size: 16px;
}

render_callback 関数を更新します。

入力されたファイル名は $attributes['fileName'] に入っているので、その値が空でなければ(ファイル名が指定されていれば)外側の div 要素に filename_wrapper クラスを追加します。

そして、入力されたファイル名($attributes['fileName'])をエスケープ処理して file_name クラスを付与した p 要素で出力します。

例えば以下のようなマークアップを出力するようにします。

<div class="wp-block-wdl-my-code-block aligncenter desert filename_wrapper" style="max-width:920px">
  <p class="file_name">ファイル名</p>
  <pre class="prettyprint linenums:7">コード</pre>
</div>

以下が変更後の render_callback 関数 my_code_block_render です。

my-code-block.php
function my_code_block_render($attributes, $content) {
  
  if (empty($attributes['codeArea'])) {
    return '';
  }
  
  $add_block_class = '';
  
  if($attributes['align']) {
    $add_block_class .= ' align' . $attributes['align'];
  }
  
  if($attributes['skin']) {
    $add_block_class .= ' '.$attributes['skin'];
  }
  
  //ファイル名が指定されていれば filename_wrapper クラスを追加
  if($attributes['fileName']) {
    $add_block_class .= ' filename_wrapper';
  }
  
  $add_style = '';
  
  if($attributes['maxWidthEnable']) {
    $add_style = ' style="max-width:' . $attributes['maxWidth'] . 'px" ';
  }
  
  $output = '<div class="wp-block-wdl-my-code-block' . $add_block_class . '" ' . $add_style .  '>';
  
  //ファイル名の要素を格納する変数
  $file_name = '';
  
  //ファイル名が指定されていればファイル名を p 要素で出力
  if($attributes['fileName']) {
    $file_name = '<p class="file_name">' . esc_html($attributes['fileName']) . '</p>';
    $output .= $file_name;
  }
  
  $add_pre_class = '';
  
  if($attributes['linenums']) {
    $add_pre_class = ' linenums';
    if($attributes['linenumsStart'] !== 1) {
      $add_pre_class .= ':' . $attributes['linenumsStart'];
    }
  }
  
  if($attributes['lang']) {
    $add_pre_class .= ' lang-' . ($attributes['lang']);
  }
  
  $output .= '<pre class="prettyprint' . $add_pre_class . '">';
  
  $output .=  esc_html($attributes['codeArea']).'</pre></div>';
  
  return $output;
}

必要に応じてスタイルを調整します。以下はファイル名をタブのように表示するスタイルの例です。

ファイル名を表示する p 要素(p.file_name)を top で位置を調整していますが環境(テーマのスタイル)に合わせて調整する必要がるかと思います。

また、スキンの各スタイルに合わせて調整も必要です。以下では角丸などの設定を追加しています。スキンによっては pre 要素の幅を 95% に設定してあるものがあるので以下では削除しています。

index.js
import { registerBlockType } from '@wordpress/blocks';
import Edit from './edit';
import './style.scss';  

registerBlockType( 'wdl/my-code-block', {
  title: 'My Code Block', 
  description: 'Code Prettify Block (Syntax Highlighter) ',
  icon: 'smiley',
  category: 'common',
  edit: Edit,
  save: () => { return null },
});
edit.js
import { InspectorControls } from '@wordpress/block-editor';
import { TextareaControl, PanelBody, PanelRow, ToggleControl, SelectControl, TextControl, RangeControl, CheckboxControl } from '@wordpress/components';
import './editor.scss';

export default function Edit( props ) {
  const { className, attributes, setAttributes } = props;
    
  //インスペクターを表示する関数
  const getInspectorControls = () => {
    return (
      <InspectorControls>
        <PanelBody
          title='シンタックスハイライト設定'
          initialOpen={true}
        >
          <PanelRow>   
            <ToggleControl 
              label={ attributes.linenums ? "行番号(表示)" : "行番号(非表示)" }
              checked={attributes.linenums}
              onChange={(val) => setAttributes({ linenums: val })}
            />
          </PanelRow>
          { attributes.linenums &&  //上記が true の場合に表示
            <PanelRow>   
              <TextControl 
                label="開始する行番号"
                type="number"
                value={ attributes.linenumsStart }
                onChange={ (val) => setAttributes({ linenumsStart: 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>
            <SelectControl
              label="lang"
              value={attributes.lang}
              options={[
                {label: "Default", value: ''},
                {label: "CSS", value: 'css'},
              ]}
              onChange={(val) => setAttributes({ lang: val })}
            />
          </PanelRow>
          <PanelRow>
            <CheckboxControl
              label="max-width を指定"
              checked={attributes.maxWidthEnable}
              onChange={(val) => setAttributes({ maxWidthEnable: val })}
              help="※ インラインスタイルで設定します"
            />
          </PanelRow>
          { attributes.maxWidthEnable && //上記が true の場合に表示
            <PanelRow>   
              <RangeControl
                label='max-width'
                value={attributes.maxWidth}
                onChange={(val) => setAttributes({ maxWidth: parseInt(val) })}
                min={300}
                max={1800}
                step={10}
                help="max-width を px で指定"
              />
            </PanelRow>
          }
          <PanelRow>
            <SelectControl
              label="skin"
              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>
    );
  }

  let codeAreaRows = attributes.codeArea.split(/\r|\r\n|\n/).length > 3 ? attributes.codeArea.split(/\r|\r\n|\n/).length : 3;

  return (   
    //配列を指定                   
    [
      getInspectorControls(),  
      <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>
    ]
  );
}
my-code-block.php
<?php
/**
 * Plugin Name:     My Code Block
 * Description:     Code Prettify Block (Syntax Highlighter) – build step required.
 * Version:         0.1.0
 * Author:          WebDesignLeaves
 *
 * @package         wdl
 */

function wdl_my_code_block_init() {
  $dir = dirname( __FILE__ );
  
  //アセットファイルの読み込み
  $script_asset = require( "$dir/build/index.asset.php" );
  
  //ブロック用のスクリプトの登録
  wp_register_script(
    //スクリプトのハンドル名
    'wdl-my-code-block-editor',
    //スクリプトの URL
    plugins_url( 'build/index.js', __FILE__ ),
    //依存するスクリプト
    $script_asset['dependencies'],
    //スクリプトのバージョン
    $script_asset['version']
  );
  
  //エディタ用のスタイルの登録
  wp_register_style(
    'wdl-my-code-block-editor-style',
    plugins_url( 'build/index.css', __FILE__ ),
    array(),
    filemtime( "$dir/build/index.css" )
  );
  
  //フロントエンド及びエディタ用のスタイルの登録
  wp_register_style(
    'wdl-my-code-block-style',
    plugins_url( 'build/style-index.css', __FILE__ ),
    array(),
    filemtime( "$dir/build/style-index.css" )
  );
  
  //ブロックを登録
  register_block_type( 
    //名前空間/ブロック名
    'wdl/my-code-block', 
    //スクリプトやスタイルをブロックに関連付け
    array(
      'editor_script' => 'wdl-my-code-block-editor',
      'editor_style'  => 'wdl-my-code-block-editor-style',
      'style'         => 'wdl-my-code-block-style',
      //PHP でレンダリングするコールバック関数を指定
      'render_callback' => 'my_code_block_render',
      //属性を設定
      'attributes' => [
        // 入力された値
        'codeArea' => [
          'type' => 'string', 
          'default' => '' 
        ],  
        //行番号の表示・非表示
        'linenums' => [
          'type' => 'boolean', 
          'default' => true 
        ],
        //行番号の開始番号
        'linenumsStart' => [
          'type' => 'number', 
          'default' => 1 
        ],
        //言語の指定
        'lang' => [
          'type' => 'string', 
          'default' => '' 
        ],
        //配置の指定
        'align' => [
          'type' => 'string', 
          'default' => '' 
        ],
        //max-width を指定するかどうか
        'maxWidthEnable' => [
          'type' => 'boolean', 
          'default' => false 
        ],
        //max-width の値
        'maxWidth' => [
          'type' => 'number', 
          'default' => 0 
        ],
        //スキン 
        'skin' => [
          'type' => 'string', 
          'default' => 'desert' 
        ],
        //ファイル名 
        'fileName' => [
          'type' => 'string', 
          'default' => '' 
        ],
      ],
    )
  );
}
add_action( 'init', 'wdl_my_code_block_init' );

// render_callback 関数の定義 
function my_code_block_render($attributes, $content) {
  
  //属性 codeArea が空なら何も表示しない
  if (empty($attributes['codeArea'])) {
    return '';
  }
  
  //ブロックに追加するクラス
  $add_block_class = '';
  
  //配置
  if($attributes['align']) {
    $add_block_class .= ' align' . $attributes['align'];
  }
  
  //スキン
  if($attributes['skin']) {
    $add_block_class .= ' '.$attributes['skin'];
  }
  
  //ファイル名が指定されていれば filename_wrapper クラスを追加
  if($attributes['fileName']) {
    $add_block_class .= ' filename_wrapper';
  }
  
  //ブロックに指定するインラインスタイル
  $add_style = '';
  
  // maxWidthEnable が true なら max-width をインラインスタイルで設定
  if($attributes['maxWidthEnable']) {
    $add_style = ' style="max-width:' . $attributes['maxWidth'] . 'px" ';
  }
  
  // ブロックの div 要素に追加のクラスとスタイルを指定
  $output = '<div class="wp-block-wdl-my-code-block' . $add_block_class . '" ' . $add_style .  '>';
  
  //ファイル名の要素を格納する変数
  $file_name = '';
  
  //ファイル名が指定されていればファイル名を p 要素で出力
  if($attributes['fileName']) {
    $file_name = '<p class="file_name">' . esc_html($attributes['fileName']) . '</p>';
    $output .= $file_name;
  }
  
  // pre 要素に追加するクラス
  $add_pre_class = '';
  
  // linenums が true なら行番号を表示するために linenums クラスを追加
  if($attributes['linenums']) {
    $add_pre_class = ' linenums';
    //行の開始番号が指定されていればその値を設定
    if($attributes['linenumsStart'] !== 1) {
      $add_pre_class .= ':' . $attributes['linenumsStart'];
    }
  }
  
  // 言語が指定されていればそのクラス(lang-xxxx)を設定
  if($attributes['lang']) {
    $add_pre_class .= ' lang-' . ($attributes['lang']);
  }
  
  //$output = '<div class="wp-block-wdl-my-code-block"' . $add_style . '>';
  $output .= '<pre class="prettyprint' . $add_pre_class . '">';
  
  //入力された値をエスケープ処理
  $output .=  esc_html($attributes['codeArea']).'</pre></div>';
  
  // 最終的なマークアップを返す
  return $output;
}

//Code-Prettify のスクリプトやスタイルの読み込み
function add_my_code_block_scripts_and_styles() {
  $dir = dirname( __FILE__ );
  
  //管理画面以外(フロントエンド側でのみ読み込む)
  if(! is_admin()) {
    
    //Code-Prettify の JavaScript ファイルの読み込み(エンキュー)
    wp_enqueue_script( 
      'code-prettify', 
      plugins_url( '/code-prettify/prettify.js', __FILE__ ), 
      array(),
      filemtime( "$dir/code-prettify/prettify.js" ),
      true
    );
    
    //CSS 用言語ハンドラーの 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
    );
    
    //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_my_code_block_scripts_and_styles');
editor.scss
/* textarea 要素 */
.components-textarea-control__input {
  font-size: 16px;
  padding: 10px;
}

.filename input {
  max-width: 300px;
  margin-left: 10px;
  font-size: 16px;
}
style.scss
.wp-block-wdl-my-code-block  {
  position: relative;
}

.wp-block-wdl-my-code-block p.file_name {
  margin: 0;
  display: inline-block;
  font-size: 15px;
  position: relative;
  top: 2px;  /* 要調整 */ 
  padding: 2px 5px !important;  /* 追加 */  
}

.wp-block-wdl-my-code-block pre.prettyprint {
  font-family: Monaco, Menlo, Consolas, 'Courier New', Courier, monospace, sans-serif;
  font-size: 16px;
  padding: 10px;
  margin-top: 0 !important;  /* 追加 */  
}  
 
.wp-block-wdl-my-code-block .prettyprint ol{
  margin-left: 10px;
  white-space: pre-wrap;  /* 折り返し */
}
 
.wp-block-wdl-my-code-block .prettyprint ol.linenums > li {
  list-style-type: decimal;  /* 行番号を1行ずつ表示 */
  border-left:solid 1px #EBB15E;  /* 行番号との区切り線 */
  padding-left: 1rem; /* 間隔の調整 */
  margin: 0 2rem; /* 間隔の調整 */
}

.wp-block-wdl-my-code-block .prettyprint ol.linenums > li::marker {
  color: #aaa; /* 行番号の色 */
  font-size: 13px;
}

.desert {
  /* desert.css をコピー */
  pre.prettyprint { display: block; background-color: #333 }
  pre .nocode { background-color: none; color: #000 }
  pre .str { color: #ffa0a0 } /* string  - pink */
  pre .kwd { color: #f0e68c; font-weight: bold }
  pre .com { color: #87ceeb } /* comment - skyblue */
  pre .typ { color: #98fb98 } /* type    - lightgreen */
  pre .lit { color: #cd5c5c } /* literal - darkred */
  pre .pun { color: #fff }    /* punctuation */
  pre .pln { color: #fff }    /* plaintext */
  pre .tag { color: #f0e68c; font-weight: bold } /* html/xml tag    - lightyellow */
  pre .atn { color: #bdb76b; font-weight: bold } /* attribute name  - khaki */
  pre .atv { color: #ffa0a0 } /* attribute value - pink */
  pre .dec { color: #98fb98 } /* decimal         - lightgreen */
  /* Specify class=linenums on a pre to get line numbering */
  ol.linenums { margin-top: 0; margin-bottom: 0; color: #AEAEAE } /* IE indents via margin-left */
  li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8 { list-style-type: none }
  /* Alternate shading for lines */
  li.L1,li.L3,li.L5,li.L7,li.L9 { }
  @media print {
    pre.prettyprint { background-color: none }
    pre .str, code .str { color: #060 }
    pre .kwd, code .kwd { color: #006; font-weight: bold }
    pre .com, code .com { color: #600; font-style: italic }
    pre .typ, code .typ { color: #404; font-weight: bold }
    pre .lit, code .lit { color: #044 }
    pre .pun, code .pun { color: #440 }
    pre .pln, code .pln { color: #000 }
    pre .tag, code .tag { color: #006; font-weight: bold }
    pre .atn, code .atn { color: #404 }
    pre .atv, code .atv { color: #060 }
  }
  /* 追加 */  
  p.file_name {background-color:#333; color: #eee; border: 1px solid #888; border-bottom: none;}  
}

.sunburst {
  /* sunburst.css をコピー */
  pre .str, code .str { color: #65B042; } /* string  - green */
  pre .kwd, code .kwd { color: #E28964; } /* keyword - dark pink */
  pre .com, code .com { color: #AEAEAE; font-style: italic; } /* comment - gray */
  pre .typ, code .typ { color: #89bdff; } /* type - light blue */
  pre .lit, code .lit { color: #3387CC; } /* literal - blue */
  pre .pun, code .pun { color: #fff; } /* punctuation - white */
  pre .pln, code .pln { color: #fff; } /* plaintext - white */
  pre .tag, code .tag { color: #89bdff; } /* html/xml tag    - light blue */
  pre .atn, code .atn { color: #bdb76b; } /* html/xml attribute name  - khaki */
  pre .atv, code .atv { color: #65B042; } /* html/xml attribute value - green */
  pre .dec, code .dec { color: #3387CC; } /* decimal - blue */

  pre.prettyprint, code.prettyprint {
    background-color: #000;
    border-radius: 8px;
    border-top-left-radius:0; /* 追加 */
  }

  pre.prettyprint {
    /* 削除 width:95%;*/
    margin: 1em auto;
    padding: 1em;
    white-space: pre-wrap;
  }

  /* Specify class=linenums on a pre to get line numbering */
  ol.linenums { margin-top: 0; margin-bottom: 0; color: #AEAEAE; } /* IE indents via margin-left */
  li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8 { list-style-type: none }
  /* Alternate shading for lines */
  li.L1,li.L3,li.L5,li.L7,li.L9 { }

  @media print {
    pre .str, code .str { color: #060; }
    pre .kwd, code .kwd { color: #006; font-weight: bold; }
    pre .com, code .com { color: #600; font-style: italic; }
    pre .typ, code .typ { color: #404; font-weight: bold; }
    pre .lit, code .lit { color: #044; }
    pre .pun, code .pun { color: #440; }
    pre .pln, code .pln { color: #000; }
    pre .tag, code .tag { color: #006; font-weight: bold; }
    pre .atn, code .atn { color: #404; }
    pre .atv, code .atv { color: #060; }
  }
  /* 追加 */ 
  p.file_name {background-color:#000; color: #eee; border: 1px solid #888; border-bottom: none; border-top-left-radius:8px; border-top-right-radius:8px;}   
}

.doxy {
  /* doxy.css をコピー */
  pre .str, code .str { color: #fec243; } /* string  - eggyolk gold */
  pre .kwd, code .kwd { color: #8470FF; } /* keyword - light slate blue */
  pre .com, code .com { color: #32cd32; font-style: italic; } /* comment - green */
  pre .typ, code .typ { color: #6ecbcc; } /* type - turq green */
  pre .lit, code .lit { color: #d06; } /* literal - cherry red */
  pre .pun, code .pun { color: #8B8970;  } /* punctuation - lemon chiffon4  */
  pre .pln, code .pln { color: #f0f0f0; } /* plaintext - white */
  pre .tag, code .tag { color: #9c9cff; } /* html/xml tag  (bluey)  */
  pre .htm, code .htm { color: #dda0dd; } /* html tag  light purply*/
  pre .xsl, code .xsl { color: #d0a0d0; } /* xslt tag  light purply*/
  pre .atn, code .atn { color: #46eeee; font-weight: normal;} /* html/xml attribute name  - lt turquoise */
  pre .atv, code .atv { color: #EEB4B4; } /* html/xml attribute value - rosy brown2 */
  pre .dec, code .dec { color: #3387CC; } /* decimal - blue */

  a {
    text-decoration: none;
  }
  pre.prettyprint, code.prettyprint {
    font-family:'Droid Sans Mono','CPMono_v07 Bold','Droid Sans';
    font-weight: bold;
    font-size: 16px;  /* 変更 */
    background-color: #0f0f0f;
    -moz-border-radius: 8px;
    -webkit-border-radius: 8px;
    -o-border-radius: 8px;
    -ms-border-radius: 8px;
    -khtml-border-radius: 8px;
    border-radius: 8px;
    border-top-left-radius:0; /* 追加 */
  }  /*  background is black (well, just a tad less dark )  */

  pre.prettyprint {
    /* 削除  width:95%;*/
    margin: 1em auto;
    padding: 1em;
    white-space: pre-wrap;
  }

  pre.prettyprint a, code.prettyprint a {
     text-decoration:none;
  }
  /* Specify class=linenums on a pre to get line numbering; line numbers themselves are the same color as punctuation */
  ol.linenums { margin-top: 0; margin-bottom: 0; color: #8B8970; } /* IE indents via margin-left */
  li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8 { list-style-type: none }
  /* Alternate shading for lines */
  li.L1,li.L3,li.L5,li.L7,li.L9 { }

  /* print is mostly unchanged from default at present  */
  @media print {
    pre.prettyprint, code.prettyprint { background-color: #fff;  }
    pre .str, code .str { color: #088; }
    pre .kwd, code .kwd { color: #006; font-weight: bold; }
    pre .com, code .com { color: #0c3; font-style: italic; }
    pre .typ, code .typ { color: #404; font-weight: bold; }
    pre .lit, code .lit { color: #044; }
    pre .pun, code .pun { color: #440; }
    pre .pln, code .pln { color: #000; }
    pre .tag, code .tag { color: #b66ff7; font-weight: bold; }
    pre .htm, code .htm { color: #606; font-weight: bold; }
    pre .xsl, code .xsl { color: #606; font-weight: bold; }
    pre .atn, code .atn { color: #c71585;  font-weight: normal; }
    pre .atv, code .atv { color: #088;  font-weight: normal; }
  }
  
  /* 追加 */ 
  p.file_name {background-color:#0f0f0f; color: #eee; border: 1px solid #888; border-bottom: none; border-top-left-radius:8px; border-top-right-radius:8px; }   
}

.sons-of-obsidian {
  /* sons-of-obsidian.css をコピー */
  .str{
      color: #EC7600;
  }
  .kwd{
      color: #93C763;
  }
  .com{
      color: #66747B;
  }
  .typ{
      color: #678CB1;
  }
  .lit{
      color: #FACD22;
  }
  .pun{
      color: #F1F2F3;
  }
  .pln{
      color: #F1F2F3;
  }
  .tag{
      color: #8AC763;
  }
  .atn{
      color: #E0E2E4;
  }
  .atv{
      color: #EC7600;
  }
  .dec{
      color: purple;
  }
  pre.prettyprint{
      border: 0px solid #888;
  }
  ol.linenums{
      margin-top: 0;
      margin-bottom: 0;
  }
  .prettyprint {
      background: #000;
  }
  li.L0, li.L1, li.L2, li.L3, li.L4, li.L5, li.L6, li.L7, li.L8, li.L9{
      color: #555;
      list-style-type: decimal;
  }
  li.L1, li.L3, li.L5, li.L7, li.L9 {
      background: #111;
  }
  @media print
  {
    .str{
        color: #060;
    }
    .kwd{
        color: #006;
        font-weight: bold;
    }
    .com{
        color: #600;
        font-style: italic;
    }
    .typ{
        color: #404;
        font-weight: bold;
    }
    .lit{
        color: #044;
    }
    .pun{
        color: #440;
    }
    .pln{
        color: #000;
    }
    .tag{
        color: #006;
        font-weight: bold;
    }
    .atn{
        color: #404;
    }
    .atv{
        color: #060;
    }
  }
  /* 追加 */ 
  p.file_name {background-color:#000; color: #eee;}   
}

プレビューボタンの追加

エディター画面でプレビューボタンを追加してフロントエンド側での表示とほぼ同じ内容をプレビューできるようにする例です。

ブロックを選択するとツールバーにプレビューボタンを表示し、クリックするとプレビューを表示します。

ツールバーの鉛筆のアイコンをクリックするか「編集モード」をクリックすると編集画面に戻ります。

スクリプトとスタイルの読み込み

スクリプトとスタイルをエディター画面でも読み込むように my-code-block.php を変更します。

但し、Code-Prettify の PR.prettyPrint() の実行は edit 関数に記述して useEffect で実行するので、init-prettify.js はエディター画面では読み込まないようにします。

enqueue_block_assets フックはエディターとフロントエンドの両方でブロックのスクリプトとスタイル(ブロックのアセット)をエンキューするために使用できます。

my-code-block.php
function add_my_code_block_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
  );

  //CSS 用言語ハンドラーの 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
  );
  
  //エディター画面では読み込まない
  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_my_code_block_scripts_and_styles');

属性を追加

編集画面(編集モード)とプレビュー表示(モード)の切り替えをするために必要な属性を設定します。

register_block_type() の attributes に以下の属性を追加します(isEditMode が true の場合は編集モードになります)。

my-code-block.php
'isEditMode' => [
  'type' => 'boolean', 
  'default' => true
],

ツールバーの追加

ツールバーに編集モードとプレビューモードを切り替えるカスタムボタンを表示し、ボタンをクリックすると属性 isEditMode の値(真偽値)を反転させて編集モードとプレビューモードを切り替えます。

ボタンの表示は edit.js に getBlockControls という関数を定義して表示するようにします(return ステートメント内に直接記述することもできます)。

また、ツールバーに必要な BlockControls や Toolbar、シンタックスハイライトのレンダリングに必要な Fragment や 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 getBlockControls = () => {
  return (
    <BlockControls>
      <Toolbar>
        <Button
          //属性 isEditMode の値により表示するラベルを切り替え
          label={ attributes.isEditMode ? "Preview" : "Edit" }
          //属性 isEditMode の値により表示するアイコンを切り替え
          icon={ attributes.isEditMode ? "format-image" : "edit" }
          className="my-custom-button"
          //setAttributes を使って属性の値を更新(真偽値を反転)
          onClick={() => setAttributes({ isEditMode: !attributes.isEditMode })}
        />
      </Toolbar>
    </BlockControls>
  );
}

シンタックスハイライトのレンダリング

以下のようなエディター画面でシンタックスハイライトをレンダリングする関数を作成します。

内容的には PHP でのレンダリングと同じですが、Javascript(JSX)で記述します。そのため、インラインスタイルはオブジェクトで記述し、max-width はキャメルケースの maxWidth とする必要があります。

edit.js
const getPreview = () => {
  //テキストエリアに入力がなければ何も表示しない
  if (attributes.codeArea ==='') {
    return null;
  }                   
  //ブロックに追加するクラス
  let add_block_class = '';  
  //配置                  
  if(attributes.align) {
    add_block_class += ' align' + attributes.align;
  }
  //スキン
  if(attributes.skin) {
    add_block_class += ' ' + attributes.skin;
  }
  //ファイル名が指定されていれば filename_wrapper クラスを追加
  if(attributes.fileName) {
    add_block_class += ' filename_wrapper';
  }
  //ブロックに指定するインラインスタイル
  let add_style = {};
  if(attributes.maxWidthEnable) {
    add_style = {maxWidth: attributes.maxWidth};
  }        

  //pre 要素に追加するクラス
  let add_pre_class = '';                    
  if(attributes.linenums) {
    add_pre_class = ' linenums';
    //行の開始番号が指定されていればその値を設定
    if(attributes.linenumsStart !== 1) {
      add_pre_class += ':' + attributes.linenumsStart;
    }
  }                   
  // 言語が指定されていればそのクラス(lang-xxxx)を設定
  if(attributes.lang) {
    add_pre_class += ' lang-' + (attributes.lang);
  }     

  return (
    <div 
      className={"wp-block-wdl-my-code-block" + add_block_class}
      style={ add_style }
    >
      { attributes.fileName &&
        <p className="file_name">{ attributes.fileName }</p>
      }
      <pre className={"prettyprint"  + add_pre_class} >
       { attributes.codeArea }
      </pre>
    </div>  
  );
}

useEffect フック

init-prettify.js に記述してある PR.prettyPrint() はコンポーネントがレンダリングされた直後に実行する必要があるため、useEffect フックを使って実行します。

useEffect はレンダーの完了時に毎回実行されますが、第2引数を指定することによって、実行される条件を制御することができます。この例の場合は isEditMode の値が変更された場合にのみ実行すれば良いので、第2引数に attributes.isEditMode を指定しています。

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

return ステートメント

return ステートメントではプレビューボタンを関数を使ってレンダリングします。

そして isEditMode が true の場合はテキストエリアを表示し、isEditMode が false の場合は先に定義した getPreview() を使ってプレビューを表示します。その際、全体を Fragment で囲みます。

また、プレビューの際は「編集モード」というリンクを Button コンポーネントに isLink プロパティを指定して追加しています。

edit.js
return (   
  //配列を指定                   
  [
    getBlockControls(), //プレビューボタン
    getInspectorControls(),  
    <Fragment>   
      { attributes.isEditMode && // isEditMode が true の場合(編集モード)
        <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 && // isEditMode が false の場合(プレビューモード)
        [
          <Button 
            onClick={() => setAttributes({ isEditMode: true })}
            isLink 
            icon="edit"
          >編集モード
          </Button>,
          getPreview()
        ]
      }
    </Fragment>   
  ]
);
index.js
import { registerBlockType } from '@wordpress/blocks';
import Edit from './edit';
import './style.scss';  

registerBlockType( 'wdl/my-code-block', {
  title: 'My Code Block', 
  description: 'Code Prettify Block (Syntax Highlighter) ',
  icon: 'smiley',
  category: 'common',
  edit: Edit,
  save: () => { return null },
});
edit.js
import { InspectorControls, BlockControls } from '@wordpress/block-editor';
import { TextareaControl, PanelBody, PanelRow, ToggleControl, SelectControl, TextControl, RangeControl, CheckboxControl, Button, Toolbar } from '@wordpress/components';
import { Fragment, useEffect } from '@wordpress/element';
import './editor.scss';

export default function Edit( props ) {
  const { className, attributes, setAttributes } = props;
    
  //インスペクターを表示する関数
  const getInspectorControls = () => {
    return (
      <InspectorControls>
        <PanelBody
          title='シンタックスハイライト設定'
          initialOpen={true}
        >
          <PanelRow>   
            <ToggleControl 
              label={ attributes.linenums ? "行番号(表示)" : "行番号(非表示)" }
              checked={attributes.linenums}
              onChange={(val) => setAttributes({ linenums: val })}
            />
          </PanelRow>
          { attributes.linenums &&  //上記が true の場合に表示
            <PanelRow>   
              <TextControl 
                label="開始する行番号"
                type="number"
                value={ attributes.linenumsStart }
                onChange={ (val) => setAttributes({ linenumsStart: 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>
            <SelectControl
              label="lang"
              value={attributes.lang}
              options={[
                {label: "Default", value: ''},
                {label: "CSS", value: 'css'},
              ]}
              onChange={(val) => setAttributes({ lang: val })}
            />
          </PanelRow>
          <PanelRow>
            <CheckboxControl
              label="max-width を指定"
              checked={attributes.maxWidthEnable}
              onChange={(val) => setAttributes({ maxWidthEnable: val })}
              help="※ インラインスタイルで設定します"
            />
          </PanelRow>
          { attributes.maxWidthEnable && //上記が true の場合に表示
            <PanelRow>   
              <RangeControl
                label='max-width'
                value={attributes.maxWidth}
                onChange={(val) => setAttributes({ maxWidth: parseInt(val) })}
                min={300}
                max={1800}
                step={10}
                help="max-width を px で指定"
              />
            </PanelRow>
          }
          <PanelRow>
            <SelectControl
              label="skin"
              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 getBlockControls = () => {
    return (
      <BlockControls>
        <Toolbar>
          <Button
            //属性 isEditMode の値により表示するラベルを切り替え
            label={ attributes.isEditMode ? "Preview" : "Edit" }
            //属性 isEditMode の値により表示するアイコンを切り替え
            icon={ attributes.isEditMode ? "format-image" : "edit" }
            className="my-custom-button"
            //setAttributes を使って属性の値を更新(真偽値を反転)
            onClick={() => setAttributes({ isEditMode: !attributes.isEditMode })}
          />
        </Toolbar>
      </BlockControls>
    );
  }
                    
  const getPreview = () => {
    //テキストエリアに入力がなければ何も表示しない
    if (attributes.codeArea ==='') {
      return null;
    }                   
    //ブロックに追加するクラス
    let add_block_class = '';  
    //配置                  
    if(attributes.align) {
      add_block_class += ' align' + attributes.align;
    }
    //スキン
    if(attributes.skin) {
      add_block_class += ' ' + attributes.skin;
    }
    //ファイル名が指定されていれば filename_wrapper クラスを追加
    if(attributes.fileName) {
      add_block_class += ' filename_wrapper';
    }
    //ブロックに指定するインラインスタイル
    let add_style = {};
    if(attributes.maxWidthEnable) {
      add_style = {maxWidth: attributes.maxWidth};
    }        
                                       
    //pre 要素に追加するクラス
    let add_pre_class = '';                    
    if(attributes.linenums) {
      add_pre_class = ' linenums';
      //行の開始番号が指定されていればその値を設定
      if(attributes.linenumsStart !== 1) {
        add_pre_class += ':' + attributes.linenumsStart;
      }
    }                   
    // 言語が指定されていればそのクラス(lang-xxxx)を設定
    if(attributes.lang) {
      add_pre_class += ' lang-' + (attributes.lang);
    }     
                       
    return (
      <div 
        className={"wp-block-wdl-my-code-block" + add_block_class}
        style={ add_style }
      >
        { attributes.fileName &&
          <p className="file_name">{ attributes.fileName }</p>
        }
        <pre className={"prettyprint"  + add_pre_class} >
         { attributes.codeArea }
        </pre>
      </div>  
    );
  }

  useEffect(() => {
    PR.prettyPrint();
  }, [attributes.isEditMode]);
                       
  let codeAreaRows = attributes.codeArea.split(/\r|\r\n|\n/).length > 3 ? attributes.codeArea.split(/\r|\r\n|\n/).length : 3;
                       
  return (   
    //配列を指定                   
    [
      getBlockControls(), //プレビューボタン
      getInspectorControls(),  
      <Fragment>   
        { attributes.isEditMode && // isEditMode が true の場合(編集モード)
          <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 && // isEditMode が false の場合(プレビューモード)
          [
            <Button 
              onClick={() => setAttributes({ isEditMode: true })}
              isLink 
              icon="edit"
            >編集モード
            </Button>,
            getPreview()
          ]
        }
      </Fragment>   
    ]
  );
}
my-code-block.php
<?php
/**
 * Plugin Name:     My Code Block
 * Description:     Code Prettify Block (Syntax Highlighter) – build step required.
 * Version:         0.1.0
 * Author:          WebDesignLeaves
 *
 * @package         wdl
 */

function wdl_my_code_block_init() {
  $dir = dirname( __FILE__ );
  
  //アセットファイルの読み込み
  $script_asset = require( "$dir/build/index.asset.php" );
  
  //ブロック用のスクリプトの登録
  wp_register_script(
    //スクリプトのハンドル名
    'wdl-my-code-block-editor',
    //スクリプトの URL
    plugins_url( 'build/index.js', __FILE__ ),
    //依存するスクリプト
    $script_asset['dependencies'],
    //スクリプトのバージョン
    $script_asset['version']
  );
  
  //エディタ用のスタイルの登録
  wp_register_style(
    'wdl-my-code-block-editor-style',
    plugins_url( 'build/index.css', __FILE__ ),
    array(),
    filemtime( "$dir/build/index.css" )
  );
  
  //フロントエンド及びエディタ用のスタイルの登録
  wp_register_style(
    'wdl-my-code-block-style',
    plugins_url( 'build/style-index.css', __FILE__ ),
    array(),
    filemtime( "$dir/build/style-index.css" )
  );
  
  //ブロックを登録
  register_block_type( 
    //名前空間/ブロック名
    'wdl/my-code-block', 
    //スクリプトやスタイルをブロックに関連付け
    array(
      'editor_script' => 'wdl-my-code-block-editor',
      'editor_style'  => 'wdl-my-code-block-editor-style',
      'style'         => 'wdl-my-code-block-style',
      //PHP でレンダリングするコールバック関数を指定
      'render_callback' => 'my_code_block_render',
      //属性を設定
      'attributes' => [
        // 入力された値
        'codeArea' => [
          'type' => 'string', 
          'default' => '' 
        ],  
        //行番号の表示・非表示
        'linenums' => [
          'type' => 'boolean', 
          'default' => true 
        ],
        //行番号の開始番号
        'linenumsStart' => [
          'type' => 'number', 
          'default' => 1 
        ],
        //言語の指定
        'lang' => [
          'type' => 'string', 
          'default' => '' 
        ],
        //配置の指定
        'align' => [
          'type' => 'string', 
          'default' => '' 
        ],
        //max-width を指定するかどうか
        'maxWidthEnable' => [
          'type' => 'boolean', 
          'default' => false 
        ],
        //max-width の値
        'maxWidth' => [
          'type' => 'number', 
          'default' => 0 
        ],
        //スキン 
        'skin' => [
          'type' => 'string', 
          'default' => 'desert' 
        ],
        //ファイル名 
        'fileName' => [
          'type' => 'string', 
          'default' => '' 
        ],
        //編集モード・プレビューモードの判定に使う値
        'isEditMode' => [
          'type' => 'boolean', 
          'default' => true
        ],  
      ],
    )
  );
}
add_action( 'init', 'wdl_my_code_block_init' );

// render_callback 関数の定義 
function my_code_block_render($attributes, $content) {
  
  //属性 codeArea が空なら何も表示しない
  if (empty($attributes['codeArea'])) {
    return '';
  }
  
  //ブロックに追加するクラス
  $add_block_class = '';
  
  //配置
  if($attributes['align']) {
    $add_block_class .= ' align' . $attributes['align'];
  }
  
  //スキン
  if($attributes['skin']) {
    $add_block_class .= ' '.$attributes['skin'];
  }
  
  //ファイル名が指定されていれば filename_wrapper クラスを追加
  if($attributes['fileName']) {
    $add_block_class .= ' filename_wrapper';
  }
  
  //ブロックに指定するインラインスタイル
  $add_style = '';
  
  // maxWidthEnable が true なら max-width をインラインスタイルで設定
  if($attributes['maxWidthEnable']) {
    $add_style = ' style="max-width:' . $attributes['maxWidth'] . 'px" ';
  }
  
  // ブロックの div 要素に追加のクラスとスタイルを指定
  $output = '<div class="wp-block-wdl-my-code-block' . $add_block_class . '" ' . $add_style .  '>';
  
  //ファイル名の要素を格納する変数
  $file_name = '';
  
  //ファイル名が指定されていればファイル名を p 要素で出力
  if($attributes['fileName']) {
    $file_name = '<p class="file_name">' . esc_html($attributes['fileName']) . '</p>';
    $output .= $file_name;
  }
  
  // pre 要素に追加するクラス
  $add_pre_class = '';
  
  // linenums が true なら行番号を表示するために linenums クラスを追加
  if($attributes['linenums']) {
    $add_pre_class = ' linenums';
    //行の開始番号が指定されていればその値を設定
    if($attributes['linenumsStart'] !== 1) {
      $add_pre_class .= ':' . $attributes['linenumsStart'];
    }
  }
  
  // 言語が指定されていればそのクラス(lang-xxxx)を設定
  if($attributes['lang']) {
    $add_pre_class .= ' lang-' . ($attributes['lang']);
  }
  
  //$output = '<div class="wp-block-wdl-my-code-block"' . $add_style . '>';
  $output .= '<pre class="prettyprint' . $add_pre_class . '">';
  
  //入力された値をエスケープ処理
  $output .=  esc_html($attributes['codeArea']).'</pre></div>';
  
  // 最終的なマークアップを返す
  return $output;
}

//Code-Prettify のスクリプトやスタイルの読み込み
function add_my_code_block_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
  );

  //CSS 用言語ハンドラーの 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
  );
  
  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_my_code_block_scripts_and_styles');
style.scss
.wp-block-wdl-my-code-block  {
  position: relative;
}

.wp-block-wdl-my-code-block p.file_name {
  margin: 0;
  display: inline-block;
  font-size: 15px;
  position: relative;
  top: 2px;  /* 要調整 */ 
  padding: 2px 5px !important;  /* 追加 */  
}

.wp-block-wdl-my-code-block pre.prettyprint {
  font-family: Monaco, Menlo, Consolas, 'Courier New', Courier, monospace, sans-serif;
  font-size: 16px;
  padding: 10px;
  margin-top: 0 !important;  /* 追加 */  
}  
 
.wp-block-wdl-my-code-block .prettyprint ol{
  margin-left: 10px;
  white-space: pre-wrap;  /* 折り返し */
}
 
.wp-block-wdl-my-code-block .prettyprint ol.linenums > li {
  list-style-type: decimal;  /* 行番号を1行ずつ表示 */
  border-left:solid 1px #EBB15E;  /* 行番号との区切り線 */
  padding-left: .8rem; /* 間隔の調整 */
  margin: 0 1.8rem; /* 間隔の調整 */
}

.wp-block-wdl-my-code-block .prettyprint ol.linenums > li::marker {
  color: #aaa; /* 行番号の色 */
  font-size: 13px;
}

.desert {
  /* desert.css をコピー */
  pre.prettyprint { display: block; background-color: #333 }
  pre .nocode { background-color: none; color: #000 }
  pre .str { color: #ffa0a0 } /* string  - pink */
  pre .kwd { color: #f0e68c; font-weight: bold }
  pre .com { color: #87ceeb } /* comment - skyblue */
  pre .typ { color: #98fb98 } /* type    - lightgreen */
  pre .lit { color: #cd5c5c } /* literal - darkred */
  pre .pun { color: #fff }    /* punctuation */
  pre .pln { color: #fff }    /* plaintext */
  pre .tag { color: #f0e68c; font-weight: bold } /* html/xml tag    - lightyellow */
  pre .atn { color: #bdb76b; font-weight: bold } /* attribute name  - khaki */
  pre .atv { color: #ffa0a0 } /* attribute value - pink */
  pre .dec { color: #98fb98 } /* decimal         - lightgreen */
  /* Specify class=linenums on a pre to get line numbering */
  ol.linenums { margin-top: 0; margin-bottom: 0; color: #AEAEAE } /* IE indents via margin-left */
  li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8 { list-style-type: none }
  /* Alternate shading for lines */
  li.L1,li.L3,li.L5,li.L7,li.L9 { }
  @media print {
    pre.prettyprint { background-color: none }
    pre .str, code .str { color: #060 }
    pre .kwd, code .kwd { color: #006; font-weight: bold }
    pre .com, code .com { color: #600; font-style: italic }
    pre .typ, code .typ { color: #404; font-weight: bold }
    pre .lit, code .lit { color: #044 }
    pre .pun, code .pun { color: #440 }
    pre .pln, code .pln { color: #000 }
    pre .tag, code .tag { color: #006; font-weight: bold }
    pre .atn, code .atn { color: #404 }
    pre .atv, code .atv { color: #060 }
  }
  /* 追加 */  
  p.file_name {background-color:#333; color: #eee; border: 1px solid #888; border-bottom: none;}  
}

.sunburst {
  /* sunburst.css をコピー */
  pre .str, code .str { color: #65B042; } /* string  - green */
  pre .kwd, code .kwd { color: #E28964; } /* keyword - dark pink */
  pre .com, code .com { color: #AEAEAE; font-style: italic; } /* comment - gray */
  pre .typ, code .typ { color: #89bdff; } /* type - light blue */
  pre .lit, code .lit { color: #3387CC; } /* literal - blue */
  pre .pun, code .pun { color: #fff; } /* punctuation - white */
  pre .pln, code .pln { color: #fff; } /* plaintext - white */
  pre .tag, code .tag { color: #89bdff; } /* html/xml tag    - light blue */
  pre .atn, code .atn { color: #bdb76b; } /* html/xml attribute name  - khaki */
  pre .atv, code .atv { color: #65B042; } /* html/xml attribute value - green */
  pre .dec, code .dec { color: #3387CC; } /* decimal - blue */

  pre.prettyprint, code.prettyprint {
    background-color: #000;
    border-radius: 8px;
    border-top-left-radius:0; /* 追加 */
  }

  pre.prettyprint {
    /* 削除 width:95%;*/
    margin: 1em auto;
    padding: 1em;
    white-space: pre-wrap;
  }

  /* Specify class=linenums on a pre to get line numbering */
  ol.linenums { margin-top: 0; margin-bottom: 0; color: #AEAEAE; } /* IE indents via margin-left */
  li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8 { list-style-type: none }
  /* Alternate shading for lines */
  li.L1,li.L3,li.L5,li.L7,li.L9 { }

  @media print {
    pre .str, code .str { color: #060; }
    pre .kwd, code .kwd { color: #006; font-weight: bold; }
    pre .com, code .com { color: #600; font-style: italic; }
    pre .typ, code .typ { color: #404; font-weight: bold; }
    pre .lit, code .lit { color: #044; }
    pre .pun, code .pun { color: #440; }
    pre .pln, code .pln { color: #000; }
    pre .tag, code .tag { color: #006; font-weight: bold; }
    pre .atn, code .atn { color: #404; }
    pre .atv, code .atv { color: #060; }
  }
  /* 追加 */ 
  p.file_name {background-color:#000; color: #eee; border: 1px solid #888; border-bottom: none; border-top-left-radius:8px; border-top-right-radius:8px;}   
}

.doxy {
  /* doxy.css をコピー */
  pre .str, code .str { color: #fec243; } /* string  - eggyolk gold */
  pre .kwd, code .kwd { color: #8470FF; } /* keyword - light slate blue */
  pre .com, code .com { color: #32cd32; font-style: italic; } /* comment - green */
  pre .typ, code .typ { color: #6ecbcc; } /* type - turq green */
  pre .lit, code .lit { color: #d06; } /* literal - cherry red */
  pre .pun, code .pun { color: #8B8970;  } /* punctuation - lemon chiffon4  */
  pre .pln, code .pln { color: #f0f0f0; } /* plaintext - white */
  pre .tag, code .tag { color: #9c9cff; } /* html/xml tag  (bluey)  */
  pre .htm, code .htm { color: #dda0dd; } /* html tag  light purply*/
  pre .xsl, code .xsl { color: #d0a0d0; } /* xslt tag  light purply*/
  pre .atn, code .atn { color: #46eeee; font-weight: normal;} /* html/xml attribute name  - lt turquoise */
  pre .atv, code .atv { color: #EEB4B4; } /* html/xml attribute value - rosy brown2 */
  pre .dec, code .dec { color: #3387CC; } /* decimal - blue */

  a {
    text-decoration: none;
  }
  pre.prettyprint, code.prettyprint {
    font-family:'Droid Sans Mono','CPMono_v07 Bold','Droid Sans';
    font-weight: bold;
    font-size: 16px;  /* 変更 */
    background-color: #0f0f0f;
    -moz-border-radius: 8px;
    -webkit-border-radius: 8px;
    -o-border-radius: 8px;
    -ms-border-radius: 8px;
    -khtml-border-radius: 8px;
    border-radius: 8px;
    border-top-left-radius:0; /* 追加 */
  }  /*  background is black (well, just a tad less dark )  */

  pre.prettyprint {
    /* 削除  width:95%;*/
    margin: 1em auto;
    padding: 1em;
    white-space: pre-wrap;
  }

  pre.prettyprint a, code.prettyprint a {
     text-decoration:none;
  }
  /* Specify class=linenums on a pre to get line numbering; line numbers themselves are the same color as punctuation */
  ol.linenums { margin-top: 0; margin-bottom: 0; color: #8B8970; } /* IE indents via margin-left */
  li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8 { list-style-type: none }
  /* Alternate shading for lines */
  li.L1,li.L3,li.L5,li.L7,li.L9 { }

  /* print is mostly unchanged from default at present  */
  @media print {
    pre.prettyprint, code.prettyprint { background-color: #fff;  }
    pre .str, code .str { color: #088; }
    pre .kwd, code .kwd { color: #006; font-weight: bold; }
    pre .com, code .com { color: #0c3; font-style: italic; }
    pre .typ, code .typ { color: #404; font-weight: bold; }
    pre .lit, code .lit { color: #044; }
    pre .pun, code .pun { color: #440; }
    pre .pln, code .pln { color: #000; }
    pre .tag, code .tag { color: #b66ff7; font-weight: bold; }
    pre .htm, code .htm { color: #606; font-weight: bold; }
    pre .xsl, code .xsl { color: #606; font-weight: bold; }
    pre .atn, code .atn { color: #c71585;  font-weight: normal; }
    pre .atv, code .atv { color: #088;  font-weight: normal; }
  }
  
  /* 追加 */ 
  p.file_name {background-color:#0f0f0f; color: #eee; border: 1px solid #888; border-bottom: none; border-top-left-radius:8px; border-top-right-radius:8px; }   
}

.sons-of-obsidian {
  /* sons-of-obsidian.css をコピー */
  .str{
      color: #EC7600;
  }
  .kwd{
      color: #93C763;
  }
  .com{
      color: #66747B;
  }
  .typ{
      color: #678CB1;
  }
  .lit{
      color: #FACD22;
  }
  .pun{
      color: #F1F2F3;
  }
  .pln{
      color: #F1F2F3;
  }
  .tag{
      color: #8AC763;
  }
  .atn{
      color: #E0E2E4;
  }
  .atv{
      color: #EC7600;
  }
  .dec{
      color: purple;
  }
  pre.prettyprint{
      border: 0px solid #888;
  }
  ol.linenums{
      margin-top: 0;
      margin-bottom: 0;
  }
  .prettyprint {
      background: #000;
  }
  li.L0, li.L1, li.L2, li.L3, li.L4, li.L5, li.L6, li.L7, li.L8, li.L9{
      color: #555;
      list-style-type: decimal;
  }
  li.L1, li.L3, li.L5, li.L7, li.L9 {
      background: #111;
  }
  @media print
  {
    .str{
        color: #060;
    }
    .kwd{
        color: #006;
        font-weight: bold;
    }
    .com{
        color: #600;
        font-style: italic;
    }
    .typ{
        color: #404;
        font-weight: bold;
    }
    .lit{
        color: #044;
    }
    .pun{
        color: #440;
    }
    .pln{
        color: #000;
    }
    .tag{
        color: #006;
        font-weight: bold;
    }
    .atn{
        color: #404;
    }
    .atv{
        color: #060;
    }
  }
  /* 追加 */ 
  p.file_name {background-color:#000; color: #eee;}   
}
editor.scss
/* textarea 要素 */
.components-textarea-control__input {
  font-size: 16px;
  padding: 10px;
}

.filename input {
  max-width: 300px;
  margin-left: 10px;
  font-size: 16px;
}

.wp-block-wdl-my-code-block .prettyprint ol.linenums > li {
  padding-left: .5rem; /* 間隔の調整 */
  margin: 0; /* 間隔の調整 */
}