Skip to content

Commit f7cf135

Browse files
committed
adding captions to left sidebar
1 parent 8a76bc8 commit f7cf135

File tree

2 files changed

+83
-20
lines changed

2 files changed

+83
-20
lines changed

pydata_sphinx_theme/__init__.py

+77-18
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,48 @@
77

88
import sphinx.builders.html
99
from sphinx.errors import ExtensionError
10+
from sphinx.environment.adapters.toctree import TocTree
11+
from sphinx import addnodes
12+
from docutils import nodes
1013

1114
from .bootstrap_html_translator import BootstrapHTML5Translator
1215
import docutils
1316

1417
__version__ = "0.1.1"
1518

1619

20+
class MyTocTree(TocTree):
21+
def get_toctree_for_subpage(
22+
self, pagename, builder, collapse=True, maxdepth=-1, **kwargs
23+
):
24+
"""Return the global TOC nodetree."""
25+
doctree = self.env.get_doctree(pagename)
26+
toctrees = [] # type: List[Element]
27+
if "includehidden" not in kwargs:
28+
kwargs["includehidden"] = True
29+
if "maxdepth" not in kwargs:
30+
kwargs["maxdepth"] = 0
31+
kwargs["collapse"] = collapse
32+
for toctreenode in doctree.traverse(addnodes.toctree):
33+
toctree = self.resolve(pagename, builder, toctreenode, prune=True, **kwargs)
34+
if toctree:
35+
toctrees.append(toctree)
36+
if not toctrees:
37+
return None
38+
result = toctrees[0]
39+
for toctree in toctrees[1:]:
40+
result.extend(toctree.children)
41+
return result
42+
43+
1744
def add_toctree_functions(app, pagename, templatename, context, doctree):
1845
"""Add functions so Jinja templates can add toctree objects.
1946
2047
This converts the docutils nodes into a nested dictionary that Jinja can
2148
use in our templating.
2249
"""
23-
from sphinx.environment.adapters.toctree import TocTree
2450

25-
def get_nav_object(maxdepth=None, collapse=True, **kwargs):
51+
def get_nav_object(maxdepth=None, collapse=True, subpage_caption=False, **kwargs):
2652
"""Return a list of nav links that can be accessed from Jinja.
2753
2854
Parameters
@@ -38,26 +64,46 @@ def get_nav_object(maxdepth=None, collapse=True, **kwargs):
3864
# The TocTree will contain the full site TocTree including sub-pages.
3965
# "collapse=True" collapses sub-pages of non-active TOC pages.
4066
# maxdepth controls how many TOC levels are returned
41-
toctree = TocTree(app.env).get_toctree_for(
67+
toc = MyTocTree(app.env)
68+
toctree = toc.get_toctree_for(
4269
pagename, app.builder, collapse=collapse, maxdepth=maxdepth, **kwargs
4370
)
71+
4472
# If no toctree is defined (AKA a single-page site), skip this
4573
if toctree is None:
4674
return []
4775

76+
if subpage_caption:
77+
# We only wish to show a single page's descendants, so we'll keep their captions
78+
subpage_toctree = toc.get_toctree_for_subpage(
79+
pagename, app.builder, collapse=collapse, maxdepth=maxdepth, **kwargs
80+
)
81+
if subpage_toctree is not None:
82+
# Find the current page in the top-level children
83+
for item in toctree.children:
84+
if isinstance(item, nodes.bullet_list) and item.attributes.get("iscurrent", []):
85+
# Append that pages' toctree so we get captions
86+
subpage_list = item.children[0]
87+
subpage_list.children = [subpage_list.children[0]] + subpage_toctree.children
88+
89+
4890
# toctree has this structure
4991
# <caption>
5092
# <bullet_list>
5193
# <list_item classes="toctree-l1">
5294
# <list_item classes="toctree-l1">
5395
# `list_item`s are the actual TOC links and are the only thing we want
54-
toc_items = [item for child in toctree.children for item in child
55-
if isinstance(item, docutils.nodes.list_item)]
96+
toc_items = []
97+
for child in toctree.children:
98+
if isinstance(child, docutils.nodes.caption):
99+
toc_items.append(child)
100+
elif isinstance(child, docutils.nodes.bullet_list):
101+
for list_entry in child:
102+
if isinstance(list_entry, docutils.nodes.list_item):
103+
toc_items.append(list_entry)
56104

57105
# Now convert our docutils nodes into dicts that Jinja can use
58-
nav = [docutils_node_to_jinja(child, only_pages=True)
59-
for child in toc_items]
60-
106+
nav = [docutils_node_to_jinja(child, only_pages=True) for child in toc_items]
61107
return nav
62108

63109
def get_page_toc_object():
@@ -73,6 +119,7 @@ def get_page_toc_object():
73119
context["get_nav_object"] = get_nav_object
74120
context["get_page_toc_object"] = get_page_toc_object
75121

122+
76123
def docutils_node_to_jinja(list_item, only_pages=False):
77124
"""Convert a docutils node to a structure that can be read by Jinja.
78125
@@ -91,6 +138,12 @@ def docutils_node_to_jinja(list_item, only_pages=False):
91138
The TocTree, converted into a dictionary with key/values that work
92139
within Jinja.
93140
"""
141+
# If a caption, pass it through
142+
if isinstance(list_item, docutils.nodes.caption):
143+
nav = {"text": list_item.astext(), "type": "caption"}
144+
return nav
145+
146+
# Else, we assume it's a list item and need to parse the item content
94147
if not list_item.children:
95148
return None
96149

@@ -100,18 +153,20 @@ def docutils_node_to_jinja(list_item, only_pages=False):
100153
# <reference> <-- the thing we want
101154
reference = list_item.children[0].children[0]
102155
title = reference.astext()
103-
url = reference.attributes["refuri"]
156+
157+
url = reference.attributes.get("refuri", "")
104158
active = "current" in list_item.attributes["classes"]
105159

106160
# If we've got an anchor link, skip it if we wish
107-
if only_pages and '#' in url:
161+
if only_pages and "#" in url:
108162
return None
109163

110164
# Converting the docutils attributes into jinja-friendly objects
111165
nav = {}
112166
nav["title"] = title
113167
nav["url"] = url
114168
nav["active"] = active
169+
nav["type"] = "ref"
115170

116171
# Recursively convert children as well
117172
# If there are sub-pages for this list_item, there should be two children:
@@ -129,19 +184,23 @@ def docutils_node_to_jinja(list_item, only_pages=False):
129184

130185
# -----------------------------------------------------------------------------
131186

187+
132188
def setup_edit_url(app, pagename, templatename, context, doctree):
133189
"""Add a function that jinja can access for returning the edit URL of a page."""
190+
134191
def get_edit_url():
135192
"""Return a URL for an "edit this page" link."""
136193
required_values = ["github_user", "github_repo", "github_version"]
137194
for val in required_values:
138195
if not context.get(val):
139-
raise ExtensionError("Missing required value for `edit this page` button. "
140-
"Add %s to your `html_context` configuration" % val)
141-
142-
github_user = context['github_user']
143-
github_repo = context['github_repo']
144-
github_version = context['github_version']
196+
raise ExtensionError(
197+
"Missing required value for `edit this page` button. "
198+
"Add %s to your `html_context` configuration" % val
199+
)
200+
201+
github_user = context["github_user"]
202+
github_repo = context["github_repo"]
203+
github_version = context["github_version"]
145204
file_name = f"{pagename}{context['page_source_suffix']}"
146205

147206
# Make sure that doc_path has a path separator only if it exists (to avoid //)
@@ -153,7 +212,7 @@ def get_edit_url():
153212
url_edit = f"https://github.com/{github_user}/{github_repo}/edit/{github_version}/{doc_path}{file_name}"
154213
return url_edit
155214

156-
context['get_edit_url'] = get_edit_url
215+
context["get_edit_url"] = get_edit_url
157216

158217

159218
# -----------------------------------------------------------------------------
@@ -174,6 +233,6 @@ def setup(app):
174233
# uses a special "dirhtml" builder so we need to replace these both with
175234
# our custom HTML builder
176235
app.set_translator("readthedocs", BootstrapHTML5Translator, override=True)
177-
app.set_translator('readthedocsdirhtml', BootstrapHTML5Translator, override=True)
236+
app.set_translator("readthedocsdirhtml", BootstrapHTML5Translator, override=True)
178237
app.connect("html-page-context", setup_edit_url)
179238
app.connect("html-page-context", add_toctree_functions)

pydata_sphinx_theme/docs-sidebar.html

+6-2
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,17 @@
77
<nav class="bd-links" id="bd-docs-nav" aria-label="Main navigation">
88

99
<div class="bd-toc-item active">
10-
{% set nav = get_nav_object(maxdepth=3, collapse=True) %}
10+
{% set nav = get_nav_object(maxdepth=3, collapse=True, subpage_caption=True) %}
1111

1212
<ul class="nav bd-sidenav">
13+
<!-- top-level pages -->
1314
{% for main_nav_item in nav %}
1415
{% if main_nav_item.active %}
16+
<!-- sub-pages within each top level page -->
1517
{% for nav_item in main_nav_item.children %}
16-
{% if nav_item.children %}
18+
{% if nav_item["type"] == "caption" %}
19+
<li class="nav-caption">{{ nav_item.text }}</li>
20+
{% elif nav_item.children %}
1721

1822
<li class="{% if nav_item.active%}active{% endif %}">
1923
<a href="{{ nav_item.url }}">{{ nav_item.title }}</a>

0 commit comments

Comments
 (0)