Sections wrapped in a shortcode do not appear in the TOC

Hi! I am having an issue with .TableOfContents

On a normal page, it generates fine - picks up my <h1> (not ideal but not a big problem) and <h2>

However, I have started using shortcodes to add custom styling around some sections. A typical file might look like this:

# Main heading

## Some content

blah blah blah

{{< gimme-a-border >}}

## More content

blah blah blah

{{< /gimme-a-border >}}

## Yet more content

blah blah blah

“Main heading”, “Some content” and “Yet more content” all appear in the TOC. “More content” does not.

This is the code for the nav with the TOC in:



{{ .TableOfContents }}
Download page as PDF

This is the code for the shortcodes:

<div class="docs-insight-tier">
    <strong style=
    "background-color: rgba(255, 185, 199, 1); 
    padding: 0.3rem;
    margin-left: -6px;
    border-radius: 0 0.25rem 0.25rem 0; 
    border: 0.25rem solid rgba(255, 185, 199, 1);"
    >Insight and above</strong>
    {{ .Inner | markdownify }}
</div>

We created our own custom page-TOC (“mini TOC”) implementation to support headings created with shortcodes (and other features). See Create a custom page TOC ("mini TOC") with Javascript.

1 Like

Thank you! That set me off on the right track. I modified your partial and came up with this, which avoids using any JavaScript:

{{ $headers := findRE "<h[2].*?>(.|\n])+?</h[2]>" .Content   }}
{{ if and (ge (len $headers) 1) (ne $.Params.toc "none") }}
<div class="docs-right-nav">
    <div class="docs-right-nav-inner">
    <ul>
    {{ range $headers }}
    <!-- for each h2 element, do the following:
    - apply Hugo's plainify function to strip the html and get the contents
    - apply lower to turn it lowercase
    - replace whitespace with hyphens
    - strip punctuation -->
    <li><a href='#{{ replaceRE  "\\s" "-" (. | plainify | lower) | replaceRE "[,.!?;:]" "" }}'>{{ . | plainify }}</a></li>    
    {{ end }}
    </ul>
        <a class="js-download" href="index.pdf">Download page as PDF</a>
    </div>
</div>
{{ end }}

However, this is really just a workaround. I would love to know why Hugo does this - in my case, the shortcode did nothing to the headings, it was just around them.

Hi,

Have a read of the docs here: https://gohugo.io/content-management/shortcodes/#shortcodes-with-markdown

I don’t really rely on the docs for the shortcodes, I’ve got myself in a mess trying to follow them before. Looking at the docs, using the following should allow the table of contents to work, right?

{{% insight-long %}}

{{% /insight-long %}}

I went and tried it, and it doesn’t. It only includes the sections outside the shortcode still.

Use without piping to markdownify in the shortcode.

What do you mean exactly? In the docs it looks like there are three ways of doing shortcodes:

  • %% with the snippet to make it use the old way
  • modern %%
  • <>

None of them allow the headings to make it into the TOC.

remove the markdownify pipe:

...
    {{ .Inner }}
</div>

That removes all the markdown formatting within the shortcode (so headings, bold etc. don’t show as formatted, you just get a load of ## and ** and so on)

Given the following content:

## Lorem 
1. one
2. *two*

{{< test >}}

## h2 inside &lt; shortcode
1. three
2. *four*

### h3 inside &lt; shortcode 
1. five
2. *six*

{{< /test >}}

---

## Sit
1. one
2. *two*

{{% test %}}
## h2 inside % shortcode
1. three
2. *four*

### h3 inside % shortcode
1. five
2. *six*
{{% /test %}}

The following shortcode test.html:

Inside: 
{{.Inner }}
End 

The following layout:

{{.TableOfContents}}
{{.Content}}

I get the following output:

<div>
  <nav id="TableOfContents">
    <ul>
      <li>
        <ul>
          <li><a href="#lorem">Lorem</a></li>
          <li><a href="#sit">Sit</a></li>
          <li><a href="#h2-inside-shortcode">h2 inside % shortcode</a>
            <ul>
              <li><a href="#h3-inside-shortcode">h3 inside % shortcode</a></li>
            </ul>
          </li>
        </ul>
      </li>
    </ul>
  </nav>

  <h2 id="lorem">Lorem</h2>
  <ol>
    <li>one</li>
    <li><em>two</em></li>
  </ol>

  Inside:
  ## h2 inside &lt; shortcode
  1. three
  2. *four*
  ### h3 inside &lt; shortcode
  1. five
  2. *six*
  End

  <hr>

  <h2 id="sit">Sit</h2>
  <ol>
    <li>one</li>
    <li><em>two</em></li>
  </ol>

  <p>Inside:</p>
  <h2 id="h2-inside-shortcode">h2 inside % shortcode</h2>
  <ol>
    <li>three</li>
    <li><em>four</em></li>
  </ol>

  <h3 id="h3-inside-shortcode">h3 inside % shortcode</h3>
  <ol>
    <li>five</li>
    <li><em>six</em></li>
  </ol>

  <p>End</p>
</div>

In short: it works. I get Table of Contents, including the headings inside the shortcode. The markdown is also properly rendered inside the shortcode.

As per the docs, using {{% shortcode %}}, and not using markdownify inside the shortcode definition.

Do you get different results? Or is this not what you are tying to do?

Which version of Hugo are each of you using? The % vs. </> shortcode processing logic changed in Hugo v0.5.0 (if I recall correctly).

So I tried this (I think)

Used the %% shortcode
Removed the markdownify pipe

I get a jumble of unprocessed markdown, and nothing in the table of contents

I am on Hugo 0.58.2

In that case you need to either show us your code or create a small dummy project that demonstrates the issue. As I said above, the setup works for me, so there may be something else going on. We need to be able to replicate the problem to help you.

Using a modified version of this in 2021 (Hugo has built in functions which do most of the work)

{{ $headers := findRE "<h[2].*?>(.|\n])+?</h[2]>" .Content   }}
{{ if and (ge (len $headers) 1) (ne $.Params.toc "none") }}
  <ul class="menu-list">
    {{ range $headers }}
      {{ $header := . | plainify | htmlUnescape }}
      <li><a href='#{{ $header | anchorize }}'>{{ $header }}</a></li>
    {{ end }}
  </ul>
{{ end }}