テーマを検索している際に、目次を自動で付けられるという記事を見つけたため、さっそくやってみた。
QooQ以外のテーマを試した際にも、目次が勝手に入るものもあった。ということはHTMLを少し編集しておけばいいだけのはず。Google検索トップで引っ掛かる「スケ郎のお話」さんの記事にあるコードをそのまま拝借して実施。
HTMLの編集
やり方はとても簡単で、テーマ画面のHTML編集から、下記コードを最後の方(</header>の前)に張り付けるだけ。
<!-- [START] 目次作成プラグイン--> <b:if cond='data:blog.pageType == "item"'> <script> //以下のオプションを好みに合わせて変更して下さい //オプションの詳しい説明は、(https://www.sukerou.com/2018/10/blogger-table-of-contents-javascript.html)を参照 var toc_options = { target: ["h2", "h3", "h4"], autoNumber: true, condTargetCount: 2, insertPosition: "firstHeadBefore", showToc: true, width: "auto", marginTop: "20px", marginBottom: "20px", indent: "20px", postBodySelector: ".widget.Blog" }; //これ以降のソースは編集しないでください ;(function (window) { var id_seq= 0; document.addEventListener('DOMContentLoaded', function () { var rootElement= document.querySelector(toc_options.postBodySelector); if (rootElement== null || typeof rootElement=== "undefined") { return;} if (toc_options.target.length== 0) return; rootContent= searchHeadLine(toc_options, rootElement); if (rootContent.children.length >= toc_options.condTargetCount) { var wrap= createElement(rootContent); appendElement(wrap);}}); function searchHeadLine(toc_options, rootElement) { var count= toc_options.target.length; var fn= function (index, element, parentContent) { var currentTarget= toc_options.target[index]; var nextTarget= index < count - 1 ? toc_options.target[index + 1] : ""; var id= "toc_headline_" + (++id_seq); var content= createItem(currentTarget, text(element), index + 1, id); parentContent.children.push(content); element.id= id; var el= next(element); if (nextTarget== "") { return;} var prevTarget= ""; for(var i= index; i >= 0; i--) { prevTarget += (toc_options.target[i] + ",");} while (true) { if (el== null || typeof el=== "undefined") break; if (tagName(el)== currentTarget) break; if (tagName(el)== nextTarget) { fn(index + 1, el, content);} else { var nextElements= el.querySelectorAll(prevTarget + nextTarget); var breakFlg= false; for (var i= 0; i < nextElements.length; i++) { if (tagName(nextElements[i]) != nextTarget) { exitFlg= true; break;} fn(index + 1, nextElements[i], content);} if (breakFlg) break;} var el= next(el);}}; var rootContent= createItem("ROOT", "", 0); var elements= rootElement.getElementsByTagName(toc_options.target[0]); for (var i= 0; i < elements.length; i++) { fn(0, elements[i], rootContent, "");} return rootContent;} function createElement(rootContent) { var wrap= document.createElement("div"); wrap.classList.add("b-toc-container"); wrap.style.marginTop= toc_options.marginTop; wrap.style.marginBottom= toc_options.marginTop; if (toc_options.width== "100%") { wrap.style.display= "block";} else { wrap.style.width= toc_options.width;} var p= document.createElement("p"); var span1= document.createElement("span"); var span2= document.createElement("span"); var span3= document.createElement("span"); span2.classList.add("b-toc-show-wrap"); span3.classList.add("b-toc-show-wrap"); var a= document.createElement("a"); span1.innerText= "目次"; span2.innerText= "["; span3.innerText= "]"; a.href= "javascript:void(0);"; p.appendChild(span1); p.appendChild(span2); p.appendChild(a); p.appendChild(span3); var toggleToc= function (state) { var s= typeof state=== "boolean" ? state : hasClass(wrap, "hide"); if (s) { a.innerText= "非表示"; wrap.classList.remove("hide");} else { a.innerText= "表示"; wrap.classList.add("hide");}}; a.addEventListener('click', toggleToc); toggleToc(toc_options.showToc); var ul= document.createElement("ul"); ul.classList.add("toc-root-list"); rootContent.children.forEach(function (content, index) { createContentItemElement(ul, content, (index + 1) + "");}); wrap.appendChild(p); wrap.appendChild(ul); return wrap;} function createContentItemElement(ul, content, no) { var li= document.createElement("li"); li.classList.add("toc-list-item"); var a= document.createElement("a"); li.style.paddingLeft= toc_options.indent; ul.style.paddingLeft= 0; a.href= "#" + content.id; smoothScroll(a); if (toc_options.autoNumber) { var spanNm= document.createElement("span"); spanNm.classList.add("toc-number"); spanNm.innerText= no + ".";} var spanText= document.createElement("span"); spanText.classList.add("toc-text"); spanText.innerText= content.text; if (toc_options.autoNumber) a.appendChild(spanNm); a.appendChild(spanText); li.appendChild(a); ul.appendChild(li); if (content.children.length > 0) { var childUl= document.createElement("ul"); childUl.classList.add("toc-sub-list"); li.appendChild(childUl); content.children.forEach(function (childContent, index) { createContentItemElement(childUl, childContent, no + "." + (index + 1));});}} function smoothScroll(a) { a.addEventListener('click', (e)=> { e.preventDefault(); let href= a.getAttribute('href'); let targetElement= document.getElementById(href.replace('#', '')); const rect= targetElement.getBoundingClientRect().top; const offset= window.pageYOffset; const target= rect + offset - 0; window.scrollTo({ top: target, behavior: 'smooth', });});} function appendElement(element) { var el= null; var rootElement= document.querySelector(toc_options.postBodySelector); if (toc_options.insertPosition== "firstHeadBefore" || toc_options.insertPosition== "firstHeadAfter") { el= rootElement.querySelector(toc_options.target[0]);} else if (toc_options.insertPosition== "top") { el= rootElement;} if (el== null) return; if (toc_options.insertPosition== "firstHeadBefore") { before(el, element);} else if (toc_options.insertPosition== "firstHeadAfter") { after(el, element);} else if (toc_options.insertPosition== "top") { before(el, element);}} function createItem(tagName, text, nestLevel, id) { return { tagName: tagName, text: text, children: [], nestLevel: nestLevel, id: id };} function text(element) { return element.innerText;} function next(element) { return element.nextElementSibling;} function prev(element) { return element.previousElementSibling;} function tagName(element) { return element.tagName.toLowerCase();} function hasClass(element, className) { return element.classList.contains(className);} function parentElement(element) { return element.parentNode;} function after(element, insertElement) { var parent= parentElement(element); var nextEl= next(element); if (parent != null && nextEl != null) { parent.insertBefore(insertElement, nextEl);}} function before(element, insertElement) { var parent= parentElement(element); if (parent != null) { parent.insertBefore(insertElement, element);}} })(window); </script> <style type="text/css"> .b-toc-container{background:#f9f9f9;border:1px solid #aaa;padding:10px;margin-bottom:1em;width:auto;display:table;font-size:95%}.b-toc-container p{text-align:center;margin:0;padding:0}.b-toc-container ul{list-style-type:none;list-style:none;margin:0;padding:0}.b-toc-container>ul{margin:15px 0 0}.b-toc-container.hide>ul{display:none}.b-toc-container ul li{margin:0;padding:0 0 0 20px;list-style:none}.b-toc-container ul li:after,.b-toc-container ul li:before{background:0;border-radius:0;content:""}.b-toc-container ul li a{text-decoration:none;color:#008db7!important;font-weight:400;display:flex;align-items:flex-start;flex-wrap:nowrap}.b-toc-container ul li .toc-number{margin:0 .5em 0 0;white-space:nowrap}.b-toc-container ul li .toc-text:hover{text-decoration:underline} </style> </b:if> <!-- [END] 目次作成プラグイン-->
設定等
オプション
詳細は参考サイトに記載されているためここでの解説は端折るが、表示する目次の階層やインデントを調整できるほか、いくつかスタイルが用意されている。
不具合1: 見出しにしても表示されない
この投稿にて発生。上記のオプション・不具合1・不具合2は”小見出し”なので表示されるべきなのだが、なぜか表示されず1. HTMLの編集 > 1.1ベースの後に1.2 オプション...となっていなかった。オプションの前に見出しを付けると問題なく表示されたことから、間にあるコード表示(Google-code-prettify)が影響している可能性あり。
不具合2: 表示されるべきでない本文が目次に入る
過去の記事を見てみると目次にしたくない項目が入ってしまっていることが見受けられる。これまで見出しの使い方をあまり意識的にしてこなかったため、”標準”で書くべきところに<h3>(小見出し)、<h4>(準見出し)が入ってしまっているらしい。
だいぶ恥ずかしい状態ではあるが、読者も少ないためゆっくり気が向いたときに直していきたい。
参考にしたサイト
- スケ郎のお話
- 「[Blogger] 目次を簡単に自動生成(忙しい人向けのコピペ素材)」
- https://www.sukerou.com/2018/10/blogger-table-of-contents-javascript.html