Gutenbergでサイドバーに選択式のCSSクラス設定を追加する

Gutenbergでサイドバーに選択式のCSSクラス設定を追加する

Gutenbergで独自に追加したい事が多くなってきたので最近カスタマイズの勉強をはじめました。

今回はクリック操作だけで既存ブロックや自作ブロックに独自のクラスを設定出来るようにしたいと思います。

元々「高度な設定」から自由に追加出来るのですが、いちいち文字を打つのが面倒なので選択式(セレクトボックス)で設定する方法です。

この記事は今までjQueryを雰囲気で書いてきた未熟な筆者が(Reactってなんぞ…?)ってとこから実践してみた試行錯誤過程の記事となっております。
間違いやスマートではない箇所が含まれる場合がありますが、その点ご理解の上読み進めていただけると幸いです。

スポンサーリンク

構築・動作環境

WordPress 5.3.2
JSX/ESNext

Windows Chrome

実現したいこと

まず、大元はこちらの記事を参考にさせていただきました。

上記はラジオボタンで切り替えられる項目をサイドバーに追加するものですが、
自分の完成イメージとしては次のような選択式の設定項目です。

完成イメージ

参考記事の内容から変更したい点

  1. attributeに独自項目を追加せず既存のclassNameだけで完結する
  2. ブロックの種類毎に違うクラス名のリストを設定する※
  3. セレクトボックスの選択式に変更する

以上を目指しました。

<pre><ul>に設定したいクラス名はそれぞれ違うのでブロックの種類毎に別のクラス名リストに切り替えたかった。

完成したコード

「実現したいこと」を踏まえた上で完成したコードがこちらです。

/*!
 * Copyright (c) 2020 kusamura
 * https://web.monogusa-note.com/
 * Released under the GPL license.
 * see http://www.gnu.org/licenses/gpl-2.0.html
 * 
 * 参考: https://www.webopixel.net/wordpress/1499.html
 */

//コンポーネントの読み込み
const { Fragment } = wp.element;
const { addFilter } = wp.hooks;
const {
	PanelBody,
	SelectControl
} = wp.components;
const { InspectorControls } = wp.editor;
const { createHigherOrderComponent } = wp.compose;

//サイドバーパネルの見出し
const TITLE = '自分用クラス名';
//基本オプション
const baseOption = [
	{ label: '設定するclassを選択する', value: '' },
];
//拡張するブロックと各オプション 
const myClassSetting = {
	'core/code': [
		{ label: 'HTML', value: 'html' },
		{ label: 'CSS', value: 'css' },
	],
	'core/list': [
		{ label: 'タイムライン', value: 'li-timeline' },
		{ label: '数字リスト', value: 'li-number'},
	],
};

//サイドバーの設定
export const addBlockControl = createHigherOrderComponent((BlockEdit) => {
	let selectOption = '';
	return ( props ) => {
		const {
			className,
		} = props.attributes;
		//myClassSettingで指定したブロックかどうか判定
		const isValidBlockType = (name) => {
			const validBlockTypes = Object.keys(myClassSetting);
			return validBlockTypes.includes(name);
		};
		//指定したブロックが選択されたら表示
		if (isValidBlockType(props.name) && props.isSelected) {
			//baseOptionと結合後クラス名だけ抜き出して配列に
			let myClassNames = baseOption.concat(myClassSetting[props.name]).map(({value}) => value);
			//オプション選択されたクラス名があるか(複数の場合も想定)
			const myClassFind = () => {
				if( !className ) return '';
				let myClassSort = myClassNames.slice();
				//クラス名の多い順に並べ替える
				myClassSort.sort(
					(a, b) => b.trim().split(/\s+/).length - a.trim().split(/\s+/).length
				);
				return myClassSort.find( (name) => {
					const classArr = className.trim().split(/\s+/);
					const searchArr = name.trim().split(/\s+/);
					return searchArr.every((v) => classArr.includes(v));
				} ) || '';
			};
			//選択されたオプションを設定
			selectOption = myClassFind();
			//myClassSettingで複数のクラス名設定していた場合を想定して一旦くっつけて配列に戻す
			myClassNames = myClassNames.join(' ').split(/\s+/);
			//SelectControlのoptionsに設定する用
			const myClassOptions = baseOption.concat( myClassSetting[props.name]);
			return (
				<Fragment>
					<BlockEdit { ...props } />
					<InspectorControls>
						<PanelBody title={ TITLE } initialOpen={ true } className="my-classname-controle">
							<SelectControl
								value={ selectOption }
								options={ myClassOptions }
								onChange={ ( changeOption ) => {
									let newClassName = changeOption;
									// 高度な設定で入力している場合は追加する
									if ( className ) {
										// 付与されているclassを取り出す
										let inputClassName = className;
										// スペース区切りを配列に
										inputClassName = inputClassName.split(/\s+/);
										// 選択したオプション以外の自分用クラスを取り除く
										let filterClassName = inputClassName.filter(
											(name) => !myClassNames.includes(name)
										);
										// 新しく選択したオプションを追加
										filterClassName.push(changeOption);
										// 配列を文字列に
										newClassName = filterClassName.join(' ');
									}
									selectOption = changeOption;
									props.setAttributes({ className: newClassName });
								} }
							/>
						</PanelBody>
					</InspectorControls>
				</Fragment>
			);
		}
		return <BlockEdit { ...props } />;
	};
}, 'addMyCustomBlockControls' );
addFilter( 'editor.BlockEdit', 'myblock/block-control', addBlockControl );

拡張するブロックとオプション設定

baseOptionはすべての拡張するブロックに表示するオプション。

拡張するブロックは「ソースコード」と「リスト」
それぞれ設定したいオプション(クラス名)を設定。

参考 WordPress ブロックエディター(Gutenberg)ブロック一覧表 – Qiita

//サイドバーパネルの見出し
const TITLE = '自分用クラス名';
//基本オプション
const baseOption = [
    { label: '設定するclassを選択する', value: '' },
];
//拡張するブロックと各オプション 
const myClassSetting = {
    'core/code': [
        { label: 'HTML', value: 'html' },
        { label: 'CSS', value: 'css' },
    ],
    'core/list': [
        { label: 'タイムライン', value: 'li-timeline' },
        { label: '数字リスト', value: 'li-number'},
    ],
};

セレクトボックスの動作

完成
  • 高度な設定からCSSクラス名を追加しても、設定した項目が選択される。
  • {label:'ABC', value:'a b c'}とか複数クラスを設定出来る
    • 「高度な設定」でa b d e cのように追加しても「ABC」が選択済みになる。

複数クラスを設定した時の動作

  • 一致するクラス名が多い順>配列順が優先で(外見上)選択される。
  • 選択をする度myClassSettingで設定したリストに含まれているクラスは一旦すべて削除され選択したクラスに変更される。

例えばオプションで次のように設定したとする。

{label:'AB', value:'a b'},
{label:'CD', 'value:c d'},
{label:'EF', value:'e f'},
{label:'ABC', value:'a b c'},
  1. 「高度な設定」の追加CSSクラスで「e f a b zzz」書いたとする。
    「EF」「AB」が当てはまる。
    この場合はオプションで設定した配列順で「AB」が優先され選択される。
  2. そのままセレクトボックスで「CD」を選択すると「a b」「e f」どちらも削除され、結果「zzz c d」になる。

  1. 「高度な設定」の追加CSSクラスで「a b zzz c d」と書いたとする。
    「AB」「CD」「ABC」が当てはまる。
    この場合一致するクラス名が多い「ABC」が優先され選択される。
  2. そのままセレクトボックスで「EF」を選択すると「a b c d」すべて削除され、結果「zzz e f」になる。

「a c zzz」と書いた場合、「a c」は「AB」にも「CD」にも当てはまらないため選択されないがリストに含まれるクラス名のため、(2)のタイミングで削除されます。(仕様)

個人的にここまで複雑な指定をする事はないのですが、一応複数設定を想定して作れたかな?と思います。多分。

もっと簡単な実装方法は?

registerBlockStyleを使えばプレビューも見れるスタイルセレクト機能を簡単に実装できます。
ただ、設定されるクラス名がis-style-クラス名になってしまいますが…。

スタイルセレクト機能
wp.blocks.registerBlockStyle("core/code", {
    name: "html",
    label: "HTML",
});
wp.blocks.registerBlockStyle("core/code", {
    name: "css",
    label: "CSS",
});
wp.blocks.registerBlockStyle("core/code", {
    name: "javascript",
    label: "JavaScript",
});

is-style-が付かない簡単な方法ってないのかな?

参考 Block Filters | Block Editor Handbook | WordPress Developer Resources

さいごに

自分がはるか昔に覚えた事よりずっとJavaScriptが進化しているのでなかなか難しいです。

日本語の解説ページが少ないのもあり、ググっては英語のページを流し読み(読めない)してなんとか少しだけつかめてきたような。そうでもないような。

とりあえず、他にどうしても作りたかった「定義リスト(DL)」と「タブ切り替え」ブロック作成までは出来たので記事作成も少しは捗りそうです。
でもコードとにらめっこする方が楽しかったりするのだった。

(あと一応記載したけどソースコードの著作権表記ってどういう書式で書けばいいかわからない)

参考

タイトルとURLをコピーしました