htmlcssmarkdownpandoc

Pandoc: Customize HTML template to place Table of Contents under a specific heading


I'm using Pandoc, as described here, to convert Markdown to HTML and I'm trying to customize the output so that the table of contents appears under a specific heading in my document. However, I'm encountering difficulties in achieving this with the default HTML template.

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="$lang$" xml:lang="$lang$"$if(dir)$ dir="$dir$"$endif$>
<head>
  <meta charset="utf-8" />
  <meta name="generator" content="pandoc" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />
$for(author-meta)$
  <meta name="author" content="$author-meta$" />
$endfor$
$if(date-meta)$
  <meta name="dcterms.date" content="$date-meta$" />
$endif$
$if(keywords)$
  <meta name="keywords" content="$for(keywords)$$keywords$$sep$, $endfor$" />
$endif$
$if(description-meta)$
  <meta name="description" content="$description-meta$" />
$endif$
  <title>$if(title-prefix)$$title-prefix$ – $endif$$pagetitle$</title>
  <style>
    $styles.html()$
  </style>
$for(css)$
  <link rel="stylesheet" href="$css$" />
$endfor$
$for(header-includes)$
  $header-includes$
$endfor$
$if(math)$
$if(mathjax)$
  <script src="https://cdnjs.cloudflare.com/polyfill/v3/polyfill.min.js?features=es6"></script>
$endif$
  $math$
$endif$
</head>
<body>
$for(include-before)$
$include-before$
$endfor$
$if(title)$
<header id="title-block-header">
<h1 class="title">$title$</h1>
$if(subtitle)$
<p class="subtitle">$subtitle$</p>
$endif$
$for(author)$
<p class="author">$author$</p>
$endfor$
$if(date)$
<p class="date">$date$</p>
$endif$
$if(abstract)$
<div class="abstract">
<div class="abstract-title">$abstract-title$</div>
$abstract$
</div>
$endif$
</header>
$endif$
$if(toc)$
<nav id="$idprefix$TOC" role="doc-toc">
$if(toc-title)$
<h2 id="$idprefix$toc-title">$toc-title$</h2>
$endif$
$table-of-contents$
</nav>
$endif$
$body$
$for(include-after)$
$include-after$
$endfor$
</body>
</html>

Current Setup

I'm using the following Pandoc command:

pandoc -F "C:\Users\foobar\AppData\Local\Yarn\bin\mermaid-filter.cmd" -t html --standalone --css=custom.css --toc --template=custom-template.html -o output.html input.md

My Markdown file structure looks like this:

# Document Title

Some introductory text...

## Table of Contents

(I want the auto-generated ToC to appear here)

## First Section

Content of the first section...

I've exported the default HTML template using:

pandoc -D html > custom-template.html

Question

How can I modify the Pandoc HTML template and/or CSS to ensure that the auto-generated table of contents is inserted right after my "Table of Contents" heading in the output HTML?

Additional Information

Here's the content of my custom.css file:

table {
    margin-left: auto;
    margin-right: auto;
    margin-bottom: 24px;
    border-spacing: 0;
    border-collapse: collapse;
    border: 2px solid black;
}

table th {
    padding: 3px 10px;
    background-color: white;
    border: 1px solid black;
}

table td {
    padding: 3px 10px;
    border: 1px solid black;
}

Any help or guidance on customizing Pandoc's output to achieve this specific table of contents placement would be greatly appreciated!


Solution

  • The table of contents is inserted in your template via $table-of-contents$. You cannot change it in the template.

    But you could insert the $table-of-contents$ in a javascript part of your template and placing it to whatever element in your dom. You can see such an approach in toc-css.

    Or you write a simple Lua filter that inserts the table of contents in your document. Here an example:

    Your input.md markdown file:

    # Document Title
    
    Some introductory text...
    
    ## Table of Contents
    
    YOURTOCMARKER
    
    ## First Section
    
    Content of the first section...
    

    A Lua filter my_filter.lua

    local your_toc = {}
    Pandoc = function(elem)
        your_toc = pandoc.structure.table_of_contents(elem)
        print(your_toc)
        return elem:walk{
            Block = function(s)
                if pandoc.utils.stringify(s) == "YOURTOCMARKER" then
                    print(type(your_toc))
                    return {
                        pandoc.RawBlock('html', '<nav id="my_toc">'), 
                        your_toc, 
                        pandoc.RawBlock('html', '</nav>'),
                    }
                end
            end
        }
    end
    

    Run with the following

    pandoc input.md -f markdown -t html -o output.html --lua-filter your_filter.lua
    

    produces the following output output.html:

    <h1 id="document-title">Document Title</h1>
    <p>Some introductory text…</p>
    <h2 id="table-of-contents">Table of Contents</h2>
    <nav id="my_toc">
      <ul>
        <li>
          <a href="#document-title" id="toc-document-title">Document Title</a>
          <ul>
            <li>
              <a href="#table-of-contents" id="toc-table-of-contents">Table of Contents</a>
            </li>
            <li>
              <a href="#first-section" id="toc-first-section">First Section</a>
            </li>
          </ul>
        </li>
      </ul>
    </nav>
    <h2 id="first-section">First Section</h2>
    <p>Content of the first section…</p>
    

    output.html

    You can of course combine this approach with the --css and --template options.