thumbnail

Gutenbergのツールバーにドロップダウンを追加する

Gutenberg(ブロックエディタ)のツールバーにボタンを追加する方法は以前紹介しました。

ただ、この方法の場合はドロップダウンでまとめることはできません。

Gutenberg プラグイン v5.3 または WordPress v5.2 からはデフォルトのボタンとともにドロップダウンにまとまるようになりましたが、独自のボタンだけでまとめたいこともあるでしょう。

ここでは独自のドロップダウンに追加する方法を紹介します。

BlockFormatControls

ボタンなどを追加できる場所はいくつか用意されています。

ツールバーへ追加するには『BlockControls』または『BlockFormatControls』を使用します。

デフォルトのツールバーボタンよりも後ろに追加する場合は『BlockFormatControls』を使用します。

registerFormatType は RichText.ToolbarControls Slot を利用するための Fill を生成するようにプログラムされています。

『BlockControls』や『BlockFormatControls』はそれぞれの Slot を利用するための Fill となるので、通常のコンポーネントを利用するのと同じ様に使用できます。

ドロップダウンを追加する

ここからは実際にフォーマットの付け外しを行う複数のボタンをひとつのドロップダウンにまとめる方法を解説します。

方針

registerFormatType は 使用するフォーマットの情報 (title(フォーマット名)や tag名、class名) とそれの付け外しの手段 (edit) を登録します。

この edit で通常は RichTextToolbarButton を使用し、押されたときに toggleFormat によってフォーマットの付け外しが行われるボタンを追加します。

ただ実際は RichTextToolbarButtonRichText.ToolbarControls という Fill に ToolbarButton を登録する関数で、これ自体はボタンの実体ではありません。

その後 RichText.ToolbarControls という Slot で登録されたボタンが取り出され、DropdownMenu に渡されて表示されます。

これと同じような仕組みを用意することで簡単にドロップダウン化することが可能です。

Slot と Fill の詳細は次の記事を参考にしてください。

実装1

webpack でのビルド環境などの準備の説明は省きます。

constant.js

使用する定数を定義します。

export const PLUGIN_NAME = 'my-dropdown';

components

いくつかのコンポーネントを用意します。

components/my-dropdown-controls.js

Slot と Fill を利用するためのヘルパーです。

const { createSlotFill } = wp.components;

const { Fill, Slot } = createSlotFill( 'MyDropdownControls' );

const MyDropdownControls = Fill;
MyDropdownControls.Slot = Slot;

export default MyDropdownControls;
components/my-dropdown.js

MyDropdownControls で登録される Fill を使用して DropdownMenu を生成します。

ツールバーの Slot を利用するための BlockFormatControls でラップすることでツールバーに表示されます。

const { BlockFormatControls } = wp.editor;
const { Toolbar, DropdownMenu } = wp.components;
import MyDropdownControls from './my-dropdown-controls';

const MyDropdown = () => <BlockFormatControls>
	<div className="editor-format-toolbar block-editor-format-toolbar">
		<Toolbar>
			<MyDropdownControls.Slot>
				{ fills => <DropdownMenu
					icon='admin-customizer'
					position="bottom left"
					label='dropdown'
					controls={ fills.map( ( [ { props } ] ) => props ) }
				/> }
			</MyDropdownControls.Slot>
		</Toolbar>
	</div>
</BlockFormatControls>;

export default MyDropdown;

ここでは icon'admin-customizer' とハードコーディングしていますが、これがツールバーに表示されるアイコンになるので適宜変更します。

他の設定も含めて色々変更できるようにしておくと便利かもしれません。

components/index.js

import の記述を減らしたり利用できる components を把握するためだけに用意しています。

import MyDropdown from './my-dropdown';
import MyDropdownControls from './my-dropdown-controls';

export { MyDropdown, MyDropdownControls };

utils

いくつかの便利関数を用意します。

utils/rich-text.js

リッチテキスト用の関数を用意します。

ここでは registerFormatType で渡す引数の生成ヘルパーを用意しています。

フォーマット名 (name) と component生成関数 (create)、 追加した順番 (index) を渡すとregisterFormatType に必要な引数を返します。

const { Fragment } = wp.element;

import { PLUGIN_NAME } from '../constant';
import { MyDropdown, MyDropdownControls } from '../components';

/**
 * @param {string} name name
 * @param {number} index index
 * @param {function} create create component function
 * @param {object} setting setting
 * @returns {array} setting
 */
export const getRichTextSetting = ( { name, create, setting = {} }, index ) => {
	const formatName = PLUGIN_NAME + '/' + name;
	const component = args => <MyDropdownControls>
		{ create( { args, name, formatName } ) }
	</MyDropdownControls>;

	setting.title = setting.title || name;
	setting.tagName = setting.tagName || 'span';
	setting.className = setting.className || name;
	setting.edit = args => {
		if ( ! index ) {
			return <Fragment>
				{ component( args ) }
				<MyDropdown/>
			</Fragment>;
		}
		return component( args );
	};
	return [ formatName, setting ];
};

肝となるのは以下の個所です。

if ( ! index ) {
	...
}

それぞれのフォーマットで MyDropdownControls を使用して Fill を登録しますが、どれか一つのフォーマットでドロップダウンを追加で生成します。

ここでは ! index 、つまり最初に追加したフォーマットの edit の時にそれを行っています。

これは一番最後や途中でも問題はありません。

utils/toolbar-button.js

ツールバーボタン生成用のヘルパーを用意します。

const { toggleFormat } = wp.richText;
const { ToolbarButton } = wp.components;

/**
 * @param {object} args args
 * @param {string} name name
 * @param {string} formatName format name
 * @returns {{onClick: onClick, icon: *, title: *, isActive: boolean}} toolbar button properties
 */
const getToolbarButtonProps = ( { args, name, formatName } ) => {
	return {
		icon: 'admin-customizer',
		title: <div className={ name }>
			{ name }
		</div>,
		onClick: () => {
			args.onChange( toggleFormat( args.value, {
				type: formatName,
			} ) );
		},
		isActive: args.isActive,
	};
};

/**
 * @param {object} args args
 * @param {string} name name
 * @param {string} formatName format name
 * @returns {*} toolbar button
 */
export const createToolbarButton = ( { args, name, formatName } ) => <ToolbarButton { ...getToolbarButtonProps( { args, name, formatName } ) } />;

icon'admin-customizer' としていますが、これがドロップダウン内で表示されるボタンのアイコンになるので適宜変更します。

utils/index.js
export { createToolbarButton } from './toolbar-button';
export { getRichTextSetting } from './rich-text';

index.js

ここまでで追加されたヘルパーを使用して実際に使用するフォーマットを登録します。

const { registerFormatType } = wp.richText;

import { createToolbarButton, getRichTextSetting } from './utils';

[
	{
		name: 'test1',
		create: createToolbarButton,
	},
	{
		name: 'test2',
		create: createToolbarButton,
	},
	{
		name: 'test3',
		create: createToolbarButton,
	},
].forEach( ( { name, create, setting = {} }, index ) => registerFormatType( ...getRichTextSetting( { name, create, setting }, index ) ) );

ここでは3つのフォーマットを作成しました。

動作確認

ビルドしたものを wp_enqueue_script で読み込ませます。

  • wp-components
  • wp-editor
  • wp-element
  • wp-rich-text

に依存していることを忘れないようにしてください。

複数の名前の Fill と Slot を用意して使用すれば複数のドロップダウンを追加することも可能です。

実装2

実装1の Fill と Slot と同じ方法でライブラリ化したのでそれを使用してみます。

install

npm install --save @technote-space/register-grouped-format-type

import して使用します。

import { Common, RichText } from '@technote-space/register-grouped-format-type';
const { registerFormatTypeGroup, registerGroupedFormatType } = RichText;
const { getToolbarButtonProps } = Common.Helpers;
RichText
registerFormatTypeGroup

この関数はグループ化されたフォーマットの登録に使用します。

/**
 * @param {string} name name
 * @param {string} title title
 * @param {string} tagName tag name
 * @param {string} className class name
 * @param {function} create create component function
 * @param {function} createInspector create inspector component function
 * @param {string} group group
 * @param {string} inspectorGroup inspector group
 * @param {object} settings settings
 * @return {object|null} registered settings
 */
registerGroupedFormatType( {
    name,
    title = name,
    tagName = 'span',
    className = name,
    create,
    createInspector,
    group = name,
    inspectorGroup = name,
    ...settings
} )
registerGroupedFormatType

この関数はグループの設定の登録に使用します。

/**
 * @param {string} name group name
 * @param {object} setting setting
 * @returns {object} registered setting
 */
registerFormatTypeGroup( name, setting = {} )

特に設定されなかった項目はデフォルト値が使用されます。

{
    icon: 'admin-customizer',
    position: 'bottom left',
    label: name,
    menuLabel: name,
    className: undefined,
    menuClassName: undefined,
    inspectorSettings: {},
}
Common.Helpers
getToolbarButtonProps

この関数は registerGroupedFormatType に必要な引数の生成を容易にします。

/**
 * @param {string} group group
 * @param {string} name name
 * @param {*} icon icon
 * @param {object} optional optional
 * @returns {object} props
 */
getToolbarButtonProps( group, name, icon, optional = { tooltipClass: undefined } )

index.js

ライブラリによって追加された関数を実際に使用して追加してみます。

import { Common, RichText } from '@technote-space/register-grouped-format-type';

const { registerFormatTypeGroup, registerGroupedFormatType } = RichText;
const { getToolbarButtonProps, getColorButtonProps, getFontSizesButtonProps } = Common.Helpers;

// register format type group setting
registerFormatTypeGroup( 'test2', {
    icon: 'admin-network',
} );

// register grouped format types
registerGroupedFormatType( getToolbarButtonProps( 'test1', 'dropdown2-test1', 'admin-customizer' ) );
registerGroupedFormatType( getToolbarButtonProps( 'test2', 'dropdown2-test2', 'admin-customizer' ) );
registerGroupedFormatType( getToolbarButtonProps( 'test2', 'dropdown2-test3', 'admin-customizer' ) );

test1 グループは1つフォーマットを登録、test2 グループ アイコンを変更して2つのフォーマットを登録しました。

動作確認

ビルドしたものを wp_enqueue_script で読み込ませます。

このライブラリは

  • wp-components
  • wp-data
  • wp-editor
  • wp-element
  • wp-rich-text
  • wp-url
  • lodash

に依存しています。

少ないコードで簡単に追加することができました。

また以下の動作になるように調整されています。

  • グループに追加した数が2つ未満の場合は通常のボタン表示
  • 付け外しが無効の場合にボタンを無効化

まとめ

公式で用意された方法がないため、store を使用する方法など、色々な方法が考えられます。

今回は SlotFill を活用してツールバーボタンのドロップダウン化を実装しましたが、これももっといろいろな使い方ができそうです。

今回のコード例を含むリポジトリ


prev articleprev thumbnail
Gutenbergのブロックを一時的に隠すプラグインを作成しました
yuzo related posts から学ぶ脆弱性を含むプラグインやテーマの特徴
next thumbnailnext article
arrow