Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 53 additions & 1 deletion src/sunpy_sphinx_theme/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@
SunPy Sphinx Theme.
"""

import json
import os
from functools import partial
from pathlib import Path
from textwrap import dedent, indent
from urllib.parse import urljoin

from pydata_sphinx_theme import utils
Expand Down Expand Up @@ -154,12 +156,54 @@ def update_html_context(app: Sphinx, pagename: str, templatename: str, context,
context["sst_pathto"] = partial(sst_pathto, context)


def generate_search_config(app):
"""
This function parses the config for the "Documentation" section of the theme config.
"""
theme_config = utils.get_theme_options_dict(app)
search_projects = theme_config.get("rtd_search_projects", None)
if search_projects is None:
navbar_links = theme_config["navbar_links"]
doc_links = next(section[1] for section in navbar_links if section[0] == "Documentation")

def filter_doc_links(links):
out_links = []
for link in links:
if isinstance(link[1], list):
out_links += filter_doc_links(link[1])
elif isinstance(link[1], str) and link[1].startswith("http"):
out_links.append({"name": link[0], "link": link[1]})
else:
err = f"Unable to parse {link} in the nav tree. Try setting search_projects explicitly or fixing navbar_links."
raise ValueError(err)
return out_links

projects = filter_doc_links(doc_links)
Copy link
Preview

Copilot AI Sep 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The variable projects is used but only defined inside the conditional block when search_projects is None. If search_projects is not None, projects will be undefined, causing a NameError when used in the JSON serialization on line 194.

Suggested change
projects = filter_doc_links(doc_links)
projects = filter_doc_links(doc_links)
else:
projects = search_projects

Copilot uses AI. Check for mistakes.


load_more_label = theme_config.get("rtd_search_load_more_label", "Load more results")
no_results_label = theme_config.get("rtd_search_no_results_label", "There are no results for this search")
script = dedent(f"""
const set_search_config = {{
"no-results":{{
"label": "{no_results_label}"
}},
"load-more":{{
"label": "{load_more_label}",
"class": "btn sd-btn sd-bg-primary sd-bg-text-primary"
}},
"projects":{indent(json.dumps(projects, indent=2), " " * 10, predicate=lambda line: line.strip() != "[")}
}};
""")
app.add_js_file(None, body=script)


def setup(app: Sphinx):
# Register theme
theme_dir = get_html_theme_path()
app.add_html_theme("sunpy", theme_dir)
app.add_css_file("sunpy_style.css", priority=600)
app.connect("builder-inited", update_config)
app.connect("builder-inited", update_config, priority=100)
app.connect("builder-inited", generate_search_config, priority=500)
app.connect("html-page-context", update_html_context)
# Conditionally include goat counter js
# We can't do this in update_config as that causes the scripts to be duplicated.
Expand Down Expand Up @@ -198,6 +242,14 @@ def setup(app: Sphinx):
loading_method="async",
)

if theme_options.get("rtd_search", True):
# Add project-wide search
app.add_css_file("css/rtd_enhanced_search.css")
app.add_js_file(
"js/rtd_enhanced_search.js",
loading_method="async",
)

return {
"parallel_read_safe": True,
"parallel_write_safe": True,
Expand Down
2 changes: 1 addition & 1 deletion src/sunpy_sphinx_theme/cards.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ def run(self):


def setup(app: Sphinx):
app.add_css_file("sunpy_cards.css", priority=600)
app.add_css_file("css/sunpy_cards.css", priority=600)
app.add_directive("custom-card", Card)
app.add_node(_Card, html=(visit_card_node, depart_card_node))
return {
Expand Down
168 changes: 168 additions & 0 deletions src/sunpy_sphinx_theme/theme/sunpy/static/css/rtd_enhanced_search.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
#pst-search-dialog[open] {
left: revert;
top: 2rem;
margin-top: 0;
transform: revert;
max-height: calc(100vh - 4rem);
}

#pst-search-dialog[open] form.bd-search {
flex-grow: 0;
}

.readthedocs-search {
background: var(--pst-color-background);
border-radius: 0.25rem;
width: 100%;
display: flex;
flex-direction: column;
overflow: hidden;
}

.readthedocs-search form {
margin: 0.5rem;
border: 0;
}

.readthedocs-search form:focus-within {
outline: 2px solid var(--pst-color-accent);
border: 0;
}

/* .readthedocs-search form.loading input { */
/* } */

.readthedocs-search form.loading .fa-magnifying-glass {
background-image: url("data:image/svg+xml,<svg width='24' height='24' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cstyle%3E.spinner_6kVp%7Btransform-origin:center;animation:spinner_irSm .75s infinite linear%7D@keyframes spinner_irSm%7B100%25%7Btransform:rotate(360deg)%7D%7D%3C/style%3E%3Cpath d='M10.72,19.9a8,8,0,0,1-6.5-9.79A7.77,7.77,0,0,1,10.4,4.16a8,8,0,0,1,9.49,6.52A1.54,1.54,0,0,0,21.38,12h.13a1.37,1.37,0,0,0,1.38-1.54,11,11,0,1,0-12.7,12.39A1.54,1.54,0,0,0,12,21.34h0A1.47,1.47,0,0,0,10.72,19.9Z' class='spinner_6kVp'/%3E%3C/svg>");
background-position: left center;
background-repeat: no-repeat;
background-size: contain;
}

html[data-theme="dark"] .readthedocs-search form.loading .fa-magnifying-glass {
background-image: url("data:image/svg+xml,<svg fill='white' width='24' height='24' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cstyle%3E.spinner_6kVp%7Btransform-origin:center;animation:spinner_irSm .75s infinite linear%7D@keyframes spinner_irSm%7B100%25%7Btransform:rotate(360deg)%7D%7D%3C/style%3E%3Cpath d='M10.72,19.9a8,8,0,0,1-6.5-9.79A7.77,7.77,0,0,1,10.4,4.16a8,8,0,0,1,9.49,6.52A1.54,1.54,0,0,0,21.38,12h.13a1.37,1.37,0,0,0,1.38-1.54,11,11,0,1,0-12.7,12.39A1.54,1.54,0,0,0,12,21.34h0A1.47,1.47,0,0,0,10.72,19.9Z' class='spinner_6kVp'/%3E%3C/svg>");
}

.readthedocs-search form.loading .fa-magnifying-glass path {
display: none;
}

.readthedocs-search .content {
max-height: calc(100% - 110px);
overflow-y: auto;
}

.readthedocs-search .tablist {
position: sticky;
display: flex;
overflow-x: auto;
top: 0;
outline: 0;
background: var(--pst-color-background);
}

.readthedocs-search .tablist button {
display: flex;
border: 0;
border-bottom: max(3px, 0.1875rem, 0.12em) solid transparent;
color: var(--pst-color-text-base);
background-color: var(--pst-color-surface);
padding: 0.5rem 1rem;
align-items: center;
white-space: nowrap;
}

.readthedocs-search .tablist button:focus {
color: var(--sst-lightest-color);
background-color: var(--sst-dark-color);
}

.readthedocs-search .tablist button:focus,
.readthedocs-search .tablist button[aria-selected="true"] {
outline: 0;
border-bottom: max(3px, 0.1875rem, 0.12em) solid var(--pst-color-secondary);
}

.readthedocs-search .tablist button svg {
padding-right: 0.25rem;
}

.readthedocs-search .tablist button .n {
padding-left: 0.25rem;
}

.readthedocs-search .results .results-message > * {
margin: 1rem;
}

.readthedocs-search .results ul {
list-style: none;
padding: 0;
margin: 0;
}

.readthedocs-search .results ul li {
margin: 0;
padding: 1rem;
}

.readthedocs-search .results ul li:focus {
background: var(--pst-color-attention-bg);
outline: 0;
}

.readthedocs-search .results ul li:first-child {
border: 0;
}

/* .readthedocs-search .results ul li > *:last-child { */
/* } */

.readthedocs-search .results ul li a:hover,
.readthedocs-search .results ul li a:focus,
.readthedocs-search .results ul li.selected a {
color: var(--pst-color-text-base);
background-color: var(--sst-footer-background-color);
}

.readthedocs-search .footer {
padding: 10px;
border-top: 1px solid #e1e4e5;
display: flex;
gap: 1em;
justify-content: space-between;
align-items: center;
font-size: 12px;
color: var(--pst-color-text-base);
background-color: var(--sst-footer-background-color);
}

.readthedocs-search .help {
display: flex;
list-style: none;
margin: 0;
padding: 0;
gap: 20px;
}

.readthedocs-search .credits {
display: flex;
align-items: center;
gap: 5px;
margin-left: auto;
text-align: right;
}
.readthedocs-search .credits a {
color: inherit;
}

.readthedocs-search .credits svg {
height: 20px;
}

@media only screen and (max-width: 70rem) {
.readthedocs-search .footer,
.readthedocs-search .help {
display: block;
}
}
Loading