Generate a Table of Contents on Bearblog
An Archaeopteryx wrote a post about Generating a ToC, and it looked like a painful process.
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:
Create the button & generate ToC
- Visit https://bearblog.dev/dashboard/customise/
- Copy+Paste the below javascript code into the 'Dashboard footer content'
- Once you have written a post, click the 'Generate ToC' button, which generates HTML and copies it into your clipboard
- 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.toc-list {} /* the outer, numbered list */
div.toc ul.toc-list {} /* 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 class="toc-head">Contents:</h2>'
+"\n"+'<ol class="toc-list">';
// 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 class=\"toc-list\">";
} 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