/
投稿ページは長くなりやすいし、目次があると何が書かれているか一目で分かってとても便利^^
今回は相当悩みましたが、parse_blocks()
を利用して「ブロックエディタ対応」「リストがきれいにネストする」「カラム内の見出し( innerBlocks) も対応」バージョンが出来ました~ \(^o^)/
ブロックエディタになって以前作成した目次は使えないし、ネットにあったコードは、カラム内の見出しを抜き出せなかったり、リストのネストがおかしかったり、で、しばし休止。。。
リストのネストを直せばいいかな~、程度に考えていたのですが。。。
innerBlock
は配列だから独立した見出しと一緒に処理できないとか、add_filter( 'render_block' ~~)
にしてみたら目次がおかしくなるとか、あっちの沼こっちの沼に潜りまくり _ _;;
気になっていたところを潰していったら、こんなコードに。
もっと良いやり方があるかも ? ですが、一応これで完成にします ^^;;
目次の作り方 コード
PHP
<?php /* functions.php に直接書き込む場合はこの行を削除 */
function generate_my_toc($content) {
if ( !is_single() || !in_the_loop() || !is_main_query() ) return $content;
$toc = '';
$pv_level = 0;
$i = 0;
$blocks = parse_blocks($content);
function process_blocks($blocks, &$toc, &$content, &$i, &$pv_level) {
foreach ($blocks as $block) {
if ($block['blockName'] == 'core/heading') {
if ( preg_match('/<h([1-6]).*?>(.*?)<\/h\\1>/', $block['innerHTML'], $m) ) {
$level = intval($m[1]);
/* IDを見出しに追加する */
$content = preg_replace_callback(
"/(<h$level.*?>)(" . preg_quote($m[2], '/') . ")(<\/h$level>)/",
function($m2) use (&$i) {
return $m2[1] . '<span id="toc-' . $i . '">' . $m2[2] . '</span>' . $m2[3];
},
$content
);
/* 目次を登録 */
if ($pv_level == 0) { $toc .= ''; }
elseif ($level == $pv_level) { $toc .= '</li>'; }
elseif ($level > $pv_level) { $toc .= PHP_EOL . '<ul>'; }
elseif ($level < $pv_level) {
$toc .= '</li>' . PHP_EOL;
for ($count = 0; $count < ($pv_level - $level); $count++) {
$toc .= '</ul>' . PHP_EOL . '</li>';
}
}
$pv_level = $level;
$toc .= PHP_EOL . '<li><a href="#toc-' . $i . '">' . strip_tags($m[2]) . '</a>';
$i++;
}
}
/* innerBlocksが存在する場合、再帰的に処理 */
if ( !empty($block['innerBlocks']) ) {
process_blocks($block['innerBlocks'], $toc, $content, $i, $pv_level);
}
}
}
/* ブロックを処理 */
process_blocks($blocks, $toc, $content, $i, $pv_level);
/* 目次書き出し */
if ($toc) {
$toc_html = '
<nav class="my-toc">
<p class="screen-reader-text">ページの目次</p>
<ul class="toc_list">' . $toc . '</li>
</ul>
</nav>' . PHP_EOL;
$content = preg_replace('/<h2.*?>/i', $toc_html . '$0', $content, 1);
}
return $content;
}
add_filter('the_content', 'generate_my_toc', 1);
目次コードの説明
設置方法
上のコードをfunctions.php
に書き込むか、pots-toc.phpとして保存し functions.php
で読み込ませます。
( 別ファイルにする場合は、最初の <?php
をお忘れなくm(__)m )
固定ページやカスタム投稿にも表示させたい場合は、コード最初にある
if ( !is_single() || !in_the_loop() || !is_main_query() ) return $content;
の !is_single()
を !is_singular()
に書き換えます
全体の流れ
大雑把ですが、以下のような流れで処理しています。
parse_blocks()
でブロックを取得。foreach
で回して、独立した'core/heading'
を処理$level
に現在の hタグの 数字部分 (h2 → 2 とか) を代入- IDを見出しに追加 → hタグにidを入れている場合を考え、
idを入れた<span>
を挿入 - 目次を登録 →
$level
(処理中の行)、$pv_level
(前の行) を比較してネスト用のタグを設定 $level
(現在の数字) を$pv_level
に代入- 目次の行を完成
- 再帰的に処理 →
innerBlocks
に対して同様の処理を行う - ブロックを処理 → 2. 3. をまとめて処理
- 目次書き出し → 目次を整形して、最初の h2 上に書き出す
WordPressが用意してくれている'render_block'
でやると「最初のh2の上に置く」が上手く働かない為、'the_content'
で処理する形にしています。