長い間やろうと思って手をつけられていなかった目次の挿入、
ようやくやりました(笑)。
いつものように写すだけではやりたいことができなかったので備忘録を兼ねて残します。
やりたいこと
- 目次が欲しい記事のみ目次を挿入する
- 目次はショートコード[toc]で挿入する
- h2, h3タグがついているものを見出しに入れる(できたら大元の設定はフレキシブルに)
- hタグの階層が違う場合は、目次にも階層をつける(<ul>の入れ子にする)
- 目次をクリックすると該当hタグに飛ぶ(idを使用)
- idをつけていなくても対応
- プラグインは使わない
- (できれば)JavaScriptも使わない
- function.phpとcssだけで済ませたい
明確ですがやりたいことは山盛りです(笑)。
作業
先にカスタマイズの中身を。
どういう創意?工夫をしたかは次の章で。
下準備
functions.phpに追加する内容です。
$h_start, $h_endは必要に応じて変更してください。
他に必要に応じてstyle.cssに追記します。
前半(headerタグにid属性追加)
// headerにidをつける(h2-h6全て)
function add_ids_to_header($the_content){
if(is_single() || is_page()){ // 投稿ページまたは固定ページのとき
$pattern_tag = ‘/<h([2-6])(.*?)>(.*?)<\/h[2-6]>/im’; // 見出しタグの検索パターン
if(preg_match_all($pattern_tag, $the_content, $tags)){ // 本文中に見出しタグが含まれていれば
// $tags[0][$i] : マッチした文字列
// $tags[1][$i] : 見出しレベル(h3なら’3′)
// $tags[2][$i] : hxタグの属性全て
$pattern_id = ‘/^<h[2-6]\s.+?id=”.+?”.+?>$/im’; // 見出しのIDパターン
// マッチした見出しごとに処理
for($i = 0; $i < count($tags[0]); $i++){
$h_counter = $i + 1; // 見出しの連番 面倒なので階層に分けない
if(!preg_match($pattern_id, $tags[0][$i])){
// idが設定されていない場合
// 変更後のid入りのhタグに変更
$my_replace = ‘<h’ . $tags[1][$i] . ‘ id=”auto_id_h’ . $tags[1][$i] . ‘_’ . $h_counter . ‘”‘ . $tags[2][$i] . ‘>’;
$my_search = ‘/<h’ . $tags[1][$i] . $tags[2][$i] . ‘>/im’;
$the_content = preg_replace($my_search, $my_replace, $the_content, 1);
}
}
}
}
return $the_content;
}
add_filter(‘the_content’, ‘add_ids_to_header’, 10);
後半(目次ショートコード)
// 目次挿入(ショートコード)
function my_toc(){
// 目次にするhタグの範囲
$h_start = 2;
$h_end = 3;
if(is_single() || is_page()){ // 投稿ページまたは固定ページのとき
$the_content = get_the_content();
$the_content = add_ids_to_header($the_content); // idが入った状態の$the_contentを取得
$pattern_tag = ‘/<h([‘. $h_start . ‘-‘ . $h_end . ‘])\s.*?id=[“\’](.*?)[“\’].*?>(.*?)<\/h[‘. $h_start . ‘-‘ . $h_end . ‘]>/i’; // 見出しタグの検索パターン
if(preg_match_all($pattern_tag, $the_content, $tags)){ // 本文中に見出しタグが含まれていれば
// $tags[0][$i] : マッチした文字列
// $tags[1][$i] : 見出しレベル(h3なら’3′)
// $tags[2][$i] : id属性値
// $tags[3][$i] : hタグ中身
$nest = 0;
// 目次ヘッダー
$toc = ‘<div class=”toc”><h2>目次</h2><ul>’;
// マッチした見出しごとに処理
for($i = 0; $i < count($tags[0]); $i++){
$toc .= ‘<li><a href=”#’ . $tags[2][$i] . ‘”>’ . wp_strip_all_tags($tags[3][$i]) . ‘</a>’;
if($i+1 == count($tags[0])){ // for最後
$toc .= ‘</li>’;
$toc .= str_repeat(‘</ul></li>’, $nest);
}else if($tags[1][$i+1] == $tags[1][$i]){ // 次が同じ階層の時
$toc .= ‘</li>’;
}else if($tags[1][$i+1] > $tags[1][$i]){ // 次の階層が低い時
$toc .= ‘<ul>’;
$nest++;
}else if($tags[1][$i+1] < $tags[1][$i]){ // 次の階層が高い時
$toc .= ‘</li>’;
$toc .= str_repeat(‘</ul></li>’, $tags[1][$i] – $tags[1][$i+1]);
$nest = $nest – ($tags[1][$i] – $tags[1][$i+1]);
}
}
// 目次フッター
$toc .= ‘</div>’;
}
}
return $toc;
}
add_shortcode(‘toc’, ‘my_toc’, 11);
記事作成時
目次を入れたいところに[toc]と入力するだけです!
あと、見出しの階層はh2の次にh3じゃなくてh4が来たりしないようにしてください。
そこは面倒なのでチェックしていません。
出来上がりはこの記事の頭の方にある目次の通りです。
今回の工夫
今回のコードの大半はReferenceに記載したことが元です。
工夫した箇所は少ないけど地味に苦戦しました(笑)。
ショートコードで目次を挿入
これを達成するためにadd_filterでhタグにidを全挿入してからadd_shortcodeしました。
add_shortcodeで$the_contentを取得してもidなしだったので、
一度add_ids_to_header($the_content)として、add_filterで使うfunctionを通しています。
目次を挿入しない記事もとりあえず全部idつけてます。
デフォルトではadd_filterをした後にadd_shortcodeするのですが、
今回は順番重要だよ!という意を込めて、読み込み順序(デフォルト値)を入力しています。
hタグの中身に括弧が入るとidがつけられなかった
前半のidをつける部分で、正規表現がうまく使えず苦戦(笑)。
preg_replaceがうまくできませんでした。
preg_quoteやってみたり色々したのですが。
結局<hx …>の部分だけ探して置換しました。(x=2,3,4,5,6)
途中で変なタグが入るとエラーに
当然といえば当然なのですが、コードを書いたりすると<h2>とかのタグが入ることがありますね。
それにも反応してしまいました。←当然
投稿を作成するときに以下を気をつければOKです。
- 「コード」ブロックに入れる時はそのままでOK
- 「カスタムHTML」に入れる時はhtmlエンコード(<→<)が必要
ちなみに、今回わかったのは、コードで最初記載して、
完成したところでカスタムHTMLにすると
勝手にエンコードしてくれます。
(カスタムHTMLブロックって縦がスクロールになるので記事の全貌が見やすくていいんだよね。)
Reference
- [WordPress]全記事に目次を自動追加するスクリプトを書いてみた at そんなにGeekじゃないエンジニアブログ
- ワードプレスで見出しに自動的に id 属性を付ける方法 at だえうワードプレス
- WordPress: フィルターより前にショートコードを実行させる at 半月期
(今回やるべきなのはその逆なので考えることはないけど)
余談
正直、目次をつけるなんてネットで調べれば簡単!と思っていたけど
一筋縄ではいかず思った以上に時間がかかってしまいました。
そんな訳で、備忘録がてらメモを残すことに。
さて、目次をつけることを見ていると
それと一緒に、「目次の下に広告を入れよう!」って出てくることも。
本当に多いですね。広告入れているブログ。
しかも、イラッとする量記事の中に挿入されてますよね。
広告消すアプリもあるから意味ないんじゃない?って思うし、
そういうアプリを使わず私は普通に見ているので、イラッとします。
hタグの直前に毎回とか本当に勘弁してほしいです。
記事の後に入るくらいにしてほしいよねー。
Leave a Reply