How to add a table of contents in Ghost without editing the site template

How to add a table of contents in Ghost without editing the site template

According to the tutorial here, if you want to add TOC you actually have to edit the site template (i.e., the handlebar file default.hbs). This is both a hassle and require higher paid tier to do it.

Looking at the tutorial the same can be achieved but direct injecting a small amount of Javascript in your post (or in site settings if you want to do it globally).

Open the side pane and go to "Code injection"

In "Post header" section paste the following (styling tocbot):

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/tocbot/4.12.3/tocbot.css">

<style>
    .gh-content {
        position: relative;
    }

    .gh-toc > .toc-list {
        position: relative;
    }

    .toc-list {
        overflow: hidden;
        list-style: none;
    }

    @media (min-width: 1300px) {
        .gh-sidebar {
            position: absolute; 
            top: 0;
            bottom: 0;
            margin-top: 4vmin;
            margin-left: 20px;
            grid-column: wide-end / main-end; /* Place the TOC to the right of the content */
            width: inline-block;
            white-space: nowrap;
        }

        .gh-toc-container {
            position: sticky; /* On larger screens, TOC will stay in the same spot on the page */
            top: 4vmin;
        }
    }

    .gh-toc .is-active-link::before {
        background-color: var(--ghost-accent-color); /* Defines TOC accent color based on Accent color set in Ghost Admin */
    } 
</style>

In "Post footer" section paste the following (load and init tocbot, and create the node to mount it):

<script src="https://cdnjs.cloudflare.com/ajax/libs/tocbot/4.12.3/tocbot.min.js"></script>

<script>
    const parent = document.querySelector(".gh-content.gh-canvas");
    // Create the <aside> element
    const asideElement = document.createElement("aside");
    asideElement.setAttribute("class", "gh-sidebar");
    //asideElement.style.zIndex = 0; // sent to back so it doesn't show on top of images

    // Create the container div for title and TOC
    const containerElement = document.createElement("div");
    containerElement.setAttribute("class", "gh-toc-container");

    // Create the title element
    const titleElement = document.createElement("div");
    titleElement.textContent = "Table of Contents";
    titleElement.style.fontWeight = "bold";
    containerElement.appendChild(titleElement);

    // Create the <div> element for TOC
    const divElement = document.createElement("div");
    divElement.setAttribute("class", "gh-toc");
    containerElement.appendChild(divElement);

    // Append the <div> element to the <aside> element
    asideElement.appendChild(containerElement);
    parent.insertBefore(asideElement, parent.firstChild);
    
    tocbot.init({
        // Where to render the table of contents.
        tocSelector: '.gh-toc',
        // Where to grab the headings to build the table of contents.
        contentSelector: '.gh-content',
        // Which headings to grab inside of the contentSelector element.
        headingSelector: 'h1, h2, h3, h4',
        // Ensure correct positioning
        hasInnerContainers: true,
    });
    
    // Get the table of contents element
    const toc = document.querySelector(".gh-toc");
    const sidebar = document.querySelector(".gh-sidebar");

    // Check the number of items in the table of contents
    const tocItems = toc.querySelectorAll('li').length;

    // Only show the table of contents if it has more than 5 items
    if (tocItems > 2) {
      sidebar.style.display = 'block';
    } else {
      sidebar.style.display = 'none';
    }
</script>

End result, I think tocbot is quite nice!

0:00
/