Sashmark / Blog

Markdown: exclude a heading from the table of contents

Bobby Huang ·

The section Contents lists and the CommonMark spec gives four search tallies beside a control tally. Under Tag the heading as unlisted, the nav and the h2 lines pandoc wrote for tocx.md are quoted. The section Section numbers bring the heading back compares two --number-sections runs, one per test file. Output with the brace syntax switched off sits in The markdown reader without header_attributes. The section The commonmark reader with and without attributes quotes nav output for both commonmark settings. A contents list written as Markdown appears in A Markdown contents list from pandoc. Six checking steps are listed in Check the nav with grep.

Key takeaways

  • Pandoc's nav for tocx.md and its h2 with the unlisted class are quoted in Tag the heading as unlisted.
  • Two runs with --number-sections are compared in Section numbers bring the heading back.
  • Brace text left in the heading and the nav is shown in The markdown reader without header_attributes.
  • Nav lists written by both commonmark settings appear in The commonmark reader with and without attributes.
  • Grep counts that test the nav are set out in Check the nav with grep.

A long notes file can hold a changelog or a scratch heading that you want on the page, yet off the contents list at the top. When pandoc writes that list, a class on the heading line can keep one heading off it.

This post puts that idea to work on two small files. For each test, version 3.8.3 of pandoc ran on macOS. The post quotes the nav entries, classes and ids pandoc wrote, and nothing past the HTML was checked. The guide to Markdown contents lists shows how to build one by hand and how pandoc writes one.

Contents lists and the CommonMark spec

Four case-insensitive fixed-string searches of CommonMark 0.31.2, kept as a text file, returned 0 each: table of contents, contents list, unlisted and toc. To prove the search could find words, grep -ci -F 'heading' on that file returned 67.

Pandoc wrote the classes and ids that the sections below quote.

The two test files

Running cat tocx.md printed:

# Intro

## Setup

## Changelog {.unlisted}

# Usage

The second file, tocx-both.md, differs in one line. Its third heading reads ## Changelog {.unlisted .unnumbered}.

Tag the heading as unlisted

The command:

pandoc -f markdown -t html -s --toc --metadata title=Doe tocx.md -o doe.html

The value title=Doe is a placeholder. The nav element in doe.html read:

<nav id="TOC" role="doc-toc">
<ul>
<li><a href="#intro" id="toc-intro">Intro</a>
<ul>
<li><a href="#setup" id="toc-setup">Setup</a></li>
</ul></li>
<li><a href="#usage" id="toc-usage">Usage</a></li>
</ul>
</nav>

Intro, Setup and Usage got an entry with a link. Changelog has no entry. The heading still sits in the body, and grep '<h2' doe.html printed:

<h2 id="setup">Setup</h2>
<h2 class="unlisted" id="changelog">Changelog</h2>

The brace text came out as a class attribute, pandoc wrote the id changelog, and the heading text was plain Changelog. A run on the same file without -s --toc gave that same h2 line.

With tocx-both.md, the nav matched, with Intro, Setup and Usage. Pandoc gave its heading as <h2 class="unlisted unnumbered" id="changelog">Changelog</h2>, both classes in one attribute.

Section numbers bring the heading back

The numbered headings post covers numbering itself. This section looks at what it did to the nav. First, tocx.md again, with one option added:

pandoc -f markdown -t html -s --toc --number-sections --metadata title=Doe tocx.md -o doe.html

The nav:

<nav id="TOC" role="doc-toc">
<ul>
<li><a href="#intro" id="toc-intro"><span
class="toc-section-number">1</span> Intro</a>
<ul>
<li><a href="#setup" id="toc-setup"><span
class="toc-section-number">1.1</span> Setup</a></li>
<li><a href="#changelog" id="toc-changelog"><span
class="toc-section-number">1.2</span> Changelog</a></li>
</ul></li>
<li><a href="#usage" id="toc-usage"><span
class="toc-section-number">2</span> Usage</a></li>
</ul>
</nav>

Changelog is back, as entry 1.2. Its body heading opened with <h2 class="unlisted" data-number="1.2" id="changelog">, so the class was still written. The commonmark+attributes reader gave a 1.2 Changelog entry in its numbered nav too.

Then tocx-both.md with the same options. Its nav held three entries: Intro numbered 1, Setup 1.1 and Usage 2. This time pandoc wrote the heading as <h2 class="unlisted unnumbered" id="changelog">Changelog</h2>, with no data-number attribute.

In this test, .unlisted on its own let the heading back into a numbered nav. The pair {.unlisted .unnumbered} kept it out.

The markdown reader without header_attributes

This run switched off the brace syntax for headings in the markdown reader:

pandoc -f markdown-header_attributes -t html -s --toc --metadata title=Doe tocx.md -o doe.html

The nav:

<nav id="TOC" role="doc-toc">
<ul>
<li><a href="#intro" id="toc-intro">Intro</a>
<ul>
<li><a href="#setup" id="toc-setup">Setup</a></li>
<li><a href="#changelog-.unlisted"
id="toc-changelog-.unlisted">Changelog {.unlisted}</a></li>
</ul></li>
<li><a href="#usage" id="toc-usage">Usage</a></li>
</ul>
</nav>

The braces turned into heading text here. The nav listed Changelog {.unlisted} and linked it to the id changelog-.unlisted. In the body, the heading read <h2 id="changelog-.unlisted">Changelog {.unlisted}</h2>, with no class. The tocx-both.md run listed its brace text the same way, under the id changelog-.unlisted-.unnumbered, and pandoc wrapped that entry across three lines.

A +header_attributes line showed up when this test ran pandoc --list-extensions=markdown.

The commonmark reader with and without attributes

Plain commonmark ran with the same options:

pandoc -f commonmark -t html -s --toc --metadata title=Doe tocx.md -o doe.html

Its nav:

<nav id="TOC" role="doc-toc">
<ul>
<li>Intro
<ul>
<li>Setup</li>
<li>Changelog {.unlisted}</li>
</ul></li>
<li>Usage</li>
</ul>
</nav>

Four entries, and none of them held a link. Changelog appeared with its braces as text. The two h2 lines in doe.html were <h2>Setup</h2> and <h2>Changelog {.unlisted}</h2>, so neither carried an id. Asked with pandoc --list-extensions=commonmark, pandoc printed -attributes.

Swapping in -f commonmark+attributes dropped Changelog from the nav, which then held Intro, Setup and Usage. The body heading turned into <h2 class="unlisted">Changelog</h2>, a class with no id. The nav entries in that run held no links either. For tocx-both.md, this reader wrote <h2 class="unlisted unnumbered">Changelog</h2>.

A Markdown contents list from pandoc

Pandoc can also write Markdown back out. This run kept the markdown reader and writer:

pandoc -f markdown -t markdown --toc -s tocx.md

Output:

- [Intro](#intro){#toc-intro}
  - [Setup](#setup){#toc-setup}
- [Usage](#usage){#toc-usage}

# Intro

## Setup

## Changelog {#changelog .unlisted}

# Usage

The list held three items, and Changelog was left out of it. Its heading line came back as ## Changelog {#changelog .unlisted}, with the id now written inside the braces next to the class.

Check the nav with grep

Work through this list if a heading you tagged still turns up in pandoc's nav.

  1. Run cat tocx.md and look for the class in braces after the heading text.
  2. Convert the file: pandoc -f markdown -t html -s --toc --metadata title=Doe tocx.md -o doe.html.
  3. Search for the nav id. grep -c 'toc-changelog' doe.html printed 0. With -f markdown-header_attributes in step 2, it printed 1.
  4. Confirm the heading is still in the body. grep -c 'class="unlisted"' doe.html printed 1.
  5. Add --number-sections after --toc in step 2 and search again. On tocx.md, grep -c 'toc-changelog' doe.html printed 1. On tocx-both.md, it printed 0.
  6. Under a commonmark reader, count links. grep -c 'href=' doe.html printed 0 for -f commonmark and for -f commonmark+attributes, and 3 for -f markdown.

Where Sashmark fits

In Sashmark's first version, as planned, you could change words while looking at a rendered view of your page. A save would then pass the file along a clean Round-Trip, a second item in that plan, and any line you skipped would come out with its bytes unchanged. Since your last reading of the file, new edits would show up in a Changes View laid out in the plan, one meant to run without git.

It is in development and has not been released. The app itself has no AI features. See the planned features, the help pages and the roadmap for where each part stands.

More at sashmark.com.

Questions

Does CommonMark have syntax that drops a heading from a contents list?
Search counts over the CommonMark text sit in Contents lists and the CommonMark spec, next to a control.
Which class kept the heading out of pandoc's nav?
The heading marked .unlisted and its nav are quoted under Tag the heading as unlisted.
Why did my unlisted heading come back in a numbered nav?
Both numbered runs are quoted in Section numbers bring the heading back.
Why does the nav show the braces as text?
Braces kept as heading text are shown in The markdown reader without header_attributes.

What Sashmark plans for v1: Features · Roadmap

View as Markdown