From 71db354fbe38a2fcf57d7d7fe6c3e903d96730ce Mon Sep 17 00:00:00 2001 From: Jim Porter Date: Thu, 13 Feb 2020 13:19:46 -0800 Subject: [PATCH] Use toc_tokens to generate the TOC This patch improves the consistency of TOC levels, so now the level is always equal to the N in the `` tag. It also allows users of the MkDocs theme to set the navigation depth to show in the TOC panel (defaulting to 2). --- docs/about/release-notes.md | 3 + docs/user-guide/styling-your-docs.md | 3 + mkdocs/contrib/search/search_index.py | 2 +- mkdocs/structure/pages.py | 2 +- mkdocs/structure/toc.py | 105 +++++--------------------- mkdocs/tests/base.py | 2 +- mkdocs/tests/config/config_tests.py | 10 ++- mkdocs/tests/structure/toc_tests.py | 33 +------- mkdocs/tests/theme_tests.py | 5 +- mkdocs/themes/mkdocs/css/base.css | 22 +++--- mkdocs/themes/mkdocs/mkdocs_theme.yml | 2 + mkdocs/themes/mkdocs/toc.html | 22 ++++-- requirements/project-min.txt | 2 +- requirements/project.txt | 2 +- setup.py | 2 +- 15 files changed, 67 insertions(+), 150 deletions(-) diff --git a/docs/about/release-notes.md b/docs/about/release-notes.md index e5e3c66ac5..7e23603300 100644 --- a/docs/about/release-notes.md +++ b/docs/about/release-notes.md @@ -95,6 +95,9 @@ do, adding `--strict`, `--theme`, `--theme-dir`, and `--site-dir`. theme (#1234). * Bugfix: Multi-row nav headers in the `mkdocs` theme no longer obscure the document content (#716). +* Add support for `navigation_depth` theme option for the `mkdocs` theme (#1970). +* `level` attribute in `page.toc` items is now 1-indexed to match the level in + `` tags (#1970). ## Version 1.0.4 (2018-09-07) diff --git a/docs/user-guide/styling-your-docs.md b/docs/user-guide/styling-your-docs.md index efb24394f4..234193d7ba 100644 --- a/docs/user-guide/styling-your-docs.md +++ b/docs/user-guide/styling-your-docs.md @@ -73,6 +73,9 @@ supports the following options: * __`search`__: Display the search modal. Default: `83` (s) +* __`navigation_depth`__: The maximum depth of the navigation tree in the + sidebar. Default: `2`. + * __`nav_style`__: This adjusts the visual style for the top navigation bar; by default, this is set to `primary` (the default), but it can also be set to `dark` or `light`. diff --git a/mkdocs/contrib/search/search_index.py b/mkdocs/contrib/search/search_index.py index c373cb32b3..1aa9fa7015 100644 --- a/mkdocs/contrib/search/search_index.py +++ b/mkdocs/contrib/search/search_index.py @@ -27,7 +27,7 @@ def _find_toc_by_id(self, toc, id_): and return the matched item in the TOC. """ for toc_item in toc: - if toc_item.url[1:] == id_: + if toc_item.id == id_: return toc_item toc_item_r = self._find_toc_by_id(toc_item.children, id_) if toc_item_r is not None: diff --git a/mkdocs/structure/pages.py b/mkdocs/structure/pages.py index 62623b05c1..afc35a1aea 100644 --- a/mkdocs/structure/pages.py +++ b/mkdocs/structure/pages.py @@ -181,7 +181,7 @@ def render(self, config, files): extension_configs=config['mdx_configs'] or {} ) self.content = md.convert(self.markdown) - self.toc = get_toc(getattr(md, 'toc', '')) + self.toc = get_toc(getattr(md, 'toc_tokens', [])) class _RelativePathTreeprocessor(Treeprocessor): diff --git a/mkdocs/structure/toc.py b/mkdocs/structure/toc.py index 009872f95e..143292a93f 100644 --- a/mkdocs/structure/toc.py +++ b/mkdocs/structure/toc.py @@ -1,16 +1,18 @@ """ Deals with generating the per-page table of contents. -For the sake of simplicity we use an existing markdown extension to generate -an HTML table of contents, and then parse that into the underlying data. +For the sake of simplicity we use the Python-Markdown `toc` extension to +generate a list of dicts for each toc item, and then store it as AnchorLinks to +maintain compatibility with older versions of MkDocs. """ -from html.parser import HTMLParser - -def get_toc(toc_html): - items = _parse_html_table_of_contents(toc_html) - return TableOfContents(items) +def get_toc(toc_tokens): + toc = [_parse_toc_token(i) for i in toc_tokens] + # For the table of contents, always mark the first element as active + if len(toc): + toc[0].active = True + return TableOfContents(toc) class TableOfContents: @@ -34,10 +36,14 @@ class AnchorLink: """ A single entry in the table of contents. """ - def __init__(self, title, url, level): - self.title, self.url, self.level = title, url, level + def __init__(self, title, id, level): + self.title, self.id, self.level = title, id, level self.children = [] + @property + def url(self): + return '#' + self.id + def __str__(self): return self.indent_print() @@ -49,79 +55,8 @@ def indent_print(self, depth=0): return ret -class _TOCParser(HTMLParser): - def __init__(self): - HTMLParser.__init__(self) - self.links = [] - - self.in_anchor = False - self.attrs = None - self.title = '' - - # Prior to Python3.4 no convert_charrefs keyword existed. - # However, in Python3.5 the default was changed to True. - # We need the False behavior in all versions but can only - # set it if it exists. - if hasattr(self, 'convert_charrefs'): # pragma: no cover - self.convert_charrefs = False - - def handle_starttag(self, tag, attrs): - if not self.in_anchor: - if tag == 'a': - self.in_anchor = True - self.attrs = dict(attrs) - - def handle_endtag(self, tag): - if tag == 'a': - self.in_anchor = False - - def handle_data(self, data): - if self.in_anchor: - self.title += data - - def handle_charref(self, ref): - self.handle_entityref("#" + ref) - - def handle_entityref(self, ref): - self.handle_data("&%s;" % ref) - - -def _parse_html_table_of_contents(html): - """ - Given a table of contents string that has been automatically generated by - the markdown library, parse it into a tree of AnchorLink instances. - - Returns a list of all the parent AnchorLink instances. - """ - lines = html.splitlines()[2:-2] - ret, parents, level = [], [], 0 - for line in lines: - parser = _TOCParser() - parser.feed(line) - if parser.title: - try: - href = parser.attrs['href'] - except KeyError: - continue - title = parser.title - nav = AnchorLink(title, href, level) - # Add the item to its parent if required. If it is a topmost - # item then instead append it to our return value. - if parents: - parents[-1].children.append(nav) - else: - ret.append(nav) - # If this item has children, store it as the current parent - if line.endswith('
    '): - level += 1 - parents.append(nav) - elif line.startswith('
'): - level -= 1 - if parents: - parents.pop() - - # For the table of contents, always mark the first element as active - if ret: - ret[0].active = True - - return ret +def _parse_toc_token(token): + anchor = AnchorLink(token['name'], token['id'], token['level']) + for i in token['children']: + anchor.children.append(_parse_toc_token(i)) + return anchor diff --git a/mkdocs/tests/base.py b/mkdocs/tests/base.py index 00183fd2c6..1ea373afcf 100644 --- a/mkdocs/tests/base.py +++ b/mkdocs/tests/base.py @@ -16,7 +16,7 @@ def get_markdown_toc(markdown_source): """ Return TOC generated by Markdown parser from Markdown source text. """ md = markdown.Markdown(extensions=['toc']) md.convert(markdown_source) - return md.toc + return md.toc_tokens def load_config(**cfg): diff --git a/mkdocs/tests/config/config_tests.py b/mkdocs/tests/config/config_tests.py index 5a52051fc1..8d562dc53c 100644 --- a/mkdocs/tests/config/config_tests.py +++ b/mkdocs/tests/config/config_tests.py @@ -119,8 +119,9 @@ def test_theme(self): 'highlightjs': True, 'hljs_style': 'github', 'hljs_languages': [], - 'shortcuts': {'help': 191, 'next': 78, 'previous': 80, 'search': 83}, - 'nav_style': 'primary' + 'navigation_depth': 2, + 'nav_style': 'primary', + 'shortcuts': {'help': 191, 'next': 78, 'previous': 80, 'search': 83} } }, { 'dirs': [os.path.join(theme_dir, 'readthedocs'), mkdocs_templates_dir], @@ -182,8 +183,9 @@ def test_theme(self): 'highlightjs': True, 'hljs_style': 'github', 'hljs_languages': [], - 'shortcuts': {'help': 191, 'next': 78, 'previous': 80, 'search': 83}, - 'nav_style': 'primary' + 'navigation_depth': 2, + 'nav_style': 'primary', + 'shortcuts': {'help': 191, 'next': 78, 'previous': 80, 'search': 83} } } ) diff --git a/mkdocs/tests/structure/toc_tests.py b/mkdocs/tests/structure/toc_tests.py index e0c254d660..591d4f7ba4 100644 --- a/mkdocs/tests/structure/toc_tests.py +++ b/mkdocs/tests/structure/toc_tests.py @@ -7,23 +7,6 @@ class TableOfContentsTests(unittest.TestCase): - def test_html_toc(self): - html = dedent(""" -
- -
- """) - expected = dedent(""" - Heading 1 - #foo - Heading 2 - #bar - """) - toc = get_toc(html) - self.assertEqual(str(toc).strip(), expected) - self.assertEqual(len(toc), 2) - def test_indented_toc(self): md = dedent(""" # Heading 1 @@ -163,20 +146,6 @@ def test_charref(self): self.assertEqual(str(toc).strip(), expected) self.assertEqual(len(toc), 1) - def test_skip_no_href(self): - html = dedent(""" -
- -
- """) - expected = 'Header 2 - #foo' - toc = get_toc(html) - self.assertEqual(str(toc).strip(), expected) - self.assertEqual(len(toc), 1) - def test_level(self): md = dedent(""" # Heading 1 @@ -192,4 +161,4 @@ def get_level_sequence(items): yield item.level yield from get_level_sequence(item.children) - self.assertEqual(tuple(get_level_sequence(toc)), (0, 1, 2, 2, 1)) + self.assertEqual(tuple(get_level_sequence(toc)), (1, 2, 3, 3, 2)) diff --git a/mkdocs/tests/theme_tests.py b/mkdocs/tests/theme_tests.py index 21676da66e..24e0ebb400 100644 --- a/mkdocs/tests/theme_tests.py +++ b/mkdocs/tests/theme_tests.py @@ -32,8 +32,9 @@ def test_simple_theme(self): 'highlightjs': True, 'hljs_style': 'github', 'hljs_languages': [], - 'shortcuts': {'help': 191, 'next': 78, 'previous': 80, 'search': 83}, - 'nav_style': 'primary' + 'navigation_depth': 2, + 'nav_style': 'primary', + 'shortcuts': {'help': 191, 'next': 78, 'previous': 80, 'search': 83} }) def test_custom_dir(self): diff --git a/mkdocs/themes/mkdocs/css/base.css b/mkdocs/themes/mkdocs/css/base.css index 0014fcf792..2de8035cde 100644 --- a/mkdocs/themes/mkdocs/css/base.css +++ b/mkdocs/themes/mkdocs/css/base.css @@ -33,10 +33,6 @@ body > .container { /* csslint ignore:end */ } -ul.nav .main { - font-weight: bold; -} - .source-links { float: right; } @@ -168,7 +164,7 @@ footer { } /* First level of nav */ -.bs-sidenav { +.bs-sidebar > .navbar-collapse > .nav { padding-top: 10px; padding-bottom: 10px; border-radius: 5px; @@ -194,16 +190,16 @@ footer { border-right: 1px solid; } -/* Nav: second level (shown on .active) */ -.bs-sidebar .nav .nav { - display: none; /* Hide by default, but at >768px, show it */ - margin-bottom: 8px; +.bs-sidebar .nav .nav .nav { + margin-left: 1em; } + +.bs-sidebar .nav > li > a { + font-weight: bold; +} + .bs-sidebar .nav .nav > li > a { - padding-top: 3px; - padding-bottom: 3px; - padding-left: 30px; - font-size: 90%; + font-weight: normal; } .headerlink { diff --git a/mkdocs/themes/mkdocs/mkdocs_theme.yml b/mkdocs/themes/mkdocs/mkdocs_theme.yml index 4b2525b09a..c151ff06af 100644 --- a/mkdocs/themes/mkdocs/mkdocs_theme.yml +++ b/mkdocs/themes/mkdocs/mkdocs_theme.yml @@ -9,6 +9,8 @@ search_index_only: false highlightjs: true hljs_languages: [] hljs_style: github + +navigation_depth: 2 nav_style: primary shortcuts: diff --git a/mkdocs/themes/mkdocs/toc.html b/mkdocs/themes/mkdocs/toc.html index 62200988a0..89223f805a 100644 --- a/mkdocs/themes/mkdocs/toc.html +++ b/mkdocs/themes/mkdocs/toc.html @@ -5,15 +5,21 @@ + {% macro toc_item(item) %} + {%- if item.level <= config.theme.navigation_depth %} + + {%- endif %} + {%- endmacro %} diff --git a/requirements/project-min.txt b/requirements/project-min.txt index 209236c33c..44029f78c4 100644 --- a/requirements/project-min.txt +++ b/requirements/project-min.txt @@ -1,7 +1,7 @@ click==3.3 Jinja2==2.10.1 livereload==2.5.1 -Markdown==3.0.1 +Markdown==3.2.1 PyYAML==3.13 tornado==4.1 mdx_gh_links>=0.2 diff --git a/requirements/project.txt b/requirements/project.txt index c94239867e..846d72c84e 100644 --- a/requirements/project.txt +++ b/requirements/project.txt @@ -1,7 +1,7 @@ click>=7.0 Jinja2>=2.10.3 livereload>=2.6.1 -Markdown>=3.1.1 +Markdown>=3.2.1 PyYAML>=5.2 tornado>=5.1.1 mdx_gh_links>=0.2 diff --git a/setup.py b/setup.py index 780b700516..d9029ab2e5 100755 --- a/setup.py +++ b/setup.py @@ -58,7 +58,7 @@ def get_packages(package): 'Jinja2>=2.10.1', 'livereload>=2.5.1', 'lunr[languages]>=0.5.2', - 'Markdown>=3.0.1', + 'Markdown>=3.2.1', 'PyYAML>=3.10', 'tornado>=5.0' ],