Table of Contents Extension

The TableOfContentsExtension automatically inserts a table of contents into your document with links to the various headings.

The Heading Permalink extension must also be included for this to work.

Installation

This extension is bundled with league/commonmark. This library can be installed via Composer:

composer require league/commonmark

See the installation section for more details.

Usage

Configure your Environment as usual and simply add the TableOfContentsExtension and HeadingPermalinkExtension provided by this package:

use League\CommonMark\CommonMarkConverter;
use League\CommonMark\Environment;
use League\CommonMark\Extension\HeadingPermalink\HeadingPermalinkExtension;
use League\CommonMark\Extension\TableOfContents\TableOfContentsExtension;

// Obtain a pre-configured Environment with all the CommonMark parsers/renderers ready-to-go
$environment = Environment::createCommonMarkEnvironment();

// Add the two extensions
$environment->addExtension(new HeadingPermalinkExtension());
$environment->addExtension(new TableOfContentsExtension());

// Set your configuration
$config = [
    // Extension defaults are shown below
    // If you're happy with the defaults, feel free to remove them from this array
    'table_of_contents' => [
        'html_class' => 'table-of-contents',
        'position' => 'top',
        'style' => 'bullet',
        'min_heading_level' => 1,
        'max_heading_level' => 6,
        'normalize' => 'relative',
        'placeholder' => null,
    ],
];

// Instantiate the converter engine and start converting some Markdown!
$converter = new CommonMarkConverter($config, $environment);
echo $converter->convertToHtml('# Awesome!');

Configuration

This extension can be configured by providing a table_of_contents array with several nested configuration options. The defaults are shown in the code example above.

html_class

The value of this nested configuration option should be a string that you want set as the <ul> or <ol> tag’s class attribute. This defaults to 'table-of-contents'.

normalize

This should be a string that defines one of three different strategies to use when generating a (potentially-nested) list from your various headings:

See “Normalization Strategies” below for more information.

position

This string controls where in the document your table of contents will be placed. There are two options:

If you’d like to customize this further, you can implement a custom event listener to locate the TableOfContents node and reposition it somewhere else in the document prior to rendering.

placeholder

When combined with 'position' => 'placeholder', this setting tells the extension which placeholder content should be replaced with the Table of Contents. For example, if you set this option to [TOC], then any lines in your document consisting of that [TOC] placeholder will be replaced by the Table of Contents. Note that this option has no default value - you must provide this string yourself.

style

This string option controls what style of HTML list should be used to render the table of contents:

min_heading_level and max_heading_level

These two settings control which headings should appear in the list. By default, all 6 levels (1, 2, 3, 4, 5, and 6). You can override this by setting the min_heading_level and/or max_heading_level to a different number (int value).

Normalization Strategies

Consider this sample Markdown input:

## Level 2 Heading

This is a sample document that starts with a level 2 heading

#### Level 4 Heading

Notice how we went from a level 2 heading to a level 4 heading!

### Level 3 Heading

And now we have a level 3 heading here.

Here’s how the different normalization strategies would handle this input:

Strategy: 'flat'

All links in your table of contents will be shown in a flat, single-level list:

<ul class="table-of-contents">
    <li>
        <p><a href="#level-2-heading">Level 2 Heading</a></p>
    </li>
    <li>
        <p><a href="#level-4-heading">Level 4 Heading</a></p>
    </li>
    <li>
        <p><a href="#level-3-heading">Level 3 Heading</a></p>
    </li>
</ul>

<!-- The rest of the content would go here -->

Strategy: 'as-is'

Level 1 headings (<h1>) will appear on the first level of the list, with level 2 headings (<h2>) nested under those, and so forth - exactly as they occur within the document. But this can get weird if your document doesn’t start with level 1 headings, or it doesn’t properly nest the levels:

<ul class="table-of-contents">
    <li>
        <ul>
            <li>
                <p><a href="#level-2-heading">Level 2 Heading</a></p>
                <ul>
                    <li>
                        <ul>
                            <li>
                                <p><a href="#level-4-heading">Level 4 Heading</a></p>
                            </li>
                        </ul>
                    </li>
                    <li>
                        <p><a href="#level-3-heading">Level 3 Heading</a></p>
                    </li>
                </ul>
            </li>
        </ul>
    </li>
</ul>

<!-- The rest of the content would go here -->

Strategy: 'relative'

Applies nesting, but handles edge cases (like incorrect nesting levels) as you’d expect:

<ul class="table-of-contents">
    <li>
        <p><a href="#level-2-heading">Level 2 Heading</a></p>
        <ul>
            <li>
                <p><a href="#level-4-heading">Level 4 Heading</a></p>
            </li>
        </ul>
        <ul>
            <li>
                <p><a href="#level-3-heading">Level 3 Heading</a></p>
            </li>
        </ul>
    </li>
</ul>

<!-- The rest of the content would go here -->