Gutenberg ブロックで書かれた記事対応の新しい目次作成記事があります。
このブログはまだはてなブログですが、WordPress に移行したサイトがあり、プラグインなしで目次を自動作成、自動表示する機能を作成しました。
- WordPress サイト「そんなには褒めないよ。映画評」
- 投稿の新規追加、更新時に目次を作成する
- 目次は h2, h3 要素から作成する(h4〜も可)
- 目次はカスタムフィールドに保存する
- 目次の表示は投稿ページ表示時にカスタムフィールドを呼び出す
- すでに目次が挿入されている記事があることを想定する
01functions.php
ご利用になる場合は、iframe など空要素になるタグで問題があります。次の記事をお読みください。
アクションフック save_post を使って、記事の投稿、更新時に目次を作成してカスタムフィールドに保存します。
/*
目次作成 登録、更新時にカスタムフィールド toc に保存する
記事内に {toc} を書いておくと single.php から呼び出される
*/
add_action( 'save_post', 'create_table_of_contents', 10, 3 );
function create_table_of_contents( $post_ID, $post, $update ) {
// class-wp-widget-text.php のコードをを利用
$doc = new DOMDocument();
// Suppress warnings generated by loadHTML.
$errors = libxml_use_internal_errors( true );
// phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
@$doc->loadHTML(
sprintf(
'<!DOCTYPE html><html><head><meta charset="%s"></head><body>%s</body></html>',
esc_attr( get_bloginfo( 'charset' ) ),
$post->post_content
)
);
libxml_use_internal_errors( $errors );
// ここまでclass-wp-widget-text.php のコードをを利用
$dom = new DOMXPath($doc);
$children = $dom->query( '//body/*' );
$toc = '<ul class="table-of-contents">';
$h2 = false;
$h3 = false;
foreach($children as $child){
$tag = $child->tagName;
if($tag == 'h2' && !$h2){
$id = $child->nodeValue;
$child->setAttribute('id', $id);
$toc .= '<li><a href="#' . $id . '">' . $child->nodeValue . '</a>';
$h2 = true;
}else if($tag == 'h3'){
if(!$h3){
$toc .= '<ul>';
$h3 = true;
}
$id = $child->nodeValue;
$child->setAttribute('id', $id);
$toc .= '<li><a href="#' . $id . '">' . $child->nodeValue . '</a></li>';
}else if($tag == 'h2' && $h2){
if($h3){
$toc .= '</ul>';
$h3 = false;
}
$id = $child->nodeValue;
$child->setAttribute('id', $id);
$toc .= '</li><li><a href="#' . $id . '">' . $child->nodeValue . '</a>';
}
}
if($h3){
$toc .= '</ul>';
}
$toc .= '</li></ul>';
$html = $doc->saveXML();
preg_match('/<body>(.*)<\/body>/is', $html, $matches);
$post->post_content = $matches[1];
remove_action('save_post','create_table_of_contents', 10, 3);
wp_update_post($post);
add_action('save_post','create_table_of_contents', 10, 3);
$result = add_post_meta($post_ID, 'toc', $toc, true);
if(!$result){
update_post_meta($post_ID, 'toc', $toc);
}
}
アクションフック save_post は投稿が保存された直後に実行されますので、保存されたデータの h2, h3 要素を DOMDocument と DOMXPath を使ってパースし目次用のリストを作成します。同時に、h2, h3 要素に要素の内容を id として追加し、目次からのページ内リンクを貼ります。
h2, h3 要素の id を追加していますので投稿内容を再保存する必要があります。ただ、そのまま wp_update_post($post); としますと無限ループに陥りますので、いったんアクションフックを削除し、保存後に再設定しています。
カスタムフィールドへの保存は、新規保存をし、すでに同名キーが存在すれば追加保存しています。
なお、カスタムフィールドのキー名の先頭にアンダーバー _ を使いますとそのカスタムフィールドはスティルスになります。
02single.php
目次の表示は、記事内の目次を表示したい位置に {toc} を挿入しておき、single.php でカスタムフィールドの目次を呼び出して置き換えます。
ショートコードを使う方法もありますが、私は AdSense を挿入するためにフィルターフックを使っていますので下のようにこの方法でやっています。
add_filter('the_content', 'add_toc');
function add_toc($content){
global $post;
if( strpos( $content, '{toc}') !== false ){
$meta_value = get_post_meta( $post->ID, 'toc', true );
$content = str_replace( '{toc}', $meta_value, $content );
}
}
あとは目次 id="toc" class="table-of-contents" をすきに装飾するだけです。
- この方法で目次を表示しているサイト
03ライセンス等
ご使用の場合は以下の注意事項をお守りください。
- ライセンスは IMUZA.com にあります。
- 紹介は歓迎ですが、バグ対応ができなくなりますので転載はしないでください。
- 紹介していただく場合は、当記事へのリンクをお願いします。
- 自己責任でお使いください。
- お問い合わせ、バグの報告、仕様変更のご要望等は Contact Us までお願いします。
