Table of Contents Tutorial for Casper Version 4 - Simplified

I’d like to have tables of contents autogenerated on my posts. Fancier stuff like a ToC that “floats” persistently along the side of the page would be nice, but I’m okay with something more basic like a simple ToC at the top of the page before the content.

I’m curious if anyone has a very recent table of contents tutorial that’s been done with recent releases of Casper (I just installed Ghost so I’m running the latest everything). I looked at a bunch of existing ones, but my web skills are pretty weak so I need something very hand-holdy and on point, and I seem to keep running into issues where something’s wonky, possibly cuz of some changes in Casper since a given tutorial was made (initially the issue was that class names had been changed and so the code I was pasting in wasn’t even looking for the right thing. Figured that out, but then I had issues with the alignment of the ToC being wonky and uncentered or other annoyances).

If you have paid/subscriber-only content that’s on point to this request and want to link that, don’t be shy, i’ll consider it :slight_smile:

3 Likes

I’d like to have tables of contents autogenerated on my posts. Fancier stuff like a ToC that “floats” persistently along the side of the page would be nice, but I’m okay with something more basic like a simple ToC at the top of the page before the content.
I’m curious if anyone has a very recent table of contents tutorial that’s been done with recent releases of Casper (I just installed Ghost so I’m running the latest everything).

I would like this as well.

And welcome to the Ghost forum, Justin :slightly_smiling_face:

1 Like

Also I think somebody else will probably respond, but if they don’t, I’ll have a go at building this and then share the code with you. I’m certainly not the most experienced or skilled developer here but I have implemented exactly this on a non-Ghost website before and I can’t imagine it will be much more difficult.

ok through trial and error i’ve managed to get something kinda working here
My approach is heavily based on this guy’s post:

you can see an example at this test page https://blog.justinmallone.com/simply-scheme-chapter-23-vectors/ (i pasted some content from my old blog to test the ToC aspect)


(The numbering is a numbering of exercises rom the headings, to be clear - that’s not automatic numbering coming from an ordered list - i suppressed displaying those).

Ok so my current method requires manually dropping something into an html block where you want the TOC to display. So it’s not automatic, but OTOH this does have the virtue of letting you choose if you want to display a TOC.

So you just pop one of these into an HTML block in the post you want a ToC for:

<div class="hc-toc"></div>

and then i’ve got this mess in the Site Footer in Code Injection to actually get everything working:

<!-- table of contents -->


<script src="https://cdnjs.cloudflare.com/ajax/libs/tocbot/4.11.1/tocbot.min.js" integrity="sha384-TnuMTlegqfBi40E9e3dO2qfmPVKDjLFCCgyZ5bR3SA/hYTJXI090bTbAr/AXO+bc" crossorigin="anonymous"></script>


<script>
tocbot.init({
// Where to render the table of contents.
tocSelector: '.hc-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, h5, h6' ,
// For headings inside relative or absolute positioned containers within content.
hasInnerContainers: true,
});

</script>

<!-- styling background of toc -->

<style>
.hc-toc {
  border: 1px solid #a2a9b1;
  background-color: #f8f9fa;
  padding: 5px;
}
</style>

<!-- Contents text above TOC entries -->
<style>
.hc-toc::before {
  content: "Table of Contents";
  display: flex;
  justify-content: left;
  font-weight: bold;
  margin-left: 10px;
}
</style>


<style>
.hc-toc ol {
   /* Move bullets inside. Applies to both first and second level list items. */
  list-style-position: inside;
  /* Cancel out Ghost's default margin-bottom: 30px. */
  margin-bottom: 0;
    list-style: none;
}
   .hc-toc li {
    /* shrink line height */
   line-height:80% ;
    }
</style)

key thing seems to be to select gh-content and not post-content or post-full-content like you may find other guides suggesting, at least if you’re on the latest Casper.

Some next steps are tweaking the styling a bit more (changing it from hyperlink blue, say) and seeing if I can get it to work with dark mode. I tried dark mode but it was a mess, will follow up later with specifics but it’s late here :slight_smile:

TO ADD A TOC (Table of Contents) IN THE CASPER THEME …

1. Paste this into your Site Header Code Injection

<!-- Tocbot Stylesheet -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/tocbot/4.10.0/tocbot.css" />

<!-- Tocbot Inline CSS (Specific Adjustments for the Casper Theme) -->
<style>
/* Offset headings from fixed header */
.post-content h2::before,
.post-content h3::before {
    display: block;
    content: " ";
    height: 84px;
    margin-top: -84px;
    visibility: hidden;
}

/* Adjust content wrapper */
.post-content {
    display: block;
}

/* Adjustments to wide and full width cards */
.kg-gallery-card,
.kg-width-wide,
.kg-width-full {
    display: flex;
    flex-direction: column;
    align-items: center;
}

.kg-gallery-card > *,
.kg-width-wide > *,
.kg-width-full > *,
figure.kg-width-full img {
    margin-left: -50vw;
    margin-right: -50vw;
}

.post-full-content pre {
    max-width: 0;
}

    /* Add a light gray box (Optional) */
    .toc {
        border: 1px solid #a2a9b1;
        background-color: #f8f9fa;
        padding: 5px;
    }
</style>

2. Paste this into your Site Footer Code Injection

<!-- Tocbot Script -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/tocbot/4.10.0/tocbot.min.js"></script>
<script>
    tocbot.init({
        tocSelector: '.toc',
        contentSelector: 'section.gh-content.gh-canvas',
        hasInnerContainers: true
    });
</script>

3. Paste this HTML snippet where you want the Table of Contents to appear

<aside class="toc"></aside>

4. For quick testing, you can navigate to a post (one with some headings) in Ghost admin and create a new HTML card in the area you want your table of contents to appear. A HTML card can be added using the “ + ” button or by typing “/HTML” and hitting enter.

Within the HTML card paste the HTML snippet:

<aside class="toc"></aside>

Then click save and view the post. The snippet you’ve added to the post should now contain a table of contents created from all the headings in that post :raised_hands:.

5. To get the TOC working on all of your site’s post and/or pages, paste the HTML snippet in your post.hbs and/or in page.hbs, or in any other template where you want the TOC to appear for every post and/or page.

5 Likes

FOR A FIXED POSITION TOC

Add this CSS also in between the style tags in the Site Header Code Injection

/* Top Right Fixed position TOC (Optional) */
.toc {
position: fixed;
top: 54px;
right: 0;
}

… since this is going to fix the TOC in a static position, it doesn’t matter where (within either the post.hbs, page.hbs, or inserted into the post editor using an HTML card) you put this snippet.

FOR A FIXED POSITION TOC IN A COLLAPSIBLE ACCORDION

1. Add this CSS in between the style tags in the Site Header Code Injection

/* Fixed position and simple styling for accordion container (Optional) */
details {
    position: fixed;
    top: 54px;
    right: 0;
    cursor: pointer;
    z-index: 100;
}
summary {
    background-color: navy;
    color: #fff;
    padding-left: 10px;
    padding-bottom: 3px;
    padding-right: 20px;
}

2. Wrap the HTML snippet as below

… replacing this HTML snippet …

<aside class="toc"></aside>

… and instead pasting this HTML snippet …

<details open>
<summary>&nbsp;Page Contents</summary>
<aside class="toc"></aside>
</details>

… since this is going to fix the TOC accordion in a static position, it doesn’t matter where (within either the post.hbs, page.hbs, or inserted into the post editor using an HTML card) you put this snippet.

You can also make the accordion open or closed by default

<details open>

… makes the TOC accordion open by default …

<details>

… makes the TOC accordion closed by default …

3 Likes

thank you so much @denvergeeks! This looks really on point. I’m going to play with some of this later :slight_smile: I see that you use details and summary tags to make the TOC collapsible, which is a really great and simple solution :slight_smile:

This is great! Is there a way to exclude the H1 (the title of the page) from the table of contents?

The only problem I can see with the fixed position TOC is that it’s going to be in front of and obscure the main post content on small screens like phones.

Would a media query be the best solution to having the TOC fixed to the side on larger screens but return to the original position at the top of the blog post on smaller screens?

And is there a simple way to remove the numerals from the TOC? Or change them to roman numerals?

good point real: small screens. I don’t know the answer re: best solution for handling that.

re: removing the numerals, I’m styling the ordered list within the toc with:

    list-style: none;

and that does it

@media only screen and (min-width: 1400px) {
.toc {
position: fixed;
top: 54px;
right: 0;
}
}

This should work I think.

Now I just have to figure out how to exclude the h1 from the list.

Worked for me :tada:

TO EXCLUDE H1 (POST/PAGE TITLE) FROM THE TOC

(From Tocbot Docs)

So change your Footer Code Injection to

<!-- Tocbot Script -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/tocbot/4.10.0/tocbot.min.js"></script>
<script>
    tocbot.init({
        tocSelector: '.toc',
        contentSelector: 'section.gh-content.gh-canvas',
        headingSelector: 'h2, h3',
        hasInnerContainers: true
    });
</script>
2 Likes

Ghost 4.0 issue

It seems that after this change:

Tocbot does not work with special characters which are in other languages (e.g. Polish: ą, ć, ę, ł, ń, ó, ś, ź, ż). It is since Ghost 4.0.

Issue example

Here is the id generated by Ghost:

<h2 id="2-z-kim-mo%C5%BCesz-kontaktowa%C4%87-si%C4%99-w-sprawie-przetwarzania-twoich-danych-osobowych">

Here is the id taken by Tocbot:

<a href="#2-z-kim-mo%C5%BCesz-kontaktowa%C4%87-si%C4%99-w-sprawie-przetwarzania-twoich-danych-osobowych" class="toc-link node-name--H2 ">

The browser (Firefox/Chrome) opens the link under:

${url}/#2-z-kim-możesz-kontaktować-się-w-sprawie-przetwarzania-twoich-danych-osobowych

and has issues with navigating to correct <h2> tag.

Fix

I’ve resolved the issue by taking the following script:

and making some customization for my language special characters:

<script>
function makeIds() {
    var content = document.querySelector('.gh-content');
    var headings = content.querySelectorAll('h2, h3');
    var headingMap = {};

    Array.prototype.forEach.call(headings, function (heading) {
        var id = heading.textContent.trim().toLowerCase()
            .split(' ').join('-').replace(/[!@#$%^&*():?\[\]'+,;=<>{}|\\`]/ig, '').replace(/ą/ig, 'a').replace(/ć/ig, 'c')
            .replace(/ę/ig, 'e').replace(/ł/ig, 'l').replace(/ń/ig, 'n').replace(/ó/ig, 'o').replace(/ś/ig, 's')
            .replace(/[źż]/ig, 'z').replace(/\//ig, '-');
        heading.id = id;
    });
}
makeIds();
</script>

I’ve pasted it at the end of page.hbs and post.hbs (because I use ToC there).

Fix example

Here is the id generated by the above script:

<h2 id="2.-z-kim-mozesz-kontaktowac-sie-w-sprawie-przetwarzania-twoich-danych-osobowych" tabindex="-1">

Here is the id taken by Tocbot:

<a href="#2.-z-kim-mozesz-kontaktowac-sie-w-sprawie-przetwarzania-twoich-danych-osobowych" class="toc-link node-name--H2 ">

The browser (Firefox/Chrome) opens the link under:

${url}/#2.-z-kim-mozesz-kontaktowac-sie-w-sprawie-przetwarzania-twoich-danych-osobowych

would there be a relatively easy way to only have a ToC show up on pages that have a particular tag? (My use case: I’m wanting to use Ulysses integration to write my posts and it’d be quite convenient to just slap on a “toc” tag in Ulysses whenever i want a ToC and call it a day)

I don’t expect a fully explanation btw - willing to do some work. But just some pointers on where to get started and links to anything roughly similar that’s been done would be great :slight_smile:

@JustinCEO See this comment from earlier today:

2 Likes

I set up a development version of Ghost on my local machine.

So if I just include

<aside class="toc"></aside>

in my post.hbs file, Ghost displays the ToC on all posts

However, if I try to make the TOC display conditional on a tag being present, e.g. with:

{{#has tag="#TOC"}}
    <aside class="toc"></aside>
{{/has}}

then nothing happens on either tagged or untagged posts

Ghost doesn’t seem to care where I put the aside HTML snippet if I’m using the “floating” fixed position ToC, per what @denvergeeks said in his earlier post. Does Ghost care where I put a has? Just trying to speculate on what the trouble might be

I’ve double checked that I actually tagged the post I’m testing with a TOC tag, that it’s a blog post, and that it’s fully updated, and I tried restarting Ghost.

lol i had a # symbol before my tag, that was wrecking the whole thing.

the correct code:

{{#has tag="TOC"}}
    <aside class="toc"></aside>
{{/has}}

it works :raised_hands:

1 Like

Nice, @JustinCEO !