Skip to content

Commit

Permalink
fixing single-page header bug
Browse files Browse the repository at this point in the history
  • Loading branch information
choldgraf committed Jun 29, 2020
1 parent 3a538b1 commit 7b56b8b
Show file tree
Hide file tree
Showing 9 changed files with 102 additions and 189 deletions.
1 change: 0 additions & 1 deletion docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,3 @@ Other sites that are using this theme:

demo/index
changelog

2 changes: 1 addition & 1 deletion docs/user_guide/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ User Guide

install
configuring
customizing
customizing
186 changes: 75 additions & 111 deletions pydata_sphinx_theme/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,135 +4,99 @@
import os

from sphinx.errors import ExtensionError
from bs4 import BeautifulSoup as bs

from .bootstrap_html_translator import BootstrapHTML5Translator
import docutils

__version__ = "0.3.2dev0"


def add_toctree_functions(app, pagename, templatename, context, doctree):
"""Add functions so Jinja templates can add toctree objects.
This converts the docutils nodes into a nested dictionary that Jinja can
use in our templating.
"""
from sphinx.environment.adapters.toctree import TocTree

def get_nav_object(maxdepth=None, collapse=True, **kwargs):
"""Return a list of nav links that can be accessed from Jinja.
Parameters
----------
maxdepth: int
How many layers of TocTree will be returned
collapse: bool
Whether to only include sub-pages of the currently-active page,
instead of sub-pages of all top-level pages of the site.
kwargs: key/val pairs
Passed to the `TocTree.get_toctree_for` Sphinx method

def get_nav_object(kind, **kwargs):
"""Return the navigation link structure in HTML. Arguments are passed
to Sphinx "toctree" function (context["toctree"] below).
We use beautifulsoup to add the right CSS classes / structure for bootstrap.
See https://www.sphinx-doc.org/en/master/templating.html#toctree.
"""
# The TocTree will contain the full site TocTree including sub-pages.
# "collapse=True" collapses sub-pages of non-active TOC pages.
# maxdepth controls how many TOC levels are returned
toctree = TocTree(app.env).get_toctree_for(
pagename, app.builder, collapse=collapse, maxdepth=maxdepth, **kwargs
)
# If no toctree is defined (AKA a single-page site), skip this
if toctree is None:
return []

# toctree has this structure
# <caption>
# <bullet_list>
# <list_item classes="toctree-l1">
# <list_item classes="toctree-l1">
# `list_item`s are the actual TOC links and are the only thing we want
toc_items = [
item
for child in toctree.children
for item in child
if isinstance(item, docutils.nodes.list_item)
]

# Now convert our docutils nodes into dicts that Jinja can use
nav = [docutils_node_to_jinja(child, only_pages=True) for child in toc_items]

return nav
toc_sphinx = context["toctree"](**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"}):
li["class"].append("active")

if kind == "navbar":
# Add CSS for bootstrap
for li in soup("li"):
li["class"].append("nav-item")
li.find("a")["class"].append("nav-link")
out = "\n".join([ii.prettify() for ii in soup.find_all("li")])

elif kind == "sidebar":
# Remove sidebar links to sub-headers on the page
for li in soup.select("li.current ul li"):
# Remove
if li.find("a"):
href = li.find("a")["href"]
if "#" in href and href != "#":
li.decompose()

# Join all the top-level `li`s together for display
current_lis = soup.select("li.current.toctree-l1 li.toctree-l2")
out = "\n".join([ii.prettify() for ii in current_lis])

return out

def get_page_toc_object():
"""Return a list of within-page TOC links that can be accessed from Jinja."""
self_toc = TocTree(app.env).get_toc_for(pagename, app.builder)

try:
# If there's only one child, assume we have a single "title" as top header
# so start the TOC at the first item's children (AKA, level 2 headers)
if len(self_toc.children) == 1:
nav = docutils_node_to_jinja(self_toc.children[0]).get("children", [])
"""Return the within-page TOC links in HTML."""

if "toc" not in context:
return ""

soup = bs(context["toc"], "html.parser")

# Add toc-hN classes
def add_header_level_recursive(ul, level):
for li in ul("li", recursive=False):
li["class"] = li.get("class", []) + [f"toc-h{level}"]
ul = li.find("ul", recursive=False)
if ul:
add_header_level_recursive(ul, level + 1)

add_header_level_recursive(soup.find("ul"), 1)

# Add in CSS classes for bootstrap
for ul in soup("ul"):
ul["class"] = ul.get("class", []) + ["nav", "section-nav", "flex-column"]
for li in soup("li"):
li["class"] = li.get("class", []) + ["nav-item", "toc-entry"]
if li.find("a"):
a = li.find("a")
a["class"] = a.get("class", []) + ["nav-link"]

# Keep only the sub-sections of the title (so no title is shown)
title = soup.find("a", attrs={"href": "#"})
if title:
title = title.parent
# Only show if children of the title item exist
if title.select("ul li"):
out = title.find("ul").prettify()
else:
nav = [docutils_node_to_jinja(item) for item in self_toc.children]
return nav
except Exception:
return {}
out = ""
else:
out = ""

return out

context["get_nav_object"] = get_nav_object
context["get_page_toc_object"] = get_page_toc_object


def docutils_node_to_jinja(list_item, only_pages=False):
"""Convert a docutils node to a structure that can be read by Jinja.
Parameters
----------
list_item : docutils list_item node
A parent item, potentially with children, corresponding to the level
of a TocTree.
only_pages : bool
Only include items for full pages in the output dictionary. Exclude
anchor links (TOC items with a URL that starts with #)
Returns
-------
nav : dict
The TocTree, converted into a dictionary with key/values that work
within Jinja.
"""
if not list_item.children:
return None

# We assume this structure of a list item:
# <list_item>
# <compact_paragraph >
# <reference> <-- the thing we want
reference = list_item.children[0].children[0]
title = reference.astext()
url = reference.attributes["refuri"]
active = "current" in list_item.attributes["classes"]

# If we've got an anchor link, skip it if we wish
if only_pages and "#" in url:
return None

# Converting the docutils attributes into jinja-friendly objects
nav = {}
nav["title"] = title
nav["url"] = url
nav["active"] = active

# Recursively convert children as well
# If there are sub-pages for this list_item, there should be two children:
# a paragraph, and a bullet_list.
nav["children"] = []
if len(list_item.children) > 1:
# The `.children` of the bullet_list has the nodes of the sub-pages.
subpage_list = list_item.children[1].children
for sub_page in subpage_list:
child_nav = docutils_node_to_jinja(sub_page, only_pages=only_pages)
if child_nav is not None:
nav["children"].append(child_nav)
return nav


# -----------------------------------------------------------------------------


Expand Down
12 changes: 1 addition & 11 deletions pydata_sphinx_theme/docs-navbar.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,7 @@

<div id="navbar-menu" class="col-lg-9 collapse navbar-collapse">
<ul id="navbar-main-elements" class="navbar-nav mr-auto">
{% set nav = get_nav_object(maxdepth=1, collapse=True) %}
{% for main_nav_item in nav %}
<li class="nav-item {% if main_nav_item.active%}active{% endif %}">
<a class="nav-link" href="{{ main_nav_item.url }}">{{ main_nav_item.title }}</a>
</li>
{% endfor %}
{% for external_link in theme_external_links %}
<li class="nav-item">
<a class="nav-link nav-external" href="{{ external_link.url }}">{{ external_link.name }}<i class="fas fa-external-link-alt"></i></a>
</li>
{% endfor %}
{{ get_nav_object("navbar", maxdepth=1, collapse=True, includehidden=True, titles_only=True) }}
</ul>


Expand Down
31 changes: 3 additions & 28 deletions pydata_sphinx_theme/docs-sidebar.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,34 +5,9 @@
{% endif %}

<nav class="bd-links" id="bd-docs-nav" aria-label="Main navigation">

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

<ul class="nav bd-sidenav">
{% for main_nav_item in nav %}
{% if main_nav_item.active %}
{% for nav_item in main_nav_item.children %}
{% if nav_item.children %}

<li class="{% if nav_item.active%}active{% endif %}">
<a href="{{ nav_item.url }}">{{ nav_item.title }}</a>
<ul>
{% for nav_item in nav_item.children %}
<li class="{% if nav_item.active%}active{% endif %}">
<a href="{{ nav_item.url }}">{{ nav_item.title }}</a>
</li>
{% endfor %}
</ul>
</li>
{% else %}
<li class="{% if nav_item.active%}active{% endif %}">
<a href="{{ nav_item.url }}">{{ nav_item.title }}</a>
</li>
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
<ul class="nav bd-sidenav">
{{ get_nav_object("sidebar", maxdepth=4, collapse=True, includehidden=True, titles_only=True) }}
</ul>

</div>
</nav>
13 changes: 1 addition & 12 deletions pydata_sphinx_theme/docs-toc.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,7 @@
{%- endif %}

<nav id="bd-toc-nav">
<ul class="nav section-nav flex-column">
{% for item in page_toc recursive %}
<li class="nav-item toc-entry toc-h{{ loop.depth + 1 }}">
<a href="{{ item.url }}" class="nav-link">{{ item.title }}</a>
{%- if item.children -%}
<ul class="nav section-nav flex-column">
{{ loop(item.children) }}
</ul>
{%- endif %}
</li>
{% endfor %}
</ul>
{{ page_toc }}
</nav>

{% include "edit_this_page.html" %}
2 changes: 1 addition & 1 deletion pydata_sphinx_theme/static/css/index.css

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ def find_version(*file_paths):
include_package_data=True,
# See http://www.sphinx-doc.org/en/stable/theming.html#distribute-your-theme-as-a-python-package
entry_points={"sphinx.html_themes": ["pydata_sphinx_theme = pydata_sphinx_theme"]},
install_requires=["sphinx"],
install_requires=["sphinx", "beautifulsoup4"],
python_requires=">=3.5",
classifiers=[
"Development Status :: 4 - Beta",
Expand Down
42 changes: 19 additions & 23 deletions src/scss/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,12 @@ table.field-list {
}
}

.bd-sidebar .nav > {
.bd-sidebar .nav {
ul {
list-style: none;
padding: 0 1.5rem;
}

li > a {
display: block;
padding: 0.25rem 1.5rem;
Expand All @@ -283,35 +288,26 @@ table.field-list {
}
}

// For page headers with toctrees under them
li.page-header {
> ul {
padding-left: 0;
}

> a {
font-size: 1.2em;
font-weight: 500;
color: black;
}
}

.active {
> a,
&:hover > a {
font-weight: 600;
color: #130654;
}
}

li > ul {
list-style: none;
padding: 0.25rem 1.5rem;

> {
li > a {
display: block;
padding: 0.25rem 1.5rem;
font-size: 0.9em;
color: rgba(0, 0, 0, 0.65);
}

.active {
> a,
&:hover > a {
font-weight: 600;
color: #130654;
}
}
}
}
}

/* adjust toc font sizes to improve overview */
Expand Down

0 comments on commit 7b56b8b

Please sign in to comment.