WordPressの任意の投稿に目次を挿入する方法。プラグインは使用せず、ショートコードを定義して対応する。
目次を自動挿入するショートコード
functions.phpに下記のコードを追記する。
<?php
class Toc_Shortcode {
private $add_script = false;
private $atts = array();
public function __construct() {
add_shortcode('toc', array($this, 'shortcode_content'));
add_action('wp_footer', array($this, 'add_script'), 999999);
add_filter('the_content', array($this, 'change_content'), 9);
}
function change_content($content) {
return "<div id=\"toc_content\">{$content}</div>";
}
public function shortcode_content($atts) {
global $post;
if (!isset($post))
return '';
$this->atts = shortcode_atts(array(
'id' => 'toc',
'class' => 'toc',
'title' => '目次',
'toggle' => true,
'opentext' => '開く',
'closetext' => '閉じる',
'close' => false,
'showcount' => 2,
'depth' => 0,
'toplevel' => 2,
'scroll' => 'smooth',
), $atts);
$this->atts['toggle'] = (false !== $this->atts['toggle'] && 'false' !== $this->atts['toggle']) ? true : false;
$this->atts['close'] = (false !== $this->atts['close'] && 'false' !== $this->atts['close']) ? true : false;
$content = $post->post_content;
$headers = array();
preg_match_all('/<([hH][1-6]).*?>(.*?)<\/[hH][1-6].*?>/u', $content, $headers);
$header_count = count($headers[0]);
$counter = 0;
$counters = array(0, 0, 0, 0, 0, 0);
$current_depth = 0;
$prev_depth = 0;
$top_level = intval($this->atts['toplevel']);
if ($top_level < 1) $top_level = 1;
if ($top_level > 6) $top_level = 6;
$this->atts['toplevel'] = $top_level;
// 表示する階層数
$max_depth = (($this->atts['depth'] == 0) ? 6 : intval($this->atts['depth']));
$toc_list = '';
for ($i = 0; $i < $header_count; $i++) {
$depth = 0;
switch (strtolower($headers[1][$i])) {
case 'h1':
$depth = 1 - $top_level + 1;
break;
case 'h2':
$depth = 2 - $top_level + 1;
break;
case 'h3':
$depth = 3 - $top_level + 1;
break;
case 'h4':
$depth = 4 - $top_level + 1;
break;
case 'h5':
$depth = 5 - $top_level + 1;
break;
case 'h6':
$depth = 6 - $top_level + 1;
break;
}
if ($depth >= 1 && $depth <= $max_depth) {
if ($current_depth == $depth) {
$toc_list .= '</li>';
}
while ($current_depth > $depth) {
$toc_list .= '</li></ul>';
$current_depth--;
$counters[$current_depth] = 0;
}
if ($current_depth != $prev_depth) {
$toc_list .= '</li>';
}
if ($current_depth < $depth) {
$class = $current_depth == 0 ? ' class="toc-list"' : '';
$style = $current_depth == 0 && $this->atts['close'] ? ' style="display: none;"' : '';
$toc_list .= "<ul{$class}{$style}>";
$current_depth++;
}
$counters[$current_depth - 1]++;
$number = $counters[0];
for ($j = 1; $j < $current_depth; $j++) {
$number .= '.' . $counters[$j];
}
$counter++;
$toc_list .= '<li><a href="#toc' . ($i + 1) . '"><span class="contentstable-number">' . $number . '</span> ' . $headers[2][$i] . '</a>';
$prev_depth = $depth;
}
}
while ($current_depth >= 1) {
$toc_list .= '</li></ul>';
$current_depth--;
}
$html = '';
if ($counter >= $this->atts['showcount']) {
$this->add_script = true;
$toggle = '';
if ($this->atts['toggle']) {
$toggle = ' <span class="toc-toggle">[<a class="internal" href="javascript:void(0);">' . ($this->atts['close'] ? $this->atts['opentext'] : $this->atts['closetext']) . '</a>]</span>';
}
$html .= '<div' . ($this->atts['id'] != '' ? ' id="' . $this->atts['id'] . '"' : '') . ' class="' . $this->atts['class'] . '">';
$html .= '<p class="toc-title">' . $this->atts['title'] . $toggle . '</p>';
$html .= $toc_list;
$html .= '</div>' . "\n";
}
return $html;
}
public function add_script() {
if (!$this->add_script) {
return false;
}
$var = wp_json_encode(array(
'open_text' => isset($this->atts['opentext']) ? $this->atts['opentext'] : '開く',
'close_text' => isset($this->atts['closetext']) ? $this->atts['closetext'] : '閉じる',
'scroll' => isset($this->atts['scroll']) ? $this->atts['scroll'] : 'smooth',
));
?>
<script type="text/javascript">
var xo_toc = <?php echo $var; ?>;
let xoToc = () => {
const entryContent = document.getElementById('toc_content');
if (!entryContent) {
return false;
}
/**
* スムーズスクロール関数
*/
let smoothScroll = (target, offset) => {
const targetRect = target.getBoundingClientRect();
const targetY = targetRect.top + window.pageYOffset - offset;
window.scrollTo({
left: 0,
top: targetY,
behavior: xo_toc['scroll']
});
};
/**
* アンカータグにイベントを登録
*/
const wpadminbar = document.getElementById('wpadminbar');
const smoothOffset = (wpadminbar ? wpadminbar.clientHeight : 0) + 2;
const links = document.querySelectorAll('.toc-list a[href*="#"]');
for (let i = 0; i < links.length; i++) {
links[i].addEventListener('click', function(e) {
const href = e.currentTarget.getAttribute('href');
const splitHref = href.split('#');
const targetID = splitHref[1];
const target = document.getElementById(targetID);
if (target) {
e.preventDefault();
smoothScroll(target, smoothOffset);
} else {
return true;
}
return false;
});
}
/**
* ヘッダータグに ID を付与
*/
const headers = entryContent.querySelectorAll('h1, h2, h3, h4, h5, h6');
for (let i = 0; i < headers.length; i++) {
headers[i].setAttribute('id', 'toc' + (i + 1));
}
/**
* 目次項目の開閉
*/
const tocs = document.getElementsByClassName('toc');
for (let i = 0; i < tocs.length; i++) {
const toggle = tocs[i].getElementsByClassName('toc-toggle')[0].getElementsByTagName('a')[0];
toggle.addEventListener('click', function(e) {
const target = e.currentTarget;
const tocList = tocs[i].getElementsByClassName('toc-list')[0];
if (tocList.hidden) {
target.innerText = xo_toc['close_text'];
} else {
target.innerText = xo_toc['open_text'];
}
tocList.hidden = !tocList.hidden;
});
}
};
xoToc();
</script>
<?php
}
}
new Toc_Shortcode();
ショートコードブロックで[toc]
と入力すると、その投稿ページの目次が自動生成されるようになる。
カスタマイズCSSはこちら。
/* toc */
#toc {
margin-top: 10px;
margin-bottom: 35px;
font-size: 15px;
padding-right: 16px;
padding-left: 16px;
padding-top: 5px;
border-top: 2px solid #2A403A;
border-bottom: 2px solid #2A403A;
display: inline-block;
}
#toc .toc-title {
font-size: 16px;
font-weight: bold;
color: #2A403A;
text-align: left;
margin: 10px 0;
}
#toc .toc-title:before {
content: '';
display: inline-block;
width: 18px;
height: 18px;
margin-right: 8px;
background-image: url(icon/toc-16.svg);
background-size: contain;
vertical-align: middle;
}
#toc .toc-toggle a {
color: #2A403A;
}
#toc ul {
counter-reset: number;
list-style: none;
margin-bottom: 0px;
padding-top: 5px;
padding-left: 8px;
padding-bottom: 5px;
}
#toc ul li {
line-height: 1.25em;
list-style-type: none;
}
#toc ul li a {
color: #000;
text-decoration: none;
font-size: 14px;
}
#toc ul li a:hover {
color: #aa4d41;
}
#toc ul ul li {
line-height: 1.25em;
margin-bottom: 0.5em;
margin-left: 18px;
}
#toc ul ul li a {
color: #000;
padding-right: 6px;
text-decoration: none;
font-size: 14px;
}
#toc_container ul ul li a:hover {
color: #aa4d41;
}
/* /toc */
備考
JavaScriptで目次を実装すると、表示のちらつきが発生したため、phpで作成した。
ショートコードのカスタマイズや、投稿全てに目次を自動挿入したい場合は、リンク先を参照すること。
参照
https://xakuro.com/blog/wordpress/277/
Related Tags