WordPressの管理画面でブロックエディタが使いたーい!
WordPressってググっても技術的な記事がなかなか出てこなくて苦労します。
前にちらっと見かけたのに探し出せない・・・。
どうしても管理画面でブロックを使う方法が知りたい。
仕方ないのでとりあえず使えるように悪あがきしてみます。
というか、
- 公式、お願いします、「簡単」に使えるようにしてください
あとだいぶソースコード削っているので足りない部分は想像でお願いします。
この記事は以下のページの延長上の内容です。
パッケージを読み込みたい
↓記事投稿時にブラウザのコンソールからJavaScriptを実行しているところ
投稿時ならブラウザでwp.apiFetch()
などいろんなパッケージが使用できます。
ちなみにwindow.wp
に含まれるパッケージの量(wp.apiFetchもある)↓
でもadd_menu_page()
で追加した管理画面からはほとんど使えません。
そもそもこの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.editPost.initializeEditor()
が実行してあります。
ではこのメソッドが何者なのか、その前にwp.editPost
はどこから来たのか、調べます。
wp-edit-post
ハンドルの正体は/wp-includes/js/dist/edit-post.js
です。
- どのように調べたかというと
WP_Scripts
クラスやその基底クラスに条件付きのブレークポイントを張ってです(to_doの変更を監視することで依存関係の元をたどることができる)。
このスクリプトの最後あたりを見ると・・・。
(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
と記入してます。
ウィジェット
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-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()
を呼びます。
この関数内でエディタ側のハンドルをエンキューするかどうかの判断に使用されます。
実行してみるにゃ!
管理ページ見たら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;
にしています。
すると「+」が追加されました(ブロックが一つも登録されてないとそもそも表示されない)。
クリックすると、
自分で登録したブロックしか候補に挙がってない
3個追加してみる。
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;
}
}
お、ちょっと表示が変わった。
いろんなブロック登場。
ただインサーターを追加していないなど完璧に実装していないのですべてのブロックを表示することができない。
検索から絞り込むことはできるが・・・。
まぁしゃーない。
ブロックを拝借しようとする問題点
ブロックを追加することはできる。
でもツールバーが出てこなかったり、インスペクターが使えないなどの問題がある。
なぜなら適切にスロットを配置してない。
なんならデバッグでonMouseMove
系のエラーが出ている。
BlockList
の先祖には様々なコンポーネントやらプロバイダーがいて、
どうせならもっと上のLayout
とかを配置してしまえばよさそうだけど、
全く別のアプリケーションになってしまう。
何とか頑張ってこれらを実装できるようにしたいけど、
これ以上ソースコード解読するのはしんどい。
やっぱり公式が何とかしてくれるのを願うしか・・・。
ということでブロックを拝借するなんて姑息なことはせず、
標準で用意されているコンポーネントだけで頑張って管理画面作っていこうと思う。
管理画面からいろんなパッケージ使えるようになったのでそれで十分かな。
とりあえず一旦WordPressの学習を終えよう。