1. ホーム
  2. WordPress

【WordPress】registerFormatType の使い方|リッチテキストに独自の書式ボタンを追加する方法

Share

ブロックエディターで文字を選択すると、太字やリンクのボタンが並んだ小さなツールバーが出てきます。ここに「蛍光ペンを引く」「用語をハイライトする」といった独自のボタンを足せるのが、registerFormatType() を使ったフォーマット API です。この記事では、選択した文字に自分で決めたタグを付けられるようにする手順を、必要な設定項目から属性の持たせ方、保存するとタグが消えてしまうときの対処までまとめて解説します。ブロックそのものを作るわけではないので、カスタムブロック開発よりずっと少ないコードで始められます。

書式は「文字の一部に付けるインラインタグ」

ブロックエディターでいう書式(format)は、段落や見出しなどのリッチテキストの一部分に付けるインライン要素のことです。太字なら <strong>、リンクなら <a> が選択範囲を囲みます。registerFormatType() はこの仕組みに独自の項目を追加する関数で、たとえば <mark> で囲んで蛍光ペン風にする、<abbr> で略語の説明を持たせる、といったことができます。

書式の実体は、リッチテキストの値に対する「この範囲にこのタグを付ける」という情報です。エディター内部では文字列とは別に書式の情報を保持していて、保存するときに HTML のタグへ書き出されます。そのため、自分で文字列に対して正規表現でタグを差し込むような処理は必要ありません。用意するのは「ボタンを押したときに書式を付け外しする」コードだけです。

カスタムブロックとの使い分け

まとまった領域として扱いたいもの、たとえば注意書きのボックスや料金表のような塊はブロックにします。一方で、文章の途中にある単語だけを装飾したい、単語に補足情報を持たせたいという用途はブロックでは表現できません。段落の中に別のブロックを入れることはできないからです。こうした「文中の一部だけ」に手を入れたいときが書式の担当範囲です。

ブロックを作るときは block.jsonedit / save が必要ですが、書式に save はありません。どう保存されるかは tagNameclassName の指定だけで決まるので、書くコードは登録処理とツールバーのボタンだけで済みます。

registerFormatType の基本の書き方

registerFormatType()@wordpress/rich-text パッケージの関数で、第1引数に書式の名前、第2引数に設定オブジェクトを渡します。まずは選択範囲を <mark> で囲むだけの、最小構成の例を見てみます。

src/formats/highlight.js
import { registerFormatType, toggleFormat } from '@wordpress/rich-text';
import { RichTextToolbarButton } from '@wordpress/block-editor';

const FORMAT_NAME = 'mytheme/highlight';

// ツールバーに出すボタン。書式の付け外しはここで行う
function HighlightButton( { isActive, value, onChange } ) {
    return (
        <RichTextToolbarButton
            icon="edit"
            title="ハイライト"
            isActive={ isActive }
            onClick={ () => {
                // 付いていれば外す、付いていなければ付ける
                onChange( toggleFormat( value, { type: FORMAT_NAME } ) );
            } }
        />
    );
}

registerFormatType( FORMAT_NAME, {
    title: 'ハイライト',   // ツールバーやメニューに表示される名前
    tagName: 'mark',      // 保存されるときのタグ
    className: null,      // タグだけで判別する(後述)
    edit: HighlightButton,
} );

これをエディターで読み込むと、文字を選択したときのツールバー(範囲が狭いときは「その他」の中)に「ハイライト」が現れます。押すと選択範囲が <mark>テキスト</mark> になり、もう一度押すと元に戻ります。toggleFormat() が付け外しの判断まで行ってくれるので、自分で状態を見る必要はありません。

書式の名前は 名前空間/名前 の形にします。使えるのは小文字の英数字とハイフンで、コアの書式(core/bold など)と衝突しないようテーマやプラグイン名を接頭辞にしておきます。

設定できる項目

第2引数に渡せる主な項目は次のとおりです。title / tagName / className / edit の4つはほぼ必ず指定します。

項目説明
titleボタンやメニューに表示される名前。必須
tagName保存時に選択範囲を囲む HTML タグ名
classNameタグに付けるクラス名。null にするとタグ名だけで判別する
editエディターに描画するコンポーネント。ツールバーのボタンをここで返す
attributes書式が持つ属性の対応表。{ 内部名: 'HTML属性名' } の形で書く
interactiveリンクのように操作できる要素を扱う書式で true にする
object画像のように中身を持たない要素を扱う書式で true にする

tagName と className の関係

tagNameclassName は、書き出す形と「その書式かどうか」を見分ける条件の両方を兼ねています。className: null ならタグ名だけで判別するので、上の例では文書中の <mark> がすべてこの書式として扱われます。

クラス名を指定した場合は、そのクラスが付いているものだけが対象になります。<span> のように単体では意味を持たないタグを使うときは、クラス名で区別するのが定番です。

src/formats/highlight.js
registerFormatType( 'mytheme/marker', {
    title: 'マーカー',
    tagName: 'span',
    className: 'has-marker',   // <span class="has-marker"> になる
    edit: MarkerButton,
} );

この指定だと、保存される HTML は <span class="has-marker">テキスト</span> になります。見た目はテーマの CSS で .has-marker に対して書けばよく、エディター側の見た目を揃えたいときは同じ CSS をエディター用スタイル(add_editor_style()enqueue_block_editor_assets)でも読み込みます。

edit コンポーネントに渡ってくる値

edit に指定したコンポーネントは、リッチテキストが選択されている間ずっと描画されます。ここに渡ってくる props を使って、現在の状態を読み取ったり書式を書き換えたりします。

props説明
isActiveカーソル位置または選択範囲にこの書式が付いているか
valueリッチテキストの現在の値(文字列と書式情報のセット)
onChange書き換えた値を反映する関数。新しい value を渡す
activeAttributesこの書式が持っている属性の現在値。attributes の内部名で引ける
contentRef編集領域の DOM への ref。ポップオーバーの位置合わせに使う

value は文字列ではなく、テキストと書式の情報をまとめたオブジェクトです。直接組み立てるものではないので、次に紹介する専用の関数に渡して加工します。

書式を付ける・外す3つの関数

@wordpress/rich-text には、値を加工して新しい値を返す関数が用意されています。いずれも元の value を書き換えるのではなく、新しい値を返すので、その結果を onChange() に渡します。

関数説明
toggleFormat()付いていれば外し、付いていなければ付ける
applyFormat()書式を付ける。属性を渡して更新するときにも使う
removeFormat()書式を外す

単純なオン・オフだけなら toggleFormat() ひとつで足ります。属性を持つ書式では、「入力された値で付け直す」ために applyFormat()、「消す」ために removeFormat() を使い分けます。

属性を持つ書式を作る

略語の説明を <abbr title="…"> で持たせる、といった「値を伴う書式」も作れます。attributes{ 内部名: 'HTML属性名' } の対応表を書いておくと、その属性の現在値が activeAttributes から内部名で読めるようになります。

src/formats/abbr.js
import {
    registerFormatType,
    applyFormat,
    removeFormat,
} from '@wordpress/rich-text';
import { RichTextToolbarButton } from '@wordpress/block-editor';

const FORMAT_NAME = 'mytheme/abbr';

function AbbrButton( { isActive, value, onChange, activeAttributes } ) {
    const onClick = () => {
        if ( isActive ) {
            // すでに付いているなら外す
            onChange( removeFormat( value, FORMAT_NAME ) );
            return;
        }

        // 現在の説明文を初期値にして入力を求める
        const description = window.prompt(
            '説明を入力してください',
            activeAttributes.description || ''
        );

        if ( ! description ) {
            return;
        }

        onChange(
            applyFormat( value, {
                type: FORMAT_NAME,
                attributes: { description },   // 内部名で渡す
            } )
        );
    };

    return (
        <RichTextToolbarButton
            icon="editor-help"
            title="略語の説明"
            isActive={ isActive }
            onClick={ onClick }
        />
    );
}

registerFormatType( FORMAT_NAME, {
    title: '略語の説明',
    tagName: 'abbr',
    className: null,
    attributes: {
        description: 'title',   // 内部名 description ⇔ HTML の title 属性
    },
    edit: AbbrButton,
} );

保存される HTML は <abbr title="世界保健機関">WHO</abbr> のようになります。applyFormat() に渡す属性のキーは HTML 側の名前ではなく attributes で決めた内部名(この例では description)です。ここを間違えると属性が付かないので注意してください。

入力欄をきちんと作るなら、window.prompt() の代わりに @wordpress/componentsPopoverTextControl を組み合わせ、contentRefuseAnchor() に渡して選択範囲の近くに表示させます。まずは動作を確かめたい段階なら、上のように単純な入力から始めても構いません。

キーボードショートカットを割り当てる

ツールバーのボタンに加えて、ショートカットキーからも呼び出せます。RichTextShortcut をボタンと並べて返すだけで、edit の中に複数の要素を置けます。

src/formats/highlight.js
import { Fragment } from '@wordpress/element';
import {
    RichTextToolbarButton,
    RichTextShortcut,
} from '@wordpress/block-editor';

function HighlightButton( { isActive, value, onChange } ) {
    const onToggle = () =>
        onChange( toggleFormat( value, { type: FORMAT_NAME } ) );

    return (
        <Fragment>
            {/* primary は Windows なら Ctrl、Mac なら command */}
            <RichTextShortcut
                type="primary"
                character="h"
                onUse={ onToggle }
            />
            <RichTextToolbarButton
                icon="edit"
                title="ハイライト"
                isActive={ isActive }
                onClick={ onToggle }
                shortcutType="primary"
                shortcutCharacter="h"
            />
        </Fragment>
    );
}

shortcutTypeshortcutCharacter をボタンにも渡しておくと、ツールチップにショートカットが併記されます。コアの書式が使っている組み合わせ(太字の Ctrl + B など)とぶつからない文字を選んでください。

書いた JavaScript をエディターに読み込ませる

書式の登録はブラウザー側で行われるので、作った JS を編集画面で読み込む必要があります。使うフックは編集画面だけで動く enqueue_block_editor_assets です。

functions.php
<?php
add_action( 'enqueue_block_editor_assets', 'mytheme_enqueue_formats' );

function mytheme_enqueue_formats() {
    $asset_file = get_theme_file_path( '/build/formats.asset.php' );

    // wp-scripts が出力する依存関係とバージョンをそのまま使う
    $asset = file_exists( $asset_file )
        ? require $asset_file
        : array(
            'dependencies' => array( 'wp-rich-text', 'wp-block-editor', 'wp-element' ),
            'version'      => '1.0.0',
        );

    wp_enqueue_script(
        'mytheme-formats',
        get_theme_file_uri( '/build/formats.js' ),
        $asset['dependencies'],
        $asset['version'],
        true
    );
}

依存関係には registerFormatType() を含む wp-rich-text、ツールバー用の wp-block-editor、JSX を動かすための wp-element が必要です。@wordpress/scripts でビルドしていれば、出力される .asset.php に必要な依存が列挙されているので、上のようにそれを渡すのが確実です。

ボタンが出ない・保存するとタグが消えるとき

ツールバーに項目が見つからない

ツールバーに直接並ぶボタンの数には限りがあり、あふれた分は右端の「その他」(縦三点のメニュー)に入ります。追加した書式は後発なので、たいていはこのメニューの中にあります。まずはメニューを開いて確認してください。

メニューにも無い場合は、登録そのものが失敗している可能性があります。ブラウザーのコンソールでエラーが出ていないか、名前が 名前空間/名前 の形式になっているか(大文字やアンダースコアは使えません)、スクリプトが編集画面で読み込まれているかを順に確認します。同じ名前で二重に登録しようとした場合もエラーになります。

保存し直すとタグや属性が消える

WordPress は投稿を保存するときに本文を KSES というフィルターに通し、許可リストに無いタグや属性を取り除きます。管理者と編集者は unfiltered_html 権限を持つので影響を受けませんが、投稿者や寄稿者の権限で保存すると、独自の data-* 属性などが落ちてしまうことがあります。「自分の環境では消えないのに、他の人が編集すると消える」ときはこれが原因です。

対処としては、wp_kses_allowed_html フィルターで使いたいタグと属性を許可リストに追加します。

functions.php
<?php
add_filter( 'wp_kses_allowed_html', 'mytheme_allow_format_tags', 10, 2 );

function mytheme_allow_format_tags( $tags, $context ) {
    // 投稿本文のときだけ許可を追加する
    if ( 'post' !== $context ) {
        return $tags;
    }

    $tags['mark'] = array(
        'class' => true,
    );

    $tags['span']['data-tooltip'] = true;

    return $tags;
}

許可するのは実際に使うタグと属性だけにとどめます。何でも通すように広げると、投稿できる HTML の制限がその分ゆるくなるためです。

別の書式と入れ替わってしまう

tagNameclassName の組み合わせが他の書式と同じだと、エディターがどちらの書式か判別できません。たとえば tagName: 'span'className: null で登録すると、あらゆる <span> がその書式として扱われ、他のプラグインが付けた <span> まで巻き込みます。汎用的なタグを使うときは必ずクラス名を付けて、他と重ならない名前にしてください。

不要な書式を外す

逆に、標準の書式を使わせたくない場合は unregisterFormatType() で外せます。書式はすべて登録されたあとに使えるようになるため、読み込みのタイミングによっては登録前に呼んでしまうことがあります。domReady() の中で実行すると確実です。

src/formats/unregister.js
import domReady from '@wordpress/dom-ready';
import { unregisterFormatType } from '@wordpress/rich-text';

domReady( () => {
    // 打ち消し線とインラインコードをツールバーから外す
    unregisterFormatType( 'core/strikethrough' );
    unregisterFormatType( 'core/code' );
} );

外すのはツールバーからの操作だけで、すでに保存されている HTML はそのまま残ります。既存の記事に使われている書式を外すと、編集画面では手を出せないのに表示上は残るという状態になるので、運用中のサイトでは影響範囲を確認してから行ってください。@wordpress/dom-ready を使う場合は、依存関係に wp-dom-ready を加えます。

まとめ

registerFormatType() は、リッチテキストの一部分に独自のタグを付けられるようにする関数です。tagNameclassName で保存される形と判別条件を決め、editRichTextToolbarButton を返すコンポーネントを渡せば、選択時のツールバーからオン・オフできるようになります。付け外しは toggleFormat()、値を伴う書式は attributes の対応表と applyFormat() / removeFormat() の組み合わせが基本形です。うまく動かないときは、ボタンが「その他」メニューに入っていないか、権限による KSES で属性が落ちていないかを確認すると原因にたどり着きやすくなります。ブロックを新しく作らずに文章表現を増やせるので、記事の書き手が多いサイトほど活躍する仕組みです。

参考ページ