An extension for Python-Markdown which extends the built-in toc with additional capabilities to customize the generated table of contents.
Aside from Python-Markdown itself, you’ll want to enable the official Python-Markdown attr_list extension.
Please refer to https://python-markdown.github.io/extensions/
In mkdocs.yml
under markdown_extensions
replace toc
with toc_ext
i.e.
markdown_extensions:
- toc_ext
This extension extends the built-in Markdown toc
extensions. All
its configuration
options are supported.
pymdown-toc-ext looks for the presence of data-toc-*
attributes on
Markdown elements. Given Markdown itself doesn’t provide support for
adding attributes, this is why the Markdown attr_list
extension is
required.
Whilst the standard toc
extension only supports Markdown headers and
uses the header depth to nest table of contents entries. toc_ext
supports generating table of contents entries linking to any element
on the page.
Entries are created based on the presence of data-toc-*
attributes
contained within your markdown.
If you provide a data-toc-label
attribute on its lonesome, then a ToC
entry will be inserted as the first entry (at the root level) in the
ToC.
[](){: #top data-toc-label="Goto Top" }
The above will insert an entry with the text "Goto Top", linking to an
empty (invisible) anchor, which is explicitly given the ID top
.
ℹ️
|
We strip data-toc-* attributes that we consume. So the
resultant generated anchor will simply be <a id="top" href=""></a> .
|
You may omit an explicit ID, and an ID will be generated for you based on the ToC label e.g.
[](){: data-toc-label="Goto Top" }
This will result in an empty/invisible anchor on the page,
<a id="goto-top" href=""></a>
and a single ToC entry with the text
Goto Top
.
By default, we insert entries at the top of the table of contents.
However, we may wish to insert the entry as a child of another entry.
This is achieved by the inclusion of the data-toc-child-of
attribute
specifying the ID of another TOC entry.
## Category 1
Some introductory text that doesn't warrant a heading.
{: data-toc-label="Intro" data-toc-child-of="category-1" }
## Category 2
**Technobabble**{: data-toc-child-of="glossary" } is really important:
* We want readers to understand the concept before moving on.
* We don't want unnecessary headings breaking the page's flow.
* We also want this definition appearing in the table of contents, for easy lookup.
## Glossary
### Peripheral
Of secondary or minor importance.
The above will generate:
-
Category 1
-
Intro
-
-
Category 2
-
Glossary
-
Technobabble
-
Peripheral
-
💡
|
You may omit data-toc-label . If you do, the ToC label will be
generated from the contents of the target element.
|
In the above example, we ended up with "Technobabble" appearing before "Peripheral". Perhaps we wanted the entries to appear in alphabetical order.
Instead of specifying data-toc-child-of
, we specify data-toc-after
,
with the ID of the element that our entry should appear after.
## Alphabet
B
{: data-toc-after="a" }
A
{: data-toc-child-of="alphabet" }
C
{: data-toc-after="b" }
The above will generate:
-
Alphabet
-
A
-
B
-
C
-
Manual ordering each entry can be tedious. Frequently we just want to
alphabetically sort child entries. We can mark an entry as sorted by
adding the attribute data-toc-sort
.
The example above can therefore be alternatively expressed as:
## Alphabet {: data-toc-sort }
B
{: data-toc-child-of="alphabet" }
A
{: data-toc-child-of="alphabet" }
C
{: data-toc-child-of="alphabet" }
Reverse ordering is supported by data-toc-sort="reverse"
e.g.
## Alphabet {: data-toc-sort="reverse" }
B
{: data-toc-child-of="alphabet" }
A
{: data-toc-child-of="alphabet" }
C
{: data-toc-child-of="alphabet" }
will generate:
-
Alphabet
-
C
-
B
-
A
-
The top level entries don’t have a parent that you can mark as sorted.
Instead data-toc-root-sort
may appear anywhere in your document.
Otherwise, it behaves just like data-toc-sort
on a TOC entry e.g.
Both:
B
{: data-toc-root-sort data-toc-label="B" }
A
{: data-toc-label="A" }
C
{: data-toc-label="C" }
and:
## B {: data-toc-root-sort }
## A
## C
will result in the table of contents:
-
A
-
B
-
C
A Markdown heading may be omitted from the generated table of contents by
adding the attribute data-toc-omit
.
## Heading 1
## Heading 2 {: data-toc-omit }
## Heading 3
will result in the table of contents:
-
Heading 1
-
Heading 3
If you omit a heading that has sub-headings, the sub-headings will be moved up a level, replacing the omitted heading.
## A
## B {: data-toc-omit }
### B1
### B2
## C
will result in the table of contents:
-
A
-
B1
-
B2
-
C
Rather than linking to an ID on the same page, you can insert a table of
contents entry that links to a URL by providing a data-toc-url
attribute.
## A
The sky is falling. [ref 1]
{: #sky-falling data-toc-label="#1 English Fairy Tales" data-toc-url="https://example.com" data-toc-child-of="references" }
## B
The earth is flat. [ref 2]
{: #dawn-treader data-toc-label="#2 The Voyage of the Dawn Treader" data-toc-url="https://example.com" data-toc-child-of="references" }
## References {: data-toc-sort }
Even though the link is to an external website, an ID is still mandatory.
|
The PyMdown MagicLink extension breaks support for URLs appearing in attribute lists. You’re unable to create custom URL entries whilst using this extension. |
We inject an additional url
field into
toc_tokens.
We use this field when generating the content of md.toc
, thus both
md.toc
and md.toc_tokens
are URL-aware. If you’ve got direct access
to either of these then you’re good to go.
If you’re using mkdocs, it doesn’t expose toc_tokens
directly, but
rather its own data model. It has a url
field, but is simply derived
from toc_tokens['id']
. Thus, we monkey-patch mkdocs to instead return
toc_tokens['url']
when it’s available.
Admittedly, this is quite a wonky solution. If you’re not using the
custom URL functionality you can disable the mkdocs patching in your
mkdocs.yml
with:
markdown_extensions:
- toc_ext:
patch_mkdocs: false
Contributions welcome.
At the time of writing pip cannot install editable poetry packages.
To appease pip, you can generate a setup.py
with:
poetry build --format sdist && tar -xvf dist/*-`poetry version -s`.tar.gz -O '*/setup.py' > setup.py
You’ll then be able to, in another Python project, install your local editable package with:
pip install -e /path/to/pymdown-toc/ext