WordPressブロック開発 全ブロックに編集メモ機能を実装する

ブロックの設定を更新して各ブロックが編集メモ機能を所有するようにカスタマイズします。

ブロックの雛形を生成する

npx @wordpress/create-block@latest editorial-notes --template @block-developer-cookbook/editorial-notes

ビルドプロセスを構成する

ブロックを作成しないため、@wordpress/scripts に使用するファイルの場所を定義する。

"start": "wp-scripts start src/notes-field.js",
"build": "wp-scripts build src/notes-field.js",

package.json ファイル内の startbuild コマンドを、src/notes.js ファイルを指すように次のように更新する。

アセットをキューに追加する

editorial-notes.phpを編集して、notes-field.jsをエンキューする

/**
 * Enqueue the JS containing our filters
 */
function editorial_notes_enqueue_scripts() {
	$notes_field_file = plugin_dir_path( __FILE__ ) . '/build/notes-field.asset.php';

	if ( file_exists( $notes_field_file ) ) {
		$assets = include $notes_field_file;

		// Enqueue the JavaScript that contains our filters.
		wp_enqueue_script(
			'notes-field',
			plugin_dir_url( __FILE__ ) . '/build/notes-field.js',
			$assets['dependencies'],
			$assets['version'],
			true
		);
	}
}
add_action( 'enqueue_block_editor_assets', __NAMESPACE__ . '\editorial_notes_enqueue_scripts' );

editorial-notes.phpを編集して、notes-field.cssをエンキューする

/**
 * Enqueue the editor only style
 */
function editorial_notes_enqueue_block_editor_css() {
	$notes_field_file = plugin_dir_path( __FILE__ ) . '/build/notes-field.asset.php';
	if ( file_exists( $notes_field_file ) ) {
		$assets = include $notes_field_file;
		// Enqueue the CSS for the has-notes class.
		wp_enqueue_style(
			'notes-class',
			plugin_dir_url( __FILE__ ) . '/build/notes-field.css',
			array(),
			$assets['version'],
		);
	}
}
add_action( 'enqueue_block_assets', __NAMESPACE__ . '\editorial_notes_enqueue_block_editor_css' );

enqueue_block_editor_assetsとenqueue_block_assetsの違い

フック名有効な場所
enqueue_block_editor_assetsブロックエディター内でのみ
enqueue_block_assetsブロックエディターとフロントエンドの両方

すべてのブロックにカスタム属性を追加する

エディターから追加されたメモを保持するために、各登録されたブロックにカスタムの「メモ」属性を追加する。これを行うために、blocks.registerBlockType フィルターというブロックフィルターを使用する。このフィルターは、ブロックが登録される時点で、ブロックの設定を変更することができる。

このフィルターのコールバック関数は、パラメータとしてブロックの設定とブロック名を受け取る。

notes-field.jsにnotes属性を定義する

/**
 * Filter the block settings to add the notes attribute.
 *
 * @param {Object} settings Settings for the block.
 * @param {string} name     The name of the block.
 *
 * @return {Object} he modified settings.
 */
function addNotesAttribute( settings, name ) {
   return {
       ...settings,
       attributes: {
           ...settings.attributes,
           notes: {
               type: 'string',
               default: '',
           },
       },
   };

addNotesAttribute 関数

ブロックの設定オブジェクトに新しい属性 notes を追加する処理を行う

  1. settings: ブロックタイプの設定オブジェクト。ブロックに関する様々な設定が含まれており、attributes プロパティもその一部。
  2. name: この引数はブロックの名前

...settings として元の設定をコピーした後、attributes プロパティに新しい属性(notes)を追加する

addFilter の登録

addFilter(
	'blocks.registerBlockType',
	'block-developer-cookbook/notes-field',
	addNotesAttribute
);

addFilter 関数

WordPress の フィルターフックを使って、特定のブロック登録処理にカスタム属性を追加している

  1. 'blocks.registerBlockType': Gutenberg ブロックタイプが登録される際に発火するフィルターフック。ブロックタイプの設定を変更することができる。
  2. 'block-developer-cookbook/notes-field': フィルターの名前。
  3. addNotesAttribute: 実際にブロックの設定に新しい属性を追加する関数

ノートを管理するためのコントロールを追加する

登録したカスタム属性を更新できるように、コントロールを作成する

/**
* Add a custom control to the block inspector controls for every block.
*
* @param {WPElement} BlockEdit The original block edit component.
*
* @return {WPElement} Element to render.
*/
function addEditorNotesField( BlockEdit ) {
   return ( props ) => {
       const {
           attributes: { notes },
           setAttributes
       } = props;
       return (
           <>
               <BlockEdit { ...props } />
               <InspectorControls>
                   <PanelBody>
                       <TextareaControl
                           label={ __(
                               'Editorial Notes',
                               'block-developer-cookbook'
                           ) }
                           value={ notes }
                           onChange={ ( notes ) =>
                               setAttributes( { notes } )
                           }
                           help={ __(
                               'Add some editorial notes for this block'
                           ) }
                       />
                   </PanelBody>
               </InspectorControls>
           </>
       );
   };
}

addEditorNotesField 関数

BlockEdit コンポーネントを拡張する関数。BlockEdit は Gutenberg ブロックの編集画面のコンポーネントで、ブロックの編集に関連する UI要素を追加する

  1. 関数内での処理
const {
	attributes: { notes },
	setAttributes,
	isSelected,
} = props;
  1. props: BlockEdit コンポーネントから渡されるプロパティ
    • attributes: { notes }: このブロックの属性の中から notes を取り出す
    • setAttributes:ブロックの属性を更新するための関数
    • isSelected: 現在このブロックが選択されているかどうかを示す Boolean 値
return (
	<>
		<BlockEdit { ...props } />
		{ isSelected && (
			<InspectorControls>
				<PanelBody>
					<TextareaControl
						label={ __(
							'Editorial Notes',
							'block-developer-cookbook'
						) }
						value={ notes }
						onChange={ ( newNotes ) =>
							setAttributes( { notes: newNotes } )
						}
						help={ __(
							'Add some editorial notes for this block'
						) }
					/>
				</PanelBody>
			</InspectorControls>
		) }
	</>
);
  1. <BlockEdit { ...props } />:
    • BlockEdit コンポーネントが元々持っているブロック編集UIがそのまま表示される。{ ...props } で渡された全てのプロパティをそのまま BlockEdit に渡しているので、元のブロック編集機能を保持したまま新しい機能を追加している

addFilter の登録

addFilter(
	'editor.BlockEdit',
	'block-developer-cookbook/notes-field-control',
	addEditorNotesField
);

ブロックにメモがある場合に表示する

ブロックエディター内でブロックにメモがあることを表現する。

Gutenberg エディター内で表示されるブロックに対して、notes 属性の内容が存在する場合に has-notes というクラスを付与する。

/**
 * Add a custom class and CSS to show when a block has notes.
 *
 * @param {WPElement} BlockListBlock The original block list block component.
 *
 * @return {WPElement} Element to render.
 */
function addNotesDisplayClass( BlockListBlock ) {
   return ( props ) => {
       const { notes } = props.attributes;
       return (
           <BlockListBlock
               { ...props }
               className={ notes.length ? 'has-notes' : '' }
           />
       );
   };
}

addFilter(
   'editor.BlockListBlock',
   'block-developer-cookbook/notes-field-class',
   addNotesDisplayClass
);

addNotesDisplayClass 関数

addNotesDisplayClass は、BlockListBlock コンポーネントを拡張するための関数。BlockListBlock は、Gutenberg エディター内で表示される各ブロックを表す

props の取り扱い:

  1. props は、Gutenberg でブロックをレンダリングする際に渡されるプロパティのオブジェクト。このオブジェクトにはブロックに関する情報が含まれており、その中に attributes というプロパティがあり、ブロックの設定(例えば、notes 属性など)が格納されている。
  2. notes 属性は、ブロック内に入力されたメモ(編集者メモ)

className の設定:

  1. className={ notes.length ? 'has-notes' : '' } によって、notes 属性に内容があるブロックには has-notes クラスが追加され、内容がない場合はクラスが追加されない

<BlockListBlock /> コンポーネントのレンダリング:

  1. <BlockListBlock /> は、実際にブロックを表示するコンポーネント。{ ...props } を使って、元々渡されているすべてのプロパティ(例えば、attributessetAttributes など)をそのまま渡している

コード全体

notes-field.js
/**
 * WordPress dependencies
 */
import { addFilter } from '@wordpress/hooks';
import { InspectorControls } from '@wordpress/block-editor';
import { PanelBody, TextareaControl } from '@wordpress/components';
import { __ } from '@wordpress/i18n';

/**
 * Internal dependencies
 */
import './notes.scss';

/**
 * Filter the block settings to add the notes attribute.
 *
 * @param {Object} settings Settings for the block.
 * @param {string} name     The name of the block.
 *
 * @return {Object} he modified settings.
 */

function addNotesAttribute( settings, name ) {
	return {
		...settings,
		attributes: {
			...settings.attributes,
			notes: {
				type: 'string',
				default: '',
			},
		},
	};
}

addFilter(
	'blocks.registerBlockType',
	'block-developer-cookbook/notes-field',
	addNotesAttribute
);

/*
 * Add a custom control to the block inspector controls for every block.
 */
function addEditorNotesField( BlockEdit ) {
	return ( props ) => {
		const {
			attributes: { notes },
			setAttributes,
			isSelected,
		} = props;

		return (
			<>
				<BlockEdit { ...props } />
				{ isSelected && (
					<InspectorControls>
						<PanelBody>
							<TextareaControl
								label={ __(
									'Editorial Notes',
									'block-developer-cookbook'
								) }
								value={ notes }
								onChange={ ( newNotes ) =>
									setAttributes( { notes: newNotes } )
								}
								help={ __(
									'Add some editorial notes for this block'
								) }
							/>
						</PanelBody>
					</InspectorControls>
				) }
			</>
		);
	};
}

addFilter(
	'editor.BlockEdit',
	'block-developer-cookbook/notes-field-control',
	addEditorNotesField
);

/*
 * Add a custom class and CSS to show when a block has notes.
 */
function addNotesDisplayClass( BlockListBlock ) {
	return ( props ) => {
		const { notes } = props.attributes;
		return (
			<BlockListBlock
				{ ...props }
				className={ notes.length ? 'has-notes' : '' }
			/>
		);
	};
}

addFilter(
	'editor.BlockListBlock',
	'block-developer-cookbook/notes-field-class',
	addNotesDisplayClass
);
editorial-notes.php
<?php

/**
 * Plugin Name:       Editorial Notes
 * Description:       Add editorial notes to each block that are stored in a custom attribute.
 * Requires at least: 6.4
 * Requires PHP:      7.0
 * Version:           1.0.3
 * Author:            The WordPress Contributors
 * License:           GPL-2.0-or-later
 * License URI:       https://www.gnu.org/licenses/gpl-2.0.html
 * Text Domain:       editorial-notes
 *
 * @package block-developers-cookbook
 */

namespace BlockDevelopersCookbook;

function editional_notes_enqueue_scripts() {
    $note_field_file = plugin_dir_path(__FILE__) . '/build/notes-field.asset.php';

    if (file_exists($note_field_file)) {
        $assets = include $note_field_file;

        wp_enqueue_script(
            'notes-field',
            plugin_dir_url(__FILE__) . '/build/notes-field.js',
            $assets['dependencies'],
            $assets['version'],
            true
        );
    }
}
add_action('enqueue_block_editor_assets', __NAMESPACE__  . '\editional_notes_enqueue_scripts');

function editorial_notes_enqueue_block_editor_css() {
    $note_field_file = plugin_dir_path(__FILE__) . '/build/notes-field.asset.php';
    if (file_exists($note_field_file)) {
        $assets = include $note_field_file;

        wp_enqueue_style(
            'note-class',
            plugin_dir_url(__FILE__) . '/build/notes-field.css',
            array(),
            $assets['version'],
        );
    }
}
add_action('enqueue_block_assets',  __NAMESPACE__  . '\editorial_notes_enqueue_block_editor_css');
package.json
{
	"name": "editorial-notes",
	"version": "1.0.3",
	"description": "Add editorial notes to each block that are stored in a custom attribute.",
	"author": "The WordPress Contributors",
	"license": "GPL-2.0-or-later",
	"main": "build/index.js",
	"scripts": {
		"build": "wp-scripts build src/notes-field.js",
		"format": "wp-scripts format",
		"lint:css": "wp-scripts lint-style",
		"lint:js": "wp-scripts lint-js",
		"packages-update": "wp-scripts packages-update",
		"plugin-zip": "wp-scripts plugin-zip",
		"start": "wp-scripts start src/notes-field.js"
	},
	"prettier": "@wordpress/prettier-config",
	"devDependencies": {
		"@wordpress/scripts": "^30.15.0"
	}
}
notes.scss
.wp-block.has-notes::before {
	content: '❗';
	position: absolute;
	z-index: 999;
	top: -0.8rem;
	left: -2rem;
	height: 2rem;
	font-size: 2rem;
}

.wp-block.has-notes {
	border: 0.3rem dashed #ff0000;
}

参考:https://blockdevelopercookbook.com/recipes/editorial-notes/