よもやまチョモランマ

プラグインなしで目次を自動作成

このブログはあくまで自分用の「おぼえがき」だから、アフィリエイトのような目次や見出しはいらないと思ってたんですけど。
そういうのも作れないとなぁと思ったので、実装してみたいと思います。

【参考】ワードプレスで目次をプラグインなし(functions.php)で実装する方法

まずCSS

/*目次*/
.outline{
	border:1px dotted #D8D8D8;
	padding:20px;
	margin-top:20px;
	display:inline-block;
	font-size:0.9em;
	line-height:1.5em;
}
.outline__toggle{display: none;}
.outline__switch::before{
	content:"開く";
	cursor:pointer;
	border: solid 1px #D8D8D8;
	padding:5px;
	font-size:0.8rem;
	margin-left:5px;
	border-radius: 5px;
}
.outline__toggle:checked + .outline__switch::before{content:"閉じる"}
.outline__switch + .outline__list{
	overflow:hidden;
	width:0;
	height:0;
	margin-top:0;
	margin-left:-20px;
	transition: 0.2s;
}
.outline__toggle:checked + .outline__switch + .outline__list {
    width: auto;
    height: auto;
    margin-top: 20px;
    transition: 0.2s;
    border-top: dotted 1px #d2d2d2;
    padding-top: 1em;
}
.outline__item:before {content: normal;}
.outline__link{
	display:relative;
	color:#191919 !important;
}
.outline__link:hover{border:none;}
.outline__number{
	display: inline-block;
	color:#7F7F7F;
	background:#F2F2F2;
	padding:3px 6px;
	font-weight:400;
	font-size:1.2rem;
	margin-right: 5px;
}
label.outline__switch {
    position: relative;
    float: right;
}
li .outline__item{
list-style-type:none!important;
}
li .outline__item:before{
content:'- ';
}
ul .outline__item{
	list-style-type:none!important;
}
ul{
		-webkit-padding-start: 1.2em;
}

続いてfunction.php

//////////////////////////////////////////////////
//Original sanitize_callback
//////////////////////////////////////////////////
// CheckBox
function fit_sanitize_checkbox( $checked ) {
    return ( ( isset( $checked ) && true == $checked ) ? true : false );
}
// radio/select
function fit_sanitize_select( $input, $setting ) {
	$input = sanitize_key( $input );
    $choices = $setting->manager->get_control($setting->id)->choices;
    return ( array_key_exists( $input, $choices ) ? $input : $setting->default );
}
// number limit
function fit_sanitize_number_range( $number, $setting ) {
    $number = absint( $number );
    $atts = $setting->manager->get_control( $setting->id )->input_attrs;
    $min = ( isset( $atts['min'] ) ? $atts['min'] : $number );
    $max = ( isset( $atts['max'] ) ? $atts['max'] : $number );
    $step = ( isset( $atts['step'] ) ? $atts['step'] : 1 );
    return ( $min <= $number && $number <= $max && is_int( $number / $step ) ? $number : $setting->default );
}

//////////////////////////////////////////////////
//投稿ページ各種設定画面
//////////////////////////////////////////////////
function fit_post_cutomizer( $wp_customize ) {
// セクション
	$wp_customize->add_section( 'fit_post_section', array(
		'title'     => '投稿ページ設定 [LION用]',
		'priority'  => 1,
	));

	// 目次の表示/非表示 セッティング
	$wp_customize->add_setting( 'fit_post_outline', array(
		'default'   => 'value1',
		'type' => 'option',
		'sanitize_callback' => 'fit_sanitize_select',
	));
	// 目次の表示/非表示 コントロール
	$wp_customize->add_control( 'fit_post_outline', array(
		'section'   => 'fit_post_section',
		'settings'  => 'fit_post_outline',
		'label'     => '■目次の表示/非表示',
		'description' => '投稿ページに目次を表示するか選択<br>
		(記事内の最初のhタグの手前に自動で挿入されます。※[outline]ショートコードで好きな位置に表示可能)',
		'type'      => 'select',
		'choices'   => array(
			'value1' => '表示する(default)',
			'value2' => '表示しない',
		),
	));
	
	// 目次を表示するための最小見出し数 セッティング
	$wp_customize->add_setting( 'fit_post_outline_number', array(
		'default'   => '1',
		'type' => 'option',
		'sanitize_callback' => 'fit_sanitize_number_range',
	));
	// 目次を表示するための最小見出し数 コントロール
	$wp_customize->add_control( 'fit_post_outline_number', array(
		'section'   => 'fit_post_section',
		'settings'  => 'fit_post_outline_number',
		'description' => '目次を表示するための最小見出し数を指定',
		'type'      => 'number',
		'input_attrs' => array(
        	'step'     => '1',
        	'min'      => '1',
        	'max'      => '50',
    	),
	));

	// 目次パネルデフォルト設定 セッティング
	$wp_customize->add_setting('fit_post_outline_close', array( 
		'type' => 'option',
		'sanitize_callback' => 'fit_sanitize_checkbox',
    ));
	// 目次パネルデフォルト設定 コントロール
	$wp_customize->add_control('fit_post_outline_close', array( 
        'section' => 'fit_post_section', 
        'settings' => 'fit_post_outline_close', 
        'label'     => '目次パネルをデフォルトで閉じておく',
        'type'      => 'checkbox',
    ));
}
add_action( 'customize_register', 'fit_post_cutomizer' );

//////////////////////////////////////////////////
//目次の表示/非表示、個別選択設定
//////////////////////////////////////////////////
if ( get_option('fit_post_outline') != 'value2') {
	function add_outline_fields() {
		//add_meta_box(表示される入力ボックスのHTMLのID, ラベル, 表示する内容を作成する関数名, 投稿タイプ, 表示方法)
		add_meta_box( 'outline_setting', '目次の個別非表示設定', 'insert_outline_fields', 'post', 'normal');
	}
	add_action('admin_menu', 'add_outline_fields');
 
	// カスタムフィールドの入力エリア
	function insert_outline_fields() {
		global $post;
	
		if( get_post_meta($post->ID,'outline_none',true) == "1" ) {
			$outline_none_check = "checked";
		}else {
			$outline_none_check = "";
		}
	
		echo '
			<div style="margin:20px 0; overflow: hidden; line-height:2;">
		  	<div style="float:left;width:120px;">目次の表示設定</div>
		  	<div style="float:right;width:calc(100% - 140px);">
		    	<input type="checkbox" name="outline_none" value="1" '.$outline_none_check.' >:この投稿では目次を非表示にしますか?
		  	</div>
		  	<div style="clear:both;"></div>
			</div>
		';
	
	}

	// カスタムフィールドの値を保存
	function save_outline_fields( $post_id ) {
		if(!empty($_POST['outline_none'])){
			update_post_meta($post_id, 'outline_none', $_POST['outline_none'] );
		}else{
			delete_post_meta($post_id, 'outline_none');
		}

	}
	add_action('save_post', 'save_outline_fields');
}

//////////////////////////////////////////////////
//オリジナル目次を作成
//////////////////////////////////////////////////
function get_outline_info($content) {
	// 目次のHTMLを入れる変数を定義します。
	$outline = '';
	// h1?h6タグの個数を入れる変数を定義します。
	$counter = 0;
    // 記事内のh1?h6タグを検索します。(idやclass属性も含むように改良)
    if (preg_match_all('/<h([1-4])[^>]*>(.*?)<\/h\1>/', $content, $matches,  PREG_SET_ORDER)) {
    	   // 記事内で使われているh1?h6タグの中の、1?6の中の一番小さな数字を取得します。
    	   // ※以降ソースの中にある、levelという単語は1?6のことを表します。
        $min_level = min(array_map(function($m) { return $m[1]; }, $matches));
        // スタート時のlevelを決定します。
        // ※このレベルが上がる毎に、<ul></li>タグが追加されていきます。
        $current_level = $min_level - 1;
        // 各レベルの出現数を格納する配列を定義します。
        $sub_levels = array('1' => 0, '2' => 0, '3' => 0, '4' => 0);
        // 記事内で見つかった、hタグの数だけループします。
        foreach ($matches as $m) {
            $level = $m[1];  // 見つかったhタグのlevelを取得します。
            $text = $m[2];  // 見つかったhタグの、タグの中身を取得します。
            // li, ulタグを閉じる処理です。2ループ目以降に中に入る可能性があります。
            // 例えば、前回処理したのがh3タグで、今回出現したのがh2タグの場合、
            // h3タグ用のulを閉じて、h2タグに備えます。
            while ($current_level > $level) {
                $current_level--;
                $outline .= '</li></ul>';
            }
            // 同じlevelの場合、liタグを閉じ、新しく開きます。
            if ($current_level == $level) {
                $outline .= '</li><li class="outline__item">';
            } else {
                // 同じlevelでない場合は、ul, liタグを追加していきます。
                // 例えば、前回処理したのがh2タグで、今回出現したのがh3タグの場合、
                // h3タグのためにulを追加します。
                while ($current_level < $level) {
                    $current_level++;
                    $outline .= sprintf('<ul class="outline__list outline__list-%s"><li class="outline__item">', $current_level);
                }
                // 見出しのレベルが変わった場合は、現在のレベル以下の出現回数をリセットします。
                for ($idx = $current_level + 0; $idx < count($sub_levels); $idx++) {
                    $sub_levels[$idx] = 0;
                }
            }
            // 各レベルの出現数を格納する配列を更新します。
            $sub_levels[$current_level]++;
            // 現在処理中のhタグの、パスを入れる配列を定義します。
            // 例えば、h2 -> h3 -> h3タグと進んでいる場合は、
            // level_fullpathはarray(1, 2)のようになります。
            // ※level_fullpath[0]の1は、1番目のh2タグの直下に入っていることを表します。
            // ※level_fullpath[1]の2は、2番目のh3を表します。
            $level_fullpath = array();
            for ($idx = $min_level; $idx <= $level; $idx++) {
                $level_fullpath[] = $sub_levels[$idx];
            }
            $target_anchor = 'outline__' . implode('_', $level_fullpath);

            // 目次に、<a href="#outline_1_2">1.2 見出し</a>のような形式で見出しを追加します。
            $outline .= sprintf('<a class="outline__link" href="#%s"><span class="outline__number" style="display:none;">%s.</span> %s</a>', $target_anchor, implode('.', $level_fullpath), strip_tags($text));
            // 本文中の見出し本体を、<h3>見出し</h3>を<h3 id="outline_1_2">見出し</h3>
            // のような形式で置き換えます。
            $hid = preg_replace('/<h([1-6])/', '<h\1 id="' .$target_anchor . '"', $m[0]);
            $content = str_replace($m[0], $hid, $content);
			
        }
        // hタグのループが終了後、閉じられていないulタグを閉じていきます。
        while ($current_level >= $min_level) {
            $outline .= '</li></ul>';
            $current_level--;
        }
        // h1?h6タグの個数
        $counter = count($matches);
    }
    return array('content' => $content, 'outline' => $outline, 'count' => $counter);
}

//目次を作成します。
function add_outline($content) {

    // 目次を表示するために必要な見出しの数
	if(get_option('fit_post_outline_number')){
		$number = get_option('fit_post_outline_number');
	}else{
		$number = 1;
	}
    // 目次関連の情報を取得します。
    $outline_info = get_outline_info($content);
    $content = $outline_info['content'];
    $outline = $outline_info['outline'];
    $count = $outline_info['count'];
	if (get_option('fit_post_outline_close') ) {
		$close = "";
	}else{
		$close = "checked";
	}
    if ($outline != '' && $count >= $number) {
        // 目次を装飾します。
        $decorated_outline = sprintf('
		<div class="outline">
		  <span class="outline__title">目次</span>
		  <input class="outline__toggle" id="outline__toggle" type="checkbox" '.$close.'>
		  <label class="outline__switch" for="outline__toggle"></label>
		  %s
		</div>', $outline);
        // カスタマイザーで目次を非表示にする以外が選択された時&個別非表示が1以外の時に目次を追加します。
		if ( get_option('fit_post_outline') != 'value2' && get_post_meta(get_the_ID(), 'outline_none', true) != '1' && is_single() ) {
        	$shortcode_outline = '[outline]';
        	if (strpos($content, $shortcode_outline) !== false) {
            	// 記事内にショートコードがある場合、ショートコードを目次で置換します。
            	$content = str_replace($shortcode_outline, $decorated_outline, $content);
        	} else if (preg_match('/<h[1-6].*>/', $content, $matches, PREG_OFFSET_CAPTURE)) {
            	// 最初のhタグの前に目次を追加します。
            	$pos = $matches[0][1];
            	$content = substr($content, 0, $pos) . $decorated_outline . substr($content, $pos);
        	}
		}
    }
	return $content;
}
add_filter('the_content', 'add_outline');

function override_mce_options( $init_array ) {
    global $allowedposttags;

    $init_array['valid_elements']          = '*[*]';
    $init_array['extended_valid_elements'] = '*[*]';
    $init_array['valid_children']          = '+a[' . implode( '|', array_keys( $allowedposttags ) ) . ']';
    $init_array['indent']                  = true;
    $init_array['wpautop']                 = false;

    return $init_array;
}

add_filter( 'tiny_mce_before_init', 'override_mce_options' );

長いっすね・・・

でも、function.phpは、とりあえずコピペしただけで、ちゃんと「外観」→「カスタマイズ」に「投稿ページ設定[Lion用]」が生まれました。すごい**

CSSが少々カスタマイズを要しました。
ウェブアイコン付けたいんだけどなー
完成したらソースさしかえてー