概要
Wordpressの段落(rich-text)には選択した部分のサイズのみ変更する機能がありません。
しかしFormat API
を使用すれば書式ツールバーにその機能を追加することができるとのこと。
また通常では+ / - ボタンのように大きくや小さくなどのカスタムボタンにすると割と簡単にできるのですが、今回はFontSizePicker
を使い複数のサイズを選択できるようにしたいと思いやってみました。
2024.08.11修正しました
WordPresss6.6になり、新しい React JSX トランスフォーム(React 17以降)になったりで@wordpress/scriptでのbuildも@wordpress/scriptバージョン28.x.xで行わないとエラーになります。
参考URL
https://make.wordpress.org/core/2024/06/06/jsx-in-wordpress-6-6/
また、エディタもiframe化などが行われてきているので、その辺りの修正を追加しました。
要件定義
ざっくりと要件定義としては
-
Format API
にて選択部分のテキストの個別サイズ変更をFontSizePicker
を使って選択できるようにする -
FontSizePicker
は他のツール(ハイライト)などと同じようにPopover
にて選択テキスト近くに表示させる
仕様
仕様策定のため調べたこと
まずは仕様の策定ですが仕様の策定のため調べたことを箇条書きに記載します。
バグや仕様上どうしても上手く行かない方法などがあるのでその辺りを含めてい記述します。
-
Format APIの
registerFormatType
メソッドを使ってカスタムフォーマットを登録する必要がある -
選択したテキストにカスタムフォーマットを反映させるには、
applyFormat
メソッドもしくはtoggleFormat
メソッドにて行う。 -
applyFormat
メソッド /toggleFormat
メソッドを実行するとエディタ全体(Popover含め)でリペイントもしくはリフローが発生する場合がある。特にFont Sizeの変更はリフローが発生する。またFontSizePicker自体もリフローされてしまう様子。 -
FontSizePickerの基本的な使い方としては、onChange関数にて変更があった際に
applyFormat
メソッド /toggleFormat
メソッドを実行させる。 -
ただし上記の方法でサイズボタンクリックですぐに
applyFormat
メソッド /toggleFormat
メソッドにて反映させると選択ボタンのアニメーションが必ず左端から飛んでくるようになりバタバタした形で不恰好になる -
上記を簡単に避けるにはサイズボタンをクリックでPopoverを閉じてしまえば良いが、どの大きさを選択したいか迷っているときは何度もFontSizePickerを表示させないといけなくなり、使い勝手が悪くなる。
-
別の方法としてはサイズボタンをクリックしただけでは
applyFormat
メソッド /toggleFormat
メソッドを実行せずに仮の方法、例えばuseRefなどを用意て選択する値を保存しておき、決定ボタンなどをクリックでapplyFormat
メソッド /toggleFormat
メソッドして決定させるという方法であればアニメーションもバタバタせずにいける -
またWP6.4.1では解決しているがWP6.3.xでは初めに設定をするときPopoverが左端に飛んでしまうというバグがありその対策としてはあらかじめ初期値を設定した状態でPopoverを起動すると左端に飛ぶことはなくなる。
https://github.com/WordPress/gutenberg/pull/54736 (参考) -
また6.3.xの場合のFontSizePickerのonChangeにおいて、すでに値が保存されている場合にいったん別サイズのボタンをクリック後に元サイズのボタンをクリックしても値が取れない。これは6.4.xでは値が取れる
仕様の策定
-
RichTextToolbarButton
にカスタムボタンを追加しそれをクリックするとPopoverにてFontSizePickerが表示する - FontSizePickerはサイズ選択ボタンのみ使用する。
- ボタンクリックアニメーションがバタバタするので、サイズボタンクリックでは、サイズ情報は仮保存とし、”決定”ボタンクリックでデータ保存
applyFormet
メソッドを実行する - 上記仕様のため初回の
RichTextToolbarButton
にて追加したカスタムボタンクリック時にPopover前に初期値(font-size:1em)で登録する。 - “キャンセル”ボタンの仕様
- キャンセルボタンは、初期値のまま他のサイズを選択していない状態でクリックした場合はremoveFormatを実行して解除する
- すでに値が反映されている状態での再編集の場合は、新たに選択した値は反映されずに前回のままにする
- “クリア”ボタンの仕様
- クリアボタンを選択したテキスト部分のFormatを解除する
- 6.3.xの場合のFontSizePickerのonChangeにおいて、すでに値が保存されている場合にいったん別サイズのボタンをクリック後に元サイズのボタンをクリックしても値が取れない場合の対処を追加
- ユーザーによるイレギュラーな操作への対応
- 初回の選択で何も選択せず、”キャンセル”ボタンもクリックせずに他ブロックや同ブロックの別箇所クリックをするとPopoverが閉じてしまう場合の処理の追加
という仕様で作成。
実装
@wordpress/script
を使用して実装していきます。
下方にコード全体を入れています。
使用するコンポーネントの読み込み
import { useState, useEffect, useRef } from "@wordpress/element";
import { Popover, Button,FontSizePicker } from "@wordpress/components";
import { registerFormatType, applyFormat, removeFormat, getActiveFormat,
+ useAnchor
} from '@wordpress/rich-text';
import { RichTextToolbarButton, RichTextShortcut } from '@wordpress/block-editor';
import { __ } from "@wordpress/i18n";
import './editor.scss';
import './style.scss'
2024.08.11追加
useAnchor
を追加で読み込んでいます。
RichTextToolbarButton用のアイコン(SVG)の設定
const font_size_icon = (<svg id="b" data-name="layer" xmlns="http://www.w3.org/2000/svg" width="293" height="219" viewBox="0 0 293 219">
<g id="c" data-name="container">
<g id="d" data-name="group">
<path id="e" data-name="path" d="m240.95,55.63h-40.35l-22.48,70.54,17.29,54.26h52l10.99,38.56h34.6l-52.05-163.37Zm-38.92,96.45s15.4-55.6,18.75-69.07c3.34,13.47,18.75,69.07,18.75,69.07h-37.49Z" stroke-width="0"/>
<path id="f" data-name="path" d="m123.87,0h-54.1L0,219h46.39l14.73-51.7h71.42l14.73,51.7h46.39L123.87,0Zm-52.18,129.29s20.65-74.53,25.13-92.59c4.48,18.05,25.13,92.59,25.13,92.59h-50.26Z" stroke-width="0"/>
</g>
</g>
</svg>);
registerFormatType / カスタムフォーマットタイプの登録
タグはspanを使用。クラス名なども任意のものを作成しておきます。
attributesはstyleのみを設定しました。
registerFormatType('efc-format/fzs', {
title: __('font size select', 'efc-block'),
tagName: 'span',
className:'has-font-size-picker',
attributes: {
'style': '',
'id': ''
},
edit: MyFontSizeChange,
});
edit
プロパティにボタンなどのコンポーネント設定をしていきます。(MyFontSizeChange
コンポーネント)
MyFontSizeChangeコンポーネント
渡されるprops
const MyFontSizeChange = (props) => {
const { isActive, value, onChange, contentRef} = props;
/** 以下処理... */
}
registerFormatTypeのeditプロパティに登録するコンポーネントに渡されるpropsは
isActive
, value
, onChange
, contentRef
, activeAttributes
などがあります。
- isActive : カスタムフォーマットが該当テキストに反映されているか
- value : 設定の値
- onChange : onChange関数は、変更された入力値を処理するために使用
- contentRef : ブロックのコンテンツが入った React 要素
- activeAttributes : 設定されたattributes
※ただし場合により上手く値を取得できない場合ありなので今回は未使用
使用する値の管理 - useState / useRef の設定
各コメントに説明を入れています。
//Popoverの開閉状態をuseStateで管理するため
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
- //選択したテキストの情報ObjectをuseStateで管理するため
- //useAnchorを使用するため不必要になったので削除2024.08.11
- const [popoverPosition, setPopoverPositon] = useState(null);
//フォントサイズを格納する変数をuseStateで管理
const [fontSize, setFontSizeState] = useState(null);
//前回に設定したfont sizeを格納
const [currentFontSize, setCurrentFontSize] = useState(null);
//前回設定した選択テキストのstart位置を管理 カーソル位置が同じブロック内だとPopoverが閉じない対策のため
const [currentValueStart, setCurrentValueStart] = useState(value.start);
//attributes.styleに入れるための値を格納しておく
const useRefFont = useRef(null);
//設定されたfont sizeを入れておくため。FontSizePickerのvalueに設定する
const useRefSize = useRef('1em');
FontSizePickerに表示する各サイズの設定
const fontSizes = [
{
name: __('極小','efc-block'),
slug: 'xsmall',
size: "0.65em",
},
{
name: __('小','efc-block'),
slug: 'small',
size: "0.75em",
},
{
name: __('中','efc-block'),
slug: 'middule',
size: "1.25em",
},
{
name: __('大','efc-block'),
slug: "large",
size: "1.5em"
},
{
name: __('特大','efc-block'),
slug: "xtralarge",
size: "1.75em"
},
];
相対サイズにて設定することで、親(段落)のサイズに対しての大きさとなります。
RichTextToolbarButtonの設定
const MyFontSizeChange = (props) => {
const { isActive, value, onChange, contentRef} = props;
/** 以下処理... */
/**
* toolbar buttonの設定
* RichTextToolbarButtonの設定
*/
//ショートカット用の設定
const shortcutType = 'primaryAlt';
const shortcutCharacter = 'f';
//ボタンのテキストを設定
const buttonText = __('フォントサイズ変更', 'efc-block');
//RichTextToolbarButtonの設定
return (
<div>
<RichTextShortcut type={shortcutType} character={shortcutCharacter} onUse={openPopover} />
<RichTextToolbarButton
icon={font_size_icon}
className="rubySetBtn"
title={buttonText}
onClick={ () => {
openPopover()
}}
isActive={ isActive }
shorcutType={shortcutType}
shorcutCharacter={shortcutCharacter}
/>
{
//Popover
isPopoverOpen &&
(<MyFontSizePoporver></MyFontSizePoporver>)
}
</div>
);
}
RichTextToolbar
にボタンを表示させる処理です。
onClick
プロパティにてPopoverを表示させる関数を実行させています。
Popover自体はMyFontSizePoporver
コンポーネントとして設定します。
RichTextShortcut
でラップするとショートカットキーでonClickと同じ動作ができます。
openPopover関数
MyFontSizePoporver
コンポーネント を開くための関数
/**
* RichTextToolbarButtonで追加したボタンをクリックした時の処理
* Popoverを開く処理
*/
const openPopover = () => {
if (!isActive) {
applyFontSizeFormat("1em");//初回のみ最初に設定
}
/**
* useAnchorを使用するので不必要に2024.08.11
- if (contentRef.current) {
- //Popoverの表示位置のため選択テキストのObjectをセットしておく
- setPopoverPositon(getSelectedRange());
- }
*/
setIsPopoverOpen(true);
};
MyFontSizePoporverコンポーネント
Popoverコンポーネント
/**
* Popoverコンポーネントの設定
*/
const MyFontSizePoporver = () => {
useEffect(() => {
if (isActive) {
//efc-format/fzsの設定されている値を取得
const activeFormat = getActiveFormat(value, 'efc-format/fzs');
//保存前(更新ボタン未クリック)と保存後でデータの持ち方が違うので、それぞれに対応するため
const chkAttributes = activeFormat.unregisteredAttributes ? activeFormat.unregisteredAttributes : activeFormat.attributes;
//設定してあるstyleからフォントサイズのみ取り出す
const styledFontsize = chkAttributes.style.replace('--custom-font-size:', '');
//font-size変更前/変更後のチェックのためのstateそれぞれに格納しておく
setFontSizeState(styledFontsize);
setCurrentFontSize(styledFontsize);
//fontSizePickerのvalueに設定しているuseRefに入れる
useRefSize.current = styledFontsize;
}
}, []);
return (
<Popover
anchor={popoverPosition}
placement="bottom-start"
focusOnMount={true}
onFocusOutside={() => {
// 初期値(1em)のままの場合はFormatを削除
resetFontSizeFormat();
}}
>
<div className="efc-fontSize-popover-content" onClick={(e) => { /*popoverClick(e.target)*/}}>
<FontSizePicker
fontSizes={fontSizes}
value={ useRefSize.current }
disableCustomFontSizes={true}
units={["em"]}
//onChangeでは保存しない
onChange={(newFontSize) => {
/**
* 6.3.xでは既存選択ボタン以外をクリック後に既存選択ボタンを再選択してもnewFontSizeに
* 値が入らないので、上記popoverClick(e.target)にて対応する
*/
//console.log(newFontSize);
useRefSize.current = newFontSize;
const activeFormat = getActiveFormat(value, 'efc-format/fzs');
const chkAttributes = activeFormat.unregisteredAttributes ? activeFormat.unregisteredAttributes : activeFormat.attributes;
const fontSizeSpanDOM = document.getElementById(chkAttributes.id);
//選択したテキストの見た目の大きさのみ変更 confirmButtonクリックでuseState走らせ値をキチンと保存
fontSizeSpanDOM.style.setProperty('--custom-font-size', newFontSize);
} }
/>
<p className="popoverInfo"><span className="attention">サイズ選択後「決定」クリックで変更が反映</span></p>
<div className="buttonWrapper">
<Button
className="clearButton"
onClick={removeFontSizeFormat}
>{ __('クリア','efc-block') }</Button>
<Button
variant="secondary"
className="confirmButton"
onClick={() => {
const activeFormat = getActiveFormat(value, 'efc-format/fzs');
const chkAttributes = activeFormat.unregisteredAttributes ? activeFormat.unregisteredAttributes : activeFormat.attributes;
const fontSizeSpanDOM = document.getElementById(chkAttributes.id);
if (fontSizeSpanDOM) {
const styledFontsize = chkAttributes.style.replace('--custom-font-size:', '');
fontSizeSpanDOM.style.setProperty('--custom-font-size', styledFontsize);
if (styledFontsize === '1em') {
removeFontSizeFormat();
} else {
closePopover();
}
} else {
closePopover();
}
}}
>{ __('キャンセル','efc-block') }</Button>
<Button
variant='primary'
className="confirmButton"
onClick={() => {
if (useRefSize.current === '1em') {
alert("フォントサイズを選択してください!");
}
else if (useRefSize.current === fontSize) {
closePopover();
} else {
conformFontSize(useRefSize.current);
}
}}
>{ __('決定','efc-block') }</Button>
</div>
</div>
</Popover>
);
};
Popoverの設定
Popover
が開いた時にuseEffect
を使用して初期化させている
以下の箇所はpropsのactiveAttributes
で取得できそうだが保存後ページリロード時にはactiveAttributes
では取得できなかった対策のためgetActiveFormat
を使って取得させている
//efc-format/fzsの設定されている値を取得
const activeFormat = getActiveFormat(value, 'efc-format/fzs');
//保存前(更新ボタン未クリック)と保存後でデータの持ち方が違うので、それぞれに対応するため
const chkAttributes = activeFormat.unregisteredAttributes ?
activeFormat.unregisteredAttributes : activeFormat.attributes;
下記の箇所はPopoverで開いたが何もフォントサイズを決定させずに同じ段落の違う部分をクリックしてPopoverが閉じた時に初期値のままになるのでその場合はFormat自体を削除する設定を追加している。
onFocusOutside={() => {
// 初期値(1em)のままの場合はFormatを削除
resetFontSizeFormat();
}}
ただし全く違う段落をクリックした際にはonFocusOutsideは感知しないのでそこのために別の処理を追加している。
resetFontSizeFormat関数
/**
* もしポップアップして何も選択せずに他のブロックをクリックした場合に
* 初期値(1em)のままFormatが残ってしまう。
* そのために処理で使う初期値の場合は削除する処理を行う関数
*/
const resetFontSizeFormat = () => {
if (getFormatAttributes().hasOwnProperty('style')) {
if (getFormatAttributes().style === '--custom-font-size:1em' && !isPopoverOpen) {
onChange(removeFormat(value, 'efc-format/fzs'));
}
}
}
getFormatAttributes関数
設定されているFormatのAttributes情報を取得
//設定されているFormatのAttributes情報を取得
const getFormatAttributes = () => {
//efc-format/fzsの設定されている値を取得
const activeFormat = getActiveFormat(value, 'efc-format/fzs');
if (!activeFormat) {
return {};
}
//保存前(更新ボタン未クリック)と保存後でデータの持ち方が違うので、それぞれに対応するため
const chkAttributes = activeFormat.unregisteredAttributes ? activeFormat.unregisteredAttributes : activeFormat.attributes;
return chkAttributes;
}
FontSizePickerの設定
onChangeプロパティ
通常はここでサイズ変更が行われた場合に設定する。
ここでapplyFormat
を実行するとPopover自体もリペイント/リフローが発生して選択ボタンアニメーションがバタバタとした動きになってしまうので仮保存の設定にしている。
またWP6.3.xの場合のFontSizePickerのonChangeにおいて、すでに値が保存されている場合にいったん別サイズのボタンをクリック後に元サイズのボタンをクリックしても値が取れない。
対応策として以下のようにラップしてpopoverClick関数内で対応する形で対応する
<div className="efc-fontSize-popover-content" onClick={(e) => { popoverClick(e.target)}}>
<FontSizePicker />
</div>
popoverClick
関数
2024.08.11修正
エディタがiframe化しているとdocument.getElementByIdなどでのDOM操作ができない。
コールバックの引数contentRef.currentに選択しているテキストの元ブロックの情報が入っているので、それを利用するように修正する。
こちらの関数は使用せずに、その下に記載するfontSizeChange
関数を使用するように修正しています。
/**
* FontSicePickerオーバーライド
* FontSizePickerにはonClickイベントがないので、divでラップしてそれにonClickを設置
*
* 気がついたらonChangeで他を選択後に既存値ボタンをクリックしてもきちんと値を取得できるようになっていた
* ので必要はないかもしれない。
*/
const popoverClick = (target) => {
//Popover のどこでもクリックしたら反応するのでターゲットを絞る処理を追加
const parent = target.closest('button');
const chkIDReg = /toggle-group-control-as-radio-group[-\d]+/;
if (parent !== null && parent.id && parent.getAttribute("id").match(chkIDReg)) {
const newFontSize = parent.getAttribute("data-value");
useRefSize.current = newFontSize;
const activeFormat = getActiveFormat(value, 'efc-format/fzs');
const chkAttributes = activeFormat.unregisteredAttributes ? activeFormat.unregisteredAttributes : activeFormat.attributes;
const fontSizeSpanDOM = document.getElementById(chkAttributes.id);
//選択したテキストの見た目の大きさのみ変更 confirmButtonクリックでuseState走らせ値をキチンと保存
fontSizeSpanDOM.style.setProperty('--custom-font-size', newFontSize);
}
}
fontSizeChange
関数
applyFormatで値を決定する方法だとPopoverがチカチカとフリックしてしまうため、一時的な見た目を合わせるため、DOMでの操作をする形にしています。
const fontSizeChange = (newFontSize) => {
useRefSize.current = newFontSize;
const activeFormat = getActiveFormat(value, 'efc-format/fzs');
const chkAttributes = activeFormat.unregisteredAttributes ? activeFormat.unregisteredAttributes : activeFormat.attributes;
if (chkAttributes) {
const fontSizeSpanDOM = contentRef.current.querySelector(`#${chkAttributes.id}`);
//選択したテキストの見た目の大きさのみ変更 confirmButtonクリックでuseState走らせ値をキチンと保存
fontSizeSpanDOM.style.setProperty('--custom-font-size', newFontSize);
}
}
その他関数群
getSelectedRange関数
2024.08.11修正
こちらもuseAnchorを使用する場合は、必要がなくなります。
/**
*
* @returns
* 選択テキストのObjectを取得して存在したら返す
* 存在しなかったら選択しているブロックの要素(段落)などを返す
*/
- const getSelectedRange = () => {
- const selection = window.getSelection();
- const range = selection.getRangeAt(0);
- return range ? range : contentRef.current
- }
closePopover関数
/** Popoverを閉じる関数 */
const closePopover = () => {
setIsPopoverOpen(false);
}
removeFontSizeFormat関数
クリアボタンをクリックした時に実行する関数。
全ての値なども一旦クリアさせる
//設定を削除
const removeFontSizeFormat = () => {
setFontSizeState(null);
setCurrentFontSize(null);
closePopover();
return (
onChange(removeFormat(value,'efc-format/fzs'))
);
};
その他の処理
resetFontSizeFormat()の実行
初期値(1em)のまま他のブロックを選択すると、Popoverが閉じてしまい初期値のままFormatが残ってしまう。他のブロックを選択した場合は変数なども引き継がないのでFormatの削除のしようがない。
対応策として次回この箇所を選択した場合に初期値のままなら一旦削除を実行させるようにする。
/**
* 初期値(1em)のままの場合はFormatを削除
* 他のブロックを選択すると、変数なども引き継がないのでFormatの削除のしようがないので
* 次回この箇所を選択した場合に初期値のままなら一旦削除を実行させる
*/
//
resetFontSizeFormat();
/**
Popoverが閉じない
Popover
が開いた状態で選択したテキストと同じブロックの違う箇所をクリックしてもPopover
が閉じない。
対策としてブロックないのカーソルの位置に変更があった場合はPopover
を閉じるようにする
/**
* カーソル位置が同じブロック内だとPopoverが閉じない対策
* startが変わると閉じるように設定
*/
if (currentValueStart !== value.start) {
setCurrentValueStart(value.start);
setFontSizeState(null);
setCurrentFontSize(null);
setIsPopoverOpen(false);
}
フォントサイズの確定
前回のフォントサイズと違う場合のみappleFormatを実行させる
//前回のフォントサイズと違う場合のみappleFormatを実行させる
useEffect(() => {
if (currentFontSize !== fontSize) {
applyFontSizeFormat(fontSize);
closePopover();
}
}, [fontSize]);
コード全体
index.js
import { useState, useEffect, useRef } from "@wordpress/element";
import { Popover, Button,FontSizePicker } from "@wordpress/components";
import { registerFormatType, applyFormat, removeFormat, getActiveFormat, useAnchor} from '@wordpress/rich-text';
import { RichTextToolbarButton, RichTextShortcut } from '@wordpress/block-editor';
import { __ } from "@wordpress/i18n";
import './editor.scss';
import './style.scss';
const font_size_icon = (<svg id="b" data-name="layer" xmlns="http://www.w3.org/2000/svg" width="293" height="219" viewBox="0 0 293 219">
<g id="c" data-name="container">
<g id="d" data-name="group">
<path id="e" data-name="path" d="m240.95,55.63h-40.35l-22.48,70.54,17.29,54.26h52l10.99,38.56h34.6l-52.05-163.37Zm-38.92,96.45s15.4-55.6,18.75-69.07c3.34,13.47,18.75,69.07,18.75,69.07h-37.49Z" stroke-width="0"/>
<path id="f" data-name="path" d="m123.87,0h-54.1L0,219h46.39l14.73-51.7h71.42l14.73,51.7h46.39L123.87,0Zm-52.18,129.29s20.65-74.53,25.13-92.59c4.48,18.05,25.13,92.59,25.13,92.59h-50.26Z" stroke-width="0"/>
</g>
</g>
</svg>);
const MyFontSizeChange = (props) => {
const { isActive, value, onChange, contentRef } = props;
/**
* Popover の設定
*/
//Popoverの開閉状態をuseStateで管理するため
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
//選択したテキストの情報ObjectをuseStateで管理するため
//useAnchorを使用するため不必要に
//const [popoverPosition, setPopoverPositon] = useState(null);
//フォントサイズを格納する変数をuseStateで管理
const [fontSize, setFontSizeState] = useState(null);
const [currentFontSize, setCurrentFontSize] = useState(null);
const [currentValueStart, setCurrentValueStart] = useState(value.start);
const useRefFont = useRef(null);
const useRefSize = useRef('1em');
const fontSizes = [
{
name: __('極小','efc-block'),
slug: 'xsmall',
size: "0.65em",
},
{
name: __('小','efc-block'),
slug: 'small',
size: "0.75em",
},
{
name: __('中','efc-block'),
slug: 'middule',
size: "1.25em",
},
{
name: __('大','efc-block'),
slug: "large",
size: "1.5em"
},
{
name: __('特大','efc-block'),
slug: "xtralarge",
size: "1.75em"
},
];
/**
*
* @returns
* 選択テキストのObjectを取得して存在したら返す
* 存在しなかったら選択しているブロックの要素(段落)などを返す
*
* useAnchorをしようするので不必要に
const getSelectedRange = () => {
const selection = window.getSelection();
const range = selection.getRangeAt(0);
return range ? range : contentRef.current
}
*/
/**
* useAnchorの設定
* useAnchorのsettingsにFormatTypeの情報+isActiveを渡すことで
* 正確な位置を取得してくれる
*/
const settings = {
name:'efc-format/fzs',
title: __('font size select', 'efc-block'),
tagName: 'span',
className: 'has-font-size-picker',
attributes: {
style: '',
},
edit: MyFontSizeChange,
};
const popoverAnchor = useAnchor({
editableContentElement: contentRef.current,
settings: { ...settings,isActive }
});
/**
* RichTextToolbarButtonで追加したボタンをクリックした時の処理
* Popoverを開く処理
*/
const openPopover = () => {
if (!isActive) {
applyFontSizeFormat("1em");//初回のみ最初に設定
}
/**
* useAnchorを使用するので不必要に
if (contentRef.current) {
//Popoverの表示位置のため選択テキストのObjectをセットしておく
//setPopoverPositon(getSelectedRange());
}
*/
setIsPopoverOpen(true);
};
/** Popoverを閉じる関数 */
const closePopover = () => {
setIsPopoverOpen(false);
}
/**
* Font Size の変更するための設定
* フォントサイズを反映させる
*/
const applyFontSizeFormat = (fontSize) => {
setCurrentFontSize(fontSize);
useRefFont.current = `--custom-font-size:${fontSize}`;
const myId = `cfz-${crypto.randomUUID()}`;
onChange(
applyFormat(value, {
type: 'efc-format/fzs',
attributes: {
style: useRefFont.current,
id: myId,
}
})
)
};
//設定を削除
const removeFontSizeFormat = () => {
setFontSizeState(null);
setCurrentFontSize(null);
closePopover();
return (
onChange(removeFormat(value,'efc-format/fzs'))
);
};
//前回のフォントサイズと違う場合のみappleFormatを実行させる
useEffect(() => {
if (currentFontSize !== fontSize) {
applyFontSizeFormat(fontSize);
closePopover();
}
}, [fontSize]);
//Confirm Font Size
const conformFontSize = (newFontSize) => {
setFontSizeState(newFontSize);
}
/**
* カーソル位置が同じブロック内だとPopoverが閉じない対策
* startが変わると閉じるように設定
*/
if (currentValueStart !== value.start) {
setCurrentValueStart(value.start);
setFontSizeState(null);
setCurrentFontSize(null);
setIsPopoverOpen(false);
}
/**------- Copmonentの設定---------- */
/**
* Popoverコンポーネントの設定
*/
const MyFontSizePoporver = () => {
useEffect(() => {
if (isActive) {
const activeFormat = getActiveFormat(value, 'efc-format/fzs');
const chkAttributes = activeFormat.unregisteredAttributes ? activeFormat.unregisteredAttributes : activeFormat.attributes;
const styledFontsize = chkAttributes.style.replace('--custom-font-size:', '');
setFontSizeState(styledFontsize);
setCurrentFontSize(styledFontsize);
useRefSize.current = styledFontsize;
}
},[]);
/**
* FontSicePickerオーバーライド
* FontSizePickerにはonClickイベントがないので、divでラップしてそれにonClickを設置
*
* FontSicePickerのonChangeがcurrentの場合でも一旦ほかに変更したら動作することを確認
const popoverClick = (target) => {
//Popover のどこでもクリックしたら反応するのでターゲットを絞る処理を追加
const parent = target.closest('button');
const chkIDReg = /toggle-group-control-as-radio-group[-\d]+/;
if (parent !== null && parent.id && parent.getAttribute("id").match(chkIDReg)) {
const newFontSize = parent.getAttribute("data-value");
useRefSize.current = newFontSize;
const activeFormat = getActiveFormat(value, 'efc-format/fzs');
const chkAttributes = activeFormat.unregisteredAttributes ? activeFormat.unregisteredAttributes : activeFormat.attributes;
const fontSizeSpanDOM = document.getElementById(chkAttributes.id);
//選択したテキストの見た目の大きさのみ変更 confirmButtonクリックでuseState走らせ値をキチンと保存
fontSizeSpanDOM.style.setProperty('--custom-font-size', newFontSize);
}
}
*/
const fontSizeChange = (newFontSize) => {
useRefSize.current = newFontSize;
const activeFormat = getActiveFormat(value, 'efc-format/fzs');
const chkAttributes = activeFormat.unregisteredAttributes ? activeFormat.unregisteredAttributes : activeFormat.attributes;
if (chkAttributes) {
const fontSizeSpanDOM = contentRef.current.querySelector(`#${chkAttributes.id}`);
//選択したテキストの見た目の大きさのみ変更 confirmButtonクリックでuseState走らせ値をキチンと保存
fontSizeSpanDOM.style.setProperty('--custom-font-size', newFontSize);
}
}
return (
<Popover
anchor={popoverAnchor}
placement="bottom-start"
focusOnMount={false}
>
<div className="efc-fontSize-popover-content">
<FontSizePicker
fontSizes={fontSizes}
value={ useRefSize.current }
disableCustomFontSizes={true}
units={["em"]}
onChange={(newFontSize) => {
fontSizeChange(newFontSize);
} }
/>
<p className="popoverInfo"><span className="attention">サイズ選択後「決定」クリックで変更が反映</span></p>
<div className="buttonWrapper">
<Button
className="clearButton"
onClick={removeFontSizeFormat}
>{ __('クリア','efc-block') }</Button>
<Button
variant="secondary"
className="confirmButton"
onClick={() => {
const activeFormat = getActiveFormat(value, 'efc-format/fzs');
//選択して反映させたFormatの情報(attributes)を取得
const chkAttributes = activeFormat.unregisteredAttributes ? activeFormat.unregisteredAttributes : activeFormat.attributes;
//const fontSizeSpanDOM = document.getElementById(chkAttributes.id);
//contentRef.currentにはFormatを反映させたテキストのritch-textのObjectが入っているのでそこからFormatを適応させたElementを取得
const fontSizeSpanDOM = contentRef.current.querySelector(`#${chkAttributes.id}`);
const styledFontsize = chkAttributes.style.replace('--custom-font-size:', '');
fontSizeSpanDOM.style.setProperty('--custom-font-size', styledFontsize);
if (styledFontsize === '1em') {
removeFontSizeFormat();
} else {
closePopover();
}
}}
>{ __('キャンセル','efc-block') }</Button>
<Button
variant='primary'
className="confirmButton"
onClick={() => {
if (useRefSize.current === '1em') {
alert("フォントサイズを選択してください!");
}
else if (useRefSize.current === fontSize) {
closePopover();
} else {
conformFontSize(useRefSize.current);
}
}}
>{ __('決定','efc-block') }</Button>
</div>
</div>
</Popover>
);
};
/**
* toolbar buttonの設定
* RichTextToolbarButtonの設定
*/
//ショートカット用の設定
const shortcutType = 'primaryAlt';
const shortcutCharacter = 'f';
//ボタンのテキストを設定
const buttonText = __('フォントサイズ変更', 'efc-block');
//RichTextToolbarButtonの設定
return (
<div>
<RichTextShortcut type={shortcutType} character={shortcutCharacter} onUse={openPopover} />
<RichTextToolbarButton
icon={font_size_icon}
className="rubySetBtn"
title={buttonText}
onClick={ () => {
openPopover();
}}
isActive={ isActive }
shorcutType={shortcutType}
shorcutCharacter={shortcutCharacter}
/>
{
//Popover
isPopoverOpen &&
(<MyFontSizePoporver></MyFontSizePoporver>)
}
</div>
);
}
registerFormatType('efc-format/fzs', {
title: __('font size select', 'efc-block'),
tagName: 'span',
className:'has-font-size-picker',
attributes: {
'style': '',
'id':''
},
edit: MyFontSizeChange,
});
スタイルシート
editor.scss
.has-font-size-picker {
--custom-font-size:1em;
font-size: var(--custom-font-size);
}
.efc-fontSize-popover-content{
padding: 12px;
min-width: 300px;
}
.popoverInfo {
margin-top: -12px;
font-size: 0.75rem;
color:#666;
}
.buttonWrapper {
display: flex;
gap: 8px;
}
.attention {
color:#3858e9;
}
style.scss
.has-font-size-picker {
--custom-font-size:1em;
font-size: var(--custom-font-size);
}
WordPress6.6以前の環境に合わせるためのPolyfillの追加
WordPress6.6での使用のためにBuildは、@wordpress/scriptsのバージョン28.x.xで行う必要がありました。
単純のアップデートしてbuildを仕直しすれば6.6でのOKなのですが、そうすると6.5などそれ以前のWordPress環境でエラーになります。
対処法としては、下記のURLに記載されているPolyfillを読み込ませておくと大丈夫でした。
まずは 任意のディレクトリを作成してそこに以下の内容でpackage.jsonを作成します。
{
"name": "runtime",
"version": "1.0.0",
"main": "webpack.conf.js",
"scripts": {
"build:runtime": "webpack --config webpack.config.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"devDependencies": {
"react": "^18.3.1",
"webpack": "^5.93.0",
"webpack-cli": "^5.1.4"
}
}
そしてインストールします。
$ npm i
同じ階層にwebpack.config.jsファイルを以下の記述で配置
const path = require('path');
module.exports = {
entry: {
'react-jsx-runtime': 'react/jsx-runtime',
},
output: {
path: path.resolve(__dirname, '../assets/js'),
filename: 'react-jsx-runtime.js',
library: {
name: 'ReactJSXRuntime',
type: 'window',
},
},
externals: {
react: 'React',
},
};
そして buildを実行
$ npm run build:runtime
そうするとassets/js
ディレクトリにreact-jsx-runtime.js
ファイルが作成されます。
PHPにて作成したreact-jsx-runtime.js
ファイルを読み込みます。
/**
* WordPress6.6以前用のPolyfill読み込み
*/
function my_plugin_block_editor_scripts() {
// JSXランタイムのポリフィルスクリプトを登録
wp_register_script(
'react-jsx-runtime',
plugins_url('assets/js/react-jsx-runtime.js', __FILE__),
[ 'react' ],
'18.3.0',
true
);
wp_enqueue_script('react-jsx-runtime');
}
add_action('enqueue_block_editor_assets', 'my_plugin_block_editor_scripts');
これで6.6以前のバージョンでも大丈夫になります。
雑感
今回はどうしてもFontSizePickerをPopoverにて使いたかったので割とトリッキーな手法になってしまっています。
もしかしてFontSizePicker
はInspectorControls
での使用を想定しているコンポーネントなのかもしれません。
Popoveコンポーネント、もしくはFontSizePickerなどはregisterFormatType
のedit
コールバック関数内にて定義をしていましたが、もしかしてuseMemoなどを使用して外部で定義をすると各Sizeボタンクリックで、即onChange(applyFormat())
を実行しても、フリッカーがおきないかもしれないので、時間を見てテストしたいと思っています。
FontSizePickerのonChangeにて、保存されている値のFont Sizeと同じボタンクリックで6.3.xでは値が取れなかったのが6.4.xでは値が取れるようになった件、何か知っている方がいましたらコメント残していただけるとありがたいです。
参考サイト/ページ
Format Library: Try to fix highlight popover jumping【GitHub】