ReedyBear's Blog

Generate a Table of Contents on Bearblog

UPDATE: This has been added to Herman's Bear Blog Plugins and should be retrieved from there. It has been updated several times with improvements.

My Styles: (inspired by An Archaeopteryx)

You can copy+paste these into your Theme's CSS code.

/* table of contents */  

.toc {  
	display: inline-block;  
	padding: 0px 24px 8px 24px;  
	margin: 0;  
	border: 2px dotted #9674cc; /* dark mode: #8439d */  
	border-radius: 4px;  
        line-height:1.5em;  
}  
.toc a {  
	text-decoration: none;  
    font-size: 1em;  
}  
.toc ol {  
  padding-left: 30px;  
}  
.toc ol > li > ul {  
   padding-left: 16px;  
}  

.toc ol > li > ul ul {  
   padding-left:32px;  
}  

An Archaeopteryx wrote a post about Generating a ToC, and it looked like a painful process.

UPDATE: They shared their ToC styles, which look very nice, and I have implemented for this post as an example. At their suggestion, I also removed the nested css classes of toc-head and toc-list.

So, I wrote some Javascript to create a button on your 'New Post' page that will generate ToC for you.

ALSO: checkout my other Bearblog Tips

Contents:

  1. Create the button & generate ToC
  2. Styling (CSS)
  3. The Code (Javascript)
  4. Sample Header 1
  5. Sample Header 2

Create the button & generate ToC

  1. Visit https://bearblog.dev/dashboard/customise/
  2. Copy+Paste the below javascript code into the 'Dashboard footer content'
  3. Once you have written a post, click the 'Generate ToC' button, which generates HTML and copies it into your clipboard
  4. Paste it into your post wherever you want it.

Note: There must be at least one blank line between any HTML and any markdown. The generated ToC HTML thus has extra blank lines added before-and after to make it a little more foolproof.

Styling (CSS)

You can use your BearBlog theme to style the table of contents however you like. Here are some selectors (but no styles):

div.toc {} /* wraps the whole thing */  
div.toc ol {} /* the outer, numbered list */  
div.toc ul{} /* any sub-lists, not numbered */  
div.toc li {} /* list item */  
div.toc li a {} /* the link within the list item */  
div.toc ol > li > a {} /* numbered links only */  
div.toc ul > li > a {} /* non-numbered links only */  
div.toc ol > li > ul > li > a {} /* tier 2 links only */  

The Code (Javascript)

Heavily commented for non-coders. This goes into your "Dashboard footer content".

You can also view and modify this in jsfiddle.

<!-- Generate Table of Contents -->  
<script type="text/javascript">  
// create a new button element  
const toc_btn = document.createElement('button');  
toc_btn.classList.add('rdb_post_restorer');  
toc_btn.setAttribute('onclick', "event.preventDefault();generate_toc();");  
toc_btn.innerText = 'Generate ToC';  
// add the button to your 'New Post' page  
document.querySelector('.sticky-controls').appendChild(toc_btn);  

/** scan your post text and generate a ToC, copying it to your clipboard */  
function generate_toc(){  
  // HTML for the start of the ToC list  
  var toc = "\n\n"+'<div class="toc">'  
      +"\n"+'<h2>Contents:</h2>'  
      +"\n"+'<ol>';  

  // get your post's content  
  const body = document.querySelector('#body_content').value;  
  // find all header occurences  
  const headers_flat = body.matchAll(/^#{1,6}/gm);  
  // the first header in the ToC should use '##'  
  let header_size = 2;  
  // 'started' is to make sure we don't add an </li> before any list items  
  let started = false;  
  // loop over the header occurences and generate html  
  for (const match of headers_flat){  
    // how many hashtags this header has  
    const length = match[0].length;  
    // you should not use '#' for section headers. Use `##`  
    if (length < 2)continue;  
    // generate indentations for the HTML so it looks nice  
    const indent = "    ";  
    const adjusted_indent = indent.repeat(length-2);  
    const li_indent = adjusted_indent + indent;  
    if (length == header_size && started == true){  
      // close a list item if it does not have a nested list  
      toc += "</li>";  
    } else if (length > header_size) {  
        // add a new sub-list for sub-headers  
        toc += "\n"+adjusted_indent+"<ul>";  
    } else if (length < header_size){  
        // end a sub-list, escape into the parent list  
        // .repeat allows us to close multiple open sub-lists at once if needed  
        toc += "</li>\n"+adjusted_indent+"</ul></li>".repeat(header_size - length);  
    }  
    header_size = length;  
    started = true;  

    // get the full text of the header  
    const start_index = match.index + length;  
    const end_index = body.indexOf("\n", match.index);  
    const header_text = body.substring(start_index, end_index)  
      .trim();  
    const header_id = slugify_title(header_text);  
    // the html for a link to one section below  
    toc += "\n"+li_indent+'<li><a href="#'+header_id+'">'+header_text+'</a>';  
  }  

  const list_closers = "</ul></li>".repeat(header_size-2);  
  // close the ToC list HTML  
  toc += "</li>\n    "+list_closers+"\n</ol></div>\n\n";  

  navigator.clipboard.writeText(toc);  
    // debugging code used during development  
    //console.log(toc);  
    //document.getElementById("sample_table").innerHTML = toc;  
}  
  /**  
   * Convert a header title into a lower-case version with special characters and spaces removed/replaced.  
   *  
   * @dirty_title any string  
   * @return a slugified string with only lowercase letters  
   */  
  function slugify_title(dirty_title){  
      let clean = dirty_title.toLowerCase();  
      clean = clean.replaceAll(/[^a-z0-9\_\- ]/g, '');  
      clean = clean.replaceAll(' ', '-');  
      clean = clean.replaceAll(/\-+/g, "-");  
      return clean;  
  }  
</script>  

Sample Header 1

This whole section is just for testing the ToC generator and showing you what it looks like when un-styled.

blah blah body text body text and what about an ###inline hashtag which should be ignored.

Sub_header 1

so let's hope that an super ## inline hashtag doesn't mess things up

Let's ne--st again!

We're now even deeper in the ToC

Sample Header 2

okay but this one will only have one nested list

Sample header 2 sub 1

testing testign 2 1

Sample header 2 sub 2

testing testing 2 2

#bearblog-featured