JavaScript: 要素内のHタグをリストし目次を自動生成 ページ内リンクを作成する

Quick Edit Pencil
Bloggerで見出しを自動インデックス

今ならもっとスマートなコードを書けると思います。これは2007年頃に書いたコード。2019年に手直ししてますがコードはグダグダです。が動きます。

投稿へ設置


<script>midashi_generator();</script>

レイアウトでガジェットのHTMLをヘッダエリアに追加


//<script type="text/javascript">

function midashi_generator(undefined) {

    // ▼------ 設定項目 ここから ------▼
    
    // --- 以下必須指定 ---

    // 見出しリスト(目次)生成する文字列の親要素を指定(Bloggerで使用する場合は変更不要)
    var midasi_generation_element = "div[id^='post-body-']";
    /* Bloggerテンプレートの場合はポスト本文のコンテナ要素 div#post-body-{postId} 
       セレクターの指定方法は下記URLを参照
       Document.querySelectorAll() https://developer.mozilla.org/ja/docs/Web/API/Document/querySelectorAll
       属性セレクタ https://developer.mozilla.org/ja/docs/Web/CSS/Attribute_selectors */
    
    // --- 以下オプション指定 ---

    // 目次を出すページタイプを指定
    var pageType = new RegExp("item|static_page|index|archive");
    // アイテムページとスタティックページの時に動く

    // 目次のタイトル
    var title = "目次";

    // 見出しタグの目次化の指定
    /* 半角数値で指定
       指定がない時は全部の見出しタグをリスト
       "2-4" とした時は タイトル (H2) と 大見出し (H3) と 小見出し (H4) をリスト
       "1|3" とした時は ページタイトル (H1) と 大見出し (H3) をリスト */
    var choiceHtags = "";

    // 目次のリストスタイル指定
    /* 半角数値で指定 */
    var style1 = ""; // 第一階層(親)
    var style2 = ""; // 第二階層(子)
    var style3 = ""; // 第三階層(孫)
    var style4 = ""; // 第四階層(ひ孫)
    // 半角英数で指定
    // 指定がない時はデフォルトのリストスタイルが適用されます。
    // 下記【】内を指定するとリストスタイル(ulタグ又はolタグ)が適用されます
    // マークなし【none】ul
    // ・ 記号【disc】ul
    // 1 2 3…【decimal】ol
    // A B C…【alpha1】ol
    // a b c…【alpha2】ol
    // Ⅰ Ⅱ Ⅲ... 【roman1】ol
    // ⅰ ⅱ ⅲ... 【roman2】ol
    // 上記【】内文字に続き半角空白の後に任意の文字列class名を指定することができます。

    // ▲------ 設定項目 ここまで ------▲

    // STEP0 必須設定項目の確認
    if (!midasi_generation_element) {
        info("設定変数の midasi_generation_element が指定されていません。");
        return;
    }

    // 目次を出すページタイプを検証(ボディクラスをチェック)
        //console.log("body.className = " + document.body.className + " /ボディCLASS属性");
    if (!pageType.test(document.body.className)) {
        //console.log("midashi-generator.js: 指定したページタイプ以外なのでスクリプトを中止しました。");
        return;
    }

    // 当スクリプトを変数にセット
    var scpt = document.getElementsByTagName("script");
    scpt = scpt[scpt.length - 1];

    // post-body-postIdを取得
    var postBody = getClosestElement(scpt, midasi_generation_element);  //console.log(postBody);
    if (!postBody) {
        info("見出し生成の範囲を特定できませんでした。見出し生成の範囲の指定を確認してください。");
        return;
    }
    var postid = postBody.id.split('-')[2]; /* postIdを取得 */
        //console.log('postid '+postid);
    var uniqueness = postid; /* 識別子を調整 */
        //console.log("uniquenessの完成 " + uniqueness);
    function getClosestElement(elem, selector) { /* 条件に合う最も近い祖先要素を取得関数 */
        var matchElems = document.querySelectorAll(selector);
        if(!matchElems.length){return null;}
        //console.log("bingo");
        var ancestorNode = elem.parentNode;
        while (ancestorNode) {
            for (var i = 0; i < matchElems.length; i++) {
                if (matchElems[i] === ancestorNode) return ancestorNode;
            }
            ancestorNode = ancestorNode.parentNode;
        }
        return null;
    };

    // ページの構築が完了したらloadを呼び出す
    (function() {
        if (document.addEventListener) { // opera,safari,mozilla向け
            document.addEventListener("DOMContentLoaded", load, false);
            //console.log("addEventListenerでloadをコールした");
        } else if (/msie/.test(userAgent)) { // IE向け
            try {
                document.documentElement.doScroll("left");
            } catch (error) {
                setTimeout(arguments.callee, 0);
                return;
            }
            load();
        } else { // その他
            window.onload = load;
        }
    })();

    // 中断メッセージ
    function info(message) {
        var el = document.createElement('span');
        el.setAttribute("style", "background-color: yellow;");
        el.innerHTML = "midashi-generator.js: Check the error details on the console.";
        scpt.parentNode.insertBefore(el, scpt.nextSibling);
        console.error('midashi-generator.js: '+message);
    }
    
    // ロードファンクション
    function load() {
        //console.log("loadを開始した");
        // STEP1

        //対象記事の読み込みを確認する
        if (!postBody) {
            var tID1 = null;
            var tID1i = 0;
            var tID1 = setInterval(function() {
                if (!postBody || typeof postBody == "undefined") {
                    if (postBody && postBody.lastChild) {
                        //console.log('被メニュー要素の中に何か表示されました');
                        core(); // ナビゲーションメニューの作成
                        clearInterval(tID1);
                    } else {
                        tID1i++;
                        if (tID1i > 10) {
                            ul1.innerHTML = "<li>設定された要素のid属性名【" + postBody.id + "】が存在しません。</li><li>要素のid属性名を再確認してください。</li>";
                            clearInterval(tID1);
                        }
                    }
                }
            }, 300);
        } else {
            core(); // ナビゲーションメニューの作成
        }

        // STEP2

        function core() {

                // STEP3

                // アーカイブページで次のページが設定されている場合は中止する
                if (/index|archive/.test(document.body.className)) {
                    //console.log("midashi-generator.js: アーカイブページで jump-link があるのでスクリプトを中止しました。");
                    var postOuter = getClosestElement(postBody, '.post-outer'); 
                    if(postOuter.querySelector(".jump-link")){
                        //console.log(postOuter.querySelector(".jump-link"));
                        return;
                    }
                }

                // ナビゲーションメニューを展開する要素を作る/一階層
                // メニューを展開する第一階層のul要素をクリエイト
                // class属性を設定
                if (!style1 || typeof style1 == "undefined") {
                    var navlist = document.createElement('ul');
                    //navlist.setAttribute("class", "default");
                } else if (/none|disc/.test(style1)) {
                    var navlist = document.createElement('ul');
                    navlist.setAttribute("class", style1);
                } else if (/decimal|alpha[1-2]|roman[1-2]/.test(style1)) {
                    var navlist = document.createElement('ol');
                    navlist.setAttribute("class", style1);
                } else {
                    var navlist = document.createElement('ul');
                    navlist.setAttribute("class", style1);
                }

                // メニューを展開する第一階層のul要素を変数にセット
                var ul1 = navlist;

                // 結果:<ul class="default" />
                // 被メニュー要素のリスト
                //console.log("postBody.id " + postBody.id);
                // 収集見出しの指定
                if (!choiceHtags || typeof choiceHtags == "undefined") {
                    choiceHtags = "1-4";
                }
                // セレクターで見出しタグを集める
                var headings = postBody.querySelectorAll("h1,h2,h3,h4");
                // 一階層リストと二階層以降
                var style, currentHtag, depth, lastHtag, a, li1, ul2, li2, ul3, li3, ul4, li4;
                lastHtag = 0;
                depth = 1;
                for (var i = 0; i < headings.length; i++) {
                    if (RegExp("h[" + choiceHtags + "]").test(headings[i].tagName.toLowerCase())) {
                        //console.log("Core for Hタグ検出");
                        // カレントの見出しタグ番号を取得
                        currentHtag = headings[i].tagName.toLowerCase().replace("h", "");
                        //console.log("currentHtag " + currentHtag);
                        // アンカー処理:見出しタグにidを追加
                        headings[i].id = "mg_" + postid + "_" + i;
                        // アンカー処理:メニューリンク要素追加
                        a = document.createElement('a');
                        a.setAttribute("href", "#mg_" + postid + "_" + i);
                        //a.setAttribute("onclick", "mg_SmoothScroll(this, '#mg_" + postid+i+"');return false;")
                        a.textContent = headings[i].textContent;
                        // 深度(二回以降)
                        //console.log("lastHtag " + lastHtag);
                        if (lastHtag > 0) {
                            depth = depth + (currentHtag - lastHtag);
                        }
                        if(depth == 0){
                            // 構造階層が壊れているときエラーメッセージを出す
                            info("見出しタグのツリー構造が成立しませんでした。見出し「"+ headings[i].innerText + "」は<" + headings[i].tagName.toLowerCase() + ">ですが、<h" + lastHtag + ">であるべきです。");
                            headings[i].setAttribute("style", "background-color: deepskyblue!important;");
                            headings[i].innerHTML = "<span style='color: white!important; background: black;display: block!important;'>midashi-generator.js: !!! You need to set &lt" + headings[i].tagName.toLowerCase() + "&gt to &lth" + lastHtag + "&gt.</span><br />" + headings[i].textContent;
                            return;
                        }
                        //console.log("depth " + depth);
                        // カレントの指定スタイルをセット
                        eval("style=style" + depth + ";");
                        if (depth == 1) {
                            // 第一階層のリストをセット
                            //console.log("第一階層 depth " + depth);
                            li1 = document.createElement('li');
                            li1.appendChild(a);
                            //console.log("lastHtag " + lastHtag);
                            //if (lastHtag == 0) {
                                ul1.appendChild(li1);
                            //}
                        } else {
                            // 二階層目以降のカレントul/olをセット
                            //console.log("二階層目以降 depth " + depth);
                            if ((currentHtag - lastHtag) == 1 || (currentHtag - lastHtag) <= 0) {
                                if (!style || typeof style == "undefined") {
                                    eval("ul" + depth + "=document.createElement('ul');");
                                } else if (/none|disc/.test(style)) {
                                    eval("ul" + depth + "=document.createElement('ul');");
                                    eval("ul" + depth + ".setAttribute('class', '" + style + "');");
                                } else if (/decimal|alpha[1-2]|roman[1-2]/.test(style)) {
                                    eval("ul" + depth + "=document.createElement('ol');");
                                    eval("ul" + depth + ".setAttribute('class', '" + style + "');");
                                } else {
                                    eval("ul" + depth + "=document.createElement('ul');");
                                    eval("ul" + depth + ".setAttribute('class', '" + style + "');");
                                }
                            }else{
                                // 構造階層が壊れているときエラーメッセージを出す
                                info("見出しタグのツリー構造が成立しませんでした。見出し「"+ headings[i].innerText + "」は<" + headings[i].tagName.toLowerCase() + ">ですが、<h" + depth + ">であるべきです。");
                                headings[i].setAttribute("style", "background-color: lightpink!important;");
                                headings[i].innerHTML = "<span style='color: white!important; background: black;display: block!important;'>midashi-generator.js: !!! You need to set &lt" + headings[i].tagName.toLowerCase() + "&gt to &lth" + depth + "&gt.</span><br />" + headings[i].textContent;
                                return;
                            } // カレントliをセット
                            eval("li" + depth + "=document.createElement('li');");
                            eval("li" + depth + ".appendChild(a);");
                            eval("ul" + depth + ".appendChild(li" + depth + ");");
                            // 親要素へ追加
                            eval("li" + (depth - 1) + ".appendChild(ul" + depth + ");");
                            eval("ul" + (depth - 1) + ".appendChild(li" + (depth - 1) + ");");
                        }
                        lastHtag = currentHtag;
                    }
                } // for
                // 第一階層へリストを追加
                ul1.appendChild(li1);

                // コンテナ書き出し
                var container;
                (function() {
                    container = document.createElement('div');
                    container.setAttribute("class", "midasi-index-container");
                    // id属性を設定
                    var setIdName = uniqueness + "-index";
                    var matchId = postBody.querySelectorAll('ul[id^="'+setIdName+'"]');
                    container.setAttribute("id", setIdName +'-'+ matchId.length);
                    postBody.insertBefore(container, scpt.nextSibling);
                })();

                // 目次を書き出し
                container.insertBefore(navlist, container.firstChild);

                // タイトルを書き出し
                if(title)writeTitle(title);
                function writeTitle(write) {
                    var el = document.createElement('div');
                    el.setAttribute("class", "midasi-index-title");
                    el.innerHTML = title;
                    container.insertBefore(el, container.firstChild);
                }

            } //sub core

    }; //sub load

}

//</script>

参考リンク


このブログの人気の投稿

書字方向 横書方向変換スクリプト 左書きから右書きへ(コピペ用途)

PowerShellのGetDetailsOf メソッドでプロパティの詳細情報のID番号と項目名を列挙します

PowerShellで複数ファイルのプロパティを取得する方法(準備編)

簡単 YouTube動画をダウンロード、音声のみ保存する方法 2019

DOMノードオブジェクトを文字列に変換する

AppleScript 改行 コード 置換

PowerShellでJPG画像のリサイズとウォーターマーク画像との合成を同時に行う

Blender: 辺の長さを数値で指定するアドオン