Skip to content

Commit d4f888e

Browse files
authored
maybe fix missing sidebar? (#1632)
* maybe fix missing sidebar? * simplify * purge internal defaults & use theme config setting * formatting * refactor and simplify toctree code
1 parent 1b599c4 commit d4f888e

File tree

3 files changed

+66
-70
lines changed

3 files changed

+66
-70
lines changed

src/pydata_sphinx_theme/theme/pydata_sphinx_theme/components/sidebar-nav-bs.html

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@
55
<div class="bd-toc-item navbar-nav">
66
{{- generate_toctree_html(
77
"sidebar",
8-
show_nav_level=theme_show_nav_level|int,
9-
maxdepth=theme_navigation_depth|int,
10-
collapse=theme_collapse_navigation|tobool,
11-
includehidden=True,
8+
show_nav_level=theme_show_nav_level | int,
9+
maxdepth=theme_navigation_depth | int,
10+
collapse=theme_collapse_navigation | tobool,
11+
includehidden=theme_sidebar_includehidden | tobool,
1212
titles_only=True
1313
)
1414
-}}

src/pydata_sphinx_theme/theme/pydata_sphinx_theme/layout.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@
8080
<div class="bd-container__inner bd-page-width">
8181
{# Primary sidebar #}
8282
{# If we have no sidebar TOC, pop the TOC component from the sidebar list #}
83-
{% if get_sidebar_toctree_length(show_nav_level=theme_show_nav_level|int) == 0 %}
83+
{% if missing_sidebar_toctree(includehidden=theme_sidebar_includehidden) %}
8484
{% set sidebars = sidebars | reject("in", "sidebar-nav-bs.html") | list %}
8585
{% endif %}
8686
<div class="bd-sidebar-primary bd-sidebar{% if not sidebars %} hide-on-wide{% endif %}">

src/pydata_sphinx_theme/toctree.py

Lines changed: 61 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@
99
from bs4 import BeautifulSoup
1010
from docutils import nodes
1111
from docutils.nodes import Node
12-
from sphinx import addnodes
13-
from sphinx.addnodes import toctree as toctree_node
12+
from sphinx.addnodes import toctree as TocTreeNodeClass
1413
from sphinx.application import Sphinx
1514
from sphinx.environment.adapters.toctree import TocTree
1615
from sphinx.locale import _
@@ -32,15 +31,8 @@ def add_inline_math(node: Node) -> str:
3231
)
3332

3433

35-
def get_unrendered_local_toctree(
36-
app: Sphinx, pagename: str, startdepth: int, collapse: bool = True, **kwargs
37-
):
38-
"""."""
39-
if "includehidden" not in kwargs:
40-
kwargs["includehidden"] = False
41-
if kwargs.get("maxdepth") == "":
42-
kwargs.pop("maxdepth")
43-
34+
def _get_ancestor_section(app: Sphinx, pagename: str, startdepth: int) -> str:
35+
"""Get the TocTree node `startdepth` levels below the root that dominates `pagename`."""
4436
toctree = TocTree(app.env)
4537
if sphinx.version_info[:2] >= (7, 2):
4638
from sphinx.environment.adapters.toctree import _get_toctree_ancestors
@@ -49,16 +41,30 @@ def get_unrendered_local_toctree(
4941
else:
5042
ancestors = toctree.get_toctree_ancestors(pagename)
5143
try:
52-
indexname = ancestors[-startdepth]
44+
return ancestors[-startdepth] # will be a pagename (string)?
5345
except IndexError:
5446
# eg for index.rst, but also special pages such as genindex, py-modindex, search
5547
# those pages don't have a "current" element in the toctree, so we can
5648
# directly return an empty string instead of using the default sphinx
5749
# toctree.get_toctree_for(pagename, app.builder, collapse, **kwargs)
58-
return ""
50+
return None
5951

60-
return get_local_toctree_for(
61-
toctree, indexname, pagename, app.builder, collapse, **kwargs
52+
53+
def get_unrendered_local_toctree(app: Sphinx, pagename: str, startdepth: int, **kwargs):
54+
"""Get the "local" (starting at `startdepth`) TocTree containing `pagename`.
55+
56+
This is similar to `context["toctree"](**kwargs)` in sphinx templating,
57+
but using the startdepth-local instead of global TOC tree.
58+
"""
59+
kwargs.setdefault("collapse", True)
60+
if kwargs.get("maxdepth") == "":
61+
kwargs.pop("maxdepth")
62+
toctree = TocTree(app.env)
63+
indexname = _get_ancestor_section(app=app, pagename=pagename, startdepth=startdepth)
64+
if indexname is None:
65+
return None
66+
return get_local_toctree_for_doc(
67+
toctree, indexname, pagename, app.builder, **kwargs
6268
)
6369

6470

@@ -67,11 +73,18 @@ def add_toctree_functions(
6773
) -> None:
6874
"""Add functions so Jinja templates can add toctree objects."""
6975

70-
def get_sidebar_toctree_length(
71-
startdepth: int = 1, show_nav_level: int = 1, **kwargs
72-
):
73-
toctree = get_unrendered_local_toctree(app, pagename, startdepth)
74-
return 0 if toctree is None else len(toctree)
76+
def missing_sidebar_toctree(startdepth: int = 1, **kwargs):
77+
"""Check if there's a sidebar TocTree that needs to be rendered.
78+
79+
Parameters:
80+
startdepth : The level of the TocTree at which to start. 0 includes the
81+
entire TocTree for the site; 1 (default) gets the TocTree for the current
82+
top-level section.
83+
84+
kwargs: passed to the Sphinx `toctree` template function.
85+
"""
86+
toctree = get_unrendered_local_toctree(app, pagename, startdepth, **kwargs)
87+
return toctree is None
7588

7689
@cache
7790
def get_or_create_id_generator(base_id: str) -> Iterator[str]:
@@ -120,8 +133,8 @@ def generate_header_nav_before_dropdown(n_links_before_dropdown):
120133
# Iterate through each toctree node in the root document
121134
# Grab the toctree pages and find the relative link + title.
122135
links_html = []
123-
# TODO: use `root.findall(toctree_node)` once docutils min version >=0.18.1
124-
for toc in traverse_or_findall(root, toctree_node):
136+
# TODO: use `root.findall(TocTreeNodeClass)` once docutils min version >=0.18.1
137+
for toc in traverse_or_findall(root, TocTreeNodeClass):
125138
for title, page in toc.attributes["entries"]:
126139
# if the page is using "self" use the correct link
127140
page = toc.attributes["parent"] if page == "self" else page
@@ -255,12 +268,15 @@ def generate_toctree_html(
255268
HTML string (if kind == "sidebar") OR BeautifulSoup object (if kind == "raw")
256269
"""
257270
if startdepth == 0:
258-
toc_sphinx = context["toctree"](**kwargs)
271+
html_toctree = context["toctree"](**kwargs)
259272
else:
260273
# select the "active" subset of the navigation tree for the sidebar
261-
toc_sphinx = index_toctree(app, pagename, startdepth, **kwargs)
274+
toctree_element = get_unrendered_local_toctree(
275+
app, pagename, startdepth, **kwargs
276+
)
277+
html_toctree = app.builder.render_partial(toctree_element)["fragment"]
262278

263-
soup = BeautifulSoup(toc_sphinx, "html.parser")
279+
soup = BeautifulSoup(html_toctree, "html.parser")
264280

265281
# pair "current" with "active" since that's what we use w/ bootstrap
266282
for li in soup("li", {"class": "current"}):
@@ -378,7 +394,7 @@ def navbar_align_class() -> List[str]:
378394

379395
context["unique_html_id"] = unique_html_id
380396
context["generate_header_nav_html"] = generate_header_nav_html
381-
context["get_sidebar_toctree_length"] = get_sidebar_toctree_length
397+
context["missing_sidebar_toctree"] = missing_sidebar_toctree
382398
context["generate_toctree_html"] = generate_toctree_html
383399
context["generate_toc_html"] = generate_toc_html
384400
context["navbar_align_class"] = navbar_align_class
@@ -443,56 +459,36 @@ def add_collapse_checkboxes(soup: BeautifulSoup) -> None:
443459
element.insert(1, checkbox)
444460

445461

446-
def get_local_toctree_for(
447-
self: TocTree, indexname: str, docname: str, builder, collapse: bool, **kwargs
462+
def get_local_toctree_for_doc(
463+
toctree: TocTree, indexname: str, pagename: str, builder, collapse: bool, **kwargs
448464
) -> List[BeautifulSoup]:
449-
"""Return the "local" TOC nodetree (relative to `indexname`)."""
450-
# this is a copy of `TocTree.get_toctree_for`, but where the sphinx version
451-
# always uses the "root" doctree:
452-
# doctree = self.env.get_doctree(self.env.config.root_doc)
453-
# we here use the `indexname` additional argument to be able to use a subset
454-
# of the doctree (e.g. starting at a second level for the sidebar):
455-
# doctree = app.env.tocs[indexname].deepcopy()
465+
"""Get the "local" TocTree containing `pagename` rooted at `indexname`.
456466
457-
doctree = self.env.tocs[indexname].deepcopy()
467+
The Sphinx equivalent is TocTree.get_toctree_for(), which always uses the "root"
468+
or "global" TocTree:
469+
470+
doctree = self.env.get_doctree(self.env.config.root_doc)
471+
472+
Whereas here we return a subset of the global toctree, rooted at `indexname`
473+
(e.g. starting at a second level for the sidebar).
474+
"""
475+
partial_doctree = toctree.env.tocs[indexname].deepcopy()
458476

459477
toctrees = []
460-
if "includehidden" not in kwargs:
461-
kwargs["includehidden"] = True
462478
if "maxdepth" not in kwargs or not kwargs["maxdepth"]:
463479
kwargs["maxdepth"] = 0
464-
else:
465-
kwargs["maxdepth"] = int(kwargs["maxdepth"])
480+
kwargs["maxdepth"] = int(kwargs["maxdepth"])
466481
kwargs["collapse"] = collapse
467482

468-
# TODO: use `doctree.findall(addnodes.toctree)` once docutils min version >=0.18.1
469-
for toctreenode in traverse_or_findall(doctree, addnodes.toctree):
470-
toctree = self.resolve(docname, builder, toctreenode, prune=True, **kwargs)
471-
if toctree:
472-
toctrees.append(toctree)
483+
# TODO: use `doctree.findall(TocTreeNodeClass)` once docutils min version >=0.18.1
484+
for _node in traverse_or_findall(partial_doctree, TocTreeNodeClass):
485+
# defaults for resolve: prune=True, maxdepth=0, titles_only=False, collapse=False, includehidden=False
486+
_toctree = toctree.resolve(pagename, builder, _node, **kwargs)
487+
if _toctree:
488+
toctrees.append(_toctree)
473489
if not toctrees:
474490
return None
475491
result = toctrees[0]
476492
for toctree in toctrees[1:]:
477493
result.extend(toctree.children)
478494
return result
479-
480-
481-
def index_toctree(
482-
app: Sphinx, pagename: str, startdepth: int, collapse: bool = True, **kwargs
483-
):
484-
"""Returns the "local" (starting at `startdepth`) TOC tree containing the current page, rendered as HTML bullet lists.
485-
486-
This is the equivalent of `context["toctree"](**kwargs)` in sphinx
487-
templating, but using the startdepth-local instead of global TOC tree.
488-
"""
489-
# this is a variant of the function stored in `context["toctree"]`, which is
490-
# defined as `lambda **kwargs: self._get_local_toctree(pagename, **kwargs)`
491-
# with `self` being the HMTLBuilder and the `_get_local_toctree` basically
492-
# returning:
493-
# return self.render_partial(TocTree(self.env).get_toctree_for(
494-
# pagename, self, collapse, **kwargs))['fragment']
495-
toctree_element = get_unrendered_local_toctree(
496-
app, pagename, startdepth, collapse, **kwargs
497-
)
498-
return app.builder.render_partial(toctree_element)["fragment"]

0 commit comments

Comments
 (0)