WordPressの管理画面でブロックエディタが使いたーい！


# WordPressの管理画面でブロックエディタが使いたーい！

WordPressってググっても技術的な記事がなかなか出てこなくて苦労します。
前にちらっと見かけたのに探し出せない・・・。

どうしても管理画面でブロックを使う方法が知りたい。
仕方ないのでとりあえず使えるように悪あがきしてみます。


というか、


* 公式、お願いします、「簡単」に使えるようにしてください


あとだいぶソースコード削っているので足りない部分は想像でお願いします。
この記事は以下のページの延長上の内容です。

https://kurage-worker.com/2023/wordpress-block-develop-memo-list




## パッケージを読み込みたい

↓記事投稿時にブラウザのコンソールからJavaScriptを実行しているところ

![wp-rest-controller-reg-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3534740/ef5b063e-f553-df8a-cda7-4cbae12b12d7.png)

投稿時ならブラウザで`wp.apiFetch()`などいろんなパッケージが使用できます。

ちなみに`window.wp`に含まれるパッケージの量(wp.apiFetchもある)↓

![wp1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3534740/1f200170-61c3-b29a-97a6-d098ad9e9165.png)


でも`add_menu_page()`で追加した管理画面からはほとんど使えません。

![wp2.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3534740/680a1529-d472-dcc6-f458-fedfddd0384e.png)


そもそもこの`wp`はどこから来た！？
どうやって読み込まれる？ などを調べていきます。



WordPressでブロックエディタが使われている箇所といったら「投稿(記事編集ページ)」と「ウィジェット」、「サイトエディタ」があります。
投稿(新規作成の場合)だと`/wp-admin/post-new.php`、ウィジェットだと`/wp-admin/widgets.php`です。サイトエディタだと(`/wp-admin/edit-site.php`)

これらのページを解読して、どのようにブロックエディタが読み込まれているか調べてみました。
それを知れば独自に管理画面に追加できるのではと考えて。



## 投稿

まずは投稿から見ていきましょう。
投稿でスクリプトを読み込むファイルは`/wp-admin/edit-form-blocks.php`にあります。
その一部を抜粋します。

まずはブラウザに出力するスクリプトの部分。
この中の`wp.editPost.initializeEditor( 'editor', "%s", %d, %s, %s )`が重要です。

```php
$init_script = <<<JS
( function() {
	window._wpLoadBlockEditor = new Promise( function( resolve ) {
		wp.domReady( function() {
			resolve( wp.editPost.initializeEditor( 'editor', "%s", %d, %s, %s ) );
		} );
	} );
} )();
JS;

$script = sprintf(
	$init_script,
	$post->post_type,
	$post->ID,
	wp_json_encode( $editor_settings ),
	wp_json_encode( $initial_edits )
);
wp_add_inline_script( 'wp-edit-post', $script );
```

次はブラウザに出力するDOMの部分。
後で必要になりますが`editor`のIDをもつ`DIV`が存在します。

```html
<div class="block-editor">
	<h1 class="screen-reader-text hide-if-no-js"><?php echo esc_html( $title ); ?></h1>
	<div id="editor" class="block-editor__container hide-if-no-js"></div>
	<div id="metaboxes" class="hidden">
		<?php the_block_editor_meta_boxes(); ?>
	</div>
```


では投稿画面からこのスクリプトが挿入されている場所を調べてみましょう。


![wp-ex-post-browser.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3534740/53022704-44f8-cdca-5fab-12a8b2203282.png)


ありました。

`wp.editPost.initializeEditor()`が実行してあります。
ではこのメソッドが何者なのか、その前に`wp.editPost`はどこから来たのか、調べます。

`wp-edit-post`ハンドルの正体は`/wp-includes/js/dist/edit-post.js`です。

* どのように調べたかというと`WP_Scripts`クラスやその基底クラスに条件付きのブレークポイントを張ってです(to_doの変更を監視することで依存関係の元をたどることができる)。

このスクリプトの最後あたりを見ると・・・。


![wp-ex-post-wp.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3534740/06e6ccd7-1c06-4550-997a-0b66f60d5412.png)


```js
(window.wp = window.wp || {}).editPost = __webpack_exports__;
```

という箇所が見られます。
WebPackで自動的に追加されているっぽい感じです。




ではこのスクリプトの中身を詳しく見ていきましょう。
Gutenbergのソースコードは`GitHub`で見ることができます。
`packages`の配下に`edit-post`はあります。

https://github.com/WordPress/gutenberg/blob/1180c17ac21cdeb938847ee7a2f64ce73aeb4182/packages/edit-post/src/index.js




ソースコードを追うとReactなコードが見つかります。
だいぶ省略しますが、大体以下のようなコードになります。


```js
export function initializeEditor(
	id,
	postType,
	postId,
	settings,
	initialEdits
) {
	const target = document.getElementById( id );
	const root = createRoot( target );

	root.render(
		<Editor
			settings={ settings }
			postId={ postId }
			postType={ postType }
			initialEdits={ initialEdits }
		/>
	);

	return root;
}
```

`initializeEditor()`の第一引数に渡すのはエレメントのIDですね、
インラインスクリプトに書かれたIDは`editor`でしたからこのIDのエレメントがReactに渡します。
この関数では省略しましたがいろんな初期化が行われています。


この関数がブラウザから実行されるわけです。
ではコンポーネントの階層を追っていきましょう。


```txt
Editor /edit-post/src/editor.js
  Layout /edit-post/src/components/layout/index.js
    VisualEditor /edit-post/src/components/visual-editor/index.js
      BlockList /block-editor/src/components/block-list/index.js
```

`BlockList`はブロックエディタ一覧を配置するコンポーネントです。
ここに`wp.data.select('core/block-editor').getBlocks()`で取得できるブロック一覧を配置するんですね。

以下の例では段落(`core/paragraph`)段落を三つ追加しました。
それぞれ`aaa`, `bbb`, `ccc`と記入してます。


![wp-ex-post-blocks.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3534740/9643f05b-76d4-95d8-b174-8bd19aab9c7d.png)




## ウィジェット

WordPressのウィジェットページのパスは`/wp-admin/widgets.php`です。
表示するとウィジェットエリアのリストがあり、さらにそれぞれのウィジェットエリアの配下にブロックがあります。

例：

```txt
ウィジェットエリア(Side Bar)
	ブロック(カテゴリ)
	ブロック(最近の投稿)
	ブロック(ナビ)

ウィジェット(Header Bar)
	ブロック(・・・)

ウィジェット(Footer Bar)
	ブロック(・・・)
```


これを理解したうえで、ではウィジェットはどのようにブロックを読み込んでいるのか見ていきます。

ウィジェットは`/wp-admin/widgets-form-blocks.php`を読み込みます。
ここに以下のインラインスクリプトがあります。



```php
wp_add_inline_script(
	'wp-edit-widgets',
	sprintf(
		'wp.domReady( function() {
			wp.editWidgets.initialize( "widgets-editor", %s );
		} );',
		wp_json_encode( $editor_settings )
	)
);
```

`wp.editWidgets`の実態は`/wp-includes/js/dist/edit-widgets.js`にあります。
このスクリプトの最後らへんにほら。

```js
(window.wp = window.wp || {}).editWidgets = __webpack_exports__;
```


ではこのスクリプトのソースコードです。

https://github.com/WordPress/gutenberg/blob/1180c17ac21cdeb938847ee7a2f64ce73aeb4182/packages/edit-widgets/src/index.js


`initializeEditor()`を思いっきり省略すると以下のようになります。

```js
export function initializeEditor( id, settings ) {
	const target = document.getElementById( id );
	const root = createRoot( target );
	root.render( <Layout blockEditorSettings={ settings } /> );

	return root;
}
export const initialize = initializeEditor;
```

さて、コンポーネントの階層は次のようになってます。

```txt
Layout　/edit-widgets/src/components/layout/index.js
	Interface　/edit-widgets/src/components/layout/interface.js
		WidgetAreasBlockEditorContent　/edit-widgets/src/components/widget-areas-block-editor-content/index.js
			BlockList　/block-editor/src/components/block-list/index.js
```

`BlockList`にはウィジェットエリア(`core/widget-area`)の一覧が配置されます。
`wp.data.select('core/block-editor').getBlocks()`で取得されるのはこのウィジェットエリア一覧です。

ウィジェットエリアは`WidgetAreaEdit`コンポーネントのことです。
さらにこの`WidgetAreaEdit`コンポーネントの階層を見ていくと

```txt
WidgetAreaEdit
    WidgetAreaInnerBlocks
        children: useInnerBlocksProps()
```

`WidgetAreaInnerBlocks`コンポーネントはさらにブロックエディタ一覧を配置します。
このコンポーネントはブロックエディタ一覧を`useInnerBlocksProps()`から取得するようになっていて、
それを`props.children`に設定する。


とまぁかなり複雑な構造になっています。

でもこれらのソースコードを見るとどうすればいいのか大体わかってきます。


## 実験にゃ！

ブロックエディタ側とＰＨＰ管理側に分かれます。


## ブロックにロジック寄生にゃ！

本来ブロックエディタは`registerBlockType()`で作成した自分自身のブロックエディタを登録しますが今回はいったん登録をやめてます。
以下のコードを見てください。

```tsx
import React from 'react';
import { createRoot, useState } from '@wordpress/element';
import { Button, TextControl } from '@wordpress/components';

import './style.scss';

const AdminForm = () =>
{
	const [content, setContent] = useState('');

	const click = () =>
	{
		window.alert(content);
		setContent('');
	}


	return (
		<>
			<TextControl value={content} onChange={setContent} />
			<Button variant="primary" onClick={e => click()}>追加</Button>
		</>
	)
}

// 初期化スクリプト
export function initializeKurageEditor(id)
{
	const target = document.getElementById(id);
	if(target)
	{
		const root = createRoot(target);
		root.render(<AdminForm />)
		return root;
	}
}

// @ts-ignore
(window.wp = window.wp || {}).kurage =
{
	initialize: initializeKurageEditor
}
```

元々のブロック無視して`AdminForm`コンポーネントを作成します。
ものすごく単純でボタンを押すとテキストボックスに入力した文字をアラートで表示するものです。

* ボタンのラベルが「追加」になってしまっていますが、何も追加しません。間違いです、はい。


`initializeKurageEditor(id)`は引数のIDからReactを開始する関数です。

最後に`wp.kurage.initialize()`を呼び出せるようにグローバルオブジェクトに追加します。


後はPHP側から`wp.kurage.initialize()`を呼び出すだけです。


## 管理画面にゃ！

```php
function create_block_kurage_worker_block_init() {
	register_block_type(
		__DIR__ . '/build',
		[
			
		]
	);
}

// PHP側のブロックエディタを追加
add_action( 'init', 'create_block_kurage_worker_block_init' );



// 管理画面の登録等を行う
add_action('admin_menu', function(){

	function showPage()
	{
		?>

		<div class="wrap">
			
			<h1>Block Editor</h1>

            <!-- みてみて、ここ重要！ -->
			<div id="blockEditor"></div>

			<?php
			
			?>

		</div>

		<?php
	}

    // 管理画面を登録
	$hook = add_menu_page(
		'クラゲ管理画面',
		'クラゲメニュー設定',
		'manage_options',
		'kurage',
		function()
		{
			showPage();
		}
	);


    // 管理画面を表示するときに実行
	add_action("load-{$hook}", function(){

		// コンポーネントのスタイル等も読み込む
		wp_enqueue_style('wp-edit-post');

		// 投稿管理画面で使うスクリプトのハンドルを拝借。
		wp_enqueue_script('wp-edit-post');
		
		
		$script = sprintf('
			<script type="text/javascript">
				jQuery(function(){
					wp.kurage.initialize("blockEditor");
				});
			</script>
		');

		// インラインスクリプトでwp.kurage.initialize()を呼び出そう
		wp_add_inline_script('wp-edit-post', $script);

		// これをtrueにしないとエディタのスクリプトが読み込まれない・・・
		add_filter('should_load_block_editor_scripts_and_styles', function($load_block_editor){
			return true;
		});

	});

});
```

`admin_menu`で管理画面の登録です。
この時`blockEditor`というIDを持った`DIV`を追加していることに注目します。

```html
<div id="blockEditor"></div>
```

次に管理画面が表示された時のコードです。

```php
add_action("load-{$hook}", function(){

    // コンポーネントのスタイル等も読み込む
	wp_enqueue_style('wp-edit-post');

    // 投稿管理画面で使うスクリプトのハンドルを拝借。
    wp_enqueue_script('wp-edit-post');
    
    
    $script = sprintf('
        <script type="text/javascript">
            jQuery(function(){
                wp.kurage.initialize("blockEditor");
            });
        </script>
    ');

    // インラインスクリプトでwp.kurage.initialize()を呼び出そう
    wp_add_inline_script('wp-edit-post', $script);

    // これをtrueにしないとエディタのスクリプトが読み込まれない・・・
    add_filter('should_load_block_editor_scripts_and_styles', function($load_block_editor){
        return true;
    });

});
```


まずはコアのスクリプトファイルを読み込みます。
投稿管理画面は`/wp-admin/edit-form-blocks.php`内で`wp_enqueue_script('wp-edit-post');`が実行されています。

`wp-edit-post`ハンドルはえらい数の依存関係を含んでます。
`wp-data`や`wp-editor`などブロック開発で使用するハンドルを子孫に持ってます。

そこでわが管理画面でこのハンドルを拝借しようじゃないか！
ということです。

これで投稿で使用するレベルの大量のパッケージを読み込むことができそうです。
もちろんすべて把握しているわけではないので漏れている可能性もあります。


次にインラインスクリプトにブロックの初期化スクリプトを読み込むべく
`wp.kurage.initialize("blockEditor");`を実行するインラインスクリプトを追加します。


`wp_enqueue_style('wp-edit-post');`も同じくエンキューします。
これをしないとボタンなどのコンポーネントにスタイルが適用されません。



次にフィルターを追加します。
`should_load_block_editor_scripts_and_styles`フィルタで`true`を返します。
投稿管理画面は`wp_enqueue_registered_block_scripts_and_styles()`を呼びます。
この関数内でエディタ側のハンドルをエンキューするかどうかの判断に使用されます。


## 実行してみるにゃ！


![wp-ex-alert.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3534740/dfc8defb-6242-6030-c7ba-ce1d259daf44.png)


管理ページ見たら`AdminForm`コンポーネントが表示されています。



これ以降は未解決です。
これ以上WordPressに踏み込むつもりはありません。



## ブロックの表示

ところで`段落`や`画像`などのブロックって利用出来ないだろうか！？
本家の投稿やウィジェットにはブロックを追加出来てました。
その正体が`BlockList`で一般の開発者が直接使うものじゃないとは思いますが、
学習目的で使ってみます。


まず`BlockList`をインポート。

```ts
// @ts-ignore
import { BlockList } from '@wordpress/block-editor';
```


`AdminForm`の戻り値を若干変更。
`BlockList`コンポーネントを追加します。

```tsx
return (
    <>
        <TextControl value={content} onChange={setContent} />
        <Button variant="primary" onClick={e => click()}>追加</Button>

        <h1>以下ブロックリスト</h1>
        <BlockList />
    </>
)
```

Editを登録

```tsx
// @ts-ignore
const blockConf: BlockConfiguration<KurageExampleBlockProps> =
{
	...metadata,
	edit: Edit,
	save,
};

registerBlockType(metadata.name, blockConf);
```


本来は作ってるはずのEdit

```tsx
export default (props: BlockEditProps<KurageExampleBlockProps>) =>
{
	const blockProps = useBlockProps();

	return (
		<div {...blockProps}>
			I am Block Editor!
		</div>	
	)
}
```

そのままでは不都合があるので`BlockList`の要素を`padding: 2em;`にしています。


![wp-ex-block-list-.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3534740/95de8016-3d72-7988-5f1e-cf3ab7a1b834.png)


すると「+」が追加されました(ブロックが一つも登録されてないとそもそも表示されない)。
クリックすると、


![wp-ex-block-list-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3534740/399c26ad-1dec-db9a-315b-d6e56f12c99c.png)


自分で登録したブロックしか候補に挙がってない
３個追加してみる。


![wp-ex-block-list-3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3534740/ca4689cd-0c81-9939-6575-3e4fa7cb32f5.png)


`BlockList`はそのまま使えなさそう、
そんなわかりきったことより、どうやって他のコアのブロックを召喚するのだろう！

`BlockList`自体は必要ないが、単に`createBlock()`でブロックを作成したいことはある。


### コアなブロックを引っ張ってくる

コアなブロックの登録は`initializeEditor()`の初期化時に行われている。

ブロックの登録は`@wordpress/block-library`を使う。

```ts
// @ts-ignore
import { registerCoreBlocks, __experimentalGetCoreBlocks } from '@wordpress/block-library';
```

初期化時にブロックを追加。

```ts
export function initializeKurageEditor(id)
{

	const coreBlocks = __experimentalGetCoreBlocks();

	// @ts-ignore
	registerCoreBlocks(coreBlocks);

	const target = document.getElementById(id);
	if(target)
	{
		const root = createRoot(target);
		root.render(<AdminForm />)
		return root;
	}
}
```

お、ちょっと表示が変わった。


![wp-ex-block-list-add-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3534740/cf649a61-c1d8-56e1-33de-886e16c3b4ea.png)



いろんなブロック登場。


![wp-ex-block-list-add-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3534740/13722e04-8f53-d385-4bcd-fd828982d721.png)



ただインサーターを追加していないなど完璧に実装していないのですべてのブロックを表示することができない。
検索から絞り込むことはできるが・・・。

まぁしゃーない。


## ブロックを拝借しようとする問題点

ブロックを追加することはできる。
でもツールバーが出てこなかったり、インスペクターが使えないなどの問題がある。
なぜなら適切にスロットを配置してない。
なんならデバッグで`onMouseMove`系のエラーが出ている。

`BlockList`の先祖には様々なコンポーネントやらプロバイダーがいて、
どうせならもっと上の`Layout`とかを配置してしまえばよさそうだけど、
全く別のアプリケーションになってしまう。

何とか頑張ってこれらを実装できるようにしたいけど、
これ以上ソースコード解読するのはしんどい。


やっぱり公式が何とかしてくれるのを願うしか・・・。


ということでブロックを拝借するなんて姑息なことはせず、
標準で用意されているコンポーネントだけで頑張って管理画面作っていこうと思う。

管理画面からいろんなパッケージ使えるようになったのでそれで十分かな。

とりあえず一旦WordPressの学習を終えよう。