0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

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

Posted at

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

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

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

というか、

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

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

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

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

wp-rest-controller-reg-2.png

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

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

wp1.png

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

wp2.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 )が重要です。

$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が存在します。

<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

ありました。

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

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

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

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

wp-ex-post-wp.png

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

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

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

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

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に渡します。
この関数では省略しましたがいろんな初期化が行われています。

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

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

ウィジェット

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

例:

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

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

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

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

ウィジェットは/wp-admin/widgets-form-blocks.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にあります。
このスクリプトの最後らへんにほら。

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

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

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

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;

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

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コンポーネントの階層を見ていくと

WidgetAreaEdit
    WidgetAreaInnerBlocks
        children: useInnerBlocksProps()

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

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

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

実験にゃ!

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

ブロックにロジック寄生にゃ!

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

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()を呼び出すだけです。

管理画面にゃ!

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を追加していることに注目します。

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

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

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-datawp-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

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

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

ブロックの表示

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

まずBlockListをインポート。

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

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

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

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

Editを登録

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

registerBlockType(metadata.name, blockConf);

本来は作ってるはずのEdit

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

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

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

wp-ex-block-list-.png

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

wp-ex-block-list-2.png

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

wp-ex-block-list-3.png

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

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

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

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

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

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

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

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

いろんなブロック登場。

wp-ex-block-list-add-2.png

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

まぁしゃーない。

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

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

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

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

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

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

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

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

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?