Skip to content

Commit

Permalink
✨ ENH: Support captions in sidebar toctree (#346)
Browse files Browse the repository at this point in the history
* Support captions in sidebar toctree

* update for latest changes with startdepth

* update tests

* add css styling for captions

* clean-up

* add changed assets

* add CSS variable to control caption color

* clean-up
  • Loading branch information
jorisvandenbossche committed Mar 27, 2021
1 parent 9908530 commit b822548
Show file tree
Hide file tree
Showing 10 changed files with 119 additions and 26 deletions.
96 changes: 81 additions & 15 deletions pydata_sphinx_theme/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
import os

from sphinx.errors import ExtensionError
from sphinx.environment.adapters.toctree import TocTree
from sphinx import addnodes

from bs4 import BeautifulSoup as bs

from .bootstrap_html_translator import BootstrapHTML5Translator
Expand Down Expand Up @@ -38,24 +41,16 @@ def generate_nav_html(kind, startdepth=None, **kwargs):
HTML string (if kind in ["navbar", "sidebar"])
or BeautifulSoup object (if kind == "raw")
"""
toc_sphinx = context["toctree"](**kwargs)
soup = bs(toc_sphinx, "html.parser")

if startdepth is None:
startdepth = 1 if kind == "sidebar" else 0

# select the "active" subset of the navigation tree for the sidebar
if startdepth > 0:
selector = " ".join(
[
"li.current.toctree-l{} ul".format(i)
for i in range(1, startdepth + 1)
]
)
subset = soup.select(selector)
if not subset:
return ""
soup = bs(str(subset[0]), "html.parser")
if startdepth == 0:
toc_sphinx = context["toctree"](**kwargs)
else:
# select the "active" subset of the navigation tree for the sidebar
toc_sphinx = index_toctree(app, pagename, startdepth, **kwargs)

soup = bs(toc_sphinx, "html.parser")

# pair "current" with "active" since that's what we use w/ bootstrap
for li in soup("li", {"class": "current"}):
Expand Down Expand Up @@ -207,6 +202,77 @@ def navbar_align_class():
context["navbar_align_class"] = navbar_align_class


def _get_local_toctree_for(
self: TocTree, indexname: str, docname: str, builder, collapse: bool, **kwargs
):
"""Return the "local" TOC nodetree (relative to `indexname`)."""
# this is a copy of `TocTree.get_toctree_for`, but where the sphinx version
# always uses the "master" doctree:
# doctree = self.env.get_doctree(self.env.config.master_doc)
# we here use the `indexname` additional argument to be able to use a subset
# of the doctree (e.g. starting at a second level for the sidebar):
# doctree = app.env.tocs[indexname].deepcopy()

doctree = self.env.tocs[indexname].deepcopy()

toctrees = []
if "includehidden" not in kwargs:
kwargs["includehidden"] = True
if "maxdepth" not in kwargs or not kwargs["maxdepth"]:
kwargs["maxdepth"] = 0
else:
kwargs["maxdepth"] = int(kwargs["maxdepth"])
kwargs["collapse"] = collapse

for toctreenode in doctree.traverse(addnodes.toctree):
toctree = self.resolve(docname, builder, toctreenode, prune=True, **kwargs)
if toctree:
toctrees.append(toctree)
if not toctrees:
return None
result = toctrees[0]
for toctree in toctrees[1:]:
result.extend(toctree.children)
return result


def index_toctree(app, pagename: str, startdepth: int, collapse: bool = True, **kwargs):
"""
Returns the "local" (starting at `startdepth`) TOC tree containing the
current page, rendered as HTML bullet lists.
This is the equivalent of `context["toctree"](**kwargs)` in sphinx
templating, but using the startdepth-local instead of global TOC tree.
"""
# this is a variant of the function stored in `context["toctree"]`, which is
# defined as `lambda **kwargs: self._get_local_toctree(pagename, **kwargs)`
# with `self` being the HMTLBuilder and the `_get_local_toctree` basically
# returning:
# return self.render_partial(TocTree(self.env).get_toctree_for(
# pagename, self, collapse, **kwargs))['fragment']

if "includehidden" not in kwargs:
kwargs["includehidden"] = False
if kwargs.get("maxdepth") == "":
kwargs.pop("maxdepth")

toctree = TocTree(app.env)
ancestors = toctree.get_toctree_ancestors(pagename)
try:
indexname = ancestors[-startdepth]
except IndexError:
# eg for index.rst, but also special pages such as genindex, py-modindex, search
# those pages don't have a "current" element in the toctree, so we can
# directly return an emtpy string instead of using the default sphinx
# toctree.get_toctree_for(pagename, app.builder, collapse, **kwargs)
return ""

toctree_element = _get_local_toctree_for(
toctree, indexname, pagename, app.builder, collapse, **kwargs
)
return app.builder.render_partial(toctree_element)["fragment"]


def soup_to_python(soup, only_pages=False):
"""
Convert the toctree html structure to python objects which can be used in Jinja.
Expand Down

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions pydata_sphinx_theme/static/css/theme.css
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
--pst-color-sidebar-link: 77, 77, 77;
--pst-color-sidebar-link-hover: var(--pst-color-active-navigation);
--pst-color-sidebar-link-active: var(--pst-color-active-navigation);
--pst-color-sidebar-caption: 77, 77, 77;
--pst-color-toc-link: 119, 117, 122;
--pst-color-toc-link-hover: var(--pst-color-active-navigation);
--pst-color-toc-link-active: var(--pst-color-active-navigation);
Expand Down
6 changes: 3 additions & 3 deletions pydata_sphinx_theme/static/webpack-macros.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@

{% macro head_pre_bootstrap() %}
<link href="{{ pathto('_static/css/theme.css', 1) }}" rel="stylesheet" />
<link href="{{ pathto('_static/css/index.f6b7ca918bee2f46fd9abac01cfb07d5.css', 1) }}" rel="stylesheet" />
<link href="{{ pathto('_static/css/index.987b06ff42468e43a77c64bd883678fc.css', 1) }}" rel="stylesheet" />
{% endmacro %}

{% macro head_js_preload() %}
<link rel="preload" as="script" href="{{ pathto('_static/js/index.1e043a052b0af929e4d8.js', 1) }}">
<link rel="preload" as="script" href="{{ pathto('_static/js/index.c681211bbbd497c597b1.js', 1) }}">
{% endmacro %}

{% macro body_post() %}
<script src="{{ pathto('_static/js/index.1e043a052b0af929e4d8.js', 1) }}"></script>
<script src="{{ pathto('_static/js/index.c681211bbbd497c597b1.js', 1) }}"></script>
{% endmacro %}
17 changes: 17 additions & 0 deletions src/scss/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,23 @@ table.field-list {
}
}

nav.bd-links {

p.caption {
font-size: 0.9em;
text-transform: uppercase;
font-weight: bold;
position: relative;
margin-top: 1.25em;
margin-bottom: 0.5em;
padding: 0 1.5rem;
color: rgba(var(--pst-color-sidebar-caption), 1);
&:first-child {
margin-top: 0;
}
}
}

.bd-sidebar .nav {
ul {
list-style: none;
Expand Down
1 change: 0 additions & 1 deletion tests/test_build.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,6 @@ def test_sidebars_captions(sphinx_build_factory, file_regression):

# Sidebar structure
sidebar = subindex_html.select("nav#bd-docs-nav")[0]
# TODO this should include the captions
file_regression.check(sidebar.prettify(), extension=".html")


Expand Down
4 changes: 2 additions & 2 deletions tests/test_build/sidebar_subpage.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
<nav aria-label="Main navigation" class="bd-links" id="bd-docs-nav">
<div class="bd-toc-item active">
<ul class="nav bd-sidenav">
<li class="toctree-l2">
<li class="toctree-l1">
<a class="reference internal" href="page1.html">
3.1. Section 1 page1
</a>
</li>
<li class="toctree-l2">
<li class="toctree-l1">
<a class="reference external" href="https://google.com">
https://google.com
</a>
Expand Down
9 changes: 7 additions & 2 deletions tests/test_build/test_sidebars_captions.html
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
<nav aria-label="Main navigation" class="bd-links" id="bd-docs-nav">
<div class="bd-toc-item active">
<p class="caption">
<span class="caption-text">
Section 1
</span>
</p>
<ul class="nav bd-sidenav">
<li class="toctree-l2">
<li class="toctree-l1">
<a class="reference internal" href="subsection1/index.html">
Subsection 1.1 index
</a>
</li>
<li class="toctree-l2">
<li class="toctree-l1">
<a class="reference internal" href="page2.html">
Section 1 page 1
</a>
Expand Down
9 changes: 7 additions & 2 deletions tests/test_build/test_sidebars_level2.html
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
<nav aria-label="Main navigation" class="bd-links" id="bd-docs-nav">
<div class="bd-toc-item active">
<!-- Use deeper level for sidebar -->
<p class="caption">
<span class="caption-text">
Subsection 1.1
</span>
</p>
<ul class="nav bd-sidenav">
<li class="toctree-l3">
<li class="toctree-l1">
<a class="reference internal" href="page1.html">
Section 1 sub 1 page 1
</a>
</li>
<li class="toctree-l3">
<li class="toctree-l1">
<a class="reference internal" href="page2.html">
Section 1 sub 1 page 2
</a>
Expand Down

0 comments on commit b822548

Please sign in to comment.